###############################################################################
# layout_geoelf.des: Layouts built around the delve command
###############################################################################

: crawl_require("dlua/layout/zonify.lua")
: crawl_require("dlua/layout/theme.lua")
: crawl_require("dlua/layout/minimum_map_area.lua")


##############################################################
# layout_twisted_cavern
#
# A long winding cavern of roughly constant width, snaking around all the
# level.  It forks from time to time, but never for long.
#
# TODO: Rework and add at weight 5.  Possible ideas on devwiki.
#
NAME: layout_twisted_cavern
DEPTH: D:4-, Depths
WEIGHT: 0
ORIENT: encompass
TAGS:   overwritable layout allow_dup unrand layout_type_narrow_caves
{{
    if is_validating() then return; end

    local gxm, gym = dgn.max_bounds()
    extend_map{width = gxm, height = gym}
    fill_area{fill = 'x', border = 'X'}

    delve(3, 3, 0, -1, 1)
    subst('X = x')

    theme.D.caves_classic(_G)
}}

##############################################################
# layout_twisted_cross
#
# A cross-like layout with 4 arms of different types.  Most of the
#  types are made using delve, making this a more complex form of
#  layout_twisted_cavern.  There are also straighter arm types.
#
# One stairs is always placed in the middle, to ensure that
#  travelling across this map does not get too slow.  This can
#  otherwise be a problem if the stairs appear at the ends of two
#  different delved arms.
#
# This layout starts by dividing the map into 4 quarters, using
#  different glyphs for each.  This is because the delve command
#  might dig tunnels into any wall of the right type and we want
#  to control it.  The map looks something like this before the
#  arms are added:
#
#   XXXXXXXXXXXXX
#   XXmmmmmmmmmXX
#   XcXmmmmmmmXvX
#   XccXmmmmmXvvX
#   XccCXmmmXvvvX
#   XccCCXmXvvvvX
#   XccCccXvvvvvX
#   XccCCXnXvvvvX
#   XccCXnnnXvvvX
#   XccXnnnnnXvvX
#   XcXnnnnnnnXvX
#   XXnnnnnnnnnXX
#   XXXXXXXXXXXXX
#
# There are currently 6 arm types:
#  1. a clone of layout_twisted_cavern
#  2. layout_twisted_cavern with wide areas
#  3. a narrower layout_twisted_cavern
#  4. a honeycomb of little connected passages
#  5. a straight and boring cross arm
#  6. a straight cross arm with a big room on the end
# More arm types welcome!
#
# TODO: improve delve command to take floor type, wall type, and
#       starting position as parameters
#

