#include "AppHdr.h"
#include "fight.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <algorithm>
#ifdef TARGET_OS_DOS
#include <conio.h>
#endif
#include "externs.h"
#include "artefact.h"
#include "beam.h"
#include "cloud.h"
#include "database.h"
#include "debug.h"
#include "delay.h"
#include "effects.h"
#include "food.h"
#include "goditem.h"
#include "invent.h"
#include "items.h"
#include "itemname.h"
#include "itemprop.h"
#include "item_use.h"
#include "kills.h"
#include "los.h"
#include "macro.h"
#include "message.h"
#include "misc.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
#include "mstuff2.h"
#include "mutation.h"
#include "ouch.h"
#include "player.h"
#include "religion.h"
#include "shopping.h"
#include "skills.h"
#include "spells1.h"
#include "spells3.h"
#include "spl-mis.h"
#include "spl-util.h"
#include "state.h"
#include "stuff.h"
#include "transfor.h"
#include "traps.h"
#include "tutorial.h"
#include "view.h"
#include "xom.h"
#ifdef NOTE_DEBUG_CHAOS_BRAND
#define NOTE_DEBUG_CHAOS_EFFECTS
#endif
#ifdef NOTE_DEBUG_CHAOS_EFFECTS
#include "notes.h"
#endif
const int HIT_WEAK = 7;
const int HIT_MED = 18;
const int HIT_STRONG = 36;
static void stab_message(actor *defender, int stab_bonus);
static inline int player_weapon_str_weight( void );
static inline int player_weapon_dex_weight( void );
static inline int calc_stat_to_hit_base( void );
static inline int calc_stat_to_dam_base( void );
static void mons_lose_attack_energy(monsters *attacker, int wpn_speed,
int which_attack);
bool test_melee_hit(int to_hit, int ev)
{
int roll = -1;
int margin = AUTOMATIC_HIT;
ev *= 2;
if (to_hit >= AUTOMATIC_HIT)
return (true);
else if (x_chance_in_y(MIN_HIT_MISS_PERCENTAGE, 100))
margin = (coinflip() ? 1 : -1) * AUTOMATIC_HIT;
else
{
roll = random2( to_hit + 1 );
margin = (roll - random2avg(ev, 2));
}
#if DEBUG_DIAGNOSTICS
float miss;
if (to_hit < ev)
miss = 100.0 - MIN_HIT_MISS_PERCENTAGE / 2.0;
else
{
miss = MIN_HIT_MISS_PERCENTAGE / 2.0 +
((100.0 - MIN_HIT_MISS_PERCENTAGE) * ev) / to_hit;
}
mprf( MSGCH_DIAGNOSTICS,
"to hit: %d; ev: %d; miss: %0.2f%%; roll: %d; result: %s%s (%d)",
to_hit, ev, miss, roll, (margin >= 0) ? "hit" : "miss",
(roll == -1) ? "!!!" : "", margin );
#endif
return (margin >= 0);
}
int effective_stat_bonus( int wepType )
{
#ifdef USE_NEW_COMBAT_STATS
int str_weight;
if (wepType == -1)
str_weight = player_weapon_str_weight();
else
str_weight = weapon_str_weight( OBJ_WEAPONS, wepType );
return ((you.strength - you.dex) * (str_weight - 5) / 10);
#else
return (0);
#endif
}
static int calc_your_to_hit_unarmed(int uattack = UNAT_NO_ATTACK,
bool vampiric = false)
{
int your_to_hit;
your_to_hit = 13 + you.dex / 2 + you.skills[SK_UNARMED_COMBAT] / 2
+ you.skills[SK_FIGHTING] / 5;
if (wearing_amulet(AMU_INACCURACY))
your_to_hit -= 5;
if (you.species == SP_VAMPIRE && uattack == UNAT_BITE)
{
your_to_hit += 1;
if (vampiric)
{
if (you.hunger_state == HS_STARVING)
your_to_hit += 2;
else if (you.hunger_state < HS_SATIATED)
your_to_hit += 1;
}
}
else if (you.species != SP_VAMPIRE && you.hunger_state == HS_STARVING)
your_to_hit -= 3;
your_to_hit += slaying_bonus(PWPN_HIT);
return your_to_hit;
}
int calc_your_to_hit( bool random_factor )
{
melee_attack attk(&you, NULL);
return attk.calc_to_hit(random_factor);
}
int calc_heavy_armour_penalty( bool random_factor )
{
const bool ur_armed = (you.weapon() != NULL);
int heavy_armour = 0;
if (player_wearing_slot(EQ_SHIELD))
{
switch (you.shield()->sub_type)
{
case ARM_SHIELD:
if (you.skills[SK_SHIELDS] < maybe_random2(7, random_factor))
heavy_armour++;
break;
case ARM_LARGE_SHIELD:
if (player_genus(GENPC_OGRE) || you.species == SP_TROLL
|| player_genus(GENPC_DRACONIAN))
{
if (you.skills[SK_SHIELDS] < maybe_random2(13, random_factor))
heavy_armour++; }
else
{
for (int i = 0; i < 3; i++)
{
if (you.skills[SK_SHIELDS] < maybe_random2(13,
random_factor))
{
heavy_armour += maybe_random2(3, random_factor);
}
}
}
break;
default:
break;
}
}
if (player_wearing_slot(EQ_BODY_ARMOUR))
{
int ev_pen = property( you.inv[you.equip[EQ_BODY_ARMOUR]],
PARM_EVASION );
if (you.species == SP_MERFOLK && grd(you.pos()) == DNGN_DEEP_WATER
&& player_is_swimming())
{
ev_pen *= 2;
}
if (ev_pen < 0 && maybe_random2(you.skills[SK_ARMOUR],
random_factor) < abs(ev_pen))
{
heavy_armour += maybe_random2( abs(ev_pen), random_factor );
}
}
if (!ur_armed && heavy_armour)
{
if (random_factor)
{
heavy_armour *= (coinflip() ? 3 : 2);
}
else
{
heavy_armour *= 5;
heavy_armour /= 2;
}
}
return (heavy_armour);
}
static bool player_fights_well_unarmed(int heavy_armour_penalty)
{
return (you.burden_state == BS_UNENCUMBERED
&& x_chance_in_y(you.skills[SK_UNARMED_COMBAT], 20)
&& x_chance_in_y(2, 1 + heavy_armour_penalty));
}
unchivalric_attack_type is_unchivalric_attack(const actor *attacker,
const actor *defender)
{
const monsters *def = (defender->atype() == ACT_PLAYER ? NULL :
dynamic_cast<const monsters*>(defender));
unchivalric_attack_type unchivalric = UCAT_NO_ATTACK;
if (defender->cannot_fight() || (attacker && !attacker->can_see(defender)))
return (unchivalric);
if (attacker && attacker->atype() == ACT_PLAYER
&& def && def->foe != MHITYOU && !mons_is_batty(def))
{
unchivalric = UCAT_DISTRACTED;
}
if (def
&& def->has_ench(ENCH_CONFUSION)
&& !mons_class_flag(def->type, M_CONFUSED))
{
unchivalric = UCAT_CONFUSED;
}
if (def && mons_is_fleeing(def))
unchivalric = UCAT_FLEEING;
if (attacker && !attacker->visible_to(defender))
unchivalric = UCAT_INVISIBLE;
if (def && mons_is_caught(def))
unchivalric = UCAT_HELD_IN_NET;
if (def && mons_is_petrifying(def))
unchivalric = UCAT_PETRIFYING;
if (def && mons_is_petrified(def))
unchivalric = UCAT_PETRIFIED;
if (defender->paralysed())
unchivalric = UCAT_PARALYSED;
if (defender->asleep())
unchivalric = UCAT_SLEEPING;
return (unchivalric);
}
melee_attack::melee_attack(actor *attk, actor *defn,
bool allow_unarmed, int which_attack)
: attacker(attk), defender(defn),
cancel_attack(false),
did_hit(false), perceived_attack(false), obvious_effect(false),
needs_message(false), attacker_visible(false), defender_visible(false),
attacker_invisible(false), defender_invisible(false),
defender_starting_attitude(ATT_HOSTILE),
unarmed_ok(allow_unarmed),
attack_number(which_attack),
to_hit(0), base_damage(0), damage_done(0),
special_damage(0), aux_damage(0), stab_attempt(false), stab_bonus(0),
weapon(NULL), damage_brand(SPWPN_NORMAL),
wpn_skill(SK_UNARMED_COMBAT), hands(HANDS_ONE), hand_half_bonus(false),
art_props(0), unrand_entry(NULL), attack_verb("bug"), verb_degree(),
no_damage_message(), special_damage_message(), unarmed_attack(),
shield(NULL), defender_shield(NULL),
heavy_armour_penalty(0), can_do_unarmed(false),
water_attack(false), miscast_level(-1), miscast_type(SPTYP_NONE),
miscast_target(NULL), final_effects()
{
init_attack();
}
void melee_attack::check_hand_half_bonus_eligible()
{
hand_half_bonus = (unarmed_ok
&& !can_do_unarmed
&& !shield
&& weapon
&& !item_cursed(*weapon)
&& hands == HANDS_HALF);
}
void melee_attack::init_attack()
{
weapon = attacker->weapon(attack_number);
damage_brand = attacker->damage_brand(attack_number);
if (weapon && weapon->base_type == OBJ_WEAPONS && is_artefact(*weapon))
{
artefact_wpn_properties(*weapon, art_props);
if (is_unrandom_artefact(*weapon))
unrand_entry = get_unrand_entry(weapon->special);
}
wpn_skill = weapon ? weapon_skill(*weapon) : SK_UNARMED_COMBAT;
if (weapon)
hands = hands_reqd(*weapon, attacker->body_size());
shield = attacker->shield();
if (defender)
defender_shield = defender->shield();
water_attack = is_water_attack(attacker, defender);
attacker_visible = attacker->observable();
attacker_invisible = (!attacker_visible && see_cell(attacker->pos()));
defender_visible = defender && defender->observable();
defender_invisible = (!defender_visible && defender
&& see_cell(defender->pos()));
needs_message = (attacker_visible || defender_visible);
if (defender && defender->atype() == ACT_MONSTER)
defender_starting_attitude = defender_as_monster()->temp_attitude();
else
{
defender_starting_attitude = ATT_HOSTILE;
}
if (defender && defender->submerged())
{
unarmed_ok = (attacker->damage_type() == DVORP_TENTACLE);
}
miscast_level = -1;
miscast_type = SPTYP_NONE;
miscast_target = NULL;
}
std::string melee_attack::actor_name(const actor *a,
description_level_type desc,
bool actor_visible,
bool actor_invisible)
{
return (actor_visible ? a->name(desc) : anon_name(desc, actor_invisible));
}
std::string melee_attack::pronoun(const actor *a,
pronoun_type pron,
bool actor_visible)
{
return (actor_visible ? a->pronoun(pron) : anon_pronoun(pron));
}
std::string melee_attack::anon_pronoun(pronoun_type pron)
{
switch (pron)
{
default:
case PRONOUN_CAP: return "It";
case PRONOUN_NOCAP: return "it";
case PRONOUN_CAP_POSSESSIVE: return "Its";
case PRONOUN_NOCAP_POSSESSIVE: return "its";
case PRONOUN_REFLEXIVE: return "itself";
}
}
std::string melee_attack::anon_name(description_level_type desc,
bool actor_invisible)
{
switch (desc)
{
case DESC_CAP_THE:
case DESC_CAP_A:
return (actor_invisible ? "It" : "Something");
case DESC_CAP_YOUR:
return ("Its");
case DESC_NOCAP_YOUR:
case DESC_NOCAP_ITS:
return ("its");
case DESC_NONE:
return ("");
case DESC_NOCAP_THE:
case DESC_NOCAP_A:
case DESC_PLAIN:
default:
return (actor_invisible? "it" : "something");
}
}
std::string melee_attack::atk_name(description_level_type desc) const
{
return actor_name(attacker, desc, attacker_visible, attacker_invisible);
}
std::string melee_attack::def_name(description_level_type desc) const
{
return actor_name(defender, desc, defender_visible, defender_invisible);
}
std::string melee_attack::wep_name(description_level_type desc,
unsigned long ignore_flags) const
{
ASSERT(weapon != NULL);
if (attacker->atype() == ACT_PLAYER)
return weapon->name(desc, false, false, false, false, ignore_flags);
std::string name;
bool possessive = false;
if (desc == DESC_CAP_YOUR)
{
desc = DESC_CAP_THE;
possessive = true;
}
else if (desc == DESC_NOCAP_YOUR)
{
desc = DESC_NOCAP_THE;
possessive = true;
}
if (possessive)
name = apostrophise(atk_name(desc)) + " ";
name += weapon->name(DESC_PLAIN, false, false, false, false, ignore_flags);
return (name);
}
bool melee_attack::is_banished(const actor *a) const
{
if (!a || a->alive())
return (false);
if (a->atype() == ACT_PLAYER)
return (you.banished);
else
return (dynamic_cast<const monsters*>(a)->flags & MF_BANISHED);
}
bool melee_attack::is_water_attack(const actor *attk,
const actor *defn) const
{
return (defn && attk->swimming() && defn->floundering());
}
void melee_attack::check_autoberserk()
{
if (weapon
&& art_props[ARTP_ANGRY] >= 1
&& !one_chance_in(1 + art_props[ARTP_ANGRY]))
{
attacker->go_berserk(false);
}
}
bool melee_attack::check_unrand_effects(bool mondied)
{
if (unrand_entry && unrand_entry->fight_func.melee_effects
&& weapon && fires_ammo_type(*weapon) == MI_NONE)
{
unrand_entry->fight_func.melee_effects(weapon, attacker, defender,
mondied);
return (!defender->alive());
}
return (false);
}
void melee_attack::identify_mimic(actor *act)
{
if (act
&& act->atype() == ACT_MONSTER
&& mons_is_mimic(act->id())
&& you.can_see(act))
{
monsters* mon = dynamic_cast<monsters*>(act);
mon->flags |= MF_KNOWN_MIMIC;
}
}
bool melee_attack::attack()
{
identify_mimic(attacker);
identify_mimic(defender);
if (attacker->atype() == ACT_PLAYER && defender->atype() == ACT_MONSTER)
{
if (stop_attack_prompt(defender_as_monster(), false, attacker->pos()))
{
cancel_attack = true;
return (false);
}
}
if (attacker != defender)
{
attacker->attacking(defender);
check_autoberserk();
}
attacker->make_hungry(3, true);
if (attacker->fumbles_attack())
{
attacker->lose_energy(EUT_ATTACK);
if (attacker == defender)
xom_is_stimulated(255);
else
xom_is_stimulated(14);
if (damage_brand == SPWPN_CHAOS)
chaos_affects_attacker();
return (false);
}
else if (attacker == defender && attacker->confused())
{
if (attacker->atype() == ACT_PLAYER)
xom_is_stimulated(255);
else
xom_is_stimulated(128);
}
if (defender->atype() == ACT_MONSTER && cell_is_solid(defender->pos())
&& mons_wall_shielded(defender_as_monster()))
{
std::string feat_name = raw_feature_description(grd(defender->pos()));
if (attacker->atype() == ACT_PLAYER)
{
player_apply_attack_delay();
if (you.can_see(defender))
{
mprf("The %s protects %s from harm.",
feat_name.c_str(),
defender->name(DESC_NOCAP_THE).c_str());
}
else
{
mprf("You hit the %s.",
feat_name.c_str());
}
}
else if (you.can_see(attacker))
{
attacker->lose_energy(EUT_ATTACK);
if (!mons_near(defender_as_monster()))
{
simple_monster_message(attacker_as_monster(),
" hits something");
}
else if (!you.can_see(attacker))
{
mprf("%s hits the %s.", defender->name(DESC_CAP_THE).c_str(),
feat_name.c_str());
}
else
{
mprf("%s tries to hit the %s, but is blocked by the %s.",
attacker->name(DESC_CAP_THE).c_str(),
defender->name(DESC_NOCAP_THE).c_str(),
feat_name.c_str());
}
}
if (damage_brand == SPWPN_CHAOS)
chaos_affects_attacker();
return (true);
}
to_hit = calc_to_hit();
god_conduct_trigger conducts[3];
disable_attack_conducts(conducts);
if (attacker->atype() == ACT_PLAYER && attacker != defender)
set_attack_conducts(conducts, defender_as_monster());
bool retval = ((attacker->atype() == ACT_PLAYER) ? player_attack() :
(defender->atype() == ACT_PLAYER) ? mons_attack_you()
: mons_attack_mons());
if (env.sanctuary_time > 0 && retval && !cancel_attack
&& attacker != defender && !attacker->confused())
{
if (is_sanctuary(attacker->pos()) || is_sanctuary(defender->pos()))
{
if (attacker->atype() == ACT_PLAYER
|| mons_friendly(attacker_as_monster()))
{
remove_sanctuary(true);
}
}
}
if (attacker->atype() == ACT_PLAYER)
{
if (damage_brand == SPWPN_CHAOS)
chaos_affects_attacker();
do_miscast();
}
enable_attack_conducts(conducts);
return (retval);
}
static int _modify_blood_amount(const int damage, const int dam_type)
{
int factor = 0;
switch (dam_type)
{
case DVORP_CRUSHING: case DVORP_TENTACLE: factor = 2;
break;
case DVORP_SLASHING: factor = 4;
break;
case DVORP_PIERCING: factor = 5;
break;
case DVORP_STABBING: factor = 8;
break;
case DVORP_SLICING: factor = 10;
break;
case DVORP_CHOPPING: factor = 17;
break;
case DVORP_CLAWING: factor = 24;
break;
}
return (damage * factor / 10);
}
static bool _vamp_wants_blood_from_monster(const monsters *mon)
{
if (you.species != SP_VAMPIRE)
return (false);
if (you.hunger_state == HS_ENGORGED)
return (false);
if (mon->is_summoned())
return (false);
if (!mons_has_blood(mon->type))
return (false);
const int chunk_type = mons_corpse_effect( mon->type );
return (chunk_type == CE_CLEAN || chunk_type == CE_CONTAMINATED
|| chunk_type == CE_POISONOUS && player_res_poison());
}
static bool _player_vampire_draws_blood(const monsters* mon, const int damage,
bool needs_bite_msg = false)
{
ASSERT(you.species == SP_VAMPIRE);
if (!_vamp_wants_blood_from_monster(mon))
return (false);
const int chunk_type = mons_corpse_effect(mon->type);
if (needs_bite_msg && !player_in_bat_form())
{
mprf( "You bite %s, and draw %s blood!",
mon->name(DESC_NOCAP_THE, true).c_str(),
mon->pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str());
}
else
mprf( "You draw %s's blood!", mon->name(DESC_NOCAP_THE, true).c_str() );
if (you.hp < you.hp_max)
{
int heal = 1 + random2(damage);
if (heal > you.experience_level)
heal = you.experience_level;
if (chunk_type == CE_CLEAN)
heal += 1 + random2(damage);
if (player_in_bat_form())
heal /= 2;
if (heal > 0)
{
inc_hp(heal, false);
mprf("You feel %sbetter.", (you.hp == you.hp_max) ? "much " : "");
}
}
if (you.hunger_state != HS_ENGORGED)
{
int food_value = 0;
if (chunk_type == CE_CLEAN)
food_value = 30 + random2avg(59, 2);
else if (chunk_type == CE_CONTAMINATED || chunk_type == CE_POISONOUS)
food_value = 15 + random2avg(29, 2);
if (player_in_bat_form())
food_value /= 2;
lessen_hunger(food_value, false);
}
did_god_conduct(DID_DRINK_BLOOD, 5 + random2(4));
return (true);
}
bool melee_attack::player_attack()
{
if (cancel_attack)
return (false);
player_apply_attack_delay();
player_stab_check();
coord_def where = defender->pos();
if (player_hits_monster())
{
did_hit = true;
if (Options.tutorial_left)
Options.tut_melee_counter++;
const bool shield_blocked = attack_shield_blocked(true);
if (shield_blocked)
damage_done = 0;
else
{
player_calc_hit_damage();
}
if (you.duration[DUR_SLIMIFY]
&& mon_can_be_slimified(defender_as_monster()))
{
damage_done = 0;
slimify_monster(defender_as_monster());
you.duration[DUR_SLIMIFY] = 0;
return (true);
}
bool hit_woke_orc = false;
if (you.religion == GOD_BEOGH
&& defender->mons_species() == MONS_ORC
&& !defender->is_summoned()
&& !mons_is_shapeshifter(defender_as_monster())
&& !player_under_penance() && you.piety >= piety_breakpoint(2)
&& mons_near(defender_as_monster()) && defender->asleep())
{
hit_woke_orc = true;
}
behaviour_event(defender_as_monster(), ME_WHACK, MHITYOU,
coord_def(), !stab_attempt);
if (damage_done > 0
&& defender->can_bleed()
&& !defender->is_summoned()
&& !defender->submerged())
{
int blood = _modify_blood_amount(damage_done,
attacker->damage_type());
if (blood > defender->stat_hp())
blood = defender->stat_hp();
bleed_onto_floor(where, defender->id(), blood, true);
}
if (damage_done > 0 || !defender_visible && !shield_blocked)
player_announce_hit();
else if (!shield_blocked && damage_done <= 0)
{
no_damage_message =
make_stringf("You %s %s.", attack_verb.c_str(),
defender->name(DESC_NOCAP_THE).c_str());
}
player_hurt_monster();
if (damage_done)
player_exercise_combat_skills();
if (player_check_monster_died())
return (true);
player_sustain_passive_damage();
if (you.species == SP_VAMPIRE && you.hunger_state < HS_SATIATED
&& stab_attempt && stab_bonus > 0)
{
_player_vampire_draws_blood(defender_as_monster(),
damage_done, true);
}
if (shield_blocked)
did_hit = false;
if (hit_woke_orc)
{
beogh_follower_convert(defender_as_monster(), true);
return (true);
}
}
else
player_warn_miss();
if (did_hit && player_monattk_hit_effects(false))
return (true);
const bool did_primary_hit = did_hit;
if (unarmed_ok && where == defender->pos() && player_aux_unarmed())
return (true);
if ((did_primary_hit || did_hit) && defender->alive()
&& where == defender->pos())
{
print_wounds(defender_as_monster());
}
return (did_primary_hit || did_hit);
}
bool melee_attack::player_aux_unarmed()
{
unwind_var<int> save_brand(damage_brand);
damage_brand = SPWPN_NORMAL;
int uattack = UNAT_NO_ATTACK;
bool simple_miss_message = false;
std::string miss_verb;
if (can_do_unarmed)
{
if (you.species == SP_NAGA)
uattack = UNAT_HEADBUTT;
else
uattack = (coinflip() ? UNAT_HEADBUTT : UNAT_KICK);
if (player_mutation_level(MUT_FANGS)
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON)
{
uattack = UNAT_BITE;
}
if ((you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| player_genus(GENPC_DRACONIAN)
|| (you.species == SP_MERFOLK && player_is_swimming())
|| player_mutation_level(MUT_STINGER))
&& one_chance_in(3))
{
uattack = UNAT_TAILSLAP;
}
if (coinflip())
uattack = UNAT_PUNCH;
if (you.species == SP_VAMPIRE && !one_chance_in(3))
uattack = UNAT_BITE;
}
for (int scount = 0; scount < 5; scount++)
{
unarmed_attack.clear();
miss_verb.clear();
simple_miss_message = false;
damage_brand = SPWPN_NORMAL;
aux_damage = 0;
switch (scount)
{
case 0:
{
if (uattack != UNAT_KICK) {
if (!player_mutation_level(MUT_HOOVES)
&& !player_mutation_level(MUT_TALONS)
|| coinflip())
{
continue;
}
}
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT)
{
continue;
}
const bool clawed_kick = player_mutation_level(MUT_TALONS);
if (clawed_kick)
{
unarmed_attack = "claw";
miss_verb = "kick";
}
else
unarmed_attack = "kick";
aux_damage = (player_mutation_level(MUT_HOOVES) ? 10
: clawed_kick ? 8 : 5);
break;
}
case 1:
if (uattack != UNAT_HEADBUTT)
{
if (!player_mutation_level(MUT_HORNS)
&& !player_mutation_level(MUT_BEAK)
|| !one_chance_in(3))
{
continue;
}
}
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT)
{
continue;
}
if (player_mutation_level(MUT_BEAK)
&& (!player_mutation_level(MUT_HORNS) || coinflip()))
{
unarmed_attack = "peck";
aux_damage = 6;
}
else
{
unarmed_attack = "headbutt";
aux_damage = 5 + player_mutation_level(MUT_HORNS) * 3;
if (you.equip[EQ_HELMET] != -1)
{
const item_def& helmet = you.inv[you.equip[EQ_HELMET]];
if (is_hard_helmet(helmet))
{
aux_damage += 2;
if (get_helmet_desc(helmet) == THELM_DESC_SPIKED
|| get_helmet_desc(helmet) == THELM_DESC_HORNED)
{
aux_damage += 3;
}
}
}
}
break;
case 2: if (uattack != UNAT_TAILSLAP)
{
if (!player_genus(GENPC_DRACONIAN)
&& !(you.species == SP_MERFOLK && player_is_swimming())
&& !player_mutation_level(MUT_STINGER)
|| (!one_chance_in(4)))
{
continue;
}
}
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT)
{
continue;
}
if (you.religion == GOD_SHINING_ONE
&& player_mutation_level(MUT_STINGER) > 0)
{
continue;
}
unarmed_attack = "tail-slap";
aux_damage = 6;
if (player_mutation_level(MUT_STINGER) > 0)
{
aux_damage += (player_mutation_level(MUT_STINGER) * 2 - 1);
damage_brand = SPWPN_VENOM;
}
if (you.species == SP_GREY_DRACONIAN && you.experience_level >= 7)
aux_damage = 12;
break;
case 3:
if (uattack != UNAT_PUNCH)
continue;
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT)
{
continue;
}
if (shield || coinflip()
|| (weapon
&& hands == HANDS_TWO
&& weapon->base_type != OBJ_STAVES
&& weapon_skill(*weapon) != SK_STAVES))
{
continue;
}
unarmed_attack = "punch";
aux_damage = 5 + you.skills[SK_UNARMED_COMBAT] / 3;
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
{
unarmed_attack = "slash";
aux_damage += 6;
simple_miss_message = true;
}
else if (you.has_usable_claws())
{
unarmed_attack = "claw";
aux_damage += roll_dice(1, 3);
simple_miss_message = true;
}
break;
case 4:
if (uattack != UNAT_BITE)
continue;
if (!player_mutation_level(MUT_FANGS))
continue;
if (you.species != SP_VAMPIRE && one_chance_in(5)
|| one_chance_in(7))
{
continue;
}
if (you.equip[EQ_HELMET] != -1
&& (get_helmet_desc((you.inv[you.equip[EQ_HELMET]])) == THELM_DESC_VISORED))
{
continue;
}
unarmed_attack = "bite";
simple_miss_message = true;
aux_damage += player_mutation_level(MUT_FANGS) * 2
+ you.skills[SK_UNARMED_COMBAT] / 5;
if (you.species == SP_VAMPIRE)
{
if (_vamp_wants_blood_from_monster(defender_as_monster()))
{
if (you.hunger_state >= HS_SATIATED && coinflip())
break;
if (you.hunger_state != HS_STARVING && coinflip())
break;
damage_brand = SPWPN_VAMPIRICISM;
}
else if (!one_chance_in(3)) continue;
}
break;
default:
continue;
}
to_hit = random2(calc_your_to_hit_unarmed(uattack,
damage_brand == SPWPN_VAMPIRICISM));
make_hungry(2, true);
alert_nearby_monsters();
did_hit = false;
bool ely_block = false;
const int evasion = defender->melee_evasion(attacker);
if (you.religion != GOD_ELYVILON
&& you.penance[GOD_ELYVILON]
&& to_hit >= evasion
&& one_chance_in(20))
{
simple_god_message(" blocks your attack.", GOD_ELYVILON);
ely_block = true;
}
if (!ely_block && (to_hit >= evasion || one_chance_in(30)))
{
behaviour_event(defender_as_monster(), ME_WHACK, MHITYOU);
if (attack_shield_blocked(true))
continue;
if (player_apply_aux_unarmed())
return (true);
}
else
{
if (simple_miss_message)
{
mprf("You miss %s.",
defender->name(DESC_NOCAP_THE).c_str());
}
else
{
mprf("Your %s misses %s.",
miss_verb.empty() ? unarmed_attack.c_str()
: miss_verb.c_str(),
defender->name(DESC_NOCAP_THE).c_str());
}
if (ely_block)
dec_penance(GOD_ELYVILON, 1 + random2(to_hit - evasion));
}
}
return (false);
}
bool melee_attack::player_apply_aux_unarmed()
{
did_hit = true;
aux_damage = player_aux_stat_modify_damage(aux_damage);
aux_damage += slaying_bonus(PWPN_DAMAGE);
aux_damage = random2(aux_damage);
wpn_skill = SK_UNARMED_COMBAT;
aux_damage = player_apply_weapon_skill(aux_damage);
aux_damage = player_apply_fighting_skill(aux_damage, true);
aux_damage = player_apply_misc_modifiers(aux_damage);
stab_bonus = 0;
aux_damage = player_apply_monster_ac(aux_damage);
aux_damage = defender->hurt(&you, aux_damage, BEAM_MISSILE, false);
damage_done = aux_damage;
if (damage_done > 0)
{
player_exercise_combat_skills();
mprf("You %s %s%s%s",
unarmed_attack.c_str(),
defender->name(DESC_NOCAP_THE).c_str(),
debug_damage_number().c_str(),
attack_strength_punctuation().c_str());
if (damage_brand == SPWPN_VENOM && coinflip())
poison_monster(defender_as_monster(), KC_YOU);
if (damage_brand == SPWPN_VAMPIRICISM && you.species == SP_VAMPIRE
&& (!stab_attempt || stab_bonus <= 0))
{
_player_vampire_draws_blood(defender_as_monster(), damage_done);
}
}
else {
mprf("You %s %s%s.",
unarmed_attack.c_str(),
defender->name(DESC_NOCAP_THE).c_str(),
you.can_see(defender) ? ", but do no damage" : "");
}
if (defender_as_monster()->hit_points < 1)
{
_monster_die(defender_as_monster(), KILL_YOU, NON_MONSTER);
return (true);
}
return (false);
}
std::string melee_attack::debug_damage_number()
{
#ifdef DEBUG_DIAGNOSTICS
return make_stringf(" for %d", damage_done);
#else
return ("");
#endif
}
std::string melee_attack::special_attack_punctuation()
{
if (special_damage < 6)
return ".";
else
return "!";
}
std::string melee_attack::attack_strength_punctuation()
{
if (attacker->atype() == ACT_PLAYER)
{
if (damage_done < HIT_WEAK)
return ".";
else if (damage_done < HIT_MED)
return "!";
else if (damage_done < HIT_STRONG)
return "!!";
else
return "!!!";
}
else
{
return (damage_done < HIT_WEAK ? "." : "!");
}
}
void melee_attack::player_announce_hit()
{
if (!verb_degree.empty() && verb_degree[0] != ' ')
verb_degree = " " + verb_degree;
msg::stream << "You " << attack_verb << ' '
<< defender->name(DESC_NOCAP_THE)
<< verb_degree << debug_damage_number()
<< attack_strength_punctuation()
<< std::endl;
}
std::string melee_attack::player_why_missed()
{
if (heavy_armour_penalty > 0
&& (to_hit + heavy_armour_penalty/2
>= defender->melee_evasion(attacker)))
{
return "Your armour prevents you from hitting ";
}
else
return "You miss ";
}
void melee_attack::player_warn_miss()
{
did_hit = false;
if (!defender->asleep())
behaviour_event(defender_as_monster(), ME_WHACK, MHITYOU);
msg::stream << player_why_missed()
<< defender->name(DESC_NOCAP_THE)
<< "."
<< std::endl;
}
bool melee_attack::player_hits_monster()
{
const int evasion = defender->melee_evasion(attacker);
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "your to-hit: %d; defender effective EV: %d",
to_hit, evasion);
#endif
return (to_hit >= evasion
|| one_chance_in(20)
|| ((defender->cannot_act() || defender->asleep())
&& !one_chance_in(10 + you.skills[SK_STABBING]))
|| mons_is_petrifying(defender_as_monster())
&& !one_chance_in(2 + you.skills[SK_STABBING]));
}
int melee_attack::player_stat_modify_damage(int damage)
{
int dammod = 78;
const int dam_stat_val = calc_stat_to_dam_base();
if (dam_stat_val > 11)
dammod += (random2(dam_stat_val - 11) * 2);
else if (dam_stat_val < 9)
dammod -= (random2(9 - dam_stat_val) * 3);
damage *= dammod;
damage /= 78;
return (damage);
}
int melee_attack::player_aux_stat_modify_damage(int damage)
{
int dammod = 10;
const int dam_stat_val = calc_stat_to_dam_base();
if (dam_stat_val > 11)
dammod += random2(dam_stat_val - 11) / 3;
else if (dam_stat_val < 9)
dammod -= random2(9 - dam_stat_val) / 2;
damage *= dammod;
damage /= 10;
return (damage);
}
int melee_attack::player_apply_water_attack_bonus(int damage)
{
return (damage * 2);
}
int melee_attack::player_apply_weapon_skill(int damage)
{
if (weapon && (weapon->base_type == OBJ_WEAPONS
|| item_is_staff( *weapon )))
{
damage *= 25 + (random2( you.skills[ wpn_skill ] + 1 ));
damage /= 25;
}
return (damage);
}
int melee_attack::player_apply_fighting_skill(int damage, bool aux)
{
const int base = aux? 40 : 30;
damage *= base + (random2(you.skills[SK_FIGHTING] + 1));
damage /= base;
return (damage);
}
int melee_attack::player_apply_misc_modifiers(int damage)
{
if (you.duration[DUR_MIGHT] > 1)
damage += 1 + random2(10);
if (you.species != SP_VAMPIRE && you.hunger_state == HS_STARVING)
damage -= random2(5);
return (damage);
}
int melee_attack::player_apply_weapon_bonuses(int damage)
{
if (weapon && weapon->base_type == OBJ_WEAPONS)
{
int wpn_damage_plus = weapon->plus2;
damage += (wpn_damage_plus > -1) ? (random2(1 + wpn_damage_plus))
: -(1 + random2(-wpn_damage_plus));
if (hand_half_bonus)
damage += random2(3);
if (get_equip_race(*weapon) == ISFLAG_DWARVEN
&& player_genus(GENPC_DWARVEN))
{
damage += random2(3);
}
if (get_equip_race(*weapon) == ISFLAG_ORCISH
&& you.species == SP_HILL_ORC)
{
if (you.religion == GOD_BEOGH && !player_under_penance())
{
#ifdef DEBUG_DIAGNOSTICS
const int orig_damage = damage;
#endif
if (you.piety > 80 || coinflip())
damage++;
damage +=
random2avg(
div_rand_round(
std::min(static_cast<int>(you.piety), 180), 33), 2);
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Damage: %d -> %d, Beogh bonus: %d",
orig_damage, damage, damage - orig_damage);
#endif
}
if (coinflip())
damage++;
}
if (you.species == SP_DEMONSPAWN && is_demonic(*weapon))
damage += random2(3);
}
return (damage);
}
void melee_attack::player_weapon_auto_id()
{
if (weapon
&& weapon->base_type == OBJ_WEAPONS
&& !is_range_weapon( *weapon )
&& !item_ident( *weapon, ISFLAG_KNOW_PLUSES )
&& x_chance_in_y(you.skills[ wpn_skill ], 100))
{
set_ident_flags( *weapon, ISFLAG_KNOW_PLUSES );
mprf("You are wielding %s.", weapon->name(DESC_NOCAP_A).c_str());
more();
you.wield_change = true;
}
}
int melee_attack::player_stab_weapon_bonus(int damage)
{
switch (wpn_skill)
{
case SK_SHORT_BLADES:
{
int bonus = (you.dex * (you.skills[SK_STABBING] + 1)) / 5;
if (weapon->sub_type != WPN_DAGGER)
bonus /= 2;
bonus = stepdown_value( bonus, 10, 10, 30, 30 );
damage += bonus;
}
case SK_LONG_BLADES:
damage *= 10 + you.skills[SK_STABBING] /
(stab_bonus + (wpn_skill == SK_SHORT_BLADES ? 0 : 1));
damage /= 10;
default:
damage *= 12 + you.skills[SK_STABBING] / stab_bonus;
damage /= 12;
}
return (damage);
}
int melee_attack::player_stab(int damage)
{
if (stab_attempt)
{
stab_message(defender, stab_bonus);
exercise(SK_STABBING, 1 + random2avg(5, 4));
did_god_conduct(DID_STABBING, 4);
}
else
{
stab_bonus = 0;
alert_nearby_monsters();
}
if (stab_bonus)
{
damage = std::max(1, damage);
if (defender->asleep())
{
if (x_chance_in_y(you.skills[SK_STABBING] + you.dex + 1, 200))
{
int stun = random2(you.dex + 1);
if (defender_as_monster()->speed_increment > stun)
defender_as_monster()->speed_increment -= stun;
else
defender_as_monster()->speed_increment = 0;
}
}
damage = player_stab_weapon_bonus(damage);
}
return (damage);
}
int melee_attack::player_apply_monster_ac(int damage)
{
if (stab_bonus)
{
if (defender->armour_class() > 0)
{
const int ac = defender->armour_class()
- random2(you.skills[SK_STABBING] / stab_bonus);
if (ac > 0)
damage -= random2(1 + ac);
}
}
else
{
if (defender->armour_class() > 0)
damage -= random2(1 + defender->armour_class());
}
if (mons_is_petrified(defender_as_monster()))
damage /= 3;
return (damage);
}
int melee_attack::player_weapon_type_modify(int damage)
{
int weap_type = WPN_UNKNOWN;
if (!weapon)
weap_type = WPN_UNARMED;
else if (item_is_staff(*weapon))
weap_type = WPN_QUARTERSTAFF;
else if (weapon->base_type == OBJ_WEAPONS)
weap_type = weapon->sub_type;
if (damage < HIT_WEAK
&& (you.species != SP_VAMPIRE || !player_in_bat_form()))
{
if (weap_type != WPN_UNKNOWN)
attack_verb = "hit";
else
attack_verb = "clumsily bash";
return (damage);
}
if (weap_type == WPN_UNARMED
&& you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE)
{
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_SPIDER:
case TRAN_BAT:
case TRAN_PIG:
if (damage < HIT_STRONG)
attack_verb = "bite";
else
attack_verb = "maul";
break;
case TRAN_BLADE_HANDS:
if (damage < HIT_MED)
attack_verb = "slash";
else if (damage < HIT_STRONG)
attack_verb = "slice";
else
attack_verb = "shred";
break;
case TRAN_STATUE:
case TRAN_LICH:
if (you.has_usable_claws())
{
if (damage < HIT_MED)
attack_verb = "claw";
else if (damage < HIT_STRONG)
attack_verb = "mangle";
else
attack_verb = "eviscerate";
break;
}
case TRAN_ICE_BEAST:
if (damage < HIT_MED)
attack_verb = "punch";
else
attack_verb = "pummel";
break;
case TRAN_DRAGON:
if (damage < HIT_MED)
attack_verb = "claw";
else if (damage < HIT_STRONG)
attack_verb = "bite";
else
{
attack_verb = "maul";
if (defender->body_size() <= SIZE_MEDIUM && coinflip())
attack_verb = "trample on";
}
break;
}
return (damage);
}
switch (weapon ? single_damage_type(*weapon) : -1)
{
case DAM_PIERCE:
if (damage < HIT_MED)
attack_verb = "puncture";
else if (damage < HIT_STRONG)
attack_verb = "impale";
else
{
attack_verb = "spit";
if (defender->atype() == ACT_MONSTER
&& defender_visible
&& mons_genus(defender_as_monster()->type) == MONS_HOG)
{
verb_degree = " like the proverbial pig";
}
else
verb_degree = " like a pig";
}
break;
case DAM_SLICE:
if (damage < HIT_MED)
attack_verb = "slash";
else if (damage < HIT_STRONG)
attack_verb = "slice";
else if (mons_genus(defender_as_monster()->type) == MONS_OGRE)
{
attack_verb = "dice";
verb_degree = " like an onion";
}
else
{
attack_verb = "open";
verb_degree = " like a pillowcase";
}
break;
case DAM_BLUDGEON:
if (damage < HIT_MED)
attack_verb = one_chance_in(4) ? "thump" : "sock";
else if (damage < HIT_STRONG)
attack_verb = "bludgeon";
else
{
attack_verb = "crush";
verb_degree = " like a grape";
}
break;
case DAM_WHIP:
if (damage < HIT_MED)
attack_verb = "whack";
else
attack_verb = "thrash";
break;
case -1: if (you.damage_type() == DVORP_CLAWING)
{
if (damage < HIT_MED)
attack_verb = "claw";
else if (damage < HIT_STRONG)
attack_verb = "mangle";
else
attack_verb = "eviscerate";
}
else
{
if (damage < HIT_MED)
attack_verb = "punch";
else
attack_verb = "pummel";
}
break;
case WPN_UNKNOWN:
default:
attack_verb = "hit";
break;
}
return (damage);
}
bool melee_attack::player_hurt_monster()
{
return damage_done && (damage_done = defender->hurt(&you, damage_done,
BEAM_MISSILE, false));
}
void melee_attack::player_exercise_combat_skills()
{
const bool helpless = defender->cannot_fight();
if (!helpless || you.skills[wpn_skill] < 1)
exercise(wpn_skill, 1);
if ((!helpless || you.skills[SK_FIGHTING] < 1)
&& one_chance_in(3))
{
exercise(SK_FIGHTING, 1);
}
}
void melee_attack::player_check_weapon_effects()
{
if (weapon && weapon->base_type == OBJ_WEAPONS)
{
if (is_holy_item(*weapon))
did_god_conduct(DID_HOLY, 1);
else if (is_demonic(*weapon))
did_god_conduct(DID_UNHOLY, 1);
else if (get_weapon_brand(*weapon) == SPWPN_SPEED
|| weapon->sub_type == WPN_QUICK_BLADE)
{
did_god_conduct(DID_HASTY, 1);
}
}
}
bool melee_attack::player_monattk_final_hit_effects(bool mondied)
{
for (unsigned int i = 0; i < final_effects.size(); ++i)
{
switch (final_effects[i].flavor)
{
case FINEFF_LIGHTNING_DISCHARGE:
if (see_cell(final_effects[i].location))
mpr("Electricity arcs through the water!");
conduct_electricity(final_effects[i].location, attacker);
break;
}
}
return mondied;
}
bool melee_attack::player_monattk_hit_effects(bool mondied)
{
player_check_weapon_effects();
mondied = check_unrand_effects(mondied) || mondied;
if (you.species == SP_VAMPIRE && you.hunger_state < HS_SATIATED
&& mondied && stab_attempt && stab_bonus > 0
&& _player_vampire_draws_blood(defender_as_monster(),
damage_done, true))
{
}
else if (mondied && damage_brand == SPWPN_VAMPIRICISM
&& you.equip[EQ_WEAPON] != -1)
{
if (defender->holiness() == MH_NATURAL
&& !defender->is_summoned()
&& damage_done > 0
&& you.hp < you.hp_max
&& !one_chance_in(5))
{
mpr("You feel better.");
const int heal = 1 + random2(damage_done);
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"Vampiric healing: damage %d, healed %d",
damage_done, heal);
#endif
inc_hp(heal, false);
if (you.hunger_state != HS_ENGORGED)
lessen_hunger(30 + random2avg(59, 2), false);
did_god_conduct(DID_NECROMANCY, 2);
}
}
if (mondied)
return player_monattk_final_hit_effects(true);
if (decapitate_hydra(damage_done))
return player_monattk_final_hit_effects(!defender->alive());
player_apply_staff_damage();
if (!special_damage && apply_damage_brand())
return player_monattk_final_hit_effects(true);
if (!no_damage_message.empty())
{
if (special_damage > 0)
emit_nodmg_hit_message();
else
{
mprf("You %s %s, but do no damage.",
attack_verb.c_str(),
defender->name(DESC_NOCAP_THE).c_str());
}
}
if (needs_message && !special_damage_message.empty())
{
mprf("%s", special_damage_message.c_str());
if (miscast_level == 0)
miscast_level = -1;
}
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Special damage to %s: %d",
defender->name(DESC_NOCAP_THE).c_str(),
special_damage);
#endif
special_damage = defender->hurt(&you, special_damage, BEAM_MISSILE, false);
if (!defender->alive())
{
_monster_die(defender_as_monster(), KILL_YOU, NON_MONSTER);
return player_monattk_final_hit_effects(true);
}
return player_monattk_final_hit_effects(false);
}
void melee_attack::_monster_die(monsters* monster, killer_type killer,
int killer_index)
{
const bool chaos = (damage_brand == SPWPN_CHAOS);
monsters* def_copy = NULL;
if (chaos)
def_copy = new monsters(*monster);
if (monster == defender && killer != KILL_RESET)
monster->attitude = defender_starting_attitude;
monster_die(monster, killer, killer_index);
if (chaos)
{
chaos_killed_defender(def_copy);
delete def_copy;
}
}
static bool is_boolean_resist(beam_type flavour)
{
switch (flavour)
{
case BEAM_ELECTRICITY:
case BEAM_MIASMA: case BEAM_NAPALM:
return (true);
default:
return (false);
}
}
static inline int get_resistible_fraction(beam_type flavour)
{
switch (flavour)
{
case BEAM_ICE:
return (25);
case BEAM_LAVA:
return (35);
case BEAM_POISON_ARROW:
return (40);
default:
return (100);
}
}
int resist_adjust_damage(actor *defender, beam_type flavour,
int res, int rawdamage, bool ranged)
{
if (!res)
return (rawdamage);
const bool monster = (defender->atype() == ACT_MONSTER);
const int resistible_fraction = get_resistible_fraction(flavour);
int resistible = rawdamage * resistible_fraction / 100;
const int irresistible = rawdamage - resistible;
if (res > 0)
{
if (monster && res >= 3)
resistible = 0;
else
{
const int bonus_res = (is_boolean_resist(flavour) ? 1 : 0);
if (monster)
resistible /= 1 + bonus_res + res * res;
else
resistible /= resist_fraction(res, bonus_res);
}
}
else if (res < 0)
resistible = resistible * (ranged? 15 : 20) / 10;
return std::max(resistible + irresistible, 0);
}
void melee_attack::calc_elemental_brand_damage( beam_type flavour,
int res,
const char *verb)
{
special_damage = resist_adjust_damage(defender, flavour, res,
random2(damage_done) / 2 + 1);
if (special_damage > 0 && verb && needs_message)
{
special_damage_message = make_stringf(
"%s %s %s%s",
atk_name(DESC_CAP_THE).c_str(),
attacker->conj_verb(verb).c_str(),
mons_defender_name().c_str(),
special_attack_punctuation().c_str());
}
}
int melee_attack::fire_res_apply_cerebov_downgrade(int res)
{
if (weapon && weapon->special == UNRAND_CEREBOV)
--res;
return (res);
}
void melee_attack::drain_defender()
{
if (defender->atype() == ACT_MONSTER && one_chance_in(3))
return;
special_damage = 1 + random2(damage_done)
/ (2 + defender->res_negative_energy());
if (defender->drain_exp(attacker, true))
{
if (defender->atype() == ACT_PLAYER)
obvious_effect = true;
else if (defender_visible)
{
special_damage_message =
make_stringf(
"%s %s %s!",
atk_name(DESC_CAP_THE).c_str(),
attacker->conj_verb("drain").c_str(),
mons_defender_name().c_str());
}
attacker->god_conduct(DID_NECROMANCY, 2);
}
}
void melee_attack::rot_defender(int amount, int immediate)
{
if (defender->rot(attacker, amount, immediate, true))
{
if (defender->atype() == ACT_MONSTER && defender_visible)
{
special_damage_message =
make_stringf(
"%s %s!",
def_name(DESC_CAP_THE).c_str(),
amount > 0 ? "rots" : "looks less resilient");
}
}
}
bool melee_attack::distortion_affects_defender()
{
if (defender->id() == MONS_BLINK_FROG
|| defender->id() == MONS_PRINCE_RIBBIT)
{
if (one_chance_in(5))
{
emit_nodmg_hit_message();
if (defender_visible)
{
special_damage_message =
make_stringf("%s %s in the translocular energy.",
def_name(DESC_CAP_THE).c_str(),
defender->conj_verb("bask").c_str());
}
defender->heal(1 + random2avg(7, 2), true); }
return (false);
}
if (one_chance_in(3))
{
if (defender_visible)
{
special_damage_message =
make_stringf(
"Space bends around %s.",
def_name(DESC_NOCAP_THE).c_str());
}
special_damage += 1 + random2avg(7, 2);
return (false);
}
if (one_chance_in(3))
{
if (defender_visible)
{
special_damage_message =
make_stringf(
"Space warps horribly around %s!",
def_name(DESC_NOCAP_THE).c_str());
}
special_damage += 3 + random2avg(24, 2);
return (false);
}
if (one_chance_in(3))
{
emit_nodmg_hit_message();
if (defender_visible)
obvious_effect = true;
defender->blink();
return (false);
}
if (!one_chance_in(3))
{
emit_nodmg_hit_message();
if (defender_visible)
obvious_effect = true;
defender->teleport(coinflip(), one_chance_in(5));
return (false);
}
if (you.level_type != LEVEL_ABYSS && coinflip())
{
emit_nodmg_hit_message();
if (defender->atype() == ACT_PLAYER && attacker_visible
&& weapon != NULL && !is_unrandom_artefact(*weapon)
&& !is_special_unrandom_artefact(*weapon))
{
if (is_artefact(*weapon))
artefact_wpn_learn_prop(*weapon, ARTP_BRAND);
else
set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
}
else if (defender_visible)
obvious_effect = true;
defender->banish(attacker->name(DESC_PLAIN, true));
return (true);
}
return (false);
}
enum chaos_type
{
CHAOS_CLONE,
CHAOS_POLY,
CHAOS_POLY_UP,
CHAOS_MAKE_SHIFTER,
CHAOS_MISCAST,
CHAOS_RAGE,
CHAOS_HEAL,
CHAOS_HASTE,
CHAOS_INVIS,
CHAOS_SLOW,
CHAOS_PARALYSIS,
CHAOS_PETRIFY,
NUM_CHAOS_TYPES
};
void melee_attack::chaos_affects_defender()
{
const bool mon = defender->atype() == ACT_MONSTER;
const bool immune = mon && mons_immune_magic(defender_as_monster());
const bool is_natural = mon && defender->holiness() == MH_NATURAL;
const bool is_shifter = mon && mons_is_shapeshifter(defender_as_monster());
const bool can_clone = mon && !mons_is_holy(defender_as_monster())
&& mons_clonable(defender_as_monster(), true);
const bool can_poly = is_shifter || (defender->can_safely_mutate()
&& !immune);
const bool can_rage = defender->can_go_berserk();
int clone_chance = can_clone ? 1 : 0;
int poly_chance = can_poly ? 1 : 0;
int poly_up_chance = can_poly && mon ? 1 : 0;
int shifter_chance = can_poly && is_natural && mon ? 1 : 0;
int rage_chance = can_rage ? 10 : 0;
int miscast_chance = 10;
if (is_shifter)
shifter_chance = 0;
if (attacker == defender)
{
clone_chance *= 2;
poly_chance *= 2;
poly_up_chance *= 2;
shifter_chance *= 2;
miscast_chance *= 2;
if (see_cell(defender->pos()))
{
if (defender->atype() == ACT_PLAYER)
mpr("You give off a flash of multicoloured light!");
else if (you.can_see(defender))
{
simple_monster_message(defender_as_monster(),
" gives off a flash of "
"multicoloured light!");
}
else
mpr("There is a flash of multicoloured light!");
}
}
int probs[NUM_CHAOS_TYPES] =
{
clone_chance, poly_chance, poly_up_chance, shifter_chance, miscast_chance, rage_chance,
10, 10, 10,
10, 10, 10, };
bolt beam;
beam.flavour = BEAM_NONE;
int choice = choose_random_weighted(probs, probs + NUM_CHAOS_TYPES);
#ifdef NOTE_DEBUG_CHAOS_EFFECTS
std::string chaos_effect = "CHAOS effect: ";
switch (choice)
{
case CHAOS_CLONE: chaos_effect += "clone"; break;
case CHAOS_POLY: chaos_effect += "polymorph"; break;
case CHAOS_POLY_UP: chaos_effect += "polymorph PPT_MORE"; break;
case CHAOS_MAKE_SHIFTER: chaos_effect += "shifter"; break;
case CHAOS_MISCAST: chaos_effect += "miscast"; break;
case CHAOS_RAGE: chaos_effect += "berserk"; break;
case CHAOS_HEAL: chaos_effect += "healing"; break;
case CHAOS_HASTE: chaos_effect += "hasting"; break;
case CHAOS_INVIS: chaos_effect += "invisible"; break;
case CHAOS_SLOW: chaos_effect += "slowing"; break;
case CHAOS_PARALYSIS: chaos_effect += "paralysis"; break;
case CHAOS_PETRIFY: chaos_effect += "petrify"; break;
default: chaos_effect += "(other)"; break;
}
take_note(Note(NOTE_MESSAGE, 0, 0, chaos_effect.c_str()), true);
#endif
switch (static_cast<chaos_type>(choice))
{
case CHAOS_CLONE:
{
ASSERT(can_clone && clone_chance > 0);
ASSERT(defender->atype() == ACT_MONSTER);
int clone_idx = clone_mons(defender_as_monster(), true,
&obvious_effect);
if (clone_idx != NON_MONSTER)
{
if (obvious_effect)
{
special_damage_message =
make_stringf("%s is duplicated!",
def_name(DESC_NOCAP_THE).c_str());
}
monsters &clone(menv[clone_idx]);
if (clone.attitude == ATT_FRIENDLY && !clone.is_summoned())
clone.mark_summoned(6, true, MON_SUMM_CLONE);
xom_is_stimulated(mons_friendly(&clone) ? 16 : 32);
}
break;
}
case CHAOS_POLY:
ASSERT(can_poly && poly_chance > 0);
beam.flavour = BEAM_POLYMORPH;
break;
case CHAOS_POLY_UP:
ASSERT(can_poly && poly_up_chance > 0);
ASSERT(defender->atype() == ACT_MONSTER);
obvious_effect = you.can_see(defender);
monster_polymorph(defender_as_monster(), RANDOM_MONSTER, PPT_MORE);
break;
case CHAOS_MAKE_SHIFTER:
{
ASSERT(can_poly && shifter_chance > 0);
ASSERT(!is_shifter);
ASSERT(defender->atype() == ACT_MONSTER);
obvious_effect = you.can_see(defender);
defender_as_monster()->add_ench(one_chance_in(3) ?
ENCH_GLOWING_SHAPESHIFTER : ENCH_SHAPESHIFTER);
monster_polymorph(defender_as_monster(), RANDOM_MONSTER);
const int friend_factor = mons_friendly(defender_as_monster()) ? 1 : 2;
const int glow_factor =
(defender_as_monster()->has_ench(ENCH_SHAPESHIFTER) ? 1 : 2);
xom_is_stimulated( 64 * friend_factor * glow_factor );
break;
}
case CHAOS_MISCAST:
{
int level = defender->get_experience_level();
int level0_chance = level;
int level1_chance = std::max( 0, level - 7);
int level2_chance = std::max( 0, level - 12);
int level3_chance = std::max( 0, level - 17);
level = random_choose_weighted(
level0_chance, 0,
level1_chance, 1,
level2_chance, 2,
level3_chance, 3,
0);
miscast_level = level;
miscast_type = SPTYP_RANDOM;
miscast_target = one_chance_in(3) ? attacker : defender;
break;
}
case CHAOS_RAGE:
ASSERT(can_rage && rage_chance > 0);
defender->go_berserk(false);
obvious_effect = you.can_see(defender);
break;
case CHAOS_HEAL:
beam.flavour = BEAM_HEALING;
break;
case CHAOS_HASTE:
beam.flavour = BEAM_HASTE;
break;
case CHAOS_INVIS:
beam.flavour = BEAM_INVISIBILITY;
break;
case CHAOS_SLOW:
beam.flavour = BEAM_SLOW;
break;
case CHAOS_PARALYSIS:
beam.flavour = BEAM_PARALYSIS;
break;
case CHAOS_PETRIFY:
beam.flavour = BEAM_PETRIFY;
break;
default:
ASSERT(!"Invalid chaos effect type");
break;
}
if (beam.flavour != BEAM_NONE)
{
beam.type = 0;
beam.range = 0;
beam.colour = BLACK;
beam.effect_known = false;
if (weapon && you.can_see(attacker))
{
beam.name = wep_name(DESC_NOCAP_YOUR);
beam.item = weapon;
}
else
beam.name = atk_name(DESC_NOCAP_THE);
beam.thrower =
(attacker->atype() == ACT_PLAYER) ? KILL_YOU
: attacker_as_monster()->confused_by_you() ? KILL_YOU_CONF
: KILL_MON;
if (beam.thrower == KILL_YOU || mons_friendly(attacker_as_monster()))
beam.attitude = ATT_FRIENDLY;
beam.beam_source = attacker->mindex();
beam.source = defender->pos();
beam.target = defender->pos();
beam.damage = dice_def(damage_done + special_damage + aux_damage, 1);
beam.ench_power = beam.damage.num;
beam.fire();
if (you.can_see(defender))
obvious_effect = beam.obvious_effect;
}
if (!you.can_see(attacker))
obvious_effect = false;
}
static bool _move_stairs(const actor* attacker, const actor* defender)
{
const coord_def orig_pos = attacker->pos();
const dungeon_feature_type stair_feat = grd(orig_pos);
if (feat_stair_direction(stair_feat) == CMD_NO_CMD)
return (false);
if (stair_feat == DNGN_ENTER_SHOP)
return (false);
if (is_notable_terrain(stair_feat)
&& is_terrain_known(orig_pos.x, orig_pos.y) && !see_cell(orig_pos))
{
return (false);
}
coord_def dest(-1, -1);
if (defender->alive() && defender->pos() != attacker->pos())
dest = defender->pos();
return slide_feature_over(attacker->pos(), dest);
}
#define DID_AFFECT() \
{ \
if (miscast_level == 0) \
miscast_level = -1; \
return; \
}
void melee_attack::chaos_affects_attacker()
{
if (miscast_level >= 1 || !attacker->alive())
return;
if (one_chance_in(100) && _move_stairs(attacker, defender))
{
#ifdef NOTE_DEBUG_CHAOS_EFFECTS
take_note(Note(NOTE_MESSAGE, 0, 0,
"CHAOS affects attacker: move stairs"), true);
#endif
DID_AFFECT();
}
if (is_valid_shaft_level()
&& (attacker->will_trigger_shaft()
|| igrd(attacker->pos()) != NON_ITEM)
&& one_chance_in(1000))
{
(void) attacker->do_shaft();
#ifdef NOTE_DEBUG_CHAOS_EFFECTS
take_note(Note(NOTE_MESSAGE, 0, 0,
"CHAOS affects attacker: shaft effect"), true);
#endif
DID_AFFECT();
}
if (weapon && one_chance_in(1000))
{
mprf("Smoke pours forth from %s!", wep_name(DESC_NOCAP_YOUR).c_str());
big_cloud(random_smoke_type(), KC_OTHER, attacker->pos(), 20,
4 + random2(8));
#ifdef NOTE_DEBUG_CHAOS_EFFECTS
take_note(Note(NOTE_MESSAGE, 0, 0,
"CHAOS affects attacker: smoke"), true);
#endif
DID_AFFECT();
}
if (weapon && player_can_hear(attacker->pos())
&& one_chance_in(200))
{
std::string msg = "";
if (!you.can_see(attacker))
{
std::string noise = getSpeakString("weapon_noise");
if (!noise.empty())
msg = "You hear " + noise;
}
else
{
msg = getSpeakString("weapon_noises");
std::string wepname = wep_name(DESC_CAP_YOUR);
if (!msg.empty())
{
msg = replace_all(msg, "@Your_weapon@", wepname);
msg = replace_all(msg, "@The_weapon@", wepname);
}
}
if (!msg.empty())
{
mpr(msg.c_str(), MSGCH_SOUND);
noisy(15, attacker->pos(), attacker->mindex());
#ifdef NOTE_DEBUG_CHAOS_EFFECTS
take_note(Note(NOTE_MESSAGE, 0, 0,
"CHAOS affects attacker: noise"), true);
#endif
DID_AFFECT();
}
}
}
static void _find_remains(monsters* mon, int &corpse_class, int &corpse_index,
item_def &fake_corpse, int &last_item,
std::vector<int> items)
{
for (int i = 0; i < NUM_MONSTER_SLOTS; ++i)
{
const int idx = mon->inv[i];
if (idx == NON_ITEM)
continue;
item_def &item(mitm[idx]);
if (!is_valid_item(item) || item.pos != mon->pos())
continue;
items.push_back(idx);
}
corpse_index = NON_ITEM;
last_item = NON_ITEM;
corpse_class = fill_out_corpse(mon, fake_corpse, true);
if (corpse_class == -1 || mons_weight(corpse_class) == 0)
return;
for (stack_iterator si(mon->pos()); si; ++si)
{
if (si->base_type == OBJ_CORPSES && si->sub_type == CORPSE_BODY)
{
if (si->orig_monnum != fake_corpse.orig_monnum
|| si->plus != fake_corpse.plus
|| si->plus2 != fake_corpse.plus2
|| si->special != fake_corpse.special
|| si->flags != fake_corpse.flags)
{
break;
}
if ((short) mon->number != si->props[MONSTER_NUMBER].get_short())
break;
corpse_index = si.link();
break;
}
else
{
for (unsigned int i = 0; i < items.size(); i++)
if (items[i] == si.link())
last_item = si.link();
}
}
}
static bool _make_zombie(monsters* mon, int corpse_class, int corpse_index,
item_def &fake_corpse, int last_item)
{
if (corpse_index != NON_ITEM || !mons_class_can_be_zombified(corpse_class))
return (false);
if (is_good_god(mon->god))
return (false);
int zombie_index = -1;
int idx = get_item_slot(0);
if (idx != NON_ITEM && last_item != NON_ITEM)
{
mitm[idx] = fake_corpse;
mitm[idx].pos = mon->pos();
mitm[idx].link = mitm[last_item].link;
mitm[last_item].link = idx;
animate_remains(mon->pos(), CORPSE_BODY, mon->behaviour,
mon->foe, mon->god, true, true, true, &zombie_index);
}
if (zombie_index == -1)
{
monster_type type = (mons_zombie_size(mon->type) == Z_SMALL) ?
MONS_ZOMBIE_SMALL : MONS_ZOMBIE_LARGE;
zombie_index = create_monster(
mgen_data(type, mon->behaviour, 0, 0, mon->pos(),
mon->foe, MG_FORCE_PLACE, mon->god,
mon->type, mon->number));
}
if (zombie_index == -1)
return (false);
monsters *zombie = &menv[zombie_index];
if (zombie->pos() != mon->pos() && zombie->is_habitable(mon->pos()))
zombie->move_to_pos(mon->pos());
if (you.can_see(mon))
{
if (you.can_see(zombie))
simple_monster_message(mon, " instantly turns into a zombie!");
else if (last_item != NON_ITEM)
{
simple_monster_message(mon, "'s equipment vanishes!");
autotoggle_autopickup(true);
}
}
else
{
simple_monster_message(zombie, " appears from thin air!");
autotoggle_autopickup(false);
}
player_angers_monster(zombie);
return (true);
}
void melee_attack::chaos_killed_defender(monsters* mon)
{
ASSERT(mon->type != -1 && mon->type != MONS_NO_MONSTER);
ASSERT(in_bounds(mon->pos()));
ASSERT(!defender->alive());
if (!attacker->alive())
return;
if (attacker->atype() == ACT_PLAYER && you.banished)
return;
if (mon->is_summoned() || (mon->flags & (MF_BANISHED | MF_HARD_RESET)))
return;
int corpse_class, corpse_index, last_item;
item_def fake_corpse;
std::vector<int> items;
_find_remains(mon, corpse_class, corpse_index, fake_corpse, last_item,
items);
if (one_chance_in(100)
&& _make_zombie(mon, corpse_class, corpse_index, fake_corpse,
last_item))
{
#ifdef NOTE_DEBUG_CHAOS_EFFECTS
take_note(Note(NOTE_MESSAGE, 0, 0,
"CHAOS killed defender: zombified monster"), true);
#endif
DID_AFFECT();
}
}
void melee_attack::do_miscast()
{
if (miscast_level == -1)
return;
ASSERT(miscast_target != NULL);
ASSERT(miscast_level >= 0 && miscast_level <= 3);
ASSERT(count_bits(miscast_type) == 1);
if (!miscast_target->alive())
return;
if (miscast_target->atype() == ACT_PLAYER && you.banished)
return;
const bool chaos_brand =
weapon && get_weapon_brand(*weapon) == SPWPN_CHAOS;
std::string hand_str;
std::string cause = atk_name(DESC_NOCAP_THE);
int source;
const int ignore_mask = ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES;
if (attacker->atype() == ACT_PLAYER)
{
source = NON_MONSTER;
if (chaos_brand)
{
cause = "a chaos effect from ";
cause += wep_name(DESC_NOCAP_YOUR,
ignore_mask
| ISFLAG_COSMETIC_MASK | ISFLAG_RACIAL_MASK);
if (miscast_target == attacker)
hand_str = wep_name(DESC_PLAIN, ignore_mask);
}
}
else
{
source = attacker->mindex();
if (chaos_brand && miscast_target == attacker
&& you.can_see(attacker))
{
hand_str = wep_name(DESC_PLAIN, ignore_mask);
}
}
MiscastEffect(miscast_target, source, (spschool_flag_type) miscast_type,
miscast_level, cause, NH_NEVER, 0, hand_str, false);
miscast_level = -1;
}
int melee_attack::random_chaos_brand()
{
int brand = SPWPN_NORMAL;
while (true)
{
brand = (random_choose_weighted(
5, SPWPN_VORPAL,
10, SPWPN_FLAMING,
10, SPWPN_FREEZING,
10, SPWPN_ELECTROCUTION,
10, SPWPN_VENOM,
10, SPWPN_CHAOS,
5, SPWPN_DRAINING,
5, SPWPN_VAMPIRICISM,
5, SPWPN_HOLY_WRATH,
2, SPWPN_CONFUSE,
2, SPWPN_DISTORTION,
0));
if (one_chance_in(3))
break;
bool susceptible = true;
switch (brand)
{
case SPWPN_FLAMING:
if (defender->is_fiery())
susceptible = false;
break;
case SPWPN_FREEZING:
if (defender->is_icy())
susceptible = false;
break;
case SPWPN_ELECTROCUTION:
if (defender->airborne())
susceptible = false;
break;
case SPWPN_VENOM:
if (defender->holiness() == MH_UNDEAD)
susceptible = false;
break;
case SPWPN_VAMPIRICISM:
if (defender->is_summoned())
{
susceptible = false;
break;
}
case SPWPN_DRAINING:
if (defender->holiness() != MH_NATURAL)
susceptible = false;
break;
case SPWPN_HOLY_WRATH:
if (defender->holiness() != MH_UNDEAD
&& defender->holiness() != MH_DEMONIC)
{
susceptible = false;
}
break;
case SPWPN_CONFUSE:
if (defender->holiness() == MH_NONLIVING
|| defender->holiness() == MH_PLANT)
{
susceptible = false;
}
break;
default:
break;
}
if (susceptible)
break;
}
#ifdef NOTE_DEBUG_CHAOS_BRAND
std::string brand_name = "CHAOS brand: ";
switch (brand)
{
case SPWPN_NORMAL: brand_name += "(plain)"; break;
case SPWPN_FLAMING: brand_name += "flaming"; break;
case SPWPN_FREEZING: brand_name += "freezing"; break;
case SPWPN_HOLY_WRATH: brand_name += "holy wrath"; break;
case SPWPN_ELECTROCUTION: brand_name += "electrocution"; break;
case SPWPN_VENOM: brand_name += "venom"; break;
case SPWPN_DRAINING: brand_name += "draining"; break;
case SPWPN_DISTORTION: brand_name += "distortion"; break;
case SPWPN_VAMPIRICISM: brand_name += "vampiricism"; break;
case SPWPN_VORPAL: brand_name += "vorpal"; break;
case SPWPN_FLAME: brand_name += "flame"; break;
case SPWPN_FROST: brand_name += "frost"; break;
case SPWPN_CHAOS: brand_name += "chaos"; break;
case SPWPN_CONFUSE: brand_name += "confusion"; break;
default: brand_name += "(other)"; break;
}
if (brand != SPWPN_CHAOS)
take_note(Note(NOTE_MESSAGE, 0, 0, brand_name.c_str()), true);
#endif
return (brand);
}
mon_attack_flavour melee_attack::random_chaos_attack_flavour()
{
mon_attack_flavour flavours[] =
{AF_FIRE, AF_COLD, AF_ELEC, AF_POISON_NASTY, AF_VAMPIRIC, AF_DISTORT,
AF_CONFUSE, AF_CHAOS};
return (RANDOM_ELEMENT(flavours));
}
bool melee_attack::apply_damage_brand()
{
bool brand_was_known = false;
if (weapon)
{
if (is_artefact(*weapon))
brand_was_known = artefact_known_wpn_property(*weapon, ARTP_BRAND);
else
brand_was_known = item_type_known(*weapon);
}
bool ret = false;
int res = 0;
special_damage = 0;
obvious_effect = false;
int brand;
if (damage_brand == SPWPN_CHAOS)
brand = random_chaos_brand();
else
brand = damage_brand;
switch (brand)
{
case SPWPN_FLAMING:
res = fire_res_apply_cerebov_downgrade(defender->res_fire());
calc_elemental_brand_damage(BEAM_FIRE, res,
defender->is_icy() ? "melt" : "burn");
defender->expose_to_element(BEAM_FIRE);
break;
case SPWPN_FREEZING:
calc_elemental_brand_damage(BEAM_COLD, defender->res_cold(), "freeze");
defender->expose_to_element(BEAM_COLD);
break;
case SPWPN_HOLY_WRATH:
if (defender->is_unholy())
special_damage = 1 + (random2(damage_done * 15) / 10);
if (special_damage && defender_visible)
{
special_damage_message =
make_stringf(
"%s %s%s",
def_name(DESC_CAP_THE).c_str(),
defender->conj_verb("convulse").c_str(),
special_attack_punctuation().c_str());
}
break;
case SPWPN_ELECTROCUTION:
if (defender->airborne() || defender->res_elec() > 0)
break;
else if (one_chance_in(3))
{
special_damage_message =
defender->atype() == ACT_PLAYER?
"You are electrocuted!"
: "There is a sudden explosion of sparks!";
special_damage = 10 + random2(15);
const coord_def& pos = defender->pos();
if (feat_is_water(grd(pos)))
{
attack_final_effect effect;
effect.flavor = FINEFF_LIGHTNING_DISCHARGE;
effect.location = pos;
final_effects.push_back(effect);
}
}
break;
case SPWPN_ORC_SLAYING:
if (is_orckind(defender))
{
special_damage = 1 + random2(3*damage_done/2);
if (defender_visible)
{
special_damage_message =
make_stringf(
"%s %s%s",
defender->name(DESC_CAP_THE).c_str(),
defender->conj_verb("convulse").c_str(),
special_attack_punctuation().c_str());
}
}
break;
case SPWPN_DRAGON_SLAYING:
if (is_dragonkind(defender))
{
special_damage = 1 + random2(3*damage_done/2);
if (defender_visible)
{
special_damage_message =
make_stringf(
"%s %s%s",
defender->name(DESC_CAP_THE).c_str(),
defender->conj_verb("convulse").c_str(),
special_attack_punctuation().c_str());
}
}
break;
case SPWPN_VENOM:
if (!one_chance_in(4))
{
int old_poison;
if (defender->atype() == ACT_PLAYER)
old_poison = you.duration[DUR_POISONING];
else
{
old_poison =
(defender_as_monster()->get_ench(ENCH_POISON)).degree;
}
emit_nodmg_hit_message();
defender->poison(attacker, 2);
if (defender->atype() == ACT_PLAYER
&& old_poison < you.duration[DUR_POISONING]
|| defender->atype() != ACT_PLAYER
&& old_poison <
(defender_as_monster()->get_ench(ENCH_POISON)).degree)
{
obvious_effect = true;
}
}
break;
case SPWPN_DRAINING:
drain_defender();
break;
case SPWPN_VORPAL:
special_damage = 1 + random2(damage_done) / 2;
break;
case SPWPN_VAMPIRICISM:
{
if (attacker->atype() == ACT_PLAYER && you.species == SP_VAMPIRE
&& player_in_bat_form())
{
_player_vampire_draws_blood(defender_as_monster(), damage_done);
break;
}
if (x_chance_in_y(defender->res_negative_energy(), 3))
break;
if (!weapon || defender->holiness() != MH_NATURAL || damage_done < 1
|| attacker->stat_hp() == attacker->stat_maxhp()
|| defender->atype() != ACT_PLAYER
&& defender_as_monster()->is_summoned()
|| one_chance_in(5))
{
break;
}
obvious_effect = true;
if (attacker->atype() == ACT_PLAYER)
mpr("You feel better.");
else if (attacker_visible)
{
if (defender->atype() == ACT_PLAYER)
{
mprf("%s draws strength from your injuries!",
attacker->name(DESC_CAP_THE).c_str());
}
else
{
mprf("%s is healed.",
attacker->name(DESC_CAP_THE).c_str());
}
}
int hp_boost = 0;
if (weapon && is_unrandom_artefact(*weapon)
&& weapon->special == UNRAND_VAMPIRES_TOOTH)
{
hp_boost = damage_done;
}
else
hp_boost = 1 + random2(damage_done);
attacker->heal(hp_boost);
if (attacker->hunger_level() != HS_ENGORGED)
attacker->make_hungry(-random2avg(59, 2));
attacker->god_conduct(DID_NECROMANCY, 2);
break;
}
case SPWPN_PAIN:
if (defender->res_negative_energy())
break;
if (x_chance_in_y(attacker->skill(SK_NECROMANCY) + 1, 8))
{
if (defender_visible)
{
special_damage_message =
make_stringf("%s %s in agony.",
defender->name(DESC_CAP_THE).c_str(),
defender->conj_verb("writhe").c_str());
}
special_damage += random2( 1 + attacker->skill(SK_NECROMANCY) );
}
attacker->god_conduct(DID_NECROMANCY, 4);
break;
case SPWPN_DISTORTION:
ret = distortion_affects_defender();
break;
case SPWPN_CONFUSE:
{
emit_nodmg_hit_message();
const int hdcheck =
(defender->holiness() == MH_NATURAL ? random2(30) : random2(22));
if (mons_class_is_confusable(defender->id())
&& hdcheck >= defender->get_experience_level())
{
bolt beam_temp;
beam_temp.thrower =
attacker->atype() == ACT_PLAYER? KILL_YOU : KILL_MON;
beam_temp.flavour = BEAM_CONFUSION;
beam_temp.beam_source = attacker->mindex();
beam_temp.apply_enchantment_to_monster(defender_as_monster());
obvious_effect = beam_temp.obvious_effect;
}
if (attacker->atype() == ACT_PLAYER && damage_brand == SPWPN_CONFUSE)
{
ASSERT(you.duration[DUR_CONFUSING_TOUCH]);
you.duration[DUR_CONFUSING_TOUCH] -= roll_dice(3, 5);
if (you.duration[DUR_CONFUSING_TOUCH] < 1)
you.duration[DUR_CONFUSING_TOUCH] = 1;
obvious_effect = false;
}
break;
}
case SPWPN_CHAOS:
chaos_affects_defender();
break;
}
if (damage_brand == SPWPN_CHAOS && brand != SPWPN_CHAOS && !ret
&& miscast_level == -1 && one_chance_in(20))
{
miscast_level = 0;
miscast_type = SPTYP_RANDOM;
miscast_target = coinflip() ? attacker : defender;
}
if (attacker->atype() == ACT_PLAYER && damage_brand == SPWPN_CHAOS)
{
if (did_god_conduct(DID_CHAOS, 2 + random2(3), brand_was_known))
obvious_effect = true;
}
if (!obvious_effect)
obvious_effect = !special_damage_message.empty();
if (obvious_effect && attacker_visible && weapon != NULL)
{
if (is_artefact(*weapon))
artefact_wpn_learn_prop(*weapon, ARTP_BRAND);
else
set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
}
return (ret);
}
bool melee_attack::chop_hydra_head( int dam,
int dam_type,
int wpn_brand )
{
if (attacker->atype() == ACT_MONSTER && !one_chance_in(4))
return (false);
if ((dam_type == DVORP_SLICING || dam_type == DVORP_CHOPPING
|| dam_type == DVORP_CLAWING)
&& dam > 0
&& (dam >= 4 || wpn_brand == SPWPN_VORPAL || coinflip()))
{
const char *verb = NULL;
if (dam_type == DVORP_CLAWING)
{
static const char *claw_verbs[] = { "rip", "tear", "claw" };
verb = RANDOM_ELEMENT(claw_verbs);
}
else
{
static const char *slice_verbs[] =
{
"slice", "lop", "chop", "hack"
};
verb = RANDOM_ELEMENT(slice_verbs);
}
if (defender_as_monster()->number == 1) {
if (defender_visible)
{
mprf("%s %s %s's last head off!",
atk_name(DESC_CAP_THE).c_str(),
attacker->conj_verb(verb).c_str(),
def_name(DESC_NOCAP_THE).c_str());
}
defender_as_monster()->number--;
if (!defender->is_summoned())
bleed_onto_floor(defender->pos(), defender->id(),
defender_as_monster()->hit_points, true);
defender->hurt(attacker, defender_as_monster()->hit_points);
return (true);
}
else
{
if (defender_visible)
{
mprf("%s %s one of %s's heads off!",
atk_name(DESC_CAP_THE).c_str(),
attacker->conj_verb(verb).c_str(),
def_name(DESC_NOCAP_THE).c_str());
}
defender_as_monster()->number--;
if (defender->holiness() == MH_NATURAL)
{
unsigned int limit = 20;
if (defender->id() == MONS_LERNAEAN_HYDRA)
limit = 27;
if (wpn_brand == SPWPN_FLAMING)
{
if (defender_visible)
mpr( "The flame cauterises the wound!" );
return (true);
}
else if (defender_as_monster()->number < limit - 1)
{
simple_monster_message(defender_as_monster(),
" grows two more!" );
defender_as_monster()->number += 2;
heal_monster(defender_as_monster(), 8 + random2(8), true);
}
}
}
}
return (false);
}
bool melee_attack::decapitate_hydra(int dam, int damage_type)
{
if (defender->atype() == ACT_MONSTER
&& defender_as_monster()->has_hydra_multi_attack()
&& defender->id() != MONS_SPECTRAL_THING)
{
const int dam_type = (damage_type != -1) ? damage_type
: attacker->damage_type();
const int wpn_brand = attacker->damage_brand();
return chop_hydra_head(dam, dam_type, wpn_brand);
}
return (false);
}
void melee_attack::player_sustain_passive_damage()
{
if (mons_class_flag(defender->id(), M_ACID_SPLASH))
weapon_acid(5);
}
int melee_attack::player_staff_damage(int skill)
{
return (random2(5*(you.skills[skill] + you.skills[SK_EVOCATIONS])/4));
}
void melee_attack::emit_nodmg_hit_message()
{
if (!no_damage_message.empty())
{
mprf("%s", no_damage_message.c_str());
no_damage_message.clear();
}
}
void melee_attack::player_apply_staff_damage()
{
special_damage = 0;
if (!weapon || !item_is_staff(*weapon))
return;
if (random2(15) > you.skills[SK_EVOCATIONS])
{
return;
}
switch (weapon->sub_type)
{
case STAFF_AIR:
if (damage_done + you.skills[SK_AIR_MAGIC] <= random2(20))
break;
special_damage =
resist_adjust_damage(defender,
BEAM_ELECTRICITY,
defender->res_elec(),
player_staff_damage(SK_AIR_MAGIC));
if (special_damage)
{
special_damage_message =
make_stringf("%s is jolted!",
defender->name(DESC_CAP_THE).c_str());
}
break;
case STAFF_COLD:
special_damage =
resist_adjust_damage(defender,
BEAM_COLD,
defender->res_cold(),
player_staff_damage(SK_ICE_MAGIC));
if (special_damage)
{
special_damage_message =
make_stringf(
"You freeze %s!",
defender->name(DESC_NOCAP_THE).c_str());
}
break;
case STAFF_EARTH:
special_damage = player_staff_damage(SK_EARTH_MAGIC);
special_damage = player_apply_monster_ac(special_damage);
if (special_damage > 0)
{
special_damage_message =
make_stringf(
"You crush %s!",
defender->name(DESC_NOCAP_THE).c_str());
}
break;
case STAFF_FIRE:
special_damage =
resist_adjust_damage(defender,
BEAM_FIRE,
defender->res_fire(),
player_staff_damage(SK_FIRE_MAGIC));
if (special_damage)
{
special_damage_message =
make_stringf(
"You burn %s!",
defender->name(DESC_NOCAP_THE).c_str());
}
break;
case STAFF_POISON:
{
int temp_rand = damage_done + you.skills[SK_POISON_MAGIC];
if (temp_rand > 30)
temp_rand = 30;
if (x_chance_in_y(temp_rand, 100))
{
emit_nodmg_hit_message();
poison_monster(defender_as_monster(), KC_YOU);
}
break;
}
case STAFF_DEATH:
if (defender->res_negative_energy())
break;
if (x_chance_in_y(you.skills[SK_NECROMANCY] + 1, 8))
{
special_damage = player_staff_damage(SK_NECROMANCY);
if (special_damage)
{
special_damage_message =
make_stringf(
"%s convulses in agony!",
defender->name(DESC_CAP_THE).c_str());
did_god_conduct(DID_NECROMANCY, 4);
}
}
break;
case STAFF_POWER:
case STAFF_SUMMONING:
case STAFF_CHANNELING:
case STAFF_CONJURATION:
case STAFF_ENCHANTMENT:
case STAFF_ENERGY:
case STAFF_WIZARDRY:
break;
default:
mpr("You're wielding some staff I've never heard of! (fight.cc)",
MSGCH_ERROR);
break;
}
if (special_damage > 0)
{
if (!item_type_known(*weapon))
{
set_ident_flags( *weapon, ISFLAG_KNOW_TYPE );
set_ident_type( *weapon, ID_KNOWN_TYPE );
mprf("You are wielding %s.", weapon->name(DESC_NOCAP_A).c_str());
more();
you.wield_change = true;
}
}
}
bool melee_attack::player_check_monster_died()
{
if (!defender->alive())
{
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Hit for %d.", damage_done);
#endif
player_monattk_hit_effects(true);
_monster_die(defender_as_monster(), KILL_YOU, NON_MONSTER);
return (true);
}
return (false);
}
void melee_attack::player_calc_hit_damage()
{
int potential_damage;
potential_damage =
!weapon ? player_calc_base_unarmed_damage()
: player_calc_base_weapon_damage();
potential_damage = player_stat_modify_damage(potential_damage);
if (water_attack)
potential_damage = player_apply_water_attack_bonus(potential_damage);
potential_damage += slaying_bonus(PWPN_DAMAGE);
damage_done =
potential_damage > 0 ? one_chance_in(3) + random2(potential_damage) : 0;
damage_done = player_apply_weapon_skill(damage_done);
damage_done = player_apply_fighting_skill(damage_done, false);
damage_done = player_apply_misc_modifiers(damage_done);
damage_done = player_apply_weapon_bonuses(damage_done);
player_weapon_auto_id();
damage_done = player_stab(damage_done);
damage_done = player_apply_monster_ac(damage_done);
damage_done = std::max(0, player_weapon_type_modify(damage_done));
}
int melee_attack::calc_to_hit(bool random)
{
return (attacker->atype() == ACT_PLAYER ? player_to_hit(random)
: mons_to_hit());
}
int melee_attack::player_to_hit(bool random_factor)
{
heavy_armour_penalty = calc_heavy_armour_penalty(random_factor);
can_do_unarmed = player_fights_well_unarmed(heavy_armour_penalty);
hand_half_bonus = unarmed_ok
&& !can_do_unarmed
&& !shield
&& weapon
&& !item_cursed( *weapon )
&& hands == HANDS_HALF;
int your_to_hit = 15 + (calc_stat_to_hit_base() / 2);
#ifdef DEBUG_DIAGNOSTICS
const int base_to_hit = your_to_hit;
#endif
if (water_attack)
your_to_hit += 5;
if (wearing_amulet(AMU_INACCURACY))
your_to_hit -= 5;
if (!you.visible_to(&you))
your_to_hit -= 5;
your_to_hit += maybe_random2(1 + you.skills[SK_FIGHTING], random_factor);
if (weapon)
{
if (wpn_skill != SK_FIGHTING)
{
if (you.skills[wpn_skill] < 1 && player_in_a_dangerous_place())
xom_is_stimulated(14);
your_to_hit += maybe_random2(you.skills[wpn_skill] + 1,
random_factor);
}
}
else
{ your_to_hit +=
(you.species == SP_TROLL || you.species == SP_GHOUL) ? 4 : 2;
your_to_hit += maybe_random2(1 + you.skills[SK_UNARMED_COMBAT],
random_factor);
}
if (weapon)
{
if (weapon->base_type == OBJ_WEAPONS)
{
your_to_hit += weapon->plus;
your_to_hit += property( *weapon, PWPN_HIT );
if (get_equip_race(*weapon) == ISFLAG_ELVEN
&& player_genus(GENPC_ELVEN))
{
your_to_hit += (random_factor && coinflip() ? 2 : 1);
}
else if (get_equip_race(*weapon) == ISFLAG_ORCISH
&& you.religion == GOD_BEOGH && !player_under_penance())
{
your_to_hit++;
}
}
else if (item_is_staff( *weapon ))
{
your_to_hit += property( *weapon, PWPN_HIT );
}
}
your_to_hit += slaying_bonus(PWPN_HIT);
if (you.hunger_state == HS_STARVING)
your_to_hit -= 3;
your_to_hit -= heavy_armour_penalty;
#if DEBUG_DIAGNOSTICS
int roll_hit = your_to_hit;
#endif
your_to_hit = maybe_random2(your_to_hit, random_factor);
#if DEBUG_DIAGNOSTICS
mprf( MSGCH_DIAGNOSTICS,
"to hit die: %d; rolled value: %d; base: %d",
roll_hit, your_to_hit, base_to_hit );
#endif
if (hand_half_bonus)
your_to_hit += maybe_random2(3, random_factor);
if (weapon && wpn_skill == SK_SHORT_BLADES && you.duration[DUR_SURE_BLADE])
{
your_to_hit += 5 +
(random_factor ? random2limit( you.duration[DUR_SURE_BLADE], 10 ) :
you.duration[DUR_SURE_BLADE] / 2);
}
if (!weapon)
{
if (you.duration[DUR_CONFUSING_TOUCH])
{
your_to_hit += maybe_random2(you.dex, random_factor);
}
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_SPIDER:
your_to_hit += maybe_random2(10, random_factor);
break;
case TRAN_BAT:
your_to_hit += maybe_random2(12, random_factor);
break;
case TRAN_ICE_BEAST:
your_to_hit += maybe_random2(10, random_factor);
break;
case TRAN_BLADE_HANDS:
your_to_hit += maybe_random2(12, random_factor);
break;
case TRAN_STATUE:
your_to_hit += maybe_random2(9, random_factor);
break;
case TRAN_DRAGON:
your_to_hit += maybe_random2(10, random_factor);
break;
case TRAN_LICH:
your_to_hit += maybe_random2(10, random_factor);
break;
case TRAN_NONE:
default:
break;
}
}
if (defender && defender->atype() == ACT_MONSTER)
{
if (defender->backlit())
your_to_hit += 2 + random2(8);
else if (!defender->visible_to(&you))
your_to_hit -= 6;
}
return (your_to_hit);
}
void melee_attack::player_stab_check()
{
if (mons_is_unknown_mimic(defender_as_monster()))
{
stab_attempt = false;
stab_bonus = 0;
return;
}
const unchivalric_attack_type uat = is_unchivalric_attack(&you, defender);
stab_attempt = (uat != UCAT_NO_ATTACK);
const bool roll_needed = (uat != UCAT_SLEEPING && uat != UCAT_PARALYSED);
int roll = 155;
if (uat == UCAT_INVISIBLE && !mons_sense_invis(defender_as_monster()))
roll -= 15;
switch (uat)
{
case UCAT_NO_ATTACK:
stab_bonus = 0;
break;
case UCAT_HELD_IN_NET:
case UCAT_PETRIFYING:
case UCAT_PETRIFIED:
case UCAT_SLEEPING:
case UCAT_PARALYSED:
stab_bonus = 1;
break;
case UCAT_INVISIBLE:
case UCAT_CONFUSED:
case UCAT_FLEEING:
stab_bonus = 2;
break;
case UCAT_DISTRACTED:
stab_bonus = 3;
break;
}
if (stab_attempt && roll_needed)
{
stab_attempt = x_chance_in_y(you.skills[SK_STABBING] + you.dex + 1,
roll);
}
}
void melee_attack::player_apply_attack_delay()
{
int attack_delay = weapon ? player_weapon_speed() : player_unarmed_speed();
attack_delay = player_apply_shield_delay(attack_delay);
if (attack_delay < 3)
attack_delay = 3;
final_attack_delay = attack_delay;
you.time_taken =
std::max(2, div_rand_round(you.time_taken * final_attack_delay, 10));
#if DEBUG_DIAGNOSTICS
mprf( MSGCH_DIAGNOSTICS,
"Weapon speed: %d; min: %d; attack time: %d",
final_attack_delay, min_delay, you.time_taken );
#endif
}
int melee_attack::player_weapon_speed()
{
int attack_delay = 0;
if (weapon && (weapon->base_type == OBJ_WEAPONS
|| item_is_staff( *weapon )))
{
attack_delay = property( *weapon, PWPN_SPEED );
attack_delay -= you.skills[ wpn_skill ] / 2;
min_delay = property( *weapon, PWPN_SPEED ) / 2;
if (wpn_skill == SK_SHORT_BLADES && min_delay > 5)
min_delay = 5;
if ((hands == HANDS_TWO || hand_half_bonus)
&& min_delay > 7)
{
min_delay = 7;
}
if (min_delay < 3)
min_delay = 3;
if (hand_half_bonus && attack_delay > 10)
attack_delay--;
if (attack_delay < min_delay)
attack_delay = min_delay;
if (weapon->base_type == OBJ_WEAPONS
&& damage_brand == SPWPN_SPEED)
{
attack_delay = (attack_delay + 1) / 2;
}
}
return (attack_delay);
}
int melee_attack::player_unarmed_speed()
{
int unarmed_delay = 10;
min_delay = 5;
if (you.burden_state == BS_UNENCUMBERED
&& one_chance_in(heavy_armour_penalty + 1))
{
unarmed_delay =
std::max(10 - you.skills[SK_UNARMED_COMBAT]
/ (player_in_bat_form() ? 3 : 5), min_delay);
}
return (unarmed_delay);
}
int melee_attack::player_apply_shield_delay(int attack_delay)
{
if (shield)
{
switch (shield->sub_type)
{
case ARM_LARGE_SHIELD:
if (you.skills[SK_SHIELDS] <= 10 + random2(17))
attack_delay++;
case ARM_SHIELD:
if (you.skills[SK_SHIELDS] <= 3 + random2(17))
attack_delay++;
break;
}
}
return (attack_delay);
}
int melee_attack::player_calc_base_unarmed_damage()
{
int damage = 3;
if (you.duration[DUR_CONFUSING_TOUCH])
{
damage = 0;
}
if (you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE)
{
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_SPIDER:
damage = 5;
break;
case TRAN_BAT:
damage = (you.species == SP_VAMPIRE ? 2 : 1);
break;
case TRAN_ICE_BEAST:
damage = 12;
break;
case TRAN_BLADE_HANDS:
damage = 12 + (you.strength / 4) + (you.dex / 4);
break;
case TRAN_STATUE:
damage = 12 + you.strength;
break;
case TRAN_DRAGON:
damage = 20 + you.strength;
break;
case TRAN_LICH:
damage = 5;
break;
}
}
else if (you.equip[ EQ_GLOVES ] == -1)
{
if (you.species == SP_TROLL)
damage += 5;
else if (you.species == SP_GHOUL)
damage += 2;
damage += player_mutation_level(MUT_CLAWS) * 2;
}
if (player_in_bat_form())
{
damage += you.skills[SK_UNARMED_COMBAT] / 5;
}
else
damage += you.skills[SK_UNARMED_COMBAT];
return (damage);
}
int melee_attack::player_calc_base_weapon_damage()
{
int damage = 0;
if (weapon->base_type == OBJ_WEAPONS || item_is_staff( *weapon ))
damage = property( *weapon, PWPN_DAMAGE );
if (shield && weapon->base_type == OBJ_WEAPONS
&& weapon_skill(*weapon) == SK_STAVES
&& hands_reqd(*weapon, you.body_size()) == HANDS_HALF)
{
damage /= 2;
}
return (damage);
}
bool melee_attack::mons_attack_mons()
{
const coord_def atk_pos = attacker->pos();
const coord_def def_pos = defender->pos();
if ((is_sanctuary(atk_pos) || is_sanctuary(def_pos))
&& attacker != defender)
{
if (mons_friendly(attacker_as_monster()))
{
if (you.pet_target == MHITYOU || you.pet_target == MHITNOT)
{
if (attacker->confused() && you.can_see(attacker))
{
mpr("Zin prevents your ally from violating sanctuary "
"in its confusion.", MSGCH_GOD);
}
else if (attacker_as_monster()->has_ench(ENCH_BERSERK)
&& you.can_see(attacker))
{
mpr("Zin prevents your ally from violating sanctuary "
"in its berserker rage.", MSGCH_GOD);
}
cancel_attack = true;
return (false);
}
}
else
{
#ifdef DEBUG_DIAGNOSTICS
mpr("Preventing hostile violation of sanctuary.",
MSGCH_DIAGNOSTICS);
#endif
cancel_attack = true;
return (false);
}
}
mons_perform_attack();
if (defender->atype() == ACT_PLAYER)
return (did_hit);
if (perceived_attack
&& (defender_as_monster()->foe == MHITNOT || one_chance_in(3))
&& attacker->alive() && defender->alive())
{
behaviour_event(defender_as_monster(), ME_WHACK, attacker->mindex());
}
if (perceived_attack && attacker->alive()
&& mons_friendly(defender_as_monster())
&& !crawl_state.arena
&& !mons_wont_attack(attacker_as_monster())
&& you.pet_target == MHITNOT
&& env.sanctuary_time <= 0)
{
you.pet_target = attacker->mindex();
}
return (did_hit);
}
bool melee_attack::mons_self_destructs()
{
if (attacker->id() == MONS_GIANT_SPORE
|| attacker->id() == MONS_BALL_LIGHTNING)
{
attacker_as_monster()->hit_points = -1;
monster_die(attacker_as_monster(), KILL_MON, attacker->mindex());
return (true);
}
return (false);
}
bool melee_attack::mons_attack_warded_off()
{
const int warding = defender->warding();
if (warding
&& attacker->is_summoned()
&& !check_mons_resist_magic(attacker_as_monster(), warding))
{
if (needs_message)
{
mprf("%s tries to attack %s, but flinches away.",
atk_name(DESC_CAP_THE).c_str(),
mons_defender_name().c_str());
}
return (true);
}
return (false);
}
int melee_attack::mons_attk_delay()
{
return (weapon ? property(*weapon, PWPN_SPEED) : 0);
}
bool melee_attack::attack_shield_blocked(bool verbose)
{
if (!defender_shield && defender->atype() != ACT_PLAYER)
return (false);
if (defender->incapacitated())
return (false);
const int con_block = random2(attacker->shield_bypass_ability(to_hit)
+ defender->shield_block_penalty());
int pro_block = defender->shield_bonus();
if (!attacker->visible_to(defender))
pro_block /= 3;
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Defender: %s, Pro-block: %d, Con-block: %d",
def_name(DESC_PLAIN).c_str(), pro_block, con_block);
#endif
if (pro_block >= con_block)
{
perceived_attack = true;
if (needs_message && verbose)
{
mprf("%s %s %s attack.",
def_name(DESC_CAP_THE).c_str(),
defender->conj_verb("block").c_str(),
atk_name(DESC_NOCAP_ITS).c_str());
}
defender->shield_block_succeeded();
return (true);
}
return (false);
}
int melee_attack::mons_calc_damage(const mon_attack_def &attk)
{
int damage = 0;
int damage_max = 0;
if (weapon
&& weapon->base_type == OBJ_WEAPONS
&& !is_range_weapon(*weapon))
{
damage_max = property( *weapon, PWPN_DAMAGE );
damage += random2( damage_max );
if (get_equip_race(*weapon) == ISFLAG_ORCISH
&& attacker->mons_species() == MONS_ORC
&& coinflip())
{
damage++;
}
if (weapon->plus2 >= 0)
damage += random2( weapon->plus2 );
else
damage -= random2( 1 - weapon->plus2 );
damage -= 1 + random2(3);
}
damage_max += attk.damage;
damage += 1 + random2(attk.damage);
if (attacker_as_monster()->has_ench(ENCH_MIGHT))
damage = damage * 3 / 2;
else if (attacker_as_monster()->has_ench(ENCH_BATTLE_FRENZY))
{
const mon_enchant ench =
attacker_as_monster()->get_ench(ENCH_BATTLE_FRENZY);
#ifdef DEBUG_DIAGNOSTICS
const int orig_damage = damage;
#endif
damage = damage * (115 + ench.degree * 15) / 100;
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "%s frenzy damage: %d->%d",
attacker->name(DESC_PLAIN).c_str(), orig_damage, damage);
#endif
}
if (water_attack)
damage *= 2;
if (defender && defender->asleep())
{
damage = damage * 5 / 2;
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Stab damage vs %s: %d",
defender->name(DESC_PLAIN).c_str(),
damage);
#endif
}
return (mons_apply_defender_ac(damage, damage_max));
}
int melee_attack::mons_apply_defender_ac(int damage, int damage_max)
{
int ac = defender->armour_class();
if (ac > 0)
{
int damage_reduction = random2(ac + 1);
if (!defender->wearing_light_armour())
{
if (const item_def *arm = defender->slot_item(EQ_BODY_ARMOUR))
{
const int armac = property(*arm, PARM_AC);
int perc = 2 * (defender->skill(SK_ARMOUR) + armac);
if (perc > 50)
perc = 50;
int min = 1 + (damage_max * perc) / 100;
if (min > ac / 2)
min = ac / 2;
if (damage_reduction < min)
damage_reduction = min;
}
}
damage -= damage_reduction;
}
if (damage < 1)
damage = 0;
return damage;
}
static const char *klown_attack[] =
{
"hit",
"poke",
"prod",
"flog",
"pound",
"slap",
"tickle",
"defenestrate",
"sucker-punch",
"elbow",
"pinch",
"strangle-hug",
"squeeze",
"tease",
"eye-gouge",
"karate-kick",
"headlock",
"wrestle",
"trip-wire",
"kneecap"
};
std::string melee_attack::mons_attack_verb(const mon_attack_def &attk)
{
if (attacker->id() == MONS_KILLER_KLOWN && attk.type == AT_HIT)
return (RANDOM_ELEMENT(klown_attack));
if (attacker->id() == MONS_KRAKEN_TENTACLE && attk.type == AT_TENTACLE_SLAP)
return ("slap");
static const char *attack_types[] =
{
"",
"hit", "bite",
"sting",
"hit",
"touch",
"engulf",
"claw",
"peck",
"headbutt",
"punch",
"kick",
"tentacle-slap",
"tail-slap",
"gore"
};
return (attack_types[attk.type]);
}
std::string melee_attack::mons_weapon_desc()
{
if (!you.can_see(attacker))
return ("");
if (weapon)
{
std::string result = "";
const item_def wpn = *weapon;
if (get_weapon_brand(wpn) == SPWPN_REACHING)
{
if (grid_distance(attacker->pos(), defender->pos()) == 2)
result += " from afar";
}
if (attacker->id() != MONS_DANCING_WEAPON)
{
result += " with ";
result += weapon->name(DESC_NOCAP_A);
}
return (result);
}
return ("");
}
std::string melee_attack::mons_defender_name()
{
if (attacker == defender)
return pronoun(attacker, PRONOUN_REFLEXIVE, attacker_visible);
else
return def_name(DESC_NOCAP_THE);
}
void melee_attack::mons_announce_hit(const mon_attack_def &attk)
{
if (water_attack && attacker_visible && attacker != defender)
{
mprf("%s uses the watery terrain to its advantage.",
attacker->name(DESC_CAP_THE).c_str());
}
if (needs_message)
{
mprf("%s %s %s%s%s%s",
atk_name(DESC_CAP_THE).c_str(),
attacker->conj_verb( mons_attack_verb(attk) ).c_str(),
mons_defender_name().c_str(),
debug_damage_number().c_str(),
mons_weapon_desc().c_str(),
attack_strength_punctuation().c_str());
}
}
void melee_attack::mons_announce_dud_hit(const mon_attack_def &attk)
{
if (needs_message)
{
mprf("%s %s %s but doesn't do any damage.",
atk_name(DESC_CAP_THE).c_str(),
attacker->conj_verb( mons_attack_verb(attk) ).c_str(),
mons_defender_name().c_str());
}
}
void melee_attack::check_defender_train_dodging()
{
if (defender->wearing_light_armour(true)
&& attacker_visible
&& one_chance_in(3))
{
perceived_attack = true;
defender->exercise(SK_DODGING, 1);
}
}
void melee_attack::check_defender_train_armour()
{
if (defender->wearing_light_armour())
return;
const item_def *arm = defender->slot_item(EQ_BODY_ARMOUR);
if (arm && coinflip() && x_chance_in_y(item_mass(*arm) + 1, 1000))
defender->exercise(SK_ARMOUR, coinflip()? 2 : 1);
}
void melee_attack::mons_set_weapon(const mon_attack_def &attk)
{
weapon = (attk.type == AT_HIT) ? attacker->weapon(attack_number) : NULL;
damage_brand = attacker->damage_brand(attack_number);
}
void melee_attack::mons_do_poison(const mon_attack_def &attk)
{
if (defender->res_poison() > 0)
return;
if (attk.flavour == AF_POISON_NASTY
|| one_chance_in(15 + 5 * (attk.flavour == AF_POISON ? 1 : 0))
|| (damage_done > 1
&& one_chance_in(attk.flavour == AF_POISON ? 4 : 3)))
{
if (needs_message)
{
if (defender->atype() == ACT_PLAYER
&& (attk.type == AT_BITE || attk.type == AT_STING))
{
if (attacker_visible)
{
mprf("%s %s was poisonous!",
apostrophise(attacker->name(DESC_CAP_THE)).c_str(),
mons_attack_verb(attk).c_str());
}
}
else
{
mprf("%s poisons %s!",
atk_name(DESC_CAP_THE).c_str(),
mons_defender_name().c_str());
}
}
int amount = 1;
if (attk.flavour == AF_POISON_NASTY)
amount++;
else if (attk.flavour == AF_POISON_MEDIUM)
amount += random2(3);
else if (attk.flavour == AF_POISON_STRONG)
amount += roll_dice(2, 5);
defender->poison(attacker, amount);
}
}
void melee_attack::mons_do_napalm()
{
if (defender->res_sticky_flame())
return;
if (one_chance_in(20) || (damage_done > 2 && one_chance_in(3)))
{
if (needs_message)
{
mprf("%s %s covered in liquid flames%s",
def_name(DESC_CAP_THE).c_str(),
defender->conj_verb("are").c_str(),
special_attack_punctuation().c_str());
}
if (defender->atype() == ACT_PLAYER)
napalm_player(random2avg(7, 3) + 1);
else
{
napalm_monster(defender_as_monster(),
mons_friendly_real(attacker_as_monster()) ?
KC_FRIENDLY : KC_OTHER,
std::min(4, 1 + random2(attacker->get_experience_level())/2));
}
}
}
void melee_attack::wasp_paralyse_defender()
{
if (attacker->id() == MONS_RED_WASP || one_chance_in(3))
defender->poison(attacker, coinflip() ? 2 : 1);
int paralyse_roll = (damage_done > 4 ? 3 : 20);
if (attacker->id() == MONS_YELLOW_WASP)
paralyse_roll += 3;
if (defender->res_poison() <= 0)
{
if (one_chance_in(paralyse_roll))
defender->paralyse(attacker, roll_dice(1, 3));
else
defender->slow_down(attacker, roll_dice(1, 3));
}
}
void melee_attack::splash_monster_with_acid(int strength)
{
special_damage += roll_dice(2, 4);
if (defender_visible)
mprf("%s is splashed with acid.", defender->name(DESC_CAP_THE).c_str());
}
void melee_attack::splash_defender_with_acid(int strength)
{
if (defender->atype() == ACT_PLAYER)
{
mpr("You are splashed with acid!");
splash_with_acid(strength);
}
else
splash_monster_with_acid(strength);
}
static void _steal_item_from_player(monsters *mon)
{
mon_inv_type mslot = NUM_MONSTER_SLOTS;
int steal_what = -1;
int total_value = 0;
for (int m = 0; m < ENDOFPACK; ++m)
{
if (!is_valid_item(you.inv[m]))
continue;
if (item_is_equipped(you.inv[m]))
continue;
mon_inv_type monslot = item_to_mslot(you.inv[m]);
if (monslot == NUM_MONSTER_SLOTS)
{
if (you.inv[m].base_type == OBJ_BOOKS)
monslot = MSLOT_SCROLL;
else if (you.inv[m].base_type == OBJ_JEWELLERY)
monslot = MSLOT_MISCELLANY;
else
continue;
}
if (mon->inv[monslot] != NON_ITEM)
{
if (monslot == MSLOT_WEAPON
&& mon->inv[MSLOT_ALT_WEAPON] == NON_ITEM)
{
monslot = MSLOT_ALT_WEAPON;
}
else
continue;
}
const int value = item_value(you.inv[m], true);
total_value += value;
if (x_chance_in_y(value, total_value))
{
steal_what = m;
mslot = monslot;
}
}
if (steal_what == -1 || you.gold > 0 && one_chance_in(10))
{
if (you.gold == 0)
{
if (silenced(mon->pos()))
return;
std::string complaint = getSpeakString("Maurice nonstealing");
if (!complaint.empty())
{
complaint = replace_all(complaint, "@The_monster@",
mon->name(DESC_CAP_THE));
mpr(complaint.c_str(), MSGCH_TALK);
}
bolt beem;
beem.source = mon->pos();
beem.target = mon->pos();
beem.beam_source = mon->mindex();
if (mon->has_ench(ENCH_TP))
{
mons_cast_noise(mon, beem, SPELL_BLINK);
monster_blink(mon);
}
else
mons_cast(mon, beem, SPELL_TELEPORT_SELF);
return;
}
const int stolen_amount = std::min(20 + random2(800), you.gold);
if (mon->inv[MSLOT_GOLD] != NON_ITEM)
{
mitm[mon->inv[MSLOT_GOLD]].quantity += stolen_amount;
}
else
{
const int idx = items(0, OBJ_GOLD, OBJ_RANDOM, true, 0, 0);
if (idx == NON_ITEM)
return;
item_def &new_item = mitm[idx];
new_item.base_type = OBJ_GOLD;
new_item.sub_type = 0;
new_item.plus = 0;
new_item.plus2 = 0;
new_item.special = 0;
new_item.flags = 0;
new_item.link = NON_ITEM;
new_item.quantity = stolen_amount;
new_item.pos.reset();
item_colour(new_item);
unlink_item(idx);
mon->inv[MSLOT_GOLD] = idx;
new_item.set_holding_monster(mon->mindex());
}
mprf("%s steals %s your gold!",
mon->name(DESC_CAP_THE).c_str(),
stolen_amount == you.gold ? "all" : "some of");
you.attribute[ATTR_GOLD_FOUND] -= stolen_amount;
you.del_gold(stolen_amount);
return;
}
ASSERT(steal_what != -1);
ASSERT(mslot != NUM_MONSTER_SLOTS);
ASSERT(mon->inv[mslot] == NON_ITEM);
int index = get_item_slot(10);
if (index == NON_ITEM)
return;
item_def &new_item = mitm[index];
new_item = you.inv[steal_what];
new_item.quantity -= random2(new_item.quantity);
new_item.pos.reset();
new_item.link = NON_ITEM;
mprf("%s steals %s!",
mon->name(DESC_CAP_THE).c_str(),
new_item.name(DESC_NOCAP_YOUR).c_str());
unlink_item(index);
mon->inv[mslot] = index;
new_item.set_holding_monster(mon->mindex());
mon->equip(new_item, mslot, true);
dec_inv_item_quantity(steal_what, new_item.quantity);
}
void melee_attack::mons_apply_attack_flavour(const mon_attack_def &attk)
{
mon_attack_flavour flavour = attk.flavour;
if (flavour == AF_CHAOS)
flavour = random_chaos_attack_flavour();
switch (flavour)
{
default:
break;
case AF_MUTATE:
if (one_chance_in(4))
defender->mutate();
break;
case AF_POISON:
case AF_POISON_NASTY:
case AF_POISON_MEDIUM:
case AF_POISON_STRONG:
mons_do_poison(attk);
break;
case AF_POISON_STR:
if (defender->res_poison() <= 0)
{
defender->poison(attacker, roll_dice(1, 3));
if (one_chance_in(4))
defender->drain_stat(STAT_STRENGTH, 1, attacker);
}
break;
case AF_ROT:
if (one_chance_in(20) || (damage_done > 2 && one_chance_in(3)))
rot_defender(2 + random2(3), damage_done > 5 ? 1 : 0);
break;
case AF_DISEASE:
defender->sicken(50 + random2(100));
break;
case AF_FIRE:
if (attacker->id() == MONS_FIRE_VORTEX)
attacker_as_monster()->hit_points = -10;
special_damage =
resist_adjust_damage(defender,
BEAM_FIRE,
defender->res_fire(),
attacker->get_experience_level()
+ random2(attacker->get_experience_level()));
if (needs_message && special_damage)
{
mprf("%s %s engulfed in flames%s",
def_name(DESC_CAP_THE).c_str(),
defender->conj_verb("are").c_str(),
special_attack_punctuation().c_str());
}
defender->expose_to_element(BEAM_FIRE, 2);
break;
case AF_COLD:
special_damage =
resist_adjust_damage(defender,
BEAM_COLD,
defender->res_cold(),
attacker->get_experience_level() +
random2(2 * attacker->get_experience_level()));
if (needs_message && special_damage)
{
mprf("%s %s %s%s",
atk_name(DESC_CAP_THE).c_str(),
attacker->conj_verb("freeze").c_str(),
mons_defender_name().c_str(),
special_attack_punctuation().c_str());
}
defender->expose_to_element(BEAM_COLD, 2);
break;
case AF_ELEC:
special_damage =
resist_adjust_damage(
defender,
BEAM_ELECTRICITY,
defender->res_elec(),
attacker->get_experience_level() +
random2(attacker->get_experience_level() / 2));
if (defender->airborne())
special_damage = special_damage * 2 / 3;
if (needs_message && special_damage)
{
mprf("%s %s %s%s",
atk_name(DESC_CAP_THE).c_str(),
attacker->conj_verb("shock").c_str(),
mons_defender_name().c_str(),
special_attack_punctuation().c_str());
}
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Shock damage: %d", special_damage);
#endif
break;
case AF_VAMPIRIC:
if (!defender->can_bleed())
break;
if (defender->is_summoned())
break;
if (x_chance_in_y(defender->res_negative_energy(), 3))
break;
if (defender->stat_hp() < defender->stat_maxhp())
{
attacker->heal(1 + random2(damage_done), coinflip());
if (needs_message)
{
mprf("%s %s strength from %s injuries!",
atk_name(DESC_CAP_THE).c_str(),
attacker->conj_verb("draw").c_str(),
def_name(DESC_NOCAP_ITS).c_str());
}
if ((damage_done > 6 && one_chance_in(3)) || one_chance_in(20))
{
if (defender->atype() == ACT_PLAYER)
mprf("You feel less resilient.");
rot_defender(0, coinflip() ? 2 : 1);
}
}
break;
case AF_DRAIN_STR:
if ((one_chance_in(20) || damage_done > 0 && one_chance_in(3))
&& defender->res_negative_energy() < random2(4))
{
defender->drain_stat(STAT_STRENGTH, 1, attacker);
}
break;
case AF_DRAIN_DEX:
if ((one_chance_in(20) || (damage_done > 0 && one_chance_in(3)))
&& defender->res_negative_energy() < random2(4))
{
defender->drain_stat(STAT_DEXTERITY, 1, attacker);
}
break;
case AF_HUNGER:
if (defender->holiness() == MH_UNDEAD)
break;
if (one_chance_in(20) || (damage_done > 0 && coinflip()))
defender->make_hungry(400, false);
break;
case AF_BLINK:
if (one_chance_in(3))
{
if (attacker_visible)
{
mprf("%s %s!", attacker->name(DESC_CAP_THE).c_str(),
attacker->conj_verb("blink").c_str());
}
attacker->blink();
}
break;
case AF_CONFUSE:
if (attk.type == AT_SPORE)
{
if (defender->res_poison() > 0)
break;
if (--(attacker_as_monster()->hit_dice) <= 0)
attacker_as_monster()->hit_points = -1;
if (defender_visible)
{
mprf("%s %s engulfed in a cloud of spores!",
defender->name(DESC_CAP_THE).c_str(),
defender->conj_verb("are").c_str());
}
}
if (one_chance_in(10)
|| (damage_done > 2 && one_chance_in(3)))
{
defender->confuse(attacker,
1 + random2(3+attacker->get_experience_level()));
}
break;
case AF_DRAIN_XP:
if (one_chance_in(30)
|| (damage_done > 5 && coinflip())
|| (attk.damage == 0 && !one_chance_in(3)))
{
drain_defender();
}
break;
case AF_PARALYSE:
wasp_paralyse_defender();
break;
case AF_ACID:
if (attacker->id() == MONS_SPINY_WORM && defender->res_poison() <= 0)
defender->poison(attacker, 2 + random2(4));
splash_defender_with_acid(3);
break;
case AF_DISTORT:
distortion_affects_defender();
break;
case AF_RAGE:
if (!one_chance_in(3) || !defender->can_go_berserk())
break;
if (needs_message)
{
mprf("%s %s %s!",
atk_name(DESC_CAP_THE).c_str(),
attacker->conj_verb("infuriate").c_str(),
mons_defender_name().c_str());
}
defender->go_berserk(false);
break;
case AF_NAPALM:
mons_do_napalm();
break;
case AF_CHAOS:
chaos_affects_defender();
break;
case AF_STEAL:
if (defender->atype() != ACT_PLAYER)
break;
_steal_item_from_player(attacker_as_monster());
break;
case AF_STEAL_FOOD:
if (defender->atype() != ACT_PLAYER)
break;
if (expose_player_to_element(BEAM_STEAL_FOOD, 10) && needs_message)
{
mprf("%s steals some of your food!",
atk_name(DESC_CAP_THE).c_str());
}
break;
}
}
void melee_attack::mons_perform_attack_rounds()
{
const int nrounds = attacker_as_monster()->has_hydra_multi_attack() ?
attacker_as_monster()->number : 4;
const coord_def pos = defender->pos();
const bool was_delayed = you_are_delayed();
attacker_as_monster()->wield_melee_weapon();
monsters* def_copy = NULL;
for (attack_number = 0; attack_number < nrounds; ++attack_number)
{
if (!defender->alive() || defender->pos() != pos)
{
if (attacker == defender
|| !attacker_as_monster()->has_multitargeting())
{
break;
}
bool end = true;
for (adjacent_iterator i(attacker->pos()); i; ++i)
{
if (*i == you.pos() && !mons_friendly(attacker_as_monster()))
{
attacker_as_monster()->foe = MHITYOU;
attacker_as_monster()->target = you.pos();
defender = &you;
end = false;
break;
}
monsters *mons = monster_at(*i);
if (mons
&& !mons_aligned(attacker_as_monster()->mindex(),
mons->mindex()))
{
defender = mons;
end = false;
break;
}
}
if (end)
break;
}
if (attack_number > 0 && attacker == defender)
break;
init_attack();
const mon_attack_def attk = mons_attack_spec(attacker_as_monster(),
attack_number);
if (attk.type == AT_NONE)
{
if (attack_number == 0)
attacker_as_monster()->lose_energy(EUT_ATTACK);
break;
}
if (attk.type != AT_HIT && !unarmed_ok)
continue;
if (attk.type == AT_SHOOT)
continue;
damage_done = 0;
mons_set_weapon(attk);
to_hit = mons_to_hit();
const bool chaos_attack = damage_brand == SPWPN_CHAOS
|| (attk.flavour == AF_CHAOS
&& attacker != defender);
if (chaos_attack && defender->atype() == ACT_MONSTER && !def_copy)
def_copy = new monsters(*defender_as_monster());
final_attack_delay = mons_attk_delay();
if (damage_brand == SPWPN_SPEED)
final_attack_delay = final_attack_delay / 2 + 1;
mons_lose_attack_energy(attacker_as_monster(),
final_attack_delay, attack_number);
bool shield_blocked = false;
bool this_round_hit = false;
if (attacker != defender)
{
if (attack_shield_blocked(true))
{
shield_blocked = true;
perceived_attack = true;
this_round_hit = did_hit = true;
}
else
check_defender_train_dodging();
}
if (!shield_blocked)
{
const int defender_evasion = defender->melee_evasion(attacker);
if (attacker == defender
|| test_melee_hit(to_hit, defender_evasion))
{
this_round_hit = did_hit = true;
perceived_attack = true;
damage_done = mons_calc_damage(attk);
}
else
{
this_round_hit = false;
perceived_attack = perceived_attack || attacker_visible;
if (needs_message)
{
mprf("%s misses %s.",
atk_name(DESC_CAP_THE).c_str(),
mons_defender_name().c_str());
}
}
}
if (check_unrand_effects())
break;
if (damage_done < 1 && this_round_hit && !shield_blocked)
mons_announce_dud_hit(attk);
if (damage_done > 0)
{
#ifdef DEBUG_DIAGNOSTICS
if (shield_blocked)
mpr("ERROR: Non-zero damage after shield block!");
#endif
mons_announce_hit(attk);
check_defender_train_armour();
if (defender->can_bleed()
&& !defender->is_summoned()
&& !defender->submerged())
{
int blood = _modify_blood_amount(damage_done,
attacker->damage_type());
if (blood > defender->stat_hp())
blood = defender->stat_hp();
bleed_onto_floor(pos, defender->id(), blood, true);
}
if (decapitate_hydra(damage_done,
attacker->damage_type(attack_number)))
{
continue;
}
special_damage = 0;
special_damage_message.clear();
if (attacker != defender && attk.flavour != AF_STEAL)
mons_apply_attack_flavour(attk);
if (!special_damage_message.empty())
mprf("%s", special_damage_message.c_str());
if (is_banished(defender))
{
if (chaos_attack && attacker->alive())
chaos_affects_attacker();
do_miscast();
break;
}
defender->hurt(attacker, damage_done + special_damage);
if (!defender->alive())
{
if (chaos_attack && defender->atype() == ACT_MONSTER)
chaos_killed_defender(def_copy);
if (chaos_attack && attacker->alive())
chaos_affects_attacker();
do_miscast();
continue;
}
if (!attacker->alive() || (attacker == defender && !weapon))
{
if (miscast_target == defender)
do_miscast();
break;
}
special_damage = 0;
special_damage_message.clear();
apply_damage_brand();
if (!special_damage_message.empty())
{
mprf("%s", special_damage_message.c_str());
if (miscast_level == 0)
miscast_level = -1;
}
if (special_damage > 0)
defender->hurt(attacker, special_damage);
if (!defender->alive())
{
if (chaos_attack && defender->atype() == ACT_MONSTER)
chaos_killed_defender(def_copy);
if (chaos_attack && attacker->alive())
chaos_affects_attacker();
do_miscast();
continue;
}
if (chaos_attack && attacker->alive())
chaos_affects_attacker();
if (miscast_target == defender)
do_miscast();
if (!attacker->alive())
break;
if (miscast_target == attacker)
do_miscast();
if (!attacker->alive())
break;
if (attk.flavour == AF_STEAL)
mons_apply_attack_flavour(attk);
}
item_def *weap = attacker_as_monster()->mslot_item(MSLOT_WEAPON);
if (weap && you.can_see(attacker) && weap->cursed()
&& is_range_weapon(*weap))
{
set_ident_flags(*weap, ISFLAG_KNOW_CURSE);
}
}
if (def_copy)
delete def_copy;
if (was_delayed && defender->atype() == ACT_PLAYER && perceived_attack
&& !attacker_visible)
{
handle_interrupted_swap(false, true);
}
}
bool melee_attack::mons_perform_attack()
{
if (attacker != defender && mons_self_destructs())
return (did_hit = perceived_attack = true);
if (attacker != defender && mons_attack_warded_off())
{
attacker->lose_energy(EUT_ATTACK, 2);
perceived_attack = true;
return (false);
}
mons_perform_attack_rounds();
return (did_hit);
}
void melee_attack::mons_check_attack_perceived()
{
if (!perceived_attack)
return;
if (defender->atype() == ACT_PLAYER)
{
interrupt_activity(AI_MONSTER_ATTACKS, attacker_as_monster());
if (you.pet_target == MHITNOT && env.sanctuary_time <= 0)
you.pet_target = attacker->mindex();
}
}
bool melee_attack::mons_attack_you()
{
mons_perform_attack();
mons_check_attack_perceived();
return (did_hit);
}
int melee_attack::mons_to_hit()
{
const int hd_mult = mons_class_flag(attacker->id(), M_FIGHTER)? 25 : 15;
int mhit = 18 + attacker->get_experience_level() * hd_mult / 10;
#ifdef DEBUG_DIAGNOSTICS
const int base_hit = mhit;
#endif
if (water_attack)
mhit += 5;
if (weapon && weapon->base_type == OBJ_WEAPONS)
mhit += weapon->plus + property(*weapon, PWPN_HIT);
if (attacker->confused())
mhit -= 5;
if (defender->backlit())
mhit += 2 + random2(8);
if (!defender->visible_to(attacker))
mhit = mhit * 65 / 100;
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "%s: Base to-hit: %d, Final to-hit: %d",
attacker->name(DESC_PLAIN).c_str(),
base_hit, mhit);
#endif
return (mhit);
}
static bool wielded_weapon_check(const item_def *weapon)
{
bool weapon_warning = false;
bool unarmed_warning = false;
if (weapon)
{
if (has_warning_inscription(*weapon, OPER_ATTACK)
|| weapon->base_type != OBJ_STAVES
&& (weapon->base_type != OBJ_WEAPONS
|| is_range_weapon(*weapon)))
{
weapon_warning = true;
}
}
else if (you.attribute[ATTR_WEAPON_SWAP_INTERRUPTED]
&& you_tran_can_wear(EQ_WEAPON))
{
unarmed_warning = true;
}
if (!you.received_weapon_warning && !you.confused()
&& (weapon_warning || unarmed_warning))
{
std::string prompt = "Really attack while ";
if (unarmed_warning)
prompt += "being unarmed?";
else
prompt += "wielding " + weapon->name(DESC_NOCAP_YOUR) + "? ";
const bool result = yesno(prompt.c_str(), true, 'n');
learned_something_new(TUT_WIELD_WEAPON);
if (result)
you.received_weapon_warning = true;
return (result);
}
return (true);
}
bool you_attack(int monster_attacked, bool unarmed_attacks)
{
ASSERT(!crawl_state.arena);
monsters *defender = &menv[monster_attacked];
melee_attack attk(&you, defender, unarmed_attacks);
interrupt_activity(AI_HIT_MONSTER, defender);
if (you.can_see(defender) && !wielded_weapon_check(attk.weapon))
{
you.turn_is_over = false;
return (false);
}
bool attack = attk.attack();
if (!attack)
{
if (attk.cancel_attack)
you.turn_is_over = false;
return (false);
}
return (true);
}
static void mons_lose_attack_energy(monsters *attacker, int wpn_speed,
int which_attack)
{
if (which_attack == 0)
attacker->lose_energy(EUT_ATTACK);
if (which_attack > 1)
return;
if (wpn_speed > 0)
{
const int atk_speed = attacker->action_energy(EUT_ATTACK);
if (which_attack > 0)
wpn_speed = div_rand_round( (2 * atk_speed + wpn_speed), 3 );
int delta = div_rand_round( (wpn_speed - 10 + (atk_speed - 10)), 2 );
if (delta > 0)
attacker->speed_increment -= delta;
}
}
bool monster_attack(monsters* attacker, bool allow_unarmed)
{
ASSERT(!crawl_state.arena);
if (mons_wont_attack(attacker) && !mons_is_confused(attacker))
return (false);
behaviour_event(attacker, ME_ALERT, MHITYOU);
melee_attack attk(attacker, &you, allow_unarmed);
attk.attack();
return (true);
}
bool monsters_fight(monsters* attacker, monsters* defender,
bool allow_unarmed)
{
melee_attack attk(attacker, defender, allow_unarmed);
return (attk.attack());
}
int weapon_str_weight( object_class_type wpn_class, int wpn_type )
{
int ret;
const int wpn_skill = weapon_skill( wpn_class, wpn_type );
const int hands = hands_reqd( wpn_class, wpn_type, you.body_size() );
switch (wpn_skill)
{
case SK_SHORT_BLADES: ret = 2; break;
case SK_LONG_BLADES: ret = 3; break;
case SK_STAVES: ret = 3; break; case SK_POLEARMS: ret = 3; break; case SK_AXES: ret = 6; break;
case SK_MACES_FLAILS: ret = 7; break;
default: ret = 5; break;
}
if (wpn_type == WPN_WHIP || wpn_type == WPN_DEMON_WHIP)
ret = 2;
else if (wpn_type == WPN_QUICK_BLADE) ret = 1;
if (hands == HANDS_TWO)
ret += 2;
if (ret > 8)
{
if (wpn_type == WPN_GIANT_CLUB || wpn_type == WPN_GIANT_SPIKED_CLUB)
ret = 9;
else
ret = 8;
}
return (ret);
}
static inline int player_weapon_str_weight()
{
const item_def* weapon = you.weapon();
if (!weapon)
return (4);
int ret = weapon_str_weight(weapon->base_type, weapon->sub_type);
if (hands_reqd(*weapon, you.body_size()) == HANDS_HALF && !you.shield())
ret += 1;
return (ret);
}
static inline int player_weapon_dex_weight( void )
{
return (10 - player_weapon_str_weight());
}
static inline int calc_stat_to_hit_base( void )
{
#ifdef USE_NEW_COMBAT_STATS
const signed int towards_str_avg = (you.strength - you.dex) / 2;
return (you.dex + towards_str_avg * player_weapon_str_weight() / 10);
#else
return (you.dex);
#endif
}
static inline int calc_stat_to_dam_base( void )
{
#ifdef USE_NEW_COMBAT_STATS
const signed int towards_dex_avg = (you.dex - you.strength) / 2;
return (you.strength + towards_dex_avg * player_weapon_dex_weight() / 10);
#else
return (you.strength);
#endif
}
static void stab_message(actor *defender, int stab_bonus)
{
switch (stab_bonus)
{
case 3: if (coinflip())
{
mprf( "You strike %s from a blind spot!",
defender->name(DESC_NOCAP_THE).c_str() );
}
else
{
mprf( "You catch %s momentarily off-guard.",
defender->name(DESC_NOCAP_THE).c_str() );
}
break;
case 2: if (!one_chance_in(3))
{
mprf( "You catch %s completely off-guard!",
defender->name(DESC_NOCAP_THE).c_str() );
}
else
{
mprf( "You strike %s from behind!",
defender->name(DESC_NOCAP_THE).c_str() );
}
break;
case 1:
mprf( "%s fails to defend %s.",
defender->name(DESC_CAP_THE).c_str(),
defender->pronoun(PRONOUN_REFLEXIVE).c_str() );
break;
}
}