Source: enemies.js

var BHell = (function (my) {

/**
 * Enemy base class. Each enemy's behaviour is controlled by a mover, an array of emitters and a parameter object, but
 * this modularity can be overridden in derived classes.
 *
 * Parameters shared between all enemies:
 *
 * - hp: Hitpoints of the enemy,
 * - speed: Movement's speed (the unit is determined by the specific mover used),
 * - period: Emitters' period,
 * - hitbox_w: Width of the hitbox used for collisions with bullets and the player,
 * - hitbox_h: Height of the hitbox,
 * - angle: Shooting angle,
 * - aim: Aim parameter for the emitters,
 * - always_aim: Always_aim parameter for the emitters,
 * - rnd: If the emitters aren't aimed at the player and rnd = true, every shot's direction is randomized,
 * - score: Score points awarded for each successful bullet hit,
 * - kill_score: Score points awarded on enemy's death,
 * - boss: If true, an hp bar will be shown,
 * - boss_bgm: If defined, plays this BGM instead of the one defined on the map,
 * - suppress_warning: If true (together with boss), doesn't show the warning sign,
 * - resume_bgm: If true, when the monster is defeated, resumes the previous BGM,
 * - bullet: bullet parameters (see {@link BHell.BHell_Bullet}).
 *
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Sprite
 */
var BHell_Enemy_Base = my.BHell_Enemy_Base = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Base.prototype = Object.create(my.BHell_Sprite.prototype);
BHell_Enemy_Base.prototype.constructor = BHell_Enemy_Base;

/**
 * Constructor. Sets the parameters shared between all classes.
 *
 * @param x X spawning coordinate.
 * @param y Y spawning coordinate.
 * @param image Enemy sprite.
 * @param params Setup parameters.
 * @param parent Container for the enemy's, emitters' and bullets' sprites.
 * @param enemyList Array in which the enemy will be pushed.
 */
BHell_Enemy_Base.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    my.BHell_Sprite.prototype.initialize.call(this, image.characterName, image.characterIndex, image.direction, image.pattern, ((params.animated != null)? params.animated: true), params.animation_speed || 25);

    this.parent = parent;
    this.parent.addChild(this);
    this.enemyList = enemyList;
    this.emitters = [];

    this.x = x;
    this.y = y;

    this.hasAppeared = false;

    // Set default parameters.
    this.hp = 1;
    this.speed = 3;
    this.period = 60;
    this.hitboxW = this.width;
    this.hitboxH = this.height;
    this.angle = Math.PI / 2;
    this.aim = false;
    this.alwaysAim = false;
    this.aimX = 0;
    this.aimY = 0;
    this.rnd = false;
    this.score = 10;
    this.killScore = 100;
    this.boss = false;
    this.suppressWarning = false;

    // Overrides default parameters with params content.
    if (params != null) {
        var tmp;

        tmp = my.parse(params.hp, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp > 0) {
            this.hp = Math.round(tmp);
        }
        tmp = my.parse(params.speed, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp < 10 && tmp > 0) {
            this.speed = tmp;
        }
        tmp = my.parse(params.period, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp > 0) {
            this.period = tmp;
        }
        tmp = my.parse(params.hitbox_w, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp > 0) {
            this.hitboxW = tmp;
        }
        tmp = my.parse(params.hitbox_h, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp > 0) {
            this.hitboxH = tmp;
        }
        tmp = my.parse(params.angle, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp !== null) {
            this.angle = tmp;
        }

        tmp = my.parse(params.aim, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp !== null) {
            this.aim = tmp;
        }

        tmp = my.parse(params.always_aim, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp !== null) {
            this.alwaysAim = tmp;
        }

        tmp = my.parse(params.aim_x, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp !== null) {
            this.aimX = tmp;
        }

        tmp = my.parse(params.aim_y, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp !== null) {
            this.aimY = tmp;
        }

        tmp = my.parse(params.rnd, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp !== null) {
            this.rnd = tmp;
        }

        tmp = my.parse(params.score, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp !== null) {
            this.score = tmp;
        }

        tmp = my.parse(params.kill_score, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp !== null) {
            this.killScore = tmp;
        }

        tmp = my.parse(params.boss, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp !== null) {
            this.boss = tmp;
        }

        tmp = my.parse(params.suppress_warning, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp !== null) {
            this.suppressWarning = tmp;
        }

        if (params.bullet != null) {
            this.bullet = Object.assign({}, params.bullet);
        }
    }

    if (this.boss) {
        my.bossMaxHp += this.hp;
        my.bossHp += this.hp;
    }
};

/**
 * Checks if the given coordinates are inside the enemy's hitbox.
 * In case of collision, stores the coordinates into this.lastX and this.lastY (useful for spawning explosions, for example).
 * @param x X coordinate.
 * @param y Y coordinate.
 * @returns {boolean} true if (x, y) is inside the hitbox.
 */
BHell_Enemy_Base.prototype.checkCollision = function (x, y) {
    var dx = Math.abs(this.x - x);
    var dy = Math.abs(this.y - y);

    if (dx < this.hitboxW / 2 && dy < this.hitboxH / 2) {
        this.lastX = x;
        this.lastY = y;
    }

    return (dx < this.hitboxW / 2 && dy < this.hitboxH / 2);
};

/**
 * Update function, called every frame.
 */
BHell_Enemy_Base.prototype.update = function () {
    my.BHell_Sprite.prototype.update.call(this);
    this.move();
    this.shoot(true);

    this.emitters.forEach(e => { // If not shooting, change the angle
        if (this.aim === false && this.rnd === true) {
            e.angle = Math.random() * 2 * Math.PI;
        }

        e.update();
    });
};

/**
 * Moves the enemy and all its emitters, delegating the calculations to its mover.
 */
BHell_Enemy_Base.prototype.move = function () {
    if (this.mover != null) {
        var p = this.mover.move(this.x, this.y, this.speed);
        this.x = p[0];
        this.y = p[1];
    }

    this.emitters.forEach(e => {
        e.move(this.x, this.y);
    });
};

/**
 * Enables/disables shooting for every emitter (but only if the player is ready to move).
 * @param t True to enable shooting, false otherwise.
 */
BHell_Enemy_Base.prototype.shoot = function (t) {
    this.emitters.forEach(e => {
        e.shooting = t && !my.player.justSpawned;
    });
};


/**
 * Makes the enemy lose one hit point, possibly killing it.
 */
BHell_Enemy_Base.prototype.hit = function () {
    this.hp--;
    $gameBHellResult.score += this.score;

    if (this.boss) {
        my.bossHp--;
    }

    if (this.hp <= 0) {
        this.die();
        $gameBHellResult.enemiesKilled++;
    }
};

/**
 * Checks if the enemy has left the map, but only after it has appeared on screen to avoid triggering the enemy's
 * destruction when it's just spawned.
 *
 * The controller relies on this method to destroy enemies, so enemies which shouldn't be destroyed when leaving the map
 * should always return false.
 * @returns {boolean} True if the enemy has appeared on the screen and is currently outside the screen.
 */
BHell_Enemy_Base.prototype.isOutsideMap = function () {
    var outside = (this.x < -this.width / 2) || (this.x > Graphics.width + this.width / 2) || (this.y >= Graphics.height + this.height / 2) || (this.y < -this.height / 2);
    var ret = this.hasAppeared && outside;

    this.hasAppeared |= !outside;

    return ret;
};

/**
 * Checks if the enemy has collided with the player.
 * @param player Player object.
 * @returns {boolean} True if there is a collision.
 */
BHell_Enemy_Base.prototype.hasCrashed = function(player) {
    var dx = Math.abs(this.x - player.x);
    var dy = Math.abs(this.y - player.y);
    var hitW = (this.hitboxW + player.hitboxW) / 2 ;
    var hitH = (this.hitboxH + player.hitboxH) / 2;

    return dx < hitW && dy < hitH && !player.immortal;
};

/**
 * Crashes the enemy, destroying it.
 * @returns {boolean} True if the crash has succeded (e.g. bosses immune to crashing will return false).
 */
BHell_Enemy_Base.prototype.crash = function() {
    if (this.boss !== true) {
        my.explosions.push(new my.BHell_Explosion(this.x, this.y, this.parent, my.explosions));
        this.destroy();
    }
    $gameBHellResult.enemiesCrashed++;

    return true;
};

/**
 * Awards the killing score and destroys the enemy.
 */
BHell_Enemy_Base.prototype.die = function() {
    $gameBHellResult.score += this.killScore;
    my.explosions.push(new my.BHell_Explosion(this.x, this.y, this.parent, my.explosions));

    this.destroy();
};

/**
 * Destroys the enemy sprite and removes it from the controller's array of enemies.
 */
BHell_Enemy_Base.prototype.destroy = function() {
    if (this.parent != null) {
        this.parent.removeChild(this);
    }
    this.enemyList.splice(this.enemyList.indexOf(this), 1);
};


/**
 * Suicide enemy class. Chases the player until they crash, it never shoots.
 *
 * No additional parameters are defined.
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Enemy_Base
 */
var BHell_Enemy_Suicide = my.BHell_Enemy_Suicide = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Suicide.prototype = Object.create(BHell_Enemy_Base.prototype);
BHell_Enemy_Suicide.prototype.constructor = BHell_Enemy_Suicide;

BHell_Enemy_Suicide.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    BHell_Enemy_Base.prototype.initialize.call(this, x, y, image, params, parent, enemyList);
    this.mover = new my.BHell_Mover_Chase();
};

    /**
     * Orbiter enemy class. Orbits around the player and shoots toward it once in a while. It is never destroyed outside the map.
     *
     * Additional parameters:
     *
     * - radius: distance from the player.
     * - counterclockwise: if true orbits counterclockwise.
     * @constructor
     * @memberOf BHell
     * @extends BHell.BHell_Enemy_Base
     */
    var BHell_Enemy_Orbiter = my.BHell_Enemy_Orbiter = function() {
        this.initialize.apply(this, arguments);
    };

    BHell_Enemy_Orbiter.prototype = Object.create(BHell_Enemy_Base.prototype);
    BHell_Enemy_Orbiter.prototype.constructor = BHell_Enemy_Orbiter;

    BHell_Enemy_Orbiter.prototype.initialize = function (x, y, image, params, parent, enemyList) {
        BHell_Enemy_Base.prototype.initialize.call(this, x, y, image, params, parent, enemyList);

        // Set default parameters for this class:
        this.radius = 250;
        this.counterclockwise = false;

        // Overrides default parameters:
        if (params != null) {
            var tmp;

            tmp = my.parse(params.radius, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
            if (tmp > 0) {
                this.radius = Math.round(tmp);
            }

            tmp = my.parse(params.counterclockwise, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
            if (tmp !== null) {
                this.counterclockwise = tmp;
            }
        }

        var emitterParams = {};
        emitterParams.x = 0;
        emitterParams.y = 0;
        emitterParams.period = this.period;
        emitterParams.angle = 0;
        emitterParams.aim = true;
        emitterParams.always_aim = true;
        emitterParams.bullet = Object.assign({}, this.bullet);

        this.mover = new my.BHell_Mover_Orbit(this.radius, this.counterclockwise);
        this.emitters.push(new my.BHell_Emitter_Angle(this.x, this.y, emitterParams, parent, my.enemyBullets));
    };

    BHell_Enemy_Orbiter.prototype.isOutsideMap = function () {
        return false;
    };

/**
 * Probe enemy class. Switches between two states: moving and shooting. In the first state it moves in a straight line
 * towards a random point on screen and never shoots, in the second state it shoots without moving.
 * Due to the moving behavior, it's impossible for it to leave the screen.
 *
 * Additional parameters:
 *
 * - shooting: length (in frames) of the shooting phase (the moving phase length depends on the distance from the
 *           randomly chosen point).
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Enemy_Base
 */
var BHell_Enemy_Probe = my.BHell_Enemy_Probe = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Probe.prototype = Object.create(BHell_Enemy_Base.prototype);
BHell_Enemy_Probe.prototype.constructor = BHell_Enemy_Probe;

BHell_Enemy_Probe.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    BHell_Enemy_Base.prototype.initialize.call(this, x, y, image, params, parent, enemyList);

    this.shooting = 120;

    if (params != null) {
        var tmp = my.parse(params.shooting, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp > 0) {
            this.shooting = tmp;
        }
    }

    this.destX = Math.random() * Graphics.width;
    this.destY = Math.random() * Graphics.height;
    this.j = 0;

    var emitterParams = {};
    emitterParams.x = 0;
    emitterParams.y = 0;
    emitterParams.period = this.period;
    emitterParams.angle = 0;
    emitterParams.aim = this.aim;
    emitterParams.always_aim = this.alwaysAim;
    emitterParams.aim_x = this.aimX;
    emitterParams.aim_y = this.aimY;
    emitterParams.rnd = this.rnd;
    emitterParams.bullet = Object.assign({}, this.bullet);

    this.moving = true;
    this.emitters.push(new my.BHell_Emitter_Angle(this.x, this.y, emitterParams, parent, my.enemyBullets));
};

/**
 * Update method. Since no mover is used, the entire moving behaviour is handled here.
 */
BHell_Enemy_Probe.prototype.update = function () {
    my.BHell_Sprite.prototype.update.call(this);

    if (this.moving) {
        var dx = this.destX - this.x;
        var dy = this.destY - this.y;
        var angle = Math.atan2(dy, dx);

        if (dx > 0) {
            this.x += Math.min(dx, Math.cos(angle) * this.speed);
        }
        else if (dx < 0) {
            this.x += Math.max(dx, Math.cos(angle) * this.speed);
        }

        if (dy > 0) {
            this.y += Math.min(dy, Math.sin(angle) * this.speed);
        }
        else if (dy < 0) {
            this.y += Math.max(dy, Math.sin(angle) * this.speed);
        }

        var shootingAngle = 0;
        if (this.aim === false && this.rnd === true) {
            shootingAngle = Math.random() * 2 * Math.PI;
        }
        this.emitters.forEach(e => {
            e.move(this.x, this.y);
            e.angle = shootingAngle;
            e.update();
        });
        if (Math.abs(dx) < 2 && Math.abs(dx) < 2) {
            this.destX = Math.random() * Graphics.width;
            this.destY = Math.random() * Graphics.height;
            this.moving = false;
        }
    }
    else {
        this.j = (this.j + 1) % this.shooting;
        this.shoot(true);
        this.emitters.forEach(e => {
            e.update();
        });
        if (this.j === 0) {
            this.moving = true;
            this.shoot(false);
        }
    }
};

/**
 * Blocker enemy class. It moves horizontally trying to stop the player (as long as its speed can keep up) and slowly
 *  creeps towards the bottom, shooting downwards.
 *
 * Added parameters:
 *
 * - vspeed: vertical speed (in pixels per frame).
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Enemy_Base
 */
var BHell_Enemy_Blocker = my.BHell_Enemy_Blocker = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Blocker.prototype = Object.create(BHell_Enemy_Base.prototype);
BHell_Enemy_Blocker.prototype.constructor = BHell_Enemy_Blocker;

BHell_Enemy_Blocker.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    BHell_Enemy_Base.prototype.initialize.call(this, x, y, image, params, parent, enemyList);

    this.vspeed = 0.2;

    if (params != null) {
        var tmp = my.parse(params.vspeed, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height, Graphics.width, Graphics.height);
        if (tmp > 0) {
            this.vspeed = tmp;
        }
    }

    var emitterParams = {};
    emitterParams.x = 0;
    emitterParams.y = 0;
    emitterParams.period = this.period;
    emitterParams.angle = Math.PI / 2;
    emitterParams.aim = false;
    emitterParams.always_aim = false;
    emitterParams.rnd = false;
    emitterParams.bullet = Object.assign({}, this.bullet);

    this.emitters.push(new my.BHell_Emitter_Angle(this.x, this.y, emitterParams, parent, my.enemyBullets));
};

