still fairly tangled, but they're better than the old monster. The idea is to also put monster-vs-player and monster-vs-monster into the melee_attack framework so that refactoring combat code with elements of 4.1 becomes easier.
This is a big refactoring, so it's likely to be buggy. Some of the combat diagnostics - notably the damage rolls - are also AWOL. Will fix going forward.
Note: The combat code is still classic b26.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@950 c06c8d41-db1a-0410-9941-cceddc491573
R22TTMI6WXWULC7ODKFF3QCB7MOTETQQ6IR4BUCUPOCQKQNCTT5AC
KEYK3CH5J46U6TTOKTWRNMYTZXMQXFVEAZUC4ZQ4QCOSJHVIBDRQC
AU3E5FTABBHFZAZHLGA3UUR5TCTRLDOEMAKTGGEWKCQVH77I3SYAC
I5N4EIR6SCLLRGKRBUKW5FKUVYK62EA5DOWIAS5XFIHZQKMCXWBAC
ITDUEUO7XAZANPC4GRB3SEDFBOV7GLFPNPTYE5LYNC3CS6BSVZTQC
ZMYT53SCUSEG26QOTSRBQ3TWMSQTGBXZNEDPLPHM5U3VHALVOOIQC
RI5TNMRQUYPOFGT3LBGNSIYVU2IAQ2F6BT2AMUMM3EFJXOZRWPAQC
QVVC7AYGVA6U64PTNA7L27422NLMO327P22BQKXEVIMPZHIHO7MQC
QUXTRAANLFJWXMRAANUIXUU2AELFIZUBNAKGZZIVTSKX6NUMWGTQC
77H4BWWPPGLM3PLZH4QTAJRXIZTSDVNCOKZE223I437FN2UJ34RQC
33ZMPQC6OXTESW7SRW765GRNJUEJRSYONRVZVIEUDAUEJ2PPMB4AC
AOAJ6D3OKSELEYKAT55XCVU5LYJ7SMCZKC6DIEGLLB3TF2LEENWQC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
ZP2KE7A2LE7Z2S7AC45WE4CXDSEVDTWIMV2EM4IBUKXYJIDU6R7QC
ABPRQBBNMOXUJJNZXAJBIIUULMCB23MKAM5T3M5YFXDORJ3IY3JAC
LXLUKS5CKXBUSVV3QTZ4SM7NWSY6JFQEBHUBQW2VUEU5DOL3RRLAC
RGWUYJO74FDGTH22CYSHBKFDGJ4S76WTLIULKPXA4QT5ZCNOB4LQC
VIFRP3HZEONFR6PQRYZYM3YUEJOQ7T4F5CZY4MN4YJMB23FMU7XAC
if (is_feature('>',gx,gy))
learned_something_new(TUT_SEEN_STAIRS);
else if (is_feature('_',gx,gy))
learned_something_new(TUT_SEEN_ALTAR);
else if (grd[gx][gy] == DNGN_CLOSED_DOOR)
learned_something_new(TUT_SEEN_DOOR,gx,gy);
else if (grd[gx][gy] == DNGN_ENTER_SHOP)
learned_something_new(TUT_SEEN_SHOP,gx,gy);
const int ex = gx - you.x_pos + 9;
const int ey = gy - you.y_pos + 9;
int object = env.show[ex][ey];
if (object)
{
if (is_feature('>',gx,gy))
learned_something_new(TUT_SEEN_STAIRS);
else if (is_feature('_',gx,gy))
learned_something_new(TUT_SEEN_ALTAR);
else if (grd[gx][gy] == DNGN_CLOSED_DOOR)
learned_something_new(TUT_SEEN_DOOR,gx,gy);
else if (grd[gx][gy] == DNGN_ENTER_SHOP)
learned_something_new(TUT_SEEN_SHOP,gx,gy);
}
}
std::string actor_name(const monsters *actor, description_level_type desc)
{
return (actor? ptr_monam(actor, desc) : pronoun_you(desc));
}
// actor_verb(NULL, "chop") == chop, as in "You chop"
// actor_verb(monster, "chop") == chops, as in "The skeletal warrior chops".
std::string actor_verb(const monsters *actor, const std::string &verb)
{
// Simplistic - we really should be conjugating the verb
// appropriately. We'll special-case as necessary.
return (actor? verb + "s" : verb);
}
int actor_damage_type(const monsters *actor)
{
return (actor? mons_damage_type(actor) : player_damage_type());
int actor_damage_brand(const monsters *actor)
{
return (actor? mons_damage_brand(actor) : player_damage_brand());
}
//////////////////////////////////////////////////////////////////////////
const int wpn = you.equip[ EQ_WEAPON ];
if (wpn != -1)
{
return (get_vorpal_type(you.inv[wpn]));
}
else if (you.equip[EQ_GLOVES] == -1 &&
you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
{
return (DVORP_SLICING);
}
else if (you.equip[EQ_GLOVES] == -1 &&
(you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| you.mutation[MUT_CLAWS]
|| you.species == SP_TROLL
|| you.species == SP_GHOUL))
{
return (DVORP_CLAWING);
}
return (DVORP_CRUSHING);
return you.damage_type();
int ret = SPWPN_NORMAL;
const int wpn = you.equip[ EQ_WEAPON ];
if (wpn != -1)
{
if ( !is_range_weapon(you.inv[wpn]) )
ret = get_weapon_brand( you.inv[wpn] );
}
else if (you.confusing_touch)
ret = SPWPN_CONFUSE;
else if (you.mutation[MUT_DRAIN_LIFE])
ret = SPWPN_DRAINING;
else
{
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_SPIDER:
ret = SPWPN_VENOM;
break;
case TRAN_ICE_BEAST:
ret = SPWPN_FREEZING;
break;
case TRAN_LICH:
ret = SPWPN_DRAINING;
break;
default:
break;
}
}
return (ret);
return you.damage_brand();
size_type ret = (base) ? SIZE_CHARACTER : transform_size( psize );
if (ret == SIZE_CHARACTER)
{
// transformation has size of character's species:
switch (you.species)
{
case SP_OGRE:
case SP_OGRE_MAGE:
case SP_TROLL:
ret = SIZE_LARGE;
break;
case SP_NAGA:
// Most of their body is on the ground giving them a low profile.
if (psize == PSIZE_TORSO || psize == PSIZE_PROFILE)
ret = SIZE_MEDIUM;
else
ret = SIZE_BIG;
break;
case SP_CENTAUR:
ret = (psize == PSIZE_TORSO) ? SIZE_MEDIUM : SIZE_BIG;
break;
case SP_SPRIGGAN:
ret = SIZE_LITTLE;
break;
case SP_HALFLING:
case SP_GNOME:
case SP_KOBOLD:
ret = SIZE_SMALL;
break;
default:
ret = SIZE_MEDIUM;
break;
}
}
return (ret);
return you.body_size(psize, base);
bool player::in_water() const
{
return !is_levitating()
&& grid_is_water(grd[you.x_pos][you.y_pos]);
}
bool player::can_swim() const
{
return (species == SP_MERFOLK);
}
bool player::swimming() const
{
return in_water() && can_swim();
}
bool player::has_spell(int spell) const
{
for (int i = 0; i < 25; i++)
{
if (spells[i] == spell)
return (true);
}
return (false);
}
bool player::floundering() const
{
return in_water() && !can_swim();
}
size_type player::body_size(int psize, bool base) const
{
size_type ret = (base) ? SIZE_CHARACTER : transform_size( psize );
if (ret == SIZE_CHARACTER)
{
// transformation has size of character's species:
switch (species)
{
case SP_OGRE:
case SP_OGRE_MAGE:
case SP_TROLL:
ret = SIZE_LARGE;
break;
case SP_NAGA:
// Most of their body is on the ground giving them a low profile.
if (psize == PSIZE_TORSO || psize == PSIZE_PROFILE)
ret = SIZE_MEDIUM;
else
ret = SIZE_BIG;
break;
case SP_CENTAUR:
ret = (psize == PSIZE_TORSO) ? SIZE_MEDIUM : SIZE_BIG;
break;
case SP_SPRIGGAN:
ret = SIZE_LITTLE;
break;
case SP_HALFLING:
case SP_GNOME:
case SP_KOBOLD:
ret = SIZE_SMALL;
break;
default:
ret = SIZE_MEDIUM;
break;
}
}
return (ret);
}
int player::damage_type(int)
{
const int wpn = equip[ EQ_WEAPON ];
if (wpn != -1)
{
return (get_vorpal_type(inv[wpn]));
}
else if (equip[EQ_GLOVES] == -1 &&
attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
{
return (DVORP_SLICING);
}
else if (equip[EQ_GLOVES] == -1 &&
(attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| mutation[MUT_CLAWS]
|| species == SP_TROLL
|| species == SP_GHOUL))
{
return (DVORP_CLAWING);
}
return (DVORP_CRUSHING);
}
int player::damage_brand(int)
{
int ret = SPWPN_NORMAL;
const int wpn = equip[ EQ_WEAPON ];
if (wpn != -1)
{
if ( !is_range_weapon(inv[wpn]) )
ret = get_weapon_brand( inv[wpn] );
}
else if (confusing_touch)
ret = SPWPN_CONFUSE;
else if (mutation[MUT_DRAIN_LIFE])
ret = SPWPN_DRAINING;
else
{
switch (attribute[ATTR_TRANSFORMATION])
{
case TRAN_SPIDER:
ret = SPWPN_VENOM;
break;
case TRAN_ICE_BEAST:
ret = SPWPN_FREEZING;
break;
case TRAN_LICH:
ret = SPWPN_DRAINING;
break;
default:
break;
}
}
return (ret);
}
item_def *player::slot_item(equipment_type eq)
{
ASSERT(eq >= EQ_WEAPON && eq <= EQ_AMULET);
const int item = equip[eq];
return (item == -1? NULL : &inv[item]);
}
// Returns the item in the player's weapon slot.
item_def *player::weapon(int /* which_attack */)
{
return slot_item(EQ_WEAPON);
}
item_def *player::shield()
{
return slot_item(EQ_SHIELD);
}
std::string player::name(description_level_type type) const
{
return (pronoun_you(type));
}
std::string player::conj_verb(const std::string &verb) const
{
return (verb);
}
int player::id() const
{
return (-1);
}
bool player::fumbles_attack(bool verbose)
{
// fumbling in shallow water <early return>:
if (floundering())
{
if (random2(dex) < 4 || one_chance_in(5))
{
if (verbose)
mpr("Unstable footing causes you to fumble your attack.");
return (true);
}
}
return (false);
}
bool player::cannot_fight() const
{
return (false);
}
void player::attacking(actor *other)
{
if (other && other->atype() == ACT_MONSTER)
{
const monsters *mons = dynamic_cast<monsters*>(other);
if (mons_friendly(mons))
did_god_conduct(DID_ATTACK_FRIEND, 5);
else
pet_target = monster_index(mons);
}
if (mutation[MUT_BERSERK] &&
(random2(100) < (mutation[MUT_BERSERK] * 10) - 5))
{
go_berserk(false);
}
}
void player::go_berserk(bool intentional)
{
::go_berserk(intentional);
}
void player::make_hungry(int hunger_increase, bool silent)
{
::make_hungry(hunger_increase, silent);
}
const int grid = grd[m->x][m->y];
return ((grid == DNGN_DEEP_WATER || grid == DNGN_SHALLOW_WATER)
// Can't use monster_habitable_grid because that'll return true
// for non-water monsters in shallow water.
&& monster_habitat(m->type) != DNGN_DEEP_WATER
&& !mons_class_flag(m->type, M_AMPHIBIOUS)
&& !mons_flies(m));
return (m->floundering());
int mons_weapon_index(const monsters *m)
{
// This randomly picks one of the wielded weapons for monsters that can use
// two weapons. Not ideal, but better than nothing. fight.cc does it right,
// for various values of right.
int weap = m->inv[MSLOT_WEAPON];
if (mons_wields_two_weapons(m))
{
const int offhand = mons_offhand_weapon_index(m);
if (offhand != NON_ITEM && (weap == NON_ITEM || coinflip()))
weap = offhand;
}
return (weap);
}
int mons_damage_type(const monsters *m)
{
const int mweap = mons_weapon_index(m);
if (mweap == NON_ITEM)
return (mons_base_damage_type(m));
return (get_vorpal_type(mitm[mweap]));
}
int mons_damage_brand(const monsters *m)
int mons_size(const monsters *m)
const int mweap = mons_weapon_index(m);
if (mweap == NON_ITEM)
return (SPWPN_NORMAL);
const item_def &weap = mitm[mweap];
return (!is_range_weapon(weap)? get_weapon_brand(weap) : SPWPN_NORMAL);
return m->body_size();
}
///////////////////////////////////////////////////////////////////////////////
// monsters methods
coord_def monsters::pos() const
{
return coord_def(x, y);
}
bool monsters::swimming() const
{
const int grid = grd[x][y];
return (grid_is_watery(grid) && monster_habitat(type) == DNGN_DEEP_WATER);
}
bool monsters::floundering() const
{
const int grid = grd[x][y];
return (grid_is_water(grid)
// Can't use monster_habitable_grid because that'll return true
// for non-water monsters in shallow water.
&& monster_habitat(type) != DNGN_DEEP_WATER
&& !mons_class_flag(type, M_AMPHIBIOUS)
&& !mons_flies(this));
}
size_type monsters::body_size(int /* psize */, bool /* base */) const
{
const monsterentry *e = seekmonster(type);
return (e? e->size : SIZE_MEDIUM);
}
int monsters::damage_type(int which_attack)
{
const item_def *mweap = weapon(which_attack);
if (!mweap)
return (mons_base_damage_type(this));
return (get_vorpal_type(*mweap));
int monsters::damage_brand(int which_attack)
{
const item_def *mweap = weapon(which_attack);
if (!mweap)
return (SPWPN_NORMAL);
return (!is_range_weapon(*mweap)? get_weapon_brand(*mweap) : SPWPN_NORMAL);
}
item_def *monsters::weapon(int which_attack)
{
if (which_attack > 1)
which_attack &= 1;
// This randomly picks one of the wielded weapons for monsters that can use
// two weapons. Not ideal, but better than nothing. fight.cc does it right,
// for various values of right.
int weap = inv[MSLOT_WEAPON];
if (which_attack && mons_wields_two_weapons(this))
{
const int offhand = mons_offhand_weapon_index(this);
if (offhand != NON_ITEM
&& (weap == NON_ITEM || which_attack == 1 || coinflip()))
{
weap = offhand;
}
}
return (weap == NON_ITEM? NULL : &mitm[weap]);
}
item_def *monsters::shield()
{
return (NULL);
}
std::string monsters::name(description_level_type desc) const
{
return (ptr_monam(this, desc));
}
std::string monsters::conj_verb(const std::string &verb) const
{
return (verb + "s");
}
int monsters::id() const
{
return (type);
}
bool monsters::fumbles_attack(bool verbose)
{
if (floundering() && one_chance_in(4))
{
if (verbose && !silenced(you.x_pos, you.y_pos)
&& !silenced(x, y))
{
mprf(MSGCH_SOUND, "You hear a splashing noise.");
}
}
return (false);
}
bool monsters::cannot_fight() const
{
return mons_class_flag(type, M_NO_EXP_GAIN)
|| mons_is_statue(type);
}
void monsters::attacking(actor * /* other */)
{
}
void monsters::go_berserk(bool /* intentional */)
{
}
- row 3: mass, experience modifier, charclass, holiness, resist magic
- row 4: damage for each of four attacks
- row 5: hit dice, described by four parameters
- row 6: AC, evasion, speed, speed_inc, sec(spell), corpse_thingy,
- row 3: monster resistance flags
- row 4: mass, experience modifier, charclass, holiness, resist magic
- row 5: damage for each of four attacks
- row 6: hit dice, described by four parameters
- row 7: AC, evasion, speed, speed_inc, sec(spell), corpse_thingy,
/* ************************************************************************
Josh added the following, but they just won't work in the game just yet ...
besides, four bear types !?!?! isn't that a *bit* excessive given the
limited diversity of existing monster types?
I'm still far from happy about the inclusion of "Shuggoths" -- I just do
not think it fits into Crawl ... {dlb}
************************************************************************ */
//jmf: it's never created anywhere yet, so you can save the punctuation.
// as to bears & wolves: the lair needs more variety.
MONS_SHUGGOTH, 'A', LIGHTGREEN, "shuggoth",
M_NO_SKELETON | M_SEE_INVIS,
MR_RES_ELEC | MR_RES_POISON | MR_RES_FIRE | MR_RES_COLD,
1000, 10, MONS_SHUGGOTH, MONS_SHUGGOTH, MH_DEMONIC, 300,
{ 5, 5, 5, 0 },
{ 10, 4, 4, 0 },
10, 10, 20, 7, MST_NO_SPELLS, CE_NOCORPSE, Z_NOZOMBIE, -1, I_NORMAL,
MONUSE_NOTHING
}
,
#endif
{
}
int item_special_wield_effect(const item_def &item)
{
if (item.base_type != OBJ_WEAPONS || !is_artefact(item))
return (SPWLD_NONE);
int i_eff = SPWPN_NORMAL;
if (is_random_artefact( item ))
i_eff = randart_wpn_property(item, RAP_BRAND);
else
i_eff = item.special;
switch (i_eff)
{
case SPWPN_SINGING_SWORD:
return (SPWLD_SING);
case SPWPN_WRATH_OF_TROG:
return (SPWLD_TROG);
case SPWPN_SCYTHE_OF_CURSES:
return (SPWLD_CURSE);
case SPWPN_MACE_OF_VARIABILITY:
return (SPWLD_VARIABLE);
case SPWPN_GLAIVE_OF_PRUNE:
return (SPWLD_NONE);
case SPWPN_SCEPTRE_OF_TORMENT:
return (SPWLD_TORMENT);
case SPWPN_SWORD_OF_ZONGULDROK:
return (SPWLD_ZONGULDROK);
case SPWPN_SWORD_OF_POWER:
return (SPWLD_POWER);
case SPWPN_STAFF_OF_OLGREB:
return (SPWLD_OLGREB);
case SPWPN_STAFF_OF_WUCAD_MU:
return (SPWLD_WUCAD_MU);
default:
return (SPWLD_NONE);
}
struct melee_attack
{
public:
// At the moment this only covers players fighting monsters
actor *attacker, *defender;
// If attacker and/or defender are monsters, these are set.
monsters *atk, *def;
bool did_hit;
bool unarmed_ok;
int attack_number;
int to_hit;
int base_damage;
int potential_damage;
int damage_done;
int special_damage;
int aux_damage;
bool stab_attempt;
int stab_bonus;
int min_delay;
int final_attack_delay;
// Attacker's damage output potential:
item_def *weapon;
int damage_brand; // Can be special even if unarmed (transforms)
int wpn_skill, hands;
int spwld; // Special wield effects?
bool hand_half_bonus;
// If weapon is a randart, its properties.
randart_properties_t art_props;
// Attack messages
std::string attack_verb, verb_degree;
std::string no_damage_message;
std::string special_damage_message;
std::string unarmed_attack;
item_def *shield;
// Armour penalties?
int heavy_armour_penalty;
bool can_do_unarmed;
// Attacker uses watery terrain to advantage vs defender. Implies that
// both attacker and defender are in water.
bool water_attack;
public:
melee_attack(actor *attacker, actor *defender,
bool allow_unarmed = true, int attack_num = -1);
// Applies attack damage and other effects.
bool attack();
int calc_to_hit(bool random = true);
private:
void init_attack();
bool is_water_attack(const actor *, const actor *) const;
void check_hand_half_bonus_eligible();
void check_autoberserk();
void check_special_wield_effects();
void emit_nodmg_hit_message();
std::string debug_damage_number();
std::string special_attack_punctuation();
std::string attack_strength_punctuation();
private:
// Monster-attack specific stuff
bool mons_attack_you();
bool mons_attack_mons();
int mons_to_hit();
private:
// Player-attack specific stuff
bool player_attack();
bool player_aux_unarmed();
bool player_apply_aux_unarmed();
int player_stat_modify_damage(int damage);
int player_aux_stat_modify_damage(int damage);
int player_to_hit(bool random_factor);
void player_apply_attack_delay();
int player_apply_water_attack_bonus(int damage);
int player_apply_weapon_bonuses(int damage);
int player_apply_weapon_skill(int damage);
int player_apply_fighting_skill(int damage, bool aux);
int player_apply_misc_modifiers(int damage);
int player_apply_monster_ac(int damage);
void player_weapon_auto_id();
int player_stab_weapon_bonus(int damage);
int player_stab(int damage);
int player_weapon_type_modify(int damage);
bool player_hits_monster();
int player_calc_base_weapon_damage();
int player_calc_base_unarmed_damage();
bool player_hurt_monster();
void player_exercise_combat_skills();
bool player_monattk_hit_effects(bool mondied);
void player_calc_brand_damage(int res, const char *message_format);
bool player_apply_damage_brand();
void player_sustain_passive_damage();
int player_staff_damage(int skill);
void player_apply_staff_damage();
bool player_check_monster_died();
void player_calc_hit_damage();
void player_stab_check();
int player_weapon_speed();
int player_unarmed_speed();
int player_apply_shield_delay(int attack_delay);
void player_announce_hit();
std::string player_why_missed();
void player_warn_miss();
};
const int weapon = you.equip[EQ_WEAPON];
const bool ur_armed = (weapon != -1); // compacts code a bit {dlb}
int wpn_skill = SK_UNARMED_COMBAT;
if (weapon != -1)
{
wpn_skill = weapon_skill( you.inv[weapon].base_type,
you.inv[weapon].sub_type );
}
int your_to_hit;
// preliminary to_hit modifications:
your_to_hit = 15 + (calc_stat_to_hit_base() / 2);
if (water_attack)
your_to_hit += 5;
if (wearing_amulet(AMU_INACCURACY))
your_to_hit -= 5;
// if you can't see yourself, you're a little less acurate.
if (you.invis && !player_see_invis())
your_to_hit -= 5;
// fighting contribution
your_to_hit += maybe_random2(1 + you.skills[SK_FIGHTING], random_factor);
// weapon skill contribution
if (ur_armed)
{
if (wpn_skill != SK_FIGHTING)
{
your_to_hit += maybe_random2(you.skills[wpn_skill] + 1,
random_factor);
}
}
else
{ // ...you must be unarmed
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);
}
// weapon bonus contribution
if (ur_armed)
{
if (you.inv[ weapon ].base_type == OBJ_WEAPONS)
{
your_to_hit += you.inv[ weapon ].plus;
your_to_hit += property( you.inv[ weapon ], PWPN_HIT );
if (get_equip_race(you.inv[ weapon ]) == ISFLAG_ELVEN
&& player_genus(GENPC_ELVEN))
{
your_to_hit += (random_factor && coinflip() ? 2 : 1);
}
}
else if (item_is_staff( you.inv[ weapon ] ))
{
// magical staff
your_to_hit += property( you.inv[ weapon ], PWPN_HIT );
}
}
// slaying bonus
your_to_hit += slaying_bonus(PWPN_HIT);
// hunger penalty
if (you.hunger_state == HS_STARVING)
your_to_hit -= 3;
// armour penalty
your_to_hit -= heavy_armour;
#if DEBUG_DIAGNOSTICS
int roll_hit = your_to_hit;
#endif
// hit roll
your_to_hit = maybe_random2(your_to_hit, random_factor);
#if DEBUG_DIAGNOSTICS
snprintf( info, INFO_SIZE, "to hit die: %d; rolled value: %d",
roll_hit, your_to_hit );
mpr( info, MSGCH_DIAGNOSTICS );
#endif
if (hand_and_a_half_bonus)
your_to_hit += maybe_random2(3, random_factor);
if (ur_armed && wpn_skill == SK_SHORT_BLADES && you.sure_blade)
your_to_hit += 5 +
(random_factor ? random2limit( you.sure_blade, 10 ) :
you.sure_blade / 2);
// other stuff
if (!ur_armed)
{
if ( you.confusing_touch )
// just trying to touch is easier that trying to damage
your_to_hit += maybe_random2(you.dex, random_factor);
switch ( you.attribute[ATTR_TRANSFORMATION] )
{
case TRAN_NONE:
break;
case TRAN_SPIDER:
your_to_hit += maybe_random2(10, 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_SERPENT_OF_HELL:
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_AIR:
your_to_hit = 0;
break;
default:
break;
}
}
return your_to_hit;
melee_attack attk(&you, NULL);
return attk.calc_to_hit(random_factor);
// fumbling in shallow water <early return>:
if (player_in_water() && !player_is_swimming())
{
if (random2(you.dex) < 4 || one_chance_in(5))
{
mpr("Unstable footing causes you to fumble your attack.");
return (true);
}
}
return (false);
return (you.burden_state == BS_UNENCUMBERED
&& random2(20) < you.skills[SK_UNARMED_COMBAT]
&& random2(1 + heavy_armour_penalty) < 2);
// Returns true if you hit the monster.
bool you_attack(int monster_attacked, bool unarmed_attacks)
//////////////////////////////////////////////////////////////////////////
// Melee attack
melee_attack::melee_attack(actor *attk, actor *defn,
bool allow_unarmed, int which_attack)
: attacker(attk), defender(defn),
atk(NULL), def(NULL),
unarmed_ok(allow_unarmed),
attack_number(which_attack),
to_hit(0), base_damage(0), potential_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),
spwld(SPWLD_NONE), hand_half_bonus(false),
art_props(0), attack_verb(), verb_degree(),
no_damage_message(), special_damage_message(), unarmed_attack(),
heavy_armour_penalty(0), can_do_unarmed(false),
water_attack(false)
monsters *defender = &menv[monster_attacked];
int your_to_hit;
int damage_done = 0;
bool hit = false;
bool base_nodamage = false;
unsigned char stab_bonus = 0; // this is never negative {dlb}
int temp_rand; // for probability determination {dlb}
init_attack();
}
const int weapon = you.equip[EQ_WEAPON];
const bool ur_armed = (weapon != -1); // compacts code a bit {dlb}
const bool bearing_shield = (you.equip[EQ_SHIELD] != -1);
void melee_attack::check_hand_half_bonus_eligible()
{
hand_half_bonus =
unarmed_ok
&& !can_do_unarmed
&& !shield
&& weapon
&& !item_cursed( *weapon )
&& hands == HANDS_HALF;
}
char str_pass[ ITEMNAME_SIZE ];
char base_damage_message[ITEMNAME_SIZE];
char special_damage_message[ITEMNAME_SIZE];
wpn_skill = weapon? weapon_skill( *weapon ) : SK_UNARMED_COMBAT;
if (weapon)
{
hands = hands_reqd( *weapon, attacker->body_size() );
spwld = item_special_wield_effect( *weapon );
}
// We're trying to hit a monster, break out of travel/explore now.
interrupt_activity(AI_HIT_MONSTER, defender);
if (ur_armed && (you.inv[weapon].base_type != OBJ_WEAPONS
|| you.inv[weapon].sub_type == WPN_BOW || you.inv[weapon].sub_type == WPN_SLING))
learned_something_new(TUT_WIELD_WEAPON);
if (ur_armed && you.inv[weapon].base_type == OBJ_WEAPONS
&& is_random_artefact( you.inv[weapon] ))
void melee_attack::check_autoberserk()
{
if (weapon
&& art_props[RAP_ANGRY] >= 1
&& !one_chance_in(1 + art_props[RAP_ANGRY]))
// Probably over protective, but in this code we're going
// to play it safe. -- bwr
for (int i = 0; i < RA_PROPERTIES; i++)
art_proprt[i] = 0;
case SPWLD_TROG:
if (coinflip())
attacker->go_berserk(false);
break;
case SPWLD_WUCAD_MU:
// XXX At some distant point in the future, allow
// miscast_effect to operate on any actor.
if (one_chance_in(9) && attacker->atype() == ACT_PLAYER)
{
miscast_effect( SPTYP_DIVINATION, random2(9), random2(70), 100,
"the Staff of Wucad Mu" );
}
break;
default:
break;
// if we're not getting potential unarmed attacks, and not wearing a
// shield, and have a suitable uncursed weapon we get the bonus.
bool use_hand_and_a_half_bonus = false;
// The attacker loses nutrition.
attacker->make_hungry(3, true);
if (weapon != -1)
{
wpn_skill = weapon_skill( you.inv[weapon].base_type,
you.inv[weapon].sub_type );
// Trying to stay general beyond this point is a recipe for insanity.
// Maybe when Stone Soup hits 1.0... :-)
return (attacker->atype() == ACT_PLAYER? player_attack() :
defender->atype() == ACT_PLAYER? mons_attack_you() :
mons_attack_mons() );
}
bool melee_attack::player_attack()
{
potential_damage =
!weapon? player_calc_base_unarmed_damage()
: player_calc_base_weapon_damage();
/*
**************************************************************************
* *
* IMPORTANT: When altering damage routines, must also change in ouch.cc *
* for saving of player ghosts. *
* *
**************************************************************************
*/
bool helpless = mons_class_flag(defender->type, M_NO_EXP_GAIN)
|| mons_is_statue(defender->type);
// This actually does more than calculate damage - it also sets up
// messages, etc.
player_calc_hit_damage();
if (mons_friendly(defender))
did_god_conduct(DID_ATTACK_FRIEND, 5);
// always upset monster regardless of damage
behaviour_event(def, ME_WHACK, MHITYOU);
if (player_hurt_monster())
player_exercise_combat_skills();
if (player_check_monster_died())
return (true);
//jmf: check for backlight enchantment
if (mons_has_ench(defender, ENCH_BACKLIGHT_I, ENCH_BACKLIGHT_IV))
your_to_hit += 2 + random2(8);
if ((did_primary_hit || did_hit) && def->type != -1)
print_wounds(def);
if (ur_armed &&
you.inv[ weapon ].base_type == OBJ_WEAPONS &&
is_random_artefact( you.inv[ weapon ] ) &&
art_proprt[RAP_ANGRY] >= 1 &&
random2(1 + art_proprt[RAP_ANGRY]))
{
go_berserk(false);
}
// Returns true to end the attack round.
bool melee_attack::player_aux_unarmed()
{
damage_brand = SPWPN_NORMAL;
int uattack = UNAT_NO_ATTACK;
case SPWLD_WUCAD_MU:
if (one_chance_in(9))
if ((you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| player_genus(GENPC_DRACONIAN)
|| (you.species == SP_MERFOLK && player_is_swimming())
|| you.mutation[ MUT_STINGER ])
&& one_chance_in(3))
// if (you.mutation[MUT_DRAIN_LIFE])
// special_brand = SPWPN_DRAINING;
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SERPENT_OF_HELL
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER)
{
continue;
}
if (you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE)
{
switch (you.attribute[ATTR_TRANSFORMATION])
unarmed_attack = "kick";
aux_damage = ((you.mutation[MUT_HOOVES]
|| you.species == SP_CENTAUR) ? 10 : 5);
break;
case 1:
if (uattack != UNAT_HEADBUTT)
case TRAN_SPIDER:
damage = 5;
// special_brand = SPWPN_VENOM;
break;
case TRAN_ICE_BEAST:
damage = 12;
// special_brand = SPWPN_FREEZING;
break;
case TRAN_BLADE_HANDS:
damage = 12 + (you.strength / 4) + (you.dex / 4);
break;
case TRAN_STATUE:
damage = 12 + you.strength;
break;
case TRAN_SERPENT_OF_HELL:
case TRAN_DRAGON:
damage = 20 + you.strength;
break;
case TRAN_LICH:
damage = 5;
// special_brand = SPWPN_DRAINING;
break;
case TRAN_AIR:
damage = 0;
break;
if ((!you.mutation[MUT_HORNS] && you.species != SP_KENKU)
|| !one_chance_in(3))
{
continue;
}
damage += (you.mutation[ MUT_CLAWS ] * 2);
}
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SERPENT_OF_HELL
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON)
{
continue;
}
damage += you.skills[SK_UNARMED_COMBAT];
}
else
{
if (you.inv[ weapon ].base_type == OBJ_WEAPONS
|| item_is_staff( you.inv[ weapon ] ))
{
damage = property( you.inv[ weapon ], PWPN_DAMAGE );
}
}
unarmed_attack =
(you.species == SP_KENKU) ? "peck" : "headbutt";
#if DEBUG_DIAGNOSTICS
const int base_damage = damage;
#endif
aux_damage = 5 + you.mutation[MUT_HORNS] * 3;
// minotaurs used to get +5 damage here, now they get
// +6 because of the horns.
if (you.equip[EQ_HELMET] != -1
&& (get_helmet_type(you.inv[you.equip[EQ_HELMET]]) == THELM_HELMET
|| get_helmet_type(you.inv[you.equip[EQ_HELMET]]) == THELM_HELM))
{
aux_damage += 2;
if (get_helmet_desc(you.inv[you.equip[EQ_HELMET]]) == THELM_DESC_SPIKED
|| get_helmet_desc(you.inv[you.equip[EQ_HELMET]]) == THELM_DESC_HORNED)
{
aux_damage += 3;
}
}
break;
case 2: /* draconians */
if (uattack != UNAT_TAILSLAP)
{
// not draconian, and not wet merfolk
if ((!player_genus(GENPC_DRACONIAN)
&& (!(you.species == SP_MERFOLK && player_is_swimming()))
&& !you.mutation[ MUT_STINGER ])
|| (!one_chance_in(4)))
if (ur_armed)
{
if (you.inv[ weapon ].base_type == OBJ_WEAPONS
|| item_is_staff( you.inv[ weapon ] ))
{
weapon_speed2 = property( you.inv[ weapon ], PWPN_SPEED );
weapon_speed2 -= you.skills[ wpn_skill ] / 2;
if (you.mutation[ MUT_STINGER ] > 0)
{
aux_damage += (you.mutation[ MUT_STINGER ] * 2 - 1);
damage_brand = SPWPN_VENOM;
}
min_speed = property( you.inv[ weapon ], PWPN_SPEED ) / 2;
/* grey dracs have spiny tails, or something */
// maybe add this to player messaging {dlb}
//
// STINGER mutation doesn't give extra damage here... that
// would probably be a bit much, we'll still get the
// poison bonus so it's still somewhat good.
if (you.species == SP_GREY_DRACONIAN && you.experience_level >= 7)
aux_damage = 12;
break;
// Using both hands can get a weapon up to speed 7
if ((hands == HANDS_TWO || use_hand_and_a_half_bonus)
&& min_speed > 7)
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SERPENT_OF_HELL
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON)
// never go faster than speed 3 (ie 3 attacks per round)
if (min_speed < 3)
min_speed = 3;
/* no punching with a shield or 2-handed wpn, except staves */
if (shield || coinflip()
|| (weapon
&& hands == HANDS_TWO
&& weapon->base_type != OBJ_STAVES
&& weapon->sub_type != WPN_QUARTERSTAFF) )
{
continue;
}
}
else
{
// Unarmed speed
if (you.burden_state == BS_UNENCUMBERED
&& one_chance_in(heavy_armour + 1))
// unified to-hit calculation
to_hit = random2( calc_your_to_hit_unarmed() );
make_hungry(2, true);
alert_nearby_monsters();
// XXX We're clobbering did_hit
did_hit = false;
if (to_hit >= def->evasion || one_chance_in(30))
case ARM_LARGE_SHIELD:
if (you.skills[SK_SHIELDS] <= 10 + random2(17))
weapon_speed2++;
// [dshaligram] Fall-through
case ARM_SHIELD:
if (you.skills[SK_SHIELDS] <= 3 + random2(17))
weapon_speed2++;
break;
mprf("Your %s misses %s.",
unarmed_attack.c_str(),
defender->name(DESC_NOCAP_THE).c_str());
#if DEBUG_DIAGNOSTICS
snprintf( info, INFO_SIZE, "Weapon speed: %d; min: %d; speed: %d; attack time: %d",
weapon_speed2, min_speed, weapon_speed2, you.time_taken );
aux_damage = player_aux_stat_modify_damage(aux_damage);
aux_damage += slaying_bonus(PWPN_DAMAGE);
bool stabAttempt = false;
bool rollNeeded = true;
// Clear stab bonus which will be set for the primary weapon attack.
stab_bonus = 0;
aux_damage = player_apply_monster_ac(aux_damage);
if (aux_damage < 1)
aux_damage = 0;
else
hurt_monster(def, damage_done);
// confused (but not perma-confused)
if (mons_has_ench(defender, ENCH_CONFUSION)
&& !mons_class_flag(defender->type, M_CONFUSED))
{
stabAttempt = true;
stab_bonus = 2;
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( def, true );
if (mons_holiness(def) == MH_HOLY)
did_god_conduct(DID_KILL_ANGEL, 1);
// see if we need to roll against dexterity / stabbing
if (stabAttempt && rollNeeded)
stabAttempt = (random2(200) <= you.skills[SK_STABBING] + you.dex);
std::string melee_attack::debug_damage_number()
{
#ifdef DEBUG_DIAGNOSTICS
return make_stringf(" for %d", damage_done);
#else
return ("");
#endif
}
// check for invisibility - no stabs on invisible monsters.
if (!player_monster_visible( defender ))
{
stabAttempt = false;
stab_bonus = 0;
}
std::string melee_attack::special_attack_punctuation()
{
if (special_damage < 3)
return ".";
else if (special_damage < 7)
return "!";
else
return "!!";
}
#if DEBUG_DIAGNOSTICS
snprintf( info, INFO_SIZE, "your to-hit: %d; defender EV: %d",
your_to_hit, defender->evasion );
std::string melee_attack::attack_strength_punctuation()
{
if (damage_done < HIT_WEAK)
return ".";
else if (damage_done < HIT_MED)
return "!";
else if (damage_done < HIT_STRONG)
return "!!";
else
return "!!!";
}
mpr( info, MSGCH_DIAGNOSTICS );
#endif
void melee_attack::player_announce_hit()
{
if (!verb_degree.empty() && verb_degree[0] != ' ')
verb_degree = " " + verb_degree;
mprf("You %s %s%s%s%s",
attack_verb.c_str(),
ptr_monam(def, DESC_NOCAP_THE),
verb_degree.c_str(),
debug_damage_number().c_str(),
attack_strength_punctuation().c_str());
}
if ((your_to_hit >= defender->evasion || one_chance_in(30)) ||
((mons_is_paralysed(defender) || defender->behaviour == BEH_SLEEP)
&& !one_chance_in(10 + you.skills[SK_STABBING])))
{
hit = true;
int dammod = 78;
std::string melee_attack::player_why_missed()
{
if ((to_hit + heavy_armour_penalty / 2) >= def->evasion)
return "Your armour prevents you from hitting ";
else
return "You miss ";
}
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);
// upset only non-sleeping monsters if we missed
if (def->behaviour != BEH_SLEEP)
behaviour_event( def, ME_WHACK, MHITYOU );
// Yes, this isn't the *2 damage that monsters get, but since
// monster hps and player hps are different (as are the sizes
// of the attacks) it just wouldn't be fair to give merfolk
// that gross a potential... still they do get something for
// making use of the water. -- bwr
if (water_attack)
damage += random2avg(10,2);
return (to_hit >= def->evasion
|| one_chance_in(3)
|| ((mons_is_paralysed(def) || def->behaviour == BEH_SLEEP)
&& !one_chance_in(10 + you.skills[SK_STABBING])));
}
#if DEBUG_DIAGNOSTICS
const int water_damage = damage;
#endif
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;
#if DEBUG_DIAGNOSTICS
const int slay_damage = 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;
if (dam_stat_val < 9)
dammod -= random2(9 - dam_stat_val) / 2;
damage *= dammod;
damage /= 10;
mpr( info, MSGCH_DIAGNOSTICS );
int melee_attack::player_apply_water_attack_bonus(int damage)
{
#if 0
// Yes, this isn't the *2 damage that monsters get, but since
// monster hps and player hps are different (as are the sizes
// of the attacks) it just wouldn't be fair to give merfolk
// that gross a potential... still they do get something for
// making use of the water. -- bwr
// return (damage + random2avg(10,2));
#if DEBUG_DIAGNOSTICS
const int roll_damage = damage_done;
#endif
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;
}
#if DEBUG_DIAGNOSTICS
const int skill_damage = damage_done;
#endif
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;
if (you.hunger_state == HS_STARVING)
damage_done -= random2(5);
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 DEBUG_DIAGNOSTICS
const int preplus_damage = damage_done;
int plus_damage = damage_done;
int bonus_damage = damage_done;
#endif
if (ur_armed && you.inv[ weapon ].base_type == OBJ_WEAPONS)
// removed 2-handed weapons from here... their "bonus" is
// already included in the damage stat for the weapon -- bwr
if (hand_half_bonus)
damage += random2(3);
if (get_equip_race(*weapon) == ISFLAG_DWARVEN
&& player_genus(GENPC_DWARVEN))
#if DEBUG_DIAGNOSTICS
plus_damage = damage_done;
#endif
void melee_attack::player_weapon_auto_id()
{
if (weapon
&& !is_range_weapon( *weapon )
&& !item_ident( *weapon, ISFLAG_KNOW_PLUSES )
&& random2(100) < you.skills[ wpn_skill ])
{
set_ident_flags( *weapon, ISFLAG_KNOW_PLUSES );
mprf("You are wielding %s.",
item_name(*weapon, DESC_NOCAP_A));
more();
you.wield_change = true;
}
}
// removed 2-handed weapons from here... their "bonus" is
// already included in the damage stat for the weapon -- bwr
if (use_hand_and_a_half_bonus)
damage_done += random2(3);
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;
}
// fall through
case SK_LONG_SWORDS:
damage *= 10 + you.skills[SK_STABBING] /
(stab_bonus + (wpn_skill == SK_SHORT_BLADES ? 0 : 1));
damage /= 10;
// fall through
default:
damage *= 12 + you.skills[SK_STABBING] / stab_bonus;
damage /= 12;
}
if (get_equip_race(you.inv[ weapon ]) == ISFLAG_ORCISH
&& you.species == SP_HILL_ORC && coinflip())
{
damage_done++;
}
int melee_attack::player_stab(int damage)
{
// The stabbing message looks better here:
if (stab_attempt)
{
// construct reasonable message
stab_message( def, stab_bonus );
#if DEBUG_DIAGNOSTICS
bonus_damage = damage_done;
#endif
exercise(SK_STABBING, 1 + random2avg(5, 4));
if (mons_holiness(def) == MH_NATURAL
|| mons_holiness(def) == MH_HOLY)
{
did_god_conduct(DID_STABBING, 4);
}
}
else
{
stab_bonus = 0;
// ok.. if you didn't backstab, you wake up the neighborhood.
// I can live with that.
alert_nearby_monsters();
}
if (!is_range_weapon( you.inv[weapon] )
&& !item_ident( you.inv[ weapon ], ISFLAG_KNOW_PLUSES )
&& random2(100) < you.skills[ wpn_skill ])
if (stab_bonus)
{
// lets make sure we have some damage to work with...
if (damage < 1)
damage = 1;
if (def->behaviour == BEH_SLEEP)
{
// Sleeping moster wakes up when stabbed but may be groggy
if (random2(200) <= you.skills[SK_STABBING] + you.dex)
set_ident_flags( you.inv[ weapon ], ISFLAG_KNOW_PLUSES );
strcpy(info, "You are wielding ");
in_name( weapon , DESC_NOCAP_A, str_pass );
strcat(info, str_pass);
strcat(info, ".");
mpr(info);
more();
you.wield_change = true;
unsigned int stun = random2( you.dex + 1 );
if (def->speed_increment > stun)
def->speed_increment -= stun;
else
def->speed_increment = 0;
if (mons_holiness(defender) == MH_NATURAL
|| mons_holiness(defender) == MH_HOLY)
{
did_god_conduct(DID_STABBING, 4);
}
}
else
int melee_attack::player_apply_monster_ac(int damage)
{
if (stab_bonus)
{
// when stabbing we can get by some of the armour
if (def->armour_class > 0)
stab_bonus = 0;
// ok.. if you didn't backstab, you wake up the neighborhood.
// I can live with that.
alert_nearby_monsters();
int ac = def->armour_class
- random2( you.skills[SK_STABBING] / stab_bonus );
if (ac > 0)
damage -= random2(1 + ac);
if (stab_bonus)
{
// lets make sure we have some damage to work with...
if (damage_done < 1)
damage_done = 1;
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 (defender->behaviour == BEH_SLEEP)
{
// Sleeping moster wakes up when stabbed but may be groggy
if (random2(200) <= you.skills[SK_STABBING] + you.dex)
{
unsigned int stun = random2( you.dex + 1 );
// All weak hits look the same, except for when the player
// has a non-weapon in hand. -- bwr
if (damage < HIT_WEAK)
{
if (weap_type != WPN_UNKNOWN)
attack_verb = "hit";
else
attack_verb = "clumsily bash";
switch (wpn_skill)
// take transformations into account, if no weapon is wielded
if (weap_type == WPN_UNARMED
&& you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE)
{
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_SPIDER:
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_ICE_BEAST:
case TRAN_STATUE:
case TRAN_LICH:
if (damage < HIT_MED)
attack_verb = "punch";
else
attack_verb = "pummel";
break;
case TRAN_DRAGON:
case TRAN_SERPENT_OF_HELL:
if (damage < HIT_MED)
attack_verb = "claw";
else if (damage < HIT_STRONG)
attack_verb = "bite";
else
case SK_SHORT_BLADES:
{
int bonus = (you.dex * (you.skills[SK_STABBING] + 1)) / 5;
attack_verb = "maul";
if (defender->body_size() <= SIZE_MEDIUM && coinflip())
attack_verb = "trample on";
}
break;
case TRAN_AIR:
attack_verb = "buffet";
break;
} // transformations
bonus = stepdown_value( bonus, 10, 10, 30, 30 );
switch (weapon? get_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";
verb_degree = " like a pig";
}
break;
damage_done += bonus;
}
// fall through
case SK_LONG_SWORDS:
damage_done *= 10 + you.skills[SK_STABBING] /
(stab_bonus + (wpn_skill == SK_SHORT_BLADES ? 0 : 1));
damage_done /= 10;
// fall through
default:
damage_done *= 12 + you.skills[SK_STABBING] / stab_bonus;
damage_done /= 12;
}
case DAM_SLICE:
if (damage < HIT_MED)
attack_verb = "slash";
else if (damage < HIT_STRONG)
attack_verb = "slice";
else
{
attack_verb = "open";
verb_degree = " like a pillowcase";
}
break;
#if DEBUG_DIAGNOSTICS
stab_damage = damage_done;
#endif
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;
// when stabbing we can get by some of the armour
if (defender->armour_class > 0)
{
int ac = defender->armour_class
- random2( you.skills[SK_STABBING] / stab_bonus );
case DAM_WHIP:
if (damage < HIT_MED)
attack_verb = "whack";
else
attack_verb = "thrash";
break;
if (ac > 0)
damage_done -= random2(1 + ac);
}
case -1: // unarmed
if (you.species == SP_TROLL || you.mutation[MUT_CLAWS])
{
if (damage < HIT_MED)
attack_verb = "claw";
else if (damage < HIT_STRONG)
attack_verb = "mangle";
else
attack_verb = "eviscerate";
mpr( info, MSGCH_DIAGNOSTICS );
#endif
void melee_attack::player_exercise_combat_skills()
{
const bool helpless = defender->cannot_fight();
if (!helpless || you.skills[ wpn_skill ] < 2)
exercise( wpn_skill, 1 );
if ((!helpless || you.skills[SK_FIGHTING] < 2)
&& one_chance_in(3))
{
exercise(SK_FIGHTING, 1);
}
}
// This doesn't actually modify damage -- bwr
damage_done = weapon_type_modify( weapon, damage_noise, damage_noise2,
damage_done );
// Returns true if the combat round should end here.
bool melee_attack::player_monattk_hit_effects(bool mondied)
{
if (mons_holiness(def) == MH_HOLY)
did_god_conduct(mondied? DID_KILL_ANGEL : DID_ATTACK_HOLY, 1);
if (hurt_monster(defender, damage_done))
if (weapon
&& weapon->base_type == OBJ_WEAPONS
&& is_demonic( *weapon ))
{
did_god_conduct(DID_UNHOLY, 1);
}
if (mondied && damage_brand == SPWPN_VAMPIRICISM)
{
if (mons_holiness(def) == MH_NATURAL
&& damage_done > 0 && you.hp < you.hp_max
&& !one_chance_in(5))
if (ur_armed && wpn_skill)
{
if (!helpless || you.skills[ wpn_skill ] < 2)
exercise( wpn_skill, 1 );
}
else
{
if (!helpless || you.skills[SK_UNARMED_COMBAT] < 2)
exercise(SK_UNARMED_COMBAT, 1);
}
if ((!helpless || you.skills[SK_FIGHTING] < 2)
&& one_chance_in(3))
{
exercise(SK_FIGHTING, 1);
}
mpr("You feel better.");
// more than if not killed
inc_hp(1 + random2(damage_done), false);
if (you.hunger_state != HS_ENGORGED)
lessen_hunger(30 + random2avg(59, 2), true);
did_god_conduct(DID_NECROMANCY, 2);
if (defender->hit_points < 1)
{
#if DEBUG_DIAGNOSTICS
/* note: doesn't take account of special weapons etc */
snprintf( info, INFO_SIZE, "Hit for %d.", damage_done );
mpr( info, MSGCH_DIAGNOSTICS );
#endif
if (ur_armed && melee_brand == SPWPN_VAMPIRICISM)
{
if (mons_holiness(defender) == MH_NATURAL
&& damage_done > 0 && you.hp < you.hp_max
&& !one_chance_in(5))
{
mpr("You feel better.");
}
if (defender->type == MONS_GIANT_SPORE)
mprf("You %s the giant spore.", damage_noise);
else if (defender->type == MONS_BALL_LIGHTNING)
mprf("You %s the ball lightning.", damage_noise);
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 (!special_damage_message.empty())
mprf("%s", special_damage_message.c_str());
if (damage_done < 1 && player_monster_visible( defender ))
{
hit = true;
base_nodamage = true;
snprintf( base_damage_message, INFO_SIZE, "You %s %s.",
damage_noise, ptr_monam(defender, DESC_NOCAP_THE));
}
if (def->hit_points < 1)
{
monster_die(def, KILL_YOU, 0);
return (true);
if ((your_to_hit + heavy_armour / 2) >= defender->evasion)
strcpy(info, "Your armour prevents you from hitting ");
else
strcpy(info, "You miss ");
void melee_attack::player_calc_brand_damage(
int res,
const char *message_format)
{
if (res == 0)
special_damage = random2(damage_done) / 2 + 1;
else if (res < 0)
special_damage = random2(damage_done) + 1;
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
strcat(info, ".");
mpr(info);
if (special_damage)
{
special_damage_message = make_stringf(
message_format,
defender->name(DESC_NOCAP_THE).c_str(),
special_attack_punctuation().c_str());
snprintf(info, INFO_SIZE, "You %s %s%s", damage_noise,
ptr_monam(defender, DESC_NOCAP_THE), damage_noise2);
#if DEBUG_DIAGNOSTICS
strcat( info, " for " );
/* note: doesn't take account of special weapons etc */
itoa( damage_done, st_prn, 10 );
strcat( info, st_prn );
#endif
if (damage_done < HIT_WEAK)
strcat(info, ".");
else if (damage_done < HIT_MED)
strcat(info, "!");
else if (damage_done < HIT_STRONG)
strcat(info, "!!");
else
strcat(info, "!!!");
case SPWPN_FLAMING:
res = mons_res_fire(def);
if (weapon && weapon->special == SPWPN_SWORD_OF_CEREBOV && res >= 0)
res = (res > 0) - 1;
torment(TORMENT_SPWLD, you.x_pos, you.y_pos);
did_god_conduct(DID_UNHOLY, 5);
case MH_UNDEAD:
special_damage = 1 + random2(damage_done);
break;
case MH_DEMONIC:
special_damage = 1 + (random2(damage_done * 15) / 10);
break;
default:
break;
/* remember, damage_done is still useful! */
if (hit)
{
if (defender->type == MONS_JELLY
|| defender->type == MONS_BROWN_OOZE
|| defender->type == MONS_ACID_BLOB
|| defender->type == MONS_ROYAL_JELLY)
case SPWPN_ORC_SLAYING:
if (mons_species(def->type) == MONS_ORC)
int specdam = 0;
if (ur_armed
&& you.inv[ weapon ].base_type == OBJ_WEAPONS
&& is_demonic( you.inv[ weapon ] ))
break;
case SPWPN_VENOM:
if (!one_chance_in(4))
// If decapitation, extra damage is bypassed.
if (actor_decapitates_hydra(NULL, defender, damage_done))
goto mons_dies;
special_damage_message =
make_stringf(
"You drain %s!",
defender->name(DESC_NOCAP_THE).c_str());
if (one_chance_in(5))
def->hit_dice--;
def->max_hit_points -= 2 + random2(3);
def->hit_points -= 2 + random2(3);
if (def->hit_points >= def->max_hit_points)
def->hit_points = def->max_hit_points;
if (def->hit_dice < 1)
def->hit_points = 0;
special_damage = 1 + (random2(damage_done) / 2);
did_god_conduct( DID_NECROMANCY, 2 );
break;
// jmf: BEGIN STAFF HACK
// How much bonus damage will a staff of <foo> do?
// FIXME: make these not macros. inline functions?
// actually, it will all be pulled out and replaced by functions -- {dlb}
//
// This is similar to the previous, in both value and distribution, except
// that instead of just SKILL, its now averaged with Evocations. -- bwr
#define STAFF_DAMAGE(SKILL) (roll_dice( 3, 1 + (you.skills[(SKILL)] + you.skills[SK_EVOCATIONS]) / 12 ))
#define STAFF_COST 2
/* 9 = speed - done before */
case SPWPN_VORPAL:
special_damage = 1 + random2(damage_done) / 2;
// note: leaving special_damage_message empty because there
// isn't one.
break;
// magic staves have their own special damage
if (ur_armed && item_is_staff( you.inv[weapon] ))
case SPWPN_VAMPIRICISM:
if (mons_holiness(def) != MH_NATURAL ||
mons_res_negative_energy(def) > 0 ||
damage_done < 1 || you.hp == you.hp_max ||
one_chance_in(5))
if (you.magic_points >= STAFF_COST
&& random2(20) <= you.skills[SK_EVOCATIONS])
{
switch (you.inv[weapon].sub_type)
{
case STAFF_AIR:
if (damage_done + you.skills[SK_AIR_MAGIC] > random2(30))
{
if (mons_res_elec(defender))
break;
// thus is probably more valuable on larger weapons?
if (weapon
&& is_fixed_artefact( *weapon )
&& weapon->special == SPWPN_VAMPIRES_TOOTH)
{
inc_hp(damage_done, false);
}
else
{
inc_hp(1 + random2(damage_done), false);
}
if (you.hunger_state != HS_ENGORGED)
lessen_hunger(random2avg(59, 2), true);
did_god_conduct( DID_NECROMANCY, 2 );
break;
specdam = STAFF_DAMAGE(SK_AIR_MAGIC);
case SPWPN_DISRUPTION:
if (mons_holiness(def) == MH_UNDEAD && !one_chance_in(3))
{
special_damage_message =
str_simple_monster_message(def, " shudders.");
special_damage += random2avg((1 + (damage_done * 3)), 3);
}
break;
case SPWPN_PAIN:
if (mons_res_negative_energy(def) <= 0
&& random2(8) <= you.skills[SK_NECROMANCY])
{
special_damage_message =
str_simple_monster_message(def, " convulses in agony.");
special_damage += random2( 1 + you.skills[SK_NECROMANCY] );
}
did_god_conduct(DID_NECROMANCY, 4);
break;
if (specdam)
{
snprintf( special_damage_message, INFO_SIZE,
"%s is jolted!",
ptr_monam(defender, DESC_CAP_THE) );
}
}
break;
case SPWPN_DISTORTION:
//jmf: blink frogs *like* distortion
// I think could be amended to let blink frogs "grow" like
// jellies do {dlb}
if (defender->id() == MONS_BLINK_FROG)
{
if (one_chance_in(5))
{
emit_nodmg_hit_message();
simple_monster_message(
def,
" basks in the translocular energy." );
heal_monster(def, 1 + random2avg(7, 2), true); // heh heh
}
break;
}
if (one_chance_in(3))
{
special_damage_message =
make_stringf(
"Space bends around %s.",
defender->name(DESC_NOCAP_THE).c_str());
special_damage += 1 + random2avg(7, 2);
break;
}
case STAFF_COLD: // FIXME: I don't think I used these right ...
if (mons_res_cold(defender) > 0)
break;
if (one_chance_in(3))
{
special_damage_message =
make_stringf(
"Space warps horribly around %s!",
ptr_monam(def, DESC_NOCAP_THE));
special_damage += 3 + random2avg(24, 2);
break;
}
if (specdam)
{
snprintf( special_damage_message, INFO_SIZE,
"You freeze %s!",
ptr_monam(defender, DESC_NOCAP_THE) );
}
break;
if (coinflip())
{
emit_nodmg_hit_message();
monster_die(def, KILL_RESET, 0);
return (true);
}
break;
case STAFF_EARTH:
if (mons_flies(defender))
break; //jmf: lame, but someone ought to resist
case SPWPN_CONFUSE:
{
emit_nodmg_hit_message();
// declaring these just to pass to the enchant function
bolt beam_temp;
beam_temp.flavour = BEAM_CONFUSION;
mons_ench_f2( def, beam_temp );
you.confusing_touch -= random2(20);
if (you.confusing_touch < 1)
you.confusing_touch = 1;
break;
}
}
if (specdam)
{
snprintf( special_damage_message, INFO_SIZE,
"You crush %s!",
ptr_monam(defender, DESC_NOCAP_THE) );
}
break;
void melee_attack::player_sustain_passive_damage()
{
if (mons_class_flag(defender->id(), M_ACID_SPLASH))
weapon_acid(5);
}
if (specdam)
{
snprintf( special_damage_message, INFO_SIZE,
"You burn %s!",
ptr_monam(defender, DESC_NOCAP_THE) );
}
break;
const int staff_cost = 2;
if (you.magic_points < staff_cost
|| 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;
if (random2(100) < temp_rand)
{
// HACK: poison_monster emits a message, so
// we need to get ours out first.
if ( base_nodamage )
{
mpr(base_damage_message);
base_nodamage = false; // don't double-message
}
poison_monster(defender, true);
}
break;
case STAFF_DEATH:
if (mons_res_negative_energy(defender) > 0)
break;
if (random2(8) <= you.skills[SK_NECROMANCY])
{
specdam = STAFF_DAMAGE(SK_NECROMANCY);
if (specdam)
{
snprintf( special_damage_message, INFO_SIZE,
"%s convulses in agony!",
ptr_monam(defender, DESC_CAP_THE));
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;
case STAFF_COLD:
if (mons_res_cold(def) > 0)
break;
if (!item_type_known(you.inv[weapon]))
{
set_ident_flags( you.inv[weapon], ISFLAG_KNOW_TYPE );
strcpy(info, "You are wielding ");
in_name( weapon, DESC_NOCAP_A, str_pass);
strcat(info, str_pass);
strcat(info, ".");
mpr(info);
more();
you.wield_change = true;
}
}
#undef STAFF_DAMAGE
#undef STAFF_COST
// END STAFF HACK
}
else
if (special_damage)
// handle special brand damage (unarmed or armed non-staff ego):
int res = 0;
switch (melee_brand)
{
case SPWPN_NORMAL:
break;
case SPWPN_FLAMING:
specdam = 0;
res = mons_res_fire(defender);
if (ur_armed && you.inv[weapon].special == SPWPN_SWORD_OF_CEREBOV)
{
if (res < 3 && res > 0)
res = 0;
else if (res == 0)
res = -1;
}
if (res == 0)
specdam = random2(damage_done) / 2 + 1;
else if (res < 0)
specdam = random2(damage_done) + 1;
if (specdam)
{
snprintf(special_damage_message, INFO_SIZE,
"You burn %s",
ptr_monam(defender, DESC_NOCAP_THE));
if (specdam < 3)
strcat(special_damage_message, ".");
else if (specdam < 7)
strcat(special_damage_message, "!");
else
strcat(special_damage_message, "!!");
}
break;
case SPWPN_FREEZING:
specdam = 0;
special_damage_message =
make_stringf(
"You freeze %s!",
defender->name(DESC_NOCAP_THE).c_str());
}
break;
if (specdam)
{
snprintf(special_damage_message, INFO_SIZE,
"You freeze %s",
ptr_monam(defender, DESC_NOCAP_THE));
if (special_damage)
{
special_damage_message =
make_stringf(
"You crush %s!",
defender->name(DESC_NOCAP_THE).c_str());
}
break;
if (specdam < 3)
strcat(special_damage_message, ".");
else if (specdam < 7)
strcat(special_damage_message, "!");
else
strcat(special_damage_message, "!!");
case STAFF_FIRE:
if (mons_res_fire(def) > 0)
break;
case SPWPN_HOLY_WRATH:
// there should be a case in here for holy monsters,
// see elsewhere in fight.cc {dlb}
specdam = 0;
switch (mons_holiness(defender))
{
case MH_UNDEAD:
specdam = 1 + random2(damage_done);
break;
if (mons_res_fire(def) < 0)
special_damage += player_staff_damage(SK_FIRE_MAGIC);
if (mons_flies(defender))
break;
else if (mons_res_elec(defender) > 0)
break;
else if (one_chance_in(3))
{
strcpy(special_damage_message,
"There is a sudden explosion of sparks!");
specdam = random2avg(28, 3);
}
break;
case STAFF_DEATH:
if (mons_res_negative_energy(def) > 0)
break;
case SPWPN_ORC_SLAYING:
if (mons_species(defender->type) == MONS_ORC)
hurt_monster(defender, 1 + random2(damage_done));
break;
if (random2(8) <= you.skills[SK_NECROMANCY])
{
special_damage = player_staff_damage(SK_NECROMANCY);
case SPWPN_VENOM:
if (!one_chance_in(4))
{
// HACK - poison_monster bites us again
if ( base_nodamage )
{
mpr(base_damage_message);
base_nodamage = false;
}
poison_monster(defender, true);
}
break;
if (special_damage)
{
special_damage_message =
make_stringf(
"%s convulses in agony!",
defender->name(DESC_CAP_THE).c_str());
snprintf(special_damage_message, INFO_SIZE, "You drain %s!",
ptr_monam(defender, DESC_NOCAP_THE));
case STAFF_POWER:
case STAFF_SUMMONING:
case STAFF_CHANNELING:
case STAFF_CONJURATION:
case STAFF_ENCHANTMENT:
case STAFF_ENERGY:
case STAFF_WIZARDRY:
break;
specdam = 1 + (random2(damage_done) / 2);
did_god_conduct( DID_NECROMANCY, 2 );
break;
bool melee_attack::player_check_monster_died()
{
if (def->hit_points < 1)
{
#if DEBUG_DIAGNOSTICS
/* note: doesn't take account of special weapons etc */
mprf( MSGCH_DIAGNOSTICS, "Hit for %d.", damage_done );
#endif
case SPWPN_VORPAL:
specdam = 1 + random2(damage_done) / 2;
// note: leaving special_damage_message empty because there
// isn't one.
break;
if (def->type == MONS_GIANT_SPORE || def->type == MONS_BALL_LIGHTNING)
mprf("You %s %s.", attack_verb.c_str(),
ptr_monam(def, DESC_NOCAP_THE));
if (mons_holiness(defender) != MH_NATURAL ||
mons_res_negative_energy(defender) > 0 ||
damage_done < 1 || you.hp == you.hp_max ||
one_chance_in(5))
break;
return (true);
}
// thus is probably more valuable on larger weapons?
if (ur_armed
&& is_fixed_artefact( you.inv[weapon] )
&& you.inv[weapon].special == SPWPN_VAMPIRES_TOOTH)
{
inc_hp(damage_done, false);
}
else
inc_hp(1 + random2(damage_done), false);
void melee_attack::player_calc_hit_damage()
{
potential_damage = player_stat_modify_damage(potential_damage);
if (you.hunger_state != HS_ENGORGED)
lessen_hunger(random2avg(59, 2), true);
if (water_attack)
potential_damage = player_apply_water_attack_bonus(potential_damage);
// apply damage bonus from ring of slaying
// (before randomization -- some of these rings
// are stupidly powerful) -- GDL
potential_damage += slaying_bonus(PWPN_DAMAGE);
damage_done = potential_damage > 0? 1 + 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);
case SPWPN_DISRUPTION:
specdam = 0;
if (mons_holiness(defender) == MH_UNDEAD && !one_chance_in(3))
{
// see previous HACKs
if ( base_nodamage )
{
mpr(base_damage_message);
base_nodamage = false;
}
simple_monster_message(defender, " shudders.");
specdam += random2avg((1 + (damage_done * 3)), 3);
}
break;
damage_done = player_stab(damage_done);
damage_done = player_apply_monster_ac(damage_done);
case SPWPN_PAIN:
specdam = 0;
if (mons_res_negative_energy(defender) <= 0
&& random2(8) <= you.skills[SK_NECROMANCY])
{
// see previous HACKs
if ( base_nodamage )
{
mpr(base_damage_message);
base_nodamage = false;
}
simple_monster_message(defender, " convulses in agony.");
specdam += random2( 1 + you.skills[SK_NECROMANCY] );
}
did_god_conduct(DID_NECROMANCY, 4);
break;
// This doesn't actually modify damage -- bwr
damage_done = player_weapon_type_modify( damage_done );
case SPWPN_DISTORTION:
//jmf: blink frogs *like* distortion
// I think could be amended to let blink frogs "grow" like
// jellies do {dlb}
if (defender->type == MONS_BLINK_FROG)
{
if (one_chance_in(5))
{
if ( base_nodamage )
{
mpr(base_damage_message);
base_nodamage = false;
}
simple_monster_message( defender,
" basks in the translocular energy." );
heal_monster(defender, 1 + random2avg(7, 2), true); // heh heh
}
break;
}
if (damage_done < 0)
damage_done = 0;
}
if (one_chance_in(3))
{
snprintf(special_damage_message, INFO_SIZE,
"Space bends around %s.",
ptr_monam(defender, DESC_NOCAP_THE));
specdam += 1 + random2avg(7, 2);
break;
}
int melee_attack::calc_to_hit(bool random)
{
return (attacker->atype() == ACT_PLAYER? player_to_hit(random)
: mons_to_hit());
}
if (one_chance_in(3))
{
snprintf( special_damage_message, INFO_SIZE,
"Space warps horribly around %s!",
ptr_monam(defender, DESC_NOCAP_THE));
specdam += 3 + random2avg(24, 2);
break;
}
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);
if (coinflip())
{
if ( base_nodamage )
{
mpr(base_damage_message);
base_nodamage = false;
}
monster_teleport(defender, coinflip());
break;
}
int your_to_hit = 15 + (calc_stat_to_hit_base() / 2);
if (coinflip())
{
if ( base_nodamage )
mpr(base_damage_message);
monster_die(defender, KILL_RESET, 0);
return (true);
}
break;
if (water_attack)
your_to_hit += 5;
// declaring these just to pass to the enchant function
struct bolt beam_temp;
beam_temp.flavour = BEAM_CONFUSION;
// if you can't see yourself, you're a little less acurate.
if (you.invis && !player_see_invis())
your_to_hit -= 5;
you.confusing_touch -= random2(20);
if (you.confusing_touch < 1)
you.confusing_touch = 1;
}
break;
} /* end switch */
}
/* remember, the hydra function sometimes skips straight to mons_dies */
#if DEBUG_DIAGNOSTICS
snprintf( info, INFO_SIZE, "brand: %d; melee special damage: %d",
melee_brand, specdam );
mpr( info, MSGCH_DIAGNOSTICS );
#endif
if ( base_nodamage )
// weapon skill contribution
if (weapon)
{
if (wpn_skill != SK_FIGHTING)
hurt_monster( defender, specdam );
} // end if (hit)
mons_dies:
if (defender->hit_points < 1)
{
monster_die(defender, KILL_YOU, 0);
return (hit);
your_to_hit += maybe_random2(1 + you.skills[SK_UNARMED_COMBAT],
random_factor);
if ((you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| player_genus(GENPC_DRACONIAN)
|| (you.species == SP_MERFOLK && player_is_swimming())
|| you.mutation[ MUT_STINGER ])
&& one_chance_in(3))
if (get_equip_race(*weapon) == ISFLAG_ELVEN
&& player_genus(GENPC_ELVEN))
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SERPENT_OF_HELL
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER)
{
continue;
}
// hunger penalty
if (you.hunger_state == HS_STARVING)
your_to_hit -= 3;
case 1:
if (unarmed_attack != UNAT_HEADBUTT)
{
if ((!you.mutation[MUT_HORNS] && you.species != SP_KENKU)
|| !one_chance_in(3))
{
continue;
}
}
#if DEBUG_DIAGNOSTICS
int roll_hit = your_to_hit;
#endif
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SERPENT_OF_HELL
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON)
{
continue;
}
// hit roll
your_to_hit = maybe_random2(your_to_hit, random_factor);
if (you.equip[EQ_HELMET] != -1
&& (get_helmet_type(you.inv[you.equip[EQ_HELMET]]) == THELM_HELMET
|| get_helmet_type(you.inv[you.equip[EQ_HELMET]]) == THELM_HELM))
{
sc_dam += 2;
if (weapon && wpn_skill == SK_SHORT_BLADES && you.sure_blade)
your_to_hit += 5 +
(random_factor ? random2limit( you.sure_blade, 10 ) :
you.sure_blade / 2);
if (get_helmet_desc(you.inv[you.equip[EQ_HELMET]]) == THELM_DESC_SPIKED
|| get_helmet_desc(you.inv[you.equip[EQ_HELMET]]) == THELM_DESC_HORNED)
{
sc_dam += 3;
}
}
break;
// other stuff
if (!weapon)
{
if ( you.confusing_touch )
// just trying to touch is easier that trying to damage
your_to_hit += maybe_random2(you.dex, random_factor);
case 2: /* draconians */
if (unarmed_attack != UNAT_TAILSLAP)
{
// not draconian and not wet merfolk
if ((!player_genus(GENPC_DRACONIAN)
&& (!(you.species == SP_MERFOLK && player_is_swimming()))
&& !you.mutation[ MUT_STINGER ])
|| (!one_chance_in(4)))
switch ( you.attribute[ATTR_TRANSFORMATION] )
{
case TRAN_SPIDER:
your_to_hit += maybe_random2(10, 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_SERPENT_OF_HELL:
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_AIR:
your_to_hit = 0;
break;
case TRAN_NONE:
default:
break;
}
}
if (you.mutation[ MUT_STINGER ] > 0)
{
sc_dam += (you.mutation[ MUT_STINGER ] * 2 - 1);
brand = SPWPN_VENOM;
}
// not paying attention (but not batty)
if (def->foe != MHITYOU && !testbits(def->flags, MF_BATTY))
{
stab_attempt = true;
stab_bonus = 3;
}
/* grey dracs have spiny tails, or something */
// maybe add this to player messaging {dlb}
//
// STINGER mutation doesn't give extra damage here... that
// would probably be a bit much, we'll still get the
// poison bonus so it's still somewhat good.
if (you.species == SP_GREY_DRACONIAN && you.experience_level >= 7)
{
sc_dam = 12;
}
break;
// confused (but not perma-confused)
if (mons_has_ench(def, ENCH_CONFUSION)
&& !mons_class_flag(def->type, M_CONFUSED))
{
stab_attempt = true;
stab_bonus = 2;
}
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SERPENT_OF_HELL
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON)
{
continue;
}
// sleeping
if (def->behaviour == BEH_SLEEP)
{
stab_attempt = true;
roll_needed = false;
stab_bonus = 1;
}
/* no punching with a shield or 2-handed wpn, except staves */
if (bearing_shield || coinflip()
|| (ur_armed && hands == HANDS_TWO
&& you.inv[weapon].base_type != OBJ_STAVES
&& you.inv[weapon].sub_type != WPN_QUARTERSTAFF) )
{
continue;
}
// helpless (plants, etc)
if (defender->cannot_fight())
stab_attempt = false;
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
{
strcpy(attack_name, "slash");
sc_dam += 6;
}
break;
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);
damage = sc_dam; //4 + you.experience_level / 3;
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;
alert_nearby_monsters();
// Short blades can get up to at least unarmed speed.
if (wpn_skill == SK_SHORT_BLADES && min_delay > 5)
min_delay = 5;
// Using both hands can get a weapon up to speed 7
if ((hands == HANDS_TWO || hand_half_bonus)
&& min_delay > 7)
{
min_delay = 7;
}
// never go faster than speed 3 (ie 3 attacks per round)
if (min_delay < 3)
min_delay = 3;
// Hand and a half bonus only helps speed up to a point, any more
// than speed 10 must come from skill and the weapon
if (hand_half_bonus && attack_delay > 10)
attack_delay--;
// apply minimum to weapon skill modification
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);
}
if (your_to_hit >= defender->evasion || one_chance_in(30))
{
hit = true;
int dammod = 10;
int melee_attack::player_unarmed_speed()
{
int unarmed_delay = 10;
const int dam_stat_val = calc_stat_to_dam_base();
min_delay = 5;
// Unarmed speed
if (you.burden_state == BS_UNENCUMBERED
&& one_chance_in(heavy_armour_penalty + 1))
{
unarmed_delay = 10 - you.skills[SK_UNARMED_COMBAT] / 5;
/* this shouldn't happen anyway...sanity */
if (unarmed_delay < min_delay)
unarmed_delay = min_delay;
}
damage *= dammod;
damage /= 10;
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++;
// [dshaligram] Fall-through
case ARM_SHIELD:
if (you.skills[SK_SHIELDS] <= 3 + random2(17))
attack_delay++;
break;
}
}
damage_done *= 25 + (random2(you.skills[SK_UNARMED_COMBAT]+1));
damage_done /= 25;
if (you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE)
{
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_SPIDER:
damage = 5;
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_SERPENT_OF_HELL:
case TRAN_DRAGON:
damage = 20 + you.strength;
break;
case TRAN_LICH:
damage = 5;
break;
case TRAN_AIR:
damage = 0;
break;
}
}
else if (you.equip[ EQ_GLOVES ] == -1)
{
// claw damage only applies for bare hands
if (you.species == SP_TROLL)
damage += 5;
else if (you.species == SP_GHOUL)
damage += 2;
if (damage_done < 1)
damage_done = 0;
else
hurt_monster(defender, damage_done);
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 (brand == SPWPN_VENOM && coinflip())
poison_monster( defender, true );
static void tutorial_weapon_check(const item_def *weapon)
{
if (weapon &&
(weapon->base_type != OBJ_WEAPONS
|| is_range_weapon(*weapon)))
{
learned_something_new(TUT_WIELD_WEAPON);
}
}
if (mons_holiness(defender) == MH_HOLY)
did_god_conduct(DID_KILL_ANGEL, 1);
// Returns true if you hit the monster.
bool you_attack(int monster_attacked, bool unarmed_attacks)
{
monsters *defender = &menv[monster_attacked];
melee_attack attk(&you, defender, unarmed_attacks);
hit = true;
}
else // no damage was done
{
strcpy(info, "You ");
strcat(info, attack_name);
strcat(info, " ");
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
// We're trying to hit a monster, break out of travel/explore now.
interrupt_activity(AI_HIT_MONSTER, defender);
mpr(info);
hit = true;
}
if (defender->hit_points < 1)
{
monster_die(defender, KILL_YOU, 0);
if (defender->type == MONS_GIANT_SPORE)
{
strcpy(info, "You ");
strcat(info, attack_name);
strcat(info, "the giant spore.");
mpr(info);
}
else if (defender->type == MONS_BALL_LIGHTNING)
{
strcpy(info, "You ");
strcat(info, attack_name);
strcat(info, "the ball lightning.");
mpr(info);
}
return (true);
}
}
else
{
strcpy(info, "Your ");
strcat(info, attack_name);
strcat(info, " misses ");
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
strcat(info, ".");
mpr(info);
}
}
}
if (hit)
print_wounds(defender);
return attk.attack();
}
// Added by DML 6/10/99.
// For now, always returns damage: that is, it never modifies values,
// just adds 'color'.
static int weapon_type_modify( int weapnum, char noise[80], char noise2[80],
int damage )
{
int weap_type = WPN_UNKNOWN;
if (weapnum == -1)
weap_type = WPN_UNARMED;
else if (item_is_staff( you.inv[weapnum] ))
weap_type = WPN_QUARTERSTAFF;
else if (you.inv[weapnum].base_type == OBJ_WEAPONS)
weap_type = you.inv[weapnum].sub_type;
noise2[0] = 0;
// All weak hits look the same, except for when the player
// has a non-weapon in hand. -- bwr
if (damage < HIT_WEAK)
{
if (weap_type != WPN_UNKNOWN)
strcpy( noise, "hit" );
else
strcpy( noise, "clumsily bash" );
return (damage);
}
// take transformations into account, if no weapon is wielded
if (weap_type == WPN_UNARMED
&& you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE)
{
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_SPIDER:
if (damage < HIT_STRONG)
strcpy( noise, "bite" );
else
strcpy( noise, "maul" );
break;
case TRAN_BLADE_HANDS:
if (damage < HIT_MED)
strcpy( noise, "slash" );
else if (damage < HIT_STRONG)
strcpy( noise, "slice" );
else
strcpy( noise, "shred" );
break;
case TRAN_ICE_BEAST:
case TRAN_STATUE:
case TRAN_LICH:
if (damage < HIT_MED)
strcpy( noise, "punch" );
else
strcpy( noise, "pummel" );
break;
case TRAN_DRAGON:
case TRAN_SERPENT_OF_HELL:
if (damage < HIT_MED)
strcpy( noise, "claw" );
else if (damage < HIT_STRONG)
strcpy( noise, "bite" );
else
strcpy( noise, "maul" );
break;
case TRAN_AIR:
strcpy( noise, "buffet" );
break;
} // transformations
return (damage);
}
switch (weap_type)
{
case WPN_KNIFE:
case WPN_DAGGER:
case WPN_SHORT_SWORD:
case WPN_TRIDENT:
case WPN_DEMON_TRIDENT:
case WPN_SPEAR:
if (damage < HIT_MED)
strcpy( noise, "puncture" );
else if (damage < HIT_STRONG)
strcpy( noise, "impale" );
else
{
strcpy( noise, "spit" );
strcpy( noise2, " like a pig" );
}
return (damage);
case WPN_BOW:
case WPN_LONGBOW:
case WPN_CROSSBOW:
case WPN_HAND_CROSSBOW:
if (damage < HIT_STRONG)
strcpy( noise, "puncture" );
else
strcpy( noise, "skewer" );
return (damage);
case WPN_LONG_SWORD:
case WPN_GREAT_SWORD:
case WPN_SCIMITAR:
case WPN_FALCHION:
case WPN_HALBERD:
case WPN_GLAIVE:
case WPN_HAND_AXE:
case WPN_WAR_AXE:
case WPN_BROAD_AXE:
case WPN_BATTLEAXE:
case WPN_SCYTHE:
case WPN_QUICK_BLADE:
case WPN_KATANA:
case WPN_LAJATANG:
case WPN_LOCHABER_AXE:
case WPN_EXECUTIONERS_AXE:
case WPN_DOUBLE_SWORD:
case WPN_TRIPLE_SWORD:
case WPN_SABRE:
case WPN_DEMON_BLADE:
case WPN_BLESSED_BLADE:
if (damage < HIT_MED)
strcpy( noise, "slash" );
else if (damage < HIT_STRONG)
strcpy( noise, "slice" );
else
{
strcpy( noise, "open" );
strcpy( noise2, " like a pillowcase" );
}
return (damage);
case WPN_SLING:
case WPN_CLUB:
case WPN_MACE:
case WPN_FLAIL:
case WPN_GREAT_MACE:
case WPN_DIRE_FLAIL:
case WPN_QUARTERSTAFF:
case WPN_GIANT_CLUB:
case WPN_HAMMER:
case WPN_ANCUS:
case WPN_MORNINGSTAR: /*for now, just a bludgeoning weapon */
case WPN_SPIKED_FLAIL: /*for now, just a bludgeoning weapon */
case WPN_EVENINGSTAR:
case WPN_GIANT_SPIKED_CLUB:
if (damage < HIT_MED)
strcpy( noise, "sock" );
else if (damage < HIT_STRONG)
strcpy( noise, "bludgeon" );
else
{
strcpy( noise, "crush" );
strcpy( noise2, " like a grape" );
}
return (damage);
case WPN_WHIP:
case WPN_DEMON_WHIP:
if (damage < HIT_MED)
strcpy( noise, "whack" );
else
strcpy( noise, "thrash" );
return (damage);
case WPN_UNARMED:
if (you.species == SP_TROLL || you.mutation[MUT_CLAWS])
{
if (damage < HIT_MED)
strcpy( noise, "claw" );
else if (damage < HIT_STRONG)
strcpy( noise, "mangle" );
else
strcpy( noise, "eviscerate" );
}
else
{
if (damage < HIT_MED)
strcpy( noise, "punch" );
else
strcpy( noise, "pummel" );
}
return (damage);
case WPN_UNKNOWN:
default:
strcpy( noise, "hit" );
return (damage);
}
} // end weapon_type_modify()
case 3: // big melee, monster surrounded/not paying attention
if (r<3)
{
snprintf( info, INFO_SIZE, "You strike %s from a blind spot!",
ptr_monam(defender, DESC_NOCAP_THE) );
}
else
{
snprintf( info, INFO_SIZE, "You catch %s momentarily off-guard.",
ptr_monam(defender, DESC_NOCAP_THE) );
}
break;
case 2: // confused/fleeing
if (r<4)
{
snprintf( info, INFO_SIZE, "You catch %s completely off-guard!",
ptr_monam(defender, DESC_NOCAP_THE) );
}
else
{
snprintf( info, INFO_SIZE, "You strike %s from behind!",
ptr_monam(defender, DESC_NOCAP_THE) );
}
break;
case 1:
snprintf( info, INFO_SIZE, "%s fails to defend %s.",
ptr_monam(defender, DESC_CAP_THE),
mons_pronoun( defender->type, PRONOUN_REFLEXIVE ) );
break;
case 3: // big melee, monster surrounded/not paying attention
if (r<3)
{
mprf( "You strike %s from a blind spot!",
ptr_monam(defender, DESC_NOCAP_THE) );
}
else
{
mprf( "You catch %s momentarily off-guard.",
ptr_monam(defender, DESC_NOCAP_THE) );
}
break;
case 2: // confused/fleeing
if (r<4)
{
mprf( "You catch %s completely off-guard!",
ptr_monam(defender, DESC_NOCAP_THE) );
}
else
{
mprf( "You strike %s from behind!",
ptr_monam(defender, DESC_NOCAP_THE) );
}
break;
case 1:
mprf( "%s fails to defend %s.",
ptr_monam(defender, DESC_CAP_THE),
mons_pronoun( defender->type, PRONOUN_REFLEXIVE ) );
break;
{
}
};
class item_def;
class melee_attack;
class coord_def;
class actor
{
public:
virtual ~actor() { }
virtual int id() const = 0;
virtual actor_type atype() const = 0;
virtual coord_def pos() const = 0;
virtual bool swimming() const = 0;
virtual bool floundering() const = 0;
virtual size_type body_size(int psize = PSIZE_TORSO,
bool base = false) const = 0;
virtual int damage_type(int which_attack = -1) = 0;
virtual int damage_brand(int which_attack = -1) = 0;
virtual item_def *weapon(int which_attack = -1) = 0;
virtual item_def *shield() = 0;
virtual void make_hungry(int nutrition, bool silent)
virtual std::string name(description_level_type type) const = 0;
virtual std::string conj_verb(const std::string &verb) const = 0;
virtual bool fumbles_attack(bool verbose = true) = 0;
// Returns true if the actor has no way to attack (plants, statues).
// (statues have only indirect attacks).
virtual bool cannot_fight() const = 0;
virtual void attacking(actor *other) = 0;
virtual void go_berserk(bool intentional) = 0;
// For sorting
bool operator < (const player &p) const;
bool has_spell(int spell) const;
size_type transform_size(int psize = PSIZE_TORSO) const;
item_def *slot_item(equipment_type eq);
// actor
int id() const;
coord_def pos() const;
bool swimming() const;
bool floundering() const;
size_type body_size(int psize = PSIZE_TORSO, bool base = false) const;
int damage_type(int attk = -1);
int damage_brand(int attk = -1);
item_def *weapon(int which_attack = -1);
item_def *shield();
std::string name(description_level_type type) const;
std::string conj_verb(const std::string &verb) const;
bool fumbles_attack(bool verbose = true);
bool cannot_fight() const;
void attacking(actor *other);
void go_berserk(bool intentional);
void make_hungry(int nutrition, bool silent);
actor_type atype() const { return ACT_PLAYER; }
coord_def pos() const
{
return coord_def(x, y);
}
public:
// actor interface
int id() const;
coord_def pos() const;
bool swimming() const;
bool floundering() const;
size_type body_size(int psize = PSIZE_TORSO, bool base = false) const;
int damage_type(int attk = -1);
int damage_brand(int attk = -1);
item_def *weapon(int which_attack = -1);
item_def *shield();
std::string name(description_level_type type) const;
std::string conj_verb(const std::string &verb) const;
bool fumbles_attack(bool verbose = true);
bool cannot_fight() const;
void attacking(actor *other);
void go_berserk(bool intentional);
actor_type atype() const { return ACT_MONSTER; }