1-- NetHack 3.7	Arch.des	$NHDT-Date: 1432512784 2015/05/25 00:13:04 $  $NHDT-Branch: master $:$NHDT-Revision: 1.10 $
2--	Copyright (c) 1989 by Jean-Christophe Collet
3--	Copyright (c) 1991 by M. Stephenson
4-- NetHack may be freely redistributed.  See license for details.
5--
6
7-- a bit kludgy...
8XMIN = 2
9XMAX = 76
10YMIN = 0
11YMAX = 19
12
13-- SHIM -- g.xstart is 1, usually
14function getmap(x, y)
15   return nh.getmap(x+1, y)
16end
17
18des.level_flags("noflip", "noteleport", "hardfloor", "inaccessibles", "nommap")
19
20-- Non diggable walls
21des.non_diggable(selection.area(00,00,76,20))
22
23-- initial room containing the upstairs
24des.room({ type = "ordinary", lit = 0, x = 28 + nh.rn2(20), y = 1 + nh.rn2(14),
25           contents = function(rm)
26              des.stair("up")
27           end
28});
29-- find upstairs location for use later
30local ustairx, ustairy
31selection.area(00,00,75,19):iterate(function(x, y)
32   if getmap(x, y)['typ_name'] == 'stairs' then
33      ustairx = x
34      ustairy = y
35   end
36end)
37
38-- Return a rectangular selection for a room that can be overlaid entirely
39-- onto stone or existing walls.
40-- One of dx and dy must be zero and the other must be either 1 or -1.
41-- If a room of at least 2x2 won't fit here, return all -1 coordinates.
42function findroom(xi, yi, maindir, straightline)
43   if xi <= XMIN or yi <= YMIN or xi >= XMAX or yi >= YMAX then
44      return nil
45   end
46   local dirs = { north = true, south = true, east = true, west = true}
47   -- don't grow backwards
48   if maindir == "north" then dirs["south"] = false end
49   if maindir == "south" then dirs["north"] = false end
50   if maindir == "east" then dirs["west"] = false end
51   if maindir == "west" then dirs["east"] = false end
52   if straightline then
53      dirs = { }
54      dirs[maindir] = true
55   end
56
57   function rnddir()
58      local elig = 0
59      local sel = nil
60      for dir, bool in pairs(dirs) do
61         if bool then
62            elig = elig + 1
63            if nh.rn2(elig) == 0 then
64               sel = dir
65            end
66         end
67      end
68      return sel
69   end
70
71   local sel = selection.new()
72   sel:set(xi, yi, 1)
73
74   local nexpand = d(2,6)
75   local growths = 0
76
77   -- bias against lots of tall vertical corridors
78   if straightline and (maindir == "north" or maindir == "south") and percent(60) then
79      nexpand = math.ceil(nexpand / 2)
80   end
81
82   for i=1,nexpand do
83      -- randomly pick a direction to grow
84      local currdir = rnddir()
85      if currdir == nil then
86         break
87      end
88      if i == 1 then
89         -- rooms should be at least 2 deep, so force this on the first attempt
90         currdir = maindir
91      end
92
93      local newsel = sel:clone():grow(currdir)
94      local addition = (newsel ~ sel) -- set subtraction would be better, but xor will do
95      local invalid = false
96      addition:iterate(function(x, y)
97         -- don't allow it to go right up against the edge of the map; leave
98         -- room for the wall
99         if x == XMIN or y == YMIN or x == XMAX or y == YMAX then
100            invalid = true
101         end
102         if getmap(x, y)["typ_name"] ~= "stone" then
103            invalid = true
104         end
105      end)
106      if invalid then
107         -- can't grow in this direction
108         dirs[currdir] = false
109         -- potential extension: in this case, cut down on nexpand
110      else
111         -- can grow
112         sel = newsel
113         growths = growths + 1
114      end
115   end
116
117   -- needs to have grown a certain number of times
118   -- possible extension: check whether a !straightline room has grown
119   -- perpendicularly (not always in maindir)
120   if growths > 3 or straightline then
121      return sel
122   else
123      return nil
124   end
125end
126
127ALLDIRS = { "north", "south", "east", "west" }
128
129local snakerooms = {}
130
131for i=1,60 do
132   -- Find an available spot on a wall where a door can be placed leading into a
133   -- corridor or another room.
134   local wallsN = selection.match(" \nw\n.")
135   local wallsS = selection.match(".\nw\n ")
136   local wallsE = selection.match(".w ")
137   local wallsW = selection.match(" w.")
138   local allwalls = wallsN | wallsS | wallsE | wallsW
139   local wx, wy = allwalls:rndcoord(1) -- remove this spot from allwalls
140
141   -- des.terrain({ selection = wallsN, typ="}" })
142   local north, south, east, west = false, false, false, false
143   local dx, dy = 0, 0
144   if wallsN:get(wx, wy) > 0 then
145      dir, dx, dy = "north", 0, -1
146   elseif wallsS:get(wx, wy) > 0 then
147      dir, dx, dy = "south", 0, 1
148   elseif wallsE:get(wx, wy) > 0 then
149      dir, dx, dy = "east", 1, 0
150   elseif wallsW:get(wx, wy) > 0 then
151      dir, dx, dy = "west", -1, 0
152   end
153
154   local tryroom = nil
155   if percent(25) then
156      tryroom = findroom(wx+dx, wy+dy, dir, false)
157   end
158   if tryroom == nil then
159      -- room failed. try a line?
160      tryroom = findroom(wx+dx, wy+dy, dir, true)
161   else
162      -- room succeeded; mark it as a candidate for being filled with snakes
163      snakerooms[#snakerooms+1] = tryroom:clone()
164   end
165   if tryroom ~= nil then
166      -- to make walls, grow in all directions
167      local expanded = tryroom:clone():grow("all")
168      des.terrain({ selection=expanded, typ='-' }) -- this will overwrite wx,wy but that's fine
169      des.terrain({ selection=tryroom, typ='.' })
170
171      local doorstate = percent(50) and "secret" or "random"
172      des.door(doorstate, wx, wy)
173   end
174end
175
176-- possible extension: match things like
177-- |.|
178-- ---
179-- x.x
180-- to create a door on them in post-proc
181
182local deadends = get_deadends()
183
184-- guard against very rare case of no dead ends
185local n_ends = 0
186deadends:iterate(function(x, y)
187   n_ends = n_ends + 1
188end)
189
190if n_ends > 0 then
191   -- Attempt to place the downstair sufficiently far away from the upstair.
192   local stairell = selection.ellipse(ustairx, ustairy, 20, 10, 1):negate()
193   local far_ends = stairell & deadends
194   local dstairx,dstairy = far_ends:rndcoord()
195   if dstairx < 0 or dstairy < 0 then
196      -- pick any dead end; potential future extension is to find the furthest
197      -- one away
198      dstairx, dstairy = deadends:rndcoord()
199   end
200   des.stair({ dir = "down", coord = {dstairx, dstairy} })
201else
202   des.stair("down")
203end
204
205-- traps
206for i=1,30 do
207   des.trap({ no_spider_on_web = true })
208end
209
210-- mummies and other enemies
211local n_mummies = 10 + d(4,4)
212local mummies_in_ends = math.min(math.floor(n_ends * 3 / 4), n_mummies)
213function mkmummy(deadend)
214   local template = { class='M', id='human mummy', waiting=1, coord={deadends:rndcoord()} }
215   if not deadend then
216      template['coord'] = nil
217   end
218   if percent(10) then
219      template['id'] = nil -- generate any M randomly
220   end
221   des.monster(template)
222end
223
224for i=1, mummies_in_ends do
225   mkmummy(true)
226   mummies_in_ends = mummies_in_ends - 1
227   n_mummies = n_mummies - 1
228end
229-- then any remaining mummies
230for i=1,n_mummies do
231   mkmummy(false)
232end
233
234-- "Indy, why does the floor move?"
235local snaketypes = { 'pit viper', 'cobra' }
236local snk_room = #snakerooms > 0 and d(#snakerooms) or nil
237-- des.terrain({ selection = snakerooms[snk_room], typ='L' })
238for i=1,4+d(6) do
239   local snkid = snaketypes[d(#snaketypes)]
240   if snk_room == nil then
241      des.monster(snkid)
242   else
243      des.monster({ id=snkid, coord={snakerooms[snk_room]:rndcoord()} })
244   end
245end
246
247bats = { "bat", "giant bat", "vampire bat" }
248for i=1,3 do
249   des.monster(bats[d(#bats)])
250end
251
252-- treasure!
253for i = 1,10+d(10) do
254   des.object({ id="gold piece", quantity = d(250) })
255end
256for i=1,2+d(3) do
257   local template = { id="chest", coord={deadends:rndcoord()} }
258   if percent(50) then
259      -- make it a mummy trap
260      template['trapped'] = 1
261      template['spe'] = 3
262      template['material'] = 'gold'
263   elseif percent(30) then
264      template['material'] = 'gold'
265   end
266   des.object(template)
267end
268local wpn_mats = { "copper", "silver", "gold", "bone" }
269local wpns = { "short sword", "dagger", "knife", "spear", "javelin", "quarterstaff", "axe", "flail" }
270for i=1,d(2,4) do
271   des.object({ id=wpns[d(#wpns)], material=wpn_mats[d(#wpn_mats)], coord={deadends:rndcoord()} })
272end
273
274