#pragma once
#include "vectors.hpp"
#include "../extern/Imgui/imgui_internal.h"
#include "../driver/driver.hpp"
#include "sdk.hpp"
#include "../utils/settings.hpp"
#include "../../aimbot/aimbot.hpp"
#include "../utils/Authentication/skStr.h"
#include "../sdk/offsets.hpp"
#include <vector>
#include <array>
#include <cmath>
#include <thread>
#include <atomic>
#include "loot.h"
#include <bit>
#include "../gui/gui/var.hpp"

namespace Constants {
    constexpr int MAX_PLAYERS = 100;
    constexpr float HEAD_OFFSET_Z = 15.0f;
    constexpr float BOX_WIDTH_RATIO = 0.60f;
    constexpr float DISTANCE_SCALE = 100.0f;
    constexpr float TEXT_SPACING = 3.0f;
    constexpr float TEXT_OFFSET = 5.0f;

    constexpr std::array<int, 4> HITBOX_BONES = { 110, 67, 7, 2 };
    constexpr int DEFAULT_BONE = 110;
    constexpr int FALLBACK_BONE = 110;
}


struct PlayerCache {
    uintptr_t mesh = 0;
    uintptr_t pawn = 0;
    uintptr_t playerState = 0;
    Vector3 bottomPos;
    Vector3 headPos;
    float distance = 0.0f;
    char teamIndex = 0;
    bool isValid = false;
    bool isVisible;
};

struct ScreenBounds {
    float left, top, right, bottom, width, height;
    Vector2 center;

    void Calculate(const Vector2& head2D, const Vector2& bottom2D, float boxHeight) {
        height = std::abs(boxHeight);
        width = boxHeight * Constants::BOX_WIDTH_RATIO;
        left = head2D.x - width * 0.5f;
        top = head2D.y;
        right = left + width;
        bottom = top + height;
        center = { left + width * 0.5f, top + height * 0.5f };
    }
};

struct ScreenInfo {
    ImVec2 size;
    float centerX;
    float centerY;
    float height;

    void Update() {
        size = ImGui::GetIO().DisplaySize;
        centerX = size.x * 0.5f;
        centerY = size.y * 0.5f;
        height = size.y;
    }
};

uint64_t decrypt(uint64_t encrypted)
{
    return std::rotl(encrypted, 55) - 0x676DBEABULL;
}



class LocalPlayerManager {
public:
    bool Update() {
        if (!UpdateGameWorld()) return false;
        if (!UpdatePlayerController()) return false;

        hasValidPawn = (PawnData::AcknowledgedPawn != 0);

        if (hasValidPawn) {
            UpdatePawnData();
        }
        else {
            ClearPawnData();
        }

        return true;
    }

    bool HasValidPawn() const { return hasValidPawn; }

private:
    bool hasValidPawn = false;


    // Current Patch: v39.11
    inline uint64_t decryptUWorld(uint64_t encrypted) {
        return std::rotl(encrypted, 56) - 603662288ULL;
    }

    bool UpdateGameWorld() {
        uintptr_t encryptedUWorld = kernel->read_t<uintptr_t>(kernel->BaseAddress + offsets::UWorld);
        uintptr_t decryptedUWorld = decryptUWorld(encryptedUWorld); // Using the new decryption method

        PawnData::GWorld = decryptedUWorld;
        if (!PawnData::GWorld) return false;

        uintptr_t gameInstance = kernel->read_t<uint64_t>(PawnData::GWorld + offsets::OwningGameInstance);
        if (!gameInstance) return false;

        PawnData::LocalPlayer = kernel->read_t<uint64_t>(
            kernel->read_t<uint64_t>(gameInstance + offsets::LocalPlayers)
        );
        return PawnData::LocalPlayer != 0;
    }


    bool UpdatePlayerController() {
        PawnData::PlayerController = kernel->read_t<uint64_t>(
            PawnData::LocalPlayer + offsets::PlayerController
        );
        PawnData::Player = kernel->read_t<uint64_t>(
            PawnData::PlayerController + offsets::AcknowledgedPawn
        );
        PawnData::AcknowledgedPawn = PawnData::Player;
        return true;
    }

