Source: movers.js

var BHell = (function (my) {

    /**
     * Mover base class. Handles complex movements for other objects (mostly enemies).
     * @constructor
     * @memberOf BHell
     */
    var BHell_Mover_Base = my.BHell_Mover_Base = function () {
        this.initialize.apply(this, arguments);
    };

    BHell_Mover_Base.prototype = Object.create(Object.prototype);
    BHell_Mover_Base.prototype.constructor = BHell_Mover_Base;

    /**
     * Mover_Base constructor.
     */
    BHell_Mover_Base.prototype.initialize = function () {
    };

    /**
     * Determines the new coordinates of the owner. The base class simply moves downwards.
     * @param oldX Old x coordinate.
     * @param oldY Old y coordinate.
     * @param speed Movement speed (in pixels per frame).
     * @returns {Array} New coordinates ret[0]: x axis, ret[1]: y axis.
     */
    BHell_Mover_Base.prototype.move = function (oldX, oldY, speed) {
        var ret = [];
        ret.push(oldX);
        ret.push(oldY + speed);
        return ret;
    };

    /**
     * Chase movement class. Constantly follows the player.
     * @constructor
     * @memberOf BHell
     * @extends BHell.BHell_Mover_Base
     */
    var BHell_Mover_Chase = my.BHell_Mover_Chase = function () {
        this.initialize.apply(this, arguments);
    };

    BHell_Mover_Chase.prototype = Object.create(BHell_Mover_Base.prototype);
    BHell_Mover_Chase.prototype.constructor = BHell_Mover_Chase;

    BHell_Mover_Chase.prototype.initialize = function () {
        BHell_Mover_Base.prototype.initialize.call(this);
    };

    /**
     * Chases the player at the given speed.
     * @param oldX
     * @param oldY
     * @param speed
     * @returns {Array}
     */
    BHell_Mover_Chase.prototype.move = function (oldX, oldY, speed) {
        var ret = [];

        if (my.player.justSpawned === false) {
            var dx = my.player.x - oldX;
            var dy = my.player.y - oldY;

            var angle = Math.atan2(dy, dx);

            ret.push(oldX + Math.cos(angle) * speed);
            ret.push(oldY + Math.sin(angle) * speed);
        }
        else {
            ret.push(oldX);
            ret.push(oldY);
        }

        return ret;
    };

    /**
     * Point movement class. Points in the player's general direction, then proceeds straight (even if the player moves away).
     * @constructor
     * @memberOf BHell
     * @extends BHell.BHell_Mover_Base
     */
    var BHell_Mover_Point = my.BHell_Mover_Point = function () {
        this.initialize.apply(this, arguments);
    };

    BHell_Mover_Point.prototype = Object.create(BHell_Mover_Base.prototype);
    BHell_Mover_Point.prototype.constructor = BHell_Mover_Point;

    /**
     * Initializes the movement's angle.
     * @param x Initial x coordinate.
     * @param y Initial y coordinate.
     */
    BHell_Mover_Point.prototype.initialize = function (x, y) {
        BHell_Mover_Base.prototype.initialize.call(this);
        var dx = my.player.x - x;
        var dy = my.player.y - y;
        this.angle = Math.atan2(dy, dx);
    };

    /**
     * Moves in a straight line, with the angle determined during initialisation.
     * @param oldX
     * @param oldY
     * @param speed
     * @returns {Array}
     */
    BHell_Mover_Point.prototype.move = function (oldX, oldY, speed) {
        var ret = [];

        ret.push(oldX + Math.cos(this.angle) * speed);
        ret.push(oldY + Math.sin(this.angle) * speed);
        return ret;
    };

    /**
     * Harmonic movement class. Moves in a straight line until a starting position is reached, then follows an harmonic
     * trajectory in the form:
     *
     * x(t) = A * cos(phi * t + alpha);
     *
     * y(t) = B * cos(theta * t + beta);
     *
     * @constructor
     * @memberOf BHell
     * @extends BHell.BHell_Mover_Base
     */
    var BHell_Mover_Harmonic = my.BHell_Mover_Harmonic = function () {
        this.initialize.apply(this, arguments);
    };

    BHell_Mover_Harmonic.prototype = Object.create(BHell_Mover_Base.prototype);
    BHell_Mover_Harmonic.prototype.constructor = BHell_Mover_Harmonic;

    /**
     * Constructor
     * @param x X coordinate of the movement's center.
     * @param y Y coordinate of the movement's center.
     * @param a Amplitude of the horizontal axis.
     * @param b Amplitude of the vertical axis.
     * @param phi Angular speed of the horizontal axis.
     * @param theta Angular speed of the vertical axis.
     * @param alpha Phase of the horizontal axis.
     * @param beta Phase of the vertical axis.
     */
    BHell_Mover_Harmonic.prototype.initialize = function (x, y, a, b, phi, theta, alpha, beta) {
        BHell_Mover_Base.prototype.initialize.call(this);

        this.inPosition = false;
        this.offX = x;
        this.offY = y;
        this.a = a;
        this.b = b;
        this.phi = phi;
        this.theta = theta;
        this.alpha = alpha;
        this.beta = beta;
        this.t = 0;
    };

    /**
     * Moves in a straight line until the starting position is reached, then follows the harmonic function defined.
     * @param oldX Old x coordinate.
     * @param oldY Old y coordinate.
     * @param speed Movement speed. In pixels per frame during the linear trajectory, in degrees per frame afterwards.
     * @returns {Array}
     */
    BHell_Mover_Harmonic.prototype.move = function (oldX, oldY, speed) {
        var ret = [];

        if (this.inPosition) {
            ret.push(oldX + this.a * Math.cos(this.alpha + this.t * this.phi));
            ret.push(oldY + this.b * Math.sin(this.beta + this.t * this.theta));
            this.t += speed * Math.PI / 360;
        }
        else {
            var dx = (this.a * Math.cos(this.alpha) + this.offX) - oldX;
            var dy = (this.b * Math.sin(this.beta) + this.offY) - oldY;
            if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) { // If the error is less than two pixels
                this.inPosition = true;
                ret.push(this.offX);
                ret.push(this.offY);
            }
            else {
                var angle = Math.atan2(dy, dx);
                ret.push(oldX + Math.cos(angle) * speed);
                ret.push(oldY + Math.sin(angle) * speed);
            }
        }
        return ret;
    };

    /**
     * Orbit movement class. Moves towards the player up to a given distance, then starts orbiting around it.
     * @constructor
     * @memberOf BHell
     * @extends BHell.BHell_Mover_Base
     */
    var BHell_Mover_Orbit = my.BHell_Mover_Orbit = function () {
        this.initialize.apply(this, arguments);
    };
    BHell_Mover_Orbit.prototype = Object.create(BHell_Mover_Base.prototype);
    BHell_Mover_Orbit.prototype.constructor = BHell_Mover_Orbit;

    /**
     * Constructor.
     * @param radius Orbit distance from the player.
     * @param counterclockwise If true orbits in the counterclockwise direction.
     */
    BHell_Mover_Orbit.prototype.initialize = function (radius, counterclockwise) {
        BHell_Mover_Base.prototype.initialize.call(this);

        this.inPosition = false;
        this.radius = radius;
        this.counterclockwise = counterclockwise;
        this.t = 3 * Math.PI / 2;
    };

    /**
     * If the player is not ready yet (e.g. it's just been resurrected) remains still, otherwise chases the player until the set
     * radius is reached, then starts orbiting.
     * @param oldX Old x coordinate.
     * @param oldY Old y coordinate.
     * @param speed Movement speed (pixels per frame during the chase phase, degrees per frame during the orbiting phase).
     * @returns {Array}
     */
    BHell_Mover_Orbit.prototype.move = function (oldX, oldY, speed) {
        var ret = [];

        if (my.player.justSpawned) {
            this.inPosition = false;
            this.t = 3 * Math.PI / 2;
            ret.push(oldX);
            ret.push(oldY);
        }
        else {
            if (this.inPosition) {
                ret.push(my.player.x + this.radius * Math.cos(this.t));
                ret.push(my.player.y + this.radius * Math.sin(this.t));

                if (this.counterclockwise) {
                    this.t -= speed * Math.PI / 360;
                }
                else {
                    this.t += speed * Math.PI / 360;
                }
                if (this.t > 2 * Math.PI) {
                    this.t -= 2 * Math.PI;
                }
            }
            else {
                var dx = my.player.x - oldX;
                var dy = my.player.y - oldY - this.radius;
                if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) { // If the error is less than two pixels
                    this.inPosition = true;
                    ret.push(dx + oldX);
                    ret.push(dy + oldY);
                }
                else {
                    var angle = Math.atan2(dy, dx);
                    ret.push(oldX + Math.cos(angle) * speed);
                    ret.push(oldY + Math.sin(angle) * speed);
                }
            }
        }

        return ret;
    };

    /**
     * Uniform Catmull-Rom spline movement class. Given four points, determines a smooth path which passes between the two innermost ones.
     *
     * @constructor
     * @memberOf BHell
     * @extends BHell.BHell_Mover_Base
     */
    var BHell_Mover_Spline = my.BHell_Mover_Spline = function () {
        this.initialize.apply(this, arguments);
    };
    BHell_Mover_Spline.prototype = Object.create(BHell_Mover_Base.prototype);
    BHell_Mover_Spline.prototype.constructor = BHell_Mover_Spline;

    /**
     * Sets up the four control points. Since the spline is uniform, no further parameters are needed.
     *
     * If P0 == P1 and P2 == P3, the trajectory is a straight line between P1 and P2, otherwise a curved path (passing
     * between P1 and P2) is determined by the relative position of P0 from P1 and P3 from P2 (e.g. if P0 is above-right of
     * P1 and P3 is below-left of P2, the overall trajectory will be an "S").
     * @param p0x X coordinate of the first point.
     * @param p0y Y coordinate of the first point.
     * @param p1x X coordinate of the second point. The movement starts from here.
     * @param p1y Y coordinate of the second point. The movement starts from here.
     * @param p2x X coordinate of the third point. The movement ends here.
     * @param p2y Y coordinate of the third point. The movement ends here.
     * @param p3x X coordinate of the last point.
     * @param p3y Y coordinate of the last point.
     */
    BHell_Mover_Spline.prototype.initialize = function (p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
        BHell_Mover_Base.prototype.initialize.call(this);
        this.t = 1;
        this.p0x = p0x;
        this.p0y = p0y;
        this.p1x = p1x;
        this.p1y = p1y;
        this.p2x = p2x;
        this.p2y = p2y;
        this.p3x = p3x;
        this.p3y = p3y;
    };

    /**
     * Moves along the initialised spline, unless P2 is already reached.
     * @param oldX Old x coordinate.
     * @param oldY Old y coordinate.
     * @param speed Movement speed (in thousandths of distance between P1 and P2 per frame).
     * @returns {Array}
     */
    BHell_Mover_Spline.prototype.move = function (oldX, oldY, speed) {
        var ret = [];

        if (this.t >= 2) {
            ret.push(oldX);
            ret.push(oldY);
        } else {
            var a1x = (1 - this.t) * this.p0x + this.t * this.p1x;
            var a2x = (2 - this.t) * this.p1x + (this.t - 1) * this.p2x;
            var a3x = (3 - this.t) * this.p2x + (this.t - 2) * this.p3x;
            var b1x = (2 - this.t) * a1x / 2 + this.t * a2x / 2;
            var b2x = (3 - this.t) * a2x / 2 + (this.t - 1) * a3x / 2;
            var a1y = (1 - this.t) * this.p0y + this.t * this.p1y;
            var a2y = (2 - this.t) * this.p1y + (this.t - 1) * this.p2y;
            var a3y = (3 - this.t) * this.p2y + (this.t - 2) * this.p3y;
            var b1y = (2 - this.t) * a1y / 2 + this.t * a2y / 2;
            var b2y = (3 - this.t) * a2y / 2 + (this.t - 1) * a3y / 2;
            var cx = (2 - this.t) * b1x + (this.t - 1) * b2x;
            var cy = (2 - this.t) * b1y + (this.t - 1) * b2y;

            ret.push(cx);
            ret.push(cy);

            this.t += speed / 1000;
        }

        return ret;
    };

    /**
     * Bounce movement class. Moves to the starting position, then moves in a straight line at a given angle,
     * bouncing on the screen's borders.
     * @constructor
     * @memberOf BHell
     * @extends BHell.BHell_Mover_Base
     */
    var BHell_Mover_Bounce = my.BHell_Mover_Bounce = function () {
        this.initialize.apply(this, arguments);
    };

    BHell_Mover_Bounce.prototype = Object.create(BHell_Mover_Base.prototype);
    BHell_Mover_Bounce.prototype.constructor = BHell_Mover_Bounce;

    /**
     * Constructor
     * @param x X coordinate of the initial position.
     * @param y Y coordinate of the initial position.
     * @param angle Initial angle.
     * @param w Width of the object to move.
     * @param h Height of the object to move.
     */
    BHell_Mover_Bounce.prototype.initialize = function (x, y, angle, w, h) {
        BHell_Mover_Base.prototype.initialize.call(this);

        this.inPosition = false;
        this.initX = x;
        this.initY = y;
        this.signX = +1;
        this.signY = +1;
        this.angle = angle;
        this.w = w;
        this.h = h;
    };

    /**
     * Moves to the starting position, then moves at this.angle, bouncing on the screen's borders.
     * @param oldX Old x coordinate.
     * @param oldY Old y coordinate.
     * @param speed Movement speed. In pixels per frame.
     * @returns {Array}
     */
    BHell_Mover_Bounce.prototype.move = function (oldX, oldY, speed) {
        var ret = [];

        if (this.inPosition) {
            var destX = oldX + Math.cos(this.angle) * speed * this.signX;
            var destY = oldY + Math.sin(this.angle) * speed * this.signY;
            if (destX < this.w / 2) {
                destX = this.w / 2;
                this.signX = -this.signX;
            }
            else if (destX > Graphics.width - this.w / 2) {
                destX = Graphics.width - this.w / 2;
                this.signX = -this.signX;
            }

            if (destY < this.h / 2) {
                destY = this.h / 2;
                this.signY = -this.signY;
            }
            else if (destY > Graphics.height - this.h / 2) {
                destY = Graphics.height - this.h / 2;
                this.signY = -this.signY;
            }

            ret.push(destX);
            ret.push(destY);
        }
        else {
            var dx = this.initX - oldX;
            var dy = this.initY - oldY;
            if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) { // If the error is less than two pixels
                this.inPosition = true;
                ret.push(this.initX);
                ret.push(this.initY);
            }
            else {
                var angle = Math.atan2(dy, dx);
                ret.push(oldX + Math.cos(angle) * speed);
                ret.push(oldY + Math.sin(angle) * speed);
            }
        }
        return ret;
    };


    return my;
}(BHell || {}));