summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source')
-rw-r--r--crawl-ref/source/fight.cc605
-rw-r--r--crawl-ref/source/fight.h10
-rw-r--r--crawl-ref/source/itemname.cc1
-rw-r--r--crawl-ref/source/itemprop.cc8
-rw-r--r--crawl-ref/source/itemprop.h1
-rw-r--r--crawl-ref/source/monstuff.cc2
-rw-r--r--crawl-ref/source/monstuff.h2
-rw-r--r--crawl-ref/source/spl-cast.cc50
-rw-r--r--crawl-ref/source/spl-mis.h12
-rw-r--r--crawl-ref/source/stuff.h8
10 files changed, 651 insertions, 48 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);
diff --git a/crawl-ref/source/fight.h b/crawl-ref/source/fight.h
index 8df5ec790d..b96c6785cb 100644
--- a/crawl-ref/source/fight.h
+++ b/crawl-ref/source/fight.h
@@ -145,6 +145,12 @@ public:
// both attacker and defender are in water.
bool water_attack;
+ // Miscast to cause after special damage is done. If miscast_level == 0
+ // the miscast is discarded if special_damage_message isn't empty.
+ int miscast_level;
+ int miscast_type;
+ actor* miscast_target;
+
public:
melee_attack(actor *attacker, actor *defender,
bool allow_unarmed = true, int attack_num = -1);
@@ -177,6 +183,9 @@ private:
std::string atk_name(description_level_type desc) const;
std::string def_name(description_level_type desc) const;
+ std::string wep_name(description_level_type desc = DESC_NOCAP_YOUR,
+ unsigned long ignore_flags = ISFLAG_KNOW_CURSE
+ | ISFLAG_KNOW_PLUSES) const;
bool attack_shield_blocked(bool verbose);
bool apply_damage_brand();
@@ -203,6 +212,7 @@ private:
void chaos_affects_attacker();
void chaos_killed_defender(monsters* def_copy);
int random_chaos_brand();
+ void do_miscast();
private:
// Monster-attack specific stuff
diff --git a/crawl-ref/source/itemname.cc b/crawl-ref/source/itemname.cc
index 46bc2c6cd4..0f74bd14cb 100644
--- a/crawl-ref/source/itemname.cc
+++ b/crawl-ref/source/itemname.cc
@@ -965,6 +965,7 @@ std::string item_def::name_aux( description_level_type desc,
const bool __know_pluses =
!basename && !qualname && !dbname
+ && !testbits(ignore_flags, ISFLAG_KNOW_PLUSES)
&& (ident || item_ident(*this, ISFLAG_KNOW_PLUSES));
const bool know_brand =
diff --git a/crawl-ref/source/itemprop.cc b/crawl-ref/source/itemprop.cc
index e418f1dc7d..6df9597b0a 100644
--- a/crawl-ref/source/itemprop.cc
+++ b/crawl-ref/source/itemprop.cc
@@ -28,6 +28,7 @@
#include "it_use2.h"
#include "macro.h"
#include "mon-util.h"
+#include "monstuff.h"
#include "notes.h"
#include "player.h"
#include "quiver.h"
@@ -2218,6 +2219,13 @@ bool food_is_rotten( const item_def &item )
&& item.sub_type == FOOD_CHUNK);
}
+int corpse_freshness( const item_def &item )
+{
+ ASSERT(item.base_type == OBJ_CORPSES);
+ ASSERT(item.special <= FRESHEST_CORPSE);
+ return (item.special);
+}
+
// Returns true if item counts as a tool for tool size comparisons and msgs.
bool is_tool( const item_def &item )
{
diff --git a/crawl-ref/source/itemprop.h b/crawl-ref/source/itemprop.h
index 8b9a2b2457..9635d6b358 100644
--- a/crawl-ref/source/itemprop.h
+++ b/crawl-ref/source/itemprop.h
@@ -717,6 +717,7 @@ int food_value( const item_def &item );
int food_turns( const item_def &item );
bool can_cut_meat( const item_def &item );
bool food_is_rotten( const item_def &item );
+int corpse_freshness( const item_def &item );
// generic item property functions:
bool is_tool( const item_def &item );
diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc
index c727e0ce6c..cebd0170e1 100644
--- a/crawl-ref/source/monstuff.cc
+++ b/crawl-ref/source/monstuff.cc
@@ -349,7 +349,7 @@ static void _place_monster_corpse(const monsters *monster, bool silent,
mitm[o].plus = corpse_class;
mitm[o].plus2 = 0; // butcher work done
mitm[o].sub_type = CORPSE_BODY;
- mitm[o].special = 210; // rot time
+ mitm[o].special = FRESHEST_CORPSE; // rot time
mitm[o].colour = mons_class_colour(corpse_class);
mitm[o].quantity = 1;
mitm[o].props[MONSTER_NUMBER] = short(monster->number);
diff --git a/crawl-ref/source/monstuff.h b/crawl-ref/source/monstuff.h
index b48a11af5e..2d73b665aa 100644
--- a/crawl-ref/source/monstuff.h
+++ b/crawl-ref/source/monstuff.h
@@ -44,6 +44,8 @@ public:
}
};
+#define FRESHEST_CORPSE 210
+
#define YOU_KILL(x) ((x) == KILL_YOU || (x) == KILL_YOU_MISSILE \
|| (x) == KILL_YOU_CONF)
#define MON_KILL(x) ((x) == KILL_MON || (x) == KILL_MON_MISSILE)
diff --git a/crawl-ref/source/spl-cast.cc b/crawl-ref/source/spl-cast.cc
index 164a716382..0a51a49333 100644
--- a/crawl-ref/source/spl-cast.cc
+++ b/crawl-ref/source/spl-cast.cc
@@ -2188,11 +2188,13 @@ void exercise_spell( spell_type spell, bool spc, bool success )
MiscastEffect::MiscastEffect(actor* _target, int _source, spell_type _spell,
int _pow, int _fail, std::string _cause,
- nothing_happens_when_type _nothing_happens) :
+ nothing_happens_when_type _nothing_happens,
+ std::string _hand_str, bool _can_plural) :
target(_target), source(_source), cause(_cause), spell(_spell),
school(SPTYP_NONE), pow(_pow), fail(_fail), level(-1), kc(KC_NCATEGORIES),
kt(KILL_NONE), mon_target(NULL), mon_source(NULL),
- nothing_happens_when(_nothing_happens)
+ nothing_happens_when(_nothing_happens), hand_str(_hand_str),
+ can_plural_hand(_can_plural)
{
ASSERT(is_valid_spell(_spell));
unsigned int schools = get_spell_disciplines(_spell);
@@ -2206,11 +2208,13 @@ MiscastEffect::MiscastEffect(actor* _target, int _source, spell_type _spell,
MiscastEffect::MiscastEffect(actor* _target, int _source,
spschool_flag_type _school, int _level,
std::string _cause,
- nothing_happens_when_type _nothing_happens) :
+ nothing_happens_when_type _nothing_happens,
+ std::string _hand_str, bool _can_plural) :
target(_target), source(_source), cause(_cause), spell(SPELL_NO_SPELL),
school(_school), pow(-1), fail(-1), level(_level), kc(KC_NCATEGORIES),
kt(KILL_NONE), mon_target(NULL), mon_source(NULL),
- nothing_happens_when(_nothing_happens)
+ nothing_happens_when(_nothing_happens), hand_str(_hand_str),
+ can_plural_hand(_can_plural)
{
ASSERT(!_cause.empty());
ASSERT(count_bits(_school) == 1);
@@ -2224,11 +2228,13 @@ MiscastEffect::MiscastEffect(actor* _target, int _source,
MiscastEffect::MiscastEffect(actor* _target, int _source,
spschool_flag_type _school, int _pow, int _fail,
std::string _cause,
- nothing_happens_when_type _nothing_happens) :
+ nothing_happens_when_type _nothing_happens,
+ std::string _hand_str, bool _can_plural) :
target(_target), source(_source), cause(_cause), spell(SPELL_NO_SPELL),
school(_school), pow(_pow), fail(_fail), level(-1), kc(KC_NCATEGORIES),
kt(KILL_NONE), mon_target(NULL), mon_source(NULL),
- nothing_happens_when(_nothing_happens)
+ nothing_happens_when(_nothing_happens), hand_str(_hand_str),
+ can_plural_hand(_can_plural)
{
ASSERT(!_cause.empty());
ASSERT(count_bits(_school) == 1);
@@ -2356,6 +2362,10 @@ void MiscastEffect::init()
if (source == MELEE_MISCAST)
source_known = false;
+ if (hand_str.empty())
+ target->hand_name(true, &can_plural_hand);
+
+ // Explosion stuff.
beam.is_beam = false;
beam.is_explosion = true;
@@ -2561,8 +2571,19 @@ void MiscastEffect::do_msg(bool suppress_nothing_happnes)
return;
}
- msg = replace_all(msg, "@hand@", target->hand_name(false));
- msg = replace_all(msg, "@hands@", target->hand_name(true));
+ if (hand_str.empty())
+ {
+ msg = replace_all(msg, "@hand@", target->hand_name(false));
+ msg = replace_all(msg, "@hands@", target->hand_name(true));
+ }
+ else
+ {
+ msg = replace_all(msg, "@hand@", hand_str);
+ if (can_plural_hand)
+ msg = replace_all(msg, "@hands@", pluralise(hand_str));
+ else
+ msg = replace_all(msg, "@hands@", hand_str);
+ }
if (target->atype() == ACT_MONSTER)
msg = do_mon_str_replacements(msg, mon_target, S_SILENT);
@@ -2854,16 +2875,13 @@ void MiscastEffect::_conjuration(int severity)
}
static void _your_hands_glow(actor* target, std::string& you_msg,
- std::string& mon_msg_seen)
+ std::string& mon_msg_seen, bool pluralize)
{
you_msg = "Your @hands@ ";
mon_msg_seen = "@The_monster@'s @hands@ ";
// No message for invisible monsters.
- bool pluralized = true;
- target->hand_name(true, &pluralized);
-
- if (pluralized)
+ if (pluralize)
{
you_msg += "glow";
mon_msg_seen += "glow";
@@ -2885,7 +2903,7 @@ void MiscastEffect::_enchantment(int severity)
switch (random2(10))
{
case 0:
- _your_hands_glow(target, you_msg, mon_msg_seen);
+ _your_hands_glow(target, you_msg, mon_msg_seen, can_plural_hand);
break;
case 1:
you_msg = "The air around you crackles with energy!";
@@ -3685,7 +3703,7 @@ void MiscastEffect::_transmigration(int severity)
switch (random2(10))
{
case 0:
- _your_hands_glow(target, you_msg, mon_msg_seen);
+ _your_hands_glow(target, you_msg, mon_msg_seen, can_plural_hand);
break;
case 1:
you_msg = "The air around you crackles with energy!";
@@ -4251,7 +4269,7 @@ void MiscastEffect::_air(int severity)
bool pluralized = true;
target->hand_name(true, &pluralized);
- if (pluralized)
+ if (pluralized && hand_str.empty())
{
you_msg = "Sparks of electricity dance between your "
"@hands@.";
diff --git a/crawl-ref/source/spl-mis.h b/crawl-ref/source/spl-mis.h
index e9363e6597..781e999795 100644
--- a/crawl-ref/source/spl-mis.h
+++ b/crawl-ref/source/spl-mis.h
@@ -43,13 +43,16 @@ class MiscastEffect
public:
MiscastEffect(actor* _target, int _source, spell_type _spell, int _pow,
int _fail, std::string _cause = "",
- nothing_happens_when_type _nothing_happens = NH_DEFAULT);
+ nothing_happens_when_type _nothing_happens = NH_DEFAULT,
+ std::string _hand_str = "", bool _can_plural_hand = true);
MiscastEffect(actor* _target, int _source, spschool_flag_type _school,
int _level, std::string _cause,
- nothing_happens_when_type _nothing_happens = NH_DEFAULT);
+ nothing_happens_when_type _nothing_happens = NH_DEFAULT,
+ std::string _hand_str = "", bool _can_plural_hand = true);
MiscastEffect(actor* _target, int _source, spschool_flag_type _school,
int _pow, int _fail, std::string _cause,
- nothing_happens_when_type _nothing_happens = NH_DEFAULT);
+ nothing_happens_when_type _nothing_happens = NH_DEFAULT,
+ std::string _hand_str = "", bool _can_plural_hand = true);
void do_miscast();
@@ -75,6 +78,9 @@ private:
nothing_happens_when_type nothing_happens_when;
+ std::string hand_str;
+ bool can_plural_hand;
+
int kill_source;
actor* act_source;
diff --git a/crawl-ref/source/stuff.h b/crawl-ref/source/stuff.h
index 996809b4ab..47245e42f2 100644
--- a/crawl-ref/source/stuff.h
+++ b/crawl-ref/source/stuff.h
@@ -214,15 +214,21 @@ int choose_random_weighted(Iterator beg, const Iterator end)
{
ASSERT(beg < end);
+#if DEBUG
+ int times_set = 0;
+#endif
+
int totalweight = 0;
- int count = 0, result = 0, times_set = 0;
+ int count = 0, result = 0;
while ( beg != end )
{
totalweight += *beg;
if ( random2(totalweight) < *beg )
{
result = count;
+#if DEBUG
times_set++;
+#endif
}
++count;
++beg;