summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/esp.cpp258
-rw-r--r--src/hl2h.cpp42
-rw-r--r--src/hl2h.h7
-rw-r--r--src/menu.cpp25
-rw-r--r--src/netvars.cpp7
-rw-r--r--src/server.cpp22
6 files changed, 328 insertions, 33 deletions
diff --git a/src/esp.cpp b/src/esp.cpp
index 1e00a92..47df57d 100644
--- a/src/esp.cpp
+++ b/src/esp.cpp
@@ -784,6 +784,264 @@ bool silent_aim_apply(CUserCmd *cmd) {
784 return true; 784 return true;
785} 785}
786 786
787// ---------------------------------------------------------------------------
788// melee killbot
789// ---------------------------------------------------------------------------
790//
791// The crowbar's CBaseHLBludgeonWeapon::Swing traces 75 units from
792// Weapon_ShootPosition along the view forward (CROWBAR_RANGE in
793// weapon_crowbar.h). It first tries a line, then a +/-16 hull backed off by
794// ~28 units, and ignores targets more than ~45 degrees off the aim direction.
795// We therefore only need to point the usercmd at the target for one command;
796// the rendered view is restored exactly like silent aim.
797
798static constexpr float MELEE_RANGE = 75.0f;
799
800static float vec_dist(const Vector &a, const Vector &b) {
801 float dx = a.x - b.x, dy = a.y - b.y, dz = a.z - b.z;
802 return sqrtf(dx * dx + dy * dy + dz * dz);
803}
804
805bool melee_weapon_active(void *local_ent) {
806 if (!local_ent)
807 return false;
808 int local = engine_get_local_player();
809
810 auto check = [&](int idx, const char **cls_out,
811 const char **model_out) -> bool {
812 if (idx <= 0 || idx > 4096)
813 return false;
814 void *wep = entitylist_get(idx);
815 void *net = entity_networkable(idx);
816 const char *cls = net ? entity_class_name(net, nullptr) : nullptr;
817 const char *model = wep ? entity_model_name(wep) : nullptr;
818 *cls_out = cls;
819 *model_out = model;
820 return (cls && strstr(cls, "Crowbar")) ||
821 (model && strstr(model, "crowbar"));
822 };
823
824 int idx = -1;
825 if (g_net.activeweapon >= 0) {
826 uint32_t h = *(uint32_t *)((uintptr_t)local_ent + g_net.activeweapon);
827 if (h != 0xFFFFFFFFu)
828 idx = (int)(h & 0xFFFF);
829 }
830 const char *cls = nullptr, *model = nullptr;
831 bool crowbar = check(idx, &cls, &model);
832
833 if (!crowbar) {
834 // Fallback: the authoritative server player's m_hActiveWeapon handle.
835 // Also covers a client netvar offset that resolves but does not point at
836 // the weapon the server is actually using.
837 if (local > 0) {
838 resolve_server_netvars();
839 int si = server_active_weapon(local);
840 if (si > 0 && si != idx) {
841 idx = si;
842 crowbar = check(idx, &cls, &model);
843 }
844 }
845 }
846
847 static int last_idx = -2;
848 static bool last_crowbar = false;
849 if (idx != last_idx || crowbar != last_crowbar) {
850 last_idx = idx;
851 last_crowbar = crowbar;
852 logf("melee: active idx=%d cls=%s model=%s crowbar=%d", idx,
853 cls ? cls : "?", model ? model : "?", (int)crowbar);
854 }
855 return crowbar;
856}
857
858bool melee_aimbot_apply(CUserCmd *cmd) {
859 if (!g_iface.entitylist || g_net.origin < 0)
860 return false;
861 int local = engine_get_local_player();
862 if (local <= 0)
863 return false;
864 void *local_ent = entitylist_get(local);
865 if (!local_ent)
866 return false;
867 if (g_net.health >= 0 &&
868 *(int *)((uintptr_t)local_ent + g_net.health) <= 0)
869 return false;
870
871 using Fn = int (*)(void *);
872 int hi = vfunc<Fn>(g_iface.entitylist, 6)(g_iface.entitylist);
873 if (hi < 0 || hi > 4096)
874 hi = 4096;
875
876 struct Cand {
877 int idx;
878 float dist;
879 Vector closest;
880 Vector center;
881 };
882 Cand cands[16];
883 int n = 0;
884
885 for (int idx = 1; idx <= hi; idx++) {
886 if (idx == local)
887 continue;
888 void *ent = entitylist_get(idx);
889 if (!ent)
890 continue;
891 void *net = entity_networkable(idx);
892 if (!net)
893 continue;
894 const char *cls = entity_class_name(net, nullptr);
895 if (!cls || !is_npc_class(cls))
896 continue;
897 uintptr_t e = (uintptr_t)ent;
898 int life = g_net.lifestate >= 0
899 ? (int)*(unsigned char *)(e + g_net.lifestate)
900 : -1;
901 if (life != LIFE_ALIVE)
902 continue;
903 const char *model = entity_model_name(ent);
904 if (model_is_friendly(model) || model_is_neutral(model))
905 continue;
906
907 Vector origin{*(float *)(e + g_net.origin),
908 *(float *)(e + g_net.origin + 4),
909 *(float *)(e + g_net.origin + 8)};
910 float minx = -16.0f, miny = -16.0f, minz = 0.0f;
911 float maxx = 16.0f, maxy = 16.0f, maxz = 72.0f;
912 if (g_net.mins >= 0) {
913 minx = *(float *)(e + g_net.mins);
914 miny = *(float *)(e + g_net.mins + 4);
915 minz = *(float *)(e + g_net.mins + 8);
916 }
917 if (g_net.maxs >= 0) {
918 maxx = *(float *)(e + g_net.maxs);
919 maxy = *(float *)(e + g_net.maxs + 4);
920 maxz = *(float *)(e + g_net.maxs + 8);
921 }
922 if (!(maxz > minz + 4.0f && maxz < 512.0f)) {
923 minz = 0.0f;
924 maxz = 72.0f;
925 }
926 if (!(maxx > minx + 1.0f && maxx < 256.0f)) {
927 minx = -16.0f;
928 maxx = 16.0f;
929 }
930 if (!(maxy > miny + 1.0f && maxy < 256.0f)) {
931 miny = -16.0f;
932 maxy = 16.0f;
933 }
934
935 Vector lo{origin.x + minx, origin.y + miny, origin.z + minz};
936 Vector hiV{origin.x + maxx, origin.y + maxy, origin.z + maxz};
937 Vector closest{g_cam_eye.x, g_cam_eye.y, g_cam_eye.z};
938 if (closest.x < lo.x) closest.x = lo.x;
939 if (closest.x > hiV.x) closest.x = hiV.x;
940 if (closest.y < lo.y) closest.y = lo.y;
941 if (closest.y > hiV.y) closest.y = hiV.y;
942 if (closest.z < lo.z) closest.z = lo.z;
943 if (closest.z > hiV.z) closest.z = hiV.z;
944 float d = vec_dist(g_cam_eye, closest);
945 if (d > MELEE_RANGE)
946 continue;
947
948 Vector center{(lo.x + hiV.x) * 0.5f, (lo.y + hiV.y) * 0.5f,
949 (lo.z + hiV.z) * 0.5f};
950
951 int pos = n;
952 while (pos > 0 && cands[pos - 1].dist > d)
953 pos--;
954 if (n < 16)
955 n++;
956 else if (pos >= 16)
957 continue;
958 for (int j = n - 1; j > pos; j--)
959 cands[j] = cands[j - 1];
960 cands[pos] = {idx, d, closest, center};
961 }
962
963 for (int ci = 0; ci < n; ci++) {
964 const Cand &c = cands[ci];
965 Vector aim{};
966 bool have_aim = false;
967
968 // Prefer the nearest visible hitbox center. This handles small NPCs
969 // (headcrabs, antlions) whose AABB center can be less useful than the
970 // actual body hitboxes.
971 void *tent = entitylist_get(c.idx);
972 if (tent && bones_update(c.idx, tent)) {
973 Vector pts[32];
974 int np = aim_points(c.idx, 0xFE, pts, 32);
975 float bestd = 1e9f;
976 for (int i = 0; i < np; i++) {
977 float pd = vec_dist(g_cam_eye, pts[i]);
978 if (pd > MELEE_RANGE || pd >= bestd)
979 continue;
980 if (!is_visible(g_cam_eye, pts[i]))
981 continue;
982 bestd = pd;
983 aim = pts[i];
984 have_aim = true;
985 }
986 }
987
988 if (!have_aim) {
989 aim = c.center;
990 float ad = vec_dist(g_cam_eye, aim);
991 if (ad > MELEE_RANGE) {
992 // The body is in range but its center is not: aim just inside the
993 // closest point on the AABB.
994 aim = c.closest;
995 aim.x += (c.center.x - c.closest.x) * 0.25f;
996 aim.y += (c.center.y - c.closest.y) * 0.25f;
997 aim.z += (c.center.z - c.closest.z) * 0.25f;
998 ad = vec_dist(g_cam_eye, aim);
999 if (ad > MELEE_RANGE)
1000 aim = c.closest;
1001 }
1002 if (!is_visible(g_cam_eye, aim))
1003 continue;
1004 }
1005
1006 QAngle orig{g_cam_view.x, g_cam_view.y, g_cam_view.z};
1007 QAngle desired = angle_to(g_cam_eye, aim);
1008 float np = desired.x;
1009 if (np > 89.0f) np = 89.0f;
1010 if (np < -89.0f) np = -89.0f;
1011 float ny = desired.y;
1012 while (ny > 180.0f) ny -= 360.0f;
1013 while (ny < -180.0f) ny += 360.0f;
1014
1015 preserve_movement(cmd, orig.y, ny);
1016 cmd->viewangles[0] = np;
1017 cmd->viewangles[1] = ny;
1018 cmd->viewangles[2] = orig.z;
1019 set_silent_restore(orig);
1020 if (g_iface.engine) {
1021 QAngle restore{orig.x, orig.y, orig.z};
1022 using SetViewFn = void (*)(void *, QAngle *);
1023 vfunc<SetViewFn>(g_iface.engine, 20)(g_iface.engine, &restore);
1024 }
1025 cmd->buttons |= IN_ATTACK;
1026
1027 static int logs = 0;
1028 if (logs < 10) {
1029 logs++;
1030 logf("melee: swing idx=%d dist=%.1f aim=(%.1f %.1f %.1f)", c.idx,
1031 c.dist, aim.x, aim.y, aim.z);
1032 }
1033 return true;
1034 }
1035
1036 static double last_none = 0.0;
1037 double now = esp_now();
1038 if (now - last_none >= 2.0) {
1039 last_none = now;
1040 logf("melee: no visible target within %.0f", MELEE_RANGE);
1041 }
1042 return false;
1043}
1044
787bool triggerbot_apply(CUserCmd *cmd) { 1045bool triggerbot_apply(CUserCmd *cmd) {
788 if (!g_iface.enginetrace || !g_iface.entitylist) 1046 if (!g_iface.enginetrace || !g_iface.entitylist)
789 return false; 1047 return false;
diff --git a/src/hl2h.cpp b/src/hl2h.cpp
index 4ba30bc..ee2e990 100644
--- a/src/hl2h.cpp
+++ b/src/hl2h.cpp
@@ -500,34 +500,36 @@ static void do_move(int seq, float frametime) {
500 if (jumpbug_apply(cmd, localIdx, ent, flags, vel, frametime)) 500 if (jumpbug_apply(cmd, localIdx, ent, flags, vel, frametime))
501 note_modified(cmd, flags, vel); 501 note_modified(cmd, flags, vel);
502 502
503 if (!feature_active(FEAT_AIMBOT) || !feature_has_bind(FEAT_AIMBOT)) 503 bool melee = feature_active(FEAT_MELEE) && melee_weapon_active(ent);
504
505 if (!feature_active(FEAT_AIMBOT) || !feature_has_bind(FEAT_AIMBOT) || melee)
504 aimbot_reset_lock(); 506 aimbot_reset_lock();
505 if (!feature_active(FEAT_SILENTAIM) || !feature_has_bind(FEAT_SILENTAIM)) 507 if (!feature_active(FEAT_SILENTAIM) || !feature_has_bind(FEAT_SILENTAIM) ||
508 melee)
506 silentaim_reset_lock(); 509 silentaim_reset_lock();
507 510
508 if (feature_active(FEAT_AIMBOT)) { 511 if (melee) {
509 static bool aim_feature_logged = false; 512 if (melee_aimbot_apply(cmd))
510 if (!aim_feature_logged) {
511 aim_feature_logged = true;
512 logf("aimbot: feature active (bind held)");
513 }
514 if (aimbot_apply(cmd)) {
515 const float *vel = (const float *)(e + g_net.velocity);
516 note_modified(cmd, flags, vel); 513 note_modified(cmd, flags, vel);
514 } else {
515 if (feature_active(FEAT_AIMBOT)) {
516 static bool aim_feature_logged = false;
517 if (!aim_feature_logged) {
518 aim_feature_logged = true;
519 logf("aimbot: feature active (bind held)");
520 }
521 if (aimbot_apply(cmd))
522 note_modified(cmd, flags, vel);
517 } 523 }
518 }
519 524
520 if (feature_active(FEAT_TRIGGERBOT)) { 525 if (feature_active(FEAT_TRIGGERBOT)) {
521 if (triggerbot_apply(cmd)) { 526 if (triggerbot_apply(cmd))
522 const float *vel = (const float *)(e + g_net.velocity); 527 note_modified(cmd, flags, vel);
523 note_modified(cmd, flags, vel);
524 } 528 }
525 }
526 529
527 if (feature_active(FEAT_SILENTAIM)) { 530 if (feature_active(FEAT_SILENTAIM)) {
528 if (silent_aim_apply(cmd)) { 531 if (silent_aim_apply(cmd))
529 const float *vel = (const float *)(e + g_net.velocity); 532 note_modified(cmd, flags, vel);
530 note_modified(cmd, flags, vel);
531 } 533 }
532 } 534 }
533} 535}
diff --git a/src/hl2h.h b/src/hl2h.h
index 08eb2ef..2680591 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -82,6 +82,7 @@ struct NetVars {
82 int lifestate = -1; 82 int lifestate = -1;
83 int modelindex = -1; 83 int modelindex = -1;
84 int absvelocity = -1; // client C_BaseEntity::m_vecAbsVelocity (prediction) 84 int absvelocity = -1; // client C_BaseEntity::m_vecAbsVelocity (prediction)
85 int activeweapon = -1; // client m_hActiveWeapon handle
85 // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72 86 // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72
86 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; } 87 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; }
87 // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100. 88 // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100.
@@ -147,6 +148,8 @@ float getfov(const QAngle &my_view, const QAngle &aim_ang);
147bool aimbot_apply(CUserCmd *cmd); 148bool aimbot_apply(CUserCmd *cmd);
148bool triggerbot_apply(CUserCmd *cmd); 149bool triggerbot_apply(CUserCmd *cmd);
149bool silent_aim_apply(CUserCmd *cmd); 150bool silent_aim_apply(CUserCmd *cmd);
151bool melee_weapon_active(void *local_ent);
152bool melee_aimbot_apply(CUserCmd *cmd);
150enum FeatureId { 153enum FeatureId {
151 FEAT_BHOP = 0, 154 FEAT_BHOP = 0,
152 FEAT_ABH = 1, 155 FEAT_ABH = 1,
@@ -154,7 +157,8 @@ enum FeatureId {
154 FEAT_AIMBOT = 3, 157 FEAT_AIMBOT = 3,
155 FEAT_TRIGGERBOT = 4, 158 FEAT_TRIGGERBOT = 4,
156 FEAT_SILENTAIM = 5, 159 FEAT_SILENTAIM = 5,
157 FEAT_JUMPBUG = 6 160 FEAT_JUMPBUG = 6,
161 FEAT_MELEE = 7
158}; 162};
159enum EspOption { 163enum EspOption {
160 ESP_OPT_SHOW_NEUTRAL = 0, 164 ESP_OPT_SHOW_NEUTRAL = 0,
@@ -179,6 +183,7 @@ void resolve_netvars();
179bool resolve_server_netvars(); 183bool resolve_server_netvars();
180int server_entity_health(int idx, int *maxhealth, int *lifestate, void **base_out); 184int server_entity_health(int idx, int *maxhealth, int *lifestate, void **base_out);
181bool server_set_velocity_z(int idx, float vz); 185bool server_set_velocity_z(int idx, float vz);
186int server_active_weapon(int idx);
182int server_model_index(void *base); 187int server_model_index(void *base);
183void find_modules(); 188void find_modules();
184void init_conmsg(); 189void init_conmsg();
diff --git a/src/menu.cpp b/src/menu.cpp
index 8df67fe..f590d69 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -64,6 +64,7 @@ static bool g_bhop_autostrafe = true;
64static FeatureState g_abh{true, 0}; 64static FeatureState g_abh{true, 0};
65static FeatureState g_jumpbug{true, 0}; 65static FeatureState g_jumpbug{true, 0};
66static bool g_jumpbug_assist = false; 66static bool g_jumpbug_assist = false;
67static FeatureState g_melee{true, 0};
67static FeatureState g_esp{true, 0}; 68static FeatureState g_esp{true, 0};
68static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu 69static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu
69static FeatureState g_triggerbot{true, 603}; // default Mouse4 70static FeatureState g_triggerbot{true, 603}; // default Mouse4
@@ -101,6 +102,8 @@ static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active,
101static MenuNode g_jumpbug_feat{NODE_FEATURE, "Jumpbug", false, 1, {}, nullptr, nullptr, nullptr}; 102static MenuNode g_jumpbug_feat{NODE_FEATURE, "Jumpbug", false, 1, {}, nullptr, nullptr, nullptr};
102static MenuNode g_jumpbug_active{NODE_TOGGLE, "Active", false, 0, {}, &g_jumpbug.active, nullptr, nullptr}; 103static MenuNode g_jumpbug_active{NODE_TOGGLE, "Active", false, 0, {}, &g_jumpbug.active, nullptr, nullptr};
103static MenuNode g_jumpbug_assist_node{NODE_TOGGLE, "Assist timing", false, 0, {}, &g_jumpbug_assist, nullptr, nullptr}; 104static MenuNode g_jumpbug_assist_node{NODE_TOGGLE, "Assist timing", false, 0, {}, &g_jumpbug_assist, nullptr, nullptr};
105static MenuNode g_melee_feat{NODE_FEATURE, "Melee killbot", false, 1, {}, nullptr, nullptr, nullptr};
106static MenuNode g_melee_active{NODE_TOGGLE, "Active", false, 0, {}, &g_melee.active, nullptr, nullptr};
104static MenuNode g_esp_feat{NODE_FEATURE, "ESP", false, 1, {}, nullptr, nullptr, nullptr}; 107static MenuNode g_esp_feat{NODE_FEATURE, "ESP", false, 1, {}, nullptr, nullptr, nullptr};
105static MenuNode g_esp_active{NODE_TOGGLE, "Active", false, 0, {}, &g_esp.active, nullptr, nullptr}; 108static MenuNode g_esp_active{NODE_TOGGLE, "Active", false, 0, {}, &g_esp.active, nullptr, nullptr};
106static MenuNode g_esp_opt_nodes[ESP_OPT_COUNT] = { 109static MenuNode g_esp_opt_nodes[ESP_OPT_COUNT] = {
@@ -156,6 +159,8 @@ static void build_tree() {
156 g_jumpbug_feat.children[0] = &g_jumpbug_active; 159 g_jumpbug_feat.children[0] = &g_jumpbug_active;
157 g_jumpbug_feat.children[1] = &g_jumpbug_assist_node; 160 g_jumpbug_feat.children[1] = &g_jumpbug_assist_node;
158 g_jumpbug_feat.child_count = 2; 161 g_jumpbug_feat.child_count = 2;
162 g_melee_feat.children[0] = &g_melee_active;
163 g_melee_feat.child_count = 1;
159 g_esp_feat.children[0] = &g_esp_active; 164 g_esp_feat.children[0] = &g_esp_active;
160 g_aimbot_feat.children[0] = &g_aimbot_active; 165 g_aimbot_feat.children[0] = &g_aimbot_active;
161 g_aimbot_feat.children[1] = &g_aimbot_bind; 166 g_aimbot_feat.children[1] = &g_aimbot_bind;
@@ -181,11 +186,12 @@ static void build_tree() {
181 g_root.children[0] = &g_bhop_feat; 186 g_root.children[0] = &g_bhop_feat;
182 g_root.children[1] = &g_abh_feat; 187 g_root.children[1] = &g_abh_feat;
183 g_root.children[2] = &g_jumpbug_feat; 188 g_root.children[2] = &g_jumpbug_feat;
184 g_root.children[3] = &g_esp_feat; 189 g_root.children[3] = &g_melee_feat;
185 g_root.children[4] = &g_aimbot_feat; 190 g_root.children[4] = &g_esp_feat;
186 g_root.children[5] = &g_triggerbot_feat; 191 g_root.children[5] = &g_aimbot_feat;
187 g_root.children[6] = &g_silentaim_feat; 192 g_root.children[6] = &g_triggerbot_feat;
188 g_root.child_count = 7; 193 g_root.children[7] = &g_silentaim_feat;
194 g_root.child_count = 8;
189} 195}
190 196
191static int g_depth[48]; // depth of each visible row (for indentation) 197static int g_depth[48]; // depth of each visible row (for indentation)
@@ -309,6 +315,7 @@ static void config_save() {
309 fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind); 315 fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind);
310 fprintf(f, "[jumpbug]\nactive=%d\nassist=%d\n", g_jumpbug.active, 316 fprintf(f, "[jumpbug]\nactive=%d\nassist=%d\n", g_jumpbug.active,
311 (int)g_jumpbug_assist); 317 (int)g_jumpbug_assist);
318 fprintf(f, "[melee]\nactive=%d\n", g_melee.active);
312 fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n" 319 fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n"
313 "fov=%d\nfov_circle=%d\n" 320 "fov=%d\nfov_circle=%d\n"
314 "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n" 321 "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n"
@@ -349,6 +356,8 @@ static void config_load() {
349 cur = &g_abh; 356 cur = &g_abh;
350 else if (strstr(line, "[jumpbug]")) 357 else if (strstr(line, "[jumpbug]"))
351 cur = &g_jumpbug; 358 cur = &g_jumpbug;
359 else if (strstr(line, "[melee]"))
360 cur = &g_melee;
352 else if (strstr(line, "[esp]")) 361 else if (strstr(line, "[esp]"))
353 cur = &g_esp; 362 cur = &g_esp;
354 else if (strstr(line, "[aimbot]")) 363 else if (strstr(line, "[aimbot]"))
@@ -537,10 +546,10 @@ static bool key_pressed(int sc) {
537// feature gate queried from hl2h.cpp do_move(): feature must be Active AND 546// feature gate queried from hl2h.cpp do_move(): feature must be Active AND
538// its bind key held (always-on when unbound). 547// its bind key held (always-on when unbound).
539static FeatureState *feature_state(int feature) { 548static FeatureState *feature_state(int feature) {
540 static FeatureState *feats[7] = {&g_bhop, &g_abh, &g_esp, 549 static FeatureState *feats[8] = {&g_bhop, &g_abh, &g_esp,
541 &g_aimbot, &g_triggerbot, &g_silentaim, 550 &g_aimbot, &g_triggerbot, &g_silentaim,
542 &g_jumpbug}; 551 &g_jumpbug, &g_melee};
543 if (feature < 0 || feature > 6) 552 if (feature < 0 || feature > 7)
544 return nullptr; 553 return nullptr;
545 return feats[feature]; 554 return feats[feature];
546} 555}
diff --git a/src/netvars.cpp b/src/netvars.cpp
index 98ebe31..3b2d3b8 100644
--- a/src/netvars.cpp
+++ b/src/netvars.cpp
@@ -89,6 +89,9 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) {
89 g_net.health = (int)(base_off + (uint32_t)poff); 89 g_net.health = (int)(base_off + (uint32_t)poff);
90 } else if (strcmp(pname2, "m_iMaxHealth") == 0 && g_net.maxhealth < 0) { 90 } else if (strcmp(pname2, "m_iMaxHealth") == 0 && g_net.maxhealth < 0) {
91 g_net.maxhealth = (int)(base_off + (uint32_t)poff); 91 g_net.maxhealth = (int)(base_off + (uint32_t)poff);
92 } else if (strcmp(pname2, "m_hActiveWeapon") == 0 &&
93 g_net.activeweapon < 0) {
94 g_net.activeweapon = (int)(base_off + (uint32_t)poff);
92 } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { 95 } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) {
93 g_net.teamnum = (int)(base_off + (uint32_t)poff); 96 g_net.teamnum = (int)(base_off + (uint32_t)poff);
94 } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { 97 } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) {
@@ -269,9 +272,9 @@ void resolve_netvars() {
269 logf("m_vecAbsVelocity unresolved; jumpbug assist disabled"); 272 logf("m_vecAbsVelocity unresolved; jumpbug assist disabled");
270 logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d " 273 logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d "
271 "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d " 274 "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d "
272 "modelindex=%d absvelocity=%d", 275 "modelindex=%d absvelocity=%d activeweapon=%d",
273 g_net.flags, g_net.velocity, g_net.movetype, g_net.health, 276 g_net.flags, g_net.velocity, g_net.movetype, g_net.health,
274 g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset, 277 g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset,
275 g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex, 278 g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex,
276 g_net.absvelocity); 279 g_net.absvelocity, g_net.activeweapon);
277} 280}
diff --git a/src/server.cpp b/src/server.cpp
index 3766906..896ce3e 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -33,6 +33,7 @@ int g_srv_lifestate = -1;
33int g_srv_modelindex = -1; 33int g_srv_modelindex = -1;
34int g_srv_absvelocity = -1; 34int g_srv_absvelocity = -1;
35int g_srv_velocity = -1; 35int g_srv_velocity = -1;
36int g_srv_activeweapon = -1;
36static bool g_srv_ready = false; 37static bool g_srv_ready = false;
37static bool g_srv_checked = false; 38static bool g_srv_checked = false;
38 39
@@ -109,12 +110,14 @@ bool resolve_server_netvars() {
109 g_srv_modelindex = datamap_offset("m_nModelIndex", 7); // FIELD_SHORT 110 g_srv_modelindex = datamap_offset("m_nModelIndex", 7); // FIELD_SHORT
110 g_srv_absvelocity = datamap_offset("m_vecAbsVelocity", 3); // FIELD_VECTOR 111 g_srv_absvelocity = datamap_offset("m_vecAbsVelocity", 3); // FIELD_VECTOR
111 g_srv_velocity = datamap_offset("m_vecVelocity", 3); // FIELD_VECTOR 112 g_srv_velocity = datamap_offset("m_vecVelocity", 3); // FIELD_VECTOR
113 g_srv_activeweapon = datamap_offset("m_hActiveWeapon", 13); // FIELD_EHANDLE
112 g_srv_ready = g_srv_health > 0; 114 g_srv_ready = g_srv_health > 0;
113 if (g_srv_ready) { 115 if (g_srv_ready) {
114 logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " 116 logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d "
115 "modelindex=%d absvel=%d vel=%d", 117 "modelindex=%d absvel=%d vel=%d activeweapon=%d",
116 g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, 118 g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate,
117 g_srv_modelindex, g_srv_absvelocity, g_srv_velocity); 119 g_srv_modelindex, g_srv_absvelocity, g_srv_velocity,
120 g_srv_activeweapon);
118 fail_logged = false; 121 fail_logged = false;
119 } else if (!fail_logged) { 122 } else if (!fail_logged) {
120 fail_logged = true; 123 fail_logged = true;
@@ -194,3 +197,18 @@ bool server_set_velocity_z(int idx, float vz) {
194 *(float *)((uintptr_t)base + g_srv_absvelocity + 8) = vz; 197 *(float *)((uintptr_t)base + g_srv_absvelocity + 8) = vz;
195 return true; 198 return true;
196} 199}
200
201// Entity index of the player's active weapon from the authoritative server
202// entity. Used as a fallback if the client m_hActiveWeapon netvar is missing.
203int server_active_weapon(int idx) {
204 if (!g_srv_ready || !g_iface.engineserver || g_srv_activeweapon <= 0)
205 return -1;
206 void *base = nullptr;
207 if (server_entity_health(idx, nullptr, nullptr, &base) < 0 || !base)
208 return -1;
209 uint32_t h = *(uint32_t *)((uintptr_t)base + g_srv_activeweapon);
210 if (h == 0xFFFFFFFFu)
211 return -1;
212 int wi = (int)(h & 0xFFFF);
213 return (wi > 0 && wi <= 4096) ? wi : -1;
214}