--luacheck: ignore
--tank battles (royale)-- mewmew made this --

local event = require 'utils.event'
local table_insert = table.insert
local math_random = math.random
local map_functions = require 'tools.map_functions'
local simplex_noise = require 'utils.simplex_noise'
simplex_noise = simplex_noise.d2
local arena_size = 160

local function shuffle(tbl)
    local size = #tbl
    for i = size, 1, -1 do
        local rand = math.random(size)
        tbl[i], tbl[rand] = tbl[rand], tbl[i]
    end
    return tbl
end

local function get_noise(name, pos)
    local seed = global.noise_seed
    local noise = {}
    local noise_seed_add = 25000
    if name == 'rocks' then
        noise[1] = simplex_noise(pos.x * 0.02, pos.y * 0.02, seed)
        seed = seed + noise_seed_add
        noise[2] = simplex_noise(pos.x * 0.1, pos.y * 0.1, seed)
        seed = seed + noise_seed_add
        local noise = noise[1] + noise[2] * 0.2
        return noise
    end
    seed = seed + noise_seed_add
    seed = seed + noise_seed_add
    if name == 'rocks_2' then
        noise[1] = simplex_noise(pos.x * 0.02, pos.y * 0.02, seed)
        seed = seed + noise_seed_add
        noise[2] = simplex_noise(pos.x * 0.1, pos.y * 0.1, seed)
        seed = seed + noise_seed_add
        local noise = noise[1] + noise[2] * 0.2
        return noise
    end
    seed = seed + noise_seed_add
    seed = seed + noise_seed_add
    if name == 'terrain' then
        noise[1] = simplex_noise(pos.x * 0.02, pos.y * 0.02, seed)
        seed = seed + noise_seed_add
        noise[2] = simplex_noise(pos.x * 0.1, pos.y * 0.1, seed)
        seed = seed + noise_seed_add
        local noise = noise[1] + noise[2] * 0.2
        return noise
    end
end

local function create_tank_battle_score_gui()
    for _, player in pairs(game.connected_players) do
        if player.gui.left['tank_battle_score'] then
            player.gui.left['tank_battle_score'].destroy()
        end
        local frame = player.gui.left.add({type = 'frame', name = 'tank_battle_score', direction = 'vertical'})

        local l = frame.add({type = 'label', caption = 'Won rounds'})
        l.style.font_color = {r = 0.98, g = 0.66, b = 0.22}
        l.style.font = 'default-listbox'

        local t = frame.add({type = 'table', column_count = 2})

        local scores = {}
        for _, player in pairs(game.connected_players) do
            if global.tank_battles_score[player.index] then
                table_insert(scores, {name = player.name, score = global.tank_battles_score[player.index]})
            end
        end

        for x = 1, #scores, 1 do
            for y = 1, #scores, 1 do
                if not scores[y + 1] then
                    break
                end
                if scores[y]['score'] < scores[y + 1]['score'] then
                    local key = scores[y]
                    scores[y] = scores[y + 1]
                    scores[y + 1] = key
                end
            end
        end

        for i = 1, 8, 1 do
            if scores[i] then
                local l = t.add({type = 'label', caption = scores[i].name})
                local color = game.players[scores[i].name].color
                color = {r = color.r * 0.6 + 0.4, g = color.g * 0.6 + 0.4, b = color.b * 0.6 + 0.4, a = 1}
                l.style.font_color = color
                l.style.font = 'default-bold'
                t.add({type = 'label', caption = scores[i].score})
            end
        end
    end
end

