@@ -73,6 +73,7 @@ public struct DreamTunnelDashConfiguration
7373 public float CustomSpeed ;
7474 public bool AllowDashCancels ;
7575 public bool RedirectConsumesNormalDash ;
76+ public bool AllowTransitions ;
7677 }
7778
7879 public static readonly DreamTunnelDashConfiguration DefaultDreamTunnelDashConfiguration = new ( )
@@ -85,6 +86,7 @@ public struct DreamTunnelDashConfiguration
8586 CustomSpeed = 0 ,
8687 AllowDashCancels = false ,
8788 RedirectConsumesNormalDash = false ,
89+ AllowTransitions = false ,
8890 } ;
8991
9092
@@ -106,6 +108,9 @@ public static void Load()
106108 IL . Celeste . Player . IsRiding_JumpThru += Player_IsRiding_JumpThru ;
107109 IL . Celeste . Player . OnCollideH += State_DreamDashEqual ;
108110 IL . Celeste . Player . OnCollideV += State_DreamDashEqual ;
111+ IL . Celeste . Player . BeforeUpTransition += Player_BeforeUpTransition ;
112+ IL . Celeste . Player . BeforeDownTransition += Player_BeforeDownTransition ;
113+ IL . Celeste . Player . TransitionTo += Player_TransitionTo ;
109114 hook_Player_orig_Update = new ILHook (
110115 typeof ( Player ) . GetMethod ( "orig_Update" ) ,
111116 Player_orig_Update ) ;
@@ -140,6 +145,9 @@ public static void Unload()
140145 IL . Celeste . Player . IsRiding_JumpThru -= Player_IsRiding_JumpThru ;
141146 IL . Celeste . Player . OnCollideH -= State_DreamDashEqual ;
142147 IL . Celeste . Player . OnCollideV -= State_DreamDashEqual ;
148+ IL . Celeste . Player . BeforeUpTransition -= Player_BeforeUpTransition ;
149+ IL . Celeste . Player . BeforeDownTransition -= Player_BeforeDownTransition ;
150+ IL . Celeste . Player . TransitionTo -= Player_TransitionTo ;
143151 hook_Player_orig_Update . Dispose ( ) ;
144152 hook_Player_orig_UpdateSprite . Dispose ( ) ;
145153
@@ -360,33 +368,100 @@ private static void Player_IsRiding_JumpThru(ILContext il)
360368 State_DreamDashNotEqual_And ( il ) ;
361369 }
362370
371+ private static void Player_BeforeUpTransition ( ILContext il )
372+ {
373+ ILCursor cursor = new ( il ) ;
374+
375+ CheckState ( cursor , Player . StRedDash , false ) ;
376+ CheckState ( cursor , Player . StRedDash , false , true ) ;
377+ }
378+
379+ private static void Player_BeforeDownTransition ( ILContext il )
380+ {
381+ ILCursor cursor = new ( il ) ;
382+
383+ CheckState ( cursor , Player . StRedDash , false , true ) ;
384+ }
385+
386+ private static void Player_TransitionTo ( ILContext il )
387+ {
388+ ILCursor cursor = new ( il ) ;
389+
390+ if ( cursor . TryGotoNext ( MoveType . Before , instr => instr . MatchCall < Actor > ( "MoveTowardsX" ) ) )
391+ {
392+ UseInsteadIfDreamTunnelDashing ( cursor , NaiveMoveTowardsX ) ;
393+ }
394+ if ( cursor . TryGotoNext ( MoveType . Before , instr => instr . MatchCall < Actor > ( "MoveTowardsY" ) ) )
395+ {
396+ UseInsteadIfDreamTunnelDashing ( cursor , NaiveMoveTowardsY ) ;
397+ }
398+ return ;
399+
400+ // utility to replace one method call with another of the same signature if the player is dream dashing
401+ static void UseInsteadIfDreamTunnelDashing < T > ( ILCursor cursor , T cb ) where T : Delegate
402+ {
403+ ILLabel normalCall = cursor . DefineLabel ( ) ;
404+ ILLabel afterNormalCall = cursor . DefineLabel ( ) ;
405+
406+ cursor . Emit ( OpCodes . Ldarg_0 ) ;
407+ cursor . EmitDelegate < Func < Player , bool > > ( player => player . StateMachine . State == St . DreamTunnelDash ) ;
408+ cursor . Emit ( OpCodes . Brfalse_S , normalCall ) ;
409+ cursor . EmitDelegate ( cb ) ;
410+ cursor . Emit ( OpCodes . Br_S , afterNormalCall ) ;
411+ cursor . MarkLabel ( normalCall ) ;
412+ // normal method call would be here
413+ cursor . Index ++ ;
414+ cursor . MarkLabel ( afterNormalCall ) ;
415+ }
416+ }
417+
418+ // hopefully the tasers don't kill me for this
419+ private static void NaiveMoveTowardsX ( Player player , float targetX , float maxAmount , Collision _ )
420+ {
421+ float toX = Calc . Approach ( player . ExactPosition . X , targetX , maxAmount ) ;
422+ player . movementCounter . X += ( float ) ( ( double ) toX - player . Position . X - player . movementCounter . X ) ;
423+ int x = ( int ) Math . Round ( player . movementCounter . X ) ;
424+ player . Position . X += x ;
425+ player . movementCounter . X -= x ;
426+ }
427+
428+ private static void NaiveMoveTowardsY ( Player player , float targetY , float maxAmount , Collision _ )
429+ {
430+ float toY = Calc . Approach ( player . ExactPosition . Y , targetY , maxAmount ) ;
431+ player . movementCounter . Y += ( float ) ( ( double ) toY - player . Position . Y - player . movementCounter . Y ) ;
432+ int y = ( int ) Math . Round ( player . movementCounter . Y ) ;
433+ player . Position . Y += y ;
434+ player . movementCounter . Y -= y ;
435+ }
436+
363437 // Patch any method that checks the player's State
364438 /// <summary>
365439 /// Use if decompilation says <c>State==9</c> and NOT followed by <c>&&</c>.
366440 /// </summary>
367- private static readonly ILContext . Manipulator State_DreamDashEqual = il => Check_State_DreamDash ( new ILCursor ( il ) , true ) ;
441+ private static readonly ILContext . Manipulator State_DreamDashEqual = il => CheckState ( new ILCursor ( il ) , Player . StDreamDash , true ) ;
368442 /// <summary>
369443 /// Use if decompilation says <c>State!=9</c> and NOT followed by <c>&&</c>.
370444 /// </summary>
371- private static readonly ILContext . Manipulator State_DreamDashNotEqual = il => Check_State_DreamDash ( new ILCursor ( il ) , false ) ;
445+ private static readonly ILContext . Manipulator State_DreamDashNotEqual = il => CheckState ( new ILCursor ( il ) , Player . StDreamDash , false ) ;
372446 /// <summary>
373447 /// Use if decompilation says <c>State==9</c> and IS followed by <c>&&</c>.
374448 /// </summary>
375- private static readonly ILContext . Manipulator State_DreamDashEqual_And = il => Check_State_DreamDash ( new ILCursor ( il ) , true , true ) ;
449+ private static readonly ILContext . Manipulator State_DreamDashEqual_And = il => CheckState ( new ILCursor ( il ) , Player . StDreamDash , true , true ) ;
376450 /// <summary>
377451 /// Use if decompilation says <c>State!=9</c> and IS followed by <c>&&</c>.
378452 /// </summary>
379- private static readonly ILContext . Manipulator State_DreamDashNotEqual_And = il => Check_State_DreamDash ( new ILCursor ( il ) , false , true ) ;
453+ private static readonly ILContext . Manipulator State_DreamDashNotEqual_And = il => CheckState ( new ILCursor ( il ) , Player . StDreamDash , false , true ) ;
380454 /// <summary>
381455 /// Patch any method that checks the player's state.
382456 /// </summary>
383- /// <remarks>Checks for <c>ldc.i4.s 9</c></remarks>
384- /// <param name="cursor"></param>
385- /// <param name="equal">Whether the decompilation says State == 9</param>
457+ /// <remarks>Checks for <c>ldc.i4.s <state></c></remarks>
458+ /// <param name="cursor">The ILCursor to use</param>
459+ /// <param name="state">The state to check for</param>
460+ /// <param name="equal">Whether the decompilation says <c>State == <state></c></param>
386461 /// <param name="and">Whether the check is followed by <c>&&</c></param>
387- private static void Check_State_DreamDash ( ILCursor cursor , bool equal , bool and = false )
462+ private static void CheckState ( ILCursor cursor , int state , bool equal , bool and = false )
388463 {
389- if ( cursor . TryGotoNext ( instr => instr . MatchLdcI4 ( Player . StDreamDash ) &&
464+ if ( cursor . TryGotoNext ( instr => instr . MatchLdcI4 ( state ) &&
390465 instr . Previous != null && instr . Previous . MatchCallvirt < StateMachine > ( "get_State" ) ) )
391466 {
392467 Instruction idx = cursor . Next ;
@@ -424,20 +499,20 @@ private static void Check_State_DreamDash(ILCursor cursor, bool equal, bool and
424499 private static void Player_orig_Update ( ILContext il )
425500 {
426501 ILCursor cursor = new ( il ) ;
427- Check_State_DreamDash ( cursor , true ) ;
428- Check_State_DreamDash ( cursor , false , true ) ;
429- Check_State_DreamDash ( cursor , false , true ) ;
502+ CheckState ( cursor , Player . StDreamDash , true ) ;
503+ CheckState ( cursor , Player . StDreamDash , false , true ) ;
504+ CheckState ( cursor , Player . StDreamDash , false , true ) ;
430505 // Not used because we DO want to enforce Level bounds.
431506 //Check_State_DreamDash(cursor, false, true);
432507 }
433508
434- // Kill the player if they attempt to DreamTunnel out of the level
509+ // Kill the player if they attempt to DreamTunnel out of the level and transitions are not enabled
435510 private static void Level_EnforceBounds ( On . Celeste . Level . orig_EnforceBounds orig , Level self , Player player )
436511 {
437512 if ( DynamicData . For ( self ) . Get < Coroutine > ( "transition" ) is not null )
438513 return ;
439514
440- if ( player . StateMachine . State == St . DreamTunnelDash )
515+ if ( player . StateMachine . State == St . DreamTunnelDash && ! CommunalHelperModule . Session . CurrentDreamTunnelDashConfiguration . AllowTransitions )
441516 {
442517 Rectangle bounds = self . Bounds ;
443518 if ( player . Right > bounds . Right || player . Left < bounds . Left || player . Top < bounds . Top || player . Bottom > bounds . Bottom )
0 commit comments