---@module 'lovr'

function lovr.load()
    ----- \ Globals / -----

    -- State
    -- intro, drilling, retracting, prepping, hopping, won, resetting
    STATE = 'intro'
    INTRO_SLIDE = 1
    ROUND = 1
    WINNING = false
    KEYBOARD_STATE = { w = 0, s = 0, a = 0, d = 0, up = 0, down = 0, left = 0, right = 0, space = 0, reset = 0 }
    HOLDING_SPACE = false
    SHOW_TUTORIAL = true

    -- Camera
    CAM_HEIGHT = 0.5
    CAM_DISTANCE = 1
    CAM_FOV = 80

    -- Score
    DURATION = 0
    DURATION_PENALTY = 5
    SCORE_BASE = 50
    SCORE = SCORE_BASE
    SCORE_INCOMING = 0

    -- Drill
    DRILL_DIRECTION = { x = 0, z = 0 }
    DRILL_POSITION = { x = 0, y = 0, z = 0 }
    DRILL_DENSITY = 0
    DRILL_DENSITY_SKEW_EXPONENT = 1.5
    DRILL_MOMENTUM_FACTOR = 2
    DRILL_DIRECTION_MAX = 0.9
    DRILL_DIRECTION_FACTOR = 2
    DRILL_STRAFE_FACTOR = 10
    DRILL_SPEED_FACTOR = 3
    DRILL_SPEED_TIMESCALE = 0.35
    DRILL_LAYER_ZERO_DENSITY = 0.5
    DRILL_SPEED = DRILL_SPEED_FACTOR * DENSITY_SKEW(DRILL_LAYER_ZERO_DENSITY)
    CURRENT_LAYER = 0

    -- Layer
    LAYERS = {}
    LAYERS_INFO = {}
    NUM_LAYERS = 20
    LAYER_GAP = 1
    CURRENT_LAYER = 0
    SECTOR_SUBDIV = 64
    SECTOR_SUBDIV = SECTOR_SUBDIV - 1
    SCROLL_START = 0
    SCROLL_FACTOR = 0.05
    SCROLL_HYPERSPEED_FACTOR = 10

    -- Waypoints
    WAYPOINTS = {}
    WAYPOINTS[1] = { coords = { x = 0, y = 0, z = 0 }, drill_direction = { x = 0, z = 0 }, layer = 0, is_puncture = false }
    LAST_WAYPOINT_Y = 0
    WAYPOINT_SUBDIV = 20
    WAYPOINT_DISPLAY_RAISE = 0.05
    RETRACT_TIME = 0
    RETRACT_WAIT_BASE = 0.0075
    RETRACT_WAIT = RETRACT_WAIT_BASE
    RETRACT_WAIT_MODIFIER = 50

    -- Punctures
    PUNCTURES = {}
    PUNCTURE_RADIUS = 0.25

    -- Hopping
    HOPPING_TIME = 0
    HOPPING_WAIT = 1
    HOPPING_HEIGHT_FACTOR = 0.125
    LANDER = { coords = { x = 0, y = 0, z = 0 }, destination = { x = 0, z = 0 } }

    -- Orbit
    ORBIT_TIME = 0
    ORBIT_SCALE = 0.25
    ORBIT_SPEED_GOAL = 0.01
    ORBIT_SPEED_BASE = 0.5
    ORBIT_SPEED = ORBIT_SPEED_BASE
    ORBIT_SPEED_SLOW_EXPONENT = 6
    MOON = { coords = { y = 1, z = 0.5 }, distance = 2 }
    MOON_DISTANCE_FACTOR = 1.3
    MOON_DISTANCE_ADDEND = 0.1
    MOON_REAL_DISTANCE = 64

    ----- / Globals \ -----

    -- Intro textures
    TEX_INTRO_1 = lovr.graphics.newTexture('assets/textures/slide-1.png', {})
    TEX_INTRO_2 = lovr.graphics.newTexture('assets/textures/slide-2.png', {})
    TEX_INTRO_3 = lovr.graphics.newTexture('assets/textures/slide-3.png', {})

    -- Music
    AUDIO_INTRO = lovr.audio.newSource('assets/sounds/music/intro.wav',
        { decode = false, spatial = false, pitchable = false })
    AUDIO_INTRO:setLooping(true)
    AUDIO_INTRO:setVolume(0.65)
    AUDIO_INTRO:play()
    AUDIO_MUSIC = lovr.audio.newSource('assets/sounds/music/music.mp3',
        { decode = false, spatial = false, pitchable = false })
    AUDIO_MUSIC:setLooping(true)
    AUDIO_MUSIC:setVolume(0)
    AUDIO_MUSIC:play()

    -- SFX
    AUDIO_WIN = lovr.audio.newSource('assets/sounds/sfx/win.wav',
        { decode = false, spatial = false, pitchable = false })
    AUDIO_WIN:setLooping(false)
    AUDIO_WIN:setVolume(0.5)
    AUDIO_PUNCTURE = lovr.audio.newSource('assets/sounds/sfx/puncture.wav',
        { decode = false, spatial = false, pitchable = true })
    AUDIO_PUNCTURE:setLooping(false)
    AUDIO_PUNCTURE:setVolume(0.5)
    AUDIO_NOISE = lovr.audio.newSource('assets/sounds/sfx/noise.wav',
        { decode = false, spatial = false, pitchable = true })
    AUDIO_NOISE:setLooping(true)
    AUDIO_NOISE:setVolume(0)
    AUDIO_NOISE:play()
    AUDIO_HOP = lovr.audio.newSource('assets/sounds/sfx/hop.wav',
        { decode = false, spatial = false, pitchable = false })
    AUDIO_HOP:setLooping(false)
    AUDIO_HOP:setVolume(0.5)

    -- Font
    FONT = lovr.graphics.newFont('assets/fonts/Fracking-Numerals.ttf', 32, 4)

    -- Set background color
    lovr.graphics.setBackgroundColor(0.012, 0.035, 0.028)

    -- Create layers
    CREATE_LAYERS()
