(function () { if (typeof Mario === "undefined") window.Mario = {}; var Player = (Mario.Player = function (pos) { //I know, I know, there are a lot of variables tracking Mario's state. //Maybe these can be consolidated some way? We'll see once they're all in. this.power = 0; this.coins = 0; this.powering = []; this.bounce = false; this.jumping = 0; this.canJump = true; this.invincibility = 0; this.crouching = false; this.fireballs = 0; this.runheld = false; this.noInput = false; this.targetPos = []; Mario.Entity.call(this, { pos: pos, sprite: new Mario.Sprite( "sprites/player.png", [80, 32], [16, 16], 0, ), hitbox: [0, 0, 16, 16], }); }); Mario.Util.inherits(Player, Mario.Entity); Player.prototype.run = function () { this.maxSpeed = 2.5; if (this.power == 2 && !this.runheld) { this.shoot(); } this.runheld = true; }; Player.prototype.shoot = function () { if (this.fireballs >= 2) return; //Projectile limit! this.fireballs += 1; var fb = new Mario.Fireball([this.pos[0] + 8, this.pos[1]]); //I hate you, Javascript. fb.spawn(this.left); this.shooting = 2; }; Player.prototype.noRun = function () { this.maxSpeed = 1.5; this.moveAcc = 0.07; this.runheld = false; }; Player.prototype.moveRight = function () { //we're on the ground if (this.vel[1] === 0 && this.standing) { if (this.crouching) { this.noWalk(); return; } this.acc[0] = this.moveAcc; this.left = false; } else { this.acc[0] = this.moveAcc; } }; Player.prototype.moveLeft = function () { if (this.vel[1] === 0 && this.standing) { if (this.crouching) { this.noWalk(); return; } this.acc[0] = -this.moveAcc; this.left = true; } else { this.acc[0] = -this.moveAcc; } }; Player.prototype.noWalk = function () { this.maxSpeed = 0; if (this.vel[0] === 0) return; if (Math.abs(this.vel[0]) <= 0.1) { this.vel[0] = 0; this.acc[0] = 0; } }; Player.prototype.crouch = function () { if (this.power === 0) { this.crouching = false; return; } if (this.standing) this.crouching = true; }; Player.prototype.noCrouch = function () { this.crouching = false; }; Player.prototype.jump = function () { if (this.vel[1] > 0) { return; } if (this.jumping) { this.jumping -= 1; } else if (this.standing && this.canJump) { this.jumping = 20; this.canJump = false; this.standing = false; this.vel[1] = -6; if (this.power === 0) { sounds.smallJump.currentTime = 0; sounds.smallJump.play(); } else { sounds.bigJump.currentTime = 0; sounds.bigJump.play(); } } }; Player.prototype.noJump = function () { this.canJump = true; if (this.jumping) { if (this.jumping <= 16) { this.vel[1] = 0; this.jumping = 0; } else this.jumping -= 1; } }; Player.prototype.setAnimation = function () { if (this.dying) return; if (this.starTime) { var index; if (this.starTime > 60) index = Math.floor(this.starTime / 2) % 3; else index = Math.floor(this.starTime / 8) % 3; this.sprite.pos[1] = level.invincibility[index]; if (this.power == 0) { this.sprite.pos[1] += 32; } this.starTime -= 1; if (this.starTime == 0) { switch (this.power) { case 0: this.sprite.pos[1] = 32; break; case 1: this.sprite.pos[1] = 0; break; case 2: this.sprite.pos[1] = 96; break; } } } //okay cool, now set the sprite if (this.crouching) { this.sprite.pos[0] = 176; this.sprite.speed = 0; return; } if (this.jumping) { this.sprite.pos[0] = 160; this.sprite.speed = 0; } else if (this.standing) { if (Math.abs(this.vel[0]) > 0) { if (this.vel[0] * this.acc[0] >= 0) { this.sprite.pos[0] = 96; this.sprite.frames = [0, 1, 2]; if (this.vel[0] < 0.2) { this.sprite.speed = 5; } else { this.sprite.speed = Math.abs(this.vel[0]) * 8; } } else if ( (this.vel[0] > 0 && this.left) || (this.vel[0] < 0 && !this.left) ) { this.sprite.pos[0] = 144; this.sprite.speed = 0; } } else { this.sprite.pos[0] = 80; this.sprite.speed = 0; } if (this.shooting) { this.sprite.pos[0] += 160; this.shooting -= 1; } } if (this.flagging) { this.sprite.pos[0] = 192; this.sprite.frames = [0, 1]; this.sprite.speed = 10; if (this.vel[1] === 0) this.sprite.frames = [0]; } //which way are we facing? if (this.left) { this.sprite.img = "sprites/playerl.png"; } else { this.sprite.img = "sprites/player.png"; } }; Player.prototype.update = function (dt, vX) { if (this.powering.length !== 0) { var next = this.powering.shift(); if (next == 5) return; this.sprite.pos = this.powerSprites[next]; this.sprite.size = this.powerSizes[next]; this.pos[1] += this.shift[next]; if (this.powering.length === 0) { delete level.items[this.touchedItem]; } return; } if (this.invincibility) { this.invincibility -= Math.round(dt * 60); } if (this.waiting) { this.waiting -= dt; if (this.waiting <= 0) { this.waiting = 0; } else return; } if (this.bounce) { this.bounce = false; this.standing = false; this.vel[1] = -3; } if (this.pos[0] <= vX) { this.pos[0] = vX; this.vel[0] = Math.max(this.vel[0], 0); } if (Math.abs(this.vel[0]) > this.maxSpeed) { this.vel[0] -= (0.05 * this.vel[0]) / Math.abs(this.vel[0]); this.acc[0] = 0; } if (this.dying) { if (this.pos[1] < this.targetPos[1]) { this.vel[1] = 1; } this.dying -= 1 * dt; if (this.dying <= 0) { player = new Mario.Player(level.playerPos); level.loader.call(); input.reset(); } } else { this.acc[1] = 0.25; if (this.pos[1] > 240) { this.die(); } } if (this.piping) { this.acc = [0, 0]; var pos = [Math.round(this.pos[0]), Math.round(this.pos[1])]; if (pos[0] === this.targetPos[0] && pos[1] === this.targetPos[1]) { this.piping = false; this.pipeLoc.call(); } } if (this.flagging) { this.acc = [0, 0]; } if (this.exiting) { this.left = false; this.flagging = false; this.vel[0] = 1.5; if (this.pos[0] >= this.targetPos[0]) { this.sprite.size = [0, 0]; this.vel = [0, 0]; window.setTimeout(function () { player.sprite.size = player.power === 0 ? [16, 16] : [16, 32]; player.exiting = false; player.noInput = false; level.loader(); if (player.power !== 0) player.pos[1] -= 16; music.overworld.currentTime = 0; }, 5000); } } //approximate acceleration this.vel[0] += this.acc[0]; this.vel[1] += this.acc[1]; this.pos[0] += this.vel[0]; this.pos[1] += this.vel[1]; this.setAnimation(); this.sprite.update(dt); }; Player.prototype.checkCollisions = function () { if (this.piping || this.dying) return; //x-axis first! var h = this.power > 0 ? 2 : 1; var w = 1; if (this.pos[1] % 16 !== 0) { h += 1; } if (this.pos[0] % 16 !== 0) { w += 1; } var baseX = Math.floor(this.pos[0] / 16); var baseY = Math.floor(this.pos[1] / 16); for (var i = 0; i < h; i++) { if (baseY + i < 0 || baseY + i >= 15) continue; for (var j = 0; j < w; j++) { if (baseY < 0) { i++; } if (level.statics[baseY + i][baseX + j]) { level.statics[baseY + i][baseX + j].isCollideWith(this); } if (level.blocks[baseY + i][baseX + j]) { level.blocks[baseY + i][baseX + j].isCollideWith(this); } } } }; Player.prototype.powerUp = function (idx) { sounds.powerup.play(); this.powering = [ 0, 5, 2, 5, 1, 5, 2, 5, 1, 5, 2, 5, 3, 5, 1, 5, 2, 5, 3, 5, 1, 5, 4, ]; this.touchedItem = idx; if (this.power === 0) { this.sprite.pos[0] = 80; var newy = this.sprite.pos[1] - 32; this.powerSprites = [ [80, newy + 32], [80, newy + 32], [320, newy], [80, newy], [128, newy], ]; this.powerSizes = [ [16, 16], [16, 16], [16, 32], [16, 32], [16, 32], ]; this.shift = [0, 16, -16, 0, -16]; this.power = 1; this.hitbox = [0, 0, 16, 32]; } else if (this.power == 1) { var curx = this.sprite.pos[0]; this.powerSprites = [ [curx, 96], [curx, level.invincibility[0]], [curx, level.invincibility[1]], [curx, level.invincibility[2]], [curx, 96], ]; this.powerSizes[([16, 32], [16, 32], [16, 32], [16, 32], [16, 32])]; this.shift = [0, 0, 0, 0, 0]; this.power = 2; } else { this.powering = []; delete level.items[idx]; //no animation, but we play the sound and you get 5000 points. } }; Player.prototype.damage = function () { if (this.power === 0) { //if you're already small, you dead! this.die(); } else { //otherwise, you get turned into small mario sounds.pipe.play(); this.powering = [ 0, 5, 1, 5, 2, 5, 1, 5, 2, 5, 1, 5, 2, 5, 1, 5, 2, 5, 1, 5, 2, 5, 3, ]; this.shift = [0, 16, -16, 16]; this.sprite.pos = [160, 0]; this.powerSprites = [ [160, 0], [240, 32], [240, 0], [160, 32], ]; this.powerSizes = [ [16, 32], [16, 16], [16, 32], [16, 16], ]; this.invincibility = 120; this.power = 0; this.hitbox = [0, 0, 16, 16]; } }; Player.prototype.die = function () { //TODO: rewrite the way sounds work to emulate the channels of an NES. music.overworld.pause(); music.underground.pause(); music.overworld.currentTime = 0; music.death.play(); this.noWalk(); this.noRun(); this.noJump(); this.acc[0] = 0; this.sprite.pos = [176, 32]; this.sprite.speed = 0; this.power = 0; this.waiting = 0.5; this.dying = 2; if (this.pos[1] < 240) { //falling into a pit doesn't do the animation. this.targetPos = [this.pos[0], this.pos[1] - 128]; this.vel = [0, -5]; } else { this.vel = [0, 0]; this.targetPos = [this.pos[0], this.pos[1] - 16]; } }; Player.prototype.star = function (idx) { delete level.items[idx]; this.starTime = 660; }; Player.prototype.pipe = function (direction, destination) { sounds.pipe.play(); this.piping = true; this.pipeLoc = destination; switch (direction) { case "LEFT": this.vel = [-1, 0]; this.targetPos = [ Math.round(this.pos[0] - 16), Math.round(this.pos[1]), ]; break; case "RIGHT": this.vel = [1, 0]; this.targetPos = [ Math.round(this.pos[0] + 16), Math.round(this.pos[1]), ]; break; case "DOWN": this.vel = [0, 1]; this.targetPos = [ Math.round(this.pos[0]), Math.round(this.pos[1] + this.hitbox[3]), ]; break; case "UP": this.vel = [0, -1]; this.targetPos = [ Math.round(this.pos[0]), Math.round(this.pos[1] - this.hitbox[3]), ]; break; } }; Player.prototype.flag = function () { this.noInput = true; this.flagging = true; this.vel = [0, 2]; this.acc = [0, 0]; }; Player.prototype.exit = function () { this.pos[0] += 16; this.targetPos[0] = level.exit * 16; this.left = true; this.setAnimation(); this.waiting = 1; this.exiting = true; }; })();