/* NetHack 3.7	sp_lev.c	$NHDT-Date: 1695159628 2023/09/19 21:40:28 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.339 $ */
/*      Copyright (c) 1989 by Jean-Christophe Collet */
/* NetHack may be freely redistributed.  See license for details. */

/*
 * This file contains the various functions that are related to the special
 * levels.
 *
 * It contains also the special level loader.
 */

#define IN_SP_LEV_C

#include "hack.h"
#include "sp_lev.h"

typedef void (*select_iter_func)(coordxy, coordxy, genericptr);

extern void mkmap(lev_init *);

static boolean match_maptyps(xint16, xint16);
static void solidify_map(void);
static void map_cleanup(void);
static void lvlfill_maze_grid(int, int, int, int, schar);
static void lvlfill_solid(schar, schar);
static void lvlfill_swamp(schar, schar, schar);
static void flip_dbridge_horizontal(struct rm *);
static void flip_dbridge_vertical(struct rm *);
static void flip_visuals(int, int, int, int, int);
static int flip_encoded_dir_bits(int, int);
static void flip_vault_guard(int, struct monst *,
                             coordxy, coordxy, coordxy, coordxy);
static void sel_set_wall_property(coordxy, coordxy, genericptr_t);
static void set_wall_property(coordxy, coordxy, coordxy, coordxy, int);
static void remove_boundary_syms(void);
static void set_door_orientation(int, int);
static boolean shared_with_room(int, int, struct mkroom *);
static void maybe_add_door(int, int, struct mkroom *);
static void link_doors_rooms(void);
static int rnddoor(void);
static int rndtrap(void);
static void get_location(coordxy *, coordxy *, getloc_flags_t,
                         struct mkroom *);
static void set_ok_location_func(boolean (*)(coordxy, coordxy));
static boolean is_ok_location(coordxy, coordxy, getloc_flags_t);
static unpacked_coord get_unpacked_coord(long, int);
static void get_room_loc(coordxy *, coordxy *, struct mkroom *);
static void get_free_room_loc(coordxy *, coordxy *, struct mkroom *,
                              packed_coord);
static boolean create_subroom(struct mkroom *, coordxy, coordxy, coordxy,
                              coordxy, xint16, xint16);
static void create_door(room_door *, struct mkroom *);
static void create_trap(spltrap *, struct mkroom *);
static int noncoalignment(aligntyp);
static boolean m_bad_boulder_spot(coordxy, coordxy);
static int pm_to_humidity(struct permonst *);
static unsigned int sp_amask_to_amask(unsigned int sp_amask);
static void create_monster(monster *, struct mkroom *);
static struct obj *create_object(object *, struct mkroom *);
static void create_altar(altar *, struct mkroom *);
static boolean search_door(struct mkroom *, coordxy *, coordxy *, xint16, int);
static void create_corridor(corridor *);
static struct mkroom *build_room(room *, struct mkroom *);
static void light_region(region *);
static void maze1xy(coord *, int);
static void fill_empty_maze(void);
static void splev_initlev(lev_init *);
static boolean generate_way_out_method(coordxy nx, coordxy ny,
                                       struct selectionvar *ov);
#if 0
/* macosx complains that these are unused */
static long sp_code_jmpaddr(long, long);
static void spo_room(struct sp_coder *);
static void spo_trap(struct sp_coder *);
static void spo_gold(struct sp_coder *);
static void spo_corridor(struct sp_coder *);
static void spo_feature(struct sp_coder *);
static void spo_terrain(struct sp_coder *);
static void spo_replace_terrain(struct sp_coder *);
static void spo_levregion(struct sp_coder *);
static void spo_region(struct sp_coder *);
static void spo_drawbridge(struct sp_coder *);
static void spo_mazewalk(struct sp_coder *);
static void spo_wall_property(struct sp_coder *);
static void spo_room_door(struct sp_coder *);
static void spo_wallify(struct sp_coder *);
static void sel_set_wallify(coordxy, coordxy, genericptr_t);
#endif
static void spo_end_moninvent(void);
static void spo_pop_container(void);
static int l_create_stairway(lua_State *, boolean);
static void spo_endroom(struct sp_coder *);
static void l_table_getset_feature_flag(lua_State *, int, int, const char *,
                                        int);
static void l_get_lregion(lua_State *, lev_region *);
static void sel_set_lit(coordxy, coordxy, genericptr_t);
static void add_doors_to_room(struct mkroom *);
static void selection_iterate(struct selectionvar *, select_iter_func,
                              genericptr_t);
static void sel_set_ter(coordxy, coordxy, genericptr_t);
static void sel_set_door(coordxy, coordxy, genericptr_t);
static void sel_set_feature(coordxy, coordxy, genericptr_t);
static void levregion_add(lev_region *);
static void get_table_xy_or_coord(lua_State *, lua_Integer *, lua_Integer *);
static int get_table_region(lua_State *, const char *, lua_Integer *,
                        lua_Integer *, lua_Integer *, lua_Integer *, boolean);
static void set_wallprop_in_selection(lua_State *, int);
static coordxy random_wdir(void);
static int floodfillchk_match_under(coordxy, coordxy);
static int floodfillchk_match_accessible(coordxy, coordxy);
static boolean sel_flood_havepoint(coordxy, coordxy, coordxy *, coordxy *, int);
static long line_dist_coord(long, long, long, long, long, long);
static void l_push_mkroom_table(lua_State *, struct mkroom *);
static int get_table_align(lua_State *);
static int get_table_monclass(lua_State *);
static int find_montype(lua_State *, const char *, int *);
static int get_table_montype(lua_State *, int *);
static lua_Integer get_table_int_or_random(lua_State *, const char *, int);
static int get_table_buc(lua_State *);
static int get_table_objclass(lua_State *);
static int find_objtype(lua_State *, const char *);
static int get_table_objtype(lua_State *);
static const char *get_mkroom_name(int);
static int get_table_roomtype_opt(lua_State *, const char *, int);
static int get_table_traptype_opt(lua_State *, const char *, int);
static int get_traptype_byname(const char *);
static lua_Integer get_table_intarray_entry(lua_State *, int, int);
static struct sp_coder *sp_level_coder_init(void);

/* lua_CFunction prototypes */
int lspo_altar(lua_State *);
int lspo_branch(lua_State *);
int lspo_corridor(lua_State *);
int lspo_door(lua_State *);
int lspo_drawbridge(lua_State *);
int lspo_engraving(lua_State *);
int lspo_feature(lua_State *);
int lspo_gold(lua_State *);
int lspo_grave(lua_State *);
int lspo_ladder(lua_State *);
int lspo_level_flags(lua_State *);
int lspo_level_init(lua_State *);
int lspo_levregion(lua_State *);
int lspo_exclusion(lua_State *);
int lspo_map(lua_State *);
int lspo_mazewalk(lua_State *);
int lspo_message(lua_State *);
int lspo_mineralize(lua_State *);
int lspo_monster(lua_State *);
int lspo_non_diggable(lua_State *);
int lspo_non_passwall(lua_State *);
int lspo_object(lua_State *);
int lspo_portal(lua_State *);
int lspo_random_corridors(lua_State *);
int lspo_region(lua_State *);
int lspo_replace_terrain(lua_State *);
int lspo_reset_level(lua_State *);
int lspo_finalize_level(lua_State *);
int lspo_room(lua_State *);
int lspo_stair(lua_State *);
int lspo_teleport_region(lua_State *);
int lspo_terrain(lua_State *);
int lspo_trap(lua_State *);
int lspo_wall_property(lua_State *);
int lspo_wallify(lua_State *);

#define SPLEV_LEFT 1
#define SPLEV_H_LEFT 2
#define SPLEV_CENTER 3
#define SPLEV_H_RIGHT 4
#define SPLEV_RIGHT 5

#define TOP 1
#define BOTTOM 5

#define sq(x) ((x) * (x))

#define XLIM 4
#define YLIM 3

#define New(type) (type *) alloc(sizeof (type))
#define NewTab(type, size) (type **) alloc(sizeof (type *) * (unsigned) size)
#define Free(ptr) \
    do {                                        \
        if (ptr)                                \
            free((genericptr_t) (ptr));         \
    } while (0)

    /*
     * No need for 'struct instance_globals g' to contain these.
     * sp_level_coder_init() always re-initializes them prior to use.
     */
static boolean splev_init_present, icedpools;
/* positions touched by level elements explicitly defined in the level */
static char SpLev_Map[COLNO][ROWNO];
#define MAX_CONTAINMENT 10
static int container_idx = 0; /* next slot in container_obj[] to use */
static struct obj *container_obj[MAX_CONTAINMENT];
static struct monst *invent_carrying_monster = (struct monst *) 0;
    /*
     * end of no 'g.'
     */

#define TYP_CANNOT_MATCH(typ) ((typ) == MAX_TYPE || (typ) == INVALID_TYPE)

void
reset_xystart_size(void)
{
    gx.xstart = 1; /* column [0] is off limits */
    gy.ystart = 0;
    gx.xsize = COLNO - 1; /* 1..COLNO-1 */
    gy.ysize = ROWNO; /* 0..ROWNO-1 */
}

/* Does typ match with levl[][].typ, considering special types
   MATCH_WALL and MAX_TYPE (aka transparency)? */
static boolean
match_maptyps(xint16 typ, xint16 levltyp)
{
    if ((typ == MATCH_WALL) && !IS_STWALL(levltyp))
        return FALSE;
    if ((typ < MAX_TYPE) && (typ != levltyp))
        return FALSE;
    return TRUE;
}

struct mapfragment *
mapfrag_fromstr(char *str)
{
    struct mapfragment *mf = (struct mapfragment *) alloc(sizeof *mf);

    char *tmps;

    mf->data = dupstr(str);

    (void) stripdigits(mf->data);
    mf->wid = str_lines_maxlen(mf->data);
    mf->hei = 0;
    tmps = mf->data;
    while (tmps && *tmps) {
        char *s1 = strchr(tmps, '\n');

        if (mf->hei > MAP_Y_LIM) {
            free(mf->data);
            free(mf);
            return NULL;
        }
        if (s1)
            s1++;
        tmps = s1;
        mf->hei++;
    }
    return mf;
}

void
mapfrag_free(struct mapfragment **mf)
{
    if (mf && *mf) {
        free((*mf)->data);
        free(*mf);
        *mf = NULL;
    }
}

schar
mapfrag_get(struct mapfragment *mf, int x, int y)
{
    if (y < 0 || x < 0 || y > mf->hei - 1 || x > mf->wid - 1)
        panic("outside mapfrag (%i,%i), wanted (%i,%i)",
              mf->wid, mf->hei, x, y);
    return splev_chr2typ(mf->data[y * (mf->wid + 1) + x]);
}

boolean
mapfrag_canmatch(struct mapfragment *mf)
{
    return ((mf->wid % 2) && (mf->hei % 2));
}

const char *
mapfrag_error(struct mapfragment *mf)
{
    const char *res = NULL;

    if (!mf) {
        res = "mapfragment error";
    } else if (!mapfrag_canmatch(mf)) {
        mapfrag_free(&mf);
        res = "mapfragment needs to have odd height and width";
    } else if (TYP_CANNOT_MATCH(mapfrag_get(mf, mf->wid / 2, mf->hei / 2))) {
        mapfrag_free(&mf);
        res = "mapfragment center must be valid terrain";
    }
    return res;
}

boolean
mapfrag_match(struct mapfragment *mf,  int x, int y)
{
    int rx, ry;

    for (rx = -(mf->wid / 2); rx <= (mf->wid / 2); rx++)
        for (ry = -(mf->hei / 2); ry <= (mf->hei / 2); ry++) {
            schar mapc = mapfrag_get(mf, rx + (mf->wid / 2),
                                     ry + (mf->hei / 2));
            schar levc = isok(x+rx, y+ry) ? levl[x+rx][y+ry].typ : STONE;

            if (!match_maptyps(mapc, levc))
                return FALSE;
        }
    return TRUE;
}

static void
solidify_map(void)
{
    coordxy x, y;

    for (x = 0; x < COLNO; x++)
        for (y = 0; y < ROWNO; y++)
            if (IS_STWALL(levl[x][y].typ) && !SpLev_Map[x][y])
                levl[x][y].wall_info |= (W_NONDIGGABLE | W_NONPASSWALL);
}

/* do a post-level-creation cleanup of map, such as
   removing boulders and traps from lava */
static void
map_cleanup(void)
{
    struct obj *otmp;
    struct trap *ttmp;
    struct engr *etmp;
    coordxy x, y;

    for (x = 0; x < COLNO; x++)
        for (y = 0; y < ROWNO; y++) {
            schar typ = levl[x][y].typ;

            if (typ == LAVAPOOL || typ == LAVAWALL || IS_POOL(typ)) {
                /* in case any boulders are on liquid, delete them */
                while ((otmp = sobj_at(BOULDER, x, y)) != 0) {
                    obj_extract_self(otmp);
                    obfree(otmp, (struct obj *)0);
                }

                /* traps on liquid? */
                if (((ttmp = t_at(x, y)) != 0)
                    && !undestroyable_trap(ttmp->ttyp))
                    deltrap(ttmp);

                /* engravings? */
                if ((etmp = engr_at(x, y)) != 0)
                  del_engr(etmp);
            }
        }
}

static void
lvlfill_maze_grid(int x1, int y1, int x2, int y2, schar filling)
{
    int x, y;

    for (x = x1; x <= x2; x++)
        for (y = y1; y <= y2; y++) {
            if (gl.level.flags.corrmaze)
                levl[x][y].typ = STONE;
            else
                levl[x][y].typ = (y < 2 || ((x % 2) && (y % 2))) ? STONE
                                                                 : filling;
        }
}

static void
lvlfill_solid(schar filling, schar lit)
{
    int x, y;

    for (x = 2; x <= gx.x_maze_max; x++)
        for (y = 0; y <= gy.y_maze_max; y++) {
            if (!set_levltyp_lit(x, y, filling, lit))
                continue;
            /* TODO: consolidate this w lspo_map ? */
            levl[x][y].flags = 0;
            levl[x][y].horizontal = 0;
            levl[x][y].roomno = 0;
            levl[x][y].edge = 0;
        }
}

static void
lvlfill_swamp(schar fg, schar bg, schar lit)
{
    int x, y;

    lvlfill_solid(bg, lit);

    /* "relaxed blockwise maze" algorithm, Jamis Buck */
    for (x = 2; x <= min(gx.x_maze_max, COLNO-2); x += 2)
        for (y = 0; y <= min(gy.y_maze_max, ROWNO-2); y += 2) {
            int c = 0;

            (void) set_levltyp_lit(x, y, fg, lit);
            if (levl[x + 1][y].typ == bg)
                ++c;
            if (levl[x][y + 1].typ == bg)
                ++c;
            if (levl[x + 1][y + 1].typ == bg)
                ++c;
            if (c == 3) {
                switch (rn2(3)) {
                case 0:
                    (void) set_levltyp_lit(x + 1,y, fg, lit);
                    break;
                case 1:
                    (void) set_levltyp_lit(x, y + 1, fg, lit);
                    break;
                case 2:
                    (void) set_levltyp_lit(x + 1, y + 1, fg, lit);
                    break;
                default:
                    break;
                }
            }
        }
}

static void
flip_dbridge_horizontal(struct rm *lev)
{
    if (IS_DRAWBRIDGE(lev->typ)) {
        if ((lev->drawbridgemask & DB_DIR) == DB_WEST) {
            lev->drawbridgemask &= ~DB_WEST;
            lev->drawbridgemask |=  DB_EAST;
        } else if ((lev->drawbridgemask & DB_DIR) == DB_EAST) {
            lev->drawbridgemask &= ~DB_EAST;
            lev->drawbridgemask |=  DB_WEST;
        }
    }
}

static void
flip_dbridge_vertical(struct rm *lev)
{
    if (IS_DRAWBRIDGE(lev->typ)) {
        if ((lev->drawbridgemask & DB_DIR) == DB_NORTH) {
            lev->drawbridgemask &= ~DB_NORTH;
            lev->drawbridgemask |=  DB_SOUTH;
        } else if ((lev->drawbridgemask & DB_DIR) == DB_SOUTH) {
            lev->drawbridgemask &= ~DB_SOUTH;
            lev->drawbridgemask |=  DB_NORTH;
        }
    }
}

/* for #wizfliplevel; not needed when flipping during level creation;
   update seen vector for whole flip area and glyph for known walls */
static void
flip_visuals(int flp, int minx, int miny, int maxx, int maxy)
{
    struct rm *lev;
    int x, y, seenv;

    for (y = miny; y <= maxy; ++y) {
        for (x = minx; x <= maxx; ++x) {
            lev = &levl[x][y];
            seenv = lev->seenv & 0xff;
            /* locations which haven't been seen can be skipped */
            if (seenv == 0)
                continue;
            /* flip <x,y>'s seen vector; not necessary for locations seen
               from all directions (the whole level after magic mapping) */
            if (seenv != SVALL) {
                /* SV2 SV1 SV0 *
                 * SV3 -+- SV7 *
                 * SV4 SV5 SV6 */
                if (flp & 1) { /* swap top and bottom */
                    seenv = swapbits(seenv, 2, 4);
                    seenv = swapbits(seenv, 1, 5);
                    seenv = swapbits(seenv, 0, 6);
                }
                if (flp & 2) { /* swap left and right */
                    seenv = swapbits(seenv, 2, 0);
                    seenv = swapbits(seenv, 3, 7);
                    seenv = swapbits(seenv, 4, 6);
                }
                lev->seenv = (uchar) seenv;
            }
            /* if <x,y> is displayed as a wall, reset its display glyph so
               that remembered, out of view T's and corners get flipped */
            if ((IS_WALL(lev->typ) || lev->typ == SDOOR)
                && glyph_is_cmap(lev->glyph))
                lev->glyph = back_to_glyph(x, y);
        }
    }
}

/* transpose an encoded direction */
static int
flip_encoded_dir_bits(int flp, int val)
{
    /* these depend on xdir[] and ydir[] order */
    if (flp & 1) {
        val = swapbits(val, 1, 7);
        val = swapbits(val, 2, 6);
        val = swapbits(val, 3, 5);
    }
    if (flp & 2) {
        val = swapbits(val, 1, 3);
        val = swapbits(val, 0, 4);
        val = swapbits(val, 7, 5);
    }

    return val;
}

#define FlipX(val) ((maxx - (val)) + minx)
#define FlipY(val) ((maxy - (val)) + miny)
#define inFlipArea(x,y) \
    ((x) >= minx && (x) <= maxx && (y) >= miny && (y) <= maxy)
#define Flip_coord(cc) \
    do {                                            \
        if ((cc).x && inFlipArea((cc).x, (cc).y)) { \
            if (flp & 1)                            \
                (cc).y = FlipY((cc).y);             \
            if (flp & 2)                            \
                (cc).x = FlipX((cc).x);             \
        }                                           \
    } while (0)

/* transpose top with bottom or left with right or both; sometimes called
   for new special levels, or for any level via the #wizfliplevel command */
void
flip_level(
    int flp,        /* mask for orientation(s) to transpose */
    boolean extras) /* False: level creation; True: #wizfliplevel is
                     * altering an active level so more needs to be done */
{
    int x, y, i, itmp;
    coordxy minx, miny, maxx, maxy;
    struct rm trm;
    struct trap *ttmp;
    struct obj *otmp;
    struct monst *mtmp;
    struct engr *etmp;
    struct mkroom *sroom;
    timer_element *timer;
    boolean ball_active = FALSE, ball_fliparea = FALSE;
    stairway *stway;

    /* nothing to do unless (flp & 1) or (flp & 2) or both */
    if ((flp & 3) == 0)
        return;

    get_level_extends(&minx, &miny, &maxx, &maxy);
    /* get_level_extends() returns -1,-1 to COLNO,ROWNO at max */
    if (miny < 0)
        miny = 0;
    if (minx < 1)
        minx = 1;
    if (maxx >= COLNO)
        maxx = (COLNO - 1);
    if (maxy >= ROWNO)
        maxy = (ROWNO - 1);

    if (extras) {
        if (Punished && uball->where != OBJ_FREE) {
            ball_active = TRUE;
            /* if hero and ball and chain are all inside flip area,
               flip b&c coordinates along with other objects; if they
               are all outside, leave them to be rejected when flipping
               so that they stay as is; if some are inside and some are
               outside, un-place here and subsequently re-place them on
               hero's [possibly new] spot below */
            if (carried(uball))
                uball->ox = u.ux, uball->oy = u.uy;
            ball_fliparea = ((inFlipArea(uball->ox, uball->oy)
                              == inFlipArea(uchain->ox, uchain->oy))
                             && (inFlipArea(uball->ox, uball->oy)
                                 == inFlipArea(u.ux, u.uy)));
            if (!ball_fliparea)
                unplacebc();
        }
    }

    /* stairs and ladders */
    for (stway = gs.stairs; stway; stway = stway->next) {
        if (flp & 1)
            stway->sy = FlipY(stway->sy);
        if (flp & 2)
            stway->sx = FlipX(stway->sx);
    }

    /* traps */
    for (ttmp = gf.ftrap; ttmp; ttmp = ttmp->ntrap) {
        if (!inFlipArea(ttmp->tx, ttmp->ty))
            continue;
        if (flp & 1) {
            ttmp->ty = FlipY(ttmp->ty);
            if (ttmp->ttyp == ROLLING_BOULDER_TRAP) {
                ttmp->launch.y = FlipY(ttmp->launch.y);
                ttmp->launch2.y = FlipY(ttmp->launch2.y);
            } else if (is_pit(ttmp->ttyp) && ttmp->conjoined) {
                ttmp->conjoined = flip_encoded_dir_bits(flp, ttmp->conjoined);
            }
        }
        if (flp & 2) {
            ttmp->tx = FlipX(ttmp->tx);
            if (ttmp->ttyp == ROLLING_BOULDER_TRAP) {
                ttmp->launch.x = FlipX(ttmp->launch.x);
                ttmp->launch2.x = FlipX(ttmp->launch2.x);
            } else if (is_pit(ttmp->ttyp) && ttmp->conjoined) {
                ttmp->conjoined = flip_encoded_dir_bits(flp, ttmp->conjoined);
            }
        }
    }

    /* objects */
    for (otmp = fobj; otmp; otmp = otmp->nobj) {
        if (!inFlipArea(otmp->ox, otmp->oy))
            continue;
        if (flp & 1)
            otmp->oy = FlipY(otmp->oy);
        if (flp & 2)
            otmp->ox = FlipX(otmp->ox);
    }

    /* buried objects */
    for (otmp = gl.level.buriedobjlist; otmp; otmp = otmp->nobj) {
        if (!inFlipArea(otmp->ox, otmp->oy))
            continue;
        if (flp & 1)
            otmp->oy = FlipY(otmp->oy);
        if (flp & 2)
            otmp->ox = FlipX(otmp->ox);
    }

    /* monsters */
    for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
        if (mtmp->isgd) {
            if (extras) /* flip mtmp->mextra->egd */
                flip_vault_guard(flp, mtmp, minx, miny, maxx, maxy);
            if (mtmp->mx == 0) /* not on map so don't flip guard->mx,my */
                continue;
        }
        /* skip the occasional earth elemental outside the flip area */
        if (!inFlipArea(mtmp->mx, mtmp->my))
            continue;
        if (flp & 1)
            mtmp->my = FlipY(mtmp->my);
        if (flp & 2)
            mtmp->mx = FlipX(mtmp->mx);

        if (mtmp->ispriest) {
            Flip_coord(EPRI(mtmp)->shrpos);
        } else if (mtmp->isshk) {
            Flip_coord(ESHK(mtmp)->shk); /* shk's preferred spot */
            Flip_coord(ESHK(mtmp)->shd); /* shop door */
        } else if (mtmp->wormno) {
            if (flp & 1)
                flip_worm_segs_vertical(mtmp, miny, maxy);
            if (flp & 2)
                flip_worm_segs_horizontal(mtmp, minx, maxx);
        }
#if 0   /* not useful unless tracking also gets flipped */
        if (extras) {
            if (mtmp->tame && has_edog(mtmp))
                Flip_coord(EDOG(mtmp)->ogoal);
        }
#endif
    }
    if (extras) { /* #wizfliplevel rather than level creation */
        for (mtmp = gm.migrating_mons; mtmp; mtmp = mtmp->nmon) {
            if (mtmp->isgd && on_level(&u.uz, &EGD(mtmp)->gdlevel)) {
                flip_vault_guard(flp, mtmp, minx, miny, maxx, maxy); /* egd */
            } else if (mtmp->ispriest
                       && on_level(&u.uz, &EPRI(mtmp)->shrlevel)) {
                Flip_coord(EPRI(mtmp)->shrpos); /* priest's altar */
            } else if (mtmp->isshk
                       && on_level(&u.uz, &ESHK(mtmp)->shoplevel)) {
                Flip_coord(ESHK(mtmp)->shk); /* shk's preferred spot */
                Flip_coord(ESHK(mtmp)->shd); /* shop door */
            }
        }
    }

    /* engravings */
    for (etmp = head_engr; etmp; etmp = etmp->nxt_engr) {
        if (flp & 1)
            etmp->engr_y = FlipY(etmp->engr_y);
        if (flp & 2)
            etmp->engr_x = FlipX(etmp->engr_x);
    }

    /* level (teleport) regions */
    for (i = 0; i < gn.num_lregions; i++) {
        if (flp & 1) {
            gl.lregions[i].inarea.y1 = FlipY(gl.lregions[i].inarea.y1);
            gl.lregions[i].inarea.y2 = FlipY(gl.lregions[i].inarea.y2);
            if (gl.lregions[i].inarea.y1 > gl.lregions[i].inarea.y2) {
                itmp = gl.lregions[i].inarea.y1;
                gl.lregions[i].inarea.y1 = gl.lregions[i].inarea.y2;
                gl.lregions[i].inarea.y2 = itmp;
            }

            gl.lregions[i].delarea.y1 = FlipY(gl.lregions[i].delarea.y1);
            gl.lregions[i].delarea.y2 = FlipY(gl.lregions[i].delarea.y2);
            if (gl.lregions[i].delarea.y1 > gl.lregions[i].delarea.y2) {
                itmp = gl.lregions[i].delarea.y1;
                gl.lregions[i].delarea.y1 = gl.lregions[i].delarea.y2;
                gl.lregions[i].delarea.y2 = itmp;
            }
        }
        if (flp & 2) {
            gl.lregions[i].inarea.x1 = FlipX(gl.lregions[i].inarea.x1);
            gl.lregions[i].inarea.x2 = FlipX(gl.lregions[i].inarea.x2);
            if (gl.lregions[i].inarea.x1 > gl.lregions[i].inarea.x2) {
                itmp = gl.lregions[i].inarea.x1;
                gl.lregions[i].inarea.x1 = gl.lregions[i].inarea.x2;
                gl.lregions[i].inarea.x2 = itmp;
            }

            gl.lregions[i].delarea.x1 = FlipX(gl.lregions[i].delarea.x1);
            gl.lregions[i].delarea.x2 = FlipX(gl.lregions[i].delarea.x2);
            if (gl.lregions[i].delarea.x1 > gl.lregions[i].delarea.x2) {
                itmp = gl.lregions[i].delarea.x1;
                gl.lregions[i].delarea.x1 = gl.lregions[i].delarea.x2;
                gl.lregions[i].delarea.x2 = itmp;
            }
        }
    }

    /* regions (poison clouds, etc) */
    for (i = 0; i < gn.n_regions; i++) {
        int j, tmp1, tmp2;
        if (flp & 1) {
            tmp1 = FlipY(gr.regions[i]->bounding_box.ly);
            tmp2 = FlipY(gr.regions[i]->bounding_box.hy);
            gr.regions[i]->bounding_box.ly = min(tmp1, tmp2);
            gr.regions[i]->bounding_box.hy = max(tmp1, tmp2);
            for (j = 0; j < gr.regions[i]->nrects; j++) {
                tmp1 = FlipY(gr.regions[i]->rects[j].ly);
                tmp2 = FlipY(gr.regions[i]->rects[j].hy);
                gr.regions[i]->rects[j].ly = min(tmp1, tmp2);
                gr.regions[i]->rects[j].hy = max(tmp1, tmp2);
            }
        }
        if (flp & 2) {
            tmp1 = FlipX(gr.regions[i]->bounding_box.lx);
            tmp2 = FlipX(gr.regions[i]->bounding_box.hx);
            gr.regions[i]->bounding_box.lx = min(tmp1, tmp2);
            gr.regions[i]->bounding_box.hx = max(tmp1, tmp2);
            for (j = 0; j < gr.regions[i]->nrects; j++) {
                tmp1 = FlipX(gr.regions[i]->rects[j].lx);
                tmp2 = FlipX(gr.regions[i]->rects[j].hx);
                gr.regions[i]->rects[j].lx = min(tmp1, tmp2);
                gr.regions[i]->rects[j].hx = max(tmp1, tmp2);
            }
        }
    }

    /* rooms */
    for (sroom = &gr.rooms[0]; ; sroom++) {
        if (sroom->hx < 0)
            break;

        if (flp & 1) {
            sroom->ly = FlipY(sroom->ly);
            sroom->hy = FlipY(sroom->hy);
            if (sroom->ly > sroom->hy) {
                itmp = sroom->ly;
                sroom->ly = sroom->hy;
                sroom->hy = itmp;
            }
        }
        if (flp & 2) {
            sroom->lx = FlipX(sroom->lx);
            sroom->hx = FlipX(sroom->hx);
            if (sroom->lx > sroom->hx) {
                itmp = sroom->lx;
                sroom->lx = sroom->hx;
                sroom->hx = itmp;
            }
        }

        if (sroom->nsubrooms)
            for (i = 0; i < sroom->nsubrooms; i++) {
                struct mkroom *rroom = sroom->sbrooms[i];

                if (flp & 1) {
                    rroom->ly = FlipY(rroom->ly);
                    rroom->hy = FlipY(rroom->hy);
                    if (rroom->ly > rroom->hy) {
                        itmp = rroom->ly;
                        rroom->ly = rroom->hy;
                        rroom->hy = itmp;
                    }
                }
                if (flp & 2) {
                    rroom->lx = FlipX(rroom->lx);
                    rroom->hx = FlipX(rroom->hx);
                    if (rroom->lx > rroom->hx) {
                        itmp = rroom->lx;
                        rroom->lx = rroom->hx;
                        rroom->hx = itmp;
                    }
                }
            }
    }

    /* doors */
    for (i = 0; i < gd.doorindex; i++) {
        Flip_coord(gd.doors[i]);
    }

    /* the map */
    if (flp & 1) {
        for (x = minx; x <= maxx; x++)
            for (y = miny; y < (miny + ((maxy - miny + 1) / 2)); y++) {
                int ny = FlipY(y);

                flip_dbridge_vertical(&levl[x][y]);
                flip_dbridge_vertical(&levl[x][ny]);

                trm = levl[x][y];
                levl[x][y] = levl[x][ny];
                levl[x][ny] = trm;

                otmp = gl.level.objects[x][y];
                gl.level.objects[x][y] = gl.level.objects[x][ny];
                gl.level.objects[x][ny] = otmp;

                mtmp = gl.level.monsters[x][y];
                gl.level.monsters[x][y] = gl.level.monsters[x][ny];
                gl.level.monsters[x][ny] = mtmp;
            }
    }
    if (flp & 2) {
        for (x = minx; x < (minx + ((maxx - minx + 1) / 2)); x++)
            for (y = miny; y <= maxy; y++) {
                int nx = FlipX(x);

                flip_dbridge_horizontal(&levl[x][y]);
                flip_dbridge_horizontal(&levl[nx][y]);

                trm = levl[x][y];
                levl[x][y] = levl[nx][y];
                levl[nx][y] = trm;

                otmp = gl.level.objects[x][y];
                gl.level.objects[x][y] = gl.level.objects[nx][y];
                gl.level.objects[nx][y] = otmp;

                mtmp = gl.level.monsters[x][y];
                gl.level.monsters[x][y] = gl.level.monsters[nx][y];
                gl.level.monsters[nx][y] = mtmp;
            }
    }

    /* timed effects */
    for (timer = gt.timer_base; timer; timer = timer->next) {
        if (timer->func_index == MELT_ICE_AWAY) {
            long ty = timer->arg.a_long & 0xffff;
            long tx = (timer->arg.a_long >> 16) & 0xffff;

            if (flp & 1)
                ty = FlipY(ty);
            if (flp & 2)
                tx = FlipX(tx);
            timer->arg.a_long = ((tx << 16) | ty);
        }
    }

    if (extras) { /* for #wizfliplevel rather than during level creation */
        /* flip hero location only if inside the flippable area */
        if (inFlipArea(u.ux, u.uy)) {
            if (flp & 1)
                u.uy = FlipY(u.uy);
            if (flp & 2)
                u.ux = FlipX(u.ux);
            /* we could flip <ux0,uy0> too if it's inside the flip area,
               but have to resort to this if outside, so just do this */
            u.ux0 = u.ux, u.uy0 = u.uy;
        }
        if (ball_active && !ball_fliparea)
            placebc();
        Flip_coord(iflags.travelcc);
        Flip_coord(gc.context.digging.pos);
    }

    fix_wall_spines(1, 0, COLNO - 1, ROWNO - 1);
    if (extras && flp) {
        set_wall_state();
        /* after wall_spines; flips seenv and wall joins */
        flip_visuals(flp, minx, miny, maxx, maxy);
    }
    vision_reset();
}

