#pragma once
#include <windows.h> 
#include <mutex>    
#include <vector>
#include <string>
#include "../../util/Config.h"
#include "../eft_api.h"
#include "../game_state.h"
#include "../../config/IntSliderValue.h"
#include "../../il2cpp/il2utils.h"
#include "../../il2cpp/Il2CppObjectInstance.h"
#include "../../il2cpp/unity.h"
#include "../../gui/menu/menu.h"
#include <fstream>
#include <unordered_map>
#include <thread>
#include <atomic>
#include <nlohmann/json.hpp> 
using json = nlohmann::json;

class EspModule : Module
{
public:
    EspModule() : Module("透视", Visuals)
    {
    }

    CheckboxValue* extracts = conf(new CheckboxValue(false, "撤离点"));
    ColorpickerValue* extracts_color = conf(new ColorpickerValue({ 0, 1, 0, 1 }, "撤离点颜色"));

    CheckboxValue* transits = conf(new CheckboxValue(false, "转场点"));
    ColorpickerValue* transits_color = conf(new ColorpickerValue({ 1, 1, 0, 1 }, "转场点颜色"));

    CheckboxValue* players = conf(new CheckboxValue(false, "玩家"));
    ColorpickerValue* player_color = conf(new ColorpickerValue({ 1, 0, 0, 1 }, "玩家颜色"));
    CheckboxValue* npc = conf(new CheckboxValue(false, "NPC"));
    ColorpickerValue* npc_color = conf(new ColorpickerValue({ 1, 1, 0, 1 }, "NPC颜色"));

    CheckboxValue* loot_esp = conf(new CheckboxValue(false, "战利品"));
    ColorpickerValue* loot_color = conf(new ColorpickerValue({ 0, 1, 1, 1 }, "战利品颜色"));
    FloatSliderValue* loot_distance = conf(new FloatSliderValue(50.f, 1.f, 500.f, "战利品距离"));

    IntSliderValue* loot_min_value = conf(new IntSliderValue(10000, 0, 500000, "战利品最小价值"));
    CheckboxValue* show_provisions = conf(new CheckboxValue(true, "显示食物/水"));

    CheckboxValue* container_esp = conf(new CheckboxValue(false, "容器"));
    ColorpickerValue* container_color = conf(new ColorpickerValue({ 1, 0.5f, 0, 1 }, "容器颜色"));
    FloatSliderValue* container_distance = conf(new FloatSliderValue(100.f, 1.f, 500.f, "容器距离"));

    IntSliderValue* font_size = conf(new IntSliderValue(12, 8, 36, "字体大小"));

    enum class player_type : char
    {
        pmc,
        npc,
    };

    struct exfil { std::string name; int status; unity::vector3 pos3d; };
    struct transit { std::string name; unity::vector3 pos3d; };
    struct player { std::string name; bool pmc; unity::vector3 pos3d; };

    struct loot_entry
    {
        std::string name;
        unity::vector3 pos3d;
        bool is_container;
        int price;
    };

    unity::vector3 my_pos = {};

    std::vector<exfil> exfil_store;
    std::vector<transit> transits_store;
    std::vector<player> player_store;
    std::vector<loot_entry> loot_store;

    struct cached_container { Il2CppObject* obj; std::string name; };
    inline static std::vector<cached_container> cached_containers{};
    inline static ULONGLONG cached_containers_last_refresh = 0;

private:
    std::unordered_map<std::string, int> market_prices;
    std::atomic<bool> prices_loaded{ false };

    struct ItemCacheData {
        std::string name;
        int price;
        bool is_provision;
    };
    std::unordered_map<Il2CppObject*, ItemCacheData> item_info_cache;
    Il2CppObject* last_game_world_ptr = nullptr;


    std::mutex data_mutex;

    int extracts_key = 0;
    int transits_key = 0;
    int players_key = 0;
    int npc_key = 0;
    int loot_key = 0;
    int container_key = 0;
    int provisions_key = 0;

