--luacheck: ignore
-- Mountain digger fortress, protect the cargo wagon! -- by MewMew

storage.offline_loot = true
local darkness = false

require 'utils.functions.soft_reset'
require 'utils.functions.basic_markets'

local Gui = require 'utils.gui'
local Map_score = require 'utils.gui.map_score'
local Collapse = require 'modules.collapse'
local RPG = require 'modules.rpg'
require 'modules.wave_defense.main'
require 'modules.biters_yield_coins'
require 'modules.no_deconstruction_of_neutral_entities'
require 'modules.shotgun_buff'
local Explosives = require 'modules.explosives'
require 'modules.mineable_wreckage_yields_scrap'
require 'modules.rocks_broken_paint_tiles'
require 'modules.rocks_heal_over_time'
require 'modules.rocks_yield_ore_veins'
local level_depth = require 'maps.mountain_fortress_v2.terrain'
local Immersive_cargo_wagons = require 'modules.immersive_cargo_wagons.main'
require 'maps.mountain_fortress_v2.flamethrower_nerf'
local BiterRolls = require 'modules.wave_defense.biter_rolls'
local BiterHealthBooster = require 'modules.biter_health_booster'
local Reset = require 'utils.functions.soft_reset'
local Pets = require 'modules.biter_pets'
local Map = require 'modules.map_info'
local WD = require 'modules.wave_defense.table'
local Treasure = require 'maps.mountain_fortress_v2.treasure'
local Locomotive = require 'maps.mountain_fortress_v2.locomotive'
local Modifier = require 'utils.player_modifiers'

local math_random = math.random
local math_abs = math.abs
local math_floor = math.floor

local Public = {}

local starting_items = { ['pistol'] = 1, ['firearm-magazine'] = 16, ['rail'] = 16, ['wood'] = 16, ['explosives'] = 32 }
local treasure_chest_messages = {
    "You notice an old crate within the rubble. It's filled with treasure!",
    "You find a chest underneath the broken rocks. It's filled with goodies!",
    'We has found the precious!'
}

local function game_over()
    game.print('The Fish Wagon was destroyed!!')
    local wave_defense_table = WD.get_table()
    wave_defense_table.game_lost = true
    wave_defense_table.target = nil
    storage.game_reset_tick = game.tick + 1800
    for _, player in pairs(game.connected_players) do
        player.play_sound { path = 'utility/game_lost', volume_modifier = 0.80 }
        Gui.call_existing_tab(player, 'Map Scores')
    end
end

local function set_scores()
    local fish_wagon = storage.locomotive_cargo
    if not fish_wagon then
        return
    end
    if not fish_wagon.valid then
        return
    end
    local score = math_floor(fish_wagon.position.y * -1)
    for _, player in pairs(game.connected_players) do
        if score > Map_score.get_score(player) then
            Map_score.set_score(player, score)
        end
    end
end

local function disable_recipes()
    local force = game.forces.player
    force.recipes['cargo-wagon'].enabled = false
    force.recipes['fluid-wagon'].enabled = false
    force.recipes['artillery-wagon'].enabled = false
    force.recipes['locomotive'].enabled = false
    force.recipes['pistol'].enabled = false
end

local function set_difficulty()
    local wave_defense_table = WD.get_table()
    local player_count = #game.connected_players

    wave_defense_table.max_active_biters = 1024

    -- threat gain / wave
    wave_defense_table.threat_gain_multiplier = 1.9 + player_count * 0.1

    local amount = player_count * 0.25 + 2
    amount = math.floor(amount)
    if amount > 6 then
        amount = 6
    end
    Collapse.set_amount(amount)

    wave_defense_table.wave_interval = 3600 - player_count * 60
    if wave_defense_table.wave_interval < 1800 then
        wave_defense_table.wave_interval = 1800
    end
end

