commit b6f21d76de6a9544b9b46dedb23401eea14791c9 Author: lilgreenland Date: Thu Jul 11 10:04:58 2019 -0700 tranfered files from website 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 0000000..f816713 Binary files /dev/null and b/favicon.ico differ 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 0000000..be7c28e Binary files /dev/null and b/sounds/ammo.ogg differ diff --git a/sounds/boom.ogg b/sounds/boom.ogg new file mode 100644 index 0000000..f757ced Binary files /dev/null and b/sounds/boom.ogg differ diff --git a/sounds/click.ogg b/sounds/click.ogg new file mode 100644 index 0000000..1fc734c Binary files /dev/null and b/sounds/click.ogg differ diff --git a/sounds/dmg/dmg0.ogg b/sounds/dmg/dmg0.ogg new file mode 100644 index 0000000..e45402d Binary files /dev/null and b/sounds/dmg/dmg0.ogg differ diff --git a/sounds/dmg/dmg1.ogg b/sounds/dmg/dmg1.ogg new file mode 100644 index 0000000..9826a50 Binary files /dev/null and b/sounds/dmg/dmg1.ogg differ diff --git a/sounds/dmg/dmg2.ogg b/sounds/dmg/dmg2.ogg new file mode 100644 index 0000000..95ba12f Binary files /dev/null and b/sounds/dmg/dmg2.ogg differ diff --git a/sounds/dmg/dmg3.ogg b/sounds/dmg/dmg3.ogg new file mode 100644 index 0000000..1d9a32c Binary files /dev/null and b/sounds/dmg/dmg3.ogg differ diff --git a/sounds/guns/airgun.ogg b/sounds/guns/airgun.ogg new file mode 100644 index 0000000..0325bc1 Binary files /dev/null and b/sounds/guns/airgun.ogg differ diff --git a/sounds/guns/bass.ogg b/sounds/guns/bass.ogg new file mode 100644 index 0000000..7976251 Binary files /dev/null and b/sounds/guns/bass.ogg differ diff --git a/sounds/guns/basssnaredrum.ogg b/sounds/guns/basssnaredrum.ogg new file mode 100644 index 0000000..87b3862 Binary files /dev/null and b/sounds/guns/basssnaredrum.ogg differ diff --git a/sounds/guns/blaster.ogg b/sounds/guns/blaster.ogg new file mode 100644 index 0000000..180489f Binary files /dev/null and b/sounds/guns/blaster.ogg differ diff --git a/sounds/guns/glock.ogg b/sounds/guns/glock.ogg new file mode 100644 index 0000000..4402b09 Binary files /dev/null and b/sounds/guns/glock.ogg differ diff --git a/sounds/guns/launcher.ogg b/sounds/guns/launcher.ogg new file mode 100644 index 0000000..6190db2 Binary files /dev/null and b/sounds/guns/launcher.ogg differ diff --git a/sounds/guns/launcher2.ogg b/sounds/guns/launcher2.ogg new file mode 100644 index 0000000..dfe7f00 Binary files /dev/null and b/sounds/guns/launcher2.ogg differ diff --git a/sounds/guns/reload.ogg b/sounds/guns/reload.ogg new file mode 100644 index 0000000..f45f720 Binary files /dev/null and b/sounds/guns/reload.ogg differ diff --git a/sounds/guns/shotgun.mp3 b/sounds/guns/shotgun.mp3 new file mode 100644 index 0000000..3002ada Binary files /dev/null and b/sounds/guns/shotgun.mp3 differ diff --git a/sounds/guns/snare.ogg b/sounds/guns/snare.ogg new file mode 100644 index 0000000..df9a83a Binary files /dev/null and b/sounds/guns/snare.ogg differ diff --git a/sounds/guns/snare2.ogg b/sounds/guns/snare2.ogg new file mode 100644 index 0000000..34f21b7 Binary files /dev/null and b/sounds/guns/snare2.ogg differ diff --git a/sounds/guns/sniper.ogg b/sounds/guns/sniper.ogg new file mode 100644 index 0000000..48254b8 Binary files /dev/null and b/sounds/guns/sniper.ogg differ diff --git a/sounds/no.ogg b/sounds/no.ogg new file mode 100644 index 0000000..7f358e6 Binary files /dev/null and b/sounds/no.ogg differ diff --git a/sounds/powerup4.ogg b/sounds/powerup4.ogg new file mode 100644 index 0000000..b42ba70 Binary files /dev/null and b/sounds/powerup4.ogg differ 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