/* for #wizfliplevel, flip guard's egd data; not needed for level creation */
static void
flip_vault_guard(
    int flp, /* 1: transpose vertically, 2: transpose horizontally, 3: both */
    struct monst *grd, /* the vault guard, has monst->mextra->egd data */
    coordxy minx, coordxy miny, /* needed by FlipX(), FlipY(), */
    coordxy maxx, coordxy maxy) /* and inFlipArea() macros     */
{
    int i;
    struct egd *egd = EGD(grd);

    if (inFlipArea(egd->gdx, egd->gdy)) {
        if (flp & 1)
            egd->gdy = FlipY(egd->gdy);
        if (flp & 2)
            egd->gdx = FlipX(egd->gdx);
    }
    if (inFlipArea(egd->ogx, egd->ogy)) {
        if (flp & 1)
            egd->ogy = FlipY(egd->ogy);
        if (flp & 2)
            egd->ogx = FlipX(egd->ogx);
    }
    for (i = egd->fcbeg; i < egd->fcend; ++i) {
        coordxy fx = egd->fakecorr[i].fx, fy = egd->fakecorr[i].fy;

        if (inFlipArea(fx, fy)) {
            if (flp & 1)
                egd->fakecorr[i].fy = FlipY(fy);
            if (flp & 2)
                egd->fakecorr[i].fx = FlipX(fx);
        }
    }
    return;
}

#undef FlipX
#undef FlipY
#undef inFlipArea

/* randomly transpose top with bottom or left with right or both;
   caller controls which transpositions are allowed */
void
flip_level_rnd(int flp, boolean extras)
{
    int c = 0;

    /* TODO?
     *  Might change rn2(2) to !rn2(3) or (rn2(5) < 2) in order to bias
     *  the outcome towards the traditional orientation.
     */
    if ((flp & 1) && rn2(2))
        c |= 1;
    if ((flp & 2) && rn2(2))
        c |= 2;

    if (c)
        flip_level(c, extras);
}


static void
sel_set_wall_property(coordxy x, coordxy y, genericptr_t arg)
{
    int prop = *(int *)arg;

    if (IS_STWALL(levl[x][y].typ) || IS_TREE(levl[x][y].typ)
        /* 3.6.2: made iron bars eligible to be flagged nondiggable
           (checked by chewing(hack.c) and zap_over_floor(zap.c)) */
        || levl[x][y].typ == IRONBARS)
        levl[x][y].wall_info |= prop;
}

/*
 * Make walls of the area (x1, y1, x2, y2) non diggable/non passwall-able
 */
static void
set_wall_property(coordxy x1, coordxy y1, coordxy x2, coordxy y2, int prop)
{
    register coordxy x, y;

    x1 = max(x1, 1);
    x2 = min(x2, COLNO - 1);
    y1 = max(y1, 0);
    y2 = min(y2, ROWNO - 1);
    for (y = y1; y <= y2; y++)
        for (x = x1; x <= x2; x++) {
            sel_set_wall_property(x, y, (genericptr_t)&prop);
        }
}

static void
remove_boundary_syms(void)
{
    /*
     * If any CROSSWALLs are found, must change to ROOM after REGION's
     * are laid out.  CROSSWALLS are used to specify "invisible"
     * boundaries where DOOR syms look bad or aren't desirable.
     */
    coordxy x, y;
    boolean has_bounds = FALSE;

    for (x = 0; x < COLNO - 1; x++)
        for (y = 0; y < ROWNO - 1; y++)
            if (levl[x][y].typ == CROSSWALL) {
                has_bounds = TRUE;
                break;
            }
    if (has_bounds) {
        for (x = 0; x < gx.x_maze_max; x++)
            for (y = 0; y < gy.y_maze_max; y++)
                if ((levl[x][y].typ == CROSSWALL) && SpLev_Map[x][y])
                    levl[x][y].typ = ROOM;
    }
}

/* used by sel_set_door() and link_doors_rooms() */
static void
set_door_orientation(int x, int y)
{
    boolean wleft, wright, wup, wdown;

    /* If there's a wall or door on either the left side or right
     * side (or both) of this secret door, make it be horizontal.
     *
     * It is feasible to put SDOOR in a corner, tee, or crosswall
     * position, although once the door is found and opened it won't
     * make a lot sense (diagonal access required).  Still, we try to
     * handle that as best as possible.  For top or bottom tee, using
     * horizontal is the best we can do.  For corner or crosswall,
     * either horizontal or vertical are just as good as each other;
     * we produce horizontal for corners and vertical for crosswalls.
     * For left or right tee, using vertical is best.
     *
     * A secret door with no adjacent walls is also feasible and makes
     * even less sense.  It will be displayed as a vertical wall while
     * hidden and become a vertical door when found.  Before resorting
     * to that, we check for solid rock which hasn't been wallified
     * yet (cf lower leftside of leader's room in Cav quest).
     */
    wleft  = (isok(x - 1, y) && (IS_WALL(levl[x - 1][y].typ)
                                 || IS_DOOR(levl[x - 1][y].typ)
                                 || levl[x - 1][y].typ == SDOOR));
    wright = (isok(x + 1, y) && (IS_WALL(levl[x + 1][y].typ)
                                 || IS_DOOR(levl[x + 1][y].typ)
                                 || levl[x + 1][y].typ == SDOOR));
    wup    = (isok(x, y - 1) && (IS_WALL(levl[x][y - 1].typ)
                                 || IS_DOOR(levl[x][y - 1].typ)
                                 || levl[x][y - 1].typ == SDOOR));
    wdown  = (isok(x, y + 1) && (IS_WALL(levl[x][y + 1].typ)
                                 || IS_DOOR(levl[x][y + 1].typ)
                                 || levl[x][y + 1].typ == SDOOR));
    if (!wleft && !wright && !wup && !wdown) {
        /* out of bounds is treated as implicit wall; should be academic
           because we don't expect to have doors so near the level's edge */
        wleft  = (!isok(x - 1, y) || IS_DOORJOIN(levl[x - 1][y].typ));
        wright = (!isok(x + 1, y) || IS_DOORJOIN(levl[x + 1][y].typ));
        wup    = (!isok(x, y - 1) || IS_DOORJOIN(levl[x][y - 1].typ));
        wdown  = (!isok(x, y + 1) || IS_DOORJOIN(levl[x][y + 1].typ));
    }
    levl[x][y].horizontal = ((wleft || wright) && !(wup && wdown)) ? 1 : 0;
}

/* is x,y right next to room droom? */
static boolean
shared_with_room(int x, int y, struct mkroom *droom)
{
    int rmno = (droom - gr.rooms) + ROOMOFFSET;

    if (!isok(x,y))
        return FALSE;
    if ((int) levl[x][y].roomno == rmno && !levl[x][y].edge)
        return FALSE;
    if (isok(x-1, y) && (int) levl[x-1][y].roomno == rmno && x-1 <= droom->hx)
        return TRUE;
    if (isok(x+1, y) && (int) levl[x+1][y].roomno == rmno && x+1 >= droom->lx)
        return TRUE;
    if (isok(x, y-1) && (int) levl[x][y-1].roomno == rmno && y-1 <= droom->hy)
        return TRUE;
    if (isok(x, y+1) && (int) levl[x][y+1].roomno == rmno && y+1 >= droom->ly)
        return TRUE;
    return FALSE;
}

/* maybe add door at x,y to room droom */
static void
maybe_add_door(int x, int y, struct mkroom *droom)
{
    if (droom->hx >= 0
        && ((!droom->irregular && inside_room(droom, x, y))
            || (int) levl[x][y].roomno == (droom - gr.rooms) + ROOMOFFSET
            || shared_with_room(x, y, droom))) {
        add_door(x, y, droom);
    }
}

/* link all doors in the map to their corresponding rooms */
static void
link_doors_rooms(void)
{
    int x, y;
    int tmpi, m;

    for (y = 0; y < ROWNO; y++)
        for (x = 0; x < COLNO; x++)
            if (IS_DOOR(levl[x][y].typ) || levl[x][y].typ == SDOOR) {
                /* in case this door was a '+' or 'S' from the
                   MAP...ENDMAP section without an explicit DOOR
                   directive, set/clear levl[][].horizontal for it */
                set_door_orientation(x, y);

                for (tmpi = 0; tmpi < gn.nroom; tmpi++) {
                    maybe_add_door(x, y, &gr.rooms[tmpi]);
                    for (m = 0; m < gr.rooms[tmpi].nsubrooms; m++) {
                        maybe_add_door(x, y, gr.rooms[tmpi].sbrooms[m]);
                    }
                }
            }
}

/*
 * Choose randomly the state (nodoor, open, closed or locked) for a door
 */
static int
rnddoor(void)
{
    static int state[] = { D_NODOOR, D_BROKEN, D_ISOPEN, D_CLOSED, D_LOCKED };

    return state[rn2(SIZE(state))];
}

/*
 * Select a random trap
 */
static int
rndtrap(void)
{
    int rtrap;

    do {
        rtrap = rnd(TRAPNUM - 1);
        switch (rtrap) {
        case HOLE: /* no random holes on special levels */
        case VIBRATING_SQUARE:
        case MAGIC_PORTAL:
            rtrap = NO_TRAP;
            break;
        case TRAPDOOR:
            if (!Can_dig_down(&u.uz))
                rtrap = NO_TRAP;
            break;
        case LEVEL_TELEP:
        case TELEP_TRAP:
            if (gl.level.flags.noteleport)
                rtrap = NO_TRAP;
            break;
        case ROLLING_BOULDER_TRAP:
        case ROCKTRAP:
            if (In_endgame(&u.uz))
                rtrap = NO_TRAP;
            break;
        }
    } while (rtrap == NO_TRAP);
    return rtrap;
}

/*
 * Translate a given coordinate from a special level definition into an actual
 * location on the map.
 *
 * If x or y is negative, we generate a random coordinate within the area. If
 * not negative, they are interpreted as relative to the last defined map or
 * room, and are output as absolute gl.level.locations coordinates.
 *
 * The "humidity" flag is used to ensure that engravings aren't created
 * underwater, or eels on dry land.
 */
static void
get_location(
    coordxy *x, coordxy *y,
    getloc_flags_t humidity,
    struct mkroom *croom)
{
    int cpt = 0;
    int mx, my, sx, sy;

    if (croom) {
        mx = croom->lx;
        my = croom->ly;
        sx = croom->hx - mx + 1;
        sy = croom->hy - my + 1;
    } else {
        mx = gx.xstart;
        my = gy.ystart;
        sx = gx.xsize;
        sy = gy.ysize;
    }

    if (*x >= 0) { /* normal locations */
        *x += mx;
        *y += my;
    } else { /* random location */
        do {
            if (croom) { /* handle irregular areas */
                coord tmpc;
                (void) somexy(croom, &tmpc);
                *x = tmpc.x;
                *y = tmpc.y;
            } else {
                *x = mx + rn2((int) sx);
                *y = my + rn2((int) sy);
            }
            if (is_ok_location(*x, *y, humidity))
                break;
        } while (++cpt < 100);
        if (cpt >= 100) {
            register int xx, yy;

            /* last try */
            for (xx = 0; xx < sx; xx++)
                for (yy = 0; yy < sy; yy++) {
                    *x = mx + xx;
                    *y = my + yy;
                    if (is_ok_location(*x, *y, humidity))
                        goto found_it;
                }
            if (!(humidity & NO_LOC_WARN)) {
                impossible("get_location:  can't find a place!");
            } else {
                *x = *y = -1;
            }
        }
    }
 found_it:
    ;

    if (!(humidity & ANY_LOC) && !isok(*x, *y)) {
        if (!(humidity & NO_LOC_WARN)) {
            /*warning("get_location:  (%d,%d) out of bounds", *x, *y);*/
            *x = gx.x_maze_max;
            *y = gy.y_maze_max;
        } else {
            *x = *y = -1;
        }
    }
}

static boolean (*is_ok_location_func)(coordxy, coordxy) = NULL;

static void
set_ok_location_func(boolean (*func)(coordxy, coordxy))
{
    is_ok_location_func = func;
}

static boolean
is_ok_location(coordxy x, coordxy y, getloc_flags_t humidity)
{
    register int typ = levl[x][y].typ;

    if (Is_waterlevel(&u.uz))
        return TRUE; /* accept any spot */

    if (is_ok_location_func)
        return is_ok_location_func(x, y);

    /* TODO: Should perhaps check if wall is diggable/passwall? */
    if (humidity & ANY_LOC)
        return TRUE;

    if ((humidity & SOLID) && IS_ROCK(typ))
        return TRUE;

    if ((humidity & (DRY|SPACELOC)) && SPACE_POS(typ)) {
        boolean bould = (sobj_at(BOULDER, x, y) != NULL);

        if (!bould || (bould && (humidity & SOLID)))
            return TRUE;
    }
    if ((humidity & WET) && is_pool(x, y))
        return TRUE;
    if ((humidity & HOT) && is_lava(x, y))
        return TRUE;
    return FALSE;
}

boolean
pm_good_location(coordxy x, coordxy y, struct permonst *pm)
{
    return is_ok_location(x, y, pm_to_humidity(pm));
}

static unpacked_coord
get_unpacked_coord(long loc, int defhumidity)
{
    static unpacked_coord c;

    if (loc & SP_COORD_IS_RANDOM) {
        c.x = c.y = -1;
        c.is_random = 1;
        c.getloc_flags = (getloc_flags_t)(loc & ~SP_COORD_IS_RANDOM);
        if (!c.getloc_flags)
            c.getloc_flags = defhumidity;
    } else {
        c.is_random = 0;
        c.getloc_flags = defhumidity;
        c.x = SP_COORD_X(loc);
        c.y = SP_COORD_Y(loc);
    }
    return c;
}

void
get_location_coord(
    coordxy *x, coordxy *y,
    int humidity,
    struct mkroom *croom,
    long crd)
{
    unpacked_coord c;

    c = get_unpacked_coord(crd, humidity);
    *x = c.x;
    *y = c.y;
    get_location(x, y, c.getloc_flags | (c.is_random ? NO_LOC_WARN : 0),
                 croom);

    if (*x == -1 && *y == -1 && c.is_random)
        get_location(x, y, humidity, croom);
}

/*
 * Get a relative position inside a room.
 * negative values for x or y means RANDOM!
 */
static void
get_room_loc(coordxy *x, coordxy *y, struct mkroom *croom)
{
    coord c;

    if (*x < 0 && *y < 0) {
        if (somexy(croom, &c)) {
            *x = c.x;
            *y = c.y;
        } else
            panic("get_room_loc : can't find a place!");
    } else {
        if (*x < 0)
            *x = rn2(croom->hx - croom->lx + 1);
        if (*y < 0)
            *y = rn2(croom->hy - croom->ly + 1);
        *x += croom->lx;
        *y += croom->ly;
    }
}

/*
 * Get a relative position inside a room.
 * negative values for x or y means RANDOM!
 */
static void
get_free_room_loc(
    coordxy *x, coordxy *y,
    struct mkroom *croom,
    packed_coord pos)
{
    coordxy try_x, try_y;
    register int trycnt = 0;

    get_location_coord(&try_x, &try_y, DRY, croom, pos);
    if (levl[try_x][try_y].typ != ROOM) {
        do {
            try_x = *x, try_y = *y;
            get_room_loc(&try_x, &try_y, croom);
        } while (levl[try_x][try_y].typ != ROOM && ++trycnt <= 100);

        if (trycnt > 100)
            panic("get_free_room_loc:  can't find a place!");
    }
    *x = try_x, *y = try_y;
}

boolean
check_room(
    coordxy *lowx, coordxy *ddx,
    coordxy *lowy, coordxy *ddy,
    boolean vault)
{
    register int x, y, hix = *lowx + *ddx, hiy = *lowy + *ddy;
    register struct rm *lev;
    int xlim, ylim, ymax;
    coordxy s_lowx, s_ddx, s_lowy, s_ddy;

    s_lowx = *lowx; s_ddx = *ddx;
    s_lowy = *lowy; s_ddy = *ddy;

    xlim = XLIM + (vault ? 1 : 0);
    ylim = YLIM + (vault ? 1 : 0);

    if (*lowx < 3)
        *lowx = 3;
    if (*lowy < 2)
        *lowy = 2;
    if (hix > COLNO - 3)
        hix = COLNO - 3;
    if (hiy > ROWNO - 3)
        hiy = ROWNO - 3;
 chk:
    if (hix <= *lowx || hiy <= *lowy)
        return FALSE;

    if (gi.in_mk_themerooms && (s_lowx != *lowx) && (s_ddx != *ddx)
        && (s_lowy != *lowy) && (s_ddy != *ddy))
        return FALSE;

    /* check area around room (and make room smaller if necessary) */
    for (x = *lowx - xlim; x <= hix + xlim; x++) {
        if (x <= 0 || x >= COLNO)
            continue;
        y = *lowy - ylim;
        ymax = hiy + ylim;
        if (y < 0)
            y = 0;
        if (ymax >= ROWNO)
            ymax = (ROWNO - 1);
        lev = &levl[x][y];
        for (; y <= ymax; y++) {
            if (lev++->typ != STONE) {
                if (!vault) {
                    debugpline2("strange area [%d,%d] in check_room.", x, y);
                }
                if (!rn2(3))
                    return FALSE;
                if (gi.in_mk_themerooms)
                    return FALSE;
                if (x < *lowx)
                    *lowx = x + xlim + 1;
                else
                    hix = x - xlim - 1;
                if (y < *lowy)
                    *lowy = y + ylim + 1;
                else
                    hiy = y - ylim - 1;
                goto chk;
            }
        }
    }
    *ddx = hix - *lowx;
    *ddy = hiy - *lowy;

    if (gi.in_mk_themerooms && (s_lowx != *lowx) && (s_ddx != *ddx)
        && (s_lowy != *lowy) && (s_ddy != *ddy))
        return FALSE;

    return TRUE;
}

/*
 * Create a new room.
 * This is still very incomplete...
 */
boolean
create_room(
    coordxy x, coordxy y,
    coordxy w, coordxy h,
    coordxy xal, coordxy yal,
    xint16 rtype, xint16 rlit)
{
    coordxy xabs = 0, yabs = 0;
    int wtmp, htmp, xaltmp, yaltmp, xtmp, ytmp;
    NhRect *r1 = 0, r2;
    int trycnt = 0;
    boolean vault = FALSE;
    int xlim = XLIM, ylim = YLIM;

    if (rtype == -1) /* Is the type random ? */
        rtype = OROOM;

    if (rtype == VAULT) {
        vault = TRUE;
        xlim++;
        ylim++;
    }

    /* on low levels the room is lit (usually) */
    /* some other rooms may require lighting */

    /* is light state random ? */
    rlit = litstate_rnd(rlit);

    /*
     * Here we will try to create a room. If some parameters are
     * random we are willing to make several try before we give
     * it up.
     */
    do {
        coordxy xborder, yborder;

        wtmp = w;
        htmp = h;
        xtmp = x;
        ytmp = y;
        xaltmp = xal;
        yaltmp = yal;

        /* First case : a totally random room */

        if ((xtmp < 0 && ytmp < 0 && wtmp < 0 && xaltmp < 0 && yaltmp < 0)
            || vault) {
            coordxy hx, hy, lx, ly, dx, dy;

            r1 = rnd_rect(); /* Get a random rectangle */

            if (!r1) { /* No more free rectangles ! */
                debugpline0("No more rects...");
                return FALSE;
            }
            hx = r1->hx;
            hy = r1->hy;
            lx = r1->lx;
            ly = r1->ly;
            if (vault) {
                dx = dy = 1;
            } else {
                dx = 2 + rn2((hx - lx > 28) ? 12 : 8);
                dy = 2 + rn2(4);
                if (dx * dy > 50)
                    dy = 50 / dx;
            }
            xborder = (lx > 0 && hx < COLNO - 1) ? 2 * xlim : xlim + 1;
            yborder = (ly > 0 && hy < ROWNO - 1) ? 2 * ylim : ylim + 1;
            if (hx - lx < dx + 3 + xborder || hy - ly < dy + 3 + yborder) {
                r1 = 0;
                continue;
            }
            xabs = lx + (lx > 0 ? xlim : 3)
                   + rn2(hx - (lx > 0 ? lx : 3) - dx - xborder + 1);
            yabs = ly + (ly > 0 ? ylim : 2)
                   + rn2(hy - (ly > 0 ? ly : 2) - dy - yborder + 1);
            if (ly == 0 && hy >= (ROWNO - 1) && (!gn.nroom || !rn2(gn.nroom))
                && (yabs + dy > ROWNO / 2)) {
                yabs = rn1(3, 2);
                if (gn.nroom < 4 && dy > 1)
                    dy--;
            }
            if (!check_room(&xabs, &dx, &yabs, &dy, vault)) {
                r1 = 0;
                continue;
            }
            wtmp = dx + 1;
            htmp = dy + 1;
            r2.lx = xabs - 1;
            r2.ly = yabs - 1;
            r2.hx = xabs + wtmp;
            r2.hy = yabs + htmp;
        } else { /* Only some parameters are random */
            int rndpos = 0;
            coordxy dx, dy;

            if (xtmp < 0 && ytmp < 0) { /* Position is RANDOM */
                xtmp = rnd(5);
                ytmp = rnd(5);
                rndpos = 1;
            }
            if (wtmp < 0 || htmp < 0) { /* Size is RANDOM */
                wtmp = rn1(15, 3);
                htmp = rn1(8, 2);
            }
            if (xaltmp == -1) /* Horizontal alignment is RANDOM */
                xaltmp = rnd(3);
            if (yaltmp == -1) /* Vertical alignment is RANDOM */
                yaltmp = rnd(3);

            /* Try to generate real (absolute) coordinates here! */

            xabs = (((xtmp - 1) * COLNO) / 5) + 1;
            yabs = (((ytmp - 1) * ROWNO) / 5) + 1;
            switch (xaltmp) {
            case SPLEV_LEFT:
                break;
            case SPLEV_RIGHT:
                xabs += (COLNO / 5) - wtmp;
                break;
            case SPLEV_CENTER:
                xabs += ((COLNO / 5) - wtmp) / 2;
                break;
            }
            switch (yaltmp) {
            case TOP:
                break;
            case BOTTOM:
                yabs += (ROWNO / 5) - htmp;
                break;
            case SPLEV_CENTER:
                yabs += ((ROWNO / 5) - htmp) / 2;
                break;
            }

            if (xabs + wtmp - 1 > COLNO - 2)
                xabs = COLNO - wtmp - 3;
            if (xabs < 2)
                xabs = 2;
            if (yabs + htmp - 1 > ROWNO - 2)
                yabs = ROWNO - htmp - 3;
            if (yabs < 2)
                yabs = 2;

            /* Try to find a rectangle that fit our room ! */

            r2.lx = xabs - 1;
            r2.ly = yabs - 1;
            r2.hx = xabs + wtmp + rndpos;
            r2.hy = yabs + htmp + rndpos;
            r1 = get_rect(&r2);
            dx = wtmp;
            dy = htmp;

            if (r1 && !check_room(&xabs, &dx, &yabs, &dy, vault)) {
                r1 = 0;
            }
        }
    } while (++trycnt <= 100 && !r1);
    if (!r1) { /* creation of room failed ? */
        return FALSE;
    }
    split_rects(r1, &r2);

    if (!vault) {
        gs.smeq[gn.nroom] = gn.nroom;
        add_room(xabs, yabs, xabs + wtmp - 1, yabs + htmp - 1, rlit, rtype,
                 FALSE);
    } else {
        gr.rooms[gn.nroom].lx = xabs;
        gr.rooms[gn.nroom].ly = yabs;
    }
    return TRUE;
}

/*
 * Create a subroom in room proom at pos x,y with width w & height h.
 * x & y are relative to the parent room.
 */
static boolean
create_subroom(
    struct mkroom *proom,
    coordxy x, coordxy y,
    coordxy w, coordxy h,
    xint16 rtype, xint16 rlit)
{
    coordxy width, height;

    width = proom->hx - proom->lx + 1;
    height = proom->hy - proom->ly + 1;

    /* There is a minimum size for the parent room */
    if (width < 4 || height < 4)
        return FALSE;

    /* Check for random position, size, etc... */

    if (w == -1)
        w = rnd(width - 3);
    if (h == -1)
        h = rnd(height - 3);
    if (x == -1)
        x = rnd(width - w);
    if (y == -1)
        y = rnd(height - h);
    if (x == 1)
        x = 0;
    if (y == 1)
        y = 0;
    if ((x + w + 1) == width)
        x++;
    if ((y + h + 1) == height)
        y++;
    if (rtype == -1)
        rtype = OROOM;
    rlit = litstate_rnd(rlit);
    add_subroom(proom, proom->lx + x, proom->ly + y, proom->lx + x + w - 1,
                proom->ly + y + h - 1, rlit, rtype, FALSE);
    return TRUE;
}

/*
 * Create a new door in a room.
 * It's placed on a wall (north, south, east or west).
 */
