The g? structs had a mix of variables that were written to
the savefile, and those that were not.
For better clarity and to distinguish those that end up in
the savefile, relocate some g? variables that get written
directly to the savefile into different structs.
This updates EDITLEVEL, although technically it probably
didn't need to, since savefile contents are not changing.
Details:
gb.bases -> svb.bases
gb.bbubbles -> svb.bbubbles
gb.branches -> svb.branches
gc.context -> svc.context
gd.disco -> svd.disco
gd.dndest -> svd.dndest
gd.doors -> svd.doors
gd.doors_alloc -> svd.doors_alloc
gd.dungeon_topology -> svd.dungeon_topology
gd.dungeons -> svd.dungeons
ge.exclusion_zones -> sve.exclusion_zones
gh.hackpid -> svh.hackpid
gi.inv_pos -> svi.inv_pos
gk.killer -> svk.killer
gl.lastseentyp -> svl.lastseentyp
gl.level -> svl.level
gl.level_info -> svl.level_info
gm.mapseenchn -> svm.mapseenchn
gm.moves -> svm.moves
gm.mvitals -> svm.mvitals
gn.n_dgns -> svn.n_dgns
gn.n_regions -> svn.n_regions
gn.nroom -> svn.nroom
go.oracle_cnt -> svo.oracle_cnt
gp.pl_character -> svp.pl_character
gp.pl_fruit -> svp.pl_fruit
gp.plname -> svp.plname
gp.program_state -> svp.program_state
gq.quest_status -> svq.quest_status
gr.rooms -> svr.rooms
gs.sp_levchn -> svs.sp_levchn
gs.spl_book -> svs.spl_book
gt.timer_id -> svt.timer_id
gt.tune -> svt.tune
gu.updest -> svu.updest
gx.xmax -> svx.xmax
gx.xmin -> svx.xmin
gy.ymax -> svy.ymax
gy.ymin -> svy.ymin
Related note:
There are some pointer variables that are heads of chains that were not
moved from 'g?' to 'sv?', because they are not actually written to the
savefile directly, but the objects/monst/trap/lightsource/timer in the
chains they point to are. That can be changed, if desired.
Examples: gi.invent, gm.migrating_objs, gb.billobjs, gm.migrating_mons,
gf.ftrap, gl.light_base, gt.timer_base
491 lines
15 KiB
C
491 lines
15 KiB
C
/* NetHack 3.7 mkmap.c $NHDT-Date: 1717432093 2024/06/03 16:28:13 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.40 $ */
|
|
/* Copyright (c) J. C. Collet, M. Stephenson and D. Cohrs, 1992 */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
#include "sp_lev.h"
|
|
|
|
#define HEIGHT (ROWNO - 1)
|
|
#define WIDTH (COLNO - 2)
|
|
|
|
staticfn void init_map(schar);
|
|
staticfn void init_fill(schar, schar);
|
|
staticfn schar get_map(int, int, schar);
|
|
staticfn void pass_one(schar, schar);
|
|
staticfn void pass_two(schar, schar);
|
|
staticfn void pass_three(schar, schar);
|
|
staticfn void join_map_cleanup(void);
|
|
staticfn void join_map(schar, schar);
|
|
staticfn void finish_map(schar, schar, boolean, boolean, boolean);
|
|
staticfn void remove_room(unsigned);
|
|
void mkmap(lev_init *);
|
|
|
|
staticfn void
|
|
init_map(schar bg_typ)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 1; i < COLNO; i++)
|
|
for (j = 0; j < ROWNO; j++) {
|
|
levl[i][j].roomno = NO_ROOM;
|
|
levl[i][j].typ = bg_typ;
|
|
levl[i][j].lit = FALSE;
|
|
}
|
|
}
|
|
|
|
staticfn void
|
|
init_fill(schar bg_typ, schar fg_typ)
|
|
{
|
|
int i, j;
|
|
long limit, count;
|
|
|
|
limit = (WIDTH * HEIGHT * 2) / 5;
|
|
count = 0;
|
|
while (count < limit) {
|
|
i = rn1(WIDTH - 1, 2);
|
|
j = rnd(HEIGHT - 1);
|
|
if (levl[i][j].typ == bg_typ) {
|
|
levl[i][j].typ = fg_typ;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
staticfn schar
|
|
get_map(int col, int row, schar bg_typ)
|
|
{
|
|
if (col <= 0 || row < 0 || col > WIDTH || row >= HEIGHT)
|
|
return bg_typ;
|
|
return levl[col][row].typ;
|
|
}
|
|
|
|
staticfn const int dirs[16] = {
|
|
-1, -1 /**/, -1, 0 /**/, -1, 1 /**/, 0, -1 /**/,
|
|
0, 1 /**/, 1, -1 /**/, 1, 0 /**/, 1, 1
|
|
};
|
|
|
|
staticfn void
|
|
pass_one(schar bg_typ, schar fg_typ)
|
|
{
|
|
int i, j;
|
|
short count, dr;
|
|
|
|
for (i = 2; i <= WIDTH; i++)
|
|
for (j = 1; j < HEIGHT; j++) {
|
|
for (count = 0, dr = 0; dr < 8; dr++)
|
|
if (get_map(i + dirs[dr * 2], j + dirs[(dr * 2) + 1], bg_typ)
|
|
== fg_typ)
|
|
count++;
|
|
|
|
switch (count) {
|
|
case 0: /* death */
|
|
case 1:
|
|
case 2:
|
|
levl[i][j].typ = bg_typ;
|
|
break;
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
levl[i][j].typ = fg_typ;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define new_loc(i, j) *(gn.new_locations + ((j) * (WIDTH + 1)) + (i))
|
|
|
|
staticfn void
|
|
pass_two(schar bg_typ, schar fg_typ)
|
|
{
|
|
int i, j;
|
|
short count, dr;
|
|
|
|
for (i = 2; i <= WIDTH; i++)
|
|
for (j = 1; j < HEIGHT; j++) {
|
|
for (count = 0, dr = 0; dr < 8; dr++)
|
|
if (get_map(i + dirs[dr * 2], j + dirs[(dr * 2) + 1], bg_typ)
|
|
== fg_typ)
|
|
count++;
|
|
if (count == 5)
|
|
new_loc(i, j) = bg_typ;
|
|
else
|
|
new_loc(i, j) = get_map(i, j, bg_typ);
|
|
}
|
|
|
|
for (i = 2; i <= WIDTH; i++)
|
|
for (j = 1; j < HEIGHT; j++)
|
|
levl[i][j].typ = new_loc(i, j);
|
|
}
|
|
|
|
staticfn void
|
|
pass_three(schar bg_typ, schar fg_typ)
|
|
{
|
|
int i, j;
|
|
short count, dr;
|
|
|
|
for (i = 2; i <= WIDTH; i++)
|
|
for (j = 1; j < HEIGHT; j++) {
|
|
for (count = 0, dr = 0; dr < 8; dr++)
|
|
if (get_map(i + dirs[dr * 2], j + dirs[(dr * 2) + 1], bg_typ)
|
|
== fg_typ)
|
|
count++;
|
|
if (count < 3)
|
|
new_loc(i, j) = bg_typ;
|
|
else
|
|
new_loc(i, j) = get_map(i, j, bg_typ);
|
|
}
|
|
|
|
for (i = 2; i <= WIDTH; i++)
|
|
for (j = 1; j < HEIGHT; j++)
|
|
levl[i][j].typ = new_loc(i, j);
|
|
}
|
|
|
|
/*
|
|
* use a flooding algorithm to find all locations that should
|
|
* have the same rm number as the current location.
|
|
* if anyroom is TRUE, use IS_ROOM to check room membership instead of
|
|
* exactly matching levl[sx][sy].typ and walls are included as well.
|
|
*/
|
|
void
|
|
flood_fill_rm(
|
|
int sx,
|
|
int sy,
|
|
int rmno,
|
|
boolean lit,
|
|
boolean anyroom)
|
|
{
|
|
int i;
|
|
int nx;
|
|
schar fg_typ = levl[sx][sy].typ;
|
|
|
|
/* back up to find leftmost uninitialized location */
|
|
while (sx > 0 && (anyroom ? IS_ROOM(levl[sx][sy].typ)
|
|
: levl[sx][sy].typ == fg_typ)
|
|
&& (int) levl[sx][sy].roomno != rmno)
|
|
sx--;
|
|
sx++; /* compensate for extra decrement */
|
|
|
|
/* assume sx,sy is valid */
|
|
if (sx < gm.min_rx)
|
|
gm.min_rx = sx;
|
|
if (sy < gm.min_ry)
|
|
gm.min_ry = sy;
|
|
|
|
for (i = sx; i <= WIDTH && levl[i][sy].typ == fg_typ; i++) {
|
|
levl[i][sy].roomno = rmno;
|
|
levl[i][sy].lit = lit;
|
|
if (anyroom) {
|
|
/* add walls to room as well */
|
|
int ii, jj;
|
|
for (ii = (i == sx ? i - 1 : i); ii <= i + 1; ii++)
|
|
for (jj = sy - 1; jj <= sy + 1; jj++)
|
|
if (isok(ii, jj) && (IS_WALL(levl[ii][jj].typ)
|
|
|| IS_DOOR(levl[ii][jj].typ)
|
|
|| levl[ii][jj].typ == SDOOR)) {
|
|
levl[ii][jj].edge = 1;
|
|
if (lit)
|
|
levl[ii][jj].lit = lit;
|
|
|
|
if (levl[ii][jj].roomno == NO_ROOM)
|
|
levl[ii][jj].roomno = rmno;
|
|
else if ((int) levl[ii][jj].roomno != rmno)
|
|
levl[ii][jj].roomno = SHARED;
|
|
}
|
|
}
|
|
gn.n_loc_filled++;
|
|
}
|
|
nx = i;
|
|
|
|
if (isok(sx, sy - 1)) {
|
|
for (i = sx; i < nx; i++)
|
|
if (levl[i][sy - 1].typ == fg_typ) {
|
|
if ((int) levl[i][sy - 1].roomno != rmno)
|
|
flood_fill_rm(i, sy - 1, rmno, lit, anyroom);
|
|
} else {
|
|
if ((i > sx || isok(i - 1, sy - 1))
|
|
&& levl[i - 1][sy - 1].typ == fg_typ) {
|
|
if ((int) levl[i - 1][sy - 1].roomno != rmno)
|
|
flood_fill_rm(i - 1, sy - 1, rmno, lit, anyroom);
|
|
}
|
|
if ((i < nx - 1 || isok(i + 1, sy - 1))
|
|
&& levl[i + 1][sy - 1].typ == fg_typ) {
|
|
if ((int) levl[i + 1][sy - 1].roomno != rmno)
|
|
flood_fill_rm(i + 1, sy - 1, rmno, lit, anyroom);
|
|
}
|
|
}
|
|
}
|
|
if (isok(sx, sy + 1)) {
|
|
for (i = sx; i < nx; i++)
|
|
if (levl[i][sy + 1].typ == fg_typ) {
|
|
if ((int) levl[i][sy + 1].roomno != rmno)
|
|
flood_fill_rm(i, sy + 1, rmno, lit, anyroom);
|
|
} else {
|
|
if ((i > sx || isok(i - 1, sy + 1))
|
|
&& levl[i - 1][sy + 1].typ == fg_typ) {
|
|
if ((int) levl[i - 1][sy + 1].roomno != rmno)
|
|
flood_fill_rm(i - 1, sy + 1, rmno, lit, anyroom);
|
|
}
|
|
if ((i < nx - 1 || isok(i + 1, sy + 1))
|
|
&& levl[i + 1][sy + 1].typ == fg_typ) {
|
|
if ((int) levl[i + 1][sy + 1].roomno != rmno)
|
|
flood_fill_rm(i + 1, sy + 1, rmno, lit, anyroom);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nx > gm.max_rx)
|
|
gm.max_rx = nx - 1; /* nx is just past valid region */
|
|
if (sy > gm.max_ry)
|
|
gm.max_ry = sy;
|
|
}
|
|
|
|
/* join_map uses temporary rooms; clean up after it */
|
|
staticfn void
|
|
join_map_cleanup(void)
|
|
{
|
|
coordxy x, y;
|
|
|
|
for (x = 1; x < COLNO; x++)
|
|
for (y = 0; y < ROWNO; y++)
|
|
levl[x][y].roomno = NO_ROOM;
|
|
svn.nroom = gn.nsubroom = 0;
|
|
svr.rooms[svn.nroom].hx = gs.subrooms[gn.nsubroom].hx = -1;
|
|
}
|
|
|
|
staticfn void
|
|
join_map(schar bg_typ, schar fg_typ)
|
|
{
|
|
struct mkroom *croom, *croom2;
|
|
|
|
int i, j;
|
|
int sx, sy;
|
|
coord sm, em;
|
|
|
|
/* first, use flood filling to find all of the regions that need joining
|
|
*/
|
|
for (i = 2; i <= WIDTH; i++)
|
|
for (j = 1; j < HEIGHT; j++) {
|
|
if (levl[i][j].typ == fg_typ && levl[i][j].roomno == NO_ROOM) {
|
|
gm.min_rx = gm.max_rx = i;
|
|
gm.min_ry = gm.max_ry = j;
|
|
gn.n_loc_filled = 0;
|
|
flood_fill_rm(i, j, svn.nroom + ROOMOFFSET, FALSE, FALSE);
|
|
if (gn.n_loc_filled > 3) {
|
|
add_room(gm.min_rx, gm.min_ry, gm.max_rx, gm.max_ry,
|
|
FALSE, OROOM, TRUE);
|
|
svr.rooms[svn.nroom - 1].irregular = TRUE;
|
|
if (svn.nroom >= (MAXNROFROOMS * 2))
|
|
goto joinm;
|
|
} else {
|
|
/*
|
|
* it's a tiny hole; erase it from the map to avoid
|
|
* having the player end up here with no way out.
|
|
*/
|
|
for (sx = gm.min_rx; sx <= gm.max_rx; sx++)
|
|
for (sy = gm.min_ry; sy <= gm.max_ry; sy++)
|
|
if ((int) levl[sx][sy].roomno
|
|
== svn.nroom + ROOMOFFSET) {
|
|
levl[sx][sy].typ = bg_typ;
|
|
levl[sx][sy].roomno = NO_ROOM;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
joinm:
|
|
/*
|
|
* Ok, now we can actually join the regions with fg_typ's.
|
|
* The rooms are already sorted due to the previous loop,
|
|
* so don't call sort_rooms(), which can screw up the roomno's
|
|
* validity in the levl structure.
|
|
*/
|
|
for (croom = &svr.rooms[0], croom2 = croom + 1;
|
|
croom2 < &svr.rooms[svn.nroom]; ) {
|
|
/* pick random starting and end locations for "corridor" */
|
|
if (!somexy(croom, &sm) || !somexy(croom2, &em)) {
|
|
/* ack! -- the level is going to be busted */
|
|
/* arbitrarily pick centers of both rooms and hope for the best */
|
|
impossible("No start/end room loc in join_map.");
|
|
sm.x = croom->lx + ((croom->hx - croom->lx) / 2);
|
|
sm.y = croom->ly + ((croom->hy - croom->ly) / 2);
|
|
em.x = croom2->lx + ((croom2->hx - croom2->lx) / 2);
|
|
em.y = croom2->ly + ((croom2->hy - croom2->ly) / 2);
|
|
}
|
|
|
|
(void) dig_corridor(&sm, &em, FALSE, fg_typ, bg_typ);
|
|
|
|
/* choose next region to join */
|
|
/* only increment croom if croom and croom2 are non-overlapping */
|
|
if (croom2->lx > croom->hx
|
|
|| ((croom2->ly > croom->hy || croom2->hy < croom->ly)
|
|
&& rn2(3))) {
|
|
croom = croom2;
|
|
}
|
|
croom2++; /* always increment the next room */
|
|
}
|
|
join_map_cleanup();
|
|
}
|
|
|
|
staticfn void
|
|
finish_map(
|
|
schar fg_typ,
|
|
schar bg_typ,
|
|
boolean lit,
|
|
boolean walled,
|
|
boolean icedpools)
|
|
{
|
|
int i, j;
|
|
|
|
if (walled)
|
|
wallify_map(1, 0, COLNO-1, ROWNO-1);
|
|
|
|
if (lit) {
|
|
for (i = 1; i < COLNO; i++)
|
|
for (j = 0; j < ROWNO; j++)
|
|
if ((!IS_ROCK(fg_typ) && levl[i][j].typ == fg_typ)
|
|
|| (!IS_ROCK(bg_typ) && levl[i][j].typ == bg_typ)
|
|
|| (bg_typ == TREE && levl[i][j].typ == bg_typ)
|
|
|| (walled && IS_WALL(levl[i][j].typ)))
|
|
levl[i][j].lit = TRUE;
|
|
for (i = 0; i < svn.nroom; i++)
|
|
svr.rooms[i].rlit = 1;
|
|
}
|
|
/* light lava even if everything's otherwise unlit;
|
|
ice might be frozen pool rather than frozen moat */
|
|
for (i = 1; i < COLNO; i++)
|
|
for (j = 0; j < ROWNO; j++) {
|
|
if (levl[i][j].typ == LAVAPOOL)
|
|
levl[i][j].lit = TRUE;
|
|
else if (levl[i][j].typ == ICE)
|
|
levl[i][j].icedpool = icedpools ? ICED_POOL : ICED_MOAT;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* TODO: If we really want to remove rooms after a map is plopped down
|
|
* in a special level, this needs to be rewritten - the maps may have
|
|
* holes in them ("x" mapchar), leaving parts of rooms still on the map.
|
|
*
|
|
* When level processed by join_map is overlaid by a MAP, some rooms may no
|
|
* longer be valid. All rooms in the region lx <= x < hx, ly <= y < hy are
|
|
* removed. Rooms partially in the region are truncated. This function
|
|
* must be called before the REGIONs or ROOMs of the map are processed, or
|
|
* those rooms will be removed as well. Assumes roomno fields in the
|
|
* region are already cleared, and roomno and irregular fields outside the
|
|
* region are all set.
|
|
*/
|
|
void
|
|
remove_rooms(int lx, int ly, int hx, int hy)
|
|
{
|
|
int i;
|
|
struct mkroom *croom;
|
|
|
|
for (i = svn.nroom - 1; i >= 0; --i) {
|
|
croom = &svr.rooms[i];
|
|
if (croom->hx < lx || croom->lx >= hx || croom->hy < ly
|
|
|| croom->ly >= hy)
|
|
continue; /* no overlap */
|
|
|
|
if (croom->lx < lx || croom->hx >= hx || croom->ly < ly
|
|
|| croom->hy >= hy) { /* partial overlap */
|
|
/* TODO: ensure remaining parts of room are still joined */
|
|
|
|
if (!croom->irregular)
|
|
impossible("regular room in joined map");
|
|
} else {
|
|
/* total overlap, remove the room */
|
|
remove_room((unsigned) i);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Remove roomno from the rooms array, decrementing nroom.
|
|
* The last room is swapped with the being-removed room and locations
|
|
* within it have their roomno field updated. Other rooms are unaffected.
|
|
* Assumes level structure contents corresponding to roomno have already
|
|
* been reset.
|
|
* Currently handles only the removal of rooms that have no subrooms.
|
|
*/
|
|
staticfn void
|
|
remove_room(unsigned int roomno)
|
|
{
|
|
struct mkroom *croom = &svr.rooms[roomno];
|
|
struct mkroom *maxroom = &svr.rooms[--svn.nroom];
|
|
int i, j;
|
|
unsigned oroomno;
|
|
|
|
if (croom != maxroom) {
|
|
/* since the order in the array only matters for making corridors,
|
|
* copy the last room over the one being removed on the assumption
|
|
* that corridors have already been dug. */
|
|
*croom = *maxroom;
|
|
|
|
/* since maxroom moved, update affected level roomno values */
|
|
oroomno = svn.nroom + ROOMOFFSET;
|
|
roomno += ROOMOFFSET;
|
|
for (i = croom->lx; i <= croom->hx; ++i)
|
|
for (j = croom->ly; j <= croom->hy; ++j) {
|
|
if (levl[i][j].roomno == oroomno)
|
|
levl[i][j].roomno = roomno;
|
|
}
|
|
}
|
|
|
|
maxroom->hx = -1; /* just like add_room */
|
|
}
|
|
|
|
#define N_P1_ITER 1 /* tune map generation via this value */
|
|
#define N_P2_ITER 1 /* tune map generation via this value */
|
|
#define N_P3_ITER 2 /* tune map smoothing via this value */
|
|
|
|
boolean
|
|
litstate_rnd(int litstate)
|
|
{
|
|
if (litstate < 0)
|
|
return (rnd(1 + abs(depth(&u.uz))) < 11 && rn2(77)) ? TRUE : FALSE;
|
|
return (boolean) litstate;
|
|
}
|
|
|
|
void
|
|
mkmap(lev_init *init_lev)
|
|
{
|
|
schar bg_typ = init_lev->bg, fg_typ = init_lev->fg;
|
|
boolean smooth = init_lev->smoothed, join = init_lev->joined;
|
|
xint16 lit = init_lev->lit, walled = init_lev->walled;
|
|
int i;
|
|
|
|
lit = litstate_rnd(lit);
|
|
|
|
gn.new_locations = (char *) alloc((WIDTH + 1) * HEIGHT);
|
|
|
|
init_map(bg_typ);
|
|
init_fill(bg_typ, fg_typ);
|
|
|
|
for (i = 0; i < N_P1_ITER; i++)
|
|
pass_one(bg_typ, fg_typ);
|
|
|
|
for (i = 0; i < N_P2_ITER; i++)
|
|
pass_two(bg_typ, fg_typ);
|
|
|
|
if (smooth)
|
|
for (i = 0; i < N_P3_ITER; i++)
|
|
pass_three(bg_typ, fg_typ);
|
|
|
|
if (join)
|
|
join_map(bg_typ, fg_typ);
|
|
|
|
finish_map(fg_typ, bg_typ, (boolean) lit, (boolean) walled,
|
|
init_lev->icedpools);
|
|
/* a walled, joined level is cavernous, not mazelike -dlc */
|
|
if (walled && join) {
|
|
svl.level.flags.is_maze_lev = FALSE;
|
|
svl.level.flags.is_cavernous_lev = TRUE;
|
|
}
|
|
free(gn.new_locations);
|
|
}
|
|
|
|
/*mkmap.c*/
|