1 /**
2  * @file
3  * @brief Terrain related functions.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "terrain.h"
9 
10 #include <algorithm>
11 #include <functional>
12 #include <sstream>
13 
14 #include "areas.h"
15 #include "attack.h"
16 #include "branch.h"
17 #include "cloud.h"
18 #include "coord.h"
19 #include "coordit.h"
20 #include "dgn-event.h"
21 #include "dgn-overview.h"
22 #include "directn.h"
23 #include "dungeon.h"
24 #include "env.h"
25 #include "tile-env.h"
26 #include "fight.h"
27 #include "feature.h"
28 #include "fprop.h"
29 #include "god-abil.h"
30 #include "item-prop.h"
31 #include "items.h"
32 #include "level-state-type.h"
33 #include "libutil.h"
34 #include "mapmark.h"
35 #include "message.h"
36 #include "mon-behv.h"
37 #include "mon-place.h"
38 #include "mon-poly.h"
39 #include "mon-util.h"
40 #include "ouch.h"
41 #include "player.h"
42 #include "random.h"
43 #include "religion.h"
44 #include "species.h"
45 #include "spl-damage.h" // ramparts_damage
46 #include "spl-transloc.h"
47 #include "state.h"
48 #include "stringutil.h"
49 #include "tag-version.h"
50 #include "tileview.h"
51 #include "transform.h"
52 #include "traps.h"
53 #include "travel.h"
54 #include "viewchar.h"
55 #include "view.h"
56 
57 static bool _revert_terrain_to(coord_def pos, dungeon_feature_type feat);
58 
actor_at(const coord_def & c)59 actor* actor_at(const coord_def& c)
60 {
61     if (!in_bounds(c))
62         return nullptr;
63     if (c == you.pos())
64         return &you;
65     return monster_at(c);
66 }
67 
68 /** Can a malign gateway be placed on this feature?
69  */
feat_is_malign_gateway_suitable(dungeon_feature_type feat)70 bool feat_is_malign_gateway_suitable(dungeon_feature_type feat)
71 {
72     return feat == DNGN_FLOOR || feat == DNGN_SHALLOW_WATER;
73 }
74 
75 /** Is this feature a type of wall?
76  */
feat_is_wall(dungeon_feature_type feat)77 bool feat_is_wall(dungeon_feature_type feat)
78 {
79     return get_feature_def(feat).flags & FFT_WALL;
80 }
81 
82 /** Is this feature one of the main stone downstairs of a level?
83  */
feat_is_stone_stair_down(dungeon_feature_type feat)84 bool feat_is_stone_stair_down(dungeon_feature_type feat)
85 {
86      return feat == DNGN_STONE_STAIRS_DOWN_I
87             || feat == DNGN_STONE_STAIRS_DOWN_II
88             || feat == DNGN_STONE_STAIRS_DOWN_III;
89 }
90 
91 /** Is this feature one of the main stone upstairs of a level?
92  */
feat_is_stone_stair_up(dungeon_feature_type feat)93 bool feat_is_stone_stair_up(dungeon_feature_type feat)
94 {
95     return feat == DNGN_STONE_STAIRS_UP_I
96            || feat == DNGN_STONE_STAIRS_UP_II
97            || feat == DNGN_STONE_STAIRS_UP_III;
98 }
99 
100 /** Is this feature one of the main stone stairs of a level?
101  */
feat_is_stone_stair(dungeon_feature_type feat)102 bool feat_is_stone_stair(dungeon_feature_type feat)
103 {
104     return feat_is_stone_stair_up(feat) || feat_is_stone_stair_down(feat);
105 }
106 
107 /** Is it possible to call this feature a staircase? (purely cosmetic)
108  */
feat_is_staircase(dungeon_feature_type feat)109 bool feat_is_staircase(dungeon_feature_type feat)
110 {
111     if (feat_is_stone_stair(feat))
112         return true;
113 
114     // All branch entries/exits are staircases, except for Zot and Vaults entry.
115     if (feat == DNGN_ENTER_VAULTS
116         || feat == DNGN_EXIT_VAULTS
117         || feat == DNGN_ENTER_ZOT
118         || feat == DNGN_EXIT_ZOT)
119     {
120         return false;
121     }
122 
123     return feat_is_branch_entrance(feat)
124            || feat_is_branch_exit(feat)
125            || feat == DNGN_ABYSSAL_STAIR;
126 }
127 
128 /**
129  * Define a memoized function from dungeon_feature_type to bool.
130  * This macro should be followed by the non-memoized version of the
131  * function body: see feat_is_branch_entrance below for an example.
132  *
133  * @param funcname The name of the function to define.
134  * @param paramname The name under which the function's single parameter,
135  *        of type dungeon_feature_type, is visible in the function body.
136  */
137 #define FEATFN_MEMOIZED(funcname, paramname) \
138     static bool _raw_ ## funcname (dungeon_feature_type); \
139     bool funcname (dungeon_feature_type feat) \
140     { \
141         static int cached[NUM_FEATURES+1] = { 0 }; \
142         if (!cached[feat]) cached[feat] = _raw_ ## funcname (feat) ? 1 : -1; \
143         return cached[feat] > 0; \
144     } \
145     static bool _raw_ ## funcname (dungeon_feature_type paramname)
146 
147 /** Is this feature a branch entrance that should show up on ^O?
148  */
FEATFN_MEMOIZED(feat_is_branch_entrance,feat)149 FEATFN_MEMOIZED(feat_is_branch_entrance, feat)
150 {
151     if (feat == DNGN_ENTER_HELL)
152         return false;
153 
154     for (branch_iterator it; it; ++it)
155     {
156         if (it->entry_stairs == feat
157             && is_connected_branch(it->id))
158         {
159             return true;
160         }
161     }
162 
163     return false;
164 }
165 
166 /** Counterpart to feat_is_branch_entrance.
167  */
FEATFN_MEMOIZED(feat_is_branch_exit,feat)168 FEATFN_MEMOIZED(feat_is_branch_exit, feat)
169 {
170     if (feat == DNGN_ENTER_HELL || feat == DNGN_EXIT_HELL)
171         return false;
172 
173     for (branch_iterator it; it; ++it)
174     {
175         if (it->exit_stairs == feat
176             && is_connected_branch(it->id))
177         {
178             return true;
179         }
180     }
181 
182     return false;
183 }
184 
185 /** Is this feature an entrance to a portal branch?
186  */
FEATFN_MEMOIZED(feat_is_portal_entrance,feat)187 FEATFN_MEMOIZED(feat_is_portal_entrance, feat)
188 {
189     // These are have different rules from normal connected branches, but they
190     // also have different rules from "portal vaults," and are more similar to
191     // real branches in some respects.
192     if (feat == DNGN_ENTER_ABYSS || feat == DNGN_ENTER_PANDEMONIUM)
193         return false;
194 
195     for (branch_iterator it; it; ++it)
196     {
197         if (it->entry_stairs == feat
198             && !is_connected_branch(it->id))
199         {
200             return true;
201         }
202     }
203 #if TAG_MAJOR_VERSION == 34
204     if (feat == DNGN_ENTER_PORTAL_VAULT)
205         return true;
206 #endif
207 
208     return false;
209 }
210 
211 /** Counterpart to feat_is_portal_entrance.
212  */
FEATFN_MEMOIZED(feat_is_portal_exit,feat)213 FEATFN_MEMOIZED(feat_is_portal_exit, feat)
214 {
215     if (feat == DNGN_EXIT_ABYSS || feat == DNGN_EXIT_PANDEMONIUM)
216         return false;
217 
218     for (branch_iterator it; it; ++it)
219     {
220         if (it->exit_stairs == feat
221             && !is_connected_branch(it->id))
222         {
223             return true;
224         }
225     }
226 #if TAG_MAJOR_VERSION == 34
227     if (feat == DNGN_EXIT_PORTAL_VAULT)
228         return true;
229 #endif
230 
231     return false;
232 }
233 
234 /** Is this feature a kind of portal?
235  */
feat_is_portal(dungeon_feature_type feat)236 bool feat_is_portal(dungeon_feature_type feat)
237 {
238     return feat == DNGN_MALIGN_GATEWAY
239         || feat_is_portal_entrance(feat)
240         || feat_is_portal_exit(feat);
241 }
242 
243 /** Is this feature a kind of level exit?
244  */
feat_is_stair(dungeon_feature_type gridc)245 bool feat_is_stair(dungeon_feature_type gridc)
246 {
247     return feat_is_travelable_stair(gridc) || feat_is_gate(gridc);
248 }
249 
250 /** Is this feature a level exit stair with a consistent endpoint?
251  */
feat_is_travelable_stair(dungeon_feature_type feat)252 bool feat_is_travelable_stair(dungeon_feature_type feat)
253 {
254     return feat_is_stone_stair(feat)
255            || feat_is_escape_hatch(feat)
256            || feat_is_branch_entrance(feat)
257            || feat_is_branch_exit(feat)
258            || feat == DNGN_ENTER_HELL
259            || feat == DNGN_EXIT_HELL;
260 }
261 
262 /** Is this feature an escape hatch?
263  */
feat_is_escape_hatch(dungeon_feature_type feat)264 bool feat_is_escape_hatch(dungeon_feature_type feat)
265 {
266     return feat == DNGN_ESCAPE_HATCH_DOWN
267            || feat == DNGN_ESCAPE_HATCH_UP;
268 }
269 
270 /** Is this feature a gate?
271   * XXX: Why does this matter??
272  */
feat_is_gate(dungeon_feature_type feat)273 bool feat_is_gate(dungeon_feature_type feat)
274 {
275     if (feat_is_portal_entrance(feat)
276         || feat_is_portal_exit(feat))
277     {
278         return true;
279     }
280 
281     switch (feat)
282     {
283     case DNGN_ENTER_ABYSS:
284     case DNGN_EXIT_THROUGH_ABYSS:
285     case DNGN_EXIT_ABYSS:
286     case DNGN_ABYSSAL_STAIR:
287     case DNGN_ENTER_PANDEMONIUM:
288     case DNGN_EXIT_PANDEMONIUM:
289     case DNGN_TRANSIT_PANDEMONIUM:
290     case DNGN_ENTER_VAULTS:
291     case DNGN_EXIT_VAULTS:
292     case DNGN_ENTER_ZOT:
293     case DNGN_EXIT_ZOT:
294     case DNGN_ENTER_HELL:
295     case DNGN_EXIT_HELL:
296     case DNGN_ENTER_DIS:
297     case DNGN_ENTER_GEHENNA:
298     case DNGN_ENTER_COCYTUS:
299     case DNGN_ENTER_TARTARUS:
300         return true;
301     default:
302         return false;
303     }
304 }
305 
306 /** What command do you use to traverse this feature?
307  *
308  *  @param feat the feature.
309  *  @returns CMD_GO_UPSTAIRS if it's a stair up, CMD_GO_DOWNSTAIRS if it's a
310  *           stair down, and CMD_NO_CMD if it can't be used to move.
311  */
feat_stair_direction(dungeon_feature_type feat)312 command_type feat_stair_direction(dungeon_feature_type feat)
313 {
314     if (feat_is_portal_entrance(feat)
315         || feat_is_branch_entrance(feat))
316     {
317         return CMD_GO_DOWNSTAIRS;
318     }
319     if (feat_is_portal_exit(feat)
320         || feat_is_branch_exit(feat))
321     {
322         return CMD_GO_UPSTAIRS;
323     }
324 
325     if (feat_is_altar(feat))
326         return CMD_GO_UPSTAIRS; // arbitrary; consistent with shops
327 
328     switch (feat)
329     {
330     case DNGN_ENTER_HELL:
331         return player_in_hell() ? CMD_GO_UPSTAIRS : CMD_GO_DOWNSTAIRS;
332 
333     case DNGN_STONE_STAIRS_UP_I:
334     case DNGN_STONE_STAIRS_UP_II:
335     case DNGN_STONE_STAIRS_UP_III:
336     case DNGN_ESCAPE_HATCH_UP:
337     case DNGN_ENTER_SHOP:
338     case DNGN_EXIT_HELL:
339         return CMD_GO_UPSTAIRS;
340 
341     case DNGN_STONE_STAIRS_DOWN_I:
342     case DNGN_STONE_STAIRS_DOWN_II:
343     case DNGN_STONE_STAIRS_DOWN_III:
344     case DNGN_ESCAPE_HATCH_DOWN:
345     case DNGN_ENTER_ABYSS:
346     case DNGN_EXIT_THROUGH_ABYSS:
347     case DNGN_EXIT_ABYSS:
348     case DNGN_ABYSSAL_STAIR:
349     case DNGN_ENTER_PANDEMONIUM:
350     case DNGN_EXIT_PANDEMONIUM:
351     case DNGN_TRANSIT_PANDEMONIUM:
352     case DNGN_TRANSPORTER:
353         return CMD_GO_DOWNSTAIRS;
354 
355     default:
356         return CMD_NO_CMD;
357     }
358 }
359 
360 /** Can you normally see through this feature?
361  */
feat_is_opaque(dungeon_feature_type feat)362 bool feat_is_opaque(dungeon_feature_type feat)
363 {
364     return get_feature_def(feat).flags & FFT_OPAQUE;
365 }
366 
367 /** Can you move into this feature in normal play?
368  */
feat_is_solid(dungeon_feature_type feat)369 bool feat_is_solid(dungeon_feature_type feat)
370 {
371     return get_feature_def(feat).flags & FFT_SOLID;
372 }
373 
374 /** Can you wall jump against this feature? (Wu Jian)?
375  */
feat_can_wall_jump_against(dungeon_feature_type feat)376 bool feat_can_wall_jump_against(dungeon_feature_type feat)
377 {
378     return feat_is_wall(feat)
379            || feat == DNGN_GRATE
380            || feat_is_closed_door(feat)
381            || feat_is_tree(feat)
382            || feat_is_statuelike(feat);
383 }
384 
385 /** Can you move into this cell in normal play?
386  */
cell_is_solid(const coord_def & c)387 bool cell_is_solid(const coord_def &c)
388 {
389     return feat_is_solid(env.grid(c));
390 }
391 
392 /** Can a human stand on this feature without flying?
393  */
feat_has_solid_floor(dungeon_feature_type feat)394 bool feat_has_solid_floor(dungeon_feature_type feat)
395 {
396     return !feat_is_solid(feat) && feat != DNGN_DEEP_WATER
397            && feat != DNGN_LAVA;
398 }
399 
400 /** Is there enough dry floor on this feature to stand without penalty?
401  */
feat_has_dry_floor(dungeon_feature_type feat)402 bool feat_has_dry_floor(dungeon_feature_type feat)
403 {
404     return feat_has_solid_floor(feat) && !feat_is_water(feat);
405 }
406 
407 /** Is this feature a variety of door?
408  */
feat_is_door(dungeon_feature_type feat)409 bool feat_is_door(dungeon_feature_type feat)
410 {
411     return feat_is_closed_door(feat) || feat_is_open_door(feat);
412 }
413 
414 /** Is this feature a variety of closed door?
415  */
feat_is_closed_door(dungeon_feature_type feat)416 bool feat_is_closed_door(dungeon_feature_type feat)
417 {
418     return feat == DNGN_CLOSED_DOOR
419            || feat == DNGN_CLOSED_CLEAR_DOOR
420            || feat_is_runed(feat)
421            || feat == DNGN_SEALED_DOOR
422            || feat == DNGN_SEALED_CLEAR_DOOR;
423 }
424 
425 /** Is this feature a variety of open door?
426  */
feat_is_open_door(dungeon_feature_type feat)427 bool feat_is_open_door(dungeon_feature_type feat)
428 {
429     return feat == DNGN_OPEN_DOOR || feat == DNGN_OPEN_CLEAR_DOOR;
430 }
431 
432 /** Has this feature been sealed by a vault warden?
433  */
feat_is_sealed(dungeon_feature_type feat)434 bool feat_is_sealed(dungeon_feature_type feat)
435 {
436     return feat == DNGN_SEALED_STAIRS_DOWN
437         || feat == DNGN_SEALED_STAIRS_UP
438         || feat == DNGN_SEALED_DOOR
439         || feat == DNGN_SEALED_CLEAR_DOOR;
440 }
441 
442 /** Is this feature a type of runed door?
443  */
feat_is_runed(dungeon_feature_type feat)444 bool feat_is_runed(dungeon_feature_type feat)
445 {
446     return feat == DNGN_RUNED_DOOR || feat == DNGN_RUNED_CLEAR_DOOR;
447 }
448 
449 /** Is the original feature at this position runed, as in a runed door?
450  */
cell_is_runed(const coord_def & p)451 bool cell_is_runed(const coord_def &p)
452 {
453     // the orig_terrain call will check the actual terrain if there's no change
454     return feat_is_runed(orig_terrain(p));
455 }
456 
457 /** Is this feature a type of statue, i.e., granite or an idol?
458  */
feat_is_statuelike(dungeon_feature_type feat)459 bool feat_is_statuelike(dungeon_feature_type feat)
460 {
461     return feat == DNGN_ORCISH_IDOL || feat == DNGN_GRANITE_STATUE;
462 }
463 
464 /** Is this feature permanent, unalterable rock?
465  */
feat_is_permarock(dungeon_feature_type feat)466 bool feat_is_permarock(dungeon_feature_type feat)
467 {
468     return feat == DNGN_PERMAROCK_WALL || feat == DNGN_CLEAR_PERMAROCK_WALL;
469 }
470 
471 /** Is this feature an open expanse used only as a map border?
472  */
feat_is_endless(dungeon_feature_type feat)473 bool feat_is_endless(dungeon_feature_type feat)
474 {
475     return feat == DNGN_OPEN_SEA || feat == DNGN_LAVA_SEA
476            || feat == DNGN_ENDLESS_SALT;
477 }
478 
479 /** Can this feature be dug?
480  */
feat_is_diggable(dungeon_feature_type feat)481 bool feat_is_diggable(dungeon_feature_type feat)
482 {
483     return feat == DNGN_ROCK_WALL || feat == DNGN_CLEAR_ROCK_WALL
484            || feat == DNGN_SLIMY_WALL || feat == DNGN_GRATE
485            || feat == DNGN_ORCISH_IDOL || feat == DNGN_GRANITE_STATUE
486            || feat == DNGN_PETRIFIED_TREE;
487 }
488 
489 /** Is this feature a type of trap?
490  *
491  *  @param feat the feature.
492  *  @returns true if it's a trap.
493  */
feat_is_trap(dungeon_feature_type feat)494 bool feat_is_trap(dungeon_feature_type feat)
495 {
496     if (!is_valid_feature_type(feat))
497         return false; // ???
498     return get_feature_def(feat).flags & FFT_TRAP;
499 }
500 
501 /** Is this feature a type of water, with the concomitant dangers/bonuss?
502  */
feat_is_water(dungeon_feature_type feat)503 bool feat_is_water(dungeon_feature_type feat)
504 {
505     return feat == DNGN_SHALLOW_WATER
506            || feat == DNGN_DEEP_WATER
507            || feat == DNGN_OPEN_SEA
508            || feat == DNGN_TOXIC_BOG
509            || feat == DNGN_MANGROVE;
510 }
511 
512 /** Does this feature have enough water to keep water-only monsters alive in it?
513  */
feat_is_watery(dungeon_feature_type feat)514 bool feat_is_watery(dungeon_feature_type feat)
515 {
516     return feat_is_water(feat) || feat == DNGN_FOUNTAIN_BLUE;
517 }
518 
519 /** Is this feature a kind of lava?
520  */
feat_is_lava(dungeon_feature_type feat)521 bool feat_is_lava(dungeon_feature_type feat)
522 {
523     return feat == DNGN_LAVA || feat == DNGN_LAVA_SEA;
524 }
525 
526 static const pair<god_type, dungeon_feature_type> _god_altars[] =
527 {
528     { GOD_ZIN, DNGN_ALTAR_ZIN },
529     { GOD_SHINING_ONE, DNGN_ALTAR_SHINING_ONE },
530     { GOD_KIKUBAAQUDGHA, DNGN_ALTAR_KIKUBAAQUDGHA },
531     { GOD_YREDELEMNUL, DNGN_ALTAR_YREDELEMNUL },
532     { GOD_XOM, DNGN_ALTAR_XOM },
533     { GOD_VEHUMET, DNGN_ALTAR_VEHUMET },
534     { GOD_OKAWARU, DNGN_ALTAR_OKAWARU },
535     { GOD_MAKHLEB, DNGN_ALTAR_MAKHLEB },
536     { GOD_SIF_MUNA, DNGN_ALTAR_SIF_MUNA },
537     { GOD_TROG, DNGN_ALTAR_TROG },
538     { GOD_NEMELEX_XOBEH, DNGN_ALTAR_NEMELEX_XOBEH },
539     { GOD_ELYVILON, DNGN_ALTAR_ELYVILON },
540     { GOD_LUGONU, DNGN_ALTAR_LUGONU },
541     { GOD_BEOGH, DNGN_ALTAR_BEOGH },
542     { GOD_JIYVA, DNGN_ALTAR_JIYVA },
543     { GOD_FEDHAS, DNGN_ALTAR_FEDHAS },
544     { GOD_CHEIBRIADOS, DNGN_ALTAR_CHEIBRIADOS },
545     { GOD_ASHENZARI, DNGN_ALTAR_ASHENZARI },
546     { GOD_DITHMENOS, DNGN_ALTAR_DITHMENOS },
547     { GOD_GOZAG, DNGN_ALTAR_GOZAG },
548     { GOD_QAZLAL, DNGN_ALTAR_QAZLAL },
549     { GOD_RU, DNGN_ALTAR_RU },
550 #if TAG_MAJOR_VERSION == 34
551     { GOD_PAKELLAS, DNGN_ALTAR_PAKELLAS },
552 #endif
553     { GOD_USKAYAW, DNGN_ALTAR_USKAYAW },
554     { GOD_HEPLIAKLQANA, DNGN_ALTAR_HEPLIAKLQANA },
555     { GOD_WU_JIAN, DNGN_ALTAR_WU_JIAN },
556     { GOD_ECUMENICAL, DNGN_ALTAR_ECUMENICAL },
557 };
558 
559 COMPILE_CHECK(ARRAYSZ(_god_altars) == NUM_GODS );
560 
561 /** Whose altar is this feature?
562  *
563  *  @param feat the feature.
564  *  @returns GOD_NO_GOD if not an altar, otherwise the god_type of the god.
565  */
feat_altar_god(dungeon_feature_type feat)566 god_type feat_altar_god(dungeon_feature_type feat)
567 {
568     for (const auto &altar : _god_altars)
569         if (altar.second == feat)
570             return altar.first;
571 
572     return GOD_NO_GOD;
573 }
574 
575 /** What feature is the altar of this god?
576  *
577  *  @param god the god.
578  *  @returns DNGN_FLOOR for an invalid god, the god's altar otherwise.
579  */
altar_for_god(god_type god)580 dungeon_feature_type altar_for_god(god_type god)
581 {
582     for (const auto &altar : _god_altars)
583         if (altar.first == god)
584             return altar.second;
585 
586     return DNGN_FLOOR;
587 }
588 
589 /** Is this feature an altar to any god?
590  */
FEATFN_MEMOIZED(feat_is_altar,grid)591 FEATFN_MEMOIZED(feat_is_altar, grid)
592 {
593     return feat_altar_god(grid) != GOD_NO_GOD;
594 }
595 
596 /** Is this feature an altar to the player's god?
597  *
598  *  @param feat the feature.
599  *  @returns true if the player has a god and this is its altar.
600  */
feat_is_player_altar(dungeon_feature_type grid)601 bool feat_is_player_altar(dungeon_feature_type grid)
602 {
603     return !you_worship(GOD_NO_GOD) && you_worship(feat_altar_god(grid));
604 }
605 
606 /** Is this feature a tree?
607  */
feat_is_tree(dungeon_feature_type feat)608 bool feat_is_tree(dungeon_feature_type feat)
609 {
610     return feat == DNGN_TREE || feat == DNGN_MANGROVE
611         || feat == DNGN_PETRIFIED_TREE || feat == DNGN_DEMONIC_TREE;
612 }
613 
614 /** Is this feature flammable?
615  */
feat_is_flammable(dungeon_feature_type feat)616 bool feat_is_flammable(dungeon_feature_type feat)
617 {
618     return feat == DNGN_TREE || feat == DNGN_MANGROVE
619         || feat == DNGN_DEMONIC_TREE;
620 }
621 
622 
623 /** Is this feature made of metal?
624  */
feat_is_metal(dungeon_feature_type feat)625 bool feat_is_metal(dungeon_feature_type feat)
626 {
627     return feat == DNGN_METAL_WALL || feat == DNGN_GRATE;
628 }
629 
630 /** Is this feature ambivalent about whether we're going up or down?
631  */
feat_is_bidirectional_portal(dungeon_feature_type feat)632 bool feat_is_bidirectional_portal(dungeon_feature_type feat)
633 {
634     return get_feature_dchar(feat) == DCHAR_ARCH
635            && feat_stair_direction(feat) != CMD_NO_CMD
636            && feat != DNGN_ENTER_ZOT
637            && feat != DNGN_EXIT_ZOT
638            && feat != DNGN_ENTER_VAULTS
639            && feat != DNGN_EXIT_VAULTS
640            && feat != DNGN_EXIT_HELL
641            && feat != DNGN_ENTER_HELL;
642 }
643 
644 /** Is this feature a type of fountain?
645  */
feat_is_fountain(dungeon_feature_type feat)646 bool feat_is_fountain(dungeon_feature_type feat)
647 {
648     return feat == DNGN_FOUNTAIN_BLUE
649            || feat == DNGN_FOUNTAIN_SPARKLING
650            || feat == DNGN_FOUNTAIN_BLOOD
651            || feat == DNGN_DRY_FOUNTAIN;
652 }
653 
654 /** Is this feature non-solid enough that you can reach past it?
655  */
feat_is_reachable_past(dungeon_feature_type feat)656 bool feat_is_reachable_past(dungeon_feature_type feat)
657 {
658     return !feat_is_opaque(feat)
659         && !feat_is_wall(feat)
660         && !feat_is_closed_door(feat)
661         && feat != DNGN_GRATE;
662 }
663 
664 /** Is this feature important to the game?
665  *
666  *  @param feat the feature.
667  *  @returns true for altars, stairs/portals, and malign gateways (???).
668  */
FEATFN_MEMOIZED(feat_is_critical,feat)669 FEATFN_MEMOIZED(feat_is_critical, feat)
670 {
671     return feat_stair_direction(feat) != CMD_NO_CMD
672            || feat_altar_god(feat) != GOD_NO_GOD
673            || feat == DNGN_TRANSPORTER_LANDING
674            || feat == DNGN_MALIGN_GATEWAY;
675 }
676 
677 /** Can you use this feature for a map border?
678  */
feat_is_valid_border(dungeon_feature_type feat)679 bool feat_is_valid_border(dungeon_feature_type feat)
680 {
681     return feat_is_wall(feat)
682            || feat_is_tree(feat)
683            || feat == DNGN_OPEN_SEA
684            || feat == DNGN_LAVA_SEA
685            || feat == DNGN_ENDLESS_SALT;
686 }
687 
688 /** Can this feature be a mimic?
689  *
690  *  @param feat the feature
691  *  @param strict if true, disallow features for which being a mimic would be bad in
692                   normal generation; vaults can still use such mimics.
693  *  @returns whether this could make a valid mimic type.
694  */
feat_is_mimicable(dungeon_feature_type feat,bool strict)695 bool feat_is_mimicable(dungeon_feature_type feat, bool strict)
696 {
697     if (!strict && feat != DNGN_FLOOR && feat != DNGN_SHALLOW_WATER
698         && feat != DNGN_DEEP_WATER)
699     {
700         return true;
701     }
702 
703     if (feat == DNGN_ENTER_ZIGGURAT)
704         return false;
705 
706     if (feat_is_portal_entrance(feat))
707         return true;
708 
709     if (feat == DNGN_ENTER_SHOP)
710         return true;
711 
712     return false;
713 }
714 
715 /** Can creatures on this feature be shafted?
716  *
717  * @param feat The feature in question.
718  * @returns Whether creatures standing on this feature can be shafted (by
719  *          magical effects, Formicid digging, etc).
720  */
feat_is_shaftable(dungeon_feature_type feat)721 bool feat_is_shaftable(dungeon_feature_type feat)
722 {
723     return feat_has_dry_floor(feat)
724            && !feat_is_stair(feat)
725            && !feat_is_portal(feat);
726 }
727 
count_neighbours_with_func(const coord_def & c,bool (* checker)(dungeon_feature_type))728 int count_neighbours_with_func(const coord_def& c, bool (*checker)(dungeon_feature_type))
729 {
730     int count = 0;
731     for (adjacent_iterator ai(c); ai; ++ai)
732     {
733         if (checker(env.grid(*ai)))
734             count++;
735     }
736     return count;
737 }
738 
739 // For internal use by find_connected_identical only.
_find_connected_identical(const coord_def & d,dungeon_feature_type ft,set<coord_def> & out,bool known_only)740 static void _find_connected_identical(const coord_def &d,
741                                       dungeon_feature_type ft,
742                                       set<coord_def>& out,
743                                       bool known_only)
744 {
745     if (env.grid(d) != ft || (known_only && !env.map_knowledge(d).known()))
746         return;
747 
748     string prop = env.markers.property_at(d, MAT_ANY, "connected_exclude");
749 
750     if (!prop.empty())
751     {
752         // Don't treat this square as connected to anything. Ignore it.
753         // Continue the search in other directions.
754         return;
755     }
756 
757     if (out.insert(d).second)
758     {
759         _find_connected_identical(coord_def(d.x+1, d.y), ft, out, known_only);
760         _find_connected_identical(coord_def(d.x-1, d.y), ft, out, known_only);
761         _find_connected_identical(coord_def(d.x, d.y+1), ft, out, known_only);
762         _find_connected_identical(coord_def(d.x, d.y-1), ft, out, known_only);
763     }
764 }
765 
766 // Find all connected cells containing ft, starting at d.
find_connected_identical(const coord_def & d,set<coord_def> & out,bool known_only)767 void find_connected_identical(const coord_def &d, set<coord_def>& out, bool known_only)
768 {
769     string prop = env.markers.property_at(d, MAT_ANY, "connected_exclude");
770 
771     if (!prop.empty())
772         out.insert(d);
773     else
774         _find_connected_identical(d, env.grid(d), out, known_only);
775 }
776 
get_door_description(int door_size,const char ** adjective,const char ** noun)777 void get_door_description(int door_size, const char** adjective, const char** noun)
778 {
779     const char* descriptions[] =
780     {
781         "miniscule " , "buggy door",
782         ""           , "door",
783         "large "     , "door",
784         ""           , "gate",
785         "huge "      , "gate",
786     };
787 
788     int max_idx = static_cast<int>(ARRAYSZ(descriptions) - 2);
789     const unsigned int idx = min(door_size*2, max_idx);
790 
791     *adjective = descriptions[idx];
792     *noun = descriptions[idx+1];
793 }
794 
get_random_stair()795 coord_def get_random_stair()
796 {
797     vector<coord_def> st;
798     for (rectangle_iterator ri(1); ri; ++ri)
799     {
800         const dungeon_feature_type feat = env.grid(*ri);
801         if (feat_is_travelable_stair(feat) && !feat_is_escape_hatch(feat)
802             && feat != DNGN_EXIT_DUNGEON
803             && feat != DNGN_EXIT_HELL)
804         {
805             st.push_back(*ri);
806         }
807     }
808     if (st.empty())
809         return coord_def();        // sanity check: shouldn't happen
810     return st[random2(st.size())];
811 }
812 
813 static unique_ptr<map_mask_boolean> _slime_wall_precomputed_neighbour_mask;
814 
_precompute_slime_wall_neighbours()815 static void _precompute_slime_wall_neighbours()
816 {
817     map_mask_boolean &mask(*_slime_wall_precomputed_neighbour_mask);
818     for (rectangle_iterator ri(1); ri; ++ri)
819     {
820         if (env.grid(*ri) == DNGN_SLIMY_WALL)
821         {
822             for (adjacent_iterator ai(*ri); ai; ++ai)
823                 mask(*ai) = true;
824         }
825     }
826 }
827 
unwind_slime_wall_precomputer(bool docompute)828 unwind_slime_wall_precomputer::unwind_slime_wall_precomputer(bool docompute)
829     : did_compute_mask(false)
830 {
831     if (!(env.level_state & LSTATE_SLIMY_WALL))
832         return;
833 
834     if (docompute && !_slime_wall_precomputed_neighbour_mask)
835     {
836         did_compute_mask = true;
837         _slime_wall_precomputed_neighbour_mask.reset(
838             new map_mask_boolean(false));
839         _precompute_slime_wall_neighbours();
840     }
841 }
842 
~unwind_slime_wall_precomputer()843 unwind_slime_wall_precomputer::~unwind_slime_wall_precomputer()
844 {
845     if (did_compute_mask)
846         _slime_wall_precomputed_neighbour_mask.reset(nullptr);
847 }
848 
slime_wall_neighbour(const coord_def & c)849 bool slime_wall_neighbour(const coord_def& c)
850 {
851     if (!(env.level_state & LSTATE_SLIMY_WALL))
852         return false;
853 
854     if (_slime_wall_precomputed_neighbour_mask)
855         return (*_slime_wall_precomputed_neighbour_mask)(c);
856 
857     // Not using count_adjacent_slime_walls because the early return might
858     // be relevant for performance here. TODO: profile it and find out.
859     for (adjacent_iterator ai(c); ai; ++ai)
860         if (env.grid(*ai) == DNGN_SLIMY_WALL)
861             return true;
862     return false;
863 }
864 
count_adjacent_slime_walls(const coord_def & pos)865 int count_adjacent_slime_walls(const coord_def &pos)
866 {
867     int count = 0;
868     for (adjacent_iterator ai(pos); ai; ++ai)
869         if (env.grid(*ai) == DNGN_SLIMY_WALL)
870             count++;
871 
872     return count;
873 }
874 
slime_wall_damage(actor * act,int delay)875 void slime_wall_damage(actor* act, int delay)
876 {
877     ASSERT(act);
878 
879     if (actor_slime_wall_immune(act))
880         return;
881 
882     const int walls = count_adjacent_slime_walls(act->pos());
883     if (!walls)
884         return;
885 
886     const int strength = div_rand_round(3 * walls * delay, BASELINE_DELAY);
887 
888     if (act->is_player())
889         you.splash_with_acid(nullptr, strength, false);
890     else
891     {
892         monster* mon = act->as_monster();
893 
894         const int dam = resist_adjust_damage(mon, BEAM_ACID,
895                                              roll_dice(2, strength));
896         if (dam > 0 && you.can_see(*mon))
897         {
898             const char *verb = act->is_icy() ? "melt" : "burn";
899             mprf((walls > 1) ? "The walls %s %s!" : "The wall %ss %s!",
900                   verb, mon->name(DESC_THE).c_str());
901         }
902         mon->hurt(nullptr, dam, BEAM_ACID);
903     }
904 }
905 
count_adjacent_icy_walls(const coord_def & pos)906 int count_adjacent_icy_walls(const coord_def &pos)
907 {
908     int count = 0;
909     for (adjacent_iterator ai(pos); ai; ++ai)
910         if (is_icecovered(*ai))
911             count++;
912 
913     return count;
914 }
915 
feat_splash_noise(dungeon_feature_type feat)916 void feat_splash_noise(dungeon_feature_type feat)
917 {
918     if (crawl_state.generating_level)
919         return;
920 
921     switch (feat)
922     {
923     case DNGN_SHALLOW_WATER:
924     case DNGN_DEEP_WATER:
925         mprf(MSGCH_SOUND, "You hear a splash.");
926         return;
927 
928     case DNGN_LAVA:
929         mprf(MSGCH_SOUND, "You hear a sizzling splash.");
930         return;
931 
932     default:
933         return;
934     }
935 }
936 
FEATFN_MEMOIZED(feat_suppress_blood,feat)937 FEATFN_MEMOIZED(feat_suppress_blood, feat)
938 {
939     if (feat_is_tree(feat))
940         return true;
941 
942     if (feat == DNGN_DRY_FOUNTAIN)
943         return true;
944 
945     // covers shops and altars
946     if (feat_stair_direction(feat) != CMD_NO_CMD)
947         return true;
948 
949     if (feat == DNGN_MALIGN_GATEWAY)
950         return true;
951 
952     if (feat == DNGN_TRAP_SHAFT)
953         return true;
954 
955     return false;
956 
957 }
958 
959 /** Does this feature destroy any items that fall into it?
960  */
feat_destroys_items(dungeon_feature_type feat)961 bool feat_destroys_items(dungeon_feature_type feat)
962 {
963     return feat == DNGN_LAVA;
964 }
965 
966 /** Does this feature make items that fall into it permanently inaccessible?
967  */
feat_eliminates_items(dungeon_feature_type feat)968 bool feat_eliminates_items(dungeon_feature_type feat)
969 {
970     return feat_destroys_items(feat)
971             // intentionally use the species version rather than the player
972             // version: switching to an amphibious form doesn't give you access
973             // to the items.
974             || feat == DNGN_DEEP_WATER && !species::likes_water(you.species);
975 }
976 
_dgn_find_nearest_square(const coord_def & pos,function<bool (const coord_def &)> acceptable,function<bool (const coord_def &)> traversable=nullptr)977 static coord_def _dgn_find_nearest_square(
978     const coord_def &pos,
979     function<bool (const coord_def &)> acceptable,
980     function<bool (const coord_def &)> traversable = nullptr)
981 {
982     bool visited[GXM][GYM];
983     memset(&visited, 0, sizeof(visited));
984 
985     vector<coord_def> points[2];
986     int iter = 0;
987     points[iter].push_back(pos);
988 
989     // TODO: Deduplicate this BFS code (see commit for other two instances).
990     while (!points[iter].empty())
991     {
992         // Iterate each layer of BFS in random order to avoid bias.
993         shuffle_array(points[iter]);
994         for (const auto &p : points[iter])
995         {
996             if (p != pos && acceptable(p))
997                 return p;
998 
999             visited[p.x][p.y] = true;
1000             for (adjacent_iterator ai(p); ai; ++ai)
1001             {
1002                 const coord_def np = coord_def(ai->x, ai->y);
1003                 if (!in_bounds(np) || visited[np.x][np.y])
1004                     continue;
1005 
1006                 if (traversable && !traversable(np))
1007                     continue;
1008 
1009                 points[!iter].push_back(np);
1010             }
1011         }
1012 
1013         points[iter].clear();
1014         iter = !iter;
1015     }
1016 
1017     return coord_def(0, 0); // Not found.
1018 }
1019 
_item_safe_square(const coord_def & pos)1020 static bool _item_safe_square(const coord_def &pos)
1021 {
1022     const dungeon_feature_type feat = env.grid(pos);
1023     return feat_is_traversable(feat) && !feat_destroys_items(feat);
1024 }
1025 
_item_traversable_square(const coord_def & pos)1026 static bool _item_traversable_square(const coord_def &pos)
1027 {
1028     return !cell_is_solid(pos);
1029 }
1030 
1031 // Moves an item on the floor to the nearest adjacent floor-space.
_dgn_shift_item(const coord_def & pos,item_def & item)1032 static bool _dgn_shift_item(const coord_def &pos, item_def &item)
1033 {
1034     // First try to avoid pushing things through solid features...
1035     coord_def np = _dgn_find_nearest_square(pos, _item_safe_square,
1036                                             _item_traversable_square);
1037     // ... but if we have to, so be it.
1038     if (!in_bounds(np) || np == pos)
1039         np = _dgn_find_nearest_square(pos, _item_safe_square);
1040 
1041     if (in_bounds(np) && np != pos)
1042     {
1043         int index = item.index();
1044         move_item_to_grid(&index, np);
1045         return true;
1046     }
1047     return false;
1048 }
1049 
_is_feature_shift_target(const coord_def & pos)1050 static bool _is_feature_shift_target(const coord_def &pos)
1051 {
1052     return env.grid(pos) == DNGN_FLOOR && !dungeon_events.has_listeners_at(pos)
1053            && !actor_at(pos);
1054 }
1055 
1056 // Moves everything at src to dst. This is not a swap operation: src will be
1057 // left with the same feature it started with, and should be overwritten with
1058 // something new. Assumes there are no actors in the destination square.
1059 //
1060 // Things that are moved:
1061 // 1. Dungeon terrain (set to DNGN_UNSEEN)
1062 // 2. Actors (including the player)
1063 // 3. Items
1064 // 4. Clouds
1065 // 5. Terrain properties
1066 // 6. Terrain colours
1067 // 7. Vault (map) mask
1068 // 8. Vault id mask
1069 // 9. Map markers, dungeon listeners, shopping list
1070 //10. Player's knowledge
dgn_move_entities_at(coord_def src,coord_def dst,bool move_player,bool move_monster,bool move_items)1071 void dgn_move_entities_at(coord_def src, coord_def dst,
1072                           bool move_player,
1073                           bool move_monster,
1074                           bool move_items)
1075 {
1076     if (!in_bounds(dst) || !in_bounds(src) || src == dst)
1077         return;
1078 
1079     move_notable_thing(src, dst);
1080 
1081     dungeon_feature_type dfeat = env.grid(src);
1082     if (dfeat == DNGN_ENTER_SHOP)
1083     {
1084         ASSERT(shop_at(src));
1085         env.shop[dst] = env.shop[src];
1086         env.shop[dst].pos = dst;
1087         env.shop.erase(src);
1088         env.grid(src) = DNGN_FLOOR;
1089     }
1090     else if (feat_is_trap(dfeat))
1091     {
1092         ASSERT(trap_at(src));
1093         env.trap[dst] = env.trap[src];
1094         env.trap[dst].pos = dst;
1095         env.trap.erase(src);
1096         env.grid(src) = DNGN_FLOOR;
1097     }
1098 
1099     env.grid(dst) = dfeat;
1100 
1101     if (move_monster || move_player)
1102         ASSERT(!actor_at(dst));
1103 
1104     if (move_monster)
1105     {
1106         if (monster* mon = monster_at(src))
1107         {
1108             mon->moveto(dst);
1109             if (mon->type == MONS_ELDRITCH_TENTACLE)
1110             {
1111                 if (mon->props.exists("base_position"))
1112                 {
1113                     coord_def delta = dst - src;
1114                     coord_def base_pos = mon->props["base_position"].get_coord();
1115                     base_pos += delta;
1116                     mon->props["base_position"].get_coord() = base_pos;
1117                 }
1118 
1119             }
1120             env.mgrid(dst) = env.mgrid(src);
1121             env.mgrid(src) = NON_MONSTER;
1122         }
1123     }
1124 
1125     if (move_player && you.pos() == src)
1126         you.shiftto(dst);
1127 
1128     if (move_items)
1129         move_item_stack_to_grid(src, dst);
1130 
1131     if (cell_is_solid(dst))
1132     {
1133         delete_cloud(src);
1134         delete_cloud(dst); // in case there was already a clear there
1135     }
1136     else
1137         move_cloud(src, dst);
1138 
1139     // Move terrain colours and properties.
1140     env.pgrid(dst) = env.pgrid(src);
1141     env.grid_colours(dst) = env.grid_colours(src);
1142 #ifdef USE_TILE
1143     tile_env.bk_fg(dst) = tile_env.bk_fg(src);
1144     tile_env.bk_bg(dst) = tile_env.bk_bg(src);
1145     tile_env.bk_cloud(dst) = tile_env.bk_cloud(src);
1146 #endif
1147     tile_env.flv(dst) = tile_env.flv(src);
1148 
1149     // Move vault masks.
1150     env.level_map_mask(dst) = env.level_map_mask(src);
1151     env.level_map_ids(dst) = env.level_map_ids(src);
1152 
1153     // Move markers, dungeon listeners and shopping list.
1154     env.markers.move(src, dst);
1155     dungeon_events.move_listeners(src, dst);
1156     shopping_list.move_things(src, dst);
1157 
1158     // Move player's knowledge.
1159     env.map_knowledge(dst) = env.map_knowledge(src);
1160     env.map_seen.set(dst, env.map_seen(src));
1161     StashTrack.move_stash(src, dst);
1162 }
1163 
_dgn_shift_feature(const coord_def & pos)1164 static bool _dgn_shift_feature(const coord_def &pos)
1165 {
1166     const dungeon_feature_type dfeat = env.grid(pos);
1167     if (!feat_is_critical(dfeat) && !env.markers.find(pos, MAT_ANY))
1168         return false;
1169 
1170     const coord_def dest =
1171         _dgn_find_nearest_square(pos, _is_feature_shift_target);
1172 
1173     dgn_move_entities_at(pos, dest, false, false, false);
1174     return true;
1175 }
1176 
_dgn_check_terrain_items(const coord_def & pos,bool preserve_items)1177 static void _dgn_check_terrain_items(const coord_def &pos, bool preserve_items)
1178 {
1179     const dungeon_feature_type feat = env.grid(pos);
1180 
1181     int item = env.igrid(pos);
1182     while (item != NON_ITEM)
1183     {
1184         const int curr = item;
1185         item = env.item[item].link;
1186 
1187         if (!feat_is_solid(feat) && !feat_destroys_items(feat))
1188             continue;
1189 
1190         // Game-critical item.
1191         if (preserve_items || env.item[curr].is_critical())
1192             _dgn_shift_item(pos, env.item[curr]);
1193         else
1194         {
1195             feat_splash_noise(feat);
1196             item_was_destroyed(env.item[curr]);
1197             destroy_item(curr);
1198         }
1199     }
1200 }
1201 
_dgn_check_terrain_monsters(const coord_def & pos)1202 static void _dgn_check_terrain_monsters(const coord_def &pos)
1203 {
1204     if (monster* m = monster_at(pos))
1205         m->apply_location_effects(pos);
1206 }
1207 
1208 // Clear blood or off of terrain that shouldn't have it. Also clear of blood if
1209 // a bloody wall has been dug out and replaced by a floor, or if a bloody floor
1210 // has been replaced by a wall.
_dgn_check_terrain_covering(const coord_def & pos,dungeon_feature_type old_feat,dungeon_feature_type new_feat)1211 static void _dgn_check_terrain_covering(const coord_def &pos,
1212                                      dungeon_feature_type old_feat,
1213                                      dungeon_feature_type new_feat)
1214 {
1215     if (!testbits(env.pgrid(pos), FPROP_BLOODY))
1216         return;
1217 
1218     if (new_feat == DNGN_UNSEEN)
1219     {
1220         // Caller has already changed the grid, and old_feat is actually
1221         // the new feat.
1222         if (old_feat != DNGN_FLOOR && !feat_is_solid(old_feat))
1223             env.pgrid(pos) &= ~(FPROP_BLOODY);
1224     }
1225     else
1226     {
1227         if (feat_is_solid(old_feat) != feat_is_solid(new_feat)
1228             || feat_is_water(new_feat) || new_feat == DNGN_LAVA
1229             || feat_is_critical(new_feat))
1230         {
1231             env.pgrid(pos) &= ~(FPROP_BLOODY);
1232         }
1233     }
1234 }
1235 
_dgn_check_terrain_player(const coord_def pos)1236 static void _dgn_check_terrain_player(const coord_def pos)
1237 {
1238     if (crawl_state.generating_level || !crawl_state.need_save)
1239         return; // don't reference player if they don't currently exist
1240 
1241     if (pos != you.pos())
1242         return;
1243 
1244     if (you.can_pass_through(pos))
1245         move_player_to_grid(pos, false);
1246     else
1247         you_teleport_now();
1248 }
1249 
1250 /**
1251  * Change a given feature to a new type, cleaning up associated issues
1252  * (monsters/items in walls, blood on water, etc) in the process.
1253  *
1254  * @param pos               The location to be changed.
1255  * @param nfeat             The feature to be changed to.
1256  * @param preserve_features Whether to shunt the old feature to a nearby loc.
1257  * @param preserve_items    Whether to shunt items to a nearby loc, if they
1258  *                          can't stay in this one.
1259  * @param temporary         Whether the terrain change is only temporary & so
1260  *                          shouldn't affect branch/travel knowledge.
1261  * @param wizmode           Whether this is a wizmode terrain change,
1262  *                          & shouldn't check whether the player can actually
1263  *                          exist in the new feature.
1264  */
dungeon_terrain_changed(const coord_def & pos,dungeon_feature_type nfeat,bool preserve_features,bool preserve_items,bool temporary,bool wizmode)1265 void dungeon_terrain_changed(const coord_def &pos,
1266                              dungeon_feature_type nfeat,
1267                              bool preserve_features,
1268                              bool preserve_items,
1269                              bool temporary,
1270                              bool wizmode)
1271 {
1272     if (env.grid(pos) == nfeat)
1273         return;
1274     if (feat_is_wall(nfeat) && monster_at(pos))
1275         return;
1276     if (feat_is_trap(nfeat) && env.trap.find(pos) == env.trap.end())
1277     {
1278         // TODO: create a trap_def in env for this case?
1279         mprf(MSGCH_ERROR,
1280             "Attempting to change terrain to a trap without a corresponding"
1281             " trap_def!");
1282         nfeat = DNGN_FLOOR;
1283     }
1284 
1285 
1286     _dgn_check_terrain_covering(pos, env.grid(pos), nfeat);
1287 
1288     if (nfeat != DNGN_UNSEEN)
1289     {
1290         if (preserve_features)
1291             _dgn_shift_feature(pos);
1292 
1293         if (!temporary)
1294             unnotice_feature(level_pos(level_id::current(), pos));
1295 
1296         env.grid(pos) = nfeat;
1297         // Reset feature tile
1298         tile_env.flv(pos).feat = 0;
1299         tile_env.flv(pos).feat_idx = 0;
1300 
1301         if (is_notable_terrain(nfeat) && you.see_cell(pos))
1302             seen_notable_thing(nfeat, pos);
1303 
1304         // Don't destroy a trap which was just placed.
1305         if (!feat_is_trap(nfeat))
1306             destroy_trap(pos);
1307     }
1308 
1309     _dgn_check_terrain_items(pos, preserve_items);
1310     _dgn_check_terrain_monsters(pos);
1311     if (!wizmode)
1312         _dgn_check_terrain_player(pos);
1313     if (!temporary && feature_mimic_at(pos))
1314         env.level_map_mask(pos) &= ~MMT_MIMIC;
1315 
1316     set_terrain_changed(pos);
1317 
1318     // Deal with doors being created by changing features.
1319     tile_init_flavour(pos);
1320 }
1321 
_announce_swap_real(coord_def orig_pos,coord_def dest_pos)1322 static void _announce_swap_real(coord_def orig_pos, coord_def dest_pos)
1323 {
1324     const dungeon_feature_type orig_feat = env.grid(dest_pos);
1325 
1326     const string orig_name =
1327         feature_description_at(dest_pos, false,
1328                             you.see_cell(orig_pos) ? DESC_THE : DESC_A);
1329 
1330     string prep = feat_preposition(orig_feat, false);
1331 
1332     string orig_actor, dest_actor;
1333     if (orig_pos == you.pos())
1334         orig_actor = "you";
1335     else if (const monster* m = monster_at(orig_pos))
1336     {
1337         if (you.can_see(*m))
1338             orig_actor = m->name(DESC_THE);
1339     }
1340 
1341     if (dest_pos == you.pos())
1342         dest_actor = "you";
1343     else if (const monster* m = monster_at(dest_pos))
1344     {
1345         if (you.can_see(*m))
1346             dest_actor = m->name(DESC_THE);
1347     }
1348 
1349     ostringstream str;
1350     str << orig_name << " ";
1351     if (you.see_cell(orig_pos) && !you.see_cell(dest_pos))
1352     {
1353         str << "suddenly disappears";
1354         if (!orig_actor.empty())
1355             str << " from " << prep << " " << orig_actor;
1356     }
1357     else if (!you.see_cell(orig_pos) && you.see_cell(dest_pos))
1358     {
1359         str << "suddenly appears";
1360         if (!dest_actor.empty())
1361             str << " " << prep << " " << dest_actor;
1362     }
1363     else
1364     {
1365         str << "moves";
1366         if (!orig_actor.empty())
1367             str << " from " << prep << " " << orig_actor;
1368         if (!dest_actor.empty())
1369             str << " to " << prep << " " << dest_actor;
1370     }
1371     str << "!";
1372     mpr(str.str());
1373 }
1374 
_announce_swap(coord_def pos1,coord_def pos2)1375 static void _announce_swap(coord_def pos1, coord_def pos2)
1376 {
1377     if (!you.see_cell(pos1) && !you.see_cell(pos2))
1378         return;
1379 
1380     const dungeon_feature_type feat1 = env.grid(pos1);
1381     const dungeon_feature_type feat2 = env.grid(pos2);
1382 
1383     if (feat1 == feat2)
1384         return;
1385 
1386     const bool notable_seen1 = is_notable_terrain(feat1) && you.see_cell(pos1);
1387     const bool notable_seen2 = is_notable_terrain(feat2) && you.see_cell(pos2);
1388 
1389     if (notable_seen1 && notable_seen2)
1390     {
1391         _announce_swap_real(pos1, pos2);
1392         _announce_swap_real(pos2, pos1);
1393     }
1394     else if (notable_seen1)
1395         _announce_swap_real(pos2, pos1);
1396     else if (notable_seen2)
1397         _announce_swap_real(pos1, pos2);
1398     else if (you.see_cell(pos2))
1399         _announce_swap_real(pos1, pos2);
1400     else
1401         _announce_swap_real(pos2, pos1);
1402 }
1403 
swap_features(const coord_def & pos1,const coord_def & pos2,bool swap_everything,bool announce)1404 bool swap_features(const coord_def &pos1, const coord_def &pos2,
1405                    bool swap_everything, bool announce)
1406 {
1407     ASSERT_IN_BOUNDS(pos1);
1408     ASSERT_IN_BOUNDS(pos2);
1409     ASSERT(pos1 != pos2);
1410 
1411     if (is_sanctuary(pos1) || is_sanctuary(pos2))
1412         return false;
1413 
1414     const dungeon_feature_type feat1 = env.grid(pos1);
1415     const dungeon_feature_type feat2 = env.grid(pos2);
1416 
1417     if (is_notable_terrain(feat1) && !you.see_cell(pos1)
1418         && env.map_knowledge(pos1).known())
1419     {
1420         return false;
1421     }
1422 
1423     if (is_notable_terrain(feat2) && !you.see_cell(pos2)
1424         && env.map_knowledge(pos2).known())
1425     {
1426         return false;
1427     }
1428 
1429     const unsigned short col1 = env.grid_colours(pos1);
1430     const unsigned short col2 = env.grid_colours(pos2);
1431 
1432     const terrain_property_t prop1 = env.pgrid(pos1);
1433     const terrain_property_t prop2 = env.pgrid(pos2);
1434 
1435     trap_def* trap1 = trap_at(pos1);
1436     trap_def* trap2 = trap_at(pos2);
1437 
1438     shop_struct* shop1 = shop_at(pos1);
1439     shop_struct* shop2 = shop_at(pos2);
1440 
1441     // Find a temporary holding place for pos1 stuff to be moved to
1442     // before pos2 is moved to pos1.
1443     coord_def temp(-1, -1);
1444     for (int x = X_BOUND_1 + 1; x < X_BOUND_2; x++)
1445     {
1446         for (int y = Y_BOUND_1 + 1; y < Y_BOUND_2; y++)
1447         {
1448             coord_def pos(x, y);
1449             if (pos == pos1 || pos == pos2)
1450                 continue;
1451 
1452             if (!env.markers.find(pos, MAT_ANY)
1453                 && !is_notable_terrain(env.grid(pos))
1454                 && !cloud_at(pos))
1455             {
1456                 temp = pos;
1457                 break;
1458             }
1459         }
1460         if (in_bounds(temp))
1461             break;
1462     }
1463 
1464     if (!in_bounds(temp))
1465     {
1466         mprf(MSGCH_ERROR, "swap_features(): No boring squares on level?");
1467         return false;
1468     }
1469 
1470     // OK, now we guarantee the move.
1471 
1472     (void) move_notable_thing(pos1, temp);
1473     env.markers.move(pos1, temp);
1474     dungeon_events.move_listeners(pos1, temp);
1475     env.grid(pos1) = DNGN_UNSEEN;
1476     env.pgrid(pos1) = terrain_property_t{};
1477 
1478     (void) move_notable_thing(pos2, pos1);
1479     env.markers.move(pos2, pos1);
1480     dungeon_events.move_listeners(pos2, pos1);
1481     env.pgrid(pos1) = prop2;
1482     env.pgrid(pos2) = prop1;
1483 
1484     (void) move_notable_thing(temp, pos2);
1485     env.markers.move(temp, pos2);
1486     dungeon_events.move_listeners(temp, pos2);
1487 
1488     // Swap features and colours.
1489     env.grid(pos2) = feat1;
1490     env.grid(pos1) = feat2;
1491 
1492     env.grid_colours(pos1) = col2;
1493     env.grid_colours(pos2) = col1;
1494 
1495     // Swap traps.
1496     if (trap1 && !trap2)
1497     {
1498         env.trap[pos2] = env.trap[pos1];
1499         env.trap[pos2].pos = pos2;
1500         env.trap.erase(pos1);
1501     }
1502     else if (!trap1 && trap2)
1503     {
1504         env.trap[pos1] = env.trap[pos2];
1505         env.trap[pos1].pos = pos1;
1506         env.trap.erase(pos2);
1507     }
1508     else if (trap1 && trap2)
1509     {
1510         trap_def tmp = env.trap[pos1];
1511         env.trap[pos1] = env.trap[pos2];
1512         env.trap[pos2] = tmp;
1513         env.trap[pos1].pos = pos1;
1514         env.trap[pos2].pos = pos2;
1515     }
1516 
1517     // Swap shops.
1518     if (shop1 && !shop2)
1519     {
1520         env.shop[pos2] = env.shop[pos1];
1521         env.shop[pos2].pos = pos2;
1522         env.shop.erase(pos1);
1523     }
1524     else if (!shop1 && shop2)
1525     {
1526         env.shop[pos1] = env.shop[pos2];
1527         env.shop[pos1].pos = pos1;
1528         env.shop.erase(pos2);
1529     }
1530     else if (shop1 && shop2)
1531     {
1532         shop_struct tmp = env.shop[pos1];
1533         env.shop[pos1] = env.shop[pos2];
1534         env.shop[pos2] = tmp;
1535         env.shop[pos1].pos = pos1;
1536         env.shop[pos2].pos = pos2;
1537     }
1538 
1539     if (!swap_everything)
1540     {
1541         _dgn_check_terrain_items(pos1, false);
1542         _dgn_check_terrain_monsters(pos1);
1543         _dgn_check_terrain_player(pos1);
1544         set_terrain_changed(pos1);
1545 
1546         _dgn_check_terrain_items(pos2, false);
1547         _dgn_check_terrain_monsters(pos2);
1548         _dgn_check_terrain_player(pos2);
1549         set_terrain_changed(pos2);
1550 
1551         if (announce)
1552             _announce_swap(pos1, pos2);
1553         return true;
1554     }
1555 
1556     // Swap items.
1557     for (stack_iterator si(pos1); si; ++si)
1558         si->pos = pos1;
1559 
1560     for (stack_iterator si(pos2); si; ++si)
1561         si->pos = pos2;
1562 
1563     // Swap monsters.
1564     // Note that trapping nets, etc., move together
1565     // with the monster/player, so don't clear them.
1566     const int m1 = env.mgrid(pos1);
1567     const int m2 = env.mgrid(pos2);
1568 
1569     env.mgrid(pos1) = m2;
1570     env.mgrid(pos2) = m1;
1571 
1572     if (monster_at(pos1))
1573     {
1574         env.mons[env.mgrid(pos1)].set_position(pos1);
1575         env.mons[env.mgrid(pos1)].clear_invalid_constrictions();
1576     }
1577     if (monster_at(pos2))
1578     {
1579         env.mons[env.mgrid(pos2)].set_position(pos2);
1580         env.mons[env.mgrid(pos2)].clear_invalid_constrictions();
1581     }
1582 
1583     swap_clouds(pos1, pos2);
1584 
1585     if (pos1 == you.pos())
1586     {
1587         you.set_position(pos2);
1588         you.clear_invalid_constrictions();
1589         viewwindow();
1590         update_screen();
1591     }
1592     else if (pos2 == you.pos())
1593     {
1594         you.set_position(pos1);
1595         you.clear_invalid_constrictions();
1596         viewwindow();
1597         update_screen();
1598     }
1599 
1600     set_terrain_changed(pos1);
1601     set_terrain_changed(pos2);
1602 
1603     if (announce)
1604         _announce_swap(pos1, pos2);
1605 
1606     return true;
1607 }
1608 
_ok_dest_cell(const actor * orig_actor,const dungeon_feature_type orig_feat,const coord_def dest_pos)1609 static bool _ok_dest_cell(const actor* orig_actor,
1610                           const dungeon_feature_type orig_feat,
1611                           const coord_def dest_pos)
1612 {
1613     const dungeon_feature_type dest_feat = env.grid(dest_pos);
1614 
1615     if (orig_feat == dest_feat)
1616         return false;
1617 
1618     if (is_notable_terrain(dest_feat))
1619         return false;
1620 
1621     if (trap_at(dest_pos))
1622         return false;
1623 
1624     actor* dest_actor = actor_at(dest_pos);
1625 
1626     if (orig_actor && !orig_actor->is_habitable_feat(dest_feat))
1627         return false;
1628     if (dest_actor && !dest_actor->is_habitable_feat(orig_feat))
1629         return false;
1630 
1631     return true;
1632 }
1633 
slide_feature_over(const coord_def & src,coord_def preferred_dest,bool announce)1634 bool slide_feature_over(const coord_def &src, coord_def preferred_dest,
1635                         bool announce)
1636 {
1637     ASSERT_IN_BOUNDS(src);
1638 
1639     const dungeon_feature_type orig_feat = env.grid(src);
1640     const actor* orig_actor = actor_at(src);
1641 
1642     if (in_bounds(preferred_dest)
1643         && _ok_dest_cell(orig_actor, orig_feat, preferred_dest))
1644     {
1645         ASSERT(preferred_dest != src);
1646     }
1647     else
1648     {
1649         int squares = 0;
1650         for (adjacent_iterator ai(src); ai; ++ai)
1651         {
1652             if (_ok_dest_cell(orig_actor, orig_feat, *ai)
1653                 && one_chance_in(++squares))
1654             {
1655                 preferred_dest = *ai;
1656             }
1657         }
1658     }
1659 
1660     if (!in_bounds(preferred_dest))
1661         return false;
1662 
1663     ASSERT(preferred_dest != src);
1664     return swap_features(src, preferred_dest, false, announce);
1665 }
1666 
1667 /**
1668  * Apply harmful environmental effects from the current tile terrain to the
1669  * player.
1670  *
1671  * @param entry     The terrain type in question.
1672  */
fall_into_a_pool(dungeon_feature_type terrain)1673 void fall_into_a_pool(dungeon_feature_type terrain)
1674 {
1675     if (terrain == DNGN_DEEP_WATER)
1676     {
1677         if (you.can_water_walk() || form_likes_water())
1678             return;
1679 
1680         if (species::likes_water(you.species) && !you.transform_uncancellable)
1681         {
1682             emergency_untransform();
1683             return;
1684         }
1685     }
1686 
1687     mprf("You fall into the %s!",
1688          (terrain == DNGN_LAVA)       ? "lava" :
1689          (terrain == DNGN_DEEP_WATER) ? "water"
1690                                       : "programming rift");
1691     // included in default force_more_message
1692     enable_emergency_flight();
1693 }
1694 
1695 typedef map<string, dungeon_feature_type> feat_desc_map;
1696 static feat_desc_map feat_desc_cache;
1697 
init_feat_desc_cache()1698 void init_feat_desc_cache()
1699 {
1700     for (int i = 0; i < NUM_FEATURES; i++)
1701     {
1702         dungeon_feature_type feat = static_cast<dungeon_feature_type>(i);
1703         string               desc = feature_description(feat);
1704 
1705         lowercase(desc);
1706         if (!feat_desc_cache.count(desc))
1707             feat_desc_cache[desc] = feat;
1708     }
1709 }
1710 
feat_by_desc(string desc)1711 dungeon_feature_type feat_by_desc(string desc)
1712 {
1713     lowercase(desc);
1714 
1715 #if TAG_MAJOR_VERSION == 34
1716     // hard-coded because all the dry fountain variants match this description,
1717     // and they have a lower enum value, so the first is incorrectly returned
1718     if (desc == "a dry fountain")
1719         return DNGN_DRY_FOUNTAIN;
1720 #endif
1721 
1722     return lookup(feat_desc_cache, desc, DNGN_UNSEEN);
1723 }
1724 
1725 // If active is true, the player is just stepping onto the feature, with the
1726 // message: "<feature> slides away as you move <prep> it!"
1727 // Else, the actor is already on the feature:
1728 // "<feature> moves from <prep origin> to <prep destination>!"
feat_preposition(dungeon_feature_type feat,bool active,const actor * who)1729 string feat_preposition(dungeon_feature_type feat, bool active, const actor* who)
1730 {
1731     const bool         airborne = !who || who->airborne();
1732     const command_type dir      = feat_stair_direction(feat);
1733 
1734     if (dir == CMD_NO_CMD)
1735     {
1736         if (feat == DNGN_STONE_ARCH)
1737             return "beside";
1738         else if (feat_is_solid(feat)) // Passwall?
1739         {
1740             if (active)
1741                 return "inside";
1742             else
1743                 return "around";
1744         }
1745         else if (!airborne)
1746         {
1747             if (feat == DNGN_LAVA || feat_is_water(feat))
1748             {
1749                 if (active)
1750                     return "into";
1751                 else
1752                     return "around";
1753             }
1754             else
1755             {
1756                 if (active)
1757                     return "onto";
1758                 else
1759                     return "under";
1760             }
1761         }
1762     }
1763 
1764     if (dir == CMD_GO_UPSTAIRS && feat_is_escape_hatch(feat))
1765     {
1766         if (active)
1767             return "under";
1768         else
1769             return "above";
1770     }
1771 
1772     if (airborne)
1773     {
1774         if (active)
1775             return "over";
1776         else
1777             return "beneath";
1778     }
1779 
1780     if (dir == CMD_GO_DOWNSTAIRS
1781         && (feat_is_staircase(feat) || feat_is_escape_hatch(feat)))
1782     {
1783         if (active)
1784             return "onto";
1785         else
1786             return "beneath";
1787     }
1788     else
1789         return "beside";
1790 }
1791 
stair_climb_verb(dungeon_feature_type feat)1792 string stair_climb_verb(dungeon_feature_type feat)
1793 {
1794     ASSERT(feat_stair_direction(feat) != CMD_NO_CMD);
1795 
1796     if (feat_is_staircase(feat))
1797         return "climb";
1798     else if (feat_is_escape_hatch(feat))
1799         return "use";
1800     else
1801         return "pass through";
1802 }
1803 
1804 /** Find the feature with this name.
1805  *
1806  *  @param name The name (not the user-visible one) to be matched.
1807  *  @returns DNGN_UNSEEN if name is "", DNGN_FLOOR if the name is for a
1808  *           dead/forbidden god, and the first entry in the enum with a
1809  *           matching name otherwise.
1810  */
dungeon_feature_by_name(const string & name)1811 dungeon_feature_type dungeon_feature_by_name(const string &name)
1812 {
1813     if (name.empty())
1814         return DNGN_UNSEEN;
1815 
1816     for (unsigned i = 0; i < NUM_FEATURES; ++i)
1817     {
1818         dungeon_feature_type feat = static_cast<dungeon_feature_type>(i);
1819 
1820         if (!is_valid_feature_type(feat))
1821             continue;
1822 
1823         if (get_feature_def(feat).vaultname == name)
1824         {
1825 
1826             if (feat_is_altar(feat)
1827                 && is_unavailable_god(feat_altar_god(feat)))
1828             {
1829                 return DNGN_FLOOR;
1830             }
1831 
1832             return feat;
1833         }
1834     }
1835 
1836     return DNGN_UNSEEN;
1837 }
1838 
1839 /** Find feature names that contain this name.
1840  *
1841  *  @param name The string to be matched.
1842  *  @returns a list of matching names.
1843  */
dungeon_feature_matches(const string & name)1844 vector<string> dungeon_feature_matches(const string &name)
1845 {
1846     vector<string> matches;
1847 
1848     if (name.empty())
1849         return matches;
1850 
1851     for (unsigned i = 0; i < NUM_FEATURES; ++i)
1852     {
1853         dungeon_feature_type feat = static_cast<dungeon_feature_type>(i);
1854 
1855         if (!is_valid_feature_type(feat))
1856             continue;
1857 
1858         const char *featname = get_feature_def(feat).vaultname;
1859         if (strstr(featname, name.c_str()))
1860             matches.emplace_back(featname);
1861     }
1862 
1863     return matches;
1864 }
1865 
1866 /** Get the lua/wizmode name for a feature.
1867  *
1868  *  @param rfeat The feature type to be found.
1869  *  @returns nullptr if rfeat is not defined, the vaultname of the corresponding
1870  *           feature_def otherwise.
1871  */
dungeon_feature_name(dungeon_feature_type rfeat)1872 const char *dungeon_feature_name(dungeon_feature_type rfeat)
1873 {
1874     if (!is_valid_feature_type(rfeat))
1875         return nullptr;
1876 
1877     return get_feature_def(rfeat).vaultname;
1878 }
1879 
destroy_wall(const coord_def & p)1880 void destroy_wall(const coord_def& p)
1881 {
1882     if (!in_bounds(p))
1883         return;
1884 
1885     // Blood does not transfer onto floor.
1886     if (is_bloodcovered(p))
1887         env.pgrid(p) &= ~(FPROP_BLOODY);
1888 
1889     _revert_terrain_to(p,
1890             env.grid(p) == DNGN_MANGROVE ? DNGN_SHALLOW_WATER : DNGN_FLOOR);
1891     env.level_map_mask(p) |= MMT_TURNED_TO_FLOOR;
1892 }
1893 
feat_type_name(dungeon_feature_type feat)1894 const char* feat_type_name(dungeon_feature_type feat)
1895 {
1896     if (feat_is_door(feat))
1897         return "door";
1898     if (feat_is_wall(feat))
1899         return "wall";
1900     if (feat == DNGN_GRATE)
1901         return "grate";
1902     if (feat_is_tree(feat))
1903         return "tree";
1904     if (feat_is_statuelike(feat))
1905         return "statue";
1906     if (feat_is_water(feat))
1907         return "water";
1908     if (feat_is_lava(feat))
1909         return "lava";
1910     if (feat_is_altar(feat))
1911         return "altar";
1912     if (feat_is_trap(feat))
1913         return "trap";
1914     if (feat_is_escape_hatch(feat))
1915         return "escape hatch";
1916     if (feat_is_portal(feat) || feat_is_gate(feat))
1917         return "portal";
1918     if (feat_is_travelable_stair(feat))
1919         return "staircase";
1920     if (feat == DNGN_ENTER_SHOP || feat == DNGN_ABANDONED_SHOP)
1921         return "shop";
1922     if (feat_is_fountain(feat))
1923         return "fountain";
1924     if (feat == DNGN_UNSEEN)
1925         return "unknown terrain";
1926     return "floor";
1927 }
1928 
set_terrain_changed(const coord_def p)1929 void set_terrain_changed(const coord_def p)
1930 {
1931     if (cell_is_solid(p))
1932         delete_cloud(p);
1933 
1934     if (env.grid(p) == DNGN_SLIMY_WALL)
1935         env.level_state |= LSTATE_SLIMY_WALL;
1936     else if (env.grid(p) == DNGN_OPEN_DOOR)
1937     {
1938         // Restore colour from door-change markers
1939         for (map_marker *marker : env.markers.get_markers_at(p))
1940         {
1941             if (marker->get_type() == MAT_TERRAIN_CHANGE)
1942             {
1943                 map_terrain_change_marker* tmarker =
1944                     dynamic_cast<map_terrain_change_marker*>(marker);
1945 
1946                 if (tmarker->change_type == TERRAIN_CHANGE_DOOR_SEAL
1947                     && tmarker->colour != BLACK)
1948                 {
1949                     // Restore the unsealed colour.
1950                     dgn_set_grid_colour_at(p, tmarker->colour);
1951                     break;
1952                 }
1953             }
1954         }
1955     }
1956 
1957     env.map_knowledge(p).flags |= MAP_CHANGED_FLAG;
1958 
1959     dungeon_events.fire_position_event(DET_FEAT_CHANGE, p);
1960 
1961     los_terrain_changed(p);
1962 }
1963 
1964 /**
1965  * Does this cell count for exploration piety?
1966  *
1967  * Don't count: endless map borders, deep water, lava, and cells explicitly
1968  * marked. (player_view_update_at in view.cc updates the flags)
1969  */
cell_triggers_conduct(const coord_def p)1970 bool cell_triggers_conduct(const coord_def p)
1971 {
1972     return !(feat_is_endless(env.grid(p))
1973              || env.grid(p) == DNGN_LAVA
1974              || env.grid(p) == DNGN_DEEP_WATER
1975              || env.pgrid(p) & FPROP_SEEN_OR_NOEXP);
1976 }
1977 
is_boring_terrain(dungeon_feature_type feat)1978 bool is_boring_terrain(dungeon_feature_type feat)
1979 {
1980     if (!is_notable_terrain(feat))
1981         return true;
1982 
1983     // Altars in the temple are boring, as are any you can never use.
1984     if (feat_is_altar(feat) && (player_in_branch(BRANCH_TEMPLE)
1985         || !player_can_join_god(feat_altar_god(feat), false)))
1986     {
1987         return true;
1988     }
1989 
1990     // Only note the first entrance to the Abyss/Pan/Hell
1991     // which is found.
1992     if ((feat == DNGN_ENTER_ABYSS || feat == DNGN_ENTER_PANDEMONIUM
1993          || feat == DNGN_ENTER_HELL)
1994          && overview_knows_num_portals(feat) > 1)
1995     {
1996         return true;
1997     }
1998 
1999     return false;
2000 }
2001 
orig_terrain(coord_def pos)2002 dungeon_feature_type orig_terrain(coord_def pos)
2003 {
2004     const map_marker *mark = env.markers.find(pos, MAT_TERRAIN_CHANGE);
2005     if (!mark)
2006         return env.grid(pos);
2007 
2008     const map_terrain_change_marker *terch
2009         = dynamic_cast<const map_terrain_change_marker *>(mark);
2010     ASSERTM(terch, "%s has incorrect class", mark->debug_describe().c_str());
2011 
2012     return terch->old_feature;
2013 }
2014 
temp_change_terrain(coord_def pos,dungeon_feature_type newfeat,int dur,terrain_change_type type,const monster * mon)2015 void temp_change_terrain(coord_def pos, dungeon_feature_type newfeat, int dur,
2016                          terrain_change_type type, const monster* mon)
2017 {
2018     dungeon_feature_type old_feat = env.grid(pos);
2019     for (map_marker *marker : env.markers.get_markers_at(pos))
2020     {
2021         if (marker->get_type() == MAT_TERRAIN_CHANGE)
2022         {
2023             map_terrain_change_marker* tmarker =
2024                     dynamic_cast<map_terrain_change_marker*>(marker);
2025 
2026             // If change type matches, just modify old one; no need to add new one
2027             if (tmarker->change_type == type)
2028             {
2029                 if (tmarker->new_feature == newfeat)
2030                 {
2031                     if (tmarker->duration < dur)
2032                     {
2033                         tmarker->duration = dur;
2034                         if (mon)
2035                             tmarker->mon_num = mon->mid;
2036                     }
2037                 }
2038                 else
2039                 {
2040                     tmarker->new_feature = newfeat;
2041                     tmarker->duration = dur;
2042                     if (mon)
2043                         tmarker->mon_num = mon->mid;
2044                 }
2045                 // ensure that terrain change happens. Sometimes a terrain
2046                 // change marker can get stuck; this allows re-doing such
2047                 // cases. Also probably needed by the else case above.
2048                 dungeon_terrain_changed(pos, newfeat, false, true, true);
2049                 return;
2050             }
2051             else
2052                 old_feat = tmarker->old_feature;
2053         }
2054     }
2055 
2056     // If we are trying to change terrain into what it already is, don't actually
2057     // add another marker (unless the current terrain is due to some OTHER marker)
2058     if (env.grid(pos) == newfeat && newfeat == old_feat)
2059         return;
2060 
2061     int col = env.grid_colours(pos);
2062     map_terrain_change_marker *marker =
2063         new map_terrain_change_marker(pos, old_feat, newfeat, dur, type,
2064                                       mon ? mon->mid : 0, col);
2065     env.markers.add(marker);
2066     env.markers.clear_need_activate();
2067     dungeon_terrain_changed(pos, newfeat, false, true, true);
2068 }
2069 
_revert_terrain_to(coord_def pos,dungeon_feature_type feat)2070 static bool _revert_terrain_to(coord_def pos, dungeon_feature_type feat)
2071 {
2072     dungeon_feature_type newfeat = feat;
2073     for (map_marker *marker : env.markers.get_markers_at(pos))
2074     {
2075         if (marker->get_type() == MAT_TERRAIN_CHANGE)
2076         {
2077             map_terrain_change_marker* tmarker =
2078                     dynamic_cast<map_terrain_change_marker*>(marker);
2079 
2080             // Don't revert sealed doors to normal doors if we're trying to
2081             // remove the door altogether
2082             // Same for destroyed trees
2083             if ((tmarker->change_type == TERRAIN_CHANGE_DOOR_SEAL
2084                 || tmarker->change_type == TERRAIN_CHANGE_FORESTED)
2085                 && newfeat == feat)
2086             {
2087                 env.markers.remove(tmarker);
2088             }
2089             else
2090             {
2091                 newfeat = tmarker->old_feature;
2092                 if (tmarker->new_feature == env.grid(pos))
2093                     env.markers.remove(tmarker);
2094             }
2095         }
2096     }
2097 
2098     if (env.grid(pos) == DNGN_RUNED_DOOR && newfeat != DNGN_RUNED_DOOR
2099         || env.grid(pos) == DNGN_RUNED_CLEAR_DOOR
2100            && newfeat != DNGN_RUNED_CLEAR_DOOR)
2101     {
2102         explored_tracked_feature(env.grid(pos));
2103     }
2104 
2105     env.grid(pos) = newfeat;
2106     set_terrain_changed(pos);
2107 
2108     tile_clear_flavour(pos);
2109     tile_init_flavour(pos);
2110 
2111     return true;
2112 }
2113 
revert_terrain_change(coord_def pos,terrain_change_type ctype)2114 bool revert_terrain_change(coord_def pos, terrain_change_type ctype)
2115 {
2116     dungeon_feature_type newfeat = DNGN_UNSEEN;
2117     int colour = BLACK;
2118 
2119     for (map_marker *marker : env.markers.get_markers_at(pos))
2120     {
2121         if (marker->get_type() == MAT_TERRAIN_CHANGE)
2122         {
2123             map_terrain_change_marker* tmarker =
2124                     dynamic_cast<map_terrain_change_marker*>(marker);
2125 
2126             if (tmarker->change_type == ctype)
2127             {
2128                 if (tmarker->colour != BLACK)
2129                     colour = tmarker->colour;
2130                 if (!newfeat)
2131                     newfeat = tmarker->old_feature;
2132                 env.markers.remove(tmarker);
2133             }
2134             else
2135             {
2136                 // If we had an old colour, give it to the other marker.
2137                 if (colour != BLACK)
2138                     tmarker->colour = colour;
2139                 colour = BLACK;
2140                 newfeat = tmarker->new_feature;
2141             }
2142         }
2143     }
2144 
2145     // Don't revert opened sealed doors.
2146     if (feat_is_door(newfeat) && env.grid(pos) == DNGN_OPEN_DOOR)
2147         newfeat = DNGN_UNSEEN;
2148 
2149     if (newfeat != DNGN_UNSEEN)
2150     {
2151         if (ctype == TERRAIN_CHANGE_BOG)
2152             env.map_knowledge(pos).set_feature(newfeat, colour);
2153         dungeon_terrain_changed(pos, newfeat, false, true);
2154         env.grid_colours(pos) = colour;
2155         return true;
2156     }
2157     else
2158         return false;
2159 }
2160 
is_temp_terrain(coord_def pos)2161 bool is_temp_terrain(coord_def pos)
2162 {
2163     for (map_marker *marker : env.markers.get_markers_at(pos))
2164         if (marker->get_type() == MAT_TERRAIN_CHANGE)
2165             return true;
2166 
2167     return false;
2168 }
2169 
plant_forbidden_at(const coord_def & p,bool connectivity_only)2170 bool plant_forbidden_at(const coord_def &p, bool connectivity_only)
2171 {
2172     // .... Prevent this arrangement by never placing a plant in a way that
2173     // #P##  locally disconnects two adjacent cells. We scan clockwise around
2174     // ##.#  p looking for maximal contiguous sequences of traversable cells.
2175     // #?##  If we find more than one (and they don't join up cyclically),
2176     //       reject the configuration so the plant doesn't disconnect floor.
2177     //
2178     // ...   We do reject many non-problematic cases, such as this one; dpeg
2179     // #P#   suggests doing a connectivity check in ruination after placing
2180     // ...   plants, and removing cut-point plants then.
2181 
2182     // First traversable index, last consecutive traversable index, and
2183     // the next traversable index after last+1.
2184     int first = -1, last = -1, next = -1;
2185     int passable = 0;
2186     for (int i = 0; i < 8; i++)
2187     {
2188         coord_def q = p + Compass[i];
2189 
2190         if (feat_is_traversable(env.grid(q), true))
2191         {
2192             ++passable;
2193             if (first < 0)
2194                 first = i;
2195             else if (last >= 0 && next < 0)
2196             {
2197                 // Found a maybe-disconnected traversable cell. This is only
2198                 // acceptable if it might connect up at the end.
2199                 if (first == 0)
2200                     next = i;
2201                 else
2202                     return true;
2203             }
2204         }
2205         else
2206         {
2207             if (first >= 0 && last < 0)
2208                 last = i - 1;
2209             else if (next >= 0)
2210                 return true;
2211         }
2212     }
2213 
2214     // ?#. Forbid this arrangement when the ? squares are walls.
2215     // #P#  If multiple plants conspire to do something similar, that's
2216     // ##?  fine: we just want to avoid the most common occurrences.
2217     //      This would be an info leak (that at least one ? is not a wall)
2218     //      were it not for the previous check.
2219 
2220     return passable <= 1 && !connectivity_only;
2221 }
2222 
2223 /*
2224  * Find an adjacent space to displace a stack of items or a creature.
2225  *
2226  * @param pos the starting position to displace from.
2227  * @param push_actor true if the goal is to move an actor, false if items
2228  * @param excluded any spots to rule out a priori. Used for e.g. imprison and
2229  *                       for multi-space doors.
2230  *
2231  * @return a (possibly empty) vector of positions where displacement is
2232  *                       possible. If `push_actor` is true but there is no
2233  *                       actor at the position, will return an empty list.
2234  */
get_push_spaces(const coord_def & pos,bool push_actor,const vector<coord_def> * excluded)2235 vector<coord_def> get_push_spaces(const coord_def& pos, bool push_actor,
2236                     const vector<coord_def>* excluded)
2237 {
2238     vector<coord_def> results;
2239     actor *act = nullptr;
2240     if (push_actor)
2241     {
2242         act = actor_at(pos);
2243         if (!act || act->is_stationary())
2244             return results;
2245     }
2246 
2247     dungeon_feature_type starting_feat = env.grid(pos);
2248     vector<coord_def> bad_spots; // used for items
2249 
2250     for (adjacent_iterator ai(pos); ai; ++ai)
2251     {
2252         dungeon_feature_type feat = env.grid(*ai);
2253 
2254         // Make sure the spot wasn't already vetoed. This is used e.g. for
2255         // imprison, to pre-exclude all the spots where a wall will be.
2256         if (excluded && find(begin(*excluded), end(*excluded), *ai)
2257                             != end(*excluded))
2258         {
2259             continue;
2260         }
2261 
2262         // can never push to a solid space
2263         if (feat_is_solid(feat))
2264             continue;
2265 
2266         // Extra checks if we're moving a monster instead of an item
2267         if (push_actor)
2268         {
2269             // these should get deep water and lava for cases where they matter
2270             if (actor_at(*ai)
2271                 || !act->can_pass_through(*ai)
2272                 || !act->is_habitable(*ai))
2273             {
2274                 continue;
2275             }
2276             results.push_back(*ai);
2277         }
2278         else
2279         {
2280             if (feat_has_solid_floor(feat))
2281                 results.push_back(*ai);
2282             else if (starting_feat == DNGN_DEEP_WATER
2283                 && feat == DNGN_DEEP_WATER)
2284             {
2285                 // Dispreferentially allow pushing items from deep water to
2286                 // deep water. Without this, zin imprison fails over deep
2287                 // water if there are items, even if the player can't see
2288                 // them.
2289                 bad_spots.push_back(*ai);
2290             }
2291             // otherwise, can't position an item on this spot
2292         }
2293     }
2294     if (!results.empty())
2295         return results;
2296     return bad_spots;
2297 }
2298 
has_push_spaces(const coord_def & pos,bool push_actor,const vector<coord_def> * excluded)2299 bool has_push_spaces(const coord_def& pos, bool push_actor,
2300                     const vector<coord_def>* excluded)
2301 {
2302     return !get_push_spaces(pos, push_actor, excluded).empty();
2303 }
2304 
2305 /**
2306  * Push items from `pos`, splashing them around whatever available spaces
2307  * there are.
2308  * @param pos the source position.
2309  * @param excluded positions that are a priori unavailable.
2310  *
2311  * @return true if any items moved, false otherwise. (Will return false if there
2312  *         were no items.)
2313  */
push_items_from(const coord_def & pos,const vector<coord_def> * excluded)2314 bool push_items_from(const coord_def& pos, const vector<coord_def>* excluded)
2315 {
2316     vector<coord_def> targets = get_push_spaces(pos, false, excluded);
2317     bool result = false;
2318     if (targets.empty())
2319         return false;
2320     // TODO: splashing is flavorful, but how annoying is it in practice?
2321     while (env.igrid(pos) != NON_ITEM)
2322         result |= move_top_item(pos, targets[random2(targets.size())]);
2323     return result;
2324 }
2325 
2326 /**
2327  * Push an actor from `pos` to some available space, if possible.
2328  *
2329  * @param pos the source position.
2330  * @param excluded excluded positions that are a priori unavailable.
2331  * @param random whether to chose the position randomly, or deterministically.
2332  *        (Useful for systematically moving a bunch of actors at once, when you
2333  *        need to worry about domino effects.)
2334  *
2335  * @return the new coordinates for the actor.
2336  */
push_actor_from(const coord_def & pos,const vector<coord_def> * excluded,bool random)2337 coord_def push_actor_from(const coord_def& pos,
2338                           const vector<coord_def>* excluded, bool random)
2339 {
2340     actor* act = actor_at(pos);
2341     if (!act)
2342         return coord_def(0,0);
2343     vector<coord_def> targets = get_push_spaces(pos, true, excluded);
2344     if (targets.empty())
2345         return coord_def(0,0);
2346     const coord_def newpos = random ? targets[random2(targets.size())]
2347                                     : targets.front();
2348     ASSERT(!newpos.origin());
2349     act->move_to_pos(newpos);
2350     // The new position of the monster is now an additional veto spot for
2351     // monsters.
2352     return newpos;
2353 }
2354 
2355 /** Close any door at the given position. Handles the grid change, but does not
2356  * mark terrain or do any event handling.
2357  *
2358  * @param dest The location of the door.
2359  */
dgn_close_door(const coord_def & dest)2360 void dgn_close_door(const coord_def &dest)
2361 {
2362     if (!feat_is_open_door(env.grid(dest)))
2363         return;
2364 
2365     if (env.grid(dest) == DNGN_OPEN_CLEAR_DOOR)
2366         env.grid(dest) = DNGN_CLOSED_CLEAR_DOOR;
2367     else
2368         env.grid(dest) = DNGN_CLOSED_DOOR;
2369 }
2370 
2371 /** Open any door at the given position. Handles the grid change, but does not
2372  * mark terrain or do any event handling.
2373  *
2374  * @param dest The location of the door.
2375  */
dgn_open_door(const coord_def & dest)2376 void dgn_open_door(const coord_def &dest)
2377 {
2378     if (!feat_is_closed_door(env.grid(dest)))
2379         return;
2380 
2381     if (env.grid(dest) == DNGN_CLOSED_CLEAR_DOOR
2382         || env.grid(dest) == DNGN_RUNED_CLEAR_DOOR)
2383     {
2384         env.grid(dest) = DNGN_OPEN_CLEAR_DOOR;
2385     }
2386     else
2387         env.grid(dest) = DNGN_OPEN_DOOR;
2388 }
2389 
ice_wall_damage(monster & mons,int delay)2390 void ice_wall_damage(monster &mons, int delay)
2391 {
2392     if (!you.duration[DUR_FROZEN_RAMPARTS]
2393         || !you.see_cell_no_trans(mons.pos())
2394         || mons_aligned(&you, &mons))
2395     {
2396         return;
2397     }
2398 
2399     const int walls = count_adjacent_icy_walls(mons.pos());
2400     if (!walls)
2401         return;
2402 
2403     const int pow = calc_spell_power(SPELL_FROZEN_RAMPARTS, true);
2404     const int undelayed_dam = ramparts_damage(pow).roll();
2405     const int orig_dam = div_rand_round(delay * undelayed_dam, BASELINE_DELAY);
2406 
2407     bolt beam;
2408     beam.flavour = BEAM_COLD;
2409     beam.thrower = KILL_YOU;
2410     int dam = mons_adjust_flavoured(&mons, beam, orig_dam);
2411     mprf("The wall freezes %s%s%s",
2412          you.can_see(mons) ? mons.name(DESC_THE).c_str() : "something",
2413          dam ? "" : " but does no damage",
2414          attack_strength_punctuation(dam).c_str());
2415 
2416     if (dam > 0)
2417     {
2418         mons.hurt(&you, dam, BEAM_COLD);
2419 
2420         if (mons.alive())
2421         {
2422             behaviour_event(&mons, ME_WHACK, &you);
2423             mons.expose_to_element(BEAM_COLD, orig_dam);
2424         }
2425     }
2426 }
2427