summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/fight.cc
diff options
context:
space:
mode:
authorzelgadis <zelgadis@c06c8d41-db1a-0410-9941-cceddc491573>2008-12-10 11:59:16 +0000
committerzelgadis <zelgadis@c06c8d41-db1a-0410-9941-cceddc491573>2008-12-10 11:59:16 +0000
commit5e333ce7bdf88c826dfc0a8a8fa0cbba8b110167 (patch)
tree0b2046da98e1c80e489a6de62f2187a05d705bb6 /crawl-ref/source/fight.cc
parenta9561bf49af71b98db45679d7489d483af7c2266 (diff)
downloadcrawl-ref-5e333ce7bdf88c826dfc0a8a8fa0cbba8b110167.tar.gz
crawl-ref-5e333ce7bdf88c826dfc0a8a8fa0cbba8b110167.zip
Changed weights of the brands a chaos weapon might simulate and the weight of
the various chaos effects, to make chaos brand less powerful. Added berserk and miscast effects for chaos effects, plus chaos weapons will occasionally give a message-only miscast effect if it would otherwise have done nothing. Added several effects that have a chance of happening to an attacker every time it uses a chaos brand/AF_CHAOS: dropping through temporary a shaft, having the stairs move out from under them, the weapon making a loud noise. Monsters killed by a chaos weapon/AF_CHAOS have a chance of immediately turning into a zombie (assuming the monster didn't leave a corpse; chaos effects don't (yet) use up corpses). git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7804 c06c8d41-db1a-0410-9941-cceddc491573
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);