static void
create_door(room_door *dd, struct mkroom *broom)
{
    int x = 0, y = 0;
    int trycnt;

    if (dd->secret == -1)
        dd->secret = rn2(2);

    if (dd->wall == W_RANDOM)
        dd->wall = W_ANY; /* speeds things up in the below loop */

    if (dd->mask == -1) {
        /* is it a locked door, closed, or a doorway? */
        if (!dd->secret) {
            if (!rn2(3)) {
                if (!rn2(5))
                    dd->mask = D_ISOPEN;
                else if (!rn2(6))
                    dd->mask = D_LOCKED;
                else
                    dd->mask = D_CLOSED;
                if (dd->mask != D_ISOPEN && !rn2(25))
                    dd->mask |= D_TRAPPED;
            } else
                dd->mask = D_NODOOR;
        } else {
            if (!rn2(5))
                dd->mask = D_LOCKED;
            else
                dd->mask = D_CLOSED;

            if (!rn2(20))
                dd->mask |= D_TRAPPED;
        }
    }

    for (trycnt = 0; trycnt < 100; ++trycnt) {
        int dwall = dd->wall, dpos = dd->pos;

        /* Convert wall and pos into an absolute coordinate! */
        switch (rn2(4)) {
        case 0:
            if (!(dwall & W_NORTH))
                continue;
            y = broom->ly - 1;
            x = broom->lx + ((dpos == -1) ? rn2(1 + broom->hx - broom->lx)
                                          : dpos);
            if (!isok(x, y - 1) || IS_ROCK(levl[x][y - 1].typ))
                continue;
            break;
        case 1:
            if (!(dwall & W_SOUTH))
                continue;
            y = broom->hy + 1;
            x = broom->lx + ((dpos == -1) ? rn2(1 + broom->hx - broom->lx)
                                          : dpos);
            if (!isok(x, y + 1) || IS_ROCK(levl[x][y + 1].typ))
                continue;
            break;
        case 2:
            if (!(dwall & W_WEST))
                continue;
            x = broom->lx - 1;
            y = broom->ly + ((dpos == -1) ? rn2(1 + broom->hy - broom->ly)
                                          : dpos);
            if (!isok(x - 1, y) || IS_ROCK(levl[x - 1][y].typ))
                continue;
            break;
        case 3:
            if (!(dwall & W_EAST))
                continue;
            x = broom->hx + 1;
            y = broom->ly + ((dpos == -1) ? rn2(1 + broom->hy - broom->ly)
                                          : dpos);
            if (!isok(x + 1, y) || IS_ROCK(levl[x + 1][y].typ))
                continue;
            break;
        default:
            /*NOTREACHED*/
            break;
        }

        if (okdoor(x, y))
            break;
    }
    if (trycnt >= 100) {
        impossible("create_door: Can't find a proper place!");
        return;
    }
    if (!set_levltyp(x, y, (dd->secret ? SDOOR : DOOR)))
        return;
    levl[x][y].doormask = dd->mask;
}

/*
 * Create a trap in a room.
 */
static void
create_trap(spltrap *t, struct mkroom *croom)
{
    coordxy x = -1, y = -1;
    coord tm;
    int mktrap_flags = MKTRAP_MAZEFLAG;

    if (t->type == VIBRATING_SQUARE) {
        pick_vibrasquare_location();
        maketrap(gi.inv_pos.x, gi.inv_pos.y, VIBRATING_SQUARE);
        return;
    } else if (croom) {
        get_free_room_loc(&x, &y, croom, t->coord);
    } else {
        int trycnt = 0;

        do {
            get_location_coord(&x, &y, DRY, croom, t->coord);
        } while ((levl[x][y].typ == STAIRS || levl[x][y].typ == LADDER)
                 && ++trycnt <= 100);
        if (trycnt > 100)
            return;
    }

    if (!t->spider_on_web)
        mktrap_flags |= MKTRAP_NOSPIDERONWEB;
    if (t->seen)
        mktrap_flags |= MKTRAP_SEEN;
    if (t->novictim)
        mktrap_flags |= MKTRAP_NOVICTIM;

    tm.x = x;
    tm.y = y;

    mktrap(t->type, mktrap_flags, (struct mkroom *) 0, &tm);
}

/*
 * Create a monster in a room.
 */
static int
noncoalignment(aligntyp alignment)
{
    int k;

    k = rn2(2);
    if (!alignment)
        return (k ? -1 : 1);
    return (k ? -alignment : 0);
}

/* attempt to screen out locations where a mimic-as-boulder shouldn't occur */
static boolean
m_bad_boulder_spot(coordxy x, coordxy y)
{
    struct rm *lev;

    /* avoid trap locations */
    if (t_at(x, y))
        return TRUE;
    /* try to avoid locations which already have a boulder (this won't
       actually work; we get called before objects have been placed...) */
    if (sobj_at(BOULDER, x, y))
        return TRUE;
    /* avoid closed doors */
    lev = &levl[x][y];
    if (IS_DOOR(lev->typ) && (lev->doormask & (D_CLOSED | D_LOCKED)) != 0)
        return TRUE;
    /* spot is ok */
    return FALSE;
}

static int
pm_to_humidity(struct permonst *pm)
{
    int loc = DRY;

    if (!pm)
        return loc;
    if (pm->mlet == S_EEL || amphibious(pm) || is_swimmer(pm))
        loc = WET;
    if (is_flyer(pm) || is_floater(pm))
        loc |= (HOT | WET);
    if (passes_walls(pm) || noncorporeal(pm))
        loc |= SOLID;
    if (likes_fire(pm))
        loc |= HOT;
    return loc;
}

/*
 * Convert a special level alignment mask (an alignment mask with possible
 * extra values/flags) to a "normal" alignment mask (no extra flags).
 *
 * When random: there is an 80% chance that the altar will be co-aligned.
 */
static unsigned int
sp_amask_to_amask(unsigned int sp_amask)
{
    unsigned int amask;

    if (sp_amask == AM_SPLEV_CO)
        amask = Align2amask(u.ualignbase[A_ORIGINAL]);
    else if (sp_amask == AM_SPLEV_NONCO)
        amask = Align2amask(noncoalignment(u.ualignbase[A_ORIGINAL]));
    else if (sp_amask == AM_SPLEV_RANDOM)
        amask = induced_align(80);
    else
        amask = sp_amask & AM_MASK;

    return amask;
}

static void
create_monster(monster *m, struct mkroom *croom)
{
    struct monst *mtmp;
    coordxy x, y;
    char class;
    unsigned int amask;
    coord cc;
    struct permonst *pm;
    unsigned g_mvflags;

    if (m->class >= 0)
        class = (char) def_char_to_monclass((char) m->class);
    else
        class = 0;

    if (class == MAXMCLASSES)
        panic("create_monster: unknown monster class '%c'", m->class);

    amask = sp_amask_to_amask(m->sp_amask);

    if (!class) {
        pm = (struct permonst *) 0;
    } else if (m->id != NON_PM) {
        pm = &mons[m->id];
        g_mvflags = (unsigned) gm.mvitals[monsndx(pm)].mvflags;
        if ((pm->geno & G_UNIQ) && (g_mvflags & G_EXTINCT))
            return;
        if (g_mvflags & G_GONE) /* genocided or extinct */
            pm = (struct permonst *) 0; /* make random monster */
    } else {
        pm = mkclass(class, G_NOGEN);
        /* if we can't get a specific monster type (pm == 0) then the
           class has been genocided, so settle for a random monster */
    }
    if (In_mines(&u.uz) && pm && your_race(pm)
        && (Race_if(PM_DWARF) || Race_if(PM_GNOME)) && rn2(3))
        pm = (struct permonst *) 0;

    if (pm) {
        int loc = pm_to_humidity(pm);

        /* If water-liking monster, first try is without DRY */
        get_location_coord(&x, &y, loc | NO_LOC_WARN, croom, m->coord);
        if (x == -1 && y == -1) {
            loc |= DRY;
            get_location_coord(&x, &y, loc, croom, m->coord);
        }
    } else {
        get_location_coord(&x, &y, DRY, croom, m->coord);
    }

    /* try to find a close place if someone else is already there */
    if (MON_AT(x, y) && enexto(&cc, x, y, pm))
        x = cc.x, y = cc.y;

    if (croom && !inside_room(croom, x, y))
        return;

    if (m->sp_amask != AM_SPLEV_RANDOM)
        mtmp = mk_roamer(pm, Amask2align(amask), x, y, m->peaceful);
    else if (PM_ARCHEOLOGIST <= m->id && m->id <= PM_WIZARD)
        mtmp = mk_mplayer(pm, x, y, FALSE);
    else
        mtmp = makemon(pm, x, y, m->mm_flags);

    if (mtmp) {
        x = mtmp->mx, y = mtmp->my; /* sanity precaution */
        m->x = x, m->y = y;
        /* handle specific attributes for some special monsters */
        if (m->name.str)
            mtmp = christen_monst(mtmp, m->name.str);

        /*
         * This doesn't complain if an attempt is made to give a
         * non-mimic/non-shapechanger an appearance or to give a
         * shapechanger a non-monster shape, it just refuses to comply.
         */
        if (m->appear_as.str
            && ((mtmp->data->mlet == S_MIMIC)
                /* shapechanger (chameleons, et al, and vampires) */
                || (mtmp->cham >= LOW_PM && m->appear == M_AP_MONSTER))
            && !Protection_from_shape_changers) {
            int i;

            switch (m->appear) {
            case M_AP_NOTHING:
                impossible(
                 "create_monster: mon has an appearance, \"%s\", but no type",
                           m->appear_as.str);
                break;

            case M_AP_FURNITURE:
                for (i = 0; i < MAXPCHARS; i++)
                    if (!strcmp(defsyms[i].explanation, m->appear_as.str))
                        break;
                if (i == MAXPCHARS) {
                    impossible("create_monster: can't find feature \"%s\"",
                               m->appear_as.str);
                } else {
                    mtmp->m_ap_type = M_AP_FURNITURE;
                    mtmp->mappearance = i;
                }
                break;

            case M_AP_OBJECT:
                for (i = 0; i < NUM_OBJECTS; i++)
                    if (OBJ_NAME(objects[i])
                        && !strcmp(OBJ_NAME(objects[i]), m->appear_as.str))
                        break;
                if (i == NUM_OBJECTS) {
                    impossible("create_monster: can't find object \"%s\"",
                               m->appear_as.str);
                } else {
                    mtmp->m_ap_type = M_AP_OBJECT;
                    mtmp->mappearance = i;
                    /* try to avoid placing mimic boulder on a trap */
                    if (i == BOULDER && m->x < 0
                        && m_bad_boulder_spot(x, y)) {
                        int retrylimit = 10;

                        remove_monster(x, y);
                        do {
                            x = m->x;
                            y = m->y;
                            get_location(&x, &y, DRY, croom);
                            if (MON_AT(x, y) && enexto(&cc, x, y, pm))
                                x = cc.x, y = cc.y;
                        } while (m_bad_boulder_spot(x, y)
                                 && --retrylimit > 0);
                        place_monster(mtmp, x, y);
                        /* if we didn't find a good spot
                           then mimic something else */
                        if (!retrylimit)
                            set_mimic_sym(mtmp);
                    }
                }
                break;

            case M_AP_MONSTER: {
                int mndx, gender_name_var = NEUTRAL;

                if (!strcmpi(m->appear_as.str, "random"))
                    mndx = select_newcham_form(mtmp);
                else
                    mndx = name_to_mon(m->appear_as.str, &gender_name_var);

                if (mndx == NON_PM || (is_vampshifter(mtmp)
                                       && !validvamp(mtmp, &mndx, S_HUMAN))) {
                    impossible("create_monster: invalid %s (\"%s\")",
                               (mtmp->data->mlet == S_MIMIC)
                                 ? "mimic appearance"
                                 : (mtmp->data == &mons[PM_WIZARD_OF_YENDOR])
                                     ? "Wizard appearance"
                                     : is_vampshifter(mtmp)
                                         ? "vampire shape"
                                         : "chameleon shape",
                               m->appear_as.str);
                } else if (&mons[mndx] == mtmp->data) {
                    /* explicitly forcing a mimic to appear as itself */
                    mtmp->m_ap_type = M_AP_NOTHING;
                    mtmp->mappearance = 0;
                } else if (mtmp->data->mlet == S_MIMIC
                           || mtmp->data == &mons[PM_WIZARD_OF_YENDOR]) {
                    /* this is ordinarily only used for Wizard clones
                       and hasn't been exhaustively tested for mimics */
                    mtmp->m_ap_type = M_AP_MONSTER;
                    mtmp->mappearance = mndx;
                } else { /* chameleon or vampire */
                    struct permonst *mdat = &mons[mndx];
                    struct permonst *olddata = mtmp->data;

                    mgender_from_permonst(mtmp, mdat);
                    if (gender_name_var != NEUTRAL)
                        mtmp->female = gender_name_var;
                    set_mon_data(mtmp, mdat);
                    if (emits_light(olddata) != emits_light(mtmp->data)) {
                        /* used to give light, now doesn't, or vice versa,
                           or light's range has changed */
                        if (emits_light(olddata))
                            del_light_source(LS_MONSTER, monst_to_any(mtmp));
                        if (emits_light(mtmp->data))
                            new_light_source(mtmp->mx, mtmp->my,
                                             emits_light(mtmp->data),
                                             LS_MONSTER, monst_to_any(mtmp));
                    }
                    if (!mtmp->perminvis || pm_invisible(olddata))
                        mtmp->perminvis = pm_invisible(mdat);
                }
                break;
            }
            default:
                impossible(
                  "create_monster: unimplemented mon appear type [%d,\"%s\"]",
                           m->appear, m->appear_as.str);
                break;
            }
            if (does_block(x, y, &levl[x][y]))
                block_point(x, y);
        }

        mtmp->female = m->female;
        if (m->peaceful > BOOL_RANDOM) {
            mtmp->mpeaceful = m->peaceful;
            /* changed mpeaceful again; have to reset malign */
            set_malign(mtmp);
        }
        if (m->asleep > BOOL_RANDOM)
            mtmp->msleeping = m->asleep;
        if (m->seentraps)
            mtmp->mtrapseen = m->seentraps;
        if (m->cancelled)
            mtmp->mcan = 1;
        if (m->revived)
            mtmp->mrevived = 1;
        if (m->avenge)
            mtmp->mavenge = 1;
        if (m->stunned)
            mtmp->mstun = 1;
        if (m->confused)
            mtmp->mconf = 1;
        if (m->invis) {
            mtmp->minvis = mtmp->perminvis = 1;
        }
        if (m->blinded) {
            mtmp->mcansee = 0;
            mtmp->mblinded = (m->blinded % 127);
        }
        if (m->paralyzed) {
            mtmp->mcanmove = 0;
            mtmp->mfrozen = (m->paralyzed % 127);
        }
        if (m->fleeing) {
            mtmp->mflee = 1;
            mtmp->mfleetim = (m->fleeing % 127);
        }
        if (m->waiting) {
            mtmp->mstrategy |= STRAT_WAITFORU;
        }
        if (m->has_invent) {
            discard_minvent(mtmp, TRUE);
            invent_carrying_monster = mtmp;
        }
    }
}

/*
 * Create an object in a room.
 */
static struct obj *
create_object(object *o, struct mkroom *croom)
{
    struct obj *otmp;
    coordxy x, y;
    char c;
    boolean named; /* has a name been supplied in level description? */

    named = o->name.str ? TRUE : FALSE;

    get_location_coord(&x, &y, DRY, croom, o->coord);

    if (o->class >= 0)
        c = o->class;
    else
        c = 0;

    if (!c) {
        otmp = mkobj_at(RANDOM_CLASS, x, y, !named);
    } else if (o->id != -1) {
        otmp = mksobj_at(o->id, x, y, TRUE, !named);
    } else {
        /*
         * The special levels are compiled with the default "text" object
         * class characters.  We must convert them to the internal format.
         */
        char oclass = (char) def_char_to_objclass(c);

        if (oclass == MAXOCLASSES)
            panic("create_object:  unexpected object class '%c'", c);

        /* KMH -- Create piles of gold properly */
        if (oclass == COIN_CLASS)
            otmp = mkgold(0L, x, y);
        else
            otmp = mkobj_at(oclass, x, y, !named);
    }

    if (o->spe != -127) /* That means NOT RANDOM! */
        otmp->spe = (schar) o->spe;

    switch (o->curse_state) {
    case 1: /* blessed */
        bless(otmp);
        break;
    case 2: /* uncursed */
        unbless(otmp);
        uncurse(otmp);
        break;
    case 3: /* cursed */
        curse(otmp);
        break;
    case 4: /* not cursed */
        uncurse(otmp);
        break;
    case 5: /* not uncursed */
        blessorcurse(otmp, 1);
        break;
    case 6: /* not blessed */
        unbless(otmp);
        break;
    default: /* random */
        break; /* keep what mkobj gave us */
    }

    /* corpsenm is "empty" if -1, random if -2, otherwise specific */
    if (o->corpsenm != NON_PM) {
        if (o->corpsenm == NON_PM - 1)
            set_corpsenm(otmp, rndmonnum());
        else
            set_corpsenm(otmp, o->corpsenm);
    }
    /* set_corpsenm() took care of egg hatch and corpse timers */

    if (named) {
        otmp = oname(otmp, o->name.str, ONAME_LEVEL_DEF);
        if (otmp->otyp == SPE_NOVEL) {
            /* needs to be an existing title */
            (void) lookup_novel(o->name.str, &otmp->novelidx);
        }
    }
    if (o->eroded) {
        if (o->eroded < 0) {
            otmp->oerodeproof = 1;
        } else {
            otmp->oeroded = (o->eroded % 4);
            otmp->oeroded2 = ((o->eroded >> 2) % 4);
        }
    } else {
        otmp->oeroded = otmp->oeroded2 = 0;
        otmp->oerodeproof = 0;
    }
    if (o->recharged)
        otmp->recharged = (o->recharged % 8);
    if (o->locked == 0 || o->locked == 1) {
        otmp->olocked = o->locked;
    } else if (o->broken) {
        otmp->obroken = 1;
        otmp->olocked = 0; /* obj generation may set */
    }
    if (o->trapped == 0 || o->trapped == 1)
        otmp->otrapped = o->trapped;
    otmp->greased = o->greased ? 1 : 0;

    if (o->quan > 0 && objects[otmp->otyp].oc_merge) {
        otmp->quan = o->quan;
        otmp->owt = weight(otmp);
    }

    /* contents (of a container or monster's inventory) */
    if (o->containment & SP_OBJ_CONTENT || invent_carrying_monster) {
        if (!container_idx) {
            if (!invent_carrying_monster) {
                /*impossible("create_object: no container");*/
                /* don't complain, the monster may be gone legally
                   (eg. unique demon already generated)
                   TODO: In the case of unique demon lords, they should
                   get their inventories even when they get generated
                   outside the des-file.  Maybe another data file that
                   determines what inventories monsters get by default?
                 */
                ; /* ['otmp' remains on floor] */
            } else {
                remove_object(otmp);
                if (otmp->otyp == SADDLE)
                    put_saddle_on_mon(otmp, invent_carrying_monster);
                else
                    (void) mpickobj(invent_carrying_monster, otmp);
            }
        } else {
            struct obj *cobj = container_obj[container_idx - 1];

            remove_object(otmp);
            if (cobj) {
                otmp = add_to_container(cobj, otmp);
                cobj->owt = weight(cobj);
            } else {
                obj_extract_self(otmp);
                /* uncreate a random artifact created in a container */
                /* FIXME: it could be intentional rather than random */
                if (otmp->oartifact)
                    artifact_exists(otmp, safe_oname(otmp), FALSE,
                                    ONAME_NO_FLAGS); /* flags don't matter */
                obfree(otmp, NULL);
                return NULL;
            }
        }
    }
    /* container */
    if (o->containment & SP_OBJ_CONTAINER) {
        delete_contents(otmp);
        if (container_idx < MAX_CONTAINMENT) {
            container_obj[container_idx] = otmp;
            container_idx++;
        } else
            impossible("create_object: too deeply nested containers.");
    }

    /* Medusa level special case: statues are petrified monsters, so they
     * are not stone-resistant and have monster inventory.  They also lack
     * other contents, but that can be specified as an empty container.
     */
    if (o->id == STATUE && Is_medusa_level(&u.uz) && o->corpsenm == NON_PM) {
        struct monst *was = NULL;
        struct obj *obj;
        int wastyp;
        int i = 0; /* prevent endless loop in case makemon always fails */

        /* Named random statues are of player types, and aren't stone-
         * resistant (if they were, we'd have to reset the name as well as
         * setting corpsenm).
         */
        for (wastyp = otmp->corpsenm; i < 1000; i++, wastyp = rndmonnum()) {
            /* makemon without rndmonst() might create a group */
            was = makemon(&mons[wastyp], 0, 0, MM_NOCOUNTBIRTH|MM_NOMSG);
            if (was) {
                if (!resists_ston(was) && !poly_when_stoned(&mons[wastyp])) {
                    (void) propagate(wastyp, TRUE, FALSE);
                    break;
                }
                mongone(was);
                was = NULL;
            }
        }
        if (was) {
            set_corpsenm(otmp, wastyp);
            while (was->minvent) {
                obj = was->minvent;
                obj->owornmask = 0;
                obj_extract_self(obj);
                (void) add_to_container(otmp, obj);
            }
            otmp->owt = weight(otmp);
            mongone(was);
        }
    }

    if (o->achievement) {
        static const char prize_warning[] = "multiple prizes on %s level";

        if (Is_mineend_level(&u.uz)) {
            if (!gc.context.achieveo.mines_prize_oid) {
                gc.context.achieveo.mines_prize_oid = otmp->o_id;
                gc.context.achieveo.mines_prize_otyp = otmp->otyp;
                /* prevent stacking; cleared when achievement is recorded;
                   will be reset in addinv_core1() */
                otmp->nomerge = 1;
            } else {
                impossible(prize_warning, "mines end");
            }
        } else if (Is_sokoend_level(&u.uz)) {
            if (!gc.context.achieveo.soko_prize_oid) {
                gc.context.achieveo.soko_prize_oid = otmp->o_id;
                gc.context.achieveo.soko_prize_otyp = otmp->otyp;
                otmp->nomerge = 1; /* redundant; Sokoban prizes don't stack;
                                    * will be reset in addinv_core1() */
            } else {
                impossible(prize_warning, "sokoban end");
            }
        } else if (!iflags.lua_testing) {
            char lbuf[QBUFSZ];

            (void) describe_level(lbuf, 1 | 2);
            impossible("create_object: unknown achievement (%s\"%s\")",
                       lbuf, simpleonames(otmp));
        }
    }

    if (!(o->containment & SP_OBJ_CONTENT)) {
        stackobj(otmp);

        if (o->lit)
            begin_burn(otmp, FALSE);

        if (o->buried) {
            boolean dealloced;

            (void) bury_an_obj(otmp, &dealloced);
            if (dealloced) {
                if (container_idx)
                    container_obj[container_idx - 1] = NULL;
                otmp = NULL;
            }
        }
    }
    return otmp;
}

/*
 * Create an altar in a room.
 */
static void
create_altar(altar *a, struct mkroom *croom)
{
    schar sproom;
    coordxy x = -1, y = -1;
    unsigned int amask;
    boolean croom_is_temple = TRUE;

    if (croom) {
        get_free_room_loc(&x, &y, croom, a->coord);
        if (croom->rtype != TEMPLE)
            croom_is_temple = FALSE;
    } else {
        get_location_coord(&x, &y, DRY, croom, a->coord);
        if ((sproom = (schar) *in_rooms(x, y, TEMPLE)) != 0)
            croom = &gr.rooms[sproom - ROOMOFFSET];
        else
            croom_is_temple = FALSE;
    }

    /* check for existing features */
    if (!set_levltyp(x, y, ALTAR))
        return;

    amask = sp_amask_to_amask(a->sp_amask);

    levl[x][y].altarmask = amask;

    if (a->shrine < 0)
        a->shrine = rn2(2); /* handle random case */

    if (!croom_is_temple || !a->shrine)
        return;

    if (a->shrine) { /* Is it a shrine  or sanctum? */
        priestini(&u.uz, croom, x, y, (a->shrine > 1));
        levl[x][y].altarmask |= AM_SHRINE;
        if (a->shrine == 2) /* high altar or sanctum */
            levl[x][y].altarmask |= AM_SANCTUM;
        gl.level.flags.has_temple = TRUE;
    }
}

/*
 * Search for a door in a room on a specified wall.
 */
static boolean
search_door(
    struct mkroom *croom,
    coordxy *x, coordxy *y,
    xint16 wall, int cnt)
{
    int dx, dy;
    int xx, yy;

    switch (wall) {
    case W_SOUTH:
        dy = 0;
        dx = 1;
        xx = croom->lx;
        yy = croom->hy + 1;
        break;
    case W_NORTH:
        dy = 0;
        dx = 1;
        xx = croom->lx;
        yy = croom->ly - 1;
        break;
    case W_EAST:
        dy = 1;
        dx = 0;
        xx = croom->hx + 1;
        yy = croom->ly;
        break;
    case W_WEST:
        dy = 1;
        dx = 0;
        xx = croom->lx - 1;
        yy = croom->ly;
        break;
    default:
        dx = dy = xx = yy = 0;
        panic("search_door: Bad wall!");
        break;
    }
    while (xx <= croom->hx + 1 && yy <= croom->hy + 1) {
        if (IS_DOOR(levl[xx][yy].typ) || levl[xx][yy].typ == SDOOR) {
            *x = xx;
            *y = yy;
            if (cnt-- <= 0)
                return TRUE;
        }
        xx += dx;
        yy += dy;
    }
    return FALSE;
}

/*
 * Dig a corridor between two points.
 */
boolean
dig_corridor(
    coord *org, coord *dest,
    boolean nxcor,
    schar ftyp, schar btyp)
{
    int dx = 0, dy = 0, dix, diy, cct;
    struct rm *crm;
    int tx, ty, xx, yy;

    xx = org->x;
    yy = org->y;
    tx = dest->x;
    ty = dest->y;
    if (xx <= 0 || yy <= 0 || tx <= 0 || ty <= 0 || xx > COLNO - 1
        || tx > COLNO - 1 || yy > ROWNO - 1 || ty > ROWNO - 1) {
        debugpline4("dig_corridor: bad coords <%d,%d> <%d,%d>.",
                    xx, yy, tx, ty);
        return FALSE;
    }
    if (tx > xx)
        dx = 1;
    else if (ty > yy)
        dy = 1;
    else if (tx < xx)
        dx = -1;
    else
        dy = -1;

    xx -= dx;
    yy -= dy;
    cct = 0;
    while (xx != tx || yy != ty) {
        /* loop: dig corridor at [xx,yy] and find new [xx,yy] */
        if (cct++ > 500 || (nxcor && !rn2(35)))
            return FALSE;

        xx += dx;
        yy += dy;

        if (xx >= COLNO - 1 || xx <= 0 || yy <= 0 || yy >= ROWNO - 1)
            return FALSE; /* impossible */

        crm = &levl[xx][yy];
        if (crm->typ == btyp) {
            if (ftyp != CORR || rn2(100)) {
                crm->typ = ftyp;
                if (nxcor && !rn2(50))
                    (void) mksobj_at(BOULDER, xx, yy, TRUE, FALSE);
            } else {
                crm->typ = SCORR;
            }
        } else if (crm->typ != ftyp && crm->typ != SCORR) {
            /* strange ... */
            return FALSE;
        }

        /* find next corridor position */
        dix = abs(xx - tx);
        diy = abs(yy - ty);

        if ((dix > diy) && diy && !rn2(dix - diy + 1)) {
            dix = 0;
        } else if ((diy > dix) && dix && !rn2(diy - dix + 1)) {
            diy = 0;
        }

        /* do we have to change direction ? */
        if (dy && dix > diy) {
            register int ddx = (xx > tx) ? -1 : 1;

            crm = &levl[xx + ddx][yy];
            if (crm->typ == btyp || crm->typ == ftyp || crm->typ == SCORR) {
                dx = ddx;
                dy = 0;
                continue;
            }
        } else if (dx && diy > dix) {
            register int ddy = (yy > ty) ? -1 : 1;

            crm = &levl[xx][yy + ddy];
            if (crm->typ == btyp || crm->typ == ftyp || crm->typ == SCORR) {
                dy = ddy;
                dx = 0;
                continue;
            }
        }

        /* continue straight on? */
        crm = &levl[xx + dx][yy + dy];
        if (crm->typ == btyp || crm->typ == ftyp || crm->typ == SCORR)
            continue;

        /* no, what must we do now?? */
        if (dx) {
            dx = 0;
            dy = (ty < yy) ? -1 : 1;
        } else {
            dy = 0;
            dx = (tx < xx) ? -1 : 1;
        }
        crm = &levl[xx + dx][yy + dy];
        if (crm->typ == btyp || crm->typ == ftyp || crm->typ == SCORR)
            continue;
        dy = -dy;
        dx = -dx;
    }
    return TRUE;
}

/*
 * Corridors always start from a door. But it can end anywhere...
 * Basically we search for door coordinates or for endpoints coordinates
 * (from a distance).
 */
