summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-27 12:54:29 +0100
committersalmonik <salmonik@tuta.io>2026-10-27 12:54:40 +0100
commita161f01105edef7f7723116a424777a3c42fb6b8 (patch)
treed05b52e1f3a29e8c20976edb6b2c5dd6148a56a0
parentDocs: add roadmap including distance weapon spread thesis (diff)
downloadhl2h-a161f01105edef7f7723116a424777a3c42fb6b8.tar.gz
hl2h-a161f01105edef7f7723116a424777a3c42fb6b8.zip
ESP: HUD speed graph (last 10 s)
- 0.1 s sampling, 100-sample ring; horizontal speed of the local player - small (240x64) panel centered in the lower half of the screen - min 0, max auto-scales to the maximum in the 10 s window; shows max value - ESP -> Show speed graph toggle, persisted as speedgraph=1
-rw-r--r--CONTEXT.md1
-rw-r--r--src/esp.cpp69
-rw-r--r--src/hl2h.h3
-rw-r--r--src/menu.cpp10
4 files changed, 78 insertions, 5 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 077c7c2..cd3811d 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -173,7 +173,6 @@ Binds use SDL scancodes; mouse buttons are pseudo-scancodes 600..604.
173 range, sorted by distance, only while the crowbar is held. 173 range, sorted by distance, only while the crowbar is held.
174- **Jumpbug**: automate the HL2 jumpbug movement glitch (trigger conditions 174- **Jumpbug**: automate the HL2 jumpbug movement glitch (trigger conditions
175 to be defined/tested). 175 to be defined/tested).
176- **Speed graph**: HUD plot of horizontal speed over time.
177- **GUI**: replace/augment the VGUI ISurface menu with an immediate-mode GUI 176- **GUI**: replace/augment the VGUI ISurface menu with an immediate-mode GUI
178 (Dear ImGui or a custom implementation); needs a design grill first. 177 (Dear ImGui or a custom implementation); needs a design grill first.
179- **Distance weapon spread thesis**: TBD — user will explain. Likely models 178- **Distance weapon spread thesis**: TBD — user will explain. Likely models
diff --git a/src/esp.cpp b/src/esp.cpp
index c0edd2e..78a357c 100644
--- a/src/esp.cpp
+++ b/src/esp.cpp
@@ -29,6 +29,12 @@ static int g_ntargets = 0;
29static int g_scr_w = 0, g_scr_h = 0; 29static int g_scr_w = 0, g_scr_h = 0;
30static bool g_esp_logged = false; 30static bool g_esp_logged = false;
31 31
32// Speed graph: 100 samples at 0.1 s = the last 10 seconds.
33static constexpr int SPEED_SAMPLES = 100;
34static float g_speed_samples[SPEED_SAMPLES] = {};
35static int g_speed_head = 0;
36static double g_speed_last_sample = 0.0;
37
32static double esp_now() { 38static double esp_now() {
33 timespec ts; 39 timespec ts;
34 clock_gettime(CLOCK_MONOTONIC, &ts); 40 clock_gettime(CLOCK_MONOTONIC, &ts);
@@ -332,6 +338,67 @@ static void draw_fov_circle() {
332 } 338 }
333} 339}
334 340
341// Speed graph: 100 samples at 0.1 s = the last 10 seconds; min is 0, max is
342// the window maximum. Small and centered in the lower half of the screen.
343static void speed_graph_sample() {
344 double now = esp_now();
345 if (now - g_speed_last_sample < 0.1)
346 return;
347 g_speed_last_sample = now;
348 float speed = 0.0f;
349 int local = engine_get_local_player();
350 if (local > 0 && g_net.velocity >= 0) {
351 void *ent = entitylist_get(local);
352 if (ent) {
353 const float *v = (const float *)((uintptr_t)ent + g_net.velocity);
354 speed = sqrtf(v[0] * v[0] + v[1] * v[1]);
355 }
356 }
357 g_speed_samples[g_speed_head] = speed;
358 g_speed_head = (g_speed_head + 1) % SPEED_SAMPLES;
359}
360
361static void draw_speed_graph() {
362 if (!esp_option(ESP_OPT_SHOW_SPEEDGRAPH))
363 return;
364 speed_graph_sample();
365
366 float maxv = 0.0f;
367 for (int i = 0; i < SPEED_SAMPLES; i++)
368 if (g_speed_samples[i] > maxv)
369 maxv = g_speed_samples[i];
370 float scale = maxv > 1.0f ? maxv : 1.0f;
371
372 const int w = 240, h = 64;
373 int x = (g_scr_w - w) / 2;
374 int y = (g_scr_h * 7) / 10 - h / 2;
375 if (x < 0) x = 0;
376 if (y < 0) y = 0;
377
378 surf_color(0, 0, 0, 120);
379 surf_rect(1, x, y, x + w, y + h);
380 surf_color(80, 80, 80, 180);
381 surf_rect(0, x, y, x + w, y + h);
382
383 surf_color(0, 255, 200, 220);
384 int px = 0, py = 0;
385 for (int i = 0; i < SPEED_SAMPLES; i++) {
386 int idx = (g_speed_head + i) % SPEED_SAMPLES; // oldest -> newest
387 float v = g_speed_samples[idx];
388 int sx = x + 1 + (i * (w - 2)) / (SPEED_SAMPLES - 1);
389 int sy = y + h - 2 - (int)((v / scale) * (float)(h - 4));
390 if (sy < y + 1) sy = y + 1;
391 if (i > 0)
392 surf_line(px, py, sx, sy);
393 px = sx;
394 py = sy;
395 }
396
397 char label[32];
398 snprintf(label, sizeof(label), "max %d", (int)maxv);
399 draw_text_col(x + 3, y + 2, label, 200, 200, 200);
400}
401
335// Draw the parent->child bone links. Bone snapshots are world-space matrices 402// Draw the parent->child bone links. Bone snapshots are world-space matrices
336// from SetupBones; project each bone once and reuse for both endpoints. 403// from SetupBones; project each bone once and reuse for both endpoints.
337static void draw_skeleton(const EspTarget &t) { 404static void draw_skeleton(const EspTarget &t) {
@@ -844,4 +911,6 @@ void esp_draw() {
844 } 911 }
845 } 912 }
846 } 913 }
914
915 draw_speed_graph();
847} 916}
diff --git a/src/hl2h.h b/src/hl2h.h
index 72cd21f..fdc88c1 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -161,7 +161,8 @@ enum EspOption {
161 ESP_OPT_SHOW_NAME = 3, 161 ESP_OPT_SHOW_NAME = 3,
162 ESP_OPT_SHOW_SKELETON = 4, 162 ESP_OPT_SHOW_SKELETON = 4,
163 ESP_OPT_SHOW_BOX = 5, 163 ESP_OPT_SHOW_BOX = 5,
164 ESP_OPT_COUNT = 6 164 ESP_OPT_SHOW_SPEEDGRAPH = 6,
165 ESP_OPT_COUNT = 7
165}; 166};
166 167
167// --------------------------------------------------------------------------- 168// ---------------------------------------------------------------------------
diff --git a/src/menu.cpp b/src/menu.cpp
index 49dd886..3e20cb3 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -87,7 +87,7 @@ static int g_silent_fov = 60;
87 87
88// ESP sub-options (menu "ESP" children). Defaults match the pre-option 88// ESP sub-options (menu "ESP" children). Defaults match the pre-option
89// behaviour: everything on. 89// behaviour: everything on.
90static bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true}; 90static bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true, true};
91 91
92// tree 92// tree
93static MenuNode g_root{NODE_ROOT, "HL2hake", false, 0, {}, nullptr, nullptr, nullptr}; 93static MenuNode g_root{NODE_ROOT, "HL2hake", false, 0, {}, nullptr, nullptr, nullptr};
@@ -105,6 +105,7 @@ static MenuNode g_esp_opt_nodes[ESP_OPT_COUNT] = {
105 {NODE_TOGGLE, "Show name", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_NAME], nullptr, nullptr}, 105 {NODE_TOGGLE, "Show name", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_NAME], nullptr, nullptr},
106 {NODE_TOGGLE, "Show skeleton", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_SKELETON], nullptr, nullptr}, 106 {NODE_TOGGLE, "Show skeleton", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_SKELETON], nullptr, nullptr},
107 {NODE_TOGGLE, "Show box", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_BOX], nullptr, nullptr}, 107 {NODE_TOGGLE, "Show box", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_BOX], nullptr, nullptr},
108 {NODE_TOGGLE, "Show speed graph", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH], nullptr, nullptr},
108}; 109};
109static MenuNode g_aimbot_feat{NODE_FEATURE, "Aimbot", false, 1, {}, nullptr, nullptr, nullptr}; 110static MenuNode g_aimbot_feat{NODE_FEATURE, "Aimbot", false, 1, {}, nullptr, nullptr, nullptr};
110static MenuNode g_aimbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_aimbot.active, nullptr, nullptr}; 111static MenuNode g_aimbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_aimbot.active, nullptr, nullptr};
@@ -308,11 +309,12 @@ static void config_save() {
308 fprintf(f, "[silentaim]\nactive=%d\nbind=%d\nfov=%d\n", g_silentaim.active, 309 fprintf(f, "[silentaim]\nactive=%d\nbind=%d\nfov=%d\n", g_silentaim.active,
309 g_silentaim.bind, g_silent_fov); 310 g_silentaim.bind, g_silent_fov);
310 fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" 311 fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n"
311 "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\n", 312 "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\nspeedgraph=%d\n",
312 g_esp.active, g_esp.bind, g_esp_opt[ESP_OPT_SHOW_NEUTRAL], 313 g_esp.active, g_esp.bind, g_esp_opt[ESP_OPT_SHOW_NEUTRAL],
313 g_esp_opt[ESP_OPT_SHOW_HEALTHBAR], 314 g_esp_opt[ESP_OPT_SHOW_HEALTHBAR],
314 g_esp_opt[ESP_OPT_SHOW_HEALTHNUM], g_esp_opt[ESP_OPT_SHOW_NAME], 315 g_esp_opt[ESP_OPT_SHOW_HEALTHNUM], g_esp_opt[ESP_OPT_SHOW_NAME],
315 g_esp_opt[ESP_OPT_SHOW_SKELETON], g_esp_opt[ESP_OPT_SHOW_BOX]); 316 g_esp_opt[ESP_OPT_SHOW_SKELETON], g_esp_opt[ESP_OPT_SHOW_BOX],
317 g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH]);
316 fclose(f); 318 fclose(f);
317} 319}
318 320
@@ -401,6 +403,8 @@ static void config_load() {
401 g_esp_opt[ESP_OPT_SHOW_SKELETON] = atoi(val) != 0; 403 g_esp_opt[ESP_OPT_SHOW_SKELETON] = atoi(val) != 0;
402 else if (strcmp(key, "box") == 0) 404 else if (strcmp(key, "box") == 0)
403 g_esp_opt[ESP_OPT_SHOW_BOX] = atoi(val) != 0; 405 g_esp_opt[ESP_OPT_SHOW_BOX] = atoi(val) != 0;
406 else if (strcmp(key, "speedgraph") == 0)
407 g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH] = atoi(val) != 0;
404 } 408 }
405 if (strcmp(key, "active") == 0) 409 if (strcmp(key, "active") == 0)
406 cur->active = atoi(val) != 0; 410 cur->active = atoi(val) != 0;