summaryrefslogtreecommitdiffstats
path: root/src/esp.cpp
blob: 87d0b80a897b1bae48bf8dae84d1fbc3e1ee1fa4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// esp.cpp — ESP drawing + shared world_to_screen / entity gather (aimbot reuses these)
#include "hl2h.h"
#include <cmath>
#include <cstring>
#include <cstddef>

static constexpr float PI_F = 3.14159265358979323846f;

// ISurface slots (binary = SDK header + 4, disassembly-confirmed)
enum SurfSlot {
  SURF_DrawSetColor = 10, SURF_FilledRect = 12, SURF_OutlinedRect = 14,
  SURF_GetScreenSize = 38,
};

struct EspTarget {
  float sx, sy;       // screen pos of feet
  float h;            // box height px
  float health, maxhealth;
  int teamnum;
  char name[48];
};
static EspTarget g_targets[128];
static int g_ntargets = 0;
static int g_scr_w = 0, g_scr_h = 0;
static bool g_esp_logged = false;

bool world_to_screen(const Vector &w, float &sx, float &sy) {
  float pitch = g_cam_view.x * PI_F / 180.0f;
  float yaw = g_cam_view.y * PI_F / 180.0f;
  float sp = sinf(pitch), cp = cosf(pitch);
  float syy = sinf(yaw), cy = cosf(yaw);
  float fwd[3] = {cp * cy, cp * syy, -sp};
  float right[3] = {syy, -cy, 0};
  float up[3] = {sp * cy, sp * syy, cp};
  float dx = w.x - g_cam_eye.x, dy = w.y - g_cam_eye.y, dz = w.z - g_cam_eye.z;
  float z = dx * fwd[0] + dy * fwd[1] + dz * fwd[2];
  if (z < 1.0f) return false;
  float x = dx * right[0] + dy * right[1] + dz * right[2];
  float y = dx * up[0] + dy * up[1] + dz * up[2];
  float tanfov = tanf(75.0f * 0.5f * PI_F / 180.0f);
  sx = (float)g_scr_w * 0.5f + (x / z) * ((float)g_scr_w * 0.5f / tanfov);
  sy = (float)g_scr_h * 0.5f - (y / z) * ((float)g_scr_w * 0.5f / tanfov);
  return true;
}

static void esp_collect(int local_idx) {
  g_ntargets = 0;
  using Fn = int (*)(void *);
  int maxent = vfunc<Fn>(g_iface.entitylist, 5)(g_iface.entitylist); // GetHighestEntityIndex
  if (maxent < 0 || maxent > 512) maxent = 512;
  for (int idx = 0; idx <= maxent && g_ntargets < 128; idx++) {
    if (idx == local_idx) continue;
    void *ent = entitylist_get(idx);
    if (!ent) continue;
    uintptr_t e = (uintptr_t)ent;
    float hpv = (float)*(int *)(e + g_net.health);  // m_iHealth is int
    if (!(hpv > 0)) continue;
    Vector origin{*(float *)(e + g_net.origin), *(float *)(e + g_net.origin + 4),
                  *(float *)(e + g_net.origin + 8)};
    // ponytail: maxs netvar may be unresolved; fixed 72-unit torso fallback
    float maxsz = g_net.maxs >= 0 ? *(float *)(e + g_net.maxs) : 72.0f;
    Vector top{origin.x, origin.y, origin.z + maxsz};
    float sx, sy, tx, ty;
    if (!world_to_screen(origin, sx, sy) || !world_to_screen(top, tx, ty))
      continue;
    float h = sy - ty;
    if (h < 4.0f || sy < 0 || ty > (float)g_scr_h) continue;  // behind / offscreen
    EspTarget &t = g_targets[g_ntargets++];
    t.sx = sx; t.sy = sy; t.h = h;
    t.health = hpv;
    t.maxhealth = (float)*(int *)(e + g_net.maxhealth);
    if (t.maxhealth <= 0) t.maxhealth = hpv;
    // classname via IClientNetworkable::GetClientClass (slot 2) -> m_pNetworkName @ +8
    using CCFn = void *(*)(void *);
    void *cc = vfunc<CCFn>(ent, 2)(ent);
    const char *nn = cc ? *(const char **)((uintptr_t)cc + 8) : nullptr;
    strncpy(t.name, nn ? nn : "?", sizeof(t.name) - 1);
    t.name[sizeof(t.name) - 1] = 0;
  }
}

static void surf_rect(int filled, int x0, int y0, int x1, int y1) {
  using Fn = void (*)(void *, int, int, int, int);
  vfunc<Fn>(g_surface, filled ? SURF_FilledRect : SURF_OutlinedRect)(g_surface, x0, y0, x1, y1);
}
static void surf_color(int r, int g, int b, int a) {
  using Fn = void (*)(void *, int, int, int, int);
  vfunc<Fn>(g_surface, SURF_DrawSetColor)(g_surface, r, g, b, a);
}

void esp_draw() {
  if (!g_surface || !g_iface.entitylist || !g_net.esp_ok()) return;
  if (!g_scr_w) {
    using Fn = void (*)(void *, int &, int &);
    vfunc<Fn>(g_surface, SURF_GetScreenSize)(g_surface, g_scr_w, g_scr_h);
    logf("ESP: screen %dx%d", g_scr_w, g_scr_h);
    if (!g_scr_w) return;
  }
  esp_collect(engine_get_local_player());
  if (g_ntargets && !g_esp_logged) {
    g_esp_logged = true;
    logf("ESP: first draw, %d targets", g_ntargets);
  }
  for (int i = 0; i < g_ntargets; i++) {
    const EspTarget &t = g_targets[i];
    // team color: 3 combine red, 2 rebels green, else white
    if (t.teamnum == 3) surf_color(255, 60, 60, 255);
    else if (t.teamnum == 2) surf_color(60, 255, 60, 255);
    else surf_color(255, 255, 255, 255);
    float w = t.h * 0.45f;  // ponytail: constant aspect, no OBB width — swap for m_vecMins/maxs span later
    surf_rect(0, (int)(t.sx - w / 2), (int)(t.sy - t.h), (int)(t.sx + w / 2), (int)t.sy);
    // health bar under box
    float frac = t.health / t.maxhealth;
    if (frac > 1) frac = 1;
    surf_color(0, 0, 0, 200);
    surf_rect(1, (int)(t.sx - w / 2), (int)t.sy + 1, (int)(t.sx + w / 2), (int)t.sy + 4);
    if (frac > 0.5f) surf_color(40, 255, 40, 255);
    else if (frac > 0.25f) surf_color(255, 255, 0, 255);
    else surf_color(255, 40, 40, 255);
    surf_rect(1, (int)(t.sx - w / 2), (int)t.sy + 2,
              (int)(t.sx - w / 2 + w * frac), (int)t.sy + 3);
    draw_text_col((int)(t.sx - w / 2), (int)(t.sy - t.h) - 14, t.name, 255, 255, 255);
  }
}