/**
 * Move method. Instead of using a mover, the behaviour is entirely handled here.
 */
BHell_Enemy_Blocker.prototype.move = function () {
    var dx = my.player.x - this.x;

    if (dx > 0) {
        this.x += Math.min(dx, this.speed);
    }
    else if (dx < 0) {
        this.x += Math.max(dx, -this.speed);
    }

    this.y += this.vspeed;

    this.emitters.forEach(e => {
        e.move(this.x, this.y);
    });
};

/**
 * Spline-based enemy base class. Sets up the moving behavior for every enemy moving in a spline pattern.
 *
 * Additional parameters:
 *
 * - A {x, y}: First spline point,
 * - B {x, y}: Second spline point,
 * - C {x, y}: Third spline point,
 * - D {x, y}: Fourth spline point.
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Enemy_Base
 */
var BHell_Enemy_Spline = my.BHell_Enemy_Spline = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Spline.prototype = Object.create(BHell_Enemy_Base.prototype);
BHell_Enemy_Spline.prototype.constructor = BHell_Enemy_Spline;

BHell_Enemy_Spline.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    BHell_Enemy_Base.prototype.initialize.call(this, x, y, image, params, parent, enemyList);

    var A = {x: x, y: y - this.height};
    var B = {x: x, y: y - this.height};
    var C = {x: x, y: Graphics.height + this.height};
    var D = {x: x, y: Graphics.height + this.height};

    if (params != null) {
        if (params.A != null) {
            A.x = my.parse(params.A.x, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height) || A.x;
            A.y = my.parse(params.A.y, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height) || A.y;
        }
        if (params.B != null) {
            B.x = my.parse(params.B.x, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height) || B.x;
            B.y = my.parse(params.B.y, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height) || B.y;
        }
        if (params.C != null) {
            C.x = my.parse(params.C.x, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height) || C.x;
            C.y = my.parse(params.C.y, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height) || C.y;
        }
        if (params.D != null) {
            D.x = my.parse(params.D.x, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height) || D.x;
            D.y = my.parse(params.D.y, this.x, this.y, this.patternWidth(), this.patternHeight(), Graphics.width, Graphics.height) || D.y;
        }
    }

    this.mover = new my.BHell_Mover_Spline(A.x, A.y, B.x, B.y, C.x, C.y, D.x, D.y);
};

