diff options
Diffstat (limited to 'crawl-ref/source/dgn-shoals.cc')
-rw-r--r-- | crawl-ref/source/dgn-shoals.cc | 501 |
1 files changed, 446 insertions, 55 deletions
diff --git a/crawl-ref/source/dgn-shoals.cc b/crawl-ref/source/dgn-shoals.cc index a3a2b12ea9..367ca497d4 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,16 +401,21 @@ 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) { - // Place (non-rune) minivaults on the other islands + // Place (non-rune) minivaults on the other islands. We + // reuse the shoal rune huts, but do not place the rune + // again. + int tries = 5; do - vault = random_map_for_tag("shoal"); - while (!vault); - dgn_place_map(vault, false, true, _pick_shoals_island()); + vault = random_map_for_tag("shoal_rune"); + while (!vault && --tries > 0); + if (vault) + dgn_place_map(vault, false, true, _pick_shoals_island(), 0); } } else @@ -464,6 +469,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 ®ion_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 ®ion_map, + const std::vector<weighted_region> ®ions) +{ + 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 +814,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 +844,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 +862,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 +982,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 +1023,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 +1060,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 +1087,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 +1108,27 @@ 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)) + { + if (player_can_hear(mons->pos())) + { + mprf(MSGCH_SOUND, "The tide is released from %s call.", + mons->name(DESC_NOCAP_YOUR, true).c_str()); + if (you.see_cell(mons->pos())) + flash_view_delay(ETC_WATER, 150); + } + shoals_apply_tides(0, true); + } +} |