From 12fde50efc7e1f1d123f9fc34ffef53d4387de2d Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 13:53:45 +0100 Subject: Add ESP, aim, view and movement batch - ESP: alert state (IDLE/ALERT/COMBAT/SCRIPT), enemy weapon labels, hostile/ neutral/friendly colour pickers, off-screen direction arrows, and trigger_changelevel volumes with next/previous map labels. - Aim: silent aim reuses the Aimbot FOV, vischeck and bone settings; add optional aim-punch compensation for aimbot, silent aim, triggerbot and melee (server bullets add m_vecPunchAngle to the usercmd angles). - View: always-on Camera / display card with camera FOV (writes the client m_iDefaultFOV netvar), viewmodel FOV and monitor gamma (client cvars). - Movement: Jump assist card with auto crouch-jump and edge jump (point ground probe 0.12 s ahead). - Spread: Engine spread override toggle gates the direct RandomFloat zeroing; off leaves only the seed search. - Server: entity count/base/classname helpers, trigger_changelevel map name and world bounds via IServerUnknown/ICollideable. - Netvars: m_vecPunchAngle and m_iDefaultFOV. --- CONTEXT.md | 69 ++++++++++- src/esp.cpp | 361 +++++++++++++++++++++++++++++++++++++++++++++++++++---- src/gui.cpp | 52 ++++++-- src/hl2h.cpp | 96 ++++++++++++++- src/hl2h.h | 27 ++++- src/menu.cpp | 147 ++++++++++++++++++---- src/netvars.cpp | 15 ++- src/server.cpp | 96 ++++++++++++++- src/vischeck.cpp | 20 +++ 9 files changed, 815 insertions(+), 68 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 98eb7d0..2e1ce52 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -20,7 +20,9 @@ covers ESP, aimbot, triggerbot and silent aim. - `HL2H_BONE_DEBUG=1` — white marker at each NPC head hitbox. - `HL2H_NO_SERVER=1` — disable server-side health reads. - Config: `$XDG_CONFIG_HOME/hl2hake/config`, falling back to - `~/.config/hl2hake/config`. Plain INI, saved on every change. + `~/.config/hl2hake/config`. Plain INI, saved on every change. New sections: + `[view]` (camera FOV, viewmodel FOV, gamma x10) and `[movement]` + (crouch-jump, edge jump). ## Runtime architecture @@ -210,6 +212,48 @@ vocabulary / decisions. resolved with that type (int fallback) and read as a byte. `0/1` means no damage. +## ESP additions + +- Alert state: server `m_NPCState` is drawn as a second small left label + (`IDLE` grey, `ALERT` yellow, `COMBAT` red, `SCRIPT` blue). Toggle + `ESP -> Alert state` (`alert=1`). +- Enemy weapon: the NPC's server `m_hActiveWeapon` handle gives the weapon + entity, whose server model index is mapped to a short label (`SMG1`, `AR2`, + `Shotgun`, `RPG`, ...) with the client class name as fallback. Toggle + `ESP -> Enemy weapon` (`weapon=1`). +- Colours: hostile / neutral / friendly box, skeleton and name colours are + three `CK_COLOR` pickers in the ESP card (`hr/hg/hb`, `nr/ng/nb`, + `fr/fg/fb`). Health bars keep their semantic red/yellow/green. +- Off-screen arrows: hostiles whose head projection is outside the screen get + a chevron clamped to a 26 px margin; behind-the-camera targets use the + dot product with the view right vector. Toggle `ESP -> Off-screen arrows` + (`arrows=1`). +- Map transitions: see below. + +## Camera / display + +- The rendered camera FOV comes from `CViewRender::OnRenderStart` copying + `C_BasePlayer::m_iDefaultFOV` into `default_fov`, so the cheat writes the + client netvar each tick when `Camera FOV > 0` (0 = stock 75). +- `viewmodel_fov` and `mat_monitorgamma` are client cvars; applied through + `ClientCmd` when their slider changes. Gamma is stored as x10 + (`[view] gamma=22` = 2.2). Both live in the always-on + `Camera / display` card (`[view] fov/vmfov/gamma`). + +## Map transitions + +- Server-only brush entities: iterate `IVEngineServer::GetEntityCount` / + `PEntityOfEntIndex`, ask `IServerNetworkable::GetClassName` (slot 3) for + `trigger_changelevel`, read `CChangeLevel::m_szMapName` + (`FIELD_CHARACTER`, char[32]) as the next map, and get the volume from + `ICollideable::WorldSpaceSurroundingBounds` (slot 17, no virtual dtor; the + interface comes from `IServerUnknown::GetCollideable`, slot 4). +- The current map name comes from the worldspawn (`entity 0`) model name + (`maps/.bsp`). The map before the last change is kept as + `g_map_prev`, so the box label reads `-> next` and `prev `. +- Scanning runs at 4 Hz in the ESP draw; boxes are projected from their 8 + world corners and drawn as an orange outline with a 10 px label. + ## Bones / ESP - `SetupBones(BONE_USED_BY_ANYTHING)` with engine `Time()`; studio header via @@ -242,6 +286,13 @@ vocabulary / decisions. `CInput::CreateMove`, the minimal-spread seed is applied again in `do_move` (same for triggerbot). Crossbow bolts and RPG rockets are led by target velocity and travel time; the crossbow also gets its 0.05 gravity drop. +- Silent aim reuses the Aimbot FOV, vischeck and bone selection (it has no + separate FOV any more); only its bind and active flag are separate. +- Aim punch: the server's bullet direction is `EyeAngles() + + m_Local.m_vecPunchAngle` (`CBasePlayer::GetAutoaimVector`). When the new + "Compensate aim punch" toggle is on, the aim/silent/melee target angle has + the client `m_vecPunchAngle` subtracted, and the triggerbot traces along + `view + punch`, so the shot lands on the crosshair target during recoil. - ABH: on the landing/jump tick, face opposite the horizontal velocity with zero forward/side movement; HL2's jump speed-bound subtraction then accelerates along the velocity. Camera restored at `FRAME_RENDER_START`. @@ -298,6 +349,9 @@ vocabulary / decisions. `CBaseEntity::FireBullets`; the `CUniformRandomStream` wrappers and constructor-time `RandomSeed(0)` resets pass other objects and are left alone. +- `Spread control -> Engine spread override` (config `engine=1`) gates the + direct zeroing; when off, the RandomFloat hook passes through and only the + seed search is used. - Seed choice alone cannot remove all of a shotgun's pellet spread: the best of 256 sequences still leaves its 8/12 draws apart. `CShotManipulator:: ApplySpread` is inlined into `CBaseEntity::FireBullets` and draws x/y via @@ -316,6 +370,19 @@ vocabulary / decisions. single-player those can be written/hooked locally; stock HL2 has no distance damage falloff. +## Jump assist + +- **Auto crouch-jump** (`[movement] crouchjump`): while airborne with the + jump button still held and upward velocity, adds `IN_DUCK`; releases it once + the ascent ends. Only the bit we added is removed, so a player who holds + crouch keeps it. Bhop clears `IN_JUMP` in the air, so this applies to manual + jumps. +- **Edge jump** (`[movement] edgejump`): on ground, moving >120 u/s, probes a + point 0.12 s ahead with a downward `MASK_PLAYERSOLID` trace. No floor within + 96 units sets `IN_JUMP`. A startsolid probe (wall/step) counts as ground, so + running into walls does not bounce. Both are simple toggles, not features + with binds. + ## Jumpbug - Automatic while falling when the predicted landing speed diff --git a/src/esp.cpp b/src/esp.cpp index 51f46bc..1e67a8a 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -24,8 +24,17 @@ struct EspTarget { bool friendly; bool neutral; int damage; // NpcDamageClass + int state; // server NPC_STATE_* + char weapon[24]; char name[48]; }; + +struct ArrowTarget { + Vector head; + bool neutral; +}; +static ArrowTarget g_arrows[32]; +static int g_narrows = 0; static EspTarget g_targets[128]; static int g_ntargets = 0; static int g_scr_w = 0, g_scr_h = 0; @@ -273,6 +282,61 @@ static void short_name(const char *model, char *out, size_t outn) { out[n] = 0; } +// Enemy weapon label. NPC weapons are server entities, so read the server +// model index first and fall back to the client class name. +static void weapon_short_name(int idx, char *out, size_t outn) { + out[0] = 0; + int wi = server_active_weapon(idx); + if (wi <= 0) + return; + const char *name = nullptr; + void *base = server_entity_base(wi); + int mi = base ? server_model_index(base) : -1; + if (mi > 0) + name = model_name_from_index(mi); + if (!name) { + void *wep = entitylist_get(wi); + if (wep) { + void *net = entity_networkable(wi); + name = entity_class_name(net, nullptr); + } + } + if (!name) + name = server_entity_classname(wi); + if (!name) + return; + static const struct { + const char *needle; + const char *label; + } map[] = { + {"smg1", "SMG1"}, {"irifle", "AR2"}, {"ar2", "AR2"}, + {"shotgun", "Shotgun"}, {"pistol", "Pistol"}, {"357", "357"}, + {"crossbow", "Crossbow"}, {"rocket", "RPG"}, {"rpg", "RPG"}, + {"grenade", "Grenade"}, {"frag", "Grenade"}, {"crowbar", "Crowbar"}, + {"physcannon", "Gravity"}, {"slam", "SLAM"}, {"bugbait", "Bugbait"}, + {"manhack", "Manhack"}, {"turret", "Turret"}, {"annabelle", "Rifle"}, + {"alyxgun", "Alyx gun"}, {"sniper", "Sniper"}, {"smg", "SMG"}, + }; + for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) { + if (strcasestr(name, map[i].needle)) { + snprintf(out, outn, "%s", map[i].label); + return; + } + } + // Last resort: the short class/model name. + short_name(name, out, outn); +} + +// Hostile / neutral / friendly colours come from the ESP colour pickers. +static void relation_rgb(const EspTarget &t, int &r, int &g, int &b) { + const int *c = t.friendly ? g_esp_col_friendly + : t.neutral ? g_esp_col_neutral + : g_esp_col_hostile; + r = c[0]; + g = c[1]; + b = c[2]; +} + static void esp_collect(int local_idx) { // outer_draw() runs once per PaintTraverseEx call (several per frame); cache // the scan briefly so we do not walk the entity list dozens of times a frame. @@ -286,6 +350,7 @@ static void esp_collect(int local_idx) { if (dbg) last_dbg = now; g_ntargets = 0; + g_narrows = 0; using Fn = int (*)(void *); int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); // GetHighestEntityIndex if (hi < 0 || hi > 4096) hi = 4096; @@ -361,6 +426,16 @@ static void esp_collect(int local_idx) { *(float *)(e + g_net.origin + 8)}; Vector bottom{origin.x, origin.y, origin.z + minz}; Vector top{origin.x, origin.y, origin.z + maxz}; + bool neutral = false; + bool friendly = npc && npc_is_friendly(idx, model, local_idx, neutral); + // Arrows are collected before projection: a hostile behind the camera + // never produces on-screen box coordinates. + if (npc && g_narrows < 32 && !friendly && + (!neutral || esp_option(ESP_OPT_SHOW_NEUTRAL))) { + ArrowTarget &a = g_arrows[g_narrows++]; + a.head = Vector{origin.x, origin.y, origin.z + maxz * 0.7f}; + a.neutral = neutral; + } float sx, sy, tx, ty; if (!world_to_screen(bottom, sx, sy) || !world_to_screen(top, tx, ty)) continue; @@ -374,10 +449,13 @@ static void esp_collect(int local_idx) { t.health = hp > 0 ? (float)hp : 0.0f; t.maxhealth = maxhp > 0 ? (float)maxhp : 100.0f; t.show_health = hp > 0; - bool neutral = false; - t.friendly = npc_is_friendly(idx, model, local_idx, neutral); + t.friendly = friendly; t.neutral = neutral; t.damage = npc_damage_class(idx, model); + t.state = npc ? server_npc_state(idx) : -1; + t.weapon[0] = 0; + if (npc) + weapon_short_name(idx, t.weapon, sizeof(t.weapon)); short_name(model, t.name, sizeof(t.name)); if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC"); } @@ -562,12 +640,9 @@ static void draw_skeleton(const EspTarget &t) { logd("skeleton: idx=%d bones=%d core=%d humanoid=%d", t.idx, n, core_count, (int)bones_is_humanoid(t.idx)); } - if (t.friendly) - surf_color(60, 255, 60, 255); - else if (t.neutral) - surf_color(220, 220, 220, 255); - else - surf_color(255, 60, 60, 255); + int sr, sg, sb; + relation_rgb(t, sr, sg, sb); + surf_color(sr, sg, sb, 255); for (int i = 0; i < n; i++) { if (!ok[i]) continue; @@ -595,6 +670,24 @@ static QAngle angle_to(const Vector &from, const Vector &to) { return a; } +// Server bullet direction adds m_Local.m_vecPunchAngle to the usercmd view +// (GetAutoaimVector), so aim angles have to be pre-subtracted by it. +static bool punch_angles(float &px, float &py) { + px = py = 0.0f; + if (!aim_punch_enabled() || g_net.punch < 0) + return false; + int local = engine_get_local_player(); + if (local <= 0) + return false; + void *le = entitylist_get(local); + if (!le) + return false; + const float *p = (const float *)((uintptr_t)le + g_net.punch); + px = p[0]; + py = p[1]; + return std::isfinite(px) && std::isfinite(py); +} + static int g_aim_lock = -1; static int g_silent_lock = -1; @@ -852,6 +945,12 @@ bool aimbot_apply(CUserCmd *cmd) { if (!bone_mask) return false; QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; + float punch_x = 0.0f, punch_y = 0.0f; + bool punch = punch_angles(punch_x, punch_y); + if (punch) { + view.x += punch_x; + view.y += punch_y; + } int target = -1; Vector point{}; float fov = 0.0f; @@ -866,6 +965,10 @@ bool aimbot_apply(CUserCmd *cmd) { if (projectile_lead_point(target, point, lead)) point = lead; QAngle desired = angle_to(g_cam_eye, point); + if (punch) { + desired.x -= punch_x; + desired.y -= punch_y; + } static int want_logs = 0; if (want_logs < 10) { want_logs++; @@ -930,20 +1033,30 @@ bool silent_aim_apply(CUserCmd *cmd) { return false; QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; QAngle view{orig.x, orig.y, 0.0f}; + float punch_x = 0.0f, punch_y = 0.0f; + bool punch = punch_angles(punch_x, punch_y); + if (punch) { + view.x += punch_x; + view.y += punch_y; + } int target = -1; Vector point{}; float fov = 0.0f; - float max_fov = (float)silentaim_fov(); + float max_fov = (float)aimbot_fov(); if (max_fov < 1.0f) max_fov = 1.0f; if (max_fov > 180.0f) max_fov = 180.0f; int *lock = feature_has_bind(FEAT_SILENTAIM) ? &g_silent_lock : nullptr; - if (!select_aim_target(view, bone_mask, true, max_fov, "silentaim", lock, - target, point, fov)) + if (!select_aim_target(view, bone_mask, aimbot_vischeck_enabled(), max_fov, + "silentaim", lock, target, point, fov)) return false; Vector lead; if (projectile_lead_point(target, point, lead)) point = lead; QAngle desired = angle_to(g_cam_eye, point); + if (punch) { + desired.x -= punch_x; + desired.y -= punch_y; + } float np = desired.x; if (np > 89.0f) np = 89.0f; if (np < -89.0f) np = -89.0f; @@ -1215,6 +1328,11 @@ bool melee_aimbot_apply(CUserCmd *cmd) { QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; QAngle desired = angle_to(g_cam_eye, aim); + float punch_x = 0.0f, punch_y = 0.0f; + if (punch_angles(punch_x, punch_y)) { + desired.x -= punch_x; + desired.y -= punch_y; + } float np = desired.x; if (np > 89.0f) np = 89.0f; if (np < -89.0f) np = -89.0f; @@ -1415,6 +1533,11 @@ bool triggerbot_apply(CUserCmd *cmd) { return false; QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; + float punch_x = 0.0f, punch_y = 0.0f; + if (punch_angles(punch_x, punch_y)) { + view.x += punch_x; + view.y += punch_y; + } 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, @@ -1513,6 +1636,181 @@ void prop_tint_update() { } } +// Small left-edge label, stacked above the box (10 px font + 1 px shadow). +static void draw_left_tag(int left, int y, const char *text, int r, int g, + int b) { + int tw = (int)strlen(text) * 6; + int tx = left - tw - 3; + if (tx < 2) + tx = 2; + draw_text_small_col(tx + 1, y + 1, text, 0, 0, 0); + draw_text_small_col(tx, y, text, r, g, b); +} + +static const char *alert_tag(int state, int &r, int &g, int &b) { + switch (state) { + case 1: r = 170; g = 180; b = 190; return "IDLE"; + case 2: r = 255; g = 220; b = 80; return "ALERT"; + case 3: r = 255; g = 90; b = 70; return "COMBAT"; + case 4: r = 150; g = 200; b = 255; return "SCRIPT"; + default: return nullptr; + } +} + +// Direction markers for hostiles outside the view. Drawn at the screen edge +// using the engine projection when the target is in front, and the view-right +// dot product when it is behind. +static void draw_edge_arrows() { + if (!g_surface || !g_scr_w || !g_narrows || !esp_option(ESP_OPT_ARROWS)) + return; + float cx = (float)g_scr_w * 0.5f, cy = (float)g_scr_h * 0.5f; + const float margin = 26.0f; + int drawn = 0; + for (int i = 0; i < g_narrows && drawn < 12; i++) { + const ArrowTarget &a = g_arrows[i]; + float sx, sy; + bool proj = world_to_screen(a.head, sx, sy); + if (proj && sx >= 4.0f && sx <= (float)g_scr_w - 4.0f && sy >= 4.0f && + sy <= (float)g_scr_h - 4.0f) + continue; // already visible as a box + float dx, dy; + if (proj) { + dx = sx - cx; + dy = sy - cy; + } else { + float yaw = g_cam_view.y * PI_F / 180.0f; + float wx = a.head.x - g_cam_eye.x; + float wy = a.head.y - g_cam_eye.y; + float right = wx * sinf(yaw) + wy * (-cosf(yaw)); + dx = right >= 0.0f ? 1.0f : -1.0f; + dy = 0.0f; + } + float len = sqrtf(dx * dx + dy * dy); + if (len < 1e-3f) { + dx = 0.0f; + dy = 1.0f; + len = 1.0f; + } + dx /= len; + dy /= len; + float half_w = cx - margin, half_h = cy - margin; + float tx = fabsf(dx) > 1e-4f ? half_w / fabsf(dx) : 1e9f; + float ty = fabsf(dy) > 1e-4f ? half_h / fabsf(dy) : 1e9f; + float d = tx < ty ? tx : ty; + float ex = cx + dx * d, ey = cy + dy * d; + float px = -dy, py = dx; + const int *col = a.neutral ? g_esp_col_neutral : g_esp_col_hostile; + surf_color(col[0], col[1], col[2], 235); + surf_line((int)ex, (int)ey, (int)(ex - dx * 13.0f + px * 6.0f), + (int)(ey - dy * 13.0f + py * 6.0f)); + surf_line((int)ex, (int)ey, (int)(ex - dx * 13.0f - px * 6.0f), + (int)(ey - dy * 13.0f - py * 6.0f)); + drawn++; + } +} + +// Map transition triggers are server-only brush entities. Draw their world +// bounds and the target map; the previous map is remembered across level +// changes from the worldspawn model name. +struct TransitionBox { + char next[128]; + Vector mins, maxs; +}; +static TransitionBox g_trans[8]; +static int g_ntrans = 0; +static char g_map_last[64]; +static char g_map_prev[64]; + +static void map_name_from_world(char *out, size_t outn) { + out[0] = 0; + void *w = entitylist_get(0); + const char *m = w ? entity_model_name(w) : nullptr; + if (!m) + return; + const char *b = strrchr(m, '/'); + b = b ? b + 1 : m; + size_t n = strcspn(b, "."); + if (n >= outn) + n = outn - 1; + memcpy(out, b, n); + out[n] = 0; +} + +static void transitions_scan() { + char cur[64]; + map_name_from_world(cur, sizeof(cur)); + if (strcmp(cur, g_map_last) != 0) { + if (g_map_last[0]) + snprintf(g_map_prev, sizeof(g_map_prev), "%s", g_map_last); + snprintf(g_map_last, sizeof(g_map_last), "%s", cur); + } + int count = server_entity_count(); + int n = 0; + for (int i = 1; i <= count && n < 8; i++) { + TransitionBox &b = g_trans[n]; + b.mins = Vector{0, 0, 0}; + b.maxs = Vector{0, 0, 0}; + if (!server_trigger_info(i, b.next, sizeof(b.next), b.mins, b.maxs)) + continue; + if (b.mins.x == b.maxs.x && b.mins.y == b.maxs.y && + b.mins.z == b.maxs.z) + continue; + n++; + } + g_ntrans = n; + static int logged = 0; + if (n > 0 && logged < 4) { + logged++; + logf("transitions: %d changelevel trigger(s), next=%s prev=%s", n, + g_trans[0].next, g_map_prev[0] ? g_map_prev : "?"); + } +} + +static void draw_transitions() { + if (!g_surface || !g_ntrans) + return; + for (int i = 0; i < g_ntrans; i++) { + const TransitionBox &b = g_trans[i]; + float minx = 1e9f, miny = 1e9f, maxx = -1e9f, maxy = -1e9f; + int seen = 0; + for (int c = 0; c < 8; c++) { + Vector p{(c & 1) ? b.maxs.x : b.mins.x, (c & 2) ? b.maxs.y : b.mins.y, + (c & 4) ? b.maxs.z : b.mins.z}; + float sx, sy; + if (!world_to_screen(p, sx, sy) || !std::isfinite(sx) || + !std::isfinite(sy)) + continue; + if (sx < minx) minx = sx; + if (sx > maxx) maxx = sx; + if (sy < miny) miny = sy; + if (sy > maxy) maxy = sy; + seen++; + } + if (!seen) + continue; + int x0 = (int)minx, x1 = (int)maxx, y0 = (int)miny, y1 = (int)maxy; + if (x1 < -2000 || x0 > g_scr_w + 2000 || y1 < -2000 || + y0 > g_scr_h + 2000) + continue; + surf_color(255, 170, 60, 200); + surf_rect(0, x0, y0, x1, y1); + char line[128]; + snprintf(line, sizeof(line), "-> %.100s", b.next); + int tx = x0; + if (tx < 4) tx = 4; + if (tx > g_scr_w - 120) tx = g_scr_w - 120; + int ty = y0 - 16; + if (ty < 4) ty = 4; + draw_text_small_col(tx + 1, ty + 1, line, 0, 0, 0); + draw_text_small_col(tx, ty, line, 255, 180, 70); + if (g_map_prev[0]) { + snprintf(line, sizeof(line), "prev %.48s", g_map_prev); + draw_text_small_col(tx + 1, ty + 13, line, 0, 0, 0); + draw_text_small_col(tx, ty + 12, line, 200, 200, 200); + } + } +} + void esp_draw() { if (!g_surface || !g_iface.entitylist || !g_net.esp_ok()) return; if (!g_scr_w) { @@ -1523,6 +1821,13 @@ void esp_draw() { } draw_fov_circle(); esp_collect(engine_get_local_player()); + static double last_trans = 0.0; + double trans_now = esp_now(); + if (trans_now - last_trans >= 0.25) { + last_trans = trans_now; + transitions_scan(); + } + draw_transitions(); for (int i = 0; i < g_ntargets; i++) { const EspTarget &t = g_targets[i]; if (t.neutral && !esp_option(ESP_OPT_SHOW_NEUTRAL)) @@ -1532,9 +1837,9 @@ void esp_draw() { if (ent && bones_update(t.idx, ent)) draw_skeleton(t); } - if (t.friendly) surf_color(60, 255, 60, 255); - else if (t.neutral) surf_color(220, 220, 220, 255); - else surf_color(255, 60, 60, 255); + int cr, cg, cb; + relation_rgb(t, cr, cg, cb); + surf_color(cr, cg, cb, 255); float w = t.h * 0.45f; // ponytail: constant aspect, no OBB width int left = (int)(t.sx - w / 2); int right = (int)(t.sx + w / 2); @@ -1568,7 +1873,8 @@ void esp_draw() { draw_text_col(num_x, top, hptext, 255, 255, 255); } if (esp_option(ESP_OPT_SHOW_NAME)) - draw_text_col(left, top - 14, t.name, 255, 255, 255); + draw_text_col(left, top - 14, t.name, cr, cg, cb); + int tag_y = top + 2; if (esp_option(ESP_OPT_DAMAGE_TAGS)) { const char *tag = nullptr; int tr = 255, tg = 255, tb = 255; @@ -1583,19 +1889,26 @@ void esp_draw() { tr = 180; tg = 180; tb = 180; } if (tag) { - // Small label on the left edge of the box, black 1px shadow for - // readability against the world (the 10px ESP font). - int tw = (int)strlen(tag) * 6; - int tx = left - tw - 3; - if (tx < 2) - tx = 2; - int ty = top + 2; - draw_text_small_col(tx + 1, ty + 1, tag, 0, 0, 0); - draw_text_small_col(tx, ty, tag, tr, tg, tb); + draw_left_tag(left, tag_y, tag, tr, tg, tb); + tag_y += 12; } } + if (esp_option(ESP_OPT_ALERT)) { + int ar, ag, ab; + const char *tag = alert_tag(t.state, ar, ag, ab); + if (tag) { + draw_left_tag(left, tag_y, tag, ar, ag, ab); + tag_y += 12; + } + } + if (esp_option(ESP_OPT_WEAPON) && t.weapon[0]) { + draw_left_tag(left, tag_y, t.weapon, 220, 220, 220); + tag_y += 12; + } } + draw_edge_arrows(); + // Groundwork probe: HL2H_BONE_DEBUG=1 draws a white marker at each NPC's // "head" hitbox center (from SetupBones + studio hitboxes). static bool bone_debug = getenv("HL2H_BONE_DEBUG") != nullptr; diff --git a/src/gui.cpp b/src/gui.cpp index b5fbc1d..974347a 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -318,6 +318,15 @@ static void build_cards() { add_card(0, &g_abh, "ABH", "Accelerate on the jump tick by facing away " "from the velocity.", true); + int jump = add_card(0, nullptr, "Jump assist", + "Extra jump handling applied while moving.", true); + add_toggle(jump, "Auto crouch-jump", + "Duck during the ascent of a held jump, release while falling.", + &g_auto_crouchjump); + add_toggle(jump, "Edge jump", + "Jump when the ground ahead of a running player disappears.", + &g_edge_jump); + int jb = add_card(0, &g_jumpbug, "Jumpbug", "Duck+jump when landing would hurt.", true); add_toggle(jb, "Assist timing", @@ -333,6 +342,9 @@ static void build_cards() { &g_aim_smooth); add_toggle(aim, "Show FOV circle", "Draw the acquisition cone on screen.", &g_aim_fov_circle); + add_toggle(aim, "Compensate aim punch", + "Aim against the server's recoil so shots land on target.", + &g_aim_punch); add_section(aim, "Bones"); static const char *bone_names[AIM_BONE_COUNT] = { "Head", "Torso", "Left arm", "Right arm", "Left leg", "Right leg"}; @@ -344,10 +356,10 @@ static void build_cards() { add_toggle(aim, bone_names[i], bone_descs[i], &g_aim_bones[i]); int sil = add_card(1, &g_silentaim, "Silent aim", - "Fire at the target without turning the rendered view.", + "Fire at the target without turning the rendered view. " + "Uses the Aimbot FOV, vischeck and bone settings.", true); - add_slider(sil, "FOV", "Maximum angular distance from the crosshair.", 1, 180, - &g_silent_fov); + add_info(sil, "Shares the Aimbot FOV, vischeck and bones."); add_card(1, &g_triggerbot, "Triggerbot", "Fire when the crosshair is on a hostile.", true); @@ -357,17 +369,27 @@ static void build_cards() { int esp = add_card(2, &g_esp, "ESP", "Draw hostile NPC information.", true); static const char *esp_names[ESP_OPT_COUNT] = { - "Show neutral", "Show healthbar", "Show healthnum", - "Show name", "Show skeleton", "Show box", - "Show speed graph", "Damage tags"}; + "Show neutral", "Show healthbar", "Show healthnum", + "Show name", "Show skeleton", "Show box", + "Show speed graph", "Damage tags", "Alert state", + "Enemy weapon", "Off-screen arrows"}; static const char *esp_descs[ESP_OPT_COUNT] = { "Include neutral wildlife.", "Draw a health bar.", "Draw the numeric health value.", "Draw the model name.", "Draw the animated bone links.", "Draw a bounding box.", "Draw the lower-center speed graph.", - "Label ARMORED (needs blast/RPG) and VULN (gravity gun) units."}; + "Label ARMORED (needs blast/RPG) and VULN (gravity gun) units.", + "Label IDLE / ALERT / COMBAT.", "Label the NPC's active weapon.", + "Point toward hostiles that are outside the view."}; for (int i = 0; i < ESP_OPT_COUNT; i++) add_toggle(esp, esp_names[i], esp_descs[i], &g_esp_opt[i]); + add_section(esp, "Colours"); + add_color(esp, "Hostile", "Box, skeleton and name colour for hostiles.", + g_esp_col_hostile); + add_color(esp, "Neutral", "Box, skeleton and name colour for neutral units.", + g_esp_col_neutral); + add_color(esp, "Friendly", "Box, skeleton and name colour for friendlies.", + g_esp_col_friendly); int items = add_card(2, &g_item_esp, "Valuable items", "Tag medkits, ammo, armor, weapons and supply crates.", true); @@ -388,6 +410,18 @@ static void build_cards() { 10000, &g_item_max_dist); g_ctrls[g_nctrl - 1].zero_unlimited = true; + int view = add_card(2, nullptr, "Camera / display", + "Camera FOV, viewmodel FOV and monitor gamma.", true); + add_slider(view, "Camera FOV", + "World camera field of view; 0 keeps the game default (75).", 0, + 140, &g_fov_camera); + add_slider(view, "Viewmodel FOV", + "Weapon model field of view; 0 keeps the game default (54).", 0, + 120, &g_fov_viewmodel); + add_slider(view, "Gamma x10", + "mat_monitorgamma x10: 16 = 1.6, 30 = 3.0, stock is 22.", 16, 30, + &g_gamma); + add_card(2, &g_fullbright, "Fullbright", "Ignore world lighting so dark areas are visible.", true); @@ -439,6 +473,10 @@ static void build_cards() { add_toggle(spread, "No spread", "Override the FireBullets seed with the best offset.", &g_spread_no_spread); + add_toggle(spread, "Engine spread override", + "Zero the pellet random offsets directly (shotgun one-hole). " + "Off = seed selection only.", + &g_spread_engine); int sys = add_card(4, nullptr, "System", "Configuration and maintenance.", true); diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 8ab1a3e..74a8c6c 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -503,7 +503,7 @@ static void hkdSrvRandomSeed(int seed) { (uintptr_t)self == g_local_server_player; if (local && g_server.contains(ret) && feature_active(FEAT_SPREAD) && - spread_no_spread_enabled()) { + spread_no_spread_enabled() && spread_engine_enabled()) { g_spread_rand_left = 8; int pellets = g_spread_pellets; if (pellets < 1) @@ -599,7 +599,7 @@ static bool g_uniform_hook_installed = false; static float hkdUniformRandomFloat(void *self, float mn, float mx) { bool enabled = g_local_server_player && feature_active(FEAT_SPREAD) && - spread_no_spread_enabled(); + spread_no_spread_enabled() && spread_engine_enabled(); bool hit = false; void *who = nullptr; if (enabled) { @@ -654,6 +654,90 @@ static void install_uniform_random_hook() { (void *)g_orig_uniform_float, (void *)&hkdUniformRandomFloat); } +// --------------------------------------------------------------------------- +// camera / viewmodel FOV and gamma +// --------------------------------------------------------------------------- +// +// The rendered camera FOV is derived from C_BasePlayer::m_iDefaultFOV every +// frame (CViewRender::OnRenderStart copies it into default_fov), so writing +// the client netvar is enough and needs no server command. The viewmodel FOV +// and monitor gamma are plain client cvars. + +static void view_settings_apply(void *ent) { + if (g_net.default_fov >= 0 && ent) { + static int last = -2; + int want = g_fov_camera > 0 ? g_fov_camera : 0; + if (want != last || g_fov_camera > 0) { + *(int *)((uintptr_t)ent + g_net.default_fov) = want; + last = want; + } + } + // 0 means "leave the game's value alone": only send a command once the + // slider is non-zero, then restore the stock value when it returns to 0. + static int last_vm = 0; + if (g_fov_viewmodel > 0 && g_fov_viewmodel != last_vm) { + char cmd[48]; + snprintf(cmd, sizeof(cmd), "viewmodel_fov %d", g_fov_viewmodel); + console_cmd(cmd); + last_vm = g_fov_viewmodel; + } else if (g_fov_viewmodel == 0 && last_vm > 0) { + console_cmd("viewmodel_fov 54"); + last_vm = 0; + } + static int last_gamma = 22; + if (g_gamma != last_gamma) { + char cmd[48]; + snprintf(cmd, sizeof(cmd), "mat_monitorgamma %d.%d", g_gamma / 10, + g_gamma % 10); + console_cmd(cmd); + last_gamma = g_gamma; + } +} + +// --------------------------------------------------------------------------- +// jump assist: crouch-jump and edge jump +// --------------------------------------------------------------------------- + +static bool g_cj_added = false; + +static bool crouch_jump_apply(CUserCmd *cmd, int flags, const float *vel) { + bool user_duck = (cmd->buttons & IN_DUCK) != 0; + bool onground = (flags & FL_ONGROUND) != 0; + bool want = g_auto_crouchjump && !onground && !user_duck && + (cmd->buttons & IN_JUMP) && vel[2] > 20.0f; + int old = cmd->buttons; + if (want) { + cmd->buttons |= IN_DUCK; + g_cj_added = true; + } else if (g_cj_added && !user_duck) { + cmd->buttons &= ~IN_DUCK; + g_cj_added = false; + } + return cmd->buttons != old; +} + +static bool edge_jump_apply(CUserCmd *cmd, void *ent, int flags, + const float *vel) { + if (!g_edge_jump || g_net.origin < 0 || !(flags & FL_ONGROUND) || + (flags & FL_INWATER)) + return false; + if (cmd->buttons & (IN_JUMP | IN_DUCK)) + return false; + float speed2d = sqrtf(vel[0] * vel[0] + vel[1] * vel[1]); + if (speed2d < 120.0f) + return false; + uintptr_t e = (uintptr_t)ent; + Vector origin{*(float *)(e + g_net.origin), *(float *)(e + g_net.origin + 4), + *(float *)(e + g_net.origin + 8)}; + // Probe the ground a fraction of a second ahead. No hit within 64 units + // means a real ledge; slopes and stairs still find a floor and are skipped. + Vector ahead{origin.x + vel[0] * 0.12f, origin.y + vel[1] * 0.12f, origin.z}; + if (!g_iface.enginetrace || point_ground(ahead, ent)) + return false; + cmd->buttons |= IN_JUMP; + return true; +} + static void do_move(int seq, float frametime) { install_seed_hook(); install_uniform_random_hook(); @@ -683,6 +767,8 @@ static void do_move(int seq, float frametime) { if (!ent) return; + view_settings_apply(ent); + // New map / respawn: drop stale bone caches and target locks. static void *last_local_ent = nullptr; if (ent != last_local_ent) { @@ -803,6 +889,12 @@ static void do_move(int seq, float frametime) { } } + bool jump_assist = crouch_jump_apply(cmd, flags, vel); + if (edge_jump_apply(cmd, ent, flags, vel)) + jump_assist = true; + if (jump_assist) + note_modified(cmd, flags, vel); + if (jumpbug_apply(cmd, localIdx, ent, flags, vel, frametime)) note_modified(cmd, flags, vel); diff --git a/src/hl2h.h b/src/hl2h.h index b7fc2f5..6b142c7 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -97,6 +97,8 @@ struct NetVars { int owner = -1; // client m_hOwnerEntity handle int weapon_owner = -1; // client m_hOwner handle (C_BaseCombatWeapon) int clr_render = -1; // client m_clrRender (entity render colour) + int punch = -1; // client m_vecPunchAngle (aim punch) + int default_fov = -1; // client m_iDefaultFOV (camera fov) // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; } // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100. @@ -193,7 +195,10 @@ enum EspOption { ESP_OPT_SHOW_BOX = 5, ESP_OPT_SHOW_SPEEDGRAPH = 6, ESP_OPT_DAMAGE_TAGS = 7, - ESP_OPT_COUNT = 8 + ESP_OPT_ALERT = 8, + ESP_OPT_WEAPON = 9, + ESP_OPT_ARROWS = 10, + ESP_OPT_COUNT = 11 }; enum AimBone { AIM_BONE_HEAD = 0, @@ -237,7 +242,16 @@ extern int g_aim_smooth; extern bool g_aim_vischeck; extern int g_aim_fov; extern bool g_aim_fov_circle; -extern int g_silent_fov; +extern bool g_aim_punch; +extern bool g_spread_engine; +extern bool g_auto_crouchjump; +extern bool g_edge_jump; +extern int g_fov_camera; +extern int g_fov_viewmodel; +extern int g_gamma; +extern int g_esp_col_hostile[3]; +extern int g_esp_col_neutral[3]; +extern int g_esp_col_friendly[3]; extern bool g_esp_opt[ESP_OPT_COUNT]; const char *scancode_name(int sc); @@ -275,6 +289,11 @@ int server_npc_relation(int idx, int local_idx); bool server_npc_has_follow_target(int idx); int server_npc_state(int idx); int server_npc_takedamage(int idx); +int server_entity_count(); +void *server_entity_base(int idx); +const char *server_entity_classname(int idx); +bool server_trigger_info(int idx, char *map, size_t mapn, Vector &mins, + Vector &maxs); int server_npc_turret_flags(int idx); int server_model_index(void *base); void find_modules(); @@ -288,11 +307,12 @@ bool esp_option(int opt); bool bhop_autostrafe_enabled(); bool jumpbug_assist_enabled(); bool spread_no_spread_enabled(); +bool spread_engine_enabled(); +bool aim_punch_enabled(); unsigned aimbot_bone_mask(); int aimbot_smooth_step(); bool aimbot_vischeck_enabled(); int aimbot_fov(); -int silentaim_fov(); bool aimbot_fov_circle(); void aimbot_reset_lock(); void silentaim_reset_lock(); @@ -310,6 +330,7 @@ bool is_visible(const Vector &eye, const Vector &point, void *self = nullptr, void *target = nullptr); void *trace_entity(const Vector &start, const Vector &end, void *ignore); float ground_distance(const Vector &origin, void *ignore, float max_dist); +bool point_ground(const Vector &point, 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 00aa189..4e521f7 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -75,11 +75,20 @@ int g_aim_smooth = 5; // degrees per tick bool g_aim_vischeck = true; int g_aim_fov = 60; // angular distance from the crosshair bool g_aim_fov_circle = true; -int g_silent_fov = 60; +bool g_aim_punch = true; // compensate server aim punch +bool g_spread_engine = true; // zero the pellet random draws, not just the seed +bool g_auto_crouchjump = false; +bool g_edge_jump = false; +int g_fov_camera = 0; // 0 = game default +int g_fov_viewmodel = 0; // 0 = game default (54) +int g_gamma = 22; // mat_monitorgamma x10, stock 2.2 +int g_esp_col_hostile[3] = {255, 60, 60}; +int g_esp_col_neutral[3] = {220, 220, 220}; +int g_esp_col_friendly[3] = {60, 255, 60}; // ESP sub-options. Defaults match the pre-option behaviour: everything on. -bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true, true, - true}; +bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true, + true, true, true, true, true}; // --------------------------------------------------------------------------- // config persistence (INI format) @@ -185,21 +194,23 @@ void config_save() { fprintf(f, "[jumpbug]\nactive=%d\nbind=%d\nassist=%d\n", g_jumpbug.active, g_jumpbug.bind, (int)g_jumpbug_assist); fprintf(f, "[melee]\nactive=%d\nbind=%d\n", g_melee.active, g_melee.bind); - fprintf(f, "[spread]\nactive=%d\nbind=%d\nno_spread=%d\n", - g_spread.active, g_spread.bind, (int)g_spread_no_spread); + fprintf(f, "[spread]\nactive=%d\nbind=%d\nno_spread=%d\nengine=%d\n", + g_spread.active, g_spread.bind, (int)g_spread_no_spread, + (int)g_spread_engine); fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n" - "fov=%d\nfov_circle=%d\n" + "fov=%d\nfov_circle=%d\npunch=%d\n" "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n" "bone_lleg=%d\nbone_rleg=%d\n", g_aimbot.active, g_aimbot.bind, g_aim_smooth, (int)g_aim_vischeck, g_aim_fov, (int)g_aim_fov_circle, - 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]); + (int)g_aim_punch, 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, "[silentaim]\nactive=%d\nbind=%d\nfov=%d\n", g_silentaim.active, - g_silentaim.bind, g_silent_fov); + fprintf(f, "[silentaim]\nactive=%d\nbind=%d\n", g_silentaim.active, + g_silentaim.bind); fprintf(f, "[fullbright]\nactive=%d\nbind=%d\n", g_fullbright.active, g_fullbright.bind); fprintf(f, "[worldtint]\nactive=%d\nbind=%d\nworld=%d\nsky=%d\n" @@ -223,13 +234,24 @@ void config_save() { (int)g_item_crates, (int)g_item_distance, g_item_max_dist); fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\nspeedgraph=%d\n" - "dmg=%d\n", + "dmg=%d\nalert=%d\nweapon=%d\narrows=%d\n" + "hr=%d\nhg=%d\nhb=%d\nnr=%d\nng=%d\nnb=%d\n" + "fr=%d\nfg=%d\nfb=%d\n", g_esp.active, g_esp.bind, g_esp_opt[ESP_OPT_SHOW_NEUTRAL], g_esp_opt[ESP_OPT_SHOW_HEALTHBAR], g_esp_opt[ESP_OPT_SHOW_HEALTHNUM], g_esp_opt[ESP_OPT_SHOW_NAME], g_esp_opt[ESP_OPT_SHOW_SKELETON], g_esp_opt[ESP_OPT_SHOW_BOX], g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH], - g_esp_opt[ESP_OPT_DAMAGE_TAGS]); + g_esp_opt[ESP_OPT_DAMAGE_TAGS], g_esp_opt[ESP_OPT_ALERT], + g_esp_opt[ESP_OPT_WEAPON], g_esp_opt[ESP_OPT_ARROWS], + g_esp_col_hostile[0], g_esp_col_hostile[1], g_esp_col_hostile[2], + g_esp_col_neutral[0], g_esp_col_neutral[1], g_esp_col_neutral[2], + g_esp_col_friendly[0], g_esp_col_friendly[1], + g_esp_col_friendly[2]); + fprintf(f, "[view]\nfov=%d\nvmfov=%d\ngamma=%d\n", g_fov_camera, + g_fov_viewmodel, g_gamma); + fprintf(f, "[movement]\ncrouchjump=%d\nedgejump=%d\n", + (int)g_auto_crouchjump, (int)g_edge_jump); gui_config_write(f); bool ok = ferror(f) == 0; if (fflush(f) != 0) @@ -250,9 +272,9 @@ static bool config_load() { return false; char line[256]; FeatureState *cur = nullptr; - bool in_esp = false, in_bhop = false, in_aim = false, in_silent = false, + bool in_esp = false, in_bhop = false, in_aim = false, in_jumpbug = false, in_spread = false, in_items = false, - in_tint = false, in_fog = false; + in_tint = false, in_fog = false, in_view = false, in_movement = false; while (fgets(line, sizeof(line), f)) { line[strcspn(line, "\r\n")] = 0; if (line[0] == '[') { @@ -284,18 +306,23 @@ static bool config_load() { cur = &g_triggerbot; else if (strstr(line, "[silentaim]")) cur = &g_silentaim; + else if (strstr(line, "[view]")) + cur = nullptr; + else if (strstr(line, "[movement]")) + cur = nullptr; else cur = nullptr; in_esp = (cur == &g_esp); in_bhop = (cur == &g_bhop); in_aim = (cur == &g_aimbot); - in_silent = (cur == &g_silentaim); in_jumpbug = (cur == &g_jumpbug); in_spread = (cur == &g_spread); in_items = (cur == &g_item_esp); in_tint = (cur == &g_world_tint); in_fog = (cur == &g_fog); - } else if (cur) { + in_view = strstr(line, "[view]") != nullptr; + in_movement = strstr(line, "[movement]") != nullptr; + } else if (cur || in_view || in_movement) { char *eq = strchr(line, '='); if (!eq) continue; @@ -307,8 +334,11 @@ static bool config_load() { if (in_jumpbug && strcmp(key, "assist") == 0) { g_jumpbug_assist = atoi(val) != 0; } - if (in_spread && strcmp(key, "no_spread") == 0) { - g_spread_no_spread = atoi(val) != 0; + if (in_spread) { + if (strcmp(key, "no_spread") == 0) + g_spread_no_spread = atoi(val) != 0; + else if (strcmp(key, "engine") == 0) + g_spread_engine = atoi(val) != 0; } if (in_items) { if (strcmp(key, "health") == 0) @@ -357,13 +387,36 @@ static bool config_load() { g_aim_fov = v; } else if (strcmp(key, "fov_circle") == 0) { g_aim_fov_circle = atoi(val) != 0; + } else if (strcmp(key, "punch") == 0) { + g_aim_punch = atoi(val) != 0; } } - if (in_silent && strcmp(key, "fov") == 0) { - int v = atoi(val); - if (v < 1) v = 1; - if (v > 180) v = 180; - g_silent_fov = v; + if (in_view) { + int *dst = nullptr; + int lo = 0, hi = 0; + if (strcmp(key, "fov") == 0) { + dst = &g_fov_camera; + hi = 140; + } else if (strcmp(key, "vmfov") == 0) { + dst = &g_fov_viewmodel; + hi = 120; + } else if (strcmp(key, "gamma") == 0) { + dst = &g_gamma; + lo = 16; + hi = 30; + } + if (dst) { + int v = atoi(val); + if (v < lo) v = lo; + if (v > hi) v = hi; + *dst = v; + } + } + if (in_movement) { + if (strcmp(key, "crouchjump") == 0) + g_auto_crouchjump = atoi(val) != 0; + else if (strcmp(key, "edgejump") == 0) + g_edge_jump = atoi(val) != 0; } if (in_tint) { if (strcmp(key, "world") == 0) @@ -432,6 +485,31 @@ static bool config_load() { g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH] = atoi(val) != 0; else if (strcmp(key, "dmg") == 0) g_esp_opt[ESP_OPT_DAMAGE_TAGS] = atoi(val) != 0; + else if (strcmp(key, "alert") == 0) + g_esp_opt[ESP_OPT_ALERT] = atoi(val) != 0; + else if (strcmp(key, "weapon") == 0) + g_esp_opt[ESP_OPT_WEAPON] = atoi(val) != 0; + else if (strcmp(key, "arrows") == 0) + g_esp_opt[ESP_OPT_ARROWS] = atoi(val) != 0; + else if (strcmp(key, "hr") == 0 || strcmp(key, "hg") == 0 || + strcmp(key, "hb") == 0) { + int v = atoi(val); + if (v < 0) v = 0; + if (v > 255) v = 255; + g_esp_col_hostile[key[1] == 'r' ? 0 : key[1] == 'g' ? 1 : 2] = v; + } else if (strcmp(key, "nr") == 0 || strcmp(key, "ng") == 0 || + strcmp(key, "nb") == 0) { + int v = atoi(val); + if (v < 0) v = 0; + if (v > 255) v = 255; + g_esp_col_neutral[key[1] == 'r' ? 0 : key[1] == 'g' ? 1 : 2] = v; + } else if (strcmp(key, "fr") == 0 || strcmp(key, "fg") == 0 || + strcmp(key, "fb") == 0) { + int v = atoi(val); + if (v < 0) v = 0; + if (v > 255) v = 255; + g_esp_col_friendly[key[1] == 'r' ? 0 : key[1] == 'g' ? 1 : 2] = v; + } } if (strcmp(key, "active") == 0) cur->active = atoi(val) != 0; @@ -513,7 +591,22 @@ void config_reset_defaults() { g_aim_vischeck = true; g_aim_fov = 60; g_aim_fov_circle = true; - g_silent_fov = 60; + g_aim_punch = true; + g_spread_engine = true; + g_auto_crouchjump = false; + g_edge_jump = false; + g_fov_camera = 0; + g_fov_viewmodel = 0; + g_gamma = 22; + g_esp_col_hostile[0] = 255; + g_esp_col_hostile[1] = 60; + g_esp_col_hostile[2] = 60; + g_esp_col_neutral[0] = 220; + g_esp_col_neutral[1] = 220; + g_esp_col_neutral[2] = 220; + g_esp_col_friendly[0] = 60; + g_esp_col_friendly[1] = 255; + g_esp_col_friendly[2] = 60; config_save(); } @@ -669,7 +762,9 @@ bool aimbot_vischeck_enabled() { return g_aim_vischeck; } int aimbot_fov() { return g_aim_fov; } -int silentaim_fov() { return g_silent_fov; } +bool aim_punch_enabled() { return g_aim_punch; } + +bool spread_engine_enabled() { return g_spread_engine; } bool aimbot_fov_circle() { return g_aim_fov_circle; } diff --git a/src/netvars.cpp b/src/netvars.cpp index 8043674..33fd6a3 100644 --- a/src/netvars.cpp +++ b/src/netvars.cpp @@ -98,6 +98,15 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { // colour lives well inside C_BaseEntity. if (off >= 0x20 && off <= 0x2000) g_net.clr_render = off; + } else if (strcmp(pname2, "m_vecPunchAngle") == 0 && g_net.punch < 0) { + int off = (int)(base_off + (uint32_t)poff); + if (off >= 0x20 && off <= 0x2000) + g_net.punch = off; + } else if (strcmp(pname2, "m_iDefaultFOV") == 0 && + g_net.default_fov < 0) { + int off = (int)(base_off + (uint32_t)poff); + if (off >= 0x20 && off <= 0x2000) + g_net.default_fov = off; } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { g_net.teamnum = (int)(base_off + (uint32_t)poff); } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { @@ -279,9 +288,11 @@ void resolve_netvars() { logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d " "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d " "modelindex=%d absvelocity=%d activeweapon=%d owner=%d " - "weapon_owner=%d clr_render=%d", + "weapon_owner=%d clr_render=%d punch=%d default_fov=%d", g_net.flags, g_net.velocity, g_net.movetype, g_net.health, g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset, g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex, - g_net.absvelocity, g_net.activeweapon, g_net.clr_render); + g_net.absvelocity, g_net.activeweapon, g_net.owner, + g_net.weapon_owner, g_net.clr_render, g_net.punch, + g_net.default_fov); } diff --git a/src/server.cpp b/src/server.cpp index f71b441..39bb162 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -52,6 +52,7 @@ static int g_srv_spawnflags = -1; static int g_srv_hacked = -1; static int g_srv_npcstate = -1; static int g_srv_takedamage = -1; +static int g_srv_mapname = -1; static bool g_srv_ready = false; static bool g_srv_checked = false; @@ -91,7 +92,7 @@ static int datamap_offset(const char *name, int want_type, int min_off = 0x40, continue; if (foff < min_off || foff > max_off) continue; - if (fsize < 1 || fsize > 64) + if (fsize < 1 || fsize > 128) continue; // DEFINE_KEYFIELD entries have an external name ("health"); plain // DEFINE_FIELD entries have none. Either way it must be in server.so. @@ -147,16 +148,19 @@ bool resolve_server_netvars() { g_srv_takedamage = datamap_offset("m_takedamage", FIELD_CHARACTER); if (g_srv_takedamage < 0) g_srv_takedamage = datamap_offset("m_takedamage", FIELD_INTEGER); + // CChangeLevel::m_szMapName is a char[cchMapNameMost] on trigger_changelevel. + g_srv_mapname = datamap_offset("m_szMapName", FIELD_CHARACTER, 0x40, 0x4000); g_srv_ready = g_srv_health > 0; if (g_srv_ready) { logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " "followtarget=%d spawnflags=%d hacked=%d npcstate=%d " - "takedamage=%d", + "takedamage=%d mapname=%d", g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, g_srv_activeweapon, g_srv_relationship, g_srv_followtarget, - g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage); + g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage, + g_srv_mapname); fail_logged = false; } else if (!fail_logged) { fail_logged = true; @@ -223,6 +227,92 @@ int server_model_index(void *base) { return (int)*(short *)((uintptr_t)base + g_srv_modelindex); } +int server_entity_count() { + if (!g_iface.engineserver) + return 0; + using CountFn = int (*)(void *); + int n = vfunc(g_iface.engineserver, SRV_GetEntityCount)( + g_iface.engineserver); + return (n > 0 && n <= 8192) ? n : 0; +} + +static void *edict_unknown(int idx) { + if (!g_iface.engineserver) + return nullptr; + using PEntityFn = void *(*)(void *, int); + void *edict = vfunc(g_iface.engineserver, SRV_PEntityOfEntIndex)( + g_iface.engineserver, idx); + if (!edict) + return nullptr; + return *(void **)((uintptr_t)edict + EDICT_UNKNOWN_OFF); +} + +void *server_entity_base(int idx) { + void *unk = edict_unknown(idx); + if (!unk) + return nullptr; + using GetBaseFn = void *(*)(void *); + return vfunc(unk, SRV_GetBaseEntity)(unk); +} + +// Linked entity class name (e.g. "weapon_smg1", "trigger_changelevel"). +const char *server_entity_classname(int idx) { + void *unk = edict_unknown(idx); + if (!unk) + return nullptr; + using GetNetFn = void *(*)(void *); + void *net = vfunc(unk, 5)(unk); + if (!net) + return nullptr; + using NameFn = const char *(*)(void *); + return vfunc(net, 3)(net); +} + +// A trigger_changelevel's target map and world-space brush bounds. The class +// name comes from IServerNetworkable::GetClassName (slot 3); the volume from +// ICollideable::WorldSpaceSurroundingBounds (slot 17, no virtual dtor). +bool server_trigger_info(int idx, char *map, size_t mapn, Vector &mins, + Vector &maxs) { + if (!g_srv_ready || g_srv_mapname < 0) + return false; + void *unk = edict_unknown(idx); + if (!unk) + return false; + using GetNetFn = void *(*)(void *); + void *net = vfunc(unk, 5)(unk); + if (!net) + return false; + using NameFn = const char *(*)(void *); + const char *cls = vfunc(net, 3)(net); + if (!cls || !strcasestr(cls, "changelevel")) + return false; + void *base = server_entity_base(idx); + if (!base) + return false; + const char *m = (const char *)((uintptr_t)base + g_srv_mapname); + size_t n = strnlen(m, 32); // cchMapNameMost + if (n == 0 || n >= 32) + return false; + for (size_t i = 0; i < n; i++) { + unsigned char c = (unsigned char)m[i]; + if (c < 0x20 || c > 0x7e) + return false; + } + if (map && mapn) { + if (n >= mapn) + n = mapn - 1; + memcpy(map, m, n); + map[n] = 0; + } + using GetCollFn = void *(*)(void *); + void *coll = vfunc(unk, 4)(unk); + if (coll) { + using BoundsFn = void (*)(void *, Vector *, Vector *); + vfunc(coll, 17)(coll, &mins, &maxs); + } + return true; +} + // Set the authoritative vertical velocity of a server entity. m_vecAbsVelocity // is what CGameMovement::SetupMove reads through GetAbsVelocity(); writing the // networked m_vecVelocity directly is deliberately avoided because the server diff --git a/src/vischeck.cpp b/src/vischeck.cpp index 97a2c12..c8c0942 100644 --- a/src/vischeck.cpp +++ b/src/vischeck.cpp @@ -232,6 +232,26 @@ void *trace_entity(const Vector &start, const Vector &end, void *ignore) { return tr.ent; } +// True when a downward point trace from just above `point` finds a standable +// surface. Used by edge jump: a ledge has no floor within the probe, slopes and +// stairs do. +bool point_ground(const Vector &point, void *ignore) { + if (!g_iface.enginetrace) + return false; + RayCompat ray; + Vector start{point.x, point.y, point.z + 4.0f}; + Vector end{point.x, point.y, point.z - 96.0f}; + 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_PLAYERSOLID, &filter, &tr); + // A start inside geometry means a wall/step there, not a ledge. + return tr.startsolid || tr.fraction < 1.0f; +} + // Vertical distance from the standing player hull's feet to the first // standable surface below. Matches CategorizePosition's 2-unit probe and its // plane.normal.z >= 0.7 rule, but sweeps much farther so the caller can -- cgit v1.2.3