]> granicus.if.org Git - nethack/commitdiff
Enable themed rooms to be constrained by level difficulty
authorcopperwater <aosdict@gmail.com>
Wed, 13 May 2020 04:15:10 +0000 (00:15 -0400)
committerPasi Kallinen <paxed@alt.org>
Tue, 29 Sep 2020 14:30:11 +0000 (17:30 +0300)
The system of themed rooms currently makes it so that any themed room
can potentially generate anywhere a themed room can be placed. This is
problematic in the long run, since it makes it difficult to design new
rooms that are an appropriate amount of challenge at all levels of the
dungeon. (A few themed rooms already have this problem: a hero starting
out on level 1 probably won't live very long when the neighboring room
is full of giant spiders, or an arch-lich has generated in a mausoleum
nearby).

This commit adds optional "mindiff" and "maxdiff" properties for
themerooms defined as tables and exposes level_difficulty() to Lua. A
themeroom whose mindiff exceeds the current level difficulty, or whose
maxdiff is lower than the current level difficulty, is prevented from
being selected.

Because the set of rooms eligible to generate on a given level is no
longer fixed, the total frequency of all the rooms can't be computed
once per game when the file is first parsed, as it was before. In place
of this, the themerooms_generate() function now uses a reservoir
sampling algorithm to choose a room from among the eligible rooms,
weighted by frequency.

dat/themerms.lua
src/nhlua.c

index 85c3e18f415a7e8f64c229fb6358015635fe4a10..db831a49633c8ccc7cd7d47474036e8dcf9e2c74 100644 (file)
@@ -1,7 +1,10 @@
 
 -- themerooms is an array of tables and/or functions.
--- the tables define "frequency" and "contents",
--- a plain function has frequency of 1
+-- the tables define "frequency", "contents", "mindiff" and "maxdiff".
+-- frequency is optional; if omitted, 1 is assumed.
+-- mindiff and maxdiff are optional and independent; if omitted, the room is
+-- not constrained by level difficulty.
+-- a plain function has frequency of 1, and no difficulty constraints.
 -- 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
@@ -538,36 +541,45 @@ end });
 
 };
 
-local total_frequency = 0;
-for i = 1, #themerooms do
-   local t = type(themerooms[i]);
+function is_eligible(room)
+   local t = type(room);
+   local diff = nh.level_difficulty();
    if (t == "table") then
-      total_frequency = total_frequency + themerooms[i].frequency;
+      if (room.mindiff ~= nil and diff < room.mindiff) then
+         return false
+      elseif (room.maxdiff ~= nil and diff > room.maxdiff) then
+         return false
+      end
    elseif (t == "function") then
-      total_frequency = total_frequency + 1;
+      -- functions currently have no constraints
    end
-end
-
-if (total_frequency == 0) then
-   error("Theme rooms total_frequency == 0");
+   return true
 end
 
 function themerooms_generate()
-   local pick = nh.rn2(total_frequency);
+   local pick = 1;
+   local total_frequency = 0;
    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;
+      -- 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
+         local this_frequency;
+         if (type(themerooms[i]) == "table" and themerooms[i].frequency ~= nil) then
+            this_frequency = themerooms[i].frequency;
+         else
+            this_frequency = 1;
          end
-      elseif (t == "function") then
-         pick = pick - 1;
-         if (pick < 0) then
-            themerooms[i]();
-            return;
+         total_frequency = total_frequency + this_frequency;
+         if (nh.rn2(total_frequency) < this_frequency) then
+            pick = i;
          end
       end
    end
+
+   local t = type(themerooms[pick]);
+   if (t == "table") then
+      themerooms[pick].contents();
+   elseif (t == "function") then
+      themerooms[pick]();
+   end
 end
index 24bcc5d59854e7bf0e365a83007336056a76e2d2..b3906592bfbd5a206de02178e85c4368b673c558 100644 (file)
@@ -34,6 +34,7 @@ static int FDECL(nhl_ing_suffix, (lua_State *));
 static int FDECL(nhl_an, (lua_State *));
 static int FDECL(nhl_rn2, (lua_State *));
 static int FDECL(nhl_random, (lua_State *));
+static int FDECL(nhl_level_difficulty, (lua_State *));
 static void FDECL(init_nhc_data, (lua_State *));
 static int FDECL(nhl_push_anything, (lua_State *, int, void *));
 static int FDECL(nhl_meta_u_index, (lua_State *));
@@ -657,6 +658,21 @@ lua_State *L;
     return 1;
 }
 
+/* level_difficulty() */
+static int
+nhl_level_difficulty(L)
+lua_State *L;
+{
+    int argc = lua_gettop(L);
+    if (argc == 0) {
+        lua_pushinteger(L, level_difficulty());
+    }
+    else {
+        nhl_error(L, "level_difficulty should not have any args");
+    }
+    return 1;
+}
+
 /* get mandatory integer value from table */
 int
 get_table_int(L, name)
@@ -831,6 +847,7 @@ static const struct luaL_Reg nhl_functions[] = {
     {"an", nhl_an},
     {"rn2", nhl_rn2},
     {"random", nhl_random},
+    {"level_difficulty", nhl_level_difficulty},
     {NULL, NULL}
 };