diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-26 17:22:25 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-26 17:22:25 +0100 |
| commit | e9944657c7ddefb3b8d2c8373c97fa47629ea1b8 (patch) | |
| tree | d79bb7b262b5791d05f1a087b0db4515ce69b47d /src | |
| parent | Aimbot v3: world-only vischeck (diff) | |
| download | hl2h-e9944657c7ddefb3b8d2c8373c97fa47629ea1b8.tar.gz hl2h-e9944657c7ddefb3b8d2c8373c97fa47629ea1b8.zip | |
Triggerbot: crosshair entity trace, hostile-only
- top-level Triggerbot feature (Active + Bind, default Mouse4, rebindable)
- trace the crosshair ray from the eye with MASK_SHOT and TRACE_EVERYTHING;
the filter ignores the local player
- if the first entity hit is a live hostile NPC, press IN_ATTACK and fix the
usercmd CRC; walls/props/friendly/neutral/corpses do not trigger
- independent of Aimbot; config [triggerbot] active/bind
Diffstat (limited to 'src')
| -rw-r--r-- | src/esp.cpp | 53 | ||||
| -rw-r--r-- | src/hl2h.cpp | 7 | ||||
| -rw-r--r-- | src/hl2h.h | 11 | ||||
| -rw-r--r-- | src/menu.cpp | 28 | ||||
| -rw-r--r-- | src/vischeck.cpp | 85 |
5 files changed, 159 insertions, 25 deletions
diff --git a/src/esp.cpp b/src/esp.cpp index 2c033ea..e6981f7 100644 --- a/src/esp.cpp +++ b/src/esp.cpp | |||
| @@ -516,6 +516,59 @@ bool aimbot_apply(CUserCmd *cmd) { | |||
| 516 | return true; | 516 | return true; |
| 517 | } | 517 | } |
| 518 | 518 | ||
| 519 | bool triggerbot_apply(CUserCmd *cmd) { | ||
| 520 | if (!g_iface.enginetrace || !g_iface.entitylist) | ||
| 521 | return false; | ||
| 522 | int local = engine_get_local_player(); | ||
| 523 | if (local <= 0) | ||
| 524 | return false; | ||
| 525 | void *local_ent = entitylist_get(local); | ||
| 526 | if (!local_ent) | ||
| 527 | return false; | ||
| 528 | if (g_net.health >= 0 && | ||
| 529 | *(int *)((uintptr_t)local_ent + g_net.health) <= 0) | ||
| 530 | return false; | ||
| 531 | |||
| 532 | QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; | ||
| 533 | Vector fwd = angle_forward_vector(view); | ||
| 534 | Vector start = g_cam_eye; | ||
| 535 | Vector end{start.x + fwd.x * 8192.0f, start.y + fwd.y * 8192.0f, | ||
| 536 | start.z + fwd.z * 8192.0f}; | ||
| 537 | void *hit = trace_entity(start, end, local_ent); | ||
| 538 | if (!hit) | ||
| 539 | return false; | ||
| 540 | |||
| 541 | using Fn = int (*)(void *); | ||
| 542 | int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); | ||
| 543 | if (hi < 0 || hi > 4096) | ||
| 544 | hi = 4096; | ||
| 545 | for (int idx = 1; idx <= hi; idx++) { | ||
| 546 | if (entitylist_get(idx) != hit) | ||
| 547 | continue; | ||
| 548 | void *net = entity_networkable(idx); | ||
| 549 | const char *cls = net ? entity_class_name(net, nullptr) : nullptr; | ||
| 550 | if (!cls || !is_npc_class(cls)) | ||
| 551 | return false; | ||
| 552 | uintptr_t e = (uintptr_t)hit; | ||
| 553 | int life = g_net.lifestate >= 0 | ||
| 554 | ? (int)*(unsigned char *)(e + g_net.lifestate) | ||
| 555 | : -1; | ||
| 556 | if (life != LIFE_ALIVE) | ||
| 557 | return false; | ||
| 558 | const char *model = entity_model_name(hit); | ||
| 559 | if (model_is_friendly(model) || model_is_neutral(model)) | ||
| 560 | return false; | ||
| 561 | cmd->buttons |= IN_ATTACK; | ||
| 562 | static int logs = 0; | ||
| 563 | if (logs < 5) { | ||
| 564 | logs++; | ||
| 565 | logf("triggerbot: fire idx=%d model=%s", idx, model ? model : "?"); | ||
| 566 | } | ||
| 567 | return true; | ||
| 568 | } | ||
| 569 | return false; | ||
| 570 | } | ||
| 571 | |||
| 519 | void esp_draw() { | 572 | void esp_draw() { |
| 520 | if (!g_surface || !g_iface.entitylist || !g_net.esp_ok()) return; | 573 | if (!g_surface || !g_iface.entitylist || !g_net.esp_ok()) return; |
| 521 | if (!g_scr_w) { | 574 | if (!g_scr_w) { |
diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 85012a0..7a7d32e 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp | |||
| @@ -255,6 +255,13 @@ static void do_move(int seq) { | |||
| 255 | note_modified(cmd, flags, vel); | 255 | note_modified(cmd, flags, vel); |
| 256 | } | 256 | } |
| 257 | } | 257 | } |
| 258 | |||
| 259 | if (feature_active(FEAT_TRIGGERBOT)) { | ||
| 260 | if (triggerbot_apply(cmd)) { | ||
| 261 | const float *vel = (const float *)(e + g_net.velocity); | ||
| 262 | note_modified(cmd, flags, vel); | ||
| 263 | } | ||
| 264 | } | ||
| 258 | } | 265 | } |
| 259 | 266 | ||
| 260 | // --------------------------------------------------------------------------- | 267 | // --------------------------------------------------------------------------- |
| @@ -136,9 +136,17 @@ struct QAngle { float x, y, z; }; | |||
| 136 | extern Vector g_cam_eye; | 136 | extern Vector g_cam_eye; |
| 137 | extern Vector g_cam_view; | 137 | extern Vector g_cam_view; |
| 138 | QAngle vector_angles(const Vector &forward); | 138 | QAngle vector_angles(const Vector &forward); |
| 139 | Vector angle_forward_vector(const QAngle &angles); | ||
| 139 | float getfov(const QAngle &my_view, const QAngle &aim_ang); | 140 | float getfov(const QAngle &my_view, const QAngle &aim_ang); |
| 140 | bool aimbot_apply(CUserCmd *cmd); | 141 | bool aimbot_apply(CUserCmd *cmd); |
| 141 | enum FeatureId { FEAT_BHOP = 0, FEAT_ABH = 1, FEAT_ESP = 2, FEAT_AIMBOT = 3 }; | 142 | bool triggerbot_apply(CUserCmd *cmd); |
| 143 | enum FeatureId { | ||
| 144 | FEAT_BHOP = 0, | ||
| 145 | FEAT_ABH = 1, | ||
| 146 | FEAT_ESP = 2, | ||
| 147 | FEAT_AIMBOT = 3, | ||
| 148 | FEAT_TRIGGERBOT = 4 | ||
| 149 | }; | ||
| 142 | enum EspOption { | 150 | enum EspOption { |
| 143 | ESP_OPT_SHOW_NEUTRAL = 0, | 151 | ESP_OPT_SHOW_NEUTRAL = 0, |
| 144 | ESP_OPT_SHOW_HEALTHBAR = 1, | 152 | ESP_OPT_SHOW_HEALTHBAR = 1, |
| @@ -176,6 +184,7 @@ void draw_text_col(int x, int y, const char *s, int r, int g, int b); | |||
| 176 | void esp_draw(); | 184 | void esp_draw(); |
| 177 | bool world_to_screen(const Vector &w, float &sx, float &sy); | 185 | bool world_to_screen(const Vector &w, float &sx, float &sy); |
| 178 | bool is_visible(const Vector &eye, const Vector &point); | 186 | bool is_visible(const Vector &eye, const Vector &point); |
| 187 | void *trace_entity(const Vector &start, const Vector &end, void *ignore); | ||
| 179 | 188 | ||
| 180 | // bones.cpp — client-side animated skeleton / hitbox access (see there) | 189 | // bones.cpp — client-side animated skeleton / hitbox access (see there) |
| 181 | bool bones_update(int idx, void *ent); | 190 | bool bones_update(int idx, void *ent); |
diff --git a/src/menu.cpp b/src/menu.cpp index d26b62e..585695c 100644 --- a/src/menu.cpp +++ b/src/menu.cpp | |||
| @@ -61,6 +61,7 @@ static bool g_bhop_autostrafe = true; | |||
| 61 | static FeatureState g_abh{true, 0}; | 61 | static FeatureState g_abh{true, 0}; |
| 62 | static FeatureState g_esp{true, 0}; | 62 | static FeatureState g_esp{true, 0}; |
| 63 | static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu | 63 | static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu |
| 64 | static FeatureState g_triggerbot{true, 603}; // default Mouse4 | ||
| 64 | 65 | ||
| 65 | // Aimbot bone checklist. Head defaults on (v1 behaviour). | 66 | // Aimbot bone checklist. Head defaults on (v1 behaviour). |
| 66 | enum { | 67 | enum { |
| @@ -113,6 +114,9 @@ static MenuNode g_aimbot_bone_nodes[AIM_BONE_COUNT] = { | |||
| 113 | }; | 114 | }; |
| 114 | static MenuNode g_aimbot_smooth_node{NODE_VALUE, "Smoothness", false, 0, {}, nullptr, | 115 | static MenuNode g_aimbot_smooth_node{NODE_VALUE, "Smoothness", false, 0, {}, nullptr, |
| 115 | nullptr, nullptr, &g_aim_smooth, 1, 45, 1}; | 116 | nullptr, nullptr, &g_aim_smooth, 1, 45, 1}; |
| 117 | static MenuNode g_triggerbot_feat{NODE_FEATURE, "Triggerbot", false, 1, {}, nullptr, nullptr, nullptr}; | ||
| 118 | static MenuNode g_triggerbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_triggerbot.active, nullptr, nullptr}; | ||
| 119 | static MenuNode g_triggerbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_triggerbot.bind, nullptr}; | ||
| 116 | 120 | ||
| 117 | static MenuNode *g_visible[32]; // flattened visible nodes for arrow navigation | 121 | static MenuNode *g_visible[32]; // flattened visible nodes for arrow navigation |
| 118 | static int g_visible_count = 0; | 122 | static int g_visible_count = 0; |
| @@ -135,6 +139,9 @@ static void build_tree() { | |||
| 135 | for (int i = 0; i < AIM_BONE_COUNT; i++) | 139 | for (int i = 0; i < AIM_BONE_COUNT; i++) |
| 136 | g_aimbot_bones_feat.children[i] = &g_aimbot_bone_nodes[i]; | 140 | g_aimbot_bones_feat.children[i] = &g_aimbot_bone_nodes[i]; |
| 137 | g_aimbot_bones_feat.child_count = AIM_BONE_COUNT; | 141 | g_aimbot_bones_feat.child_count = AIM_BONE_COUNT; |
| 142 | g_triggerbot_feat.children[0] = &g_triggerbot_active; | ||
| 143 | g_triggerbot_feat.children[1] = &g_triggerbot_bind; | ||
| 144 | g_triggerbot_feat.child_count = 2; | ||
| 138 | for (int i = 0; i < ESP_OPT_COUNT; i++) | 145 | for (int i = 0; i < ESP_OPT_COUNT; i++) |
| 139 | g_esp_feat.children[i + 1] = &g_esp_opt_nodes[i]; | 146 | g_esp_feat.children[i + 1] = &g_esp_opt_nodes[i]; |
| 140 | g_esp_feat.child_count = ESP_OPT_COUNT + 1; | 147 | g_esp_feat.child_count = ESP_OPT_COUNT + 1; |
| @@ -142,7 +149,8 @@ static void build_tree() { | |||
| 142 | g_root.children[1] = &g_abh_feat; | 149 | g_root.children[1] = &g_abh_feat; |
| 143 | g_root.children[2] = &g_esp_feat; | 150 | g_root.children[2] = &g_esp_feat; |
| 144 | g_root.children[3] = &g_aimbot_feat; | 151 | g_root.children[3] = &g_aimbot_feat; |
| 145 | g_root.child_count = 4; | 152 | g_root.children[4] = &g_triggerbot_feat; |
| 153 | g_root.child_count = 5; | ||
| 146 | } | 154 | } |
| 147 | 155 | ||
| 148 | static int g_depth[32]; // depth of each visible row (for indentation) | 156 | static int g_depth[32]; // depth of each visible row (for indentation) |
| @@ -214,6 +222,8 @@ static void config_save() { | |||
| 214 | g_aim_bones[AIM_BONE_HEAD], g_aim_bones[AIM_BONE_TORSO], | 222 | g_aim_bones[AIM_BONE_HEAD], g_aim_bones[AIM_BONE_TORSO], |
| 215 | g_aim_bones[AIM_BONE_LARM], g_aim_bones[AIM_BONE_RARM], | 223 | g_aim_bones[AIM_BONE_LARM], g_aim_bones[AIM_BONE_RARM], |
| 216 | g_aim_bones[AIM_BONE_LLEG], g_aim_bones[AIM_BONE_RLEG]); | 224 | g_aim_bones[AIM_BONE_LLEG], g_aim_bones[AIM_BONE_RLEG]); |
| 225 | fprintf(f, "[triggerbot]\nactive=%d\nbind=%d\n", g_triggerbot.active, | ||
| 226 | g_triggerbot.bind); | ||
| 217 | fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" | 227 | fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" |
| 218 | "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\n", | 228 | "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\n", |
| 219 | g_esp.active, g_esp.bind, g_esp_opt[ESP_OPT_SHOW_NEUTRAL], | 229 | g_esp.active, g_esp.bind, g_esp_opt[ESP_OPT_SHOW_NEUTRAL], |
| @@ -241,6 +251,8 @@ static void config_load() { | |||
| 241 | cur = &g_esp; | 251 | cur = &g_esp; |
| 242 | else if (strstr(line, "[aimbot]")) | 252 | else if (strstr(line, "[aimbot]")) |
| 243 | cur = &g_aimbot; | 253 | cur = &g_aimbot; |
| 254 | else if (strstr(line, "[triggerbot]")) | ||
| 255 | cur = &g_triggerbot; | ||
| 244 | else | 256 | else |
| 245 | cur = nullptr; | 257 | cur = nullptr; |
| 246 | in_esp = (cur == &g_esp); | 258 | in_esp = (cur == &g_esp); |
| @@ -401,8 +413,9 @@ static bool key_pressed(int sc) { | |||
| 401 | // feature gate queried from hl2h.cpp do_move(): feature must be Active AND | 413 | // feature gate queried from hl2h.cpp do_move(): feature must be Active AND |
| 402 | // its bind key held (always-on when unbound). | 414 | // its bind key held (always-on when unbound). |
| 403 | bool feature_active(int feature) { | 415 | bool feature_active(int feature) { |
| 404 | static FeatureState *feats[4] = {&g_bhop, &g_abh, &g_esp, &g_aimbot}; | 416 | static FeatureState *feats[5] = {&g_bhop, &g_abh, &g_esp, &g_aimbot, |
| 405 | if (feature < 0 || feature > 3) | 417 | &g_triggerbot}; |
| 418 | if (feature < 0 || feature > 4) | ||
| 406 | return false; | 419 | return false; |
| 407 | FeatureState *f = feats[feature]; | 420 | FeatureState *f = feats[feature]; |
| 408 | if (!f->active) | 421 | if (!f->active) |
| @@ -479,7 +492,7 @@ static void menu_handle_input() { | |||
| 479 | MenuNode *n = g_visible[g_selected]; | 492 | MenuNode *n = g_visible[g_selected]; |
| 480 | if (n->bind_ptr) { | 493 | if (n->bind_ptr) { |
| 481 | // clear same bind from other features | 494 | // clear same bind from other features |
| 482 | FeatureState *features[] = {&g_bhop, &g_abh, &g_aimbot}; | 495 | FeatureState *features[] = {&g_bhop, &g_abh, &g_aimbot, &g_triggerbot}; |
| 483 | for (auto *f : features) | 496 | for (auto *f : features) |
| 484 | if (&f->bind != n->bind_ptr && f->bind == i) | 497 | if (&f->bind != n->bind_ptr && f->bind == i) |
| 485 | f->bind = 0; | 498 | f->bind = 0; |
| @@ -694,10 +707,11 @@ void outer_draw() { | |||
| 694 | draw_text(270, 10 + i * 20, "[press key]"); | 707 | draw_text(270, 10 + i * 20, "[press key]"); |
| 695 | else { | 708 | else { |
| 696 | /* stable name for current bind */ | 709 | /* stable name for current bind */ |
| 697 | static char bn[3][32]; | 710 | static char bn[4][32]; |
| 698 | static FeatureState *feats[] = {&g_bhop, &g_abh, &g_aimbot}; | 711 | static FeatureState *feats[] = {&g_bhop, &g_abh, &g_aimbot, |
| 712 | &g_triggerbot}; | ||
| 699 | int fi = 0; | 713 | int fi = 0; |
| 700 | for (int k = 0; k < 3; k++) | 714 | for (int k = 0; k < 4; k++) |
| 701 | if (n->bind_ptr == &feats[k]->bind) | 715 | if (n->bind_ptr == &feats[k]->bind) |
| 702 | fi = k; | 716 | fi = k; |
| 703 | snprintf(bn[fi], 32, "[%s]", scancode_name(*n->bind_ptr)); | 717 | snprintf(bn[fi], 32, "[%s]", scancode_name(*n->bind_ptr)); |
diff --git a/src/vischeck.cpp b/src/vischeck.cpp index 92d194b..428f54f 100644 --- a/src/vischeck.cpp +++ b/src/vischeck.cpp | |||
| @@ -30,9 +30,19 @@ static_assert(sizeof(VectorAlignedCompat) == 16, "VectorAligned layout"); | |||
| 30 | static_assert(offsetof(RayCompat, delta) == 16, "Ray_t delta offset"); | 30 | static_assert(offsetof(RayCompat, delta) == 16, "Ray_t delta offset"); |
| 31 | static_assert(offsetof(RayCompat, isRay) == 64, "Ray_t isRay offset"); | 31 | static_assert(offsetof(RayCompat, isRay) == 64, "Ray_t isRay offset"); |
| 32 | 32 | ||
| 33 | // trace_t / CBaseTrace prefix. cplane_t is Vector + float + type + signbits | 33 | // trace_t / CGameTrace layout (Source 2013, 32-bit): |
| 34 | // + 2 pad bytes, so fraction lands at 0x2C and startsolid at 0x37. | 34 | // CBaseTrace: startpos 0, endpos 12, cplane_t plane 24 (20 bytes), |
| 35 | struct TraceCompat { | 35 | // fraction 44, contents 48, dispFlags 52, allsolid 54, |
| 36 | // startsolid 55 | ||
| 37 | // CGameTrace: fractionleftsolid 56, csurface_t surface 60 (8 bytes), | ||
| 38 | // hitgroup 68, physicsbone 72, m_pEnt 76, hitbox 80 | ||
| 39 | struct SurfaceCompat { | ||
| 40 | const char *name; | ||
| 41 | short surfaceProps; | ||
| 42 | unsigned short flags; | ||
| 43 | }; | ||
| 44 | |||
| 45 | struct GameTraceCompat { | ||
| 36 | Vector startpos; | 46 | Vector startpos; |
| 37 | Vector endpos; | 47 | Vector endpos; |
| 38 | Vector normal; | 48 | Vector normal; |
| @@ -45,11 +55,18 @@ struct TraceCompat { | |||
| 45 | uint16_t dispFlags; | 55 | uint16_t dispFlags; |
| 46 | bool allsolid; | 56 | bool allsolid; |
| 47 | bool startsolid; | 57 | bool startsolid; |
| 48 | uint8_t rest[128]; // CGameTrace tail (surface, ent, hitbox, ...) | 58 | float fractionleftsolid; |
| 59 | SurfaceCompat surface; | ||
| 60 | int hitgroup; | ||
| 61 | short physicsbone; | ||
| 62 | void *ent; | ||
| 63 | int hitbox; | ||
| 64 | uint8_t rest[64]; // future-proof / avoid short writes | ||
| 49 | }; | 65 | }; |
| 50 | 66 | ||
| 51 | static_assert(offsetof(TraceCompat, fraction) == 44, "trace_t fraction"); | 67 | static_assert(offsetof(GameTraceCompat, fraction) == 44, "trace_t fraction"); |
| 52 | static_assert(offsetof(TraceCompat, startsolid) == 55, "trace_t startsolid"); | 68 | static_assert(offsetof(GameTraceCompat, startsolid) == 55, "trace_t startsolid"); |
| 69 | static_assert(offsetof(GameTraceCompat, ent) == 76, "trace_t m_pEnt"); | ||
| 53 | 70 | ||
| 54 | // ITraceFilter has no virtual destructor: vtable[0] = ShouldHitEntity, | 71 | // ITraceFilter has no virtual destructor: vtable[0] = ShouldHitEntity, |
| 55 | // vtable[1] = GetTraceType. | 72 | // vtable[1] = GetTraceType. |
| @@ -63,9 +80,32 @@ struct WorldOnlyFilter : ITraceFilterCompat { | |||
| 63 | int GetTraceType() const override { return 1; } // TRACE_WORLD_ONLY | 80 | int GetTraceType() const override { return 1; } // TRACE_WORLD_ONLY |
| 64 | }; | 81 | }; |
| 65 | 82 | ||
| 83 | struct EntityFilter : ITraceFilterCompat { | ||
| 84 | void *ignore; | ||
| 85 | explicit EntityFilter(void *ig) : ignore(ig) {} | ||
| 86 | bool ShouldHitEntity(void *pEntity, int) override { | ||
| 87 | return pEntity != ignore; | ||
| 88 | } | ||
| 89 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING | ||
| 90 | }; | ||
| 91 | |||
| 66 | // MASK_VISIBLE = CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_OPAQUE | | 92 | // MASK_VISIBLE = CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_OPAQUE | |
| 67 | // CONTENTS_IGNORE_NODRAW_OPAQUE | 93 | // CONTENTS_IGNORE_NODRAW_OPAQUE |
| 68 | constexpr unsigned MASK_VISIBLE = 0x1u | 0x4000u | 0x80u | 0x2000u; | 94 | constexpr unsigned MASK_VISIBLE = 0x1u | 0x4000u | 0x80u | 0x2000u; |
| 95 | // MASK_SHOT = SOLID|MOVEABLE|MONSTER|WINDOW|DEBRIS|HITBOX | ||
| 96 | constexpr unsigned MASK_SHOT = 0x1u | 0x4000u | 0x2000000u | 0x4u | | ||
| 97 | 0x4000000u | 0x40000000u; | ||
| 98 | |||
| 99 | static void make_ray(RayCompat &ray, const Vector &start, const Vector &end) { | ||
| 100 | float dx = end.x - start.x, dy = end.y - start.y, dz = end.z - start.z; | ||
| 101 | ray = RayCompat{}; | ||
| 102 | ray.start = {start.x, start.y, start.z, 0.0f}; | ||
| 103 | ray.delta = {dx, dy, dz, 0.0f}; | ||
| 104 | ray.startOffset = {0.0f, 0.0f, 0.0f, 0.0f}; | ||
| 105 | ray.extents = {0.0f, 0.0f, 0.0f, 0.0f}; | ||
| 106 | ray.isRay = true; | ||
| 107 | ray.isSwept = (dx * dx + dy * dy + dz * dz) > 0.0f; | ||
| 108 | } | ||
| 69 | 109 | ||
| 70 | } // namespace | 110 | } // namespace |
| 71 | 111 | ||
| @@ -79,20 +119,31 @@ bool is_visible(const Vector &eye, const Vector &point) { | |||
| 79 | return true; | 119 | return true; |
| 80 | } | 120 | } |
| 81 | 121 | ||
| 82 | float dx = point.x - eye.x, dy = point.y - eye.y, dz = point.z - eye.z; | 122 | RayCompat ray; |
| 83 | RayCompat ray{}; | 123 | make_ray(ray, eye, point); |
| 84 | ray.start = {eye.x, eye.y, eye.z, 0.0f}; | 124 | GameTraceCompat tr{}; |
| 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; | 125 | WorldOnlyFilter filter; |
| 93 | using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, | 126 | using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, |
| 94 | TraceCompat *); | 127 | GameTraceCompat *); |
| 95 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, | 128 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, |
| 96 | MASK_VISIBLE, &filter, &tr); | 129 | MASK_VISIBLE, &filter, &tr); |
| 97 | return tr.fraction > 0.97f && !tr.startsolid; | 130 | return tr.fraction > 0.97f && !tr.startsolid; |
| 98 | } | 131 | } |
| 132 | |||
| 133 | // Trace the crosshair ray and return the first entity hit (nullptr for world | ||
| 134 | // or no hit). `ignore` is the local player entity pointer. | ||
| 135 | void *trace_entity(const Vector &start, const Vector &end, void *ignore) { | ||
| 136 | if (!g_iface.enginetrace) | ||
| 137 | return nullptr; | ||
| 138 | RayCompat ray; | ||
| 139 | make_ray(ray, start, end); | ||
| 140 | GameTraceCompat tr{}; | ||
| 141 | EntityFilter filter(ignore); | ||
| 142 | using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, | ||
| 143 | GameTraceCompat *); | ||
| 144 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, | ||
| 145 | MASK_SHOT, &filter, &tr); | ||
| 146 | if (tr.fraction >= 1.0f || tr.startsolid) | ||
| 147 | return nullptr; | ||
| 148 | return tr.ent; | ||
| 149 | } | ||