NAME:   layout_twisted_cross
DEPTH:  Depths
WEIGHT: 5
ORIENT: encompass
TAGS:   overwritable layout unrand layout_type_narrow_caves
TAGS:   no_rotate no_vmirror no_hmirror uniq_layout_twisted_delve
{{
    local gxm, gym = dgn.max_bounds()
    extend_map{width = gxm, height = gym}
    fill_area{fill = 'x', border = 'X'}

    local center_x = math.floor(gxm / 2)
    local center_y = math.floor(gym / 2)

    for x = 1, gxm - 2 do
        for y = 1, gym - 2 do
            local x_off = x - center_x
            local y_off = y - center_y
            local abs_x_off = math.abs(x_off)
            local abs_y_off = math.abs(y_off)

            if (math.abs(abs_x_off - abs_y_off) < 2) then
                -- a big X across the map
                mapgrd[x][y] = "X"
            else
                -- divide the map into 4 quarters
                if (abs_x_off > abs_y_off) then
                    if (x_off < 0) then
                        mapgrd[x][y] = "c"
                    else
                        mapgrd[x][y] = "v"
                    end
                else
                    if (y_off < 0) then
                        mapgrd[x][y] = "m"
                    else
                        mapgrd[x][y] = "n"
                    end
                end
            end
        end
    end

    -- seeds for the 4 corner paths
    mapgrd[center_x - 6][center_y] = "."
    mapgrd[center_x + 6][center_y] = "."
    mapgrd[center_x][center_y - 6] = "."
    mapgrd[center_x][center_y + 6] = "."

    --
    -- roughen the borders
    --  -> this stops delve from making straight paths
    --  -> these will never be seen in-game
    --

    smear_map { onto = "cvmn", smear = "X", iterations = 200 }

    --
    -- choose random types for the branches
    --  -> we put all the branch types in a random order
    --     and use the first 4
    --  -> this guarantees we only get one of each
    --

    local branch_type = {}
    branch_type[0] = 0
    for i = 1, 5 do
        local other_index = crawl.random2(i + 1)
        branch_type[i] = branch_type[other_index]
        branch_type[other_index] = i
    end

    local quarter_glyphs = { "c", "v", "m", "n" }
    for i = 1, 4 do
        subst(quarter_glyphs[i] .. ' = x')

        -- !send infiniplex LUA switch statements
        if (branch_type[i] == 0) then
            -- classic twisted cavern branch
            delve(3, 3, 0,  crawl.random_range(200, 400), 1)

        elseif (branch_type[i] == 1) then
            -- "roomier" twisted cavern branch
            delve(3, 4, 0,  crawl.random_range(250, 500), 1)

        elseif (branch_type[i] == 2) then
            -- narrower twisted cavern branch
            delve(2, 4, 10, crawl.random_range(150, 250), 1)

        elseif (branch_type[i] == 3) then
            -- a mess of interconnected areas
            delve(2, 3, 25, crawl.random_range(200, 300), 10000)

        elseif (branch_type[i] == 4) then
            -- draw a straight "cross" arm outward

            -- really half of thickness
            local arm_thickness = crawl.random_range(1, 2)

            if (i == 1) then
                fill_area { x1 = center_x - 25, y1 = center_y - arm_thickness,
                            x2 = center_x,      y2 = center_y + arm_thickness,
                            fill = "." }
            elseif (i == 2) then
                fill_area { x1 = center_x,      y1 = center_y - arm_thickness,
                            x2 = center_x + 25, y2 = center_y + arm_thickness,
                            fill = "." }
            elseif (i == 3) then
                fill_area { x1 = center_x - arm_thickness, y1 = center_y - 25,
                            x2 = center_x + arm_thickness, y2 = center_y,
                            fill = "." }
            else
                fill_area { x1 = center_x - arm_thickness, y1 = center_y,
                            x2 = center_x + arm_thickness, y2 = center_y + 25,
                            fill = "." }
            end

        elseif (branch_type[i] == 5) then
            -- draw a straight arm outward with a room on it

            -- really half of thickness
            local arm_thickness = crawl.random_range(1, 2)


            local room_func = util.random_choose_weighted(
              { {make_circle,         3},
                {make_diamond,        3},
                {make_square,         2},
                {make_rounded_square, 1} } )
            local room_radius = crawl.random_range(4, 7)

            if (i == 1) then
                fill_area { x1 = center_x - 20, y1 = center_y - arm_thickness,
                            x2 = center_x,      y2 = center_y + arm_thickness,
                            fill = "." }
                room_func({x = center_x - 20, y = center_y,
                           radius = room_radius, fill = "." })
            elseif (i == 2) then
                fill_area { x1 = center_x,      y1 = center_y - arm_thickness,
                            x2 = center_x + 20, y2 = center_y + arm_thickness,
                            fill = "." }
                room_func({x = center_x + 20, y = center_y,
                           radius = room_radius, fill = "." })
            elseif (i == 3) then
                fill_area { x1 = center_x - arm_thickness, y1 = center_y - 20,
                            x2 = center_x + arm_thickness, y2 = center_y,
                            fill = "." }
                room_func({x = center_x, y = center_y - 20,
                           radius = room_radius, fill = "." })
            else
                fill_area { x1 = center_x - arm_thickness, y1 = center_y,
                            x2 = center_x + arm_thickness, y2 = center_y + 20,
                            fill = "." }
                room_func({x = center_x, y = center_y + 20,
                           radius = room_radius, fill = "." })
            end
        end

        subst('x = X')
    end

    --
    -- Place a central cross-shaped room that connects the four
    --  branches.  Add one stairs to prevent (or at least reduce)
    --  really long automovement between levels.
    --

    fill_area { x1 = center_x - 1, y1 = center_y - 5,
                x2 = center_x + 1, y2 = center_y + 5, fill = "." }
    fill_area { x1 = center_x - 5, y1 = center_y - 1,
                x2 = center_x + 5, y2 = center_y + 1, fill = "." }
    mapgrd[center_x][center_y] = "{"
    subst('{ = {([})]')

    -- general finishing stuff
    subst('X = x')
    zonify.map_fill_zones(_G, 1, 'x')
    theme.D.caves_classic(_G)
}}
# Enforce minimum floor size - otherwise could get very tiny floors
validate {{
  return minimum_map_area.is_map_big_enough(_G, minimum_map_area.NARROW_CAVES)
}}

