1 #include "AppHdr.h"
2 
3 #include "dgn-shoals.h"
4 
5 #include <algorithm>
6 #include <cmath>
7 #include <vector>
8 
9 #include "act-iter.h"
10 #include "colour.h"
11 #include "coordit.h"
12 #include "dgn-height.h"
13 #include "dungeon.h"
14 #include "english.h"
15 #include "flood-find.h"
16 #include "item-prop.h"
17 #include "libutil.h"
18 #include "mapmark.h"
19 #include "maps.h"
20 #include "message.h"
21 #include "mgen-data.h"
22 #include "mon-place.h"
23 #include "state.h"
24 #include "stringutil.h"
25 #include "traps.h"
26 #include "view.h"
27 
28 static const char *PROPS_SHOALS_TIDE_KEY = "shoals-tide-height";
29 static const char *PROPS_SHOALS_TIDE_VEL = "shoals-tide-velocity";
30 static const char *PROPS_SHOALS_TIDE_UPDATE_TIME = "shoals-tide-update-time";
31 
32 static dgn_island_plan _shoals_islands;
33 
34 static const int SHOALS_ISLAND_COLLIDE_DIST2 = 5 * 5;
35 
36 // The raw tide height / TIDE_MULTIPLIER is the actual tide height. The higher
37 // the tide multiplier, the slower the tide advances and recedes. A multiplier
38 // of X implies that the tide will advance visibly about once in X turns.
39 static int TIDE_MULTIPLIER = 2;
40 
41 static int LOW_TIDE = -18 * TIDE_MULTIPLIER;
42 static int HIGH_TIDE = 25 * TIDE_MULTIPLIER;
43 
44 // The highest a tide can be called by a tide caller such as Ilsuiw.
45 static const int HIGH_CALLED_TIDE = 50;
46 static const int TIDE_DECEL_MARGIN = 8;
47 static const int PEAK_TIDE_VELOCITY = 2;
48 static const int CALL_TIDE_VELOCITY = 21;
49 
50 // The area around the user of a call tide spell that is subject to
51 // local tide elevation.
52 static const int TIDE_CALL_RADIUS = 8;
53 static const int MAX_SHOAL_PLANTS = 180;
54 
55 static const int _shoals_margin = 6;
56 
57 enum shoals_height_thresholds
58 {
59     SHT_UNDEFINED = -10000,
60     SHT_STONE = 400,
61     SHT_ROCK  = 135,
62     SHT_FLOOR = 0,
63     SHT_SHALLOW_WATER = -30,
64 };
65 
66 enum class tide_dir
67 {
68     rising,
69     falling,
70 };
71 
72 static tide_dir _shoals_tide_direction;
73 static monster* tide_caller = nullptr;
74 static coord_def tide_caller_pos;
75 static int tide_called_turns = 0;
76 static int tide_called_peak = 0;
77 static int shoals_plant_quota = 0;
78 
_shoals_feature_by_height(int height)79 static dungeon_feature_type _shoals_feature_by_height(int height)
80 {
81     return height >= SHT_STONE ? DNGN_STONE_WALL :
82         height >= SHT_ROCK ? DNGN_ROCK_WALL :
83         height >= SHT_FLOOR ? DNGN_FLOOR :
84         height >= SHT_SHALLOW_WATER ? DNGN_SHALLOW_WATER
85         : DNGN_DEEP_WATER;
86 }
87 
_shoals_feature_at(const coord_def & c)88 static dungeon_feature_type _shoals_feature_at(const coord_def &c)
89 {
90     const int height = dgn_height_at(c);
91     return _shoals_feature_by_height(height);
92 }
93 
_shoals_feature_height(dungeon_feature_type feat)94 static int _shoals_feature_height(dungeon_feature_type feat)
95 {
96     switch (feat)
97     {
98     case DNGN_STONE_WALL:
99         return SHT_STONE;
100     case DNGN_FLOOR:
101         return SHT_FLOOR;
102     case DNGN_SHALLOW_WATER:
103         return SHT_SHALLOW_WATER;
104     case DNGN_DEEP_WATER:
105         return SHT_SHALLOW_WATER - 1;
106     default:
107         return feat_is_solid(feat) ? SHT_ROCK : SHT_FLOOR;
108     }
109 }
110 
111 // Returns true if the given feature can be affected by Shoals tides.
_shoals_tide_susceptible_feat(dungeon_feature_type feat)112 static inline bool _shoals_tide_susceptible_feat(dungeon_feature_type feat)
113 {
114     return feat == DNGN_SHALLOW_WATER || feat == DNGN_FLOOR;
115 }
116 
117 // Return true if tide effects can propagate through this square.
118 // NOTE: uses RNG!
_shoals_tide_passable_feat(dungeon_feature_type feat)119 static inline bool _shoals_tide_passable_feat(dungeon_feature_type feat)
120 {
121     return feat_is_watery(feat)
122            // The Shoals tide can sometimes lap past the doorways of rooms
123            // near the water. Note that the actual probability of the tide
124            // getting through a doorway is this probability * 0.5 --
125            // see _shoals_apply_tide.
126            || feat_is_open_door(feat)
127            || feat_is_closed_door(feat) && one_chance_in(3);
128 }
129 
_shoals_init_heights()130 static void _shoals_init_heights()
131 {
132     dgn_initialise_heightmap(SHT_SHALLOW_WATER - 3);
133 }
134 
_shoals_island_plan()135 static dgn_island_plan _shoals_island_plan()
136 {
137     dgn_island_plan plan;
138     plan.level_border_depth = _shoals_margin;
139     plan.n_aux_centres = int_range(0, 3);
140     plan.aux_centre_offset_range = int_range(2, 10);
141 
142     plan.atoll_roll = 10;
143     plan.island_separation_dist2 = SHOALS_ISLAND_COLLIDE_DIST2;
144 
145     plan.n_island_centre_delta_points = int_range(50, 60);
146     plan.island_centre_radius_range = int_range(3, 10);
147     plan.island_centre_point_height_increment = int_range(80, 110);
148 
149     plan.n_island_aux_delta_points = int_range(25, 45);
150     plan.island_aux_radius_range = int_range(2, 7);
151     plan.island_aux_point_height_increment = int_range(50, 65);
152 
153     return plan;
154 }
155 
_shoals_init_islands(int depth)156 static void _shoals_init_islands(int depth)
157 {
158     const int nislands = 20 - depth * 2;
159     _shoals_islands = _shoals_island_plan();
160     _shoals_islands.build(nislands);
161 }
162 
_shoals_build_cliff()163 static void _shoals_build_cliff()
164 {
165     const coord_def cliffc = dgn_random_point_in_margin(_shoals_margin * 2);
166     const int length = random_range(6, 15);
167     const double angle = dgn_degrees_to_radians(random2(360));
168     const int_range n_cliff_points(40, 60);
169     const int cliff_point_radius = 3;
170     const int_range cliff_height_increment(100, 130);
171 
172     for (int i = 0; i < length; i += 3)
173     {
174         const int distance = i - length / 2;
175         coord_def place =
176             cliffc + coord_def(static_cast<int>(distance * cos(angle)),
177                                static_cast<int>(distance * sin(angle)));
178         coord_def fuzz;
179         fuzz.x = random_range(-2, 2);
180         fuzz.y = random_range(-2, 2);
181 
182         place += fuzz;
183         dgn_island_centred_at(place, resolve_range(n_cliff_points),
184                               cliff_point_radius, cliff_height_increment,
185                               _shoals_margin);
186     }
187 }
188 
_shoals_cliffs()189 static void _shoals_cliffs()
190 {
191     const int ncliffs = random_range(0, 6, 2);
192     for (int i = 0; i < ncliffs; ++i)
193         _shoals_build_cliff();
194 }
195 
_shoals_smooth_water()196 static void _shoals_smooth_water()
197 {
198     for (rectangle_iterator ri(0); ri; ++ri)
199         dgn_smooth_height_at(*ri, 1, SHT_SHALLOW_WATER - 1);
200 }
201 
_shoals_apply_level()202 static void _shoals_apply_level()
203 {
204     for (rectangle_iterator ri(1); ri; ++ri)
205         if (!map_masked(*ri, MMT_VAULT))
206             env.grid(*ri) = _shoals_feature_at(*ri);
207 }
208 
_shoals_postbuild_apply_level()209 static void _shoals_postbuild_apply_level()
210 {
211     for (rectangle_iterator ri(1); ri; ++ri)
212     {
213         if (!map_masked(*ri, MMT_VAULT))
214         {
215             const dungeon_feature_type feat = env.grid(*ri);
216             if (feat_is_water(feat) || feat == DNGN_ROCK_WALL
217                 || feat == DNGN_STONE_WALL || feat == DNGN_FLOOR)
218             {
219                 env.grid(*ri) = _shoals_feature_at(*ri);
220             }
221         }
222     }
223 }
224 
225 // Returns all points in deep water with an adjacent square in shallow water.
_shoals_water_depth_change_points()226 static vector<coord_def> _shoals_water_depth_change_points()
227 {
228     vector<coord_def> points;
229     for (rectangle_iterator ri(1); ri; ++ri)
230     {
231         coord_def c(*ri);
232         if (env.grid(c) == DNGN_DEEP_WATER
233             && dgn_has_adjacent_feat(c, DNGN_SHALLOW_WATER))
234         {
235             points.push_back(c);
236         }
237     }
238     return points;
239 }
240 
_shoals_deepen_water_at(coord_def p,int distance)241 static inline void _shoals_deepen_water_at(coord_def p, int distance)
242 {
243     dgn_height_at(p) -= distance * 7;
244 }
245 
_shoals_deepen_water()246 static void _shoals_deepen_water()
247 {
248     vector<coord_def> pages[2];
249     int current_page = 0;
250     pages[current_page] = _shoals_water_depth_change_points();
251     FixedArray<bool, GXM, GYM> seen_points(false);
252 
253     for (const coord_def &pos : pages[current_page])
254         seen_points(pos) = true;
255 
256     int distance = 0;
257     while (!pages[current_page].empty())
258     {
259         const int next_page = !current_page;
260         vector<coord_def> &cpage(pages[current_page]);
261         vector<coord_def> &npage(pages[next_page]);
262         for (const coord_def &c : cpage)
263         {
264             if (distance)
265                 _shoals_deepen_water_at(c, distance);
266 
267             for (adjacent_iterator ai(c); ai; ++ai)
268             {
269                 const coord_def adj(*ai);
270                 if (!seen_points(adj)
271                     && (adj - c).abs() == 1
272                     && env.grid(adj) == DNGN_DEEP_WATER)
273                 {
274                     npage.push_back(adj);
275                     seen_points(adj) = true;
276                 }
277             }
278         }
279         cpage.clear();
280         current_page = next_page;
281         distance++;
282     }
283 }
284 
_shoals_furniture()285 static void _shoals_furniture()
286 {
287     dgn_place_stone_stairs();
288 }
289 
_shoals_deepen_edges()290 static void _shoals_deepen_edges()
291 {
292     const int edge = 1;
293     const int deepen_by = 1000;
294     // Water of the edge of the screen is too deep to be exposed by tides.
295     for (int y = 1; y < GYM - 2; ++y)
296     {
297         for (int x = 1; x <= edge; ++x)
298         {
299             dgn_height_at(coord_def(x, y)) -= deepen_by;
300             dgn_height_at(coord_def(GXM - 1 - x, y)) -= deepen_by;
301         }
302     }
303     for (int x = 1; x < GXM - 2; ++x)
304     {
305         for (int y = 1; y <= edge; ++y)
306         {
307             dgn_height_at(coord_def(x, y)) -= deepen_by;
308             dgn_height_at(coord_def(x, GYM - 1 - y)) -= deepen_by;
309         }
310     }
311 }
312 
_shoals_contiguous_feature_flood(FixedArray<short,GXM,GYM> & rmap,coord_def c,dungeon_feature_type feat,int nregion,int size_limit)313 static int _shoals_contiguous_feature_flood(
314     FixedArray<short, GXM, GYM> &rmap,
315     coord_def c,
316     dungeon_feature_type feat,
317     int nregion,
318     int size_limit)
319 {
320     vector<coord_def> visit(1, c);
321     int npoints = 1;
322     for (size_t i = 0; i < visit.size() && npoints < size_limit; ++i)
323     {
324         const coord_def p(visit[i]);
325         rmap(p) = nregion;
326 
327         if (npoints < size_limit)
328         {
329             for (adjacent_iterator ai(p); ai && npoints < size_limit; ++ai)
330             {
331                 const coord_def adj(*ai);
332                 if (in_bounds(adj) && !rmap(adj) && env.grid(adj) == feat
333                     && !map_masked(adj, MMT_VAULT))
334                 {
335                     rmap(adj) = nregion;
336                     visit.push_back(adj);
337                     ++npoints;
338                 }
339             }
340         }
341     }
342     return npoints;
343 }
344 
_shoals_region_center(FixedArray<short,GXM,GYM> & rmap,coord_def c)345 static coord_def _shoals_region_center(
346     FixedArray<short, GXM, GYM> &rmap,
347     coord_def c)
348 {
349     const int nregion(rmap(c));
350     int nseen = 0;
351 
352     double cx = 0.0, cy = 0.0;
353     vector<coord_def> visit(1, c);
354     FixedArray<bool, GXM, GYM> visited(false);
355     // visit can be modified by push_back during this loop
356     for (size_t i = 0; i < visit.size(); ++i)
357     {
358         const coord_def p(visit[i]);
359         visited(p) = true;
360 
361         ++nseen;
362         if (nseen == 1)
363         {
364             cx = p.x;
365             cy = p.y;
366         }
367         else
368         {
369             cx = (cx * (nseen - 1) + p.x) / nseen;
370             cy = (cy * (nseen - 1) + p.y) / nseen;
371         }
372 
373         for (adjacent_iterator ai(p); ai; ++ai)
374         {
375             const coord_def adj(*ai);
376             if (in_bounds(adj) && !visited(adj) && rmap(adj) == nregion)
377             {
378                 visited(adj) = true;
379                 visit.push_back(adj);
380             }
381         }
382     }
383 
384     const coord_def cgravity(static_cast<int>(cx), static_cast<int>(cy));
385     coord_def closest_to_center;
386     int closest_distance = 0;
387     for (const coord_def &p : visit)
388     {
389         const int dist2 = (p - cgravity).abs();
390         if (closest_to_center.origin() || closest_distance > dist2)
391         {
392             closest_to_center = p;
393             closest_distance = dist2;
394         }
395     }
396     return closest_to_center;
397 }
398 
399 struct weighted_region
400 {
401     int weight;
402     coord_def pos;
403 
weighted_regionweighted_region404     weighted_region(int _weight, coord_def _pos) : weight(_weight), pos(_pos)
405     {
406     }
407 };
408 
409 static vector<weighted_region>
_shoals_point_feat_cluster(dungeon_feature_type feat,const int wanted_count,grid_short & region_map)410 _shoals_point_feat_cluster(dungeon_feature_type feat,
411                            const int wanted_count,
412                            grid_short &region_map)
413 {
414     vector<weighted_region> regions;
415     int region = 1;
416     for (rectangle_iterator ri(1); ri; ++ri)
417     {
418         coord_def c(*ri);
419         if (!region_map(c) && env.grid(c) == feat
420             && !map_masked(c, MMT_VAULT))
421         {
422             const int featcount =
423                 _shoals_contiguous_feature_flood(region_map,
424                                                  c,
425                                                  feat,
426                                                  region++,
427                                                  wanted_count * 3 / 2);
428             if (featcount >= wanted_count)
429                 regions.emplace_back(featcount, c);
430         }
431     }
432     return regions;
433 }
434 
_shoals_pick_region(grid_short & region_map,const vector<weighted_region> & regions)435 static coord_def _shoals_pick_region(
436     grid_short &region_map,
437     const vector<weighted_region> &regions)
438 {
439     if (regions.empty())
440         return coord_def();
441     return _shoals_region_center(region_map,
442                                  regions[random2(regions.size())].pos);
443 }
444 
_shoals_make_plant_at(coord_def p)445 static void _shoals_make_plant_at(coord_def p)
446 {
447     if (shoals_plant_quota > 0) // a bad person could post-decrement here...
448     {
449         mons_place(mgen_data::hostile_at(MONS_PLANT, false, p));
450         --shoals_plant_quota;
451     }
452 }
453 
_shoals_plantworthy_feat(dungeon_feature_type feat)454 static bool _shoals_plantworthy_feat(dungeon_feature_type feat)
455 {
456     return feat == DNGN_SHALLOW_WATER || feat == DNGN_FLOOR;
457 }
458 
_shoals_make_plant_near(coord_def c,int radius,dungeon_feature_type preferred_feat,grid_bool * verboten)459 static void _shoals_make_plant_near(coord_def c, int radius,
460                                     dungeon_feature_type preferred_feat,
461                                     grid_bool *verboten)
462 {
463     if (shoals_plant_quota <= 0)
464         return;
465 
466     const int ntries = 5;
467     for (int i = 0; i < ntries; ++i)
468     {
469         const coord_def plant_place(
470             dgn_random_point_from(c, random2(1 + radius), _shoals_margin));
471         if (!plant_place.origin()
472             && !monster_at(plant_place)
473             && !map_masked(plant_place, MMT_VAULT))
474         {
475             const dungeon_feature_type feat(env.grid(plant_place));
476             if (_shoals_plantworthy_feat(feat)
477                 && (feat == preferred_feat || coinflip())
478                 && (!verboten || !(*verboten)(plant_place)))
479             {
480                 _shoals_make_plant_at(plant_place);
481                 return;
482             }
483         }
484     }
485 }
486 
_shoals_plant_cluster(coord_def c,int nplants,int radius,dungeon_feature_type favoured_feat,grid_bool * verboten)487 static void _shoals_plant_cluster(coord_def c, int nplants, int radius,
488                                   dungeon_feature_type favoured_feat,
489                                   grid_bool *verboten)
490 {
491     for (int i = 0; i < nplants; ++i)
492         _shoals_make_plant_near(c, radius, favoured_feat, verboten);
493 }
494 
_shoals_plant_supercluster(coord_def c,dungeon_feature_type favoured_feat,grid_bool * verboten=nullptr)495 static void _shoals_plant_supercluster(coord_def c,
496                                        dungeon_feature_type favoured_feat,
497                                        grid_bool *verboten = nullptr)
498 {
499     int nplants = random_range(10, 17, 2);
500     int radius = random_range(3, 9);
501     _shoals_plant_cluster(c, nplants, radius, favoured_feat, verboten);
502 
503     const int nadditional_clusters(max(0, random_range(-1, 4, 2)));
504     for (int i = 0; i < nadditional_clusters; ++i)
505     {
506         const coord_def satellite(
507             dgn_random_point_from(c, random_range(2, 12), _shoals_margin));
508         if (!satellite.origin())
509         {
510             nplants = random_range(5, 12, 2);
511             radius = random_range(2, 7);
512             _shoals_plant_cluster(satellite, nplants, radius, favoured_feat,
513                                   verboten);
514         }
515     }
516 }
517 
_shoals_generate_water_plants(coord_def mangrove_central)518 static void _shoals_generate_water_plants(coord_def mangrove_central)
519 {
520     if (!mangrove_central.origin())
521         _shoals_plant_supercluster(mangrove_central, DNGN_SHALLOW_WATER);
522 }
523 
524 struct coord_dbl
525 {
526     double x, y;
527 
coord_dblcoord_dbl528     coord_dbl(double _x, double _y) : x(_x), y(_y) { }
operator +coord_dbl529     coord_dbl operator + (const coord_dbl &o) const
530     {
531         return coord_dbl(x + o.x, y + o.y);
532     }
operator +=coord_dbl533     coord_dbl &operator += (const coord_dbl &o)
534     {
535         x += o.x;
536         y += o.y;
537         return *this;
538     }
539 };
540 
_int_coord(const coord_dbl & c)541 static coord_def _int_coord(const coord_dbl &c)
542 {
543     return coord_def(static_cast<int>(c.x), static_cast<int>(c.y));
544 }
545 
_shoals_windshadows(grid_bool & windy)546 static vector<coord_def> _shoals_windshadows(grid_bool &windy)
547 {
548     const int wind_angle_degrees = random2(360);
549     const double wind_angle(dgn_degrees_to_radians(wind_angle_degrees));
550     const coord_dbl wi(cos(wind_angle), sin(wind_angle));
551     const double epsilon = 1e-5;
552 
553     vector<coord_dbl> wind_points;
554     if (wi.x > epsilon || wi.x < -epsilon)
555     {
556         for (int y = 1; y < GYM - 1; ++y)
557             wind_points.emplace_back(wi.x > epsilon ? 1 : GXM - 2, y);
558     }
559     if (wi.y > epsilon || wi.y < -epsilon)
560     {
561         for (int x = 1; x < GXM - 1; ++x)
562             wind_points.emplace_back(x, wi.y > epsilon ? 1 : GYM - 2);
563     }
564 
565     // wind_points can be modified during this loop via emplace_back
566     for (size_t i = 0; i < wind_points.size(); ++i)
567     {
568         const coord_def here(_int_coord(wind_points[i]));
569         windy(here) = true;
570 
571         coord_dbl next = wind_points[i] + wi;
572         while (_int_coord(next) == here)
573             next += wi;
574 
575         const coord_def nextp(_int_coord(next));
576         if (in_bounds(nextp) && !windy(nextp) && !cell_is_solid(nextp))
577         {
578             windy(nextp) = true;
579             wind_points.push_back(next);
580         }
581     }
582 
583     // To avoid plants cropping up inside vaults, mark everything inside
584     // vaults as "windy".
585     for (rectangle_iterator ri(1); ri; ++ri)
586         if (map_masked(*ri, MMT_VAULT))
587             windy(*ri) = true;
588 
589     // Now we know the places in the wind shadow:
590     vector<coord_def> wind_shadows;
591     for (rectangle_iterator ri(1); ri; ++ri)
592     {
593         const coord_def p(*ri);
594         if (!windy(p) && env.grid(p) == DNGN_FLOOR
595             && (dgn_has_adjacent_feat(p, DNGN_STONE_WALL)
596                 || dgn_has_adjacent_feat(p, DNGN_ROCK_WALL)))
597         {
598             wind_shadows.push_back(p);
599         }
600     }
601     return wind_shadows;
602 }
603 
_shoals_generate_wind_sheltered_plants(vector<coord_def> & places,grid_bool & windy)604 static void _shoals_generate_wind_sheltered_plants(vector<coord_def> &places,
605                                                    grid_bool &windy)
606 {
607     if (places.empty())
608         return;
609 
610     const int chosen = random2(places.size());
611     const coord_def spot = places[random2(places.size())];
612     places.erase(places.begin() + chosen);
613 
614     _shoals_plant_supercluster(spot, DNGN_FLOOR, &windy);
615 }
616 
dgn_shoals_generate_flora()617 void dgn_shoals_generate_flora()
618 {
619     // Water clusters are groups of plants clustered near the water.
620     // Wind clusters are groups of plants clustered in wind shadow --
621     // possibly because they can grow better without being exposed to the
622     // strong winds of the Shoals.
623     //
624     // Yeah, the strong winds aren't there yet, but they could be!
625     //
626     const int n_water_clusters = max(0, random_range(-1, 6, 2));
627     const int n_wind_clusters = max(0, random_range(-2, 2, 2));
628 
629     shoals_plant_quota = MAX_SHOAL_PLANTS;
630 
631     if (n_water_clusters)
632     {
633         grid_short region_map(0);
634         vector<weighted_region> regions(
635             _shoals_point_feat_cluster(DNGN_SHALLOW_WATER, 6, region_map));
636 
637         for (int i = 0; i < n_water_clusters; ++i)
638         {
639             const coord_def p(_shoals_pick_region(region_map, regions));
640             _shoals_generate_water_plants(p);
641         }
642     }
643 
644     if (n_wind_clusters)
645     {
646         grid_bool windy(false);
647         vector<coord_def> wind_shadows = _shoals_windshadows(windy);
648         for (int i = 0; i < n_wind_clusters; ++i)
649             _shoals_generate_wind_sheltered_plants(wind_shadows, windy);
650     }
651 }
652 
dgn_build_shoals_level()653 void dgn_build_shoals_level()
654 {
655         // TODO: Attach this information to the vault name string
656         //       instead of the build method string.
657     env.level_build_method += make_stringf(" [depth %d]", you.depth);
658 
659     const int shoals_depth = you.depth - 1;
660     dgn_replace_area(0, 0, GXM-1, GYM-1, DNGN_ROCK_WALL, DNGN_OPEN_SEA,
661                      MMT_VAULT);
662     _shoals_init_heights();
663     _shoals_init_islands(shoals_depth);
664     _shoals_cliffs();
665     dgn_smooth_heights();
666     _shoals_apply_level();
667     _shoals_deepen_water();
668     _shoals_deepen_edges();
669     _shoals_smooth_water();
670     _shoals_furniture();
671 }
672 
673 // Search the map for vaults and set the terrain heights for features
674 // in the vault to reasonable levels.
shoals_postprocess_level()675 void shoals_postprocess_level()
676 {
677     if (!player_in_branch(BRANCH_SHOALS) || !env.heightmap)
678         return;
679 
680     for (rectangle_iterator ri(1); ri; ++ri)
681     {
682         const coord_def c(*ri);
683         if (!(env.level_map_mask(c) & MMT_VAULT))
684             continue;
685 
686         // Don't mess with tide immune squares at all.
687         if (is_tide_immune(c))
688             continue;
689 
690         const dungeon_feature_type feat(env.grid(c));
691         if (!_shoals_tide_susceptible_feat(feat) && !feat_is_solid(feat))
692             continue;
693 
694         const dungeon_feature_type expected_feat(_shoals_feature_at(c));
695         // It would be nice to do actual height contours within
696         // vaults, but for now, keep it simple.
697         if (feat != expected_feat)
698             dgn_height_at(c) = _shoals_feature_height(feat);
699     }
700 
701     // Apply tide now, since the tide is likely to be nonzero unless
702     // this is Shoals:1
703     shoals_apply_tides(0, true);
704 }
705 
_shoals_clamp_height_at(const coord_def & c,int clamp_height=SHT_ROCK-1)706 static void _shoals_clamp_height_at(const coord_def &c,
707                                      int clamp_height = SHT_ROCK - 1)
708 {
709     if (!in_bounds(c))
710         return;
711 
712     if (dgn_height_at(c) > clamp_height)
713         dgn_height_at(c) = clamp_height;
714 }
715 
_shoals_connect_smooth_height_at(const coord_def & c)716 static void _shoals_connect_smooth_height_at(const coord_def &c)
717 {
718     if (map_bounds_with_margin(c, 3))
719         dgn_smooth_height_at(c, 1);
720 }
721 
_shoals_connecting_point_smooth(const coord_def & c,int radius)722 static void _shoals_connecting_point_smooth(const coord_def &c, int radius)
723 {
724     for (int dy = 0; dy < radius; ++dy)
725     {
726         for (int dx = 0; dx < radius; ++dx)
727         {
728             _shoals_connect_smooth_height_at(c + coord_def(dy, dx));
729             if (dy)
730                 _shoals_connect_smooth_height_at(c + coord_def(-dy, dx));
731             if (dx)
732                 _shoals_connect_smooth_height_at(c + coord_def(dy, -dx));
733             if (dx && dy)
734                 _shoals_connect_smooth_height_at(c + coord_def(-dy, -dx));
735         }
736     }
737 }
738 
_shoals_connecting_point_clamp_height(const coord_def & c,int radius)739 static void _shoals_connecting_point_clamp_height(
740     const coord_def &c, int radius)
741 {
742     if (!in_bounds(c))
743         return;
744 
745     for (rectangle_iterator ri(c - coord_def(radius, radius),
746                                c + coord_def(radius, radius)); ri; ++ri)
747     {
748         _shoals_clamp_height_at(*ri);
749     }
750 
751     const int min_height_threshold = (SHT_SHALLOW_WATER + SHT_FLOOR) / 2;
752     if (dgn_height_at(c) < min_height_threshold)
753         dgn_height_at(c) = min_height_threshold;
754 }
755 
dgn_shoals_connect_point(const coord_def & point)756 bool dgn_shoals_connect_point(const coord_def &point)
757 {
758     flood_find<feature_grid, coord_predicate> ff(env.grid, in_bounds, true,
759                                                  false);
760     ff.add_feat(DNGN_FLOOR);
761 
762     const coord_def target = ff.find_first_from(point, env.level_map_mask);
763     if (!in_bounds(target))
764         return false;
765 
766     const vector<coord_def> track =
767         dgn_join_the_dots_pathfind(point, target, MMT_VAULT);
768 
769     if (!track.empty())
770     {
771         const int n_points = 15;
772         const int radius = 4;
773 
774         for (auto tc : track)
775         {
776             int height = 0, npoints = 0;
777             for (radius_iterator ri(tc, radius, C_POINTY); ri; ++ri)
778             {
779                 if (in_bounds(*ri))
780                 {
781                     height += dgn_height_at(*ri);
782                     ++npoints;
783                 }
784             }
785 
786             const int target_height = SHT_FLOOR;
787             if (height < target_height)
788             {
789                 const int elevation_change = target_height - height;
790                 const int elevation_change_per_dot =
791                     max(1, elevation_change / n_points + 1);
792 
793                 dgn_island_centred_at(tc, n_points, radius,
794                                       int_range(elevation_change_per_dot,
795                                                 elevation_change_per_dot + 20),
796                                       3);
797             }
798         }
799 
800         for (int i = track.size() - 1; i >= 0; --i)
801         {
802             const coord_def &p(track[i]);
803             _shoals_connecting_point_smooth(p, radius + 2);
804         }
805         for (int i = track.size() - 1; i >= 0; --i)
806         {
807             const coord_def &p(track[i]);
808             _shoals_connecting_point_clamp_height(p, radius + 2);
809         }
810 
811         _shoals_postbuild_apply_level();
812     }
813     return !track.empty();
814 }
815 
_shoals_run_tide(int & tide,int & acc)816 static void _shoals_run_tide(int &tide, int &acc)
817 {
818     // If someone is calling the tide, the tide velocity is clamped high.
819     if (tide_caller)
820         acc = CALL_TIDE_VELOCITY;
821     // If there's no tide caller and our velocity is suspiciously high,
822     // reset it to a falling tide at peak velocity.
823     else if (abs(acc) > PEAK_TIDE_VELOCITY)
824         acc = -PEAK_TIDE_VELOCITY;
825 
826     tide += acc;
827     tide = max(min(tide, HIGH_TIDE), LOW_TIDE);
828     if ((tide == HIGH_TIDE && acc > 0) || (tide == LOW_TIDE && acc < 0))
829         acc = -acc;
830     bool in_decel_margin =
831         (abs(tide - HIGH_TIDE) < TIDE_DECEL_MARGIN)
832         || (abs(tide - LOW_TIDE) < TIDE_DECEL_MARGIN);
833     if ((abs(acc) > 1) == in_decel_margin)
834         acc = in_decel_margin? acc / 2 : acc * 2;
835 }
836 
_shoals_tide_wash_blood_away_at(coord_def c)837 static void _shoals_tide_wash_blood_away_at(coord_def c)
838 {
839     env.pgrid(c) &= ~FPROP_BLOODY;
840 }
841 
_shoals_apply_tide_feature_at(coord_def c,dungeon_feature_type feat)842 static void _shoals_apply_tide_feature_at(
843     coord_def c,
844     dungeon_feature_type feat)
845 {
846     const dungeon_feature_type current_feat = env.grid(c);
847 
848     if (feat == current_feat)
849         return;
850 
851     if (crawl_state.generating_level)
852         env.grid(c) = feat;
853     else
854         dungeon_terrain_changed(c, feat, false, true);
855 }
856 
857 // Determines if the tide is rising or falling based on before and
858 // after features at the same square.
_shoals_feature_tide_height_change(dungeon_feature_type oldfeat,dungeon_feature_type newfeat)859 static tide_dir _shoals_feature_tide_height_change(
860     dungeon_feature_type oldfeat,
861     dungeon_feature_type newfeat)
862 {
863     const int height_delta =
864         _shoals_feature_height(newfeat) - _shoals_feature_height(oldfeat);
865     // If the apparent height of the new feature is greater (floor vs water),
866     // the tide is receding.
867     return height_delta < 0 ? tide_dir::rising : tide_dir::falling;
868 }
869 
_shoals_apply_tide_at(coord_def c,int tide)870 static void _shoals_apply_tide_at(coord_def c, int tide)
871 {
872     if (is_tide_immune(c))
873         return;
874 
875     const int effective_height = dgn_height_at(c) - tide;
876     dungeon_feature_type newfeat =
877         _shoals_feature_by_height(effective_height);
878     // Make sure we're not sprouting new walls, or deep water.
879     if (feat_is_wall(newfeat))
880         newfeat = DNGN_FLOOR;
881     if (feat_is_water(newfeat))
882         newfeat = DNGN_SHALLOW_WATER;
883     const dungeon_feature_type oldfeat = env.grid(c);
884 
885 
886     if (oldfeat == newfeat
887         || (_shoals_feature_tide_height_change(oldfeat, newfeat) !=
888             _shoals_tide_direction))
889     {
890         return;
891     }
892 
893     _shoals_apply_tide_feature_at(c, newfeat);
894 }
895 
_shoals_tide_at(coord_def pos,int base_tide)896 static int _shoals_tide_at(coord_def pos, int base_tide)
897 {
898     if (!tide_caller)
899         return base_tide;
900 
901     pos -= tide_caller->pos();
902     if (pos.rdist() > TIDE_CALL_RADIUS)
903         return base_tide;
904 
905     return base_tide + max(0, tide_called_peak - pos.rdist() * 3);
906 }
907 
_shoals_extra_tide_seeds()908 static vector<coord_def> _shoals_extra_tide_seeds()
909 {
910     return find_marker_positions_by_prop("tide_seed");
911 }
912 
_shoals_apply_tide(int tide)913 static void _shoals_apply_tide(int tide)
914 {
915     vector<coord_def> pages[2];
916     int current_page = 0;
917 
918     // Start from corners of the map.
919     pages[current_page].emplace_back(1,1);
920     pages[current_page].emplace_back(GXM - 2, 1);
921     pages[current_page].emplace_back(1, GYM - 2);
922     pages[current_page].emplace_back(GXM - 2, GYM - 2);
923 
924     // Find any extra seeds -- markers with tide_seed="y".
925     const vector<coord_def> extra_seeds(_shoals_extra_tide_seeds());
926     pages[current_page].insert(pages[current_page].end(),
927                                extra_seeds.begin(), extra_seeds.end());
928 
929     FixedArray<bool, GXM, GYM> seen_points(false);
930 
931     while (!pages[current_page].empty())
932     {
933         int next_page = !current_page;
934         vector<coord_def> &cpage(pages[current_page]);
935         vector<coord_def> &npage(pages[next_page]);
936 
937         for (const coord_def &c : cpage)
938         {
939             const dungeon_feature_type herefeat(env.grid(c));
940             const bool was_wet = (_shoals_tide_passable_feat(herefeat)
941                                   && !is_temp_terrain(c));
942             seen_points(c) = true;
943             if (_shoals_tide_susceptible_feat(herefeat))
944                 _shoals_apply_tide_at(c, _shoals_tide_at(c, tide));
945 
946             const bool is_wet(feat_is_water(env.grid(c)));
947 
948             // Only squares that were wet (before applying tide
949             // effects!) can propagate the tide onwards. If the tide is
950             // receding and just left the square dry, there's only a chance of
951             // it continuing past and draining other squares through this one.
952             if (was_wet && (is_wet || coinflip()))
953             {
954                 for (adjacent_iterator ai(c); ai; ++ai)
955                 {
956                     coord_def adj(*ai);
957                     if (!in_bounds(adj))
958                         continue;
959                     if (!seen_points(adj))
960                     {
961                         const dungeon_feature_type feat = env.grid(adj);
962                         if (_shoals_tide_passable_feat(feat)
963                             || _shoals_tide_susceptible_feat(feat))
964                         {
965                             npage.push_back(adj);
966                             seen_points(adj) = true;
967                         }
968                         // Squares that the tide cannot directly
969                         // affect may still lose bloodspatter as the
970                         // tide goes past.
971                         else if (is_bloodcovered(adj)
972                                  && one_chance_in(15))
973                         {
974                             _shoals_tide_wash_blood_away_at(adj);
975                         }
976                     }
977                 }
978             }
979         }
980 
981         cpage.clear();
982         current_page = next_page;
983     }
984 }
985 
_shoals_init_tide()986 static void _shoals_init_tide()
987 {
988     CrawlHashTable &props = you.props;
989     if (!props.exists(PROPS_SHOALS_TIDE_KEY))
990     {
991         props[PROPS_SHOALS_TIDE_KEY].get_short() = 0;
992         props[PROPS_SHOALS_TIDE_VEL].get_short() = PEAK_TIDE_VELOCITY;
993         props[PROPS_SHOALS_TIDE_UPDATE_TIME].get_int() = 0;
994     }
995     if (!env.properties.exists(PROPS_SHOALS_TIDE_KEY))
996         env.properties[PROPS_SHOALS_TIDE_KEY].get_short() = 0;
997 }
998 
_shoals_find_tide_caller()999 static monster* _shoals_find_tide_caller()
1000 {
1001     for (monster_iterator mi; mi; ++mi)
1002         if (mi->has_ench(ENCH_TIDE))
1003             return *mi;
1004     return nullptr;
1005 }
1006 
shoals_apply_tides(int turns_elapsed,bool force)1007 void shoals_apply_tides(int turns_elapsed, bool force)
1008 {
1009     if (!player_in_branch(BRANCH_SHOALS)
1010         || (!turns_elapsed && !force)
1011         || !env.heightmap)
1012     {
1013         return;
1014     }
1015 
1016     // isolate from main levelgen rng if called from there; the behaviour of
1017     // this function is dependent on global state (tide direction, etc) that
1018     // may impact the number of rolls depending on when the player changes
1019     // shoals levels, so doing this with the levelgen rng has downstream
1020     // effects.
1021     rng::generator tide_rng(rng::GAMEPLAY);
1022 
1023     CrawlHashTable &props(you.props);
1024     _shoals_init_tide();
1025 
1026     // Make sure we don't do too much catch-up if another Shoals level
1027     // has been updating the tide.
1028     if (turns_elapsed > 1)
1029     {
1030         const int last_updated_time =
1031             props[PROPS_SHOALS_TIDE_UPDATE_TIME].get_int();
1032         const int turn_delta = (you.elapsed_time - last_updated_time) / 10;
1033         turns_elapsed = min(turns_elapsed, turn_delta);
1034     }
1035 
1036     const int TIDE_UNIT = HIGH_TIDE - LOW_TIDE;
1037     // If we've been gone a long time, eliminate some unnecessary math.
1038     if (turns_elapsed > TIDE_UNIT * 2)
1039         turns_elapsed = turns_elapsed % TIDE_UNIT + TIDE_UNIT;
1040 
1041     unwind_var<monster* > tide_caller_unwind(tide_caller,
1042                                              _shoals_find_tide_caller());
1043     if (tide_caller)
1044     {
1045         tide_called_turns = tide_caller->props[TIDE_CALL_TURN].get_int();
1046         tide_called_turns = you.num_turns - tide_called_turns;
1047         if (tide_called_turns < 1L)
1048             tide_called_turns = 1L;
1049         tide_called_peak  = min(HIGH_CALLED_TIDE, int(tide_called_turns * 5));
1050         tide_caller_pos = tide_caller->pos();
1051     }
1052 
1053     int tide = props[PROPS_SHOALS_TIDE_KEY].get_short();
1054     int acc = props[PROPS_SHOALS_TIDE_VEL].get_short();
1055     const int old_tide = env.properties[PROPS_SHOALS_TIDE_KEY].get_short();
1056     while (turns_elapsed-- > 0)
1057         _shoals_run_tide(tide, acc);
1058 
1059     props[PROPS_SHOALS_TIDE_KEY].get_short() = tide;
1060     props[PROPS_SHOALS_TIDE_VEL].get_short() = acc;
1061     props[PROPS_SHOALS_TIDE_UPDATE_TIME].get_int() = you.elapsed_time;
1062     env.properties[PROPS_SHOALS_TIDE_KEY].get_short() = tide;
1063 
1064     if (force
1065         || tide_caller
1066         || old_tide / TIDE_MULTIPLIER != tide / TIDE_MULTIPLIER)
1067     {
1068         _shoals_tide_direction =
1069             tide > old_tide ? tide_dir::rising : tide_dir::falling;
1070         _shoals_apply_tide(tide / TIDE_MULTIPLIER);
1071     }
1072 }
1073 
shoals_release_tide(monster * mons)1074 void shoals_release_tide(monster* mons)
1075 {
1076     if (player_in_branch(BRANCH_SHOALS))
1077     {
1078         if (player_can_hear(mons->pos()))
1079         {
1080             mprf(MSGCH_SOUND, "The tide is released from %s call.",
1081                  apostrophise(mons->name(DESC_YOUR, true)).c_str());
1082             if (you.see_cell(mons->pos()))
1083                 flash_view_delay(UA_MONSTER, ETC_WATER, 150);
1084         }
1085         shoals_apply_tides(0, true);
1086     }
1087 }
1088 
1089 #ifdef WIZARD
_shoals_change_tide_granularity(int newval)1090 static void _shoals_change_tide_granularity(int newval)
1091 {
1092     LOW_TIDE        = LOW_TIDE * newval / TIDE_MULTIPLIER;
1093     HIGH_TIDE       = HIGH_TIDE * newval / TIDE_MULTIPLIER;
1094     TIDE_MULTIPLIER = newval;
1095 }
1096 
_tidemod_keyfilter(int & c)1097 static keyfun_action _tidemod_keyfilter(int &c)
1098 {
1099     return c == '+' || c == '-'? KEYFUN_BREAK : KEYFUN_PROCESS;
1100 }
1101 
_shoals_force_tide(CrawlHashTable & props,int increment)1102 static void _shoals_force_tide(CrawlHashTable &props, int increment)
1103 {
1104     int tide = props[PROPS_SHOALS_TIDE_KEY].get_short();
1105     tide += increment * TIDE_MULTIPLIER;
1106     tide = min(HIGH_TIDE, max(LOW_TIDE, tide));
1107     props[PROPS_SHOALS_TIDE_KEY] = short(tide);
1108     _shoals_tide_direction = increment > 0 ? tide_dir::rising : tide_dir::falling;
1109     _shoals_apply_tide(tide / TIDE_MULTIPLIER);
1110 }
1111 
wizard_mod_tide()1112 void wizard_mod_tide()
1113 {
1114     if (!player_in_branch(BRANCH_SHOALS) || !env.heightmap)
1115     {
1116         mprf(MSGCH_WARN, "Not in Shoals or no heightmap; tide not available.");
1117         return;
1118     }
1119 
1120     char buf[80];
1121     while (true)
1122     {
1123         mprf(MSGCH_PROMPT,
1124              "Tide inertia: %d. New value "
1125              "(smaller = faster tide) or use +/- to change tide: ",
1126              TIDE_MULTIPLIER);
1127         mpr("");
1128         const int res =
1129             cancellable_get_line(buf, sizeof buf, nullptr, _tidemod_keyfilter);
1130         clear_messages(true);
1131         if (key_is_escape(res))
1132             break;
1133         if (!res)
1134         {
1135             const int newgran = atoi(buf);
1136             if (newgran > 0 && newgran < 3000)
1137                 _shoals_change_tide_granularity(newgran);
1138         }
1139         if (res == '+' || res == '-')
1140         {
1141             _shoals_force_tide(you.props, res == '+'? 2 : -2);
1142             viewwindow();
1143             update_screen();
1144         }
1145     }
1146 }
1147 #endif
1148