summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-26 17:22:25 +0100
committersalmonik <salmonik@tuta.io>2026-10-26 17:22:25 +0100
commite9944657c7ddefb3b8d2c8373c97fa47629ea1b8 (patch)
treed79bb7b262b5791d05f1a087b0db4515ce69b47d
parentAimbot v3: world-only vischeck (diff)
downloadhl2h-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
-rw-r--r--src/esp.cpp53
-rw-r--r--src/hl2h.cpp7
-rw-r--r--src/hl2h.h11
-rw-r--r--src/menu.cpp28
-rw-r--r--src/vischeck.cpp85
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
519bool 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
519void esp_draw() { 572void 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// ---------------------------------------------------------------------------
diff --git a/src/hl2h.h b/src/hl2h.h
index 98d93f4..28970b0 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -136,9 +136,17 @@ struct QAngle { float x, y, z; };
136extern Vector g_cam_eye; 136extern Vector g_cam_eye;
137extern Vector g_cam_view; 137extern Vector g_cam_view;
138QAngle vector_angles(const Vector &forward); 138QAngle vector_angles(const Vector &forward);
139Vector angle_forward_vector(const QAngle &angles);
139float getfov(const QAngle &my_view, const QAngle &aim_ang); 140float getfov(const QAngle &my_view, const QAngle &aim_ang);
140bool aimbot_apply(CUserCmd *cmd); 141bool aimbot_apply(CUserCmd *cmd);
141enum FeatureId { FEAT_BHOP = 0, FEAT_ABH = 1, FEAT_ESP = 2, FEAT_AIMBOT = 3 }; 142bool triggerbot_apply(CUserCmd *cmd);
143enum FeatureId {
144 FEAT_BHOP = 0,
145 FEAT_ABH = 1,
146 FEAT_ESP = 2,
147 FEAT_AIMBOT = 3,
148 FEAT_TRIGGERBOT = 4
149};
142enum EspOption { 150enum 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);
176void esp_draw(); 184void esp_draw();
177bool world_to_screen(const Vector &w, float &sx, float &sy); 185bool world_to_screen(const Vector &w, float &sx, float &sy);
178bool is_visible(const Vector &eye, const Vector &point); 186bool is_visible(const Vector &eye, const Vector &point);
187void *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)
181bool bones_update(int idx, void *ent); 190bool 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;
61static FeatureState g_abh{true, 0}; 61static FeatureState g_abh{true, 0};
62static FeatureState g_esp{true, 0}; 62static FeatureState g_esp{true, 0};
63static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu 63static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu
64static 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).
66enum { 67enum {
@@ -113,6 +114,9 @@ static MenuNode g_aimbot_bone_nodes[AIM_BONE_COUNT] = {
113}; 114};
114static MenuNode g_aimbot_smooth_node{NODE_VALUE, "Smoothness", false, 0, {}, nullptr, 115static 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};
117static MenuNode g_triggerbot_feat{NODE_FEATURE, "Triggerbot", false, 1, {}, nullptr, nullptr, nullptr};
118static MenuNode g_triggerbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_triggerbot.active, nullptr, nullptr};
119static MenuNode g_triggerbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_triggerbot.bind, nullptr};
116 120
117static MenuNode *g_visible[32]; // flattened visible nodes for arrow navigation 121static MenuNode *g_visible[32]; // flattened visible nodes for arrow navigation
118static int g_visible_count = 0; 122static 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
148static int g_depth[32]; // depth of each visible row (for indentation) 156static 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).
403bool feature_active(int feature) { 415bool 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");
30static_assert(offsetof(RayCompat, delta) == 16, "Ray_t delta offset"); 30static_assert(offsetof(RayCompat, delta) == 16, "Ray_t delta offset");
31static_assert(offsetof(RayCompat, isRay) == 64, "Ray_t isRay offset"); 31static_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),
35struct 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
39struct SurfaceCompat {
40 const char *name;
41 short surfaceProps;
42 unsigned short flags;
43};
44
45struct 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
51static_assert(offsetof(TraceCompat, fraction) == 44, "trace_t fraction"); 67static_assert(offsetof(GameTraceCompat, fraction) == 44, "trace_t fraction");
52static_assert(offsetof(TraceCompat, startsolid) == 55, "trace_t startsolid"); 68static_assert(offsetof(GameTraceCompat, startsolid) == 55, "trace_t startsolid");
69static_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
83struct 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
68constexpr unsigned MASK_VISIBLE = 0x1u | 0x4000u | 0x80u | 0x2000u; 94constexpr unsigned MASK_VISIBLE = 0x1u | 0x4000u | 0x80u | 0x2000u;
95// MASK_SHOT = SOLID|MOVEABLE|MONSTER|WINDOW|DEBRIS|HITBOX
96constexpr unsigned MASK_SHOT = 0x1u | 0x4000u | 0x2000000u | 0x4u |
97 0x4000000u | 0x40000000u;
98
99static 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.
135void *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}