Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Исходник EspPreview feature // supremacy c+p ready

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
15 Окт 2019
Сообщения
56
Реакции
11
esp_preview.h:
Expand Collapse Copy
#pragma once
class PreviewForm;

class EspPreview {
    friend class PreviewForm;

public:
    // lifecycle.
    void Init();
    void OnLevelInit();
    void OnLevelShutdown();

    // 3d model render. call from hooks::sceneend.
    void Render3D();

    // 2d esp overlay render. call from client::onpaint after the gui.
    void Render2D();

    // per-frame input + form positioning. call from client::onpaint before g_gui.think.
    void Think();

    // re-entry guard, used by chams::drawmodel to skip its own logic for our draw.
    __forceinline bool IsRunning() const {
        return m_running;
    }

    // rect publish from the form. read by render3d / render2d.
    __forceinline void SetRect(const Rect& r) {
        m_rect = r;
        m_has_rect = true;
    }

    // input from the form.
    void Rotate(float dyaw, float dpitch);
    void Zoom(float delta);

    // state setters drive the preview from real player state later if you want.
    __forceinline void SetHealth(int hp) {
        m_health = Clamp(hp, 0, 100);
    }
    __forceinline void SetArmor(int armor, bool helmet) {
        m_armor = armor;
        m_has_helmet = helmet;
    }
    __forceinline void SetWeapon(int def) {
        m_weapon_def = def;
    }
    __forceinline void SetName(const std::string& n) {
        m_name = n;
    }

private:
    void EnsureModel();
    void ComposeIdleBones();
    void UpdateCameraBasis();
    bool ProjectWorld(const vec3_t& world, vec2_t& screen);
    Rect ComputeBoneAabb();

    // synthetic-skeleton helpers (defined in esp_preview.cpp). these are used by the.
    // 2d esp so it works in main menu where the engine model loader returns null.
    vec3_t SynthBoneWorld(int idx) const;

    // 2d overlay primitives.
    void DrawSilhouette();
    void DrawBox();
    void DrawSkeleton();
    void DrawHealth();
    void DrawName();
    void DrawWeapon();
    void DrawFlags();

    // model state.
    const model_t* m_model = nullptr;
    studiohdr_t* m_hdr = nullptr;
    int m_model_index = -1;
    matrix3x4a_t m_bones[MAXSTUDIOBONES]{};
    bool m_bones_dirty = true;

    // pose state.
    ang_t m_eye_angles{};
    float m_lby = 0.f;
    vec3_t m_origin{0.f, 0.f, 0.f};
    int m_health = 100;
    int m_armor = 100;
    bool m_has_helmet = true;
    int m_weapon_def = 7 /* AK47 */;
    std::string m_name = "supremacy";

    // camera state — 110 units in front of the dummy on +x axis, looking back at it.
    // (yaw 180). updatecamerabasis runs on first render() call to fill basis vectors.
    // these defaults must produce a non-degenerate projection on default construction.
    float m_fov = 50.f;
    vec3_t m_cam_origin{90.f, 0.f, 35.f};
    ang_t m_cam_angles{0.f, 180.f, 0.f};
    vec3_t m_cam_fwd{-1.f, 0.f, 0.f};
    vec3_t m_cam_right{0.f, 1.f, 0.f};
    vec3_t m_cam_up{0.f, 0.f, 1.f};

    // rect published by previewform::draw. coords are screen-absolute.
    Rect m_rect{};
    bool m_has_rect = false;

    // rotate-on-drag state, driven by think().
    Point m_last_mouse{};
    bool m_dragging = false;

    // re-entry guard.
    bool m_running = false;
};

// ---------------------------------------------------------------------------.
// preview form: a thin draggable window with just background + border + the.
// preview content. no tabs, no elements. register it after the main menu form.
// ---------------------------------------------------------------------------.
class PreviewForm : public Form {
public:
    PreviewForm();

    // painted from gui::draw, virtual override.
    void draw() override;
};

extern EspPreview g_esp_preview;
extern PreviewForm g_preview_form;
esp_preview.cpp:
Expand Collapse Copy
#include "../../includes.h"
EspPreview g_esp_preview{};

