###############################################################################
# layout.des: All large layout vaults go here. These are defined by having
#             both ORIENT: encompass and TAGS: layout. These are not true
#             vaults, in that the dungeon builder can add other vaults on
#             top of them.
#
###############################################################################

{{
    -- Call func num times around a circle of radius centered at (x, y)
    function apply_circle(x, y, num, radius, scale_x, scale_y, func)
        radius = math.floor(radius)
        for n = 1, num do
            local rad = n * 2 * math.pi / num

            local rx = math.floor(math.cos(rad) * radius * scale_x + 0.5)
            local ry = math.floor(math.sin(rad) * radius * scale_y + 0.5)

            func(x + rx, y + ry)
        end
    end

    function spotty_stairs(mapgrd)
        local gxm, gym = dgn.max_bounds()

        local in_slime = you.in_branch("slime")
        local elevator = not in_slime

        local stairs = { '}', ')', ']', '(', '[' }

        local make_floor = function (x, y)
            mapgrd[x][y] = '.'
        end

        if not elevator then table.insert(stairs, '{') end

        for i, glyph in ipairs(stairs) do
            local x, y
            repeat
                x = crawl.random_range(5, gxm - 5)
                y = crawl.random_range(5, gym - 5)
            until mapgrd[x][y] == 'x' and mapgrd[x + 1][y] == 'x'

            mapgrd[x][y] = glyph

            apply_circle(x, y, 4, 1, 1, 1, make_floor)

            if in_slime then
                apply_circle(x, y, 8, 2, 1, 1, make_floor)
            end

            if elevator and glyph == '}' then
                mapgrd[x + 1][y] = '{'
            end
        end
    end

    function random_wall_material(e)
        if not you.in_branch("Lair") and crawl.one_chance_in(20) then
            e.subst('x : ccv')
        end
    end
}}

##############################################################
# layout_forbidden_doughnut
#
# This replaces dungeon.cc:_plan_1(). It usually creates a
# room with a large hole in the middle.
#
NAME:   layout_forbidden_donut
DEPTH:  4-22, Pan, !Vault
WEIGHT: 1 (Pan), 10
ORIENT: encompass
TAGS:   overwritable layout allow_dup unrand
{{
    local gxm, gym = dgn.max_bounds()
    extend_map{width=gxm, height=gym, fill='x'}

    layout_type "open"

    local width = (10 - crawl.random2(7))

    local floor = '.'
    local wall = 'x'

    -- construct donut
    fill_area{x1=10, y1=10, x2=gxm-10, y2=10+width, fill=floor}
    fill_area{x1=10, y1=60-width, x2=gxm-10, y2=gym-10, fill=floor}
    fill_area{x1=10, y1=10, x2=10+width, y2=gym-10, fill=floor}
    fill_area{x1=60-width, y1=10, x2=gxm-10, y2=gym-10, fill=floor}

    local spotty = crawl.coinflip()
    local smears = crawl.random2(300)

    -- sometimes add a hallway cross through center
    if crawl.coinflip() then
        local width2 = 1 + crawl.random2(5)

        fill_area{x1=10, y1=gym/2-width2, x2=gxm-10, y2=gym/2+width2, fill=floor}
        fill_area{x1=gxm/2-width2, y1=10, x2=gxm/2+width2, y2=gym-10, fill=floor}

        -- sometimes add a small octagon room
        if crawl.coinflip() then
            local obl = 0
            if crawl.coinflip() then
                obl = 5 + crawl.random2(20)
            end

            local fill = crawl.random_element({
                ["."] = 10,
                ["w"] = 5,
                ["l"] = 1,
            })
            octa_room{x1=25, y1=25, x2=gxm-25, y2=gym-25, oblique=obl, replace='x', inside=fill}

            -- decrease spotty chance
            spotty = crawl.one_chance_in(5)
        end
    end

    local spotty_boxy = crawl.coinflip()
    local smear_boxy = crawl.coinflip()

    if spotty then
        spotty_map{boxy=spotty_boxy}
    end
    if not spotty and crawl.one_chance_in(4) or spotty then
        smear_map{iterations=smears, smear='x', onto='.', boxy=smear_boxy}
    end

    random_wall_material(_G)
}}
MAP
ENDMAP