end

----- Good luck!!! :3 -----

function lovr.keypressed(key)
    if (key == 'w') then
        KEYBOARD_STATE.w = 1
    elseif (key == 'up') then
        KEYBOARD_STATE.up = 1
    elseif (key == 's') then
        KEYBOARD_STATE.s = 1
    elseif (key == 'down') then
        KEYBOARD_STATE.down = 1
    elseif (key == 'a') then
        KEYBOARD_STATE.a = 1
    elseif (key == 'left') then
        KEYBOARD_STATE.left = 1
    elseif (key == 'd') then
        KEYBOARD_STATE.d = 1
    elseif (key == 'right') then
        KEYBOARD_STATE.right = 1
    elseif (key == 'space' and (STATE == 'drilling' or STATE == 'intro')) then
        KEYBOARD_STATE.space = 1
    elseif (key == 'r' and (INTRO_SLIDE >= 3)) then
        KEYBOARD_STATE.reset = 1
        AUDIO_INTRO:stop()
        AUDIO_MUSIC:setVolume(0.65)
        AUDIO_NOISE:setVolume(0.5 + DRILL_LAYER_ZERO_DENSITY)
        AUDIO_HOP:stop()
        if STATE ~= 'intro' then
            STATE = 'resetting'
        end
    elseif (key == 'escape') then
        lovr.event.quit(0)
    end
end

function lovr.keyreleased(key)
    if (key == 'w') then
        KEYBOARD_STATE.w = 0
    elseif (key == 'up') then
        KEYBOARD_STATE.up = 0
    elseif (key == 's') then
        KEYBOARD_STATE.s = 0
    elseif (key == 'down') then
        KEYBOARD_STATE.down = 0
    elseif (key == 'a') then
        KEYBOARD_STATE.a = 0
    elseif (key == 'left') then
        KEYBOARD_STATE.left = 0
    elseif (key == 'd') then
        KEYBOARD_STATE.d = 0
    elseif (key == 'right') then
        KEYBOARD_STATE.right = 0
    elseif (key == 'space') then
        KEYBOARD_STATE.space = 0
    elseif (key == 'r') then
        KEYBOARD_STATE.reset = 0
    end
end

function DENSITY_SKEW(density)
    return 1 / (1 + (density / (1 - density)) ^ -DRILL_DENSITY_SKEW_EXPONENT)
end