namespace {
    // candidate model paths for the optional 3d render. tried in order; loading is.
    // best-effort and fails silently in main menu (no map context). the 2d esp uses.
    // the synthetic skeleton below regardless, so the feature works everywhere.
    constexpr const char* kModelPaths[] = {
        "models/player/custom_player/legacy/tm_phoenix.mdl",
        "models/player/custom_player/legacy/tm_leet_variantA.mdl",
        "models/player/custom_player/legacy/tm_balkan_variantA.mdl",
        "models/player/custom_player/legacy/tm_professional.mdl",
        "models/player/custom_player/legacy/ctm_st6.mdl",
        "models/player/custom_player/legacy/ctm_fbi.mdl",
    };

    // hardcoded humanoid skeleton used by the 2d esp. positions are in csgo player.
    // model space (z = up, x = forward, y = left). a real csgo player is ~72 units.
    // tall, eye height ~64, so these match real-game projection scales.
    // // indices are referenced by name in drawskeleton; do not reorder without updating.
    // the connection table below.
    enum SynthBone : int {
        SB_PELVIS = 0,
        SB_BELLY,
        SB_CHEST,
        SB_NECK,
        SB_HEAD,
        SB_L_SHOULDER,
        SB_L_ELBOW,
        SB_L_HAND,
        SB_R_SHOULDER,
        SB_R_ELBOW,
        SB_R_HAND,
        SB_L_HIP,
        SB_L_KNEE,
        SB_L_FOOT,
        SB_R_HIP,
        SB_R_KNEE,
        SB_R_FOOT,
        SB_COUNT,
    };

    // raw triples vec3_t's ctor isn't constexpr in this sdk, so we can't make.
    // an array of vec3_t constexpr. synthboneworld pulls these by index.
    constexpr float kSynthLocal[SB_COUNT][3] = {
        /* SB_PELVIS      */ {0.f, 0.f, 38.f},
        /* SB_BELLY       */ {0.f, 0.f, 48.f},
        /* SB_CHEST       */ {0.f, 0.f, 56.f},
        /* SB_NECK        */ {0.f, 0.f, 64.f},
        /* SB_HEAD        */ {0.f, 0.f, 70.f},
        /* SB_L_SHOULDER  */ {0.f, 7.f, 60.f},
        /* SB_L_ELBOW     */ {2.f, 11.f, 50.f},
        /* SB_L_HAND      */ {4.f, 13.f, 40.f},
        /* SB_R_SHOULDER  */ {0.f, -7.f, 60.f},
        /* SB_R_ELBOW     */ {2.f, -11.f, 50.f},
        /* SB_R_HAND      */ {4.f, -13.f, 40.f},
        /* SB_L_HIP       */ {0.f, 4.f, 38.f},
        /* SB_L_KNEE      */ {0.f, 5.f, 22.f},
        /* SB_L_FOOT      */ {0.f, 5.f, 3.f},
        /* SB_R_HIP       */ {0.f, -4.f, 38.f},
        /* SB_R_KNEE      */ {0.f, -5.f, 22.f},
        /* SB_R_FOOT      */ {0.f, -5.f, 3.f},
    };

    struct Bone2 {
        int a, b;
    };

    // limb segments for the filled silhouette. each entry has thicknesses at both.
    // endpoints (in screen pixels) so we can taper limbs naturally. drawn back-to-front.
    // using a simple depth sort.
    struct LimbSeg {
        int a, b;
        float thick_a, thick_b;
    };

    constexpr LimbSeg kLimbs[] = {
        // torso — thickest part of the figure.
        {SB_NECK, SB_CHEST, 4.5f, 7.f},
        {SB_CHEST, SB_BELLY, 7.f, 6.5f},
        {SB_BELLY, SB_PELVIS, 6.5f, 6.f},

        // left arm.
        {SB_CHEST, SB_L_SHOULDER, 6.f, 3.5f},
        {SB_L_SHOULDER, SB_L_ELBOW, 3.5f, 2.8f},
        {SB_L_ELBOW, SB_L_HAND, 2.8f, 2.5f},

        // right arm.
        {SB_CHEST, SB_R_SHOULDER, 6.f, 3.5f},
        {SB_R_SHOULDER, SB_R_ELBOW, 3.5f, 2.8f},
        {SB_R_ELBOW, SB_R_HAND, 2.8f, 2.5f},

        // left leg.
        {SB_PELVIS, SB_L_HIP, 5.f, 4.f},
        {SB_L_HIP, SB_L_KNEE, 4.f, 3.5f},
        {SB_L_KNEE, SB_L_FOOT, 3.5f, 3.f},

        // right leg.
        {SB_PELVIS, SB_R_HIP, 5.f, 4.f},
        {SB_R_HIP, SB_R_KNEE, 4.f, 3.5f},
        {SB_R_KNEE, SB_R_FOOT, 3.5f, 3.f},
    };