##############################################################
# layout_cross
#
# This replaces dungeon.cc:_plan_2(). It creates a large cross
# of varying width.
#
NAME:    layout_cross
DEPTH:   Pan, 4-22, !Vault
WEIGHT:  1 (Pan), 10
ORIENT:  encompass
TAGS:    overwritable layout allow_dup unrand

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

    layout_type "cross"

    local floor = '.'
    local wall = 'x'
    local border = 'x'

    local width = 5 - crawl.random2(5)
    local height = 5 - crawl.random2(5)

    -- Include a small possibility of adding windows around the cross.
    -- This layout can get used with spotty_level, so don't make this
    -- chance too large as lava/water prevents that from happening.
    local window = not you.in_branch("Lair") and crawl.one_chance_in(20)

    if window then
        if crawl.coinflip() then
            wall = 'l'
            border = 'L'
            kfeat("L = endless_lava")
        else
            wall = 'w'
            border = 'L'
            kfeat("L = open_sea")
        end
    end

    fill_area{fill = border}
    fill_area{x1=1, y1=1, x2=gxm-2, y2=gym-2, fill = wall}

    -- create window
    if window then
        local clear = 'm'
        fill_area{x1=10, y1=gym/2-height-1, x2=gxm-10, y2=gym/2-height-1, fill=clear}
        fill_area{x1=10, y1=gym/2+height+1, x2=gxm-10, y2=gym/2+height+1, fill=clear}
        fill_area{x1=gxm/2-width-1, y1=10, x2=gxm/2-width-1, y2=gym-10, fill=clear}
        fill_area{x1=gxm/2+width+1, y1=10, x2=gxm/2+width+1, y2=gym-10, fill=clear}
    end

    -- create a cross
    fill_area{x1=10, y1=gym/2-height, x2=gxm-10, y2=gym/2+height, fill=floor}
    fill_area{x1=gxm/2-width, y1=10, x2=gxm/2+width, y2=gym-10, fill=floor}

    if not crawl.one_chance_in(4) then
        spotty_map{boxy = crawl.coinflip()}
    end

    random_wall_material(_G)
}}
MAP
ENDMAP

#############################################################
# layout_big_octagon
#
# This replaces dungeon.cc:_plan_6(). It has an octagonal
# room with some number of pillars in the middle. The stairs
# are generally all grouped together.
#

NAME: layout_big_octagon
DEPTH:   4-22, !Vault
WEIGHT:  1 (Pan), 10
ORIENT:  encompass
TAGS:    overwritable layout allow_dup unrand

