dofile_once("mods/quant.ew/NoitaPatcher/load.lua")
np = require("noitapatcher")

package.cpath = package.cpath .. ";./mods/quant.ew/?.dll"
package.path = package.path .. ";./mods/quant.ew/?.lua"
print(package.cpath)

dofile_once("data/scripts/lib/utilities.lua")

np.InstallShootProjectileFiredCallbacks()
np.EnableGameSimulatePausing(false)
np.InstallDamageDetailsPatch()
np.SilenceLogs("Warning - streaming didn't find any chunks it could stream away...\n")

ewext = require("ewext")

-- Make some stuff global, as it's way too annoying to import each time.
constants = dofile_once("mods/quant.ew/files/core/constants.lua")
ctx = dofile_once("mods/quant.ew/files/core/ctx.lua")
util = dofile_once("mods/quant.ew/files/core/util.lua")
net = dofile_once("mods/quant.ew/files/core/net.lua")
inventory_helper = dofile_once("mods/quant.ew/files/core/inventory_helper.lua")
player_fns = dofile_once("mods/quant.ew/files/core/player_fns.lua")

local cos = dofile_once("mods/quant.ew/files/system/player/player_cosmetics.lua")

local perk_fns = dofile_once("mods/quant.ew/files/core/perk_fns.lua")

local version = ModDoesFileExist("mods/quant.ew/files/version.lua") and dofile_once("mods/quant.ew/files/version.lua")
    or "unknown (dev build)"
print("Noita EW version: " .. version)

dofile_once("data/scripts/lib/coroutines.lua")

ModLuaFileAppend("data/scripts/gun/gun.lua", "mods/quant.ew/files/resource/append/gun.lua")
ModLuaFileAppend("data/scripts/gun/gun_actions.lua", "mods/quant.ew/files/resource/append/action_fix.lua")

if ModSettingGet("quant.ew.enable_log") or true then
    ModMagicNumbersFileAdd("mods/quant.ew/files/magic_numbers.xml")
else
    ModMagicNumbersFileAdd("mods/quant.ew/files/magic.xml")
end

util.add_cross_call("ew_per_peer_seed", function()
    return tonumber(string.sub(ctx.my_id, 8, 12), 16), tonumber(string.sub(ctx.my_id, 12), 16)
end)

util.add_cross_call("ew_spectator", function()
    if ctx.spectating_over_peer_id == nil then
        return (ctx.my_player ~= nil and ctx.my_player.entity) or EntityGetWithTag("player_unit")[1]
    else
        return ctx.players[ctx.spectating_over_peer_id].entity
    end
end)

if GameHasFlagRun("ending_game_completed") then
    ModTextFileSetContent("data/entities/misc/loose_chunks.xml", "<Entity/>")
    ModTextFileSetContent("data/entities/misc/loose_chunks_huge.xml", "<Entity/>")
    ModTextFileSetContent("data/entities/projectiles/deck/crumbling_earth_effect.xml", "<Entity/>")
end