    // connectivity for skeleton lines.
    constexpr Bone2 kSynthEdges[] = {
        {SB_PELVIS, SB_BELLY},
        {SB_BELLY, SB_CHEST},
        {SB_CHEST, SB_NECK},
        {SB_NECK, SB_HEAD},
        {SB_CHEST, SB_L_SHOULDER},
        {SB_L_SHOULDER, SB_L_ELBOW},
        {SB_L_ELBOW, SB_L_HAND},
        {SB_CHEST, SB_R_SHOULDER},
        {SB_R_SHOULDER, SB_R_ELBOW},
        {SB_R_ELBOW, SB_R_HAND},
        {SB_PELVIS, SB_L_HIP},
        {SB_L_HIP, SB_L_KNEE},
        {SB_L_KNEE, SB_L_FOOT},
        {SB_PELVIS, SB_R_HIP},
        {SB_R_HIP, SB_R_KNEE},
        {SB_R_KNEE, SB_R_FOOT},
    };

    // synthetic world coordinates for the preview scene.
    // the dummy stands at the origin; we look at its mid-body (z ~ 35) from in front.
    // 90 units back with fov 50 makes the ~70-unit-tall figure fill most of the rect.
    constexpr float kCamDistance = 90.f;
    constexpr float kCamHeight = 35.f;

    // rotation / zoom feel.
    constexpr float kYawPerPixel = 0.4f;
    constexpr float kPitchPerPixel = 0.4f;
    constexpr float kFovMin = 25.f;
    constexpr float kFovMax = 90.f;
    constexpr float kFovDefault = 50.f;

    // lby desync clamp band, matches the resolver's intuition.
    constexpr float kLbyBand = 60.f;
}

// ---------------------------------------------------------------------------.
// lifecycle.
// ---------------------------------------------------------------------------.
void EspPreview::Init() {
    m_model = nullptr;
    m_hdr = nullptr;
    m_model_index = -1;
    m_bones_dirty = true;

    m_eye_angles = {};
    m_lby = 0.f;
    m_origin = vec3_t{0.f, 0.f, 0.f};
    m_health = 100;
    m_armor = 100;
    m_has_helmet = true;
    m_weapon_def = AK47;
    m_name = XOR("supremacy");

    m_fov = kFovDefault;
    // camera sits in front of the dummy (positive x) looking back at it (yaw 180).
    // player faces +x by default, so this gives a face-on view.
    m_cam_origin = vec3_t{kCamDistance, 0.f, kCamHeight};
    m_cam_angles = ang_t{0.f, 180.f, 0.f};
    UpdateCameraBasis();

    m_has_rect = false;
    m_running = false;
}

void EspPreview::OnLevelInit() {
    // model_t and studiohdr_t pointers do not survive level transitions.
    // drop them and re-fetch on next ensuremodel.
    m_model = nullptr;
    m_hdr = nullptr;
    m_model_index = -1;
    m_bones_dirty = true;
}

void EspPreview::OnLevelShutdown() {
    OnLevelInit();
}

// ---------------------------------------------------------------------------.
// model / bones.
// ---------------------------------------------------------------------------.
void EspPreview::EnsureModel() {
    if (m_hdr)
        return;

    if (!g_csgo.m_model_info)
        return;

    // helper — assign on first valid (model, hdr) pair.
    auto adopt = [this](const model_t* m, studiohdr_t* hdr, int idx) {
        m_model = m;
        m_hdr = hdr;
        m_model_index = idx;
        m_bones_dirty = true;
    };

    // pass 1: precached lookup via getmodelindex / getmodel. this is the path that.
    // features/skins/skins.h uses successfully — if a player model is precached for.
    // the current map, this finds it.
    for (const char* path : kModelPaths) {
        int idx = g_csgo.m_model_info->GetModelIndex(path);
        if (idx <= 0)
            continue;

        const model_t* m = g_csgo.m_model_info->GetModel(idx);
        if (!m)
            continue;

        studiohdr_t* hdr = g_csgo.m_model_info->GetStudioModel(m);
        if (!hdr || hdr->numbones <= 0)
            continue;

        adopt(m, hdr, idx);
        return;
    }

    // pass 2: force-load via findorloadmodel. works when the model isn't precached.
    // for the current level but is available on disk. fails in main menu state.
    for (const char* path : kModelPaths) {
        const model_t* m = g_csgo.m_model_info->FindOrLoadModel(path);
        if (!m)
            continue;

        studiohdr_t* hdr = g_csgo.m_model_info->GetStudioModel(m);
        if (!hdr || hdr->numbones <= 0)
            continue;

        adopt(m, hdr, g_csgo.m_model_info->GetModelIndex(path));
        return;
    }
}

