#pragma once
#include <windows.h> 
#include <mutex>    
#include <vector>
#include <string>
#include <cmath>
#include <cfloat>
#include <optional>
#include <array>
#include <algorithm>
#include "../Module.h"
#include "../ModuleCategory.h"
#include "../../config/ActionRowValue.h"
#include "../../config/CheckboxValue.h"
#include "../../config/ColorpickerValue.h"
#include "../../config/MenuSectionValue.h"
#include "../eft_api.h"
#include "../game_state.h"
#include "../esp_text.h"
#include "../item_pricing.h"
#include "../quest_esp.h"
#include "../skeleton_visibility.h"
#include "../minimap.h"
#include "MinimapModule.h"
#include "ChamsModule.h"
#include "../../config/IntSliderValue.h"
#include "../../config/IntSliderEditableValue.h"
#include "../../config/FloatSliderValue.h"
#include "../../config/SelectValue.h"
#include "../../il2cpp/exports.h"
#include "../../il2cpp/il2utils.h"
#include "../../il2cpp/Il2CppObjectInstance.h"
#include "../../il2cpp/unity.h"
#include "../../gui/menu/menu.h"

#include <filesystem>
#include <fstream>
#include <cstring>

#include <nlohmann/json.hpp>

#include "../../util/i18n.h"
#include "../../util/logger.h"
#include "../../util/module_paths.h"
#include <algorithm>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <memory>
namespace esp_bones
{
    // Skeleton._values indices — reliable for legs and forearms only (Stupid.Solutions HumanBones enum)
    namespace sk
    {
        inline constexpr int l_collarbone = 94;
        inline constexpr int l_forearm1 = 96;
        inline constexpr int l_forearm2 = 97;
        inline constexpr int l_forearm3 = 98;
        inline constexpr int l_palm = 99;
        inline constexpr int r_collarbone = 115;
        inline constexpr int r_forearm1 = 117;
        inline constexpr int r_forearm2 = 118;
        inline constexpr int r_forearm3 = 119;
        inline constexpr int r_palm = 120;
    }

    // PlayerBones fields — reliable for torso and head (dump-verified)
    namespace pb
    {
        inline constexpr const char* pelvis = "Pelvis";
        inline constexpr const char* spine3 = "Spine3";
        inline constexpr const char* neck = "Neck";
        inline constexpr const char* head = "Head";
    }

    // Skeleton._keys suffixes — used when enum index != list position (left leg on 1.0.6)
    namespace sk_name
    {
        inline constexpr const char* l_thigh1 = "Base HumanLThigh1";
        inline constexpr const char* l_thigh2 = "Base HumanLThigh2";
        inline constexpr const char* l_calf = "Base HumanLCalf";
        inline constexpr const char* l_foot = "Base HumanLFoot";
        inline constexpr const char* l_toe = "Base HumanLToe";
        inline constexpr const char* r_thigh1 = "Base HumanRThigh1";
        inline constexpr const char* r_thigh2 = "Base HumanRThigh2";
        inline constexpr const char* r_calf = "Base HumanRCalf";
        inline constexpr const char* r_foot = "Base HumanRFoot";
        inline constexpr const char* r_toe = "Base HumanRToe";
    }

    enum class bone_src { sk_index, player_bone, sk_suffix };

    struct bone_ref
    {
        bone_src src;
        int index;
        const char* name;
    };

    struct bone_link { bone_ref from; bone_ref to; };

    inline bone_ref sk_ref(const int idx) { return {bone_src::sk_index, idx, nullptr}; }
    inline bone_ref pb_ref(const char* field) { return {bone_src::player_bone, 0, field}; }
    inline bone_ref suffix_ref(const char* suffix) { return {bone_src::sk_suffix, 0, suffix}; }

    inline const std::vector<bone_link>& skeleton_links()
    {
        static const std::vector<bone_link> links = {
            // Left leg: resolve by _keys name — index 20-24 does not match transforms on this build
            {pb_ref(pb::pelvis), suffix_ref(sk_name::l_thigh1)},
            {suffix_ref(sk_name::l_thigh1), suffix_ref(sk_name::l_thigh2)},
            {suffix_ref(sk_name::l_thigh2), suffix_ref(sk_name::l_calf)},
            {suffix_ref(sk_name::l_calf), suffix_ref(sk_name::l_foot)},
            {suffix_ref(sk_name::l_foot), suffix_ref(sk_name::l_toe)},
            {pb_ref(pb::pelvis), suffix_ref(sk_name::r_thigh1)},
            {suffix_ref(sk_name::r_thigh1), suffix_ref(sk_name::r_thigh2)},
            {suffix_ref(sk_name::r_thigh2), suffix_ref(sk_name::r_calf)},
            {suffix_ref(sk_name::r_calf), suffix_ref(sk_name::r_foot)},
            {suffix_ref(sk_name::r_foot), suffix_ref(sk_name::r_toe)},
            {pb_ref(pb::pelvis), pb_ref(pb::spine3)}, {pb_ref(pb::spine3), pb_ref(pb::neck)},
            {pb_ref(pb::neck), pb_ref(pb::head)},
            {pb_ref(pb::spine3), sk_ref(sk::l_collarbone)}, {sk_ref(sk::l_collarbone), sk_ref(sk::l_forearm1)},
            {sk_ref(sk::l_forearm1), sk_ref(sk::l_forearm2)}, {sk_ref(sk::l_forearm2), sk_ref(sk::l_forearm3)},
            {sk_ref(sk::l_forearm3), sk_ref(sk::l_palm)},
            {pb_ref(pb::spine3), sk_ref(sk::r_collarbone)}, {sk_ref(sk::r_collarbone), sk_ref(sk::r_forearm1)},
            {sk_ref(sk::r_forearm1), sk_ref(sk::r_forearm2)}, {sk_ref(sk::r_forearm2), sk_ref(sk::r_forearm3)},
            {sk_ref(sk::r_forearm3), sk_ref(sk::r_palm)},
        };
        return links;
    }

    inline std::string bone_key(const bone_ref& ref)
    {
        switch (ref.src)
        {
        case bone_src::sk_index: return "i:" + std::to_string(ref.index);
        case bone_src::sk_suffix: return std::string("s:") + ref.name;
        case bone_src::player_bone: return std::string("p:") + ref.name;
        }
        return {};
    }

    // EBodyPart: Head=0, Chest=1, Stomach=2, LeftArm=3, RightArm=4, LeftLeg=5, RightLeg=6
    inline int body_part_for_bone(const bone_ref& ref)
    {
        if (ref.src == bone_src::player_bone)
        {
            if (ref.name == pb::head || ref.name == pb::neck) return 0;
            if (ref.name == pb::spine3) return 1;
            if (ref.name == pb::pelvis) return 2;
        }

        if (ref.src == bone_src::sk_index)
        {
            if (ref.index >= sk::l_collarbone && ref.index <= sk::l_palm) return 3;
            if (ref.index >= sk::r_collarbone && ref.index <= sk::r_palm) return 4;
        }

        if (ref.src == bone_src::sk_suffix && ref.name)
        {
            if (strncmp(ref.name, "Base HumanL", 11) == 0) return 5;
            if (strncmp(ref.name, "Base HumanR", 11) == 0) return 6;
        }

        return 1;
    }

    inline int body_part_for_link(const bone_link& link)
    {
        const int to_part = body_part_for_bone(link.to);
        const int from_part = body_part_for_bone(link.from);
        if (to_part != from_part) return to_part;
        return to_part;
    }

    inline Il2CppObject* get_player_body(Il2CppObject* player_obj)
    {
        if (!player_obj || !player_obj->klass) return nullptr;

        const MethodInfo* get_body = il2utils::resolve_method(player_obj->klass, "get_PlayerBody", 0);
        if (get_body && get_body->methodPointer)
        {
            const auto fn = reinterpret_cast<Il2CppObject*(*)(Il2CppObject*)>(get_body->methodPointer);
            Il2CppObject* body = fn(player_obj);
            if (body) return body;
        }

        const FieldInfo* body_field = il2utils::resolve_field(player_obj->klass, "_playerBody");
        if (!body_field) return nullptr;
        return *reinterpret_cast<Il2CppObject**>(reinterpret_cast<UINT64>(player_obj) + body_field->offset);
    }

    inline Il2CppObject* get_body_player_bones(Il2CppObject* player_obj)
    {
        Il2CppObject* body = get_player_body(player_obj);
        if (!body || !body->klass) return nullptr;

        const FieldInfo* bones_field = il2utils::resolve_field(body->klass, "PlayerBones");
        if (!bones_field) return nullptr;
        return *reinterpret_cast<Il2CppObject**>(reinterpret_cast<UINT64>(body) + bones_field->offset);
    }

    inline Il2CppObject* get_skeleton_root(Il2CppObject* player_obj)
    {
        Il2CppObject* body = get_player_body(player_obj);
        if (!body || !body->klass) return nullptr;

        const FieldInfo* skeleton_field = il2utils::resolve_field(body->klass, "SkeletonRootJoint");
        if (!skeleton_field) return nullptr;

        Il2CppObject* skeleton_root = *reinterpret_cast<Il2CppObject**>(
            reinterpret_cast<UINT64>(body) + skeleton_field->offset);
        if (!skeleton_root || !skeleton_root->klass) return nullptr;
        return skeleton_root;
    }

    inline Il2CppArray* get_skeleton_values_array(Il2CppObject* player_obj)
    {
        Il2CppObject* skeleton_root = get_skeleton_root(player_obj);
        if (!skeleton_root) return nullptr;

        const FieldInfo* values_field = il2utils::resolve_field(skeleton_root->klass, "_values");
        if (!values_field) return nullptr;

        auto* values_list = *reinterpret_cast<unity::list**>(
            reinterpret_cast<UINT64>(skeleton_root) + values_field->offset);
        if (!values_list || !values_list->m_p_list_array) return nullptr;
        return values_list->m_p_list_array;
    }

    inline bool get_bone_by_suffix(Il2CppObject* player_obj, const char* suffix, unity::vector3& out)
    {
        if (!suffix || !suffix[0]) return false;

        Il2CppObject* skeleton_root = get_skeleton_root(player_obj);
        if (!skeleton_root) return false;

        const FieldInfo* keys_field = il2utils::resolve_field(skeleton_root->klass, "_keys");
        if (!keys_field) return false;

        auto* keys_list = *reinterpret_cast<unity::list**>(
            reinterpret_cast<UINT64>(skeleton_root) + keys_field->offset);
        Il2CppArray* values = get_skeleton_values_array(player_obj);
        if (!keys_list || !keys_list->m_p_list_array || !values) return false;

        Il2CppArray* keys = keys_list->m_p_list_array;
        const size_t count = keys->max_length < values->max_length ? keys->max_length : values->max_length;

        for (size_t i = 0; i < count; ++i)
        {
            Il2CppString* key_str = reinterpret_cast<Il2CppString**>(&keys->data)[i];
            if (!key_str) continue;

            const std::string key = il2utils::conv_string(key_str);
            if (key.find(suffix) == std::string::npos) continue;

            Il2CppObject* transform = reinterpret_cast<Il2CppObject**>(&values->data)[i];
            if (!transform) return false;

            out = unity::transform_get_pos(transform);
            return true;
        }
        return false;
    }

    inline bool get_bone_by_index(Il2CppObject* player_obj, const int bone_idx, unity::vector3& out)
    {
        Il2CppArray* values = get_skeleton_values_array(player_obj);
        if (!values || bone_idx < 0 || bone_idx >= static_cast<int>(values->max_length)) return false;

        Il2CppObject* transform = reinterpret_cast<Il2CppObject**>(&values->data)[bone_idx];
        if (!transform) return false;

        out = unity::transform_get_pos(transform);
        return true;
    }

    inline bool get_bifacial_pos(Il2CppObject* bifacial, unity::vector3& out)
    {
        if (!bifacial || !bifacial->klass) return false;
        const MethodInfo* method = il2utils::resolve_method(bifacial->klass, "get_position", 0);
        if (!method || !method->methodPointer) return false;
        const auto fn = reinterpret_cast<unity::vector3(*)(Il2CppObject*)>(method->methodPointer);
        out = fn(bifacial);
        return true;
    }

    inline bool get_player_bone_field(Il2CppObject* player_obj, const char* field_name, unity::vector3& out)
    {
        Il2CppObject* pb = get_body_player_bones(player_obj);
        if (!pb || !pb->klass || !field_name) return false;

        const FieldInfo* field = il2utils::resolve_field(pb->klass, field_name);
        if (!field) return false;

        if (strcmp(field_name, pb::neck) == 0)
        {
            Il2CppObject* transform = *reinterpret_cast<Il2CppObject**>(
                reinterpret_cast<UINT64>(pb) + field->offset);
            if (!transform) return false;
            out = unity::transform_get_pos(transform);
            return true;
        }

        Il2CppObject* bifacial = *reinterpret_cast<Il2CppObject**>(
            reinterpret_cast<UINT64>(pb) + field->offset);
        return get_bifacial_pos(bifacial, out);
    }

    inline bool resolve_bone(Il2CppObject* player_obj, const bone_ref& ref, unity::vector3& out)
    {
        switch (ref.src)
        {
        case bone_src::sk_index: return get_bone_by_index(player_obj, ref.index, out);
        case bone_src::player_bone: return get_player_bone_field(player_obj, ref.name, out);
        case bone_src::sk_suffix: return get_bone_by_suffix(player_obj, ref.name, out);
        }
        return false;
    }

    inline void collect_skeleton_bones(Il2CppObject* player_obj, std::unordered_map<std::string, unity::vector3>& bones)
    {
        for (const auto& link : skeleton_links())
        {
            const std::string from_key = bone_key(link.from);
            const std::string to_key = bone_key(link.to);

            if (!bones.contains(from_key))
            {
                unity::vector3 pos{};
                if (resolve_bone(player_obj, link.from, pos))
                    bones[from_key] = pos;
            }
            if (!bones.contains(to_key))
            {
                unity::vector3 pos{};
                if (resolve_bone(player_obj, link.to, pos))
                    bones[to_key] = pos;
            }
        }
    }
}

class EspModule : Module
{
public:
    EspModule() : Module("ESP", Visuals, "esp_mod")
    {
    }

    MenuSectionValue* section_world_ = conf(new MenuSectionValue("world_sec")); 
    CheckboxValue* extracts = conf(new CheckboxValue(false, "extracts_cb"));
    ColorpickerValue* extracts_color = conf(new ColorpickerValue({ 0, 1, 0, 1 }, "extract_color_clr"));
    CheckboxValue* transits = conf(new CheckboxValue(false, "transits_cb"));
    ColorpickerValue* transits_color = conf(new ColorpickerValue({ 1, 1, 0, 1 }, "transit_color_clr"));
    CheckboxValue* grenade_esp = conf(new CheckboxValue(false, "grenade_esp_cb"));
    FloatSliderValue* grenade_esp_distance = conf(new FloatSliderValue(200.f, 25.f, 800.f, "grenade_esp_distance_itm"));
    ColorpickerValue* grenade_color = conf(new ColorpickerValue({ 1.f, 0.35f, 0.f, 1.f }, "grenade_color_clr"));
    CheckboxValue* grenade_show_fuse = conf(new CheckboxValue(true, "grenade_show_fuse_cb"));

    MenuSectionValue* section_players_ = conf(new MenuSectionValue("players_npc_sec"));
    CheckboxValue* players = conf(new CheckboxValue(false, "players_cb"));
    ColorpickerValue* player_color = conf(new ColorpickerValue({ 1, 0, 0, 1 }, "player_color_clr"));
    CheckboxValue* npc = conf(new CheckboxValue(false, "npc_cb"));
    ColorpickerValue* scav_color = conf(new ColorpickerValue({ 1, 1, 0, 1 }, "scav_color_clr"));
    ColorpickerValue* boss_color = conf(new ColorpickerValue({ 1, 0, 1, 1 }, "boss_color_clr"));
    ColorpickerValue* guard_color = conf(new ColorpickerValue({ 1, 0.5f, 0, 1 }, "guard_color_clr"));
    ColorpickerValue* cultist_color = conf(new ColorpickerValue({ 0.8f, 0, 0.2f, 1 }, "cultist_color_clr"));
    ColorpickerValue* raider_color = conf(new ColorpickerValue({ 0, 0.6f, 1, 1 }, "raider_color_clr"));
    ColorpickerValue* defector_color = conf(new ColorpickerValue({ 0.7f, 0.3f, 1, 1 }, "defector_color_clr"));
    ColorpickerValue* black_division_color = conf(new ColorpickerValue({ 0.85f, 0.15f, 0.2f, 1 }, "black_division_color_clr"));
    ColorpickerValue* btr_color = conf(new ColorpickerValue({ 0.9f, 0.25f, 0.1f, 1 }, "btr_color_clr"));
    CheckboxValue* show_bot_names = conf(new CheckboxValue(false, "show_bot_names_cb"));
    FloatSliderValue* player_distance = conf(new FloatSliderValue(200.f, 50.f, 1500.f, "player_distance_itm"));

    MenuSectionValue* section_overlay_ = conf(new MenuSectionValue("overlay_sec"));
    CheckboxValue* player_box = conf(new CheckboxValue(false, "esp_box_cb"));
    FloatSliderValue* box_distance = conf(new FloatSliderValue(200.f, 50.f, 1500.f, "box_distance_itm"));
    ColorpickerValue* box_color = conf(new ColorpickerValue({ 1.f, 1.f, 0.f, 1.f }, "box_color_clr")); 
    FloatSliderValue* box_thickness = conf(new FloatSliderValue(2.f, 1.f, 5.f, "box_thickness_itm"));
    CheckboxValue* box_visibility = conf(new CheckboxValue(false, "box_visibility_cb"));
    ColorpickerValue* box_visible_color = conf(new ColorpickerValue({ 0.f, 1.f, 0.f, 1.f }, "box_visible_color_clr"));
    ColorpickerValue* box_occluded_color = conf(new ColorpickerValue({ 1.f, 0.f, 0.f, 1.f }, "box_occluded_color_clr"));
    CheckboxValue* player_health = conf(new CheckboxValue(false, "health_bar_cb"));
    ColorpickerValue* health_color = conf(new ColorpickerValue({ 0.f, 1.f, 0.f, 1.f }, "health_high_color_clr"));
    ColorpickerValue* health_low_color = conf(new ColorpickerValue({ 1.f, 0.f, 0.f, 1.f }, "health_low_color_clr"));