local function load_modules()
    ctx.load_system("uniq_flags")
    ctx.load_system("ewext_init")

    -- ctx.dofile_and_add_hooks("mods/quant.ew/files/system/item_sync.lua")

    ctx.dofile_and_add_hooks("mods/quant.ew/files/system/player_sync.lua")
    -- ctx.dofile_and_add_hooks("mods/quant.ew/files/system/enemy_sync.lua")

    if ctx.proxy_opt.game_mode == "shared_health" then
        ctx.load_system("damage")
        ctx.load_system("heart_pickups")
        ctx.load_system("patch_meat_biome")
        ctx.load_system("kivi_patch")
    end
    if ctx.proxy_opt.game_mode == "local_health" then
        ctx.load_system("local_health")
        ctx.load_system("notplayer_ai")
        ctx.load_system("spectator_helps")
        ctx.load_system("end_fight")
    end

    ctx.dofile_and_add_hooks("mods/quant.ew/files/system/nickname.lua")

    if ctx.proxy_opt.debug then
        ctx.dofile_and_add_hooks("mods/quant.ew/files/system/debug.lua")
    end

    ctx.load_system("fungal_shift")
    ctx.dofile_and_add_hooks("mods/quant.ew/files/system/weather_sync.lua")
    ctx.load_system("polymorph")

    ctx.load_system("world_sync")

    -- ctx.load_system("spawn_hooks")
    ctx.dofile_and_add_hooks("mods/quant.ew/files/system/proxy_info.lua")
    ctx.load_system("perk_patches")

    ctx.load_system("player_tether")

    ctx.load_system("kolmi")
    ctx.load_system("ending")
    ctx.load_system("spell_patches")
    ctx.load_system("enemy_scaling")

    ctx.load_system("patch_dragon_boss")

    ctx.load_system("player_arrows")
    ctx.load_system("player_ping")
    ctx.load_system("extra_genomes")
    ctx.load_system("game_effect_sync")
    ctx.load_system("orb_sync")
    ctx.load_system("flag_sync")
    ctx.load_system("essence_sync")
    ctx.load_system("spectate")
    ctx.load_system("effect_data_sync")
    -- if ctx.proxy_opt.item_dedup then
    -- ctx.load_system("gen_sync")
    -- end
    ctx.load_system("karl")
    ctx.load_system("remove_wand_sound")
    if ctx.proxy_opt.randomize_perks then
        ctx.load_system("randomize_perks")
    end
    ctx.load_system("streaming_sync")
    if not ctx.proxy_opt.same_loadout then
        ctx.load_system("rnd_wands")
    end
    ctx.load_system("worms")
    ctx.load_system("wand_charm")
    ctx.load_system("stevari")
    ctx.load_system("angry_ghost_memory")
    ctx.load_system("gate_boss")
    ctx.load_system("tapion")
    ctx.load_system("world_sync_cuts")
    ctx.load_system("hamis")
    ctx.load_system("greed")
    ctx.load_system("spell_refresh")
    ctx.load_system("shiny_orb")
    ctx.load_system("potion_mimic")
    ctx.load_system("map")
    ctx.load_system("homunculus")
    ctx.load_system("text")
    ctx.load_system("ragdoll_fix")
    ctx.load_system("explosion_cuts")
    ctx.load_system("wang_hooks")
    ctx.load_system("entity_sync_helper")
    ctx.load_system("telekenisis")
    ctx.load_system("dice")
    ctx.load_system("sampo")
    ctx.load_system("meat")
    ctx.load_system("boss_damage")
    if ctx.proxy_opt.pvp then
        ctx.load_system("pvp")
    end
end

local function load_extra_modules()
    print("Starting to load extra stuff")
    for _, file in ipairs(ModLuaFileGetAppends("mods/quant.ew/files/api/extra_modules.lua")) do
        ctx.dofile_and_add_hooks(file)
    end
end

local last_mana = -1

local function fire()
    local inventory_component = EntityGetFirstComponentIncludingDisabled(ctx.my_player.entity, "Inventory2Component")
    if inventory_component ~= nil then
        local last_switch = ComponentGetValue2(inventory_component, "mLastItemSwitchFrame")
        local switched_now = last_switch == GameGetFrameNum()
        local special_seed = tonumber(GlobalsGetValue("ew_player_rng", "0"))
        local fire_data = player_fns.make_fire_data(special_seed, ctx.my_player, last_mana)
        if fire_data ~= nil then
            if switched_now then
                fire_data.switched_now = true
            end
            net.send_fire(fire_data)
        end
    end
end

