frontend/1/mario/things.js
2023-05-21 21:16:27 -04:00

3034 lines
87 KiB
JavaScript

/* Things.js */
// Stores Thing creators, functions, and manipulators
/* Typical Things */
// There is a lot of information stored in each of these.
// Variable amounts of arguments are passed to the constructor:
// * var mything = new Thing(ConstructionFunc[, arg1[, arg2[, ...]]]);
// * var mygoomb = new Thing(Goomba);
// * var mykoopa = new Thing(Koopa, true);
function Thing(type) {
// If there isn't a type, don't do anything
if(arguments.length == 0 || !type) return;
// Otherwise make this based off the type
// Make sure this is legally created
var self = this === window ? new Thing() : this,
args = self.args = arrayMake(arguments);
args[0] = self;
type.apply(self, args);
self.alive = true;
self.placed = this.outerok = 0;
self.xvel = this.xvel || 0;
self.yvel = this.yvel || 0;
if(self.tolx == null) self.tolx = 0;
if(self.toly == null) self.toly = unitsized8;
self.movement = self.movement; // why..?
self.collide = self.collide || function() {}; // To do: why does taking this out mess things up?
self.death = self.death || killNormal;
self.animate = self.animate || emergeUp;
if(self.width * unitsize < quads.width && self.height * unitsize < quads.height)
self.maxquads = 4; // self could be done with modular stuff... beh
else self.maxquads = quads.length;
self.quads = new Array(self.maxquads)
self.overlaps = [];
self.title = self.title || type.name;
self.spritewidth = self.spritewidth || self.width;
self.spriteheight = self.spriteheight || self.height;
self.sprite = "";
try { setContextStuff(self, self.spritewidth, self.spriteheight); }
catch(err) {
log("Thing context fail", err, self.title, self);
setTimeout(function() { setContextStuff(self, self.spritewidth, self.spriteheight); }, 1);
}
return self;
}
function setContextStuff(me, spritewidth, spriteheight) {
me.spritewidthpixels = me.spritewidth * unitsize;
me.spriteheightpixels = me.spriteheight * unitsize;
me.canvas = getCanvas(me.spritewidthpixels, me.spriteheightpixels);
me.context = me.canvas.getContext("2d");
me.imageData = me.context.getImageData(0, 0, me.spritewidthpixels, me.spriteheightpixels);
me.sprite_type = me.sprite_type || "neither";
canvasDisableSmoothing(me, me.context);
}
// A helper function to make a Thing given the type and arguments
function ThingCreate(type, args) {
var newthing = new Thing();
Thing.apply(newthing, [type].concat(args));
return newthing;
}
/* Thing Functions */
function setCharacter(me, type) {
me.type = type.split(" ")[0]; // so 'shell smart' becomes 'shell'
me.resting = me.under = me.undermid = false;
me.alive = me.character = true;
me.libtype = "characters";
setClassInitial(me, "character " + type);
}
function setSolid(me, name) {
me.type = "solid";
me.name = name;
me.solid = me.alive = true;
me.speed = me.speed || 0; // vertical speed
me.collide = me.collide || characterTouchedSolid;
me.bottomBump = me.bottomBump || function() { /*play("Bump");*/ };
me.action = me.action || function() {};
me.jump = me.jump || function() {};
me.spritewidth = me.spritewidth || 8;
me.spriteheight = me.spriteheight || 8;
me.libtype = "solids";
setClassInitial(me, "solid " + name);
}
function setScenery(me, name) {
setSolid(me, name);
me.libtype = "scenery";
}
// The primary function for placing a thing on the map
function addThing(me, left, top) {
// If me is a function (e.g. 'addThing(Goomba, ...)), make a new thing with that
if(me instanceof Function) {
me = new Thing(me);
}
placeThing(me, left, top);
window[me.libtype].push(me);
me.placed = true;
determineThingQuadrants(me);
if(me.onadding) me.onadding(); // generally just for sprite cycles
setThingSprite(me);
window["last_" + (me.title || me.group || "unknown")] = me;
return me;
}
// Called by addThing for simple placement
function placeThing(me, left, top) {
setLeft(me, left);
setTop(me, top);
updateSize(me);
return me;
}
function addText(html, left, top) {
var element = createElement("div", {innerHTML: html, className: "text",
left: left,
top: top,
onclick: body.onclick || canvas.onclick,
style: {
marginLeft: left + "px",
marginTop: top + "px"
}});
body.appendChild(element);
texts.push(element);
return element;
}
// Called by funcSpawner placed by pushPreText
// Kills the text once it's too far away
function spawnText(me, settings) {
var element = me.element = addText("", me.left, me.top);
if(typeof(settings) == "object") proliferate(element, settings);
else element.innerHTML = settings;
me.movement = false;
}
// Set at the end of shiftToLocation
function checkTexts() {
var delx = quads.delx,
element, me, i;
for(i = texts.length - 1; i >= 0; --i) {
me = texts[i]
element = texts[i].element || me;
me.right = me.left + element.clientWidth
if(me.right < delx) {
body.removeChild(element);
killNormal(me);
deleteThing(element, texts, i);
}
}
}
// To do: make this use a variable number of arguments!
function pushPreThing(type, xloc, yloc, extras, more) {
var prething = new PreThing(map.refx + xloc, map.refy - yloc, type, extras, more);
if(prething.object.solid) {
map.area.width = max(map.area.width, prething.xloc + prething.object.width);
map.area.presolids.push(prething);
}
else map.area.precharacters.push(prething);
return prething;
}
/*
* Characters (except Mario, who has his own .js)
*/
/*
* Items
*/
function Mushroom(me, type) {
me.group = "item";
me.width = me.height = 8;
me.speed = .42 * unitsize;
me.animate = emergeUp;
me.movement = moveSimple;
me.collide = collideFriendly;
me.jump = mushroomJump;
me.death = killNormal;
me.nofire = true;
var name = "mushroom";
switch(type) {
case 1: me.action = gainLife; name += " gainlife"; break;
case -1: me.action = killMario; name += " death"; break;
default: me.action = marioShroom; name += " regular"; break;
}
setCharacter(me, name);
}
function mushroomJump(me) {
me.yvel -= unitsize * 1.4;
me.top -= unitsize;
me.bottom -= unitsize;
updatePosition(me);
}
function FireFlower(me) {
me.group = "item";
me.width = me.height = 8;
me.animate = emergeUp;
me.collide = collideFriendly;
me.action = marioShroom;
me.nofall = me.nofire = true;
me.movement = false;
setCharacter(me, "fireflower");
TimeHandler.addSpriteCycle(me, ["one", "two", "three", "four"]);
}
function FireBall(me, moveleft) {
me.group = "item";
me.width = me.height = 4;
me.speed = unitsize * 1.75;
me.gravity = gravity * 1.56;
me.jumpheight = unitsize * 1.56;
me.nofire = me.nostar = me.collide_primary = true;
me.moveleft = moveleft;
me.animate = emergeFire;
me.movement = moveJumping;
me.collide = fireEnemy;
me.death = fireExplodes;
setCharacter(me, "fireball");
TimeHandler.addSpriteCycle(me, ["one", "two", "three", "four"], 4);
}
function fireEnemy(enemy, me) {
if(!me.alive || me.emerging || enemy.nofire || enemy.height <= unitsize) return;
if(enemy.solid) {
playLocal("Bump", me.right);
}
else {
playLocal("Kick", me.right);
enemy.death(enemy, 2);
scoreEnemyFire(enemy);
}
me.death(me);
}
function fireDeleted() {
--mario.numballs;
}
function fireExplodes(me) {
var fire = new Thing(Firework);
addThing(fire, me.left - fire.width / 2, me.top - fire.height / 2);
fire.animate();
killNormal(me);
}
function Star(me) { // GOLDEEN GOLDEEN
me.group = "item";
me.width = 7;
me.height = 8;
me.speed = unitsize * .56;
me.jumpheight = unitsize * 1.17;
me.gravity = gravity / 2.8;
me.animate = emergeUp;
me.movement = moveJumping;
me.collide = collideFriendly;
me.action = marioStar;
me.death = killNormal;
me.nofire = true;
setCharacter(me, "star item"); // Item class so mario's star isn't confused with this
TimeHandler.addSpriteCycle(me, ["one", "two", "three", "four"], 0, 7);
}
function Shell(me, smart) {
me.width = 8; me.height = 7;
me.group = "item";
me.speed = unitsizet2;
me.collide_primary = true;
me.moveleft = me.xvel = me.move = me.hitcount = me.peeking = me.counting = me.landing = me.enemyhitcount = 0;
me.smart = smart;
me.movement = moveShell;
me.collide = hitShell;
me.death = killFlip;
me.spawntype = Koopa;
var name = "shell" + (smart ? " smart" : " dumb");
setCharacter(me, name);
}
function hitShell(one, two) {
// Assuming two is shell
if(one.type == "shell" && two.type != one.type) return hitShell(two, one);
switch(one.type) {
// Hitting a wall
case "solid":
if(two.right < one.right) {
playLocal("Bump", one.left);
setRight(two, one.left);
two.xvel = -two.speed;
two.moveleft = true;
} else {
playLocal("Bump", one.right);
setLeft(two, one.right);
two.xvel = two.speed;
two.moveleft = false;
}
break;
// Hitting Mario
case "mario":
var shelltoleft = objectToLeft(two, one),
mariojump = one.yvel > 0 && one.bottom <= two.top + unitsizet2;
// Star Mario is pretty easy
if(one.star) {
scoreMarioShell(one, two);
return two.death(two, 2);
}
// If the shell is already being landed on by Mario:
if(two.landing) {
// If the recorded landing direction hasn't changed:
if(two.shelltoleft == shelltoleft) {
// Increase the landing count, and don't do anything.
++two.landing;
// If it's now a count of 1, score the shell
if(two.landing == 1) scoreMarioShell(one, two);
// Reduce that count very soon
TimeHandler.addEvent(function(two) { --two.landing; }, 2, two);
}
// Otherwise, the shell has reversed direction during land. Mario should die.
else {
// This prevents accidentally scoring Mario's hit
mario.death(mario);
}
return;
}
// Mario is kicking the shell (either hitting a still shell or jumping onto a shell)
if(two.xvel == 0 || mariojump) {
// Mario has has hit the shell in a dominant matter. You go, Mario!
two.counting = 0;
scoreMarioShell(one, two);
// The shell is peeking
if(two.peeking) {
two.peeking = false;
removeClass(two, "peeking");
two.height -= unitsized8;
updateSize(two);
}
// If the shell's xvel is 0 (standing still)
if(two.xvel == 0) {
if(shelltoleft) {
two.moveleft = true;
two.xvel = -two.speed;
} else {
two.moveleft = false;
two.xvel = two.speed;
}
// Make sure to know not to kill Mario too soon
++two.hitcount;
TimeHandler.addEvent(function(two) { --two.hitcount; }, 2, two);
}
// Otherwise set the xvel to 0
else two.xvel = 0;
// Mario is landing on the shell (movements, xvels already set)
if(mariojump) {
play("Kick");
// The shell is moving
if(!two.xvel) {
jumpEnemy(one, two);
one.yvel *= 2;
scoreMarioShell(one, two);
setBottom(one, two.top - unitsize, true);
}
// The shell isn't moving
else {
// shelltoleft ? setRight(two, one.left) : setLeft(two, one.right);
scoreMarioShell(one, two);
}
++two.landing;
two.shelltoleft = shelltoleft;
TimeHandler.addEvent(function(two) { --two.landing; }, 2, two);
}
}
else {
// Since the shell is moving and Mario isn't, if the xvel is towards Mario, that's a death
if(!two.hitcount && ((shelltoleft && two.xvel < 0) || (!shelltoleft && two.xvel > 0)))
one.death(one);
}
break;
// Shell hitting another shell
case "shell":
// If one is moving...
if(one.xvel != 0) {
// and two is also moving, knock off each other
if(two.xvel != 0) {
var temp = one.xvel;
shiftHoriz(one, one.xvel = two.xvel);
shiftHoriz(two, two.xvel = temp);
}
// otherwise one kills two
else {
score(two, 500);
two.death(two);
}
}
// otherwise two kills one
else if(two.xvel != 0) {
score(one, 500);
one.death(one);
}
break;
default:
switch(one.group) {
case "enemy":
if(two.xvel) {
// If the shell is moving, kill the enemy
if(one.type.split(" ")[0] == "koopa") {
// If the enemy is a koopa, make it a shell
// To do: automate this for things with shells (koopas, beetles)
var spawn = new Thing(Shell, one.smart);
addThing(spawn, one.left, one.bottom - spawn.height * unitsize);
killFlip(spawn);
killNormal(one);
} // Otherwise just kill it normally
else killFlip(one);
play("Kick");
score(one, findScore(two.enemyhitcount), true);
++two.enemyhitcount;
} // Otherwise the enemy just turns around
else one.moveleft = objectToLeft(one, two);
break;
case "item":
if(one.type == "shell") {
if(two.xvel) killFlip(one);
if(one.xvel) killFlip(two);
}
else return;
break;
}
break;
}
}
function moveShell(me) {
if(me.xvel != 0) return;
if(++me.counting == 350) {
addClass(me, "peeking");
me.peeking = true;
me.height += unitsized8;
updateSize(me);
} else if(me.counting == 490) {
var spawn = new Thing(me.spawntype, me.smart);
addThing(spawn, me.left, me.bottom - spawn.height * unitsize);
killNormal(me);
}
}
// Assuming one is Mario, two is item
function collideFriendly(one, two) {
if(one.type != "mario") return;
if(two.action) two.action(one);
two.death(two);
}
/*
* Enemies
*/
function jumpEnemy(me, enemy) {
if(me.keys.up) me.yvel = unitsize * -1.4;
else me.yvel = unitsize * -.7;
me.xvel *= .91;
play("Kick");
if(enemy.group != "item" || enemy.type == "shell")
score(enemy, findScore(me.jumpcount++ + me.jumpers), true);
++me.jumpers;
TimeHandler.addEvent(function(me) { --me.jumpers; }, 1, me);
}
function Goomba(me) {
me.width = me.height = 8;
me.speed = unitsize * .21;
me.toly = unitsize;
me.moveleft = me.noflip = true;
me.smart = false;
me.group = "enemy";
me.movement = moveSimple;
me.collide = collideEnemy;
me.death = killGoomba;
setCharacter(me, "goomba");
TimeHandler.addSpriteCycleSynched(me, [unflipHoriz, flipHoriz]);
}
// Big: true if it should skip squash (fire, shell, etc)
function killGoomba(me, big) {
if(!me.alive) return;
if(!big) {
var squash = new Thing(DeadGoomba);
addThing(squash, me.left, me.bottom - squash.height * unitsize);
TimeHandler.addEvent(killNormal, 21, squash);
killNormal(me);
}
else killFlip(me);
}
function DeadGoomba(me) {
me.width = 8;
me.height = 4;
me.movement = false;
me.nocollide = me.nocollide = true;
me.death = killNormal;
setSolid(me, "deadGoomba");
}
// If fly == true, then it's jumping
// If fly is otherwise true (an array), it's floating
function Koopa(me, smart, fly) {
me.width = 8;
me.height = 12;
me.speed = me.xvel = unitsize * .21;
me.moveleft = me.skipoverlaps = true;
me.group = "enemy";
me.smart = smart; // will it run off the edge
var name = "koopa";
name += (me.smart ? " smart" : " dumb");
if(me.smart) name+= " smart";
if(fly) {
name += " flying";
me.winged = true;
if(fly == true) {
me.movement = moveJumping;
me.jumpheight = unitsize * 1.17;
me.gravity = gravity / 2.8;
}
else {
me.movement = moveFloating;
me.ytop = me.begin = fly[0] * unitsize;
me.ybot = me.end = fly[1] * unitsize;
me.nofall = me.fly = true;
me.changing = me.xvel = 0;
me.yvel = me.maxvel = unitsized4;
}
}
else {
name += " regular";
if(me.smart) me.movement = moveSmart;
else me.movement = moveSimple;
}
me.collide = collideEnemy;
me.death = killKoopa;
setCharacter(me, name);
TimeHandler.addSpriteCycleSynched(me, ["one", "two"]);
me.toly = unitsizet2;
}
// Big: true if it should skip shell (fire, shell, etc)
function killKoopa(me, big) {
if(!me.alive) return;
var spawn;
if((big && big != 2) || me.winged) spawn = new Thing(Koopa, me.smart);
else spawn = new Thing(Shell, me.smart);
// Puts it on stack, so it executes immediately after upkeep
TimeHandler.addEvent(
function(spawn, me) {
addThing(spawn, me.left, me.bottom - spawn.height * unitsize);
spawn.moveleft = me.moveleft;
},
0,
spawn, me
);
killNormal(me);
if(big == 2) killFlip(spawn);
else return spawn;
}
function Pirhana(me, evil) {
me.width = 8;
me.height = 12;
me.counter = 0;
me.countermax = me.height * unitsize;
me.dir = unitsized8;
me.toly = unitsizet8;
me.nofall = me.deadly = me.nocollidesolid = me.repeat = true;
me.group = "enemy";
me.collide = collideEnemy;
me.death = killNormal;
me.movement = movePirhanaInit;
me.death = killPirhana;
setCharacter(me, "pirhana");
}
// The visual representation of a pirhana is visual_scenery; the collider is a character
function movePirhanaInit(me) {
me.hidden = true;
var scenery = me.visual_scenery = new Thing(Sprite, "Pirhana");
addThing(scenery, me.left, me.top);
TimeHandler.addSpriteCycle(scenery, ["one", "two"]);
me.movement = movePirhanaNew;
// Pirhanas start out minimal
movePirhanaNew(me, me.height * unitsize);
}
// Moving a pirhana moves both it and its scenery
function movePirhanaNew(me, amount) {
amount = amount || me.dir;
me.counter += amount;
shiftVert(me, amount);
shiftVert(me.visual_scenery, amount);
// Height is 0
if(me.counter <= 0 || me.counter >= me.countermax) {
me.movement = false;
me.dir *= -1;
TimeHandler.addEvent(movePirhanaRestart, 35, me);
}
}
function movePirhanaRestart(me) {
var marmid = getMidX(mario);
// If Mario's too close and counter == 0, don't do anything
if(me.counter >= me.countermax && marmid > me.left - unitsizet8 && marmid < me.right + unitsizet8) {
setTimeout(movePirhanaRestart, 7, me);
return;
}
// Otherwise start again
me.movement = movePirhanaNew;
}
function killPirhana(me) {
if(!me && !(me = this)) return;
killNormal(me);
killNormal(me.visual_scenery);
}
// Really just checks toly for pirhanas.
function marioAboveEnemy(mario, enemy) {
if(mario.bottom < enemy.top + enemy.toly) return true;
return false;
}
// Assuming one should generally be Mario/thing, two is enemy
function collideEnemy(one, two) {
// Check for life
if(!characterIsAlive(one) || !characterIsAlive(two)) return;
// Check for nocollidechar
if((one.nocollidechar && !two.mario) || (two.nocollidechar && !one.mario)) return;
// Items
if(one.group == "item") {
if(one.collide_primary) return one.collide(two, one);
// if(two.height < unitsized16 || two.width < unitsized16) return;
return;
}
// Mario on top of enemy
if(!map.underwater && one.mario && ((one.star && !two.nostar) || (!two.deadly && objectOnTop(one, two)))) {
// Enforces toly
if(marioAboveEnemy(one, two)) return;
// Mario is on top of them (or star):
if(one.mario && !one.star) TimeHandler.addEvent(function(one, two) { jumpEnemy(one, two); }, 0, one, two);
else two.nocollide = true;
// Kill the enemy
//// If killed returns a Thing, then it's a shell
//// Make sure Mario isn't immediately hitting the shell
var killed = two.death(two, one.star * 2);
if(one.star) scoreEnemyStar(two);
else {
scoreEnemyStomp(two);
/*TimeHandler.addEvent(function(one, two) { */setBottom(one, min(one.bottom, two.top + unitsize));/* }, 0, one, two);*/
}
// Make Mario have the hopping thing
addClass(one, "hopping");
removeClasses(one, "running skidding jumping one two three")
// addClass(one, "running three");
one.hopping = true;
if(mario.power == 1) setMarioSizeSmall(one);
}
// Mario getting hit by an enemy
else if(one.mario) {
if(!marioAboveEnemy(one, two)) one.death(one);
}
// Two regular characters colliding
else two.moveleft = !(one.moveleft = objectToLeft(one, two));
}
function Podoboo(me, jumpheight) {
me.width = 7;
me.height = 8;
me.deadly = me.nofall = me.nocollidesolid = me.nofire = true;
me.gravity = map.gravity / 2.1;
me.jumpheight = (jumpheight || 64) * unitsize;
me.speed = -map.maxyvel;
me.movement = movePodobooInit;
me.collide = collideEnemy;
me.betweentime = 70;
setCharacter(me, "podoboo");
}
function movePodobooInit(me) {
if(!characterIsAlive(me)) return;
// For the sake of the editor, flip this & make it hidden on the first movement
// flipVert(me);
me.hidden = true;
me.heightnorm = me.top;
me.heightfall = me.top - me.jumpheight;
TimeHandler.addEvent(podobooJump, me.betweentime, me);
me.movement = false;
}
function podobooJump(me) {
if(!characterIsAlive(me)) return;
unflipVert(me);
me.yvel = me.speed + me.gravity;
me.movement = movePodobooUp;
me.hidden = false;
// Sadly, this appears to be occasionally necessary
setThingSprite(me);
}
function movePodobooUp(me) {
shiftVert(me, me.speed, true);
if(me.top - gamescreen.top > me.heightfall) return;
me.nofall = false;
me.movement = movePodobooSwitch;
}
function movePodobooSwitch(me) {
if(me.yvel <= 0) return;
flipVert(me);
me.movement = movePodobooDown;
}
function movePodobooDown(me) {
if(me.top < me.heightnorm) return;
setTop(me, me.heightnorm, true);
me.movement = false;
me.nofall = me.hidden = true;
me.heightfall = me.top - me.jumpheight;
TimeHandler.addEvent(podobooJump, me.betweentime, me);
}
function HammerBro(me) {
me.width = 8;
me.height = 12;
me.group = "enemy";
me.collide = collideEnemy;
me.statex = me.counter = me.statey = me.counterx = me.countery = me.level = me.throwcount = 0;
me.death = killFlip;
me.movement = moveHammerBro;
setCharacter(me, "hammerbro");
me.gravity = gravity / 2;
TimeHandler.addSpriteCycle(me, ["one", "two"]);
TimeHandler.addEvent(throwHammer, 35, me, 7);
TimeHandler.addEventInterval(jumpHammerBro, 140, Infinity, me);
}
function moveHammerBro(me) {
// Slide side to side
me.xvel = Math.sin(Math.PI * (me.counter += .007)) / 2.1;
// Make him turn to look at mario if needed
lookTowardMario(me);
// If falling, don't collide with solids
me.nocollidesolid = me.yvel < 0 || me.falling;
}
function throwHammer(me, count) {
if(!characterIsAlive(me) || me.right < -unitsizet32) return;
if(count != 3) {
switchClass(me, "thrown", "throwing");
}
TimeHandler.addEvent(function(me) {
if(count != 3) {
if(!characterIsAlive(me)) return;
// Throw the hammer...
switchClass(me, "throwing", "thrown");
// var hammer = new Thing(Hammer, me.lookleft);
addThing(new Thing(Hammer, me.lookleft), me.left - unitsizet2, me.top - unitsizet2);
// ...and go again
}
if(count > 0) TimeHandler.addEvent(throwHammer, 7, me, --count);
else {
TimeHandler.addEvent(throwHammer, 70, me, 7);
removeClass(me, "thrown");
}
}, 14, me);
}
function jumpHammerBro(me) {
if(!characterIsAlive(me)) return true; // finish
if(!me.resting) return; // just skip
// If it's ok, jump down
if(map.floor - (me.bottom / unitsize) >= jumplev1 - 2 && me.resting.name != "floor" && Math.floor(Math.random() * 2)) {
me.yvel = unitsize * -.7;
me.falling = true;
TimeHandler.addEvent(function(me) { me.falling = false; }, 42, me);
}
// Otherwise, jump up
else me.yvel = unitsize * -2.1;
me.resting = false;
}
function Hammer(me, left) {
me.width = me.height = 8;
me.nocollidesolid = me.nocollidechar = me.deadly = me.nofire = true;
me.collide = collideEnemy;
me.yvel = -unitsize * 1.4;
me.xvel = unitsize / 1.4;
if(left) me.xvel *= -1;
me.gravity = gravity / 2.1;
setCharacter(me, "hammer");
TimeHandler.addSpriteCycle(me, ["one", "two", "three", "four"], 3);
}
function Cannon(me, height, nofire) {
me.width = 8;
me.height = (height || 1) * 8;
me.spriteheight = 16;
if(!nofire) me.movement = moveCannonInit;
me.timer = 117;
me.repeat = true;
setSolid(me, "cannon");
}
function moveCannonInit(me) {
TimeHandler.addEventInterval(
function(me) {
if(mario.right > me.left - unitsizet8 && mario.left < me.right + unitsizet8)
return; // don't fire if Mario is too close
var spawn = new Thing(BulletBill);
if(objectToLeft(mario, me)) {
addThing(spawn, me.left, me.top);
spawn.direction = spawn.moveleft = true;
spawn.xvel *= -1;
flipHoriz(spawn);
}
else addThing(spawn, me.left + me.width, me.top);
playLocal("Bump", me.right);
}, 270, Infinity, me);
me.movement = false;
}
function BulletBill(me) {
me.width = 8; me.height = 7;
me.group = "enemy";
me.nofall = me.nofire = me.nocollidesolid = me.nocollidechar = true;
me.speed = me.xvel = unitsized2;
me.movement = moveSimple;
me.collide = collideEnemy;
me.death = killFlip;
setCharacter(me, "bulletbill");
}
function Bowser(me, hard) {
me.width = me.height = 16;
me.speed = .28 * unitsize;
me.gravity = gravity / 2.8;
me.deadly = me.dx = me.lookleft = me.nokillend = me.skipoverlaps = true;
me.moveleft = me.smart = me.movecount = me.jumpcount = me.firecount = me.deathcount = 0;
me.killonend = freezeBowser;
me.counter = -.7;
me.group = "enemy";
me.movement = moveBowserInit;
me.collide = collideEnemy;
me.death = killBowser;
setCharacter(me, "bowser");
TimeHandler.addSpriteCycle(me, ["one", "two"]);
if(hard) TimeHandler.addEvent(throwHammer, 35, me, 7);
}
function moveBowserInit(me) {
TimeHandler.addEventInterval(bowserJumps, 117, Infinity, me);
TimeHandler.addEventInterval(bowserFires, 280, Infinity, me);
TimeHandler.addEventInterval(bowserFires, 350, Infinity, me);
TimeHandler.addEventInterval(bowserFires, 490, Infinity, me);
me.movement = moveBowser;
}
function moveBowser(me) {
if(!characterIsAlive(mario)) return;
lookTowardMario(me);
if(me.lookleft) me.xvel = Math.sin(Math.PI * (me.counter += .007)) / 1.4;
else me.xvel = min(me.xvel + .07, .84);
}
function bowserJumps(me) {
if(!characterIsAlive(me)) return true;
if(!me.resting || !me.lookleft) return;
me.yvel = unitsize * -1.4;
me.resting = false;
// If there is a platform, don't bump into it
me.nocollidesolid = true;
TimeHandler.addEventInterval(function(me) {
if(me.yvel > unitsize) {
me.nocollidesolid = false;
return true;
}
}, 3, Infinity, me);
}
function bowserFires(me) {
if(!characterIsAlive(me) || !characterIsAlive(mario)) return true;
if(!me.lookleft) return;
// Close the mouth
addClass(me, "firing");
playLocal("Bowser Fires", me.left);
// After a little bit, open and fire
TimeHandler.addEvent(function(me) {
var top = me.top + unitsizet4,
fire = new Thing(BowserFire, roundDigit(mario.bottom, unitsizet8));
removeClass(me, "firing");
addThing(fire, me.left - unitsizet8, top);
play("Bowser Fires");
}, 14, me);
}
// This is for when Fiery Mario kills bowser - the normal one is listed under the castle things
function killBowser(me, big) {
if(big) {
me.nofall = false;
return killFlip(me);
}
if(++me.deathcount == 5) {
me.yvel = me.speed = me.movement = 0;
killFlip(me, 350);
score(me, 5000);
}
}
// For when the axe is hit
function freezeBowser(me) {
me.movement = false;
thingStoreVelocity(me);
}
// Each fireball leaves his mouth, and while moving horizontally, shifts its yloc
function BowserFire(me, ylev) {
me.width = 12;
me.height = 4;
me.xvel = unitsize * -.63;
me.deadly = me.nofall = me.nocollidesolid = me.nofire = true;
me.collide = collideEnemy;
if(ylev) {
me.ylev = ylev;
me.movement = moveFlying;
}
setCharacter(me, "bowserfire");
TimeHandler.addSpriteCycle(me, [unflipVert, flipVert]);
}
function moveFlying(me) {
if(round(me.bottom) == round(me.ylev)) {
me.movement = false;
return;
}
shiftVert(me, min(max(0, me.ylev - me.bottom), unitsize));
}
function WaterBlock(me, width) {
me.height = 16;
me.width = width;
me.spritewidth = me.spriteheight = 1 / scale;
me.repeat = true;
setSolid(me, "water-block");
}
function Blooper(me) {
me.width = 8;
me.height = 12;
me.nocollidesolid = me.nofall = me.moveleft = 1;
me.squeeze = me.counter = 0;
me.speed = unitsized2;
me.xvel = me.speedinv = -unitsized4;
me.movement = moveBlooper;
me.collide = collideEnemy;
me.death = killFlip;
setCharacter(me, "blooper");
}
// Normally goes up at increasing rate
// Every X seconds, squeezes to go down
//// Minimum Y seconds, continues if Mario is below until bottom is 8 above floor
function moveBlooper(me) {
switch(me.counter) {
case 56: me.squeeze = true; ++me.counter; break;
case 63: squeezeBlooper(me); break;
default: ++me.counter; break;
}
if(me.squeeze) me.yvel = max(me.yvel + .021, .7); // going down
else me.yvel = min(me.yvel - .035, -.7); // going up
shiftVert(me, me.yvel, true);
if(!me.squeeze) {
if(mario.left > me.right + unitsizet8) {
// Go to the right
me.xvel = min(me.speed, me.xvel + unitsized32);
}
else if(mario.right < me.left - unitsizet8) {
// Go to the left
me.xvel = max(me.speedinv, me.xvel - unitsized32);
}
}
}
function squeezeBlooper(me) {
if(me.squeeze != 2) addClass(me, "squeeze");
// if(!me.squeeze) me.yvel = 0;
me.squeeze = 2;
me.xvel /= 1.17;
setHeight(me, 10, true, true);
// (104 (map.floor) - 12 (blooper.height) - 2) * unitsize
if(me.top > mario.bottom || me.bottom > 360) unsqueezeBlooper(me);
}
function unsqueezeBlooper(me) {
me.squeeze = false;
removeClass(me, "squeeze");
me.counter = 0;
setHeight(me, 12, true, true);
// me.yvel /= 3;
}
// Red cheepcheeps are faster
function CheepCheep(me, red, jumping) {
me.width = me.height = 8;
me.group = "enemy";
var name = "cheepcheep " + (red ? "red" : "");
// Doubled for fun! (also editor checking)
me.red = red;
setCheepVelocities(me);
if(jumping) {
name += " jumping";
me.jumping = true;
me.movement = moveCheepJumping;
}
else me.movement = moveCheepInit;
me.nofall = me.nocollidesolid = me.nocollidechar = true;
me.death = killFlip;
me.collide = collideEnemy;
setCharacter(me, name);
TimeHandler.addSpriteCycle(me, ["one", "two"]);
}
function setCheepVelocities(me) {
if(me.red) {
me.xvel = -unitsized4;
me.yvel = unitsize / -24;
} else {
me.xvel = unitsize / -6;
me.yvel = -unitsized32;
}
}
function moveCheepInit(me) {
setCheepVelocities(me);
if(me.top < mario.top) me.yvel *= -1;
moveCheep(me);
me.movement = moveCheep;
}
function moveCheep(me) {
shiftVert(me, me.yvel);
}
function moveCheepJumping(me) {
shiftVert(me, me.yvel += unitsize / 14);
}
function startCheepSpawn() {
return map.zone_cheeps = TimeHandler.addEventInterval(
function() {
if(!map.zone_cheeps) return true;
var spawn = new Thing(CheepCheep, true, true);
addThing(spawn, Math.random() * mario.left * mario.maxspeed / unitsized2, gamescreen.height * unitsize);
spawn.xvel = Math.random() * mario.maxspeed;
spawn.yvel = unitsize * -2.33;
flipHoriz(spawn);
spawn.movement = function(me) {
if(me.top < ceilmax) me.movement = moveCheepJumping;
else shiftVert(me, me.yvel);
};
}, 21, Infinity
);
}
function Bubble(me) {
me.width = me.height = 2;
me.nofall = me.nocollide = true;
me.movement = function(me) {
me.top < unitsizet16 ? killNormal(me) : shiftVert(me, me.yvel);
};
me.yvel = -unitsized4;
setCharacter(me, "bubble");
}
// Typically at height ??
function Lakitu(me, norepeat) {
me.width = 8;
me.height = 12;
me.nofall = me.noshiftx = me.nocollidesolid = true;
me.mariodiff = me.counter = 0;
me.dir = -1;
me.norepeat = norepeat;
me.mariodiff = unitsizet16;
me.group = "enemy";
me.collide = collideEnemy;
me.movement = moveLakituInit;
me.death = killLakitu;
setCharacter(me, "lakitu out");
map.has_lakitu = me;
}
// The lakitu's position starts to the right of mario ...
function moveLakituInit(me) {
if(map.has_lakitu && me.norepeat) return killNormal(me);
TimeHandler.addEventInterval(function(me) {
if(me.alive) throwSpiny(me);
else return true;
}, 140, Infinity, me);
me.movement = moveLakituInit2;
moveLakituInit2(me);
map.has_lakitu = me;
}
function moveLakituInit2(me) {
if(me.right < mario.left) {
moveLakitu(me);
me.movement = moveLakitu;
map.lakitu = me;
return true;
}
shiftHoriz(me, -unitsize);
}
// Then, once it's close enough, is always relative to mario.
// This fluctuates between +/-32 (* unitsize)
function moveLakitu(me) {
// If mario is moving quickly to the right, move in front of him and stay there
if(mario.xvel > unitsized8 && mario.left > gamescreen.width * unitsized2) {
if(me.left < mario.right + unitsizet16) {
// To the 'left' of mario
slideToXLoc(me, mario.right + unitsizet32 + mario.xvel, mario.maxspeed * 1.4);
me.counter = 0;
}
}
// Otherwise, creepily orbit around him
else {
// me.xvel = 0;
me.counter += .007;
slideToXLoc(me, mario.left + mario.xvel + Math.sin(Math.PI * me.counter) * 117, mario.maxspeed * .7);
}
// log("moveLakitu after: " + (me.right - me.left) + "\n");
}
function throwSpiny(me) {
if(!characterIsAlive(me)) return false;
switchClass(me, "out", "hiding");
TimeHandler.addEvent(function(me) {
if(me.dead) return false;
var spawn = new Thing(SpinyEgg);
addThing(spawn, me.left, me.top);
spawn.yvel = unitsize * -2.1;
switchClass(me, "hiding", "out");
}, 21, me);
}
function killLakitu(me) {
delete me.noscroll;
killFlip(me);
}
function Spiny(me) {
me.width = me.height = 8;
me.group = "enemy";
me.speed = unitsize * .21;
me.deadly = me.moveleft = true;
me.smart = false;
me.death = killFlip;
me.collide = collideEnemy;
me.movement = moveSimple;
setCharacter(me, "spiny");
TimeHandler.addSpriteCycle(me, ["one", "two"]);
}
function SpinyEgg(me) {
me.height = 8; me.width = 7;
me.group = "enemy";
me.deadly = true;
me.movement = moveSpinyEgg;
me.spawntype = Spiny;
me.spawner = me.death = createSpiny;
me.collide = collideEnemy;
setCharacter(me, "spinyegg");
TimeHandler.addSpriteCycle(me, ["one", "two"]);
}
function moveSpinyEgg(me) {
if(me.resting) createSpiny(me);
}
function createSpiny(me) {
var spawn = new Thing(Spiny);
addThing(spawn, me.left, me.top);
spawn.moveleft = objectToLeft(mario, spawn);
killNormal(me);
}
function Beetle(me) {
me.width = me.height = 8;
me.group = "enemy";
me.speed = me.xvel = unitsize * .21;
me.moveleft = me.nofire = true;
me.smart = false;
me.collide = collideEnemy;
me.movement = moveSmart;
me.death = killBeetle;
setCharacter(me, "beetle");
// me.toly = unitsizet8;
TimeHandler.addSpriteCycleSynched(me, ["one", "two"]);
}
// Big: true if it should skip shell (fire, shell, etc)
function killBeetle(me, big) {
if(!me.alive) return;
var spawn;
if(big && big != 2) spawn = new Thing(Koopa, me.smart);
else spawn = new Thing(BeetleShell, me.smart);
// Puts it on stack, so it executes immediately after upkeep
TimeHandler.addEvent(
function(spawn, me) {
addThing(spawn, me.left, me.bottom - spawn.height * unitsize);
spawn.moveleft = me.moveleft;
},
0,
spawn, me
);
killNormal(me);
if(big == 2) killFlip(spawn);
else return spawn;
}
function BeetleShell(me) {
me.width = me.height = 8;
me.nofire = true;
me.group = "item";
me.speed = unitsizet2;
me.moveleft = me.xvel = me.move = me.hitcount = me.peeking = me.counting = me.landing = me.enemyhitcount = 0;
me.movement = moveShell;
me.collide = hitShell;
me.death = killFlip;
me.spawntype = Beetle;
setCharacter(me, "shell beetle");
}
function Coin(me, solid) {
me.group = "coin";
me.width = 5;
me.height = 7;
me.nofall = me.coin = me.nofire = me.nocollidechar = me.nokillend = me.onlyupsolids = me.skipoverlaps = true;
me.tolx = 0;
me.toly = unitsized2;
me.collide = hitCoin;
me.animate = coinEmerge;
me.death = killNormal;
setCharacter(me, "coin one");
TimeHandler.addSpriteCycleSynched(me, ["one", "two", "three", "two", "one"]);
// Enabling solid allows this to deliberately be placed behind characters, for visual reasons (like in 1-3)
if(solid) me.movement = coinBecomesSolid;
}
function coinBecomesSolid(me) {
switchContainers(me, characters, solids);
me.movement = false;
}
function hitCoin(me, coin) {
if(!me.mario) return;
play("Coin");
score(me, 200, false);
gainCoin();
killNormal(coin);
}
function gainCoin() {
if(++data.coins.amount >= 100) {
data.coins.amount = 0;
gainLife();
}
updateDataElement(data.coins);
}
function coinEmerge(me, solid) {
play("Coin");
removeClass(me, "still");
switchContainers(me, characters, scenery);
score(me, 200, false);
gainCoin();
me.nocollide = me.alive = me.nofall = me.emerging = true;
determineThingQuadrants(me);
if(me.blockparent) me.movement = coinEmergeMoveParent;
else me.movement = coinEmergeMove;
me.yvel = -unitsize;
TimeHandler.addEvent(function(me) { me.yvel *= -1; }, 25, me);
TimeHandler.addEvent(function(me) {
killNormal(me);
deleteThing(me, scenery, scenery.indexOf(me));
}, 49, me);
TimeHandler.addEventInterval(coinEmergeMovement, 1, Infinity, me, solid);
TimeHandler.clearClassCycle(me, 0);
addClass(me, "anim");
TimeHandler.addSpriteCycle(me, ["anim1", "anim2", "anim3", "anim4", "anim3", "anim2"], 0, 5);
}
function coinEmergeMovement(me, solid) {
if(!me.alive) return true;
shiftVert(me, me.yvel);
// if(solid && solid.alive && me.bottom > solid.bottom || me.top > solid.top) {
// killNormal(me);
// return true;
// }
}
function coinEmergeMove(me) {
shiftVert(me, me.yvel, true);
}
function coinEmergeMoveParent(me) {
if(me.bottom >= me.blockparent.bottom) killNormal(me);
else shiftVert(me, me.yvel, true);
}
/*
* Mario
*/
function Mario(me) {
setMarioSizeSmall(me);
me.walkspeed = unitsized2;
me.canjump = me.nofiredeath = me.nofire = me.mario = me.nokillend = 1;
me.numballs = me.moveleft = me.skidding = me.star = me.dying = me.nofall = me.maxvel = me.paddling = me.jumpers = me.landing = 0;
me.running = ''; // Evalues to false for cycle checker
me.power = data.mariopower; // 1 for normal, 2 for big, 3 for fiery
me.maxspeed = me.maxspeedsave = unitsize * 1.35; // Really only used for timed animations
me.scrollspeed = unitsize * 1.75;
me.keys = new Keys();
me.fire = marioFires;
me.movement = moveMario;
me.death = killMario;
setCharacter(me, "mario normal small still");
me.tolx = unitsizet2;
me.toly = 0;
me.gravity = map.gravity;
if(map.underwater) {
me.swimming = true;
TimeHandler.addSpriteCycle(me, ["swim1", "swim2"], "swimming", 5);
}
}
function placeMario(xloc, yloc) {
clearOldMario();
window.mario = new Thing(Mario);
var adder = addThing(mario, xloc || unitsizet16, yloc || (map.floor - mario.height) * unitsize);
if(data.mariopower >= 2) {
marioGetsBig(mario, true);
if(data.mariopower == 3) marioGetsFire(mario, true);
}
return adder;
}
function clearOldMario() {
if(!window.mario) return;
// if(mario.element) removeElement(mario);
mario.alive = false;
mario.dead = true;
}
function Keys() {
// Run: 0 for no, 1 for right, -1 for left
// Crouch: 0 for no, 1 for yes
// Jump: 0 for no, jumplev = 1 through jumpmax for yes
this.run = this.crouch = this.jump = this.jumplev = this.sprint = 0;
}
// Stores .*vel under .*velold for shroom-style events
function thingStoreVelocity(me, keepmove) {
me.xvelOld = me.xvel || 0;
me.yvelOld = me.yvel || 0;
me.nofallOld = me.nofall || false;
me.nocollideOld = me.nocollide || false;
me.movementOld = me.movement || me.movementOld;
me.nofall = me.nocollide = true;
me.xvel = me.yvel = false;
if(!keepmove) me.movement = false;
}
// Retrieves .*vel from .*velold
function thingRetrieveVelocity(me, novel) {
if(!novel) {
me.xvel = me.xvelOld || 0;
me.yvel = me.yvelOld || 0;
}
me.movement = me.movementOld || me.movement;
me.nofall = me.nofallOld || false;
me.nocollide = me.nocollideOld || false;
}
function removeCrouch() {
mario.crouching = false;
mario.toly = mario.tolyold || 0;
if(mario.power != 1) {
removeClass(mario, "crouching");
mario.height = 16;
updateBottom(mario, 0);
updateSize(mario);
}
}
function marioShroom(me) {
if(me.shrooming) return;
play("Powerup");
score(me, 1000, true);
if(me.power == 3) return;
me.shrooming = true;
(++me.power == 2 ? marioGetsBig : marioGetsFire)(me);
storeMarioStats();
}
// These three modifiers don't change power levels.
function marioGetsBig(me, noanim) {
setMarioSizeLarge(me);
me.keys.down = 0;
removeClasses(mario, "crouching small");
updateBottom(me, 0);
updateSize(me);
if(!noanim) {
// pause();
// Mario cycles through 'shrooming1', 'shrooming2', etc.
addClass(mario, "shrooming");
var stages = [1,2,1,2,3,2,3];
for(var i = stages.length - 1; i >= 0; --i)
stages[i] = "shrooming" + stages[i];
// Clear Mario's movements
thingStoreVelocity(mario);
// The last event in stages clears it, resets Mario's movements, and stops
stages.push(function(me, settings) {
me.shrooming = settings.length = 0;
addClass(me, "large");
removeClasses(me, "shrooming shrooming3");
thingRetrieveVelocity(mario);
return true;
});
TimeHandler.addSpriteCycle(me, stages, "shrooming", 6);
}
else addClass(me, "large");
}
function marioGetsSmall(me) {
var bottom = mario.bottom;
// pause();
me.keys.down = 0;
thingStoreVelocity(me);
addClass(me, "small");
flicker(me);
// Step one
removeClasses(mario, "running skidding jumping fiery");
addClass(mario, "paddling");
// Step two (t+21)
TimeHandler.addEvent(function(mario) {
removeClass(mario, "large");
setMarioSizeSmall(mario);
setBottom(mario, bottom - unitsize);
}, 21, mario);
// Step three (t+42)
TimeHandler.addEvent(function(mario) {
thingRetrieveVelocity(mario, false);
mario.nocollidechar = true;
removeClass(mario, "paddling");
if(mario.running || mario.xvel) addClass(mario, "running");
TimeHandler.addEvent(setThingSprite, 1, mario);
}, 42, mario);
// Step four (t+70);
TimeHandler.addEvent(function(mario) {
mario.nocollidechar = false;
}, 70, mario);
}
function marioGetsFire(me) {
removeClass(me, "intofiery");
addClass(me, "fiery");
mario.shrooming = false;
}
function setMarioSizeSmall(me) {
setSize(me, 8, 8, true);
updateSize(me);
}
function setMarioSizeLarge(me) {
setSize(me, 8, 16, true);
updateSize(me);
}
// To do: add in unitsize measurement?
function moveMario(me) {
// Not jumping
if(!me.keys.up) me.keys.jump = 0;
// Jumping
else if(me.keys.jump > 0 && (me.yvel <= 0 || map.underwater) ) {
if(map.underwater) marioPaddles(me);
if(me.resting) {
if(me.resting.xvel) me.xvel += me.resting.xvel;
me.resting = false;
}
// Jumping, not resting
else {
if(!me.jumping && !map.underwater) {
switchClass(me, "running skidding", "jumping");
}
me.jumping = true;
}
if(!map.underwater) {
var dy = unitsize / (pow(++me.keys.jumplev, map.jumpmod - .0014 * me.xvel));
me.yvel = max(me.yvel - dy, map.maxyvelinv);
}
}
// Crouching
if(me.keys.crouch && !me.crouching && me.resting) {
if(me.power != 1) {
me.crouching = true;
addClass(me, "crouching");
me.height = 11;
me.tolyold = me.toly;
me.toly = unitsizet4;
updateBottom(me, 0);
updateSize(me);
}
// Pipe movement
if(me.resting.actionTop)
me.resting.actionTop(me, me.resting, me.resting.transport);
}
// Running
var decel = 0 ; // (how much to decrease)
// If a button is pressed, hold/increase speed
if(me.keys.run != 0 && !me.crouching) {
var dir = me.keys.run,
// No sprinting underwater
sprinting = (me.keys.sprint && !map.underwater) || 0,
adder = dir * (.098 * (sprinting + 1));
// Reduce the speed, both by subtracting and dividing a little
me.xvel += adder || 0;
me.xvel *= .98;
decel = .0007;
// If you're accelerating in the opposite direction from your current velocity, that's a skid
if(/*sprinting && */signBool(me.keys.run) == me.moveleft) {
if(!me.skidding) {
addClass(me, "skidding");
me.skidding = true;
}
}
// Otherwise make sure you're not skidding
else if(me.skidding) {
removeClass(me, "skidding");
me.skidding = false;
}
}
// Otherwise slow down a bit/*, with a little more if crouching*/
else {
me.xvel *= (.98/* - Boolean(me.crouching) * .07*/);
decel = .035;
}
if(me.xvel > decel) me.xvel-=decel;
else if(me.xvel < -decel) me.xvel+=decel;
else if(me.xvel!=0) {
me.xvel = 0;
if(!window.nokeys && me.keys.run==0) {
if(me.keys.left_down)me.keys.run=-1;
else if(me.keys.right_down)me.keys.run=1;
}
}
// Movement mods
// Slowing down
if(Math.abs(me.xvel) < .14) {
if(me.running) {
me.running = false;
if(mario.power == 1) setMarioSizeSmall(me);
removeClasses(me, "running skidding one two three");
addClass(me, "still");
TimeHandler.clearClassCycle(me, "running");
}
}
// Not moving slowly
else if(!me.running) {
me.running = true;
switchClass(me, "still", "running");
marioStartRunningCycle(me);
if(me.power == 1) setMarioSizeSmall(me);
}
if(me.xvel > 0) {
me.xvel = min(me.xvel, me.maxspeed);
if(me.moveleft && (me.resting || map.underwater)) {
unflipHoriz(me);
me.moveleft = false;
}
}
else if(me.xvel < 0) {
me.xvel = max(me.xvel, me.maxspeed * -1);
if(!me.moveleft && (me.resting || map.underwater)) {
flipHoriz(me);
me.moveleft = true;
}
}
// Resting stops a bunch of other stuff
if(me.resting) {
// Hopping
if(me.hopping) {
removeClass(me, "hopping");
if(me.xvel) addClass(me, "running");
me.hopping = false;
}
// Jumping
me.keys.jumplev = me.yvel = me.jumpcount = 0;
if(me.jumping) {
me.jumping = false;
removeClass(me, "jumping");
if(me.power == 1) setMarioSizeSmall(me);
addClass(me, abs(me.xvel) < .14 ? "still" : "running");
}
// Paddling
if(me.paddling) {
me.paddling = me.swimming = false;
removeClasses(me, "paddling swim1 swim2");
TimeHandler.clearClassCycle(me, "paddling");
addClass(me, "running");
}
}
if(isNaN(me.xvel)) debugger;
}
// Gives Mario visual running
function marioStartRunningCycle(me) {
// setMarioRunningCycler sets the time between cycles
me.running = TimeHandler.addSpriteCycle(me, ["one", "two", "three", "two"], "running", setMarioRunningCycler);
}
// Used by Mario's running cycle to determine how fast he should switch between sprites
function setMarioRunningCycler(event) {
event.timeout = 5 + ceil(mario.maxspeedsave - abs(mario.xvel));
}
function marioPaddles(me) {
if(!me.paddling) {
removeClasses(me, /*"running */"skidding paddle1 paddle2 paddle3 paddle4 paddle5");
addClass(me, "paddling");
TimeHandler.clearClassCycle(me, "paddling_cycle");
TimeHandler.addSpriteCycle(me, ["paddle1", "paddle2", "paddle3", "paddle3", "paddle2", "paddle1", function() { return me.paddling = false; }], "paddling_cycle", 5);
}
me.paddling = me.swimming = true;
me.yvel = unitsize * -.84;
}
function marioBubbles() {
var bubble = new Thing(Bubble);
addThing(bubble, mario.right, mario.top);
// TimeHandler.addEvent(killNormal, 140, bubble);
}
function moveMarioVine(me) {
var attached = me.attached;
if(me.bottom < attached.top) return unattachMario(me);
if(me.keys.run == me.attachoff) {
while(objectsTouch(me, attached))
shiftHoriz(me, me.keys.run, true);
return unattachMario(me);
}
// If Mario is moving up, simply move up
if(me.keys.up) {
me.animatednow = true;
shiftVert(me, unitsized4 * -1, true);
}
// If mario is moving down, move down and check for unattachment
else if(me.keys.crouch) {
me.animatednow = true;
shiftVert(me, unitsized2, true);
if(me.bottom > attached.bottom - unitsizet4) return unattachMario(me);
}
else me.animatednow = false;
if(me.animatednow && !me.animated) {
addClass(me, "animated");
} else if(!me.animatednow && me.animated) {
removeClass(me, "animated");
}
me.animated = me.animatednow;
if(me.bottom < -16) { // ceilmax (104) - ceillev (88)
locMovePreparations(me);
if(!attached.locnum && map.random) goToTransport(["Random", "Sky", "Vine"]);
else shiftToLocation(attached.locnum);
}
}
function unattachMario(me) {
me.movement = moveMario;//me.movementsave;
removeClasses(me, "climbing", "animated");
TimeHandler.clearClassCycle(me, "climbing");
me.yvel = me.skipoverlaps = me.attachoff = me.nofall = me.climbing = me.attached = me.attached.attached = false;
me.xvel = me.keys.run;
}
function marioHopsOff(me, solid, addrun) {
removeClasses(me, "climbing running");
addClass(me, "jumping");
console
me.piping = me.nocollide = me.nofall = me.climbing = false;
me.gravity = gravity / 4;
me.xvel = 3.5;
me.yvel = -3.5;
TimeHandler.addEvent(function(me) {
unflipHoriz(me);
me.gravity = gravity;
me.movement = moveMario;
me.attached = false;
if(addrun) {
addClass(me, "running")
marioStartRunningCycle(me);
}
}, 21, me);
}
function marioFires() {
if(mario.numballs >= 2) return;
++mario.numballs;
addClass(mario, "firing");
var ball = new Thing(FireBall, mario.moveleft, true);
ball.yvel = unitsize
addThing(ball, mario.right + unitsized4, mario.top + unitsizet8);
if(mario.moveleft) setRight(ball, mario.left - unitsized4, true);
ball.animate(ball);
ball.ondelete = fireDeleted;
TimeHandler.addEvent(function(mario) { removeClass(mario, "firing"); }, 7, mario);
}
function emergeFire(me) {
play("Fireball");
}
function marioStar(me) {
if(me.star) return;
++me.star;
play("Powerup");
playTheme("Star", true);
TimeHandler.addEvent(marioRemoveStar, 560, me);
switchClass(me, "normal", "star");
TimeHandler.addSpriteCycle(me, ["star1", "star2", "star3", "star4"], "star", 5);
}
function marioRemoveStar(me) {
if(!me.star) return;
--me.star;
removeClasses(me, "star star1 star2 star3 star4");
TimeHandler.clearClassCycle(me, "star");
addClass(me, "normal");
playTheme();
}
// Big means it must happen: 2 means no animation
function killMario(me, big) {
if(!me.alive || me.flickering || me.dying) return;
// If this is an auto kill, it's for rizzles
if(big == 2) {
notime = true;
me.dead = me.dying = true;
}
// Otherwise it's just a regular (enemy, time, etc.) kill
else {
// If Mario can survive this, just power down
if(!big && me.power > 1) {
play("Power Down");
me.power = 1;
storeMarioStats();
return marioGetsSmall(me);
}
// Otherwise, if this isn't a big one, animate a death
else if(big != 2) {
// Make this look dead
TimeHandler.clearAllCycles(me);
setSize(me, 7.5, 7, true);
updateSize(me);
setClass(me, "character mario dead");
// Pause some things
nokeys = notime = me.dying = true;
thingStoreVelocity(me);
// Make this the top of characters
containerForefront(me, characters);
// After a tiny bit, animate
TimeHandler.addEvent(function(me) {
thingRetrieveVelocity(me, true);
me.nocollide = true;
me.movement = me.resting = false;
me.gravity = gravity / 2.1;
me.yvel = unitsize * -1.4;
}, 7, me);
}
}
// Clear and reset
pauseAllSounds();
if(!window.editing) play("Mario Dies");
me.nocollide = me.nomove = nokeys = 1;
--data.lives.amount;
if(!map.random) data.score.amount = data.scoreold;
// If it's in editor, (almost) immediately set map
if(window.editing) {
setTimeout(function() {
editorSubmitGameFuncPlay();
editor.playing = editor.playediting = true;
}, 35 * timer);
}
// If the map is normal, or failing that a game over is reached, timeout a reset
else if(!map.random || data.lives.amount <= 0) {
window.reset = setTimeout(data.lives.amount ? setMap : gameOver, timer * 280);
}
// Otherwise it's random; spawn him again
else {
nokeys = notime = false;
updateDataElement(data.score);
updateDataElement(data.lives);
// placeMario(unitsizet16, unitsizet8 * -1 + (map.underwater * unitsize * 24));
TimeHandler.addEvent(function() {
marioDropsIn();
playTheme();
// }, 7 * (map.respawndist || 17));
}, 117);
}
}
// Used by random maps to respawn
function marioDropsIn() {
// Clear and place Mario
clearOldMario();
placeMario(unitsizet16, unitsizet8 * -1 + (map.underwater * unitsize * 24));
flicker(mario);
// Give a Resting Stone for him to land, unless it's underwater...
if(!map.underwater) {
mario.nocollide = true;
TimeHandler.addEvent(function() {
mario.nocollide = false;
addThing(new Thing(RestingStone), mario.left, mario.bottom + mario.yvel);
}, map.respawndist || 17);
}
// ...in which case just fix his gravity
else mario.gravity = gravity / 2.8;
}
function gameOver() {
// Having a gamecount of -1 truly means it's all over
gameon = false;
pause();
pauseTheme();
play("Game Over");
var innerHTML = "<div style='font-size:49px;padding-top: " + (innerHeight / 2 - 28/*49*/) + "px'>GAME OVER</div>";
// innerHTML += "<p style='font-size:14px;opacity:.49;width:490px;margin:auto;margin-top:49px;'>";
// innerHTML += "You have run out of lives. Maybe you're not ready for playing real games...";
innerHTML += "</p>";
body.className = "Night"; // to make it black
body.innerHTML = innerHTML;
window.gamecount = Infinity;
clearMarioStats();
setTimeout(gameRestart, 7000);
}
function gameRestart() {
seedlast = .007;
body.style.visibility = "hidden";
body.innerHTML = body.style.paddingTop = body.style.fontSize = "";
body.appendChild(canvas);
gameon = true;
map.random ? setMapRandom() : setMap(1,1);
TimeHandler.addEvent(function() { body.style.visibility = ""; });
setLives(3);
}
/*
* Solids
*/
function Floor(me, length, height) {
// log(arguments);
me.width = (length || 1) * 8;
me.height = (height * 8) || unitsizet32;
me.spritewidth = 8;
me.spriteheight = 8;
me.repeat = true;
setSolid(me, "floor");
}
// To do: stop using clouds, and use Stone instead
function Clouds(me, length) {
me.width = length * 8;
me.height = 8;
setSolid(me, "clouds");
}
function Brick(me, content) {
me.width = me.height = 8;
me.used = false;
me.bottomBump = brickBump;
if(!content) me.contents = false;
else {
if(content instanceof Array) {
me.contents = content;
while(me.contents.length < 3) me.contents.push(false);
} else me.contents = [content, false, false];
}
me.death = killNormal;
setSolid(me, "brick unused");
me.tolx = 1;
}
function brickBump(me, character) {
if(me.up || character.type != "mario") return;
play("Bump");
if(me.used) return;
me.up = character;
if(character.power > 1 && !me.contents)
return TimeHandler.addEvent(brickBreak, 2, me, character); // wait until after collision testing to delete (for coins)
// Move the brick
blockBumpMovement(me);
// If the brick has contents,
if(me.contents) {
// Turn normal Mushrooms into FireFlowers if Mario is large
if(mario.power > 1 && me.contents[0] == Mushroom && !me.contents[1]) me.contents[0] = FireFlower;
TimeHandler.addEvent(
function(me) {
var contents = me.contents,
out = new Thing(contents[0], contents[1], contents[2]);
addThing(out, me.left, me.top);
setMidXObj(out, me, true);
out.blockparent = me;
out.animate(out, me);
// Do special preps for coins
if(me.contents[0] == Coin) {
if(me.lastcoin) makeUsedBlock(me);
TimeHandler.addEvent( function(me) { me.lastcoin = true; }, 245, me );
} else makeUsedBlock(me);
},
7,
me
);
}
}
function makeUsedBlock(me) {
me.used = true;
switchClass(me, "unused", "used");
}
function brickBreak(me, character) {
play("Break Block");
score(me, 50);
me.up = character;
TimeHandler.addEvent(placeShards, 1, me);
killNormal(me);
}
function placeShards(me) {
for(var i = 0, shard; i < 4; ++i) {
shard = new Thing(BrickShard);
addThing(shard,
me.left + (i < 2) * me.width * unitsize - unitsizet2,
me.top + (i % 2) * me.height * unitsize - unitsizet2);
shard.xvel = unitsized2 - unitsize * (i > 1);
shard.yvel = unitsize * -1.4 + i % 2;
TimeHandler.addEvent(killNormal, 350, shard);
}
}
// Listed in characters because of gravity. Has nocollide, so it's ok
function BrickShard(me) {
me.width = me.height = 4;
me.nocollide = true;
me.death = killNormal;
setCharacter(me, "brickshard");
TimeHandler.addSpriteCycle(me, [unflipHoriz, flipHoriz]);
}
function attachEmerge(me, solid) {
me.animate = setInterval(function() {
setBottom(me, solid.top, true);
if(!solid.up) {
clearInterval(me.animate);
me.animate = false;
}
}, timer);
}
function Block(me, content, hidden) {
me.width = me.height = 8;
me.used = false;
me.bottomBump = blockBump;
if(!content) me.contents = [Coin]; else {
if(content instanceof Array) {
me.contents = content;
while(me.contents.length < 3) me.contents.push(false);
} else me.contents = [content, false, false];
}
me.death = killNormal;
setSolid(me, "Block unused");
if(!hidden) me.hidden = false;
else {
me.hidden = me.hidden = me.skipoverlaps = true;
}
me.tolx = 1;
TimeHandler.addSpriteCycleSynched(me, ["one", "two", "three", "two", "one"]);
}
function blockBump(me, character) {
if(character.type != "mario") return;
if(me.used) {
play("Bump");
return;
}
me.used = 1;
me.hidden = me.hidden = me.skipoverlaps = false;
me.up = character;
blockBumpMovement(me);
removeClass(me, "hidden");
switchClass(me, "unused", "used");
if(mario.power > 1 && me.contents[0] == Mushroom && !me.contents[1]) me.contents[0] = FireFlower;
TimeHandler.addEvent(blockContentsEmerge, 7, me);
}
// out is a coin by default, but can also be other things - [1] and [2] are arguments
function blockContentsEmerge(me) {
var out = new Thing(me.contents[0], me.contents[1], me.contents[2]);
addThing(out, me.left, me.top);
setMidXObj(out, me, true);
out.blockparent = me;
out.animate(out, me);
}
function Pipe(me, height, transport) {
me.width = me.spritewidth = 16;
me.height = (height || 1) * 8;
if(transport !== false) {
me.actionTop = intoPipeVert;
me.transport = transport;
}
setSolid(me, "pipe");
}
function PipeSide(me, transport, small) {
me.width = me.spritewidth = small ? 8 : 19.5;
me.height = me.spriteheight = 16;
if(transport) {
me.actionLeft = intoPipeHoriz;
me.transport = transport;
}
setSolid(me, "pipe side " + (small ? "small" : ""));
}
function PipeVertical(me, height) {
me.spritewidth = me.width = 16;
me.spriteheight = me.repeat = 1;
me.height = height;
setSolid(me, "pipe vertical");
}
// Locnum is -1 if this is a cutscene vine
// The actual vine is just a dummy
function Vine(me, locnum) {
me.width = me.spriteheight = 7;
me.height = 0;
me.locnum = locnum;
me.nocollide = me.nofall = me.repeat = true;
me.animate = vineEmerge;
me.movement = vineMovement;
setCharacter(me, "vine");
}
function vineEmerge(me, solid) {
play("Vine Emerging");
setHeight(me, 0);
me.movement = vineMovement;
TimeHandler.addEvent(vineEnable, 14, me);
TimeHandler.addEventInterval(vineStay, 1, 14, me, solid);
}
function vineStay(me, solid) {
setBottom(me, solid.top);
}
function vineEnable(me) {
me.nocollide = false;
me.collide = touchVine;
}
function vineMovement(me) {
increaseHeightTop(me, unitsized4);
if(me.attached) shiftVert(me.attached, -unitsized4, true);
}
function touchVine(me, vine) {
if(!me.mario || me.attached || me.climbing || me.bottom > vine.bottom + unitsizet2) return;
vine.attached = me;
me.attached = vine;
me.nofall = me.skipoverlaps = true;
me.xvel = me.yvel = me.resting = me.jumping = me.jumpcount = me.running = 0;
me.attachleft = !objectToLeft(me, vine);
me.attachoff = me.attachleft * 2 - 1;
me.movementsave = me.movement;
me.movement = moveMarioVine;
me.keys = new Keys();
// Reset classes to be in vine mode
TimeHandler.clearClassCycle(me, "running");
removeClass(me, "running skidding");
unflipHoriz(me);
if(me.attachleft) flipHoriz(me);
addClass(me, "climbing");
// setSize(me, 7, 8, true);
me.climbing = TimeHandler.addSpriteCycle(me, ["one", "two"], "climbing");
// Make sure you're looking at the vine, and from the right distance
lookTowardThing(me, vine);
if(!me.attachleft) setRight(me, vine.left + unitsizet4);
else setLeft(me, vine.right - unitsizet4);
}
function Springboard(me) {
me.width = 8;
me.height = me.heightnorm = 14.5;
me.tension = me.tensionsave = 0;
me.dir = 1; // 1 for down, -1 for up (ydir)
me.collide = collideSpring;
setSolid(me, "springboard");
}
function collideSpring(me, spring) {
if(me.yvel >= 0 && me.mario && !spring.tension && characterOnSolid(me, spring))
return springMarioInit(spring, me);
return characterTouchedSolid(me, spring);
}
function springMarioInit(spring, mario) {
spring.tension = spring.tensionsave = max(mario.yvel * .77, unitsize);
mario.movement = moveMarioSpringDown;
mario.spring = spring;
mario.xvel /= 2.8;
}
function moveMarioSpringDown(me) {
// If you've moved off the spring, get outta here
if(!objectsTouch(me, me.spring)) {
me.movement = moveMario;
me.spring.movement = moveSpringUp;
me.spring = false;
return;
}
// If the spring is contracted, go back up
if(me.spring.height < unitsize * 2.5 || me.spring.tension < unitsized32) {
me.movement = moveMarioSpringUp;
me.spring.movement = moveSpringUp;
return;
}
// Make sure it's hard to slide off
if(me.left < me.spring.left + unitsizet2 || me.right > me.spring.right - unitsizet2)
me.xvel /= 1.4;
reduceSpringHeight(me.spring, me.spring.tension);
setBottom(me, me.spring.top, true);
me.spring.tension /= 2;
updateSize(me.spring);
}
function moveMarioSpringUp(me) {
if(!me.spring || !objectsTouch(me, me.spring)) {
me.spring = false;
me.movement = moveMario;
return;
}
}
function moveSpringUp(spring) {
reduceSpringHeight(spring, -spring.tension);
spring.tension *= 2;
if(spring == mario.spring)
setBottom(mario, spring.top, true);
if(spring.height > spring.heightnorm) {
if(spring == mario.spring) {
mario.yvel = max(-unitsizet2, spring.tensionsave * -.98);
mario.resting = mario.spring = false;
}
reduceSpringHeight(spring, (spring.height - spring.heightnorm) * unitsize);
spring.tension = spring.tensionsave = spring.movement = false;
}
}
function reduceSpringHeight(spring, dy) {
reduceHeight(spring, dy, true);
}
function Stone(me, width, height) {
me.width = (width * 8) || 8;
me.height = (height * 8) || 8;
me.repeat = true;
setSolid(me, "Stone");
}
// For historical reasons
function GenericStone(me, width, height) { return Stone(me, width, height); }
function RestingStone(me) {
me.width = me.height = 8;
me.used = false;
me.movement = RestingStoneUnused;
setSolid(me, "Stone hidden");
me.title = "Stone";
}
function RestingStoneUnused(me) {
// Wait until Mario isn't resting
if(!mario.resting) return;
// If Mario is resting on something else, this is unecessary
if(mario.resting != me) return killNormal(me);
// Make the stone wait until it's no longer being rested upon
me.movement = RestingStoneUsed;
removeClass(me, "hidden");
setThingSprite(mario);
}
function RestingStoneUsed(me) {
if(!mario.resting) return killNormal(me);
}
function CastleBlock(me, arg1, arg2) {
me.width = me.height = 8;
var length, dt, hidden = false;
// Check for fireballs
if(arg1 instanceof Array) {
length = arg1[0];
dt = arg1[1];
hidden = arg2;
}
else {
length = arg1;
dt = arg2;
}
// If it's not hidden, set the background
if(!hidden) {
setSolid(me, "castleblock");
}
// Otherwise it's invisible
else setSolid(me, "castleblockinvis");
// Since there are fireballs, set that stuff
if(length) {
me.balls = new Array(length);
me.dt = .07 * (dt ? 1 : -1);
me.timeout = round(7 / (abs(dt) || 1));
me.movement = castleBlockSpawn;
me.timer = me.counter = 0;
me.angle = .25;
}
}
function castleBlockSpawn(me) {
for(var i=0; i<me.balls.length; ++i) {
spawn = new Thing(CastleFireBall, i * 4);
var mid = me.width * unitsized4, midx = me.left + mid, midy = me.top + mid;
me.balls[i] = addThing(spawn, midx + i * unitsize * 3, midy + i * unitsize * 3);
}
me.movement = false;
var interval = abs(me.dt) || 1;
TimeHandler.addEventInterval(castleBlockEvent, me.timeout, Infinity, me);
}
function castleBlockEvent(me) {
me.midx = me.left;// + me.width * unitsize / 2;
me.midy = me.top;// + me.height * unitsize / 2;
me.counter = 0;
me.angle += me.dt
// Skip i=0 because it doesn't move
for(var i = 1; i < me.balls.length; ++i) {
setMidX(me.balls[i], me.midx + (i) * unitsizet4 * Math.cos(me.angle * Math.PI), true);
setMidY(me.balls[i], me.midy + (i) * unitsizet4 * Math.sin(me.angle * Math.PI), true);
}
}
// Set to solids because they spawn with their CastleBlocks
function CastleFireBall(me, distance) {
me.width = me.height = 4;
me.deadly = me.nofire = me.nocollidechar = me.nocollidesolid = me.nofall = me.nostar = me.outerok = me.skipoverlaps = true;
me.movement = false;
me.collide = collideEnemy;
setCharacter(me, "fireball castle");
TimeHandler.addSpriteCycle(me, ["one", "two", "three", "four"], 4);
}
function CastleBridge(me, length) {
me.height = 8;
me.width = length * 8 || 4;
me.spritewidth = 4;
me.repeat = true;
setSolid(me, "CastleBridge");
}
function CastleChain(me) {
me.height = 8;
me.width = me.spritewidth = 7.5;
me.nocollide = true;
setSolid(me, "castlechain");
// make standardized detector
}
function CastleAxe(me) {
me.width = me.height = 8;
me.spritewidth = me.spriteheight = 8;
me.nocollide = true;
setSolid(me, "castleaxe");
TimeHandler.addSpriteCycle(me, ["one", "two", "three", "two"]);
}
// Step 1 of getting to that jerkface Toad
function CastleAxeFalls(me, collider) {
var axe = collider.axe;
// Don't do this if Mario would fall without the bridge
if(!me.mario ||
me.right < axe.left + unitsize ||
me.bottom > axe.bottom - unitsize) return;
// Immediately kill the axe and collider
killNormal(axe);
killNormal(collider);
// Pause Mario & wipe the other characters
notime = nokeys = true;
thingStoreVelocity(me);
killOtherCharacters();
TimeHandler.addEvent(killNormal, 7, axe.chain);
TimeHandler.addEvent(CastleAxeKillsBridge, 14, axe.bridge, axe);
pauseTheme();
playTheme("World Clear", false, false);
}
// Step 2 of getting to that jerkface Toad
function CastleAxeKillsBridge(bridge, axe) {
// Decrease the size of the bridge
bridge.width -= 2;
bridge.right -= unitsizet2;
// If it's still here, go again
if(bridge.width > 0) TimeHandler.addEvent(CastleAxeKillsBridge, 1, bridge, axe);
// Otherwise call the next step
else {
bridge.width = 0;
TimeHandler.addEvent(CastleAxeKillsBowser, 1, axe.bowser);
}
setWidth(bridge, bridge.width);
}
// Step 3 of getting to that jerkface Toad
function CastleAxeKillsBowser(bowser) {
bowser.nofall = false;
TimeHandler.addEvent(CastleAxeContinues, 35, mario);
}
// Step 4 of getting to that jerkface Toad
function CastleAxeContinues(mario) {
map.canscroll = true;
startWalking(mario);
}
function Toad(me) {
me.width = 16;
me.height = me.spriteheight = 12;
me.group = "toad";
setSolid(me, "toad npc");
}
function Peach(me) {
me.width = 16;
me.height = me.spriteheight = 12;
me.group = "peach";
setSolid(me, "peach npc");
}
// CollideCastleNPC is actually called by the FuncCollider
function collideCastleNPC(me, collider) {
killNormal(collider);
me.keys.run = 0;
TimeHandler.addEvent(function(text) {
var i;
for(i = 0; i < text.length; ++i)
TimeHandler.addEvent(proliferate, i * 70, text[i].element, {style: {visibility: "visible"}});
TimeHandler.addEvent(endLevel, (i + 3) * 70);
}, 21, collider.text);
}
function TreeTop(me, width) {
// Tree trunks are scenery
me.width = width * 8;
me.height = 8;
me.repeat = true;
setSolid(me, "treetop");
}
function ShroomTop(me, width) {
// Shroom trunks are scenery
me.width = width * 8;
me.height = 8;
me.repeat = true;
setSolid(me, "shroomtop");
}
// For the floaty ones, if the yvel is really big, +1000 points
function Platform(me, width, settings) {
me.width = (width || 4) * 4;
me.height = 4;
me.spritewidth = 4;
me.moving = 0;
me.repeat = me.killonend = true;
if(typeof(settings) == "function") settings = [settings];
if(settings instanceof Array) {
me.movement = settings[0];
me.begin = settings[1] * unitsize;
me.end = settings[2] * unitsize;
me.maxvel = (settings[3] || 1.5) * unitsized4;
if(me.movement == moveFloating || me.movement == movePlatformSpawn)
me.yvel = me.maxvel;
else me.xvel = me.maxvel;
me.changing = 0; // 0 for normal, |1| for forward/back, |2| for waiting
}
if(me.movement == collideTransport) {
me.movement = false;
me.collide = collideTransport;
}
setSolid(me, "platform");
}
function PlatformGenerator(me, width, dir) {
me.width = width * 4;
me.interval = 35;
me.height = me.interval * 6;
me.dir = dir;
me.nocollide = me.hidden = true;
me.movement = PlatformGeneratorInit;
setSolid(me, "platformgenerator");
}
function PlatformGeneratorInit(me) {
for(var i = 0, inc = me.interval, height = me.height; i < height; i += inc) {
me.platlast = new Thing(Platform, me.width / 4, [movePlatformSpawn, 0, 0, 1.5]);
me.platlast.yvel *= me.dir;
if(me.dir == 1) addThing(me.platlast, me.left, me.top + i * unitsize);
else addThing(me.platlast, me.left, me.bottom - i * unitsize);
me.platlast.parent = me;
i += me.interval;
}
me.movement = false;
}
function movePlatformSpawn(me) {
// This is like movePlatformNorm, but also checks for whether it's out of bounds
// Assumes it's been made with a PlatformGenerator as the parent
// To do: make the PlatformGenerator check one at a time, not each of them.
if(me.bottom < me.parent.top) {
setBottom(me, me.parent.bottom);
detachMario(me);
}
else if(me.top > me.parent.bottom) {
setTop(me, me.parent.top);
detachMario(me);
}
else movePlatformNorm(me);
}
function movePlatformNorm(me) {
shiftHoriz(me, me.xvel);
shiftVert(me, me.yvel);
if(me == mario.resting && me.alive) {
setBottom(mario, me.top);
shiftHoriz(mario, me.xvel);
if(mario.right > innerWidth) setRight(mario, innerWidth);
}
}
function detachMario(me) {
if(mario.resting != me) return;
mario.resting = false;
}
// Placed via pushPreScale
// pushPreScale(xloc, yloc, width, [platwidth, offy1, offy2]);
// settings = [platwidth, offy1, offy2] (offy is distance from top to platform)
function Scale(me, width, settings) {
me.height = 5;
me.width = width * 4;
me.spritewidth = me.spriteheight = 5;
me.repeat = me.nocollide = true;
setSolid(me, "scale");
}
function Flag(me) {
me.width = me.height = 8;
me.nocollide = true;
setSolid(me, "flag");
}
function FlagPole(me) {
me.width = 1;
me.height = 72;
me.nocollide = me.repeat = true;
setSolid(me, "flagpole");
}
function FlagTop(me) {
me.spritewidth = me.spriteheight = me.width = me.height = 4;
me.nocollide = true;
setSolid(me, "flagtop");
}
// The detectors are invisible, and just for ending the level.
function FlagDetector(me) {
me.width = 2;
me.height = 100;
me.collide = FlagCollision;
setSolid(me, "flagdetector");
me.hidden = true;
}
function CastleDoorDetector(me) {
me.width = me.height = 4;
me.collide = endLevelPoints;
setSolid(me, "castledoor");
me.hidden = true;
}
function FlagCollision(me, detector) {
if(!me || !me.mario) return killNormal(me);
window.detector = detector;
pauseAllSounds();
play("Flagpole");
// Reset and clear most stuff, including killing all other characters
killOtherCharacters();
nokeys = notime = mario.nofall = 1;
// Mostly clear Mario, and set him to the pole's left
mario.xvel = mario.yvel = mario.keys.up = mario.keys.jump = map.canscroll = map.ending = mario.movement = 0;
mario.nocollidechar = true;
setRight(me, detector.pole.left, true);
removeClasses(me, "running jumping skidding");
addClass(me, "climbing animated");
updateSize(me);
TimeHandler.addSpriteCycle(me, ["one", "two"], "climbing");
marioRemoveStar(mario); // just in case
// Start the movement
var mebot = false,
flagbot = false,
scoreheight = (detector.stone.top - me.bottom) / unitsize,
down = setInterval(function() {
// Move mario until he hits the bottom, at which point mebot = true
if(!mebot) {
if(me.bottom >= detector.stone.top) {
scoreMarioFlag(scoreheight, detector.stone);
mebot = true;
setBottom(me, detector.stone.top, true);
removeClass(mario, "animated");
TimeHandler.clearClassCycle(mario, "climbing");
} else shiftVert(me, unitsize, true);
}
// Same for the flag
if(!flagbot) {
if(detector.flag.bottom >= detector.stone.top) {
flagbot = true;
setBottom(detector.flag, detector.stone.top, true);
} else shiftVert(detector.flag, unitsize, true);
}
// Once both are at the bottom:
if(mebot && flagbot) {
setBottom(me, detector.stone.top, true);
clearInterval(down);
setTimeout(function() { FlagOff(me, detector.pole); }, timer * 21);
}
refillCanvas();
}, timer);
}
// See http://themushroomkingdom.net/smb_breakdown.shtml near bottom
// Stages: 8, 28, 40, 62
function scoreMarioFlag(diff, stone) {
var amount;
// log(diff);
// Cases of...
if(diff < 28) {
// 0 to 8
if(diff < 8) { amount = 100; }
// 8 to 28
else { amount = 400; }
}
else {
// 28 to 40
if(diff < 40) { amount = 800; }
// 40 to 62
else if(diff < 62) { amount = 2000; }
// 62 to infinity and beyond
else { amount = 5000; }
}
score(mario, amount, true);
}
function FlagOff(me, pole) {
mario.keys.run = notime = nokeys = 1;
mario.maxspeed = mario.walkspeed;
flipHoriz(me);
TimeHandler.clearClassCycle(me, "climbing");
setLeft(me, pole.right, true);
setTimeout(function() {
play("Stage Clear");
marioHopsOff(me, pole, true);
}, timer * 14);
}
// Me === Mario
function endLevelPoints(me, detector) {
if(!me || !me.mario) return;
// Stop the game, and get rid of mario and the detectors
notime = nokeys = true;
killNormal(detector);
killNormal(me);
// Determine the number of fireballs (1, 3, and 6 become not 0)
var numfire = getLast(String(data.time.amount));
if(!(numfire == 1 || numfire == 3 || numfire == 6)) numfire = 0;
// Count down the points (x50)
var points = setInterval(function() {
// 50 for each
--data.time.amount;
data.score.amount += 50;
updateDataElement(data.score);
updateDataElement(data.time);
// Each point(x50) plays the coin noise
play("Coin");
// Once it's done, move on to the fireworks.
if(data.time.amount <= 0) {
// pause();
clearInterval(points);
setTimeout(function() { endLevelFireworks(me, numfire, detector); }, timer * 49);
}
}, timerd2);
}
function endLevelFireworks(me, numfire, detector) {
var nextnum, nextfunc,
i = 0;
if(numfire) {
// var castlemid = detector.castle.left + detector.castle.width * unitsized2;
var castlemid = detector.left + 32 * unitsized2;
while(i < numfire)
explodeFirework(++i, castlemid); //pre-increment since explodeFirework expects numbers starting at 1
nextnum = timer * (i + 2) * 42;
}
else nextnum = 0;
// The actual endLevel happens after all the fireworks are done
nextfunc = function() { setTimeout(function() { endLevel(); }, nextnum); };
// If the Stage Clear sound is still playing, wait for it to finish
if(sounds["Stage Clear"] && !sounds["Stage Clear"].paused)
sounds["Stage Clear"].addEventListener("ended", function() { TimeHandler.addEvent(nextfunc, 35); });
// Otherwise just start it immediately
else nextfunc();
}
function explodeFirework(num, castlemid) {
setTimeout(function() {
var fire = new Thing(Firework, num);
addThing(fire, castlemid + fire.locs[0] - unitsize * 6, unitsizet16 + fire.locs[1]);
fire.animate();
}, timer * num * 42);
}
function Firework(me, num) {
me.width = me.height = 8;
me.nocollide = me.nofire = me.nofall = true;
// Number is >0 if this is ending of level
if(num)
switch(num) {
// These probably aren't the exact same as original... :(
case 1: me.locs = [unitsizet16, unitsizet16]; break;
case 2: me.locs = [-unitsizet16, unitsizet16]; break;
case 3: me.locs = [unitsizet16 * 2, unitsizet16 * 2]; break;
case 4: me.locs = [unitsizet16 * -2, unitsizet16 * 2]; break;
case 5: me.locs = [0,unitsizet16 * 1.5]; break;
default: me.locs = [0,0]; break;
}
// Otherwise, it's just a normal explosion
me.animate = function() {
var name = me.className + " n";
if(me.locs) play("Firework");
TimeHandler.addEvent(function(me) { setClass(me, name + 1); }, 0, me);
TimeHandler.addEvent(function(me) { setClass(me, name + 2); }, 7, me);
TimeHandler.addEvent(function(me) { setClass(me, name + 3); }, 14, me);
TimeHandler.addEvent(function(me) { killNormal(me); }, 21, me);
}
setCharacter(me, "firework");
}
function Coral(me, height) {
me.width = 8;
me.height = height * 8;
me.repeat = true;
setSolid(me, "coral");
}
function BridgeBase(me, width) {
me.height = 4;
me.spritewidth = 4;
me.width = width * 8;
me.repeat = true;
setSolid(me, "bridge-base");
}
function WarpWorld(me) {
me.width = 106; // 13 * 8 + 2
me.height = 88;
me.movement = setWarpWorldInit;
me.collide = enableWarpWorldText;
me.pirhanas = [];
me.pipes = [];
me.texts = [];
me.hidden = true;
setSolid(me, "warpworld");
}
function setWarpWorldInit(me) {
// Just reduces the size
shiftHoriz(me, me.width * unitsized2);
me.width /= 2;
updateSize(me);
me.movement = false;
}
function enableWarpWorldText(me, warp) {
var pirhanas = warp.pirhanas,
texts = warp.texts, i;
for(i in pirhanas) {
pirhanas[i].death();
}
for(i in texts)
texts[i].element.style.visibility = "";
killNormal(warp);
}
/* Scenery */
// Scenery sizes are stored in window.scenery
// After creation, they're processed
function resetScenery() {
window.Scenery = {
// Individual sizes for scenery
sprites: {
"BrickHalf": [8, 4],
"BrickPlain": [8, 8],
"Bush1": [16, 8],
"Bush2": [24, 8],
"Bush3": [32, 8],
"Castle": [75, 88],
"CastleDoor": [8, 20],
"CastleRailing": [8, 4],
"CastleRailingFilled": [8, 4],
"CastleTop": [12, 12],
"CastleWall": [8, 48],
"Cloud1": [16, 12],
"Cloud2": [24, 12],
"Cloud3": [32, 12],
"HillSmall": [24, 9.5],
"HillLarge": [40, 17.5],
"Fence": [8, 8],
"Pirhana": [8, 12],
"pirhana": [8, 12],
"PlantSmall": [7, 15],
"PlantLarge": [8, 23],
"Railing": [4, 4],
"ShroomTrunk": [8, 8],
"String": [1, 1],
"TreeTrunk": [8, 8],
"Water": {
0: 4,
1: 5,
spriteCycle: ["one", "two", "three", "four"]
},
"WaterFill": [4, 5]
},
// Patterns of scenery that can be placed in one call
// Each ends with "Blank" to signify the ending width
patterns: {
backreg: [
["HillLarge", 0, 0],
["Cloud1", 68, 68],
["Bush3", 92, 0],
["HillSmall", 128, 0],
["Cloud1", 156, 76],
["Bush1", 188, 0],
["Cloud3", 220, 68],
["Cloud2", 292, 76],
["Bush2", 332, 0],
["Blank", 384]
],
backcloud: [
["Cloud2", 28, 64],
["Cloud1", 76, 32],
["Cloud2", 148, 72],
["Cloud1", 228, 0],
["Cloud1", 284, 32],
["Cloud1", 308, 40],
["Cloud1", 372, 0],
["Blank", 384]
],
backcloudmin: [ // used for random map generation
["Cloud1", 68, 68],
["Cloud1", 156, 76],
["Cloud3", 220, 68],
["Cloud2", 292, 76],
["Blank", 384]
],
backfence: [
["PlantSmall", 88, 0],
["PlantLarge", 104, 0],
["Fence", 112, 0, 4],
["Cloud1", 148, 68],
["PlantLarge", 168, 0],
["PlantSmall", 184, 0],
["PlantSmall", 192, 0],
["Cloud1", 220, 76],
["Cloud2", 244, 68],
["Fence", 304, 0, 2],
["PlantSmall", 320, 0],
["Fence", 328, 0],
["PlantLarge", 344, 0],
["Cloud1", 364, 76],
["Cloud2", 388, 68],
["Blank", 384]
],
backfencemin: [
["PlantLarge", 104, 0],
["Fence", 112, 0, 4],
["Cloud1", 148, 68],
["PlantLarge", 168, 0],
["PlantSmall", 184, 0],
["PlantSmall", 192, 0],
["Cloud1", 220, 76],
["Cloud2", 244, 68],
["Fence", 304, 0, 2],
["PlantSmall", 320, 0],
["Fence", 328, 0],
["Cloud1", 364, 76],
["Cloud2", 388, 68],
["Blank", 384]
],
backfencemin2: [
["Cloud2", 4, 68],
["PlantSmall", 88, 0],
["PlantLarge", 104, 0],
["Fence", 112, 0, 1],
["Fence", 128, 0, 2],
["Cloud1", 148, 68],
// ["PlantLarge", 168, 0],
["PlantSmall", 184, 0],
["PlantSmall", 192, 0],
["Cloud1", 220, 76],
["Cloud2", 244, 68],
["Fence", 304, 0, 2],
["PlantSmall", 320, 0],
["Fence", 328, 0],
["PlantLarge", 344, 0],
["Cloud1", 364, 76],
["Cloud2", 388, 68],
["Blank", 384]
],
backfencemin3: [
["Cloud2", 4, 68],
["PlantSmall", 88, 0],
["PlantLarge", 104, 0],
["Fence", 112, 0, 4],
["Cloud1", 148, 68],
["PlantSmall", 184, 0],
["PlantSmall", 192, 0],
["Cloud1", 220, 76],
["Cloud2", 244, 68],
["Cloud1", 364, 76],
["Cloud2", 388, 68],
["Blank", 384]
]
}
};
processSceneryPatterns(Scenery.patterns);
}
// Sets the width of them and removes the blank element
function processSceneryPatterns(patterns) {
var current, i;
for(i in patterns) {
current = patterns[i];
if(!current.length) continue;
// The last array in current should be ["blank", width]
current.width = current[current.length - 1][1];
current.pop();
}
}
// Used in worlds like 5-2 where patterns are slightly inaccurate
function SceneryBlocker(me, width, height) {
me.width = width || 8;
me.height = height || 8;
me.nocollide = me.hidden = true;
setSolid(me, "sceneryblocker");
}
// A sprite, based on scenery.sprites[name]
// It's repeated (reps = [x-rep, y-rep]) times ([1,1] by default)
function Sprite(me, name, reps) {
if(!reps) reps = [1,1];
// Grab the template from window.Scenery (or complain if it's not there)
var template = me.template = Scenery.sprites[name];
if(!template) {
log("No sprite template found for", name);
return;
}
// Sizing is gotten from the listing under setScenery
me.width = (me.spritewidth = template[0]) * (reps[0] || 1);
me.height = (me.spriteheight = template[1]) * (reps[1] || 1);
me.unitwidth = me.spritewidth * unitsize;
me.unitheight = me.spriteheight * unitsize;
me.nocollide = me.maxquads = 1;
me.repeat = true;
setScenery(me, "scenery " + name);
me.title = name;
// If the listing has a SpriteCycle, do that
if(template.spriteCycleTimer) TimeHandler.addSpriteCycle(me, spriteCycleTimer, spriteCycleTimer || undefined)
}
// To do: is this ever used? (no longer used in sky)
function LocationShifter(me, loc, size) {
me.loc = loc;
me.width = size[0];
me.height = size[1];
me.collide = collideLocationShifter;
me.hidden = true;
setSolid(me, "blue");
return;
}
function collideLocationShifter(me, shifter) {
if(!me.mario) return;
shifter.nocollide = mario.piping = true;
TimeHandler.addEvent(
function(me) {
shiftToLocation(shifter.loc);
if(map.random) entryRandom(me);
}, 1, me );
}
function ScrollBlocker(me, big) {
me.width = 40;
me.height = 140; //gamescreen.height;
me.nocollide = me.hidden = true;
me.big = big;
me.movement = function() {
if(me.left - mario.xvel <= gamescreen.right - gamescreen.left) {
map.canscroll = me.movement = false;
map.noscroll = me.big; // really just for random
}
}
setSolid(me, "scrollblocker");
}
function ScrollEnabler(me) {
me.width = 40;
me.height = 140;//gamescreen.height;
me.hidden = true;
me.collide = function() {
if(me.left - mario.xvel <= gamescreen.right - gamescreen.left) {
map.canscroll = me.nocollide = true;
}
}
setSolid(me, "scrollenabler");
}
function zoneToggler(me, func) {
me.width = 40;
me.height = 140;//gamescreen.height;
me.func = func;
me.hidden = true;
me.collide = function(me, zone) {
zone.func();
zone.nocollide = true;
}
setSolid(me, "zonetoggler " + func.name);
}
function GenerationStarter(me, func, arg) {
me.width = 8;
me.height = gamescreen.height + 20;
me.func = func;
me.arg = arg;
me.collide = function(character, me) {
if(character.type != "mario") return false;
spawnMap();
killNormal(me);
};
me.movement = function(me) {
me.movement = false;
addClass(me, "used");
me.func((gamescreen.left + me.right) / unitsize, me.arg);
};
setSolid(me, "generationstarter");
me.hidden = true;
}
function castleDecider(me, xloc, secnum) {
me.height = ceilmax;
me.width = 10;
me.nocollide = true;
me.xloc = xloc;
me.section = map.area.sections[secnum];
me.next = map.area.sections[secnum + 1];
me.movement = function(me) {
if(me.left > gamescreen.right - gamescreen.left || !me.section.activated) return;
var section = me.section;
section.numpass = section.colliders.length = 0;
if(section.passed) {
++map.area.sections.current;
me.next(me.xloc);
}
else section(me.xloc);
section.activated = section.passed = false;
spawnMap();
killNormal(me);
}
setSolid(me, "decider blue " + secnum);
me.hidden = true;
}
// Used for general function activators, like zones
function FuncCollider(me, func, position) {
// Fancy positions are [width, height]
if(position) {
me.width = position[0];
me.height = position[1];
}
// Normally position is nothing
else {
me.width = 8;
me.height = ceilmax + 40;
}
me.collide = func;
me.hidden = true;
setSolid(me, "funccollider blue " + func.name);
}
function FuncSpawner(me, func, argument) {
me.width = 8; me.height = 8;
me.movement = function() { func(me, argument); };
me.argument = argument;
me.nocollide = me.hidden = true;
setSolid(me, "funccollider blue " + func.name);
}
// To do: replace this whenever possible
function Collider(me, size, funcs) {
me.width = size[0];
me.height = size[1];
if(funcs instanceof Array) {
me.func = funcs[0] || function() {};
me.movement = funcs[1] || function() {}
}
else {
me.func = funcs || function() {};
me.movement = false;
}
me.collide = function(character, me) {
if(!character.mario) return false;
me.func(character, me);
}
setSolid(me, "collider blue " + me.func.name);
me.hidden = true;
}