/** * 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() */