diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-27 15:22:19 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-27 15:22:19 +0100 |
| commit | 27c9ad766e1b1333922b4c14cbde9d9b77c8dab0 (patch) | |
| tree | 7c758c0d431a389cc3826495b4c6d20ca28c6530 | |
| parent | Melee killbot: 360-degree crowbar silent aim (diff) | |
| download | hl2h-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.md | 30 | ||||
| -rw-r--r-- | src/esp.cpp | 136 | ||||
| -rw-r--r-- | src/hl2h.cpp | 3 | ||||
| -rw-r--r-- | src/hl2h.h | 7 | ||||
| -rw-r--r-- | src/menu.cpp | 41 |
5 files changed, 202 insertions, 15 deletions
| @@ -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 | |||
| 1056 | static bool g_spread_ready = false; | ||
| 1057 | static int g_spread_seed1 = 0; | ||
| 1058 | static int g_spread_seed8 = 0; // first pellet exact, 7 spread | ||
| 1059 | static int g_spread_seed12 = 0; // all 12 spread | ||
| 1060 | |||
| 1061 | static 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 | |||
| 1133 | static 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 | |||
| 1150 | bool 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 | |||
| 1045 | bool triggerbot_apply(CUserCmd *cmd) { | 1181 | bool 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 | // --------------------------------------------------------------------------- |
| @@ -129,7 +129,7 @@ struct CUserCmd { | |||
| 129 | // engine constants | 129 | // engine constants |
| 130 | // --------------------------------------------------------------------------- | 130 | // --------------------------------------------------------------------------- |
| 131 | 131 | ||
| 132 | enum { IN_ATTACK = 1 << 0, IN_JUMP = 1 << 1, IN_DUCK = 1 << 2, IN_USE = 1 << 5 }; | 132 | enum { IN_ATTACK = 1 << 0, IN_JUMP = 1 << 1, IN_DUCK = 1 << 2, IN_USE = 1 << 5, IN_ATTACK2 = 1 << 11 }; |
| 133 | enum { FL_ONGROUND = 1 << 0, FL_DUCKING = 1 << 1, FL_INWATER = 1 << 9 }; | 133 | enum { FL_ONGROUND = 1 << 0, FL_DUCKING = 1 << 1, FL_INWATER = 1 << 9 }; |
| 134 | enum { MOVETYPE_WALK = 2, MOVETYPE_NOCLIP = 8, MOVETYPE_LADDER = 9 }; | 134 | enum { MOVETYPE_WALK = 2, MOVETYPE_NOCLIP = 8, MOVETYPE_LADDER = 9 }; |
| 135 | 135 | ||
| @@ -150,6 +150,7 @@ bool triggerbot_apply(CUserCmd *cmd); | |||
| 150 | bool silent_aim_apply(CUserCmd *cmd); | 150 | bool silent_aim_apply(CUserCmd *cmd); |
| 151 | bool melee_weapon_active(void *local_ent); | 151 | bool melee_weapon_active(void *local_ent); |
| 152 | bool melee_aimbot_apply(CUserCmd *cmd); | 152 | bool melee_aimbot_apply(CUserCmd *cmd); |
| 153 | bool spread_control_apply(CUserCmd *cmd, void *local_ent); | ||
| 153 | enum FeatureId { | 154 | enum 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 | }; |
| 163 | enum EspOption { | 165 | enum EspOption { |
| 164 | ESP_OPT_SHOW_NEUTRAL = 0, | 166 | ESP_OPT_SHOW_NEUTRAL = 0, |
| @@ -193,6 +195,7 @@ bool feature_bind_held(int feature); | |||
| 193 | bool esp_option(int opt); | 195 | bool esp_option(int opt); |
| 194 | bool bhop_autostrafe_enabled(); | 196 | bool bhop_autostrafe_enabled(); |
| 195 | bool jumpbug_assist_enabled(); | 197 | bool jumpbug_assist_enabled(); |
| 198 | bool spread_no_spread_enabled(); | ||
| 196 | unsigned aimbot_bone_mask(); | 199 | unsigned aimbot_bone_mask(); |
| 197 | int aimbot_smooth_step(); | 200 | int aimbot_smooth_step(); |
| 198 | bool aimbot_vischeck_enabled(); | 201 | bool 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}; | |||
| 65 | static FeatureState g_jumpbug{true, 0}; | 65 | static FeatureState g_jumpbug{true, 0}; |
| 66 | static bool g_jumpbug_assist = false; | 66 | static bool g_jumpbug_assist = false; |
| 67 | static FeatureState g_melee{true, 0}; | 67 | static FeatureState g_melee{true, 0}; |
| 68 | static FeatureState g_spread{true, 0}; | ||
| 69 | static bool g_spread_no_spread = true; | ||
| 68 | static FeatureState g_esp{true, 0}; | 70 | static FeatureState g_esp{true, 0}; |
| 69 | static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu | 71 | static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu |
| 70 | static FeatureState g_triggerbot{true, 603}; // default Mouse4 | 72 | static FeatureState g_triggerbot{true, 603}; // default Mouse4 |
| @@ -104,6 +106,9 @@ static MenuNode g_jumpbug_active{NODE_TOGGLE, "Active", false, 0, {}, &g_jumpbug | |||
| 104 | static MenuNode g_jumpbug_assist_node{NODE_TOGGLE, "Assist timing", false, 0, {}, &g_jumpbug_assist, nullptr, nullptr}; | 106 | static MenuNode g_jumpbug_assist_node{NODE_TOGGLE, "Assist timing", false, 0, {}, &g_jumpbug_assist, nullptr, nullptr}; |
| 105 | static MenuNode g_melee_feat{NODE_FEATURE, "Melee killbot", false, 1, {}, nullptr, nullptr, nullptr}; | 107 | static MenuNode g_melee_feat{NODE_FEATURE, "Melee killbot", false, 1, {}, nullptr, nullptr, nullptr}; |
| 106 | static MenuNode g_melee_active{NODE_TOGGLE, "Active", false, 0, {}, &g_melee.active, nullptr, nullptr}; | 108 | static MenuNode g_melee_active{NODE_TOGGLE, "Active", false, 0, {}, &g_melee.active, nullptr, nullptr}; |
| 109 | static MenuNode g_spread_feat{NODE_FEATURE, "Spread control", false, 1, {}, nullptr, nullptr, nullptr}; | ||
| 110 | static MenuNode g_spread_active{NODE_TOGGLE, "Active", false, 0, {}, &g_spread.active, nullptr, nullptr}; | ||
| 111 | static MenuNode g_spread_no_spread_node{NODE_TOGGLE, "No spread", false, 0, {}, &g_spread_no_spread, nullptr, nullptr}; | ||
| 107 | static MenuNode g_esp_feat{NODE_FEATURE, "ESP", false, 1, {}, nullptr, nullptr, nullptr}; | 112 | static MenuNode g_esp_feat{NODE_FEATURE, "ESP", false, 1, {}, nullptr, nullptr, nullptr}; |
| 108 | static MenuNode g_esp_active{NODE_TOGGLE, "Active", false, 0, {}, &g_esp.active, nullptr, nullptr}; | 113 | static MenuNode g_esp_active{NODE_TOGGLE, "Active", false, 0, {}, &g_esp.active, nullptr, nullptr}; |
| 109 | static MenuNode g_esp_opt_nodes[ESP_OPT_COUNT] = { | 114 | static 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 | ||
| 197 | static int g_depth[48]; // depth of each visible row (for indentation) | 206 | static 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). |
| 548 | static FeatureState *feature_state(int feature) { | 565 | static 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 | ||
| 580 | bool jumpbug_assist_enabled() { return g_jumpbug_assist; } | 597 | bool jumpbug_assist_enabled() { return g_jumpbug_assist; } |
| 581 | 598 | ||
| 599 | bool spread_no_spread_enabled() { return g_spread_no_spread; } | ||
| 600 | |||
| 582 | unsigned aimbot_bone_mask() { | 601 | unsigned 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]) |
