Fix rare door in corner of room bug

There were couple reports of doors being generated in a corner
of a room.  This happened for randomly generated irregular rooms,
because the code that was deciding if a corridor starting or
ending location was good did not handle irregular rooms at all.

This changes the corridor code so it can now generate start and
end points properly for any valid position in irregular rooms,
instead of only on the edges.  This means the corridor sometimes
meanders a bit more than before, because it tries to find
the end point away from the edge of the room rectangle.

Also added is sanity checking for the randomly generated rooms
and corridors level, testing for door placement and room connectivity.

And another fix for a rare special case where dig_corridor
created a zero-tile long corridor; the entrance door was placed,
but there was nothing behind it.

Fixes github #1269 and #1385
This commit is contained in:
Pasi Kallinen
2025-03-16 09:46:50 +02:00
parent ef6ca6c5fa
commit b2c071bc66
4 changed files with 205 additions and 55 deletions

View File

@@ -2972,7 +2972,7 @@ extern void flip_level_rnd(int, boolean);
extern boolean check_room(coordxy *, coordxy *, coordxy *, coordxy *, boolean) NONNULLPTRS;
extern boolean create_room(coordxy, coordxy, coordxy, coordxy,
coordxy, coordxy, xint16, xint16);
extern boolean dig_corridor(coord *, coord *, boolean, schar, schar) NONNULLARG12;
extern boolean dig_corridor(coord *, coord *, int *, boolean, schar, schar) NONNULLARG12;
extern void fill_special_room(struct mkroom *) NO_NNARGS;
extern void wallify_map(coordxy, coordxy, coordxy, coordxy);
extern boolean load_special(const char *) NONNULLARG1;

View File