static void
create_corridor(corridor *c)
{
    coord org, dest;

    if (c->src.room == -1) {
        makecorridors(); /*makecorridors(c->src.door);*/
        return;
    }

    /* Safety railings - if there's ever a case where des.corridor() needs
     * to be called with src/destwall="random", that logic first needs to be
     * implemented in search_door. */
    if (c->src.wall == W_ANY || c->src.wall == W_RANDOM
        || c->dest.wall == W_ANY || c->dest.wall == W_RANDOM) {
        impossible("create_corridor to/from a random wall");
        return;
    }
    if (!search_door(&gr.rooms[c->src.room], &org.x, &org.y, c->src.wall,
                     c->src.door))
        return;
    if (c->dest.room != -1) {
        if (!search_door(&gr.rooms[c->dest.room],
                         &dest.x, &dest.y, c->dest.wall, c->dest.door))
            return;
        switch (c->src.wall) {
        case W_NORTH:
            org.y--;
            break;
        case W_SOUTH:
            org.y++;
            break;
        case W_WEST:
            org.x--;
            break;
        case W_EAST:
            org.x++;
            break;
        }
        switch (c->dest.wall) {
        case W_NORTH:
            dest.y--;
            break;
        case W_SOUTH:
            dest.y++;
            break;
        case W_WEST:
            dest.x--;
            break;
        case W_EAST:
            dest.x++;
            break;
        }
        (void) dig_corridor(&org, &dest, FALSE, CORR, STONE);
    }
}

/*
 * Fill a room (shop, zoo, etc...) with appropriate stuff.
 */
void
fill_special_room(struct mkroom *croom)
{
    int i;

    if (!croom)
        return;

    /* First recurse into subrooms. We don't want to block an ordinary room
     * with a special subroom from having the subroom filled, or an unfilled
     * outer room preventing a special subroom from being filled. */
    for (i = 0; i < croom->nsubrooms; ++i) {
        fill_special_room(croom->sbrooms[i]);
    }

    if (croom->rtype == OROOM || croom->rtype == THEMEROOM
        || croom->needfill == FILL_NONE)
        return;

    if (croom->needfill == FILL_NORMAL) {
        int x, y;

        /* Shop ? */
        if (croom->rtype >= SHOPBASE) {
            stock_room(croom->rtype - SHOPBASE, croom);
            gl.level.flags.has_shop = TRUE;
            return;
        }

        switch (croom->rtype) {
        case VAULT:
            for (x = croom->lx; x <= croom->hx; x++)
                for (y = croom->ly; y <= croom->hy; y++)
                    (void) mkgold((long) rn1(abs(depth(&u.uz)) * 100, 51),
                                  x, y);
            break;
        case COURT:
        case ZOO:
        case BEEHIVE:
        case ANTHOLE:
        case COCKNEST:
        case LEPREHALL:
        case MORGUE:
        case BARRACKS:
            fill_zoo(croom);
            break;
        }
    }
    switch (croom->rtype) {
    case VAULT:
        gl.level.flags.has_vault = TRUE;
        break;
    case ZOO:
        gl.level.flags.has_zoo = TRUE;
        break;
    case COURT:
        gl.level.flags.has_court = TRUE;
        break;
    case MORGUE:
        gl.level.flags.has_morgue = TRUE;
        break;
    case BEEHIVE:
        gl.level.flags.has_beehive = TRUE;
        break;
    case BARRACKS:
        gl.level.flags.has_barracks = TRUE;
        break;
    case TEMPLE:
        gl.level.flags.has_temple = TRUE;
        break;
    case SWAMP:
        gl.level.flags.has_swamp = TRUE;
        break;
    }
}

static struct mkroom *
build_room(room *r, struct mkroom *mkr)
{
    boolean okroom;
    struct mkroom *aroom;
    xint16 rtype = (!r->chance || rn2(100) < r->chance) ? r->rtype : OROOM;

    if (mkr) {
        aroom = &gs.subrooms[gn.nsubroom];
        okroom = create_subroom(mkr, r->x, r->y, r->w, r->h, rtype, r->rlit);
    } else {
        aroom = &gr.rooms[gn.nroom];
        okroom = create_room(r->x, r->y, r->w, r->h, r->xalign, r->yalign,
                             rtype, r->rlit);
    }

    if (okroom) {
#ifdef SPECIALIZATION
        topologize(aroom, FALSE); /* set roomno */
#else
        topologize(aroom); /* set roomno */
#endif
        aroom->needfill = r->needfill;
        aroom->needjoining = r->joined;
        return aroom;
    }
    return (struct mkroom *) 0;
}

/*
 * set lighting in a region that will not become a room.
 */
static void
light_region(region *tmpregion)
{
    register boolean litstate = tmpregion->rlit ? 1 : 0;
    register int hiy = tmpregion->y2;
    register int x, y;
    register struct rm *lev;
    int lowy = tmpregion->y1;
    int lowx = tmpregion->x1, hix = tmpregion->x2;

    if (litstate) {
        /* adjust region size for walls, but only if lighted */
        lowx = max(lowx - 1, 1);
        hix = min(hix + 1, COLNO - 1);
        lowy = max(lowy - 1, 0);
        hiy = min(hiy + 1, ROWNO - 1);
    }
    for (x = lowx; x <= hix; x++) {
        lev = &levl[x][lowy];
        for (y = lowy; y <= hiy; y++) {
            if (lev->typ != LAVAPOOL) /* this overrides normal lighting */
                lev->lit = litstate;
            lev++;
        }
    }
}

void
wallify_map(coordxy x1, coordxy y1, coordxy x2, coordxy y2)
{
    coordxy x, y, xx, yy, lo_xx, lo_yy, hi_xx, hi_yy;

    y1 = max(y1, 0);
    x1 = max(x1, 1);
    y2 = min(y2, ROWNO - 1);
    x2 = min(x2, COLNO - 1);
    for (y = y1; y <= y2; y++) {
        lo_yy = (y > 0) ? y - 1 : 0;
        hi_yy = (y < y2) ? y + 1 : y2;
        for (x = x1; x <= x2; x++) {
            if (levl[x][y].typ != STONE)
                continue;
            lo_xx = (x > 1) ? x - 1 : 1;
            hi_xx = (x < x2) ? x + 1 : x2;
            for (yy = lo_yy; yy <= hi_yy; yy++)
                for (xx = lo_xx; xx <= hi_xx; xx++)
                    if (IS_ROOM(levl[xx][yy].typ)
                        || levl[xx][yy].typ == CROSSWALL) {
                        levl[x][y].typ = (yy != y) ? HWALL : VWALL;
                        yy = hi_yy; /* end `yy' loop */
                        break;      /* end `xx' loop */
                    }
        }
    }
}

/*
 * Select a random coordinate in the maze.
 *
 * We want a place not 'touched' by the loader.  That is, a place in
 * the maze outside every part of the special level.
 */
static void
maze1xy(coord *m, int humidity)
{
    register int x, y, tryct = 2000;
    /* tryct:  normally it won't take more than ten or so tries due
       to the circumstances under which we'll be called, but the
       `humidity' screening might drastically change the chances */

    do {
        x = rn1(gx.x_maze_max - 3, 3);
        y = rn1(gy.y_maze_max - 3, 3);
        if (--tryct < 0)
            break; /* give up */
    } while (!(x % 2) || !(y % 2) || SpLev_Map[x][y]
             || !is_ok_location((coordxy) x, (coordxy) y, humidity));

    m->x = (coordxy) x, m->y = (coordxy) y;
}

/*
 * If there's a significant portion of maze unused by the special level,
 * we don't want it empty.
 *
 * Makes the number of traps, monsters, etc. proportional
 * to the size of the maze.
 */
static void
fill_empty_maze(void)
{
    int mapcountmax, mapcount, mapfact;
    coordxy x, y;
    coord mm;

    mapcountmax = mapcount = (gx.x_maze_max - 2) * (gy.y_maze_max - 2);
    mapcountmax = mapcountmax / 2;

    for (x = 2; x < gx.x_maze_max; x++)
        for (y = 0; y < gy.y_maze_max; y++)
            if (SpLev_Map[x][y])
                mapcount--;

    if ((mapcount > (int) (mapcountmax / 10))) {
        mapfact = (int) ((mapcount * 100L) / mapcountmax);
        for (x = rnd((int) (20 * mapfact) / 100); x; x--) {
            maze1xy(&mm, DRY);
            (void) mkobj_at(rn2(2) ? GEM_CLASS : RANDOM_CLASS, mm.x, mm.y,
                            TRUE);
        }
        for (x = rnd((int) (12 * mapfact) / 100); x; x--) {
            maze1xy(&mm, DRY);
            (void) mksobj_at(BOULDER, mm.x, mm.y, TRUE, FALSE);
        }
        for (x = rn2(2); x; x--) {
            maze1xy(&mm, DRY);
            (void) makemon(&mons[PM_MINOTAUR], mm.x, mm.y, NO_MM_FLAGS);
        }
        for (x = rnd((int) (12 * mapfact) / 100); x; x--) {
            maze1xy(&mm, DRY);
            (void) makemon((struct permonst *) 0, mm.x, mm.y, NO_MM_FLAGS);
        }
        for (x = rn2((int) (15 * mapfact) / 100); x; x--) {
            maze1xy(&mm, DRY);
            (void) mkgold(0L, mm.x, mm.y);
        }
        for (x = rn2((int) (15 * mapfact) / 100); x; x--) {
            int trytrap;

            maze1xy(&mm, DRY);
            trytrap = rndtrap();
            if (sobj_at(BOULDER, mm.x, mm.y))
                while (is_pit(trytrap) || is_hole(trytrap))
                    trytrap = rndtrap();
            (void) maketrap(mm.x, mm.y, trytrap);
        }
    }
}

static void
splev_initlev(lev_init *linit)
{
    switch (linit->init_style) {
    default:
        impossible("Unrecognized level init style.");
        break;
    case LVLINIT_NONE:
        break;
    case LVLINIT_SOLIDFILL:
        if (linit->lit == BOOL_RANDOM)
            linit->lit = rn2(2);
        lvlfill_solid(linit->filling, linit->lit);
        break;
    case LVLINIT_MAZEGRID:
        lvlfill_maze_grid(2, 0, gx.x_maze_max, gy.y_maze_max, linit->bg);
        break;
    case LVLINIT_MAZE:
        create_maze(linit->corrwid, linit->wallthick, linit->rm_deadends);
        break;
    case LVLINIT_ROGUE:
        makeroguerooms();
        break;
    case LVLINIT_MINES:
        if (linit->lit == BOOL_RANDOM)
            linit->lit = rn2(2);
        if (linit->filling > -1)
            lvlfill_solid(linit->filling, 0);
        linit->icedpools = icedpools;
        mkmap(linit);
        break;
    case LVLINIT_SWAMP:
        if (linit->lit == BOOL_RANDOM)
            linit->lit = rn2(2);
        lvlfill_swamp(linit->fg, linit->bg, linit->lit);
        break;
    }
}

#if 0
static long
sp_code_jmpaddr(long curpos, long jmpaddr)
{
    return (curpos + jmpaddr);
}
#endif


/*ARGUSED*/
static void
spo_end_moninvent(void)
{
    if (invent_carrying_monster)
        m_dowear(invent_carrying_monster, TRUE);
    invent_carrying_monster = NULL;
}

/*ARGUSED*/
static void
spo_pop_container(void)
{
    if (container_idx > 0) {
        container_idx--;
        container_obj[container_idx] = NULL;
    }
}

/* push a table on lua stack: {width=wid, height=hei} */
static void
l_push_wid_hei_table(lua_State *L, int wid, int hei)
{
    lua_newtable(L);
    nhl_add_table_entry_int(L, "width", wid);
    nhl_add_table_entry_int(L, "height", hei);
}

/* push a table on lua stack containing room data */
static void
l_push_mkroom_table(lua_State *L, struct mkroom *tmpr)
{
    lua_newtable(L);
    nhl_add_table_entry_int(L, "width", 1 + (tmpr->hx - tmpr->lx));
    nhl_add_table_entry_int(L, "height", 1 + (tmpr->hy - tmpr->ly));
    nhl_add_table_entry_region(L, "region", tmpr->lx, tmpr->ly,
                               tmpr->hx, tmpr->hy);
    nhl_add_table_entry_bool(L, "lit", (boolean) tmpr->rlit);
    nhl_add_table_entry_bool(L, "irregular", tmpr->irregular);
    nhl_add_table_entry_bool(L, "needjoining", tmpr->needjoining);
    nhl_add_table_entry_str(L, "type", get_mkroom_name(tmpr->rtype));
}

DISABLE_WARNING_UNREACHABLE_CODE

/* message("What a strange feeling!"); */
int
lspo_message(lua_State *L)
{
    char *levmsg;
    int old_n, n;
    const char *msg;

    int argc = lua_gettop(L);

    if (argc < 1) {
        nhl_error(L, "Wrong parameters");
        /*NOTREACHED*/
        return 0;
    }

    create_des_coder();

    msg = luaL_checkstring(L, 1);

    old_n = gl.lev_message ? (Strlen(gl.lev_message) + 1) : 0;
    n = Strlen(msg);

    levmsg = (char *) alloc(old_n + n + 1);
    if (old_n)
        levmsg[old_n - 1] = '\n';
    if (gl.lev_message)
        (void) memcpy((genericptr_t) levmsg, (genericptr_t) gl.lev_message,
                      old_n - 1);
    (void) memcpy((genericptr_t) &levmsg[old_n], msg, n);
    levmsg[old_n + n] = '\0';
    Free(gl.lev_message);
    gl.lev_message = levmsg;

    return 0;  /* number of results */
}

RESTORE_WARNING_UNREACHABLE_CODE

static int
get_table_align(lua_State *L)
{
    static const char *const gtaligns[] = {
        "noalign", "law", "neutral", "chaos",
        "coaligned", "noncoaligned", "random", NULL
    };
    static const int aligns2i[] = {
        AM_NONE, AM_LAWFUL, AM_NEUTRAL, AM_CHAOTIC,
        AM_SPLEV_CO, AM_SPLEV_NONCO, AM_SPLEV_RANDOM, 0
    };

    int a = aligns2i[get_table_option(L, "align", "random", gtaligns)];

    return a;
}

static int
get_table_monclass(lua_State *L)
{
    char *s = get_table_str_opt(L, "class", NULL);
    int ret = -1;

    if (s && strlen(s) == 1)
        ret = (int) *s;
    Free(s);
    return ret;
}

static int
find_montype(
    lua_State *L UNUSED,
    const char *s,
    int *mgender)
{
    int i, mgend = NEUTRAL;

    i = name_to_monplus(s, (const char **) 0, &mgend);
    if (i >= LOW_PM && i < NUMMONS) {
        if (is_male(&mons[i]) || is_female(&mons[i]))
            mgend = is_female(&mons[i]) ? FEMALE : MALE;
        else
            mgend = (mgend == FEMALE) ? FEMALE
                        : (mgend == MALE) ? MALE : rn2(2);
        if (mgender)
            *mgender = mgend;
        return i;
    }
    if (mgender)
        *mgender = NEUTRAL;
    return NON_PM;
}

static int
get_table_montype(lua_State *L, int *mgender)
{
    char *s = get_table_str_opt(L, "id", NULL);
    int ret = NON_PM;

    if (s) {
        ret = find_montype(L, s, mgender);
        Free(s);
        if (ret == NON_PM)
            nhl_error(L, "Unknown monster id");
    }
    return ret;
}

/* Get x and y values from a table (which the caller has already checked for
 * the existence of), handling both a table with x= and y= specified and a
 * table with coord= specified.
 * Returns absolute rather than map-relative coordinates; the caller of this
 * function must decide if it wants to interpret the coordinates as
 * map-relative and adjust accordingly. */
static void
get_table_xy_or_coord(
    lua_State *L,
    lua_Integer *x,
    lua_Integer *y)
{
    lua_Integer mx = get_table_int_opt(L, "x", -1);
    lua_Integer my = get_table_int_opt(L, "y", -1);

    if (mx == -1 && my == -1) {
        lua_getfield(L, 1, "coord");
        (void) get_coord(L, -1, &mx, &my);
        lua_pop(L, 1);
    }

    *x = mx;
    *y = my;
}

/* monster(); */
/* monster("wood nymph"); */
/* monster("D"); */
/* monster("giant eel",11,06); */
/* monster("hill giant", {08,06}); */
/* monster({ id = "giant mimic", appear_as = "obj:boulder" }); */
/* monster({ class = "H", peaceful = 0 }); */
int
lspo_monster(lua_State *L)
{
    int argc = lua_gettop(L);
    monster tmpmons;
    lua_Integer mx = -1, my = -1;
    int mgend = NEUTRAL;
    char *mappear = NULL;

    create_des_coder();

    tmpmons.peaceful = -1;
    tmpmons.asleep = -1;
    tmpmons.name.str = NULL;
    tmpmons.appear = 0;
    tmpmons.appear_as.str = (char *) 0;
    tmpmons.sp_amask = AM_SPLEV_RANDOM;
    tmpmons.female = 0;
    tmpmons.invis = 0;
    tmpmons.cancelled = 0;
    tmpmons.revived = 0;
    tmpmons.avenge = 0;
    tmpmons.fleeing = 0;
    tmpmons.blinded = 0;
    tmpmons.paralyzed = 0;
    tmpmons.stunned = 0;
    tmpmons.confused = 0;
    tmpmons.seentraps = 0;
    tmpmons.has_invent = 0;
    tmpmons.waiting = 0;
    tmpmons.mm_flags = NO_MM_FLAGS;

    if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) {
        const char *paramstr = luaL_checkstring(L, 1);

        if (strlen(paramstr) == 1) {
            tmpmons.class = *paramstr;
            tmpmons.id = NON_PM;
        } else {
            tmpmons.class = -1;
            tmpmons.id = find_montype(L, paramstr, &mgend);
            tmpmons.female = (mgend == FEMALE) ? FEMALE
                                : (mgend == MALE) ? MALE : rn2(2);
        }
    } else if (argc == 2 && lua_type(L, 1) == LUA_TSTRING
               && lua_type(L, 2) == LUA_TTABLE) {
        const char *paramstr = luaL_checkstring(L, 1);

        (void) get_coord(L, 2, &mx, &my);

        if (strlen(paramstr) == 1) {
            tmpmons.class = *paramstr;
            tmpmons.id = NON_PM;
        } else {
            tmpmons.class = -1;
            tmpmons.id = find_montype(L, paramstr, &mgend);
            tmpmons.female = (mgend == FEMALE) ? FEMALE
                                : (mgend == MALE) ? MALE : rn2(2);
        }

    } else if (argc == 3) {
        const char *paramstr = luaL_checkstring(L, 1);

        mx = luaL_checkinteger(L, 2);
        my = luaL_checkinteger(L, 3);

        if (strlen(paramstr) == 1) {
            tmpmons.class = *paramstr;
            tmpmons.id = NON_PM;
        } else {
            tmpmons.class = -1;
            tmpmons.id = find_montype(L, paramstr, &mgend);
            tmpmons.female = (mgend == FEMALE) ? FEMALE
                                : (mgend == MALE) ? MALE : rn2(2);
        }
    } else {
        lcheck_param_table(L);

        tmpmons.peaceful = get_table_boolean_opt(L, "peaceful", BOOL_RANDOM);
        tmpmons.asleep = get_table_boolean_opt(L, "asleep", BOOL_RANDOM);
        tmpmons.name.str = get_table_str_opt(L, "name", NULL);
        tmpmons.appear = 0;
        tmpmons.appear_as.str = (char *) 0;
        tmpmons.sp_amask = get_table_align(L);
        tmpmons.female = get_table_boolean_opt(L, "female", BOOL_RANDOM);
        tmpmons.invis = get_table_boolean_opt(L, "invisible", FALSE);
        tmpmons.cancelled = get_table_boolean_opt(L, "cancelled", FALSE);
        tmpmons.revived = get_table_boolean_opt(L, "revived", FALSE);
        tmpmons.avenge = get_table_boolean_opt(L, "avenge", FALSE);
        tmpmons.fleeing = get_table_int_opt(L, "fleeing", 0);
        tmpmons.blinded = get_table_int_opt(L, "blinded", 0);
        tmpmons.paralyzed = get_table_int_opt(L, "paralyzed", 0);
        tmpmons.stunned = get_table_boolean_opt(L, "stunned", FALSE);
        tmpmons.confused = get_table_boolean_opt(L, "confused", FALSE);
        tmpmons.waiting = get_table_boolean_opt(L, "waiting", FALSE);
        tmpmons.seentraps = 0; /* TODO: list of trap names to bitfield */
        tmpmons.has_invent = 0;

        if (!get_table_boolean_opt(L, "tail", TRUE))
            tmpmons.mm_flags |= MM_NOTAIL;
        if (!get_table_boolean_opt(L, "group", TRUE))
            tmpmons.mm_flags |= MM_NOGRP;
        if (get_table_boolean_opt(L, "adjacentok", FALSE))
            tmpmons.mm_flags |= MM_ADJACENTOK;
        if (get_table_boolean_opt(L, "ignorewater", FALSE))
            tmpmons.mm_flags |= MM_IGNOREWATER;
        if (!get_table_boolean_opt(L, "countbirth", TRUE))
            tmpmons.mm_flags |= MM_NOCOUNTBIRTH;

        mappear = get_table_str_opt(L, "appear_as", NULL);
        if (mappear) {
            if (!strncmp("obj:", mappear, 4)) {
                tmpmons.appear = M_AP_OBJECT;
            } else if (!strncmp("mon:", mappear, 4)) {
                tmpmons.appear = M_AP_MONSTER;
            } else if (!strncmp("ter:", mappear, 4)) {
                tmpmons.appear = M_AP_FURNITURE;
            } else {
                nhl_error(L, "Unknown appear_as type");
            }
            tmpmons.appear_as.str = dupstr(&mappear[4]);
            Free(mappear);
        }

        get_table_xy_or_coord(L, &mx, &my);

        tmpmons.id = get_table_montype(L, &mgend);
        /* get_table_montype will return a random gender if the species isn't
         * all-male or all-female; if the level designer specified a certain
         * gender, override that random one now, unless it *is* a one-gender
         * species, in which case don't override (don't permit creation of a
         * male nymph or female Nazgul, etc.) */
        if (mgend != NEUTRAL
            && (tmpmons.female == BOOL_RANDOM || is_female(&mons[tmpmons.id])
                || is_male(&mons[tmpmons.id])))
            tmpmons.female = mgend;
        /* safety net - if find_montype did not find a gender for this species
         * (should cause a lua error anyway) */
        if (tmpmons.female == BOOL_RANDOM)
            tmpmons.female = 0;

        tmpmons.class = get_table_monclass(L);

        lua_getfield(L, 1, "inventory");
        if (!lua_isnil(L, -1)) {
            tmpmons.has_invent = 1;
        }
    }

    if (mx == -1 && my == -1)
        tmpmons.coord = SP_COORD_PACK_RANDOM(0);
    else
        tmpmons.coord = SP_COORD_PACK(mx, my);

    if (tmpmons.id != NON_PM && tmpmons.class == -1)
        tmpmons.class = def_monsyms[(int) mons[tmpmons.id].mlet].sym;

    create_monster(&tmpmons, gc.coder->croom);

    if (tmpmons.has_invent && lua_type(L, -1) == LUA_TFUNCTION) {
        lua_remove(L, -2);
        if (nhl_pcall(L, 0, 0)){
            impossible("Lua error: %s", lua_tostring(L, -1));
        }
        spo_end_moninvent();
    } else
        lua_pop(L, 1);

    Free(tmpmons.name.str);
    Free(tmpmons.appear_as.str);

    return 0;
}

DISABLE_WARNING_UNREACHABLE_CODE

/* the hash key 'name' is an integer or "random",
   or if not existent, also return rndval */
static lua_Integer
get_table_int_or_random(lua_State *L, const char *name, int rndval)
{
    lua_Integer ret;
    char buf[BUFSZ];

    lua_getfield(L, 1, name);
    if (lua_type(L, -1) == LUA_TNIL) {
        lua_pop(L, 1);
        return rndval;
    }
    if (!lua_isnumber(L, -1)) {
        const char *tmp = lua_tostring(L, -1);

        if (tmp && !strcmpi("random", tmp)) {
            lua_pop(L, 1);
            return rndval;
        }
        Sprintf(buf, "Expected integer or \"random\" for \"%s\", got ", name);
        if (tmp)
            Sprintf(eos(buf), "\"%s\"", tmp);
        else
            Strcat(buf, "<Null>");
        nhl_error(L, buf);
        /*NOTREACHED*/
        lua_pop(L, 1);
        return 0;
    }
    ret = luaL_optinteger(L, -1, rndval);
    lua_pop(L, 1);
    return ret;
}

RESTORE_WARNING_UNREACHABLE_CODE

static int
get_table_buc(lua_State *L)
{
    static const char *const bucs[] = {
        "random", "blessed", "uncursed", "cursed",
        "not-cursed", "not-uncursed", "not-blessed", NULL,
    };
    static const int bucs2i[] = { 0, 1, 2, 3, 4, 5, 6, 0 };
    int curse_state = bucs2i[get_table_option(L, "buc", "random", bucs)];

    return curse_state;
}

static int
get_table_objclass(lua_State *L)
{
    char *s = get_table_str_opt(L, "class", NULL);
    int ret = -1;

    if (s && strlen(s) == 1)
        ret = (int) *s;
    Free(s);
    return ret;
}

static int
find_objtype(lua_State *L, const char *s)
{
    if (s && *s) {
        int i;
        const char *objname;
        char class = 0;

        /* In objects.h, some item classes are defined without prefixes
           (such as "scroll of ") in their names, making some names (such
           as "teleportation") ambiguous.  Get the object class if it is
           specified, and only return an object of the matching class. */
        static struct objclasspfx {
            const char *prefix;
            char class;
        } class_prefixes[] = {
            { "ring of ", RING_CLASS },
            { "potion of ", POTION_CLASS },
            { "scroll of ", SCROLL_CLASS },
            { "spellbook of ", SPBOOK_CLASS },
            { "wand of ", WAND_CLASS },
            { NULL, 0 }
        };

        if (strstri(s, " of ")) {
            for (i = 0; class_prefixes[i].prefix; i++) {
                const char *p = class_prefixes[i].prefix;

                if (!strncmpi(s, p, strlen(p))) {
                    class = class_prefixes[i].class;
                    s = s + strlen(p);
                    break;
                }
            }
        }

        /* find by object name */
        for (i = 0; i < NUM_OBJECTS; i++) {
            objname = OBJ_NAME(objects[i]);
            if ((!class || class == objects[i].oc_class)
                && objname && !strcmpi(s, objname))
                return i;
        }

        /*
         * FIXME:
         *  If the file specifies "orange potion", the actual object
         *  description is just "orange" and won't match.  [There's a
         *  reason that wish handling is insanely complicated.]  And
         *  even if that gets fixed, if the file specifies "gray stone"
         *  it will start matching but would always pick the first one.
         *
         *  "orange potion" is an unlikely thing to have in a special
         *  level description but "gray stone" is not....
         */

        /* find by object description */
        for (i = 0; i < NUM_OBJECTS; i++) {
            objname = OBJ_DESCR(objects[i]);
            if (objname && !strcmpi(s, objname))
                return i;
        }

        nhl_error(L, "Unknown object id");
    }
    return STRANGE_OBJECT;
}

static int
get_table_objtype(lua_State *L)
{
    char *s = get_table_str_opt(L, "id", NULL);
    int ret = find_objtype(L, s);

    Free(s);
    return ret;
}

