/* 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 = "
GAME OVER
"; // innerHTML += "

"; // innerHTML += "You have run out of lives. Maybe you're not ready for playing real games..."; innerHTML += "

"; 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 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; }