In my processing video game, I use a sprite sheet for the player animations. However I need to 'flip' the sprites from their right actions to display a left action as I dont have left actions within the sprite sheet to save space. Right now the method I use: scale(1,-1) flips the player correctly, however it doesnt keep them in place i.e their position on screen isnt what it seems because the matrix function doesnt translate the player before flippin meaning that when 'flipped' they do it around the player which is incorrect, please fix it:

class Player {
    float x, y;
    int health, maxHealth = 3;
    int facingDirection; // 0: down, 1: up, 2: left, 3: right
    float speed = 3;
    boolean[] keys = new boolean[4]; // 0: up, 1: down, 2: left, 3: right
    float invulnerabilityTimer = 0, invulnerabilityDuration = 1;

    // Animation-related variables
    PImage spriteSheet;
    PImage[][] animations;
    int currentFrame = 0;
    float animationTimer = 0;
    float animationSpeed = 0.1; // Time between frames
    int currentAnimation = 0; // 0: walking down, 1: walking up, 2: walking side, 3: dying, 4: attack down, 5: attack up, 6: attack side
    boolean isMoving = false;
    boolean isAttacking = false;
    boolean isDying = false;

    static final int SPRITE_WIDTH = 48;
    static final int SPRITE_HEIGHT = 48;
    static final int WALK_DOWN = 0;
    static final int WALK_UP = 1;
    static final int WALK_SIDE = 2;
    static final int DYING = 3;
    static final int ATTACK_DOWN = 4;
    static final int ATTACK_UP = 5;
    static final int ATTACK_SIDE = 6;

    static final int FRAME_COUNT = 4; // Number of frames per animation

    Player(float x, float y, PImage spriteSheet) {
        this.x = x;
        this.y = y;
        this.facingDirection = 0;
        this.health = maxHealth;
        this.spriteSheet = spriteSheet;
        loadAnimations();
    }

    void loadAnimations() {
        animations = new PImage[7][FRAME_COUNT]; // 7 rows, 4 frames per animation
        for (int row = 0; row < 7; row++) {
            for (int col = 0; col < FRAME_COUNT; col++) {
                animations[row][col] = spriteSheet.get(col * SPRITE_WIDTH, row * SPRITE_HEIGHT, SPRITE_WIDTH, SPRITE_HEIGHT);
            }
        }
    }

    void setMove(int k, boolean b) {
        switch(k) {
            case 87: keys[0] = b; break; // W
            case 83: keys[1] = b; break; // S
            case 65: keys[2] = b; break; // A
            case 68: keys[3] = b; break; // D
            case SHIFT: 
            isRunning = b;
            speed = isRunning ? 3 : 1;  
            break; //run
        }
        isMoving = keys[0] || keys[1] || keys[2] || keys[3];
    }
    
    void update() {
        float newX = x, newY = y;
        
        if (keys[0]) { // Up
            newY -= speed;
            facingDirection = 1;
        }
        if (keys[1]) { // Down
            newY += speed;
            facingDirection = 0;
        }
        if (keys[2]) { // Left
            newX -= speed;
            facingDirection = 2;
        }
        if (keys[3]) { // Right
            newX += speed;
            facingDirection = 3;
        }
        
        if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
            int tileX = floor(newX / TILE_SIZE);
            int tileY = floor(newY / TILE_SIZE);
            if (map[tileX][tileY].type == TileType.EMPTY) {
                x = newX;
                y = newY;
            }
        }
        
        // Update animation
        if (animationTimer >= animationSpeed) {
            animationTimer -= animationSpeed;
            currentFrame = (currentFrame + 1) % FRAME_COUNT;
        } else {
            animationTimer += 1.0 / frameRate;
        }
        
        if (invulnerabilityTimer > 0) {
            invulnerabilityTimer -= 1.0 / frameRate;
        }

        updateAnimationState();
    }

    void updateAnimationState() {
        if (isDying) {
            currentAnimation = DYING;
        } else if (isAttacking) {
            switch (facingDirection) {
                case 0: currentAnimation = ATTACK_DOWN; break;
                case 1: currentAnimation = ATTACK_UP; break;
                case 2:
                case 3: currentAnimation = ATTACK_SIDE; break;
            }
        } else if (isMoving) {
            switch (facingDirection) {
                case 0: currentAnimation = WALK_DOWN; break;
                case 1: currentAnimation = WALK_UP; break;
                case 2:
                case 3: currentAnimation = WALK_SIDE; break;
            }
        } else {
            // If not moving, use the first frame of the corresponding walking animation
            currentFrame = 0;
        }
    }

    void display() {
        pushMatrix();
        translate(x, y);

        if (invulnerabilityTimer > 0 && (int)(invulnerabilityTimer * 10) % 2 == 0) {
            tint(255, 128); // Semi-transparent
        }

        if (facingDirection == 2) { // Facing left
            scale(-1, 1); // Flip horizontally
            translate(-SPRITE_WIDTH, 0); // Align the flipped image
        }

        image(animations[currentAnimation][currentFrame], -SPRITE_WIDTH/2, -SPRITE_HEIGHT/2);

        if (mining) {
            image(pickaxeSprite, 0, -TILE_SIZE / 2, TILE_SIZE, TILE_SIZE);
        } else {
            image(gunSprite, 0, -TILE_SIZE / 2, TILE_SIZE, TILE_SIZE);
        }
        
        popMatrix();
        noTint();
    }

    boolean canTakeDamage() {
        return invulnerabilityTimer <= 0;
    }

    void takeDamage() {
        if (canTakeDamage()) {
            health--;
            invulnerabilityTimer = invulnerabilityDuration;
            if (health <= 0) {
                isDying = true;
            }
        }
    }
    
    void shoot() {
        isAttacking = true;
        float angle = atan2(mouseY - y, mouseX - x);
        bullets.add(new Bullet(x, y, angle));
        isAttacking = false;
    }

    void stopAttacking() {
        isAttacking = false;
    }
}