    MenuSectionValue* section_skeleton_ = conf(new MenuSectionValue("skeleton_sec"));
    CheckboxValue* skeleton = conf(new CheckboxValue(false, "skeleton_cb"));
    CheckboxValue* skeleton_health_colors = conf(new CheckboxValue(false, "health_color_limbs_cb"));
    FloatSliderValue* skeleton_distance = conf(new FloatSliderValue(200.f, 50.f, 1500.f, "skeleton_distance_itm"));
    ColorpickerValue* skeleton_color = conf(new ColorpickerValue({ 0.5f, 0.5f, 0.5f, 1.f }, "skeleton_color_clr"));
    FloatSliderValue* skeleton_thickness = conf(new FloatSliderValue(1.7f, 0.5f, 5.f, "skeleton_thickness_itm"));
    CheckboxValue* skeleton_head_circle = conf(new CheckboxValue(false, "skeleton_head_circle_cb"));
    // --- skeleton visibility ESP (rollback: remove block through skeleton_occluded_color) ---
    CheckboxValue* skeleton_visibility = conf(new CheckboxValue(false, "skeleton_visibility_cb"));
    ColorpickerValue* skeleton_visible_color = conf(new ColorpickerValue({ 0.f, 1.f, 0.f, 1.f }, "skeleton_visible_color_clr"));
    ColorpickerValue* skeleton_occluded_color = conf(new ColorpickerValue({ 1.f, 0.f, 0.f, 1.f }, "skeleton_occluded_color_clr"));
    SelectValue* skeleton_vis_health_mode = conf(new SelectValue(0, {
        "health_on_visible_sel",
        "blend_sel",
        "dim_occluded_sel"
    }, "visibility_health_mode_sel"));
    // --- end skeleton visibility ESP ---

    MenuSectionValue* section_loot_ = conf(new MenuSectionValue("loot_sec"));
    CheckboxValue* loot_esp = conf(new CheckboxValue(false, "loot_cb"));
    FloatSliderValue* loot_min_distance = conf(new FloatSliderValue(0.f, 0.f, 50.f, "loot_min_distance_itm"));
    FloatSliderValue* loot_distance = conf(new FloatSliderValue(50.f, 1.f, 1500.f, "loot_max_distance_itm"));
    IntSliderValue* loot_min_value = conf(new IntSliderValue(10000, 0, 500000, "loot_min_value_itm"));
    CheckboxValue* show_loot_prices = conf(new CheckboxValue(false, "show_loot_prices_cb"));
    CheckboxValue* loot_price_gradient = conf(new CheckboxValue(false, "loot_price_gradient_cb"));
    ColorpickerValue* loot_gradient_cheap = conf(new ColorpickerValue({ 0.45f, 0.45f, 0.45f, 0.85f }, "gradient_cheap_color_clr"));
    ColorpickerValue* loot_gradient_expensive = conf(new ColorpickerValue({ 0.85f, 0.7f, 0.2f, 1.f }, "gradient_expensive_color_clr"));
    ColorpickerValue* loot_gradient_above_max = conf(new ColorpickerValue({ 0.85f, 0.7f, 0.2f, 1.f }, "gradient_above_max_color_clr"));
    IntSliderEditableValue* loot_gradient_max = conf(new IntSliderEditableValue(10000, 0, 1000000, "gradient_max_value_itm"));
    ColorpickerValue* loot_color = conf(new ColorpickerValue({ 0, 1, 1, 1 }, "loot_color_clr"));
    CheckboxValue* show_provisions = conf(new CheckboxValue(false, "show_food_water_cb"));
    CheckboxValue* corpse_show_loot = conf(new CheckboxValue(false, "show_corpse_loot_cb"));

    MenuSectionValue* section_containers_ = conf(new MenuSectionValue("containers_sec"));
    CheckboxValue* container_esp = conf(new CheckboxValue(false, "containers_laggy_cb"));
    CheckboxValue* container_show_loot = conf(new CheckboxValue(false, "show_container_loot_cb"));
    CheckboxValue* container_show_value = conf(new CheckboxValue(false, "show_container_prices_cb"));
    FloatSliderValue* container_distance = conf(new FloatSliderValue(100.f, 1.f, 1500.f, "container_distance_itm"));
    ColorpickerValue* container_color = conf(new ColorpickerValue({ 1, 0.5f, 0, 1 }, "container_color_clr"));

    MenuSectionValue* section_quest_ = conf(new MenuSectionValue("quest_sec"));
    CheckboxValue* quest_esp_enabled = conf(new CheckboxValue(false, "quest_esp_cb"));
    CheckboxValue* quest_esp_descriptions = conf(new CheckboxValue(false, "quest_descriptions_cb"));
    FloatSliderValue* quest_esp_distance = conf(new FloatSliderValue(500.f, 50.f, 2000.f, "quest_esp_dist_itm"));
    ColorpickerValue* quest_visit_color = conf(new ColorpickerValue({ 0, 1, 1, 1 }, "quest_visit_color_clr"));
    ColorpickerValue* quest_find_color = conf(new ColorpickerValue({ 1, 1, 0, 1 }, "quest_find_color_clr"));
    ColorpickerValue* quest_place_color = conf(new ColorpickerValue({ 1, 0, 1, 1 }, "quest_place_color_clr"));




















    struct exfil { std::string name; int status; unity::vector3 pos3d; };
    struct transit { std::string name; unity::vector3 pos3d; };
    struct grenade_entry
    {
        std::string label;
        unity::vector3 pos3d{};
        float distance = 0.f;
        float fuse_remaining = -1.f;
    };
    struct bot_corpse_marker
    {
        unity::vector3 pos3d{};
        float distance = 0.f;
    };

    struct player
    {
        std::wstring label_wide;
        std::string prefix_utf8;
        std::string name_utf8;
        std::string label_utf8;
        bool show_name_line = false;
        eft_api::npc_kind kind = eft_api::npc_kind::scav;
        unity::vector3 pos3d{};
        unity::vector3 head_pos{};
        float distance = 0.f;
        float health_current = 0.f;
        float health_max = 0.f;
        bool is_alive = true;
        bool any_part_visible = true;
    };

    struct skeleton_entry
    {
        std::unordered_map<std::string, unity::vector3> bones;
        eft_api::npc_kind kind = eft_api::npc_kind::scav;
        float distance = 0.f;
        std::array<eft_api::health_value, 7> limb_health{};
        std::array<bool, 7> limb_destroyed{};
        bool has_limb_health = false;
        // --- skeleton visibility ESP (rollback: remove link_visible) ---
        std::vector<bool> link_visible;
        // --- end skeleton visibility ESP ---
    };

    struct loot_entry
    {
        std::string name;
        unity::vector3 pos3d;
        bool is_container;
        int price;
        bool is_quest = false;
        float distance = 0.f;
        bool is_corpse_loot = false;
    };

    struct container_item_entry
    {
        std::string name;
        int count = 1;
        int price = 0;
        bool is_quest = false;
        bool is_provision = false;
    };

    struct container_esp_entry
    {
        std::string container_name;
        unity::vector3 pos3d;
        float distance = 0.f;
        std::shared_ptr<const std::vector<container_item_entry>> items;
        int total_price = 0;
    };

    unity::vector3 my_pos = {};

     std::vector<exfil> exfil_store;
    std::vector<transit> transits_store;
    std::vector<grenade_entry> grenade_store;
    std::vector<player> player_store;
    std::vector<loot_entry> loot_store;
    std::shared_ptr<const std::vector<container_esp_entry>> container_store_;
    std::shared_ptr<const std::vector<container_esp_entry>> corpse_store_;
    std::vector<skeleton_entry> skeleton_store;
    std::vector<quest_esp::poi_entry> quest_poi_store;
    std::vector<bot_corpse_marker> bot_corpse_store_;
    ULONGLONG bot_corpse_last_scan_ms_ = 0;

    struct container_registry_entry
    {
        uintptr_t obj_key = 0;
        std::string name;
        unity::vector3 pos3d{};
        float distance = 0.f;
        float dist_sq = 0.f;
        bool has_pos = false;
        bool in_range = false;
    };

    struct corpse_registry_entry
    {
        uintptr_t obj_key = 0;
        Il2CppObject* inventory_root = nullptr;
        std::string name;
        unity::vector3 pos3d{};
        float distance = 0.f;
        float dist_sq = 0.f;
        bool has_pos = false;
        bool in_range = false;
    };

    struct container_item_cache
    {
        uintptr_t obj_key = 0;
        std::string name;
        std::vector<container_item_entry> items;
        std::shared_ptr<const std::vector<container_item_entry>> items_ptr;
        int cached_item_count = 0;
        int total_price = 0;
        ULONGLONG last_refresh = 0;
    };
    inline static std::vector<container_registry_entry> container_registry_{};
    inline static std::unordered_map<uintptr_t, container_item_cache> container_item_caches_{};
    inline static std::vector<loot_entry> container_marker_snapshot_{};
    inline static size_t container_position_cursor_ = 0;
    inline static size_t container_loot_refresh_cursor_ = 0;
    inline static std::vector<corpse_registry_entry> corpse_registry_{};
    inline static std::unordered_map<uintptr_t, container_item_cache> corpse_item_caches_{};
    inline static size_t corpse_loot_refresh_cursor_ = 0;
    inline static ULONGLONG corpse_last_tick_ms_ = 0;
    inline static ULONGLONG corpse_last_scan_ms_ = 0;
    inline static ULONGLONG container_registry_scan_ms_ = 0;
    inline static ULONGLONG container_last_tick_ms_ = 0;

private:
    struct ItemCacheData {
        std::string name;
        int price;
        bool is_provision;
        bool is_corpse_loot = false;
    };
    std::unordered_map<Il2CppObject*, ItemCacheData> item_info_cache;
    std::unordered_map<std::string, std::string> template_name_cache_;
    const Il2CppObject* last_game_world_ptr = nullptr;
    std::string last_raid_location_id_;
    std::string current_location_id;
    std::string current_location_display_name;
    bool minimap_configs_scanned = false;
    mutable float minimap_center_u_ = 0.5f;
    mutable float minimap_center_v_ = 0.5f;
    mutable bool minimap_follow_player_ = true;
    mutable bool minimap_has_last_player_pos_ = false;
    mutable unity::vector3 minimap_last_player_pos_{};
    mutable bool minimap_dragging_ = false;
    mutable ImVec2 minimap_drag_last_{};
    mutable float compass_anim_yaw_ = 0.f;
    mutable bool compass_anim_initialized_ = false;
    mutable ULONGLONG compass_anim_last_ms_ = 0;
    uint64_t last_prices_version_ = 0;
    ULONGLONG quest_esp_last_refresh_ms_ = 0;

    std::mutex data_mutex;

    static ImVec4 lerp_color(const ImVec4& a, const ImVec4& b, const float t)
    {
        const float clamped = t < 0.f ? 0.f : (t > 1.f ? 1.f : t);
        return {
            a.x + (b.x - a.x) * clamped,
            a.y + (b.y - a.y) * clamped,
            a.z + (b.z - a.z) * clamped,
            a.w + (b.w - a.w) * clamped
        };
    }

    ImVec4 color_for_loot_price(const int price, const bool is_quest, const bool is_provision) const
    {
        if (!loot_price_gradient->get_value())
            return loot_color->get_value();

        const ImVec4 cheap = loot_gradient_cheap->get_value();
        const ImVec4 expensive = loot_gradient_expensive->get_value();
        const ImVec4 above_max = loot_gradient_above_max->get_value();

        if (price <= 0)
            return cheap;

        const int max_val = loot_gradient_max->get_value();
        if (max_val <= 0 || price >= max_val)
            return above_max;

        const float t = static_cast<float>(price) / static_cast<float>(max_val);
        return lerp_color(cheap, expensive, t);
    }

    ImVec4 get_npc_color(const eft_api::npc_kind kind) const
    {
        switch (kind)
        {
        case eft_api::npc_kind::boss:
            return boss_color->get_value();
        case eft_api::npc_kind::guard:
            return guard_color->get_value();
        case eft_api::npc_kind::cultist:
            return cultist_color->get_value();
        case eft_api::npc_kind::raider:
            return raider_color->get_value();
        case eft_api::npc_kind::defector:
            return defector_color->get_value();
        case eft_api::npc_kind::black_division:
            return black_division_color->get_value();
        case eft_api::npc_kind::btr:
            return btr_color->get_value();
        case eft_api::npc_kind::scav:
        default:
            return scav_color->get_value();
        }
    }

    static void draw_corner_box(ImDrawList* draw_list, const ImVec2& pos, const ImVec2& size,
                                const ImU32 color, const float thickness, const float corner_len)
    {
        const float x = pos.x;
        const float y = pos.y;
        const float w = size.x;
        const float h = size.y;

        draw_list->AddLine({x, y}, {x + corner_len, y}, color, thickness);
        draw_list->AddLine({x, y}, {x, y + corner_len}, color, thickness);
        draw_list->AddLine({x + w - corner_len, y}, {x + w, y}, color, thickness);
        draw_list->AddLine({x + w, y}, {x + w, y + corner_len}, color, thickness);
        draw_list->AddLine({x, y + h - corner_len}, {x, y + h}, color, thickness);
        draw_list->AddLine({x, y + h}, {x + corner_len, y + h}, color, thickness);
        draw_list->AddLine({x + w - corner_len, y + h}, {x + w, y + h}, color, thickness);
        draw_list->AddLine({x + w, y + h - corner_len}, {x + w, y + h}, color, thickness);
    }

    void draw_player_health_bar(ImDrawList* draw_list, const ImVec2& box_pos, const ImVec2& box_size,
                                const player& entry) const
    {
        if (entry.health_max <= 0.01f)
            return;

        const float sf = menu::get_scale_factor();
        const float bar_w = 4.f * sf;
        const float bar_x = box_pos.x - 6.f * sf;
        const float bar_top = box_pos.y;
        const float bar_bottom = box_pos.y + box_size.y;

        draw_list->AddRectFilled({bar_x, bar_top}, {bar_x + bar_w, bar_bottom}, IM_COL32(0, 0, 0, 180));

        float ratio = 1.f;
        if (!entry.is_alive)
            ratio = 0.f;
        else
            ratio = (std::max)(0.f, (std::min)(1.f, entry.health_current / entry.health_max));

        const float filled_h = box_size.y * ratio;
        const ImVec4 col = ratio < 0.5f ? health_low_color->get_value() : health_color->get_value();
        draw_list->AddRectFilled({bar_x, bar_bottom - filled_h}, {bar_x + bar_w, bar_bottom},
                                   ImGui::GetColorU32(col));
    }

    void draw_player_label_text(ImDrawList* draw_list, const ImVec2& anchor, const ImVec4& color,
                                const player& entry, const float distance) const
    {
        const float font = esp_text::esp_font_size();
        ImFont* hud_font = menu::get_hud_font();

        if (show_bot_names->get_value() && entry.show_name_line)
        {
            auto text_size = [&](const std::string& text) -> ImVec2
            {
                if (hud_font)
                    return hud_font->CalcTextSizeA(font, FLT_MAX, 0.f, text.c_str());
                return ImGui::CalcTextSize(text.c_str());
            };

            const ImVec2 prefix_size = text_size(entry.prefix_utf8);
            const float line_gap = 2.f * menu::get_scale_factor();
            const float name_h = entry.name_utf8.empty() ? 0.f : text_size(entry.name_utf8).y;
            const float total_h = prefix_size.y + (name_h > 0.f ? line_gap + name_h : 0.f);
            const float top_y = anchor.y - total_h * 0.5f;

            esp_text::draw_simple(draw_list, hud_font, font,
                {anchor.x, top_y + prefix_size.y * 0.5f}, color, entry.prefix_utf8, distance);

            if (!entry.name_utf8.empty())
            {
                esp_text::draw_simple(draw_list, hud_font, font,
                    {anchor.x, top_y + prefix_size.y + line_gap + name_h * 0.5f},
                    color, entry.name_utf8, distance);
            }
            return;
        }

        esp_text::draw_simple(draw_list, hud_font, font, anchor, color, entry.label_utf8, distance);
    }

    bool is_player_any_part_visible(const unity::vector3& vis_origin,
                                    Il2CppObject* player_obj) const
    {
        if (!skeleton_vis::is_ready() || !player_obj) return true;

        std::unordered_map<std::string, unity::vector3> bones;
        esp_bones::collect_skeleton_bones(player_obj, bones);
        if (bones.empty())
        {
            unity::vector3 head{};
            unity::vector3 feet{};
            if (!eft_api::try_get_head_position(player_obj, head))
                return true;

            Il2CppObject* player_tr = skeleton_vis::get_player_transform(player_obj);
            if (player_tr) feet = unity::transform_get_pos(player_tr);
            else feet = head;

            if (skeleton_vis::is_target_visible(vis_origin, head, player_obj)) return true;
            return skeleton_vis::is_target_visible(vis_origin, feet, player_obj);
        }

        for (const auto& bone : bones)
        {
            if (skeleton_vis::is_target_visible(vis_origin, bone.second, player_obj))
                return true;
        }
        return false;
    }

    bool compute_player_box_layout(Il2CppObject* cam, const player& entry, ImVec2& box_pos,
                                   ImVec2& box_size, ImVec2& label_anchor, ImVec4& color) const
    {
        unity::vector3 head_screen = eft_api::world_to_screen(cam, entry.head_pos, game_state::current_zoom);
        unity::vector3 feet_screen = eft_api::world_to_screen(cam, entry.pos3d, game_state::current_zoom);
        if (!eft_api::is_visible(head_screen) && !eft_api::is_visible(feet_screen))
            return false;

        const float sf = menu::get_scale_factor();
        const float top = head_screen.y - 10.f * sf;
        const float height = fabsf(head_screen.y - feet_screen.y) + 10.f * sf;
        const float width = height * 0.65f;
        box_pos = {feet_screen.x - width * 0.5f, top};
        box_size = {width, height};
        label_anchor = {feet_screen.x, top - 12.f * sf};
        color = entry.kind == eft_api::npc_kind::pmc
            ? player_color->get_value()
            : get_npc_color(entry.kind);
        return true;
    }

    void draw_player_box_geometry(ImDrawList* draw_list, const ImVec2& box_pos, const ImVec2& box_size,
                                  const ImVec4& color, const player& entry) const
    {
        const float sf = menu::get_scale_factor();
        ImVec4 box_tint = color;
        if (player_box->get_value())
        {
            box_tint = box_visibility->get_value()
                ? (entry.any_part_visible
                    ? box_visible_color->get_value()
                    : box_occluded_color->get_value())
                : box_color->get_value();
        }
        const ImU32 box_col = player_box->get_value()
            ? ImGui::GetColorU32(box_tint)
            : ImGui::GetColorU32(color);

        if (player_box->get_value())
        {
            draw_corner_box(draw_list, box_pos, box_size, box_col,
                box_thickness->get_value() * sf, 5.f * sf);
        }

        if (player_health->get_value())
            draw_player_health_bar(draw_list, box_pos, box_size, entry);
    }

    void draw_player_box_overlay(ImDrawList* draw_list, Il2CppObject* cam, const player& entry) const
    {
        ImVec2 box_pos{};
        ImVec2 box_size{};
        ImVec2 label_anchor{};
        ImVec4 color{};
        if (!compute_player_box_layout(cam, entry, box_pos, box_size, label_anchor, color))
            return;

        draw_player_box_geometry(draw_list, box_pos, box_size, color, entry);
        draw_player_label_text(draw_list, label_anchor, color, entry, entry.distance);
    }

