diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-27 20:17:07 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-27 20:17:07 +0100 |
| commit | 59432708a4ae926e4be2959dfbb2b4eb80be3fd6 (patch) | |
| tree | 3233f49d2c9b3affbb97d832b4afdccbfb5f43b6 | |
| parent | Fix tooltip truncation; add Stucklaunch assist (diff) | |
| download | hl2h-59432708a4ae926e4be2959dfbb2b4eb80be3fd6.tar.gz hl2h-59432708a4ae926e4be2959dfbb2b4eb80be3fd6.zip | |
Stucklaunch: alternate attack/use and expose spam rate
- Gravity-gun spam now alternates +attack (grab) and +use (drop) instead of
pressing both together, which would punt the prop.
- Add a Spam rate slider (1..16 ticks, default 2) so the pickup/drop cycle can
be tuned; it persists as spam_ticks in [stucklaunch].
- Log the prop index the first few times a stucklaunch engages to aid testing.
| -rw-r--r-- | CONTEXT.md | 6 | ||||
| -rw-r--r-- | src/gui.cpp | 4 | ||||
| -rw-r--r-- | src/hl2h.cpp | 34 | ||||
| -rw-r--r-- | src/hl2h.h | 2 | ||||
| -rw-r--r-- | src/menu.cpp | 14 |
5 files changed, 44 insertions, 16 deletions
| @@ -267,9 +267,9 @@ vocabulary / decisions. | |||
| 267 | ## Stucklaunch assist | 267 | ## Stucklaunch assist |
| 268 | 268 | ||
| 269 | - Optional Movement feature; Active is off by default. | 269 | - Optional Movement feature; Active is off by default. |
| 270 | - While engaged it holds jump, alternates +use every 2 ticks to regrab a | 270 | - While engaged it holds jump, alternates +use (or +attack/+use as a |
| 271 | physics prop, optionally alternates +attack while the gravity gun is | 271 | pickup/drop cycle while the gravity gun is active) every N ticks (default |
| 272 | active, and air-strafes the hops. | 272 | 2), and air-strafes the hops. |
| 273 | - Engagement comes from `m_hGroundEntity`, latched for ~0.5 s so the prop | 273 | - Engagement comes from `m_hGroundEntity`, latched for ~0.5 s so the prop |
| 274 | stays "active" during each hop. If that netvar is unavailable it runs | 274 | stays "active" during each hop. If that netvar is unavailable it runs |
| 275 | whenever the feature is active, so bind it to a key. | 275 | whenever the feature is active, so bind it to a key. |
diff --git a/src/gui.cpp b/src/gui.cpp index dc0e4eb..fc95d1f 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -301,10 +301,12 @@ static void build_cards() { | |||
| 301 | "Rapidly press +use to keep regrabbing the prop.", | 301 | "Rapidly press +use to keep regrabbing the prop.", |
| 302 | &g_stucklaunch_use_spam); | 302 | &g_stucklaunch_use_spam); |
| 303 | add_toggle(sl, "Gravity gun spam", | 303 | add_toggle(sl, "Gravity gun spam", |
| 304 | "Rapidly press +attack while holding the gravity gun.", | 304 | "Alternate +attack and +use while holding the gravity gun.", |
| 305 | &g_stucklaunch_gravspam); | 305 | &g_stucklaunch_gravspam); |
| 306 | add_toggle(sl, "Auto-strafe", "Air-strafe each hop.", | 306 | add_toggle(sl, "Auto-strafe", "Air-strafe each hop.", |
| 307 | &g_stucklaunch_autostrafe); | 307 | &g_stucklaunch_autostrafe); |
| 308 | add_slider(sl, "Spam rate", "Ticks between spam toggles; higher is slower.", | ||
| 309 | 1, 16, &g_stucklaunch_spam_ticks); | ||
| 308 | 310 | ||
| 309 | int jb = add_card(0, &g_jumpbug, "Jumpbug", | 311 | int jb = add_card(0, &g_jumpbug, "Jumpbug", |
| 310 | "Duck+jump when landing would hurt.", true); | 312 | "Duck+jump when landing would hurt.", true); |
diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 63fdcdc..84de815 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp | |||
| @@ -178,8 +178,14 @@ static bool stucklaunch_prop_ok(void *ent, int tick) { | |||
| 178 | uint32_t h = *(uint32_t *)((uintptr_t)ent + g_net.groundentity); | 178 | uint32_t h = *(uint32_t *)((uintptr_t)ent + g_net.groundentity); |
| 179 | int gi = (h == 0xFFFFFFFFu) ? 0 : (int)(h & 0xFFFF); | 179 | int gi = (h == 0xFFFFFFFFu) ? 0 : (int)(h & 0xFFFF); |
| 180 | bool on = gi > 0 && gi <= 4096 && entity_is_physics_prop(gi); | 180 | bool on = gi > 0 && gi <= 4096 && entity_is_physics_prop(gi); |
| 181 | if (on) | 181 | if (on) { |
| 182 | static int prop_logs = 0; | ||
| 183 | if (prop_logs < 4) { | ||
| 184 | prop_logs++; | ||
| 185 | logf("stucklaunch: prop idx=%d", gi); | ||
| 186 | } | ||
| 182 | last_prop_tick = tick; | 187 | last_prop_tick = tick; |
| 188 | } | ||
| 183 | return tick - last_prop_tick < 32; // ~0.5 s at 66 tick | 189 | return tick - last_prop_tick < 32; // ~0.5 s at 66 tick |
| 184 | } | 190 | } |
| 185 | 191 | ||
| @@ -189,20 +195,28 @@ static bool stucklaunch_prop_ok(void *ent, int tick) { | |||
| 189 | static void stucklaunch_apply(CUserCmd *cmd, void *local_ent, bool onground, | 195 | static void stucklaunch_apply(CUserCmd *cmd, void *local_ent, bool onground, |
| 190 | const float *vel) { | 196 | const float *vel) { |
| 191 | cmd->buttons |= IN_JUMP; | 197 | cmd->buttons |= IN_JUMP; |
| 192 | int phase = (cmd->tick_count / 2) & 1; | 198 | int period = stucklaunch_spam_ticks(); |
| 193 | if (stucklaunch_use_spam_enabled()) { | 199 | if (period < 1) |
| 200 | period = 1; | ||
| 201 | int phase = (cmd->tick_count / period) & 1; | ||
| 202 | bool gravity = stucklaunch_gravspam_enabled() && | ||
| 203 | active_weapon_is_gravity_gun(local_ent); | ||
| 204 | if (gravity) { | ||
| 205 | // Pick up with +attack, drop with +use on the next half-cycle. Pressing | ||
| 206 | // both together would punt instead of regrabbing. | ||
| 207 | if (phase == 0) { | ||
| 208 | cmd->buttons |= IN_ATTACK; | ||
| 209 | cmd->buttons &= ~IN_USE; | ||
| 210 | } else { | ||
| 211 | cmd->buttons |= IN_USE; | ||
| 212 | cmd->buttons &= ~IN_ATTACK; | ||
| 213 | } | ||
| 214 | } else if (stucklaunch_use_spam_enabled()) { | ||
| 194 | if (phase == 0) | 215 | if (phase == 0) |
| 195 | cmd->buttons |= IN_USE; | 216 | cmd->buttons |= IN_USE; |
| 196 | else | 217 | else |
| 197 | cmd->buttons &= ~IN_USE; | 218 | cmd->buttons &= ~IN_USE; |
| 198 | } | 219 | } |
| 199 | if (stucklaunch_gravspam_enabled() && | ||
| 200 | active_weapon_is_gravity_gun(local_ent)) { | ||
| 201 | if (phase == 0) | ||
| 202 | cmd->buttons |= IN_ATTACK; | ||
| 203 | else | ||
| 204 | cmd->buttons &= ~IN_ATTACK; | ||
| 205 | } | ||
| 206 | if (stucklaunch_autostrafe_enabled() && !onground) | 220 | if (stucklaunch_autostrafe_enabled() && !onground) |
| 207 | apply_autostrafe(cmd, vel); | 221 | apply_autostrafe(cmd, vel); |
| 208 | } | 222 | } |
| @@ -213,6 +213,7 @@ extern bool g_spread_no_spread; | |||
| 213 | extern bool g_stucklaunch_use_spam; | 213 | extern bool g_stucklaunch_use_spam; |
| 214 | extern bool g_stucklaunch_gravspam; | 214 | extern bool g_stucklaunch_gravspam; |
| 215 | extern bool g_stucklaunch_autostrafe; | 215 | extern bool g_stucklaunch_autostrafe; |
| 216 | extern int g_stucklaunch_spam_ticks; | ||
| 216 | extern bool g_aim_bones[AIM_BONE_COUNT]; | 217 | extern bool g_aim_bones[AIM_BONE_COUNT]; |
| 217 | extern int g_aim_smooth; | 218 | extern int g_aim_smooth; |
| 218 | extern bool g_aim_vischeck; | 219 | extern bool g_aim_vischeck; |
| @@ -254,6 +255,7 @@ bool spread_no_spread_enabled(); | |||
| 254 | bool stucklaunch_use_spam_enabled(); | 255 | bool stucklaunch_use_spam_enabled(); |
| 255 | bool stucklaunch_gravspam_enabled(); | 256 | bool stucklaunch_gravspam_enabled(); |
| 256 | bool stucklaunch_autostrafe_enabled(); | 257 | bool stucklaunch_autostrafe_enabled(); |
| 258 | int stucklaunch_spam_ticks(); | ||
| 257 | bool active_weapon_is_gravity_gun(void *local_ent); | 259 | bool active_weapon_is_gravity_gun(void *local_ent); |
| 258 | bool entity_is_physics_prop(int idx); | 260 | bool entity_is_physics_prop(int idx); |
| 259 | unsigned aimbot_bone_mask(); | 261 | unsigned aimbot_bone_mask(); |
diff --git a/src/menu.cpp b/src/menu.cpp index 00ba9be..0b8e20b 100644 --- a/src/menu.cpp +++ b/src/menu.cpp | |||
| @@ -48,6 +48,7 @@ FeatureState g_stucklaunch{false, 0}; // opt-in movement assist | |||
| 48 | bool g_stucklaunch_use_spam = true; | 48 | bool g_stucklaunch_use_spam = true; |
| 49 | bool g_stucklaunch_gravspam = false; | 49 | bool g_stucklaunch_gravspam = false; |
| 50 | bool g_stucklaunch_autostrafe = true; | 50 | bool g_stucklaunch_autostrafe = true; |
| 51 | int g_stucklaunch_spam_ticks = 2; | ||
| 51 | 52 | ||
| 52 | // Aimbot bone checklist. Head defaults on (v1 behaviour). | 53 | // Aimbot bone checklist. Head defaults on (v1 behaviour). |
| 53 | bool g_aim_bones[AIM_BONE_COUNT] = {true, false, false, | 54 | bool g_aim_bones[AIM_BONE_COUNT] = {true, false, false, |
| @@ -163,10 +164,10 @@ void config_save() { | |||
| 163 | g_bhop.bind, g_bhop_autostrafe); | 164 | g_bhop.bind, g_bhop_autostrafe); |
| 164 | fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind); | 165 | fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind); |
| 165 | fprintf(f, "[stucklaunch]\nactive=%d\nbind=%d\nuse_spam=%d\n" | 166 | fprintf(f, "[stucklaunch]\nactive=%d\nbind=%d\nuse_spam=%d\n" |
| 166 | "gravspam=%d\nautostrafe=%d\n", | 167 | "gravspam=%d\nautostrafe=%d\nspam_ticks=%d\n", |
| 167 | g_stucklaunch.active, g_stucklaunch.bind, | 168 | g_stucklaunch.active, g_stucklaunch.bind, |
| 168 | (int)g_stucklaunch_use_spam, (int)g_stucklaunch_gravspam, | 169 | (int)g_stucklaunch_use_spam, (int)g_stucklaunch_gravspam, |
| 169 | (int)g_stucklaunch_autostrafe); | 170 | (int)g_stucklaunch_autostrafe, g_stucklaunch_spam_ticks); |
| 170 | fprintf(f, "[jumpbug]\nactive=%d\nbind=%d\nassist=%d\n", | 171 | fprintf(f, "[jumpbug]\nactive=%d\nbind=%d\nassist=%d\n", |
| 171 | g_jumpbug.active, g_jumpbug.bind, (int)g_jumpbug_assist); | 172 | g_jumpbug.active, g_jumpbug.bind, (int)g_jumpbug_assist); |
| 172 | fprintf(f, "[melee]\nactive=%d\nbind=%d\n", g_melee.active, g_melee.bind); | 173 | fprintf(f, "[melee]\nactive=%d\nbind=%d\n", g_melee.active, g_melee.bind); |
| @@ -268,6 +269,12 @@ static bool config_load() { | |||
| 268 | g_stucklaunch_gravspam = atoi(val) != 0; | 269 | g_stucklaunch_gravspam = atoi(val) != 0; |
| 269 | else if (strcmp(key, "autostrafe") == 0) | 270 | else if (strcmp(key, "autostrafe") == 0) |
| 270 | g_stucklaunch_autostrafe = atoi(val) != 0; | 271 | g_stucklaunch_autostrafe = atoi(val) != 0; |
| 272 | else if (strcmp(key, "spam_ticks") == 0) { | ||
| 273 | int v = atoi(val); | ||
| 274 | if (v < 1) v = 1; | ||
| 275 | if (v > 16) v = 16; | ||
| 276 | g_stucklaunch_spam_ticks = v; | ||
| 277 | } | ||
| 271 | } | 278 | } |
| 272 | if (in_aim) { | 279 | if (in_aim) { |
| 273 | if (strcmp(key, "smooth") == 0) { | 280 | if (strcmp(key, "smooth") == 0) { |
| @@ -351,6 +358,7 @@ void config_reset_defaults() { | |||
| 351 | g_stucklaunch_use_spam = true; | 358 | g_stucklaunch_use_spam = true; |
| 352 | g_stucklaunch_gravspam = false; | 359 | g_stucklaunch_gravspam = false; |
| 353 | g_stucklaunch_autostrafe = true; | 360 | g_stucklaunch_autostrafe = true; |
| 361 | g_stucklaunch_spam_ticks = 2; | ||
| 354 | g_jumpbug = {true, 0}; | 362 | g_jumpbug = {true, 0}; |
| 355 | g_jumpbug_assist = false; | 363 | g_jumpbug_assist = false; |
| 356 | g_melee = {true, 0}; | 364 | g_melee = {true, 0}; |
| @@ -497,6 +505,8 @@ bool stucklaunch_gravspam_enabled() { return g_stucklaunch_gravspam; } | |||
| 497 | 505 | ||
| 498 | bool stucklaunch_autostrafe_enabled() { return g_stucklaunch_autostrafe; } | 506 | bool stucklaunch_autostrafe_enabled() { return g_stucklaunch_autostrafe; } |
| 499 | 507 | ||
| 508 | int stucklaunch_spam_ticks() { return g_stucklaunch_spam_ticks; } | ||
| 509 | |||
| 500 | unsigned aimbot_bone_mask() { | 510 | unsigned aimbot_bone_mask() { |
| 501 | unsigned m = 0; | 511 | unsigned m = 0; |
| 502 | if (g_aim_bones[AIM_BONE_HEAD]) | 512 | if (g_aim_bones[AIM_BONE_HEAD]) |
