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