##############################################################
# layout_twisted_circle
#
# A bunch of rooms with 3-wide passages around and between them.
#  The passages are made using delve, making this a more complex
#  form of layout_twisted_cavern.
#
# The layout is enclosed in a circular area, although the rooms
#  can be partially outside this border.
#

NAME:   layout_twisted_circle
DEPTH:  Zot
WEIGHT: 10
ORIENT: encompass
TAGS:   overwritable layout unrand allow_dup layout_type_narrow_caves
TAGS:   no_rotate no_vmirror no_hmirror
{{
    local MAP_RADIUS = 28

    local ROOM_RADIUS_MIN = 3
    local ROOM_RADIUS_MAX = 5
    local room_count      = crawl.random_range(10, 15)

    local DELVE_SIZE_MIN = 50
    local DELVE_SIZE_MAX = 100
    local delve_count    = crawl.random_range(15, 20)
    local delve_sanity   = 1000

    local gxm, gym = dgn.max_bounds()
    extend_map{width = gxm, height = gym, fill = "X"}

    -- this is the area we will use for the map
    local center_x = math.floor(gxm / 2)
    local center_y = math.floor(gym / 2)
    make_circle {x = center_x, y = center_y, radius = MAP_RADIUS, fill = "x" }

    -- block off the primary vault (if any)
    for x = 1, gxm - 2 do
        for y = 1, gym - 2 do
            if dgn.in_vault(x, y) then
                mapgrd[x][y] = "X"
            end
        end
    end

    --
    -- place the room placeholders
    --  -> for now, these are just blocked areas so the paths
    --     don't go through them
    --  -> we will place the real rooms at these same positions
    --     after we have passages to connect them to
    --

    local rooms = {}
    for i = 1, room_count do
        -- choose a random room position in the circle
        local x_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)
        local y_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)
        while (x_off * x_off + x_off * x_off > MAP_RADIUS * MAP_RADIUS) do
            x_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)
            y_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)
        end

        rooms[i] = {}
        rooms[i].x_base = center_x + x_off
        rooms[i].y_base = center_y + y_off
        rooms[i].radius = crawl.random_range(ROOM_RADIUS_MIN, ROOM_RADIUS_MAX)
        rooms[i].x1 = rooms[i].x_base - rooms[i].radius
        rooms[i].x2 = rooms[i].x_base + rooms[i].radius
        rooms[i].y1 = rooms[i].y_base - rooms[i].radius
        rooms[i].y2 = rooms[i].y_base + rooms[i].radius
        rooms[i].floor = "X"
        rooms[i].wall  = "X"
        rooms[i].door_count = 0

        make_round_box (rooms[i])
    end

    -- delve some passages around the rooms
    local placed = 0
    local sanity = 0
    while (placed < delve_count and sanity < delve_sanity) do
        sanity = sanity + 1

        -- choose random starting point to the path
        local x_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)
        local y_off = crawl.random_range(-MAP_RADIUS, MAP_RADIUS)

        -- if its a workable location
        if (x_off * x_off + x_off * x_off < MAP_RADIUS * MAP_RADIUS and
            count_neighbors{ x = center_x + x_off,
                             y = center_y + y_off, feat = "x" } > 6) then

            -- add the path starting location
            mapgrd[center_x + x_off][center_y + y_off] = "."

            -- delve out a path
            local size = crawl.random_range(DELVE_SIZE_MIN, DELVE_SIZE_MAX)
            delve(3, 3, 0, size, 1)

            -- wall off this path so others can't join onto it
            widen_paths{find = "x", replace = "X", passable = ".", boxy = true}
            placed = placed + 1
        end
    end

    -- connect up the passages
    connect_adjacent_rooms { wall = "X", floor = ".", replace = ".",
                             max = 2000 }

    -- place the rooms using the placeholders
    for i = 1, room_count do
        -- these are just updates from last time
        rooms[i].floor = "."
        rooms[i].wall  = "x"
        rooms[i].door_count       = crawl.random_range(2, 4)
        rooms[i].veto_gates       = true
        rooms[i].veto_if_no_doors = true

        local check_size = math.floor(rooms[i].radius / 2)
        if not find_in_area {x1 = rooms[i].x_base - check_size,
                             y1 = rooms[i].y_base - check_size,
                             x2 = rooms[i].x_base + check_size,
                             y2 = rooms[i].y_base + check_size,
                             find = "", find_vault = true } then
            make_round_box (rooms[i])
        end
    end

    -- general finishing stuff
    subst('X = x')
    zonify.map_fill_zones(_G, 1, 'x')
    -- theme.D.caves_classic(_G) -- no special walls in Zot
}}
# Enforce minimum floor size - otherwise could get very tiny floors
validate {{
  return minimum_map_area.is_map_big_enough(_G, minimum_map_area.NARROW_CAVES)
}}