function Public.reset_map()
    Immersive_cargo_wagons.reset()

    for _, player in pairs(game.players) do
        if player.controller_type == defines.controllers.editor then
            player.toggle_map_editor()
        end
    end
    local wave_defense_table = WD.get_table()
    storage.chunk_queue = {}
    storage.offline_players = {}

    local map_gen_settings = {
        ['seed'] = math_random(1, 1000000),
        ['width'] = level_depth,
        ['water'] = 0.1,
        ['starting_area'] = 1,
        ['cliff_settings'] = { cliff_elevation_interval = 0, cliff_elevation_0 = 0 },
        ['default_enable_all_autoplace_controls'] = true,
        ['autoplace_settings'] = {
            ['entity'] = { treat_missing_as_default = false },
            ['tile'] = { treat_missing_as_default = true },
            ['decorative'] = { treat_missing_as_default = true }
        }
    }

    if not storage.active_surface_index then
        storage.active_surface_index = game.create_surface('mountain_fortress', map_gen_settings).index
    else
        game.forces.player.set_spawn_position({ -2, 16 }, game.surfaces[storage.active_surface_index])
        storage.active_surface_index = Reset.soft_reset_map(game.surfaces[storage.active_surface_index], map_gen_settings, starting_items).index
    end

    local surface = game.surfaces[storage.active_surface_index]

    Explosives.set_surface_whitelist({ [surface.name] = true })

    if darkness then
        surface.min_brightness = 0.10
        surface.brightness_visual_weights = { 0.90, 0.90, 0.90 }
        surface.daytime = 0.42
        surface.freeze_daytime = true
        surface.solar_power_multiplier = 999
    end

    surface.request_to_generate_chunks({ 0, 0 }, 2)
    surface.force_generate_chunk_requests()

    for x = -768 + 32, 768 - 32, 32 do
        surface.request_to_generate_chunks({ x, 96 }, 1)
        surface.force_generate_chunk_requests()
    end

    game.difficulty_settings.technology_price_multiplier = 0.5
    game.map_settings.enemy_evolution.destroy_factor = 0
    game.map_settings.enemy_evolution.pollution_factor = 0
    game.map_settings.enemy_evolution.time_factor = 0
    game.map_settings.enemy_expansion.enabled = true
    game.map_settings.enemy_expansion.max_expansion_cooldown = 3600
    game.map_settings.enemy_expansion.min_expansion_cooldown = 3600
    game.map_settings.enemy_expansion.settler_group_max_size = 8
    game.map_settings.enemy_expansion.settler_group_min_size = 16
    game.map_settings.pollution.enabled = false

    game.forces.player.technologies['land-mine'].enabled = false
    game.forces.player.technologies['landfill'].enabled = false
    game.forces.player.technologies['fluid-wagon'].enabled = false
    game.forces.player.technologies['railway'].researched = true
    disable_recipes()

    game.forces.player.set_spawn_position({ -2, 16 }, surface)
    game.forces.enemy.set_ammo_damage_modifier('bullet', 1)
    game.forces.enemy.set_turret_attack_modifier('gun-turret', 1)

    Locomotive.locomotive_spawn(surface, { x = 0, y = 16 })

    WD.reset_wave_defense()
    wave_defense_table.surface_index = storage.active_surface_index
    wave_defense_table.target = storage.locomotive_cargo
    wave_defense_table.nest_building_density = 32
    wave_defense_table.game_lost = false
    game.reset_time_played()

    Collapse.set_kill_entities(false)
    Collapse.set_speed(8)
    Collapse.set_amount(1)
    Collapse.set_max_line_size(level_depth)
    Collapse.set_surface_index(surface.index)
    Collapse.set_position({ 0, 130 })
    Collapse.set_direction('north')

    RPG.rpg_reset_all_players()

    set_difficulty()
end

local wagon_types = {
    ['cargo-wagon'] = true,
    ['artillery-wagon'] = true,
    ['fluid-wagon'] = true,
    ['locomotive'] = true
}

local function protect_train(event)
    local entity = event.entity
    if entity.force.index ~= 1 then
        return
    end --Player Force
    if wagon_types[entity.type] then
        if event.cause then
            if event.cause.force.index == 2 then
                return
            end
        end
        event.entity.health = event.entity.health + event.final_damage_amount
    end
end

