actions/movement.js

/**
 * Contains functions for different movement patterns to be used by AIs.
 * 
 * @memberof ClubCrawler.Actions
 * 
 * @namespace Movement
 *  
 */
import Phaser from "phaser";

/**
 * Validates a MovementConfig Object
 * 
 * @memberof ClubCrawler.Actions.Movement
 * @param {Phaser.GameObjects.GameObject} caller - The caller game object
 * @param {ClubCrawler.Types.MovementConfig} config - The movement config
 * @returns {boolean}
 */
function validatePhysicsMove(caller, config) {
    if(!caller) {
        validatePhysicsMove(this, config);
    }
    if(!config.scene) {
        if(!caller.scene) {
            console.error('No scene could be found for', caller);
            return false;
        }
        else {
            config.scene = caller.scene;
        }
    }
    if(!caller.body) {
        console.error('No physics body found in moveTowards function on', caller);
        return false;
    }
    if(!config.repeatTime) {
        if(!caller.updateSpeed) {
            console.error('No update speed could be determined in movetowards function on', caller);
            return false;
        } else {
            config.repeatTime = caller.updateSpeed;
        }
    } else {
        config.repeatTime = config.repeatTime;
    }
    if(!config.velocityIncrement) {
        if(!caller.velocityIncrement) {
            console.error('No velocity Increment could be determined for', caller);
            return false;
        } else{
            config.velocityIncrement = caller.velocityIncrement;
        }
    }
    if(!caller.moving) {
        //console.error('No "moving" boolean property could be found for', caller);
        caller.moving = true;
    }
    if(!config.moveRatio) config.moveRatio = 1;
    if(!config.lastX) config.lastX = caller.x;
    if(!config.lastY) config.lastY = caller.y;
    if(!config.failedAttempts) config.failedAttempts = 0;
    if(!config.maxFailedAttempts) config.maxFailedAttempts = 10;
    if(!config.failureDrift) config.failureDrift = 1;
    if(Phaser.Math.Difference(config.lastX, caller.x) <= config.failureDrift && Phaser.Math.Difference(config.lastY, caller.y) <= config.failureDrift) {
        if(config.maxFailedAttempts > 0) {
            config.failedAttempts += 1;
        }
    }
    if(config.failedAttempts > config.maxFailedAttempts && config.maxFailedAttempts > 0) {
        return false;
    }
    return true;
}
/**
 * retreat from player movement pattern
 * 
 * @memberof ClubCrawler.Actions.Movement
 * @param {Phaser.GameObjects.GameObject} caller - The caller game object
 * @param {ClubCrawler.Types.MovementConfig} config - The movement config
 * @returns {boolean}
 */
function retreatFromPlayer(caller, config) {
    if(!caller) {
        caller = this;
    }
    if(!validatePhysicsMove(caller, config)) {
        return false;
    }
    let player = config.scene.player;

    let hypoteneuse = Phaser.Math.Distance.Between(player.x, player.y, caller.x, caller.y);
    
    let adjacent = caller.x - player.x;
    let opposite = caller.y - player.y;
    let sine = opposite/hypoteneuse;
    let cosine = adjacent/hypoteneuse;

    let speedX = config.velocityIncrement * cosine * config.moveRatio;
    let speedY = config.velocityIncrement * sine * config.moveRatio; 

    caller.body.setVelocityX(speedX);
    caller.body.setVelocityY(speedY);

    config.scene.time.delayedCall(config.repeatTime, moveTowardsPlayer, [caller, config]);
}

/**
 * move towards player
 * 
 * @memberof ClubCrawler.Actions.Movement
 * @param {Phaser.GameObjects.GameObject} caller - The caller game object
 * @param {ClubCrawler.Types.MovementConfig} config - The movement config
 * @returns {boolean}
 */
function moveTowardsPlayer(caller, config) {
    if(!caller) {
        caller = this;
    }
    if(!validatePhysicsMove(caller, config)) {
        return false;
    }
    let player = config.scene.player;

    let hypoteneuse = Phaser.Math.Distance.Between(player.x, player.y, caller.x, caller.y);
    
    let adjacent = player.x - caller.x;
    let opposite = player.y - caller.y;
    let sine = opposite/hypoteneuse;
    let cosine = adjacent/hypoteneuse;

    let speedX = config.velocityIncrement * cosine;
    let speedY = config.velocityIncrement * sine; 

    caller.body.setVelocityX(speedX);
    caller.body.setVelocityY(speedY);

    return true;
}

/**
 * move towards player repeatedly (probably not necessary if you are calling it after a regular Sense update)
 * 
 * @memberof ClubCrawler.Actions.Movement
 * @param {Phaser.GameObjects.GameObject} caller - The caller game object
 * @param {ClubCrawler.Types.MovementConfig} config - The movement config
 * @returns {boolean}
 */
function moveTowardsPlayerRepeat(caller, config) {
    if(!caller) {
        caller = this;
    }
    if(moveTowardsPlayer(caller, config)) {
        caller.nextMoveEvent = config.scene.time.delayedCall(config.repeatTime, moveTowardsPlayerRepeat, [caller, config]);
        return true;
    } else {
        return false;
    }
}
/**
 * Moves in a random direction, but tries not to move into colliding walls
 * 
 * @memberof ClubCrawler.Actions.Movement
 * @param {Phaser.GameObjects.GameObject} caller - The caller game object
 * @param {ClubCrawler.Types.MovementConfig} config - The movement config
 * @returns {boolean}
 */
function moveRandomly(caller, config) {
    if(!validatePhysicsMove(caller, config)) {
        return false;
    } 
    let sine = (Math.random() - 0.5)*2;
    let cosine = (Math.random() - 0.5)*2;
    let speedX = config.velocityIncrement * cosine * config.moveRatio;
    let speedY = config.velocityIncrement * sine * config.moveRatio;
    let collisions = caller.body.checkCollision;
    if(collisions.up || collisions.down) {
        speedY = -speedY;
    }
    if(collisions.left || collisions.right) {
        speedX = -speedX;
    }
    caller.body.setVelocityX(speedX);
    caller.body.setVelocityY(speedY);
    return true;
}
/**
 * move randomly repeatedly (not necessary if you are trying to move after a sense update)
 * 
 * @memberof ClubCrawler.Actions.Movement
 * @param {Phaser.GameObjects.GameObject} caller - The caller game object
 * @param {ClubCrawler.Types.MovementConfig} config - The movement config
 * @returns {boolean}
 */
function moveRandomlyRepeat(caller, config) {
    if(!caller) {
        caller = this;
    }
    if(moveRandomly(caller, config)) {
        caller.nextMoveEvent = config.scene.time.delayedCall(config.repeatTime, moveRandomlyRepeat, [caller, config], this);
        return true;
    } else {
        return false;
    }
}

module.exports = {
    circle: null,
    chargePlayer: null,
    MoveTowardsPlayer: moveTowardsPlayer,
    MoveTowardsPlayerRepeat: moveTowardsPlayerRepeat,
    RetreatFromPlayer: retreatFromPlayer,
    MoveRandomly: moveRandomly,
    MoveRandomlyRepeat: moveRandomlyRepeat,
    zigZag: null
}