function OnProjectileFired(
    shooter_id,
    projectile_id,
    initial_rng,
    position_x,
    position_y,
    target_x,
    target_y,
    send_message,
    unknown1,
    multicast_index,
    unknown3
)
    ctx.hook.on_projectile_fired(
        shooter_id,
        projectile_id,
        initial_rng,
        position_x,
        position_y,
        target_x,
        target_y,
        send_message,
        unknown1,
        multicast_index,
        unknown3
    )
    if not EntityHasTag(shooter_id, "player_unit") and not EntityHasTag(shooter_id, "ew_client") then
        return -- Not fired by player, we don't care about it (for now?)
    end
    EntityAddTag(projectile_id, "ew_no_enemy_sync")
    local projectileComponent = EntityGetFirstComponentIncludingDisabled(projectile_id, "ProjectileComponent")
    local entity_that_shot
    if projectileComponent == nil then
        entity_that_shot = GameGetWorldStateEntity()
    else
        entity_that_shot = ComponentGetValue2(projectileComponent, "mEntityThatShot")
    end

    local shooter_player_data = player_fns.get_player_data_by_local_entity_id(shooter_id)
    local rng = 0
    -- Was shot locally
    if shooter_id == ctx.my_player.entity then
        -- If it was an initial shot by host
        if entity_that_shot == 0 and multicast_index ~= -1 and unknown3 == 0 then
            if tonumber(GlobalsGetValue("ew_wand_fired", "0")) ~= 0 then
                rng = initial_rng
                table.insert(shooter_player_data.projectile_rng_init, rng)
            else
                rng = (shooter_player_data.projectile_seed_chain[shooter_id - 1] or 0) + 25
            end
        else
            rng = (shooter_player_data.projectile_seed_chain[entity_that_shot] or 0) + 25
        end
    else
        if entity_that_shot == 0 and multicast_index ~= -1 and unknown3 == 0 then
            if #shooter_player_data.projectile_rng_init > 0 then
                rng = table.remove(shooter_player_data.projectile_rng_init, 1)
            else
                rng = (shooter_player_data.projectile_seed_chain[shooter_id - 1] or 0) + 25
            end
        else
            rng = (shooter_player_data.projectile_seed_chain[entity_that_shot] or 0) + 25
        end
    end
    shooter_player_data.projectile_seed_chain[shooter_id - 1] = rng
    shooter_player_data.projectile_seed_chain[entity_that_shot] = rng
    shooter_player_data.projectile_seed_chain[projectile_id] = rng
    for _, lua in ipairs(EntityGetComponent(projectile_id, "LuaComponent") or {}) do
        local src = ComponentGetValue2(lua, "script_source_file")
        if
            src == "data/scripts/projectiles/transmutation.lua"
            or src == "data/scripts/projectiles/random_explosion.lua"
            or src == "data/scripts/projectiles/fizzle.lua"
            or src == "data/scripts/projectiles/teleport_cast.lua"
        then
            EntityAddComponent2(
                projectile_id,
                "VariableStorageComponent",
                { name = "ew_transmutation", value_int = rng }
            )
        end
    end
    local n = EntityGetFilename(projectile_id)
    if
        n == "data/entities/items/pickup/egg_hollow.xml"
        or string.sub(n, 1, 31) == "data/entities/items/pickup/egg_"
    then
        EntityAddComponent2(projectile_id, "VariableStorageComponent", { _tags = "ew_egg", value_int = rng })
        local body = EntityGetFirstComponentIncludingDisabled(projectile_id, "PhysicsBody2Component")
        if body ~= nil then
            ComponentSetValue2(body, "destroy_body_if_entity_destroyed", true)
        end
        if shooter_player_data.peer_id ~= ctx.my_id and string.sub(n, 1, 31) == "data/entities/items/pickup/egg_" then
            local exp = EntityGetFirstComponentIncludingDisabled(projectile_id, "ExplodeOnDamageComponent")
            if exp ~= nil then
                ComponentObjectSetValue2(exp, "config_explosion", "load_this_entity", "")
            end
        end
        --ewext.sync_projectile(projectile_id, shooter_player_data.peer_id, rng)
    elseif EntityHasTag(projectile_id, "ew_projectile_position_sync") then
        local body = EntityGetFirstComponentIncludingDisabled(projectile_id, "PhysicsBody2Component")
        local proj = EntityGetFirstComponentIncludingDisabled(projectile_id, "ProjectileComponent")
        local life = EntityGetFirstComponentIncludingDisabled(projectile_id, "LifetimeComponent")
        if proj == nil or ComponentGetValue2(proj, "lifetime") > 4 or ComponentGetValue2(life, "lifetime") > 4 then
            if EntityGetIsAlive(projectile_id) then
                ewext.sync_projectile(projectile_id, shooter_player_data.peer_id, rng)
            end
        end
        if shooter_player_data.peer_id ~= ctx.my_id then
            if proj ~= nil then
                local lif = ComponentGetValue2(proj, "lifetime")
                if lif > 0 then
                    ComponentSetValue2(proj, "lifetime", lif * ctx.my_player.fps / shooter_player_data.fps)
                end
            end
            if life ~= nil then
                local lif = ComponentGetValue2(life, "lifetime")
                if lif > 0 then
                    ComponentSetValue2(life, "lifetime", lif * ctx.my_player.fps / shooter_player_data.fps)
                end
            end
            if body ~= nil then
                ComponentSetValue2(body, "destroy_body_if_entity_destroyed", true)
            end
        end
    end
    np.SetProjectileSpreadRNG(rng)
    if shooter_player_data.peer_id ~= ctx.my_id then
        for _, com in ipairs(EntityGetComponent(projectile_id, "LuaComponent") or {}) do
            local s = ComponentGetValue2(com, "script_source_file")
            if
                s == "data/entities/projectiles/deck/pebble_player.xml"
                or s == "data/scripts/animals/wand_ghost.lua"
                or s == "data/scripts/animals/pebble_player_spawn.lua"
            then
                EntityRemoveComponent(projectile_id, com)
            end
        end
    end
