Fixed tracer explosion beams generating clouds.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@959 c06c8d41-db1a-0410-9941-cceddc491573
QDTVLBRGHDTRUVT7I3O72K6TMOYAUSAJBZUHGOEFU2RKJNUPWZSQC
RIMJO42HB75SAZH44KZ2UH2QULOPIJLMEN4CR2CYNR2EKEKLFHSQC
TPSCOEB3X6LNZ7F5UBMHDZ4IUE4H3RLX6IGNW65VSJBDMNNZ3FMAC
GBUB77EAYHOFY6GQ5IY3ZSBC7FSQFZZKYNBD5QMCQFIKFLYLWHOQC
IJ7CEBTMSNGTBLUYTRT57MAUKCN5VALWIJ4T34ULGP3JYFWALPMQC
NVZRXXE5HL76ZOGL6H4LXJJMTWCULFGUJMOZKE7UQBAVDKJRMF3QC
QDWDUURSNLMT6AXNNJ3DEQCWAKCAIHV6MP5F7QGIBGXOG2BI2NPQC
R22TTMI6WXWULC7ODKFF3QCB7MOTETQQ6IR4BUCUPOCQKQNCTT5AC
77H4BWWPPGLM3PLZH4QTAJRXIZTSDVNCOKZE223I437FN2UJ34RQC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
33ZMPQC6OXTESW7SRW765GRNJUEJRSYONRVZVIEUDAUEJ2PPMB4AC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
NFOXLH722RGWYY5D63VV6SF2XEJBEOQEFQME6FSA4HZRK3CPLSRQC
RI5TNMRQUYPOFGT3LBGNSIYVU2IAQ2F6BT2AMUMM3EFJXOZRWPAQC
ABPRQBBNMOXUJJNZXAJBIIUULMCB23MKAM5T3M5YFXDORJ3IY3JAC
ITDUEUO7XAZANPC4GRB3SEDFBOV7GLFPNPTYE5LYNC3CS6BSVZTQC
JW2KRJHES33W7UTWZ6NDO4TLMK4EFU4HKZXBWR2UJOMPCCOTR4CQC
ZMYT53SCUSEG26QOTSRBQ3TWMSQTGBXZNEDPLPHM5U3VHALVOOIQC
3WRAJZ5ZLOSIZHFBUH5552QC4F3GAK7AXF6VIQFVN6VY7PUO6HPQC
return (desc == DESC_CAP_A || desc == DESC_CAP_THE? "You" : "you");
switch (desc)
{
case DESC_CAP_A: case DESC_CAP_THE:
return "You";
case DESC_NOCAP_A: case DESC_NOCAP_THE:
default:
return "you";
case DESC_CAP_YOUR:
return "Your";
case DESC_NOCAP_YOUR:
return "your";
}
{
if (hunger_increase > 0)
::make_hungry(hunger_increase, silent);
else if (hunger_increase < 0)
::lessen_hunger(-hunger_increase, silent);
}
int player::holy_aura() const
{
return (duration[DUR_REPEL_UNDEAD]? piety : 0);
}
int player::warding() const
{
if (wearing_amulet(AMU_WARDING))
return (30);
if (religion == GOD_VEHUMET && duration[DUR_PRAYER]
&& !player_under_penance() && piety >= piety_breakpoint(2))
{
// Clamp piety at 160 and scale that down to a max of 30.
const int wardpiety = piety > 160? 160 : piety;
return (wardpiety * 3 / 16);
}
return (0);
}
bool player::paralysed() const
{
return (paralysis);
}
bool player::confused() const
::make_hungry(hunger_increase, silent);
return (conf);
}
int player::shield_block_penalty() const
{
return (5 * shield_blocks * shield_blocks);
}
int player::shield_bonus() const
{
return random2(player_shield_class())
+ (random2(dex) / 4)
+ (random2(skill_bump(SK_SHIELDS)) / 4)
- 1;
}
int player::shield_bypass_ability(int tohit) const
{
return (10 + tohit * 2);
}
void player::shield_block_succeeded()
{
shield_blocks++;
if (one_chance_in(4))
exercise(SK_SHIELDS, 1);
}
bool player::wearing_light_armour(bool with_skill) const
{
return (player_light_armour(with_skill));
}
void player::exercise(skill_type sk, int qty)
{
::exercise(sk, qty);
}
int player::skill(skill_type sk, bool bump) const
{
return (bump? skill_bump(sk) : skills[sk]);
}
int player::armour_class() const
{
return (player_AC());
}
int player::melee_evasion(const actor *act) const
{
return (random2limit(player_evasion(), 40)
+ random2(you.dex) / 3
- (act->visible()? 2 : 14)
- (you_are_delayed()? 5 : 0));
}
void player::heal(int amount, bool max_too)
{
::inc_hp(amount, max_too);
}
int player::holiness() const
{
if (is_undead)
return (MH_UNDEAD);
if (species == SP_DEMONSPAWN)
return (MH_DEMONIC);
return (MH_NATURAL);
}
int player::res_fire() const
{
return (player_res_fire());
}
int player::res_cold() const
{
return (player_res_cold());
}
int player::res_elec() const
{
return (player_res_electricity());
}
int player::res_poison() const
{
return (player_res_poison());
}
int player::res_negative_energy() const
{
return (player_prot_life());
}
bool player::levitates() const
{
return (player_is_levitating());
}
int player::mons_species() const
{
switch (species)
{
case SP_HILL_ORC:
return (MONS_ORC);
case SP_ELF: case SP_HIGH_ELF: case SP_GREY_ELF:
case SP_DEEP_ELF: case SP_SLUDGE_ELF:
return (MONS_ELF);
default:
return (MONS_HUMAN);
}
}
void player::poison(actor*, int amount)
{
::poison_player(amount);
}
void player::expose_to_element(beam_type element, int st)
{
::expose_player_to_element(element, st);
}
void player::blink()
{
random_blink(true);
}
void player::teleport(bool now, bool abyss_shift)
{
if (now)
you_teleport2(true, abyss_shift);
else
you_teleport();
}
void player::hurt(actor *agent, int amount)
{
if (agent->atype() == ACT_MONSTER)
ouch(amount, monster_index( dynamic_cast<monsters*>(agent) ),
KILLED_BY_MONSTER);
else
{
// Should never happen!
ASSERT(false);
ouch(amount, 0, KILLED_BY_SOMETHING);
}
if (religion == GOD_XOM && hp <= hp_max / 3
&& one_chance_in(10))
{
Xom_acts(true, experience_level, false);
}
}
void player::drain_stat(int stat, int amount)
{
lose_stat(stat, amount);
}
void player::rot(actor *who, int rotlevel, int immed_rot)
{
if (is_undead)
return;
if (rotlevel)
rot_player( rotlevel );
if (immed_rot)
rot_hp(immed_rot);
if (rotlevel && one_chance_in(4))
disease_player( 50 + random2(100) );
void player::confuse(int str)
{
confuse_player(str);
}
void player::paralyse(int str)
{
mprf( "You %s the ability to move!",
(paralysis) ? "still haven't" : "suddenly lose" );
if (str > paralysis)
paralysis = str;
if (paralysis > 13)
paralysis = 13;
}
void player::slow_down(int str)
{
::slow_player( str );
}
// hpdice[4]: [0]=HD [1]=min_hp [2]=rand_hp [3]=add_hp
// min hp = [0]*[1]+[3] & max hp = [0]*([1]+[2])+[3])
// example: the Iron Golem, hpdice={15,7,4,0}
// 15*7 < hp < 15*(7+4),
// 105 < hp < 165
// hp will be around 135 each time. (assuming an good random number generator)
// !!!!!!! The system is exactly the same as before, only the way of writing changed !!!!
unsigned char hpdice[4]; // until we have monsters with 32767 hp,this is easily possible
// hpdice[4]: [0]=HD [1]=min_hp [2]=rand_hp [3]=add_hp
// min hp = [0]*[1]+[3] & max hp = [0]*([1]+[2])+[3])
// example: the Iron Golem, hpdice={15,7,4,0}
// 15*7 < hp < 15*(7+4),
// 105 < hp < 165
// hp will be around 135 each time.
unsigned hpdice[4];
ASSERT(rt >= 0);
ASSERT(rt <= 3);
switch (monster->type)
{
case MONS_ZOMBIE_SMALL: case MONS_ZOMBIE_LARGE:
case MONS_SKELETON_SMALL: case MONS_SKELETON_LARGE:
case MONS_SIMULACRUM_SMALL: case MONS_SIMULACRUM_LARGE:
case MONS_SPECTRAL_THING:
return (true);
default:
return (false);
}
}
if (rt < 0 || rt > 3) // make it fool-proof
return (0);
int downscale_zombie_damage(int damage)
{
// these are cumulative, of course: {dlb}
if (damage > 1)
damage--;
if (damage > 4)
damage--;
if (damage > 11)
damage--;
if (damage > 14)
damage--;
return (smc->damage[rt]);
mon_attack_def downscale_zombie_attack(const monsters *mons,
mon_attack_def attk)
{
switch (attk.type)
{
case AT_STING: case AT_SPORE:
case AT_TOUCH: case AT_ENGULF:
attk.type = AT_HIT;
break;
default:
break;
}
if (mons->type == MONS_SIMULACRUM_LARGE
|| mons->type == MONS_SIMULACRUM_SMALL)
{
attk.flavour = AF_COLD;
}
else
{
attk.flavour = AF_PLAIN;
}
attk.damage = downscale_zombie_damage(attk.damage);
return (attk);
}
mon_attack_def mons_attack_spec(const monsters *mons, int attk_number)
{
int mc = mons->type;
const bool zombified = mons_is_zombified(mons);
if ((attk_number < 0 || attk_number > 3) || mc == MONS_HYDRA)
attk_number = 0;
if (mc == MONS_PLAYER_GHOST || mc == MONS_PANDEMONIUM_DEMON)
return mon_attack_def::attk(ghost.values[GVAL_DAMAGE]);
if (zombified)
mc = mons_zombie_base(mons);
mon_attack_def attk = smc->attack[attk_number];
if (attk.flavour == AF_KLOWN)
{
switch (random2(6))
{
case 0: attk.flavour = AF_POISON_NASTY; break;
case 1: attk.flavour = AF_ROT; break;
case 2: attk.flavour = AF_DRAIN_XP; break;
case 3: attk.flavour = AF_FIRE; break;
case 4: attk.flavour = AF_COLD; break;
case 5: attk.flavour = AF_BLINK; break;
}
}
return (zombified? downscale_zombie_attack(mons, attk) : attk);
}
int mons_damage(int mc, int rt)
{
if (rt < 0 || rt > 3)
rt = 0;
return smc->attack[rt].damage;
}
static int equip_slot_to_mslot(equipment_type eq)
{
switch (eq)
{
case EQ_WEAPON: return MSLOT_WEAPON;
case EQ_BODY_ARMOUR: return MSLOT_ARMOUR;
default: return (-1);
}
}
item_def *monsters::slot_item(equipment_type eq)
{
int mslot = equip_slot_to_mslot(eq);
int mindex = mslot == -1? NON_ITEM : inv[mslot];
return (mindex == NON_ITEM? NULL: &mitm[mindex]);
{
}
void monsters::expose_to_element(beam_type, int)
{
}
void monsters::banish()
{
// [dshaligram] FIXME: We should really put these monsters on a
// queue and load them when the player enters the Abyss.
monster_die(this, KILL_RESET, 0);
}
int monsters::holy_aura() const
{
return ((type == MONS_DAEVA || type == MONS_ANGEL)? hit_dice : 0);
}
bool monsters::visible() const
{
return (mons_near(this) && player_monster_visible(this));
}
bool monsters::confused() const
{
return (mons_is_confused(this));
}
bool monsters::paralysed() const
{
return (mons_is_paralysed(this));
}
bool monsters::asleep() const
{
return (mons_is_sleeping(this));
}
int monsters::shield_bonus() const
{
// XXX: Monsters don't actually get shields yet.
const item_def *shld = const_cast<monsters*>(this)->shield();
if (shld)
{
const int shld_c = property(*shld, PARM_AC);
return (random2(shld_c + random2(hit_dice / 2)) / 2);
}
return (0);
}
int monsters::shield_block_penalty() const
{
return (0);
}
int monsters::shield_bypass_ability(int) const
{
return (15 + hit_dice / 2);
}
int monsters::armour_class() const
{
return (ac);
}
int monsters::melee_evasion(const actor *act) const
{
int evasion = ev;
if (paralysed() || asleep() || one_chance_in(20))
evasion = 0;
else if (confused())
evasion /= 2;
return (evasion);
}
void monsters::heal(int amount, bool max_too)
{
hit_points += amount;
if (max_too)
max_hit_points += amount;
if (hit_points > max_hit_points)
hit_points = max_hit_points;
}
int monsters::holiness() const
{
return (mons_holiness(this));
}
int monsters::res_fire() const
{
return (mons_res_fire(this));
}
int monsters::res_cold() const
{
return (mons_res_cold(this));
}
int monsters::res_elec() const
{
return (mons_res_elec(this));
}
int monsters::res_poison() const
{
return (mons_res_poison(this));
}
int monsters::res_negative_energy() const
{
return (mons_res_negative_energy(this));
}
bool monsters::levitates() const
{
return (mons_flies(this));
}
int monsters::mons_species() const
{
return ::mons_species(type);
}
void monsters::poison(actor *agent, int amount)
{
if (amount <= 0)
return;
// Scale poison down for monsters.
if (!(amount /= 2))
amount = 1;
poison_monster(this, agent->atype() == ACT_PLAYER, amount);
}
int monsters::skill(skill_type sk, bool) const
{
switch (sk)
{
case SK_NECROMANCY:
return (holiness() == MH_UNDEAD? hit_dice / 2 : hit_dice / 3);
default:
return (0);
}
}
void monsters::blink()
{
monster_blink(this);
}
void monsters::teleport(bool now, bool)
{
monster_teleport(this, now, false);
}
bool monsters::alive() const
{
return (hit_points > 0 && type != -1);
}
void monsters::hurt(actor *agent, int amount)
{
if (amount <= 0)
return;
hit_points -= amount;
if ((hit_points < 1 || hit_dice < 1) && type != -1)
{
if (agent->atype() == ACT_PLAYER)
monster_die(this, KILL_YOU, 0);
else
monster_die(this, KILL_MON,
monster_index( dynamic_cast<monsters*>(agent) ));
}
}
void monsters::rot(actor *agent, int rotlevel, int immed_rot)
if (mons_holiness(this) != MH_NATURAL)
return;
// Apply immediate damage because we can't handle rotting for monsters yet.
const int damage = immed_rot + random2(rotlevel * 3);
if (damage)
{
if (mons_near(this) && player_monster_visible(this))
mprf("%s %s!",
name(DESC_CAP_THE).c_str(),
rotlevel == 0? "looks less resilient" : "rots");
hurt(agent, damage);
if (alive())
{
max_hit_points -= immed_rot * 2;
if (hit_points > max_hit_points)
hit_points = max_hit_points;
}
}
void monsters::confuse(int strength)
{
bolt beam_temp;
beam_temp.flavour = BEAM_CONFUSION;
mons_ench_f2( this, beam_temp );
}
void monsters::paralyse(int strength)
{
bolt paralysis;
paralysis.flavour = BEAM_PARALYSIS;
mons_ench_f2(this, paralysis);
}
void monsters::slow_down(int strength)
{
bolt slow;
slow.flavour = BEAM_SLOW;
mons_ench_f2(this, slow);
}
0, 10, MONS_QUASIT, MONS_QUASIT, MH_DEMONIC, 50,
{ 3, 2, 2, 0 },
0, 10, MONS_QUASIT, MONS_QUASIT, MH_DEMONIC, 5,
{ {AT_BITE, AF_DRAIN_DEX, 3}, {AT_CLAW, AF_DRAIN_DEX, 2}, {AT_CLAW, AF_DRAIN_DEX, 2}, {AT_NONE, AF_PLAIN, 0} },
MR_RES_FIRE | MR_RES_COLD | MR_RES_POISON,
0, 15, MONS_HUMAN, MONS_KILLER_KLOWN, MH_NATURAL, MAG_IMMUNE,
{ 30, 0, 0, 0 },
MR_NO_FLAGS,
0, 15, MONS_HUMAN, MONS_KILLER_KLOWN, MH_NATURAL, -6,
{ {AT_HIT, AF_KLOWN, 30}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
0, 10, MONS_CLAY_GOLEM, MONS_ELECTRIC_GOLEM, MH_NONLIVING, MAG_IMMUNE,
{ 12, 12, 12, 12 },
0, 10, MONS_CLAY_GOLEM, MONS_ELECTRIC_GOLEM, MH_NONLIVING, -8,
{ {AT_HIT, AF_ELEC, 12}, {AT_HIT, AF_ELEC, 12}, {AT_HIT, AF_ELEC, 12}, {AT_HIT, AF_PLAIN, 12} },
snprintf( info, INFO_SIZE, "You %s the ability to move!",
(you.paralysis) ? "still haven't" : "suddenly lose" );
mpr( info, MSGCH_WARN );
new_value = 2 + random2( 6 + you.paralysis );
if (new_value > you.paralysis)
you.paralysis = new_value;
if (you.paralysis > 13)
you.paralysis = 13;
you.paralyse(2 + random2( 6 + you.paralysis ));
bool attack_shield_blocked(bool verbose);
bool apply_damage_brand();
void calc_elemental_brand_damage(int res, const char *verb);
int resist_adjust_damage(int res, int rawdamage);
int fire_res_apply_cerebov_downgrade(int res);
void drain_defender();
void drain_player();
void drain_monster();
void check_defender_train_armour();
void check_defender_train_dodging();
void splash_defender_with_acid(int strength);
void splash_monster_with_acid(int strength);
bool mons_self_destructs();
bool mons_attack_warded_off();
int mons_attk_delay();
int mons_calc_damage(const mon_attack_def &attk);
void mons_apply_attack_flavour(const mon_attack_def &attk);
int mons_apply_defender_ac(int damage, int damage_max);
bool mons_perform_attack();
void mons_perform_attack_rounds();
void mons_check_attack_perceived();
std::string mons_attack_verb(const mon_attack_def &attk);
std::string mons_weapon_desc();
void mons_announce_hit(const mon_attack_def &attk);
void mons_announce_dud_hit(const mon_attack_def &attk);
void mons_set_weapon(const mon_attack_def &attk);
void mons_do_poison(const mon_attack_def &attk);
std::string mons_defender_name();
void wasp_paralyse_defender();
void melee_attack::player_calc_brand_damage(
int melee_attack::resist_adjust_damage(int res, int rawdamage)
{
if (res > 0)
{
if (defender->atype() == ACT_MONSTER)
rawdamage = 0;
else
rawdamage /= 1 + res * res;
}
else if (res < 0)
rawdamage *= 2;
if (rawdamage < 0)
rawdamage = 0;
return (rawdamage);
}
void melee_attack::calc_elemental_brand_damage(
bool melee_attack::player_apply_damage_brand()
void melee_attack::drain_defender()
{
// What to do, they're different...
if (defender->atype() == ACT_PLAYER)
drain_player();
else
drain_monster();
}
void melee_attack::drain_player()
{
drain_exp();
special_damage =
random2(damage_done) / (2 + defender->res_negative_energy()) + 1;
// Noop for monsters, but what the heck.
attacker->god_conduct(DID_NECROMANCY, 2);
}
void melee_attack::drain_monster()
{
if (defender->res_negative_energy() > 0 || one_chance_in(3))
return;
special_damage_message =
make_stringf(
"%s %s %s!",
attacker->name(DESC_CAP_THE).c_str(),
attacker->conj_verb("drain").c_str(),
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);
attacker->god_conduct(DID_NECROMANCY, 2);
}
bool melee_attack::apply_damage_brand()
res = mons_res_fire(def);
if (weapon && weapon->special == SPWPN_SWORD_OF_CEREBOV && res >= 0)
res = (res > 0) - 1;
player_calc_brand_damage(res, "You burn %s%s");
res = fire_res_apply_cerebov_downgrade( defender->res_fire() );
calc_elemental_brand_damage(res, "burn");
defender->expose_to_element(BEAM_FIRE);
special_damage_message =
"There is a sudden explosion of sparks!";
special_damage = random2avg(28, 3);
special_damage_message =
defender->atype() == ACT_PLAYER?
"You are electrocuted!"
: "There is a sudden explosion of sparks!";
special_damage = 10 + random2(15);
if (mons_res_negative_energy(def) > 0 || one_chance_in(3))
break;
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);
drain_defender();
break;
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;
if (mons_holiness(def) != MH_NATURAL ||
mons_res_negative_energy(def) > 0 ||
damage_done < 1 || you.hp == you.hp_max ||
if (defender->holiness() != MH_NATURAL ||
defender->res_negative_energy() > 0 ||
damage_done < 1 || attacker->stat_hp() == attacker->stat_maxhp() ||
inc_hp(1 + random2(damage_done), false);
int hp_boost = 0;
// thus is probably more valuable on larger weapons?
if (weapon
&& is_fixed_artefact( *weapon )
&& weapon->special == SPWPN_VAMPIRES_TOOTH)
{
hp_boost = damage_done;
}
else
{
hp_boost = 1 + random2(damage_done);
}
attacker->heal(hp_boost);
str_simple_monster_message(def, " convulses in agony.");
special_damage += random2( 1 + you.skills[SK_NECROMANCY] );
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) );
simple_monster_message(
def,
" basks in the translocular energy." );
heal_monster(def, 1 + random2avg(7, 2), true); // heh heh
special_damage_message =
make_stringf("%s %s in the translocular energy.",
defender->name(DESC_CAP_THE).c_str(),
defender->conj_verb("bask").c_str());
defender->heal(1 + random2avg(7, 2), true); // heh heh
if (coinflip())
// Used to be coinflip() || coinflip() for players, just coinflip()
// for monsters; this is a compromise. Note that it makes banishment
// a touch more likely for players, and a shade less likely for
// monsters.
if (!one_chance_in(3))
{
return (false);
}
bool melee_attack::mons_attack_you()
{
return (false);
}
int melee_attack::mons_to_hit()
// TODO
return 0;
}
///////////////////////////////////////////////////////////////////////////
static void tutorial_weapon_check(const item_def *weapon)
{
if (weapon &&
(weapon->base_type != OBJ_WEAPONS
|| is_range_weapon(*weapon)))
mons_perform_attack();
if (perceived_attack && (def->foe == MHITNOT || one_chance_in(3))
&& atk->alive())
}
// 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);
// We're trying to hit a monster, break out of travel/explore now.
interrupt_activity(AI_HIT_MONSTER, defender);
// For tutorials, check if the player is fighting with something unsuitable
tutorial_weapon_check(attk.weapon);
return attk.attack();
return (did_hit);
struct monsters *attacker = &menv[monster_attacking];
int damage_taken = 0;
bool hit = false;
bool blocked = false;
// being attacked by a water creature while standing in water?
bool water_attack = false;
bool bearing_shield = (you.equip[EQ_SHIELD] != -1);
int mmov_x = 0;
int specdam = 0;
char heads = 0; // for hydras {dlb}
int hand_used = 0;
int extraDamage = 0; // from special mon. attacks (burn, freeze, etc)
int resistValue = 0; // player resist value (varies)
int wpn_speed;
char str_pass[ ITEMNAME_SIZE ];
#if DEBUG_DIAGNOSTICS
char st_prn[ 20 ];
#endif
if (attacker->type == MONS_HYDRA)
heads = attacker->number;
if (mons_friendly(attacker))
return;
if (attacker->type == MONS_GIANT_SPORE
|| attacker->type == MONS_BALL_LIGHTNING)
const int holy = defender->holy_aura();
if (holy && mons_holiness(atk) == MH_UNDEAD
&& !check_mons_resist_magic( atk, holy ))
attacker->hit_points = -1;
return;
}
// if a friend wants to help, they can attack <monster_attacking>
if (you.pet_target == MHITNOT)
you.pet_target = monster_attacking;
if (mons_has_ench( attacker, ENCH_SUBMERGED ))
return;
// If a mimic is attacking the player, it is thereafter known.
if (mons_is_mimic(attacker->type))
attacker->flags |= MF_KNOWN_MIMIC;
if (you.duration[DUR_REPEL_UNDEAD]
&& mons_holiness( attacker ) == MH_UNDEAD
&& !check_mons_resist_magic( attacker, you.piety ))
{
if (simple_monster_message(
attacker,
" tries to attack you, but is repelled by your holy aura."))
interrupt_activity( AI_MONSTER_ATTACKS, attacker );
return;
}
if (wearing_amulet(AMU_WARDING)
|| (you.religion == GOD_VEHUMET && you.duration[DUR_PRAYER]
&& (!player_under_penance() && you.piety >= piety_breakpoint(2))))
{
if (mons_has_ench(attacker, ENCH_ABJ_I, ENCH_ABJ_VI))
if (needs_message)
// should be scaled {dlb}
if (coinflip())
{
if (simple_monster_message(
attacker,
" tries to attack you, but flinches away."))
interrupt_activity( AI_MONSTER_ATTACKS, attacker );
return;
}
mprf("%s tries to attack %s, but is repelled by %s holy auro.",
atk->name(DESC_CAP_THE).c_str(),
defender->name(DESC_NOCAP_THE).c_str(),
defender->pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str());
if (grd[attacker->x][attacker->y] == DNGN_SHALLOW_WATER
&& !mons_flies( attacker )
&& !mons_class_flag( attacker->type, M_AMPHIBIOUS )
&& monster_habitat( attacker->type ) == DNGN_FLOOR
&& one_chance_in(4))
// [dshaligram] Note: warding is no longer a simple 50% chance.
const int warding = defender->warding();
if (warding
&& mons_is_summoned(atk)
&& !check_mons_resist_magic( atk, warding ))
if (simple_monster_message(attacker, " splashes around in the water."))
interrupt_activity( AI_MONSTER_ATTACKS, attacker );
return;
}
if (player_in_water()
&& !player_is_swimming()
&& monster_habitat( attacker->type ) == DNGN_DEEP_WATER)
{
water_attack = true;
simple_monster_message(attacker,
" uses the watery terrain to its advantage.");
}
char runthru;
bool player_perceives_attack = false;
for (runthru = 0; runthru < 4; runthru++)
{
blocked = false;
wpn_speed = 0; // 0 = didn't attack w/ weapon
if (attacker->type == MONS_HYDRA)
if (needs_message)
if (attacker->type == MONS_ZOMBIE_SMALL
|| attacker->type == MONS_ZOMBIE_LARGE
|| attacker->type == MONS_SKELETON_SMALL
|| attacker->type == MONS_SKELETON_LARGE
|| attacker->type == MONS_SIMULACRUM_SMALL
|| attacker->type == MONS_SIMULACRUM_LARGE
|| attacker->type == MONS_SPECTRAL_THING)
{
mdam = mons_damage(attacker->number, runthru);
int melee_attack::mons_attk_delay()
{
return (weapon? property(*weapon, PWPN_SPEED) : 0);
}
if ((attacker->type == MONS_TWO_HEADED_OGRE
|| attacker->type == MONS_ETTIN)
&& runthru == 1)
{
hand_used = 1;
}
const int con_block = random2(attacker->shield_bypass_ability(to_hit)
+ defender->shield_block_penalty());
const int pro_block = defender->shield_bonus();
if (pro_block >= con_block)
{
perceived_attack = true;
damage_taken = 0;
int mons_to_hit = 16 + attacker->hit_dice; //* attacker->hit_dice;//* 3
if (water_attack)
mons_to_hit += 5;
if (attacker->inv[hand_used] != NON_ITEM
&& mitm[attacker->inv[hand_used]].base_type == OBJ_WEAPONS)
{
mons_to_hit += mitm[attacker->inv[hand_used]].plus;
mons_to_hit += property( mitm[attacker->inv[hand_used]], PWPN_HIT );
wpn_speed = property( mitm[attacker->inv[hand_used]], PWPN_SPEED );
}
// Factors against blocking
// [dshaligram] Scaled back HD effect to 50% of previous, reduced
// shield_blocks multiplier to 5.
const int con_block =
random2(15 + attacker->hit_dice / 2
+ (5 * you.shield_blocks * you.shield_blocks));
// Factors for blocking:
// [dshaligram] Added weighting for shields skill, increased dex bonus
// from .2 to .25
const int pro_block =
random2(player_shield_class())
+ (random2(you.dex) / 4)
+ (random2(skill_bump(SK_SHIELDS)) / 4)
- 1;
if (needs_message && verbose)
mprf("%s %s %s attack.",
defender->name(DESC_CAP_THE).c_str(),
defender->conj_verb("block").c_str(),
attacker->name(DESC_NOCAP_YOUR).c_str());
if (!you.paralysis && !you_are_delayed() && !you.conf
&& player_monster_visible( attacker )
&& player_shield_class() > 0
&& con_block <= pro_block)
{
you.shield_blocks++;
defender->shield_block_succeeded();
blocked = true;
hit = false;
player_perceives_attack = true;
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 (bearing_shield && one_chance_in(4))
exercise(SK_SHIELDS, 1);
}
else if (player_light_armour(true) && one_chance_in(3))
if (get_equip_race(*weapon) == ISFLAG_ORCISH
&& mons_species(atk->type) == MONS_ORC
&& coinflip())
const int player_dodge = random2limit(player_evasion(), 40)
+ random2(you.dex) / 3
- (player_monster_visible(attacker) ? 2 : 14)
- (you_are_delayed() ? 5 : 0);
if (!blocked
&& (random2(mons_to_hit) >= player_dodge || one_chance_in(30)))
{
hit = true;
int damage_size = 0;
if (attacker->inv[hand_used] != NON_ITEM
&& mitm[attacker->inv[hand_used]].base_type == OBJ_WEAPONS
&& (mitm[attacker->inv[hand_used]].sub_type < WPN_SLING
|| mitm[attacker->inv[hand_used]].sub_type > WPN_CROSSBOW))
{
damage_size = property( mitm[attacker->inv[hand_used]],
PWPN_DAMAGE );
damage_taken = random2(damage_size);
if (get_equip_race(mitm[attacker->inv[hand_used]]) == ISFLAG_ORCISH
&& mons_species(attacker->type) == MONS_ORC
&& coinflip())
{
damage_taken++;
}
if (mitm[attacker->inv[hand_used]].plus2 >= 0)
{
/* + or 0 to-dam */
damage_taken += random2(mitm[attacker->inv[hand_used]].plus2 + 1);
}
else
{
/* - to-dam */
damage_taken -= (random2(1 + abs(mitm[attacker->inv[hand_used]].plus2)));
}
damage_taken -= 1 + random2(3); //1;
}
damage_size += mdam;
damage_taken += 1 + random2(mdam);
if (water_attack)
damage_taken *= 2;
int ac = player_AC();
if (ac > 0)
{
int damage_reduction = random2(ac + 1);
if (!player_light_armour())
{
const int body_arm_ac = property( you.inv[you.equip[EQ_BODY_ARMOUR]],
PARM_AC );
int percent = 2 * (you.skills[SK_ARMOUR] + body_arm_ac);
if (percent > 50)
percent = 50;
int min = 1 + (damage_size * percent) / 100;
}
else if (!blocked)
{
hit = false;
if (simple_monster_message(attacker, " misses you."))
player_perceives_attack = true;
}
if (water_attack)
damage *= 2;
if ((hit && !blocked) || damage_taken > 0)
player_perceives_attack = true;
if (damage_taken < 1 && hit && !blocked)
{
mprf("%s hits you but doesn't do any damage.",
ptr_monam(attacker, DESC_CAP_THE));
}
return (mons_apply_defender_ac(damage, damage_max));
}
hit = true;
mmov_x = attacker->inv[hand_used];
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " hits you");
#if DEBUG_DIAGNOSTICS
strcat(info, " for ");
// note: doesn't take account of special weapons etc
itoa( damage_taken, st_prn, 10 );
strcat( info, st_prn );
#endif
if (attacker->type != MONS_DANCING_WEAPON && mmov_x != NON_ITEM
&& mitm[mmov_x].base_type == OBJ_WEAPONS
&& !is_range_weapon( mitm[mmov_x] ))
if (const item_def *arm = defender->slot_item(EQ_BODY_ARMOUR))
strcat(info, "!");
mpr( info, MSGCH_PLAIN );
if (hit)
{
if (you.equip[EQ_BODY_ARMOUR] != -1)
{
const int body_arm_mass = item_mass( you.inv[you.equip[EQ_BODY_ARMOUR]] );
int min = 1 + (damage_max * perc) / 100;
if (min > ac / 2)
min = ac / 2;
// Being generous here...this can train both Armour
// and Dodging.
if (!player_light_armour(false) && coinflip()
&& random2(1000) <= body_arm_mass)
{
// raised from 1 {bwross}
exercise(SK_ARMOUR, (coinflip() ? 2 : 1));
}
}
if (damage_reduction < min)
damage_reduction = min;
/* special attacks: */
int mclas = attacker->type;
if (mclas == MONS_KILLER_KLOWN)
{
switch (random2(6))
{
case 0:
// comment and enum do not match {dlb}
mclas = MONS_SNAKE; // scorp
break;
case 1:
mclas = MONS_NECROPHAGE;
break;
case 2:
mclas = MONS_WRAITH;
break;
case 3:
mclas = MONS_FIRE_ELEMENTAL;
break;
case 4:
mclas = MONS_ICE_BEAST;
break;
case 5:
mclas = MONS_PHANTOM;
break;
}
}
switch (mclas)
{
case MONS_GILA_MONSTER:
case MONS_GIANT_ANT:
case MONS_WOLF_SPIDER:
case MONS_REDBACK:
if (player_res_poison())
break;
if (one_chance_in(20)
|| (damage_taken > 3 && one_chance_in(4)))
{
simple_monster_message( attacker,
"'s bite was poisonous!" );
if (attacker->type == MONS_REDBACK)
poison_player( random2avg(9, 2) + 3 );
else
poison_player(1);
}
break;
case MONS_KILLER_BEE:
case MONS_BUMBLEBEE:
if (!player_res_poison()
&& (one_chance_in(20)
|| (damage_taken > 2 && one_chance_in(3))))
{
simple_monster_message( attacker, " stings you!" );
if (attacker->type == MONS_BUMBLEBEE)
poison_player( random2(3) );
else
poison_player(1);
}
break;
case MONS_ROTTING_DEVIL:
case MONS_NECROPHAGE:
case MONS_GHOUL:
case MONS_DEATH_OOZE:
if (you.is_undead)
break;
// both sides call random2() - looking familiar by now {dlb}
if (one_chance_in(20) || (damage_taken > 2 && one_chance_in(3)))
{
rot_player( 2 + random2(3) );
if (damage_taken > 5)
rot_hp(1);
}
if (one_chance_in(4))
disease_player( 50 + random2(100) );
break;
case MONS_KOMODO_DRAGON:
case MONS_GIANT_MOSQUITO:
if (!one_chance_in(3))
disease_player( 50 + random2(100) );
break;
case MONS_FIRE_VORTEX:
attacker->hit_points = -10;
// fall through -- intentional? {dlb}
case MONS_FIRE_ELEMENTAL:
case MONS_BALRUG:
case MONS_SUN_DEMON:
strcpy(info, "You are engulfed in flames");
resistValue = player_res_fire();
extraDamage = 15 + random2(15);
if (resistValue > 0)
{
extraDamage /= (1 + resistValue * resistValue);
}
else
{
if (resistValue < 0)
extraDamage += 8 + random2(8);
}
strcat(info, (extraDamage < 10) ? "." :
(extraDamage < 25) ? "!" :
"!!");
mpr(info);
if (you.duration[DUR_CONDENSATION_SHIELD] > 0)
{
mpr("Your icy shield dissipates!", MSGCH_DURATION);
you.duration[DUR_CONDENSATION_SHIELD] = 0;
you.redraw_armour_class = 1;
}
damage_taken += extraDamage;
expose_player_to_element(BEAM_FIRE, 1);
break;
case MONS_SMALL_SNAKE:
case MONS_SNAKE:
case MONS_GIANT_MITE:
case MONS_GOLD_MIMIC:
case MONS_WEAPON_MIMIC:
case MONS_ARMOUR_MIMIC:
case MONS_SCROLL_MIMIC:
case MONS_POTION_MIMIC:
if (!player_res_poison()
&& (one_chance_in(20)
|| (damage_taken > 2 && one_chance_in(4))))
{
poison_player(1);
}
break;
case MONS_QUEEN_BEE:
case MONS_GIANT_CENTIPEDE:
case MONS_SOLDIER_ANT:
case MONS_QUEEN_ANT:
if (!player_res_poison())
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " stings you!");
mpr(info);
poison_player(2);
}
break;
case MONS_SCORPION:
case MONS_BROWN_SNAKE:
case MONS_BLACK_SNAKE:
case MONS_YELLOW_SNAKE:
case MONS_SPINY_FROG:
if (!player_res_poison()
&& (one_chance_in(15)
|| (damage_taken > 2 && one_chance_in(4))))
// ^^^yep, this should be a function^^^ {dlb}
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " poisons you!");
mpr(info);
poison_player(1);
}
break;
case MONS_SHADOW_DRAGON:
case MONS_SPECTRAL_THING:
if (coinflip())
break;
// fall-through {dlb}
case MONS_WIGHT: // less likely because wights do less damage
case MONS_WRAITH:
// enum does not match comment 14jan2000 {dlb}
case MONS_SOUL_EATER: // shadow devil
// enum does not match comment 14jan2000 {dlb}
case MONS_SPECTRAL_WARRIOR: // spectre
case MONS_SHADOW_FIEND:
case MONS_ORANGE_RAT:
case MONS_SHADOW_WRAITH:
case MONS_ANCIENT_LICH:
case MONS_LICH:
case MONS_BORIS:
if (one_chance_in(30) || (damage_taken > 5 && coinflip()))
drain_exp();
break;
case MONS_RED_WASP:
if (!player_res_poison())
poison_player( (coinflip() ? 2 : 1) );
// intentional fall-through {dlb}
case MONS_YELLOW_WASP:
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " stings you.");
mpr(info);
if (!player_res_poison()
&& (one_chance_in(20)
|| (damage_taken > 2 && !one_chance_in(3))))
// maybe I should flip back the other way? {dlb}
{
if (you.paralysis > 0)
mpr("You still can't move!", MSGCH_WARN);
else
mpr("You suddenly lose the ability to move!",
MSGCH_WARN);
if ( you.paralysis == 0 || mclas == MONS_RED_WASP )
you.paralysis += 1 + random2(3);
}
break;
case MONS_SPINY_WORM:
if (!player_res_poison())
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " stings you!");
mpr(info);
poison_player( 2 + random2(4) );
}
// intentional fall-through {dlb}
case MONS_BROWN_OOZE:
case MONS_ACID_BLOB:
case MONS_ROYAL_JELLY:
case MONS_JELLY:
mpr("You are splashed with acid!");
splash_with_acid(3);
break;
case MONS_SIMULACRUM_SMALL:
case MONS_SIMULACRUM_LARGE:
resistValue = player_res_cold();
if (resistValue > 0)
extraDamage = 0;
else if (resistValue == 0)
{
extraDamage += roll_dice( 1, 4 );
mprf("%s chills you.", ptr_monam(attacker, DESC_CAP_THE));
}
else if (resistValue < 0)
{
extraDamage = roll_dice( 2, 4 );
mprf("%s freezes you.", ptr_monam(attacker, DESC_CAP_THE));
}
damage_taken += extraDamage;
expose_player_to_element(BEAM_COLD, 1);
break;
case MONS_ICE_DEVIL:
case MONS_ICE_BEAST:
case MONS_FREEZING_WRAITH:
case MONS_ICE_FIEND:
case MONS_WHITE_IMP:
case MONS_ANTAEUS:
case MONS_AZURE_JELLY:
extraDamage = attacker->hit_dice + random2(attacker->hit_dice * 2);
resistValue = player_res_cold();
if (resistValue > 0)
{
extraDamage /= (1 + resistValue * resistValue);
}
if (resistValue < 0)
{
extraDamage += attacker->hit_dice +
random2(attacker->hit_dice * 2);
}
if (extraDamage > 4)
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
if (extraDamage < 10)
strcat(info, " chills you.");
else
strcat(info, " freezes you!");
if (extraDamage > 19)
strcat(info, "!");
mpr(info);
}
damage_taken += extraDamage;
expose_player_to_element(BEAM_COLD, 1);
break;
case MONS_ELECTRIC_GOLEM:
if (!player_res_electricity())
{
damage_taken += attacker->hit_dice
+ random2(attacker->hit_dice * 2);
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " shocks you!");
mpr(info);
}
break;
case MONS_VAMPIRE:
if (you.is_undead)
break;
/* ******************************************************************
if ( damage_taken > 6 && one_chance_in(3) || !one_chance_in(20))
{
mpr("You feel less resilient.");
you.hp_max -= ( coinflip() ? 2 : 1 );
deflate_hp(you.hp_max, false);
heal_monster(attacker, 5 + random2(8), true);
}
****************************************************************** */
// heh heh {dlb}
// oh, this is mean! {gdl}
if (heal_monster(attacker, random2(damage_taken), true))
simple_monster_message(attacker, " draws strength from your injuries!");
break;
case MONS_SHADOW:
if (player_prot_life() <= random2(3)
&& (one_chance_in(20)
|| (damage_taken > 0 && one_chance_in(3))))
{
lose_stat(STAT_STRENGTH, 1);
}
break;
case MONS_HUNGRY_GHOST:
if (you.is_undead == US_UNDEAD)
break;
if (one_chance_in(20) || (damage_taken > 0 && coinflip()))
make_hungry(400, false);
break;
case MONS_GUARDIAN_NAGA:
break;
case MONS_PHANTOM:
case MONS_INSUBSTANTIAL_WISP:
case MONS_BLINK_FROG:
case MONS_MIDGE:
if (one_chance_in(3))
{
simple_monster_message(attacker, " blinks.");
monster_blink(attacker);
}
break;
case MONS_JELLYFISH:
case MONS_ORANGE_DEMON:
// if ( !one_chance_in(3) ) break;
if (player_res_poison())
break;
if (attacker->type == MONS_ORANGE_DEMON
&& (!one_chance_in(4) || runthru != 1))
{
break;
}
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " stings you!");
mpr(info);
poison_player(1);
lose_stat(STAT_STRENGTH, 1);
break;
case MONS_PULSATING_LUMP:
if (one_chance_in(3))
{
if (one_chance_in(5))
mutate(100);
else
give_bad_mutation();
}
break;
case MONS_MOTH_OF_WRATH:
if (one_chance_in(3))
{
simple_monster_message(attacker, " infuriates you!");
go_berserk(false);
}
break;
default:
break;
} // end of switch for special attacks.
/* use break for level drain, maybe with beam variables,
because so many creatures use it. */
/* special weapons */
if (hit
&& (attacker->inv[hand_used] != NON_ITEM
|| ((attacker->type == MONS_PLAYER_GHOST
|| attacker->type == MONS_PANDEMONIUM_DEMON)
&& ghost.values[ GVAL_BRAND ] != SPWPN_NORMAL)))
{
unsigned char itdam;
if (attacker->type == MONS_PLAYER_GHOST
|| attacker->type == MONS_PANDEMONIUM_DEMON)
{
itdam = ghost.values[ GVAL_BRAND ];
}
else
{
const item_def& mons_weapon = mitm[attacker->inv[hand_used]];
if ( is_range_weapon(mons_weapon) )
itdam = SPWPN_NORMAL;
else
itdam = mons_weapon.special;
}
specdam = 0;
switch (itdam)
{
case SPWPN_NORMAL:
default:
break;
case SPWPN_SWORD_OF_CEREBOV:
case SPWPN_FLAMING:
specdam = 0;
resistValue = player_res_fire();
if (itdam == SPWPN_SWORD_OF_CEREBOV)
resistValue -= 1;
if (resistValue > 0)
{
damage_taken += (random2(damage_taken) / 2 + 1) /
(1 + (resistValue * resistValue));
}
if (resistValue <= 0)
specdam = random2(damage_taken) / 2 + 1;
if (resistValue < 0)
specdam += random2(damage_taken) / 2 + 1;
if (specdam)
{
simple_monster_message(attacker, " burns you.");
/* **********************
commented out for now
if (specdam < 3)
strcat(info, ".");
if (specdam >= 3 && specdam < 7)
strcat(info, "!");
if (specdam >= 7)
strcat(info, "!!");
*********************** */
}
if (you.duration[DUR_CONDENSATION_SHIELD] > 0)
{
mpr("Your icy shield dissipates!", MSGCH_DURATION);
you.duration[DUR_CONDENSATION_SHIELD] = 0;
you.redraw_armour_class = 1;
}
break;
case SPWPN_FREEZING:
specdam = 0;
resistValue = player_res_cold();
if (resistValue <= 0)
specdam = random2(damage_taken) / 2 + 1;
if (resistValue < 0)
specdam += random2(damage_taken) / 2 + 1;
if (resistValue > 0)
{
damage_taken += (random2(damage_taken) / 2 + 1) /
(1 + (resistValue * resistValue));
}
if (specdam)
{
simple_monster_message(attacker, " freezes you.");
/* **********************
commented out for now
if (specdam < 3)
strcat(info, ".");
if (specdam >= 3 && specdam < 7)
strcat(info, "!");
if (specdam >= 7)
strcat(info, "!!");
*********************** */
}
break;
case SPWPN_HOLY_WRATH:
if (attacker->type == MONS_PLAYER_GHOST)
break; // ghosts can't wield holy wrath
if (you.is_undead)
{
specdam = random2(damage_taken);
if (specdam)
{
strcpy(info, "The wound is extremely painful");
if (specdam < 3)
strcat(info, ".");
else if (specdam < 7)
strcat(info, "!");
else
strcat(info, "!!");
mpr(info);
}
}
break;
case SPWPN_ELECTROCUTION:
// This runs the risk of making levitation into a
// cheap version of the Resist spell (with added
// bonuses), so it shouldn't be used anywhere else,
// and should possibly be removed from this case as
// well. -- bwr
if (player_is_levitating()) // you're not grounded
break;
damage -= damage_reduction;
}
specdam = 0;
// This is related to old code where elec wpns had charges
#if 0
if (menv[monster_attacking].type != MONS_PLAYER_GHOST
&& (mitm[attacker->inv[hand_used]].plus2 <= 0
|| item_cursed( mitm[attacker->inv[hand_used]] )))
{
break;
}
#endif
std::string melee_attack::mons_attack_verb(const mon_attack_def &attk)
{
static const char *attack_types[] =
{
"",
"hit", // including weapon attacks
"bite",
"sting",
case SPWPN_STAFF_OF_OLGREB:
case SPWPN_VENOM:
if (!player_res_poison() && one_chance_in(3))
{
simple_monster_message(attacker,
(attacker->type == MONS_DANCING_WEAPON)
? " is poisoned!" : "'s weapon is poisoned!");
poison_player(2);
}
break;
case SPWPN_PROTECTION:
break;
case SPWPN_DRAINING:
drain_exp();
specdam = random2(damage_taken) / (2 + player_prot_life()) + 1;
break;
case SPWPN_SPEED:
wpn_speed = (wpn_speed + 1) / 2;
break;
case SPWPN_VORPAL:
specdam = 1 + (random2(damage_taken) / 2);
break;
case SPWPN_VAMPIRES_TOOTH:
case SPWPN_VAMPIRICISM:
specdam = 0; // note does no extra damage
if (you.is_undead)
break;
if (one_chance_in(5))
break;
// heh heh {dlb}
if (heal_monster(attacker, 1 + random2(damage_taken), true))
simple_monster_message(attacker, " draws strength from your injuries!");
break;
case SPWPN_DISRUPTION:
if (attacker->type == MONS_PLAYER_GHOST)
break;
if (you.is_undead)
{
specdam = random2(damage_taken) + random2(damage_taken)
+ random2(damage_taken) + random2(damage_taken);
if (specdam)
{
strcpy(info, "You are blasted by holy energy");
if (specdam < 7)
strcat(info, ".");
else if (specdam < 15)
strcat(info, "!");
else
strcat(info, "!!");
mpr(info);
}
}
break;
case SPWPN_DISTORTION:
if (one_chance_in(3))
{
mpr("Your body is twisted painfully.");
specdam += 1 + random2avg(7, 2);
break;
}
if (one_chance_in(3))
{
mpr("Your body is terribly warped!");
specdam += 3 + random2avg(24, 2);
break;
}
if (one_chance_in(3))
{
random_blink(true);
break;
}
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());
mprf("%s %s %s%s%s%s",
attacker->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());
}
if (coinflip())
{
you_teleport();
break;
}
void melee_attack::mons_announce_dud_hit(const mon_attack_def &attk)
{
mprf("%s %s %s but doesn't do any damage.",
attacker->name(DESC_CAP_THE).c_str(),
attacker->conj_verb( mons_attack_verb(attk) ).c_str(),
mons_defender_name().c_str());
}
if (coinflip())
{
you_teleport2( true, one_chance_in(5) );
break;
}
void melee_attack::check_defender_train_dodging()
{
if (defender->wearing_light_armour(false)
&& attacker_visible
&& one_chance_in(3))
{
perceived_attack = true;
defender->exercise(SK_DODGING, 1);
}
}
if (coinflip() && you.level_type != LEVEL_ABYSS)
{
you.banished = true;
break;
}
break;
} // end of switch
} // end of special weapons
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() && random2(1000) <= item_mass(*arm))
defender->exercise(SK_ARMOUR, coinflip()? 2 : 1);
}
if (damage_taken > 0)
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) )
|| (damage_done > 2
&& one_chance_in( attk.flavour == AF_POISON? 4 : 3 )))
{
if (needs_message)
mons_lose_attack_energy(attacker, wpn_speed, runthru);
} // end of for runthru
if (player_perceives_attack)
interrupt_activity(AI_MONSTER_ATTACKS, attacker);
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);
struct monsters *attacker = &menv[monster_attacking];
struct monsters *defender = &menv[monster_attacked];
// [dshaligram] Adopted 4.1.2's wasp mechanics, in slightly modified form.
if (attacker->id() == MONS_RED_WASP)
defender->poison( attacker, coinflip()? 2 : 1 );
int weapon = -1; // monster weapon, if any
int damage_taken = 0;
bool hit = false;
// int mmov_x = 0;
bool water_attack = false;
int specdam = 0;
int hand_used = 0;
bool sees = false;
int wpn_speed; // 0 == didn't use actual weapon
int habitat = monster_habitat( attacker->type );
char str_pass[ ITEMNAME_SIZE ];
if (!defender->res_poison() && one_chance_in( damage_done > 4? 3 : 20 ))
defender->paralyse( roll_dice(1, 3) );
else
defender->slow_down( roll_dice(1, 3) );
}
if (attacker->type == MONS_GIANT_SPORE
|| attacker->type == MONS_BALL_LIGHTNING)
{
attacker->hit_points = -1;
return false;
}
if (mons_has_ench( attacker, ENCH_SUBMERGED )
&& habitat != DNGN_FLOOR
&& habitat != monster_habitat( defender->type ))
{
return false;
}
if (attacker->fumbles_attack(true))
return true;
// habitat is the favoured habitat of the attacker
water_attack = defender->floundering() && attacker->swimming();
void melee_attack::splash_defender_with_acid(int strength)
{
if (defender->atype() == ACT_PLAYER)
splash_with_acid(strength);
else
splash_monster_with_acid(strength);
}
if (mons_near(attacker) && mons_near(defender))
sees = true;
// Any objects seen in combat are thereafter known mimics.
if (mons_is_mimic(attacker->type) && mons_near(attacker))
attacker->flags |= MF_KNOWN_MIMIC;
if (mons_is_mimic(defender->type) && mons_near(defender))
defender->flags |= MF_KNOWN_MIMIC;
// now disturb defender, regardless
behaviour_event(defender, ME_WHACK, monster_attacking);
int heads = 0;
if (attacker->type == MONS_HYDRA)
heads = attacker->number;
void melee_attack::mons_apply_attack_flavour(const mon_attack_def &attk)
{
// Most of this is from BWR 4.1.2.
int mdam = mons_damage(attacker->type, runthru);
wpn_speed = 0;
case AF_POISON:
case AF_POISON_NASTY:
case AF_POISON_MEDIUM:
case AF_POISON_STRONG:
mons_do_poison(attk);
break;
if (attacker->type == MONS_ZOMBIE_SMALL
|| attacker->type == MONS_ZOMBIE_LARGE
|| attacker->type == MONS_SKELETON_SMALL
|| attacker->type == MONS_SKELETON_LARGE
|| attacker->type == MONS_SIMULACRUM_SMALL
|| attacker->type == MONS_SIMULACRUM_LARGE
|| attacker->type == MONS_SPECTRAL_THING)
// what do these things have in common? {dlb}
case AF_POISON_STR:
res = defender->res_poison();
if (res <= 0)
mdam = mons_damage(attacker->number, runthru);
// cumulative reductions - series of if-conditions
// is necessary: {dlb}
if (mdam > 1)
mdam--;
if (mdam > 2)
mdam--;
if (mdam > 5)
mdam--;
if (mdam > 9)
mdam--; // was: "-= 2" {dlb}
defender->poison( attacker, roll_dice(1,3) );
if (one_chance_in(4))
defender->drain_stat( STAT_STRENGTH, 1 );
if (mons_wields_two_weapons(attacker)
&& runthru == 1 && attacker->inv[MSLOT_MISSILE] != NON_ITEM)
{
hand_used = 1;
}
case AF_DISEASE:
if (defender->atype() == ACT_PLAYER)
disease_player( 50 + random2(100) );
break;
int mons_to_hit = 20 + attacker->hit_dice * 5;
// * menv [monster_attacking].hit_dice; // * 3
special_damage =
resist_adjust_damage(defender->res_fire(),
atk->hit_dice + random2(atk->hit_dice));
if (needs_message && special_damage)
mprf("%s %s engulfed in flames%s",
defender->name(DESC_CAP_THE).c_str(),
defender->conj_verb("are").c_str(),
special_attack_punctuation().c_str());
weapon = attacker->inv[hand_used];
case AF_COLD:
special_damage =
resist_adjust_damage(defender->res_cold(),
atk->hit_dice + random2( 2 * atk->hit_dice ));
if (needs_message && special_damage)
mprf("%s %s %s!",
attacker->name(DESC_CAP_THE).c_str(),
attacker->conj_verb("freeze").c_str(),
defender->name(DESC_NOCAP_THE).c_str());
break;
if (weapon != NON_ITEM)
{
mons_to_hit += mitm[weapon].plus;
// mons_to_hit += 3 * property( mitm[weapon], PWPN_HIT );
mons_to_hit += property( mitm[weapon], PWPN_HIT );
case AF_ELEC:
special_damage =
resist_adjust_damage(defender->res_elec(),
atk->hit_dice + random2( 2 * atk->hit_dice ));
if (defender->levitates())
special_damage = special_damage * 2 / 3;
damage_taken = random2(property( mitm[attacker->inv[hand_used]],
PWPN_DAMAGE ));
if (get_equip_race(mitm[attacker->inv[hand_used]]) == ISFLAG_ORCISH
&& mons_species(attacker->type) == MONS_ORC
&& coinflip())
{
damage_taken++;
}
//if (mitm[mons_inv[i][0]].plus > 80) damage_taken -= 100;
// damage_taken += mitm[mons_inv[i][0]].plus;
if (mitm[attacker->inv[hand_used]].plus2 >= 0)
{
/* + or 0 to-dam */
damage_taken += random2(mitm[attacker->inv[hand_used]].plus2 + 1);
}
else
{
/* - to-dam */
damage_taken -= random2(abs(mitm[attacker->inv[hand_used]].plus2 + 1));
}
damage_taken -= 1 + random2(3); //1;
mprf("%s %s strength from %s injuries!",
attacker->name(DESC_CAP_THE).c_str(),
attacker->conj_verb("draw").c_str(),
defender->name(DESC_NOCAP_YOUR).c_str());
if (water_attack)
damage_taken *= 2;
damage_taken -= random2(1 + defender->armour_class);
if (damage_taken < 1)
damage_taken = 0;
}
else
{
hit = false;
if (sees)
// 4.1.2 actually drains max hp; we're being nicer and just doing
// a rot effect.
if ((damage_done > 6 && one_chance_in(3)) || one_chance_in(20))
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " misses ");
if (attacker == defender)
strcat(info,
mons_pronoun(attacker->type, PRONOUN_REFLEXIVE));
else
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
strcat(info, ".");
mpr(info);
if (defender->atype() == ACT_PLAYER)
mprf("You feel less resilient.");
defender->rot( attacker, 0, coinflip()? 2 : 1 );
if (sees)
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " hits ");
if ( attacker == defender )
strcat(info,
mons_pronoun(attacker->type, PRONOUN_REFLEXIVE));
else
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
#if DEBUG_DIAGNOSTICS
strcat(info, " for ");
// note: doesn't take account of special weapons etc
itoa(damage_taken, st_prn, 10);
strcat(info, st_prn);
#endif
strcat(info, "."); // but doesn't do any you.damage.");
mpr(info);
}
defender->drain_stat(STAT_STRENGTH, 1);
int mmov_x = attacker->inv[hand_used];
if (sees)
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " hits ");
if (attacker == defender)
strcat(info,
mons_pronoun(attacker->type, PRONOUN_REFLEXIVE));
else
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
if (attacker->type != MONS_DANCING_WEAPON
&& attacker->inv[hand_used] != NON_ITEM
&& mitm[attacker->inv[hand_used]].base_type == OBJ_WEAPONS
&& !is_range_weapon( mitm[attacker->inv[hand_used]] ))
{
strcat(info, " with ");
it_name(mmov_x, DESC_NOCAP_A, str_pass); // was 7
strcat(info, str_pass);
}
strcat(info, "! ");
mpr(info);
}
if (actor_decapitates_hydra(attacker, defender, damage_taken))
{
mons_lose_attack_energy(attacker, wpn_speed, runthru);
if (defender->hit_points < 1)
{
monster_die(defender, KILL_MON, monster_attacking);
return (true);
}
// Skip rest of attack.
continue;
}
// special attacks:
switch (attacker->type)
{
// enum does not match comment 14jan2000 {dlb}
case MONS_CENTAUR: // cockatrice
case MONS_JELLY:
case MONS_GUARDIAN_NAGA:
break;
case MONS_GIANT_ANT:
case MONS_WOLF_SPIDER:
case MONS_REDBACK:
case MONS_SPINY_WORM:
case MONS_JELLYFISH:
case MONS_ORANGE_DEMON:
if (attacker->type == MONS_SPINY_WORM || one_chance_in(20)
|| (damage_taken > 3 && one_chance_in(4)))
{
if (sees)
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " stings ");
if (attacker == defender)
strcat(info, mons_pronoun(attacker->type,
PRONOUN_REFLEXIVE));
else
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
strcat(info, ".");
mpr(info);
}
poison_monster(defender, false);
}
break;
case MONS_KILLER_BEE:
case MONS_BUMBLEBEE:
if (one_chance_in(20)
|| (damage_taken > 2 && one_chance_in(3)))
{
if (sees)
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " stings ");
if (attacker == defender)
strcat(info, mons_pronoun(attacker->type,
PRONOUN_REFLEXIVE));
else
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
strcat(info, ".");
mpr(info);
}
poison_monster(defender, false);
}
break;
case MONS_NECROPHAGE:
case MONS_ROTTING_DEVIL:
case MONS_GHOUL:
case MONS_DEATH_OOZE:
if (mons_res_negative_energy( defender ))
break;
if (one_chance_in(20)
|| (damage_taken > 2 && one_chance_in(3)))
{
defender->max_hit_points -= 1 + random2(3);
defender->drain_stat(STAT_DEXTERITY, 1);
}
break;
case MONS_FIRE_VORTEX:
attacker->hit_points = -10;
// deliberate fall-through
case MONS_FIRE_ELEMENTAL:
case MONS_BALRUG:
case MONS_SUN_DEMON:
specdam = 0;
if (mons_res_fire(defender) == 0)
specdam = 15 + random2(15);
else if (mons_res_fire(defender) < 0)
specdam = 20 + random2(25);
if (one_chance_in(20) || (damage_done > 0 && coinflip()))
defender->make_hungry(400, false);
break;
case MONS_QUEEN_BEE:
case MONS_GIANT_CENTIPEDE:
case MONS_SOLDIER_ANT:
case MONS_QUEEN_ANT:
//if ((damage_taken > 2 && one_chance_in(3) ) || one_chance_in(20) )
//{
if (sees)
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " stings ");
if (attacker == defender)
strcat(info, mons_pronoun(attacker->type,
PRONOUN_REFLEXIVE));
else
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
strcat(info, ".");
mpr(info);
}
poison_monster(defender, false);
//}
break;
if (--atk->hit_dice <= 0)
atk->hit_points = -1;
// enum does not match comment 14jan2000 {dlb}
case MONS_SCORPION: // snake
case MONS_BROWN_SNAKE:
case MONS_BLACK_SNAKE:
case MONS_YELLOW_SNAKE:
case MONS_GOLD_MIMIC:
case MONS_WEAPON_MIMIC:
case MONS_ARMOUR_MIMIC:
case MONS_SCROLL_MIMIC:
case MONS_POTION_MIMIC:
if (one_chance_in(20) || (damage_taken > 2 && one_chance_in(4)))
poison_monster(defender, false);
break;
if (needs_message)
mprf("%s %s engulfed in a cloud of spores!",
defender->name(DESC_CAP_THE).c_str(),
defender->conj_verb("are").c_str());
}
case MONS_SHADOW_DRAGON:
case MONS_SPECTRAL_THING:
if (coinflip())
break;
// intentional fall-through
case MONS_WIGHT:
case MONS_WRAITH:
case MONS_SOUL_EATER:
case MONS_SHADOW_FIEND:
case MONS_SPECTRAL_WARRIOR:
case MONS_ORANGE_RAT:
case MONS_ANCIENT_LICH:
case MONS_LICH:
case MONS_BORIS:
if (mons_res_negative_energy( defender ))
break;
if (one_chance_in(10)
|| (damage_done > 2 && one_chance_in(3)))
{
defender->confuse( 1 + random2( 3 + atk->hit_dice ) );
}
break;
if (defender->hit_points >= defender->max_hit_points)
defender->hit_points = defender->max_hit_points;
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;
}
}
if (defender->hit_points < 1 || defender->hit_dice < 1)
{
monster_die(defender, KILL_MON, monster_attacking);
return true;
}
}
break;
// enum does not match comment 14jan2000 {dlb}
case MONS_WORM: // giant wasp
break;
case MONS_SIMULACRUM_SMALL:
case MONS_SIMULACRUM_LARGE:
specdam = 0;
if (mons_res_cold(defender) == 0)
specdam = roll_dice( 1, 4 );
else if (mons_res_cold(defender) < 0)
specdam = roll_dice( 2, 4 );
if (specdam && sees)
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " freezes ");
if (attacker == defender)
strcat(info, mons_pronoun(attacker->type,
PRONOUN_REFLEXIVE));
else
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
strcat(info, ".");
mpr(info);
}
void melee_attack::mons_perform_attack_rounds()
{
const int nrounds = atk->type == MONS_HYDRA? atk->number : 4;
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "%s gets %d attacks",
attacker->name(DESC_CAP_THE).c_str(),
nrounds);
#endif
case MONS_ICE_DEVIL:
case MONS_ICE_BEAST:
case MONS_FREEZING_WRAITH:
case MONS_ICE_FIEND:
case MONS_WHITE_IMP:
case MONS_AZURE_JELLY:
case MONS_ANTAEUS:
specdam = 0;
if (mons_res_cold(defender) == 0)
{
specdam = attacker->hit_dice + random2(attacker->hit_dice * 2);
}
else if (mons_res_cold(defender) < 0)
{
specdam = random2(attacker->hit_dice * 3) + (attacker->hit_dice * 2);
}
// Monsters hitting themselves get just one round.
if (attack_number > 0 && attacker == defender)
break;
const mon_attack_def attk = mons_attack_spec(atk, attack_number);
if (attk.type == AT_NONE)
break;
if (specdam && sees)
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " freezes ");
if (attacker == defender)
strcat(info, mons_pronoun(attacker->type,
PRONOUN_REFLEXIVE));
else
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
strcat(info, ".");
mpr(info);
}
damage_taken += specdam;
break;
damage_done = 0;
mons_set_weapon(attk);
to_hit = mons_to_hit();
case MONS_ELECTRIC_GOLEM:
if (mons_flies(defender) == 0 && mons_res_elec(defender) == 0)
{
specdam = attacker->hit_dice + random2(attacker->hit_dice * 2);
}
final_attack_delay = mons_attk_delay();
if (damage_brand == SPWPN_SPEED)
final_attack_delay = final_attack_delay / 2 + 1;
if (specdam && sees)
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " shocks ");
if (attacker == defender)
strcat(info, mons_pronoun(attacker->type,
PRONOUN_REFLEXIVE));
else
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
strcat(info, ".");
mpr(info);
}
damage_taken += specdam;
break;
mons_lose_attack_energy(atk, final_attack_delay, attack_number);
// heh heh {dlb}
if (heal_monster(attacker, random2(damage_taken), true))
simple_monster_message(attacker, " is healed.");
break;
if (attacker != defender)
{
if (attack_shield_blocked(true))
{
shield_blocked = true;
perceived_attack = true;
this_round_hit = did_hit = true;
if (specdam)
{
if (sees)
{
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " burns ");
if (attacker == defender)
strcat(info, mons_pronoun(attacker->type,
PRONOUN_REFLEXIVE));
else
strcat(info, ptr_monam(defender, DESC_NOCAP_THE ));
if (damage_done > 0)
{
mons_announce_hit(attk);
check_defender_train_armour();
case SPWPN_FREEZING:
specdam = 0;
// Monsters attacking themselves don't get attack flavour, the
// message sequences look too weird.
if (attacker != defender)
mons_apply_attack_flavour(attk);
if (!special_damage_message.empty())
mprf("%s", special_damage_message.c_str());
defender->hurt(attacker, damage_done + special_damage);
if (!defender->alive() || attacker == defender)
return;
strcpy(info, ptr_monam(attacker, DESC_CAP_THE));
strcat(info, " freezes ");
if (attacker == defender)
strcat(info, mons_pronoun(attacker->type,
PRONOUN_REFLEXIVE));
else
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
if (special_damage > 0)
defender->hurt(attacker, special_damage);
}
}
}
if (specdam < 3)
strcat(info, ".");
else if (specdam < 7)
strcat(info, "!");
else
strcat(info, "!!");
bool melee_attack::mons_perform_attack()
{
if (attacker != defender && mons_self_destructs())
return (did_hit = perceived_attack = true);
case SPWPN_HOLY_WRATH:
if (attacker->type == MONS_PLAYER_GHOST)
break;
specdam = 0;
switch (mons_holiness(defender))
{
case MH_HOLY:
// I would think that it would do zero damage {dlb}
damage_taken -= 5 + random2(5);
break;
mons_perform_attack_rounds();
return (did_hit);
}
case MH_DEMONIC:
specdam += 1 + (random2(damage_taken) * 15) / 10;
break;
if (defender->atype() == ACT_PLAYER)
{
interrupt_activity(AI_MONSTER_ATTACKS, atk);
// if a friend wants to help, they can attack <monster_attacking>
if (you.pet_target == MHITNOT)
you.pet_target = monster_index(atk);
}
}
case SPWPN_ELECTROCUTION:
//if ( attacker->type == MONS_PLAYER_GHOST ) break;
if (mons_flies(defender) > 0 || mons_res_elec(defender) > 0)
break;
int melee_attack::mons_to_hit()
{
int mhit = 16 + atk->hit_dice;
if (water_attack)
mhit += 5;
specdam += 10 + random2(15);
//mitm[attacker->inv[hand_used]].plus2 --;
}
break;
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);
}
}
case SPWPN_ORC_SLAYING:
if (mons_species(defender->type) == MONS_ORC)
hurt_monster(defender, 1 + random2(damage_taken));
break;
// 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);
//case 7: // protection
case SPWPN_DRAINING:
if (!mons_res_negative_energy( defender )
&& (one_chance_in(30)
|| (damage_taken > 5 && coinflip())))
{
simple_monster_message(defender, " is drained");
if (one_chance_in(5))
defender->hit_dice--;
defender->max_hit_points -= 2 + random2(3);
defender->hit_points -= 2 + random2(3);
if (defender->hit_points >= defender->max_hit_points)
defender->hit_points = defender->max_hit_points;
if (defender->hit_points < 1
|| defender->hit_dice < 1)
{
monster_die(defender, KILL_MON, monster_attacking);
return true;
}
specdam = 1 + (random2(damage_taken) / 2);
}
break;
case SPWPN_SPEED:
wpn_speed = (wpn_speed + 1) / 2;
break;
case SPWPN_VORPAL:
specdam += 1 + (random2(damage_taken) / 2);
break;
case SPWPN_VAMPIRES_TOOTH:
case SPWPN_VAMPIRICISM:
specdam = 0; // note does no extra damage
if (mons_res_negative_energy( defender ))
break;
if (one_chance_in(5))
break;
// heh heh {dlb}
if (heal_monster(attacker, 1 + random2(damage_taken), true))
simple_monster_message(attacker, " is healed.");
break;
case SPWPN_DISRUPTION:
if (attacker->type == MONS_PLAYER_GHOST)
break;
specdam = 0;
if (mons_holiness(defender) == MH_UNDEAD
&& !one_chance_in(3))
{
simple_monster_message(defender, " shudders.");
specdam += random2avg(1 + (3 * damage_taken), 3);
}
break;
case SPWPN_DISTORTION:
if (one_chance_in(3))
{
if (mons_near(defender)
&& player_monster_visible(defender))
{
strcpy(info, "Space bends around ");
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
strcat(info, ".");
mpr(info);
}
specdam += 1 + random2avg(7, 2);
break;
}
if (one_chance_in(3))
{
if (mons_near(defender)
&& player_monster_visible(defender))
{
strcpy(info, "Space warps horribly around ");
strcat(info, ptr_monam(defender, DESC_NOCAP_THE));
strcat(info, "!");
mpr(info);
}
// For tutorials, check if the player is fighting with something unsuitable
tutorial_weapon_check(attk.weapon);
if (one_chance_in(3))
{
monster_blink(defender);
break;
}
// Lose attack energy for attacking with a weapon. The monster has already lost
// the base attack cost by this point.
static void mons_lose_attack_energy(monsters *attacker, int wpn_speed,
int which_attack)
{
// Monsters lose energy only for the first two weapon attacks; subsequent
// hits are free.
if (which_attack > 1)
return;
// speed adjustment for weapon using monsters
if (wpn_speed > 0)
{
// only get one third penalty/bonus for second weapons.
if (which_attack > 0)
wpn_speed = (20 + wpn_speed) / 3;
if (coinflip())
{
monster_die(defender, KILL_RESET, monster_attacking);
break;
}
break;
}
}
} // end of if special weapon
void monster_attack(int monster_attacking)
{
monsters *attacker = &menv[monster_attacking];
if (mons_friendly(attacker) && !mons_is_confused(attacker))
return;
if (damage_taken > 0)
{
hurt_monster(defender, damage_taken);
if (defender->hit_points < 1)
{
monster_die(defender, KILL_MON, monster_attacking);
return true;
}
}
mons_lose_attack_energy(attacker, wpn_speed, runthru);
} // end of for runthru
bool monsters_fight(int monster_attacking, int monster_attacked)
{
monsters *attacker = &menv[monster_attacking];
monsters *defender = &menv[monster_attacked];
virtual void hurt(actor *attacker, int amount) = 0;
virtual void heal(int amount, bool max_too = false) = 0;
virtual void banish() = 0;
virtual void blink() = 0;
virtual void teleport(bool right_now = false, bool abyss_shift = false) = 0;
virtual void poison(actor *attacker, int amount = 1) = 0;
virtual void paralyse(int strength) = 0;
virtual void slow_down(int strength) = 0;
virtual void confuse(int strength) = 0;
virtual void rot(actor *attacker, int rotlevel, int immediate_rot) = 0;
virtual void expose_to_element(beam_type element, int strength = 0) = 0;
virtual void drain_stat(int stat, int amount) { }
virtual bool wearing_light_armour(bool = true) const { return (true); }
virtual int skill(skill_type sk, bool skill_bump = false) const
{
return (0);
}
virtual void exercise(skill_type sk, int qty) { }
virtual int stat_hp() const = 0;
virtual int stat_maxhp() const = 0;
virtual int armour_class() const = 0;
virtual int melee_evasion(const actor *attacker) const = 0;
virtual int shield_bonus() const = 0;
virtual int shield_block_penalty() const = 0;
virtual int shield_bypass_ability(int tohit) const = 0;
virtual void shield_block_succeeded() { }
virtual int mons_species() const = 0;
virtual int holiness() const = 0;
virtual int res_fire() const = 0;
virtual int res_cold() const = 0;
virtual int res_elec() const = 0;
virtual int res_poison() const = 0;
virtual int res_negative_energy() const = 0;
virtual bool levitates() const = 0;
virtual bool paralysed() const = 0;
virtual bool confused() const = 0;
virtual bool asleep() const { return (false); }
virtual void god_conduct(int thing_done, int level) { }
virtual bool incapacitated() const
{
return paralysed() || confused();
}
virtual int holy_aura() const
{
return (0);
}
virtual int warding() const
{
return (0);
}
virtual bool visible() const
{
return (true);
}
actor_type atype() const { return ACT_PLAYER; }
int hunger_level() const { return hunger_state; }
void make_hungry(int nutrition, bool silent = true);
void poison(actor *agent, int amount = 1);
void paralyse(int str);
void slow_down(int str);
void confuse(int strength);
void rot(actor *agent, int rotlevel, int immed_rot);
void heal(int amount, bool max_too = false);
void hurt(actor *agent, int amount);
int holy_aura() const;
int warding() const;
int mons_species() const;
int holiness() const;
int res_fire() const;
int res_cold() const;
int res_elec() const;
int res_poison() const;
int res_negative_energy() const;
bool levitates() const;
bool paralysed() const;
bool confused() const;
int armour_class() const;
int melee_evasion(const actor *attacker) const;
int stat_hp() const { return hp; }
int stat_maxhp() const { return hp_max; }
int shield_bonus() const;
int shield_block_penalty() const;
int shield_bypass_ability(int tohit) const;
void shield_block_succeeded();
bool wearing_light_armour(bool with_skill = true) const;
void exercise(skill_type skill, int qty);
int skill(skill_type skill, bool skill_bump = false) const;
int mons_species() const;
int holiness() const;
int res_fire() const;
int res_cold() const;
int res_elec() const;
int res_poison() const;
int res_negative_energy() const;
bool levitates() const;
bool paralysed() const;
bool confused() const;
bool asleep() const;
int holy_aura() const;
int armour_class() const;
int melee_evasion(const actor *attacker) const;
void poison(actor *agent, int amount = 1);
void paralyse(int str);
void slow_down(int str);
void confuse(int strength);
void rot(actor *agent, int rotlevel, int immed_rot);
void hurt(actor *agent, int amount);
void heal(int amount, bool max_too = false);
void blink();
void teleport(bool right_now = false, bool abyss_shift = false);
int stat_hp() const { return hit_points; }
int stat_maxhp() const { return max_hit_points; }
int shield_bonus() const;
int shield_block_penalty() const;
int shield_bypass_ability(int tohit) const;
enum mon_attack_type
{
AT_NONE,
AT_HIT, // including weapon attacks
AT_BITE,
AT_STING,
AT_SPORE,
AT_TOUCH,
AT_ENGULF,
AT_CLAW,
AT_TAIL_SLAP
};
enum mon_attack_flavour
{
AF_PLAIN,
AF_ACID,
AF_BLINK,
AF_COLD,
AF_CONFUSE,
AF_DISEASE,
AF_DRAIN_DEX,
AF_DRAIN_STR,
AF_DRAIN_XP,
AF_ELEC,
AF_FIRE,
AF_HUNGER,
AF_MUTATE,
AF_BAD_MUTATE,
AF_PARALYSE,
AF_POISON,
AF_POISON_NASTY,
AF_POISON_MEDIUM,
AF_POISON_STRONG,
AF_POISON_STR,
AF_ROT,
AF_VAMPIRIC,
AF_KLOWN
};