1092 lines
31 KiB
C
1092 lines
31 KiB
C
/* NetHack 3.7 mkroom.c $NHDT-Date: 1613086701 2021/02/11 23:38:21 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.52 $ */
|
|
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
|
/*-Copyright (c) Robert Patrick Rankin, 2011. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
/*
|
|
* Entry points:
|
|
* do_mkroom() -- make and stock a room of a given type
|
|
* nexttodoor() -- return TRUE if adjacent to a door
|
|
* has_dnstairs() -- return TRUE if given room has a down staircase
|
|
* has_upstairs() -- return TRUE if given room has an up staircase
|
|
* courtmon() -- generate a court monster
|
|
* save_rooms() -- save rooms into file fd
|
|
* rest_rooms() -- restore rooms from file fd
|
|
* cmap_to_type() -- convert S_xxx symbol to XXX topology code
|
|
*/
|
|
|
|
#include "hack.h"
|
|
|
|
static boolean isbig(struct mkroom *);
|
|
static struct mkroom *pick_room(boolean);
|
|
static void mkshop(void), mkzoo(int), mkswamp(void);
|
|
static void mk_zoo_thronemon(coordxy, coordxy);
|
|
static void mktemple(void);
|
|
static coord *shrine_pos(int);
|
|
static struct permonst *morguemon(void);
|
|
static struct permonst *squadmon(void);
|
|
static void save_room(NHFILE *, struct mkroom *);
|
|
static void rest_room(NHFILE *, struct mkroom *);
|
|
static boolean invalid_shop_shape(struct mkroom *sroom);
|
|
|
|
#define sq(x) ((x) * (x))
|
|
|
|
extern const struct shclass shtypes[]; /* defined in shknam.c */
|
|
|
|
static boolean
|
|
isbig(struct mkroom *sroom)
|
|
{
|
|
int area = (sroom->hx - sroom->lx + 1)
|
|
* (sroom->hy - sroom->ly + 1);
|
|
|
|
return (boolean) (area > 20);
|
|
}
|
|
|
|
/* make and stock a room of a given type */
|
|
void
|
|
do_mkroom(int roomtype)
|
|
{
|
|
if (roomtype >= SHOPBASE) {
|
|
mkshop(); /* someday, we should be able to specify shop type */
|
|
} else {
|
|
switch (roomtype) {
|
|
case COURT:
|
|
mkzoo(COURT);
|
|
break;
|
|
case ZOO:
|
|
mkzoo(ZOO);
|
|
break;
|
|
case BEEHIVE:
|
|
mkzoo(BEEHIVE);
|
|
break;
|
|
case MORGUE:
|
|
mkzoo(MORGUE);
|
|
break;
|
|
case BARRACKS:
|
|
mkzoo(BARRACKS);
|
|
break;
|
|
case SWAMP:
|
|
mkswamp();
|
|
break;
|
|
case TEMPLE:
|
|
mktemple();
|
|
break;
|
|
case LEPREHALL:
|
|
mkzoo(LEPREHALL);
|
|
break;
|
|
case COCKNEST:
|
|
mkzoo(COCKNEST);
|
|
break;
|
|
case ANTHOLE:
|
|
mkzoo(ANTHOLE);
|
|
break;
|
|
default:
|
|
impossible("Tried to make a room of type %d.", roomtype);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
mkshop(void)
|
|
{
|
|
struct mkroom *sroom;
|
|
int i = -1;
|
|
char *ep = (char *) 0; /* (init == lint suppression) */
|
|
|
|
/* first determine shoptype */
|
|
if (wizard) {
|
|
ep = nh_getenv("SHOPTYPE");
|
|
if (ep) {
|
|
if (*ep == 'z' || *ep == 'Z') {
|
|
mkzoo(ZOO);
|
|
return;
|
|
}
|
|
if (*ep == 'm' || *ep == 'M') {
|
|
mkzoo(MORGUE);
|
|
return;
|
|
}
|
|
if (*ep == 'b' || *ep == 'B') {
|
|
mkzoo(BEEHIVE);
|
|
return;
|
|
}
|
|
if (*ep == 't' || *ep == 'T' || *ep == '\\') {
|
|
mkzoo(COURT);
|
|
return;
|
|
}
|
|
if (*ep == 's' || *ep == 'S') {
|
|
mkzoo(BARRACKS);
|
|
return;
|
|
}
|
|
if (*ep == 'a' || *ep == 'A') {
|
|
mkzoo(ANTHOLE);
|
|
return;
|
|
}
|
|
if (*ep == 'c' || *ep == 'C') {
|
|
mkzoo(COCKNEST);
|
|
return;
|
|
}
|
|
if (*ep == 'l' || *ep == 'L') {
|
|
mkzoo(LEPREHALL);
|
|
return;
|
|
}
|
|
if (*ep == '_') {
|
|
mktemple();
|
|
return;
|
|
}
|
|
if (*ep == '}') {
|
|
mkswamp();
|
|
return;
|
|
}
|
|
for (i = 0; shtypes[i].name; i++)
|
|
if (*ep == def_oc_syms[(int) shtypes[i].symb].sym)
|
|
goto gottype;
|
|
if (*ep == 'g' || *ep == 'G')
|
|
i = 0;
|
|
else if (*ep == 'v' || *ep == 'V')
|
|
i = FODDERSHOP - SHOPBASE; /* veggy food */
|
|
else
|
|
i = -1;
|
|
}
|
|
}
|
|
|
|
gottype:
|
|
for (sroom = &gr.rooms[0];; sroom++) {
|
|
/* return from this loop: cannot find any eligible room to be a shop
|
|
* continue: sroom is ineligible
|
|
* break: sroom is eligible
|
|
*/
|
|
if (sroom->hx < 0)
|
|
return;
|
|
if (sroom - gr.rooms >= gn.nroom) {
|
|
impossible("rooms[] not closed by -1?");
|
|
return;
|
|
}
|
|
if (sroom->rtype != OROOM)
|
|
continue;
|
|
if (has_dnstairs(sroom) || has_upstairs(sroom))
|
|
continue;
|
|
if (sroom->doorct == 1 || (wizard && ep && sroom->doorct != 0)) {
|
|
if (invalid_shop_shape(sroom))
|
|
continue;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
if (!sroom->rlit) {
|
|
coordxy x, y;
|
|
|
|
for (x = sroom->lx - 1; x <= sroom->hx + 1; x++)
|
|
for (y = sroom->ly - 1; y <= sroom->hy + 1; y++)
|
|
levl[x][y].lit = 1;
|
|
sroom->rlit = 1;
|
|
}
|
|
|
|
if (i < 0) { /* shoptype not yet determined */
|
|
int j;
|
|
|
|
/* pick a shop type at random */
|
|
for (j = rnd(100), i = 0; (j -= shtypes[i].prob) > 0; i++)
|
|
continue;
|
|
|
|
/* big rooms cannot be wand or book shops,
|
|
* - so make them general stores
|
|
*/
|
|
if (isbig(sroom) && (shtypes[i].symb == WAND_CLASS
|
|
|| shtypes[i].symb == SPBOOK_CLASS))
|
|
i = 0;
|
|
}
|
|
sroom->rtype = SHOPBASE + i;
|
|
|
|
/* set room bits before stocking the shop */
|
|
#ifdef SPECIALIZATION
|
|
topologize(sroom, FALSE); /* doesn't matter - this is a special room */
|
|
#else
|
|
topologize(sroom);
|
|
#endif
|
|
|
|
/* The shop used to be stocked here, but this no longer happens--all we do
|
|
is set its rtype, and it gets stocked at the end of makelevel() along
|
|
with other special rooms. */
|
|
sroom->needfill = FILL_NORMAL;
|
|
}
|
|
|
|
/* pick an unused room, preferably with only one door */
|
|
static struct mkroom *
|
|
pick_room(boolean strict)
|
|
{
|
|
struct mkroom *sroom;
|
|
int i = gn.nroom;
|
|
|
|
for (sroom = &gr.rooms[rn2(gn.nroom)]; i--; sroom++) {
|
|
if (sroom == &gr.rooms[gn.nroom])
|
|
sroom = &gr.rooms[0];
|
|
if (sroom->hx < 0)
|
|
return (struct mkroom *) 0;
|
|
if (sroom->rtype != OROOM)
|
|
continue;
|
|
if (!strict) {
|
|
if (has_upstairs(sroom) || (has_dnstairs(sroom) && rn2(3)))
|
|
continue;
|
|
} else if (has_upstairs(sroom) || has_dnstairs(sroom))
|
|
continue;
|
|
if (sroom->doorct == 1 || !rn2(5) || wizard)
|
|
return sroom;
|
|
}
|
|
return (struct mkroom *) 0;
|
|
}
|
|
|
|
static void
|
|
mkzoo(int type)
|
|
{
|
|
struct mkroom *sroom;
|
|
|
|
if ((sroom = pick_room(FALSE)) != 0) {
|
|
sroom->rtype = type;
|
|
/* room does not get stocked at this time - it will get stocked at the
|
|
* end of makelevel() */
|
|
sroom->needfill = FILL_NORMAL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
mk_zoo_thronemon(coordxy x, coordxy y)
|
|
{
|
|
int i = rnd(level_difficulty());
|
|
int pm = (i > 9) ? PM_OGRE_TYRANT
|
|
: (i > 5) ? PM_ELVEN_MONARCH
|
|
: (i > 2) ? PM_DWARF_RULER
|
|
: PM_GNOME_RULER;
|
|
struct monst *mon = makemon(&mons[pm], x, y, NO_MM_FLAGS);
|
|
|
|
if (mon) {
|
|
mon->msleeping = 1;
|
|
mon->mpeaceful = 0;
|
|
set_malign(mon);
|
|
/* Give him a sceptre to pound in judgment */
|
|
(void) mongets(mon, MACE);
|
|
}
|
|
}
|
|
|
|
void
|
|
fill_zoo(struct mkroom *sroom)
|
|
{
|
|
struct monst *mon;
|
|
int sx, sy, i;
|
|
int sh, goldlim = 0, type = sroom->rtype;
|
|
coordxy tx = 0, ty = 0;
|
|
int rmno = (int) ((sroom - gr.rooms) + ROOMOFFSET);
|
|
coord mm;
|
|
|
|
/* Note: This doesn't check needfill; it assumes the caller has already
|
|
done that. */
|
|
sh = sroom->fdoor;
|
|
switch (type) {
|
|
case COURT:
|
|
if (gl.level.flags.is_maze_lev) {
|
|
for (tx = sroom->lx; tx <= sroom->hx; tx++)
|
|
for (ty = sroom->ly; ty <= sroom->hy; ty++)
|
|
if (IS_THRONE(levl[tx][ty].typ))
|
|
goto throne_placed;
|
|
}
|
|
i = 100;
|
|
do { /* don't place throne on top of stairs */
|
|
(void) somexyspace(sroom, &mm);
|
|
tx = mm.x;
|
|
ty = mm.y;
|
|
} while (occupied((coordxy) tx, (coordxy) ty) && --i > 0);
|
|
throne_placed:
|
|
mk_zoo_thronemon(tx, ty);
|
|
break;
|
|
case BEEHIVE:
|
|
tx = sroom->lx + (sroom->hx - sroom->lx + 1) / 2;
|
|
ty = sroom->ly + (sroom->hy - sroom->ly + 1) / 2;
|
|
if (sroom->irregular) {
|
|
/* center might not be valid, so put queen elsewhere */
|
|
if ((int) levl[tx][ty].roomno != rmno || levl[tx][ty].edge) {
|
|
(void) somexyspace(sroom, &mm);
|
|
tx = mm.x;
|
|
ty = mm.y;
|
|
}
|
|
}
|
|
break;
|
|
case ZOO:
|
|
case LEPREHALL:
|
|
goldlim = 500 * level_difficulty();
|
|
break;
|
|
}
|
|
|
|
for (sx = sroom->lx; sx <= sroom->hx; sx++)
|
|
for (sy = sroom->ly; sy <= sroom->hy; sy++) {
|
|
if (sroom->irregular) {
|
|
if ((int) levl[sx][sy].roomno != rmno || levl[sx][sy].edge
|
|
|| (sroom->doorct
|
|
&& distmin(sx, sy, gd.doors[sh].x, gd.doors[sh].y) <= 1))
|
|
continue;
|
|
} else if (!SPACE_POS(levl[sx][sy].typ)
|
|
|| (sroom->doorct
|
|
&& ((sx == sroom->lx && gd.doors[sh].x == sx - 1)
|
|
|| (sx == sroom->hx && gd.doors[sh].x == sx + 1)
|
|
|| (sy == sroom->ly && gd.doors[sh].y == sy - 1)
|
|
|| (sy == sroom->hy
|
|
&& gd.doors[sh].y == sy + 1))))
|
|
continue;
|
|
/* don't place monster on explicitly placed throne */
|
|
if (type == COURT && IS_THRONE(levl[sx][sy].typ))
|
|
continue;
|
|
mon = makemon((type == COURT)
|
|
? courtmon()
|
|
: (type == BARRACKS)
|
|
? squadmon()
|
|
: (type == MORGUE)
|
|
? morguemon()
|
|
: (type == BEEHIVE)
|
|
? (sx == tx && sy == ty
|
|
? &mons[PM_QUEEN_BEE]
|
|
: &mons[PM_KILLER_BEE])
|
|
: (type == LEPREHALL)
|
|
? &mons[PM_LEPRECHAUN]
|
|
: (type == COCKNEST)
|
|
? &mons[PM_COCKATRICE]
|
|
: (type == ANTHOLE)
|
|
? antholemon()
|
|
: (struct permonst *) 0,
|
|
sx, sy, MM_ASLEEP | MM_NOGRP);
|
|
if (mon) {
|
|
mon->msleeping = 1;
|
|
if (type == COURT && mon->mpeaceful) {
|
|
mon->mpeaceful = 0;
|
|
set_malign(mon);
|
|
}
|
|
}
|
|
switch (type) {
|
|
case ZOO:
|
|
case LEPREHALL:
|
|
if (sroom->doorct) {
|
|
int distval = dist2(sx, sy, gd.doors[sh].x, gd.doors[sh].y);
|
|
i = sq(distval);
|
|
} else
|
|
i = goldlim;
|
|
if (i >= goldlim)
|
|
i = 5 * level_difficulty();
|
|
goldlim -= i;
|
|
(void) mkgold((long) rn1(i, 10), sx, sy);
|
|
break;
|
|
case MORGUE:
|
|
if (!rn2(5))
|
|
(void) mk_tt_object(CORPSE, sx, sy);
|
|
if (!rn2(10)) /* lots of treasure buried with dead */
|
|
(void) mksobj_at((rn2(3)) ? LARGE_BOX : CHEST, sx, sy,
|
|
TRUE, FALSE);
|
|
if (!rn2(5))
|
|
make_grave(sx, sy, (char *) 0);
|
|
break;
|
|
case BEEHIVE:
|
|
if (!rn2(3))
|
|
(void) mksobj_at(LUMP_OF_ROYAL_JELLY, sx, sy, TRUE,
|
|
FALSE);
|
|
break;
|
|
case BARRACKS:
|
|
if (!rn2(20)) /* the payroll and some loot */
|
|
(void) mksobj_at((rn2(3)) ? LARGE_BOX : CHEST, sx, sy,
|
|
TRUE, FALSE);
|
|
break;
|
|
case COCKNEST:
|
|
if (!rn2(3)) {
|
|
struct obj *sobj = mk_tt_object(STATUE, sx, sy);
|
|
|
|
if (sobj) {
|
|
for (i = rn2(5); i; i--)
|
|
(void) add_to_container(
|
|
sobj, mkobj(RANDOM_CLASS, FALSE));
|
|
sobj->owt = weight(sobj);
|
|
}
|
|
}
|
|
break;
|
|
case ANTHOLE:
|
|
if (!rn2(3))
|
|
(void) mkobj_at(FOOD_CLASS, sx, sy, FALSE);
|
|
break;
|
|
}
|
|
}
|
|
switch (type) {
|
|
case COURT: {
|
|
struct obj *chest, *gold;
|
|
levl[tx][ty].typ = THRONE;
|
|
(void) somexyspace(sroom, &mm);
|
|
gold = mksobj(GOLD_PIECE, TRUE, FALSE);
|
|
gold->quan = (long) rn1(50 * level_difficulty(), 10);
|
|
gold->owt = weight(gold);
|
|
/* the royal coffers */
|
|
chest = mksobj_at(CHEST, mm.x, mm.y, TRUE, FALSE);
|
|
add_to_container(chest, gold);
|
|
chest->owt = weight(chest);
|
|
chest->spe = 2; /* so it can be found later */
|
|
gl.level.flags.has_court = 1;
|
|
break;
|
|
}
|
|
case BARRACKS:
|
|
gl.level.flags.has_barracks = 1;
|
|
break;
|
|
case ZOO:
|
|
gl.level.flags.has_zoo = 1;
|
|
break;
|
|
case MORGUE:
|
|
gl.level.flags.has_morgue = 1;
|
|
break;
|
|
case SWAMP:
|
|
gl.level.flags.has_swamp = 1;
|
|
break;
|
|
case BEEHIVE:
|
|
gl.level.flags.has_beehive = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* make a swarm of undead around mm */
|
|
void
|
|
mkundead(
|
|
coord *mm,
|
|
boolean revive_corpses,
|
|
int mm_flags)
|
|
{
|
|
int cnt = (level_difficulty() + 1) / 10 + rnd(5);
|
|
struct permonst *mdat;
|
|
struct obj *otmp;
|
|
coord cc;
|
|
|
|
while (cnt--) {
|
|
mdat = morguemon();
|
|
if (mdat && enexto(&cc, mm->x, mm->y, mdat)
|
|
&& (!revive_corpses
|
|
|| !(otmp = sobj_at(CORPSE, cc.x, cc.y))
|
|
|| !revive(otmp, FALSE)))
|
|
(void) makemon(mdat, cc.x, cc.y, mm_flags);
|
|
}
|
|
gl.level.flags.graveyard = TRUE; /* reduced chance for undead corpse */
|
|
}
|
|
|
|
static struct permonst *
|
|
morguemon(void)
|
|
{
|
|
int i = rn2(100), hd = rn2(level_difficulty());
|
|
|
|
if (hd > 10 && i < 10) {
|
|
if (Inhell || In_endgame(&u.uz)) {
|
|
return mkclass(S_DEMON, 0);
|
|
} else {
|
|
int ndemon_res = ndemon(A_NONE);
|
|
if (ndemon_res != NON_PM)
|
|
return &mons[ndemon_res];
|
|
/* else do what? As is, it will drop to ghost/wraith/zombie */
|
|
}
|
|
}
|
|
|
|
if (hd > 8 && i > 85)
|
|
return mkclass(S_VAMPIRE, 0);
|
|
|
|
return ((i < 20) ? &mons[PM_GHOST]
|
|
: (i < 40) ? &mons[PM_WRAITH]
|
|
: mkclass(S_ZOMBIE, 0));
|
|
}
|
|
|
|
struct permonst *
|
|
antholemon(void)
|
|
{
|
|
int mtyp, indx, trycnt = 0;
|
|
|
|
/* casts are for dealing with time_t */
|
|
indx = (int) ((long) ubirthday % 3L);
|
|
indx += level_difficulty();
|
|
/* Same monsters within a level, different ones between levels */
|
|
do {
|
|
switch ((indx + trycnt) % 3) {
|
|
case 0:
|
|
mtyp = PM_SOLDIER_ANT;
|
|
break;
|
|
case 1:
|
|
mtyp = PM_FIRE_ANT;
|
|
break;
|
|
default:
|
|
mtyp = PM_GIANT_ANT;
|
|
break;
|
|
}
|
|
/* try again if chosen type has been genocided or used up */
|
|
} while (++trycnt < 3 && (gm.mvitals[mtyp].mvflags & G_GONE));
|
|
|
|
return ((gm.mvitals[mtyp].mvflags & G_GONE) ? (struct permonst *) 0
|
|
: &mons[mtyp]);
|
|
}
|
|
|
|
static void
|
|
mkswamp(void) /* Michiel Huisjes & Fred de Wilde */
|
|
{
|
|
struct mkroom *sroom;
|
|
int i, eelct = 0;
|
|
coordxy sx, sy;
|
|
int rmno;
|
|
|
|
for (i = 0; i < 5; i++) { /* turn up to 5 rooms swampy */
|
|
sroom = &gr.rooms[rn2(gn.nroom)];
|
|
if (sroom->hx < 0 || sroom->rtype != OROOM || has_upstairs(sroom)
|
|
|| has_dnstairs(sroom))
|
|
continue;
|
|
|
|
rmno = (int)(sroom - gr.rooms) + ROOMOFFSET;
|
|
|
|
/* satisfied; make a swamp */
|
|
sroom->rtype = SWAMP;
|
|
for (sx = sroom->lx; sx <= sroom->hx; sx++)
|
|
for (sy = sroom->ly; sy <= sroom->hy; sy++) {
|
|
if (!IS_ROOM(levl[sx][sy].typ)
|
|
|| (int) levl[sx][sy].roomno != rmno)
|
|
continue;
|
|
if (!OBJ_AT(sx, sy) && !MON_AT(sx, sy) && !t_at(sx, sy)
|
|
&& !nexttodoor(sx, sy)) {
|
|
if ((sx + sy) % 2) {
|
|
del_engr_at(sx, sy);
|
|
levl[sx][sy].typ = POOL;
|
|
if (!eelct || !rn2(4)) {
|
|
/* mkclass() won't do, as we might get kraken */
|
|
(void) makemon(rn2(5)
|
|
? &mons[PM_GIANT_EEL]
|
|
: rn2(2)
|
|
? &mons[PM_PIRANHA]
|
|
: &mons[PM_ELECTRIC_EEL],
|
|
sx, sy, NO_MM_FLAGS);
|
|
eelct++;
|
|
}
|
|
} else if (!rn2(4)) /* swamps tend to be moldy */
|
|
(void) makemon(mkclass(S_FUNGUS, 0), sx, sy,
|
|
NO_MM_FLAGS);
|
|
}
|
|
}
|
|
gl.level.flags.has_swamp = 1;
|
|
}
|
|
}
|
|
|
|
static coord *
|
|
shrine_pos(int roomno)
|
|
{
|
|
static coord buf;
|
|
int delta;
|
|
struct mkroom *troom = &gr.rooms[roomno - ROOMOFFSET];
|
|
|
|
/* if width and height are odd, placement will be the exact center;
|
|
if either or both are even, center point is a hypothetical spot
|
|
between map locations and placement will be adjacent to that */
|
|
delta = troom->hx - troom->lx;
|
|
buf.x = troom->lx + delta / 2;
|
|
if ((delta % 2) && rn2(2))
|
|
buf.x++;
|
|
delta = troom->hy - troom->ly;
|
|
buf.y = troom->ly + delta / 2;
|
|
if ((delta % 2) && rn2(2))
|
|
buf.y++;
|
|
return &buf;
|
|
}
|
|
|
|
static void
|
|
mktemple(void)
|
|
{
|
|
struct mkroom *sroom;
|
|
coord *shrine_spot;
|
|
struct rm *lev;
|
|
|
|
if (!(sroom = pick_room(TRUE)))
|
|
return;
|
|
|
|
/* set up Priest and shrine */
|
|
sroom->rtype = TEMPLE;
|
|
/*
|
|
* In temples, shrines are blessed altars
|
|
* located in the center of the room
|
|
*/
|
|
shrine_spot = shrine_pos((int) ((sroom - gr.rooms) + ROOMOFFSET));
|
|
lev = &levl[shrine_spot->x][shrine_spot->y];
|
|
lev->typ = ALTAR;
|
|
lev->altarmask = induced_align(80);
|
|
priestini(&u.uz, sroom, shrine_spot->x, shrine_spot->y, FALSE);
|
|
lev->altarmask |= AM_SHRINE;
|
|
gl.level.flags.has_temple = 1;
|
|
}
|
|
|
|
boolean
|
|
nexttodoor(int sx, int sy)
|
|
{
|
|
int dx, dy;
|
|
struct rm *lev;
|
|
|
|
for (dx = -1; dx <= 1; dx++)
|
|
for (dy = -1; dy <= 1; dy++) {
|
|
if (!isok(sx + dx, sy + dy))
|
|
continue;
|
|
lev = &levl[sx + dx][sy + dy];
|
|
if (IS_DOOR(lev->typ) || lev->typ == SDOOR)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
boolean
|
|
has_dnstairs(struct mkroom *sroom)
|
|
{
|
|
stairway *stway = gs.stairs;
|
|
|
|
while (stway) {
|
|
if (!stway->up && inside_room(sroom, stway->sx, stway->sy))
|
|
return TRUE;
|
|
stway = stway->next;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
boolean
|
|
has_upstairs(struct mkroom *sroom)
|
|
{
|
|
stairway *stway = gs.stairs;
|
|
|
|
while (stway) {
|
|
if (stway->up && inside_room(sroom, stway->sx, stway->sy))
|
|
return TRUE;
|
|
stway = stway->next;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int
|
|
somex(struct mkroom *croom)
|
|
{
|
|
return rn1(croom->hx - croom->lx + 1, croom->lx);
|
|
}
|
|
|
|
int
|
|
somey(struct mkroom *croom)
|
|
{
|
|
return rn1(croom->hy - croom->ly + 1, croom->ly);
|
|
}
|
|
|
|
boolean
|
|
inside_room(struct mkroom *croom, coordxy x, coordxy y)
|
|
{
|
|
if (croom->irregular) {
|
|
int i = (int) ((croom - gr.rooms) + ROOMOFFSET);
|
|
return (!levl[x][y].edge && (int) levl[x][y].roomno == i);
|
|
}
|
|
|
|
return (boolean) (x >= croom->lx - 1 && x <= croom->hx + 1
|
|
&& y >= croom->ly - 1 && y <= croom->hy + 1);
|
|
}
|
|
|
|
/* return a coord c inside mkroom croom, but not in a subroom.
|
|
returns TRUE if any such space found.
|
|
can return a non-accessible location, eg. inside a wall
|
|
if a themed room is not irregular, but has some non-room terrain */
|
|
boolean
|
|
somexy(struct mkroom *croom, coord *c)
|
|
{
|
|
int try_cnt = 0;
|
|
int i;
|
|
|
|
if (croom->irregular) {
|
|
i = (int) ((croom - gr.rooms) + ROOMOFFSET);
|
|
|
|
while (try_cnt++ < 100) {
|
|
c->x = somex(croom);
|
|
c->y = somey(croom);
|
|
if (!levl[c->x][c->y].edge && (int) levl[c->x][c->y].roomno == i)
|
|
return TRUE;
|
|
}
|
|
/* try harder; exhaustively search until one is found */
|
|
for (c->x = croom->lx; c->x <= croom->hx; c->x++)
|
|
for (c->y = croom->ly; c->y <= croom->hy; c->y++)
|
|
if (!levl[c->x][c->y].edge
|
|
&& (int) levl[c->x][c->y].roomno == i)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
if (!croom->nsubrooms) {
|
|
c->x = somex(croom);
|
|
c->y = somey(croom);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Check that coords doesn't fall into a subroom or into a wall */
|
|
|
|
while (try_cnt++ < 100) {
|
|
c->x = somex(croom);
|
|
c->y = somey(croom);
|
|
if (IS_WALL(levl[c->x][c->y].typ))
|
|
continue;
|
|
for (i = 0; i < croom->nsubrooms; i++)
|
|
if (inside_room(croom->sbrooms[i], c->x, c->y))
|
|
goto you_lose;
|
|
break;
|
|
you_lose:
|
|
;
|
|
}
|
|
if (try_cnt >= 100)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
/* like somexy(), but returns an accessible location */
|
|
boolean
|
|
somexyspace(struct mkroom* croom, coord *c)
|
|
{
|
|
int trycnt = 0;
|
|
boolean okay;
|
|
|
|
do {
|
|
okay = somexy(croom, c) && isok(c->x, c->y) && !occupied(c->x, c->y)
|
|
&& (levl[c->x][c->y].typ == ROOM
|
|
|| levl[c->x][c->y].typ == CORR
|
|
|| levl[c->x][c->y].typ == ICE);
|
|
} while (trycnt++ < 100 && !okay);
|
|
return okay;
|
|
}
|
|
|
|
/*
|
|
* Search for a special room given its type (zoo, court, etc...)
|
|
* Special values :
|
|
* - ANY_SHOP
|
|
* - ANY_TYPE
|
|
*/
|
|
struct mkroom *
|
|
search_special(schar type)
|
|
{
|
|
struct mkroom *croom;
|
|
|
|
for (croom = &gr.rooms[0]; croom->hx >= 0; croom++)
|
|
if ((type == ANY_TYPE && croom->rtype != OROOM)
|
|
|| (type == ANY_SHOP && croom->rtype >= SHOPBASE)
|
|
|| croom->rtype == type)
|
|
return croom;
|
|
for (croom = &gs.subrooms[0]; croom->hx >= 0; croom++)
|
|
if ((type == ANY_TYPE && croom->rtype != OROOM)
|
|
|| (type == ANY_SHOP && croom->rtype >= SHOPBASE)
|
|
|| croom->rtype == type)
|
|
return croom;
|
|
return (struct mkroom *) 0;
|
|
}
|
|
|
|
struct permonst *
|
|
courtmon(void)
|
|
{
|
|
int i = rn2(60) + rn2(3 * level_difficulty());
|
|
|
|
if (i > 100)
|
|
return mkclass(S_DRAGON, 0);
|
|
else if (i > 95)
|
|
return mkclass(S_GIANT, 0);
|
|
else if (i > 85)
|
|
return mkclass(S_TROLL, 0);
|
|
else if (i > 75)
|
|
return mkclass(S_CENTAUR, 0);
|
|
else if (i > 60)
|
|
return mkclass(S_ORC, 0);
|
|
else if (i > 45)
|
|
return &mons[PM_BUGBEAR];
|
|
else if (i > 30)
|
|
return &mons[PM_HOBGOBLIN];
|
|
else if (i > 15)
|
|
return mkclass(S_GNOME, 0);
|
|
else
|
|
return mkclass(S_KOBOLD, 0);
|
|
}
|
|
|
|
static const struct {
|
|
unsigned pm;
|
|
unsigned prob;
|
|
} squadprob[] = { { PM_SOLDIER, 80 },
|
|
{ PM_SERGEANT, 15 },
|
|
{ PM_LIEUTENANT, 4 },
|
|
{ PM_CAPTAIN, 1 } };
|
|
|
|
/* return soldier types. */
|
|
static struct permonst *
|
|
squadmon(void)
|
|
{
|
|
int sel_prob, i, cpro, mndx;
|
|
|
|
sel_prob = rnd(80 + level_difficulty());
|
|
|
|
cpro = 0;
|
|
for (i = 0; i < SIZE(squadprob); i++) {
|
|
cpro += squadprob[i].prob;
|
|
if (cpro > sel_prob) {
|
|
mndx = squadprob[i].pm;
|
|
goto gotone;
|
|
}
|
|
}
|
|
mndx = ROLL_FROM(squadprob).pm;
|
|
gotone:
|
|
if (!(gm.mvitals[mndx].mvflags & G_GONE))
|
|
return &mons[mndx];
|
|
else
|
|
return (struct permonst *) 0;
|
|
}
|
|
|
|
/*
|
|
* save_room : A recursive function that saves a room and its subrooms
|
|
* (if any).
|
|
*/
|
|
static void
|
|
save_room(NHFILE *nhfp, struct mkroom *r)
|
|
{
|
|
short i;
|
|
|
|
/*
|
|
* Well, I really should write only useful information instead
|
|
* of writing the whole structure. That is I should not write
|
|
* the gs.subrooms pointers, but who cares ?
|
|
*/
|
|
if (nhfp->structlevel)
|
|
bwrite(nhfp->fd, (genericptr_t) r, sizeof (struct mkroom));
|
|
for (i = 0; i < r->nsubrooms; i++) {
|
|
save_room(nhfp, r->sbrooms[i]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* save_rooms : Save all the rooms on disk!
|
|
*/
|
|
void
|
|
save_rooms(NHFILE *nhfp)
|
|
{
|
|
short i;
|
|
|
|
/* First, write the number of rooms */
|
|
if (nhfp->structlevel)
|
|
bwrite(nhfp->fd, (genericptr_t) &gn.nroom, sizeof(gn.nroom));
|
|
for (i = 0; i < gn.nroom; i++)
|
|
save_room(nhfp, &gr.rooms[i]);
|
|
}
|
|
|
|
static void
|
|
rest_room(NHFILE *nhfp, struct mkroom *r)
|
|
{
|
|
short i;
|
|
|
|
if (nhfp->structlevel)
|
|
mread(nhfp->fd, (genericptr_t) r, sizeof(struct mkroom));
|
|
|
|
for (i = 0; i < r->nsubrooms; i++) {
|
|
r->sbrooms[i] = &gs.subrooms[gn.nsubroom];
|
|
rest_room(nhfp, &gs.subrooms[gn.nsubroom]);
|
|
gs.subrooms[gn.nsubroom++].resident = (struct monst *) 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rest_rooms : That's for restoring rooms. Read the rooms structure from
|
|
* the disk.
|
|
*/
|
|
void
|
|
rest_rooms(NHFILE *nhfp)
|
|
{
|
|
short i;
|
|
|
|
if (nhfp->structlevel)
|
|
mread(nhfp->fd, (genericptr_t) &gn.nroom, sizeof(gn.nroom));
|
|
|
|
gn.nsubroom = 0;
|
|
for (i = 0; i < gn.nroom; i++) {
|
|
rest_room(nhfp, &gr.rooms[i]);
|
|
gr.rooms[i].resident = (struct monst *) 0;
|
|
}
|
|
gr.rooms[gn.nroom].hx = -1; /* restore ending flags */
|
|
gs.subrooms[gn.nsubroom].hx = -1;
|
|
}
|
|
|
|
/* convert a display symbol for terrain into topology type;
|
|
used for remembered terrain when mimics pose as furniture */
|
|
int
|
|
cmap_to_type(int sym)
|
|
{
|
|
int typ = STONE; /* catchall */
|
|
|
|
switch (sym) {
|
|
case S_stone:
|
|
typ = STONE;
|
|
break;
|
|
case S_vwall:
|
|
typ = VWALL;
|
|
break;
|
|
case S_hwall:
|
|
typ = HWALL;
|
|
break;
|
|
case S_tlcorn:
|
|
typ = TLCORNER;
|
|
break;
|
|
case S_trcorn:
|
|
typ = TRCORNER;
|
|
break;
|
|
case S_blcorn:
|
|
typ = BLCORNER;
|
|
break;
|
|
case S_brcorn:
|
|
typ = BRCORNER;
|
|
break;
|
|
case S_crwall:
|
|
typ = CROSSWALL;
|
|
break;
|
|
case S_tuwall:
|
|
typ = TUWALL;
|
|
break;
|
|
case S_tdwall:
|
|
typ = TDWALL;
|
|
break;
|
|
case S_tlwall:
|
|
typ = TLWALL;
|
|
break;
|
|
case S_trwall:
|
|
typ = TRWALL;
|
|
break;
|
|
case S_ndoor: /* no door (empty doorway) */
|
|
case S_vodoor: /* open door in vertical wall */
|
|
case S_hodoor: /* open door in horizontal wall */
|
|
case S_vcdoor: /* closed door in vertical wall */
|
|
case S_hcdoor:
|
|
typ = DOOR;
|
|
break;
|
|
case S_bars:
|
|
typ = IRONBARS;
|
|
break;
|
|
case S_tree:
|
|
typ = TREE;
|
|
break;
|
|
case S_room:
|
|
case S_darkroom:
|
|
typ = ROOM;
|
|
break;
|
|
case S_corr:
|
|
case S_litcorr:
|
|
typ = CORR;
|
|
break;
|
|
case S_upstair:
|
|
case S_dnstair:
|
|
typ = STAIRS;
|
|
break;
|
|
case S_upladder:
|
|
case S_dnladder:
|
|
typ = LADDER;
|
|
break;
|
|
case S_altar:
|
|
typ = ALTAR;
|
|
break;
|
|
case S_grave:
|
|
typ = GRAVE;
|
|
break;
|
|
case S_throne:
|
|
typ = THRONE;
|
|
break;
|
|
case S_sink:
|
|
typ = SINK;
|
|
break;
|
|
case S_fountain:
|
|
typ = FOUNTAIN;
|
|
break;
|
|
case S_pool:
|
|
typ = POOL;
|
|
break;
|
|
case S_ice:
|
|
typ = ICE;
|
|
break;
|
|
case S_lava:
|
|
typ = LAVAPOOL;
|
|
break;
|
|
case S_vodbridge: /* open drawbridge spanning north/south */
|
|
case S_hodbridge:
|
|
typ = DRAWBRIDGE_DOWN;
|
|
break; /* east/west */
|
|
case S_vcdbridge: /* closed drawbridge in vertical wall */
|
|
case S_hcdbridge:
|
|
typ = DBWALL;
|
|
break;
|
|
case S_air:
|
|
typ = AIR;
|
|
break;
|
|
case S_cloud:
|
|
typ = CLOUD;
|
|
break;
|
|
case S_water:
|
|
typ = WATER;
|
|
break;
|
|
case S_lavawall:
|
|
typ = LAVAWALL;
|
|
break;
|
|
default:
|
|
break; /* not a cmap symbol? */
|
|
}
|
|
return typ;
|
|
}
|
|
|
|
/* With the introduction of themed rooms, there are certain room shapes that may
|
|
* generate a door, the square just inside the door, and only one other ROOM
|
|
* square touching that one. E.g.
|
|
* ---
|
|
* ---..
|
|
* +....
|
|
* ---..
|
|
* ---
|
|
* This means that if the room becomes a shop, the shopkeeper will move between
|
|
* those two squares nearest the door without ever allowing the player to get
|
|
* past them.
|
|
* Before approving sroom as a shop, check for this circumstance, and if it
|
|
* exists, don't consider it as valid for a shop.
|
|
*
|
|
* Note that the invalidity of the shape derives from the position of its door
|
|
* already being chosen. It's quite possible that if the door were somewhere
|
|
* else on the perimeter of this room, it would work fine as a shop.*/
|
|
static boolean
|
|
invalid_shop_shape(struct mkroom *sroom)
|
|
{
|
|
coordxy x, y;
|
|
coordxy doorx = gd.doors[sroom->fdoor].x;
|
|
coordxy doory = gd.doors[sroom->fdoor].y;
|
|
coordxy insidex = 0, insidey = 0, insidect = 0;
|
|
|
|
/* First, identify squares inside the room and next to the door. */
|
|
for (x = max(doorx - 1, sroom->lx);
|
|
x <= min(doorx + 1, sroom->hx); x++) {
|
|
for (y = max(doory - 1, sroom->ly);
|
|
y <= min(doory + 1, sroom->hy); y++) {
|
|
if (levl[x][y].typ == ROOM) {
|
|
insidex = x;
|
|
insidey = y;
|
|
insidect++;
|
|
}
|
|
}
|
|
}
|
|
if (insidect < 1) {
|
|
impossible("invalid_shop_shape: no squares inside door?");
|
|
return TRUE;
|
|
}
|
|
/* if insidect > 1, then the shopkeeper already has alternate
|
|
* squares to move to so we don't need to check further. */
|
|
if (insidect == 1) {
|
|
/* But if it is 1, scan all adjacent squares for other squares
|
|
* that are part of this room. */
|
|
insidect = 0;
|
|
for (x = max(insidex - 1, sroom->lx);
|
|
x <= min(insidex + 1, sroom->hx); x++) {
|
|
for (y = max(insidey - 1, sroom->ly);
|
|
y <= min(insidey + 1, sroom->hy); y++) {
|
|
if (x == insidex && y == insidey)
|
|
continue;
|
|
if (levl[x][y].typ == ROOM)
|
|
insidect++;
|
|
}
|
|
}
|
|
if (insidect == 1) {
|
|
/* shopkeeper standing just inside the door can only move
|
|
* to one other square; this cannot be a shop. */
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*mkroom.c*/
|