configure
Doxyfile
man/victrix-abyssi.6
+cave.cc
combat.cc
combat.hh
coord.cc
main.cc
map.cc
map.hh
+mapgen.hh
mon2.cc
monsters.cc
monsters.hh
pobj_comp
rng.cc
rng.hh
+shrine.cc
sorcery.cc
sorcery.hh
u.cc
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
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
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
--- /dev/null
+/*! \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
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)); }
/*! \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,
};
+class Level;
+class Level_key;
class Permobj;
class Permon;
class Obj;
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];
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);
}
*/
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;
}
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];
}
}
}
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();
{
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);
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;
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");
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);
}
}
*/
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
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)
{
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);
}
#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;
{
if (l->chunks[i][j])
{
- delete[] l->chunks[i][j];
+ delete l->chunks[i][j];
}
l->chunks[i][j] = nullptr;
}
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
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)
{
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))
{
{
debug_excavation_bailout();
}
+ return c;
}
/*! \brief Entry point for level generation.
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;
}
}
}
-/*! \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;
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;
return You_pass;
}
+/*! \brief Populate the level! */
void populate_level(void)
{
int i;
}
}
-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 },
{ "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;
}
}
-/*! \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];
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);
{
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]);
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
#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)
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
struct shrine
{
bool used;
+ uint32_t connection_mask;
char const *grid[SHRINE_HEIGHT];
};
//! 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
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
{
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;
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;
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);
--- /dev/null
+/*! \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
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;
}
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);
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");
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)
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);
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);
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);
}
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. */
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;
}
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;
}
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"
*
--- /dev/null
+/*! \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
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. */
case SKIN_FLOOR:
case BONE_FLOOR:
case DOOR:
- case STAIRS:
+ case STAIRS_UP:
+ case STAIRS_DOWN:
case ALTAR:
reloc_player(c);
return Cost_std;
{
Coord oc = u.pos;
u.pos = c;
- touch_one_screen(oc);
look_around_you();
if (lvl.obj_at(c) != NO_OBJ)
{
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);
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();