summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/fight.cc
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source/fight.cc')
-rw-r--r--crawl-ref/source/fight.cc605
1 files changed, 578 insertions, 27 deletions
diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc
index 6f44529f7e..5722449471 100644
--- a/crawl-ref/source/fight.cc
+++ b/crawl-ref/source/fight.cc
@@ -10,6 +10,7 @@
#include "fight.h"
#include <string.h>
+#include <sstream>
#include <stdlib.h>
#include <stdio.h>
#include <algorithm>
@@ -24,6 +25,7 @@
#include "cloud.h"
#include "debug.h"
#include "delay.h"
+#include "dgnevent.h"
#include "effects.h"
#include "food.h"
#include "it_use2.h"
@@ -53,6 +55,7 @@
#include "spl-util.h"
#include "stuff.h"
#include "transfor.h"
+#include "traps.h"
#include "tutorial.h"
#include "view.h"
#include "xom.h"
@@ -332,7 +335,8 @@ melee_attack::melee_attack(actor *attk, actor *defn,
no_damage_message(), special_damage_message(), unarmed_attack(),
shield(NULL), defender_shield(NULL),
heavy_armour_penalty(0), can_do_unarmed(false),
- water_attack(false)
+ water_attack(false), miscast_level(-1), miscast_type(SPTYP_NONE),
+ miscast_target(NULL)
{
init_attack();
}
@@ -385,6 +389,10 @@ void melee_attack::init_attack()
if (defender && defender->submerged())
unarmed_ok = false;
+
+ miscast_level = -1;
+ miscast_type = SPTYP_NONE;
+ miscast_target = NULL;
}
std::string melee_attack::actor_name(const actor *a,
@@ -448,6 +456,40 @@ std::string melee_attack::def_name(description_level_type desc) const
return actor_name(defender, desc, defender_visible, defender_invisible);
}
+std::string melee_attack::wep_name(description_level_type desc,
+ unsigned long ignore_flags) const
+{
+ ASSERT(weapon != NULL);
+
+ if (attacker->atype() == ACT_PLAYER)
+ return weapon->name(desc, false, false, false, false, ignore_flags);
+
+ std::string name;
+ bool possessive = false;
+ if (desc == DESC_CAP_YOUR)
+ {
+ desc = DESC_CAP_THE;
+ possessive = true;
+ }
+ else if (desc == DESC_NOCAP_YOUR)
+ {
+ desc = DESC_NOCAP_THE;
+ possessive = true;
+ }
+
+ if (possessive)
+ {
+ name = atk_name(desc);
+ name += "'s ";
+ // Proper English-language possessive.
+ name = replace_all(name, "s's ", "s' ");
+ }
+
+ name += weapon->name(desc, false, false, false, false, ignore_flags);
+
+ return (name);
+}
+
bool melee_attack::is_water_attack(const actor *attk,
const actor *defn) const
{
@@ -500,6 +542,8 @@ bool melee_attack::attack()
identify_mimic(atk);
identify_mimic(def);
+ const coord_def def_pos = defender->pos();
+
if (attacker->atype() == ACT_PLAYER && attacker != defender)
{
if (stop_attack_prompt(def, false, false))
@@ -536,6 +580,9 @@ bool melee_attack::attack()
else
xom_is_stimulated(14);
+ if (damage_brand == SPWPN_CHAOS)
+ chaos_affects_attacker();
+
return (false);
}
// Non-fumbled self-attacks due to confusion are still pretty
@@ -593,6 +640,9 @@ bool melee_attack::attack()
feat_name.c_str());
}
}
+ if (damage_brand == SPWPN_CHAOS)
+ chaos_affects_attacker();
+
return (true);
}
@@ -620,6 +670,14 @@ bool melee_attack::attack()
}
}
+ if (attacker->atype() == ACT_PLAYER)
+ {
+ if (damage_brand == SPWPN_CHAOS)
+ chaos_affects_attacker();
+
+ do_miscast();
+ }
+
enable_attack_conducts(conducts);
return retval;
@@ -856,6 +914,8 @@ bool melee_attack::player_attack()
// Returns true to end the attack round.
bool melee_attack::player_aux_unarmed()
{
+ unwind_var<int> save_brand(damage_brand);
+
damage_brand = SPWPN_NORMAL;
int uattack = UNAT_NO_ATTACK;
bool simple_miss_message = false;
@@ -1836,7 +1896,12 @@ bool melee_attack::player_monattk_hit_effects(bool mondied)
}
if (needs_message && !special_damage_message.empty())
+ {
mprf("%s", special_damage_message.c_str());
+ // Don't do a message-only miscast right after a special damage
+ if (miscast_level == 0)
+ miscast_level = -1;
+ }
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Special damage to %s: %d",
@@ -2126,6 +2191,8 @@ enum chaos_type
CHAOS_POLY,
CHAOS_POLY_UP,
CHAOS_MAKE_SHIFTER,
+ CHAOS_MISCAST,
+ CHAOS_RAGE,
CHAOS_HEAL,
CHAOS_HASTE,
CHAOS_INVIS,
@@ -2148,11 +2215,14 @@ void melee_attack::chaos_affects_defender()
&& mons_clonable(def, true);
const bool can_poly = is_shifter || (defender->can_safely_mutate()
&& !immune);
+ const bool can_rage = defender->can_go_berserk();
- int clone_chance = can_clone ? 1 : 0;
- int poly_chance = can_poly ? 1 : 0;
- int poly_up_chance = can_poly && mon ? 1 : 0;
- int shifter_chance = can_poly && mon ? 1 : 0;
+ int clone_chance = can_clone ? 1 : 0;
+ int poly_chance = can_poly ? 1 : 0;
+ int poly_up_chance = can_poly && mon ? 1 : 0;
+ int shifter_chance = can_poly && mon ? 1 : 0;
+ int rage_chance = can_rage ? 10 : 0;
+ int miscast_chance = 10;
if (is_chaotic)
{
@@ -2174,6 +2244,17 @@ void melee_attack::chaos_affects_defender()
}
}
+ // A chaos self-attack increased the chance of certain effects,
+ // due to a short-circuit/feedback/resonance/whatever.
+ if (attacker == defender)
+ {
+ clone_chance *= 2;
+ poly_chance *= 2;
+ poly_up_chance *= 2;
+ shifter_chance *= 2;
+ miscast_chance *= 2;
+ }
+
// NOTE: Must appear in exact same order as in chaos_type enumeration.
int probs[NUM_CHAOS_TYPES] =
{
@@ -2181,14 +2262,16 @@ void melee_attack::chaos_affects_defender()
poly_chance, // CHAOS_POLY
poly_up_chance, // CHAOS_POLY_UP
shifter_chance, // CHAOS_MAKE_SHIFTER
+ miscast_chance, // CHAOS_MISCAST
+ rage_chance, // CHAOS_RAGE
- 5, // CHAOS_HEAL
- 5, // CHAOS_HASTE
- 5, // CHAOS_INVIS
+ 10, // CHAOS_HEAL
+ 10, // CHAOS_HASTE
+ 10, // CHAOS_INVIS
- 15, // CHAOS_SLOW
- 15, // CHAOS_PARA
- 15, // CHAOS_PETRIFY
+ 10, // CHAOS_SLOW
+ 10, // CHAOS_PARA
+ 10, // CHAOS_PETRIFY
};
bolt beam;
@@ -2199,12 +2282,17 @@ void melee_attack::chaos_affects_defender()
{
case CHAOS_CLONE:
{
- ASSERT(can_clone);
+ ASSERT(can_clone && clone_chance > 0);
ASSERT(defender->atype() == ACT_MONSTER);
- int clone_idx = clone_mons(def, false, &obvious_effect);
+ int clone_idx = clone_mons(def, true, &obvious_effect);
if (clone_idx != NON_MONSTER)
{
+ if (obvious_effect)
+ special_damage_message =
+ make_stringf("%s is duplicated!",
+ def_name(DESC_NOCAP_THE).c_str());
+
monsters &clone(menv[clone_idx]);
// The player shouldn't get new permanent followers from cloning.
if (clone.attitude == ATT_FRIENDLY && !mons_is_summoned(&clone))
@@ -2214,12 +2302,12 @@ void melee_attack::chaos_affects_defender()
}
case CHAOS_POLY:
- ASSERT(can_poly);
+ ASSERT(can_poly && poly_chance > 0);
beam.flavour = BEAM_POLYMORPH;
break;
case CHAOS_POLY_UP:
- ASSERT(can_poly);
+ ASSERT(can_poly && poly_up_chance > 0);
ASSERT(defender->atype() == ACT_MONSTER);
obvious_effect = you.can_see(defender);
@@ -2227,7 +2315,7 @@ void melee_attack::chaos_affects_defender()
break;
case CHAOS_MAKE_SHIFTER:
- ASSERT(can_poly);
+ ASSERT(can_poly && shifter_chance > 0);
ASSERT(!is_shifter);
ASSERT(defender->atype() == ACT_MONSTER);
@@ -2238,6 +2326,34 @@ void melee_attack::chaos_affects_defender()
monster_polymorph(def, RANDOM_MONSTER, PPT_SAME, true);
break;
+ case CHAOS_MISCAST:
+ {
+ int level = defender->get_experience_level();
+
+ // At level == 27 there's a 20.3% chance of a level 3 miscast.
+ int level1_chance = level;
+ int level2_chance = std::max( 0, level - 7);
+ int level3_chance = std::max( 0, level - 15);
+
+ level = random_choose_weighted(
+ level1_chance, 1,
+ level2_chance, 2,
+ level3_chance, 3,
+ 0);
+
+ miscast_level = level;
+ miscast_type = SPTYP_RANDOM;
+ miscast_target = coinflip() ? attacker : defender;
+
+ break;
+ }
+
+ case CHAOS_RAGE:
+ ASSERT(can_rage && rage_chance > 0);
+ defender->go_berserk(false);
+ obvious_effect = you.can_see(defender);
+ break;
+
case CHAOS_HEAL:
beam.flavour = BEAM_HEALING;
break;
@@ -2280,7 +2396,7 @@ void melee_attack::chaos_affects_defender()
if (weapon && you.can_see(attacker))
{
- beam.name = weapon->name(DESC_NOCAP_A);
+ beam.name = wep_name(DESC_NOCAP_YOUR);
beam.item = weapon;
}
else
@@ -2292,7 +2408,7 @@ void melee_attack::chaos_affects_defender()
beam.beam_source =
(attacker->atype() == ACT_PLAYER) ? MHITYOU : monster_index(atk);
- beam.source = attacker->pos();
+ beam.source = defender->pos();
beam.target = defender->pos();
beam.damage = dice_def(damage_done + special_damage + aux_damage, 1);
@@ -2309,13 +2425,387 @@ void melee_attack::chaos_affects_defender()
obvious_effect = false;
}
+static bool _move_stairs(const actor* attacker, const actor* defender)
+{
+ const coord_def orig_pos = attacker->pos();
+ const dungeon_feature_type stair_feat = grd(orig_pos);
+
+ if (grid_stair_direction(stair_feat) == CMD_NO_CMD)
+ return (false);
+
+ // Moving shops is too much trouble, and anyways the player can't
+ // use them to escape.
+ if (stair_feat == DNGN_ENTER_SHOP)
+ return false;
+
+ const bool stair_is_marker = env.markers.find(orig_pos, MAT_ANY);
+
+ coord_def dest(-1, -1);
+ // Prefer to send it under the defender.
+ if (defender->alive() && defender->pos() != attacker->pos())
+ {
+ dungeon_feature_type feat = grd(defender->pos());
+ if (!grid_destroys_items(feat) && !grid_is_solid(feat)
+ && !grid_is_water(feat) && !grid_is_trap(feat, true))
+ {
+ dest = defender->pos();
+ }
+
+ // Don't try to swap two markers.
+ if (stair_is_marker && env.markers.find(defender->pos(), MAT_ANY))
+ dest.set(-1, -1);
+ }
+
+ if (!in_bounds(dest))
+ {
+ radius_iterator ri(attacker->pos(), 1, true, false, true);
+
+ int squares = 0;
+ for (; ri; ++ri)
+ {
+ // Don't try to swap two markers.
+ if (stair_is_marker && env.markers.find(*ri, MAT_ANY))
+ continue;
+
+ dungeon_feature_type feat = grd(defender->pos());
+ if (!grid_destroys_items(feat) && !grid_is_solid(feat)
+ && !grid_is_water(feat) && !grid_is_trap(feat, true))
+ {
+ if (one_chance_in(++squares))
+ dest = *ri;
+ }
+ }
+ }
+
+ if (!in_bounds(dest))
+ return (false);
+
+ ASSERT(dest != orig_pos);
+
+ const bool dest_is_marker = env.markers.find(dest, MAT_ANY);
+ const dungeon_feature_type dest_feat = grd(defender->pos());
+
+ ASSERT(!(dest_is_marker && stair_is_marker));
+
+ dungeon_terrain_changed(orig_pos, dest_feat);
+ dungeon_terrain_changed(dest, stair_feat);
+
+ if (stair_is_marker)
+ {
+ env.markers.move(orig_pos, dest);
+ dungeon_events.move_listeners(orig_pos, dest);
+ }
+ else if (dest_is_marker)
+ {
+ env.markers.move(dest, orig_pos);
+ dungeon_events.move_listeners(dest, orig_pos);
+ }
+
+ if (!see_grid(orig_pos) && !see_grid(dest))
+ return (true);
+
+ std::string orig_actor, dest_actor;
+ if (orig_pos == you.pos())
+ orig_actor = "you";
+ else if (mgrd(orig_pos) != NON_MONSTER)
+ {
+ monsters &mon(menv[mgrd(orig_pos)]);
+
+ if (you.can_see(&mon))
+ orig_actor = mon.name(DESC_NOCAP_THE);
+ }
+
+ if (dest == you.pos())
+ dest_actor = "you";
+ else if (mgrd(dest) != NON_MONSTER)
+ {
+ monsters &mon(menv[mgrd(dest)]);
+
+ if (you.can_see(&mon))
+ dest_actor = mon.name(DESC_NOCAP_THE);
+ }
+
+ std::string stair_name =
+ feature_description(dest, false,
+ see_grid(orig_pos) ? DESC_CAP_THE : DESC_CAP_A,
+ false);
+ std::string prep;
+
+ if (grid_stair_direction(stair_feat) == CMD_GO_DOWNSTAIRS
+ && (stair_name.find("stair") || grid_is_escape_hatch(stair_feat)))
+ {
+ prep = "beneath";
+ }
+ else if (grid_is_escape_hatch(stair_feat))
+ prep = "above";
+ else
+ prep = "beside";
+
+ std::ostringstream str;
+ str << stair_name << " ";
+ if (see_grid(orig_pos) && !see_grid(dest))
+ {
+ str << "suddenly disappears";
+ if (!orig_actor.empty())
+ str << " from " << prep << " " << orig_actor;
+ }
+ else if (!see_grid(orig_pos) && see_grid(dest))
+ {
+ str << "suddenly appears";
+ if (!dest_actor.empty())
+ str << " " << prep << " " << dest_actor;
+ }
+ else
+ {
+ str << "moves";
+ if (!orig_actor.empty())
+ str << " from " << prep << " " << orig_actor;
+ if (!dest_actor.empty())
+ str << " to " << prep << " " << dest_actor;
+ }
+ str << "!";
+ mpr(str.str().c_str());
+
+ return (true);
+}
+
+#define DID_AFFECT() \
+{ \
+ if (miscast_level == 0) \
+ miscast_level = -1; \
+ return; \
+}
+
void melee_attack::chaos_affects_attacker()
{
+ if (miscast_level >= 1 || !attacker->alive())
+ return;
+
+ // Move stairs out from under the attacker.
+ if (one_chance_in(100) && _move_stairs(attacker, defender))
+ DID_AFFECT();
+
+ // Dump attacker or items under attacker to another level.
+ if (is_valid_shaft_level()
+ && (attacker->will_trigger_shaft()
+ || igrd(attacker->pos()) != NON_ITEM)
+ && one_chance_in(1000))
+ {
+ (void) attacker->do_shaft();
+ DID_AFFECT();
+ }
+
+ // Make a loud noise.
+ if (weapon && player_can_hear(attacker->pos())
+ && one_chance_in(1000))
+ {
+ std::string msg = wep_name(DESC_CAP_YOUR);
+ msg += " twangs alarmingly!";
+
+ if (!you.can_see(attacker))
+ msg = "You hear a loud twang.";
+
+ noisy(15, attacker->pos(), msg.c_str());
+ DID_AFFECT();
+ }
+
+ return;
+}
+
+static void _find_remains(monsters* mon, int &corpse_class, int &corpse,
+ int &last_item, std::vector<int> items)
+{
+ for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
+ {
+ const int idx = mon->inv[i];
+
+ if (idx == NON_ITEM)
+ continue;
+
+ item_def &item(mitm[idx]);
+
+ if (!is_valid_item(item) || item.pos != mon->pos())
+ continue;
+
+ items.push_back(idx);
+ }
+
+ corpse = NON_ITEM;
+ last_item = NON_ITEM;
+
+ corpse_class = mons_species(mon->type);
+
+ if (corpse_class == MONS_DRACONIAN)
+ corpse_class = draco_subspecies(mon);
+
+ if (mon->has_ench(ENCH_SHAPESHIFTER))
+ corpse_class = MONS_SHAPESHIFTER;
+ else if (mon->has_ench(ENCH_GLOWING_SHAPESHIFTER))
+ corpse_class = MONS_GLOWING_SHAPESHIFTER;
+
+ // Stop at first non-matching corpse, since the freshest corpse will
+ // be at the top of the stack.
+ for (stack_iterator si(mon->pos()); si; ++si)
+ {
+ if (si->base_type == OBJ_CORPSES && si->sub_type == CORPSE_BODY)
+ {
+ if (si->plus != corpse_class)
+ break;
+
+ // It should have just been dropped.
+ if (corpse_freshness(*si) != FRESHEST_CORPSE)
+ break;
+
+ // If there was an opportunity to butcher it then it can't
+ // have just been dropped.
+ if (si->plus2 != 0)
+ break;
+
+ // It can't have been just made if it was picked up or
+ // dropped.
+ if (si->flags & (ISFLAG_DROPPED | ISFLAG_BEEN_IN_INV))
+ break;
+
+ // If it's a hydra the number of heads must match.
+ if ((int) mon->number != si->props[MONSTER_NUMBER].get_long())
+ break;
+
+ // Got it!
+ corpse = si.link();
+ break;
+ }
+ else
+ {
+ // Last item which we're sure belonded to the monster.
+ for (unsigned int i = 0; i < items.size(); i++)
+ {
+ if (items[i] == si.link())
+ last_item = si.link();
+ }
+ }
+ }
+}
+
+static bool _make_zombie(monsters* mon, int corpse_class, int corpse,
+ int last_item)
+{
+ // If the monster dropped a corpse then don't waste it by turning
+ // it into a zombie.
+ if (corpse != NON_ITEM || !mons_class_can_be_zombified(corpse_class))
+ return (false);
+
+ int idx = get_item_slot();
+ if (idx != NON_ITEM && last_item != NON_ITEM)
+ {
+ // Fake a corpse
+ item_def &corpse_item(mitm[idx]);
+ corpse_item.base_type = OBJ_CORPSES;
+ corpse_item.sub_type = CORPSE_BODY;
+ corpse_item.plus = corpse_class;
+ corpse_item.orig_monnum = mon->type + 1;
+ corpse_item.pos = mon->pos();
+ corpse_item.quantity = 1;
+ corpse_item.props[MONSTER_NUMBER] = short(mon->number);
+
+ // Insert it in the item stack right after the monster's
+ // last item, so it will be equipped with all the monster's
+ // items.
+ corpse_item.link = mitm[last_item].link;
+ mitm[last_item].link = idx;
+
+ if (animate_remains(mon->pos(), CORPSE_BODY, mon->behaviour,
+ mon->foe, mon->god, true, true))
+ {
+ if (you.can_see(mon))
+ simple_monster_message(mon,
+ " instantly turns into a zombie!");
+ else if (see_grid(mon->pos()))
+ mpr("A zombie appears out of nowhere!");
+ return (true);
+ }
+ }
+ return (false);
}
// NOTE: Isn't called if monster dies from poisoning caused by chaos.
-void melee_attack::chaos_killed_defender(monsters* def_copy)
+void melee_attack::chaos_killed_defender(monsters* mon)
{
+ ASSERT(mon->type != -1 && mon->type != MONS_PROGRAM_BUG);
+ ASSERT(in_bounds(mon->pos()));
+ ASSERT(!defender->alive());
+
+ if (!attacker->alive())
+ return;
+
+ int corpse_class, corpse, last_item;
+ std::vector<int> items;
+ _find_remains(mon, corpse_class, corpse, last_item, items);
+
+ if (one_chance_in(100) &&
+ _make_zombie(mon, corpse_class, corpse, last_item))
+ {
+ DID_AFFECT();
+ }
+}
+
+void melee_attack::do_miscast()
+{
+ if (miscast_level == -1)
+ return;
+
+ ASSERT(miscast_target != NULL);
+ ASSERT(miscast_level >= 0 && miscast_level <= 3);
+ ASSERT(count_bits(miscast_type) == 1);
+
+ if (!miscast_target->alive())
+ return;
+
+ const bool chaos_brand =
+ weapon && get_weapon_brand(*weapon) == SPWPN_CHAOS;
+
+ // If the miscast is happening on the attacker's side and is due to
+ // a chaos weapon then make smoke/sand/etc pour out of the weapon
+ // instead of the attacker's hands.
+ std::string hand_str;
+
+ std::string cause = atk_name(DESC_NOCAP_THE);
+ int source;
+
+ const int ignore_mask = ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES;
+
+ if (attacker->atype() == ACT_PLAYER)
+ {
+ source = NON_MONSTER;
+ if (chaos_brand)
+ {
+ cause = "a chaos effect from ";
+ // Ignore a lot of item flags to make cause as short as possible,
+ // so it will (hopefully) fit onto a single line in the death
+ // cause screen.
+ cause += wep_name(DESC_NOCAP_YOUR,
+ ignore_mask
+ | ISFLAG_COSMETIC_MASK | ISFLAG_RACIAL_MASK);
+
+ if (miscast_target == attacker)
+ hand_str = wep_name(DESC_PLAIN, ignore_mask);
+ }
+ }
+ else
+ {
+ source = attacker->mindex();
+
+ if (chaos_brand && miscast_target == attacker
+ && you.can_see(attacker))
+ {
+ hand_str = wep_name(DESC_PLAIN, ignore_mask);
+ }
+ }
+
+ MiscastEffect(miscast_target, source, (spschool_flag_type) miscast_type,
+ miscast_level, cause, NH_NEVER, hand_str, false);
+
+ // Don't do miscast twice for one attack.
+ miscast_level = -1;
}
// NOTE: random_chaos_brand() and random_chaos_attack_flavour() should
@@ -2324,11 +2814,19 @@ void melee_attack::chaos_killed_defender(monsters* def_copy)
// by the non-chaos brands/flavours they return.
int melee_attack::random_chaos_brand()
{
- int brands[] = {SPWPN_FLAMING, SPWPN_FREEZING, SPWPN_ELECTROCUTION,
- SPWPN_VENOM, SPWPN_DRAINING, SPWPN_VAMPIRICISM,
- SPWPN_PAIN, SPWPN_DISTORTION, SPWPN_CONFUSE,
- SPWPN_CHAOS, SPWPN_NORMAL};
- return (RANDOM_ELEMENT(brands));
+ return (random_choose_weighted(
+ 15, SPWPN_NORMAL,
+ 10, SPWPN_FLAMING,
+ 10, SPWPN_FREEZING,
+ 10, SPWPN_ELECTROCUTION,
+ 10, SPWPN_VENOM,
+ 10, SPWPN_CHAOS,
+ 5, SPWPN_VORPAL,
+ 5, SPWPN_DRAINING,
+ 5, SPWPN_VAMPIRICISM,
+ 2, SPWPN_CONFUSE,
+ 2, SPWPN_DISTORTION,
+ 0));
}
mon_attack_flavour melee_attack::random_chaos_attack_flavour()
@@ -2599,6 +3097,14 @@ bool melee_attack::apply_damage_brand()
break;
}
+ if (damage_brand == SPWPN_CHAOS && brand != SPWPN_CHAOS && !ret
+ && miscast_level == -1 && one_chance_in(20))
+ {
+ miscast_level = 0;
+ miscast_type = SPTYP_RANDOM;
+ miscast_target = coinflip() ? attacker : defender;
+ }
+
if (attacker->atype() == ACT_PLAYER && damage_brand == SPWPN_CHAOS)
{
// If your god objects to using chaos then it makes the
@@ -4154,8 +4660,9 @@ void melee_attack::mons_perform_attack_rounds()
mons_set_weapon(attk);
to_hit = mons_to_hit();
- const bool chaos_attack = attk.flavour == AF_CHAOS
- || damage_brand == SPWPN_CHAOS;
+ const bool chaos_attack = damage_brand == SPWPN_CHAOS
+ || (attk.flavour == AF_CHAOS
+ && attacker != defender);
// Make copy of monster before monster_die() resets it.
if (chaos_attack && defender->atype() == ACT_MONSTER && !def_copy)
@@ -4249,25 +4756,55 @@ void melee_attack::mons_perform_attack_rounds()
if (!special_damage_message.empty())
mprf("%s", special_damage_message.c_str());
+ // Defender banished, bail before chaos_killed_defender() is
+ // killed since the defender is still alive in the Abyss.
+ if (!defender->alive())
+ {
+ if (chaos_attack && attacker->alive())
+ chaos_affects_attacker();
+
+ do_miscast();
+
+ break;
+ }
+
defender->hurt(attacker, damage_done + special_damage);
if (!defender->alive())
{
if (chaos_attack && defender->atype() == ACT_MONSTER)
chaos_killed_defender(def_copy);
+
+ if (chaos_attack && attacker->alive())
+ chaos_affects_attacker();
+
+ do_miscast();
break;
}
// Yredelemnul's injury mirroring can kill the attacker.
- if (!attacker->alive() || attacker == defender)
+ // Also, bail if the monster is attacking itself without a weapon
+ // since intrinsic monster attack flavours aren't applied for
+ // self attacks.
+ if (!attacker->alive() || (attacker == defender && !weapon))
+ {
+ if (miscast_target == defender)
+ do_miscast();
break;
+ }
special_damage = 0;
special_damage_message.clear();
apply_damage_brand();
if (!special_damage_message.empty())
+ {
mprf("%s", special_damage_message.c_str());
+ // Don't do message-only miscasts along with a special
+ // damage message.
+ if (miscast_level == 0)
+ miscast_level = -1;
+ }
if (special_damage > 0)
defender->hurt(attacker, special_damage);
@@ -4276,12 +4813,26 @@ void melee_attack::mons_perform_attack_rounds()
{
if (chaos_attack && defender->atype() == ACT_MONSTER)
chaos_killed_defender(def_copy);
+
+ if (chaos_attack && attacker->alive())
+ chaos_affects_attacker();
+
+ do_miscast();
break;
}
+ if (chaos_attack && attacker->alive())
+ chaos_affects_attacker();
+
+ if (miscast_target == defender)
+ do_miscast();
+
// Yredelemnul's injury mirroring can kill the attacker.
if (!attacker->alive())
break;
+
+ if (miscast_target == attacker)
+ do_miscast();
}
item_def *weap = atk->mslot_item(MSLOT_WEAPON);