1###############################################################################
2# layout_geoelf.des: Layouts buit around the delve command
3###############################################################################
4
5: crawl_require("dlua/layout/zonify.lua")
6: crawl_require("dlua/layout/theme.lua")
7: crawl_require("dlua/layout/minimum_map_area.lua")
8
9
10##############################################################
11# layout_twisted_cavern
12#
13# A long winding cavern of roughly constant width, snaking around all the
14# level.  It forks from time to time, but never for long.
15#
16# TODO: Rework and add at weight 5.  Possible ideas on devwiki.
17#
18NAME: layout_twisted_cavern
19DEPTH: D:4-, Depths
20WEIGHT: 0
21ORIENT: encompass
22TAGS:   overwritable layout allow_dup unrand layout_type_narrow_caves
23{{
24    if is_validating() then return; end
25
26    local gxm, gym = dgn.max_bounds()
27    extend_map{width = gxm, height = gym}
28    fill_area{fill = 'x', border = 'X'}
29
30    delve(3, 3, 0, -1, 1)
31    subst('X = x')
32
33    theme.D.caves_classic(_G)
34}}
35
36##############################################################
37# layout_twisted_cross
38#
39# A cross-like layout with 4 arms of different types.  Most of the
40#  types are made using delve, making this a more complex form of
41#  layout_twisted_cavern.  There are also straighter arm types.
42#
43# One stairs is always placed in the middle, to ensure that
44#  travelling across this map does not get too slow.  This can
45#  otherwise be a problem if the stairs appear at the ends of two
46#  different delved arms.
47#
48# This layout starts by dividing the map into 4 quarters, using
49#  different glyphs for each.  This is because the delve command
50#  might dig tunnels into any wall of the right type and we want
51#  to control it.  The map looks something like this before the
52#  arms are added:
53#
54#   XXXXXXXXXXXXX
55#   XXmmmmmmmmmXX
56#   XcXmmmmmmmXvX
57#   XccXmmmmmXvvX
58#   XccCXmmmXvvvX
59#   XccCCXmXvvvvX
60#   XccCccXvvvvvX
61#   XccCCXnXvvvvX
62#   XccCXnnnXvvvX
63#   XccXnnnnnXvvX
64#   XcXnnnnnnnXvX
65#   XXnnnnnnnnnXX
66#   XXXXXXXXXXXXX
67#
68# There are currently 6 arm types:
69#  1. a clone of layout_twisted_cavern
70#  2. layout_twisted_cavern with wide areas
71#  3. a narrower layout_twisted_cavern
72#  4. a honeycomb of little connected passages
73#  5. a straight and boring cross arm
74#  6. a straight cross arm with a big room on the end
75# More arm types welcome!
76#
77# TODO: improve delve command to take floor type, wall type, and
78#       starting position as parameters
79#
80
81NAME:   layout_twisted_cross
82DEPTH:  Depths
83WEIGHT: 5
84ORIENT: encompass
85TAGS:   overwritable layout unrand layout_type_narrow_caves
86TAGS:   no_rotate no_vmirror no_hmirror uniq_layout_twisted_delve
87{{
88    local gxm, gym = dgn.max_bounds()
89    extend_map{width = gxm, height = gym}
90    fill_area{fill = 'x', border = 'X'}
91
92    local center_x = math.floor(gxm / 2)
93    local center_y = math.floor(gym / 2)
94
95    for x = 1, gxm - 2 do
96        for y = 1, gym - 2 do
97            local x_off = x - center_x
98            local y_off = y - center_y
99            local abs_x_off = math.abs(x_off)
100            local abs_y_off = math.abs(y_off)
101
102            if (math.abs(abs_x_off - abs_y_off) < 2) then
103                -- a big X across the map
104                mapgrd[x][y] = "X"
105            else
106                -- divide the map into 4 quarters
107                if (abs_x_off > abs_y_off) then
108                    if (x_off < 0) then
109                        mapgrd[x][y] = "c"
110                    else
111                        mapgrd[x][y] = "v"
112                    end
113                else
114                    if (y_off < 0) then
115                        mapgrd[x][y] = "m"
116                    else
117                        mapgrd[x][y] = "n"
118                    end
119                end
120            end
121        end
122    end
123
124    -- seeds for the 4 corner paths
125    mapgrd[center_x - 6][center_y] = "."
126    mapgrd[center_x + 6][center_y] = "."
127    mapgrd[center_x][center_y - 6] = "."
128    mapgrd[center_x][center_y + 6] = "."
129
130    --
131    -- roughen the borders
132    --  -> this stops delve from making straight paths
133    --  -> these will bever be seen in-game
134    --
135
136    smear_map { onto = "cvmn", smear = "X", iterations = 200 }
137
138    --
139    -- choose random types for the branches
140    --  -> we put all the branch types in a random order
141    --     and use the first 4
142    --  -> this guarantees we only get one of each
143    --
144
145    local branch_type = {}
146    branch_type[0] = 0
147    for i = 1, 5 do
148        local other_index = crawl.random2(i + 1)
149        branch_type[i] = branch_type[other_index]
150        branch_type[other_index] = i
151    end
152
153    local quarter_glyphs = { "c", "v", "m", "n" }
154    for i = 1, 4 do
155        subst(quarter_glyphs[i] .. ' = x')
156
157        -- !send infiniplex LUA switch statements
158        if (branch_type[i] == 0) then
159            -- classic twisted cavern branch
160            delve(3, 3, 0,  crawl.random_range(200, 400), 1)
161
162        elseif (branch_type[i] == 1) then
163            -- "roomier" twisted cavern branch
164            delve(3, 4, 0,  crawl.random_range(250, 500), 1)
165
166        elseif (branch_type[i] == 2) then
167            -- narrower twisted cavern branch
168            delve(2, 4, 10, crawl.random_range(150, 250), 1)
169
170        elseif (branch_type[i] == 3) then
171            -- a mess of interconnected areas
172            delve(2, 3, 25, crawl.random_range(200, 300), 10000)
173
174        elseif (branch_type[i] == 4) then
175            -- draw a straight "cross" arm outward
176
177            -- really half of thickness
178            local arm_thickness = crawl.random_range(1, 2)
179
180            if (i == 1) then
181                fill_area { x1 = center_x - 25, y1 = center_y - arm_thickness,
182                            x2 = center_x,      y2 = center_y + arm_thickness,
183                            fill = "." }
184            elseif (i == 2) then
185                fill_area { x1 = center_x,      y1 = center_y - arm_thickness,
186                            x2 = center_x + 25, y2 = center_y + arm_thickness,
187                            fill = "." }
188            elseif (i == 3) then
189                fill_area { x1 = center_x - arm_thickness, y1 = center_y - 25,
190                            x2 = center_x + arm_thickness, y2 = center_y,
191                            fill = "." }
192            else
193                fill_area { x1 = center_x - arm_thickness, y1 = center_y,
194                            x2 = center_x + arm_thickness, y2 = center_y + 25,
195                            fill = "." }
196            end
197
198        elseif (branch_type[i] == 5) then
199            -- draw a straight arm outward with a room on it
200
201            -- really half of thickness
202            local arm_thickness = crawl.random_range(1, 2)
203
204
205            local room_func = util.random_choose_weighted(
206              { {make_circle,         3},
207                {make_diamond,        3},
208                {make_square,         2},
209                {make_rounded_square, 1} } )
210            local room_radius = crawl.random_range(4, 7)
211
212            if (i == 1) then
213                fill_area { x1 = center_x - 20, y1 = center_y - arm_thickness,
214                            x2 = center_x,      y2 = center_y + arm_thickness,
215                            fill = "." }
216                room_func({x = center_x - 20, y = center_y,
217                           radius = room_radius, fill = "." })
218            elseif (i == 2) then
219                fill_area { x1 = center_x,      y1 = center_y - arm_thickness,
220                            x2 = center_x + 20, y2 = center_y + arm_thickness,
221                            fill = "." }
222                room_func({x = center_x + 20, y = center_y,
223                           radius = room_radius, fill = "." })
224            elseif (i == 3) then
225                fill_area { x1 = center_x - arm_thickness, y1 = center_y - 20,
226                            x2 = center_x + arm_thickness, y2 = center_y,
227                            fill = "." }
228                room_func({x = center_x, y = center_y - 20,
229                           radius = room_radius, fill = "." })
230            else
231                fill_area { x1 = center_x - arm_thickness, y1 = center_y,
232                            x2 = center_x + arm_thickness, y2 = center_y + 20,
233                            fill = "." }
234                room_func({x = center_x, y = center_y + 20,
235                           radius = room_radius, fill = "." })
236            end
237        end
238
239        subst('x = X')
240    end
241
242    --
243    -- Place a central cross-shaped room that connects the four
244    --  branches.  Add one stairs to prevent (or at least reduce)
245    --  really long automovement between levels.
246    --
247
248    fill_area { x1 = center_x - 1, y1 = center_y - 5,
249                x2 = center_x + 1, y2 = center_y + 5, fill = "." }
250    fill_area { x1 = center_x - 5, y1 = center_y - 1,
251                x2 = center_x + 5, y2 = center_y + 1, fill = "." }
252    mapgrd[center_x][center_y] = "{"
253    subst('{ = {([})]')
254
255    -- general finishing stuff
256    subst('X = x')
257    zonify.map_fill_zones(_G, 1, 'x')
258    theme.D.caves_classic(_G)
259}}
260# Enforce minimum floor size - otherwise could get very tiny floors
261validate {{
262  return minimum_map_area.is_map_big_enough(_G, minimum_map_area.NARROW_CAVES)
263}}
264
265##############################################################
266# layout_twisted_circle
267#
268# A bunch of rooms with 3-wide passages around and between them.
269#  The passages are made using delve, making this a more complex
270#  form of layout_twisted_cavern.
271#
272# The layout is enclosed in a circular area, although the rooms
273#  can be partially outside this border.
274#
275
276NAME:   layout_twisted_circle
277DEPTH:  Zot
278WEIGHT: 10
279ORIENT: encompass
280TAGS:   overwritable layout unrand allow_dup layout_type_narrow_caves
281TAGS:   no_rotate no_vmirror no_hmirror
282{{
283    local MAP_RADIUS = 28
284
285    local ROOM_RADIUS_MIN = 3
286    local ROOM_RADIUS_MAX = 5
287    local room_count      = crawl.random_range(10, 15)
288
289    local DELVE_SIZE_MIN = 50
290    local DELVE_SIZE_MAX = 100
291    local delve_count    = crawl.random_range(15, 20)
292    local delve_sanity   = 1000
293
294    local gxm, gym = dgn.max_bounds()
295    extend_map{width = gxm, height = gym, fill = "X"}
296
297    -- this is the area we will use for the map
298    local center_x = math.floor(gxm / 2)
299    local center_y = math.floor(gym / 2)
300    make_circle {x = center_x, y = center_y, radius = MAP_RADIUS, fill = "x" }
301
302    -- block off the primary vault (if any)
303    for x = 1, gxm - 2 do
304        for y = 1, gym - 2 do
305            if dgn.in_vault(x, y) then
306                mapgrd[x][y] = "X"
307            end
308        end
309    end
310
311    --
312    -- place the room placeholders
313    --  -> for now, these are just blocked areas so the paths
314    --     don't go through them
315    --  -> we will place the real rooms at these same positions
316    --     after we have passages to connect them to
317    --
318
319    local rooms = {}
320    for i = 1, room_count do
321        -- choose a random room position in the circle
322        local x_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)
323        local y_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)
324        while (x_off * x_off + x_off * x_off > MAP_RADIUS * MAP_RADIUS) do
325            x_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)
326            y_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)
327        end
328
329        rooms[i] = {}
330        rooms[i].x_base = center_x + x_off
331        rooms[i].y_base = center_y + y_off
332        rooms[i].radius = crawl.random_range(ROOM_RADIUS_MIN, ROOM_RADIUS_MAX)
333        rooms[i].x1 = rooms[i].x_base - rooms[i].radius
334        rooms[i].x2 = rooms[i].x_base + rooms[i].radius
335        rooms[i].y1 = rooms[i].y_base - rooms[i].radius
336        rooms[i].y2 = rooms[i].y_base + rooms[i].radius
337        rooms[i].floor = "X"
338        rooms[i].wall  = "X"
339        rooms[i].door_count = 0
340
341        make_round_box (rooms[i])
342    end
343
344    -- delve some passages around the rooms
345    local placed = 0
346    local sanity = 0
347    while (placed < delve_count and sanity < delve_sanity) do
348        sanity = sanity + 1
349
350        -- choose random starting point to the path
351        local x_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)
352        local y_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)
353
354        -- if its a workable location
355        if (x_off * x_off + x_off * x_off < MAP_RADIUS * MAP_RADIUS and
356            count_neighbors{ x = center_x + x_off,
357                             y = center_y + y_off, feat = "x" } > 6) then
358
359            -- add the path starting location
360            mapgrd[center_x + x_off][center_y + y_off] = "."
361
362            -- delve out a path
363            local size = crawl.random_range(DELVE_SIZE_MIN, DELVE_SIZE_MAX)
364            delve(3, 3, 0, size, 1)
365
366            -- wall off this path so others can't join onto it
367            widen_paths{find = "x", replace = "X", passable = ".", boxy = true}
368            placed = placed + 1
369        end
370    end
371
372    -- connect up the passages
373    connect_adjacent_rooms { wall = "X", floor = ".", replace = ".",
374                             max = 2000 }
375
376    -- place the rooms using the placeholders
377    for i = 1, room_count do
378        -- these are just updates from last time
379        rooms[i].floor = "."
380        rooms[i].wall  = "x"
381        rooms[i].door_count       = crawl.random_range(2, 4)
382        rooms[i].veto_gates       = true
383        rooms[i].veto_if_no_doors = true
384
385        local check_size = math.floor(rooms[i].radius / 2)
386        if not find_in_area {x1 = rooms[i].x_base - check_size,
387                             y1 = rooms[i].y_base - check_size,
388                             x2 = rooms[i].x_base + check_size,
389                             y2 = rooms[i].y_base + check_size,
390                             find = "", find_vault = true } then
391            make_round_box (rooms[i])
392        end
393    end
394
395    -- general finishing stuff
396    subst('X = x')
397    zonify.map_fill_zones(_G, 1, 'x')
398    -- theme.D.caves_classic(_G) -- no special walls in Zot
399}}
400# Enforce minimum floor size - otherwise could get very tiny floors
401validate {{
402  return minimum_map_area.is_map_big_enough(_G, minimum_map_area.NARROW_CAVES)
403}}
404
405##############################################################
406# layout_spider_delve
407#
408# Previously named "layout_delve".
409#
410# TODO: Use "layout_type_narrow_caves" tag for passage-y version
411#       and "layout_type_open_caves" tag for more open version.
412#
413NAME:   layout_spider_delve
414DEPTH:  Spider
415WEIGHT: 25
416ORIENT: encompass
417TAGS:   overwritable layout allow_dup unrand layout_type_narrow_caves
418{{
419    if is_validating() then return; end
420
421    local gxm, gym = dgn.max_bounds()
422    extend_map{width = gxm, height = gym}
423    fill_area{fill = 'x', border = 'X'}
424
425    local ngb_min = 2
426    local ngb_max = crawl.random_range(3, 8)
427    if crawl.one_chance_in(10) then
428      -- sometimes use a more cramped layout
429      ngb_min = 1
430      ngb_max = crawl.random_range(5, 7)
431    end
432    if crawl.one_chance_in(20) then
433      -- or a wider one
434      ngb_min = 3
435      ngb_max = 4
436    end
437    local connchance = util.random_choose_weighted
438        { {0, 2}, {5, 1}, {20, 1}, {50, 1}, {100, 1} }
439    local top = util.random_choose_weighted
440        { {1, 1}, {20, 1}, {125, 1}, {500, 1}, {999999, 1} }
441    crawl.dpr("<lightmagenta>delve(" .. ngb_min .. ", " .. ngb_max .. ", " ..
442              connchance .. ", -1, " .. top .. ")</lightmagenta>")
443    delve(ngb_min, ngb_max, connchance, -1, top)
444    if crawl.coinflip() then
445      crawl.dpr("delve: adding water");
446      subst('. = @')
447      delve(ngb_min, ngb_max, 100, 100, top)
448      subst('. = w')
449      subst('@ = .')
450    end
451    subst('X = x')
452}}
453
454################################################################
455# layout_cocytus_delve
456#
457# Previously named "layout_cocytus".
458#
459# An ice cave approximation for Cocytus.  There is one main
460# delved path that is guaranteed to contain at least one up and
461# down stairs.  There are also other delved paths and
462# diamond-shaped rooms, but these might be behind pools of
463# water.  To avoid trapping the player, all random teleports go
464# to the main path.
465#
466# TODO: Serious improvements, less glyph switching
467#
468NAME:   layout_cocytus_delve
469DEPTH:  Coc
470WEIGHT: 10
471ORIENT: encompass
472TAGS:   overwritable layout allow_dup unrand layout_type_narrow_caves
473TAGS:   no_rotate no_vmirror no_hmirror
474{{
475    if is_validating() then return; end
476
477    local gxm, gym = dgn.max_bounds()
478    extend_map{width = gxm, height = gym}
479
480    local map_border = 5
481    local map_x_min = map_border
482    local map_y_min = map_border
483    local map_x_max = gxm - map_border - 1
484    local map_y_max = gym - map_border - 1
485    fill_area { x1 = map_x_min, y1 = map_y_min,
486                x2 = map_x_max, y2 = map_y_max, fill = 'x', border = 'X' }
487
488    -- Due to the behaviour of the spotty_map, delve, and
489    --  fill_disconnected functions, we need to make some
490    --  strange substitutions to have the right glyphs on the
491    --  map when we call these functions.  We will put the
492    --  glyphs right again later.
493    --    -> spotty_map always expands '.' glyphs with '.'s
494    --    -> delve always places '.' glyphs and seems to have
495    --       trouble with 'w's
496    --    -> fill_disconnected will not fill custom-defined
497    --       glyphs, even if they are kfeat-ed to another glyph
498
499    -- Add some water patches.
500    --    1. Add some circles of floor
501    --    2. Expand the floor circles using spotty_map
502    --    3. Convert the floor to unbreakable wall (later water)
503    for i = 1, crawl.random_range(40, 60) do
504        local x = crawl.random_range(map_x_min, map_x_max)
505        local y = crawl.random_range(map_y_min, map_y_max)
506        local radius = crawl.random_range(0, 1)
507        if crawl.one_chance_in(15) then
508            radius = crawl.random_range(2, 4)
509        end
510
511        make_circle ({ x = x, y = y, radius = radius, fill = '.'})
512    end
513    local iterations = crawl.random_range(150, 350)
514    spotty_map { boxy = false, iterations = iterations }
515    subst('. = X')
516
517    -- Delve the paths
518    local ngb_min = 2
519    local ngb_max = 5
520    -- chance of connecting to an adjacent passage
521    local connchance = 0
522    local total_count = crawl.random_range(200, 500)
523    local top = 10
524
525    -- Delve the main path and add two sets of stairs.  This
526    --  guarantees the map is connected without crossing water.
527    --  Keep the '@' glyph for connectivity checking later.
528    delve(ngb_min, ngb_max, connchance, total_count, top)
529    nsubst('. = 1:@ / *:.')
530    local x, y = farthest_from('@')
531    mapgrd[x][y] = '{'
532    x, y = farthest_from('{')
533    mapgrd[x][y] = '}'
534    subst('. = >')
535
536    -- Delve some smaller paths
537    for i = 1, crawl.random_range(1, 3) do
538        ngb_min = util.random_choose_weighted { {1, 3}, {2, 1} }
539        ngb_max = crawl.random_range(2, 5)
540        connchance = util.random_choose_weighted
541            { {0, 1}, {2, 2}, {5, 1}, {20, 1} }
542        total_count = crawl.random_range(50, 150)
543        top = util.random_choose_weighted
544            { {1, 1}, {20, 1}, {125, 1}, {500, 1}, {999999, 1} }
545
546        delve(2, 3, 3, 150, 100)
547    end
548    subst('. = <')
549
550    -- Fix up the map after delving.
551    --    1. The edges become walls
552    --    2. The remaining unbreakable walls become water
553    fill_area { x1 = 0,         y1 = 0,
554                x2 = map_x_min, y2 = gym-1,     fill = 'x' }
555    fill_area { x1 = map_x_max, y1 = 0,
556                x2 = gxm-1,     y2 = gym-1,     fill = 'x' }
557    fill_area { x1 = map_x_min, y1 = 0,
558                x2 = map_x_max, y2 = map_y_min, fill = 'x' }
559    fill_area { x1 = map_x_min, y1 = map_y_max,
560                x2 = map_x_max, y2 = gym-1,     fill = 'x' }
561    subst('X = w')
562
563    -- Add some rooms
564    for i = 1, crawl.random_range(15, 25) do
565        local x = crawl.random_range(8, gxm-9)
566        local y = crawl.random_range(8, gym-9)
567        local radius = util.random_choose_weighted
568            { {1, 30}, {2, 50}, {3, 15}, {4, 4}, {5, 1} }
569
570        octa_room { x1 = x - radius, y1 = y - radius,
571                    x2 = x + radius, y2 = y + radius,
572                    oblique = radius, replace = 'x', inside = '<'}
573    end
574
575    -- Finishing up stuff.
576    --     1. Fill everything not connected to the '@' set when
577    --        delving the first path.
578    --     2. Set the first delved path to floor.
579    --     3. Set the rooms and the other delved paths to
580    --        no_tele_into floor ('_').  These areas might be
581    --        inescapable if water covers the path out.
582    fill_disconnected{wanted = '@'}
583    subst('@> = .')
584    subst('< = _')
585    kfeat('_ = .')
586    kprop('_ = no_tele_into')
587}}
588