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 ®ion_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 ®ion_map,
437 const vector<weighted_region> ®ions)
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