    void UpdatePawnData() {
        PawnData::Mesh = kernel->read_t<uint64_t>(
            PawnData::AcknowledgedPawn + offsets::Mesh
        );

        uintptr_t localPlayerState = kernel->read_t<uint64_t>(
            PawnData::AcknowledgedPawn + offsets::PlayerState
        );

        PawnData::RootComponent = kernel->read_t<uint64_t>(
            PawnData::AcknowledgedPawn + offsets::RootComponent
        );
        PawnData::LocalWeapon = kernel->read_t<uint64_t>(
            PawnData::AcknowledgedPawn + offsets::CurrentWeapon
        );
        PawnData::WeaponData = kernel->read_t<uint64_t>(
            PawnData::AcknowledgedPawn + offsets::WeaponData
        );
        PawnData::LocalVehicle = kernel->read_t<uint64_t>(
            PawnData::AcknowledgedPawn + offsets::CurrentVehicle
        );

        PawnData::TeamIndex = localPlayerState ?
            kernel->read_t<char>(localPlayerState + offsets::TeamIndex) : 0;
    }

    void ClearPawnData() {
        PawnData::Mesh = 0;
        PawnData::RootComponent = 0;
        PawnData::LocalWeapon = 0;
        PawnData::WeaponData = 0;
        PawnData::LocalVehicle = 0;
        PawnData::TeamIndex = 0;
    }
};



class PlayerProcessor {
public:
    bool ProcessPlayer(int index, uintptr_t playerArray, bool hasLocalPawn, PlayerCache& cache) {
        cache.playerState = kernel->read_t<uintptr_t>(
            playerArray + index * sizeof(uintptr_t)
        );
        if (!cache.playerState) return false;

        cache.pawn = kernel->read_t<uintptr_t>(
            cache.playerState + offsets::PawnPrivate
        );
        if (!cache.pawn) return false;

        if (hasLocalPawn && cache.pawn == PawnData::Player) return false;
        if (is_dead(cache.pawn)) return false;

        cache.mesh = kernel->read_t<uint64_t>(cache.pawn + offsets::Mesh);
        if (!cache.mesh) return false;

        cache.teamIndex = kernel->read_t<char>(
            cache.playerState + offsets::TeamIndex
        );

        bool isEnemy = !hasLocalPawn || cache.teamIndex != PawnData::TeamIndex;
        if (!isEnemy) return false;

        cache.bottomPos = GetBoneLocation(cache.mesh, 1);
        cache.headPos = GetBoneLocation(cache.mesh, 110);
        cache.distance = vCamera.Location.Distance(cache.bottomPos) / Constants::DISTANCE_SCALE;
        cache.isVisible = IsEnemyVisible(cache.mesh);
        cache.isValid = true;

        return true;
    }
};