local function biters_chew_rocks_faster(event)
    if event.entity.force.index ~= 3 then
        return
    end --Neutral Force
    if not event.cause then
        return
    end
    if not event.cause.valid then
        return
    end
    if event.cause.force.index ~= 2 then
        return
    end --Enemy Force
    event.entity.health = event.entity.health - event.final_damage_amount * 5
end

local function hidden_biter(entity)
    local surface = entity.surface
    local h = math_floor(math_abs(entity.position.y))
    local m = 1 / level_depth

    local count = 64
    for _ = 1, 2, 1 do
        local c = math_floor(math_random(0, h + level_depth) * m) + 1
        if c < count then
            count = c
        end
    end

    local position = surface.find_non_colliding_position('small-biter', entity.position, 16, 0.5)
    if not position then
        position = entity.position
    end

    BiterRolls.wave_defense_set_unit_raffle(h * 0.20)

    for _ = 1, count, 1 do
        local unit
        if math_random(1, 3) == 1 then
            unit = surface.create_entity({ name = BiterRolls.wave_defense_roll_spitter_name(), position = position })
        else
            unit = surface.create_entity({ name = BiterRolls.wave_defense_roll_biter_name(), position = position })
        end

        if math_random(1, 128) == 1 then
            BiterHealthBooster.add_boss_unit(unit, m * h + 5, 0.38)
        end
    end
end

local function hidden_worm(entity)
    BiterRolls.wave_defense_set_worm_raffle(math.sqrt(entity.position.x ^ 2 + entity.position.y ^ 2) * 0.20)
    entity.surface.create_entity({ name = BiterRolls.wave_defense_roll_worm_name(), position = entity.position })
end

local function hidden_biter_pet(event)
    if math_random(1, 2048) ~= 1 then
        return
    end
    BiterRolls.wave_defense_set_unit_raffle(math.sqrt(event.entity.position.x ^ 2 + event.entity.position.y ^ 2) * 0.25)
    local unit
    if math_random(1, 3) == 1 then
        unit = event.entity.surface.create_entity({ name = BiterRolls.wave_defense_roll_spitter_name(), position = event.entity.position })
    else
        unit = event.entity.surface.create_entity({ name = BiterRolls.wave_defense_roll_biter_name(), position = event.entity.position })
    end
    Pets.biter_pets_tame_unit(game.players[event.player_index], unit, true)
end