/**
 * Small fry enemy class. Shoots with an angle emitter.
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Enemy_Spline
 */
var BHell_Enemy_Smallfry = my.BHell_Enemy_Smallfry = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Smallfry.prototype = Object.create(BHell_Enemy_Spline.prototype);
BHell_Enemy_Smallfry.prototype.constructor = BHell_Enemy_Smallfry;

BHell_Enemy_Smallfry.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    BHell_Enemy_Spline.prototype.initialize.call(this, x, y, image, params, parent, enemyList);

    var emitterParams = {};
    emitterParams.x = 0;
    emitterParams.y = 0;
    emitterParams.period = this.period;
    emitterParams.angle = this.angle;
    emitterParams.aim = this.aim;
    emitterParams.aim_x = this.aimX;
    emitterParams.aim_y = this.aimY;
    emitterParams.rnd = this.rnd;
    emitterParams.bullet = Object.assign({}, this.bullet);

    this.emitters.push(new my.BHell_Emitter_Angle(this.x, this.y, emitterParams, parent, my.enemyBullets));
};

/**
 * Gunner enemy base class. Alternates two states: shooting and cooling down (during which
 * it can't shoot).
 *
 * Additional parameters:
 *
 * - cooldown: Length (in frames) of the cooldown phase,
 * - shooting: Length (in frames) of the shooting phase,
 * - stop_on_shooting: if true the enemy halts its movements while shooting.
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Enemy_Spline
 */