class ESPRenderer {
public:
    void RenderPlayer(
        ImDrawList* draw,
        const PlayerCache& cache,
        const ScreenBounds& bounds,
        const ScreenInfo& screen
    ) {
        float* _color = cache.isVisible ?
            var->c_esp.box_vis_col : var->c_esp.box_invis_col;
		const ImColor color = ImColor(
			static_cast<int>(_color[0] * 255),
			static_cast<int>(_color[1] * 255),
			static_cast<int>(_color[2] * 255),
			static_cast<int>(_color[3] * 255)
		);

        float* _skeletonColor = cache.isVisible ?
            var->c_esp.skel_vis_col : var->c_esp.skel_invis_col;

		const ImColor skeletonColor = ImColor(
			static_cast<int>(_skeletonColor[0] * 255),
			static_cast<int>(_skeletonColor[1] * 255),
			static_cast<int>(_skeletonColor[2] * 255),
			static_cast<int>(_skeletonColor[3] * 255)
		);

        const ImColor whiteColor(255, 255, 255);

        if (var->c_esp.player_box) {
            DrawBox(
                bounds.left, bounds.top, bounds.width, bounds.height,
                color, 2.0f, BoxType(var->c_esp.box_type), settings::esp.boxOutlined
            );
        }

        if (var->c_esp.snaplines) {
            draw->AddLine(
                ImVec2(screen.centerX, screen.height),
                ImVec2(bounds.center.x, bounds.bottom),
                IM_COL32(255, 255, 255, 255), 1.0f
            );
        }

        if (var->c_esp.skeleton_esp) {
            RenderSkeleton(draw, cache.mesh, skeletonColor);
        }

        RenderText(cache, bounds, whiteColor);
    }

private:
    void RenderSkeleton(ImDrawList* draw, uintptr_t mesh, const ImColor& color) {
        vCamera = GetViewAngles();

        for (const auto& bonePair : boneConnections) {
            Vector3 bone1Loc = GetBoneLocation(mesh, bonePair.bone1);
            Vector3 bone2Loc = GetBoneLocation(mesh, bonePair.bone2);

            float boneLength = sqrt(
                pow(bone2Loc.x - bone1Loc.x, 2) +
                pow(bone2Loc.y - bone1Loc.y, 2) +
                pow(bone2Loc.z - bone1Loc.z, 2)
            );

            float maxLength = 100.0f;

            if ((bonePair.bone1 == 73 && bonePair.bone2 == 75) ||
                (bonePair.bone1 == 80 && bonePair.bone2 == 82)) {
                maxLength = 30.0f;
            }

            if (boneLength > maxLength) continue;

            auto mMatrix = to_matrix(vCamera.Rotation);
            auto XAxis = Vector3(mMatrix.m[0][0], mMatrix.m[0][1], mMatrix.m[0][2]);
            auto YAxis = Vector3(mMatrix.m[1][0], mMatrix.m[1][1], mMatrix.m[1][2]);
            auto ZAxis = Vector3(mMatrix.m[2][0], mMatrix.m[2][1], mMatrix.m[2][2]);

            auto delta1 = bone1Loc - vCamera.Location;
            auto delta2 = bone2Loc - vCamera.Location;

            float depth1 = delta1.Dot(XAxis);
            float depth2 = delta2.Dot(XAxis);

            if (depth1 < 1.0f || depth2 < 1.0f) continue;

            Vector2 wts1 = ProjectWorldToScreen(bone1Loc);
            Vector2 wts2 = ProjectWorldToScreen(bone2Loc);

            if (wts1.x < -Width || wts1.x > Width * 2) continue;
            if (wts1.y < -Height || wts1.y > Height * 2) continue;
            if (wts2.x < -Width || wts2.x > Width * 2) continue;
            if (wts2.y < -Height || wts2.y > Height * 2) continue;

            draw->AddLine(
                ImVec2(wts1.x, wts1.y),
                ImVec2(wts2.x, wts2.y),
                ImColor(0, 0, 0),
                3.0f
            );
            draw->AddLine(
                ImVec2(wts1.x, wts1.y),
                ImVec2(wts2.x, wts2.y),
                color,
                2.0f
            );
        }
    }
    void RenderText(const PlayerCache& cache, const ScreenBounds& bounds, const ImColor& whiteColor) {
        const float textSize = static_cast<float>(var->c_esp.text_size);
        float currentY = bounds.top - 18.0f;

        if (var->c_esp.username) {
            std::string username = GetPlayerName(cache.playerState);
            ImVec2 usernameSize = ImGui::CalcTextSize(username.c_str());
            ImVec2 pos(bounds.center.x - usernameSize.x * 0.5f, currentY);
            DrawTextOutlined(username.c_str(), pos, textSize, whiteColor);
            currentY += usernameSize.y + Constants::TEXT_SPACING;
        }

        if (var->c_esp.platform) {
            ImColor platformColor;
            std::string platformName = get_player_platform(cache.playerState, platformColor);
            ImVec2 platformSize = ImGui::CalcTextSize(platformName.c_str());
            ImVec2 pos(bounds.center.x - platformSize.x * 0.5f, currentY);
            DrawTextOutlined(platformName.c_str(), pos, textSize, platformColor);
        }

        float bottomY = bounds.bottom + Constants::TEXT_OFFSET;

        if (var->c_esp.rank) {
            uintptr_t habenaro = kernel->read_t<uintptr_t>(cache.playerState + offsets::HabaneroComponent);
            int32_t ranked_progress = kernel->read_t<int32_t>(habenaro + offsets::RankedProgress + 0x10);
            std::string rank_name = get_rank_name(ranked_progress);
            ImColor rank_color = get_rank_color(ranked_progress);

            ImVec2 text_size = ImGui::CalcTextSize(rank_name.c_str());
            ImVec2 pos(bounds.center.x - text_size.x * 0.5f, bottomY);
            DrawTextOutlined(rank_name.c_str(), pos, textSize, rank_color);
            bottomY += text_size.y + Constants::TEXT_SPACING;
        }

        if (var->c_esp.weapon) {
            std::string weaponName = GetWeaponName(cache.pawn);
            const char* displayName = weaponName.empty() ?
                skCrypt("No Item").decrypt() : weaponName.c_str();
            ImColor weaponColor = GetWeaponColor(static_cast<int32_t>(GetRarity(cache.pawn)));

            ImVec2 text_size = ImGui::CalcTextSize(displayName);
            ImVec2 pos(bounds.center.x - text_size.x * 0.5f, bottomY);
            DrawTextOutlined(displayName, pos, textSize, weaponColor);
            bottomY += text_size.y + Constants::TEXT_SPACING;
        }

        if (var->c_esp.distance) {
            char distBuffer[16];
            snprintf(distBuffer, sizeof(distBuffer), skCrypt("%dm").decrypt(),
                static_cast<int>(cache.distance));

            ImVec2 text_size = ImGui::CalcTextSize(distBuffer);
            ImVec2 pos(bounds.center.x - text_size.x * 0.5f, bottomY);
            DrawTextOutlined(distBuffer, pos, textSize, whiteColor);
        }
    }
};

