summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDarshan Shaligram <dshaligram@users.sourceforge.net>2009-12-29 20:24:07 +0530
committerDarshan Shaligram <dshaligram@users.sourceforge.net>2009-12-29 20:24:07 +0530
commit43d86ff397acfe79b6699845ae4e2ec9a8e485d0 (patch)
tree72a76a5ed900aa4ab9465bef1cbd0d7807bbbccd
parent3f8334b021c123a371f0dfe8062c3a84eebba673 (diff)
parent8efdaf11a28299dda120a8afe78702686a6e2a47 (diff)
downloadcrawl-ref-43d86ff397acfe79b6699845ae4e2ec9a8e485d0.tar.gz
crawl-ref-43d86ff397acfe79b6699845ae4e2ec9a8e485d0.zip
Merge branch 'shoals++'
-rw-r--r--crawl-ref/source/actor.h5
-rw-r--r--crawl-ref/source/beam.cc191
-rw-r--r--crawl-ref/source/beam.h6
-rw-r--r--crawl-ref/source/dat/descript/monsters.txt14
-rw-r--r--crawl-ref/source/dgn-shoals.cc486
-rw-r--r--crawl-ref/source/dgn-shoals.h3
-rw-r--r--crawl-ref/source/dungeon.cc66
-rw-r--r--crawl-ref/source/dungeon.h9
-rw-r--r--crawl-ref/source/enum.h28
-rw-r--r--crawl-ref/source/externs.h10
-rw-r--r--crawl-ref/source/fight.cc7
-rw-r--r--crawl-ref/source/main.cc2
-rw-r--r--crawl-ref/source/mon-abil.cc12
-rw-r--r--crawl-ref/source/mon-act.cc29
-rw-r--r--crawl-ref/source/mon-cast.cc31
-rw-r--r--crawl-ref/source/mon-data.h55
-rw-r--r--crawl-ref/source/mon-gear.cc73
-rw-r--r--crawl-ref/source/mon-pick.cc10
-rw-r--r--crawl-ref/source/mon-place.cc11
-rw-r--r--crawl-ref/source/mon-spll.h15
-rw-r--r--crawl-ref/source/mon-stuff.cc5
-rw-r--r--crawl-ref/source/mon-util.cc7
-rw-r--r--crawl-ref/source/monster.cc28
-rw-r--r--crawl-ref/source/monster.h8
-rw-r--r--crawl-ref/source/ouch.cc7
-rw-r--r--crawl-ref/source/player.cc13
-rw-r--r--crawl-ref/source/player.h5
-rw-r--r--crawl-ref/source/shopping.cc2
-rw-r--r--crawl-ref/source/spl-cast.cc6
-rw-r--r--crawl-ref/source/spl-data.h26
-rw-r--r--crawl-ref/source/spl-util.cc2
-rw-r--r--crawl-ref/source/stuff.h5
-rw-r--r--crawl-ref/source/view.cc13
-rw-r--r--crawl-ref/source/view.h1
34 files changed, 1043 insertions, 148 deletions
diff --git a/crawl-ref/source/actor.h b/crawl-ref/source/actor.h
index a6d4899bbb..e3b3ae4af6 100644
--- a/crawl-ref/source/actor.h
+++ b/crawl-ref/source/actor.h
@@ -37,6 +37,10 @@ public:
// occupied.
virtual bool move_to_pos(const coord_def &c) = 0;
+ virtual void apply_location_effects(const coord_def &oldpos,
+ killer_type killer = KILL_NONE,
+ int killernum = -1) = 0;
+
virtual void set_position(const coord_def &c);
virtual const coord_def& pos() const { return position; }
@@ -213,6 +217,7 @@ public:
virtual int res_poison() const = 0;
virtual int res_rotting() const = 0;
virtual int res_asphyx() const = 0;
+ virtual int res_water_drowning() const = 0;
virtual int res_sticky_flame() const = 0;
virtual int res_holy_energy(const actor *attacker) const = 0;
virtual int res_negative_energy() const = 0;
diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc
index 8b05a833d9..abe97ceb66 100644
--- a/crawl-ref/source/beam.cc
+++ b/crawl-ref/source/beam.cc
@@ -28,6 +28,7 @@
#include "coord.h"
#include "coordit.h"
#include "delay.h"
+#include "dungeon.h"
#include "dgnevent.h"
#include "effects.h"
#include "env.h"
@@ -518,6 +519,22 @@ const zap_info zap_data[] = {
},
{
+ ZAP_PRIMAL_WAVE,
+ "great wave of water",
+ 200,
+ new calcdice_calculator<4, 14, 3, 5>,
+ new tohit_calculator<10, 1, 25>,
+ LIGHTBLUE,
+ false,
+ BEAM_WATER,
+ DCHAR_WAVY,
+ true,
+ false,
+ false,
+ 6
+ },
+
+ {
ZAP_CONFUSION,
"0",
100,
@@ -1915,6 +1932,13 @@ coord_def bolt::pos() const
return ray.pos();
}
+bool bolt::need_regress() const
+{
+ return ((is_explosion && !in_explosion_phase)
+ || drop_item
+ || origin_spell == SPELL_PRIMAL_WAVE);
+}
+
// Returns true if the beam ended due to hitting the wall.
bool bolt::hit_wall()
{
@@ -1976,8 +2000,7 @@ bool bolt::hit_wall()
{
// Regress for explosions: blow up in an open grid (if regressing
// makes any sense). Also regress when dropping items.
- if (pos() != source
- && ((is_explosion && !in_explosion_phase) || drop_item))
+ if (pos() != source && need_regress())
{
do
ray.regress();
@@ -2315,6 +2338,17 @@ int mons_adjust_flavoured(monsters *monster, bolt &pbolt, int hurted,
}
break;
+ case BEAM_WATER:
+ hurted = resist_adjust_damage(monster, pbolt.flavour,
+ monster->res_asphyx(),
+ hurted, true);
+ if (doFlavouredEffects)
+ {
+ if (!hurted)
+ simple_monster_message(monster, " shrugs off the wave.");
+ }
+ break;
+
case BEAM_COLD:
hurted = resist_adjust_damage(monster, pbolt.flavour,
monster->res_cold(),
@@ -2926,6 +2960,31 @@ void mimic_alert(monsters *mimic)
mimic->flags |= MF_KNOWN_MIMIC;
}
+void create_feat_at(coord_def center,
+ dungeon_feature_type overwriteable,
+ dungeon_feature_type newfeat)
+{
+ if (grd(center) == overwriteable)
+ dungeon_terrain_changed(center, newfeat, true, false, true);
+}
+
+void create_feat_splash(coord_def center,
+ dungeon_feature_type overwriteable,
+ dungeon_feature_type newfeat,
+ int radius,
+ int nattempts)
+{
+ // Always affect center.
+ create_feat_at(center, overwriteable, newfeat);
+ for (int i = 0; i < nattempts; ++i)
+ {
+ const coord_def newp(dgn_random_point_visible_from(center, radius));
+ if (newp.origin() || grd(newp) != overwriteable)
+ continue;
+ create_feat_at(newp, overwriteable, newfeat);
+ }
+}
+
bool bolt::is_bouncy(dungeon_feature_type feat) const
{
if (real_flavour == BEAM_CHAOS && feat_is_solid(feat))
@@ -3009,6 +3068,24 @@ void bolt::affect_endpoint()
if (is_tracer)
return;
+ if (origin_spell == SPELL_PRIMAL_WAVE) // &&coinflip()
+ {
+ if (you.see_cell(pos()))
+ {
+ mprf("The wave splashes down.");
+ noisy(25, pos());
+ }
+ else
+ {
+ noisy(25, pos(), "You hear a splash.");
+ }
+ create_feat_splash(pos(),
+ DNGN_FLOOR,
+ DNGN_SHALLOW_WATER,
+ 2,
+ random_range(1, 9, 2));
+ }
+
// FIXME: why don't these just have is_explosion set?
// They don't explode in tracers: why not?
if (name == "orb of electricity"
@@ -4312,6 +4389,9 @@ void bolt::affect_player()
internal_ouch(hurted);
range_used += range_used_on_hit(&you);
+
+ if (flavour == BEAM_WATER)
+ water_hits_actor(&you);
}
int bolt::beam_source_as_target() const
@@ -4678,6 +4758,23 @@ void bolt::monster_post_hit(monsters* mon, int dmg)
mimic_alert(mon);
else if (dmg)
beogh_follower_convert(mon, true);
+
+ if (flavour == BEAM_WATER)
+ water_hits_actor(mon);
+}
+
+void bolt::water_hits_actor(actor *act)
+{
+ const coord_def oldpos(act->pos());
+ if (knockback_actor(act))
+ {
+ if (you.can_see(act))
+ mprf("%s %s knocked back by the %s.",
+ act->name(DESC_CAP_THE).c_str(),
+ act->conj_verb("are").c_str(),
+ this->name.c_str());
+ act->apply_location_effects(oldpos, killer(), beam_source);
+ }
}
// Return true if the block succeeded (including reflections.)
@@ -4989,21 +5086,24 @@ void bolt::affect_monster(monsters* mon)
// Apply flavoured specials.
mons_adjust_flavoured(mon, *this, postac, true);
- // If the beam is an actual missile or of the MMISSILE type (Earth magic)
- // we might bleed on the floor.
- if (!engulfs
- && (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE)
- && !mon->is_summoned() && !mon->submerged())
+ // mons_adjust_flavoured may kill the monster directly.
+ if (mon->alive())
{
- // Using raw_damage instead of the flavoured one!
- // assumes DVORP_PIERCING, factor: 0.5
- const int blood = std::min(postac/2, mon->hit_points);
- bleed_onto_floor(mon->pos(), mon->type, blood, true);
+ // If the beam is an actual missile or of the MMISSILE type
+ // (Earth magic) we might bleed on the floor.
+ if (!engulfs
+ && (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE)
+ && !mon->is_summoned() && !mon->submerged())
+ {
+ // Using raw_damage instead of the flavoured one!
+ // assumes DVORP_PIERCING, factor: 0.5
+ const int blood = std::min(postac/2, mon->hit_points);
+ bleed_onto_floor(mon->pos(), mon->type, blood, true);
+ }
+ // Now hurt monster.
+ mon->hurt(agent(), final, flavour, false);
}
- // Now hurt monster.
- mon->hurt(agent(), final, flavour, false);
-
int corpse = -1;
monsters orig = *mon;
@@ -5541,6 +5641,34 @@ int bolt::range_used_on_hit(const actor* victim) const
return (used);
}
+// Checks whether the beam knocks back the supplied actor. The actor
+// should have already failed their EV check, so the save is entirely
+// body-mass-based.
+bool bolt::knockback_actor(actor *act)
+{
+ ASSERT(ray.pos() == act->pos());
+
+ const coord_def oldpos(ray.pos());
+ const ray_def ray_copy(ray);
+ ray.advance();
+
+ const coord_def newpos(ray.pos());
+ if (newpos == oldpos || actor_at(newpos) || feat_is_solid(grd(newpos))
+ || !act->can_pass_through(newpos)
+ // Save is based on target's body weight.
+ || random2(2500) < act->body_weight())
+ {
+ ray = ray_copy;
+ return false;
+ }
+
+ act->move_to_pos(newpos);
+
+ // Knockback cannot ever kill the actor directly - caller must do
+ // apply_location_effects after messaging.
+ return true;
+}
+
// Takes a bolt and refines it for use in the explosion function.
// Explosions which do not follow from beams (e.g., scrolls of
// immolation) bypass this function.
@@ -5717,7 +5845,6 @@ static sweep_type _radial_sweep(int r)
}
#define MAX_EXPLOSION_RADIUS 9
-
// Returns true if we saw something happening.
bool bolt::explode(bool show_more, bool hole_in_the_middle)
{
@@ -6048,21 +6175,24 @@ bool bolt::nice_to(const monsters *mon) const
//
// TODO: Eventually it'd be nice to have a proper factory for these things
// (extended from setup_mons_cast() and zapping() which act as limited ones).
-bolt::bolt() : range(-2), type('*'), colour(BLACK), flavour(BEAM_MAGIC),
- real_flavour(BEAM_MAGIC), drop_item(false), item(NULL), source(), target(),
- damage(0, 0), ench_power(0), hit(0), thrower(KILL_MISC), ex_size(0),
- beam_source(MHITNOT), source_name(), name(), short_name(), hit_verb(),
- loudness(0), noise_msg(), is_beam(false), is_explosion(false),
- is_big_cloud(false), aimed_at_spot(false), aux_source(),
- affects_nothing(false), affects_items(true), effect_known(true),
- draw_delay(15), special_explosion(NULL), range_funcs(), damage_funcs(),
- hit_funcs(), aoe_funcs(), obvious_effect(false), seen(false), heard(false),
- path_taken(), range_used(0), is_tracer(false), aimed_at_feet(false),
- msg_generated(false), passed_target(false), in_explosion_phase(false),
- smart_monster(false), can_see_invis(false), attitude(ATT_HOSTILE),
- foe_ratio(0), chose_ray(false), beam_cancelled(false),
- dont_stop_player(false), bounces(false), bounce_pos(), reflections(0),
- reflector(-1), auto_hit(false)
+bolt::bolt() : origin_spell(SPELL_NO_SPELL),
+ range(-2), type('*'), colour(BLACK), flavour(BEAM_MAGIC),
+ real_flavour(BEAM_MAGIC), drop_item(false), item(NULL),
+ source(), target(), damage(0, 0), ench_power(0), hit(0),
+ thrower(KILL_MISC), ex_size(0), beam_source(MHITNOT),
+ source_name(), name(), short_name(), hit_verb(),
+ loudness(0), noise_msg(), is_beam(false), is_explosion(false),
+ is_big_cloud(false), aimed_at_spot(false), aux_source(),
+ affects_nothing(false), affects_items(true), effect_known(true),
+ draw_delay(15), special_explosion(NULL), range_funcs(),
+ damage_funcs(), hit_funcs(), aoe_funcs(), obvious_effect(false),
+ seen(false), heard(false), path_taken(), range_used(0),
+ is_tracer(false), aimed_at_feet(false), msg_generated(false),
+ passed_target(false), in_explosion_phase(false),
+ smart_monster(false), can_see_invis(false),
+ attitude(ATT_HOSTILE), foe_ratio(0), chose_ray(false),
+ beam_cancelled(false), dont_stop_player(false), bounces(false),
+ bounce_pos(), reflections(0), reflector(-1), auto_hit(false)
{
}
@@ -6191,6 +6321,7 @@ std::string beam_type_name(beam_type type)
case BEAM_POTION_COLD: // fall through
case BEAM_COLD: return ("cold");
+ case BEAM_WATER: return ("water");
case BEAM_MAGIC: return ("magic");
case BEAM_ELECTRICITY: return ("electricity");
diff --git a/crawl-ref/source/beam.h b/crawl-ref/source/beam.h
index 61f5640d07..7c60170f41 100644
--- a/crawl-ref/source/beam.h
+++ b/crawl-ref/source/beam.h
@@ -62,6 +62,8 @@ typedef bool (*explosion_aoe_func)(bolt& beam, const coord_def& target);
struct bolt
{
// INPUT parameters set by caller
+ spell_type origin_spell; // may be SPELL_NO_SPELL for non-spell
+ // beams.
int range;
unsigned type; // missile gfx
int colour;
@@ -192,6 +194,7 @@ public:
// Return whether any affected cell was seen.
bool explode(bool show_more = true, bool hole_in_the_middle = false);
+ bool knockback_actor(actor *actor);
private:
void do_fire();
@@ -213,6 +216,7 @@ private:
bool nasty_to(const monsters* mon) const;
bool nice_to(const monsters* mon) const;
bool found_player() const;
+ bool need_regress() const;
int beam_source_as_target() const;
int range_used_on_hit(const actor* victim) const;
@@ -241,6 +245,8 @@ public:
void affect_place_explosion_clouds();
void affect_endpoint();
+ void water_hits_actor(actor *act);
+
// Stuff when a monster or player is hit.
void affect_player_enchantment();
void tracer_affect_player();
diff --git a/crawl-ref/source/dat/descript/monsters.txt b/crawl-ref/source/dat/descript/monsters.txt
index da05efc32f..579bbeb6a3 100644
--- a/crawl-ref/source/dat/descript/monsters.txt
+++ b/crawl-ref/source/dat/descript/monsters.txt
@@ -1048,7 +1048,19 @@ This tall and powerful demon is Mara, Lord of Illusions, mighty among dreamers.
%%%%
merfolk
-Half fish, half man, the merfolk are citizens of both water and land, and they'll fiercely protect their chosen territory.
+Half fish, half man, the merfolk are citizens of both water and land, and fierce protectors of their chosen territory.
+%%%%
+merfolk impaler
+
+A powerfully muscled merfolk warrior, bearing a great trident.
+%%%%
+merfolk aquamancer
+
+A slender merfolk mystic with unusually webby hands. Its form shifts and glistens as if seen through ocean spray.
+%%%%
+merfolk javelineer
+
+A sinewy merfolk fighter with a piercing gaze and a large bundle of javelins.
%%%%
mermaid
diff --git a/crawl-ref/source/dgn-shoals.cc b/crawl-ref/source/dgn-shoals.cc
index a3a2b12ea9..b91dc3a195 100644
--- a/crawl-ref/source/dgn-shoals.cc
+++ b/crawl-ref/source/dgn-shoals.cc
@@ -1,6 +1,7 @@
#include "AppHdr.h"
#include "branch.h"
+#include "colour.h"
#include "coord.h"
#include "coordit.h"
#include "dungeon.h"
@@ -9,15 +10,21 @@
#include "flood_find.h"
#include "items.h"
#include "maps.h"
+#include "mgen_data.h"
+#include "mon-iter.h"
#include "mon-place.h"
#include "mon-util.h"
#include "random.h"
#include "terrain.h"
+#include "view.h"
#include <algorithm>
#include <vector>
#include <cmath>
+typedef FixedArray<bool, GXM, GYM> grid_bool;
+typedef FixedArray<short, GXM, GYM> grid_short;
+
const char *ENVP_SHOALS_TIDE_KEY = "shoals-tide-height";
const char *ENVP_SHOALS_TIDE_VEL = "shoals-tide-velocity";
@@ -38,6 +45,23 @@ const int N_PERTURB_OFFSET_HIGH = 45;
const int PERTURB_OFFSET_RADIUS_LOW = 2;
const int PERTURB_OFFSET_RADIUS_HIGH = 7;
+// The raw tide height / TIDE_MULTIPLIER is the actual tide height. The higher
+// the tide multiplier, the slower the tide advances and recedes. A multiplier
+// of X implies that the tide will advance visibly about once in X turns.
+const int TIDE_MULTIPLIER = 30;
+
+const int LOW_TIDE = -18 * TIDE_MULTIPLIER;
+const int HIGH_TIDE = 25 * TIDE_MULTIPLIER;
+
+// The highest a tide can be called by a tide caller such as Ilsuiw.
+const int HIGH_CALLED_TIDE = 25;
+const int TIDE_DECEL_MARGIN = 8;
+const int PEAK_TIDE_ACCEL = 2;
+
+// The area around the user of a call tide spell that is subject to
+// local tide elevation.
+const int TIDE_CALL_RADIUS = 8;
+
const int _shoals_margin = 6;
enum shoals_height_thresholds
@@ -56,11 +80,10 @@ enum tide_direction
};
static tide_direction _shoals_tide_direction;
-
-static double _to_radians(int degrees)
-{
- return degrees * M_PI / 180;
-}
+static monsters *tide_caller = NULL;
+static coord_def tide_caller_pos;
+static long tide_called_turns = 0L;
+static int tide_called_peak = 0;
static dungeon_feature_type _shoals_feature_by_height(int height)
{
@@ -118,33 +141,9 @@ static void _shoals_init_heights()
shoals_heights(*ri) = SHT_SHALLOW_WATER - 3;
}
-static double _angle_fuzz()
+static coord_def _random_point_from(const coord_def &c, int radius)
{
- double fuzz = _to_radians(random2(15));
- return coinflip()? fuzz : -fuzz;
-}
-
-static coord_def _random_point_from(const coord_def &c, int radius,
- int directed_angle = -1)
-{
- const double directed_radians(
- directed_angle == -1? 0.0 : _to_radians(directed_angle));
- int attempts = 70;
- while (attempts-- > 0)
- {
- const double angle =
- directed_angle == -1? _to_radians(random2(360))
- : ((coinflip()? directed_radians : -directed_radians)
- + _angle_fuzz());
- coord_def res = c + coord_def(radius * cos(angle),
- radius * sin(angle));
- if (res.x >= _shoals_margin && res.x < GXM - _shoals_margin
- && res.y >= _shoals_margin && res.y < GYM - _shoals_margin)
- {
- return res;
- }
- }
- return coord_def();
+ return dgn_random_point_from(c, radius, _shoals_margin);
}
static coord_def _random_point(int offset = 0)
@@ -224,12 +223,13 @@ static void _shoals_build_cliff()
if (in_bounds(cliffc))
{
const int length = random_range(6, 15);
- double angle = _to_radians(random2(360));
+ double angle = dgn_degrees_to_radians(random2(360));
for (int i = 0; i < length; i += 3)
{
int distance = i - length / 2;
- coord_def place = cliffc + coord_def(distance * cos(angle),
- distance * sin(angle));
+ coord_def place =
+ cliffc + coord_def(static_cast<int>(distance * cos(angle)),
+ static_cast<int>(distance * sin(angle)));
coord_def fuzz = coord_def(random_range(-2, 2),
random_range(-2, 2));
place += fuzz;
@@ -401,7 +401,8 @@ static void _shoals_furniture(int margin)
const coord_def p = _pick_shoals_island_distant_from(c);
// Place the rune
const map_def *vault = random_map_for_tag("shoal_rune");
- dgn_place_map(vault, false, true, p);
+ dgn_ensure_vault_placed(dgn_place_map(vault, false, true, p),
+ false);
const int nhuts = std::min(8, int(_shoals_islands.size()));
for (int i = 2; i < nhuts; ++i)
@@ -464,6 +465,334 @@ static void _shoals_deepen_edges()
}
}
+static int _shoals_contiguous_feature_flood(
+ FixedArray<short, GXM, GYM> &rmap,
+ coord_def c,
+ dungeon_feature_type feat,
+ int nregion,
+ int size_limit)
+{
+ std::vector<coord_def> visit;
+ visit.push_back(c);
+ int npoints = 1;
+ for (size_t i = 0; i < visit.size() && npoints < size_limit; ++i)
+ {
+ const coord_def p(visit[i]);
+ rmap(p) = nregion;
+
+ if (npoints < size_limit)
+ {
+ for (adjacent_iterator ai(p); ai && npoints < size_limit; ++ai)
+ {
+ const coord_def adj(*ai);
+ if (in_bounds(adj) && !rmap(adj) && grd(adj) == feat
+ && unforbidden(adj, MMT_VAULT))
+ {
+ rmap(adj) = nregion;
+ visit.push_back(adj);
+ ++npoints;
+ }
+ }
+ }
+ }
+ return npoints;
+}
+
+static coord_def _shoals_region_center(
+ FixedArray<short, GXM, GYM> &rmap,
+ coord_def c)
+{
+ const int nregion(rmap(c));
+ int nseen = 0;
+
+ double cx = 0.0, cy = 0.0;
+ std::vector<coord_def> visit;
+ visit.push_back(c);
+ FixedArray<bool, GXM, GYM> visited(false);
+ for (size_t i = 0; i < visit.size(); ++i)
+ {
+ const coord_def p(visit[i]);
+ visited(p) = true;
+
+ ++nseen;
+ if (nseen == 1)
+ {
+ cx = p.x;
+ cy = p.y;
+ }
+ else
+ {
+ cx = (cx * (nseen - 1) + p.x) / nseen;
+ cy = (cy * (nseen - 1) + p.y) / nseen;
+ }
+
+ for (adjacent_iterator ai(p); ai; ++ai)
+ {
+ const coord_def adj(*ai);
+ if (in_bounds(adj) && !visited(adj) && rmap(adj) == nregion)
+ {
+ visited(adj) = true;
+ visit.push_back(adj);
+ }
+ }
+ }
+
+ const coord_def cgravity(static_cast<int>(cx), static_cast<int>(cy));
+ coord_def closest_to_center;
+ int closest_distance = 0;
+ for (int i = 0, size = visit.size(); i < size; ++i)
+ {
+ const coord_def p(visit[i]);
+ const int dist2 = (p - cgravity).abs();
+ if (closest_to_center.origin() || closest_distance > dist2)
+ {
+ closest_to_center = p;
+ closest_distance = dist2;
+ }
+ }
+ return closest_to_center;
+}
+
+struct weighted_region
+{
+ int weight;
+ coord_def pos;
+
+ weighted_region(int _weight, coord_def _pos) : weight(_weight), pos(_pos)
+ {
+ }
+};
+
+static std::vector<weighted_region>
+_shoals_point_feat_cluster(dungeon_feature_type feat,
+ const int wanted_count,
+ grid_short &region_map)
+{
+ std::vector<weighted_region> regions;
+ int region = 1;
+ for (rectangle_iterator ri(1); ri; ++ri)
+ {
+ coord_def c(*ri);
+ if (!region_map(c) && grd(c) == feat
+ && unforbidden(c, MMT_VAULT))
+ {
+ const int featcount =
+ _shoals_contiguous_feature_flood(region_map,
+ c,
+ feat,
+ region++,
+ wanted_count * 3 / 2);
+ if (featcount >= wanted_count)
+ regions.push_back(weighted_region(featcount, c));
+ }
+ }
+ return (regions);
+}
+
+static coord_def _shoals_pick_region(
+ grid_short &region_map,
+ const std::vector<weighted_region> &regions)
+{
+ if (regions.empty())
+ return coord_def();
+ return _shoals_region_center(region_map,
+ regions[random2(regions.size())].pos);
+}
+
+static void _shoals_make_plant_at(coord_def p)
+{
+ // [ds] Why is hostile_at() saddled with unnecessary parameters
+ // related to summoning?
+ mons_place(mgen_data::hostile_at(MONS_PLANT, "", false, 0, 0, p));
+}
+
+static bool _shoals_plantworthy_feat(dungeon_feature_type feat)
+{
+ return (feat == DNGN_SHALLOW_WATER || feat == DNGN_FLOOR);
+}
+
+static void _shoals_make_plant_near(coord_def c, int radius,
+ dungeon_feature_type preferred_feat,
+ grid_bool *verboten)
+{
+ const int ntries = 5;
+ for (int i = 0; i < ntries; ++i)
+ {
+ const coord_def plant_place(_random_point_from(c, random2(1 + radius)));
+ if (!plant_place.origin()
+ && !monster_at(plant_place))
+ {
+ const dungeon_feature_type feat(grd(plant_place));
+ if (_shoals_plantworthy_feat(feat)
+ && (feat == preferred_feat || coinflip())
+ && (!verboten || !(*verboten)(plant_place)))
+ {
+ _shoals_make_plant_at(plant_place);
+ return;
+ }
+ }
+ }
+}
+
+static void _shoals_plant_cluster(coord_def c, int nplants, int radius,
+ dungeon_feature_type favoured_feat,
+ grid_bool *verboten)
+{
+ for (int i = 0; i < nplants; ++i)
+ _shoals_make_plant_near(c, radius, favoured_feat, verboten);
+}
+
+static void _shoals_plant_supercluster(coord_def c,
+ dungeon_feature_type favoured_feat,
+ grid_bool *verboten = NULL)
+{
+ _shoals_plant_cluster(c, random_range(10, 25, 2),
+ random_range(3, 9), favoured_feat,
+ verboten);
+
+ const int nadditional_clusters(std::max(0, random_range(-1, 4, 2)));
+ for (int i = 0; i < nadditional_clusters; ++i)
+ {
+ const coord_def satellite(
+ _random_point_from(c, random_range(2, 12)));
+ if (!satellite.origin())
+ _shoals_plant_cluster(satellite, random_range(5, 23, 2),
+ random_range(2, 7),
+ favoured_feat,
+ verboten);
+ }
+}
+
+static void _shoals_generate_water_plants(coord_def mangrove_central)
+{
+ if (!mangrove_central.origin())
+ _shoals_plant_supercluster(mangrove_central, DNGN_SHALLOW_WATER);
+}
+
+struct coord_dbl
+{
+ double x, y;
+
+ coord_dbl(double _x, double _y) : x(_x), y(_y) { }
+ coord_dbl operator + (const coord_dbl &o) const
+ {
+ return coord_dbl(x + o.x, y + o.y);
+ }
+ coord_dbl &operator += (const coord_dbl &o)
+ {
+ x += o.x;
+ y += o.y;
+ return *this;
+ }
+};
+
+static coord_def _int_coord(const coord_dbl &c)
+{
+ return coord_def(static_cast<int>(c.x), static_cast<int>(c.y));
+}
+
+static std::vector<coord_def> _shoals_windshadows(grid_bool &windy)
+{
+ const int wind_angle_degrees = random2(360);
+ const double wind_angle(dgn_degrees_to_radians(wind_angle_degrees));
+ const coord_dbl wi(cos(wind_angle), sin(wind_angle));
+ const double epsilon = 1e-5;
+
+ std::vector<coord_dbl> wind_points;
+ if (wi.x > epsilon || wi.x < -epsilon)
+ {
+ for (int y = 1; y < GYM - 1; ++y)
+ wind_points.push_back(coord_dbl(wi.x > epsilon ? 1 : GXM - 2, y));
+ }
+ if (wi.y > epsilon || wi.y < -epsilon)
+ {
+ for (int x = 1; x < GXM - 1; ++x)
+ wind_points.push_back(coord_dbl(x, wi.y > epsilon ? 1 : GYM - 2));
+ }
+
+ for (size_t i = 0; i < wind_points.size(); ++i)
+ {
+ const coord_def here(_int_coord(wind_points[i]));
+ windy(here) = true;
+
+ coord_dbl next = wind_points[i] + wi;
+ while (_int_coord(next) == here)
+ next += wi;
+
+ const coord_def nextp(_int_coord(next));
+ if (in_bounds(nextp) && !windy(nextp) && !feat_is_solid(grd(nextp)))
+ {
+ windy(nextp) = true;
+ wind_points.push_back(next);
+ }
+ }
+
+ // To avoid plants cropping up inside vaults, mark everything inside
+ // vaults as "windy".
+ for (rectangle_iterator ri(1); ri; ++ri)
+ if (!unforbidden(*ri, MMT_VAULT))
+ windy(*ri) = true;
+
+ // Now we know the places in the wind shadow:
+ std::vector<coord_def> wind_shadows;
+ for (rectangle_iterator ri(1); ri; ++ri)
+ {
+ const coord_def p(*ri);
+ if (!windy(p) && grd(p) == DNGN_FLOOR
+ && (_has_adjacent_feat(p, DNGN_STONE_WALL)
+ || _has_adjacent_feat(p, DNGN_ROCK_WALL)))
+ wind_shadows.push_back(p);
+ }
+ return wind_shadows;
+}
+
+static void _shoals_generate_wind_sheltered_plants(
+ std::vector<coord_def> &places, grid_bool &windy)
+{
+ if (places.empty())
+ return;
+
+ const int chosen = random2(places.size());
+ const coord_def spot = places[random2(places.size())];
+ places.erase(places.begin() + chosen);
+
+ _shoals_plant_supercluster(spot, DNGN_FLOOR, &windy);
+}
+
+static void _shoals_generate_flora()
+{
+ // Water clusters are groups of plants clustered near the water.
+ // Wind clusters are groups of plants clustered in wind shadow --
+ // possibly because they can grow better without being exposed to the
+ // strong winds of the Shoals.
+ //
+ // Yeah, the strong winds aren't there yet, but they could be!
+ //
+ const int n_water_clusters = std::max(0, random_range(-1, 6, 2));
+ const int n_wind_clusters = std::max(0, random_range(-2, 2, 2));
+
+ if (n_water_clusters)
+ {
+ grid_short region_map(0);
+ std::vector<weighted_region> regions(
+ _shoals_point_feat_cluster(DNGN_SHALLOW_WATER, 6, region_map));
+
+ for (int i = 0; i < n_water_clusters; ++i)
+ {
+ const coord_def p(_shoals_pick_region(region_map, regions));
+ _shoals_generate_water_plants(p);
+ }
+ }
+
+ if (n_wind_clusters)
+ {
+ grid_bool windy(false);
+ std::vector<coord_def> wind_shadows = _shoals_windshadows(windy);
+ for (int i = 0; i < n_wind_clusters; ++i)
+ _shoals_generate_wind_sheltered_plants(wind_shadows, windy);
+ }
+}
+
void prepare_shoals(int level_number)
{
dgn_Build_Method += make_stringf(" shoals+ [%d]", level_number);
@@ -481,6 +810,9 @@ void prepare_shoals(int level_number)
_shoals_deepen_edges();
_shoals_smooth_water();
_shoals_furniture(_shoals_margin);
+
+ // This has to happen after placing shoal rune vault!
+ _shoals_generate_flora();
}
// Search the map for vaults and set the terrain heights for features
@@ -508,18 +840,16 @@ void shoals_postprocess_level()
}
}
-// The raw tide height / TIDE_MULTIPLIER is the actual tide height. The higher
-// the tide multiplier, the slower the tide advances and recedes. A multiplier
-// of X implies that the tide will advance visibly about once in X turns.
-const int TIDE_MULTIPLIER = 30;
-
-const int LOW_TIDE = -18 * TIDE_MULTIPLIER;
-const int HIGH_TIDE = 25 * TIDE_MULTIPLIER;
-const int TIDE_DECEL_MARGIN = 8;
-const int START_TIDE_RISE = 2;
-
static void _shoals_run_tide(int &tide, int &acc)
{
+ // If someone is calling the tide, the acceleration is clamped high.
+ if (tide_caller)
+ acc = 15;
+ // If there's no tide caller and our acceleration is suspiciously high,
+ // reset it to a falling tide at peak acceleration.
+ else if (abs(acc) > PEAK_TIDE_ACCEL)
+ acc = -PEAK_TIDE_ACCEL;
+
tide += acc;
tide = std::max(std::min(tide, HIGH_TIDE), LOW_TIDE);
if ((tide == HIGH_TIDE && acc > 0)
@@ -528,7 +858,7 @@ static void _shoals_run_tide(int &tide, int &acc)
bool in_decel_margin =
(abs(tide - HIGH_TIDE) < TIDE_DECEL_MARGIN)
|| (abs(tide - LOW_TIDE) < TIDE_DECEL_MARGIN);
- if ((abs(acc) == 2) == in_decel_margin)
+ if ((abs(acc) > 1) == in_decel_margin)
acc = in_decel_margin? acc / 2 : acc * 2;
}
@@ -648,6 +978,23 @@ static void _shoals_apply_tide_at(coord_def c, int tide)
_shoals_apply_tide_feature_at(c, newfeat);
}
+static int _shoals_tide_at(coord_def pos, int base_tide)
+{
+ if (!tide_caller)
+ return base_tide;
+
+ const int rl_distance = grid_distance(pos, tide_caller_pos);
+ if (rl_distance > TIDE_CALL_RADIUS)
+ return base_tide;
+
+ const int distance =
+ static_cast<int>(sqrt((pos - tide_caller->pos()).abs()));
+ if (distance > TIDE_CALL_RADIUS)
+ return base_tide;
+
+ return (base_tide + std::max(0, tide_called_peak - distance * 3));
+}
+
static void _shoals_apply_tide(int tide)
{
std::vector<coord_def> pages[2];
@@ -672,7 +1019,7 @@ static void _shoals_apply_tide(int tide)
coord_def c(cpage[i]);
const bool was_wet(_shoals_tide_passable_feat(grd(c)));
seen_points(c) = true;
- _shoals_apply_tide_at(c, tide);
+ _shoals_apply_tide_at(c, _shoals_tide_at(c, tide));
const bool is_wet(feat_is_water(grd(c)));
// Only squares that were wet (before applying tide
@@ -709,13 +1056,22 @@ static void _shoals_init_tide()
if (!env.properties.exists(ENVP_SHOALS_TIDE_KEY))
{
env.properties[ENVP_SHOALS_TIDE_KEY] = short(0);
- env.properties[ENVP_SHOALS_TIDE_VEL] = short(2);
+ env.properties[ENVP_SHOALS_TIDE_VEL] = short(PEAK_TIDE_ACCEL);
}
}
-void shoals_apply_tides(int turns_elapsed)
+static monsters *_shoals_find_tide_caller()
+{
+ for (monster_iterator mi; mi; ++mi)
+ if (mi->has_ench(ENCH_TIDE))
+ return *mi;
+ return NULL;
+}
+
+void shoals_apply_tides(int turns_elapsed, bool force)
{
- if (!player_in_branch(BRANCH_SHOALS) || !turns_elapsed
+ if (!player_in_branch(BRANCH_SHOALS)
+ || (!turns_elapsed && !force)
|| !env.heightmap.get())
{
return;
@@ -727,6 +1083,20 @@ void shoals_apply_tides(int turns_elapsed)
turns_elapsed = turns_elapsed % TIDE_UNIT + TIDE_UNIT;
_shoals_init_tide();
+
+ unwind_var<monsters*> tide_caller_unwind(tide_caller,
+ _shoals_find_tide_caller());
+ if (tide_caller)
+ {
+ tide_called_turns = tide_caller->props[TIDE_CALL_TURN].get_long();
+ tide_called_turns = you.num_turns - tide_called_turns;
+ if (tide_called_turns < 1L)
+ tide_called_turns = 1L;
+ tide_called_peak = std::min(HIGH_CALLED_TIDE,
+ int(tide_called_turns * 5));
+ tide_caller_pos = tide_caller->pos();
+ }
+
int tide = env.properties[ENVP_SHOALS_TIDE_KEY].get_short();
int acc = env.properties[ENVP_SHOALS_TIDE_VEL].get_short();
const int old_tide = tide;
@@ -734,10 +1104,24 @@ void shoals_apply_tides(int turns_elapsed)
_shoals_run_tide(tide, acc);
env.properties[ENVP_SHOALS_TIDE_KEY] = short(tide);
env.properties[ENVP_SHOALS_TIDE_VEL] = short(acc);
- if (old_tide / TIDE_MULTIPLIER != tide / TIDE_MULTIPLIER)
+ if (force
+ || tide_caller
+ || old_tide / TIDE_MULTIPLIER != tide / TIDE_MULTIPLIER)
{
_shoals_tide_direction =
tide > old_tide ? TIDE_RISING : TIDE_FALLING;
_shoals_apply_tide(tide / TIDE_MULTIPLIER);
}
}
+
+void shoals_release_tide(monsters *mons)
+{
+ if (player_in_branch(BRANCH_SHOALS)
+ && player_can_hear(you.pos()))
+ {
+ mprf(MSGCH_SOUND, "The tide is released from %s call.",
+ mons->name(DESC_NOCAP_YOUR, true).c_str());
+ flash_view_delay(ETC_WATER, 150);
+ shoals_apply_tides(0, true);
+ }
+}
diff --git a/crawl-ref/source/dgn-shoals.h b/crawl-ref/source/dgn-shoals.h
index f558f6606f..1d14c620f2 100644
--- a/crawl-ref/source/dgn-shoals.h
+++ b/crawl-ref/source/dgn-shoals.h
@@ -3,6 +3,7 @@
void prepare_shoals(int level_number);
void shoals_postprocess_level();
-void shoals_apply_tides(int turns_elapsed);
+void shoals_apply_tides(int turns_elapsed, bool force = false);
+void shoals_release_tide(monsters *caller);
#endif
diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc
index 48268f2067..93e5086ef6 100644
--- a/crawl-ref/source/dungeon.cc
+++ b/crawl-ref/source/dungeon.cc
@@ -13,6 +13,7 @@
#include <set>
#include <sstream>
#include <algorithm>
+#include <cmath>
#include "abyss.h"
#include "artefact.h"
@@ -897,8 +898,8 @@ void dgn_register_place(const vault_placement &place, bool register_vault)
#endif
}
-static bool _ensure_vault_placed(bool vault_success,
- bool disable_further_vaults)
+bool dgn_ensure_vault_placed(bool vault_success,
+ bool disable_further_vaults)
{
if (!vault_success)
dgn_level_vetoed = true;
@@ -909,9 +910,9 @@ static bool _ensure_vault_placed(bool vault_success,
static bool _ensure_vault_placed_ex( bool vault_success, const map_def *vault )
{
- return _ensure_vault_placed( vault_success,
- (!vault->has_tag("extra")
- && vault->orient == MAP_ENCOMPASS) );
+ return dgn_ensure_vault_placed( vault_success,
+ (!vault->has_tag("extra")
+ && vault->orient == MAP_ENCOMPASS) );
}
static coord_def _find_level_feature(int feat)
@@ -1766,7 +1767,7 @@ static void _build_overflow_temples(int level_number)
// find the overflow temple map, so don't veto the level.
return;
- if (!_ensure_vault_placed(_build_vaults(level_number, vault), false))
+ if (!dgn_ensure_vault_placed(_build_vaults(level_number, vault), false))
{
#ifdef DEBUG_TEMPLES
mprf(MSGCH_DIAGNOSTICS, "Couldn't place overlfow temple '%s', "
@@ -2245,7 +2246,7 @@ static builder_rc_type _builder_by_type(int level_number, char level_type)
pandemon_level_names[which_demon]);
}
- _ensure_vault_placed( _build_vaults(level_number, vault), true );
+ dgn_ensure_vault_placed( _build_vaults(level_number, vault), true );
}
else
{
@@ -2322,7 +2323,7 @@ static void _portal_vault_level(int level_number)
dgn_replace_area(0, 0, GXM-1, GYM-1, DNGN_ROCK_WALL,
vault->border_fill_type);
- _ensure_vault_placed( _build_vaults(level_number, vault), true );
+ dgn_ensure_vault_placed( _build_vaults(level_number, vault), true );
}
else
{
@@ -6002,7 +6003,7 @@ static bool _plan_1(int level_number)
ASSERT(vault);
bool success = _build_vaults(level_number, vault);
- _ensure_vault_placed(success, false);
+ dgn_ensure_vault_placed(success, false);
return false;
}
@@ -6016,7 +6017,7 @@ static bool _plan_2(int level_number)
ASSERT(vault);
bool success = _build_vaults(level_number, vault);
- _ensure_vault_placed(success, false);
+ dgn_ensure_vault_placed(success, false);
return false;
}
@@ -6030,7 +6031,7 @@ static bool _plan_3(int level_number)
ASSERT(vault);
bool success = _build_vaults(level_number, vault);
- _ensure_vault_placed(success, false);
+ dgn_ensure_vault_placed(success, false);
return true;
}
@@ -6174,7 +6175,7 @@ static bool _plan_6(int level_number)
ASSERT(vault);
bool success = _build_vaults(level_number, vault);
- _ensure_vault_placed(success, false);
+ dgn_ensure_vault_placed(success, false);
// This "back door" is often one of the easier ways to get out of
// pandemonium... the easiest is to use the banish spell.
@@ -7619,6 +7620,47 @@ static coord_def _dgn_find_closest_to_stone_stairs(coord_def base_pos)
return (np.nearest);
}
+
+double dgn_degrees_to_radians(int degrees)
+{
+ return degrees * M_PI / 180;
+}
+
+coord_def dgn_random_point_from(const coord_def &c, int radius, int margin)
+{
+ int attempts = 70;
+ while (attempts-- > 0)
+ {
+ const double angle = dgn_degrees_to_radians(random2(360));
+ const coord_def res =
+ c + coord_def(static_cast<int>(radius * cos(angle)),
+ static_cast<int>(radius * sin(angle)));
+ if (res.x >= margin && res.x < GXM - margin
+ && res.y >= margin && res.y < GYM - margin)
+ {
+ return res;
+ }
+ }
+ return coord_def();
+}
+
+coord_def dgn_random_point_visible_from(const coord_def &c,
+ int radius,
+ int margin,
+ int tries)
+{
+ while (tries-- > 0)
+ {
+ const coord_def point = dgn_random_point_from(c, radius, margin);
+ if (point.origin())
+ continue;
+ if (!cell_see_cell(c, point))
+ continue;
+ return point;
+ }
+ return coord_def();
+}
+
coord_def dgn_find_feature_marker(dungeon_feature_type feat)
{
std::vector<map_marker*> markers = env.markers.get_all();
diff --git a/crawl-ref/source/dungeon.h b/crawl-ref/source/dungeon.h
index bc140c3f2e..ded9cea53a 100644
--- a/crawl-ref/source/dungeon.h
+++ b/crawl-ref/source/dungeon.h
@@ -179,6 +179,12 @@ bool builder(int level_number, int level_type);
void dgn_flush_map_memory();
+double dgn_degrees_to_radians(int degrees);
+coord_def dgn_random_point_from(const coord_def &c, int radius, int margin = 1);
+coord_def dgn_random_point_visible_from(const coord_def &c,
+ int radius,
+ int margin = 1,
+ int tries = 5);
coord_def dgn_find_feature_marker(dungeon_feature_type feat);
// Set floor/wall colour based on the mons_alloc array. Used for
@@ -268,6 +274,9 @@ void dgn_replace_area(int sx, int sy, int ex, int ey,
dungeon_feature_type feature,
unsigned mmask = 0, bool needs_update = false);
+bool dgn_ensure_vault_placed(bool vault_success,
+ bool disable_further_vaults);
+
inline int count_feature_in_box( const coord_def& p1, const coord_def& p2,
dungeon_feature_type feat )
{
diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h
index 2db6cbe37d..27560aa979 100644
--- a/crawl-ref/source/enum.h
+++ b/crawl-ref/source/enum.h
@@ -199,23 +199,24 @@ enum beam_type // beam[].flavour
BEAM_MMISSILE, // and similarly irresistible things
BEAM_FIRE,
BEAM_COLD,
- BEAM_MAGIC, // 5
+ BEAM_WATER,
+ BEAM_MAGIC,
BEAM_ELECTRICITY,
BEAM_POISON,
BEAM_NEG,
BEAM_ACID,
- BEAM_MIASMA, // 10
+ BEAM_MIASMA,
BEAM_SPORE,
BEAM_POISON_ARROW,
BEAM_HELLFIRE,
BEAM_NAPALM,
- BEAM_STEAM, // 15
+ BEAM_STEAM,
BEAM_ENERGY,
BEAM_HOLY,
BEAM_FRAG,
BEAM_LAVA,
- BEAM_ICE, // 20
+ BEAM_ICE,
BEAM_NUKE,
BEAM_RANDOM, // currently translates into FIRE..ACID
BEAM_CHAOS,
@@ -223,22 +224,22 @@ enum beam_type // beam[].flavour
// Enchantments
BEAM_SLOW,
BEAM_FIRST_ENCHANTMENT = BEAM_SLOW,
- BEAM_HASTE, // 25
+ BEAM_HASTE,
BEAM_MIGHT,
BEAM_HEALING,
BEAM_PARALYSIS,
BEAM_CONFUSION,
- BEAM_INVISIBILITY, // 30
+ BEAM_INVISIBILITY,
BEAM_DIGGING,
BEAM_TELEPORT,
BEAM_POLYMORPH,
BEAM_CHARM,
- BEAM_BANISH, // 35
+ BEAM_BANISH,
BEAM_DEGENERATE,
BEAM_ENSLAVE_UNDEAD,
BEAM_ENSLAVE_SOUL,
BEAM_PAIN,
- BEAM_DISPEL_UNDEAD, // 40
+ BEAM_DISPEL_UNDEAD,
BEAM_DISINTEGRATION,
BEAM_ENSLAVE_DEMON,
BEAM_BLINK,
@@ -1253,6 +1254,7 @@ enum enchant_type
ENCH_SPORE_PRODUCTION, // 35
ENCH_SLOUCH,
ENCH_SWIFT,
+ ENCH_TIDE,
// Update enchantment names in mon-util.cc when adding or removing
// enchantments.
@@ -1818,6 +1820,12 @@ enum monster_type // (int) menv[].type
MONS_TOADSTOOL,
MONS_BUSH,
MONS_BALLISTOMYCETE, // 200
+
+ // Shoals guardians
+ MONS_MERFOLK_IMPALER,
+ MONS_MERFOLK_AQUAMANCER,
+ MONS_MERFOLK_JAVELINEER,
+
//jmf: end new monsters
MONS_WHITE_IMP = 220, // 220
MONS_LEMURE,
@@ -2319,6 +2327,7 @@ enum mon_spellbook_type
MST_HAROLD,
MST_MARA,
MST_MARA_FAKE,
+ MST_MERFOLK_AQUAMANCER,
MST_TEST_SPAWNER = 200,
NUM_MSTYPES,
@@ -2921,6 +2930,8 @@ enum spell_type
SPELL_FAKE_MARA_SUMMON,
SPELL_SUMMON_RAKSHASA,
SPELL_SUMMON_PLAYER_GHOST,
+ SPELL_PRIMAL_WAVE,
+ SPELL_CALL_TIDE,
NUM_SPELLS
};
@@ -3111,6 +3122,7 @@ enum zap_type
ZAP_PARALYSIS,
ZAP_FIRE,
ZAP_COLD,
+ ZAP_PRIMAL_WAVE,
ZAP_CONFUSION,
ZAP_INVISIBILITY,
ZAP_DIGGING,
diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h
index 5f4f5e7e40..1c3a962d5d 100644
--- a/crawl-ref/source/externs.h
+++ b/crawl-ref/source/externs.h
@@ -92,6 +92,11 @@ class KillMaster;
class ghost_demon;
struct glyph;
+template <typename Z> inline Z sgn(Z x)
+{
+ return (x < 0 ? -1 : (x > 0 ? 1 : 0));
+}
+
struct coord_def
{
int x;
@@ -215,6 +220,11 @@ struct coord_def
return (copy *= mul);
}
+ coord_def sgn() const
+ {
+ return coord_def(::sgn(x), ::sgn(y));
+ }
+
int abs() const
{
return (x * x + y * y);
diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc
index c88ca09f3b..0df1de443c 100644
--- a/crawl-ref/source/fight.cc
+++ b/crawl-ref/source/fight.cc
@@ -2161,6 +2161,8 @@ static bool is_boolean_resist(beam_type flavour)
case BEAM_ELECTRICITY:
case BEAM_MIASMA: // rotting
case BEAM_NAPALM:
+ case BEAM_WATER: // water asphyxiation damage,
+ // bypassed by being water inhabitant.
return (true);
default:
return (false);
@@ -2173,6 +2175,11 @@ static inline int get_resistible_fraction(beam_type flavour)
{
switch (flavour)
{
+ // Drowning damage from water is resistible by being a water thing, or
+ // otherwise asphyx resistant.
+ case BEAM_WATER:
+ return (40);
+
// Assume ice storm and throw icicle are mostly solid.
case BEAM_ICE:
return (25);
diff --git a/crawl-ref/source/main.cc b/crawl-ref/source/main.cc
index 9dcd774a00..1ad6e1af6c 100644
--- a/crawl-ref/source/main.cc
+++ b/crawl-ref/source/main.cc
@@ -4631,7 +4631,7 @@ static void _compile_time_asserts()
COMPILE_CHECK(SP_VAMPIRE == 30 , c3);
COMPILE_CHECK(SPELL_DEBUGGING_RAY == 103 , c4);
COMPILE_CHECK(SPELL_RETURNING_AMMUNITION == 162 , c5);
- COMPILE_CHECK(NUM_SPELLS == 215 , c6);
+ COMPILE_CHECK(NUM_SPELLS == 217 , c6);
//jmf: NEW ASSERTS: we ought to do a *lot* of these
COMPILE_CHECK(NUM_SPECIES < SP_UNKNOWN , c7);
diff --git a/crawl-ref/source/mon-abil.cc b/crawl-ref/source/mon-abil.cc
index b64ffcf9b4..1c98be0c34 100644
--- a/crawl-ref/source/mon-abil.cc
+++ b/crawl-ref/source/mon-abil.cc
@@ -303,17 +303,7 @@ static bool _do_merge(monsters *initial_slime, monsters *merge_to)
merge_to->name(DESC_NOCAP_A).c_str());
}
- flash_view(LIGHTGREEN);
-
- int flash_delay = 150;
- // Scale delay to match change in arena_delay.
- if (crawl_state.arena)
- {
- flash_delay *= Options.arena_delay;
- flash_delay /= 600;
- }
-
- delay(flash_delay);
+ flash_view_delay(LIGHTGREEN, 150);
}
else if (you.can_see(initial_slime))
mpr("A slime creature suddenly disappears!");
diff --git a/crawl-ref/source/mon-act.cc b/crawl-ref/source/mon-act.cc
index 16c025e217..31d1c990a7 100644
--- a/crawl-ref/source/mon-act.cc
+++ b/crawl-ref/source/mon-act.cc
@@ -332,6 +332,24 @@ static void _maybe_set_patrol_route(monsters *monster)
}
}
+// Keep kraken tentacles from wandering too far away from the boss monster.
+static void _kraken_tentacle_movement_clamp(monsters *tentacle)
+{
+ if (tentacle->type != MONS_KRAKEN_TENTACLE)
+ return;
+
+ const int kraken_idx = tentacle->number;
+ ASSERT(!invalid_monster_index(kraken_idx));
+
+ monsters *kraken = &menv[kraken_idx];
+ const int distance_to_head =
+ grid_distance(tentacle->pos(), kraken->pos());
+ // Beyond max distance, the only move the tentacle can make is
+ // back towards the head.
+ if (distance_to_head >= KRAKEN_TENTACLE_RANGE)
+ mmov = (kraken->pos() - tentacle->pos()).sgn();
+}
+
//---------------------------------------------------------------
//
// handle_movement
@@ -378,8 +396,7 @@ static void _handle_movement(monsters *monster)
delta = monster->target - monster->pos();
// Move the monster.
- mmov.x = (delta.x > 0) ? 1 : ((delta.x < 0) ? -1 : 0);
- mmov.y = (delta.y > 0) ? 1 : ((delta.y < 0) ? -1 : 0);
+ mmov = delta.sgn();
if (mons_is_fleeing(monster) && monster->travel_target != MTRAV_WALL
&& (!monster->friendly()
@@ -1122,7 +1139,12 @@ static bool _mons_throw(struct monsters *monster, struct bolt &pbolt,
{
const mon_attack_def attk = mons_attack_spec(monster, 0);
if (attk.type == AT_SHOOT)
- ammoDamBonus += random2avg(attk.damage, 2);
+ {
+ if (projected == LRET_THROWN && wepClass == OBJ_MISSILES)
+ ammoHitBonus += random2avg(attk.damage, 2);
+ else
+ ammoDamBonus += random2avg(attk.damage, 2);
+ }
}
if (projected == LRET_THROWN)
@@ -1849,6 +1871,7 @@ static void _handle_monster_move(monsters *monster)
{
// Calculates mmov based on monster target.
_handle_movement(monster);
+ _kraken_tentacle_movement_clamp(monster);
if (mons_is_confused(monster)
|| monster->type == MONS_AIR_ELEMENTAL
diff --git a/crawl-ref/source/mon-cast.cc b/crawl-ref/source/mon-cast.cc
index 7bbbd77862..5190625c61 100644
--- a/crawl-ref/source/mon-cast.cc
+++ b/crawl-ref/source/mon-cast.cc
@@ -217,6 +217,7 @@ bolt mons_spells( monsters *mons, spell_type spell_cast, int power,
beam.type = dchar_glyph(DCHAR_FIRED_ZAP); // default
beam.thrower = KILL_MON_MISSILE;
+ beam.origin_spell = real_spell;
// FIXME: this should use the zap_data[] struct from beam.cc!
switch (real_spell)
@@ -361,6 +362,19 @@ bolt mons_spells( monsters *mons, spell_type spell_cast, int power,
beam.is_beam = true;
break;
+ case SPELL_PRIMAL_WAVE:
+ beam.name = "great wave of water";
+ // Water attack is weaker than the pure elemental damage
+ // attacks, but also less resistible.
+ beam.damage = dice_def( 3, 6 + power / 12 );
+ beam.colour = LIGHTBLUE;
+ beam.flavour = BEAM_WATER;
+ // Huge wave of water is hard to dodge.
+ beam.hit = 20 + power / 20;
+ beam.is_beam = false;
+ beam.type = dchar_glyph(DCHAR_WAVY);
+ break;
+
case SPELL_FREEZING_CLOUD:
beam.name = "freezing blast";
beam.damage = dice_def( 2, 9 + power / 11 );
@@ -825,6 +839,7 @@ bool setup_mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast,
case SPELL_SUMMON_EYEBALLS:
case SPELL_SUMMON_BUTTERFLIES:
case SPELL_MISLEAD:
+ case SPELL_CALL_TIDE:
return (true);
default:
if (check_validity)
@@ -840,6 +855,8 @@ bool setup_mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast,
bolt theBeam = mons_spells(monster, spell_cast, power);
+ // [ds] remind me again why we're doing this piecemeal copying?
+ pbolt.origin_spell = theBeam.origin_spell;
pbolt.colour = theBeam.colour;
pbolt.range = theBeam.range;
pbolt.hit = theBeam.hit;
@@ -1662,6 +1679,18 @@ void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast,
simple_monster_message(monster, " seems to move somewhat quicker.");
return;
+ case SPELL_CALL_TIDE:
+ {
+ const int tide_duration = random_range(18, 50, 2);
+ monster->add_ench(mon_enchant(ENCH_TIDE, 0, KC_OTHER,
+ tide_duration * 10));
+ monster->props[TIDE_CALL_TURN] = you.num_turns;
+ simple_monster_message(monster,
+ " sings a water chant to call the tide!");
+ flash_view_delay(ETC_WATER, 300);
+ return;
+ }
+
case SPELL_SUMMON_SMALL_MAMMALS:
case SPELL_VAMPIRE_SUMMON:
if (spell_cast == SPELL_SUMMON_SMALL_MAMMALS)
@@ -1821,7 +1850,7 @@ void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast,
if (created == -1)
continue;
- // Mara's clones are special; they have the same stats as him, and
+ // Mara's clones are special; they have the same stats as him, and
// are exact clones, so they are created damaged if necessary, with
// identical enchants and with the same items.
monsters *new_fake = &menv[created];
diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h
index f40dbb7c10..9460407bf2 100644
--- a/crawl-ref/source/mon-data.h
+++ b/crawl-ref/source/mon-data.h
@@ -1107,7 +1107,7 @@ static monsterentry mondata[] = {
// merfolk ('m')
{
- MONS_MERFOLK, 'm', LIGHTBLUE, "merfolk",
+ MONS_MERFOLK, 'm', BLUE, "merfolk",
M_WARM_BLOOD | M_SPEAKS,
MR_NO_FLAGS,
500, 10, MONS_MERFOLK, MONS_MERFOLK, MH_NATURAL, -3,
@@ -1119,12 +1119,49 @@ static monsterentry mondata[] = {
},
{
+ MONS_MERFOLK_IMPALER, 'm', LIGHTBLUE, "merfolk impaler",
+ M_WARM_BLOOD,
+ MR_NO_FLAGS,
+ 500, 10, MONS_MERFOLK, MONS_MERFOLK, MH_NATURAL, -3,
+ { {AT_HIT, AF_PLAIN, 34}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
+ { 16, 4, 3, 0 },
+ // Impalers prefer light armour, and are dodging experts.
+ 0, 23, MST_NO_SPELLS, CE_CONTAMINATED, Z_SMALL, S_SHOUT,
+ I_NORMAL, HT_AMPHIBIOUS_WATER, FL_NONE, 10, ATTACK_ENERGY(6),
+ MONUSE_MAGIC_ITEMS, MONEAT_NOTHING, SIZE_MEDIUM
+},
+
+{
+ MONS_MERFOLK_JAVELINEER, 'm', LIGHTGREY, "merfolk javelineer",
+ M_WARM_BLOOD | M_ARCHER,
+ MR_NO_FLAGS,
+ 500, 10, MONS_MERFOLK, MONS_MERFOLK, MH_NATURAL, -4,
+ { {AT_SHOOT, AF_PLAIN, 16}, {AT_HIT, AF_PLAIN, 20}, AT_NO_ATK, AT_NO_ATK },
+ { 15, 4, 2, 0 },
+ 0, 15, MST_NO_SPELLS, CE_CONTAMINATED, Z_SMALL, S_SHOUT,
+ I_NORMAL, HT_AMPHIBIOUS_WATER, FL_NONE, 10, MISSILE_ENERGY(8),
+ MONUSE_MAGIC_ITEMS, MONEAT_NOTHING, SIZE_MEDIUM
+},
+
+{
+ MONS_MERFOLK_AQUAMANCER, 'm', LIGHTGREEN, "merfolk aquamancer",
+ M_WARM_BLOOD | M_SPELLCASTER | M_ACTUAL_SPELLS,
+ MR_RES_COLD,
+ 500, 10, MONS_MERFOLK, MONS_MERFOLK, MH_NATURAL, -4,
+ { {AT_HIT, AF_PLAIN, 15}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
+ { 15, 3, 3, 0 },
+ 0, 12, MST_MERFOLK_AQUAMANCER, CE_CONTAMINATED, Z_SMALL, S_SHOUT,
+ I_NORMAL, HT_AMPHIBIOUS_WATER, FL_NONE, 10, DEFAULT_ENERGY,
+ MONUSE_MAGIC_ITEMS, MONEAT_NOTHING, SIZE_MEDIUM
+},
+
+{
MONS_MERMAID, 'm', CYAN, "mermaid",
M_SPELLCASTER | M_WARM_BLOOD | M_SPEAKS,
MR_NO_FLAGS,
500, 10, MONS_MERMAID, MONS_MERMAID, MH_NATURAL, -5,
{ {AT_HIT, AF_PLAIN, 10}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
- { 8, 2, 3, 0 },
+ { 8, 3, 3, 0 },
4, 12, MST_NO_SPELLS, CE_CONTAMINATED, Z_SMALL, S_SHOUT,
I_NORMAL, HT_AMPHIBIOUS_WATER, FL_NONE, 10, DEFAULT_ENERGY,
MONUSE_WEAPONS_ARMOUR, MONEAT_NOTHING, SIZE_MEDIUM
@@ -1136,7 +1173,7 @@ static monsterentry mondata[] = {
MR_NO_FLAGS,
500, 12, MONS_MERMAID, MONS_SIREN, MH_NATURAL, -7,
{ {AT_HIT, AF_PLAIN, 10}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
- { 8, 2, 3, 0 },
+ { 13, 5, 3, 0 },
4, 12, MST_NO_SPELLS, CE_CONTAMINATED, Z_SMALL, S_SHOUT,
I_NORMAL, HT_AMPHIBIOUS_WATER, FL_NONE, 10, DEFAULT_ENERGY,
MONUSE_WEAPONS_ARMOUR, MONEAT_NOTHING, SIZE_MEDIUM
@@ -2419,7 +2456,7 @@ static monsterentry mondata[] = {
M_WARM_BLOOD | M_BATTY,
MR_RES_POISON,
1000, 12, MONS_HARPY, MONS_HARPY, MH_NATURAL, -3,
- { {AT_CLAW, AF_PLAIN, 10}, {AT_CLAW, AF_STEAL_FOOD, 8},
+ { {AT_CLAW, AF_PLAIN, 19}, {AT_CLAW, AF_STEAL_FOOD, 14},
AT_NO_ATK, AT_NO_ATK },
{ 7, 3, 5, 0 },
2, 10, MST_NO_SPELLS, CE_CONTAMINATED, Z_BIG, S_SCREECH,
@@ -3303,7 +3340,7 @@ static monsterentry mondata[] = {
M_COLD_BLOOD | M_SPELLCASTER,
MR_NO_FLAGS,
1500, 20, MONS_KRAKEN, MONS_KRAKEN, MH_NATURAL, -3,
- { {AT_BITE, AF_PLAIN, 15}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
+ { {AT_BITE, AF_PLAIN, 65}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
{ 20, 10, 10, 0 },
20, 0, MST_KRAKEN, CE_NOCORPSE, Z_NOZOMBIE, S_SILENT,
I_ANIMAL, HT_WATER, FL_NONE, 10, DEFAULT_ENERGY,
@@ -3315,10 +3352,10 @@ static monsterentry mondata[] = {
M_COLD_BLOOD | M_NO_EXP_GAIN,
MR_RES_ASPHYX,
0, 10, MONS_KRAKEN_TENTACLE, MONS_KRAKEN_TENTACLE, MH_NATURAL, MAG_IMMUNE,
- { {AT_TENTACLE_SLAP, AF_PLAIN, 15}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
- { 5, 3, 5, 0 },
+ { {AT_TENTACLE_SLAP, AF_PLAIN, 29}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
+ { 12, 3, 2, 0 },
5, 7, MST_NO_SPELLS, CE_NOCORPSE, Z_NOZOMBIE, S_SILENT,
- I_ANIMAL, HT_WATER, FL_NONE, 10, DEFAULT_ENERGY,
+ I_ANIMAL, HT_AMPHIBIOUS_WATER, FL_NONE, 10, DEFAULT_ENERGY,
MONUSE_NOTHING, MONEAT_NOTHING, SIZE_LARGE
},
@@ -4589,7 +4626,7 @@ static monsterentry mondata[] = {
MR_NO_FLAGS,
500, 10, MONS_MERFOLK, MONS_MERFOLK, MH_NATURAL, -7,
{ {AT_HIT, AF_PLAIN, 10}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
- { 9, 0, 0, 54 },
+ { 16, 0, 0, 150 },
5, 18, MST_ILSUIW, CE_CONTAMINATED, Z_NOZOMBIE, S_SHOUT,
I_NORMAL, HT_AMPHIBIOUS_WATER, FL_NONE, 10, DEFAULT_ENERGY,
MONUSE_WEAPONS_ARMOUR, MONEAT_NOTHING, SIZE_MEDIUM
diff --git a/crawl-ref/source/mon-gear.cc b/crawl-ref/source/mon-gear.cc
index 01edc35951..8affc9587f 100644
--- a/crawl-ref/source/mon-gear.cc
+++ b/crawl-ref/source/mon-gear.cc
@@ -12,6 +12,7 @@
#include "mon-gear.h"
#include "artefact.h"
+#include "colour.h"
#include "dungeon.h"
#include "env.h"
#include "itemprop.h"
@@ -582,6 +583,52 @@ static item_make_species_type _give_weapon(monsters *mon, int level,
}
break;
+ case MONS_ILSUIW:
+ item_race = MAKE_ITEM_NO_RACE;
+ item.base_type = OBJ_WEAPONS;
+ item.sub_type = WPN_TRIDENT;
+ item.special = SPWPN_FREEZING;
+ item.plus = random_range(-1, 6, 2);
+ item.plus2 = random_range(-1, 6, 2);
+ item.colour = ETC_ICE;
+ force_item = true;
+ break;
+
+ case MONS_MERFOLK_IMPALER:
+ item_race = MAKE_ITEM_NO_RACE;
+ item.base_type = OBJ_WEAPONS;
+ item.sub_type = random_choose_weighted(100, WPN_TRIDENT,
+ 15, WPN_DEMON_TRIDENT,
+ 0);
+ if (coinflip())
+ level = MAKE_GOOD_ITEM;
+ else if (coinflip())
+ {
+ // Per dpeg request :)
+ item.special = SPWPN_REACHING;
+ item.plus = random_range(-1, 6, 2);
+ item.plus2 = random_range(-1, 5, 2);
+ force_item = true;
+ }
+ break;
+
+
+ case MONS_MERFOLK_AQUAMANCER:
+ item_race = MAKE_ITEM_NO_RACE;
+ item.base_type = OBJ_WEAPONS;
+ item.sub_type = WPN_SABRE;
+ if (coinflip())
+ level = MAKE_GOOD_ITEM;
+ break;
+
+ case MONS_MERFOLK_JAVELINEER:
+ item_race = MAKE_ITEM_NO_RACE;
+ item.base_type = OBJ_WEAPONS;
+ item.sub_type = WPN_SPEAR;
+ if (!one_chance_in(3))
+ level = MAKE_GOOD_ITEM;
+ break;
+
case MONS_MERFOLK:
if (one_chance_in(3))
{
@@ -1028,6 +1075,15 @@ static void _give_ammo(monsters *mon, int level,
qty = random_range(4, 7);
break;
+ case MONS_MERFOLK_JAVELINEER:
+ weap_class = OBJ_MISSILES;
+ weap_type = MI_JAVELIN;
+ item_race = MAKE_ITEM_NO_RACE;
+ qty = random_range(9, 23, 2);
+ if (one_chance_in(3))
+ level = MAKE_GOOD_ITEM;
+ break;
+
case MONS_MERFOLK:
if (!one_chance_in(3))
{
@@ -1342,6 +1398,22 @@ void give_armour(monsters *mon, int level)
break;
}
+ case MONS_MERFOLK_IMPALER:
+ item_race = MAKE_ITEM_NO_RACE;
+ item.base_type = OBJ_ARMOUR;
+ item.sub_type = random_choose_weighted(100, ARM_ROBE,
+ 60, ARM_LEATHER_ARMOUR,
+ 5, ARM_TROLL_LEATHER_ARMOUR,
+ 5, ARM_STEAM_DRAGON_ARMOUR,
+ 0);
+ break;
+
+ case MONS_MERFOLK_JAVELINEER:
+ item_race = MAKE_ITEM_NO_RACE;
+ item.base_type = OBJ_ARMOUR;
+ item.sub_type = ARM_LEATHER_ARMOUR;
+ break;
+
case MONS_ANGEL:
case MONS_SIGMUND:
case MONS_WIGHT:
@@ -1448,6 +1520,7 @@ void give_armour(monsters *mon, int level)
case MONS_WIZARD:
case MONS_ILSUIW:
case MONS_MARA:
+ case MONS_MERFOLK_AQUAMANCER:
if (item_race == MAKE_ITEM_RANDOM_RACE)
item_race = MAKE_ITEM_NO_RACE;
item.base_type = OBJ_ARMOUR;
diff --git a/crawl-ref/source/mon-pick.cc b/crawl-ref/source/mon-pick.cc
index 84d540d8c8..091650e0a0 100644
--- a/crawl-ref/source/mon-pick.cc
+++ b/crawl-ref/source/mon-pick.cc
@@ -1706,8 +1706,6 @@ int mons_shoals_level(int mcls)
case MONS_MERFOLK:
case MONS_MERMAID:
case MONS_CENTAUR:
- case MONS_ETTIN:
- case MONS_SHEEP:
case MONS_HIPPOGRIFF:
mlev++;
break;
@@ -1721,6 +1719,9 @@ int mons_shoals_level(int mcls)
case MONS_CYCLOPS: // will have a sheep band
case MONS_SIREN:
case MONS_HARPY:
+ case MONS_MERFOLK_IMPALER:
+ case MONS_MERFOLK_AQUAMANCER:
+ case MONS_MERFOLK_JAVELINEER:
mlev += 3;
break;
@@ -1748,8 +1749,6 @@ int mons_shoals_rare(int mcls)
case MONS_PLANT:
return 150;
- case MONS_ETTIN:
- case MONS_SHEEP:
case MONS_MERFOLK:
return 50;
@@ -1760,10 +1759,13 @@ int mons_shoals_rare(int mcls)
case MONS_GIANT_BAT:
case MONS_BUTTERFLY:
case MONS_CENTAUR:
+ case MONS_MERFOLK_IMPALER:
+ case MONS_MERFOLK_JAVELINEER:
return 35;
case MONS_SIREN:
case MONS_YAKTAUR:
+ case MONS_MERFOLK_AQUAMANCER:
return 25;
case MONS_CYCLOPS:
diff --git a/crawl-ref/source/mon-place.cc b/crawl-ref/source/mon-place.cc
index 27b6c8fe20..cb54b20982 100644
--- a/crawl-ref/source/mon-place.cc
+++ b/crawl-ref/source/mon-place.cc
@@ -598,7 +598,7 @@ static monster_type _resolve_monster_type(monster_type mon_type,
mon_type = MONS_DANCING_WEAPON;
else
{
- if (you.level_type == LEVEL_PORTAL_VAULT
+ if (you.level_type == LEVEL_PORTAL_VAULT
&& vault_mon_types.size() > 0)
{
int i = choose_random_weighted(vault_mon_weights.begin(),
@@ -636,7 +636,7 @@ static monster_type _resolve_monster_type(monster_type mon_type,
}
else if (you.level_type == LEVEL_PORTAL_VAULT)
{
- // XXX: We don't have a random monster list here, so pick one
+ // XXX: We don't have a random monster list here, so pick one
// from where we were.
place.level_type = LEVEL_DUNGEON;
*lev_mons = place.absdepth();
@@ -2401,7 +2401,12 @@ static monster_type _band_member(band_type band, int power)
break;
}
case BAND_ILSUIW:
- mon_type = coinflip()? MONS_MERFOLK : MONS_MERMAID;
+ mon_type = static_cast<monster_type>(
+ random_choose_weighted(30, MONS_MERMAID,
+ 15, MONS_MERFOLK,
+ 10, MONS_MERFOLK_JAVELINEER,
+ 10, MONS_MERFOLK_IMPALER,
+ 0));
break;
case BAND_AZRAEL:
diff --git a/crawl-ref/source/mon-spll.h b/crawl-ref/source/mon-spll.h
index 526d37f378..344ae8e6b8 100644
--- a/crawl-ref/source/mon-spll.h
+++ b/crawl-ref/source/mon-spll.h
@@ -1043,8 +1043,8 @@
{ MST_ILSUIW,
{
- SPELL_THROW_FROST, // was: SPELL_CONFUSED (jpeg)
- SPELL_SLOW,
+ SPELL_THROW_ICICLE,
+ SPELL_CALL_TIDE,
SPELL_INVISIBILITY,
SPELL_BLINK,
SPELL_WATER_ELEMENTALS,
@@ -1319,6 +1319,17 @@
}
},
+ { MST_MERFOLK_AQUAMANCER,
+ {
+ SPELL_PRIMAL_WAVE,
+ SPELL_BOLT_OF_COLD,
+ SPELL_THROW_ICICLE,
+ SPELL_NO_SPELL,
+ SPELL_NO_SPELL,
+ SPELL_BLINK
+ }
+ },
+
{ MST_TEST_SPAWNER,
{
SPELL_SHADOW_CREATURES,
diff --git a/crawl-ref/source/mon-stuff.cc b/crawl-ref/source/mon-stuff.cc
index 7a85194ea2..ca7b9cf12c 100644
--- a/crawl-ref/source/mon-stuff.cc
+++ b/crawl-ref/source/mon-stuff.cc
@@ -1323,7 +1323,7 @@ static int _tentacle_too_far(monsters *head, monsters *tentacle)
// If this ever changes, we'd need to check if the head and tentacle
// are still in the same pool.
// XXX: Actually, using Fedhas's Sunlight power you can separate pools...
- return grid_distance(head->pos(), tentacle->pos()) > LOS_RADIUS;
+ return grid_distance(head->pos(), tentacle->pos()) > KRAKEN_TENTACLE_RANGE;
}
void mons_relocated(monsters *monster)
@@ -1422,6 +1422,9 @@ int monster_die(monsters *monster, killer_type killer,
return (-1);
}
+ // If the monster was calling the tide, let go now.
+ monster->del_ench(ENCH_TIDE);
+
crawl_state.inc_mon_acting(monster);
ASSERT(!( YOU_KILL(killer) && crawl_state.arena ));
diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc
index 3687e1711c..5895a0c542 100644
--- a/crawl-ref/source/mon-util.cc
+++ b/crawl-ref/source/mon-util.cc
@@ -2390,6 +2390,13 @@ bool ms_waste_of_time( const monsters *mon, spell_type monspell )
// handled here as well. - bwr
switch (monspell)
{
+ case SPELL_CALL_TIDE:
+ return (!player_in_branch(BRANCH_SHOALS)
+ || mon->has_ench(ENCH_TIDE)
+ || !foe
+ || (grd(mon->pos()) == DNGN_DEEP_WATER
+ && grd(foe->pos()) == DNGN_DEEP_WATER));
+
case SPELL_BRAIN_FEED:
ret = (foe != &you);
break;
diff --git a/crawl-ref/source/monster.cc b/crawl-ref/source/monster.cc
index f9d18c97d9..2f83c01d6f 100644
--- a/crawl-ref/source/monster.cc
+++ b/crawl-ref/source/monster.cc
@@ -12,6 +12,7 @@
#include "coordit.h"
#include "delay.h"
#include "dgnevent.h"
+#include "dgn-shoals.h"
#include "directn.h"
#include "env.h"
#include "fight.h"
@@ -3308,6 +3309,21 @@ int monsters::res_asphyx() const
return (res);
}
+int monsters::res_water_drowning() const
+{
+ const int res = res_asphyx();
+ if (res)
+ return res;
+ switch (mons_habitat(this))
+ {
+ case HT_WATER:
+ case HT_AMPHIBIOUS_WATER:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
int monsters::res_poison() const
{
int u = get_mons_resists(this).poison;
@@ -4290,6 +4306,10 @@ void monsters::remove_enchantment_effect(const mon_enchant &me, bool quiet)
{
switch (me.ench)
{
+ case ENCH_TIDE:
+ shoals_release_tide(this);
+ break;
+
case ENCH_BERSERK:
scale_hp(2, 3);
break;
@@ -5550,7 +5570,9 @@ void monsters::check_redraw(const coord_def &old) const
}
}
-void monsters::apply_location_effects(const coord_def &oldpos)
+void monsters::apply_location_effects(const coord_def &oldpos,
+ killer_type killer,
+ int killernum)
{
if (oldpos != pos())
dungeon_events.fire_position_event(DET_MONSTER_MOVED, pos());
@@ -5583,7 +5605,7 @@ void monsters::apply_location_effects(const coord_def &oldpos)
ptrap->trigger(*this);
if (alive())
- mons_check_pool(this, pos());
+ mons_check_pool(this, pos(), killer, killernum);
if (alive() && has_ench(ENCH_SUBMERGED)
&& (!monster_can_submerge(this, grd(pos()))
@@ -6014,7 +6036,7 @@ static const char *enchant_names[] =
"short-lived", "paralysis", "sick", "sleep", "fatigue", "held",
"blood-lust", "neutral", "petrifying", "petrified", "magic-vulnerable",
"soul-ripe", "decay", "hungry", "flopping", "spore-producing",
- "downtrodden", "swift", "bug"
+ "downtrodden", "swift", "tide", "bug"
};
static const char *_mons_enchantment_name(enchant_type ench)
diff --git a/crawl-ref/source/monster.h b/crawl-ref/source/monster.h
index 524c48b4d4..b93ed65571 100644
--- a/crawl-ref/source/monster.h
+++ b/crawl-ref/source/monster.h
@@ -3,6 +3,9 @@
#include "actor.h"
+const int KRAKEN_TENTACLE_RANGE = 3;
+#define TIDE_CALL_TURN "tide-call-turn"
+
class mon_enchant
{
public:
@@ -126,7 +129,9 @@ public:
bool is_summoned(int* duration = NULL, int* summon_type = NULL) const;
bool has_action_energy() const;
void check_redraw(const coord_def &oldpos) const;
- void apply_location_effects(const coord_def &oldpos);
+ void apply_location_effects(const coord_def &oldpos,
+ killer_type killer = KILL_NONE,
+ int killernum = -1);
void moveto(const coord_def& c);
bool move_to_pos(const coord_def &newpos);
@@ -315,6 +320,7 @@ public:
int res_poison() const;
int res_rotting() const;
int res_asphyx() const;
+ int res_water_drowning() const;
int res_sticky_flame() const;
int res_holy_energy(const actor *) const;
int res_negative_energy() const;
diff --git a/crawl-ref/source/ouch.cc b/crawl-ref/source/ouch.cc
index 40352ecc0c..34f8bc736c 100644
--- a/crawl-ref/source/ouch.cc
+++ b/crawl-ref/source/ouch.cc
@@ -91,6 +91,13 @@ int check_your_resists(int hurted, beam_type flavour)
switch (flavour)
{
+ case BEAM_WATER:
+ hurted = resist_adjust_damage(&you, flavour,
+ you.res_water_drowning(), hurted, true);
+ if (!hurted)
+ mpr("You shrug off the wave.");
+ break;
+
case BEAM_STEAM:
hurted = resist_adjust_damage(&you, flavour,
player_res_steam(), hurted, true);
diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc
index 631e04e622..e490908d1b 100644
--- a/crawl-ref/source/player.cc
+++ b/crawl-ref/source/player.cc
@@ -6454,6 +6454,12 @@ int player::res_elec() const
return (player_res_electricity() * 2);
}
+int player::res_water_drowning() const
+{
+ return (res_asphyx() ||
+ (you.species == SP_MERFOLK && !transform_changed_physiology()));
+}
+
int player::res_asphyx() const
{
// The undead are immune to asphyxiation, or so we'll assume.
@@ -7051,6 +7057,13 @@ bool player::move_to_pos(const coord_def &c)
return false;
}
+void player::apply_location_effects(const coord_def &oldpos,
+ killer_type killer,
+ int killernum)
+{
+ move_player_to_grid(pos(), false, true, true, false);
+}
+
void player::shiftto(const coord_def &c)
{
crawl_view.shift_player_to(c);
diff --git a/crawl-ref/source/player.h b/crawl-ref/source/player.h
index c1a3c62b29..1e5c58d47a 100644
--- a/crawl-ref/source/player.h
+++ b/crawl-ref/source/player.h
@@ -464,6 +464,7 @@ public:
int res_poison() const;
int res_rotting() const;
int res_asphyx() const;
+ int res_water_drowning() const;
int res_sticky_flame() const;
int res_holy_energy(const actor *) const;
int res_negative_energy() const;
@@ -513,6 +514,10 @@ public:
bool do_shaft();
+ void apply_location_effects(const coord_def &oldpos,
+ killer_type killer = KILL_NONE,
+ int killernum = -1);
+
////////////////////////////////////////////////////////////////
PlaceInfo& get_place_info() const ; // Current place info
diff --git a/crawl-ref/source/shopping.cc b/crawl-ref/source/shopping.cc
index 8cb0dfa4be..2b5d582316 100644
--- a/crawl-ref/source/shopping.cc
+++ b/crawl-ref/source/shopping.cc
@@ -2430,7 +2430,7 @@ void ShoppingList::move_things(const coord_def &_src, const coord_def &_dst)
void ShoppingList::forget_pos(const level_pos &pos)
{
- if (crawl_state.map_stat_gen || crawl_state.test)
+ if (!crawl_state.need_save)
// Shopping list is unitialized and uneeded.
return;
diff --git a/crawl-ref/source/spl-cast.cc b/crawl-ref/source/spl-cast.cc
index 9f875c298f..acaaa7ebca 100644
--- a/crawl-ref/source/spl-cast.cc
+++ b/crawl-ref/source/spl-cast.cc
@@ -1139,6 +1139,7 @@ spret_type your_spells(spell_type spell, int powc, bool allow_fail)
dist spd;
bolt beam;
+ beam.origin_spell = spell;
// [dshaligram] Any action that depends on the spellcasting attempt to have
// succeeded must be performed after the switch().
@@ -1419,6 +1420,11 @@ spret_type your_spells(spell_type spell, int powc, bool allow_fail)
return (SPRET_ABORT);
break;
+ case SPELL_PRIMAL_WAVE:
+ if (!zapping(ZAP_PRIMAL_WAVE, powc, beam, true))
+ return (SPRET_ABORT);
+ break;
+
case SPELL_STONE_ARROW:
if (!zapping(ZAP_STONE_ARROW, powc, beam, true))
return (SPRET_ABORT);
diff --git a/crawl-ref/source/spl-data.h b/crawl-ref/source/spl-data.h
index 61b544f635..75f92737b3 100644
--- a/crawl-ref/source/spl-data.h
+++ b/crawl-ref/source/spl-data.h
@@ -2642,6 +2642,32 @@
},
{
+ SPELL_PRIMAL_WAVE, "Primal Wave",
+ SPTYP_CONJURATION | SPTYP_ICE,
+ SPFLAG_DIR_OR_TARGET,
+ 6,
+ 200,
+ 7, 7,
+ 0,
+ NULL,
+ true,
+ false
+},
+
+{
+ SPELL_CALL_TIDE, "Call Tide",
+ SPTYP_TRANSLOCATION,
+ SPFLAG_MONSTER,
+ 7,
+ 0,
+ -1, -1,
+ 0,
+ NULL,
+ false,
+ false
+},
+
+{
SPELL_NO_SPELL, "nonexistent spell",
0,
SPFLAG_TESTING,
diff --git a/crawl-ref/source/spl-util.cc b/crawl-ref/source/spl-util.cc
index 75d7264e07..4e61296b5b 100644
--- a/crawl-ref/source/spl-util.cc
+++ b/crawl-ref/source/spl-util.cc
@@ -997,6 +997,8 @@ spell_type zap_type_to_spell(zap_type zap)
return(SPELL_BOLT_OF_FIRE);
case ZAP_COLD:
return(SPELL_BOLT_OF_COLD);
+ case ZAP_PRIMAL_WAVE:
+ return(SPELL_PRIMAL_WAVE);
case ZAP_CONFUSION:
return(SPELL_CONFUSE);
case ZAP_INVISIBILITY:
diff --git a/crawl-ref/source/stuff.h b/crawl-ref/source/stuff.h
index 89757eb99d..684df4175d 100644
--- a/crawl-ref/source/stuff.h
+++ b/crawl-ref/source/stuff.h
@@ -83,11 +83,6 @@ inline bool testbits(unsigned long flags, unsigned long test)
return ((flags & test) == test);
}
-template <typename Z> inline Z sgn(Z x)
-{
- return (x < 0 ? -1 : (x > 0 ? 1 : 0));
-}
-
bool is_trap_square(dungeon_feature_type grid);
void zap_los_monsters(bool items_also);
diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc
index b8e6447a89..94ad2a572c 100644
--- a/crawl-ref/source/view.cc
+++ b/crawl-ref/source/view.cc
@@ -635,6 +635,19 @@ void flash_view(int colour)
viewwindow(false, false);
}
+void flash_view_delay(int colour, long flash_delay)
+{
+ flash_view(colour);
+ // Scale delay to match change in arena_delay.
+ if (crawl_state.arena)
+ {
+ flash_delay *= Options.arena_delay;
+ flash_delay /= 600;
+ }
+
+ delay(flash_delay);
+}
+
static void _debug_pane_bounds()
{
#if DEBUG_PANE_BOUNDS
diff --git a/crawl-ref/source/view.h b/crawl-ref/source/view.h
index 7a618c182b..70029c000d 100644
--- a/crawl-ref/source/view.h
+++ b/crawl-ref/source/view.h
@@ -37,6 +37,7 @@ std::string screenshot(bool fullscreen = false);
bool view_update();
void view_update_at(const coord_def &pos);
void flash_view(int colour = BLACK); // inside #ifndef USE_TILE?
+void flash_view_delay(int colour = BLACK, long delay = 150);
#ifndef USE_TILE
void flash_monster_colour(const monsters *mon, unsigned char fmc_colour,
int fmc_delay);