void EspPreview::ComposeIdleBones() {
    if (!m_hdr)
        return;

    // root transform yaw by lby, position at dummy origin.
    matrix3x4_t root;
    math::AngleMatrix(ang_t{0.f, m_lby, 0.f}, m_origin, root);

    const int count = Min(m_hdr->numbones, static_cast<int>(MAXSTUDIOBONES));
    for (int i = 0; i < count; ++i) {
        const auto* b = m_hdr->pBone(i);
        if (!b)
            continue;

        // local bone transform bind pose from the studiohdr.
        matrix3x4_t local;
        QuaternionMatrix(b->quat, b->pos, local);

        if (b->parent == -1 || b->parent >= count)
            math::ConcatTransforms(root, local, m_bones[i]);
        else
            math::ConcatTransforms(m_bones[b->parent], local, m_bones[i]);
    }

    m_bones_dirty = false;
}

void EspPreview::UpdateCameraBasis() {
    math::AngleVectors(m_cam_angles, &m_cam_fwd, &m_cam_right, &m_cam_up);
}

// ---------------------------------------------------------------------------.
// projection analytical (no engine matrix needed).
// ---------------------------------------------------------------------------.
bool EspPreview::ProjectWorld(const vec3_t& world, vec2_t& screen) {
    if (!m_has_rect)
        return false;

    // transform world -> view space.
    vec3_t delta = world - m_cam_origin;
    float fwd = delta.dot(m_cam_fwd);
    if (fwd < 0.001f)
        return false;

    float right = delta.dot(m_cam_right);
    float up = delta.dot(m_cam_up);

    const float aspect = float(m_rect.w) / float(m_rect.h);
    const float half_tan = std::tan(DEG2RAD(m_fov) * 0.5f);
    if (half_tan < 0.0001f)
        return false;

    // normalized device coords [-1, 1].
    float ndc_x = (right / fwd) / (half_tan * aspect);
    float ndc_y = (up / fwd) / half_tan;

    // pixel coords inside the widget rect.
    screen.x = m_rect.x + (m_rect.w * 0.5f) + (ndc_x * m_rect.w * 0.5f);
    screen.y = m_rect.y + (m_rect.h * 0.5f) - (ndc_y * m_rect.h * 0.5f);
    return true;
}

// ---------------------------------------------------------------------------.
// input.
// ---------------------------------------------------------------------------.
void EspPreview::Rotate(float dyaw, float dpitch) {
    // camera orbit: accumulate angles, then re-place camera on a sphere around the.
    // dummy's chest. the dummy itself doesn't rotate so the user actually sees the.
    // preview turn relative to the camera.
    // yaw only. dpitch ignored so the figure can only spin sideways.
    (void)dpitch;
    m_cam_angles.y = math::NormalizedAngle(m_cam_angles.y + dyaw);
    m_cam_angles.x = 0.f;
    m_cam_angles.z = 0.f;

    vec3_t fwd;
    math::AngleVectors(m_cam_angles, &fwd);

    const vec3_t look_at = m_origin + vec3_t{0.f, 0.f, kCamHeight};
    m_cam_origin = look_at - fwd * kCamDistance;

    UpdateCameraBasis();
}

void EspPreview::Zoom(float delta) {
    m_fov = Clamp(m_fov - delta, kFovMin, kFovMax);
}

// ---------------------------------------------------------------------------.
// ---------------------------------------------------------------------------.
void EspPreview::Render3D() {
}

