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