end

function OnProjectileFiredPost(
    shooter_id,
    projectile_id,
    rng,
    position_x,
    position_y,
    target_x,
    target_y,
    send_message,
    unknown1,
    multicast_index,
    unknown3
)
    if EntityHasTag(shooter_id, "ew_client") then
        local shooter_player_data = player_fns.get_player_data_by_local_entity_id(shooter_id)
        local vel = EntityGetFirstComponentIncludingDisabled(projectile_id, "VelocityComponent")
        if vel ~= nil then
            local x, y = ComponentGetValue2(vel, "mVelocity")
            local m = shooter_player_data.fps / ctx.my_player.fps
            ComponentSetValue2(vel, x * m, y * m)
        end
    end
end

util.add_cross_call("ew_is_wand_pickup", function()
    return ctx.is_wand_pickup
end)

util.add_cross_call("ew_pvp", function()
    return ctx.proxy_opt.pvp
end)

util.add_cross_call("ew_do_i_own", util.do_i_own)

local cross_force_send_inventory = false

util.add_cross_call("ew_api_force_send_inventory", function()
    cross_force_send_inventory = true
end)

function OnPausedChanged(paused, is_wand_pickup)
    ctx.is_paused = paused
    ctx.is_wand_pickup = is_wand_pickup

    local players = EntityGetWithTag("player_unit") or {}

    if players[1] then
        np.RegisterPlayerEntityId(players[1])
        --local inventory_gui = EntityGetFirstComponentIncludingDisabled(players[1], "InventoryGuiComponent")
        if paused then
            --EntitySetComponentIsEnabled(players[1], inventory_gui, false)
            np.EnableInventoryGuiUpdate(false)
            np.EnablePlayerItemPickUpper(false)
        else
            --EntitySetComponentIsEnabled(players[1], inventory_gui, true)
            np.EnableInventoryGuiUpdate(true)
            np.EnablePlayerItemPickUpper(true)
        end
    end
end

function OnWorldInitialized() -- This is called once the game world is initialized. Doesn't ensure any world chunks actually exist. Use OnPlayerSpawned to ensure the chunks around player have been loaded or created.
    if ctx.is_host then
        GameAddFlagRun("ew_flag_this_is_host")
    else
        GameRemoveFlagRun("ew_flag_this_is_host")
    end
    ctx.hook.on_world_initialized()
end

local last_chunk

local last_flex

function OnPlayerSpawned(player_entity) -- This runs when player entity has been created
    print("Initial player entity: " .. player_entity)
    if ctx.proxy_opt.home_on_players then
        EntityAddTag(player_entity, "homing_target")
    end

    if GlobalsGetValue("ew_player_count", "") == "" then
        GlobalsSetValue("ew_player_count", "1")
    end

    local x, y = EntityGetTransform(player_entity)
    ctx.initial_player_pos = { x = x, y = y }

    local my_player = player_fns.make_playerdata_for(player_entity, ctx.my_id)
    ctx.players[ctx.my_id] = my_player
    ctx.player_data_by_local_entity[player_entity] = my_player
    ctx.ready = true
    ctx.my_player = my_player

    EntityAddTag(player_entity, "ew_peer")

    if not GameHasFlagRun("ew_flag_notplayer_active") then
        EntityAddComponent2(
            player_entity,
            "LuaComponent",
            { script_wand_fired = "mods/quant.ew/files/resource/cbs/count_times_wand_fired.lua" }
        )
    end

    net.send_welcome()

    local item_pick = EntityGetFirstComponentIncludingDisabled(player_entity, "ItemPickUpperComponent")
    if item_pick ~= nil then
        ComponentSetValue2(item_pick, "is_immune_to_kicks", true)
    end

    ctx.hook.on_local_player_spawn(my_player)
    ctx.hook.on_should_send_updates()

    GamePrint("Noita Entangled Worlds version " .. version)

    OnPausedChanged(false, false)

    print("Game state entity: " .. GameGetWorldStateEntity())

    if not GameHasFlagRun("ew_flag_notplayer_active") then
        cos.player_cosmetics(player_entity)
        cos.player_color(player_entity)
    else
        local damage = ComponentGetValue2(player_entity, "DamageModelComponent")
        if damage ~= nil then
            ComponentSetValue2(damage, "ui_report_damage", false)
            ComponentSetValue2(damage, "hp", 2 ^ -38)
        end
        EntityInflictDamage(
            player_entity,
            1000000,
            "DAMAGE_CURSE",
            "dont rejoin",
            "NONE",
            0,
            0,
            GameGetWorldStateEntity()
        )
        GameAddFlagRun("ew_kill_player")
    end
    last_flex = ModSettingGet("quant.ew.flex")
    np.MagicNumbersSetValue("GRID_FLEXIBLE_MAX_UPDATES", last_flex)
    local controls_component = EntityGetFirstComponentIncludingDisabled(player_entity, "ControlsComponent")
    ComponentSetValue2(controls_component, "enabled", true)
    for _, child in ipairs(EntityGetAllChildren(player_entity) or {}) do
        local com = EntityGetFirstComponentIncludingDisabled(child, "LuaComponent")
        if com ~= nil and ComponentGetValue2(com, "script_source_file") == "data/scripts/perks/map.lua" then
            EntityRemoveComponent(child, com)
            EntityAddComponent2(child, "LuaComponent", { script_source_file = "data/scripts/perks/map.lua" })
            return
        end
    end
    inventory_helper.setup_inventory()
