diff --git a/include/extern.h b/include/extern.h index 3a7041556..78ca5fd26 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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; diff --git a/src/mklev.c b/src/mklev.c index 6cffe2a52..6939d3906 100644 --- a/src/mklev.c +++ b/src/mklev.c @@ -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; diff --git a/src/mkmap.c b/src/mkmap.c index 2c5fb53ec..fdf50649c 100644 --- a/src/mkmap.c +++ b/src/mkmap.c @@ -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 */ diff --git a/src/sp_lev.c b/src/sp_lev.c index 73d349515..8bd135217 100644 --- a/src/sp_lev.c +++ b/src/sp_lev.c @@ -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); } }