summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crawl-ref/source/defines.h2
-rw-r--r--crawl-ref/source/direct.cc6
-rw-r--r--crawl-ref/source/enum.h5
-rw-r--r--crawl-ref/source/externs.h22
-rw-r--r--crawl-ref/source/fight.cc17
-rw-r--r--crawl-ref/source/misc.cc26
-rw-r--r--crawl-ref/source/mon-data.h8
-rw-r--r--crawl-ref/source/mon-spll.h9
-rw-r--r--crawl-ref/source/mon-util.cc379
-rw-r--r--crawl-ref/source/monspeak.cc4
-rw-r--r--crawl-ref/source/monstuff.cc11
-rw-r--r--crawl-ref/source/mstuff2.cc69
-rw-r--r--crawl-ref/source/mstuff2.h1
-rw-r--r--crawl-ref/source/player.cc34
-rw-r--r--crawl-ref/source/stuff.cc8
-rw-r--r--crawl-ref/source/travel.h2
-rw-r--r--crawl-ref/source/view.cc3
17 files changed, 459 insertions, 147 deletions
diff --git a/crawl-ref/source/defines.h b/crawl-ref/source/defines.h
index cca786c9d0..4758daf6be 100644
--- a/crawl-ref/source/defines.h
+++ b/crawl-ref/source/defines.h
@@ -75,6 +75,8 @@
// max y-bound for level generation {dlb}
#define GYM 70
+#define INFINITE_DISTANCE 30000
+
// this is the size of the border around the playing area (see in_bounds())
#define BOUNDARY_BORDER 1
diff --git a/crawl-ref/source/direct.cc b/crawl-ref/source/direct.cc
index 1f745b1a78..ce747fd17d 100644
--- a/crawl-ref/source/direct.cc
+++ b/crawl-ref/source/direct.cc
@@ -1392,6 +1392,9 @@ static void describe_mons_enchantment(const monsters &mons,
// internally valid.
if (paralysed && (ench.ench == ENCH_SLOW || ench.ench == ENCH_HASTE))
return;
+
+ if (ench.ench == ENCH_HASTE && mons.has_ench(ENCH_BERSERK))
+ return;
strcpy(info, mons_pronoun(mons.type, PRONOUN_CAP));
@@ -1412,6 +1415,9 @@ static void describe_mons_enchantment(const monsters &mons,
case ENCH_SLOW:
strcat(info, " is moving slowly.");
break;
+ case ENCH_BERSERK:
+ strcat(info, " is berserk!");
+ break;
case ENCH_HASTE:
strcat(info, " is moving very quickly.");
break;
diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h
index 149ae24fd4..1f3338384a 100644
--- a/crawl-ref/source/enum.h
+++ b/crawl-ref/source/enum.h
@@ -1194,6 +1194,7 @@ enum enchant_type
ENCH_PARALYSIS, // 20
ENCH_SICK,
ENCH_SLEEPY, // Monster can't wake until this wears off.
+ ENCH_FATIGUE, // Post-berserk fatigue.
NUM_ENCHANTMENTS
};
@@ -2300,7 +2301,8 @@ enum mon_attack_flavour
AF_ROT,
AF_VAMPIRIC,
AF_KLOWN,
- AF_DISTORT
+ AF_DISTORT,
+ AF_RAGE
};
enum mon_attitude_type
@@ -2581,6 +2583,7 @@ enum mon_spellbook_type
MST_DRAC_CALLER,
MST_DRAC_SHIFTER,
MST_CURSE_TOE,
+ MST_RUPERT,
NUM_MSTYPES,
MST_NO_SPELLS = 250
};
diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h
index 0560a0e2be..cb4e1f6735 100644
--- a/crawl-ref/source/externs.h
+++ b/crawl-ref/source/externs.h
@@ -142,6 +142,7 @@ public:
// (statues have only indirect attacks).
virtual bool cannot_fight() const = 0;
virtual void attacking(actor *other) = 0;
+ virtual bool can_go_berserk() const = 0;
virtual void go_berserk(bool intentional) = 0;
virtual void hurt(actor *attacker, int amount) = 0;
virtual void heal(int amount, bool max_too = false) = 0;
@@ -238,6 +239,8 @@ struct coord_def
{
set(0, 0);
}
+
+ int distance_from(const coord_def &b) const;
bool operator == (const coord_def &other) const {
return x == other.x && y == other.y;
@@ -746,6 +749,8 @@ public:
bool cannot_fight() const;
void attacking(actor *other);
+ bool can_go_berserk() const;
+ bool can_go_berserk(bool verbose) const;
void go_berserk(bool intentional);
void banish(const std::string &who = "");
void blink();
@@ -907,16 +912,22 @@ public:
public:
kill_category kill_alignment() const;
+
+ int foe_distance() const;
+ bool needs_berserk(bool check_spells = true) const;
- bool has_ench(enchant_type ench,
- enchant_type ench2 = ENCH_NONE) const;
+ bool has_ench(enchant_type ench) const;
+ bool has_ench(enchant_type ench, enchant_type ench2) const;
mon_enchant get_ench(enchant_type ench,
enchant_type ench2 = ENCH_NONE) const;
- bool add_ench(const mon_enchant &);
- void update_ench(const mon_enchant &);
- bool del_ench(enchant_type ench, bool quiet = false);
+ bool add_ench(const mon_enchant &);
+ void update_ench(const mon_enchant &);
+ bool del_ench(enchant_type ench, bool quiet = false);
+
+ void scale_hp(int num, int den);
void lose_ench_levels(const mon_enchant &e, int levels);
+ void add_enchantment_effect(const mon_enchant &me, bool quiet = false);
void remove_enchantment_effect(const mon_enchant &me, bool quiet = false);
void apply_enchantments(int speed);
void apply_enchantment(mon_enchant me, int speed);
@@ -963,6 +974,7 @@ public:
int skill(skill_type skill, bool skill_bump = false) const;
void attacking(actor *other);
+ bool can_go_berserk() const;
void go_berserk(bool intentional);
void banish(const std::string &who = "");
void expose_to_element(beam_type element, int strength = 0);
diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc
index 4ccd1cb207..a8de265865 100644
--- a/crawl-ref/source/fight.cc
+++ b/crawl-ref/source/fight.cc
@@ -2494,6 +2494,10 @@ int melee_attack::mons_calc_damage(const mon_attack_def &attk)
damage_max += attk.damage;
damage += 1 + random2(attk.damage);
+ // Berserk monsters get bonus damage.
+ if (atk->has_ench(ENCH_BERSERK))
+ damage = damage * 3 / 2;
+
if (water_attack)
damage *= 2;
@@ -2887,6 +2891,19 @@ void melee_attack::mons_apply_attack_flavour(const mon_attack_def &attk)
case AF_DISTORT:
distortion_affects_defender();
break;
+
+ case AF_RAGE:
+ if (!one_chance_in(3) || !defender->can_go_berserk())
+ break;
+
+ if (needs_message)
+ mprf("%s %s %s!",
+ attacker->name(DESC_CAP_THE).c_str(),
+ attacker->conj_verb("infuriate").c_str(),
+ defender->name(DESC_NOCAP_THE).c_str());
+
+ defender->go_berserk(false);
+ break;
}
}
diff --git a/crawl-ref/source/misc.cc b/crawl-ref/source/misc.cc
index 1369f7ed76..9fb999beee 100644
--- a/crawl-ref/source/misc.cc
+++ b/crawl-ref/source/misc.cc
@@ -1678,32 +1678,12 @@ void weird_colours(unsigned char coll, char wc[30])
bool go_berserk(bool intentional)
{
- if (you.berserker)
- {
- if (intentional)
- mpr("You're already berserk!");
- // or else you won't notice -- no message here.
- return false;
- }
-
- if (you.exhausted)
- {
- if (intentional)
- mpr("You're too exhausted to go berserk.");
- // or else they won't notice -- no message here
- return false;
- }
-
- if (you.is_undead)
- {
- if (intentional)
- mpr("You cannot raise a blood rage in your lifeless body.");
- // or else you won't notice -- no message here
- return false;
- }
+ if (!you.can_go_berserk(intentional))
+ return (false);
if (Options.tutorial_left)
Options.tut_berserk_counter++;
+
mpr("A red film seems to cover your vision as you go berserk!");
mpr("You feel yourself moving faster!");
mpr("You feel mighty!");
diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h
index bf3d0b56a9..c400468b40 100644
--- a/crawl-ref/source/mon-data.h
+++ b/crawl-ref/source/mon-data.h
@@ -3221,7 +3221,7 @@
0, 20, MONS_HUMAN, MONS_HUMAN, MH_NATURAL, -5,
{ {AT_HIT, AF_PLAIN, 21}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 16, 0, 0, 123 },
- 0, 10, 10, 7, MST_WIZARD_IV, CE_CONTAMINATED, Z_SMALL, S_SHOUT, I_NORMAL,
+ 0, 10, 10, 7, MST_RUPERT, CE_CONTAMINATED, Z_SMALL, S_SHOUT, I_NORMAL,
MONUSE_WEAPONS_ARMOUR, SIZE_MEDIUM
}
,
@@ -3891,12 +3891,12 @@
{
MONS_MOTH_OF_WRATH, 'y', BROWN, "moth of wrath",
- M_FLIES,
+ M_FLIES | M_SPECIAL_ABILITY,
MR_NO_FLAGS,
0, 10, MONS_MOTH_OF_WRATH, MONS_MOTH_OF_WRATH, MH_NATURAL, -3,
- { {AT_BITE, AF_PLAIN, 25}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
+ { {AT_BITE, AF_RAGE, 25}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 9, 3, 5, 0 },
- 0, 10, 12, 7, MST_NO_SPELLS, CE_CLEAN, Z_SMALL, S_SHOUT, I_NORMAL,
+ 0, 10, 12, 7, MST_NO_SPELLS, CE_CLEAN, Z_SMALL, S_SHOUT, I_HIGH,
MONUSE_NOTHING, SIZE_MEDIUM
}
,
diff --git a/crawl-ref/source/mon-spll.h b/crawl-ref/source/mon-spll.h
index 8498265dde..4131fb405e 100644
--- a/crawl-ref/source/mon-spll.h
+++ b/crawl-ref/source/mon-spll.h
@@ -784,4 +784,13 @@
MS_SUMMON_UNDEAD,
MS_TORMENT },
+ // Crusader theme.
+ { MST_RUPERT,
+ MS_PARALYSIS,
+ MS_CONFUSE,
+ MS_BERSERK_RAGE,
+ MS_NO_SPELL,
+ MS_NO_SPELL,
+ MS_BERSERK_RAGE },
+
#endif
diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc
index e8cc6205fd..420c536589 100644
--- a/crawl-ref/source/mon-util.cc
+++ b/crawl-ref/source/mon-util.cc
@@ -1865,75 +1865,75 @@ bool ms_requires_tracer(int monspell)
switch(monspell)
{
- case MS_BANISHMENT:
- case MS_COLD_BOLT:
- case MS_ICE_BOLT:
- case MS_SHOCK:
- case MS_MAGMA:
- case MS_CONFUSE:
- case MS_CRYSTAL_SPEAR:
- case MS_DISINTEGRATE:
- case MS_ENERGY_BOLT:
- case MS_FIRE_BOLT:
- case MS_FIREBALL:
- case MS_FLAME:
- case MS_FROST:
- case MS_HELLFIRE:
- case MS_IRON_BOLT:
- case MS_LIGHTNING_BOLT:
- case MS_MARSH_GAS:
- case MS_MIASMA:
- case MS_METAL_SPLINTERS:
- case MS_MMISSILE:
- case MS_NEGATIVE_BOLT:
- case MS_ORB_ENERGY:
- case MS_PAIN:
- case MS_PARALYSIS:
- case MS_POISON_BLAST:
- case MS_POISON_ARROW:
- case MS_POISON_SPLASH:
- case MS_QUICKSILVER_BOLT:
- case MS_SLOW:
- case MS_STEAM_BALL:
- case MS_STICKY_FLAME:
- case MS_STING:
- case MS_STONE_ARROW:
- case MS_TELEPORT_OTHER:
- case MS_VENOM_BOLT:
- requires = true;
- break;
-
- // self-niceties and direct effects
- case MS_ANIMATE_DEAD:
- case MS_BLINK:
- case MS_BRAIN_FEED:
- case MS_DIG:
- case MS_FAKE_RAKSHASA_SUMMON:
- case MS_HASTE:
- case MS_HEAL:
- case MS_HELLFIRE_BURST:
- case MS_INVIS:
- case MS_LEVEL_SUMMON:
- case MS_MUTATION:
- case MS_SMITE:
- case MS_SUMMON_BEAST:
- case MS_SUMMON_DEMON_LESSER:
- case MS_SUMMON_DEMON:
- case MS_SUMMON_DEMON_GREATER:
- case MS_SUMMON_UFETUBUS:
- case MS_TELEPORT:
- case MS_TORMENT:
- case MS_SUMMON_SMALL_MAMMALS:
- case MS_VAMPIRE_SUMMON:
- case MS_CANTRIP:
+ case MS_BANISHMENT:
+ case MS_COLD_BOLT:
+ case MS_ICE_BOLT:
+ case MS_SHOCK:
+ case MS_MAGMA:
+ case MS_CONFUSE:
+ case MS_CRYSTAL_SPEAR:
+ case MS_DISINTEGRATE:
+ case MS_ENERGY_BOLT:
+ case MS_FIRE_BOLT:
+ case MS_FIREBALL:
+ case MS_FLAME:
+ case MS_FROST:
+ case MS_HELLFIRE:
+ case MS_IRON_BOLT:
+ case MS_LIGHTNING_BOLT:
+ case MS_MARSH_GAS:
+ case MS_MIASMA:
+ case MS_METAL_SPLINTERS:
+ case MS_MMISSILE:
+ case MS_NEGATIVE_BOLT:
+ case MS_ORB_ENERGY:
+ case MS_PAIN:
+ case MS_PARALYSIS:
+ case MS_POISON_BLAST:
+ case MS_POISON_ARROW:
+ case MS_POISON_SPLASH:
+ case MS_QUICKSILVER_BOLT:
+ case MS_SLOW:
+ case MS_STEAM_BALL:
+ case MS_STICKY_FLAME:
+ case MS_STING:
+ case MS_STONE_ARROW:
+ case MS_TELEPORT_OTHER:
+ case MS_VENOM_BOLT:
+ requires = true;
+ break;
- // meaningless, but sure, why not?
- case MS_NO_SPELL:
- break;
+ // self-niceties and direct effects
+ case MS_ANIMATE_DEAD:
+ case MS_BLINK:
+ case MS_BRAIN_FEED:
+ case MS_DIG:
+ case MS_FAKE_RAKSHASA_SUMMON:
+ case MS_HASTE:
+ case MS_HEAL:
+ case MS_HELLFIRE_BURST:
+ case MS_INVIS:
+ case MS_LEVEL_SUMMON:
+ case MS_MUTATION:
+ case MS_SMITE:
+ case MS_SUMMON_BEAST:
+ case MS_SUMMON_DEMON_LESSER:
+ case MS_SUMMON_DEMON:
+ case MS_SUMMON_DEMON_GREATER:
+ case MS_SUMMON_UFETUBUS:
+ case MS_TELEPORT:
+ case MS_TORMENT:
+ case MS_SUMMON_SMALL_MAMMALS:
+ case MS_VAMPIRE_SUMMON:
+ case MS_CANTRIP:
+ case MS_BERSERK_RAGE:
- default:
- break;
+ // meaningless, but sure, why not?
+ case MS_NO_SPELL:
+ break;
+ default:
+ break;
}
return (requires);
@@ -1949,39 +1949,39 @@ bool ms_direct_nasty(int monspell)
switch(monspell)
{
- // self-niceties/summonings
- case MS_ANIMATE_DEAD:
- case MS_BLINK:
- case MS_DIG:
- case MS_FAKE_RAKSHASA_SUMMON:
- case MS_HASTE:
- case MS_HEAL:
- case MS_INVIS:
- case MS_LEVEL_SUMMON:
- case MS_SUMMON_BEAST:
- case MS_SUMMON_DEMON_LESSER:
- case MS_SUMMON_DEMON:
- case MS_SUMMON_DEMON_GREATER:
- case MS_SUMMON_UFETUBUS:
- case MS_TELEPORT:
- case MS_SUMMON_SMALL_MAMMALS:
- case MS_VAMPIRE_SUMMON:
- nasty = false;
- break;
+ // self-niceties/summonings
+ case MS_ANIMATE_DEAD:
+ case MS_BLINK:
+ case MS_DIG:
+ case MS_FAKE_RAKSHASA_SUMMON:
+ case MS_HASTE:
+ case MS_HEAL:
+ case MS_INVIS:
+ case MS_LEVEL_SUMMON:
+ case MS_SUMMON_BEAST:
+ case MS_SUMMON_DEMON_LESSER:
+ case MS_SUMMON_DEMON:
+ case MS_SUMMON_DEMON_GREATER:
+ case MS_SUMMON_UFETUBUS:
+ case MS_TELEPORT:
+ case MS_SUMMON_SMALL_MAMMALS:
+ case MS_VAMPIRE_SUMMON:
+ nasty = false;
+ break;
- case MS_BRAIN_FEED:
- case MS_HELLFIRE_BURST:
- case MS_MUTATION:
- case MS_SMITE:
- case MS_TORMENT:
+ case MS_BRAIN_FEED:
+ case MS_HELLFIRE_BURST:
+ case MS_MUTATION:
+ case MS_SMITE:
+ case MS_TORMENT:
+ case MS_BERSERK_RAGE:
// meaningless, but sure, why not?
- case MS_NO_SPELL:
- break;
-
- default:
- break;
+ case MS_NO_SPELL:
+ break;
+ default:
+ break;
}
return (nasty);
@@ -2072,12 +2072,16 @@ bool ms_waste_of_time( struct monsters *mon, int monspell )
int intel, est_magic_resist, power, diff;
struct monsters *targ;
-
// Eventually, we'll probably want to be able to have monsters
// learn which of their elemental bolts were resisted and have those
// handled here as well. -- bwr
switch (monspell)
{
+ case MS_BERSERK_RAGE:
+ if (!mon->needs_berserk(false))
+ ret = true;
+ break;
+
case MS_HASTE:
if (mon->has_ench(ENCH_HASTE))
ret = true;
@@ -2620,6 +2624,24 @@ void monsters::attacking(actor * /* other */)
void monsters::go_berserk(bool /* intentional */)
{
+ if (holiness() != MH_NATURAL || has_ench(ENCH_FATIGUE))
+ return;
+
+ if (has_ench(ENCH_SLOW))
+ {
+ del_ench(ENCH_SLOW);
+ simple_monster_message(
+ this,
+ make_stringf(" shakes off %s lethargy.",
+ name(DESC_NOCAP_YOUR).c_str()).c_str());
+ }
+ del_ench(ENCH_HASTE);
+ del_ench(ENCH_FATIGUE);
+
+ const int duration = 20 + random2avg(20, 2);
+ add_ench(mon_enchant(ENCH_BERSERK, duration));
+ add_ench(mon_enchant(ENCH_HASTE, duration));
+ simple_monster_message( this, " goes berserk!" );
}
void monsters::expose_to_element(beam_type, int)
@@ -3043,19 +3065,20 @@ void monsters::load_spells(int book)
}
}
-bool monsters::has_ench(enchant_type ench,
- enchant_type ench2) const
+bool monsters::has_ench(enchant_type ench) const
+{
+ return (enchantments.find(ench) != enchantments.end());
+}
+
+bool monsters::has_ench(enchant_type ench, enchant_type ench2) const
{
if (ench2 == ENCH_NONE)
ench2 = ench;
for (int i = ench; i <= ench2; ++i)
{
- if (enchantments.find( static_cast<enchant_type>(i) ) !=
- enchantments.end())
- {
+ if (has_ench(static_cast<enchant_type>(i)))
return (true);
- }
}
return (false);
}
@@ -3083,8 +3106,10 @@ void monsters::update_ench(const mon_enchant &ench)
{
mon_enchant_list::iterator i = enchantments.find(ench);
if (i != enchantments.end())
+ {
enchantments.erase(i);
- enchantments.insert(ench);
+ enchantments.insert(ench);
+ }
}
}
@@ -3095,8 +3120,12 @@ bool monsters::add_ench(const mon_enchant &ench)
return (false);
mon_enchant_list::iterator i = enchantments.find(ench);
+ bool new_enchantment = false;
if (i == enchantments.end())
+ {
+ new_enchantment = true;
enchantments.insert(ench);
+ }
else
{
mon_enchant new_ench = *i + ench;
@@ -3104,24 +3133,39 @@ bool monsters::add_ench(const mon_enchant &ench)
enchantments.insert(new_ench);
}
+ if (new_enchantment)
+ add_enchantment_effect(ench);
+
+ return (true);
+}
+
+void monsters::add_enchantment_effect(const mon_enchant &ench, bool)
+{
// check for slow/haste
- if (ench.ench == ENCH_HASTE)
+ switch (ench.ench)
{
+ case ENCH_BERSERK:
+ // Inflate hp.
+ scale_hp(3, 2);
+ break;
+
+ case ENCH_HASTE:
if (speed >= 100)
speed = 100 + ((speed - 100) * 2);
else
speed *= 2;
- }
+ break;
- if (ench.ench == ENCH_SLOW)
- {
+ case ENCH_SLOW:
if (speed >= 100)
speed = 100 + ((speed - 100) / 2);
else
speed /= 2;
- }
+ break;
- return (true);
+ default:
+ break;
+ }
}
bool monsters::del_ench(enchant_type ench, bool quiet)
@@ -3141,6 +3185,10 @@ void monsters::remove_enchantment_effect(const mon_enchant &me, bool quiet)
{
switch (me.ench)
{
+ case ENCH_BERSERK:
+ scale_hp(2, 3);
+ break;
+
case ENCH_HASTE:
if (speed >= 100)
speed = 100 + ((speed - 100) / 2);
@@ -3290,11 +3338,23 @@ void monsters::timeout_enchantments(int levels)
lose_ench_levels(*i, levels);
break;
+ case ENCH_BERSERK:
+ del_ench(i->ench);
+ del_ench(ENCH_HASTE);
+ break;
+
+ case ENCH_FATIGUE:
+ del_ench(i->ench);
+ del_ench(ENCH_SLOW);
+ break;
+
case ENCH_TP:
+ del_ench(i->ench);
teleport(true);
break;
case ENCH_CONFUSION:
+ del_ench(i->ench);
blink();
break;
@@ -3317,13 +3377,38 @@ void monsters::apply_enchantment(mon_enchant me, int spd)
{
switch (me.ench)
{
+ case ENCH_BERSERK:
+ lose_ench_levels(me, 1);
+ if (me.degree <= 1)
+ {
+ simple_monster_message(this, " is no longer berserk.");
+ del_ench(ENCH_HASTE);
+ const int duration = random_range(7, 13);
+ add_ench(mon_enchant(ENCH_FATIGUE, duration));
+ add_ench(mon_enchant(ENCH_SLOW, duration));
+ }
+ break;
+
+ case ENCH_FATIGUE:
+ lose_ench_levels(me, 1);
+ if (me.degree <= 1)
+ {
+ simple_monster_message(this, " looks more energetic.");
+ del_ench(ENCH_SLOW);
+ }
+ break;
+
case ENCH_SLOW:
- if (random2(250) <= mod_speed( hit_dice + 10, spd ))
+ if (me.degree > 0)
+ lose_ench_levels(me, 1);
+ else if (random2(250) <= mod_speed( hit_dice + 10, spd ))
del_ench(me.ench);
break;
case ENCH_HASTE:
- if (random2(1000) < mod_speed( 25, spd ))
+ if (me.degree > 0)
+ lose_ench_levels(me, 1);
+ else if (random2(1000) < mod_speed( 25, spd ))
del_ench(ENCH_HASTE);
break;
@@ -3608,6 +3693,19 @@ void monsters::apply_enchantments(int spd)
}
}
+void monsters::scale_hp(int num, int den)
+{
+ hit_points = hit_points * num / den;
+ max_hit_points = max_hit_points * num / den;
+
+ if (hit_points < 1)
+ hit_points = 1;
+ if (max_hit_points < 1)
+ max_hit_points = 1;
+ if (hit_points > max_hit_points)
+ hit_points = max_hit_points;
+}
+
kill_category monsters::kill_alignment() const
{
return (attitude == ATT_FRIENDLY? KC_FRIENDLY : KC_OTHER);
@@ -3704,6 +3802,63 @@ void monsters::check_speed()
}
}
+int monsters::foe_distance() const
+{
+ // early out -- no foe!
+ if (foe == MHITNOT)
+ return (INFINITE_DISTANCE);
+
+ if (foe == MHITYOU)
+ return grid_distance(x, y, you.x_pos, you.y_pos);
+
+ // must be a monster
+ const monsters *my_foe = &menv[foe];
+ if (my_foe->alive())
+ return grid_distance(x, y, my_foe->x, my_foe->y);
+
+ return (INFINITE_DISTANCE);
+}
+
+bool monsters::can_go_berserk() const
+{
+ if (holiness() != MH_NATURAL)
+ return (false);
+
+ if (has_ench(ENCH_FATIGUE) || has_ench(ENCH_BERSERK))
+ return (false);
+
+ // If we have no melee attack, going berserk is pointless.
+ const mon_attack_def attk = mons_attack_spec(this, 0);
+ if (attk.type == AT_NONE || attk.damage == 0)
+ return (false);
+
+ return (true);
+}
+
+bool monsters::needs_berserk(bool check_spells) const
+{
+ if (!can_go_berserk())
+ return (false);
+
+ if (has_ench(ENCH_HASTE) || has_ench(ENCH_TP))
+ return (false);
+
+ if (foe_distance() > 3)
+ return (false);
+
+ if (check_spells)
+ {
+ for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
+ {
+ const int spell = spells[i];
+ if (spell != MS_NO_SPELL && spell != MS_BERSERK_RAGE)
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
/////////////////////////////////////////////////////////////////////////
// mon_enchant
@@ -3712,7 +3867,7 @@ static const char *enchant_names[] =
"none", "slow", "haste", "fear", "conf", "inv", "pois", "bers",
"rot", "summon", "abj", "backlit", "charm", "fire",
"gloshifter", "shifter", "tp", "wary", "submerged",
- "short lived", "paralysis", "sick", "sleep", "bug"
+ "short lived", "paralysis", "sick", "sleep", "fatigue", "bug"
};
const char *mons_enchantment_name(enchant_type ench)
diff --git a/crawl-ref/source/monspeak.cc b/crawl-ref/source/monspeak.cc
index e473939cea..948061e48e 100644
--- a/crawl-ref/source/monspeak.cc
+++ b/crawl-ref/source/monspeak.cc
@@ -224,6 +224,10 @@ bool mons_speaks(const monsters *monster)
if (monster->has_ench(ENCH_CHARM))
return false;
+ // berserk monsters just want your hide.
+ if (monster->has_ench(ENCH_BERSERK))
+ return false;
+
if (monster->has_ench(ENCH_CONFUSION))
{
if (mons_holiness( monster ) == MH_DEMONIC
diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc
index fd0cf3f637..cccb5c4036 100644
--- a/crawl-ref/source/monstuff.cc
+++ b/crawl-ref/source/monstuff.cc
@@ -2233,6 +2233,11 @@ static bool handle_special_ability(struct monsters *monster, bolt & beem)
break;
+ case MONS_MOTH_OF_WRATH:
+ if (one_chance_in(3))
+ used = moth_incite_monsters(monster);
+ break;
+
case MONS_PIT_FIEND:
if (one_chance_in(3))
break;
@@ -3568,7 +3573,11 @@ static void handle_monster_move(int i, monsters *monster)
beem.target_y = monster->target_y;
if (monster->behaviour != BEH_SLEEP
- && monster->behaviour != BEH_WANDER)
+ && monster->behaviour != BEH_WANDER
+
+ // berserking monsters are limited to running up and
+ // hitting their foes.
+ && !monster->has_ench(ENCH_BERSERK))
{
// prevents unfriendlies from nuking you from offscreen.
// How nice!
diff --git a/crawl-ref/source/mstuff2.cc b/crawl-ref/source/mstuff2.cc
index 9eafee035f..618cbdeb28 100644
--- a/crawl-ref/source/mstuff2.cc
+++ b/crawl-ref/source/mstuff2.cc
@@ -366,6 +366,10 @@ void mons_cast(struct monsters *monster, struct bolt &pbolt, int spell_cast)
switch (spell_cast)
{
+ case MS_BERSERK_RAGE:
+ monster->go_berserk(true);
+ return;
+
case MS_SUMMON_SMALL_MAMMALS:
case MS_VAMPIRE_SUMMON:
if ( spell_cast == MS_SUMMON_SMALL_MAMMALS )
@@ -719,6 +723,8 @@ void setup_mons_cast(struct monsters *monster, struct bolt &pbolt, int spell_cas
case MS_TORMENT:
case MS_SUMMON_DEMON_GREATER:
case MS_CANTRIP:
+ case MS_BERSERK_RAGE:
+ case MS_MIGHT:
return;
default:
break;
@@ -1946,3 +1952,66 @@ bool orange_statue_effects(monsters *mons)
return (false);
}
+
+static bool make_monster_angry(const monsters *mon, monsters *targ)
+{
+ if (mon->attitude != targ->attitude)
+ return (false);
+
+ // targ is guaranteed to have a foe (needs_berserk checks this).
+ // Now targ needs to be closer to *its* foe than mon is (otherwise
+ // mon might be in the way).
+
+ coord_def victim;
+ if (targ->foe == MHITYOU)
+ victim = you.pos();
+ else if (targ->foe != MHITNOT)
+ {
+ const monsters *vmons = &menv[targ->foe];
+ if (!vmons->alive())
+ return (false);
+ victim = vmons->pos();
+ }
+ else
+ {
+ // Should be impossible. needs_berserk should find this case.
+ ASSERT(false);
+ return (false);
+ }
+
+ // If mon may be blocking targ from its victim, don't try.
+ if (victim.distance_from(targ->pos()) > victim.distance_from(mon->pos()))
+ return (false);
+
+ const bool need_message = mons_near(mon) && player_monster_visible(mon);
+ if (need_message)
+ mprf("%s goads %s on!", mon->name(DESC_CAP_THE).c_str(),
+ targ->name(DESC_NOCAP_THE).c_str());
+
+ targ->go_berserk(false);
+
+ return (true);
+}
+
+bool moth_incite_monsters(const monsters *mon)
+{
+ int goaded = 0;
+ for (int i = 0; i < MAX_MONSTERS; ++i)
+ {
+ monsters *targ = &menv[i];
+ if (targ == mon || !targ->alive() || !targ->needs_berserk())
+ continue;
+
+ if (mon->pos().distance_from(targ->pos()) > 3)
+ continue;
+
+ // Cannot goad other moths of wrath!
+ if (targ->type == MONS_MOTH_OF_WRATH)
+ continue;
+
+ if (make_monster_angry(mon, targ) && !one_chance_in(3 * ++goaded))
+ return (true);
+ }
+
+ return (false);
+}
diff --git a/crawl-ref/source/mstuff2.h b/crawl-ref/source/mstuff2.h
index aedb2cdcd0..d584406762 100644
--- a/crawl-ref/source/mstuff2.h
+++ b/crawl-ref/source/mstuff2.h
@@ -102,5 +102,6 @@ void throw_type(int lnchClass, int lnchType, int wepClass, int wepType,
bool orange_statue_effects(monsters *mons);
bool silver_statue_effects(monsters *mons);
+bool moth_incite_monsters(const monsters *mon);
#endif
diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc
index abf7f089e0..836b7d877f 100644
--- a/crawl-ref/source/player.cc
+++ b/crawl-ref/source/player.cc
@@ -4934,6 +4934,40 @@ void player::go_berserk(bool intentional)
::go_berserk(intentional);
}
+bool player::can_go_berserk() const
+{
+ return can_go_berserk(false);
+}
+
+bool player::can_go_berserk(bool verbose) const
+{
+ if (you.berserker)
+ {
+ if (verbose)
+ mpr("You're already berserk!");
+ // or else you won't notice -- no message here.
+ return (false);
+ }
+
+ if (you.exhausted)
+ {
+ if (verbose)
+ mpr("You're too exhausted to go berserk.");
+ // or else they won't notice -- no message here
+ return (false);
+ }
+
+ if (you.is_undead)
+ {
+ if (verbose)
+ mpr("You cannot raise a blood rage in your lifeless body.");
+ // or else you won't notice -- no message here
+ return (false);
+ }
+
+ return (true);
+}
+
void player::god_conduct(int thing_done, int level)
{
::did_god_conduct(thing_done, level);
diff --git a/crawl-ref/source/stuff.cc b/crawl-ref/source/stuff.cc
index 74c9250bdd..5e1898cd00 100644
--- a/crawl-ref/source/stuff.cc
+++ b/crawl-ref/source/stuff.cc
@@ -1090,3 +1090,11 @@ void zap_los_monsters()
}
}
}
+
+//////////////////////////////////////////////////////////////////////////
+// coord_def
+int coord_def::distance_from(const coord_def &other) const
+{
+ return (grid_distance(x, y, other.x, other.y));
+}
+
diff --git a/crawl-ref/source/travel.h b/crawl-ref/source/travel.h
index d60311b07c..53ba304588 100644
--- a/crawl-ref/source/travel.h
+++ b/crawl-ref/source/travel.h
@@ -482,7 +482,7 @@ protected:
protected:
static const int UNFOUND_DIST = -30000;
- static const int INFINITE_DIST = 30000;
+ static const int INFINITE_DIST = INFINITE_DISTANCE;
protected:
run_mode_type runmode;
diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc
index 9e1c719c5f..601d6d8cdf 100644
--- a/crawl-ref/source/view.cc
+++ b/crawl-ref/source/view.cc
@@ -522,6 +522,9 @@ static int get_mons_colour(const monsters *mons)
{
int col = mons->colour;
+ if (mons->has_ench(ENCH_BERSERK))
+ col = RED;
+
if (mons_friendly(mons))
{
col |= COLFLAG_FRIENDLY_MONSTER;