summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/attack.cc
diff options
context:
space:
mode:
authorSteve Melenchuk <smelenchuk@gmail.com>2014-04-17 19:21:53 -0600
committerSteve Melenchuk <smelenchuk@gmail.com>2014-04-27 17:02:33 -0600
commit51a3c3daa54985a74a5024480c4b9819e9c41da9 (patch)
tree97046b335838c43d593a58def219ca0e43fb6c22 /crawl-ref/source/attack.cc
parentfde016144d74dc363d43e65ef647e980391aeadc (diff)
downloadcrawl-ref-51a3c3daa54985a74a5024480c4b9819e9c41da9.tar.gz
crawl-ref-51a3c3daa54985a74a5024480c4b9819e9c41da9.zip
Ranged attack brands!
Launchers use the equivalent melee brand effects if the missile isn't an elemental brand (or the attacker is Nessos). Missiles have their own set of effects; flame, frost, and poison use effects basically identical to weapon brands, and everything else has its old effect back. The lajatang of Order works again. Hooray!
Diffstat (limited to 'crawl-ref/source/attack.cc')
-rw-r--r--crawl-ref/source/attack.cc950
1 files changed, 946 insertions, 4 deletions
diff --git a/crawl-ref/source/attack.cc b/crawl-ref/source/attack.cc
index 5abde4aa98..68fced277f 100644
--- a/crawl-ref/source/attack.cc
+++ b/crawl-ref/source/attack.cc
@@ -20,15 +20,20 @@
#include "externs.h"
#include "enum.h"
#include "env.h"
+#include "godconduct.h"
#include "fight.h"
#include "fineff.h"
+#include "itemname.h"
#include "itemprop.h"
#include "misc.h"
#include "mon-behv.h"
+#include "mon-clone.h"
#include "mon-death.h"
#include "monster.h"
#include "libutil.h"
#include "player.h"
+#include "spl-miscast.h"
+#include "spl-util.h"
#include "state.h"
#include "stuff.h"
#include "xom.h"
@@ -54,7 +59,9 @@ attack::attack(actor *attk, actor *defn)
defender_body_armour_penalty(0), defender_shield_penalty(0),
attacker_body_armour_penalty(0), attacker_shield_penalty(0),
attacker_armour_tohit_penalty(0), attacker_shield_tohit_penalty(0),
- defender_shield(NULL), aux_source(""), kill_type(KILLED_BY_MONSTER)
+ defender_shield(NULL), miscast_level(-1), miscast_type(SPTYP_NONE),
+ miscast_target(NULL), fake_chaos_attack(false),
+ aux_source(""), kill_type(KILLED_BY_MONSTER)
{
// No effective code should execute, we'll call init_attack again from
// the child class, since initializing an attack will vary based the within
@@ -468,6 +475,636 @@ void attack::alert_defender()
}
}
+bool attack::distortion_affects_defender()
+{
+ //jmf: blink frogs *like* distortion
+ // I think could be amended to let blink frogs "grow" like
+ // jellies do {dlb}
+ if (defender->is_monster()
+ && mons_genus(defender->type) == MONS_BLINK_FROG)
+ {
+ if (one_chance_in(5))
+ {
+ if (defender_visible)
+ {
+ special_damage_message =
+ make_stringf("%s %s in the distortional energy.",
+ def_name(DESC_THE).c_str(),
+ defender->conj_verb("bask").c_str());
+ }
+
+ defender->heal(1 + random2avg(7, 2), true); // heh heh
+ }
+ return false;
+ }
+
+ if (one_chance_in(3))
+ {
+ special_damage_message =
+ make_stringf("Space bends around %s.",
+ def_name(DESC_THE).c_str());
+ special_damage += 1 + random2avg(7, 2);
+ return false;
+ }
+
+ if (one_chance_in(3))
+ {
+ special_damage_message =
+ make_stringf("Space warps horribly around %s!",
+ def_name(DESC_THE).c_str());
+ special_damage += 3 + random2avg(24, 2);
+ return false;
+ }
+
+// if (simu)
+// return false;
+
+ if (one_chance_in(3))
+ {
+ if (defender_visible)
+ obvious_effect = true;
+ if (!defender->no_tele(true, false))
+ (new blink_fineff(defender))->schedule();
+ return false;
+ }
+
+ if (!one_chance_in(3))
+ {
+ if (defender_visible)
+ obvious_effect = true;
+ if (crawl_state.game_is_sprint() && defender->is_player()
+ || defender->no_tele())
+ {
+ if (defender->is_player())
+ canned_msg(MSG_STRANGE_STASIS);
+ }
+ else if (coinflip())
+ (new distortion_tele_fineff(defender))->schedule();
+ else
+ defender->teleport();
+ return false;
+ }
+
+ if (!player_in_branch(BRANCH_ABYSS) && coinflip())
+ {
+ if (defender->is_player() && attacker_visible
+ && weapon != NULL && !is_unrandom_artefact(*weapon)
+ && !is_special_unrandom_artefact(*weapon))
+ {
+ // If the player is being sent to the Abyss by being attacked
+ // with a distortion weapon, then we have to ID it before
+ // the player goes to Abyss, while the weapon object is
+ // still in memory.
+ if (is_artefact(*weapon))
+ artefact_wpn_learn_prop(*weapon, ARTP_BRAND);
+ else
+ set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
+ }
+ else if (defender_visible)
+ obvious_effect = true;
+
+ defender->banish(attacker, attacker->name(DESC_PLAIN, true));
+ return true;
+ }
+
+ return false;
+}
+
+void attack::antimagic_affects_defender(int pow)
+{
+ int amount = 0;
+ if (defender->is_player())
+ {
+ amount = min(you.magic_points, random2avg(pow, 3));
+ if (!amount)
+ return;
+ mprf(MSGCH_WARN, "You feel your power leaking away.");
+ drain_mp(amount);
+ obvious_effect = true;
+ }
+ else if (mons_antimagic_affected(defender->as_monster()))
+ {
+ int dur = div_rand_round(pow * 8, defender->as_monster()->hit_dice);
+ amount = random2(dur + 1);
+ dur = amount * BASELINE_DELAY;
+ defender->as_monster()->add_ench(mon_enchant(ENCH_ANTIMAGIC, 0,
+ attacker, // doesn't matter
+ dur));
+ special_damage_message =
+ apostrophise(defender->name(DESC_THE))
+ + " magic leaks into the air.";
+ obvious_effect = true;
+ }
+}
+
+void attack::pain_affects_defender()
+{
+ if (defender->res_negative_energy())
+ return;
+
+ if (!one_chance_in(attacker->skill_rdiv(SK_NECROMANCY) + 1))
+ {
+ if (defender_visible)
+ {
+ special_damage_message =
+ make_stringf("%s %s in agony.",
+ defender->name(DESC_THE).c_str(),
+ defender->conj_verb("writhe").c_str());
+ }
+ special_damage += random2(1 + attacker->skill_rdiv(SK_NECROMANCY));
+ }
+
+ attacker->god_conduct(DID_NECROMANCY, 4);
+}
+
+// TODO: Move this somewhere sane
+enum chaos_type
+{
+ CHAOS_CLONE,
+ CHAOS_POLY,
+ CHAOS_POLY_UP,
+ CHAOS_MAKE_SHIFTER,
+ CHAOS_MISCAST,
+ CHAOS_RAGE,
+ CHAOS_HEAL,
+ CHAOS_HASTE,
+ CHAOS_INVIS,
+ CHAOS_SLOW,
+ CHAOS_PARALYSIS,
+ CHAOS_PETRIFY,
+ NUM_CHAOS_TYPES
+};
+
+// XXX: We might want to vary the probabilities for the various effects
+// based on whether the source is a weapon of chaos or a monster with
+// AF_CHAOS.
+void attack::chaos_affects_defender()
+{
+ monster * const mon = defender->as_monster();
+ const bool firewood = mon && mons_is_firewood(mon);
+ const bool immune = mon && mons_immune_magic(mon);
+ const bool is_natural = mon && defender->holiness() == MH_NATURAL;
+ const bool is_shifter = mon && mon->is_shapeshifter();
+ const bool can_clone = mon && mons_clonable(mon, true);
+ const bool can_poly = is_shifter || (defender->can_safely_mutate()
+ && !immune && !firewood);
+ const bool can_rage = defender->can_go_berserk();
+ const bool can_slow = !firewood;
+ const bool can_petrify= mon && !defender->res_petrify();
+
+ 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 && is_natural && mon ? 1 : 0;
+ int rage_chance = can_rage ? 10 : 0;
+ int miscast_chance = 10;
+ int slowpara_chance= can_slow ? 10 : 0;
+ int petrify_chance = can_slow && can_petrify ? 10 : 0;
+
+ // Already a shifter?
+ if (is_shifter)
+ shifter_chance = 0;
+
+ // A chaos self-attack increases 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;
+
+ // Inform player that something is up.
+ if (!fake_chaos_attack && you.see_cell(defender->pos()))
+ {
+ if (defender->is_player())
+ mpr("You give off a flash of multicoloured light!");
+ else if (you.can_see(defender))
+ {
+ simple_monster_message(mon, " gives off a flash of"
+ " multicoloured light!");
+ }
+ else
+ mpr("There is a flash of multicoloured light!");
+ }
+ }
+
+ // NOTE: Must appear in exact same order as in chaos_type enumeration.
+ int probs[NUM_CHAOS_TYPES] =
+ {
+ clone_chance, // CHAOS_CLONE
+ poly_chance, // CHAOS_POLY
+ poly_up_chance, // CHAOS_POLY_UP
+ shifter_chance, // CHAOS_MAKE_SHIFTER
+ miscast_chance, // CHAOS_MISCAST
+ rage_chance, // CHAOS_RAGE
+
+ 10, // CHAOS_HEAL
+ slowpara_chance,// CHAOS_HASTE
+ 10, // CHAOS_INVIS
+
+ slowpara_chance,// CHAOS_SLOW
+ slowpara_chance,// CHAOS_PARALYSIS
+ petrify_chance, // CHAOS_PETRIFY
+ };
+
+ bolt beam;
+ beam.flavour = BEAM_NONE;
+
+ int choice = choose_random_weighted(probs, probs + NUM_CHAOS_TYPES);
+#ifdef NOTE_DEBUG_CHAOS_EFFECTS
+ string chaos_effect = "CHAOS effect: ";
+ switch (choice)
+ {
+ case CHAOS_CLONE: chaos_effect += "clone"; break;
+ case CHAOS_POLY: chaos_effect += "polymorph"; break;
+ case CHAOS_POLY_UP: chaos_effect += "polymorph PPT_MORE"; break;
+ case CHAOS_MAKE_SHIFTER: chaos_effect += "shifter"; break;
+ case CHAOS_MISCAST: chaos_effect += "miscast"; break;
+ case CHAOS_RAGE: chaos_effect += "berserk"; break;
+ case CHAOS_HEAL: chaos_effect += "healing"; break;
+ case CHAOS_HASTE: chaos_effect += "hasting"; break;
+ case CHAOS_INVIS: chaos_effect += "invisible"; break;
+ case CHAOS_SLOW: chaos_effect += "slowing"; break;
+ case CHAOS_PARALYSIS: chaos_effect += "paralysis"; break;
+ case CHAOS_PETRIFY: chaos_effect += "petrify"; break;
+ default: chaos_effect += "(other)"; break;
+ }
+
+ take_note(Note(NOTE_MESSAGE, 0, 0, chaos_effect.c_str()), true);
+#endif
+
+ switch (static_cast<chaos_type>(choice))
+ {
+ case CHAOS_CLONE:
+ {
+ ASSERT(can_clone);
+ ASSERT(clone_chance > 0);
+ ASSERT(defender->is_monster());
+
+ if (monster *clone = clone_mons(mon, true, &obvious_effect))
+ {
+ if (obvious_effect)
+ {
+ special_damage_message =
+ make_stringf("%s is duplicated!",
+ def_name(DESC_THE).c_str());
+ }
+
+ // The player shouldn't get new permanent followers from cloning.
+ if (clone->attitude == ATT_FRIENDLY && !clone->is_summoned())
+ clone->mark_summoned(6, true, MON_SUMM_CLONE);
+
+ // Monsters being cloned is interesting.
+ xom_is_stimulated(clone->friendly() ? 12 : 25);
+ }
+ break;
+ }
+
+ case CHAOS_POLY:
+ ASSERT(can_poly);
+ ASSERT(poly_chance > 0);
+ beam.flavour = BEAM_POLYMORPH;
+ break;
+
+ case CHAOS_POLY_UP:
+ ASSERT(can_poly);
+ ASSERT(poly_up_chance > 0);
+ ASSERT(defender->is_monster());
+
+ obvious_effect = you.can_see(defender);
+ monster_polymorph(mon, RANDOM_MONSTER, PPT_MORE);
+ break;
+
+ case CHAOS_MAKE_SHIFTER:
+ {
+ ASSERT(can_poly);
+ ASSERT(shifter_chance > 0);
+ ASSERT(!is_shifter);
+ ASSERT(defender->is_monster());
+
+ obvious_effect = you.can_see(defender);
+ mon->add_ench(one_chance_in(3) ? ENCH_GLOWING_SHAPESHIFTER
+ : ENCH_SHAPESHIFTER);
+ // Immediately polymorph monster, just to make the effect obvious.
+ monster_polymorph(mon, RANDOM_MONSTER);
+
+ // Xom loves it if this happens!
+ const int friend_factor = mon->friendly() ? 1 : 2;
+ const int glow_factor = mon->has_ench(ENCH_SHAPESHIFTER) ? 1 : 2;
+ xom_is_stimulated(64 * friend_factor * glow_factor);
+ break;
+ }
+ case CHAOS_MISCAST:
+ {
+ int level = defender->get_experience_level();
+
+ // At level == 27 there's a 13.9% chance of a level 3 miscast.
+ int level0_chance = level;
+ int level1_chance = max(0, level - 7);
+ int level2_chance = max(0, level - 12);
+ int level3_chance = max(0, level - 17);
+
+ level = random_choose_weighted(
+ level0_chance, 0,
+ level1_chance, 1,
+ level2_chance, 2,
+ level3_chance, 3,
+ 0);
+
+ miscast_level = level;
+ miscast_type = SPTYP_RANDOM;
+ miscast_target = one_chance_in(3) ? attacker : defender;
+ break;
+ }
+
+ case CHAOS_RAGE:
+ ASSERT(can_rage);
+ ASSERT(rage_chance > 0);
+ defender->go_berserk(false);
+ obvious_effect = you.can_see(defender);
+ break;
+
+ // For these, obvious_effect is computed during beam.fire() below.
+ case CHAOS_HEAL:
+ beam.flavour = BEAM_HEALING;
+ break;
+ case CHAOS_HASTE:
+ beam.flavour = BEAM_HASTE;
+ break;
+ case CHAOS_INVIS:
+ beam.flavour = BEAM_INVISIBILITY;
+ break;
+ case CHAOS_SLOW:
+ beam.flavour = BEAM_SLOW;
+ break;
+ case CHAOS_PARALYSIS:
+ beam.flavour = BEAM_PARALYSIS;
+ break;
+ case CHAOS_PETRIFY:
+ beam.flavour = BEAM_PETRIFY;
+ break;
+
+ default:
+ die("Invalid chaos effect type");
+ break;
+ }
+
+ if (beam.flavour != BEAM_NONE)
+ {
+ beam.glyph = 0;
+ beam.range = 0;
+ beam.colour = BLACK;
+ beam.effect_known = false;
+ // Wielded brand is always known, but maybe this was from a call
+ // to chaos_affect_actor.
+ beam.effect_wanton = !fake_chaos_attack;
+
+ if (weapon && you.can_see(attacker))
+ {
+ beam.name = wep_name(DESC_YOUR);
+ beam.item = weapon;
+ }
+ else
+ beam.name = atk_name(DESC_THE);
+
+ beam.thrower =
+ (attacker->is_player()) ? KILL_YOU
+ : attacker->as_monster()->confused_by_you() ? KILL_YOU_CONF
+ : KILL_MON;
+
+ if (beam.thrower == KILL_YOU || attacker->as_monster()->friendly())
+ beam.attitude = ATT_FRIENDLY;
+
+ beam.beam_source = attacker->mindex();
+
+ beam.source = defender->pos();
+ beam.target = defender->pos();
+
+ beam.damage = dice_def(damage_done + special_damage + aux_damage, 1);
+
+ beam.ench_power = beam.damage.num;
+
+ const bool you_could_see = you.can_see(defender);
+ beam.fire();
+
+ if (you_could_see)
+ obvious_effect = beam.obvious_effect;
+ }
+
+ if (!you.can_see(attacker))
+ obvious_effect = false;
+}
+
+// NOTE: random_chaos_brand() and random_chaos_attack_flavour() should
+// return a set of effects that are roughly the same, to make it easy
+// for chaos_affects_defender() not to do duplicate effects caused
+// by the non-chaos brands/flavours they return.
+brand_type attack::random_chaos_brand()
+{
+ brand_type brand = SPWPN_NORMAL;
+ // Assuming the chaos to be mildly intelligent, try to avoid brands
+ // that clash with the most basic resists of the defender,
+ // i.e. its holiness.
+ while (true)
+ {
+ brand = (random_choose_weighted(
+ 5, SPWPN_VORPAL,
+ 10, SPWPN_FLAMING,
+ 10, SPWPN_FREEZING,
+ 10, SPWPN_ELECTROCUTION,
+ 10, SPWPN_VENOM,
+ 10, SPWPN_CHAOS,
+ 5, SPWPN_DRAINING,
+ 5, SPWPN_VAMPIRICISM,
+ 5, SPWPN_HOLY_WRATH,
+ 5, SPWPN_ANTIMAGIC,
+ 2, SPWPN_CONFUSE,
+ 2, SPWPN_DISTORTION,
+ 0));
+
+ if (one_chance_in(3))
+ break;
+
+ bool susceptible = true;
+ switch (brand)
+ {
+ case SPWPN_FLAMING:
+ if (defender->is_fiery())
+ susceptible = false;
+ break;
+ case SPWPN_FREEZING:
+ if (defender->is_icy())
+ susceptible = false;
+ break;
+ case SPWPN_VENOM:
+ if (defender->holiness() == MH_UNDEAD)
+ susceptible = false;
+ break;
+ case SPWPN_VAMPIRICISM:
+ if (defender->is_summoned())
+ {
+ susceptible = false;
+ break;
+ }
+ // intentional fall-through
+ case SPWPN_DRAINING:
+ if (defender->holiness() != MH_NATURAL)
+ susceptible = false;
+ break;
+ case SPWPN_HOLY_WRATH:
+ if (defender->holiness() != MH_UNDEAD
+ && defender->holiness() != MH_DEMONIC)
+ {
+ susceptible = false;
+ }
+ break;
+ case SPWPN_CONFUSE:
+ if (defender->holiness() == MH_NONLIVING
+ || defender->holiness() == MH_PLANT)
+ {
+ susceptible = false;
+ }
+ break;
+ case SPWPN_ANTIMAGIC:
+ if (defender->as_monster() &&
+ !defender->as_monster()->can_use_spells())
+ {
+ susceptible = false;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (susceptible)
+ break;
+ }
+#ifdef NOTE_DEBUG_CHAOS_BRAND
+ string brand_name = "CHAOS brand: ";
+ switch (brand)
+ {
+ case SPWPN_NORMAL: brand_name += "(plain)"; break;
+ case SPWPN_FLAMING: brand_name += "flaming"; break;
+ case SPWPN_FREEZING: brand_name += "freezing"; break;
+ case SPWPN_HOLY_WRATH: brand_name += "holy wrath"; break;
+ case SPWPN_ELECTROCUTION: brand_name += "electrocution"; break;
+ case SPWPN_VENOM: brand_name += "venom"; break;
+ case SPWPN_DRAINING: brand_name += "draining"; break;
+ case SPWPN_DISTORTION: brand_name += "distortion"; break;
+ case SPWPN_VAMPIRICISM: brand_name += "vampiricism"; break;
+ case SPWPN_VORPAL: brand_name += "vorpal"; break;
+ case SPWPN_ANTIMAGIC: brand_name += "anti-magic"; break;
+ // ranged weapon brands
+ case SPWPN_FLAME: brand_name += "flame"; break;
+ case SPWPN_FROST: brand_name += "frost"; break;
+
+ // both ranged and non-ranged
+ case SPWPN_CHAOS: brand_name += "chaos"; break;
+ case SPWPN_CONFUSE: brand_name += "confusion"; break;
+ default: brand_name += "(other)"; break;
+ }
+
+ // Pretty much duplicated by the chaos effect note,
+ // which will be much more informative.
+ if (brand != SPWPN_CHAOS)
+ take_note(Note(NOTE_MESSAGE, 0, 0, brand_name.c_str()), true);
+#endif
+ return brand;
+}
+
+void attack::do_miscast()
+{
+ if (miscast_level == -1)
+ return;
+
+ ASSERT(miscast_target != NULL);
+ ASSERT_RANGE(miscast_level, 0, 4);
+ ASSERT(count_bits(miscast_type) == 1);
+
+ if (!miscast_target->alive())
+ return;
+
+ if (miscast_target->is_player() && you.banished)
+ 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.
+ string hand_str;
+
+ string cause = atk_name(DESC_THE);
+ int source;
+
+ const int ignore_mask = ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES;
+
+ if (attacker->is_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_YOUR, ignore_mask | ISFLAG_COSMETIC_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, 0, hand_str, false);
+
+ // Don't do miscast twice for one attack.
+ miscast_level = -1;
+}
+
+void attack::drain_defender()
+{
+ if (defender->is_monster() && one_chance_in(3))
+ return;
+
+ if (defender->holiness() != MH_NATURAL)
+ return;
+
+ special_damage = resist_adjust_damage(defender, BEAM_NEG,
+ defender->res_negative_energy(),
+ (1 + random2(damage_done)) / 2);
+
+ if (defender->drain_exp(attacker, true, 20 + min(35, damage_done)))
+ {
+ if (defender->is_player())
+ obvious_effect = true;
+ else if (defender_visible)
+ {
+ special_damage_message =
+ make_stringf(
+ "%s %s %s!",
+ atk_name(DESC_THE).c_str(),
+ attacker->conj_verb("drain").c_str(),
+ defender_name().c_str());
+ }
+
+ attacker->god_conduct(DID_NECROMANCY, 2);
+ }
+}
+
int attack::inflict_damage(int dam, beam_type flavour, bool clean)
{
if (flavour == NUM_BEAMS)
@@ -931,6 +1568,309 @@ bool attack::attack_shield_blocked(bool verbose)
return false;
}
+attack_flavour attack::random_chaos_attack_flavour()
+{
+ attack_flavour flavours[] =
+ {AF_FIRE, AF_COLD, AF_ELEC, AF_POISON, AF_VAMPIRIC, AF_DISTORT,
+ AF_CONFUSE, AF_CHAOS};
+ return RANDOM_ELEMENT(flavours);
+}
+
+bool attack::apply_damage_brand(const char *what)
+{
+ bool brand_was_known = false;
+ int brand = 0;
+ bool ret = false;
+
+ if (weapon)
+ {
+ if (is_artefact(*weapon))
+ brand_was_known = artefact_known_wpn_property(*weapon, ARTP_BRAND);
+ else
+ brand_was_known = item_type_known(*weapon);
+ }
+
+ special_damage = 0;
+ obvious_effect = false;
+ brand = damage_brand == SPWPN_CHAOS ? random_chaos_brand() : damage_brand;
+
+ if (brand != SPWPN_FLAMING && brand != SPWPN_FREEZING
+ && brand != SPWPN_FLAME && brand != SPWPN_FROST
+ && brand != SPWPN_ELECTROCUTION && brand != SPWPN_VAMPIRICISM
+ && !defender->alive())
+ {
+ // Most brands have no extra effects on just killed enemies, and the
+ // effect would be often inappropriate.
+ return false;
+ }
+
+ if (!damage_done
+ && (brand == SPWPN_FLAMING || brand == SPWPN_FREEZING
+ || brand == SPWPN_FLAME || brand == SPWPN_FROST
+ || brand == SPWPN_HOLY_WRATH || brand == SPWPN_DRAGON_SLAYING
+ || brand == SPWPN_VORPAL || brand == SPWPN_VAMPIRICISM
+ || brand == SPWPN_ANTIMAGIC))
+ {
+ // These brands require some regular damage to function.
+ return false;
+ }
+
+ switch (brand)
+ {
+ case SPWPN_FLAMING:
+ case SPWPN_FLAME:
+ calc_elemental_brand_damage(BEAM_FIRE, defender->res_fire(),
+ defender->is_icy() ? "melt" : "burn",
+ what);
+ defender->expose_to_element(BEAM_FIRE);
+ attacker->god_conduct(DID_FIRE, 1);
+ break;
+
+ case SPWPN_FREEZING:
+ case SPWPN_FROST:
+ calc_elemental_brand_damage(BEAM_COLD, defender->res_cold(), "freeze",
+ what);
+ defender->expose_to_element(BEAM_COLD, 2, false);
+ break;
+
+ case SPWPN_HOLY_WRATH:
+ if (defender->undead_or_demonic())
+ special_damage = 1 + (random2(damage_done * 15) / 10);
+
+ if (special_damage && defender_visible)
+ {
+ special_damage_message =
+ make_stringf(
+ "%s %s%s",
+ def_name(DESC_THE).c_str(),
+ defender->conj_verb("convulse").c_str(),
+ special_attack_punctuation().c_str());
+ }
+ break;
+
+ case SPWPN_ELECTROCUTION:
+ if (defender->res_elec() > 0)
+ break;
+ else if (one_chance_in(3))
+ {
+ special_damage_message =
+ defender->is_player()?
+ "You are electrocuted!"
+ : "There is a sudden explosion of sparks!";
+ special_damage = 8 + random2(13);
+ special_damage_flavour = BEAM_ELECTRICITY;
+ }
+
+ break;
+
+ case SPWPN_DRAGON_SLAYING:
+ if (is_dragonkind(defender))
+ {
+ special_damage = 1 + random2(3*damage_done/2);
+ if (defender_visible)
+ {
+ special_damage_message =
+ make_stringf(
+ "%s %s%s",
+ defender->name(DESC_THE).c_str(),
+ defender->conj_verb("convulse").c_str(),
+ special_attack_punctuation().c_str());
+ }
+ }
+ break;
+
+ case SPWPN_VENOM:
+ if (!one_chance_in(4))
+ {
+ int old_poison;
+
+ if (defender->is_player())
+ old_poison = you.duration[DUR_POISONING];
+ else
+ {
+ old_poison =
+ (defender->as_monster()->get_ench(ENCH_POISON)).degree;
+ }
+
+ defender->poison(attacker, 6 + random2(8) + random2(damage_done * 3 / 2));
+
+ if (defender->is_player()
+ && old_poison < you.duration[DUR_POISONING]
+ || !defender->is_player()
+ && old_poison <
+ (defender->as_monster()->get_ench(ENCH_POISON)).degree)
+ {
+ obvious_effect = true;
+ }
+
+ }
+ break;
+
+ case SPWPN_DRAINING:
+ drain_defender();
+ break;
+
+ case SPWPN_VORPAL:
+ special_damage = 1 + random2(damage_done) / 3;
+ // Note: Leaving special_damage_message empty because there isn't one.
+ break;
+
+ case SPWPN_VAMPIRICISM:
+ {
+ if (x_chance_in_y(defender->res_negative_energy(), 3))
+ break;
+
+ if (!weapon || defender->holiness() != MH_NATURAL || damage_done < 1
+ || attacker->stat_hp() == attacker->stat_maxhp()
+ || !defender->is_player()
+ && defender->as_monster()->is_summoned()
+ || attacker->is_player() && you.duration[DUR_DEATHS_DOOR]
+ || !attacker->is_player()
+ && attacker->as_monster()->has_ench(ENCH_DEATHS_DOOR)
+ || (x_chance_in_y(2, 5) && !(weapon->special == UNRAND_LEECH)))
+ {
+ break;
+ }
+
+ obvious_effect = true;
+
+ // Handle weapon effects.
+ // We only get here if we've done base damage, so no
+ // worries on that score.
+ if (attacker->is_player())
+ mpr("You feel better.");
+ else if (attacker_visible)
+ {
+ if (defender->is_player())
+ {
+ mprf("%s draws strength from your injuries!",
+ attacker->name(DESC_THE).c_str());
+ }
+ else
+ {
+ mprf("%s is healed.",
+ attacker->name(DESC_THE).c_str());
+ }
+ }
+
+ int hp_boost = weapon->special == UNRAND_VAMPIRES_TOOTH
+ ? damage_done : 1 + random2(damage_done);
+
+ dprf(DIAG_COMBAT, "Vampiric Healing: damage %d, healed %d",
+ damage_done, hp_boost);
+ attacker->heal(hp_boost);
+
+ attacker->god_conduct(DID_NECROMANCY, 2);
+ break;
+ }
+ case SPWPN_PAIN:
+ pain_affects_defender();
+ break;
+
+ case SPWPN_DISTORTION:
+ ret = distortion_affects_defender();
+ break;
+
+ case SPWPN_CONFUSE:
+ {
+ // This was originally for confusing touch and it doesn't really
+ // work on the player, but a monster with a chaos weapon will
+ // occasionally come up with this brand. -cao
+ if (defender->is_player())
+ break;
+
+ // Also used for players in fungus form.
+ if (attacker->is_player()
+ && you.form == TRAN_FUNGUS
+ && !you.duration[DUR_CONFUSING_TOUCH]
+ && defender->is_unbreathing())
+ {
+ break;
+ }
+
+ const int hdcheck =
+ (defender->holiness() == MH_NATURAL ? random2(30) : random2(22));
+
+ if (mons_class_is_confusable(defender->type)
+ && hdcheck >= defender->get_experience_level()
+ && !one_chance_in(5)
+ && !defender->as_monster()->check_clarity(false))
+ {
+ // Declaring these just to pass to the enchant function.
+ bolt beam_temp;
+ beam_temp.thrower =
+ attacker->is_player() ? KILL_YOU : KILL_MON;
+ beam_temp.flavour = BEAM_CONFUSION;
+ beam_temp.beam_source = attacker->mindex();
+ beam_temp.apply_enchantment_to_monster(defender->as_monster());
+ obvious_effect = beam_temp.obvious_effect;
+ }
+
+ if (attacker->is_player() && damage_brand == SPWPN_CONFUSE
+ && you.duration[DUR_CONFUSING_TOUCH])
+ {
+ you.duration[DUR_CONFUSING_TOUCH] -= roll_dice(3, 5)
+ * BASELINE_DELAY;
+
+ if (you.duration[DUR_CONFUSING_TOUCH] < 1)
+ you.duration[DUR_CONFUSING_TOUCH] = 1;
+ obvious_effect = false;
+ }
+ break;
+ }
+
+ case SPWPN_CHAOS:
+ chaos_affects_defender();
+ break;
+
+ case SPWPN_ANTIMAGIC:
+ antimagic_affects_defender(damage_done);
+ 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->is_player() && damage_brand == SPWPN_CHAOS)
+ {
+ // If your god objects to using chaos, then it makes the
+ // brand obvious.
+ if (did_god_conduct(DID_CHAOS, 2 + random2(3), brand_was_known))
+ obvious_effect = true;
+ }
+ if (!obvious_effect)
+ obvious_effect = !special_damage_message.empty();
+
+ if (needs_message && !special_damage_message.empty())
+ {
+ mprf("%s", special_damage_message.c_str());
+
+ special_damage_message.clear();
+ // Don't do message-only miscasts along with a special
+ // damage message.
+ if (miscast_level == 0)
+ miscast_level = -1;
+ }
+
+ if (special_damage > 0)
+ inflict_damage(special_damage, special_damage_flavour);
+
+ if (obvious_effect && attacker_visible && weapon != NULL)
+ {
+ if (is_artefact(*weapon))
+ artefact_wpn_learn_prop(*weapon, ARTP_BRAND);
+ else
+ set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
+ }
+
+ return ret;
+}
+
/* Calculates special damage, prints appropriate combat text
*
* Applies a particular damage brand to the current attack, the setup and
@@ -939,7 +1879,8 @@ bool attack::attack_shield_blocked(bool verbose)
*/
void attack::calc_elemental_brand_damage(beam_type flavour,
int res,
- const char *verb)
+ const char *verb,
+ const char *what)
{
special_damage = resist_adjust_damage(defender, flavour, res,
random2(damage_done) / 2 + 1);
@@ -948,8 +1889,9 @@ void attack::calc_elemental_brand_damage(beam_type flavour,
{
special_damage_message = make_stringf(
"%s %s %s%s",
- atk_name(DESC_THE).c_str(),
- attacker->conj_verb(verb).c_str(),
+ what ? what : atk_name(DESC_THE).c_str(),
+ what ? pluralise(verb).c_str() // XXX: may need to change this
+ : attacker->conj_verb(verb).c_str(),
defender_name().c_str(),
special_attack_punctuation().c_str());
}