/**
* This is the kirikaeshi command. Thank you, Kendo. :)
* Initiates combat and makes a number of quick attacks on the target.
* @author Sandoz, 2002.
* @changed Rewrote completely for the new combat system.
* - Sandoz, June 2003.
*/
#define __ATTACK_DATA_CLASS__
#include <combat.h>
#include <player.h>
#include <tasks.h>
#include <weapon.h>
#define GP_COST 50
#define LEARN_LEVEL 50
#define MAX_HITS 5
#define OFF_SKILL "fighting.combat.special.tactics"
#define WEP_SKILL "fighting.combat.special.weapon"
#define DEF_SKILL "general.perception"
inherit COMMAND_BASE;
private void execute_attack( object off, object def, object wep,
string *names, mixed data, string *areas );
/**
* This method returns whether or not the specified player
* can learn this command.
* @param ob the player to test
* @return 1 if they can learn it, 0 if not
*/
int query_learnable_by( object ob ) {
return ob->query_skill(OFF_SKILL) >= LEARN_LEVEL &&
ob->query_skill(WEP_SKILL) >= LEARN_LEVEL;
} /* query_learnable_by() */
/** @ignore yes */
int cmd( object *targets, object *weapons ) {
string *names, *areas;
object target, weapon;
int mod, i, off, def;
mapping map;
mixed data;
mapping hiding;
if( TP->query_fighting() ) {
add_failed_mess("You cannot use a kirikaeshi attack while already "
"in combat with someone.\n");
return 0;
}
if( sizeof( targets ) > 1 ) {
add_failed_mess("You can only make a kirikaeshi attack on one "
"target at a time.\n");
return 0;
}
targets = HEALTH_H->do_attack_checks( targets, 0 );
if( !targets )
return 0;
if( !sizeof(targets) )
return 1;
target = targets[ 0 ];
if( mapp( map = target->query_attackable_areas() ) ) {
areas = ({ });
if( !undefinedp( map["head"] ) )
areas += ({"head"});
if( !undefinedp( map["neck"] ) )
areas += ({"neck"});
}
if( !sizeof(areas) ) {
add_failed_mess("Oh dear, $I seems to have no head or neck to "
"pummel at. Puzzling, no?\n");
return 0;
}
if( sizeof( weapons ) > 1 ) {
add_failed_mess("You can only use one weapon for the kirikaeshi "
"attack.\n");
return 0;
}
if( !sizeof(weapons) ) {
add_failed_mess("You need a weapon for the kirikaeshi "
"attack.\n");
return 0;
}
weapon = weapons[ 0 ];
if( !weapon->query_weapon() || weapon->query_no_limbs() != 2 ) {
add_failed_mess("You need a two-handed weapon to use the kirikaeshi "
"attack.\n");
return 0;
}
i = sizeof( names = weapon->query_attack_names() );
data = weapon->query_attack_data();
// Weed out unappropriate attacks.
while( i-- ) {
int j = i * W_ARRAY_SIZE;
switch( data[j+W_SKILL] ) {
case "sharp" :
case "blunt" :
continue;
default:
names = delete( names, i, 1 );
data = delete( data, j, W_ARRAY_SIZE );
}
}
if( !sizeof(names) ) {
add_failed_mess("$I doesn't appear to be a suitable weapon to "
"execute a kirikaeshi attack with, because it is not meant "
"for swinging motions.\n");
return 0;
}
if( target->is_fighting(TP) ) {
add_failed_mess("$I appears to be fighting you already.\n", targets );
return 0;
}
if( ENV(TP)->query_transport() ) {
add_failed_mess("You cannot execute a kirikaeshi attack while you "
"are mounted.\n");
return 0;
}
if( ENV(target)->query_transport() ) {
add_failed_mess("You cannot execute a kirikaeshi attack on mounted "
"opponents.\n");
return 0;
}
if( weapon->query_wielded() != TP ) {
add_failed_mess("You are not holding $I.\n", weapons );
return 0;
}
if( !TASKER->point_tasker( TP, "fighting", GP_COST ) ) {
add_failed_mess("You do not have enough energy to execute a "
"kirikaeshi attack on $I.\n", targets );
return 0;
}
hiding = (mapping)TP->query_hide_invis();
if( hiding["hiding"] ) {
TP->remove_hide_invis("hiding");
}
// Start out with a 25% bonus.
mod = 25;
// Impose a lower limit to it, just in case.
if( ( i = weapon->query_weight() ) < 10 )
i = 10;
// Increase the modifier by a weighted average of our
// special.weapon skill and the weight of the weapon used.
mod += TP->query_skill_bonus(WEP_SKILL) / i;
off = TP->query_skill_bonus(OFF_SKILL);
def = target->query_skill_bonus(DEF_SKILL);
event( TP, "inform", sprintf("kirikaeshi: tactics %i vs perception %i, "
"modifier: %i", off, def, mod ), "debug");
mod = TASKER->compare_skills( TP, OFF_SKILL, target, DEF_SKILL,
mod, TM_COMMAND, TM_FREE );
switch( mod ) {
case OFFAWARD :
tell_object( TP, "%^YELLOW%^"+({
"You feel more skilled at making surprise attacks",
"You discover a new aspect of tactical fighting",
"You realize something new about surprise attacks"})
[ random( 3 ) ]+" as you execute the kirikaeshi attack on "+
target->the_short()+"%^RESET%^\n" );
case OFFWIN :
XP_H->handle_xp( TP, GP_COST, 1 );
call_out( (: execute_attack :), 0, TP, target, weapon,
names, data, areas );
break;
case DEFAWARD :
tell_object( def, "%^YELLOW%^You feel more perceptive as you "+
choice( ({"foil ", "thwart "}) )+TP->poss_short()+" attempt "
"to launch at you with "+weapon->poss_short()+".\n%^RESET%^");
case DEFWIN :
XP_H->handle_xp( TP, GP_COST, 0 );
// Let's attack normally if we are good enough.
// Otherwise let the target attack us instead.
if( random(off) > def / 3 ) {
tell_object( TP, "You attempt to launch at "+
target->the_short()+" with "+weapon->poss_short()+", but "+
target->HE+" turns to face you just in time, although you "
"keep going, albeit with less vigour.\n");
tell_object( target, TP->one_short()+" attempts to launch at you "
"with "+weapon->poss_short()+", but you turn to face "+
TP->HIM+" just in time, thwarting "+TP->HIS+" enthusiasm.\n");
tell_room( ENV(TP), TP->one_short()+" attempts to launch at "+
target->one_short()+" with "+weapon->poss_short()+", but "+
target->HE+" turns to face "+TP->HIM+" just in time, "
"thwarting "+TP->HIS+" enthusiasm.\n", ({ TP, target }) );
TP->attack_ob(target);
} else {
tell_object( TP, "You attempt to launch at "+
target->the_short()+" with "+weapon->poss_short()+", but "+
target->HE+" turns to face you just in time and you lose your "
"concentration.\n");
tell_object( target, TP->one_short()+" attempts to launch at you "
"with "+weapon->poss_short()+", but you turn to face "+
TP->HIM+" just in time, foiling "+TP->HIS+" plans.\n");
tell_room( ENV(TP), TP->one_short()+" attempts to launch at "+
target->one_short()+" with "+weapon->poss_short()+", but "+
target->HE+" turns to face "+TP->HIM+" just in time, "
"foiling "+TP->HIS+" plans.\n", ({ TP, target }) );
TP->adjust_time_left( -ROUND_TIME );
target->attack_ob(TP);
}
}
return 1;
} /* cmd() */
/** @ignore yes */
private void finish_attack( object off, object def ) {
off->attack_ob(def);
tell_object( off, "You run out of breath and proceed to attack "+
def->the_short()+" normally.\n");
tell_room( ENV(off), off->the_short()+" seems to have run "
"out of breath and proceeds to attack "+
def->the_short()+" normally.\n", off );
} /* finish_attack() */
/** @ignore yes */
private void execute_attack( object off, object def, object wep,
string *names, mixed data, string *areas ) {
int i, j, dif, weight, perc, bon;
if( !wep ) {
tell_object( off, "Bereft of all hope, you abort your attack when "
"you realize you are not in possession of the weapon anymore.\n");
return 0;
}
if( wep->query_wielded() != off ) {
tell_object( off, "Bereft of all hope, you abort your attack when "
"you realize you are not holding "+wep->the_short()+" anymore.\n");
return 0;
}
if( wep->query_no_limbs() != 2 ) {
tell_object( off, "Bereft of all hope, you abort your attack when "
"you realize "+wep->the_short()+" is not a two-handed weapon.\n");
return 0;
}
if( ENV(off) != ENV(def) ) {
tell_object( off, "Bereft of all hope, you abort your attack when "
"you realize "+def->the_short()+" is not here anymore.\n");
return 0;
}
tell_object( off, "You raise "+wep->poss_short()+" high above your "
"head and launch at "+def->the_short()+" with a terrible cry, "
"starting to hew at alternate sides of "+def->HIS+" "+
query_multiple_short(areas)+".\n");
tell_object( def, off->one_short()+" suddenly raises "+off->HIS+" "+
wep->short()+" high above "+off->HIS+" head and launches at you "
"with a terrible cry, starting to hew at alternate sides of your "+
query_multiple_short(areas)+".\n");
tell_room( ENV(off), off->one_short()+" suddenly raises "+off->HIS+" "+
wep->short()+" high above "+off->HIS+" head and launches at "+
def->one_short()+" with a terrible cry, starting to hew at "
"alternate sides of "+def->HIS+" "+query_multiple_short(areas)+".\n",
({ off, def }) );
// Impose a lower limit on the weight again.
if( ( weight = wep->query_weight() ) < 10 )
weight = 10;
weight = 25 + ( 4 * weight ) / ( off->query_str() / 2 );
perc = 50;
perc += COMBAT_H->calc_attack_percentage( off, ({ wep }), ({ }) );
// Start with a random attack to a random area, then alternate.
j = random( sizeof(names) );
bon = off->query_skill_bonus(WEP_SKILL);
event( off, "inform", sprintf("Kirikaeshi base difficulty: %i, attack "
"percentage: %i, sp.we: %i", weight, perc, bon ), "debug");
for( i = 0; i < MAX_HITS; i++ ) {
int a_dif, damage, k, a_bonus;
class attack_data att;
if( j == sizeof(names) )
j = 0;
damage = wep->calc_attack( j, perc );
a_dif = damage / 5;
k = j * W_ARRAY_SIZE;
if( data[ k + W_CHANCE ] < 100 )
a_dif += a_dif * ( 100 - data[ k + W_CHANCE ] ) / 100;
a_dif += weight;
dif += a_dif;
a_bonus = to_int( bon / 50.0 + ( bon / 30.0 * ( MAX_HITS - 1 - i ) ) );
event( off, "inform", sprintf("Attack no. %i - weapon damage: %i, att "
"diff: %i, total diff: %i, att bonus: %i", i + 1, damage, a_dif,
dif, a_bonus ), "debug");
switch( TASKER->perform_task( off, WEP_SKILL, dif, TM_CONTINUOUS ) ) {
case AWARD:
tell_object( off, "%^YELLOW%^"+({
"You feel more confident in handling "+wep->the_short()+".",
"You realize you are more capable of quick movements with "+
wep->the_short()+" than before."})[random(2)]+"%^RESET%^\n");
case SUCCEED:
att = new( class attack_data );
att->attacker = off;
att->target = def;
att->weapon = wep;
att->damage = damage;
att->skill = data[ k + W_SKILL ];
att->type = data[ k + W_TYPE ];
att->name = names[j++];
att->focus = choice(areas);
att->final_damage = damage;
att->attack_bonus = a_bonus;
att->unprotectable = 1;
COMBAT_H->attack_round( off, !i, att );
// We have ran out of time.
if( !COMBAT_H->check_time_left( off, 1 ) ) {
event( off, "inform", "We have ran out of time!", "debug");
return finish_attack( off, def );
}
break;
default :
return finish_attack( off, def );
}
}
return finish_attack( off, def );
} /* execute_attack() */
/** @ignore yes */
mixed query_patterns() {
return ({
"<indirect:living:here'target'> [with] <indirect:object:me'weapon'>",
(: cmd( $1[0], $1[1] ) :),
"<indirect:living:here'target'>",
(: cmd( $1, TP->query_weapons() ) :),
});
} /* query_patterns() */