I hope this doesn't break anything. There seemed to be one or two misplaced 'end' statements, but after some massaging I'm not sure about this anymore. There were lots of wide lines; those are easy. The Water-surrounded vault had very inconsistent indentation so was harder to untangle.
940 lines
28 KiB
Lua
940 lines
28 KiB
Lua
-- NetHack themerms.lua $NHDT-Date: 1743399789 2025/03/30 21:43:09 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.36 $
|
|
-- 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(). When the level has been generated, with
|
|
-- joining corridors and rooms filled, the core calls post_level_generate().
|
|
-- The lua state is persistent through the gameplay, but not across saves,
|
|
-- so remember to reset any variables.
|
|
|
|
|
|
local postprocess = { };
|
|
|
|
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,
|
|
|
|
-- Cloud room
|
|
function(rm)
|
|
local fog = selection.room();
|
|
for i = 1, (fog:numpoints() / 4) do
|
|
des.monster({ id = "fog cloud", asleep = true });
|
|
end
|
|
des.gas_cloud({ selection = fog });
|
|
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,
|
|
|
|
-- Garden
|
|
{
|
|
eligible = function(rm) return rm.lit == true; end,
|
|
contents = function(rm)
|
|
local s = selection.room();
|
|
local npts = (s:numpoints() / 6);
|
|
for i = 1, npts do
|
|
des.monster({ id = "wood nymph", asleep = true });
|
|
if (percent(30)) then
|
|
des.feature("fountain");
|
|
end
|
|
end
|
|
table.insert(postprocess, { handler = make_garden_walls,
|
|
data = { sel = selection.room() } });
|
|
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
|
|
table.insert(postprocess, { handler = make_dig_engraving,
|
|
data = { 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,
|
|
|
|
-- Teleportation hub
|
|
function(rm)
|
|
local locs = selection.room():filter_mapchar(".");
|
|
for i = 1, 2 + nh.rn2(3) do
|
|
local pos = locs:rndcoord(1);
|
|
if (pos.x > 0) then
|
|
pos.x = pos.x + rm.region.x1 - 1;
|
|
pos.y = pos.y + rm.region.y1;
|
|
table.insert(postprocess, { handler = make_a_trap,
|
|
data = { type = "teleport", seen = true,
|
|
coord = pos, teledest = 1 } });
|
|
end
|
|
end
|
|
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 an 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 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" ] == "glass" then
|
|
-- explicitly force chest to be unlocked
|
|
box = des.object({ id = "chest", coord = chest_spots[1],
|
|
olocked = "no" });
|
|
else
|
|
-- 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
|
|
}); -- des.map
|
|
end,
|
|
|
|
-- Twin businesses
|
|
{
|
|
mindiff = 4; -- arbitrary; needs to be greater than 1 since no shops on 1
|
|
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()
|
|
end
|
|
|
|
-- called after all rooms have been generated
|
|
-- but before creating connecting corridors/doors, or filling rooms
|
|
function post_themerooms_generate()
|
|
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
|
|
|
|
-- postprocess callback: create an engraving pointing at a location
|
|
function make_dig_engraving(data)
|
|
local floors = selection.negate():filter_mapchar(".");
|
|
local pos = floors:rndcoord(0);
|
|
local tx = data.x - pos.x - 1;
|
|
local ty = data.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
|
|
|
|
-- postprocess callback: turn room walls into trees
|
|
function make_garden_walls(data)
|
|
local sel = data.sel:grow();
|
|
des.replace_terrain({ selection = sel, fromterrain="w", toterrain = "T" });
|
|
end
|
|
|
|
-- postprocess callback: make a trap
|
|
function make_a_trap(data)
|
|
if (data.teledest == 1 and data.type == "teleport") then
|
|
local locs = selection.negate():filter_mapchar(".");
|
|
repeat
|
|
data.teledest = locs:rndcoord(1);
|
|
until (data.teledest.x ~= data.coord.x
|
|
and data.teledest.y ~= data.coord.y);
|
|
end
|
|
des.trap(data);
|
|
end
|
|
|
|
-- called once after the whole level has been generated
|
|
function post_level_generate()
|
|
for i, v in ipairs(postprocess) do
|
|
v.handler(v.data);
|
|
end
|
|
postprocess = { };
|
|
end
|