/* object(); */
/* object("sack"); */
/* object("scimitar", 6,7); */
/* object("scimitar", {6,7}); */
/* object({ class = "%" }); */
/* object({ id = "boulder", x = 03, y = 12}); */
/* object({ id = "boulder", coord = {03,12} }); */
int
lspo_object(lua_State *L)
{
    static object zeroobject = {
            { 0 },   /* Str_or_len name */
            0,       /* corpsenm */
            0, 0,    /* id, spe */
            0,       /* coord */
            0, 0,    /* coordxy x,y */
            0, 0,    /* class, containment */
            0,       /* curse_state */
            0,       /* quan */
            0,       /* buried */
            0,       /* lit */
            0, 0, 0, 0, /* eroded, locked, trapped, recharged */
            0, 0, 0, 0, /* invis, greased, broken, achievment */
    };
#if 0
    int nparams = 0;
#endif
    long quancnt;
    object tmpobj;
    lua_Integer ox = -1, oy = -1;
    int argc = lua_gettop(L);
    int maybe_contents = 0;
    struct obj *otmp = NULL;

    create_des_coder();

    tmpobj = zeroobject;
    tmpobj.name.str = (char *) 0;
    tmpobj.spe = -127;
    tmpobj.quan = -1;
    tmpobj.trapped = -1;
    tmpobj.locked = -1;
    tmpobj.corpsenm = NON_PM;

    if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) {
        const char *paramstr = luaL_checkstring(L, 1);

        if (strlen(paramstr) == 1) {
            tmpobj.class = *paramstr;
            tmpobj.id = STRANGE_OBJECT;
        } else {
            tmpobj.class = -1;
            tmpobj.id = find_objtype(L, paramstr);
        }
    } else if (argc == 2 && lua_type(L, 1) == LUA_TSTRING
               && lua_type(L, 2) == LUA_TTABLE) {
        const char *paramstr = luaL_checkstring(L, 1);

        (void) get_coord(L, 2, &ox, &oy);

        if (strlen(paramstr) == 1) {
            tmpobj.class = *paramstr;
            tmpobj.id = STRANGE_OBJECT;
        } else {
            tmpobj.class = -1;
            tmpobj.id = find_objtype(L, paramstr);
        }
    } else if (argc == 3 && lua_type(L, 2) == LUA_TNUMBER
               && lua_type(L, 3) == LUA_TNUMBER) {
        const char *paramstr = luaL_checkstring(L, 1);

        ox = luaL_checkinteger(L, 2);
        oy = luaL_checkinteger(L, 3);

        if (strlen(paramstr) == 1) {
            tmpobj.class = *paramstr;
            tmpobj.id = STRANGE_OBJECT;
        } else {
            tmpobj.class = -1;
            tmpobj.id = find_objtype(L, paramstr);
        }
    } else {
        lcheck_param_table(L);

        tmpobj.spe = get_table_int_or_random(L, "spe", -127);
        tmpobj.curse_state = get_table_buc(L);
        tmpobj.corpsenm = NON_PM;
        tmpobj.name.str = get_table_str_opt(L, "name", (char *) 0);
        tmpobj.quan = get_table_int_or_random(L, "quantity", -1);
        tmpobj.buried = get_table_boolean_opt(L, "buried", 0);
        tmpobj.lit = get_table_boolean_opt(L, "lit", 0);
        tmpobj.eroded = get_table_int_opt(L, "eroded", 0);
        tmpobj.locked = get_table_boolean_opt(L, "locked", -1);
        tmpobj.trapped = get_table_boolean_opt(L, "trapped", -1);
        tmpobj.recharged = get_table_int_opt(L, "recharged", 0);
        tmpobj.greased = get_table_boolean_opt(L, "greased", 0);
        tmpobj.broken = get_table_boolean_opt(L, "broken", 0);
        tmpobj.achievement = get_table_boolean_opt(L, "achievement", 0);

        get_table_xy_or_coord(L, &ox, &oy);

        tmpobj.id = get_table_objtype(L);
        tmpobj.class = get_table_objclass(L);
        maybe_contents = 1;
    }

    if (ox == -1 && oy == -1)
        tmpobj.coord = SP_COORD_PACK_RANDOM(0);
    else
        tmpobj.coord = SP_COORD_PACK(ox, oy);

    if (tmpobj.class == -1 && tmpobj.id > STRANGE_OBJECT)
        tmpobj.class = objects[tmpobj.id].oc_class;
    else if (tmpobj.class > -1 && tmpobj.id == STRANGE_OBJECT)
        tmpobj.id = -1;

    if (tmpobj.id == STATUE || tmpobj.id == EGG
        || tmpobj.id == CORPSE || tmpobj.id == TIN
        || tmpobj.id == FIGURINE) {
        struct permonst *pm = NULL;
        boolean nonpmobj = FALSE;
        int i;
        char *montype = get_table_str_opt(L, "montype", NULL);

        if (montype) {
            if ((tmpobj.id == TIN && (!strcmpi(montype, "spinach")
                /* id="tin",montype="empty" produces an empty tin */
                                      || !strcmpi(montype, "empty")))
                /* id="egg",montype="empty" produces a generic, unhatchable
                   egg rather than an "empty egg" */
                || (tmpobj.id == EGG && !strcmpi(montype, "empty"))) {
                tmpobj.corpsenm = NON_PM;
                tmpobj.spe = !strcmpi(montype, "spinach") ? 1 : 0;
                nonpmobj = TRUE;
            } else if (strlen(montype) == 1
                       && def_char_to_monclass(*montype) != MAXMCLASSES) {
                pm = mkclass(def_char_to_monclass(*montype),
                             G_NOGEN | G_IGNORE);
            } else {
                for (i = LOW_PM; i < NUMMONS; i++)
                    if (!strcmpi(mons[i].pmnames[NEUTRAL], montype)
                        || (mons[i].pmnames[MALE] != 0
                            && !strcmpi(mons[i].pmnames[MALE], montype))
                        || (mons[i].pmnames[FEMALE] != 0
                            && !strcmpi(mons[i].pmnames[FEMALE], montype))) {
                        pm = &mons[i];
                        break;
                    }
            }
            free((genericptr_t) montype);
            if (pm)
                tmpobj.corpsenm = monsndx(pm);
            else if (!nonpmobj)
                nhl_error(L, "Unknown montype");
        }
        if (tmpobj.id == STATUE || tmpobj.id == CORPSE) {
            int lflags = 0;

            if (get_table_boolean_opt(L, "historic", 0))
                lflags |= CORPSTAT_HISTORIC;
            if (get_table_boolean_opt(L, "male", 0))
                lflags |= CORPSTAT_MALE;
            if (get_table_boolean_opt(L, "female", 0))
                lflags |= CORPSTAT_FEMALE;
            tmpobj.spe = lflags;
        } else if (tmpobj.id == EGG) {
            tmpobj.spe = get_table_boolean_opt(L, "laid_by_you", 0) ? 1 : 0;
        } else if (!nonpmobj) { /* tmpobj.spe is already set for nonpmobj */
            tmpobj.spe = 0;
        }
    }

    quancnt = (tmpobj.id > STRANGE_OBJECT) ? tmpobj.quan : 0;

    if (container_idx)
        tmpobj.containment |= SP_OBJ_CONTENT;

    if (maybe_contents) {
        lua_getfield(L, 1, "contents");
        if (!lua_isnil(L, -1))
            tmpobj.containment |= SP_OBJ_CONTAINER;
    }

    do {
        otmp = create_object(&tmpobj, gc.coder->croom);
        quancnt--;
    } while ((quancnt > 0) && ((tmpobj.id > STRANGE_OBJECT)
                               && !objects[tmpobj.id].oc_merge));

    if (lua_type(L, -1) == LUA_TFUNCTION) {
        lua_remove(L, -2);
        nhl_push_obj(L, otmp);
        if (nhl_pcall(L, 1, 0)){
            impossible("Lua error: %s", lua_tostring(L, -1));
        }
    } else
        lua_pop(L, 1);

    if ((tmpobj.containment & SP_OBJ_CONTAINER) != 0)
        spo_pop_container();

    Free(tmpobj.name.str);

    nhl_push_obj(L, otmp);

    return 1;
}

/* level_flags("noteleport", "mazelevel", ... ); */
int
lspo_level_flags(lua_State *L)
{
    int argc = lua_gettop(L);
    int i;

    create_des_coder();

    if (argc < 1)
        nhl_error(L, "expected string params");

    for (i = 1; i <= argc; i++) {
        const char *s = luaL_checkstring(L, i);

        if (!strcmpi(s, "noteleport"))
            gl.level.flags.noteleport = 1;
        else if (!strcmpi(s, "hardfloor"))
            gl.level.flags.hardfloor = 1;
        else if (!strcmpi(s, "nommap"))
            gl.level.flags.nommap = 1;
        else if (!strcmpi(s, "shortsighted"))
            gl.level.flags.shortsighted = 1;
        else if (!strcmpi(s, "arboreal"))
            gl.level.flags.arboreal = 1;
        else if (!strcmpi(s, "mazelevel"))
            gl.level.flags.is_maze_lev = 1;
        else if (!strcmpi(s, "shroud"))
            gl.level.flags.hero_memory = 1;
        else if (!strcmpi(s, "graveyard"))
            gl.level.flags.graveyard = 1;
        else if (!strcmpi(s, "icedpools"))
            icedpools = 1;
        else if (!strcmpi(s, "corrmaze"))
            gl.level.flags.corrmaze = 1;
        else if (!strcmpi(s, "premapped"))
            gc.coder->premapped = 1;
        else if (!strcmpi(s, "solidify"))
            gc.coder->solidify = 1;
        else if (!strcmpi(s, "sokoban"))
            Sokoban = 1; /* gl.level.flags.sokoban_rules */
        else if (!strcmpi(s, "inaccessibles"))
            gc.coder->check_inaccessibles = 1;
        else if (!strcmpi(s, "noflipx"))
            gc.coder->allow_flips &= ~2;
        else if (!strcmpi(s, "noflipy"))
            gc.coder->allow_flips &= ~1;
        else if (!strcmpi(s, "noflip"))
            gc.coder->allow_flips = 0;
        else if (!strcmpi(s, "temperate"))
            gl.level.flags.temperature = 0;
        else if (!strcmpi(s, "hot"))
            gl.level.flags.temperature = 1;
        else if (!strcmpi(s, "cold"))
            gl.level.flags.temperature = -1;
        else if (!strcmpi(s, "nomongen"))
            gl.level.flags.rndmongen = 0;
        else if (!strcmpi(s, "nodeathdrops"))
            gl.level.flags.deathdrops = 0;
        else if (!strcmpi(s, "noautosearch"))
            gl.level.flags.noautosearch = 1;
        else if (!strcmpi(s, "fumaroles"))
            gl.level.flags.fumaroles = 1;
        else if (!strcmpi(s, "stormy"))
            gl.level.flags.stormy = 1;
        else {
            char buf[BUFSZ];

            Sprintf(buf, "Unknown level flag %s", s);
            nhl_error(L, buf);
        }
    }

    return 0;
}

/* level_init({ style = "solidfill", fg = " " }); */
/* level_init({ style = "mines", fg = ".", bg = "}",
                smoothed=true, joined=true, lit=0 }) */
int
lspo_level_init(lua_State *L)
{
    static const char *const initstyles[] = {
        "solidfill", "mazegrid", "maze", "rogue", "mines", "swamp", NULL
    };
    static const int initstyles2i[] = {
        LVLINIT_SOLIDFILL, LVLINIT_MAZEGRID, LVLINIT_MAZE, LVLINIT_ROGUE,
        LVLINIT_MINES, LVLINIT_SWAMP, 0
    };
    lev_init init_lev;

    create_des_coder();

    lcheck_param_table(L);

    splev_init_present = TRUE;

    init_lev.init_style
        = initstyles2i[get_table_option(L, "style", "solidfill", initstyles)];
    init_lev.fg = get_table_mapchr_opt(L, "fg", ROOM);
    init_lev.bg = get_table_mapchr_opt(L, "bg", INVALID_TYPE);
    init_lev.smoothed = get_table_boolean_opt(L, "smoothed", FALSE);
    init_lev.joined = get_table_boolean_opt(L, "joined", FALSE);
    init_lev.lit = get_table_boolean_opt(L, "lit", BOOL_RANDOM);
    init_lev.walled = get_table_boolean_opt(L, "walled", FALSE);
    init_lev.filling = get_table_mapchr_opt(L, "filling", init_lev.fg);
    init_lev.corrwid = get_table_int_opt(L, "corrwid", -1);
    init_lev.wallthick = get_table_int_opt(L, "wallthick", -1);
    init_lev.rm_deadends = !get_table_boolean_opt(L, "deadends", TRUE);

    gc.coder->lvl_is_joined = init_lev.joined;

    if (init_lev.bg == INVALID_TYPE)
        init_lev.bg = (init_lev.init_style == LVLINIT_SWAMP) ? MOAT : STONE;

    splev_initlev(&init_lev);

    return 0;
}

/* engraving({ x = 1, y = 1, type="burn", text="Foo" }); */
/* engraving({ coord={1, 1}, type="burn", text="Foo" }); */
/* engraving({x,y}, "engrave", "Foo"); */
int
lspo_engraving(lua_State *L)
{
    static const char *const engrtypes[] = {
        "dust", "engrave", "burn", "mark", "blood", NULL
    };
    static const int engrtypes2i[] = {
        DUST, ENGRAVE, BURN, MARK, ENGR_BLOOD, 0
    };
    int etyp = DUST;
    char *txt = (char *) 0;
    long ecoord;
    coordxy x = -1, y = -1;
    int argc = lua_gettop(L);
    boolean guardobjs = FALSE;
    boolean wipeout = TRUE;
    struct engr *ep;

    create_des_coder();

    if (argc == 1) {
        lua_Integer ex, ey;
        lcheck_param_table(L);

        get_table_xy_or_coord(L, &ex, &ey);
        x = ex;
        y = ey;
        etyp = engrtypes2i[get_table_option(L, "type", "engrave", engrtypes)];
        txt = get_table_str(L, "text");
        wipeout = get_table_boolean_opt(L, "degrade", TRUE);
        guardobjs = get_table_boolean_opt(L, "guardobjects", FALSE);
    } else if (argc == 3) {
        lua_Integer ex, ey;
        (void) get_coord(L, 1, &ex, &ey);
        x = ex;
        y = ey;
        etyp = engrtypes2i[luaL_checkoption(L, 2, "engrave", engrtypes)];
        txt = dupstr(luaL_checkstring(L, 3));
    } else {
        nhl_error(L, "Wrong parameters");
    }

    if (x == -1 && y == -1)
        ecoord = SP_COORD_PACK_RANDOM(0);
    else
        ecoord = SP_COORD_PACK(x, y);

    get_location_coord(&x, &y, DRY, gc.coder->croom, ecoord);
    make_engr_at(x, y, txt, 0L, etyp);
    Free(txt);
    ep = engr_at(x, y);
    if (ep) {
        ep->guardobjects = guardobjs;
        ep->nowipeout = !wipeout;
    }
    return 0;
}

int
lspo_mineralize(lua_State *L)
{
    int gem_prob, gold_prob, kelp_moat, kelp_pool;

    create_des_coder();

    lcheck_param_table(L);
    /* -1 produces default mineralize behavior */
    gem_prob = get_table_int_opt(L, "gem_prob", -1);
    gold_prob = get_table_int_opt(L, "gold_prob", -1);
    kelp_moat = get_table_int_opt(L, "kelp_moat", -1);
    kelp_pool = get_table_int_opt(L, "kelp_pool", -1);

    mineralize(kelp_pool, kelp_moat, gold_prob, gem_prob, TRUE);

    return 0;
}

static const struct {
    const char *name;
    int type;
} room_types[] = {
    { "ordinary", OROOM },
    { "themed", THEMEROOM },
    { "throne", COURT },
    { "swamp", SWAMP },
    { "vault", VAULT },
    { "beehive", BEEHIVE },
    { "morgue", MORGUE },
    { "barracks", BARRACKS },
    { "zoo", ZOO },
    { "delphi", DELPHI },
    { "temple", TEMPLE },
    { "anthole", ANTHOLE },
    { "cocknest", COCKNEST },
    { "leprehall", LEPREHALL },
    { "shop", SHOPBASE },
    { "armor shop", ARMORSHOP },
    { "scroll shop", SCROLLSHOP },
    { "potion shop", POTIONSHOP },
    { "weapon shop", WEAPONSHOP },
    { "food shop", FOODSHOP },
    { "ring shop", RINGSHOP },
    { "wand shop", WANDSHOP },
    { "tool shop", TOOLSHOP },
    { "book shop", BOOKSHOP },
    { "health food shop", FODDERSHOP },
    { "candle shop", CANDLESHOP },
    { 0, 0 }
};

static const char *
get_mkroom_name(int rtype)
{
    int i;

    for (i = 0; room_types[i].name; i++)
        if (room_types[i].type == rtype)
            return room_types[i].name;
    return NULL;
}

static int
get_table_roomtype_opt(lua_State *L, const char *name, int defval)
{
    char *roomstr = get_table_str_opt(L, name, emptystr);
    int i, res = defval;

    if (roomstr && *roomstr) {
        for (i = 0; room_types[i].name; i++)
            if (!strcmpi(roomstr, room_types[i].name)) {
                res = room_types[i].type;
                break;
            }
        if (!room_types[i].name)
            impossible("Unknown room type '%s'", roomstr);
    }
    Free(roomstr);
    return res;
}

/* room({ type="ordinary", lit=1, x=3,y=3, xalign="center",yalign="center",
 *        w=11,h=9 }); */
/* room({ lit=1, coord={3,3}, xalign="center",yalign="center", w=11,h=9 }); */
/* room({ coord={3,3}, xalign="center",yalign="center", w=11,h=9,
 *        contents=function(room) ... end }); */
int
lspo_room(lua_State *L)
{
    create_des_coder();

    if (gi.in_mk_themerooms && gt.themeroom_failed)
        return 0;

    lcheck_param_table(L);

    if (gc.coder->n_subroom > MAX_NESTED_ROOMS) {
        panic("Too deeply nested rooms?!");
    } else {
        static const char *const left_or_right[] = {
            "left", "half-left", "center", "half-right", "right",
            "none", "random", NULL
        };
        static const int l_or_r2i[] = {
            SPLEV_LEFT, SPLEV_H_LEFT, SPLEV_CENTER, SPLEV_H_RIGHT,
            SPLEV_RIGHT, -1, -1, -1
        };
        static const char *const top_or_bot[] = {
            "top", "center", "bottom", "none", "random", NULL
        };
        static const int t_or_b2i[] = { TOP, SPLEV_CENTER, BOTTOM,
                                        -1, -1, -1 };
        room tmproom;
        struct mkroom *tmpcr;
        lua_Integer rx, ry;

        get_table_xy_or_coord(L, &rx, &ry);
        tmproom.x = rx, tmproom.y = ry;
        if ((tmproom.x == -1 || tmproom.y == -1) && tmproom.x != tmproom.y)
            nhl_error(L, "Room must have both x and y");

        tmproom.w = get_table_int_opt(L, "w", -1);
        tmproom.h = get_table_int_opt(L, "h", -1);

        if ((tmproom.w == -1 || tmproom.h == -1) && tmproom.w != tmproom.h)
            nhl_error(L, "Room must have both w and h");

        tmproom.xalign = l_or_r2i[get_table_option(L, "xalign", "random",
                                                   left_or_right)];
        tmproom.yalign = t_or_b2i[get_table_option(L, "yalign", "random",
                                                   top_or_bot)];
        tmproom.rtype = get_table_roomtype_opt(L, "type", OROOM);
        tmproom.chance = get_table_int_opt(L, "chance", 100);
        tmproom.rlit = get_table_int_opt(L, "lit", -1);
        /* theme rooms default to unfilled */
        tmproom.needfill = get_table_int_opt(L, "filled",
                                             gi.in_mk_themerooms ? 0 : 1);
        tmproom.joined = get_table_boolean_opt(L, "joined", TRUE);

        if (!gc.coder->failed_room[gc.coder->n_subroom - 1]) {
            tmpcr = build_room(&tmproom, gc.coder->croom);
            if (tmpcr) {
                int n = gc.coder->n_subroom;

                gc.coder->tmproomlist[n] = tmpcr; /* TRUE to get here... */
                gc.coder->failed_room[n] = FALSE;
                /* added a subroom, make parent room irregular */
                if (gc.coder->tmproomlist[n - 1])
                    gc.coder->tmproomlist[n - 1]->irregular = TRUE;
                gc.coder->n_subroom++;
                update_croom();
                lua_getfield(L, 1, "contents");
                if (lua_type(L, -1) == LUA_TFUNCTION) {
                    lua_remove(L, -2);
                    l_push_mkroom_table(L, tmpcr);
                    if (nhl_pcall(L, 1, 0)){
                        impossible("Lua error: %s", lua_tostring(L, -1));
                    }
                } else
                    lua_pop(L, 1);
                spo_endroom(gc.coder);
                add_doors_to_room(tmpcr);
                return 0;
            }
            if (gi.in_mk_themerooms)
                gt.themeroom_failed = TRUE;
        } /* failed to create parent room, so fail this too */
    }
    gc.coder->tmproomlist[gc.coder->n_subroom] = (struct mkroom *) 0;
    gc.coder->failed_room[gc.coder->n_subroom] = TRUE;
    gc.coder->n_subroom++;
    update_croom();
    spo_endroom(gc.coder);
    if (gi.in_mk_themerooms)
        gt.themeroom_failed = TRUE;

    return 0;
}

static void
spo_endroom(struct sp_coder *coder UNUSED)
{
    if (gc.coder->n_subroom > 1) {
        gc.coder->n_subroom--;
        gc.coder->tmproomlist[gc.coder->n_subroom] = NULL;
        gc.coder->failed_room[gc.coder->n_subroom] = TRUE;
    } else {
        /* no subroom, get out of top-level room */
        /* Need to ensure xstart/ystart/xsize/ysize have something sensible,
           in case there's some stuff to be created outside the outermost
           room, and there's no MAP. */
        if (gx.xsize <= 1 && gy.ysize <= 1)
            reset_xystart_size();
    }
    update_croom();
}

/* callback for is_ok_location.
   stairs generated at random location shouldn't overwrite special terrain */
static boolean
good_stair_loc(coordxy x, coordxy y)
{
    schar typ = levl[x][y].typ;

    return (typ == ROOM || typ == CORR || typ == ICE);
}

static int
l_create_stairway(lua_State *L, boolean using_ladder)
{
    static const char *const stairdirs[] = { "down", "up", NULL };
    static const int stairdirs2i[] = { 0, 1 };
    int argc = lua_gettop(L);
    coordxy x = -1, y = -1;
    struct trap *badtrap;

    long scoord;
    int up = 0; /* default is down */
    int ltype = lua_type(L, 1);

    create_des_coder();

    if (argc == 1 && ltype == LUA_TTABLE) {
        lua_Integer ax = -1, ay = -1;
        lcheck_param_table(L);
        get_table_xy_or_coord(L, &ax, &ay);
        up = stairdirs2i[get_table_option(L, "dir", "down", stairdirs)];
        x = (coordxy) ax;
        y = (coordxy) ay;
    } else {
        lua_Integer ix = -1, iy = -1;
        if (argc > 0 && ltype == LUA_TSTRING) {
            up = stairdirs2i[luaL_checkoption(L, 1, "down", stairdirs)];
            lua_remove(L, 1);
        }
        nhl_get_xy_params(L, &ix, &iy);
        x = (coordxy) ix;
        y = (coordxy) iy;
    }

    if (x == -1 && y == -1) {
        set_ok_location_func(good_stair_loc);
        scoord = SP_COORD_PACK_RANDOM(0);
    } else
        scoord = SP_COORD_PACK(x, y);

    get_location_coord(&x, &y, DRY, gc.coder->croom, scoord);
    set_ok_location_func(NULL);
    if ((badtrap = t_at(x, y)) != 0)
        deltrap(badtrap);
    SpLev_Map[x][y] = 1;

    if (using_ladder) {
        levl[x][y].typ = LADDER;
        if (up) {
            d_level dest;

            dest.dnum = u.uz.dnum;
            dest.dlevel = u.uz.dlevel - 1;
            stairway_add(x, y, TRUE, TRUE, &dest);
            levl[x][y].ladder = LA_UP;
        } else {
            d_level dest;

            dest.dnum = u.uz.dnum;
            dest.dlevel = u.uz.dlevel + 1;
            stairway_add(x, y, FALSE, TRUE, &dest);
            levl[x][y].ladder = LA_DOWN;
        }
    } else {
        mkstairs(x, y, (char) up, gc.coder->croom,
                 !(scoord & SP_COORD_IS_RANDOM));
    }
    return 0;
}

/* stair("up"); */
/* stair({ dir = "down" }); */
/* stair({ dir = "down", x = 4, y = 7 }); */
/* stair({ dir = "down", coord = {x,y} }); */
/* stair("down", 4, 7); */
/* TODO: stair(selection, "down"); */
/* TODO: stair("up", {x,y}); */
int
lspo_stair(lua_State *L)
{
    return l_create_stairway(L, FALSE);
}

/* ladder("down"); */
/* ladder("up", 6,10); */
/* ladder({ x=11, y=05, dir="down" }); */
int
lspo_ladder(lua_State *L)
{
    return l_create_stairway(L, TRUE);
}

/* grave(); */
/* grave(x,y, "text"); */
/* grave({ x = 1, y = 1 }); */
/* grave({ x = 1, y = 1, text = "Foo" }); */
/* grave({ coord = {1, 1}, text = "Foo" }); */
int
lspo_grave(lua_State *L)
{
    int argc = lua_gettop(L);
    coordxy x, y;
    long scoord;
    lua_Integer ax,ay;
    char *txt;

    create_des_coder();

    if (argc == 3) {
        x = ax = luaL_checkinteger(L, 1);
        y = ay = luaL_checkinteger(L, 2);
        txt = dupstr(luaL_checkstring(L, 3));
    } else {
        lcheck_param_table(L);

        get_table_xy_or_coord(L, &ax, &ay);
        x = ax, y = ay;
        txt = get_table_str_opt(L, "text", NULL);
    }

    if (x == -1 && y == -1)
        scoord = SP_COORD_PACK_RANDOM(0);
    else
        scoord = SP_COORD_PACK(ax, ay);

    get_location_coord(&x, &y, DRY, gc.coder->croom, scoord);

    if (isok(x, y) && !t_at(x, y)) {
        levl[x][y].typ = GRAVE;
        make_grave(x, y, txt); /* note: 'txt' might be Null */
    }
    Free(txt);
    return 0;
}

/* altar({ x=NN, y=NN, align=ALIGNMENT, type=SHRINE }); */
/* des.altar({ coord = {5, 10}, align="noalign", type="altar" }); */
int
lspo_altar(lua_State *L)
{
    static const char *const shrines[] = {
        "altar", "shrine", "sanctum", NULL
    };
    static const int shrines2i[] = { 0, 1, 2, 0 };

    altar tmpaltar;

    lua_Integer x, y;
    long acoord;
    int shrine;
    int al;

    create_des_coder();

    lcheck_param_table(L);

    get_table_xy_or_coord(L, &x, &y);

    al = get_table_align(L);
    shrine = shrines2i[get_table_option(L, "type", "altar", shrines)];

    if (x == -1 && y == -1)
        acoord = SP_COORD_PACK_RANDOM(0);
    else
        acoord = SP_COORD_PACK(x, y);

    tmpaltar.coord = acoord;
    tmpaltar.sp_amask = al;
    tmpaltar.shrine = shrine;

    create_altar(&tmpaltar, gc.coder->croom);

    return 0;
}

static const struct {
    const char *name;
    int type;
} trap_types[] = { { "arrow", ARROW_TRAP },
                   { "dart", DART_TRAP },
                   { "falling rock", ROCKTRAP },
                   { "board", SQKY_BOARD },
                   { "bear", BEAR_TRAP },
                   { "land mine", LANDMINE },
                   { "rolling boulder", ROLLING_BOULDER_TRAP },
                   { "sleep gas", SLP_GAS_TRAP },
                   { "rust", RUST_TRAP },
                   { "fire", FIRE_TRAP },
                   { "pit", PIT },
                   { "spiked pit", SPIKED_PIT },
                   { "hole", HOLE },
                   { "trap door", TRAPDOOR },
                   { "teleport", TELEP_TRAP },
                   { "level teleport", LEVEL_TELEP },
                   { "magic portal", MAGIC_PORTAL },
                   { "web", WEB },
                   { "statue", STATUE_TRAP },
                   { "magic", MAGIC_TRAP },
                   { "anti magic", ANTI_MAGIC },
                   { "polymorph", POLY_TRAP },
                   { "vibrating square", VIBRATING_SQUARE },
                   { "random", -1 },
                   { 0, NO_TRAP } };

static int
get_table_traptype_opt(lua_State *L, const char *name, int defval)
{
    char *trapstr = get_table_str_opt(L, name, emptystr);
    int i, res = defval;

    if (trapstr && *trapstr) {
        for (i = 0; trap_types[i].name; i++)
            if (!strcmpi(trapstr, trap_types[i].name)) {
                res = trap_types[i].type;
                break;
            }
    }
    Free(trapstr);
    return res;
}

const char *
get_trapname_bytype(int ttyp)
{
    int i;

    for (i = 0; trap_types[i].name; i++)
        if (ttyp == trap_types[i].type)
            return trap_types[i].name;

    return NULL;
}

static int
get_traptype_byname(const char *trapname)
{
    int i;

    for (i = 0; trap_types[i].name; i++)
        if (!strcmpi(trapname, trap_types[i].name))
            return trap_types[i].type;

    return NO_TRAP;
}

/* trap({ type = "hole", x = 1, y = 1 }); */
/* trap({ type = "web", spider_on_web = 0 }); */
/* trap("hole", 3, 4); */
/* trap("level teleport", {5, 8}); */
/* trap("rust") */
/* trap(); */
int
lspo_trap(lua_State *L)
{
    spltrap tmptrap;
    lua_Integer x, y;
    int argc = lua_gettop(L);

    create_des_coder();

    tmptrap.spider_on_web = TRUE;
    tmptrap.seen = FALSE;
    tmptrap.novictim = FALSE;

    if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) {
        const char *trapstr = luaL_checkstring(L, 1);

        tmptrap.type = get_traptype_byname(trapstr);
        x = y = -1;
    } else if (argc == 2 && lua_type(L, 1) == LUA_TSTRING
               && lua_type(L, 2) == LUA_TTABLE) {
        const char *trapstr = luaL_checkstring(L, 1);

        tmptrap.type = get_traptype_byname(trapstr);
        (void) get_coord(L, 2, &x, &y);
    } else if (argc == 3) {
        const char *trapstr = luaL_checkstring(L, 1);

        tmptrap.type = get_traptype_byname(trapstr);
        x = luaL_checkinteger(L, 2);
        y = luaL_checkinteger(L, 3);
    } else {
        lcheck_param_table(L);

        get_table_xy_or_coord(L, &x, &y);
        tmptrap.type = get_table_traptype_opt(L, "type", -1);
        tmptrap.spider_on_web = get_table_boolean_opt(L, "spider_on_web", 1);
        tmptrap.seen = get_table_boolean_opt(L, "seen", FALSE);
        tmptrap.novictim = !get_table_boolean_opt(L, "victim", TRUE);

        lua_getfield(L, -1, "launchfrom");
        if (lua_type(L, -1) == LUA_TTABLE) {
            lua_Integer lx = -1, ly = -1;

            (void) get_coord(L, -1, &lx, &ly);
            lua_pop(L, 1);
            gl.launchplace.x = lx;
            gl.launchplace.y = ly;
        }
    }

    if (tmptrap.type == NO_TRAP)
        nhl_error(L, "Unknown trap type");

    if (x == -1 && y == -1)
        tmptrap.coord = SP_COORD_PACK_RANDOM(0);
    else
        tmptrap.coord = SP_COORD_PACK(x, y);

    create_trap(&tmptrap, gc.coder->croom);
    gl.launchplace.x = gl.launchplace.y = 0;

    return 0;
}