local loot = {
    --{{name = "submachine-gun", count = 1}, weight = 2},
    --{{name = "combat-shotgun", count = 1}, weight = 2},
    --{{name = "flamethrower", count = 1}, weight = 1},
    --{{name = "rocket-launcher", count = 1}, weight = 2},

    {{name = 'flamethrower-ammo', count = 16}, weight = 2},
    {{name = 'piercing-shotgun-shell', count = 16}, weight = 2},
    --{{name = "piercing-rounds-magazine", count = 16}, weight = 2},
    --{{name = "uranium-rounds-magazine", count = 8}, weight = 1},
    {{name = 'explosive-rocket', count = 8}, weight = 2},
    {{name = 'rocket', count = 8}, weight = 2},
    {{name = 'grenade', count = 16}, weight = 2},
    {{name = 'cluster-grenade', count = 8}, weight = 2},
    --{{name = "poison-capsule", count = 4}, weight = 1},
    {{name = 'defender-capsule', count = 6}, weight = 1},
    {{name = 'distractor-capsule', count = 3}, weight = 1},
    --{{name = "destroyer-capsule", count = 1}, weight = 1},

    {{name = 'cannon-shell', count = 8}, weight = 16},
    {{name = 'explosive-cannon-shell', count = 8}, weight = 16},
    {{name = 'uranium-cannon-shell', count = 8}, weight = 6},
    {{name = 'explosive-uranium-cannon-shell', count = 8}, weight = 6},
    --{{name = "light-armor", count = 1}, weight = 5},
    --{{name = "heavy-armor", count = 1}, weight = 2},
    {{name = 'modular-armor', count = 1}, weight = 3},
    {{name = 'power-armor', count = 1}, weight = 2},
    {{name = 'power-armor-mk2', count = 1}, weight = 1},
    --{{name = "battery-mk2-equipment", count = 1}, weight = 2},
    {{name = 'energy-shield-equipment', count = 1}, weight = 2},
    {{name = 'exoskeleton-equipment', count = 1}, weight = 2},
    {{name = 'fusion-reactor-equipment', count = 1}, weight = 2},
    {{name = 'repair-pack', count = 1}, weight = 6},
    {{name = 'coal', count = 16}, weight = 3},
    --{{name = "solid-fuel", count = 8}, weight = 2},
    {{name = 'nuclear-fuel', count = 1}, weight = 1},
    {{name = 'gate', count = 16}, weight = 2},
    {{name = 'stone-wall', count = 16}, weight = 2}
}
local loot_raffle = {}
for _, item in pairs(loot) do
    for x = 1, item.weight, 1 do
        table.insert(loot_raffle, item[1])
    end
end

local function get_valid_random_spawn_position(surface)
    local chunks = {}
    for chunk in surface.get_chunks() do
        table_insert(chunks, {x = chunk.x, y = chunk.y})
    end
    chunks = shuffle(chunks)

    for _, chunk in pairs(chunks) do
        if chunk.x * 32 < arena_size and chunk.y * 32 < arena_size and chunk.x * 32 >= arena_size * -1 and chunk.y * 32 >= arena_size * -1 then
            local area = {{chunk.x * 32 - 64, chunk.y * 32 - 64}, {chunk.x * 32 + 64, chunk.y * 32 + 64}}
            if surface.count_entities_filtered({name = 'tank', area = area}) == 0 then
                local pos = surface.find_non_colliding_position('tank', {chunk.x * 32 + 16, chunk.y * 32 + 16}, 16, 8)
                return pos
            end
        end
    end

    local pos = surface.find_non_colliding_position('tank', {0, 0}, 32, 4)
    if pos then
        return pos
    end

    return {0, 0}
end

local function put_players_into_arena()
    for _, player in pairs(game.connected_players) do
        local permissions_group = game.permissions.get_group('Default')
        permissions_group.add_player(player.name)

        if player.character then
            player.character.destroy()
            player.character = nil
        end

        player.create_character()

        player.insert({name = 'combat-shotgun', count = 1})
        player.insert({name = 'rocket-launcher', count = 1})
        player.insert({name = 'flamethrower', count = 1})

        local surface = game.surfaces['nauvis']
        local pos = get_valid_random_spawn_position(surface)

        player.force.chart(surface, {{x = -1 * arena_size, y = -1 * arena_size}, {x = arena_size, y = arena_size}})

        player.teleport(pos, surface)
        local tank = surface.create_entity({name = 'tank', force = game.forces[player.index], position = pos})
        tank.insert({name = 'coal', count = 24})
        tank.insert({name = 'cannon-shell', count = 16})
        tank.set_driver(player)
    end
end

