Big-ball-of-mud commit - stretchy levels! stretchy-levels
authorMartin Read <martin@blackswordsonics.com>
Fri, 21 Feb 2014 01:17:41 +0000 (01:17 +0000)
committerMartin Read <martin@blackswordsonics.com>
Fri, 21 Feb 2014 01:17:41 +0000 (01:17 +0000)
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.

19 files changed:
MANIFEST
Makefile
cave.cc [new file with mode: 0644]
coord.hh
core.hh
display-nc.cc
fov.cc
log.cc
map.cc
map.hh
mapgen.hh [new file with mode: 0644]
monsters.cc
notify-local-tty.cc
notify.hh
objects.cc
rng.cc
rng.hh
shrine.cc [new file with mode: 0644]
u.cc

index ba2f42d..dadebd6 100644 (file)
--- 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
index a5de63c..6598895 100644 (file)
--- 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 (file)
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
index e8c07a6..1a2016a 100644 (file)
--- 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 (file)
--- 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;
index 9f07f6c..202d48e 100644 (file)
@@ -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 (file)
--- 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 (file)
--- 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 (file)
--- a/map.cc
+++ b/map.cc
 #include "victrix-abyssi.hh"
 #include "objects.hh"
 #include "monsters.hh"
+#include "mapgen.hh"
 
 #include <string.h>
 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<Stair_detail> *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<Stair_detail> *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_detail> 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 (file)
--- a/map.hh
+++ b/map.hh
 #include "victrix-abyssi.hh"
 #endif
 
+#include <deque>
 /* 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<Stair_detail> 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<Stair_detail> *result) const;
+    Stair_detail find_stairs(Coord pos) const;
+    int find_stairs(Terrain t, std::vector<Stair_detail> *result) const;
+    int find_stairs(Level_key from, Terrain t, std::vector<Stair_detail> *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 (file)
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
index f6debe4..cbc31bf 100644 (file)
@@ -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);
index 4fe0a3a..807dbd1 100644 (file)
@@ -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)
index 77ad492..419114e 100644 (file)
--- 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);
index cc8b9e0..bc53025 100644 (file)
@@ -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 (file)
--- 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 (file)
--- 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 (file)
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 <string.h>
+/*! \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 (file)
--- 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();