    bool extracts_key_pressed = false;
    bool transits_key_pressed = false;
    bool players_key_pressed = false;
    bool npc_key_pressed = false;
    bool loot_key_pressed = false;
    bool container_key_pressed = false;
    bool provisions_key_pressed = false;
    int* waiting_for_key_ptr = nullptr;

    void load_market_prices()
    {
        std::string json_path = "../launcher/tarkov_prices.json";
        std::string json_path_non_steam = "../BsgLauncher/tarkov_prices.json";

        Configuration cfg;
        if (cfg.load("launcher.cfg"))
        {

            json_path = cfg.get("marketJson", json_path);
            if (!json_path.empty()) {
                json_path.erase(0, json_path.find_first_not_of(" \t\r\n\""));
                json_path.erase(json_path.find_last_not_of(" \t\r\n\"") + 1);
            }
        }

        std::ifstream file(json_path);
        // Check for if file was able to be opened, if not try the alternate path (non-steam) automatically since non steam users are likely still more and most people have no idea how to edit configs
        // otherwise exit early as before
        if (!file.is_open()) {
            std::ifstream file(json_path_non_steam);
            if (!file.is_open()) {
                prices_loaded = true;
                return;
            }
        }

        try
        {
            json j;
            file >> j;

            auto data_it = j.find("data");
            if (data_it != j.end())
            {
                auto items_it = data_it->find("items");
                if (items_it != data_it->end() && items_it->is_array())
                {
                    for (const auto& item : *items_it)
                    {
                        auto id_it = item.find("id");
                        if (id_it != item.end() && id_it->is_string())
                        {
                            std::string id = id_it->get<std::string>();
                            int price = 0;

                            auto low_price_it = item.find("lastLowPrice");
                            if (low_price_it != item.end() && low_price_it->is_number()) {
                                price = static_cast<int>(low_price_it->get<double>());
                            }

                            if (price == 0) {
                                auto avg_price_it = item.find("avg24hPrice");
                                if (avg_price_it != item.end() && avg_price_it->is_number()) {
                                    price = static_cast<int>(avg_price_it->get<double>());
                                }
                            }

                            if (price == 0) {
                                auto sell_for_it = item.find("sellFor");
                                if (sell_for_it != item.end() && sell_for_it->is_array()) {
                                    for (const auto& sell_option : *sell_for_it) {
                                        auto sell_price_it = sell_option.find("price");
                                        if (sell_price_it != sell_option.end() && sell_price_it->is_number()) {
                                            int p = static_cast<int>(sell_price_it->get<double>());
                                            if (p > price) price = p;

                                        }
                                    }
                                }
                            }

                            if (price > 0) {
                                market_prices[id] = price;
                            }
                        }
                    }
                }
            }
        }
        catch (...) {}

        prices_loaded = true;
    }

    std::string get_key_name(int key_code)
    {
        if (key_code == 0) return "NONE";
        if (key_code > 0 && key_code < 165) return menu::KeyNames[key_code];
        return "Key " + std::to_string(key_code);
    }

    void handle_hotkey(int key_code, bool& is_pressed_state, CheckboxValue* checkbox)
    {
        if (key_code == 0 || waiting_for_key_ptr != nullptr || checkbox == nullptr) return;

        if (GetAsyncKeyState(key_code) & 0x8000) {
            if (!is_pressed_state) {
                checkbox->set(checkbox->get_value() ? "false" : "true");
                is_pressed_state = true;
            }
        }
        else {
            is_pressed_state = false;
        }
    }

    void draw_keybind_button(const char* label, int& key_var)
    {
        ImGui::Text("%s", label);

        float btn_width = 85.0f;
        ImGui::SameLine(ImGui::GetWindowWidth() - btn_width - 20.0f);

        std::string btn_text;
        if (waiting_for_key_ptr == &key_var) {
            btn_text = "[ ... ]";
        }
        else {
            btn_text = "[ " + get_key_name(key_var) + " ]";
        }

        ImGui::PushID(label);
        if (ImGui::Button(btn_text.c_str(), ImVec2(btn_width, 0))) {
            waiting_for_key_ptr = &key_var;
        }
        ImGui::PopID();

        if (waiting_for_key_ptr == &key_var) {
            for (int i = 3; i < 256; i++) {
                if (GetAsyncKeyState(i) & 0x8000) {
                    if (i == VK_ESCAPE) {
                        key_var = 0;
                    }
                    else {
                        key_var = i;
                    }
                    waiting_for_key_ptr = nullptr;
                    break;
                }
            }
        }
    }

public:
    void init() override
    {
        std::thread([this]() {
            load_market_prices();
            }).detach();
    }