var BHell_Enemy_Gunner_Base = my.BHell_Enemy_Gunner_Base = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Gunner_Base.prototype = Object.create(BHell_Enemy_Spline.prototype);
BHell_Enemy_Gunner_Base.prototype.constructor = BHell_Enemy_Gunner_Base;

BHell_Enemy_Gunner_Base.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    BHell_Enemy_Spline.prototype.initialize.call(this, x, y, image, params, parent, enemyList);

    this.cooldown = 60;
    this.shooting = 60;
    this.stopOnShooting = false;

    if (params != null) {
        this.cooldown = params.cooldown || this.cooldown;
        this.shooting = params.shooting || this.shooting;
        this.stopOnShooting = params.stop_on_shooting || this.stopOnShooting;
    }

    this.j = 1;
    this.coolingDown = true;
};

/**
 * Update method. Alternates between shooting and cooling down phases. If rnd = true, the shooting angle is changed only
 * during cooldown.
 */
BHell_Enemy_Gunner_Base.prototype.update = function () {
    my.BHell_Sprite.prototype.update.call(this);
    if (this.stopOnShooting === false) {
        this.move();
    }

    if (this.coolingDown) {
        this.j = (this.j + 1) % this.cooldown;
        this.shoot(false);
        if (this.stopOnShooting === true) {
            this.move();
        }
        this.emitters.forEach(e => { // If not shooting, change the angle
            if (this.aim === false && this.rnd === true) {
                e.angle = Math.random() * 2 * Math.PI;
            }

            e.update();
        });
        if (this.j === 0) {
            this.coolingDown = false;
        }
    }
    else {
        this.j = (this.j + 1) % this.shooting;
        this.shoot(true);
        this.emitters.forEach(e => { // If shooting, keep the aim steady
            e.update();
        });
        if (this.j === 0) {
            this.coolingDown = true;
        }
    }
};

