/**
* This is the combat handler.
* Rewritten from the old combat effect/shadow.
* @author Sandoz, 2003.
*/
#define __MESSAGE_DATA_CLASS__
#define __SPECIAL_ATTACK_DATA_CLASS__
#include <combat.h>
#include <player.h>
#include <tasks.h>
#define INFORM 1
// #define DODGE_INFORM 1
// #define PARRY_INFORM 1
// #define AC_INFORM 1
// #define DAMAGE_INFORM 1
/**
* This defines the percentual bonus to dodging that being
* unburdened gives you.
*/
#define DODGE_WEIGHT 60
/**
* This defines the crossover weight for weapons being dodged.
* Weapons lighter than this are harder to dodge, heavier than this easier.
*/
#define DODGE_WEAPON_WEIGHT 30
/**
* This defines the crossover weight for weapons being parried.
* Weapons heavier than this are harder to parry, lighter than this easier.
*/
#define PARRY_WEAPON_WEIGHT 30
/**
* This defines the percentual bonus blocking with a shield should give.
* Twice this bonus is given when the shield is covering the area that
* is being attacked.
*/
#define SHIELD_BLOCK_BONUS 15
/**
* This defines the percentual bonus to defending against a special attack.
*/
#define SPECIAL_DEFENSE_BONUS 15
/**
* This defines the percentual bonus to defending against an attack
* aimed at a specific bodypart.
*/
#define FOCUS_DEFENSE_BONUS 5
/**
* This defines the percentual bonus a level of attitude gives to one's
* attack/defence.
*/
#define MANOEUVRE_ATTITUDE_BONUS 10
/**
* This defines the time taken to perform an action eg. attack, parry or dodge.
*/
#define ACTION_TIME ( ( ROUND_TIME * COMBAT_SPEED ) / 6 )
/** @ignore yes */
#define PARRY_SKILL "fighting.combat.parry.melee"
/** @ignore yes */
#define DODGING_SKILL "fighting.combat.dodging.melee"
/** @ignore yes */
#define UNARMED_SKILL "fighting.combat.special.unarmed"
private void stop_hunting();
protected void create() {
call_out( (: stop_hunting :), 120 );
} /* create() */
/** @ignore yes */
private void stop_hunting() {
call_out( (: stop_hunting :), 120 );
foreach( object ob in named_livings() )
catch( ob->stop_hunting() );
} /* stop_hunting() */
/**
* This method returns a nice description of the creature's attack.
* @param attacker the creature attacking
* @param target the creature being attacker
* @param weapon the weapon used
* @param skill the melee skill used
* @param type the type of the attack
* @param name the name of the attack used
* @param the body area being attacked
*/
class message_data query_attack_desc( object attacker, object target,
mixed weapon, string skill, string type,
string name, string area ) {
class message_data ret;
ret = new( class message_data );
ret->attacker = "You";
ret->others = attacker->one_short();
// Assume it is an unarmed attack.
if( weapon == attacker )
skill = type;
switch( skill ) {
case "blunt" :
switch( name ) {
case "hoof" :
ret->attacker += " kick at ";
ret->others += " kicks at ";
break;
case "hands" :
ret->attacker += " punch at ";
ret->others += " punches at ";
break;
case "feet" :
ret->attacker += " kick at ";
ret->others += " kicks at ";
break;
default :
ret->attacker += " swing at ";
ret->others += " swings at ";
}
break;
case "pierce" :
switch( name ) {
case "bite" :
ret->attacker += " attempt to bite ";
ret->others += " attempts to bite ";
break;
default :
ret->attacker += " thrust at ";
ret->others += " thrusts at ";
}
break;
case "sharp" :
switch( name ) {
case "slash" :
ret->attacker += " slash at ";
ret->others += " slashes at ";
break;
case "slice" :
ret->attacker += " slice at ";
ret->others += " slices at ";
break;
case "chop" :
ret->attacker += " chop at ";
ret->others += " chops at ";
break;
case "claws" :
ret->attacker += " attempt to scratch ";
ret->others += " attempts to scratch ";
break;
case "chew" :
ret->attacker += " attempt to bite ";
ret->others += " attempts to bite ";
break;
default :
ret->attacker += " hack at ";
ret->others += " hacks at ";
}
break;
case "unarmed" :
switch( name ) {
case "hands" :
ret->attacker += " punch at ";
ret->others += " punches at ";
break;
case "feet" :
ret->attacker += " kick at ";
ret->others += " kicks at ";
break;
default :
ret->attacker += " swing at ";
ret->others += " swings at ";
}
break;
default :
ret->attacker += " BROKEN MESSAGE ";
ret->others += " BROKEN MESSAGE ";
}
ret->attacker += target->poss_short()+" "+area;
ret->others += target->poss_short()+" "+area;
if( stringp(weapon) ) {
ret->attacker += " with your "+weapon;
ret->others += " with "+attacker->HIS+" "+weapon;
} else if( weapon != attacker ) {
ret->attacker += " with "+weapon->poss_short();
ret->others += " with "+weapon->poss_short();
}
ret->defender = ret->others;
return ret;
} /* query_attack_desc() */
/** @ignore yes */
void write_messages( int damage, int blocked, mixed stopped_by,
object thing, object attacker, string skill,
string type, string name, object weapon, string area,
class message_data start, int special ) {
int pverbose, tverbose;
string mess;
pverbose = interactive(attacker) && attacker->query_verbose("combat");
tverbose = interactive(thing) && thing->query_verbose("combat");
// These are the messages for missing.
if( !damage && !blocked && thing->query_visible(attacker) ) {
if( !start )
start = query_attack_desc( attacker, thing, weapon, skill, type,
name, area );
if( pverbose )
tell_object( attacker, start->attacker+", but miss "+
thing->HIM+" completely.\n");
if( tverbose )
tell_object( thing, start->defender+", but $V$0=misses,miss$V$ "
"you completely.\n");
thing->event_missed_me(attacker);
event( ENV(thing), "see", start->others+", but misses "+
thing->HIM+" completely.\n", attacker, ({ thing, attacker }) );
return;
}
// These are the messages for armour absorbing 1/3 or more of the blow.
if( blocked && ( blocked > damage / 3 ) ) {
if( !start )
start = query_attack_desc( attacker, thing, weapon, skill, type,
name, area );
mess = ( ( objectp(stopped_by) && query_group( stopped_by ) ) ||
stopped_by == "scales" ? " absorb " : " absorbs " );
if( !damage || blocked == damage )
mess += "all";
else
mess += ( blocked > damage * 2 / 3 ? "most" : "some");
mess += " of ";
if( thing->query_visible(attacker) )
if( pverbose || ( blocked < damage * 2 / 3 ) )
tell_object( attacker, start->attacker+", but "+
( objectp(stopped_by) ? stopped_by->poss_short() :
thing->HIS+" "+stopped_by )+mess+"your blow.\n");
else
tell_object( attacker, start->defender+".\n");
if( tverbose || ( blocked < damage * 2 / 3 ) )
tell_object( thing, start->defender+", but "+
( objectp(stopped_by) ? stopped_by->poss_short() :
"your "+stopped_by )+mess+"the blow.\n");
event( ENV(thing), "see", start->others+", but "+
( objectp(stopped_by) ? stopped_by->poss_short() :
thing->HIS+" "+stopped_by )+mess+"the blow.\n",
attacker, ({ thing, attacker }) );
return;
}
if( special ) {
tell_object( attacker, start->attacker+".\n");
tell_object( thing, start->defender+".\n");
event( ENV(thing), "see", start->others+".\n", attacker,
({ thing, attacker }) );
}
// These are the messages for damage being done.
start = ATTACK_MESS_H->query_message( damage, type, thing, attacker,
name, weapon, area );
tell_object( attacker, start->attacker );
tell_object( thing, start->defender );
event( ENV(thing), "see", start->others, attacker,
({ thing, attacker }) );
} /* write_messages() */
/**
* @ignore yes
* Whether someone gets to parry or dodge is dependant on the value of
* special manoevure. This function determines whether to reset that value
* ie. prevent them parrying/dodging again this round.
* We return a percentual value here.
* The more defensive our attitude is, the more negative the return value.
* @param thing the creature to reset the manoeuvre for
* @param skill the defensive skill used
*/
int do_reset_manoeuvre( object thing, string skill ) {
int repeat_chance, bonus, i;
// Chance is 0-600 dependant on skill.
repeat_chance = thing->query_skill_bonus(skill);
i = thing->query_raw_combat_attitude();
// Now modify that based on what their attitude is.
// A 10% bonus/penalty per attitude level.
bonus = i * MANOEUVRE_ATTITUDE_BONUS;
repeat_chance -= i * 200;
// Make sure they always have a chance of running out.
if( repeat_chance > 550 )
repeat_chance = 550;
// See if they'll get to defend again.
if( repeat_chance < random(600) )
thing->reset_special_manoeuvre();
return bonus;
} /* do_reset_manoeuvre() */
/** @ignore yes */
private void melee_tm( object attacker, object weapon ) {
tell_object( attacker, "%^YELLOW%^"+ replace( ({
"You feel that your skill with $weapon$ has increased.",
"You feel more able to use $weapon$.",
"You seem to be a step closer to mastering $weapon$.",
})[ random( 3 ) ], "$weapon$", ( weapon == attacker ?
"unarmed combat" : weapon->the_short() ) )+"%^RESET%^\n");
} /* melee_tm() */
/**
* This method checks to see if we dodge an attack.
* @param attacker the attacker
* @param victim the victim
* @param wep being attacked with
* @param name the name of the attack used
* @param skill the weapon skill used
* @param damage the amount of damage they take
* @param type the type of damage
* @param area the body area attacked
* @param area_name the name of the body area attacked
* @param mod the percentual modifier for defending against the attack
* @param mess the attack message associated with the attack
* @return 1 upon success, 0 upon failure
*/
int dodge_attack( object attacker, object victim, object wep, string name,
string skill, int damage, string type, string area,
string area_name, int mod, class message_data mess ) {
int pverbose, tverbose, i;
#ifdef TIMING
if( !victim->check_time_left( 0 ) || !attacker->query_visible(victim) ) {
#else
if( !victim->query_special_manoeuvre() || victim->queue_commands() ||
!attacker->query_visible(victim) ) {
#endif
#ifdef DODGE_INFORM
event( filter( INV(ENV(attacker)), (: $1->query_creator() :) ),
"inform", victim->query_name()+" not dodging this attack, "
"Special: "+!victim->query_special_manoeuvre()+" Time left: "+
victim->query_time_left()+" Queue: "+
victim->queue_commands()+" Visible: "+
!attacker->query_visible(victim), "combat");
#endif
return 0;
}
pverbose = interactive(attacker) && attacker->query_verbose("combat");
tverbose = interactive(victim) && victim->query_verbose("combat");
mod += do_reset_manoeuvre( victim, DODGING_SKILL);
mod += attacker->query_raw_combat_attitude() * MANOEUVRE_ATTITUDE_BONUS;
// A possible bonus ranging from -DODGE_WEIGHT/2 to DODGE_WEIGHT/2
mod += DODGE_WEIGHT / 2;
mod -= DODGE_WEIGHT - ( DODGE_WEIGHT * victim->query_loc_weight() ) /
( victim->query_max_weight() + 1 );
i = wep->query_weight();
// Unarmed.
if( wep == attacker )
i /= 40;
// A bonus to dodging large weapons, and penalty for lighter ones.
mod += ( DODGE_WEAPON_WEIGHT - i ) / 4;
if( !victim->query_visible(attacker) )
mod += 50;
#ifdef DODGE_INFORM
event( filter( INV(ENV(attacker)), (: $1->query_creator() :) ),
"inform", victim->query_name()+" (attitude: "+
victim->query_combat_attitude()+", bonus: "+
victim->query_skill_bonus(DODGING_SKILL)+") attempting to dodge "+
attacker->query_name()+" (attitude: "+
attacker->query_combat_attitude()+", bonus: "+
attacker->query_skill_bonus(MELEE+skill)+"), modifier: "+
mod+", special: "+special, "combat");
#endif
mod = TASKER->compare_skills( attacker, MELEE+skill, victim,
DODGING_SKILL, mod, TM_FREE, TM_FREE );
switch( mod ) {
case OFFAWARD :
melee_tm( attacker, wep );
case OFFWIN :
// If you fail your dodge you have a 66% chance of not being able
// to dodge again this round.
if( random( 3 ) )
victim->reset_special_manoeuvre();
return 0;
case DEFAWARD :
tell_object( victim, "%^YELLOW%^"+ replace( ({"You move more "
"nimbly than you thought you could in dodging $attacker$",
"You managed to predict $attacker$'s attack, letting you "
"dodge it more easily", "You feel better at dodging as you "
"avoid $attacker$'s attack" })[ random( 3 ) ], "$attacker$",
attacker->the_short() ) +".%^RESET%^\n" );
case DEFWIN :
if( !mess )
mess = query_attack_desc( attacker, victim, wep, skill, type,
name, area_name );
if( pverbose )
tell_object( attacker, mess->attacker+", but "+
victim->HE+" dodges out of the way.\n");
if( tverbose )
tell_object( victim, mess->defender+", but you dodge out of "
"the way.\n");
event( ENV(victim), "see", mess->others+", but "+victim->HE+" dodges "
"out of the way.\n", victim, ({ attacker, victim }) );
return 1;
}
} /* dodge_attack() */
/**
* This method checks to see if they can parry with their bare hands.
* @param attacker the attacker
* @param victim the victim
* @param wep being attacked with
* @param name the name of the attack used
* @param skill the weapon skill used
* @param damage the amount of damage they take
* @param type the type of damage
* @param area the body area attacked
* @param area_name the name of the body area attacked
* @param mod the percentual modifier for defending against the attack
* @param mess the attack message associated with the attack
* @return always return 1
*/
protected int unarmed_parry( object attacker, object victim, object wep,
string name, string skill, int damage,
string type, string area, string area_name,
int mod, class message_data mess ) {
int i, ac, pverbose, tverbose;
string part, verb, what;
pverbose = interactive(attacker) && attacker->query_verbose("combat");
tverbose = interactive(victim) && victim->query_verbose("combat");
mod += do_reset_manoeuvre( victim, UNARMED_SKILL );
mod += attacker->query_raw_combat_attitude() * MANOEUVRE_ATTITUDE_BONUS;
i = wep->query_weight();
// Unarmed.
if( wep == attacker )
i /= 40;
// A small bonus to parrying small weapons, and penalty for heavier ones.
mod -= ( PARRY_WEAPON_WEIGHT - i ) / 2;
if( !victim->query_visible(attacker) )
mod += 50;
// Take into account the relative weights of the weapon being attacked
// with, and us - the parrier.
if( wep->query_weapon() == 1 )
mod += ( wep->query_weight() - ( victim->query_weight() / 20 ) ) / 4;
#ifdef PARRY_INFORM
event( filter( INV(ENV(attacker)), (: $1->query_creator() :) ),
"inform", victim->query_name()+" (attitude: "+
victim->query_combat_attitude()+", bonus: "+
victim->query_skill_bonus(UNARMED_SKILL)+") attempting "
"to parry unarmed "+attacker->query_name()+" (attitude: "+
attacker->query_combat_attitude()+", bonus: "+
attacker->query_skill_bonus(MELEE+skill)+", weapon: "+
wep->a_short()+", weight: "+wep->query_weight()+"), modifier: "+
mod+", special: "+special, "combat");
#endif
if( !mess )
mess = query_attack_desc( attacker, victim, wep, skill, type, name,
area_name );
mod = TASKER->compare_skills( attacker, MELEE+skill, victim,
UNARMED_SKILL, mod, TM_FREE, TM_FREE );
switch( mod ) {
case OFFAWARD :
melee_tm( attacker, wep );
case OFFWIN :
// If you fail your parry you have a 66% chance of not being able
// to defend again this round.
if( random( 3 ) )
victim->reset_special_manoeuvre();
part = random( 4 ) ? "arm" : "hand";
switch( type ) {
case "blunt" :
verb = "smashes";
break;
case "pierce" :
verb = "spears";
break;
case "sharp" :
verb = "slashes";
break;
default :
verb = "blasts";
}
if( wep == attacker )
what = attacker->poss_short()+" attack";
else
what = wep->poss_short();
tell_object( attacker, mess->attacker+", but "+
victim->the_short()+" moves "+victim->HIS+" "+part+" in "
"anticipation and "+what+" "+verb+" it.\n");
tell_object( victim, mess->defender+", but you move your "+part+" in "
"anticipation and "+what+" "+verb+" it.\n");
event( ENV(victim), "see", mess->others+", but "+
victim->the_short()+" moves "+victim->HIS+" "+part+" in "
"anticipation and "+what+" "+verb+" it.\n",
victim, ({ attacker, victim }) );
ac = victim->query_ac( type, damage, part+"s");
wep->hit_weapon( ac, type );
damage -= ac;
if( damage > 0 ) {
victim->adjust_hp( -damage, attacker );
if( victim->query_monitor() )
HEALTH_H->register_monitor( victim, 0 );
}
return 1;
case DEFAWARD :
tell_object( victim, "%^YELLOW%^"+ replace( ({"You move more "
"accurately than you thought you could in deflecting "
"$attacker$'s attack", "You just manage to deflect "
"$attacker$'s attack, but you'll know better next time",
"You feel better at parrying unarmed as you deflect "
"$attacker$'s attack"})[random( 3 )], "$attacker$",
attacker->the_short() ) +".%^RESET%^\n");
case DEFWIN :
part = ( area == "hands" ? "" : " with one hand");
if( pverbose )
tell_object( attacker, mess->attacker+", but "+
victim->HE+" deflects the attack"+part+".\n");
if( tverbose )
tell_object( victim, mess->defender+", but you deflect "+
"the attack"+part+".\n");
event( ENV(victim), "see", mess->others+", but "+
victim->HE+" deflects the attack"+part+".\n",
victim, ({ attacker, victim }) );
return 1;
}
} /* unarmed_parry() */
/**
* This method checks to see if they can parry with their weapon.
* @param attacker the attacker
* @param victim the victim
* @param wep being attacked with
* @param name the name of the attack used
* @param skill the weapon skill used
* @param damage the amount of damage they take
* @param type the type of damage
* @param area the body area attacked
* @param area_name the name of the body area attacked
* @param mod the percentual modifier for defending against the attack
* @param mess the attack message associated with the attack
* @return 1 upon success, 0 upon failure
*/
int parry_attack( object attacker, object victim, object wep, string name,
string skill, int damage, string type, string area,
string area_name, int mod, class message_data mess ) {
int i, pverbose, tverbose;
string hand, parrying, *hands, *verb;
object *things, with;
#ifdef TIMING
if( !victim->check_time_left( 0 ) || !attacker->query_visible(victim) ) {
#else
if( !victim->query_special_manoeuvre() || victim->queue_commands() ||
!attacker->query_visible(victim) ) {
#endif
#ifdef PARRY_INFORM
event( filter( INV(ENV(attacker)), (: $1->query_creator() :) ),
"inform", victim->query_name()+" not parrying this attack, "
"Special: "+!victim->query_special_manoeuvre()+" Time left: "+
victim->query_time_left()+" Queue: "+
victim->queue_commands()+" Visible: "+
!attacker->query_visible(victim), "combat");
#endif
return 0;
}
things = victim->query_holding();
switch( hand = victim->query_combat_parry() ) {
case "all" :
case "both" :
break;
default :
i = sizeof( hands = victim->query_limbs() );
while( i-- ) {
if( hands[ i ] != hand+" hand") {
hands = delete( hands, i, 1 );
things = delete( things, i, 1 );
}
}
}
if( !sizeof( things -= ({ 0 }) ) ) {
if( victim->query_unarmed_parry() )
return unarmed_parry( attacker, victim, wep, name, skill,
damage, type, area, area_name, mod, mess );
else
return 0;
}
pverbose = interactive(attacker) && attacker->query_verbose("combat");
tverbose = interactive(victim) && victim->query_verbose("combat");
mod += do_reset_manoeuvre( victim, PARRY_SKILL );
mod += attacker->query_raw_combat_attitude() * MANOEUVRE_ATTITUDE_BONUS;
i = wep->query_weight();
// Unarmed.
if( wep == attacker )
i /= 40;
// A bonus to parrying small weapons, and penalty for heavier ones.
mod -= ( PARRY_WEAPON_WEIGHT - i ) / 4;
if( !victim->query_visible(attacker) )
mod += 50;
with = choice( things );
// Let us check for a shield.
if( with->query_shield() ) {
mod += with->query_weight() / ( victim->query_str() + 1 );
// A bonus for blocking with a shield.
mod -= SHIELD_BLOCK_BONUS;
// If the shield is covering the area being attacked,
// it should be very easy to block.
if( area && member_array( area, CLOTHING_H->query_zone_names(
with->query_type() ) ) != -1 ) {
mod -= SHIELD_BLOCK_BONUS;
// This signifies that we let the shield absorb the blow,
// instead of us moving the shield to block.
i = 1;
}
} else {
mod += 2 * with->query_weight() / ( victim->query_str() + 1 );
}
// Take into account the relative weights of the two weapons.
if( wep->query_weapon() == 1 )
mod += ( wep->query_weight() - with->query_weight() ) / 4;
#ifdef PARRY_INFORM
event( filter( INV(ENV(attacker)), (: $1->query_creator() :) ),
"inform", victim->query_name()+" (attitude: "+
victim->query_combat_attitude()+", bonus: "+
victim->query_skill_bonus(PARRY_SKILL)+", using: "+
with->a_short()+", weight: "+with->query_weight()+") attempting "
"to parry "+attacker->query_name()+" (attitude: "+
attacker->query_combat_attitude()+", bonus: "+
attacker->query_skill_bonus(MELEE+skill)+", weapon: "+
wep->a_short()+", weight: "+wep->query_weight()+"), modifier: "+
mod+", special: "+special, "combat");
#endif
verb = ( with->query_weapon() ?
({"parry", "parries", "parrying"}) :
({"block", "blocks", "blocking"}) );
mod = TASKER->compare_skills( attacker, MELEE+skill, victim,
PARRY_SKILL, mod, TM_FREE, TM_FREE );
switch( mod ) {
case OFFAWARD :
melee_tm( attacker, wep );
case OFFWIN :
// If you fail your parry you have a 66% chance of not being able
// to defend again this round.
if( random( 3 ) )
victim->reset_special_manoeuvre();
return 0;
case DEFAWARD :
tell_object( victim, "%^YELLOW%^"+ replace( ({"You move more "
"surely than you thought you could in $verbing$ $attacker$'s "
"attack", "You just manage to $verb$ $attacker$'s attack, "
"but you'll know better next time", "You feel better at "
"$verbing$ as you deflect $attacker$'s attack"})[random( 3 )],
({"$verb$", verb[ 0 ], "$verbing$", verb[ 2 ], "$attacker$",
attacker->the_short() }) )+".%^RESET%^\n");
case DEFWIN :
if( !mess )
mess = query_attack_desc( attacker, victim, wep, skill, type,
name, area_name );
parrying = with->poss_short();
if( pverbose )
tell_object( attacker, mess->attacker+", but "+victim->HE+
( i == 1 ? " effortlessly " : " ")+verb[ 1 ]+" the "
"attack with "+parrying+".\n");
if( tverbose )
tell_object( victim, mess->defender+", but you "+( i == 1 ?
"effortlessly " : "")+verb[ 0 ]+" the attack with "+
parrying+".\n");
event( ENV(victim), "see", mess->others+", but "+victim->HE+
( i == 1 ? " effortlessly " : " ")+verb[ 1 ]+" the attack "
"with "+parrying+".\n", victim, ({ attacker, victim }) );
if( with->query_weapon() )
with->hit_weapon( damage, type );
else
with->do_damage( type, damage );
wep->hit_weapon( damage, type );
victim->successful_parry( attacker, with, damage );
return 1;
}
} /* parry_attack() */
/** @ignore yes */
void clear_protection( object attacker, object victim ) {
if( member_array( victim, attacker->query_protectors() ) != -1 ) {
tell_object( victim, "You stop protecting "+
attacker->the_short()+" as "+attacker->HE+" moves to "
"attack you!\n");
tell_object( attacker, "You move to attack "+
victim->the_short()+", forfeiting "+victim->HIS+" protection.\n");
tell_room( ENV(attacker), "In return for being attacked, "+
victim->the_short()+" stops protecting "+
attacker->the_short()+".\n", ({ attacker, victim }) );
attacker->remove_protector( victim );
}
} /* clear_protection() */
/** @ignore yes */
void combat_actions( object player, object target ) {
int i, chance;
object thing, *things;
mixed actions;
if( !player || !target )
return;
thing = ENV(player);
if( thing != ENV( target ) )
return;
things = filter( INV(thing), (: living($1) == !userp($1) :) );
foreach( thing in things ) {
actions = thing->query_combat_actions();
if( sizeof( actions ) < 2 )
continue;
chance = random( actions[ 0 ] );
i = 1;
while( chance > -1 ) {
if( chance < actions[ i ] )
call_out("combat_action", 1, player, target, thing,
actions[ i + 2 ] );
chance -= actions[ i ];
i += 3;
}
}
} /* combat_actions() */
/** @ignore yes */
void combat_action( object player, object target, object thing, mixed action ) {
object place;
if( !target || !thing )
return;
if( !place = ENV(player) )
return;
if( place != ENV(target) || place != ENV(thing) )
return;
if( stringp(action) ) {
thing->do_command( action );
return;
}
if( functionp(action) )
evaluate( action, player, target );
if( pointerp(action) && sizeof(action) == 1 && stringp( action[ 0 ] ) ) {
call_other( thing, action[ 0 ], player, target );
return;
}
if( pointerp(action) && sizeof(action) == 2 )
call_other( action[ 0 ], action[ 1 ], thing, player, target );
} /* combat_action() */
/** @ignore yes */
int level_out( int number ) {
int i, levelled;
while( number && i++ < 10 ) {
if( number < 100 ) {
levelled += ( number * ( 11 - i ) ) / 10;
number = 0;
} else {
levelled += 10 * ( 11 - i );
number -= 100;
}
}
return levelled;
} /* level_out() */
/**
* @ignore yes
* Flag means to ignore attitude and darkness etc.
*/
int calc_attack_percentage( object attacker, object *weapons,
object *holding, int flag ) {
int perc, weight, dex, str;
if( !ENV(attacker) )
return 0;
dex = attacker->query_dex();
str = attacker->query_str();
perc = 100;
// Start out at 70-130, depending on your attitude.
if( !flag )
perc += attacker->query_raw_combat_attitude() * 15;
// The fatter you are, the less you attack.
// This should be a penalty of about 25-40.
perc -= attacker->query_weight() / 2 / ( str + dex );
// Unarmed percentage.
if( !sizeof(weapons) ) {
// A bonus of about 20-40.
perc += ( str + dex ) * ( 1 + attacker->query_free_limbs() ) / 2;
} else {
// Calculate the total weight of weapons.
foreach( object thing in weapons )
weight += thing->query_weight();
// A penalty or bonus based on the weight of weapons used
// and our strength.
perc += str - ( weight * 5 ) / str;
if( !flag ) {
// Extra bonus based on strength for two-handed weapons.
if( sizeof(weapons) == 1 && weapons[0]->query_no_limbs() > 1 )
perc += str / 2;
// A penalty based on dexterity for using multiple weapons.
// A penalty of 40 with 3 dex, 11 with 21 dex, etc.
if( sizeof(weapons) > 1 )
perc -= 40 - ( dex * dex ) / 15;
// A little bonus for fighting with one one-handed weapon only.
if( sizeof(weapons) == 1 && sizeof(holding) == 1 &&
attacker->query_free_limbs() )
perc += ( str + dex ) / 4;
}
}
// A bonus of up to +/- 10 for being unburdened.
perc -= 10 - ( 20 * attacker->query_loc_weight() ) /
( attacker->query_max_weight() + 1 );
if( perc < 1 )
return 0;
if( flag )
return perc;
switch( attacker->check_dark( ENV(attacker)->query_light() ) ) {
case -2 :
case 2 :
return perc / 4;
case -1 :
case 1 :
return perc / 2;
default :
return perc;
}
} /* calc_attack_percentage() */
/**
* This method figures out all the attacks by looking at the weapon and
* basic attacks on the npc/player.
* @param attacker the attacker
* @param target who we are attacking
* @return ([ weapon : attacks ])
*/
private mapping calc_attacks( object attacker, object target ) {
int perc;
object *holding, *weapons, ob;
mapping attacks;
#ifdef INFORM
int limbs = attacker->query_free_limbs();
#endif
holding = attacker->query_holding() - ({ 0 });
weapons = attacker->query_weapons();
// Whip through everything being held that is not a weapon and do
// a little damage to them. Non-weapon things should have fairly
// low conditions.
foreach( ob in holding ) {
if( !ob->query_weapon() ) {
ob->do_damage("crush", 10 + random(50) );
holding -= ({ ob });
}
}
perc = calc_attack_percentage( attacker, weapons, holding, 0 );
attacks = ([ ]);
if( !sizeof(weapons) )
attacks[attacker] = attacker->weapon_attacks( perc, target )[0..3];
else
foreach( ob in weapons )
attacks[ob] = ob->weapon_attacks( perc, target )[0..3];
#ifdef INFORM
event( filter( INV(ENV(attacker)), (: $1->query_creator() :) ), "inform",
attacker->query_name()+" attacking with "+( sizeof(weapons) ?
query_multiple_short(weapons) : query_num( limbs )+" free limb"+
( limbs != 1 ? "s" : "") )+", perc "+perc, "combat");
#endif
return attacks;
} /* calc_attacks() */
/** @ignore yes */
void attack_round( object attacker ) {
int back, bonus, damage, weapon_damage, riding, parry;
object weapon, thing, *things;
mapping attacks, areas;
mixed special, tmp;
// Player cannot fight while passed out,
// queuing commands, without environment or net-dead.
if( attacker->query_property( PASSED_OUT ) || attacker->dont_attack_me() ||
!attacker->check_time_left( 1 ) || !ENV(attacker) ||
( userp(attacker) && !interactive(attacker) ) ) {
#ifdef INFORM
event( filter( INV(ENV(attacker)), (: $1->query_creator() :) ),
"inform", attacker->query_name()+" not attacking this round.",
"combat");
#endif
return;
}
attacker->set_special_manoeuvre();
// Choose a target to attack for this round.
if( sizeof( things = attacker->query_attackables() ) ) {
if( !( thing = attacker->query_concentrating() ) ||
member_array( thing, things ) == -1 )
thing = choice( things );
} else {
// Maybe the last attacker is here, but invisible.
if( !thing = attacker->query_attacker() )
return;
attacker->set_attacker( 0 );
if( thing->query_visible( attacker ) )
return;
if( random(100) < attacker->query_wimpy() ) {
attacker->run_away();
return;
}
tell_object( attacker, "You swing wildly, attempting to hit your "+
"invisible opponent.\n" );
event( ENV(attacker), "see", attacker->one_short()+" $V$0=swings,"
"swing$V$ wildly.\n", attacker, attacker );
return;
}
attacker->remove_hide_invis("hiding");
if( thing->query_visible(attacker) )
thing->remove_hide_invis("hiding");
// Cancel protection if the player attacks a protector.
clear_protection( attacker, thing );
// If attack fails, assume a message is given already.
if( !thing->attack_by( attacker ) )
return;
// Pass out the relevant information about the attack.
attacker->fight_in_progress( thing );
attacker->set_target( thing );
// Check for tactical specials first.
if( functionp( special = attacker->special_attack( thing, 0 ) ) ) {
// Assume that the arguments are already set by the
// special attack itself.
// Only return if the special returns 1, otherwise continue,
// as we may have had bad arguments, and the special failed.
if( evaluate( special ) )
return;
}
attacks = calc_attacks( attacker, thing );
// Check out what bodyparts they have.
areas = thing->query_attackable_areas();
// Figure out any +s/-s from them being mounted.
if( ENV(attacker)->query_transport() && ENV(thing)->query_transport() ) {
// Both riding, no penalties.
} else if( ENV(thing)->query_transport() ) {
// They are riding... So we get some penalties.
riding = -1;
} else if( ENV(attacker)->query_transport() ) {
riding = 1;
}
// Make one attack at a time on the target.
foreach( weapon, tmp in attacks ) {
string area, response;
int success, mod;
mixed focus;
if( !weapon || !sizeof(tmp) )
continue;
attacker->set_weapon( weapon );
if( !thing || ENV(thing) != ENV(attacker) )
break;
area = 0;
damage = tmp[ 0 ];
bonus = attacker->query_skill_bonus( MELEE+tmp[ 1 ] );
bonus += 40 * attacker->query_raw_combat_attitude();
// Give people some +ve and -ve changes due to being on a horse.
if( riding )
bonus += riding * ( riding < 0 ? 100 : 50 );
if( bonus > 0 ) {
back = ACTION_TIME - ( bonus / 50 );
if( back > 0 )
attacker->adjust_time_left( - back );
} else {
attacker->adjust_time_left( -ACTION_TIME );
}
weapon_damage = damage;
// Make the actual damage a weighted average of their skill &
// the weapon damage. Cap it at Nx the weapon damage so
// you don't get Cohen killing someone with a toothpick.
if( bonus < 1 )
bonus = 1;
if( weapon != attacker )
damage = to_int( sqrt( to_float( damage * bonus ) ) );
if( damage > 3 * weapon_damage )
damage = 3 * weapon_damage;
back = damage;
if( weapon != attacker )
back += weapon->query_weight();
if( ( back = level_out( back ) ) < 0 )
back = 0;
#ifdef INFORM
event( filter( INV(ENV(attacker)), (: $1->query_creator() :) ),
"inform", attacker->query_name()+" attacking with "+
weapon->query_short()+" - base damage "+weapon_damage+" with "
"bonus "+bonus+" for a total of "+damage+" damage", "combat");
event( filter( INV(ENV(attacker)), (: $1->query_creator() :) ),
"inform", attacker->query_name()+" using "+
weapon->short()+" at "+back +" difficulty and "+bonus+" bonus",
"combat");
#endif
// Test to see if the player can use this attack.
switch( TASKER->attempt_task( back, 25 + bonus, 50 ) ) {
case AWARD :
case SUCCEED :
success = 1;
break;
default :
success = 0;
}
special = attacker->special_attack( thing, weapon );
if( functionp( special ) )
special = evaluate( special, attacker, thing, weapon );
if( classp( special ) ) {
// Make specials slightly easier to defend against.
mod = special->hit_bonus - SPECIAL_DEFENSE_BONUS;
parry = !special->undefendable;
tmp = special->attacks;
damage = tmp[0];
focus = special->focus || attacker->query_combat_focus();
} else {
mod = 0;
parry = 1;
focus = attacker->query_combat_focus();
}
if( !success ) {
back += to_int( 3 * sqrt( to_float( back ) ) ) - bonus;
if( back > 0 )
damage -= 2 * back;
else
damage = 0;
if( damage < 1 )
damage = 0;
}
if( pointerp( focus ) ) {
if( sizeof( focus &= keys(areas) ) )
area = choice( focus );
} else if( stringp( focus ) && !undefinedp( areas[focus] ) ) {
area = focus;
}
if( !area ) {
area = choice( keys(areas) );
} else {
// Make focused attacks slightly easier to defend against.
mod -= FOCUS_DEFENSE_BONUS;
}
if( !damage ) {
// We missed, print the relevant messages.
write_messages( damage, 0, 0, thing, attacker, tmp[1], tmp[2],
tmp[3], weapon, choice( areas[area] ),
classp(special) ? special->messages : 0, classp(special) );
if( thing->query_visible(attacker) )
continue;
else
break;
}
// Test to see if the target has magical protection.
if( thing->block_attack( attacker, weapon, MELEE+tmp[1],
damage, mod ) ) {
if( thing->query_visible(attacker) )
continue;
else
break;
} else if( parry ) {
if( ( response = thing->query_combat_response() ) == "neutral")
response = random( 2 ) ? "dodge" : "parry";
if( !sizeof( thing->query_holding() - ({ 0 }) ) &&
!thing->query_unarmed_parry() )
response = "dodge";
switch( response ) {
case "dodge" :
// Test to see if the target dodges.
parry = dodge_attack( attacker, thing, weapon, tmp[3], tmp[1],
damage, tmp[2], area, choice( areas[area] ), mod,
classp(special) ? special->messages : 0 );
break;
case "parry" :
// Test to see if the target parries.
parry = parry_attack( attacker, thing, weapon, tmp[3], tmp[1],
damage, tmp[2], area, choice( areas[area] ), mod,
classp(special) ? special->messages : 0 );
break;
default :
error( sprintf("%O using an invalid response %O.\n",
thing, response ) );
}
// We were dodged or parried.
if( parry ) {
if( thing->query_visible(attacker) )
continue;
else
break;
}
}
// See if a protector moves to accept the blow for the target.
thing = thing->check_protection(attacker);
// Subtract the target's armour class from the damage.
back = thing->query_ac( tmp[2], damage, area );
#ifdef AC_INFORM
event( filter( INV(ENV(attacker)), (: $1->query_creator() :) ),
"inform", thing->query_name()+" has AC of "+back+" on "+
area+" for "+tmp[2], "combat");
#endif
#ifdef DAMAGE_INFORM
event( filter( INV(ENV(attacker)), (: $1->query_creator() :) ),
"inform", attacker->query_name()+"'s damage: "+
damage+", AC: "+back+" ("+area+"), hitting "+
thing->query_name()+" for: "+( damage - back ), "combat");
#endif
weapon->hit_weapon( back, tmp[2] );
// Do the damage, if there is any, and print relevant messages.
write_messages( damage, back, thing->query_stopped(), thing,
attacker, tmp[1], tmp[2], tmp[3], weapon, choice( areas[area] ),
classp(special) ? special->messages : 0, classp(special) );
// Do the damage, if any got past through our armour.
if( damage > back ) {
damage -= back;
thing->adjust_hp( -damage, attacker, weapon, tmp[3] );
if( thing->query_monitor() )
HEALTH_H->register_monitor( thing, 0 );
}
if( weapon )
weapon->attack_function( tmp[3], damage, thing, attacker );
if( !thing->query_visible(attacker) )
break;
}
// Check for point monitoring.
if( attacker->query_monitor() )
HEALTH_H->register_monitor( attacker, 1 );
if( !userp( attacker ) )
combat_actions( attacker, thing );
} /* attack_round() */