/* NetHack 3.5 mkmaze.c $NHDT-Date: 1426465437 2015/03/16 00:23:57 $ $NHDT-Branch: debug $:$NHDT-Revision: 1.20 $ */ /* SCCS Id: @(#)mkmaze.c 3.5 2007/06/18 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" #include "sp_lev.h" #include "lev.h" /* save & restore info */ /* from sp_lev.c, for fixup_special() */ extern lev_region *lregions; extern int num_lregions; STATIC_DCL boolean FDECL(iswall,(int,int)); STATIC_DCL boolean FDECL(iswall_or_stone,(int,int)); STATIC_DCL boolean FDECL(is_solid,(int,int)); STATIC_DCL int FDECL(extend_spine, (int [3][3], int, int, int)); STATIC_DCL boolean FDECL(okay,(int,int,int)); STATIC_DCL void FDECL(maze0xy,(coord *)); STATIC_DCL boolean FDECL(put_lregion_here,(XCHAR_P,XCHAR_P,XCHAR_P, XCHAR_P,XCHAR_P,XCHAR_P,XCHAR_P,BOOLEAN_P,d_level *)); STATIC_DCL void NDECL(fixup_special); STATIC_DCL void FDECL(move, (int *,int *,int)); STATIC_DCL void NDECL(setup_waterlevel); STATIC_DCL void NDECL(unsetup_waterlevel); STATIC_OVL boolean iswall(x,y) int x,y; { register int type; if (!isok(x,y)) return FALSE; type = levl[x][y].typ; return (IS_WALL(type) || IS_DOOR(type) || type == SDOOR || type == IRONBARS); } STATIC_OVL boolean iswall_or_stone(x,y) int x,y; { register int type; /* out of bounds = stone */ if (!isok(x,y)) return TRUE; type = levl[x][y].typ; return (type == STONE || IS_WALL(type) || IS_DOOR(type) || type == SDOOR || type == IRONBARS); } /* return TRUE if out of bounds, wall or rock */ STATIC_OVL boolean is_solid(x,y) int x, y; { return (!isok(x,y) || IS_STWALL(levl[x][y].typ)); } /* * Return 1 (not TRUE - we're doing bit vectors here) if we want to extend * a wall spine in the (dx,dy) direction. Return 0 otherwise. * * To extend a wall spine in that direction, first there must be a wall there. * Then, extend a spine unless the current position is surrounded by walls * in the direction given by (dx,dy). E.g. if 'x' is our location, 'W' * a wall, '.' a room, 'a' anything (we don't care), and our direction is * (0,1) - South or down - then: * * a a a * W x W This would not extend a spine from x down * W W W (a corridor of walls is formed). * * a a a * W x W This would extend a spine from x down. * . W W */ STATIC_OVL int extend_spine(locale, wall_there, dx, dy) int locale[3][3]; int wall_there, dx, dy; { int spine, nx, ny; nx = 1 + dx; ny = 1 + dy; if (wall_there) { /* wall in that direction */ if (dx) { if (locale[ 1][0] && locale[ 1][2] && /* EW are wall/stone */ locale[nx][0] && locale[nx][2]) { /* diag are wall/stone */ spine = 0; } else { spine = 1; } } else { /* dy */ if (locale[0][ 1] && locale[2][ 1] && /* NS are wall/stone */ locale[0][ny] && locale[2][ny]) { /* diag are wall/stone */ spine = 0; } else { spine = 1; } } } else { spine = 0; } return spine; } /* * Wall cleanup. This function has two purposes: (1) remove walls that * are totally surrounded by stone - they are redundant. (2) correct * the types so that they extend and connect to each other. */ void wallification(x1, y1, x2, y2) int x1, y1, x2, y2; { uchar type; register int x,y; struct rm *lev; int bits; int locale[3][3]; /* rock or wall status surrounding positions */ /* * Value 0 represents a free-standing wall. It could be anything, * so even though this table says VWALL, we actually leave whatever * typ was there alone. */ static xchar spine_array[16] = { VWALL, HWALL, HWALL, HWALL, VWALL, TRCORNER, TLCORNER, TDWALL, VWALL, BRCORNER, BLCORNER, TUWALL, VWALL, TLWALL, TRWALL, CROSSWALL }; /* sanity check on incoming variables */ if (x1<0 || x2>=COLNO || x1>x2 || y1<0 || y2>=ROWNO || y1>y2) panic("wallification: bad bounds (%d,%d) to (%d,%d)",x1,y1,x2,y2); /* Step 1: change walls surrounded by rock to rock. */ for(x = x1; x <= x2; x++) for(y = y1; y <= y2; y++) { lev = &levl[x][y]; type = lev->typ; if (IS_WALL(type) && type != DBWALL) { if (is_solid(x-1,y-1) && is_solid(x-1,y ) && is_solid(x-1,y+1) && is_solid(x, y-1) && is_solid(x, y+1) && is_solid(x+1,y-1) && is_solid(x+1,y ) && is_solid(x+1,y+1)) lev->typ = STONE; } } /* * Step 2: set the correct wall type. We can't combine steps * 1 and 2 into a single sweep because we depend on knowing if * the surrounding positions are stone. */ for(x = x1; x <= x2; x++) for(y = y1; y <= y2; y++) { lev = &levl[x][y]; type = lev->typ; if ( !(IS_WALL(type) && type != DBWALL)) continue; /* set the locations TRUE if rock or wall or out of bounds */ locale[0][0] = iswall_or_stone(x-1,y-1); locale[1][0] = iswall_or_stone( x,y-1); locale[2][0] = iswall_or_stone(x+1,y-1); locale[0][1] = iswall_or_stone(x-1, y); locale[2][1] = iswall_or_stone(x+1, y); locale[0][2] = iswall_or_stone(x-1,y+1); locale[1][2] = iswall_or_stone( x,y+1); locale[2][2] = iswall_or_stone(x+1,y+1); /* determine if wall should extend to each direction NSEW */ bits = (extend_spine(locale, iswall(x,y-1), 0, -1) << 3) | (extend_spine(locale, iswall(x,y+1), 0, 1) << 2) | (extend_spine(locale, iswall(x+1,y), 1, 0) << 1) | extend_spine(locale, iswall(x-1,y), -1, 0); /* don't change typ if wall is free-standing */ if (bits) lev->typ = spine_array[bits]; } } STATIC_OVL boolean okay(x,y,dir) int x,y; register int dir; { move(&x,&y,dir); move(&x,&y,dir); if(x<3 || y<3 || x>x_maze_max || y>y_maze_max || levl[x][y].typ != 0) return(FALSE); return(TRUE); } STATIC_OVL void maze0xy(cc) /* find random starting point for maze generation */ coord *cc; { cc->x = 3 + 2*rn2((x_maze_max>>1) - 1); cc->y = 3 + 2*rn2((y_maze_max>>1) - 1); return; } /* * Bad if: * pos is occupied OR * pos is inside restricted region (lx,ly,hx,hy) OR * NOT (pos is corridor and a maze level OR pos is a room OR pos is air) */ boolean bad_location(x, y, lx, ly, hx, hy) xchar x, y; xchar lx, ly, hx, hy; { return((boolean)(occupied(x, y) || within_bounded_area(x,y, lx,ly, hx,hy) || !((levl[x][y].typ == CORR && level.flags.is_maze_lev) || levl[x][y].typ == ROOM || levl[x][y].typ == AIR))); } /* pick a location in area (lx, ly, hx, hy) but not in (nlx, nly, nhx, nhy) */ /* and place something (based on rtype) in that region */ void place_lregion(lx, ly, hx, hy, nlx, nly, nhx, nhy, rtype, lev) xchar lx, ly, hx, hy; xchar nlx, nly, nhx, nhy; xchar rtype; d_level *lev; { int trycnt; boolean oneshot; xchar x, y; if(!lx) { /* default to whole level */ /* * if there are rooms and this a branch, let place_branch choose * the branch location (to avoid putting branches in corridors). */ if(rtype == LR_BRANCH && nroom) { place_branch(Is_branchlev(&u.uz), 0, 0); return; } lx = 1; hx = COLNO-1; ly = 1; hy = ROWNO-1; } /* first a probabilistic approach */ oneshot = (lx == hx && ly == hy); for (trycnt = 0; trycnt < 200; trycnt++) { x = rn1((hx - lx) + 1, lx); y = rn1((hy - ly) + 1, ly); if (put_lregion_here(x,y,nlx,nly,nhx,nhy,rtype,oneshot,lev)) return; } /* then a deterministic one */ oneshot = TRUE; for (x = lx; x <= hx; x++) for (y = ly; y <= hy; y++) if (put_lregion_here(x,y,nlx,nly,nhx,nhy,rtype,oneshot,lev)) return; impossible("Couldn't place lregion type %d!", rtype); } STATIC_OVL boolean put_lregion_here(x,y,nlx,nly,nhx,nhy,rtype,oneshot,lev) xchar x, y; xchar nlx, nly, nhx, nhy; xchar rtype; boolean oneshot; d_level *lev; { if (bad_location(x, y, nlx, nly, nhx, nhy)) { if (!oneshot) { return FALSE; /* caller should try again */ } else { /* Must make do with the only location possible; avoid failure due to a misplaced trap. It might still fail if there's a dungeon feature here. */ struct trap *t = t_at(x,y); if (t && t->ttyp != MAGIC_PORTAL) deltrap(t); if (bad_location(x, y, nlx, nly, nhx, nhy)) return FALSE; } } switch (rtype) { case LR_TELE: case LR_UPTELE: case LR_DOWNTELE: /* "something" means the player in this case */ if(MON_AT(x, y)) { /* move the monster if no choice, or just try again */ if(oneshot) (void) rloc(m_at(x,y), FALSE); else return(FALSE); } u_on_newpos(x, y); break; case LR_PORTAL: mkportal(x, y, lev->dnum, lev->dlevel); break; case LR_DOWNSTAIR: case LR_UPSTAIR: mkstairs(x, y, (char)rtype, (struct mkroom *)0); break; case LR_BRANCH: place_branch(Is_branchlev(&u.uz), x, y); break; } return(TRUE); } static boolean was_waterlevel; /* ugh... this shouldn't be needed */ /* this is special stuff that the level compiler cannot (yet) handle */ STATIC_OVL void fixup_special() { register lev_region *r = lregions; struct d_level lev; register int x, y; struct mkroom *croom; boolean added_branch = FALSE; if (was_waterlevel) { was_waterlevel = FALSE; u.uinwater = 0; unsetup_waterlevel(); } if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) { level.flags.hero_memory = 0; was_waterlevel = TRUE; /* water level is an odd beast - it has to be set up before calling place_lregions etc. */ setup_waterlevel(); } for(x = 0; x < num_lregions; x++, r++) { switch(r->rtype) { case LR_BRANCH: added_branch = TRUE; goto place_it; case LR_PORTAL: if(*r->rname.str >= '0' && *r->rname.str <= '9') { /* "chutes and ladders" */ lev = u.uz; lev.dlevel = atoi(r->rname.str); } else { s_level *sp = find_level(r->rname.str); lev = sp->dlevel; } /* fall into... */ case LR_UPSTAIR: case LR_DOWNSTAIR: place_it: place_lregion(r->inarea.x1, r->inarea.y1, r->inarea.x2, r->inarea.y2, r->delarea.x1, r->delarea.y1, r->delarea.x2, r->delarea.y2, r->rtype, &lev); break; case LR_TELE: case LR_UPTELE: case LR_DOWNTELE: /* save the region outlines for goto_level() */ if(r->rtype == LR_TELE || r->rtype == LR_UPTELE) { updest.lx = r->inarea.x1; updest.ly = r->inarea.y1; updest.hx = r->inarea.x2; updest.hy = r->inarea.y2; updest.nlx = r->delarea.x1; updest.nly = r->delarea.y1; updest.nhx = r->delarea.x2; updest.nhy = r->delarea.y2; } if(r->rtype == LR_TELE || r->rtype == LR_DOWNTELE) { dndest.lx = r->inarea.x1; dndest.ly = r->inarea.y1; dndest.hx = r->inarea.x2; dndest.hy = r->inarea.y2; dndest.nlx = r->delarea.x1; dndest.nly = r->delarea.y1; dndest.nhx = r->delarea.x2; dndest.nhy = r->delarea.y2; } /* place_lregion gets called from goto_level() */ break; } if (r->rname.str) free((genericptr_t) r->rname.str), r->rname.str = 0; } /* place dungeon branch if not placed above */ if (!added_branch && Is_branchlev(&u.uz)) { place_lregion(0,0,0,0,0,0,0,0,LR_BRANCH,(d_level *)0); } /* Still need to add some stuff to level file */ if (Is_medusa_level(&u.uz)) { struct obj *otmp; int tryct; croom = &rooms[0]; /* only one room on the medusa level */ for (tryct = rnd(4); tryct; tryct--) { x = somex(croom); y = somey(croom); if (goodpos(x, y, (struct monst *)0, 0)) { otmp = mk_tt_object(STATUE, x, y); while (otmp && (poly_when_stoned(&mons[otmp->corpsenm]) || pm_resistance(&mons[otmp->corpsenm],MR_STONE))) { /* set_corpsenm() handles weight too */ set_corpsenm(otmp, rndmonnum()); } } } if (rn2(2)) otmp = mk_tt_object(STATUE, somex(croom), somey(croom)); else /* Medusa statues don't contain books */ otmp = mkcorpstat(STATUE, (struct monst *)0, (struct permonst *)0, somex(croom), somey(croom), CORPSTAT_NONE); if (otmp) { while (pm_resistance(&mons[otmp->corpsenm],MR_STONE) || poly_when_stoned(&mons[otmp->corpsenm])) { /* set_corpsenm() handles weight too */ set_corpsenm(otmp, rndmonnum()); } } } else if(Is_wiz1_level(&u.uz)) { croom = search_special(MORGUE); create_secret_door(croom, W_SOUTH|W_EAST|W_WEST); } else if(Is_knox(&u.uz)) { /* using an unfilled morgue for rm id */ croom = search_special(MORGUE); /* avoid inappropriate morgue-related messages */ level.flags.graveyard = level.flags.has_morgue = 0; croom->rtype = OROOM; /* perhaps it should be set to VAULT? */ /* stock the main vault */ for(x = croom->lx; x <= croom->hx; x++) for(y = croom->ly; y <= croom->hy; y++) { (void) mkgold((long) rn1(300, 600), x, y); if (!rn2(3) && !is_pool(x,y)) (void)maketrap(x, y, rn2(3) ? LANDMINE : SPIKED_PIT); } } else if (Role_if(PM_PRIEST) && In_quest(&u.uz)) { /* less chance for undead corpses (lured from lower morgues) */ level.flags.graveyard = 1; } else if (Is_stronghold(&u.uz)) { level.flags.graveyard = 1; } else if(Is_sanctum(&u.uz)) { croom = search_special(TEMPLE); create_secret_door(croom, W_ANY); } else if(on_level(&u.uz, &orcus_level)) { register struct monst *mtmp, *mtmp2; /* it's a ghost town, get rid of shopkeepers */ for(mtmp = fmon; mtmp; mtmp = mtmp2) { mtmp2 = mtmp->nmon; if(mtmp->isshk) mongone(mtmp); } } if (lregions) free((genericptr_t) lregions), lregions = 0; num_lregions = 0; } void makemaz(s) register const char *s; { int x,y; char protofile[20]; s_level *sp = Is_special(&u.uz); coord mm; if(*s) { if(sp && sp->rndlevs) Sprintf(protofile, "%s-%d", s, rnd((int) sp->rndlevs)); else Strcpy(protofile, s); } else if(*(dungeons[u.uz.dnum].proto)) { if(dunlevs_in_dungeon(&u.uz) > 1) { if(sp && sp->rndlevs) Sprintf(protofile, "%s%d-%d", dungeons[u.uz.dnum].proto, dunlev(&u.uz), rnd((int) sp->rndlevs)); else Sprintf(protofile, "%s%d", dungeons[u.uz.dnum].proto, dunlev(&u.uz)); } else if(sp && sp->rndlevs) { Sprintf(protofile, "%s-%d", dungeons[u.uz.dnum].proto, rnd((int) sp->rndlevs)); } else Strcpy(protofile, dungeons[u.uz.dnum].proto); } else Strcpy(protofile, ""); /* SPLEVTYPE format is "level-choice,level-choice"... */ if (wizard && *protofile && sp && sp->rndlevs) { char *ep = getenv("SPLEVTYPE"); /* not nh_getenv */ if (ep) { /* rindex always succeeds due to code in prior block */ int len = (int)((rindex(protofile, '-') - protofile) + 1); while (ep && *ep) { if (!strncmp(ep, protofile, len)) { int pick = atoi(ep + len); /* use choice only if valid */ if (pick > 0 && pick <= (int) sp->rndlevs) Sprintf(protofile + len, "%d", pick); break; } else { ep = index(ep, ','); if (ep) ++ep; } } } } if(*protofile) { Strcat(protofile, LEV_EXT); if(load_special(protofile)) { fixup_special(); /* some levels can end up with monsters on dead mon list, including light source monsters */ dmonsfree(); return; /* no mazification right now */ } impossible("Couldn't load \"%s\" - making a maze.", protofile); } level.flags.is_maze_lev = TRUE; level.flags.corrmaze = !rn2(3); if (level.flags.corrmaze) for(x = 2; x < x_maze_max; x++) for(y = 2; y < y_maze_max; y++) levl[x][y].typ = STONE; else for(x = 2; x <= x_maze_max; x++) for(y = 2; y <= y_maze_max; y++) levl[x][y].typ = ((x % 2) && (y % 2)) ? STONE : HWALL; maze0xy(&mm); walkfrom((int) mm.x, (int) mm.y, 0); /* put a boulder at the maze center */ (void) mksobj_at(BOULDER, (int) mm.x, (int) mm.y, TRUE, FALSE); if (!level.flags.corrmaze) wallification(2, 2, x_maze_max, y_maze_max); mazexy(&mm); mkstairs(mm.x, mm.y, 1, (struct mkroom *)0); /* up */ if (!Invocation_lev(&u.uz)) { mazexy(&mm); mkstairs(mm.x, mm.y, 0, (struct mkroom *)0); /* down */ } else { /* choose "vibrating square" location */ #define x_maze_min 2 #define y_maze_min 2 /* * Pick a position where the stairs down to Moloch's Sanctum * level will ultimately be created. At that time, an area * will be altered: walls removed, moat and traps generated, * boulders destroyed. The position picked here must ensure * that that invocation area won't extend off the map. * * We actually allow up to 2 squares around the usual edge of * the area to get truncated; see mkinvokearea(mklev.c). */ #define INVPOS_X_MARGIN (6 - 2) #define INVPOS_Y_MARGIN (5 - 2) #define INVPOS_DISTANCE 11 int x_range = x_maze_max - x_maze_min - 2*INVPOS_X_MARGIN - 1, y_range = y_maze_max - y_maze_min - 2*INVPOS_Y_MARGIN - 1; if (x_range <= INVPOS_X_MARGIN || y_range <= INVPOS_Y_MARGIN || (x_range * y_range) <= (INVPOS_DISTANCE * INVPOS_DISTANCE)) debugpline2("inv_pos: maze is too small! (%d x %d)", x_maze_max, y_maze_max); inv_pos.x = inv_pos.y = 0; /*{occupied() => invocation_pos()}*/ do { x = rn1(x_range, x_maze_min + INVPOS_X_MARGIN + 1); y = rn1(y_range, y_maze_min + INVPOS_Y_MARGIN + 1); /* we don't want it to be too near the stairs, nor to be on a spot that's already in use (wall|trap) */ } while (x == xupstair || y == yupstair || /*(direct line)*/ abs(x - xupstair) == abs(y - yupstair) || distmin(x, y, xupstair, yupstair) <= INVPOS_DISTANCE || !SPACE_POS(levl[x][y].typ) || occupied(x, y)); inv_pos.x = x; inv_pos.y = y; #undef INVPOS_X_MARGIN #undef INVPOS_Y_MARGIN #undef INVPOS_DISTANCE #undef x_maze_min #undef y_maze_min } /* place branch stair or portal */ place_branch(Is_branchlev(&u.uz), 0, 0); for(x = rn1(8,11); x; x--) { mazexy(&mm); (void) mkobj_at(rn2(2) ? GEM_CLASS : 0, mm.x, mm.y, TRUE); } for(x = rn1(10,2); x; x--) { mazexy(&mm); (void) mksobj_at(BOULDER, mm.x, mm.y, TRUE, FALSE); } for (x = rn2(3); x; x--) { mazexy(&mm); (void) makemon(&mons[PM_MINOTAUR], mm.x, mm.y, NO_MM_FLAGS); } for(x = rn1(5,7); x; x--) { mazexy(&mm); (void) makemon((struct permonst *) 0, mm.x, mm.y, NO_MM_FLAGS); } for(x = rn1(6,7); x; x--) { mazexy(&mm); (void) mkgold(0L,mm.x,mm.y); } for(x = rn1(6,7); x; x--) mktrap(0,1,(struct mkroom *) 0, (coord*) 0); } #ifdef MICRO /* Make the mazewalk iterative by faking a stack. This is needed to * ensure the mazewalk is successful in the limited stack space of * the program. This iterative version uses the minimum amount of stack * that is totally safe. */ void walkfrom(x,y,typ) int x,y; schar typ; { #define CELLS (ROWNO * COLNO) / 4 /* a maze cell is 4 squares */ char mazex[CELLS + 1], mazey[CELLS + 1]; /* char's are OK */ int q, a, dir, pos; int dirs[4]; if (!typ) { if (level.flags.corrmaze) typ = CORR; else typ = ROOM; } pos = 1; mazex[pos] = (char) x; mazey[pos] = (char) y; while (pos) { x = (int) mazex[pos]; y = (int) mazey[pos]; if(!IS_DOOR(levl[x][y].typ)) { /* might still be on edge of MAP, so don't overwrite */ levl[x][y].typ = typ; levl[x][y].flags = 0; } q = 0; for (a = 0; a < 4; a++) if(okay(x, y, a)) dirs[q++]= a; if (!q) pos--; else { dir = dirs[rn2(q)]; move(&x, &y, dir); levl[x][y].typ = typ; move(&x, &y, dir); pos++; if (pos > CELLS) panic("Overflow in walkfrom"); mazex[pos] = (char) x; mazey[pos] = (char) y; } } } #else void walkfrom(x,y,typ) int x,y; schar typ; { register int q,a,dir; int dirs[4]; if (!typ) { if (level.flags.corrmaze) typ = CORR; else typ = ROOM; } if(!IS_DOOR(levl[x][y].typ)) { /* might still be on edge of MAP, so don't overwrite */ levl[x][y].typ = typ; levl[x][y].flags = 0; } while(1) { q = 0; for(a = 0; a < 4; a++) if(okay(x,y,a)) dirs[q++]= a; if(!q) return; dir = dirs[rn2(q)]; move(&x,&y,dir); levl[x][y].typ = typ; move(&x,&y,dir); walkfrom(x,y,typ); } } #endif /* MICRO */ STATIC_OVL void move(x,y,dir) register int *x, *y; register int dir; { switch(dir){ case 0: --(*y); break; case 1: (*x)++; break; case 2: (*y)++; break; case 3: --(*x); break; default: panic("move: bad direction"); } } void mazexy(cc) /* find random point in generated corridors, so we don't create items in moats, bunkers, or walls */ coord *cc; { int cpt=0; do { cc->x = 3 + 2*rn2((x_maze_max>>1) - 1); cc->y = 3 + 2*rn2((y_maze_max>>1) - 1); cpt++; } while (cpt < 100 && levl[cc->x][cc->y].typ != (level.flags.corrmaze ? CORR : ROOM)); if (cpt >= 100) { register int x, y; /* last try */ for (x = 0; x < (x_maze_max>>1) - 1; x++) for (y = 0; y < (y_maze_max>>1) - 1; y++) { cc->x = 3 + 2 * x; cc->y = 3 + 2 * y; if (levl[cc->x][cc->y].typ == (level.flags.corrmaze ? CORR : ROOM)) return; } panic("mazexy: can't find a place!"); } return; } void bound_digging() /* put a non-diggable boundary around the initial portion of a level map. * assumes that no level will initially put things beyond the isok() range. * * we can't bound unconditionally on the last line with something in it, * because that something might be a niche which was already reachable, * so the boundary would be breached * * we can't bound unconditionally on one beyond the last line, because * that provides a window of abuse for wallified special levels */ { register int x,y; register unsigned typ; register struct rm *lev; boolean found, nonwall; int xmin,xmax,ymin,ymax; if(Is_earthlevel(&u.uz)) return; /* everything diggable here */ found = nonwall = FALSE; for(xmin=0; !found; xmin++) { lev = &levl[xmin][0]; for(y=0; y<=ROWNO-1; y++, lev++) { typ = lev->typ; if(typ != STONE) { found = TRUE; if(!IS_WALL(typ)) nonwall = TRUE; } } } xmin -= (nonwall || !level.flags.is_maze_lev) ? 2 : 1; if (xmin < 0) xmin = 0; found = nonwall = FALSE; for(xmax=COLNO-1; !found; xmax--) { lev = &levl[xmax][0]; for(y=0; y<=ROWNO-1; y++, lev++) { typ = lev->typ; if(typ != STONE) { found = TRUE; if(!IS_WALL(typ)) nonwall = TRUE; } } } xmax += (nonwall || !level.flags.is_maze_lev) ? 2 : 1; if (xmax >= COLNO) xmax = COLNO-1; found = nonwall = FALSE; for(ymin=0; !found; ymin++) { lev = &levl[xmin][ymin]; for(x=xmin; x<=xmax; x++, lev += ROWNO) { typ = lev->typ; if(typ != STONE) { found = TRUE; if(!IS_WALL(typ)) nonwall = TRUE; } } } ymin -= (nonwall || !level.flags.is_maze_lev) ? 2 : 1; found = nonwall = FALSE; for(ymax=ROWNO-1; !found; ymax--) { lev = &levl[xmin][ymax]; for(x=xmin; x<=xmax; x++, lev += ROWNO) { typ = lev->typ; if(typ != STONE) { found = TRUE; if(!IS_WALL(typ)) nonwall = TRUE; } } } ymax += (nonwall || !level.flags.is_maze_lev) ? 2 : 1; for (x = 0; x < COLNO; x++) for (y = 0; y < ROWNO; y++) if (y <= ymin || y >= ymax || x <= xmin || x >= xmax) { #ifdef DCC30_BUG lev = &levl[x][y]; lev->wall_info |= W_NONDIGGABLE; #else levl[x][y].wall_info |= W_NONDIGGABLE; #endif } } void mkportal(x, y, todnum, todlevel) register xchar x, y, todnum, todlevel; { /* a portal "trap" must be matched by a */ /* portal in the destination dungeon/dlevel */ register struct trap *ttmp = maketrap(x, y, MAGIC_PORTAL); if (!ttmp) { impossible("portal on top of portal??"); return; } debugpline4("mkportal: at <%d,%d>, to %s, level %d", x, y, dungeons[todnum].dname, todlevel); ttmp->dst.dnum = todnum; ttmp->dst.dlevel = todlevel; return; } void fumaroles() { xchar n; boolean snd = FALSE, loud = FALSE; for (n = rn2(3)+2; n; n--) { xchar x = rn1(COLNO-4,3); xchar y = rn1(ROWNO-4,3); if (levl[x][y].typ == LAVAPOOL) { NhRegion *r = create_gas_cloud(x,y, 4+rn2(5), rn1(10,5)); clear_heros_fault(r); snd = TRUE; if (distu(x,y) < 15) loud = TRUE; } } if (snd && !Deaf) Norep("You hear a %swhoosh!", loud ? "loud " : ""); } /* * Special waterlevel stuff in endgame (TH). * * Some of these functions would probably logically belong to some * other source files, but they are all so nicely encapsulated here. */ /* to ease the work of debuggers at this stage */ #define register #define CONS_OBJ 0 #define CONS_MON 1 #define CONS_HERO 2 #define CONS_TRAP 3 static struct bubble *bbubbles, *ebubbles; static struct trap *wportal; static int xmin, ymin, xmax, ymax; /* level boundaries */ /* bubble movement boundaries */ #define bxmin (xmin + 1) #define bymin (ymin + 1) #define bxmax (xmax - 1) #define bymax (ymax - 1) STATIC_DCL void NDECL(set_wportal); STATIC_DCL void FDECL(mk_bubble, (int,int,int)); STATIC_DCL void FDECL(mv_bubble, (struct bubble *,int,int,BOOLEAN_P)); void movebubbles() { static boolean up; register struct bubble *b; register int x, y, i, j; struct trap *btrap; static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0, 0, 0, 0, 0, 0, 0 }; static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0, 1, 0, 0, 0, 0 }; /* set up the portal the first time bubbles are moved */ if (!wportal) set_wportal(); vision_recalc(2); if (Is_waterlevel(&u.uz)) { /* keep attached ball&chain separate from bubble objects */ if (Punished) unplacebc(); /* * Pick up everything inside of a bubble then fill all bubble * locations. */ for (b = up ? bbubbles : ebubbles; b; b = up ? b->next : b->prev) { if (b->cons) panic("movebubbles: cons != null"); for (i = 0, x = b->x; i < (int) b->bm[0]; i++, x++) for (j = 0, y = b->y; j < (int) b->bm[1]; j++, y++) if (b->bm[j + 2] & (1 << i)) { if (!isok(x,y)) { impossible("movebubbles: bad pos (%d,%d)", x,y); continue; } /* pick up objects, monsters, hero, and traps */ if (OBJ_AT(x,y)) { struct obj *olist = (struct obj *) 0, *otmp; struct container *cons = (struct container *) alloc(sizeof(struct container)); while ((otmp = level.objects[x][y]) != 0) { remove_object(otmp); otmp->ox = otmp->oy = 0; otmp->nexthere = olist; olist = otmp; } cons->x = x; cons->y = y; cons->what = CONS_OBJ; cons->list = (genericptr_t) olist; cons->next = b->cons; b->cons = cons; } if (MON_AT(x,y)) { struct monst *mon = m_at(x,y); struct container *cons = (struct container *) alloc(sizeof(struct container)); cons->x = x; cons->y = y; cons->what = CONS_MON; cons->list = (genericptr_t) mon; cons->next = b->cons; b->cons = cons; if(mon->wormno) remove_worm(mon); else remove_monster(x, y); newsym(x,y); /* clean up old position */ mon->mx = mon->my = 0; } if (!u.uswallow && x == u.ux && y == u.uy) { struct container *cons = (struct container *) alloc(sizeof(struct container)); cons->x = x; cons->y = y; cons->what = CONS_HERO; cons->list = (genericptr_t) 0; cons->next = b->cons; b->cons = cons; } if ((btrap = t_at(x,y)) != 0) { struct container *cons = (struct container *) alloc(sizeof(struct container)); cons->x = x; cons->y = y; cons->what = CONS_TRAP; cons->list = (genericptr_t) btrap; cons->next = b->cons; b->cons = cons; } levl[x][y] = water_pos; block_point(x,y); } } } else if (Is_airlevel(&u.uz)) { for (x = 0; x < COLNO; x++) for (y = 0; y < ROWNO; y++) { levl[x][y] = air_pos; unblock_point(x,y); } } /* * Every second time traverse down. This is because otherwise * all the junk that changes owners when bubbles overlap * would eventually end up in the last bubble in the chain. */ up = !up; for (b = up ? bbubbles : ebubbles; b; b = up ? b->next : b->prev) { register int rx = rn2(3), ry = rn2(3); mv_bubble(b,b->dx + 1 - (!b->dx ? rx : (rx ? 1 : 0)), b->dy + 1 - (!b->dy ? ry : (ry ? 1 : 0)), FALSE); } /* put attached ball&chain back */ if (Is_waterlevel(&u.uz) && Punished) placebc(); vision_full_recalc = 1; } /* when moving in water, possibly (1 in 3) alter the intended destination */ void water_friction() { register int x, y, dx, dy; register boolean eff = FALSE; if (Swimming && rn2(4)) return; /* natural swimmers have advantage */ if (u.dx && !rn2(!u.dy ? 3 : 6)) { /* 1/3 chance or half that */ /* cancel delta x and choose an arbitrary delta y value */ x = u.ux; do { dy = rn2(3) - 1; /* -1, 0, 1 */ y = u.uy + dy; } while (dy && (!isok(x,y) || !is_pool(x,y))); u.dx = 0; u.dy = dy; eff = TRUE; } else if (u.dy && !rn2(!u.dx ? 3 : 5)) { /* 1/3 or 1/5*(5/6) */ /* cancel delta y and choose an arbitrary delta x value */ y = u.uy; do { dx = rn2(3) - 1; /* -1 .. 1 */ x = u.ux + dx; } while (dx && (!isok(x,y) || !is_pool(x,y))); u.dy = 0; u.dx = dx; eff = TRUE; } if (eff) pline("Water turbulence affects your movements."); } void save_waterlevel(fd, mode) int fd, mode; { register struct bubble *b; if (!Is_waterlevel(&u.uz) && !Is_airlevel(&u.uz)) return; if (perform_bwrite(mode)) { int n = 0; for (b = bbubbles; b; b = b->next) ++n; bwrite(fd, (genericptr_t)&n, sizeof (int)); bwrite(fd, (genericptr_t)&xmin, sizeof (int)); bwrite(fd, (genericptr_t)&ymin, sizeof (int)); bwrite(fd, (genericptr_t)&xmax, sizeof (int)); bwrite(fd, (genericptr_t)&ymax, sizeof (int)); for (b = bbubbles; b; b = b->next) bwrite(fd, (genericptr_t)b, sizeof (struct bubble)); } if (release_data(mode)) unsetup_waterlevel(); } void restore_waterlevel(fd) register int fd; { register struct bubble *b = (struct bubble *)0, *btmp; register int i; int n; if (!Is_waterlevel(&u.uz) && !Is_airlevel(&u.uz)) return; set_wportal(); mread(fd,(genericptr_t)&n,sizeof(int)); mread(fd,(genericptr_t)&xmin,sizeof(int)); mread(fd,(genericptr_t)&ymin,sizeof(int)); mread(fd,(genericptr_t)&xmax,sizeof(int)); mread(fd,(genericptr_t)&ymax,sizeof(int)); for (i = 0; i < n; i++) { btmp = b; b = (struct bubble *)alloc(sizeof(struct bubble)); mread(fd,(genericptr_t)b,sizeof(struct bubble)); if (bbubbles) { btmp->next = b; b->prev = btmp; } else { bbubbles = b; b->prev = (struct bubble *)0; } mv_bubble(b,0,0,TRUE); } ebubbles = b; b->next = (struct bubble *)0; was_waterlevel = TRUE; } const char * waterbody_name(x, y) xchar x,y; { register struct rm *lev; schar ltyp; if (!isok(x,y)) return "drink"; /* should never happen */ lev = &levl[x][y]; ltyp = lev->typ; if (ltyp == DRAWBRIDGE_UP) ltyp = db_under_typ(lev->drawbridgemask); if (ltyp == LAVAPOOL) return "lava"; else if (ltyp == ICE) return "ice"; else if (ltyp == POOL) return "pool of water"; else if (ltyp == WATER || Is_waterlevel(&u.uz)) ; /* fall through to default return value */ else if (Is_juiblex_level(&u.uz)) return "swamp"; else if (ltyp == MOAT && !Is_medusa_level(&u.uz)) return "moat"; return "water"; } STATIC_OVL void set_wportal() { /* there better be only one magic portal on water level... */ for (wportal = ftrap; wportal; wportal = wportal->ntrap) if (wportal->ttyp == MAGIC_PORTAL) return; impossible("set_wportal(): no portal!"); } STATIC_OVL void setup_waterlevel() { register int x, y; register int xskip, yskip; register int water_glyph = cmap_to_glyph(S_water); register int air_glyph = cmap_to_glyph(S_air); /* ouch, hardcoded... */ xmin = 3; ymin = 1; xmax = 78; ymax = 20; /* set hero's memory to water */ for (x = xmin; x <= xmax; x++) for (y = ymin; y <= ymax; y++) levl[x][y].glyph = Is_waterlevel(&u.uz) ? water_glyph : air_glyph; /* make bubbles */ if (Is_waterlevel(&u.uz)) { xskip = 10 + rn2(10); yskip = 4 + rn2(4); } else { xskip = 6 + rn2(4); yskip = 3 + rn2(3); } for (x = bxmin; x <= bxmax; x += xskip) for (y = bymin; y <= bymax; y += yskip) mk_bubble(x,y,rn2(7)); } STATIC_OVL void unsetup_waterlevel() { register struct bubble *b, *bb; /* free bubbles */ for (b = bbubbles; b; b = bb) { bb = b->next; free((genericptr_t)b); } bbubbles = ebubbles = (struct bubble *)0; } STATIC_OVL void mk_bubble(x,y,n) register int x, y, n; { /* * These bit masks make visually pleasing bubbles on a normal aspect * 25x80 terminal, which naturally results in them being mathematically * anything but symmetric. For this reason they cannot be computed * in situ, either. The first two elements tell the dimensions of * the bubble's bounding box. */ static uchar bm2[] = {2,1,0x3}, bm3[] = {3,2,0x7,0x7}, bm4[] = {4,3,0x6,0xf,0x6}, bm5[] = {5,3,0xe,0x1f,0xe}, bm6[] = {6,4,0x1e,0x3f,0x3f,0x1e}, bm7[] = {7,4,0x3e,0x7f,0x7f,0x3e}, bm8[] = {8,4,0x7e,0xff,0xff,0x7e}, *bmask[] = {bm2,bm3,bm4,bm5,bm6,bm7,bm8}; register struct bubble *b; if (x >= bxmax || y >= bymax) return; if (n >= SIZE(bmask)) { impossible("n too large (mk_bubble)"); n = SIZE(bmask) - 1; } if (bmask[n][1] > MAX_BMASK) { panic("bmask size is larger than MAX_BMASK"); } b = (struct bubble *)alloc(sizeof(struct bubble)); if ((x + (int) bmask[n][0] - 1) > bxmax) x = bxmax - bmask[n][0] + 1; if ((y + (int) bmask[n][1] - 1) > bymax) y = bymax - bmask[n][1] + 1; b->x = x; b->y = y; b->dx = 1 - rn2(3); b->dy = 1 - rn2(3); /* y dimension is the length of bitmap data - see bmask above */ (void)memcpy((genericptr_t)b->bm, (genericptr_t)bmask[n], (bmask[n][1]+2)*sizeof(b->bm[0])); b->cons = 0; if (!bbubbles) bbubbles = b; if (ebubbles) { ebubbles->next = b; b->prev = ebubbles; } else b->prev = (struct bubble *)0; b->next = (struct bubble *)0; ebubbles = b; mv_bubble(b,0,0,TRUE); } /* * The player, the portal and all other objects and monsters * float along with their associated bubbles. Bubbles may overlap * freely, and the contents may get associated with other bubbles in * the process. Bubbles are "sticky", meaning that if the player is * in the immediate neighborhood of one, he/she may get sucked inside. * This property also makes leaving a bubble slightly difficult. */ STATIC_OVL void mv_bubble(b,dx,dy,ini) register struct bubble *b; register int dx, dy; register boolean ini; { register int x, y, i, j, colli = 0; struct container *cons, *ctemp; /* clouds move slowly */ if (!Is_airlevel(&u.uz) || !rn2(6)) { /* move bubble */ if (dx < -1 || dx > 1 || dy < -1 || dy > 1) { /* pline("mv_bubble: dx = %d, dy = %d", dx, dy); */ dx = sgn(dx); dy = sgn(dy); } /* * collision with level borders? * 1 = horizontal border, 2 = vertical, 3 = corner */ if (b->x <= bxmin) colli |= 2; if (b->y <= bymin) colli |= 1; if ((int) (b->x + b->bm[0] - 1) >= bxmax) colli |= 2; if ((int) (b->y + b->bm[1] - 1) >= bymax) colli |= 1; if (b->x < bxmin) { pline("bubble xmin: x = %d, xmin = %d", b->x, bxmin); b->x = bxmin; } if (b->y < bymin) { pline("bubble ymin: y = %d, ymin = %d", b->y, bymin); b->y = bymin; } if ((int) (b->x + b->bm[0] - 1) > bxmax) { pline("bubble xmax: x = %d, xmax = %d", b->x + b->bm[0] - 1, bxmax); b->x = bxmax - b->bm[0] + 1; } if ((int) (b->y + b->bm[1] - 1) > bymax) { pline("bubble ymax: y = %d, ymax = %d", b->y + b->bm[1] - 1, bymax); b->y = bymax - b->bm[1] + 1; } /* bounce if we're trying to move off the border */ if (b->x == bxmin && dx < 0) dx = -dx; if (b->x + b->bm[0] - 1 == bxmax && dx > 0) dx = -dx; if (b->y == bymin && dy < 0) dy = -dy; if (b->y + b->bm[1] - 1 == bymax && dy > 0) dy = -dy; b->x += dx; b->y += dy; } /* draw the bubbles */ for (i = 0, x = b->x; i < (int) b->bm[0]; i++, x++) for (j = 0, y = b->y; j < (int) b->bm[1]; j++, y++) if (b->bm[j + 2] & (1 << i)) { if (Is_waterlevel(&u.uz)) { levl[x][y].typ = AIR; levl[x][y].lit = 1; unblock_point(x,y); } else if (Is_airlevel(&u.uz)) { levl[x][y].typ = CLOUD; levl[x][y].lit = 1; block_point(x,y); } } if (Is_waterlevel(&u.uz)) { /* replace contents of bubble */ for (cons = b->cons; cons; cons = ctemp) { ctemp = cons->next; cons->x += dx; cons->y += dy; switch(cons->what) { case CONS_OBJ: { struct obj *olist, *otmp; for (olist=(struct obj *)cons->list; olist; olist=otmp) { otmp = olist->nexthere; place_object(olist, cons->x, cons->y); } break; } case CONS_MON: { struct monst *mon = (struct monst *) cons->list; (void) mnearto(mon, cons->x, cons->y, TRUE); break; } case CONS_HERO: { int ux0 = u.ux, uy0 = u.uy; /* change u.ux0 and u.uy0? */ u.ux = cons->x; u.uy = cons->y; newsym(ux0, uy0); /* clean up old position */ if (MON_AT(cons->x, cons->y)) { mnexto(m_at(cons->x,cons->y)); } break; } case CONS_TRAP: { struct trap *btrap = (struct trap *) cons->list; btrap->tx = cons->x; btrap->ty = cons->y; break; } default: impossible("mv_bubble: unknown bubble contents"); break; } free((genericptr_t)cons); } b->cons = 0; } /* boing? */ switch (colli) { case 1: b->dy = -b->dy; break; case 3: b->dy = -b->dy; /* fall through */ case 2: b->dx = -b->dx; break; default: /* sometimes alter direction for fun anyway (higher probability for stationary bubbles) */ if (!ini && ((b->dx || b->dy) ? !rn2(20) : !rn2(5))) { b->dx = 1 - rn2(3); b->dy = 1 - rn2(3); } } } /*mkmaze.c*/