@@ -62,10 +62,8 @@ public static void activateHooks() {
62
62
"orig_UpdateSprite" : "UpdateSprite" ;
63
63
64
64
// 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 ;
69
67
70
68
// block "climb hopping" on top of sideways jumpthrus, because this just looks weird.
71
69
On . Celeste . Player . ClimbHopBlockedCheck += onPlayerClimbHopBlockedCheck ;
@@ -95,9 +93,8 @@ public static void deactivateHooks() {
95
93
hooksActive = false ;
96
94
97
95
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 ;
101
98
102
99
On . Celeste . Player . ClimbHopBlockedCheck -= onPlayerClimbHopBlockedCheck ;
103
100
@@ -113,104 +110,39 @@ public static void deactivateHooks() {
113
110
On . Celeste . Player . NormalUpdate -= onPlayerNormalUpdate ;
114
111
}
115
112
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 ) ;
140
115
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 ) ) {
154
118
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 ;
162
125
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.
184
130
}
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
- }
200
131
201
- // continue moving
202
- moveAmount += moveDirection ;
203
- moveH -= moveDirection ;
204
- self . X += moveDirection ;
132
+ return null ;
133
+ } ) ;
205
134
}
135
+ }
206
136
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 ;
212
143
}
213
- return solid != null || collidedWithJumpthru ;
144
+
145
+ return false ;
214
146
}
215
147
216
148
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
225
157
private static void modCollideChecks ( ILContext il ) {
226
158
ILCursor cursor = new ILCursor ( il ) ;
227
159
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
+
228
167
while ( cursor . Next != null ) {
229
168
Instruction next = cursor . Next ;
230
169
231
170
// we want to replace all CollideChecks with solids here.
232
171
if ( next . OpCode == OpCodes . Call && ( next . Operand as MethodReference ) ? . FullName == "System.Boolean Monocle.Entity::CollideCheck<Celeste.Solid>(Microsoft.Xna.Framework.Vector2)" ) {
233
172
Logger . Log ( "SpringCollab2020/SidewaysJumpThru" , $ "Patching Entity.CollideCheck to include sideways jumpthrus at { cursor . Index } in IL for { il . Method . Name } ") ;
234
173
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 ) => {
237
178
// we still want to check for solids...
238
- if ( self . CollideCheck < Solid > ( checkAtPosition ) )
179
+ if ( orig ) {
239
180
return true ;
181
+ }
240
182
241
183
// if we are not checking a side, this certainly has nothing to do with jumpthrus.
242
184
if ( self . Position . X == checkAtPosition . X )
243
185
return false ;
244
186
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 ) ;
252
188
} ) ;
253
189
}
254
190
255
191
if ( next . OpCode == OpCodes . Callvirt && ( next . Operand as MethodReference ) ? . FullName == "System.Boolean Monocle.Scene::CollideCheck<Celeste.Solid>(Microsoft.Xna.Framework.Vector2)" ) {
256
192
Logger . Log ( "SpringCollab2020/SidewaysJumpThru" , $ "Patching Scene.CollideCheck to include sideways jumpthrus at { cursor . Index } in IL for { il . Method . Name } ") ;
257
193
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
+ } ) ;
260
202
}
261
203
262
204
cursor . Index ++ ;
263
205
}
264
206
}
265
207
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
+
266
239
private static int onPlayerNormalUpdate ( On . Celeste . Player . orig_NormalUpdate orig , Player self ) {
267
240
int result = orig ( self ) ;
268
241
0 commit comments