end

local last_n = 1

local function on_world_pre_update_inner()
    if ctx.my_player == nil or ctx.my_player.entity == nil then
        return
    end

    GlobalsSetValue("ew_player_rng", tostring(GameGetFrameNum()))

    net.update()
    if ctx.run_ended then
        return
    end

    local inventory_gui_comp = EntityGetFirstComponentIncludingDisabled(ctx.my_player.entity, "InventoryGuiComponent")
    if inventory_gui_comp and inventory_gui_comp ~= 0 then
        local inventory_open = ComponentGetValue2(inventory_gui_comp, "mActive")
        if ctx.is_inventory_open and not inventory_open then
            ctx.events.inventory_maybe_just_changed = true
        end
        ctx.is_inventory_open = inventory_open
    end

    if GameGetFrameNum() % 16 == 13 and not ctx.run_ended then
        if ctx.host_id == ctx.my_id then
            local new_chunk = tonumber(ModSettingGet("quant.ew.chunk_target") or 24) or 24
            if last_chunk ~= new_chunk then
                last_chunk = new_chunk
                np.MagicNumbersSetValue("STREAMING_CHUNK_TARGET", last_chunk)
            end
        end
        local new_flex = ModSettingGet("quant.ew.flex")
        if new_flex ~= last_flex then
            last_flex = new_flex
            np.MagicNumbersSetValue("GRID_FLEXIBLE_MAX_UPDATES", last_flex)
        end
        player_fns.respawn_if_necessary()
    end

    local sha_check = GameGetFrameNum() % 5 == 0 and inventory_helper.has_inventory_changed(ctx.my_player)
    if
        ctx.events.new_player_just_connected
        or ctx.events.inventory_maybe_just_changed
        or sha_check
        or cross_force_send_inventory
    then
        cross_force_send_inventory = false
        local inventory_state, spells = player_fns.serialize_items(ctx.my_player)
        if inventory_state ~= nil then
            net.send_player_inventory(inventory_state, spells)
        end
    end

    -- Perk sync
    if GameGetFrameNum() % 120 == 10 and not ctx.run_ended then
        local perk_data = perk_fns.get_my_perks()
        if perk_data ~= nil then
            net.send_player_perks(perk_data)
        end
    end

    if not ctx.run_ended then
        --local ti = GameGetRealWorldTimeSinceStarted()
        local n = EntitiesGetMaxID()
        local arr = {}
        for ent = last_n + 1, n do
            if EntityGetIsAlive(ent) then
                if
                    not ctx.is_host
                    and ctx.proxy_opt.disable_kummitus
                    and EntityGetName(ent) == "$animal_playerghost"
                then
                    EntityKill(ent)
                else
                    table.insert(arr, ent)
                    local homing = EntityGetFirstComponentIncludingDisabled(ent, "HomingComponent")
                    if homing ~= nil then
                        local projcom = EntityGetFirstComponentIncludingDisabled(ent, "ProjectileComponent")
                        if projcom ~= nil then
                            local whoshot = ComponentGetValue2(projcom, "mWhoShot")
                            if EntityHasTag(whoshot, "ew_notplayer") or GameHasFlagRun("ending_game_completed") then
                                ComponentSetValue2(homing, "target_tag", "ew_peer")
                            end
                        end
                    end
                end
            end
        end
        if #arr ~= 0 then
            ctx.hook.on_new_entity(arr)
        end
        last_n = n
        --local tf = GameGetRealWorldTimeSinceStarted()
        if ctx.is_host then
            ctx.hook.on_world_update_host()
        else
            ctx.hook.on_world_update_client()
        end
        --local tf2 = GameGetRealWorldTimeSinceStarted()
        ctx.hook.on_world_update()
        --[[local tf3 = GameGetRealWorldTimeSinceStarted()
        if GameGetFrameNum() % 5 == 0 then
            GamePrint(
                math.ceil((tf - ti) * 1000000)
                    .. " "
                    .. math.ceil((tf2 - tf) * 1000000)
                    .. " "
                    .. math.ceil((tf3 - tf2) * 1000000)
            )
        end]]
    end

    perk_fns.on_world_update()
    wake_up_waiting_threads(1)