// Gunner enemy: shoots a series of bullets.
/**
 * Gunner enemy class. Gunner with an Angle emitter.
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Enemy_Gunner_Base
 */
var BHell_Enemy_Gunner = my.BHell_Enemy_Gunner = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Gunner.prototype = Object.create(BHell_Enemy_Gunner_Base.prototype);
BHell_Enemy_Gunner.prototype.constructor = BHell_Enemy_Gunner;

BHell_Enemy_Gunner.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    BHell_Enemy_Gunner_Base.prototype.initialize.call(this, x, y, image, params, parent, enemyList);

    var emitterParams = {};
    emitterParams.x = 0;
    emitterParams.y = 0;
    emitterParams.aim = this.aim;
    emitterParams.always_aim = this.alwaysAim;
    emitterParams.aim_x = this.aimX;
    emitterParams.aim_y = this.aimY;
    emitterParams.period = this.period;
    emitterParams.angle = this.angle;
    emitterParams.bullet = Object.assign({}, this.bullet);

    this.emitters.push(new my.BHell_Emitter_Angle(this.x, this.y, emitterParams, parent, my.enemyBullets));
};

/**
 * Sprayer enemy class. Gunner with a Spray emitter.
 *
 * Additional parameters:
 *
 * - a: initial angle for the spray arc,
 * - b: final angle for the spray arc,
 * - n: number of bullets to shot.
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Enemy_Gunner_Base
 */
