@@ -44,6 +44,12 @@ interface StrafingConfig {
4444 options : StrafingOptions ;
4545}
4646
47+ interface ClickState {
48+ nextPressTime : number ;
49+ releaseTime : number ;
50+ isPressed : boolean ;
51+ }
52+
4753// =============================================================================
4854// Main Script Class
4955// =============================================================================
@@ -84,7 +90,11 @@ class StrafingScript {
8490 // UI/Timing
8591 private statusMessage = '' ;
8692 private statusExpiry = 0 ;
87- private lastClickTime : { left : number ; right : number } = { left : 0 , right : 0 } ;
93+ private clickStates : Record < 'left' | 'right' , ClickState > = {
94+ left : { nextPressTime : 0 , releaseTime : 0 , isPressed : false } ,
95+ right : { nextPressTime : 0 , releaseTime : 0 , isPressed : false } ,
96+ } ;
97+ private currentSelection : any = null ;
8898
8999 // Key definitions
90100 private readonly keys = {
@@ -192,6 +202,16 @@ class StrafingScript {
192202 JsMacros . off ( this . tickListener ) ;
193203 this . tickListener = null ;
194204 }
205+
206+ // Clear visuals
207+ try {
208+ if ( this . currentSelection ) {
209+ const primary = BaritoneAPI . getProvider ( ) . getPrimaryBaritone ( ) ;
210+ primary . getSelectionManager ( ) . removeSelection ( this . currentSelection ) ;
211+ this . currentSelection = null ;
212+ }
213+ } catch ( e ) { }
214+
195215 this . startTime = 0 ;
196216 this . totalDistance = 0 ;
197217 this . showStatus ( '§cStrafing stopped.' ) ;
@@ -218,6 +238,12 @@ class StrafingScript {
218238 this . totalDistance = 0 ;
219239 this . lastPos = null ;
220240
241+ // Reset click states
242+ this . clickStates = {
243+ left : { nextPressTime : 0 , releaseTime : 0 , isPressed : false } ,
244+ right : { nextPressTime : 0 , releaseTime : 0 , isPressed : false } ,
245+ } ;
246+
221247 this . updateVisuals ( ) ;
222248
223249 if ( ! this . tickListener ) {
@@ -232,13 +258,19 @@ class StrafingScript {
232258 private updateVisuals ( ) {
233259 try {
234260 const primary = BaritoneAPI . getProvider ( ) . getPrimaryBaritone ( ) ;
261+
262+ if ( this . currentSelection ) {
263+ primary . getSelectionManager ( ) . removeSelection ( this . currentSelection ) ;
264+ this . currentSelection = null ;
265+ }
266+
235267 const { '1' : p1 , '2' : p2 } = this . config . position ;
236268
237269 if ( ! isNaN ( p1 . x ) && ! isNaN ( p2 . x ) ) {
238270 // BetterBlockPos constructor expects ints
239271 const bp1 = new ( BetterBlockPos as any ) ( Math . floor ( p1 . x ) , Math . floor ( p1 . y ) , Math . floor ( p1 . z ) ) ;
240272 const bp2 = new ( BetterBlockPos as any ) ( Math . floor ( p2 . x ) , Math . floor ( p2 . y ) , Math . floor ( p2 . z ) ) ;
241- primary . getSelectionManager ( ) . addSelection ( bp1 , bp2 ) ;
273+ this . currentSelection = primary . getSelectionManager ( ) . addSelection ( bp1 , bp2 ) ;
242274 }
243275 } catch ( e ) {
244276 Chat . log ( `§cVisuals Error: ${ e } ` ) ;
@@ -309,10 +341,11 @@ class StrafingScript {
309341 }
310342
311343 // 4. Flight Recovery
312- if ( ! player . getAbilities ( ) . getFlying ( ) ) {
344+ if ( ! player . getAbilities ( ) . getFlying ( ) && ! this . isStarting ) {
313345 this . isStarting = true ;
314346 this . startTickCounter = 0 ;
315347 this . cleanupKeys ( ) ;
348+ Chat . log ( '§7Enabling flight...' ) ;
316349 }
317350
318351 if ( this . isStarting ) {
@@ -341,21 +374,27 @@ class StrafingScript {
341374 this . showStatus ( '§aStrafing started!' ) ;
342375 return ;
343376 }
344- Chat . log ( '§7Enabling flight...' ) ;
345377 }
346378
347379 const tick = this . startTickCounter ++ ;
348380
349- // Flight activation sequence (Jump spam)
350- if ( tick === 0 || tick === 4 ) this . keys . jump . set ( true ) ;
351- if ( tick === 2 || tick === 25 ) this . keys . jump . set ( false ) ;
352-
353- if ( tick > 25 ) {
381+ // Flight activation sequence (Fast Double Jump)
382+ // Tick 0: Press
383+ // Tick 2: Release
384+ // Tick 4: Press (Toggle flight)
385+ // Tick 6: Release
386+
387+ if ( tick === 0 ) this . keys . jump . set ( true ) ;
388+ if ( tick === 2 ) this . keys . jump . set ( false ) ;
389+ if ( tick === 4 ) this . keys . jump . set ( true ) ;
390+ if ( tick === 6 ) this . keys . jump . set ( false ) ;
391+
392+ if ( tick > 6 ) {
354393 if ( player . getAbilities ( ) . getFlying ( ) ) {
355394 this . isStarting = false ;
356395 this . showStatus ( '§aStrafing started!' ) ;
357- } else if ( tick > 60 ) {
358- this . startTickCounter = 0 ; // Retry
396+ } else if ( tick > 20 ) { // Retry after 1 second
397+ this . startTickCounter = 0 ;
359398 }
360399 }
361400 }
@@ -447,18 +486,39 @@ class StrafingScript {
447486
448487 if ( interact . mode === 'click' ) {
449488 const cps = interact . cps || 10 ;
450- const delayMs = 1000 / cps ;
451-
452- // Use generic object indexing to store last click time safely
453- const lastClick = ( this . lastClickTime as any ) [ refDir ] || 0 ;
454-
455- if ( Date . now ( ) - lastClick >= delayMs ) {
456- // Perform a quick click
457- if ( activeKey . pressed ) activeKey . release ( ) ;
458- else {
459- activeKey . hold ( ) ;
460- ( this . lastClickTime as any ) [ refDir ] = Date . now ( ) ;
461- }
489+ const interval = 1000 / cps ;
490+ const state = this . clickStates [ refDir ] ;
491+ const now = Date . now ( ) ;
492+
493+ // Phase 1: Release if time has passed
494+ if ( state . isPressed && now >= state . releaseTime ) {
495+ activeKey . set ( false ) ;
496+ state . isPressed = false ;
497+ // Return early to ensure at least one tick of 'release' state
498+ // before pressing again. This prevents "phantom releases" where
499+ // the key is released and re-pressed in the same tick.
500+ return ;
501+ }
502+
503+ // Phase 2: Press if time has passed and not currently pressing
504+ if ( ! state . isPressed && now >= state . nextPressTime ) {
505+ activeKey . set ( true ) ;
506+ state . isPressed = true ;
507+
508+ // Dynamic hold time based on CPS to ensure reliability
509+ // Aim for ~60-70% duty cycle, but ensure at least 60ms if possible (for reliable server tick detection)
510+ // and ensure we leave at least 25ms gap for the release logic.
511+
512+ const maxPossibleHold = Math . max ( 0 , interval - 40 ) ; // Leave ~40ms gap
513+ const targetHold = Math . min ( maxPossibleHold , Math . max ( 60 , interval * 0.6 ) ) ;
514+ const randomness = Math . min ( 30 , maxPossibleHold - targetHold ) ; // Add jitter if room exists
515+
516+ const holdTime = targetHold + Math . random ( ) * randomness ;
517+
518+ state . releaseTime = now + holdTime ;
519+
520+ // Schedule next press
521+ state . nextPressTime = Math . max ( now , state . nextPressTime ) + interval ;
462522 }
463523 } else if ( interact . mode === 'hold' ) {
464524 activeKey . set ( true ) ;
0 commit comments