summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 13:53:45 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 13:53:45 +0100
commit12fde50efc7e1f1d123f9fc34ffef53d4387de2d (patch)
tree4de53807ce23df9b63598a0e3a12620ff19eb7cc
parentNo spread: detect pellet draws from the FireBullets seed call (diff)
downloadhl2h-12fde50efc7e1f1d123f9fc34ffef53d4387de2d.tar.gz
hl2h-12fde50efc7e1f1d123f9fc34ffef53d4387de2d.zip
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.
-rw-r--r--CONTEXT.md69
-rw-r--r--src/esp.cpp361
-rw-r--r--src/gui.cpp52
-rw-r--r--src/hl2h.cpp96
-rw-r--r--src/hl2h.h27
-rw-r--r--src/menu.cpp147
-rw-r--r--src/netvars.cpp15
-rw-r--r--src/server.cpp96
-rw-r--r--src/vischeck.cpp20
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.
20 - `HL2H_BONE_DEBUG=1` — white marker at each NPC head hitbox. 20 - `HL2H_BONE_DEBUG=1` — white marker at each NPC head hitbox.
21 - `HL2H_NO_SERVER=1` — disable server-side health reads. 21 - `HL2H_NO_SERVER=1` — disable server-side health reads.
22- Config: `$XDG_CONFIG_HOME/hl2hake/config`, falling back to 22- Config: `$XDG_CONFIG_HOME/hl2hake/config`, falling back to
23 `~/.config/hl2hake/config`. Plain INI, saved on every change. 23 `~/.config/hl2hake/config`. Plain INI, saved on every change. New sections:
24 `[view]` (camera FOV, viewmodel FOV, gamma x10) and `[movement]`
25 (crouch-jump, edge jump).
24 26
25## Runtime architecture 27## Runtime architecture
26 28
@@ -210,6 +212,48 @@ vocabulary / decisions.
210 resolved with that type (int fallback) and read as a byte. `0/1` means no 212 resolved with that type (int fallback) and read as a byte. `0/1` means no
211 damage. 213 damage.
212 214
215## ESP additions
216
217- Alert state: server `m_NPCState` is drawn as a second small left label
218 (`IDLE` grey, `ALERT` yellow, `COMBAT` red, `SCRIPT` blue). Toggle
219 `ESP -> Alert state` (`alert=1`).
220- Enemy weapon: the NPC's server `m_hActiveWeapon` handle gives the weapon
221 entity, whose server model index is mapped to a short label (`SMG1`, `AR2`,
222 `Shotgun`, `RPG`, ...) with the client class name as fallback. Toggle
223 `ESP -> Enemy weapon` (`weapon=1`).
224- Colours: hostile / neutral / friendly box, skeleton and name colours are
225 three `CK_COLOR` pickers in the ESP card (`hr/hg/hb`, `nr/ng/nb`,
226 `fr/fg/fb`). Health bars keep their semantic red/yellow/green.
227- Off-screen arrows: hostiles whose head projection is outside the screen get
228 a chevron clamped to a 26 px margin; behind-the-camera targets use the
229 dot product with the view right vector. Toggle `ESP -> Off-screen arrows`
230 (`arrows=1`).
231- Map transitions: see below.
232
233## Camera / display
234
235- The rendered camera FOV comes from `CViewRender::OnRenderStart` copying
236 `C_BasePlayer::m_iDefaultFOV` into `default_fov`, so the cheat writes the
237 client netvar each tick when `Camera FOV > 0` (0 = stock 75).
238- `viewmodel_fov` and `mat_monitorgamma` are client cvars; applied through
239 `ClientCmd` when their slider changes. Gamma is stored as x10
240 (`[view] gamma=22` = 2.2). Both live in the always-on
241 `Camera / display` card (`[view] fov/vmfov/gamma`).
242
243## Map transitions
244
245- Server-only brush entities: iterate `IVEngineServer::GetEntityCount` /
246 `PEntityOfEntIndex`, ask `IServerNetworkable::GetClassName` (slot 3) for
247 `trigger_changelevel`, read `CChangeLevel::m_szMapName`
248 (`FIELD_CHARACTER`, char[32]) as the next map, and get the volume from
249 `ICollideable::WorldSpaceSurroundingBounds` (slot 17, no virtual dtor; the
250 interface comes from `IServerUnknown::GetCollideable`, slot 4).
251- The current map name comes from the worldspawn (`entity 0`) model name
252 (`maps/<name>.bsp`). The map before the last change is kept as
253 `g_map_prev`, so the box label reads `-> next` and `prev <map>`.
254- Scanning runs at 4 Hz in the ESP draw; boxes are projected from their 8
255 world corners and drawn as an orange outline with a 10 px label.
256
213## Bones / ESP 257## Bones / ESP
214 258
215- `SetupBones(BONE_USED_BY_ANYTHING)` with engine `Time()`; studio header via 259- `SetupBones(BONE_USED_BY_ANYTHING)` with engine `Time()`; studio header via
@@ -242,6 +286,13 @@ vocabulary / decisions.
242 `CInput::CreateMove`, the minimal-spread seed is applied again in `do_move` 286 `CInput::CreateMove`, the minimal-spread seed is applied again in `do_move`
243 (same for triggerbot). Crossbow bolts and RPG rockets are led by target 287 (same for triggerbot). Crossbow bolts and RPG rockets are led by target
244 velocity and travel time; the crossbow also gets its 0.05 gravity drop. 288 velocity and travel time; the crossbow also gets its 0.05 gravity drop.
289- Silent aim reuses the Aimbot FOV, vischeck and bone selection (it has no
290 separate FOV any more); only its bind and active flag are separate.
291- Aim punch: the server's bullet direction is `EyeAngles() +
292 m_Local.m_vecPunchAngle` (`CBasePlayer::GetAutoaimVector`). When the new
293 "Compensate aim punch" toggle is on, the aim/silent/melee target angle has
294 the client `m_vecPunchAngle` subtracted, and the triggerbot traces along
295 `view + punch`, so the shot lands on the crosshair target during recoil.
245- ABH: on the landing/jump tick, face opposite the horizontal velocity with 296- ABH: on the landing/jump tick, face opposite the horizontal velocity with
246 zero forward/side movement; HL2's jump speed-bound subtraction then 297 zero forward/side movement; HL2's jump speed-bound subtraction then
247 accelerates along the velocity. Camera restored at `FRAME_RENDER_START`. 298 accelerates along the velocity. Camera restored at `FRAME_RENDER_START`.
@@ -298,6 +349,9 @@ vocabulary / decisions.
298 `CBaseEntity::FireBullets`; the `CUniformRandomStream` wrappers and 349 `CBaseEntity::FireBullets`; the `CUniformRandomStream` wrappers and
299 constructor-time `RandomSeed(0)` resets pass other objects and are left 350 constructor-time `RandomSeed(0)` resets pass other objects and are left
300 alone. 351 alone.
352- `Spread control -> Engine spread override` (config `engine=1`) gates the
353 direct zeroing; when off, the RandomFloat hook passes through and only the
354 seed search is used.
301- Seed choice alone cannot remove all of a shotgun's pellet spread: the best 355- Seed choice alone cannot remove all of a shotgun's pellet spread: the best
302 of 256 sequences still leaves its 8/12 draws apart. `CShotManipulator:: 356 of 256 sequences still leaves its 8/12 draws apart. `CShotManipulator::
303 ApplySpread` is inlined into `CBaseEntity::FireBullets` and draws x/y via 357 ApplySpread` is inlined into `CBaseEntity::FireBullets` and draws x/y via
@@ -316,6 +370,19 @@ vocabulary / decisions.
316 single-player those can be written/hooked locally; stock HL2 has no 370 single-player those can be written/hooked locally; stock HL2 has no
317 distance damage falloff. 371 distance damage falloff.
318 372
373## Jump assist
374
375- **Auto crouch-jump** (`[movement] crouchjump`): while airborne with the
376 jump button still held and upward velocity, adds `IN_DUCK`; releases it once
377 the ascent ends. Only the bit we added is removed, so a player who holds
378 crouch keeps it. Bhop clears `IN_JUMP` in the air, so this applies to manual
379 jumps.
380- **Edge jump** (`[movement] edgejump`): on ground, moving >120 u/s, probes a
381 point 0.12 s ahead with a downward `MASK_PLAYERSOLID` trace. No floor within
382 96 units sets `IN_JUMP`. A startsolid probe (wall/step) counts as ground, so
383 running into walls does not bounce. Both are simple toggles, not features
384 with binds.
385
319## Jumpbug 386## Jumpbug
320 387
321- Automatic while falling when the predicted landing speed 388- 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 {
24 bool friendly; 24 bool friendly;
25 bool neutral; 25 bool neutral;
26 int damage; // NpcDamageClass 26 int damage; // NpcDamageClass
27 int state; // server NPC_STATE_*
28 char weapon[24];
27 char name[48]; 29 char name[48];
28}; 30};
31
32struct ArrowTarget {
33 Vector head;
34 bool neutral;
35};
36static ArrowTarget g_arrows[32];
37static int g_narrows = 0;
29static EspTarget g_targets[128]; 38static EspTarget g_targets[128];
30static int g_ntargets = 0; 39static int g_ntargets = 0;
31static int g_scr_w = 0, g_scr_h = 0; 40static 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) {
273 out[n] = 0; 282 out[n] = 0;
274} 283}
275 284
285// Enemy weapon label. NPC weapons are server entities, so read the server
286// model index first and fall back to the client class name.
287static void weapon_short_name(int idx, char *out, size_t outn) {
288 out[0] = 0;
289 int wi = server_active_weapon(idx);
290 if (wi <= 0)
291 return;
292 const char *name = nullptr;
293 void *base = server_entity_base(wi);
294 int mi = base ? server_model_index(base) : -1;
295 if (mi > 0)
296 name = model_name_from_index(mi);
297 if (!name) {
298 void *wep = entitylist_get(wi);
299 if (wep) {
300 void *net = entity_networkable(wi);
301 name = entity_class_name(net, nullptr);
302 }
303 }
304 if (!name)
305 name = server_entity_classname(wi);
306 if (!name)
307 return;
308 static const struct {
309 const char *needle;
310 const char *label;
311 } map[] = {
312 {"smg1", "SMG1"}, {"irifle", "AR2"}, {"ar2", "AR2"},
313 {"shotgun", "Shotgun"}, {"pistol", "Pistol"}, {"357", "357"},
314 {"crossbow", "Crossbow"}, {"rocket", "RPG"}, {"rpg", "RPG"},
315 {"grenade", "Grenade"}, {"frag", "Grenade"}, {"crowbar", "Crowbar"},
316 {"physcannon", "Gravity"}, {"slam", "SLAM"}, {"bugbait", "Bugbait"},
317 {"manhack", "Manhack"}, {"turret", "Turret"}, {"annabelle", "Rifle"},
318 {"alyxgun", "Alyx gun"}, {"sniper", "Sniper"}, {"smg", "SMG"},
319 };
320 for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) {
321 if (strcasestr(name, map[i].needle)) {
322 snprintf(out, outn, "%s", map[i].label);
323 return;
324 }
325 }
326 // Last resort: the short class/model name.
327 short_name(name, out, outn);
328}
329
330// Hostile / neutral / friendly colours come from the ESP colour pickers.
331static void relation_rgb(const EspTarget &t, int &r, int &g, int &b) {
332 const int *c = t.friendly ? g_esp_col_friendly
333 : t.neutral ? g_esp_col_neutral
334 : g_esp_col_hostile;
335 r = c[0];
336 g = c[1];
337 b = c[2];
338}
339
276static void esp_collect(int local_idx) { 340static void esp_collect(int local_idx) {
277 // outer_draw() runs once per PaintTraverseEx call (several per frame); cache 341 // outer_draw() runs once per PaintTraverseEx call (several per frame); cache
278 // the scan briefly so we do not walk the entity list dozens of times a frame. 342 // 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) {
286 if (dbg) last_dbg = now; 350 if (dbg) last_dbg = now;
287 351
288 g_ntargets = 0; 352 g_ntargets = 0;
353 g_narrows = 0;
289 using Fn = int (*)(void *); 354 using Fn = int (*)(void *);
290 int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); // GetHighestEntityIndex 355 int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); // GetHighestEntityIndex
291 if (hi < 0 || hi > 4096) hi = 4096; 356 if (hi < 0 || hi > 4096) hi = 4096;
@@ -361,6 +426,16 @@ static void esp_collect(int local_idx) {
361 *(float *)(e + g_net.origin + 8)}; 426 *(float *)(e + g_net.origin + 8)};
362 Vector bottom{origin.x, origin.y, origin.z + minz}; 427 Vector bottom{origin.x, origin.y, origin.z + minz};
363 Vector top{origin.x, origin.y, origin.z + maxz}; 428 Vector top{origin.x, origin.y, origin.z + maxz};
429 bool neutral = false;
430 bool friendly = npc && npc_is_friendly(idx, model, local_idx, neutral);
431 // Arrows are collected before projection: a hostile behind the camera
432 // never produces on-screen box coordinates.
433 if (npc && g_narrows < 32 && !friendly &&
434 (!neutral || esp_option(ESP_OPT_SHOW_NEUTRAL))) {
435 ArrowTarget &a = g_arrows[g_narrows++];
436 a.head = Vector{origin.x, origin.y, origin.z + maxz * 0.7f};
437 a.neutral = neutral;
438 }
364 float sx, sy, tx, ty; 439 float sx, sy, tx, ty;
365 if (!world_to_screen(bottom, sx, sy) || !world_to_screen(top, tx, ty)) 440 if (!world_to_screen(bottom, sx, sy) || !world_to_screen(top, tx, ty))
366 continue; 441 continue;
@@ -374,10 +449,13 @@ static void esp_collect(int local_idx) {
374 t.health = hp > 0 ? (float)hp : 0.0f; 449 t.health = hp > 0 ? (float)hp : 0.0f;
375 t.maxhealth = maxhp > 0 ? (float)maxhp : 100.0f; 450 t.maxhealth = maxhp > 0 ? (float)maxhp : 100.0f;
376 t.show_health = hp > 0; 451 t.show_health = hp > 0;
377 bool neutral = false; 452 t.friendly = friendly;
378 t.friendly = npc_is_friendly(idx, model, local_idx, neutral);
379 t.neutral = neutral; 453 t.neutral = neutral;
380 t.damage = npc_damage_class(idx, model); 454 t.damage = npc_damage_class(idx, model);
455 t.state = npc ? server_npc_state(idx) : -1;
456 t.weapon[0] = 0;
457 if (npc)
458 weapon_short_name(idx, t.weapon, sizeof(t.weapon));
381 short_name(model, t.name, sizeof(t.name)); 459 short_name(model, t.name, sizeof(t.name));
382 if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC"); 460 if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC");
383 } 461 }
@@ -562,12 +640,9 @@ static void draw_skeleton(const EspTarget &t) {
562 logd("skeleton: idx=%d bones=%d core=%d humanoid=%d", t.idx, n, core_count, 640 logd("skeleton: idx=%d bones=%d core=%d humanoid=%d", t.idx, n, core_count,
563 (int)bones_is_humanoid(t.idx)); 641 (int)bones_is_humanoid(t.idx));
564 } 642 }
565 if (t.friendly) 643 int sr, sg, sb;
566 surf_color(60, 255, 60, 255); 644 relation_rgb(t, sr, sg, sb);
567 else if (t.neutral) 645 surf_color(sr, sg, sb, 255);
568 surf_color(220, 220, 220, 255);
569 else
570 surf_color(255, 60, 60, 255);
571 for (int i = 0; i < n; i++) { 646 for (int i = 0; i < n; i++) {
572 if (!ok[i]) 647 if (!ok[i])
573 continue; 648 continue;
@@ -595,6 +670,24 @@ static QAngle angle_to(const Vector &from, const Vector &to) {
595 return a; 670 return a;
596} 671}
597 672
673// Server bullet direction adds m_Local.m_vecPunchAngle to the usercmd view
674// (GetAutoaimVector), so aim angles have to be pre-subtracted by it.
675static bool punch_angles(float &px, float &py) {
676 px = py = 0.0f;
677 if (!aim_punch_enabled() || g_net.punch < 0)
678 return false;
679 int local = engine_get_local_player();
680 if (local <= 0)
681 return false;
682 void *le = entitylist_get(local);
683 if (!le)
684 return false;
685 const float *p = (const float *)((uintptr_t)le + g_net.punch);
686 px = p[0];
687 py = p[1];
688 return std::isfinite(px) && std::isfinite(py);
689}
690
598static int g_aim_lock = -1; 691static int g_aim_lock = -1;
599static int g_silent_lock = -1; 692static int g_silent_lock = -1;
600 693
@@ -852,6 +945,12 @@ bool aimbot_apply(CUserCmd *cmd) {
852 if (!bone_mask) 945 if (!bone_mask)
853 return false; 946 return false;
854 QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; 947 QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f};
948 float punch_x = 0.0f, punch_y = 0.0f;
949 bool punch = punch_angles(punch_x, punch_y);
950 if (punch) {
951 view.x += punch_x;
952 view.y += punch_y;
953 }
855 int target = -1; 954 int target = -1;
856 Vector point{}; 955 Vector point{};
857 float fov = 0.0f; 956 float fov = 0.0f;
@@ -866,6 +965,10 @@ bool aimbot_apply(CUserCmd *cmd) {
866 if (projectile_lead_point(target, point, lead)) 965 if (projectile_lead_point(target, point, lead))
867 point = lead; 966 point = lead;
868 QAngle desired = angle_to(g_cam_eye, point); 967 QAngle desired = angle_to(g_cam_eye, point);
968 if (punch) {
969 desired.x -= punch_x;
970 desired.y -= punch_y;
971 }
869 static int want_logs = 0; 972 static int want_logs = 0;
870 if (want_logs < 10) { 973 if (want_logs < 10) {
871 want_logs++; 974 want_logs++;
@@ -930,20 +1033,30 @@ bool silent_aim_apply(CUserCmd *cmd) {
930 return false; 1033 return false;
931 QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; 1034 QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z};
932 QAngle view{orig.x, orig.y, 0.0f}; 1035 QAngle view{orig.x, orig.y, 0.0f};
1036 float punch_x = 0.0f, punch_y = 0.0f;
1037 bool punch = punch_angles(punch_x, punch_y);
1038 if (punch) {
1039 view.x += punch_x;
1040 view.y += punch_y;
1041 }
933 int target = -1; 1042 int target = -1;
934 Vector point{}; 1043 Vector point{};
935 float fov = 0.0f; 1044 float fov = 0.0f;
936 float max_fov = (float)silentaim_fov(); 1045 float max_fov = (float)aimbot_fov();
937 if (max_fov < 1.0f) max_fov = 1.0f; 1046 if (max_fov < 1.0f) max_fov = 1.0f;
938 if (max_fov > 180.0f) max_fov = 180.0f; 1047 if (max_fov > 180.0f) max_fov = 180.0f;
939 int *lock = feature_has_bind(FEAT_SILENTAIM) ? &g_silent_lock : nullptr; 1048 int *lock = feature_has_bind(FEAT_SILENTAIM) ? &g_silent_lock : nullptr;
940 if (!select_aim_target(view, bone_mask, true, max_fov, "silentaim", lock, 1049 if (!select_aim_target(view, bone_mask, aimbot_vischeck_enabled(), max_fov,
941 target, point, fov)) 1050 "silentaim", lock, target, point, fov))
942 return false; 1051 return false;
943 Vector lead; 1052 Vector lead;
944 if (projectile_lead_point(target, point, lead)) 1053 if (projectile_lead_point(target, point, lead))
945 point = lead; 1054 point = lead;
946 QAngle desired = angle_to(g_cam_eye, point); 1055 QAngle desired = angle_to(g_cam_eye, point);
1056 if (punch) {
1057 desired.x -= punch_x;
1058 desired.y -= punch_y;
1059 }
947 float np = desired.x; 1060 float np = desired.x;
948 if (np > 89.0f) np = 89.0f; 1061 if (np > 89.0f) np = 89.0f;
949 if (np < -89.0f) np = -89.0f; 1062 if (np < -89.0f) np = -89.0f;
@@ -1215,6 +1328,11 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1215 1328
1216 QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; 1329 QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z};
1217 QAngle desired = angle_to(g_cam_eye, aim); 1330 QAngle desired = angle_to(g_cam_eye, aim);
1331 float punch_x = 0.0f, punch_y = 0.0f;
1332 if (punch_angles(punch_x, punch_y)) {
1333 desired.x -= punch_x;
1334 desired.y -= punch_y;
1335 }
1218 float np = desired.x; 1336 float np = desired.x;
1219 if (np > 89.0f) np = 89.0f; 1337 if (np > 89.0f) np = 89.0f;
1220 if (np < -89.0f) np = -89.0f; 1338 if (np < -89.0f) np = -89.0f;
@@ -1415,6 +1533,11 @@ bool triggerbot_apply(CUserCmd *cmd) {
1415 return false; 1533 return false;
1416 1534
1417 QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f}; 1535 QAngle view{cmd->viewangles[0], cmd->viewangles[1], 0.0f};
1536 float punch_x = 0.0f, punch_y = 0.0f;
1537 if (punch_angles(punch_x, punch_y)) {
1538 view.x += punch_x;
1539 view.y += punch_y;
1540 }
1418 Vector fwd = angle_forward_vector(view); 1541 Vector fwd = angle_forward_vector(view);
1419 Vector start = g_cam_eye; 1542 Vector start = g_cam_eye;
1420 Vector end{start.x + fwd.x * 8192.0f, start.y + fwd.y * 8192.0f, 1543 Vector end{start.x + fwd.x * 8192.0f, start.y + fwd.y * 8192.0f,
@@ -1513,6 +1636,181 @@ void prop_tint_update() {
1513 } 1636 }
1514} 1637}
1515 1638
1639// Small left-edge label, stacked above the box (10 px font + 1 px shadow).
1640static void draw_left_tag(int left, int y, const char *text, int r, int g,
1641 int b) {
1642 int tw = (int)strlen(text) * 6;
1643 int tx = left - tw - 3;
1644 if (tx < 2)
1645 tx = 2;
1646 draw_text_small_col(tx + 1, y + 1, text, 0, 0, 0);
1647 draw_text_small_col(tx, y, text, r, g, b);
1648}
1649
1650static const char *alert_tag(int state, int &r, int &g, int &b) {
1651 switch (state) {
1652 case 1: r = 170; g = 180; b = 190; return "IDLE";
1653 case 2: r = 255; g = 220; b = 80; return "ALERT";
1654 case 3: r = 255; g = 90; b = 70; return "COMBAT";
1655 case 4: r = 150; g = 200; b = 255; return "SCRIPT";
1656 default: return nullptr;
1657 }
1658}
1659
1660// Direction markers for hostiles outside the view. Drawn at the screen edge
1661// using the engine projection when the target is in front, and the view-right
1662// dot product when it is behind.
1663static void draw_edge_arrows() {
1664 if (!g_surface || !g_scr_w || !g_narrows || !esp_option(ESP_OPT_ARROWS))
1665 return;
1666 float cx = (float)g_scr_w * 0.5f, cy = (float)g_scr_h * 0.5f;
1667 const float margin = 26.0f;
1668 int drawn = 0;
1669 for (int i = 0; i < g_narrows && drawn < 12; i++) {
1670 const ArrowTarget &a = g_arrows[i];
1671 float sx, sy;
1672 bool proj = world_to_screen(a.head, sx, sy);
1673 if (proj && sx >= 4.0f && sx <= (float)g_scr_w - 4.0f && sy >= 4.0f &&
1674 sy <= (float)g_scr_h - 4.0f)
1675 continue; // already visible as a box
1676 float dx, dy;
1677 if (proj) {
1678 dx = sx - cx;
1679 dy = sy - cy;
1680 } else {
1681 float yaw = g_cam_view.y * PI_F / 180.0f;
1682 float wx = a.head.x - g_cam_eye.x;
1683 float wy = a.head.y - g_cam_eye.y;
1684 float right = wx * sinf(yaw) + wy * (-cosf(yaw));
1685 dx = right >= 0.0f ? 1.0f : -1.0f;
1686 dy = 0.0f;
1687 }
1688 float len = sqrtf(dx * dx + dy * dy);
1689 if (len < 1e-3f) {
1690 dx = 0.0f;
1691 dy = 1.0f;
1692 len = 1.0f;
1693 }
1694 dx /= len;
1695 dy /= len;
1696 float half_w = cx - margin, half_h = cy - margin;
1697 float tx = fabsf(dx) > 1e-4f ? half_w / fabsf(dx) : 1e9f;
1698 float ty = fabsf(dy) > 1e-4f ? half_h / fabsf(dy) : 1e9f;
1699 float d = tx < ty ? tx : ty;
1700 float ex = cx + dx * d, ey = cy + dy * d;
1701 float px = -dy, py = dx;
1702 const int *col = a.neutral ? g_esp_col_neutral : g_esp_col_hostile;
1703 surf_color(col[0], col[1], col[2], 235);
1704 surf_line((int)ex, (int)ey, (int)(ex - dx * 13.0f + px * 6.0f),
1705 (int)(ey - dy * 13.0f + py * 6.0f));
1706 surf_line((int)ex, (int)ey, (int)(ex - dx * 13.0f - px * 6.0f),
1707 (int)(ey - dy * 13.0f - py * 6.0f));
1708 drawn++;
1709 }
1710}
1711
1712// Map transition triggers are server-only brush entities. Draw their world
1713// bounds and the target map; the previous map is remembered across level
1714// changes from the worldspawn model name.
1715struct TransitionBox {
1716 char next[128];
1717 Vector mins, maxs;
1718};
1719static TransitionBox g_trans[8];
1720static int g_ntrans = 0;
1721static char g_map_last[64];
1722static char g_map_prev[64];
1723
1724static void map_name_from_world(char *out, size_t outn) {
1725 out[0] = 0;
1726 void *w = entitylist_get(0);
1727 const char *m = w ? entity_model_name(w) : nullptr;
1728 if (!m)
1729 return;
1730 const char *b = strrchr(m, '/');
1731 b = b ? b + 1 : m;
1732 size_t n = strcspn(b, ".");
1733 if (n >= outn)
1734 n = outn - 1;
1735 memcpy(out, b, n);
1736 out[n] = 0;
1737}
1738
1739static void transitions_scan() {
1740 char cur[64];
1741 map_name_from_world(cur, sizeof(cur));
1742 if (strcmp(cur, g_map_last) != 0) {
1743 if (g_map_last[0])
1744 snprintf(g_map_prev, sizeof(g_map_prev), "%s", g_map_last);
1745 snprintf(g_map_last, sizeof(g_map_last), "%s", cur);
1746 }
1747 int count = server_entity_count();
1748 int n = 0;
1749 for (int i = 1; i <= count && n < 8; i++) {
1750 TransitionBox &b = g_trans[n];
1751 b.mins = Vector{0, 0, 0};
1752 b.maxs = Vector{0, 0, 0};
1753 if (!server_trigger_info(i, b.next, sizeof(b.next), b.mins, b.maxs))
1754 continue;
1755 if (b.mins.x == b.maxs.x && b.mins.y == b.maxs.y &&
1756 b.mins.z == b.maxs.z)
1757 continue;
1758 n++;
1759 }
1760 g_ntrans = n;
1761 static int logged = 0;
1762 if (n > 0 && logged < 4) {
1763 logged++;
1764 logf("transitions: %d changelevel trigger(s), next=%s prev=%s", n,
1765 g_trans[0].next, g_map_prev[0] ? g_map_prev : "?");
1766 }
1767}
1768
1769static void draw_transitions() {
1770 if (!g_surface || !g_ntrans)
1771 return;
1772 for (int i = 0; i < g_ntrans; i++) {
1773 const TransitionBox &b = g_trans[i];
1774 float minx = 1e9f, miny = 1e9f, maxx = -1e9f, maxy = -1e9f;
1775 int seen = 0;
1776 for (int c = 0; c < 8; c++) {
1777 Vector p{(c & 1) ? b.maxs.x : b.mins.x, (c & 2) ? b.maxs.y : b.mins.y,
1778 (c & 4) ? b.maxs.z : b.mins.z};
1779 float sx, sy;
1780 if (!world_to_screen(p, sx, sy) || !std::isfinite(sx) ||
1781 !std::isfinite(sy))
1782 continue;
1783 if (sx < minx) minx = sx;
1784 if (sx > maxx) maxx = sx;
1785 if (sy < miny) miny = sy;
1786 if (sy > maxy) maxy = sy;
1787 seen++;
1788 }
1789 if (!seen)
1790 continue;
1791 int x0 = (int)minx, x1 = (int)maxx, y0 = (int)miny, y1 = (int)maxy;
1792 if (x1 < -2000 || x0 > g_scr_w + 2000 || y1 < -2000 ||
1793 y0 > g_scr_h + 2000)
1794 continue;
1795 surf_color(255, 170, 60, 200);
1796 surf_rect(0, x0, y0, x1, y1);
1797 char line[128];
1798 snprintf(line, sizeof(line), "-> %.100s", b.next);
1799 int tx = x0;
1800 if (tx < 4) tx = 4;
1801 if (tx > g_scr_w - 120) tx = g_scr_w - 120;
1802 int ty = y0 - 16;
1803 if (ty < 4) ty = 4;
1804 draw_text_small_col(tx + 1, ty + 1, line, 0, 0, 0);
1805 draw_text_small_col(tx, ty, line, 255, 180, 70);
1806 if (g_map_prev[0]) {
1807 snprintf(line, sizeof(line), "prev %.48s", g_map_prev);
1808 draw_text_small_col(tx + 1, ty + 13, line, 0, 0, 0);
1809 draw_text_small_col(tx, ty + 12, line, 200, 200, 200);
1810 }
1811 }
1812}
1813
1516void esp_draw() { 1814void esp_draw() {
1517 if (!g_surface || !g_iface.entitylist || !g_net.esp_ok()) return; 1815 if (!g_surface || !g_iface.entitylist || !g_net.esp_ok()) return;
1518 if (!g_scr_w) { 1816 if (!g_scr_w) {
@@ -1523,6 +1821,13 @@ void esp_draw() {
1523 } 1821 }
1524 draw_fov_circle(); 1822 draw_fov_circle();
1525 esp_collect(engine_get_local_player()); 1823 esp_collect(engine_get_local_player());
1824 static double last_trans = 0.0;
1825 double trans_now = esp_now();
1826 if (trans_now - last_trans >= 0.25) {
1827 last_trans = trans_now;
1828 transitions_scan();
1829 }
1830 draw_transitions();
1526 for (int i = 0; i < g_ntargets; i++) { 1831 for (int i = 0; i < g_ntargets; i++) {
1527 const EspTarget &t = g_targets[i]; 1832 const EspTarget &t = g_targets[i];
1528 if (t.neutral && !esp_option(ESP_OPT_SHOW_NEUTRAL)) 1833 if (t.neutral && !esp_option(ESP_OPT_SHOW_NEUTRAL))
@@ -1532,9 +1837,9 @@ void esp_draw() {
1532 if (ent && bones_update(t.idx, ent)) 1837 if (ent && bones_update(t.idx, ent))
1533 draw_skeleton(t); 1838 draw_skeleton(t);
1534 } 1839 }
1535 if (t.friendly) surf_color(60, 255, 60, 255); 1840 int cr, cg, cb;
1536 else if (t.neutral) surf_color(220, 220, 220, 255); 1841 relation_rgb(t, cr, cg, cb);
1537 else surf_color(255, 60, 60, 255); 1842 surf_color(cr, cg, cb, 255);
1538 float w = t.h * 0.45f; // ponytail: constant aspect, no OBB width 1843 float w = t.h * 0.45f; // ponytail: constant aspect, no OBB width
1539 int left = (int)(t.sx - w / 2); 1844 int left = (int)(t.sx - w / 2);
1540 int right = (int)(t.sx + w / 2); 1845 int right = (int)(t.sx + w / 2);
@@ -1568,7 +1873,8 @@ void esp_draw() {
1568 draw_text_col(num_x, top, hptext, 255, 255, 255); 1873 draw_text_col(num_x, top, hptext, 255, 255, 255);
1569 } 1874 }
1570 if (esp_option(ESP_OPT_SHOW_NAME)) 1875 if (esp_option(ESP_OPT_SHOW_NAME))
1571 draw_text_col(left, top - 14, t.name, 255, 255, 255); 1876 draw_text_col(left, top - 14, t.name, cr, cg, cb);
1877 int tag_y = top + 2;
1572 if (esp_option(ESP_OPT_DAMAGE_TAGS)) { 1878 if (esp_option(ESP_OPT_DAMAGE_TAGS)) {
1573 const char *tag = nullptr; 1879 const char *tag = nullptr;
1574 int tr = 255, tg = 255, tb = 255; 1880 int tr = 255, tg = 255, tb = 255;
@@ -1583,19 +1889,26 @@ void esp_draw() {
1583 tr = 180; tg = 180; tb = 180; 1889 tr = 180; tg = 180; tb = 180;
1584 } 1890 }
1585 if (tag) { 1891 if (tag) {
1586 // Small label on the left edge of the box, black 1px shadow for 1892 draw_left_tag(left, tag_y, tag, tr, tg, tb);
1587 // readability against the world (the 10px ESP font). 1893 tag_y += 12;
1588 int tw = (int)strlen(tag) * 6;
1589 int tx = left - tw - 3;
1590 if (tx < 2)
1591 tx = 2;
1592 int ty = top + 2;
1593 draw_text_small_col(tx + 1, ty + 1, tag, 0, 0, 0);
1594 draw_text_small_col(tx, ty, tag, tr, tg, tb);
1595 } 1894 }
1596 } 1895 }
1896 if (esp_option(ESP_OPT_ALERT)) {
1897 int ar, ag, ab;
1898 const char *tag = alert_tag(t.state, ar, ag, ab);
1899 if (tag) {
1900 draw_left_tag(left, tag_y, tag, ar, ag, ab);
1901 tag_y += 12;
1902 }
1903 }
1904 if (esp_option(ESP_OPT_WEAPON) && t.weapon[0]) {
1905 draw_left_tag(left, tag_y, t.weapon, 220, 220, 220);
1906 tag_y += 12;
1907 }
1597 } 1908 }
1598 1909
1910 draw_edge_arrows();
1911
1599 // Groundwork probe: HL2H_BONE_DEBUG=1 draws a white marker at each NPC's 1912 // Groundwork probe: HL2H_BONE_DEBUG=1 draws a white marker at each NPC's
1600 // "head" hitbox center (from SetupBones + studio hitboxes). 1913 // "head" hitbox center (from SetupBones + studio hitboxes).
1601 static bool bone_debug = getenv("HL2H_BONE_DEBUG") != nullptr; 1914 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() {
318 add_card(0, &g_abh, "ABH", "Accelerate on the jump tick by facing away " 318 add_card(0, &g_abh, "ABH", "Accelerate on the jump tick by facing away "
319 "from the velocity.", true); 319 "from the velocity.", true);
320 320
321 int jump = add_card(0, nullptr, "Jump assist",
322 "Extra jump handling applied while moving.", true);
323 add_toggle(jump, "Auto crouch-jump",
324 "Duck during the ascent of a held jump, release while falling.",
325 &g_auto_crouchjump);
326 add_toggle(jump, "Edge jump",
327 "Jump when the ground ahead of a running player disappears.",
328 &g_edge_jump);
329
321 int jb = add_card(0, &g_jumpbug, "Jumpbug", 330 int jb = add_card(0, &g_jumpbug, "Jumpbug",
322 "Duck+jump when landing would hurt.", true); 331 "Duck+jump when landing would hurt.", true);
323 add_toggle(jb, "Assist timing", 332 add_toggle(jb, "Assist timing",
@@ -333,6 +342,9 @@ static void build_cards() {
333 &g_aim_smooth); 342 &g_aim_smooth);
334 add_toggle(aim, "Show FOV circle", "Draw the acquisition cone on screen.", 343 add_toggle(aim, "Show FOV circle", "Draw the acquisition cone on screen.",
335 &g_aim_fov_circle); 344 &g_aim_fov_circle);
345 add_toggle(aim, "Compensate aim punch",
346 "Aim against the server's recoil so shots land on target.",
347 &g_aim_punch);
336 add_section(aim, "Bones"); 348 add_section(aim, "Bones");
337 static const char *bone_names[AIM_BONE_COUNT] = { 349 static const char *bone_names[AIM_BONE_COUNT] = {
338 "Head", "Torso", "Left arm", "Right arm", "Left leg", "Right leg"}; 350 "Head", "Torso", "Left arm", "Right arm", "Left leg", "Right leg"};
@@ -344,10 +356,10 @@ static void build_cards() {
344 add_toggle(aim, bone_names[i], bone_descs[i], &g_aim_bones[i]); 356 add_toggle(aim, bone_names[i], bone_descs[i], &g_aim_bones[i]);
345 357
346 int sil = add_card(1, &g_silentaim, "Silent aim", 358 int sil = add_card(1, &g_silentaim, "Silent aim",
347 "Fire at the target without turning the rendered view.", 359 "Fire at the target without turning the rendered view. "
360 "Uses the Aimbot FOV, vischeck and bone settings.",
348 true); 361 true);
349 add_slider(sil, "FOV", "Maximum angular distance from the crosshair.", 1, 180, 362 add_info(sil, "Shares the Aimbot FOV, vischeck and bones.");
350 &g_silent_fov);
351 363
352 add_card(1, &g_triggerbot, "Triggerbot", 364 add_card(1, &g_triggerbot, "Triggerbot",
353 "Fire when the crosshair is on a hostile.", true); 365 "Fire when the crosshair is on a hostile.", true);
@@ -357,17 +369,27 @@ static void build_cards() {
357 369
358 int esp = add_card(2, &g_esp, "ESP", "Draw hostile NPC information.", true); 370 int esp = add_card(2, &g_esp, "ESP", "Draw hostile NPC information.", true);
359 static const char *esp_names[ESP_OPT_COUNT] = { 371 static const char *esp_names[ESP_OPT_COUNT] = {
360 "Show neutral", "Show healthbar", "Show healthnum", 372 "Show neutral", "Show healthbar", "Show healthnum",
361 "Show name", "Show skeleton", "Show box", 373 "Show name", "Show skeleton", "Show box",
362 "Show speed graph", "Damage tags"}; 374 "Show speed graph", "Damage tags", "Alert state",
375 "Enemy weapon", "Off-screen arrows"};
363 static const char *esp_descs[ESP_OPT_COUNT] = { 376 static const char *esp_descs[ESP_OPT_COUNT] = {
364 "Include neutral wildlife.", "Draw a health bar.", 377 "Include neutral wildlife.", "Draw a health bar.",
365 "Draw the numeric health value.", "Draw the model name.", 378 "Draw the numeric health value.", "Draw the model name.",
366 "Draw the animated bone links.", "Draw a bounding box.", 379 "Draw the animated bone links.", "Draw a bounding box.",
367 "Draw the lower-center speed graph.", 380 "Draw the lower-center speed graph.",
368 "Label ARMORED (needs blast/RPG) and VULN (gravity gun) units."}; 381 "Label ARMORED (needs blast/RPG) and VULN (gravity gun) units.",
382 "Label IDLE / ALERT / COMBAT.", "Label the NPC's active weapon.",
383 "Point toward hostiles that are outside the view."};
369 for (int i = 0; i < ESP_OPT_COUNT; i++) 384 for (int i = 0; i < ESP_OPT_COUNT; i++)
370 add_toggle(esp, esp_names[i], esp_descs[i], &g_esp_opt[i]); 385 add_toggle(esp, esp_names[i], esp_descs[i], &g_esp_opt[i]);
386 add_section(esp, "Colours");
387 add_color(esp, "Hostile", "Box, skeleton and name colour for hostiles.",
388 g_esp_col_hostile);
389 add_color(esp, "Neutral", "Box, skeleton and name colour for neutral units.",
390 g_esp_col_neutral);
391 add_color(esp, "Friendly", "Box, skeleton and name colour for friendlies.",
392 g_esp_col_friendly);
371 393
372 int items = add_card(2, &g_item_esp, "Valuable items", 394 int items = add_card(2, &g_item_esp, "Valuable items",
373 "Tag medkits, ammo, armor, weapons and supply crates.", true); 395 "Tag medkits, ammo, armor, weapons and supply crates.", true);
@@ -388,6 +410,18 @@ static void build_cards() {
388 10000, &g_item_max_dist); 410 10000, &g_item_max_dist);
389 g_ctrls[g_nctrl - 1].zero_unlimited = true; 411 g_ctrls[g_nctrl - 1].zero_unlimited = true;
390 412
413 int view = add_card(2, nullptr, "Camera / display",
414 "Camera FOV, viewmodel FOV and monitor gamma.", true);
415 add_slider(view, "Camera FOV",
416 "World camera field of view; 0 keeps the game default (75).", 0,
417 140, &g_fov_camera);
418 add_slider(view, "Viewmodel FOV",
419 "Weapon model field of view; 0 keeps the game default (54).", 0,
420 120, &g_fov_viewmodel);
421 add_slider(view, "Gamma x10",
422 "mat_monitorgamma x10: 16 = 1.6, 30 = 3.0, stock is 22.", 16, 30,
423 &g_gamma);
424
391 add_card(2, &g_fullbright, "Fullbright", 425 add_card(2, &g_fullbright, "Fullbright",
392 "Ignore world lighting so dark areas are visible.", true); 426 "Ignore world lighting so dark areas are visible.", true);
393 427
@@ -439,6 +473,10 @@ static void build_cards() {
439 add_toggle(spread, "No spread", 473 add_toggle(spread, "No spread",
440 "Override the FireBullets seed with the best offset.", 474 "Override the FireBullets seed with the best offset.",
441 &g_spread_no_spread); 475 &g_spread_no_spread);
476 add_toggle(spread, "Engine spread override",
477 "Zero the pellet random offsets directly (shotgun one-hole). "
478 "Off = seed selection only.",
479 &g_spread_engine);
442 480
443 int sys = add_card(4, nullptr, "System", "Configuration and maintenance.", 481 int sys = add_card(4, nullptr, "System", "Configuration and maintenance.",
444 true); 482 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) {
503 (uintptr_t)self == g_local_server_player; 503 (uintptr_t)self == g_local_server_player;
504 504
505 if (local && g_server.contains(ret) && feature_active(FEAT_SPREAD) && 505 if (local && g_server.contains(ret) && feature_active(FEAT_SPREAD) &&
506 spread_no_spread_enabled()) { 506 spread_no_spread_enabled() && spread_engine_enabled()) {
507 g_spread_rand_left = 8; 507 g_spread_rand_left = 8;
508 int pellets = g_spread_pellets; 508 int pellets = g_spread_pellets;
509 if (pellets < 1) 509 if (pellets < 1)
@@ -599,7 +599,7 @@ static bool g_uniform_hook_installed = false;
599 599
600static float hkdUniformRandomFloat(void *self, float mn, float mx) { 600static float hkdUniformRandomFloat(void *self, float mn, float mx) {
601 bool enabled = g_local_server_player && feature_active(FEAT_SPREAD) && 601 bool enabled = g_local_server_player && feature_active(FEAT_SPREAD) &&
602 spread_no_spread_enabled(); 602 spread_no_spread_enabled() && spread_engine_enabled();
603 bool hit = false; 603 bool hit = false;
604 void *who = nullptr; 604 void *who = nullptr;
605 if (enabled) { 605 if (enabled) {
@@ -654,6 +654,90 @@ static void install_uniform_random_hook() {
654 (void *)g_orig_uniform_float, (void *)&hkdUniformRandomFloat); 654 (void *)g_orig_uniform_float, (void *)&hkdUniformRandomFloat);
655} 655}
656 656
657// ---------------------------------------------------------------------------
658// camera / viewmodel FOV and gamma
659// ---------------------------------------------------------------------------
660//
661// The rendered camera FOV is derived from C_BasePlayer::m_iDefaultFOV every
662// frame (CViewRender::OnRenderStart copies it into default_fov), so writing
663// the client netvar is enough and needs no server command. The viewmodel FOV
664// and monitor gamma are plain client cvars.
665
666static void view_settings_apply(void *ent) {
667 if (g_net.default_fov >= 0 && ent) {
668 static int last = -2;
669 int want = g_fov_camera > 0 ? g_fov_camera : 0;
670 if (want != last || g_fov_camera > 0) {
671 *(int *)((uintptr_t)ent + g_net.default_fov) = want;
672 last = want;
673 }
674 }
675 // 0 means "leave the game's value alone": only send a command once the
676 // slider is non-zero, then restore the stock value when it returns to 0.
677 static int last_vm = 0;
678 if (g_fov_viewmodel > 0 && g_fov_viewmodel != last_vm) {
679 char cmd[48];
680 snprintf(cmd, sizeof(cmd), "viewmodel_fov %d", g_fov_viewmodel);
681 console_cmd(cmd);
682 last_vm = g_fov_viewmodel;
683 } else if (g_fov_viewmodel == 0 && last_vm > 0) {
684 console_cmd("viewmodel_fov 54");
685 last_vm = 0;
686 }
687 static int last_gamma = 22;
688 if (g_gamma != last_gamma) {
689 char cmd[48];
690 snprintf(cmd, sizeof(cmd), "mat_monitorgamma %d.%d", g_gamma / 10,
691 g_gamma % 10);
692 console_cmd(cmd);
693 last_gamma = g_gamma;
694 }
695}
696
697// ---------------------------------------------------------------------------
698// jump assist: crouch-jump and edge jump
699// ---------------------------------------------------------------------------
700
701static bool g_cj_added = false;
702
703static bool crouch_jump_apply(CUserCmd *cmd, int flags, const float *vel) {
704 bool user_duck = (cmd->buttons & IN_DUCK) != 0;
705 bool onground = (flags & FL_ONGROUND) != 0;
706 bool want = g_auto_crouchjump && !onground && !user_duck &&
707 (cmd->buttons & IN_JUMP) && vel[2] > 20.0f;
708 int old = cmd->buttons;
709 if (want) {
710 cmd->buttons |= IN_DUCK;
711 g_cj_added = true;
712 } else if (g_cj_added && !user_duck) {
713 cmd->buttons &= ~IN_DUCK;
714 g_cj_added = false;
715 }
716 return cmd->buttons != old;
717}
718
719static bool edge_jump_apply(CUserCmd *cmd, void *ent, int flags,
720 const float *vel) {
721 if (!g_edge_jump || g_net.origin < 0 || !(flags & FL_ONGROUND) ||
722 (flags & FL_INWATER))
723 return false;
724 if (cmd->buttons & (IN_JUMP | IN_DUCK))
725 return false;
726 float speed2d = sqrtf(vel[0] * vel[0] + vel[1] * vel[1]);
727 if (speed2d < 120.0f)
728 return false;
729 uintptr_t e = (uintptr_t)ent;
730 Vector origin{*(float *)(e + g_net.origin), *(float *)(e + g_net.origin + 4),
731 *(float *)(e + g_net.origin + 8)};
732 // Probe the ground a fraction of a second ahead. No hit within 64 units
733 // means a real ledge; slopes and stairs still find a floor and are skipped.
734 Vector ahead{origin.x + vel[0] * 0.12f, origin.y + vel[1] * 0.12f, origin.z};
735 if (!g_iface.enginetrace || point_ground(ahead, ent))
736 return false;
737 cmd->buttons |= IN_JUMP;
738 return true;
739}
740
657static void do_move(int seq, float frametime) { 741static void do_move(int seq, float frametime) {
658 install_seed_hook(); 742 install_seed_hook();
659 install_uniform_random_hook(); 743 install_uniform_random_hook();
@@ -683,6 +767,8 @@ static void do_move(int seq, float frametime) {
683 if (!ent) 767 if (!ent)
684 return; 768 return;
685 769
770 view_settings_apply(ent);
771
686 // New map / respawn: drop stale bone caches and target locks. 772 // New map / respawn: drop stale bone caches and target locks.
687 static void *last_local_ent = nullptr; 773 static void *last_local_ent = nullptr;
688 if (ent != last_local_ent) { 774 if (ent != last_local_ent) {
@@ -803,6 +889,12 @@ static void do_move(int seq, float frametime) {
803 } 889 }
804 } 890 }
805 891
892 bool jump_assist = crouch_jump_apply(cmd, flags, vel);
893 if (edge_jump_apply(cmd, ent, flags, vel))
894 jump_assist = true;
895 if (jump_assist)
896 note_modified(cmd, flags, vel);
897
806 if (jumpbug_apply(cmd, localIdx, ent, flags, vel, frametime)) 898 if (jumpbug_apply(cmd, localIdx, ent, flags, vel, frametime))
807 note_modified(cmd, flags, vel); 899 note_modified(cmd, flags, vel);
808 900
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 {
97 int owner = -1; // client m_hOwnerEntity handle 97 int owner = -1; // client m_hOwnerEntity handle
98 int weapon_owner = -1; // client m_hOwner handle (C_BaseCombatWeapon) 98 int weapon_owner = -1; // client m_hOwner handle (C_BaseCombatWeapon)
99 int clr_render = -1; // client m_clrRender (entity render colour) 99 int clr_render = -1; // client m_clrRender (entity render colour)
100 int punch = -1; // client m_vecPunchAngle (aim punch)
101 int default_fov = -1; // client m_iDefaultFOV (camera fov)
100 // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72 102 // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72
101 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; } 103 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; }
102 // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100. 104 // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100.
@@ -193,7 +195,10 @@ enum EspOption {
193 ESP_OPT_SHOW_BOX = 5, 195 ESP_OPT_SHOW_BOX = 5,
194 ESP_OPT_SHOW_SPEEDGRAPH = 6, 196 ESP_OPT_SHOW_SPEEDGRAPH = 6,
195 ESP_OPT_DAMAGE_TAGS = 7, 197 ESP_OPT_DAMAGE_TAGS = 7,
196 ESP_OPT_COUNT = 8 198 ESP_OPT_ALERT = 8,
199 ESP_OPT_WEAPON = 9,
200 ESP_OPT_ARROWS = 10,
201 ESP_OPT_COUNT = 11
197}; 202};
198enum AimBone { 203enum AimBone {
199 AIM_BONE_HEAD = 0, 204 AIM_BONE_HEAD = 0,
@@ -237,7 +242,16 @@ extern int g_aim_smooth;
237extern bool g_aim_vischeck; 242extern bool g_aim_vischeck;
238extern int g_aim_fov; 243extern int g_aim_fov;
239extern bool g_aim_fov_circle; 244extern bool g_aim_fov_circle;
240extern int g_silent_fov; 245extern bool g_aim_punch;
246extern bool g_spread_engine;
247extern bool g_auto_crouchjump;
248extern bool g_edge_jump;
249extern int g_fov_camera;
250extern int g_fov_viewmodel;
251extern int g_gamma;
252extern int g_esp_col_hostile[3];
253extern int g_esp_col_neutral[3];
254extern int g_esp_col_friendly[3];
241extern bool g_esp_opt[ESP_OPT_COUNT]; 255extern bool g_esp_opt[ESP_OPT_COUNT];
242 256
243const char *scancode_name(int sc); 257const char *scancode_name(int sc);
@@ -275,6 +289,11 @@ int server_npc_relation(int idx, int local_idx);
275bool server_npc_has_follow_target(int idx); 289bool server_npc_has_follow_target(int idx);
276int server_npc_state(int idx); 290int server_npc_state(int idx);
277int server_npc_takedamage(int idx); 291int server_npc_takedamage(int idx);
292int server_entity_count();
293void *server_entity_base(int idx);
294const char *server_entity_classname(int idx);
295bool server_trigger_info(int idx, char *map, size_t mapn, Vector &mins,
296 Vector &maxs);
278int server_npc_turret_flags(int idx); 297int server_npc_turret_flags(int idx);
279int server_model_index(void *base); 298int server_model_index(void *base);
280void find_modules(); 299void find_modules();
@@ -288,11 +307,12 @@ bool esp_option(int opt);
288bool bhop_autostrafe_enabled(); 307bool bhop_autostrafe_enabled();
289bool jumpbug_assist_enabled(); 308bool jumpbug_assist_enabled();
290bool spread_no_spread_enabled(); 309bool spread_no_spread_enabled();
310bool spread_engine_enabled();
311bool aim_punch_enabled();
291unsigned aimbot_bone_mask(); 312unsigned aimbot_bone_mask();
292int aimbot_smooth_step(); 313int aimbot_smooth_step();
293bool aimbot_vischeck_enabled(); 314bool aimbot_vischeck_enabled();
294int aimbot_fov(); 315int aimbot_fov();
295int silentaim_fov();
296bool aimbot_fov_circle(); 316bool aimbot_fov_circle();
297void aimbot_reset_lock(); 317void aimbot_reset_lock();
298void silentaim_reset_lock(); 318void silentaim_reset_lock();
@@ -310,6 +330,7 @@ bool is_visible(const Vector &eye, const Vector &point,
310 void *self = nullptr, void *target = nullptr); 330 void *self = nullptr, void *target = nullptr);
311void *trace_entity(const Vector &start, const Vector &end, void *ignore); 331void *trace_entity(const Vector &start, const Vector &end, void *ignore);
312float ground_distance(const Vector &origin, void *ignore, float max_dist); 332float ground_distance(const Vector &origin, void *ignore, float max_dist);
333bool point_ground(const Vector &point, void *ignore);
313 334
314// bones.cpp — client-side animated skeleton / hitbox access (see there) 335// bones.cpp — client-side animated skeleton / hitbox access (see there)
315bool bones_update(int idx, void *ent); 336bool 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
75bool g_aim_vischeck = true; 75bool g_aim_vischeck = true;
76int g_aim_fov = 60; // angular distance from the crosshair 76int g_aim_fov = 60; // angular distance from the crosshair
77bool g_aim_fov_circle = true; 77bool g_aim_fov_circle = true;
78int g_silent_fov = 60; 78bool g_aim_punch = true; // compensate server aim punch
79bool g_spread_engine = true; // zero the pellet random draws, not just the seed
80bool g_auto_crouchjump = false;
81bool g_edge_jump = false;
82int g_fov_camera = 0; // 0 = game default
83int g_fov_viewmodel = 0; // 0 = game default (54)
84int g_gamma = 22; // mat_monitorgamma x10, stock 2.2
85int g_esp_col_hostile[3] = {255, 60, 60};
86int g_esp_col_neutral[3] = {220, 220, 220};
87int g_esp_col_friendly[3] = {60, 255, 60};
79 88
80// ESP sub-options. Defaults match the pre-option behaviour: everything on. 89// ESP sub-options. Defaults match the pre-option behaviour: everything on.
81bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true, true, 90bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true,
82 true}; 91 true, true, true, true, true};
83 92
84// --------------------------------------------------------------------------- 93// ---------------------------------------------------------------------------
85// config persistence (INI format) 94// config persistence (INI format)
@@ -185,21 +194,23 @@ void config_save() {
185 fprintf(f, "[jumpbug]\nactive=%d\nbind=%d\nassist=%d\n", 194 fprintf(f, "[jumpbug]\nactive=%d\nbind=%d\nassist=%d\n",
186 g_jumpbug.active, g_jumpbug.bind, (int)g_jumpbug_assist); 195 g_jumpbug.active, g_jumpbug.bind, (int)g_jumpbug_assist);
187 fprintf(f, "[melee]\nactive=%d\nbind=%d\n", g_melee.active, g_melee.bind); 196 fprintf(f, "[melee]\nactive=%d\nbind=%d\n", g_melee.active, g_melee.bind);
188 fprintf(f, "[spread]\nactive=%d\nbind=%d\nno_spread=%d\n", 197 fprintf(f, "[spread]\nactive=%d\nbind=%d\nno_spread=%d\nengine=%d\n",
189 g_spread.active, g_spread.bind, (int)g_spread_no_spread); 198 g_spread.active, g_spread.bind, (int)g_spread_no_spread,
199 (int)g_spread_engine);
190 fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n" 200 fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n"
191 "fov=%d\nfov_circle=%d\n" 201 "fov=%d\nfov_circle=%d\npunch=%d\n"
192 "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n" 202 "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n"
193 "bone_lleg=%d\nbone_rleg=%d\n", 203 "bone_lleg=%d\nbone_rleg=%d\n",
194 g_aimbot.active, g_aimbot.bind, g_aim_smooth, 204 g_aimbot.active, g_aimbot.bind, g_aim_smooth,
195 (int)g_aim_vischeck, g_aim_fov, (int)g_aim_fov_circle, 205 (int)g_aim_vischeck, g_aim_fov, (int)g_aim_fov_circle,
196 g_aim_bones[AIM_BONE_HEAD], g_aim_bones[AIM_BONE_TORSO], 206 (int)g_aim_punch, g_aim_bones[AIM_BONE_HEAD],
197 g_aim_bones[AIM_BONE_LARM], g_aim_bones[AIM_BONE_RARM], 207 g_aim_bones[AIM_BONE_TORSO], g_aim_bones[AIM_BONE_LARM],
198 g_aim_bones[AIM_BONE_LLEG], g_aim_bones[AIM_BONE_RLEG]); 208 g_aim_bones[AIM_BONE_RARM], g_aim_bones[AIM_BONE_LLEG],
209 g_aim_bones[AIM_BONE_RLEG]);
199 fprintf(f, "[triggerbot]\nactive=%d\nbind=%d\n", g_triggerbot.active, 210 fprintf(f, "[triggerbot]\nactive=%d\nbind=%d\n", g_triggerbot.active,
200 g_triggerbot.bind); 211 g_triggerbot.bind);
201 fprintf(f, "[silentaim]\nactive=%d\nbind=%d\nfov=%d\n", g_silentaim.active, 212 fprintf(f, "[silentaim]\nactive=%d\nbind=%d\n", g_silentaim.active,
202 g_silentaim.bind, g_silent_fov); 213 g_silentaim.bind);
203 fprintf(f, "[fullbright]\nactive=%d\nbind=%d\n", g_fullbright.active, 214 fprintf(f, "[fullbright]\nactive=%d\nbind=%d\n", g_fullbright.active,
204 g_fullbright.bind); 215 g_fullbright.bind);
205 fprintf(f, "[worldtint]\nactive=%d\nbind=%d\nworld=%d\nsky=%d\n" 216 fprintf(f, "[worldtint]\nactive=%d\nbind=%d\nworld=%d\nsky=%d\n"
@@ -223,13 +234,24 @@ void config_save() {
223 (int)g_item_crates, (int)g_item_distance, g_item_max_dist); 234 (int)g_item_crates, (int)g_item_distance, g_item_max_dist);
224 fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" 235 fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n"
225 "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\nspeedgraph=%d\n" 236 "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\nspeedgraph=%d\n"
226 "dmg=%d\n", 237 "dmg=%d\nalert=%d\nweapon=%d\narrows=%d\n"
238 "hr=%d\nhg=%d\nhb=%d\nnr=%d\nng=%d\nnb=%d\n"
239 "fr=%d\nfg=%d\nfb=%d\n",
227 g_esp.active, g_esp.bind, g_esp_opt[ESP_OPT_SHOW_NEUTRAL], 240 g_esp.active, g_esp.bind, g_esp_opt[ESP_OPT_SHOW_NEUTRAL],
228 g_esp_opt[ESP_OPT_SHOW_HEALTHBAR], 241 g_esp_opt[ESP_OPT_SHOW_HEALTHBAR],
229 g_esp_opt[ESP_OPT_SHOW_HEALTHNUM], g_esp_opt[ESP_OPT_SHOW_NAME], 242 g_esp_opt[ESP_OPT_SHOW_HEALTHNUM], g_esp_opt[ESP_OPT_SHOW_NAME],
230 g_esp_opt[ESP_OPT_SHOW_SKELETON], g_esp_opt[ESP_OPT_SHOW_BOX], 243 g_esp_opt[ESP_OPT_SHOW_SKELETON], g_esp_opt[ESP_OPT_SHOW_BOX],
231 g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH], 244 g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH],
232 g_esp_opt[ESP_OPT_DAMAGE_TAGS]); 245 g_esp_opt[ESP_OPT_DAMAGE_TAGS], g_esp_opt[ESP_OPT_ALERT],
246 g_esp_opt[ESP_OPT_WEAPON], g_esp_opt[ESP_OPT_ARROWS],
247 g_esp_col_hostile[0], g_esp_col_hostile[1], g_esp_col_hostile[2],
248 g_esp_col_neutral[0], g_esp_col_neutral[1], g_esp_col_neutral[2],
249 g_esp_col_friendly[0], g_esp_col_friendly[1],
250 g_esp_col_friendly[2]);
251 fprintf(f, "[view]\nfov=%d\nvmfov=%d\ngamma=%d\n", g_fov_camera,
252 g_fov_viewmodel, g_gamma);
253 fprintf(f, "[movement]\ncrouchjump=%d\nedgejump=%d\n",
254 (int)g_auto_crouchjump, (int)g_edge_jump);
233 gui_config_write(f); 255 gui_config_write(f);
234 bool ok = ferror(f) == 0; 256 bool ok = ferror(f) == 0;
235 if (fflush(f) != 0) 257 if (fflush(f) != 0)
@@ -250,9 +272,9 @@ static bool config_load() {
250 return false; 272 return false;
251 char line[256]; 273 char line[256];
252 FeatureState *cur = nullptr; 274 FeatureState *cur = nullptr;
253 bool in_esp = false, in_bhop = false, in_aim = false, in_silent = false, 275 bool in_esp = false, in_bhop = false, in_aim = false,
254 in_jumpbug = false, in_spread = false, in_items = false, 276 in_jumpbug = false, in_spread = false, in_items = false,
255 in_tint = false, in_fog = false; 277 in_tint = false, in_fog = false, in_view = false, in_movement = false;
256 while (fgets(line, sizeof(line), f)) { 278 while (fgets(line, sizeof(line), f)) {
257 line[strcspn(line, "\r\n")] = 0; 279 line[strcspn(line, "\r\n")] = 0;
258 if (line[0] == '[') { 280 if (line[0] == '[') {
@@ -284,18 +306,23 @@ static bool config_load() {
284 cur = &g_triggerbot; 306 cur = &g_triggerbot;
285 else if (strstr(line, "[silentaim]")) 307 else if (strstr(line, "[silentaim]"))
286 cur = &g_silentaim; 308 cur = &g_silentaim;
309 else if (strstr(line, "[view]"))
310 cur = nullptr;
311 else if (strstr(line, "[movement]"))
312 cur = nullptr;
287 else 313 else
288 cur = nullptr; 314 cur = nullptr;
289 in_esp = (cur == &g_esp); 315 in_esp = (cur == &g_esp);
290 in_bhop = (cur == &g_bhop); 316 in_bhop = (cur == &g_bhop);
291 in_aim = (cur == &g_aimbot); 317 in_aim = (cur == &g_aimbot);
292 in_silent = (cur == &g_silentaim);
293 in_jumpbug = (cur == &g_jumpbug); 318 in_jumpbug = (cur == &g_jumpbug);
294 in_spread = (cur == &g_spread); 319 in_spread = (cur == &g_spread);
295 in_items = (cur == &g_item_esp); 320 in_items = (cur == &g_item_esp);
296 in_tint = (cur == &g_world_tint); 321 in_tint = (cur == &g_world_tint);
297 in_fog = (cur == &g_fog); 322 in_fog = (cur == &g_fog);
298 } else if (cur) { 323 in_view = strstr(line, "[view]") != nullptr;
324 in_movement = strstr(line, "[movement]") != nullptr;
325 } else if (cur || in_view || in_movement) {
299 char *eq = strchr(line, '='); 326 char *eq = strchr(line, '=');
300 if (!eq) 327 if (!eq)
301 continue; 328 continue;
@@ -307,8 +334,11 @@ static bool config_load() {
307 if (in_jumpbug && strcmp(key, "assist") == 0) { 334 if (in_jumpbug && strcmp(key, "assist") == 0) {
308 g_jumpbug_assist = atoi(val) != 0; 335 g_jumpbug_assist = atoi(val) != 0;
309 } 336 }
310 if (in_spread && strcmp(key, "no_spread") == 0) { 337 if (in_spread) {
311 g_spread_no_spread = atoi(val) != 0; 338 if (strcmp(key, "no_spread") == 0)
339 g_spread_no_spread = atoi(val) != 0;
340 else if (strcmp(key, "engine") == 0)
341 g_spread_engine = atoi(val) != 0;
312 } 342 }
313 if (in_items) { 343 if (in_items) {
314 if (strcmp(key, "health") == 0) 344 if (strcmp(key, "health") == 0)
@@ -357,13 +387,36 @@ static bool config_load() {
357 g_aim_fov = v; 387 g_aim_fov = v;
358 } else if (strcmp(key, "fov_circle") == 0) { 388 } else if (strcmp(key, "fov_circle") == 0) {
359 g_aim_fov_circle = atoi(val) != 0; 389 g_aim_fov_circle = atoi(val) != 0;
390 } else if (strcmp(key, "punch") == 0) {
391 g_aim_punch = atoi(val) != 0;
360 } 392 }
361 } 393 }
362 if (in_silent && strcmp(key, "fov") == 0) { 394 if (in_view) {
363 int v = atoi(val); 395 int *dst = nullptr;
364 if (v < 1) v = 1; 396 int lo = 0, hi = 0;
365 if (v > 180) v = 180; 397 if (strcmp(key, "fov") == 0) {
366 g_silent_fov = v; 398 dst = &g_fov_camera;
399 hi = 140;
400 } else if (strcmp(key, "vmfov") == 0) {
401 dst = &g_fov_viewmodel;
402 hi = 120;
403 } else if (strcmp(key, "gamma") == 0) {
404 dst = &g_gamma;
405 lo = 16;
406 hi = 30;
407 }
408 if (dst) {
409 int v = atoi(val);
410 if (v < lo) v = lo;
411 if (v > hi) v = hi;
412 *dst = v;
413 }
414 }
415 if (in_movement) {
416 if (strcmp(key, "crouchjump") == 0)
417 g_auto_crouchjump = atoi(val) != 0;
418 else if (strcmp(key, "edgejump") == 0)
419 g_edge_jump = atoi(val) != 0;
367 } 420 }
368 if (in_tint) { 421 if (in_tint) {
369 if (strcmp(key, "world") == 0) 422 if (strcmp(key, "world") == 0)
@@ -432,6 +485,31 @@ static bool config_load() {
432 g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH] = atoi(val) != 0; 485 g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH] = atoi(val) != 0;
433 else if (strcmp(key, "dmg") == 0) 486 else if (strcmp(key, "dmg") == 0)
434 g_esp_opt[ESP_OPT_DAMAGE_TAGS] = atoi(val) != 0; 487 g_esp_opt[ESP_OPT_DAMAGE_TAGS] = atoi(val) != 0;
488 else if (strcmp(key, "alert") == 0)
489 g_esp_opt[ESP_OPT_ALERT] = atoi(val) != 0;
490 else if (strcmp(key, "weapon") == 0)
491 g_esp_opt[ESP_OPT_WEAPON] = atoi(val) != 0;
492 else if (strcmp(key, "arrows") == 0)
493 g_esp_opt[ESP_OPT_ARROWS] = atoi(val) != 0;
494 else if (strcmp(key, "hr") == 0 || strcmp(key, "hg") == 0 ||
495 strcmp(key, "hb") == 0) {
496 int v = atoi(val);
497 if (v < 0) v = 0;
498 if (v > 255) v = 255;
499 g_esp_col_hostile[key[1] == 'r' ? 0 : key[1] == 'g' ? 1 : 2] = v;
500 } else if (strcmp(key, "nr") == 0 || strcmp(key, "ng") == 0 ||
501 strcmp(key, "nb") == 0) {
502 int v = atoi(val);
503 if (v < 0) v = 0;
504 if (v > 255) v = 255;
505 g_esp_col_neutral[key[1] == 'r' ? 0 : key[1] == 'g' ? 1 : 2] = v;
506 } else if (strcmp(key, "fr") == 0 || strcmp(key, "fg") == 0 ||
507 strcmp(key, "fb") == 0) {
508 int v = atoi(val);
509 if (v < 0) v = 0;
510 if (v > 255) v = 255;
511 g_esp_col_friendly[key[1] == 'r' ? 0 : key[1] == 'g' ? 1 : 2] = v;
512 }
435 } 513 }
436 if (strcmp(key, "active") == 0) 514 if (strcmp(key, "active") == 0)
437 cur->active = atoi(val) != 0; 515 cur->active = atoi(val) != 0;
@@ -513,7 +591,22 @@ void config_reset_defaults() {
513 g_aim_vischeck = true; 591 g_aim_vischeck = true;
514 g_aim_fov = 60; 592 g_aim_fov = 60;
515 g_aim_fov_circle = true; 593 g_aim_fov_circle = true;
516 g_silent_fov = 60; 594 g_aim_punch = true;
595 g_spread_engine = true;
596 g_auto_crouchjump = false;
597 g_edge_jump = false;
598 g_fov_camera = 0;
599 g_fov_viewmodel = 0;
600 g_gamma = 22;
601 g_esp_col_hostile[0] = 255;
602 g_esp_col_hostile[1] = 60;
603 g_esp_col_hostile[2] = 60;
604 g_esp_col_neutral[0] = 220;
605 g_esp_col_neutral[1] = 220;
606 g_esp_col_neutral[2] = 220;
607 g_esp_col_friendly[0] = 60;
608 g_esp_col_friendly[1] = 255;
609 g_esp_col_friendly[2] = 60;
517 config_save(); 610 config_save();
518} 611}
519 612
@@ -669,7 +762,9 @@ bool aimbot_vischeck_enabled() { return g_aim_vischeck; }
669 762
670int aimbot_fov() { return g_aim_fov; } 763int aimbot_fov() { return g_aim_fov; }
671 764
672int silentaim_fov() { return g_silent_fov; } 765bool aim_punch_enabled() { return g_aim_punch; }
766
767bool spread_engine_enabled() { return g_spread_engine; }
673 768
674bool aimbot_fov_circle() { return g_aim_fov_circle; } 769bool aimbot_fov_circle() { return g_aim_fov_circle; }
675 770
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) {
98 // colour lives well inside C_BaseEntity. 98 // colour lives well inside C_BaseEntity.
99 if (off >= 0x20 && off <= 0x2000) 99 if (off >= 0x20 && off <= 0x2000)
100 g_net.clr_render = off; 100 g_net.clr_render = off;
101 } else if (strcmp(pname2, "m_vecPunchAngle") == 0 && g_net.punch < 0) {
102 int off = (int)(base_off + (uint32_t)poff);
103 if (off >= 0x20 && off <= 0x2000)
104 g_net.punch = off;
105 } else if (strcmp(pname2, "m_iDefaultFOV") == 0 &&
106 g_net.default_fov < 0) {
107 int off = (int)(base_off + (uint32_t)poff);
108 if (off >= 0x20 && off <= 0x2000)
109 g_net.default_fov = off;
101 } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { 110 } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) {
102 g_net.teamnum = (int)(base_off + (uint32_t)poff); 111 g_net.teamnum = (int)(base_off + (uint32_t)poff);
103 } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { 112 } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) {
@@ -279,9 +288,11 @@ void resolve_netvars() {
279 logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d " 288 logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d "
280 "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d " 289 "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d "
281 "modelindex=%d absvelocity=%d activeweapon=%d owner=%d " 290 "modelindex=%d absvelocity=%d activeweapon=%d owner=%d "
282 "weapon_owner=%d clr_render=%d", 291 "weapon_owner=%d clr_render=%d punch=%d default_fov=%d",
283 g_net.flags, g_net.velocity, g_net.movetype, g_net.health, 292 g_net.flags, g_net.velocity, g_net.movetype, g_net.health,
284 g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset, 293 g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset,
285 g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex, 294 g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex,
286 g_net.absvelocity, g_net.activeweapon, g_net.clr_render); 295 g_net.absvelocity, g_net.activeweapon, g_net.owner,
296 g_net.weapon_owner, g_net.clr_render, g_net.punch,
297 g_net.default_fov);
287} 298}
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;
52static int g_srv_hacked = -1; 52static int g_srv_hacked = -1;
53static int g_srv_npcstate = -1; 53static int g_srv_npcstate = -1;
54static int g_srv_takedamage = -1; 54static int g_srv_takedamage = -1;
55static int g_srv_mapname = -1;
55static bool g_srv_ready = false; 56static bool g_srv_ready = false;
56static bool g_srv_checked = false; 57static bool g_srv_checked = false;
57 58
@@ -91,7 +92,7 @@ static int datamap_offset(const char *name, int want_type, int min_off = 0x40,
91 continue; 92 continue;
92 if (foff < min_off || foff > max_off) 93 if (foff < min_off || foff > max_off)
93 continue; 94 continue;
94 if (fsize < 1 || fsize > 64) 95 if (fsize < 1 || fsize > 128)
95 continue; 96 continue;
96 // DEFINE_KEYFIELD entries have an external name ("health"); plain 97 // DEFINE_KEYFIELD entries have an external name ("health"); plain
97 // DEFINE_FIELD entries have none. Either way it must be in server.so. 98 // DEFINE_FIELD entries have none. Either way it must be in server.so.
@@ -147,16 +148,19 @@ bool resolve_server_netvars() {
147 g_srv_takedamage = datamap_offset("m_takedamage", FIELD_CHARACTER); 148 g_srv_takedamage = datamap_offset("m_takedamage", FIELD_CHARACTER);
148 if (g_srv_takedamage < 0) 149 if (g_srv_takedamage < 0)
149 g_srv_takedamage = datamap_offset("m_takedamage", FIELD_INTEGER); 150 g_srv_takedamage = datamap_offset("m_takedamage", FIELD_INTEGER);
151 // CChangeLevel::m_szMapName is a char[cchMapNameMost] on trigger_changelevel.
152 g_srv_mapname = datamap_offset("m_szMapName", FIELD_CHARACTER, 0x40, 0x4000);
150 g_srv_ready = g_srv_health > 0; 153 g_srv_ready = g_srv_health > 0;
151 if (g_srv_ready) { 154 if (g_srv_ready) {
152 logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " 155 logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d "
153 "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " 156 "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d "
154 "followtarget=%d spawnflags=%d hacked=%d npcstate=%d " 157 "followtarget=%d spawnflags=%d hacked=%d npcstate=%d "
155 "takedamage=%d", 158 "takedamage=%d mapname=%d",
156 g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, 159 g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate,
157 g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, 160 g_srv_modelindex, g_srv_absvelocity, g_srv_velocity,
158 g_srv_activeweapon, g_srv_relationship, g_srv_followtarget, 161 g_srv_activeweapon, g_srv_relationship, g_srv_followtarget,
159 g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage); 162 g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage,
163 g_srv_mapname);
160 fail_logged = false; 164 fail_logged = false;
161 } else if (!fail_logged) { 165 } else if (!fail_logged) {
162 fail_logged = true; 166 fail_logged = true;
@@ -223,6 +227,92 @@ int server_model_index(void *base) {
223 return (int)*(short *)((uintptr_t)base + g_srv_modelindex); 227 return (int)*(short *)((uintptr_t)base + g_srv_modelindex);
224} 228}
225 229
230int server_entity_count() {
231 if (!g_iface.engineserver)
232 return 0;
233 using CountFn = int (*)(void *);
234 int n = vfunc<CountFn>(g_iface.engineserver, SRV_GetEntityCount)(
235 g_iface.engineserver);
236 return (n > 0 && n <= 8192) ? n : 0;
237}
238
239static void *edict_unknown(int idx) {
240 if (!g_iface.engineserver)
241 return nullptr;
242 using PEntityFn = void *(*)(void *, int);
243 void *edict = vfunc<PEntityFn>(g_iface.engineserver, SRV_PEntityOfEntIndex)(
244 g_iface.engineserver, idx);
245 if (!edict)
246 return nullptr;
247 return *(void **)((uintptr_t)edict + EDICT_UNKNOWN_OFF);
248}
249
250void *server_entity_base(int idx) {
251 void *unk = edict_unknown(idx);
252 if (!unk)
253 return nullptr;
254 using GetBaseFn = void *(*)(void *);
255 return vfunc<GetBaseFn>(unk, SRV_GetBaseEntity)(unk);
256}
257
258// Linked entity class name (e.g. "weapon_smg1", "trigger_changelevel").
259const char *server_entity_classname(int idx) {
260 void *unk = edict_unknown(idx);
261 if (!unk)
262 return nullptr;
263 using GetNetFn = void *(*)(void *);
264 void *net = vfunc<GetNetFn>(unk, 5)(unk);
265 if (!net)
266 return nullptr;
267 using NameFn = const char *(*)(void *);
268 return vfunc<NameFn>(net, 3)(net);
269}
270
271// A trigger_changelevel's target map and world-space brush bounds. The class
272// name comes from IServerNetworkable::GetClassName (slot 3); the volume from
273// ICollideable::WorldSpaceSurroundingBounds (slot 17, no virtual dtor).
274bool server_trigger_info(int idx, char *map, size_t mapn, Vector &mins,
275 Vector &maxs) {
276 if (!g_srv_ready || g_srv_mapname < 0)
277 return false;
278 void *unk = edict_unknown(idx);
279 if (!unk)
280 return false;
281 using GetNetFn = void *(*)(void *);
282 void *net = vfunc<GetNetFn>(unk, 5)(unk);
283 if (!net)
284 return false;
285 using NameFn = const char *(*)(void *);
286 const char *cls = vfunc<NameFn>(net, 3)(net);
287 if (!cls || !strcasestr(cls, "changelevel"))
288 return false;
289 void *base = server_entity_base(idx);
290 if (!base)
291 return false;
292 const char *m = (const char *)((uintptr_t)base + g_srv_mapname);
293 size_t n = strnlen(m, 32); // cchMapNameMost
294 if (n == 0 || n >= 32)
295 return false;
296 for (size_t i = 0; i < n; i++) {
297 unsigned char c = (unsigned char)m[i];
298 if (c < 0x20 || c > 0x7e)
299 return false;
300 }
301 if (map && mapn) {
302 if (n >= mapn)
303 n = mapn - 1;
304 memcpy(map, m, n);
305 map[n] = 0;
306 }
307 using GetCollFn = void *(*)(void *);
308 void *coll = vfunc<GetCollFn>(unk, 4)(unk);
309 if (coll) {
310 using BoundsFn = void (*)(void *, Vector *, Vector *);
311 vfunc<BoundsFn>(coll, 17)(coll, &mins, &maxs);
312 }
313 return true;
314}
315
226// Set the authoritative vertical velocity of a server entity. m_vecAbsVelocity 316// Set the authoritative vertical velocity of a server entity. m_vecAbsVelocity
227// is what CGameMovement::SetupMove reads through GetAbsVelocity(); writing the 317// is what CGameMovement::SetupMove reads through GetAbsVelocity(); writing the
228// networked m_vecVelocity directly is deliberately avoided because the server 318// 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) {
232 return tr.ent; 232 return tr.ent;
233} 233}
234 234
235// True when a downward point trace from just above `point` finds a standable
236// surface. Used by edge jump: a ledge has no floor within the probe, slopes and
237// stairs do.
238bool point_ground(const Vector &point, void *ignore) {
239 if (!g_iface.enginetrace)
240 return false;
241 RayCompat ray;
242 Vector start{point.x, point.y, point.z + 4.0f};
243 Vector end{point.x, point.y, point.z - 96.0f};
244 make_ray(ray, start, end);
245 GameTraceCompat tr{};
246 EntityFilter filter(ignore);
247 using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *,
248 GameTraceCompat *);
249 vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray,
250 MASK_PLAYERSOLID, &filter, &tr);
251 // A start inside geometry means a wall/step there, not a ledge.
252 return tr.startsolid || tr.fraction < 1.0f;
253}
254
235// Vertical distance from the standing player hull's feet to the first 255// Vertical distance from the standing player hull's feet to the first
236// standable surface below. Matches CategorizePosition's 2-unit probe and its 256// standable surface below. Matches CategorizePosition's 2-unit probe and its
237// plane.normal.z >= 0.7 rule, but sweeps much farther so the caller can 257// plane.normal.z >= 0.7 rule, but sweeps much farther so the caller can