class AimbotSystem {
public:
    void Update(
        const std::vector<PlayerCache>& players,
        const ScreenInfo& screen,
        bool hasLocalPawn
    ) {
        validTargets.clear();

        if (!settings::aimbot.enabled || !hasLocalPawn) return;

        CollectValidTargets(players, screen);
        ProcessBestTarget(screen);
    }

private:
    std::vector<TargetInfo> validTargets;

    void CollectValidTargets(const std::vector<PlayerCache>& players, const ScreenInfo& screen) {
        const float fovSize = settings::aimbot.fovsize;

        for (const auto& player : players) {
            if (!player.isValid) continue;

            Vector3 headPos = player.headPos;
            headPos.z += Constants::HEAD_OFFSET_Z;
            Vector2 head2D = ProjectWorldToScreen(headPos);

            if (head2D.x == 0.0f || head2D.y == 0.0f) continue;

            float dx = head2D.x - screen.centerX;
            float dy = head2D.y - screen.centerY;
            float crosshairDist = std::sqrt(dx * dx + dy * dy);

            if (crosshairDist <= fovSize) {
                validTargets.push_back({
                    player.mesh,
                    player.pawn,
                    head2D,
                    player.isVisible,
                    player.distance
                    });
            }
        }
    }

    void ProcessBestTarget(const ScreenInfo& screen) {
        if (validTargets.empty()) return;

        TargetInfo* bestTarget = SelectBestTarget(screen);
        if (!bestTarget || !bestTarget->mesh) return;

        Vector3 hitbox = CalculateHitbox(bestTarget);
        if (hitbox.x == 0.0f && hitbox.y == 0.0f && hitbox.z == 0.0f) return;

        if (settings::aimbot.prediction && PawnData::LocalWeapon) {
            hitbox = ApplyPrediction(bestTarget->pawn, hitbox);
        }

        Vector2 hitboxScreen = ProjectWorldToScreen(hitbox);
        if (hitboxScreen.x == 0.0f || hitboxScreen.y == 0.0f) return;

        if (ShouldAim(bestTarget->isVisible)) {
            ExecuteAim(bestTarget->mesh, hitboxScreen);
        }
    }

    TargetInfo* SelectBestTarget(const ScreenInfo& screen) {
        TargetInfo* bestTarget = nullptr;
        float bestValue = FLT_MAX;

        for (auto& target : validTargets) {
            float value = CalculateTargetValue(target, screen);
            if (value < bestValue) {
                bestTarget = &target;
                bestValue = value;
            }
        }

        return bestTarget;
    }

    float CalculateTargetValue(const TargetInfo& target, const ScreenInfo& screen) {
        if (settings::aimbot.targetPriority == 1) {
            return target.distance;
        }

        float dx = target.head2D.x - screen.centerX;
        float dy = target.head2D.y - screen.centerY;
        return std::sqrt(dx * dx + dy * dy);
    }

    Vector3 CalculateHitbox(TargetInfo* target) {
        int hitboxIndex = settings::aimbot.Hitbox;
        if (hitboxIndex < 0) hitboxIndex = 0;
        if (hitboxIndex >= static_cast<int>(Constants::HITBOX_BONES.size())) {
            hitboxIndex = static_cast<int>(Constants::HITBOX_BONES.size()) - 1;
        }

        int targetBone = Constants::HITBOX_BONES[hitboxIndex];
        Vector3 hitbox = GetBoneLocation(target->mesh, targetBone);

        if (hitbox.x == 0.0f && hitbox.y == 0.0f && hitbox.z == 0.0f) {
            hitbox = GetBoneLocation(target->mesh, Constants::FALLBACK_BONE);
        }

        return hitbox;
    }

