From 561c27ece7f0e75a3e5fb91c6d56a899659240a1 Mon Sep 17 00:00:00 2001 From: Martin Read Date: Fri, 21 Feb 2014 01:17:41 +0000 Subject: [PATCH] Big-ball-of-mud commit - stretchy levels! Levels now stretch to accommodate changes, and the level generation has been substantially overhauled. There's also some other bug fixes mushed in with this lot; I am not even going to *try* to unpick everything in a coherent manner to provide a file-by-file breakdown. --- MANIFEST | 3 + Makefile | 5 +- cave.cc | 114 ++++++++++ coord.hh | 6 + core.hh | 5 +- display-nc.cc | 168 +++++++++------ fov.cc | 2 +- log.cc | 21 +- map.cc | 597 ++++++++++++++++++++++++++++------------------------ map.hh | 78 ++++++- mapgen.hh | 73 +++++++ monsters.cc | 5 +- notify-local-tty.cc | 12 +- notify.hh | 2 + objects.cc | 12 +- rng.cc | 12 +- rng.hh | 8 +- shrine.cc | 266 +++++++++++++++++++++++ u.cc | 23 +- 19 files changed, 1032 insertions(+), 380 deletions(-) create mode 100644 cave.cc create mode 100644 mapgen.hh create mode 100644 shrine.cc diff --git a/MANIFEST b/MANIFEST index ba2f42d..dadebd6 100644 --- a/MANIFEST +++ b/MANIFEST @@ -4,6 +4,7 @@ Makefile configure Doxyfile man/victrix-abyssi.6 +cave.cc combat.cc combat.hh coord.cc @@ -19,6 +20,7 @@ log.cc main.cc map.cc map.hh +mapgen.hh mon2.cc monsters.cc monsters.hh @@ -35,6 +37,7 @@ pmon_comp pobj_comp rng.cc rng.hh +shrine.cc sorcery.cc sorcery.hh u.cc diff --git a/Makefile b/Makefile index a5de63c..6598895 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ vpath %.o . GENERATED_OBJS:=permobj.o permons.o GENERATED_SOURCE:=permobj.cc pobj_id.hh permons.cc pmon_id.hh GENERATED_MAKES:=dirs.mk features.mk -HANDWRITTEN_OBJS:=combat.o coord.o display-nc.o fov.o log.o main.o map.o monsters.o mon2.o notify-local-tty.o objects.o pmon2.o rng.o sorcery.o u.o util.o +HANDWRITTEN_OBJS:=cave.o combat.o coord.o display-nc.o fov.o log.o main.o map.o monsters.o mon2.o notify-local-tty.o objects.o pmon2.o rng.o shrine.o sorcery.o u.o util.o OBJS:=$(GENERATED_OBJS) $(HANDWRITTEN_OBJS) GAME:=victrix-abyssi MAJVERS:=0 @@ -93,6 +93,8 @@ permobj.cc pobj_id.hh: $(srcdir)/default.permobjs $(srcdir)/pobj_comp permons.cc pmon_id.hh: $(srcdir)/default.permons $(srcdir)/pmon_comp $(srcdir)/pmon_comp $< ## Dependencies for the build +cave.o: $(srcdir)/cave.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/map.hh $(srcdir)/mapgen.hh + combat.o: $(srcdir)/combat.cc $(srcdir)/combat.hh $(srcdir)/victrix-abyssi.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh coord.o: $(srcdir)/coord.cc $(srcdir)/coord.hh @@ -119,6 +121,7 @@ pmon2.o: $(srcdir)/pmon2.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(sr objects.o: $(srcdir)/objects.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh +shrine.o: $(srcdir)/shrine.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/map.hh $(srcdir)/mapgen.hh sorcery.o: $(srcdir)/sorcery.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/sorcery.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh u.o: $(srcdir)/u.cc $(srcdir)/combat.hh $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh diff --git a/cave.cc b/cave.cc new file mode 100644 index 0000000..04e8660 --- /dev/null +++ b/cave.cc @@ -0,0 +1,114 @@ +/*! \file cave.cc + * \brief Basic cave-style level generation algorithm + */ + +/* Copyright 2014 Martin Read + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "victrix-abyssi.hh" +#include "objects.hh" +#include "monsters.hh" +#include "mapgen.hh" + +void place_cave_stairs(void) +{ + Coord c; + Coord d; + int stair_tries = 0; + do + { + c = lvl.random_point(1); + } while (!(terrain_props[lvl.terrain_at(c)].flags & TFLAG_floor) || + (lvl.flags_at(c) & MAPFLAG_HARDWALL)); + lvl.add_stairs_at(c, STAIRS_DOWN, lvl.self.naive_next()); + do + { + d = lvl.random_point(1); + ++stair_tries; + } while (!(terrain_props[lvl.terrain_at(d)].flags & TFLAG_floor) || + (lvl.flags_at(d) & MAPFLAG_HARDWALL) || + (d.dist_cheb(c) < (10 - (stair_tries / 40)))); + lvl.add_stairs_at(d, STAIRS_UP, lvl.self.naive_prev()); +} + +/*! \brief Excavate a cave level. + * + * This algorithm runs two random walks on the map. + */ +void build_level_cave(void) +{ + Coord c = { GUIDE_EDGE_SIZE / 2, GUIDE_EDGE_SIZE / 2 }; + int num_pools; + int walk_data[4] = { 1, FLOOR, WALL, FLOOR }; + + initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true); + run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS); + //run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS); + if ((lvl.theme != THEME_UNDEAD) && (depth > 20) && !zero_die(4)) + { + num_pools = inc_flat(1, 4); + walk_data[0] = 2; + walk_data[1] = LAVA; + } + else if ((depth > 10) && !zero_die(3)) + { + num_pools = inc_flat(1, 4); + walk_data[0] = 2; + walk_data[1] = WATER; + } + else + { + num_pools = 0; + } + while (num_pools > 0) + { + int pool_size = inc_flat(9, 36); + do { + c = lvl.random_point(2); + } while (lvl.terrain_at(c) != FLOOR); + run_random_walk(c, excavation_write, walk_data, pool_size); + --num_pools; + } + place_cave_stairs(); +} + +/*! \brief Excavate a cave level with intrusions */ +void build_level_intrusions(void) +{ + Coord c = { GUIDE_EDGE_SIZE / 2, GUIDE_EDGE_SIZE / 2 }; + int i; + int walk_data[4] = { 1, FLOOR, WALL, FLOOR }; + + initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true); + for (i = 0; i < 6; ++i) + { + place_random_intrusion(WALL); + } + run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS); + /* and now the stairs */ + place_cave_stairs(); +} + +/* cave.cc */ +// vim:cindent:ts=8:sw=4:expandtab diff --git a/coord.hh b/coord.hh index e8c07a6..1a2016a 100644 --- a/coord.hh +++ b/coord.hh @@ -57,6 +57,12 @@ public: int lensq_eucl(void) const { return y * y + x * x; } Offset const& operator +=(Offset const& right) { y += right.y; x += right.x; return *this; } Offset const& operator -=(Offset const& right) { y -= right.y; x -= right.x; return *this; } + Offset const& operator *=(int right) { y *= right; x *= right; return *this; } + Offset const& operator /=(int right) { y /= right; x /= right; return *this; } + Offset operator +(Offset const& right) const { Offset o = { y + right.y, x + right.x }; return o; } + Offset operator -(Offset const& right) const { Offset o = { y - right.y, x - right.x }; return o; } + Offset operator *(int right) const { Offset o = { y * right, x * right }; return o; } + Offset operator /(int right) const { Offset o = { y / right, x / right }; return o; } bool operator !=(Offset const& right) const { return (y != right.y) || (x != right.x); } bool operator ==(Offset const& right) const { return (y == right.y) && (x == right.x); } bool operator <(Offset const& right) const { return (y < right.y) || ((y == right.y) && (x < right.x)); } diff --git a/core.hh b/core.hh index d2cbbe0..d92ba8f 100644 --- a/core.hh +++ b/core.hh @@ -109,7 +109,8 @@ enum Damtyp { /*! \brief Identification code for player actions */ enum Game_cmd { - REJECTED_ACTION = -1, WALK, STAND_STILL, GO_DOWN_STAIRS, ATTACK, + REJECTED_ACTION = -1, WALK, STAND_STILL, GO_UP_STAIRS, GO_DOWN_STAIRS, + ATTACK, GET_ITEM, DROP_ITEM, WIELD_WEAPON, WEAR_ARMOUR, TAKE_OFF_ARMOUR, PUT_ON_RING, REMOVE_RING, QUAFF_POTION, READ_SCROLL, THROW_FLASK, EAT_FOOD, @@ -157,6 +158,8 @@ struct Action }; +class Level; +class Level_key; class Permobj; class Permon; class Obj; diff --git a/display-nc.cc b/display-nc.cc index 9f07f6c..202d48e 100644 --- a/display-nc.cc +++ b/display-nc.cc @@ -106,14 +106,14 @@ cchar_t terrain_tiles[NUM_TERRAINS]; cchar_t permobj_tiles[NUM_OF_PERMOBJS]; -#define DISP_HEIGHT 21 -#define DISP_WIDTH 21 +#define DISP_HEIGHT (DISP_NC_SIDE) +#define DISP_WIDTH (DISP_NC_SIDE) cchar_t permon_tiles[NUM_OF_PERMONS]; cchar_t blank_tile; cchar_t player_tile; -cchar_t const *back_buffer[DUN_HEIGHT][DUN_WIDTH]; +cchar_t const *back_buffer[DISP_HEIGHT][DISP_WIDTH]; Attr_wrapper attr_override[DISP_HEIGHT][DISP_WIDTH]; cchar_t const *front_buffer[DISP_HEIGHT][DISP_WIDTH]; @@ -186,14 +186,14 @@ static cchar_t const *object_char(int object_id) return permobj_tiles + object_id; } -/*! \brief Call newsym() on the entire level and request a hard redraw +/*! \brief Repopulate the back buffer and set the hard redraw flag */ void touch_back_buffer(void) { Coord c; - for (c.y = 0; c.y < DUN_HEIGHT; ++c.y) + for (c.y = u.pos.y - DISP_NC_RADIUS; c.y <= u.pos.y + DISP_NC_RADIUS; ++c.y) { - for (c.x = 0; c.x < DUN_WIDTH; ++c.x) + for (c.x = u.pos.x - DISP_NC_RADIUS; c.x <= u.pos.x + DISP_NC_RADIUS; ++c.x) { newsym(c); } @@ -210,36 +210,50 @@ void touch_back_buffer(void) */ void newsym(Coord c) { - cchar_t const *ch; - int obj = lvl.obj_at(c); - int mon = lvl.mon_at(c); - Terrain terr = lvl.terrain_at(c); - uint32_t flags = lvl.flags_at(c); - ch = back_buffer[c.y][c.x]; - if (c == u.pos) + Offset camoff = { DISP_NC_RADIUS + (c.y - u.pos.y), DISP_NC_RADIUS + (c.x - u.pos.x) }; + cchar_t const *ch = back_buffer[camoff.y][camoff.x]; + if ((camoff.y < 0) || (camoff.y >= DISP_HEIGHT) || + (camoff.x < 0) || (camoff.x >= DISP_WIDTH)) { - back_buffer[c.y][c.x] = &player_tile; + /* out of frame */ + return; } - else if ((!show_terrain) && (mon != NO_MON) && mon_visible(lvl.mon_at(c))) + if ((c.y < lvl.min_y()) || (c.y > lvl.max_y()) || + (c.x < lvl.min_x()) || (c.x > lvl.max_x())) { - back_buffer[c.y][c.x] = monster_char(monsters[mon].mon_id); + back_buffer[camoff.y][camoff.x] = &blank_tile; } - else if (flags & MAPFLAG_EXPLORED) + else { - if ((!show_terrain) && (obj != NO_OBJ)) + int obj = lvl.obj_at(c); + int mon = lvl.mon_at(c); + Terrain terr = lvl.terrain_at(c); + uint32_t flags = lvl.flags_at(c); + if (c == u.pos) { - back_buffer[c.y][c.x] = object_char(objects[obj].obj_id); + back_buffer[camoff.y][camoff.x] = &player_tile; + } + else if ((!show_terrain) && (mon != NO_MON) && occupant_visible(c)) + { + back_buffer[camoff.y][camoff.x] = monster_char(monsters[mon].mon_id); + } + else if (flags & MAPFLAG_EXPLORED) + { + if ((!show_terrain) && (obj != NO_OBJ)) + { + back_buffer[camoff.y][camoff.x] = object_char(objects[obj].obj_id); + } + else + { + back_buffer[camoff.y][camoff.x] = terrain_char(terr); + } } else { - back_buffer[c.y][c.x] = terrain_char(terr); + back_buffer[camoff.y][camoff.x] = &blank_tile; } } - else - { - back_buffer[c.y][c.x] = &blank_tile; - } - if (ch != back_buffer[c.y][c.x]) + if (ch != back_buffer[camoff.y][camoff.x]) { map_updated = 1; } @@ -254,31 +268,20 @@ static void draw_world(void) Coord c; Coord d; - for (i = 0; i < DISP_NC_SIDE; i++) + for ((i = 0), (c.y = u.pos.y - DISP_NC_RADIUS); i < DISP_HEIGHT; ++i, ++c.y) { - c.y = u.pos.y + i - DISP_NC_RADIUS; - for (j = 0; j < DISP_NC_SIDE; j++) + for ((j = 0), (c.x = u.pos.x - DISP_NC_RADIUS); j < DISP_WIDTH; j++, ++c.x) { - c.x = u.pos.x + j - DISP_NC_RADIUS; d.y = i - DISP_NC_RADIUS + MAX_FOV_RADIUS; d.x = j - DISP_NC_RADIUS + MAX_FOV_RADIUS; - if ((c.y < 0) || (c.x < 0) || - (c.y >= DUN_HEIGHT) || (c.x >= DUN_WIDTH)) - { - if ((front_buffer[i][j] != &blank_tile) || hard_redraw) - { - mvwadd_wch(world_window, i, j, &blank_tile); - } - front_buffer[i][j] = &blank_tile; - } - else if (hard_redraw || (front_buffer[i][j] != back_buffer[c.y][c.x])) + if (hard_redraw || (front_buffer[i][j] != back_buffer[i][j])) { - mvwadd_wch(world_window, i, j, back_buffer[c.y][c.x]); + mvwadd_wch(world_window, i, j, back_buffer[i][j]); if (!player_fov.test(d)) { mvwchgat(world_window, i, j, 1, A_BOLD, Gcol_d_grey, nullptr); } - front_buffer[i][j] = back_buffer[c.y][c.x]; + front_buffer[i][j] = back_buffer[i][j]; } } } @@ -482,12 +485,6 @@ int launch_user_interface(void) init_pair(Gcol_d_grey, COLOR_BLACK, COLOR_BLACK); init_pair(Gcol_purple, COLOR_MAGENTA, COLOR_BLACK); init_pair(Gcol_cyan, COLOR_CYAN, COLOR_BLACK); - for (i = 0; i < DISP_HEIGHT; ++i) - { - for (j = 0; j < DISP_WIDTH; ++j) - { - } - } if (force_ascii) { load_ascii_tiles(); @@ -496,6 +493,13 @@ int launch_user_interface(void) { load_unicode_tiles(); } + for (i = 0; i < DISP_HEIGHT; ++i) + { + for (j = 0; j < DISP_HEIGHT; ++j) + { + front_buffer[i][j] = back_buffer[i][j] = &blank_tile; + } + } /* OK. We want a 21x21 viewport (player at centre), a 21x58 message * window, and a 2x80 status line. */ status_window = newwin(2, 80, DISP_HEIGHT + 1, 0); @@ -1185,16 +1189,36 @@ void get_player_action(Action *act) case '?': print_help(); break; - case '>': - if (lvl.terrain_at(u.pos) == STAIRS) + case '<': { - act->cmd = GO_DOWN_STAIRS; + Terrain t = lvl.terrain_at(u.pos); + if (terrain_props[t].flags & TFLAG_ascend) + { + act->cmd = GO_UP_STAIRS; + return; + } + else + { + print_msg("There are no stairs here.\n"); + } + // TODO accept this keystroke as "enter portal" too } - else + break; + case '>': { - print_msg("There are no stairs here.\n"); + Terrain t = lvl.terrain_at(u.pos); + if (terrain_props[t].flags & TFLAG_descend) + { + act->cmd = GO_DOWN_STAIRS; + return; + } + else + { + print_msg("There are no stairs here.\n"); + } + // TODO accept this keystroke as "enter portal" too } - return; + break; case '5': case '.': act->cmd = STAND_STILL; @@ -1213,6 +1237,36 @@ void get_player_action(Action *act) return; } break; +#ifdef DEBUG_MAPDUMP + case 'M': + { + Coord c; + for (c.y = lvl.min_y(); c.y <= lvl.max_y(); ++c.y) + { + for (c.x = lvl.min_x(); c.x <= lvl.max_x(); ++c.x) + { + lvl.set_flags_at(c, MAPFLAG_EXPLORED); + } + } + touch_back_buffer(); + display_update(); + } + break; +#endif + default: + if ((ch > 127)) + { + print_msg("Unrecognized non-ASCII character %#x.\n", ch); + } + else if (iscntrl(ch)) + { + print_msg("Unrecognized control character %#x.\n", ch); + } + else + { + print_msg("Unrecognized command '%c'.\n", ch); + } + break; } } print_msg(Msg_prio::Bug, "BUG: broke out of input loop!\n"); @@ -1357,16 +1411,8 @@ void touch_one_screen(Coord c) Coord c2; for (c2.y = c.y - DISP_NC_RADIUS; c2.y <= c.y + DISP_NC_RADIUS; c2.y++) { - if ((c2.y < 0) || (c2.y >= DUN_HEIGHT)) - { - continue; - } for (c2.x = c.x - DISP_NC_RADIUS; c2.x <= c.x + DISP_NC_RADIUS; c2.x++) { - if ((c2.x < 0) || (c2.x >= DUN_WIDTH)) - { - continue; - } newsym(c2); } } diff --git a/fov.cc b/fov.cc index 18730ac..b5dd8bd 100644 --- a/fov.cc +++ b/fov.cc @@ -67,7 +67,7 @@ static void compute_row(Radiance *rad, int octant, int radius, double inmost_slo */ static bool dflt_blk(Coord c) { - return ((c.y <= 0) || (c.x <= 0) || (c.y >= DUN_HEIGHT - 1) || (c.x >= DUN_WIDTH - 1) || (terrain_is_opaque(lvl.terrain_at(c)))); + return ((c.y <= lvl.min_y()) || (c.x <= lvl.min_x()) || (c.y >= lvl.max_y()) || (c.x >= lvl.max_x()) || (terrain_is_opaque(lvl.terrain_at(c)))); } /*! \brief Function for marking affected cells as explored diff --git a/log.cc b/log.cc index 8909a19..8d10a83 100644 --- a/log.cc +++ b/log.cc @@ -253,9 +253,10 @@ int load_game(void) void write_char_dump(void) { FILE *fp; - char filename[32]; + char filename[64]; int i; - snprintf(filename, 31, "%s.dump", u.name); + Coord c; + snprintf(filename, 63, "%s-%llx.dump", u.name, (unsigned long long) time(NULL)); fp = fopen(filename, "w"); if (fp == nullptr) { @@ -276,6 +277,22 @@ void write_char_dump(void) fputc('\n', fp); } } + fprintf(fp, "=== MAP ===\n"); + for (c.y = lvl.min_y(); c.y <= lvl.max_y(); ++c.y) + { + for (c.x = lvl.min_x(); c.x <= lvl.max_x(); ++c.x) + { + if (lvl.flags_at(c) & MAPFLAG_EXPLORED) + { + fputc(terrain_props[lvl.terrain_at(c)].ascii, fp); + } + else + { + fputc(' ', fp); + } + } + fputc('\n', fp); + } fflush(fp); fclose(fp); } diff --git a/map.cc b/map.cc index c40b03f..709a6a0 100644 --- a/map.cc +++ b/map.cc @@ -29,21 +29,12 @@ #include "victrix-abyssi.hh" #include "objects.hh" #include "monsters.hh" +#include "mapgen.hh" #include Level lvl; int depth = 1; -static Pass_fail get_levgen_mon_floor(Coord *c); -static void build_level_shrine(void); -static void build_level_intrusions(void); -static void build_level_cave(void); -static void build_level_dungeonbash(void); -static int excavation_write(Coord c, void *data); -static int intrusion_write(Coord c, void *data); -static void initialize_chunks(Level *l, int height, int width, bool dense); -static void drop_all_chunks(Level *l); - void drop_all_chunks(Level *l) { int i; @@ -56,7 +47,7 @@ void drop_all_chunks(Level *l) { if (l->chunks[i][j]) { - delete[] l->chunks[i][j]; + delete l->chunks[i][j]; } l->chunks[i][j] = nullptr; } @@ -92,24 +83,122 @@ void initialize_chunks(Level *l, int height, int width, bool dense) int i; int j; drop_all_chunks(l); + l->origin_off = Stationary; l->chunks_high = height; l->chunks_wide = width; new_chunk_grid = new Chunk ** [height]; for (i = 0; i < height; ++i) { new_chunk_row = new Chunk * [width]; - if (dense) + for (j = 0; j < width; ++j) { - for (j = 0; j < width; ++j) - { - new_chunk_row[j] = new_chunk = new Chunk(l->dead_space); - } + new_chunk_row[j] = new_chunk = (dense ? new Chunk(l->dead_space) : nullptr); } new_chunk_grid[i] = new_chunk_row; } l->chunks = new_chunk_grid; } +void Level::vivify(Coord c) +{ + Coord rc = c + origin_off; + while (rc.y < 0) + { + grow(North); + rc = c + origin_off; + } + while (rc.y > (chunks_high << CHUNK_SHIFT)) + { + grow(South); + } + while (rc.x < 0) + { + grow(West); + rc = c + origin_off; + } + while (rc.x > (chunks_wide << CHUNK_SHIFT)) + { + grow(East); + } + if (!chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]) + { + chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT] = new Chunk(dead_space); + } +} + +void Level::grow(Offset o, bool dense) +{ + Chunk ***new_chunk_grid; + Chunk **new_chunk_row; + int i; + int j; + if (o.y < 0) + { + int i; + origin_off.y += CHUNK_EDGE; + ++chunks_high; + new_chunk_grid = new Chunk **[chunks_high]; + for (i = 0; i < (chunks_high - 1); ++i) + { + new_chunk_grid[i + 1] = chunks[i]; + } + new_chunk_grid[0] = new_chunk_row = new Chunk *[chunks_wide]; + for (i = 0; i < chunks_wide; ++i) + { + new_chunk_row[i] = dense ? new Chunk(dead_space) : nullptr; + } + delete[] chunks; + chunks = new_chunk_grid; + } + else if (o.y > 0) + { + ++chunks_high; + new_chunk_grid = new Chunk **[chunks_high]; + for (i = 0; i < (chunks_high - 1); ++i) + { + new_chunk_grid[i] = chunks[i]; + } + new_chunk_grid[chunks_high - 1] = new_chunk_row = new Chunk *[chunks_wide]; + for (i = 0; i < chunks_wide; ++i) + { + new_chunk_row[i] = dense ? new Chunk(dead_space) : nullptr; + } + delete[] chunks; + chunks = new_chunk_grid; + } + if (o.x < 0) + { + ++chunks_wide; + origin_off.x += CHUNK_EDGE; + for (i = 0; i < chunks_high; ++i) + { + new_chunk_row = new Chunk *[chunks_wide]; + for (j = 0; j < chunks_wide - 1; ++j) + { + new_chunk_row[j + 1] = chunks[i][j]; + } + new_chunk_row[0] = dense ? new Chunk(dead_space) : nullptr; + delete[] chunks[i]; + chunks[i] = new_chunk_row; + } + } + else if (o.x > 0) + { + ++chunks_wide; + for (i = 0; i < chunks_high; ++i) + { + new_chunk_row = new Chunk *[chunks_wide]; + for (j = 0; j < chunks_wide - 1; ++j) + { + new_chunk_row[j] = chunks[i][j]; + } + new_chunk_row[chunks_wide - 1] = dense ? new Chunk(dead_space) : nullptr; + delete[] chunks[i]; + chunks[i] = new_chunk_row; + } + } +} + /*! \brief Find a random point on the level * * \param margin Width of the "dead" space along each edge in which the point cannot appear @@ -117,8 +206,64 @@ void initialize_chunks(Level *l, int height, int width, bool dense) Coord Level::random_point(int margin) const { Coord tl = { margin, margin }; - Coord br = { DUN_HEIGHT - (margin + 1), DUN_WIDTH - (margin + 1) }; - return inclusive_boxed(tl, br); + Coord br = { GUIDE_EDGE_SIZE - (margin + 1), GUIDE_EDGE_SIZE - (margin + 1) }; + return inc_boxed(tl, br); +} + +int Level::add_stairs_at(Coord c, Terrain t, Level_key l) +{ + Stair_detail sd = { c, t, l }; + if (!(terrain_props[t].flags & + (TFLAG_ascend | TFLAG_portal | TFLAG_descend))) + { + return NO_STAIRS; + } + set_terrain_at(c, t); + stairs.push_back(sd); + return stairs.size() - 1; +} + +Level_key const No_level = { 65535, -32768 }; + +Stair_detail const Bad_stairs = { Nowhere, FLOOR, No_level }; +Stair_detail Level::find_stairs(Coord pos) const +{ + for (auto iter = stairs.begin(); iter != stairs.end(); ++iter) + { + if (iter->pos == pos) + { + return *iter; + } + } + return Bad_stairs; +} + +int Level::find_stairs(Terrain t, std::vector *dest) const +{ + int count = 0; + for (auto iter = stairs.begin(); iter != stairs.end(); ++iter) + { + if (iter->t == t) + { + dest->push_back(*iter); + ++count; + } + } + return count; +} + +int Level::find_stairs(Level_key from, std::vector *dest) const +{ + int count = 0; + for (auto iter = lvl.stairs.begin(); iter != lvl.stairs.end(); ++iter) + { + if (iter->destination == from) + { + dest->push_back(*iter); + ++count; + } + } + return count; } void leave_level(void) @@ -141,46 +286,98 @@ void make_new_level(void) { build_level(); populate_level(); - inject_player(); + inject_player(lvl.self.naive_prev()); look_around_you(); notify_change_of_depth(); } -typedef int (*rwalk_mod_funcptr)(Coord c, void *data); +/*! \brief Random walk which grows the level + * + * This version of run_random_walk will extend the level boundaries instead + * of rejecting out-of-bounds coordinates. + */ +Coord run_random_walk(Coord oc, rwalk_mod_funcptr func, + void const *priv_ptr, int cells) +{ + int i; + Coord c = oc; + int bailout = 10000; + + func(c, priv_ptr); + for (i = 0; (i < cells) && (bailout > 0); --bailout) + { + oc = c; + if (zero_die(2)) + { + c.y += (zero_die(2) ? -1 : 1); + c.y = std::min(lvl.max_y() - 2, std::max(c.y, lvl.min_y() + 2)); + } + else + { + c.x += (zero_die(2) ? -1 : 1); + c.x = std::min(lvl.max_x() - 2, std::max(c.x, lvl.min_x() + 2)); + } + switch (func(c, priv_ptr)) + { + case 0: + /* nothing changed */ + break; + case 1: + /* changed normally */ + ++i; + break; + case 2: + /* reject! */ + c = oc; + break; + } + } + if (bailout < 1) + { + debug_excavation_bailout(); + } + return c; +} -static void run_random_walk(Coord oc, rwalk_mod_funcptr func, - void *priv_ptr, int cells) +/*! \brief Random walk which grows the level + * + * This version of run_random_walk will extend the level boundaries instead + * of rejecting out-of-bounds coordinates. + */ +Coord run_random_walk_unbounded(Coord oc, rwalk_mod_funcptr func, + void const *priv_ptr, int cells) { int i; Coord c = oc; int bailout = 10000; + func(c, priv_ptr); for (i = 0; (i < cells) && (bailout > 0); --bailout) { oc = c; if (zero_die(2)) { c.y += (zero_die(2) ? -1 : 1); - if (c.y == 0) - { - c.y = 2; - } - else if (c.y == DUN_HEIGHT - 1) - { - c.y = DUN_HEIGHT - 3; - } } else { c.x += (zero_die(2) ? -1 : 1); - if (c.x == 0) - { - c.x = 2; - } - else if (c.x == DUN_WIDTH - 1) - { - c.x = DUN_WIDTH - 3; - } + } + if (c.y <= lvl.min_y()) + { + lvl.grow(North, true); + } + if (c.y >= lvl.max_y()) + { + lvl.grow(South, true); + } + if (c.x <= lvl.min_x()) + { + lvl.grow(West, true); + } + if (c.x >= lvl.max_x()) + { + lvl.grow(East, true); } switch (func(c, priv_ptr)) { @@ -201,6 +398,7 @@ static void run_random_walk(Coord oc, rwalk_mod_funcptr func, { debug_excavation_bailout(); } + return c; } /*! \brief Entry point for level generation. @@ -210,15 +408,16 @@ static void run_random_walk(Coord oc, rwalk_mod_funcptr func, void build_level(void) { int theme_roll; - /* Snapshot the running RNG state, so that we can rebuild the map from - * the saved RNG state at game reload. */ + lvl.self.depth = (int16_t) depth; + lvl.self.dungeon = 0; + lvl.stairs.clear(); rng.extract_serialization(saved_state_buffer, saved_state_size); theme_roll = zero_die(depth + 50); if (!zero_die(4)) { lvl.layout = LAYOUT_CAVE_INTRUSIONS; } - else if ((depth >= 15) && !zero_die(6)) + else if ((depth > 1) && !zero_die(6)) { lvl.layout = LAYOUT_CAVE_SHRINE; } @@ -259,179 +458,36 @@ void build_level(void) } } -/*! \brief Build a dungeonbash-style rooms-and-corridors level - */ +/*! \brief Build a dungeonbash-style rooms-and-corridors level */ void build_level_dungeonbash() { + int chy; + int chx; /* We know we're going to use all nine chunks, so create them * immediately. */ - initialize_chunks(&lvl, DUNGEONBASH_EDGE_CHUNKS, DUNGEONBASH_EDGE_CHUNKS, true); -} - -/*! \brief Excavate a cave level. - * - * This algorithm runs two random walks on the map. - */ -void build_level_cave(void) -{ - Coord c = { DUN_HEIGHT / 2, DUN_WIDTH / 2 }; - int num_pools; - int walk_data[4] = { 1, FLOOR, WALL, FLOOR }; - - initialize_chunks(&lvl, DUNGEONBASH_EDGE_CHUNKS, DUNGEONBASH_EDGE_CHUNKS, true); - run_random_walk(c, excavation_write, walk_data, LEVGEN_WALK_CELLS); - run_random_walk(c, excavation_write, walk_data, LEVGEN_WALK_CELLS); - if ((lvl.theme != THEME_UNDEAD) && (depth > 20) && !zero_die(4)) - { - num_pools = inclusive_flat(1, 4); - walk_data[0] = 2; - walk_data[1] = LAVA; - } - else if ((depth > 10) && !zero_die(3)) - { - num_pools = inclusive_flat(1, 4); - walk_data[0] = 2; - walk_data[1] = WATER; - } - else - { - num_pools = 0; - } - while (num_pools > 0) + initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true); + /* One room per chunk. */ + for (chy = 0; chy < lvl.chunks_high; ++chy) { - int pool_size = inclusive_flat(9, 36); - do { - c = lvl.random_point(2); - } while (lvl.terrain_at(c) != FLOOR); - run_random_walk(c, excavation_write, walk_data, pool_size); - --num_pools; - } - /* Add the stairs */ - do - { - c = lvl.random_point(1); - } while (lvl.terrain_at(c) != FLOOR); - lvl.set_terrain_at(c, STAIRS); -} - -struct shrine shrines[4] = -{ - { - true, - { - "...........", - ".#########.", - ".#.......#.", - ".#..._...#.", - ".##.....##.", - ".+..###..+.", - ".##.....##.", - ".#..._...#.", - ".#.......#.", - ".#########.", - "...........", - }, - }, - { - true, - { - "...........", - ".LLL###LLL.", - ".####L####.", - ".#..L_L..#.", - ".##.....##.", - ".#..###..#.", - ".##.....##.", - ".#.......#.", - ".#.......#.", - ".#+#####+#.", - "...........", - }, - }, - { - true, - { - "...........", - ".#########.", - ".#WW...WW#.", - ".#W.._..W#.", - ".#..WWW..#.", - ".+..WWW..+.", - ".#..WWW..#.", - ".#W.._..W#.", - ".#WW...WW#.", - ".#########.", - "...........", - }, - }, - { - true, - { - "...........", - ".####+####.", - ".#.......#.", - ".#.#.#.#.#.", - ".#.......#.", - ".+.#._.#.+.", - ".#.......#.", - ".#.#.#.#.#.", - ".#.......#.", - ".####+####.", - "...........", - }, - }, -}; - -static void build_level_shrine(void) -{ - Coord c; - int shrine_ty = inclusive_flat(DUN_HEIGHT / 4, DUN_HEIGHT / 2); - int shrine_lx = inclusive_flat(DUN_WIDTH / 4, DUN_WIDTH / 2); - int walk_data[4] = { 2, FLOOR, WALL, FLOOR }; - int i, j; - int shrine_num = zero_die(sizeof shrines / sizeof shrines[0]); - - initialize_chunks(&lvl, DUNGEONBASH_EDGE_CHUNKS, DUNGEONBASH_EDGE_CHUNKS, true); - for ((i = 0), (c.y = shrine_ty); i < SHRINE_HEIGHT; ++i, ++c.y) - { - for ((j = 0), (c.x = shrine_lx); j < SHRINE_WIDTH; ++j, ++c.x) + for (chx = 0; chx < lvl.chunks_wide; ++ chx) { - switch (shrines[shrine_num].grid[i][j]) + /* Smallest allowed room has a 2x2 interior. */ + Offset room_size = { MIN_ROOM_EDGE + zero_die(5), MIN_ROOM_EDGE + zero_die(5) }; + /* Each dimension has a 1-in-3 chance to get another boost */ + if (!zero_die(3)) { - case '.': - lvl.set_terrain_at(c, FLOOR); - break; - case '#': - lvl.set_terrain_at(c, MASONRY_WALL); - break; - case '+': - lvl.set_terrain_at(c, DOOR); - break; - case '_': - lvl.set_terrain_at(c, ALTAR); - break; - case 'L': - lvl.set_terrain_at(c, LAVA); - break; - case 'W': - lvl.set_terrain_at(c, WATER); - break; + room_size.y += zero_die(5); + } + if (!zero_die(3)) + { + room_size.x += zero_die(5); } } } - run_random_walk(c, excavation_write, walk_data, LEVGEN_WALK_CELLS); - run_random_walk(c, excavation_write, walk_data, LEVGEN_WALK_CELLS); - /* and now the stairs, which are not in the shrine */ - do - { - c = lvl.random_point(1); - } while ((lvl.terrain_at(c) != FLOOR) && - (c.y >= shrine_ty) && (c.y < shrine_ty + SHRINE_HEIGHT) && - (c.x >= shrine_lx) && (c.x < shrine_lx + SHRINE_WIDTH)); - lvl.set_terrain_at(c, STAIRS); } -static int excavation_write(Coord c, void *data) +/*! \brief Excavation function for use with random walks */ +int excavation_write(Coord c, void const *data) { int const *data_as_ints = (int const *) data; int const *overwrite = data_as_ints + 2; @@ -454,78 +510,52 @@ static int excavation_write(Coord c, void *data) return 0; } -static int intrusion_write(Coord c, void *data) +/*! \brief "Intrusion" function for use with random walks */ +int intrusion_write(Coord c, void const *data) { + Terrain const *tptr = (Terrain const *) data; if ((lvl.terrain_at(c) != WALL) || (lvl.flags_at(c) & MAPFLAG_HARDWALL)) { return 0; } /* Don't intrude too closely on the centre of the level */ - if ((c.y > ((DUN_HEIGHT / 2) - 4)) && (c.y < ((DUN_HEIGHT / 2) - 4))) + if ((c.y > ((GUIDE_EDGE_SIZE / 2) - 4)) && (c.y < ((GUIDE_EDGE_SIZE / 2) - 4))) { return 2; } - if ((c.x > ((DUN_WIDTH / 2) - 4)) && (c.x < ((DUN_WIDTH / 2) - 4))) + if ((c.x > ((GUIDE_EDGE_SIZE / 2) - 4)) && (c.x < ((GUIDE_EDGE_SIZE / 2) - 4))) { return 2; } + if (tptr) + { + lvl.set_terrain_at(c, *tptr); + } lvl.set_flags_at(c, MAPFLAG_HARDWALL); return 1; } -void build_level_intrusions(void) +/*! \brief Do a random walk laying down an exclusion area */ +void place_random_intrusion(Terrain new_wall) { Coord c; - int i; - int intrusion_size; - int walk_data[4] = { 1, FLOOR, WALL, FLOOR }; - - initialize_chunks(&lvl, DUNGEONBASH_EDGE_CHUNKS, DUNGEONBASH_EDGE_CHUNKS, true); - for (i = 0; i < 6; ++i) - { - do - { - if (zero_die(2)) - { - c.x = inclusive_flat(1, DUN_WIDTH / 3); - } - else - { - c.x = inclusive_flat((2 * DUN_WIDTH) / 3, DUN_WIDTH - 2); - } - if (zero_die(2)) - { - c.y = inclusive_flat(1, DUN_HEIGHT / 3); - } - else - { - c.y = inclusive_flat((2 * DUN_HEIGHT) / 3, DUN_HEIGHT - 2); - } - } while (lvl.flags_at(c) & MAPFLAG_HARDWALL); - intrusion_size = inclusive_flat(27, 54); - run_random_walk(c, intrusion_write, nullptr, intrusion_size); - } - c.y = DUN_HEIGHT / 2; - c.x = DUN_WIDTH / 2; - run_random_walk(c, excavation_write, walk_data, LEVGEN_WALK_CELLS); - run_random_walk(c, excavation_write, walk_data, LEVGEN_WALK_CELLS); - /* and now the stairs */ + int intrusion_size = inc_flat(27, 54); do { - c.y = exclusive_flat(0, DUN_HEIGHT - 1); - c.x = exclusive_flat(0, DUN_WIDTH - 1); - } while (lvl.terrain_at(c) != FLOOR); - lvl.set_terrain_at(c, STAIRS); + c.x = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2); + c.y = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2); + } while (lvl.flags_at(c) & MAPFLAG_HARDWALL); + run_random_walk(c, intrusion_write, &new_wall, intrusion_size); } +/*! \brief Get a valid square to generate a monster on */ Pass_fail get_levgen_mon_floor(Coord *c) { int cell_try; Coord t; for (cell_try = 0; cell_try < 200; cell_try++) { - t.y = exclusive_flat(0, DUN_HEIGHT - 1); - t.x = exclusive_flat(0, DUN_WIDTH - 1); + t = lvl.random_point(1); if ((lvl.terrain_at(t) != FLOOR) || (lvl.mon_at(t) != NO_MON)) { t = Nowhere; @@ -541,6 +571,7 @@ Pass_fail get_levgen_mon_floor(Coord *c) return You_pass; } +/*! \brief Populate the level! */ void populate_level(void) { int i; @@ -575,32 +606,34 @@ void populate_level(void) } } -void inject_player(void) +/*! \brief Inject the player into the level based on where they arrived from */ +void inject_player(Level_key from) { - int cell_try; - for (cell_try = 0; cell_try < 200; cell_try++) + /* For now we are allowing only one linkage between levels */ + Coord c; + std::vector stair_list; + int i = lvl.find_stairs(from, &stair_list); + if (i != 0) { - u.pos.y = exclusive_flat(0, DUN_HEIGHT - 1); - u.pos.x = exclusive_flat(0, DUN_WIDTH - 1); - if (lvl.terrain_at(u.pos) != FLOOR) - { - continue; - } - if (lvl.mon_at(u.pos) != NO_MON) - { - continue; - } - break; + c = stair_list[0].pos; + } + else + { + fprintf(stderr, "Couldn't find any stairs!\n"); + abort(); } + u.pos = c; reloc_player(u.pos); } +/*! \brief Look around the player */ void look_around_you(void) { compute_fov(); notify_fov(); } +/*! \brief Description of terrain types */ Terrain_props terrain_props[NUM_TERRAINS] = { { "cave wall", '#', "█", Gcol_brown, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile }, @@ -610,33 +643,31 @@ Terrain_props terrain_props[NUM_TERRAINS] = { "skin wall", '#', "█", Gcol_l_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile }, { "bone wall", '#', "█", Gcol_white, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile }, { "door", '+', "+", Gcol_l_grey, TFLAG_opaque | TFLAG_block_missile }, - { "floor", '.', "·", Gcol_l_grey, 0 }, - { "amethyst floor", '.', "·", Gcol_purple, 0 }, - { "iron floor", '.', "·", Gcol_iron, 0 }, - { "skin floor", '.', "·", Gcol_l_purple, 0 }, - { "bone floor", '.', "·", Gcol_white, 0 }, + { "floor", '.', "·", Gcol_l_grey, TFLAG_floor }, + { "amethyst floor", '.', "·", Gcol_purple, TFLAG_floor }, + { "iron floor", '.', "·", Gcol_iron, TFLAG_floor }, + { "skin floor", '.', "·", Gcol_l_purple, TFLAG_floor }, + { "bone floor", '.', "·", Gcol_white, TFLAG_floor }, { "altar", '_', "_", Gcol_l_grey, 0 }, - { "stairs", '>', ">", Gcol_l_grey, 0 }, + { "upward stairs", '<', "<", Gcol_l_grey, TFLAG_ascend }, + { "downward stairs", '>', ">", Gcol_l_grey, TFLAG_descend }, { "lava", '}', "≈", Gcol_red, TFLAG_fire_hazard }, { "water", '}', "≈", Gcol_blue, TFLAG_drown_hazard }, }; -bool terrain_opacity[NUM_TERRAINS] = -{ - true, true, true, false, - false, false, false, false -}; - +/*! \brief self-explanatory */ bool terrain_is_opaque(Terrain terr) { return terrain_props[terr].flags & TFLAG_opaque; } +/*! \brief self-explanatory */ bool terrain_blocks_beings(Terrain terr) { return terrain_props[terr].flags & TFLAG_block_beings; } +/*! \brief self-explanatory */ bool terrain_blocks_missiles(Terrain terr) { return terrain_props[terr].flags & TFLAG_block_missile; @@ -673,8 +704,7 @@ void deserialize_chunk(FILE *fp, Chunk *c) } } -/*! \brief Write a Chunk out to a FILE. - */ +/*! \brief Write a Chunk out to a FILE. */ void serialize_chunk(FILE *fp, Chunk const *c) { static uint32_t serializable_terrain[CHUNK_EDGE][CHUNK_EDGE]; @@ -696,14 +726,17 @@ void serialize_chunk(FILE *fp, Chunk const *c) fwrite(serializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp); } -/*! \brief Serialize a Level - */ +/*! \brief Serialize a Level */ void serialize_level(FILE *fp, Level const *l) { uint32_t tmp; uint32_t tmp_pair[2]; + uint16_t tmp_shorts[2]; int i; int j; + tmp_shorts[0] = htons(l->self.dungeon); + tmp_shorts[1] = htons(l->self.depth); + fwrite(tmp_shorts, sizeof tmp_shorts[0], 2, fp); tmp_pair[0] = htonl(l->origin_off.y); tmp_pair[1] = htonl(l->origin_off.x); fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp); @@ -742,8 +775,12 @@ void deserialize_level(FILE *fp, Level *l) { uint32_t tmp; uint32_t tmp_pair[2]; + uint16_t tmp_shorts[2]; uint32_t i; uint32_t j; + wrapped_fread(tmp_shorts, sizeof tmp_shorts[0], 2, fp); + l->self.dungeon = ntohs(tmp_shorts[0]); + l->self.depth = (int16_t) ntohs(tmp_shorts[1]); wrapped_fread(tmp_pair, sizeof tmp_pair[0], 2, fp); l->origin_off.y = (int) ntohl(tmp_pair[0]); l->origin_off.x = (int) ntohl(tmp_pair[1]); @@ -773,5 +810,11 @@ void deserialize_level(FILE *fp, Level *l) while (1); } -/* map.c */ +uint32_t rotate_connection_mask(uint32_t val, int clockwise_steps) +{ + clockwise_steps &= 0x3; + return ((val << clockwise_steps) | ((val & 0xf) >> (4 - clockwise_steps))) & 0xf; +} + +/* map.cc */ // vim:cindent:ts=8:sw=4:expandtab diff --git a/map.hh b/map.hh index 6942c9c..d71ea2c 100644 --- a/map.hh +++ b/map.hh @@ -33,10 +33,10 @@ #include "victrix-abyssi.hh" #endif +#include /* XXX enum terrain_num */ enum Terrain { - // cheap hack: opaque terrain goes first, and walls very first. - WALL = 0, MASONRY_WALL, AMETHYST_WALL, IRON_WALL, SKIN_WALL, BONE_WALL, DOOR, FLOOR, AMETHYST_FLOOR, IRON_FLOOR, SKIN_FLOOR, BONE_FLOOR, ALTAR, STAIRS, LAVA, WATER + WALL = 0, MASONRY_WALL, AMETHYST_WALL, IRON_WALL, SKIN_WALL, BONE_WALL, DOOR, FLOOR, AMETHYST_FLOOR, IRON_FLOOR, SKIN_FLOOR, BONE_FLOOR, ALTAR, STAIRS_UP, STAIRS_DOWN, LAVA, WATER }; #define MAX_TERRAIN (WATER) #define NUM_TERRAINS (1 + MAX_TERRAIN) @@ -55,13 +55,6 @@ enum level_layout LAYOUT_DUNGEONBASH /* maybe not for this version: dungeonbash-style room grid */ }; -#define LEVGEN_WALK_CELLS 300 -#define DUN_WIDTH 48 -#define DUN_HEIGHT 48 -#define ROOM_HT_DELTA 4 -#define ROOM_WD_DELTA 4 -#define MAX_ROOMS 9 -#define DUNGEONBASH_EDGE_CHUNKS 3 #define NO_REGION 0xffffffffu #define SHRINE_HEIGHT 11 @@ -71,6 +64,7 @@ enum level_layout struct shrine { bool used; + uint32_t connection_mask; char const *grid[SHRINE_HEIGHT]; }; @@ -87,10 +81,16 @@ struct Terrain_props //! Array of terrain properties extern Terrain_props terrain_props[NUM_TERRAINS]; +#define NO_STAIRS (-1) + #define TFLAG_opaque 0x00000001u #define TFLAG_block_beings 0x00000002u #define TFLAG_block_ether 0x00000004u #define TFLAG_block_missile 0x00000008u +#define TFLAG_portal 0x00000010u +#define TFLAG_ascend 0x00000020u +#define TFLAG_descend 0x00000040u +#define TFLAG_floor 0x00000080u #define TFLAG_fire_hazard 0x00010000u #define TFLAG_fall_hazard 0x00020000u #define TFLAG_drown_hazard 0x00040000u @@ -125,6 +125,39 @@ public: Chunk(Terrain t = WALL); }; +#define MIN_ROOM_EDGE 4 + +class Level_key +{ +public: + uint16_t dungeon; + int16_t depth; + /* + * g++ 4.7's brain-dead dogmatic narrowing detection makes the casts below + * compulsory. And no, don't tell me to use 32-bit integers for these + * fields, because I AM NEVER GOING TO SUPPORT DUNGEONS LARGER THAN 64 + * KIBILEVELS, OR HAVING MORE THAN 64 KIBIDUNGEONS. + */ + Level_key naive_next() const { Level_key l = { dungeon, (int16_t) (depth + 1) }; return l; } + Level_key naive_prev() const { Level_key l = { dungeon, (int16_t) (depth - 1) }; return l; } + bool operator ==(Level_key right) const { return (dungeon == right.dungeon) && (depth == right.depth); } + bool operator <(Level_key right) const { return (dungeon < right.dungeon) || ((dungeon == right.dungeon) && (depth < right.depth)); } +}; + +extern Level_key const No_level; + +/*! \brief Description of a staircase leading away from the level + */ +class Stair_detail +{ +public: + Coord pos; + Terrain t; + Level_key destination; +}; + +extern Stair_detail const Bad_stairs; + //! The top-tier object for describing everything about a level class Level { @@ -136,6 +169,8 @@ public: Terrain dead_space; //!< Terrain to fill new chunks with and return for Coords in unpopulated chunks level_theme theme; //!< Will affect monster and maybe item generation level_layout layout; //!< Determines generation algorithm + Level_key self; + std::deque stairs; Terrain terrain_at(Coord c) const { Coord rc = c + origin_off; @@ -223,6 +258,29 @@ public: return !((c.y < 0) || (c.x < 0) || (c.y >= (chunks_high << CHUNK_SHIFT)) || (c.x >= (chunks_wide << CHUNK_SHIFT))); } + int min_y() const + { + return -(origin_off.y); + } + int max_y() const + { + return (chunks_high << CHUNK_SHIFT) - origin_off.y - 1; + } + int min_x() const + { + return -(origin_off.x); + } + int max_x() const + { + return (chunks_wide << CHUNK_SHIFT) - origin_off.x - 1; + } + int add_stairs_at(Coord c, Terrain t, Level_key l); + int find_stairs(Level_key from, std::vector *result) const; + Stair_detail find_stairs(Coord pos) const; + int find_stairs(Terrain t, std::vector *result) const; + int find_stairs(Level_key from, Terrain t, std::vector *result) const; + void grow(Offset o, bool dense = true); + void vivify(Coord c); }; extern Level lvl; @@ -235,7 +293,7 @@ extern void leave_level(void); extern void make_new_level(void); extern void build_level(void); extern void populate_level(void); -extern void inject_player(void); +extern void inject_player(Level_key from); extern void look_around_you(void); extern bool terrain_is_opaque(Terrain terr); extern bool terrain_blocks_beings(Terrain terr); diff --git a/mapgen.hh b/mapgen.hh new file mode 100644 index 0000000..4936e8e --- /dev/null +++ b/mapgen.hh @@ -0,0 +1,73 @@ +/*! \file mapgen.hh + * \brief This is a map-generation header for map-generation modules. + */ + +/* Copyright 2014 Martin Read + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MAPGEN_HH +#define MAPGEN_HH + +#include "map.hh" + +#define LEVGEN_WALK_CELLS 300 +#define ROOM_HT_DELTA 4 +#define ROOM_WD_DELTA 4 +#define MAX_ROOMS 9 +#define GUIDE_EDGE_CHUNKS 3 +#define GUIDE_EDGE_SIZE (GUIDE_EDGE_CHUNKS << CHUNK_SHIFT) + +extern int depth; +extern enum level_theme current_theme; +extern enum level_layout current_layout; + +void build_level_shrine(void); +void build_level_intrusions(void); +void build_level_cave(void); +void build_level_dungeonbash(void); +Pass_fail get_levgen_mon_floor(Coord *c); +int excavation_write(Coord c, void const *data); +int intrusion_write(Coord c, void const *data); +void initialize_chunks(Level *l, int height, int width, bool dense); +void drop_all_chunks(Level *l); +void place_shrine(Coord topleft, shrine const *sh, uint32_t accept_conns = 0, Terrain shwall = MASONRY_WALL, Terrain shfloor = FLOOR, bool margin_is_wall = false); +void place_random_intrusion(Terrain new_wall = WALL); +void place_cave_stairs(void); + +#define ROOMCONN_NORTH 0x00000001u +#define ROOMCONN_EAST 0x00000002u +#define ROOMCONN_SOUTH 0x00000004u +#define ROOMCONN_WEST 0x00000008u +uint32_t rotate_connection_mask(uint32_t val, int clockwise_steps); + +typedef int (*rwalk_mod_funcptr)(Coord c, void const *data); +Coord run_random_walk(Coord oc, rwalk_mod_funcptr func, + void const *priv_ptr, int cells); +Coord run_random_walk_unbounded(Coord oc, rwalk_mod_funcptr func, + void const *priv_ptr, int cells); + +#endif + +/* mapgen.hh */ +// vim:cindent:expandtab diff --git a/monsters.cc b/monsters.cc index f6debe4..cbc31bf 100644 --- a/monsters.cc +++ b/monsters.cc @@ -235,7 +235,7 @@ void death_drop(int mon) bool Mon::can_pass(Coord c) const { Terrain terr; - if ((c.y < 0) || (c.x < 0) || (c.y >= DUN_HEIGHT) || (c.x >= DUN_WIDTH)) + if (!lvl.in_bounds(c)) { return false; } @@ -378,8 +378,7 @@ Pass_fail teleport_mon(int mon) Coord c; for (cell_try = 0; cell_try < 200; cell_try++) { - c.y = exclusive_flat(0, DUN_HEIGHT - 1); - c.x = exclusive_flat(0, DUN_WIDTH - 1); + c = lvl.random_point(1); if ((lvl.mon_at(c) == NO_MON) && (lvl.terrain_at(c) == FLOOR) && (c != u.pos)) { reloc_mon(mon, c); diff --git a/notify-local-tty.cc b/notify-local-tty.cc index 4fe0a3a..807dbd1 100644 --- a/notify-local-tty.cc +++ b/notify-local-tty.cc @@ -259,6 +259,11 @@ void notify_cant_go(void) print_msg("You cannot go there.\n"); } +void notify_ascent_blocked(void) +{ + print_msg("A mysterious force prevents you climbing the stairs.\n"); +} + void notify_wasted_gain(void) { print_msg("You feel disappointed.\n"); @@ -1072,9 +1077,14 @@ void debug_quaff_non_potion(void) print_msg(Msg_prio::Bug, "BUG: quaffing non-potion\n"); } +void debug_ascend_non_stairs(void) +{ + print_msg(Msg_prio::Bug, "BUG: Received GO_UP_STAIRS while not on up stairs.\n"); +} + void debug_descend_non_stairs(void) { - print_msg(Msg_prio::Bug, "BUG: Received use-stairs command standing on non-stairs.\n"); + print_msg(Msg_prio::Bug, "BUG: Received GO_DOWN_STAIRS while not on down stairs.\n"); } void debug_wizmode_violation(void) diff --git a/notify.hh b/notify.hh index 77ad492..419114e 100644 --- a/notify.hh +++ b/notify.hh @@ -161,6 +161,7 @@ void notify_fov(void); void notify_tick(void); void notify_change_of_depth(void); void notify_load_complete(void); +void notify_ascent_blocked(void); // Debugging notifications void debug_move_oob(Coord c); @@ -180,6 +181,7 @@ void debug_put_on_uncarried_ring(void); void debug_eat_non_food(int obj); void debug_read_non_scroll(void); void debug_quaff_non_potion(void); +void debug_ascend_non_stairs(void); void debug_descend_non_stairs(void); void debug_wizmode_violation(void); void debug_excavation_bailout(void); diff --git a/objects.cc b/objects.cc index cc8b9e0..bc53025 100644 --- a/objects.cc +++ b/objects.cc @@ -207,7 +207,7 @@ Action_cost quaff_potion(int obj) break; case PO_HEALING_POTION: { - int healpercent = inclusive_flat(30, 50); + int healpercent = inc_flat(30, 50); int healamount = (healpercent * ((u.hpmax > 60) ? u.hpmax : 60)) / 100; heal_u(healamount, 1, 1); } @@ -335,13 +335,13 @@ int create_obj_class(enum poclass_num po_class, int quantity, bool with_you, Coo switch (po_class) { case POCLASS_POTION: - po_idx = inclusive_flat(PO_FIRST_POTION, PO_LAST_POTION); + po_idx = inc_flat(PO_FIRST_POTION, PO_LAST_POTION); break; case POCLASS_SCROLL: - po_idx = inclusive_flat(PO_FIRST_SCROLL, PO_LAST_SCROLL); + po_idx = inc_flat(PO_FIRST_SCROLL, PO_LAST_SCROLL); break; case POCLASS_RING: - po_idx = inclusive_flat(PO_FIRST_RING, PO_LAST_RING); + po_idx = inc_flat(PO_FIRST_RING, PO_LAST_RING); break; default: /* No getting armour/weapons by class... yet. */ @@ -715,14 +715,14 @@ Action_cost zap_weapon(void) notify_firestaff_activation(1); for (c.y = u.pos.y - 1; c.y <= u.pos.y + 1; ++c.y) { - if ((c.y < 0) || (c.y >= DUN_HEIGHT)) + if ((c.y < lvl.min_y()) || (c.y >= lvl.max_y())) { continue; } for (c.x = u.pos.x - 1; c.x <= u.pos.x + 1; ++c.x) { int mon; - if ((c.x < 0) || (c.x >= DUN_WIDTH)) + if ((c.x < lvl.min_x()) || (c.x >= lvl.max_x())) { continue; } diff --git a/rng.cc b/rng.cc index 6647a43..bd47dc4 100644 --- a/rng.cc +++ b/rng.cc @@ -83,25 +83,25 @@ void rng_init(void) saved_state_buffer = malloc(saved_state_size); } -int exclusive_flat(int lower, int upper) +int exc_flat(int lower, int upper) { return lower + one_die(upper - lower - 1); } -int inclusive_flat(int lower, int upper) +int inc_flat(int lower, int upper) { return lower + zero_die(upper - lower + 1); } -Coord inclusive_boxed(Coord topleft, Coord botright) +Coord inc_boxed(Coord topleft, Coord botright) { - Coord c = { inclusive_flat(topleft.y, botright.y), inclusive_flat(topleft.x, botright.x) }; + Coord c = { inc_flat(topleft.y, botright.y), inc_flat(topleft.x, botright.x) }; return c; } -Coord exclusive_boxed(Coord topleft, Coord botright) +Coord exc_boxed(Coord topleft, Coord botright) { - Coord c = { exclusive_flat(topleft.y, botright.y), exclusive_flat(topleft.x, botright.x) }; + Coord c = { exc_flat(topleft.y, botright.y), exc_flat(topleft.x, botright.x) }; return c; } diff --git a/rng.hh b/rng.hh index 408be11..c1a9681 100644 --- a/rng.hh +++ b/rng.hh @@ -43,10 +43,10 @@ extern void rng_init(void); extern int zero_die(int sides); /* 0..n-1 */ extern int one_die(int sides); /* 1..n */ extern int dice(int count, int sides); -extern int exclusive_flat(int lower, int upper); /* l+1 ... u-1 */ -extern int inclusive_flat(int lower, int upper); /* l ... u */ -extern Coord inclusive_boxed(Coord topleft, Coord botright); -extern Coord exclusive_boxed(Coord topleft, Coord botright); +extern int exc_flat(int lower, int upper); /* l+1 ... u-1 */ +extern int inc_flat(int lower, int upper); /* l ... u */ +extern Coord inc_boxed(Coord topleft, Coord botright); +extern Coord exc_boxed(Coord topleft, Coord botright); /*! \brief An instance of Bob Jenkins' "small noncryptographic PRNG" * diff --git a/shrine.cc b/shrine.cc new file mode 100644 index 0000000..949faaf --- /dev/null +++ b/shrine.cc @@ -0,0 +1,266 @@ +/*! \file shrine.cc + * \brief Shrine level generation + */ + +/* Copyright 2014 Martin Read + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "victrix-abyssi.hh" +#include "objects.hh" +#include "monsters.hh" +#include "mapgen.hh" + +#include +/*! \brief Construction descriptors for shrines */ +struct shrine shrines[4] = +{ + { + true, + ROOMCONN_EAST | ROOMCONN_WEST, + { + "11111111111", + "1#########1", + "1#.......#1", + "1#..._...#1", + "1##.....##1", + "0+..###..+0", + "1##.....##1", + "1#..._...#1", + "1#.......#1", + "1#########1", + "11111111111", + }, + }, + { + true, + ROOMCONN_SOUTH, + { + "11111111111", + "1LLL###LLL1", + "1####L####1", + "1#..L_L..#1", + "1##.....##1", + "1#..###..#1", + "1##.....##1", + "1#.......#1", + "1#.......#1", + "1#+#####+#1", + "11011111011", + }, + }, + { + true, + ROOMCONN_EAST | ROOMCONN_WEST, + { + "11111111111", + "1#########1", + "1#WW...WW#1", + "1#W.._..W#1", + "1#..WWW..#1", + "0+..WWW..+0", + "1#..WWW..#1", + "1#W.._..W#1", + "1#WW...WW#1", + "1#########1", + "11111111111", + }, + }, + { + true, + ROOMCONN_NORTH | ROOMCONN_EAST | ROOMCONN_SOUTH | ROOMCONN_WEST, + { + "11111011111", + "1####+####1", + "1#.......#1", + "1#.#.#.#.#1", + "1#.......#1", + "0+.#._.#.+0", + "1#.......#1", + "1#.#.#.#.#1", + "1#.......#1", + "1####+####1", + "11111011111", + }, + }, +}; + +/*! \brief Construction helper for shrines */ +void place_shrine(Coord topleft, shrine const *sh, uint32_t accept_conns, Terrain shwall, Terrain shfloor, bool margin_is_wall) +{ + Coord c; + Coord start; + int shy; + int shx; + Offset outer_mapstep; + Offset inner_mapstep; + uint32_t tstmask; + int r = 0; + if (accept_conns) + { + for (r = 0; r < 4; ++r) + { + tstmask = rotate_connection_mask(sh->connection_mask, r); + if (tstmask & accept_conns) + { + print_msg("Test %#x matches requested mask %#x at rotation %d\n", tstmask, accept_conns, r); + break; + } + } + } + switch (r) + { + default: + DEBUG_CONTROL_FLOW(); + // fallthru + case 0: + outer_mapstep = South; + inner_mapstep = East; + start.y = topleft.y; + start.x = topleft.x; + break; + case 1: + outer_mapstep = West; + inner_mapstep = South; + start.y = topleft.y; + start.x = topleft.x + SHRINE_WIDTH - 1; + break; + case 2: + outer_mapstep = North; + inner_mapstep = West; + start.y = topleft.y + SHRINE_HEIGHT - 1; + start.x = topleft.x + SHRINE_WIDTH - 1; + break; + case 3: + outer_mapstep = East; + inner_mapstep = North; + start.y = topleft.y + SHRINE_HEIGHT - 1; + start.x = topleft.x; + break; + } + c = start; + for (shy = 0; shy < SHRINE_HEIGHT; ++shy, (c += outer_mapstep)) + { + for (shx = 0; shx < SHRINE_WIDTH; ++shx, (c += inner_mapstep)) + { + switch (sh->grid[shy][shx]) + { + case '0': + lvl.set_terrain_at(c, shfloor); + /* We want to be able to slap this down as a room, which means + * that at least one square on the connectable edges must not + * be HARDWALL. Those squares are marked '0'. */ + break; + case '1': + lvl.set_terrain_at(c, margin_is_wall ? shwall : shfloor); + lvl.set_flags_at(c, MAPFLAG_HARDWALL); + break; + case '.': + lvl.set_terrain_at(c, shfloor); + lvl.set_flags_at(c, MAPFLAG_HARDWALL); + break; + case '#': + lvl.set_terrain_at(c, shwall); + lvl.set_flags_at(c, MAPFLAG_HARDWALL); + break; + case '+': + lvl.set_terrain_at(c, DOOR); + lvl.set_flags_at(c, MAPFLAG_HARDWALL); + break; + case '_': + lvl.set_terrain_at(c, ALTAR); + lvl.set_flags_at(c, MAPFLAG_HARDWALL); + break; + case 'L': + lvl.set_terrain_at(c, LAVA); + lvl.set_flags_at(c, MAPFLAG_HARDWALL); + break; + case 'W': + lvl.set_terrain_at(c, WATER); + lvl.set_flags_at(c, MAPFLAG_HARDWALL); + break; + } + } + c -= inner_mapstep * SHRINE_WIDTH; + } +} + +/*! \brief Excavate a cave-with-shrine level. */ +void build_level_shrine(void) +{ + Coord shrinepos = { inc_flat(GUIDE_EDGE_SIZE / 4, GUIDE_EDGE_SIZE / 2), inc_flat(GUIDE_EDGE_SIZE / 4, GUIDE_EDGE_SIZE / 2) }; + Coord c; + Coord c2; + Coord c3; + uint32_t mask = 0; + int walk_data[4] = { 1, FLOOR, WALL }; + int shrine_num = zero_die(sizeof shrines / sizeof shrines[0]); + int intrusions; + int i; + + initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true); + intrusions = dice(2, 4) - 1; + for (i = 0; i < 6; ++i) + { + place_random_intrusion(WALL); + } + switch (zero_die(4)) + { + case 0: + c.y = shrinepos.y - 1; + c.x = shrinepos.x + SHRINE_WIDTH / 2; + c2 = c + East; + c3 = c + West; + mask = ROOMCONN_NORTH; + break; + case 1: + c.y = shrinepos.y + SHRINE_HEIGHT; + c.x = shrinepos.x + SHRINE_WIDTH / 2; + c2 = c + East; + c3 = c + West; + mask = ROOMCONN_SOUTH; + break; + case 2: + c.y = shrinepos.y + SHRINE_WIDTH / 2; + c.x = shrinepos.x - 1; + c2 = c + South; + c3 = c + North; + mask = ROOMCONN_WEST; + break; + case 3: + c.y = shrinepos.y + SHRINE_WIDTH / 2; + c.x = shrinepos.x + SHRINE_WIDTH; + c2 = c + South; + c3 = c + North; + mask = ROOMCONN_EAST; + } + place_shrine(shrinepos, shrines + shrine_num, mask); + lvl.set_terrain_at(c, FLOOR); + lvl.set_terrain_at(c2, FLOOR); + lvl.set_terrain_at(c3, FLOOR); + run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS); + place_cave_stairs(); +} + +/* shrine.cc */ +// vim:cindent:ts=8:sw=4:expandtab diff --git a/u.cc b/u.cc index 7f705df..07cdf2c 100644 --- a/u.cc +++ b/u.cc @@ -98,8 +98,7 @@ void recalc_defence(void) Action_cost move_player(Offset delta) { Coord c = u.pos + delta; - if ((c.y < 0) || (c.y >= DUN_HEIGHT) || - (c.x < 0) || (c.x >= DUN_WIDTH)) + if (!lvl.in_bounds(c)) { debug_move_oob(c); return Cost_none; /* No movement. */ @@ -134,7 +133,8 @@ Action_cost move_player(Offset delta) case SKIN_FLOOR: case BONE_FLOOR: case DOOR: - case STAIRS: + case STAIRS_UP: + case STAIRS_DOWN: case ALTAR: reloc_player(c); return Cost_std; @@ -176,7 +176,6 @@ void reloc_player(Coord c) { Coord oc = u.pos; u.pos = c; - touch_one_screen(oc); look_around_you(); if (lvl.obj_at(c) != NO_OBJ) { @@ -418,8 +417,7 @@ Pass_fail teleport_u(void) Coord c; for (cell_try = 0; cell_try < 200; cell_try++) { - c.y = exclusive_flat(0, DUN_HEIGHT - 1); - c.x = exclusive_flat(0, DUN_WIDTH - 1); + c = lvl.random_point(1); if ((lvl.mon_at(c) == NO_MON) && (lvl.terrain_at(c) == FLOOR) && (c != u.pos)) { reloc_player(c); @@ -781,8 +779,19 @@ Action_cost do_player_action(Action *act) return Cost_none; } + case GO_UP_STAIRS: + if (lvl.terrain_at(u.pos) == STAIRS_UP) + { + notify_ascent_blocked(); + } + else + { + debug_ascend_non_stairs(); + } + return Cost_none; + case GO_DOWN_STAIRS: - if (lvl.terrain_at(u.pos) == STAIRS) + if (lvl.terrain_at(u.pos) == STAIRS_DOWN) { leave_level(); make_new_level(); -- 2.11.0