diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 14:28:21 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 14:28:21 +0100 |
| commit | 714262bcf759a238daf906de16e7b204712a104f (patch) | |
| tree | d208ecfbbdb0f5f2f27aa888c806e38198ad1d52 | |
| parent | Remove gamma slider; keep HDR brightness (diff) | |
| download | hl2h-714262bcf759a238daf906de16e7b204712a104f.tar.gz hl2h-714262bcf759a238daf906de16e7b204712a104f.zip | |
Add usable item filter, projectile ESP and gravity-gun melee
- Items: "Only if usable" hides health tags at full health and armor at full
suit. Health ceiling is the server player's m_iMaxHealth; armor uses the
server m_ArmorValue against HL2's 100 cap. Fails open when unreadable.
- Projectiles: new Visuals feature/card for thrown grenades, RPG rockets,
helicopter bombs and AR2 balls. Fuse from server m_flDetonateTime vs engine
time; radius from m_DmgRadius with per-type fallback; optional 24-segment
radius ring and FRAG timer label.
- Melee: the killbot now handles the gravity gun as well; only VULNERABLE
targets, 1200-unit range, primary attack to launch the held object, and no
aim-punch compensation because the launch uses EyeVectors.
- Server/netvars: m_ArmorValue, m_flDetonateTime, m_DmgRadius.
| -rw-r--r-- | CONTEXT.md | 42 | ||||
| -rw-r--r-- | src/esp.cpp | 283 | ||||
| -rw-r--r-- | src/gui.cpp | 15 | ||||
| -rw-r--r-- | src/hl2h.cpp | 11 | ||||
| -rw-r--r-- | src/hl2h.h | 15 | ||||
| -rw-r--r-- | src/menu.cpp | 39 | ||||
| -rw-r--r-- | src/netvars.cpp | 10 | ||||
| -rw-r--r-- | src/server.cpp | 49 |
8 files changed, 429 insertions, 35 deletions
| @@ -310,10 +310,16 @@ vocabulary / decisions. | |||
| 310 | 310 | ||
| 311 | ## Melee killbot | 311 | ## Melee killbot |
| 312 | 312 | ||
| 313 | - Active only while the local player's active weapon is the crowbar. The | 313 | - Active only while the local player's active weapon is the crowbar or the |
| 314 | client `m_hActiveWeapon` handle is used first; if it is missing, the | 314 | gravity gun. The client `m_hActiveWeapon` handle is used first; if it is |
| 315 | authoritative server `m_hActiveWeapon` handle (0x824) is used to get the | 315 | missing, the authoritative server `m_hActiveWeapon` handle (0x824) is used |
| 316 | weapon entity index. | 316 | to get the weapon entity index. |
| 317 | - With the gravity gun, only `VULNERABLE` targets (headcrab, antlion, | ||
| 318 | manhack) are selected, the range is 1200 units, and the attack button is | ||
| 319 | `IN_ATTACK`: the primary attack launches a held object along | ||
| 320 | `EyeVectors()`. No aim-punch compensation is applied there (EyeVectors does | ||
| 321 | not add punch); the crowbar path keeps it. Without a held object the primary | ||
| 322 | is a short physics punch. | ||
| 317 | - Map transitions often switch the active weapon away from the crowbar. The | 323 | - Map transitions often switch the active weapon away from the crowbar. The |
| 318 | state change is logged as `melee: active ... crowbar=0/1` (normal log), and | 324 | state change is logged as `melee: active ... crowbar=0/1` (normal log), and |
| 319 | a one-time toast says to equip the crowbar. If the client weapon model is | 325 | a one-time toast says to equip the crowbar. If the client weapon model is |
| @@ -328,9 +334,9 @@ vocabulary / decisions. | |||
| 328 | movement keys are rotated with `preserve_movement`, and the rendered view is | 334 | movement keys are rotated with `preserve_movement`, and the rendered view is |
| 329 | restored immediately and at `FRAME_RENDER_START` (same silent-aim path). | 335 | restored immediately and at `FRAME_RENDER_START` (same silent-aim path). |
| 330 | This gives a 360-degree silent melee attack. | 336 | This gives a 360-degree silent melee attack. |
| 331 | - While the crowbar is active and the killbot feature is on, the gun aimbot, | 337 | - While either melee weapon is active and the killbot feature is on, the gun |
| 332 | triggerbot and silent aim are skipped to avoid fighting over the usercmd | 338 | aimbot, triggerbot and silent aim are skipped to avoid fighting over the |
| 333 | view. | 339 | usercmd view. |
| 334 | - Vischeck uses the shared `MASK_VISIBLE`/world-only path (static props are | 340 | - Vischeck uses the shared `MASK_VISIBLE`/world-only path (static props are |
| 335 | ignored). | 341 | ignored). |
| 336 | 342 | ||
| @@ -583,6 +589,11 @@ vocabulary / decisions. | |||
| 583 | ## Valuable items | 589 | ## Valuable items |
| 584 | 590 | ||
| 585 | - Visuals feature; Active is on by default and unbound (always-on). | 591 | - Visuals feature; Active is on by default and unbound (always-on). |
| 592 | - "Only if usable" (`[items] usable=1`) hides health pickups when the local | ||
| 593 | player is at full health and armor pickups at full suit. The health ceiling | ||
| 594 | is the server player's `m_iMaxHealth` (not a hardcoded 100); armor uses the | ||
| 595 | HL2 `MAX_NORMAL_BATTERY` cap of 100, read from the server | ||
| 596 | `m_ArmorValue`. If the values cannot be read the filter fails open. | ||
| 586 | - Scans the client entity list every 100 ms and tags health, ammo, armor/suit, | 597 | - Scans the client entity list every 100 ms and tags health, ammo, armor/suit, |
| 587 | world weapon pickups, the two supply-crate models and generic items. | 598 | world weapon pickups, the two supply-crate models and generic items. |
| 588 | - Each tag is a colored marker with a name and optional distance, filtered by | 599 | - Each tag is a colored marker with a name and optional distance, filtered by |
| @@ -601,6 +612,23 @@ vocabulary / decisions. | |||
| 601 | item crates. The item scan falls back to the server entity's model index | 612 | item crates. The item scan falls back to the server entity's model index |
| 602 | (`server_model_index`) to recover the model name. | 613 | (`server_model_index`) to recover the model name. |
| 603 | 614 | ||
| 615 | ## Projectile ESP | ||
| 616 | |||
| 617 | - Visuals feature `g_projectiles` (`[projectiles]`, Active on, unbound). | ||
| 618 | - Scans the client entity list every 100 ms for world-model `w_grenade`, | ||
| 619 | `w_missile*`, `helicopter_bomb` and `combineball`. Weapon entities are | ||
| 620 | excluded by class (`...weapon...`); thrown grenades are recognised by | ||
| 621 | `CGrenadeFrag`-style class names or by an owner handle. | ||
| 622 | - Fuse: `CBaseGrenade::m_flDetonateTime` is not networked, so it is read from | ||
| 623 | the server entity datamap (`FIELD_TIME`) and compared with | ||
| 624 | `IVEngineClient::Time()` (slot 14). RPG rockets have no fuse. | ||
| 625 | - Blast radius: networked client `m_DmgRadius` when available, server | ||
| 626 | `m_DmgRadius` (`FIELD_FLOAT`) as fallback, then 300/200/150/300 for | ||
| 627 | frag/RPG/AR2/bomb. | ||
| 628 | - Draws a small marker and a `FRAG 1.2s`-style label; the optional radius ring | ||
| 629 | is a 24-segment horizontal circle projected at the projectile height. | ||
| 630 | Toggles: Fuse timer, Blast radius. | ||
| 631 | |||
| 604 | ## Roadmap / TODO | 632 | ## Roadmap / TODO |
| 605 | 633 | ||
| 606 | - **Server-side spread/recoil control**: not planned; recoil and accuracy | 634 | - **Server-side spread/recoil control**: not planned; recoil and accuracy |
diff --git a/src/esp.cpp b/src/esp.cpp index 7baf2c8..c956d1b 100644 --- a/src/esp.cpp +++ b/src/esp.cpp | |||
| @@ -898,7 +898,8 @@ static bool active_weapon_contains(void *local_ent, const char *needle) { | |||
| 898 | void *net = entity_networkable(idx); | 898 | void *net = entity_networkable(idx); |
| 899 | const char *cls = net ? entity_class_name(net, nullptr) : nullptr; | 899 | const char *cls = net ? entity_class_name(net, nullptr) : nullptr; |
| 900 | const char *model = wep ? entity_model_name(wep) : nullptr; | 900 | const char *model = wep ? entity_model_name(wep) : nullptr; |
| 901 | return (cls && strstr(cls, needle)) || (model && strstr(model, needle)); | 901 | return (cls && strcasestr(cls, needle)) || |
| 902 | (model && strcasestr(model, needle)); | ||
| 902 | } | 903 | } |
| 903 | 904 | ||
| 904 | // Crossbow bolts and RPG rockets are not hitscan. Lead moving targets by the | 905 | // Crossbow bolts and RPG rockets are not hitscan. Lead moving targets by the |
| @@ -1175,7 +1176,35 @@ bool melee_weapon_active(void *local_ent) { | |||
| 1175 | return crowbar; | 1176 | return crowbar; |
| 1176 | } | 1177 | } |
| 1177 | 1178 | ||
| 1178 | bool melee_aimbot_apply(CUserCmd *cmd) { | 1179 | bool gravity_gun_active(void *local_ent) { |
| 1180 | if (!local_ent) | ||
| 1181 | return false; | ||
| 1182 | if (active_weapon_contains(local_ent, "physcannon")) | ||
| 1183 | return true; | ||
| 1184 | // Same server fallback as the crowbar: map transitions can leave the | ||
| 1185 | // client activeweapon handle stale. | ||
| 1186 | int local = engine_get_local_player(); | ||
| 1187 | if (local <= 0 || !g_iface.engineserver) | ||
| 1188 | return false; | ||
| 1189 | resolve_server_netvars(); | ||
| 1190 | int si = server_active_weapon(local); | ||
| 1191 | if (si <= 0) | ||
| 1192 | return false; | ||
| 1193 | void *wep = entitylist_get(si); | ||
| 1194 | void *net = entity_networkable(si); | ||
| 1195 | const char *cls = net ? entity_class_name(net, nullptr) : nullptr; | ||
| 1196 | const char *model = wep ? entity_model_name(wep) : nullptr; | ||
| 1197 | if (!model) { | ||
| 1198 | void *sbase = nullptr; | ||
| 1199 | server_entity_health(si, nullptr, nullptr, &sbase); | ||
| 1200 | if (sbase) | ||
| 1201 | model = model_name_from_index(server_model_index(sbase)); | ||
| 1202 | } | ||
| 1203 | return (cls && strcasestr(cls, "physcannon")) || | ||
| 1204 | (model && strcasestr(model, "physcannon")); | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | bool melee_aimbot_apply(CUserCmd *cmd, bool gravity) { | ||
| 1179 | if (!g_iface.entitylist || g_net.origin < 0) | 1208 | if (!g_iface.entitylist || g_net.origin < 0) |
| 1180 | return false; | 1209 | return false; |
| 1181 | int local = engine_get_local_player(); | 1210 | int local = engine_get_local_player(); |
| @@ -1188,6 +1217,9 @@ bool melee_aimbot_apply(CUserCmd *cmd) { | |||
| 1188 | *(int *)((uintptr_t)local_ent + g_net.health) <= 0) | 1217 | *(int *)((uintptr_t)local_ent + g_net.health) <= 0) |
| 1189 | return false; | 1218 | return false; |
| 1190 | 1219 | ||
| 1220 | // The punted object can travel much farther than the crowbar. | ||
| 1221 | float max_range = gravity ? 1200.0f : MELEE_RANGE; | ||
| 1222 | |||
| 1191 | using Fn = int (*)(void *); | 1223 | using Fn = int (*)(void *); |
| 1192 | int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); | 1224 | int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); |
| 1193 | if (hi < 0 || hi > 4096) | 1225 | if (hi < 0 || hi > 4096) |
| @@ -1226,6 +1258,9 @@ bool melee_aimbot_apply(CUserCmd *cmd) { | |||
| 1226 | continue; | 1258 | continue; |
| 1227 | if (!npc_can_be_damaged(idx, model)) | 1259 | if (!npc_can_be_damaged(idx, model)) |
| 1228 | continue; | 1260 | continue; |
| 1261 | // The gravity gun is only the tool for physics-vulnerable units. | ||
| 1262 | if (gravity && npc_damage_class(idx, model) != NPC_DMG_VULNERABLE) | ||
| 1263 | continue; | ||
| 1229 | 1264 | ||
| 1230 | Vector origin{*(float *)(e + g_net.origin), | 1265 | Vector origin{*(float *)(e + g_net.origin), |
| 1231 | *(float *)(e + g_net.origin + 4), | 1266 | *(float *)(e + g_net.origin + 4), |
| @@ -1265,7 +1300,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) { | |||
| 1265 | if (closest.z < lo.z) closest.z = lo.z; | 1300 | if (closest.z < lo.z) closest.z = lo.z; |
| 1266 | if (closest.z > hiV.z) closest.z = hiV.z; | 1301 | if (closest.z > hiV.z) closest.z = hiV.z; |
| 1267 | float d = vec_dist(g_cam_eye, closest); | 1302 | float d = vec_dist(g_cam_eye, closest); |
| 1268 | if (d > MELEE_RANGE) | 1303 | if (d > max_range) |
| 1269 | continue; | 1304 | continue; |
| 1270 | 1305 | ||
| 1271 | Vector center{(lo.x + hiV.x) * 0.5f, (lo.y + hiV.y) * 0.5f, | 1306 | Vector center{(lo.x + hiV.x) * 0.5f, (lo.y + hiV.y) * 0.5f, |
| @@ -1298,7 +1333,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) { | |||
| 1298 | float bestd = 1e9f; | 1333 | float bestd = 1e9f; |
| 1299 | for (int i = 0; i < np; i++) { | 1334 | for (int i = 0; i < np; i++) { |
| 1300 | float pd = vec_dist(g_cam_eye, pts[i]); | 1335 | float pd = vec_dist(g_cam_eye, pts[i]); |
| 1301 | if (pd > MELEE_RANGE || pd >= bestd) | 1336 | if (pd > max_range || pd >= bestd) |
| 1302 | continue; | 1337 | continue; |
| 1303 | if (!is_visible(g_cam_eye, pts[i], local_ent, tent)) | 1338 | if (!is_visible(g_cam_eye, pts[i], local_ent, tent)) |
| 1304 | continue; | 1339 | continue; |
| @@ -1311,7 +1346,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) { | |||
| 1311 | if (!have_aim) { | 1346 | if (!have_aim) { |
| 1312 | aim = c.center; | 1347 | aim = c.center; |
| 1313 | float ad = vec_dist(g_cam_eye, aim); | 1348 | float ad = vec_dist(g_cam_eye, aim); |
| 1314 | if (ad > MELEE_RANGE) { | 1349 | if (ad > max_range) { |
| 1315 | // The body is in range but its center is not: aim just inside the | 1350 | // The body is in range but its center is not: aim just inside the |
| 1316 | // closest point on the AABB. | 1351 | // closest point on the AABB. |
| 1317 | aim = c.closest; | 1352 | aim = c.closest; |
| @@ -1319,7 +1354,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) { | |||
| 1319 | aim.y += (c.center.y - c.closest.y) * 0.25f; | 1354 | aim.y += (c.center.y - c.closest.y) * 0.25f; |
| 1320 | aim.z += (c.center.z - c.closest.z) * 0.25f; | 1355 | aim.z += (c.center.z - c.closest.z) * 0.25f; |
| 1321 | ad = vec_dist(g_cam_eye, aim); | 1356 | ad = vec_dist(g_cam_eye, aim); |
| 1322 | if (ad > MELEE_RANGE) | 1357 | if (ad > max_range) |
| 1323 | aim = c.closest; | 1358 | aim = c.closest; |
| 1324 | } | 1359 | } |
| 1325 | if (!is_visible(g_cam_eye, aim, local_ent, tent)) | 1360 | if (!is_visible(g_cam_eye, aim, local_ent, tent)) |
| @@ -1328,8 +1363,10 @@ bool melee_aimbot_apply(CUserCmd *cmd) { | |||
| 1328 | 1363 | ||
| 1329 | QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; | 1364 | QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z}; |
| 1330 | QAngle desired = angle_to(g_cam_eye, aim); | 1365 | QAngle desired = angle_to(g_cam_eye, aim); |
| 1366 | // The crowbar uses GetAutoaimVector (adds punch); the gravity gun's | ||
| 1367 | // launch uses EyeVectors (no punch), so only compensate for melee. | ||
| 1331 | float punch_x = 0.0f, punch_y = 0.0f; | 1368 | float punch_x = 0.0f, punch_y = 0.0f; |
| 1332 | if (punch_angles(punch_x, punch_y)) { | 1369 | if (!gravity && punch_angles(punch_x, punch_y)) { |
| 1333 | desired.x -= punch_x; | 1370 | desired.x -= punch_x; |
| 1334 | desired.y -= punch_y; | 1371 | desired.y -= punch_y; |
| 1335 | } | 1372 | } |
| @@ -1350,13 +1387,15 @@ bool melee_aimbot_apply(CUserCmd *cmd) { | |||
| 1350 | using SetViewFn = void (*)(void *, QAngle *); | 1387 | using SetViewFn = void (*)(void *, QAngle *); |
| 1351 | vfunc<SetViewFn>(g_iface.engine, 20)(g_iface.engine, &restore); | 1388 | vfunc<SetViewFn>(g_iface.engine, 20)(g_iface.engine, &restore); |
| 1352 | } | 1389 | } |
| 1390 | // Gravity gun primary attack launches the held object (EyeVectors); | ||
| 1391 | // secondary would drop it instead. | ||
| 1353 | cmd->buttons |= IN_ATTACK; | 1392 | cmd->buttons |= IN_ATTACK; |
| 1354 | 1393 | ||
| 1355 | static int logs = 0; | 1394 | static int logs = 0; |
| 1356 | if (logs < 10) { | 1395 | if (logs < 10) { |
| 1357 | logs++; | 1396 | logs++; |
| 1358 | logd("melee: swing idx=%d dist=%.1f aim=(%.1f %.1f %.1f)", c.idx, | 1397 | logd("melee: %s idx=%d dist=%.1f aim=(%.1f %.1f %.1f)", |
| 1359 | c.dist, aim.x, aim.y, aim.z); | 1398 | gravity ? "punt" : "swing", c.idx, c.dist, aim.x, aim.y, aim.z); |
| 1360 | } | 1399 | } |
| 1361 | return true; | 1400 | return true; |
| 1362 | } | 1401 | } |
| @@ -1365,7 +1404,7 @@ bool melee_aimbot_apply(CUserCmd *cmd) { | |||
| 1365 | double now = esp_now(); | 1404 | double now = esp_now(); |
| 1366 | if (now - last_none >= 2.0) { | 1405 | if (now - last_none >= 2.0) { |
| 1367 | last_none = now; | 1406 | last_none = now; |
| 1368 | logd("melee: no visible target within %.0f", MELEE_RANGE); | 1407 | logd("melee: no visible target within %.0f", max_range); |
| 1369 | } | 1408 | } |
| 1370 | return false; | 1409 | return false; |
| 1371 | } | 1410 | } |
| @@ -2331,6 +2370,30 @@ static void items_collect(int local_idx) { | |||
| 2331 | } | 2370 | } |
| 2332 | } | 2371 | } |
| 2333 | 2372 | ||
| 2373 | // Authoritative local health/armor for the "only if usable" item filter. | ||
| 2374 | // The health ceiling comes from the server player's m_iMaxHealth, not a | ||
| 2375 | // hardcoded 100; when it cannot be read the filter fails open. | ||
| 2376 | static void local_vitals(int &hp, int &maxhp, int &armor) { | ||
| 2377 | hp = maxhp = armor = -1; | ||
| 2378 | int local = engine_get_local_player(); | ||
| 2379 | if (local <= 0) | ||
| 2380 | return; | ||
| 2381 | void *le = entitylist_get(local); | ||
| 2382 | if (le && g_net.health >= 0) | ||
| 2383 | hp = *(int *)((uintptr_t)le + g_net.health); | ||
| 2384 | if (g_iface.engineserver) { | ||
| 2385 | int smax = -1; | ||
| 2386 | int shp = server_entity_health(local, &smax, nullptr, nullptr); | ||
| 2387 | if (shp >= 0) | ||
| 2388 | hp = shp; | ||
| 2389 | if (smax > 0) | ||
| 2390 | maxhp = smax; | ||
| 2391 | armor = server_player_armor(local); | ||
| 2392 | } | ||
| 2393 | if (maxhp < 0 && le && g_net.maxhealth >= 0) | ||
| 2394 | maxhp = *(int *)((uintptr_t)le + g_net.maxhealth); | ||
| 2395 | } | ||
| 2396 | |||
| 2334 | void esp_draw_items() { | 2397 | void esp_draw_items() { |
| 2335 | if (!g_surface || !g_iface.entitylist || g_net.origin < 0) | 2398 | if (!g_surface || !g_iface.entitylist || g_net.origin < 0) |
| 2336 | return; | 2399 | return; |
| @@ -2348,10 +2411,21 @@ void esp_draw_items() { | |||
| 2348 | } | 2411 | } |
| 2349 | items_collect(local); | 2412 | items_collect(local); |
| 2350 | 2413 | ||
| 2414 | int vit_hp = -1, vit_max = -1, vit_armor = -1; | ||
| 2415 | if (g_item_usable) | ||
| 2416 | local_vitals(vit_hp, vit_max, vit_armor); | ||
| 2417 | |||
| 2351 | for (int i = 0; i < g_nitems; i++) { | 2418 | for (int i = 0; i < g_nitems; i++) { |
| 2352 | const ItemTarget &t = g_item_targets[i]; | 2419 | const ItemTarget &t = g_item_targets[i]; |
| 2353 | if (!item_kind_enabled(t.kind)) | 2420 | if (!item_kind_enabled(t.kind)) |
| 2354 | continue; | 2421 | continue; |
| 2422 | if (g_item_usable) { | ||
| 2423 | if (t.kind == IK_HEALTH && vit_hp >= 0 && vit_max > 0 && | ||
| 2424 | vit_hp >= vit_max) | ||
| 2425 | continue; | ||
| 2426 | if (t.kind == IK_ARMOR && vit_armor >= 0 && vit_armor >= 100) | ||
| 2427 | continue; | ||
| 2428 | } | ||
| 2355 | float sx, sy; | 2429 | float sx, sy; |
| 2356 | if (!world_to_screen(t.origin, sx, sy)) | 2430 | if (!world_to_screen(t.origin, sx, sy)) |
| 2357 | continue; | 2431 | continue; |
| @@ -2402,3 +2476,192 @@ void esp_draw_items() { | |||
| 2402 | draw_text_col(tx, ty, text, r, g, b); | 2476 | draw_text_col(tx, ty, text, r, g, b); |
| 2403 | } | 2477 | } |
| 2404 | } | 2478 | } |
| 2479 | |||
| 2480 | // --------------------------------------------------------------------------- | ||
| 2481 | // projectile ESP: grenades, rockets, helicopter bombs, AR2 balls | ||
| 2482 | // --------------------------------------------------------------------------- | ||
| 2483 | |||
| 2484 | enum ProjKind { PK_FRAG, PK_RPG, PK_BOMB, PK_BALL }; | ||
| 2485 | |||
| 2486 | struct ProjTarget { | ||
| 2487 | int idx; | ||
| 2488 | int kind; | ||
| 2489 | Vector origin; | ||
| 2490 | float fuse; // seconds; < 0 = impact / no fuse | ||
| 2491 | float radius; | ||
| 2492 | }; | ||
| 2493 | |||
| 2494 | static ProjTarget g_projs[32]; | ||
| 2495 | static int g_nprojs = 0; | ||
| 2496 | |||
| 2497 | static float engine_curtime() { | ||
| 2498 | if (!g_iface.engine) | ||
| 2499 | return 0.0f; | ||
| 2500 | using TimeFn = float (*)(void *); | ||
| 2501 | return vfunc<TimeFn>(g_iface.engine, 14)(g_iface.engine); | ||
| 2502 | } | ||
| 2503 | |||
| 2504 | static const char *proj_name(int kind) { | ||
| 2505 | switch (kind) { | ||
| 2506 | case PK_FRAG: return "FRAG"; | ||
| 2507 | case PK_RPG: return "RPG"; | ||
| 2508 | case PK_BOMB: return "BOMB"; | ||
| 2509 | case PK_BALL: return "AR2"; | ||
| 2510 | default: return "PROJ"; | ||
| 2511 | } | ||
| 2512 | } | ||
| 2513 | |||
| 2514 | static void projs_collect(int local_idx) { | ||
| 2515 | static double last_scan = 0.0; | ||
| 2516 | double now = esp_now(); | ||
| 2517 | if (now - last_scan < 0.10) | ||
| 2518 | return; | ||
| 2519 | last_scan = now; | ||
| 2520 | g_nprojs = 0; | ||
| 2521 | if (!g_iface.entitylist || g_net.origin < 0) | ||
| 2522 | return; | ||
| 2523 | resolve_server_netvars(); | ||
| 2524 | using Fn = int (*)(void *); | ||
| 2525 | int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist); | ||
| 2526 | if (hi < 0 || hi > 4096) | ||
| 2527 | hi = 4096; | ||
| 2528 | float curtime = engine_curtime(); | ||
| 2529 | for (int idx = 1; idx <= hi && g_nprojs < 32; idx++) { | ||
| 2530 | if (idx == local_idx) | ||
| 2531 | continue; | ||
| 2532 | void *ent = entitylist_get(idx); | ||
| 2533 | if (!ent) | ||
| 2534 | continue; | ||
| 2535 | void *net = entity_networkable(idx); | ||
| 2536 | if (!net) | ||
| 2537 | continue; | ||
| 2538 | const char *cls = entity_class_name(net, nullptr); | ||
| 2539 | const char *model = entity_model_name(ent); | ||
| 2540 | if (!model) | ||
| 2541 | continue; | ||
| 2542 | bool owned = item_weapon_is_owned(ent); | ||
| 2543 | bool frag_cls = cls && strcasestr(cls, "grenade") && | ||
| 2544 | !strcasestr(cls, "weapon"); | ||
| 2545 | bool missile_cls = cls && strcasestr(cls, "missile") && | ||
| 2546 | !strcasestr(cls, "weapon"); | ||
| 2547 | int kind; | ||
| 2548 | if (strcasestr(model, "w_grenade") && | ||
| 2549 | (frag_cls || (!cls && owned))) { | ||
| 2550 | kind = PK_FRAG; | ||
| 2551 | } else if ((strcasestr(model, "w_missile") || | ||
| 2552 | strcasestr(model, "missile_launch") || | ||
| 2553 | strcasestr(model, "missile_closed")) && | ||
| 2554 | (missile_cls || (!cls && owned))) { | ||
| 2555 | kind = PK_RPG; | ||
| 2556 | } else if (strcasestr(model, "helicopter_bomb")) { | ||
| 2557 | kind = PK_BOMB; | ||
| 2558 | } else if (strcasestr(model, "combineball")) { | ||
| 2559 | kind = PK_BALL; | ||
| 2560 | } else { | ||
| 2561 | continue; | ||
| 2562 | } | ||
| 2563 | uintptr_t e = (uintptr_t)ent; | ||
| 2564 | ProjTarget &p = g_projs[g_nprojs++]; | ||
| 2565 | p.idx = idx; | ||
| 2566 | p.kind = kind; | ||
| 2567 | p.origin = Vector{*(float *)(e + g_net.origin), | ||
| 2568 | *(float *)(e + g_net.origin + 4), | ||
| 2569 | *(float *)(e + g_net.origin + 8)}; | ||
| 2570 | // The client m_DmgRadius is networked; fall back to the server field and | ||
| 2571 | // then to the stock radius for this projectile type. | ||
| 2572 | p.radius = -1.0f; | ||
| 2573 | if (g_net.dmg_radius >= 0) | ||
| 2574 | p.radius = *(float *)(e + g_net.dmg_radius); | ||
| 2575 | if (!(p.radius > 1.0f && p.radius < 2048.0f)) | ||
| 2576 | p.radius = server_grenade_radius(idx); | ||
| 2577 | if (!(p.radius > 1.0f && p.radius < 2048.0f)) | ||
| 2578 | p.radius = kind == PK_RPG ? 200.0f : kind == PK_BALL ? 150.0f : 300.0f; | ||
| 2579 | // m_flDetonateTime is server-only (not in the send table). | ||
| 2580 | p.fuse = -1.0f; | ||
| 2581 | if (kind != PK_RPG) { | ||
| 2582 | float det = server_grenade_detonate(idx); | ||
| 2583 | if (det > 0.0f && curtime > 0.0f) | ||
| 2584 | p.fuse = det - curtime; | ||
| 2585 | if (!(p.fuse > -2.0f && p.fuse < 30.0f)) | ||
| 2586 | p.fuse = -1.0f; | ||
| 2587 | } | ||
| 2588 | static int logs = 0; | ||
| 2589 | if (logs < 8) { | ||
| 2590 | logs++; | ||
| 2591 | logf("projectile: idx=%d cls=%s model=%s kind=%s fuse=%.2f " | ||
| 2592 | "radius=%.0f", | ||
| 2593 | idx, cls ? cls : "?", model, proj_name(kind), p.fuse, p.radius); | ||
| 2594 | } | ||
| 2595 | } | ||
| 2596 | } | ||
| 2597 | |||
| 2598 | void esp_draw_projectiles() { | ||
| 2599 | if (!g_surface || !g_iface.entitylist || g_net.origin < 0) | ||
| 2600 | return; | ||
| 2601 | if (!g_scr_w) { | ||
| 2602 | using Fn = void (*)(void *, int &, int &); | ||
| 2603 | vfunc<Fn>(g_surface, SURF_GetScreenSize)(g_surface, g_scr_w, g_scr_h); | ||
| 2604 | if (!g_scr_w) | ||
| 2605 | return; | ||
| 2606 | } | ||
| 2607 | projs_collect(engine_get_local_player()); | ||
| 2608 | for (int i = 0; i < g_nprojs; i++) { | ||
| 2609 | const ProjTarget &p = g_projs[i]; | ||
| 2610 | float sx, sy; | ||
| 2611 | if (!world_to_screen(p.origin, sx, sy)) | ||
| 2612 | continue; | ||
| 2613 | if (sx < -40 || sx > (float)g_scr_w + 40 || sy < -40 || | ||
| 2614 | sy > (float)g_scr_h + 40) | ||
| 2615 | continue; | ||
| 2616 | int r = 255, g = 170, b = 60; | ||
| 2617 | if (p.kind == PK_RPG) { | ||
| 2618 | r = 255; g = 120; b = 40; | ||
| 2619 | } else if (p.kind == PK_BOMB) { | ||
| 2620 | r = 255; g = 60; b = 60; | ||
| 2621 | } else if (p.kind == PK_BALL) { | ||
| 2622 | r = 120; g = 220; b = 255; | ||
| 2623 | } else if (p.fuse >= 0.0f && p.fuse < 0.8f) { | ||
| 2624 | r = 255; g = 60; b = 60; | ||
| 2625 | } | ||
| 2626 | int x = (int)sx, y = (int)sy; | ||
| 2627 | surf_color(r, g, b, 255); | ||
| 2628 | surf_rect(0, x - 4, y - 4, x + 4, y + 4); | ||
| 2629 | surf_rect(1, x - 1, y - 1, x + 2, y + 2); | ||
| 2630 | char text[64]; | ||
| 2631 | if (g_proj_fuse && p.fuse >= 0.0f) | ||
| 2632 | snprintf(text, sizeof(text), "%s %.1fs", proj_name(p.kind), p.fuse); | ||
| 2633 | else | ||
| 2634 | snprintf(text, sizeof(text), "%s", proj_name(p.kind)); | ||
| 2635 | int tx = x + 9; | ||
| 2636 | if (tx > g_scr_w - 80) | ||
| 2637 | tx = x - 9 - (int)strlen(text) * 6; | ||
| 2638 | if (tx < 4) | ||
| 2639 | tx = 4; | ||
| 2640 | int ty = y - 14; | ||
| 2641 | if (ty < 4) | ||
| 2642 | ty = 4; | ||
| 2643 | draw_text_small_col(tx + 1, ty + 1, text, 0, 0, 0); | ||
| 2644 | draw_text_small_col(tx, ty, text, r, g, b); | ||
| 2645 | if (g_proj_radius && p.radius > 1.0f) { | ||
| 2646 | const int SEG = 24; | ||
| 2647 | float lx = 0.0f, ly = 0.0f; | ||
| 2648 | bool have = false; | ||
| 2649 | surf_color(r, g, b, 150); | ||
| 2650 | for (int k = 0; k <= SEG; k++) { | ||
| 2651 | float a = (float)k * 2.0f * PI_F / (float)SEG; | ||
| 2652 | Vector w{p.origin.x + cosf(a) * p.radius, | ||
| 2653 | p.origin.y + sinf(a) * p.radius, p.origin.z}; | ||
| 2654 | float rx, ry; | ||
| 2655 | if (!world_to_screen(w, rx, ry)) { | ||
| 2656 | have = false; | ||
| 2657 | continue; | ||
| 2658 | } | ||
| 2659 | if (have) | ||
| 2660 | surf_line((int)lx, (int)ly, (int)rx, (int)ry); | ||
| 2661 | lx = rx; | ||
| 2662 | ly = ry; | ||
| 2663 | have = true; | ||
| 2664 | } | ||
| 2665 | } | ||
| 2666 | } | ||
| 2667 | } | ||
diff --git a/src/gui.cpp b/src/gui.cpp index 59c4364..2f832a2 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -364,7 +364,9 @@ static void build_cards() { | |||
| 364 | add_card(1, &g_triggerbot, "Triggerbot", | 364 | add_card(1, &g_triggerbot, "Triggerbot", |
| 365 | "Fire when the crosshair is on a hostile.", true); | 365 | "Fire when the crosshair is on a hostile.", true); |
| 366 | 366 | ||
| 367 | add_card(1, &g_melee, "Melee killbot", "360-degree crowbar silent aim.", | 367 | add_card(1, &g_melee, "Melee killbot", |
| 368 | "360-degree crowbar swing, or a gravity-gun launch at VULN " | ||
| 369 | "targets while holding an object.", | ||
| 368 | true); | 370 | true); |
| 369 | 371 | ||
| 370 | int esp = add_card(2, &g_esp, "ESP", "Draw hostile NPC information.", true); | 372 | int esp = add_card(2, &g_esp, "ESP", "Draw hostile NPC information.", true); |
| @@ -405,11 +407,22 @@ static void build_cards() { | |||
| 405 | &g_item_crates); | 407 | &g_item_crates); |
| 406 | add_toggle(items, "Show distance", "Append the distance in units.", | 408 | add_toggle(items, "Show distance", "Append the distance in units.", |
| 407 | &g_item_distance); | 409 | &g_item_distance); |
| 410 | add_toggle(items, "Only if usable", | ||
| 411 | "Hide health kits at full health and armor at full suit.", | ||
| 412 | &g_item_usable); | ||
| 408 | add_slider(items, "Max distance", | 413 | add_slider(items, "Max distance", |
| 409 | "0 = unlimited; otherwise items farther away are hidden.", 0, | 414 | "0 = unlimited; otherwise items farther away are hidden.", 0, |
| 410 | 10000, &g_item_max_dist); | 415 | 10000, &g_item_max_dist); |
| 411 | g_ctrls[g_nctrl - 1].zero_unlimited = true; | 416 | g_ctrls[g_nctrl - 1].zero_unlimited = true; |
| 412 | 417 | ||
| 418 | int proj = add_card(2, &g_projectiles, "Projectiles", | ||
| 419 | "Tag grenades, rockets, bombs and AR2 balls.", true); | ||
| 420 | add_toggle(proj, "Fuse timer", | ||
| 421 | "Show the remaining time until a grenade or ball detonates.", | ||
| 422 | &g_proj_fuse); | ||
| 423 | add_toggle(proj, "Blast radius", | ||
| 424 | "Draw the projectile's damage radius on the ground.", &g_proj_radius); | ||
| 425 | |||
| 413 | int view = add_card(2, nullptr, "Camera / display", | 426 | int view = add_card(2, nullptr, "Camera / display", |
| 414 | "Camera FOV, viewmodel FOV and HDR brightness.", true); | 427 | "Camera FOV, viewmodel FOV and HDR brightness.", true); |
| 415 | add_slider(view, "Camera FOV", | 428 | add_slider(view, "Camera FOV", |
diff --git a/src/hl2h.cpp b/src/hl2h.cpp index ca7ee85..703a582 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp | |||
| @@ -909,15 +909,18 @@ static void do_move(int seq, float frametime) { | |||
| 909 | note_modified(cmd, flags, vel); | 909 | note_modified(cmd, flags, vel); |
| 910 | 910 | ||
| 911 | bool melee = feature_active(FEAT_MELEE) && melee_weapon_active(ent); | 911 | bool melee = feature_active(FEAT_MELEE) && melee_weapon_active(ent); |
| 912 | bool gravity = feature_active(FEAT_MELEE) && gravity_gun_active(ent); | ||
| 913 | bool melee_mode = melee || gravity; | ||
| 912 | 914 | ||
| 913 | if (!feature_active(FEAT_AIMBOT) || !feature_has_bind(FEAT_AIMBOT) || melee) | 915 | if (!feature_active(FEAT_AIMBOT) || !feature_has_bind(FEAT_AIMBOT) || |
| 916 | melee_mode) | ||
| 914 | aimbot_reset_lock(); | 917 | aimbot_reset_lock(); |
| 915 | if (!feature_active(FEAT_SILENTAIM) || !feature_has_bind(FEAT_SILENTAIM) || | 918 | if (!feature_active(FEAT_SILENTAIM) || !feature_has_bind(FEAT_SILENTAIM) || |
| 916 | melee) | 919 | melee_mode) |
| 917 | silentaim_reset_lock(); | 920 | silentaim_reset_lock(); |
| 918 | 921 | ||
| 919 | if (melee) { | 922 | if (melee_mode) { |
| 920 | if (melee_aimbot_apply(cmd)) | 923 | if (melee_aimbot_apply(cmd, gravity && !melee)) |
| 921 | note_modified(cmd, flags, vel); | 924 | note_modified(cmd, flags, vel); |
| 922 | } else { | 925 | } else { |
| 923 | if (feature_active(FEAT_AIMBOT)) { | 926 | if (feature_active(FEAT_AIMBOT)) { |
| @@ -99,6 +99,7 @@ struct NetVars { | |||
| 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) | 100 | int punch = -1; // client m_vecPunchAngle (aim punch) |
| 101 | int default_fov = -1; // client m_iDefaultFOV (camera fov) | 101 | int default_fov = -1; // client m_iDefaultFOV (camera fov) |
| 102 | int dmg_radius = -1; // client C_BaseGrenade::m_DmgRadius | ||
| 102 | // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72 | 103 | // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72 |
| 103 | bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; } | 104 | bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; } |
| 104 | // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100. | 105 | // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100. |
| @@ -165,7 +166,8 @@ bool aimbot_apply(CUserCmd *cmd); | |||
| 165 | bool triggerbot_apply(CUserCmd *cmd); | 166 | bool triggerbot_apply(CUserCmd *cmd); |
| 166 | bool silent_aim_apply(CUserCmd *cmd); | 167 | bool silent_aim_apply(CUserCmd *cmd); |
| 167 | bool melee_weapon_active(void *local_ent); | 168 | bool melee_weapon_active(void *local_ent); |
| 168 | bool melee_aimbot_apply(CUserCmd *cmd); | 169 | bool gravity_gun_active(void *local_ent); |
| 170 | bool melee_aimbot_apply(CUserCmd *cmd, bool gravity); | ||
| 169 | bool spread_control_apply(CUserCmd *cmd, void *local_ent); | 171 | bool spread_control_apply(CUserCmd *cmd, void *local_ent); |
| 170 | int spread_best_seed(int pellets); | 172 | int spread_best_seed(int pellets); |
| 171 | extern int g_spread_pellets; | 173 | extern int g_spread_pellets; |
| @@ -184,7 +186,8 @@ enum FeatureId { | |||
| 184 | FEAT_WORLD_TINT = 11, | 186 | FEAT_WORLD_TINT = 11, |
| 185 | FEAT_FOG = 12, | 187 | FEAT_FOG = 12, |
| 186 | FEAT_SKYBOX = 13, | 188 | FEAT_SKYBOX = 13, |
| 187 | FEAT_COUNT = 14 | 189 | FEAT_PROJECTILES = 14, |
| 190 | FEAT_COUNT = 15 | ||
| 188 | }; | 191 | }; |
| 189 | enum EspOption { | 192 | enum EspOption { |
| 190 | ESP_OPT_SHOW_NEUTRAL = 0, | 193 | ESP_OPT_SHOW_NEUTRAL = 0, |
| @@ -221,7 +224,8 @@ struct FeatureState { | |||
| 221 | 224 | ||
| 222 | extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp, | 225 | extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp, |
| 223 | g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright, | 226 | g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright, |
| 224 | g_world_tint, g_fog, g_skybox; | 227 | g_world_tint, g_fog, g_skybox, g_projectiles; |
| 228 | extern bool g_proj_fuse, g_proj_radius; | ||
| 225 | extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on; | 229 | extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on; |
| 226 | extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3]; | 230 | extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3]; |
| 227 | extern bool g_fog_skybox, g_fog_disable; | 231 | extern bool g_fog_skybox, g_fog_disable; |
| @@ -236,6 +240,7 @@ extern bool g_item_armor; | |||
| 236 | extern bool g_item_weapons; | 240 | extern bool g_item_weapons; |
| 237 | extern bool g_item_crates; | 241 | extern bool g_item_crates; |
| 238 | extern bool g_item_distance; | 242 | extern bool g_item_distance; |
| 243 | extern bool g_item_usable; | ||
| 239 | extern int g_item_max_dist; | 244 | extern int g_item_max_dist; |
| 240 | extern bool g_aim_bones[AIM_BONE_COUNT]; | 245 | extern bool g_aim_bones[AIM_BONE_COUNT]; |
| 241 | extern int g_aim_smooth; | 246 | extern int g_aim_smooth; |
| @@ -289,6 +294,9 @@ int server_npc_relation(int idx, int local_idx); | |||
| 289 | bool server_npc_has_follow_target(int idx); | 294 | bool server_npc_has_follow_target(int idx); |
| 290 | int server_npc_state(int idx); | 295 | int server_npc_state(int idx); |
| 291 | int server_npc_takedamage(int idx); | 296 | int server_npc_takedamage(int idx); |
| 297 | int server_player_armor(int idx); | ||
| 298 | float server_grenade_detonate(int idx); | ||
| 299 | float server_grenade_radius(int idx); | ||
| 292 | int server_entity_count(); | 300 | int server_entity_count(); |
| 293 | void *server_entity_base(int idx); | 301 | void *server_entity_base(int idx); |
| 294 | const char *server_entity_classname(int idx); | 302 | const char *server_entity_classname(int idx); |
| @@ -322,6 +330,7 @@ void draw_text_col(int x, int y, const char *s, int r, int g, int b); | |||
| 322 | void draw_text_small_col(int x, int y, const char *s, int r, int g, int b); | 330 | void draw_text_small_col(int x, int y, const char *s, int r, int g, int b); |
| 323 | void esp_draw(); | 331 | void esp_draw(); |
| 324 | void esp_draw_items(); | 332 | void esp_draw_items(); |
| 333 | void esp_draw_projectiles(); | ||
| 325 | void prop_tint_update(); | 334 | void prop_tint_update(); |
| 326 | void studio_props_init(); | 335 | void studio_props_init(); |
| 327 | bool world_to_screen(const Vector &w, float &sx, float &sy); | 336 | bool world_to_screen(const Vector &w, float &sx, float &sy); |
diff --git a/src/menu.cpp b/src/menu.cpp index 020d2c5..521bf43 100644 --- a/src/menu.cpp +++ b/src/menu.cpp | |||
| @@ -60,12 +60,16 @@ int g_fog_rgb[3] = {160, 170, 190}; | |||
| 60 | int g_fog_start = 200; | 60 | int g_fog_start = 200; |
| 61 | int g_fog_end = 3000; | 61 | int g_fog_end = 3000; |
| 62 | FeatureState g_skybox{false, 0}; // skybox.png image override | 62 | FeatureState g_skybox{false, 0}; // skybox.png image override |
| 63 | FeatureState g_projectiles{true, 0}; // grenade / rocket ESP | ||
| 64 | bool g_proj_fuse = true; | ||
| 65 | bool g_proj_radius = true; | ||
| 63 | bool g_item_health = true; | 66 | bool g_item_health = true; |
| 64 | bool g_item_ammo = true; | 67 | bool g_item_ammo = true; |
| 65 | bool g_item_armor = true; | 68 | bool g_item_armor = true; |
| 66 | bool g_item_weapons = true; | 69 | bool g_item_weapons = true; |
| 67 | bool g_item_crates = true; | 70 | bool g_item_crates = true; |
| 68 | bool g_item_distance = true; | 71 | bool g_item_distance = true; |
| 72 | bool g_item_usable = true; | ||
| 69 | int g_item_max_dist = 5000; | 73 | int g_item_max_dist = 5000; |
| 70 | 74 | ||
| 71 | // Aimbot bone checklist. Head defaults on (v1 behaviour). | 75 | // Aimbot bone checklist. Head defaults on (v1 behaviour). |
| @@ -227,11 +231,16 @@ void config_save() { | |||
| 227 | g_fog_start, g_fog_end, (int)g_fog_skybox, (int)g_fog_disable); | 231 | g_fog_start, g_fog_end, (int)g_fog_skybox, (int)g_fog_disable); |
| 228 | fprintf(f, "[skybox]\nactive=%d\nbind=%d\n", g_skybox.active, | 232 | fprintf(f, "[skybox]\nactive=%d\nbind=%d\n", g_skybox.active, |
| 229 | g_skybox.bind); | 233 | g_skybox.bind); |
| 234 | fprintf(f, "[projectiles]\nactive=%d\nbind=%d\nfuse=%d\nradius=%d\n", | ||
| 235 | g_projectiles.active, g_projectiles.bind, (int)g_proj_fuse, | ||
| 236 | (int)g_proj_radius); | ||
| 230 | fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n" | 237 | fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n" |
| 231 | "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n", | 238 | "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n" |
| 239 | "usable=%d\n", | ||
| 232 | g_item_esp.active, g_item_esp.bind, (int)g_item_health, | 240 | g_item_esp.active, g_item_esp.bind, (int)g_item_health, |
| 233 | (int)g_item_ammo, (int)g_item_armor, (int)g_item_weapons, | 241 | (int)g_item_ammo, (int)g_item_armor, (int)g_item_weapons, |
| 234 | (int)g_item_crates, (int)g_item_distance, g_item_max_dist); | 242 | (int)g_item_crates, (int)g_item_distance, g_item_max_dist, |
| 243 | (int)g_item_usable); | ||
| 235 | fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" | 244 | fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" |
| 236 | "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\nspeedgraph=%d\n" | 245 | "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\nspeedgraph=%d\n" |
| 237 | "dmg=%d\nalert=%d\nweapon=%d\narrows=%d\n" | 246 | "dmg=%d\nalert=%d\nweapon=%d\narrows=%d\n" |
| @@ -274,7 +283,8 @@ static bool config_load() { | |||
| 274 | FeatureState *cur = nullptr; | 283 | FeatureState *cur = nullptr; |
| 275 | bool in_esp = false, in_bhop = false, in_aim = false, | 284 | bool in_esp = false, in_bhop = false, in_aim = false, |
| 276 | in_jumpbug = false, in_spread = false, in_items = false, | 285 | in_jumpbug = false, in_spread = false, in_items = false, |
| 277 | in_tint = false, in_fog = false, in_view = false, in_movement = false; | 286 | in_tint = false, in_fog = false, in_view = false, in_movement = false, |
| 287 | in_proj = false; | ||
| 278 | while (fgets(line, sizeof(line), f)) { | 288 | while (fgets(line, sizeof(line), f)) { |
| 279 | line[strcspn(line, "\r\n")] = 0; | 289 | line[strcspn(line, "\r\n")] = 0; |
| 280 | if (line[0] == '[') { | 290 | if (line[0] == '[') { |
| @@ -300,6 +310,8 @@ static bool config_load() { | |||
| 300 | cur = &g_fog; | 310 | cur = &g_fog; |
| 301 | else if (strstr(line, "[skybox]")) | 311 | else if (strstr(line, "[skybox]")) |
| 302 | cur = &g_skybox; | 312 | cur = &g_skybox; |
| 313 | else if (strstr(line, "[projectiles]")) | ||
| 314 | cur = &g_projectiles; | ||
| 303 | else if (strstr(line, "[aimbot]")) | 315 | else if (strstr(line, "[aimbot]")) |
| 304 | cur = &g_aimbot; | 316 | cur = &g_aimbot; |
| 305 | else if (strstr(line, "[triggerbot]")) | 317 | else if (strstr(line, "[triggerbot]")) |
| @@ -322,6 +334,7 @@ static bool config_load() { | |||
| 322 | in_fog = (cur == &g_fog); | 334 | in_fog = (cur == &g_fog); |
| 323 | in_view = strstr(line, "[view]") != nullptr; | 335 | in_view = strstr(line, "[view]") != nullptr; |
| 324 | in_movement = strstr(line, "[movement]") != nullptr; | 336 | in_movement = strstr(line, "[movement]") != nullptr; |
| 337 | in_proj = (cur == &g_projectiles); | ||
| 325 | } else if (cur || in_view || in_movement) { | 338 | } else if (cur || in_view || in_movement) { |
| 326 | char *eq = strchr(line, '='); | 339 | char *eq = strchr(line, '='); |
| 327 | if (!eq) | 340 | if (!eq) |
| @@ -353,6 +366,8 @@ static bool config_load() { | |||
| 353 | g_item_crates = atoi(val) != 0; | 366 | g_item_crates = atoi(val) != 0; |
| 354 | else if (strcmp(key, "distance") == 0) | 367 | else if (strcmp(key, "distance") == 0) |
| 355 | g_item_distance = atoi(val) != 0; | 368 | g_item_distance = atoi(val) != 0; |
| 369 | else if (strcmp(key, "usable") == 0) | ||
| 370 | g_item_usable = atoi(val) != 0; | ||
| 356 | else if (strcmp(key, "max_dist") == 0) { | 371 | else if (strcmp(key, "max_dist") == 0) { |
| 357 | int v = atoi(val); | 372 | int v = atoi(val); |
| 358 | if (v < 0) v = 0; // 0 = unlimited | 373 | if (v < 0) v = 0; // 0 = unlimited |
| @@ -360,6 +375,12 @@ static bool config_load() { | |||
| 360 | g_item_max_dist = v; | 375 | g_item_max_dist = v; |
| 361 | } | 376 | } |
| 362 | } | 377 | } |
| 378 | if (in_proj) { | ||
| 379 | if (strcmp(key, "fuse") == 0) | ||
| 380 | g_proj_fuse = atoi(val) != 0; | ||
| 381 | else if (strcmp(key, "radius") == 0) | ||
| 382 | g_proj_radius = atoi(val) != 0; | ||
| 383 | } | ||
| 363 | if (in_aim) { | 384 | if (in_aim) { |
| 364 | if (strcmp(key, "smooth") == 0) { | 385 | if (strcmp(key, "smooth") == 0) { |
| 365 | int v = atoi(val); | 386 | int v = atoi(val); |
| @@ -581,12 +602,16 @@ void config_reset_defaults() { | |||
| 581 | g_fog_start = 200; | 602 | g_fog_start = 200; |
| 582 | g_fog_end = 3000; | 603 | g_fog_end = 3000; |
| 583 | g_skybox = {false, 0}; | 604 | g_skybox = {false, 0}; |
| 605 | g_projectiles = {true, 0}; | ||
| 606 | g_proj_fuse = true; | ||
| 607 | g_proj_radius = true; | ||
| 584 | g_item_health = true; | 608 | g_item_health = true; |
| 585 | g_item_ammo = true; | 609 | g_item_ammo = true; |
| 586 | g_item_armor = true; | 610 | g_item_armor = true; |
| 587 | g_item_weapons = true; | 611 | g_item_weapons = true; |
| 588 | g_item_crates = true; | 612 | g_item_crates = true; |
| 589 | g_item_distance = true; | 613 | g_item_distance = true; |
| 614 | g_item_usable = true; | ||
| 590 | g_item_max_dist = 5000; | 615 | g_item_max_dist = 5000; |
| 591 | g_aimbot = {true, 604}; | 616 | g_aimbot = {true, 604}; |
| 592 | g_triggerbot = {true, 603}; | 617 | g_triggerbot = {true, 603}; |
| @@ -713,7 +738,7 @@ static FeatureState *feature_state(int feature) { | |||
| 713 | static FeatureState *feats[FEAT_COUNT] = { | 738 | static FeatureState *feats[FEAT_COUNT] = { |
| 714 | &g_bhop, &g_abh, &g_esp, &g_aimbot, &g_triggerbot, | 739 | &g_bhop, &g_abh, &g_esp, &g_aimbot, &g_triggerbot, |
| 715 | &g_silentaim, &g_jumpbug, &g_melee, &g_spread, &g_item_esp, | 740 | &g_silentaim, &g_jumpbug, &g_melee, &g_spread, &g_item_esp, |
| 716 | &g_fullbright, &g_world_tint, &g_fog, &g_skybox}; | 741 | &g_fullbright, &g_world_tint, &g_fog, &g_skybox, &g_projectiles}; |
| 717 | if (feature < 0 || feature >= FEAT_COUNT) | 742 | if (feature < 0 || feature >= FEAT_COUNT) |
| 718 | return nullptr; | 743 | return nullptr; |
| 719 | return feats[feature]; | 744 | return feats[feature]; |
| @@ -940,9 +965,11 @@ void outer_draw() { | |||
| 940 | static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; | 965 | static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; |
| 941 | bool esp_on = feature_active(FEAT_ESP); | 966 | bool esp_on = feature_active(FEAT_ESP); |
| 942 | bool items_on = feature_active(FEAT_ITEM_ESP); | 967 | bool items_on = feature_active(FEAT_ITEM_ESP); |
| 968 | bool projs_on = feature_active(FEAT_PROJECTILES); | ||
| 943 | bool toasts = gui_toasts_active(); | 969 | bool toasts = gui_toasts_active(); |
| 944 | if (!g_surface || | 970 | if (!g_surface || |
| 945 | (!gui_is_open() && !draw_test && !esp_on && !items_on && !toasts)) | 971 | (!gui_is_open() && !draw_test && !esp_on && !items_on && !projs_on && |
| 972 | !toasts)) | ||
| 946 | return; | 973 | return; |
| 947 | 974 | ||
| 948 | // Level/menu -> game transitions can invalidate the VGUI font handle. | 975 | // Level/menu -> game transitions can invalidate the VGUI font handle. |
| @@ -1007,6 +1034,8 @@ void outer_draw() { | |||
| 1007 | esp_draw(); | 1034 | esp_draw(); |
| 1008 | if (items_on && overlay_font) | 1035 | if (items_on && overlay_font) |
| 1009 | esp_draw_items(); | 1036 | esp_draw_items(); |
| 1037 | if (projs_on && overlay_font) | ||
| 1038 | esp_draw_projectiles(); | ||
| 1010 | if (draw_test && overlay_font) { | 1039 | if (draw_test && overlay_font) { |
| 1011 | surf_draw_set_color(255, 0, 0, 255); | 1040 | surf_draw_set_color(255, 0, 0, 255); |
| 1012 | surf_draw_filled_rect(20, 20, 180, 60); | 1041 | surf_draw_filled_rect(20, 20, 180, 60); |
diff --git a/src/netvars.cpp b/src/netvars.cpp index 33fd6a3..ef564cd 100644 --- a/src/netvars.cpp +++ b/src/netvars.cpp | |||
| @@ -107,6 +107,11 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 107 | int off = (int)(base_off + (uint32_t)poff); | 107 | int off = (int)(base_off + (uint32_t)poff); |
| 108 | if (off >= 0x20 && off <= 0x2000) | 108 | if (off >= 0x20 && off <= 0x2000) |
| 109 | g_net.default_fov = off; | 109 | g_net.default_fov = off; |
| 110 | } else if (strcmp(pname2, "m_DmgRadius") == 0 && | ||
| 111 | g_net.dmg_radius < 0) { | ||
| 112 | int off = (int)(base_off + (uint32_t)poff); | ||
| 113 | if (off >= 0x20 && off <= 0x2000) | ||
| 114 | g_net.dmg_radius = off; | ||
| 110 | } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { | 115 | } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { |
| 111 | g_net.teamnum = (int)(base_off + (uint32_t)poff); | 116 | g_net.teamnum = (int)(base_off + (uint32_t)poff); |
| 112 | } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { | 117 | } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { |
| @@ -288,11 +293,12 @@ void resolve_netvars() { | |||
| 288 | logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d " | 293 | logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d " |
| 289 | "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d " | 294 | "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d " |
| 290 | "modelindex=%d absvelocity=%d activeweapon=%d owner=%d " | 295 | "modelindex=%d absvelocity=%d activeweapon=%d owner=%d " |
| 291 | "weapon_owner=%d clr_render=%d punch=%d default_fov=%d", | 296 | "weapon_owner=%d clr_render=%d punch=%d default_fov=%d " |
| 297 | "dmg_radius=%d", | ||
| 292 | g_net.flags, g_net.velocity, g_net.movetype, g_net.health, | 298 | g_net.flags, g_net.velocity, g_net.movetype, g_net.health, |
| 293 | g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset, | 299 | g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset, |
| 294 | g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex, | 300 | g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex, |
| 295 | g_net.absvelocity, g_net.activeweapon, g_net.owner, | 301 | g_net.absvelocity, g_net.activeweapon, g_net.owner, |
| 296 | g_net.weapon_owner, g_net.clr_render, g_net.punch, | 302 | g_net.weapon_owner, g_net.clr_render, g_net.punch, |
| 297 | g_net.default_fov); | 303 | g_net.default_fov, g_net.dmg_radius); |
| 298 | } | 304 | } |
diff --git a/src/server.cpp b/src/server.cpp index 39bb162..40aa850 100644 --- a/src/server.cpp +++ b/src/server.cpp | |||
| @@ -31,7 +31,9 @@ enum { | |||
| 31 | FIELD_CHARACTER = 8, | 31 | FIELD_CHARACTER = 8, |
| 32 | FIELD_EMBEDDED = 10, | 32 | FIELD_EMBEDDED = 10, |
| 33 | FIELD_CUSTOM = 11, | 33 | FIELD_CUSTOM = 11, |
| 34 | FIELD_EHANDLE = 13 | 34 | FIELD_EHANDLE = 13, |
| 35 | FIELD_TIME = 16, | ||
| 36 | FIELD_FLOAT = 1 | ||
| 35 | }; | 37 | }; |
| 36 | 38 | ||
| 37 | // SF_FLOOR_TURRET_CITIZEN (npc_turret_floor.h). Citizen-built turrets | 39 | // SF_FLOOR_TURRET_CITIZEN (npc_turret_floor.h). Citizen-built turrets |
| @@ -53,6 +55,9 @@ static int g_srv_hacked = -1; | |||
| 53 | static int g_srv_npcstate = -1; | 55 | static int g_srv_npcstate = -1; |
| 54 | static int g_srv_takedamage = -1; | 56 | static int g_srv_takedamage = -1; |
| 55 | static int g_srv_mapname = -1; | 57 | static int g_srv_mapname = -1; |
| 58 | static int g_srv_armor = -1; | ||
| 59 | static int g_srv_detonate = -1; | ||
| 60 | static int g_srv_grenade_radius = -1; | ||
| 56 | static bool g_srv_ready = false; | 61 | static bool g_srv_ready = false; |
| 57 | static bool g_srv_checked = false; | 62 | static bool g_srv_checked = false; |
| 58 | 63 | ||
| @@ -150,17 +155,22 @@ bool resolve_server_netvars() { | |||
| 150 | g_srv_takedamage = datamap_offset("m_takedamage", FIELD_INTEGER); | 155 | g_srv_takedamage = datamap_offset("m_takedamage", FIELD_INTEGER); |
| 151 | // CChangeLevel::m_szMapName is a char[cchMapNameMost] on trigger_changelevel. | 156 | // CChangeLevel::m_szMapName is a char[cchMapNameMost] on trigger_changelevel. |
| 152 | g_srv_mapname = datamap_offset("m_szMapName", FIELD_CHARACTER, 0x40, 0x4000); | 157 | g_srv_mapname = datamap_offset("m_szMapName", FIELD_CHARACTER, 0x40, 0x4000); |
| 158 | // CBasePlayer::m_ArmorValue (int). | ||
| 159 | g_srv_armor = datamap_offset("m_ArmorValue", FIELD_INTEGER); | ||
| 160 | // CBaseGrenade fuse / blast radius (m_flDetonateTime is not networked). | ||
| 161 | g_srv_detonate = datamap_offset("m_flDetonateTime", FIELD_TIME); | ||
| 162 | g_srv_grenade_radius = datamap_offset("m_DmgRadius", FIELD_FLOAT); | ||
| 153 | g_srv_ready = g_srv_health > 0; | 163 | g_srv_ready = g_srv_health > 0; |
| 154 | if (g_srv_ready) { | 164 | if (g_srv_ready) { |
| 155 | logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " | 165 | logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " |
| 156 | "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " | 166 | "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " |
| 157 | "followtarget=%d spawnflags=%d hacked=%d npcstate=%d " | 167 | "followtarget=%d spawnflags=%d hacked=%d npcstate=%d " |
| 158 | "takedamage=%d mapname=%d", | 168 | "takedamage=%d mapname=%d armor=%d detonate=%d dmgradius=%d", |
| 159 | g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, | 169 | g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, |
| 160 | g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, | 170 | g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, |
| 161 | g_srv_activeweapon, g_srv_relationship, g_srv_followtarget, | 171 | g_srv_activeweapon, g_srv_relationship, g_srv_followtarget, |
| 162 | g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage, | 172 | g_srv_spawnflags, g_srv_hacked, g_srv_npcstate, g_srv_takedamage, |
| 163 | g_srv_mapname); | 173 | g_srv_mapname, g_srv_armor, g_srv_detonate, g_srv_grenade_radius); |
| 164 | fail_logged = false; | 174 | fail_logged = false; |
| 165 | } else if (!fail_logged) { | 175 | } else if (!fail_logged) { |
| 166 | fail_logged = true; | 176 | fail_logged = true; |
| @@ -425,6 +435,39 @@ bool server_set_velocity_z(int idx, float vz) { | |||
| 425 | return true; | 435 | return true; |
| 426 | } | 436 | } |
| 427 | 437 | ||
| 438 | // Local player's HEV suit armor from the authoritative server entity. | ||
| 439 | int server_player_armor(int idx) { | ||
| 440 | if (!g_srv_ready || g_srv_armor < 0) | ||
| 441 | return -1; | ||
| 442 | void *base = nullptr; | ||
| 443 | server_entity_health(idx, nullptr, nullptr, &base); | ||
| 444 | if (!base) | ||
| 445 | return -1; | ||
| 446 | return *(int *)((uintptr_t)base + g_srv_armor); | ||
| 447 | } | ||
| 448 | |||
| 449 | // CBaseGrenade fuse time and blast radius from the server entity. Both are | ||
| 450 | // non-networked (m_flDetonateTime is commented out of the send table). | ||
| 451 | float server_grenade_detonate(int idx) { | ||
| 452 | if (!g_srv_ready || g_srv_detonate < 0) | ||
| 453 | return -1.0f; | ||
| 454 | void *base = nullptr; | ||
| 455 | server_entity_health(idx, nullptr, nullptr, &base); | ||
| 456 | if (!base) | ||
| 457 | return -1.0f; | ||
| 458 | return *(float *)((uintptr_t)base + g_srv_detonate); | ||
| 459 | } | ||
| 460 | |||
| 461 | float server_grenade_radius(int idx) { | ||
| 462 | if (!g_srv_ready || g_srv_grenade_radius < 0) | ||
| 463 | return -1.0f; | ||
| 464 | void *base = nullptr; | ||
| 465 | server_entity_health(idx, nullptr, nullptr, &base); | ||
| 466 | if (!base) | ||
| 467 | return -1.0f; | ||
| 468 | return *(float *)((uintptr_t)base + g_srv_grenade_radius); | ||
| 469 | } | ||
| 470 | |||
| 428 | // Entity index of the player's active weapon from the authoritative server | 471 | // Entity index of the player's active weapon from the authoritative server |
| 429 | // entity. Used as a fallback if the client m_hActiveWeapon netvar is missing. | 472 | // entity. Used as a fallback if the client m_hActiveWeapon netvar is missing. |
| 430 | int server_active_weapon(int idx) { | 473 | int server_active_weapon(int idx) { |
