private static bool onActorMoveHExact(On.Celeste.Actor.orig_MoveHExact orig, Actor self, int moveH, Collision onCollide, Solid pusher)
        {
            // fall back to vanilla if no sideways jumpthru is in the room.
            if (self.SceneAs <Level>().Tracker.CountEntities <SidewaysJumpThru>() == 0)
            {
                return(orig(self, moveH, onCollide, pusher));
            }

            Vector2 targetPosition    = self.Position + Vector2.UnitX * moveH;
            int     moveDirection     = Math.Sign(moveH);
            int     moveAmount        = 0;
            bool    movingLeftToRight = moveH > 0;

            while (moveH != 0)
            {
                bool didCollide = false;

                // check if colliding with a solid
                Solid solid = self.CollideFirst <Solid>(self.Position + Vector2.UnitX * moveDirection);
                if (solid != null)
                {
                    didCollide = true;
                }
                else
                {
                    // check if colliding with a sideways jumpthru
                    SidewaysJumpThru jumpThru = self.CollideFirstOutside <SidewaysJumpThru>(self.Position + Vector2.UnitX * moveDirection);
                    if (jumpThru != null && jumpThru.AllowLeftToRight != movingLeftToRight)
                    {
                        // there is a sideways jump-thru and we are moving in the opposite direction => collision
                        didCollide = true;
                    }
                }

                if (didCollide)
                {
                    Vector2 movementCounter = (Vector2)actorMovementCounter.GetValue(self);
                    movementCounter.X = 0f;
                    actorMovementCounter.SetValue(self, movementCounter);
                    onCollide?.Invoke(new CollisionData {
                        Direction      = Vector2.UnitX * moveDirection,
                        Moved          = Vector2.UnitX * moveAmount,
                        TargetPosition = targetPosition,
                        Hit            = solid,
                        Pusher         = pusher
                    });
                    return(true);
                }

                // continue moving
                moveAmount += moveDirection;
                moveH      -= moveDirection;
                self.X     += moveDirection;
            }
            return(false);
        }
        private static void modCollideChecks(ILContext il)
        {
            ILCursor cursor = new ILCursor(il);

            while (cursor.Next != null)
            {
                Instruction next = cursor.Next;

                // we want to replace all CollideChecks with solids here.
                if (next.OpCode == OpCodes.Call && (next.Operand as MethodReference)?.FullName == "System.Boolean Monocle.Entity::CollideCheck<Celeste.Solid>(Microsoft.Xna.Framework.Vector2)")
                {
                    Logger.Log("SpringCollab2020/SidewaysJumpThru", $"Patching Entity.CollideCheck to include sideways jumpthrus at {cursor.Index} in IL for {il.Method.Name}");

                    cursor.Remove();
                    cursor.EmitDelegate <Func <Entity, Vector2, bool> >((self, checkAtPosition) => {
                        // we still want to check for solids...
                        if (self.CollideCheck <Solid>(checkAtPosition))
                        {
                            return(true);
                        }

                        // if we are not checking a side, this certainly has nothing to do with jumpthrus.
                        if (self.Position.X == checkAtPosition.X)
                        {
                            return(false);
                        }

                        // our entity also collides if this is with a jumpthru and we are colliding with the solid side of it.
                        // we are in this case if the jumpthru is left to right (the "solid" side of it is the right one)
                        // and we are checking the collision on the left side of the player for example.
                        bool collideOnLeftSideOfPlayer = (self.Position.X > checkAtPosition.X);
                        SidewaysJumpThru jumpthru      = self.CollideFirstOutside <SidewaysJumpThru>(checkAtPosition);
                        return(jumpthru != null && self is Player player && (jumpthru.AllowLeftToRight == collideOnLeftSideOfPlayer) &&
                               jumpthru.Bottom >= self.Top + checkAtPosition.Y - self.Position.Y + 3);
                    });
                }

                if (next.OpCode == OpCodes.Callvirt && (next.Operand as MethodReference)?.FullName == "System.Boolean Monocle.Scene::CollideCheck<Celeste.Solid>(Microsoft.Xna.Framework.Vector2)")
                {
                    Logger.Log("SpringCollab2020/SidewaysJumpThru", $"Patching Scene.CollideCheck to include sideways jumpthrus at {cursor.Index} in IL for {il.Method.Name}");

                    cursor.Remove();
                    cursor.EmitDelegate <Func <Scene, Vector2, bool> >((self, vector) => self.CollideCheck <Solid>(vector) || self.CollideCheck <SidewaysJumpThru>(vector));
                }

                cursor.Index++;
            }
        }
        private static int onPlayerNormalUpdate(On.Celeste.Player.orig_NormalUpdate orig, Player self)
        {
            int result = orig(self);

            // kill speed if player is going towards a jumpthru.
            if (self.Speed.X != 0)
            {
                bool             movingLeftToRight = self.Speed.X > 0;
                SidewaysJumpThru jumpThru          = self.CollideFirstOutside <SidewaysJumpThru>(self.Position + Vector2.UnitX * Math.Sign(self.Speed.X));
                if (jumpThru != null && jumpThru.AllowLeftToRight != movingLeftToRight)
                {
                    self.Speed.X = 0;
                }
            }

            return(result);
        }
        private static bool onPlatformMoveHExactCollideSolids(On.Celeste.Platform.orig_MoveHExactCollideSolids orig, Platform self,
                                                              int moveH, bool thruDashBlocks, Action <Vector2, Vector2, Platform> onCollide)
        {
            // fall back to vanilla if no sideways jumpthru is in the room.
            if (self.SceneAs <Level>().Tracker.CountEntities <SidewaysJumpThru>() == 0)
            {
                return(orig(self, moveH, thruDashBlocks, onCollide));
            }

            float x                    = self.X;
            int   moveDirection        = Math.Sign(moveH);
            int   moveAmount           = 0;
            Solid solid                = null;
            bool  movingLeftToRight    = moveH > 0;
            bool  collidedWithJumpthru = false;

            while (moveH != 0)
            {
                if (thruDashBlocks)
                {
                    // check if we have dash blocks to break on our way.
                    foreach (DashBlock entity in self.Scene.Tracker.GetEntities <DashBlock>())
                    {
                        if (self.CollideCheck(entity, self.Position + Vector2.UnitX * moveDirection))
                        {
                            entity.Break(self.Center, Vector2.UnitX * moveDirection, true, true);
                            self.SceneAs <Level>().Shake(0.2f);
                            Input.Rumble(RumbleStrength.Medium, RumbleLength.Medium);
                        }
                    }
                }

                // check for collision with a solid
                solid = self.CollideFirst <Solid>(self.Position + Vector2.UnitX * moveDirection);

                // check for collision with a sideways jumpthru
                SidewaysJumpThru jumpThru = self.CollideFirstOutside <SidewaysJumpThru>(self.Position + Vector2.UnitX * moveDirection);
                if (jumpThru != null && jumpThru.AllowLeftToRight != movingLeftToRight)
                {
                    // there is a sideways jump-thru and we are moving in the opposite direction => collision
                    collidedWithJumpthru = true;
                }

                if (solid != null || collidedWithJumpthru)
                {
                    break;
                }

                // continue moving
                moveAmount += moveDirection;
                moveH      -= moveDirection;
                self.X     += moveDirection;
            }

            // actually move and call the collision callback if any
            self.X = x;
            self.MoveHExact(moveAmount);
            if (solid != null && onCollide != null)
            {
                onCollide(Vector2.UnitX * moveDirection, Vector2.UnitX * moveAmount, solid);
            }
            return(solid != null || collidedWithJumpthru);
        }