Начинающий
- Статус
- Оффлайн
- Регистрация
- 15 Окт 2019
- Сообщения
- 56
- Реакции
- 11
esp_preview.h:
#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:
#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);
}