DISABLE_WARNING_UNREACHABLE_CODE

/* gold(500, 3,5); */
/* gold(500, {5, 6}); */
/* gold({ amount = 500, x = 2, y = 5 });*/
/* gold({ amount = 500, coord = {2, 5} });*/
/* gold(); */
int
lspo_gold(lua_State *L)
{
    int argc = lua_gettop(L);
    coordxy x, y;
    long amount;
    long gcoord;
    lua_Integer gldx, gldy;

    create_des_coder();

    if (argc == 3) {
        amount = luaL_checkinteger(L, 1);
        x = gldx = luaL_checkinteger(L, 2);
        y = gldy = luaL_checkinteger(L, 2);
    } else if (argc == 2 && lua_type(L, 2) == LUA_TTABLE) {
        amount = luaL_checkinteger(L, 1);
        (void) get_coord(L, 2, &gldx, &gldy);
        x = gldx;
        y = gldy;
    } else if (argc == 0 || (argc == 1 && lua_type(L, 1) == LUA_TTABLE)) {
        lcheck_param_table(L);

        amount = get_table_int_opt(L, "amount", -1);
        get_table_xy_or_coord(L, &gldx, &gldy);
        x = gldx, y = gldy;
    } else {
        nhl_error(L, "Wrong parameters");
        /*NOTREACHED*/
        return 0;
    }

    if (x == -1 && y == -1)
        gcoord = SP_COORD_PACK_RANDOM(0);
    else
        gcoord = SP_COORD_PACK(gldx, gldy);

    get_location_coord(&x, &y, DRY, gc.coder->croom, gcoord);
    if (amount < 0)
        amount = rnd(200);
    mkgold(amount, x, y);

    return 0;
}

RESTORE_WARNING_UNREACHABLE_CODE

/* corridor({ srcroom=1, srcdoor=2, srcwall="north",
 *            destroom=2, destdoor=1, destwall="west" }); */
int
lspo_corridor(lua_State *L)
{
    static const char *const walldirs[] = {
        "all", "random", "north", "west", "east", "south", NULL
    };
    static const int walldirs2i[] = {
        W_ANY, W_RANDOM, W_NORTH, W_WEST, W_EAST, W_SOUTH, 0
    };
    corridor tc;

    create_des_coder();

    lcheck_param_table(L);

    tc.src.room = get_table_int(L, "srcroom");
    tc.src.door = get_table_int(L, "srcdoor");
    tc.src.wall = walldirs2i[get_table_option(L, "srcwall", "all", walldirs)];
    tc.dest.room = get_table_int(L, "destroom");
    tc.dest.door = get_table_int(L, "destdoor");
    tc.dest.wall = walldirs2i[get_table_option(L, "destwall", "all",
                                               walldirs)];

    create_corridor(&tc);

    return 0;
}

/* random_corridors(); */
int
lspo_random_corridors(lua_State *L UNUSED)
{
    corridor tc;

    create_des_coder();

    tc.src.room = -1;
    tc.src.door = -1;
    tc.src.wall = -1;
    tc.dest.room = -1;
    tc.dest.door = -1;
    tc.dest.wall = -1;

    create_corridor(&tc);

    return 0;
}

/* selection */
struct selectionvar *
selection_new(void)
{
    struct selectionvar *tmps = (struct selectionvar *) alloc(sizeof *tmps);

    tmps->wid = COLNO;
    tmps->hei = ROWNO;
    tmps->bounds_dirty = FALSE;
    tmps->bounds.lx = COLNO;
    tmps->bounds.ly = ROWNO;
    tmps->bounds.hx = tmps->bounds.hy = 0;
    tmps->map = (char *) alloc((COLNO * ROWNO) + 1);
    (void) memset(tmps->map, 1, (COLNO * ROWNO));
    tmps->map[(COLNO * ROWNO)] = '\0';

    return tmps;
}

void
selection_free(struct selectionvar *sel, boolean freesel)
{
    if (sel) {
        Free(sel->map);
        sel->map = NULL;
        if (freesel)
            free((genericptr_t) sel);
        else
            (void) memset((genericptr_t) sel, 0, sizeof *sel);
    }
}

/* clear selection, setting all locations to value val */
void
selection_clear(struct selectionvar *sel, int val)
{
    (void) memset(sel->map, 1 + val, (COLNO * ROWNO));
    if (val) {
        sel->bounds.lx = 0;
        sel->bounds.ly = 0;
        sel->bounds.hx = COLNO - 1;
        sel->bounds.hy = ROWNO - 1;
    } else {
        sel->bounds.lx = COLNO;
        sel->bounds.ly = ROWNO;
        sel->bounds.hx = sel->bounds.hy = 0;
    }
    sel->bounds_dirty = FALSE;
}

struct selectionvar *
selection_clone(struct selectionvar *sel)
{
    struct selectionvar *tmps = (struct selectionvar *) alloc(sizeof *tmps);

    *tmps = *sel;
    tmps->map = dupstr(sel->map);

    return tmps;
}

/* get boundary rect of selection sel into b */
void
selection_getbounds(struct selectionvar *sel, NhRect *b)
{
    if (!sel || !b)
        return;

    selection_recalc_bounds(sel);

    if (sel->bounds.lx >= sel->wid) {
        b->lx = 0;
        b->ly = 0;
        b->hx = COLNO - 1;
        b->hy = ROWNO - 1;
    } else {
        b->lx = sel->bounds.lx;
        b->ly = sel->bounds.ly;
        b->hx = sel->bounds.hx;
        b->hy = sel->bounds.hy;
    }
}

/* recalc the boundary of selection, if necessary */
void
selection_recalc_bounds(struct selectionvar *sel)
{
    coordxy x, y;
    NhRect r;

    if (!sel->bounds_dirty)
        return;

    sel->bounds.lx = COLNO;
    sel->bounds.ly = ROWNO;
    sel->bounds.hx = sel->bounds.hy = 0;

    r.lx = r.ly = r.hx = r.hy = -1;

    /* left */
    for (x = 0; x < sel->wid; x++) {
        for (y = 0; y < sel->hei; y++) {
            if (selection_getpoint(x, y, sel)) {
                r.lx = x;
                break;
            }
        }
        if (r.lx > -1)
            break;
    }

    if (r.lx > -1) {
        /* right */
        for (x = sel->wid-1; x >= r.lx; x--) {
            for (y = 0; y < sel->hei; y++) {
                if (selection_getpoint(x, y, sel)) {
                    r.hx = x;
                    break;
                }
            }
            if (r.hx > -1)
                break;
        }

        /* top */
        for (y = 0; y < sel->hei; y++) {
            for (x = r.lx; x <= r.hx; x++) {
                if (selection_getpoint(x, y, sel)) {
                    r.ly = y;
                    break;
                }
            }
            if (r.ly > -1)
                break;
        }

        /* bottom */
        for (y = sel->hei-1; y >= r.ly; y--) {
            for (x = r.lx; x <= r.hx; x++) {
                if (selection_getpoint(x, y, sel)) {
                    r.hy = y;
                    break;
                }
            }
            if (r.hy > -1)
                break;
        }
        sel->bounds = r;
    }

    sel->bounds_dirty = FALSE;
}

coordxy
selection_getpoint(
    coordxy x, coordxy y,
    struct selectionvar *sel)
{
    if (!sel || !sel->map)
        return 0;
    if (x < 0 || y < 0 || x >= sel->wid || y >= sel->hei)
        return 0;

    return (sel->map[sel->wid * y + x] - 1);
}

void
selection_setpoint(
    coordxy x, coordxy y,
    struct selectionvar *sel,
    int c)
{
    if (!sel || !sel->map)
        return;
    if (x < 0 || y < 0 || x >= sel->wid || y >= sel->hei)
        return;

    if (c && !sel->bounds_dirty) {
        if (sel->bounds.lx > x) sel->bounds.lx = x;
        if (sel->bounds.ly > y) sel->bounds.ly = y;
        if (sel->bounds.hx < x) sel->bounds.hx = x;
        if (sel->bounds.hy < y) sel->bounds.hy = y;
    } else {
        sel->bounds_dirty = TRUE;
    }

    sel->map[sel->wid * y + x] = (char) (c + 1);
}

struct selectionvar *
selection_not(struct selectionvar *s)
{
    int x, y;
    NhRect tmprect;

    for (x = 0; x < s->wid; x++)
        for (y = 0; y < s->hei; y++)
            selection_setpoint(x, y, s, selection_getpoint(x, y, s) ? 0 : 1);
    selection_getbounds(s, &tmprect);
    return s;
}

struct selectionvar *
selection_filter_mapchar(struct selectionvar *ov,  xint16 typ, int lit)
{
    int x, y;
    struct selectionvar *ret;
    NhRect rect;

    if (!ov)
        return NULL;

    ret = selection_new();

    selection_getbounds(ov, &rect);

    for (x = rect.lx; x <= rect.hx; x++)
        for (y = rect.ly; y <= rect.hy; y++)
            if (selection_getpoint(x, y, ov)
                && match_maptyps(typ, levl[x][y].typ)) {
                switch (lit) {
                default:
                case -2:
                    selection_setpoint(x, y, ret, 1);
                    break;
                case -1:
                    selection_setpoint(x, y, ret, rn2(2));
                    break;
                case 0:
                case 1:
                    if (levl[x][y].lit == (unsigned int) lit)
                        selection_setpoint(x, y, ret, 1);
                    break;
                }
            }
    return ret;
}

struct selectionvar *
selection_filter_percent(
    struct selectionvar *ov,
    int percent)
{
    int x, y;
    struct selectionvar *ret;
    NhRect rect;

    if (!ov)
        return NULL;

    ret = selection_new();

    selection_getbounds(ov, &rect);

    for (x = rect.lx; x <= rect.hx; x++)
        for (y = rect.ly; y <= rect.hy; y++)
            if (selection_getpoint(x, y, ov) && (rn2(100) < percent))
                selection_setpoint(x, y, ret, 1);

    return ret;
}

int
selection_rndcoord(
    struct selectionvar *ov,
    coordxy *x, coordxy *y,
    boolean removeit)
{
    int idx = 0;
    int c;
    int dx, dy;
    NhRect rect;

    selection_getbounds(ov, &rect);

    for (dx = rect.lx; dx <= rect.hx; dx++)
        for (dy = rect.ly; dy <= rect.hy; dy++)
            if (selection_getpoint(dx, dy, ov))
                idx++;

    if (idx) {
        c = rn2(idx);
        for (dx = rect.lx; dx <= rect.hx; dx++)
            for (dy = rect.ly; dy <= rect.hy; dy++)
                if (selection_getpoint(dx, dy, ov)) {
                    if (!c) {
                        *x = dx;
                        *y = dy;
                        if (removeit)
                            selection_setpoint(dx, dy, ov, 0);
                        return 1;
                    }
                    c--;
                }
    }
    *x = *y = -1;
    return 0;
}

/* Choose a single random W_* direction. */
static coordxy
random_wdir(void)
{
    static const coordxy wdirs[4] = { W_NORTH, W_SOUTH, W_EAST, W_WEST };
    return wdirs[rn2(4)];
}

void
selection_do_grow(struct selectionvar *ov, int dir)
{
    coordxy x, y;
    struct selectionvar *tmp;
    NhRect rect;

    if (!ov)
        return;

    tmp = selection_new();

    if (dir == W_RANDOM)
        dir = random_wdir();

    selection_getbounds(ov, &rect);

    for (x = max(0, rect.lx-1); x <= min(COLNO-1, rect.hx+1); x++)
        for (y = max(0, rect.ly-1); y <= min(ROWNO-1, rect.hy+1); y++) {
            /* note:  dir is a mask of multiple directions, but the only
               way to specify diagonals is by including the two adjacent
               orthogonal directions, which effectively specifies three-
               way growth [WEST|NORTH => WEST plus WEST|NORTH plus NORTH] */
            if (((dir & W_WEST) && selection_getpoint(x + 1, y, ov))
                || (((dir & (W_WEST | W_NORTH)) == (W_WEST | W_NORTH))
                    && selection_getpoint(x + 1, y + 1, ov))
                || ((dir & W_NORTH) && selection_getpoint(x, y + 1, ov))
                || (((dir & (W_NORTH | W_EAST)) == (W_NORTH | W_EAST))
                    && selection_getpoint(x - 1, y + 1, ov))
                || ((dir & W_EAST) && selection_getpoint(x - 1, y, ov))
                || (((dir & (W_EAST | W_SOUTH)) == (W_EAST | W_SOUTH))
                    && selection_getpoint(x - 1, y - 1, ov))
                || ((dir & W_SOUTH) && selection_getpoint(x, y - 1, ov))
                ||  (((dir & (W_SOUTH | W_WEST)) == (W_SOUTH | W_WEST))
                     && selection_getpoint(x + 1, y - 1, ov))) {
                selection_setpoint(x, y, tmp, 1);
            }
        }

    selection_getbounds(tmp, &rect);

    for (x = rect.lx; x <= rect.hx; x++)
        for (y = rect.ly; y <= rect.hy; y++)
            if (selection_getpoint(x, y, tmp))
                selection_setpoint(x, y, ov, 1);

    selection_free(tmp, TRUE);
}

static int (*selection_flood_check_func)(coordxy, coordxy);
static schar floodfillchk_match_under_typ;

void
set_selection_floodfillchk(int (*f)(coordxy, coordxy))
{
    selection_flood_check_func = f;
}

static int
floodfillchk_match_under(coordxy x, coordxy y)
{
    return (floodfillchk_match_under_typ == levl[x][y].typ);
}

void
set_floodfillchk_match_under(coordxy typ)
{
    floodfillchk_match_under_typ = typ;
    set_selection_floodfillchk(floodfillchk_match_under);
}

static int
floodfillchk_match_accessible(coordxy x, coordxy y)
{
    return (ACCESSIBLE(levl[x][y].typ)
            || levl[x][y].typ == SDOOR
            || levl[x][y].typ == SCORR);
}

/* check whethere <x,y> is already in xs[],ys[] */
static boolean
sel_flood_havepoint(
    coordxy x, coordxy y,
    coordxy xs[], coordxy ys[],
    int n)
{
    coordxy xx = x, yy = y;

    while (n > 0) {
        --n;
        if (xs[n] == xx && ys[n] == yy)
            return TRUE;
    }
    return FALSE;
}

void
selection_floodfill(
    struct selectionvar *ov,
    coordxy x, coordxy y,
    boolean diagonals)
{
    struct selectionvar *tmp = selection_new();
#define SEL_FLOOD_STACK (COLNO * ROWNO)
#define SEL_FLOOD(nx, ny) \
    do {                                      \
        if (idx < SEL_FLOOD_STACK) {          \
            dx[idx] = (nx);                   \
            dy[idx] = (ny);                   \
            idx++;                            \
        } else                                \
            panic(floodfill_stack_overrun);   \
    } while (0)
#define SEL_FLOOD_CHKDIR(mx, my, sel) \
    do {                                                        \
        if (isok((mx), (my))                                    \
            && (*selection_flood_check_func)((mx), (my))        \
            && !selection_getpoint((mx), (my), (sel))           \
            && !sel_flood_havepoint((mx), (my), dx, dy, idx))   \
            SEL_FLOOD((mx), (my));                              \
    } while (0)
    static const char floodfill_stack_overrun[] = "floodfill stack overrun";
    int idx = 0;
    coordxy dx[SEL_FLOOD_STACK];
    coordxy dy[SEL_FLOOD_STACK];

    if (selection_flood_check_func == (int (*)(coordxy, coordxy)) 0) {
        selection_free(tmp, TRUE);
        return;
    }
    SEL_FLOOD(x, y);
    do {
        idx--;
        x = dx[idx];
        y = dy[idx];
        if (isok(x, y)) {
            selection_setpoint(x, y, ov, 1);
            selection_setpoint(x, y, tmp, 1);
        }
        SEL_FLOOD_CHKDIR((x + 1), y, tmp);
        SEL_FLOOD_CHKDIR((x - 1), y, tmp);
        SEL_FLOOD_CHKDIR(x, (y + 1), tmp);
        SEL_FLOOD_CHKDIR(x, (y - 1), tmp);
        if (diagonals) {
            SEL_FLOOD_CHKDIR((x + 1), (y + 1), tmp);
            SEL_FLOOD_CHKDIR((x - 1), (y - 1), tmp);
            SEL_FLOOD_CHKDIR((x - 1), (y + 1), tmp);
            SEL_FLOOD_CHKDIR((x + 1), (y - 1), tmp);
        }
    } while (idx > 0);
#undef SEL_FLOOD
#undef SEL_FLOOD_STACK
#undef SEL_FLOOD_CHKDIR
    selection_free(tmp, TRUE);
}

/* McIlroy's Ellipse Algorithm */
void
selection_do_ellipse(
    struct selectionvar *ov,
    int xc, int yc,
    int a, int b,
    int filled)
{ /* e(x,y) = b^2*x^2 + a^2*y^2 - a^2*b^2 */
    int x = 0, y = b;
    long a2 = (long) a * a, b2 = (long) b * b;
    long crit1 = -(a2 / 4 + a % 2 + b2);
    long crit2 = -(b2 / 4 + b % 2 + a2);
    long crit3 = -(b2 / 4 + b % 2);
    long t = -a2 * y; /* e(x+1/2,y-1/2) - (a^2+b^2)/4 */
    long dxt = 2 * b2 * x, dyt = -2 * a2 * y;
    long d2xt = 2 * b2, d2yt = 2 * a2;
    long width = 1;
    long i;

    if (!ov)
        return;

    filled = !filled;

    if (!filled) {
        while (y >= 0 && x <= a) {
            selection_setpoint(xc + x, yc + y, ov, 1);
            if (x != 0 || y != 0)
                selection_setpoint(xc - x, yc - y, ov, 1);
            if (x != 0 && y != 0) {
                selection_setpoint(xc + x, yc - y, ov, 1);
                selection_setpoint(xc - x, yc + y, ov, 1);
            }
            if (t + b2 * x <= crit1       /* e(x+1,y-1/2) <= 0 */
                || t + a2 * y <= crit3) { /* e(x+1/2,y) <= 0 */
                x++;
                dxt += d2xt;
                t += dxt;
            } else if (t - a2 * y > crit2) { /* e(x+1/2,y-1) > 0 */
                y--;
                dyt += d2yt;
                t += dyt;
            } else {
                x++;
                dxt += d2xt;
                t += dxt;
                y--;
                dyt += d2yt;
                t += dyt;
            }
        }
    } else {
        while (y >= 0 && x <= a) {
            if (t + b2 * x <= crit1       /* e(x+1,y-1/2) <= 0 */
                || t + a2 * y <= crit3) { /* e(x+1/2,y) <= 0 */
                x++;
                dxt += d2xt;
                t += dxt;
                width += 2;
            } else if (t - a2 * y > crit2) { /* e(x+1/2,y-1) > 0 */
                for (i = 0; i < width; i++)
                    selection_setpoint(xc - x + i, yc - y, ov, 1);
                if (y != 0)
                    for (i = 0; i < width; i++)
                        selection_setpoint(xc - x + i, yc + y, ov, 1);
                y--;
                dyt += d2yt;
                t += dyt;
            } else {
                for (i = 0; i < width; i++)
                    selection_setpoint(xc - x + i, yc - y, ov, 1);
                if (y != 0)
                    for (i = 0; i < width; i++)
                        selection_setpoint(xc - x + i, yc + y, ov, 1);
                x++;
                dxt += d2xt;
                t += dxt;
                y--;
                dyt += d2yt;
                t += dyt;
                width += 2;
            }
        }
    }
}

/* square of distance from line segment (x1,y1, x2,y2) to point (x3,y3) */
static long
line_dist_coord(long x1, long y1, long x2, long y2, long x3, long y3)
{
    long px = x2 - x1;
    long py = y2 - y1;
    long s = px * px + py * py;
    long x, y, dx, dy, distsq = 0;
    float lu = 0;

    if (x1 == x2 && y1 == y2)
        return dist2(x1, y1, x3, y3);

    lu = ((x3 - x1) * px + (y3 - y1) * py) / (float) s;
    if (lu > 1)
        lu = 1;
    else if (lu < 0)
        lu = 0;

    x = x1 + lu * px;
    y = y1 + lu * py;
    dx = x - x3;
    dy = y - y3;
    distsq = dx * dx + dy * dy;

    return distsq;
}

/* guts of l_selection_gradient */
void
selection_do_gradient(
    struct selectionvar *ov,
    long x, long y,
    long x2,long y2,
    long gtyp,
    long mind, long maxd)
{
    long dx, dy, dofs;

    if (mind > maxd) {
        long tmp = mind;
        mind = maxd;
        maxd = tmp;
    }

    dofs = maxd * maxd - mind * mind;
    if (dofs < 1)
        dofs = 1;

    switch (gtyp) {
    default:
        impossible("Unrecognized gradient type! Defaulting to radial...");
        /* FALLTHRU */
    case SEL_GRADIENT_RADIAL: {
        for (dx = 0; dx < COLNO; dx++)
            for (dy = 0; dy < ROWNO; dy++) {
                long d0 = line_dist_coord(x, y, x2, y2, dx, dy);

                if (d0 <= mind * mind
                    || (d0 <= maxd * maxd && d0 - mind * mind < rn2(dofs)))
                    selection_setpoint(dx, dy, ov, 1);
            }
        break;
    }
    case SEL_GRADIENT_SQUARE: {
        for (dx = 0; dx < COLNO; dx++)
            for (dy = 0; dy < ROWNO; dy++) {
                long d1 = line_dist_coord(x, y, x2, y2, x, dy);
                long d2 = line_dist_coord(x, y, x2, y2, dx, y);
                long d3 = line_dist_coord(x, y, x2, y2, x2, dy);
                long d4 = line_dist_coord(x, y, x2, y2, dx, y2);
                long d5 = line_dist_coord(x, y, x2, y2, dx, dy);
                long d0 = min(d5, min(max(d1, d2), max(d3, d4)));

                if (d0 <= mind * mind
                    || (d0 <= maxd * maxd && d0 - mind * mind < rn2(dofs)))
                    selection_setpoint(dx, dy, ov, 1);
            }
        break;
    } /*case*/
    } /*switch*/
}

/* bresenham line algo */
void
selection_do_line(
    coordxy x1, coordxy y1,
    coordxy x2, coordxy y2,
    struct selectionvar *ov)
{
    int d0, dx, dy, ai, bi, xi, yi;

    if (x1 < x2) {
        xi = 1;
        dx = x2 - x1;
    } else {
        xi = -1;
        dx = x1 - x2;
    }
    if (y1 < y2) {
        yi = 1;
        dy = y2 - y1;
    } else {
        yi = -1;
        dy = y1 - y2;
    }

    selection_setpoint(x1, y1, ov, 1);

    if (!dx && !dy) {
        /* single point - already all done */
        ;
    } else if (dx > dy) {
        ai = (dy - dx) * 2;
        bi = dy * 2;
        d0 = bi - dx;
        do {
            if (d0 >= 0) {
                y1 += yi;
                d0 += ai;
            } else
                d0 += bi;
            x1 += xi;
            selection_setpoint(x1, y1, ov, 1);
        } while (x1 != x2);
    } else {
        ai = (dx - dy) * 2;
        bi = dx * 2;
        d0 = bi - dy;
        do {
            if (d0 >= 0) {
                x1 += xi;
                d0 += ai;
            } else
                d0 += bi;
            y1 += yi;
            selection_setpoint(x1, y1, ov, 1);
        } while (y1 != y2);
    }
}

void
selection_do_randline(
    coordxy x1, coordxy y1,
    coordxy x2, coordxy y2,
    schar rough,
    schar rec,
    struct selectionvar *ov)
{
    int mx, my;
    int dx, dy;

    if (rec < 1 || (x2 == x1 && y2 == y1))
        return;

    if (rough > max(abs(x2 - x1), abs(y2 - y1)))
        rough = max(abs(x2 - x1), abs(y2 - y1));

    if (rough < 2) {
        mx = ((x1 + x2) / 2);
        my = ((y1 + y2) / 2);
    } else {
        do {
            dx = rn2(rough) - (rough / 2);
            dy = rn2(rough) - (rough / 2);
            mx = ((x1 + x2) / 2) + dx;
            my = ((y1 + y2) / 2) + dy;
        } while ((mx > COLNO - 1 || mx < 0 || my < 0 || my > ROWNO - 1));
    }

    if (!selection_getpoint(mx, my, ov)) {
        selection_setpoint(mx, my, ov, 1);
    }

    rough = (rough * 2) / 3;

    rec--;

    selection_do_randline(x1, y1, mx, my, rough, rec, ov);
    selection_do_randline(mx, my, x2, y2, rough, rec, ov);

    selection_setpoint(x2, y2, ov, 1);
}

static void
selection_iterate(
    struct selectionvar *ov,
    select_iter_func func,
    genericptr_t arg)
{
    coordxy x, y;
    NhRect rect;

    if (!ov)
        return;

    selection_getbounds(ov, &rect);

    for (x = rect.lx; x <= rect.hx; x++)
        for (y = rect.ly; y <= rect.hy; y++)
            if (isok(x,y) && selection_getpoint(x, y, ov))
                (*func)(x, y, arg);
}

/* change map location terrain type during level creation */
static void
sel_set_ter(coordxy x, coordxy y, genericptr_t arg)
{
    terrain terr;

    terr = *(terrain *) arg;
    if (!set_levltyp_lit(x, y, terr.ter, terr.tlit))
        return;
    /* TODO: move this below into set_levltyp? */
    /* handle doors and secret doors */
    if (levl[x][y].typ == SDOOR || IS_DOOR(levl[x][y].typ)) {
        if (levl[x][y].typ == SDOOR)
            levl[x][y].doormask = D_CLOSED;
        if (x && (IS_WALL(levl[x - 1][y].typ) || levl[x - 1][y].horizontal))
            levl[x][y].horizontal = 1;
    } else if (levl[x][y].typ == HWALL || levl[x][y].typ == IRONBARS) {
        levl[x][y].horizontal = 1;
    } else if (splev_init_present && levl[x][y].typ == ICE) {
        levl[x][y].icedpool = icedpools ? ICED_POOL : ICED_MOAT;
    }
}

static void
sel_set_feature(coordxy x, coordxy y, genericptr_t arg)
{
    if (!isok(x, y)) {
#ifdef EXTRA_SANITY_CHECKS
        impossible("sel_set_feature(%i,%i,%i) !isok", x, y, (*(int *) arg));
#endif /*EXTRA_SANITY_CHECKS*/
        return;
    }
    if (IS_FURNITURE(levl[x][y].typ))
        return;
    levl[x][y].typ = (*(int *) arg);
}

static void
sel_set_door(coordxy dx, coordxy dy, genericptr_t arg)
{
    coordxy typ = *(coordxy *) arg;
    coordxy x = dx, y = dy;

    if (!IS_DOOR(levl[x][y].typ) && levl[x][y].typ != SDOOR)
        levl[x][y].typ = (typ & D_SECRET) ? SDOOR : DOOR;
    if (typ & D_SECRET) {
        typ &= ~D_SECRET;
        if (typ < D_CLOSED)
            typ = D_CLOSED;
    }
    set_door_orientation(x, y); /* set/clear levl[x][y].horizontal */
    levl[x][y].doormask = typ;
    SpLev_Map[x][y] = 1;
}

DISABLE_WARNING_UNREACHABLE_CODE

/* door({ x = 1, y = 1, state = "nodoor" }); */
/* door({ coord = {1, 1}, state = "nodoor" }); */
/* door({ wall = "north", pos = 3, state="secret" }); */
/* door("nodoor", 1, 2); */
int
lspo_door(lua_State *L)
{
    static const char *const doorstates[] = {
        "random", "open", "closed", "locked", "nodoor", "broken",
        "secret", NULL
    };
    static const int doorstates2i[] = {
        -1, D_ISOPEN, D_CLOSED, D_LOCKED, D_NODOOR, D_BROKEN, D_SECRET
    };
    int msk;
    coordxy x, y;
    coordxy typ;
    int argc = lua_gettop(L);

    create_des_coder();

    if (argc == 3) {
        msk = doorstates2i[luaL_checkoption(L, 1, "random", doorstates)];
        x = luaL_checkinteger(L, 2);
        y = luaL_checkinteger(L, 3);

    } else {
        lua_Integer dx, dy;
        lcheck_param_table(L);

        get_table_xy_or_coord(L, &dx, &dy);
        x = dx, y = dy;
        msk = doorstates2i[get_table_option(L, "state", "random", doorstates)];
    }

    typ = (msk == -1) ? rnddoor() : (coordxy) msk;

    if (x == -1 && y == -1) {
        static const char *const walldirs[] = {
            "all", "random", "north", "west", "east", "south", NULL
        };
        /* Note that "random" is also W_ANY, because create_door just wants a
         * mask of acceptable walls */
        static const int walldirs2i[] = {
            W_ANY, W_ANY, W_NORTH, W_WEST, W_EAST, W_SOUTH, 0
        };
        room_door tmpd;

        tmpd.secret = (typ == D_SECRET) ? 1 : 0;
        tmpd.mask = msk;
        tmpd.pos = get_table_int_opt(L, "pos", -1);
        tmpd.wall = walldirs2i[get_table_option(L, "wall", "all", walldirs)];

        create_door(&tmpd, gc.coder->croom);
    } else {
        /*selection_iterate(sel, sel_set_door, (genericptr_t) &typ);*/
        get_location_coord(&x, &y, ANY_LOC, gc.coder->croom,
                           SP_COORD_PACK(x, y));
        if (!isok(x, y)) {
            nhl_error(L, "door coord not ok");
            /*NOTREACHED*/
            return 0;
        }
        sel_set_door(x, y, (genericptr_t) &typ);
    }

    return 0;
}