    void draw_menu_extra() override
    {
        ImGui::Dummy(ImVec2(0, 8.0f));
        ImGui::Separator();
        ImGui::Dummy(ImVec2(0, 5.0f));
        ImGui::TextColored(ImVec4(0.15f, 0.85f, 0.15f, 1.00f), "Hotkeys Bindings");
        ImGui::Dummy(ImVec2(0, 5.0f));

        draw_keybind_button("Extracts Key", extracts_key);
        draw_keybind_button("Transits Key", transits_key);
        draw_keybind_button("Players Key", players_key);
        draw_keybind_button("NPC Key", npc_key);
        draw_keybind_button("Loot Key", loot_key);
        draw_keybind_button("Containers Key", container_key);
        draw_keybind_button("Food Key", provisions_key);
    }

    void draw_overlay(ImDrawList* draw_list) override
    {
        if (!game_state::is_in_raid) return;

        std::vector<exfil> local_exfils;
        std::vector<transit> local_transits;
        std::vector<player> local_players;
        std::vector<loot_entry> local_loots;

        {
            std::lock_guard<std::mutex> lock(data_mutex);
            local_exfils = exfil_store;
            local_transits = transits_store;
            local_players = player_store;
            local_loots = loot_store;
        }

        Il2CppObject* cam = unity::get_current_camera();
        if (!cam) return;

        char text_buf[256];

        if (extracts->get_value())
        {
            for (const auto& [name, status, pos3d] : local_exfils)
            {
                unity::vector3 screen_pos = eft_api::world_to_screen(cam, pos3d, game_state::current_zoom);
                if (!eft_api::is_visible(screen_pos)) continue;

                const float distance = my_pos.distance(pos3d);
                const char* status_string = "Unknown";
                switch (status)
                {
                case 8: status_string = "Hidden"; break;
                case 7: status_string = "Inactive"; break;
                case 6: status_string = "Pending"; break;
                case 5:
                case 4: status_string = "Open"; break;
                case 2: status_string = "Available"; break;
                case 1: status_string = "Closed"; break;
                }

                snprintf(text_buf, sizeof(text_buf), "[%dm] %s (%s)", static_cast<int>(distance), name.c_str(), status_string);

                imgui_addons::centered_text(draw_list, menu::get_hud_font(),
                    static_cast<float>(font_size->get_value()) * menu::get_scale_factor(),
                    { screen_pos.x, screen_pos.y }, extracts_color->get_value(), text_buf);
            }
        }

        if (transits->get_value())
        {
            for (const auto& [name, pos3d] : local_transits)
            {
                unity::vector3 screen_pos = eft_api::world_to_screen(cam, pos3d, game_state::current_zoom);
                if (!eft_api::is_visible(screen_pos)) continue;

                const float distance = my_pos.distance(pos3d);
                snprintf(text_buf, sizeof(text_buf), "[%dm] %s", static_cast<int>(distance), name.c_str());

                imgui_addons::centered_text(draw_list, menu::get_hud_font(),
                    static_cast<float>(font_size->get_value()) * menu::get_scale_factor(),
                    { screen_pos.x, screen_pos.y }, transits_color->get_value(), text_buf);
            }
        }

        if ((players->get_value()) || (npc->get_value()))
        {
            for (const auto& [name, pmc, pos3d] : local_players)
            {
                unity::vector3 screen_pos = eft_api::world_to_screen(cam, pos3d, game_state::current_zoom);
                if (!eft_api::is_visible(screen_pos)) continue;

                const float distance = my_pos.distance(pos3d);
                snprintf(text_buf, sizeof(text_buf), "[%dm] %s", static_cast<int>(distance), name.c_str());

                if (pmc && players->get_value()) {
                    imgui_addons::centered_text(draw_list, menu::get_hud_font(),
                        static_cast<float>(font_size->get_value()) * menu::get_scale_factor(),
                        { screen_pos.x, screen_pos.y + 10 * menu::get_scale_factor() },
                        player_color->get_value(), text_buf);
                }
                else if (!pmc && npc->get_value()) {
                    imgui_addons::centered_text(draw_list, menu::get_hud_font(),
                        static_cast<float>(font_size->get_value()) * menu::get_scale_factor(),
                        { screen_pos.x, screen_pos.y + 10 * menu::get_scale_factor() },
                        npc_color->get_value(), text_buf);
                }
            }
        }

        const bool show_loot = loot_esp->get_value();
        const bool show_containers = container_esp->get_value();
        const float loot_max_sq = loot_distance->get_value() * loot_distance->get_value();
        const float container_max_sq = container_distance->get_value() * container_distance->get_value();

        if (show_loot || show_containers)
        {
            for (const auto& l : local_loots)
            {
                if (l.is_container && !show_containers) continue;
                if (!l.is_container && !show_loot) continue;

                const float dx = my_pos.x - l.pos3d.x;
                const float dy = my_pos.y - l.pos3d.y;
                const float dz = my_pos.z - l.pos3d.z;
                const float dist_sq = dx * dx + dy * dy + dz * dz;

                const float max_dist_sq = l.is_container ? container_max_sq : loot_max_sq;
                if (dist_sq > max_dist_sq) continue;

                unity::vector3 screen_pos = eft_api::world_to_screen(cam, l.pos3d, game_state::current_zoom);
                if (!eft_api::is_visible(screen_pos)) continue;

                const int distance = static_cast<int>(sqrtf(dist_sq));

                if (!l.is_container && l.price > 0) {
                    snprintf(text_buf, sizeof(text_buf), "[%dm] %s (%d\xE2\x82\xBD)", distance, l.name.c_str(), l.price);
                }
                else {
                    snprintf(text_buf, sizeof(text_buf), "[%dm] %s", distance, l.name.c_str());
                }

                ImVec4 color = l.is_container ? container_color->get_value() : loot_color->get_value();

                imgui_addons::centered_text(
                    draw_list,
                    ImVec2((float)screen_pos.x, (float)screen_pos.y),
                    color,
                    text_buf
                );
            }
        }
    }
    void application_update() override
    {
        handle_hotkey(extracts_key, extracts_key_pressed, extracts);
        handle_hotkey(transits_key, transits_key_pressed, transits);
        handle_hotkey(players_key, players_key_pressed, players);
        handle_hotkey(npc_key, npc_key_pressed, npc);
        handle_hotkey(loot_key, loot_key_pressed, loot_esp);
        handle_hotkey(container_key, container_key_pressed, container_esp);
        handle_hotkey(provisions_key, provisions_key_pressed, show_provisions);
    }

