Skip to content

Commit 4471bd9

Browse files
committed
Block player from climbing past jumpthru
1 parent 3d3e67f commit 4471bd9

File tree

1 file changed

+48
-5
lines changed

1 file changed

+48
-5
lines changed

Entities/UpsideDownJumpThru.cs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,17 @@ class UpsideDownJumpThru : JumpThru {
2222

2323
public static void Load() {
2424
using (new DetourContext { Before = { "*" } }) { // these don't always call the orig methods, better apply them first.
25+
// fix general actor/platform behavior to make them comply with jumpthrus.
2526
On.Celeste.Actor.MoveVExact += onActorMoveVExact;
2627
On.Celeste.Platform.MoveVExactCollideSolids += onPlatformMoveVExactCollideSolids;
2728
}
2829

30+
// fix player specific behavior allowing them to go through upside-down jumpthrus.
2931
On.Celeste.Player.OnCollideV += onPlayerOnCollideV;
3032

33+
// block player if they try to climb past an upside-down jumpthru.
34+
IL.Celeste.Player.ClimbUpdate += patchPlayerClimbUpdate;
35+
3136
// ignore upside-down jumpthrus in select places.
3237
playerOrigUpdateHook = new ILHook(typeof(Player).GetMethod("orig_Update"), filterOutJumpThrusFromCollideChecks);
3338
IL.Celeste.Player.DashUpdate += filterOutJumpThrusFromCollideChecks;
@@ -39,6 +44,7 @@ public static void Unload() {
3944
On.Celeste.Platform.MoveVExactCollideSolids -= onPlatformMoveVExactCollideSolids;
4045

4146
On.Celeste.Player.OnCollideV -= onPlayerOnCollideV;
47+
IL.Celeste.Player.ClimbUpdate -= patchPlayerClimbUpdate;
4248

4349
if (playerOrigUpdateHook != null)
4450
playerOrigUpdateHook.Dispose();
@@ -78,7 +84,7 @@ private static bool onActorMoveVExact(On.Celeste.Actor.orig_MoveVExact orig, Act
7884
}
7985
}
8086

81-
if(didCollide) {
87+
if (didCollide) {
8288
Vector2 movementCounter = (Vector2) actorMovementCounter.GetValue(self);
8389
movementCounter.Y = 0f;
8490
if (onCollide != null) {
@@ -184,7 +190,7 @@ private static void filterOutJumpThrusFromCollideChecks(ILContext il) {
184190
case "T Monocle.Entity::CollideFirstOutside<Celeste.JumpThru>(Microsoft.Xna.Framework.Vector2)":
185191
Logger.Log("SpringCollab2020/UpsideDownJumpThru", $"Patching CollideFirstOutside at {cursor.Index} in IL for {il.Method.Name}");
186192
cursor.Index++;
187-
193+
188194
// nullify if mod jumpthru.
189195
cursor.EmitDelegate<Func<JumpThru, JumpThru>>(jumpThru => {
190196
if (jumpThru?.GetType() == typeof(UpsideDownJumpThru))
@@ -218,8 +224,8 @@ private static void filterOutJumpThrusFromCollideChecks(ILContext il) {
218224

219225
// remove all mod jumpthrus from the returned list.
220226
cursor.EmitDelegate<Func<List<Entity>, List<Entity>>>(matches => {
221-
for(int i = 0; i < matches.Count; i++) {
222-
if(matches[i].GetType() == typeof(UpsideDownJumpThru)) {
227+
for (int i = 0; i < matches.Count; i++) {
228+
if (matches[i].GetType() == typeof(UpsideDownJumpThru)) {
223229
matches.RemoveAt(i);
224230
i--;
225231
}
@@ -234,11 +240,48 @@ private static void filterOutJumpThrusFromCollideChecks(ILContext il) {
234240
}
235241
}
236242

243+
private static void patchPlayerClimbUpdate(ILContext il) {
244+
ILCursor cursor = new ILCursor(il);
245+
246+
// in decompiled code, we want to get ourselves just before the last occurrence of "if (climbNoMoveTimer <= 0f)".
247+
while (cursor.TryGotoNext(
248+
instr => instr.MatchStfld<Vector2>("Y"),
249+
instr => instr.MatchLdarg(0),
250+
instr => instr.MatchLdfld<Player>("climbNoMoveTimer"),
251+
instr => instr.MatchLdcR4(0f))) {
252+
253+
cursor.Index += 2;
254+
255+
FieldInfo f_lastClimbMove = typeof(Player).GetField("lastClimbMove", BindingFlags.NonPublic | BindingFlags.Instance);
256+
257+
Logger.Log("SpringCollab2020/UpsideDownJumpThru", $"Injecting collide check to block climbing at {cursor.Index} in IL for {il.Method.Name}");
258+
259+
cursor.Emit(OpCodes.Ldarg_0);
260+
cursor.Emit(OpCodes.Ldarg_0);
261+
cursor.Emit(OpCodes.Ldfld, f_lastClimbMove);
262+
263+
// if climbing is blocked by an upside down jumpthru, cancel the climb (lastClimbMove = 0 and Speed.Y = 0).
264+
// injecting that at that point in the method allows it to drain stamina as if the player was not climbing.
265+
cursor.EmitDelegate<Func<Player, int, int>>((self, lastClimbMove) => {
266+
if (Input.MoveY.Value == -1 && self.CollideCheck<UpsideDownJumpThru>(self.Position - Vector2.UnitY)) {
267+
self.Speed.Y = 0;
268+
return 0;
269+
}
270+
return lastClimbMove;
271+
});
272+
273+
cursor.Emit(OpCodes.Stfld, f_lastClimbMove);
274+
cursor.Emit(OpCodes.Ldarg_0);
275+
}
276+
}
277+
278+
279+
237280

238281
private int columns;
239282
private string overrideTexture;
240283

241-
public UpsideDownJumpThru(EntityData data, Vector2 offset)
284+
public UpsideDownJumpThru(EntityData data, Vector2 offset)
242285
: base(data.Position + offset, data.Width, false) {
243286

244287
columns = data.Width / 8;

0 commit comments

Comments
 (0)