1 /**
2  * @file
3  * @brief Dungeon related wizard functions.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "wiz-dgn.h"
9 
10 #include "act-iter.h"
11 #include "branch.h"
12 #include "coordit.h"
13 #include "dactions.h"
14 #include "delay.h"
15 #include "describe.h"
16 #include "dgn-overview.h"
17 #include "dungeon.h"
18 #include "tile-env.h"
19 #include "files.h"
20 #include "libutil.h"
21 #include "maps.h"
22 #include "message.h"
23 #include "place.h"
24 #include "prompt.h"
25 #include "religion.h"
26 #include "spl-goditem.h" // detect_items
27 #include "stairs.h"
28 #include "state.h"
29 #include "stringutil.h"
30 #include "tag-version.h"
31 #include "terrain.h"
32 #include "tileview.h"
33 #include "tiles-build-specific.h"
34 #include "traps.h"
35 #include "view.h"
36 #include "wiz-mon.h"
37 
38 #ifdef WIZARD
_find_appropriate_stairs(bool down)39 static dungeon_feature_type _find_appropriate_stairs(bool down)
40 {
41     if (player_in_branch(BRANCH_PANDEMONIUM))
42     {
43         if (down)
44             return DNGN_TRANSIT_PANDEMONIUM;
45         else
46             return DNGN_EXIT_PANDEMONIUM;
47     }
48 
49     int depth = you.depth;
50     if (down)
51         depth++;
52     else
53         depth--;
54 
55     // Can't go down from bottom level of a branch.
56     if (depth > brdepth[you.where_are_you])
57     {
58         mpr("Can't go down from the bottom of a branch.");
59         return DNGN_UNSEEN;
60     }
61     // Going up from top level of branch
62     else if (depth == 0)
63         return your_branch().exit_stairs;
64     // Branch non-edge cases
65     else if (depth >= 1)
66     {
67         if (down)
68             return DNGN_STONE_STAIRS_DOWN_I;
69         else
70             return DNGN_ESCAPE_HATCH_UP;
71     }
72     else
73     {
74         mpr("Bug in determining level exit.");
75         return DNGN_UNSEEN;
76     }
77 }
78 
wizard_place_stairs(bool down)79 void wizard_place_stairs(bool down)
80 {
81     dungeon_feature_type stairs = _find_appropriate_stairs(down);
82 
83     if (stairs == DNGN_UNSEEN)
84         return;
85 
86     mprf("Creating %sstairs.", down ? "down" : "up");
87     dungeon_terrain_changed(you.pos(), stairs);
88 }
89 
90 static level_id _wizard_level_target = level_id();
91 
wizard_level_travel(bool down)92 void wizard_level_travel(bool down)
93 {
94     dungeon_feature_type stairs = _find_appropriate_stairs(down);
95 
96     if (stairs == DNGN_UNSEEN)
97         return;
98 
99     // This lets us, for example, use &U to exit from Pandemonium and
100     // &D to go to the next level.
101     command_type real_dir = feat_stair_direction(stairs);
102     if (down && real_dir == CMD_GO_UPSTAIRS
103         || !down && real_dir == CMD_GO_DOWNSTAIRS)
104     {
105         down = !down;
106     }
107 
108     _wizard_level_target = stair_destination(stairs, "", false);
109 
110     if (down)
111         down_stairs(stairs, false, false);
112     else
113         up_stairs(stairs, false);
114 
115     _wizard_level_target = level_id();
116 }
117 
is_wizard_travel_target(const level_id l)118 bool is_wizard_travel_target(const level_id l)
119 {
120     return _wizard_level_target.is_valid() && l == _wizard_level_target;
121 }
122 
_wizard_go_to_level(const level_pos & pos)123 static void _wizard_go_to_level(const level_pos &pos)
124 {
125     dungeon_feature_type stair_taken =
126         absdungeon_depth(pos.id.branch, pos.id.depth) > env.absdepth0 ?
127         DNGN_STONE_STAIRS_DOWN_I : DNGN_STONE_STAIRS_UP_I;
128 
129     if (pos.id.depth == brdepth[pos.id.branch])
130         stair_taken = DNGN_STONE_STAIRS_DOWN_I;
131 
132     if (!player_in_branch(pos.id.branch) && pos.id.depth == 1
133         && pos.id.branch != BRANCH_DUNGEON)
134     {
135         stair_taken = branches[pos.id.branch].entry_stairs;
136     }
137 
138     if (is_connected_branch(pos.id.branch))
139         you.level_stack.clear();
140     else
141     {
142         for (int i = you.level_stack.size() - 1; i >= 0; i--)
143             if (you.level_stack[i].id == pos.id)
144                 you.level_stack.resize(i);
145         if (!player_in_branch(pos.id.branch))
146             you.level_stack.push_back(level_pos::current());
147     }
148 
149     const level_id old_level = level_id::current();
150 
151     you.where_are_you = static_cast<branch_type>(pos.id.branch);
152     you.depth         = pos.id.depth;
153     _wizard_level_target = pos.id;
154 
155     leaving_level_now(stair_taken);
156     const bool newlevel = load_level(stair_taken, LOAD_ENTER_LEVEL, old_level);
157     tile_new_level(newlevel);
158     if (!crawl_state.test)
159         save_game_state();
160     new_level();
161     seen_monsters_react();
162     viewwindow();
163     update_screen();
164 
165     // Tell stash-tracker and travel that we've changed levels.
166     trackers_init_new_level();
167     _wizard_level_target = level_id();
168 }
169 
wizard_interlevel_travel()170 void wizard_interlevel_travel()
171 {
172     string name;
173     const level_pos pos =
174         prompt_translevel_target(TPF_ALLOW_UPDOWN | TPF_SHOW_ALL_BRANCHES, name);
175 
176     if (pos.id.depth < 1 || pos.id.depth > brdepth[pos.id.branch])
177     {
178         canned_msg(MSG_OK);
179         return;
180     }
181 
182     _wizard_go_to_level(pos);
183 }
184 
wizard_create_feature(const coord_def & pos)185 bool wizard_create_feature(const coord_def& pos)
186 {
187     const bool mimic = (pos != you.pos());
188     char specs[256];
189     dungeon_feature_type feat;
190     if (mimic)
191         mprf(MSGCH_PROMPT, "Create what kind of feature mimic? ");
192     else
193         mprf(MSGCH_PROMPT, "Create which feature? ");
194 
195     if (cancellable_get_line_autohist(specs, sizeof(specs)) || specs[0] == 0)
196     {
197         canned_msg(MSG_OK);
198         return false;
199     }
200 
201     if (int feat_num = atoi(specs))
202         feat = static_cast<dungeon_feature_type>(feat_num);
203     else
204     {
205         string name = lowercase_string(specs);
206         name = replace_all(name, " ", "_");
207         feat = dungeon_feature_by_name(name);
208         if (feat == DNGN_UNSEEN) // no exact match
209         {
210             vector<string> matches = dungeon_feature_matches(name);
211 
212             if (matches.empty())
213             {
214                 const feature_property_type fprop(str_to_fprop(name));
215                 if (fprop != FPROP_NONE)
216                 {
217                     env.pgrid(you.pos()) |= fprop;
218                     mprf("Set fprops \"%s\" at (%d,%d)",
219                          name.c_str(), you.pos().x, you.pos().y);
220                 }
221                 else
222                 {
223                     mprf(MSGCH_DIAGNOSTICS, "No features matching '%s'",
224                          name.c_str());
225                 }
226                 return false;
227             }
228 
229             // Only one possible match, use that.
230             if (matches.size() == 1)
231             {
232                 name = matches[0];
233                 feat = dungeon_feature_by_name(name);
234             }
235             // Multiple matches, list them to wizard
236             else
237             {
238                 string prefix = "No exact match for feature '" +
239                                 name +  "', possible matches are: ";
240 
241                 // Use mpr_comma_separated_list() because the list
242                 // might be *LONG*.
243                 mpr_comma_separated_list(prefix, matches, " and ", ", ",
244                                          MSGCH_DIAGNOSTICS);
245                 return wizard_create_feature(pos);
246             }
247         }
248     }
249 
250     if (mimic && !feat_is_mimicable(feat, false)
251         && !yesno("This isn't a valid feature mimic. Create it anyway? ",
252                   true, 'n'))
253     {
254         canned_msg(MSG_OK);
255         return false;
256     }
257 
258     if (feat == DNGN_ENTER_SHOP)
259         return debug_make_shop(pos);
260 
261     if (feat_is_trap(feat))
262         return debug_make_trap(pos);
263 
264     tile_env.flv(pos).feat = 0;
265     tile_env.flv(pos).special = 0;
266     env.grid_colours(pos) = 0;
267     const dungeon_feature_type old_feat = env.grid(pos);
268     dungeon_terrain_changed(pos, feat, false, false, false, true);
269     // Update gate tiles, if existing.
270     if (feat_is_door(old_feat) || feat_is_door(feat))
271     {
272         const coord_def left  = pos - coord_def(1, 0);
273         const coord_def right = pos + coord_def(1, 0);
274         if (map_bounds(left) && feat_is_door(env.grid(left)))
275             tile_init_flavour(left);
276         if (map_bounds(right) && feat_is_door(env.grid(right)))
277             tile_init_flavour(right);
278     }
279     if (pos == you.pos() && cell_is_solid(pos))
280         you.wizmode_teleported_into_rock = true;
281 
282     if (mimic)
283         env.level_map_mask(pos) |= MMT_MIMIC;
284 
285     if (you.see_cell(pos))
286         view_update_at(pos);
287 
288     return true;
289 }
290 
wizard_list_branches()291 void wizard_list_branches()
292 {
293     for (branch_iterator it; it; ++it)
294     {
295         if (parent_branch(it->id) == NUM_BRANCHES)
296             continue;
297         else if (brentry[it->id].is_valid())
298         {
299             mprf(MSGCH_DIAGNOSTICS, "Branch %d (%s) is on %s",
300                  it->id, it->longname, brentry[it->id].describe().c_str());
301         }
302         else if (is_random_subbranch(it->id))
303         {
304             mprf(MSGCH_DIAGNOSTICS, "Branch %d (%s) was not generated "
305                  "this game", it->id, it->longname);
306         }
307     }
308 
309     if (!you.props.exists(OVERFLOW_TEMPLES_KEY))
310         return;
311 
312     mprf(MSGCH_DIAGNOSTICS, "----");
313     mprf(MSGCH_DIAGNOSTICS, "Overflow temples: ");
314 
315     CrawlVector &levels = you.props[OVERFLOW_TEMPLES_KEY].get_vector();
316 
317     vector<string> temple_strings;
318     vector<string> god_names;
319 
320     for (unsigned int i = 0; i < levels.size(); i++)
321     {
322         CrawlStoreValue &val = levels[i];
323 
324         // Does this level have an overflow temple?
325         if (val.get_flags() & SFLAG_UNSET)
326             continue;
327 
328         CrawlVector &temples = val.get_vector();
329 
330         if (temples.empty())
331             continue;
332 
333         temple_strings.clear();
334 
335         for (CrawlHashTable &temple_hash : temples)
336         {
337             god_names.clear();
338             CrawlVector    &gods        = temple_hash[TEMPLE_GODS_KEY];
339 
340             for (unsigned int k = 0; k < gods.size(); k++)
341             {
342                 god_type god = (god_type) gods[k].get_byte();
343 
344                 god_names.push_back(god_name(god));
345             }
346             temple_strings.push_back(
347                 comma_separated_line(god_names.begin(), god_names.end()));
348         }
349 
350         mprf(MSGCH_DIAGNOSTICS, "%u on D:%u (%s)", temples.size(),
351              i + 1,
352              comma_separated_line(temple_strings.begin(),
353                                   temple_strings.end(), "; ", "; ").c_str()
354           );
355     }
356 }
357 
wizard_map_level()358 void wizard_map_level()
359 {
360     if (!is_map_persistent())
361     {
362         if (!yesno("Force level to be mappable?", true, 'n'))
363         {
364             canned_msg(MSG_OK);
365             return;
366         }
367         env.properties[FORCE_MAPPABLE_KEY] = true;
368     }
369 
370     mpr("Mapping level.");
371     magic_mapping(1000, 100, true, true);
372 
373     for (rectangle_iterator ri(BOUNDARY_BORDER - 1); ri; ++ri)
374     {
375         update_item_at(*ri, true);
376         show_update_at(*ri, LAYER_ITEMS);
377 
378 #ifdef USE_TILE
379         tiles.update_minimap(*ri);
380         tile_draw_map_cell(*ri, true);
381 #endif
382 #ifdef USE_TILE_WEB
383         tiles.mark_for_redraw(*ri);
384 #endif
385     }
386 }
387 
debug_make_trap(const coord_def & pos)388 bool debug_make_trap(const coord_def& pos)
389 {
390     char requested_trap[80];
391     trap_type trap = TRAP_UNASSIGNED;
392     int gridch     = env.grid(pos);
393 
394     if (gridch != DNGN_FLOOR)
395     {
396         mpr("You need to be on a floor square to make a trap.");
397         return false;
398     }
399 
400     msgwin_get_line("What kind of trap? ",
401                     requested_trap, sizeof(requested_trap));
402     if (!*requested_trap)
403     {
404         canned_msg(MSG_OK);
405         return false;
406     }
407 
408     string spec = lowercase_string(requested_trap);
409     vector<trap_type> matches;
410     vector<string>    match_names;
411 
412     if (spec == "random" || spec == "any")
413         trap = TRAP_RANDOM;
414 
415     for (int t =
416 #if TAG_MAJOR_VERSION == 34
417             TRAP_DART
418 #else
419             TRAP_ARROW
420 #endif
421             ; t < NUM_TRAPS; ++t)
422     {
423         const trap_type tr = static_cast<trap_type>(t);
424         string tname       = lowercase_string(trap_name(tr));
425         if (spec.find(tname) != spec.npos)
426         {
427             trap = tr;
428             break;
429         }
430         else if (tname.find(spec) != tname.npos)
431         {
432             matches.push_back(tr);
433             match_names.push_back(tname);
434         }
435     }
436 
437     if (trap == TRAP_UNASSIGNED)
438     {
439         if (matches.empty())
440         {
441             mprf("I know no traps named \"%s\".", spec.c_str());
442             return false;
443         }
444         // Only one match, use that
445         else if (matches.size() == 1)
446             trap = matches[0];
447         else
448         {
449             string prefix = "No exact match for trap '";
450             prefix += spec;
451             prefix += "', possible matches are: ";
452             mpr_comma_separated_list(prefix, match_names);
453             return false;
454         }
455     }
456 
457     place_specific_trap(you.pos(), trap);
458     mprf("Created %s, marked it undiscovered.",
459          (trap == TRAP_RANDOM)
460             ? "a random trap"
461             : trap_at(you.pos())->name(DESC_A).c_str());
462 
463     if (trap == TRAP_SHAFT && !is_valid_shaft_level())
464         mpr("NOTE: Shaft traps aren't valid on this level.");
465 
466     return true;
467 }
468 
debug_make_shop(const coord_def & pos)469 bool debug_make_shop(const coord_def& pos)
470 {
471     if (env.grid(pos) != DNGN_FLOOR)
472     {
473         mpr("Insufficient floor-space for new Wal-Mart.");
474         return false;
475     }
476 
477     char requested_shop[80];
478     msgwin_get_line("What kind of shop? ",
479                     requested_shop, sizeof(requested_shop));
480     if (!*requested_shop)
481     {
482         canned_msg(MSG_OK);
483         return false;
484     }
485 
486     const shop_type new_shop_type = str_to_shoptype(requested_shop);
487 
488     if (new_shop_type == SHOP_UNASSIGNED)
489     {
490         mprf("Bad shop type: \"%s\"", requested_shop);
491         list_shop_types();
492         return false;
493     }
494 
495     place_spec_shop(pos, new_shop_type);
496     mpr("Done.");
497     return true;
498 }
499 
_free_all_vaults()500 static void _free_all_vaults()
501 {
502     for (rectangle_iterator ri(MAPGEN_BORDER); ri; ++ri)
503         env.level_map_ids(*ri) = INVALID_MAP_INDEX;
504 
505     for (auto &vp : env.level_vaults)
506         vp->seen = false;
507 
508     dgn_erase_unused_vault_placements();
509 }
510 
debug_load_map_by_name(string name,bool primary)511 static void debug_load_map_by_name(string name, bool primary)
512 {
513     if (primary)
514         _free_all_vaults();
515 
516     const bool place_on_us = !primary && strip_tag(name, "*", true);
517 
518     level_clear_vault_memory();
519     const map_def *toplace = find_map_by_name(name);
520     if (!toplace)
521     {
522         vector<string> matches = find_map_matches(name);
523 
524         if (matches.empty())
525         {
526             mprf("Can't find map named '%s'.", name.c_str());
527             return;
528         }
529         else if (matches.size() == 1)
530         {
531             string prompt = "Only match is '";
532             prompt += matches[0];
533             prompt += "', use that?";
534             if (!yesno(prompt.c_str(), true, 'y'))
535             {
536                 canned_msg(MSG_OK);
537                 return;
538             }
539 
540             toplace = find_map_by_name(matches[0]);
541         }
542         else
543         {
544             string prompt = "No exact matches for '";
545             prompt += name;
546             prompt += "', possible matches are: ";
547             mpr_comma_separated_list(prompt, matches);
548             return;
549         }
550     }
551 
552     coord_def where(-1, -1);
553     if (place_on_us)
554     {
555         if (toplace->orient == MAP_FLOAT || toplace->orient == MAP_NONE)
556         {
557             coord_def size = toplace->map.size();
558             coord_def tl   = you.pos() - (size / 2) - coord_def(-1, -1);
559             coord_def br   = you.pos() + (size / 2) + coord_def(-1, -1);
560 
561             for (rectangle_iterator ri(tl, br); ri; ++ri)
562             {
563                 if (!in_bounds(*ri))
564                 {
565                     mprf("Placing %s on top of you would put part of the "
566                          "map outside of the level, cancelling.",
567                          toplace->name.c_str());
568                     return;
569                 }
570             }
571             // We're okay.
572             where = you.pos();
573         }
574         else
575         {
576             mprf("%s decides where it goes, can't place where you are.",
577                  toplace->name.c_str());
578         }
579     }
580 
581     if (primary)
582     {
583         if (toplace->orient == MAP_ENCOMPASS
584             && !toplace->is_usable_in(level_id::current())
585             && !yesno("Warning: this is an encompass vault not designed "
586                        "for this location; placing it with &P may result in "
587                        "crashes and save corruption. Continue?", true, 'y'))
588         {
589             mprf("Ok; try placing with &L or go to the relevant location to "
590                  "safely place with &P.");
591             return;
592         }
593         if (toplace->is_minivault())
594             you.props["force_minivault"] = toplace->name;
595         else
596             you.props["force_map"] = toplace->name;
597         wizard_recreate_level();
598         you.props.erase("force_minivault");
599         you.props.erase("force_map");
600 
601         // We just saved with you.props["force_map"] set; save again in
602         // case we crash (lest we have the property forever).
603         if (!crawl_state.test)
604             save_game_state();
605     }
606     else
607     {
608         unwind_var<string_set> um(you.uniq_map_names, string_set());
609         unwind_var<string_set> umt(you.uniq_map_tags, string_set());
610         unwind_var<string_set> um_a(you.uniq_map_names_abyss, string_set());
611         unwind_var<string_set> umt_a(you.uniq_map_tags_abyss, string_set());
612         unwind_var<string_set> lum(env.level_uniq_maps, string_set());
613         unwind_var<string_set> lumt(env.level_uniq_map_tags, string_set());
614         if (dgn_place_map(toplace, false, false, where))
615         {
616             mprf("Successfully placed %s.", toplace->name.c_str());
617             // Fix up doors from vaults and any changes to the default walls
618             // and floors from the vault.
619             tile_init_flavour();
620             // Transporters would normally be made from map markers by the
621             // builder.
622             dgn_make_transporters_from_markers();
623         }
624         else
625         {
626             mprf("Failed to place %s; last builder error: %s",
627                 toplace->name.c_str(), crawl_state.last_builder_error.c_str());
628         }
629     }
630 }
631 
632 static input_history mini_hist(10), primary_hist(10);
633 
debug_place_map(bool primary)634 void debug_place_map(bool primary)
635 {
636     char what_to_make[100];
637     clear_messages();
638     mprf(MSGCH_PROMPT, primary ? "Enter map name: " :
639          "Enter map name (prefix it with * for local placement): ");
640     if (cancellable_get_line(what_to_make, sizeof what_to_make,
641                             primary ? &primary_hist : &mini_hist))
642     {
643         canned_msg(MSG_OK);
644         return;
645     }
646 
647     string what = what_to_make;
648     trim_string(what);
649     if (what.empty())
650     {
651         canned_msg(MSG_OK);
652         return;
653     }
654 
655     debug_load_map_by_name(what, primary);
656 }
657 
_debug_kill_traps()658 static void _debug_kill_traps()
659 {
660     for (rectangle_iterator ri(1); ri; ++ri)
661         if (feat_is_trap(env.grid(*ri)))
662             destroy_trap(*ri);
663 }
664 
_debug_time_explore()665 static int _debug_time_explore()
666 {
667     viewwindow();
668     update_screen();
669     start_explore(false);
670 
671     unwind_var<int> es(Options.explore_stop, 0);
672 
673     const int start = you.num_turns;
674     while (you_are_delayed())
675     {
676         you.turn_is_over = false;
677         handle_delay();
678         you.num_turns++;
679     }
680 
681     // Elapsed time might not match up if explore had to go through
682     // shallow water.
683     PlaceInfo& pi = you.get_place_info();
684     pi.elapsed_total = (pi.elapsed_explore + pi.elapsed_travel +
685                         pi.elapsed_interlevel + pi.elapsed_resting +
686                         pi.elapsed_other);
687 
688     PlaceInfo& pi2 = you.global_info;
689     pi2.elapsed_total = (pi2.elapsed_explore + pi2.elapsed_travel +
690                          pi2.elapsed_interlevel + pi2.elapsed_resting +
691                          pi2.elapsed_other);
692 
693     return you.num_turns - start;
694 }
695 
_debug_destroy_doors()696 static void _debug_destroy_doors()
697 {
698     for (int y = 0; y < GYM; ++y)
699         for (int x = 0; x < GXM; ++x)
700         {
701             const dungeon_feature_type feat = env.grid[x][y];
702             if (feat_is_closed_door(feat))
703                 env.grid[x][y] = DNGN_FLOOR;
704         }
705 }
706 
707 // Turns off greedy explore, then:
708 // a) Destroys all traps on the level.
709 // b) Kills all monsters on the level.
710 // c) Suppresses monster generation.
711 // d) Converts all closed doors to floor.
712 // e) Forgets map.
713 // f) Counts number of turns needed to explore the level.
debug_test_explore()714 void debug_test_explore()
715 {
716     wizard_dismiss_all_monsters(true);
717     _debug_kill_traps();
718     _debug_destroy_doors();
719 
720     forget_map();
721 
722     // Remember where we are now.
723     const coord_def where = you.pos();
724 
725     const int explore_turns = _debug_time_explore();
726 
727     // Return to starting point.
728     you.moveto(where);
729 
730     mprf("Explore took %d turns.", explore_turns);
731 }
732 
wizard_list_levels()733 void wizard_list_levels()
734 {
735     if (!you.level_stack.empty())
736     {
737         mpr("Level stack:");
738         for (unsigned int i = 0; i < you.level_stack.size(); i++)
739         {
740             mprf(MSGCH_DIAGNOSTICS, i+1, // inhibit merging
741                  "%-10s (%d,%d)", you.level_stack[i].id.describe().c_str(),
742                  you.level_stack[i].pos.x, you.level_stack[i].pos.y);
743         }
744     }
745 
746     travel_cache.update_daction_counters();
747 
748     vector<level_id> levs = travel_cache.known_levels();
749 
750     mpr("Known levels:");
751     for (unsigned int i = 0; i < levs.size(); i++)
752     {
753         const LevelInfo* lv = travel_cache.find_level_info(levs[i]);
754         ASSERT(lv);
755 
756         string cnts = "";
757         for (int j = 0; j < NUM_DACTION_COUNTERS; j++)
758         {
759             char num[20];
760             sprintf(num, "%d/", lv->daction_counters[j]);
761             cnts += num;
762         }
763         mprf(MSGCH_DIAGNOSTICS, i+1, // inhibit merging
764              "%-10s : %s", levs[i].describe().c_str(), cnts.c_str());
765     }
766 
767     string cnts = "";
768     for (int j = 0; j < NUM_DACTION_COUNTERS; j++)
769     {
770         char num[20];
771         sprintf(num, "%d/", query_daction_counter((daction_type)j));
772         cnts += num;
773     }
774     mprf("%-10s : %s", "`- total", cnts.c_str());
775 }
776 
wizard_recreate_level()777 void wizard_recreate_level()
778 {
779     mpr("Regenerating level.");
780 
781     // Need to allow reuse of vaults, otherwise we'd run out of them fast.
782     #ifndef DEBUG_VETO_RESUME
783     _free_all_vaults();
784     #endif
785 
786     for (monster_iterator mi; mi; ++mi)
787     {
788         if (mons_is_unique(mi->type))
789         {
790             remove_unique_annotation(*mi);
791             you.unique_creatures.set(mi->type, false);
792             mi->flags |= MF_TAKING_STAIRS; // no Abyss transit
793         }
794     }
795 
796     level_id lev = level_id::current();
797     _wizard_level_target = lev;
798     dungeon_feature_type stair_taken = DNGN_STONE_STAIRS_DOWN_I;
799 
800     if (lev.depth == 1 && lev != BRANCH_DUNGEON)
801         stair_taken = branches[lev.branch].entry_stairs;
802 
803     leaving_level_now(stair_taken);
804     delete_level(lev);
805     const bool newlevel = load_level(stair_taken, LOAD_START_GAME, lev);
806     you.get_place_info().levels_seen--;
807     tile_new_level(newlevel);
808     if (!crawl_state.test)
809         save_game_state();
810     new_level();
811     seen_monsters_react();
812     viewwindow();
813     update_screen();
814 
815     trackers_init_new_level();
816 }
817 
wizard_clear_used_vaults()818 void wizard_clear_used_vaults()
819 {
820     you.uniq_map_tags.clear();
821     you.uniq_map_names.clear();
822     you.uniq_map_tags_abyss.clear();
823     you.uniq_map_names_abyss.clear();
824     env.level_uniq_maps.clear();
825     env.level_uniq_map_tags.clear();
826     mpr("All vaults are now eligible for [re]use.");
827 }
828 
wizard_abyss_speed()829 void wizard_abyss_speed()
830 {
831     char specs[256];
832     mprf(MSGCH_PROMPT, "Set Abyss speed to what? (now %d, higher value = "
833                        "higher speed) ", you.abyss_speed);
834 
835     if (!cancellable_get_line(specs, sizeof(specs)))
836     {
837         const int speed = atoi(specs);
838         if (speed || specs[0] == '0')
839         {
840             mprf("Setting Abyss speed to %i.", speed);
841             you.abyss_speed = speed;
842             return;
843         }
844     }
845 
846     canned_msg(MSG_OK);
847 }
848 #endif
849