    void gameworld_update(const Il2CppClass* game_world_class, Il2CppObjectInstance game_world_instance,
        Il2CppObjectInstance main_player) override
    {
        if (game_world_instance.get_instance() != last_game_world_ptr) {
            item_info_cache.clear();
            last_game_world_ptr = game_world_instance.get_instance();
        }

        const auto main_player_game_object = unity::component_get_game_object(main_player.get_instance());
        const auto main_player_transform = unity::gameobject_get_transform(main_player_game_object);
        my_pos = unity::transform_get_pos(main_player_transform);

        const auto get_profile = main_player.get_method<Il2CppObject * (*)(Il2CppObject*)>("get_Profile", 0);
        Il2CppObjectInstance main_profile(get_profile(main_player.get_instance()));

        const std::string main_nickname = eft_api::to_english(il2utils::conv_wstring(
            main_profile.get_method<Il2CppString * (*)(Il2CppObject*)>("get_Nickname", 0)(
                main_profile.get_instance())));

        std::vector<exfil> temp_exfils;
        std::vector<transit> temp_transits;
        std::vector<player> temp_players;
        std::vector<loot_entry> temp_loots;

        if (extracts->get_value())
        {
            Il2CppObjectInstance exfiltration_controller(
                game_world_instance.get_field<Il2CppObject*>("<ExfiltrationController>k__BackingField"));

            const auto exfils = exfiltration_controller.get_method<Il2CppArray * (
                *)(Il2CppObject*, Il2CppObject*)>("EligiblePoints", 1)(exfiltration_controller.get_instance(),
                    main_profile.get_instance());

            if (exfils && exfils->max_length > 0) {
                temp_exfils.reserve(exfils->max_length);
                for (size_t i = 0; i < exfils->max_length; ++i)
                {
                    Il2CppObject* exfil = reinterpret_cast<Il2CppObject**>(&exfils->data)[i];
                    if (!exfil) continue;
                    Il2CppObjectInstance exfiltration_point(exfil);

                    const int status = exfiltration_point.get_field<int>("_status");
                    Il2CppObjectInstance exfil_settings(exfiltration_point.get_field<Il2CppObject*>("Settings"));
                    const std::string name = il2utils::conv_string(exfil_settings.get_field<Il2CppString*>("Name"));

                    Il2CppObject* game_object = unity::component_get_game_object(exfiltration_point.get_instance());
                    Il2CppObject* tranform = unity::gameobject_get_transform(game_object);

                    temp_exfils.push_back({ name, status, unity::transform_get_pos(tranform) });
                }
            }
        }

        if (transits->get_value())
        {
            Il2CppObjectInstance transit_controller(
                game_world_instance.get_field<Il2CppObject*>("<TransitController>k__BackingField"));
            const auto transits_by_id = transit_controller.get_field<unity::dict<int, Il2CppObject*>*>("pointsById");

            if (transits_by_id && transits_by_id->m_i_count > 0) {
                temp_transits.reserve(transits_by_id->m_i_count);
                for (int i = 0; i < transits_by_id->m_i_count; ++i)
                {
                    Il2CppObjectInstance transit(transits_by_id->get_value_by_index(i));
                    Il2CppObjectInstance parameters(transit.get_field<Il2CppObject*>("parameters"));
                    const std::string location = il2utils::conv_string(parameters.get_field<Il2CppString*>("location"));

                    Il2CppObject* transit_game_object = unity::component_get_game_object(transit.get_instance());
                    Il2CppObject* transit_transform = unity::gameobject_get_transform(transit_game_object);

                    temp_transits.push_back({ location, unity::transform_get_pos(transit_transform) });
                }
            }
        }

        if ((players->get_value()) || (npc->get_value()))
        {
            Il2CppArray* alive_players = game_world_instance.get_field<unity::list*>("AllAlivePlayersList")->m_p_list_array;

            if (alive_players && alive_players->max_length > 0) {
                temp_players.reserve(alive_players->max_length);
                for (size_t i = 0; i < alive_players->max_length; ++i)
                {
                    Il2CppObject* player_object = reinterpret_cast<Il2CppObject**>(&alive_players->data)[i];
                    if (!player_object) continue;
                    Il2CppObjectInstance player_instance(player_object);
                    Il2CppObjectInstance profile_instance(get_profile(player_instance.get_instance()));

                    std::string nickname = eft_api::to_english(il2utils::conv_wstring(
                        profile_instance.get_method<Il2CppString * (*)(Il2CppObject*)>("get_Nickname", 0)(
                            profile_instance.get_instance())));

                    if (nickname == main_nickname) continue;

                    Il2CppObject* player_game_object = unity::component_get_game_object(player_instance.get_instance());
                    Il2CppObject* player_transform = unity::gameobject_get_transform(player_game_object);

                    Il2CppObjectInstance inventory_controller(player_instance.get_field<Il2CppObject*>("_inventoryController"));
                    Il2CppObjectInstance inventory(
                        inventory_controller.get_method<Il2CppObject * (*)(Il2CppObject*)>("get_Inventory", 0)(
                            inventory_controller.get_instance()));
                    Il2CppObjectInstance equipment(inventory.get_field<Il2CppObject*>("Equipment"));
                    Il2CppObjectInstance equipment_slot(
                        equipment.get_method<Il2CppObject * (*)(Il2CppObject*, int)>("GetSlot", 1)(
                            equipment.get_instance(), 13));

                    Il2CppObject* item_ptr = equipment_slot.get_method<Il2CppObject * (*)(Il2CppObject*)>(
                        "get_ContainedItem", 0)(equipment_slot.get_instance());

                    temp_players.push_back({ nickname, item_ptr != nullptr, unity::transform_get_pos(player_transform) });
                }
            }
        }

        if ((loot_esp->get_value()) || (container_esp->get_value()))
        {
            const float min_dist_sq = 2.0f * 2.0f;

            if (loot_esp->get_value())
            {
                static auto* assembly_csharp = il2utils::resolve_image("Assembly-CSharp.dll");
                static auto* localization_extensions_class = il2utils::resolve_class(assembly_csharp, "EFT", "LocalizationExtensions");
                static auto localized_short_name = il2utils::get_method<Il2CppString * (*)(Il2CppObject*)>(localization_extensions_class, "LocalizedShortName", 1);

                Il2CppObject* loot_hydra_obj = game_world_instance.get_field<Il2CppObject*>("LootItems");
                if (loot_hydra_obj)
                {
                    Il2CppObjectInstance loot_hydra(loot_hydra_obj);
                    unity::list* loot_list = loot_hydra.get_field<unity::list*>("_iteration");

                    if (loot_list && loot_list->m_p_list_array)
                    {
                        Il2CppArray* loot_array = loot_list->m_p_list_array;
                        const int count = static_cast<int>(loot_array->max_length);
                        temp_loots.reserve(count);

                        const float loot_max_dist_sq = loot_distance->get_value() * loot_distance->get_value();

                        for (int i = 0; i < count; ++i)
                        {
                            Il2CppObject* loot_obj = reinterpret_cast<Il2CppObject**>(&loot_array->data)[i];
                            if (!loot_obj) continue;

                            Il2CppObject* game_object = unity::component_get_game_object(loot_obj);
                            if (!game_object) continue;
                            Il2CppObject* transform = unity::gameobject_get_transform(game_object);
                            if (!transform) continue;

                            unity::vector3 pos = unity::transform_get_pos(transform);

                            const float dx = my_pos.x - pos.x;
                            const float dy = my_pos.y - pos.y;
                            const float dz = my_pos.z - pos.z;
                            const float dist_sq = dx * dx + dy * dy + dz * dz;

                            if (dist_sq < min_dist_sq || dist_sq > loot_max_dist_sq) continue;

                            Il2CppObjectInstance loot_instance(loot_obj);
                            Il2CppObject* item_obj = loot_instance.get_field<Il2CppObject*>("_item");
                            if (!item_obj) continue;

                            std::string item_name = "Item";
                            int item_market_price = 0;
                            bool is_provision = false;

                            auto cache_it = item_info_cache.find(item_obj);
                            if (cache_it != item_info_cache.end()) {
                                item_name = cache_it->second.name;
                                item_market_price = cache_it->second.price;
                                is_provision = cache_it->second.is_provision;
                            }
                            else {
                                if (item_obj->klass && item_obj->klass->name) {
                                    const char* cls_name = item_obj->klass->name;
                                    if (strstr(cls_name, "Food") != nullptr || strstr(cls_name, "Drink") != nullptr) {
                                        is_provision = true;
                                    }
                                }

                                Il2CppObjectInstance item_inst(item_obj);
                                Il2CppObject* template_obj = item_inst.get_method<Il2CppObject * (*)(Il2CppObject*)>("get_Template", 0)(item_obj);
                                if (template_obj && prices_loaded.load())
                                {
                                    Il2CppObjectInstance template_inst(template_obj);
                                    Il2CppString* id_il2str = template_inst.get_method<Il2CppString * (*)(Il2CppObject*)>("get_StringId", 0)(template_obj);
                                    if (id_il2str) {
                                        std::string item_id = eft_api::to_english(il2utils::conv_wstring(id_il2str));
                                        auto price_it = market_prices.find(item_id);
                                        if (price_it != market_prices.end()) {
                                            item_market_price = price_it->second;
                                        }
                                    }
                                }

                                if (localized_short_name) {
                                    Il2CppString* name_str = localized_short_name(item_obj);
                                    if (name_str) {
                                        item_name = eft_api::to_english(il2utils::conv_wstring(name_str));
                                        if (item_name.empty()) item_name = "Item";
                                    }
                                }

                                item_info_cache[item_obj] = { item_name, item_market_price, is_provision };
                            }

                            bool wants_provisions = show_provisions->get_value();

                            if (is_provision && !wants_provisions) continue;
                            bool bypass_filter = (is_provision && wants_provisions);
                            if (!bypass_filter && item_market_price < loot_min_value->get_value()) continue;

                            temp_loots.push_back({ item_name, pos, false, item_market_price });
                        }
                    }
                }
            }

            if (container_esp->get_value())
            {
                ULONGLONG now = GetTickCount64();

                if (now - cached_containers_last_refresh >= 15000 || cached_containers.empty())
                {
                    cached_containers_last_refresh = now;
                    cached_containers.clear();

                    unity::list* loot_list = game_world_instance.get_field<unity::list*>("LootList");
                    if (loot_list && loot_list->m_p_list_array)
                    {
                        Il2CppArray* loot_array = loot_list->m_p_list_array;
                        const int count = static_cast<int>(loot_array->max_length);
                        cached_containers.reserve(count);

                        for (int i = 0; i < count; ++i)
                        {
                            Il2CppObject* obj = reinterpret_cast<Il2CppObject**>(&loot_array->data)[i];
                            if (!obj) continue;

                            const char* class_name = (obj->klass && obj->klass->name) ? obj->klass->name : "";
                            if (!class_name || !class_name[0]) continue;

                            const bool looks_like_container =
                                strstr(class_name, "LootableContainer") != nullptr ||
                                strstr(class_name, "Container") != nullptr ||
                                strstr(class_name, "Corpse") != nullptr;

                            if (!looks_like_container) continue;

                            std::string name = unity::get_name(obj);
                            if (name.empty()) name = class_name;

                            cached_containers.push_back({ obj, std::move(name) });
                        }
                    }
                }

                const float container_max_dist_sq = container_distance->get_value() * container_distance->get_value();
                temp_loots.reserve(temp_loots.size() + cached_containers.size());

                for (const auto& entry : cached_containers)
                {
                    Il2CppObject* cont_obj = entry.obj;
                    if (!cont_obj) continue;

                    Il2CppObject* game_object = unity::component_get_game_object(cont_obj);
                    if (!game_object) continue;
                    Il2CppObject* transform = unity::gameobject_get_transform(game_object);
                    if (!transform) continue;

                    unity::vector3 pos = unity::transform_get_pos(transform);

                    const float dx = my_pos.x - pos.x;
                    const float dy = my_pos.y - pos.y;
                    const float dz = my_pos.z - pos.z;
                    const float dist_sq = dx * dx + dy * dy + dz * dz;

                    if (dist_sq < min_dist_sq || dist_sq > container_max_dist_sq) continue;

                    temp_loots.push_back({ entry.name, pos, true, 0 });
                }
            }
            else
            {
                cached_containers.clear();
                cached_containers_last_refresh = 0;
            }
        }

        {
            std::lock_guard<std::mutex> lock(data_mutex);
            exfil_store = std::move(temp_exfils);
            transits_store = std::move(temp_transits);
            player_store = std::move(temp_players);
            loot_store = std::move(temp_loots);
        }
    }
};