@@ -23,6 +23,8 @@ staticfn void mk_knox_portal(coordxy, coordxy);
staticfn void makevtele(void);
staticfn void fill_ordinary_room(struct mkroom *, boolean) NONNULLARG1;
staticfn void themerooms_post_level_generate(void);
staticfn boolean chk_okdoor(coordxy, coordxy);
staticfn void mklev_sanity_check(void);
staticfn void makelevel(void);
staticfn boolean bydoor(coordxy, coordxy);
staticfn void mktrap_victim(struct trap *);
@@ -42,8 +44,10 @@ staticfn void do_room_or_subroom(struct mkroom *,
coordxy, coordxy, coordxy, coordxy,
boolean, schar, boolean, boolean);
staticfn void makerooms(void);
staticfn boolean door_into_nonjoined(coordxy, coordxy);
staticfn boolean finddpos(coord *, coordxy, coordxy, coordxy, coordxy);
staticfn boolean good_rm_wall_doorpos(coordxy, coordxy, int, struct mkroom *);
staticfn boolean finddpos_shift(coordxy *, coordxy *, int, struct mkroom *);
staticfn boolean finddpos(coord *, int, struct mkroom *);
#define create_vault() create_room(-1, -1, 2, 2, -1, -1, VAULT, TRUE)
#define init_vault() gv.vault_x = -1
@@ -63,57 +67,135 @@ mkroom_cmp(const genericptr vx, const genericptr vy)
return (x->lx > y->lx);
}
/* Return TRUE if a door placed at (x, y) which otherwise passes okdoor()
* checks would be connecting into an area that was declared as joined=false.
* Checking for this in finddpos() enables us to have rooms with sub-areas
* (such as shops) that will never randomly generate unwanted doors in order
* to connect them up to other areas.
*/
/* is x,y a good location for a door into room? */
staticfn boolean
door_into_nonjoined(coordxy x, coordxy y)
good_rm_wall_doorpos(coordxy x, coordxy y, int dir, struct mkroom *room)
{
coordxy tx, ty, i;
coordxy tx, ty;
int rmno;
for (i = 0; i < 4; i++) {
tx = x + xdir[dirs_ord[i]];
ty = y + ydir[dirs_ord[i]];
if (!isok(tx, ty) || IS_OBSTRUCTED(levl[tx][ty].typ))
continue;
if (!isok(x, y) || !room->needjoining)
return FALSE;
/* Is this connecting to a room that doesn't want joining? */
if (levl[tx][ty].roomno >= ROOMOFFSET
&& !svr.rooms[levl[tx][ty].roomno - ROOMOFFSET].needjoining) {
return TRUE;
if (!(levl[x][y].typ == HWALL
|| levl[x][y].typ == VWALL
|| IS_DOOR(levl[x][y].typ)
|| levl[x][y].typ == SDOOR))
return FALSE;
if (bydoor(x, y))
return FALSE;
tx = x + xdir[dir];
ty = y + ydir[dir];
if (!isok(tx,ty) || IS_OBSTRUCTED(levl[tx][ty].typ))
return FALSE;
rmno = (room - svr.rooms) + ROOMOFFSET;
if (rmno != levl[tx][ty].roomno)
return FALSE;
return TRUE;
}
/* starting from x,y going towards dir, find a good location for a door */
staticfn boolean
finddpos_shift(coordxy *x, coordxy *y, int dir, struct mkroom *aroom)
{
coordxy dx, dy;
dir = DIR_180(dir);
dx = xdir[dir];
dy = ydir[dir];
if (good_rm_wall_doorpos(*x, *y, dir, aroom))
return TRUE;
/* irregular rooms may have the room wall away from the room rectangular
area; go into the area until we encounter something */
if (aroom->irregular) {
coordxy rx = *x, ry = *y;
boolean fail = FALSE;
while (!fail && isok(rx, ry)
&& (levl[rx][ry].typ == STONE || levl[rx][ry].typ == CORR)) {
rx += dx;
ry += dy;
if (good_rm_wall_doorpos(rx, ry, dir, aroom)) {
*x = rx;
*y = ry;
return TRUE;
}
if (!(levl[rx][ry].typ == STONE || levl[rx][ry].typ == CORR))
fail = TRUE;
if (rx < aroom->lx || rx > aroom->hx
|| ry < aroom->ly || ry > aroom->hy)
fail = TRUE;
}
}
return FALSE;
}
/* find a valid door position at room edge.
dir is the preferred edge of the room.
if found, returns TRUE and the coordinate in cc */
staticfn boolean
finddpos(
coord *cc,
coordxy xl, coordxy yl,
coordxy xh, coordxy yh)
finddpos(coord *cc, int dir, struct mkroom *aroom)
{
coordxy x, y;
coordxy x1, y1, x2, y2;
int tryct = 0;
x = rn1(xh - xl + 1, xl);
y = rn1(yh - yl + 1, yl);
if (okdoor(x, y) && !door_into_nonjoined(x, y))
goto gotit;
switch (dir) {
case DIR_N:
x1 = aroom->lx;
x2 = aroom->hx;
y1 = aroom->ly - 1;
y2 = aroom->ly - 1;
break;
case DIR_S:
x1 = aroom->lx;
x2 = aroom->hx;
y1 = aroom->hy + 1;
y2 = aroom->hy + 1;
break;
case DIR_W:
x1 = aroom->lx - 1;
x2 = aroom->lx - 1;
y1 = aroom->ly;
y2 = aroom->hy;
break;
case DIR_E:
x1 = aroom->hx + 1;
x2 = aroom->hx + 1;
y1 = aroom->ly;
y2 = aroom->hy;
break;
default:
impossible("finddpos: illegal dir");
return FALSE;
}
for (x = xl; x <= xh; x++)
for (y = yl; y <= yh; y++)
if (okdoor(x, y) && !door_into_nonjoined(x, y))
/* try random points */
do {
x = (x2 - x1) ? rn1(x2 - x1 + 1, x1) : x1;
y = (y2 - y1) ? rn1(y2 - y1 + 1, y1) : y1;
if (finddpos_shift(&x, &y, dir, aroom))
goto gotit;
} while (++tryct < 20);
/* try all the points */
for (x = x1; x <= x2; x++)
for (y = y1; y <= y2; y++)
if (finddpos_shift(&x, &y, dir, aroom))
goto gotit;
for (x = xl; x <= xh; x++)
for (y = yl; y <= yh; y++)
if (IS_DOOR(levl[x][y].typ) || levl[x][y].typ == SDOOR)
goto gotit;
/* cannot find something reasonable -- strange */
cc->x = xl;
cc->y = yh;
cc->x = x1;
cc->y = y1;
return FALSE;
gotit:
cc->x = x;
@@ -349,6 +431,8 @@ join(int a, int b, boolean nxcor)
coordxy tx, ty, xx, yy;
struct mkroom *croom, *troom;
int dx, dy;
int npoints;
boolean dig_result;
croom = &svr.rooms[a];
troom = &svr.rooms[b];
@@ -366,36 +450,36 @@ join(int a, int b, boolean nxcor)
dy = 0;
xx = croom->hx + 1;
tx = troom->lx - 1;
if (!finddpos(&cc, xx, croom->ly, xx, croom->hy))
if (!finddpos(&cc, DIR_E, croom))
return;
if (!finddpos(&tt, tx, troom->ly, tx, troom->hy))
if (!finddpos(&tt, DIR_W, troom))
return;
} else if (troom->hy < croom->ly) {
dy = -1;
dx = 0;
yy = croom->ly - 1;
ty = troom->hy + 1;
if (!finddpos(&cc, croom->lx, yy, croom->hx, yy))
if (!finddpos(&cc, DIR_N, croom))
return;
if (!finddpos(&tt, troom->lx, ty, troom->hx, ty))
if (!finddpos(&tt, DIR_S, troom))
return;
} else if (troom->hx < croom->lx) {
dx = -1;
dy = 0;
xx = croom->lx - 1;
tx = troom->hx + 1;
if (!finddpos(&cc, xx, croom->ly, xx, croom->hy))
if (!finddpos(&cc, DIR_W, croom))
return;
if (!finddpos(&tt, tx, troom->ly, tx, troom->hy))
if (!finddpos(&tt, DIR_E, troom))
return;
} else {
dy = 1;
dx = 0;
yy = croom->hy + 1;
ty = troom->ly - 1;
if (!finddpos(&cc, croom->lx, yy, croom->hx, yy))
if (!finddpos(&cc, DIR_S, croom))
return;
if (!finddpos(&tt, troom->lx, ty, troom->hx, ty))
if (!finddpos(&tt, DIR_N, troom))
return;
}
xx = cc.x;
@@ -404,16 +488,20 @@ join(int a, int b, boolean nxcor)
ty = tt.y - dy;
if (nxcor && levl[xx + dx][yy + dy].typ != STONE)
return;
if (okdoor(xx, yy) || !nxcor)
dodoor(xx, yy, croom);
org.x = xx + dx;
org.y = yy + dy;
dest.x = tx;
dest.y = ty;
if (!dig_corridor(&org, &dest, nxcor,
svl.level.flags.arboreal ? ROOM : CORR, STONE))
dig_result = dig_corridor(&org, &dest, &npoints, nxcor,
svl.level.flags.arboreal ? ROOM : CORR, STONE);
/* we created at least 1 tile of corridor, even if it failed */
if ((npoints > 0) && (okdoor(xx, yy) || !nxcor))
dodoor(xx, yy, croom);
if (!dig_result)
return;
/* we succeeded in digging the corridor */
@@ -616,13 +704,11 @@ place_niche(
if (rn2(2)) {
*dy = 1;
if (!finddpos(&dd, aroom->lx, aroom->hy + 1,
aroom->hx, aroom->hy + 1))
if (!finddpos(&dd, DIR_S, aroom))
return FALSE;
} else {
*dy = -1;
if (!finddpos(&dd, aroom->lx, aroom->ly - 1,
aroom->hx, aroom->ly - 1))
if (!finddpos(&dd, DIR_N, aroom))
return FALSE;
}
*xx = dd.x;
@@ -1103,6 +1189,60 @@ themerooms_post_level_generate(void)
lua_gc(themes, LUA_GCCOLLECT);
}
/* if x,y is door, does it open into solid terrain */
staticfn boolean
chk_okdoor(coordxy x, coordxy y)
{
if (IS_DOOR(levl[x][y].typ)) {
if (levl[x][y].horizontal) {
if ((isok(x, y-1) && (levl[x][y-1].typ > TREE))
&& (isok(x, y+1) && (levl[x][y+1].typ <= TREE)))
return FALSE;
if ((isok(x, y-1) && (levl[x][y-1].typ <= TREE))
&& (isok(x, y+1) && (levl[x][y+1].typ > TREE)))
return FALSE;
} else {
if ((isok(x-1, y) && (levl[x-1][y].typ > TREE))
&& (isok(x+1, y) && (levl[x+1][y].typ <= TREE)))
return FALSE;
if ((isok(x-1, y) && (levl[x-1][y].typ <= TREE))
&& (isok(x+1, y) && (levl[x+1][y].typ > TREE)))
return FALSE;
}
return TRUE;
}
return TRUE;
}
/* check mklev created level sanity */
staticfn void
mklev_sanity_check(void)
{
coordxy x, y;
int i;
int rmno = -1;
if (!(iflags.sanity_check || iflags.debug_fuzzer))
return;
for (y = 0; y < ROWNO; y++) {
for (x = 1; x < COLNO; x++) {
if (!chk_okdoor(x,y))
impossible("levl[%i][%i] door not ok", x, y);
}
}
for (i = 0; i < svn.nroom; i++) {
if (!svr.rooms[i].needjoining)
continue;
if (rmno == -1)
rmno = gs.smeq[i];
if (rmno != -1 && gs.smeq[i] != rmno)
impossible("room %i not connected?", i);
}
}
staticfn void
makelevel(void)
{
@@ -1166,6 +1306,8 @@ makelevel(void)
makecorridors();
make_niches();
mklev_sanity_check();
/* make a secret treasure vault, not connected to the rest */
if (do_vault()) {
coordxy w, h;

View File

@@ -313,7 +313,7 @@ join_map(schar bg_typ, schar fg_typ)
em.y = croom2->ly + ((croom2->hy - croom2->ly) / 2);
}
(void) dig_corridor(&sm, &em, FALSE, fg_typ, bg_typ);
(void) dig_corridor(&sm, &em, NULL, FALSE, fg_typ, bg_typ);
/* choose next region to join */
/* only increment croom if croom and croom2 are non-overlapping */

View File

@@ -2535,10 +2535,12 @@ search_door(
* Dig a corridor between two points, using terrain ftyp.
* if nxcor is TRUE, he corridor may be blocked by a boulder,
* or just end without reaching the destination.
* if not null, npoints has the number of map locations used
*/
boolean
dig_corridor(
coord *org, coord *dest,
int *npoints,
boolean nxcor,
schar ftyp, schar btyp)
{
@@ -2546,6 +2548,8 @@ dig_corridor(
struct rm *crm;
int tx, ty, xx, yy;
if (npoints)
*npoints = 0;
xx = org->x;
yy = org->y;
tx = dest->x;
@@ -2582,8 +2586,12 @@ dig_corridor(
crm = &levl[xx][yy];
if (crm->typ == btyp) {
if (ftyp == CORR && maybe_sdoor(100)) {
if (npoints)
(*npoints)++;
crm->typ = SCORR;
} else {
if (npoints)
(*npoints)++;
crm->typ = ftyp;
if (nxcor && !rn2(50))
(void) mksobj_at(BOULDER, xx, yy, TRUE, FALSE);
@@ -2704,7 +2712,7 @@ create_corridor(corridor *c)
dest.x++;
break;
}
(void) dig_corridor(&org, &dest, FALSE, CORR, STONE);
(void) dig_corridor(&org, &dest, NULL, FALSE, CORR, STONE);
}
}