diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-26 17:16:35 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-26 17:16:35 +0100 |
| commit | b37e1f6892895e438aa7974e1144a6269d94e4aa (patch) | |
| tree | 1baf0285bb7d914a4d257169cf4a2de87a8db04e | |
| parent | ESP: add Show box toggle (diff) | |
| download | hl2h-b37e1f6892895e438aa7974e1144a6269d94e4aa.tar.gz hl2h-b37e1f6892895e438aa7974e1144a6269d94e4aa.zip | |
Aimbot v3: world-only vischeck
- resolve EngineTraceClient003; TraceRay (slot 4) from the eye to the exact
selected hitbox/bone point with MASK_VISIBLE
- TRACE_WORLD_ONLY filter as requested (static props are not tested, per SDK)
- Aimbot -> Vischeck toggle, persisted as vischeck=1; when on, blocked targets
are skipped and the next-nearest candidate is tried
- target candidates are ranked by angle; on each target the nearest visible
selected hitbox point wins
| -rwxr-xr-x | build.sh | 2 | ||||
| -rw-r--r-- | src/esp.cpp | 96 | ||||
| -rw-r--r-- | src/hl2h.cpp | 6 | ||||
| -rw-r--r-- | src/hl2h.h | 3 | ||||
| -rw-r--r-- | src/menu.cpp | 16 | ||||
| -rw-r--r-- | src/vischeck.cpp | 98 |
6 files changed, 179 insertions, 42 deletions
| @@ -3,6 +3,6 @@ set -e | |||
| 3 | cd "$(dirname "$0")" | 3 | cd "$(dirname "$0")" |
| 4 | g++ -m32 -O2 -std=c++17 -Wall -Wextra -Werror -fPIC -shared -fvisibility=hidden \ | 4 | g++ -m32 -O2 -std=c++17 -Wall -Wextra -Werror -fPIC -shared -fvisibility=hidden \ |
| 5 | -o libhl2hake.so \ | 5 | -o libhl2hake.so \ |
| 6 | src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp src/esp.cpp src/server.cpp src/bones.cpp \ | 6 | src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp src/esp.cpp src/server.cpp src/bones.cpp src/vischeck.cpp \ |
| 7 | -ldl | 7 | -ldl |
| 8 | file libhl2hake.so | 8 | file libhl2hake.so |
diff --git a/src/esp.cpp b/src/esp.cpp index 57f67ae..2c033ea 100644 --- a/src/esp.cpp +++ b/src/esp.cpp | |||
| @@ -358,9 +358,13 @@ bool aimbot_apply(CUserCmd *cmd) { | |||
| 358 | hi = 4096; | 358 | hi = 4096; |
| 359 | 359 | ||
| 360 | constexpr float MAX_FOV = 60.0f; // only things near the crosshair | 360 | constexpr float MAX_FOV = 60.0f; // only things near the crosshair |
| 361 | float best_fov = MAX_FOV; | 361 | struct Candidate { |
| 362 | int best_idx = -1; | 362 | int idx; |
| 363 | Vector best_head{}; | 363 | float fov; |
| 364 | Vector approx; | ||
| 365 | }; | ||
| 366 | Candidate cands[16]; | ||
| 367 | int ncands = 0; | ||
| 364 | 368 | ||
| 365 | for (int idx = 1; idx <= hi; idx++) { | 369 | for (int idx = 1; idx <= hi; idx++) { |
| 366 | if (idx == local) | 370 | if (idx == local) |
| @@ -394,14 +398,22 @@ bool aimbot_apply(CUserCmd *cmd) { | |||
| 394 | Vector approx{origin.x, origin.y, | 398 | Vector approx{origin.x, origin.y, |
| 395 | origin.z + maxz * ((bone_mask & (1u << 1)) ? 0.9f : 0.55f)}; | 399 | origin.z + maxz * ((bone_mask & (1u << 1)) ? 0.9f : 0.55f)}; |
| 396 | float fov = getfov(view, angle_to(g_cam_eye, approx)); | 400 | float fov = getfov(view, angle_to(g_cam_eye, approx)); |
| 397 | if (fov >= best_fov) | 401 | if (fov >= MAX_FOV) |
| 398 | continue; | 402 | continue; |
| 399 | best_fov = fov; | 403 | |
| 400 | best_idx = idx; | 404 | // keep candidates sorted by approximate angle, nearest first |
| 401 | best_head = approx; | 405 | int pos = ncands; |
| 406 | while (pos > 0 && cands[pos - 1].fov > fov) | ||
| 407 | pos--; | ||
| 408 | if (ncands < 16) | ||
| 409 | ncands++; | ||
| 410 | for (int j = ncands - 1; j > pos; j--) | ||
| 411 | cands[j] = cands[j - 1]; | ||
| 412 | if (pos < 16) | ||
| 413 | cands[pos] = {idx, fov, approx}; | ||
| 402 | } | 414 | } |
| 403 | 415 | ||
| 404 | if (best_idx < 0) { | 416 | if (ncands == 0) { |
| 405 | static double last_none = 0.0; | 417 | static double last_none = 0.0; |
| 406 | double now = esp_now(); | 418 | double now = esp_now(); |
| 407 | if (now - last_none >= 2.0) { | 419 | if (now - last_none >= 2.0) { |
| @@ -411,43 +423,57 @@ bool aimbot_apply(CUserCmd *cmd) { | |||
| 411 | return false; | 423 | return false; |
| 412 | } | 424 | } |
| 413 | 425 | ||
| 414 | static int sel_logs = 0; | 426 | // Walk targets nearest-first. On each, pick the nearest selected hitbox |
| 415 | if (sel_logs < 5) { | 427 | // point that passes vischeck; skip to the next target if all are blocked. |
| 416 | sel_logs++; | 428 | int best_idx = -1; |
| 417 | logf("aimbot: target idx=%d fov=%.2f eye=(%.1f %.1f %.1f) " | 429 | Vector best_head{}; |
| 418 | "approx_head=(%.1f %.1f %.1f)", | 430 | for (int ci = 0; ci < ncands && best_idx < 0; ci++) { |
| 419 | best_idx, best_fov, g_cam_eye.x, g_cam_eye.y, g_cam_eye.z, | 431 | int idx = cands[ci].idx; |
| 420 | best_head.x, best_head.y, best_head.z); | 432 | void *ent = entitylist_get(idx); |
| 421 | } | 433 | if (!ent || !bones_update(idx, ent)) |
| 422 | 434 | continue; | |
| 423 | // Refine to the nearest selected hitbox/bone point on the chosen target. | 435 | Vector pts[32]; |
| 424 | Vector pts[32]; | 436 | int npoints = aim_points(idx, bone_mask, pts, 32); |
| 425 | int npoints = 0; | 437 | float aim_fov = 1e9f; |
| 426 | void *best_ent = entitylist_get(best_idx); | 438 | Vector aim_pt{}; |
| 427 | if (best_ent && bones_update(best_idx, best_ent)) | 439 | for (int i = 0; i < npoints; i++) { |
| 428 | npoints = aim_points(best_idx, bone_mask, pts, 32); | 440 | float f = getfov(view, angle_to(g_cam_eye, pts[i])); |
| 429 | if (npoints <= 0) | 441 | if (f >= aim_fov) |
| 430 | return false; | 442 | continue; |
| 431 | float aim_fov = 1e9f; | 443 | if (aimbot_vischeck_enabled() && !is_visible(g_cam_eye, pts[i])) |
| 432 | Vector aim_pt{}; | 444 | continue; |
| 433 | for (int i = 0; i < npoints; i++) { | ||
| 434 | float f = getfov(view, angle_to(g_cam_eye, pts[i])); | ||
| 435 | if (f < aim_fov) { | ||
| 436 | aim_fov = f; | 445 | aim_fov = f; |
| 437 | aim_pt = pts[i]; | 446 | aim_pt = pts[i]; |
| 438 | } | 447 | } |
| 448 | if (aim_fov <= MAX_FOV && aim_fov < 1e8f) { | ||
| 449 | best_idx = idx; | ||
| 450 | best_head = aim_pt; | ||
| 451 | static int sel_logs = 0; | ||
| 452 | if (sel_logs < 5) { | ||
| 453 | sel_logs++; | ||
| 454 | logf("aimbot: target idx=%d points=%d fov=%.2f vischeck=%d", idx, | ||
| 455 | npoints, aim_fov, (int)aimbot_vischeck_enabled()); | ||
| 456 | } | ||
| 457 | } | ||
| 439 | } | 458 | } |
| 440 | if (aim_fov > MAX_FOV) | 459 | |
| 460 | if (best_idx < 0) { | ||
| 461 | static double last_vis = 0.0; | ||
| 462 | double now = esp_now(); | ||
| 463 | if (now - last_vis >= 2.0) { | ||
| 464 | last_vis = now; | ||
| 465 | logf("aimbot: no visible aim point (vischeck=%d)", | ||
| 466 | (int)aimbot_vischeck_enabled()); | ||
| 467 | } | ||
| 441 | return false; | 468 | return false; |
| 442 | best_head = aim_pt; | 469 | } |
| 443 | 470 | ||
| 444 | QAngle desired = angle_to(g_cam_eye, best_head); | 471 | QAngle desired = angle_to(g_cam_eye, best_head); |
| 445 | static int want_logs = 0; | 472 | static int want_logs = 0; |
| 446 | if (want_logs < 10) { | 473 | if (want_logs < 10) { |
| 447 | want_logs++; | 474 | want_logs++; |
| 448 | logf("aimbot: aim=(%.1f %.1f %.1f) points=%d fov=%.2f want=%.2f/%.2f", | 475 | logf("aimbot: aim=(%.1f %.1f %.1f) want=%.2f/%.2f", best_head.x, |
| 449 | best_head.x, best_head.y, best_head.z, npoints, aim_fov, desired.x, | 476 | best_head.y, best_head.z, desired.x, desired.y); |
| 450 | desired.y); | ||
| 451 | } | 477 | } |
| 452 | float dp = desired.x - cmd->viewangles[0]; | 478 | float dp = desired.x - cmd->viewangles[0]; |
| 453 | float dy = desired.y - cmd->viewangles[1]; | 479 | float dy = desired.y - cmd->viewangles[1]; |
diff --git a/src/hl2h.cpp b/src/hl2h.cpp index c3b94fe..85012a0 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp | |||
| @@ -408,9 +408,11 @@ static void *setup_thread(void *) { | |||
| 408 | g_iface.modelinfo = ef("VModelInfoClient006", nullptr); | 408 | g_iface.modelinfo = ef("VModelInfoClient006", nullptr); |
| 409 | g_iface.engineserver = ef("VEngineServer021", nullptr); | 409 | g_iface.engineserver = ef("VEngineServer021", nullptr); |
| 410 | g_iface.debugoverlay = ef("VDebugOverlay003", nullptr); | 410 | g_iface.debugoverlay = ef("VDebugOverlay003", nullptr); |
| 411 | g_iface.enginetrace = ef("EngineTraceClient003", nullptr); | ||
| 411 | logf("modelinfo: %p (VModelInfoClient006)", g_iface.modelinfo); | 412 | logf("modelinfo: %p (VModelInfoClient006)", g_iface.modelinfo); |
| 412 | logf("engineserver: %p (VEngineServer021), debugoverlay: %p", | 413 | logf("engineserver: %p (VEngineServer021), debugoverlay: %p, " |
| 413 | g_iface.engineserver, g_iface.debugoverlay); | 414 | "enginetrace: %p (EngineTraceClient003)", |
| 415 | g_iface.engineserver, g_iface.debugoverlay, g_iface.enginetrace); | ||
| 414 | 416 | ||
| 415 | resolve_netvars(); | 417 | resolve_netvars(); |
| 416 | if (!find_input_global()) | 418 | if (!find_input_global()) |
| @@ -51,6 +51,7 @@ struct GameInterfaces { | |||
| 51 | void *modelinfo = nullptr; // IVModelInfoClient006 (model index -> model name) | 51 | void *modelinfo = nullptr; // IVModelInfoClient006 (model index -> model name) |
| 52 | void *engineserver = nullptr; // IVEngineServer021 (edict access, incl. server entities) | 52 | void *engineserver = nullptr; // IVEngineServer021 (edict access, incl. server entities) |
| 53 | void *debugoverlay = nullptr; // IVDebugOverlay003 (engine-accurate world->screen) | 53 | void *debugoverlay = nullptr; // IVDebugOverlay003 (engine-accurate world->screen) |
| 54 | void *enginetrace = nullptr; // EngineTraceClient003 (world vischeck) | ||
| 54 | }; | 55 | }; |
| 55 | extern GameInterfaces g_iface; | 56 | extern GameInterfaces g_iface; |
| 56 | 57 | ||
| @@ -168,11 +169,13 @@ bool esp_option(int opt); | |||
| 168 | bool bhop_autostrafe_enabled(); | 169 | bool bhop_autostrafe_enabled(); |
| 169 | unsigned aimbot_bone_mask(); | 170 | unsigned aimbot_bone_mask(); |
| 170 | int aimbot_smooth_step(); | 171 | int aimbot_smooth_step(); |
| 172 | bool aimbot_vischeck_enabled(); | ||
| 171 | void *entitylist_get(int idx); | 173 | void *entitylist_get(int idx); |
| 172 | int engine_get_local_player(); | 174 | int engine_get_local_player(); |
| 173 | void draw_text_col(int x, int y, const char *s, int r, int g, int b); | 175 | void draw_text_col(int x, int y, const char *s, int r, int g, int b); |
| 174 | void esp_draw(); | 176 | void esp_draw(); |
| 175 | bool world_to_screen(const Vector &w, float &sx, float &sy); | 177 | bool world_to_screen(const Vector &w, float &sx, float &sy); |
| 178 | bool is_visible(const Vector &eye, const Vector &point); | ||
| 176 | 179 | ||
| 177 | // bones.cpp — client-side animated skeleton / hitbox access (see there) | 180 | // bones.cpp — client-side animated skeleton / hitbox access (see there) |
| 178 | bool bones_update(int idx, void *ent); | 181 | bool bones_update(int idx, void *ent); |
diff --git a/src/menu.cpp b/src/menu.cpp index db52fd2..d26b62e 100644 --- a/src/menu.cpp +++ b/src/menu.cpp | |||
| @@ -75,6 +75,7 @@ enum { | |||
| 75 | static bool g_aim_bones[AIM_BONE_COUNT] = {true, false, false, | 75 | static bool g_aim_bones[AIM_BONE_COUNT] = {true, false, false, |
| 76 | false, false, false}; | 76 | false, false, false}; |
| 77 | static int g_aim_smooth = 5; // degrees per tick | 77 | static int g_aim_smooth = 5; // degrees per tick |
| 78 | static bool g_aim_vischeck = true; | ||
| 78 | 79 | ||
| 79 | // ESP sub-options (menu "ESP" children). Defaults match the pre-option | 80 | // ESP sub-options (menu "ESP" children). Defaults match the pre-option |
| 80 | // behaviour: everything on. | 81 | // behaviour: everything on. |
| @@ -101,6 +102,7 @@ static MenuNode g_aimbot_feat{NODE_FEATURE, "Aimbot", false, 1, {}, nullptr, nul | |||
| 101 | static MenuNode g_aimbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_aimbot.active, nullptr, nullptr}; | 102 | static MenuNode g_aimbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_aimbot.active, nullptr, nullptr}; |
| 102 | static MenuNode g_aimbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_aimbot.bind, nullptr}; | 103 | static MenuNode g_aimbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_aimbot.bind, nullptr}; |
| 103 | static MenuNode g_aimbot_bones_feat{NODE_FEATURE, "Bones", false, 0, {}, nullptr, nullptr, nullptr}; | 104 | static MenuNode g_aimbot_bones_feat{NODE_FEATURE, "Bones", false, 0, {}, nullptr, nullptr, nullptr}; |
| 105 | static MenuNode g_aimbot_vischeck_node{NODE_TOGGLE, "Vischeck", false, 0, {}, &g_aim_vischeck, nullptr, nullptr}; | ||
| 104 | static MenuNode g_aimbot_bone_nodes[AIM_BONE_COUNT] = { | 106 | static MenuNode g_aimbot_bone_nodes[AIM_BONE_COUNT] = { |
| 105 | {NODE_TOGGLE, "Head", false, 0, {}, &g_aim_bones[AIM_BONE_HEAD], nullptr, nullptr}, | 107 | {NODE_TOGGLE, "Head", false, 0, {}, &g_aim_bones[AIM_BONE_HEAD], nullptr, nullptr}, |
| 106 | {NODE_TOGGLE, "Torso", false, 0, {}, &g_aim_bones[AIM_BONE_TORSO], nullptr, nullptr}, | 108 | {NODE_TOGGLE, "Torso", false, 0, {}, &g_aim_bones[AIM_BONE_TORSO], nullptr, nullptr}, |
| @@ -126,9 +128,10 @@ static void build_tree() { | |||
| 126 | g_esp_feat.children[0] = &g_esp_active; | 128 | g_esp_feat.children[0] = &g_esp_active; |
| 127 | g_aimbot_feat.children[0] = &g_aimbot_active; | 129 | g_aimbot_feat.children[0] = &g_aimbot_active; |
| 128 | g_aimbot_feat.children[1] = &g_aimbot_bind; | 130 | g_aimbot_feat.children[1] = &g_aimbot_bind; |
| 129 | g_aimbot_feat.children[2] = &g_aimbot_bones_feat; | 131 | g_aimbot_feat.children[2] = &g_aimbot_vischeck_node; |
| 130 | g_aimbot_feat.children[3] = &g_aimbot_smooth_node; | 132 | g_aimbot_feat.children[3] = &g_aimbot_bones_feat; |
| 131 | g_aimbot_feat.child_count = 4; | 133 | g_aimbot_feat.children[4] = &g_aimbot_smooth_node; |
| 134 | g_aimbot_feat.child_count = 5; | ||
| 132 | for (int i = 0; i < AIM_BONE_COUNT; i++) | 135 | for (int i = 0; i < AIM_BONE_COUNT; i++) |
| 133 | g_aimbot_bones_feat.children[i] = &g_aimbot_bone_nodes[i]; | 136 | g_aimbot_bones_feat.children[i] = &g_aimbot_bone_nodes[i]; |
| 134 | g_aimbot_bones_feat.child_count = AIM_BONE_COUNT; | 137 | g_aimbot_bones_feat.child_count = AIM_BONE_COUNT; |
| @@ -203,10 +206,11 @@ static void config_save() { | |||
| 203 | fprintf(f, "[bhop]\nactive=%d\nbind=%d\nautostrafe=%d\n", g_bhop.active, | 206 | fprintf(f, "[bhop]\nactive=%d\nbind=%d\nautostrafe=%d\n", g_bhop.active, |
| 204 | g_bhop.bind, g_bhop_autostrafe); | 207 | g_bhop.bind, g_bhop_autostrafe); |
| 205 | fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind); | 208 | fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind); |
| 206 | fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\n" | 209 | fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n" |
| 207 | "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n" | 210 | "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n" |
| 208 | "bone_lleg=%d\nbone_rleg=%d\n", | 211 | "bone_lleg=%d\nbone_rleg=%d\n", |
| 209 | g_aimbot.active, g_aimbot.bind, g_aim_smooth, | 212 | g_aimbot.active, g_aimbot.bind, g_aim_smooth, |
| 213 | (int)g_aim_vischeck, | ||
| 210 | g_aim_bones[AIM_BONE_HEAD], g_aim_bones[AIM_BONE_TORSO], | 214 | g_aim_bones[AIM_BONE_HEAD], g_aim_bones[AIM_BONE_TORSO], |
| 211 | g_aim_bones[AIM_BONE_LARM], g_aim_bones[AIM_BONE_RARM], | 215 | g_aim_bones[AIM_BONE_LARM], g_aim_bones[AIM_BONE_RARM], |
| 212 | g_aim_bones[AIM_BONE_LLEG], g_aim_bones[AIM_BONE_RLEG]); | 216 | g_aim_bones[AIM_BONE_LLEG], g_aim_bones[AIM_BONE_RLEG]); |
| @@ -269,6 +273,8 @@ static void config_load() { | |||
| 269 | g_aim_bones[AIM_BONE_LLEG] = atoi(val) != 0; | 273 | g_aim_bones[AIM_BONE_LLEG] = atoi(val) != 0; |
| 270 | } else if (strcmp(key, "bone_rleg") == 0) { | 274 | } else if (strcmp(key, "bone_rleg") == 0) { |
| 271 | g_aim_bones[AIM_BONE_RLEG] = atoi(val) != 0; | 275 | g_aim_bones[AIM_BONE_RLEG] = atoi(val) != 0; |
| 276 | } else if (strcmp(key, "vischeck") == 0) { | ||
| 277 | g_aim_vischeck = atoi(val) != 0; | ||
| 272 | } | 278 | } |
| 273 | } | 279 | } |
| 274 | if (in_esp) { | 280 | if (in_esp) { |
| @@ -429,6 +435,8 @@ unsigned aimbot_bone_mask() { | |||
| 429 | 435 | ||
| 430 | int aimbot_smooth_step() { return g_aim_smooth; } | 436 | int aimbot_smooth_step() { return g_aim_smooth; } |
| 431 | 437 | ||
| 438 | bool aimbot_vischeck_enabled() { return g_aim_vischeck; } | ||
| 439 | |||
| 432 | static void menu_handle_input() { | 440 | static void menu_handle_input() { |
| 433 | poll_keyboard(); | 441 | poll_keyboard(); |
| 434 | 442 | ||
diff --git a/src/vischeck.cpp b/src/vischeck.cpp new file mode 100644 index 0000000..92d194b --- /dev/null +++ b/src/vischeck.cpp | |||
| @@ -0,0 +1,98 @@ | |||
| 1 | // vischeck.cpp — world-only line-of-sight test via EngineTraceClient003. | ||
| 2 | // | ||
| 3 | // IEngineTrace::TraceRay (vtable slot 4) is given a ray from the eye to the | ||
| 4 | // aim point and a TRACE_WORLD_ONLY filter. MASK_VISIBLE ignores glass/windows | ||
| 5 | // (you can see through them) while still hitting opaque world geometry. | ||
| 6 | // Per the SDK, TRACE_WORLD_ONLY does not test static props; that is the | ||
| 7 | // deliberate trade-off for this build. | ||
| 8 | |||
| 9 | #include "hl2h.h" | ||
| 10 | #include <cstddef> | ||
| 11 | #include <cstdint> | ||
| 12 | |||
| 13 | namespace { | ||
| 14 | |||
| 15 | struct alignas(16) VectorAlignedCompat { | ||
| 16 | float x, y, z, w; | ||
| 17 | }; | ||
| 18 | |||
| 19 | // Ray_t: four VectorAligned (16 bytes each) followed by two bools. | ||
| 20 | struct alignas(16) RayCompat { | ||
| 21 | VectorAlignedCompat start; | ||
| 22 | VectorAlignedCompat delta; | ||
| 23 | VectorAlignedCompat startOffset; | ||
| 24 | VectorAlignedCompat extents; | ||
| 25 | bool isRay; | ||
| 26 | bool isSwept; | ||
| 27 | }; | ||
| 28 | |||
| 29 | static_assert(sizeof(VectorAlignedCompat) == 16, "VectorAligned layout"); | ||
| 30 | static_assert(offsetof(RayCompat, delta) == 16, "Ray_t delta offset"); | ||
| 31 | static_assert(offsetof(RayCompat, isRay) == 64, "Ray_t isRay offset"); | ||
| 32 | |||
| 33 | // trace_t / CBaseTrace prefix. cplane_t is Vector + float + type + signbits | ||
| 34 | // + 2 pad bytes, so fraction lands at 0x2C and startsolid at 0x37. | ||
| 35 | struct TraceCompat { | ||
| 36 | Vector startpos; | ||
| 37 | Vector endpos; | ||
| 38 | Vector normal; | ||
| 39 | float dist; | ||
| 40 | uint8_t type; | ||
| 41 | uint8_t signbits; | ||
| 42 | uint8_t pad[2]; | ||
| 43 | float fraction; | ||
| 44 | int contents; | ||
| 45 | uint16_t dispFlags; | ||
| 46 | bool allsolid; | ||
| 47 | bool startsolid; | ||
| 48 | uint8_t rest[128]; // CGameTrace tail (surface, ent, hitbox, ...) | ||
| 49 | }; | ||
| 50 | |||
| 51 | static_assert(offsetof(TraceCompat, fraction) == 44, "trace_t fraction"); | ||
| 52 | static_assert(offsetof(TraceCompat, startsolid) == 55, "trace_t startsolid"); | ||
| 53 | |||
| 54 | // ITraceFilter has no virtual destructor: vtable[0] = ShouldHitEntity, | ||
| 55 | // vtable[1] = GetTraceType. | ||
| 56 | struct ITraceFilterCompat { | ||
| 57 | virtual bool ShouldHitEntity(void *pEntity, int contentsMask) = 0; | ||
| 58 | virtual int GetTraceType() const = 0; | ||
| 59 | }; | ||
| 60 | |||
| 61 | struct WorldOnlyFilter : ITraceFilterCompat { | ||
| 62 | bool ShouldHitEntity(void *, int) override { return false; } | ||
| 63 | int GetTraceType() const override { return 1; } // TRACE_WORLD_ONLY | ||
| 64 | }; | ||
| 65 | |||
| 66 | // MASK_VISIBLE = CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_OPAQUE | | ||
| 67 | // CONTENTS_IGNORE_NODRAW_OPAQUE | ||
| 68 | constexpr unsigned MASK_VISIBLE = 0x1u | 0x4000u | 0x80u | 0x2000u; | ||
| 69 | |||
| 70 | } // namespace | ||
| 71 | |||
| 72 | bool is_visible(const Vector &eye, const Vector &point) { | ||
| 73 | static bool warned = false; | ||
| 74 | if (!g_iface.enginetrace) { | ||
| 75 | if (!warned) { | ||
| 76 | warned = true; | ||
| 77 | logf("vischeck: EngineTraceClient003 unavailable; failing open"); | ||
| 78 | } | ||
| 79 | return true; | ||
| 80 | } | ||
| 81 | |||
| 82 | float dx = point.x - eye.x, dy = point.y - eye.y, dz = point.z - eye.z; | ||
| 83 | RayCompat ray{}; | ||
| 84 | ray.start = {eye.x, eye.y, eye.z, 0.0f}; | ||
| 85 | ray.delta = {dx, dy, dz, 0.0f}; | ||
| 86 | ray.startOffset = {0.0f, 0.0f, 0.0f, 0.0f}; | ||
| 87 | ray.extents = {0.0f, 0.0f, 0.0f, 0.0f}; | ||
| 88 | ray.isRay = true; | ||
| 89 | ray.isSwept = (dx * dx + dy * dy + dz * dz) > 0.0f; | ||
| 90 | |||
| 91 | TraceCompat tr{}; | ||
| 92 | WorldOnlyFilter filter; | ||
| 93 | using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, | ||
| 94 | TraceCompat *); | ||
| 95 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, | ||
| 96 | MASK_VISIBLE, &filter, &tr); | ||
| 97 | return tr.fraction > 0.97f && !tr.startsolid; | ||
| 98 | } | ||