function CREATE_LAYERS()
    for y = 1, NUM_LAYERS, 1 do
        local layer = {}
        LAYERS_INFO[y] = {
            scroll_x = 0,
            scroll_z = SCROLL_START + ((y - 1) * LAYER_GAP * SCROLL_FACTOR),
            offset_x = 0,
            offset_z = 0,
            position_y = -y * LAYER_GAP,
            seed = lovr.math.random() * 9999
        }
        PUNCTURES[y] = {}
        for x = -1, 1, 1 / SECTOR_SUBDIV * 2 do
            for z = -1, 1, 1 / SECTOR_SUBDIV * 2 do
                local sector = {
                    x = x,
                    y = LAYERS_INFO[y].position_y,
                    z = z,
                    density = lovr.math.noise(x, LAYERS_INFO[y].position_y + LAYERS_INFO[y].seed, z)
                }
                layer[#layer + 1] = sector
            end
        end
        LAYERS[y] = layer
    end
end

function SET_SECTOR_DENSITY(dt, addend_x, addend_z)
    for y, layer in pairs(LAYERS) do
        if (y == CURRENT_LAYER or y == CURRENT_LAYER + 1) then
            local hyperspeed = 0
            if STATE ~= 'drilling' then
                hyperspeed = SCROLL_HYPERSPEED_FACTOR
            end
            LAYERS_INFO[y].offset_x = LAYERS_INFO[y].offset_x + (LAYERS_INFO[y].scroll_x * dt) + addend_x
            LAYERS_INFO[y].offset_z =
                LAYERS_INFO[y].offset_z -
                (LAYERS_INFO[y].scroll_z * MOON.distance * dt) -
                (hyperspeed * dt) +
                addend_z
            for _, sector in pairs(layer) do
                sector.density = lovr.math.noise(
                    sector.x + LAYERS_INFO[y].offset_x,
                    sector.y + LAYERS_INFO[y].seed,
                    sector.z + LAYERS_INFO[y].offset_z
                )
            end
            for _, sector in pairs(layer) do
                local close_to_puncture = false
                local distance_to_puncture = PUNCTURE_RADIUS
                for _, puncture in pairs(PUNCTURES[y]) do
                    if (
                            -puncture.coords.y == sector.y and
                            (puncture.round ~= ROUND or puncture.coords.y == ((CURRENT_LAYER + 1) * LAYER_GAP)) and
                            math.abs(puncture.coords.x - DRILL_POSITION.x) < 1 + PUNCTURE_RADIUS and
                            math.abs(puncture.coords.z - DRILL_POSITION.z) < 1 + PUNCTURE_RADIUS
                        ) then
                        close_to_puncture = true
                        local potential_distance = math.sqrt(
                            (sector.x - puncture.coords.x + DRILL_POSITION.x) ^ 2 +
                            (sector.z - puncture.coords.z + DRILL_POSITION.z) ^ 2
                        )
                        distance_to_puncture = math.min(potential_distance, distance_to_puncture)
                    end
                end
                if close_to_puncture then
                    sector.density = math.min(
                        sector.density + ((PUNCTURE_RADIUS - distance_to_puncture) / PUNCTURE_RADIUS), 1)
                end
            end
        end
    end
end

function lovr.update(dt)
    -- Activate win if needed
    if (SCORE_INCOMING >= SCORE and not WINNING) then
        WINNING = true
        STATE = 'retracting'
        LANDER.destination.x = LANDER.coords.x
        LANDER.destination.z = LANDER.coords.z
    end

    -- Retract with space
    if (KEYBOARD_STATE.space == 1 and not WINNING and STATE ~= 'intro') then
        STATE = 'retracting'
        LANDER.destination.x = (LANDER.coords.x * 2) - DRILL_POSITION.x
        LANDER.destination.z = (LANDER.coords.z * 2) - DRILL_POSITION.z
        KEYBOARD_STATE.space = 0
    end

    if (STATE == 'drilling') then
        -- Track time
        DURATION = DURATION + dt

        -- Track orbit
        ORBIT_TIME = math.fmod((ORBIT_TIME + (dt * ORBIT_SPEED)), 1)
        local moon_coords_y = 0
        local moon_coords_z = 0
        moon_coords_y = math.cos(2 * math.pi * ORBIT_TIME)
        moon_coords_z = 0.5 * math.sin(2 * math.pi * ORBIT_TIME)
        MOON.coords.y = moon_coords_y * ORBIT_SCALE
        MOON.coords.z = moon_coords_z * ORBIT_SCALE
        MOON.distance = ((math.cos(4 * math.pi * ORBIT_TIME) / 2 + 0.5) * MOON_DISTANCE_FACTOR) + MOON_DISTANCE_ADDEND

        -- Drill direction
        if ((KEYBOARD_STATE.w + KEYBOARD_STATE.up) > (KEYBOARD_STATE.s + KEYBOARD_STATE.down)) then
            DRILL_DIRECTION.z = math.max(DRILL_DIRECTION.z - (dt * DRILL_DIRECTION_FACTOR), -DRILL_DIRECTION_MAX)
        end
        if ((KEYBOARD_STATE.s + KEYBOARD_STATE.down) > (KEYBOARD_STATE.w + KEYBOARD_STATE.up)) then
            DRILL_DIRECTION.z = math.min(DRILL_DIRECTION.z + (dt * DRILL_DIRECTION_FACTOR), DRILL_DIRECTION_MAX)
        end
        if ((KEYBOARD_STATE.a + KEYBOARD_STATE.left) > (KEYBOARD_STATE.d + KEYBOARD_STATE.right)) then
            DRILL_DIRECTION.x = math.max(DRILL_DIRECTION.x - (dt * DRILL_DIRECTION_FACTOR), -DRILL_DIRECTION_MAX)
        end
        if ((KEYBOARD_STATE.d + KEYBOARD_STATE.right) > (KEYBOARD_STATE.a + KEYBOARD_STATE.left)) then
            DRILL_DIRECTION.x = math.min(DRILL_DIRECTION.x + (dt * DRILL_DIRECTION_FACTOR), DRILL_DIRECTION_MAX)
        end

        -- Update drill strafe
        DRILL_DENSITY = DRILL_LAYER_ZERO_DENSITY
        if CURRENT_LAYER > 0 then
            DRILL_DENSITY = lovr.math.noise(
                LAYERS_INFO[CURRENT_LAYER].offset_x,
                LAYERS_INFO[CURRENT_LAYER].position_y + LAYERS_INFO[CURRENT_LAYER].seed,
                LAYERS_INFO[CURRENT_LAYER].offset_z
            )
            DRILL_DENSITY = DENSITY_SKEW(DRILL_DENSITY)
        end
        if (ROUND > 1 and PUNCTURES[CURRENT_LAYER]) then
            local close_to_puncture = false
            local distance_to_puncture = PUNCTURE_RADIUS
            for _, puncture in pairs(PUNCTURES[CURRENT_LAYER]) do
                if (
                        puncture.round ~= ROUND and
                        math.abs(puncture.coords.x - DRILL_POSITION.x) < PUNCTURE_RADIUS and
                        math.abs(puncture.coords.z - DRILL_POSITION.z) < PUNCTURE_RADIUS
                    ) then
                    close_to_puncture = true
                    local potential_distance = math.sqrt(
                        (DRILL_POSITION.x - puncture.coords.x) ^ 2 +
                        (DRILL_POSITION.z - puncture.coords.z) ^ 2
                    )
                    distance_to_puncture = math.min(potential_distance, distance_to_puncture)
                    distance_to_puncture = math.min(potential_distance, distance_to_puncture)
                end
            end
            if close_to_puncture then
                DRILL_DENSITY = math.min(DRILL_DENSITY + ((PUNCTURE_RADIUS - distance_to_puncture) / PUNCTURE_RADIUS), 1)
            end
        end
        DRILL_DENSITY = math.max(0.2, DRILL_DENSITY)
        AUDIO_NOISE:setPitch(0.3 + DRILL_DENSITY * 1.2)
        AUDIO_NOISE:setVolume(0.3 + DRILL_DENSITY * 1.2)
        local drill_strafe = {
            x = DRILL_DIRECTION.x / (DRILL_DIRECTION.x + DRILL_MOMENTUM_FACTOR) * DRILL_STRAFE_FACTOR * DRILL_DENSITY,
            z = DRILL_DIRECTION.z / (DRILL_DIRECTION.z + DRILL_MOMENTUM_FACTOR) * DRILL_STRAFE_FACTOR * DRILL_DENSITY
        }

        -- Update drill position
        DRILL_POSITION.x = DRILL_POSITION.x + (drill_strafe.x * dt)
        DRILL_POSITION.z = DRILL_POSITION.z + (drill_strafe.z * dt)
        DRILL_SPEED = math.sin(math.pi / 2 * (1 - math.max(math.abs(DRILL_DIRECTION.x), math.abs(DRILL_DIRECTION.z)))) *
            DRILL_SPEED_FACTOR *
            DRILL_DENSITY
        DRILL_POSITION.y =
            DRILL_POSITION.y + (DRILL_SPEED * DRILL_SPEED_TIMESCALE * dt)

        -- Set density for all sectors
        SET_SECTOR_DENSITY(dt, drill_strafe.x * dt, drill_strafe.z * dt)

        -- Place waypoint if needed
        if DRILL_POSITION.y - LAST_WAYPOINT_Y >= LAYER_GAP / WAYPOINT_SUBDIV then
            WAYPOINTS[#WAYPOINTS + 1] = {
                coords = { x = DRILL_POSITION.x, y = DRILL_POSITION.y, z = DRILL_POSITION.z },
                drill_direction = { x = DRILL_DIRECTION.x, z = DRILL_DIRECTION.z },
                layer = CURRENT_LAYER,
                is_puncture = false
            }
            LAST_WAYPOINT_Y = DRILL_POSITION.y
        end

        -- Handle puncture
        if DRILL_POSITION.y - (CURRENT_LAYER * LAYER_GAP) > LAYER_GAP then
            WAYPOINTS[#WAYPOINTS].is_puncture = true
            CURRENT_LAYER = CURRENT_LAYER + 1
            local close_to_puncture = false
            local distance_to_puncture = PUNCTURE_RADIUS
            for _, puncture in pairs(PUNCTURES[CURRENT_LAYER]) do
                if (
                        math.abs(puncture.coords.x - DRILL_POSITION.x) < PUNCTURE_RADIUS and
                        math.abs(puncture.coords.z - DRILL_POSITION.z) < PUNCTURE_RADIUS
                    ) then
                    close_to_puncture = true
                    local potential_distance = math.sqrt(
                        (DRILL_POSITION.x - puncture.coords.x) ^ 2 +
                        (DRILL_POSITION.z - puncture.coords.z) ^ 2
                    )
                    distance_to_puncture = math.min(potential_distance, distance_to_puncture)
                    distance_to_puncture = math.min(potential_distance, distance_to_puncture)
                end
            end
            local points = 1 - lovr.math.noise(
                LAYERS_INFO[CURRENT_LAYER].offset_x,
                LAYERS_INFO[CURRENT_LAYER].position_y + LAYERS_INFO[CURRENT_LAYER].seed,
                LAYERS_INFO[CURRENT_LAYER].offset_z
            )
            if close_to_puncture then
                points = points - math.max(((PUNCTURE_RADIUS - distance_to_puncture) / PUNCTURE_RADIUS), 0)
            end
            points = math.max(points, 0)
            SCORE_INCOMING = SCORE_INCOMING + points
            PUNCTURES[CURRENT_LAYER][#PUNCTURES[CURRENT_LAYER] + 1] =
            { round = ROUND, coords = { x = DRILL_POSITION.x, y = CURRENT_LAYER * LAYER_GAP, z = DRILL_POSITION.z } }
            LANDER.destination.x = (LANDER.coords.x * 2) - DRILL_POSITION.x
            LANDER.destination.z = (LANDER.coords.z * 2) - DRILL_POSITION.z
            AUDIO_PUNCTURE:setPitch(1 + (points / 3))
            AUDIO_PUNCTURE:play()
        end

        -- If drill passed last layer, begin retracting
        if (CURRENT_LAYER == NUM_LAYERS) then
            STATE = 'retracting'
            CURRENT_LAYER = CURRENT_LAYER - 1
            SHOW_TUTORIAL = false
        end
    elseif STATE == 'retracting' then
        AUDIO_NOISE:setPitch(2)
        AUDIO_NOISE:setVolume(1.5)
        -- Set density for all sectors
        SET_SECTOR_DENSITY(dt, 0, 0)

        RETRACT_TIME = RETRACT_TIME + dt
        local end_retract = RETRACT_TIME < RETRACT_WAIT
        local did_puncture = false
        while not end_retract do
            RETRACT_TIME = RETRACT_TIME - RETRACT_WAIT
            local current_waypoint = WAYPOINTS[#WAYPOINTS]
            CURRENT_LAYER = current_waypoint.layer
            DRILL_POSITION.x = current_waypoint.coords.x
            DRILL_POSITION.y = current_waypoint.coords.y
            DRILL_POSITION.z = current_waypoint.coords.z
            DRILL_DIRECTION.x = current_waypoint.drill_direction.x
            DRILL_DIRECTION.z = current_waypoint.drill_direction.z

            if WAYPOINTS[#WAYPOINTS].is_puncture then
                did_puncture = true
            end
            table.remove(WAYPOINTS, #WAYPOINTS)
            if (RETRACT_TIME < RETRACT_WAIT or #WAYPOINTS == 0) then
                end_retract = true
                RETRACT_WAIT = RETRACT_WAIT_BASE * (#WAYPOINTS / (#WAYPOINTS + RETRACT_WAIT_MODIFIER))
                if did_puncture then
                    local audio_frack = lovr.audio.newSource('assets/sounds/sfx/frack.wav',
                        { decode = false, spatial = false, pitchable = false })
                    audio_frack:setLooping(false)
                    audio_frack:setVolume(0.5)
                    audio_frack:play()
                end
            end
        end
        if #WAYPOINTS == 0 then
            STATE = 'prepping'
            RETRACT_TIME = 0
            RETRACT_WAIT = RETRACT_WAIT_BASE
        end
    end
    if STATE == 'prepping' then
        CURRENT_LAYER = 0
        LAST_WAYPOINT_Y = 0
        WAYPOINTS[1] = { coords = { x = 0, y = 0, z = 0 }, drill_direction = { x = 0, z = 0 }, layer = 0, is_puncture = false }
        ROUND = ROUND + 1
        AUDIO_HOP = lovr.audio.newSource('assets/sounds/sfx/hop.wav',
            { decode = false, spatial = false, pitchable = false })
        AUDIO_HOP:setLooping(false)
        AUDIO_HOP:setVolume(0.5)
        AUDIO_HOP:play()
        STATE = 'hopping'
    end
    if STATE == 'hopping' then
        AUDIO_NOISE:setVolume(0)
        HOPPING_TIME = HOPPING_TIME + dt
        LANDER.coords.y = 2 * math.sin(math.pi * (HOPPING_TIME / HOPPING_WAIT)) * HOPPING_HEIGHT_FACTOR
        DRILL_POSITION.x = (LANDER.destination.x - LANDER.coords.x) * (HOPPING_TIME / HOPPING_WAIT) + LANDER.coords.x
        DRILL_POSITION.z = (LANDER.destination.z - LANDER.coords.z) * (HOPPING_TIME / HOPPING_WAIT) + LANDER.coords.z
        SET_SECTOR_DENSITY(dt, 0, 0)
        if HOPPING_TIME >= HOPPING_WAIT then
            HOPPING_TIME = 0
            LANDER.coords.y = 0
            LANDER.coords.x = LANDER.destination.x
            LANDER.coords.z = LANDER.destination.z
            DRILL_DIRECTION.x = 0
            DRILL_DIRECTION.z = 0
            SCORE = math.max(SCORE - SCORE_INCOMING, 0)
            SCORE_INCOMING = 0
            if (not WINNING) then
                DURATION = DURATION + DURATION_PENALTY
            end
            ORBIT_SPEED =
                ORBIT_SPEED_GOAL +
                (ORBIT_SPEED_BASE - ORBIT_SPEED_GOAL) *
                (1 - math.sqrt(-((SCORE / SCORE_BASE) ^ ORBIT_SPEED_SLOW_EXPONENT - 1)))
            STATE = 'drilling'
            if WINNING then
                AUDIO_WIN:play()
                AUDIO_INTRO:play()
                AUDIO_MUSIC:setVolume(0)
                AUDIO_NOISE:setVolume(0)
                STATE = 'won'
            end
        end
    end
    if STATE == 'resetting' then
        STATE = 'drilling'
        ROUND = 1
        HOLDING_SPACE = false
        WINNING = false
        DURATION = 0
        SCORE = SCORE_BASE
        SCORE_INCOMING = 0
        DRILL_DIRECTION = { x = 0, z = 0 }
        DRILL_POSITION = { x = 0, y = 0, z = 0 }
        DRILL_DENSITY = 0
        DRILL_SPEED = DRILL_SPEED_FACTOR * DENSITY_SKEW(DRILL_LAYER_ZERO_DENSITY)
        CURRENT_LAYER = 0
        LAYERS = {}
        LAYERS_INFO = {}
        CURRENT_LAYER = 0
        WAYPOINTS = {}
        WAYPOINTS[1] = { coords = { x = 0, y = 0, z = 0 }, drill_direction = { x = 0, z = 0 }, layer = 0 }
        LAST_WAYPOINT_Y = 0
        RETRACT_TIME = 0
        RETRACT_WAIT = RETRACT_WAIT_BASE
        PUNCTURES = {}
        HOPPING_TIME = 0
        LANDER = { coords = { x = 0, y = 0, z = 0 }, destination = { x = 0, z = 0 } }
        MOON = { coords = { y = 1, z = 0.5 }, distance = 2 }
        ORBIT_TIME = 0
        ORBIT_SPEED = ORBIT_SPEED_BASE
        CREATE_LAYERS()
    end
end

function lovr.draw(pass)
    if STATE == 'intro' then
        -- Set camera
        pass:setProjection('orthographic')
        pass:setViewPose(1, 0, 0, 0, math.rad(0), 0, 0, 0)
        if INTRO_SLIDE == 1 then
            pass:fill(TEX_INTRO_1)
            if math.max(KEYBOARD_STATE.w, KEYBOARD_STATE.s, KEYBOARD_STATE.a, KEYBOARD_STATE.d, KEYBOARD_STATE.up, KEYBOARD_STATE.down, KEYBOARD_STATE.left, KEYBOARD_STATE.right) == 1 then
                INTRO_SLIDE = 2
            end
        elseif INTRO_SLIDE == 2 then
            pass:fill(TEX_INTRO_2)
            if KEYBOARD_STATE.space == 1 then
                INTRO_SLIDE = 3
            end
        elseif INTRO_SLIDE == 3 then
            pass:fill(TEX_INTRO_3)
            if KEYBOARD_STATE.reset == 1 then
                INTRO_SLIDE = 4
                STATE = 'drilling'
            end
        end
        return
    end

    -- Set camera
    pass:setProjection('perspective', math.rad(CAM_FOV))
    local camera_position = DRILL_POSITION.y - (CURRENT_LAYER * LAYER_GAP) - LAYER_GAP + CAM_HEIGHT
    pass:setViewPose(1, 0, camera_position, CAM_DISTANCE, math.rad(0), 0, 0, 0)

    -- Tutorial
    if SHOW_TUTORIAL then
        pass:setColor(0.56, 0.23, 0.38)
        pass:text('go fast', -0.95, camera_position + (LAYER_GAP / 2.5), -0.95, 0.15,
            math.rad(0), 0, 0, 0, 0, 'left', 'middle')
        pass:text('top layer: speed', 0, camera_position + (LAYER_GAP / 2.4), -0.95, 0.1,
            math.rad(0), 0, 0, 0, 0, 'center', 'middle')
        pass:text('bottom layer: score', 0, camera_position - (LAYER_GAP / 2.4), -0.95, 0.1,
            math.rad(0), 0, 0, 0, 0, 'center', 'middle')
    end

    -- Font
    pass:setFont(FONT)

    -- Draw layers
    for y = CURRENT_LAYER, CURRENT_LAYER + 1, 1 do
        if y ~= 0 then
            local layer = LAYERS[y]
            local draw_density = 0
            for _, sector in pairs(layer) do
                draw_density = 1 - DENSITY_SKEW(sector.density)
                pass:setColor(math.max(0.05, draw_density - 0.55), draw_density, draw_density / 1.25)
                pass:plane(
                    sector.x, sector.y + DRILL_POSITION.y, sector.z,
                    1 / SECTOR_SUBDIV * 2, 1 / SECTOR_SUBDIV * 2,
                    math.rad(90),
                    1, 0, 0
                )
            end
        end
    end

    -- Draw HUD
    -- Duration
    pass:setColor(0.48, 0.91, 0.82)
    if (STATE == 'won') then
        pass:setColor(0.56, 0.23, 0.38)
    end
    pass:text(string.format('%.3f', DURATION), -1, camera_position + (LAYER_GAP / 3), -0.95, 0.3,
        math.rad(90), 0, 1, 0, 0, 'right', 'middle')
    -- Score
    pass:setColor(0.48, 0.91, 0.82)
    pass:text(string.format('%.3f', SCORE), -1, camera_position - (LAYER_GAP / 2.5), -0.95, 0.3,
        math.rad(90), 0, 1, 0, 0, 'right', 'middle')
    -- Duration penalty
    if (STATE == 'retracting' or STATE == 'prepping' or STATE == 'hopping') then
        pass:setColor(0.56, 0.23, 0.38)
        if not WINNING then
            pass:text(string.format('%.3f', DURATION_PENALTY),
                -1, camera_position + (LAYER_GAP / 3) - 0.165, -0.95, 0.2,
                math.rad(90), 0, 1, 0, 0, 'right', 'middle')
        end
    end
    -- Incoming score
    pass:text(string.format('%.3f', SCORE_INCOMING), -1, camera_position - (LAYER_GAP / 2.6) + 0.175, -0.95, 0.2,
        math.rad(90), 0, 1, 0, 0, 'right', 'middle')
    -- Drill speed
    pass:setColor(0.48, 0.91, 0.82)
    local draw_drill_speed = 0
    if STATE == 'drilling' then
        draw_drill_speed = DRILL_SPEED
    end
    pass:text(string.format('%.3f', draw_drill_speed), 1, camera_position + (LAYER_GAP / 3), -0.95, 0.3,
        math.rad(-90), 0, 1, 0, 0, 'left', 'middle')
    -- Drill y-coord
    if CURRENT_LAYER == NUM_LAYERS - 1 then
        pass:setColor(0.56, 0.23, 0.38)
    end
    pass:text(string.format('%.3f', DRILL_POSITION.y / NUM_LAYERS), 1, camera_position - (LAYER_GAP / 2.5), -0.95,
        0.3, math.rad(-90), 0, 1, 0, 0, 'left', 'middle')
    -- Orbit
    pass:setColor(0.15, 0.55, 0.55)
    pass:cylinder(1, camera_position, 0, 0.0625, 0.00625, math.rad(90), 0, 1, 0)
    pass:setColor(0.48, 0.91, 0.82)
    pass:cylinder(1, camera_position + MOON.coords.y, MOON.coords.z, 0.0125, 0.00625, math.rad(90), 0, 1, 0)

    -- Draw waypoints
    pass:setColor(0.53, 0.41, 0.47)
    for _, waypoint in pairs(WAYPOINTS) do
        if (
                waypoint.coords.y - (CURRENT_LAYER * LAYER_GAP) > 0 and
                waypoint.coords.y - (CURRENT_LAYER * LAYER_GAP) < 1 and
                math.abs(waypoint.coords.x - DRILL_POSITION.x) < 1 and
                math.abs(waypoint.coords.z - DRILL_POSITION.z) < 1 and
                waypoint.coords.y < DRILL_POSITION.y
            )
        then
            pass:cube(
                (waypoint.coords.x - DRILL_POSITION.x),
                -(waypoint.coords.y - DRILL_POSITION.y) + WAYPOINT_DISPLAY_RAISE,
                (waypoint.coords.z - DRILL_POSITION.z),
                0.00625
            )
        end
    end

    -- Draw punctures
    for _, layer in pairs(PUNCTURES) do
        for _, puncture in pairs(layer) do
            pass:setColor(0.15, 0.55, 0.55)
            if (puncture.round == ROUND and puncture.coords.y ~= ((CURRENT_LAYER + 1) * LAYER_GAP)) then
                pass:setColor(0.53, 0.41, 0.47)
            end
            if (
                    (puncture.coords.y == (CURRENT_LAYER * LAYER_GAP) or puncture.coords.y == ((CURRENT_LAYER + 1) * LAYER_GAP)) and
                    math.abs(puncture.coords.x - DRILL_POSITION.x) < 1 and
                    math.abs(puncture.coords.z - DRILL_POSITION.z) < 1
                )
            then
                pass:cylinder(
                    (puncture.coords.x - DRILL_POSITION.x),
                    -(puncture.coords.y - DRILL_POSITION.y),
                    (puncture.coords.z - DRILL_POSITION.z),
                    0.025, 0.0125, math.rad(90), 1, 0, 0, true, 0, 2 * math.pi, 16
                )
            end
        end
    end

    -- Draw physical things
    -- Drill
    pass:setColor(0.56, 0.23, 0.38)
    pass:cone(
        vector(0, 0.0625 + LANDER.coords.y, 0),
        vector(0.0625 * DRILL_DIRECTION.x, LANDER.coords.y, 0.0625 * DRILL_DIRECTION.z),
        0.03125, 32
    )
    -- Markers
    pass:setColor(0.78, 0, 0.35)
    if (CURRENT_LAYER ~= 0) then
        pass:cube(0, DRILL_POSITION.y - (CURRENT_LAYER * LAYER_GAP), 0, 0.0125)
    end
    pass:cube(0, DRILL_POSITION.y - (CURRENT_LAYER * LAYER_GAP) - LAYER_GAP, 0, 0.0125)
    -- Controls
    pass:setColor(0.48, 0.91, 0.82)
    pass:cube(0, DRILL_POSITION.y - (CURRENT_LAYER * LAYER_GAP) - (LAYER_GAP / 2), -1.25, 0.05)
    pass:setColor(0.56, 0.23, 0.38)
    pass:cube(
        DRILL_DIRECTION.x * 0.25,
        DRILL_POSITION.y - (CURRENT_LAYER * LAYER_GAP) - (LAYER_GAP / 2) - (DRILL_DIRECTION.z * 0.25),
        -1,
        0.025
    )
    pass:setColor(0.78, 0, 0.35)
    pass:box(
        0, DRILL_POSITION.y - (CURRENT_LAYER * LAYER_GAP) - (LAYER_GAP / 2), -1.25,
        0.5, 0.5, 0.001, 0, 0, 0, 0, 'line'
    )
    -- Lander and ground plane
    if (CURRENT_LAYER == 0) then
        pass:setColor(0.56, 0.23, 0.38)
        if STATE == 'hopping' then
            pass:cube(
                0,
                LANDER.coords.y + DRILL_POSITION.y + 0.125,
                0,
                0.125)
        else
            if (math.abs(LANDER.coords.x - DRILL_POSITION.x) < 1.25 and math.abs(LANDER.coords.z - DRILL_POSITION.z) < 1.25) then
                pass:cube(
                    LANDER.coords.x - DRILL_POSITION.x,
                    LANDER.coords.y + DRILL_POSITION.y + 0.125,
                    LANDER.coords.z - DRILL_POSITION.z,
                    0.125)
            end
        end
        pass:setColor(0.78, 0, 0.35)
        pass:box(0, DRILL_POSITION.y, 0, 2, 0.001, 2, 0, 0, 0, 0, 'line')
    end
    -- Horizon
    pass:setColor(0.012, 0.035, 0.028)
    -- pass:setColor(0, 0, 0)
    pass:plane(0, -115 + camera_position, 0, 4096, 2048, math.rad(90), 1, 0, 0)
    -- Earth
    pass:setColor(0.016, 0.059, 0.047)
    MOON_REAL_DISTANCE = 9220
    pass:sphere(
        0,
        -(MOON.coords.y * MOON_REAL_DISTANCE) + camera_position,
        -(MOON.coords.z * MOON_REAL_DISTANCE),
        128
    )
end