RESTORE_WARNING_UNREACHABLE_CODE

static void
l_table_getset_feature_flag(
    lua_State *L,
    int x, int y,
    const char *name,
    int flag)
{
    int val = get_table_boolean_opt(L, name, -2);

    if (val != -2) {
        if (val == -1)
            val = rn2(2);
        if (val)
            levl[x][y].flags |= flag;
        else
            levl[x][y].flags &= ~flag;
    }
}

/* guts of nhl_abs_coord; convert a coordinate relative to a map or room
 * into an absolute coordinate in gl.level.locations.
 *
 * If there is no enclosing map or room, the coordinates are assumed to be
 * absolute already.
 *
 * Part of the reason this is a function is to make it clearer in the calling
 * code that this conversion is what is intended.
 *
 * NOTE: if the coordinates are going to get passed to one of the get_location
 * family of functions, this should NOT be called; get_location already makes
 * an adjustment like this. (What this function supports which get_location
 * doesn't is the input coordinates being negative. get_location will treat
 * that as "level designer wants a random coordinate".) */
void
cvt_to_abscoord(coordxy *x, coordxy *y)
{
    /* since commit 99715e0, xstart and ystart are only relevant in mklev when
     * maps are being used, and 0 otherwise. It is possible in the future that
     * map positions and dimensions can be saved and retrieved outside of
     * mklev which would reintroduce nonzero xstart/ystart/xsiz/ysiz, but
     * this is not currently implemented, so this function can be assumed to
     * have no effect outside of mklev.
     */
    if (gc.coder && gc.coder->croom) {
        *x += gc.coder->croom->lx;
        *y += gc.coder->croom->ly;
    } else {
        *x += gx.xstart;
        *y += gy.ystart;
    }
}

/* inverse of cvt_to_abscoord; turn an absolute gl.level.locations coordinate
 * into one relative to the current map or room. */
void
cvt_to_relcoord(coordxy *x, coordxy *y)
{
    if (gc.coder && gc.coder->croom) {
        *x -= gc.coder->croom->lx;
        *y -= gc.coder->croom->ly;
    } else {
        *x -= gx.xstart;
        *y -= gy.ystart;
    }
}

DISABLE_WARNING_UNREACHABLE_CODE

/* convert map-relative coordinate to absolute.
  local ax,ay = nh.abscoord(rx, ry);
  local pt = nh.abscoord({ x = 10, y = 5 });
 */
int
nhl_abs_coord(lua_State *L)
{
    int argc = lua_gettop(L);
    coordxy x = -1, y = -1;

    if (argc == 2) {
        x = (coordxy) lua_tointeger(L, 1);
        y = (coordxy) lua_tointeger(L, 2);
        cvt_to_abscoord(&x, &y);
        lua_pushinteger(L, x);
        lua_pushinteger(L, y);
        return 2;
    } else if (argc == 1 && lua_type(L, 1) == LUA_TTABLE) {
        x = (coordxy) get_table_int(L, "x");
        y = (coordxy) get_table_int(L, "y");
        cvt_to_abscoord(&x, &y);
        lua_newtable(L);
        nhl_add_table_entry_int(L, "x", x);
        nhl_add_table_entry_int(L, "y", y);
        return 1;
    } else {
        nhl_error(L, "nhl_abs_coord: Wrong args");
        /*NOTREACHED*/
        return 0;
    }
}

/* feature("fountain", x, y); */
/* feature("fountain", {x,y}); */
/* feature({ type="fountain", x=NN, y=NN }); */
/* feature({ type="fountain", coord={NN, NN} }); */
/* feature({ type="tree", coord={NN, NN}, swarm=true, looted=false }); */
int
lspo_feature(lua_State *L)
{
    static const char *const features[] = { "fountain", "sink", "pool",
                                            "throne", "tree", NULL };
    static const int features2i[] = { FOUNTAIN, SINK, POOL,
                                      THRONE, TREE, STONE };
    coordxy x, y;
    int typ;
    int argc = lua_gettop(L);
    boolean can_have_flags = FALSE;
    long fcoord;
    int humidity;

    create_des_coder();

    if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) {
        typ = features2i[luaL_checkoption(L, 1, NULL, features)];
        x = y = -1;
    } else if (argc == 2 && lua_type(L, 1) == LUA_TSTRING
        && lua_type(L, 2) == LUA_TTABLE) {
        lua_Integer fx, fy;
        typ = features2i[luaL_checkoption(L, 1, NULL, features)];
        (void) get_coord(L, 2, &fx, &fy);
        x = fx;
        y = fy;
    } else if (argc == 3) {
        typ = features2i[luaL_checkoption(L, 1, NULL, features)];
        x = luaL_checkinteger(L, 2);
        y = luaL_checkinteger(L, 3);
    } else {
        lua_Integer fx, fy;
        lcheck_param_table(L);

        get_table_xy_or_coord(L, &fx, &fy);
        x = fx, y = fy;
        typ = features2i[get_table_option(L, "type", NULL, features)];
        can_have_flags = TRUE;
    }

    if (x == -1 && y == -1) {
        fcoord = SP_COORD_PACK_RANDOM(0);
        humidity = DRY; /* pick a regular space, no rock or other furniture */
    }
    else {
        fcoord = SP_COORD_PACK(x, y);
        humidity = ANY_LOC; /* assume the author knows what they're doing */
    }
    get_location_coord(&x, &y, humidity, gc.coder->croom, fcoord);

    if (typ == STONE)
        impossible("feature has unknown type param.");
    else
        sel_set_feature(x, y, (genericptr_t) &typ);

    if (levl[x][y].typ != typ || !can_have_flags)
        return 0;

    switch (typ) {
    default:
        break;
    case FOUNTAIN:
        l_table_getset_feature_flag(L, x, y, "looted", F_LOOTED);
        l_table_getset_feature_flag(L, x, y, "warned", F_WARNED);
        break;
    case SINK:
        l_table_getset_feature_flag(L, x, y, "pudding", S_LPUDDING);
        l_table_getset_feature_flag(L, x, y, "dishwasher", S_LDWASHER);
        l_table_getset_feature_flag(L, x, y, "ring", S_LRING);
        break;
    case THRONE:
        l_table_getset_feature_flag(L, x, y, "looted", T_LOOTED);
        break;
    case TREE:
        l_table_getset_feature_flag(L, x, y, "looted", TREE_LOOTED);
        l_table_getset_feature_flag(L, x, y, "swarm", TREE_SWARM);
        break;
    }

    return 0;
}

/*
 * [lit_state: 1 On, 0 Off, -1 random, -2 leave as-is]
 * terrain({ x=NN, y=NN, typ=MAPCHAR, lit=lit_state });
 * terrain({ coord={X, Y}, typ=MAPCHAR, lit=lit_state });
 * terrain({ selection=SELECTION, typ=MAPCHAR, lit=lit_state });
 * terrain( SELECTION, MAPCHAR [, lit_state ] );
 * terrain({x,y}, MAPCHAR);
 * terrain(x,y, MAPCHAR);
 */
int
lspo_terrain(lua_State *L)
{
    terrain tmpterrain;
    coordxy x = 0, y = 0;
    struct selectionvar *sel = NULL;
    int argc = lua_gettop(L);

    create_des_coder();
    tmpterrain.tlit = SET_LIT_NOCHANGE;
    tmpterrain.ter = INVALID_TYPE;

    if (argc == 1) {
        lua_Integer tx, ty;
        lcheck_param_table(L);

        get_table_xy_or_coord(L, &tx, &ty);
        x = tx, y = ty;
        if (tx == -1 && ty == -1) {
            lua_getfield(L, 1, "selection");
            sel = l_selection_check(L, -1);
            lua_pop(L, 1);
        }
        tmpterrain.ter = get_table_mapchr(L, "typ");
        tmpterrain.tlit = get_table_int_opt(L, "lit", SET_LIT_NOCHANGE);
    } else if (argc == 2 && lua_type(L, 1) == LUA_TTABLE
               && lua_type(L, 2) == LUA_TSTRING) {
        lua_Integer tx, ty;
        tmpterrain.ter = check_mapchr(luaL_checkstring(L, 2));
        lua_pop(L, 1);
        (void) get_coord(L, 1, &tx, &ty);
        x = tx;
        y = ty;
    } else if (argc == 2) {
        sel = l_selection_check(L, 1);
        tmpterrain.ter = check_mapchr(luaL_checkstring(L, 2));
    } else if (argc == 3) {
        x = luaL_checkinteger(L, 1);
        y = luaL_checkinteger(L, 2);
        tmpterrain.ter = check_mapchr(luaL_checkstring(L, 3));
    } else {
        nhl_error(L, "wrong parameters");
    }

    if (tmpterrain.ter == INVALID_TYPE)
        nhl_error(L, "Erroneous map char");

    if (sel) {
        selection_iterate(sel, sel_set_ter, (genericptr_t) &tmpterrain);
    } else {
        get_location_coord(&x, &y, ANY_LOC, gc.coder->croom,
                           SP_COORD_PACK(x, y));
        if (!isok(x, y)) {
            nhl_error(L, "terrain coord not ok");
            /*NOTREACHED*/
            return 0;
        }
        sel_set_ter(x, y, (genericptr_t) &tmpterrain);
    }

    return 0;
}

/*
 * replace_terrain({ x1=NN,y1=NN, x2=NN,y2=NN, fromterrain=MAPCHAR,
 *                   toterrain=MAPCHAR, lit=N, chance=NN });
 * replace_terrain({ region={x1,y1, x2,y2}, fromterrain=MAPCHAR,
 *                   toterrain=MAPCHAR, lit=N, chance=NN });
 * replace_terrain({ selection=selection.area(2,5, 40,10),
 *                   fromterrain=MAPCHAR, toterrain=MAPCHAR });
 * replace_terrain({ selection=SEL, mapfragment=[[...]],
 *                   toterrain=MAPCHAR });
 */
int
lspo_replace_terrain(lua_State *L)
{
    coordxy totyp, fromtyp;
    struct mapfragment *mf = NULL;
    struct selectionvar *sel = NULL;
    boolean freesel = FALSE;
    coordxy x, y;
    lua_Integer x1, y1, x2, y2;
    int chance;
    int tolit;
    NhRect rect;

    create_des_coder();

    lcheck_param_table(L);

    totyp = get_table_mapchr(L, "toterrain");

    if (totyp >= MAX_TYPE)
        return 0;

    fromtyp = get_table_mapchr_opt(L, "fromterrain", INVALID_TYPE);

    if (fromtyp == INVALID_TYPE) {
        const char *err;
        char *tmpstr = get_table_str(L, "mapfragment");
        mf = mapfrag_fromstr(tmpstr);
        free(tmpstr);

        if ((err = mapfrag_error(mf)) != NULL) {
            nhl_error(L, err);
            /*NOTREACHED*/
        }
    }

    chance = get_table_int_opt(L, "chance", 100);
    tolit = get_table_int_opt(L, "lit", SET_LIT_NOCHANGE);
    x1 = get_table_int_opt(L, "x1", -1);
    y1 = get_table_int_opt(L, "y1", -1);
    x2 = get_table_int_opt(L, "x2", -1);
    y2 = get_table_int_opt(L, "y2", -1);

    if (x1 == -1 && y1 == -1 && x2 == -1 && y2 == -1) {
        get_table_region(L, "region", &x1, &y1, &x2, &y2, TRUE);
    }

    if (x1 == -1 && y1 == -1 && x2 == -1 && y2 == -1) {
        lua_getfield(L, 1, "selection");
        if (lua_type(L, -1) != LUA_TNIL)
            sel = l_selection_check(L, -1);
        lua_pop(L, 1);
    }

    if (!sel) {
        sel = selection_new();
        freesel = TRUE;

        if (x1 == -1 && y1 == -1 && x2 == -1 && y2 == -1) {
            (void) selection_clear(sel, 1);
        } else {
            coordxy rx1, ry1, rx2, ry2;
            rx1 = x1, ry1 = y1, rx2 = x2, ry2 = y2;
            get_location(&rx1, &ry1, ANY_LOC, gc.coder->croom);
            get_location(&rx2, &ry2, ANY_LOC, gc.coder->croom);
            for (x = max(rx1, 0); x <= min(rx2, COLNO - 1); x++)
                for (y = max(ry1, 0); y <= min(ry2, ROWNO - 1); y++)
                    selection_setpoint(x, y, sel, 1);
        }
    }

    selection_getbounds(sel, &rect);

    for (x = max(1, rect.lx); x <= rect.hx; x++)
        for (y = rect.ly; y <= rect.hy; y++)
            if (selection_getpoint(x, y,sel)) {
                if (mf) {
                    if (mapfrag_match(mf, x, y) && (rn2(100)) < chance)
                        (void) set_levltyp_lit(x, y, totyp, tolit);
                } else {
                    if (((fromtyp == MATCH_WALL && IS_STWALL(levl[x][y].typ))
                         || levl[x][y].typ == fromtyp)
                        && rn2(100) < chance)
                        (void) set_levltyp_lit(x, y, totyp, tolit);
                }
            }

    if (freesel)
        selection_free(sel, TRUE);

    mapfrag_free(&mf);

    return 0;
}

static boolean
generate_way_out_method(
    coordxy nx, coordxy ny,
    struct selectionvar *ov)
{
    static const int escapeitems[] = {
        PICK_AXE, DWARVISH_MATTOCK, WAN_DIGGING,
        WAN_TELEPORTATION, SCR_TELEPORTATION, RIN_TELEPORTATION
    };
    struct selectionvar *ov2 = selection_new(), *ov3;
    coordxy x, y;
    boolean res = TRUE;

    selection_floodfill(ov2, nx, ny, TRUE);
    ov3 = selection_clone(ov2);

    /* try to make a secret door */
    while (selection_rndcoord(ov3, &x, &y, TRUE)) {
        if (isok(x + 1, y) && !selection_getpoint(x + 1, y, ov)
            && IS_WALL(levl[x + 1][y].typ)
            && isok(x + 2, y) &&  selection_getpoint(x + 2, y, ov)
            && ACCESSIBLE(levl[x + 2][y].typ)) {
            levl[x + 1][y].typ = SDOOR;
            goto gotitdone;
        }
        if (isok(x - 1, y) && !selection_getpoint(x - 1, y, ov)
            && IS_WALL(levl[x - 1][y].typ)
            && isok(x - 2, y) && selection_getpoint(x - 2, y, ov)
            && ACCESSIBLE(levl[x - 2][y].typ)) {
            levl[x - 1][y].typ = SDOOR;
            goto gotitdone;
        }
        if (isok(x, y + 1) && !selection_getpoint(x, y + 1, ov)
            && IS_WALL(levl[x][y + 1].typ)
            && isok(x, y + 2) && selection_getpoint(x, y + 2, ov)
            && ACCESSIBLE(levl[x][y + 2].typ)) {
            levl[x][y + 1].typ = SDOOR;
            goto gotitdone;
        }
        if (isok(x, y - 1) && !selection_getpoint(x, y - 1, ov)
            && IS_WALL(levl[x][y - 1].typ)
            && isok(x, y - 2) && selection_getpoint(x, y - 2, ov)
            && ACCESSIBLE(levl[x][y - 2].typ)) {
            levl[x][y - 1].typ = SDOOR;
            goto gotitdone;
        }
    }

    /* try to make a hole or a trapdoor */
    if (Can_fall_thru(&u.uz)) {
        selection_free(ov3, TRUE);
        ov3 = selection_clone(ov2);
        while (selection_rndcoord(ov3, &x, &y, TRUE)) {
            if (maketrap(x, y, rn2(2) ? HOLE : TRAPDOOR))
                goto gotitdone;
        }
    }

    /* generate one of the escape items */
    if (selection_rndcoord(ov2, &x, &y, FALSE)) {
        mksobj_at(escapeitems[rn2(SIZE(escapeitems))], x, y, TRUE, FALSE);
        goto gotitdone;
    }

    res = FALSE;
 gotitdone:
    selection_free(ov2, TRUE);
    selection_free(ov3, TRUE);
    return res;
}

static void
ensure_way_out(void)
{
    struct selectionvar *ov = selection_new();
    struct trap *ttmp = gf.ftrap;
    coordxy x, y;
    boolean ret = TRUE;
    stairway *stway = gs.stairs;

    set_selection_floodfillchk(floodfillchk_match_accessible);

    while (stway) {
        if (stway->tolev.dnum == u.uz.dnum)
            selection_floodfill(ov, stway->sx, stway->sy, TRUE);
        stway = stway->next;
    }

    while (ttmp) {
        if ((undestroyable_trap(ttmp->ttyp) || is_hole(ttmp->ttyp))
            && !selection_getpoint(ttmp->tx, ttmp->ty, ov))
            selection_floodfill(ov, ttmp->tx, ttmp->ty, TRUE);
        ttmp = ttmp->ntrap;
    }

    do {
        ret = TRUE;
        for (x = 1; x < COLNO; x++)
            for (y = 0; y < ROWNO; y++)
                if (ACCESSIBLE(levl[x][y].typ)
                    && !selection_getpoint(x, y, ov)) {
                    if (generate_way_out_method(x, y, ov))
                        selection_floodfill(ov, x, y, TRUE);
                    ret = FALSE;
                    goto outhere;
                }
 outhere:
        ;
    } while (!ret);
    selection_free(ov, TRUE);
}

DISABLE_WARNING_UNREACHABLE_CODE

static lua_Integer
get_table_intarray_entry(lua_State *L, int tableidx, int entrynum)
{
    lua_Integer ret = 0;
    if (tableidx < 0)
        tableidx--;

    lua_pushinteger(L, entrynum);
    lua_gettable(L, tableidx);
    if (lua_isnumber(L, -1)) {
        ret = lua_tointeger(L, -1);
    } else {
        char buf[BUFSZ];

        Sprintf(buf, "Array entry #%i is %s, expected number",
                1, luaL_typename(L, -1));
        nhl_error(L, buf);
    }
    lua_pop(L, 1);
    return ret;
}

static int
get_table_region(
    lua_State *L,
    const char *name,
    lua_Integer *x1, lua_Integer *y1,
    lua_Integer *x2, lua_Integer *y2,
    boolean optional)
{
    lua_Integer arrlen;

    lua_getfield(L, 1, name);
    if (optional && lua_type(L, -1) == LUA_TNIL) {
        lua_pop(L, 1);
        return 1;
    }

    luaL_checktype(L, -1, LUA_TTABLE);

    lua_len(L, -1);
    arrlen = lua_tointeger(L, -1);
    lua_pop(L, 1);
    if (arrlen != 4) {
        nhl_error(L, "Not a region");
        /*NOTREACHED*/
        lua_pop(L, 1);
        return 0;
    }

    *x1 = get_table_intarray_entry(L, -1, 1);
    *y1 = get_table_intarray_entry(L, -1, 2);
    *x2 = get_table_intarray_entry(L, -1, 3);
    *y2 = get_table_intarray_entry(L, -1, 4);

    lua_pop(L, 1);
    return 1;
}

boolean
get_coord(lua_State *L, int i, lua_Integer *x, lua_Integer *y)
{
    boolean ret = FALSE;
    int ltyp = lua_type(L, i);

    if (ltyp == LUA_TTABLE) {
        int arrlen;
        boolean gotx = FALSE;

        lua_getfield(L, i, "x");
        if (!lua_isnil(L, -1)) {
            *x = luaL_checkinteger(L, -1);
            gotx = TRUE;
        }
        lua_pop(L, 1);

        if (gotx) {
            lua_getfield(L, i, "y");
            if (!lua_isnil(L, -1)) {
                *y = luaL_checkinteger(L, -1);
                lua_pop(L, 1);
                ret = TRUE;
            } else {
                nhl_error(L, "Not a coordinate");
                /*NOTREACHED*/
                return FALSE;
            }
        } else {
            lua_len(L, i);
            arrlen = lua_tointeger(L, -1);
            lua_pop(L, 1);
            if (arrlen != 2) {
                nhl_error(L, "Not a coordinate");
                /*NOTREACHED*/
                return FALSE;
            }

            *x = get_table_intarray_entry(L, i, 1);
            *y = get_table_intarray_entry(L, i, 2);

            return TRUE;
        }
    } else if (ltyp != LUA_TNIL) {
        /* non-existent coord is ok */
        nhl_error(L, "non-table coord specified");
    }
    return ret;
}

RESTORE_WARNING_UNREACHABLE_CODE

static void
levregion_add(lev_region *lregion)
{
    if (!lregion->in_islev) {
        get_location(&lregion->inarea.x1, &lregion->inarea.y1, ANY_LOC,
                     (struct mkroom *) 0);
        get_location(&lregion->inarea.x2, &lregion->inarea.y2, ANY_LOC,
                     (struct mkroom *) 0);
    }

    if (!lregion->del_islev) {
        get_location(&lregion->delarea.x1, &lregion->delarea.y1,
                     ANY_LOC, (struct mkroom *) 0);
        get_location(&lregion->delarea.x2, &lregion->delarea.y2,
                     ANY_LOC, (struct mkroom *) 0);
    }
    if (gn.num_lregions) {
        /* realloc the lregion space to add the new one */
        lev_region *newl = (lev_region *) alloc(
            sizeof (lev_region) * (unsigned) (1 + gn.num_lregions));

        (void) memcpy((genericptr_t) (newl), (genericptr_t) gl.lregions,
                      sizeof (lev_region) * gn.num_lregions);
        Free(gl.lregions);
        gn.num_lregions++;
        gl.lregions = newl;
    } else {
        gn.num_lregions = 1;
        gl.lregions = (lev_region *) alloc(sizeof (lev_region));
    }
    (void) memcpy(&gl.lregions[gn.num_lregions - 1], lregion,
                  sizeof (lev_region));
}

/* get params from topmost lua hash:
   - region = {x1,y1,x2,y2}
   - exclude = {x1,y1,x2,y2} (optional)
   - region_islev=true, exclude_islev=true (optional)
   - negative x and y are invalid */
static void
l_get_lregion(lua_State *L, lev_region *tmplregion)
{
    lua_Integer x1,y1,x2,y2;

    get_table_region(L, "region", &x1, &y1, &x2, &y2, FALSE);
    tmplregion->inarea.x1 = x1;
    tmplregion->inarea.y1 = y1;
    tmplregion->inarea.x2 = x2;
    tmplregion->inarea.y2 = y2;

    x1 = y1 = x2 = y2 = -1;
    get_table_region(L, "exclude", &x1, &y1, &x2, &y2, TRUE);
    tmplregion->delarea.x1 = x1;
    tmplregion->delarea.y1 = y1;
    tmplregion->delarea.x2 = x2;
    tmplregion->delarea.y2 = y2;

    tmplregion->in_islev = get_table_boolean_opt(L, "region_islev", 0);
    tmplregion->del_islev = get_table_boolean_opt(L, "exclude_islev", 0);

    /* if x1 is still negative, exclude wasn't specified, so we should treat
     * it as if there is no exclude region at all. Force exclude_islev to
     * true so the -1,-1,-1,-1 region is safely off the map and won't
     * interfere with branch or portal placement. */
    if (x1 < 0)
        tmplregion->del_islev = TRUE;
}

/* teleport_region({ region = { x1,y1, x2,y2 } }); */
/* teleport_region({ region = { x1,y1, x2,y2 }, [ region_islev = 1, ]
 *     exclude = { x1,y1, x2,y2 }, [ exclude_islen = 1, ] [ dir = "up" ] }); */
/* TODO: maybe allow using selection, with a new method "getextents()"? */
int
lspo_teleport_region(lua_State *L)
{
    static const char *const teledirs[] = { "both", "down", "up", NULL };
    static const int teledirs2i[] = { LR_TELE, LR_DOWNTELE, LR_UPTELE, -1 };
    lev_region tmplregion;

    create_des_coder();
    lcheck_param_table(L);
    l_get_lregion(L, &tmplregion);
    tmplregion.rtype = teledirs2i[get_table_option(L, "dir", "both",
                                                   teledirs)];
    tmplregion.padding = 0;
    tmplregion.rname.str = NULL;

    levregion_add(&tmplregion);

    return 0;
}

/* TODO: FIXME
   from lev_comp SPO_LEVREGION was called as:
   - STAIR:(x1,y1,x2,y2),(x1,y1,x2,y2),dir
   - PORTAL:(x1,y1,x2,y2),(x1,y1,x2,y2),string
   - BRANCH:(x1,y1,x2,y2),(x1,y1,x2,y2),dir
*/
/* levregion({ region = { x1,y1, x2,y2 }, exclude = { x1,y1, x2,y2 },
 *             type = "portal", name="air" }); */
/* TODO: allow region to be optional, defaulting to whole level */
int
lspo_levregion(lua_State *L)
{
    static const char *const regiontypes[] = {
        "stair-down", "stair-up", "portal", "branch",
        "teleport", "teleport-up", "teleport-down", NULL
    };
    static const int regiontypes2i[] = {
        LR_DOWNSTAIR, LR_UPSTAIR, LR_PORTAL, LR_BRANCH,
        LR_TELE, LR_UPTELE, LR_DOWNTELE, 0
    };
    lev_region tmplregion;

    create_des_coder();
    lcheck_param_table(L);
    l_get_lregion(L, &tmplregion);
    tmplregion.rtype = regiontypes2i[get_table_option(L, "type", "stair-down",
                                                      regiontypes)];
    tmplregion.padding = get_table_int_opt(L, "padding", 0);
    tmplregion.rname.str = get_table_str_opt(L, "name", NULL);

    levregion_add(&tmplregion);
    return 0;
}

/* exclusion({ type = "teleport", region = { x1,y1, x2,y2 } }); */
int
lspo_exclusion(lua_State *L)
{
    static const char *const ez_types[] = {
        "teleport", "teleport-up", "teleport-down", NULL
    };
    static const int ez_types2i[] = {
        LR_TELE, LR_UPTELE, LR_DOWNTELE, 0
    };
    struct exclusion_zone *ez = (struct exclusion_zone *) alloc(sizeof *ez);
    lua_Integer x1,y1,x2,y2;

    create_des_coder();
    lcheck_param_table(L);
    ez->zonetype = ez_types2i[get_table_option(L, "type", "teleport",
                                                      ez_types)];
    get_table_region(L, "region", &x1, &y1, &x2, &y2, FALSE);
    ez->lx = x1;
    ez->ly = y1;
    ez->hx = x2;
    ez->hy = y2;
    cvt_to_abscoord(&ez->lx, &ez->ly);
    cvt_to_abscoord(&ez->hx, &ez->hy);
    ez->next = ge.exclusion_zones;
    ge.exclusion_zones = ez;
    return 0;
}

static void
sel_set_lit(coordxy x, coordxy y, genericptr_t arg)
{
     int lit = *(int *)arg;

     levl[x][y].lit = (levl[x][y].typ == LAVAPOOL) ? 1 : lit;
}

/* Add to the room any doors within/bordering it */
static void
add_doors_to_room(struct mkroom *croom)
{
    coordxy x, y;
    int i;

    for (x = croom->lx - 1; x <= croom->hx + 1; x++)
        for (y = croom->ly - 1; y <= croom->hy + 1; y++)
            if (IS_DOOR(levl[x][y].typ) || levl[x][y].typ == SDOOR)
                maybe_add_door(x, y, croom);
    for (i = 0; i < croom->nsubrooms; i++)
        add_doors_to_room(croom->sbrooms[i]);
}

DISABLE_WARNING_UNREACHABLE_CODE

/* region(selection, lit); */
/* region({ x1=NN, y1=NN, x2=NN, y2=NN, lit=BOOL, type=ROOMTYPE, joined=BOOL,
 *          irregular=BOOL, filled=NN [ , contents = FUNCTION ] }); */