end

function OnWorldPreUpdate() -- This is called every time the game is about to start updating the world
    if net.connect_failed then
        if GameGetFrameNum() % 180 == 0 then
            GamePrint("Entangled Worlds mod is enabled, but it couldn't connect to proxy!")
            GamePrint("You need to start the proxy and join the lobby first.")
            GamePrint("If you want to play singleplayer, disable the mod and start a new game.")
        end
        return
    end
    util.tpcall(on_world_pre_update_inner)
    --[[if InputIsKeyJustDown(11) then
        local x, y = EntityGetTransform(ctx.my_player.entity)
        for _ = 1, 16 do
            EntityLoad("data/entities/animals/longleg.xml", x, y)
        end
    end]]
end

local function on_world_post_update_inner()
    if ctx.my_player == nil or ctx.my_player.entity == nil then
        return
    end

    if ctx.run_ended then
        return
    end

    ctx.hook.on_world_update_post()

    local times_wand_fired = tonumber(GlobalsGetValue("ew_wand_fired", "0"))
    GlobalsSetValue("ew_wand_fired", "0")
    local wand = player_fns.get_active_held_item(ctx.my_player.entity)
    local ability = EntityGetFirstComponentIncludingDisabled(wand, "AbilityComponent")
    if
        times_wand_fired > 0
        or (
            wand ~= nil
            and EntityHasTag(wand, "card_action")
            and ability ~= nil
            and ComponentGetValue2(ability, "mCastDelayStartFrame") == GameGetFrameNum()
        )
    then
        fire()
    end
    if ability ~= nil then
        last_mana = ComponentGetValue2(ability, "mana")
    else
        last_mana = -1
    end
end

function OnWorldPostUpdate() -- This is called every time the game has finished updating the world
    if net.connect_failed then
        return
    end
    util.tpcall(on_world_post_update_inner)
    ctx.events = {}
    net.proxy_send("flush", "")
    ctx.finish()
end

function register_localizations(translation_file, clear_count)
    clear_count = clear_count or 0

    local loc_content = ModTextFileGetContent("data/translations/common.csv") -- Gets the original translations of the game

    local append_content = ModTextFileGetContent(translation_file) -- Gets my own translations file

    -- Split the append_content into lines
    local lines = {}
    for line in append_content:gmatch("[^\n]+") do
        table.insert(lines, line)
    end

    -- Remove the first clear_count lines
    for i = 1, clear_count do
        table.remove(lines, 1)
    end

    -- Reconstruct append_content after removing clear_count lines
    local new_append_content = table.concat(lines, "\n")

    -- if loc_content does not end with a new line, add one
    if not loc_content:match("\n$") then
        loc_content = loc_content .. "\n"
    end

    -- Concatenate loc_content and new_append_content without extra newline character
    local new_content = loc_content .. new_append_content .. "\n"

    -- Set the new content to the file
    ModTextFileSetContent("data/translations/common.csv", new_content)
end

function OnModPreInit()
    register_localizations("mods/quant.ew/translations.csv", 1)
    ctx.init()
    net.init()

    if not net.connect_failed then
        util.tpcall(load_modules)
        print("Entangled worlds init complete")
    end
end

function OnModInit()
    load_extra_modules()
end

function OnModPostInit()
    ctx.hook.on_late_init()
end

function OnPlayerDied(player_entity)
    ctx.hook.on_player_died(player_entity)
    print("player died")
end
