From 3605f18a8e05b58b7e08e5631804c50057d527eb Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Thu, 15 Sep 2022 18:08:32 +0300 Subject: [PATCH] Split themeroom shape from themeroom contents Previously, the tetris-shaped rooms were always either normal rooms, or turned into shops or other special rooms in NetHack core. Now, the themed room lua code first picks the themed room (which can be a themed or shaped), and some of those will then pick a random filling (eg. ice floor, traps, corpses, 3 altars). Adds a new lua binding to create a selection picking locations in current room. The content-function in special level regions now get passed the room data as a parameter. --- dat/themerms.lua | 396 +++++++++++++++++++++++++---------------------- doc/lua.adoc | 14 ++ include/extern.h | 1 + src/nhlsel.c | 24 +++ src/sp_lev.c | 22 ++- 5 files changed, 270 insertions(+), 187 deletions(-) diff --git a/dat/themerms.lua b/dat/themerms.lua index 4b2aeb05d..1c97015f2 100644 --- a/dat/themerms.lua +++ b/dat/themerms.lua @@ -17,6 +17,139 @@ -- core calls themerooms_generate() multiple times per level -- to generate a single themed room. +themeroom_fills = { + + -- Ice room + function(rm) + local ice = selection.room(); + des.terrain(ice, "I"); + if (percent(25)) then + local mintime = 1000 - (nh.level_difficulty() * 100); + local ice_melter = function(x,y) + nh.start_timer_at(x,y, "melt-ice", mintime + nh.rn2(1000)); + end; + ice:iterate(ice_melter); + end + end, + + -- Boulder room + { + mindiff = 4, + contents = function(rm) + local locs = selection.room():percentage(30); + local func = function(x,y) + if (percent(50)) then + des.object("boulder", x, y); + else + des.trap("rolling boulder", x, y); + end + end; + locs:iterate(func); + end + }, + + -- Spider nest + function(rm) + local spooders = nh.level_difficulty() > 8; + local locs = selection.room():percentage(30); + local func = function(x,y) + des.trap({ type = "web", x = x, y = y, + spider_on_web = spooders and percent(80) }); + end + locs:iterate(func); + end, + + -- Trap room + function(rm) + local traps = { "arrow", "dart", "falling rock", "bear", + "land mine", "sleep gas", "rust", + "anti magic" }; + shuffle(traps); + local locs = selection.room():percentage(30); + local func = function(x,y) + des.trap(traps[1], x, y); + end + locs:iterate(func); + end, + + -- Buried treasure + function(rm) + des.object({ id = "chest", buried = true, contents = function() + for i = 1, d(3,4) do + des.object(); + end + end }); + end, + + -- Massacre + function(rm) + 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" }; + local idx = math.random(#mon); + for i = 1, d(5,5) do + if (percent(10)) then idx = math.random(#mon); end + des.object({ id = "corpse", montype = mon[idx] }); + end + end, + + -- Statuary + function(rm) + for i = 1, d(5,5) do + des.object({ id = "statue" }); + end + for i = 1, d(3) do + des.trap("statue"); + end + end, + + -- Light source + { + eligible = function(rm) return rm.lit == false; end, + contents = function(rm) + des.object({ id = "oil lamp", lit = true }); + end + }, + + -- Temple of the gods + function(rm) + des.altar({ align = align[1] }); + des.altar({ align = align[2] }); + des.altar({ align = align[3] }); + end, + + -- Ghost of an Adventurer + function(rm) + local loc = selection.room():rndcoord(0); + des.monster({ id = "ghost", asleep = true, waiting = true, coord = loc }); + if percent(65) then + des.object({ id = "dagger", coord = loc, buc = "not-blessed" }); + end + if percent(55) then + des.object({ class = ")", coord = loc, buc = "not-blessed" }); + end + if percent(45) then + des.object({ id = "bow", coord = loc, buc = "not-blessed" }); + des.object({ id = "arrow", coord = loc, buc = "not-blessed" }); + end + if percent(65) then + des.object({ class = "[", coord = loc, buc = "not-blessed" }); + end + if percent(20) then + des.object({ class = "=", coord = loc, buc = "not-blessed" }); + end + if percent(20) then + des.object({ class = "?", coord = loc, buc = "not-blessed" }); + end + end, + +}; + themerooms = { { -- the "default" room @@ -71,114 +204,26 @@ themerooms = { }); end, - -- Ice room - function() - des.room({ type = "themed", filled = 1, - contents = function() - local ice = selection.floodfill(1,1); - des.terrain(ice, "I"); - if (percent(25)) then - local mintime = 1000 - (nh.level_difficulty() * 100); - local ice_melter = function(x,y) - nh.start_timer_at(x,y, "melt-ice", mintime + nh.rn2(1000)); - end; - ice:iterate(ice_melter); - end - end - }); - end, - - -- Boulder room { - mindiff = 4, + frequency = 6, contents = function() - des.room({ type = "themed", - contents = function(rm) - for x = 0, rm.width - 1 do - for y = 0, rm.height - 1 do - if (percent(30)) then - if (percent(50)) then - des.object("boulder"); - else - des.trap("rolling boulder"); - end - end - end - end - end - }); + des.room({ type = "themed", contents = themeroom_fill }); end }, - -- Spider nest - function() - des.room({ type = "themed", - contents = function(rm) - local spooders = nh.level_difficulty() > 8; - for x = 0, rm.width - 1 do - for y = 0, rm.height - 1 do - if (percent(30)) then - des.trap({ type = "web", x = x, y = y, - spider_on_web = spooders and percent(80) }); - end - end - end - end - }); - end, + { + frequency = 2, + contents = function() + des.room({ type = "themed", lit = 0, contents = themeroom_fill }); + 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 - 1 do - for y = 0, rm.height - 1 do - if (percent(30)) 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, + { + frequency = 2, + contents = function() + des.room({ type = "themed", filled = 1, contents = themeroom_fill }); + end + }, -- Pillars function() @@ -198,70 +243,6 @@ themerooms = { }); 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, - - -- Ghost of an Adventurer - function() - des.room({ type = "themed", lit = 0, - contents = function(rm) - local px = nh.rn2(rm.width); - local py = nh.rn2(rm.height); - des.monster({ id = "ghost", asleep = true, waiting = true, coord = {px,py} }); - if percent(65) then - des.object({ id = "dagger", coord = {px,py}, buc = "not-blessed" }); - end - if percent(55) then - des.object({ class = ")", coord = {px,py}, buc = "not-blessed" }); - end - if percent(45) then - des.object({ id = "bow", coord = {px,py}, buc = "not-blessed" }); - des.object({ id = "arrow", coord = {px,py}, buc = "not-blessed" }); - end - if percent(65) then - des.object({ class = "[", coord = {px,py}, buc = "not-blessed" }); - end - if percent(20) then - des.object({ class = "=", coord = {px,py}, buc = "not-blessed" }); - end - if percent(20) then - des.object({ class = "?", coord = {px,py}, buc = "not-blessed" }); - end - end - }); - end, - -- Mausoleum function() des.room({ type = "themed", w = 5 + nh.rn2(3)*2, h = 5 + nh.rn2(3)*2, @@ -310,7 +291,7 @@ themerooms = { |......| |......| |......| ---------]], contents = function(m) des.region({ region={1,1,3,3}, type="ordinary", irregular=true, filled=1 }); end }); +--------]], contents = function(m) filler_region(1,1); end }); end, -- L-shaped, rot 1 @@ -323,7 +304,7 @@ xxx|...| |......| |......| |......| ---------]], contents = function(m) des.region({ region={5,1,5,3}, type="ordinary", irregular=true, filled=1 }); end }); +--------]], contents = function(m) filler_region(5,1); end }); end, -- L-shaped, rot 2 @@ -336,7 +317,7 @@ xxx|...| ----...| xxx|...| xxx|...| -xxx-----]], contents = function(m) des.region({ region={1,1,2,2}, type="ordinary", irregular=true, filled=1 }); end }); +xxx-----]], contents = function(m) filler_region(1,1); end }); end, -- L-shaped, rot 3 @@ -349,7 +330,7 @@ xxx-----]], contents = function(m) des.region({ region={1,1,2,2}, type="ordinary |...---- |...|xxx |...|xxx ------xxx]], contents = function(m) des.region({ region={1,1,2,2}, type="ordinary", irregular=true, filled=1 }); end }); +-----xxx]], contents = function(m) filler_region(1,1); end }); end, -- Blocked center @@ -371,7 +352,7 @@ if (percent(30)) then 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, filled=1 }); +filler_region(1,1); end }); end, @@ -384,7 +365,7 @@ x--.--x |.....| --...-- x--.--x -xx---xx]], contents = function(m) des.region({ region={3,3,3,3}, type="ordinary", irregular=true, filled=1 }); end }); +xx---xx]], contents = function(m) filler_region(3,3); end }); end, -- Circular, medium @@ -398,7 +379,7 @@ x--...--x |.......| --.....-- x--...--x -xx-----xx]], contents = function(m) des.region({ region={4,4,4,4}, type="ordinary", irregular=true, filled=1 }); end }); +xx-----xx]], contents = function(m) filler_region(4,4); end }); end, -- Circular, big @@ -414,7 +395,7 @@ x-.......-x --.......-- x-.......-x x---...---x -xxx-----xxx]], contents = function(m) des.region({ region={5,5,5,5}, type="ordinary", irregular=true, filled=1 }); end }); +xxx-----xxx]], contents = function(m) filler_region(5,5); end }); end, -- T-shaped @@ -427,7 +408,7 @@ xxx|...|xxx |.........| |.........| |.........| ------------]], contents = function(m) des.region({ region={5,5,5,5}, type="ordinary", irregular=true, filled=1 }); end }); +-----------]], contents = function(m) filler_region(5,5); end }); end, -- T-shaped, rot 1 @@ -443,7 +424,7 @@ xxx|...|xxx |...---- |...|xxx |...|xxx ------xxx]], contents = function(m) des.region({ region={2,2,2,2}, type="ordinary", irregular=true, filled=1 }); end }); +-----xxx]], contents = function(m) filler_region(2,2); end }); end, -- T-shaped, rot 2 @@ -456,7 +437,7 @@ xxx|...|xxx ----...---- xxx|...|xxx xxx|...|xxx -xxx-----xxx]], contents = function(m) des.region({ region={2,2,2,2}, type="ordinary", irregular=true, filled=1 }); end }); +xxx-----xxx]], contents = function(m) filler_region(2,2); end }); end, -- T-shaped, rot 3 @@ -472,7 +453,7 @@ xxx|...| ----...| xxx|...| xxx|...| -xxx-----]], contents = function(m) des.region({ region={5,5,5,5}, type="ordinary", irregular=true, filled=1 }); end }); +xxx-----]], contents = function(m) filler_region(5,5); end }); end, -- S-shaped @@ -488,7 +469,7 @@ xxx-----]], contents = function(m) des.region({ region={5,5,5,5}, type="ordinary ----...| xxx|...| xxx|...| -xxx-----]], contents = function(m) des.region({ region={2,2,2,2}, type="ordinary", irregular=true, filled=1 }); end }); +xxx-----]], contents = function(m) filler_region(2,2); end }); end, -- S-shaped, rot 1 @@ -501,7 +482,7 @@ xxx|......| |......---- |......|xxx |......|xxx ---------xxx]], contents = function(m) des.region({ region={5,5,5,5}, type="ordinary", irregular=true, filled=1 }); end }); +--------xxx]], contents = function(m) filler_region(5,5); end }); end, -- Z-shaped @@ -517,7 +498,7 @@ xxx|...| |...---- |...|xxx |...|xxx ------xxx]], contents = function(m) des.region({ region={5,5,5,5}, type="ordinary", irregular=true, filled=1 }); end }); +-----xxx]], contents = function(m) filler_region(5,5); end }); end, -- Z-shaped, rot 1 @@ -530,7 +511,7 @@ xxx|...| ----......| xxx|......| xxx|......| -xxx--------]], contents = function(m) des.region({ region={2,2,2,2}, type="ordinary", irregular=true, filled=1 }); end }); +xxx--------]], contents = function(m) filler_region(2,2); end }); end, -- Cross @@ -546,7 +527,7 @@ xxx|...|xxx ----...---- xxx|...|xxx xxx|...|xxx -xxx-----xxx]], contents = function(m) des.region({ region={6,6,6,6}, type="ordinary", irregular=true, filled=1 }); end }); +xxx-----xxx]], contents = function(m) filler_region(6,6); end }); end, -- Four-leaf clover @@ -562,7 +543,7 @@ xx|.....|xx |.........| |...---...| |...|x|...| ------x-----]], contents = function(m) des.region({ region={6,6,6,6}, type="ordinary", irregular=true, filled=1 }); end }); +-----x-----]], contents = function(m) filler_region(6,6); end }); end, -- Water-surrounded vault @@ -639,7 +620,18 @@ end }); }; -function is_eligible(room) + +function filler_region(x, y) + local rmtyp = "ordinary"; + local func = nil; + if (percent(30)) then + rmtyp = "themed"; + func = themeroom_fill; + end + des.region({ region={x,y,x,y}, type=rmtyp, irregular=true, filled=1, contents = func }); +end + +function is_eligible(room, mkrm) local t = type(room); local diff = nh.level_difficulty(); if (t == "table") then @@ -648,6 +640,9 @@ function is_eligible(room) elseif (room.maxdiff ~= nil and diff > room.maxdiff) then return false end + if (mkrm ~= nil and room.eligible ~= nil) then + return room.eligible(mkrm); + end elseif (t == "function") then -- functions currently have no constraints end @@ -660,7 +655,7 @@ function themerooms_generate() for i = 1, #themerooms do -- Reservoir sampling: select one room from the set of eligible rooms, -- which may change on different levels because of level difficulty. - if is_eligible(themerooms[i]) then + if is_eligible(themerooms[i], nil) then local this_frequency; if (type(themerooms[i]) == "table" and themerooms[i].frequency ~= nil) then this_frequency = themerooms[i].frequency; @@ -682,3 +677,32 @@ function themerooms_generate() themerooms[pick](); end end + +function themeroom_fill(rm) + local pick = 1; + local total_frequency = 0; + for i = 1, #themeroom_fills do + -- Reservoir sampling: select one room from the set of eligible rooms, + -- which may change on different levels because of level difficulty. + if is_eligible(themeroom_fills[i], rm) then + local this_frequency; + if (type(themeroom_fills[i]) == "table" and themeroom_fills[i].frequency ~= nil) then + this_frequency = themeroom_fills[i].frequency; + else + this_frequency = 1; + end + total_frequency = total_frequency + this_frequency; + -- avoid rn2(0) if a room has freq 0 + if this_frequency > 0 and nh.rn2(total_frequency) < this_frequency then + pick = i; + end + end + end + + local t = type(themeroom_fills[pick]); + if (t == "table") then + themeroom_fills[pick].contents(rm); + elseif (t == "function") then + themeroom_fills[pick](rm); + end +end diff --git a/doc/lua.adoc b/doc/lua.adoc index e0959480d..8b614bb4d 100644 --- a/doc/lua.adoc +++ b/doc/lua.adoc @@ -1070,6 +1070,20 @@ Example: local coord = selection.rndcoord(sel); local coord = selection.rndcoord(sel, 1); + +=== room + +Create a selection of locations inside the (current) room. + +Example: + + des.room({ type = "ordinary", contents = function(rm) + local sel = selection.room(); + des.terrain(sel, "I"); + end + }); + + === set Set the value for location (x,y) in the selection. diff --git a/include/extern.h b/include/extern.h index 32b88d989..bbb337376 100644 --- a/include/extern.h +++ b/include/extern.h @@ -2612,6 +2612,7 @@ extern boolean get_coord(lua_State *, int, lua_Integer *, lua_Integer *); extern void cvt_to_abscoord(coordxy *, coordxy *); extern void cvt_to_relcoord(coordxy *, coordxy *); extern int nhl_abs_coord(lua_State *); +extern struct selectionvar *selection_from_mkroom(struct mkroom *); extern void update_croom(void); extern const char *get_trapname_bytype(int); extern void l_register_des(lua_State *); diff --git a/src/nhlsel.c b/src/nhlsel.c index 085ac5996..96ed226d2 100644 --- a/src/nhlsel.c +++ b/src/nhlsel.c @@ -17,6 +17,7 @@ static int l_selection_getpoint(lua_State *); static int l_selection_setpoint(lua_State *); static int l_selection_filter_percent(lua_State *); static int l_selection_rndcoord(lua_State *); +static int l_selection_room(lua_State *); static int l_selection_getbounds(lua_State *); static boolean params_sel_2coords(lua_State *, struct selectionvar **, coordxy *, coordxy *, coordxy *, coordxy *); @@ -383,6 +384,28 @@ l_selection_rndcoord(lua_State *L) return 1; } +/* local s = selection.room(); */ +static int +l_selection_room(lua_State *L) +{ + struct selectionvar *sel; + int argc = lua_gettop(L); + struct mkroom *croom = NULL; + + if (argc == 1) { + int i = luaL_checkinteger(L, -1); + + croom = (i >= 0 && i < g.nroom) ? &g.rooms[i] : NULL; + } + + sel = selection_from_mkroom(croom); + + l_selection_push_copy(L, sel); + selection_free(sel, TRUE); + + return 1; +} + /* local rect = sel:bounds(); */ static int l_selection_getbounds(lua_State *L) @@ -899,6 +922,7 @@ static const struct luaL_Reg l_selection_methods[] = { { "gradient", l_selection_gradient }, { "iterate", l_selection_iterate }, { "bounds", l_selection_getbounds }, + { "room", l_selection_room }, { NULL, NULL } }; diff --git a/src/sp_lev.c b/src/sp_lev.c index a546bc6e0..959cab09c 100644 --- a/src/sp_lev.c +++ b/src/sp_lev.c @@ -6056,7 +6056,8 @@ lspo_region(lua_State *L) lua_getfield(L, 1, "contents"); if (lua_type(L, -1) == LUA_TFUNCTION) { lua_remove(L, -2); - if (nhl_pcall(L, 0, 0)){ + l_push_mkroom_table(L, troom); + if (nhl_pcall(L, 1, 0)){ impossible("Lua error: %s", lua_tostring(L, -1)); } } else @@ -6664,6 +6665,25 @@ TODO: g.coder->croom needs to be updated return 0; } +struct selectionvar * +selection_from_mkroom(struct mkroom *croom) +{ + struct selectionvar *sel = selection_new(); + coordxy x, y; + + if (!croom && g.coder && g.coder->croom) + croom = g.coder->croom; + if (!croom) + return sel; + + 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 == (croom - g.rooms) + ROOMOFFSET) + selection_setpoint(x, y, sel, 1); + return sel; +} + void update_croom(void) {