var BHell_Enemy_Sprayer = my.BHell_Enemy_Sprayer = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Sprayer.prototype = Object.create(BHell_Enemy_Gunner_Base.prototype);
BHell_Enemy_Sprayer.prototype.constructor = BHell_Enemy_Sprayer;

BHell_Enemy_Sprayer.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    BHell_Enemy_Gunner_Base.prototype.initialize.call(this, x, y, image, params, parent, enemyList);

    this.a = Math.PI / 2 - Math.PI / 16;
    this.b = Math.PI / 2 + Math.PI / 16;
    this.n = 10;

    if(params != null) {
        this.a = params.a || this.a;
        this.b = params.b || this.b;
        this.n = params.n || this.n;
    }

    var emitterParams = {};
    emitterParams.x = 0;
    emitterParams.y = 0;
    emitterParams.period = this.period;
    emitterParams.angle = this.angle;
    emitterParams.aim_x = this.aimX;
    emitterParams.aim_y = this.aimY;
    emitterParams.a = this.a;
    emitterParams.b = this.b;
    emitterParams.n = this.n;
    emitterParams.aim = this.aim;
    emitterParams.bullet = Object.assign({}, this.bullet);

    this.emitters.push(new my.BHell_Emitter_Spray(this.x, this.y, emitterParams, parent, my.enemyBullets));
};