    ImVec4 limb_health_color(const eft_api::health_value& part, const bool destroyed) const
    {
        if (destroyed || part.maximum <= 0.01f || part.current <= 0.01f)
            return {0.22f, 0.22f, 0.24f, 1.f};

        const float ratio = part.current / part.maximum;
        const float t = 1.f - (std::max)(0.f, (std::min)(1.f, ratio));
        const ImVec4 healthy = {0.68f, 0.68f, 0.68f, 1.f};
        const ImVec4 critical = {0.88f, 0.14f, 0.10f, 1.f};
        return {
            healthy.x + (critical.x - healthy.x) * t,
            healthy.y + (critical.y - healthy.y) * t,
            healthy.z + (critical.z - healthy.z) * t,
            1.f
        };
    }

    ImU32 skeleton_link_color(const skeleton_entry& entry, const int body_part,
                              const std::optional<size_t> link_index = std::nullopt) const
    {
        const bool health_on = skeleton_health_colors->get_value() && entry.has_limb_health
            && body_part >= 0 && body_part < static_cast<int>(entry.limb_health.size());
        const ImVec4 health_col = health_on
            ? limb_health_color(
                entry.limb_health[static_cast<size_t>(body_part)],
                entry.limb_destroyed[static_cast<size_t>(body_part)])
            : skeleton_color->get_value();

        const bool vis_on = skeleton_visibility->get_value() && link_index
            && *link_index < entry.link_visible.size();
        const bool link_visible = vis_on && entry.link_visible[*link_index];

        if (vis_on && health_on)
        {
            switch (skeleton_vis_health_mode->get_value())
            {
            case 0: // Health on Visible — visible limb uses HP color, occluded uses red
                return ImGui::GetColorU32(link_visible
                    ? health_col
                    : skeleton_occluded_color->get_value());
            case 1: // Blend — mix HP color with visibility tint
                return ImGui::GetColorU32(link_visible
                    ? lerp_color(health_col, skeleton_visible_color->get_value(), 0.35f)
                    : lerp_color(health_col, skeleton_occluded_color->get_value(), 0.55f));
            case 2: // Dim Occluded — visible full HP, hidden limb is darkened HP
            default:
                if (link_visible) return ImGui::GetColorU32(health_col);
                return ImGui::GetColorU32({
                    health_col.x * 0.4f,
                    health_col.y * 0.4f,
                    health_col.z * 0.4f,
                    health_col.w * 0.65f
                });
            }
        }

        if (vis_on)
        {
            return ImGui::GetColorU32(link_visible
                ? skeleton_visible_color->get_value()
                : skeleton_occluded_color->get_value());
        }

        if (health_on)
            return ImGui::GetColorU32(health_col);

        return ImGui::GetColorU32(skeleton_color->get_value());
    }

    skeleton_entry collect_skeleton(Il2CppObject* player_obj, const eft_api::npc_kind kind,
                                    const float distance, const bool check_visibility,
                                    const unity::vector3& vis_origin) const
    {
        skeleton_entry entry{};
        entry.kind = kind;
        entry.distance = distance;
        esp_bones::collect_skeleton_bones(player_obj, entry.bones);
        entry.has_limb_health = eft_api::try_get_limb_health(
            player_obj, entry.limb_health.data(), entry.limb_destroyed.data(),
            static_cast<int>(entry.limb_health.size()));

        // --- skeleton visibility ESP (rollback: remove this block) ---
        if (check_visibility && !entry.bones.empty())
        {
            const auto& links = esp_bones::skeleton_links();
            entry.link_visible.assign(links.size(), false);
            std::unordered_map<std::string, bool> bone_visible_cache;

            for (size_t i = 0; i < links.size(); ++i)
            {
                const std::string to_key = esp_bones::bone_key(links[i].to);
                const auto bone_it = entry.bones.find(to_key);
                if (bone_it == entry.bones.end()) continue;

                bool visible = false;
                if (const auto cached = bone_visible_cache.find(to_key); cached != bone_visible_cache.end())
                {
                    visible = cached->second;
                }
                else
                {
                    visible = skeleton_vis::is_target_visible(vis_origin, bone_it->second, player_obj);
                    bone_visible_cache[to_key] = visible;
                }

                entry.link_visible[i] = visible;
            }
        }
        // --- end skeleton visibility ESP ---

        return entry;
    }

    void draw_skeleton_entry(ImDrawList* draw_list, Il2CppObject* cam, const skeleton_entry& entry) const
    {
        const auto& links = esp_bones::skeleton_links();
        const float thickness = skeleton_thickness->get_value() * menu::get_scale_factor();

        auto to_screen = [&](const esp_bones::bone_ref& ref) -> std::optional<ImVec2>
        {
            const auto it = entry.bones.find(esp_bones::bone_key(ref));
            if (it == entry.bones.end()) return std::nullopt;
            const unity::vector3 screen_pos = eft_api::world_to_screen(cam, it->second, game_state::current_zoom);
            if (!eft_api::is_visible(screen_pos)) return std::nullopt;
            return ImVec2(screen_pos.x, screen_pos.y);
        };

        for (size_t link_index = 0; link_index < links.size(); ++link_index)
        {
            const auto& link = links[link_index];
            const auto start = to_screen(link.from);
            const auto end = to_screen(link.to);
            if (!start || !end) continue;

            const int body_part = esp_bones::body_part_for_link(link);
            // draw_list->AddLine(*start, *end, skeleton_link_color(entry, body_part), thickness);
            draw_list->AddLine(*start, *end,
                skeleton_link_color(entry, body_part, link_index), thickness);
        }

        if (!skeleton_head_circle->get_value()) return;

        const auto head_screen = to_screen(esp_bones::pb_ref(esp_bones::pb::head));
        const auto neck_screen = to_screen(esp_bones::pb_ref(esp_bones::pb::neck));
        if (!head_screen || !neck_screen) return;

        const float head_radius = sqrtf(
            (head_screen->x - neck_screen->x) * (head_screen->x - neck_screen->x) +
            (head_screen->y - neck_screen->y) * (head_screen->y - neck_screen->y)) * 1.15f;
        if (head_radius < 1.f) return;

        const int segments = entry.distance < 20.f ? 48 : 24;
        const std::optional<size_t> head_link_index = [&]() -> std::optional<size_t>
        {
            for (size_t i = 0; i < links.size(); ++i)
            {
                if (links[i].to.src == esp_bones::bone_src::player_bone
                    && links[i].to.name == esp_bones::pb::head)
                    return i;
            }
            return std::nullopt;
        }();
        // draw_list->AddCircle(*head_screen, head_radius,
        //     skeleton_link_color(entry, esp_bones::body_part_for_bone(esp_bones::pb_ref(esp_bones::pb::head))),
        //     segments, thickness);
        draw_list->AddCircle(*head_screen, head_radius,
            skeleton_link_color(entry,
                esp_bones::body_part_for_bone(esp_bones::pb_ref(esp_bones::pb::head)),
                head_link_index),
            segments, thickness);
    }

    static float get_transform_yaw_y(Il2CppObject* transform)
    {
        if (!transform || !transform->klass) return 0.f;

        const MethodInfo* method = il2utils::resolve_method(transform->klass, "get_eulerAngles", 0);
        if (!method || !method->methodPointer) return 0.f;

        const auto get_euler = reinterpret_cast<unity::vector3(*)(Il2CppObject*)>(method->methodPointer);
        return get_euler(transform).y;
    }

    static float get_view_yaw(Il2CppObject* cam)
    {
        if (!cam) return 0.f;

        Il2CppObject* cam_go = unity::component_get_game_object(cam);
        if (!cam_go) return 0.f;

        Il2CppObject* cam_tr = unity::gameobject_get_transform(cam_go);
        return get_transform_yaw_y(cam_tr);
    }

    static float normalize_degrees(float deg)
    {
        while (deg < 0.f) deg += 360.f;
        while (deg >= 360.f) deg -= 360.f;
        return deg;
    }

    static float shortest_angle_delta(float from_deg, float to_deg)
    {
        float delta = to_deg - from_deg;
        while (delta > 180.f) delta -= 360.f;
        while (delta < -180.f) delta += 360.f;
        return delta;
    }

    void update_compass_anim(const float target_yaw) const
    {
        const ULONGLONG now = GetTickCount64();
        if (!compass_anim_initialized_)
        {
            compass_anim_yaw_ = target_yaw;
            compass_anim_initialized_ = true;
            compass_anim_last_ms_ = now;
            return;
        }

        float dt = static_cast<float>(now - compass_anim_last_ms_) * 0.001f;
        compass_anim_last_ms_ = now;
        dt = (std::min)(dt, 0.1f);

        const float delta = shortest_angle_delta(compass_anim_yaw_, target_yaw);
        const float smooth = 1.f - expf(-dt * 14.f);
        compass_anim_yaw_ = normalize_degrees(compass_anim_yaw_ + delta * smooth);
    }

    bool world_to_radar_point(const unity::vector3& pos, const float view_yaw, const float cx, const float cy,
                              const float range, const float scale, ImVec2& out) const
    {
        const float dx = pos.x - my_pos.x;
        const float dz = pos.z - my_pos.z;
        const float horiz_dist = sqrtf(dx * dx + dz * dz);
        if (horiz_dist < 0.01f) return false;

        constexpr float deg2rad = 3.14159265f / 180.f;
        const float rel = atan2f(dx, dz) - view_yaw * deg2rad;
        const float sin_r = sinf(rel);
        const float cos_r = cosf(rel);
        const float axis_denom = fmaxf(fabsf(sin_r), fabsf(cos_r));
        if (axis_denom < 0.0001f) return false;

        const float max_horiz_dist = range / axis_denom;
        if (horiz_dist > max_horiz_dist) return false;

        const float off_x = horiz_dist * sin_r * scale;
        const float off_y = -horiz_dist * cos_r * scale;
        out = {cx + off_x, cy + off_y};
        return true;
    }

    static bool player_moved_on_map(const unity::vector3& current, const unity::vector3& previous)
    {
        constexpr float eps = 0.015f;
        const float dx = current.x - previous.x;
        const float dz = current.z - previous.z;
        return dx * dx + dz * dz > eps * eps;
    }

    static ImU32 col_alpha(const ImU32 col, const float opacity)
    {
        const int a = static_cast<int>(((col >> IM_COL32_A_SHIFT) & 0xFF) * opacity);
        return (col & ~IM_COL32_A_MASK) | (static_cast<ImU32>(a) << IM_COL32_A_SHIFT);
    }

    static ImU32 col4_alpha(const ImVec4& color, const float opacity)
    {
        return ImGui::GetColorU32(ImVec4(color.x, color.y, color.z, color.w * opacity));
    }

    static bool normalize_dir(float& dir_x, float& dir_y)
    {
        const float len_sq = dir_x * dir_x + dir_y * dir_y;
        if (len_sq < 0.0001f)
        {
            dir_x = 0.f;
            dir_y = -1.f;
            return false;
        }

        const float inv_len = 1.f / sqrtf(len_sq);
        dir_x *= inv_len;
        dir_y *= inv_len;
        return true;
    }

    static float minimap_glyph_half_extent(ImFont* hud_font, const float font_size, const char* symbol)
    {
        const ImVec2 size = hud_font
            ? hud_font->CalcTextSizeA(font_size, FLT_MAX, 0.f, symbol)
            : ImGui::CalcTextSize(symbol);
        return (std::max)(size.x, size.y) * 0.5f;
    }

    static float minimap_label_anchor_y(const ImVec2& point, const float icon_font,
                                        const float height_dy, const float sf, ImFont* hud_font,
                                        const char* marker_symbol = nullptr)
    {
        const esp_text::minimap_marker_settings& mm = esp_text::minimap_markers();
        const float gap = 0.5f * sf;
        const char* sym = (marker_symbol && marker_symbol[0]) ? marker_symbol : mm.exfil.c_str();
        const float sym_half = minimap_glyph_half_extent(hud_font, icon_font, sym);
        float stack_bottom = point.y + sym_half;

        if (height_dy <= -1.f)
        {
            const float height_half = minimap_glyph_half_extent(
                hud_font, mm.size_height_down, mm.height_down.c_str());
            stack_bottom = point.y + sym_half + gap + height_half * 2.f;
        }

        return stack_bottom + gap;
    }

    void draw_minimap_player_marker(ImDrawList* draw_list, const ImVec2& origin,
                                    const float dir_x, const float dir_y, const float /*sf*/,
                                    const float opacity, const bool rotate_map) const
    {
        const esp_text::minimap_marker_settings& mm = esp_text::minimap_markers();
        float rotation_rad = 0.f;
        if (!rotate_map)
        {
            const float dir_len_sq = dir_x * dir_x + dir_y * dir_y;
            if (dir_len_sq > 0.0001f)
                rotation_rad = atan2f(dir_x, -dir_y);
        }

        draw_minimap_symbol_marker(draw_list, origin, mm.player.c_str(),
            mm.size_player, opacity, ImVec4(1.f, 1.f, 1.f, 1.f), rotation_rad);
    }

    static void rotate_draw_list_verts(ImDrawList* draw_list, const int vtx_start, const ImVec2& center,
                                       const float rad)
    {
        if (rad == 0.f) return;

        const float s = sinf(rad);
        const float c = cosf(rad);
        for (int i = vtx_start; i < draw_list->VtxBuffer.Size; ++i)
        {
            ImDrawVert& v = draw_list->VtxBuffer[i];
            const ImVec2 rel = {v.pos.x - center.x, v.pos.y - center.y};
            v.pos.x = center.x + rel.x * c - rel.y * s;
            v.pos.y = center.y + rel.x * s + rel.y * c;
        }
    }

    void draw_minimap_symbol_marker(ImDrawList* draw_list, const ImVec2& center,
                                    const char* symbol, const float font_size,
                                    const float opacity, const ImVec4& color,
                                    const float rotation_rad = 0.f) const
    {
        if (!symbol || !symbol[0]) return;

        ImFont* hud_font = menu::get_hud_font();
        const ImVec2 text_size = hud_font
            ? hud_font->CalcTextSizeA(font_size, FLT_MAX, 0.f, symbol)
            : ImGui::CalcTextSize(symbol);

        const ImVec2 top_left = {
            center.x - text_size.x * 0.5f,
            center.y - text_size.y * 0.5f
        };

        const ImVec4 tinted = ImVec4(color.x, color.y, color.z, color.w * opacity);
        const int vtx_start = draw_list->VtxBuffer.Size;

        if (esp_text::minimap_marker_outline_enabled())
        {
            esp_text::draw_outlined(draw_list, hud_font, font_size, top_left, tinted, symbol);
            rotate_draw_list_verts(draw_list, vtx_start, center, rotation_rad);
            return;
        }

        const ImU32 shadow_col = col_alpha(IM_COL32(0, 0, 0, 210), opacity);
        const ImU32 fill_col = col4_alpha(color, opacity);
        const ImVec2 shadow_offset = {0.8f, 0.8f};

        if (hud_font)
        {
            draw_list->AddText(hud_font, font_size,
                {top_left.x + shadow_offset.x, top_left.y + shadow_offset.y},
                shadow_col, symbol);
            draw_list->AddText(hud_font, font_size, top_left, fill_col, symbol);
        }
        else
        {
            draw_list->AddText(
                {top_left.x + shadow_offset.x, top_left.y + shadow_offset.y},
                shadow_col, symbol);
            draw_list->AddText(top_left, fill_col, symbol);
        }

        rotate_draw_list_verts(draw_list, vtx_start, center, rotation_rad);
    }

    static float pick_grid_spacing(const float range)
    {
        if (range >= 450.f) return 100.f;
        if (range >= 280.f) return 50.f;
        if (range >= 140.f) return 25.f;
        return 10.f;
    }

    void draw_minimap_grid(ImDrawList* draw_list, const bool use_map,
                           const minimap::map_bounds& display_bounds,
                           const minimap::map_view_transform& view,
                           const float map_yaw_deg, const float cx, const float cy,
                           const float range, const float radar_scale,
                           const float rx, const float ry, const float size,
                           const float sf, const float opacity, const bool rotate_map) const
    {
        const float spacing = pick_grid_spacing(range);
        const float extent_scale = rotate_map ? 1.48f : 1.08f;
        const float extent = range * extent_scale;

        float grid_center_x = my_pos.x;
        float grid_center_z = my_pos.z;
        if (use_map && !minimap_follow_player_)
        {
            const float world_w = display_bounds.max_x - display_bounds.min_x;
            const float world_h = display_bounds.max_z - display_bounds.min_z;
            grid_center_x = display_bounds.min_x + minimap_center_u_ * world_w;
            grid_center_z = display_bounds.min_z + (1.f - minimap_center_v_) * world_h;
        }

        const auto draw_world_line = [&](const float x0, const float z0, const float x1, const float z1,
                                         const ImU32 col, const float thickness)
        {
            ImVec2 p0{};
            ImVec2 p1{};
            if (use_map)
            {
                if (!minimap::world_to_map_point(display_bounds, x0, z0, view, p0)
                    || !minimap::world_to_map_point(display_bounds, x1, z1, view, p1))
                    return;
            }
            else
            {
                const unity::vector3 w0{x0, my_pos.y, z0};
                const unity::vector3 w1{x1, my_pos.y, z1};
                if (!world_to_radar_point(w0, map_yaw_deg, cx, cy, range, radar_scale, p0)
                    || !world_to_radar_point(w1, map_yaw_deg, cx, cy, range, radar_scale, p1))
                    return;
            }

            draw_list->AddLine(p0, p1, col, thickness);
        };

        const auto is_major_line = [&](const float value)
        {
            const float major = spacing * 2.f;
            const float mod = fmodf(fabsf(value), major);
            return mod < 0.05f || major - mod < 0.05f;
        };

        const float start_x = floorf((grid_center_x - extent) / spacing) * spacing;
        for (float wx = start_x; wx <= grid_center_x + extent + 0.01f; wx += spacing)
        {
            const bool major = is_major_line(wx);
            const ImU32 col = col_alpha(major ? IM_COL32(210, 220, 230, 95) : IM_COL32(180, 190, 200, 45), opacity);
            const float thickness = (major ? 1.15f : 0.75f) * sf;
            draw_world_line(wx, grid_center_z - extent, wx, grid_center_z + extent, col, thickness);
        }

        const float start_z = floorf((grid_center_z - extent) / spacing) * spacing;
        for (float wz = start_z; wz <= grid_center_z + extent + 0.01f; wz += spacing)
        {
            const bool major = is_major_line(wz);
            const ImU32 col = col_alpha(major ? IM_COL32(210, 220, 230, 95) : IM_COL32(180, 190, 200, 45), opacity);
            const float thickness = (major ? 1.15f : 0.75f) * sf;
            draw_world_line(grid_center_x - extent, wz, grid_center_x + extent, wz, col, thickness);
        }
    }