local function get_arena_layout_modifiers()
    global.arena_layout_modifiers = {}

    local tree_raffle = {}
    for _, e in pairs(game.entity_prototypes) do
        if e.type == 'tree' then
            table.insert(tree_raffle, e.name)
        end
    end
    global.arena_layout_modifiers.arena_tree = tree_raffle[math_random(1, #tree_raffle)]
    global.arena_layout_modifiers.arena_tree_chance = math_random(4, 20)
    global.arena_layout_modifiers.arena_tree_noise = math_random(0, 75) * 0.01

    local entity_raffle = {}
    local types = {'furnace', 'assembling-machine', 'power-switch', 'programmable-speaker', 'reactor'}
    for _, e in pairs(game.entity_prototypes) do
        for _, t in pairs(types) do
            if e.type == t then
                table.insert(entity_raffle, e.name)
            end
        end
    end
    global.arena_layout_modifiers.secret_entity = entity_raffle[math_random(1, #entity_raffle)]

    local tile_raffle = {}
    local tile_blacklist = {
        ['water'] = true,
        ['deepwater'] = true,
        ['water-green'] = true,
        ['deepwater-green'] = true,
        ['out-of-map'] = true,
        ['hazard-concrete-left'] = true,
        ['hazard-concrete-right'] = true,
        ['lab-dark-1'] = true,
        ['lab-dark-2'] = true,
        ['lab-white'] = true,
        ['refined-hazard-concrete-left'] = true,
        ['refined-hazard-concrete-right'] = true,
        ['tutorial-grid'] = true
    }
    for _, t in pairs(game.tile_prototypes) do
        if not tile_blacklist[t.name] then
            table.insert(tile_raffle, t.name)
        end
    end
    global.arena_layout_modifiers.arena_tile_1 = tile_raffle[math_random(1, #tile_raffle)]
    global.arena_layout_modifiers.arena_tile_2 = tile_raffle[math_random(1, #tile_raffle)]
end

local function regenerate_arena()
    local surface = game.surfaces['nauvis']
    for chunk in surface.get_chunks() do
        surface.set_chunk_generated_status(chunk, defines.chunk_generated_status.custom_tiles)
    end
    global.noise_seed = nil
    surface.request_to_generate_chunks({0, 0}, math.ceil(arena_size / 32) + 3)

    get_arena_layout_modifiers()

    surface.force_generate_chunk_requests()
    surface.daytime = 1
    surface.freeze_daytime = 1

    global.current_arena_size = arena_size

    put_players_into_arena()

    global.game_stage = 'ongoing_game'
end

function shrink_arena()
    local surface = game.surfaces['nauvis']

    if global.current_arena_size < 0 then
        return
    end

    local shrink_width = 8
    local current_arena_size = global.current_arena_size
    local tiles = {}

    for x = arena_size * -1, arena_size, 1 do
        for y = current_arena_size * -1 - shrink_width, current_arena_size * -1, 1 do
            local pos = {x = x, y = y}
            if surface.get_tile(pos).name ~= 'water' and surface.get_tile(pos).name ~= 'deepwater' then
                if x > current_arena_size or y > current_arena_size or x < current_arena_size * -1 or y < current_arena_size * -1 then
                    if math_random(1, 3) ~= 1 then
                        table_insert(tiles, {name = 'water', position = pos})
                    end
                end
            end
        end
    end

    for x = arena_size * -1, arena_size, 1 do
        for y = current_arena_size, current_arena_size + shrink_width, 1 do
            local pos = {x = x, y = y}
            if surface.get_tile(pos).name ~= 'water' and surface.get_tile(pos).name ~= 'deepwater' then
                if x > current_arena_size or y > current_arena_size or x < current_arena_size * -1 or y < current_arena_size * -1 then
                    if math_random(1, 3) ~= 1 then
                        table_insert(tiles, {name = 'water', position = pos})
                    end
                end
            end
        end
    end

    for x = current_arena_size * -1 - shrink_width, current_arena_size * -1, 1 do
        for y = arena_size * -1, arena_size, 1 do
            local pos = {x = x, y = y}
            if surface.get_tile(pos).name ~= 'water' and surface.get_tile(pos).name ~= 'deepwater' then
                if x > current_arena_size or y > current_arena_size or x < current_arena_size * -1 or y < current_arena_size * -1 then
                    if math_random(1, 3) ~= 1 then
                        table_insert(tiles, {name = 'water', position = pos})
                    end
                end
            end
        end
    end

    for x = current_arena_size, current_arena_size + shrink_width, 1 do
        for y = arena_size * -1, arena_size, 1 do
            local pos = {x = x, y = y}
            if surface.get_tile(pos).name ~= 'water' and surface.get_tile(pos).name ~= 'deepwater' then
                if x > current_arena_size or y > current_arena_size or x < current_arena_size * -1 or y < current_arena_size * -1 then
                    if math_random(1, 3) ~= 1 then
                        table_insert(tiles, {name = 'water', position = pos})
                    end
                end
            end
        end
    end

    global.current_arena_size = global.current_arena_size - 1

    surface.set_tiles(tiles, true)
end

local function get_arena_entity(surface, pos)
    local entity = false
    local noise = get_noise('rocks', pos)
    local noise2 = get_noise('rocks_2', pos)

    if noise > -0.1 and noise < 0.1 and noise2 > -0.3 and noise2 < 0.3 then
        return {name = 'rock-big', position = pos}
    end

    if math_random(1, 16) == 1 and noise2 > 0.78 then
        if surface.can_place_entity({name = 'wooden-chest', position = pos, force = 'enemy'}) then
            return {name = 'wooden-chest', position = pos, force = 'enemy'}
        end
    end

    if math_random(1, 16) == 1 and noise2 < -0.78 then
        if surface.can_place_entity({name = 'wooden-chest', position = pos, force = 'enemy'}) then
            return {name = 'wooden-chest', position = pos, force = 'enemy'}
        end
    end

    if math_random(1, global.arena_layout_modifiers.arena_tree_chance) == 1 and noise > global.arena_layout_modifiers.arena_tree_noise then
        return {name = global.arena_layout_modifiers.arena_tree, position = pos}
    end

    if math_random(1, 1024) == 1 then
        if math_random(1, 16) == 1 then
            if surface.can_place_entity({name = global.arena_layout_modifiers.secret_entity, position = pos, force = 'enemy'}) then
                return {name = global.arena_layout_modifiers.secret_entity, position = pos, force = 'enemy'}
            end
        end
        if math_random(1, 64) == 1 then
            if surface.can_place_entity({name = 'big-worm-turret', position = pos, force = 'enemy'}) then
                return {name = 'big-worm-turret', position = pos, force = 'enemy'}
            end
        end
        if math_random(1, 32) == 1 then
            if surface.can_place_entity({name = 'medium-worm-turret', position = pos, force = 'enemy'}) then
                return {name = 'medium-worm-turret', position = pos, force = 'enemy'}
            end
        end
        if math_random(1, 512) == 1 then
            if surface.can_place_entity({name = 'behemoth-biter', position = pos, force = 'enemy'}) then
                return {name = 'behemoth-biter', position = pos, force = 'enemy'}
            end
        end
        if math_random(1, 64) == 1 then
            if surface.can_place_entity({name = 'big-biter', position = pos, force = 'enemy'}) then
                return {name = 'big-biter', position = pos, force = 'enemy'}
            end
        end
    end
end

local function render_arena_chunk(event)
    if not global.noise_seed then
        global.noise_seed = math_random(1, 2097152)
    end
    local surface = event.surface
    local left_top = event.area.left_top

    for _, entity in pairs(surface.find_entities_filtered({area = event.area})) do
        if entity.valid then
            if entity.name ~= 'character' then
                entity.destroy()
            end
        end
    end

    local tiles = {}
    for x = 0, 31, 1 do
        for y = 0, 31, 1 do
            local pos = {x = left_top.x + x, y = left_top.y + y}
            if pos.x > arena_size or pos.y > arena_size or pos.x < arena_size * -1 or pos.y < arena_size * -1 then
                table_insert(tiles, {name = 'water', position = pos})
            else
                local noise = get_noise('terrain', pos)
                if noise > 0 then
                    table_insert(tiles, {name = global.arena_layout_modifiers.arena_tile_1, position = pos})
                else
                    table_insert(tiles, {name = global.arena_layout_modifiers.arena_tile_2, position = pos})
                end
                local entity = get_arena_entity(surface, pos)
                if entity then
                    surface.create_entity(entity)
                end
            end
        end
    end
    surface.set_tiles(tiles, true)
end

local function kill_idle_players()
    for _, player in pairs(game.connected_players) do
        if player.character then
            if player.afk_time > 600 then
                local area = {{player.position.x - 1, player.position.y - 1}, {player.position.x + 1, player.position.y + 1}}
                local water_tile_count = player.surface.count_tiles_filtered({name = {'water', 'deepwater'}, area = area})
                if water_tile_count > 3 then
                    player.character.die()
                    game.print(player.name .. ' drowned.', {r = 150, g = 150, b = 0})
                else
                    if player.afk_time > 9000 then
                        player.character.die()
                        game.print(player.name .. ' was idle for too long.', {r = 150, g = 150, b = 0})
                    end
                end
            end
        end
    end
end

local function check_for_game_over()
    local surface = game.surfaces['nauvis']

    kill_idle_players()

    local alive_players = 0
    for _, player in pairs(game.connected_players) do
        if player.character then
            alive_players = alive_players + 1
        end
    end

    if alive_players > 1 then
        return
    end -----------------

    local player
    for _, p in pairs(game.connected_players) do
        if p.character then
            player = p
        end
    end

    if alive_players == 1 then
        if not global.tank_battles_score[player.index] then
            global.tank_battles_score[player.index] = 1
        else
            global.tank_battles_score[player.index] = global.tank_battles_score[player.index] + 1
        end
        game.print(player.name .. ' has won the battle!', {r = 150, g = 150, b = 0})
        create_tank_battle_score_gui()
    end

    if alive_players == 0 then
        game.print('No alive players! Round ends in a draw!', {r = 150, g = 150, b = 0})
    end

    global.game_stage = 'lobby'
end

local function set_unique_player_force(player)
    if not game.forces[player.index] then
        game.create_force(player.index)
        game.forces[player.index].technologies['follower-robot-count-1'].researched = true
        game.forces[player.index].technologies['follower-robot-count-2'].researched = true
        game.forces[player.index].technologies['follower-robot-count-3'].researched = true
        game.forces[player.index].technologies['follower-robot-count-4'].researched = true
        game.forces[player.index].technologies['follower-robot-count-5'].researched = true
    end
    player.force = game.forces[player.index]
end

local function on_player_joined_game(event)
    local player = game.players[event.player_index]

    set_unique_player_force(player)

    if not global.map_init_done then
        local spectator_permission_group = game.permissions.create_group('Spectator')
        for action_name, _ in pairs(defines.input_action) do
            spectator_permission_group.set_allows_action(defines.input_action[action_name], false)
        end
        spectator_permission_group.set_allows_action(defines.input_action.write_to_console, true)
        spectator_permission_group.set_allows_action(defines.input_action.gui_click, true)
        spectator_permission_group.set_allows_action(defines.input_action.gui_selection_state_changed, true)
        spectator_permission_group.set_allows_action(defines.input_action.start_walking, true)
        spectator_permission_group.set_allows_action(defines.input_action.open_kills_gui, true)
        spectator_permission_group.set_allows_action(defines.input_action.open_character_gui, true)
        --spectator_permission_group.set_allows_action(defines.input_action.open_equipment_gui, true)
        spectator_permission_group.set_allows_action(defines.input_action.edit_permission_group, true)
        spectator_permission_group.set_allows_action(defines.input_action.toggle_show_entity_info, true)

        get_arena_layout_modifiers()

        global.tank_battles_score = {}
        global.game_stage = 'lobby'

        global.map_init_done = true
    end

    if #global.tank_battles_score > 0 then
        create_tank_battle_score_gui()
    end

    if game.surfaces['nauvis'] then
        player.character.destroy()
        player.character = nil
        local permissions_group = game.permissions.get_group('Spectator')
        permissions_group.add_player(player.name)
        player.teleport({0, 0}, game.surfaces['nauvis'])
        if global.lobby_timer then
            global.lobby_timer = 1200
        end
    end
end

local function on_marked_for_deconstruction(event)
    event.entity.cancel_deconstruction(game.players[event.player_index].force.name)
end

local function on_chunk_generated(event)
    render_arena_chunk(event)
end

local function on_player_respawned(event)
    local player = game.players[event.player_index]

    local permissions_group = game.permissions.get_group('Spectator')
    permissions_group.add_player(player.name)
    player.character.destroy()
    player.character = nil

    player.print('You are now spectating.', {r = 0, g = 150, b = 150})
end

local function lobby()
    local connected_players_count = 0
    local permissions_group = game.permissions.get_group('Default')

    for _, player in pairs(game.connected_players) do
        connected_players_count = connected_players_count + 1
    end

    if connected_players_count < 2 then
        --game.print("Waiting for players.", {r = 0, g = 150, b = 150})
        return
    end

    if not global.lobby_timer then
        global.lobby_timer = 1200
    end
    if global.lobby_timer % 600 == 0 then
        if global.lobby_timer <= 0 then
            game.print('Round has started!', {r = 0, g = 150, b = 150})
        else
            game.print('Round will begin in ' .. global.lobby_timer / 60 .. ' seconds.', {r = 0, g = 150, b = 150})
        end
    end
    global.lobby_timer = global.lobby_timer - 300
    if global.lobby_timer >= 0 then
        return
    end
    global.lobby_timer = nil
    global.game_stage = 'regenerate_arena'
end

local function on_tick(event)
    if game.tick % 300 == 0 then
        if global.game_stage == 'lobby' then
            lobby()
        end
        if global.game_stage == 'regenerate_arena' then
            regenerate_arena()
        end
        if global.game_stage == 'ongoing_game' then
            shrink_arena()
            check_for_game_over()
        end
    end
end

local function on_entity_died(event)
    if event.entity.name == 'wooden-chest' then
        local loot = loot_raffle[math_random(1, #loot_raffle)]
        event.entity.surface.spill_item_stack(event.entity.position, loot, true)
    --event.entity.surface.create_entity({name = "flying-text", position = event.entity.position, text = loot.name, color = {r=0.98, g=0.66, b=0.22}})
    end
end

local function on_player_died(event)
    local player = game.players[event.player_index]
    local str = ' '
    if event.cause then
        if event.cause.name ~= nil then
            str = ' by ' .. event.cause.name
        end
        if event.cause.name == 'character' then
            str = ' by ' .. event.cause.player.name
        end
        if event.cause.name == 'tank' then
            local driver = event.cause.get_driver()
            if driver.player then
                str = ' by ' .. driver.player.name
            end
        end
    end
    for _, target_player in pairs(game.connected_players) do
        if target_player.name ~= player.name then
            player.print(player.name .. ' was killed' .. str, {r = 0.99, g = 0.0, b = 0.0})
        end
    end
end

----------share chat with player and spectator force-------------------
local function on_console_chat(event)
    if not event.message then
        return
    end
    if not event.player_index then
        return
    end
    local player = game.players[event.player_index]

    local color = {}
    color = player.color
    color.r = color.r * 0.6 + 0.35
    color.g = color.g * 0.6 + 0.35
    color.b = color.b * 0.6 + 0.35
    color.a = 1

    for _, target_player in pairs(game.connected_players) do
        if target_player.name ~= player.name then
            target_player.print(player.name .. ': ' .. event.message, color)
        end
    end
end

event.add(defines.events.on_tick, on_tick)
event.add(defines.events.on_console_chat, on_console_chat)
event.add(defines.events.on_entity_died, on_entity_died)
event.add(defines.events.on_entity_damaged, on_entity_damaged)
event.add(defines.events.on_player_died, on_player_died)
event.add(defines.events.on_marked_for_deconstruction, on_marked_for_deconstruction)
event.add(defines.events.on_player_joined_game, on_player_joined_game)
event.add(defines.events.on_player_respawned, on_player_respawned)
event.add(defines.events.on_chunk_generated, on_chunk_generated)
