var BHell = (function (my) {
/**
* Controller class. Handles the game mechanics.
*
* Note: Generators in a map with negative scroll speed are still placed bottom-to-top, only the in-game scrolling is affected (ie. the map will scroll top-to-bottom).
*
* @constructor
* @memberOf BHell
*/
var BHell_Controller = my.BHell_Controller = function () {
this.initialize.apply(this, arguments);
};
BHell_Controller.prototype = Object.create(Object.prototype);
BHell_Controller.prototype.constructor = BHell_Controller;
/**
* Constructor. Creates player and generators.
* @param stage Map id for the stage.
* @param playerId Id of the player which will be created.
* @param lives Initial lives for the player (-1: infinite).
* @param parent Container for the sprites.
*/
BHell_Controller.prototype.initialize = function (stage, playerId, lives, parent) {
my.friendlyBullets = [];
my.enemyBullets = [];
my.bulletsHit = 0;
my.bulletsLost = 0;
this.enemies = [];
my.explosions = [];
this.parent = parent;
this.stage = stage;
my.player = new my.BHell_Player(playerId, lives, false, this.parent);
my.player.spawn();
this.generators = [];
this.activeGenerators = [];
my.bossMaxHp = 0;
my.bossHp = 0;
my.bossOnScreen = false;
my.displayWarning = false;
this.messages = [];
// Create the generators from the map's events.
this.stage.events().forEach(e => {
var regex = /<(.+):([0-9]+):([0-9]+):((?:true)|(?:false))(?::((?:true)|(?:false)))?>/i;
var comments = "";
e.event().pages[0].list.filter(l => {
return l.code === 108 || l.code === 408;
}).forEach(l => {
comments += l.parameters[0] + " ";
});
// If an event has some message, store it in the messages array.
var list = e.event().pages[0].list.filter(l => {
return l.code === 101 || l.code === 401;
});
var indexes = [];
for (var i = 0; i < list.length; i++) {
if (list[i].code === 101) {
indexes.push(i);
}
}
for (var i = 0; i < indexes.length; i++) {
var message = {};
message.y = e.event().y;
if (i < indexes.length - 1) {
message.list = list.slice(indexes[i], indexes[i + 1]);
}
else {
message.list = list.slice(indexes[i]);
}
this.messages.push(message);
}
var grps = regex.exec(e.event().note);
if (grps != null) {
this.generators.push(new my.BHell_Generator(e.event().x * this.stage.tileWidth(), e.event().y, e.event().pages[0].image, grps[1], Number(grps[2]), Number(grps[3]), (grps[4] === "true"), (grps[5] === "true"), comments, this.enemies, this.parent));
}
});
var regex = /<bhell:(-?[0-9]+(?:\.[0-9]*))>/i;
var grps = regex.exec($dataMap.note);
this.scrollSpeed = 0;
this.stageY = this.stage.height();
if (grps != null) {
this.scrollSpeed = Number(grps[1]);
}
else {
this.scrollSpeed = 0.1;
console.warn("This map doesn't have a <bhell:speed> note. Setting default speed to 0.1.");
}
this.paused = false;
this.scrolling = true;
my.playing = true;
$gameBHellResult = $gameBHellResult || {};
$gameBHellResult.retries = $gameBHellResult.retries || 0;
$gameBHellResult.livesLost = $gameBHellResult.livesLost || 0;
$gameBHellResult.bombsUsed = $gameBHellResult.bombsUsed || 0;
$gameBHellResult.hitRatio = 0;
$gameBHellResult.enemiesKilled = 0;
$gameBHellResult.enemiesMissed = 0;
$gameBHellResult.enemiesCrashed = 0;
$gameBHellResult.score = 0;
$gameBHellResult.gaveUp = false;
$gameBHellResult.won = false;
};
/**
* Updates the game's loop until all generators are cleared or until the player looses all its lives.
*/
BHell_Controller.prototype.update = function () {
var i;
var g;
var e;
var b;
var m;
if (this.generators.length === 0 && this.activeGenerators.length === 0 && this.enemies.length === 0) {
// Victory.
$gameBHellResult.won = true;
my.player.win();
}
else if (my.player.lives === 0) {
// Defeat.
$gameBHellResult.won = false;
my.playing = false;
}
else if (my.playing && !this.paused && !$gameMessage.isBusy()) { // Main update loop.
// Scroll the stage if there are no stopping generators.
if (this.activeGenerators.filter(g => {
return g.stop === true;
}).length === 0) {
this.stage.scrollUp(this.scrollSpeed);
this.scrolling = true;
}
else {
this.scrolling = false;
}
// If the player can move and there are no active generators to synchronize, update the stage's progression.
if (my.player.justSpawned === false && !my.displayWarning) {
if (this.activeGenerators.filter(g => {
return g.sync === true;
}).length === 0) {
this.stageY -= Math.abs(this.scrollSpeed); // Design choice: a negative scrollSpeed still requires generators to be placed from bottom to top.
}
var bossGenerators = this.activeGenerators.filter(g => {
return g.bossGenerator === true;
});
my.bossOnScreen = bossGenerators.length > 0;
if (!my.bossOnScreen) {
my.bossMaxHp = 0;
my.bossHp = 0;
}
// If it's time to activate a generator, move it from the stage array to the active array.
for (i = 0; i < this.generators.length; i++) {
g = this.generators[i];
if (g.y >= this.stageY) {
this.activeGenerators.push(g);
// If the BGM needs to be changed, save the old one and play the new one.
if (g.bossBgm !== null) {
if (g.resumeBgm) {
my.prevBossBgm = AudioManager.saveBgm();
}
my.bgm = my.bgm || {"name": "", "pan": 0, "pitch": 100, "volume": 90};
my.bgm.name = g.bossBgm;
AudioManager.fadeOutBgm(1);
AudioManager.playBgm(my.bgm);
AudioManager.fadeInBgm(1);
}
// If the warning sign needs to be displayed, pause the generators and show it.
if (g.bossGenerator && !g.suppressWarning) {
my.displayWarning = true;
if (my.warningSign == null) {
my.warningSign = new my.BHell_Warning(my.warningImg, my.warningDuration, my.warningSE, this.parent);
}
}
this.generators.splice(this.generators.indexOf(g), 1);
i--;
}
}
// If it's time to show a message, display it and remove it from the message list.
var msg = this.messages.filter(m => {
return m.y >= this.stageY;
});
if (!$gameMessage.isBusy()) {
if (msg.length > 0) {
m = msg[0];
while (m.list.length > 0) {
var l = m.list[0];
if (l.code === 101) {
$gameMessage.newPage();
$gameMessage.setFaceImage(l.parameters[0], l.parameters[1]);
$gameMessage.setBackground(l.parameters[2]);
$gameMessage.setPositionType(l.parameters[3]);
}
else {
$gameMessage.add(l.parameters[0]);
}
m.list.splice(0, 1);
}
this.messages.splice(this.messages.indexOf(m), 1);
}
}
// Update the active generators. If a generator is synchronized, wait until there are no more enemies to remove it.
if (!my.displayWarning) {
for (i = 0; i < this.activeGenerators.length; i++) {
g = this.activeGenerators[i];
g.update();
if (g.n === 0) {
if (g.sync === false || this.enemies.length === 0) {
this.activeGenerators.splice(this.activeGenerators.indexOf(g), 1);
i--;
// If the BGM needs to be restored to the previous one, do it.
if (g.bossBgm !== null && g.resumeBgm) {
if (my.prevBossBgm.name !== "") {
my.bgm = my.prevBossBgm;
AudioManager.fadeOutBgm(1);
AudioManager.playBgm(my.bgm);
AudioManager.fadeInBgm(1);
}
else {
AudioManager.fadeOutBgm(1);
}
}
}
}
}
}
}
// Update the enemies.
for (i = 0; i < this.enemies.length; i++) {
if (i >= 0) {
e = this.enemies[i];
if (e.isOutsideMap()) {
e.destroy();
i--;
$gameBHellResult.enemiesMissed++;
}
if (e.hasCrashed(my.player)) {
my.player.die(false);
e.crash();
i--;
}
}
}
// Update the player's bullets.
for (i = 0; i < my.friendlyBullets.length; i++) {
b = my.friendlyBullets[i];
if (b.isOutsideMap()) {
b.destroy();
my.bulletsLost++;
i--;
} else {
var enemiesHit = this.enemies.filter(e => {
return e.checkCollision(b.x, b.y);
});
if (enemiesHit.length > 0) {
my.bulletsHit++;
b.destroy();
i--;
enemiesHit[0].hit(); // Each bullet hits only ONE enemy.
}
}
}
this.playerHit = false;
// Update the enemy bullets.
for (i = 0; i < my.enemyBullets.length; i++) {
b = my.enemyBullets[i];
if (b.isOutsideMap()) {
b.destroy();
i--;
}
if (!this.playerHit && my.player.checkCollision(b.x, b.y)) {
this.playerHit = true; // If a bullet has already hit the player during this frame, ignore every other collision (because the player is either dead or has thrown an autobomb).
b.destroy();
my.player.die(true);
i--;
} else if (!b.grazed && my.player.checkGrazing(b.x, b.y)) {
b.grazed = true; // Avoid grazing the same bullet multiple times.
$gameBHellResult.score += my.grazingScore;
}
}
// Update explosions. If the stage is scrolling, move the explosions with it.
for (i = 0; i < my.explosions.length; i++) {
e = my.explosions[i];
if (this.stage.isLoopVertical() || this.stage._displayY !== 0) {
e.speed = this.scrollSpeed * this.stage.tileHeight();
}
else {
e.speed = 0;
}
}
}
};
/**
* Pushes a new message on the message list.
* @param face Faceset.
* @param index Index of the face to display.
* @param background Background window format (0: window, 1: shadow, 2: transparent).
* @param position Window position (0: bottom, 1: middle, 2: top).
* @param lines Array of lines to display with the given settings.
*/
BHell_Controller.prototype.pushMessage = function (face, index, background, position, lines) {
for (var i = 0; i < lines.length / 4; i++) {
var msg = {};
msg.list = [];
var tmp = {};
tmp.code = 101;
tmp.parameters = [face, index, background, position];
msg.list.push(tmp);
for (var j = 0; j < 4; j++) {
if (i * 4 + j < lines.length) {
tmp = {};
tmp.code = 401;
tmp.parameters = [lines[i * 4 + j]];
msg.list.push(tmp);
}
}
msg.y = this.stageY;
this.messages.push(msg);
}
};
/**
* Destroys every enemy bullet on screen.
*/
BHell_Controller.prototype.destroyEnemyBullets = function () {
while (my.enemyBullets.length > 0) {
var b = my.enemyBullets[0];
my.explosions.push(new my.BHell_Explosion(b.x, b.y, this.parent, my.explosions));
b.destroy();
}
};
return my;
}(BHell || {}));