local function hidden_treasure(event)
    if math_random(1, 320) ~= 1 then
        return
    end
    game.players[event.player_index].print(treasure_chest_messages[math_random(1, #treasure_chest_messages)], { r = 0.98, g = 0.66, b = 0.22 })
    Treasure(event.entity.surface, event.entity.position, 'wooden-chest')
end

local projectiles = { 'grenade', 'explosive-rocket', 'grenade', 'explosive-rocket', 'explosive-cannon-projectile' }
local function angry_tree(entity, cause)
    if entity.type ~= 'tree' then
        return
    end
    if math.abs(entity.position.y) < level_depth then
        return
    end
    if math_random(1, 4) == 1 then
        hidden_biter(entity)
    end
    if math_random(1, 8) == 1 then
        hidden_worm(entity)
    end
    if math_random(1, 16) ~= 1 then
        return
    end
    local position = false
    if cause then
        if cause.valid then
            position = cause.position
        end
    end
    if not position then
        position = { entity.position.x + (-20 + math_random(0, 40)), entity.position.y + (-20 + math_random(0, 40)) }
    end

    entity.surface.create_entity(
        {
            name = projectiles[math_random(1, 5)],
            position = entity.position,
            force = 'neutral',
            source = entity.position,
            target = position,
            max_range = 16,
            speed = 0.01
        }
    )
end

local function give_coin(player)
    player.insert({ name = 'coin', count = 1 })
end

local function on_player_mined_entity(event)
    if not event.entity.valid then
        return
    end
    if event.entity.force.index ~= 3 then
        return
    end

    if event.entity.type == 'simple-entity' then
        give_coin(game.players[event.player_index])

        if math_random(1, 30) == 1 then
            hidden_biter(event.entity)
            return
        end
        if math_random(1, 512) == 1 then
            hidden_worm(event.entity)
            return
        end
        hidden_biter_pet(event)
        hidden_treasure(event)
    end

    angry_tree(event.entity, game.players[event.player_index].character)
end

local function on_pre_player_left_game(event)
    local player = game.players[event.player_index]
    if player.controller_type == defines.controllers.editor then
        player.toggle_map_editor()
    end
    if player.character then
        storage.offline_players[#storage.offline_players + 1] = { index = event.player_index, tick = game.tick }
    end
end

local function on_entity_died(event)
    if not event.entity.valid then
        return
    end
    if event.entity == storage.locomotive_cargo then
        set_scores()
        game_over()
        event.entity.surface.spill_item_stack(event.entity.position, { name = 'raw-fish', count = 512 }, false)
        return
    end

    if event.cause then
        if event.cause.valid then
            if event.cause.force.index == 2 or event.cause.force.index == 3 then
                return
            end
        end
    end

    if event.entity.force.index == 3 then
        if event.entity.name == 'character' then
            return
        end
        --local r_max = 15 - math.floor(math.abs(event.entity.position.y) / (level_depth * 0.5))
        --if r_max < 3 then r_max = 3 end
        if math_random(1, 8) == 1 then
            hidden_biter(event.entity)
        end

        if math_random(1, 256) == 1 then
            hidden_worm(event.entity)
        end

        angry_tree(event.entity, event.cause)
    end
end

local function on_entity_damaged(event)
    if not event.entity.valid then
        return
    end
    protect_train(event)

    if not event.entity.health then
        return
    end
    biters_chew_rocks_faster(event)
    --neutral_force_player_damage_resistance(event)
end

local function on_research_finished(event)
    disable_recipes()
    event.research.force.character_inventory_slots_bonus = game.forces.player.mining_drill_productivity_bonus * 50 -- +5 Slots / level
    local mining_speed_bonus = game.forces.player.mining_drill_productivity_bonus * 5                              -- +50% speed / level
    if event.research.force.technologies['steel-axe'].researched then
        mining_speed_bonus = mining_speed_bonus + 0.5
    end -- +50% speed for steel-axe research
    event.research.force.manual_mining_speed_modifier = mining_speed_bonus
end

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

    set_difficulty()

    local surface = game.surfaces[storage.active_surface_index]

    if player.online_time == 0 then
        player.teleport(surface.find_non_colliding_position('character', game.forces.player.get_spawn_position(surface), 32, 0.5), surface)
        for item, amount in pairs(starting_items) do
            player.insert({ name = item, count = amount })
        end
    end

    local icw = Immersive_cargo_wagons.get_table()
    if player.surface.index ~= storage.active_surface_index and not icw.trains[tonumber(player.surface.name)] then
        player.character = nil
        player.set_controller({ type = defines.controllers.god })
        player.create_character()
        player.teleport(surface.find_non_colliding_position('character', game.forces.player.get_spawn_position(surface), 32, 0.5), surface)
        for item, amount in pairs(starting_items) do
            player.insert({ name = item, count = amount })
        end
    end

    Modifier.update_single_modifier(player, 'character_mining_speed_modifier', 'mountain_fortress', 0.5)
    Modifier.update_player_modifiers(player)

    local tile = surface.get_tile(player.position)
    if tile.valid then
        if tile.name == 'out-of-map' then
            player.teleport(surface.find_non_colliding_position('character', game.forces.player.get_spawn_position(surface), 32, 0.5), surface)
        end
    end
end

local function on_player_left_game(event)
    set_difficulty()
end

local function offline_players()
    local current_tick = game.tick
    local players = storage.offline_players
    local surface = game.surfaces[storage.active_surface_index]
    if #players > 0 then
        --log("nonzero offline players")
        local later = {}
        for i = 1, #players, 1 do
            if players[i] and game.players[players[i].index] and game.players[players[i].index].connected then
                --game.print("deleting already online character from list")
                players[i] = nil
            else
                if players[i] and players[i].tick < game.tick - 54000 then
                    --log("spawning corpse")
                    local player_inv = {}
                    local items = {}
                    player_inv[1] = game.players[players[i].index].get_inventory(defines.inventory.character_main)
                    player_inv[2] = game.players[players[i].index].get_inventory(defines.inventory.character_armor)
                    player_inv[3] = game.players[players[i].index].get_inventory(defines.inventory.character_guns)
                    player_inv[4] = game.players[players[i].index].get_inventory(defines.inventory.character_ammo)
                    player_inv[5] = game.players[players[i].index].get_inventory(defines.inventory.character_trash)
                    local e = surface.create_entity({ name = 'character', position = game.forces.player.get_spawn_position(surface), force = 'neutral' })
                    local inv = e.get_inventory(defines.inventory.character_main)
                    for ii = 1, 5, 1 do
                        if player_inv[ii].valid then
                            for iii = 1, #player_inv[ii], 1 do
                                if player_inv[ii][iii].valid then
                                    items[#items + 1] = player_inv[ii][iii]
                                end
                            end
                        end
                    end
                    if #items > 0 then
                        for item = 1, #items, 1 do
                            if items[item].valid then
                                inv.insert(items[item])
                            end
                        end
                        game.print({ 'chronosphere.message_accident' }, { r = 0.98, g = 0.66, b = 0.22 })
                        e.die('neutral')
                    else
                        e.destroy()
                    end

                    for ii = 1, 5, 1 do
                        if player_inv[ii].valid then
                            player_inv[ii].clear()
                        end
                    end
                    players[i] = nil
                else
                    later[#later + 1] = players[i]
                end
            end
        end
        players = {}
        if #later > 0 then
            for i = 1, #later, 1 do
                players[#players + 1] = later[i]
            end
        end
        storage.offline_players = players
    end
end

local function tick()
    local tick = game.tick
    if tick % 30 == 0 then
        if tick % 1800 == 0 then
            if not Locomotive.set_player_spawn_and_refill_fish() and not storage.game_reset_tick then
                game_over()
            end
            local surface = game.surfaces[storage.active_surface_index]
            local position = surface.find_non_colliding_position('stone-furnace', Collapse.get_position(), 128, 1)
            if position then
                local wave_defense_table = WD.get_table()
                wave_defense_table.spawn_position = position
            end
            if storage.offline_loot then
                offline_players()
            end
            set_scores()
        end
        if storage.game_reset_tick then
            if storage.game_reset_tick < tick then
                storage.game_reset_tick = nil
                require 'maps.mountain_fortress_v2.main'.reset_map()
            end
            return
        end
        Locomotive.fish_tag()
    end
end

local function on_init()
    local T = Map.Pop_info()
    T.localised_category = 'mountain_fortress'
    T.main_caption_color = { r = 150, g = 150, b = 0 }
    T.sub_caption_color = { r = 0, g = 150, b = 0 }
    storage.rocks_yield_ore_maximum_amount = 500
    storage.rocks_yield_ore_base_amount = 40
    storage.rocks_yield_ore_distance_modifier = 0.020

    Explosives.set_destructible_tile('out-of-map', 1500)
    Explosives.set_destructible_tile('water', 1000)
    Explosives.set_destructible_tile('water-green', 1000)
    Explosives.set_destructible_tile('deepwater-green', 1000)
    Explosives.set_destructible_tile('deepwater', 1000)
    Explosives.set_destructible_tile('water-shallow', 1000)
    Explosives.set_destructible_tile('water-mud', 1000)

    Map_score.set_score_description('Wagon distance reached:')

    Public.reset_map()
end

local event = require 'utils.event'
event.on_init(on_init)
event.on_nth_tick(2, tick)
event.add(defines.events.on_entity_damaged, on_entity_damaged)
event.add(defines.events.on_entity_died, on_entity_died)
event.add(defines.events.on_player_joined_game, on_player_joined_game)
event.add(defines.events.on_player_left_game, on_player_left_game)
event.add(defines.events.on_pre_player_left_game, on_pre_player_left_game)
event.add(defines.events.on_player_mined_entity, on_player_mined_entity)
event.add(defines.events.on_research_finished, on_research_finished)

require 'modules.rocks_yield_ore'

return Public