// ---------------------------------------------------------------------------.
// 2d render runs inside onpaint after the gui.
// ---------------------------------------------------------------------------.
void EspPreview::Render2D() {
    if (!g_menu.main.players.preview.get())
        return;

    if (!m_has_rect)
        return;

    // 2d esp uses the synthetic skeleton from ksynthlocal no engine model required.
    // the 3d model is opportunistic and gated separately inside render3d.
    UpdateCameraBasis();

    // clip every primitive to the preview rect so we never bleed onto the menu chrome.
    int cx0, cy0, cx1, cy1;
    g_csgo.m_surface->GetClipRect(cx0, cy0, cx1, cy1);
    g_csgo.m_surface->SetClipRect(m_rect.x, m_rect.y, m_rect.x + m_rect.w, m_rect.y + m_rect.h);

    // drive overlays from the existing enemy esp toggles, so the preview reflects.
    // exactly what enemies will look like in-game. multidropdown::get(0) is the.
    // "enemy" item for these (see visuals::drawplayer in features/visuals/visuals.cpp).
    auto& p = g_menu.main.players;

    // filled body silhouette goes first so esp overlays render on top.
    // tinted with chams color if chams enemy is enabled, otherwise a neutral grey.
    DrawSilhouette();

    if (p.box.get(0))
        DrawBox();
    if (p.skeleton.get(0))
        DrawSkeleton();
    if (p.health.get(0))
        DrawHealth();
    if (p.name.get(0))
        DrawName();
    if (p.weapon.get(0))
        DrawWeapon();
    if (!p.flags_enemy.GetActiveIndices().empty())
        DrawFlags();

    g_csgo.m_surface->SetClipRect(cx0, cy0, cx1, cy1);
}

// ---------------------------------------------------------------------------.
// 2d helpers.
// ---------------------------------------------------------------------------.
vec3_t EspPreview::SynthBoneWorld(int idx) const {
    const float lx = kSynthLocal[idx][0];
    const float ly = kSynthLocal[idx][1];
    const float lz = kSynthLocal[idx][2];

    // rotate local position by m_lby around z-axis, then translate to dummy origin.
    const float yaw = DEG2RAD(m_lby);
    const float c = std::cos(yaw);
    const float s = std::sin(yaw);

    return {
        m_origin.x + (lx * c - ly * s),
        m_origin.y + (lx * s + ly * c),
        m_origin.z + lz,
    };
}

Rect EspPreview::ComputeBoneAabb() {
    Rect out{0, 0, 0, 0};

    float lo_x = FLT_MAX, lo_y = FLT_MAX;
    float hi_x = -FLT_MAX, hi_y = -FLT_MAX;
    bool any = false;

    for (int i = 0; i < SB_COUNT; ++i) {
        vec2_t s;
        if (!ProjectWorld(SynthBoneWorld(i), s))
            continue;

        lo_x = Min(lo_x, s.x);
        lo_y = Min(lo_y, s.y);
        hi_x = Max(hi_x, s.x);
        hi_y = Max(hi_y, s.y);
        any = true;
    }

    if (!any)
        return out;

    out.x = static_cast<int>(lo_x);
    out.y = static_cast<int>(lo_y);
    out.w = static_cast<int>(hi_x - lo_x);
    out.h = static_cast<int>(hi_y - lo_y);
    return out;
}