/* region({ region={x1,y1, x2,y2}, type="ordinary" }); */
int
lspo_region(lua_State *L)
{
    coordxy dx1, dy1, dx2, dy2;
    register struct mkroom *troom;
    boolean do_arrival_room = FALSE, room_not_needed,
            irregular = FALSE, joined = TRUE;
    int rtype = OROOM, rlit = 1, needfill = 0;
    int argc = lua_gettop(L);

    create_des_coder();

    if (argc <= 1) {
        lcheck_param_table(L);

        /* TODO: "unfilled" ==> filled=0, "filled" ==> filled=1, and
         * "lvflags_only" ==> filled=2, probably in a get_table_needfill_opt */
        needfill = get_table_int_opt(L, "filled", 0);
        irregular = get_table_boolean_opt(L, "irregular", 0);
        joined = get_table_boolean_opt(L, "joined", TRUE);
        do_arrival_room = get_table_boolean_opt(L, "arrival_room", 0);
        rtype = get_table_roomtype_opt(L, "type", OROOM);
        rlit = get_table_int_opt(L, "lit", -1);
        dx1 = get_table_int_opt(L, "x1", -1); /* TODO: area */
        dy1 = get_table_int_opt(L, "y1", -1);
        dx2 = get_table_int_opt(L, "x2", -1);
        dy2 = get_table_int_opt(L, "y2", -1);

        if (dx1 == -1 && dy1 == -1 && dx2 == -1 && dy2 == -1) {
            lua_Integer rx1, ry1, rx2, ry2;
            get_table_region(L, "region", &rx1, &ry1, &rx2, &ry2, FALSE);
            dx1 = rx1; dy1 = ry1;
            dx2 = rx2; dy2 = ry2;
        }
        if (dx1 == -1 && dy1 == -1 && dx2 == -1 && dy2 == -1) {
            nhl_error(L, "region needs region");
        }

    } else if (argc == 2) {
        /* region(selection, "lit"); */
        static const char *const lits[] = { "unlit", "lit", NULL };
        struct selectionvar *orig = l_selection_check(L, 1),
                            *sel = selection_clone(orig);

        rlit = luaL_checkoption(L, 2, "lit", lits);

        /*
    TODO: lit=random
        */
        if (rlit)
            selection_do_grow(sel, W_ANY);
        selection_iterate(sel, sel_set_lit, (genericptr_t) &rlit);

        /* TODO: skip the rest of this function? */
        return 0;
    } else {
        nhl_error(L, "Wrong parameters");
        /*NOTREACHED*/
        return 0;
    }

    rlit = litstate_rnd(rlit);

    get_location(&dx1, &dy1, ANY_LOC, (struct mkroom *) 0);
    get_location(&dx2, &dy2, ANY_LOC, (struct mkroom *) 0);

    /* Many regions are simple, rectangular areas that just need to set
     * lighting in an area. In that case, we don't need to do anything
     * complicated by creating a room. The exceptions are:
     *  - Special rooms (which usually need to be filled).
     *  - Irregular regions (more convenient to use the room-making code).
     *  - Themed room regions (which often have contents).
     *  - When a room is desired to constrain the arrival of migrating
     *    monsters (see the mon_arrive function for details).
     */
    room_not_needed = (rtype == OROOM && !irregular
                       && !do_arrival_room && !gi.in_mk_themerooms);
    if (room_not_needed || gn.nroom >= MAXNROFROOMS) {
        region tmpregion;
        if (!room_not_needed)
            impossible("Too many rooms on new level!");
        tmpregion.rlit = rlit;
        tmpregion.x1 = dx1;
        tmpregion.y1 = dy1;
        tmpregion.x2 = dx2;
        tmpregion.y2 = dy2;
        light_region(&tmpregion);

        return 0;
    }

    troom = &gr.rooms[gn.nroom];

    /* mark rooms that must be filled, but do it later */
    troom->needfill = needfill;

    troom->needjoining = joined;

    if (irregular) {
        gm.min_rx = gm.max_rx = dx1;
        gm.min_ry = gm.max_ry = dy1;
        gs.smeq[gn.nroom] = gn.nroom;
        flood_fill_rm(dx1, dy1, gn.nroom + ROOMOFFSET, rlit, TRUE);
        add_room(gm.min_rx, gm.min_ry, gm.max_rx, gm.max_ry, FALSE, rtype,
                 TRUE);
        troom->rlit = rlit;
        troom->irregular = TRUE;
    } else {
        add_room(dx1, dy1, dx2, dy2, rlit, rtype, TRUE);
#ifdef SPECIALIZATION
        topologize(troom, FALSE); /* set roomno */
#else
        topologize(troom); /* set roomno */
#endif
    }

    if (!room_not_needed) {
        if (gc.coder->n_subroom > 1) {
            impossible("region as subroom");
        } else {
            gc.coder->tmproomlist[gc.coder->n_subroom] = troom;
            gc.coder->failed_room[gc.coder->n_subroom] = FALSE;
            gc.coder->n_subroom++;
            update_croom();
            lua_getfield(L, 1, "contents");
            if (lua_type(L, -1) == LUA_TFUNCTION) {
                lua_remove(L, -2);
                l_push_mkroom_table(L, troom);
                if (nhl_pcall(L, 1, 0)){
                    impossible("Lua error: %s", lua_tostring(L, -1));
                }
            } else {
                lua_pop(L, 1);
            }
            spo_endroom(gc.coder);
            add_doors_to_room(troom);
        }
    }

    return 0;
}

/* drawbridge({ dir="east", state="closed", x=05,y=08 }); */
/* drawbridge({ dir="east", state="closed", coord={05,08} }); */
int
lspo_drawbridge(lua_State *L)
{
    static const char *const mwdirs[] = {
        "north", "south", "west", "east", "random", NULL
    };
    static const int mwdirs2i[] = {
        DB_NORTH, DB_SOUTH, DB_WEST, DB_EAST, -1, -2
    };
    static const char *const dbopens[] = {
        "open", "closed", "random", NULL
    };
    static const int dbopens2i[] = { 1, 0, -1, -2 };
    coordxy x, y;
    lua_Integer mx, my;
    int dir;
    int db_open;
    long dcoord;

    create_des_coder();

    lcheck_param_table(L);

    get_table_xy_or_coord(L, &mx, &my);

    dir = mwdirs2i[get_table_option(L, "dir", "random", mwdirs)];
    dcoord = SP_COORD_PACK(mx, my);
    db_open = dbopens2i[get_table_option(L, "state", "random", dbopens)];
    x = mx;
    y = my;

    get_location_coord(&x, &y, DRY | WET | HOT, gc.coder->croom, dcoord);
    if (!isok(mx, my)) {
        nhl_error(L, "drawbridge coord not ok");
        /*NOTREACHED*/
        return 0;
    }
    if (db_open == -1)
        db_open = !rn2(2);
    if (!create_drawbridge(x, y, dir, db_open ? TRUE : FALSE))
        impossible("Cannot create drawbridge.");
    SpLev_Map[x][y] = 1;

    return 0;
}

/* mazewalk({ x = NN, y = NN, typ = ".", dir = "north", stocked = 0 }); */
/* mazewalk({ coord = {XX, YY}, typ = ".", dir = "north", stocked = 0 }); */
/* mazewalk(x,y,dir); */
int
lspo_mazewalk(lua_State *L)
{
    static const char *const mwdirs[] = {
        "north", "south", "east", "west", "random", NULL
    };
    static const int mwdirs2i[] = {
        W_NORTH, W_SOUTH, W_EAST, W_WEST, W_RANDOM, -2
    };
    coordxy x, y;
    lua_Integer mx, my;
    coordxy ftyp = ROOM;
    int fstocked = 1, dir = -1;
    long mcoord;
    int argc = lua_gettop(L);

    create_des_coder();

    if (argc == 3) {
        mx = luaL_checkinteger(L, 1);
        my = luaL_checkinteger(L, 2);
        dir = mwdirs2i[luaL_checkoption(L, 3, "random", mwdirs)];
    } else {
        lcheck_param_table(L);

        get_table_xy_or_coord(L, &mx, &my);
        ftyp = get_table_mapchr_opt(L, "typ", ROOM);
        fstocked = get_table_boolean_opt(L, "stocked", 1);
        dir = mwdirs2i[get_table_option(L, "dir", "random", mwdirs)];
    }

    mcoord = SP_COORD_PACK(mx, my);
    x = mx;
    y = my;

    get_location_coord(&x, &y, ANY_LOC, gc.coder->croom, mcoord);

    if (!isok(x, y)) {
        nhl_error(L, "mazewalk coord not ok");
        /*NOTREACHED*/
        return 0;
    }

    if (ftyp < 1) {
        ftyp = gl.level.flags.corrmaze ? CORR : ROOM;
    }

    if (dir == W_RANDOM)
        dir = random_wdir();

    /* don't use move() - it doesn't use W_NORTH, etc. */
    switch (dir) {
    case W_NORTH:
        --y;
        break;
    case W_SOUTH:
        y++;
        break;
    case W_EAST:
        x++;
        break;
    case W_WEST:
        --x;
        break;
    default:
        impossible("mazewalk: Bad direction");
    }

    if (!IS_DOOR(levl[x][y].typ)) {
        levl[x][y].typ = ftyp;
        levl[x][y].flags = 0;
    }

    /*
     * We must be sure that the parity of the coordinates for
     * walkfrom() is odd.  But we must also take into account
     * what direction was chosen.
     */
    if (!(x % 2)) {
        if (dir == W_EAST)
            x++;
        else
            x--;

        /* no need for IS_DOOR check; out of map bounds */
        levl[x][y].typ = ftyp;
        levl[x][y].flags = 0;
    }

    if (!(y % 2)) {
        if (dir == W_SOUTH)
            y++;
        else
            y--;
    }

    walkfrom(x, y, ftyp);
    if (fstocked)
        fill_empty_maze();

    return 0;
}

RESTORE_WARNING_UNREACHABLE_CODE

/* wall_property({ x1=0, y1=0, x2=78, y2=20, property="nondiggable" }); */
/* wall_property({ region = {1,0, 78,20}, property="nonpasswall" }); */
int
lspo_wall_property(lua_State *L)
{
    static const char *const wprops[] = {
        "nondiggable", "nonpasswall", NULL
    };
    static const int wprop2i[] = { W_NONDIGGABLE, W_NONPASSWALL, -1 };
    coordxy dx1 = -1, dy1 = -1, dx2 = -1, dy2 = -1;
    int wprop;

    create_des_coder();

    lcheck_param_table(L);

    dx1 = get_table_int_opt(L, "x1", -1);
    dy1 = get_table_int_opt(L, "y1", -1);
    dx2 = get_table_int_opt(L, "x2", -1);
    dy2 = get_table_int_opt(L, "y2", -1);

    if (dx1 == -1 && dy1 == -1 && dx2 == -1 && dy2 == -1) {
        lua_Integer rx1, ry1, rx2, ry2;
        get_table_region(L, "region", &rx1, &ry1, &rx2, &ry2, FALSE);
        dx1 = rx1; dy1 = ry1;
        dx2 = rx2; dy2 = ry2;
    }

    wprop = wprop2i[get_table_option(L, "property", "nondiggable", wprops)];

    if (dx1 == -1)
        dx1 = gx.xstart - 1;
    if (dy1 == -1)
        dy1 = gy.ystart - 1;
    if (dx2 == -1)
        dx2 = gx.xstart + gx.xsize + 1;
    if (dy2 == -1)
        dy2 = gy.ystart + gy.ysize + 1;

    get_location(&dx1, &dy1, ANY_LOC, (struct mkroom *) 0);
    get_location(&dx2, &dy2, ANY_LOC, (struct mkroom *) 0);

    set_wall_property(dx1, dy1, dx2, dy2, wprop);

    return 0;
}

static void
set_wallprop_in_selection(lua_State *L, int prop)
{
    int argc = lua_gettop(L);
    boolean freesel = FALSE;
    struct selectionvar *sel = (struct selectionvar *) 0;

    create_des_coder();

    if (argc == 1) {
        sel = l_selection_check(L, -1);
    } else if (argc == 0) {
        freesel = TRUE;
        sel = selection_new();
        selection_clear(sel, 1);
    }

    if (sel) {
        selection_iterate(sel, sel_set_wall_property, (genericptr_t) &prop);
        if (freesel)
            selection_free(sel, TRUE);
    }
}

/* non_diggable(selection); */
/* non_diggable(); */
int
lspo_non_diggable(lua_State *L)
{
    set_wallprop_in_selection(L, W_NONDIGGABLE);
    return 0;
}

/* non_passwall(selection); */
/* non_passwall(); */
int
lspo_non_passwall(lua_State *L)
{
    set_wallprop_in_selection(L, W_NONPASSWALL);
    return 0;
}

#if 0
/*ARGSUSED*/
static void
sel_set_wallify(coordxy x, coordxy y, genericptr_t arg UNUSED)
{
    wallify_map(x, y, x, y);
}
#endif

/* TODO: wallify(selection) */
/* wallify({ x1=NN,y1=NN, x2=NN,y2=NN }); */
/* wallify(); */
int
lspo_wallify(lua_State *L)
{
    int dx1 = -1, dy1 = -1, dx2 = -1, dy2 = -1;

    /* TODO: clamp coord values */
    /* TODO: maybe allow wallify({x1,y1}, {x2,y2}) */
    /* TODO: is_table_coord(), is_table_area(),
             get_table_coord(), get_table_area() */

    create_des_coder();

    if (lua_gettop(L) == 1) {
        dx1 = get_table_int(L, "x1");
        dy1 = get_table_int(L, "y1");
        dx2 = get_table_int(L, "x2");
        dy2 = get_table_int(L, "y2");
    }

    wallify_map(dx1 < 0 ? (gx.xstart - 1) : dx1,
                dy1 < 0 ? (gy.ystart - 1) : dy1,
                dx2 < 0 ? (gx.xstart + gx.xsize + 1) : dx2,
                dy2 < 0 ? (gy.ystart + gy.ysize + 1) : dy2);

    return 0;
}

/* reset_level is only needed for testing purposes */
int
lspo_reset_level(lua_State *L UNUSED)
{
    boolean wtower = In_W_tower(u.ux, u.uy, &u.uz);

    iflags.lua_testing = TRUE;
    if (L)
        create_des_coder();
    makemap_prepost(TRUE, wtower);
    gi.in_mklev = TRUE;
    oinit(); /* assign level dependent obj probabilities */
    clear_level_structures();
    return 0;
}

/* finalize_level is only needed for testing purposes */
int
lspo_finalize_level(lua_State *L UNUSED)
{
    boolean wtower = In_W_tower(u.ux, u.uy, &u.uz);
    int i;

    if (L)
        create_des_coder();

    link_doors_rooms();
    remove_boundary_syms();

    /* TODO: ensure_way_out() needs rewrite */
    if (L && gc.coder->check_inaccessibles)
        ensure_way_out();

    map_cleanup();

    /* FIXME: Ideally, we want this call to only cover areas of the map
     * which were not inserted directly by the special level file (see
     * the insect legs on Baalzebub's level, for instance). Since that
     * is currently not possible, we overload the corrmaze flag for this
     * purpose.
     */
    if (!gl.level.flags.corrmaze)
        wallification(1, 0, COLNO - 1, ROWNO - 1);

    if (L)
        flip_level_rnd(gc.coder->allow_flips, FALSE);

    count_level_features();

    if (L && gc.coder->solidify)
        solidify_map();

    /* This must be done before premap_detect(),
     * otherwise branch stairs won't be premapped. */
    fixup_special();

    if (L && gc.coder->premapped)
        premap_detect();

    level_finalize_topology();

    for (i = 0; i < gn.nroom; ++i) {
        fill_special_room(&gr.rooms[i]);
    }

    makemap_prepost(FALSE, wtower);
    iflags.lua_testing = FALSE;
    return 0;
}

DISABLE_WARNING_UNREACHABLE_CODE

/* map({ x = 10, y = 10, map = [[...]] }); */
/* map({ coord = {10, 10}, map = [[...]] }); */
/* map({ halign = "center", valign = "center", map = [[...]] }); */
/* map({ map = [[...]], contents = function(map) ... end }); */
/* map([[...]]) */
/* local selection = map( ... ); */
int
lspo_map(lua_State *L)
{
    /*
TODO: allow passing an array of strings as map data
TODO: handle if map lines aren't same length
TODO: gc.coder->croom needs to be updated
     */

    static const char *const left_or_right[] = {
        "left", "half-left", "center", "half-right", "right", "none", NULL
    };
    static const int l_or_r2i[] = {
        SPLEV_LEFT, SPLEV_H_LEFT, SPLEV_CENTER, SPLEV_H_RIGHT,
        SPLEV_RIGHT, -1, -1
    };
    static const char *const top_or_bot[] = {
        "top", "center", "bottom", "none", NULL
    };
    static const int t_or_b2i[] = { TOP, SPLEV_CENTER, BOTTOM, -1, -1 };
    int lr, tb;
    lua_Integer x = -1, y = -1;
    struct mapfragment *mf;
    char *tmpstr;
    int argc = lua_gettop(L);
    boolean has_contents = FALSE;
    int tryct = 0;
    int ox, oy;
    boolean lit = FALSE;
    struct selectionvar *sel;

    create_des_coder();

    if (gi.in_mk_themerooms && gt.themeroom_failed)
        return 0;

    if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) {
        tmpstr = dupstr(luaL_checkstring(L, 1));
        lr = tb = SPLEV_CENTER;
        mf = mapfrag_fromstr(tmpstr);
        free(tmpstr);
    } else {
        lcheck_param_table(L);
        lr = l_or_r2i[get_table_option(L, "halign", "none", left_or_right)];
        tb = t_or_b2i[get_table_option(L, "valign", "none", top_or_bot)];
        get_table_xy_or_coord(L, &x, &y);
        tmpstr = get_table_str(L, "map");
        lit = (boolean) get_table_boolean_opt(L, "lit", FALSE);
        lua_getfield(L, 1, "contents");
        if (lua_type(L, -1) == LUA_TFUNCTION) {
            lua_remove(L, -2);
            has_contents = TRUE;
        } else {
            lua_pop(L, 1);
        }
        mf = mapfrag_fromstr(tmpstr);
        free(tmpstr);
    }

    if (!mf) {
        nhl_error(L, "Map data error");
        /*NOTREACHED*/
        return 0;
    }

    sel = selection_new();
    ox = x;
    oy = y;
 redo_maploc:
    gx.xsize = mf->wid;
    gy.ysize = mf->hei;

    if (lr == -1 && tb == -1) {
        if (gi.in_mk_themerooms && (ox == -1 || oy == -1)) {
            if (ox == -1) {
                if (gc.coder->croom) {
                    x = somex(gc.coder->croom) - mf->wid;
                    if (x < 1)
                        x = 1;
                } else {
                    x = 1 + rn2(COLNO - 1 - mf->wid);
                }
            }

            if (oy == -1) {
                if (gc.coder->croom) {
                    y = somey(gc.coder->croom) - mf->hei;
                    if (y < 1)
                        y = 1;
                } else {
                    y = rn2(ROWNO - mf->wid);
                }
            }
        }

        if (isok(x, y)) {
            /* x,y is given, place map starting at x,y */
            if (gc.coder->croom) {
                /* in a room? adjust to room relative coords */
                gx.xstart = x + gc.coder->croom->lx;
                gy.ystart = y + gc.coder->croom->ly;
                gx.xsize = min(mf->wid,
                              (gc.coder->croom->hx - gc.coder->croom->lx));
                gy.ysize = min(mf->hei,
                              (gc.coder->croom->hy - gc.coder->croom->ly));
            } else {
                gx.xsize = mf->wid;
                gy.ysize = mf->hei;
                gx.xstart = x;
                gy.ystart = y;
            }
        } else {
            mapfrag_free(&mf);
            nhl_error(L, "Map requires either x,y or halign,valign params");
            selection_free(sel, TRUE);
            return 0;
        }
    } else {
        /* place map starting at halign,valign */
        switch (lr) {
        case SPLEV_LEFT:
            gx.xstart = splev_init_present ? 1 : 3;
            break;
        case SPLEV_H_LEFT:
            gx.xstart = 2 + ((gx.x_maze_max - 2 - gx.xsize) / 4);
            break;
        case SPLEV_CENTER:
            gx.xstart = 2 + ((gx.x_maze_max - 2 - gx.xsize) / 2);
            break;
        case SPLEV_H_RIGHT:
            gx.xstart = 2 + ((gx.x_maze_max - 2 - gx.xsize) * 3 / 4);
            break;
        case SPLEV_RIGHT:
            gx.xstart = gx.x_maze_max - gx.xsize - 1;
            break;
        }
        switch (tb) {
        case TOP:
            gy.ystart = 3;
            break;
        case SPLEV_CENTER:
            gy.ystart = 2 + ((gy.y_maze_max - 2 - gy.ysize) / 2);
            break;
        case BOTTOM:
            gy.ystart = gy.y_maze_max - gy.ysize - 1;
            break;
        }
        if (!(gx.xstart % 2))
            gx.xstart++;
        if (!(gy.ystart % 2))
            gy.ystart++;
    }

    if (gy.ystart < 0 || gy.ystart + gy.ysize > ROWNO) {
        if (gi.in_mk_themerooms) {
            gt.themeroom_failed = TRUE;
            goto skipmap;
        }
        /* try to move the start a bit */
        gy.ystart += (gy.ystart > 0) ? -2 : 2;
        if (gy.ysize == ROWNO)
            gy.ystart = 0;
        if (gy.ystart < 0 || gy.ystart + gy.ysize > ROWNO)
            gy.ystart = 0;
    }
    if (gx.xsize <= 1 && gy.ysize <= 1) {
        reset_xystart_size();
    } else {
        coordxy mptyp;
        terrain terr;

        /* Themed rooms should never overwrite anything */
        if (gi.in_mk_themerooms) {
            boolean isokp = TRUE;
            for (y = gy.ystart - 1;
                 y < min(ROWNO, gy.ystart + gy.ysize) + 1; y++)
                for (x = gx.xstart - 1;
                     x < min(COLNO, gx.xstart + gx.xsize) + 1; x++) {
                    if (!isok(x, y)) {
                        isokp = FALSE;
                    } else if (y < gy.ystart || y >= (gy.ystart + gy.ysize)
                            || x < gx.xstart || x >= (gx.xstart + gx.xsize)) {
                        if (levl[x][y].typ != STONE
                            || levl[x][y].roomno != NO_ROOM)
                            isokp = FALSE;
                    } else {
                        mptyp = mapfrag_get(mf, x - gx.xstart, y - gy.ystart);
                        if (mptyp >= MAX_TYPE)
                            continue;
                        if ((levl[x][y].typ != STONE
                             && levl[x][y].typ != mptyp)
                            || levl[x][y].roomno != NO_ROOM)
                            isokp = FALSE;
                    }
                    if (!isokp) {
                        if (tryct++ < 100 && (lr == -1 || tb == -1))
                            goto redo_maploc;
                        gt.themeroom_failed = TRUE;
                        goto skipmap;
                    }
                }
        }

        /* Load the map */
        for (y = gy.ystart; y < min(ROWNO, gy.ystart + gy.ysize); y++)
            for (x = gx.xstart; x < min(COLNO, gx.xstart + gx.xsize); x++) {
                mptyp = mapfrag_get(mf, (x - gx.xstart), (y - gy.ystart));
                if (mptyp == INVALID_TYPE) {
                    /* TODO: warn about illegal map char */
                    continue;
                }
                if (mptyp >= MAX_TYPE)
                    continue;
                /* clear out levl: load_common_data may set them */
                levl[x][y].flags = 0;
                levl[x][y].horizontal = 0;
                levl[x][y].roomno = 0;
                levl[x][y].edge = 0;
                SpLev_Map[x][y] = 1;
                selection_setpoint(x, y, sel, 1);
                terr.ter = mptyp;
                terr.tlit = lit;
                sel_set_ter(x, y, &terr);
            }
    }

 skipmap:
    mapfrag_free(&mf);

    if (gi.in_mk_themerooms && gt.themeroom_failed) {
        /* this mutated xstart and ystart in the process of trying to make a
         * themed room, so undo them */
        reset_xystart_size();
    }
    else if (has_contents) {
        l_push_wid_hei_table(L, gx.xsize, gy.ysize);
        if (nhl_pcall(L, 1, 0)){
            impossible("Lua error: %s", lua_tostring(L, -1));
        }
        reset_xystart_size();
    }

    /* return selection where map locations were put */
    l_selection_push_copy(L, sel);
    selection_free(sel, TRUE);

    return 1;
}

RESTORE_WARNING_UNREACHABLE_CODE

struct selectionvar *
selection_from_mkroom(struct mkroom *croom)
{
    struct selectionvar *sel = selection_new();
    coordxy x, y;
    unsigned rmno;

    if (!croom && gc.coder && gc.coder->croom)
        croom = gc.coder->croom;
    if (!croom)
        return sel;

    rmno = (unsigned)((croom - gr.rooms) + ROOMOFFSET);
    for (y = croom->ly; y <= croom->hy; y++)
        for (x = croom->lx; x <= croom->hx; x++)
            if (isok(x, y) && !levl[x][y].edge
                && levl[x][y].roomno == rmno)
                selection_setpoint(x, y, sel, 1);
    return sel;
}

void
update_croom(void)
{
    if (!gc.coder)
        return;

    if (gc.coder->n_subroom)
        gc.coder->croom = gc.coder->tmproomlist[gc.coder->n_subroom - 1];
    else
        gc.coder->croom = NULL;
}

static struct sp_coder *
sp_level_coder_init(void)
{
    int tmpi;
    struct sp_coder *coder = (struct sp_coder *) alloc(sizeof *coder);

    coder->premapped = FALSE;
    coder->solidify = FALSE;
    coder->check_inaccessibles = FALSE;
    coder->allow_flips = 3; /* allow flipping level horiz/vert */
    coder->croom = NULL;
    coder->n_subroom = 1;
    coder->lvl_is_joined = FALSE;
    coder->room_stack = 0;

    splev_init_present = FALSE;
    icedpools = FALSE;

    for (tmpi = 0; tmpi <= MAX_NESTED_ROOMS; tmpi++) {
        coder->tmproomlist[tmpi] = (struct mkroom *) 0;
        coder->failed_room[tmpi] = FALSE;
    }

    update_croom();

    for (tmpi = 0; tmpi < MAX_CONTAINMENT; tmpi++)
        container_obj[tmpi] = NULL;
    container_idx = 0;

    invent_carrying_monster = NULL;

    (void) memset((genericptr_t) SpLev_Map, 0, sizeof SpLev_Map);

    gl.level.flags.is_maze_lev = 0;
    gl.level.flags.temperature = In_hell(&u.uz) ? 1 : 0;
    gl.level.flags.rndmongen = 1;
    gl.level.flags.deathdrops = 1;

    reset_xystart_size();

    return coder;
}


static const struct luaL_Reg nhl_functions[] = {
    { "message", lspo_message },
    { "monster", lspo_monster },
    { "object", lspo_object },
    { "level_flags", lspo_level_flags },
    { "level_init", lspo_level_init },
    { "engraving", lspo_engraving },
    { "mineralize", lspo_mineralize },
    { "door", lspo_door },
    { "stair", lspo_stair },
    { "ladder", lspo_ladder },
    { "grave", lspo_grave },
    { "altar", lspo_altar },
    { "map", lspo_map },
    { "feature", lspo_feature },
    { "terrain", lspo_terrain },
    { "replace_terrain", lspo_replace_terrain },
    { "room", lspo_room },
    { "corridor", lspo_corridor },
    { "random_corridors", lspo_random_corridors },
    { "gold", lspo_gold },
    { "trap", lspo_trap },
    { "mazewalk", lspo_mazewalk },
    { "drawbridge", lspo_drawbridge },
    { "region", lspo_region },
    { "levregion", lspo_levregion },
    { "exclusion", lspo_exclusion },
    { "wallify", lspo_wallify },
    { "wall_property", lspo_wall_property },
    { "non_diggable", lspo_non_diggable },
    { "non_passwall", lspo_non_passwall },
    { "teleport_region", lspo_teleport_region },
    { "reset_level", lspo_reset_level },
    { "finalize_level", lspo_finalize_level },
    /* TODO: { "branch", lspo_branch }, */
    /* TODO: { "portal", lspo_portal }, */
    { NULL, NULL }
};

/* TODO:

 - if des-file used MAZE_ID to start a level, the level needs
   des.level_flags("mazelevel")
 - expose gc.coder->croom or gx.xstart gy.ystart and gx.xsize gy.ysize to lua.
 - detect a "subroom" automatically.
 - new function get_mapchar(x,y) to return the mapchar on map
 - many params should accept their normal type (eg, int or bool), AND "random"
 - automatically add shuffle(array)
 - automatically add align = { "law", "neutral", "chaos" } and shuffle it.
   (remove from lua files)
 - grab the header comments from des-files and add add them to the lua files

*/

void
l_register_des(lua_State *L)
{
    /* register des -table, and functions for it */
    lua_newtable(L);
    luaL_setfuncs(L, nhl_functions, 0);
    lua_setglobal(L, "des");
}

void
create_des_coder(void)
{
    if (!gc.coder)
        gc.coder = sp_level_coder_init();
}

/*
 * General loader
 */
boolean
load_special(const char *name)
{
    boolean result = FALSE;
    nhl_sandbox_info sbi = {NHL_SB_SAFE, 0, 0, 0};

    create_des_coder();

    if (!load_lua(name, &sbi))
        goto give_up;

    link_doors_rooms();
    remove_boundary_syms();

    /* TODO: ensure_way_out() needs rewrite */
    if (gc.coder->check_inaccessibles)
        ensure_way_out();

    map_cleanup();

    /* FIXME: Ideally, we want this call to only cover areas of the map
     * which were not inserted directly by the special level file (see
     * the insect legs on Baalzebub's level, for instance). Since that
     * is currently not possible, we overload the corrmaze flag for this
     * purpose.
     */
    if (!gl.level.flags.corrmaze)
        wallification(1, 0, COLNO - 1, ROWNO - 1);

    flip_level_rnd(gc.coder->allow_flips, FALSE);

    count_level_features();

    if (gc.coder->solidify)
        solidify_map();

    /* This must be done before premap_detect(),
     * otherwise branch stairs won't be premapped. */
    fixup_special();

    if (gc.coder->premapped)
        premap_detect();

    result = TRUE;
 give_up:
    Free(gc.coder);
    gc.coder = NULL;

    return result;
}

/*sp_lev.c*/
