From b6f21d76de6a9544b9b46dedb23401eea14791c9 Mon Sep 17 00:00:00 2001 From: lilgreenland Date: Thu, 11 Jul 2019 10:04:58 -0700 Subject: [PATCH] tranfered files from website --- README.md | 3 + favicon.ico | Bin 0 -> 318 bytes index.html | 420 +++++++++ js/backup.js | 1449 ++++++++++++++++++++++++++++++ js/bullets.js | 843 ++++++++++++++++++ js/engine.js | 173 ++++ js/game.js | 1150 ++++++++++++++++++++++++ js/index.js | 275 ++++++ js/level.js | 1424 +++++++++++++++++++++++++++++ js/level.procedural.js | 998 +++++++++++++++++++++ js/levelold.js | 833 +++++++++++++++++ js/lib/matter 0.12.min.js | 89 ++ js/lib/matter.min.js | 90 ++ js/lib/randomColor.js | 417 +++++++++ js/lib/randomColor.min.js | 1 + js/lib/stats.min.js | 5 + js/mobs.js | 1000 +++++++++++++++++++++ js/player.js | 1211 +++++++++++++++++++++++++ js/powerups.js | 245 +++++ js/spawn.js | 1583 +++++++++++++++++++++++++++++++++ sounds/ammo.ogg | Bin 0 -> 6011 bytes sounds/boom.ogg | Bin 0 -> 8297 bytes sounds/click.ogg | Bin 0 -> 5824 bytes sounds/dmg/dmg0.ogg | Bin 0 -> 5671 bytes sounds/dmg/dmg1.ogg | Bin 0 -> 6106 bytes sounds/dmg/dmg2.ogg | Bin 0 -> 5636 bytes sounds/dmg/dmg3.ogg | Bin 0 -> 6016 bytes sounds/guns/airgun.ogg | Bin 0 -> 7175 bytes sounds/guns/bass.ogg | Bin 0 -> 9252 bytes sounds/guns/basssnaredrum.ogg | Bin 0 -> 5629 bytes sounds/guns/blaster.ogg | Bin 0 -> 5130 bytes sounds/guns/glock.ogg | Bin 0 -> 11918 bytes sounds/guns/launcher.ogg | Bin 0 -> 6424 bytes sounds/guns/launcher2.ogg | Bin 0 -> 5594 bytes sounds/guns/reload.ogg | Bin 0 -> 5824 bytes sounds/guns/shotgun.mp3 | Bin 0 -> 39729 bytes sounds/guns/snare.ogg | Bin 0 -> 23928 bytes sounds/guns/snare2.ogg | Bin 0 -> 12855 bytes sounds/guns/sniper.ogg | Bin 0 -> 10596 bytes sounds/no.ogg | Bin 0 -> 4297 bytes sounds/powerup4.ogg | Bin 0 -> 6249 bytes style.css | 214 +++++ 42 files changed, 12423 insertions(+) create mode 100644 README.md create mode 100644 favicon.ico create mode 100644 index.html create mode 100644 js/backup.js create mode 100644 js/bullets.js create mode 100644 js/engine.js create mode 100644 js/game.js create mode 100644 js/index.js create mode 100644 js/level.js create mode 100644 js/level.procedural.js create mode 100644 js/levelold.js create mode 100644 js/lib/matter 0.12.min.js create mode 100644 js/lib/matter.min.js create mode 100644 js/lib/randomColor.js create mode 100644 js/lib/randomColor.min.js create mode 100644 js/lib/stats.min.js create mode 100644 js/mobs.js create mode 100644 js/player.js create mode 100644 js/powerups.js create mode 100644 js/spawn.js create mode 100644 sounds/ammo.ogg create mode 100644 sounds/boom.ogg create mode 100644 sounds/click.ogg create mode 100644 sounds/dmg/dmg0.ogg create mode 100644 sounds/dmg/dmg1.ogg create mode 100644 sounds/dmg/dmg2.ogg create mode 100644 sounds/dmg/dmg3.ogg create mode 100644 sounds/guns/airgun.ogg create mode 100644 sounds/guns/bass.ogg create mode 100644 sounds/guns/basssnaredrum.ogg create mode 100644 sounds/guns/blaster.ogg create mode 100644 sounds/guns/glock.ogg create mode 100644 sounds/guns/launcher.ogg create mode 100644 sounds/guns/launcher2.ogg create mode 100644 sounds/guns/reload.ogg create mode 100644 sounds/guns/shotgun.mp3 create mode 100644 sounds/guns/snare.ogg create mode 100644 sounds/guns/snare2.ogg create mode 100644 sounds/guns/sniper.ogg create mode 100644 sounds/no.ogg create mode 100644 sounds/powerup4.ogg create mode 100644 style.css diff --git a/README.md b/README.md new file mode 100644 index 0000000..ca9e407 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +2-d physics platformer shooter + +https://landgreen.github.io/sidescroller/ diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f81671301e31c01ae217f85e23a63ad45ab85574 GIT binary patch literal 318 zcmZQzU<5(|0RbS%!l1#(z#zuJz@P!d0zj+)#2|4HXaJKC0wf0l#>U1B|NsAoaHvWO T0s+WC3>s>+Ael5(9mxOy3y}uS literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..7668415 --- /dev/null +++ b/index.html @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + n-gon + + + + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ info +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FIREleft mouse
FIELDright mouse / spacebar
MOVEWASD / arrows
GUNSQ / E / mouse wheel
ZOOM+ / -
PAUSEP
+
+ + + + + Github + + + +

Written by Ross Landgreen

+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Q + E + W + S + D + A + + + + + + + + + + + + + + guns + move + fire + field + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/js/backup.js b/js/backup.js new file mode 100644 index 0000000..586a585 --- /dev/null +++ b/js/backup.js @@ -0,0 +1,1449 @@ +//global player variables +let player, jumpSensor, playerBody, playerHead, headSensor; + +// player Object Prototype ********************************************* +mech = { + +} + +const mech = { + spawn: function () { + //player as a series of vertices + let vector = Vertices.fromPath('0 40 0 115 20 130 30 130 50 115 50 40'); + playerBody = Matter.Bodies.fromVertices(0, 0, vector); + //this sensor check if the player is on the ground to enable jumping + jumpSensor = Bodies.rectangle(0, 46, 36, 6, { + sleepThreshold: 99999999999, + isSensor: true, + }); + //this part of the player lowers on crouch + vector = Vertices.fromPath('0 -66 18 -82 0 -37 50 -37 50 -66 32 -82'); + playerHead = Matter.Bodies.fromVertices(0, -55, vector); + //a part of player that senses if the player's head is empty and can return after crouching + headSensor = Bodies.rectangle(0, -57, 48, 45, { + sleepThreshold: 99999999999, + isSensor: true, + }); + player = Body.create({ //combine jumpSensor and playerBody + parts: [playerBody, playerHead, jumpSensor, headSensor], + inertia: Infinity, //prevents player rotation + friction: 0.002, + //frictionStatic: 0.5, + restitution: 0.3, + sleepThreshold: Infinity, + collisionFilter: { + group: 0, + category: 0x001000, + mask: 0x010001 + }, + }); + //Matter.Body.setPosition(player, mech.spawnPos); + //Matter.Body.setVelocity(player, mech.spawnVel); + Matter.Body.setMass(player, mech.mass); + World.add(engine.world, [player]); + //holding body constraint + const holdConstraint = Constraint.create({ + pointA: { + x: 0, + y: 0 + }, + //setting constaint to jump sensor because it has to be on something until the player picks up things + bodyB: jumpSensor, + stiffness: 0.4, + }); + World.add(engine.world, holdConstraint); + }, + width: 50, + radius: 30, + fillColor: '#fff', + fillColorDark: '#ddd', + fireCDcycle: 0, + gun: 'machine', //current gun in use + gunOptions: { //keeps track of keys that switch guns (used in the onkeypress event) + 49: 'machine', + 50: 'needle', + 51: 'shot', + 52: 'rail', + 53: 'cannon', + 54: 'super', + 55: 'lob', + // 55: 'spiritBomb', + // 56: 'experimental' + } + height: 42, + yOffWhen: { + crouch: 22, + stand: 49, + jump: 70 + }, + yOff: 70, + yOffGoal: 70, + onGround: false, //checks if on ground or in air + onBody: { + id: 0, + index: 0, + type: "map", + action: '' + }, + numTouching = 0, + crouch = false, + isHeadClear = true, + spawnPos = { + x: 0, + y: 0 + }, + spawnVel = { + x: 0, + y: 0 + }, + pos = { + x: this.spawnPos.x, + y: this.spawnPos.y + }, + setPosToSpawn = function (xPos, yPos) { + this.spawnPos.x = xPos + this.spawnPos.y = yPos + this.pos.x = xPos; + this.pos.y = yPos; + this.Vx = this.spawnVel.x; + this.Vy = this.spawnVel.y; + Matter.Body.setPosition(player, this.spawnPos); + Matter.Body.setVelocity(player, this.spawnVel); + }; + this.Sy = this.pos.y; //adds a smoothing effect to vertical only + this.Vx = 0; + this.Vy = 0; + this.VxMax = 7; + this.mass = 5; + this.Fx = 0.004 * this.mass; //run Force on ground + this.FxAir = 0.0006 * this.mass; //run Force in Air + this.Fy = -0.05 * this.mass; //jump Force + this.angle = 0; + this.walk_cycle = 0; + this.stepSize = 0; + this.flipLegs = -1; + this.hip = { + x: 12, + y: 24, + }; + this.knee = { + x: 0, + y: 0, + x2: 0, + y2: 0 + }; + this.foot = { + x: 0, + y: 0 + }; + this.legLength1 = 55; + this.legLength2 = 45; + this.canvasX = canvas.width / 2; + this.canvasY = canvas.height / 2; + this.transX = this.canvasX - this.pos.x; + this.transY = this.canvasX - this.pos.x; + this.mouse = { + x: canvas.width / 3, + y: canvas.height + }; + this.getMousePos = function (x, y) { + this.mouse.x = x; + this.mouse.y = y; + }; + this.testingMoveLook = function () { + //move + this.pos.x = player.position.x; + this.pos.y = playerBody.position.y - this.yOff; + this.Vx = player.velocity.x; + this.Vy = player.velocity.y; + //look + this.canvasX = canvas.width / 2 + this.canvasY = canvas.height / 2 + this.transX = this.canvasX - this.pos.x; + this.transY = this.canvasY - this.pos.y; + this.angle = Math.atan2(this.mouse.y - this.canvasY, this.mouse.x - this.canvasX); + } + + this.move = function () { + this.pos.x = player.position.x; + this.pos.y = playerBody.position.y - this.yOff; + this.Vx = player.velocity.x; + this.Vy = player.velocity.y; + }; + this.look = function () { + //set a max on mouse look + let mX = this.mouse.x; + if (mX > canvas.width * 0.8) { + mX = canvas.width * 0.8; + } else if (mX < canvas.width * 0.2) { + mX = canvas.width * 0.2; + } + let mY = this.mouse.y; + if (mY > canvas.height * 0.8) { + mY = canvas.height * 0.8; + } else if (mY < canvas.height * 0.2) { + mY = canvas.height * 0.2; + } + //set mouse look + this.canvasX = this.canvasX * 0.94 + (canvas.width - mX) * 0.06; + this.canvasY = this.canvasY * 0.94 + (canvas.height - mY) * 0.06; + + //set translate values + this.transX = this.canvasX - this.pos.x; + this.Sy = 0.99 * this.Sy + 0.01 * (this.pos.y); + //hard caps how behind y position tracking can get. + if (this.Sy - this.pos.y > canvas.height / 2) { + this.Sy = this.pos.y + canvas.height / 2; + } else if (this.Sy - this.pos.y < -canvas.height / 2) { + this.Sy = this.pos.y - canvas.height / 2; + } + this.transY = this.canvasY - this.Sy; //gradual y camera tracking + //this.transY = this.canvasY - this.pos.y; //normal y camera tracking + + //make player head angled towards mouse + //this.angle = Math.atan2(this.mouse.y - this.canvasY, this.mouse.x - this.canvasX); + this.angle = Math.atan2(this.mouse.y + (this.Sy - this.pos.y) * game.zoom - this.canvasY, this.mouse.x - this.canvasX); + }; + this.doCrouch = function () { + if (!this.crouch) { + this.crouch = true; + this.yOffGoal = this.yOffWhen.crouch; + Matter.Body.translate(playerHead, { + x: 0, + y: 40 + }) + } + } + this.undoCrouch = function () { + this.crouch = false; + this.yOffGoal = this.yOffWhen.stand; + Matter.Body.translate(playerHead, { + x: 0, + y: -40 + }) + } + this.enterAir = function () { + this.onGround = false; + player.frictionAir = 0.001; + if (this.isHeadClear) { + if (this.crouch) { + this.undoCrouch(); + } + this.yOffGoal = this.yOffWhen.jump; + }; + } + this.enterLand = function () { + this.onGround = true; + if (this.crouch) { + if (this.isHeadClear) { + this.undoCrouch(); + player.frictionAir = 0.12; + } else { + this.yOffGoal = this.yOffWhen.crouch; + player.frictionAir = 0.5; + } + } else { + this.yOffGoal = this.yOffWhen.stand; + player.frictionAir = 0.12; + } + }; + this.buttonCD_jump = 0; //cooldown for player buttons + this.keyMove = function () { + if (this.onGround) { //on ground ********************** + if (this.crouch) { //crouch + if (!(keys[40] || keys[83]) && this.isHeadClear) { //not pressing crouch anymore + this.undoCrouch(); + player.frictionAir = 0.12; + } + } else if (keys[40] || keys[83]) { //on ground && not crouched and pressing s or down + this.doCrouch(); + player.frictionAir = 0.5; + } else if ((keys[32] || keys[38] || keys[87]) && this.buttonCD_jump + 20 < game.cycle) { //jump + this.buttonCD_jump = game.cycle; //can't jump until 20 cycles pass + Matter.Body.setVelocity(player, { //zero player velocity for consistant jumps + x: player.velocity.x, + y: 0 + }); + player.force.y = this.Fy / game.delta; //jump force / delta so that force is the same on game slowdowns + } + //horizontal move on ground + if (keys[37] || keys[65]) { //left or a + if (player.velocity.x > -this.VxMax) { + player.force.x += -this.Fx / game.delta; + } + } else if (keys[39] || keys[68]) { //right or d + if (player.velocity.x < this.VxMax) { + player.force.x += this.Fx / game.delta; + } + } + + } else { // in air ********************************** + //check for short jumps + if (this.buttonCD_jump + 60 > game.cycle && //just pressed jump + !(keys[32] || keys[38] || keys[87]) && //but not pressing jump key + this.Vy < 0) { // and velocity is up + Matter.Body.setVelocity(player, { //reduce player velocity every cycle until not true + x: player.velocity.x, + y: player.velocity.y * 0.94 + }); + } + if (keys[37] || keys[65]) { // move player left / a + if (player.velocity.x > -this.VxMax + 2) { + player.force.x += -this.FxAir / game.delta; + } + } else if (keys[39] || keys[68]) { //move player right / d + if (player.velocity.x < this.VxMax - 2) { + player.force.x += this.FxAir / game.delta; + } + } + } + //smoothly move height towards height goal ************ + this.yOff = this.yOff * 0.85 + this.yOffGoal * 0.15 + }; + this.death = function () { + location.reload(); + //Matter.Body.setPosition(player, this.spawnPos); + //Matter.Body.setVelocity(player, this.spawnVel); + //this.dropBody(); + //game.zoom = 0; //zooms out all the way + //this.health = 1; + } + this.health = 1; + this.regen = function () { + if (this.health < 1 && game.cycle % 15 === 0) { + this.addHealth(0.01); + } + }; + this.drawHealth = function () { + if (this.health < 1) { + ctx.fillStyle = 'rgba(100, 100, 100, 0.5)'; + ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, 60, 10); + ctx.fillStyle = "#f00"; + ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, 60 * this.health, 10); + } + }; + this.addHealth = function (heal) { + this.health += heal; + if (this.health > 1) this.health = 1; + } + this.damage = function (dmg) { + this.health -= dmg; + if (this.health <= 0) { + this.death(); + } + } + this.deathCheck = function () { + if (this.pos.y > game.fallHeight) { // if player is 4000px deep reset to spawn Position and Velocity + this.death(); + } + }; + this.hitMob = function (i, dmg) { + this.damage(dmg); + playSound("dmg" + Math.floor(Math.random() * 4)); + //extra kick between player and mob + //this section would be better with forces but they don't work... + let angle = Math.atan2(player.position.y - mob[i].position.y, player.position.x - mob[i].position.x); + Matter.Body.setVelocity(player, { + x: player.velocity.x + 8 * Math.cos(angle), + y: player.velocity.y + 8 * Math.sin(angle) + }); + Matter.Body.setVelocity(mob[i], { + x: mob[i].velocity.x - 8 * Math.cos(angle), + y: mob[i].velocity.y - 8 * Math.sin(angle) + }); + } + + this.buttonCD = 0; //cooldown for player buttons + this.eaten = []; + this.eat = function () { + if (keys[81] && this.buttonCD < game.cycle) { + this.buttonCD = game.cycle + 5; + this.findClosestBody(); + let i = this.closest.index + if (this.closest.dist < 150) { + //draw eating + ctx.lineWidth = 10; + ctx.strokeStyle = '#5f9'; + ctx.beginPath(); + ctx.moveTo(this.pos.x + 15 * Math.cos(this.angle), this.pos.y + 15 * Math.sin(this.angle)); + ctx.lineTo(body[i].position.x, body[i].position.y); + ctx.stroke(); + //set to eaten + body[i].eaten = true; + //drop body + body[i].frictionAir = 0; + this.isHolding = false; + holdConstraint.bodyB = jumpSensor; //set on sensor to get the constraint on somethign else + //add to eaten + body[i].collisionFilter.category = 0x000000; + body[i].collisionFilter.mask = 0x000000; + //Matter.Body.setStatic(body[i], true) + Matter.Sleeping.set(body[i], true) + Matter.Body.scale(body[i], 0.5, 0.5) + Matter.Body.setVelocity(body[i], { + x: 0, + y: 0 + }); + Matter.Body.setAngularVelocity(body[i], 0.08) //(Math.random()*0.5-1)*0.1) + //Matter.World.remove(engine.world, body[i]); + //body.splice(i, 1); + this.eaten[this.eaten.length] = { + index: i, + cycle: game.cycle + } + } + } + //control behavior of eaten bodies + for (let j = 0; j < this.eaten.length; j++) { + const pos = { + x: this.pos.x + 60 * Math.cos((game.cycle + this.eaten[j].cycle) * 0.05), + y: this.pos.y + 30 * Math.sin((game.cycle + this.eaten[j].cycle) * 0.05), + } + Matter.Body.setPosition(body[this.eaten[j].index], pos); + //Matter.Body.setVelocity(body[this.eaten[j].index],{x:0, y:0}); + + } + + if (keys[69] && this.buttonCD < game.cycle && this.eaten.length) { + this.buttonCD = game.cycle + 5; + let i = this.eaten[this.eaten.length - 1].index; + body[i].eaten = false; + body[i].collisionFilter.category = 0x000001; + body[i].collisionFilter.mask = 0x000101; + Matter.Body.setVelocity(body[i], { + x: 0, + y: 0 + }); + //Matter.Body.setStatic(body[i], false) + Matter.Sleeping.set(body[i], false) + Matter.Body.scale(body[i], 2, 2); + Matter.Body.setPosition(body[i], { + x: this.pos.x + 20 * Math.cos(this.angle), + y: this.pos.y + 20 * Math.sin(this.angle) + }); + const impulse = 0.06 * body[i].mass; + const f = { + x: impulse * Math.cos(this.angle) / game.delta, + y: impulse * Math.sin(this.angle) / game.delta + } + body[i].force = f; + this.eaten.pop(); + } + }; + + this.holdKeyDown = 0; + this.keyHold = function () { //checks for holding/dropping/picking up bodies + if (this.isHolding) { + //give the constaint more length and less stiffness if it is pulled out of position + const Dx = body[this.holdingBody].position.x - holdConstraint.pointA.x; + const Dy = body[this.holdingBody].position.y - holdConstraint.pointA.y; + holdConstraint.length = Math.sqrt(Dx * Dx + Dy * Dy) * 0.95; + holdConstraint.stiffness = -0.01 * holdConstraint.length + 1; + if (holdConstraint.length > 90) this.dropBody(); //drop it if the constraint gets too long + holdConstraint.pointA = { //set constraint position + x: this.pos.x + 50 * Math.cos(this.angle), //just in front of player nose + y: this.pos.y + 50 * Math.sin(this.angle) + }; + if (keys[81]) { // q = rotate the body + body[this.holdingBody].torque = 0.05 * body[this.holdingBody].mass; + } + //look for dropping held body + if (this.buttonCD < game.cycle) { + if (keys[69]) { //if holding e drops + this.holdKeyDown++; + } else if (this.holdKeyDown && !keys[69]) { + this.dropBody(); //if you hold down e long enough the body is thrown + this.throwBody(); + } + } + } else if (keys[69]) { //when not holding e = pick up body + this.findClosestBody(); + if (this.closest.dist < 150) { //pick up if distance closer then 100*100 + this.isHolding = true; + this.holdKeyDown = 0; + this.buttonCD = game.cycle + 20; + this.holdingBody = this.closest.index; //set new body to be the holdingBody + //body[this.closest.index].isSensor = true; //sensor seems a bit inconsistant + //body[this.holdingBody].collisionFilter.group = -2; //don't collide with player + body[mech.holdingBody].collisionFilter.category = 0x000001; + body[mech.holdingBody].collisionFilter.mask = 0x000001; + body[this.holdingBody].frictionAir = 0.1; //makes the holding body less jittery + holdConstraint.bodyB = body[this.holdingBody]; + holdConstraint.length = 0; + holdConstraint.pointA = { + x: this.pos.x + 50 * Math.cos(this.angle), + y: this.pos.y + 50 * Math.sin(this.angle) + }; + } + } + }; + this.dropBody = function () { + let timer; //reset player collision + function resetPlayerCollision() { + timer = setTimeout(function () { + const dx = mech.pos.x - body[mech.holdingBody].position.x + const dy = mech.pos.y - body[mech.holdingBody].position.y + if (dx * dx + dy * dy > 15000) { + body[mech.holdingBody].collisionFilter.category = 0x000001; + body[mech.holdingBody].collisionFilter.mask = 0x001101; + //body[mech.holdingBody].collisionFilter.group = 2; //can collide with player + } else { + resetPlayerCollision(); + } + }, 100); + } + resetPlayerCollision(); + this.isHolding = false; + body[this.holdingBody].frictionAir = 0.01; + holdConstraint.bodyB = jumpSensor; //set on sensor to get the constaint on somethign else + }; + this.throwMax = 150; + this.throwBody = function () { + let throwMag = 0; + if (this.holdKeyDown > 20) { + if (this.holdKeyDown > this.throwMax) this.holdKeyDown = this.throwMax; + //scale fire with mass and with holdKeyDown time + throwMag = body[this.holdingBody].mass * this.holdKeyDown * 0.001; + } + body[this.holdingBody].force.x = throwMag * Math.cos(this.angle); + body[this.holdingBody].force.y = throwMag * Math.sin(this.angle); + }; + this.isHolding = false; + this.holdingBody = 0; + this.closest = { + dist: 1000, + index: 0 + }; + this.lookingAtMob = function (mob, threshold) { + //calculate a vector from mob to player and make it length 1 + const diff = Matter.Vector.normalise(Matter.Vector.sub(mob.position, player.position)); + const dir = { //make a vector for the player's direction of length 1 + x: Math.cos(mech.angle), + y: Math.sin(mech.angle) + } + //the dot prodcut of diff and dir will return how much over lap between the vectors + const dot = Matter.Vector.dot(dir, diff); + if (dot > threshold) { + return true; + } else { + return false; + } + } + this.lookingAt = function (i) { + //calculate a vector from mob to player and make it length 1 + const diff = Matter.Vector.normalise(Matter.Vector.sub(body[i].position, player.position)); + const dir = { //make a vector for the player's direction of length 1 + x: Math.cos(mech.angle), + y: Math.sin(mech.angle) + } + //the dot prodcut of diff and dir will return how much over lap between the vectors + const dot = Matter.Vector.dot(dir, diff); + //console.log(dot); + if (dot > 0.9) { + return true; + } else { + return false; + } + } + this.findClosestBody = function () { + let mag = 100000; + let index = 0; + for (let i = 0; i < body.length; i++) { + let isLooking = this.lookingAt(i); + let collisionM = Matter.Query.ray(map, body[i].position, this.pos) + //let collisionB = Matter.Query.ray(body, body[i].position, this.pos) + if (collisionM.length) isLooking = false; + //magnitude of the distance between the poistion vectors of player and each body + const dist = Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)); + if (dist < mag && body[i].mass < player.mass && isLooking && !body[i].eaten) { + mag = dist; + index = i; + } + } + this.closest.dist = mag; + this.closest.index = index; + }; + this.exit = function () { + game.nextLevel(); + window.location.reload(false); + } + this.standingOnActions = function () { + if (this.onBody.type === 'map') { + var that = this; //brings the thisness of the player deeper into the actions object + var actions = { + 'death': function () { + that.death(); + }, + 'exit': function () { + that.exit(); + }, + 'slow': function () { + Matter.Body.setVelocity(player, { //reduce player velocity every cycle until not true + x: player.velocity.x * 0.5, + y: player.velocity.y * 0.5 + }); + }, + 'launch': function () { + //that.dropBody(); + Matter.Body.setVelocity(player, { //zero player velocity for consistant jumps + x: player.velocity.x, + y: 0 + }); + player.force.y = -0.1 * that.mass / game.delta; + }, + 'default': function () {} + }; + (actions[map[this.onBody.index].action] || actions['default'])(); + } + } + this.drawLeg = function (stroke) { + ctx.save(); + ctx.scale(this.flipLegs, 1); //leg lines + ctx.strokeStyle = stroke; + ctx.lineWidth = 7; + ctx.beginPath(); + ctx.moveTo(this.hip.x, this.hip.y); + ctx.lineTo(this.knee.x, this.knee.y); + ctx.lineTo(this.foot.x, this.foot.y); + ctx.stroke(); + //toe lines + ctx.lineWidth = 4; + ctx.beginPath(); + ctx.moveTo(this.foot.x, this.foot.y); + ctx.lineTo(this.foot.x - 15, this.foot.y + 5); + ctx.moveTo(this.foot.x, this.foot.y); + ctx.lineTo(this.foot.x + 15, this.foot.y + 5); + ctx.stroke(); + //hip joint + ctx.strokeStyle = '#333'; + ctx.fillStyle = this.fillColor; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.arc(this.hip.x, this.hip.y, 11, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + //knee joint + ctx.beginPath(); + ctx.arc(this.knee.x, this.knee.y, 7, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + //foot joint + ctx.beginPath(); + ctx.arc(this.foot.x, this.foot.y, 6, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + ctx.restore(); + }; + this.calcLeg = function (cycle_offset, offset) { + this.hip.x = 12 + offset; + this.hip.y = 24 + offset; + //stepSize goes to zero if Vx is zero or not on ground (make this transition cleaner) + this.stepSize = 0.9 * this.stepSize + 0.1 * (8 * Math.sqrt(Math.abs(this.Vx)) * this.onGround); + //changes to stepsize are smoothed by adding only a percent of the new value each cycle + const stepAngle = 0.037 * this.walk_cycle + cycle_offset; + this.foot.x = 2 * this.stepSize * Math.cos(stepAngle) + offset; + this.foot.y = offset + this.stepSize * Math.sin(stepAngle) + this.yOff + this.height; + const Ymax = this.yOff + this.height; + if (this.foot.y > Ymax) this.foot.y = Ymax; + + //calculate knee position as intersection of circle from hip and foot + const d = Math.sqrt((this.hip.x - this.foot.x) * (this.hip.x - this.foot.x) + + (this.hip.y - this.foot.y) * (this.hip.y - this.foot.y)); + const l = (this.legLength1 * this.legLength1 - this.legLength2 * this.legLength2 + d * d) / (2 * d); + const h = Math.sqrt(this.legLength1 * this.legLength1 - l * l); + this.knee.x = l / d * (this.foot.x - this.hip.x) - h / d * (this.foot.y - this.hip.y) + this.hip.x + offset; + this.knee.y = l / d * (this.foot.y - this.hip.y) + h / d * (this.foot.x - this.hip.x) + this.hip.y; + }; + this.draw = function () { + ctx.fillStyle = this.fillColor; + if (this.mouse.x > canvas.width / 2) { + this.flipLegs = 1; + } else { + this.flipLegs = -1; + } + this.walk_cycle += this.flipLegs * this.Vx; + + //draw body + ctx.save(); + ctx.translate(this.pos.x, this.pos.y); + this.calcLeg(Math.PI, -3); + this.drawLeg('#444'); + this.calcLeg(0, 0); + this.drawLeg('#333'); + ctx.rotate(this.angle); + ctx.strokeStyle = '#333'; + ctx.lineWidth = 2; + //ctx.fillStyle = this.fillColor; + let grd = ctx.createLinearGradient(-30, 0, 30, 0); + grd.addColorStop(0, this.fillColorDark); + grd.addColorStop(1, this.fillColor); + ctx.fillStyle = grd; + ctx.beginPath(); + //ctx.moveTo(0, 0); + ctx.arc(0, 0, 30, 0, 2 * Math.PI); + ctx.arc(15, 0, 4, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + ctx.restore(); + + //draw holding graphics + if (this.isHolding) { + if (this.holdKeyDown > 20) { + if (this.holdKeyDown > this.throwMax) { + ctx.strokeStyle = 'rgba(255, 0, 255, 0.8)'; + } else { + ctx.strokeStyle = 'rgba(255, 0, 255, ' + (0.2 + 0.4 * this.holdKeyDown / this.throwMax) + ')'; + } + } else { + ctx.strokeStyle = 'rgba(0, 255, 255, 0.2)'; + } + ctx.lineWidth = 10; + ctx.beginPath(); + ctx.moveTo(holdConstraint.bodyB.position.x + Math.random() * 2, + holdConstraint.bodyB.position.y + Math.random() * 2); + ctx.lineTo(this.pos.x + 15 * Math.cos(this.angle), this.pos.y + 15 * Math.sin(this.angle)); + //ctx.lineTo(holdConstraint.pointA.x,holdConstraint.pointA.y); + ctx.stroke(); + } + }; +}; + + + + +//global player variables +let player, jumpSensor, playerBody, playerHead, headSensor; + +// player Object Prototype ********************************************* +const mechProto = function () { + this.spawn = function () { + //player as a series of vertices + let vector = Vertices.fromPath('0 40 0 115 20 130 30 130 50 115 50 40'); + playerBody = Matter.Bodies.fromVertices(0, 0, vector); + //this sensor check if the player is on the ground to enable jumping + jumpSensor = Bodies.rectangle(0, 46, 36, 6, { + sleepThreshold: 99999999999, + isSensor: true, + }); + //this part of the player lowers on crouch + vector = Vertices.fromPath('0 -66 18 -82 0 -37 50 -37 50 -66 32 -82'); + playerHead = Matter.Bodies.fromVertices(0, -55, vector); + //a part of player that senses if the player's head is empty and can return after crouching + headSensor = Bodies.rectangle(0, -57, 48, 45, { + sleepThreshold: 99999999999, + isSensor: true, + }); + player = Body.create({ //combine jumpSensor and playerBody + parts: [playerBody, playerHead, jumpSensor, headSensor], + inertia: Infinity, //prevents player rotation + friction: 0.002, + //frictionStatic: 0.5, + restitution: 0.3, + sleepThreshold: Infinity, + collisionFilter: { + group: 0, + category: 0x001000, + mask: 0x010001 + }, + }); + //Matter.Body.setPosition(player, mech.spawnPos); + //Matter.Body.setVelocity(player, mech.spawnVel); + Matter.Body.setMass(player, mech.mass); + World.add(engine.world, [player]); + //holding body constraint + const holdConstraint = Constraint.create({ + pointA: { + x: 0, + y: 0 + }, + //setting constaint to jump sensor because it has to be on something until the player picks up things + bodyB: jumpSensor, + stiffness: 0.4, + }); + World.add(engine.world, holdConstraint); + }; + this.width = 50; + this.radius = 30; + this.fillColor = '#fff'; + this.fillColorDark = '#ddd'; + // const hue = '353'; + // const sat = '100'; + // const lit = '90'; + // this.fillColor = 'hsl('+hue+','+sat+'%,'+lit+'%)'; + // this.fillColorDark = 'hsl('+hue+','+(sat-10)+'%,'+(lit-10)+'%)'; + // this.guns = { + // machine: { + // key: 49, + // ammo: infinite, + // isActive: true, + // isAvailable: true, + // }, + // needle: { + // key: 50, + // ammo: 10, + // isActive: false, + // isAvailable: true, + // }, + // shot: { + // key: 51, + // ammo: 10, + // isActive: false, + // isAvailable: true, + // } + // } + this.fireCDcycle = 0; + this.gun = 'machine'; //current gun in use + this.gunOptions = { //keeps track of keys that switch guns (used in the onkeypress event) + 49: 'machine', + 50: 'needle', + 51: 'shot', + 52: 'rail', + 53: 'cannon', + 54: 'super', + 55: 'lob', + // 55: 'spiritBomb', + // 56: 'experimental' + } + this.height = 42; + this.yOffWhen = { + crouch: 22, + stand: 49, + jump: 70 + } + this.yOff = 70; + this.yOffGoal = 70; + this.onGround = false; //checks if on ground or in air + this.onBody = { + id: 0, + index: 0, + type: "map", + action: '' + }; + this.numTouching = 0; + this.crouch = false; + this.isHeadClear = true; + this.spawnPos = { + x: 0, + y: 0 + }; + this.spawnVel = { + x: 0, + y: 0 + }; + this.pos = { + x: this.spawnPos.x, + y: this.spawnPos.y + }; + this.setPosToSpawn = function (xPos, yPos) { + this.spawnPos.x = xPos + this.spawnPos.y = yPos + this.pos.x = xPos; + this.pos.y = yPos; + this.Vx = this.spawnVel.x; + this.Vy = this.spawnVel.y; + Matter.Body.setPosition(player, this.spawnPos); + Matter.Body.setVelocity(player, this.spawnVel); + }; + this.Sy = this.pos.y; //adds a smoothing effect to vertical only + this.Vx = 0; + this.Vy = 0; + this.VxMax = 7; + this.mass = 5; + this.Fx = 0.004 * this.mass; //run Force on ground + this.FxAir = 0.0006 * this.mass; //run Force in Air + this.Fy = -0.05 * this.mass; //jump Force + this.angle = 0; + this.walk_cycle = 0; + this.stepSize = 0; + this.flipLegs = -1; + this.hip = { + x: 12, + y: 24, + }; + this.knee = { + x: 0, + y: 0, + x2: 0, + y2: 0 + }; + this.foot = { + x: 0, + y: 0 + }; + this.legLength1 = 55; + this.legLength2 = 45; + this.canvasX = canvas.width / 2; + this.canvasY = canvas.height / 2; + this.transX = this.canvasX - this.pos.x; + this.transY = this.canvasX - this.pos.x; + this.mouse = { + x: canvas.width / 3, + y: canvas.height + }; + this.getMousePos = function (x, y) { + this.mouse.x = x; + this.mouse.y = y; + }; + this.testingMoveLook = function () { + //move + this.pos.x = player.position.x; + this.pos.y = playerBody.position.y - this.yOff; + this.Vx = player.velocity.x; + this.Vy = player.velocity.y; + //look + this.canvasX = canvas.width / 2 + this.canvasY = canvas.height / 2 + this.transX = this.canvasX - this.pos.x; + this.transY = this.canvasY - this.pos.y; + this.angle = Math.atan2(this.mouse.y - this.canvasY, this.mouse.x - this.canvasX); + } + + this.move = function () { + this.pos.x = player.position.x; + this.pos.y = playerBody.position.y - this.yOff; + this.Vx = player.velocity.x; + this.Vy = player.velocity.y; + }; + this.look = function () { + //set a max on mouse look + let mX = this.mouse.x; + if (mX > canvas.width * 0.8) { + mX = canvas.width * 0.8; + } else if (mX < canvas.width * 0.2) { + mX = canvas.width * 0.2; + } + let mY = this.mouse.y; + if (mY > canvas.height * 0.8) { + mY = canvas.height * 0.8; + } else if (mY < canvas.height * 0.2) { + mY = canvas.height * 0.2; + } + //set mouse look + this.canvasX = this.canvasX * 0.94 + (canvas.width - mX) * 0.06; + this.canvasY = this.canvasY * 0.94 + (canvas.height - mY) * 0.06; + + //set translate values + this.transX = this.canvasX - this.pos.x; + this.Sy = 0.99 * this.Sy + 0.01 * (this.pos.y); + //hard caps how behind y position tracking can get. + if (this.Sy - this.pos.y > canvas.height / 2) { + this.Sy = this.pos.y + canvas.height / 2; + } else if (this.Sy - this.pos.y < -canvas.height / 2) { + this.Sy = this.pos.y - canvas.height / 2; + } + this.transY = this.canvasY - this.Sy; //gradual y camera tracking + //this.transY = this.canvasY - this.pos.y; //normal y camera tracking + + //make player head angled towards mouse + //this.angle = Math.atan2(this.mouse.y - this.canvasY, this.mouse.x - this.canvasX); + this.angle = Math.atan2(this.mouse.y + (this.Sy - this.pos.y) * game.zoom - this.canvasY, this.mouse.x - this.canvasX); + }; + this.doCrouch = function () { + if (!this.crouch) { + this.crouch = true; + this.yOffGoal = this.yOffWhen.crouch; + Matter.Body.translate(playerHead, { + x: 0, + y: 40 + }) + } + } + this.undoCrouch = function () { + this.crouch = false; + this.yOffGoal = this.yOffWhen.stand; + Matter.Body.translate(playerHead, { + x: 0, + y: -40 + }) + } + this.enterAir = function () { + this.onGround = false; + player.frictionAir = 0.001; + if (this.isHeadClear) { + if (this.crouch) { + this.undoCrouch(); + } + this.yOffGoal = this.yOffWhen.jump; + }; + } + this.enterLand = function () { + this.onGround = true; + if (this.crouch) { + if (this.isHeadClear) { + this.undoCrouch(); + player.frictionAir = 0.12; + } else { + this.yOffGoal = this.yOffWhen.crouch; + player.frictionAir = 0.5; + } + } else { + this.yOffGoal = this.yOffWhen.stand; + player.frictionAir = 0.12; + } + }; + this.buttonCD_jump = 0; //cooldown for player buttons + this.keyMove = function () { + if (this.onGround) { //on ground ********************** + if (this.crouch) { //crouch + if (!(keys[40] || keys[83]) && this.isHeadClear) { //not pressing crouch anymore + this.undoCrouch(); + player.frictionAir = 0.12; + } + } else if (keys[40] || keys[83]) { //on ground && not crouched and pressing s or down + this.doCrouch(); + player.frictionAir = 0.5; + } else if ((keys[32] || keys[38] || keys[87]) && this.buttonCD_jump + 20 < game.cycle) { //jump + this.buttonCD_jump = game.cycle; //can't jump until 20 cycles pass + Matter.Body.setVelocity(player, { //zero player velocity for consistant jumps + x: player.velocity.x, + y: 0 + }); + player.force.y = this.Fy / game.delta; //jump force / delta so that force is the same on game slowdowns + } + //horizontal move on ground + if (keys[37] || keys[65]) { //left or a + if (player.velocity.x > -this.VxMax) { + player.force.x += -this.Fx / game.delta; + } + } else if (keys[39] || keys[68]) { //right or d + if (player.velocity.x < this.VxMax) { + player.force.x += this.Fx / game.delta; + } + } + + } else { // in air ********************************** + //check for short jumps + if (this.buttonCD_jump + 60 > game.cycle && //just pressed jump + !(keys[32] || keys[38] || keys[87]) && //but not pressing jump key + this.Vy < 0) { // and velocity is up + Matter.Body.setVelocity(player, { //reduce player velocity every cycle until not true + x: player.velocity.x, + y: player.velocity.y * 0.94 + }); + } + if (keys[37] || keys[65]) { // move player left / a + if (player.velocity.x > -this.VxMax + 2) { + player.force.x += -this.FxAir / game.delta; + } + } else if (keys[39] || keys[68]) { //move player right / d + if (player.velocity.x < this.VxMax - 2) { + player.force.x += this.FxAir / game.delta; + } + } + } + //smoothly move height towards height goal ************ + this.yOff = this.yOff * 0.85 + this.yOffGoal * 0.15 + }; + this.death = function () { + location.reload(); + //Matter.Body.setPosition(player, this.spawnPos); + //Matter.Body.setVelocity(player, this.spawnVel); + //this.dropBody(); + //game.zoom = 0; //zooms out all the way + //this.health = 1; + } + this.health = 1; + this.regen = function () { + if (this.health < 1 && game.cycle % 15 === 0) { + this.addHealth(0.01); + } + }; + this.drawHealth = function () { + if (this.health < 1) { + ctx.fillStyle = 'rgba(100, 100, 100, 0.5)'; + ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, 60, 10); + ctx.fillStyle = "#f00"; + ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, 60 * this.health, 10); + } + }; + this.addHealth = function (heal) { + this.health += heal; + if (this.health > 1) this.health = 1; + } + this.damage = function (dmg) { + this.health -= dmg; + if (this.health <= 0) { + this.death(); + } + } + this.deathCheck = function () { + if (this.pos.y > game.fallHeight) { // if player is 4000px deep reset to spawn Position and Velocity + this.death(); + } + }; + this.hitMob = function (i, dmg) { + this.damage(dmg); + playSound("dmg" + Math.floor(Math.random() * 4)); + //extra kick between player and mob + //this section would be better with forces but they don't work... + let angle = Math.atan2(player.position.y - mob[i].position.y, player.position.x - mob[i].position.x); + Matter.Body.setVelocity(player, { + x: player.velocity.x + 8 * Math.cos(angle), + y: player.velocity.y + 8 * Math.sin(angle) + }); + Matter.Body.setVelocity(mob[i], { + x: mob[i].velocity.x - 8 * Math.cos(angle), + y: mob[i].velocity.y - 8 * Math.sin(angle) + }); + } + + this.buttonCD = 0; //cooldown for player buttons + this.eaten = []; + this.eat = function () { + if (keys[81] && this.buttonCD < game.cycle) { + this.buttonCD = game.cycle + 5; + this.findClosestBody(); + let i = this.closest.index + if (this.closest.dist < 150) { + //draw eating + ctx.lineWidth = 10; + ctx.strokeStyle = '#5f9'; + ctx.beginPath(); + ctx.moveTo(this.pos.x + 15 * Math.cos(this.angle), this.pos.y + 15 * Math.sin(this.angle)); + ctx.lineTo(body[i].position.x, body[i].position.y); + ctx.stroke(); + //set to eaten + body[i].eaten = true; + //drop body + body[i].frictionAir = 0; + this.isHolding = false; + holdConstraint.bodyB = jumpSensor; //set on sensor to get the constraint on somethign else + //add to eaten + body[i].collisionFilter.category = 0x000000; + body[i].collisionFilter.mask = 0x000000; + //Matter.Body.setStatic(body[i], true) + Matter.Sleeping.set(body[i], true) + Matter.Body.scale(body[i], 0.5, 0.5) + Matter.Body.setVelocity(body[i], { + x: 0, + y: 0 + }); + Matter.Body.setAngularVelocity(body[i], 0.08) //(Math.random()*0.5-1)*0.1) + //Matter.World.remove(engine.world, body[i]); + //body.splice(i, 1); + this.eaten[this.eaten.length] = { + index: i, + cycle: game.cycle + } + } + } + //control behavior of eaten bodies + for (let j = 0; j < this.eaten.length; j++) { + const pos = { + x: this.pos.x + 60 * Math.cos((game.cycle + this.eaten[j].cycle) * 0.05), + y: this.pos.y + 30 * Math.sin((game.cycle + this.eaten[j].cycle) * 0.05), + } + Matter.Body.setPosition(body[this.eaten[j].index], pos); + //Matter.Body.setVelocity(body[this.eaten[j].index],{x:0, y:0}); + + } + + if (keys[69] && this.buttonCD < game.cycle && this.eaten.length) { + this.buttonCD = game.cycle + 5; + let i = this.eaten[this.eaten.length - 1].index; + body[i].eaten = false; + body[i].collisionFilter.category = 0x000001; + body[i].collisionFilter.mask = 0x000101; + Matter.Body.setVelocity(body[i], { + x: 0, + y: 0 + }); + //Matter.Body.setStatic(body[i], false) + Matter.Sleeping.set(body[i], false) + Matter.Body.scale(body[i], 2, 2); + Matter.Body.setPosition(body[i], { + x: this.pos.x + 20 * Math.cos(this.angle), + y: this.pos.y + 20 * Math.sin(this.angle) + }); + const impulse = 0.06 * body[i].mass; + const f = { + x: impulse * Math.cos(this.angle) / game.delta, + y: impulse * Math.sin(this.angle) / game.delta + } + body[i].force = f; + this.eaten.pop(); + } + }; + + this.holdKeyDown = 0; + this.keyHold = function () { //checks for holding/dropping/picking up bodies + if (this.isHolding) { + //give the constaint more length and less stiffness if it is pulled out of position + const Dx = body[this.holdingBody].position.x - holdConstraint.pointA.x; + const Dy = body[this.holdingBody].position.y - holdConstraint.pointA.y; + holdConstraint.length = Math.sqrt(Dx * Dx + Dy * Dy) * 0.95; + holdConstraint.stiffness = -0.01 * holdConstraint.length + 1; + if (holdConstraint.length > 90) this.dropBody(); //drop it if the constraint gets too long + holdConstraint.pointA = { //set constraint position + x: this.pos.x + 50 * Math.cos(this.angle), //just in front of player nose + y: this.pos.y + 50 * Math.sin(this.angle) + }; + if (keys[81]) { // q = rotate the body + body[this.holdingBody].torque = 0.05 * body[this.holdingBody].mass; + } + //look for dropping held body + if (this.buttonCD < game.cycle) { + if (keys[69]) { //if holding e drops + this.holdKeyDown++; + } else if (this.holdKeyDown && !keys[69]) { + this.dropBody(); //if you hold down e long enough the body is thrown + this.throwBody(); + } + } + } else if (keys[69]) { //when not holding e = pick up body + this.findClosestBody(); + if (this.closest.dist < 150) { //pick up if distance closer then 100*100 + this.isHolding = true; + this.holdKeyDown = 0; + this.buttonCD = game.cycle + 20; + this.holdingBody = this.closest.index; //set new body to be the holdingBody + //body[this.closest.index].isSensor = true; //sensor seems a bit inconsistant + //body[this.holdingBody].collisionFilter.group = -2; //don't collide with player + body[mech.holdingBody].collisionFilter.category = 0x000001; + body[mech.holdingBody].collisionFilter.mask = 0x000001; + body[this.holdingBody].frictionAir = 0.1; //makes the holding body less jittery + holdConstraint.bodyB = body[this.holdingBody]; + holdConstraint.length = 0; + holdConstraint.pointA = { + x: this.pos.x + 50 * Math.cos(this.angle), + y: this.pos.y + 50 * Math.sin(this.angle) + }; + } + } + }; + this.dropBody = function () { + let timer; //reset player collision + function resetPlayerCollision() { + timer = setTimeout(function () { + const dx = mech.pos.x - body[mech.holdingBody].position.x + const dy = mech.pos.y - body[mech.holdingBody].position.y + if (dx * dx + dy * dy > 15000) { + body[mech.holdingBody].collisionFilter.category = 0x000001; + body[mech.holdingBody].collisionFilter.mask = 0x001101; + //body[mech.holdingBody].collisionFilter.group = 2; //can collide with player + } else { + resetPlayerCollision(); + } + }, 100); + } + resetPlayerCollision(); + this.isHolding = false; + body[this.holdingBody].frictionAir = 0.01; + holdConstraint.bodyB = jumpSensor; //set on sensor to get the constaint on somethign else + }; + this.throwMax = 150; + this.throwBody = function () { + let throwMag = 0; + if (this.holdKeyDown > 20) { + if (this.holdKeyDown > this.throwMax) this.holdKeyDown = this.throwMax; + //scale fire with mass and with holdKeyDown time + throwMag = body[this.holdingBody].mass * this.holdKeyDown * 0.001; + } + body[this.holdingBody].force.x = throwMag * Math.cos(this.angle); + body[this.holdingBody].force.y = throwMag * Math.sin(this.angle); + }; + this.isHolding = false; + this.holdingBody = 0; + this.closest = { + dist: 1000, + index: 0 + }; + this.lookingAtMob = function (mob, threshold) { + //calculate a vector from mob to player and make it length 1 + const diff = Matter.Vector.normalise(Matter.Vector.sub(mob.position, player.position)); + const dir = { //make a vector for the player's direction of length 1 + x: Math.cos(mech.angle), + y: Math.sin(mech.angle) + } + //the dot prodcut of diff and dir will return how much over lap between the vectors + const dot = Matter.Vector.dot(dir, diff); + if (dot > threshold) { + return true; + } else { + return false; + } + } + this.lookingAt = function (i) { + //calculate a vector from mob to player and make it length 1 + const diff = Matter.Vector.normalise(Matter.Vector.sub(body[i].position, player.position)); + const dir = { //make a vector for the player's direction of length 1 + x: Math.cos(mech.angle), + y: Math.sin(mech.angle) + } + //the dot prodcut of diff and dir will return how much over lap between the vectors + const dot = Matter.Vector.dot(dir, diff); + //console.log(dot); + if (dot > 0.9) { + return true; + } else { + return false; + } + } + this.findClosestBody = function () { + let mag = 100000; + let index = 0; + for (let i = 0; i < body.length; i++) { + let isLooking = this.lookingAt(i); + let collisionM = Matter.Query.ray(map, body[i].position, this.pos) + //let collisionB = Matter.Query.ray(body, body[i].position, this.pos) + if (collisionM.length) isLooking = false; + //magnitude of the distance between the poistion vectors of player and each body + const dist = Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)); + if (dist < mag && body[i].mass < player.mass && isLooking && !body[i].eaten) { + mag = dist; + index = i; + } + } + this.closest.dist = mag; + this.closest.index = index; + }; + this.exit = function () { + game.nextLevel(); + window.location.reload(false); + } + this.standingOnActions = function () { + if (this.onBody.type === 'map') { + var that = this; //brings the thisness of the player deeper into the actions object + var actions = { + 'death': function () { + that.death(); + }, + 'exit': function () { + that.exit(); + }, + 'slow': function () { + Matter.Body.setVelocity(player, { //reduce player velocity every cycle until not true + x: player.velocity.x * 0.5, + y: player.velocity.y * 0.5 + }); + }, + 'launch': function () { + //that.dropBody(); + Matter.Body.setVelocity(player, { //zero player velocity for consistant jumps + x: player.velocity.x, + y: 0 + }); + player.force.y = -0.1 * that.mass / game.delta; + }, + 'default': function () {} + }; + (actions[map[this.onBody.index].action] || actions['default'])(); + } + } + this.drawLeg = function (stroke) { + ctx.save(); + ctx.scale(this.flipLegs, 1); //leg lines + ctx.strokeStyle = stroke; + ctx.lineWidth = 7; + ctx.beginPath(); + ctx.moveTo(this.hip.x, this.hip.y); + ctx.lineTo(this.knee.x, this.knee.y); + ctx.lineTo(this.foot.x, this.foot.y); + ctx.stroke(); + //toe lines + ctx.lineWidth = 4; + ctx.beginPath(); + ctx.moveTo(this.foot.x, this.foot.y); + ctx.lineTo(this.foot.x - 15, this.foot.y + 5); + ctx.moveTo(this.foot.x, this.foot.y); + ctx.lineTo(this.foot.x + 15, this.foot.y + 5); + ctx.stroke(); + //hip joint + ctx.strokeStyle = '#333'; + ctx.fillStyle = this.fillColor; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.arc(this.hip.x, this.hip.y, 11, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + //knee joint + ctx.beginPath(); + ctx.arc(this.knee.x, this.knee.y, 7, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + //foot joint + ctx.beginPath(); + ctx.arc(this.foot.x, this.foot.y, 6, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + ctx.restore(); + }; + this.calcLeg = function (cycle_offset, offset) { + this.hip.x = 12 + offset; + this.hip.y = 24 + offset; + //stepSize goes to zero if Vx is zero or not on ground (make this transition cleaner) + this.stepSize = 0.9 * this.stepSize + 0.1 * (8 * Math.sqrt(Math.abs(this.Vx)) * this.onGround); + //changes to stepsize are smoothed by adding only a percent of the new value each cycle + const stepAngle = 0.037 * this.walk_cycle + cycle_offset; + this.foot.x = 2 * this.stepSize * Math.cos(stepAngle) + offset; + this.foot.y = offset + this.stepSize * Math.sin(stepAngle) + this.yOff + this.height; + const Ymax = this.yOff + this.height; + if (this.foot.y > Ymax) this.foot.y = Ymax; + + //calculate knee position as intersection of circle from hip and foot + const d = Math.sqrt((this.hip.x - this.foot.x) * (this.hip.x - this.foot.x) + + (this.hip.y - this.foot.y) * (this.hip.y - this.foot.y)); + const l = (this.legLength1 * this.legLength1 - this.legLength2 * this.legLength2 + d * d) / (2 * d); + const h = Math.sqrt(this.legLength1 * this.legLength1 - l * l); + this.knee.x = l / d * (this.foot.x - this.hip.x) - h / d * (this.foot.y - this.hip.y) + this.hip.x + offset; + this.knee.y = l / d * (this.foot.y - this.hip.y) + h / d * (this.foot.x - this.hip.x) + this.hip.y; + }; + this.draw = function () { + ctx.fillStyle = this.fillColor; + if (this.mouse.x > canvas.width / 2) { + this.flipLegs = 1; + } else { + this.flipLegs = -1; + } + this.walk_cycle += this.flipLegs * this.Vx; + + //draw body + ctx.save(); + ctx.translate(this.pos.x, this.pos.y); + this.calcLeg(Math.PI, -3); + this.drawLeg('#444'); + this.calcLeg(0, 0); + this.drawLeg('#333'); + ctx.rotate(this.angle); + ctx.strokeStyle = '#333'; + ctx.lineWidth = 2; + //ctx.fillStyle = this.fillColor; + let grd = ctx.createLinearGradient(-30, 0, 30, 0); + grd.addColorStop(0, this.fillColorDark); + grd.addColorStop(1, this.fillColor); + ctx.fillStyle = grd; + ctx.beginPath(); + //ctx.moveTo(0, 0); + ctx.arc(0, 0, 30, 0, 2 * Math.PI); + ctx.arc(15, 0, 4, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + ctx.restore(); + + //draw holding graphics + if (this.isHolding) { + if (this.holdKeyDown > 20) { + if (this.holdKeyDown > this.throwMax) { + ctx.strokeStyle = 'rgba(255, 0, 255, 0.8)'; + } else { + ctx.strokeStyle = 'rgba(255, 0, 255, ' + (0.2 + 0.4 * this.holdKeyDown / this.throwMax) + ')'; + } + } else { + ctx.strokeStyle = 'rgba(0, 255, 255, 0.2)'; + } + ctx.lineWidth = 10; + ctx.beginPath(); + ctx.moveTo(holdConstraint.bodyB.position.x + Math.random() * 2, + holdConstraint.bodyB.position.y + Math.random() * 2); + ctx.lineTo(this.pos.x + 15 * Math.cos(this.angle), this.pos.y + 15 * Math.sin(this.angle)); + //ctx.lineTo(holdConstraint.pointA.x,holdConstraint.pointA.y); + ctx.stroke(); + } + }; +}; + + + + +//makes the player object based on mechprototype +const mech = new mechProto(); \ No newline at end of file diff --git a/js/bullets.js b/js/bullets.js new file mode 100644 index 0000000..0fa8278 --- /dev/null +++ b/js/bullets.js @@ -0,0 +1,843 @@ +let bullet = []; + +const b = { + dmgScale: null, //scales all gun damage from momentum, but not raw .dmg //this is reset in game.reset + gravity: 0.0006, //most other bodies have gravity = 0.001 + activeGun: null, //current gun in use by player + inventoryGun: 0, + inventory: [0], //list of what guns player has // 0 starts with basic gun + giveGuns(gun = "all", ammoPacks = 2) { + if (gun === "all") { + b.activeGun = 0; + for (let i = 0; i < b.guns.length; i++) { + b.guns[i].have = true; + b.guns[i].ammo = b.guns[i].ammoPack * ammoPacks; + b.inventory[i] = i; + } + } else { + if (!b.guns[gun].have) b.inventory.push(gun); + b.activeGun = gun; + b.guns[gun].have = true; + b.guns[gun].ammo = b.guns[gun].ammoPack * ammoPacks; + } + game.makeGunHUD(); + }, + fireProps(cd, speed, dir, me) { + mech.fireCDcycle = game.cycle + cd; // cool down + Matter.Body.setVelocity(bullet[me], { + x: mech.Vx / 2 + speed * Math.cos(dir), + y: mech.Vy / 2 + speed * Math.sin(dir) + }); + World.add(engine.world, bullet[me]); //add bullet to world + }, + fireAttributes(dir) { + return { + // density: 0.0015, //frictionAir: 0.01, //restitution: 0, + angle: dir, + friction: 0.5, + frictionAir: 0, + dmg: 0, //damage done in addition to the damage from momentum + classType: "bullet", + collisionFilter: { + category: 0x000100, + mask: 0x000011 //mask: 0x000101, //for self collision + }, + minDmgSpeed: 10, + onDmg() {}, //this.endCycle = 0 //triggers despawn + onEnd() {} + }; + }, + muzzleFlash(radius = 10) { + ctx.fillStyle = "#fb0"; + ctx.beginPath(); + ctx.arc(mech.pos.x + 35 * Math.cos(mech.angle), mech.pos.y + 35 * Math.sin(mech.angle), radius, 0, 2 * Math.PI); + ctx.fill(); + }, + drawOneBullet(vertices) { + ctx.beginPath(); + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.fillStyle = "#000"; + ctx.fill(); + }, + removeConsBB(me) { + for (let i = 0, len = consBB.length; i < len; ++i) { + if (consBB[i].bodyA === me) { + consBB[i].bodyA = consBB[i].bodyB; + consBB.splice(i, 1); + // b.removeConsBB(me); + break; + } else if (consBB[i].bodyB === me) { + consBB[i].bodyB = consBB[i].bodyA; + consBB.splice(i, 1); + // b.removeConsBB(me); + break; + } + } + }, + explode(me) { + // typically explode is used for some bullets with .onEnd + + //add dmg to draw queue + game.drawList.push({ + x: bullet[me].position.x, + y: bullet[me].position.y, + radius: bullet[me].explodeRad, + color: "rgba(255,0,0,0.4)", + time: game.drawTime + }); + let dist, sub, knock; + const dmg = b.dmgScale * bullet[me].explodeRad * 0.01; + + const alertRange = 100 + bullet[me].explodeRad * 2; //alert range + //add alert to draw queue + game.drawList.push({ + x: bullet[me].position.x, + y: bullet[me].position.y, + radius: alertRange, + color: "rgba(100,20,0,0.03)", + time: game.drawTime + }); + + //player damage and knock back + sub = Matter.Vector.sub(bullet[me].position, player.position); + dist = Matter.Vector.magnitude(sub); + if (dist < bullet[me].explodeRad) { + mech.damage(bullet[me].explodeRad * 0.00035); + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), -Math.sqrt(dmg) * player.mass / 30); + player.force.x += knock.x; + player.force.y += knock.y; + mech.drop(); + } else if (dist < alertRange) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), -Math.sqrt(dmg) * player.mass / 55); + player.force.x += knock.x; + player.force.y += knock.y; + mech.drop(); + } + + //body knock backs + for (let i = 0, len = body.length; i < len; ++i) { + sub = Matter.Vector.sub(bullet[me].position, body[i].position); + dist = Matter.Vector.magnitude(sub); + if (dist < bullet[me].explodeRad) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) / 18); + body[i].force.x += knock.x; + body[i].force.y += knock.y; + } else if (dist < alertRange) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) / 40); + body[i].force.x += knock.x; + body[i].force.y += knock.y; + } + } + + //power up knock backs + for (let i = 0, len = powerUp.length; i < len; ++i) { + sub = Matter.Vector.sub(bullet[me].position, powerUp[i].position); + dist = Matter.Vector.magnitude(sub); + if (dist < bullet[me].explodeRad) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) / 26); + powerUp[i].force.x += knock.x; + powerUp[i].force.y += knock.y; + } else if (dist < alertRange) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) / 40); + powerUp[i].force.x += knock.x; + powerUp[i].force.y += knock.y; + } + } + + //bullet knock backs + for (let i = 0, len = bullet.length; i < len; ++i) { + if (me !== i) { + sub = Matter.Vector.sub(bullet[me].position, bullet[i].position); + dist = Matter.Vector.magnitude(sub); + if (dist < bullet[me].explodeRad) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * bullet[i].mass) / 10); + bullet[i].force.x += knock.x; + bullet[i].force.y += knock.y; + } else if (dist < alertRange) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * bullet[i].mass) / 20); + bullet[i].force.x += knock.x; + bullet[i].force.y += knock.y; + } + } + } + + //destroy all bullets in range + // for (let i = 0, len = bullet.length; i < len; ++i) { + // if (me != i) { + // sub = Matter.Vector.sub(bullet[me].position, bullet[i].position); + // dist = Matter.Vector.magnitude(sub); + // if (dist < bullet[me].explodeRad) { + // bullet[i].endCycle = game.cycle; + // } + // } + // } + + //mob damage and knock back with no alert + // for (let i = 0, len = mob.length; i < len; ++i) { + // if (mob[i].alive) { + // let vertices = mob[i].vertices; + // for (let j = 0, len = vertices.length; j < len; j++) { + // sub = Matter.Vector.sub(bullet[me].position, vertices[j]); + // dist = Matter.Vector.magnitude(sub); + // if (dist < bullet[me].explodeRad) { + // mob[i].damage(dmg); + // mob[i].locatePlayer(); + // knock = Matter.Vector.mult(Matter.Vector.normalise(sub), -Math.sqrt(dmg) * mob[i].mass / 18); + // mob[i].force.x += knock.x; + // mob[i].force.y += knock.y; + // break; + // } + // } + // } + // } + + //mob damage and knock back with alert + let damageScale = 1; // reduce dmg for each new target to limit total AOE damage + for (let i = 0, len = mob.length; i < len; ++i) { + if (mob[i].alive) { + sub = Matter.Vector.sub(bullet[me].position, mob[i].position); + dist = Matter.Vector.magnitude(sub); + if (dist < bullet[me].explodeRad) { + mob[i].damage(dmg * damageScale); + mob[i].locatePlayer(); + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) / 18); + mob[i].force.x += knock.x; + mob[i].force.y += knock.y; + damageScale *= 0.85 + } else if (!mob[i].seePlayer.recall && dist < alertRange) { + mob[i].locatePlayer(); + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) / 35); + mob[i].force.x += knock.x; + mob[i].force.y += knock.y; + } + } + } + + // Matter.Vector.magnitudeSquared(Matter.Vector.sub(bullet[me].position, mob[i].position)) + }, + guns: [ + // { + // name: "field emitter", + // ammo: Infinity, + // ammoPack: Infinity, + // have: true, + // fire() {} + // }, + { + name: "laser", + ammo: 0, + ammoPack: 350, + have: false, + fire() { + //mech.fireCDcycle = game.cycle + 1 + let best; + const color = "#f00"; + const range = 3000; + const path = [{ + x: mech.pos.x + 20 * Math.cos(mech.angle), + y: mech.pos.y + 20 * Math.sin(mech.angle) + }, + { + x: mech.pos.x + range * Math.cos(mech.angle), + y: mech.pos.y + range * Math.sin(mech.angle) + } + ]; + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + }; + const checkforCollisions = function () { + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + vertexCollision(path[path.length - 2], path[path.length - 1], mob); + vertexCollision(path[path.length - 2], path[path.length - 1], map); + vertexCollision(path[path.length - 2], path[path.length - 1], body); + }; + const laserHitMob = function (dmg) { + if (best.who.alive) { + dmg *= b.dmgScale * 0.065; + best.who.damage(dmg); + best.who.locatePlayer(); + //draw mob damage circle + ctx.fillStyle = color; + ctx.beginPath(); + ctx.arc(path[path.length - 1].x, path[path.length - 1].y, Math.sqrt(dmg) * 100, 0, 2 * Math.PI); + ctx.fill(); + } + }; + + const reflection = function () { + // https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector + const n = Matter.Vector.perp(Matter.Vector.normalise(Matter.Vector.sub(best.v1, best.v2))); + const d = Matter.Vector.sub(path[path.length - 1], path[path.length - 2]); + const nn = Matter.Vector.mult(n, 2 * Matter.Vector.dot(d, n)); + const r = Matter.Vector.normalise(Matter.Vector.sub(d, nn)); + path[path.length] = Matter.Vector.add(Matter.Vector.mult(r, range), path[path.length - 1]); + }; + //beam before reflection + checkforCollisions(); + if (best.dist2 != Infinity) { + //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + laserHitMob(1); + + //1st reflection beam + reflection(); + //ugly bug fix: this stops the reflection on a bug where the beam gets trapped inside a body + let who = best.who; + checkforCollisions(); + if (best.dist2 != Infinity) { + //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + laserHitMob(0.75); + + //2nd reflection beam + //ugly bug fix: this stops the reflection on a bug where the beam gets trapped inside a body + if (who !== best.who) { + reflection(); + checkforCollisions(); + if (best.dist2 != Infinity) { + //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + laserHitMob(0.5); + } + } + } + } + ctx.fillStyle = color; + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.lineDashOffset = 300 * Math.random() + // ctx.setLineDash([200 * Math.random(), 250 * Math.random()]); + + ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]); + for (let i = 1, len = path.length; i < len; ++i) { + ctx.beginPath(); + ctx.moveTo(path[i - 1].x, path[i - 1].y); + ctx.lineTo(path[i].x, path[i].y); + ctx.stroke(); + ctx.globalAlpha *= 0.5; //reflections are less intense + // ctx.globalAlpha -= 0.1; //reflections are less intense + } + ctx.setLineDash([0, 0]); + ctx.globalAlpha = 1; + } + }, + { + name: "rapid fire", + ammo: 0, + ammoPack: 100, + have: false, + fire() { + const me = bullet.length; + b.muzzleFlash(15); + // if (Math.random() > 0.2) mobs.alert(500); + const dir = (Math.random() - 0.5) * 0.15 + mech.angle; + bullet[me] = Bodies.rectangle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 17, 5, b.fireAttributes(dir)); + b.fireProps(5, 40, dir, me); //cd , speed + bullet[me].endCycle = game.cycle + 60; + bullet[me].frictionAir = 0.01; + bullet[me].do = function () { + this.force.y += this.mass * 0.001; + }; + } + }, + { + name: "spray", + ammo: 0, + ammoPack: 8, + have: false, + fire() { + b.muzzleFlash(35); + // mobs.alert(650); + for (let i = 0; i < 9; i++) { + const me = bullet.length; + const dir = (Math.random() - 0.5) * 0.6 + mech.angle; + bullet[me] = Bodies.rectangle( + mech.pos.x + 35 * Math.cos(mech.angle) + 15 * (Math.random() - 0.5), + mech.pos.y + 35 * Math.sin(mech.angle) + 15 * (Math.random() - 0.5), + 11, + 11, + b.fireAttributes(dir) + ); + b.fireProps(30, 36 + Math.random() * 11, dir, me); //cd , speed + bullet[me].endCycle = game.cycle + 60; + bullet[me].frictionAir = 0.02; + bullet[me].do = function () { + this.force.y += this.mass * 0.001; + }; + } + } + }, + { + name: "needles", + ammo: 0, + ammoPack: 17, + have: false, + fire() { + const me = bullet.length; + const dir = mech.angle; + bullet[me] = Bodies.rectangle(mech.pos.x + 40 * Math.cos(mech.angle), mech.pos.y + 40 * Math.sin(mech.angle), 31, 2, b.fireAttributes(dir)); + b.fireProps(20, 45, dir, me); //cd , speed + bullet[me].endCycle = game.cycle + 180; + bullet[me].dmg = 1; + b.drawOneBullet(bullet[me].vertices); + bullet[me].do = function () { + //low gravity + this.force.y += this.mass * 0.0002; + }; + } + }, + { + name: "missiles", + ammo: 0, + ammoPack: 8, + have: false, + fireCycle: 0, + ammoLoaded: 0, + fire() { + //calculate how many new missiles have loaded since the last time fired + const ammoLoadTime = 36 + const maxLoaded = 6 + this.ammoLoaded += Math.floor(Math.abs(game.cycle - this.fireCycle) / ammoLoadTime) + this.ammoLoaded = Math.min(maxLoaded, this.ammoLoaded) + + if (this.ammoLoaded < 1) { + mech.fireCDcycle = game.cycle + ammoLoadTime; // cool down if no ammo loaded + this.ammo++; //counteract the normal ammo reduction by adding back one if not firing + } else { + this.fireCycle = game.cycle //keep track of last time fired to calculate ammo loaded + this.ammoLoaded-- + + const thrust = 0.0003; + let dir = mech.angle + 0.1 * (0.5 - Math.random()); + const me = bullet.length; + bullet[me] = Bodies.rectangle(mech.pos.x + 40 * Math.cos(mech.angle), mech.pos.y + 40 * Math.sin(mech.angle) - 3, 30, 4, b.fireAttributes(dir)); + b.fireProps(6, -5 - 3 * (0.5 - Math.random()), dir, me); //cd , speed + b.drawOneBullet(bullet[me].vertices); + // Matter.Body.setDensity(bullet[me], 0.01) //doesn't help with reducing explosion knock backs + bullet[me].force.y += 0.00045; //a small push down at first to make it seem like the missile is briefly falling + bullet[me].frictionAir = 0 + bullet[me].endCycle = game.cycle + Math.floor(265 + Math.random() * 20); + bullet[me].explodeRad = 145 + 40 * Math.random(); + bullet[me].lookFrequency = Math.floor(8 + Math.random() * 7); + bullet[me].onEnd = b.explode; //makes bullet do explosive damage at end + bullet[me].onDmg = function () { + this.endCycle = 0; //bullet ends cycle after doing damage // also triggers explosion + }; + bullet[me].lockedOn = null; + bullet[me].do = function () { + if (!(game.cycle % this.lookFrequency)) { + this.close = null; + this.lockedOn = null; + let closeDist = Infinity; + for (let i = 0, len = mob.length; i < len; ++i) { + if ( + mob[i].alive && + mob[i].dropPowerUp && + Matter.Query.ray(map, this.position, mob[i].position).length === 0 && + Matter.Query.ray(body, this.position, mob[i].position).length === 0 + ) { + const dist = Matter.Vector.magnitude(Matter.Vector.sub(this.position, mob[i].position)); + if (dist < closeDist) { + this.close = mob[i].position; + closeDist = dist; + this.lockedOn = mob[i]; + } + } + } + //explode when bullet is close enough to target + if (this.close && closeDist < this.explodeRad * 0.7) { + this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion + } + + if (this.lockedOn) { + this.frictionAir = 0.04; //extra friction + + //draw locked on targeting + ctx.beginPath(); + const vertices = this.lockedOn.vertices; + ctx.moveTo(this.position.x, this.position.y); + const mod = Math.floor((game.cycle / 3) % vertices.length); + ctx.lineTo(vertices[mod].x, vertices[mod].y); + ctx.strokeStyle = "rgba(0,0,155,0.35)"; //"#2f6"; + ctx.lineWidth = 1; + ctx.stroke(); + } + } + + //rotate missile towards the target + if (this.close) { + const face = { + x: Math.cos(this.angle), + y: Math.sin(this.angle) + }; + const target = Matter.Vector.normalise(Matter.Vector.sub(this.position, this.close)); + if (Matter.Vector.dot(target, face) > -0.98) { + if (Matter.Vector.cross(target, face) > 0) { + Matter.Body.rotate(this, 0.08); + } else { + Matter.Body.rotate(this, -0.08); + } + } + } + //accelerate in direction bullet is facing + const dir = this.angle; // + (Math.random() - 0.5); + this.force.x += Math.cos(dir) * thrust; + this.force.y += Math.sin(dir) * thrust; + + //draw rocket + ctx.beginPath(); + ctx.arc(this.position.x - Math.cos(this.angle) * 27 + (Math.random() - 0.5) * 4, this.position.y - Math.sin(this.angle) * 27 + (Math.random() - 0.5) * 4, 11, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(255,155,0,0.5)"; + ctx.fill(); + + } + } + } + }, + { + name: "flak", + ammo: 0, + ammoPack: 9, + have: false, + fire() { + b.muzzleFlash(30); + const totalBullets = 7 + const angleStep = 0.08 / totalBullets + let dir = mech.angle - angleStep * totalBullets / 2; + for (let i = 0; i < totalBullets; i++) { //5 -> 7 + dir += angleStep + const me = bullet.length; + bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), 17, 4, b.fireAttributes(dir)); + b.fireProps(35, 25 + 25 * Math.random(), dir, me); //cd , speed + //Matter.Body.setDensity(bullet[me], 0.00001); + bullet[me].endCycle = game.cycle + 16 + Math.ceil(7 * Math.random()) + bullet[me].restitution = 0; + bullet[me].friction = 1; + bullet[me].explodeRad = 90 + (Math.random() - 0.5) * 50; + bullet[me].onEnd = b.explode; + bullet[me].onDmg = function () { + this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion + }; + bullet[me].do = function () { + this.force.y += this.mass * 0.0004; + // if (this.speed < 10) { //if slow explode + // for (let i = 0, len = bullet.length; i < len; i++) { + // bullet[i].endCycle = 0 //all other bullets explode + // } + // } + } + } + } + }, + { + name: "grenade", + ammo: 0, + ammoPack: 4, + have: false, + fire() { + const me = bullet.length; + const dir = mech.angle; + bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 22, b.fireAttributes(dir)); + bullet[me].radius = 22; //used from drawing timer + b.fireProps(40, 32, dir, me); //cd , speed + b.drawOneBullet(bullet[me].vertices); + Matter.Body.setDensity(bullet[me], 0.000001); + bullet[me].endCycle = game.cycle + 140; + // bullet[me].restitution = 0.3; + // bullet[me].frictionAir = 0.01; + // bullet[me].friction = 0.15; + bullet[me].restitution = 0; + bullet[me].friction = 1; + + bullet[me].explodeRad = 350 + Math.floor(Math.random() * 60); + bullet[me].onEnd = b.explode; //makes bullet do explosive damage before despawn + bullet[me].minDmgSpeed = 1; + bullet[me].onDmg = function () { + this.endCycle = 0; //bullet ends cycle after doing damage //this triggers explosion + }; + bullet[me].do = function () { + //extra gravity for harder arcs + this.force.y += this.mass * 0.0022; + //draw timer + if (!(game.cycle % 10)) { + if (this.isFlashOn) { + this.isFlashOn = false; + } else { + this.isFlashOn = true; + } + } + if (this.isFlashOn) { + ctx.fillStyle = "#000"; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.radius, 0, 2 * Math.PI); + ctx.fill(); + //draw clock on timer + ctx.fillStyle = "#f12"; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.radius * (1 - (this.endCycle - game.cycle) / 140), 0, 2 * Math.PI); + ctx.fill(); + } + }; + } + }, + { + name: "M80", + ammo: 0, + ammoPack: 45, + have: false, + fire() { + const me = bullet.length; + const dir = mech.angle; // + Math.random() * 0.05; + bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 10, b.fireAttributes(dir)); + b.fireProps(8, 26, dir, me); //cd , speed + b.drawOneBullet(bullet[me].vertices); + Matter.Body.setDensity(bullet[me], 0.000001); + bullet[me].totalCycles = 120; + bullet[me].endCycle = game.cycle + bullet[me].totalCycles; + bullet[me].restitution = 0.6; + bullet[me].explodeRad = 125; + bullet[me].onEnd = b.explode; //makes bullet do explosive damage before despawn + bullet[me].minDmgSpeed = 1; + bullet[me].dmg = 0.25; + bullet[me].onDmg = function () { + this.endCycle = 0; //bullet ends cycle after doing damage //this triggers explosion + }; + bullet[me].do = function () { + //extra gravity for harder arcs + this.force.y += this.mass * 0.0025; + }; + } + }, + { + name: "one shot", + ammo: 0, + ammoPack: 4, + have: false, + fire() { + b.muzzleFlash(45); + // mobs.alert(800); + const me = bullet.length; + const dir = mech.angle; + bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), 60, 25, b.fireAttributes(dir)); + b.fireProps(30, 54, dir, me); //cd , speed + bullet[me].endCycle = game.cycle + 180; + bullet[me].do = function () { + this.force.y += this.mass * 0.0005; + }; + } + }, + { + name: "super balls", + ammo: 0, + ammoPack: 10, + have: false, + fire() { + b.muzzleFlash(20); + // mobs.alert(450); + let dir = mech.angle - 0.05; + for (let i = 0; i < 3; i++) { + const me = bullet.length; + bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 7, b.fireAttributes(dir)); + b.fireProps(20, 30, dir, me); //cd , speed + Matter.Body.setDensity(bullet[me], 0.0001); + bullet[me].endCycle = game.cycle + 360; + bullet[me].dmg = 0.5; + bullet[me].minDmgSpeed = 0; + bullet[me].restitution = 0.96; + bullet[me].friction = 0; + bullet[me].do = function () { + this.force.y += this.mass * 0.001; + }; + dir += 0.05; + } + } + }, + { + name: "wave beam", + ammo: 0, + ammoPack: 120, + have: false, + fire() { + mech.fireCDcycle = game.cycle + 7; // cool down + const endCycle = game.cycle + 94 + const bulletRadius = 5; + const speed = 35; + const spread = Math.PI / 2 * 0.65 // smaller = faster speed, larger = faster rotation? + const dir = mech.angle + const pos = { + x: mech.pos.x + 20 * Math.cos(mech.angle), + y: mech.pos.y + 20 * Math.sin(mech.angle) + } + const props = { + angle: dir, + endCycle: endCycle, + inertia: Infinity, + restitution: 1, + friction: 1, + frictionAir: -0.003, + minDmgSpeed: 0, + isConstrained: true, + dmg: 0, //damage done in addition to the damage from momentum + classType: "bullet", + collisionFilter: { + category: 0x000100, + mask: 0x000010 + }, + onDmg() { + if (this.isConstrained) { + this.isConstrained = false + b.removeConsBB(this) + // this.endCycle = 0 //triggers despawn + } + }, + onEnd() { + if (this.isConstrained) { + this.isConstrained = false + b.removeConsBB(this) + } + }, + do() {} + } + + //first bullet + const me = bullet.length; + bullet[me] = Bodies.circle(pos.x, pos.y, bulletRadius, props); + Matter.Body.setVelocity(bullet[me], { + x: speed * Math.cos(dir + spread), + y: speed * Math.sin(dir + spread) + }); + World.add(engine.world, bullet[me]); //add bullet to world + // Matter.Body.setDensity(bullet[me], 0.0005); //0.001 is normal + + //second bullet + const me2 = bullet.length; + bullet[me2] = Bodies.circle(pos.x, pos.y, bulletRadius, props); + Matter.Body.setVelocity(bullet[me2], { + x: speed * Math.cos(dir - spread), + y: speed * Math.sin(dir - spread) + }); + World.add(engine.world, bullet[me2]); //add bullet to world + // Matter.Body.setDensity(bullet[me2], 0.0005); //0.001 is normal + + //constraint + const meCons = consBB.length + consBB[meCons] = Constraint.create({ + bodyA: bullet[me], + bodyB: bullet[me2], + stiffness: 0.008 + }); + World.add(engine.world, consBB[meCons]); + } + } + ], + fire() { + if (game.mouseDown && mech.fireCDcycle < game.cycle && !(keys[32] || game.mouseDownRight) && !mech.isHolding && b.inventory.length) { + if (b.guns[this.activeGun].ammo > 0) { + b.guns[this.activeGun].fire(); + b.guns[this.activeGun].ammo--; + game.updateGunHUD(); + } else { + mech.fireCDcycle = game.cycle + 30; //cooldown + game.makeTextLog("
NO AMMO
E / Q", 200); + } + } + }, + gamepadFire() { + if (game.gamepad.rightTrigger && mech.fireCDcycle < game.cycle && !(keys[32] || game.gamepad.leftTrigger) && !mech.isHolding && b.inventory.length) { + if (b.guns[this.activeGun].ammo > 0) { + b.guns[this.activeGun].fire(); + b.guns[this.activeGun].ammo--; + game.updateGunHUD(); + } else { + mech.fireCDcycle = game.cycle + 30; //cooldown + game.makeTextLog("
NO AMMO
E / Q", 200); + } + } + }, + draw() { + ctx.beginPath(); + let i = bullet.length; + while (i--) { + //draw + let vertices = bullet[i].vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + //remove bullet if at endcycle for that bullet + if (bullet[i].endCycle < game.cycle) { + bullet[i].onEnd(i); //some bullets do stuff on end + if (bullet[i]) { + Matter.World.remove(engine.world, bullet[i]); + bullet.splice(i, 1); + } else { + break; //if bullet[i] doesn't exist don't complete the for loop, because the game probably reset + } + } + } + ctx.fillStyle = "#000"; + ctx.fill(); + //do things + for (let i = 0, len = bullet.length; i < len; i++) { + bullet[i].do(); + } + } +}; \ No newline at end of file diff --git a/js/engine.js b/js/engine.js new file mode 100644 index 0000000..cd0ba31 --- /dev/null +++ b/js/engine.js @@ -0,0 +1,173 @@ +//matter.js *********************************************************** +// module aliases +const Engine = Matter.Engine, + World = Matter.World, + Events = Matter.Events, + Composites = Matter.Composites, + Composite = Matter.Composite, + Constraint = Matter.Constraint, + Vertices = Matter.Vertices, + Query = Matter.Query, + Body = Matter.Body, + Bodies = Matter.Bodies; + +// create an engine +const engine = Engine.create(); +engine.world.gravity.scale = 0; //turn off gravity (it's added back in later) +// engine.velocityIterations = 100 +// engine.positionIterations = 100 +// engine.enableSleeping = true + +// matter events ********************************************************* +//************************************************************************ +//************************************************************************ +//************************************************************************ + +function playerOnGroundCheck(event) { + //runs on collisions events + function enter() { + mech.numTouching++; + if (!mech.onGround) mech.enterLand(); + } + const pairs = event.pairs; + for (let i = 0, j = pairs.length; i != j; ++i) { + let pair = pairs[i]; + if (pair.bodyA === jumpSensor) { + mech.standingOn = pair.bodyB; //keeping track to correctly provide recoil on jump + enter(); + } else if (pair.bodyB === jumpSensor) { + mech.standingOn = pair.bodyA; //keeping track to correctly provide recoil on jump + enter(); + } + } + mech.numTouching = 0; +} + +function playerOffGroundCheck(event) { + //runs on collisions events + function enter() { + if (mech.onGround && mech.numTouching === 0) mech.enterAir(); + } + const pairs = event.pairs; + for (let i = 0, j = pairs.length; i != j; ++i) { + if (pairs[i].bodyA === jumpSensor) { + enter(); + } else if (pairs[i].bodyB === jumpSensor) { + enter(); + } + } +} + +function playerHeadCheck(event) { + //runs on collisions events + if (mech.crouch) { + mech.isHeadClear = true; + const pairs = event.pairs; + for (let i = 0, j = pairs.length; i != j; ++i) { + if (pairs[i].bodyA === headSensor) { + mech.isHeadClear = false; + } else if (pairs[i].bodyB === headSensor) { + mech.isHeadClear = false; + } + } + } +} + +function mobCollisionChecks(event) { + const pairs = event.pairs; + for (let i = 0, j = pairs.length; i != j; i++) { + for (let k = 0; k < mob.length; k++) { + if (mob[k].alive && mech.alive) { + if (pairs[i].bodyA === mob[k]) { + collide(pairs[i].bodyB); + break; + } else if (pairs[i].bodyB === mob[k]) { + collide(pairs[i].bodyA); + break; + } + + function collide(obj) { + //player and mob collision + if (obj === playerBody || obj === playerHead) { + if (mech.damageImmune < game.cycle) { + //player is immune to mob collision damage for 30 cycles + mech.damageImmune = game.cycle + 30; + mob[k].foundPlayer(); + let dmg = Math.min(Math.max(0.025 * Math.sqrt(mob[k].mass), 0.05), 0.3) * game.dmgScale; //player damage is capped at 0.3*dmgScale of 1.0 + mech.damage(dmg); + if (mob[k].onHit) mob[k].onHit(k); + game.drawList.push({ + //add dmg to draw queue + x: pairs[i].activeContacts[0].vertex.x, + y: pairs[i].activeContacts[0].vertex.y, + radius: dmg * 500, + color: game.mobDmgColor, + time: game.drawTime + }); + } + //extra kick between player and mob + //this section would be better with forces but they don't work... + let angle = Math.atan2(player.position.y - mob[k].position.y, player.position.x - mob[k].position.x); + Matter.Body.setVelocity(player, { + x: player.velocity.x + 8 * Math.cos(angle), + y: player.velocity.y + 8 * Math.sin(angle) + }); + Matter.Body.setVelocity(mob[k], { + x: mob[k].velocity.x - 8 * Math.cos(angle), + y: mob[k].velocity.y - 8 * Math.sin(angle) + }); + return; + } + //bullet mob collisions + if (obj.classType === "bullet" && obj.speed > obj.minDmgSpeed) { + mob[k].foundPlayer(); + let dmg = b.dmgScale * (obj.dmg + 0.15 * obj.mass * Matter.Vector.magnitude(Matter.Vector.sub(mob[k].velocity, obj.velocity))); + mob[k].damage(dmg); + obj.onDmg(); //some bullets do actions when they hits things, like despawn + game.drawList.push({ + //add dmg to draw queue + x: pairs[i].activeContacts[0].vertex.x, + y: pairs[i].activeContacts[0].vertex.y, + radius: Math.sqrt(dmg) * 40, + color: game.playerDmgColor, + time: game.drawTime + }); + return; + } + //mob and body collisions + if (obj.classType === "body" && obj.speed > 5) { + const v = Matter.Vector.magnitude(Matter.Vector.sub(mob[k].velocity, obj.velocity)); + if (v > 8) { + let dmg = b.dmgScale * v * Math.sqrt(obj.mass) * 0.05; + mob[k].damage(dmg); + if (mob[k].distanceToPlayer2() < 1000000) mob[k].foundPlayer(); + game.drawList.push({ + //add dmg to draw queue + x: pairs[i].activeContacts[0].vertex.x, + y: pairs[i].activeContacts[0].vertex.y, + radius: Math.sqrt(dmg) * 40, + color: game.playerDmgColor, + time: game.drawTime + }); + return; + } + } + } + } + } + } +} + +//determine if player is on the ground +Events.on(engine, "collisionStart", function (event) { + playerOnGroundCheck(event); + playerHeadCheck(event); + mobCollisionChecks(event); +}); +Events.on(engine, "collisionActive", function (event) { + playerOnGroundCheck(event); + playerHeadCheck(event); +}); +Events.on(engine, "collisionEnd", function (event) { + playerOffGroundCheck(event); +}); \ No newline at end of file diff --git a/js/game.js b/js/game.js new file mode 100644 index 0000000..bc9881c --- /dev/null +++ b/js/game.js @@ -0,0 +1,1150 @@ +// game Object ******************************************************** +//********************************************************************* +const game = { + loop() {}, + mouseLoop() { + game.cycle++; //tracks game cycles + if (game.clearNow) { + game.clearNow = false; + game.clearMap(); + level.start(); + } + game.gravity(); + Engine.update(engine, game.delta); + game.wipe(); + game.textLog(); + mech.keyMove(); + level.checkZones(); + level.checkQuery(); + mech.move(); + mech.look(); + game.fallChecks(); + ctx.save(); + game.camera(); + if (game.testing) { + mech.draw(); + game.draw.wireFrame(); + game.draw.cons(); + game.draw.testing(); + game.drawCircle(); + ctx.restore(); + game.getCoords.out(); + game.testingOutput(); + } else { + level.drawFillBGs(); + level.exit.draw(); + level.enter.draw(); + game.draw.powerUp(); + mobs.draw(); + game.draw.cons(); + game.draw.body(); + mech.draw(); + mech.hold(); + level.drawFills(); + game.draw.drawMapPath(); + mobs.loop(); + b.draw(); + b.fire(); + game.drawCircle(); + ctx.restore(); + } + game.drawCursor(); + }, + gamepadLoop() { + game.cycle++; //tracks game cycles + // game.polGamepad(); + if (game.clearNow) { + game.clearNow = false; + game.clearMap(); + level.start(); + } + game.gravity(); + Engine.update(engine, game.delta); + game.wipe(); + game.textLog(); + mech.gamepadMove(); + level.checkZones(); + level.checkQuery(); + mech.move(); + mech.gamepadLook(); + game.fallChecks(); + ctx.save(); + game.gamepadCamera(); + if (game.testing) { + mech.draw(); + game.draw.wireFrame(); + game.draw.cons(); + game.draw.testing(); + game.drawCircle(); + ctx.restore(); + game.getCoords.out(); + game.testingOutput(); + } else { + level.drawFillBGs(); + level.exit.draw(); + level.enter.draw(); + game.draw.powerUp(); + mobs.draw(); + game.draw.cons(); + game.draw.body(); + mech.draw(); + mech.hold(); + level.drawFills(); + game.draw.drawMapPath(); + mobs.loop(); + b.draw(); + b.gamepadFire(); + game.drawCircle(); + ctx.restore(); + } + // game.drawCursor(); + }, + gamepad: { + connected: false, + cycle: 0, + leftTrigger: false, + rightTrigger: false, + leftAxisThreshold: 0.6, + leftAxis: { + x: 0, + y: 0 + }, + rightAxis: { + x: 0, + y: 0 + }, + cycleWeaponCD: 0 + }, + polGamepad: function () { + const gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []); + if (!gamepads) return; + gp = gamepads[0]; + // console.log(gp) + + if (game.onTitlePage) { + if (gp.buttons[6].pressed || gp.buttons[7].pressed) game.startGame(); //triggers start of game + } else { + //left d-pad + if (gp.axes[0] > game.gamepad.leftAxisThreshold) { + game.gamepad.leftAxis.x = 1 + } else if (gp.axes[0] < -game.gamepad.leftAxisThreshold) { + game.gamepad.leftAxis.x = -1 + } else { + game.gamepad.leftAxis.x = 0 + } + if (gp.axes[1] > game.gamepad.leftAxisThreshold) { + game.gamepad.leftAxis.y = -1 + } else if (gp.axes[1] < -game.gamepad.leftAxisThreshold) { + game.gamepad.leftAxis.y = 1 + } else { + game.gamepad.leftAxis.y = 0 + } + //right d-pad + const limit = 0.08 + if (Math.abs(gp.axes[2]) > limit) game.gamepad.rightAxis.x = gp.axes[2] * 0.08 + game.gamepad.rightAxis.x * 0.92 //smoothing the input + if (Math.abs(gp.axes[3]) > limit) game.gamepad.rightAxis.y = gp.axes[3] * 0.08 + game.gamepad.rightAxis.y * 0.92 //smoothing the input + // if (Math.abs(gp.axes[0]) > limit) game.gamepad.rightAxis.x = gp.axes[0] * 0.2 + game.gamepad.rightAxis.x * 0.8 //smoothing the input + // if (Math.abs(gp.axes[1]) > limit) game.gamepad.rightAxis.y = gp.axes[1] * 0.2 + game.gamepad.rightAxis.y * 0.8 //smoothing the input + // if (Math.abs(gp.axes[2]) > limit) game.gamepad.rightAxis.x = gp.axes[2] + // if (Math.abs(gp.axes[3]) > limit) game.gamepad.rightAxis.y = gp.axes[3] + + // left and right trigger + if (gp.buttons[6].pressed) { + game.gamepad.leftTrigger = true; + game.mouseDownRight = true + } else { + game.gamepad.leftTrigger = false; + game.mouseDownRight = false + } + if (gp.buttons[7].pressed) { + game.gamepad.rightTrigger = true; + game.mouseDown = true + } else { + game.gamepad.rightTrigger = false; + game.mouseDown = false + } + //jump + if (gp.buttons[0].pressed) { //gp.axes[1] < -0.8 || + game.gamepad.jump = true; + } else { + game.gamepad.jump = false; + } + //buttons that trigger a button CD + if (game.gamepad.cycleWeaponCD < game.gamepad.cycle) { + if (gp.buttons[4].pressed || gp.buttons[12].pressed) { + game.gamepad.cycleWeaponCD = game.gamepad.cycle + 15 + game.previousGun(); + } + if (gp.buttons[5].pressed || gp.buttons[13].pressed) { + game.gamepad.cycleWeaponCD = game.gamepad.cycle + 15 + game.nextGun(); + } + + if (gp.buttons[9].pressed) { + game.gamepad.cycleWeaponCD = game.gamepad.cycle + 60 + if (game.paused) { + game.paused = false; + requestAnimationFrame(cycle); + } else { + game.paused = true; + game.makeTextLog("

PAUSED

", 1); + } + } + // if (gp.buttons[14].pressed) { + // game.zoomScale /= 0.995; + // game.setZoom(); + // } else if (gp.buttons[15].pressed) { + // game.zoomScale *= 0.995; + // game.setZoom(); + // } + } + } + // // logs button numbers + // for (let i = 0, len = gp.buttons.length; i < len; i++) { + // if (gp.buttons[i].pressed) { + // console.log(i) + // // console.log(game.gamepad) + // } + // } + }, + mouse: { + x: canvas.width / 2, + y: canvas.height / 2 + }, + mouseInGame: { + x: 0, + y: 0 + }, + levelsCleared: 0, + g: 0.001, + dmgScale: 1, + onTitlePage: true, + paused: false, + testing: false, //testing mode: shows wireframe and some variables + cycle: 0, //total cycles, 60 per second + fpsCap: 72, //limits frames per second to 144/2=72+1=73, on most monitors the fps is capped at 60fps by the hardware + cyclePaused: 0, + fallHeight: 3000, //below this y position the player dies + lastTimeStamp: 0, //tracks time stamps for measuring delta + delta: 1000 / 60, //speed of game engine //looks like it has to be 16 to match player input + buttonCD: 0, + // dropFPS(cap = 40, time = 15) { + // game.fpsCap = cap + // game.fpsInterval = 1000 / game.fpsCap; + // game.defaultFPSCycle = game.cycle + time + // const normalFPS = function () { + // if (game.defaultFPSCycle < game.cycle) { + // game.fpsCap = 72 + // game.fpsInterval = 1000 / game.fpsCap; + // } else { + // requestAnimationFrame(normalFPS); + // } + // }; + // requestAnimationFrame(normalFPS); + // }, + drawCursor() { + const size = 10; + ctx.beginPath(); + ctx.moveTo(game.mouse.x - size, game.mouse.y); + ctx.lineTo(game.mouse.x + size, game.mouse.y); + ctx.moveTo(game.mouse.x, game.mouse.y - size); + ctx.lineTo(game.mouse.x, game.mouse.y + size); + ctx.lineWidth = 2; + ctx.strokeStyle = "#000"; //'rgba(0,0,0,0.4)' + ctx.stroke(); // Draw it + }, + drawList: [], //so you can draw a first frame of explosions.. I know this is bad + drawTime: 8, //how long circles are drawn. use to push into drawlist.time + mobDmgColor: "rgba(255,0,0,0.7)", //used top push into drawList.color + playerDmgColor: "rgba(0,0,0,0.7)", //used top push into drawList.color + drawCircle() { + //draws a circle for two cycles, used for showing damage mostly + let i = this.drawList.length; + while (i--) { + ctx.beginPath(); //draw circle + ctx.arc(this.drawList[i].x, this.drawList[i].y, this.drawList[i].radius, 0, 2 * Math.PI); + ctx.fillStyle = this.drawList[i].color; + ctx.fill(); + if (this.drawList[i].time) { + //remove when timer runs out + this.drawList[i].time--; + } else { + this.drawList.splice(i, 1); + } + } + }, + lastLogTime: 0, + lastLogTimeBig: 0, + boldActiveGunHUD() { + if (b.inventory.length > 0) { + for (let i = 0, len = b.inventory.length; i < len; ++i) { + // document.getElementById(b.inventory[i]).style.fontSize = "25px"; + document.getElementById(b.inventory[i]).style.opacity = "0.3"; + } + // document.getElementById(b.activeGun).style.fontSize = "30px"; + document.getElementById(b.activeGun).style.opacity = "1"; + } + }, + updateGunHUD() { + for (let i = 0, len = b.inventory.length; i < len; ++i) { + document.getElementById(b.inventory[i]).innerHTML = b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo; + } + }, + makeGunHUD() { + //remove all nodes + const myNode = document.getElementById("guns"); + while (myNode.firstChild) { + myNode.removeChild(myNode.firstChild); + } + //add nodes + for (let i = 0, len = b.inventory.length; i < len; ++i) { + const node = document.createElement("div"); + node.setAttribute("id", b.inventory[i]); + let textnode = document.createTextNode(b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo); + node.appendChild(textnode); + document.getElementById("guns").appendChild(node); + } + game.boldActiveGunHUD(); + }, + makeTextLog(text, time = 180) { + document.getElementById("text-log").innerHTML = text; + document.getElementById("text-log").style.opacity = 1; + game.lastLogTime = game.cycle + time; + }, + textLog() { + if (game.lastLogTime && game.lastLogTime < game.cycle) { + game.lastLogTime = 0; + // document.getElementById("text-log").innerHTML = " "; + document.getElementById("text-log").style.opacity = 0; + } + }, + // timing: function() { + // this.cycle++; //tracks game cycles + // //delta is used to adjust forces on game slow down; + // this.delta = (engine.timing.timestamp - this.lastTimeStamp) / 16.666666666666; + // this.lastTimeStamp = engine.timing.timestamp; //track last engine timestamp + // }, + nextGun() { + if (b.inventory.length > 0) { + b.inventoryGun++; + if (b.inventoryGun > b.inventory.length - 1) b.inventoryGun = 0; + game.switchGun(); + } + }, + previousGun() { + if (b.inventory.length > 0) { + b.inventoryGun--; + if (b.inventoryGun < 0) b.inventoryGun = b.inventory.length - 1; + game.switchGun(); + } + }, + switchGun() { + b.activeGun = b.inventory[b.inventoryGun]; + game.updateGunHUD(); + game.boldActiveGunHUD(); + // mech.drop(); + }, + keyPress() { + //runs on key press event + // if (keys[49]) { + // // press 1 + // b.inventoryGun = 0; + // game.switchGun(); + // } else if (keys[50]) { + // // press 2 + // b.inventoryGun = 1; + // game.switchGun(); + // } else if (keys[51]) { + // // press 3 + // b.inventoryGun = 2; + // game.switchGun(); + // } else if (keys[52]) { + // // press 4 + // b.inventoryGun = 3; + // game.switchGun(); + // } else if (keys[53]) { + // // press 5 + // b.inventoryGun = 4; + // game.switchGun(); + // } else if (keys[54]) { + // // press 6 + // b.inventoryGun = 5; + // game.switchGun(); + // } else if (keys[55]) { + // // press 7 + // b.inventoryGun = 6; + // game.switchGun(); + // } else if (keys[56]) { + // // press 8 + // b.inventoryGun = 7; + // game.switchGun(); + // } else if (keys[57]) { + // // press 9 + // b.inventoryGun = 8; + // game.switchGun(); + // } else if (keys[48]) { + // // press 0 + // b.inventoryGun = 9; + // game.switchGun(); + // } + + + if (keys[189]) { + // - key + game.zoomScale /= 0.9; + game.setZoom(); + } else if (keys[187]) { + // = key + game.zoomScale *= 0.9; + game.setZoom(); + } + + //full screen toggle + // if (keys[13]) { + // //enter key + // var doc = window.document; + // var docEl = doc.documentElement; + + // var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen; + // var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen; + + // if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) { + // requestFullScreen.call(docEl); + // } else { + // cancelFullScreen.call(doc); + // } + // setupCanvas(); + // } + + + if (keys[69]) { + // e swap to next active gun + game.nextGun(); + } else if (keys[81]) { + //q swap to previous active gun + game.previousGun(); + } + + if (keys[80]) { + //p for pause + if (game.paused) { + game.paused = false; + requestAnimationFrame(cycle); + } else { + game.paused = true; + game.makeTextLog("

PAUSED

", 1); + } + } + + //toggle testing mode + if (keys[84]) { + // 84 = t + if (this.testing) { + this.testing = false; + } else { + this.testing = true; + } + } else if (this.testing) { + //only in testing mode + if (keys[70]) { + // f for power ups + for (let i = 0; i < 16; ++i) { + powerUps.spawnRandomPowerUp(game.mouseInGame.x, game.mouseInGame.y, 0, 0); + } + } + if (keys[82]) { + // r to teleport to mouse + Matter.Body.setPosition(player, this.mouseInGame); + Matter.Body.setVelocity(player, { + x: 0, + y: 0 + }); + } + } + }, + zoom: null, + zoomScale: 1000, + setZoom(zoomScale = game.zoomScale) { //use in window resize in index.js + game.zoomScale = zoomScale + game.zoom = canvas.height / zoomScale; //sets starting zoom scale + }, + zoomTransition(newZoomScale, step = 2) { + const isBigger = (newZoomScale - game.zoomScale > 0) ? true : false; + requestAnimationFrame(zLoop); + const currentLevel = level.onLevel + + function zLoop() { + if (currentLevel != level.onLevel) return //stop the zoom if player goes to a new level + + if (isBigger) { + game.zoomScale += step + if (game.zoomScale >= newZoomScale) { + game.setZoom(newZoomScale); + return + } + } else { + game.zoomScale -= step + if (game.zoomScale <= newZoomScale) { + game.setZoom(newZoomScale); + return + } + } + + game.setZoom(); + requestAnimationFrame(zLoop); + } + }, + camera() { + ctx.translate(canvas.width2, canvas.height2); //center + ctx.scale(game.zoom, game.zoom); //zoom in once centered + ctx.translate(-canvas.width2 + mech.transX, -canvas.height2 + mech.transY); //translate + //calculate in game mouse position by undoing the zoom and translations + game.mouseInGame.x = (game.mouse.x - canvas.width2) / game.zoom + canvas.width2 - mech.transX; + game.mouseInGame.y = (game.mouse.y - canvas.height2) / game.zoom + canvas.height2 - mech.transY; + }, + gamepadCamera() { + ctx.translate(canvas.width2, canvas.height2); //center + ctx.scale(game.zoom, game.zoom); //zoom in once centered + ctx.translate(-canvas.width2 + mech.transX, -canvas.height2 + mech.transY); //translate + //calculate in game mouse position by undoing the zoom and translations + game.mouseInGame.x = (game.gamepad.rightAxis.x * canvas.width2) / game.zoom + canvas.width2 - mech.transX; + game.mouseInGame.y = (game.gamepad.rightAxis.y * canvas.height2) / game.zoom + canvas.height2 - mech.transY; + }, + zoomInFactor: 0, + startZoomIn(time = 180) { + game.zoom = 0; + let count = 0; + requestAnimationFrame(zLoop); + + function zLoop() { + game.zoom += canvas.height / game.zoomScale / time; + count++; + if (count < time) { + requestAnimationFrame(zLoop); + } else { + game.setZoom(); + } + } + }, + wipe() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + // ctx.fillStyle = "#000"; + // ctx.fillRect(0, 0, canvas.width, canvas.height); + + // ctx.globalAlpha = (mech.health < 0.7) ? (mech.health+0.3)*(mech.health+0.3) : 1 + // if (mech.health < 0.7) { + // ctx.globalAlpha= 0.3 + mech.health + // ctx.fillStyle = document.body.style.backgroundColor + // ctx.fillRect(0, 0, canvas.width, canvas.height); + // ctx.globalAlpha=1; + // } else { + // ctx.clearRect(0, 0, canvas.width, canvas.height); + // } + //ctx.fillStyle = "rgba(255,255,255," + (1 - Math.sqrt(player.speed)*0.1) + ")"; + //ctx.fillStyle = "rgba(255,255,255,0.4)"; + //ctx.fillRect(0, 0, canvas.width, canvas.height); + }, + gravity() { + function addGravity(bodies, magnitude) { + for (var i = 0; i < bodies.length; i++) { + bodies[i].force.y += bodies[i].mass * magnitude; + } + } + addGravity(powerUp, game.g); + addGravity(body, game.g); + player.force.y += player.mass * mech.gravity; + }, + reset() { + //removes guns and ammo + b.inventory = []; + for (let i = 0, len = b.guns.length; i < len; ++i) { + if (b.guns[i].ammo != Infinity) { + b.guns[i].ammo = 0; + b.guns[i].have = false; + } else { + b.inventory.push(i); + } + } + game.paused = false; + engine.timing.timeScale = 1; + game.dmgScale = 1; + b.dmgScale = 0.7; + b.activeGun = null; + game.makeGunHUD(); + mech.drop(); + mech.addHealth(1); + mech.fieldUpgrades[0](); //reset to starting field? or let them keep the field + mech.alive = true; + level.onLevel = 0; + game.levelsCleared = 0; + // level.onLevel = Math.floor(Math.random() * level.levels.length); //picks a random starting level + game.clearNow = true; + document.getElementById("text-log").style.opacity = 0; + document.getElementById("fade-out").style.opacity = 0; + }, + firstRun: true, + splashReturn() { + game.onTitlePage = true; + // document.getElementById('splash').onclick = 'run(this)'; + document.getElementById("splash").onclick = function () { + game.startGame(); + }; + document.getElementById("controls").style.display = "inline"; + document.getElementById("splash").style.display = "inline"; + document.getElementById("dmg").style.display = "none"; + document.getElementById("health-bg").style.display = "none"; + document.body.style.cursor = "auto"; + }, + fpsInterval: 0, //set in startGame + then: null, + startGameWithMouse() { + disconnectGamepad(); + game.startGame(); + }, + startGame() { + game.onTitlePage = false; + document.getElementById("controls").style.display = "none"; + document.getElementById("splash").onclick = null; //removes the onclick effect so the function only runs once + document.getElementById("splash").style.display = "none"; //hides the element that spawned the function + document.getElementById("dmg").style.display = "inline"; + document.getElementById("health-bg").style.display = "inline"; + + // window.onmousedown = function (e) { + // //mouse up event in set in index.js + + // // game.mouseDown = true; + // if (e.which === 3) { + // game.mouseDownRight = true; + // } else { + // game.mouseDown = true; + // } + // // keep this disabled unless building maps + // // if (!game.mouseDown){ + // // game.getCoords.pos1.x = Math.round(game.mouseInGame.x / 25) * 25; + // // game.getCoords.pos1.y = Math.round(game.mouseInGame.y / 25) * 25; + // // } + + // // mech.throw(); + // }; + + document.body.style.cursor = "none"; + if (this.firstRun) { + mech.spawn(); //spawns the player + level.levels = shuffle(level.levels); //shuffles order of maps + level.levels.unshift("bosses"); //add bosses level to the end of the randomized levels list + } + game.reset(); + game.firstRun = false; + + + //setup FPS cap + game.fpsInterval = 1000 / game.fpsCap; + game.then = Date.now(); + requestAnimationFrame(cycle); //starts game loop + game.lastLogTime = game.cycle + 360; + }, + clearNow: false, + clearMap() { + //if player is holding something this remembers it before it gets deleted + let holdTarget; + if (mech.holdingTarget) { + holdTarget = mech.holdingTarget; + } + mech.drop(); + level.fill = []; + level.fillBG = []; + level.zones = []; + level.queryList = []; + this.drawList = []; + + function removeAll(array) { + for (let i = 0; i < array.length; ++i) Matter.World.remove(engine.world, array[i]); + } + removeAll(map); + map = []; + removeAll(body); + body = []; + removeAll(mob); + mob = []; + removeAll(powerUp); + powerUp = []; + removeAll(cons); + cons = []; + removeAll(consBB); + consBB = []; + removeAll(bullet); + bullet = []; + // if player was holding something this makes a new copy to hold + if (holdTarget) { + len = body.length; + body[len] = Matter.Bodies.fromVertices(0, 0, holdTarget.vertices, { + friction: holdTarget.friction, + frictionAir: holdTarget.frictionAir, + frictionStatic: holdTarget.frictionStatic + }); + mech.holdingTarget = body[len]; + } + }, + getCoords: { + //used when building maps, outputs a draw rect command to console, only works in testing mode + pos1: { + x: 0, + y: 0 + }, + pos2: { + x: 0, + y: 0 + }, + out() { + if (keys[49]) { + this.pos1.x = Math.round(game.mouseInGame.x / 25) * 25; + this.pos1.y = Math.round(game.mouseInGame.y / 25) * 25; + } + if (keys[50]) { + //press 1 in the top left; press 2 in the bottom right;copy command from console + this.pos2.x = Math.round(game.mouseInGame.x / 25) * 25; + this.pos2.y = Math.round(game.mouseInGame.y / 25) * 25; + window.getSelection().removeAllRanges(); + var range = document.createRange(); + range.selectNode(document.getElementById("test")); + window.getSelection().addRange(range); + document.execCommand("copy"); + window.getSelection().removeAllRanges(); + console.log(`spawn.mapRect(${this.pos1.x}, ${this.pos1.y}, ${this.pos2.x - this.pos1.x}, ${this.pos2.y - this.pos1.y}); //`); + } + } + }, + fallChecks() { + // if 4000px deep + if (mech.pos.y > game.fallHeight) mech.death(); + + if (!(game.cycle % 420)) { + remove = function (who) { + let i = who.length; + while (i--) { + if (who[i].position.y > game.fallHeight) { + Matter.World.remove(engine.world, who[i]); + who.splice(i, 1); + } + } + }; + remove(mob); + remove(body); + remove(powerUp); + } + }, + testingOutput() { + ctx.textAlign = "right"; + ctx.fillStyle = "#000"; + let line = 100; + const x = canvas.width - 5; + ctx.fillText("T: exit testing mode", x, line); + line += 20; + ctx.fillText("R: teleport to mouse", x, line); + line += 20; + ctx.fillText("F: spawn power ups", x, line); + line += 30; + + ctx.fillText("cycle: " + game.cycle, x, line); + line += 20; + ctx.fillText("x: " + player.position.x.toFixed(0), x, line); + line += 20; + ctx.fillText("y: " + player.position.y.toFixed(0), x, line); + line += 20; + ctx.fillText("Vx: " + mech.Vx.toFixed(2), x, line); + line += 20; + ctx.fillText("Vy: " + mech.Vy.toFixed(2), x, line); + line += 20; + ctx.fillText("Fx: " + player.force.x.toFixed(3), x, line); + line += 20; + ctx.fillText("Fy: " + player.force.y.toFixed(3), x, line); + line += 20; + ctx.fillText("yOff: " + mech.yOff.toFixed(1), x, line); + line += 20; + ctx.fillText("mass: " + player.mass.toFixed(1), x, line); + line += 20; + ctx.fillText("onGround: " + mech.onGround, x, line); + line += 20; + ctx.fillText("crouch: " + mech.crouch, x, line); + line += 20; + ctx.fillText("isHeadClear: " + mech.isHeadClear, x, line); + line += 20; + ctx.fillText("HeadIsSensor: " + headSensor.isSensor, x, line); + line += 20; + ctx.fillText("frictionAir: " + player.frictionAir.toFixed(3), x, line); + line += 20; + ctx.fillText("stepSize: " + mech.stepSize.toFixed(2), x, line); + line += 20; + ctx.fillText("zoom: " + this.zoom.toFixed(4), x, line); + line += 20; + ctx.textAlign = "center"; + ctx.fillText(`(${this.mouseInGame.x.toFixed(1)}, ${this.mouseInGame.y.toFixed(1)})`, this.mouse.x, this.mouse.y - 20); + }, + draw: { + powerUp() { + // draw power up + // ctx.globalAlpha = 0.4 * Math.sin(game.cycle * 0.15) + 0.6; + // for (let i = 0, len = powerUp.length; i < len; ++i) { + // let vertices = powerUp[i].vertices; + // ctx.beginPath(); + // ctx.moveTo(vertices[0].x, vertices[0].y); + // for (let j = 1; j < vertices.length; j += 1) { + // ctx.lineTo(vertices[j].x, vertices[j].y); + // } + // ctx.lineTo(vertices[0].x, vertices[0].y); + // ctx.fillStyle = powerUp[i].color; + // ctx.fill(); + // } + // ctx.globalAlpha = 1; + ctx.globalAlpha = 0.4 * Math.sin(game.cycle * 0.15) + 0.6; + for (let i = 0, len = powerUp.length; i < len; ++i) { + ctx.beginPath(); + ctx.arc(powerUp[i].position.x, powerUp[i].position.y, powerUp[i].size, 0, 2 * Math.PI); + ctx.fillStyle = powerUp[i].color; + ctx.fill(); + } + ctx.globalAlpha = 1; + }, + // map: function() { + // ctx.beginPath(); + // for (let i = 0, len = map.length; i < len; ++i) { + // let vertices = map[i].vertices; + // ctx.moveTo(vertices[0].x, vertices[0].y); + // for (let j = 1; j < vertices.length; j += 1) { + // ctx.lineTo(vertices[j].x, vertices[j].y); + // } + // ctx.lineTo(vertices[0].x, vertices[0].y); + // } + // ctx.fillStyle = "#444"; + // ctx.fill(); + // }, + mapPath: null, //holds the path for the map to speed up drawing + setPaths() { + //runs at each new level to store the path for the map since the map doesn't change + this.mapPath = new Path2D(); + for (let i = 0, len = map.length; i < len; ++i) { + let vertices = map[i].vertices; + this.mapPath.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + this.mapPath.lineTo(vertices[j].x, vertices[j].y); + } + this.mapPath.lineTo(vertices[0].x, vertices[0].y); + } + }, + mapFill: "#444", + bodyFill: "#999", + bodyStroke: "#222", + drawMapPath() { + ctx.fillStyle = this.mapFill; + ctx.fill(this.mapPath); + }, + + seeEdges() { + const eye = { + x: mech.pos.x + 20 * Math.cos(mech.angle), + y: mech.pos.y + 20 * Math.sin(mech.angle) + }; + //find all vertex nodes in range and in LOS + findNodes = function (domain, center) { + let nodes = []; + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + + for (let j = 0, len = vertices.length; j < len; j++) { + //calculate distance to player + const dx = vertices[j].x - center.x; + const dy = vertices[j].y - center.y; + if (dx * dx + dy * dy < 800 * 800 && Matter.Query.ray(domain, center, vertices[j]).length === 0) { + nodes.push(vertices[j]); + } + } + } + return nodes; + }; + let nodes = findNodes(map, eye); + //sort node list by angle to player + nodes.sort(function (a, b) { + //sub artan2 from player loc + const dx = a.x - eye.x; + const dy = a.y - eye.y; + return Math.atan2(dy, dx) - Math.atan2(dy, dx); + }); + //draw nodes + ctx.lineWidth = 2; + ctx.strokeStyle = "#000"; + ctx.beginPath(); + for (let i = 0; i < nodes.length; ++i) { + ctx.lineTo(nodes[i].x, nodes[i].y); + } + ctx.stroke(); + }, + see() { + const vertexCollision = function ( + v1, + v1End, + domain, + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + } + ) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + return best; + }; + const range = 3000; + ctx.beginPath(); + for (let i = 0; i < Math.PI * 2; i += Math.PI / 2 / 100) { + const cosAngle = Math.cos(mech.angle + i); + const sinAngle = Math.sin(mech.angle + i); + + const start = { + x: mech.pos.x + 20 * cosAngle, + y: mech.pos.y + 20 * sinAngle + }; + const end = { + x: mech.pos.x + range * cosAngle, + y: mech.pos.y + range * sinAngle + }; + let result = vertexCollision(start, end, map); + result = vertexCollision(start, end, body, result); + result = vertexCollision(start, end, mob, result); + + if (result.dist2 < range * range) { + // ctx.arc(result.x, result.y, 2, 0, 2 * Math.PI); + ctx.lineTo(result.x, result.y); + } else { + // ctx.arc(end.x, end.y, 2, 0, 2 * Math.PI); + ctx.lineTo(end.x, end.y); + } + } + // ctx.lineWidth = 1; + // ctx.strokeStyle = "#000"; + // ctx.stroke(); + ctx.fillStyle = "rgba(0,0,0,0.3)"; + ctx.fillStyle = "#fff"; + ctx.fill(); + ctx.clip(); + }, + body() { + ctx.beginPath(); + for (let i = 0, len = body.length; i < len; ++i) { + let vertices = body[i].vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + } + ctx.lineWidth = 2; + ctx.fillStyle = this.bodyFill; + ctx.fill(); + ctx.strokeStyle = this.bodyStroke; + ctx.stroke(); + }, + cons() { + ctx.beginPath(); + for (let i = 0, len = cons.length; i < len; ++i) { + ctx.moveTo(cons[i].pointA.x, cons[i].pointA.y); + ctx.lineTo(cons[i].bodyB.position.x, cons[i].bodyB.position.y); + } + for (let i = 0, len = consBB.length; i < len; ++i) { + ctx.moveTo(consBB[i].bodyA.position.x, consBB[i].bodyA.position.y); + ctx.lineTo(consBB[i].bodyB.position.x, consBB[i].bodyB.position.y); + } + ctx.lineWidth = 2; + // ctx.strokeStyle = "#999"; + ctx.strokeStyle = "rgba(0,0,0,0.15)"; + ctx.stroke(); + }, + wireFrame() { + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillStyle = "#999"; + const bodies = Composite.allBodies(engine.world); + ctx.beginPath(); + for (let i = 0; i < bodies.length; ++i) { + //ctx.fillText(bodies[i].id,bodies[i].position.x,bodies[i].position.y); //shows the id of every body + let vertices = bodies[i].vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + } + ctx.lineWidth = 1; + ctx.strokeStyle = "#000"; + ctx.stroke(); + }, + testing() { + //zones + ctx.beginPath(); + for (let i = 0, len = level.zones.length; i < len; ++i) { + ctx.rect(level.zones[i].x1, level.zones[i].y1 + 70, level.zones[i].x2 - level.zones[i].x1, level.zones[i].y2 - level.zones[i].y1); + } + ctx.fillStyle = "rgba(0, 255, 0, 0.3)"; + ctx.fill(); + //query zones + ctx.beginPath(); + for (let i = 0, len = level.queryList.length; i < len; ++i) { + ctx.rect( + level.queryList[i].bounds.max.x, + level.queryList[i].bounds.max.y, + level.queryList[i].bounds.min.x - level.queryList[i].bounds.max.x, + level.queryList[i].bounds.min.y - level.queryList[i].bounds.max.y + ); + } + ctx.fillStyle = "rgba(0, 0, 255, 0.2)"; + ctx.fill(); + //jump + ctx.beginPath(); + let bodyDraw = jumpSensor.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(255, 0, 0, 0.3)"; + ctx.fill(); + ctx.strokeStyle = "#000"; + ctx.stroke(); + //main body + ctx.beginPath(); + bodyDraw = playerBody.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(0, 255, 255, 0.3)"; + ctx.fill(); + ctx.stroke(); + //head + ctx.beginPath(); + bodyDraw = playerHead.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(255, 255, 0, 0.3)"; + ctx.fill(); + ctx.stroke(); + //head sensor + ctx.beginPath(); + bodyDraw = headSensor.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(0, 0, 255, 0.3)"; + ctx.fill(); + ctx.stroke(); + } + }, + checkLineIntersection(v1, v1End, v2, v2End) { + // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point + let denominator, a, b, numerator1, numerator2; + let result = { + x: null, + y: null, + onLine1: false, + onLine2: false + }; + denominator = (v2End.y - v2.y) * (v1End.x - v1.x) - (v2End.x - v2.x) * (v1End.y - v1.y); + if (denominator == 0) { + return result; + } + a = v1.y - v2.y; + b = v1.x - v2.x; + numerator1 = (v2End.x - v2.x) * a - (v2End.y - v2.y) * b; + numerator2 = (v1End.x - v1.x) * a - (v1End.y - v1.y) * b; + a = numerator1 / denominator; + b = numerator2 / denominator; + + // if we cast these lines infinitely in both directions, they intersect here: + result.x = v1.x + a * (v1End.x - v1.x); + result.y = v1.y + a * (v1End.y - v1.y); + // if line1 is a segment and line2 is infinite, they intersect if: + if (a > 0 && a < 1) result.onLine1 = true; + // if line2 is a segment and line1 is infinite, they intersect if: + if (b > 0 && b < 1) result.onLine2 = true; + // if line1 and line2 are segments, they intersect if both of the above are true + return result; + }, + //was used in level design + buildingUp(e) { + if (game.mouseDown) { + game.getCoords.pos2.x = Math.round(game.mouseInGame.x / 25) * 25; + game.getCoords.pos2.y = Math.round(game.mouseInGame.y / 25) * 25; + let out; + + //body rect mode + out = `spawn.mapRect(${game.getCoords.pos1.x}, ${game.getCoords.pos1.y}, ${game.getCoords.pos2.x - game.getCoords.pos1.x}, ${game.getCoords.pos2.y - + game.getCoords.pos1.y});`; + + //mob spawn + //out = `spawn.randomMob(${game.getCoords.pos1.x}, ${game.getCoords.pos1.y}, 0.3);` + + //draw foreground + //out = `level.fill.push({ x: ${game.getCoords.pos1.x}, y: ${game.getCoords.pos1.y}, width: ${game.getCoords.pos2.x-game.getCoords.pos1.x}, height: ${game.getCoords.pos2.y-game.getCoords.pos1.y}, color: "rgba(0,0,0,0.1)"});`; + + //draw background fill + //out = `level.fillBG.push({ x: ${game.getCoords.pos1.x}, y: ${game.getCoords.pos1.y}, width: ${game.getCoords.pos2.x-game.getCoords.pos1.x}, height: ${game.getCoords.pos2.y-game.getCoords.pos1.y}, color: "#ccc"});`; + + //svg mode + //out = 'rect x="'+game.getCoords.pos1.x+'" y="'+ game.getCoords.pos1.y+'" width="'+(game.getCoords.pos2.x-game.getCoords.pos1.x)+'" height="'+(game.getCoords.pos2.y-game.getCoords.pos1.y)+'"'; + + console.log(out); + // document.getElementById("copy-this").innerHTML = out + // + // window.getSelection().removeAllRanges(); + // var range = document.createRange(); + // range.selectNode(document.getElementById('copy-this')); + // window.getSelection().addRange(range); + // document.execCommand('copy') + // window.getSelection().removeAllRanges(); + } + } +}; \ No newline at end of file diff --git a/js/index.js b/js/index.js new file mode 100644 index 0000000..2ab2f0f --- /dev/null +++ b/js/index.js @@ -0,0 +1,275 @@ +"use strict"; +/* TODO: ******************************************* +***************************************************** + +run fast when shift is pressed + drains fieldMeter + +give mobs more animal-like behaviors + like rainworld + give mobs something to do when they don't see player + explore map + eat power ups + drop power up (if killed after eating one) + mobs some times aren't aggressive + when low on life or after taking a large hit + mobs can fight each other + this might be hard to code + when not near other mobs they try to group up. + +gun power ups + +explosion radius + +dmg + life steal + +bullet size + get bonus ammo / reduced ammo use + bullets pass through walls + unlimited ammo capacity + add in a max ammo capacity + + + +mutators (as a power up) + infinite ammo + or just more ammo from drops? + or 50% chance to not use up a bullet? + increased fire rate for guns + how to make laser fire faster? + orbiting orb fires at random targets + missiles at random targets + + low gravity + double jumps + higher horizontal run speed? + + vampire damage + shield (recharges fast, but only upto 10% of life) + +Active use abilities (can get ideas from spacetime) + blink (short distance teleport) + would reverse if they end up in solid wall + beacon teleport + push (push blocks, mobs, and bullets away from player) + invulnerability (force field that stops mobs and bullets) + burst of speed + intangible (can move through bodies, bullets, and mobs. Not map elements) + +game mechanics + mechanics that support the physics engine + add rope/constraint + store/spawn bodies in player (like the game Starfall) + get ideas from game: limbo / inside + environmental hazards + laser + lava + button / switch + door + fizzler + moving platform + map zones + water + low friction ground + bouncy ground + + give each foot a sensor to check for ground collisions + feet with not go into the ground even on slanted ground + this might be not worth it, but it might look really cool + +track foot positions with velocity better as the player walks/crouch/runs + +add bullet on damage effects + effects could: + add to the array mod.do new mob behaviors + add a damage over time + add a freeze + change mob traits + mass + friction + damage done + change things about the bullet + bounce the bullet again in a new direction + fire several bullets as shrapnel + increase the bullet size to do AOE dmg?? (how) + just run a for loop over all mobs, and do damage to the one that are close + bullets return to player + use a constraint? does bullet just start with a constraint or is it added on damage? + change the player + vampire bullets heal for the damage done + or give the player a shield?? + or only heal if the mob dies (might be tricky) + remove standing on player actions + replace with check if player feet are in an area. + +unused ideas +passive: walk through blocks (difficult to implement) + + + + + + +//collision info: + category mask +powerUp: 0x 100000 0x 100001 +mobBull: 0x 010000 0x 001001 +player: 0x 001000 0x 010011 +bullet: 0x 000100 0x 000011 +mob: 0x 000010 0x 001101 +map: 0x 000001 0x 111111 +body: 0x 000001 0x 011111 + +? hold: 0x 000001 0x 000001 + + +*/ + +//set up canvas +var canvas = document.getElementById("canvas"); +//using "const" causes problems in safari when an ID shares the same name. +const ctx = canvas.getContext("2d"); +document.body.style.backgroundColor = "#fff"; + +//disable pop up menu on right click +document.oncontextmenu = function () { + return false; +} + +function setupCanvas() { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + canvas.width2 = canvas.width / 2; //precalculated because I use this often (in mouse look) + canvas.height2 = canvas.height / 2; + canvas.diagonal = Math.sqrt(canvas.width2 * canvas.width2 + canvas.height2 * canvas.height2); + ctx.font = "15px Arial"; + ctx.lineJoin = "round"; + ctx.lineCap = "round"; + // ctx.lineCap='square'; + game.setZoom(); +} +setupCanvas(); +window.onresize = () => { + setupCanvas(); +}; + +//mouse move input +document.body.addEventListener("mousemove", (e) => { + game.mouse.x = e.clientX; + game.mouse.y = e.clientY; +}); + +document.body.addEventListener("mouseup", (e) => { + // game.buildingUp(e); //uncomment when building levels + game.mouseDown = false; + // console.log(e) + if (e.which === 3) { + game.mouseDownRight = false; + } else { + game.mouseDown = false; + } +}); + +document.body.addEventListener("mousedown", (e) => { + if (e.which === 3) { + game.mouseDownRight = true; + } else { + game.mouseDown = true; + } +}); + +//keyboard input +const keys = []; +document.body.addEventListener("keydown", (e) => { + keys[e.keyCode] = true; + game.keyPress(); +}); + +document.body.addEventListener("keyup", (e) => { + keys[e.keyCode] = false; +}); + +document.body.addEventListener("wheel", (e) => { + if (e.deltaY > 0) { + game.nextGun(); + } else { + game.previousGun(); + } +}, { + passive: true +}); + + +// window.addEventListener("gamepadconnected", function (e) { +// console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", +// e.gamepad.index, e.gamepad.id, +// e.gamepad.buttons.length, e.gamepad.axes.length); + +// }); + +game.loop = game.mouseLoop; +window.addEventListener("gamepadconnected", function (e) { + console.log('gamepad connected') + document.getElementById("gamepad").style.display = "inline"; + game.gamepad.connected = true; + polGamepadCycle(); + game.loop = game.gamepadLoop; +}); +window.addEventListener("gamepaddisconnected", function (e) { + disconnectGamepad() +}); + +function disconnectGamepad() { + console.log('gamepad disconnected') + document.getElementById("gamepad").style.display = "none"; + game.gamepad.connected = false; + game.loop = game.mouseLoop; +} + +//this runs to get gamepad data even when paused +function polGamepadCycle() { + game.gamepad.cycle++ + if (game.gamepad.connected) requestAnimationFrame(polGamepadCycle); + game.polGamepad() +} + + +// function playSound(id) { +// //play sound +// if (false) { +// //sounds are turned off for now +// // if (document.getElementById(id)) { +// var sound = document.getElementById(id); //setup audio +// sound.currentTime = 0; //reset position of playback to zero //sound.load(); +// sound.play(); +// } +// } + +function shuffle(array) { + var currentIndex = array.length, + temporaryValue, + randomIndex; + // While there remain elements to shuffle... + while (0 !== currentIndex) { + // Pick a remaining element... + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + // And swap it with the current element. + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } + return array; +} + + + +//main loop ************************************************************ +//********************************************************************** +function cycle() { + if (!game.paused) requestAnimationFrame(cycle); + const now = Date.now(); + const elapsed = now - game.then; // calc elapsed time since last loop + if (elapsed > game.fpsInterval) { // if enough time has elapsed, draw the next frame + game.then = now - (elapsed % game.fpsInterval); // Get ready for next frame by setting then=now. Also, adjust for fpsInterval not being multiple of 16.67 + game.loop(); + } +} \ No newline at end of file diff --git a/js/level.js b/js/level.js new file mode 100644 index 0000000..f2ad272 --- /dev/null +++ b/js/level.js @@ -0,0 +1,1424 @@ +//global game variables +let body = []; //non static bodies +let map = []; //all static bodies +let cons = []; //all constraints between a point and a body +let consBB = []; //all constraints between two bodies +//main object for spawning levels +const level = { + maxJump: 390, + boostScale: 0.000023, + levels: ["skyscrapers", "rooftops", "warehouse", "highrise", "towers"], + onLevel: 0, + start() { + // game.zoomScale = 1400 //1400 + if (game.levelsCleared === 0) { + this.intro(); + // spawn.setSpawnList(); + // game.levelsCleared = 3; //for testing to simulate all possible mobs spawns + // this.bosses(); + // this.testingMap(); + // this.skyscrapers(); + // this.rooftops(); + // this.warehouse(); + // this.highrise(); + // this.towers(); + } else { + spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns + this[this.levels[this.onLevel]](); //picks the current map from the the levels array + this.levelAnnounce(); + } + game.setZoom(); + this.addToWorld(); //add bodies to game engine + game.draw.setPaths(); + }, + //****************************************************************************************************************** + //****************************************************************************************************************** + testingMap() { + game.zoomScale = 1400 //1400 is normal + game.zoomTransition(1400) + spawn.setSpawnList(); + game.levelsCleared = 7; //for testing to simulate all possible mobs spawns + for (let i = 0; i < 7; i++) { + game.dmgScale += 0.4; //damage done by mobs increases each level + b.dmgScale *= 0.9; //damage done by player decreases each level + } + mech.setPosToSpawn(-75, -60); //normal spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + + level.exit.x = 3500; + level.exit.y = -870; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + //start with all guns + b.giveGuns("all", 1000) + + + // this.addZone(250, -1000, 500, 1500, "laser"); + //spawn.debris(0, -900, 4500, 10); //15 debris per level + // setTimeout(function() { + // document.body.style.backgroundColor = "#eee"; + // }, 1); + document.body.style.backgroundColor = "#fff"; + // document.body.style.backgroundColor = "#fafcff"; + // document.body.style.backgroundColor = "#bbb"; + // document.body.style.backgroundColor = "#eee4e4"; + // document.body.style.backgroundColor = "#dcdcde"; + // document.body.style.backgroundColor = "#e0e5e0"; + + // this.addQueryRegion(550, -25, 100, 50, "bounce", { Vx: 0, Vy: -25 }); + // level.fillBG.push({ x: 550, y: -25, width: 100, height: 50, color: "#ff0" }); + + spawn.mapRect(-1200, 0, 2200, 300); //left ground + spawn.mapRect(3500, -860, 100, 50); //ground bump wall + spawn.mapVertex(1250, 0, "0 0 0 300 -500 600 -500 300"); + spawn.mapRect(1500, -300, 2000, 300); //upper ground + spawn.mapVertex(3750, 0, "0 600 0 300 -500 0 -500 300"); + spawn.mapRect(4000, 0, 1000, 300); //right lower ground + spawn.mapRect(2200, -600, 600, 50); //center platform + spawn.mapRect(1300, -850, 700, 50); //center platform + spawn.mapRect(3000, -850, 700, 50); //center platform + // spawn.mapRect(0, -2000, 3000, 50); //center platform + spawn.spawnBuilding(-200, -250, 275, 240, false, true, "left"); //far left; player spawns in side + // spawn.boost(350, 0, -1000); + // for (let i = 0; i < 10; i++) { + // powerUps.spawn(950, -425, "gun", false); + // } + // for (let i = 0; i < 5; i++) { + // powerUps.spawn(2500 + i * 20, -1300, "gun", false); + // powerUps.spawn(2500 + i * 20, -1100, "ammo", false); + // } + // spawn.nodeBoss(-500, -600, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); + // spawn.lineBoss(-500, -600, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); + // spawn.bodyRect(-135, -50, 50, 50); + // spawn.bodyRect(-140, -100, 50, 50); + // spawn.bodyRect(-145, -150, 60, 50); + // spawn.bodyRect(-140, -200, 50, 50); + // spawn.bodyRect(-95, -50, 40, 50); + // spawn.bodyRect(-90, -100, 60, 50); + // spawn.bodyRect(300, -150, 140, 50); + // spawn.bodyRect(300, -150, 30, 30); + // spawn.bodyRect(300, -150, 20, 20); + // spawn.bodyRect(300, -150, 40, 100); + // spawn.bodyRect(300, -150, 40, 90); + // spawn.bodyRect(300, -150, 30, 60); + // spawn.bodyRect(300, -150, 40, 70); + // spawn.bodyRect(300, -150, 40, 60); + // spawn.bodyRect(300, -150, 20, 20); + // spawn.bodyRect(500, -150, 140, 110); + // spawn.bodyRect(600, -150, 140, 100); + // spawn.bodyRect(400, -150, 140, 160); + // spawn.bodyRect(500, -150, 110, 110); + powerUps.spawn(400, -400, "field", false, '4'); + // powerUps.spawn(400, -400, "gun", false); + // spawn.bodyRect(-45, -100, 40, 50); + // spawn.starter(800, -1150); + // spawn.groupBoss(-600, -550); + // for (let i = 0; i < 1; ++i) { + // spawn.chaser(800, -1150); + // } + spawn.groupBoss(900, -1070); + // for (let i = 0; i < 20; i++) { + // spawn.randomBoss(-100, -1470); + // } + }, + bosses() { + game.zoomTransition(1500) + + // spawn.setSpawnList(); + // spawn.setSpawnList(); + // game.levelsCleared = 7; //for testing to simulate all possible mobs spawns + // for (let i = 0; i < game.levelsCleared; i++) { + // game.dmgScale += 0.4; //damage done by mobs increases each level + // b.dmgScale *= 0.9; //damage done by player decreases each level + // } + + document.body.style.backgroundColor = "#444"; + + level.fillBG.push({ + x: -150, + y: -1150, + width: 7000, + height: 1200, + color: "#eee" + }); + + level.fill.push({ + x: 6400, + y: -550, + width: 300, + height: 350, + color: "rgba(0,255,255,0.1)" + }); + + mech.setPosToSpawn(0, -750); //normal spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = 6500; + level.exit.y = -230; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + spawn.mapRect(-250, 0, 7000, 200); //ground + spawn.mapRect(-350, -1200, 200, 1400); //left wall + spawn.mapRect(-250, -1200, 7000, 200); //roof + spawn.mapRect(-250, -700, 1000, 900); // shelf + spawn.mapRect(-250, -1200, 1000, 250); // shelf roof + powerUps.spawnStartingPowerUps(600, -800); + + function blockDoor(x, y, blockSize = 58) { + spawn.mapRect(x, y - 290, 40, 60); // door lip + spawn.mapRect(x, y, 40, 50); // door lip + for (let i = 0; i < 4; ++i) { + spawn.bodyRect(x + 5, y - 260 + i * blockSize, 30, blockSize); + } + } + blockDoor(710, -710); + + spawn[spawn.pickList[0]](1500, -200, 100 + game.levelsCleared * 8); + spawn.mapRect(2500, -1200, 200, 750); //right wall + blockDoor(2585, -210) + spawn.mapRect(2500, -200, 200, 300); //right wall + + spawn.nodeBoss(3500, -200, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); + spawn.mapRect(4500, -1200, 200, 750); //right wall + blockDoor(4585, -210) + spawn.mapRect(4500, -200, 200, 300); //right wall + + spawn.lineBoss(5000, -200, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); + spawn.mapRect(6400, -1200, 400, 750); //right wall + spawn.mapRect(6400, -200, 400, 300); //right wall + spawn.mapRect(6700, -1200, 200, 1400); //right wall + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump + + for (let i = 0; i < 5; ++i) { + if (game.levelsCleared * Math.random() > 3 * i) { + spawn.randomBoss(2000 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); + } + if (game.levelsCleared * Math.random() > 2.6 * i) { + spawn.randomBoss(3500 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); + } + if (game.levelsCleared * Math.random() > 2.4 * i) { + spawn.randomBoss(5000 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); + } + } + }, + //empty map for testing mobs + intro() { + game.zoomScale = 1000 //1400 is normal + game.zoomTransition(1600, 1) + + mech.setPosToSpawn(460, -100); //normal spawn + level.enter.x = -1000000; //offscreen + level.enter.y = -400; + level.exit.x = 2800; + level.exit.y = -335; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + document.body.style.backgroundColor = "#444"; + //controls instructions + // game.makeTextLog( + // "

W
A S D
", + // Infinity + // ); + + game.makeTextLog( + "



right mouse / space bar:
pick up things
", + Infinity + ); + level.fill.push({ + x: -150, + y: -1150, + width: 2750, + height: 1200, + color: "rgba(0,70,80,0.1)" + }); + + level.fillBG.push({ + x: -150, + y: -1150, + width: 2900, + height: 1200, + color: "#fff" + }); + level.fillBG.push({ + x: 2600, + y: -600, + width: 400, + height: 500, + color: "#edf9f9" + }); + + level.fillBG.push({ + x: 1600, + y: -500, + width: 100, + height: 100, + color: "#eee" + }); + + level.fillBG.push({ + x: -55, + y: -283, + width: 12, + height: 100, + color: "#eee" + }); + + //faster way to draw a wire + function wallWire(x, y, width, height, front = false) { + if (front) { + level.fill.push({ + x: x, + y: y, + width: width, + height: height, + color: "#aaa" + }); + } else { + level.fillBG.push({ + x: x, + y: y, + width: width, + height: height, + color: "#eee" + }); + } + } + + for (let i = 0; i < 3; i++) { + wallWire(100 - 10 * i, -1050 - 10 * i, 5, 800); + wallWire(100 - 10 * i, -255 - 10 * i, -300, 5); + } + for (let i = 0; i < 5; i++) { + wallWire(1000 + 10 * i, -1050 - 10 * i, 5, 600); + wallWire(1000 + 10 * i, -450 - 10 * i, 150, 5); + wallWire(1150 + 10 * i, -450 - 10 * i, 5, 500); + } + for (let i = 0; i < 3; i++) { + wallWire(2650 - 10 * i, -700 - 10 * i, -300, 5); + wallWire(2350 - 10 * i, -700 - 10 * i, 5, 800); + } + for (let i = 0; i < 5; i++) { + wallWire(1625 + 10 * i, -1050, 5, 1200); + } + for (let i = 0; i < 4; i++) { + wallWire(1650, -470 + i * 10, 670 - i * 10, 5); + wallWire(1650 + 670 - i * 10, -470 + i * 10, 5, 600); + } + for (let i = 0; i < 3; i++) { + wallWire(-200 - i * 10, -245 + i * 10, 1340, 5); + wallWire(1140 - i * 10, -245 + i * 10, 5, 300); + wallWire(-200 - i * 10, -215 + i * 10, 660, 5); + wallWire(460 - i * 10, -215 + i * 10, 5, 300); + } + spawn.mapRect(-250, 0, 3000, 200); //ground + spawn.mapRect(-350, -1200, 200, 1400); //left wall + spawn.mapRect(3000, -1200, 200, 1400); //right wall + spawn.mapRect(-250, -1200, 3000, 200); //roof + spawn.mapRect(2600, -300, 500, 500); //exit shelf + spawn.mapRect(2600, -1200, 500, 600); //exit roof + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump + spawn.mapRect(-95, -1100, 80, 110); //wire source + spawn.mapRect(410, -10, 90, 20); //small platform for player + + // spawn.bodyRect(-35, -50, 50, 50); + // spawn.bodyRect(-40, -100, 50, 50); + // spawn.bodyRect(-45, -150, 60, 50); + // spawn.bodyRect(-40, -200, 50, 50); + // spawn.bodyRect(5, -50, 40, 50); + // spawn.bodyRect(10, -100, 60, 50); + // spawn.bodyRect(-10, -150, 40, 50); + // spawn.bodyRect(55, -100, 40, 50); + // spawn.bodyRect(-150, -300, 100, 100); + // spawn.bodyRect(-150, -200, 100, 100); + // spawn.bodyRect(-150, -100, 100, 100); + + // spawn.bodyRect(1790, -50, 40, 50); + // spawn.bodyRect(1875, -100, 200, 90); + spawn.bodyRect(2425, -120, 70, 50); + spawn.bodyRect(2400, -100, 100, 60); + spawn.bodyRect(2500, -150, 100, 150); //exit step + + mech.health = 0.25; + mech.displayHealth(); + powerUps.spawn(-100, 0, "heal", false); //starting gun + powerUps.spawn(1900, -150, "heal", false); //starting gun + powerUps.spawn(2050, -150, "heal", false); //starting gun + // powerUps.spawn(2050, -150, "field", false); //starting gun + powerUps.spawn(2300, -150, "gun", false); //starting gun + + spawn.wireFoot(); + spawn.wireFootLeft(); + spawn.wireKnee(); + spawn.wireKneeLeft(); + spawn.wireHead(); + }, + + rooftops() { + game.zoomTransition(1700) //1400 is normal + + document.body.style.backgroundColor = "#dcdcde"; + + if (Math.random() < 0.75) { + //normal direction start in top left + mech.setPosToSpawn(-450, -2050); + level.exit.x = 3600; + level.exit.y = -300; + spawn.mapRect(3600, -285, 100, 50); //ground bump wall + //mobs that spawn in exit room + spawn.randomSmallMob(4100, -100); + spawn.randomSmallMob(4600, -100); + spawn.randomMob(3765, -450, 0.3); + level.fill.push({ + x: -650, + y: -2300, + width: 450, + height: 300, + color: "rgba(0,0,0,0.15)" + }); + } else { + //reverse direction, start in bottom right + mech.setPosToSpawn(3650, -310); + level.exit.x = -550; + level.exit.y = -2030; + spawn.mapRect(-550, -2015, 100, 50); //ground bump wall + spawn.boost(4950, 0, 1600); + level.fillBG.push({ + x: -650, + y: -2300, + width: 450, + height: 300, + color: "#d4f4f4" + }); + } + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + spawn.debris(1650, -1800, 3800, 20); //20 debris per level + powerUps.spawnStartingPowerUps(2450, -1675); + + //foreground + + level.fill.push({ + x: 3450, + y: -1250, + width: 1100, + height: 1250, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 4550, + y: -725, + width: 900, + height: 725, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3400, + y: 100, + width: 2150, + height: 900, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: -700, + y: -1950, + width: 2100, + height: 2950, + color: "rgba(0,0,0,0.1)" + }); + + level.fill.push({ + x: 1950, + y: -1950, + width: 600, + height: 350, + color: "rgba(0,0,0,0.1)" + }); + + level.fill.push({ + x: 1950, + y: -1550, + width: 1025, + height: 550, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 1600, + y: -900, + width: 1650, + height: 1900, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3450, + y: -1550, + width: 350, + height: 300, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 700, + y: -2225, + width: 700, + height: 225, + color: "rgba(0,0,0,0.1)" + }); + + //spawn.mapRect(-700, 0, 6250, 100); //ground + spawn.mapRect(3400, 0, 2150, 100); //ground + spawn.mapRect(-700, -2000, 2100, 50); //Top left ledge + spawn.bodyRect(1300, -2125, 50, 125, 0.8); + spawn.bodyRect(1307, -2225, 50, 100, 0.8); + spawn.mapRect(-700, -2350, 50, 400); //far left starting left wall + spawn.mapRect(-700, -2010, 500, 50); //far left starting ground + spawn.mapRect(-700, -2350, 500, 50); //far left starting ceiling + spawn.mapRect(-250, -2350, 50, 200); //far left starting right part of wall + spawn.bodyRect(-240, -2150, 30, 36); //door to starting room + spawn.bodyRect(-240, -2115, 30, 36); //door to starting room + spawn.bodyRect(-240, -2080, 30, 35); //door to starting room + spawn.bodyRect(-240, -2045, 30, 35); //door to starting room + + + spawn.mapRect(1950, -2000, 600, 50); + + + spawn.bodyRect(200, -2150, 200, 220, 0.8); + spawn.mapRect(700, -2275, 700, 50); + spawn.bodyRect(1050, -2350, 30, 30, 0.8); + spawn.boost(1800, -1000, 1200); + spawn.bodyRect(1625, -1100, 100, 75); + spawn.bodyRect(1350, -1025, 400, 25); // ground plank + spawn.mapRect(-700, -1000, 2100, 100); //lower left ledge + spawn.bodyRect(350, -1100, 200, 100, 0.8); + spawn.bodyRect(370, -1200, 100, 100, 0.8); + spawn.bodyRect(360, -1300, 100, 100, 0.8); + spawn.bodyRect(950, -1050, 300, 50, 0.8); + spawn.bodyRect(-600, -1250, 400, 250, 0.8); + spawn.mapRect(1600, -1000, 1650, 100); //middle ledge + spawn.bodyRect(2600, -1950, 100, 250, 0.8); + spawn.bodyRect(2700, -1125, 125, 125, 0.8); + spawn.bodyRect(2710, -1250, 125, 125, 0.8); + spawn.bodyRect(2705, -1350, 75, 100, 0.8); + spawn.mapRect(3450, -1600, 350, 50); + spawn.mapRect(1950, -1600, 1025, 50); + spawn.bodyRect(3100, -1015, 375, 15); + spawn.bodyRect(3500, -850, 75, 125, 0.8); + spawn.mapRect(3450, -1000, 50, 580); //left building wall + spawn.bodyRect(3460, -420, 30, 144); + + + spawn.mapRect(5450, -775, 100, 875); //right building wall + spawn.bodyRect(4850, -750, 300, 25, 0.8); + spawn.bodyRect(3925, -1400, 100, 150, 0.8); + spawn.mapRect(3450, -1250, 1100, 50); + spawn.mapRect(3450, -1225, 50, 75); + spawn.mapRect(4500, -1225, 50, 350); + spawn.mapRect(3450, -725, 1500, 50); + spawn.mapRect(5100, -725, 400, 50); + spawn.mapRect(4500, -700, 50, 600); + spawn.bodyRect(4510, -100, 30, 100, 0.8); + spawn.mapRect(4500, -925, 100, 50); + + spawn.spawnStairs(3800, 0, 3, 150, 206); //stairs top exit + spawn.mapRect(3400, -275, 450, 275); //exit platform + + + spawn.randomSmallMob(2200, -1775); + spawn.randomSmallMob(4000, -825); + spawn.randomSmallMob(-350, -2400); + spawn.randomMob(4250, -1350, 0.8); + spawn.randomMob(2550, -1350, 0.8); + spawn.randomMob(1225, -2400, 0.3); + spawn.randomMob(1120, -1200, 0.3); + spawn.randomMob(3000, -1150, 0.2); + spawn.randomMob(3200, -1150, 0.3); + spawn.randomMob(3300, -1750, 0.3); + spawn.randomMob(3650, -1350, 0.3); + spawn.randomMob(3600, -1800, 0.1); + spawn.randomMob(5200, -100, 0.3); + spawn.randomMob(5275, -900, 0.2); + spawn.randomMob(900, -2125, 0.3); + spawn.randomBoss(600, -1575, 0); + spawn.randomBoss(2225, -1325, 0.4); + spawn.randomBoss(4900, -1200, 0); + //spawn.randomBoss(4850, -1250,0.7); + if (game.levelsCleared > 4) spawn.bomber(2500, -2400, 100); + }, + skyscrapers() { + game.zoomTransition(2000) //1400 is normal + + mech.setPosToSpawn(-50, -50); //normal spawn + //mech.setPosToSpawn(1550, -1200); //spawn left high + //mech.setPosToSpawn(1800, -2000); //spawn near exit + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = 1500; + level.exit.y = -1875; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + powerUps.spawnStartingPowerUps(1475, -1175); + spawn.debris(0, -2200, 4500, 20); //20 debris per level + document.body.style.backgroundColor = "#dcdcde"; + + //foreground + level.fill.push({ + x: 2500, + y: -1100, + width: 450, + height: 250, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 2400, + y: -550, + width: 600, + height: 150, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 2550, + y: -1650, + width: 250, + height: 200, + color: "rgba(0,0,0,0.1)" + }); + //level.fill.push({ x: 1350, y: -2100, width: 400, height: 250, color: "rgba(0,255,255,0.1)" }); + level.fill.push({ + x: 700, + y: -110, + width: 400, + height: 110, + color: "rgba(0,0,0,0.2)" + }); + level.fill.push({ + x: 3600, + y: -110, + width: 400, + height: 110, + color: "rgba(0,0,0,0.2)" + }); + level.fill.push({ + x: -250, + y: -300, + width: 450, + height: 300, + color: "rgba(0,0,0,0.15)" + }); + + //background + level.fillBG.push({ + x: 1300, + y: -1800, + width: 750, + height: 1800, + color: "#d4d4d7" + }); + level.fillBG.push({ + x: 3350, + y: -1325, + width: 50, + height: 1325, + color: "#d4d4d7" + }); + level.fillBG.push({ + x: 1350, + y: -2100, + width: 400, + height: 250, + color: "#d4f4f4" + }); + + spawn.mapRect(-300, 0, 5000, 300); //***********ground + spawn.mapRect(-300, -350, 50, 400); //far left starting left wall + spawn.mapRect(-300, -10, 500, 50); //far left starting ground + spawn.mapRect(-300, -350, 500, 50); //far left starting ceiling + spawn.mapRect(150, -350, 50, 200); //far left starting right part of wall + spawn.bodyRect(170, -130, 14, 140, 1, spawn.propsFriction); //door to starting room + spawn.boost(475, 0, 1300); + spawn.mapRect(700, -1100, 400, 990); //far left building + spawn.mapRect(1600, -400, 1500, 500); //long center building + spawn.mapRect(1345, -1100, 250, 25); //left platform + spawn.mapRect(1755, -1100, 250, 25); //right platform + spawn.mapRect(1300, -1850, 750, 50); //left higher platform + spawn.mapRect(1300, -2150, 50, 350); //left higher platform left edge wall + spawn.mapRect(1300, -2150, 450, 50); //left higher platform roof + spawn.mapRect(1500, -1860, 100, 50); //ground bump wall + spawn.mapRect(2400, -850, 600, 300); //center floating large square + //spawn.bodyRect(2500, -1100, 25, 250); //wall before chasers + spawn.mapRect(2500, -1450, 450, 350); //higher center floating large square + spawn.mapRect(2500, -1675, 50, 300); //left wall on higher center floating large square + spawn.mapRect(2500, -1700, 300, 50); //roof on higher center floating large square + spawn.mapRect(3300, -850, 150, 25); //ledge by far right building + spawn.mapRect(3300, -1350, 150, 25); //higher ledge by far right building + spawn.mapRect(3600, -1100, 400, 990); //far right building + spawn.boost(4150, 0, 1300); + + spawn.bodyRect(3200, -1375, 300, 25, 0.9); + spawn.bodyRect(1825, -1875, 400, 25, 0.9); + // spawn.bodyRect(1800, -575, 250, 150, 0.8); + spawn.bodyRect(1800, -600, 250, 200, 0.8); + spawn.bodyRect(2557, -450, 35, 55, 0.7); + spawn.bodyRect(2957, -450, 30, 15, 0.7); + spawn.bodyRect(2900, -450, 60, 45, 0.7); + spawn.bodyRect(915, -1200, 60, 100, 0.95); + spawn.bodyRect(925, -1300, 50, 100, 0.95); + if (Math.random() < 0.9) { + spawn.bodyRect(2300, -1720, 400, 20); + spawn.bodyRect(2590, -1780, 80, 80); + } + spawn.bodyRect(2925, -1100, 25, 250, 0.8); + spawn.bodyRect(3325, -1550, 50, 200, 0.3); + if (Math.random() < 0.8) { + spawn.bodyRect(1400, -75, 200, 75); //block to get up ledge from ground + spawn.bodyRect(1525, -125, 50, 50); //block to get up ledge from ground + } + spawn.bodyRect(1025, -1110, 400, 25, 0.9); //block on far left building + spawn.bodyRect(1425, -1110, 115, 25, 0.9); //block on far left building + spawn.bodyRect(1540, -1110, 300, 25, 0.9); //block on far left building + + if (game.levelsCleared > 2) spawn.shooterBoss(2200, -1300); + spawn.randomSmallMob(1300, -70); + spawn.randomSmallMob(3200, -100); + spawn.randomSmallMob(4450, -100); + spawn.randomSmallMob(2700, -475); + spawn.randomMob(2650, -975, 0.8); + spawn.randomMob(2650, -1550, 0.8); + spawn.randomMob(4150, -200, 0.15); + spawn.randomMob(1700, -1300, 0.2); + spawn.randomMob(1850, -1950, 0.25); + spawn.randomMob(2610, -1880, 0.25); + spawn.randomMob(3350, -950, 0.25); + spawn.randomMob(1690, -2250, 0.25); + spawn.randomMob(2200, -600, 0.2); + spawn.randomMob(850, -1300, 0.25); + spawn.randomMob(-100, -900, -0.2); + spawn.randomBoss(3700, -1500, 0.4); + spawn.randomBoss(1700, -900, 0.4); + }, + highrise() { + game.zoomTransition(1500) //1400 is normal + document.body.style.backgroundColor = "#dcdcde" //"#fafcff"; + mech.setPosToSpawn(0, -700); //normal spawn + //mech.setPosToSpawn(-2000, -1700); // left ledge spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = -4275; + level.exit.y = -2805; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + powerUps.spawnStartingPowerUps(-2550, -700); + + // spawn.laserZone(-550, -350, 10, 400, 0.3) + // spawn.deathQuery(-550, -350, 50, 400) + + // spawn.debris(-3950, -2575, 1050, 4); //20 debris per level + spawn.debris(-2325, -1825, 2400); //20 debris per level + spawn.debris(-2625, -600, 925); //20 debris per level + // if (!game.levelsCleared) powerUps.spawn(2450, -1675, "gun", false); + //background + level.fillBG.push({ + x: -4425, + y: -3050, + width: 425, + height: 275, + color: "#cff" + }); + //foreground + level.fill.push({ + x: -1650, + y: -1575, + width: 550, + height: 425, + color: "rgba(0,0,0,0.12)" + }); + level.fill.push({ + x: -2600, + y: -2400, + width: 450, + height: 1800, + color: "rgba(0,0,0,0.12)" + }); + level.fill.push({ + x: -3425, + y: -2150, + width: 525, + height: 1550, + color: "rgba(0,0,0,0.12)" + }); + level.fill.push({ + x: -1850, + y: -1150, + width: 2025, + height: 1150, + color: "rgba(0,0,0,0.12)" + }); + + //hidden zone + level.fill.push({ + x: -4450, + y: -955, + width: 1025, + height: 360, + color: "rgba(64,64,64,0.97)" + }); + // level.fill.push({ + // x: -4050, + // y: -955, + // width: 625, + // height: 360, + // color: "#444" + // }); + powerUps.spawn(-4300, -700, "heal"); + powerUps.spawn(-4200, -700, "ammo"); + powerUps.spawn(-4100, -700, "gun"); + spawn.mapRect(-4450, -1000, 100, 500); + spawn.bodyRect(-3576, -750, 150, 150); + + + //building 1 + spawn.bodyRect(-1000, -675, 25, 25); + spawn.mapRect(-2225, 0, 2475, 150); + spawn.mapRect(175, -1000, 75, 1100); + + spawn.mapRect(-175, -985, 25, 175); + spawn.bodyRect(-170, -810, 14, 160, 1, spawn.propsFriction); //door to starting room + spawn.mapRect(-600, -650, 825, 50); + spawn.mapRect(-1300, -650, 500, 50); + spawn.mapRect(-175, -250, 425, 300); + spawn.bodyRect(-75, -300, 50, 50); + + // spawn.boost(-750, 0, 0, -0.01); + spawn.boost(-750, 0, 1700); + spawn.bodyRect(-425, -1375, 400, 225); + spawn.mapRect(-1125, -1575, 50, 475); + spawn.bodyRect(-1475, -1275, 250, 125); + spawn.bodyRect(-825, -1160, 250, 10); + + spawn.mapRect(-1650, -1575, 400, 50); + spawn.mapRect(-600, -1150, 850, 175); + + spawn.mapRect(-1850, -1150, 1050, 175); + spawn.bodyRect(-1907, -1600, 550, 25); + spawn.bodyRect(-1400, -125, 125, 125); + spawn.bodyRect(-1100, -125, 150, 125); + spawn.bodyRect(-1360, -200, 75, 75); + spawn.bodyRect(-1200, -75, 75, 75); + + //building 2 + spawn.mapRect(-4450, -600, 2300, 750); + spawn.mapRect(-2225, -500, 175, 550); + spawn.boost(-2800, -600, 1000); + spawn.mapRect(-3450, -1325, 550, 50); + spawn.mapRect(-3425, -2200, 525, 50); + spawn.mapRect(-2600, -1750, 450, 50); + spawn.mapRect(-2600, -2450, 450, 50); + spawn.bodyRect(-2275, -2700, 50, 60); + spawn.bodyRect(-2600, -1975, 250, 225); + spawn.bodyRect(-3415, -1425, 100, 100); + spawn.bodyRect(-3400, -1525, 100, 100); + spawn.bodyRect(-3305, -1425, 100, 100); + + //building 3 + spawn.mapRect(-4450, -1750, 1025, 1000); + spawn.mapRect(-3750, -2000, 175, 275); + spawn.mapRect(-4000, -2350, 275, 675); + // spawn.mapRect(-4450, -2650, 475, 1000); + spawn.mapRect(-4450, -2775, 475, 1125); + spawn.bodyRect(-3715, -2050, 50, 50); + spawn.bodyRect(-3570, -1800, 50, 50); + spawn.bodyRect(-2970, -2250, 50, 50); + spawn.bodyRect(-3080, -2250, 40, 40); + spawn.bodyRect(-3420, -650, 50, 50); + + //exit + spawn.mapRect(-4450, -3075, 25, 300); + spawn.mapRect(-4450, -3075, 450, 25); + spawn.mapRect(-4025, -3075, 25, 100); + spawn.mapRect(-4275, -2785, 100, 25); + + //mobs + spawn.randomMob(-2500, -2700, 1); + spawn.randomMob(-3200, -750, 1); + spawn.randomMob(-1875, -775, 0.2); + spawn.randomMob(-950, -1675, 0.2); + spawn.randomMob(-1525, -1750, 0.2); + spawn.randomMob(-1375, -1400, 0.2); + spawn.randomMob(-1625, -1275, 0.2); + spawn.randomMob(-1900, -1250, 0.2); + spawn.randomMob(-2250, -1850, 0.2); + spawn.randomMob(-2475, -2200, 0.2); + spawn.randomMob(-3000, -1475, 0.2); + spawn.randomMob(-3850, -2500, 0.2); + spawn.randomMob(-3650, -2125, 0.2); + spawn.randomMob(-4010, -3200, 0.2); + spawn.randomMob(-3500, -1825, 0.2); + spawn.randomMob(-975, -100, 0); + spawn.randomMob(-1050, -725, 0.2); + spawn.randomMob(-1525, -100, 0); + spawn.randomMob(-525, -1700, -0.1); + spawn.randomMob(-125, -1500, -0.1); + spawn.randomMob(-325, -1900, -0.1); + spawn.randomMob(-550, -100, -0.1); + spawn.randomBoss(-3250, -2700, 0.2); + spawn.randomBoss(-2450, -1100, 0); + }, + warehouse() { + game.zoomTransition(1300) + document.body.style.backgroundColor = "#bbb"; + mech.setPosToSpawn(25, -60); //normal spawn + //mech.setPosToSpawn(-2000, -1700); // left ledge spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = 425; + level.exit.y = -35; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + //level.addQueryRegion(-600, -250, 180, 420, "death", [[player]],{}); + + spawn.debris(-2250, 1330, 3000, 7); //20 debris per level + spawn.debris(-3000, -800, 3280, 7); //20 debris per level + spawn.debris(-1400, 410, 2300, 6); //20 debris per level + powerUps.spawnStartingPowerUps(25, 500); + + //foreground + // level.fill.push({ x: -3025, y: 50, width: 4125, height: 1350, color: "rgba(0,0,0,0.05)"}); + // level.fill.push({ x: -1800, y: -500, width: 1975, height: 550, color: "rgba(0,0,0,0.05)"}); + // level.fill.push({ x: -2600, y: -150, width: 700, height: 200, color: "rgba(0,0,0,0.05)"}); + //background + const BGColor = "#f3f3ea"; + level.fillBG.push({ + x: -3025, + y: 50, + width: 4125, + height: 1350, + color: BGColor + }); + level.fillBG.push({ + x: -1800, + y: -500, + width: 1625, + height: 555, + color: BGColor + }); + level.fillBG.push({ + x: -177, + y: -250, + width: 350, + height: 300, + color: "#e3e3da" + }); + level.fillBG.push({ + x: -2600, + y: -150, + width: 700, + height: 205, + color: BGColor + }); + level.fillBG.push({ + x: 300, + y: -250, + width: 350, + height: 250, + color: "#cff" + }); + spawn.mapRect(-1500, 0, 2750, 100); + spawn.mapRect(175, -270, 125, 300); + spawn.mapRect(-1900, -600, 1775, 100); + spawn.mapRect(-1900, -600, 100, 1300); + //house + spawn.mapRect(-175, -550, 50, 400); + spawn.mapRect(-175, -15, 350, 50); + spawn.mapRect(-25, -25, 100, 50); + // spawn.mapRect(-175, -275, 350, 25); + // spawn.mapRect(-175, -250, 25, 75); + // spawn.bodyRect(-170, -175, 14, 160, 1, spawn.propsFriction); //door to starting room + //exit house + spawn.mapRect(300, -15, 350, 50); + spawn.mapRect(-150, -300, 800, 50); + spawn.mapRect(600, -275, 50, 75); + spawn.mapRect(425, -25, 100, 25); + // spawn.mapRect(-1900, 600, 2700, 100); + spawn.mapRect(1100, 0, 150, 1500); + spawn.mapRect(-2850, 1400, 4100, 100); + spawn.mapRect(-2375, 875, 1775, 100); + spawn.mapRect(-1450, 950, 75, 346); + spawn.mapRect(-1433, 662, 41, 111); + spawn.bodyRect(-1418, 773, 11, 102, 1, spawn.propsFriction); //blocking path + spawn.mapRect(-2950, 1250, 175, 250); + spawn.mapRect(-3050, 1100, 150, 400); + spawn.mapRect(-3150, 50, 125, 1450); + spawn.mapRect(-2375, 600, 3175, 100); + spawn.mapRect(-2125, 400, 250, 275); + // spawn.mapRect(-1950, -400, 100, 25); + spawn.mapRect(-3150, 50, 775, 100); + spawn.mapRect(-2600, -250, 775, 100); + spawn.bodyRect(-1350, -200, 200, 200, 1, spawn.propsSlide); //weight + spawn.bodyRect(-1800, 0, 300, 100, 1, spawn.propsHoist); //hoist + cons[cons.length] = Constraint.create({ + pointA: { + x: -1650, + y: -500 + }, + bodyB: body[body.length - 1], + stiffness: 0.000076, + length: 1 + }); + + spawn.bodyRect(400, 400, 200, 200, 1, spawn.propsSlide); //weight + spawn.bodyRect(800, 600, 300, 100, 1, spawn.propsHoist); //hoist + cons[cons.length] = Constraint.create({ + pointA: { + x: 950, + y: 100 + }, + bodyB: body[body.length - 1], + stiffness: 0.000076, + length: 1 + }); + + spawn.bodyRect(-2775, 1150, 190, 150, 1, spawn.propsSlide); //weight + spawn.bodyRect(-2575, 1150, 200, 150, 1, spawn.propsSlide); //weight + spawn.bodyRect(-2775, 1300, 400, 100, 1, spawn.propsHoist); //hoist + cons[cons.length] = Constraint.create({ + pointA: { + x: -2575, + y: 150 + }, + bodyB: body[body.length - 1], + stiffness: 0.000076, + length: 220 + }); + + //blocks + //spawn.bodyRect(-155, -150, 10, 140, 1, spawn.propsFriction); + spawn.bodyRect(-165, -150, 30, 35, 1); + spawn.bodyRect(-165, -115, 30, 35, 1); + spawn.bodyRect(-165, -80, 30, 35, 1); + spawn.bodyRect(-165, -45, 30, 35, 1); + + spawn.bodyRect(-750, 400, 150, 150, 0.5); + spawn.bodyRect(-200, 1175, 250, 225, 1); //block to get to top path on bottom level + // spawn.bodyRect(-1450, 737, 75, 103, 0.5); //blocking path + + spawn.bodyRect(-2525, -50, 145, 100, 0.5); + spawn.bodyRect(-2325, -300, 150, 100, 0.5); + spawn.bodyRect(-1275, -750, 200, 150, 0.5); //roof block + spawn.bodyRect(-525, -700, 125, 100, 0.5); //roof block + + //mobs + spawn.randomSmallMob(-1125, 550); + spawn.randomSmallMob(-2325, 800); + spawn.randomSmallMob(-2950, -50); + spawn.randomSmallMob(825, 300); + spawn.randomSmallMob(-900, 825); + spawn.randomMob(-2025, 175, 0.6); + spawn.randomMob(-2325, 450, 0.6); + spawn.randomMob(-2925, 675, 0.5); + spawn.randomMob(-2700, 300, 0.2); + spawn.randomMob(-2500, 300, 0.2); + spawn.randomMob(-2075, -425, 0.2); + spawn.randomMob(-1550, -725, 0.2); + spawn.randomMob(375, 1100, 0.1); + spawn.randomMob(-1425, -100, 0.1); + spawn.randomMob(-800, -750, 0); + spawn.randomMob(400, -350, 0); + spawn.randomMob(650, 1300, 0); + spawn.randomMob(-750, -150, 0); + spawn.randomMob(475, 300, 0); + spawn.randomMob(-75, -700, 0); + spawn.randomMob(900, -200, -0.1); + spawn.randomBoss(-125, 275, -0.2); + spawn.randomBoss(-825, 1000, 0.2); + spawn.randomBoss(-1300, -1100, -0.3); + //spawn.randomBoss(600, -1575, 0); + //spawn.randomMob(1120, -1200, 0.3); + //spawn.randomSmallMob(2200, -1775); + + if (game.levelsCleared > 2) spawn.snaker(-1300 + Math.random() * 2000, -2200); //boss snake with head + }, + towers() { + game.zoomTransition(1400) + if (Math.random() < 0.75) { + //normal direction start in top left + mech.setPosToSpawn(1375, -1550); //normal spawn + level.exit.x = 3250; + level.exit.y = -530; + // spawn.randomSmallMob(3550, -550); + } else { + //reverse direction, start in bottom right + mech.setPosToSpawn(3250, -530); //normal spawn + level.exit.x = 1375; + level.exit.y = -1530; + spawn.bodyRect(3655, -650, 40, 150); //door + } + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 50); //ground bump wall + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + document.body.style.backgroundColor = "#e0e5e0"; + //foreground + level.fill.push({ + x: -550, + y: -1700, + width: 1300, + height: 1700, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 750, + y: -1450, + width: 650, + height: 1450, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 750, + y: -1950, + width: 800, + height: 450, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3000, + y: -1000, + width: 650, + height: 1000, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3650, + y: -1300, + width: 1300, + height: 1300, + color: "rgba(0,0,0,0.1)" + }); + + //mech.setPosToSpawn(600, -1200); //normal spawn + //mech.setPosToSpawn(525, -150); //ground first building + //mech.setPosToSpawn(3150, -700); //near exit spawn + spawn.debris(-300, -200, 1000, 5); //ground debris //20 debris per level + spawn.debris(3500, -200, 800, 5); //ground debris //20 debris per level + spawn.debris(-300, -650, 1200, 5); //1st floor debris //20 debris per level + spawn.debris(3500, -650, 800, 5); //1st floor debris //20 debris per level + powerUps.spawnStartingPowerUps(-525, -700); + + spawn.mapRect(-600, 25, 5600, 300); //ground + spawn.mapRect(-600, 0, 2000, 50); //ground + spawn.mapRect(-600, -1700, 50, 2000 - 100); //left wall + spawn.bodyRect(-295, -1540, 40, 40); //center block under wall + spawn.bodyRect(-298, -1580, 40, 40); //center block under wall + spawn.bodyRect(1500, -1540, 30, 30); //left of entrance + + spawn.mapRect(1550, -2000, 50, 550); //right wall + spawn.mapRect(1350, -2000 + 505, 50, 1295); //right wall + spawn.mapRect(-600, -2000 + 250, 2000 - 700, 50); //roof left + spawn.mapRect(-600 + 1300, -2000, 50, 300); //right roof wall + spawn.mapRect(-600 + 1300, -2000, 900, 50); //center wall + map[map.length] = Bodies.polygon(425, -1700, 0, 15); //circle above door + spawn.bodyRect(420, -1675, 15, 170, 1, spawn.propsDoor); // door + body[body.length - 1].isNotHoldable = true; + //makes door swing + consBB[consBB.length] = Constraint.create({ + bodyA: body[body.length - 1], + pointA: { + x: 0, + y: -90 + }, + bodyB: map[map.length - 1], + stiffness: 1 + }); + spawn.mapRect(-600 + 300, -2000 * 0.75, 1900, 50); //3rd floor + spawn.mapRect(-600 + 2000 * 0.7, -2000 * 0.74, 50, 375); //center wall + spawn.bodyRect(-600 + 2000 * 0.7, -2000 * 0.5 - 106, 50, 106); //center block under wall + spawn.mapRect(-600, -1000, 1100, 50); //2nd floor + spawn.mapRect(600, -1000, 500, 50); //2nd floor + spawn.spawnStairs(-600, -1000, 4, 250, 350); //stairs 2nd + spawn.mapRect(350, -600, 350, 150); //center table + spawn.mapRect(-600 + 300, -2000 * 0.25, 2000 - 300, 50); //1st floor + spawn.spawnStairs(-600 + 2000 - 50, -500, 4, 250, 350, true); //stairs 1st + spawn.spawnStairs(-600, 0, 4, 250, 350); //stairs ground + spawn.bodyRect(700, -200, 100, 100); //center block under wall + spawn.bodyRect(700, -300, 100, 100); //center block under wall + spawn.bodyRect(700, -400, 100, 100); //center block under wall + spawn.mapRect(1390, 13, 30, 20); //step left + spawn.mapRect(2980, 13, 30, 20); //step right + spawn.mapRect(3000, 0, 2000, 50); //ground + spawn.bodyRect(4250, -700, 50, 100); + spawn.bodyRect(3000, -200, 50, 200); //door + spawn.mapRect(3000, -1000, 50, 800); //left wall + spawn.mapRect(3000 + 2000 - 50, -1300, 50, 1100); //right wall + spawn.mapRect(4150, -600, 350, 150); //table + spawn.mapRect(3650, -1300, 50, 650); //exit wall + spawn.mapRect(3650, -1300, 1350, 50); //exit wall + + spawn.mapRect(3000, -2000 * 0.5, 700, 50); //exit roof + spawn.mapRect(3000, -2000 * 0.25, 2000 - 300, 50); //1st floor + spawn.spawnStairs(3000 + 2000 - 50, 0, 4, 250, 350, true); //stairs ground + + // tether ball + if (game.levelsCleared > 2) { + level.fillBG.push({ + x: 2495, + y: -500, + width: 10, + height: 525, + color: "#ccc" + }); + spawn.tether(2850, -80) + cons[cons.length] = Constraint.create({ + pointA: { + x: 2500, + y: -500 + }, + bodyB: mob[mob.length - 1], + stiffness: 0.00012 + }); + //chance to spawn a ring of exploding mobs around this boss + if (game.levelsCleared > 4) spawn.nodeBoss(2850, -80, "spawns", 8, 20, 105); + } + + spawn.randomSmallMob(4575, -560, 1); + spawn.randomSmallMob(1315, -880, 1); + spawn.randomSmallMob(800, -600); + spawn.randomSmallMob(-100, -1600); + spawn.randomMob(4100, -225, 0.8); + spawn.randomMob(-250, -700, 0.8); + spawn.randomMob(4500, -225, 0.15); + spawn.randomMob(3250, -225, 0.15); + spawn.randomMob(-100, -225, 0.1); + spawn.randomMob(1150, -225, 0.15); + spawn.randomMob(2000, -225, 0.15); + spawn.randomMob(450, -225, 0.15); + spawn.randomMob(100, -1200, 1); + spawn.randomMob(950, -1150, -0.1); + spawn.randomBoss(1800, -800, -0.2); + spawn.randomBoss(4150, -1000, 0.6); + }, + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + enter: { + x: 0, + y: 0, + draw() { + ctx.beginPath(); + ctx.moveTo(this.x, this.y + 30); + ctx.lineTo(this.x, this.y - 80); + ctx.bezierCurveTo(this.x, this.y - 170, this.x + 100, this.y - 170, this.x + 100, this.y - 80); + ctx.lineTo(this.x + 100, this.y + 30); + ctx.lineTo(this.x, this.y + 30); + ctx.fillStyle = "#ccc"; + ctx.fill(); + } + }, + exit: { + x: 0, + y: 0, + draw() { + ctx.beginPath(); + ctx.moveTo(this.x, this.y + 30); + ctx.lineTo(this.x, this.y - 80); + ctx.bezierCurveTo(this.x, this.y - 170, this.x + 100, this.y - 170, this.x + 100, this.y - 80); + ctx.lineTo(this.x + 100, this.y + 30); + ctx.lineTo(this.x, this.y + 30); + ctx.fillStyle = "#0ff"; + ctx.fill(); + } + }, + fillBG: [], + drawFillBGs() { + for (let i = 0, len = level.fillBG.length; i < len; ++i) { + const f = level.fillBG[i]; + ctx.fillStyle = f.color; + ctx.fillRect(f.x, f.y, f.width, f.height); + } + }, + + fill: [], + drawFills() { + for (let i = 0, len = level.fill.length; i < len; ++i) { + const f = level.fill[i]; + ctx.fillStyle = f.color; + ctx.fillRect(f.x, f.y, f.width, f.height); + } + }, + zones: [], //zone do actions when player is in a region // to effect everything use a query + checkZones() { + for (let i = 0, len = this.zones.length; i < len; ++i) { + if ( + player.position.x > this.zones[i].x1 && + player.position.x < this.zones[i].x2 && + player.position.y > this.zones[i].y1 && + player.position.y < this.zones[i].y2 + ) { + this.zoneActions[this.zones[i].action](i); + break; + } + } + }, + addZone(x, y, width, height, action, info) { + this.zones[this.zones.length] = { + x1: x, + y1: y - 150, + x2: x + width, + y2: y + height - 70, //-70 to adjust for player height + action: action, + info: info + }; + }, + zoneActions: { + fling(i) { + Matter.Body.setVelocity(player, { + x: level.zones[i].info.Vx, + y: level.zones[i].info.Vy + }); + }, + nextLevel() { + //enter when player isn't falling + if (player.velocity.y < 0.1) { + //increases difficulty + game.levelsCleared++; + if (game.levelsCleared > 1) { + game.dmgScale += 0.25; //damage done by mobs increases each level + b.dmgScale *= 0.93; //damage done by player decreases each level + } + //cycles map to next level + level.onLevel++; + if (level.onLevel > level.levels.length - 1) level.onLevel = 0; + + game.clearNow = true; //triggers in the physics engine to remove all physics bodies + } + }, + death() { + mech.death(); + }, + laser(i) { + //draw these in game with spawn.background + mech.damage(level.zones[i].info.dmg); + }, + slow() { + Matter.Body.setVelocity(player, { + x: player.velocity.x * 0.5, + y: player.velocity.y * 0.5 + }); + } + }, + queryList: [], //queries do actions on many objects in regions (for only player use a zone) + checkQuery() { + let bounds, action, info; + + function isInZone(targetArray) { + let results = Matter.Query.region(targetArray, bounds); + for (let i = 0, len = results.length; i < len; ++i) { + level.queryActions[action](results[i], info); + } + } + for (let i = 0, len = level.queryList.length; i < len; ++i) { + bounds = level.queryList[i].bounds; + action = level.queryList[i].action; + info = level.queryList[i].info; + for (let j = 0, l = level.queryList[i].groups.length; j < l; ++j) { + isInZone(level.queryList[i].groups[j]); + } + } + }, + //oddly query regions can't get smaller than 50 width? + addQueryRegion(x, y, width, height, action, groups = [ + [player], body, mob, powerUp, bullet + ], info) { + this.queryList[this.queryList.length] = { + bounds: { + min: { + x: x, + y: y + }, + max: { + x: x + width, + y: y + height + } + }, + action: action, + groups: groups, + info: info + }; + }, + queryActions: { + bounce(target, info) { + //jerky fling upwards + Matter.Body.setVelocity(target, { + x: info.Vx + (Math.random() - 0.5) * 6, + y: info.Vy + }); + target.torque = (Math.random() - 0.5) * 2 * target.mass; + }, + boost(target, info) { + // if (target.velocity.y < 0) { + // mech.undoCrouch(); + // mech.enterAir(); + mech.buttonCD_jump = 0; // reset short jump counter to prevent short jumps on boosts + mech.hardLandCD = 0 // disable hard landing + Matter.Body.setVelocity(target, { + x: target.velocity.x + (Math.random() - 0.5) * 2, + y: info + }); + }, + force(target, info) { + if (target.velocity.y < 0) { + //gently force up if already on the way up + target.force.x += info.Vx * target.mass; + target.force.y += info.Vy * target.mass; + } else { + target.force.y -= 0.0007 * target.mass; //gently fall in on the way down + } + }, + antiGrav(target) { + target.force.y -= 0.0011 * target.mass; + }, + death(target) { + target.death(); + } + }, + levelAnnounce() { + let text = "L" + (game.levelsCleared) + " " + level.levels[level.onLevel]; + if (game.levelsCleared === 0) text = ""; + // text = "Level " + (game.levelsCleared + 1) + ": " + spawn.pickList[0] + "s + " + spawn.pickList[1] + "s"; + game.makeTextLog(text, 300); + document.title = "n-gon: " + text; + + // text = text + " with population: "; + // for (let i = 0, len = spawn.pickList.length; i < len; ++i) { + // if (spawn.pickList[i] != spawn.pickList[i - 1]) { + // text += spawn.pickList[i] + ", "; + // } + // } + // this.speech(text); + // game.makeTextLog(text, 360); + }, + addToWorld(mapName) { + //needs to be run to put bodies into the world + for (let i = 0; i < body.length; i++) { + //body[i].collisionFilter.group = 0; + body[i].collisionFilter.category = 0x0000001; + body[i].collisionFilter.mask = 0x011111; + body[i].classType = "body"; + World.add(engine.world, body[i]); //add to world + } + for (let i = 0; i < map.length; i++) { + //map[i].collisionFilter.group = 0; + map[i].collisionFilter.category = 0x000001; + map[i].collisionFilter.mask = 0x111111; + Matter.Body.setStatic(map[i], true); //make static + World.add(engine.world, map[i]); //add to world + } + for (let i = 0; i < cons.length; i++) { + World.add(engine.world, cons[i]); + } + for (let i = 0; i < consBB.length; i++) { + World.add(engine.world, consBB[i]); + } + } +}; \ No newline at end of file diff --git a/js/level.procedural.js b/js/level.procedural.js new file mode 100644 index 0000000..7a47a19 --- /dev/null +++ b/js/level.procedural.js @@ -0,0 +1,998 @@ +//global game variables +let body = []; //non static bodies +let map = []; //all static bodies +let cons = []; //all constaints between a point and a body +let consBB = []; //all constaints between two bodies +//main object for spawning levels +const level = { + onLevel: undefined, + start: function() { + // game.levelsCleared = 3; //for testing to simulate all possible mobs spawns + // game.draw.setMapColors(); + spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns + // this.procedural(); + this.testingMap(); + //this.warehouse(); //this.highrise(); //this.towers(); // this.skyscrapers(); //this.rooftops(); + this.addToWorld(); //add map to world + game.draw.setPaths(); + this.levelAnnounce(); + }, + //****************************************************************************************************************** + //****************************************************************************************************************** + //empty map for testing mobs + testingMap: function() { + // game.levelsCleared = 5; //for testing to simulate all possible mobs spawns + // for (let i = 0; i < 5; i++) { + // game.dmgScale += 0.4; //damage done by mobs increases each level + // b.dmgScale *= 0.9; //damage done by player decreases each level + // } + mech.setPosToSpawn(-75, -60); //normal spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + + level.exit.x = 3500; + level.exit.y = -870; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + // this.addZone(250, -1000, 500, 1500, "laser"); + //spawn.debris(0, -900, 4500, 10); //15 debris per level + // setTimeout(function() { + // document.body.style.backgroundColor = "#eee"; + // }, 1); + document.body.style.backgroundColor = "#fff"; + // document.body.style.backgroundColor = "#fafcff"; + // document.body.style.backgroundColor = "#bbb"; + // document.body.style.backgroundColor = "#eee4e4"; + // document.body.style.backgroundColor = "#dcdcde"; + // document.body.style.backgroundColor = "#e0e5e0"; + + // this.addQueryRegion(550, -25, 100, 50, "bounce", { Vx: 0, Vy: -25 }); + // level.fillBG.push({ x: 550, y: -25, width: 100, height: 50, color: "#ff0" }); + + spawn.mapRect(-1200, 0, 2200, 300); //left ground + spawn.mapRect(3500, -860, 100, 50); //ground bump wall + spawn.mapVertex(1250, 0, "0 0 0 300 -500 600 -500 300"); + spawn.mapRect(1500, -300, 2000, 300); //upper ground + spawn.mapVertex(3750, 0, "0 600 0 300 -500 0 -500 300"); + spawn.mapRect(4000, 0, 1000, 300); //right lower ground + spawn.mapRect(2200, -600, 600, 50); //center platform + spawn.mapRect(1300, -850, 700, 50); //center platform + spawn.mapRect(3000, -850, 700, 50); //center platform + // spawn.mapRect(0, -2000, 3000, 50); //center platform + spawn.spawnBuilding(-200, -250, 275, 240, false, true, "left"); //far left; player spawns in side + // spawn.boost(350, 0, -1000); + for (let i = 0; i < 17; i++) { + powerUps.spawn(450, -125, "gun", false); + } + for (let i = 0; i < 5; i++) { + powerUps.spawn(2500 + i * 20, -1300, "gun", false); + powerUps.spawn(2500 + i * 20, -1100, "ammo", false); + } + spawn.bodyRect(700, -50, 50, 50); + spawn.bodyRect(700, -100, 50, 50); + spawn.bodyRect(700, -150, 50, 50); + spawn.bodyRect(700, -200, 50, 50); + spawn.bodyRect(-100, -260, 250, 10); + // spawn.springer(100, -550); + // spawn.grower(100, -550); + // spawn.chaser(100, -550); + // spawn.striker(100, -550); + // spawn.spinner(100, -550); + // spawn.hopper(100, -550); + // spawn.grower(100, -550); + // spawn.springer(100, -550); + // spawn.zoomer(100, -550); + // spawn.shooter(100, -550); + // spawn.beamer(100, -550); + // spawn.focuser(100, -550); + // spawn.laser(100, -550); + // spawn.blinker(100, -550); + // spawn.drifter(100, -550); + // spawn.sucker(100, -550); + // spawn.exploder(100, -550); + // spawn.spawner(100, -550); + // spawn.ghoster(100, -550); + // spawn.sneaker(100, -550); + // spawn.bomber(100, -550); + + // spawn.flocker(-600, -650); + + // spawn.flocker(-600, -550); + // spawn.flocker(-600, -750); + // spawn.starter(-600, -550); + // spawn.flocker(-900, -850); + // spawn.flocker(-600, -550); + // spawn.flocker(-600, -750); + spawn.starter(-600, -550); + // for (let i = 0; i < 4; ++i) { + // spawn.shooter(800, -1150); + // } + // spawn.nodeBoss(900, -1070, "shooter", 9); + // spawn.randomBoss(-100, -1470); + // spawn.randomBoss(500, -1470); + // spawn.randomBoss(900, -1470); + // spawn.randomBoss(900, -1000); + }, + //**************************************************************************************************** + //**************************************************************************************************** + //**************************************************************************************************** + //**************************************************************************************************** + //**************************************************************************************************** + procedural: function() { + const mobChance = 0.3; + const maxJump = 390; + const boostScale = 0.000023; + // + mech.setPosToSpawn(0, -10); //normal spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y - 15; + //starting zone + spawn.mapRect(-250, -320, 50, 400); //far left starting left wall + spawn.mapRect(-250, -0, 500, 110); //far left starting ground + spawn.mapRect(-250, -340, 500, 50); //far left starting ceiling + spawn.mapRect(200, -320, 50, 180); //far left starting right part of wall + spawn.bodyRect(220, -120, 14, 140, 1, spawn.propsFriction); //door to starting room + spawn.mapRect(-50, -10, 100, 50); + spawn.mapRect(0, 10, 400, 100); //***********ground + if (b.inventory.length < 3) { + powerUps.spawn(550, -50, "gun", false); //starting gun + } else { + powerUps.spawnRandomPowerUp(550, -50); + } + level.fill.push({ + x: -250, + y: -300, + width: 500, + height: 300, + color: "rgba(0,0,0,0.15)" + }); + + let o = { x: 400, y: 10 }; //keeps track of where the next mapNode should be added + mapNode = [ + function() { + //one or two shelves + let nodeRange = { x: 650 + Math.round(Math.random() * 900), y: 0 }; + spawn.mapRect(o.x, o.y, nodeRange.x, 100); + spawn.randomMob(o.x + 200 + 600 * Math.random(), o.y - 50, mobChance); + spawn.debris(o.x + 50, o.y, nodeRange.x - 100, 2); + + //shelf(s) + const shelfElevation = 230 + 200 * Math.random(); + const shelfWidth = nodeRange.x - 200 - 300 * Math.random(); + if (Math.random() < 0.85 && shelfWidth > 650) { + const len = 1 + Math.ceil(Math.random() * Math.random() * Math.random() * 5); + const sep = 50 + 100 * Math.random(); + const x1 = o.x + nodeRange.x / 2 - shelfWidth / 2; + const x2 = o.x + nodeRange.x / 2 + sep; + for (let i = 1; i < len; ++i) { + //two shelves + spawn.mapRect(x1, o.y - shelfElevation * i, shelfWidth / 2 - sep, 50); + spawn.mapRect(x2, o.y - shelfElevation * i, shelfWidth / 2 - sep, 50); + //plank connecting shelves + spawn.bodyRect( + o.x + (nodeRange.x - shelfWidth) / 2 + shelfWidth / 2 - sep - 75, + o.y - shelfElevation * i - 30, + sep + 250, + 10, + 0.6, + spawn.propsFrictionMedium + ); + } + level.fillBG.push({ + x: x1, + y: o.y - shelfElevation * (len - 1), + width: shelfWidth / 2 - sep, + height: shelfElevation * (len - 1), + color: "#f0f0f3" + }); + level.fillBG.push({ + x: x2, + y: o.y - shelfElevation * (len - 1), + width: shelfWidth / 2 - sep, + height: shelfElevation * (len - 1), + color: "#f0f0f3" + }); + } else { + const len = 1 + Math.ceil(Math.random() * Math.random() * Math.random() * 5); + for (let i = 1; i < len; ++i) { + //one shelf + spawn.mapRect(o.x + (nodeRange.x - shelfWidth) / 2, o.y - shelfElevation * i, shelfWidth, 50); + level.fillBG.push({ + x: o.x + (nodeRange.x - shelfWidth) / 2, + y: o.y - shelfElevation * (len - 1), + width: shelfWidth, + height: shelfElevation * (len - 1), + color: "#f0f0f3" + }); + } + } + + //square block + const blockSize = 60 + Math.random() * 120; + if (shelfElevation > blockSize + 100) { + spawn.bodyRect(o.x + 100 + Math.random() * (nodeRange.x - 200), o.y - blockSize, blockSize, blockSize, 0.3); + } else { + spawn.bodyRect(o.x + 50 + (nodeRange.x - shelfWidth) / 2, o.y - shelfElevation - blockSize, blockSize, blockSize, 0.3); + } + spawn.randomMob(o.x + (nodeRange.x - shelfWidth) / 2, o.y - shelfElevation - 100, mobChance); //mob above shelf + spawn.debris(o.x + 50 + (nodeRange.x - shelfWidth) / 2, o.y - shelfElevation - 50, shelfWidth - 100, 1); + // set starting position for new mapNode + o.x += nodeRange.x; + o.y += nodeRange.y; + }, + function() { + //several platforms that rise up + const wide = 120 + Math.floor(Math.random() * Math.random() * 450); + let nodeRange = { x: 100, y: -Math.floor(200 + Math.random() * 150) }; + for (let i = 0, len = 1 + Math.ceil(Math.random() * 4); i < len; i++) { + spawn.platform(o.x + nodeRange.x, o.y, wide, nodeRange.y); + spawn.debris(o.x + nodeRange.x - 120, o.y - 50, wide + 250, Math.floor(Math.random() * 0.8 + 0.5)); + if (Math.random() < 0.3) { + spawn.randomBoss(o.x + nodeRange.x + 20 + (wide - 40) * Math.random(), o.y + nodeRange.y - 450, mobChance); + } else { + spawn.randomMob(o.x + nodeRange.x + 20 + (wide - 40) * Math.random(), o.y + nodeRange.y - 50, mobChance); //mob above shelf + } + nodeRange.x += Math.floor(wide + 155 + Math.random() * 200); + nodeRange.y -= Math.floor(115 + Math.random() * 200); + } + spawn.mapRect(o.x, o.y, nodeRange.x, 100); //ground + spawn.mapRect(o.x + nodeRange.x, o.y + nodeRange.y + 15, 100, -nodeRange.y + 85); //right wall + // set starting position for new mapNode + o.x += nodeRange.x; + o.y += nodeRange.y; + }, + function() { + //s-shaped building goes up 2 levels + const floorHeight = maxJump - Math.floor(Math.random() * 150); //maxJump = 390 + let nodeRange = { x: 700 + Math.floor(Math.random() * 800), y: -floorHeight * 2 }; + const wallWidth = 20 + Math.floor(Math.random() * 40); + const numberOfFloors = 2 + Math.floor(Math.random() * 2); + const frontYardWidth = 250; + o.x += frontYardWidth; + spawn.mapRect(o.x - frontYardWidth, o.y, nodeRange.x + frontYardWidth, 100); //first floor ground + let floorWidth = nodeRange.x - wallWidth - 250 - Math.random() * Math.random() * nodeRange.x * 0.4; //possible open area with only half long 2nd floor + spawn.mapRect(o.x + wallWidth, o.y - floorHeight, floorWidth, wallWidth); //second floor + spawn.mapRect(o.x + 300, o.y - floorHeight * 2, nodeRange.x - 300, wallWidth); //third floor + if (numberOfFloors > 2) spawn.mapRect(o.x, o.y - floorHeight * 3, nodeRange.x, wallWidth); //optional roof + spawn.mapRect(o.x, o.y - floorHeight * numberOfFloors, wallWidth, floorHeight * numberOfFloors - 175); //left building wall + if (Math.random() < 0.8) { + spawn.mapRect(o.x + nodeRange.x - wallWidth, o.y - floorHeight * 2 + wallWidth, wallWidth, floorHeight * 2); //right building wall + } else { + spawn.mapRect(o.x + nodeRange.x - wallWidth, o.y - floorHeight * 2 + wallWidth, wallWidth, floorHeight * 2 - 175 - wallWidth); //right building wall with right door + } + level.fill.push({ + x: o.x, + y: o.y - floorHeight * numberOfFloors, + width: nodeRange.x, + height: floorHeight * numberOfFloors, + color: "rgba(0,0,0,0.1)" + }); + + //random extras + const debrisRange = nodeRange.x - wallWidth * 4; + spawn.debris(o.x + wallWidth, o.y - 50, debrisRange, 1); + spawn.debris(o.x + wallWidth, o.y - 50 - floorHeight, debrisRange - 250, 1); + spawn.debris(o.x + wallWidth + 250, o.y - 50 - floorHeight * 2, debrisRange - 250, 1); + spawn.randomSmallMob(o.x + wallWidth + Math.random() * debrisRange, o.y - 80, 3); + spawn.randomSmallMob(o.x + wallWidth + Math.random() * (debrisRange - 250), o.y - 80 - floorHeight); + spawn.randomSmallMob(o.x + wallWidth + 250 + Math.random() * (debrisRange - 250), o.y - 80 - floorHeight * 2); + let blockSize = 70 + Math.random() * 70; + spawn.bodyRect(o.x - blockSize + nodeRange.x - wallWidth - Math.random() * 30, o.y - blockSize, blockSize, blockSize, 0.4); + blockSize = 70 + Math.random() * 100; + spawn.bodyRect(o.x + wallWidth + Math.random() * 30, o.y - floorHeight - blockSize, blockSize, blockSize, 0.4); + + o.x += nodeRange.x; + if (Math.random() < 0.5) o.y += nodeRange.y; //start the next level at the top floor of the building + }, + function() { + //building with several floors that goes down a couple levels + const numberOfFloors = 2 + Math.floor(Math.random() * 2); + const floorHeight = maxJump - Math.floor(Math.random() * 150); //maxJump = 390 + let nodeRange = { x: 825 + Math.floor(Math.random() * 800), y: floorHeight * numberOfFloors }; + const wallWidth = 20 + Math.floor(Math.random() * 40); + const frontYardWidth = 250; + o.x += frontYardWidth; + spawn.mapRect(o.x, o.y - floorHeight * 2, nodeRange.x, wallWidth); //roof level 2 + if (Math.random() < 0.5) { + spawn.mapRect(o.x + 300, o.y - floorHeight, nodeRange.x - 600, wallWidth); //level 1 + } else if (Math.random() < 0.5) { + spawn.mapRect(o.x + 300, o.y - floorHeight, nodeRange.x - 600, wallWidth + floorHeight); //level 1 + } + spawn.mapRect(o.x - frontYardWidth, o.y, nodeRange.x + frontYardWidth - 300, wallWidth); //ground + // spawn.mapRect(o.x - frontYardWidth, o.y + 10, frontYardWidth, 100 - 10); // makes the front yard look 100 deep + let floorWidth = nodeRange.x - wallWidth - 250 - Math.random() * Math.random() * nodeRange.x * 0.4; //possible open area with only half long 2nd floor + spawn.mapRect(o.x + nodeRange.x - floorWidth, o.y + floorHeight, floorWidth, wallWidth); //B1 floor + spawn.mapRect(o.x, o.y + floorHeight * numberOfFloors, nodeRange.x, 100); //B2 floor + + spawn.mapRect(o.x, o.y - floorHeight * 2, wallWidth, floorHeight * 2 - 175); //left building wall above ground + spawn.mapRect(o.x, o.y + wallWidth, wallWidth, floorHeight * numberOfFloors); //left building wall lower + spawn.mapRect(o.x + nodeRange.x - wallWidth, o.y + wallWidth - floorHeight * 2, wallWidth, floorHeight * (numberOfFloors + 2) - wallWidth - 175); //right building wall with right door + + level.fill.push({ + x: o.x, + y: o.y - floorHeight * 2, + width: nodeRange.x, + height: floorHeight * (numberOfFloors + 2), + color: "rgba(0,0,0,0.1)" + }); + //random extras + spawn.debris(o.x, o.y - 50, nodeRange.x - 300, 1); //ground + spawn.debris(o.x, o.y + floorHeight * 2 - 50, nodeRange.x, 1); //B2 + spawn.randomSmallMob(o.x + wallWidth + Math.random() * nodeRange.x - 300, o.y - 50); //ground + if (numberOfFloors === 3) { + spawn.randomBoss(o.x + nodeRange.x / 2, o.y + floorHeight * 2 - 50); + } else { + spawn.randomSmallMob(o.x + wallWidth + Math.random() * nodeRange.x * 0.8, o.y + floorHeight - 50); //B1 + spawn.randomSmallMob(o.x + wallWidth + Math.random() * nodeRange.x * 0.8, o.y + floorHeight * numberOfFloors - 50); //B2 + } + + o.x += nodeRange.x; + o.y += nodeRange.y; //start the next level at the top floor of the building + }, + function() { + //large room with boost to roof exit + const wallWidth = 20 + Math.floor(Math.random() * 40); + const boostX = 500 + Math.floor(Math.random() * 1500); + const boostPadding = 35 + Math.floor(Math.random() * 200); + let nodeRange = { x: boostX + 500 + Math.floor(Math.random() * 500), y: 700 + Math.floor(Math.random() * 600) }; + //optional basement mode + if (Math.random() < 0.75 && boostX > 1000) { + spawn.mapRect(o.x, o.y, 200 + wallWidth, wallWidth); //ground entrance + const basementDepth = Math.min(400 + Math.floor(Math.random() * 300), nodeRange.y - 200); + o.y += basementDepth; + o.x += 200; + spawn.mapRect(o.x, o.y, nodeRange.x, 100); //basement ground + spawn.mapRect(o.x, o.y - nodeRange.y + 15, wallWidth, nodeRange.y - 200 - basementDepth); //left building wall + spawn.mapRect(o.x, o.y - basementDepth + 15, wallWidth, basementDepth); //left basement wall + } else { + spawn.mapRect(o.x, o.y, nodeRange.x + 200, 100); //ground + o.x += 200; + spawn.mapRect(o.x, o.y - nodeRange.y + 15, wallWidth, nodeRange.y - 200); //left building wall + } + spawn.mapRect(o.x + nodeRange.x - wallWidth, o.y - nodeRange.y + 15, wallWidth, nodeRange.y); //right building wall + spawn.mapRect(o.x, o.y - nodeRange.y, boostX - boostPadding, wallWidth); //left roof + spawn.mapRect(o.x + boostX + 100 + boostPadding, o.y - nodeRange.y, nodeRange.x - boostX - 100 - boostPadding, wallWidth); //right roof + + if (boostPadding < 100 && nodeRange.y < 1300) { + spawn.boost(o.x + boostX, o.y, nodeRange.y + 600); + spawn.bodyRect(o.x + boostX - boostPadding - 100, o.y - nodeRange.y - 20, 300 + 2 * boostPadding, 10, 1); + } else { + spawn.boost(o.x + boostX, o.y, nodeRange.y); + } + spawn.debris(o.x, o.y - nodeRange.y - 50, boostX - boostPadding, 1); //on roof + spawn.debris(o.x, o.y - 50, boostX - boostPadding, 1); //on ground + let blockSize = 60 + Math.random() * 150; + spawn.bodyRect(o.x + wallWidth + Math.random() * (boostX - blockSize - wallWidth), o.y - blockSize, blockSize, blockSize, 0.8); //left + spawn.bodyRect(o.x + nodeRange.x - blockSize - 10, o.y - blockSize, blockSize, blockSize, 0.8); //right + + const ledgeHeight = 500 + Math.random() * (nodeRange.y - 900) - 150; + if (Math.random() < 0.5) { + spawn.randomMob(o.x + 200, o.y - ledgeHeight, mobChance); //mob in left top corner + spawn.randomBoss(o.x + boostX + 100 + boostPadding + 100, o.y - nodeRange.y - 500, mobChance); //boss above right roof + } else { + spawn.randomBoss(o.x + 300, o.y - ledgeHeight); //mob in left top corner + spawn.randomMob(o.x + boostX + 100 + boostPadding + 100, o.y - ledgeHeight, mobChance); //mob above right ledge + } + + level.fill.push({ + x: o.x, + y: o.y - nodeRange.y, + width: nodeRange.x, + height: nodeRange.y, + color: "rgba(0,0,0,0.1)" + }); + o.x += nodeRange.x; + if (Math.random() < 0.5) o.y -= nodeRange.y; //start the next level at the top floor of the building or not + }, + function() { + //series of platforming ledges + //first platform to get up to top height + let wallWidth = 25 + Math.floor(Math.random() * 30); + const startingX = o.x; + const frontYard = 200; + let firstPlatW = 800 + Math.floor(Math.random() * 350); + o.x += frontYard; + //optional extra lower floor + let zeroFloor = 0; + if (Math.random() < 0.7) { + zeroFloor = maxJump - 100; + spawn.mapRect(o.x, o.y - zeroFloor, firstPlatW + 100, zeroFloor + 15); //0th floor + } + const firstFloorH = maxJump - Math.floor(Math.random() * 50) + zeroFloor; + const fullHeight = firstFloorH + 300 + 300; + + const totalCases = 4; + switch (Math.ceil(Math.random() * totalCases)) { + case 1: + spawn.mapRect(o.x, o.y - firstFloorH, firstPlatW, 100); //1st floor + spawn.mapRect(o.x, o.y - firstFloorH - 300, 100, 315); //1st floor left wall + spawn.mapRect(o.x + 300, o.y - fullHeight, firstPlatW - 400, 450); //2st floor + + level.fill.push({ + //darkness under first floor left building + x: o.x, + y: o.y - firstFloorH, + width: firstPlatW, + height: firstFloorH - zeroFloor, + color: "rgba(0,0,0,0.15)" + }); + level.fill.push({ + //darkness under second floor left building + x: o.x + 300, + y: o.y - firstFloorH - 300, + width: firstPlatW - 400, + height: firstFloorH - zeroFloor, + color: "rgba(0,0,0,0.2)" + }); + break; + case 2: + if (Math.random() < 0.1) { + spawn.mapRect(o.x + 200, o.y - firstFloorH, 200, firstFloorH + 15 - zeroFloor); + } else { + const size = 50 + Math.floor(Math.random() * 100); + for (let i = 0, len = Math.ceil(Math.random() * 8); i < len; ++i) { + spawn.bodyRect(o.x + 200, o.y - zeroFloor - size * (1 + i), size, size); + } + } + spawn.boost(o.x + firstPlatW - 100, o.y - zeroFloor, fullHeight); //-0.007 + break; + case 3: + spawn.mapRect(o.x + 200, o.y - firstFloorH, firstPlatW - 200, 100); //1st floor + spawn.mapRect(o.x + 400, o.y - firstFloorH - 300, firstPlatW - 400, 315); //1st floor left wall + spawn.mapRect(o.x + 600, o.y - fullHeight, firstPlatW - 600, 450); //2st floor + level.fill.push({ + //darkness under second floor left building + x: o.x + 200, + y: o.y - firstFloorH, + width: firstPlatW - 200, + height: firstFloorH - zeroFloor, + color: "rgba(0,0,0,0.1)" + }); + break; + case 4: + const poleWidth = 50; + const platWidth = 200; + const secondPlatX = platWidth + 125; + const totalWidth = platWidth + secondPlatX; + spawn.mapRect(o.x + firstPlatW - totalWidth, o.y - firstFloorH - 350, platWidth, wallWidth); + spawn.mapRect(o.x + firstPlatW - totalWidth, o.y - firstFloorH + 100, platWidth, wallWidth); + spawn.mapRect(o.x + secondPlatX + firstPlatW - totalWidth, o.y - firstFloorH - 100, platWidth, wallWidth); + spawn.mapRect(o.x + secondPlatX + firstPlatW - totalWidth, o.y - fullHeight, platWidth, wallWidth); + level.fillBG.push({ + x: o.x + platWidth / 2 - poleWidth / 2 + firstPlatW - totalWidth, + y: o.y - firstFloorH - 350, + width: poleWidth, + height: firstFloorH + 350 - zeroFloor, + color: "rgba(0,0,0,0.2)" + }); + level.fillBG.push({ + x: o.x + platWidth / 2 - poleWidth / 2 + secondPlatX + firstPlatW - totalWidth, + y: o.y - fullHeight, + width: poleWidth, + height: fullHeight - zeroFloor, + color: "rgba(0,0,0,0.2)" + }); + if (Math.random() < 0.9) { + const platX = firstPlatW - totalWidth - platWidth - 125; + spawn.mapRect(o.x + platX, o.y - firstFloorH - 100, platWidth, wallWidth); + spawn.mapRect(o.x + platX, o.y - fullHeight, platWidth, wallWidth); + + level.fillBG.push({ + x: o.x + platWidth / 2 - poleWidth / 2 + platX, + y: o.y - fullHeight, + width: poleWidth, + height: fullHeight - zeroFloor, + color: "rgba(0,0,0,0.2)" + }); + } + break; + } + spawn.debris(o.x, o.y - zeroFloor - 50, firstPlatW, 2); + spawn.randomMob(o.x + firstPlatW - 200 - Math.random() * 600, o.y - zeroFloor - 100, mobChance); //in shadow + spawn.randomMob(o.x + firstPlatW - 200 - Math.random() * 400, o.y - fullHeight - 100, mobChance); //top + spawn.randomBoss(o.x + firstPlatW - 200, o.y - fullHeight - 500, mobChance); //top + + //random platforms + let offX = o.x + firstPlatW; + const len = Math.ceil(Math.random() * Math.random() * 4.5); + for (let i = 0; i < len; i++) { + const totalCases = 3; + switch (Math.ceil(Math.random() * totalCases)) { + case 1: + const width = 150 + Math.floor(Math.random() * 500); + const middle = Math.floor(width / 2); + spawn.mapRect(offX + 300, o.y - fullHeight, width, wallWidth); //top platform + if (Math.random() < 0.5) spawn.mapRect(offX + 300, o.y - 50, width, 65); //ground bump + //optional room on second floor + if (width > 400) { + roomHeight = Math.min(maxJump, width) - 100; + roomLipWidth = 200; + spawn.mapRect(offX + 300 + width - wallWidth, o.y - fullHeight - roomHeight, wallWidth, roomHeight + 15); //room right wall + spawn.mapRect(offX + 300 + roomLipWidth, o.y - fullHeight - roomHeight, width - roomLipWidth, wallWidth); //room roof + level.fill.push({ + x: offX + 300 + roomLipWidth, + y: o.y - fullHeight - roomHeight + wallWidth, + width: width - roomLipWidth, + height: roomHeight - wallWidth, + color: "rgba(0,0,0,0.1)" + }); + } else if (Math.random() < 0.5) { + spawn.mapRect(offX + 300, o.y - firstFloorH, width, wallWidth); //middle platform + spawn.bodyRect(offX + 300 - Math.floor(Math.random() * 100), o.y - fullHeight - 20, width + Math.floor(Math.random() * 300), 20, 0.7); //plank on top platform) + } + level.fillBG.push({ + x: offX + 300 + middle - 25, + y: o.y - fullHeight, + width: 50, + height: fullHeight, + color: "rgba(0,0,0,0.2)" + }); + spawn.debris(offX + 300, o.y - 50, width, 1); + spawn.randomMob(offX + 300 + Math.random() * width, o.y - fullHeight - 100, 1); //top + offX += 300 + width; + break; + case 2: + const width2 = 500 + Math.floor(Math.random() * 400); + const forkDepth = 300; + const forkBaseHeight = fullHeight - forkDepth - (maxJump - 100) - 250; + spawn.mapRect(offX + 300, o.y - maxJump + 100, width2, maxJump - 100); //base + spawn.mapRect(offX + 300, o.y - fullHeight + forkDepth, width2, forkBaseHeight); //fork base + if (Math.random() < 0.7) spawn.mapRect(offX + 300, o.y - fullHeight, 100, forkDepth); //left fork + spawn.mapRect(offX + 300 + width2 - 100, o.y - fullHeight, 100, forkDepth); //right fork + + level.fill.push({ + x: offX + 300, + y: o.y - fullHeight + forkDepth + forkBaseHeight, + width: width2, + height: fullHeight - forkDepth - forkBaseHeight - maxJump + 100, + color: "rgba(0,0,0,0.1)" + }); + spawn.debris(offX + 300, o.y - maxJump - 100, width2, 1); + spawn.randomMob(offX + 450 + Math.random() * (width2 - 300), o.y - fullHeight + 200, 1); //top + offX += 300 + width2; + break; + case 3: + const width3 = 200 + Math.floor(Math.random() * 300); + if (Math.random() < 0.7) { + spawn.mapRect(offX + 300, o.y - fullHeight, width3, fullHeight - 150); //top platform + + level.fill.push({ + x: offX + 300, + y: o.y - 150, + width: width3, + height: 150, + color: "rgba(0,0,0,0.25)" + }); + } else { + //add a gap + const gap = (fullHeight - 150) / 2 + 100; + spawn.mapRect(offX + 300, o.y - fullHeight, width3, fullHeight - 150 - gap); //top platform + spawn.mapRect(offX + 300, o.y - fullHeight + gap, width3, fullHeight - 150 - gap); //top platform + level.fill.push({ + x: offX + 300, + y: o.y - 150, + width: width3, + height: 150, + color: "rgba(0,0,0,0.25)" + }); + level.fill.push({ + x: offX + 300, + y: o.y - 150 - gap, + width: width3, + height: 200, + color: "rgba(0,0,0,0.25)" + }); + } + spawn.randomMob(offX + 300 + Math.random() * width3, o.y - fullHeight - 100, mobChance); //top + spawn.debris(offX + 300, o.y - fullHeight - 100, width3, 1); + offX += 300 + width3; + break; + } + } + o.x += offX - o.x + 200; + spawn.mapRect(startingX, o.y, o.x - startingX + 15, 100); //ground + spawn.mapRect(o.x, o.y - fullHeight + 15, 100, fullHeight + 85); //right wall + o.y -= fullHeight; + }, + function() { + const width = 200 + Math.ceil(Math.random() * 200); + const height = (maxJump - 50) * (1 - Math.random() * 0.4); + const len = 2 + Math.ceil(Math.random() * 4); + const gapWidth = 150 + Math.ceil(Math.random() * 325); + const stairsUp = function() { + const x = o.x; + for (let i = 0; i < len; ++i) { + if (Math.random() < 0.4 && height > 200 && i !== 0) { + //hidden alcove + spawn.mapRect(o.x, o.y, width, 50); //ledge + spawn.mapRect(o.x + width - 50, o.y, (len - i - 1) * width + 50, height + 15); //back wall + level.fill.push({ + x: o.x, + y: o.y + 50, + width: width - 50, + height: height - 50, + color: "rgba(0,0,0,0.15)" + }); + spawn.randomMob(o.x + width - 100, o.y + height / 2 + 50); + } else { + spawn.mapRect(o.x, o.y, (len - i) * width, height + 15); //ledge + } + if (Math.random() < 0.5) spawn.debris(o.x, o.y - 50, width, 1); + o.x += width; + o.y -= height; + } + o.y += height; + }; + const stairsDown = function() { + const x = o.x; + for (let i = 0; i < len; ++i) { + if (Math.random() < 0.4 && height > 200 && i !== len - 1) { + //hidden alcove + spawn.mapRect(o.x, o.y, width, 50); //ledge + spawn.mapRect(x, o.y, -x + o.x + 50, height + 15); //back wall + level.fill.push({ + x: o.x + 50, + y: o.y + 50, + width: width - 50, + height: height - 50, + color: "rgba(0,0,0,0.15)" + }); + spawn.randomSmallMob(o.x + 100, o.y + height / 2 + 50); + } else { + spawn.mapRect(x, o.y, width - x + o.x, height + 15); //ledge + } + if (Math.random() < 0.5) spawn.debris(o.x, o.y - 50, width, 1); + o.x += width; + o.y += height; + } + o.y -= height; + }; + const spawnGapBoss = function() { + if (game.levelsCleared !== 0 || Math.random() < 0.3 + game.levelsCleared * 0.11) { + spawn.bodyRect(o.x - 50, o.y - 15, gapWidth + 100, 15); //plank over gap to catch boss + spawn.randomBoss(o.x + gapWidth / 2 - 50, o.y - 500); + } else if (game.levelsCleared < 1) { + spawn.bodyRect(o.x - 50, o.y - 15, gapWidth + 100, 15); //plank over gap + } + }; + if (Math.random() < 0.1) { + spawn.mapRect(o.x, o.y, len * width + 300, 100); //front porch + o.x += 300; + spawn.mapRect(o.x + len * width + gapWidth, o.y, len * width + 300, 100); //back porch + o.y -= height; + stairsUp(); + spawnGapBoss(); + o.x += gapWidth; + stairsDown(); + o.y += height; + o.x += 300; + } else { + spawn.mapRect(o.x, o.y, 300, 100); //front porch + o.x += 275; + stairsDown(); + spawnGapBoss(); + o.x += gapWidth; + stairsUp(); + } + o.x -= 15; + } + // function() { + // platform = function(x, y, width, height, extend = 0) { + // spawn.mapRect(x, y - height, width, 50); + // level.fillBG.push({ + // x: x + width / 2 - 25, + // y: y - height, + // width: 50, + // height: height + extend, + // color: "rgba(0,0,0,0.15)" + // }); + // spawn.debris(x, y - height - 50, width, Math.floor(Math.random() * 1.5)); + // spawn.randomMob(x + Math.random() * (width - 50) + 25, y - height - 50, mobChance); + // }; + // let nodeRange = { x: 1500 + Math.floor(Math.random() * 500), y: 0, down: false, up: false }; + // // const wallWidth = 20 + Math.floor(Math.random() * 40); + // //level 1 + // const ledge = { width: nodeRange.x / 2, height: Math.max((maxJump - 200) * Math.random() + 200, 200) }; + // if (Math.random() < 0.33) { + // //flat ground + // spawn.mapRect(o.x, o.y, nodeRange.x, 100); //ground + // ledge.height = 0; + // } else { + // if (Math.random() < 0.5) { + // //level down + // nodeRange.down = true; + // spawn.mapRect(o.x, o.y, ledge.width + 100 - 25, 100); //ground + // spawn.mapRect(o.x + ledge.width, o.y, 100, ledge.height + 100); //ledge wall + // o.y += ledge.height; + // spawn.mapRect(o.x + ledge.width, o.y, nodeRange.x - ledge.width, 100); //ground + // const wide = Math.min(250 + Math.random() * (ledge.width - 250), nodeRange.x - ledge.width - 350); + // platform(o.x + 250 + ledge.width, o.y, wide, ledge.height); + // } else { + // //level up + // nodeRange.down = false; + // spawn.mapRect(o.x, o.y, ledge.width + 100, 100); //ground + // spawn.mapRect(o.x + ledge.width, o.y - ledge.height, 100, ledge.height + 100); //ledge wall + // const wide = Math.min(250 + Math.random() * (ledge.width - 250), ledge.width - 250); + // platform(o.x + 150, o.y, wide, ledge.height); + // o.y -= ledge.height; + // spawn.mapRect(o.x + ledge.width + 25, o.y, nodeRange.x - ledge.width - 25, 100); //ground + // } + // } + + // // platform(x, o.y, width, maxJump * 2 - 100); + // o.x += nodeRange.x; + // } + // function() { + // platform = function(x, y, width, height) { + // spawn.mapRect(x, y - height, width, 50); + // level.fillBG.push({ + // x: x + width / 2 - 25, + // y: y - height, + // width: 50, + // height: height, + // color: "rgba(0,0,0,0.15)" + // }); + // spawn.debris(x, y - height - 50, width, Math.floor(Math.random() * 1.5)); + // spawn.randomMob(x + Math.random() * (width - 50) + 25, y - height - 50, mobChance); + // }; + // let nodeRange = { x: 1500 + Math.floor(Math.random() * 500), y: 0, down: false, up: false }; + // let level = maxJump - 100 * Math.random(); + + // // platform(x, o.y, width, maxJump * 2 - 100); + // o.x += nodeRange.x; + // } + ]; + // + // + //randomized zone spawns + const mapNodes = Math.min(4, 2 + game.levelsCleared); + // const mapNodes = 1; + for (let i = 0; i < mapNodes; ++i) { + // mapNode[1](); + mapNode[Math.floor(Math.random() * mapNode.length)](); //run a random mapNode + } + //ending zone + o.x += 200; + level.exit.x = o.x + 200; + level.exit.y = o.y - 35; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + spawn.mapRect(o.x + 200, o.y - 20, 100, 50); + spawn.mapRect(o.x + 450, o.y - 330, 50, 380); + spawn.mapRect(o.x - 200, o.y - 10, 700, 110); + spawn.mapRect(o.x, o.y - 325, 50, 100); + spawn.mapRect(o.x, o.y - 350, 500, 50); + // spawn.mapRect(o.x - 200, o.y, 300, 200); + // spawn.mapGunPowerUp(o.x - 50, o.y + 0); //spawns a gun on most early levels + // level.fillBG.push({ + // x: o.x, + // y: o.y, + // width: 450, + // height: -350, + // color: "#dff" + // }); + level.fill.push({ + x: o.x, + y: o.y, + width: 450, + height: -350, + color: "rgba(0, 255, 255, 0.15)" + }); + //set new fall height + game.fallHeight = o.y + 15000; + }, + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + enter: { + x: 0, + y: 0, + draw: function() { + ctx.beginPath(); + ctx.moveTo(this.x, this.y + 30); + ctx.lineTo(this.x, this.y - 80); + ctx.bezierCurveTo(this.x, this.y - 170, this.x + 100, this.y - 170, this.x + 100, this.y - 80); + ctx.lineTo(this.x + 100, this.y + 30); + ctx.lineTo(this.x, this.y + 30); + ctx.fillStyle = "#ccc"; + ctx.fill(); + } + }, + exit: { + x: 0, + y: 0, + draw: function() { + ctx.beginPath(); + ctx.moveTo(this.x, this.y + 30); + ctx.lineTo(this.x, this.y - 80); + ctx.bezierCurveTo(this.x, this.y - 170, this.x + 100, this.y - 170, this.x + 100, this.y - 80); + ctx.lineTo(this.x + 100, this.y + 30); + ctx.lineTo(this.x, this.y + 30); + ctx.fillStyle = "#0ff"; + ctx.fill(); + } + }, + fillBG: [], + drawFillBGs: function() { + for (let i = 0, len = level.fillBG.length; i < len; ++i) { + const f = level.fillBG[i]; + ctx.fillStyle = f.color; + ctx.fillRect(f.x, f.y, f.width, f.height); + } + }, + + fill: [], + drawFills: function() { + for (let i = 0, len = level.fill.length; i < len; ++i) { + const f = level.fill[i]; + ctx.fillStyle = f.color; + ctx.fillRect(f.x, f.y, f.width, f.height); + } + }, + zones: [], //zone do actions when player is in a region // to effect everything use a query + checkZones: function() { + for (let i = 0, len = this.zones.length; i < len; ++i) { + if ( + player.position.x > this.zones[i].x1 && + player.position.x < this.zones[i].x2 && + player.position.y > this.zones[i].y1 && + player.position.y < this.zones[i].y2 + ) { + this.zoneActions[this.zones[i].action](i); + break; + } + } + }, + addZone: function(x, y, width, height, action, info) { + this.zones[this.zones.length] = { + x1: x, + y1: y - 150, + x2: x + width, + y2: y + height - 70, //-70 to adjust for player height + action: action, + info: info + }; + }, + zoneActions: { + fling: function(i) { + Matter.Body.setVelocity(player, { + x: level.zones[i].info.Vx, + y: level.zones[i].info.Vy + }); + }, + nextLevel: function() { + //enter when player isn't falling + if (player.velocity.y < 0.1) { + game.dmgScale += 0.3; //damage done by mobs increases each level + b.dmgScale *= 0.92; //damage done by player decreases each level + game.levelsCleared++; + game.clearNow = true; //triggers in the physics engine to remove all physics bodies + } + }, + death: function() { + mech.death(); + }, + laser: function(i) { + //draw these in game with spawn.background + mech.damage(level.zones[i].info.dmg); + }, + slow: function() { + Matter.Body.setVelocity(player, { + x: player.velocity.x * 0.5, + y: player.velocity.y * 0.5 + }); + } + }, + queryList: [], //queries do actions on many objects in regions (for only player use a zone) + checkQuery: function() { + let bounds, action, info; + + function isInZone(targetArray) { + let results = Matter.Query.region(targetArray, bounds); + for (let i = 0, len = results.length; i < len; ++i) { + level.queryActions[action](results[i], info); + } + } + for (let i = 0, len = level.queryList.length; i < len; ++i) { + bounds = level.queryList[i].bounds; + action = level.queryList[i].action; + info = level.queryList[i].info; + for (let j = 0, l = level.queryList[i].groups.length; j < l; ++j) { + isInZone(level.queryList[i].groups[j]); + } + } + }, + //oddly query regions can't get smaller than 50 width? + addQueryRegion: function(x, y, width, height, action, groups = [[player], body, mob, powerUp, bullet], info) { + this.queryList[this.queryList.length] = { + bounds: { + min: { + x: x, + y: y + }, + max: { + x: x + width, + y: y + height + } + }, + action: action, + groups: groups, + info: info + }; + }, + queryActions: { + bounce: function(target, info) { + //jerky fling upwards + Matter.Body.setVelocity(target, { + x: info.Vx + (Math.random() - 0.5) * 6, + y: info.Vy + }); + target.torque = (Math.random() - 0.5) * 2 * target.mass; + }, + boost: function(target, info) { + if (target.velocity.y < 0) { + mech.buttonCD_jump = 0; //reset short jump counter to pre vent short jumps on boosts + Matter.Body.setVelocity(target, { + x: target.velocity.x, + y: info + }); + } + }, + force: function(target, info) { + if (target.velocity.y < 0) { + //gently force up if already on the way up + target.force.x += info.Vx * target.mass; + target.force.y += info.Vy * target.mass; + } else { + target.force.y -= 0.0007 * target.mass; //gently fall in on the way down + } + }, + antiGrav: function(target) { + target.force.y -= 0.0011 * target.mass; + }, + death: function(target) { + target.death(); + } + }, + levelAnnounce: function() { + // let text = "n-gon L" + (game.levelsCleared + 1) + " " + level.levels[level.onLevel]; + let text = "n-gon Level " + (game.levelsCleared + 1); + document.title = text; + // text = "Level " + (game.levelsCleared + 1) + ": " + spawn.pickList[0] + "s + " + spawn.pickList[1] + "s"; + // game.makeTextLog(text, 300); + + // text = text + " with population: "; + // for (let i = 0, len = spawn.pickList.length; i < len; ++i) { + // if (spawn.pickList[i] != spawn.pickList[i - 1]) { + // text += spawn.pickList[i] + ", "; + // } + // } + // this.speech(text); + // game.makeTextLog(text, 360); + }, + addToWorld: function(mapName) { + //needs to be run to put bodies into the world + for (let i = 0; i < body.length; i++) { + //body[i].collisionFilter.group = 0; + body[i].collisionFilter.category = 0x0000001; + body[i].collisionFilter.mask = 0x011111; + body[i].classType = "body"; + World.add(engine.world, body[i]); //add to world + } + for (let i = 0; i < map.length; i++) { + //map[i].collisionFilter.group = 0; + map[i].collisionFilter.category = 0x000001; + map[i].collisionFilter.mask = 0x111111; + Matter.Body.setStatic(map[i], true); //make static + World.add(engine.world, map[i]); //add to world + } + for (let i = 0; i < cons.length; i++) { + World.add(engine.world, cons[i]); + } + for (let i = 0; i < consBB.length; i++) { + World.add(engine.world, consBB[i]); + } + } +}; diff --git a/js/levelold.js b/js/levelold.js new file mode 100644 index 0000000..576addd --- /dev/null +++ b/js/levelold.js @@ -0,0 +1,833 @@ +//global game variables +let body = []; //non static bodies +let map = []; //all static bodies +let cons = []; //all constaints between a point and a body +let consBB = []; //all constaints between two bodies +//main object for spawning levels +const level = { + levels: ["towers", "skyscrapers", "rooftops", "warehouse", 'highrise'], // name of the level methods that the player runs through + onLevel: undefined, + start: function() { + // game.levelsCleared = 3; //for testing to simulate all possible mobs spawns + spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns + this[this.levels[this.onLevel]](); //spawn the level player is on, this cycles in a loop + //this.boss(); + //this.warehouse(); + //this.highrise(); + //this.towers(); + //this.skyscrapers(); + //this.rooftops(); + this.addToWorld(); //add map to world + this.levelAnnounce(); + }, + //****************************************************************************************************************** + //****************************************************************************************************************** + //empty map for testing mobs + boss: function() { + game.levelsCleared = 10; //for testing to simulate all possible mobs spawns + mech.setPosToSpawn(-75, -60); //normal spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + + level.exit.x = 3500; + level.exit.y = -870; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + // this.addZone(250, -1000, 500, 1500, "laser"); + spawn.debris(0, -900, 4500, 10); //20 debris per level + document.body.style.backgroundColor = "#eee"; + // document.body.style.backgroundColor = "#fafcff"; + // document.body.style.backgroundColor = "#bbb"; + // document.body.style.backgroundColor = "#eee4e4"; + // document.body.style.backgroundColor = "#dcdcde"; + // document.body.style.backgroundColor = "#e0e5e0"; + + // this.addQueryRegion(550, -25, 100, 50, "bounce", { Vx: 0, Vy: -25 }); + // level.fillBG.push({ x: 550, y: -25, width: 100, height: 50, color: "#ff0" }); + + spawn.mapRect(3500, -860, 100, 50); //ground bump wall + spawn.mapRect(-1200, 0, 2200, 300); //ground + spawn.mapVertex(1250, 0, "0 0 0 300 -500 600 -500 300"); + spawn.mapRect(1500, -300, 2000, 300); //upper ground + spawn.mapVertex(3750, 0, "0 600 0 300 -500 0 -500 300"); + spawn.mapRect(4000, 0, 1000, 300); //right lower ground + spawn.mapRect(2200, -600, 600, 50); //center platform + spawn.mapRect(1300, -850, 700, 50); //center platform + spawn.mapRect(3000, -850, 700, 50); //center platform + spawn.spawnBuilding(-200, -250, 275, 240, false, true, "left"); //far left; player spawns in side + //spawn.boost(350, 0, 0, -0.005); + powerUps.spawn(450, -125, "gun", false); + // powerUps.spawn(450, -125, "gun", false); + // powerUps.spawn(450, -125, "gun", false); + for (let i = 0; i < 5; i++) { + //powerUps.spawn(2500+i*15, -1000, "gun", false); + powerUps.spawn(2500+ i*20, -1300, "gun", false); + powerUps.spawn(2500 + i * 20, -1100, "ammo", false); + } + spawn.bodyRect(700, -50, 50, 50); + spawn.bodyRect(700, -100, 50, 50); + spawn.bodyRect(700, -150, 50, 50); + spawn.bodyRect(700, -200, 50, 50); + spawn.bodyRect(-100, -260, 250, 10); + + spawn.chaser(1240, -1100, 40); + + spawn.blackHoler(400, -1400); + spawn.shooter(1300, -1150, 20); + spawn.shooter(800, -1150, 50); + // spawn.shooter(400, -1150, 150); + spawn.lineBoss(900, -1470,'laserTracker',4); + // spawn.randomBoss(-100, -1470); + + }, + warehouse: function() { + // document.body.style.backgroundColor = (Math.random() < 0.5) ? "#aaa" : "#e3e3f0" + document.body.style.backgroundColor = "#bbb"; + mech.setPosToSpawn(25, -60); //normal spawn + //mech.setPosToSpawn(-2000, -1700); // left ledge spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = 425; + level.exit.y = -35; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + //level.addQueryRegion(-600, -250, 180, 420, "death", [[player]],{}); + + spawn.debris(-2250, 1330, 3000, 7); //20 debris per level + spawn.debris(-3000, -800, 3280, 7); //20 debris per level + spawn.debris(-1400, 410, 2300, 6); //20 debris per level + if (game.levelsCleared < 2) powerUps.spawn(-1250, 560, "gun", false); //starting gun + //foreground + // level.fill.push({ x: -3025, y: 50, width: 4125, height: 1350, color: "rgba(0,0,0,0.05)"}); + // level.fill.push({ x: -1800, y: -500, width: 1975, height: 550, color: "rgba(0,0,0,0.05)"}); + // level.fill.push({ x: -2600, y: -150, width: 700, height: 200, color: "rgba(0,0,0,0.05)"}); + //background + const BGColor = "#f3f3ea"; + level.fillBG.push({ x: -3025, y: 50, width: 4125, height: 1350, color: BGColor }); + level.fillBG.push({ x: -1800, y: -500, width: 1975, height: 555, color: BGColor }); + level.fillBG.push({ x: -2600, y: -150, width: 700, height: 205, color: BGColor }); + level.fillBG.push({ x: 300, y: -250, width: 350, height: 250, color: "#cff" }); + spawn.mapRect(-1500, 0, 2750, 100); + spawn.mapRect(175, -600, 125, 700); + spawn.mapRect(-1900, -600, 2200, 100); + spawn.mapRect(-1900, -600, 100, 1300); + //house + spawn.mapRect(-175, -550, 50, 400); + spawn.mapRect(-175, -15, 350, 50); + spawn.mapRect(-25, -25, 100, 50); + // spawn.mapRect(-175, -275, 350, 25); + // spawn.mapRect(-175, -250, 25, 75); + // spawn.bodyRect(-170, -175, 14, 160, 1, spawn.propsFriction); //door to starting room + //exit house + spawn.mapRect(300, -15, 350, 50); + spawn.mapRect(300, -275, 350, 25); + spawn.mapRect(625, -250, 25, 75); + spawn.mapRect(425, -25, 100, 25); + // spawn.mapRect(-1900, 600, 2700, 100); + spawn.mapRect(1100, 0, 150, 1500); + spawn.mapRect(-2850, 1400, 4100, 100); + spawn.mapRect(-2375, 875, 1775, 100); + spawn.mapRect(-1450, 950, 75, 346); + spawn.mapRect(-1433, 662, 41, 111); + spawn.bodyRect(-1418, 773, 11, 102, 1, spawn.propsFriction); //blocking path + spawn.mapRect(-2950, 1250, 175, 250); + spawn.mapRect(-3050, 1100, 150, 400); + spawn.mapRect(-3150, 50, 125, 1450); + spawn.mapRect(-2375, 600, 3175, 100); + spawn.mapRect(-2125, 300, 250, 325); + spawn.mapRect(-1950, -400, 100, 25); + spawn.mapRect(-3150, 50, 775, 100); + spawn.mapRect(-2600, -200, 775, 50); + spawn.bodyRect(-1350, -200, 200, 200, 1, spawn.propsSlide); //weight + spawn.bodyRect(-1800, 0, 300, 100, 1, spawn.propsHoist); //hoist + cons[cons.length] = Constraint.create({ + pointA: { + x: -1650, + y: -500 + }, + bodyB: body[body.length - 1], + stiffness: 0.0005, + length: 1 + }); + + spawn.bodyRect(400, 400, 200, 200, 1, spawn.propsSlide); //weight + spawn.bodyRect(800, 600, 300, 100, 1, spawn.propsHoist); //hoist + cons[cons.length] = Constraint.create({ + pointA: { + x: 950, + y: 100 + }, + bodyB: body[body.length - 1], + stiffness: 0.0005, + length: 1 + }); + + spawn.bodyRect(-2775, 1150, 190, 150, 1, spawn.propsSlide); //weight + spawn.bodyRect(-2575, 1150, 200, 150, 1, spawn.propsSlide); //weight + spawn.bodyRect(-2775, 1300, 400, 100, 1, spawn.propsHoist); //hoist + cons[cons.length] = Constraint.create({ + pointA: { + x: -2575, + y: 150 + }, + bodyB: body[body.length - 1], + stiffness: 0.0005, + length: 220 + }); + //blocks + //spawn.bodyRect(-155, -150, 10, 140, 1, spawn.propsFriction); + spawn.bodyRect(-165, -150, 30, 35, 1); + spawn.bodyRect(-165, -115, 30, 35, 1); + spawn.bodyRect(-165, -80, 30, 35, 1); + spawn.bodyRect(-165, -45, 30, 35, 1); + + spawn.bodyRect(-750, 400, 150, 150, 0.5); + spawn.bodyRect(-200, 1175, 250, 225, 1); //block to get to top path on bottom level + // spawn.bodyRect(-1450, 737, 75, 103, 0.5); //blocking path + + spawn.bodyRect(-2525, -50, 145, 100, 0.5); + spawn.bodyRect(-2325, -300, 150, 100, 0.5); + spawn.bodyRect(-1275, -750, 200, 150, 0.5); //roof block + spawn.bodyRect(-525, -700, 125, 100, 0.5); //roof block + + //mobs + spawn.randomSmallMob(-1125, 550); + spawn.randomSmallMob(-2325, 800); + spawn.randomSmallMob(-2950, -50); + spawn.randomSmallMob(825, 300); + spawn.randomSmallMob(-900, 825); + spawn.randomMob(-2025, 175, 0.7); + spawn.randomMob(-2325, 450, 0.7); + spawn.randomMob(-2925, 675, 0.7); + spawn.randomMob(-2700, 300, 0.25); + spawn.randomMob(-2500, 300, 0.25); + spawn.randomMob(-2075, -425, 0.25); + spawn.randomMob(-1550, -725, 0.25); + spawn.randomMob(375, 1100, 0.15); + spawn.randomMob(-1425, -100, 0.3); + spawn.randomMob(-800, -750, 0.2); + spawn.randomMob(400, -350, 0); + spawn.randomMob(650, 1300, 0.1); + spawn.randomMob(-750, -150, 0); + spawn.randomMob(475, 300, 0); + spawn.randomMob(-75, -700, 0); + spawn.randomMob(900, -200, -0.1); + spawn.randomBoss(-125, 275, -0.1); + spawn.randomBoss(-825, 1000, 0.3); + spawn.randomBoss(-1300, -1100, 0.1); + //spawn.randomBoss(600, -1575, 0); + //spawn.randomMob(1120, -1200, 0.3); + //spawn.randomSmallMob(2200, -1775); // + }, + highrise: function() { + document.body.style.backgroundColor = "#fafcff"; + mech.setPosToSpawn(0, -700); //normal spawn + //mech.setPosToSpawn(-2000, -1700); // left ledge spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = -4275; + level.exit.y = -2805; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + if (game.levelsCleared < 2) powerUps.spawn(-2550, -700, "gun", false); //starting gun + + // spawn.laserZone(-550, -350, 10, 400, 0.3) + // spawn.deathQuery(-550, -350, 50, 400) + + // spawn.debris(-3950, -2575, 1050, 4); //20 debris per level + spawn.debris(-2325, -1825, 2400, 10); //20 debris per level + spawn.debris(-2625, -700, 925, 10); //20 debris per level + // if (!game.levelsCleared) powerUps.spawn(2450, -1675, "gun", false); + //background + level.fillBG.push({ x: -4425, y: -3050, width: 425, height: 275, color: "#cff"}); + //foreground + level.fill.push({ x: -1650, y: -1575, width: 550, height: 425, color: "rgba(10,10,0,0.12)"}); + level.fill.push({ x: -2600, y: -2400, width: 450, height: 1800, color: "rgba(10,10,0,0.12)"}); + level.fill.push({ x: -3425, y: -2150, width: 525, height: 1550, color: "rgba(10,10,0,0.12)"}); + level.fill.push({ x: -1850, y: -1150, width: 2025, height: 1150, color: "rgba(10,10,0,0.12)"}); + + //building 1 + spawn.bodyRect(-1000, -675, 25, 25); + spawn.mapRect(-2225, 0, 2475, 150); + spawn.mapRect(175, -1000, 75, 1100); + + spawn.mapRect(-175, -985, 25, 175); + spawn.bodyRect(-170, -810, 14, 160, 1, spawn.propsFriction); //door to starting room + spawn.mapRect(-600, -650, 825, 50); + spawn.mapRect(-1300, -650, 500, 50); + spawn.mapRect(-175, -250, 425, 300); + spawn.bodyRect(-75, -300, 50, 50); + + spawn.boost(-750, 0, 0, -0.01); + spawn.bodyRect(-425, -1375, 400, 225); + spawn.mapRect(-1125, -1575, 50, 475); + spawn.bodyRect(-1475, -1275, 250, 125); + spawn.bodyRect(-825, -1160, 250, 10); + + spawn.mapRect(-1650, -1575, 400, 50); + spawn.mapRect(-600, -1150, 850, 175); + + spawn.mapRect(-1850, -1150, 1050, 175); + spawn.bodyRect(-1907, -1600, 550, 25); + spawn.bodyRect(-1400, -125, 125, 125); + spawn.bodyRect(-1100, -125, 150, 125); + spawn.bodyRect(-1360, -200, 75, 75); + spawn.bodyRect(-1200, -75, 75, 75); + + //building 2 + spawn.mapRect(-3450, -600, 1300, 750); + spawn.mapRect(-2225, -400, 175, 550); + spawn.boost(-2800, -600, 0, -0.005); + spawn.mapRect(-3450, -1325, 550, 50); + spawn.mapRect(-3425, -2200, 525, 50); + spawn.mapRect(-2600, -1750, 450, 50); + spawn.mapRect(-2600, -2450, 450, 50); + spawn.bodyRect(-2275, -2700, 50, 60); + spawn.bodyRect(-2600, -1975, 250, 225); + spawn.bodyRect(-3415, -1425, 100, 100); + spawn.bodyRect(-3400, -1525, 100, 100); + spawn.bodyRect(-3305, -1425, 100, 100); + + //building 3 + spawn.mapRect(-4450, -1750, 1025, 1900); + spawn.mapRect(-3750, -2000, 175, 275); + spawn.mapRect(-4000, -2425, 275, 675); + // spawn.mapRect(-4450, -2650, 475, 1000); + spawn.mapRect(-4450, -2775, 475, 1125); + spawn.bodyRect(-3715, -2050, 50, 50); + spawn.bodyRect(-3570, -1800, 50, 50); + spawn.bodyRect(-2970, -2250, 50, 50); + spawn.bodyRect(-3080, -2250, 40, 40); + spawn.bodyRect(-3420, -650, 50, 50); + + //exit + spawn.mapRect(-4450, -3075, 25, 300); + spawn.mapRect(-4450, -3075, 450, 25); + spawn.mapRect(-4025, -3075, 25, 100); + spawn.mapRect(-4275, -2785, 100, 25); + + //mobs + spawn.randomMob(-2500, -2700, 1); + spawn.randomMob(-3200, -750, 1); + spawn.randomMob(-1875, -775, 0.2); + spawn.randomMob(-950, -1675, 0.2); + spawn.randomMob(-1525, -1750, 0.2); + spawn.randomMob(-1375, -1400, 0.2); + spawn.randomMob(-1625, -1275, 0.2); + spawn.randomMob(-1900, -1250, 0.2); + spawn.randomMob(-2250, -1850, 0.2); + spawn.randomMob(-2475, -2200, 0.2); + spawn.randomMob(-3000, -1475, 0.2); + spawn.randomMob(-3850, -2500, 0.2); + spawn.randomMob(-3650, -2125, 0.2); + spawn.randomMob(-4010, -3200, 0.2); + spawn.randomMob(-3500, -1825, 0.2); + spawn.randomMob(-975, -100, 0); + spawn.randomMob(-1050, -725, 0.2); + spawn.randomMob(-1525, -100, 0); + spawn.randomMob(-525, -1700, -0.1); + spawn.randomMob(-125, -1500, -0.1); + spawn.randomMob(-325, -1900, -0.1); + spawn.randomMob(-550, -100, -0.1); + spawn.randomBoss(-3250, -2700, 0.2); + spawn.randomBoss(-2450, -1100, 0); + + }, + //****************************************************************************************************************** + //****************************************************************************************************************** + rooftops: function() { + document.body.style.backgroundColor = "#eee4e4"; + // this.addZone(-700, -50, 4100, 100, "death"); + mech.setPosToSpawn(-450, -2050); //normal spawn + //mech.setPosToSpawn(4600, -900); //normal spawn + //mech.setPosToSpawn(4400, -400); //normal spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = 3600; + level.exit.y = -300; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + spawn.debris(1650, -1800, 3800, 20); //20 debris per level + if (game.levelsCleared < 2) powerUps.spawn(2450, -1675, "gun", false); + + //foreground + level.fill.push({ x: -650, y: -2300, width: 450, height: 300, color: "rgba(0,0,0,0.15)" }); + level.fill.push({ x: 3450, y: -1250, width: 1100, height: 1250, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 4550, y: -725, width: 900, height: 725, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 3400, y: 100, width: 2150, height: 900, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: -700, y: -1900, width: 2100, height: 2900, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 1950, y: -1550, width: 1025, height: 550, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 1600, y: -900, width: 1600, height: 1900, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 3450, y: -1550, width: 350, height: 300, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 700, y: -2225, width: 700, height: 225, color: "rgba(0,0,0,0.1)" }); + + //spawn.mapRect(-700, 0, 6250, 100); //ground + spawn.mapRect(3400, 0, 2150, 100); //ground + spawn.mapRect(-700, -2000, 2100, 100); //Top left ledge + spawn.bodyRect(1350, -2125, 50, 125, 0.8); // + spawn.bodyRect(1350, -2225, 50, 100, 0.8); // + spawn.mapRect(-700, -2350, 50, 400); //far left starting left wall + spawn.mapRect(-700, -2010, 500, 50); //far left starting ground + spawn.mapRect(-700, -2350, 500, 50); //far left starting ceiling + spawn.mapRect(-250, -2350, 50, 200); //far left starting right part of wall + spawn.bodyRect(-240, -2150, 30, 36); //door to starting room + spawn.bodyRect(-240, -2115, 30, 36); //door to starting room + spawn.bodyRect(-240, -2080, 30, 35); //door to starting room + spawn.bodyRect(-240, -2045, 30, 35); //door to starting room + + spawn.bodyRect(200, -2150, 200, 220, 0.8); // + spawn.mapRect(700, -2275, 700, 50); // + spawn.bodyRect(1050, -2350, 30, 30, 0.8); // + spawn.boost(1800, -1000); + spawn.bodyRect(1625, -1100, 100, 75); // + spawn.bodyRect(1350, -1025, 400, 25); // + spawn.mapRect(-700, -1000, 2100, 100); //lower left ledge + spawn.bodyRect(350, -1100, 200, 100, 0.8); // + spawn.bodyRect(370, -1200, 100, 100, 0.8); // + spawn.bodyRect(360, -1300, 100, 100, 0.8); // + spawn.bodyRect(950, -1050, 300, 50, 0.8); // + spawn.bodyRect(-600, -1250, 400, 250, 0.8); // + spawn.mapRect(1600, -1000, 1600, 100); //middle ledge + spawn.bodyRect(2600, -1950, 100, 250, 0.8); // + spawn.bodyRect(2700, -1125, 125, 125, 0.8); // + spawn.bodyRect(2710, -1250, 125, 125, 0.8); // + spawn.bodyRect(2705, -1350, 75, 100, 0.8); // + spawn.mapRect(3450, -1600, 350, 50); // + spawn.mapRect(1950, -1600, 1025, 50); // + spawn.bodyRect(3100, -1015, 375, 15, 0.8); // + spawn.bodyRect(3500, -850, 75, 125, 0.8); // + spawn.mapRect(3400, -1000, 100, 1100); //left building wall + spawn.mapRect(5450, -775, 100, 875); //right building wall + spawn.bodyRect(4850, -750, 300, 25, 0.8); // + spawn.bodyRect(3925, -1400, 100, 150, 0.8); // + spawn.mapRect(3450, -1250, 1100, 50); // + spawn.mapRect(3450, -1225, 50, 75); // + spawn.mapRect(4500, -1225, 50, 350); // + spawn.mapRect(3450, -725, 1450, 50); // + spawn.mapRect(5100, -725, 400, 50); // + spawn.mapRect(4500, -700, 50, 600); // + spawn.bodyRect(4500, -100, 50, 100, 0.8); // + // spawn.boost(4950, 0, 0, -0.005); + + spawn.spawnStairs(3800, 0, 3, 150, 206); //stairs top exit + spawn.mapRect(3500, -275, 350, 275); //exit platform + spawn.mapRect(3600, -285, 100, 50); //ground bump wall + + spawn.randomSmallMob(2200, -1775); // + spawn.randomSmallMob(4000, -825); // + spawn.randomSmallMob(4100, -100); + spawn.randomSmallMob(4600, -100); + spawn.randomSmallMob(-350, -2400); // + spawn.randomMob(4250, -1350, 0.8); // + spawn.randomMob(2550, -1350, 0.8); // + spawn.randomMob(1225, -2400, 0.3); // + spawn.randomMob(1120, -1200, 0.3); + spawn.randomMob(3000, -1150, 0.2); // + spawn.randomMob(3200, -1150, 0.3); // + spawn.randomMob(3300, -1750, 0.3); // + spawn.randomMob(3650, -1350, 0.3); // + spawn.randomMob(3600, -1800, 0.1); // + spawn.randomMob(5200, -100, 0.3); + spawn.randomMob(5275, -900, 0.2); + spawn.randomMob(3765, -450, 0.3); // + spawn.randomMob(900, -2125, 0.3); // + spawn.randomBoss(600, -1575, 0); + spawn.randomBoss(2225, -1325, 0.4); // + spawn.randomBoss(4900, -1200, 0); // + //spawn.randomBoss(4850, -1250,0.7); + }, + //****************************************************************************************************************** + //****************************************************************************************************************** + towers: function() { + mech.setPosToSpawn(1375, -1550); //normal spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = 3250; + level.exit.y = -530; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + document.body.style.backgroundColor = "#e0e5e0"; + //foreground + level.fill.push({ x: -550, y: -1700, width: 1300, height: 1700, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 750, y: -1450, width: 650, height: 1450, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 750, y: -1950, width: 800, height: 450, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 3000, y: -1000, width: 650, height: 1000, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 3650, y: -1300, width: 1300, height: 1300, color: "rgba(0,0,0,0.1)" }); + //background + level.fillBG.push({ x: 2495, y: -500, width: 10, height: 525, color: "#ccc" }); + + //mech.setPosToSpawn(600, -1200); //normal spawn + //mech.setPosToSpawn(525, -150); //ground first building + //mech.setPosToSpawn(3150, -700); //near exit spawn + spawn.debris(-300, -200, 4800, 10); //ground debris //20 debris per level + spawn.debris(-300, -650, 4800, 10); //1st floor debris //20 debris per level + if (game.levelsCleared < 2) powerUps.spawn(525, -700, "gun", false); + + spawn.mapRect(-600, 25, 5600, 300); //ground + spawn.mapRect(-600, 0, 2000, 50); //ground + spawn.mapRect(-600, -1700, 50, 2000 - 100); //left wall + spawn.bodyRect(-295, -1540, 40, 40); //center block under wall + spawn.bodyRect(-298, -1580, 40, 40); //center block under wall + spawn.bodyRect(1500, -1540, 30, 30); //left of entrance + + spawn.mapRect(1550, -2000, 50, 550); //right wall + spawn.mapRect(1350, -2000 + 505, 50, 1295); //right wall + spawn.mapRect(-600, -2000 + 250, 2000 - 700, 50); //roof left + spawn.mapRect(-600 + 1300, -2000, 50, 300); //right roof wall + spawn.mapRect(-600 + 1300, -2000, 900, 50); //center wall + map[map.length] = Bodies.polygon(425, -1700, 0, 15); //circle above door + spawn.bodyRect(420, -1675, 15, 170, 1, spawn.propsDoor); // door + //makes door swing + consBB[consBB.length] = Constraint.create({ + bodyA: body[body.length - 1], + pointA: { + x: 0, + y: -90 + }, + bodyB: map[map.length - 1], + stiffness: 1 + }); + spawn.mapRect(-600 + 300, -2000 * 0.75, 1900, 50); //3rd floor + spawn.mapRect(-600 + 2000 * 0.7, -2000 * 0.74, 50, 375); //center wall + spawn.bodyRect(-600 + 2000 * 0.7, -2000 * 0.5 - 106, 50, 106); //center block under wall + spawn.mapRect(-600, -1000, 1100, 50); //2nd floor + spawn.mapRect(600, -1000, 500, 50); //2nd floor + spawn.spawnStairs(-600, -1000, 4, 250, 350); //stairs 2nd + spawn.mapRect(350, -600, 350, 150); //center table + spawn.mapRect(-600 + 300, -2000 * 0.25, 2000 - 300, 50); //1st floor + spawn.spawnStairs(-600 + 2000 - 50, -500, 4, 250, 350, true); //stairs 1st + spawn.spawnStairs(-600, 0, 4, 250, 350); //stairs ground + spawn.bodyRect(700, -200, 100, 100); //center block under wall + spawn.bodyRect(700, -300, 100, 100); //center block under wall + spawn.bodyRect(700, -400, 100, 100); //center block under wall + spawn.mapRect(1390, 13, 30, 20); //step left + spawn.mapRect(2980, 13, 30, 20); //step right + spawn.mapRect(3000, 0, 2000, 50); //ground + spawn.bodyRect(4250, -700, 50, 100); + spawn.bodyRect(3000, -200, 50, 200); //door + spawn.mapRect(3000, -1000, 50, 800); //left wall + spawn.mapRect(3000 + 2000 - 50, -1300, 50, 1100); //right wall + spawn.mapRect(4150, -600, 350, 150); //table + spawn.mapRect(3650, -1300, 50, 650); //exit wall + spawn.mapRect(3650, -1300, 1350, 50); //exit wall + spawn.mapRect(3000 + 250, -510, 100, 50); //ground bump wall + spawn.mapRect(3000, -2000 * 0.5, 700, 50); //exit roof + spawn.mapRect(3000, -2000 * 0.25, 2000 - 300, 50); //1st floor + spawn.spawnStairs(3000 + 2000 - 50, 0, 4, 250, 350, true); //stairs ground + //teatherball + spawn[spawn.pickList[0]](2850, -80, 40 + game.levelsCleared * 8); + cons[cons.length] = Constraint.create({ + pointA: { + x: 2500, + y: -500 + }, + bodyB: mob[mob.length - 1], + stiffness: 0.0004 + }); + spawn.randomSmallMob(3550, -550); + spawn.randomSmallMob(4575, -560, 1); + spawn.randomSmallMob(1315, -880, 1); + spawn.randomSmallMob(800, -600); + spawn.randomSmallMob(-100, -1600); + spawn.randomMob(4100, -225, 0.8); + spawn.randomMob(-250, -700, 0.8); + spawn.randomMob(4500, -225, 0.15); + spawn.randomMob(3250, -225, 0.15); + spawn.randomMob(-100, -225, 0.1); + spawn.randomMob(1150, -225, 0.15); + spawn.randomMob(2000, -225, 0.15); + spawn.randomMob(450, -225, 0.15); + spawn.randomMob(100, -1200, 1); + spawn.randomMob(950, -1150, -0.1); + spawn.randomBoss(1800, -800, 0.4); + spawn.randomBoss(4150, -1000, 0.6); + }, + //****************************************************************************************************************** + //****************************************************************************************************************** + skyscrapers: function() { + mech.setPosToSpawn(-50, -50); //normal spawn + //mech.setPosToSpawn(1550, -1200); //spawn left high + //mech.setPosToSpawn(1800, -2000); //spawn near exit + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = 1500; + level.exit.y = -1875; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + if (game.levelsCleared < 2) powerUps.spawn(1475, -1175, "gun", false); + spawn.debris(0, -2200, 4500, 20); //20 debris per level + document.body.style.backgroundColor = "#dcdcde"; + + //foreground + level.fill.push({ x: 2500, y: -1100, width: 450, height: 250, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 2400, y: -550, width: 600, height: 150, color: "rgba(0,0,0,0.1)" }); + level.fill.push({ x: 2550, y: -1650, width: 250, height: 200, color: "rgba(0,0,0,0.1)" }); + //level.fill.push({ x: 1350, y: -2100, width: 400, height: 250, color: "rgba(0,255,255,0.1)" }); + level.fill.push({ x: 700, y: -110, width: 400, height: 110, color: "rgba(0,0,0,0.2)" }); + level.fill.push({ x: 3600, y: -110, width: 400, height: 110, color: "rgba(0,0,0,0.2)" }); + level.fill.push({ x: -250, y: -300, width: 450, height: 300, color: "rgba(0,0,0,0.15)" }); + + //background + level.fillBG.push({ x: 1300, y: -1800, width: 750, height: 1800, color: "#d4d4d7" }); + level.fillBG.push({ x: 3350, y: -1325, width: 50, height: 1325, color: "#d4d4d7" }); + level.fillBG.push({ x: 1350, y: -2100, width: 400, height: 250, color: "#d4f4f4" }); + + spawn.mapRect(-300, 0, 5000, 300); //***********ground + spawn.mapRect(-300, -350, 50, 400); //far left starting left wall + spawn.mapRect(-300, -10, 500, 50); //far left starting ground + spawn.mapRect(-300, -350, 500, 50); //far left starting ceiling + spawn.mapRect(150, -350, 50, 200); //far left starting right part of wall + spawn.bodyRect(170, -130, 14, 140, 1, spawn.propsFriction); //door to starting room + spawn.boost(475, 0, 0.0005, -0.007); + spawn.mapRect(700, -1100, 400, 990); //far left building + spawn.mapRect(1600, -400, 1500, 500); //long center building + spawn.mapRect(1345, -1100, 250, 25); //left platform + spawn.mapRect(1755, -1100, 250, 25); //right platform + spawn.mapRect(1300, -1850, 750, 50); //left higher platform + spawn.mapRect(1300, -2150, 50, 350); //left higher platform left edge wall + spawn.mapRect(1300, -2150, 450, 50); //left higher platform roof + spawn.mapRect(1500, -1860, 100, 50); //ground bump wall + spawn.mapRect(2400, -850, 600, 300); //center floating large square + //spawn.bodyRect(2500, -1100, 25, 250); //wall before chasers + spawn.mapRect(2500, -1450, 450, 350); //higher center floating large square + spawn.mapRect(2500, -1700, 50, 300); //left wall on higher center floating large square + spawn.mapRect(2500, -1700, 300, 50); //roof on higher center floating large square + spawn.mapRect(3300, -850, 150, 25); //ledge by far right building + spawn.mapRect(3300, -1350, 150, 25); //higher ledge by far right building + spawn.mapRect(3600, -1100, 400, 990); //far right building + spawn.boost(4150, 0, -0.0005, -0.007); + + spawn.bodyRect(3200, -1375, 300, 25, 0.9); + spawn.bodyRect(1825, -1875, 400, 25, 0.9); + // spawn.bodyRect(1800, -575, 250, 150, 0.8); + spawn.bodyRect(1800, -600, 250, 200, 0.8); + spawn.bodyRect(2557, -450, 35, 55, 0.7); + spawn.bodyRect(2957, -450, 30, 15, 0.7); + spawn.bodyRect(2900, -450, 60, 45, 0.7); + spawn.bodyRect(1915, -1200, 60, 100, 0.8); + spawn.bodyRect(1925, -1300, 50, 100, 0.8); + if (Math.random() < 0.9) { + spawn.bodyRect(2300, -1720, 400, 20); + spawn.bodyRect(2590, -1780, 80, 80); + } + spawn.bodyRect(2925, -1100, 25, 250, 0.8); + spawn.bodyRect(3325, -1550, 50, 200, 0.3); + if (Math.random() < 0.8) { + spawn.bodyRect(1400, -75, 200, 75); //block to get up ledge from ground + spawn.bodyRect(1525, -125, 50, 50); //block to get up ledge from ground + } + spawn.bodyRect(1025, -1110, 400, 10, 0.9); //block on far left building + spawn.bodyRect(1550, -1110, 250, 10, 0.9); //block on far left building + + spawn.randomSmallMob(1300, -70); + spawn.randomSmallMob(3200, -100); + spawn.randomSmallMob(4450, -100); + spawn.randomSmallMob(2700, -475); + spawn.randomMob(2650, -975, 0.8); + spawn.randomMob(2650, -1550, 0.8); + spawn.randomMob(4150, -200, 0.15); + spawn.randomMob(1700, -1300, 0.2); + spawn.randomMob(1850, -1950, 0.25); + spawn.randomMob(2610, -1880, 0.25); + spawn.randomMob(3350, -950, 0.25); + spawn.randomMob(1690, -2250, 0.25); + spawn.randomMob(2200, -600, 0.2); + spawn.randomMob(900, -1300, 0.25); + spawn.randomMob(-100, -900, -0.2); + spawn.randomBoss(3700, -1500, 0.4); + spawn.randomBoss(1700, -900, 0.4); + }, + //***************************************************************************************************************** + //***************************************************************************************************************** + enter: { + x: 0, + y: 0, + draw: function() { + ctx.beginPath(); + ctx.moveTo(this.x, this.y + 30); + ctx.lineTo(this.x, this.y - 80); + ctx.bezierCurveTo(this.x, this.y - 170, this.x + 100, this.y - 170, this.x + 100, this.y - 80); + ctx.lineTo(this.x + 100, this.y + 30); + ctx.lineTo(this.x, this.y + 30); + ctx.fillStyle = "#ccc"; + ctx.fill(); + } + }, + exit: { + x: 0, + y: 0, + draw: function() { + ctx.beginPath(); + ctx.moveTo(this.x, this.y + 30); + ctx.lineTo(this.x, this.y - 80); + ctx.bezierCurveTo(this.x, this.y - 170, this.x + 100, this.y - 170, this.x + 100, this.y - 80); + ctx.lineTo(this.x + 100, this.y + 30); + ctx.lineTo(this.x, this.y + 30); + ctx.fillStyle = "#0ff"; + ctx.fill(); + } + }, + fillBG: [], + drawFillBGs: function() { + + for (let i = 0, len = level.fillBG.length; i < len; ++i) { + const f = level.fillBG[i]; + ctx.fillStyle = f.color; + ctx.fillRect(f.x, f.y, f.width, f.height); + } + + }, + + fill: [], + drawFills: function() { + for (let i = 0, len = level.fill.length; i < len; ++i) { + const f = level.fill[i]; + ctx.fillStyle = f.color; + ctx.fillRect(f.x, f.y, f.width, f.height); + } + }, + zones: [], //zone do actions when player is in a region // to effect everything use a query + checkZones: function() { + for (let i = 0, len = this.zones.length; i < len; ++i) { + if ( + player.position.x > this.zones[i].x1 && + player.position.x < this.zones[i].x2 && + player.position.y > this.zones[i].y1 && + player.position.y < this.zones[i].y2 + ) { + this.zoneActions[this.zones[i].action](i); + break; + } + } + }, + addZone: function(x, y, width, height, action, info) { + this.zones[this.zones.length] = { + x1: x, + y1: y - 150, + x2: x + width, + y2: y + height - 70, //-70 to adjust for player height + action: action, + info: info + }; + }, + zoneActions: { + fling: function(i) { + Matter.Body.setVelocity(player, { + x: level.zones[i].info.Vx, + y: level.zones[i].info.Vy + }); + }, + nextLevel: function() { + level.onLevel++; + if (level.onLevel > level.levels.length - 1) level.onLevel = 0; + game.dmgScale += 0.2; //damage done by mobs increases each level + b.dmgScale *= 0.85; //damage done by player decreases each level + game.levelsCleared++; + game.clearNow = true; + }, + death: function() { + mech.death(); + }, + laser: function(i) { + //draw these in game with spawn.background + mech.damage(level.zones[i].info.dmg); + }, + slow: function() { + Matter.Body.setVelocity(player, { + //reduce player velocity every cycle until not true + x: player.velocity.x * 0.5, + y: player.velocity.y * 0.5 + }); + } + }, + queryList: [], //queries do actions on many objects in regions + checkQuery: function() { + let bounds, action, info; + function isInZone(targetArray) { + let results = Matter.Query.region(targetArray, bounds); + for (let i = 0, len = results.length; i < len; ++i) { + level.queryActions[action](results[i], info); + } + } + for (let i = 0, len = level.queryList.length; i < len; ++i) { + bounds = level.queryList[i].bounds; + action = level.queryList[i].action; + info = level.queryList[i].info; + for (let j = 0, l = level.queryList[i].groups.length; j < l; ++j) { + isInZone(level.queryList[i].groups[j]); + } + } + }, + //oddly query regions can't get smaller than 50 width? + addQueryRegion: function(x, y, width, height, action, groups = [[player], body, mob, powerUp, bullet], info) { + this.queryList[this.queryList.length] = { + bounds: { + min: { + x: x, + y: y + }, + max: { + x: x + width, + y: y + height + } + }, + action: action, + groups: groups, + info: info + }; + }, + queryActions: { + bounce: function(target, info) { + //jerky fling upwards + Matter.Body.setVelocity(target, { x: info.Vx + (Math.random() - 0.5) * 6, y: info.Vy }); + target.torque = (Math.random() - 0.5) * 2 * target.mass; + }, + force: function(target, info) { + if (target.velocity.y < 0) { + //gently force up if already on the way up + target.force.x += info.Vx * target.mass; + target.force.y += info.Vy * target.mass; + } else { + target.force.y -= 0.0007 * target.mass; //gently fall in on the way down + } + }, + antiGrav: function(target) { + target.force.y -= 0.0011 * target.mass; + }, + death: function(target){ + target.death() + } + }, + levelAnnounce: function() { + let text = "level " + (game.levelsCleared + 1) + " " + level.levels[level.onLevel]; + document.title = text; + // text = text + " with population: "; + // for (let i = 0, len = spawn.pickList.length; i < len; ++i) { + // if (spawn.pickList[i] != spawn.pickList[i - 1]) { + // text += spawn.pickList[i] + ", "; + // } + // } + // this.speech(text); + // game.makeTextLog(text, 360); + }, + addToWorld: function(mapName) { + //needs to be run to put bodies into the world + for (let i = 0; i < body.length; i++) { + //body[i].collisionFilter.group = 0; + body[i].collisionFilter.category = 0x0000001; + body[i].collisionFilter.mask = 0x111101; + body[i].classType = "body"; + World.add(engine.world, body[i]); //add to world + } + for (let i = 0; i < map.length; i++) { + //map[i].collisionFilter.group = 0; + map[i].collisionFilter.category = 0x000001; + map[i].collisionFilter.mask = 0x111111; + Matter.Body.setStatic(map[i], true); //make static + World.add(engine.world, map[i]); //add to world + } + for (let i = 0; i < cons.length; i++) { + World.add(engine.world, cons[i]); + } + for (let i = 0; i < consBB.length; i++) { + World.add(engine.world, consBB[i]); + } + } +}; diff --git a/js/lib/matter 0.12.min.js b/js/lib/matter 0.12.min.js new file mode 100644 index 0000000..46eafe8 --- /dev/null +++ b/js/lib/matter 0.12.min.js @@ -0,0 +1,89 @@ +/** +* matter-js 0.12.0 by @liabru 2017-02-02 +* http://brm.io/matter-js/ +* License MIT +*/ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Matter=e()}}(function(){return function e(t,n,o){function i(s,a){if(!n[s]){if(!t[s]){var l="function"==typeof require&&require;if(!a&&l)return l(s,!0);if(r)return r(s,!0);var c=new Error("Cannot find module '"+s+"'");throw c.code="MODULE_NOT_FOUND",c}var u=n[s]={exports:{}};t[s][0].call(u.exports,function(e){var n=t[s][1][e];return i(n?n:e)},u,u.exports,e,t,n,o)}return n[s].exports}for(var r="function"==typeof require&&require,s=0;s0&&r.rotateAbout(s.position,n,e.position,s.position)}},o.setVelocity=function(e,t){e.positionPrev.x=e.position.x-t.x,e.positionPrev.y=e.position.y-t.y,e.velocity.x=t.x,e.velocity.y=t.y,e.speed=r.magnitude(e.velocity)},o.setAngularVelocity=function(e,t){e.anglePrev=e.angle-t,e.angularVelocity=t,e.angularSpeed=Math.abs(e.angularVelocity)},o.translate=function(e,t){ +o.setPosition(e,r.add(e.position,t))},o.rotate=function(e,t){o.setAngle(e,e.angle+t)},o.scale=function(e,n,r,s){for(var a=0;a0&&(f.position.x+=e.velocity.x,f.position.y+=e.velocity.y),0!==e.angularVelocity&&(i.rotate(f.vertices,e.angularVelocity,e.position),c.rotate(f.axes,e.angularVelocity),p>0&&r.rotateAbout(f.position,e.angularVelocity,e.position,f.position)),l.update(f.bounds,f.vertices,e.velocity)}},o.applyForce=function(e,t,n){e.force.x+=n.x,e.force.y+=n.y;var o={x:t.x-e.position.x,y:t.y-e.position.y};e.torque+=o.x*n.y-o.y*n.x};var t=function(e){for(var t={mass:0,area:0,inertia:0,centre:{x:0,y:0}},n=1===e.parts.length?0:1;n1?1:0;d1?1:0;f0:0!==(e.mask&t.category)&&0!==(t.mask&e.category)}}()},{"../geometry/Bounds":26,"./Pair":7,"./SAT":11}],6:[function(e,t,n){var o={};t.exports=o;var i=e("./Pair"),r=e("./Detector"),s=e("../core/Common");!function(){o.create=function(e){var t={controller:o,detector:r.collisions,buckets:{},pairs:{},pairsList:[],bucketWidth:48,bucketHeight:48};return s.extend(t,e)},o.update=function(n,o,i,r){var s,p,f,m,v,y=i.world,g=n.buckets,x=!1; +for(s=0;sy.bounds.max.x||h.bounds.max.yy.bounds.max.y)){var b=t(n,h);if(!h.region||b.id!==h.region.id||r){h.region&&!r||(h.region=b);var w=e(b,h.region);for(p=w.startCol;p<=w.endCol;p++)for(f=w.startRow;f<=w.endRow;f++){v=a(p,f),m=g[v];var S=p>=b.startCol&&p<=b.endCol&&f>=b.startRow&&f<=b.endRow,C=p>=h.region.startCol&&p<=h.region.endCol&&f>=h.region.startRow&&f<=h.region.endRow;!S&&C&&C&&m&&u(n,m,h),(h.region===b||S&&!C||r)&&(m||(m=l(g,v)),c(n,m,h))}h.region=b,x=!0}}}x&&(n.pairsList=d(n))},o.clear=function(e){e.buckets={},e.pairs={},e.pairsList=[]};var e=function(e,t){var o=Math.min(e.startCol,t.startCol),i=Math.max(e.endCol,t.endCol),r=Math.min(e.startRow,t.startRow),s=Math.max(e.endRow,t.endRow);return n(o,i,r,s)},t=function(e,t){var o=t.bounds,i=Math.floor(o.min.x/e.bucketWidth),r=Math.floor(o.max.x/e.bucketWidth),s=Math.floor(o.min.y/e.bucketHeight),a=Math.floor(o.max.y/e.bucketHeight); +return n(i,r,s,a)},n=function(e,t,n,o){return{id:e+","+t+","+n+","+o,startCol:e,endCol:t,startRow:n,endRow:o}},a=function(e,t){return"C"+e+"R"+t},l=function(e,t){var n=e[t]=[];return n},c=function(e,t,n){for(var o=0;o0?o.push(n):delete e.pairs[t[i]];return o}}()},{"../core/Common":14,"./Detector":5,"./Pair":7}],7:[function(e,t,n){var o={};t.exports=o;var i=e("./Contact");!function(){o.create=function(e,t){var n=e.bodyA,i=e.bodyB,r=e.parentA,s=e.parentB,a={id:o.id(n,i),bodyA:n,bodyB:i,contacts:{},activeContacts:[],separation:0,isActive:!0,isSensor:n.isSensor||i.isSensor,timeCreated:t,timeUpdated:t,inverseMass:r.inverseMass+s.inverseMass,friction:Math.min(r.friction,s.friction), +frictionStatic:Math.max(r.frictionStatic,s.frictionStatic),restitution:Math.max(r.restitution,s.restitution),slop:Math.max(r.slop,s.slop)};return o.update(a,e,t),a},o.update=function(e,t,n){var r=e.contacts,s=t.supports,a=e.activeContacts,l=t.parentA,c=t.parentB;if(e.collision=t,e.inverseMass=l.inverseMass+c.inverseMass,e.friction=Math.min(l.friction,c.friction),e.frictionStatic=Math.max(l.frictionStatic,c.frictionStatic),e.restitution=Math.max(l.restitution,c.restitution),e.slop=Math.max(l.slop,c.slop),a.length=0,t.collided){for(var u=0;ue&&c.push(s);for(s=0;sf.friction*f.frictionStatic*E*n&&(O=V,F=s.clamp(f.friction*R*n,-O,O));var L=r.cross(A,g),W=r.cross(P,g),q=b/(v.inverseMass+y.inverseMass+v.inverseInertia*L*L+y.inverseInertia*W*W);if(_*=q,F*=q,I<0&&I*I>o._restingThresh*n)S.normalImpulse=0;else{var N=S.normalImpulse;S.normalImpulse=Math.min(S.normalImpulse+_,0),_=S.normalImpulse-N}if(T*T>o._restingThreshTangent*n)S.tangentImpulse=0;else{var D=S.tangentImpulse;S.tangentImpulse=s.clamp(S.tangentImpulse+F,-O,O),F=S.tangentImpulse-D}i.x=g.x*_+x.x*F,i.y=g.y*_+x.y*F,v.isStatic||v.isSleeping||(v.positionPrev.x+=i.x*v.inverseMass,v.positionPrev.y+=i.y*v.inverseMass, +v.anglePrev+=r.cross(A,i)*v.inverseInertia),y.isStatic||y.isSleeping||(y.positionPrev.x-=i.x*y.inverseMass,y.positionPrev.y-=i.y*y.inverseMass,y.anglePrev-=r.cross(P,i)*y.inverseInertia)}}}}}()},{"../core/Common":14,"../geometry/Bounds":26,"../geometry/Vector":28,"../geometry/Vertices":29}],11:[function(e,t,n){var o={};t.exports=o;var i=e("../geometry/Vertices"),r=e("../geometry/Vector");!function(){o.collides=function(t,o,s){var a,l,c,u,d=!1;if(s){var p=t.parent,f=o.parent,m=p.speed*p.speed+p.angularSpeed*p.angularSpeed+f.speed*f.speed+f.angularSpeed*f.angularSpeed;d=s&&s.collided&&m<.2,u=s}else u={collided:!1,bodyA:t,bodyB:o};if(s&&d){var v=u.axisBody,y=v===t?o:t,g=[v.axes[s.axisNumber]];if(c=e(v.vertices,y.vertices,g),u.reused=!0,c.overlap<=0)return u.collided=!1,u}else{if(a=e(t.vertices,o.vertices,t.axes),a.overlap<=0)return u.collided=!1,u;if(l=e(o.vertices,t.vertices,o.axes),l.overlap<=0)return u.collided=!1,u;a.overlapi?i=a:a=0?s.index-1:u.length-1;i=u[f],c.x=i.x-d.x,c.y=i.y-d.y,l=-r.dot(n,c),a=i;var m=(s.index+1)%u.length;return i=u[m],c.x=i.x-d.x,c.y=i.y-d.y,o=-r.dot(n,c),o0&&(P=0);var B,M={x:m.x*P,y:m.y*P};i&&!i.isStatic&&(B=r.cross(x,M)*i.inverseInertia*(1-n.angularStiffness),i.constraintImpulse.x-=v.x,i.constraintImpulse.y-=v.y,i.constraintImpulse.angle+=B,i.position.x-=v.x,i.position.y-=v.y,i.angle+=B),s&&!s.isStatic&&(B=r.cross(h,M)*s.inverseInertia*(1-n.angularStiffness),s.constraintImpulse.x+=v.x,s.constraintImpulse.y+=v.y,s.constraintImpulse.angle-=B,s.position.x+=v.x,s.position.y+=v.y,s.angle-=B)}}},o.postSolveAll=function(e){for(var t=0;t0&&(u.position.x+=o.x,u.position.y+=o.y),0!==o.angle&&(i.rotate(u.vertices,o.angle,n.position),l.rotate(u.axes,o.angle),c>0&&r.rotateAbout(u.position,o.angle,n.position,u.position)),a.update(u.bounds,u.vertices,n.velocity)}o.angle=0,o.x=0,o.y=0}}}}()},{"../core/Common":14,"../core/Sleeping":22,"../geometry/Axes":25,"../geometry/Bounds":26,"../geometry/Vector":28,"../geometry/Vertices":29}],13:[function(e,t,n){var o={};t.exports=o;var i=e("../geometry/Vertices"),r=e("../core/Sleeping"),s=e("../core/Mouse"),a=e("../core/Events"),l=e("../collision/Detector"),c=e("./Constraint"),u=e("../body/Composite"),d=e("../core/Common"),p=e("../geometry/Bounds");!function(){o.create=function(t,n){var i=(t?t.mouse:null)||(n?n.mouse:null);i||(t&&t.render&&t.render.canvas?i=s.create(t.render.canvas):n&&n.element?i=s.create(n.element):(i=s.create(),d.warn("MouseConstraint.create: options.mouse was undefined, options.element was undefined, may not function as expected"))); +var r=c.create({label:"Mouse Constraint",pointA:i.position,pointB:{x:0,y:0},length:.01,stiffness:.1,angularStiffness:1,render:{strokeStyle:"#90EE90",lineWidth:3}}),l={type:"mouseConstraint",mouse:i,element:null,body:null,constraint:r,collisionFilter:{category:1,mask:4294967295,group:0}},p=d.extend(l,n);return a.on(t,"beforeUpdate",function(){var n=u.allBodies(t.world);o.update(p,n),e(p)}),p},o.update=function(e,t){var n=e.mouse,o=e.constraint,s=e.body;if(0===n.button){if(o.bodyB)r.set(o.bodyB,!1),o.pointA=n.position;else for(var c=0;c1?1:0;u>16)+o,r=(n>>8&255)+o,s=(255&n)+o;return"#"+(16777216+65536*(i<255?i<1?0:i:255)+256*(r<255?r<1?0:r:255)+(s<255?s<1?0:s:255)).toString(16).slice(1)},o.shuffle=function(e){for(var t=e.length-1;t>0;t--){var n=Math.floor(o.random()*(t+1)),i=e[t];e[t]=e[n],e[n]=i}return e},o.choose=function(e){return e[Math.floor(o.random()*e.length)]},o.isElement=function(e){try{return e instanceof HTMLElement}catch(t){return"object"==typeof e&&1===e.nodeType&&"object"==typeof e.style&&"object"==typeof e.ownerDocument}},o.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)},o.isFunction=function(e){return"function"==typeof e},o.isPlainObject=function(e){return"object"==typeof e&&e.constructor===Object; +},o.isString=function(e){return"[object String]"===toString.call(e)},o.clamp=function(e,t,n){return en?n:e},o.sign=function(e){return e<0?-1:1},o.now=function(){var e=window.performance||{};return e.now=function(){return e.now||e.webkitNow||e.msNow||e.oNow||e.mozNow||function(){return+new Date}}(),e.now()},o.random=function(t,n){return t="undefined"!=typeof t?t:0,n="undefined"!=typeof n?n:1,t+e()*(n-t)};var e=function(){return o._seed=(9301*o._seed+49297)%233280,o._seed/233280};o.colorToNumber=function(e){return e=e.replace("#",""),3==e.length&&(e=e.charAt(0)+e.charAt(0)+e.charAt(1)+e.charAt(1)+e.charAt(2)+e.charAt(2)),parseInt(e,16)},o.logLevel=1,o.log=function(){console&&o.logLevel>0&&o.logLevel<=3&&console.log.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},o.info=function(){console&&o.logLevel>0&&o.logLevel<=2&&console.info.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},o.warn=function(){console&&o.logLevel>0&&o.logLevel<=3&&console.warn.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments))); +},o.nextId=function(){return o._nextId++},o.indexOf=function(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0;n0&&u.trigger(o,"collisionStart",{pairs:w.collisionStart}),s.preSolvePosition(w.list),c=0;c0&&u.trigger(o,"collisionActive",{ +pairs:w.collisionActive}),w.collisionEnd.length>0&&u.trigger(o,"collisionEnd",{pairs:w.collisionEnd}),e(x),u.trigger(o,"afterUpdate",g),o},o.merge=function(e,t){if(f.extend(e,t),t.world){e.world=t.world,o.clear(e);for(var n=d.allBodies(e.world),i=0;ir?(i.warn("Plugin.register:",o.toString(t),"was upgraded to",o.toString(e)),o._registry[e.name]=e):n-1},o.isFor=function(e,t){var n=e.for&&o.dependencyParse(e.for);return!e.for||t.name===n.name&&o.versionSatisfies(t.version,n.range)},o.use=function(e,t){if(e.uses=(e.uses||[]).concat(t||[]),0===e.uses.length)return void i.warn("Plugin.use:",o.toString(e),"does not specify any dependencies to install.");for(var n=o.dependencies(e),r=i.topologicalSort(n),s=[],a=0;a0&&i.info(s.join(" "))},o.dependencies=function(e,t){var n=o.dependencyParse(e),r=n.name;if(t=t||{},!(r in t)){e=o.resolve(e)||e,t[r]=i.map(e.uses||[],function(t){ +o.isPlugin(t)&&o.register(t);var r=o.dependencyParse(t),s=o.resolve(t);return s&&!o.versionSatisfies(s.version,r.range)?(i.warn("Plugin.dependencies:",o.toString(s),"does not satisfy",o.toString(r),"used by",o.toString(n)+"."),s._warned=!0,e._warned=!0):s||(i.warn("Plugin.dependencies:",o.toString(t),"used by",o.toString(n),"could not be resolved."),e._warned=!0),r.name});for(var s=0;s=i[2];if("^"===n.operator)return i[0]>0?s[0]===i[0]&&r.number>=n.number:i[1]>0?s[1]===i[1]&&s[2]>=i[2]:s[2]===i[2]}return e===t||"*"===e}}()},{"./Common":14}],21:[function(e,t,n){var o={};t.exports=o;var i=e("./Events"),r=e("./Engine"),s=e("./Common");!function(){var e,t;if("undefined"!=typeof window&&(e=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame,t=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame),!e){var n;e=function(e){n=setTimeout(function(){e(s.now())},1e3/60)},t=function(){clearTimeout(n)}}o.create=function(e){ +var t={fps:60,correction:1,deltaSampleSize:60,counterTimestamp:0,frameCounter:0,deltaHistory:[],timePrev:null,timeScalePrev:1,frameRequestId:null,isFixed:!1,enabled:!0},n=s.extend(t,e);return n.delta=n.delta||1e3/n.fps,n.deltaMin=n.deltaMin||1e3/n.fps,n.deltaMax=n.deltaMax||1e3/(.5*n.fps),n.fps=1e3/n.delta,n},o.run=function(t,n){return"undefined"!=typeof t.positionIterations&&(n=t,t=o.create()),function i(r){t.frameRequestId=e(i),r&&t.enabled&&o.tick(t,n,r)}(),t},o.tick=function(e,t,n){var o,s=t.timing,a=1,l={timestamp:s.timestamp};i.trigger(e,"beforeTick",l),i.trigger(t,"beforeTick",l),e.isFixed?o=e.delta:(o=n-e.timePrev||e.delta,e.timePrev=n,e.deltaHistory.push(o),e.deltaHistory=e.deltaHistory.slice(-e.deltaSampleSize),o=Math.min.apply(null,e.deltaHistory),o=oe.deltaMax?e.deltaMax:o,a=o/e.delta,e.delta=o),0!==e.timeScalePrev&&(a*=s.timeScale/e.timeScalePrev),0===s.timeScale&&(a=0),e.timeScalePrev=s.timeScale,e.correction=a,e.frameCounter+=1,n-e.counterTimestamp>=1e3&&(e.fps=e.frameCounter*((n-e.counterTimestamp)/1e3), +e.counterTimestamp=n,e.frameCounter=0),i.trigger(e,"tick",l),i.trigger(t,"tick",l),t.world.isModified&&t.render&&t.render.controller&&t.render.controller.clear&&t.render.controller.clear(t.render),i.trigger(e,"beforeUpdate",l),r.update(t,o,a),i.trigger(e,"afterUpdate",l),t.render&&t.render.controller&&(i.trigger(e,"beforeRender",l),i.trigger(t,"beforeRender",l),t.render.controller.world(t.render),i.trigger(e,"afterRender",l),i.trigger(t,"afterRender",l)),i.trigger(e,"afterTick",l),i.trigger(t,"afterTick",l)},o.stop=function(e){t(e.frameRequestId)},o.start=function(e,t){o.run(e,t)}}()},{"./Common":14,"./Engine":15,"./Events":16}],22:[function(e,t,n){var o={};t.exports=o;var i=e("./Events");!function(){o._motionWakeThreshold=.18,o._motionSleepThreshold=.08,o._minBias=.9,o.update=function(e,t){for(var n=t*t*t,i=0;i0&&r.motion=r.sleepThreshold&&o.set(r,!0)):r.sleepCounter>0&&(r.sleepCounter-=1)}else o.set(r,!1)}},o.afterCollisions=function(e,t){for(var n=t*t*t,i=0;io._motionWakeThreshold*n&&o.set(c,!1)}}}},o.set=function(e,t){var n=e.isSleeping;t?(e.isSleeping=!0,e.sleepCounter=e.sleepThreshold,e.positionImpulse.x=0,e.positionImpulse.y=0,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.anglePrev=e.angle,e.speed=0,e.angularSpeed=0,e.motion=0,n||i.trigger(e,"sleepStart")):(e.isSleeping=!1,e.sleepCounter=0,n&&i.trigger(e,"sleepEnd"))}}()},{"./Events":16}],23:[function(e,t,n){(function(n){var o={};t.exports=o;var i=e("../geometry/Vertices"),r=e("../core/Common"),s=e("../body/Body"),a=e("../geometry/Bounds"),l=e("../geometry/Vector"),c="undefined"!=typeof window?window.decomp:"undefined"!=typeof n?n.decomp:null; +!function(){o.rectangle=function(e,t,n,o,a){a=a||{};var l={label:"Rectangle Body",position:{x:e,y:t},vertices:i.fromPath("L 0 0 L "+n+" 0 L "+n+" "+o+" L 0 "+o)};if(a.chamfer){var c=a.chamfer;l.vertices=i.chamfer(l.vertices,c.radius,c.quality,c.qualityMin,c.qualityMax),delete a.chamfer}return s.create(r.extend({},l,a))},o.trapezoid=function(e,t,n,o,a,l){l=l||{},a*=.5;var c,u=(1-2*a)*n,d=n*a,p=d+u,f=p+d;c=a<.5?"L 0 0 L "+d+" "+-o+" L "+p+" "+-o+" L "+f+" 0":"L 0 0 L "+p+" "+-o+" L "+f+" 0";var m={label:"Trapezoid Body",position:{x:e,y:t},vertices:i.fromPath(c)};if(l.chamfer){var v=l.chamfer;m.vertices=i.chamfer(m.vertices,v.radius,v.quality,v.qualityMin,v.qualityMax),delete l.chamfer}return s.create(r.extend({},m,l))},o.circle=function(e,t,n,i,s){i=i||{};var a={label:"Circle Body",circleRadius:n};s=s||25;var l=Math.ceil(Math.max(10,Math.min(s,n)));return l%2===1&&(l+=1),o.polygon(e,t,l,n,r.extend({},a,i))},o.polygon=function(e,t,n,a,l){if(l=l||{},n<3)return o.circle(e,t,a,l);for(var c=2*Math.PI/n,u="",d=.5*c,p=0;p0&&i.area(P)1?(f=s.create(r.extend({parts:m.slice(0)},o)),s.setPosition(f,{x:e,y:t}),f):m[0]}}()}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../body/Body":1,"../core/Common":14,"../geometry/Bounds":26,"../geometry/Vector":28,"../geometry/Vertices":29}],24:[function(e,t,n){var o={};t.exports=o;var i=e("../body/Composite"),r=e("../constraint/Constraint"),s=e("../core/Common"),a=e("../body/Body"),l=e("./Bodies");!function(){o.stack=function(e,t,n,o,r,s,l){for(var c,u=i.create({label:"Stack" +}),d=e,p=t,f=0,m=0;mv&&(v=x),a.translate(g,{x:.5*h,y:.5*x}),d=g.bounds.max.x+r,i.addBody(u,g),c=g,f+=1}else d+=r}p+=v+s,d=e}return u},o.chain=function(e,t,n,o,a,l){for(var c=e.bodies,u=1;u0)for(c=0;c0&&(p=f[c-1+(l-1)*t],i.addConstraint(e,r.create(s.extend({bodyA:p,bodyB:d},a)))),o&&cp)){c=p-c;var m=c,v=n-1-c;if(!(sv)){1===d&&a.translate(u,{x:(s+(n%2===1?1:-1))*f,y:0});var y=u?s*f:0;return l(e+y+s*r,o,s,c,u,d)}}})},o.newtonsCradle=function(e,t,n,o,s){for(var a=i.create({label:"Newtons Cradle"}),c=0;ce.max.x&&(e.max.x=i.x),i.xe.max.y&&(e.max.y=i.y),i.y0?e.max.x+=n.x:e.min.x+=n.x,n.y>0?e.max.y+=n.y:e.min.y+=n.y)},o.contains=function(e,t){return t.x>=e.min.x&&t.x<=e.max.x&&t.y>=e.min.y&&t.y<=e.max.y},o.overlaps=function(e,t){return e.min.x<=t.max.x&&e.max.x>=t.min.x&&e.max.y>=t.min.y&&e.min.y<=t.max.y},o.translate=function(e,t){e.min.x+=t.x,e.max.x+=t.x,e.min.y+=t.y,e.max.y+=t.y},o.shift=function(e,t){var n=e.max.x-e.min.x,o=e.max.y-e.min.y;e.min.x=t.x,e.max.x=t.x+n,e.min.y=t.y,e.max.y=t.y+o}}()},{}],27:[function(e,t,n){var o={};t.exports=o;e("../geometry/Bounds");!function(){o.pathToVertices=function(t,n){ +var o,i,r,s,a,l,c,u,d,p,f,m,v=[],y=0,g=0,x=0;n=n||15;var h=function(e,t,n){var o=n%2===1&&n>1;if(!d||e!=d.x||t!=d.y){d&&o?(f=d.x,m=d.y):(f=0,m=0);var i={x:f+e,y:m+t};!o&&d||(d=i),v.push(i),g=f+e,x=m+t}},b=function(e){var t=e.pathSegTypeAsLetter.toUpperCase();if("Z"!==t){switch(t){case"M":case"L":case"T":case"C":case"S":case"Q":g=e.x,x=e.y;break;case"H":g=e.x;break;case"V":x=e.y}h(g,x,e.pathSegType)}};for(e(t),r=t.getTotalLength(),l=[],o=0;o0)return!1}return!0},o.scale=function(e,t,n,r){if(1===t&&1===n)return e;r=r||o.centre(e);for(var s,a,l=0;l=0?l-1:e.length-1],u=e[l],d=e[(l+1)%e.length],p=t[l0&&(r|=2),3===r)return!1;return 0!==r||null},o.hull=function(e){var t,n,o=[],r=[];for(e=e.slice(0),e.sort(function(e,t){var n=e.x-t.x;return 0!==n?n:e.y-t.y}),n=0;n=2&&i.cross3(r[r.length-2],r[r.length-1],t)<=0;)r.pop();r.push(t)}for(n=e.length-1;n>=0;n--){for(t=e[n];o.length>=2&&i.cross3(o[o.length-2],o[o.length-1],t)<=0;)o.pop();o.push(t)}return o.pop(),r.pop(), +o.concat(r)}}()},{"../core/Common":14,"../geometry/Vector":28}],30:[function(e,t,n){var o=t.exports=e("../core/Matter");o.Body=e("../body/Body"),o.Composite=e("../body/Composite"),o.World=e("../body/World"),o.Contact=e("../collision/Contact"),o.Detector=e("../collision/Detector"),o.Grid=e("../collision/Grid"),o.Pairs=e("../collision/Pairs"),o.Pair=e("../collision/Pair"),o.Query=e("../collision/Query"),o.Resolver=e("../collision/Resolver"),o.SAT=e("../collision/SAT"),o.Constraint=e("../constraint/Constraint"),o.MouseConstraint=e("../constraint/MouseConstraint"),o.Common=e("../core/Common"),o.Engine=e("../core/Engine"),o.Events=e("../core/Events"),o.Mouse=e("../core/Mouse"),o.Runner=e("../core/Runner"),o.Sleeping=e("../core/Sleeping"),o.Plugin=e("../core/Plugin"),o.Bodies=e("../factory/Bodies"),o.Composites=e("../factory/Composites"),o.Axes=e("../geometry/Axes"),o.Bounds=e("../geometry/Bounds"),o.Svg=e("../geometry/Svg"),o.Vector=e("../geometry/Vector"),o.Vertices=e("../geometry/Vertices"), +o.Render=e("../render/Render"),o.RenderPixi=e("../render/RenderPixi"),o.World.add=o.Composite.add,o.World.remove=o.Composite.remove,o.World.addComposite=o.Composite.addComposite,o.World.addBody=o.Composite.addBody,o.World.addConstraint=o.Composite.addConstraint,o.World.clear=o.Composite.clear,o.Engine.run=o.Runner.run},{"../body/Body":1,"../body/Composite":2,"../body/World":3,"../collision/Contact":4,"../collision/Detector":5,"../collision/Grid":6,"../collision/Pair":7,"../collision/Pairs":8,"../collision/Query":9,"../collision/Resolver":10,"../collision/SAT":11,"../constraint/Constraint":12,"../constraint/MouseConstraint":13,"../core/Common":14,"../core/Engine":15,"../core/Events":16,"../core/Matter":17,"../core/Metrics":18,"../core/Mouse":19,"../core/Plugin":20,"../core/Runner":21,"../core/Sleeping":22,"../factory/Bodies":23,"../factory/Composites":24,"../geometry/Axes":25,"../geometry/Bounds":26,"../geometry/Svg":27,"../geometry/Vector":28,"../geometry/Vertices":29,"../render/Render":31, +"../render/RenderPixi":32}],31:[function(e,t,n){var o={};t.exports=o;var i=e("../core/Common"),r=e("../body/Composite"),s=e("../geometry/Bounds"),a=e("../core/Events"),l=e("../collision/Grid"),c=e("../geometry/Vector"),u=e("../core/Mouse");!function(){var e,t;"undefined"!=typeof window&&(e=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout(function(){e(i.now())},1e3/60)},t=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame),o.create=function(e){var t={controller:o,engine:null,element:null,canvas:null,mouse:null,frameRequestId:null,options:{width:800,height:600,pixelRatio:1,background:"#18181d",wireframeBackground:"#0f0f13",hasBounds:!!e.bounds,enabled:!0,wireframes:!0,showSleeping:!0,showDebug:!1,showBroadphase:!1,showBounds:!1,showVelocity:!1,showCollisions:!1,showSeparations:!1,showAxes:!1,showPositions:!1, +showAngleIndicator:!1,showIds:!1,showShadows:!1,showVertexNumbers:!1,showConvexHulls:!1,showInternalEdges:!1,showMousePosition:!1}},r=i.extend(t,e);return r.canvas&&(r.canvas.width=r.options.width||r.canvas.width,r.canvas.height=r.options.height||r.canvas.height),r.mouse=e.mouse,r.engine=e.engine,r.canvas=r.canvas||n(r.options.width,r.options.height),r.context=r.canvas.getContext("2d"),r.textures={},r.bounds=r.bounds||{min:{x:0,y:0},max:{x:r.canvas.width,y:r.canvas.height}},1!==r.options.pixelRatio&&o.setPixelRatio(r,r.options.pixelRatio),i.isElement(r.element)?r.element.appendChild(r.canvas):i.log("Render.create: options.element was undefined, render.canvas was created but not appended","warn"),r},o.run=function(t){!function n(i){t.frameRequestId=e(n),o.world(t)}()},o.stop=function(e){t(e.frameRequestId)},o.setPixelRatio=function(e,t){var n=e.options,o=e.canvas;"auto"===t&&(t=d(o)),n.pixelRatio=t,o.setAttribute("data-pixel-ratio",t),o.width=n.width*t,o.height=n.height*t,o.style.width=n.width+"px", +o.style.height=n.height+"px",e.context.scale(t,t)},o.lookAt=function(e,t,n,o){o="undefined"==typeof o||o,t=i.isArray(t)?t:[t],n=n||{x:0,y:0};for(var r={min:{x:1/0,y:1/0},max:{x:-(1/0),y:-(1/0)}},s=0;sr.max.x&&(r.max.x=c.x),l.yr.max.y&&(r.max.y=c.y))}var d=r.max.x-r.min.x+2*n.x,p=r.max.y-r.min.y+2*n.y,f=e.canvas.height,m=e.canvas.width,v=m/f,y=d/p,g=1,x=1;y>v?x=y/v:g=v/y,e.options.hasBounds=!0,e.bounds.min.x=r.min.x,e.bounds.max.x=r.min.x+d*g,e.bounds.min.y=r.min.y,e.bounds.max.y=r.min.y+p*x,o&&(e.bounds.min.x+=.5*d-d*g*.5,e.bounds.max.x+=.5*d-d*g*.5,e.bounds.min.y+=.5*p-p*x*.5,e.bounds.max.y+=.5*p-p*x*.5),e.bounds.min.x-=n.x,e.bounds.max.x-=n.x,e.bounds.min.y-=n.y,e.bounds.max.y-=n.y,e.mouse&&(u.setScale(e.mouse,{x:(e.bounds.max.x-e.bounds.min.x)/e.canvas.width,y:(e.bounds.max.y-e.bounds.min.y)/e.canvas.height +}),u.setOffset(e.mouse,e.bounds.min))},o.startViewTransform=function(e){var t=e.bounds.max.x-e.bounds.min.x,n=e.bounds.max.y-e.bounds.min.y,o=t/e.options.width,i=n/e.options.height;e.context.scale(1/o,1/i),e.context.translate(-e.bounds.min.x,-e.bounds.min.y)},o.endViewTransform=function(e){e.context.setTransform(e.options.pixelRatio,0,0,e.options.pixelRatio,0,0)},o.world=function(e){var t,n=e.engine,i=n.world,d=e.canvas,p=e.context,m=e.options,v=r.allBodies(i),y=r.allConstraints(i),g=m.wireframes?m.wireframeBackground:m.background,x=[],h=[],b={timestamp:n.timing.timestamp};if(a.trigger(e,"beforeRender",b),e.currentBackground!==g&&f(e,g),p.globalCompositeOperation="source-in",p.fillStyle="transparent",p.fillRect(0,0,d.width,d.height),p.globalCompositeOperation="source-over",m.hasBounds){for(t=0;t=500){var c="";s.timing&&(c+="fps: "+Math.round(s.timing.fps)+l),e.debugString=c,e.debugTimestamp=o.timing.timestamp}if(e.debugString){n.font="12px Arial",a.wireframes?n.fillStyle="rgba(255,255,255,0.5)":n.fillStyle="rgba(0,0,0,0.5)";for(var u=e.debugString.split("\n"),d=0;d1?1:0;s1?1:0;a1?1:0;r1?1:0;l1?1:0;r1?1:0;r1?1:0;i0)){var d=o.activeContacts[0].vertex.x,p=o.activeContacts[0].vertex.y;2===o.activeContacts.length&&(d=(o.activeContacts[0].vertex.x+o.activeContacts[1].vertex.x)/2,p=(o.activeContacts[0].vertex.y+o.activeContacts[1].vertex.y)/2),i.bodyB===i.supports[0].body||i.bodyA.isStatic===!0?a.moveTo(d-8*i.normal.x,p-8*i.normal.y):a.moveTo(d+8*i.normal.x,p+8*i.normal.y), +a.lineTo(d,p)}l.wireframes?a.strokeStyle="rgba(255,165,0,0.7)":a.strokeStyle="orange",a.lineWidth=1,a.stroke()},o.separations=function(e,t,n){var o,i,r,s,a,l=n,c=e.options;for(l.beginPath(),a=0;a1?1:0;p0&&r.rotateAbout(s.position,n,e.position,s.position)}},o.setVelocity=function(e,t){e.positionPrev.x=e.position.x-t.x,e.positionPrev.y=e.position.y-t.y,e.velocity.x=t.x,e.velocity.y=t.y,e.speed=r.magnitude(e.velocity)},o.setAngularVelocity=function(e,t){e.anglePrev=e.angle-t,e.angularVelocity=t,e.angularSpeed=Math.abs(e.angularVelocity)},o.translate=function(e,t){o.setPosition(e,r.add(e.position,t)); +},o.rotate=function(e,t,n){if(n){var i=Math.cos(t),r=Math.sin(t),s=e.position.x-n.x,a=e.position.y-n.y;o.setPosition(e,{x:n.x+(s*i-a*r),y:n.y+(s*r+a*i)}),o.setAngle(e,e.angle+t)}else o.setAngle(e,e.angle+t)},o.scale=function(e,n,r,s){for(var a=0;a0&&(f.position.x+=e.velocity.x,f.position.y+=e.velocity.y),0!==e.angularVelocity&&(i.rotate(f.vertices,e.angularVelocity,e.position),c.rotate(f.axes,e.angularVelocity),p>0&&r.rotateAbout(f.position,e.angularVelocity,e.position,f.position)),l.update(f.bounds,f.vertices,e.velocity)}},o.applyForce=function(e,t,n){e.force.x+=n.x,e.force.y+=n.y;var o={x:t.x-e.position.x,y:t.y-e.position.y};e.torque+=o.x*n.y-o.y*n.x};var t=function(e){for(var t={mass:0,area:0,inertia:0,centre:{x:0,y:0}},n=1===e.parts.length?0:1;n1?1:0;u1?1:0;f0:0!==(e.mask&t.category)&&0!==(t.mask&e.category)}}()},{"../geometry/Bounds":26,"./Pair":7,"./SAT":11}],6:[function(e,t,n){var o={};t.exports=o; +var i=e("./Pair"),r=e("./Detector"),s=e("../core/Common");!function(){o.create=function(e){var t={controller:o,detector:r.collisions,buckets:{},pairs:{},pairsList:[],bucketWidth:48,bucketHeight:48};return s.extend(t,e)},o.update=function(n,o,i,r){var s,p,f,m,v,y=i.world,g=n.buckets,x=!1;for(s=0;sy.bounds.max.x||h.bounds.max.yy.bounds.max.y)){var b=t(n,h);if(!h.region||b.id!==h.region.id||r){h.region&&!r||(h.region=b);var w=e(b,h.region);for(p=w.startCol;p<=w.endCol;p++)for(f=w.startRow;f<=w.endRow;f++){v=a(p,f),m=g[v];var S=p>=b.startCol&&p<=b.endCol&&f>=b.startRow&&f<=b.endRow,C=p>=h.region.startCol&&p<=h.region.endCol&&f>=h.region.startRow&&f<=h.region.endRow;!S&&C&&C&&m&&d(n,m,h),(h.region===b||S&&!C||r)&&(m||(m=l(g,v)),c(n,m,h))}h.region=b,x=!0}}}x&&(n.pairsList=u(n))},o.clear=function(e){e.buckets={},e.pairs={},e.pairsList=[]};var e=function(e,t){var o=Math.min(e.startCol,t.startCol),i=Math.max(e.endCol,t.endCol),r=Math.min(e.startRow,t.startRow),s=Math.max(e.endRow,t.endRow); +return n(o,i,r,s)},t=function(e,t){var o=t.bounds,i=Math.floor(o.min.x/e.bucketWidth),r=Math.floor(o.max.x/e.bucketWidth),s=Math.floor(o.min.y/e.bucketHeight),a=Math.floor(o.max.y/e.bucketHeight);return n(i,r,s,a)},n=function(e,t,n,o){return{id:e+","+t+","+n+","+o,startCol:e,endCol:t,startRow:n,endRow:o}},a=function(e,t){return"C"+e+"R"+t},l=function(e,t){var n=e[t]=[];return n},c=function(e,t,n){for(var o=0;o0?o.push(n):delete e.pairs[t[i]];return o}}()},{"../core/Common":14,"./Detector":5,"./Pair":7}],7:[function(e,t,n){var o={};t.exports=o;var i=e("./Contact");!function(){o.create=function(e,t){var n=e.bodyA,i=e.bodyB,r=e.parentA,s=e.parentB,a={ +id:o.id(n,i),bodyA:n,bodyB:i,contacts:{},activeContacts:[],separation:0,isActive:!0,isSensor:n.isSensor||i.isSensor,timeCreated:t,timeUpdated:t,inverseMass:r.inverseMass+s.inverseMass,friction:Math.min(r.friction,s.friction),frictionStatic:Math.max(r.frictionStatic,s.frictionStatic),restitution:Math.max(r.restitution,s.restitution),slop:Math.max(r.slop,s.slop)};return o.update(a,e,t),a},o.update=function(e,t,n){var r=e.contacts,s=t.supports,a=e.activeContacts,l=t.parentA,c=t.parentB;if(e.collision=t,e.inverseMass=l.inverseMass+c.inverseMass,e.friction=Math.min(l.friction,c.friction),e.frictionStatic=Math.max(l.frictionStatic,c.frictionStatic),e.restitution=Math.max(l.restitution,c.restitution),e.slop=Math.max(l.slop,c.slop),a.length=0,t.collided){for(var d=0;de&&c.push(s);for(s=0;sf.friction*f.frictionStatic*E*n&&(F=V,L=s.clamp(f.friction*_*n,-F,F));var O=r.cross(A,g),q=r.cross(P,g),W=b/(v.inverseMass+y.inverseMass+v.inverseInertia*O*O+y.inverseInertia*q*q);if(R*=W,L*=W,I<0&&I*I>o._restingThresh*n)S.normalImpulse=0;else{var D=S.normalImpulse;S.normalImpulse=Math.min(S.normalImpulse+R,0),R=S.normalImpulse-D}if(T*T>o._restingThreshTangent*n)S.tangentImpulse=0;else{var N=S.tangentImpulse;S.tangentImpulse=s.clamp(S.tangentImpulse+L,-F,F),L=S.tangentImpulse-N; +}i.x=g.x*R+x.x*L,i.y=g.y*R+x.y*L,v.isStatic||v.isSleeping||(v.positionPrev.x+=i.x*v.inverseMass,v.positionPrev.y+=i.y*v.inverseMass,v.anglePrev+=r.cross(A,i)*v.inverseInertia),y.isStatic||y.isSleeping||(y.positionPrev.x-=i.x*y.inverseMass,y.positionPrev.y-=i.y*y.inverseMass,y.anglePrev-=r.cross(P,i)*y.inverseInertia)}}}}}()},{"../core/Common":14,"../geometry/Bounds":26,"../geometry/Vector":28,"../geometry/Vertices":29}],11:[function(e,t,n){var o={};t.exports=o;var i=e("../geometry/Vertices"),r=e("../geometry/Vector");!function(){o.collides=function(t,o,s){var a,l,c,d,u=!1;if(s){var p=t.parent,f=o.parent,m=p.speed*p.speed+p.angularSpeed*p.angularSpeed+f.speed*f.speed+f.angularSpeed*f.angularSpeed;u=s&&s.collided&&m<.2,d=s}else d={collided:!1,bodyA:t,bodyB:o};if(s&&u){var v=d.axisBody,y=v===t?o:t,g=[v.axes[s.axisNumber]];if(c=e(v.vertices,y.vertices,g),d.reused=!0,c.overlap<=0)return d.collided=!1,d}else{if(a=e(t.vertices,o.vertices,t.axes),a.overlap<=0)return d.collided=!1,d;if(l=e(o.vertices,t.vertices,o.axes), +l.overlap<=0)return d.collided=!1,d;a.overlapi?i=a:a=0?s.index-1:d.length-1;i=d[f],c.x=i.x-u.x,c.y=i.y-u.y,l=-r.dot(n,c),a=i;var m=(s.index+1)%d.length;return i=d[m],c.x=i.x-u.x,c.y=i.y-u.y,o=-r.dot(n,c),o0?1:.7),t.damping=t.damping||0,t.angularStiffness=t.angularStiffness||0,t.angleA=t.bodyA?t.bodyA.angle:t.angleA,t.angleB=t.bodyB?t.bodyB.angle:t.angleB,t.plugin={};var s={visible:!0,lineWidth:2,strokeStyle:"#ffffff",type:"line",anchors:!0};return 0===t.length&&t.stiffness>.1?(s.type="pin",s.anchors=!1):t.stiffness<.9&&(s.type="spring"),t.render=c.extend(s,t.render),t},o.preSolveAll=function(e){for(var t=0;t0&&(u.position.x+=c.x,u.position.y+=c.y),0!==c.angle&&(i.rotate(u.vertices,c.angle,n.position),l.rotate(u.axes,c.angle),d>0&&r.rotateAbout(u.position,c.angle,n.position,u.position)),a.update(u.bounds,u.vertices,n.velocity)}c.angle*=o._warming,c.x*=o._warming,c.y*=o._warming}}}}()},{"../core/Common":14,"../core/Sleeping":22,"../geometry/Axes":25,"../geometry/Bounds":26,"../geometry/Vector":28,"../geometry/Vertices":29}],13:[function(e,t,n){ +var o={};t.exports=o;var i=e("../geometry/Vertices"),r=e("../core/Sleeping"),s=e("../core/Mouse"),a=e("../core/Events"),l=e("../collision/Detector"),c=e("./Constraint"),d=e("../body/Composite"),u=e("../core/Common"),p=e("../geometry/Bounds");!function(){o.create=function(t,n){var i=(t?t.mouse:null)||(n?n.mouse:null);i||(t&&t.render&&t.render.canvas?i=s.create(t.render.canvas):n&&n.element?i=s.create(n.element):(i=s.create(),u.warn("MouseConstraint.create: options.mouse was undefined, options.element was undefined, may not function as expected")));var r=c.create({label:"Mouse Constraint",pointA:i.position,pointB:{x:0,y:0},length:.01,stiffness:.1,angularStiffness:1,render:{strokeStyle:"#90EE90",lineWidth:3}}),l={type:"mouseConstraint",mouse:i,element:null,body:null,constraint:r,collisionFilter:{category:1,mask:4294967295,group:0}},p=u.extend(l,n);return a.on(t,"beforeUpdate",function(){var n=d.allBodies(t.world);o.update(p,n),e(p)}),p},o.update=function(e,t){var n=e.mouse,o=e.constraint,s=e.body; +if(0===n.button){if(o.bodyB)r.set(o.bodyB,!1),o.pointA=n.position;else for(var c=0;c1?1:0;d0;t--){var n=Math.floor(o.random()*(t+1)),i=e[t];e[t]=e[n],e[n]=i}return e},o.choose=function(e){ +return e[Math.floor(o.random()*e.length)]},o.isElement=function(e){return e instanceof HTMLElement},o.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)},o.isFunction=function(e){return"function"==typeof e},o.isPlainObject=function(e){return"object"==typeof e&&e.constructor===Object},o.isString=function(e){return"[object String]"===toString.call(e)},o.clamp=function(e,t,n){return en?n:e},o.sign=function(e){return e<0?-1:1},o.now=function(){if(window.performance){if(window.performance.now)return window.performance.now();if(window.performance.webkitNow)return window.performance.webkitNow()}return new Date-o._nowStartTime},o.random=function(t,n){return t="undefined"!=typeof t?t:0,n="undefined"!=typeof n?n:1,t+e()*(n-t)};var e=function(){return o._seed=(9301*o._seed+49297)%233280,o._seed/233280};o.colorToNumber=function(e){return e=e.replace("#",""),3==e.length&&(e=e.charAt(0)+e.charAt(0)+e.charAt(1)+e.charAt(1)+e.charAt(2)+e.charAt(2)),parseInt(e,16)},o.logLevel=1, +o.log=function(){console&&o.logLevel>0&&o.logLevel<=3&&console.log.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},o.info=function(){console&&o.logLevel>0&&o.logLevel<=2&&console.info.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},o.warn=function(){console&&o.logLevel>0&&o.logLevel<=3&&console.warn.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},o.nextId=function(){return o._nextId++},o.indexOf=function(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0;n0&&d.trigger(o,"collisionStart",{pairs:w.collisionStart}),s.preSolvePosition(w.list),c=0;c0&&d.trigger(o,"collisionActive",{pairs:w.collisionActive}),w.collisionEnd.length>0&&d.trigger(o,"collisionEnd",{pairs:w.collisionEnd}),e(x),d.trigger(o,"afterUpdate",g),o},o.merge=function(e,t){if(f.extend(e,t),t.world){e.world=t.world,o.clear(e);for(var n=u.allBodies(e.world),i=0;ir?(i.warn("Plugin.register:",o.toString(t),"was upgraded to",o.toString(e)),o._registry[e.name]=e):n-1},o.isFor=function(e,t){var n=e.for&&o.dependencyParse(e.for);return!e.for||t.name===n.name&&o.versionSatisfies(t.version,n.range)},o.use=function(e,t){if(e.uses=(e.uses||[]).concat(t||[]),0===e.uses.length)return void i.warn("Plugin.use:",o.toString(e),"does not specify any dependencies to install.");for(var n=o.dependencies(e),r=i.topologicalSort(n),s=[],a=0;a0&&i.info(s.join(" "))},o.dependencies=function(e,t){var n=o.dependencyParse(e),r=n.name;if(t=t||{},!(r in t)){e=o.resolve(e)||e,t[r]=i.map(e.uses||[],function(t){o.isPlugin(t)&&o.register(t);var r=o.dependencyParse(t),s=o.resolve(t);return s&&!o.versionSatisfies(s.version,r.range)?(i.warn("Plugin.dependencies:",o.toString(s),"does not satisfy",o.toString(r),"used by",o.toString(n)+"."),s._warned=!0,e._warned=!0):s||(i.warn("Plugin.dependencies:",o.toString(t),"used by",o.toString(n),"could not be resolved."),e._warned=!0),r.name});for(var s=0;s=i[2];if("^"===n.operator)return i[0]>0?s[0]===i[0]&&r.number>=n.number:i[1]>0?s[1]===i[1]&&s[2]>=i[2]:s[2]===i[2]; +}return e===t||"*"===e}}()},{"./Common":14}],21:[function(e,t,n){var o={};t.exports=o;var i=e("./Events"),r=e("./Engine"),s=e("./Common");!function(){var e,t;if("undefined"!=typeof window&&(e=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame,t=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame),!e){var n;e=function(e){n=setTimeout(function(){e(s.now())},1e3/60)},t=function(){clearTimeout(n)}}o.create=function(e){var t={fps:60,correction:1,deltaSampleSize:60,counterTimestamp:0,frameCounter:0,deltaHistory:[],timePrev:null,timeScalePrev:1,frameRequestId:null,isFixed:!1,enabled:!0},n=s.extend(t,e);return n.delta=n.delta||1e3/n.fps,n.deltaMin=n.deltaMin||1e3/n.fps,n.deltaMax=n.deltaMax||1e3/(.5*n.fps),n.fps=1e3/n.delta,n},o.run=function(t,n){return"undefined"!=typeof t.positionIterations&&(n=t,t=o.create()),function i(r){t.frameRequestId=e(i), +r&&t.enabled&&o.tick(t,n,r)}(),t},o.tick=function(e,t,n){var o,s=t.timing,a=1,l={timestamp:s.timestamp};i.trigger(e,"beforeTick",l),i.trigger(t,"beforeTick",l),e.isFixed?o=e.delta:(o=n-e.timePrev||e.delta,e.timePrev=n,e.deltaHistory.push(o),e.deltaHistory=e.deltaHistory.slice(-e.deltaSampleSize),o=Math.min.apply(null,e.deltaHistory),o=oe.deltaMax?e.deltaMax:o,a=o/e.delta,e.delta=o),0!==e.timeScalePrev&&(a*=s.timeScale/e.timeScalePrev),0===s.timeScale&&(a=0),e.timeScalePrev=s.timeScale,e.correction=a,e.frameCounter+=1,n-e.counterTimestamp>=1e3&&(e.fps=e.frameCounter*((n-e.counterTimestamp)/1e3),e.counterTimestamp=n,e.frameCounter=0),i.trigger(e,"tick",l),i.trigger(t,"tick",l),t.world.isModified&&t.render&&t.render.controller&&t.render.controller.clear&&t.render.controller.clear(t.render),i.trigger(e,"beforeUpdate",l),r.update(t,o,a),i.trigger(e,"afterUpdate",l),t.render&&t.render.controller&&(i.trigger(e,"beforeRender",l),i.trigger(t,"beforeRender",l),t.render.controller.world(t.render), +i.trigger(e,"afterRender",l),i.trigger(t,"afterRender",l)),i.trigger(e,"afterTick",l),i.trigger(t,"afterTick",l)},o.stop=function(e){t(e.frameRequestId)},o.start=function(e,t){o.run(e,t)}}()},{"./Common":14,"./Engine":15,"./Events":16}],22:[function(e,t,n){var o={};t.exports=o;var i=e("./Events");!function(){o._motionWakeThreshold=.18,o._motionSleepThreshold=.08,o._minBias=.9,o.update=function(e,t){for(var n=t*t*t,i=0;i0&&r.motion=r.sleepThreshold&&o.set(r,!0)):r.sleepCounter>0&&(r.sleepCounter-=1)}else o.set(r,!1)}},o.afterCollisions=function(e,t){for(var n=t*t*t,i=0;io._motionWakeThreshold*n&&o.set(c,!1)}}}},o.set=function(e,t){var n=e.isSleeping;t?(e.isSleeping=!0,e.sleepCounter=e.sleepThreshold,e.positionImpulse.x=0,e.positionImpulse.y=0,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.anglePrev=e.angle,e.speed=0,e.angularSpeed=0,e.motion=0,n||i.trigger(e,"sleepStart")):(e.isSleeping=!1,e.sleepCounter=0,n&&i.trigger(e,"sleepEnd"))}}()},{"./Events":16}],23:[function(e,t,n){(function(n){var o={};t.exports=o;var i=e("../geometry/Vertices"),r=e("../core/Common"),s=e("../body/Body"),a=e("../geometry/Bounds"),l=e("../geometry/Vector"),c="undefined"!=typeof window?window.decomp:"undefined"!=typeof n?n.decomp:null;!function(){o.rectangle=function(e,t,n,o,a){a=a||{};var l={label:"Rectangle Body",position:{x:e,y:t},vertices:i.fromPath("L 0 0 L "+n+" 0 L "+n+" "+o+" L 0 "+o)};if(a.chamfer){var c=a.chamfer;l.vertices=i.chamfer(l.vertices,c.radius,c.quality,c.qualityMin,c.qualityMax), +delete a.chamfer}return s.create(r.extend({},l,a))},o.trapezoid=function(e,t,n,o,a,l){l=l||{},a*=.5;var c,d=(1-2*a)*n,u=n*a,p=u+d,f=p+u;c=a<.5?"L 0 0 L "+u+" "+-o+" L "+p+" "+-o+" L "+f+" 0":"L 0 0 L "+p+" "+-o+" L "+f+" 0";var m={label:"Trapezoid Body",position:{x:e,y:t},vertices:i.fromPath(c)};if(l.chamfer){var v=l.chamfer;m.vertices=i.chamfer(m.vertices,v.radius,v.quality,v.qualityMin,v.qualityMax),delete l.chamfer}return s.create(r.extend({},m,l))},o.circle=function(e,t,n,i,s){i=i||{};var a={label:"Circle Body",circleRadius:n};s=s||25;var l=Math.ceil(Math.max(10,Math.min(s,n)));return l%2===1&&(l+=1),o.polygon(e,t,l,n,r.extend({},a,i))},o.polygon=function(e,t,n,a,l){if(l=l||{},n<3)return o.circle(e,t,a,l);for(var c=2*Math.PI/n,d="",u=.5*c,p=0;p0&&i.area(P)1?(f=s.create(r.extend({parts:m.slice(0)},o)),s.setPosition(f,{x:e,y:t}),f):m[0]}}()}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../body/Body":1,"../core/Common":14,"../geometry/Bounds":26,"../geometry/Vector":28,"../geometry/Vertices":29}],24:[function(e,t,n){var o={};t.exports=o;var i=e("../body/Composite"),r=e("../constraint/Constraint"),s=e("../core/Common"),a=e("../body/Body"),l=e("./Bodies");!function(){o.stack=function(e,t,n,o,r,s,l){for(var c,d=i.create({label:"Stack"}),u=e,p=t,f=0,m=0;mv&&(v=x),a.translate(g,{x:.5*h,y:.5*x}),u=g.bounds.max.x+r,i.addBody(d,g),c=g,f+=1}else u+=r}p+=v+s,u=e}return d},o.chain=function(e,t,n,o,a,l){ +for(var c=e.bodies,d=1;d0)for(c=0;c0&&(p=f[c-1+(l-1)*t],i.addConstraint(e,r.create(s.extend({bodyA:p,bodyB:u},a)))),o&&cp)){c=p-c;var m=c,v=n-1-c;if(!(sv)){ +1===u&&a.translate(d,{x:(s+(n%2===1?1:-1))*f,y:0});var y=d?s*f:0;return l(e+y+s*r,o,s,c,d,u)}}})},o.newtonsCradle=function(e,t,n,o,s){for(var a=i.create({label:"Newtons Cradle"}),c=0;ce.max.x&&(e.max.x=i.x),i.xe.max.y&&(e.max.y=i.y),i.y0?e.max.x+=n.x:e.min.x+=n.x,n.y>0?e.max.y+=n.y:e.min.y+=n.y)},o.contains=function(e,t){return t.x>=e.min.x&&t.x<=e.max.x&&t.y>=e.min.y&&t.y<=e.max.y},o.overlaps=function(e,t){return e.min.x<=t.max.x&&e.max.x>=t.min.x&&e.max.y>=t.min.y&&e.min.y<=t.max.y},o.translate=function(e,t){e.min.x+=t.x,e.max.x+=t.x,e.min.y+=t.y,e.max.y+=t.y},o.shift=function(e,t){var n=e.max.x-e.min.x,o=e.max.y-e.min.y;e.min.x=t.x,e.max.x=t.x+n,e.min.y=t.y,e.max.y=t.y+o}}()},{}],27:[function(e,t,n){var o={};t.exports=o;e("../geometry/Bounds");!function(){o.pathToVertices=function(t,n){var o,i,r,s,a,l,c,d,u,p,f,m,v=[],y=0,g=0,x=0;n=n||15;var h=function(e,t,n){var o=n%2===1&&n>1;if(!u||e!=u.x||t!=u.y){u&&o?(f=u.x,m=u.y):(f=0,m=0);var i={x:f+e,y:m+t};!o&&u||(u=i),v.push(i),g=f+e,x=m+t}},b=function(e){var t=e.pathSegTypeAsLetter.toUpperCase();if("Z"!==t){switch(t){case"M":case"L":case"T":case"C": +case"S":case"Q":g=e.x,x=e.y;break;case"H":g=e.x;break;case"V":x=e.y}h(g,x,e.pathSegType)}};for(e(t),r=t.getTotalLength(),l=[],o=0;o0)return!1}return!0},o.scale=function(e,t,n,r){if(1===t&&1===n)return e;r=r||o.centre(e);for(var s,a,l=0;l=0?l-1:e.length-1],d=e[l],u=e[(l+1)%e.length],p=t[l0&&(r|=2),3===r)return!1;return 0!==r||null},o.hull=function(e){var t,n,o=[],r=[];for(e=e.slice(0),e.sort(function(e,t){var n=e.x-t.x;return 0!==n?n:e.y-t.y}),n=0;n=2&&i.cross3(r[r.length-2],r[r.length-1],t)<=0;)r.pop();r.push(t)}for(n=e.length-1;n>=0;n-=1){for(t=e[n];o.length>=2&&i.cross3(o[o.length-2],o[o.length-1],t)<=0;)o.pop();o.push(t)}return o.pop(),r.pop(),o.concat(r)}}()},{"../core/Common":14,"../geometry/Vector":28}],30:[function(e,t,n){var o=t.exports=e("../core/Matter");o.Body=e("../body/Body"),o.Composite=e("../body/Composite"),o.World=e("../body/World"),o.Contact=e("../collision/Contact"),o.Detector=e("../collision/Detector"), +o.Grid=e("../collision/Grid"),o.Pairs=e("../collision/Pairs"),o.Pair=e("../collision/Pair"),o.Query=e("../collision/Query"),o.Resolver=e("../collision/Resolver"),o.SAT=e("../collision/SAT"),o.Constraint=e("../constraint/Constraint"),o.MouseConstraint=e("../constraint/MouseConstraint"),o.Common=e("../core/Common"),o.Engine=e("../core/Engine"),o.Events=e("../core/Events"),o.Mouse=e("../core/Mouse"),o.Runner=e("../core/Runner"),o.Sleeping=e("../core/Sleeping"),o.Plugin=e("../core/Plugin"),o.Bodies=e("../factory/Bodies"),o.Composites=e("../factory/Composites"),o.Axes=e("../geometry/Axes"),o.Bounds=e("../geometry/Bounds"),o.Svg=e("../geometry/Svg"),o.Vector=e("../geometry/Vector"),o.Vertices=e("../geometry/Vertices"),o.Render=e("../render/Render"),o.RenderPixi=e("../render/RenderPixi"),o.World.add=o.Composite.add,o.World.remove=o.Composite.remove,o.World.addComposite=o.Composite.addComposite,o.World.addBody=o.Composite.addBody,o.World.addConstraint=o.Composite.addConstraint,o.World.clear=o.Composite.clear, +o.Engine.run=o.Runner.run},{"../body/Body":1,"../body/Composite":2,"../body/World":3,"../collision/Contact":4,"../collision/Detector":5,"../collision/Grid":6,"../collision/Pair":7,"../collision/Pairs":8,"../collision/Query":9,"../collision/Resolver":10,"../collision/SAT":11,"../constraint/Constraint":12,"../constraint/MouseConstraint":13,"../core/Common":14,"../core/Engine":15,"../core/Events":16,"../core/Matter":17,"../core/Metrics":18,"../core/Mouse":19,"../core/Plugin":20,"../core/Runner":21,"../core/Sleeping":22,"../factory/Bodies":23,"../factory/Composites":24,"../geometry/Axes":25,"../geometry/Bounds":26,"../geometry/Svg":27,"../geometry/Vector":28,"../geometry/Vertices":29,"../render/Render":31,"../render/RenderPixi":32}],31:[function(e,t,n){var o={};t.exports=o;var i=e("../core/Common"),r=e("../body/Composite"),s=e("../geometry/Bounds"),a=e("../core/Events"),l=e("../collision/Grid"),c=e("../geometry/Vector"),d=e("../core/Mouse");!function(){var e,t;"undefined"!=typeof window&&(e=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||function(e){ +window.setTimeout(function(){e(i.now())},1e3/60)},t=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame),o.create=function(e){var t={controller:o,engine:null,element:null,canvas:null,mouse:null,frameRequestId:null,options:{width:800,height:600,pixelRatio:1,background:"#18181d",wireframeBackground:"#0f0f13",hasBounds:!!e.bounds,enabled:!0,wireframes:!0,showSleeping:!0,showDebug:!1,showBroadphase:!1,showBounds:!1,showVelocity:!1,showCollisions:!1,showSeparations:!1,showAxes:!1,showPositions:!1,showAngleIndicator:!1,showIds:!1,showShadows:!1,showVertexNumbers:!1,showConvexHulls:!1,showInternalEdges:!1,showMousePosition:!1}},r=i.extend(t,e);return r.canvas&&(r.canvas.width=r.options.width||r.canvas.width,r.canvas.height=r.options.height||r.canvas.height),r.mouse=e.mouse,r.engine=e.engine,r.canvas=r.canvas||n(r.options.width,r.options.height),r.context=r.canvas.getContext("2d"),r.textures={},r.bounds=r.bounds||{min:{x:0, +y:0},max:{x:r.canvas.width,y:r.canvas.height}},1!==r.options.pixelRatio&&o.setPixelRatio(r,r.options.pixelRatio),i.isElement(r.element)?r.element.appendChild(r.canvas):i.log("Render.create: options.element was undefined, render.canvas was created but not appended","warn"),r},o.run=function(t){!function n(i){t.frameRequestId=e(n),o.world(t)}()},o.stop=function(e){t(e.frameRequestId)},o.setPixelRatio=function(e,t){var n=e.options,o=e.canvas;"auto"===t&&(t=u(o)),n.pixelRatio=t,o.setAttribute("data-pixel-ratio",t),o.width=n.width*t,o.height=n.height*t,o.style.width=n.width+"px",o.style.height=n.height+"px",e.context.scale(t,t)},o.lookAt=function(e,t,n,o){o="undefined"==typeof o||o,t=i.isArray(t)?t:[t],n=n||{x:0,y:0};for(var r={min:{x:1/0,y:1/0},max:{x:-(1/0),y:-(1/0)}},s=0;sr.max.x&&(r.max.x=c.x),l.yr.max.y&&(r.max.y=c.y)); +}var u=r.max.x-r.min.x+2*n.x,p=r.max.y-r.min.y+2*n.y,f=e.canvas.height,m=e.canvas.width,v=m/f,y=u/p,g=1,x=1;y>v?x=y/v:g=v/y,e.options.hasBounds=!0,e.bounds.min.x=r.min.x,e.bounds.max.x=r.min.x+u*g,e.bounds.min.y=r.min.y,e.bounds.max.y=r.min.y+p*x,o&&(e.bounds.min.x+=.5*u-u*g*.5,e.bounds.max.x+=.5*u-u*g*.5,e.bounds.min.y+=.5*p-p*x*.5,e.bounds.max.y+=.5*p-p*x*.5),e.bounds.min.x-=n.x,e.bounds.max.x-=n.x,e.bounds.min.y-=n.y,e.bounds.max.y-=n.y,e.mouse&&(d.setScale(e.mouse,{x:(e.bounds.max.x-e.bounds.min.x)/e.canvas.width,y:(e.bounds.max.y-e.bounds.min.y)/e.canvas.height}),d.setOffset(e.mouse,e.bounds.min))},o.startViewTransform=function(e){var t=e.bounds.max.x-e.bounds.min.x,n=e.bounds.max.y-e.bounds.min.y,o=t/e.options.width,i=n/e.options.height;e.context.scale(1/o,1/i),e.context.translate(-e.bounds.min.x,-e.bounds.min.y)},o.endViewTransform=function(e){e.context.setTransform(e.options.pixelRatio,0,0,e.options.pixelRatio,0,0)},o.world=function(e){var t,n=e.engine,i=n.world,u=e.canvas,p=e.context,m=e.options,v=r.allBodies(i),y=r.allConstraints(i),g=m.wireframes?m.wireframeBackground:m.background,x=[],h=[],b={ +timestamp:n.timing.timestamp};if(a.trigger(e,"beforeRender",b),e.currentBackground!==g&&f(e,g),p.globalCompositeOperation="source-in",p.fillStyle="transparent",p.fillRect(0,0,u.width,u.height),p.globalCompositeOperation="source-over",m.hasBounds){for(t=0;t=500){var c="";s.timing&&(c+="fps: "+Math.round(s.timing.fps)+l),e.debugString=c,e.debugTimestamp=o.timing.timestamp}if(e.debugString){n.font="12px Arial",a.wireframes?n.fillStyle="rgba(255,255,255,0.5)":n.fillStyle="rgba(0,0,0,0.5)";for(var d=e.debugString.split("\n"),u=0;u1?1:0;s1?1:0;a1?1:0;r1?1:0;l1?1:0;r1?1:0;r1?1:0;i0)){var u=o.activeContacts[0].vertex.x,p=o.activeContacts[0].vertex.y;2===o.activeContacts.length&&(u=(o.activeContacts[0].vertex.x+o.activeContacts[1].vertex.x)/2,p=(o.activeContacts[0].vertex.y+o.activeContacts[1].vertex.y)/2),i.bodyB===i.supports[0].body||i.bodyA.isStatic===!0?a.moveTo(u-8*i.normal.x,p-8*i.normal.y):a.moveTo(u+8*i.normal.x,p+8*i.normal.y),a.lineTo(u,p); +}l.wireframes?a.strokeStyle="rgba(255,165,0,0.7)":a.strokeStyle="orange",a.lineWidth=1,a.stroke()},o.separations=function(e,t,n){var o,i,r,s,a,l=n,c=e.options;for(l.beginPath(),a=0;a1?1:0;p colors.length) { + // Since we're generating multiple colors, + // incremement the seed. Otherwise we'd just + // generate the same color each time... + if (seed && options.seed) options.seed += 1; + + colors.push(randomColor(options)); + } + + options.count = totalColors; + + return colors; + } + + // First we pick a hue (H) + H = pickHue(options); + + // Then use H to determine saturation (S) + S = pickSaturation(H, options); + + // Then use S and H to determine brightness (B). + B = pickBrightness(H, S, options); + + // Then we return the HSB color in the desired format + return setFormat([H, S, B], options); + }; + + function pickHue(options) { + var hueRange = getHueRange(options.hue), + hue = randomWithin(hueRange); + + // Instead of storing red as two seperate ranges, + // we group them, using negative numbers + if (hue < 0) { + hue = 360 + hue; + } + + return hue; + } + + function pickSaturation(hue, options) { + if (options.hue === "monochrome") { + return 0; + } + + if (options.luminosity === "random") { + return randomWithin([0, 100]); + } + + var saturationRange = getSaturationRange(hue); + + var sMin = saturationRange[0], + sMax = saturationRange[1]; + + switch (options.luminosity) { + case "bright": + sMin = 55; + break; + + case "dark": + sMin = sMax - 10; + break; + + case "light": + sMax = 55; + break; + } + + return randomWithin([sMin, sMax]); + } + + function pickBrightness(H, S, options) { + var bMin = getMinimumBrightness(H, S), + bMax = 100; + + switch (options.luminosity) { + case "dark": + bMax = bMin + 20; + break; + + case "light": + bMin = (bMax + bMin) / 2; + break; + + case "random": + bMin = 0; + bMax = 100; + break; + } + + return randomWithin([bMin, bMax]); + } + + function setFormat(hsv, options) { + switch (options.format) { + case "hsvArray": + return hsv; + + case "hslArray": + return HSVtoHSL(hsv); + + case "hsl": + var hsl = HSVtoHSL(hsv); + return "hsl(" + hsl[0] + ", " + hsl[1] + "%, " + hsl[2] + "%)"; + + case "hsla": + var hslColor = HSVtoHSL(hsv); + var alpha = options.alpha || Math.random(); + return "hsla(" + hslColor[0] + ", " + hslColor[1] + "%, " + hslColor[2] + "%, " + alpha + ")"; + + case "rgbArray": + return HSVtoRGB(hsv); + + case "rgb": + var rgb = HSVtoRGB(hsv); + return "rgb(" + rgb.join(", ") + ")"; + + case "rgba": + var rgbColor = HSVtoRGB(hsv); + var alpha = options.alpha || Math.random(); + return "rgba(" + rgbColor.join(", ") + ", " + alpha + ")"; + + default: + return HSVtoHex(hsv); + } + } + + function getMinimumBrightness(H, S) { + var lowerBounds = getColorInfo(H).lowerBounds; + + for (var i = 0; i < lowerBounds.length - 1; i++) { + var s1 = lowerBounds[i][0], + v1 = lowerBounds[i][1]; + + var s2 = lowerBounds[i + 1][0], + v2 = lowerBounds[i + 1][1]; + + if (S >= s1 && S <= s2) { + var m = (v2 - v1) / (s2 - s1), + b = v1 - m * s1; + + return m * S + b; + } + } + + return 0; + } + + function getHueRange(colorInput) { + if (typeof parseInt(colorInput) === "number") { + var number = parseInt(colorInput); + + if (number < 360 && number > 0) { + return [number, number]; + } + } + + if (typeof colorInput === "string") { + if (colorDictionary[colorInput]) { + var color = colorDictionary[colorInput]; + if (color.hueRange) { + return color.hueRange; + } + } else if (colorInput.match(/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i)) { + var hue = HexToHSB(colorInput)[0]; + return [hue, hue]; + } + } + + return [0, 360]; + } + + function getSaturationRange(hue) { + return getColorInfo(hue).saturationRange; + } + + function getColorInfo(hue) { + // Maps red colors to make picking hue easier + if (hue >= 334 && hue <= 360) { + hue -= 360; + } + + for (var colorName in colorDictionary) { + var color = colorDictionary[colorName]; + if (color.hueRange && hue >= color.hueRange[0] && hue <= color.hueRange[1]) { + return colorDictionary[colorName]; + } + } + return "Color not found"; + } + + function randomWithin(range) { + if (seed === null) { + return Math.floor(range[0] + Math.random() * (range[1] + 1 - range[0])); + } else { + //Seeded random algorithm from http://indiegamr.com/generate-repeatable-random-numbers-in-js/ + var max = range[1] || 1; + var min = range[0] || 0; + seed = (seed * 9301 + 49297) % 233280; + var rnd = seed / 233280.0; + return Math.floor(min + rnd * (max - min)); + } + } + + function HSVtoHex(hsv) { + var rgb = HSVtoRGB(hsv); + + function componentToHex(c) { + var hex = c.toString(16); + return hex.length == 1 ? "0" + hex : hex; + } + + var hex = "#" + componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]); + + return hex; + } + + function defineColor(name, hueRange, lowerBounds) { + var sMin = lowerBounds[0][0], + sMax = lowerBounds[lowerBounds.length - 1][0], + bMin = lowerBounds[lowerBounds.length - 1][1], + bMax = lowerBounds[0][1]; + + colorDictionary[name] = { + hueRange: hueRange, + lowerBounds: lowerBounds, + saturationRange: [sMin, sMax], + brightnessRange: [bMin, bMax] + }; + } + + function loadColorBounds() { + defineColor("monochrome", null, [[0, 0], [100, 0]]); + + defineColor("red", [-26, 18], [[20, 100], [30, 92], [40, 89], [50, 85], [60, 78], [70, 70], [80, 60], [90, 55], [100, 50]]); + + defineColor("orange", [19, 46], [[20, 100], [30, 93], [40, 88], [50, 86], [60, 85], [70, 70], [100, 70]]); + + defineColor("yellow", [47, 62], [[25, 100], [40, 94], [50, 89], [60, 86], [70, 84], [80, 82], [90, 80], [100, 75]]); + + defineColor("green", [63, 178], [[30, 100], [40, 90], [50, 85], [60, 81], [70, 74], [80, 64], [90, 50], [100, 40]]); + + defineColor("blue", [179, 257], [[20, 100], [30, 86], [40, 80], [50, 74], [60, 60], [70, 52], [80, 44], [90, 39], [100, 35]]); + + defineColor("purple", [258, 282], [[20, 100], [30, 87], [40, 79], [50, 70], [60, 65], [70, 59], [80, 52], [90, 45], [100, 42]]); + + defineColor("pink", [283, 334], [[20, 100], [30, 90], [40, 86], [60, 84], [80, 80], [90, 75], [100, 73]]); + } + + function HSVtoRGB(hsv) { + // this doesn't work for the values of 0 and 360 + // here's the hacky fix + var h = hsv[0]; + if (h === 0) { + h = 1; + } + if (h === 360) { + h = 359; + } + + // Rebase the h,s,v values + h = h / 360; + var s = hsv[1] / 100, + v = hsv[2] / 100; + + var h_i = Math.floor(h * 6), + f = h * 6 - h_i, + p = v * (1 - s), + q = v * (1 - f * s), + t = v * (1 - (1 - f) * s), + r = 256, + g = 256, + b = 256; + + switch (h_i) { + case 0: + r = v; + g = t; + b = p; + break; + case 1: + r = q; + g = v; + b = p; + break; + case 2: + r = p; + g = v; + b = t; + break; + case 3: + r = p; + g = q; + b = v; + break; + case 4: + r = t; + g = p; + b = v; + break; + case 5: + r = v; + g = p; + b = q; + break; + } + + var result = [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)]; + return result; + } + + function HexToHSB(hex) { + hex = hex.replace(/^#/, ""); + hex = hex.length === 3 ? hex.replace(/(.)/g, "$1$1") : hex; + + var red = parseInt(hex.substr(0, 2), 16) / 255, + green = parseInt(hex.substr(2, 2), 16) / 255, + blue = parseInt(hex.substr(4, 2), 16) / 255; + + var cMax = Math.max(red, green, blue), + delta = cMax - Math.min(red, green, blue), + saturation = cMax ? delta / cMax : 0; + + switch (cMax) { + case red: + return [60 * (((green - blue) / delta) % 6) || 0, saturation, cMax]; + case green: + return [60 * ((blue - red) / delta + 2) || 0, saturation, cMax]; + case blue: + return [60 * ((red - green) / delta + 4) || 0, saturation, cMax]; + } + } + + function HSVtoHSL(hsv) { + var h = hsv[0], + s = hsv[1] / 100, + v = hsv[2] / 100, + k = (2 - s) * v; + + return [h, Math.round(s * v / (k < 1 ? k : 2 - k) * 10000) / 100, k / 2 * 100]; + } + + function stringToInteger(string) { + var total = 0; + for (var i = 0; i !== string.length; i++) { + if (total >= Number.MAX_SAFE_INTEGER) break; + total += string.charCodeAt(i); + } + return total; + } + + return randomColor; +}); diff --git a/js/lib/randomColor.min.js b/js/lib/randomColor.min.js new file mode 100644 index 0000000..1fd463b --- /dev/null +++ b/js/lib/randomColor.min.js @@ -0,0 +1 @@ +(function(root,factory){if(typeof define==="function"&&define.amd){define([],factory)}else if(typeof exports==="object"){var randomColor=factory();if(typeof module==="object"&&module&&module.exports){exports=module.exports=randomColor}exports.randomColor=randomColor}else{root.randomColor=factory()}})(this,function(){var seed=null;var colorDictionary={};loadColorBounds();var randomColor=function(options){options=options||{};if(options.seed&&options.seed===parseInt(options.seed,10)){seed=options.seed}else if(typeof options.seed==="string"){seed=stringToInteger(options.seed)}else if(options.seed!==undefined&&options.seed!==null){throw new TypeError("The seed value must be an integer")}else{seed=null}var H,S,B;if(options.count!==null&&options.count!==undefined){var totalColors=options.count,colors=[];options.count=null;while(totalColors>colors.length){if(seed&&options.seed)options.seed+=1;colors.push(randomColor(options))}options.count=totalColors;return colors}H=pickHue(options);S=pickSaturation(H,options);B=pickBrightness(H,S,options);return setFormat([H,S,B],options)};function pickHue(options){var hueRange=getHueRange(options.hue),hue=randomWithin(hueRange);if(hue<0){hue=360+hue}return hue}function pickSaturation(hue,options){if(options.luminosity==="random"){return randomWithin([0,100])}if(options.hue==="monochrome"){return 0}var saturationRange=getSaturationRange(hue);var sMin=saturationRange[0],sMax=saturationRange[1];switch(options.luminosity){case"bright":sMin=55;break;case"dark":sMin=sMax-10;break;case"light":sMax=55;break}return randomWithin([sMin,sMax])}function pickBrightness(H,S,options){var bMin=getMinimumBrightness(H,S),bMax=100;switch(options.luminosity){case"dark":bMax=bMin+20;break;case"light":bMin=(bMax+bMin)/2;break;case"random":bMin=0;bMax=100;break}return randomWithin([bMin,bMax])}function setFormat(hsv,options){switch(options.format){case"hsvArray":return hsv;case"hslArray":return HSVtoHSL(hsv);case"hsl":var hsl=HSVtoHSL(hsv);return"hsl("+hsl[0]+", "+hsl[1]+"%, "+hsl[2]+"%)";case"hsla":var hslColor=HSVtoHSL(hsv);return"hsla("+hslColor[0]+", "+hslColor[1]+"%, "+hslColor[2]+"%, "+Math.random()+")";case"rgbArray":return HSVtoRGB(hsv);case"rgb":var rgb=HSVtoRGB(hsv);return"rgb("+rgb.join(", ")+")";case"rgba":var rgbColor=HSVtoRGB(hsv);return"rgba("+rgbColor.join(", ")+", "+Math.random()+")";default:return HSVtoHex(hsv)}}function getMinimumBrightness(H,S){var lowerBounds=getColorInfo(H).lowerBounds;for(var i=0;i=s1&&S<=s2){var m=(v2-v1)/(s2-s1),b=v1-m*s1;return m*S+b}}return 0}function getHueRange(colorInput){if(typeof parseInt(colorInput)==="number"){var number=parseInt(colorInput);if(number<360&&number>0){return[number,number]}}if(typeof colorInput==="string"){if(colorDictionary[colorInput]){var color=colorDictionary[colorInput];if(color.hueRange){return color.hueRange}}}return[0,360]}function getSaturationRange(hue){return getColorInfo(hue).saturationRange}function getColorInfo(hue){if(hue>=334&&hue<=360){hue-=360}for(var colorName in colorDictionary){var color=colorDictionary[colorName];if(color.hueRange&&hue>=color.hueRange[0]&&hue<=color.hueRange[1]){return colorDictionary[colorName]}}return"Color not found"}function randomWithin(range){if(seed===null){return Math.floor(range[0]+Math.random()*(range[1]+1-range[0]))}else{var max=range[1]||1;var min=range[0]||0;seed=(seed*9301+49297)%233280;var rnd=seed/233280;return Math.floor(min+rnd*(max-min))}}function HSVtoHex(hsv){var rgb=HSVtoRGB(hsv);function componentToHex(c){var hex=c.toString(16);return hex.length==1?"0"+hex:hex}var hex="#"+componentToHex(rgb[0])+componentToHex(rgb[1])+componentToHex(rgb[2]);return hex}function defineColor(name,hueRange,lowerBounds){var sMin=lowerBounds[0][0],sMax=lowerBounds[lowerBounds.length-1][0],bMin=lowerBounds[lowerBounds.length-1][1],bMax=lowerBounds[0][1];colorDictionary[name]={hueRange:hueRange,lowerBounds:lowerBounds,saturationRange:[sMin,sMax],brightnessRange:[bMin,bMax]}}function loadColorBounds(){defineColor("monochrome",null,[[0,0],[100,0]]);defineColor("red",[-26,18],[[20,100],[30,92],[40,89],[50,85],[60,78],[70,70],[80,60],[90,55],[100,50]]);defineColor("orange",[19,46],[[20,100],[30,93],[40,88],[50,86],[60,85],[70,70],[100,70]]);defineColor("yellow",[47,62],[[25,100],[40,94],[50,89],[60,86],[70,84],[80,82],[90,80],[100,75]]);defineColor("green",[63,178],[[30,100],[40,90],[50,85],[60,81],[70,74],[80,64],[90,50],[100,40]]);defineColor("blue",[179,257],[[20,100],[30,86],[40,80],[50,74],[60,60],[70,52],[80,44],[90,39],[100,35]]);defineColor("purple",[258,282],[[20,100],[30,87],[40,79],[50,70],[60,65],[70,59],[80,52],[90,45],[100,42]]);defineColor("pink",[283,334],[[20,100],[30,90],[40,86],[60,84],[80,80],[90,75],[100,73]])}function HSVtoRGB(hsv){var h=hsv[0];if(h===0){h=1}if(h===360){h=359}h=h/360;var s=hsv[1]/100,v=hsv[2]/100;var h_i=Math.floor(h*6),f=h*6-h_i,p=v*(1-s),q=v*(1-f*s),t=v*(1-(1-f)*s),r=256,g=256,b=256;switch(h_i){case 0:r=v;g=t;b=p;break;case 1:r=q;g=v;b=p;break;case 2:r=p;g=v;b=t;break;case 3:r=p;g=q;b=v;break;case 4:r=t;g=p;b=v;break;case 5:r=v;g=p;b=q;break}var result=[Math.floor(r*255),Math.floor(g*255),Math.floor(b*255)];return result}function HSVtoHSL(hsv){var h=hsv[0],s=hsv[1]/100,v=hsv[2]/100,k=(2-s)*v;return[h,Math.round(s*v/(k<1?k:2-k)*1e4)/100,k/2*100]}function stringToInteger(string){var total=0;for(var i=0;i!==string.length;i++){if(total>=Number.MAX_SAFE_INTEGER)break;total+=string.charCodeAt(i)}return total}return randomColor}); \ No newline at end of file diff --git a/js/lib/stats.min.js b/js/lib/stats.min.js new file mode 100644 index 0000000..61757f3 --- /dev/null +++ b/js/lib/stats.min.js @@ -0,0 +1,5 @@ +// stats.js - http://github.com/mrdoob/stats.js +(function(f,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"===typeof define&&define.amd?define(e):f.Stats=e()})(this,function(){var f=function(){function e(a){c.appendChild(a.dom);return a}function u(a){for(var d=0;dg+1E3&&(r.update(1E3*a/(c-g),100),g=c,a=0,t)){var d=performance.memory;t.update(d.usedJSHeapSize/ +1048576,d.jsHeapSizeLimit/1048576)}return c},update:function(){k=this.end()},domElement:c,setMode:u}};f.Panel=function(e,f,l){var c=Infinity,k=0,g=Math.round,a=g(window.devicePixelRatio||1),r=80*a,h=48*a,t=3*a,v=2*a,d=3*a,m=15*a,n=74*a,p=30*a,q=document.createElement("canvas");q.width=r;q.height=h;q.style.cssText="width:80px;height:48px";var b=q.getContext("2d");b.font="bold "+9*a+"px Helvetica,Arial,sans-serif";b.textBaseline="top";b.fillStyle=l;b.fillRect(0,0,r,h);b.fillStyle=f;b.fillText(e,t,v); +b.fillRect(d,m,n,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d,m,n,p);return{dom:q,update:function(h,w){c=Math.min(c,h);k=Math.max(k,h);b.fillStyle=l;b.globalAlpha=1;b.fillRect(0,0,r,m);b.fillStyle=f;b.fillText(g(h)+" "+e+" ("+g(c)+"-"+g(k)+")",t,v);b.drawImage(q,d+a,m,n-a,p,d,m,n-a,p);b.fillRect(d+n-a,m,a,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d+n-a,m,a,g((1-h/w)*p))}}};return f}); diff --git a/js/mobs.js b/js/mobs.js new file mode 100644 index 0000000..c6e90a0 --- /dev/null +++ b/js/mobs.js @@ -0,0 +1,1000 @@ +//create array of mobs +let mob = []; +//method to populate the array above +const mobs = { + loop() { + let i = mob.length; + while (i--) { + if (mob[i].alive) { + mob[i].do(); + } else { + mob[i].replace(i); //removing mob and replace with body, this is done here to avoid an array index bug with drawing I think + } + } + }, + draw() { + ctx.lineWidth = 2; + let i = mob.length; + while (i--) { + ctx.beginPath(); + const vertices = mob[i].vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1, len = vertices.length; j < len; ++j) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.fillStyle = mob[i].fill; + ctx.strokeStyle = mob[i].stroke; + ctx.fill(); + ctx.stroke(); + } + }, + alert(range) { + range = range * range; + for (let i = 0; i < mob.length; i++) { + if (mob[i].distanceToPlayer2() < range) mob[i].locatePlayer(); + } + }, + startle(amount) { + for (let i = 0; i < mob.length; i++) { + if (!mob[i].seePlayer.yes) { + mob[i].force.x += amount * mob[i].mass * (Math.random() - 0.5); + mob[i].force.y += amount * mob[i].mass * (Math.random() - 0.5); + } + } + }, + //********************************************************************************************** + //********************************************************************************************** + spawn(xPos, yPos, sides, radius, color) { + let i = mob.length; + mob[i] = Matter.Bodies.polygon(xPos, yPos, sides, radius, { + //inertia: Infinity, //prevents rotation + mob: true, + density: 0.001, + //friction: 0, + frictionAir: 0.005, + //frictionStatic: 0, + restitution: 0.5, + collisionFilter: { + group: 0, + category: 0x000010, + mask: 0x001111 + }, + onHit: undefined, + alive: true, + index: i, + health: 1, + accelMag: 0.001, + cd: 0, //game cycle when cooldown will be over + delay: 60, //static: time between cooldowns + fill: color, + stroke: "#000", + seePlayer: { + yes: false, + recall: 0, + position: { + x: xPos, + y: yPos + } + }, + radius: radius, + spawnPos: { + x: xPos, + y: yPos + }, + seeAtDistance2: 4000000, //sqrt(4000000) = 2000 = max seeing range + distanceToPlayer() { + const dx = this.position.x - player.position.x; + const dy = this.position.y - player.position.y; + return Math.sqrt(dx * dx + dy * dy); + }, + distanceToPlayer2() { + const dx = this.position.x - player.position.x; + const dy = this.position.y - player.position.y; + return dx * dx + dy * dy; + }, + gravity() { + this.force.y += this.mass * this.g; + }, + seePlayerFreq: 20 + Math.round(Math.random() * 20), //how often NPC checks to see where player is, lower numbers have better vision + foundPlayer() { + this.locatePlayer(); + if (!this.seePlayer.yes) { + this.alertNearByMobs(); + this.seePlayer.yes = true; + } + }, + lostPlayer() { + this.seePlayer.yes = false; + this.seePlayer.recall -= this.seePlayerFreq; + if (this.seePlayer.recall < 0) this.seePlayer.recall = 0; + }, + memory: 120, //default time to remember player's location + locatePlayer() { + // updates mob's memory of player location + this.seePlayer.recall = this.memory + Math.round(this.memory * Math.random()); //seconds before mob falls a sleep + this.seePlayer.position.x = player.position.x; + this.seePlayer.position.y = player.position.y; + }, + locatePlayerByDist() { + if (this.distanceToPlayer2() < this.locateRange) { + this.locatePlayer(); + } + }, + seePlayerCheck() { + if (!(game.cycle % this.seePlayerFreq)) { + if ( + this.distanceToPlayer2() < this.seeAtDistance2 && + Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && + Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0 + ) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + }, + seePlayerCheckByDistance() { + if (!(game.cycle % this.seePlayerFreq)) { + if (this.distanceToPlayer2() < this.seeAtDistance2) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + }, + seePlayerByDistOrLOS() { + if (!(game.cycle % this.seePlayerFreq)) { + if ( + this.distanceToPlayer2() < this.seeAtDistance2 || + (Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0) + ) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + }, + seePlayerByDistAndLOS() { + if (!(game.cycle % this.seePlayerFreq)) { + if ( + this.distanceToPlayer2() < this.seeAtDistance2 && + (Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0) + ) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + }, + isLookingAtPlayer(threshold) { + const diff = Matter.Vector.normalise(Matter.Vector.sub(player.position, this.position)); + //make a vector for the mob's direction of length 1 + const dir = { + x: Math.cos(this.angle), + y: Math.sin(this.angle) + }; + //the dot product of diff and dir will return how much over lap between the vectors + const dot = Matter.Vector.dot(dir, diff); + // console.log(Math.cos(dot)*180/Math.PI) + if (dot > threshold) { + return true; + } else { + return false; + } + }, + lookRange: 0.2 + Math.random() * 0.2, + lookTorque: 0.0000004 * (Math.random() > 0.5 ? -1 : 1), + seePlayerByLookingAt() { + if (!(game.cycle % this.seePlayerFreq) && (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange))) { + if ( + this.distanceToPlayer2() < this.seeAtDistance2 && + Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && + Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0 + ) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + //if you don't recall player location rotate and draw to show where you are looking + if (!this.seePlayer.recall) { + this.torque = this.lookTorque * this.inertia; + //draw + const range = Math.PI * this.lookRange; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range); + ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true); + ctx.fillStyle = "rgba(0,0,0,0.07)"; + ctx.fill(); + } + }, + mechPosRange() { + return { + x: player.position.x, // + (Math.random() - 0.5) * 50, + y: player.position.y + (Math.random() - 0.5) * 110 + }; + //mob vision for testing + // ctx.beginPath(); + // ctx.lineWidth = "5"; + // ctx.strokeStyle = "#ff0"; + // ctx.moveTo(this.position.x, this.position.y); + // ctx.lineTo(targetPos.x, targetPos.y); + // ctx.stroke(); + // return targetPos; + }, + laserBeam() { + if (game.cycle % 7 && this.seePlayer.yes) { + ctx.setLineDash([125 * Math.random(), 125 * Math.random()]); + // ctx.lineDashOffset = 6*(game.cycle % 215); + if (this.distanceToPlayer() < this.laserRange) { + //if (Math.random()>0.2 && this.seePlayer.yes && this.distanceToPlayer2()<800000) { + mech.damage(0.0004 * game.dmgScale); + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mech.pos.x, mech.pos.y); + ctx.lineTo(mech.pos.x + (Math.random() - 0.5) * 3000, mech.pos.y + (Math.random() - 0.5) * 3000); + ctx.lineWidth = 2; + ctx.strokeStyle = "rgb(255,0,170)"; + ctx.stroke(); + + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(255,0,170,0.15)"; + ctx.fill(); + } + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.laserRange * 0.9, 0, 2 * Math.PI); + ctx.strokeStyle = "rgba(255,0,170,0.5)"; + ctx.lineWidth = 1; + ctx.stroke(); + ctx.setLineDash([]); + } + }, + laser() { + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + }; + if (this.seePlayer.recall) { + this.torque = this.lookTorque * this.inertia * 2; + + const seeRange = 2500; + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const look = { + x: this.position.x + seeRange * Math.cos(this.angle), + y: this.position.y + seeRange * Math.sin(this.angle) + }; + vertexCollision(this.position, look, map); + vertexCollision(this.position, look, body); + vertexCollision(this.position, look, [player]); + // hitting player + if (best.who === player) { + dmg = 0.004 * game.dmgScale; + mech.damage(dmg); + //draw damage + ctx.fillStyle = color; + ctx.beginPath(); + ctx.arc(best.x, best.y, dmg * 2000, 0, 2 * Math.PI); + ctx.fill(); + } + //draw beam + if (best.dist2 === Infinity) { + best = look; + } + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(best.x, best.y); + ctx.strokeStyle = "#f00"; // Purple path + ctx.lineWidth = 1; + ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([0, 0]); + } + }, + searchSpring() { + ctx.beginPath(); + ctx.arc(this.cons.pointA.x, this.cons.pointA.y, 6, 0, 2 * Math.PI); + ctx.arc(this.cons2.pointA.x, this.cons2.pointA.y, 6, 0, 2 * Math.PI); + // ctx.arc(this.cons.bodyB.position.x, this.cons.bodyB.position.y,6,0,2*Math.PI); + ctx.fillStyle = "#222"; + ctx.fill(); + + if (!(game.cycle % this.seePlayerFreq)) { + if ( + (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange)) && + this.distanceToPlayer2() < this.seeAtDistance2 && + Matter.Query.ray(map, this.position, player.position).length === 0 && + Matter.Query.ray(body, this.position, player.position).length === 0 + ) { + this.foundPlayer(); + if (!(game.cycle % (this.seePlayerFreq * 2))) { + this.springTarget.x = this.seePlayer.position.x; + this.springTarget.y = this.seePlayer.position.y; + this.cons.length = -200; + this.cons2.length = 100 + 1.5 * this.radius; + } else { + this.springTarget2.x = this.seePlayer.position.x; + this.springTarget2.y = this.seePlayer.position.y; + this.cons.length = 100 + 1.5 * this.radius; + this.cons2.length = -200; + } + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + //if you don't recall player location rotate and draw to show where you are looking + if (!this.seePlayer.recall) { + this.torque = this.lookTorque * this.inertia; + //draw + const range = Math.PI * this.lookRange; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range); + ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true); + ctx.fillStyle = "rgba(0,0,0,0.07)"; + ctx.fill(); + //spring to random place on map + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + }; + const seeRange = 3000; + if (!(game.cycle % (this.seePlayerFreq * 10))) { + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const look = { + x: this.position.x + seeRange * Math.cos(this.angle), + y: this.position.y + seeRange * Math.sin(this.angle) + }; + vertexCollision(this.position, look, map); + if (best.dist2 != Infinity) { + this.springTarget.x = best.x; + this.springTarget.y = best.y; + this.cons.length = 100 + 1.5 * this.radius; + this.cons2.length = 100 + 1.5 * this.radius; + } + } + if (!((game.cycle + this.seePlayerFreq * 5) % (this.seePlayerFreq * 10))) { + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const look = { + x: this.position.x + seeRange * Math.cos(this.angle), + y: this.position.y + seeRange * Math.sin(this.angle) + }; + vertexCollision(this.position, look, map); + if (best.dist2 != Infinity) { + this.springTarget2.x = best.x; + this.springTarget2.y = best.y; + this.cons.length = 100 + 1.5 * this.radius; + this.cons2.length = 100 + 1.5 * this.radius; + } + } + } + }, + alertNearByMobs() { + //this.alertRange2 is set at the very bottom of this mobs, after mob is made + for (let i = 0; i < mob.length; i++) { + if (!mob[i].seePlayer.recall && Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, mob[i].position)) < this.alertRange2) { + mob[i].locatePlayer(); + } + } + //add alert to draw queue + // game.drawList.push({ + // x: this.position.x, + // y: this.position.y, + // radius: Math.sqrt(this.alertRange2), + // color: "rgba(0,0,0,0.02)", + // time: game.drawTime + // }); + }, + zoom() { + this.zoomMode--; + if (this.zoomMode > 150) { + this.drawTrail(); + if (this.seePlayer.recall) { + //attraction to player + const forceMag = this.accelMag * this.mass; + const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle); + } + } else if (this.zoomMode < 0) { + this.zoomMode = 300; + this.setupTrail(); + } + }, + setupTrail() { + this.trail = []; + for (let i = 0; i < this.trailLength; ++i) { + this.trail.push({ + x: this.position.x, + y: this.position.y + }); + } + }, + drawTrail() { + //dont' forget to run setupTrail() after mob spawn + const t = this.trail; + const len = t.length; + t.pop(); + t.unshift({ + x: this.position.x, + y: this.position.y + }); + //draw + ctx.strokeStyle = this.trailFill; + ctx.beginPath(); + // ctx.moveTo(t[0].x, t[0].y); + // ctx.lineTo(t[0].x, t[0].y); + // ctx.globalAlpha = 0.2; + // ctx.lineWidth = this.radius * 3; + // ctx.stroke(); + ctx.globalAlpha = 0.5 / len; + ctx.lineWidth = this.radius * 1.95; + for (let i = 0; i < len; ++i) { + // ctx.lineWidth *= 0.96; + ctx.lineTo(t[i].x, t[i].y); + ctx.stroke(); + } + ctx.globalAlpha = 1; + }, + // darkness() { + // // var grd = ctx.createRadialGradient(this.position.x, this.position.y, this.eventHorizon/3, this.position.x, this.position.y, this.eventHorizon); + // // grd.addColorStop(0, "rgba(0,0,0,1)"); + // // grd.addColorStop(1, "rgba(0,0,0,0)"); + // // ctx.fillStyle=grd; + // // ctx.beginPath(); + // // ctx.arc(this.position.x, this.position.y, this.eventHorizon, 0, 2 * Math.PI); + // // ctx.fill(); + + // ctx.beginPath(); + // ctx.arc(this.position.x, this.position.y, this.eventHorizon * 0.33, 0, 2 * Math.PI); + // ctx.fillStyle = "rgba(0,0,0,0.7)"; + // ctx.fill(); + // ctx.beginPath(); + // ctx.arc(this.position.x, this.position.y, this.eventHorizon * 0.66, 0, 2 * Math.PI); + // ctx.fillStyle = "rgba(0,0,0,0.4)"; + // ctx.fill(); + // ctx.beginPath(); + // ctx.arc(this.position.x, this.position.y, this.eventHorizon, 0, 2 * Math.PI); + // ctx.fillStyle = "rgba(0,0,0,0.1)"; + // ctx.fill(); + // }, + curl() { + //cause all mobs, and bodies to rotate in a circle + const range = 1000 + + applyCurl = function (center, array) { + for (let i = 0; i < array.length; ++i) { + const sub = Matter.Vector.sub(center, array[i].position) + const radius2 = Matter.Vector.magnitudeSquared(sub); + + //if too close, like center mob or shield, don't curl // if too far don't curl + if (radius2 < range * range && radius2 > 10000) { + const curlVector = Matter.Vector.perp(Matter.Vector.normalise(sub)) + + //apply curl force + const mag = Matter.Vector.mult(curlVector, 10) + Matter.Body.setVelocity(array[i], { + x: array[i].velocity.x * 0.99 + mag.x * 0.01, + y: array[i].velocity.y * 0.99 + mag.y * 0.01 + }) + + //draw curl + ctx.beginPath(); + ctx.moveTo(array[i].position.x, array[i].position.y); + ctx.lineTo(array[i].position.x + curlVector.x * 100, array[i].position.y + curlVector.y * 100); + ctx.lineWidth = 2; + ctx.strokeStyle = "#000"; + ctx.stroke(); + } + } + } + applyCurl(this.position, mob); + applyCurl(this.position, body); + + + //draw limit + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, range, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(55,255,255, 0.1)"; + ctx.fill(); + }, + pullPlayer() { + if (this.seePlayer.yes && Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, player.position)) < 1000000) { + const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + player.force.x -= 1.3 * Math.cos(angle) * (mech.onGround ? 2 * player.mass * game.g : player.mass * game.g); + player.force.y -= 0.97 * player.mass * game.g * Math.sin(angle); + + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mech.pos.x, mech.pos.y); + ctx.lineWidth = Math.min(60, this.radius * 2); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.stroke(); + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.3)"; + ctx.fill(); + } + }, + repelBullets() { + if (this.seePlayer.yes) { + ctx.lineWidth = "8"; + ctx.strokeStyle = this.fill; + ctx.beginPath(); + for (let i = 0, len = bullet.length; i < len; ++i) { + const dx = bullet[i].position.x - this.position.x; + const dy = bullet[i].position.y - this.position.y; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 500) { + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(bullet[i].position.x, bullet[i].position.y); + const angle = Math.atan2(dy, dx); + const mag = (1500 * bullet[i].mass * game.g) / dist; + bullet[i].force.x += mag * Math.cos(angle); + bullet[i].force.y += mag * Math.sin(angle); + } + } + ctx.stroke(); + } + }, + attraction() { + //accelerate towards the player + if (this.seePlayer.recall) { + // && dx * dx + dy * dy < 2000000) { + const forceMag = this.accelMag * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle); + } + }, + repulsionRange: 500000, + repulsion() { + //accelerate towards the player + if (this.seePlayer.recall && this.distanceToPlayer2() < this.repulsionRange) { + // && dx * dx + dy * dy < 2000000) { + const forceMag = this.accelMag * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x -= 2 * forceMag * Math.cos(angle); + this.force.y -= 2 * forceMag * Math.sin(angle); // - 0.0007 * this.mass; //antigravity + } + }, + hop() { + //accelerate towards the player after a delay + if (this.cd < game.cycle && this.seePlayer.recall && this.speed < 1) { + this.cd = game.cycle + this.delay; + const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle) - 0.04 * this.mass; //antigravity + } + }, + hoverOverPlayer() { + if (this.seePlayer.recall) { + // vertical positioning + const rangeY = 250; + if (this.position.y > this.seePlayer.position.y - this.hoverElevation + rangeY) { + this.force.y -= this.accelMag * this.mass; + } else if (this.position.y < this.seePlayer.position.y - this.hoverElevation - rangeY) { + this.force.y += this.accelMag * this.mass; + } + // horizontal positioning + const rangeX = 150; + if (this.position.x > this.seePlayer.position.x + this.hoverXOff + rangeX) { + this.force.x -= this.accelMag * this.mass; + } else if (this.position.x < this.seePlayer.position.x + this.hoverXOff - rangeX) { + this.force.x += this.accelMag * this.mass; + } + } + // else { + // this.gravity(); + // } + }, + grow() { + if (this.seePlayer.recall) { + if (this.radius < 80) { + const scale = 1.01; + Matter.Body.scale(this, scale, scale); + this.radius *= scale; + // this.torque = -0.00002 * this.inertia; + this.fill = `hsl(144, ${this.radius}%, 50%)`; + } + } else { + if (this.radius > 15) { + const scale = 0.99; + Matter.Body.scale(this, scale, scale); + this.radius *= scale; + this.fill = `hsl(144, ${this.radius}%, 50%)`; + } + } + }, + search() { + //be sure to declare searchTarget in mob spawn + //accelerate towards the searchTarget + if (!this.seePlayer.recall) { + const newTarget = function (that) { + if (Math.random() < 0.05) { + that.searchTarget = player.position; //chance to target player + } else { + //target random body + that.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; + } + }; + + const sub = Matter.Vector.sub(this.searchTarget, this.position); + if (Matter.Vector.magnitude(sub) > this.radius * 2) { + // ctx.beginPath(); + // ctx.strokeStyle = "#aaa"; + // ctx.moveTo(this.position.x, this.position.y); + // ctx.lineTo(this.searchTarget.x,this.searchTarget.y); + // ctx.stroke(); + //accelerate at 0.1 of normal acceleration + this.force = Matter.Vector.mult(Matter.Vector.normalise(sub), this.accelMag * this.mass * 0.2); + } else { + //after reaching random target switch to new target + newTarget(this); + } + //switch to a new target after a while + if (!(game.cycle % (this.seePlayerFreq * 15))) { + newTarget(this); + } + } + }, + strike() { + //teleport to player when close enough on CD + if (this.seePlayer.recall && this.cd < game.cycle) { + const dist = Matter.Vector.sub(this.seePlayer.position, this.position); + const distMag = Matter.Vector.magnitude(dist); + if (distMag < 430) { + this.cd = game.cycle + this.delay; + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + Matter.Body.translate(this, Matter.Vector.mult(Matter.Vector.normalise(dist), distMag - 20 - radius)); + ctx.lineTo(this.position.x, this.position.y); + ctx.lineWidth = radius * 2; + ctx.strokeStyle = this.fill; //"rgba(0,0,0,0.5)"; //'#000' + ctx.stroke(); + } + } + }, + blink() { + //teleport towards player as a way to move + if (this.seePlayer.recall && !(game.cycle % this.blinkRate)) { + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + const dist = Matter.Vector.sub(this.seePlayer.position, this.position); + const distMag = Matter.Vector.magnitude(dist); + const unitVector = Matter.Vector.normalise(dist); + const rando = (Math.random() - 0.5) * 50; + if (distMag < this.blinkLength) { + Matter.Body.translate(this, Matter.Vector.mult(unitVector, distMag + rando)); + } else { + Matter.Body.translate(this, Matter.Vector.mult(unitVector, this.blinkLength + rando)); + } + ctx.lineTo(this.position.x, this.position.y); + ctx.lineWidth = radius * 2; + ctx.strokeStyle = this.stroke; //"rgba(0,0,0,0.5)"; //'#000' + ctx.stroke(); + } + }, + drift() { + //teleport towards player as a way to move + if (this.seePlayer.recall && !(game.cycle % this.blinkRate)) { + // && !mech.lookingAtMob(this,0.5)){ + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + const dist = Matter.Vector.sub(this.seePlayer.position, this.position); + const distMag = Matter.Vector.magnitude(dist); + const vector = Matter.Vector.mult(Matter.Vector.normalise(dist), this.blinkLength); + if (distMag < this.blinkLength) { + Matter.Body.setPosition(this, this.seePlayer.position); + Matter.Body.translate(this, { + x: (Math.random() - 0.5) * 50, + y: (Math.random() - 0.5) * 50 + }); + } else { + vector.x += (Math.random() - 0.5) * 200; + vector.y += (Math.random() - 0.5) * 200; + Matter.Body.translate(this, vector); + } + ctx.lineTo(this.position.x, this.position.y); + ctx.lineWidth = radius * 2; + ctx.strokeStyle = this.stroke; + ctx.stroke(); + } + }, + bomb() { + //throw a mob/bullet at player + if ( + !(game.cycle % this.fireFreq) && + Math.abs(this.position.x - this.seePlayer.position.x) < 400 && //above player + Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && //see player + Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0 + ) { + spawn.bullet(this.position.x, this.position.y + this.radius * 0.5, 10 + Math.ceil(this.radius / 15), 5); + //add spin and speed + Matter.Body.setAngularVelocity(mob[mob.length - 1], (Math.random() - 0.5) * 0.5); + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x, + y: this.velocity.y + }); + //spin for mob as well + Matter.Body.setAngularVelocity(this, (Math.random() - 0.5) * 0.25); + } + }, + fire() { + const setNoseShape = () => { + const mag = this.radius + this.radius * this.noseLength; + this.vertices[1].x = this.position.x + Math.cos(this.angle) * mag; + this.vertices[1].y = this.position.y + Math.sin(this.angle) * mag; + }; + //throw a mob/bullet at player + if (this.seePlayer.recall) { + //set direction to turn to fire + if (!(game.cycle % this.seePlayerFreq)) { + this.fireDir = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position)); + this.fireDir.y -= Math.abs(this.seePlayer.position.x - this.position.x) / 1600; //gives the bullet an arc + this.fireAngle = Math.atan2(this.fireDir.y, this.fireDir.x); + } + //rotate towards fireAngle + const angle = this.angle + Math.PI / 2; + c = Math.cos(angle) * this.fireDir.x + Math.sin(angle) * this.fireDir.y; + const threshold = 0.1; + if (c > threshold) { + this.torque += 0.000004 * this.inertia; + } else if (c < -threshold) { + this.torque -= 0.000004 * this.inertia; + } else if (this.noseLength > 1.5) { + //fire + spawn.bullet(this.vertices[1].x, this.vertices[1].y, 5 + Math.ceil(this.radius / 15), 5); + const v = 15; + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + this.fireDir.x * v + Math.random(), + y: this.velocity.y + this.fireDir.y * v + Math.random() + }); + this.noseLength = 0; + // recoil + this.force.x -= 0.005 * this.fireDir.x * this.mass; + this.force.y -= 0.005 * this.fireDir.y * this.mass; + } + if (this.noseLength < 1.5) this.noseLength += this.fireFreq; + setNoseShape(); + } else if (this.noseLength > 0.1) { + this.noseLength -= this.fireFreq / 2; + setNoseShape(); + } + // else if (this.noseLength < -0.1) { + // this.noseLength += this.fireFreq / 4; + // setNoseShape(); + // } + }, + turnToFacePlayer() { + //turn to face player + const dx = player.position.x - this.position.x; + const dy = -player.position.y + this.position.y; + const dist = this.distanceToPlayer(); + const angle = this.angle + Math.PI / 2; + c = Math.cos(angle) * dx - Math.sin(angle) * dy; + // if (c > 0.04) { + // Matter.Body.rotate(this, 0.01); + // } else if (c < 0.04) { + // Matter.Body.rotate(this, -0.01); + // } + if (c > 0.04 * dist) { + this.torque += 0.002 * this.mass; + } else if (c < 0.04) { + this.torque -= 0.002 * this.mass; + } + }, + facePlayer() { + const unitVector = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position)); + const angle = Math.atan2(unitVector.y, unitVector.x); + Matter.Body.setAngle(this, angle - Math.PI); + }, + explode() { + mech.damage(Math.min(Math.max(0.02 * Math.sqrt(this.mass), 0.05), 0.35) * game.dmgScale); + this.dropPowerUp = false; + this.death(); //death with no power up or body + }, + timeLimit() { + this.timeLeft--; + if (this.timeLeft < 0) { + this.dropPowerUp = false; + this.death(); //death with no power up + } + }, + healthBar() { + //draw health bar + if (this.seePlayer.recall) { + // && this.health < 1 + const h = this.radius * 0.3; + const w = this.radius * 2; + const x = this.position.x - w / 2; + const y = this.position.y - w * 0.7; + ctx.fillStyle = "rgba(100, 100, 100, 0.3)"; + ctx.fillRect(x, y, w, h); + ctx.fillStyle = "rgba(255,0,0,0.7)"; + ctx.fillRect(x, y, w * this.health, h); + } + }, + damage(dmg) { + this.health -= dmg / Math.sqrt(this.mass); + //this.fill = this.color + this.health + ')'; + if (this.health < 0.1) this.death(); + this.onDamage(this); //custom damage effects + }, + onDamage() { + // a placeholder for custom effects on mob damage + //to use declare custom method in mob spawn + }, + onDeath() { + // a placeholder for custom effects on mob death + //to use declare custom method in mob spawn + }, + leaveBody: true, + dropPowerUp: true, + death() { + this.onDeath(this); //custom death effects + this.removeConsBB(); + this.alive = false; + if (this.dropPowerUp) powerUps.spawnRandomPowerUp(this.position.x, this.position.y, this.mass, radius); + }, + removeConsBB() { + for (let i = 0, len = consBB.length; i < len; ++i) { + if (consBB[i].bodyA === this) { + if (consBB[i].bodyB.shield) { + consBB[i].bodyB.do = function () { + this.death(); + }; + } + consBB[i].bodyA = consBB[i].bodyB; + consBB.splice(i, 1); + this.removeConsBB(); + break; + } else if (consBB[i].bodyB === this) { + if (consBB[i].bodyA.shield) { + consBB[i].bodyA.do = function () { + this.death(); + }; + } + consBB[i].bodyB = consBB[i].bodyA; + consBB.splice(i, 1); + this.removeConsBB(); + break; + } + } + }, + removeCons() { + for (let i = 0, len = cons.length; i < len; ++i) { + if (cons[i].bodyA === this) { + cons[i].bodyA = cons[i].bodyB; + cons.splice(i, 1); + this.removeCons(); + break; + } else if (cons[i].bodyB === this) { + cons[i].bodyB = cons[i].bodyA; + cons.splice(i, 1); + this.removeCons(); + break; + } + } + }, + //replace dead mob with a regular body + replace(i) { + if (this.leaveBody) { + const len = body.length; + body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, this.vertices); + Matter.Body.setVelocity(body[len], this.velocity); + Matter.Body.setAngularVelocity(body[len], this.angularVelocity); + body[len].collisionFilter.category = 0x000001; + body[len].collisionFilter.mask = 0x011111; + // body[len].collisionFilter.category = body[len].collisionFilter.category //0x000001; + // body[len].collisionFilter.mask = body[len].collisionFilter.mask //0x011111; + + //large mobs or too many bodyes go intangible and fall until removed from game to help performance + if (body[len].mass > 10 || 40 + 30 * Math.random() < body.length) { + body[len].collisionFilter.mask = 0x000100; + } + body[len].classType = "body"; + World.add(engine.world, body[len]); //add to world + } + Matter.World.remove(engine.world, this); + mob.splice(i, 1); + } + }); + mob[i].alertRange2 = Math.pow(mob[i].radius * 3 + 200, 2); + World.add(engine.world, mob[i]); //add to world + } +}; \ No newline at end of file diff --git a/js/player.js b/js/player.js new file mode 100644 index 0000000..27bd7cf --- /dev/null +++ b/js/player.js @@ -0,0 +1,1211 @@ +//global player variables for use in matter.js physics +let player, jumpSensor, playerBody, playerHead, headSensor; + +// player Object Prototype ********************************************* +const mech = { + spawn() { + //load player in matter.js physic engine + let vector = Vertices.fromPath("0 40 50 40 50 115 0 115 30 130 20 130"); //player as a series of vertices + playerBody = Matter.Bodies.fromVertices(0, 0, vector); + jumpSensor = Bodies.rectangle(0, 46, 36, 6, { + //this sensor check if the player is on the ground to enable jumping + sleepThreshold: 99999999999, + isSensor: true + }); + vector = Vertices.fromPath("16 -82 2 -66 2 -37 43 -37 43 -66 30 -82"); + playerHead = Matter.Bodies.fromVertices(0, -55, vector); //this part of the player lowers on crouch + headSensor = Bodies.rectangle(0, -57, 48, 45, { + //senses if the player's head is empty and can return after crouching + sleepThreshold: 99999999999, + isSensor: true + }); + player = Body.create({ + //combine jumpSensor and playerBody + parts: [playerBody, playerHead, jumpSensor, headSensor], + inertia: Infinity, //prevents player rotation + friction: 0.002, + frictionAir: 0.001, + //frictionStatic: 0.5, + restitution: 0, + sleepThreshold: Infinity, + collisionFilter: { + group: 0, + category: 0x001000, + mask: 0x010011 + }, + death() { + mech.death(); + } + }); + Matter.Body.setMass(player, mech.mass); + World.add(engine.world, [player]); + + mech.holdConstraint = Constraint.create({ + //holding body constraint + pointA: { + x: 0, + y: 0 + }, + bodyB: jumpSensor, //setting constraint to jump sensor because it has to be on something until the player picks up things + stiffness: 0.4 + }); + World.add(engine.world, mech.holdConstraint); + }, + width: 50, + radius: 30, + fillColor: "#fff", + fillColorDark: "#ccc", + height: 42, + yOffWhen: { + crouch: 22, + stand: 49, + jump: 70 + }, + mass: 5, + Fx: 0.015, //run Force on ground + FxAir: 0.015, //run Force in Air + definePlayerMass(mass = 5) { + Matter.Body.setMass(player, mass); + //reduce air and ground move forces + this.Fx = 0.075 / mass + this.FxAir = 0.375 / mass / mass + //make player stand a bit lower when holding heavy masses + this.yOffWhen.stand = Math.max(this.yOffWhen.crouch, Math.min(49, 49 - (mass - 5) * 6)) + if (this.onGround && !this.crouch) this.yOffGoal = this.yOffWhen.stand; + }, + yOff: 70, + yOffGoal: 70, + onGround: false, //checks if on ground or in air + standingOn: undefined, + numTouching: 0, + crouch: false, + isHeadClear: true, + spawnPos: { + x: 0, + y: 0 + }, + spawnVel: { + x: 0, + y: 0 + }, + pos: { + x: 0, + y: 0 + }, + setPosToSpawn(xPos, yPos) { + this.spawnPos.x = this.pos.x = xPos; + this.spawnPos.y = this.pos.y = yPos; + this.transX = this.transSmoothX = canvas.width2 - this.pos.x; + this.transY = this.transSmoothY = canvas.height2 - this.pos.y; + this.Vx = this.spawnVel.x; + this.Vy = this.spawnVel.y; + player.force.x = 0; + player.force.y = 0; + Matter.Body.setPosition(player, this.spawnPos); + Matter.Body.setVelocity(player, this.spawnVel); + }, + Sy: 0, //adds a smoothing effect to vertical only + Vx: 0, + Vy: 0, + jumpForce: 0.38, + gravity: 0.0019, + friction: { + ground: 0.01, + air: 0.0025 + }, + angle: 0, + walk_cycle: 0, + stepSize: 0, + flipLegs: -1, + hip: { + x: 12, + y: 24 + }, + knee: { + x: 0, + y: 0, + x2: 0, + y2: 0 + }, + foot: { + x: 0, + y: 0 + }, + legLength1: 55, + legLength2: 45, + transX: 0, + transY: 0, + move() { + this.pos.x = player.position.x; + this.pos.y = playerBody.position.y - this.yOff; + this.Vx = player.velocity.x; + this.Vy = player.velocity.y; + }, + transSmoothX: 0, + transSmoothY: 0, + lastGroundedPositionY: 0, + // mouseZoom: 0, + look() { + //always on mouse look + this.angle = Math.atan2( + game.mouseInGame.y - this.pos.y, + game.mouseInGame.x - this.pos.x + ); + //smoothed mouse look translations + const scale = 0.8; + this.transSmoothX = canvas.width2 - this.pos.x - (game.mouse.x - canvas.width2) * scale; + this.transSmoothY = canvas.height2 - this.pos.y - (game.mouse.y - canvas.height2) * scale; + + this.transX += (this.transSmoothX - this.transX) * 0.07; + this.transY += (this.transSmoothY - this.transY) * 0.07; + }, + gamepadLook() { + this.angle = Math.atan2( + game.gamepad.rightAxis.y, + game.gamepad.rightAxis.x + ); + // this.transX += (canvas.width2 - this.pos.x - this.transX) * 0.07 - game.gamepad.rightAxis.x * 12; + // this.transY += (canvas.height2 - this.pos.y - this.transY) * 0.03 - game.gamepad.rightAxis.y * 6; + this.transX += (canvas.width2 - this.pos.x - this.transX) * 0.02 - game.gamepad.leftAxis.x * 6; + this.transY += (canvas.height2 - this.pos.y - this.transY) * 0.02 + game.gamepad.leftAxis.y * 8; + }, + doCrouch() { + if (!this.crouch) { + this.crouch = true; + this.yOffGoal = this.yOffWhen.crouch; + Matter.Body.translate(playerHead, { + x: 0, + y: 40 + }); + } + }, + undoCrouch() { + if (this.crouch) { + this.crouch = false; + this.yOffGoal = this.yOffWhen.stand; + Matter.Body.translate(playerHead, { + x: 0, + y: -40 + }); + } + }, + hardLandCD: 0, + enterAir() { + //triggered in engine.js on collision + this.onGround = false; + this.hardLandCD = 0 // disable hard landing + if (this.isHeadClear) { + if (this.crouch) { + this.undoCrouch(); + } + this.yOffGoal = this.yOffWhen.jump; + } + }, + //triggered in engine.js on collision + enterLand() { + this.onGround = true; + if (this.crouch) { + if (this.isHeadClear) { + this.undoCrouch(); + } else { + this.yOffGoal = this.yOffWhen.crouch; + } + } else { + //sets a hard land where player stays in a crouch for a bit and can't jump + //crouch is forced in keyMove() on ground section below + const momentum = player.velocity.y * player.mass //player mass is 5 so this triggers at 20 down velocity, unless the player is holding something + if (momentum > 100) { + this.doCrouch(); + this.yOff = this.yOffWhen.jump; + this.hardLandCD = game.cycle + Math.min(momentum / 6 - 6, 40) + } else { + this.yOffGoal = this.yOffWhen.stand; + } + } + }, + buttonCD_jump: 0, //cool down for player buttons + keyMove() { + if (this.onGround) { //on ground ********************** + if (this.crouch) { + if (!(keys[83] || keys[40]) && this.isHeadClear && this.hardLandCD < game.cycle) this.undoCrouch(); + } else if (keys[83] || keys[40] || this.hardLandCD > game.cycle) { + this.doCrouch(); //on ground && not crouched and pressing s or down + } else if ((keys[87] || keys[38]) && this.buttonCD_jump + 20 < game.cycle && this.yOffWhen.stand > 23) { + this.buttonCD_jump = game.cycle; //can't jump again until 20 cycles pass + + //apply a fraction of the jump force to the body the player is jumping off of + Matter.Body.applyForce(mech.standingOn, mech.pos, { + x: 0, + y: this.jumpForce * 0.12 * Math.min(mech.standingOn.mass, 5) + }); + + player.force.y = -this.jumpForce; //player jump force + Matter.Body.setVelocity(player, { //zero player y-velocity for consistent jumps + x: player.velocity.x, + y: 0 + }); + } + + //horizontal move on ground + //apply a force to move + if (keys[65] || keys[37]) { //left / a + player.force.x -= this.Fx + if (player.velocity.x > -2) player.force.x -= this.Fx * 0.5 + } else if (keys[68] || keys[39]) { //right / d + player.force.x += this.Fx + if (player.velocity.x < 2) player.force.x += this.Fx * 0.5 + } else { + const stoppingFriction = 0.92; + Matter.Body.setVelocity(player, { + x: player.velocity.x * stoppingFriction, + y: player.velocity.y * stoppingFriction + }); + } + //come to a stop if fast or if no move key is pressed + if (player.speed > 4) { + const stoppingFriction = (this.crouch) ? 0.7 : 0.89; + Matter.Body.setVelocity(player, { + x: player.velocity.x * stoppingFriction, + y: player.velocity.y * stoppingFriction + }); + } + + } else { // in air ********************************** + //check for short jumps + if ( + this.buttonCD_jump + 60 > game.cycle && //just pressed jump + !(keys[87] || keys[38]) && //but not pressing jump key + this.Vy < 0 //moving up + ) { + Matter.Body.setVelocity(player, { + //reduce player y-velocity every cycle + x: player.velocity.x, + y: player.velocity.y * 0.94 + }); + } + const limit = 125 / player.mass / player.mass + if (keys[65] || keys[37]) { + if (player.velocity.x > -limit) player.force.x -= this.FxAir; // move player left / a + } else if (keys[68] || keys[39]) { + if (player.velocity.x < limit) player.force.x += this.FxAir; //move player right / d + } + } + + //smoothly move leg height towards height goal + this.yOff = this.yOff * 0.85 + this.yOffGoal * 0.15; + }, + gamepadMove() { + if (this.onGround) { //on ground ********************** + if (this.crouch) { + if (game.gamepad.leftAxis.y !== -1 && this.isHeadClear && this.hardLandCD < game.cycle) this.undoCrouch(); + } else if (game.gamepad.leftAxis.y === -1 || this.hardLandCD > game.cycle) { + this.doCrouch(); //on ground && not crouched and pressing s or down + } else if (game.gamepad.jump && this.buttonCD_jump + 20 < game.cycle && this.yOffWhen.stand > 23) { + this.buttonCD_jump = game.cycle; //can't jump again until 20 cycles pass + + //apply a fraction of the jump force to the body the player is jumping off of + Matter.Body.applyForce(mech.standingOn, mech.pos, { + x: 0, + y: this.jumpForce * 0.12 * Math.min(mech.standingOn.mass, 5) + }); + + player.force.y = -this.jumpForce; //player jump force + Matter.Body.setVelocity(player, { //zero player y-velocity for consistent jumps + x: player.velocity.x, + y: 0 + }); + } + + //horizontal move on ground + //apply a force to move + if (game.gamepad.leftAxis.x === -1) { //left / a + player.force.x -= this.Fx + if (player.velocity.x > -2) player.force.x -= this.Fx * 0.5 + } else if (game.gamepad.leftAxis.x === 1) { //right / d + player.force.x += this.Fx + if (player.velocity.x < 2) player.force.x += this.Fx * 0.5 + } else { + const stoppingFriction = 0.92; + Matter.Body.setVelocity(player, { + x: player.velocity.x * stoppingFriction, + y: player.velocity.y * stoppingFriction + }); + } + //come to a stop if fast or if no move key is pressed + if (player.speed > 4) { + const stoppingFriction = (this.crouch) ? 0.7 : 0.89; + Matter.Body.setVelocity(player, { + x: player.velocity.x * stoppingFriction, + y: player.velocity.y * stoppingFriction + }); + } + + } else { // in air ********************************** + //check for short jumps + if ( + this.buttonCD_jump + 60 > game.cycle && //just pressed jump + !game.gamepad.jump && //but not pressing jump key + this.Vy < 0 //moving up + ) { + Matter.Body.setVelocity(player, { + //reduce player y-velocity every cycle + x: player.velocity.x, + y: player.velocity.y * 0.94 + }); + } + const limit = 125 / player.mass / player.mass + if (game.gamepad.leftAxis.x === -1) { + if (player.velocity.x > -limit) player.force.x -= this.FxAir; // move player left / a + } else if (game.gamepad.leftAxis.x === 1) { + if (player.velocity.x < limit) player.force.x += this.FxAir; //move player right / d + } + } + + //smoothly move leg height towards height goal + this.yOff = this.yOff * 0.85 + this.yOffGoal * 0.15; + }, + alive: true, + death() { + if (this.alive) { + this.alive = false; + game.paused = true; + this.health = 0; + this.displayHealth(); + document.getElementById("text-log").style.opacity = 0; //fade out any active text logs + document.getElementById("fade-out").style.opacity = 1; //slowly fades out + setTimeout(function () { + game.splashReturn(); + }, 5000); + } + }, + health: 0, + // regen() { + // if (this.health < 1 && game.cycle % 15 === 0) { + // this.addHealth(0.01); + // } + // }, + drawHealth() { + if (this.health < 1) { + ctx.fillStyle = "rgba(100, 100, 100, 0.5)"; + ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, 60, 10); + ctx.fillStyle = "#f00"; + ctx.fillRect( + this.pos.x - this.radius, + this.pos.y - 50, + 60 * this.health, + 10 + ); + } + }, + displayHealth() { + id = document.getElementById("health"); + id.style.width = Math.floor(300 * this.health) + "px"; + //css animation blink if health is low + if (this.health < 0.3) { + id.classList.add("low-health"); + } else { + id.classList.remove("low-health"); + } + }, + addHealth(heal) { + this.health += heal; + if (this.health > 1) this.health = 1; + // document.getElementById("health").setAttribute("width", 225 * this.health); + this.displayHealth(); + }, + defaultFPSCycle: 0, //tracks when to return to normal fps + damage(dmg) { + if (dmg * player.mass > 0.35) { + this.drop(); //drop block if holding + } + this.health -= dmg; + if (this.health < 0) { + this.health = 0; + this.death(); + return; + } + this.displayHealth(); + + // freeze game and display a full screen red color + if (dmg > 0.05) { + game.fpsCap = 4 //40 - Math.min(25, 100 * dmg) + game.fpsInterval = 1000 / game.fpsCap; + } else { + game.fpsCap = 72 + game.fpsInterval = 1000 / game.fpsCap; + } + document.getElementById("dmg").style.transition = "opacity 0s"; + document.getElementById("dmg").style.opacity = 0.1 + Math.min(0.6, dmg * 4); + mech.defaultFPSCycle = game.cycle + const normalFPS = function () { + if (mech.defaultFPSCycle < game.cycle) { //back to default values + game.fpsCap = 72 + game.fpsInterval = 1000 / game.fpsCap; + document.getElementById("dmg").style.transition = "opacity 1s"; + document.getElementById("dmg").style.opacity = "0"; + } else { + requestAnimationFrame(normalFPS); + } + }; + requestAnimationFrame(normalFPS); + }, + damageImmune: 0, + hitMob(i, dmg) { + //prevents damage happening too quick + }, + buttonCD: 0, //cooldown for player buttons + usePowerUp(i) { + powerUp[i].effect(); + Matter.World.remove(engine.world, powerUp[i]); + powerUp.splice(i, 1); + }, + // ********************************************* + // **************** holding ******************** + // ********************************************* + closest: { + dist: 1000, + index: 0 + }, + isHolding: false, + throwCharge: 0, + fireCDcycle: 0, + fieldCDcycle: 0, + fieldMode: 0, //basic field mode before upgrades + // these values are set on reset by setHoldDefaults() + fieldMeter: 0, + fieldRegen: 0, + fieldMode: 0, + holdingMassScale: 0, + throwChargeRate: 0, + throwChargeMax: 0, + fieldFireCD: 0, + fieldDamage: 0, + grabRange: 0, + fieldArc: 0, + fieldThreshold: 0, + calculateFieldThreshold() { + this.fieldThreshold = Math.cos(this.fieldArc * Math.PI) + }, + setHoldDefaults() { + this.fieldMeter = 1; + this.fieldRegen = 0.0015; + this.fieldCDcycle = 0; + this.holdingMassScale = 0.5; + this.throwChargeRate = 2; + this.throwChargeMax = 50; + this.fieldFireCD = 15; + this.fieldDamage = 0; // a value of 1.0 kills a small mob in 2-3 hits on level 1 + this.grabRange = 175; + this.fieldArc = 0.2; + this.calculateFieldThreshold(); + this.jumpForce = 0.38; + this.Fx = 0.015; //run Force on ground + this.FxAir = 0.015; //run Force in Air + this.gravity = 0.0019; + // this.phaseBlocks(0x011111) + }, + drawFieldMeter() { + if (this.fieldMeter < 1) { + mech.fieldMeter += mech.fieldRegen; + ctx.fillStyle = "rgba(0, 0, 0, 0.4)"; + ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, 60, 10); + 110, 170, 200 + ctx.fillStyle = "rgb(50,220,255)"; + ctx.fillRect( + this.pos.x - this.radius, + this.pos.y - 50, + 60 * this.fieldMeter, + 10 + ); + } else { + mech.fieldMeter = 1 + } + }, + lookingAt(who) { + //calculate a vector from body to player and make it length 1 + const diff = Matter.Vector.normalise(Matter.Vector.sub(who.position, mech.pos)); + //make a vector for the player's direction of length 1 + const dir = { + x: Math.cos(mech.angle), + y: Math.sin(mech.angle) + }; + //the dot product of diff and dir will return how much over lap between the vectors + // console.log(Matter.Vector.dot(dir, diff)) + if (Matter.Vector.dot(dir, diff) > this.fieldThreshold) { + return true; + } + return false; + }, + drop() { + if (this.isHolding) { + this.isHolding = false; + this.definePlayerMass() + this.holdingTarget.collisionFilter.category = 0x000001; + this.holdingTarget.collisionFilter.mask = 0x011111; + this.holdingTarget = null; + this.throwCharge = 0; + } + }, + drawHold(target, stroke = true) { + const eye = 15; + const len = target.vertices.length - 1; + ctx.fillStyle = "rgba(110,170,200," + (0.2 + 0.4 * Math.random()) + ")"; + ctx.lineWidth = 1; + ctx.strokeStyle = "#000"; + ctx.beginPath(); + ctx.moveTo( + mech.pos.x + eye * Math.cos(this.angle), + mech.pos.y + eye * Math.sin(this.angle) + ); + ctx.lineTo(target.vertices[len].x, target.vertices[len].y); + ctx.lineTo(target.vertices[0].x, target.vertices[0].y); + ctx.fill(); + if (stroke) ctx.stroke(); + for (let i = 0; i < len; i++) { + ctx.beginPath(); + ctx.moveTo( + mech.pos.x + eye * Math.cos(this.angle), + mech.pos.y + eye * Math.sin(this.angle) + ); + ctx.lineTo(target.vertices[i].x, target.vertices[i].y); + ctx.lineTo(target.vertices[i + 1].x, target.vertices[i + 1].y); + ctx.fill(); + if (stroke) ctx.stroke(); + } + }, + holding() { + this.fieldMeter -= this.fieldRegen; + Matter.Body.setPosition(this.holdingTarget, { + x: mech.pos.x + 70 * Math.cos(this.angle), + y: mech.pos.y + 70 * Math.sin(this.angle) + }); + Matter.Body.setVelocity(this.holdingTarget, player.velocity); + Matter.Body.rotate(this.holdingTarget, 0.01 / this.holdingTarget.mass); //gently spin the block + }, + throw () { + if ((keys[32] || game.mouseDownRight)) { + if (this.fieldMeter > 0.002) { + this.fieldMeter -= 0.002; + this.throwCharge += this.throwChargeRate;; + //draw charge + const x = mech.pos.x + 15 * Math.cos(this.angle); + const y = mech.pos.y + 15 * Math.sin(this.angle); + const len = this.holdingTarget.vertices.length - 1; + const edge = this.throwCharge * this.throwCharge * 0.02; + const grd = ctx.createRadialGradient(x, y, edge, x, y, edge + 5); + grd.addColorStop(0, "rgba(255,50,150,0.3)"); + grd.addColorStop(1, "transparent"); + ctx.fillStyle = grd; + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(this.holdingTarget.vertices[len].x, this.holdingTarget.vertices[len].y); + ctx.lineTo(this.holdingTarget.vertices[0].x, this.holdingTarget.vertices[0].y); + ctx.fill(); + for (let i = 0; i < len; i++) { + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(this.holdingTarget.vertices[i].x, this.holdingTarget.vertices[i].y); + ctx.lineTo(this.holdingTarget.vertices[i + 1].x, this.holdingTarget.vertices[i + 1].y); + ctx.fill(); + } + } else { + this.drop() + } + } else if (this.throwCharge > 0) { + //throw the body + this.fireCDcycle = game.cycle + this.fieldFireCD; + this.isHolding = false; + //bullet-like collisions + this.holdingTarget.collisionFilter.category = 0x000100; + this.holdingTarget.collisionFilter.mask = 0x111111; + //check every second to see if player is away from thrown body, and make solid + const solid = function (that) { + const dx = that.position.x - player.position.x; + const dy = that.position.y - player.position.y; + if (dx * dx + dy * dy > 10000 && that.speed < 3 && that !== mech.holdingTarget) { + that.collisionFilter.category = 0x000001; //make solid + that.collisionFilter.mask = 0x011111; + } else { + setTimeout(solid, 50, that); + } + }; + setTimeout(solid, 400, this.holdingTarget); + //throw speed scales a bit with mass + const speed = Math.min(85, Math.min(54 / this.holdingTarget.mass + 5, 48) * Math.min(this.throwCharge, this.throwChargeMax) / 50); + + this.throwCharge = 0; + Matter.Body.setVelocity(this.holdingTarget, { + x: player.velocity.x + Math.cos(this.angle) * speed, + y: player.velocity.y + Math.sin(this.angle) * speed + }); + //player recoil //stronger in x-dir to prevent jump hacking + Matter.Body.setVelocity(player, { + x: player.velocity.x - Math.cos(this.angle) * speed / 20 * Math.sqrt(this.holdingTarget.mass), + y: player.velocity.y - Math.sin(this.angle) * speed / 80 * Math.sqrt(this.holdingTarget.mass) + }); + this.definePlayerMass() //return to normal player mass + } + }, + drawField() { + //draw field + const range = this.grabRange - 20; + ctx.beginPath(); + ctx.arc(this.pos.x, this.pos.y, range, this.angle - Math.PI * this.fieldArc, this.angle + Math.PI * this.fieldArc, false); + let eye = 13; + ctx.lineTo(mech.pos.x + eye * Math.cos(this.angle), mech.pos.y + eye * Math.sin(this.angle)); + if (this.holdingTarget) { + ctx.fillStyle = "rgba(110,170,200," + (0.05 + 0.1 * Math.random()) + ")"; + } else { + ctx.fillStyle = "rgba(110,170,200," + (0.15 + 0.15 * Math.random()) + ")"; + } + ctx.fill(); + //draw random lines in field for cool effect + let offAngle = this.angle + 2 * Math.PI * this.fieldArc * (Math.random() - 0.5); + ctx.beginPath(); + eye = 15; + ctx.moveTo(mech.pos.x + eye * Math.cos(this.angle), mech.pos.y + eye * Math.sin(this.angle)); + ctx.lineTo(this.pos.x + range * Math.cos(offAngle), this.pos.y + range * Math.sin(offAngle)); + ctx.strokeStyle = "rgba(120,170,255,0.4)"; + ctx.stroke(); + }, + grabPowerUp() { + //look for power ups to grab + if (mech.fieldCDcycle < game.cycle) { + const grabPowerUpRange2 = (this.grabRange + 200) * (this.grabRange + 200) + for (let i = 0, len = powerUp.length; i < len; ++i) { + const dxP = mech.pos.x - powerUp[i].position.x; + const dyP = mech.pos.y - powerUp[i].position.y; + const dist2 = dxP * dxP + dyP * dyP; + + // float towards player if looking at and in range or if very close to player + if (dist2 < grabPowerUpRange2 && this.lookingAt(powerUp[i]) || dist2 < 14000) { + this.fieldMeter -= this.fieldRegen * 0.5; + powerUp[i].force.x += 7 * (dxP / dist2) * powerUp[i].mass; + powerUp[i].force.y += 7 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * game.g; //negate gravity + //extra friction + Matter.Body.setVelocity(powerUp[i], { + x: powerUp[i].velocity.x * 0.4, + y: powerUp[i].velocity.y * 0.4 + }); + if (dist2 < 5000) { //use power up if it is close enough + //player knockback + Matter.Body.setVelocity(player, { + x: player.velocity.x + ((powerUp[i].velocity.x * powerUp[i].mass) / player.mass) * 0.2, + y: player.velocity.y + ((powerUp[i].velocity.y * powerUp[i].mass) / player.mass) * 0.2 + }); + mech.usePowerUp(i); + // this.fireCDcycle = game.cycle + 10; //cool down + return; + } + // return; + } + } + } + }, + pushMobs() { + // push all mobs in range + for (let i = 0, len = mob.length; i < len; ++i) { + if (this.lookingAt(mob[i]) && Matter.Vector.magnitude(Matter.Vector.sub(mob[i].position, this.pos)) < this.grabRange && Matter.Query.ray(map, mob[i].position, this.pos).length === 0) { + const fieldBlockCost = Math.max(0.02, mob[i].mass * 0.02) + if (this.fieldMeter > fieldBlockCost) { + this.fieldMeter -= fieldBlockCost; + if (this.fieldDamage) mob[i].damage(b.dmgScale * this.fieldDamage); + mob[i].locatePlayer(); + this.drawHold(mob[i]); + //mob and player knock back + const angle = Math.atan2(player.position.y - mob[i].position.y, player.position.x - mob[i].position.x); + const mass = Math.min(Math.sqrt(mob[i].mass), 6); + // console.log(mob[i].mass, Math.sqrt(mob[i].mass), mass) + Matter.Body.setVelocity(mob[i], { + x: player.velocity.x - (15 * Math.cos(angle)) / mass, + y: player.velocity.y - (15 * Math.sin(angle)) / mass + }); + Matter.Body.setVelocity(player, { + x: player.velocity.x + 5 * Math.cos(angle) * mass, + y: player.velocity.y + 5 * Math.sin(angle) * mass + }); + } + } + } + }, + lookForPickUp(range = this.grabRange) { //find body to pickup + this.fieldMeter -= this.fieldRegen; + const grabbing = { + targetIndex: null, + targetRange: range, + // lookingAt: false //false to pick up object in range, but not looking at + }; + for (let i = 0, len = body.length; i < len; ++i) { + if (Matter.Query.ray(map, body[i].position, this.pos).length === 0) { + //is this next body a better target then my current best + const dist = Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)); + const looking = this.lookingAt(body[i]); + // if (dist < grabbing.targetRange && (looking || !grabbing.lookingAt) && !body[i].isNotHoldable) { + if (dist < grabbing.targetRange && looking && !body[i].isNotHoldable) { + grabbing.targetRange = dist; + grabbing.targetIndex = i; + // grabbing.lookingAt = looking; + } + } + } + // set pick up target for when mouse is released + if (body[grabbing.targetIndex]) { + this.holdingTarget = body[grabbing.targetIndex]; + // + ctx.beginPath(); //draw on each valid body + let vertices = this.holdingTarget.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.fillStyle = "rgba(190,215,230," + (0.3 + 0.7 * Math.random()) + ")"; + ctx.fill(); + + ctx.globalAlpha = 0.2; + this.drawHold(this.holdingTarget); + ctx.globalAlpha = 1; + } else { + this.holdingTarget = null; + } + }, + pickUp() { + //triggers when a hold target exits and field button is released + this.isHolding = true; + if (this.holdingTarget) { + this.holdingTarget.collisionFilter.category = 0x000001; + this.holdingTarget.collisionFilter.mask = 0x111111; + } + //combine momentum + const px = player.velocity.x * player.mass + this.holdingTarget.velocity.x * this.holdingTarget.mass; + const py = player.velocity.y * player.mass + this.holdingTarget.velocity.y * this.holdingTarget.mass; + Matter.Body.setVelocity(player, { + x: px / (player.mass + this.holdingTarget.mass), + y: py / (player.mass + this.holdingTarget.mass) + }); + this.definePlayerMass(5 + this.holdingTarget.mass * this.holdingMassScale) + //collide with nothing + this.holdingTarget.collisionFilter.category = 0x000000; + this.holdingTarget.collisionFilter.mask = 0x000000; + }, + hold() {}, + fieldUpgrades: [ + () => { + mech.fieldMode = 0; + mech.setHoldDefaults(); + mech.hold = function () { + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed + mech.drawField(); + mech.grabPowerUp(); + mech.pushMobs(); + mech.lookForPickUp(); + } else if (mech.holdingTarget && mech.fireCDcycle < game.cycle) { //holding, but field button is released + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + mech.drawFieldMeter() + } + }, + () => { + mech.fieldMode = 1; + game.makeTextLog("

Time Dilation Field


active ability: hold left and right mouse to slow time
passive bonus: +field regeneration", 1000); //
passive bonus: can phase through blocks + mech.setHoldDefaults(); + mech.fieldRegen = 0.01; //0.0015 + mech.hold = function () { + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if (game.mouseDown && (keys[32] || game.mouseDownRight) && mech.fieldCDcycle < game.cycle) { //both mouse keys down + if (mech.fieldMeter > mech.fieldRegen * 1.15) { + mech.fieldMeter -= mech.fieldRegen * 1.15; + const range = 900; + //draw slow field + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, range, 0, 2 * Math.PI); + ctx.fillStyle = "#f5f5ff"; + ctx.globalCompositeOperation = "difference"; + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + + function slow(who, friction = 0) { + for (let i = 0, len = who.length; i < len; ++i) { + dist = Matter.Vector.magnitude(Matter.Vector.sub(who[i].position, mech.pos)) + if (dist < range) { + Matter.Body.setAngularVelocity(who[i], who[i].angularVelocity * friction) + Matter.Body.setVelocity(who[i], { + x: who[i].velocity.x * friction, + y: who[i].velocity.y * friction + }); + } + } + } + slow(mob); + slow(body); + slow(bullet); + } else { + mech.fieldCDcycle = game.cycle + 120; + } + } else if ((keys[32] || game.mouseDownRight) && mech.fieldMeter > 0.1) { //field button is pressed + mech.drawField(); + mech.grabPowerUp(); + mech.pushMobs(); + mech.lookForPickUp(130); + } else if (mech.holdingTarget && mech.fireCDcycle < game.cycle) { //holding, but field button is released + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + mech.drawFieldMeter() + } + }, + () => { + mech.fieldMode = 2; + game.makeTextLog("

Kinetic Energy Field


passive bonus: +field emitter damage
passive bonus: +throw energy", 1000); + mech.setHoldDefaults(); + // mech.fieldRegen = 0.0008; // 0.0015 is normal + //throw quicker and harder + mech.throwChargeRate = 4; //0.5 + mech.throwChargeMax = 300; //50 + //passive field does extra damage + mech.grabRange = 180; + mech.fieldArc = 0.08; + mech.fieldDamage = 2.5; + + mech.hold = function () { + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if ((keys[32] || game.mouseDownRight) && mech.fieldMeter > 0.15) { //not hold but field button is pressed + //draw field + const range = mech.grabRange - 20; + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, range, mech.angle - Math.PI * mech.fieldArc, mech.angle + Math.PI * mech.fieldArc, false); + let eye = 13; + ctx.lineTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle)); + if (mech.holdingTarget) { + ctx.fillStyle = "rgba(255,50,150," + (0.05 + 0.1 * Math.random()) + ")"; + } else { + ctx.fillStyle = "rgba(255,50,150," + (0.15 + 0.15 * Math.random()) + ")"; + } + ctx.fill(); + //draw random lines in field for cool effect + let offAngle = mech.angle + 2 * Math.PI * mech.fieldArc * (Math.random() - 0.5); + ctx.beginPath(); + eye = 15; + ctx.moveTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle)); + ctx.lineTo(mech.pos.x + range * Math.cos(offAngle), mech.pos.y + range * Math.sin(offAngle)); + ctx.strokeStyle = "rgba(120,170,255,0.4)"; + ctx.stroke(); + + mech.grabPowerUp(); + mech.pushMobs(); + mech.lookForPickUp(); + } else if (mech.holdingTarget && mech.fireCDcycle < game.cycle) { //holding, but field button is released + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + mech.drawFieldMeter() + } + }, + () => { + mech.fieldMode = 3; + game.makeTextLog("

Mass Recycler


active ability: hold left and right mouse to convert blocks into health
negative effect: -energy regen", 1000); + mech.setHoldDefaults(); + mech.fieldRegen = 0.0005; //0.0015 + mech.hold = function () { + // health drain + // if (game.cycle % 360 === 0 && mech.health > 0.2) { + // mech.health = mech.health * 0.97 - 0.01; + // if (mech.health < 0) { + // mech.health = 0; + // mech.death(); + // return; + // } + // mech.displayHealth(); + // } + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if (game.mouseDown && (keys[32] || game.mouseDownRight) && mech.fieldCDcycle < game.cycle) { //both mouse keys down + if (mech.fieldMeter > mech.fieldRegen) { + // mech.fieldMeter -= mech.fieldRegen + const range = 165; + //draw range + ctx.globalCompositeOperation = "screen" //"lighter" // "destination-atop" //"difference" //"color-burn"; + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, range, 0, 2 * Math.PI); + ctx.lineWidth = 1; + ctx.fillStyle = "rgba(150,210,180," + (0.9 + Math.random() * 0.1) + ")"; + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + ctx.strokeStyle = "#364"; + ctx.stroke(); + + //find all blocks in range + for (let i = 0, len = body.length; i < len; i++) { + if (!body[i].isNotHoldable) { + dist = Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, mech.pos)) + const healCost = Math.sqrt(0.003 * body[i].mass) + 0.04 + if (dist < range && mech.fieldMeter > healCost + 0.2) { // convert block to heal power up + // mech.fieldCDcycle = game.cycle + 4; + mech.fieldMeter -= healCost + powerUps.spawnHeal(body[i].position.x, body[i].position.y, 120 * healCost); + Matter.World.remove(engine.world, body[i]); + body.splice(i, 1); + break + } + } + } + } else { + mech.fieldCDcycle = game.cycle + 120; + } + } else if ((keys[32] || game.mouseDownRight) && mech.fieldMeter > 0.2) { //field button is pressed + mech.drawField(); + mech.grabPowerUp(); + mech.pushMobs(); + mech.lookForPickUp(130); + } else if (mech.holdingTarget && mech.fireCDcycle < game.cycle) { //holding, but field button is released + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + mech.drawFieldMeter() + } + }, + () => { + mech.fieldMode = 4; + game.makeTextLog("

Negative Mass Field


active ability: hold left and right mouse to nullify gravity
passive bonuses: +field size, +hold larger blocks", 1000); + mech.setHoldDefaults(); + mech.fieldArc = 1; //field covers full 360 degrees + mech.calculateFieldThreshold(); + mech.holdingMassScale = 0.05; //can hold heavier blocks + // mech.gravity = 0.0015; //passive reduce gravity from default 0.0019 + + mech.hold = function () { + const range = 200 + 35 * Math.sin(game.cycle / 20) + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if (game.mouseDown && (keys[32] || game.mouseDownRight) && mech.fieldCDcycle < game.cycle) { //both mouse keys down //push away + if (mech.fieldMeter > 0.005) { + mech.fieldMeter -= 0.005; + + //look for nearby objects to make zero-g + function zeroG(who) { + for (let i = 0, len = who.length; i < len; ++i) { + sub = Matter.Vector.sub(who[i].position, mech.pos); + dist = Matter.Vector.magnitude(sub); + if (dist < range) { + who[i].force.y -= who[i].mass * game.g; + } + } + } + zeroG(powerUp); + zeroG(body); + + player.force.y -= player.mass * mech.gravity; // + 0.005 * Math.sin(game.cycle / 10); //wobble + //add player vertical friction to reduce map jump craziness + Matter.Body.setVelocity(player, { + x: player.velocity.x, + y: player.velocity.y * 0.99 + }); + + //draw zero-G range + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y + 15, range, 0, 2 * Math.PI); + ctx.globalCompositeOperation = "color-burn"; + ctx.fillStyle = "rgb(90,90,100)"; + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + + } else { + //trigger cooldown + mech.fieldCDcycle = game.cycle + 120; + } + } else if ((keys[32] || game.mouseDownRight) && mech.fieldMeter > 0.2 && mech.fieldCDcycle < game.cycle) { //not hold but field button is pressed + mech.grabRange = range + mech.drawField(); + mech.grabPowerUp(); + mech.pushMobs(); + mech.lookForPickUp(); + } else if (mech.holdingTarget && mech.fireCDcycle < game.cycle && mech.fieldCDcycle < game.cycle) { //holding, but field button is released + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + mech.drawFieldMeter() + } + }, + ], + // () => { + // mech.fieldMode = 4; + // game.makeTextLog("

Negative Mass Field


active ability: hold left and right mouse to push things away
passive bonuses: +field size, -player gravity", 1000); //
passive bonus: can phase through blocks + // mech.setHoldDefaults(); + // mech.fieldArc = 1; + // mech.calculateFieldThreshold(); + // mech.holdingMassScale = 0.05; + // mech.gravity = 0.0015; //passive reduce gravity from default 0.0019 + + // mech.hold = function () { + // if (mech.isHolding) { + // mech.drawHold(mech.holdingTarget); + // mech.holding(); + // mech.throw(); + // } else if (game.mouseDown && (keys[32] || game.mouseDownRight) && mech.fieldCDcycle < game.cycle) { //both mouse keys down //push away + // if (mech.fieldMeter > 0.004) { + // mech.fieldMeter -= 0.004; + // const range = 450 + 100 * Math.sin(game.cycle / 20) + + // //draw push + // ctx.beginPath(); + // ctx.arc(mech.pos.x, mech.pos.y, range, 0, 2 * Math.PI); + // ctx.globalCompositeOperation = "color-burn"; + // ctx.fillStyle = "rgb(90,90,100)"; + // ctx.fill(); + // ctx.globalCompositeOperation = "source-over"; + + // function pushAway(who) { + // for (let i = 0, len = who.length; i < len; ++i) { + // sub = Matter.Vector.sub(who[i].position, mech.pos); + // dist = Matter.Vector.magnitude(sub); + // if (dist < range) { + // knock = Matter.Vector.mult(Matter.Vector.normalise(sub), who[i].mass / 200); + // who[i].force.x += knock.x; + // who[i].force.y += knock.y; + // // player.force.x -= knock.x / 10; + // // player.force.y -= knock.y / 10; + // } + // } + // } + // pushAway(body); + // pushAway(mob); + // pushAway(bullet); + // pushAway(powerUp); + // } else { + // mech.fieldCDcycle = game.cycle + 120; + // } + // } else if ((keys[32] || game.mouseDownRight) && mech.fieldMeter > 0.2) { //not hold but field button is pressed + // mech.grabRange = 200 + 50 * Math.sin(game.cycle / 20) + // mech.drawField(); + // mech.grabPowerUp(); + // mech.pushMobs(); + // mech.lookForPickUp(); + // } else if (mech.holdingTarget && mech.fireCDcycle < game.cycle) { //holding, but field button is released + // mech.pickUp(); + // } else { + // mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + // } + // mech.drawFieldMeter() + // } + // }, + // ], + // phaseBlocks(mask = 0x010111) { + // level.bodyCollisionFilterMask = mask; + // for (let i = 0, len = body.length; i < len; i++) { + // body[i].collisionFilter.category = 0x0000001; + // body[i].collisionFilter.mask = mask; + // } + // }, + drawLeg(stroke) { + // if (game.mouseInGame.x > this.pos.x) { + if (mech.angle > -Math.PI / 2 && mech.angle < Math.PI / 2) { + this.flipLegs = 1; + } else { + this.flipLegs = -1; + } + ctx.save(); + ctx.scale(this.flipLegs, 1); //leg lines + ctx.beginPath(); + ctx.moveTo(this.hip.x, this.hip.y); + ctx.lineTo(this.knee.x, this.knee.y); + ctx.lineTo(this.foot.x, this.foot.y); + ctx.strokeStyle = stroke; + ctx.lineWidth = 7; + ctx.stroke(); + + //toe lines + ctx.beginPath(); + ctx.moveTo(this.foot.x, this.foot.y); + ctx.lineTo(this.foot.x - 15, this.foot.y + 5); + ctx.moveTo(this.foot.x, this.foot.y); + ctx.lineTo(this.foot.x + 15, this.foot.y + 5); + ctx.lineWidth = 4; + ctx.stroke(); + + //hip joint + ctx.beginPath(); + ctx.arc(this.hip.x, this.hip.y, 11, 0, 2 * Math.PI); + //knee joint + ctx.moveTo(this.knee.x + 7, this.knee.y); + ctx.arc(this.knee.x, this.knee.y, 7, 0, 2 * Math.PI); + //foot joint + ctx.moveTo(this.foot.x + 6, this.foot.y); + ctx.arc(this.foot.x, this.foot.y, 6, 0, 2 * Math.PI); + ctx.fillStyle = this.fillColor; + ctx.fill(); + ctx.lineWidth = 2; + ctx.stroke(); + ctx.restore(); + }, + calcLeg(cycle_offset, offset) { + this.hip.x = 12 + offset; + this.hip.y = 24 + offset; + //stepSize goes to zero if Vx is zero or not on ground (make this transition cleaner) + this.stepSize = + 0.8 * this.stepSize + + 0.2 * (7 * Math.sqrt(Math.abs(this.Vx)) * this.onGround); + //changes to stepsize are smoothed by adding only a percent of the new value each cycle + const stepAngle = 0.034 * this.walk_cycle + cycle_offset; + this.foot.x = 2.2 * this.stepSize * Math.cos(stepAngle) + offset; + this.foot.y = offset + 1.2 * this.stepSize * Math.sin(stepAngle) + this.yOff + this.height; + const Ymax = this.yOff + this.height; + if (this.foot.y > Ymax) this.foot.y = Ymax; + + //calculate knee position as intersection of circle from hip and foot + const d = Math.sqrt((this.hip.x - this.foot.x) * (this.hip.x - this.foot.x) + (this.hip.y - this.foot.y) * (this.hip.y - this.foot.y)); + const l = (this.legLength1 * this.legLength1 - this.legLength2 * this.legLength2 + d * d) / (2 * d); + const h = Math.sqrt(this.legLength1 * this.legLength1 - l * l); + this.knee.x = (l / d) * (this.foot.x - this.hip.x) - (h / d) * (this.foot.y - this.hip.y) + this.hip.x + offset; + this.knee.y = (l / d) * (this.foot.y - this.hip.y) + (h / d) * (this.foot.x - this.hip.x) + this.hip.y; + }, + draw() { + ctx.fillStyle = this.fillColor; + this.walk_cycle += this.flipLegs * this.Vx; + + //draw body + ctx.save(); + ctx.translate(this.pos.x, this.pos.y); + this.calcLeg(Math.PI, -3); + this.drawLeg("#4a4a4a"); + this.calcLeg(0, 0); + this.drawLeg("#333"); + ctx.rotate(this.angle); + + ctx.beginPath(); + ctx.arc(0, 0, 30, 0, 2 * Math.PI); + let grd = ctx.createLinearGradient(-30, 0, 30, 0); + grd.addColorStop(0, this.fillColorDark); + grd.addColorStop(1, this.fillColor); + ctx.fillStyle = grd; + ctx.fill(); + ctx.arc(15, 0, 4, 0, 2 * Math.PI); + ctx.strokeStyle = "#333"; + ctx.lineWidth = 2; + ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(15, 0, 3, 0, 2 * Math.PI); + // ctx.fillStyle = '#9cf' //'#0cf'; + // ctx.fill() + ctx.restore(); + } +}; \ No newline at end of file diff --git a/js/powerups.js b/js/powerups.js new file mode 100644 index 0000000..93e222a --- /dev/null +++ b/js/powerups.js @@ -0,0 +1,245 @@ +let powerUp = []; + +const powerUps = { + heal: { + name: "heal", + color: "#0f9", + size() { + return 40 * Math.sqrt(0.1 + Math.random() * 0.5); + }, + effect() { + let heal = this.size / 40; + mech.addHealth(heal * heal); + //game.makeTextLog('heal for '+(heal*100).toFixed(0)+'%',80) + } + }, + field: { + name: "field", + color: "#f9f", + size() { + return 40; + }, + effect() { + const previousMode = mech.fieldMode + + if (!this.mode) { //this.mode is set if the power up has been ejected from player + mode = mech.fieldMode + while (mode === mech.fieldMode) { + mode = Math.ceil(Math.random() * (mech.fieldUpgrades.length - 1)) + } + mech.fieldUpgrades[mode](); //choose random field upgrade that you don't already have + } else { + mech.fieldUpgrades[this.mode](); //set a predetermined power up + } + + if (previousMode !== 0) { //pop the old field out in case player wants to swap back + // mech.fieldMeter = 0 //drop field meter to zero to prevent automatic pickup + mech.fieldCDcycle = game.cycle + 60; //trigger fieldCD to stop power up grab automatic pick up of spawn + powerUps.spawn(mech.pos.x, mech.pos.y, "field", false, previousMode); + } + + + // mech.fieldUpgrades[3](); + + //pause game so player can read above the new field + // game.fpsCap = 0 //40 - Math.min(25, 100 * dmg) + // game.fpsInterval = 1000 / game.fpsCap; + + // function unpause() { + // game.fpsCap = 72 + // game.fpsInterval = 1000 / game.fpsCap; + // document.body.removeEventListener("keydown", unpause); + // } + // document.body.addEventListener("keydown", unpause); + } + }, + ammo: { + name: "ammo", + color: "#467", + size() { + return 17; + }, + effect() { + //only get ammo for guns player has + let target; + if (b.inventory.length > 0) { + //add ammo to a gun in inventory + target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]]; + } else { + //if you don't have a gun just add ammo to a random gun + target = b.guns[Math.floor(Math.random() * b.guns.length)]; + } + //ammo given scales as mobs take more hits to kill + const ammo = Math.ceil((target.ammoPack * (0.60 + 0.5 * Math.random())) / b.dmgScale); + target.ammo += ammo; + game.updateGunHUD(); + game.makeTextLog("+" + ammo + " ammo: " + target.name, 180); + } + }, + gun: { + name: "gun", + color: "#0cf", + size() { + return 30; + }, + effect() { + //find what guns I don't have + let options = []; + for (let i = 0; i < b.guns.length; ++i) { + if (!b.guns[i].have) options.push(i); + } + //give player a gun they don't already have if possible + if (options.length > 0) { + let newGun = options[Math.floor(Math.random() * options.length)]; + // newGun = 4; //makes every gun you pick up this type //enable for testing one gun + if (b.activeGun === null) { + b.activeGun = newGun //if no active gun switch to new gun + game.makeTextLog( + "



left mouse: fire weapon
", + Infinity + ); + } else { + game.makeTextLog( + // "
new gun: " + b.guns[newGun].name + "
E / Q", + "
new gun: " + b.guns[newGun].name + "
", + 360 + ); + } + b.guns[newGun].have = true; + b.inventory.push(newGun); + b.guns[newGun].ammo += b.guns[newGun].ammoPack * 2; + game.makeGunHUD(); + } else { + //if you have all guns then get ammo + const ammoTarget = Math.floor(Math.random() * (b.guns.length)); + const ammo = Math.ceil(b.guns[ammoTarget].ammoPack * 2); + b.guns[ammoTarget].ammo += ammo; + game.updateGunHUD(); + game.makeTextLog("+" + ammo + " ammo: " + b.guns[ammoTarget].name, 180); + } + } + }, + spawnRandomPowerUp(x, y) { //mostly used after mob dies + if (Math.random() * Math.random() - 0.25 > Math.sqrt(mech.health) || Math.random() < 0.04) { //spawn heal chance is higher at low health + powerUps.spawn(x, y, "heal"); + return; + } + if (Math.random() < 0.18) { + if (b.inventory.length > 0) powerUps.spawn(x, y, "ammo"); + return; + } + if (Math.random() < 0.005 * (8 - b.inventory.length)) { //a new gun has a low chance for each not acquired gun to drop + powerUps.spawn(x, y, "gun"); + return; + } + if (Math.random() < 0.006) { + powerUps.spawn(x, y, "field"); + return; + } + }, + chooseRandomPowerUp(x, y) { //100% chance to drop a random power up //used in spawn.debris + if (Math.random() < 0.5) { + powerUps.spawn(x, y, "heal", false); + } else { + powerUps.spawn(x, y, "ammo", false); + } + }, + spawnStartingPowerUps(x, y) { + if (b.inventory.length < 3) { + powerUps.spawn(x, y, "gun", false); //starting gun + } else { + powerUps.spawnRandomPowerUp(x, y); + powerUps.spawnRandomPowerUp(x, y); + powerUps.spawnRandomPowerUp(x, y); + powerUps.spawnRandomPowerUp(x, y); + } + }, + spawn(x, y, target, moving = true, mode) { + let i = powerUp.length; + target = powerUps[target]; + size = target.size(); + powerUp[i] = Matter.Bodies.polygon(x, y, 0, size, { + density: 0.001, + frictionAir: 0.01, + restitution: 0.8, + collisionFilter: { + group: 0, + category: 0x100000, + mask: 0x100001 + }, + endCycle: game.cycle + 1080, //if change time also update color fade out + color: target.color, + sat: 1, + effect: target.effect, + mode: mode, + name: target.name, + size: size + }); + if (moving) { + Matter.Body.setVelocity(powerUp[i], { + x: (Math.random() - 0.5) * 15, + y: Math.random() * -9 - 3 + }); + } + World.add(engine.world, powerUp[i]); //add to world + }, + spawnHeal(x, y, size) { //used by the mass recycler power up + let i = powerUp.length; + const target = powerUps["heal"]; + powerUp[i] = Matter.Bodies.polygon(x, y, 0, size, { + density: 0.001, + frictionAir: 0.01, + restitution: 0.8, + collisionFilter: { + group: 0, + category: 0x100000, + mask: 0x100001 + }, + endCycle: game.cycle + 1080, //if change time also update color fade out + color: target.color, + sat: 1, + effect: target.effect, + name: target.name, + size: size + }); + // Matter.Body.setVelocity(powerUp[i], { + // x: (Math.random() - 0.5) * 3, + // y: -7 * Math.random() - 5 + // }); + World.add(engine.world, powerUp[i]); //add to world + }, + attractionLoop() { + for (let i = 0, len = powerUp.length; i < len; ++i) { + const dxP = player.position.x - powerUp[i].position.x; + const dyP = player.position.y - powerUp[i].position.y; + const dist2 = dxP * dxP + dyP * dyP; + //gravitation for pickup + if (dist2 < 100000 && (powerUp[i].name != "heal" || mech.health < 1)) { + if (dist2 < 2000) { + //knock back from grabbing power up + Matter.Body.setVelocity(player, { + x: player.velocity.x + ((powerUp[i].velocity.x * powerUp[i].mass) / player.mass) * 0.25, + y: player.velocity.y + ((powerUp[i].velocity.y * powerUp[i].mass) / player.mass) * 0.25 + }); + mech.usePowerUp(i); + break; + } + //power up needs to be able to see player to gravitate + if (Matter.Query.ray(map, powerUp[i].position, player.position).length === 0) { // && Matter.Query.ray(body, powerUp[i].position, player.position).length === 0 + //extra friction + Matter.Body.setVelocity(powerUp[i], { + x: powerUp[i].velocity.x * 0.97, + y: powerUp[i].velocity.y * 0.97 + }); + //float towards player + powerUp[i].force.x += (dxP / dist2) * powerUp[i].mass * 1.6; + powerUp[i].force.y += (dyP / dist2) * powerUp[i].mass * 1.6 - powerUp[i].mass * game.g; //negate gravity + //draw the pulling effect + ctx.globalAlpha = 0.2; + mech.drawHold(powerUp[i], false); + ctx.globalAlpha = 1; + } + } + } + } +}; \ No newline at end of file diff --git a/js/spawn.js b/js/spawn.js new file mode 100644 index 0000000..55c045c --- /dev/null +++ b/js/spawn.js @@ -0,0 +1,1583 @@ +//main object for spawning things in a level +const spawn = { + pickList: ["starter", "starter"], + fullPickList: [ + "chaser", "chaser", "chaser", + "striker", "striker", + "spinner", + "hopper", "hopper", "hopper", "hopper", + "grower", + "springer", + "shooter", "shooter", "shooter", "shooter", "shooter", + "beamer", + "focuser", + "laser", "laser", + "blinker", + "sucker", + "exploder", "exploder", "exploder", + "spawner", + "ghoster", + "sneaker", + ], + allowedBossList: ["chaser", "spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter"], //"zoomer", + setSpawnList() { + //this is run at the start of each new level to determine the possible mobs for the level + //each level has 2 mobs: one new mob and one from the last level + spawn.pickList.splice(0, 1); + spawn.pickList.push(spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]); + }, + randomMob(x, y, chance = 1) { + if (Math.random() < chance + 0.1 * (game.levelsCleared - 1) && mob.length < 4 + game.levelsCleared * 2) { + const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + this[pick](x, y); + } + }, + randomSmallMob( + x, + y, + num = Math.max(Math.min(Math.round(Math.random() * (game.levelsCleared - 1) * 0.5 - 0.4), 4), 0), + size = 16 + Math.ceil(Math.random() * 15), + chance = 1 + ) { + if (Math.random() < chance + (game.levelsCleared - 1) * 0.03 && mob.length < 4 + game.levelsCleared * 2) { + for (let i = 0; i < num; ++i) { + const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + this[pick](x + Math.round((Math.random() - 0.5) * 20) + i * size * 2.5, y + Math.round((Math.random() - 0.5) * 20), size); + } + } + }, + randomBoss(x, y, chance = 1) { + if (Math.random() < chance + (game.levelsCleared - 1) * 0.15 && game.levelsCleared !== 1 && mob.length < 4 + game.levelsCleared * 2.1 || chance == Infinity) { + //choose from the possible picklist + let pick = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + //is the pick able to be a boss? + let canBeBoss = false; + for (let i = 0, len = this.allowedBossList.length; i < len; ++i) { + if (this.allowedBossList[i] === pick) { + canBeBoss = true; + break; + } + } + if (canBeBoss) { + if (Math.random() < 0.55) { + this.nodeBoss(x, y, pick); + } else { + this.lineBoss(x, y, pick); + } + } else { + if (Math.random() < 0.07) { + this[pick](x, y, 90 + Math.random() * 40); //one extra large mob + } else if (Math.random() < 0.35) { + this.groupBoss(x, y) //hidden grouping blocks + } else { + pick = (Math.random() < 0.5) ? "randomList" : "random"; + if (Math.random() < 0.55) { + this.nodeBoss(x, y, pick); + } else { + this.lineBoss(x, y, pick); + } + } + } + } + }, + + //mob templates ********************************************************************************************* + //*********************************************************************************************************** + groupBoss(x, y, num = 5 + Math.random() * 8) { + for (let i = 0; i < num; i++) { + const radius = 25 + Math.floor(Math.random() * 20) + spawn.grouper(x + Math.random() * radius, y + Math.random() * radius, radius); + } + }, + grouper(x, y, radius = 27 + Math.floor(Math.random() * 10)) { + mobs.spawn(x, y, 4, radius, "#777"); + let me = mob[mob.length - 1]; + me.g = 0.0002; //required if using 'gravity' + me.accelMag = 0.0007; + me.groupingRangeMax = 250000 + Math.random() * 100000; + me.groupingRangeMin = (radius * 8) * (radius * 8); + me.groupingStrength = 0.0005 + me.memory = 200; + + me.do = function () { + this.gravity(); + if (this.seePlayer.recall) { + this.seePlayerByDistAndLOS(); + this.healthBar(); + this.attraction(); + //tether to other blocks + ctx.beginPath(); + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i] != this && mob[i].dropPowerUp) { //don't tether to self, bullets, shields, ... + const distance2 = Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, mob[i].position)) + if (distance2 < this.groupingRangeMax) { + if (!mob[i].seePlayer.recall) mob[i].seePlayerByDistAndLOS(); //wake up sleepy mobs + if (distance2 > this.groupingRangeMin) { + const angle = Math.atan2(mob[i].position.y - this.position.y, mob[i].position.x - this.position.x); + const forceMag = this.groupingStrength * mob[i].mass; + mob[i].force.x -= forceMag * Math.cos(angle); + mob[i].force.y -= forceMag * Math.sin(angle); + } + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mob[i].position.x, mob[i].position.y); + } + } + } + ctx.strokeStyle = "#000"; + ctx.lineWidth = 1; + ctx.stroke(); + } + } + }, + starter(x, y, radius = 30) { + //easy mob for on level 1 + mobs.spawn(x, y, 8, radius, "#9ccdc6"); + let me = mob[mob.length - 1]; + me.accelMag = 0.00055; + me.memory = 60; + Matter.Body.setDensity(me, 0.0005) // normal density is 0.001 // this reduces life by half and decreases knockback + + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.attraction(); + }; + }, + chaser(x, y, radius = 35 + Math.ceil(Math.random() * 40)) { + mobs.spawn(x, y, 8, radius, "#2c9790"); + let me = mob[mob.length - 1]; + // Matter.Body.setDensity(me, 0.0007); //extra dense //normal is 0.001 //makes effective life much lower + me.friction = 0; + me.frictionAir = 0; + me.accelMag = 0.001; + me.g = me.accelMag * 0.6; //required if using 'gravity' + me.memory = 50; + if (Math.random() < Math.min((game.levelsCleared - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.attraction(); + }; + }, + grower(x, y, radius = 15) { + mobs.spawn(x, y, 7, radius, "hsl(144, 15%, 50%)"); + let me = mob[mob.length - 1]; + me.big = false; //required for grow + me.accelMag = 0.00045; + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.attraction(); + this.grow(); + }; + }, + springer(x, y, radius = 20 + Math.ceil(Math.random() * 35)) { + mobs.spawn(x, y, 8, radius, "#b386e8"); + let me = mob[mob.length - 1]; + me.friction = 0; + me.frictionAir = 0.1; + me.lookTorque = 0.000005; + me.g = 0.0002; //required if using 'gravity' + me.seePlayerFreq = Math.ceil(40 + 25 * Math.random()); + const springStiffness = 0.002; + const springDampening = 0.1; + + me.springTarget = { + x: me.position.x, + y: me.position.y + }; + const len = cons.length; + cons[len] = Constraint.create({ + pointA: me.springTarget, + bodyB: me, + stiffness: springStiffness, + damping: springDampening + }); + cons[len].length = 100 + 1.5 * radius; + me.cons = cons[len]; + + me.springTarget2 = { + x: me.position.x, + y: me.position.y + }; + const len2 = cons.length; + cons[len2] = Constraint.create({ + pointA: me.springTarget2, + bodyB: me, + stiffness: springStiffness, + damping: springDampening + }); + cons[len2].length = 100 + 1.5 * radius; + me.cons2 = cons[len2]; + + me.onDeath = function () { + this.removeCons(); + }; + if (Math.random() < Math.min((game.levelsCleared - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + this.gravity(); + this.searchSpring(); + }; + }, + zoomer(x, y, radius = 20 + Math.ceil(Math.random() * 30)) { + mobs.spawn(x, y, 6, radius, "#ffe2fd"); + let me = mob[mob.length - 1]; + me.trailLength = 20; //required for trails + me.setupTrail(); //fill trails array up with the current position of mob + me.trailFill = "#ff00f0"; + me.g = 0.001; //required if using 'gravity' + me.frictionAir = 0.02; + me.accelMag = 0.004; + me.memory = 30; + me.zoomMode = 150; + me.onHit = function () { + this.zoomMode = 150; + }; + me.do = function () { + this.healthBar(); + this.seePlayerByDistAndLOS(); + this.zoom(); + this.gravity(); + }; + }, + hopper(x, y, radius = 30 + Math.ceil(Math.random() * 30)) { + mobs.spawn(x, y, 5, radius, "rgb(0,200,180)"); + let me = mob[mob.length - 1]; + me.accelMag = 0.04; + me.g = 0.0015; //required if using 'gravity' + me.frictionAir = 0.018; + me.restitution = 0; + me.delay = 110; + me.randomHopFrequency = 50 + Math.floor(Math.random() * 1000); + me.randomHopCD = game.cycle + me.randomHopFrequency; + me.do = function () { + this.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.hop(); + //randomly hob if not aware of player + if (this.randomHopCD < game.cycle && this.speed < 1 && !this.seePlayer.recall) { + this.randomHopCD = game.cycle + this.randomHopFrequency; + //slowly change randomHopFrequency after each hop + this.randomHopFrequency = Math.max(100, this.randomHopFrequency + (0.5 - Math.random()) * 200); + const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass * (0.1 + Math.random() * 0.3); + const angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI; + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle) - 0.04 * this.mass; //antigravity + } + }; + }, + spinner(x, y, radius = 30 + Math.ceil(Math.random() * 35)) { + mobs.spawn(x, y, 5, radius, "#000000"); + let me = mob[mob.length - 1]; + me.fill = "#28b"; + me.rememberFill = me.fill; + me.cdBurst1 = 0; //must add for burstAttraction + me.cdBurst2 = 0; //must add for burstAttraction + me.delay = 0; + me.burstDir = { + x: 0, + y: 0 + }; + me.accelMag = 0.16; + me.frictionAir = 0.022; + me.lookTorque = 0.0000014; + me.restitution = 0; + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + //accelerate towards the player after a delay + if (this.seePlayer.recall) { + if (this.cdBurst2 < game.cycle && this.angularSpeed < 0.01) { + this.cdBurst2 = Infinity; + this.cdBurst1 = game.cycle + 40; + this.burstDir = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position)); + } else if (this.cdBurst1 < game.cycle) { + this.cdBurst2 = game.cycle + this.delay; + this.cdBurst1 = Infinity; + this.force = Matter.Vector.mult(this.burstDir, this.mass * 0.25); + this.fill = this.rememberFill; + } else if (this.cdBurst1 != Infinity) { + this.torque += 0.000035 * this.inertia; + this.fill = randomColor({ + hue: "blue" + }); + //draw attack vector + const mag = this.radius * 2 + 200; + const gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, mag); + gradient.addColorStop(0, "rgba(0,0,0,0.2)"); + gradient.addColorStop(1, "transparent"); + ctx.strokeStyle = gradient; + ctx.lineWidth = 5; + ctx.setLineDash([10, 20]); //30 + const dir = Matter.Vector.add(this.position, Matter.Vector.mult(this.burstDir, mag)); + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(dir.x, dir.y); + ctx.stroke(); + ctx.setLineDash([]); + } else { + this.fill = this.rememberFill; + } + } else { + this.cdBurst2 = 0; + } + }; + }, + sucker(x, y, radius = 30 + Math.ceil(Math.random() * 70)) { + radius = 9 + radius / 8; //extra small + mobs.spawn(x, y, 6, radius, "#000"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; //used for drawSneaker + me.eventHorizon = radius * 23; //required for blackhole + me.seeAtDistance2 = (me.eventHorizon + 500) * (me.eventHorizon + 500); //vision limit is event horizon + me.accelMag = 0.00009; + // me.frictionAir = 0.005; + me.memory = 600; + Matter.Body.setDensity(me, 0.004); //extra dense //normal is 0.001 //makes effective life much larger + // me.collisionFilter.mask = 0x001100; //move through walls + me.do = function () { + //keep it slow, to stop issues from explosion knock backs + if (this.speed > 5) { + Matter.Body.setVelocity(this, { + x: this.velocity.x * 0.99, + y: this.velocity.y * 0.99 + }); + } + this.seePlayerByDistOrLOS(); + if (this.seePlayer.recall) { + //eventHorizon waves in and out + eventHorizon = this.eventHorizon * (0.93 + 0.17 * Math.sin(game.cycle * 0.011)) + + //accelerate towards the player + const forceMag = this.accelMag * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle); + + //draw darkness + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.25, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.9)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.55, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.5)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.1)"; + ctx.fill(); + + this.healthBar(); + //when player is inside event horizon + if (Matter.Vector.magnitude(Matter.Vector.sub(this.position, player.position)) < eventHorizon) { + mech.damage(0.00015 * game.dmgScale); + if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.01 + const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + player.force.x -= 1.25 * Math.cos(angle) * player.mass * game.g * (mech.onGround ? 1.8 : 1); + player.force.y -= 0.96 * player.mass * game.g * Math.sin(angle); + //draw line to player + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mech.pos.x, mech.pos.y); + ctx.lineWidth = Math.min(60, this.radius * 2); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.stroke(); + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.3)"; + ctx.fill(); + } + } + } + }, + beamer(x, y, radius = 15 + Math.ceil(Math.random() * 15)) { + mobs.spawn(x, y, 4, radius, "rgb(255,0,190)"); + let me = mob[mob.length - 1]; + me.repulsionRange = 73000; //squared + me.laserRange = 370; + // me.seePlayerFreq = 2 + Math.round(Math.random() * 5); + me.accelMag = 0.0005; + me.frictionStatic = 0; + me.friction = 0; + if (Math.random() < Math.min(0.2 + (game.levelsCleared - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.attraction(); + this.repulsion(); + //laser beam + this.laserBeam(); + }; + }, + focuser(x, y, radius = 30 + Math.ceil(Math.random() * 10)) { + radius = Math.ceil(radius * 0.7); + mobs.spawn(x, y, 4, radius, "rgb(0,0,255)"); + let me = mob[mob.length - 1]; + Matter.Body.setDensity(me, 0.003); //extra dense //normal is 0.001 + me.restitution = 0; + me.laserPos = me.position; //required for laserTracking + me.repulsionRange = 1200000; //squared + //me.seePlayerFreq = 2 + Math.round(Math.random() * 5); + me.accelMag = 0.0002; + me.frictionStatic = 0; + me.friction = 0; + me.onDamage = function () { + this.laserPos = this.position; + }; + // if (Math.random() < Math.min(0.2 + game.levelsCleared * 0.1, 0.7)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + const dist2 = this.distanceToPlayer2(); + //laser Tracking + if (this.seePlayer.yes && dist2 < 4000000) { + this.attraction(); + const rangeWidth = 2000; //this is sqrt of 4000000 from above if() + //targeting laser will slowly move from the mob to the player's position + this.laserPos = Matter.Vector.add(this.laserPos, Matter.Vector.mult(Matter.Vector.sub(player.position, this.laserPos), 0.1)); + let targetDist = Matter.Vector.magnitude(Matter.Vector.sub(this.laserPos, mech.pos)); + const r = 10; + + // ctx.setLineDash([15, 200]); + // ctx.lineDashOffset = 20*(game.cycle % 215); + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + if (targetDist < r + 15) { + // || dist2 < 80000 + targetDist = r + 10; + //charge at player + const forceMag = this.accelMag * 40 * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle); + } else { + //high friction if can't lock onto player + Matter.Body.setVelocity(this, { + x: this.velocity.x * 0.96, + y: this.velocity.y * 0.96 + }); + } + if (dist2 > 80000) { + const laserWidth = 0.002; + let laserOffR = Matter.Vector.rotateAbout(this.laserPos, (targetDist - r) * laserWidth, this.position); + let sub = Matter.Vector.normalise(Matter.Vector.sub(laserOffR, this.position)); + laserOffR = Matter.Vector.add(laserOffR, Matter.Vector.mult(sub, rangeWidth)); + ctx.lineTo(laserOffR.x, laserOffR.y); + + let laserOffL = Matter.Vector.rotateAbout(this.laserPos, (targetDist - r) * -laserWidth, this.position); + sub = Matter.Vector.normalise(Matter.Vector.sub(laserOffL, this.position)); + laserOffL = Matter.Vector.add(laserOffL, Matter.Vector.mult(sub, rangeWidth)); + ctx.lineTo(laserOffL.x, laserOffL.y); + // ctx.fillStyle = "rgba(0,0,255,0.15)"; + var gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, rangeWidth); + gradient.addColorStop(0, `rgba(0,0,255,${((r + 5) * (r + 5)) / (targetDist * targetDist)})`); + gradient.addColorStop(1, "transparent"); + ctx.fillStyle = gradient; + ctx.fill(); + } + } else { + this.laserPos = this.position; + } + }; + }, + laser(x, y, radius = 30) { + //only on level 1 + mobs.spawn(x, y, 3, radius, "#f00"); + let me = mob[mob.length - 1]; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + Matter.Body.rotate(me, Math.random() * Math.PI * 2); + me.accelMag = 0.00007; + me.onHit = function () { + //run this function on hitting player + this.explode(); + }; + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.attraction(); + this.laser(); + }; + }, + striker(x, y, radius = 15 + Math.ceil(Math.random() * 25)) { + mobs.spawn(x, y, 5, radius, "rgb(221,102,119)"); + let me = mob[mob.length - 1]; + me.accelMag = 0.0004; + me.g = 0.0002; //required if using 'gravity' + me.frictionStatic = 0; + me.friction = 0; + me.delay = 60; + Matter.Body.rotate(me, Math.PI * 0.1); + me.onDamage = function () { + this.cd = game.cycle + this.delay; + }; + me.do = function () { + this.healthBar(); + this.seePlayerCheck(); + this.attraction(); + this.gravity(); + this.strike(); + }; + }, + sneaker(x, y, radius = 15 + Math.ceil(Math.random() * 25)) { + let me; + mobs.spawn(x, y, 5, radius, "transparent"); + me = mob[mob.length - 1]; + me.accelMag = 0.0007; + me.g = 0.0002; //required if using 'gravity' + me.stroke = "transparent"; //used for drawSneaker + me.alpha = 1; //used in drawSneaker + // me.leaveBody = false; + me.canTouchPlayer = false; //used in drawSneaker + me.collisionFilter.mask = 0x000111; //can't touch player + // me.memory = 420; + // me.seePlayerFreq = 60 + Math.round(Math.random() * 30); + me.do = function () { + this.seePlayerCheck(); + this.attraction(); + this.gravity(); + //draw + if (this.seePlayer.yes) { + if (this.alpha < 1) this.alpha += 0.01; + } else { + if (this.alpha > 0) this.alpha -= 0.03; + } + if (this.alpha > 0) { + if (this.alpha > 0.95) { + this.healthBar(); + if (!this.canTouchPlayer) { + this.canTouchPlayer = true; + this.collisionFilter.mask = 0x001111; //can touch player + } + } + //draw body + ctx.beginPath(); + const vertices = this.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1, len = vertices.length; j < len; ++j) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.fillStyle = `rgba(0,0,0,${this.alpha * this.alpha})`; + ctx.fill(); + } else if (this.canTouchPlayer) { + this.canTouchPlayer = false; + this.collisionFilter.mask = 0x000111; //can't touch player + } + }; + }, + ghoster(x, y, radius = 40 + Math.ceil(Math.random() * 100)) { + let me; + mobs.spawn(x, y, 7, radius, "transparent"); + me = mob[mob.length - 1]; + me.seeAtDistance2 = 1000000; + me.accelMag = 0.00014; + if (map.length) me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search + Matter.Body.setDensity(me, 0.00065); //normal is 0.001 //makes effective life much lower + me.stroke = "transparent"; //used for drawGhost + me.alpha = 1; //used in drawGhost + me.canTouchPlayer = false; //used in drawGhost + // me.leaveBody = false; + me.collisionFilter.mask = 0x000100; //move through walls and player + me.memory = 480; + me.do = function () { + //cap max speed + if (this.speed > 5) { + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x * 0.8, + y: this.velocity.y * 0.8 + }); + } + this.seePlayerCheckByDistance(); + this.attraction(); + this.search(); + //draw + if (this.distanceToPlayer2() - this.seeAtDistance2 < 0) { + if (this.alpha < 1) this.alpha += 0.004; + } else { + if (this.alpha > 0) this.alpha -= 0.03; + } + if (this.alpha > 0) { + if (this.alpha > 0.9) { + this.healthBar(); + if (!this.canTouchPlayer) { + this.canTouchPlayer = true; + this.collisionFilter.mask = 0x001110; //can touch player but not walls + } + } + //draw body + ctx.beginPath(); + const vertices = this.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1, len = vertices.length; j < len; ++j) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.lineWidth = 1; + ctx.strokeStyle = `rgba(0,0,0,${this.alpha * this.alpha})`; + ctx.stroke(); + } else if (this.canTouchPlayer) { + this.canTouchPlayer = false; + this.collisionFilter.mask = 0x000110; //can't touch player or walls + } + }; + }, + blinker(x, y, radius = 45 + Math.ceil(Math.random() * 70)) { + mobs.spawn(x, y, 6, radius, "transparent"); + let me = mob[mob.length - 1]; + Matter.Body.setDensity(me, 0.0005); //normal is 0.001 //makes effective life much lower + me.stroke = "rgb(0,200,255)"; //used for drawGhost + Matter.Body.rotate(me, Math.random() * 2 * Math.PI); + me.blinkRate = 40 + Math.round(Math.random() * 60); //required for blink + me.blinkLength = 150 + Math.round(Math.random() * 200); //required for blink + // me.collisionFilter.mask = 0x001100; //move through walls + me.isStatic = true; + // me.leaveBody = false; + me.memory = 360; + me.seePlayerFreq = 40 + Math.round(Math.random() * 30); + me.isBig = false; + me.scaleMag = Math.max(5 - me.mass, 1.75); + me.onDeath = function () { + if (this.isBig) { + Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag); + this.isBig = false; + } + }; + me.do = function () { + this.healthBar(); + this.seePlayerCheck(); + this.blink(); + //strike by expanding + if (this.isBig) { + if (this.cd - this.delay + 15 < game.cycle) { + Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag); + this.isBig = false; + } + } else if (this.seePlayer.yes && this.cd < game.cycle) { + const dist = Matter.Vector.sub(this.seePlayer.position, this.position); + const distMag2 = Matter.Vector.magnitudeSquared(dist); + if (distMag2 < 80000) { + this.cd = game.cycle + this.delay; + Matter.Body.scale(this, this.scaleMag, this.scaleMag); + this.isBig = true; + } + } + }; + }, + // drifter(x, y, radius = 15 + Math.ceil(Math.random() * 40)) { + // mobs.spawn(x, y, 4.5, radius, "transparent"); + // let me = mob[mob.length - 1]; + // me.stroke = "rgb(0,200,255)"; //used for drawGhost + // Matter.Body.rotate(me, Math.random() * 2 * Math.PI); + // me.blinkRate = 30 + Math.round(Math.random() * 30); //required for blink/drift + // me.blinkLength = 160; //required for blink/drift + // //me.collisionFilter.mask = 0x001100; //move through walls + // me.isStatic = true; + // me.memory = 360; + // me.seePlayerFreq = 40 + Math.round(Math.random() * 30); + // me.do = function () { + // this.healthBar(); + // this.seePlayerCheck(); + // this.drift(); + // }; + // }, + bomber(x, y, radius = 120 + Math.ceil(Math.random() * 70)) { + //boss that drops bombs from above and holds a set distance from player + mobs.spawn(x, y, 3, radius, "transparent"); + let me = mob[mob.length - 1]; + Matter.Body.setDensity(me, 0.0015 + 0.0005 * Math.sqrt(game.levelsCleared)); //extra dense //normal is 0.001 //makes effective life much larger + + me.stroke = "rgba(255,0,200)"; //used for drawGhost + me.seeAtDistance2 = 2000000; + me.fireFreq = Math.ceil(30 + 2000 / radius); + me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search + me.hoverElevation = 400 + (Math.random() - 0.5) * 200; //squared + me.hoverXOff = (Math.random() - 0.5) * 100; + me.accelMag = Math.floor(10 * (Math.random() + 5)) * 0.00001; + me.g = 0.0002; //required if using 'gravity' // gravity called in hoverOverPlayer + me.frictionStatic = 0; + me.friction = 0; + me.frictionAir = 0.01; + // me.memory = 300; + // Matter.Body.setDensity(me, 0.0015); //extra dense //normal is 0.001 + me.collisionFilter.mask = 0x001100; //move through walls + spawn.shield(me, x, y); + me.onDeath = function () { + if (Math.random() < 0.2 || mech.fieldMode === 0) powerUps.spawn(this.position.x, this.position.y, "field"); //bosss spawn field upgrades + }; + me.do = function () { + this.healthBar(); + this.seePlayerCheckByDistance(); + this.hoverOverPlayer(); + this.bomb(); + this.search(); + }; + }, + shooter(x, y, radius = 25 + Math.ceil(Math.random() * 50)) { + mobs.spawn(x, y, 3, radius, "rgb(255,100,150)"); + let me = mob[mob.length - 1]; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + me.memory = 120; + me.fireFreq = 0.007 + Math.random() * 0.005; + me.noseLength = 0; + me.fireAngle = 0; + me.accelMag = 0.0005; + me.frictionAir = 0.05; + me.lookTorque = 0.0000025 * (Math.random() > 0.5 ? -1 : 1); + me.fireDir = { + x: 0, + y: 0 + }; + if (Math.random() < Math.min(0.15 + (game.levelsCleared - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.fire(); + }; + }, + shooterBoss(x, y, radius = 85 + Math.ceil(Math.random() * 50)) { + //boss spawns on skyscraper level + mobs.spawn(x, y, 3, radius, "rgb(255,70,180)"); + let me = mob[mob.length - 1]; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + me.memory = 240; + me.fireFreq = 0.02; + me.noseLength = 0; + me.fireAngle = 0; + me.accelMag = 0.005; + me.frictionAir = 0.1; + me.lookTorque = 0.000005 * (Math.random() > 0.5 ? -1 : 1); + me.fireDir = { + x: 0, + y: 0 + }; + Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.levelsCleared)); //extra dense //normal is 0.001 //makes effective life much larger + spawn.shield(me, x, y); + me.onDeath = function () { + if (Math.random() < 0.2 || mech.fieldMode === 0) powerUps.spawn(this.position.x, this.position.y, "field"); //boss spawns field upgrades + }; + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.fire(); + }; + }, + bullet(x, y, radius = 6, sides = 0) { + //bullets + mobs.spawn(x, y, sides, radius, "rgb(255,0,0)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + me.onHit = function () { + this.explode(); + }; + Matter.Body.setDensity(me, 0.002); //normal is 0.001 + me.timeLeft = 240; + me.g = 0.001; //required if using 'gravity' + me.frictionAir = 0; + me.restitution = 0.8; + me.leaveBody = false; + me.dropPowerUp = false; + // me.collisionFilter.mask = 0x001000; + me.collisionFilter.category = 0x010000; + me.do = function () { + this.gravity(); + this.timeLimit(); + }; + }, + spawner(x, y, radius = 55 + Math.ceil(Math.random() * 50)) { + mobs.spawn(x, y, 4, radius, "rgb(255,150,0)"); + let me = mob[mob.length - 1]; + me.g = 0.0004; //required if using 'gravity' + me.leaveBody = false; + // me.dropPowerUp = false; + me.onDeath = function () { + //run this function on death + for (let i = 0; i < Math.ceil(this.mass * 0.2 + Math.random() * 3); ++i) { + spawn.spawns(this.position.x + (Math.random() - 0.5) * radius * 2, this.position.y + (Math.random() - 0.5) * radius * 2); + Matter.Body.setVelocity(mob[mob.length - 1], { + x: (Math.random() - 0.5) * 25, + y: (Math.random() - 0.5) * 25 + }); + } + }; + if (Math.random() < Math.min((game.levelsCleared - 1) * 0.1, 0.5)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.attraction(); + }; + }, + spawns(x, y, radius = 15 + Math.ceil(Math.random() * 5)) { + mobs.spawn(x, y, 4, radius, "rgb(255,0,0)"); + let me = mob[mob.length - 1]; + me.onHit = function () { + //run this function on hitting player + this.explode(); + }; + me.g = 0.0001; //required if using 'gravity' + me.accelMag = 0.0003; + me.memory = 30; + me.leaveBody = false; + me.seePlayerFreq = 80 + Math.round(Math.random() * 50); + me.frictionAir = 0.002; + me.do = function () { + this.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.attraction(); + }; + }, + exploder(x, y, radius = 25 + Math.ceil(Math.random() * 50)) { + mobs.spawn(x, y, 4, radius, "rgb(255,0,0)"); + let me = mob[mob.length - 1]; + me.onHit = function () { + //run this function on hitting player + this.explode(); + }; + me.g = 0.0004; //required if using 'gravity' + me.do = function () { + this.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.attraction(); + }; + }, + snaker(x, y, radius = 80) { + //snake boss with a laser head + mobs.spawn(x, y, 8, radius, "rgb(255,50,130)"); + let me = mob[mob.length - 1]; + me.accelMag = 0.0012; + me.memory = 200; + me.laserRange = 500; + Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.levelsCleared)); //extra dense //normal is 0.001 //makes effective life much larger + spawn.shield(me, x, y); + if (Math.random() < Math.min((game.levelsCleared - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + me.onDeath = function () { + if (Math.random() < 0.2 || mech.fieldMode === 0) powerUps.spawn(this.position.x, this.position.y, "field"); //boss spawns field upgrades + }; + me.do = function () { + this.healthBar(); + this.seePlayerCheck(); + this.attraction(); + this.laserBeam(); + // this.curl(); + }; + + //snake tail + const nodes = Math.min(3 + Math.ceil(Math.random() * game.levelsCleared + 2), 8) + spawn.lineBoss(x + 105, y, "spawns", nodes); + //constraint boss with first 3 mobs in lineboss + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - nodes], + bodyB: mob[mob.length - 1 - nodes], + stiffness: 0.05 + }); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - nodes + 1], + bodyB: mob[mob.length - 1 - nodes], + stiffness: 0.05 + }); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - nodes + 2], + bodyB: mob[mob.length - 1 - nodes], + stiffness: 0.05 + }); + + }, + tether(x, y, radius = 90) { + // constrained mob boss for the towers level + // often has a ring of mobs around it + mobs.spawn(x, y, 8, radius, "rgb(0,60,80)"); + let me = mob[mob.length - 1]; + me.g = 0.0001; //required if using 'gravity' + me.accelMag = 0.002; + me.memory = 20; + Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.levelsCleared)); //extra dense //normal is 0.001 //makes effective life much larger + spawn.shield(me, x, y); + if (Math.random() < Math.min((game.levelsCleared - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + + me.onDeath = function () { + if (Math.random() < 0.2 || mech.fieldMode === 0) powerUps.spawn(this.position.x, this.position.y, "field"); //boss spawns field upgrades + this.removeCons(); //remove constraint + }; + me.do = function () { + this.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.attraction(); + }; + }, + shield(target, x, y, stiffness = 0.4) { + if (this.allowShields) { + mobs.spawn(x, y, 9, target.radius + 20, "rgba(220,220,255,0.6)"); + let me = mob[mob.length - 1]; + me.stroke = "rgb(220,220,255)"; + Matter.Body.setDensity(me, 0.0001) //very low density to not mess with the original mob's motion + me.shield = true; + me.collisionFilter.mask = 0x000100; //don't collide with bodies, map, player, and mobs, only bullets + consBB[consBB.length] = Constraint.create({ + //attach shield to last spawned mob + bodyA: me, + bodyB: target, + stiffness: stiffness, + damping: 0.1 + }); + me.onDamage = function () { + //make sure the mob that owns the shield can tell when damage is done + this.alertNearByMobs(); + }; + me.leaveBody = false; + me.dropPowerUp = false; + //swap order of shield and mob, so that mob is behind shield graphically + mob[mob.length - 1] = mob[mob.length - 2]; + mob[mob.length - 2] = me; + me.do = function () {}; + } + }, + bossShield(nodes, x, y, radius) { + mobs.spawn(x, y, 9, radius, "rgba(220,220,255,0.65)"); + let me = mob[mob.length - 1]; + me.stroke = "rgb(220,220,255)"; + Matter.Body.setDensity(me, 0.00005) //very low density to not mess with the original mob's motion + me.frictionAir = 0; + me.shield = true; + me.collisionFilter.mask = 0x000100; //don't collide with bodies, map, and mobs, only bullets and player + //constrain to all mob nodes in boss + for (let i = 0; i < nodes; ++i) { + consBB[consBB.length] = Constraint.create({ + bodyA: me, + bodyB: mob[mob.length - i - 2], + stiffness: 0.4, + damping: 0.1 + }); + } + me.onDamage = function () { + //make sure the mob that owns the shield can tell when damage is done + this.alertNearByMobs(); + }; + me.leaveBody = false; + me.dropPowerUp = false; + mob[mob.length - 1] = mob[mob.length - 1 - nodes]; + mob[mob.length - 1 - nodes] = me; + me.do = function () {}; + }, + //complex constrained mob templates********************************************************************** + //******************************************************************************************************* + allowShields: true, + nodeBoss( + x, + y, + spawn = "striker", + nodes = Math.min(2 + Math.ceil(Math.random() * (game.levelsCleared + 2)), 8), + //Math.ceil(Math.random() * 3) + Math.min(4,Math.ceil(game.levelsCleared/2)), + radius = Math.ceil(Math.random() * 10) + 17, // radius of each node mob + sideLength = Math.ceil(Math.random() * 100) + 70, // distance between each node mob + stiffness = Math.random() * 0.03 + 0.005 + ) { + this.allowShields = false; //don't want shields on boss mobs + const angle = 2 * Math.PI / nodes + for (let i = 0; i < nodes; ++i) { + let whoSpawn = spawn; + if (spawn === "random") { + whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)]; + } else if (spawn === "randomList") { + whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + } + this[whoSpawn](x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius); + } + if (Math.random() < 0.3) { + this.constrain2AdjacentMobs(nodes, stiffness * 2, true); + } else { + this.constrainAllMobCombos(nodes, stiffness); + } + //spawn shield for entire boss + if (nodes > 2 && Math.random() < 0.998) { + this.bossShield(nodes, x, y, sideLength + 2.5 * radius + nodes * 6 - 25); + // this.bossShield(nodes, x, y, sideLength / (2 * Math.sin(Math.PI / nodes))); + } + this.allowShields = true; + }, + // nodeBoss( + // x, + // y, + // spawn = "striker", + // nodes = Math.min(2 + Math.round(Math.random() * game.levelsCleared), 8), + // //Math.ceil(Math.random() * 3) + Math.min(4,Math.ceil(game.levelsCleared/2)), + // radius = Math.ceil(Math.random() * 10) + 17, // radius of each node mob + // l = Math.ceil(Math.random() * 100) + 70, // distance between each node mob + // stiffness = Math.random() * 0.03 + 0.005 + // ) { + // this.allowShields = false; //dont' want shields on boss mobs + // let px = 0; + // let py = 0; + // let a = (2 * Math.PI) / nodes; + // for (let i = 0; i < nodes; ++i) { + // px += l * Math.cos(a * i); + // py += l * Math.sin(a * i); + // let whoSpawn = spawn; + // if (spawn === "random") { + // whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)]; + // } else if (spawn === "randomList") { + // whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + // } + // this[whoSpawn](x + px, y + py, radius); + // } + // if (Math.random() < 0.3) { + // this.constrain2AdjacentMobs(nodes, stiffness * 2, true); + // } else { + // this.constrainAllMobCombos(nodes, stiffness); + // } + // this.allowShields = true; + // }, + lineBoss( + x, + y, + spawn = "striker", + nodes = Math.min(3 + Math.ceil(Math.random() * game.levelsCleared + 2), 8), + //Math.ceil(Math.random() * 3) + Math.min(4,Math.ceil(game.levelsCleared/2)), + radius = Math.ceil(Math.random() * 10) + 17, + l = Math.ceil(Math.random() * 80) + 30, + stiffness = Math.random() * 0.06 + 0.01 + ) { + this.allowShields = false; //dont' want shields on boss mobs + for (let i = 0; i < nodes; ++i) { + let whoSpawn = spawn; + if (spawn === "random") { + whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)]; + } else if (spawn === "randomList") { + whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + } + this[whoSpawn](x + i * radius + i * l, y, radius); + } + this.constrain2AdjacentMobs(nodes, stiffness); + this.allowShields = true; + }, + //constraints ************************************************************************************************ + //************************************************************************************************************* + constrainAllMobCombos(nodes, stiffness) { + //runs through every combination of last 'num' bodies and constrains them + for (let i = 1; i < nodes + 1; ++i) { + for (let j = i + 1; j < nodes + 1; ++j) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - i], + bodyB: mob[mob.length - j], + stiffness: stiffness + }); + } + } + }, + constrain2AdjacentMobs(nodes, stiffness, loop = false) { + //runs through every combination of last 'num' bodies and constrains them + for (let i = 0; i < nodes - 1; ++i) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - i - 1], + bodyB: mob[mob.length - i - 2], + stiffness: stiffness + }); + } + if (nodes > 2) { + for (let i = 0; i < nodes - 2; ++i) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - i - 1], + bodyB: mob[mob.length - i - 3], + stiffness: stiffness + }); + } + } + //optional connect the tail to head + if (loop && nodes > 3) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - 1], + bodyB: mob[mob.length - nodes], + stiffness: stiffness + }); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - 2], + bodyB: mob[mob.length - nodes], + stiffness: stiffness + }); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - 1], + bodyB: mob[mob.length - nodes + 1], + stiffness: stiffness + }); + } + }, + constraintPB(x, y, bodyIndex, stiffness) { + cons[cons.length] = Constraint.create({ + pointA: { + x: x, + y: y + }, + bodyB: body[bodyIndex], + stiffness: stiffness + }); + }, + constraintBB(bodyIndexA, bodyIndexB, stiffness) { + consBB[consBB.length] = Constraint.create({ + bodyA: body[bodyIndexA], + bodyB: body[bodyIndexB], + stiffness: stiffness + }); + }, + // body and map spawns ****************************************************************************** + //********************************************************************************************** + wireHead() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1300 + mobs.spawn(breakingPoint, -100, 0, 7.5, "transparent"); + let me = mob[mob.length - 1]; + //touch only walls + me.collisionFilter.category = 0x100000; + me.collisionFilter.mask = 0x100001; + me.inertia = Infinity; + me.g = 0.0004; //required for gravity + me.restitution = 0; + me.stroke = "transparent" + me.freeOfWires = false; + me.frictionStatic = 1; + me.friction = 1; + me.frictionAir = 0.01; + + me.do = function () { + let wireX = -50; + let wireY = -1000; + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.fill = "#000" + this.force.x += -0.003; + player.force.x += 0.06; + // player.force.y -= 0.15; + } + + //player is extra heavy from wires + Matter.Body.setVelocity(player, { + x: player.velocity.x, + y: player.velocity.y + 0.3 + }) + + //player friction from the wires + if (mech.pos.x > 700 && player.velocity.x > -2) { + let wireFriction = 0.75 * Math.min(0.6, Math.max(0, 100 / (breakingPoint - mech.pos.x))); + if (!mech.onGround) wireFriction *= 3 + Matter.Body.setVelocity(player, { + x: player.velocity.x - wireFriction, + y: player.velocity.y + }) + } + //move to player + Matter.Body.setPosition(this, { + x: mech.pos.x + (42 * Math.cos(mech.angle + Math.PI)), + y: mech.pos.y + (42 * Math.sin(mech.angle + Math.PI)) + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + if (!this.freeOfWires) ctx.lineTo(mech.pos.x + (30 * Math.cos(mech.angle + Math.PI)), mech.pos.y + (30 * Math.sin(mech.angle + Math.PI))); + ctx.lineCap = "butt"; + ctx.lineWidth = 15; + ctx.strokeStyle = "#000"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + wireKnee() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1425 + mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); + let me = mob[mob.length - 1]; + //touch only walls + me.collisionFilter.category = 0x100000; + me.collisionFilter.mask = 0x100001; + me.g = 0.0003; //required for gravity + // me.restitution = 0; + me.stroke = "transparent" + // me.inertia = Infinity; + me.restitution = 0; + me.freeOfWires = false; + me.frictionStatic = 1; + me.friction = 1; + me.frictionAir = 0.01; + + me.do = function () { + let wireX = -50 - 20; + let wireY = -1000; + + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.force.x -= 0.0004; + this.fill = "#222"; + } + //move mob to player + mech.calcLeg(0, 0); + Matter.Body.setPosition(this, { + x: mech.pos.x + mech.flipLegs * mech.knee.x - 5, + y: mech.pos.y + mech.knee.y + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + ctx.lineWidth = 5; + ctx.strokeStyle = "#222"; + ctx.lineCap = "butt"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + wireKneeLeft() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1400 + mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); + let me = mob[mob.length - 1]; + //touch only walls + me.collisionFilter.category = 0x100000; + me.collisionFilter.mask = 0x100001; + me.g = 0.0003; //required for gravity + // me.restitution = 0; + me.stroke = "transparent" + // me.inertia = Infinity; + me.restitution = 0; + me.freeOfWires = false; + me.frictionStatic = 1; + me.friction = 1; + me.frictionAir = 0.01; + + me.do = function () { + let wireX = -50 - 35; + let wireY = -1000; + + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.force.x += -0.0003; + this.fill = "#333"; + } + //move mob to player + mech.calcLeg(Math.PI, -3); + Matter.Body.setPosition(this, { + x: mech.pos.x + mech.flipLegs * mech.knee.x - 5, + y: mech.pos.y + mech.knee.y + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + ctx.lineWidth = 5; + ctx.lineCap = "butt"; + ctx.strokeStyle = "#333"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + wireFoot() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1350 + mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); + let me = mob[mob.length - 1]; + //touch only walls + me.collisionFilter.category = 0x100000; + me.collisionFilter.mask = 0x100001; + me.g = 0.0003; //required for gravity + me.restitution = 0; + me.stroke = "transparent" + // me.inertia = Infinity; + me.freeOfWires = false; + // me.frictionStatic = 1; + // me.friction = 1; + me.frictionAir = 0.01; + + me.do = function () { + let wireX = -50 + 16; + let wireY = -1000; + + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.force.x += -0.0006; + this.fill = "#222"; + } + //move mob to player + mech.calcLeg(0, 0); + Matter.Body.setPosition(this, { + x: mech.pos.x + mech.flipLegs * mech.foot.x - 5, + y: mech.pos.y + mech.foot.y - 1 + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + ctx.lineWidth = 5; + ctx.lineCap = "butt"; + ctx.strokeStyle = "#222"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + wireFootLeft() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1325 + mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); + let me = mob[mob.length - 1]; + //touch only walls + me.collisionFilter.category = 0x100000; + me.collisionFilter.mask = 0x100001; + me.g = 0.0003; //required for gravity + me.restitution = 0; + me.stroke = "transparent" + // me.inertia = Infinity; + me.freeOfWires = false; + // me.frictionStatic = 1; + // me.friction = 1; + me.frictionAir = 0.01; + + me.do = function () { + let wireX = -50 + 26; + let wireY = -1000; + + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.force.x += -0.0005; + this.fill = "#333"; + } + //move mob to player + mech.calcLeg(Math.PI, -3); + Matter.Body.setPosition(this, { + x: mech.pos.x + mech.flipLegs * mech.foot.x - 5, + y: mech.pos.y + mech.foot.y - 1 + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + ctx.lineWidth = 5; + ctx.strokeStyle = "#333"; + ctx.lineCap = "butt"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + boost(x, y, height = 1000) { + spawn.mapVertex(x + 50, y + 35, "120 40 -120 40 -50 -40 50 -40"); + // level.addZone(x, y, 100, 30, "fling", {Vx:Vx, Vy: Vy}); + level.addQueryRegion(x, y - 20, 100, 20, "boost", [ + [player], body, mob, powerUp, bullet + ], -1.1 * Math.sqrt(Math.abs(height))); + let color = "rgba(200,0,255,"; + level.fillBG.push({ + x: x, + y: y - 25, + width: 100, + height: 25, + color: color + "0.2)" + }); + level.fillBG.push({ + x: x, + y: y - 55, + width: 100, + height: 55, + color: color + "0.1)" + }); + level.fillBG.push({ + x: x, + y: y - 120, + width: 100, + height: 120, + color: color + "0.05)" + }); + }, + laserZone(x, y, width, height, dmg) { + level.addZone(x, y, width, height, "laser", { + dmg + }); + level.fill.push({ + x: x, + y: y, + width: width, + height: height, + color: "#f00" + }); + }, + deathQuery(x, y, width, height) { + level.addQueryRegion(x, y, width, height, "death", [ + [player], mob + ]); + level.fill.push({ + x: x, + y: y, + width: width, + height: height, + color: "#f00" + }); + }, + platform(x, y, width, height) { + const size = 20; + spawn.mapRect(x, y + height, width, 30); + level.fillBG.push({ + x: x + width / 2 - size / 2, + y: y, + width: size, + height: height, + color: "#f0f0f3" + }); + }, + debris(x, y, width, number = Math.floor(3 + Math.random() * 11)) { + for (let i = 0; i < number; ++i) { + if (Math.random() < 0.15) { + powerUps.chooseRandomPowerUp(x + Math.random() * width, y); + } else { + const size = 18 + Math.random() * 25; + spawn.bodyRect(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random()), 1); + // body[body.length] = Bodies.rectangle(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random())); + } + } + }, + bodyRect( + x, + y, + width, + height, + chance = 1, + properties = { + friction: 0.05, + frictionAir: 0.01 + } + ) { + if (Math.random() < chance) { + body[body.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties); + } + }, + bodyVertex(x, y, vector, properties) { + //addes shape to body array + body[body.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties); + }, + mapRect(x, y, width, height, properties) { + //addes reactangles to map array + var len = map.length; + map[len] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties); + }, + mapVertex(x, y, vector, properties) { + //addes shape to map array + var len = map.length; + map[len] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties); + }, + //complex map templates + spawnBuilding(x, y, w, h, leftDoor, rightDoor, walledSide) { + this.mapRect(x, y, w, 25); //roof + this.mapRect(x, y + h, w, 35); //ground + if (walledSide === "left") { + this.mapRect(x, y, 25, h); //wall left + } else { + this.mapRect(x, y, 25, h - 150); //wall left + if (leftDoor) { + this.bodyRect(x + 5, y + h - 150, 15, 150, this.propsFriction); //door left + } + } + if (walledSide === "right") { + this.mapRect(x - 25 + w, y, 25, h); //wall right + } else { + this.mapRect(x - 25 + w, y, 25, h - 150); //wall right + if (rightDoor) { + this.bodyRect(x + w - 20, y + h - 150, 15, 150, this.propsFriction); //door right + } + } + }, + spawnStairs(x, y, num, w, h, stepRight) { + w += 50; + if (stepRight) { + for (let i = 0; i < num; i++) { + this.mapRect(x - (w / num) * (1 + i), y - h + (i * h) / num, w / num + 50, h - (i * h) / num + 50); + } + } else { + for (let i = 0; i < num; i++) { + this.mapRect(x + (i * w) / num, y - h + (i * h) / num, w / num + 50, h - (i * h) / num + 50); + } + } + }, + //premade property options************************************************************************************* + //************************************************************************************************************* + //Object.assign({}, propsHeavy, propsBouncy, propsNoRotation) //will combine properties into a new object + propsFriction: { + friction: 0.5, + frictionAir: 0.02, + frictionStatic: 1 + }, + propsFrictionMedium: { + friction: 0.15, + frictionStatic: 1 + }, + propsBouncy: { + friction: 0, + frictionAir: 0, + frictionStatic: 0, + restitution: 1 + }, + propsSlide: { + friction: 0.003, + frictionStatic: 0.4, + restitution: 0, + density: 0.002 + }, + propsLight: { + density: 0.001 + }, + propsOverBouncy: { + friction: 0, + frictionAir: 0, + frictionStatic: 0, + restitution: 1.05 + }, + propsHeavy: { + density: 0.01 //default density is 0.001 + }, + propsIsNotHoldable: { + isNotHoldable: true + }, + propsNoRotation: { + inertia: Infinity //prevents rotation + }, + propsHoist: { + inertia: Infinity, //prevents rotation + frictionAir: 0.001, + friction: 0, + frictionStatic: 0, + restitution: 0, + isNotHoldable: true + // density: 0.0001 + }, + propsDoor: { + density: 0.001, //default density is 0.001 + friction: 0, + frictionAir: 0.03, + frictionStatic: 0, + restitution: 0 + }, + sandPaper: { + friction: 1, + frictionStatic: 1, + restitution: 0 + } +}; \ No newline at end of file diff --git a/sounds/ammo.ogg b/sounds/ammo.ogg new file mode 100644 index 0000000000000000000000000000000000000000..be7c28e523852f7444abd507fee3281c1a275647 GIT binary patch literal 6011 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 PM^e(RQLd=6l)?W1NIX43 literal 0 HcmV?d00001 diff --git a/sounds/boom.ogg b/sounds/boom.ogg new file mode 100644 index 0000000000000000000000000000000000000000..f757ceda010cf2191eeab0073934628851dab39a GIT binary patch literal 8297 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 literal 0 HcmV?d00001 diff --git a/sounds/click.ogg b/sounds/click.ogg new file mode 100644 index 0000000000000000000000000000000000000000..1fc734c1fc8f6d94d5f7a74d0c80a914659a0e63 GIT binary patch literal 5824 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 literal 0 HcmV?d00001 diff --git a/sounds/dmg/dmg0.ogg b/sounds/dmg/dmg0.ogg new file mode 100644 index 0000000000000000000000000000000000000000..e45402db51b72d008a1ccc2d5192d5a806bd7e28 GIT binary patch literal 5671 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 F{{Ud-Vh8{L literal 0 HcmV?d00001 diff --git a/sounds/dmg/dmg2.ogg b/sounds/dmg/dmg2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..95ba12f4695ffb54806a0bbaaf444dae15688a28 GIT binary patch literal 5636 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 AaR2}S literal 0 HcmV?d00001 diff --git a/sounds/dmg/dmg3.ogg b/sounds/dmg/dmg3.ogg new file mode 100644 index 0000000000000000000000000000000000000000..1d9a32cc641c748aa29a6895561f74a4084c3515 GIT binary patch literal 6016 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 O`Ae-dcfXTURs9d+T_S7% literal 0 HcmV?d00001 diff --git a/sounds/guns/airgun.ogg b/sounds/guns/airgun.ogg new file mode 100644 index 0000000000000000000000000000000000000000..0325bc1184e090554ce5659306bc5947d117a230 GIT binary patch literal 7175 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 b%utKV?7;q)YoXj literal 0 HcmV?d00001 diff --git a/sounds/guns/bass.ogg b/sounds/guns/bass.ogg new file mode 100644 index 0000000000000000000000000000000000000000..7976251c912f6814a1a3358c39634ce140da77fa GIT binary patch literal 9252 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 literal 0 HcmV?d00001 diff --git a/sounds/guns/glock.ogg b/sounds/guns/glock.ogg new file mode 100644 index 0000000000000000000000000000000000000000..4402b0990bf070aeade2a0c15ffa4527ae279e3f GIT binary patch literal 11918 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 D-??HM literal 0 HcmV?d00001 diff --git a/sounds/guns/reload.ogg b/sounds/guns/reload.ogg new file mode 100644 index 0000000000000000000000000000000000000000..f45f720dc8f42e641bae05a9fa50b430a7c0127b GIT binary patch literal 5824 zcmcImc{tSF+y9P~T`EbXGP2AN84n4mEQzr;5=QYfOtQz=ml9)=hQy2|WF3#e2$?CP zsAO!BtXU$;Iz-vZSl;jG`Tefzy{`A)_gvTa%zfXVbKmDa_j2Y~y1H5eJix!BN%s%2 zB^2)sISL8%@x!>`xFX0Nr9a4v*gwDN5KFG+e+O3+0&?n)NsdA~TmKpK9e-?awe8$) z-BhvkbCvdV!(7?6ENvpKrixHgRa5;-8fNL`?RLu#i^F>R!?|W5JO8Ne)i<~TT803^ zC~>IVL2INlKN*FB?>ypc<#6$cbGMce)J4oX*5n{1%uDQHm^g>HSAB=9*~)lHQ@1z#*Ku?7o*|@rgQPPN!D<+@hDCKG z+H42mJTGM1XF&jXYbAW)QY05RzYB;2#4q@YH~LClOpHJa zQLHo!@lt!DwJ>5Qg3tkv5$m?lq6o|I8yIJ~v`d@nPJO{lo)$Y)DFeiUyAkz>P*x&N z7`Y4TmtfK)ZP=~_&mx&2VYI3g69Hv%ugqj7$pR@)sggjo7La=Nm39IE?`iO}vOV4D^5t^ zh1Iw@6a_yC{amOLPvmI`5H0KWnBtaq)9o=Vw6Lf+4^~_}9#S}+URWHOM=dTaW|mQl z%RED#xN*@Z@Zw^S^(Zc8#}&J`7M2V!ri&S~EtTFim8FAEidz^|W~Fy=nO9AvC%f## zAoE*D@l1LlJG5|`Si%)QvGc8O{N48QmyP(3IL_0jL3^R9!*3!Et6tR!)Jaz9OZd6KO)jwwpBPbD~~zW-mYV5~?&Uxl<1&7fnQ(Q0PfWr9Y-(P-qWYPa4WpgOmiCY5@13(GOWGX~bBe zguspg-KAgga93tn>3ifBNSG=j=n~H0=8_;#u-5@jZIEz?r_t@KO*K-IFrKuYPe^60 zUbF{NxwjKVq|wtH%&HsgJVfNSO@Uq^X$`$-8h3NW3UU&ftAQqJkUBANI6V>R37T^7 z)F@2Bcvke-p&lSub~sad4+;GM1Wc7QSdt#z8sjJccpbR|I3Fp1&FM%a1Lws7;BR4P zqTWq$zxP`3cvQQVf*ej%pC68G*W-s$+O_y8Roz;Axg?T498M8;fXPvM(I)^P=QRY7 ze!a{5egi~M#9ja}1X&iWK@!iBrbtG2Nal-!$)-qxrYPdh?Rrg;xOP4GL6B0A!;9&| zDc}U7kia`gPzt>GJUd8sQlLZoA%7gg2qOs6{wQtu9=sV+f%0fCS_qC!LB`3k-lOl+ z-hDC_pvCm6dC0LkF-Ej^b{Itfm<1i0eXS2ucaZ}U`GE6W58Aa{#W3LY2vz4tfHx0V zeVN>|?FRuqD85~55-djj$xNJ>K7VdBI8jKQ;Gyv-`c_UN5T*hlbHrc1S<{K z4rhU})SBc1pzB-!WVryB1*e?oL@w2#H^pW7q5hx@Jp2E3h*9n#NGK$n0!~Hw|57|q zF1s!J|DMSIdmli0M34{I;ydhWXQHhnC8ENOgBbW%Az3epCvt;LK=4!iEs!R18bm!9 z1-BQ}kM2ObgI$k89>kL{LQpaZEePHT9&jq1go42v4w)jUlwP$-*@7grDUFe)#YZXV zMZ41)#PpFB^j?%lMF9A3Powvuy_5?`Xb)vpFUC`jl7jZ2g6(ETrAs)Na%HBphEEu< zs}2bPU%YPtk;dh|1aiT%w;#W3*8>y+@Twufz;o*eii$&kdEiopNW{b+86Hd(x2(X5 zO5CnS5XGakL75d&fE#gcmbf*DLJB}Z3TzMXbRhL_D!6^~NA_>?Y{%}uGSc4dTHRj! z$I5o{VBB)vrUc~vn^ow4DGFEtJGeLX@hRE-78y_V(YHIqz$X_Ez+(p7g+xVN;^)Ed z;pe`>0Dj;X02m8K#>U>95Ed2ND=r~}u}dJ_f-i2U6c` zN*ulEh4pgvy6NG01M7VArkjVW*9{+6clVp1%-z)?;am8&IgW^E&iSD~k<&Vv5 z2|mvlcMRM}zLD6=ta`CEVsNog)Vw)VD&o=9Zyz zzr0;OEjClSlAB)ao(wZ02FkDa^A|A_^=m&Y2>QG(l*EZH^hg}rj9qX0 zuA2FR;4H1ptmfb~%~vLGy9NDoo2j$rdNq7s&o!NRx9KY0vpfkh{kmcK()un@X_0w6 zX9~Spp9ySizEKwO)zS5j46?^sn)bj_>ax~wEoa?~zIf?m`uINFD&Y&vl;N|r@H3~n z+k;^}dP`Jgnn3lvGv-?1wWGW`eUOl|(=ZZMnR4-=-fnLPIfF1evYtu82r{Xn_=)6TXhR0uSTk?O$Z!);eosKX4J+QfS_(*V>?rXjv-_iX zb5!KN$OdFM&2?3rsX}soc4a5WW5;?^eQ%?BpZ#uaim=G{OUu+rXcinYqYH8C8C#M_qeI)^F`iV#UjEB1M3H^>= zOIlH33nBg)k8S$I-*mZZz+t_(HRcJ4W}0`n*Y`K4%1%8z6VbDTeAn^xV1}3Fejx6u z*mX8KHC{AUAZGyL8Rl~b%l`Rmgs=5gY9VK6({E;)DfgVWb|7OlIMy`%1|3Ij?!WYv zHThJt^5EE#A#K(4-QzC*RYATx0m@d&Hnp>t+VTvI+h1m?h~EB7PVVBvXBm38#)hmh zJdgw_@+;+@B;bp}>jSrfIT6&26@znEoXiGlpB;-aaQfi*7hx!wq-*~HH_>4PJrOc8 zCE~T#TBSfpFiJ68_kE;Tg4gI9eSyEe=>>h?hKtZz>Jc$Jk+bthMUHj<^oyfcBJg#x z^@>7RQyMG|ZRJ)EIRM-yW;3?Vs8?T4F599U$w>V$tCJl@J79NjvC3%p0bxQTn=No4 zzwSe2-5xyEye{YE?Q{F~BR)&Lx5zpd+gE<%ZsxABeY-Ik!9NC`v=u<$$@&u$LV2*Y z#F_52PY}XE{Iln+y+?prCXc_kZgzNPSz~wm?&_fpQjzDWhowIKjKYt~>eM?4DcZj| z)C0PrQJKBjS)IlO;@2KLv|mu#Y@W5nrPM_Pl3J}lf7_41F|0;K+S5n+ut`4rdsd9v z_9X2~uocVSJ8x$(!n>SIe^GZFia2OLh)tVucVERY;q zm35MkLB6fviKG4fPv&B8&f*21DH|09&2p8R+Tm=`!o?TANyz-LW|@`U_e-fGkKHUU zB#c}=rL??rN3Ov&;q|`S)!lb~yrvyIxpvahm3Xf?BczjY(PARu?1m!KaQ27~^&W5` zv+c)1Aa&CF&Mp@z=b)(2I~#jl)vvgeyGR`gyE>dFl(<3JiYwc+_cndex)$hEIW3iNtKPk$Ormnwtc($ zQsc2tg0y0-lkV0Yq(W5Pu6T%P?$ej;s$Tw!lSw+z7UzkVo;vHKMa3rakhW0i_`6dW z4EjjIi04IzNp&kAwt~miyMM)V5b^)BoGYUNL>=?1_MrZpg{*1>t33+R0zA#fq5!j5kG~F1j9(Pu4>&~igT8r4dU8erh z4|VMK$9*NTlnnUryytoDYdDs6ui!3KK*Pfq1r7&1_T6NxpLH;AAzK7hHu~2mTd~k# zMT5kH@{G~TSAJIQy^yXgl^!TMc~WQPboxc1(N)#fe!1w$qIaAJp4p@kJyYYXziLU-)tl5VPI<~9gEbsmBYLCMi$=C2aB4`h}!=fMiA$|F- zp|!^^NAHSr5_>fpjE)0}fC`?gN?Y}Y4iW76i=30>Hq<{2m-weq69RQhU&l+dhXl{o z2Wy6wAHY?hYqr)VH-1%Ar&E6X&9gC-t;RRlqdL?QB-a|>|Ezz`+2;@=77|#@Rts5p zD(d>YR+V6((68&KJ;fa}PZ8C7#gvxjAIA-^~r5ek*p@%z^A)4xVH_#dbaNuvt5M z5q0od(Bu7kPV9oD+&pcs>>zC;ZXAQ>F*_R`GHF%!o%QN-G{@-!(tN)N#0!6BgVd+kl25gFC<ON;ZB4Kh>PVe)V<4 zDY8ry6YA4`S!gSo{9bjow>itv8gcyD?R6{pQ|}qkh3389D&v#{%a629?Q_dn0si`* zdIF5D2QRxhD!jA(}ZlQpc^W{RkBMxodRaJ7ZRxq+!LGv2pBcwdW@UCxV&TZ ztt+nuFxD6ehWIfibm({Zd`HGW2*@_9ai! zaMooyBKq=hYy`T&2(u4$TuJ`e+25UNTN#I*sisZkC_xBUVfLS2h+KYhlLk1|WrS~1 z=}WKPoL*c1{x$cg=~KCC9wP)2mnvWASPsQf0Kan`LzH>S&`QgQwQ3=5+s0k$09!6nWuuZ|TJH7M# zr=K0OufB6^L+=cCow#meG@!I^y3s|Z3zsf+qgeLUi$Th%+yBhT;)Xft^3<=($}ZFI z$2kI-7dD^1l?~M5ZMvVYp827l3X8o`TIf^wd%T4cd%q+@%@?@Q5~^7)FKERHo5)t* zT6GjiCPNv-c*N>12dAy1@RPNN8x{tjQU}i4`nsL<(F+iT$?(BbPN@vOm<_r2Sl#lC fuZ@V1N0E@nEsShiU%rR*$0xJVbbkfP8svWf=tao^ literal 0 HcmV?d00001 diff --git a/sounds/guns/shotgun.mp3 b/sounds/guns/shotgun.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..3002ada28ee5cd71ffcef1b95eebb3cad4472f8a GIT binary patch literal 39729 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 LU3uWc0qwp4(QiwG literal 0 HcmV?d00001 diff --git a/sounds/guns/snare2.ogg b/sounds/guns/snare2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..34f21b73421bf4ce9124a28905d031bf5bff1cc5 GIT binary patch literal 12855 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 Yb{eZzTh5Z)gfLMhj|R3dEei0z0IbM=m;e9( literal 0 HcmV?d00001 diff --git a/sounds/guns/sniper.ogg b/sounds/guns/sniper.ogg new file mode 100644 index 0000000000000000000000000000000000000000..48254b868782169a40c09f84f50945491de7d8a3 GIT binary patch literal 10596 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 literal 0 HcmV?d00001 diff --git a/sounds/powerup4.ogg b/sounds/powerup4.ogg new file mode 100644 index 0000000000000000000000000000000000000000..b42ba702aa0131891eb53a8566ecdda464e6b9fd GIT binary patch literal 6249 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 literal 0 HcmV?d00001 diff --git a/style.css b/style.css new file mode 100644 index 0000000..f1dfdd3 --- /dev/null +++ b/style.css @@ -0,0 +1,214 @@ +body { + font-family: "Helvetica", "Arial", sans-serif; + margin: 0; + overflow: hidden; + background-color: #fff; + user-select: none; + /*cursor: crosshair;*/ +} + +canvas { + position: absolute; + top: 0; + left: 0; + z-index: 0; +} + +#splash { + user-select: none; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 2; +} + +*:focus { + outline: none; +} + +table { + border-collapse: collapse; + /* border: 1px solid #eee; */ + width: 360px; + /* background-color: #ddd; */ +} + +summary { + font-size: 1.2em; +} + +#controls { + position: absolute; + bottom: 0px; + left: 1px; + z-index: 12; + font-size: 1.3em; + /* background-color: #ccc; */ + /* border-radius: 5px; */ +} + +#controls-div { + padding: 10px; + border-radius: 8px; + background-color: #ddd; +} + +#dmg { + position: absolute; + z-index: 2; + width: 100%; + height: 100%; + display: none; + background-color: #f67; + opacity: 0; + transition: opacity 1s; +} + +#health-bg { + position: absolute; + top: 30px; + left: 40px; + height: 20px; + width: 300px; + background-color: #000; + opacity: 0.1; + z-index: 1; + pointer-events: none; + display: none; +} + +#health { + position: absolute; + top: 30px; + left: 40px; + height: 20px; + width: 0px; + transition: width 1s ease-out; + opacity: 0.6; + z-index: 2; + pointer-events: none; + background-color: #f00; +} + +.low-health { + animation: blink 250ms infinite alternate; +} + +@keyframes blink { + from { + opacity: 0.9; + } + + to { + opacity: 0.2; + } +} + +#fade-out { + position: absolute; + z-index: 2; + width: 100%; + height: 100%; + background-color: #fff; + opacity: 0; + transition: opacity 5s; + pointer-events: none; +} + +#guns { + position: absolute; + top: 55px; + left: 40px; + z-index: 2; + font-size: 25px; + color: #111; + background-color: rgba(255, 255, 255, 0.4); + user-select: none; + pointer-events: none; + padding: 0px 5px 0px 5px; + border-radius: 5px; + /*border: 2px solid rgba(0, 0, 0, 0.4);*/ +} + +#text-log { + position: absolute; + top: 20px; + left: 0; + width: 100%; + line-height: 180%; + text-align: center; + z-index: 2; + font-size: 1.3em; + color: #000; + opacity: 0; + transition: opacity 0.5s; + pointer-events: none; + user-select: none; +} + +.box { + padding: 3px 8px 3px 8px; + border: 2px solid #444; + border-radius: 4px; + background-color: rgba(255, 255, 255, 0.5); +} + +.wrapper { + display: grid; + grid-template-columns: 360px 10px; + align-self: center; + justify-content: center; +} + +.grid-box { + align-self: center; + justify-self: center; +} + +.mouse { + color: #ccc; + position: relative; + padding: 37px 30px 37px 30px; + border-radius: 25px; + border: 2px solid #444; + background-color: rgba(255, 255, 255, 0.5); +} + +.mouse:after { + content: ""; + position: absolute; + z-index: 1; + top: 4px; + left: 26px; + border-radius: 25px; + /* background: #444; */ + border: 2px solid #444; + + width: 4px; + height: 20px; + border-radius: 10px / 25px; +} + +.mouse-line { + position: relative; + top: 30px; + left: 0px; +} + +.mouse-line:after { + content: ""; + position: absolute; + z-index: 1; + top: -35px; + left: -30px; + width: 60px; + height: 2px; + border-radius: 8px; + background: #444; +} + +.right { + text-align: right; +} \ No newline at end of file