// ---------------------------------------------------------------------------.
// filled body silhouette — draws each limb as a tapered quad, head as a circle.
// limbs are sorted back-to-front by depth so the closer side overlaps the farther.
// side correctly. tint by chams color when enemy chams is active, otherwise grey.
// ---------------------------------------------------------------------------.
void EspPreview::DrawSilhouette() {
    // pick a color. chams enemy color if user has it on, else neutral grey-blue.
    Color tint = {120, 130, 145, 230};
    if (g_menu.main.players.chams_enemy.get(0)) {
        tint = g_menu.main.players.chams_enemy_vis.get();
        tint.a() = 230;
    }

    // sort limbs by midpoint depth (distance to camera) so far limbs draw first.
    struct DrawOrder {
        int idx;
        float depth;
    };
    std::array<DrawOrder, sizeof(kLimbs) / sizeof(LimbSeg)> order{};
    for (size_t i = 0; i < order.size(); ++i) {
        const auto& l = kLimbs[i];
        vec3_t a = SynthBoneWorld(l.a);
        vec3_t b = SynthBoneWorld(l.b);
        vec3_t mid = (a + b) * 0.5f;
        vec3_t to_cam = mid - m_cam_origin;
        order[i].idx = static_cast<int>(i);
        order[i].depth = to_cam.dot(m_cam_fwd); // distance along view fwd.
    }
    std::sort(order.begin(), order.end(), [](const DrawOrder& x, const DrawOrder& y) { return x.depth > y.depth; });

    // draw each limb as a tapered filled quad.
    for (const auto& o : order) {
        const auto& l = kLimbs[o.idx];

        vec2_t a, b;
        if (!ProjectWorld(SynthBoneWorld(l.a), a))
            continue;
        if (!ProjectWorld(SynthBoneWorld(l.b), b))
            continue;

        // perpendicular unit vector in screen space.
        float dx = b.x - a.x, dy = b.y - a.y;
        float len = std::sqrt(dx * dx + dy * dy);
        if (len < 0.01f)
            continue;

        float px = -dy / len;
        float py = dx / len;

        // build the trapezoid. fall back to parallel lines (which are known to work).
        // rather than drawtexturedpolygon, which produced empty polygons even with.
        // proper texture binding. each limb gets a stack of lines offset by the.
        // perpendicular, interpolating the thickness from thick_a at a to thick_b at b.
        int steps = static_cast<int>(std::ceil(Max(l.thick_a, l.thick_b))) * 2;
        for (int s = -steps; s <= steps; ++s) {
            float f = static_cast<float>(s) / static_cast<float>(steps); // -1 .. +1.
            float off_a = f * l.thick_a;
            float off_b = f * l.thick_b;

            int x0 = static_cast<int>(a.x + px * off_a);
            int y0 = static_cast<int>(a.y + py * off_a);
            int x1 = static_cast<int>(b.x + px * off_b);
            int y1 = static_cast<int>(b.y + py * off_b);
            render::line(x0, y0, x1, y1, tint);
        }
    }

    // head — filled circle.
    vec2_t head_screen;
    if (ProjectWorld(SynthBoneWorld(SB_HEAD), head_screen)) {
        // scale circle radius with camera distance so it stays proportional under zoom.
        vec3_t head_w = SynthBoneWorld(SB_HEAD);
        float depth = (head_w - m_cam_origin).dot(m_cam_fwd);
        float radius = depth > 0.01f ? (700.f / depth) : 8.f;
        radius = Clamp(radius, 4.f, 14.f);
        render::circle(static_cast<int>(head_screen.x),
                       static_cast<int>(head_screen.y),
                       static_cast<int>(radius),
                       16,
                       tint);
    }
}

void EspPreview::DrawBox() {
    Rect box = ComputeBoneAabb();
    if (box.w <= 0 || box.h <= 0)
        return;

    Color clr = g_menu.main.players.box_enemy.get();
    clr.a() = 255;
    render::rect_outlined(box.x, box.y, box.w, box.h, clr, {10, 10, 10, 200});
}

void EspPreview::DrawSkeleton() {
    Color clr = g_menu.main.players.skeleton_enemy.get();
    clr.a() = 255;

    for (const auto& e : kSynthEdges) {
        vec2_t a, b;
        if (!ProjectWorld(SynthBoneWorld(e.a), a))
            continue;
        if (!ProjectWorld(SynthBoneWorld(e.b), b))
            continue;

        render::line(static_cast<int>(a.x), static_cast<int>(a.y), static_cast<int>(b.x), static_cast<int>(b.y), clr);
    }
}

void EspPreview::DrawHealth() {
    Rect box = ComputeBoneAabb();
    if (box.w <= 0 || box.h <= 0)
        return;

    int hp = Clamp(m_health, 0, 100);
    int r = Min((510 * (100 - hp)) / 100, 255);
    int g = Min((510 * hp) / 100, 255);

    int h = box.h - 2;
    int fill = static_cast<int>(std::round(hp * h / 100.f));

    render::rect_filled(box.x - 6, box.y, 4, h + 2, {10, 10, 10, 200});
    render::rect(box.x - 5, box.y + 1 + (h - fill), 2, fill, {r, g, 0, 255});

    if (hp < 100)
        render::esp_small.string(box.x - 5, box.y + 1 + (h - fill) - 5, {255, 255, 255, 200}, std::to_string(hp), render::ALIGN_CENTER);
}

void EspPreview::DrawName() {
    Rect box = ComputeBoneAabb();
    if (box.w <= 0 || box.h <= 0)
        return;

    Color clr = g_menu.main.players.name_color.get();
    clr.a() = 200;

    render::esp.string(box.x + box.w / 2,
                       box.y - render::esp.m_size.m_height,
                       clr,
                       m_name,
                       render::ALIGN_CENTER);
}