    Vector3 ApplyPrediction(uintptr_t pawn, const Vector3& hitbox) {
        uintptr_t root = kernel->read_t<uint64_t>(pawn + offsets::RootComponent);
        if (!root) return hitbox;

        Vector3 velocity = kernel->read_t<Vector3>(root + offsets::ComponentVelocity);
        float distance = vCamera.Location.Distance(hitbox);
        float projectileSpeed = kernel->read_t<float>(
            PawnData::LocalWeapon + offsets::ProjectileSpeed
        );
        float projectileGravity = kernel->read_t<float>(
            PawnData::LocalWeapon + offsets::ProjectileGravity
        );

        if (projectileSpeed > 0.0f) {
            return PredictLocation(hitbox, velocity, projectileSpeed, projectileGravity, distance);
        }

        return hitbox;
    }

    bool ShouldAim(bool isVisible) {
        return (!settings::aimbot.vischeck || isVisible) &&
            (GetAsyncKeyState(settings::aimbot.aimkey) & 0x8000);
    }

    void ExecuteAim(uintptr_t mesh, const Vector2& hitboxScreen) {
        if (settings::aimbot.memoryaim) {
            memoryaim(mesh, hitboxScreen);
        }
    }
};
//
//void ProjectileTeleport() {
//    Vector3 closestPlayer = { DBL_MAX, DBL_MAX, DBL_MAX };
//    double closestDistance = DBL_MAX;
//    uintptr_t closestMesh = NULL;
//
//    uintptr_t gameState = kernel->read_t<uintptr_t>(PawnData::GWorld + offsets::GameState);
//    if (!gameState) return;
//
//    uintptr_t playerArray = kernel->read_t<uintptr_t>(gameState + offsets::PlayerArray);
//    int playerCount = kernel->read_t<int>(gameState + offsets::PlayerArray + sizeof(uintptr_t));
//
//    if (!playerArray || playerCount <= 0) return;
//
//    for (int i = 0; i < playerCount && i < Constants::MAX_PLAYERS; ++i) {
//        uintptr_t playerState = kernel->read_t<uintptr_t>(playerArray + i * sizeof(uintptr_t));
//        if (!playerState) continue;
//
//        uintptr_t pawn = kernel->read_t<uintptr_t>(playerState + offsets::PawnPrivate);
//        if (!pawn || pawn == PawnData::Player) continue;
//        if (is_dead(pawn)) continue;
//
//        uintptr_t mesh = kernel->read_t<uint64_t>(pawn + offsets::Mesh);
//        if (!mesh) continue;
//
//        char teamIndex = kernel->read_t<char>(playerState + offsets::TeamIndex);
//        if (PawnData::AcknowledgedPawn && teamIndex == PawnData::TeamIndex) continue;
//
//        auto bone_head = GetBoneLocation(mesh, 110);
//        auto screen_bone = ProjectWorldToScreen(bone_head);
//        double dx = screen_bone.x - (Width / 2.0);
//        double dy = screen_bone.y - (Height / 2.0);
//        float dist = sqrtf(dx * dx + dy * dy);
//
//        if (dist <= settings::aimbot.fov && dist < closestDistance) {
//            closestPlayer = bone_head;
//            closestDistance = dist;
//            closestMesh = mesh;
//        }
//    }
//
//    if (closestPlayer.x == DBL_MAX || closestMesh == NULL) return;
//
//    uintptr_t persistent_level = kernel->read_t<uintptr_t>(PawnData::GWorld + 0x40);
//    if (!persistent_level) return;
//
//    uintptr_t actors = kernel->read_t<uintptr_t>(persistent_level + 0x168);
//    if (!actors) return;
//
//    int actor_count = kernel->read_t<int>(persistent_level + 0x168 + sizeof(uintptr_t));
//    if (actor_count <= 0 || actor_count > 5000) return;
//
//    int projectilesTeleported = 0;
//    for (int i = 0; i < actor_count && i < 5000; ++i) {
//        uintptr_t actor = kernel->read_t<uintptr_t>(actors + (i * sizeof(uintptr_t)));
//        if (!actor) continue;
//
//        int name_index = kernel->read_t<int>(actor + 0x18);
//        std::string actorName = FName::ToString(name_index);
//        if (actorName.find("Bullet") == std::string::npos) continue;
//
//        uintptr_t root = kernel->read_t<uintptr_t>(actor + offsets::RootComponent);
//        if (!root) continue;
//
//        FTransform c2w = kernel->read_t<FTransform>(root + offsets::ComponentToWorld);
//        if (c2w.translation.x != closestPlayer.x) {
//            c2w.translation = closestPlayer;
//            kernel->write_t<FTransform>(root + offsets::ComponentToWorld, c2w);
//            kernel->write_t<Vector3>(root + offsets::RelativeLocation, closestPlayer);
//            projectilesTeleported++;
//        }
//    }
//}

