protected tilemapCollisions()

in libs/game/physics.ts [349:588]


    protected tilemapCollisions(movingSprite: MovingSprite, tm: tiles.TileMap) {
        const s = movingSprite.sprite;
        // if the sprite is already clipping into a wall,
        // allow free movement rather than randomly 'fixing' it
        if (s.flags & sprites.Flag.IsClipping) {
            if (!tm.isOnWall(s)) {
                s.flags &= ~sprites.Flag.IsClipping;
            }
        }
        if (!s.isStatic()) s.setHitbox();
        const hbox = s._hitbox;
        const tileScale = tm.scale;
        const tileSize = 1 << tileScale;

        const xDiff = Fx.sub(
            s._x,
            s._lastX
        );

        const yDiff = Fx.sub(
            s._y,
            s._lastY
        );

        if (!(s.flags & SPRITE_NO_WALL_COLLISION)) {
            if (xDiff !== Fx.zeroFx8) {
                const right = xDiff > Fx.zeroFx8;
                const x0 = Fx.toIntShifted(
                    Fx.add(
                        right ?
                            Fx.add(hbox.right, Fx.oneFx8)
                            :
                            Fx.sub(hbox.left, Fx.oneFx8),
                        Fx.oneHalfFx8
                    ),
                    tileScale
                );

                const collidedTiles: sprites.StaticObstacle[] = [];

                // check collisions with tiles sprite is moving towards horizontally
                for (
                    let y = Fx.sub(hbox.top, yDiff);
                    y < Fx.iadd(tileSize, Fx.sub(hbox.bottom, yDiff));
                    y = Fx.iadd(tileSize, y)
                ) {
                    const y0 = Fx.toIntShifted(
                        Fx.add(
                            Fx.min(
                                y,
                                Fx.sub(
                                    hbox.bottom,
                                    yDiff
                                )
                            ),
                            Fx.oneHalfFx8
                        ),
                        tileScale
                    );

                    if (tm.isObstacle(x0, y0)) {
                        const obstacle = tm.getObstacle(x0, y0);
                        if (!collidedTiles.some(o => o.tileIndex === obstacle.tileIndex)) {
                            collidedTiles.push(obstacle);
                        }
                    }
                }

                if (collidedTiles.length) {
                    const collisionDirection = right ? CollisionDirection.Right : CollisionDirection.Left;
                    s._x = Fx.sub(
                        right ?
                            Fx.sub(
                                Fx8(x0 << tileScale),
                                hbox.width
                            )
                            :
                            Fx8((x0 + 1) << tileScale),
                        hbox.ox
                    );

                    for (const tile of collidedTiles) {
                        if(!(s.flags & SPRITE_NO_WALL_COLLISION)) {
                            s.registerObstacle(collisionDirection, tile, tm);
                        }
                    }

                    if (s.flags & sprites.Flag.DestroyOnWall) {
                        s.destroy();
                    } else if (s._vx === movingSprite.cachedVx) {
                        // sprite collision event didn't change velocity in this direction;
                        // apply normal updates
                        if (s.flags & sprites.Flag.BounceOnWall) {
                            if ((!right && s.vx < 0) || (right && s.vx > 0)) {
                                s._vx = Fx.neg(s._vx);
                                movingSprite.xStep = Fx.neg(movingSprite.xStep);
                                movingSprite.dx = Fx.neg(movingSprite.dx);
                            }
                        } else {
                            movingSprite.dx = Fx.zeroFx8;
                            s._vx = Fx.zeroFx8;
                        }
                    } else if (Math.sign(Fx.toInt(s._vx)) === Math.sign(Fx.toInt(movingSprite.cachedVx))) {
                        // sprite collision event changed velocity,
                        // but still facing same direction; prevent further movement this update.
                        movingSprite.dx = Fx.zeroFx8;
                    }
                }
            }

            if (yDiff !== Fx.zeroFx8) {
                const down = yDiff > Fx.zeroFx8;
                const y0 = Fx.toIntShifted(
                    Fx.add(
                        down ?
                            Fx.add(hbox.bottom, Fx.oneFx8)
                            :
                            Fx.sub(hbox.top, Fx.oneFx8),
                        Fx.oneHalfFx8
                    ),
                    tileScale
                );
                const collidedTiles: sprites.StaticObstacle[] = [];

                // check collisions with tiles sprite is moving towards vertically
                for (
                    let x = hbox.left;
                    x < Fx.iadd(tileSize, hbox.right);
                    x = Fx.iadd(tileSize, x)
                ) {
                    const x0 = Fx.toIntShifted(
                        Fx.add(
                            Fx.min(
                                x,
                                hbox.right
                            ),
                            Fx.oneHalfFx8
                        ),
                        tileScale
                    );

                    if (tm.isObstacle(x0, y0)) {
                        const obstacle = tm.getObstacle(x0, y0);
                        if (!collidedTiles.some(o => o.tileIndex === obstacle.tileIndex)) {
                            collidedTiles.push(obstacle);
                        }
                    }
                }

                if (collidedTiles.length) {
                    const collisionDirection = down ? CollisionDirection.Bottom : CollisionDirection.Top;
                    s._y = Fx.sub(
                        down ?
                            Fx.sub(
                                Fx8(y0 << tileScale),
                                hbox.height
                            )
                            :
                            Fx8((y0 + 1) << tileScale),
                        hbox.oy
                    );

                    for (const tile of collidedTiles) {
                        if(!(s.flags & SPRITE_NO_WALL_COLLISION)) {
                            s.registerObstacle(collisionDirection, tile, tm);
                        }
                    }

                    if (s.flags & sprites.Flag.DestroyOnWall) {
                        s.destroy();
                    } else if (s._vy === movingSprite.cachedVy) {
                        // sprite collision event didn't change velocity in this direction;
                        // apply normal updates
                        if (s.flags & sprites.Flag.BounceOnWall) {
                            if ((!down && s.vy < 0) || (down && s.vy > 0)) {
                                s._vy = Fx.neg(s._vy);
                                movingSprite.yStep = Fx.neg(movingSprite.yStep);
                                movingSprite.dy = Fx.neg(movingSprite.dy);
                            }
                        } else {
                            movingSprite.dy = Fx.zeroFx8;
                            s._vy = Fx.zeroFx8;
                        }
                    } else if (Math.sign(Fx.toInt(s._vy)) === Math.sign(Fx.toInt(movingSprite.cachedVy))) {
                        // sprite collision event changed velocity,
                        // but still facing same direction; prevent further movement this update.
                        movingSprite.dy = Fx.zeroFx8;
                    }
                }
            }
        }


        if (!(s.flags & SPRITE_NO_TILE_OVERLAPS)) {
            // Now that we've moved, check all of the tiles underneath the current position
            // for overlaps
            const overlappedTiles: tiles.Location[] = [];
            for (
                let x = hbox.left;
                x < Fx.iadd(tileSize, hbox.right);
                x = Fx.iadd(tileSize, x)
            ) {
                const x0 = Fx.toIntShifted(
                    Fx.add(
                        Fx.min(
                            x,
                            hbox.right
                        ),
                        Fx.oneHalfFx8
                    ),
                    tileScale
                );
                for (
                    let y = hbox.top;
                    y < Fx.iadd(tileSize, hbox.bottom);
                    y = Fx.iadd(tileSize, y)
                ) {
                    const y0 = Fx.toIntShifted(
                        Fx.add(
                            Fx.min(
                                y,
                                hbox.bottom
                            ),
                            Fx.oneHalfFx8
                        ),
                        tileScale
                    );

                    // if the sprite can move through walls, it can overlap the underlying tile.
                    if (!tm.isObstacle(x0, y0) || !!(s.flags & sprites.Flag.GhostThroughWalls)) {
                        overlappedTiles.push(tm.getTile(x0, y0));
                    }
                }
            }

            if (overlappedTiles.length) {
                this.tilemapOverlaps(s, overlappedTiles);
            }
        }
    }