void EspPreview::DrawWeapon() {
    Rect box = ComputeBoneAabb();
    if (box.w <= 0 || box.h <= 0)
        return;

    const Color clr{255, 255, 255, 200};

    // mirror visuals::drawplayer — weapon_mode 0 = text, 1 = icon.
    if (g_menu.main.players.weapon_mode.get() == 0) {
        // preview has no real weapon entity to call getlocalizedname on, so use a.
        // small lookup keyed on the same m_weapon_def we feed the icon path. add.
        // entries here if you want more weapons to display by name.
        const char* name = nullptr;
        switch (m_weapon_def) {
            case DEAGLE:
                name = "DESERT EAGLE";
                break;
            case ELITE:
                name = "DUAL BERETTAS";
                break;
            case FIVESEVEN:
                name = "FIVE-SEVEN";
                break;
            case GLOCK:
                name = "GLOCK-18";
                break;
            case AK47:
                name = "AK-47";
                break;
            case AUG:
                name = "AUG";
                break;
            case AWP:
                name = "AWP";
                break;
            case FAMAS:
                name = "FAMAS";
                break;
            case G3SG1:
                name = "G3SG1";
                break;
            case GALIL:
                name = "GALIL AR";
                break;
            case M249:
                name = "M249";
                break;
            case M4A4:
                name = "M4A4";
                break;
            case MAC10:
                name = "MAC-10";
                break;
            case P90:
                name = "P90";
                break;
            case UMP45:
                name = "UMP-45";
                break;
            case XM1014:
                name = "XM1014";
                break;
            case BIZON:
                name = "PP-BIZON";
                break;
            case MAG7:
                name = "MAG-7";
                break;
            case NEGEV:
                name = "NEGEV";
                break;
            case SAWEDOFF:
                name = "SAWED-OFF";
                break;
            case TEC9:
                name = "TEC-9";
                break;
            case ZEUS:
                name = "ZEUS X27";
                break;
            case P2000:
                name = "P2000";
                break;
            case MP7:
                name = "MP7";
                break;
            case MP9:
                name = "MP9";
                break;
            case NOVA:
                name = "NOVA";
                break;
            case P250:
                name = "P250";
                break;
            case SCAR20:
                name = "SCAR-20";
                break;
            case SG553:
                name = "SG 553";
                break;
            case SSG08:
                name = "SSG 08";
                break;
            case M4A1S:
                name = "M4A1-S";
                break;
            case USPS:
                name = "USP-S";
                break;
            case CZ75A:
                name = "CZ75-AUTO";
                break;
            case REVOLVER:
                name = "R8 REVOLVER";
                break;
            default:
                name = "WEAPON";
                break;
        }
        render::esp_small.string(box.x + box.w / 2, box.y + box.h, clr, name, render::ALIGN_CENTER);
    } else {
        auto it = g_visuals.m_weapon_icons.find(m_weapon_def);
        if (it == g_visuals.m_weapon_icons.end())
            return;

        // icons are tall — move them up like visuals::drawplayer does (offset -= 5).
        std::string icon = tfm::format(XOR("%c"), it->second);
        render::cs.string(box.x + box.w / 2, box.y + box.h - 5, clr, icon, render::ALIGN_CENTER);
    }
}

void EspPreview::DrawFlags() {
    Rect box = ComputeBoneAabb();
    if (box.w <= 0 || box.h <= 0)
        return;

    // mirror visuals::drawplayer's flag list, but driven by representative preview state.
    // rather than a real player entity. order/indices match the flags_enemy dropdown:.
    // 0: money, 1: armor, 2: scoped, 3: flashed, 4: reloading, 5: bomb.
    std::vector<std::pair<std::string, Color>> flags;
    const auto active = g_menu.main.players.flags_enemy.GetActiveIndices();

    auto has = [&](int idx) {
        for (int v : active)
            if (v == idx)
                return true;
        return false;
    };

    if (has(0))
        flags.push_back({XOR("$16000"), {150, 200, 60, 200}});

    if (has(1)) {
        if (m_has_helmet && m_armor > 0)
            flags.push_back({XOR("HK"), {255, 255, 255, 200}});
        else if (m_has_helmet)
            flags.push_back({XOR("H"), {255, 255, 255, 200}});
        else if (m_armor > 0)
            flags.push_back({XOR("K"), {255, 255, 255, 200}});
    }

    if (has(2))
        flags.push_back({XOR("ZOOM"), {60, 180, 225, 200}});

    if (has(3))
        flags.push_back({XOR("FLASHED"), {255, 255, 0, 200}});

    if (has(4))
        flags.push_back({XOR("RELOAD"), {60, 180, 225, 200}});

    if (has(5))
        flags.push_back({XOR("BOMB"), {255, 0, 0, 200}});

    for (size_t i = 0; i < flags.size(); ++i) {
        const auto& f = flags[i];
        int offset = static_cast<int>(i) * (render::esp_small.m_size.m_height - 1);
        render::esp_small.string(box.x + box.w + 2, box.y + offset, f.second, f.first);
    }
}

