summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/travel.cc
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source/travel.cc')
-rwxr-xr-xcrawl-ref/source/travel.cc2923
1 files changed, 2923 insertions, 0 deletions
diff --git a/crawl-ref/source/travel.cc b/crawl-ref/source/travel.cc
new file mode 100755
index 0000000000..d65a852b9c
--- /dev/null
+++ b/crawl-ref/source/travel.cc
@@ -0,0 +1,2923 @@
+/*
+ * File: travel.cc
+ * Summary: Travel stuff
+ * Written by: Darshan Shaligram
+ *
+ * Known issues:
+ * Hardcoded dungeon features all over the place - this thing is a devil to
+ * refactor.
+ */
+#include "AppHdr.h"
+#include "files.h"
+#include "FixAry.h"
+#include "clua.h"
+#include "mon-util.h"
+#include "player.h"
+#include "stash.h"
+#include "stuff.h"
+#include "travel.h"
+#include "view.h"
+#include <stdarg.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#ifdef DOS
+#include <dos.h>
+#endif
+
+#define TC_MAJOR_VERSION ((unsigned char) 4)
+#define TC_MINOR_VERSION ((unsigned char) 4)
+
+enum IntertravelDestination
+{
+ // Go down a level
+ ID_DOWN = -100,
+
+ // Go up a level
+ ID_UP = -99,
+
+ // Repeat last travel
+ ID_REPEAT = -101,
+
+ // Cancel interlevel travel
+ ID_CANCEL = -1000,
+};
+
+TravelCache travel_cache;
+
+// Tracks the distance between the target location on the target level and the
+// stairs on the level.
+static std::vector<stair_info> curr_stairs;
+
+// Squares that are not safe to travel to on the current level.
+static std::vector<coord_def> curr_excludes;
+
+// This is where we last tried to take a stair during interlevel travel.
+// Note that last_stair.depth should be set to -1 before initiating interlevel
+// travel.
+static level_id last_stair;
+
+// Where travel wants to get to.
+static level_pos travel_target;
+
+// The place in the Vestibule of Hell where all portals to Hell land.
+static level_pos travel_hell_entry;
+
+static bool traps_inited = false;
+
+// TODO: Do we need this or would a different header be better?
+inline int sgn(int x)
+{
+ return x < 0? -1 : (x > 0);
+}
+
+/* These are defined in view.cc. BWR says these may become obsolete in next
+ * release? */
+extern unsigned char (*mapch) (unsigned char);
+extern unsigned char (*mapch2) (unsigned char);
+extern unsigned char mapchar(unsigned char ldfk);
+extern unsigned char mapchar2(unsigned char ldfk);
+
+
+// Array of points on the map, each value being the distance the character
+// would have to travel to get there. Negative distances imply that the point
+// is a) a trap or hostile terrain or b) only reachable by crossing a trap or
+// hostile terrain.
+short point_distance[GXM][GYM];
+
+unsigned char curr_waypoints[GXM][GYM];
+
+signed char curr_traps[GXM][GYM];
+
+static FixedArray< unsigned short, GXM, GYM > mapshadow;
+
+// Clockwise, around the compass from north (same order as enum RUN_DIR)
+// Copied from acr.cc
+static const struct coord_def Compass[8] =
+{
+ { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 },
+ { 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 },
+};
+
+#define TRAVERSABLE 1
+#define IMPASSABLE 0
+#define FORBIDDEN -1
+
+// Map of terrain types that are traversable.
+static signed char traversable_terrain[256];
+
+static int trans_negotiate_stairs();
+static int find_transtravel_square(const level_pos &pos, bool verbose = true);
+
+static bool loadlev_populate_stair_distances(const level_pos &target);
+static void populate_stair_distances(const level_pos &target);
+
+// Determines whether the player has seen this square, given the user-visible
+// character.
+//
+// The player is assumed to have seen the square if:
+// a. The square is mapped (the env map char is not zero)
+// b. The square was *not* magic-mapped.
+//
+bool is_player_mapped(unsigned char envch)
+{
+ // Note that we're relying here on mapch(DNGN_FLOOR) != mapch2(DNGN_FLOOR)
+ // and that no *other* dungeon feature renders as mapch(DNGN_FLOOR).
+ // The check for a ~ is to ensure explore stops for items turned up by
+ // detect items.
+ return envch && envch != mapch(DNGN_FLOOR) && envch != '~';
+}
+
+inline bool is_trap(unsigned char grid)
+{
+ return (grid == DNGN_TRAP_MECHANICAL || grid == DNGN_TRAP_MAGICAL
+ || grid == DNGN_TRAP_III);
+}
+
+// Returns true if there is a known trap at (x,y). Returns false for non-trap
+// squares as also for undiscovered traps.
+//
+inline bool is_trap(int x, int y)
+{
+ return is_trap( grd[x][y] );
+}
+
+// Returns true if this feature takes extra time to cross.
+inline int feature_traverse_cost(unsigned char feature)
+{
+ return (feature == DNGN_SHALLOW_WATER || feature == DNGN_CLOSED_DOOR? 2 :
+ is_trap(feature) ? 3 : 1);
+}
+
+// Returns true if the dungeon feature supplied is an altar.
+inline bool is_altar(unsigned char grid)
+{
+ return grid >= DNGN_ALTAR_ZIN && grid <= DNGN_ALTAR_ELYVILON;
+}
+
+inline bool is_altar(const coord_def &c)
+{
+ return is_altar(grd[c.x][c.y]);
+}
+
+inline bool is_player_altar(unsigned char grid)
+{
+ // An ugly hack, but that's what religion.cc does.
+ return you.religion
+ && grid == DNGN_ALTAR_ZIN - 1 + you.religion;
+}
+
+inline bool is_player_altar(const coord_def &c)
+{
+ return is_player_altar(grd[c.x][c.y]);
+}
+
+// Copies FixedArray src to FixedArray dest.
+//
+inline void copy(const FixedArray<unsigned short, GXM, GYM> &src,
+ FixedArray<unsigned short, GXM, GYM> &dest)
+{
+ dest = src;
+}
+
+#ifdef CLUA_BINDINGS
+static void init_traps()
+{
+ memset(curr_traps, -1, sizeof curr_traps);
+ for (int i = 0; i < MAX_TRAPS; ++i)
+ {
+ int x = env.trap[i].x,
+ y = env.trap[i].y;
+ if (x > 0 && x < GXM && y > 0 && y < GYM)
+ curr_traps[x][y] = i;
+ }
+ traps_inited = true;
+}
+
+static const char *trap_names[] =
+{
+ "dart", "arrow", "spear", "axe",
+ "teleport", "amnesia", "blade",
+ "bolt", "zot", "needle",
+};
+
+static const char *trap_name(int x, int y)
+{
+ if (!traps_inited)
+ init_traps();
+
+ const int ti = curr_traps[x][y];
+ if (ti != -1)
+ {
+ int type = env.trap[ti].type;
+ if (type >= 0 && type < NUM_TRAPS)
+ return (trap_names[type]);
+ }
+ return ("");
+}
+#endif
+
+/*
+ * Returns true if the character can cross this dungeon feature.
+ */
+inline bool is_traversable(unsigned char grid)
+{
+ return traversable_terrain[(int) grid] == TRAVERSABLE;
+}
+
+static bool is_excluded(int x, int y)
+{
+ for (int i = 0, count = curr_excludes.size(); i < count; ++i)
+ {
+ const coord_def &c = curr_excludes[i];
+ int dx = c.x - x,
+ dy = c.y - y;
+ if (dx * dx + dy * dy <= Options.travel_exclude_radius2)
+ return true;
+ }
+ return false;
+}
+
+static bool is_exclude_root(int x, int y)
+{
+ for (int i = 0, count = curr_excludes.size(); i < count; ++i)
+ {
+ const coord_def &c = curr_excludes[i];
+ if (c.x == x && c.y == y)
+ return true;
+ }
+ return false;
+}
+
+const char *run_mode_name(int runmode)
+{
+ return runmode == RUN_TRAVEL? "travel" :
+ runmode == RUN_INTERLEVEL? "intertravel" :
+ runmode == RUN_EXPLORE? "explore" :
+ runmode > 0? "run" :
+ "";
+}
+
+unsigned char is_waypoint(int x, int y)
+{
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS
+ || you.level_type == LEVEL_PANDEMONIUM)
+ return 0;
+ return curr_waypoints[x][y];
+}
+
+#ifdef STASH_TRACKING
+inline bool is_stash(LevelStashes *ls, int x, int y)
+{
+ if (!ls)
+ return (false);
+ Stash *s = ls->find_stash(x, y);
+ return s && s->enabled;
+}
+#endif
+
+void clear_excludes()
+{
+ // Sanity checks
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
+ return;
+
+ curr_excludes.clear();
+
+ if (can_travel_interlevel())
+ {
+ LevelInfo &li = travel_cache.get_level_info(
+ level_id::get_current_level_id());
+ li.update();
+ }
+}
+
+void toggle_exclude(int x, int y)
+{
+ // Sanity checks
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
+ return;
+
+ if (x <= 0 || x >= GXM || y <= 0 || y >= GYM) return;
+ if (!env.map[x - 1][y - 1]) return;
+
+ if (is_exclude_root(x, y))
+ {
+ for (int i = 0, count = curr_excludes.size(); i < count; ++i)
+ {
+ const coord_def &c = curr_excludes[i];
+ if (c.x == x && c.y == y)
+ {
+ curr_excludes.erase( curr_excludes.begin() + i );
+ break ;
+ }
+ }
+ }
+ else
+ {
+ coord_def c = { x, y };
+ curr_excludes.push_back(c);
+ }
+
+ if (can_travel_interlevel())
+ {
+ LevelInfo &li = travel_cache.get_level_info(
+ level_id::get_current_level_id());
+ li.update();
+ }
+}
+
+void update_excludes()
+{
+ // Sanity checks
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
+ return;
+ for (int i = curr_excludes.size() - 1; i >= 0; --i)
+ {
+ int x = curr_excludes[i].x,
+ y = curr_excludes[i].y;
+ if (!env.map[x - 1][y - 1])
+ curr_excludes.erase( curr_excludes.begin() + i );
+ }
+}
+
+void forget_square(int x, int y)
+{
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
+ return;
+
+ if (is_exclude_root(x, y))
+ toggle_exclude(x, y);
+}
+
+/*
+ * Returns true if the square at (x,y) is a dungeon feature the character
+ * can't (under normal circumstances) safely cross.
+ *
+ * Note: is_reseedable can return true for dungeon features that is_traversable
+ * also returns true for. This is okay, because is_traversable always
+ * takes precedence over is_reseedable. is_reseedable is used only to
+ * decide which squares to reseed from when flood-filling outwards to
+ * colour the level map. It does not affect pathing of actual
+ * travel/explore.
+ */
+static bool is_reseedable(int x, int y)
+{
+ if (is_excluded(x, y))
+ return true;
+ unsigned char grid = grd[x][y];
+ return (grid == DNGN_DEEP_WATER || grid == DNGN_SHALLOW_WATER ||
+ grid == DNGN_LAVA || is_trap(x, y));
+}
+
+/*
+ * Returns true if the square at (x,y) is okay to travel over. If ignore_hostile
+ * is true, returns true even for dungeon features the character can normally
+ * not cross safely (deep water, lava, traps).
+ */
+static bool is_travel_ok(int x, int y, bool ignore_hostile)
+{
+ unsigned char grid = grd[x][y];
+
+ unsigned char envc = (unsigned char) env.map[x - 1][y - 1];
+ if (!envc) return false;
+
+ // Special-case secret doors so that we don't run into awkwardness when
+ // a monster opens a secret door without the hero seeing it, but the travel
+ // code paths through the secret door because it looks at the actual grid,
+ // rather than the env overmap. Hopefully there won't be any more such
+ // cases.
+ if (envc == mapch2(DNGN_SECRET_DOOR)) return false;
+
+ unsigned char mon = mgrd[x][y];
+ if (mon != NON_MONSTER)
+ {
+ // Kludge warning: navigating around zero-exp beasties uses knowledge
+ // that the player may not have (the player may not
+ // know that there's a plant at any given (x,y), but we
+ // know, because we're looking directly at the grid).
+ // Arguably the utility of this feature is greater than
+ // the information we're giving the player for free.
+ // Navigate around plants and fungi. Yet another tasty hack.
+ if (player_monster_visible(&menv[mon]) &&
+ mons_flag( menv[mon].type, M_NO_EXP_GAIN ))
+ {
+ extern short point_distance[GXM][GYM];
+
+ // We have to set the point_distance array if the level map is
+ // to be properly coloured. The caller isn't going to do it because
+ // we say this square is inaccessible, so in a horrible hack, we
+ // do it ourselves. Ecch.
+ point_distance[x][y] = ignore_hostile? -42 : 42;
+ return false;
+ }
+ }
+
+ // If 'ignore_hostile' is true, we're ignoring hazards that can be
+ // navigated over if the player is willing to take damage, or levitate.
+ if (ignore_hostile && is_reseedable(x, y))
+ return true;
+
+ return (is_traversable(grid)
+#ifdef CLUA_BINDINGS
+ ||
+ (is_trap(x, y) &&
+ clua.callbooleanfn(false, "ch_cross_trap",
+ "s", trap_name(x, y)))
+#endif
+ )
+ && !is_excluded(x, y);
+}
+
+// Returns true if the location at (x,y) is monster-free and contains no clouds.
+static bool is_safe(int x, int y)
+{
+ unsigned char mon = mgrd[x][y];
+ if (mon != NON_MONSTER)
+ {
+ // If this is an invisible critter, say we're safe to get here, but
+ // turn off travel - the result should be that the player bashes into
+ // the monster and stops travelling right there. Same treatment applies
+ // to mimics.
+ if (!player_monster_visible(&menv[mon]) ||
+ mons_is_mimic( menv[mon].type ))
+ {
+ you.running = 0;
+ return true;
+ }
+
+ // Stop before wasting energy on plants and fungi.
+ if (mons_flag( menv[mon].type, M_NO_EXP_GAIN ))
+ return false;
+
+ // If this is any *other* monster, it'll be visible and
+ // a) Friendly, in which case we'll displace it, no problem.
+ // b) Unfriendly, in which case we're in deep trouble, since travel
+ // should have been aborted already by the checks in view.cc.
+ }
+ const char cloud = env.cgrid[x][y];
+ if (cloud == EMPTY_CLOUD)
+ return true;
+
+ // We can also safely run through smoke.
+ const char cloud_type = env.cloud[ cloud ].type;
+ return cloud_type == CLOUD_GREY_SMOKE ||
+ cloud_type == CLOUD_GREY_SMOKE_MON ||
+ cloud_type == CLOUD_BLUE_SMOKE ||
+ cloud_type == CLOUD_BLUE_SMOKE_MON ||
+ cloud_type == CLOUD_PURP_SMOKE ||
+ cloud_type == CLOUD_PURP_SMOKE_MON ||
+ cloud_type == CLOUD_BLACK_SMOKE ||
+ cloud_type == CLOUD_BLACK_SMOKE_MON;
+}
+
+static bool player_is_permalevitating()
+{
+ return you.levitation > 1 &&
+ ((you.species == SP_KENKU && you.experience_level >= 15)
+ || player_equip_ego_type( EQ_BOOTS, SPARM_LEVITATION ));
+}
+
+static void set_pass_feature(unsigned char grid, signed char pass)
+{
+ if (traversable_terrain[(unsigned) grid] != FORBIDDEN)
+ traversable_terrain[(unsigned) grid] = pass;
+}
+
+/*
+ * Sets traversable terrain based on the character's role and whether or not he
+ * has permanent levitation
+ */
+static void init_terrain_check()
+{
+ // Merfolk get deep water.
+ signed char water = you.species == SP_MERFOLK? TRAVERSABLE : IMPASSABLE;
+ // If the player has overridden deep water already, we'll respect that.
+ set_pass_feature(DNGN_DEEP_WATER, water);
+
+ // Permanently levitating players can cross most hostile terrain.
+ signed char trav = player_is_permalevitating()?
+ TRAVERSABLE : IMPASSABLE;
+ if (you.species != SP_MERFOLK)
+ set_pass_feature(DNGN_DEEP_WATER, trav);
+ set_pass_feature(DNGN_LAVA, trav);
+ set_pass_feature(DNGN_TRAP_MECHANICAL, trav);
+}
+
+void travel_init_new_level()
+{
+ // Zero out last travel coords
+ you.run_x = you.run_y = 0;
+ you.travel_x = you.travel_y = 0;
+
+ traps_inited = false;
+ curr_excludes.clear();
+ travel_cache.set_level_excludes();
+ travel_cache.update_waypoints();
+}
+
+/*
+ * Sets up travel-related stuff.
+ */
+void initialise_travel()
+{
+ // Need a better way to do this. :-(
+ traversable_terrain[DNGN_FLOOR] =
+ traversable_terrain[DNGN_ENTER_HELL] =
+ traversable_terrain[DNGN_OPEN_DOOR] =
+ traversable_terrain[DNGN_BRANCH_STAIRS] =
+ traversable_terrain[DNGN_UNDISCOVERED_TRAP] =
+ traversable_terrain[DNGN_ENTER_SHOP] =
+ traversable_terrain[DNGN_ENTER_LABYRINTH] =
+ traversable_terrain[DNGN_STONE_STAIRS_DOWN_I] =
+ traversable_terrain[DNGN_STONE_STAIRS_DOWN_II] =
+ traversable_terrain[DNGN_STONE_STAIRS_DOWN_III] =
+ traversable_terrain[DNGN_ROCK_STAIRS_DOWN] =
+ traversable_terrain[DNGN_STONE_STAIRS_UP_I] =
+ traversable_terrain[DNGN_STONE_STAIRS_UP_II] =
+ traversable_terrain[DNGN_STONE_STAIRS_UP_III] =
+ traversable_terrain[DNGN_ROCK_STAIRS_UP] =
+ traversable_terrain[DNGN_ENTER_DIS] =
+ traversable_terrain[DNGN_ENTER_GEHENNA] =
+ traversable_terrain[DNGN_ENTER_COCYTUS] =
+ traversable_terrain[DNGN_ENTER_TARTARUS] =
+ traversable_terrain[DNGN_ENTER_ABYSS] =
+ traversable_terrain[DNGN_EXIT_ABYSS] =
+ traversable_terrain[DNGN_STONE_ARCH] =
+ traversable_terrain[DNGN_ENTER_PANDEMONIUM] =
+ traversable_terrain[DNGN_EXIT_PANDEMONIUM] =
+ traversable_terrain[DNGN_TRANSIT_PANDEMONIUM] =
+ traversable_terrain[DNGN_ENTER_ORCISH_MINES] =
+ traversable_terrain[DNGN_ENTER_HIVE] =
+ traversable_terrain[DNGN_ENTER_LAIR] =
+ traversable_terrain[DNGN_ENTER_SLIME_PITS] =
+ traversable_terrain[DNGN_ENTER_VAULTS] =
+ traversable_terrain[DNGN_ENTER_CRYPT] =
+ traversable_terrain[DNGN_ENTER_HALL_OF_BLADES] =
+ traversable_terrain[DNGN_ENTER_ZOT] =
+ traversable_terrain[DNGN_ENTER_TEMPLE] =
+ traversable_terrain[DNGN_ENTER_SNAKE_PIT] =
+ traversable_terrain[DNGN_ENTER_ELVEN_HALLS] =
+ traversable_terrain[DNGN_ENTER_TOMB] =
+ traversable_terrain[DNGN_ENTER_SWAMP] =
+ traversable_terrain[DNGN_RETURN_FROM_ORCISH_MINES] =
+ traversable_terrain[DNGN_RETURN_FROM_HIVE] =
+ traversable_terrain[DNGN_RETURN_FROM_LAIR] =
+ traversable_terrain[DNGN_RETURN_FROM_SLIME_PITS] =
+ traversable_terrain[DNGN_RETURN_FROM_VAULTS] =
+ traversable_terrain[DNGN_RETURN_FROM_CRYPT] =
+ traversable_terrain[DNGN_RETURN_FROM_HALL_OF_BLADES] =
+ traversable_terrain[DNGN_RETURN_FROM_ZOT] =
+ traversable_terrain[DNGN_RETURN_FROM_TEMPLE] =
+ traversable_terrain[DNGN_RETURN_FROM_SNAKE_PIT] =
+ traversable_terrain[DNGN_RETURN_FROM_ELVEN_HALLS] =
+ traversable_terrain[DNGN_RETURN_FROM_TOMB] =
+ traversable_terrain[DNGN_RETURN_FROM_SWAMP] =
+ traversable_terrain[DNGN_ALTAR_ZIN] =
+ traversable_terrain[DNGN_ALTAR_SHINING_ONE] =
+ traversable_terrain[DNGN_ALTAR_KIKUBAAQUDGHA] =
+ traversable_terrain[DNGN_ALTAR_YREDELEMNUL] =
+ traversable_terrain[DNGN_ALTAR_XOM] =
+ traversable_terrain[DNGN_ALTAR_VEHUMET] =
+ traversable_terrain[DNGN_ALTAR_OKAWARU] =
+ traversable_terrain[DNGN_ALTAR_MAKHLEB] =
+ traversable_terrain[DNGN_ALTAR_SIF_MUNA] =
+ traversable_terrain[DNGN_ALTAR_TROG] =
+ traversable_terrain[DNGN_ALTAR_NEMELEX_XOBEH] =
+ traversable_terrain[DNGN_ALTAR_ELYVILON] =
+ traversable_terrain[DNGN_BLUE_FOUNTAIN] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_I] =
+ traversable_terrain[DNGN_SPARKLING_FOUNTAIN] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_II] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_III] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_IV] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_V] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_VI] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_VII] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_VIII] =
+ traversable_terrain[DNGN_PERMADRY_FOUNTAIN] =
+ traversable_terrain[DNGN_CLOSED_DOOR] =
+ traversable_terrain[DNGN_SHALLOW_WATER] =
+ TRAVERSABLE;
+}
+
+/*
+ * Given a dungeon feature description, returns the feature number. This is a
+ * crude hack and currently recognises only (deep/shallow) water.
+ *
+ * Returns -1 if the feature named is not recognised, else returns the feature
+ * number (guaranteed to be 0-255).
+ */
+int get_feature_type(const std::string &feature)
+{
+ if (feature.find("deep water") != std::string::npos)
+ return DNGN_DEEP_WATER;
+ if (feature.find("shallow water") != std::string::npos)
+ return DNGN_SHALLOW_WATER;
+ return -1;
+}
+
+/*
+ * Given a feature description, prevents travel to locations of that feature
+ * type.
+ */
+void prevent_travel_to(const std::string &feature)
+{
+ int feature_type = get_feature_type(feature);
+ if (feature_type != -1)
+ traversable_terrain[feature_type] = FORBIDDEN;
+}
+
+bool is_stair(unsigned gridc)
+{
+ return (is_travelable_stair(gridc)
+ || gridc == DNGN_ENTER_ABYSS
+ || gridc == DNGN_ENTER_LABYRINTH
+ || gridc == DNGN_ENTER_PANDEMONIUM
+ || gridc == DNGN_EXIT_PANDEMONIUM
+ || gridc == DNGN_TRANSIT_PANDEMONIUM);
+}
+
+/*
+ * Returns true if the given dungeon feature can be considered a stair.
+ */
+bool is_travelable_stair(unsigned gridc)
+{
+ switch (gridc)
+ {
+ case DNGN_ENTER_HELL:
+ case DNGN_STONE_STAIRS_DOWN_I:
+ case DNGN_STONE_STAIRS_DOWN_II:
+ case DNGN_STONE_STAIRS_DOWN_III:
+ case DNGN_ROCK_STAIRS_DOWN:
+ case DNGN_STONE_STAIRS_UP_I:
+ case DNGN_STONE_STAIRS_UP_II:
+ case DNGN_STONE_STAIRS_UP_III:
+ case DNGN_ROCK_STAIRS_UP:
+ case DNGN_ENTER_DIS:
+ case DNGN_ENTER_GEHENNA:
+ case DNGN_ENTER_COCYTUS:
+ case DNGN_ENTER_TARTARUS:
+ case DNGN_ENTER_ORCISH_MINES:
+ case DNGN_ENTER_HIVE:
+ case DNGN_ENTER_LAIR:
+ case DNGN_ENTER_SLIME_PITS:
+ case DNGN_ENTER_VAULTS:
+ case DNGN_ENTER_CRYPT:
+ case DNGN_ENTER_HALL_OF_BLADES:
+ case DNGN_ENTER_ZOT:
+ case DNGN_ENTER_TEMPLE:
+ case DNGN_ENTER_SNAKE_PIT:
+ case DNGN_ENTER_ELVEN_HALLS:
+ case DNGN_ENTER_TOMB:
+ case DNGN_ENTER_SWAMP:
+ case DNGN_RETURN_FROM_ORCISH_MINES:
+ case DNGN_RETURN_FROM_HIVE:
+ case DNGN_RETURN_FROM_LAIR:
+ case DNGN_RETURN_FROM_SLIME_PITS:
+ case DNGN_RETURN_FROM_VAULTS:
+ case DNGN_RETURN_FROM_CRYPT:
+ case DNGN_RETURN_FROM_HALL_OF_BLADES:
+ case DNGN_RETURN_FROM_ZOT:
+ case DNGN_RETURN_FROM_TEMPLE:
+ case DNGN_RETURN_FROM_SNAKE_PIT:
+ case DNGN_RETURN_FROM_ELVEN_HALLS:
+ case DNGN_RETURN_FROM_TOMB:
+ case DNGN_RETURN_FROM_SWAMP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+#define ES_item (Options.explore_stop & ES_ITEM)
+#define ES_shop (Options.explore_stop & ES_SHOP)
+#define ES_stair (Options.explore_stop & ES_STAIR)
+#define ES_altar (Options.explore_stop & ES_ALTAR)
+
+/*
+ * Given a square that has just become visible during explore, returns true
+ * if the player might consider the square worth stopping explore for.
+ */
+static bool is_interesting_square(int x, int y)
+{
+ if (ES_item && igrd[x + 1][y + 1] != NON_ITEM)
+ return true;
+
+ unsigned char grid = grd[x + 1][y + 1];
+ return (ES_shop && grid == DNGN_ENTER_SHOP)
+ || (ES_stair && is_stair(grid))
+ || (ES_altar && is_altar(grid)
+ && you.where_are_you != BRANCH_ECUMENICAL_TEMPLE);
+}
+
+static void userdef_run_stoprunning_hook(void)
+{
+#ifdef CLUA_BINDINGS
+ if (you.running)
+ clua.callfn("ch_stop_running", "s", run_mode_name(you.running));
+#endif
+}
+
+static void userdef_run_startrunning_hook(void)
+{
+#ifdef CLUA_BINDINGS
+ if (you.running)
+ clua.callfn("ch_start_running", "s", run_mode_name(you.running));
+#endif
+}
+
+/*
+ * Stops shift+running and all forms of travel.
+ */
+void stop_running(void)
+{
+ userdef_run_stoprunning_hook();
+ you.running = 0;
+}
+
+void start_running(void)
+{
+ userdef_run_startrunning_hook();
+}
+
+/*
+ * Top-level travel control (called from input() in acr.cc).
+ *
+ * travel() is responsible for making the individual moves that constitute
+ * (interlevel) travel and explore and deciding when travel and explore
+ * end.
+ *
+ * Don't call travel() if you.running >= 0.
+ */
+void travel(int *keyin, char *move_x, char *move_y)
+{
+ *keyin = 128;
+
+ // Abort travel/explore if you're confused or a key was pressed.
+ if (kbhit() || you.conf)
+ {
+ stop_running();
+ *keyin = 0;
+ if (Options.travel_delay == -1)
+ redraw_screen();
+ return ;
+ }
+
+ if (Options.explore_stop && you.running == RUN_EXPLORE)
+ {
+ // Scan through the shadow map, compare it with the actual map, and if
+ // there are any squares of the shadow map that have just been
+ // discovered and contain an item, or have an interesting dungeon
+ // feature, stop exploring.
+ for (int y = 0; y < GYM - 1; ++y)
+ {
+ for (int x = 0; x < GXM - 1; ++x)
+ {
+ if (!is_player_mapped(mapshadow[x][y])
+ && is_player_mapped((unsigned char) env.map[x][y])
+ && is_interesting_square(x, y))
+ {
+ stop_running();
+ y = GYM;
+ break;
+ }
+ }
+ }
+ copy(env.map, mapshadow);
+ }
+
+ if (you.running == RUN_EXPLORE)
+ {
+ // Exploring
+ you.run_x = 0;
+ find_travel_pos(you.x_pos, you.y_pos, NULL, NULL);
+ // No place to go?
+ if (!you.run_x)
+ stop_running();
+ }
+
+ if (you.running == RUN_INTERLEVEL && !you.run_x)
+ {
+ // Interlevel travel. Since you.run_x is zero, we've either just
+ // initiated travel, or we've just climbed or descended a staircase,
+ // and we need to figure out where to travel to next.
+ if (!find_transtravel_square(travel_target) || !you.run_x)
+ stop_running();
+ }
+
+ if (you.running < 0)
+ {
+ // Remember what run-mode we were in so that we can resume explore/
+ // interlevel travel correctly.
+ int runmode = you.running;
+
+ // Get the next step to make. If the travel command can't find a route,
+ // we turn off travel (find_travel_pos does that automatically).
+ find_travel_pos(you.x_pos, you.y_pos, move_x, move_y);
+
+ if (!*move_x && !*move_y)
+ {
+ // If we've reached the square we were traveling towards, travel
+ // should stop if this is simple travel. If we're exploring, we
+ // should continue doing so (explore has its own end condition
+ // upstairs); if we're traveling between levels and we've reached
+ // our travel target, we're on a staircase and should take it.
+ if (you.x_pos == you.run_x && you.y_pos == you.run_y)
+ {
+ if (runmode == RUN_EXPLORE)
+ you.running = RUN_EXPLORE; // Turn explore back on
+
+ // For interlevel travel, we'll want to take the stairs unless
+ // the interlevel travel specified a destination square and
+ // we've reached that destination square.
+ else if (runmode == RUN_INTERLEVEL
+ && (travel_target.pos.x != you.x_pos
+ || travel_target.pos.y != you.y_pos
+ || travel_target.id !=
+ level_id::get_current_level_id()))
+ {
+ if (last_stair.depth != -1
+ && last_stair == level_id::get_current_level_id())
+ {
+ // We're trying to take the same stairs again. Baaad.
+
+ // We don't directly call stop_running() because
+ // you.running is probably 0, and stop_running() won't
+ // notify Lua hooks if you.running == 0.
+ you.running = runmode;
+ stop_running();
+ return;
+ }
+ you.running = RUN_INTERLEVEL;
+ *keyin = trans_negotiate_stairs();
+
+ // If, for some reason, we fail to use the stairs, we
+ // need to make sure we don't go into an infinite loop
+ // trying to take it again and again. We'll check
+ // last_stair before attempting to take stairs again.
+ last_stair = level_id::get_current_level_id();
+
+ // This is important, else we'll probably stop traveling
+ // the moment we clear the stairs. That's because the
+ // (run_x, run_y) destination will no longer be valid on
+ // the new level. Setting run_x to zero forces us to
+ // recalculate our travel target next turn (see previous
+ // if block).
+ you.run_x = you.run_y = 0;
+ }
+ else
+ {
+ you.running = runmode;
+ stop_running();
+ }
+ }
+ else
+ {
+ you.running = runmode;
+ stop_running();
+ }
+ }
+ else if (Options.travel_delay > 0)
+ delay(Options.travel_delay);
+ }
+
+ if (!you.running && Options.travel_delay == -1)
+ redraw_screen();
+}
+
+/*
+ * The travel algorithm is based on the NetHack travel code written by Warwick
+ * Allison - used with his permission.
+ */
+void find_travel_pos(int youx, int youy,
+ char *move_x, char *move_y,
+ std::vector<coord_def>* features)
+{
+ init_terrain_check();
+
+ int start_x = you.run_x, start_y = you.run_y;
+ int dest_x = youx, dest_y = youy;
+ bool floodout = false;
+#ifdef STASH_TRACKING
+ LevelStashes *lev = features? stashes.find_current_level() : NULL;
+#endif
+
+ // Normally we start from the destination and floodfill outwards, looking
+ // for the character's current position. If we're merely trying to populate
+ // the point_distance array (or exploring), we'll want to start from the
+ // character's current position and fill outwards
+ if (!move_x || !move_y)
+ {
+ start_x = youx;
+ start_y = youy;
+
+ dest_x = dest_y = -1;
+
+ floodout = true;
+ }
+
+ // Abort run if we're trying to go someplace evil
+ if (dest_x != -1 && !is_travel_ok(start_x, start_y, false) &&
+ !is_trap(start_x, start_y))
+ {
+ you.running = 0;
+ return ;
+ }
+
+ // Abort run if we're going nowhere.
+ if (start_x == dest_x && start_y == dest_y)
+ {
+ you.running = 0;
+ return ;
+ }
+
+ // How many points are we currently considering? We start off with just one
+ // point, and spread outwards like a flood-filler.
+ int points = 1;
+
+ // How many points we'll consider next iteration.
+ int next_iter_points = 0;
+
+ // How far we've traveled from (start_x, start_y), in moves (a diagonal move
+ // is no longer than an orthogonal move).
+ int traveled_distance = 1;
+
+ // Which index of the circumference array are we currently looking at?
+ int circ_index = 0;
+
+ // The circumference points of the floodfilled area, for this iteration
+ // and the next (this iteration's points being circ_index amd the next one's
+ // being !circ_index).
+ static FixedVector<coord_def, GXM * GYM> circumference[2];
+
+ // Coordinates of all discovered traps. If we're exploring instead of
+ // travelling, we'll reseed from these points after we've explored the map
+ std::vector<coord_def> trap_seeds;
+
+ // When set to true, the travel code ignores features, traps and hostile
+ // terrain, and simply tries to map contiguous floorspace. Will only be set
+ // to true if we're exploring, instead of travelling.
+ bool ignore_hostile = false;
+
+ // Set the seed point
+ circumference[circ_index][0].x = start_x;
+ circumference[circ_index][0].y = start_y;
+
+ // Zap out previous distances array
+ memset(point_distance, 0, sizeof point_distance);
+
+ for ( ; points > 0; ++traveled_distance, circ_index = !circ_index,
+ points = next_iter_points, next_iter_points = 0)
+ {
+ for (int i = 0; i < points; ++i)
+ {
+ int x = circumference[circ_index][i].x,
+ y = circumference[circ_index][i].y;
+
+ // (x,y) is a known (explored) location - we never put unknown
+ // points in the circumference vector, so we don't need to examine
+ // the map array, just the grid array.
+ unsigned char feature = grd[x][y];
+
+ // If this is a feature that'll take time to travel past, we
+ // simulate that extra turn by taking this feature next turn,
+ // thereby artificially increasing traveled_distance.
+ //
+ // Note: I don't know how slow walking through shallow water and
+ // opening closed doors is - right now it's considered to have
+ // the cost of two normal moves.
+ int feat_cost = feature_traverse_cost(feature);
+ if (feat_cost > 1
+ && point_distance[x][y] > traveled_distance - feat_cost)
+ {
+ circumference[!circ_index][next_iter_points].x = x;
+ circumference[!circ_index][next_iter_points].y = y;
+ next_iter_points++;
+ continue;
+ }
+
+ // For each point, we look at all surrounding points. Take them
+ // orthogonals first so that the travel path doesn't zigzag all over
+ // the map. Note the (dir = 1) is intentional assignment.
+ for (int dir = 0; dir < 8; (dir += 2) == 8 && (dir = 1))
+ {
+ int dx = x + Compass[dir].x, dy = y + Compass[dir].y;
+
+ if (dx <= 0 || dx >= GXM || dy <= 0 || dy >= GYM) continue;
+
+ unsigned char envf = env.map[dx - 1][dy - 1];
+
+ if (floodout && you.running == RUN_EXPLORE
+ && !is_player_mapped(envf))
+ {
+ // Setting run_x and run_y here is evil - this function
+ // should ideally not modify game state in any way.
+ you.run_x = x;
+ you.run_y = y;
+
+ return;
+ }
+
+ if ((dx != dest_x || dy != dest_y)
+ && !is_travel_ok(dx, dy, ignore_hostile))
+ {
+ // This point is not okay to travel on, but if this is a
+ // trap, we'll want to put it on the feature vector anyway.
+ if (is_reseedable(dx, dy)
+ && !point_distance[dx][dy]
+ && (dx != start_x || dy != start_y))
+ {
+ if (features)
+ {
+ coord_def c = { dx, dy };
+ if (is_trap(dx, dy) || is_exclude_root(dx, dy))
+ features->push_back(c);
+ trap_seeds.push_back(c);
+ }
+
+ // Appropriate mystic number. Nobody else should check
+ // this number, since this square is unsafe for travel.
+ point_distance[dx][dy] =
+ is_exclude_root(dx, dy)? PD_EXCLUDED :
+ is_excluded(dx, dy) ? PD_EXCLUDED_RADIUS :
+ PD_TRAP;
+ }
+ continue;
+ }
+
+ if (dx == dest_x && dy == dest_y)
+ {
+ // Hallelujah, we're home!
+ if (is_safe(x, y) && move_x && move_y)
+ {
+ *move_x = sgn(x - dest_x);
+ *move_y = sgn(y - dest_y);
+ }
+ return ;
+ }
+ else if (!point_distance[dx][dy])
+ {
+ // This point is going to be on the agenda for the next
+ // iteration
+ circumference[!circ_index][next_iter_points].x = dx;
+ circumference[!circ_index][next_iter_points].y = dy;
+ next_iter_points++;
+
+ point_distance[dx][dy] = traveled_distance;
+
+ // Negative distances here so that show_map can colour
+ // the map differently for these squares.
+ if (ignore_hostile)
+ {
+ point_distance[dx][dy] = -point_distance[dx][dy];
+ if (is_exclude_root(dx, dy))
+ point_distance[dx][dy] = PD_EXCLUDED;
+ else if (is_excluded(dx, dy))
+ point_distance[dx][dy] = PD_EXCLUDED_RADIUS;
+ }
+
+ unsigned char feature = grd[dx][dy];
+ if (features && !ignore_hostile
+ && ((feature != DNGN_FLOOR
+ && feature != DNGN_SHALLOW_WATER
+ && feature != DNGN_DEEP_WATER
+ && feature != DNGN_LAVA)
+ || is_waypoint(dx, dy)
+#ifdef STASH_TRACKING
+ || is_stash(lev, dx, dy)
+#endif
+ )
+ && (dx != start_x || dy != start_y))
+ {
+ coord_def c = { dx, dy };
+ features->push_back(c);
+ }
+
+ if (features && is_exclude_root(dx, dy) && dx != start_x
+ && dy != start_y)
+ {
+ coord_def c = { dx, dy };
+ features->push_back(c);
+ }
+ }
+ } // for (dir = 0; dir < 8 ...
+ } // for (i = 0; i < points ...
+
+ if (!next_iter_points && features && !move_x && !ignore_hostile
+ && trap_seeds.size())
+ {
+ // Reseed here
+ for (unsigned i = 0; i < trap_seeds.size(); ++i)
+ circumference[!circ_index][i] = trap_seeds[i];
+ next_iter_points = trap_seeds.size();
+ ignore_hostile = true;
+ }
+ } // for ( ; points > 0 ...
+}
+
+// Mappings of which branches spring from which other branches, essential to
+// walk backwards up the tree. Yes, this is a horrible abuse of coord_def.
+static coord_def branch_backout[] =
+{
+ { BRANCH_SWAMP, BRANCH_LAIR },
+ { BRANCH_SLIME_PITS, BRANCH_LAIR },
+ { BRANCH_SNAKE_PIT, BRANCH_LAIR },
+
+ { BRANCH_HALL_OF_BLADES, BRANCH_VAULTS },
+ { BRANCH_CRYPT, BRANCH_VAULTS },
+
+ { BRANCH_TOMB, BRANCH_CRYPT },
+
+ { BRANCH_ELVEN_HALLS, BRANCH_ORCISH_MINES },
+
+ { BRANCH_ORCISH_MINES, BRANCH_MAIN_DUNGEON },
+ { BRANCH_HIVE, BRANCH_MAIN_DUNGEON },
+ { BRANCH_LAIR, BRANCH_MAIN_DUNGEON },
+ { BRANCH_VAULTS, BRANCH_MAIN_DUNGEON },
+ { BRANCH_HALL_OF_ZOT, BRANCH_MAIN_DUNGEON },
+ { BRANCH_ECUMENICAL_TEMPLE, BRANCH_MAIN_DUNGEON },
+};
+
+/*
+ * Given a branch id, returns the parent branch. If the branch id is not found,
+ * returns BRANCH_MAIN_DUNGEON.
+ */
+unsigned char find_parent_branch(unsigned char br)
+{
+ for (unsigned i = 0;
+ i < sizeof(branch_backout) / sizeof(branch_backout[0]);
+ i++)
+ {
+ if (branch_backout[i].x == br)
+ return (unsigned char) branch_backout[i].y;
+ }
+ return 0;
+}
+
+extern FixedVector<char, MAX_BRANCHES> stair_level;
+
+void find_parent_branch(unsigned char br, int depth,
+ unsigned char *pb, int *pd)
+{
+ int lev = stair_level[br];
+ if (lev <= 0)
+ {
+ *pb = 0;
+ *pd = 0; // Check depth before using *pb.
+ return ;
+ }
+
+ *pb = find_parent_branch(br);
+ *pd = subdungeon_depth(*pb, lev);
+}
+
+// Appends the passed in branch/depth to the given vector, then attempts to
+// repeat the operation with the parent branch of the given branch.
+//
+// As an example of what it does, assume this dungeon structure
+// Stairs to lair on D:11
+// Stairs to snake pit on lair:5
+//
+// If level 3 of the snake pit is the level we want to track back from,
+// we'd call trackback(vec, BRANCH_SNAKE_PIT, 3), and the resulting vector will
+// look like:
+// { BRANCH_SNAKE_PIT, 3 }, { BRANCH_LAIR, 5 }, { BRANCH_MAIN_DUNGEON, 11 }
+// (Assuming, of course, that the vector started out empty.)
+//
+void trackback(std::vector<level_id> &vec,
+ unsigned char branch, int subdepth)
+{
+ if (subdepth < 1 || subdepth > MAX_LEVELS) return;
+
+ level_id lid( branch, subdepth );
+ vec.push_back(lid);
+
+ if (branch != BRANCH_MAIN_DUNGEON)
+ {
+ unsigned char pb;
+ int pd;
+ find_parent_branch(branch, subdepth, &pb, &pd);
+ if (pd)
+ trackback(vec, pb, pd);
+ }
+}
+
+void track_intersect(std::vector<level_id> &cur,
+ std::vector<level_id> &targ,
+ level_id *cx)
+{
+ cx->branch = 0;
+ cx->depth = -1;
+
+ int us = cur.size() - 1, them = targ.size() - 1;
+
+ for ( ; us >= 0 && them >= 0; us--, them--)
+ {
+ if (cur[us].branch != targ[them].branch)
+ break;
+ }
+
+ us++, them++;
+
+ if (us < (int) cur.size() && them < (int) targ.size() && us >= 0 &&
+ them >= 0)
+ *cx = targ[them];
+}
+
+/*
+ * Returns the number of stairs the player would need to take to go from
+ * the 'first' level to the 'second' level. If there's no obvious route between
+ * 'first' and 'second', returns -1. If first == second, returns 0.
+ */
+int level_distance(level_id first, level_id second)
+{
+ if (first == second) return 0;
+
+ std::vector<level_id> fv, sv;
+
+ // If in the same branch, easy.
+ if (first.branch == second.branch)
+ return abs(first.depth - second.depth);
+
+ // Figure out the dungeon structure between the two levels.
+ trackback(fv, first.branch, first.depth);
+ trackback(sv, second.branch, second.depth);
+
+ level_id intersect;
+ track_intersect(fv, sv, &intersect);
+
+ if (intersect.depth == -1) // No common ground?
+ return -1;
+
+ int distance = 0;
+ // If the common branch is not the same as the current branch, we'll
+ // have to walk up the branch tree until we get to the common branch.
+ while (first.branch != intersect.branch)
+ {
+ distance += first.depth;
+
+ find_parent_branch(first.branch, first.depth,
+ &first.branch, &first.depth);
+ if (!first.depth)
+ return -1;
+ }
+
+ // Now first.branch == intersect.branch
+ distance += abs(first.depth - intersect.depth);
+
+ bool ignore_end = true;
+ for (int i = sv.size() - 1; i >= 0; --i)
+ {
+ if (ignore_end)
+ {
+ if (sv[i].branch == intersect.branch)
+ ignore_end = false;
+ continue;
+ }
+ distance += sv[i].depth;
+ }
+ return distance;
+}
+
+static struct
+{
+ const char *branch_name, *full_name;
+ char hotkey;
+} branches [] =
+{
+ { "Dungeon", "the Main Dungeon", 'D' },
+ { "Dis", "Dis", 'I' },
+ { "Gehenna", "Gehenna", 'W' },
+ { "Hell", "Hell", 'U' },
+ { "Cocytus", "Cocytus", 'X' },
+ { "Tartarus", "Tartarus", 'Y' },
+ { "Inferno", "", 'R' },
+ { "The Pit", "", '0' },
+ { "------------", "", '-' },
+ { "------------", "", '-' },
+ { "Orcish Mines", "the Orcish Mines", 'O' },
+ { "Hive", "the Hive", 'H' },
+ { "Lair", "the Lair", 'L' },
+ { "Slime Pits", "the Slime Pits", 'M' },
+ { "Vaults", "the Vaults", 'V' },
+ { "Crypt", "the Crypt", 'C' },
+ { "Hall of Blades", "the Hall of Blades", 'B' },
+ { "Zot", "the Realm of Zot", 'Z' },
+ { "Temple", "the Ecumenical Temple", 'T' },
+ { "Snake Pit", "the Snake Pit", 'P' },
+ { "Elven Halls", "the Elven Halls", 'E' },
+ { "Tomb", "the Tomb", 'G' },
+ { "Swamp", "the Swamp", 'S' }
+};
+
+static struct
+{
+ const char *abbr;
+ unsigned char branch;
+} branch_abbrvs[] =
+{
+ { "D", BRANCH_MAIN_DUNGEON },
+ { "Dis", BRANCH_DIS },
+ { "Geh", BRANCH_GEHENNA },
+ { "Hell", BRANCH_VESTIBULE_OF_HELL },
+ { "Coc", BRANCH_COCYTUS },
+ { "Tar", BRANCH_TARTARUS },
+ { "inf", BRANCH_INFERNO },
+ { "pit", BRANCH_THE_PIT },
+ { "Orc", BRANCH_ORCISH_MINES },
+ { "Hive", BRANCH_HIVE },
+ { "Lair", BRANCH_LAIR },
+ { "Slime", BRANCH_SLIME_PITS },
+ { "Vault", BRANCH_VAULTS },
+ { "Crypt", BRANCH_CRYPT },
+ { "Blades", BRANCH_HALL_OF_BLADES },
+ { "Zot", BRANCH_HALL_OF_ZOT },
+ { "Temple", BRANCH_ECUMENICAL_TEMPLE },
+ { "Snake", BRANCH_SNAKE_PIT },
+ { "Elf", BRANCH_ELVEN_HALLS },
+ { "Tomb", BRANCH_TOMB },
+ { "Swamp", BRANCH_SWAMP },
+};
+
+void set_trans_travel_dest(char *buffer, int maxlen, const level_pos &target)
+{
+ if (!buffer) return;
+
+ const char *branch = NULL;
+ for (unsigned i = 0; i < sizeof(branch_abbrvs) / sizeof(branch_abbrvs[0]);
+ ++i)
+ {
+ if (branch_abbrvs[i].branch == target.id.branch)
+ {
+ branch = branch_abbrvs[i].abbr;
+ break;
+ }
+ }
+
+ if (!branch) return;
+
+ unsigned char branch_id = target.id.branch;
+ // Show level+depth information and tack on an @(x,y) if the player
+ // wants to go to a specific square on the target level. We don't use
+ // actual coordinates since that will give away level information we
+ // don't want the player to have.
+ if (branch_id != BRANCH_ECUMENICAL_TEMPLE
+ && branch_id != BRANCH_HALL_OF_BLADES
+ && branch_id != BRANCH_VESTIBULE_OF_HELL)
+ snprintf(buffer, maxlen, "%s:%d%s", branch, target.id.depth,
+ target.pos.x != -1? " @ (x,y)" : "");
+ else
+ snprintf(buffer, maxlen, "%s%s", branch,
+ target.pos.x != -1? " @ (x,y)" : "");
+}
+
+// Returns the level on the given branch that's closest to the player's
+// current location.
+static int get_nearest_level_depth(unsigned char branch)
+{
+ int depth = 1;
+
+ // Hell needs special treatment, because we can't walk up
+ // Hell and its branches to the main dungeon.
+ if (branch == BRANCH_MAIN_DUNGEON &&
+ (player_in_branch( BRANCH_VESTIBULE_OF_HELL ) ||
+ player_in_branch( BRANCH_COCYTUS ) ||
+ player_in_branch( BRANCH_TARTARUS ) ||
+ player_in_branch( BRANCH_DIS ) ||
+ player_in_branch( BRANCH_GEHENNA )))
+ return you.hell_exit + 1;
+
+ level_id id = level_id::get_current_level_id();
+ do
+ {
+ find_parent_branch(id.branch, id.depth,
+ &id.branch, &id.depth);
+ if (id.depth && id.branch == branch)
+ {
+ depth = id.depth;
+ break;
+ }
+ } while (id.depth);
+
+ return depth;
+}
+
+static char trans_travel_dest[30];
+
+// Returns true if the player character knows of the existence of the given
+// branch (which would make the branch a valid target for interlevel travel).
+static bool is_known_branch(unsigned char branch)
+{
+ // The main dungeon is always known.
+ if (branch == BRANCH_MAIN_DUNGEON) return true;
+
+ // If we're in the branch, it darn well is known.
+ if (you.where_are_you == branch) return true;
+
+ // If the overmap knows the stairs to this branch, we know the branch.
+ unsigned char par;
+ int pdep;
+ find_parent_branch(branch, 1, &par, &pdep);
+ if (pdep)
+ return true;
+
+ // Do a brute force search in the travel cache for this branch.
+ return travel_cache.is_known_branch(branch);
+
+ // Doing this to check for the reachability of a branch is slow enough to
+ // be noticeable.
+
+ // Ask interlevel travel if it knows how to go there. If it knows how to
+ // get (partway) there, return true.
+
+ // level_pos pos;
+ // pos.id.branch = branch;
+ // pos.id.depth = 1;
+
+ // return find_transtravel_square(pos, false) != 0;
+}
+
+/*
+ * Returns a list of the branches that the player knows the location of the
+ * stairs to, in the same order as overmap.cc lists them.
+ */
+static std::vector<unsigned char> get_known_branches()
+{
+ // Lifted from overmap.cc. XXX: Move this to a better place?
+ const unsigned char list_order[] =
+ {
+ BRANCH_MAIN_DUNGEON,
+ BRANCH_ECUMENICAL_TEMPLE,
+ BRANCH_ORCISH_MINES, BRANCH_ELVEN_HALLS,
+ BRANCH_LAIR, BRANCH_SWAMP, BRANCH_SLIME_PITS, BRANCH_SNAKE_PIT,
+ BRANCH_HIVE,
+ BRANCH_VAULTS, BRANCH_HALL_OF_BLADES, BRANCH_CRYPT, BRANCH_TOMB,
+ BRANCH_VESTIBULE_OF_HELL,
+ BRANCH_DIS, BRANCH_GEHENNA, BRANCH_COCYTUS, BRANCH_TARTARUS,
+ BRANCH_HALL_OF_ZOT
+ };
+
+ std::vector<unsigned char> branches;
+ for (unsigned i = 0; i < sizeof list_order / sizeof(*list_order); ++i)
+ {
+ if (is_known_branch(list_order[i]))
+ branches.push_back(list_order[i]);
+ }
+
+ return branches;
+}
+
+static int prompt_travel_branch()
+{
+ unsigned char branch = BRANCH_MAIN_DUNGEON; // Default
+ std::vector<unsigned char> br = get_known_branches();
+
+ // Don't kill the prompt even if the only branch we know is the main dungeon
+ // This keeps things consistent for the player.
+ if (br.size() < 1) return branch;
+
+ bool waypoint_list = false;
+ int waycount = travel_cache.get_waypoint_count();
+ for ( ; ; )
+ {
+ char buf[100];
+ if (waypoint_list)
+ travel_cache.list_waypoints();
+ else
+ {
+ int linec = 0;
+ std::string line;
+ for (int i = 0, count = br.size(); i < count; ++i, ++linec)
+ {
+ if (linec == 4)
+ {
+ linec = 0;
+ mpr(line.c_str());
+ line = "";
+ }
+ snprintf(buf, sizeof buf, "(%c) %-14s ", branches[br[i]].hotkey,
+ branches[br[i]].branch_name);
+ line += buf;
+ }
+ if (line.length())
+ mpr(line.c_str());
+ }
+
+ char shortcuts[100];
+ *shortcuts = 0;
+ if (*trans_travel_dest || waycount || waypoint_list)
+ {
+ strncpy(shortcuts, "(", sizeof shortcuts);
+ if (waypoint_list)
+ strncat(shortcuts, "[*] lists branches", sizeof shortcuts);
+ else if (waycount)
+ strncat(shortcuts, "[*] lists waypoints", sizeof shortcuts);
+
+ if (*trans_travel_dest)
+ {
+ char travel_dest[60];
+ snprintf(travel_dest, sizeof travel_dest, "[Enter] for %s",
+ trans_travel_dest);
+ if (waypoint_list || waycount)
+ strncat( shortcuts, ", ", sizeof shortcuts);
+ strncat(shortcuts, travel_dest, sizeof shortcuts);
+ }
+ strncat(shortcuts, ") ", sizeof shortcuts);
+ }
+ snprintf(buf, sizeof buf, "Where do you want to go? %s", shortcuts);
+ mpr(buf, MSGCH_PROMPT);
+
+ int keyin = get_ch();
+ switch (keyin)
+ {
+ case ESCAPE:
+ return (ID_CANCEL);
+ case '\n': case '\r':
+ return (ID_REPEAT);
+ case '<':
+ return (ID_UP);
+ case '>':
+ return (ID_DOWN);
+ case '*':
+ if (waypoint_list || waycount)
+ {
+ waypoint_list = !waypoint_list;
+ mesclr();
+ continue;
+ }
+ break;
+ default:
+ // Is this a branch hotkey?
+ for (int i = 0, count = br.size(); i < count; ++i)
+ {
+ if (toupper(keyin) == branches[br[i]].hotkey)
+ return (br[i]);
+ }
+
+ // Possibly a waypoint number?
+ if (keyin >= '0' && keyin <= '9')
+ return (-1 - (keyin - '0'));
+ return (ID_CANCEL);
+ }
+ }
+}
+
+static int prompt_travel_depth(unsigned char branch)
+{
+ // Handle one-level branches by not prompting.
+ if (branch == BRANCH_ECUMENICAL_TEMPLE ||
+ branch == BRANCH_VESTIBULE_OF_HELL ||
+ branch == BRANCH_HALL_OF_BLADES)
+ return 1;
+
+ char buf[100];
+ int depth = get_nearest_level_depth(branch);
+
+ snprintf(buf, sizeof buf, "What level of %s do you want to go to? "
+ "[default %d] ", branches[branch].full_name, depth);
+ mesclr();
+ mpr(buf, MSGCH_PROMPT);
+
+ if (!cancelable_get_line( buf, sizeof buf ))
+ return 0;
+
+ if (*buf)
+ depth = atoi(buf);
+
+ return depth;
+}
+
+static bool is_hell_branch(int branch)
+{
+ return branch == BRANCH_DIS || branch == BRANCH_TARTARUS
+ || branch == BRANCH_COCYTUS || branch == BRANCH_GEHENNA;
+
+}
+
+static level_pos find_up_level()
+{
+ level_id curr = level_id::get_current_level_id();
+ curr.depth--;
+
+ if (is_hell_branch(curr.branch))
+ {
+ curr.branch = BRANCH_VESTIBULE_OF_HELL;
+ curr.depth = 1;
+ return (curr);
+ }
+
+ if (curr.depth < 1)
+ {
+ if (curr.branch != BRANCH_MAIN_DUNGEON)
+ {
+ level_id parent;
+ find_parent_branch(curr.branch, curr.depth,
+ &parent.branch, &parent.depth);
+ if (parent.depth > 0)
+ return (parent);
+ else if (curr.branch == BRANCH_VESTIBULE_OF_HELL)
+ {
+ parent.branch = BRANCH_MAIN_DUNGEON;
+ parent.depth = you.hell_exit + 1;
+ return (parent);
+ }
+ }
+ return level_pos();
+ }
+
+ return (curr);
+}
+
+static level_pos find_down_level()
+{
+ level_id curr = level_id::get_current_level_id();
+ curr.depth++;
+ return (curr);
+}
+
+static level_pos prompt_translevel_target()
+{
+ level_pos target;
+ int branch = prompt_travel_branch();
+
+ if (branch == ID_CANCEL)
+ return (target);
+
+ // If user chose to repeat last travel, return that.
+ if (branch == ID_REPEAT)
+ return (travel_target);
+
+ if (branch == ID_UP)
+ {
+ target = find_up_level();
+ if (target.id.depth > -1)
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ target);
+ return (target);
+ }
+
+ if (branch == ID_DOWN)
+ {
+ target = find_down_level();
+ if (target.id.depth > -1)
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ target);
+ return (target);
+ }
+
+ if (branch < 0)
+ {
+ travel_cache.travel_to_waypoint(-branch - 1);
+ return target;
+ }
+
+ target.id.branch = branch;
+
+ // User's chosen a branch, so now we ask for a level.
+ target.id.depth = prompt_travel_depth(target.id.branch);
+
+ if (target.id.depth < 1 || target.id.depth >= MAX_LEVELS)
+ target.id.depth = -1;
+
+ if (target.id.depth > -1)
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ target);
+
+ return target;
+}
+
+void start_translevel_travel(const level_pos &pos)
+{
+ travel_target = pos;
+
+ if (pos.id != level_id::get_current_level_id())
+ {
+ if (!loadlev_populate_stair_distances(pos))
+ {
+ mpr("Level memory is imperfect, aborting.");
+ return ;
+ }
+ }
+ else
+ populate_stair_distances(pos);
+
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ travel_target);
+ start_translevel_travel(false);
+}
+
+void start_translevel_travel(bool prompt_for_destination)
+{
+ // Update information for this level. We need it even for the prompts, so
+ // we can't wait to confirm that the user chose to initiate travel.
+ travel_cache.get_level_info(level_id::get_current_level_id()).update();
+
+ if (prompt_for_destination)
+ {
+ // prompt_translevel_target may actually initiate travel directly if
+ // the user chose a waypoint instead of a branch + depth. As far as
+ // we're concerned, if the target depth is unset, we need to take no
+ // further action.
+ level_pos target = prompt_translevel_target();
+ if (target.id.depth == -1) return;
+
+ travel_target = target;
+ }
+
+ if (level_id::get_current_level_id() == travel_target.id &&
+ (travel_target.pos.x == -1 ||
+ (travel_target.pos.x == you.x_pos &&
+ travel_target.pos.y == you.y_pos)))
+ {
+ mpr("You're already here!");
+ return ;
+ }
+
+ if (travel_target.id.depth > -1)
+ {
+ you.running = RUN_INTERLEVEL;
+ you.run_x = you.run_y = 0;
+ last_stair.depth = -1;
+ start_running();
+ }
+}
+
+int stair_direction(int stair)
+{
+ return ((stair < DNGN_STONE_STAIRS_UP_I
+ || stair > DNGN_ROCK_STAIRS_UP)
+ && (stair < DNGN_RETURN_FROM_ORCISH_MINES
+ || stair > DNGN_RETURN_FROM_SWAMP))
+ ? '>' : '<';
+}
+
+int trans_negotiate_stairs()
+{
+ return stair_direction(grd[you.x_pos][you.y_pos]);
+}
+
+int absdungeon_depth(unsigned char branch, int subdepth)
+{
+ int realdepth = subdepth - 1;
+
+ if (branch >= BRANCH_ORCISH_MINES && branch <= BRANCH_SWAMP)
+ realdepth = subdepth + you.branch_stairs[branch - 10];
+
+ if (branch >= BRANCH_DIS && branch <= BRANCH_THE_PIT)
+ realdepth = subdepth + 26;
+
+ return realdepth;
+}
+
+int subdungeon_depth(unsigned char branch, int depth)
+{
+ int curr_subdungeon_level = depth + 1;
+
+ // maybe last part better expresssed as <= PIT {dlb}
+ if (branch >= BRANCH_DIS && branch <= BRANCH_THE_PIT)
+ curr_subdungeon_level = depth - 26;
+
+ /* Remember, must add this to the death_string in ouch */
+ if (branch >= BRANCH_ORCISH_MINES && branch <= BRANCH_SWAMP)
+ curr_subdungeon_level = depth
+ - you.branch_stairs[branch - 10];
+
+ return curr_subdungeon_level;
+}
+
+static int target_distance_from(const coord_def &pos)
+{
+ for (int i = 0, count = curr_stairs.size(); i < count; ++i)
+ if (curr_stairs[i].position == pos)
+ return curr_stairs[i].distance;
+ return -1;
+}
+
+/*
+ * Sets best_stair to the coordinates of the best stair on the player's current
+ * level to take to get to the 'target' level. Should be called with 'distance'
+ * set to 0, 'stair' set to (you.x_pos, you.y_pos) and 'best_distance' set to
+ * -1. 'cur' should be the player's current level.
+ *
+ * If best_stair remains unchanged when this function returns, there is no
+ * travel-safe path between the player's current level and the target level OR
+ * the player's current level *is* the target level.
+ *
+ * This function relies on the point_distance array being correctly populated
+ * with a floodout call to find_travel_pos starting from the player's location.
+ */
+static int find_transtravel_stair( const level_id &cur,
+ const level_pos &target,
+ int distance,
+ const coord_def &stair,
+ level_id &closest_level,
+ int &best_level_distance,
+ coord_def &best_stair)
+{
+ int local_distance = -1;
+ level_id player_level = level_id::get_current_level_id();
+
+ // Have we reached the target level?
+ if (cur == target.id)
+ {
+ // If there's no target position on the target level, or we're on the
+ // target, we're home.
+ if (target.pos.x == -1 || target.pos == stair)
+ return distance;
+
+ // If there *is* a target position, we need to work out our distance
+ // from it.
+ int deltadist = target_distance_from(stair);
+
+ if (deltadist == -1 && cur == player_level)
+ {
+ // Okay, we don't seem to have a distance available to us, which
+ // means we're either (a) not standing on stairs or (b) whoever
+ // initiated interlevel travel didn't call
+ // populate_stair_distances. Assuming we're not on stairs, that
+ // situation can arise only if interlevel travel has been triggered
+ // for a location on the same level. If that's the case, we can get
+ // the distance off the point_distance matrix.
+ deltadist = point_distance[target.pos.x][target.pos.y];
+ if (!deltadist &&
+ (stair.x != target.pos.x || stair.y != target.pos.y))
+ deltadist = -1;
+ }
+
+ if (deltadist != -1)
+ {
+ local_distance = distance + deltadist;
+
+ // See if this is a degenerate case of interlevel travel:
+ // A degenerate case of interlevel travel decays to normal travel;
+ // we identify this by checking if:
+ // a. The current level is the target level.
+ // b. The target square is reachable from the 'current' square.
+ // c. The current square is where the player is.
+ //
+ // Note that even if this *is* degenerate, interlevel travel may
+ // still be able to find a shorter route, since it can consider
+ // routes that leave and reenter the current level.
+ if (player_level == target.id && stair.x == you.x_pos
+ && stair.y == you.y_pos)
+ best_stair = target.pos;
+
+ // The local_distance is already set, but there may actually be
+ // stairs we can take that'll get us to the target faster than the
+ // direct route, so we also try the stairs.
+ }
+ }
+
+ LevelInfo &li = travel_cache.get_level_info(cur);
+ std::vector<stair_info> &stairs = li.get_stairs();
+
+ // this_stair being NULL is perfectly acceptable, since we start with
+ // coords as the player coords, and the player need not be standing on
+ // stairs.
+ stair_info *this_stair = li.get_stair(stair);
+
+ if (!this_stair && cur != player_level)
+ {
+ // Whoops, there's no stair in the travel cache for the current
+ // position, and we're not on the player's current level (i.e., there
+ // certainly *should* be a stair here). Since we can't proceed in any
+ // reasonable way, bail out.
+ return local_distance;
+ }
+
+ for (int i = 0, count = stairs.size(); i < count; ++i)
+ {
+ stair_info &si = stairs[i];
+
+ int deltadist = li.distance_between(this_stair, &si);
+ if (!this_stair)
+ {
+ deltadist = point_distance[si.position.x][si.position.y];
+ if (!deltadist &&
+ (you.x_pos != si.position.x || you.y_pos != si.position.y))
+ deltadist = -1;
+ }
+
+ // deltadist == 0 is legal (if this_stair is NULL), since the player
+ // may be standing on the stairs. If two stairs are disconnected,
+ // deltadist has to be negative.
+ if (deltadist < 0) continue;
+
+ int dist2stair = distance + deltadist;
+ if (si.distance == -1 || si.distance > dist2stair)
+ {
+ si.distance = dist2stair;
+
+ dist2stair += Options.travel_stair_cost;
+ ++dist2stair; // Account for the cost of taking the stairs
+
+ // Already too expensive? Short-circuit.
+ if (local_distance != -1 && dist2stair >= local_distance)
+ continue;
+
+ const level_pos &dest = si.destination;
+
+ // We can only short-circuit the stair-following process if we
+ // have no exact target location. If there *is* an exact target
+ // location, we can't follow stairs for which we have incomplete
+ // information.
+ if (target.pos.x == -1 && dest.id == target.id)
+ {
+ if (local_distance == -1 || local_distance > dist2stair)
+ {
+ local_distance = dist2stair;
+ if (cur == player_level && you.x_pos == stair.x &&
+ you.y_pos == stair.y)
+ best_stair = si.position;
+ }
+ continue;
+ }
+
+ if (dest.id.depth > -1) { // We have a valid level descriptor.
+ int dist = level_distance(dest.id, target.id);
+ if (dist != -1 &&
+ (dist < best_level_distance ||
+ best_level_distance == -1))
+ {
+ best_level_distance = dist;
+ closest_level = dest.id;
+ }
+ }
+
+ // If we don't know where these stairs go, we can't take them.
+ if (!dest.is_valid()) continue;
+
+ // We need to get the stairs at the new location and set the
+ // distance on them as well.
+ LevelInfo &lo = travel_cache.get_level_info(dest.id);
+ stair_info *so = lo.get_stair(dest.pos);
+
+ if (so)
+ {
+ if (so->distance == -1 || so->distance > dist2stair)
+ so->distance = dist2stair;
+ else
+ continue; // We've already been here.
+ }
+
+ // Okay, take these stairs and keep going.
+ int newdist = find_transtravel_stair(dest.id, target,
+ dist2stair, dest.pos, closest_level,
+ best_level_distance, best_stair);
+ if (newdist != -1 &&
+ (local_distance == -1 || local_distance > newdist))
+ {
+ local_distance = newdist;
+ if (cur == player_level && you.x_pos == stair.x &&
+ you.y_pos == stair.y)
+ best_stair = si.position;
+ }
+ }
+ }
+ return local_distance;
+}
+
+static bool loadlev_populate_stair_distances(const level_pos &target)
+{
+ crawl_environment tmp = env;
+ if (!travel_load_map(target.id.branch,
+ absdungeon_depth(target.id.branch, target.id.depth)))
+ {
+ env = tmp;
+ return false;
+ }
+
+ std::vector<coord_def> old_excludes = curr_excludes;
+
+ curr_excludes.clear();
+ LevelInfo &li = travel_cache.get_level_info(target.id);
+ li.set_level_excludes();
+
+ populate_stair_distances(target);
+
+ env = tmp;
+ curr_excludes = old_excludes;
+ return !curr_stairs.empty();
+}
+
+static void populate_stair_distances(const level_pos &target)
+{
+ // Populate point_distance.
+ find_travel_pos(target.pos.x, target.pos.y, NULL, NULL, NULL);
+
+ LevelInfo &li = travel_cache.get_level_info(target.id);
+ const std::vector<stair_info> &stairs = li.get_stairs();
+
+ curr_stairs.clear();
+ for (int i = 0, count = stairs.size(); i < count; ++i)
+ {
+ stair_info si = stairs[i];
+ si.distance = point_distance[si.position.x][si.position.y];
+ if (!si.distance && target.pos != si.position)
+ si.distance = -1;
+ if (si.distance < -1)
+ si.distance = -1;
+
+ curr_stairs.push_back(si);
+ }
+}
+
+static int find_transtravel_square(const level_pos &target, bool verbose)
+{
+ level_id current = level_id::get_current_level_id();
+
+ coord_def best_stair = { -1, -1 };
+ coord_def cur_stair = { you.x_pos, you.y_pos };
+ level_id closest_level;
+ int best_level_distance = -1;
+ travel_cache.reset_distances();
+
+ find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, NULL);
+
+ find_transtravel_stair(current, target,
+ 0, cur_stair, closest_level,
+ best_level_distance, best_stair);
+
+ if (best_stair.x != -1 && best_stair.y != -1)
+ {
+ you.run_x = best_stair.x;
+ you.run_y = best_stair.y;
+ return 1;
+ }
+ else if (best_level_distance != -1 && closest_level != current
+ && target.pos.x == -1)
+ {
+ int current_dist = level_distance(current, target.id);
+ level_pos newlev;
+ newlev.id = closest_level;
+ if (current_dist == -1 || best_level_distance < current_dist)
+ return find_transtravel_square(newlev, verbose);
+ }
+
+ if (verbose && target.id != current)
+ mpr("Sorry, I don't know how to get there.");
+ return 0;
+}
+
+void start_travel(int x, int y)
+{
+ // Redundant target?
+ if (x == you.x_pos && y == you.y_pos) return ;
+
+ // Start running
+ you.running = RUN_TRAVEL;
+ you.run_x = x;
+ you.run_y = y;
+
+ // Remember where we're going so we can easily go back if interrupted.
+ you.travel_x = x;
+ you.travel_y = y;
+
+ // Check whether we can get to the square.
+ find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, NULL);
+
+ if (point_distance[x][y] == 0 &&
+ (x != you.x_pos || you.run_y != you.y_pos) &&
+ is_travel_ok(x, y, false))
+ {
+ // We'll need interlevel travel to get here.
+ travel_target.id = level_id::get_current_level_id();
+ travel_target.pos.x = x;
+ travel_target.pos.y = y;
+
+ you.running = RUN_INTERLEVEL;
+ you.run_x = you.run_y = 0;
+ last_stair.depth = -1;
+
+ // We need the distance of the target from the various stairs around.
+ populate_stair_distances(travel_target);
+
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ travel_target);
+ }
+
+ start_running();
+}
+
+void start_explore()
+{
+ you.running = RUN_EXPLORE;
+ if (Options.explore_stop)
+ {
+ // Clone shadow array off map
+ copy(env.map, mapshadow);
+ }
+ start_running();
+}
+
+/*
+ * Given a feature vector, arranges the features in the order that the player
+ * is most likely to be interested in. Currently, the only thing it does is to
+ * put altars of the player's religion at the front of the list.
+ */
+void arrange_features(std::vector<coord_def> &features)
+{
+ for (int i = 0, count = features.size(); i < count; ++i)
+ {
+ if (is_player_altar(features[i]))
+ {
+ int place = i;
+ // Shuffle this altar as far up the list as possible.
+ for (int j = place - 1; j >= 0; --j)
+ {
+ if (is_altar(features[j]))
+ {
+ if (is_player_altar(features[j]))
+ break;
+
+ coord_def temp = features[j];
+ features[j] = features[place];
+ features[place] = temp;
+
+ place = j;
+ }
+ }
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Interlevel travel classes
+
+static void writeCoord(FILE *file, const coord_def &pos)
+{
+ writeShort(file, pos.x);
+ writeShort(file, pos.y);
+}
+
+static void readCoord(FILE *file, coord_def &pos)
+{
+ pos.x = readShort(file);
+ pos.y = readShort(file);
+}
+
+level_id level_id::get_current_level_id()
+{
+ level_id id;
+ id.branch = you.where_are_you;
+ id.depth = subdungeon_depth(you.where_are_you, you.your_level);
+
+ return id;
+}
+
+level_id level_id::get_next_level_id(const coord_def &pos)
+{
+ short gridc = grd[pos.x][pos.y];
+ level_id id = get_current_level_id();
+
+ switch (gridc)
+ {
+ case DNGN_STONE_STAIRS_DOWN_I: case DNGN_STONE_STAIRS_DOWN_II:
+ case DNGN_STONE_STAIRS_DOWN_III: case DNGN_ROCK_STAIRS_DOWN:
+ id.depth++;
+ break;
+ case DNGN_STONE_STAIRS_UP_I: case DNGN_STONE_STAIRS_UP_II:
+ case DNGN_STONE_STAIRS_UP_III: case DNGN_ROCK_STAIRS_UP:
+ id.depth--;
+ break;
+ case DNGN_ENTER_HELL:
+ id.branch = BRANCH_VESTIBULE_OF_HELL;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_DIS:
+ id.branch = BRANCH_DIS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_GEHENNA:
+ id.branch = BRANCH_GEHENNA;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_COCYTUS:
+ id.branch = BRANCH_COCYTUS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_TARTARUS:
+ id.branch = BRANCH_TARTARUS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_ORCISH_MINES:
+ id.branch = BRANCH_ORCISH_MINES;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_HIVE:
+ id.branch = BRANCH_HIVE;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_LAIR:
+ id.branch = BRANCH_LAIR;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_SLIME_PITS:
+ id.branch = BRANCH_SLIME_PITS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_VAULTS:
+ id.branch = BRANCH_VAULTS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_CRYPT:
+ id.branch = BRANCH_CRYPT;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_HALL_OF_BLADES:
+ id.branch = BRANCH_HALL_OF_BLADES;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_ZOT:
+ id.branch = BRANCH_HALL_OF_ZOT;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_TEMPLE:
+ id.branch = BRANCH_ECUMENICAL_TEMPLE;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_SNAKE_PIT:
+ id.branch = BRANCH_SNAKE_PIT;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_ELVEN_HALLS:
+ id.branch = BRANCH_ELVEN_HALLS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_TOMB:
+ id.branch = BRANCH_TOMB;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_SWAMP:
+ id.branch = BRANCH_SWAMP;
+ id.depth = 1;
+ break;
+ case DNGN_RETURN_FROM_ORCISH_MINES: case DNGN_RETURN_FROM_HIVE:
+ case DNGN_RETURN_FROM_LAIR: case DNGN_RETURN_FROM_SLIME_PITS:
+ case DNGN_RETURN_FROM_VAULTS: case DNGN_RETURN_FROM_CRYPT:
+ case DNGN_RETURN_FROM_HALL_OF_BLADES: case DNGN_RETURN_FROM_ZOT:
+ case DNGN_RETURN_FROM_TEMPLE: case DNGN_RETURN_FROM_SNAKE_PIT:
+ case DNGN_RETURN_FROM_ELVEN_HALLS: case DNGN_RETURN_FROM_TOMB:
+ case DNGN_RETURN_FROM_SWAMP:
+ find_parent_branch(id.branch, id.depth, &id.branch, &id.depth);
+ if (!id.depth)
+ {
+ id.branch = find_parent_branch(you.where_are_you);
+ id.depth = -1;
+ }
+ break;
+ }
+ return id;
+}
+
+void level_id::save(FILE *file) const
+{
+ writeByte(file, branch);
+ writeShort(file, depth);
+}
+
+void level_id::load(FILE *file)
+{
+ branch = readByte(file);
+ depth = readShort(file);
+}
+
+void level_pos::save(FILE *file) const
+{
+ id.save(file);
+ writeCoord(file, pos);
+}
+
+void level_pos::load(FILE *file)
+{
+ id.load(file);
+ readCoord(file, pos);
+}
+
+void stair_info::save(FILE *file) const
+{
+ writeCoord(file, position);
+ destination.save(file);
+ writeByte(file, guessed_pos? 1 : 0);
+}
+
+void stair_info::load(FILE *file)
+{
+ readCoord(file, position);
+ destination.load(file);
+ guessed_pos = readByte(file) != 0;
+}
+
+LevelInfo::LevelInfo(const LevelInfo &other)
+{
+ stairs = other.stairs;
+ excludes = other.excludes;
+ int sz = stairs.size() * stairs.size();
+ stair_distances = new short [ sz ];
+ if (other.stair_distances)
+ memcpy(stair_distances, other.stair_distances, sz * sizeof(int));
+}
+
+const LevelInfo &LevelInfo::operator = (const LevelInfo &other)
+{
+ if (&other == this)
+ return *this;
+
+ stairs = other.stairs;
+ excludes = other.excludes;
+ int sz = stairs.size() * stairs.size();
+ delete [] stair_distances;
+ stair_distances = new short [ sz ];
+ if (other.stair_distances)
+ memcpy(stair_distances, other.stair_distances, sz * sizeof(short));
+ return *this;
+}
+
+LevelInfo::~LevelInfo()
+{
+ delete [] stair_distances;
+}
+
+void LevelInfo::set_level_excludes()
+{
+ curr_excludes = excludes;
+}
+
+void LevelInfo::update()
+{
+ // We need to update all stair information and distances on this level.
+ update_excludes();
+
+ // First, set excludes, so that stair distances will be correctly populated.
+ excludes = curr_excludes;
+
+ // First, we get all known stairs
+ std::vector<coord_def> stair_positions;
+ get_stairs(stair_positions);
+
+ // Make sure our stair list is correct.
+ correct_stair_list(stair_positions);
+
+ update_stair_distances();
+}
+
+void LevelInfo::update_stair_distances()
+{
+ // Now we update distances for all the stairs, relative to all other
+ // stairs.
+ for (int s = 0, end = stairs.size(); s < end; ++s)
+ {
+ // For each stair, we need to ask travel to populate the distance
+ // array.
+ find_travel_pos(stairs[s].position.x, stairs[s].position.y,
+ NULL, NULL, NULL);
+
+ for (int other = 0; other < end; ++other)
+ {
+ int ox = stairs[other].position.x,
+ oy = stairs[other].position.y;
+ int dist = point_distance[ox][oy];
+
+ // Note dist == 0 is illegal because we can't have two stairs on
+ // the same square.
+ if (dist <= 0) dist = -1;
+ stair_distances[ s * stairs.size() + other ] = dist;
+ stair_distances[ other * stairs.size() + s ] = dist;
+ }
+ }
+}
+
+void LevelInfo::update_stair(int x, int y, const level_pos &p, bool guess)
+{
+ stair_info *si = get_stair(x, y);
+
+ // What 'guess' signifies: whenever you take a stair from A to B, the
+ // travel code knows that the stair takes you from A->B. In that case,
+ // update_stair() is called with guess == false.
+ //
+ // Unfortunately, Crawl doesn't guarantee that A->B implies B->A, but the
+ // travel code has to assume that anyway (because that's what the player
+ // will expect), and call update_stair() again with guess == true.
+ //
+ // The idea of using 'guess' is that we'll update the stair's destination
+ // with a guess only if we know that the currently set destination is
+ // itself a guess.
+ //
+ if (si && (si->guessed_pos || !guess))
+ {
+ si->destination = p;
+ si->guessed_pos = guess;
+
+ if (!guess && p.id.branch == BRANCH_VESTIBULE_OF_HELL
+ && id.branch == BRANCH_MAIN_DUNGEON)
+ travel_hell_entry = p;
+ }
+}
+
+stair_info *LevelInfo::get_stair(int x, int y)
+{
+ coord_def c = { x, y };
+ return get_stair(c);
+}
+
+stair_info *LevelInfo::get_stair(const coord_def &pos)
+{
+ int index = get_stair_index(pos);
+ return index != -1? &stairs[index] : NULL;
+}
+
+int LevelInfo::get_stair_index(const coord_def &pos) const
+{
+ for (int i = stairs.size() - 1; i >= 0; --i)
+ {
+ if (stairs[i].position == pos)
+ return i;
+ }
+ return -1;
+}
+
+void LevelInfo::add_waypoint(const coord_def &pos)
+{
+ if (pos.x < 0 || pos.y < 0) return;
+
+ // First, make sure we don't already have this position in our stair list.
+ for (int i = 0, sz = stairs.size(); i < sz; ++i)
+ if (stairs[i].position == pos)
+ return;
+
+ stair_info si;
+ si.position = pos;
+ si.destination.id.depth = -2; // Magic number for waypoints.
+
+ stairs.push_back(si);
+
+ delete [] stair_distances;
+ stair_distances = new short [ stairs.size() * stairs.size() ];
+
+ update_stair_distances();
+}
+
+void LevelInfo::remove_waypoint(const coord_def &pos)
+{
+ for (std::vector<stair_info>::iterator i = stairs.begin();
+ i != stairs.end(); ++i)
+ {
+ if (i->position == pos && i->destination.id.depth == -2)
+ {
+ stairs.erase(i);
+ break;
+ }
+ }
+
+ delete [] stair_distances;
+ stair_distances = new short [ stairs.size() * stairs.size() ];
+
+ update_stair_distances();
+}
+
+void LevelInfo::correct_stair_list(const std::vector<coord_def> &s)
+{
+ // If we have a waypoint on this level, we'll always delete stair_distances
+ delete [] stair_distances;
+ stair_distances = NULL;
+
+ // First we kill any stairs in 'stairs' that aren't there in 's'.
+ for (std::vector<stair_info>::iterator i = stairs.begin();
+ i != stairs.end(); ++i)
+ {
+ // Waypoints are not stairs, so we skip them.
+ if (i->destination.id.depth == -2) continue;
+
+ bool found = false;
+ for (int j = s.size() - 1; j >= 0; --j)
+ {
+ if (s[j] == i->position)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ stairs.erase(i--);
+ }
+
+ // For each stair in 's', make sure we have a corresponding stair
+ // in 'stairs'.
+ for (int i = 0, sz = s.size(); i < sz; ++i)
+ {
+ bool found = false;
+ for (int j = stairs.size() - 1; j >= 0; --j)
+ {
+ if (s[i] == stairs[j].position)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ stair_info si;
+ si.position = s[i];
+ si.destination.id = level_id::get_next_level_id(s[i]);
+ if (si.destination.id.branch == BRANCH_VESTIBULE_OF_HELL
+ && id.branch == BRANCH_MAIN_DUNGEON
+ && travel_hell_entry.is_valid())
+ si.destination = travel_hell_entry;
+
+ // We don't know where on the next level these stairs go to, but
+ // that can't be helped. That information will have to be filled
+ // in whenever the player takes these stairs.
+ stairs.push_back(si);
+ }
+ }
+
+ stair_distances = new short [ stairs.size() * stairs.size() ];
+}
+
+int LevelInfo::distance_between(const stair_info *s1, const stair_info *s2)
+ const
+{
+ if (!s1 || !s2) return 0;
+ if (s1 == s2) return 0;
+
+ int i1 = get_stair_index(s1->position),
+ i2 = get_stair_index(s2->position);
+ if (i1 == -1 || i2 == -1) return 0;
+
+ return stair_distances[ i1 * stairs.size() + i2 ];
+}
+
+void LevelInfo::get_stairs(std::vector<coord_def> &st)
+{
+ // These are env map coords, not grid coordinates.
+ for (int y = 0; y < GYM - 1; ++y)
+ {
+ for (int x = 0; x < GXM - 1; ++x)
+ {
+ unsigned char grid = grd[x + 1][y + 1];
+ unsigned char envc = (unsigned char) env.map[x][y];
+
+ if (envc && is_travelable_stair(grid))
+ {
+ // Convert to grid coords, because that's what we use
+ // everywhere else.
+ coord_def stair = { x + 1, y + 1 };
+ st.push_back(stair);
+ }
+ }
+ }
+}
+
+void LevelInfo::reset_distances()
+{
+ for (int i = 0, count = stairs.size(); i < count; ++i)
+ {
+ stairs[i].reset_distance();
+ }
+}
+
+bool LevelInfo::is_known_branch(unsigned char branch) const
+{
+ for (int i = 0, count = stairs.size(); i < count; ++i)
+ {
+ if (stairs[i].destination.id.branch == branch)
+ return true;
+ }
+ return false;
+}
+
+void LevelInfo::travel_to_waypoint(const coord_def &pos)
+{
+ stair_info *target = get_stair(pos);
+ if (!target) return;
+
+ curr_stairs.clear();
+ for (int i = 0, sz = stairs.size(); i < sz; ++i)
+ {
+ if (stairs[i].destination.id.depth == -2) continue;
+
+ stair_info si = stairs[i];
+ si.distance = distance_between(target, &stairs[i]);
+
+ curr_stairs.push_back(si);
+ }
+
+ start_translevel_travel(false);
+}
+
+void LevelInfo::save(FILE *file) const
+{
+ int stair_count = stairs.size();
+ // How many stairs do we know of?
+ writeShort(file, stair_count);
+ for (int i = 0; i < stair_count; ++i)
+ stairs[i].save(file);
+
+ if (stair_count)
+ {
+ // XXX Assert stair_distances != NULL?
+ // Save stair distances as short ints.
+ for (int i = stair_count * stair_count - 1; i >= 0; --i)
+ writeShort(file, stair_distances[i]);
+ }
+
+ writeShort(file, excludes.size());
+ if (excludes.size())
+ {
+ for (int i = 0, count = excludes.size(); i < count; ++i)
+ {
+ writeShort(file, excludes[i].x);
+ writeShort(file, excludes[i].y);
+ }
+ }
+}
+
+#define EXCLUDE_LOAD_LIMIT 20
+void LevelInfo::load(FILE *file)
+{
+ stairs.clear();
+ int stair_count = readShort(file);
+ for (int i = 0; i < stair_count; ++i)
+ {
+ stair_info si;
+ si.load(file);
+ stairs.push_back(si);
+
+ if (id.branch == BRANCH_MAIN_DUNGEON &&
+ si.destination.id.branch == BRANCH_VESTIBULE_OF_HELL &&
+ !travel_hell_entry.is_valid() &&
+ si.destination.is_valid())
+ travel_hell_entry = si.destination;
+ }
+
+ if (stair_count)
+ {
+ delete [] stair_distances;
+ stair_distances = new short [ stair_count * stair_count ];
+ for (int i = stair_count * stair_count - 1; i >= 0; --i)
+ stair_distances[i] = readShort(file);
+ }
+
+ excludes.clear();
+ int nexcludes = readShort(file);
+ if (nexcludes)
+ {
+ for (int i = 0; i < nexcludes; ++i)
+ {
+ coord_def c;
+ c.x = readShort(file);
+ c.y = readShort(file);
+ excludes.push_back(c);
+ }
+ }
+}
+
+void LevelInfo::fixup()
+{
+ // The only fixup we do now is for the hell entry.
+ if (id.branch != BRANCH_MAIN_DUNGEON || !travel_hell_entry.is_valid())
+ return;
+ for (int i = 0, count = stairs.size(); i < count; ++i)
+ {
+ stair_info &si = stairs[i];
+ if (si.destination.id.branch == BRANCH_VESTIBULE_OF_HELL
+ && !si.destination.is_valid())
+ si.destination = travel_hell_entry;
+ }
+}
+
+void TravelCache::travel_to_waypoint(int num)
+{
+ if (num < 0 || num >= TRAVEL_WAYPOINT_COUNT) return;
+ if (waypoints[num].id.depth == -1) return;
+
+ travel_target = waypoints[num];
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ travel_target);
+ LevelInfo &li = get_level_info(travel_target.id);
+ li.travel_to_waypoint(travel_target.pos);
+}
+
+void TravelCache::list_waypoints() const
+{
+ std::string line;
+ char dest[30];
+ char choice[50];
+ int count = 0;
+
+ for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
+ {
+ if (waypoints[i].id.depth == -1) continue;
+
+ set_trans_travel_dest(dest, sizeof dest, waypoints[i]);
+ // All waypoints will have @ (x,y), remove that.
+ char *at = strchr(dest, '@');
+ if (at)
+ *--at = 0;
+
+ snprintf(choice, sizeof choice, "(%d) %-8s", i, dest);
+ line += choice;
+ if (!(++count % 5))
+ {
+ mpr(line.c_str());
+ line = "";
+ }
+ }
+ if (line.length())
+ mpr(line.c_str());
+}
+
+unsigned char TravelCache::is_waypoint(const level_pos &lp) const
+{
+ for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
+ {
+ if (lp == waypoints[i])
+ return '0' + i;
+ }
+ return 0;
+}
+
+void TravelCache::update_waypoints() const
+{
+ level_pos lp;
+ lp.id = level_id::get_current_level_id();
+
+ memset(curr_waypoints, 0, sizeof curr_waypoints);
+ for (lp.pos.x = 1; lp.pos.x < GXM; ++lp.pos.x)
+ {
+ for (lp.pos.y = 1; lp.pos.y < GYM; ++lp.pos.y)
+ {
+ unsigned char wpc = is_waypoint(lp);
+ if (wpc)
+ curr_waypoints[lp.pos.x][lp.pos.y] = wpc;
+ }
+ }
+}
+
+void TravelCache::add_waypoint(int x, int y)
+{
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS
+ || you.level_type == LEVEL_PANDEMONIUM)
+ {
+ mpr("Sorry, you can't set a waypoint here.");
+ return;
+ }
+
+ mesclr();
+ if (get_waypoint_count())
+ {
+ mpr("Existing waypoints");
+ list_waypoints();
+ }
+
+ mpr("Assign waypoint to what number? (0-9) ", MSGCH_PROMPT);
+ int keyin = get_ch();
+
+ if (keyin < '0' || keyin > '9') return;
+
+ int waynum = keyin - '0';
+
+ if (waypoints[waynum].is_valid())
+ {
+ bool unique_waypoint = true;
+ for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
+ {
+ if (i == waynum) continue;
+ if (waypoints[waynum] == waypoints[i])
+ {
+ unique_waypoint = false;
+ break;
+ }
+ }
+
+ if (unique_waypoint)
+ {
+ LevelInfo &li = get_level_info(waypoints[waynum].id);
+ li.remove_waypoint(waypoints[waynum].pos);
+ }
+ }
+
+ if (x == -1 || y == -1)
+ {
+ x = you.x_pos;
+ y = you.y_pos;
+ }
+ coord_def pos = { x, y };
+ const level_id &lid = level_id::get_current_level_id();
+
+ LevelInfo &li = get_level_info(lid);
+ li.add_waypoint(pos);
+
+ waypoints[waynum].id = lid;
+ waypoints[waynum].pos = pos;
+
+ update_waypoints();
+}
+
+int TravelCache::get_waypoint_count() const
+{
+ int count = 0;
+ for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
+ if (waypoints[i].is_valid())
+ count++;
+ return count;
+}
+
+void TravelCache::reset_distances()
+{
+ std::map<level_id, LevelInfo, level_id::less_than>::iterator i =
+ levels.begin();
+ for ( ; i != levels.end(); ++i)
+ i->second.reset_distances();
+}
+
+bool TravelCache::is_known_branch(unsigned char branch) const
+{
+ std::map<level_id, LevelInfo, level_id::less_than>::const_iterator i =
+ levels.begin();
+ for ( ; i != levels.end(); ++i)
+ if (i->second.is_known_branch(branch))
+ return true;
+ return false;
+}
+
+void TravelCache::save(FILE *file) const
+{
+ // Travel cache version information
+ writeByte(file, TC_MAJOR_VERSION);
+ writeByte(file, TC_MINOR_VERSION);
+
+ // How many levels do we have?
+ writeShort(file, levels.size());
+
+ // Save all the levels we have
+ std::map<level_id, LevelInfo, level_id::less_than>::const_iterator i =
+ levels.begin();
+ for ( ; i != levels.end(); ++i)
+ {
+ i->first.save(file);
+ i->second.save(file);
+ }
+
+ for (int wp = 0; wp < TRAVEL_WAYPOINT_COUNT; ++wp)
+ waypoints[wp].save(file);
+}
+
+void TravelCache::load(FILE *file)
+{
+ levels.clear();
+
+ // Check version. If not compatible, we just ignore the file altogether.
+ unsigned char major = readByte(file),
+ minor = readByte(file);
+ if (major != TC_MAJOR_VERSION || minor != TC_MINOR_VERSION) return ;
+
+ int level_count = readShort(file);
+ for (int i = 0; i < level_count; ++i)
+ {
+ level_id id;
+ id.load(file);
+ LevelInfo linfo;
+ // Must set id before load, or travel_hell_entry will not be
+ // correctly set.
+ linfo.id = id;
+ linfo.load(file);
+ levels[id] = linfo;
+ }
+
+ for (int wp = 0; wp < TRAVEL_WAYPOINT_COUNT; ++wp)
+ waypoints[wp].load(file);
+
+ fixup_levels();
+}
+
+void TravelCache::set_level_excludes()
+{
+ if (can_travel_interlevel())
+ get_level_info(level_id::get_current_level_id()).set_level_excludes();
+}
+
+void TravelCache::update()
+{
+ if (can_travel_interlevel())
+ get_level_info(level_id::get_current_level_id()).update();
+ else
+ update_excludes();
+}
+
+void TravelCache::fixup_levels()
+{
+ std::map<level_id, LevelInfo, level_id::less_than>::iterator i =
+ levels.begin();
+ for ( ; i != levels.end(); ++i)
+ i->second.fixup();
+}
+
+bool can_travel_interlevel()
+{
+ return !(you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS
+ || you.level_type == LEVEL_PANDEMONIUM);
+}