/**
 * Burster enemy class. Gunner with a Burst emitter.
 *
 * Additional parameters:
 *
 * - shots: number of bullets to fire in one burst,
 * - dispersion: radius of the dispersion circle.
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Enemy_Gunner_Base
 */
var BHell_Enemy_Burster = my.BHell_Enemy_Burster = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Burster.prototype = Object.create(BHell_Enemy_Gunner_Base.prototype);
BHell_Enemy_Burster.prototype.constructor = BHell_Enemy_Burster;

BHell_Enemy_Burster.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    BHell_Enemy_Gunner_Base.prototype.initialize.call(this, x, y, image, params, parent, enemyList);

    this.shots = 30;
    this.dispersion = 50;

    if (params != null) {
        this.shots = params.shots || this.shots;
        this.dispersion = params.dispersion || this.dispersion;
    }

    var emitterParams = {};
    emitterParams.x = 0;
    emitterParams.y = 0;
    emitterParams.period = this.period;
    emitterParams.aim = this.aim;
    emitterParams.angle = this.angle;
    emitterParams.aim_x = this.aimX;
    emitterParams.aim_y = this.aimY;
    emitterParams.shots = this.shots;
    emitterParams.dispersion = this.dispersion;
    emitterParams.bullet = Object.assign({}, this.bullet);

    this.emitters.push(new my.BHell_Emitter_Burst(this.x, this.y, emitterParams, parent, my.enemyBullets));
};

/**
 * Starshooter enemy class. Gunner with a star pattern (360 degrees Spray emitter).
 *
 * Additional parameters:
 *
 * - n: number of points for the star.
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Enemy_Gunner_Base
 */
var BHell_Enemy_Starshooter = my.BHell_Enemy_Starshooter = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Starshooter.prototype = Object.create(BHell_Enemy_Gunner_Base.prototype);
BHell_Enemy_Starshooter.prototype.constructor = BHell_Enemy_Starshooter;

BHell_Enemy_Starshooter.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    BHell_Enemy_Gunner_Base.prototype.initialize.call(this, x, y, image, params, parent, enemyList);

    this.n = 5;

    if (params != null) {
        this.shots = params.shots || this.shots;
        this.n = params.n || this.n;
    }

    var emitterParams = {};
    emitterParams.x = 0;
    emitterParams.y = 0;
    emitterParams.period = this.period;
    emitterParams.angle = this.angle;
    emitterParams.aim_x = this.aimX;
    emitterParams.aim_y = this.aimY;
    emitterParams.aim = this.aim;
    emitterParams.always_aim = this.alwaysAim;
    emitterParams.a = 0;
    emitterParams.b = 2 * Math.PI;
    emitterParams.n = this.n;
    emitterParams.bullet = Object.assign({}, this.bullet);

    this.emitters.push(new my.BHell_Emitter_Spray(this.x, this.y, emitterParams, parent, my.enemyBullets));
};

/**
 * Swirler enemy class. Starshooter with slowly rotating angle.
 *
 * Additional parameters:
 *
 * - rotation_angle: rotation speed (in radians per frame) of the bullets' swirl.
 * @constructor
 * @memberOf BHell
 * @extends BHell.BHell_Enemy_Starshooter
 */
var BHell_Enemy_Swirler = my.BHell_Enemy_Swirler = function() {
    this.initialize.apply(this, arguments);
};

BHell_Enemy_Swirler.prototype = Object.create(BHell_Enemy_Starshooter.prototype);
BHell_Enemy_Swirler.prototype.constructor = BHell_Enemy_Swirler;

BHell_Enemy_Swirler.prototype.initialize = function (x, y, image, params, parent, enemyList) {
    BHell_Enemy_Starshooter.prototype.initialize.call(this, x, y, image, params, parent, enemyList);

    this.rotation_angle = 0.01;

    if (params != null) {
        this.rotation_angle = params.rotation_angle || this.rotation_angle;
    }
};

BHell_Enemy_Swirler.prototype.update = function () {
    this.emitters.forEach(e => {
        e.a += this.rotation_angle;
        e.b += this.rotation_angle;
    });

    BHell_Enemy_Starshooter.prototype.update.call(this);
};

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