// ---------------------------------------------------------------------------.
// per-frame input + form sync.
// call from client::onpaint *before* g_gui.think() so we observe the press.
// before the gui consumes it for form dragging.
// ---------------------------------------------------------------------------.
void EspPreview::Think() {
    // the preview form is visible only when the main menu is open and the checkbox is on.
    // closing the menu (vk_insert) collapses the preview along with it.
    g_preview_form.m_open = g_menu.main.m_open && g_menu.main.players.preview.get();

    // once the form has fully faded out, drop the published rect so render3d /.
    // render2d don't keep drawing off-screen on the next-but-one frame.
    if (!g_preview_form.m_open && g_preview_form.m_alpha == 0) {
        m_has_rect = false;
        m_dragging = false;
        return;
    }

    // the form's draw publishes the inner content rect via setrect each frame.
    // without a rect, no input handling.
    if (!m_has_rect)
        return;

    // drag-to-rotate inside the preview rect. left mouse button press starts the drag.
    // the gui's form-drag activates only outside the client rect, so these don't fight.
    bool pressed = g_input.GetKeyPress(VK_LBUTTON);
    bool down = g_input.GetKeyState(VK_LBUTTON);

    if (pressed && g_input.IsCursorInRect(m_rect))
        m_dragging = true;

    if (m_dragging && !down)
        m_dragging = false;

    if (m_dragging) {
        int dx = g_input.m_mouse.x - m_last_mouse.x;
        int dy = g_input.m_mouse.y - m_last_mouse.y;
        if (dx || dy)
            Rotate(dx * kYawPerPixel, dy * kPitchPerPixel);
    }

    m_last_mouse = g_input.m_mouse;
}

// ---------------------------------------------------------------------------.
// preview form a draggable window with just a background and the preview.
// content. no tabs, no elements. registered with g_gui after the main form.
// ---------------------------------------------------------------------------.
PreviewForm g_preview_form{};

PreviewForm::PreviewForm() {
    // default placement. menu::init repositions us next to the main form on first.
    // frame; after that the user can drag this form independently.
    SetPosition(700, 50);
    SetSize(210, 250);
}

void PreviewForm::draw() {
    // closed -> tick the opacity down for a fade-out, then bail.
    // open  -> tick it up.
    constexpr float frequency = 1.f / 0.5f;
    float step = frequency * g_csgo.m_globals->m_frametime;
    m_opacity = m_open ? Clamp(m_opacity + step, 0.f, 1.f)
                       : Clamp(m_opacity - step, 0.f, 1.f);
    m_alpha = static_cast<int>(0xff * m_opacity);

    if (!m_alpha)
        return;

    Rect r = GetFormRect();

    // background — same palette as form::draw_background, but we paint it ourselves.
    // because that helper is private.
    render::rect_filled(r.x, r.y, r.w, r.h, {12, 12, 12, m_alpha});

    // 4-layer border (one thinner than mainform). drops one of the duplicate dark.
    // rings — keeps the black outline + light + dark + light pattern.
    const Color border_colors[4] = {
        {5, 5, 5, m_alpha},
        {60, 60, 60, m_alpha},
        {40, 40, 40, m_alpha},
        {60, 60, 60, m_alpha},
    };
    for (int i = 0; i < 4; ++i)
        render::rect(r.x + i, r.y + i, r.w - 2 * i, r.h - 2 * i, border_colors[i]);

    // title strip.
    render::menu_shade.string(r.x + 10, r.y + 5, {180, 180, 180, m_alpha}, XOR("esp preview"));
    render::rect_filled(r.x + 6, r.y + 19, r.w - 12, 1, {40, 40, 40, m_alpha});

    // inner content rect — leave a 6-px border for dragging, plus 16-px title strip.
    Rect inner{r.x + 6, r.y + 22, r.w - 12, r.h - 28};
    render::rect_filled(inner.x, inner.y, inner.w, inner.h, {17, 17, 17, m_alpha});

    // publish to the preview state so render3d / render2d know where to draw.
    g_esp_preview.SetRect(inner);
}
 
Назад
Сверху Снизу