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.
This commit is contained in:
Pasi Kallinen
2022-09-15 18:08:32 +03:00
parent 1c177dcb39
commit 3605f18a8e
5 changed files with 270 additions and 187 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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 *);

View File

@@ -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 }
};

View File

@@ -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)
{