class PlayerCacheManager {
private:
    std::vector<PlayerCache> cache_;
    std::atomic<bool> scanning_{ false };
    std::thread scan_thread_;
    std::atomic<bool> should_stop_{ false };

public:
    PlayerCacheManager() = default;

    ~PlayerCacheManager() {
        Stop();
    }

    void Start() {
        should_stop_ = false;
        scan_thread_ = std::thread(&PlayerCacheManager::ScanLoop, this);
    }

    void Stop() {
        should_stop_ = true;
        if (scan_thread_.joinable()) {
            scan_thread_.join();
        }
    }

    std::vector<PlayerCache> GetPlayers() {
        return cache_;
    }

    bool HasData() const {
        return !cache_.empty();
    }

private:
    void ScanLoop() {
        PlayerProcessor processor;
        LocalPlayerManager localPlayer;

        while (!should_stop_) {
            if (scanning_.exchange(true)) {
                std::this_thread::sleep_for(std::chrono::milliseconds(5));
                continue;
            }

            std::vector<PlayerCache> newPlayers;

            if (!localPlayer.Update()) {
                scanning_ = false;
                std::this_thread::sleep_for(std::chrono::milliseconds(16));
                continue;
            }

            bool hasLocalPawn = localPlayer.HasValidPawn();

            uintptr_t gameState = kernel->read_t<uintptr_t>(
                PawnData::GWorld + offsets::GameState
            );
            if (!gameState) {
                scanning_ = false;
                std::this_thread::sleep_for(std::chrono::milliseconds(16));
                continue;
            }

            uintptr_t playerArray = kernel->read_t<uintptr_t>(
                gameState + offsets::PlayerArray
            );
            int playerCount = kernel->read_t<int>(
                gameState + offsets::PlayerArray + sizeof(uintptr_t)
            );

            if (!playerArray || playerCount <= 0) {
                scanning_ = false;
                std::this_thread::sleep_for(std::chrono::milliseconds(16));
                continue;
            }

            size_t reserveSize = (playerCount < Constants::MAX_PLAYERS) ? playerCount : Constants::MAX_PLAYERS;
            newPlayers.reserve(reserveSize);

            for (int i = 0; i < playerCount && i < Constants::MAX_PLAYERS; ++i) {
                PlayerCache cache;
                if (processor.ProcessPlayer(i, playerArray, hasLocalPawn, cache)) {
                    newPlayers.push_back(cache);
                }
            }

            cache_ = std::move(newPlayers);
            scanning_ = false;

            std::this_thread::sleep_for(std::chrono::milliseconds(16));
        }
    }
};

inline PlayerCacheManager g_PlayerCache;

void ActorLoop() {
    static bool initialized = false;
    if (!initialized) {
        g_PlayerCache.Start();
        initialized = true;
    }

    static LocalPlayerManager localPlayer;
    static ESPRenderer espRenderer;
    static AimbotSystem aimbot;

    if (!localPlayer.Update()) return;
    bool hasLocalPawn = localPlayer.HasValidPawn();

    //if (settings::exploits.bulletp) {
    //    ProjectileTeleport();
    //}

    ImDrawList* draw = ImGui::GetForegroundDrawList();
    ScreenInfo screen;
    screen.Update();

    if (settings::aimbot.drawfov) {
        draw_fov_circle(
            settings::aimbot.fovsize,
            ImColor(255, 255, 255, 255),
            1.5f, 100
        );
    }

    std::vector<PlayerCache> playerCaches = g_PlayerCache.GetPlayers();

    for (const auto& cache : playerCaches) {
        if (!cache.isValid) continue;

        Vector3 headPos = cache.headPos;
        headPos.z += Constants::HEAD_OFFSET_Z;
        Vector2 head2D = ProjectWorldToScreen(headPos);
        Vector2 bottom2D = ProjectWorldToScreen(cache.bottomPos);

        float boxHeight = head2D.y - bottom2D.y;
        ScreenBounds bounds;
        bounds.Calculate(head2D, bottom2D, boxHeight);

        espRenderer.RenderPlayer(draw, cache, bounds, screen);
    }

    aimbot.Update(playerCaches, screen, hasLocalPawn);

    WorldLoop(draw);
}