    static ImVec2 square_compass_ring_point(const float cx, const float cy, const float outer_half,
                                            const float deg)
    {
        float d = normalize_degrees(deg);
        const float side = outer_half * 2.f;

        if (d < 45.f)
        {
            const float t = (d + 45.f) / 90.f;
            return {cx - outer_half + t * side, cy - outer_half};
        }
        if (d < 135.f)
        {
            const float t = (d - 45.f) / 90.f;
            return {cx + outer_half, cy - outer_half + t * side};
        }
        if (d < 225.f)
        {
            const float t = (d - 135.f) / 90.f;
            return {cx + outer_half - t * side, cy + outer_half};
        }
        if (d < 315.f)
        {
            const float t = (d - 225.f) / 90.f;
            return {cx - outer_half, cy + outer_half - t * side};
        }

        const float t = (d - 315.f) / 90.f;
        return {cx - outer_half + t * side, cy - outer_half};
    }

    static void minimap_compass_padding(const float sf, float& pad_h, float& pad_v)
    {
        const float frame = 20.f * sf;
        const float label_ring = 12.f * sf;
        const ImVec2 cardinal_sz = ImGui::CalcTextSize("N");
        const ImVec2 deg_sz = ImGui::CalcTextSize("315");
        const float label_half_w = (std::max)(cardinal_sz.x, deg_sz.x) * 0.5f;
        const float label_half_h = cardinal_sz.y * 0.5f;
        const float edge = frame + label_ring;
        pad_h = edge + label_half_w;
        pad_v = edge + label_half_h;
    }

    void draw_minimap_compass_frame(ImDrawList* draw_list, const float rx, const float ry,
                                    const float size, const float sf, const float opacity,
                                    const float heading_deg, const bool rotate_map,
                                    const bool draw_backdrop, const ImVec4& backdrop_color) const
    {
        const float cx = rx + size * 0.5f;
        const float cy = ry + size * 0.5f;
        const float inner_half = size * 0.5f;
        const float pad = 20.f * sf;
        const float outer_half = inner_half + pad;
        const float frame_l = cx - outer_half;
        const float frame_t = cy - outer_half;
        const float frame_r = cx + outer_half;
        const float frame_b = cy + outer_half;
        const float inner_l = cx - inner_half;
        const float inner_t = cy - inner_half;
        const float inner_r = cx + inner_half;
        const float inner_b = cy + inner_half;

        const float flow_offset = rotate_map ? heading_deg : 0.f;
        const auto ring_pt = [&](const float deg, const float radius) -> ImVec2
        {
            return square_compass_ring_point(cx, cy, radius, deg - flow_offset);
        };

        const ImU32 frame_bg = col4_alpha(backdrop_color, opacity);
        const ImU32 frame_line = col_alpha(IM_COL32(220, 228, 235, 220), opacity);
        const ImU32 tick_col = col_alpha(IM_COL32(190, 198, 208, 180), opacity);
        const ImU32 major_tick_col = col_alpha(IM_COL32(235, 240, 245, 230), opacity);
        const ImU32 north_col = col_alpha(IM_COL32(255, 70, 70, 220), opacity);

        if (draw_backdrop)
        {
            draw_list->AddRectFilled({frame_l, frame_t}, {frame_r, inner_t}, frame_bg);
            draw_list->AddRectFilled({frame_l, inner_b}, {frame_r, frame_b}, frame_bg);
            draw_list->AddRectFilled({frame_l, inner_t}, {inner_l, inner_b}, frame_bg);
            draw_list->AddRectFilled({inner_r, inner_t}, {frame_r, inner_b}, frame_bg);
        }
        draw_list->AddRect({frame_l, frame_t}, {frame_r, frame_b}, frame_line, 5.f * sf, 0, 1.4f * sf);

        for (int deg_i = 0; deg_i < 360; deg_i += 5)
        {
            const float deg = static_cast<float>(deg_i);
            const bool major = (deg_i % 15) == 0;
            const float tick_outer = outer_half;
            const float tick_inner = outer_half - (major ? 7.f : 4.f) * sf;

            const ImVec2 p_out = ring_pt(deg, tick_outer);
            const ImVec2 p_in = ring_pt(deg, tick_inner);
            draw_list->AddLine(p_in, p_out, major ? major_tick_col : tick_col,
                (major ? 1.4f : 0.9f) * sf);

            if (major && (deg_i % 90) != 0)
            {
                char deg_buf[8];
                snprintf(deg_buf, sizeof(deg_buf), "%d", deg_i);
                const ImVec2 label_pos = ring_pt(deg, outer_half + 10.f * sf);
                const ImVec2 text_size = ImGui::CalcTextSize(deg_buf);
                draw_list->AddText(
                    {label_pos.x - text_size.x * 0.5f, label_pos.y - text_size.y * 0.5f},
                    col_alpha(IM_COL32(200, 206, 214, 170), opacity), deg_buf);
            }
        }

        static const char* cardinals[] = {"N", "E", "S", "W"};
        static const float cardinal_deg[] = {0.f, 90.f, 180.f, 270.f};
        for (int i = 0; i < 4; ++i)
        {
            const ImVec2 pos = ring_pt(cardinal_deg[i], outer_half + 12.f * sf);
            const ImVec2 text_size = ImGui::CalcTextSize(cardinals[i]);
            const ImU32 col = i == 0
                ? north_col
                : col_alpha(IM_COL32(220, 225, 232, 220), opacity);
            draw_list->AddText(
                {pos.x - text_size.x * 0.5f, pos.y - text_size.y * 0.5f},
                col, cardinals[i]);
        }

        const ImU32 marker_col = col_alpha(IM_COL32(255, 90, 70, 240), opacity);
        const ImVec2 marker_outer = ring_pt(heading_deg, outer_half - 1.f * sf);
        const ImVec2 marker_inner = ring_pt(heading_deg, inner_half + 3.f * sf);
        draw_list->AddLine(marker_inner, marker_outer, marker_col, 2.4f * sf);
        draw_list->AddCircleFilled(marker_outer, 2.6f * sf, marker_col);
    }

    void draw_minimap_height_indicator(ImDrawList* draw_list, const ImVec2& point,
                                       const float marker_y, const float player_y,
                                       const float marker_font, const char* marker_symbol,
                                       const float sf, const ImVec4& color,
                                       const float opacity) const
    {
        const float dy = marker_y - player_y;
        if (dy > -1.f && dy < 1.f)
            return;

        const bool above = dy >= 1.f;
        const esp_text::minimap_marker_settings& mm = esp_text::minimap_markers();
        ImFont* hud_font = menu::get_hud_font();
        const float gap = 0.5f * sf;
        const float sym_half = minimap_glyph_half_extent(hud_font, marker_font, marker_symbol);
        const char* height_symbol = above ? mm.height_up.c_str() : mm.height_down.c_str();
        const float glyph_font = above ? mm.size_height_up : mm.size_height_down;
        const float height_half = minimap_glyph_half_extent(hud_font, glyph_font, height_symbol);
        const float offset = sym_half + height_half + gap;
        const ImVec2 glyph_center = above
            ? ImVec2{point.x, point.y - offset}
            : ImVec2{point.x, point.y + offset};

        draw_minimap_symbol_marker(draw_list, glyph_center, height_symbol, glyph_font, opacity, color);
    }

    static bool is_point_inside_minimap(const ImVec2& point, const float rx, const float ry,
                                        const float size, const float pad = 0.f)
    {
        return point.x >= rx - pad && point.x <= rx + size + pad
            && point.y >= ry - pad && point.y <= ry + size + pad;
    }

    void draw_radar_marker_label(ImDrawList* draw_list, const ImVec2& point, const ImVec4& color,
                                 const std::string& text, const float font_size,
                                 const float opacity, const float clip_rx = 0.f,
                                 const float clip_ry = 0.f, const float clip_size = 0.f,
                                 const float marker_icon_font = 0.f,
                                 const float height_dy = 0.f, const float sf = 1.f) const
    {
        if (text.empty()) return;

        ImFont* hud_font = menu::get_hud_font();
        const ImVec2 text_size = hud_font
            ? hud_font->CalcTextSizeA(font_size, FLT_MAX, 0.f, text.c_str())
            : ImGui::CalcTextSize(text.c_str());

        const float anchor_y = marker_icon_font > 0.f
            ? minimap_label_anchor_y(point, marker_icon_font, height_dy, sf, hud_font)
            : point.y + 5.f;
        ImVec2 top_left = {
            point.x - text_size.x * 0.5f,
            anchor_y
        };

        if (clip_size > 0.f)
        {
            const float clip_max_x = clip_rx + clip_size;
            const float clip_max_y = clip_ry + clip_size;
            top_left.x = (std::max)(clip_rx, (std::min)(top_left.x, clip_max_x - text_size.x));
            top_left.y = (std::max)(clip_ry, (std::min)(top_left.y, clip_max_y - text_size.y));
        }

        const ImVec4 tinted = ImVec4(color.x, color.y, color.z, color.w * opacity);
        esp_text::draw_simple_topleft(draw_list, hud_font, font_size, top_left, tinted, text);
    }

    bool resolve_minimap_marker_point(const bool use_map, const minimap::map_bounds& display_bounds,
                                      const minimap::map_view_transform& view, const unity::vector3& pos,
                                      const float map_yaw, const float cx, const float cy,
                                      const float range, const float radar_scale,
                                      const float rx, const float ry, const float size,
                                      ImVec2& out) const
    {
        if (use_map)
        {
            float u = 0.f;
            float v = 0.f;
            if (!minimap::world_to_uv(display_bounds, pos.x, pos.z, u, v))
                return false;
            if (u < 0.f || u > 1.f || v < 0.f || v > 1.f)
                return false;
            if (!minimap::world_to_map_point(display_bounds, pos.x, pos.z, view, out))
                return false;
        }
        else if (!world_to_radar_point(pos, map_yaw, cx, cy, range, radar_scale, out))
        {
            return false;
        }

        return is_point_inside_minimap(out, rx, ry, size, 2.f);
    }

    void draw_radar_header_label(ImDrawList* draw_list, const ImVec2& top_left, const std::string& text,
                                 const float font_size, const float opacity) const
    {
        if (text.empty()) return;

        const ImVec4 color = ImVec4(1.f, 1.f, 1.f, 0.9f * opacity);
        esp_text::draw_simple_topleft(draw_list, menu::get_hud_font(), font_size, top_left, color, text);
    }

    static std::string read_location_id(const Il2CppClass* game_world_class,
                                        Il2CppObjectInstance& game_world_instance)
    {
        const Il2CppClass* classes[] = {
            game_world_class,
            game_world_instance.get_instance() ? game_world_instance.get_instance()->klass : nullptr
        };

        for (const Il2CppClass* clazz : classes)
        {
            if (!clazz) continue;

            for (const char* field_name : {"<LocationId>k__BackingField", "LocationId", "_locationId"})
            {
                const FieldInfo* field = il2utils::resolve_field(clazz, field_name);
                if (!field) continue;

                Il2CppString* value = *reinterpret_cast<Il2CppString**>(
                    reinterpret_cast<UINT64>(game_world_instance.get_instance()) + field->offset);
                if (value)
                    return il2utils::conv_string(value);
            }

            const MethodInfo* location_method = il2utils::resolve_method(clazz, "get_LocationId", 0);
            if (location_method && location_method->methodPointer)
            {
                const auto get_location = reinterpret_cast<Il2CppString*(*)(Il2CppObject*)>(
                    location_method->methodPointer);
                Il2CppString* value = get_location(game_world_instance.get_instance());
                if (value)
                    return il2utils::conv_string(value);
            }
        }

        return {};
    }

    struct minimap_layout
    {
        float rx = 0.f;
        float ry = 0.f;
        float size = 0.f;
    };

    minimap_layout compute_minimap_layout(const float sf) const
    {
        minimap_layout layout{};
        const float screen_w = menu::get_width();
        const float screen_h = menu::get_height();
        float margin_x = minimap_module_self->radar_margin_x->get_value() * sf;
        float margin_y = minimap_module_self->radar_margin_y->get_value() * sf;

        float compass_pad_h = 0.f;
        float compass_pad_v = 0.f;
        if (minimap_module_self->radar_minimap_compass->get_value())
            minimap_compass_padding(sf, compass_pad_h, compass_pad_v);

        layout.size = eft_api::is_external_loot_inventory_open_cached()
            ? 120.f * sf
            : minimap_module_self->radar_size->get_value() * sf;

        switch (minimap_module_self->radar_minimap_corner->get_value())
        {
        case 1: // top right
            layout.rx = screen_w - margin_x - compass_pad_h - layout.size;
            layout.ry = margin_y + compass_pad_v;
            break;
        case 0: // bottom right
        default:
            layout.rx = screen_w - margin_x - compass_pad_h - layout.size;
            layout.ry = screen_h - margin_y - compass_pad_v - layout.size;
            break;
        }

        return layout;
    }

    static float minimap_header_label_y(const float ry, const float size, const int corner)
    {
        if (corner == 1)
            return ry + size + 4.f;
        return ry - 18.f;
    }

    void handle_radar_window_input(const float rx, const float ry, const float size,
                                   const minimap::map_view_transform* view) const
    {
        ImGuiIO& io = ImGui::GetIO();
        const ImVec2 mouse = io.MousePos;
        const bool hovered = mouse.x >= rx && mouse.x <= rx + size
            && mouse.y >= ry && mouse.y <= ry + size;

        if (!hovered)
        {
            minimap_dragging_ = false;
            return;
        }

        if (io.MouseWheel != 0.f)
        {
            const float step = io.MouseWheel * 5.f;
            minimap_module_self->radar_range->set_value(minimap_module_self->radar_range->get_value() - step);
        }

        if (!view)
        {
            minimap_dragging_ = false;
            return;
        }

        if (io.MouseDown[0])
        {
            if (!minimap_dragging_)
            {
                minimap_dragging_ = true;
                minimap_drag_last_ = mouse;
            }
            else if (view->map_w > 1.f && view->map_h > 1.f)
            {
                const ImVec2 delta = {mouse.x - minimap_drag_last_.x, mouse.y - minimap_drag_last_.y};

                constexpr float drag_threshold = 4.f;
                if (fabsf(delta.x) > drag_threshold || fabsf(delta.y) > drag_threshold)
                    minimap_follow_player_ = false;

                const ImVec2 uv_last = minimap::screen_to_map_uv(*view, minimap_drag_last_);
                const ImVec2 uv_now = minimap::screen_to_map_uv(*view, mouse);
                minimap_drag_last_ = mouse;

                minimap_center_u_ += uv_last.x - uv_now.x;
                minimap_center_v_ += uv_last.y - uv_now.y;
            }
        }
        else
        {
            minimap_dragging_ = false;
        }
    }

    void scan_bot_corpses_for_minimap(
        Il2CppObjectInstance& game_world_instance,
        std::vector<bot_corpse_marker>& out) const
    {
        out.clear();
        if (!minimap_module_self
            || !minimap_module_self->radar->get_value()
            || !minimap_module_self->radar_minimap_bot_corpses->get_value())
            return;

        Il2CppObject* loot_hydra_obj = game_world_instance.get_field<Il2CppObject*>("LootItems");
        if (!loot_hydra_obj) return;

        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) return;

        Il2CppArray* loot_array = loot_list->m_p_list_array;
        const int count = static_cast<int>(loot_array->max_length);
        const float range = minimap_module_self->radar_range->get_value();
        const float max_dist_sq = range * range;
        Il2CppObject* game_world = game_world_instance.get_instance();

        out.reserve(16);
        for (int i = 0; i < count; ++i)
        {
            Il2CppObject* loot_obj = reinterpret_cast<Il2CppObject**>(&loot_array->data)[i];
            if (!loot_obj || !eft_api::class_name_contains(loot_obj, "Corpse")) continue;
            if (!eft_api::try_is_bot_corpse(loot_obj, game_world)) 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;

            const 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 > max_dist_sq) continue;

