-- NetHack themerms.lua	$NHDT-Date: 1652196294 2022/05/10 15:24:54 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.16 $
--	Copyright (c) 2020 by Pasi Kallinen
-- NetHack may be freely redistributed.  See license for details.
--
-- themerooms is an array of tables and/or functions.
-- 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
--     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.
--
-- for each level, the core first calls pre_themerooms_generate(),
-- then it calls themerooms_generate() multiple times until it decides
-- enough rooms have been generated, and then it calls
-- post_themerooms_generate().  The lua state is persistent through
-- the gameplay, but not across saves, so remember to reset any variables.


local buried_treasure = { };

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(otmp)
                      local xobj = otmp:totable();
                      -- keep track of the last buried treasure
                      if (xobj.NO_OBJ == nil) then
                         buried_treasure = { x = xobj.ox, y = xobj.oy };
                      end
                      for i = 1, d(3,4) do
                         des.object();
                      end
      end });
   end,

   -- Buried zombies
   function(rm)
      local diff = nh.level_difficulty()
      -- start with [1..4] for low difficulty
      local zombifiable = { "kobold", "gnome", "orc", "dwarf" };
      if diff > 3 then          -- medium difficulty
         zombifiable[5], zombifiable[6] = "elf", "human";
         if diff > 6 then       -- high difficulty (relatively speaking)
            zombifiable[7], zombifiable[8] = "ettin", "giant";
         end
      end
      for i = 1, (rm.width * rm.height) / 2 do
         shuffle(zombifiable);
         local o = des.object({ id = "corpse", montype = zombifiable[1],
                                buried = true });
         o:stop_timer("rot-corpse");
         o:start_timer("zombify-mon", math.random(990, 1010));
      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,

   -- Storeroom
   function(rm)
      local locs = selection.room():percentage(30);
      local func = function(x,y)
         if (percent(25)) then
            des.object("chest");
         else
            des.monster({ class = "m", appear_as = "obj:chest" });
         end
      end;
      locs:iterate(func);
   end,
};

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

   -- Nesting rooms
   function()
      des.room({ type = "ordinary", w = 9 + nh.rn2(4), h = 9 + nh.rn2(4), filled = 1,
                 contents = function(rm)
                    local wid = math.random(math.floor(rm.width / 2), rm.width - 2);
                    local hei = math.random(math.floor(rm.height / 2), rm.height - 2);
                    des.room({ type = "ordinary", w = wid,h = hei, filled = 1,
                               contents = function()
                                  if (percent(90)) then
                                     des.room({ type = "ordinary", filled = 1,
                                                contents = function()
                                                   des.door({ state="random", wall="all" });
                                                   if (percent(15)) then
                                                      des.door({ state="random", wall="all" });
                                                   end
                                                end
                                     });
                                  end
                                  des.door({ state="random", wall="all" });
                                  if (percent(15)) then
                                     des.door({ state="random", wall="all" });
                                  end
                               end
                    });
                 end
      });
   end,

   {
      frequency = 6,
      contents = function()
         des.room({ type = "themed", contents = themeroom_fill });
      end
   },

   {
      frequency = 2,
      contents = function()
         des.room({ type = "themed", lit = 0, contents = themeroom_fill });
      end
   },

   {
      frequency = 2,
      contents = function()
         des.room({ type = "themed", filled = 1, contents = themeroom_fill });
      end
   },

   -- Pillars
   function()
      des.room({ type = "themed", w = 10, h = 10,
                 contents = function(rm)
                    local terr = { "-", "-", "-", "-", "L", "P", "T" };
                    shuffle(terr);
                    for x = 0, (rm.width / 4) - 1 do
                       for y = 0, (rm.height / 4) - 1 do
                          des.terrain({ x = x * 4 + 2, y = y * 4 + 2, typ = terr[1], lit = -2 });
                          des.terrain({ x = x * 4 + 3, y = y * 4 + 2, typ = terr[1], lit = -2 });
                          des.terrain({ x = x * 4 + 2, y = y * 4 + 3, typ = terr[1], lit = -2 });
                          des.terrain({ x = x * 4 + 3, y = y * 4 + 3, typ = terr[1], lit = -2 });
                       end
                    end
                 end
      });
   end,

   -- Mausoleum
   function()
      des.room({ type = "themed", w = 5 + nh.rn2(3)*2, h = 5 + nh.rn2(3)*2,
                 contents = function(rm)
                    des.room({ type = "themed",
			       x = (rm.width - 1) / 2, y = (rm.height - 1) / 2,
			       w = 1, h = 1, joined = false,
                               contents = function()
                                  if (percent(50)) then
                                     local mons = { "M", "V", "L", "Z" };
                                     shuffle(mons);
                                     des.monster({ class = mons[1], x=0,y=0, waiting = 1 });
                                  else
                                     des.object({ id = "corpse", montype = "@", coord = {0,0} });
                                  end
                                  if (percent(20)) then
                                     des.door({ state="secret", wall="all" });
                                  end
                               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 - 1) / 2, (rm.height - 1) / 2,
				feature[1]);
                 end
      });
   end,

   -- L-shaped
   function()
      des.map({ map = [[
-----xxx
|...|xxx
|...|xxx
|...----
|......|
|......|
|......|
--------]], contents = function(m) filler_region(1,1); end });
   end,

   -- L-shaped, rot 1
   function()
      des.map({ map = [[
xxx-----
xxx|...|
xxx|...|
----...|
|......|
|......|
|......|
--------]], contents = function(m) filler_region(5,1); end });
   end,

   -- L-shaped, rot 2
   function()
      des.map({ map = [[
--------
|......|
|......|
|......|
----...|
xxx|...|
xxx|...|
xxx-----]], contents = function(m) filler_region(1,1); end });
   end,

   -- L-shaped, rot 3
   function()
      des.map({ map = [[
--------
|......|
|......|
|......|
|...----
|...|xxx
|...|xxx
-----xxx]], contents = function(m) filler_region(1,1); 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
filler_region(1,1);
end });
   end,

   -- Circular, small
   function()
      des.map({ map = [[
xx---xx
x--.--x
--...--
|.....|
--...--
x--.--x
xx---xx]], contents = function(m) filler_region(3,3); end });
   end,

   -- Circular, medium
   function()
      des.map({ map = [[
xx-----xx
x--...--x
--.....--
|.......|
|.......|
|.......|
--.....--
x--...--x
xx-----xx]], contents = function(m) filler_region(4,4); end });
   end,

   -- Circular, big
   function()
      des.map({ map = [[
xxx-----xxx
x---...---x
x-.......-x
--.......--
|.........|
|.........|
|.........|
--.......--
x-.......-x
x---...---x
xxx-----xxx]], contents = function(m) filler_region(5,5); end });
   end,

   -- T-shaped
   function()
      des.map({ map = [[
xxx-----xxx
xxx|...|xxx
xxx|...|xxx
----...----
|.........|
|.........|
|.........|
-----------]], contents = function(m) filler_region(5,5); end });
   end,

   -- T-shaped, rot 1
   function()
      des.map({ map = [[
-----xxx
|...|xxx
|...|xxx
|...----
|......|
|......|
|......|
|...----
|...|xxx
|...|xxx
-----xxx]], contents = function(m) filler_region(2,2); end });
   end,

   -- T-shaped, rot 2
   function()
      des.map({ map = [[
-----------
|.........|
|.........|
|.........|
----...----
xxx|...|xxx
xxx|...|xxx
xxx-----xxx]], contents = function(m) filler_region(2,2); end });
   end,

   -- T-shaped, rot 3
   function()
      des.map({ map = [[
xxx-----
xxx|...|
xxx|...|
----...|
|......|
|......|
|......|
----...|
xxx|...|
xxx|...|
xxx-----]], contents = function(m) filler_region(5,5); end });
   end,

   -- S-shaped
   function()
      des.map({ map = [[
-----xxx
|...|xxx
|...|xxx
|...----
|......|
|......|
|......|
----...|
xxx|...|
xxx|...|
xxx-----]], contents = function(m) filler_region(2,2); end });
   end,

   -- S-shaped, rot 1
   function()
      des.map({ map = [[
xxx--------
xxx|......|
xxx|......|
----......|
|......----
|......|xxx
|......|xxx
--------xxx]], contents = function(m) filler_region(5,5); end });
   end,

   -- Z-shaped
   function()
      des.map({ map = [[
xxx-----
xxx|...|
xxx|...|
----...|
|......|
|......|
|......|
|...----
|...|xxx
|...|xxx
-----xxx]], contents = function(m) filler_region(5,5); end });
   end,

   -- Z-shaped, rot 1
   function()
      des.map({ map = [[
--------xxx
|......|xxx
|......|xxx
|......----
----......|
xxx|......|
xxx|......|
xxx--------]], contents = function(m) filler_region(2,2); end });
   end,

   -- Cross
   function()
      des.map({ map = [[
xxx-----xxx
xxx|...|xxx
xxx|...|xxx
----...----
|.........|
|.........|
|.........|
----...----
xxx|...|xxx
xxx|...|xxx
xxx-----xxx]], contents = function(m) filler_region(6,6); end });
   end,

   -- Four-leaf clover
   function()
      des.map({ map = [[
-----x-----
|...|x|...|
|...---...|
|.........|
---.....---
xx|.....|xx
---.....---
|.........|
|...---...|
|...|x|...|
-----x-----]], contents = function(m) filler_region(6,6); end });
   end,

   -- Water-surrounded vault
   function()
      des.map({ map = [[
}}}}}}
}----}
}|..|}
}|..|}
}----}
}}}}}}]], contents = function(m) des.region({ region={3,3,3,3}, type="themed", irregular=true, filled=0, joined=false });
     local nasty_undead = { "giant zombie", "ettin zombie", "vampire lord" };
     local chest_spots = { { 2, 2 }, { 3, 2 }, { 2, 3 }, { 3, 3 } };

     shuffle(chest_spots)
     -- Guarantee an escape item inside one of the chests, to prevent the hero
     -- falling in from above and becoming permanently stuck
     -- [cf. generate_way_out_method(sp_lev.c)].
     -- If the escape item is made of glass or crystal, make sure that the
     -- chest isn't locked so that kicking it to gain access to its contents
     -- won't be necessary; otherwise retain lock state from random creation.
     -- "pick-axe", "dwarvish mattock" could be included in the list of escape
     -- items but don't normally generate in containers.
     local escape_items = {
        "scroll of teleportation", "ring of teleportation",
        "wand of teleportation", "wand of digging"
     };
     local itm = obj.new(escape_items[math.random(#escape_items)]);
     local itmcls = itm:class()
     local box
     if itmcls[ "material" ] == 19 then                         -- GLASS==19
         -- item is made of glass so explicitly force chest to be unlocked
	 box = des.object({ id = "chest", coord = chest_spots[1],
                            olocked = "no" });
     else
         -- item isn't made of glass; accept random locked/unlocked state
	 box = des.object({ id = "chest", coord = chest_spots[1] });
     end;
     box:addcontent(itm);

     for i = 2, #chest_spots do
         des.object({ id = "chest", coord = chest_spots[i] });
     end

     shuffle(nasty_undead);
     des.monster(nasty_undead[1], 2, 2);
     des.exclusion({ type = "teleport", region = { 2,2, 3,3 } });
end });
   end,

   -- Twin businesses
   {
      mindiff = 4; -- arbitrary
      contents = function()
         -- Due to the way room connections work in mklev.c, we must guarantee
         -- that the "aisle" between the shops touches all four walls of the
         -- larger room. Thus it has an extra width and height.
         des.room({ type="themed", w=9, h=5, contents = function()
               -- There are eight possible placements of the two shops, four of
               -- which have the vertical aisle in the center.
               southeast = function() return percent(50) and "south" or "east" end
               northeast = function() return percent(50) and "north" or "east" end
               northwest = function() return percent(50) and "north" or "west" end
               southwest = function() return percent(50) and "south" or "west" end
               placements = {
                  { lx = 1, ly = 1, rx = 4, ry = 1, lwall = "south", rwall = southeast() },
                  { lx = 1, ly = 2, rx = 4, ry = 2, lwall = "north", rwall = northeast() },
                  { lx = 1, ly = 1, rx = 5, ry = 1, lwall = southeast(), rwall = southwest() },
                  { lx = 1, ly = 1, rx = 5, ry = 2, lwall = southeast(), rwall = northwest() },
                  { lx = 1, ly = 2, rx = 5, ry = 1, lwall = northeast(), rwall = southwest() },
                  { lx = 1, ly = 2, rx = 5, ry = 2, lwall = northeast(), rwall = northwest() },
                  { lx = 2, ly = 1, rx = 5, ry = 1, lwall = southwest(), rwall = "south" },
                  { lx = 2, ly = 2, rx = 5, ry = 2, lwall = northwest(), rwall = "north" }
               }
               ltype,rtype = "weapon shop","armor shop"
               if percent(50) then
                  ltype,rtype = rtype,ltype
               end
               shopdoorstate = function()
                  if percent(1) then
                     return "locked"
                  elseif percent(50) then
                     return "closed"
                  else
                     return "open"
                  end
               end
               p = placements[d(#placements)]
               des.room({ type=ltype, x=p["lx"], y=p["ly"], w=3, h=3, filled=1, joined=false,
                           contents = function()
                     des.door({ state=shopdoorstate(), wall=p["lwall"] })
                  end
               });
               des.room({ type=rtype, x=p["rx"], y=p["ry"], w=3, h=3, filled=1, joined=false,
                           contents = function()
                     des.door({ state=shopdoorstate(), wall=p["rwall"] })
                  end
               });
            end
         });
      end
   },

};


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
      if (room.mindiff ~= nil and diff < room.mindiff) then
         return false
      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
   return true
end

-- called repeatedly until the core decides there are enough rooms
function themerooms_generate()
   local pick = 1;
   local total_frequency = 0;
   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], nil) 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
         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(themerooms[pick]);
   if (t == "table") then
      themerooms[pick].contents();
   elseif (t == "function") then
      themerooms[pick]();
   end
end

-- called before any rooms are generated
function pre_themerooms_generate()
   -- reset the buried treasure location
   buried_treasure = { };
end

-- called after all rooms have been generated
function post_themerooms_generate()
   if (buried_treasure.x ~= nil) then
      local floors = selection.negate():filter_mapchar(".");
      local pos = floors:rndcoord(0);
      local tx = buried_treasure.x - pos.x - 1;
      local ty = buried_treasure.y - pos.y;
      local dig = "";
      if (tx == 0 and ty == 0) then
         dig = " here";
      else
         if (tx < 0 or tx > 0) then
            dig = string.format(" %i %s", math.abs(tx), (tx > 0) and "east" or "west");
         end
         if (ty < 0 or ty > 0) then
            dig = dig .. string.format(" %i %s", math.abs(ty), (ty > 0) and "south" or "north");
         end
      end
      des.engraving({ coord = pos, type = "burn", text = "Dig" .. dig });
   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

