/*
* File: wiz-dgn.h
* Summary: Dungeon related wizard functions.
* Written by: Linley Henzell and Jesse Jones
*/
#include "AppHdr.h"
#include "wiz-dgn.h"
#include "branch.h"
#include "cio.h"
#include "coord.h"
#include "coordit.h"
#include "delay.h"
#include "dungeon.h"
#include "effects.h"
#include "env.h"
#include "files.h"
#include "items.h"
#include "l_defs.h"
#include "mapmark.h"
#include "maps.h"
#include "message.h"
#include "misc.h"
#include "options.h"
#include "place.h"
#include "player.h"
#include "religion.h"
#include "stuff.h"
#include "terrain.h"
#include "travel.h"
#include "traps.h"
#include "view.h"
#include "wiz-mon.h"
#ifdef WIZARD
static dungeon_feature_type _find_appropriate_stairs(bool down)
{
if (you.level_type == LEVEL_DUNGEON)
{
int depth = subdungeon_depth(you.where_are_you, you.your_level);
if (down)
depth++;
else
depth--;
// Can't go down from bottom level of a branch.
if (depth > branches[you.where_are_you].depth)
{
mpr("Can't go down from the bottom of a branch.");
return DNGN_UNSEEN;
}
// Going up from top level of branch
else if (depth == 0)
{
// Special cases
if (you.where_are_you == BRANCH_VESTIBULE_OF_HELL)
return DNGN_EXIT_HELL;
else if (you.where_are_you == BRANCH_MAIN_DUNGEON)
return DNGN_STONE_STAIRS_UP_I;
dungeon_feature_type stairs = your_branch().exit_stairs;
if (stairs < DNGN_RETURN_FROM_FIRST_BRANCH
|| stairs > DNGN_RETURN_FROM_LAST_BRANCH)
{
mpr("This branch has no exit stairs defined.");
return DNGN_UNSEEN;
}
return (stairs);
}
// Branch non-edge cases
else if (depth >= 1)
{
if (down)
return DNGN_STONE_STAIRS_DOWN_I;
else
return DNGN_ESCAPE_HATCH_UP;
}
else
{
mpr("Bug in determining level exit.");
return DNGN_UNSEEN;
}
}
switch (you.level_type)
{
case LEVEL_LABYRINTH:
if (down)
{
mpr("Can't go down in the Labyrinth.");
return DNGN_UNSEEN;
}
else
return DNGN_ESCAPE_HATCH_UP;
case LEVEL_ABYSS:
return DNGN_EXIT_ABYSS;
case LEVEL_PANDEMONIUM:
if (down)
return DNGN_TRANSIT_PANDEMONIUM;
else
return DNGN_EXIT_PANDEMONIUM;
case LEVEL_PORTAL_VAULT:
return DNGN_EXIT_PORTAL_VAULT;
default:
mpr("Unknown level type.");
return DNGN_UNSEEN;
}
mpr("Impossible occurrence in find_appropriate_stairs()");
return DNGN_UNSEEN;
}
void wizard_place_stairs( bool down )
{
dungeon_feature_type stairs = _find_appropriate_stairs(down);
if (stairs == DNGN_UNSEEN)
return;
dungeon_terrain_changed(you.pos(), stairs, false);
}
// Try to find and use stairs already in the portal vault level,
// since this might be a multi-level portal vault like a ziggurat.
bool _take_portal_vault_stairs( const bool down )
{
ASSERT(you.level_type == LEVEL_PORTAL_VAULT);
const command_type cmd = down ? CMD_GO_DOWNSTAIRS : CMD_GO_UPSTAIRS;
coord_def stair_pos(-1, -1);
for (rectangle_iterator ri(1); ri; ++ri)
{
if (feat_stair_direction(grd(*ri)) == cmd)
{
stair_pos = *ri;
break;
}
}
if (!in_bounds(stair_pos))
return (false);
clear_trapping_net();
you.set_position(stair_pos);
if (down)
down_stairs(you.your_level);
else
up_stairs();
return (true);
}
void wizard_level_travel( bool down )
{
if (you.level_type == LEVEL_PORTAL_VAULT)
if (_take_portal_vault_stairs(down))
return;
dungeon_feature_type stairs = _find_appropriate_stairs(down);
if (stairs == DNGN_UNSEEN)
return;
// This lets us, for example, use &U to exit from Pandemonium and
// &D to go to the next level.
command_type real_dir = feat_stair_direction(stairs);
if (down && real_dir == CMD_GO_UPSTAIRS
|| !down && real_dir == CMD_GO_DOWNSTAIRS)
{
down = !down;
}
if (down)
down_stairs(you.your_level, stairs);
else
up_stairs(stairs);
}
static void _wizard_go_to_level(const level_pos &pos)
{
const int abs_depth = absdungeon_depth(pos.id.branch, pos.id.depth);
dungeon_feature_type stair_taken =
abs_depth > you.your_level? DNGN_STONE_STAIRS_DOWN_I
: DNGN_STONE_STAIRS_UP_I;
if (abs_depth > you.your_level && pos.id.depth == 1
&& pos.id.branch != BRANCH_MAIN_DUNGEON)
{
stair_taken = branches[pos.id.branch].entry_stairs;
}
const int old_level = you.your_level;
const branch_type old_where = you.where_are_you;
const level_area_type old_level_type = you.level_type;
you.level_type = LEVEL_DUNGEON;
you.where_are_you = static_cast<branch_type>(pos.id.branch);
you.your_level = abs_depth;
const bool newlevel = load(stair_taken, LOAD_ENTER_LEVEL, old_level_type,
old_level, old_where);
#ifdef USE_TILE
TileNewLevel(newlevel);
#else
UNUSED(newlevel);
#endif
if (!crawl_state.test)
save_game_state();
new_level();
viewwindow(true);
// Tell stash-tracker and travel that we've changed levels.
trackers_init_new_level(true);
}
void wizard_interlevel_travel()
{
std::string name;
const level_pos pos =
prompt_translevel_target(TPF_ALLOW_UPDOWN | TPF_SHOW_ALL_BRANCHES, name).p;
if (pos.id.depth < 1 || pos.id.depth > branches[pos.id.branch].depth)
{
canned_msg(MSG_OK);
return;
}
_wizard_go_to_level(pos);
}
void wizard_create_portal()
{
mpr("Destination for portal (defaults to 'bazaar')? ", MSGCH_PROMPT);
char specs[256];
if (cancelable_get_line(specs, sizeof(specs)))
{
canned_msg( MSG_OK );
return;
}
std::string dst = specs;
dst = trim_string(dst);
dst = replace_all(dst, " ", "_");
if (dst.empty())
dst = "bazaar";
if (!find_map_by_name(dst) && !random_map_for_tag(dst))
{
mprf("No map named '%s' or tagged '%s'.", dst.c_str(), dst.c_str());
}
else
{
map_wiz_props_marker *marker = new map_wiz_props_marker(you.pos());
marker->set_property("dst", dst);
marker->set_property("desc", "wizard portal, dest = " + dst);
env.markers.add(marker);
dungeon_terrain_changed(you.pos(), DNGN_ENTER_PORTAL_VAULT, false);
}
}
void wizard_create_feature()
{
char specs[256];
int feat_num;
dungeon_feature_type feat;
mpr("Create which feature? ", MSGCH_PROMPT);
if (!cancelable_get_line(specs, sizeof(specs)) && specs[0] != 0)
{
if ((feat_num = atoi(specs)))
{
feat = static_cast<dungeon_feature_type>(feat_num);
}
else
{
std::string name = lowercase_string(specs);
name = replace_all(name, " ", "_");
feat = dungeon_feature_by_name(name);
if (feat == DNGN_UNSEEN) // no exact match
{
std::vector<std::string> matches =
dungeon_feature_matches(name);
if (matches.empty())
{
const feature_property_type fprop(str_to_fprop(name));
if (fprop != FPROP_NONE)
{
env.pgrid(you.pos()) |= fprop;
mprf("Set fprops \"%s\" at (%d,%d)",
name.c_str(), you.pos().x, you.pos().y);
}
else
{
mprf(MSGCH_DIAGNOSTICS, "No features matching '%s'",
name.c_str());
}
return;
}
// Only one possible match, use that.
if (matches.size() == 1)
{
name = matches[0];
feat = dungeon_feature_by_name(name);
}
// Multiple matches, list them to wizard
else
{
std::string prefix = "No exact match for feature '" +
name + "', possible matches are: ";
// Use mpr_comma_separated_list() because the list
// might be *LONG*.
mpr_comma_separated_list(prefix, matches, " and ", ", ",
MSGCH_DIAGNOSTICS);
return;
}
}
}
if (feat == DNGN_ENTER_SHOP)
{
debug_make_shop();
return;
}
dungeon_terrain_changed(you.pos(), feat, false);
#ifdef USE_TILE
env.tile_flv(you.pos()).special = 0;
#endif
}
else
canned_msg(MSG_OK);
}
void wizard_list_branches()
{
for (int i = 0; i < NUM_BRANCHES; ++i)
{
if (branches[i].startdepth != - 1)
{
mprf(MSGCH_DIAGNOSTICS, "Branch %d (%s) is on level %d of %s",
i, branches[i].longname, branches[i].startdepth,
branches[branches[i].parent_branch].abbrevname);
}
else if (i == BRANCH_SWAMP || i == BRANCH_SHOALS)
{
mprf(MSGCH_DIAGNOSTICS, "Branch %d (%s) was not generated "
"this game", i, branches[i].longname);
}
}
if (!you.props.exists(OVERFLOW_TEMPLES_KEY))
return;
mpr("----", MSGCH_DIAGNOSTICS);
mpr("Overflow temples: ", MSGCH_DIAGNOSTICS);
CrawlVector &levels = you.props[OVERFLOW_TEMPLES_KEY].get_vector();
for (unsigned int i = 0; i < levels.size(); i++)
{
CrawlStoreValue &val = levels[i];
// Does this level have an overflow temple?
if (val.get_flags() & SFLAG_UNSET)
continue;
CrawlVector &temples = val.get_vector();
if (temples.size() == 0)
continue;
std::vector<std::string> god_names;
for (unsigned int j = 0; j < temples.size(); j++)
{
CrawlHashTable &temple_hash = temples[j];
CrawlVector &gods = temple_hash[TEMPLE_GODS_KEY];
for (unsigned int k = 0; k < gods.size(); k++)
{
god_type god = (god_type) gods[k].get_byte();
god_names.push_back(god_name(god));
}
}
mprf(MSGCH_DIAGNOSTICS, "%lu on D:%lu (%s)", temples.size(),
i + 1,
comma_separated_line( god_names.begin(),
god_names.end() ).c_str()
);
}
}
void wizard_reveal_traps()
{
int traps_revealed = reveal_traps(1000);
mprf("Revealed %d traps.", traps_revealed);
}
void wizard_map_level()
{
if (testbits(env.level_flags, LFLAG_NOT_MAPPABLE)
|| testbits(get_branch_flags(), BFLAG_NOT_MAPPABLE))
{
if (!yesno("Force level to be mappable?", true, 'n'))
{
canned_msg( MSG_OK );
return;
}
unset_level_flags(LFLAG_NOT_MAPPABLE | LFLAG_NO_MAGIC_MAP);
unset_branch_flags(BFLAG_NOT_MAPPABLE | BFLAG_NO_MAGIC_MAP);
}
magic_mapping(1000, 100, true, true);
}
static int find_trap_slot()
{
for (int i = 0; i < MAX_TRAPS; ++i)
if (env.trap[i].type == TRAP_UNASSIGNED)
return (i);
return (-1);
}
void debug_make_trap()
{
char requested_trap[80];
int trap_slot = find_trap_slot();
trap_type trap = TRAP_UNASSIGNED;
int gridch = grd(you.pos());
if (trap_slot == -1)
{
mpr("Sorry, this level can't take any more traps.");
return;
}
if (gridch != DNGN_FLOOR)
{
mpr("You need to be on a floor square to make a trap.");
return;
}
mprf(MSGCH_PROMPT, "What kind of trap? ");
get_input_line( requested_trap, sizeof( requested_trap ) );
if (!*requested_trap)
return;
strlwr(requested_trap);
std::vector<trap_type> matches;
std::vector<std::string> match_names;
for (int t = TRAP_DART; t < NUM_TRAPS; ++t)
{
const trap_type tr = static_cast<trap_type>(t);
const char* tname = trap_name(tr);
if (strstr(requested_trap, tname))
{
trap = tr;
break;
}
else if (strstr(tname, requested_trap))
{
matches.push_back(tr);
match_names.push_back(tname);
}
}
if (trap == TRAP_UNASSIGNED)
{
if (matches.empty())
{
mprf("I know no traps named \"%s\"", requested_trap);
return;
}
// Only one match, use that
else if (matches.size() == 1)
trap = matches[0];
else
{
std::string prefix = "No exact match for trap '";
prefix += requested_trap;
prefix += "', possible matches are: ";
mpr_comma_separated_list(prefix, match_names);
return;
}
}
place_specific_trap(you.pos(), trap);
mprf("Created a %s trap, marked it undiscovered", trap_name(trap));
if (trap == TRAP_SHAFT && !is_valid_shaft_level())
mpr("NOTE: Shaft traps aren't valid on this level.");
}
void debug_make_shop()
{
char requested_shop[80];
int gridch = grd(you.pos());
bool have_shop_slots = false;
int new_shop_type = SHOP_UNASSIGNED;
bool representative = false;
if (gridch != DNGN_FLOOR)
{
mpr("Insufficient floor-space for new Wal-Mart.");
return;
}
for (int i = 0; i < MAX_SHOPS; ++i)
{
if (env.shop[i].type == SHOP_UNASSIGNED)
{
have_shop_slots = true;
break;
}
}
if (!have_shop_slots)
{
mpr("There are too many shops on this level.");
return;
}
mprf(MSGCH_PROMPT, "What kind of shop? ");
get_input_line( requested_shop, sizeof( requested_shop ) );
if (!*requested_shop)
return;
strlwr(requested_shop);
std::string s = replace_all_of(requested_shop, "*", "");
new_shop_type = str_to_shoptype(s);
if (new_shop_type == SHOP_UNASSIGNED || new_shop_type == -1)
{
mprf("Bad shop type: \"%s\"", requested_shop);
return;
}
representative = !!strchr(requested_shop, '*');
place_spec_shop(you.your_level, you.pos(),
new_shop_type, representative);
link_items();
mprf("Done.");
}
static void debug_load_map_by_name(std::string name)
{
const bool place_on_us = strip_tag(name, "*", true);
level_clear_vault_memory();
const map_def *toplace = find_map_by_name(name);
if (!toplace)
{
std::vector<std::string> matches = find_map_matches(name);
if (matches.empty())
{
mprf("Can't find map named '%s'.", name.c_str());
return;
}
else if (matches.size() == 1)
{
std::string prompt = "Only match is '";
prompt += matches[0];
prompt += "', use that?";
if (!yesno(prompt.c_str(), true, 'y'))
return;
toplace = find_map_by_name(matches[0]);
}
else
{
std::string prompt = "No exact matches for '";
prompt += name;
prompt += "', possible matches are: ";
mpr_comma_separated_list(prompt, matches);
return;
}
}
coord_def where(-1, -1);
if (place_on_us)
{
if (toplace->orient == MAP_FLOAT || toplace->orient == MAP_NONE)
{
coord_def size = toplace->map.size();
coord_def tl = you.pos() - (size / 2) - coord_def(-1, -1);
coord_def br = you.pos() + (size / 2) + coord_def(-1, -1);
for (rectangle_iterator ri(tl, br); ri; ++ri)
{
if (!in_bounds(*ri))
{
mprf("Placing %s on top of you would put part of the "
"map outside of the level, cancelling.",
toplace->name.c_str());
return;
}
}
// We're okay.
where = you.pos();
}
else
{
mprf("%s decides where it goes, can't place where you are.",
toplace->name.c_str());
}
}
if (dgn_place_map(toplace, true, false, where))
mprf("Successfully placed %s.", toplace->name.c_str());
else
mprf("Failed to place %s.", toplace->name.c_str());
}
void debug_place_map()
{
char what_to_make[100];
mesclr();
mprf(MSGCH_PROMPT, "Enter map name: ");
if (cancelable_get_line_autohist(what_to_make, sizeof what_to_make))
{
canned_msg(MSG_OK);
return;
}
std::string what = what_to_make;
trim_string(what);
if (what.empty())
{
canned_msg(MSG_OK);
return;
}
debug_load_map_by_name(what);
}
static void _debug_kill_traps()
{
for (rectangle_iterator ri(1); ri; ++ri)
if (feat_is_trap(grd(*ri), true))
destroy_trap(*ri);
}
static int _debug_time_explore()
{
viewwindow(false);
start_explore(false);
unwind_var<int> es(Options.explore_stop, 0);
const long start = you.num_turns;
while (you_are_delayed())
{
you.turn_is_over = false;
handle_delay();
you.num_turns++;
}
// Elapsed time might not match up if explore had to go through
// shallow water.
PlaceInfo& pi = you.get_place_info();
pi.elapsed_total = (pi.elapsed_explore + pi.elapsed_travel +
pi.elapsed_interlevel + pi.elapsed_resting +
pi.elapsed_other);
PlaceInfo& pi2 = you.global_info;
pi2.elapsed_total = (pi2.elapsed_explore + pi2.elapsed_travel +
pi2.elapsed_interlevel + pi2.elapsed_resting +
pi2.elapsed_other);
return (you.num_turns - start);
}
static void _debug_destroy_doors()
{
for (int y = 0; y < GYM; ++y)
for (int x = 0; x < GXM; ++x)
{
const dungeon_feature_type feat = grd[x][y];
if (feat == DNGN_SECRET_DOOR || feat_is_closed_door(feat))
grd[x][y] = DNGN_FLOOR;
}
}
// Turns off greedy explore, then:
// a) Destroys all traps on the level.
// b) Kills all monsters on the level.
// c) Suppresses monster generation.
// d) Converts all closed doors and secret doors to floor.
// e) Forgets map.
// f) Counts number of turns needed to explore the level.
void debug_test_explore()
{
wizard_dismiss_all_monsters(true);
_debug_kill_traps();
_debug_destroy_doors();
forget_map(100);
// Remember where we are now.
const coord_def where = you.pos();
const int explore_turns = _debug_time_explore();
// Return to starting point.
you.moveto(where);
mprf("Explore took %d turns.", explore_turns);
}
void debug_shift_labyrinth()
{
if (you.level_type != LEVEL_LABYRINTH)
{
mpr("This only makes sense in a labyrinth!");
return;
}
change_labyrinth(true);
}
#endif