@@ -62,10 +62,8 @@ public static void activateHooks() {
6262 "orig_UpdateSprite" : "UpdateSprite" ;
6363
6464 // implement the basic collision between actors/platforms and sideways jumpthrus.
65- using ( new DetourContext { Before = { "*" } } ) { // these don't always call the orig methods, better apply them first.
66- On . Celeste . Actor . MoveHExact += onActorMoveHExact ;
67- On . Celeste . Platform . MoveHExactCollideSolids += onPlatformMoveHExactCollideSolids ;
68- }
65+ IL . Celeste . Actor . MoveHExact += addSidewaysJumpthrusInHorizontalMoveMethods ;
66+ IL . Celeste . Platform . MoveHExactCollideSolids += addSidewaysJumpthrusInHorizontalMoveMethods ;
6967
7068 // block "climb hopping" on top of sideways jumpthrus, because this just looks weird.
7169 On . Celeste . Player . ClimbHopBlockedCheck += onPlayerClimbHopBlockedCheck ;
@@ -95,9 +93,8 @@ public static void deactivateHooks() {
9593 hooksActive = false ;
9694
9795 Logger . Log ( LogLevel . Info , "SpringCollab2020/SidewaysJumpThru" , "=== Deactivating sideways jumpthru hooks" ) ;
98-
99- On . Celeste . Actor . MoveHExact -= onActorMoveHExact ;
100- On . Celeste . Platform . MoveHExactCollideSolids -= onPlatformMoveHExactCollideSolids ;
96+ IL . Celeste . Actor . MoveHExact -= addSidewaysJumpthrusInHorizontalMoveMethods ;
97+ IL . Celeste . Platform . MoveHExactCollideSolids -= addSidewaysJumpthrusInHorizontalMoveMethods ;
10198
10299 On . Celeste . Player . ClimbHopBlockedCheck -= onPlayerClimbHopBlockedCheck ;
103100
@@ -113,104 +110,39 @@ public static void deactivateHooks() {
113110 On . Celeste . Player . NormalUpdate -= onPlayerNormalUpdate ;
114111 }
115112
116- private static bool onActorMoveHExact ( On . Celeste . Actor . orig_MoveHExact orig , Actor self , int moveH , Collision onCollide , Solid pusher ) {
117- // fall back to vanilla if no sideways jumpthru is in the room.
118- if ( self . SceneAs < Level > ( ) . Tracker . CountEntities < SidewaysJumpThru > ( ) == 0 )
119- return orig ( self , moveH , onCollide , pusher ) ;
120-
121- Vector2 targetPosition = self . Position + Vector2 . UnitX * moveH ;
122- int moveDirection = Math . Sign ( moveH ) ;
123- int moveAmount = 0 ;
124- bool movingLeftToRight = moveH > 0 ;
125- while ( moveH != 0 ) {
126- bool didCollide = false ;
127-
128- // check if colliding with a solid
129- Solid solid = self . CollideFirst < Solid > ( self . Position + Vector2 . UnitX * moveDirection ) ;
130- if ( solid != null ) {
131- didCollide = true ;
132- } else {
133- // check if colliding with a sideways jumpthru
134- SidewaysJumpThru jumpThru = self . CollideFirstOutside < SidewaysJumpThru > ( self . Position + Vector2 . UnitX * moveDirection ) ;
135- if ( jumpThru != null && jumpThru . AllowLeftToRight != movingLeftToRight ) {
136- // there is a sideways jump-thru and we are moving in the opposite direction => collision
137- didCollide = true ;
138- }
139- }
113+ private static void addSidewaysJumpthrusInHorizontalMoveMethods ( ILContext il ) {
114+ ILCursor cursor = new ILCursor ( il ) ;
140115
141- if ( didCollide ) {
142- Vector2 movementCounter = ( Vector2 ) actorMovementCounter . GetValue ( self ) ;
143- movementCounter . X = 0f ;
144- actorMovementCounter . SetValue ( self , movementCounter ) ;
145- onCollide ? . Invoke ( new CollisionData {
146- Direction = Vector2 . UnitX * moveDirection ,
147- Moved = Vector2 . UnitX * moveAmount ,
148- TargetPosition = targetPosition ,
149- Hit = solid ,
150- Pusher = pusher
151- } ) ;
152- return true ;
153- }
116+ if ( cursor . TryGotoNext ( MoveType . After , instr => instr . MatchCall < Entity > ( "CollideFirst" ) )
117+ && cursor . TryGotoNext ( instr => instr . OpCode == OpCodes . Brfalse_S || instr . OpCode == OpCodes . Brtrue_S ) ) {
154118
155- // continue moving
156- moveAmount += moveDirection ;
157- moveH -= moveDirection ;
158- self . X += moveDirection ;
159- }
160- return false ;
161- }
119+ Logger . Log ( "SpringCollab2020/SidewaysJumpThru" , $ "Injecting sideways jumpthru check at { cursor . Index } in IL for { il . Method . Name } ") ;
120+ cursor . Emit ( OpCodes . Ldarg_0 ) ;
121+ cursor . Emit ( OpCodes . Ldarg_1 ) ;
122+ cursor . EmitDelegate < Func < Solid , Entity , int , Solid > > ( ( orig , self , moveH ) => {
123+ if ( orig != null )
124+ return orig ;
162125
163- private static bool onPlatformMoveHExactCollideSolids ( On . Celeste . Platform . orig_MoveHExactCollideSolids orig , Platform self ,
164- int moveH , bool thruDashBlocks , Action < Vector2 , Vector2 , Platform > onCollide ) {
165- // fall back to vanilla if no sideways jumpthru is in the room.
166- if ( self . SceneAs < Level > ( ) . Tracker . CountEntities < SidewaysJumpThru > ( ) == 0 )
167- return orig ( self , moveH , thruDashBlocks , onCollide ) ;
168-
169- float x = self . X ;
170- int moveDirection = Math . Sign ( moveH ) ;
171- int moveAmount = 0 ;
172- Solid solid = null ;
173- bool movingLeftToRight = moveH > 0 ;
174- bool collidedWithJumpthru = false ;
175- while ( moveH != 0 ) {
176- if ( thruDashBlocks ) {
177- // check if we have dash blocks to break on our way.
178- foreach ( DashBlock entity in self . Scene . Tracker . GetEntities < DashBlock > ( ) ) {
179- if ( self . CollideCheck ( entity , self . Position + Vector2 . UnitX * moveDirection ) ) {
180- entity . Break ( self . Center , Vector2 . UnitX * moveDirection , true , true ) ;
181- self . SceneAs < Level > ( ) . Shake ( 0.2f ) ;
182- Input . Rumble ( RumbleStrength . Medium , RumbleLength . Medium ) ;
183- }
126+ int moveDirection = Math . Sign ( moveH ) ;
127+ bool movingLeftToRight = moveH > 0 ;
128+ if ( checkCollisionWithSidewaysJumpthruWhileMoving ( self , moveDirection , movingLeftToRight ) ) {
129+ return new Solid ( Vector2 . Zero , 0 , 0 , false ) ; // what matters is that it is non null.
184130 }
185- }
186-
187- // check for collision with a solid
188- solid = self . CollideFirst < Solid > ( self . Position + Vector2 . UnitX * moveDirection ) ;
189-
190- // check for collision with a sideways jumpthru
191- SidewaysJumpThru jumpThru = self . CollideFirstOutside < SidewaysJumpThru > ( self . Position + Vector2 . UnitX * moveDirection ) ;
192- if ( jumpThru != null && jumpThru . AllowLeftToRight != movingLeftToRight ) {
193- // there is a sideways jump-thru and we are moving in the opposite direction => collision
194- collidedWithJumpthru = true ;
195- }
196-
197- if ( solid != null || collidedWithJumpthru ) {
198- break ;
199- }
200131
201- // continue moving
202- moveAmount += moveDirection ;
203- moveH -= moveDirection ;
204- self . X += moveDirection ;
132+ return null ;
133+ } ) ;
205134 }
135+ }
206136
207- // actually move and call the collision callback if any
208- self . X = x ;
209- self . MoveHExact ( moveAmount ) ;
210- if ( solid != null && onCollide != null ) {
211- onCollide ( Vector2 . UnitX * moveDirection , Vector2 . UnitX * moveAmount , solid ) ;
137+ private static bool checkCollisionWithSidewaysJumpthruWhileMoving ( Entity self , int moveDirection , bool movingLeftToRight ) {
138+ // check if colliding with a sideways jumpthru
139+ SidewaysJumpThru jumpThru = self . CollideFirstOutside < SidewaysJumpThru > ( self . Position + Vector2 . UnitX * moveDirection ) ;
140+ if ( jumpThru != null && jumpThru . AllowLeftToRight != movingLeftToRight ) {
141+ // there is a sideways jump-thru and we are moving in the opposite direction => collision
142+ return true ;
212143 }
213- return solid != null || collidedWithJumpthru ;
144+
145+ return false ;
214146 }
215147
216148 private static bool onPlayerClimbHopBlockedCheck ( On . Celeste . Player . orig_ClimbHopBlockedCheck orig , Player self ) {
@@ -225,44 +157,85 @@ private static bool onPlayerClimbHopBlockedCheck(On.Celeste.Player.orig_ClimbHop
225157 private static void modCollideChecks ( ILContext il ) {
226158 ILCursor cursor = new ILCursor ( il ) ;
227159
160+ // create a Vector2 temporary variable
161+ VariableDefinition checkAtPositionStore = new VariableDefinition ( il . Import ( typeof ( Vector2 ) ) ) ;
162+ il . Body . Variables . Add ( checkAtPositionStore ) ;
163+
164+ bool isClimb = il . Method . Name . Contains ( "Climb" ) ;
165+ bool isWallJump = il . Method . Name . Contains ( "WallJump" ) || il . Method . Name . Contains ( "NormalUpdate" ) ;
166+
228167 while ( cursor . Next != null ) {
229168 Instruction next = cursor . Next ;
230169
231170 // we want to replace all CollideChecks with solids here.
232171 if ( next . OpCode == OpCodes . Call && ( next . Operand as MethodReference ) ? . FullName == "System.Boolean Monocle.Entity::CollideCheck<Celeste.Solid>(Microsoft.Xna.Framework.Vector2)" ) {
233172 Logger . Log ( "SpringCollab2020/SidewaysJumpThru" , $ "Patching Entity.CollideCheck to include sideways jumpthrus at { cursor . Index } in IL for { il . Method . Name } ") ;
234173
235- cursor . Remove ( ) ;
236- cursor . EmitDelegate < Func < Entity , Vector2 , bool > > ( ( self , checkAtPosition ) => {
174+ callOrigMethodKeepingEverythingOnStack ( cursor , checkAtPositionStore , isSceneCollideCheck : false ) ;
175+
176+ // mod the result
177+ cursor . EmitDelegate < Func < bool , Entity , Vector2 , bool > > ( ( orig , self , checkAtPosition ) => {
237178 // we still want to check for solids...
238- if ( self . CollideCheck < Solid > ( checkAtPosition ) )
179+ if ( orig ) {
239180 return true ;
181+ }
240182
241183 // if we are not checking a side, this certainly has nothing to do with jumpthrus.
242184 if ( self . Position . X == checkAtPosition . X )
243185 return false ;
244186
245- // our entity also collides if this is with a jumpthru and we are colliding with the solid side of it.
246- // we are in this case if the jumpthru is left to right (the "solid" side of it is the right one)
247- // and we are checking the collision on the left side of the player for example.
248- bool collideOnLeftSideOfPlayer = ( self . Position . X > checkAtPosition . X ) ;
249- SidewaysJumpThru jumpthru = self . CollideFirstOutside < SidewaysJumpThru > ( checkAtPosition ) ;
250- return jumpthru != null && self is Player player && ( jumpthru . AllowLeftToRight == collideOnLeftSideOfPlayer )
251- && jumpthru . Bottom >= self . Top + checkAtPosition . Y - self . Position . Y + 3 ;
187+ return entityCollideCheckWithSidewaysJumpthrus ( self , checkAtPosition , isClimb , isWallJump ) ;
252188 } ) ;
253189 }
254190
255191 if ( next . OpCode == OpCodes . Callvirt && ( next . Operand as MethodReference ) ? . FullName == "System.Boolean Monocle.Scene::CollideCheck<Celeste.Solid>(Microsoft.Xna.Framework.Vector2)" ) {
256192 Logger . Log ( "SpringCollab2020/SidewaysJumpThru" , $ "Patching Scene.CollideCheck to include sideways jumpthrus at { cursor . Index } in IL for { il . Method . Name } ") ;
257193
258- cursor . Remove ( ) ;
259- cursor . EmitDelegate < Func < Scene , Vector2 , bool > > ( ( self , vector ) => self . CollideCheck < Solid > ( vector ) || self . CollideCheck < SidewaysJumpThru > ( vector ) ) ;
194+ callOrigMethodKeepingEverythingOnStack ( cursor , checkAtPositionStore , isSceneCollideCheck : true ) ;
195+
196+ cursor . EmitDelegate < Func < bool , Scene , Vector2 , bool > > ( ( orig , self , vector ) => {
197+ if ( orig ) {
198+ return true ;
199+ }
200+ return sceneCollideCheckWithSidewaysJumpthrus ( self , vector , isClimb , isWallJump ) ;
201+ } ) ;
260202 }
261203
262204 cursor . Index ++ ;
263205 }
264206 }
265207
208+ private static void callOrigMethodKeepingEverythingOnStack ( ILCursor cursor , VariableDefinition checkAtPositionStore , bool isSceneCollideCheck ) {
209+ // store the position in the local variable
210+ cursor . Emit ( OpCodes . Stloc , checkAtPositionStore ) ;
211+ cursor . Emit ( OpCodes . Ldloc , checkAtPositionStore ) ;
212+
213+ // let vanilla call CollideCheck
214+ cursor . Index ++ ;
215+
216+ // reload the parameters
217+ cursor . Emit ( OpCodes . Ldarg_0 ) ;
218+ if ( isSceneCollideCheck ) {
219+ cursor . Emit ( OpCodes . Call , typeof ( Entity ) . GetProperty ( "Scene" ) . GetGetMethod ( ) ) ;
220+ }
221+
222+ cursor . Emit ( OpCodes . Ldloc , checkAtPositionStore ) ;
223+ }
224+
225+ private static bool entityCollideCheckWithSidewaysJumpthrus ( Entity self , Vector2 checkAtPosition , bool isClimb , bool isWallJump ) {
226+ // our entity collides if this is with a jumpthru and we are colliding with the solid side of it.
227+ // we are in this case if the jumpthru is left to right (the "solid" side of it is the right one)
228+ // and we are checking the collision on the left side of the player for example.
229+ bool collideOnLeftSideOfPlayer = ( self . Position . X > checkAtPosition . X ) ;
230+ SidewaysJumpThru jumpthru = self . CollideFirstOutside < SidewaysJumpThru > ( checkAtPosition ) ;
231+ return jumpthru != null && self is Player && jumpthru . AllowLeftToRight == collideOnLeftSideOfPlayer
232+ && jumpthru . Bottom >= self . Top + checkAtPosition . Y - self . Position . Y + 3 ;
233+ }
234+
235+ private static bool sceneCollideCheckWithSidewaysJumpthrus ( Scene self , Vector2 vector , bool isClimb , bool isWallJump ) {
236+ return self . CollideCheck < SidewaysJumpThru > ( vector ) ;
237+ }
238+
266239 private static int onPlayerNormalUpdate ( On . Celeste . Player . orig_NormalUpdate orig , Player self ) {
267240 int result = orig ( self ) ;
268241
0 commit comments