##############################################################
# layout_spider_delve
#
# Previously named "layout_delve".
#
# TODO: Use "layout_type_narrow_caves" tag for passage-y version
#       and "layout_type_open_caves" tag for more open version.
#
NAME:   layout_spider_delve
DEPTH:  Spider
WEIGHT: 25
ORIENT: encompass
TAGS:   overwritable layout allow_dup unrand layout_type_narrow_caves
{{
    if is_validating() then return; end

    local gxm, gym = dgn.max_bounds()
    extend_map{width = gxm, height = gym}
    fill_area{fill = 'x', border = 'X'}

    local ngb_min = 2
    local ngb_max = crawl.random_range(3, 6)
    if crawl.one_chance_in(5) then
      -- sometimes use a higher ngb_max. This may be overridden later
      ngb_max = crawl.random_range(7, 8)
    end
    if crawl.one_chance_in(10) then
      -- sometimes use a more cramped layout
      ngb_min = 1
      ngb_max = crawl.random_range(5, 7)
    elseif crawl.one_chance_in(20) then
      -- or a wider one
      ngb_min = 3
      ngb_max = 4
    end
    local connchance = util.random_choose_weighted
        { {0, 2}, {5, 1}, {20, 1}, {50, 1}, {100, 1} }
    local top = util.random_choose_weighted
        { {1, 1}, {20, 1}, {125, 1} }
    if ngb_max < 7 then
        -- high top and ngb_max together generates a big open level
        -- only allow high top if ngb_max isn't too high
        top = util.random_choose_weighted { {top, 3}, {500, 1}, {999999, 1} }
    end
    crawl.dpr("<lightmagenta>delve(" .. ngb_min .. ", " .. ngb_max .. ", " ..
              connchance .. ", -1, " .. top .. ")</lightmagenta>")
    delve(ngb_min, ngb_max, connchance, -1, top)
    if crawl.one_chance_in(4) then
      crawl.dpr("delve: adding water");
      subst('. = @')
      delve(ngb_min, ngb_max, 100, 100, top)
      subst('. = w')
      subst('@ = .')
    end
    subst('X = x')
}}

