Skip to content

Commit ae01d78

Browse files
committed
Port MaxHelpingHand fixes to minimize mod conflicts involving jumpthrus
1 parent 56c4abd commit ae01d78

File tree

2 files changed

+180
-220
lines changed

2 files changed

+180
-220
lines changed

Entities/SidewaysJumpThru.cs

Lines changed: 82 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)