diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 13:53:45 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 13:53:45 +0100 |
| commit | 12fde50efc7e1f1d123f9fc34ffef53d4387de2d (patch) | |
| tree | 4de53807ce23df9b63598a0e3a12620ff19eb7cc | |
| parent | No spread: detect pellet draws from the FireBullets seed call (diff) | |
| download | hl2h-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.md | 69 | ||||
| -rw-r--r-- | src/esp.cpp | 361 | ||||
| -rw-r--r-- | src/gui.cpp | 52 | ||||
| -rw-r--r-- | src/hl2h.cpp | 96 | ||||
| -rw-r--r-- | src/hl2h.h | 27 | ||||
| -rw-r--r-- | src/menu.cpp | 147 | ||||
| -rw-r--r-- | src/netvars.cpp | 15 | ||||
| -rw-r--r-- | src/server.cpp | 96 | ||||
| -rw-r--r-- | src/vischeck.cpp | 20 |
9 files changed, 815 insertions, 68 deletions
| @@ -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 | |||
| 32 | struct ArrowTarget { | ||
| 33 | Vector head; | ||
| 34 | bool neutral; | ||
| 35 | }; | ||
| 36 | static ArrowTarget g_arrows[32]; | ||
| 37 | static int g_narrows = 0; | ||
| 29 | static EspTarget g_targets[128]; | 38 | static EspTarget g_targets[128]; |
| 30 | static int g_ntargets = 0; | 39 | static int g_ntargets = 0; |
| 31 | static int g_scr_w = 0, g_scr_h = 0; | 40 | static int g_scr_w = 0, g_scr_h = 0; |
| @@ -273,6 +282,61 @@ static void short_name(const char *model, char *out, size_t outn) { | |||
| 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. | ||
| 287 | static 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. | ||
| 331 | static 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 | |||
| 276 | static void esp_collect(int local_idx) { | 340 | static 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. | ||
| 675 | static 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 | |||
| 598 | static int g_aim_lock = -1; | 691 | static int g_aim_lock = -1; |
| 599 | static int g_silent_lock = -1; | 692 | static 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). | ||
| 1640 | static 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 | |||
| 1650 | static 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. | ||
| 1663 | static 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. | ||
| 1715 | struct TransitionBox { | ||
| 1716 | char next[128]; | ||
| 1717 | Vector mins, maxs; | ||
| 1718 | }; | ||
| 1719 | static TransitionBox g_trans[8]; | ||
| 1720 | static int g_ntrans = 0; | ||
| 1721 | static char g_map_last[64]; | ||
| 1722 | static char g_map_prev[64]; | ||
| 1723 | |||
| 1724 | static 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 | |||
| 1739 | static 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 | |||
| 1769 | static 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 | |||
| 1516 | void esp_draw() { | 1814 | void 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 | ||
| 600 | static float hkdUniformRandomFloat(void *self, float mn, float mx) { | 600 | static 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 | |||
| 666 | static 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 | |||
| 701 | static bool g_cj_added = false; | ||
| 702 | |||
| 703 | static 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 | |||
| 719 | static 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 | |||
| 657 | static void do_move(int seq, float frametime) { | 741 | static 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 | ||
| @@ -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 | }; |
| 198 | enum AimBone { | 203 | enum AimBone { |
| 199 | AIM_BONE_HEAD = 0, | 204 | AIM_BONE_HEAD = 0, |
| @@ -237,7 +242,16 @@ extern int g_aim_smooth; | |||
| 237 | extern bool g_aim_vischeck; | 242 | extern bool g_aim_vischeck; |
| 238 | extern int g_aim_fov; | 243 | extern int g_aim_fov; |
| 239 | extern bool g_aim_fov_circle; | 244 | extern bool g_aim_fov_circle; |
| 240 | extern int g_silent_fov; | 245 | extern bool g_aim_punch; |
| 246 | extern bool g_spread_engine; | ||
| 247 | extern bool g_auto_crouchjump; | ||
| 248 | extern bool g_edge_jump; | ||
| 249 | extern int g_fov_camera; | ||
| 250 | extern int g_fov_viewmodel; | ||
| 251 | extern int g_gamma; | ||
| 252 | extern int g_esp_col_hostile[3]; | ||
| 253 | extern int g_esp_col_neutral[3]; | ||
| 254 | extern int g_esp_col_friendly[3]; | ||
| 241 | extern bool g_esp_opt[ESP_OPT_COUNT]; | 255 | extern bool g_esp_opt[ESP_OPT_COUNT]; |
| 242 | 256 | ||
| 243 | const char *scancode_name(int sc); | 257 | const char *scancode_name(int sc); |
| @@ -275,6 +289,11 @@ int server_npc_relation(int idx, int local_idx); | |||
| 275 | bool server_npc_has_follow_target(int idx); | 289 | bool server_npc_has_follow_target(int idx); |
| 276 | int server_npc_state(int idx); | 290 | int server_npc_state(int idx); |
| 277 | int server_npc_takedamage(int idx); | 291 | int server_npc_takedamage(int idx); |
| 292 | int server_entity_count(); | ||
| 293 | void *server_entity_base(int idx); | ||
| 294 | const char *server_entity_classname(int idx); | ||
| 295 | bool server_trigger_info(int idx, char *map, size_t mapn, Vector &mins, | ||
| 296 | Vector &maxs); | ||
| 278 | int server_npc_turret_flags(int idx); | 297 | int server_npc_turret_flags(int idx); |
| 279 | int server_model_index(void *base); | 298 | int server_model_index(void *base); |
| 280 | void find_modules(); | 299 | void find_modules(); |
| @@ -288,11 +307,12 @@ bool esp_option(int opt); | |||
| 288 | bool bhop_autostrafe_enabled(); | 307 | bool bhop_autostrafe_enabled(); |
| 289 | bool jumpbug_assist_enabled(); | 308 | bool jumpbug_assist_enabled(); |
| 290 | bool spread_no_spread_enabled(); | 309 | bool spread_no_spread_enabled(); |
| 310 | bool spread_engine_enabled(); | ||
| 311 | bool aim_punch_enabled(); | ||
| 291 | unsigned aimbot_bone_mask(); | 312 | unsigned aimbot_bone_mask(); |
| 292 | int aimbot_smooth_step(); | 313 | int aimbot_smooth_step(); |
| 293 | bool aimbot_vischeck_enabled(); | 314 | bool aimbot_vischeck_enabled(); |
| 294 | int aimbot_fov(); | 315 | int aimbot_fov(); |
| 295 | int silentaim_fov(); | ||
| 296 | bool aimbot_fov_circle(); | 316 | bool aimbot_fov_circle(); |
| 297 | void aimbot_reset_lock(); | 317 | void aimbot_reset_lock(); |
| 298 | void silentaim_reset_lock(); | 318 | void 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); |
| 311 | void *trace_entity(const Vector &start, const Vector &end, void *ignore); | 331 | void *trace_entity(const Vector &start, const Vector &end, void *ignore); |
| 312 | float ground_distance(const Vector &origin, void *ignore, float max_dist); | 332 | float ground_distance(const Vector &origin, void *ignore, float max_dist); |
| 333 | bool 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) |
| 315 | bool bones_update(int idx, void *ent); | 336 | bool bones_update(int idx, void *ent); |
diff --git a/src/menu.cpp b/src/menu.cpp index 00aa189..4e521f7 100644 --- a/src/menu.cpp +++ b/src/menu.cpp | |||
| @@ -75,11 +75,20 @@ int g_aim_smooth = 5; // degrees per tick | |||
| 75 | bool g_aim_vischeck = true; | 75 | bool g_aim_vischeck = true; |
| 76 | int g_aim_fov = 60; // angular distance from the crosshair | 76 | int g_aim_fov = 60; // angular distance from the crosshair |
| 77 | bool g_aim_fov_circle = true; | 77 | bool g_aim_fov_circle = true; |
| 78 | int g_silent_fov = 60; | 78 | bool g_aim_punch = true; // compensate server aim punch |
| 79 | bool g_spread_engine = true; // zero the pellet random draws, not just the seed | ||
| 80 | bool g_auto_crouchjump = false; | ||
| 81 | bool g_edge_jump = false; | ||
| 82 | int g_fov_camera = 0; // 0 = game default | ||
| 83 | int g_fov_viewmodel = 0; // 0 = game default (54) | ||
| 84 | int g_gamma = 22; // mat_monitorgamma x10, stock 2.2 | ||
| 85 | int g_esp_col_hostile[3] = {255, 60, 60}; | ||
| 86 | int g_esp_col_neutral[3] = {220, 220, 220}; | ||
| 87 | int 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. |
| 81 | bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true, true, | 90 | bool 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 | ||
| 670 | int aimbot_fov() { return g_aim_fov; } | 763 | int aimbot_fov() { return g_aim_fov; } |
| 671 | 764 | ||
| 672 | int silentaim_fov() { return g_silent_fov; } | 765 | bool aim_punch_enabled() { return g_aim_punch; } |
| 766 | |||
| 767 | bool spread_engine_enabled() { return g_spread_engine; } | ||
| 673 | 768 | ||
| 674 | bool aimbot_fov_circle() { return g_aim_fov_circle; } | 769 | bool 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; | |||
| 52 | static int g_srv_hacked = -1; | 52 | static int g_srv_hacked = -1; |
| 53 | static int g_srv_npcstate = -1; | 53 | static int g_srv_npcstate = -1; |
| 54 | static int g_srv_takedamage = -1; | 54 | static int g_srv_takedamage = -1; |
| 55 | static int g_srv_mapname = -1; | ||
| 55 | static bool g_srv_ready = false; | 56 | static bool g_srv_ready = false; |
| 56 | static bool g_srv_checked = false; | 57 | static 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 | ||
| 230 | int 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 | |||
| 239 | static 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 | |||
| 250 | void *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"). | ||
| 259 | const 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). | ||
| 274 | bool 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. | ||
| 238 | bool 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 |
