summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-27 15:22:19 +0100
committersalmonik <salmonik@tuta.io>2026-10-27 15:22:19 +0100
commit27c9ad766e1b1333922b4c14cbde9d9b77c8dab0 (patch)
tree7c758c0d431a389cc3826495b4c6d20ca28c6530
parentMelee killbot: 360-degree crowbar silent aim (diff)
downloadhl2h-27c9ad766e1b1333922b4c14cbde9d9b77c8dab0.tar.gz
hl2h-27c9ad766e1b1333922b4c14cbde9d9b77c8dab0.zip
Spread control: client-seed no-spread
HL2's CBaseEntity::FireBullets seeds its RNG from the usercmd random_seed (m_bUseServerRandomSeed is false for HL2 weapons), so the client can select the 8-bit seed that puts CShotManipulator::ApplySpread closest to the center. - New feature Spread control -> Active / No spread (both default on). - Precompute the best seed for a single shot and for the shotgun's 8-shot primary (first pellet accurate) and 12-shot secondary windows using the engine's own RandomSeed/RandomFloat from libvstdlib. - Set cmd->random_seed on attack commands; note_modified repairs the CRC. - The random cone offset is neutralized, but the cone size, the pistol accuracy penalty, server view punch and any damage falloff remain server-side; stock HL2 has no distance damage falloff.
-rw-r--r--CONTEXT.md30
-rw-r--r--src/esp.cpp136
-rw-r--r--src/hl2h.cpp3
-rw-r--r--src/hl2h.h7
-rw-r--r--src/menu.cpp41
5 files changed, 202 insertions, 15 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 8fb2197..861fbf8 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -111,6 +111,9 @@ HL2hake
111 Assist timing 111 Assist timing
112 Melee killbot 112 Melee killbot
113 Active 113 Active
114 Spread control
115 Active
116 No spread
114 ESP 117 ESP
115 Active 118 Active
116 Show neutral / healthbar / healthnum / name / skeleton / box / speed graph 119 Show neutral / healthbar / healthnum / name / skeleton / box / speed graph
@@ -186,6 +189,27 @@ Binds use SDL scancodes; mouse buttons are pseudo-scancodes 600..604.
186- Vischeck uses the shared `MASK_VISIBLE`/world-only path (static props are 189- Vischeck uses the shared `MASK_VISIBLE`/world-only path (static props are
187 ignored). 190 ignored).
188 191
192## Spread control
193
194- HL2's `CBaseEntity::FireBullets` seeds its RNG from
195 `CBaseEntity::GetPredictionRandomSeed(info.m_bUseServerRandomSeed)`; HL2
196 weapons leave `m_bUseServerRandomSeed` false, so the server uses the
197 client usercmd's `random_seed` (masked to 8 bits). This is the TF2-style
198 client-side seed hook.
199- `Spread control -> No spread` precomputes, with the engine's own
200 `RandomSeed`/`RandomFloat`, the 8-bit seed whose
201 `CShotManipulator::ApplySpread` offset is smallest. It sets
202 `cmd->random_seed` on attack commands; `note_modified` repairs the CRC.
203- Shotguns use consecutive seeds per pellet. The primary's first pellet is
204 `FIRE_BULLETS_FIRST_SHOT_ACCURATE`, so the table excludes it; the secondary
205 uses all 12. Separate best starting seeds are precomputed for 1, 8 and 12
206 pellets.
207- This attacks the random cone offset only. The cone size, the pistol's
208 `m_flAccuracyPenalty` growth, the server view punch added to
209 `GetAutoaimVector`, and any damage falloff are server-side state. In
210 single-player those can be written/hooked locally; stock HL2 has no
211 distance damage falloff.
212
189## Jumpbug 213## Jumpbug
190 214
191- Automatic while falling when the predicted landing speed 215- Automatic while falling when the predicted landing speed
@@ -237,6 +261,8 @@ Binds use SDL scancodes; mouse buttons are pseudo-scancodes 600..604.
237 261
238- **GUI**: replace/augment the VGUI ISurface menu with an immediate-mode GUI 262- **GUI**: replace/augment the VGUI ISurface menu with an immediate-mode GUI
239 (Dear ImGui or a custom implementation); needs a design grill first. 263 (Dear ImGui or a custom implementation); needs a design grill first.
240- **Distance weapon spread thesis**: TBD — user will explain. Likely models 264- **Server-side spread/recoil control**: optional single-player writes to the
241 weapon spread as a function of distance for aim/trigger decisions or visuals. 265 pistol accuracy penalty and view punch, and a true zero-cone hook if wanted.
266- **Distance weapon spread thesis**: addressed by Spread control; stock HL2
267 hitscan damage is range-independent, so distance efficiency is the cone.
242 268
diff --git a/src/esp.cpp b/src/esp.cpp
index 47df57d..0117d27 100644
--- a/src/esp.cpp
+++ b/src/esp.cpp
@@ -1042,6 +1042,142 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1042 return false; 1042 return false;
1043} 1043}
1044 1044
1045// ---------------------------------------------------------------------------
1046// weapon spread control
1047// ---------------------------------------------------------------------------
1048//
1049// HL2's CBaseEntity::FireBullets uses CBaseEntity::GetPredictionRandomSeed()
1050// (the client usercmd's random_seed) unless a weapon opts into
1051// m_bUseServerRandomSeed. HL2 weapons do not, so the client can choose the
1052// 8-bit seed and with it the random cone offset. CShotManipulator::ApplySpread
1053// draws four uniform floats per attempt; we precompute the best seed for one
1054// shot and for the shotgun's pellet windows with the engine's own RNG.
1055
1056static bool g_spread_ready = false;
1057static int g_spread_seed1 = 0;
1058static int g_spread_seed8 = 0; // first pellet exact, 7 spread
1059static int g_spread_seed12 = 0; // all 12 spread
1060
1061static void spread_compute() {
1062 using SeedFn = void (*)(int);
1063 using FloatFn = float (*)(float, float);
1064 SeedFn seed = (SeedFn)dlsym(RTLD_DEFAULT, "RandomSeed");
1065 FloatFn rf = (FloatFn)dlsym(RTLD_DEFAULT, "RandomFloat");
1066 if (!seed || !rf) {
1067 size_t slash = g_engine.path.find_last_of('/');
1068 if (slash != std::string::npos) {
1069 std::string path = g_engine.path.substr(0, slash + 1) + "libvstdlib.so";
1070 void *h = dlopen(path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL);
1071 if (!h)
1072 h = dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL);
1073 if (h) {
1074 seed = (SeedFn)dlsym(h, "RandomSeed");
1075 rf = (FloatFn)dlsym(h, "RandomFloat");
1076 }
1077 }
1078 }
1079 if (!seed || !rf) {
1080 logf("spread: RandomSeed/RandomFloat unavailable");
1081 return;
1082 }
1083
1084 auto draw = [&](int s, float &x, float &y) {
1085 seed(s);
1086 int it = 0;
1087 do {
1088 // CShotManipulator::ApplySpread with the default bias of 1.0:
1089 // shotBias = 1.0, flatness = 0.5.
1090 x = rf(-1.0f, 1.0f) * 0.5f + rf(-1.0f, 1.0f) * 0.5f;
1091 y = rf(-1.0f, 1.0f) * 0.5f + rf(-1.0f, 1.0f) * 0.5f;
1092 it++;
1093 } while (x * x + y * y > 1.0f && it < 64);
1094 };
1095
1096 float best1 = 1e9f;
1097 for (int s = 0; s < 256; s++) {
1098 float x, y;
1099 draw(s, x, y);
1100 float r2 = x * x + y * y;
1101 if (r2 < best1) {
1102 best1 = r2;
1103 g_spread_seed1 = s;
1104 }
1105 }
1106
1107 auto window = [&](int n, int first) {
1108 int best_s = 0;
1109 float best = 1e9f;
1110 for (int s = 0; s < 256; s++) {
1111 float score = 0.0f;
1112 for (int i = first; i < n; i++) {
1113 float x, y;
1114 draw(s + i, x, y);
1115 score += x * x + y * y;
1116 }
1117 if (score < best) {
1118 best = score;
1119 best_s = s;
1120 }
1121 }
1122 return best_s;
1123 };
1124 g_spread_seed8 = window(8, 1);
1125 g_spread_seed12 = window(12, 0);
1126 seed(0);
1127
1128 logf("spread: seeds single=%d shotgun8=%d shotgun12=%d", g_spread_seed1,
1129 g_spread_seed8, g_spread_seed12);
1130 g_spread_ready = true;
1131}
1132
1133static bool active_weapon_is_shotgun(void *local_ent) {
1134 if (!local_ent || g_net.activeweapon < 0)
1135 return false;
1136 uint32_t h = *(uint32_t *)((uintptr_t)local_ent + g_net.activeweapon);
1137 if (h == 0xFFFFFFFFu)
1138 return false;
1139 int idx = (int)(h & 0xFFFF);
1140 if (idx <= 0 || idx > 4096)
1141 return false;
1142 void *wep = entitylist_get(idx);
1143 void *net = entity_networkable(idx);
1144 const char *cls = net ? entity_class_name(net, nullptr) : nullptr;
1145 const char *model = wep ? entity_model_name(wep) : nullptr;
1146 return (cls && strstr(cls, "Shotgun")) ||
1147 (model && strstr(model, "shotgun"));
1148}
1149
1150bool spread_control_apply(CUserCmd *cmd, void *local_ent) {
1151 if (!feature_active(FEAT_SPREAD) || !spread_no_spread_enabled())
1152 return false;
1153 if (!(cmd->buttons & (IN_ATTACK | IN_ATTACK2)))
1154 return false;
1155 if (!g_spread_ready) {
1156 static bool tried = false;
1157 if (!tried) {
1158 tried = true;
1159 spread_compute();
1160 }
1161 }
1162 if (!g_spread_ready)
1163 return false;
1164
1165 bool shotgun = active_weapon_is_shotgun(local_ent);
1166 int seed = g_spread_seed1;
1167 if (shotgun)
1168 seed = (cmd->buttons & IN_ATTACK2) ? g_spread_seed12 : g_spread_seed8;
1169 if (cmd->random_seed == seed)
1170 return false;
1171 cmd->random_seed = seed;
1172
1173 static int logs = 0;
1174 if (logs < 10) {
1175 logs++;
1176 logf("spread: seed=%d (%s)", seed, shotgun ? "shotgun" : "single");
1177 }
1178 return true;
1179}
1180
1045bool triggerbot_apply(CUserCmd *cmd) { 1181bool triggerbot_apply(CUserCmd *cmd) {
1046 if (!g_iface.enginetrace || !g_iface.entitylist) 1182 if (!g_iface.enginetrace || !g_iface.entitylist)
1047 return false; 1183 return false;
diff --git a/src/hl2h.cpp b/src/hl2h.cpp
index ee2e990..dddcbdc 100644
--- a/src/hl2h.cpp
+++ b/src/hl2h.cpp
@@ -532,6 +532,9 @@ static void do_move(int seq, float frametime) {
532 note_modified(cmd, flags, vel); 532 note_modified(cmd, flags, vel);
533 } 533 }
534 } 534 }
535
536 if (spread_control_apply(cmd, ent))
537 note_modified(cmd, flags, vel);
535} 538}
536 539
537// --------------------------------------------------------------------------- 540// ---------------------------------------------------------------------------
diff --git a/src/hl2h.h b/src/hl2h.h
index 2680591..ef8e8d6 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -129,7 +129,7 @@ struct CUserCmd {
129// engine constants 129// engine constants
130// --------------------------------------------------------------------------- 130// ---------------------------------------------------------------------------
131 131
132enum { IN_ATTACK = 1 << 0, IN_JUMP = 1 << 1, IN_DUCK = 1 << 2, IN_USE = 1 << 5 }; 132enum { IN_ATTACK = 1 << 0, IN_JUMP = 1 << 1, IN_DUCK = 1 << 2, IN_USE = 1 << 5, IN_ATTACK2 = 1 << 11 };
133enum { FL_ONGROUND = 1 << 0, FL_DUCKING = 1 << 1, FL_INWATER = 1 << 9 }; 133enum { FL_ONGROUND = 1 << 0, FL_DUCKING = 1 << 1, FL_INWATER = 1 << 9 };
134enum { MOVETYPE_WALK = 2, MOVETYPE_NOCLIP = 8, MOVETYPE_LADDER = 9 }; 134enum { MOVETYPE_WALK = 2, MOVETYPE_NOCLIP = 8, MOVETYPE_LADDER = 9 };
135 135
@@ -150,6 +150,7 @@ bool triggerbot_apply(CUserCmd *cmd);
150bool silent_aim_apply(CUserCmd *cmd); 150bool silent_aim_apply(CUserCmd *cmd);
151bool melee_weapon_active(void *local_ent); 151bool melee_weapon_active(void *local_ent);
152bool melee_aimbot_apply(CUserCmd *cmd); 152bool melee_aimbot_apply(CUserCmd *cmd);
153bool spread_control_apply(CUserCmd *cmd, void *local_ent);
153enum FeatureId { 154enum FeatureId {
154 FEAT_BHOP = 0, 155 FEAT_BHOP = 0,
155 FEAT_ABH = 1, 156 FEAT_ABH = 1,
@@ -158,7 +159,8 @@ enum FeatureId {
158 FEAT_TRIGGERBOT = 4, 159 FEAT_TRIGGERBOT = 4,
159 FEAT_SILENTAIM = 5, 160 FEAT_SILENTAIM = 5,
160 FEAT_JUMPBUG = 6, 161 FEAT_JUMPBUG = 6,
161 FEAT_MELEE = 7 162 FEAT_MELEE = 7,
163 FEAT_SPREAD = 8
162}; 164};
163enum EspOption { 165enum EspOption {
164 ESP_OPT_SHOW_NEUTRAL = 0, 166 ESP_OPT_SHOW_NEUTRAL = 0,
@@ -193,6 +195,7 @@ bool feature_bind_held(int feature);
193bool esp_option(int opt); 195bool esp_option(int opt);
194bool bhop_autostrafe_enabled(); 196bool bhop_autostrafe_enabled();
195bool jumpbug_assist_enabled(); 197bool jumpbug_assist_enabled();
198bool spread_no_spread_enabled();
196unsigned aimbot_bone_mask(); 199unsigned aimbot_bone_mask();
197int aimbot_smooth_step(); 200int aimbot_smooth_step();
198bool aimbot_vischeck_enabled(); 201bool aimbot_vischeck_enabled();
diff --git a/src/menu.cpp b/src/menu.cpp
index f590d69..9dffc9e 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -40,7 +40,7 @@ struct MenuNode {
40 const char *label; 40 const char *label;
41 bool expanded; // for root/feature: whether children are visible 41 bool expanded; // for root/feature: whether children are visible
42 int child_count; 42 int child_count;
43 MenuNode *children[8]; // max children per node 43 MenuNode *children[12]; // max children per node
44 44
45 // leaf data 45 // leaf data
46 bool *active_ptr; // NODE_TOGGLE: pointer to the active flag 46 bool *active_ptr; // NODE_TOGGLE: pointer to the active flag
@@ -65,6 +65,8 @@ static 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_melee{true, 0};
68static FeatureState g_spread{true, 0};
69static bool g_spread_no_spread = true;
68static FeatureState g_esp{true, 0}; 70static FeatureState g_esp{true, 0};
69static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu 71static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu
70static FeatureState g_triggerbot{true, 603}; // default Mouse4 72static FeatureState g_triggerbot{true, 603}; // default Mouse4
@@ -104,6 +106,9 @@ static MenuNode g_jumpbug_active{NODE_TOGGLE, "Active", false, 0, {}, &g_jumpbug
104static MenuNode g_jumpbug_assist_node{NODE_TOGGLE, "Assist timing", false, 0, {}, &g_jumpbug_assist, nullptr, nullptr}; 106static 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}; 107static 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}; 108static MenuNode g_melee_active{NODE_TOGGLE, "Active", false, 0, {}, &g_melee.active, nullptr, nullptr};
109static MenuNode g_spread_feat{NODE_FEATURE, "Spread control", false, 1, {}, nullptr, nullptr, nullptr};
110static MenuNode g_spread_active{NODE_TOGGLE, "Active", false, 0, {}, &g_spread.active, nullptr, nullptr};
111static MenuNode g_spread_no_spread_node{NODE_TOGGLE, "No spread", false, 0, {}, &g_spread_no_spread, nullptr, nullptr};
107static MenuNode g_esp_feat{NODE_FEATURE, "ESP", false, 1, {}, nullptr, nullptr, nullptr}; 112static MenuNode g_esp_feat{NODE_FEATURE, "ESP", false, 1, {}, nullptr, nullptr, nullptr};
108static MenuNode g_esp_active{NODE_TOGGLE, "Active", false, 0, {}, &g_esp.active, nullptr, nullptr}; 113static MenuNode g_esp_active{NODE_TOGGLE, "Active", false, 0, {}, &g_esp.active, nullptr, nullptr};
109static MenuNode g_esp_opt_nodes[ESP_OPT_COUNT] = { 114static MenuNode g_esp_opt_nodes[ESP_OPT_COUNT] = {
@@ -161,6 +166,9 @@ static void build_tree() {
161 g_jumpbug_feat.child_count = 2; 166 g_jumpbug_feat.child_count = 2;
162 g_melee_feat.children[0] = &g_melee_active; 167 g_melee_feat.children[0] = &g_melee_active;
163 g_melee_feat.child_count = 1; 168 g_melee_feat.child_count = 1;
169 g_spread_feat.children[0] = &g_spread_active;
170 g_spread_feat.children[1] = &g_spread_no_spread_node;
171 g_spread_feat.child_count = 2;
164 g_esp_feat.children[0] = &g_esp_active; 172 g_esp_feat.children[0] = &g_esp_active;
165 g_aimbot_feat.children[0] = &g_aimbot_active; 173 g_aimbot_feat.children[0] = &g_aimbot_active;
166 g_aimbot_feat.children[1] = &g_aimbot_bind; 174 g_aimbot_feat.children[1] = &g_aimbot_bind;
@@ -187,11 +195,12 @@ static void build_tree() {
187 g_root.children[1] = &g_abh_feat; 195 g_root.children[1] = &g_abh_feat;
188 g_root.children[2] = &g_jumpbug_feat; 196 g_root.children[2] = &g_jumpbug_feat;
189 g_root.children[3] = &g_melee_feat; 197 g_root.children[3] = &g_melee_feat;
190 g_root.children[4] = &g_esp_feat; 198 g_root.children[4] = &g_spread_feat;
191 g_root.children[5] = &g_aimbot_feat; 199 g_root.children[5] = &g_esp_feat;
192 g_root.children[6] = &g_triggerbot_feat; 200 g_root.children[6] = &g_aimbot_feat;
193 g_root.children[7] = &g_silentaim_feat; 201 g_root.children[7] = &g_triggerbot_feat;
194 g_root.child_count = 8; 202 g_root.children[8] = &g_silentaim_feat;
203 g_root.child_count = 9;
195} 204}
196 205
197static int g_depth[48]; // depth of each visible row (for indentation) 206static int g_depth[48]; // depth of each visible row (for indentation)
@@ -316,6 +325,8 @@ static void config_save() {
316 fprintf(f, "[jumpbug]\nactive=%d\nassist=%d\n", g_jumpbug.active, 325 fprintf(f, "[jumpbug]\nactive=%d\nassist=%d\n", g_jumpbug.active,
317 (int)g_jumpbug_assist); 326 (int)g_jumpbug_assist);
318 fprintf(f, "[melee]\nactive=%d\n", g_melee.active); 327 fprintf(f, "[melee]\nactive=%d\n", g_melee.active);
328 fprintf(f, "[spread]\nactive=%d\nno_spread=%d\n", g_spread.active,
329 (int)g_spread_no_spread);
319 fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n" 330 fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n"
320 "fov=%d\nfov_circle=%d\n" 331 "fov=%d\nfov_circle=%d\n"
321 "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n" 332 "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n"
@@ -346,7 +357,7 @@ static void config_load() {
346 char line[256]; 357 char line[256];
347 FeatureState *cur = nullptr; 358 FeatureState *cur = nullptr;
348 bool in_esp = false, in_bhop = false, in_aim = false, in_silent = false, 359 bool in_esp = false, in_bhop = false, in_aim = false, in_silent = false,
349 in_jumpbug = false; 360 in_jumpbug = false, in_spread = false;
350 while (fgets(line, sizeof(line), f)) { 361 while (fgets(line, sizeof(line), f)) {
351 line[strcspn(line, "\r\n")] = 0; 362 line[strcspn(line, "\r\n")] = 0;
352 if (line[0] == '[') { 363 if (line[0] == '[') {
@@ -358,6 +369,8 @@ static void config_load() {
358 cur = &g_jumpbug; 369 cur = &g_jumpbug;
359 else if (strstr(line, "[melee]")) 370 else if (strstr(line, "[melee]"))
360 cur = &g_melee; 371 cur = &g_melee;
372 else if (strstr(line, "[spread]"))
373 cur = &g_spread;
361 else if (strstr(line, "[esp]")) 374 else if (strstr(line, "[esp]"))
362 cur = &g_esp; 375 cur = &g_esp;
363 else if (strstr(line, "[aimbot]")) 376 else if (strstr(line, "[aimbot]"))
@@ -373,6 +386,7 @@ static void config_load() {
373 in_aim = (cur == &g_aimbot); 386 in_aim = (cur == &g_aimbot);
374 in_silent = (cur == &g_silentaim); 387 in_silent = (cur == &g_silentaim);
375 in_jumpbug = (cur == &g_jumpbug); 388 in_jumpbug = (cur == &g_jumpbug);
389 in_spread = (cur == &g_spread);
376 } else if (cur) { 390 } else if (cur) {
377 char *eq = strchr(line, '='); 391 char *eq = strchr(line, '=');
378 if (!eq) 392 if (!eq)
@@ -385,6 +399,9 @@ static void config_load() {
385 if (in_jumpbug && strcmp(key, "assist") == 0) { 399 if (in_jumpbug && strcmp(key, "assist") == 0) {
386 g_jumpbug_assist = atoi(val) != 0; 400 g_jumpbug_assist = atoi(val) != 0;
387 } 401 }
402 if (in_spread && strcmp(key, "no_spread") == 0) {
403 g_spread_no_spread = atoi(val) != 0;
404 }
388 if (in_aim) { 405 if (in_aim) {
389 if (strcmp(key, "smooth") == 0) { 406 if (strcmp(key, "smooth") == 0) {
390 int v = atoi(val); 407 int v = atoi(val);
@@ -546,10 +563,10 @@ static bool key_pressed(int sc) {
546// feature gate queried from hl2h.cpp do_move(): feature must be Active AND 563// feature gate queried from hl2h.cpp do_move(): feature must be Active AND
547// its bind key held (always-on when unbound). 564// its bind key held (always-on when unbound).
548static FeatureState *feature_state(int feature) { 565static FeatureState *feature_state(int feature) {
549 static FeatureState *feats[8] = {&g_bhop, &g_abh, &g_esp, 566 static FeatureState *feats[9] = {&g_bhop, &g_abh, &g_esp,
550 &g_aimbot, &g_triggerbot, &g_silentaim, 567 &g_aimbot, &g_triggerbot, &g_silentaim,
551 &g_jumpbug, &g_melee}; 568 &g_jumpbug, &g_melee, &g_spread};
552 if (feature < 0 || feature > 7) 569 if (feature < 0 || feature > 8)
553 return nullptr; 570 return nullptr;
554 return feats[feature]; 571 return feats[feature];
555} 572}
@@ -579,6 +596,8 @@ bool bhop_autostrafe_enabled() { return g_bhop_autostrafe; }
579 596
580bool jumpbug_assist_enabled() { return g_jumpbug_assist; } 597bool jumpbug_assist_enabled() { return g_jumpbug_assist; }
581 598
599bool spread_no_spread_enabled() { return g_spread_no_spread; }
600
582unsigned aimbot_bone_mask() { 601unsigned aimbot_bone_mask() {
583 unsigned m = 0; 602 unsigned m = 0;
584 if (g_aim_bones[AIM_BONE_HEAD]) 603 if (g_aim_bones[AIM_BONE_HEAD])