From 9b74ea0b225a67a3c60f2c775a9f2328f499d9f0 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Thu, 9 Apr 2020 07:22:22 +0300 Subject: [PATCH] Shaped and themed rooms Allows creating shaped or themed rooms for the Dungeons of Doom via lua script. Invalidates bones and saves. Makefiles updated for unix/linux by adding themerms.lua, but other OSes need to have that added. --- dat/dungeon.lua | 1 + dat/themerms.lua | 529 ++++++++++++++++++++++++++++++++++++++++++ doc/fixes37.0 | 1 + include/decl.h | 2 + include/dungeon.h | 2 + include/extern.h | 2 + include/mkroom.h | 2 + include/patchlevel.h | 2 +- src/decl.c | 2 + src/dungeon.c | 6 + src/mklev.c | 213 +++++++++++++---- src/mkmaze.c | 1 + src/mkroom.c | 17 ++ src/save.c | 6 + src/sp_lev.c | 107 ++++++++- sys/unix/Makefile.top | 2 +- 16 files changed, 842 insertions(+), 53 deletions(-) create mode 100644 dat/themerms.lua diff --git a/dat/dungeon.lua b/dat/dungeon.lua index be504fbd2..bf4d4b9bf 100644 --- a/dat/dungeon.lua +++ b/dat/dungeon.lua @@ -6,6 +6,7 @@ dungeon = { base = 25, range = 5, alignment = "unaligned", + themerooms = "themerms.lua", branches = { { name = "The Gnomish Mines", diff --git a/dat/themerms.lua b/dat/themerms.lua new file mode 100644 index 000000000..70339791a --- /dev/null +++ b/dat/themerms.lua @@ -0,0 +1,529 @@ + +-- themerooms is an array of tables and/or functions. +-- the tables define "frequency" and "contents", +-- a plain function has frequency of 1 +-- des.room({ type = "ordinary", filled = 1 }) +-- - ordinary rooms can be converted to shops or any other special rooms. +-- - filled = 1 means the room gets random room contents, even if it +-- doesn't get converted into a special room. Without filled, +-- the room only gets what you define in here. +-- - use type = "themed" to force a room that's never converted +-- to a special room, such as a shop or a temple. +-- core calls themerooms_generate() multiple times per level +-- to generate a single themed room. + + +themerooms = { + { + -- the "default" room + frequency = 1000, + contents = function() + des.room({ type = "ordinary", filled = 1 }); + end + }, + + -- Fake Delphi + function() + des.room({ type = "ordinary", w = 11,h = 9, filled = 1, + contents = function() + des.room({ type = "ordinary", x = 4,y = 3, w = 3,h = 3, filled = 1, + contents = function() + des.door({ state="random", wall="all" }); + end + }); + end + }); + end, + + -- Room in a room + -- FIXME: subroom location is too often left/top? + function() + des.room({ type = "ordinary", filled = 1, + contents = function() + des.room({ type = "ordinary", + contents = function() + des.door({ state="random", wall="all" }); + end + }); + end + }); + end, + + -- Huge room, with another room inside (90%) + function() + des.room({ type = "ordinary", w = nh.rn2(10)+11,h = nh.rn2(5)+8, filled = 1, + contents = function() + if (percent(90)) then + des.room({ type = "ordinary", filled = 1, + contents = function() + des.door({ state="random", wall="all" }); + if (percent(50)) then + des.door({ state="random", wall="all" }); + end + end + }); + end + end + }); + end, + + -- Ice room + function() + des.room({ type = "themed", filled = 1, + contents = function() + des.terrain(selection.floodfill(1,1), "I"); + end + }); + end, + + -- Boulder room + function() + des.room({ type = "themed", + contents = function() + for i = 1, 3 + d(6) do + des.object("boulder"); + end + for i = 1, d(4) do + des.trap("rolling boulder"); + end + end + }); + end, + + -- Spider nest + function() + des.room({ type = "themed", + contents = function() + for i = 1, d(3,3) do + des.trap("web"); + end + end + }); + end, + + -- Trap room + function() + des.room({ type = "themed", filled = 0, + contents = function(rm) + local traps = { "arrow", "dart", "falling rock", "bear", + "land mine", "sleep gas", "rust", + "anti magic" }; + shuffle(traps); + for x = 0, rm.width do + for y = 0, rm.height do + if (percent(75)) then + des.trap(traps[1], x, y); + end + end + end + end + }); + end, + + -- Buried treasure + function() + des.room({ type = "ordinary", filled = 1, + contents = function() + des.object({ id = "chest", buried = true, contents = function() + for i = 1, d(3,4) do + des.object(); + end + end }); + end + }); + end, + + -- Massacre + function() + des.room({ type = "themed", + contents = function() + local mon = { "apprentice", "warrior", "ninja", "thug", + "hunter", "acolyte", "abbot", "page", + "attendant", "neanderthal", "chieftain", + "student", "wizard", "valkyrie", "tourist", + "samurai", "rogue", "ranger", "priestess", + "priest", "monk", "knight", "healer", + "cavewoman", "caveman", "barbarian", + "archeologist" }; + shuffle(mon); + for i = 1, d(5,5) do + if (percent(10)) then shuffle(mon); end + des.object({ id = "corpse", montype = mon[1] }); + end + end + }); + end, + + -- Statuary + function() + des.room({ type = "themed", + contents = function() + for i = 1, d(5,5) do + des.object({ id = "statue" }); + end + for i = 1, d(3) do + des.trap("statue"); + end + end + }); + end, + + -- Light source + function() + des.room({ type = "themed", lit = 0, + contents = function() + des.object({ id = "oil lamp", lit = true }); + end + }); + end, + + -- Temple of the gods + function() + des.room({ type = "themed", + contents = function() + des.altar({ align = align[1] }); + des.altar({ align = align[2] }); + des.altar({ align = align[3] }); + end + }); + end, + + -- Mausoleum + function() + des.room({ type = "themed", w = 5,h = 5, + contents = function() + local pts = { {1,1}, {2,1}, {3,1}, + {1,2}, {3,2}, + {1,3}, {2,3}, {3,3} }; + for i = 1, #pts do + des.terrain(pts[i], "-"); + end + if (percent(50)) then + local mons = { "M", "V", "L", "Z" }; + shuffle(mons); + des.monster(mons[1], 2,2); + else + des.object({ id = "corpse", montype = "@", coord = {2,2} }); + end + if (percent(20)) then + local place = { {2,1}, {1,2}, {3,2}, {2,3} }; + shuffle(place); + des.terrain(place[1], "S"); + end + end + }); + end, + + -- Random dungeon feature in the middle of a odd-sized room + function() + local wid = 3 + (nh.rn2(3) * 2); + local hei = 3 + (nh.rn2(3) * 2); + des.room({ type = "ordinary", filled = 1, w = wid, h = hei, + contents = function(rm) + local feature = { "C", "L", "I", "P", "T" }; + shuffle(feature); + des.terrain(rm.width / 2, rm.height / 2, feature[1]); + end + }); + end, + + -- L-shaped + function() + des.map({ map = [[ +-----xxx +|...|xxx +|...|xxx +|...---- +|......| +|......| +|......| +--------]], contents = function(m) des.region({ region={1,1,3,3}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- L-shaped, rot 1 + function() + des.map({ map = [[ +xxx----- +xxx|...| +xxx|...| +----...| +|......| +|......| +|......| +--------]], contents = function(m) des.region({ region={5,1,5,3}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- L-shaped, rot 2 + function() + des.map({ map = [[ +-------- +|......| +|......| +|......| +----...| +xxx|...| +xxx|...| +xxx-----]], contents = function(m) des.region({ region={1,1,2,2}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- L-shaped, rot 3 + function() + des.map({ map = [[ +-------- +|......| +|......| +|......| +|...---- +|...|xxx +|...|xxx +-----xxx]], contents = function(m) des.region({ region={1,1,2,2}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- Blocked center + function() + des.map({ map = [[ +----------- +|.........| +|.........| +|.........| +|...LLL...| +|...LLL...| +|...LLL...| +|.........| +|.........| +|.........| +-----------]], contents = function(m) +if (percent(30)) then + local terr = { "-", "P" }; + shuffle(terr); + des.replace_terrain({ region = {1,1, 9,9}, fromterrain = "L", toterrain = terr[1] }); +end +des.region({ region={1,1,2,2}, type="ordinary", irregular=true, prefilled=true }); +end }); + end, + + -- Circular, small + function() + des.map({ map = [[ +xx---xx +x--.--x +--...-- +|.....| +--...-- +x--.--x +xx---xx]], contents = function(m) des.region({ region={3,3,3,3}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- Circular, medium + function() + des.map({ map = [[ +xx-----xx +x--...--x +--.....-- +|.......| +|.......| +|.......| +--.....-- +x--...--x +xx-----xx]], contents = function(m) des.region({ region={4,4,4,4}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- Circular, big + function() + des.map({ map = [[ +xxx-----xxx +x---...---x +x-.......-x +--.......-- +|.........| +|.........| +|.........| +--.......-- +x-.......-x +x---...---x +xxx-----xxx]], contents = function(m) des.region({ region={5,5,5,5}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- T-shaped + function() + des.map({ map = [[ +xxx-----xxx +xxx|...|xxx +xxx|...|xxx +----...---- +|.........| +|.........| +|.........| +-----------]], contents = function(m) des.region({ region={5,5,5,5}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- T-shaped, rot 1 + function() + des.map({ map = [[ +-----xxx +|...|xxx +|...|xxx +|...---- +|......| +|......| +|......| +|...---- +|...|xxx +|...|xxx +-----xxx]], contents = function(m) des.region({ region={2,2,2,2}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- T-shaped, rot 2 + function() + des.map({ map = [[ +----------- +|.........| +|.........| +|.........| +----...---- +xxx|...|xxx +xxx|...|xxx +xxx-----xxx]], contents = function(m) des.region({ region={2,2,2,2}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- T-shaped, rot 3 + function() + des.map({ map = [[ +xxx----- +xxx|...| +xxx|...| +----...| +|......| +|......| +|......| +----...| +xxx|...| +xxx|...| +xxx-----]], contents = function(m) des.region({ region={5,5,5,5}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- S-shaped + function() + des.map({ map = [[ +-----xxx +|...|xxx +|...|xxx +|...---- +|......| +|......| +|......| +----...| +xxx|...| +xxx|...| +xxx-----]], contents = function(m) des.region({ region={2,2,2,2}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- S-shaped, rot 1 + function() + des.map({ map = [[ +xxx-------- +xxx|......| +xxx|......| +----......| +|......---- +|......|xxx +|......|xxx +--------xxx]], contents = function(m) des.region({ region={5,5,5,5}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- Z-shaped + function() + des.map({ map = [[ +xxx----- +xxx|...| +xxx|...| +----...| +|......| +|......| +|......| +|...---- +|...|xxx +|...|xxx +-----xxx]], contents = function(m) des.region({ region={5,5,5,5}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- Z-shaped, rot 1 + function() + des.map({ map = [[ +--------xxx +|......|xxx +|......|xxx +|......---- +----......| +xxx|......| +xxx|......| +xxx--------]], contents = function(m) des.region({ region={2,2,2,2}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- Cross + function() + des.map({ map = [[ +xxx-----xxx +xxx|...|xxx +xxx|...|xxx +----...---- +|.........| +|.........| +|.........| +----...---- +xxx|...|xxx +xxx|...|xxx +xxx-----xxx]], contents = function(m) des.region({ region={6,6,6,6}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + + -- Four-leaf clover + function() + des.map({ map = [[ +-----x----- +|...|x|...| +|...---...| +|.........| +---.....--- +xx|.....|xx +---.....--- +|.........| +|...---...| +|...|x|...| +-----x-----]], contents = function(m) des.region({ region={6,6,6,6}, type="ordinary", irregular=true, prefilled=true }); end }); + end, + +}; + +local total_frequency = 0; +for i = 1, #themerooms do + local t = type(themerooms[i]); + if (t == "table") then + total_frequency = total_frequency + themerooms[i].frequency; + elseif (t == "function") then + total_frequency = total_frequency + 1; + end +end + +if (total_frequency == 0) then + error("Theme rooms total_frequency == 0"); +end + +function themerooms_generate() + local pick = nh.rn2(total_frequency); + for i = 1, #themerooms do + local t = type(themerooms[i]); + if (t == "table") then + pick = pick - themerooms[i].frequency; + if (pick < 0) then + themerooms[i].contents(); + return; + end + elseif (t == "function") then + pick = pick - 1; + if (pick < 0) then + themerooms[i](); + return; + end + end + end +end diff --git a/doc/fixes37.0 b/doc/fixes37.0 index f856561e4..21abbc8b2 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -271,6 +271,7 @@ include more skill information in ^X output when dual-wielding item-using monsters will zap wand of undead turning at corpse-wielding hero when the corpse is harmful boiling a pool or fountain now creates a temporary cloud of steam +random themed rooms in the dungeons of doom Platform- and/or Interface-Specific New Features diff --git a/include/decl.h b/include/decl.h index f9d57e774..b86254fb2 100644 --- a/include/decl.h +++ b/include/decl.h @@ -1136,6 +1136,8 @@ struct instance_globals { struct sp_coder *coder; xchar xstart, ystart; xchar xsize, ysize; + boolean in_mk_themerooms; + boolean themeroom_failed; /* spells.c */ int spl_sortmode; /* index into spl_sortchoices[] */ diff --git a/include/dungeon.h b/include/dungeon.h index 75577dbfa..34ae5afdf 100644 --- a/include/dungeon.h +++ b/include/dungeon.h @@ -57,6 +57,7 @@ typedef struct dungeon { /* basic dungeon identifier */ char dname[24]; /* name of the dungeon (eg. "Hell") */ char proto[15]; /* name of prototype file (eg. "tower") */ char fill_lvl[15]; /* name of "fill" level protype file */ + char themerms[15]; /* lua file name containing themed rooms */ char boneid; /* character to id dungeon in bones files */ d_flags flags; /* dungeon flags */ xchar entry_lev; /* entry level */ @@ -64,6 +65,7 @@ typedef struct dungeon { /* basic dungeon identifier */ xchar dunlev_ureached; /* how deep you have been in this dungeon */ int ledger_start, /* the starting depth in "real" terms */ depth_start; /* the starting depth in "logical" terms */ + lua_State *themelua; /* themerms compiled lua */ } dungeon; /* diff --git a/include/extern.h b/include/extern.h index 5d3106d32..7e3b78ad2 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1446,6 +1446,7 @@ E int FDECL(somex, (struct mkroom *)); E int FDECL(somey, (struct mkroom *)); E boolean FDECL(inside_room, (struct mkroom *, XCHAR_P, XCHAR_P)); E boolean FDECL(somexy, (struct mkroom *, coord *)); +E boolean FDECL(somexyspace, (struct mkroom *, coord *)); E void FDECL(mkundead, (coord *, BOOLEAN_P, int)); E struct permonst *NDECL(courtmon); E void FDECL(save_rooms, (NHFILE *)); @@ -2478,6 +2479,7 @@ E void FDECL(sysopt_seduce_set, (int)); /* ### sp_lev.c ### */ #if !defined(CROSSCOMPILE) || defined(CROSSCOMPILE_TARGET) +E void NDECL(create_des_coder); E struct mapfragment *FDECL(mapfrag_fromstr, (char *)); E void FDECL(mapfrag_free, (struct mapfragment **)); E schar FDECL(mapfrag_get, (struct mapfragment *, int, int)); diff --git a/include/mkroom.h b/include/mkroom.h index 286c0c68d..6bc6204ef 100644 --- a/include/mkroom.h +++ b/include/mkroom.h @@ -19,6 +19,7 @@ struct mkroom { schar fdoor; /* index for the first door of the room */ schar nsubrooms; /* number of subrooms */ boolean irregular; /* true if room is non-rectangular */ + schar roomnoidx; struct mkroom *sbrooms[MAX_SUBROOMS]; /* Subrooms pointers */ struct monst *resident; /* priest/shopkeeper/guard for this room */ }; @@ -48,6 +49,7 @@ struct shclass { /* values for rtype in the room definition structure */ enum roomtype_types { OROOM = 0, /* ordinary room */ + THEMEROOM = 1, /* like OROOM, but never converted to special room */ COURT = 2, /* contains a throne */ SWAMP = 3, /* contains pools */ VAULT = 4, /* detached room usually reached via teleport trap */ diff --git a/include/patchlevel.h b/include/patchlevel.h index 6ef56a27d..614c73bd5 100644 --- a/include/patchlevel.h +++ b/include/patchlevel.h @@ -14,7 +14,7 @@ * Incrementing EDITLEVEL can be used to force invalidation of old bones * and save files. */ -#define EDITLEVEL 16 +#define EDITLEVEL 17 #define COPYRIGHT_BANNER_A "NetHack, Copyright 1985-2020" #define COPYRIGHT_BANNER_B \ diff --git a/src/decl.c b/src/decl.c index 28b501d34..a6de86e35 100644 --- a/src/decl.c +++ b/src/decl.c @@ -638,6 +638,8 @@ const struct instance_globals g_init = { UNDEFINED_VALUE, /* ystart */ UNDEFINED_VALUE, /* xsize */ UNDEFINED_VALUE, /* ysize */ + FALSE, /* in_mk_themerooms */ + FALSE, /* themeroom_failed */ /* spells.c */ 0, /* spl_sortmode */ diff --git a/src/dungeon.c b/src/dungeon.c index d588a8c8d..1bbcaafc2 100644 --- a/src/dungeon.c +++ b/src/dungeon.c @@ -842,6 +842,7 @@ init_dungeons() i = 0; while (lua_next(L, tidx) != 0) { char *dgn_name, *dgn_bonetag, *dgn_protoname, *dgn_fill; + char *dgn_themerms; int dgn_base, dgn_range, dgn_align, dgn_entry, dgn_chance, dgn_flags; if (!lua_istable(L, -1)) @@ -858,6 +859,7 @@ init_dungeons() dgn_chance = get_table_int_opt(L, "chance", 100); dgn_flags = get_dgn_flags(L); dgn_fill = get_table_str_opt(L, "lvlfill", emptystr); + dgn_themerms = get_table_str_opt(L, "themerooms", emptystr); debugpline4("DUNGEON[%i]: %s, base=(%i,%i)", i, dgn_name, dgn_base, dgn_range); @@ -870,6 +872,7 @@ init_dungeons() free((genericptr_t) dgn_bonetag); free((genericptr_t) dgn_protoname); free((genericptr_t) dgn_fill); + free((genericptr_t) dgn_themerms); continue; } @@ -1023,10 +1026,13 @@ init_dungeons() Strcpy(g.dungeons[i].fill_lvl, dgn_fill); /* FIXME: fill_lvl len */ Strcpy(g.dungeons[i].dname, dgn_name); /* FIXME: dname length */ Strcpy(g.dungeons[i].proto, dgn_protoname); /* FIXME: proto length */ + Strcpy(g.dungeons[i].themerms, dgn_themerms); /* FIXME: length */ + g.dungeons[i].themelua = (lua_State *) 0; g.dungeons[i].boneid = *dgn_bonetag ? *dgn_bonetag : 0; free((genericptr) dgn_fill); /* free((genericptr) dgn_protoname); -- stored in pd.tmpdungeon[] */ free((genericptr) dgn_bonetag); + free((genericptr) dgn_themerms); if (dgn_range) g.dungeons[i].num_dunlevs = (xchar) rn1(dgn_range, dgn_base); diff --git a/src/mklev.c b/src/mklev.c index afef6ea10..be812e1aa 100644 --- a/src/mklev.c +++ b/src/mklev.c @@ -9,6 +9,9 @@ /* croom->lx etc are schar (width <= int), so % arith ensures that */ /* conversion of result to int is reasonable */ +static boolean FDECL(generate_stairs_room_good, (struct mkroom *, int)); +static struct mkroom *NDECL(generate_stairs_find_room); +static void NDECL(generate_stairs); static void FDECL(mkfount, (int, struct mkroom *)); static boolean FDECL(find_okay_roompos, (struct mkroom *, coord *)); static void FDECL(mksink, (struct mkroom *)); @@ -94,9 +97,14 @@ xchar xl, yl, xh, yh; return; } +/* Sort rooms on the level so they're ordered from left to right on the map. + makecorridors() by default links rooms N and N+1 */ void sort_rooms() { + int i, x, y; + int ri[MAXNROFROOMS+1]; + #if defined(SYSV) || defined(DGUX) #define CAST_nroom (unsigned) g.nroom #else @@ -104,6 +112,17 @@ sort_rooms() #endif qsort((genericptr_t) g.rooms, CAST_nroom, sizeof (struct mkroom), do_comp); #undef CAST_nroom + + /* Update the roomnos on the map */ + for (i = 0; i < g.nroom; i++) + ri[g.rooms[i].roomnoidx] = i; + + for (x = 1; x < COLNO; x++) + for (y = 0; y < ROWNO; y++) { + int rno = levl[x][y].roomno; + if (rno >= ROOMOFFSET && rno < MAXNROFROOMS+1) + levl[x][y].roomno = ri[rno - ROOMOFFSET] + ROOMOFFSET; + } } static void @@ -140,6 +159,7 @@ boolean is_room; } else croom->rlit = 0; + croom->roomnoidx = (croom - g.rooms); croom->lx = lowx; croom->hx = hix; croom->ly = lowy; @@ -156,6 +176,7 @@ boolean is_room; croom->nsubrooms = 0; croom->sbrooms[0] = (struct mkroom *) 0; if (!special) { + croom->needjoining = TRUE; for (x = lowx - 1; x <= hix + 1; x++) for (y = lowy - 1; y <= hiy + 1; y += (hiy - lowy + 2)) { levl[x][y].typ = HWALL; @@ -222,6 +243,27 @@ static void makerooms() { boolean tried_vault = FALSE; + int themeroom_tries = 0; + boolean dothemes = (g.dungeons[u.uz.dnum].themelua != NULL); + char *fname = g.dungeons[u.uz.dnum].themerms; + + if (*fname && !g.dungeons[u.uz.dnum].themelua) { + g.dungeons[u.uz.dnum].themelua = nhl_init(); + if (g.dungeons[u.uz.dnum].themelua) { + if (!nhl_loadlua(g.dungeons[u.uz.dnum].themelua, fname)) { + /* loading lua failed, don't use themed rooms */ + g.dungeons[u.uz.dnum].themerms[0] = '\0'; + lua_close(g.dungeons[u.uz.dnum].themelua); + g.dungeons[u.uz.dnum].themelua = NULL; + } else { + dothemes = TRUE; + } + } + } + + if (dothemes) { + create_des_coder(); + } /* make rooms until satisfied */ /* rnd_rect() will returns 0 if no more rects are available... */ @@ -233,10 +275,26 @@ makerooms() g.vault_y = g.rooms[g.nroom].ly; g.rooms[g.nroom].hx = -1; } - } else if (!create_room(-1, -1, -1, -1, -1, -1, OROOM, -1)) - return; + } else { + if (dothemes) { + g.in_mk_themerooms = TRUE; + g.themeroom_failed = FALSE; + lua_getglobal(g.dungeons[u.uz.dnum].themelua, "themerooms_generate"); + lua_call(g.dungeons[u.uz.dnum].themelua, 0, 0); + g.in_mk_themerooms = FALSE; + if (g.themeroom_failed && ((themeroom_tries++ > 10) || (g.nroom >= (MAXNROFROOMS / 6)))) + break; + } else { + if (!create_room(-1, -1, -1, -1, -1, -1, OROOM, -1)) + break;; + } + } + } + if (dothemes) { + wallification(1, 0, COLNO - 1, ROWNO - 1); + free(g.coder); + g.coder = NULL; } - return; } static void @@ -252,6 +310,9 @@ boolean nxcor; croom = &g.rooms[a]; troom = &g.rooms[b]; + if (!croom->needjoining || !troom->needjoining) + return; + /* find positions cc and tt for doors in croom and troom and direction for a corridor between them */ @@ -711,25 +772,7 @@ makelevel() makerooms(); sort_rooms(); - /* construct stairs (up and down in different rooms if possible) */ - croom = &g.rooms[rn2(g.nroom)]; - if (!Is_botlevel(&u.uz)) - mkstairs(somex(croom), somey(croom), 0, croom); /* down */ - if (g.nroom > 1) { - troom = croom; - croom = &g.rooms[rn2(g.nroom - 1)]; - if (croom == troom) - croom++; - } - - if (u.uz.dlevel != 1) { - xchar sx, sy; - do { - sx = somex(croom); - sy = somey(croom); - } while (occupied(sx, sy)); - mkstairs(sx, sy, 1, croom); /* up */ - } + generate_stairs(); /* up and down stairs */ branchp = Is_branchlev(&u.uz); /* possible dungeon branch */ room_threshold = branchp ? 4 : 3; /* minimum number of rooms needed @@ -807,7 +850,10 @@ makelevel() /* for each room: put things inside */ for (croom = g.rooms; croom->hx > 0; croom++) { int trycnt = 0; - if (croom->rtype != OROOM) + coord pos; + if (croom->rtype != OROOM && croom->rtype != THEMEROOM) + continue; + if (!croom->needfill) continue; /* put a sleeping monster inside */ @@ -816,13 +862,11 @@ makelevel() while a monster was on the stairs. Conclusion: we have to check for monsters on the stairs anyway. */ - if (u.uhave.amulet || !rn2(3)) { - x = somex(croom); - y = somey(croom); - tmonst = makemon((struct permonst *) 0, x, y, MM_NOGRP); + if ((u.uhave.amulet || !rn2(3)) && somexyspace(croom, &pos)) { + tmonst = makemon((struct permonst *) 0, pos.x, pos.y, MM_NOGRP); if (tmonst && tmonst->data == &mons[PM_GIANT_SPIDER] - && !occupied(x, y)) - (void) maketrap(x, y, WEB); + && !occupied(pos.x, pos.y)) + (void) maketrap(pos.x, pos.y, WEB); } /* put traps and mimics inside */ x = 8 - (level_difficulty() / 6); @@ -830,8 +874,8 @@ makelevel() x = 2; while (!rn2(x) && (++trycnt < 1000)) mktrap(0, 0, croom, (coord *) 0); - if (!rn2(3)) - (void) mkgold(0L, somex(croom), somey(croom)); + if (!rn2(3) && somexyspace(croom, &pos)) + (void) mkgold(0L, pos.x, pos.y); if (Is_rogue_level(&u.uz)) goto skip_nonrogue; if (!rn2(10)) @@ -847,18 +891,18 @@ makelevel() mkgrave(croom); /* put statues inside */ - if (!rn2(20)) + if (!rn2(20) && somexyspace(croom, &pos)) (void) mkcorpstat(STATUE, (struct monst *) 0, - (struct permonst *) 0, somex(croom), - somey(croom), CORPSTAT_INIT); + (struct permonst *) 0, pos.x, + pos.y, CORPSTAT_INIT); /* put box/chest inside; * 40% chance for at least 1 box, regardless of number * of rooms; about 5 - 7.5% for 2 boxes, least likely * when few rooms; chance for 3 or more is negligible. */ - if (!rn2(g.nroom * 5 / 2)) - (void) mksobj_at((rn2(3)) ? LARGE_BOX : CHEST, somex(croom), - somey(croom), TRUE, FALSE); + if (!rn2(g.nroom * 5 / 2) && somexyspace(croom, &pos)) + (void) mksobj_at((rn2(3)) ? LARGE_BOX : CHEST, + pos.x, pos.y, TRUE, FALSE); /* maybe make some graffiti */ if (!rn2(27 + 3 * abs(depth(&u.uz)))) { @@ -867,8 +911,9 @@ makelevel() if (mesg) { do { - x = somex(croom); - y = somey(croom); + somexyspace(croom, &pos); + x = pos.x; + y = pos.y; } while (levl[x][y].typ != ROOM && !rn2(40)); if (!(IS_POOL(levl[x][y].typ) || IS_FURNITURE(levl[x][y].typ))) @@ -877,15 +922,15 @@ makelevel() } skip_nonrogue: - if (!rn2(3)) { - (void) mkobj_at(0, somex(croom), somey(croom), TRUE); + if (!rn2(3) && somexyspace(croom, &pos)) { + (void) mkobj_at(0, pos.x, pos.y, TRUE); tryct = 0; while (!rn2(5)) { if (++tryct > 100) { impossible("tryct overflow4"); break; } - (void) mkobj_at(0, somex(croom), somey(croom), TRUE); + (void) mkobj_at(0, pos.x, pos.y, TRUE); } } } @@ -1124,7 +1169,7 @@ coord *mp; do croom = &g.rooms[rn2(g.nroom)]; while ((croom == g.dnstairs_room || croom == g.upstairs_room - || croom->rtype != OROOM) && (++tryct < 100)); + || (croom->rtype != OROOM && croom->rtype != THEMEROOM)) && (++tryct < 100)); } else croom = &g.rooms[rn2(g.nroom)]; @@ -1133,6 +1178,7 @@ coord *mp; impossible("Can't place branch!"); } while ((occupied(mp->x, mp->y) || (levl[mp->x][mp->y].typ != CORR + && levl[mp->x][mp->y].typ != ICE && levl[mp->x][mp->y].typ != ROOM)) && (++cnt < 1000)); } return croom; @@ -1250,6 +1296,10 @@ xchar x, y; boolean near_door = bydoor(x, y); return ((levl[x][y].typ == HWALL || levl[x][y].typ == VWALL) + && (isok(x-1,y) && !IS_ROCK(levl[x-1][y].typ) + || isok(x+1,y) && !IS_ROCK(levl[x+1][y].typ) + || isok(x,y-1) && !IS_ROCK(levl[x][y-1].typ) + || isok(x,y+1) && !IS_ROCK(levl[x][y+1].typ)) && g.doorindex < DOORMAX && !near_door); } @@ -1574,6 +1624,83 @@ struct mkroom *croom; levl[x][y].ladder = up ? LA_UP : LA_DOWN; } +/* is room a good one to generate up or down stairs in? */ +/* phase values, smaller allows for more relaxed criteria: + 2 == no relaxed criteria + 1 == allow a themed room + 0 == allow same room as existing up/downstairs + -1 == allow an unjoined room +*/ +static boolean +generate_stairs_room_good(croom, phase) +struct mkroom *croom; +int phase; +{ + return (croom && (croom->needjoining || (phase < 0)) + && ((croom != g.dnstairs_room && croom != g.upstairs_room) + || phase < 1) + && (croom->rtype == OROOM + || ((phase < 2) || croom->rtype == THEMEROOM))); +} + +/* find a good room to generate an up or down stairs in */ +static struct mkroom * +generate_stairs_find_room() +{ + struct mkroom *croom; + int i, phase, tryct = 0; + + if (!g.nroom) + return (struct mkroom *) 0; + + for (phase = 2; phase > -1; phase--) { + do { + croom = &g.rooms[rn2(g.nroom)]; + } while (!generate_stairs_room_good(croom, phase) && (tryct++ < 50)); + if (tryct < 50) + return croom; + } + + for (phase = 2; phase > -2; phase--) { + for (i = 0; i < g.nroom; i++) { + croom = &g.rooms[i]; + if (generate_stairs_room_good(croom, phase)) + return croom; + } + } + + croom = &g.rooms[rn2(g.nroom)]; + return croom; +} + +/* construct stairs up and down within the same branch, + up and down in different rooms if possible */ +static void +generate_stairs() +{ + struct mkroom *croom = generate_stairs_find_room(); + coord pos; + + if (!Is_botlevel(&u.uz)) { + if (!somexyspace(croom, &pos)) { + pos.x = somex(croom); + pos.y = somey(croom); + } + mkstairs(pos.x, pos.y, 0, croom); /* down */ + } + + if (g.nroom > 1) + croom = generate_stairs_find_room(); + + if (u.uz.dlevel != 1) { + if (!somexyspace(croom, &pos)) { + pos.x = somex(croom); + pos.y = somey(croom); + } + mkstairs(pos.x, pos.y, 1, croom); /* up */ + } +} + static void mkfount(mazeflag, croom) int mazeflag; diff --git a/src/mkmaze.c b/src/mkmaze.c index 719f26a67..014bdd3fd 100644 --- a/src/mkmaze.c +++ b/src/mkmaze.c @@ -996,6 +996,7 @@ const char *s; if (*protofile) { check_ransacked(protofile); Strcat(protofile, LEV_EXT); + g.in_mk_themerooms = FALSE; if (load_special(protofile)) { /* some levels can end up with monsters on dead mon list, including light source monsters */ diff --git a/src/mkroom.c b/src/mkroom.c index c8e3b677e..ffb076658 100644 --- a/src/mkroom.c +++ b/src/mkroom.c @@ -713,6 +713,23 @@ coord *c; return TRUE; } +boolean +somexyspace(croom, c) +struct mkroom *croom; +coord *c; +{ + int trycnt = 0; + boolean okay; + + do { + okay = somexy(croom, c) && isok(c->x, c->y) && !occupied(c->x, c->y) + && (levl[c->x][c->y].typ == ROOM + || levl[c->x][c->y].typ == CORR + || levl[c->x][c->y].typ == ICE); + } while (trycnt++ < 100 && !okay); + return okay; +} + /* * Search for a special room given its type (zoo, court, etc...) * Special values : diff --git a/src/save.c b/src/save.c index f7af31a9d..cf95c198b 100644 --- a/src/save.c +++ b/src/save.c @@ -1079,11 +1079,17 @@ free_dungeons() { #ifdef FREE_ALL_MEMORY NHFILE tnhfp; + int i; zero_nhfile(&tnhfp); /* also sets fd to -1 */ tnhfp.mode = FREEING; savelevchn(&tnhfp); save_dungeon(&tnhfp, FALSE, TRUE); + for (i = 0; i < g.n_dgns; i++) + if (g.dungeons[i].themelua) { + lua_close(g.dungeons[i].themelua); + g.dungeons[i].themelua = (lua_State *) 0; + } #endif return; } diff --git a/src/sp_lev.c b/src/sp_lev.c index 48177b0fc..2847f3497 100755 --- a/src/sp_lev.c +++ b/src/sp_lev.c @@ -23,7 +23,6 @@ typedef void FDECL((*select_iter_func), (int, int, genericptr)); extern void FDECL(mkmap, (lev_init *)); -static void NDECL(create_des_coder); static void NDECL(solidify_map); static void FDECL(lvlfill_maze_grid, (int, int, int, int, SCHAR_P)); static void FDECL(lvlfill_solid, (SCHAR_P, SCHAR_P)); @@ -1311,6 +1310,10 @@ boolean vault; register int x, y, hix = *lowx + *ddx, hiy = *lowy + *ddy; register struct rm *lev; int xlim, ylim, ymax; + xchar 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); @@ -1327,6 +1330,10 @@ boolean vault; if (hix <= *lowx || hiy <= *lowy) return FALSE; + if (g.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) @@ -1345,6 +1352,8 @@ boolean vault; } if (!rn2(3)) return FALSE; + if (g.in_mk_themerooms) + return FALSE; if (x < *lowx) *lowx = x + xlim + 1; else @@ -1359,6 +1368,11 @@ boolean vault; } *ddx = hix - *lowx; *ddy = hiy - *lowy; + + if (g.in_mk_themerooms && (s_lowx != *lowx) && (s_ddx != *ddx) + && (s_lowy != *lowy) && (s_ddy != *ddy)) + return FALSE; + return TRUE; } @@ -1462,6 +1476,7 @@ xchar rtype, rlit; r2.hy = yabs + htmp; } else { /* Only some parameters are random */ int rndpos = 0; + xchar dx, dy; if (xtmp < 0 && ytmp < 0) { /* Position is RANDOM */ xtmp = rnd(5); @@ -1518,6 +1533,12 @@ xchar rtype, rlit; 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 ? */ @@ -3703,9 +3724,8 @@ static const struct { const char *name; int type; } room_types[] = { - /* for historical reasons, room types are not contiguous numbers */ - /* (type 1 is skipped) */ { "ordinary", OROOM }, + { "themed", THEMEROOM }, { "throne", COURT }, { "swamp", SWAMP }, { "vault", VAULT }, @@ -3764,6 +3784,9 @@ lua_State *L; { create_des_coder(); + if (g.in_mk_themerooms && g.themeroom_failed) + return 0; + lcheck_param_table(L); if (g.coder->n_subroom > MAX_NESTED_ROOMS) { @@ -3802,7 +3825,7 @@ lua_State *L; 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); - tmproom.filled = get_table_int_opt(L, "filled", 1); + tmproom.filled = get_table_int_opt(L, "filled", g.in_mk_themerooms ? 0 : 1); tmproom.joined = get_table_int_opt(L, "joined", 1); if (!g.coder->failed_room[g.coder->n_subroom - 1]) { @@ -3822,6 +3845,8 @@ lua_State *L; spo_endroom(g.coder); return 0; } + if (g.in_mk_themerooms) + g.themeroom_failed = TRUE; } /* failed to create parent room, so fail this too */ } g.coder->tmproomlist[g.coder->n_subroom] = (struct mkroom *) 0; @@ -3829,6 +3854,8 @@ lua_State *L; g.coder->n_subroom++; update_croom(); spo_endroom(g.coder); + if (g.in_mk_themerooms) + g.themeroom_failed = TRUE; return 0; } @@ -5628,7 +5655,7 @@ lua_State *L; /* for an ordinary room, `prefilled' is a flag to force an actual room to be created (such rooms are used to control placement of migrating monster arrivals) */ - room_not_needed = (rtype == OROOM && !irregular && !prefilled); + room_not_needed = (rtype == OROOM && !irregular && !prefilled && !g.in_mk_themerooms); if (room_not_needed || g.nroom >= MAXNROFROOMS) { region tmpregion; if (!room_not_needed) @@ -5668,6 +5695,9 @@ lua_State *L; #endif } + if (g.in_mk_themerooms && prefilled) + troom->needfill = 1; + if (!room_not_needed) { if (g.coder->n_subroom > 1) impossible("region as subroom"); @@ -6009,9 +6039,14 @@ TODO: g.coder->croom needs to be updated struct mapfragment *mf; int argc = lua_gettop(L); boolean has_contents = FALSE; + int tryct = 0; + int ox, oy; create_des_coder(); + if (g.in_mk_themerooms && g.themeroom_failed) + return 0; + if (argc == 1 && lua_type(L, 1) == LUA_TSTRING) { char *tmpstr = dupstr(luaL_checkstring(L, 1)); lr = tb = CENTER; @@ -6040,10 +6075,34 @@ TODO: g.coder->croom needs to be updated return 0; } + ox = x; + oy = y; +redo_maploc: + g.xsize = mf->wid; g.ysize = mf->hei; if (lr == -1 && tb == -1) { + if (g.in_mk_themerooms && (ox == -1 || oy == -1)) { + if (ox == -1) { + if (g.coder->croom) { + x = somex(g.coder->croom) - mf->wid; + if (x < 1) x = 1; + } else { + x = 1 + rn2(COLNO - 1 - mf->wid); + } + } + + if (oy == -1) { + if (g.coder->croom) { + y = somey(g.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 (g.coder->croom) { @@ -6102,6 +6161,10 @@ TODO: g.coder->croom needs to be updated } if (g.ystart < 0 || g.ystart + g.ysize > ROWNO) { + if (g.in_mk_themerooms) { + g.themeroom_failed = TRUE; + goto skipmap; + } /* try to move the start a bit */ g.ystart += (g.ystart > 0) ? -2 : 2; if (g.ysize == ROWNO) @@ -6117,6 +6180,32 @@ TODO: g.coder->croom needs to be updated } else { xchar mptyp; + /* Themed rooms should never overwrite anything */ + if (g.in_mk_themerooms) { + boolean isokp = TRUE; + for (y = g.ystart - 1; y < min(ROWNO, g.ystart + g.ysize) + 1; y++) + for (x = g.xstart - 1; x < min(COLNO, g.xstart + g.xsize) + 1; x++) { + if (!isok(x, y)) { + isokp = FALSE; + } else if (y < g.ystart || y >= (g.ystart + g.ysize) + || x < g.xstart || x >= (g.xstart + g.xsize)) { + if (levl[x][y].typ != STONE) isokp = FALSE; + if (levl[x][y].roomno != NO_ROOM) isokp = FALSE; + } else { + mptyp = mapfrag_get(mf, (x - g.xstart), (y - g.ystart)); + if (mptyp >= MAX_TYPE) continue; + if (levl[x][y].typ != STONE && levl[x][y].typ != mptyp) isokp = FALSE; + if (levl[x][y].roomno != NO_ROOM) isokp = FALSE; + } + if (!isokp) { + if ((tryct++ < 100) && ((lr == -1) || (tb == -1))) + goto redo_maploc; + g.themeroom_failed = TRUE; + goto skipmap; + } + } + } + /* Load the map */ for (y = g.ystart; y < min(ROWNO, g.ystart + g.ysize); y++) for (x = g.xstart; x < min(COLNO, g.xstart + g.xsize); x++) { @@ -6158,14 +6247,16 @@ TODO: g.coder->croom needs to be updated else if (splev_init_present && levl[x][y].typ == ICE) levl[x][y].icedpool = icedpools ? ICED_POOL : ICED_MOAT; } - if (g.coder->lvl_is_joined) + if (g.coder->lvl_is_joined && !g.in_mk_themerooms) remove_rooms(g.xstart, g.ystart, g.xstart + g.xsize, g.ystart + g.ysize); } +skipmap: + mapfrag_free(&mf); - if (has_contents) { + if (has_contents && !(g.in_mk_themerooms && g.themeroom_failed)) { l_push_wid_hei_table(L, g.xsize, g.ysize); lua_call(L, 1, 0); } @@ -6290,7 +6381,7 @@ lua_State *L; lua_setglobal(L, "des"); } -static void +void create_des_coder() { if (!g.coder) diff --git a/sys/unix/Makefile.top b/sys/unix/Makefile.top index 87e6def79..5fb067907 100644 --- a/sys/unix/Makefile.top +++ b/sys/unix/Makefile.top @@ -86,7 +86,7 @@ DATHELP = help hh cmdhelp keyhelp history opthelp wizhelp SPEC_LEVS = asmodeus.lua baalz.lua bigrm-*.lua castle.lua fakewiz?.lua \ juiblex.lua knox.lua medusa-?.lua minend-?.lua minefill.lua \ minetn-?.lua oracle.lua orcus.lua sanctum.lua soko?-?.lua \ - tower?.lua valley.lua wizard?.lua nhlib.lua \ + tower?.lua valley.lua wizard?.lua nhlib.lua themerms.lua \ astral.lua air.lua earth.lua fire.lua water.lua QUEST_LEVS = ???-goal.lua ???-fil?.lua ???-loca.lua ???-strt.lua