################################################################
# layout_cocytus_delve
#
# Previously named "layout_cocytus".
#
# An ice cave approximation for Cocytus.  There is one main
# delved path that is guaranteed to contain at least one up and
# down stairs.  There are also other delved paths and
# diamond-shaped rooms, but these might be behind pools of
# water.  To avoid trapping the player, all random teleports go
# to the main path.
#
# TODO: Serious improvements, less glyph switching
#
NAME:   layout_cocytus_delve
DEPTH:  Coc
WEIGHT: 10
ORIENT: encompass
TAGS:   overwritable layout allow_dup unrand layout_type_narrow_caves
TAGS:   no_rotate no_vmirror no_hmirror
{{
    if is_validating() then return; end

    local gxm, gym = dgn.builder_bounds()
    extend_map{width = gxm, height = gym}

    local map_border = 5
    local map_x_min = map_border
    local map_y_min = map_border
    local map_x_max = gxm - map_border - 1
    local map_y_max = gym - map_border - 1
    fill_area { x1 = map_x_min, y1 = map_y_min,
                x2 = map_x_max, y2 = map_y_max, fill = 'x', border = 'X' }

    -- Due to the behaviour of the spotty_map, delve, and
    --  fill_disconnected functions, we need to make some
    --  strange substitutions to have the right glyphs on the
    --  map when we call these functions.  We will put the
    --  glyphs right again later.
    --    -> spotty_map always expands '.' glyphs with '.'s
    --    -> delve always places '.' glyphs and seems to have
    --       trouble with 'w's
    --    -> fill_disconnected will not fill custom-defined
    --       glyphs, even if they are kfeat-ed to another glyph

    -- Add some water patches.
    --    1. Add some circles of floor
    --    2. Expand the floor circles using spotty_map
    --    3. Convert the floor to unbreakable wall (later water)
    for i = 1, crawl.random_range((20 * gym) / dgn.GYM, (30 * gym) / dgn.GYM) do
        local x = crawl.random_range(map_x_min, map_x_max)
        local y = crawl.random_range(map_y_min, map_y_max)
        local radius = crawl.random_range(0, 1)
        if crawl.one_chance_in(15) then
            radius = crawl.random_range(2, 4)
        end

        make_circle ({ x = x, y = y, radius = radius, fill = '.'})
    end
    local iterations = (crawl.random_range(100, 300) * gym) / dgn.GYM
    spotty_map { boxy = false, iterations = iterations }
    subst('. = R')

    -- Delve the paths
    local ngb_min = 2
    local ngb_max = 4
    -- chance of connecting to an adjacent passage
    local connchance = 0
    local total_count = crawl.random_range(100, 300)
    local top = 10

    -- Delve the main path and add two sets of stairs.  This
    --  guarantees the map is connected without crossing water.
    --  Keep the '@' glyph for connectivity checking later.
    delve(ngb_min, ngb_max, connchance, total_count, top)
    nsubst('. = 1:@ / *:.')
    local x, y = farthest_from('@')
    mapgrd[x][y] = '{'
    x, y = farthest_from('{')
    mapgrd[x][y] = '}'
    subst('. = >')

    -- Delve some smaller paths
    for i = 1, (gym / 20) do
        delve(2, 3, 3, 50, 100)
    end
    subst('. = <')

    -- Fix up the map after delving.
    fill_area { x1 = 0,         y1 = 0,
                x2 = map_x_min, y2 = gym-1,     fill = 'x' }
    fill_area { x1 = map_x_max, y1 = 0,
                x2 = gxm-1,     y2 = gym-1,     fill = 'x' }
    fill_area { x1 = map_x_min, y1 = 0,
                x2 = map_x_max, y2 = map_y_min, fill = 'x' }
    fill_area { x1 = map_x_min, y1 = map_y_max,
                x2 = map_x_max, y2 = gym-1,     fill = 'x' }
    subst('R = w')

    -- Add some rooms
    for i = 1, crawl.random_range(1,5) do
        local x = crawl.random_range(8, gxm-9)
        local y = crawl.random_range(8, gym-9)
        local radius = util.random_choose_weighted
            { {1, 30}, {2, 50}, {3, 15}, {4, 4}, {5, 1} }

         octa_room { x1 = x - radius, y1 = y - radius,
                   x2 = x + radius, y2 = y + radius,
                    oblique = radius, replace = 'x', inside = '<'}
    end

    -- Finishing up stuff.
    --     1. Fill everything not connected to the '@' set when
    --        delving the first path.
    --     2. Set the first delved path to floor.
    --     3. Set the rooms and the other delved paths to
    --        no_tele_into floor ('_').  These areas might be
    --        inescapable if water covers the path out.
    fill_disconnected{wanted = '@'}
    subst('@> = .')
    subst('< = _')
    kfeat('_ = .')
    kprop('_ = no_tele_into')
}}
