From e9944657c7ddefb3b8d2c8373c97fa47629ea1b8 Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 26 Oct 2026 17:22:25 +0100 Subject: 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 --- src/esp.cpp | 53 +++++++++++++++++++++++++++++++++++ src/hl2h.cpp | 7 +++++ src/hl2h.h | 11 +++++++- src/menu.cpp | 28 ++++++++++++++----- 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) { return true; } +bool triggerbot_apply(CUserCmd *cmd) { + if (!g_iface.enginetrace || !g_iface.entitylist) + return false; + int local = engine_get_local_player(); + if (local <= 0) + return false; + void *local_ent = entitylist_get(local); + if (!local_ent) + return false; + if (g_net.health >= 0 && + *(int *)((uintptr_t)local_ent + g_net.health) <= 0) + return false; + + QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; + Vector fwd = angle_forward_vector(view); + Vector start = g_cam_eye; + Vector end{start.x + fwd.x * 8192.0f, start.y + fwd.y * 8192.0f, + start.z + fwd.z * 8192.0f}; + void *hit = trace_entity(start, end, local_ent); + if (!hit) + return false; + + using Fn = int (*)(void *); + int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); + if (hi < 0 || hi > 4096) + hi = 4096; + for (int idx = 1; idx <= hi; idx++) { + if (entitylist_get(idx) != hit) + continue; + void *net = entity_networkable(idx); + const char *cls = net ? entity_class_name(net, nullptr) : nullptr; + if (!cls || !is_npc_class(cls)) + return false; + uintptr_t e = (uintptr_t)hit; + int life = g_net.lifestate >= 0 + ? (int)*(unsigned char *)(e + g_net.lifestate) + : -1; + if (life != LIFE_ALIVE) + return false; + const char *model = entity_model_name(hit); + if (model_is_friendly(model) || model_is_neutral(model)) + return false; + cmd->buttons |= IN_ATTACK; + static int logs = 0; + if (logs < 5) { + logs++; + logf("triggerbot: fire idx=%d model=%s", idx, model ? model : "?"); + } + return true; + } + return false; +} + void esp_draw() { if (!g_surface || !g_iface.entitylist || !g_net.esp_ok()) return; 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) { note_modified(cmd, flags, vel); } } + + if (feature_active(FEAT_TRIGGERBOT)) { + if (triggerbot_apply(cmd)) { + const float *vel = (const float *)(e + g_net.velocity); + note_modified(cmd, flags, vel); + } + } } // --------------------------------------------------------------------------- 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; }; extern Vector g_cam_eye; extern Vector g_cam_view; QAngle vector_angles(const Vector &forward); +Vector angle_forward_vector(const QAngle &angles); float getfov(const QAngle &my_view, const QAngle &aim_ang); bool aimbot_apply(CUserCmd *cmd); -enum FeatureId { FEAT_BHOP = 0, FEAT_ABH = 1, FEAT_ESP = 2, FEAT_AIMBOT = 3 }; +bool triggerbot_apply(CUserCmd *cmd); +enum FeatureId { + FEAT_BHOP = 0, + FEAT_ABH = 1, + FEAT_ESP = 2, + FEAT_AIMBOT = 3, + FEAT_TRIGGERBOT = 4 +}; enum EspOption { ESP_OPT_SHOW_NEUTRAL = 0, 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); void esp_draw(); bool world_to_screen(const Vector &w, float &sx, float &sy); bool is_visible(const Vector &eye, const Vector &point); +void *trace_entity(const Vector &start, const Vector &end, void *ignore); // bones.cpp — client-side animated skeleton / hitbox access (see there) 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; static FeatureState g_abh{true, 0}; static FeatureState g_esp{true, 0}; static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu +static FeatureState g_triggerbot{true, 603}; // default Mouse4 // Aimbot bone checklist. Head defaults on (v1 behaviour). enum { @@ -113,6 +114,9 @@ static MenuNode g_aimbot_bone_nodes[AIM_BONE_COUNT] = { }; static MenuNode g_aimbot_smooth_node{NODE_VALUE, "Smoothness", false, 0, {}, nullptr, nullptr, nullptr, &g_aim_smooth, 1, 45, 1}; +static MenuNode g_triggerbot_feat{NODE_FEATURE, "Triggerbot", false, 1, {}, nullptr, nullptr, nullptr}; +static MenuNode g_triggerbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_triggerbot.active, nullptr, nullptr}; +static MenuNode g_triggerbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_triggerbot.bind, nullptr}; static MenuNode *g_visible[32]; // flattened visible nodes for arrow navigation static int g_visible_count = 0; @@ -135,6 +139,9 @@ static void build_tree() { for (int i = 0; i < AIM_BONE_COUNT; i++) g_aimbot_bones_feat.children[i] = &g_aimbot_bone_nodes[i]; g_aimbot_bones_feat.child_count = AIM_BONE_COUNT; + g_triggerbot_feat.children[0] = &g_triggerbot_active; + g_triggerbot_feat.children[1] = &g_triggerbot_bind; + g_triggerbot_feat.child_count = 2; for (int i = 0; i < ESP_OPT_COUNT; i++) g_esp_feat.children[i + 1] = &g_esp_opt_nodes[i]; g_esp_feat.child_count = ESP_OPT_COUNT + 1; @@ -142,7 +149,8 @@ static void build_tree() { g_root.children[1] = &g_abh_feat; g_root.children[2] = &g_esp_feat; g_root.children[3] = &g_aimbot_feat; - g_root.child_count = 4; + g_root.children[4] = &g_triggerbot_feat; + g_root.child_count = 5; } static int g_depth[32]; // depth of each visible row (for indentation) @@ -214,6 +222,8 @@ static void config_save() { g_aim_bones[AIM_BONE_HEAD], g_aim_bones[AIM_BONE_TORSO], g_aim_bones[AIM_BONE_LARM], g_aim_bones[AIM_BONE_RARM], g_aim_bones[AIM_BONE_LLEG], g_aim_bones[AIM_BONE_RLEG]); + fprintf(f, "[triggerbot]\nactive=%d\nbind=%d\n", g_triggerbot.active, + g_triggerbot.bind); fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\n", g_esp.active, g_esp.bind, g_esp_opt[ESP_OPT_SHOW_NEUTRAL], @@ -241,6 +251,8 @@ static void config_load() { cur = &g_esp; else if (strstr(line, "[aimbot]")) cur = &g_aimbot; + else if (strstr(line, "[triggerbot]")) + cur = &g_triggerbot; else cur = nullptr; in_esp = (cur == &g_esp); @@ -401,8 +413,9 @@ static bool key_pressed(int sc) { // feature gate queried from hl2h.cpp do_move(): feature must be Active AND // its bind key held (always-on when unbound). bool feature_active(int feature) { - static FeatureState *feats[4] = {&g_bhop, &g_abh, &g_esp, &g_aimbot}; - if (feature < 0 || feature > 3) + static FeatureState *feats[5] = {&g_bhop, &g_abh, &g_esp, &g_aimbot, + &g_triggerbot}; + if (feature < 0 || feature > 4) return false; FeatureState *f = feats[feature]; if (!f->active) @@ -479,7 +492,7 @@ static void menu_handle_input() { MenuNode *n = g_visible[g_selected]; if (n->bind_ptr) { // clear same bind from other features - FeatureState *features[] = {&g_bhop, &g_abh, &g_aimbot}; + FeatureState *features[] = {&g_bhop, &g_abh, &g_aimbot, &g_triggerbot}; for (auto *f : features) if (&f->bind != n->bind_ptr && f->bind == i) f->bind = 0; @@ -694,10 +707,11 @@ void outer_draw() { draw_text(270, 10 + i * 20, "[press key]"); else { /* stable name for current bind */ - static char bn[3][32]; - static FeatureState *feats[] = {&g_bhop, &g_abh, &g_aimbot}; + static char bn[4][32]; + static FeatureState *feats[] = {&g_bhop, &g_abh, &g_aimbot, + &g_triggerbot}; int fi = 0; - for (int k = 0; k < 3; k++) + for (int k = 0; k < 4; k++) if (n->bind_ptr == &feats[k]->bind) fi = k; 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"); static_assert(offsetof(RayCompat, delta) == 16, "Ray_t delta offset"); static_assert(offsetof(RayCompat, isRay) == 64, "Ray_t isRay offset"); -// trace_t / CBaseTrace prefix. cplane_t is Vector + float + type + signbits -// + 2 pad bytes, so fraction lands at 0x2C and startsolid at 0x37. -struct TraceCompat { +// trace_t / CGameTrace layout (Source 2013, 32-bit): +// CBaseTrace: startpos 0, endpos 12, cplane_t plane 24 (20 bytes), +// fraction 44, contents 48, dispFlags 52, allsolid 54, +// startsolid 55 +// CGameTrace: fractionleftsolid 56, csurface_t surface 60 (8 bytes), +// hitgroup 68, physicsbone 72, m_pEnt 76, hitbox 80 +struct SurfaceCompat { + const char *name; + short surfaceProps; + unsigned short flags; +}; + +struct GameTraceCompat { Vector startpos; Vector endpos; Vector normal; @@ -45,11 +55,18 @@ struct TraceCompat { uint16_t dispFlags; bool allsolid; bool startsolid; - uint8_t rest[128]; // CGameTrace tail (surface, ent, hitbox, ...) + float fractionleftsolid; + SurfaceCompat surface; + int hitgroup; + short physicsbone; + void *ent; + int hitbox; + uint8_t rest[64]; // future-proof / avoid short writes }; -static_assert(offsetof(TraceCompat, fraction) == 44, "trace_t fraction"); -static_assert(offsetof(TraceCompat, startsolid) == 55, "trace_t startsolid"); +static_assert(offsetof(GameTraceCompat, fraction) == 44, "trace_t fraction"); +static_assert(offsetof(GameTraceCompat, startsolid) == 55, "trace_t startsolid"); +static_assert(offsetof(GameTraceCompat, ent) == 76, "trace_t m_pEnt"); // ITraceFilter has no virtual destructor: vtable[0] = ShouldHitEntity, // vtable[1] = GetTraceType. @@ -63,9 +80,32 @@ struct WorldOnlyFilter : ITraceFilterCompat { int GetTraceType() const override { return 1; } // TRACE_WORLD_ONLY }; +struct EntityFilter : ITraceFilterCompat { + void *ignore; + explicit EntityFilter(void *ig) : ignore(ig) {} + bool ShouldHitEntity(void *pEntity, int) override { + return pEntity != ignore; + } + int GetTraceType() const override { return 0; } // TRACE_EVERYTHING +}; + // MASK_VISIBLE = CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_OPAQUE | // CONTENTS_IGNORE_NODRAW_OPAQUE constexpr unsigned MASK_VISIBLE = 0x1u | 0x4000u | 0x80u | 0x2000u; +// MASK_SHOT = SOLID|MOVEABLE|MONSTER|WINDOW|DEBRIS|HITBOX +constexpr unsigned MASK_SHOT = 0x1u | 0x4000u | 0x2000000u | 0x4u | + 0x4000000u | 0x40000000u; + +static void make_ray(RayCompat &ray, const Vector &start, const Vector &end) { + float dx = end.x - start.x, dy = end.y - start.y, dz = end.z - start.z; + ray = RayCompat{}; + ray.start = {start.x, start.y, start.z, 0.0f}; + ray.delta = {dx, dy, dz, 0.0f}; + ray.startOffset = {0.0f, 0.0f, 0.0f, 0.0f}; + ray.extents = {0.0f, 0.0f, 0.0f, 0.0f}; + ray.isRay = true; + ray.isSwept = (dx * dx + dy * dy + dz * dz) > 0.0f; +} } // namespace @@ -79,20 +119,31 @@ bool is_visible(const Vector &eye, const Vector &point) { return true; } - float dx = point.x - eye.x, dy = point.y - eye.y, dz = point.z - eye.z; - RayCompat ray{}; - ray.start = {eye.x, eye.y, eye.z, 0.0f}; - ray.delta = {dx, dy, dz, 0.0f}; - ray.startOffset = {0.0f, 0.0f, 0.0f, 0.0f}; - ray.extents = {0.0f, 0.0f, 0.0f, 0.0f}; - ray.isRay = true; - ray.isSwept = (dx * dx + dy * dy + dz * dz) > 0.0f; - - TraceCompat tr{}; + RayCompat ray; + make_ray(ray, eye, point); + GameTraceCompat tr{}; WorldOnlyFilter filter; using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, - TraceCompat *); + GameTraceCompat *); vfunc(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, MASK_VISIBLE, &filter, &tr); return tr.fraction > 0.97f && !tr.startsolid; } + +// Trace the crosshair ray and return the first entity hit (nullptr for world +// or no hit). `ignore` is the local player entity pointer. +void *trace_entity(const Vector &start, const Vector &end, void *ignore) { + if (!g_iface.enginetrace) + return nullptr; + RayCompat ray; + make_ray(ray, start, end); + GameTraceCompat tr{}; + EntityFilter filter(ignore); + using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, + GameTraceCompat *); + vfunc(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, + MASK_SHOT, &filter, &tr); + if (tr.fraction >= 1.0f || tr.startsolid) + return nullptr; + return tr.ent; +} -- cgit v1.2.3