            out.push_back({pos, sqrtf(dist_sq)});
        }
    }

    void draw_radar_overlay(ImDrawList* draw_list, const std::vector<player>& players,
                            const std::vector<exfil>& exfils, const std::vector<transit>& transits,
                            const std::vector<quest_esp::poi_entry>& quest_pois,
                            const std::vector<bot_corpse_marker>& bot_corpses,
                            const float view_yaw) const
    {
        const float sf = menu::get_scale_factor();
        const minimap_layout layout = compute_minimap_layout(sf);
        const float size = layout.size;
        const float rx = layout.rx;
        const float ry = layout.ry;
        const float opacity = (std::min)(1.f, (std::max)(0.f,
            minimap_module_self->radar_minimap_opacity->get_value() / 100.f));
        const float range = minimap_module_self->radar_range->get_value();
        const bool show_map_texture = minimap_module_self->radar_minimap->get_value();
        const bool rotate = minimap_module_self->radar_minimap_rotate->get_value();
        const bool calibrate = minimap::editor_state().minimap_calibrate;
        const float map_yaw = rotate ? view_yaw : 0.f;
        const float cx = rx + size * 0.5f;
        const float cy = ry + size * 0.5f;
        const float half = size * 0.5f;
        const float radar_scale = half / range;
        const esp_text::minimap_marker_settings& mm = esp_text::minimap_markers();
        const float label_font = esp_text::minimap_label_font_size();

        const minimap::map_floor* floor = nullptr;
        minimap::map_bounds display_bounds{};
        minimap::map_view_transform view{};
        bool use_map = false;

        {
            const int style_idx = minimap_module_self->radar_minimap_style->get_value();
            minimap::set_map_style(style_idx >= 1
                ? minimap::map_style::satellite_style
                : minimap::map_style::abstract_style);
            minimap::request_active_map(current_location_id);

            uint32_t map_w = 0;
            uint32_t map_h = 0;
            if (minimap::get_active_floor(my_pos.x, my_pos.y, my_pos.z, floor) && minimap::is_map_ready() && floor
                && minimap::get_active_display_bounds(display_bounds)
                && minimap::get_map_size(map_w, map_h))
            {
                float player_u = 0.5f;
                float player_v = 0.5f;
                minimap::world_to_uv(display_bounds, my_pos.x, my_pos.z, player_u, player_v);

                if (!minimap_follow_player_ && minimap_has_last_player_pos_
                    && player_moved_on_map(my_pos, minimap_last_player_pos_))
                    minimap_follow_player_ = true;

                if (minimap_follow_player_)
                {
                    minimap_center_u_ = player_u;
                    minimap_center_v_ = player_v;
                }

                minimap_last_player_pos_ = my_pos;
                minimap_has_last_player_pos_ = true;

                if (minimap::build_map_view(display_bounds, map_w, map_h, rx, ry, size,
                    minimap_center_u_, minimap_center_v_, range, map_yaw, rotate, view))
                {
                    use_map = true;
                    handle_radar_window_input(rx, ry, size, &view);
                }
            }
        }

        if (!use_map)
            handle_radar_window_input(rx, ry, size, nullptr);

        const bool draw_backdrop = minimap_module_self->radar_minimap_backdrop->get_value();
        const ImVec4 backdrop_color = minimap_module_self->radar_minimap_backdrop_color->get_value();

        draw_list->PushClipRect({rx, ry}, {rx + size, ry + size}, true);
        if (draw_backdrop)
        {
            draw_list->AddRectFilled({rx, ry}, {rx + size, ry + size},
                col4_alpha(backdrop_color, opacity), 4.f);
        }

        if (use_map && show_map_texture)
            minimap::draw_active_map(draw_list, view, opacity);

        if (minimap_module_self->radar_minimap_grid->get_value())
        {
            const float grid_opacity = opacity * (minimap_module_self->radar_minimap_grid_opacity->get_value() / 100.f);
            draw_minimap_grid(draw_list, use_map && floor, display_bounds, view,
                map_yaw, cx, cy, range, radar_scale, rx, ry, size, sf, grid_opacity, rotate);
        }

        if (use_map && floor && minimap::editor_show_zone_outlines())
        {
            const minimap::map_editor_state& map_editor = minimap::editor_state();
            minimap::draw_overlay_zone_outlines(draw_list, *floor, view, map_editor.overlay_layers, opacity,
                map_editor.selected_overlay);
        }

        constexpr float deg2rad = 3.14159265f / 180.f;
        const float view_yaw_rad = view_yaw * deg2rad;

        if (use_map && floor)
        {
            ImVec2 player_point{};
            if (minimap::world_to_map_point(display_bounds, my_pos.x, my_pos.z, view, player_point))
            {
                float dir_x = sinf(view_yaw_rad);
                float dir_y = -cosf(view_yaw_rad);
                const float ahead_x = my_pos.x + sinf(view_yaw_rad) * 6.f;
                const float ahead_z = my_pos.z + cosf(view_yaw_rad) * 6.f;
                ImVec2 ahead_point{};
                if (minimap::world_to_map_point(display_bounds, ahead_x, ahead_z, view, ahead_point))
                {
                    dir_x = ahead_point.x - player_point.x;
                    dir_y = ahead_point.y - player_point.y;
                }

                draw_minimap_player_marker(draw_list, player_point,
                    dir_x, dir_y, sf, opacity, rotate);
                if (calibrate)
                {
                    char buf[128];
                    snprintf(buf, sizeof(buf), "You [%.0f,%.1f,%.0f]", my_pos.x, my_pos.y, my_pos.z);
                    draw_radar_marker_label(draw_list, player_point,
                        ImVec4(1.f, 1.f, 1.f, 1.f), buf, label_font, opacity,
                        0.f, 0.f, 0.f, mm.size_player, 0.f, sf);
                }
            }
        }
        else
        {
            draw_minimap_player_marker(draw_list, {cx, cy},
                sinf(view_yaw_rad), -cosf(view_yaw_rad), sf, opacity, rotate);
            if (calibrate)
            {
                char buf[128];
                snprintf(buf, sizeof(buf), "You [%.0f,%.1f,%.0f]", my_pos.x, my_pos.y, my_pos.z);
                draw_radar_marker_label(draw_list, {cx, cy},
                    ImVec4(1.f, 1.f, 1.f, 1.f), buf, label_font, opacity,
                    0.f, 0.f, 0.f, mm.size_player, 0.f, sf);
            }
        }

        if (minimap_module_self->radar_minimap_quest_esp->get_value())
        {
            const float max_dist = quest_esp_distance->get_value();
            for (const auto& poi : quest_pois)
            {
                if (poi.distance > max_dist) continue;

                const ImVec4 color = quest_poi_color(poi.type);

                ImVec2 point{};
                if (!resolve_minimap_marker_point(use_map && floor, display_bounds, view, poi.pos,
                        map_yaw, cx, cy, range, radar_scale, rx, ry, size, point))
                    continue;

                draw_minimap_symbol_marker(draw_list, point, mm.quest.c_str(),
                    mm.size_quest, opacity, color);
                draw_minimap_height_indicator(draw_list, point, poi.pos.y, my_pos.y,
                    mm.size_quest, mm.quest.c_str(), sf, color, opacity);

                if (minimap_module_self->radar_minimap_quest_names->get_value() || calibrate)
                {
                    char buf[160];
                    if (calibrate)
                        snprintf(buf, sizeof(buf), "%s [%.0f,%.0f]",
                            poi.quest_name.c_str(), poi.pos.x, poi.pos.z);
                    else
                        snprintf(buf, sizeof(buf), "%s", poi.quest_name.c_str());
                    draw_radar_marker_label(draw_list, point, color, buf, label_font, opacity,
                        rx, ry, size, mm.size_quest, poi.pos.y - my_pos.y, sf);
                }
            }
        }

        if (minimap_module_self->radar_exfils->get_value())
        {
            for (const auto& exfil : exfils)
            {
                ImVec2 point{};
                if (!resolve_minimap_marker_point(use_map && floor, display_bounds, view, exfil.pos3d,
                        map_yaw, cx, cy, range, radar_scale, rx, ry, size, point))
                    continue;

                const ImVec4 color = extracts_color->get_value();
                draw_minimap_symbol_marker(draw_list, point, mm.exfil.c_str(),
                    mm.size_exfil, opacity, color);
                draw_minimap_height_indicator(draw_list, point, exfil.pos3d.y, my_pos.y,
                    mm.size_exfil, mm.exfil.c_str(), sf, color, opacity);

                std::string label = exfil.name;
                if (calibrate)
                {
                    char buf[128];
                    snprintf(buf, sizeof(buf), "%s [%.0f,%.0f]", exfil.name.c_str(), exfil.pos3d.x, exfil.pos3d.z);
                    label = buf;
                }
                draw_radar_marker_label(draw_list, point, color, label, label_font, opacity,
                    rx, ry, size, mm.size_exfil, exfil.pos3d.y - my_pos.y, sf);
            }

            for (const auto& transit : transits)
            {
                ImVec2 point{};
                if (!resolve_minimap_marker_point(use_map && floor, display_bounds, view, transit.pos3d,
                        map_yaw, cx, cy, range, radar_scale, rx, ry, size, point))
                    continue;

                const ImVec4 color = transits_color->get_value();
                draw_minimap_symbol_marker(draw_list, point, mm.transit.c_str(),
                    mm.size_transit, opacity, color);
                draw_minimap_height_indicator(draw_list, point, transit.pos3d.y, my_pos.y,
                    mm.size_transit, mm.transit.c_str(), sf, color, opacity);

                std::string label = transit.name;
                if (calibrate)
                {
                    char buf[128];
                    snprintf(buf, sizeof(buf), "%s [%.0f,%.0f]", transit.name.c_str(), transit.pos3d.x, transit.pos3d.z);
                    label = buf;
                }
                draw_radar_marker_label(draw_list, point, color, label, label_font, opacity,
                    rx, ry, size, mm.size_transit, transit.pos3d.y - my_pos.y, sf);
            }
        }

        if (minimap_module_self->radar_minimap_bots->get_value())
        {
            for (const auto& p : players)
            {
                ImVec2 point{};
                if (!resolve_minimap_marker_point(use_map && floor, display_bounds, view, p.pos3d,
                        map_yaw, cx, cy, range, radar_scale, rx, ry, size, point))
                    continue;

                const ImVec4 col = p.kind == eft_api::npc_kind::pmc
                    ? player_color->get_value() : get_npc_color(p.kind);
                draw_minimap_symbol_marker(draw_list, point, mm.entity.c_str(),
                    mm.size_entity, opacity, col);
                draw_minimap_height_indicator(draw_list, point, p.pos3d.y, my_pos.y,
                    mm.size_entity, mm.entity.c_str(), sf, col, opacity);

                if (minimap_module_self->radar_minimap_bot_names->get_value())
                {
                    const std::string& name_text = !p.name_utf8.empty() ? p.name_utf8 : p.label_utf8;
                    if (!name_text.empty())
                    {
                        draw_radar_marker_label(draw_list, point, col, name_text, label_font, opacity,
                            rx, ry, size, mm.size_entity, p.pos3d.y - my_pos.y, sf);
                    }
                }
                else if (calibrate)
                {
                    char buf[128];
                    snprintf(buf, sizeof(buf), "%s [%.0f,%.0f]",
                        p.name_utf8.c_str(), p.pos3d.x, p.pos3d.z);
                    draw_radar_marker_label(draw_list, point, col, buf, label_font, opacity,
                        rx, ry, size, mm.size_entity, p.pos3d.y - my_pos.y, sf);
                }
            }
        }

        if (minimap_module_self->radar_minimap_bot_corpses->get_value())
        {
            const ImVec4 corpse_col = minimap_module_self->radar_minimap_bot_corpse_color->get_value();
            for (const auto& corpse : bot_corpses)
            {
                ImVec2 point{};
                if (!resolve_minimap_marker_point(use_map && floor, display_bounds, view, corpse.pos3d,
                        map_yaw, cx, cy, range, radar_scale, rx, ry, size, point))
                    continue;

                draw_minimap_symbol_marker(draw_list, point, mm.entity.c_str(),
                    mm.size_entity, opacity, corpse_col);
                draw_minimap_height_indicator(draw_list, point, corpse.pos3d.y, my_pos.y,
                    mm.size_entity, mm.entity.c_str(), sf, corpse_col, opacity);
            }
        }

        draw_list->PopClipRect();

        if (minimap_module_self->radar_minimap_compass->get_value())
        {
            update_compass_anim(view_yaw);
            draw_minimap_compass_frame(draw_list, rx, ry, size, sf, opacity,
                compass_anim_yaw_, rotate, draw_backdrop, backdrop_color);
        }
        else
        {
            draw_list->AddRect({rx, ry}, {rx + size, ry + size},
                col_alpha(IM_COL32(255, 255, 255, 200), opacity), 4.f);
        }

        const std::string floor_label = use_map ? minimap::get_minimap_floor_label() : std::string();
        const std::string& location_label = !current_location_display_name.empty()
            ? current_location_display_name
            : current_location_id;
        char label[256];
        if (use_map && calibrate)
        {
            snprintf(label, sizeof(label), "Entities: %zu | %s | %s | Y:%.1f | Range: %dm",
                players.size(), location_label.c_str(), floor_label.c_str(),
                my_pos.y, static_cast<int>(range));
        }
        else if (use_map)
        {
            snprintf(label, sizeof(label), "Entities: %zu | %s | %s | Range: %dm",
                players.size(), location_label.c_str(), floor_label.c_str(),
                static_cast<int>(range));
        }
        else
        {
            snprintf(label, sizeof(label), "Entities: %zu | Range: %dm | %s",
                players.size(), static_cast<int>(range), minimap::get_status_message().c_str());
        }

        draw_radar_header_label(draw_list,
            {rx, minimap_header_label_y(ry, size, minimap_module_self->radar_minimap_corner->get_value())}, label,
            label_font, opacity);
    }

    static void format_price_suffix(char* buf, const size_t buf_size, const int price)
    {
        if (price >= 1000)
            snprintf(buf, buf_size, "[%dK]", price / 1000);
        else
            snprintf(buf, buf_size, "(%d \xE2\x82\xBD)", price);
    }

    bool should_show_container_item(const container_item_entry& item) const
    {
        const bool wants_provisions = show_provisions->get_value();
        if (item.is_provision && !wants_provisions) return false;
        if (item.is_quest) return true;
        if (!item_pricing::has_prices()) return true;
        if (item.price <= 0) return true;
        return item.price >= loot_min_value->get_value();
    }

    bool should_show_corpse_item(const container_item_entry& item) const
    {
        if (item.is_provision && !show_provisions->get_value()) return false;
        return true;
    }

    container_item_cache* find_container_item_cache(const uintptr_t obj_key)
    {
        const auto found = container_item_caches_.find(obj_key);
        return found == container_item_caches_.end() ? nullptr : &found->second;
    }

    std::string resolve_localized_item_name(
        Il2CppObject* item_obj,
        Il2CppString*(*localized_short_name)(Il2CppObject*),
        Il2CppString*(*localized_name)(Il2CppObject*))
    {
        if (!eft_api::is_il2cpp_object_strict(item_obj))
            return "item_mod";

        const std::string template_id = eft_api::try_get_item_template_id(item_obj);
        if (!template_id.empty())
        {
            const auto cached = template_name_cache_.find(template_id);
            if (cached != template_name_cache_.end())
                return cached->second;
        }

        std::string name = eft_api::try_get_item_localized_name(
            item_obj, localized_short_name, localized_name);
        if (name.empty() || name == "item_mod")
            name = eft_api::try_get_item_display_name(item_obj);

        if (!template_id.empty())
            template_name_cache_[template_id] = name;

        return name;
    }

    void fill_item_cache_entries(
        container_item_cache& entry,
        Il2CppObject* root_item,
        const std::function<bool(const std::function<bool(Il2CppObject*)>&)>& foreach_items)
    {
        entry.items.clear();
        entry.items_ptr.reset();
        entry.cached_item_count = 0;
        entry.total_price = 0;

        if (!eft_api::is_il2cpp_object_strict(root_item)) return;

        static auto* localization_assembly = il2utils::resolve_image("Assembly-CSharp.dll");
        static auto* localization_class = il2utils::resolve_class(
            localization_assembly, "EFT", "LocalizationExtensions");
        static auto localized_short_name = il2utils::get_method<Il2CppString * (*)(Il2CppObject*)>(
            localization_class, "LocalizedShortName", 1);
        static auto localized_name = il2utils::get_method<Il2CppString * (*)(Il2CppObject*)>(
            localization_class, "LocalizedName", 1);

        const std::string root_name = resolve_localized_item_name(
            root_item, localized_short_name, localized_name);
        if (!root_name.empty() && root_name != "item_mod")
            entry.name = root_name;

        std::unordered_map<std::string, size_t> merged_indices;
        int raw_item_count = 0;

        foreach_items([&](Il2CppObject* item_obj) -> bool
        {
            if (!eft_api::is_inventory_item_valid(item_obj)) return true;

            const int stack_count = eft_api::try_get_item_stack_count(item_obj);
            const int stack_max = eft_api::try_get_item_stack_max(item_obj);
            if (stack_count > stack_max || stack_count > 100) return true;

            std::string item_name = resolve_localized_item_name(
                item_obj, localized_short_name, localized_name);

            ++raw_item_count;

            const std::string template_id = eft_api::try_get_item_template_id(item_obj);
            const bool is_quest = eft_api::try_get_item_is_quest(item_obj);
            const bool is_provision = eft_api::try_get_item_is_provision(item_obj);
            const int unit_price = item_pricing::resolve_item_sell_price(item_obj, template_id);

            int add_count = 1;
            if (stack_count > 1) add_count += stack_count - 1;

            const auto found = merged_indices.find(template_id);
            if (!template_id.empty() && found != merged_indices.end())
            {
                auto& existing = entry.items[found->second];
                existing.count += add_count;
                existing.price = unit_price * existing.count;
                entry.total_price += unit_price * add_count;
                return true;
            }

            container_item_entry loot_item{
                item_name,
                add_count,
                unit_price * add_count,
                is_quest,
                is_provision
            };

            if (!template_id.empty())
                merged_indices.emplace(template_id, entry.items.size());

            entry.total_price += loot_item.price;
            entry.items.push_back(std::move(loot_item));
            return true;
        });

        entry.cached_item_count = raw_item_count;
        entry.items_ptr = std::make_shared<std::vector<container_item_entry>>(entry.items);
    }

    void refresh_container_item_cache(container_item_cache& entry, Il2CppObject* container_obj)
    {
        entry.last_refresh = GetTickCount64();

        if (!eft_api::is_il2cpp_object_strict(container_obj)) return;
        if (!eft_api::is_container_object_alive(container_obj)) return;
        if (!eft_api::is_lootable_container_valid(container_obj)) return;

        Il2CppObject* item_owner = eft_api::try_get_container_item_owner(container_obj);
        Il2CppObject* root_item = eft_api::try_get_controller_root_item(item_owner);
        if (!eft_api::is_il2cpp_object_strict(root_item)) return;

        fill_item_cache_entries(entry, root_item, [&](const auto& visitor)
        {
            return eft_api::foreach_container_item(root_item, visitor);
        });
    }

    void refresh_corpse_item_cache(container_item_cache& entry, Il2CppObject* inventory_root)
    {
        if (!eft_api::is_corpse_lootable_inventory(inventory_root)) return;

        fill_item_cache_entries(entry, inventory_root, [&](const auto& visitor)
        {
            return eft_api::foreach_corpse_lootable_item(inventory_root, visitor);
        });
        entry.last_refresh = GetTickCount64();
    }

    container_item_cache* find_corpse_item_cache(const uintptr_t obj_key)
    {
        const auto found = corpse_item_caches_.find(obj_key);
        return found == corpse_item_caches_.end() ? nullptr : &found->second;
    }

    bool update_container_registry_position(container_registry_entry& entry)
    {
        Il2CppObject* obj = reinterpret_cast<Il2CppObject*>(entry.obj_key);
        if (!eft_api::is_il2cpp_object_likely_valid(obj)) return false;

        Il2CppObject* game_object = unity::component_get_game_object(obj);
        if (!game_object) return false;

        Il2CppObject* transform = unity::gameobject_get_transform(game_object);
        if (!transform) return false;

        entry.pos3d = unity::transform_get_pos(transform);
        entry.has_pos = true;
        return true;
    }

    void refresh_container_registry_distances(
        const unity::vector3& player_pos,
        const float min_dist_sq,
        const float max_dist_sq)
    {
        for (auto& entry : container_registry_)
        {
            if (!entry.has_pos) continue;

            const float dx = player_pos.x - entry.pos3d.x;
            const float dy = player_pos.y - entry.pos3d.y;
            const float dz = player_pos.z - entry.pos3d.z;
            entry.dist_sq = dx * dx + dy * dy + dz * dz;
            entry.distance = sqrtf(entry.dist_sq);
            entry.in_range = entry.dist_sq >= min_dist_sq && entry.dist_sq <= max_dist_sq;
        }
    }

    bool update_corpse_registry_position(corpse_registry_entry& entry)
    {
        Il2CppObject* obj = reinterpret_cast<Il2CppObject*>(entry.obj_key);
        if (!eft_api::is_il2cpp_object_likely_valid(obj)) return false;

        Il2CppObject* game_object = unity::component_get_game_object(obj);
        if (!game_object) return false;

        Il2CppObject* transform = unity::gameobject_get_transform(game_object);
        if (!transform) return false;

        entry.pos3d = unity::transform_get_pos(transform);
        entry.has_pos = true;
        return true;
    }

    static bool is_active_thrown_grenade(Il2CppObject* obj)
    {
        if (!obj || !obj->klass || !obj->klass->name) return false;

        const char* cls = obj->klass->name;
        if (strstr(cls, "Flare") != nullptr) return false;
        if (strstr(cls, "Grenade") == nullptr) return false;

        Il2CppObjectInstance inst(obj);
        if (!inst.get_field<bool>("_live")) return false;
        if (inst.get_field<bool>("_isDestroyed")) return false;

        if (const FieldInfo* planted_field = il2utils::resolve_field(obj->klass, "_isPlantedItem"))
        {
            const bool planted = *reinterpret_cast<bool*>(
                reinterpret_cast<uintptr_t>(obj) + planted_field->offset);
            if (planted) return false;
        }

        return true;
    }

    static float try_get_grenade_fuse_remaining(Il2CppObject* grenade_obj)
    {
        if (!grenade_obj) return -1.f;

        Il2CppObjectInstance grenade_inst(grenade_obj);
        const auto get_weapon = grenade_inst.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_WeaponSource", 0);
        if (!get_weapon) return -1.f;

        Il2CppObject* weapon = get_weapon(grenade_obj);
        if (!weapon) return -1.f;

        Il2CppObjectInstance weapon_inst(weapon);
        const auto get_delay = weapon_inst.get_method<float(*)(Il2CppObject*)>("get_GetExplDelay", 0);
        if (!get_delay) return -1.f;

        const float expl_delay = get_delay(weapon);
        if (expl_delay <= 0.f) return -1.f;

        const FieldInfo* time_field = il2utils::resolve_field(grenade_obj->klass, "_timeSpent");
        if (!time_field) return -1.f;

        const float time_spent = *reinterpret_cast<float*>(
            reinterpret_cast<uintptr_t>(grenade_obj) + time_field->offset);
        return (std::max)(0.f, expl_delay - time_spent);
    }

    static std::string resolve_grenade_label(
        Il2CppObject* grenade_obj,
        Il2CppString*(*localized_short_name)(Il2CppObject*))
    {
        if (!grenade_obj) return i18n::tr("grenade_lbl");

        Il2CppObjectInstance grenade_inst(grenade_obj);
        const auto get_weapon = grenade_inst.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_WeaponSource", 0);
        Il2CppObject* weapon = get_weapon ? get_weapon(grenade_obj) : nullptr;

        if (weapon && localized_short_name)
        {
            Il2CppString* name_str = localized_short_name(weapon);
            if (name_str)
            {
                std::string name = eft_api::to_display_utf8(il2utils::conv_wstring(name_str));
                if (!name.empty()) return name;
            }
        }

        if (const FieldInfo* ammo_field = il2utils::resolve_field(grenade_obj->klass, "_grenadeAmmo"))
        {
            Il2CppObject* ammo = *reinterpret_cast<Il2CppObject**>(
                reinterpret_cast<uintptr_t>(grenade_obj) + ammo_field->offset);
            if (ammo && localized_short_name)
            {
                Il2CppString* name_str = localized_short_name(ammo);
                if (name_str)
                {
                    std::string name = eft_api::to_display_utf8(il2utils::conv_wstring(name_str));
                    if (!name.empty()) return name;
                }
            }
        }

        return i18n::tr("grenade_lbl");
    }

    void scan_grenade_esp(
        Il2CppObjectInstance& game_world_instance,
        const unity::vector3& player_pos,
        std::vector<grenade_entry>& out_grenades)
    {
        Il2CppObject* grenades_hydra_obj = game_world_instance.get_field<Il2CppObject*>("Grenades");
        if (!grenades_hydra_obj) return;

        Il2CppObjectInstance grenades_hydra(grenades_hydra_obj);
        unity::list* grenade_list = grenades_hydra.get_field<unity::list*>("_iteration");
        if (!grenade_list || !grenade_list->m_p_list_array) return;

        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);

        Il2CppArray* grenade_array = grenade_list->m_p_list_array;
        const int count = static_cast<int>(grenade_array->max_length);
        out_grenades.reserve(static_cast<size_t>(count));

        const float max_dist = grenade_esp_distance->get_value();
        const float max_dist_sq = max_dist * max_dist;

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

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

            const unity::vector3 pos = unity::transform_get_pos(transform);
            const float dx = player_pos.x - pos.x;
            const float dy = player_pos.y - pos.y;
            const float dz = player_pos.z - pos.z;
            const float dist_sq = dx * dx + dy * dy + dz * dz;
            if (dist_sq > max_dist_sq) continue;

            grenade_entry entry;
            entry.label = resolve_grenade_label(grenade_obj, localized_short_name);
            entry.pos3d = pos;
            entry.distance = sqrtf(dist_sq);
            entry.fuse_remaining = try_get_grenade_fuse_remaining(grenade_obj);
            out_grenades.push_back(std::move(entry));
        }
    }

    void refresh_corpse_registry_distances(
        const unity::vector3& player_pos,
        const float min_dist_sq,
        const float max_dist_sq)
    {
        for (auto& entry : corpse_registry_)
        {
            if (!entry.has_pos) continue;

            const float dx = player_pos.x - entry.pos3d.x;
            const float dy = player_pos.y - entry.pos3d.y;
            const float dz = player_pos.z - entry.pos3d.z;
            entry.dist_sq = dx * dx + dy * dy + dz * dz;
            entry.distance = sqrtf(entry.dist_sq);
            entry.in_range = entry.dist_sq >= min_dist_sq && entry.dist_sq <= max_dist_sq;
        }
    }

    void scan_corpse_registry(
        Il2CppObjectInstance& game_world_instance,
        const unity::vector3& player_pos,
        const float min_dist_sq,
        const float max_dist_sq)
    {
        Il2CppObject* loot_hydra_obj = game_world_instance.get_field<Il2CppObject*>("LootItems");
        if (!loot_hydra_obj) return;

        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) return;

        Il2CppArray* loot_array = loot_list->m_p_list_array;
        const int count = static_cast<int>(loot_array->max_length);

        std::unordered_map<uintptr_t, corpse_registry_entry> existing;
        existing.reserve(corpse_registry_.size());
        for (auto& entry : corpse_registry_)
            existing.emplace(entry.obj_key, std::move(entry));

        std::vector<corpse_registry_entry> merged;
        merged.reserve(16);

        for (int i = 0; i < count; ++i)
        {
            Il2CppObject* loot_obj = reinterpret_cast<Il2CppObject**>(&loot_array->data)[i];
            if (!loot_obj || !eft_api::class_name_contains(loot_obj, "Corpse")) 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 = player_pos.x - pos.x;
            const float dy = player_pos.y - pos.y;
            const float dz = player_pos.z - pos.z;
            const float dist_sq = dx * dx + dy * dy + dz * dz;
            if (dist_sq < min_dist_sq || dist_sq > max_dist_sq) continue;

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

            Il2CppObject* inventory_root = eft_api::try_resolve_corpse_inventory_root(loot_obj, item_obj);
            if (!inventory_root) continue;

            const uintptr_t obj_key = reinterpret_cast<uintptr_t>(loot_obj);
            const auto prev = existing.find(obj_key);
            if (prev != existing.end())
            {
                corpse_registry_entry entry = std::move(prev->second);
                existing.erase(prev);
                entry.inventory_root = inventory_root;
                entry.pos3d = pos;
                entry.has_pos = true;
                entry.dist_sq = dist_sq;
                entry.distance = sqrtf(dist_sq);
                entry.in_range = true;
                merged.push_back(std::move(entry));
                continue;
            }

            merged.push_back({
                obj_key,
                inventory_root,
                "Corpse",
                pos,
                sqrtf(dist_sq),
                dist_sq,
                true,
                true
            });
        }

        corpse_registry_ = std::move(merged);
        refresh_corpse_registry_distances(player_pos, min_dist_sq, max_dist_sq);
    }

    void update_corpse_loot_esp(
        Il2CppObjectInstance& game_world_instance,
        std::vector<container_esp_entry>& out_corpses,
        bool& store_dirty)
    {
        const ULONGLONG now = GetTickCount64();
        constexpr ULONGLONG corpse_tick_interval_ms = 150;
        constexpr ULONGLONG corpse_scan_interval_ms = 500;
        constexpr int max_corpse_refreshes_per_tick = 1;
        constexpr ULONGLONG corpse_refresh_interval_ms = 5000;
        constexpr size_t max_tracked_corpses = 16;

        if (now - corpse_last_tick_ms_ < corpse_tick_interval_ms)
            return;
        corpse_last_tick_ms_ = now;

        const float loot_min = loot_min_distance->get_value();
        const float min_dist_sq = loot_min * loot_min;
        const float max_dist = loot_distance->get_value();
        const float max_dist_sq = max_dist * max_dist;

        if (now - corpse_last_scan_ms_ >= corpse_scan_interval_ms || corpse_registry_.empty())
        {
            scan_corpse_registry(game_world_instance, my_pos, min_dist_sq, max_dist_sq);
            corpse_last_scan_ms_ = now;

            std::unordered_set<uintptr_t> live_keys;
            live_keys.reserve(corpse_registry_.size());
            for (const auto& entry : corpse_registry_)
                live_keys.insert(entry.obj_key);

            for (auto it = corpse_item_caches_.begin(); it != corpse_item_caches_.end();)
            {
                if (live_keys.find(it->first) == live_keys.end())
                    it = corpse_item_caches_.erase(it);
                else
                    ++it;
            }
        }
        else
        {
            for (auto& entry : corpse_registry_)
                update_corpse_registry_position(entry);
            refresh_corpse_registry_distances(my_pos, min_dist_sq, max_dist_sq);
        }

        std::vector<corpse_registry_entry*> nearby;
        nearby.reserve(corpse_registry_.size());
        for (auto& entry : corpse_registry_)
        {
            if (entry.in_range)
                nearby.push_back(&entry);
        }

        if (nearby.size() > max_tracked_corpses)
        {
            std::partial_sort(
                nearby.begin(),
                nearby.begin() + static_cast<std::ptrdiff_t>(max_tracked_corpses),
                nearby.end(),
                [](const corpse_registry_entry* a, const corpse_registry_entry* b)
                {
                    return a->dist_sq < b->dist_sq;
                });
            nearby.resize(max_tracked_corpses);
        }

        if (!nearby.empty())
        {
            int refreshed = 0;
            for (size_t n = 0; n < nearby.size() && refreshed < max_corpse_refreshes_per_tick; ++n)
            {
                const size_t idx = (corpse_loot_refresh_cursor_ + n) % nearby.size();
                corpse_registry_entry& reg = *nearby[idx];
                if (!eft_api::is_il2cpp_object_strict(reg.inventory_root)) continue;

                container_item_cache& cache = corpse_item_caches_[reg.obj_key];
                cache.obj_key = reg.obj_key;
                if (cache.name.empty()) cache.name = reg.name;

                const bool needs_items = !cache.items_ptr || cache.items_ptr->empty();
                const bool stale = cache.last_refresh == 0
                    || now - cache.last_refresh >= corpse_refresh_interval_ms;
                if (!stale && !needs_items) continue;

                refresh_corpse_item_cache(cache, reg.inventory_root);
                ++refreshed;
            }
            corpse_loot_refresh_cursor_ = (corpse_loot_refresh_cursor_ + 1) % nearby.size();
        }

        out_corpses.clear();
        out_corpses.reserve(nearby.size());
        for (const corpse_registry_entry* reg : nearby)
        {
            container_esp_entry visible{};
            visible.container_name = reg->name;
            visible.pos3d = reg->pos3d;
            visible.distance = reg->distance;

            if (container_item_cache* cache_ptr = find_corpse_item_cache(reg->obj_key))
            {
                visible.total_price = cache_ptr->total_price;
                visible.items = cache_ptr->items_ptr;
                if (!cache_ptr->name.empty()) visible.container_name = cache_ptr->name;
            }

            out_corpses.push_back(std::move(visible));
        }

        store_dirty = true;
    }

    void draw_inventory_list_entry(
        ImDrawList* draw_list,
        Il2CppObject* cam,
        const container_esp_entry& entry,
        char* text_buf,
        const size_t text_buf_size,
        const ImVec4& header_color,
        const bool show_total_price,
        const bool show_item_prices,
        const bool corpse_list = false) const
    {
        const float sf = menu::get_scale_factor();
        ImFont* hud_font = menu::get_hud_font();
        const float hud_font_size = esp_text::esp_font_size();
        const float line_h = esp_text::text_size(hud_font, hud_font_size, "Ay").y + 2.f * menu::get_scale_factor();
        const float line_w = 2.f * sf;
        const float text_gap = 4.f * sf;
        const float initial_y_offset = line_h + 4.f * sf;
        const float line_x = 10.f * sf;

        std::vector<const container_item_entry*> visible_items;
        if (entry.items)
        {
            visible_items.reserve(entry.items->size());
            for (const auto& item : *entry.items)
            {
                const bool visible = corpse_list
                    ? should_show_corpse_item(item)
                    : should_show_container_item(item);
                if (visible)
                    visible_items.push_back(&item);
            }
        }

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

        const int distance_m = static_cast<int>(entry.distance);

        if (visible_items.empty())
        {
            snprintf(text_buf, text_buf_size, "[%dm] %s", distance_m, entry.container_name.c_str());
            esp_text::draw_simple(draw_list, hud_font, hud_font_size,
                ImVec2(screen_pos.x, screen_pos.y),
                header_color, text_buf, entry.distance);
            return;
        }

        const ImVec2 anchor = {screen_pos.x, screen_pos.y};
        const float bar_top = anchor.y - initial_y_offset;
        const float bar_height = static_cast<float>(visible_items.size()) * line_h + line_h * 0.5f;
        const float bar_left = anchor.x - line_x;
        const float text_right = bar_left - line_w - text_gap;

        draw_list->AddRectFilled(
            {bar_left, bar_top},
            {bar_left + line_w, bar_top + bar_height},
            IM_COL32(255, 220, 0, 220));

        if (show_total_price && entry.total_price > 0)
        {
            char price_suffix[32]{};
            format_price_suffix(price_suffix, sizeof(price_suffix), entry.total_price);
            snprintf(text_buf, text_buf_size, "[%dm] %s %s",
                distance_m, entry.container_name.c_str(), price_suffix);
        }
        else
        {
            snprintf(text_buf, text_buf_size, "[%dm] %s", distance_m, entry.container_name.c_str());
        }

        {
            const ImVec2 header_size = esp_text::text_size(hud_font, hud_font_size, text_buf);
            esp_text::draw_simple_topleft(draw_list, hud_font, hud_font_size,
                {text_right - header_size.x, bar_top - line_h},
                header_color, text_buf, entry.distance);
        }

        float item_y = bar_top;
        for (const container_item_entry* item : visible_items)
        {
            const ImVec4 item_color = color_for_loot_price(item->price, item->is_quest, item->is_provision);

            if (show_item_prices && item->price > 0)
            {
                char price_suffix[32]{};
                format_price_suffix(price_suffix, sizeof(price_suffix), item->price);

                if (item->count > 1)
                {
                    snprintf(text_buf, text_buf_size, "%s %s (%d)",
                        item->name.c_str(), price_suffix, item->count);
                }
                else
                {
                    snprintf(text_buf, text_buf_size, "%s %s",
                        item->name.c_str(), price_suffix);
                }
            }
            else if (item->count > 1)
            {
                snprintf(text_buf, text_buf_size, "%s (%d)", item->name.c_str(), item->count);
            }
            else
            {
                snprintf(text_buf, text_buf_size, "%s", item->name.c_str());
            }

            const ImVec2 item_size = esp_text::text_size(hud_font, hud_font_size, text_buf);
            esp_text::draw_simple_topleft(draw_list, hud_font, hud_font_size,
                {text_right - item_size.x, item_y},
                item_color, text_buf, entry.distance);
            item_y += line_h;
        }
    }

    void draw_container_entry(
        ImDrawList* draw_list,
        Il2CppObject* cam,
        const container_esp_entry& entry,
        char* text_buf,
        const size_t text_buf_size) const
    {
        const bool show_prices = container_show_value->get_value();
        draw_inventory_list_entry(draw_list, cam, entry, text_buf, text_buf_size,
            container_color->get_value(), show_prices, show_prices);
    }

    void draw_corpse_entry(
        ImDrawList* draw_list,
        Il2CppObject* cam,
        const container_esp_entry& entry,
        char* text_buf,
        const size_t text_buf_size) const
    {
        const bool show_prices = show_loot_prices->get_value();
        draw_inventory_list_entry(draw_list, cam, entry, text_buf, text_buf_size,
            loot_color->get_value(), show_prices, show_prices, true);
    }

    ImVec4 quest_poi_color(const quest_esp::poi_type type) const
    {
        switch (type)
        {
        case quest_esp::poi_type::visit: return quest_visit_color->get_value();
        case quest_esp::poi_type::find: return quest_find_color->get_value();
        case quest_esp::poi_type::place: return quest_place_color->get_value();
        }
        return quest_visit_color->get_value();
    }

    static std::string quest_poi_group_key(const unity::vector3& pos, const std::string& name)
    {
        char buf[320];
        snprintf(buf, sizeof(buf), "%d|%d|%d|%s",
            static_cast<int>(pos.x * 10.f),
            static_cast<int>(pos.y * 10.f),
            static_cast<int>(pos.z * 10.f),
            name.c_str());
        return buf;
    }

    void draw_quest_esp_overlay(ImDrawList* draw_list, Il2CppObject* cam,
                                const std::vector<quest_esp::poi_entry>& pois,
                                char* text_buf, const size_t text_buf_size) const
    {
        if (!quest_esp_enabled->get_value()) return;

        const float max_dist = quest_esp_distance->get_value();
        const float font = esp_text::quest_esp_font_size();
        ImFont* hud_font = menu::get_hud_font();

        std::unordered_map<std::string, std::vector<const quest_esp::poi_entry*>> groups;
        groups.reserve(pois.size());

        for (const auto& poi : pois)
        {
            if (poi.distance > max_dist) continue;
            groups[quest_poi_group_key(poi.pos, poi.quest_name)].push_back(&poi);
        }

        for (const auto& [key, group] : groups)
        {
            (void)key;
            if (group.empty()) continue;

            const quest_esp::poi_entry& anchor_poi = *group.front();
            unity::vector3 screen_pos = eft_api::world_to_screen(cam, anchor_poi.pos, game_state::current_zoom);
            if (!eft_api::is_visible(screen_pos)) continue;

            snprintf(text_buf, text_buf_size, "[QUEST] [%dm] %s",
                static_cast<int>(anchor_poi.distance), anchor_poi.quest_name.c_str());

            // rollback: только название квеста, без описания условия ниже
            // esp_text::draw_simple(draw_list, hud_font, font,
            //     {screen_pos.x, screen_pos.y}, quest_poi_color(anchor_poi.type), text_buf, anchor_poi.distance);
            // continue;

            esp_text::draw_simple(draw_list, hud_font, font,
                {screen_pos.x, screen_pos.y}, quest_poi_color(anchor_poi.type), text_buf, anchor_poi.distance);

            if (quest_esp_descriptions->get_value())
            {
                std::vector<std::string> descriptions;
                descriptions.reserve(group.size());
                for (const quest_esp::poi_entry* poi : group)
                {
                    if (!poi || poi->description.empty()) continue;

                    const auto duplicate = std::find(descriptions.begin(), descriptions.end(), poi->description);
                    if (duplicate != descriptions.end()) continue;

                    descriptions.push_back(poi->description);
                }

                if (!descriptions.empty())
                {
                    const float sf = menu::get_scale_factor();
                    const float desc_font = font * 0.85f;
                    const ImVec4 desc_color = {0.92f, 0.92f, 0.92f, 0.88f};
                    float line_y = screen_pos.y + esp_text::text_size(hud_font, font, text_buf).y + 2.f * sf;

                    for (const std::string& description : descriptions)
                    {
                        esp_text::draw_simple(draw_list, hud_font, desc_font,
                            {screen_pos.x, line_y}, desc_color, description, anchor_poi.distance);
                        line_y += esp_text::text_size(hud_font, desc_font, description).y + 1.f * sf;
                    }
                }
            }
        }
    }

