Files
nethack/src/mkmap.c
nhmall 6c0ae092c6 distinguish global variables that get written to savefile
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
2024-07-13 14:57:50 -04:00

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*/