{{
    -- Step 1: Big octagon.

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

    layout_type "open"

    local oblique = 10 + crawl.random2(20)
    octa_room{
        x1 = 10,
        y1 = 10,
        x2 = gxm-10,
        y2 = gym-10,
        oblique = oblique,
        replace = 'x',
        inside = '.'}

    local do_smear = crawl.coinflip()
    if do_smear then
        local iterations = 100 + crawl.random2(800)
        smear_map{iterations = iterations, boxy = false}

        --Fill in disconnected zones now, prior to adding the stairs.
        --Temporarily set a (passable) unique symbol in the middle of the map,
        --to guarantee everything is connected to it after smearing.
        mapgrd[gxm/2][gym/2] = '@'
        fill_disconnected{wanted = '@'}
        mapgrd[gxm/2][gym/2] = '.'
    end

    -- Step 2: Add pillars

    -- pillar types and relative weights
    local pillar_fill = {
        ["x"] = 15,
        ["b"] = 5,
        ["v"] = 4,
        ["m"] = 3,
        ["w"] = 2,
        ["l"] = 1,
    }
    if (you.in_branch("lair")) then
        pillar_fill["t"] = 15
    end

    -- Potential pillar drawing routines
    local pillar_func = {
        make_circle,
        make_diamond,
        make_square,
        make_rounded_square}

    -- Pillar size params
    -- NOTE: Be careful about tweaking the ranges here. Pillars that are
    -- too large, close, or large in number can entirely surround the center.

    local pfunc = pillar_func[crawl.random2(#pillar_func) + 1]
    pfunc = make_diamond
    local num = 3 + crawl.random2(9)
    local pillar_radius = 1 + crawl.random2(3)
    local circle_radius = 2 + crawl.random2(6) + pillar_radius * 2 + num / 2
    local fill = crawl.random_element(pillar_fill)

    -- beautification hack: no "circle" pillars of radius 1
    if pfunc == make_circle and pillar_radius == 1 then
        fill = crawl.random_element({"G", "X", "t"})
        kfeat("X = stone_arch")
    end

    -- Finally, make the pillars
    local make_pillar = function(x, y)
        return pfunc({
            x = x,
            y = y,
            radius = pillar_radius,
            fill = fill})
    end
    apply_circle(gxm/2, gym/2, num, circle_radius, 1, 1, make_pillar)

    -- Step 3: Create stairs

    -- Potential stair locations
    -- 0) random
    -- 1) inside
    -- 2) up
    -- 3) right
    -- 4) down
    -- 5) left

    local up_loc = crawl.random2(6)
    local down_loc = crawl.random2(6)
    while up_loc == down_loc do
        down_loc = crawl.random2(6)
    end

    local up_stairs = {"{", "(", "["}
    local down_stairs = {"}", ")", "]"}
    local full_stair_set = {[up_loc] = up_stairs, [down_loc] = down_stairs}

    for loc, stair_list in pairs (full_stair_set) do
        for i = 1, #stair_list do
            local st = stair_list[i]

            if loc == 0 then
                replace_random({find = ".", replace = st, required = true})
            elseif loc == 1 then
                mapgrd[gxm/2 + i - 2][gym/2 + 1 - math.abs(i - 2)] = st
            elseif loc == 2 then
                replace_first({
                    required = true,
                    x = gxm/2 + i - 2,
                    y = 0,
                    xdir = 0,
                    ydir = 1,
                    find = ".",
                    replace = st})
            elseif loc == 3 then
                replace_first({
                    required = true,
                    x = gxm - 1,
                    y = gym/2 + i - 2,
                    xdir = -1,
                    ydir = 0,
                    find = ".",
                    replace = st})
            elseif loc == 4 then
                replace_first({
                    required = true,
                    x = gxm/2 + i - 2,
                    y = gym - 1,
                    xdir = 0,
                    ydir = -1,
                    find = ".",
                    replace = st})
            elseif loc == 5 then
                replace_first({
                    required = true,
                    x = 0,
                    y = gym/2 + i - 2,
                    xdir = 1,
                    ydir = 0,
                    find = ".",
                    replace = st})
            end
        end
    end

    if (you.in_branch("D") and you.subdepth() > 21 or you.where() == "Pan")
      and crawl.coinflip()
    then
        mapgrd[40][36] = 'O'
        mapgrd[41][36] = 'O'
        nsubst('O = 1:O / *:.')
        kfeat('O = enter_abyss')
    end

    random_wall_material(_G)
}}
MAP
ENDMAP

