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/ranged_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/ranged_attack.cc')
-rw-r--r-- | crawl-ref/source/ranged_attack.cc | 419 |
1 files changed, 413 insertions, 6 deletions
diff --git a/crawl-ref/source/ranged_attack.cc b/crawl-ref/source/ranged_attack.cc index 32b7a056c1..2dedafb16f 100644 --- a/crawl-ref/source/ranged_attack.cc +++ b/crawl-ref/source/ranged_attack.cc @@ -9,14 +9,19 @@ #include "actor.h" #include "beam.h" +#include "coord.h" #include "exercise.h" #include "godconduct.h" #include "itemname.h" #include "itemprop.h" +#include "libutil.h" #include "mon-behv.h" +#include "mon-stuff.h" #include "monster.h" #include "player.h" #include "random.h" +#include "stuff.h" +#include "teleport.h" ranged_attack::ranged_attack(actor *attk, actor *defn, item_def *proj, bool tele) : @@ -170,6 +175,7 @@ bool ranged_attack::handle_phase_attempted() bool ranged_attack::handle_phase_blocked() { + ASSERT(!attack_ignores_shield(false)); range_used = BEAM_STOP; return attack::handle_phase_blocked(); } @@ -233,10 +239,14 @@ bool ranged_attack::handle_phase_dodged() bool ranged_attack::handle_phase_hit() { - range_used = BEAM_STOP; + // XXX: this kind of hijacks the shield block check + if (!attack_ignores_shield(false)) + range_used = BEAM_STOP; damage_done = calc_damage(); - if (damage_done > 0) + if (damage_done > 0 + || projectile->base_type == OBJ_MISSILES + && projectile->sub_type == MI_NEEDLE) { if (!handle_phase_damaged()) return false; @@ -249,7 +259,13 @@ bool ranged_attack::handle_phase_hit() defender->name(DESC_THE).c_str()); } - apply_damage_brand(); + if (using_weapon()) + { + if (apply_damage_brand(projectile->name(DESC_THE).c_str())) + return false; + if (apply_missile_brand()) + return false; + } // XXX: unify this with melee_attack's code if (attacker->is_player()) @@ -272,6 +288,11 @@ int ranged_attack::weapon_damage() return 0; int dam = property(*projectile, PWPN_DAMAGE); + if (projectile->base_type == OBJ_MISSILES + && get_ammo_brand(*projectile) == SPMSL_STEEL) + { + dam = div_rand_round(dam * 13, 10); + } if (weapon) dam += property(*weapon, PWPN_DAMAGE); else if (projectile->base_type == OBJ_MISSILES @@ -292,12 +313,398 @@ int ranged_attack::apply_damage_modifiers(int damage, int damage_max, bool ranged_attack::attack_ignores_shield(bool verbose) { + if (using_weapon() + && (weapon && get_weapon_brand(*weapon) == SPWPN_PENETRATION + || projectile->base_type == OBJ_MISSILES + && get_ammo_brand(*projectile) == SPMSL_PENETRATION)) + { + if (verbose) + { + mprf("%s pierces through %s %s!", + projectile->name(DESC_THE).c_str(), + apostrophise(defender_name()).c_str(), + defender_shield ? defender_shield->name(DESC_PLAIN).c_str() + : "shielding"); + } + return true; + } + return false; } -bool ranged_attack::apply_damage_brand() +bool ranged_attack::apply_damage_brand(const char *what) { - return false; + if (!weapon || !is_range_weapon(*weapon)) + return false; + + const brand_type brand = get_weapon_brand(*weapon); + + // No stacking elemental brands, unless you're Nessos. + if (attacker->type != MONS_NESSOS + && projectile->base_type == OBJ_MISSILES + && get_ammo_brand(*projectile) != SPMSL_NORMAL + && (brand == SPWPN_FLAMING + || brand == SPWPN_FREEZING + || brand == SPWPN_HOLY_WRATH + || brand == SPWPN_ELECTROCUTION + || brand == SPWPN_VENOM + || brand == SPWPN_FLAME + || brand == SPWPN_FROST + || brand == SPWPN_CHAOS)) + { + return false; + } + + damage_brand = brand; + return attack::apply_damage_brand(what); +} + +special_missile_type ranged_attack::random_chaos_missile_brand() +{ + special_missile_type brand = SPMSL_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( + 10, SPMSL_FLAME, + 10, SPMSL_FROST, + 10, SPMSL_POISONED, + 10, SPMSL_CHAOS, + 5, SPMSL_PARALYSIS, + 5, SPMSL_SLOW, + 5, SPMSL_SLEEP, + 5, SPMSL_FRENZY, + 2, SPMSL_CURARE, + 2, SPMSL_CONFUSION, + 2, SPMSL_DISPERSAL, + 0)); + + if (one_chance_in(3)) + break; + + bool susceptible = true; + switch (brand) + { + case SPMSL_FLAME: + if (defender->is_fiery()) + susceptible = false; + break; + case SPMSL_FROST: + if (defender->is_icy()) + susceptible = false; + break; + case SPMSL_POISONED: + if (defender->holiness() == MH_UNDEAD) + susceptible = false; + break; + case SPMSL_DISPERSAL: + if (defender->no_tele(true, false, true)) + susceptible = false; + break; + case SPMSL_CONFUSION: + if (defender->holiness() == MH_PLANT) + { + susceptible = false; + break; + } + // fall through + case SPMSL_SLOW: + case SPMSL_SLEEP: + case SPMSL_PARALYSIS: + if (defender->holiness() == MH_UNDEAD + || defender->holiness() == MH_NONLIVING) + { + susceptible = false; + } + break; + case SPMSL_FRENZY: + if (defender->holiness() == MH_UNDEAD + || defender->holiness() == MH_NONLIVING + || defender->is_player() + && !you.can_go_berserk(false, false, false) + || defender->is_monster() + && !defender->as_monster()->can_go_frenzy()) + { + susceptible = false; + } + break; + default: + break; + } + + if (susceptible) + break; + } +#ifdef NOTE_DEBUG_CHAOS_BRAND + string brand_name = "CHAOS missile: "; + switch (brand) + { + case SPMSL_NORMAL: brand_name += "(plain)"; break; + case SPMSL_FLAME: brand_name += "flame"; break; + case SPMSL_FROST: brand_name += "frost"; break; + case SPMSL_POISONED: brand_name += "poisoned"; break; + case SPMSL_CURARE: brand_name += "curare"; break; + case SPMSL_CHAOS: brand_name += "chaos"; break; + case SPMSL_DISPERSAL: brand_name += "dispersal"; break; + case SPMSL_SLOW: brand_name += "slow"; break; + case SPMSL_SLEEP: brand_name += "sleep"; break; + case SPMSL_CONFUSION: brand_name += "confusion"; break; + case SPMSL_FRENZY: brand_name += "frenzy"; break; + default: brand_name += "(other)"; break; + } + + // Pretty much duplicated by the chaos effect note, + // which will be much more informative. + if (brand != SPMSL_CHAOS) + take_note(Note(NOTE_MESSAGE, 0, 0, brand_name.c_str()), true); +#endif + return brand; +} + +bool ranged_attack::blowgun_check(special_missile_type type) +{ + if (defender->holiness() == MH_UNDEAD + || defender->holiness() == MH_NONLIVING) + { + if (needs_message) + { + if (defender->is_monster()) + { + simple_monster_message(defender->as_monster(), + " is unaffected."); + } + else + canned_msg(MSG_YOU_UNAFFECTED); + } + return false; + } + + const int enchantment = weapon && using_weapon() ? weapon->plus : 0; + + if (attacker->is_monster()) + { + int chance = 85 - ((defender->get_experience_level() + - attacker->get_experience_level()) * 5 / 2); + chance += enchantment * 4; + chance = min(95, chance); + + if (type == SPMSL_FRENZY) + chance = chance / 2; + else if (type == SPMSL_PARALYSIS || type == SPMSL_SLEEP) + chance = chance * 4 / 5; + + return x_chance_in_y(chance, 100); + } + + const int skill = you.skill_rdiv(SK_THROWING); + + // You have a really minor chance of hitting with no skills or good + // enchants. + if (defender->get_experience_level() < 15 && random2(100) <= 2) + return true; + + const int resist_roll = 2 + random2(4 + skill + enchantment); + + dprf("Brand rolled %d against defender HD: %d.", + resist_roll, defender->get_experience_level()); + + if (resist_roll < defender->get_experience_level()) + { + if (needs_message) + { + if (defender->is_monster()) + simple_monster_message(defender->as_monster(), " resists."); + else + canned_msg(MSG_YOU_RESIST); + } + return false; + } + + return true; + +} + +int ranged_attack::blowgun_duration_roll(special_missile_type type) +{ + const int base_power = (attacker->is_monster()) + ? attacker->get_experience_level() + : attacker->skill_rdiv(SK_THROWING); + + const int plus = weapon && using_weapon() ? weapon->plus : 0; + + // Scale down nastier needle effects against players. + // Fixed duration regardless of power, since power already affects success + // chance considerably, and this helps avoid effects being too nasty from + // high HD shooters and too ignorable from low ones. + if (defender->is_player()) + { + switch (type) + { + case SPMSL_PARALYSIS: + return 3 + random2(4); + case SPMSL_SLEEP: + return 5 + random2(5); + case SPMSL_CONFUSION: + return 2 + random2(4); + case SPMSL_SLOW: + return 5 + random2(7); + default: + return 5 + random2(5); + } + } + else + return 5 + random2(base_power + plus); +} + +bool ranged_attack::apply_missile_brand() +{ + if (projectile->base_type != OBJ_MISSILES) + return false; + + special_missile_type brand = get_ammo_brand(*projectile); + if (brand == SPMSL_CHAOS) + brand = random_chaos_missile_brand(); + + switch (brand) + { + default: + break; + case SPMSL_FLAME: + if (get_weapon_brand(*weapon) == SPWPN_FROST + || get_weapon_brand(*weapon) == SPWPN_FREEZING) + { + break; + } + calc_elemental_brand_damage(BEAM_FIRE, defender->res_fire(), + defender->is_icy() ? "melt" : "burn", + projectile->name(DESC_THE).c_str()); + defender->expose_to_element(BEAM_FIRE); + attacker->god_conduct(DID_FIRE, 1); + break; + case SPMSL_FROST: + if (get_weapon_brand(*weapon) == SPWPN_FLAME + || get_weapon_brand(*weapon) == SPWPN_FLAMING) + { + break; + } + calc_elemental_brand_damage(BEAM_COLD, defender->res_fire(), "freeze", + projectile->name(DESC_THE).c_str()); + defender->expose_to_element(BEAM_COLD, 2, false); + break; + case SPMSL_POISONED: + 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 SPMSL_CURARE: + obvious_effect = curare_actor(attacker, defender, + projectile->name(DESC_PLAIN), + atk_name(DESC_PLAIN)); + break; + case SPMSL_CHAOS: + chaos_affects_defender(); + break; + case SPMSL_DISPERSAL: + if (damage_done > 0) + { + if (defender->no_tele(true, true)) + { + if (defender->is_player()) + canned_msg(MSG_STRANGE_STASIS); + } + else + { + coord_def pos, pos2; + const bool no_sanct = defender->kill_alignment() == KC_OTHER; + if (random_near_space(defender, defender->pos(), pos, false, + no_sanct) + && random_near_space(defender, defender->pos(), pos2, false, + no_sanct)) + { + const coord_def from = attacker->pos(); + if (distance2(pos2, from) > distance2(pos, from)) + pos = pos2; + + if (defender->is_player()) + defender->blink_to(pos); + else + defender->as_monster()->blink_to(pos, false, false); + } + } + } + break; + case SPMSL_SILVER: + special_damage = silver_damages_victim(defender, damage_done, + special_damage_message); + break; + case SPMSL_PARALYSIS: + if (!blowgun_check(brand)) + break; + defender->paralyse(attacker, blowgun_duration_roll(brand)); + break; + case SPMSL_SLOW: + if (!blowgun_check(brand)) + break; + defender->slow_down(attacker, blowgun_duration_roll(brand)); + break; + case SPMSL_SLEEP: + if (!blowgun_check(brand)) + break; + defender->put_to_sleep(attacker, blowgun_duration_roll(brand)); + break; + case SPMSL_CONFUSION: + if (!blowgun_check(brand)) + break; + defender->confuse(attacker, blowgun_duration_roll(brand)); + break; + case SPMSL_FRENZY: + if (!blowgun_check(brand)) + break; + if (defender->is_monster()) + defender->as_monster()->go_frenzy(attacker); + else + defender->go_berserk(false); + break; + } + + 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); + + return !defender->alive(); } bool ranged_attack::check_unrand_effects() @@ -316,7 +723,7 @@ void ranged_attack::adjust_noise() void ranged_attack::set_attack_verb() { - attack_verb = "hits"; + attack_verb = attack_ignores_shield(false) ? "pierces through" : "hits"; } void ranged_attack::announce_hit() |