public:
    void export_exfil_coords()
    {
        if (!game_state::is_in_raid)
        {
            logger::warn("Export exfils: enter a raid first.");
            return;
        }

        std::vector<exfil> local_exfils;
        std::vector<transit> local_transits;
        unity::vector3 player_pos{};
        std::string location = current_location_id;

        {
            std::lock_guard<std::mutex> lock(data_mutex);
            local_exfils = exfil_store;
            local_transits = transits_store;
            player_pos = my_pos;
            if (location.empty())
                location = current_location_id;
        }

        if (local_exfils.empty() && local_transits.empty())
        {
            logger::warn("Export exfils: no data yet. Enable Extracts & Transits and wait ~1s in raid.");
            return;
        }

        using json = nlohmann::json;
        json root;
        root["location"] = location.empty() ? "unknown" : location;
        root["player"] = json::array({ player_pos.x, player_pos.y, player_pos.z });

        json exfils_json = json::array();

        for (const auto& exfil : local_exfils)
        {
            exfils_json.push_back({
                {"name", exfil.name},
                {"world", json::array({ exfil.pos3d.x, exfil.pos3d.z })},
                {"y", exfil.pos3d.y}
            });

            logger::info("Exfil '" + exfil.name + "' world=[" +
                std::to_string(static_cast<int>(exfil.pos3d.x)) + "," +
                std::to_string(static_cast<int>(exfil.pos3d.z)) + "] y=" +
                std::to_string(static_cast<int>(exfil.pos3d.y)));
        }

        json transits_json = json::array();
        for (const auto& transit : local_transits)
        {
            transits_json.push_back({
                {"name", transit.name},
                {"world", json::array({ transit.pos3d.x, transit.pos3d.z })},
                {"y", transit.pos3d.y}
            });
            logger::info("Transit '" + transit.name + "' world=[" +
                std::to_string(static_cast<int>(transit.pos3d.x)) + "," +
                std::to_string(static_cast<int>(transit.pos3d.z)) + "]");
        }

        root["exfils"] = exfils_json;
        root["transits"] = transits_json;
        root["note"] = "Exfil world coordinates for minimap calibration.";

        const std::string maps_dir = minimap::get_maps_directory();
        std::error_code ec;
        if (!maps_dir.empty())
            std::filesystem::create_directories(maps_dir, ec);

        const std::string file_name = "exfil_coords_" + (location.empty() ? "unknown" : location) + ".json";
        const std::string out_path = maps_dir.empty()
            ? file_name
            : item_pricing::join_path(maps_dir, file_name);

        std::ofstream out(out_path);
        if (!out.is_open())
        {
            logger::error("Export exfils: failed to write " + out_path);
            return;
        }

        out << root.dump(2);
        logger::info("Export exfils: saved " + out_path);
        logger::info("Player pos: x=" + std::to_string(static_cast<int>(player_pos.x)) +
            " y=" + std::to_string(static_cast<int>(player_pos.y)) +
            " z=" + std::to_string(static_cast<int>(player_pos.z)));
    }

    void clear_runtime_cache() override
    {
        {
            std::lock_guard<std::mutex> lock(data_mutex);
            item_info_cache.clear();
            template_name_cache_.clear();
            eft_api::clear_localization_cache();
            container_registry_.clear();
            container_item_caches_.clear();
            corpse_registry_.clear();
            corpse_item_caches_.clear();
            container_marker_snapshot_.clear();
            container_position_cursor_ = 0;
            container_loot_refresh_cursor_ = 0;
            corpse_loot_refresh_cursor_ = 0;
            corpse_last_tick_ms_ = 0;
            corpse_last_scan_ms_ = 0;
            container_registry_scan_ms_ = 0;
            container_last_tick_ms_ = 0;
            quest_esp_last_refresh_ms_ = 0;
            exfil_store.clear();
            transits_store.clear();
            player_store.clear();
            loot_store.clear();
            grenade_store.clear();
            container_store_.reset();
            corpse_store_.reset();
            skeleton_store.clear();
            quest_poi_store.clear();
            bot_corpse_store_.clear();
            bot_corpse_last_scan_ms_ = 0;
        }

        quest_esp::clear_caches();
        last_prices_version_ = 0;
    }

    [[nodiscard]] size_t get_runtime_cache_count() override
    {
        std::lock_guard<std::mutex> lock(data_mutex);
        size_t count = item_info_cache.size() + template_name_cache_.size();

        for (const auto& [_, cache] : container_item_caches_)
            count += 1 + cache.items.size();

        for (const auto& [_, cache] : corpse_item_caches_)
            count += 1 + cache.items.size();

        count += quest_esp::get_runtime_cache_count();
        return count;
    }

    [[nodiscard]] size_t get_runtime_cache_bytes() override
    {
        std::lock_guard<std::mutex> lock(data_mutex);
        size_t bytes = 0;
        for (const auto& [_, info] : item_info_cache)
            bytes += info.name.size() + 64;
        for (const auto& [_, name] : template_name_cache_)
            bytes += name.size() + 32;

        for (const auto& [_, cache] : container_item_caches_)
        {
            bytes += 48;
            for (const auto& item : cache.items)
                bytes += item.name.size() + 48;
        }
        for (const auto& [_, cache] : corpse_item_caches_)
        {
            bytes += 48;
            for (const auto& item : cache.items)
                bytes += item.name.size() + 48;
        }

        bytes += quest_esp::get_runtime_cache_bytes();
        return bytes;
    }

    bool prices_feature_enabled() const
    {
        return show_loot_prices->get_value()
            || container_show_value->get_value()
            || loot_price_gradient->get_value();
    }

    void init() override
    {
        extracts->set_master_toggle(true);
        transits->set_master_toggle(true);
        players->set_master_toggle(true);
        npc->set_master_toggle(true);
        player_box->set_master_toggle(true);
        player_health->set_master_toggle(true);
        skeleton->set_master_toggle(true);
        loot_esp->set_master_toggle(true);
        container_esp->set_master_toggle(true);
        quest_esp_enabled->set_master_toggle(true);
        grenade_esp->set_master_toggle(true);
    }

    void prepare_menu_draw() override
    {
        const auto dis = [](ConfigValue* v, const bool disabled) { v->set_disabled(disabled); };

        const bool world_extracts = extracts->get_value();
        const bool world_transits = transits->get_value();
        const bool grenades_on = grenade_esp->get_value();
        const bool players_on = players->get_value();
        const bool npc_on = npc->get_value();
        const bool player_labels_on = players_on || npc_on;
        const bool box_on = player_box->get_value();
        const bool health_on = player_health->get_value();
        const bool skeleton_on = skeleton->get_value();
        const bool loot_on = loot_esp->get_value();
        const bool gradient_on = loot_on && loot_price_gradient->get_value();
        const bool containers_on = container_esp->get_value();
        const bool quest_overlay_on = quest_esp_enabled->get_value();
        const bool quest_map_on = minimap_module_self->radar_minimap_quest_esp->get_value();
        const bool quest_poi_on = quest_overlay_on || quest_map_on;

        section_world_->set_section_active(world_extracts || world_transits || grenades_on);
        dis(extracts_color, !world_extracts);
        dis(transits_color, !world_transits);
        dis(grenade_esp_distance, !grenades_on);
        dis(grenade_color, !grenades_on);
        dis(grenade_show_fuse, !grenades_on);

        section_players_->set_section_active(player_labels_on);
        dis(player_color, !players_on);
        dis(scav_color, !npc_on);
        dis(boss_color, !npc_on);
        dis(guard_color, !npc_on);
        dis(cultist_color, !npc_on);
        dis(raider_color, !npc_on);
        dis(defector_color, !npc_on);
        dis(black_division_color, !npc_on);
        dis(btr_color, !npc_on);
        dis(show_bot_names, !player_labels_on);
        dis(player_distance, !player_labels_on);

        section_overlay_->set_section_active(box_on || health_on);
        dis(box_distance, !box_on);
        const bool box_vis_on = box_on && box_visibility->get_value();
        dis(box_color, !box_on || box_vis_on);
        dis(box_thickness, !box_on);
        dis(box_visibility, !box_on);
        dis(box_visible_color, !box_vis_on);
        dis(box_occluded_color, !box_vis_on);
        dis(health_color, !health_on);
        dis(health_low_color, !health_on);

        section_skeleton_->set_section_active(skeleton_on);
        dis(skeleton_health_colors, !skeleton_on);
        dis(skeleton_distance, !skeleton_on);
        dis(skeleton_color, !skeleton_on);
        dis(skeleton_thickness, !skeleton_on);
        dis(skeleton_head_circle, !skeleton_on);
        // --- skeleton visibility ESP (rollback: remove block) ---
        const bool skeleton_vis_on = skeleton_on && skeleton_visibility->get_value();
        const bool skeleton_vis_health_on = skeleton_vis_on && skeleton_health_colors->get_value();
        dis(skeleton_visibility, !skeleton_on);
        dis(skeleton_visible_color, !skeleton_vis_on);
        dis(skeleton_occluded_color, !skeleton_vis_on);
        dis(skeleton_vis_health_mode, !skeleton_vis_health_on);
        // --- end skeleton visibility ESP ---

        section_loot_->set_section_active(loot_on);
        dis(loot_min_distance, !loot_on);
        dis(loot_distance, !loot_on);
        dis(loot_min_value, !loot_on);
        dis(show_loot_prices, !loot_on);
        dis(loot_price_gradient, !loot_on);
        dis(loot_gradient_cheap, !gradient_on);
        dis(loot_gradient_expensive, !gradient_on);
        dis(loot_gradient_above_max, !gradient_on);
        dis(loot_gradient_max, !gradient_on);
        dis(loot_color, !loot_on);
        dis(show_provisions, !loot_on);

        section_containers_->set_section_active(containers_on);
        dis(container_show_loot, !containers_on);
        dis(container_show_value, !containers_on);
        dis(corpse_show_loot, !loot_on);
        dis(container_distance, !containers_on);
        dis(container_color, !containers_on);

        section_quest_->set_section_active(quest_poi_on);
        // dis(quest_item_distance, !quest_loot_on);
        // dis(quest_item_color, !quest_loot_on);
        dis(quest_esp_distance, !quest_poi_on);
        dis(quest_esp_descriptions, !quest_overlay_on);
        dis(quest_visit_color, !quest_poi_on);
        dis(quest_find_color, !quest_poi_on);
        dis(quest_place_color, !quest_poi_on);
    }

    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::shared_ptr<const std::vector<container_esp_entry>> local_containers;
        std::shared_ptr<const std::vector<container_esp_entry>> local_corpses;
        std::vector<skeleton_entry> local_skeletons;
        std::vector<quest_esp::poi_entry> local_quest_poi;
        std::vector<grenade_entry> local_grenades;
        std::vector<bot_corpse_marker> local_bot_corpses;

        {
             std::lock_guard<std::mutex> lock(data_mutex);
            local_exfils = exfil_store;
            local_transits = transits_store;
            local_grenades = grenade_store;
            local_players = player_store;
            local_loots = loot_store;
            local_containers = container_store_;
            local_corpses = corpse_store_;
            local_skeletons = skeleton_store;
            local_quest_poi = quest_poi_store;
            local_bot_corpses = bot_corpse_store_;
        }

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

        const float player_max_dist = player_distance->get_value();
        const float hud_font_size = esp_text::esp_font_size();
        ImFont* hud_font = menu::get_hud_font();
        const esp_text::settings text_settings = esp_text::current_settings();
        if (text_settings.depth_sort)
            esp_text::begin_frame();
        const bool batch_loot_labels = text_settings.clustering || text_settings.depth_sort;
        std::vector<esp_text::label> loot_labels;
        if (batch_loot_labels)
            loot_labels.reserve(local_loots.size());

        char text_buf[256];

        if (minimap_module_self->radar->get_value())
            draw_radar_overlay(draw_list, local_players, local_exfils, local_transits,
                local_quest_poi, local_bot_corpses, get_view_yaw(cam));

        const bool show_player_esp = players->get_value() || npc->get_value();
        const float box_max_dist = box_distance->get_value();
        if (show_player_esp)
        {
            for (const auto& entry : local_players)
            {
                if (entry.distance > player_max_dist) continue;

                if (entry.kind == eft_api::npc_kind::pmc)
                {
                    if (!players->get_value()) continue;
                }
                else if (!npc->get_value())
                {
                    continue;
                }

                if ((player_box->get_value() || player_health->get_value())
                    && entry.distance <= box_max_dist)
                {
                    ImVec2 box_pos{};
                    ImVec2 box_size{};
                    ImVec2 label_anchor{};
                    ImVec4 color{};
                    if (compute_player_box_layout(cam, entry, box_pos, box_size, label_anchor, color))
                        draw_player_box_geometry(draw_list, box_pos, box_size, color, entry);
                }
            }
        }

        if (skeleton->get_value())
        {
            const float skel_max_dist = skeleton_distance->get_value();
            for (const auto& entry : local_skeletons)
            {
                if (entry.distance > skel_max_dist) continue;
                draw_skeleton_entry(draw_list, cam, entry);
            }
        }

         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);

                esp_text::draw_simple(draw_list, hud_font, hud_font_size,
                    {screen_pos.x, screen_pos.y}, extracts_color->get_value(), text_buf, distance);
            }
        }

         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());

                esp_text::draw_simple(draw_list, hud_font, hud_font_size,
                    {screen_pos.x, screen_pos.y}, transits_color->get_value(), text_buf, distance);
            }
        }

        if (grenade_esp->get_value())
        {
            const float grenade_max = grenade_esp_distance->get_value();
            const ImVec4 grenade_col = grenade_color->get_value();
            const bool show_fuse = grenade_show_fuse->get_value();

            for (const auto& entry : local_grenades)
            {
                if (entry.distance > grenade_max) continue;

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

                if (show_fuse && entry.fuse_remaining >= 0.f)
                {
                    snprintf(text_buf, sizeof(text_buf), "[%dm] %s (%.1fs)",
                        static_cast<int>(entry.distance), entry.label.c_str(), entry.fuse_remaining);
                }
                else
                {
                    snprintf(text_buf, sizeof(text_buf), "[%dm] %s",
                        static_cast<int>(entry.distance), entry.label.c_str());
                }

                esp_text::draw_simple(draw_list, hud_font, hud_font_size,
                    {screen_pos.x, screen_pos.y}, grenade_col, text_buf, entry.distance);
            }
        }

        if (show_player_esp)
        {
            for (const auto& entry : local_players)
            {
                if (entry.distance > player_max_dist) continue;

                if (entry.kind == eft_api::npc_kind::pmc)
                {
                    if (!players->get_value()) continue;
                }
                else if (!npc->get_value())
                {
                    continue;
                }

                const ImVec4 color = entry.kind == eft_api::npc_kind::pmc
                    ? player_color->get_value()
                    : get_npc_color(entry.kind);

                if ((player_box->get_value() || player_health->get_value())
                    && entry.distance <= box_max_dist)
                {
                    ImVec2 box_pos{};
                    ImVec2 box_size{};
                    ImVec2 label_anchor{};
                    ImVec4 box_color{};
                    if (!compute_player_box_layout(cam, entry, box_pos, box_size, label_anchor, box_color))
                        continue;

                    draw_player_label_text(draw_list, label_anchor, color, entry, entry.distance);
                    continue;
                }

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

                draw_player_label_text(draw_list,
                    {screen_pos.x, screen_pos.y + 10 * menu::get_scale_factor()},
                    color, entry, entry.distance);
            }
        }

         const bool show_loot = loot_esp->get_value();
        const bool show_containers = container_esp->get_value();
        const bool show_container_loot = show_containers && container_show_loot->get_value();
        const bool show_corpse_loot = show_loot && corpse_show_loot->get_value();
        const float loot_max = loot_distance->get_value();
        const float container_max = container_distance->get_value();

        if (show_container_loot && local_containers)
        {
            for (const auto& container : *local_containers)
            {
                if (container.distance > container_max) continue;
                draw_container_entry(draw_list, cam, container, text_buf, sizeof(text_buf));
            }
        }

        if (show_corpse_loot && local_corpses)
        {
            for (const auto& corpse : *local_corpses)
            {
                if (corpse.distance > loot_max) continue;
                draw_corpse_entry(draw_list, cam, corpse, text_buf, sizeof(text_buf));
            }
        }

        if (show_loot || show_containers)
        {
             for (const auto& l : local_loots)
            {
                if (l.is_container && show_container_loot) continue;
                if (l.is_corpse_loot && show_corpse_loot) continue;
                if (l.is_quest) continue;

                float dist_sq = 0.f;
                if (l.is_container && l.distance > 0.f)
                    dist_sq = l.distance * l.distance;
                else
                {
                    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;
                    dist_sq = dx * dx + dy * dy + dz * dz;
                }

                const float normal_max = l.is_container ? container_max : loot_max;
                const bool show_normal = (l.is_container ? show_containers : show_loot)
                    && dist_sq <= normal_max * normal_max;

                if (!show_normal) 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 = l.is_container && l.distance > 0.f
                    ? static_cast<int>(l.distance)
                    : static_cast<int>(sqrtf(dist_sq));

                {
                    if (show_loot_prices->get_value() && !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());
                    }

                    const ImVec4 color = l.is_container
                        ? container_color->get_value()
                        : (l.is_corpse_loot
                            ? loot_color->get_value()
                            : color_for_loot_price(l.price, l.is_quest, false));

                    if (batch_loot_labels)
                    {
                        esp_text::label entry;
                        entry.pos = {screen_pos.x, screen_pos.y};
                        entry.color = color;
                        entry.text = text_buf;
                        entry.distance = static_cast<float>(distance);
                        entry.price = l.price;
                        loot_labels.push_back(std::move(entry));
                    }
                    else
                    {
                        esp_text::draw_simple(draw_list, hud_font, hud_font_size,
                            {screen_pos.x, screen_pos.y}, color, text_buf,
                            static_cast<float>(distance));
                    }
                }
            }
        }

        if (batch_loot_labels && !loot_labels.empty())
            esp_text::draw_labels(draw_list, hud_font, hud_font_size, loot_labels, text_settings);

        draw_quest_esp_overlay(draw_list, cam, local_quest_poi, text_buf, sizeof(text_buf));

        if (text_settings.depth_sort)
            esp_text::flush_pending(draw_list, hud_font);
    }
    void reset_raid_context()
    {
        minimap_center_u_ = 0.5f;
        minimap_center_v_ = 0.5f;
        minimap_follow_player_ = true;
        minimap_has_last_player_pos_ = false;
        minimap_dragging_ = false;
        compass_anim_initialized_ = false;
        compass_anim_last_ms_ = 0;

        clear_runtime_cache();

        if (chams_module_self)
            chams_module_self->applied_players.clear();
    }

    void application_update() override
    {
        const uint64_t prices_version = item_pricing::get_version();
        if (prices_version != last_prices_version_)
        {
            item_info_cache.clear();
            container_item_caches_.clear();
            corpse_item_caches_.clear();
            last_prices_version_ = prices_version;
        }

        if (!game_state::is_in_raid && (last_game_world_ptr || !last_raid_location_id_.empty()))
        {
            reset_raid_context();
            last_game_world_ptr = nullptr;
            last_raid_location_id_.clear();
            current_location_id.clear();
            current_location_display_name.clear();
        }
    }

    void gameworld_update(const Il2CppClass* game_world_class, Il2CppObjectInstance game_world_instance,
        Il2CppObjectInstance main_player) override
    {
        if (!minimap_configs_scanned)
        {
            minimap::scan_configs();
            minimap_configs_scanned = true;
        }

        if (prices_feature_enabled())
            item_pricing::ensure_loaded();

        current_location_id = read_location_id(game_world_class, game_world_instance);
        current_location_display_name = eft_api::try_get_location_display_name(current_location_id);
        eft_api::update_external_loot_inventory_open_cached();

        const Il2CppObject* game_world_ptr = game_world_instance.get_instance();
        const bool world_changed = game_world_ptr != last_game_world_ptr;
        const bool location_changed = !current_location_id.empty()
            && !last_raid_location_id_.empty()
            && current_location_id != last_raid_location_id_;

        if (world_changed || location_changed)
        {
            reset_raid_context();
            if (!current_location_id.empty())
                minimap::notify_location_changed(current_location_id);
            last_game_world_ptr = game_world_ptr;
        }

        if (!current_location_id.empty())
            last_raid_location_id_ = current_location_id;

        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::wstring main_nickname_wide = il2utils::conv_wstring(
            main_profile.get_method<Il2CppString*(*)(Il2CppObject*)>("get_Nickname", 0)(
                main_profile.get_instance()));
        const std::string main_nickname = eft_api::to_display_utf8(main_nickname_wide);

        const bool skeleton_vis_enabled = skeleton->get_value() && skeleton_visibility->get_value()
            && skeleton_vis::is_ready();
        const bool box_vis_enabled = player_box->get_value() && box_visibility->get_value()
            && skeleton_vis::is_ready();
        unity::vector3 vis_origin{};
        const bool has_vis_origin = (skeleton_vis_enabled || box_vis_enabled)
            && skeleton_vis::try_get_visibility_origin(main_player.get_instance(), vis_origin);

         std::vector<exfil> temp_exfils;
        std::vector<transit> temp_transits;
        std::vector<player> temp_players;
        std::vector<loot_entry> temp_loots;
        std::vector<container_esp_entry> temp_containers;
        std::vector<container_esp_entry> temp_corpses;
        std::vector<skeleton_entry> temp_skeletons;
        std::vector<quest_esp::poi_entry> temp_quest_poi;
        std::vector<grenade_entry> temp_grenades;
        bool container_store_dirty = false;
        bool corpse_store_dirty = false;
        bool quest_poi_dirty = false;

        const bool need_exfils = extracts->get_value()
            || (minimap_module_self->radar->get_value() && minimap_module_self->radar_exfils->get_value());

         if (need_exfils)
        {
            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");
                    const std::string name = eft_api::try_get_exfil_display_name(exfil);

                    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) });
                }
            }
        }

        const bool need_transits = transits->get_value()
            || (minimap_module_self->radar->get_value() && minimap_module_self->radar_exfils->get_value());

         if (need_transits)
        {
            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));
                    const std::string location = eft_api::try_get_transit_display_name(transit.get_instance());

                    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) });
                }
            }
        }

        const bool need_players = players->get_value() || npc->get_value()
            || minimap_module_self->radar->get_value()
            || skeleton->get_value()
            || player_box->get_value()
            || player_health->get_value();

        if (need_players)
        {
            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);

                    Il2CppObject* profile_ptr = get_profile(player_instance.get_instance());
                    if (!profile_ptr) continue;
                    Il2CppObjectInstance profile_instance(profile_ptr);

                    const std::wstring nickname_wide = il2utils::conv_wstring(
                        profile_instance.get_method<Il2CppString*(*)(Il2CppObject*)>("get_Nickname", 0)(
                            profile_instance.get_instance()));
                    if (nickname_wide == main_nickname_wide
                        || eft_api::to_display_utf8(nickname_wide) == main_nickname)
                        continue;

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

                    const bool is_pmc = eft_api::try_detect_is_pmc(player_object);
                    const int profile_side = eft_api::get_profile_side(profile_ptr);
                    const unity::vector3 player_pos = unity::transform_get_pos(player_transform);
                    const float distance = my_pos.distance(player_pos);
                    const eft_api::player_label label = eft_api::build_player_label(
                        is_pmc, nickname_wide, static_cast<int>(distance),
                        show_bot_names->get_value() || minimap_module_self->radar_minimap_bot_names->get_value(),
                        profile_ptr, player_object, profile_side);

                    unity::vector3 head_pos = player_pos;
                    eft_api::try_get_head_position(player_object, head_pos);

                    eft_api::health_value health{};
                    bool is_alive = true;
                    if (player_health->get_value() && distance <= box_distance->get_value())
                        eft_api::try_get_player_health(player_object, health, is_alive);

                    bool any_part_visible = true;
                    if (box_vis_enabled && has_vis_origin && distance <= box_distance->get_value())
                        any_part_visible = is_player_any_part_visible(vis_origin, player_object);

                    temp_players.push_back({
                        label.text_wide,
                        label.prefix_utf8,
                        label.name_utf8,
                        label.text_utf8,
                        label.show_name_line,
                        label.kind,
                        player_pos,
                        head_pos,
                        distance,
                        health.current,
                        health.maximum,
                        is_alive,
                        any_part_visible
                    });

                    if (skeleton->get_value() && distance <= skeleton_distance->get_value())
                    {
                        // auto skel = collect_skeleton(player_object, label.kind, distance);
                        auto skel = collect_skeleton(player_object, label.kind, distance,
                            skeleton_vis_enabled && has_vis_origin, vis_origin);
                        if (!skel.bones.empty())
                            temp_skeletons.push_back(std::move(skel));
                    }
                }
            }
        }

        if (grenade_esp->get_value())
            scan_grenade_esp(game_world_instance, my_pos, temp_grenades);

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

            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();
                        const bool scan_corpse_loot = corpse_show_loot->get_value();

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

                            if (scan_corpse_loot && eft_api::class_name_contains(loot_obj, "Corpse"))
                                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;

                            const bool is_corpse_loot = eft_api::is_bot_corpse_loot(loot_obj, item_obj);

                            std::string item_name = "item_mod";
                            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;

                                if (item_market_price == 0 && item_pricing::has_prices()) {
                                    const std::string item_id = eft_api::try_get_item_template_id(item_obj);
                                    item_market_price = item_pricing::resolve_item_sell_price(item_obj, item_id);
                                    cache_it->second.price = item_market_price;
                                }
                            }
                            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;
                                    }
                                }

                                const std::string item_id = eft_api::try_get_item_template_id(item_obj);
                                if (item_pricing::has_prices())
                                    item_market_price = item_pricing::resolve_item_sell_price(item_obj, item_id);

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

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

                             bool wants_provisions = show_provisions->get_value();

                            if (is_provision && !wants_provisions) continue;
                            bool bypass_filter = is_corpse_loot || (is_provision && wants_provisions);
                            if (!bypass_filter
                                && item_pricing::has_prices()
                                && item_market_price < loot_min_value->get_value()) continue;

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

                        if (scan_corpse_loot)
                            update_corpse_loot_esp(game_world_instance, temp_corpses, corpse_store_dirty);
                    }
                }
            }
            else if (corpse_show_loot->get_value())
            {
                corpse_registry_.clear();
                corpse_item_caches_.clear();
                corpse_loot_refresh_cursor_ = 0;
                corpse_last_tick_ms_ = 0;
                corpse_last_scan_ms_ = 0;
                if (!temp_corpses.empty())
                {
                    temp_corpses.clear();
                    corpse_store_dirty = true;
                }
            }

            if (container_esp->get_value())
            {
                const bool show_container_loot = container_show_loot->get_value();
                const float container_max_dist = container_distance->get_value();
                const float container_max_dist_sq = container_max_dist * container_max_dist;
                const ULONGLONG now = GetTickCount64();
                constexpr int position_updates_far_per_tick = 24;
                constexpr int max_loot_refreshes_per_tick = 1;
                constexpr ULONGLONG loot_refresh_interval_ms = 5000;
                constexpr ULONGLONG container_discovery_interval_ms = 120000;
                constexpr ULONGLONG container_tick_interval_ms = 100;

                if (now - container_last_tick_ms_ < container_tick_interval_ms)
                {
                    temp_loots.insert(
                        temp_loots.end(),
                        container_marker_snapshot_.begin(),
                        container_marker_snapshot_.end());
                }
                else
                {
                    container_last_tick_ms_ = now;

                    static auto* container_assembly = il2utils::resolve_image("Assembly-CSharp.dll");
                    static Il2CppObject* lootable_container_type = nullptr;
                    if (!lootable_container_type)
                    {
                        lootable_container_type = il2utils::get_system_type(
                            container_assembly, "EFT.Interactive", "LootableContainer");
                    }

                    if (lootable_container_type
                        && (now - container_registry_scan_ms_ >= container_discovery_interval_ms
                            || container_registry_.empty()))
                    {
                        std::unordered_map<uintptr_t, container_registry_entry> existing;
                        existing.reserve(container_registry_.size());
                        for (auto& entry : container_registry_)
                            existing.emplace(entry.obj_key, std::move(entry));

                        size_t found = 0;
                        Il2CppObject** objects = unity::find_objects_of_type(lootable_container_type, &found);

                        std::vector<container_registry_entry> merged;
                        merged.reserve(found);

                        for (size_t i = 0; i < found; ++i)
                        {
                            Il2CppObject* obj = objects[i];
                            if (!eft_api::is_il2cpp_object_likely_valid(obj)) continue;

                            const uintptr_t obj_key = reinterpret_cast<uintptr_t>(obj);
                            const auto prev = existing.find(obj_key);
                            if (prev != existing.end())
                            {
                                container_registry_entry entry = std::move(prev->second);
                                existing.erase(prev);
                                if (entry.name.empty()
                                    || entry.name == "LootableContainer"
                                    || entry.name == "Container")
                                {
                                    entry.name = eft_api::try_get_lootable_container_display_name(obj);
                                }
                                merged.push_back(std::move(entry));
                                continue;
                            }

                            std::string name = eft_api::try_get_lootable_container_display_name(obj);
                            if (name.empty())
                                name = obj->klass->name ? obj->klass->name : "Container";
                            merged.push_back({
                                obj_key,
                                std::move(name),
                                {},
                                0.f,
                                0.f,
                                false,
                                false
                            });
                        }

                        container_registry_ = std::move(merged);
                        container_registry_scan_ms_ = now;
                        container_position_cursor_ = 0;

                        for (auto& entry : container_registry_)
                        {
                            if (!entry.has_pos)
                                update_container_registry_position(entry);
                        }
                    }

                    {
                        int bootstrapped = 0;
                        for (auto& entry : container_registry_)
                        {
                            if (entry.has_pos) continue;
                            if (bootstrapped++ >= 32) break;
                            update_container_registry_position(entry);
                        }
                    }

                    refresh_container_registry_distances(my_pos, min_dist_sq, container_max_dist_sq);

                    std::vector<size_t> in_range_indices;
                    in_range_indices.reserve(32);
                    for (size_t i = 0; i < container_registry_.size(); ++i)
                    {
                        if (container_registry_[i].in_range)
                            in_range_indices.push_back(i);
                    }

                    for (const size_t index : in_range_indices)
                        update_container_registry_position(container_registry_[index]);

                    refresh_container_registry_distances(my_pos, min_dist_sq, container_max_dist_sq);

                    if (!container_registry_.empty())
                    {
                        const size_t registry_size = container_registry_.size();
                        for (int updated = 0;
                             updated < position_updates_far_per_tick && registry_size > 0;
                             ++updated)
                        {
                            container_position_cursor_ %= registry_size;
                            const size_t index = container_position_cursor_++;
                            if (container_registry_[index].in_range) continue;

                            update_container_registry_position(container_registry_[index]);
                        }

                        refresh_container_registry_distances(my_pos, min_dist_sq, container_max_dist_sq);
                    }

                    std::vector<const container_registry_entry*> nearby;
                    nearby.reserve(in_range_indices.size());
                    for (const auto& entry : container_registry_)
                    {
                        if (!entry.has_pos || !entry.in_range) continue;
                        nearby.push_back(&entry);
                    }

                    if (show_container_loot && !nearby.empty())
                    {
                        int refreshed = 0;
                        for (size_t n = 0; n < nearby.size() && refreshed < max_loot_refreshes_per_tick; ++n)
                        {
                            const size_t idx = (container_loot_refresh_cursor_ + n) % nearby.size();
                            const container_registry_entry& nearby_entry = *nearby[idx];
                            Il2CppObject* obj = reinterpret_cast<Il2CppObject*>(nearby_entry.obj_key);
                            if (!eft_api::is_il2cpp_object_likely_valid(obj)) continue;

                            container_item_cache& cache = container_item_caches_[nearby_entry.obj_key];
                            cache.obj_key = nearby_entry.obj_key;
                            if (cache.name.empty()) cache.name = nearby_entry.name;

                            const bool stale = cache.last_refresh == 0
                                || now - cache.last_refresh >= loot_refresh_interval_ms;
                            if (!stale) continue;

                            cache.name = nearby_entry.name;
                            refresh_container_item_cache(cache, obj);
                            ++refreshed;
                        }
                        container_loot_refresh_cursor_ = (container_loot_refresh_cursor_ + 1) % nearby.size();

                        std::unordered_set<uintptr_t> live_keys;
                        live_keys.reserve(nearby.size());
                        for (const container_registry_entry* entry : nearby)
                            live_keys.insert(entry->obj_key);

                        for (auto it = container_item_caches_.begin(); it != container_item_caches_.end();)
                        {
                            if (live_keys.find(it->first) == live_keys.end())
                                it = container_item_caches_.erase(it);
                            else
                                ++it;
                        }
                    }
                    else if (!show_container_loot)
                    {
                        container_item_caches_.clear();
                    }

                    container_marker_snapshot_.clear();
                    container_marker_snapshot_.reserve(nearby.size());
                    temp_containers.clear();
                    temp_containers.reserve(nearby.size());

                    for (const container_registry_entry* entry : nearby)
                    {
                        if (show_container_loot)
                        {
                            container_esp_entry visible{};
                            visible.container_name = entry->name;
                            visible.pos3d = entry->pos3d;
                            visible.distance = entry->distance;

                            if (container_item_cache* cache_ptr = find_container_item_cache(entry->obj_key))
                            {
                                visible.total_price = cache_ptr->total_price;
                                visible.items = cache_ptr->items_ptr;
                                if (!cache_ptr->name.empty()) visible.container_name = cache_ptr->name;
                            }

                            temp_containers.push_back(std::move(visible));
                        }
                        else
                        {
                            container_marker_snapshot_.push_back({
                                entry->name,
                                entry->pos3d,
                                true,
                                0,
                                false,
                                entry->distance
                            });
                        }
                    }

                    temp_loots.insert(
                        temp_loots.end(),
                        container_marker_snapshot_.begin(),
                        container_marker_snapshot_.end());
                    container_store_dirty = true;
                }
            }
            else
            {
                container_registry_.clear();
                container_item_caches_.clear();
                container_marker_snapshot_.clear();
                container_position_cursor_ = 0;
                container_loot_refresh_cursor_ = 0;
                container_registry_scan_ms_ = 0;
                container_last_tick_ms_ = 0;
                container_store_dirty = true;
                temp_containers.clear();
            }
        }

        const bool need_quest_poi = quest_esp_enabled->get_value()
            || minimap_module_self->radar_minimap_quest_esp->get_value();

        if (need_quest_poi)
        {
            const ULONGLONG now = GetTickCount64();
            constexpr ULONGLONG quest_refresh_ms = 5000;
            constexpr size_t quest_loot_per_tick = 350;

            if (quest_esp::is_collect_idle()
                && now - quest_esp_last_refresh_ms_ >= quest_refresh_ms)
            {
                quest_esp::start_collect_cycle(
                    game_world_instance.get_instance(),
                    main_player.get_instance(),
                    my_pos);
                quest_esp_last_refresh_ms_ = now;
            }

            if (quest_esp::is_collect_scanning_loot())
                quest_esp::advance_collect(quest_loot_per_tick);

            if (quest_esp::is_collect_ready())
            {
                temp_quest_poi = quest_esp::take_collect_result();
                quest_poi_dirty = true;
            }
        }
        else
        {
            quest_esp::reset_incremental();
            if (!quest_poi_store.empty())
            {
                temp_quest_poi.clear();
                quest_poi_dirty = true;
            }
            quest_esp_last_refresh_ms_ = 0;
        }

        std::vector<bot_corpse_marker> temp_bot_corpses;
        bool bot_corpse_dirty = false;
        if (minimap_module_self
            && minimap_module_self->radar->get_value()
            && minimap_module_self->radar_minimap_bot_corpses->get_value())
        {
            const ULONGLONG now = GetTickCount64();
            constexpr ULONGLONG bot_corpse_scan_interval_ms = 500;
            if (now - bot_corpse_last_scan_ms_ >= bot_corpse_scan_interval_ms)
            {
                scan_bot_corpses_for_minimap(game_world_instance, temp_bot_corpses);
                bot_corpse_last_scan_ms_ = now;
                bot_corpse_dirty = true;
            }
        }
        else if (!bot_corpse_store_.empty() || bot_corpse_last_scan_ms_ != 0)
        {
            temp_bot_corpses.clear();
            bot_corpse_last_scan_ms_ = 0;
            bot_corpse_dirty = true;
        }

         {
            std::lock_guard<std::mutex> lock(data_mutex);
            exfil_store = std::move(temp_exfils);
            transits_store = std::move(temp_transits);
            grenade_store = std::move(temp_grenades);
            player_store = std::move(temp_players);
            loot_store = std::move(temp_loots);
            if (container_store_dirty)
            {
                container_store_ = std::make_shared<std::vector<container_esp_entry>>(
                    std::move(temp_containers));
            }
            if (corpse_store_dirty)
            {
                corpse_store_ = std::make_shared<std::vector<container_esp_entry>>(
                    std::move(temp_corpses));
            }
            skeleton_store = std::move(temp_skeletons);
            if (quest_poi_dirty)
                quest_poi_store = std::move(temp_quest_poi);
            if (bot_corpse_dirty)
                bot_corpse_store_ = std::move(temp_bot_corpses);
        }
    }
};