##############################################################
# layout_rooms
#
# This replaces dungeon.cc:_plan_3().
#
NAME: layout_rooms
DEPTH: Pan, 4-22, !Vault
WEIGHT: 6 (Pan), 20
ORIENT: encompass
TAGS: overwritable layout allow_dup unrand
{{
    function random_room_point(room)
        return dgn.point(room.x1 + crawl.random2(room.x2 - room.x1),
                         room.y1 + crawl.random2(room.y2 - room.y1))
    end

    function join_the_dots_p(start, finish)
        return join_the_dots({
            x1 = start.x,
            y1 = start.y,
            x2 = finish.x,
            y2 = finish.y})
    end

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

    layout_type "rooms"

    local num_rooms = 30 + crawl.random2(90)
    local exclusive = not crawl.one_chance_in(10)
    local exclusive2 = crawl.coinflip()

    local rooms = {}

    for i = 0, num_rooms do
        local new_room = {
            x1 = 10 + crawl.random2(50),
            y1 = 10 + crawl.random2(40)
        }
        new_room.x2 = new_room.x1 + 2 + crawl.random2(8)
        new_room.y2 = new_room.y1 + 2 + crawl.random2(8)

        local not_walls = count_antifeature_in_box({
            x1 = new_room.x1,
            y1 = new_room.y1,
            x2 = new_room.x2,
            y2 = new_room.y2,
            feat = "x"})

        if (not exclusive or not_walls == 0) then
            replace_area({
                x1 = new_room.x1,
                y1 = new_room.y1,
                x2 = new_room.x2,
                y2 = new_room.y2,
                find = "x",
                replace = "."})

            if #rooms > 0 and not exclusive2 then
                join_the_dots_p(random_room_point(new_room),
                                random_room_point(rooms[#rooms]))
            end

            table.insert(rooms, new_room)
            if #rooms >= 30 then
                break
            end
        end
    end

    if exclusive2 then
        for i = 2, #rooms do
          join_the_dots_p(random_room_point(rooms[i]),
                          random_room_point(rooms[i - 1]))
        end
    end

    random_wall_material(_G)
}}
MAP
ENDMAP

##############################################################
# layout_city
#
# This replaces dungeon.cc:_city_level().
#
NAME: layout_city
DEPTH: Dis, Vault, 15-
WEIGHT: 1 (Vault), 10
CHANCE: 100% (Dis)
ORIENT: encompass
TAGS: overwritable layout allow_dup unrand
{{
    function treasure_area (x1, y1, x2, y2)
        x2 = x2 + 1
        y2 = y2 + 1

        if x2 <= x2 or y2 <= y2 then
            return false
        end

        if (x2 - x1) * (y2 - y1) >= 40 then
            return false
        end

        local tl = dgn.point(x1, y1)
        local br = dgn.point(x2 - 1, y2 - 1)

        for point in iter.rect_iterator(tl, br) do
            if is_valid_coord {x = point.x, y = point.y } then
                if is_passable_coord { x=point.x, y=point.y } or crawl.coinflip() then
                    mapgrd[point.x][point.y] = "|"
                end
            end
        end

        return true
    end

    local gxm, gym = dgn.max_bounds()

    extend_map{width = gxm, height = gym, fill = 'x'}

    layout_type "city"

    local wall_type_room
    local wall_type
    local rooms = {}

    local xs = 0
    local ys = 0
    local ax1 = 0
    local bx2 = 0
    local ay1 = 0
    local by2 = 0
    local i, j

    wall_type = crawl.random_element { ['x'] = 3, ['c'] = 3, ['v'] = 2 }

    if crawl.one_chance_in(100) then
        wall_type = 'b'
    end

    fill_area { x1=8, y1=8, x2=gxm-9, y2=gym-9, fill="." }

    for i = 0, 5 do
        for j = 0, 4 do
            xs = 8 + (i * 13)
            ys = 8 + (j * 14)
            a1 = xs + crawl.random2avg(5, 2);
            a2 = ys + crawl.random2avg(5, 2);
            b1 = xs + 11 - crawl.random2avg(5, 2);
            b2 = ys + 11 - crawl.random2avg(5, 2);

            if crawl.x_chance_in_y(6, 7) and is_valid_coord {x=a1, y=a2} and is_valid_coord{x=b1, y=b2} then
                wall_type_room = crawl.random_element {
                    [wall_type] = 216,
                    ['c'] = 9,
                    ['x'] = 9,
                    ['v'] = 5
                }

                if crawl.one_chance_in(250) then
                    wall_type_room = "b"
                end

                table.insert(rooms, {a1, a2, b1, b2})
                make_box { x1=a1, y1=a2, x2=b1, y2=b2, wall=wall_type_room }

                if b1 - a1 > 5 and b2 - a2 > 5 and crawl.one_chance_in(8) then
                    table.insert(rooms, {a1+2, a2+2, b1-2, b2-2})
                    make_box { x1=a1+2, y1=a2+2, x2=b1-2, y2=b2-2, wall=wall_type_room }

                    if crawl.one_chance_in(3) then
                        treasure_area(a1+3, a2+3, b1-3, b2-3)
                    end
                end
            end
        end
    end

    for _, room in ipairs(rooms) do
        local doors = 1 + crawl.random2(5) - crawl.random2(3)
        if doors < 1 then
            doors = 1
        end

        if doors > 3 and crawl.one_chance_in(3) then
            doors = 2
        end

        make_box_doors {x1=room[1], y1=room[2], x2=room[3], y2=room[4], number=doors}
    end
}}
MAP
ENDMAP

##############################################################
# layout_misc
#
# This replaces dungeon.cc:_plan_5().
NAME: layout_misc
DEPTH: Pan, 4-22, !Vault
WEIGHT: 1 (Pan), 10
ORIENT: encompass
TAGS: overwritable layout allow_dup unrand
{{
    function random_coord(max_x, max_y)
        return crawl.random2(max_x - 20) + 10,
               crawl.random2(max_y - 20) + 10
    end

    local gxm, gym = dgn.max_bounds()
    local corridors = crawl.random_range(15, 25)

    extend_map { width = gxm, height = gym, fill = 'x' }

    layout_type "misc"

    local x, y = random_coord(gxm, gym)
    local new_x, new_y

    while corridors > 0 do
        new_x, new_y = random_coord(gxm, gym)

        join_the_dots { x1 = x, y1 = y, x2 = new_x, y2 = new_y }

        x, y = new_x, new_y
        corridors = corridors - 1
    end

    spotty_map { boxy = true, iterations = 100 }

    random_wall_material(_G)
}}
MAP
ENDMAP

NAME: layout_caves
DEPTH: Hive, Orc, Slime, !Vault
CHANCE: 100%
ORIENT: encompass
TAGS: overwritable layout allow_dup unrand
{{
    local gxm, gym = dgn.max_bounds()

    extend_map { width = gxm, height = gym, fill = 'x' }

    layout_type "caves"

    spotty_stairs(mapgrd)

    local iterations

    if you.at_branch_bottom() then
        iterations = 600 + crawl.random2(600)
    else
        iterations = 100 + crawl.random2(500)
    end

    spotty_map { boxy = false, iterations = iterations }
}}
MAP
ENDMAP

NAME: layout_spotty
DEPTH: 9-22, !Vault
WEIGHT: 10
ORIENT: encompass
TAGS: overwritable layout allow_dup unrand
{{
    local gxm, gym = dgn.max_bounds()

    extend_map { width = gxm, height = gym, fill = 'x' }

    layout_type "caves"

    spotty_stairs(mapgrd)

    local boxy = crawl.coinflip()
    local iterations = 200 + crawl.random2(boxy and 750 or 1500)

    spotty_map { boxy = boxy, iterations = iterations }
}}
MAP
ENDMAP

## This replaces dungeon.cc:_roguey_level.
NAME: layout_roguey
DEPTH: 1-, !Vault
WEIGHT: 40
ORIENT: encompass
TAGS: overwritable layout allow_dup unrand
{{
    function make_inner_room(room, feat, treasure)
        local inner_room = {
            x1 = room.x1 + 1, y1 = room.y1 + 1,
            x2 = room.x2 - 1, y2 = room.y2 - 1,
            wall = feat, number = 1
        }

        if treasure then inner_room.floor = '*' end

        make_box(inner_room)
        make_box_doors(inner_room)
    end

    function sign(a)
        return a > 0 and 1 or a < 0 and -1 or 0
    end

    function link_rooms(r1, r2, sideways)
        local x1, y1, x2, y2
        if sideways then
            x1 = r1.x2
            y1 = crawl.random_range(r1.y1 + 1, r1.y2 - 1)
            x2 = r2.x1
            y2 = crawl.random_range(r2.y1 + 1, r2.y2 - 1)
        else
            x1 = crawl.random_range(r1.x1 + 1, r1.x2 - 1)
            y1 = r1.y2
            x2 = crawl.random_range(r2.x1 + 1, r2.x2 - 1)
            y2 = r2.y1
        end

        while x1 ~= x2 or y1 ~= y2 do
            if crawl.coinflip() then
                x1 = x1 + sign(x2 - x1)
            else
                y1 = y1 + sign(y2 - y1)
            end

            mapgrd[x1][y1] = '.'
        end
    end

    local gxm, gym = dgn.max_bounds()

    extend_map { width = gxm, height = gym, fill = 'x' }

    layout_type "rooms"

    local rows = 5
    local cols = 5
    local special = -1

    if crawl.one_chance_in(10) and dgn.map_by_tag('special_room', true) then
        special = crawl.random_range(1, rows * cols)
    end

    local rooms = {}

    for y = 0, cols - 1 do
        for x = 0, rows - 1 do
            local base_x, base_y = x * 13 + 8, y * 11 + 8

            local room = {
                x1 = base_x + crawl.random2(4),
                y1 = base_y + crawl.random2(4),
                x2 = base_x + 6 + crawl.random2(5),
                y2 = base_y + 6 + crawl.random2(3),
                fill = '.',
            }

            table.insert(rooms, room)
        end
    end

    for i, room in ipairs(rooms) do
        if i == special then
            room.fill = 'O'
        end

        fill_area(room)

        if room.fill == '.'
           and (room.x2 - room.x1 > 5)
           and (room.y2 - room.y1 > 5)
           and (you.in_branch("D") or you.in_branch("Lair"))
           and crawl.x_chance_in_y(you.absdepth() - 9, 450)
        then
            local feat = crawl.random_element { ['v'] = 2, ['c'] = 3, ['x'] = 3 }
            make_inner_room(room, feat, crawl.coinflip())
        end
    end

    for i, room in ipairs(rooms) do
        -- There is a neighbour on the right, connect to it.
        if i % cols ~= 0 then
            link_rooms(room, rooms[i + 1], true)
        end

        -- There is a neighbour below, connect to it.
        if i + cols <= #rooms then
            link_rooms(room, rooms[i + cols], false)
        end
    end

    -- Prevent staircases from being placed in special or inner rooms.
    _G.nsubst('. = { / ( / [ / < / } / ) / ] / > / .')

    if special ~= -1 then
        _G.subvault('O : special_room')
    end
}}
MAP
ENDMAP

# Dummy layouts that just call C++ functions that do all the work.
NAME: layout_basic
DEPTH: 1-, !Vault
WEIGHT: 90
ORIENT: encompass
TAGS: overwritable layout allow_dup unrand
{{
    if not is_validating() then layout_basic() end
}}
MAP
ENDMAP

NAME: layout_bigger_room
DEPTH: 9-22, !Vault, !Crypt
WEIGHT: 10
ORIENT: encompass
TAGS: overwritable layout allow_dup unrand
{{
    if not is_validating() then layout_bigger_room() end
}}
MAP
ENDMAP

NAME: layout_chaotic_city
DEPTH: Vault, Pan, 4-
WEIGHT: 1 (Pan), 2 (Vault), 10
ORIENT: encompass
TAGS: overwritable layout allow_dup unrand
{{
    if not is_validating() then layout_chaotic_city() end
}}
MAP
ENDMAP

NAME: layout_shoals
DEPTH: Shoals
CHANCE: 100%
ORIENT: encompass
TAGS: overwritable layout allow_dup unrand
{{
    if not is_validating() then layout_shoals() end
}}
MAP
ENDMAP

NAME: layout_swamp
DEPTH: Swamp
CHANCE: 100%
ORIENT: encompass
TAGS: overwritable layout allow_dup unrand
{{
    if not is_validating() then dgn.layout_swamp() end
}}
MAP
ENDMAP
