summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/ranged_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/ranged_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/ranged_attack.cc')
-rw-r--r--crawl-ref/source/ranged_attack.cc419
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()