/**
* This is the combat handler.
* Rewritten from the old combat effect/shadow.
* @author Sandoz, 2003.
*/
#define __ATTACK_DATA_CLASS__
#define __SPECIAL_ATTACK_DATA__
#include <combat.h>
#include <player.h>
#include <tasks.h>
// #define TIMING_INFORM
// #define ATTACK_INFORM
// #define DODGE_INFORM
// #define PARRY_INFORM
#define TM_INFORM
// #define AC_INFORM
// #define DAMAGE_INFORM
/**
* This defines the percentual penalty to different actions when being hurt.
*/
#define HP_MOD 10
/**
* This defines the percentual bonus to dodging that being
* unburdened gives you.
*/
#define DODGE_WEIGHT 30
/**
* 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 40
/**
* This defines the percentual bonus to parrying that being
* unburdened gives you.
*/
#define PARRY_WEIGHT 10
/**
* 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 40
/**
* 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 5
/**
* 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
* defense.
*/
#define DEF_MANOEUVRE_ATT_BONUS 10
/**
* This defines the percentual bonus a level of attitude gives to one's
* attack.
*/
#define OFF_MANOEUVRE_ATT_BONUS 5
/**
* This defines the time taken to perform an action eg. attack, parry or dodge.
*/
#define ACTION_TIME (ROUND_TIME / 2)
/** @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 int extra_id;
private class attack_data *extra_attacks;
private void stop_hunting();
private void flush_extra_attacks();
void attack_round( object attacker, int extra,
class attack_data attack,
class special_attack_data sp );
private void do_inform( object who, string fmt, mixed *args ... ) {
event( filter( INV(ENV(who)), (: $1->query_creator() :) ), "inform",
( sizeof(args) ? sprintf( fmt, args ... ) : fmt ), "combat");
} /* log_file() */
protected void create() {
extra_attacks = ({ });
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;
int hilt;
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 ";
weapon = "hooves";
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;
case "beak" :
ret->attacker += " thrust at ";
ret->others += " thrusts at ";
weapon = "beak";
break;
case "horns" :
ret->attacker += " thrust at ";
ret->others += " thrusts at ";
weapon = "horns";
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 += " hack at ";
ret->others += " hacks at ";
weapon = "claws";
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;
case "bash" :
ret->attacker += " swing at ";
ret->others += " swings at ";
weapon = " with the "+
weapon->query_hilt()+" of "+weapon->poss_short();
hilt = 1;
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( hilt ) {
ret->attacker += weapon;
ret->others += weapon;
} else 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 blocked, mixed stopped_by, class attack_data att ) {
object off, def, wep;
int pverbose, tverbose, damage, actual;
class message_data messages;
off = att->attacker;
def = att->target;
wep = att->weapon;
messages = att->messages;
damage = att->final_damage;
pverbose = interactive(off) && off->query_verbose("combat");
tverbose = interactive(def) && def->query_verbose("combat");
// These are the messages for missing.
if( !damage && !blocked && def->query_visible(off) ) {
if( !messages )
messages = query_attack_desc( off, def, wep, att->skill, att->type,
att->name, att->area );
if( pverbose )
tell_object( off, messages->attacker+", but miss "+
def->HIM+" completely.\n");
if( tverbose )
tell_object( def, messages->defender+", but $V$0=misses,miss$V$ "
"you completely.\n");
def->event_missed_me(off);
event( ENV(off), "see", messages->others+", but misses "+
def->HIM+" completely.\n", off, ({ off, def }) );
return;
}
// We were dodged, parried or magically blocked.
if( !att->success ) {
if( pverbose )
tell_object( off, messages->attacker );
if( tverbose )
tell_object( def, messages->defender );
event( ENV(off), "see", messages->others, off, ({ off, def }) );
return;
}
// Actual damage done.
if( damage > blocked )
actual = damage - blocked;
else
actual = 0;
// These are the messages for damage being done.
if( actual ) {
class message_data tmp;
tmp = ATTACK_MESS_H->query_message( actual * COMBAT_DAMAGE,
att->type, def, off, att->name, wep, att->area );
if( messages ) {
messages->attacker += ". $C$"+tmp->attacker;
messages->defender += ". $C$"+tmp->defender;
messages->others += ". $C$"+tmp->others;
} else {
messages = tmp;
}
}
// These are the messages for armour absorbing 1/3 or more of the blow.
if( blocked && ( blocked > damage / 3 ) ) {
string mess, bit;
if( !messages )
messages = query_attack_desc( off, def, wep, att->skill, att->type,
att->name, att->area );
if( !actual ) {
mess = ( ( objectp(stopped_by) && query_group(stopped_by) ) ||
stopped_by == "scales" ? " absorb " : " absorbs ");
mess += "all";
bit = ", but ";
} else {
mess = " absorbing ";
mess += ( blocked > damage * 2 / 3 ? "most" : "some");
bit = " despite ";
}
mess += " of ";
if( def->query_visible(off) ) {
if( pverbose || ( blocked < damage * 2 / 3 ) )
tell_object( off, messages->attacker + bit +
( objectp(stopped_by) ? stopped_by->poss_short() :
def->HIS+" "+stopped_by ) + mess + "your blow.\n");
} else {
tell_object( off, messages->attacker+".\n");
}
if( tverbose || ( blocked < damage * 2 / 3 ) )
tell_object( def, messages->defender + bit +
( objectp(stopped_by) ? stopped_by->poss_short() :
"your "+stopped_by ) + mess + "the blow.\n");
event( ENV(off), "see", messages->others + bit +
( objectp(stopped_by) ? stopped_by->poss_short() :
def->HIS+" "+stopped_by ) + mess + "the blow.\n",
off, ({ off, def }) );
return;
}
tell_object( off, messages->attacker+".\n");
tell_object( def, messages->defender+".\n");
event( ENV(off), "see", messages->others+".\n", off, ({ off, def }) );
} /* 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, skill_bonus, bonus, i, time;
// Chance is 0-800 dependant on skill.
repeat_chance = skill_bonus = 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 * DEF_MANOEUVRE_ATT_BONUS;
repeat_chance -= i * 200;
// Make sure they always have a chance of running out.
if( repeat_chance > 750 )
repeat_chance = 750;
// See if they'll get to defend again.
if( repeat_chance < random(800) )
thing->reset_special_manoeuvre();
// Let's give a bonus to time taken based on attitude as well.
skill_bonus -= i * 50;
if( skill_bonus ) {
time = ACTION_TIME / 2 - ( skill_bonus / 50 );
if( time < 2 )
time = 2;
} else {
time = ACTION_TIME / 2;
}
thing->adjust_time_left( -time );
#ifdef TIMING_INFORM
do_inform( thing, "TIMING: %s - adjusting time by: %i",
thing->query_name(), ACTION_TIME / 2 );
#endif
return bonus;
} /* do_reset_manoeuvre() */
/**
* This method checks to see if the player has time to perform the action.
* @param ob the object to check for time
* @param off this is an offensive move
* @return 1 if they do, 0 if they do not
*/
int check_time_left( object ob, int off ) {
int time_left, action_time;
// If a player has a special prepared and is trying to defend they don't.
if( !ob->query_special_manoeuvre() && !off )
return 0;
// NPCs don't have time.
if( !interactive(ob) )
return 1;
if( ob->queue_commands() )
return 0;
time_left = ob->query_time_left();
action_time = ACTION_TIME;
// If they have some time left.
if( time_left >= action_time )
return 1;
switch( ob->query_raw_combat_attitude() ) {
case 1..2 :
// If they're in offensive mode and its an offensive command and
// they're not more than 1 round overdrawn then ok.
if( off && time_left > -action_time )
return 1;
break;
case -2..-1 :
if( !off && time_left > -action_time )
return 1;
break;
}
return 0;
} /* check_time_left() */
/** @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() */
/** @ignore yes */
class task_class_result compare_skills( object offob, string offskill,
object defob, string defskill,
int modifier ) {
int offbonus, defbonus, difficulty, percent, defdeg;
class task_class_result result;
offbonus = offob->query_skill_bonus(offskill);
defbonus = defob->query_skill_bonus(defskill);
difficulty = offbonus + modifier * offbonus / 100;
TASKER->set_control( ({ defob, defskill }) );
// Args: difficulty, bonus, upper, half, use_class.
// Let's have a slightly lower tm chance for defensive tms,
// since they are done each time.
result = TASKER->attempt_task_e( difficulty, defbonus, 15, 100, 1 );
#ifdef TM_INFORM
do_inform( offob,
"Combat TM: %s (%s: %i) attacking %s (%s: %i)",
offob->query_cap_name(), explode( offskill, ".")[<1], offbonus,
defob->query_cap_name(), explode( defskill, ".")[<2], defbonus );
do_inform( offob, "Combat TM: Modifier: %i, Defense difficulty : %i",
modifier, difficulty );
#endif
defdeg = result->degree;
switch( result->result ) {
case AWARD :
if( !TASKER->is_valid_tm( defob, defskill ) ||
!defob->add_skill_level( defskill, 1, TO ) ) {
result->result = DEFWIN;
return result;
}
result->result = DEFAWARD;
#ifdef TM_INFORM
do_inform( offob, "Combat TM: DEFWIN");
#endif
return result;
case SUCCEED :
result->result = DEFWIN;
#ifdef TM_INFORM
do_inform( offob, "Combat TM: DEFWIN");
#endif
return result;
default :
}
difficulty = defbonus - modifier * defbonus / 100;
TASKER->set_control( ({ offob, offskill }) );
// Args: difficulty, bonus, upper, half, use_class.
result = TASKER->attempt_task_e( difficulty, offbonus, 18, 100, 1 );
#ifdef TM_INFORM
do_inform( offob, "Combat TM: Modifier: %i, Attack difficulty : %i",
modifier, difficulty );
#endif
switch( result->result ) {
case AWARD :
if( !TASKER->is_valid_tm( offob, offskill ) ||
!offob->add_skill_level( offskill, 1, TO ) ) {
result->result = OFFWIN;
return result;
}
result->result = OFFAWARD;
#ifdef TM_INFORM
do_inform( offob, "Combat TM: OFFWIN");
#endif
return result;
case SUCCEED :
result->result = OFFWIN;
#ifdef TM_INFORM
do_inform( offob, "Combat TM: OFFWIN");
#endif
return result;
default :
}
// If both lose the skill checks, we throw dice, basically.
if( !offbonus && !defbonus )
percent = 50;
else
percent = ( offbonus * 100 ) / ( offbonus + defbonus );
percent += modifier;
// Add a small modifier from the taskmaster result degrees as well.
percent += ( result->degree - defdeg ) / 4;
if( random(100) < percent ) {
result->result = OFFWIN;
#ifdef TM_INFORM
do_inform( offob, "Combat TM: DRAW-OFFWIN, Percent: %i, Degree: %i",
percent, result->degree );
#endif
} else {
result->result = DEFWIN;
result->degree = defdeg;
#ifdef TM_INFORM
do_inform( offob, "Combat TM: DRAW-DEFWIN, Percent: %i, Degree: %i",
percent, result->degree );
#endif
}
return result;
} /* compare_skills() */
/** @ignore yes */
string success_degree( int degree, string input ) {
switch( degree ) {
case -100..-76 :
return input + " with pure luck";
case -75..-51 :
return "clumsily " + input;
case -50..-26 :
return "barely " + input;
case -25..25 :
return input;
case 26..50 :
return "skillfully " + input;
case 51..75 :
return "expertly " + input;
case 76..100 :
return input + " with no effort at all";
default :
return input + " in an act of buggy goodness";
}
} /* success_degree() */
/**
* This method checks to see if we dodge an attack.
* @param attack the attack to dodge
* @return the modified attack data class
*/
class attack_data dodge_attack( class attack_data att ) {
int pverbose, tverbose, mod, burden;
class task_class_result result;
class message_data mess;
object off, def, wep;
string str;
off = att->attacker;
def = att->target;
wep = att->weapon;
mod = att->attack_bonus;
if( !check_time_left( def, 0 ) || !off->query_visible(def) ) {
#ifdef DODGE_INFORM
do_inform( off, "DODGE: %s not dodging this attack, Special: %i Time "
"left: %i Queue: %i Visible: %i", def->query_name(),
!def->query_special_manoeuvre(), def->query_time_left(),
def->queue_commands(), !off->query_visible(def) );
#endif
att->success = 1;
return att;
}
pverbose = interactive(off) && off->query_verbose("combat");
tverbose = interactive(def) && def->query_verbose("combat");
mod += do_reset_manoeuvre( def, DODGING_SKILL );
mod += off->query_raw_combat_attitude() * OFF_MANOEUVRE_ATT_BONUS;
burden = def->query_burden();
// A possible bonus ranging from -DODGE_WEIGHT/2 to DODGE_WEIGHT/2
mod += DODGE_WEIGHT * burden / 100 - DODGE_WEIGHT / 2;
// A bonus to dodging large weapons, and penalty for lighter ones.
if( wep != off )
mod += ( DODGE_WEAPON_WEIGHT - wep->query_weight() ) / 4;
#ifdef DODGE_INFORM
do_inform( off, "DODGE: %s (att: %s, bonus: %i, burden: %i)\n"
" VS: %s (att: %s, bonus: %i, weapon weight: %i)\n"
" Attack bonus: %i, final modifier: %i",
def->query_name(), def->query_combat_attitude(),
def->query_skill_bonus(DODGING_SKILL), burden, off->query_name(),
off->query_combat_attitude(),
off->query_skill_bonus(MELEE+att->skill), wep->query_weight(),
att->attack_bonus, mod );
#endif
result = compare_skills( off, MELEE+att->skill, def, DODGING_SKILL, mod );
switch( result->result ) {
case OFFAWARD :
melee_tm( off, 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 ) )
def->reset_special_manoeuvre();
att->success = 1;
return att;
case DEFAWARD :
tell_object( def, "%^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$",
off->the_short() ) +".%^RESET%^\n" );
case DEFWIN :
if( !mess = att->messages )
mess = query_attack_desc( off, def, wep, att->skill, att->type,
att->name, att->area );
str = success_degree( result->degree, "dodge out of the way");
mess->defender += ", but you "+str+".\n";
str = success_degree( result->degree, "dodges out of the way");
mess->attacker += ", but "+def->HE+" "+str+".\n";
mess->others += ", but "+def->HE+" "+str+".\n";
att->messages = mess;
att->success = 0;
return att;
}
} /* dodge_attack() */
/**
* This method checks to see if they can parry with their bare hands.
* @param attack the attack to dodge
* @return the modified attack data class
*/
protected class attack_data unarmed_parry( class attack_data att ) {
int pverbose, tverbose, mod;
class task_class_result result;
class message_data mess;
object off, def, wep;
mapping areas;
string str;
off = att->attacker;
def = att->target;
wep = att->weapon;
mod = att->attack_bonus;
pverbose = interactive(off) && off->query_verbose("combat");
tverbose = interactive(def) && def->query_verbose("combat");
mod += do_reset_manoeuvre( def, UNARMED_SKILL );
mod += off->query_raw_combat_attitude() * OFF_MANOEUVRE_ATT_BONUS;
// A small bonus to parrying small weapons, and penalty for heavier ones.
if( wep != off )
mod -= ( PARRY_WEAPON_WEIGHT - wep->query_weight() ) / 4;
// A possible bonus ranging from -PARRY_WEIGHT/2 to PARRY_WEIGHT/2
mod += PARRY_WEIGHT * def->query_burden() / 100 - PARRY_WEIGHT / 2;
#ifdef PARRY_INFORM
do_inform( off, "UNARMED PARRY: %s (att: %s, bonus: %i)\n"
" VS: %s (att: %s, bonus: %i, weapon: %s, weight: %i)\n"
" Attack bonus: %i, final modifier: %i", def->query_name(),
def->query_combat_attitude(), def->query_skill_bonus(UNARMED_SKILL),
off->query_name(), off->query_combat_attitude(),
off->query_skill_bonus(MELEE+att->skill), wep->short(),
wep->query_weight(), att->attack_bonus, mod );
#endif
if( !mess = att->messages )
mess = query_attack_desc( off, def, wep, att->skill, att->type,
att->name, att->area );
areas = def->query_attackable_areas();
result = compare_skills( off, MELEE+att->skill, def, UNARMED_SKILL, mod );
switch( result->result ) {
case OFFAWARD :
melee_tm( off, 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 ) )
def->reset_special_manoeuvre();
att->success = 1;
att->area = 0;
if( att->focus == "arms" || ( random( 4 ) && att->focus != "hands") )
att->focus = "hands";
else
att->focus = "arms";
att->area = choice( areas["arms"] );
tell_object( off, mess->attacker+", but "+def->the_short()+" moves "
"in "+def->HIS+" "+att->area+" to attempt to deflect the "
"attack.\n");
tell_object( def, mess->defender+", but you move in your "+
att->area+" to attempt to deflect the attack.\n");
event( ENV(off), "see", mess->others+", but "+
def->the_short()+" moves in "+def->HIS+" "+att->area+" to "
"attempt to deflect the attack.\n", off, ({ off, def }) );
att->messages = 0;
return att;
case DEFAWARD :
tell_object( def, "%^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$",
off->the_short() ) +".%^RESET%^\n");
case DEFWIN :
str = success_degree( result->degree, "deflect the blow");
mess->defender += ", but you "+str;
str = success_degree( result->degree, "deflects the blow");
mess->attacker += ", but "+def->HE+" "+str;
mess->others += ", but "+def->HE+" "+str;
if( att->focus != "hands") {
string part = choice( areas["hands"] );
mess->attacker += " with "+def->HIS+" "+part;
mess->defender += " with your "+part;
mess->others += " with "+def->HIS+" "+part;
}
mess->attacker += ".\n";
mess->defender += ".\n";
mess->others += ".\n";
att->messages = mess;
att->success = 0;
return att;
}
} /* unarmed_parry() */
/**
* This method checks to see if they can parry with their weapon.
* @param attack the attack to dodge
* @return the modified attack data class
*/
class attack_data parry_attack( class attack_data att ) {
int i, pverbose, tverbose, mod;
string parrying, *hands, *verb, str;
object *things, with, off, def, wep;
class task_class_result result;
class message_data mess;
off = att->attacker;
def = att->target;
if( !check_time_left( def, 0 ) || !off->query_visible(def) ) {
#ifdef PARRY_INFORM
do_inform( off, "PARRY: %s not parrying this attack, Special: %i "
"Time left: %i Queue: %i Visible: %i", def->query_name(),
!def->query_special_manoeuvre(), def->query_time_left(),
def->queue_commands(), !off->query_visible(def) );
#endif
att->success = 1;
return att;
}
things = def->query_holding();
switch( str = def->query_combat_parry() ) {
case "all" :
case "both" :
break;
default :
i = sizeof( hands = def->query_limbs() );
while( i-- ) {
if( hands[ i ] != str+" hand") {
hands = delete( hands, i, 1 );
things = delete( things, i, 1 );
}
}
}
if( !sizeof( things -= ({ 0 }) ) ) {
if( def->query_unarmed_parry() ) {
return unarmed_parry( att );
} else {
att->success = 1;
return att;
}
}
wep = att->weapon;
mod = att->attack_bonus;
pverbose = interactive(off) && off->query_verbose("combat");
tverbose = interactive(def) && def->query_verbose("combat");
mod += do_reset_manoeuvre( def, PARRY_SKILL );
mod += off->query_raw_combat_attitude() * OFF_MANOEUVRE_ATT_BONUS;
// A possible bonus ranging from -PARRY_WEIGHT/2 to PARRY_WEIGHT/2
mod += PARRY_WEIGHT * def->query_burden() / 100 - PARRY_WEIGHT / 2;
with = choice( things );
// Let us check for a shield.
if( with->query_shield() ) {
mod += with->query_weight() / ( def->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( member_array( att->focus, CLOTHING_H->query_zone_names(
with->query_type() ) ) != -1 )
mod -= SHIELD_BLOCK_BONUS;
} else {
things = def->query_holding();
// Get the number of hands used.
i = sizeof(things) - sizeof( things - ({ with }) );
mod += 2 * with->query_weight() / ( i * def->query_str() + 1 );
}
// Take the relative weights of the two weapons into account as well.
if( wep != off )
mod += ( wep->query_weight() - with->query_weight() ) / 4;
#ifdef PARRY_INFORM
do_inform( off,
"PARRY: %s (att: %s, bonus: %i, using: %s, weight: %i)\n"
" VS: %s (att: %s, bonus: %i, weapon: %s, weight: %i)\n"
" Attack bonus: %i, final modifier: %i", def->query_name(),
def->query_combat_attitude(), def->query_skill_bonus(PARRY_SKILL),
with->short(), with->query_weight(), off->query_name(),
off->query_combat_attitude(),
off->query_skill_bonus(MELEE+att->skill), wep->short(),
wep->query_weight(), att->attack_bonus, mod );
#endif
verb = ( with->query_weapon() ?
({"parry", "parries", "parrying"}) :
({"block", "blocks", "blocking"}) );
result = compare_skills( off, MELEE+att->skill, def, PARRY_SKILL, mod );
switch( result->result ) {
case OFFAWARD :
melee_tm( off, 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 ) )
def->reset_special_manoeuvre();
att->success = 1;
return att;
case DEFAWARD :
tell_object( def, "%^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$",
off->the_short() }) )+".%^RESET%^\n");
case DEFWIN :
if( with->query_weapon() )
with->hit_weapon( att->final_damage, att->type );
else
with->do_damage( att->type, att->final_damage );
wep->hit_weapon( att->final_damage, att->type );
if( !mess = att->messages )
mess = query_attack_desc( off, def, wep, att->skill, att->type,
att->name, att->area );
parrying = with->poss_short();
str = success_degree( result->degree,
verb[ 0 ]+" the blow with "+parrying );
mess->defender += ", but you "+str+".\n";
str = success_degree( result->degree,
verb[ 1 ]+" the blow with "+parrying );
mess->attacker += ", but "+def->HE+" "+str+".\n";
mess->others += ", but "+def->HE+" "+str+".\n";
att->messages = mess;
att->success = 0;
// Here we have a hook to a function in weapons/shields.
// This function should return the (possibly) modified attack
// data class. If not, we just return the original class.
return with->parry_attack(att) || att;
}
} /* 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() */
/**
* This method checks whether someone jumps in an protects us.
* @param thing the thing to check for protectors
* @param attacker the attacker
* @return the thing that protects us, or ourself
*/
object check_protection( object thing, object attacker ) {
object *protectors;
if( !sizeof( protectors = thing->query_protectors() ) )
return thing;
protectors = shuffle( filter( protectors, (:
!$1->query_property("dead") && !$1->query_property(PASSED_OUT) &&
( $1->query_time_left() >= 0 ) && !$1->queue_commands() &&
( !userp($1) || interactive($1) ) && !$1->query_sanctuary() &&
( ENV($1) == ENV($2) ) && $2->query_visible($1) :), thing ) );
foreach( object ob in protectors ) {
if( !random( 5 ) || ob == attacker || pk_check( ob, attacker ) )
continue;
ob->remove_hide_invis("hiding");
tell_object( ob, "You bravely throw yourself in front of "+
thing->the_short()+" to protect "+thing->HIM+" from "+
attacker->poss_short()+" attack.\n");
tell_object( thing, ob->one_short()+" bravely throws "+
ob->HIM+"self in front of "+thing->one_short()+" to protect you "
"from "+attacker->poss_short()+" attack.\n");
tell_room( ENV(thing), ob->one_short()+" bravely throws "+
ob->HIM+"self in front of "+thing->one_short()+" to protect "+
thing->HIM+" from "+attacker->poss_short()+" attack.\n",
({ ob, thing }) );
ob->attack_by(attacker);
attacker->set_target(ob);
return ob;
}
return thing;
} /* check_protection() */
/** @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() */
/**
* This method returns the percentual penalty to actions performed
* by the specified creature when hurt. This value should be
* negative or 0.
* @param ob the creature to get the penalty for
* @return the percentual penalty to actions due to being hurt
*/
int hp_modifier( object ob ) {
return HP_MOD * ob->query_hp() / ( ob->query_max_hp() + 1 ) - HP_MOD;
} /* hp_modifier() */
/**
* @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, burden, limbs;
if( !ENV(attacker) )
return 0;
dex = attacker->query_dex();
str = attacker->query_str();
perc = 90;
// Start out at up to 100 on the most aggressive attitude.
if( !flag )
perc += attacker->query_raw_combat_attitude() * 5;
// The fatter you are, the less you attack.
// This should be a penalty of about 25-40.
perc -= attacker->query_weight() / 2 / ( str + dex );
if( !flag )
perc += hp_modifier( attacker );
burden = attacker->query_burden();
// Unarmed percentage.
if( !sizeof(weapons) ) {
if( sizeof( attacker->query_limbs() ) )
limbs = attacker->query_free_limbs();
else
limbs = 1;
// A bonus of about 10-20.
perc += ( str + dex ) * ( 1 + limbs ) / 5;
// A (extra) bonus of up to +/- 10 for being unburdened.
perc += 10 - 20 * burden / 100;
} 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 * burden / 100;
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() */
/** @ignore yes */
private void flush_extra_attacks() {
class attack_data att, *attacks;
object off, wep;
extra_id = 0;
// Store it in a local variable so that the global variable
// would always be an empty array when we finish, even if we error.
attacks = shuffle( extra_attacks );
extra_attacks = ({ });
foreach( att in attacks ) {
if( ( off = att->attacker ) && att->target && ( wep = att->weapon ) &&
( ( wep == off && !sizeof( off->query_weapons() ) ) ||
wep->query_wielded() == off ) )
attack_round( off, 1, att, 0 );
}
} /* flush_extra_attacks() */
/** @ignore yes */
private void register_extra_attack( class attack_data attack ) {
if( !extra_id )
extra_id = call_out( (: flush_extra_attacks :), 0 );
extra_attacks += ({ attack });
} /* register_extra_attack() */
/** @ignore yes */
private void finish_attack( object attacker, object target, int extra ) {
if( !extra ) {
// Check for point monitoring.
if( attacker->query_monitor() )
HEALTH_H->register_monitor( attacker, 0 );
// Check for combat actions for the next round.
if( !userp( attacker ) )
combat_actions( attacker, target );
}
} /* finish_attack() */
/** @ignore yes */
void ( object attacker, int extra, class attack_data attack,
class special_attack_data sp ) {
object thing, weapon, *things, *holding, *weapons;
class attack_data extra_attack;
int back, bonus, riding, perc;
mixed tmp;
// Player cannot fight while passed out,
// queuing commands, without environment, net-dead or just plain dead.
if( attacker->query_property(PASSED_OUT) || attacker->dont_attack_me() ||
!check_time_left( attacker, 1 ) || !ENV(attacker) ||
( userp(attacker) && !interactive(attacker) ) ||
attacker->query_hp() < 0 ) {
#ifdef TIMING_INFORM
do_inform( attacker, "TIMING: %s skipping round - time left: %i",
attacker->query_name(), attacker->query_time_left() );
#endif
return;
}
if( !extra )
attacker->set_special_manoeuvre();
#ifdef TIMING_INFORM
do_inform( attacker, "TIMING: %s - time left in the start of the "
"attack round%s: %i", attacker->query_name(), extra ? " (EXTRA)" :
"", attacker->query_time_left() );
#endif
if( attack ) {
thing = attack->target;
weapon = attack->weapon;
}
// Choose a target to attack for this round.
if( thing ) {
if( ENV(thing) != ENV(attacker) || thing->query_property("dead") ||
!thing->query_visible(attacker) ) {
return;
}
} else 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() ) ||
ENV(thing) != ENV(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.
if( !extra )
attacker->fight_in_progress( thing );
attacker->set_target( thing );
if( !attack ) {
// Give 1 xp for an attack round.
// This would only benefit utter newbies.
if( PO == attacker )
attacker->adjust_xp( 1 );
attack = new( class attack_data );
attack->attacker = attacker;
attack->target = thing;
// See if we have a tactical special queued.
if( sp = attacker->special_attack( SP_TACTICAL, attack ) ) {
call_other( sp->ob, sp->fun, attacker, thing, sp->data,
PREPARE_ATTACK );
return;
}
holding = attacker->query_holding() - ({ 0 });
weapons = shuffle( 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( object ob in holding ) {
if( !ob->query_weapon() ) {
ob->do_damage("crush", 10 + random(50) );
holding -= ({ ob });
}
}
perc = calc_attack_percentage( attacker, weapons, holding, 0 );
if( sizeof( weapons ) ) {
weapon = weapons[0];
weapons = weapons[1..1];
} else {
weapon = attacker;
if( sizeof( attacker->query_attack_names() ) > 1 )
weapons = ({ attacker });
}
if( sizeof( weapons ) ) {
// Register an extra attack with 50% of our current
// attack percentage.
tmp = weapons[0]->weapon_attacks( perc / 2, thing );
if( sizeof(tmp) ) {
extra_attack = new( class attack_data );
extra_attack->attacker = attacker;
extra_attack->target = thing;
extra_attack->weapon = weapons[0];
extra_attack->damage = tmp[0];
extra_attack->skill = tmp[1];
extra_attack->type = tmp[2];
extra_attack->name = tmp[3];
extra_attack->final_damage = tmp[0];
extra_attack->attack_bonus = -5;
}
}
if( !sizeof( tmp = weapon->weapon_attacks( perc, thing ) ) ) {
if( extra_attack )
register_extra_attack( extra_attack );
return finish_attack( attacker, thing, extra );
}
attack->weapon = weapon;
attack->damage = tmp[0];
attack->skill = tmp[1];
attack->type = tmp[2];
attack->name = tmp[3];
attack->final_damage = tmp[0];
}
// See if we have a special queued.
if( !sp && classp( sp = attacker->special_attack( SP_MELEE, attack ) ) ) {
attack->data = sp->data;
attack->flags = sp->flags;
// This could be a tactical as well, in this case it should
// return 0, if we want to return after executing the
// special attack.
if( attack->flags & PREPARE_ATTACK )
attack = call_other( sp->ob, sp->fun, attack, PREPARE_ATTACK );
if( !attack ) {
if( extra_attack )
register_extra_attack( extra_attack );
return finish_attack( attacker, thing, extra );
}
}
// 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;
}
if( weapon && classp(attack) && thing && ENV(thing) == ENV(attacker) &&
thing->query_hp() >= 0 ) {
string response;
mapping areas;
attacker->set_weapon( weapon );
// Check out what bodyparts they have.
areas = thing->query_attackable_areas();
bonus = attacker->query_skill_bonus( MELEE+attack->skill );
bonus += 50 * 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 + attack->attack_bonus > 0 ) {
back = ACTION_TIME - ( bonus + attack->attack_bonus ) / 25;
// Let's make heavier weapons use more action points.
if( weapon != attacker )
back += weapon->query_weight() / attacker->query_str();
if( back < 2 )
back = 2;
} else {
back = ACTION_TIME;
}
attacker->adjust_time_left( -back );
#ifdef TIMING_INFORM
do_inform( attacker, "TIMING: %s - adjusting time by: %i",
attacker->query_name(), ACTION_TIME );
#endif
if( bonus < 1 )
bonus = 1;
// Make the actual damage a weighted average of their skill and
// the weapon damage.
if( weapon != attacker ) {
attack->final_damage = to_int( sqrt(
to_float( attack->damage * bonus ) ) );
} else {
// Let's make unarmed slightly harder to hit with.
attack->attack_bonus -= 5;
}
// Cap the final damage at 3x weapon damage.
if( attack->final_damage > 3 * attack->damage )
attack->final_damage = 3 * attack->damage;
back = attack->final_damage;
if( weapon != attacker )
back += weapon->query_weight();
else
back += weapon->query_weight() / 40;
if( ( back = level_out( back ) ) < 0 )
back = 0;
back += attack->difficulty;
#ifdef ATTACK_INFORM
do_inform( attacker, "ATTACK: %s using %s%s, weapon "
"damage: %i, final damage: %i, difficulty: %i (modifier: %i), "
"bonus: %i", attacker->query_name(), weapon != attacker ?
weapon->a_short() : "unarmed", perc ? ", percentage: "+perc : "",
attack->damage, attack->final_damage * COMBAT_DAMAGE, back,
attack->difficulty, bonus );
#endif
tmp = bonus + 50;
// Test to see if the player can use this attack.
switch( TASKER->attempt_task_e( back, tmp, 1, 40 ) ) {
case AWARD :
case SUCCEED :
attack->success = 1;
// Now check if they can have an extra attack too.
if( extra_attack ) {
switch( TASKER->attempt_task_e( back, bonus, 1, 80 ) ) {
case AWARD :
case SUCCEED :
register_extra_attack( extra_attack );
break;
default :
}
}
break;
default :
// Let's miss if the attack is way too hard for us,
// otherwise halve the damage and make it harder to hit.
if( back > tmp ) {
attack->success = 0;
attack->final_damage = 0;
} else {
attack->success = 1;
attack->final_damage /= 2;
attack->attack_bonus -= 5;
}
}
// Compare their hit point modifiers.
attack->attack_bonus += hp_modifier(attacker) - hp_modifier(thing);
// See what our special attack has to say about it.
if( attack->flags & MODIFY_ATTACK )
attack = call_other( sp->ob, sp->fun, attack, MODIFY_ATTACK );
if( !attack->focus )
attack->focus = attacker->query_combat_focus();
if( attack->focus && undefinedp( areas[attack->focus] ) )
attack->focus = 0;
if( !attack->focus ) {
// If there is no focus, pick a random body area to hit.
attack->focus = choice( keys(areas) );
} else {
// Make focused attacks slightly easier to defend against.
attack->attack_bonus -= FOCUS_DEFENSE_BONUS;
}
// Pick an area name.
attack->area = choice( areas[attack->focus] );
// Make sure the target sees their hit points when attacked.
if( thing->query_monitor() )
HEALTH_H->register_monitor( thing, 0 );
if( !attack->final_damage ) {
// See what our special attack has to say about it.
if( attack->flags & MODIFY_MESSAGES )
attack = call_other( sp->ob, sp->fun, attack, MODIFY_MESSAGES );
// We missed, print the relevant messages.
write_messages( 0, 0, attack );
return finish_attack( attacker, thing, extra );
}
// See what our special attack has to say about it.
if( attack->flags & MODIFY_DAMAGE )
attack = call_other( sp->ob, sp->fun, attack, MODIFY_DAMAGE );
// See if a protector moves to accept the blow for the target.
if( !attack->unprotectable )
thing = check_protection( thing, attacker );
if( thing == attack->target ) {
// Test to see if the target has magical protection.
// block_attack() should set attack->success to 0 if blocking
// the attack successfully, -1 if dodging/parrying should
// still be attempted or 1 if the attack wasn't blocked and
// dodging/parrying shouldn't be allowed.
// Only do this if no-one is protecting us.
if( classp( tmp = thing->block_attack( attack ) ) ) {
attack = tmp;
if( attack->success != -1 ) {
// See what our special attack has to say about it.
if( attack->flags & MODIFY_MESSAGES )
attack = call_other( sp->ob, sp->fun, attack,
MODIFY_MESSAGES );
write_messages( 0, 0, attack );
return finish_attack( attacker, thing, extra );
}
}
// If there was no magical effect blocking.
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.
attack = dodge_attack( attack );
break;
case "parry" :
// Test to see if the target parries.
attack = parry_attack( attack );
break;
default :
error( sprintf("%O using an invalid response %O.\n",
thing, response ) );
}
// The attack was either magically blocked, dodged or parried.
if( !attack->success ) {
// See what our special attack has to say about it.
if( attack->flags & MODIFY_MESSAGES )
attack = call_other( sp->ob, sp->fun, attack,
MODIFY_MESSAGES );
write_messages( 0, 0, attack );
return finish_attack( attacker, thing, extra );
}
} else {
attack->target = thing;
}
// See what our special attack has to say about it.
if( attack->flags & MODIFY_MESSAGES )
attack = call_other( sp->ob, sp->fun, attack, MODIFY_MESSAGES );
// Subtract the target's armour class from the damage.
back = thing->query_ac( attack->type, attack->final_damage,
attack->focus );
#ifdef AC_INFORM
do_inform( attacker, "AC: %s has AC of %i on %s for %s",
thing->query_name(), back, attack->focus, attack->type );
#endif
#ifdef DAMAGE_INFORM
do_inform( attacker, "DAMAGE: %s - damage: %i, AC: %i (%s), "
"hitting %s for %i", attacker->query_name(),
attack->final_damage * COMBAT_DAMAGE,
back * COMBAT_DAMAGE, attack->focus, thing->query_name(),
( attack->final_damage - back ) * COMBAT_DAMAGE );
#endif
weapon->hit_weapon( back, attack->type );
// Do the damage, if there is any, and print relevant messages.
write_messages( back, thing->query_stopped(), attack );
// Do the damage, if any got past through our armour.
if( attack->final_damage > back ) {
attack->final_damage -= back;
thing->adjust_hp( -attack->final_damage * COMBAT_DAMAGE,
attacker, weapon, attack->name );
} else {
// Armour absorbed all of the blow.
attack->final_damage = 0;
}
// Call the weapon's attack functions.
if( weapon )
weapon->attack_function( attack->name,
attack->final_damage * COMBAT_DAMAGE, thing, attacker );
// See what our special attack has to say about it.
if( attack->flags & FINISH_ATTACK )
attack = call_other( sp->ob, sp->fun, attack, FINISH_ATTACK );
} else {
#ifdef TIMING_INFORM
do_inform( attacker, "TIMING: %s - no attacks this round.",
attacker->query_name() );
#endif
}
finish_attack( attacker, thing, extra );
} /* attack_round() */