diff options
author | Steve Melenchuk <smelenchuk@gmail.com> | 2014-04-17 19:21:53 -0600 |
---|---|---|
committer | Steve Melenchuk <smelenchuk@gmail.com> | 2014-04-27 17:02:33 -0600 |
commit | 51a3c3daa54985a74a5024480c4b9819e9c41da9 (patch) | |
tree | 97046b335838c43d593a58def219ca0e43fb6c22 /crawl-ref/source/attack.cc | |
parent | fde016144d74dc363d43e65ef647e980391aeadc (diff) | |
download | crawl-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.cc | 950 |
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()); } |