From a57ff0c4c17125d083921719f7de256a897f6909 Mon Sep 17 00:00:00 2001 From: landgreen Date: Wed, 4 Nov 2020 05:36:50 -0800 Subject: [PATCH] overfill update to mod: anthropic principle - only works once per level but gives 6 seconds of damage immunity and 2 extra heal power ups most energy regeneration effects now overfill energy above the max by default piezo electricity over fills energy by 300% (was 100%) --- index.html | 756 ++++++++--------- js/bullet.js | 9 +- js/engine.js | 354 ++++---- js/game.js | 23 +- js/level.js | 16 +- js/mob.js | 2250 +++++++++++++++++++++++++------------------------- js/mods.js | 20 +- js/player.js | 25 +- js/spawn.js | 207 ++--- todo.txt | 50 +- 10 files changed, 1845 insertions(+), 1865 deletions(-) diff --git a/index.html b/index.html index 0e37d1e..8b2c748 100644 --- a/index.html +++ b/index.html @@ -2,57 +2,57 @@ - - - + + function gtag() { + dataLayer.push(arguments); + } + gtag('js', new Date()); + gtag('config', 'UA-113454647-1'); + - - - - - - - - - + + + + + + + + + - n-gon - - + n-gon + + - -
-
-
-
-
-
-
-
-
-
- + +
+
+
+
+
+
+
+
+
+
+ - - + - - + - - + - - + - - -
-
-
-
-
-
-
-
- - - custom - - -
-
-
- settings -
- - -
- - - -
- - -
- - -
-
-
-
-
- controls -
- To change controls click a box
- and press an unused key. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FIREMouseLeft
FIELDSPACEMouseRight
JUMPWArrowUp
CROUCHSArrowDown
LEFTAArrowLeft
RIGHTDArrowRight
GUN →QMouseWheel
GUN ←EMouseWheel
PAUSEP
TESTINGT
- to default keys -
-
-
-
-
- about -
- - - - - - - - - Chat about n-gon in the discord.
Let me know about ideas, - or bugs. -
-
-
- - - - - Github - - - - Github hosts the source code for n-gon.
It's - written in JavaScript, CSS, and HTML. - -
-
-
-
+ + + + - - - - + - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - Q - E - W - S - A - D - - - + + - - - + + + - - - - - - switch - guns - move - fire - field - - + + switch + guns + move + fire + field + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/js/bullet.js b/js/bullet.js index 2d763f7..df9a245 100644 --- a/js/bullet.js +++ b/js/bullet.js @@ -3223,6 +3223,7 @@ const b = { ctx.stroke(); ctx.globalAlpha = 1; } else if (mod.beamSplitter) { + const divergence = mech.crouch ? 0.15 : 0.2 let dmg = mod.laserDamage * 0.9 const where = { x: mech.pos.x + 20 * Math.cos(mech.angle), @@ -3234,12 +3235,12 @@ const b = { }, dmg) for (let i = 1; i < 1 + mod.beamSplitter; i++) { b.laser(where, { - x: where.x + 3000 * Math.cos(mech.angle + i * 0.2), - y: where.y + 3000 * Math.sin(mech.angle + i * 0.2) + x: where.x + 3000 * Math.cos(mech.angle + i * divergence), + y: where.y + 3000 * Math.sin(mech.angle + i * divergence) }, dmg) b.laser(where, { - x: where.x + 3000 * Math.cos(mech.angle - i * 0.2), - y: where.y + 3000 * Math.sin(mech.angle - i * 0.2) + x: where.x + 3000 * Math.cos(mech.angle - i * divergence), + y: where.y + 3000 * Math.sin(mech.angle - i * divergence) }, dmg) dmg *= 0.9 } diff --git a/js/engine.js b/js/engine.js index 5092847..5eb26fd 100644 --- a/js/engine.js +++ b/js/engine.js @@ -1,16 +1,16 @@ //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, - Vector = Matter.Vector; + 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, + Vector = Matter.Vector; // create an engine const engine = Engine.create(); @@ -21,38 +21,38 @@ engine.world.gravity.scale = 0; //turn off gravity (it's added back in later) // 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 - if (mech.standingOn.alive !== true) enter(); - } else if (pair.bodyB === jumpSensor) { - mech.standingOn = pair.bodyA; //keeping track to correctly provide recoil on jump - if (mech.standingOn.alive !== true) enter(); + //runs on collisions events + function enter() { + mech.numTouching++; + if (!mech.onGround) mech.enterLand(); } - } - mech.numTouching = 0; + 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 + if (mech.standingOn.alive !== true) enter(); + } else if (pair.bodyB === jumpSensor) { + mech.standingOn = pair.bodyA; //keeping track to correctly provide recoil on jump + if (mech.standingOn.alive !== true) 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(); + //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) { @@ -71,158 +71,158 @@ function playerOffGroundCheck(event) { // } function collisionChecks(event) { - const pairs = event.pairs; - for (let i = 0, j = pairs.length; i != j; i++) { + const pairs = event.pairs; + for (let i = 0, j = pairs.length; i != j; i++) { - // //map + bullet collisions - // if (pairs[i].bodyA.collisionFilter.category === cat.map && pairs[i].bodyB.collisionFilter.category === cat.bullet) { - // collideBulletStatic(pairs[i].bodyB) - // } else if (pairs[i].bodyB.collisionFilter.category === cat.map && pairs[i].bodyA.collisionFilter.category === cat.bullet) { - // collideBulletStatic(pairs[i].bodyA) - // } - // //triggers when the bullets hits something static - // function collideBulletStatic(obj, speedThreshold = 12, massThreshold = 2) { - // if (obj.onWallHit) obj.onWallHit(); - // } + // //map + bullet collisions + // if (pairs[i].bodyA.collisionFilter.category === cat.map && pairs[i].bodyB.collisionFilter.category === cat.bullet) { + // collideBulletStatic(pairs[i].bodyB) + // } else if (pairs[i].bodyB.collisionFilter.category === cat.map && pairs[i].bodyA.collisionFilter.category === cat.bullet) { + // collideBulletStatic(pairs[i].bodyA) + // } + // //triggers when the bullets hits something static + // function collideBulletStatic(obj, speedThreshold = 12, massThreshold = 2) { + // if (obj.onWallHit) obj.onWallHit(); + // } - // function collidePlayer(obj) { - // //player dmg from hitting a body - // if (obj.classType === "body" && obj.speed > 10 && mech.immuneCycle < mech.cycle) { - // const velocityThreshold = 30 //keep this lines up with player.enterLand numbers (130/5 = 26) - // if (player.position.y > obj.position.y) { //block is above the player look at total momentum difference - // const velocityDiffMag = Vector.magnitude(Vector.sub(player.velocity, obj.velocity)) - // if (velocityDiffMag > velocityThreshold) hit(velocityDiffMag - velocityThreshold) - // } else { //block is below player only look at horizontal momentum difference - // const velocityDiffMagX = Math.abs(obj.velocity.x - player.velocity.x) - // if (velocityDiffMagX > velocityThreshold) hit(velocityDiffMagX - velocityThreshold) - // } + // function collidePlayer(obj) { + // //player dmg from hitting a body + // if (obj.classType === "body" && obj.speed > 10 && mech.immuneCycle < mech.cycle) { + // const velocityThreshold = 30 //keep this lines up with player.enterLand numbers (130/5 = 26) + // if (player.position.y > obj.position.y) { //block is above the player look at total momentum difference + // const velocityDiffMag = Vector.magnitude(Vector.sub(player.velocity, obj.velocity)) + // if (velocityDiffMag > velocityThreshold) hit(velocityDiffMag - velocityThreshold) + // } else { //block is below player only look at horizontal momentum difference + // const velocityDiffMagX = Math.abs(obj.velocity.x - player.velocity.x) + // if (velocityDiffMagX > velocityThreshold) hit(velocityDiffMagX - velocityThreshold) + // } - // function hit(dmg) { - // mech.immuneCycle = mech.cycle + mod.collisionImmuneCycles; //player is immune to collision damage for 30 cycles - // dmg = Math.min(Math.max(Math.sqrt(dmg) * obj.mass * 0.01, 0.02), 0.15); - // mech.damage(dmg); - // 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 - // }); - // } - // } - // } + // function hit(dmg) { + // mech.immuneCycle = mech.cycle + mod.collisionImmuneCycles; //player is immune to collision damage for 30 cycles + // dmg = Math.min(Math.max(Math.sqrt(dmg) * obj.mass * 0.01, 0.02), 0.15); + // mech.damage(dmg); + // 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 + // }); + // } + // } + // } - //mob + (player,bullet,body) collisions - for (let k = 0; k < mob.length; k++) { - if (mob[k].alive && mech.alive) { - if (pairs[i].bodyA === mob[k]) { - collideMob(pairs[i].bodyB); - break; - } else if (pairs[i].bodyB === mob[k]) { - collideMob(pairs[i].bodyA); - break; - } + //mob + (player,bullet,body) collisions + for (let k = 0; k < mob.length; k++) { + if (mob[k].alive && mech.alive) { + if (pairs[i].bodyA === mob[k]) { + collideMob(pairs[i].bodyB); + break; + } else if (pairs[i].bodyB === mob[k]) { + collideMob(pairs[i].bodyA); + break; + } - function collideMob(obj) { - //player + mob collision - if ( - mech.immuneCycle < mech.cycle && - (obj === playerBody || obj === playerHead) && - !(mod.isFreezeHarmImmune && (mob[k].isSlowed || mob[k].isStunned)) - ) { - mech.immuneCycle = mech.cycle + mod.collisionImmuneCycles; //player is immune to collision damage for 30 cycles - 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 - if (mod.isPiezo) mech.energy = mech.maxEnergy; - mech.damage(dmg); - if (mod.isBayesian) powerUps.ejectMod() - if (mob[k].onHit) mob[k].onHit(k); - //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) - }); + function collideMob(obj) { + //player + mob collision + if ( + mech.immuneCycle < mech.cycle && + (obj === playerBody || obj === playerHead) && + !(mod.isFreezeHarmImmune && (mob[k].isSlowed || mob[k].isStunned)) + ) { + mech.immuneCycle = mech.cycle + mod.collisionImmuneCycles; //player is immune to collision damage for 30 cycles + 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 + if (mod.isPiezo && mech.energy < mech.maxEnergy * 3) mech.energy = mech.maxEnergy * 3 + mech.damage(dmg); + if (mod.isBayesian) powerUps.ejectMod() + if (mob[k].onHit) mob[k].onHit(k); + //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) + }); - if (mod.isAnnihilation && !mob[k].shield && !mob[k].isShielded && mech.energy > 0.34 * mech.maxEnergy) { - mech.energy -= 0.33 * mech.maxEnergy - mech.immuneCycle = 0; //player doesn't go immune to collision damage - mob[k].death(); - game.drawList.push({ //add dmg to draw queue - x: pairs[i].activeContacts[0].vertex.x, - y: pairs[i].activeContacts[0].vertex.y, - radius: dmg * 2000, - color: "rgba(255,0,255,0.2)", - time: game.drawTime - }); - } else { - 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 - }); + if (mod.isAnnihilation && !mob[k].shield && !mob[k].isShielded && mech.energy > 0.34 * mech.maxEnergy) { + mech.energy -= 0.33 * mech.maxEnergy + mech.immuneCycle = 0; //player doesn't go immune to collision damage + mob[k].death(); + game.drawList.push({ //add dmg to draw queue + x: pairs[i].activeContacts[0].vertex.x, + y: pairs[i].activeContacts[0].vertex.y, + radius: dmg * 2000, + color: "rgba(255,0,255,0.2)", + time: game.drawTime + }); + } else { + 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 + }); + } + return; + // } + } + //mob + bullet collisions + if (obj.classType === "bullet" && obj.speed > obj.minDmgSpeed) { + obj.beforeDmg(mob[k]); //some bullets do actions when they hits things, like despawn //forces don't seem to work here + let dmg = b.dmgScale * (obj.dmg + 0.15 * obj.mass * Vector.magnitude(Vector.sub(mob[k].velocity, obj.velocity))) + if (mod.isCrit && mob[k].isStunned) dmg *= 4 + mob[k].foundPlayer(); + mob[k].damage(dmg); + game.drawList.push({ //add dmg to draw queue + x: pairs[i].activeContacts[0].vertex.x, + y: pairs[i].activeContacts[0].vertex.y, + radius: Math.log(2 * dmg + 1.1) * 40, + color: game.playerDmgColor, + time: game.drawTime + }); + return; + } + //mob + body collisions + if (obj.classType === "body" && obj.speed > 6) { + const v = Vector.magnitude(Vector.sub(mob[k].velocity, obj.velocity)); + if (v > 9) { + let dmg = 0.05 * b.dmgScale * v * obj.mass * mod.throwChargeRate; + if (mob[k].isShielded) dmg *= 0.35 + mob[k].damage(dmg, true); + const stunTime = dmg / Math.sqrt(obj.mass) + if (stunTime > 0.5) mobs.statusStun(mob[k], 30 + 60 * Math.sqrt(stunTime)) + if (mob[k].distanceToPlayer2() < 1000000 && !mech.isCloak) mob[k].foundPlayer(); + game.drawList.push({ + x: pairs[i].activeContacts[0].vertex.x, + y: pairs[i].activeContacts[0].vertex.y, + radius: Math.log(2 * dmg + 1.1) * 40, + color: game.playerDmgColor, + time: game.drawTime + }); + return; + } + } + } } - return; - // } - } - //mob + bullet collisions - if (obj.classType === "bullet" && obj.speed > obj.minDmgSpeed) { - obj.beforeDmg(mob[k]); //some bullets do actions when they hits things, like despawn //forces don't seem to work here - let dmg = b.dmgScale * (obj.dmg + 0.15 * obj.mass * Vector.magnitude(Vector.sub(mob[k].velocity, obj.velocity))) - if (mod.isCrit && mob[k].isStunned) dmg *= 4 - mob[k].foundPlayer(); - mob[k].damage(dmg); - game.drawList.push({ //add dmg to draw queue - x: pairs[i].activeContacts[0].vertex.x, - y: pairs[i].activeContacts[0].vertex.y, - radius: Math.log(2 * dmg + 1.1) * 40, - color: game.playerDmgColor, - time: game.drawTime - }); - return; - } - //mob + body collisions - if (obj.classType === "body" && obj.speed > 6) { - const v = Vector.magnitude(Vector.sub(mob[k].velocity, obj.velocity)); - if (v > 9) { - let dmg = 0.05 * b.dmgScale * v * obj.mass * mod.throwChargeRate; - if (mob[k].isShielded) dmg *= 0.35 - mob[k].damage(dmg, true); - const stunTime = dmg / Math.sqrt(obj.mass) - if (stunTime > 0.5) mobs.statusStun(mob[k], 30 + 60 * Math.sqrt(stunTime)) - if (mob[k].distanceToPlayer2() < 1000000 && !mech.isCloak) mob[k].foundPlayer(); - game.drawList.push({ - x: pairs[i].activeContacts[0].vertex.x, - y: pairs[i].activeContacts[0].vertex.y, - radius: Math.log(2 * dmg + 1.1) * 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); - collisionChecks(event); +Events.on(engine, "collisionStart", function(event) { + playerOnGroundCheck(event); + // playerHeadCheck(event); + collisionChecks(event); }); -Events.on(engine, "collisionActive", function (event) { - playerOnGroundCheck(event); - // playerHeadCheck(event); +Events.on(engine, "collisionActive", function(event) { + playerOnGroundCheck(event); + // playerHeadCheck(event); }); -Events.on(engine, "collisionEnd", function (event) { - playerOffGroundCheck(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 index 10b051b..ad1dac7 100644 --- a/js/game.js +++ b/js/game.js @@ -499,6 +499,7 @@ const game = { mech.spawn(); //spawns the player + level.levels = level.playableLevels.slice(0) //copy array, not by just by assignment if (game.isCommunityMaps) { level.levels.push("stronghold"); level.levels.push("basement"); @@ -681,26 +682,10 @@ const game = { checks() { if (!(mech.cycle % 60)) { //once a second - //every second energy above max energy loses 25% - if (mech.energy > mech.maxEnergy) mech.energy = mech.maxEnergy + (mech.energy - mech.maxEnergy) * 0.75 + //energy overfill + if (mech.energy > mech.maxEnergy) mech.energy = mech.maxEnergy + (mech.energy - mech.maxEnergy) * 0.8 //every second energy above max energy loses 25% if (mech.pos.y > game.fallHeight) { // if 4000px deep - - - // Matter.Body.setPosition(player, { - // x: player.position.x, - // y: level.enter.y - 5000 - // }); - - // mech.pos.x = player.position.x; - // mech.pos.y = playerBody.position.y - mech.yOff; - // const scale = 0.8; - // const velocityScale = 12 - // mech.transSmoothX = canvas.width2 - mech.pos.x - (game.mouse.x - canvas.width2) * scale + player.velocity.x * velocityScale; - // mech.transSmoothY = canvas.height2 - mech.pos.y - (game.mouse.y - canvas.height2) * scale + player.velocity.y * velocityScale; - // mech.transX += (mech.transSmoothX - mech.transX) * 1; - // mech.transY += (mech.transSmoothY - mech.transY) * 1; - Matter.Body.setVelocity(player, { x: 0, y: 0 @@ -738,7 +723,7 @@ const game = { // } if (mech.lastKillCycle + 300 > mech.cycle) { //effects active for 5 seconds after killing a mob - if (mod.isEnergyRecovery && mech.energy < mech.maxEnergy) mech.energy += mech.maxEnergy * 0.06 + if (mod.isEnergyRecovery) mech.energy += mech.maxEnergy * 0.05 if (mod.isHealthRecovery) mech.addHealth(0.01) } diff --git a/js/level.js b/js/level.js index 9f1242b..f8528f0 100644 --- a/js/level.js +++ b/js/level.js @@ -8,7 +8,8 @@ const level = { onLevel: -1, levelsCleared: 0, bossKilled: false, - levels: ["skyscrapers", "rooftops", "warehouse", "highrise", "office", "aerie", "satellite", "sewers", "testChamber"], + playableLevels: ["skyscrapers", "rooftops", "warehouse", "highrise", "office", "aerie", "satellite", "sewers", "testChamber"], + levels: [], start() { if (level.levelsCleared === 0) { //this code only runs on the first level // level.difficultyIncrease(8) @@ -17,7 +18,7 @@ const level = { // game.setZoom(); // mech.isCloak = true; // mech.setField("wormhole") - // b.giveGuns("grenades") + // b.giveGuns("laser") // for (let i = 0; i < 10; i++) { // mod.giveMod("laser-bot"); // } @@ -3796,26 +3797,19 @@ const level = { } }, nextLevel() { - if (level.bossKilled) { - level.levelsCleared++; - // level.levels.unshift("gauntlet"); //add bosses level to the end of the randomized levels list - // level.levels.unshift("finalBoss"); //add bosses level to the end of the randomized levels list - } - - + if (level.bossKilled) level.levelsCleared++; level.difficultyIncrease(game.difficultyMode) //increase difficulty based on modes if (level.levelsCleared > level.levels.length) level.difficultyIncrease(game.difficultyMode) if (level.levelsCleared > level.levels.length * 1.25) level.difficultyIncrease(game.difficultyMode) if (level.levelsCleared > level.levels.length * 1.5) level.difficultyIncrease(game.difficultyMode) if (level.levelsCleared > level.levels.length * 2) level.difficultyIncrease(game.difficultyMode) - level.onLevel++; //cycles map to next level if (level.onLevel > level.levels.length - 1) level.onLevel = 0; - //reset lost mod display for (let i = 0; i < mod.mods.length; i++) { if (mod.mods[i].isLost) mod.mods[i].isLost = false; } + mod.isDeathAvoidedThisLevel = false; game.updateModHUD(); game.clearNow = true; //triggers in game.clearMap to remove all physics bodies and setup for new map }, diff --git a/js/mob.js b/js/mob.js index ecf49d5..4129763 100644 --- a/js/mob.js +++ b/js/mob.js @@ -2,1139 +2,1139 @@ 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(); - } - }, - healthBar() { - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].seePlayer.recall && mob[i].showHealthBar) { - const h = mob[i].radius * 0.3; - const w = mob[i].radius * 2; - const x = mob[i].position.x - w / 2; - const y = mob[i].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 * mob[i].health, h); - } - } - }, - statusSlow(who, cycles = 60) { - applySlow(who) - //look for mobs near the target - if (mod.isAoESlow) { - const range = (320 + 150 * Math.random()) ** 2 - for (let i = 0, len = mob.length; i < len; i++) { - if (Vector.magnitudeSquared(Vector.sub(who.position, mob[i].position)) < range) applySlow(mob[i]) - } - game.drawList.push({ - x: who.position.x, - y: who.position.y, - radius: Math.sqrt(range), - color: "rgba(0,100,255,0.05)", - time: 3 - }); - } - - function applySlow() { - if (!who.shield && !who.isShielded && !mech.isBodiesAsleep) { - if (who.isBoss) cycles = Math.floor(cycles * 0.25) - - let i = who.status.length + loop() { + let i = mob.length; while (i--) { - if (who.status[i].type === "slow") who.status.splice(i, 1); //remove other "slow" effects on this mob - } - who.isSlowed = true; - who.status.push({ - effect() { - Matter.Body.setVelocity(who, { - x: 0, - y: 0 - }); - Matter.Body.setAngularVelocity(who, 0); - ctx.beginPath(); - ctx.moveTo(who.vertices[0].x, who.vertices[0].y); - for (let j = 1, len = who.vertices.length; j < len; ++j) { - ctx.lineTo(who.vertices[j].x, who.vertices[j].y); - } - ctx.lineTo(who.vertices[0].x, who.vertices[0].y); - ctx.strokeStyle = "rgba(0,100,255,0.8)"; - ctx.lineWidth = 15; - ctx.stroke(); - ctx.fillStyle = who.fill - ctx.fill(); - }, - endEffect() { - //check to see if there are not other freeze effects? - who.isSlowed = false; - }, - type: "slow", - endCycle: game.cycle + cycles, - }) - } - } - }, - statusStun(who, cycles = 180) { - if (!who.shield && !who.isShielded && !mech.isBodiesAsleep) { - Matter.Body.setVelocity(who, { - x: who.velocity.x * 0.8, - y: who.velocity.y * 0.8 - }); - Matter.Body.setAngularVelocity(who, who.angularVelocity * 0.8); - //remove other "stun" effects on this mob - let i = who.status.length - while (i--) { - if (who.status[i].type === "stun") who.status.splice(i, 1); - } - who.isStunned = true; - who.status.push({ - effect() { - who.seePlayer.yes = false; - who.seePlayer.recall = 0; - who.seePlayer.position = { - x: who.position.x + 100 * (Math.random() - 0.5), - y: who.position.y + 100 * (Math.random() - 0.5) - } - if (who.velocity.y < 2) who.force.y += who.mass * 0.0004 //extra gravity - - //draw health bar - const h = who.radius * 0.3; - const w = who.radius * 2; - const x = who.position.x - w / 2; - const y = who.position.y - w * 0.7; - ctx.fillStyle = "rgba(100, 100, 100, 0.3)"; - ctx.fillRect(x, y, w, h); - ctx.fillStyle = `rgba(${Math.floor(255*Math.random())},${Math.floor(255*Math.random())},${Math.floor(255*Math.random())},0.5)` - ctx.fillRect(x, y, w * who.health, h); - - //draw fill inside mob - ctx.beginPath(); - ctx.moveTo(who.vertices[0].x, who.vertices[0].y); - for (let j = 1, len = who.vertices.length; j < len; ++j) { - ctx.lineTo(who.vertices[j].x, who.vertices[j].y); - } - ctx.lineTo(who.vertices[0].x, who.vertices[0].y); - ctx.stroke(); - ctx.fill(); - - - }, - endEffect() { - who.isStunned = false - }, - type: "stun", - endCycle: game.cycle + cycles, - }) - } - }, - statusDoT(who, tickDamage, cycles = 180) { - if (!who.isShielded && !mech.isBodiesAsleep) { - who.status.push({ - effect() { - if ((game.cycle - this.startCycle) % 30 === 0) { - let dmg = b.dmgScale * tickDamage - who.damage(dmg); - game.drawList.push({ //add dmg to draw queue - x: who.position.x + (Math.random() - 0.5) * who.radius * 0.5, - y: who.position.y + (Math.random() - 0.5) * who.radius * 0.5, - radius: Math.log(2 * dmg + 1.1) * 40, - color: "rgba(0,80,80,0.9)", - time: game.drawTime - }); - } - if (true) { - //check for nearby mobs - - } - }, - endEffect() {}, - // type: "DoT", - endCycle: game.cycle + cycles, - startCycle: game.cycle - }) - } - }, - // statusBurn(who, tickDamage, cycles = 90 + Math.floor(90 * Math.random())) { - // if (!who.isShielded) { - // //remove other "burn" effects on this mob - // let i = who.status.length - // while (i--) { - // if (who.status[i].type === "burn") who.status.splice(i, 1); - // } - // who.status.push({ - // effect() { - // if ((game.cycle - this.startCycle) % 15 === 0) { - // let dmg = b.dmgScale * tickDamage * 0.5 * (1 + Math.random()) - // who.damage(dmg); - // game.drawList.push({ //add dmg to draw queue - // x: who.position.x, - // y: who.position.y, - // radius: Math.log(2 * dmg + 1.1) * 40, - // color: `rgba(255,${Math.floor(200*Math.random())},0,0.9)`, - // time: game.drawTime - // }); - // } - // }, - // type: "burn", - // endCycle: game.cycle + cycles, - // startCycle: game.cycle - // }) - // } - // }, - - //********************************************************************************************** - //********************************************************************************************** - 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: cat.mob, - mask: cat.player | cat.map | cat.body | cat.bullet | cat.mob - }, - onHit: undefined, - alive: true, - index: i, - health: mod.mobSpawnWithHealth, - showHealthBar: true, - accelMag: 0.001 * game.accelScale, - 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 - }, - status: [], // [ { effect(), endCycle } ] - checkStatus() { - let j = this.status.length; - while (j--) { - this.status[j].effect(); - if (this.status[j].endCycle < game.cycle) { - this.status[j].endEffect(); - this.status.splice(j, 1); - } - } - }, - isSlowed: false, - isStunned: false, - seeAtDistance2: Infinity, //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: Math.floor((30 + 30 * Math.random()) * game.lookFreqScale), //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; - }, - 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 && Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)) < this.alertRange2) { - mob[i].locatePlayer(); - } - } - }, - alwaysSeePlayer() { - if (!mech.isCloak) { - this.seePlayer.recall = true; - this.seePlayer.position.x = player.position.x; - this.seePlayer.position.y = player.position.y; - } - }, - 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 && - !mech.isCloak - ) { - this.foundPlayer(); - } else if (this.seePlayer.recall) { - this.lostPlayer(); - } - } - }, - seePlayerCheckByDistance() { - if (!(game.cycle % this.seePlayerFreq)) { - if (this.distanceToPlayer2() < this.seeAtDistance2 && !mech.isCloak) { - 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)) && - !mech.isCloak - ) { - this.foundPlayer(); - } else if (this.seePlayer.recall) { - this.lostPlayer(); - } - } - }, - isLookingAtPlayer(threshold) { - const diff = Vector.normalise(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 = 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 && - !mech.isCloak - ) { - 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 - }; - }, - // hacked() { //set this.hackedTarget variable before running this method - // //find a new target - // if (!(game.cycle % this.seePlayerFreq)) { - // this.hackedTarget = null - // for (let i = 0, len = mob.length; i < len; i++) { - // if (mob[i] !== this) { - // // const DIST = Vector.magnitude(Vector.sub(this.position, mob[j])); - // if (Matter.Query.ray(map, this.position, mob[i].position).length === 0 && - // Matter.Query.ray(body, this.position, mob[i].position).length === 0) { - // this.hackedTarget = mob[i] - // } - // } - // } - // } - // //acceleration towards targets - // if (this.hackedTarget) { - // this.force = Vector.mult(Vector.normalise(Vector.sub(this.hackedTarget.position, this.position)), this.mass * 0.0015) - // } - // }, - 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 (mech.immuneCycle < mech.cycle) mech.damage(0.0003 * game.dmgScale); - if (mech.energy > 0.1) mech.energy -= 0.003 - 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([]); - ctx.fillStyle = "rgba(255,0,170,0.03)"; - ctx.fill(); - } - }, - 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); - if (!mech.isCloak) vertexCollision(this.position, look, [player]); - // hitting player - if (best.who === player) { - if (mech.immuneCycle < mech.cycle) { - const dmg = 0.0012 * game.dmgScale; - mech.damage(dmg); - //draw damage - ctx.fillStyle = "#f00"; - ctx.beginPath(); - ctx.arc(best.x, best.y, dmg * 10000, 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() { - //draw the two dots on the end of the springs - 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.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 && - !mech.isCloak - ) { - this.foundPlayer(); - } else if (this.seePlayer.recall) { - this.lostPlayer(); - } - } - }, - springAttack() { - // set new values of the ends of the spring constraints - const stepRange = 600 - if (this.seePlayer.recall && Matter.Query.ray(map, this.position, player.position).length === 0) { - if (!(game.cycle % (this.seePlayerFreq * 2))) { - const unit = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)) - const goal = Vector.add(this.position, Vector.mult(unit, stepRange)) - this.springTarget.x = goal.x; - this.springTarget.y = goal.y; - // 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 if (!(game.cycle % this.seePlayerFreq)) { - const unit = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)) - const goal = Vector.add(this.position, Vector.mult(unit, stepRange)) - this.springTarget2.x = goal.x; - this.springTarget2.y = goal.y; - // 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 { - this.torque = this.lookTorque * this.inertia; - //draw looking around arcs - 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] - }; - } - } - } - }; - //move to a random location - if (!(game.cycle % (this.seePlayerFreq * 5))) { - best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - const seeRange = 3000; - 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; - } - } - } - }, - curl(range = 1000, mag = -10) { - //cause all mobs, and bodies to rotate in a circle - applyCurl = function (center, array, isAntiGravity = true) { - for (let i = 0; i < array.length; ++i) { - const sub = Vector.sub(center, array[i].position) - const radius2 = 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 = Vector.mult(Vector.perp(Vector.normalise(sub)), mag) - //apply curl force - Matter.Body.setVelocity(array[i], { - x: array[i].velocity.x * 0.94 + curlVector.x * 0.06, - y: array[i].velocity.y * 0.94 + curlVector.y * 0.06 - }) - if (isAntiGravity) array[i].force.y -= 0.8 * game.g * array[i].mass - // //draw curl, for debugging - // ctx.beginPath(); - // ctx.moveTo(array[i].position.x, array[i].position.y); - // ctx.lineTo(array[i].position.x + curlVector.x * 10, array[i].position.y + curlVector.y * 10); - // ctx.lineWidth = 2; - // ctx.strokeStyle = "#000"; - // ctx.stroke(); - } - } - } - applyCurl(this.position, mob, false); - applyCurl(this.position, body); - applyCurl(this.position, powerUp); - // applyCurl(this.position, bullet); // too powerful, just stops all bullets need to write a curl function just for bullets - // applyCurl(this.position, [player]); - - //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 && Vector.magnitudeSquared(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 -= game.accelScale * 0.00113 * player.mass * Math.cos(angle) * (mech.onGround ? 2 : 1); - player.force.y -= game.accelScale * 0.00084 * player.mass * 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); - const force = Vector.mult(Vector.normalise(Vector.sub(this.seePlayer.position, this.position)), this.accelMag * this.mass) - this.force.x += force.x; - this.force.y += force.y; - } - }, - 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 - } - }, - 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 (!mech.isBodiesAsleep) { - 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.0005) { - that.searchTarget = player.position; //chance to target player + if (mob[i].alive) { + mob[i].do(); } else { - //target random body - that.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; + mob[i].replace(i); //removing mob and replace with body, this is done here to avoid an array index bug with drawing I think } - }; - - const sub = Vector.sub(this.searchTarget, this.position); - if (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 = Vector.mult(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); - } } - }, - 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 = Vector.sub(this.seePlayer.position, this.position); - const distMag = Vector.magnitude(dist); - const unitVector = Vector.normalise(dist); - const rando = (Math.random() - 0.5) * 50; - if (distMag < this.blinkLength) { - Matter.Body.translate(this, Vector.mult(unitVector, distMag + rando)); - } else { - Matter.Body.translate(this, 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(); + }, + 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(); } - }, - 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 = Vector.sub(this.seePlayer.position, this.position); - const distMag = Vector.magnitude(dist); - const vector = Vector.mult(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 + }, + healthBar() { + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].seePlayer.recall && mob[i].showHealthBar) { + const h = mob[i].radius * 0.3; + const w = mob[i].radius * 2; + const x = mob[i].position.x - w / 2; + const y = mob[i].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 * mob[i].health, h); + } + } + }, + statusSlow(who, cycles = 60) { + applySlow(who) + //look for mobs near the target + if (mod.isAoESlow) { + const range = (320 + 150 * Math.random()) ** 2 + for (let i = 0, len = mob.length; i < len; i++) { + if (Vector.magnitudeSquared(Vector.sub(who.position, mob[i].position)) < range) applySlow(mob[i]) + } + game.drawList.push({ + x: who.position.x, + y: who.position.y, + radius: Math.sqrt(range), + color: "rgba(0,100,255,0.05)", + time: 3 }); - } 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.bomb(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() { - if (!mech.isBodiesAsleep) { - 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 = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); - this.fireDir.y -= Math.abs(this.seePlayer.position.x - this.position.x) / 2500; //gives the bullet an arc //was / 1600 - } - //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 + 3 * Math.random(), - y: this.velocity.y + this.fireDir.y * v + 3 * 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(); - // } - } - }, - // launch() { - // if (this.seePlayer.recall) { - // //fire - // spawn.seeker(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() - // }); - // // recoil - // this.force.x -= 0.005 * this.fireDir.x * this.mass; - // this.force.y -= 0.005 * this.fireDir.y * this.mass; - // } - // }, - 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 = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); - const angle = Math.atan2(unitVector.y, unitVector.x); - Matter.Body.setAngle(this, angle - Math.PI); - }, - explode(mass = this.mass) { - mech.damage(Math.min(Math.max(0.02 * Math.sqrt(mass), 0.01), 0.35) * game.dmgScale); - this.dropPowerUp = false; - this.death(); //death with no power up or body - }, - timeLimit() { - if (!mech.isBodiesAsleep) { - this.timeLeft--; - if (this.timeLeft < 0) { - this.dropPowerUp = false; - this.death(); //death with no power up - } - } - }, - healthBar() { //draw health by mob //most health bars are drawn in mobs.healthbar(); - if (this.seePlayer.recall) { - 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, isBypassShield = false) { - if (!this.isShielded || isBypassShield) { - dmg *= mod.damageFromMods() - //mobs specific damage changes - if (mod.isFarAwayDmg) dmg *= 1 + Math.sqrt(Math.max(500, Math.min(3000, this.distanceToPlayer())) - 500) * 0.0067 //up to 50% dmg at max range of 3500 - if (this.shield) dmg *= 0.075 - //energy and heal drain should be calculated after damage boosts - if (mod.energySiphon && dmg !== Infinity && this.dropPowerUp) { - mech.energy += Math.min(this.health, dmg) * mod.energySiphon - if (mech.energy > mech.maxEnergy) mech.energy = mech.maxEnergy - } - if (mod.healthDrain && dmg !== Infinity && this.dropPowerUp) { - mech.addHealth(Math.min(this.health, dmg) * mod.healthDrain) - if (mech.health > mech.maxHealth) mech.health = mech.maxHealth - } - dmg /= Math.sqrt(this.mass) - this.health -= dmg - //this.fill = this.color + this.health + ')'; - this.onDamage(dmg); //custom damage effects - if (this.health < 0.05 && this.alive) this.death(); - } - }, - 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; //triggers mob removal in mob[i].replace(i) - if (this.dropPowerUp) { - if (mod.isEnergyLoss) mech.energy *= 0.75; - powerUps.spawnRandomPowerUp(this.position.x, this.position.y); - mech.lastKillCycle = mech.cycle; //tracks the last time a kill was made, mostly used in game.checks() - if (Math.random() < mod.sporesOnDeath) { - const len = Math.min(25, Math.floor(2 + this.mass * (0.5 + 0.5 * Math.random()))) - for (let i = 0; i < len; i++) { - b.spore(this.position) - } - } - if (Math.random() < mod.isBotSpawner) { - b.randomBot(this.position, false) - bullet[bullet.length - 1].endCycle = game.cycle + 1000 + Math.floor(400 * Math.random()) - } - if (mod.isExplodeMob) b.explosion(this.position, Math.min(550, Math.sqrt(this.mass + 2.5) * 50)) - if (mod.nailsDeathMob) b.targetedNail(this.position, mod.nailsDeathMob, 40 + 7 * Math.random()) - } else if (mod.isShieldAmmo && this.shield) { - let type = "ammo" - if (Math.random() < 0.4) { - type = "heal" - } else if (Math.random() < 0.3 && !mod.isSuperDeterminism) { - type = "reroll" - } - for (let i = 0, len = Math.ceil(2 * Math.random()); i < len; i++) { - powerUps.spawn(this.position.x, this.position.y, type); - } - } - }, - 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 there are too many bodies don't turn into blocks to help performance - if (this.leaveBody && body.length < 60 && this.mass < 200 && this.radius > 18) { - const len = body.length; - const v = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) //might help with vertex collision issue, not sure - body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, v); - Matter.Body.setVelocity(body[len], Vector.mult(this.velocity, 0.5)); - Matter.Body.setAngularVelocity(body[len], this.angularVelocity); - body[len].collisionFilter.category = cat.body; - body[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet; - // if (body[len].mass > 10 || 45 + 10 * Math.random() < body.length) { - // body[len].collisionFilter.mask = cat.player | cat.bullet | cat.mob | cat.mobBullet; - // } - body[len].classType = "body"; - World.add(engine.world, body[len]); //add to world + function applySlow() { + if (!who.shield && !who.isShielded && !mech.isBodiesAsleep) { + if (who.isBoss) cycles = Math.floor(cycles * 0.25) - //large mobs shrink so they don't block paths - if (body[len].mass > 9) { - const shrink = function (that, massLimit) { - if (that.mass > massLimit) { - const scale = 0.95; - Matter.Body.scale(that, scale, scale); - setTimeout(shrink, 20, that, massLimit); - } - }; - shrink(body[len], 7 + 4 * Math.random()) - } + let i = who.status.length + while (i--) { + if (who.status[i].type === "slow") who.status.splice(i, 1); //remove other "slow" effects on this mob + } + who.isSlowed = true; + who.status.push({ + effect() { + Matter.Body.setVelocity(who, { + x: 0, + y: 0 + }); + Matter.Body.setAngularVelocity(who, 0); + ctx.beginPath(); + ctx.moveTo(who.vertices[0].x, who.vertices[0].y); + for (let j = 1, len = who.vertices.length; j < len; ++j) { + ctx.lineTo(who.vertices[j].x, who.vertices[j].y); + } + ctx.lineTo(who.vertices[0].x, who.vertices[0].y); + ctx.strokeStyle = "rgba(0,100,255,0.8)"; + ctx.lineWidth = 15; + ctx.stroke(); + ctx.fillStyle = who.fill + ctx.fill(); + }, + endEffect() { + //check to see if there are not other freeze effects? + who.isSlowed = false; + }, + type: "slow", + endCycle: game.cycle + cycles, + }) + } } - Matter.World.remove(engine.world, this); - mob.splice(i, 1); - } - }); - mob[i].alertRange2 = Math.pow(mob[i].radius * 3 + 550, 2); - World.add(engine.world, mob[i]); //add to world - } + }, + statusStun(who, cycles = 180) { + if (!who.shield && !who.isShielded && !mech.isBodiesAsleep) { + Matter.Body.setVelocity(who, { + x: who.velocity.x * 0.8, + y: who.velocity.y * 0.8 + }); + Matter.Body.setAngularVelocity(who, who.angularVelocity * 0.8); + //remove other "stun" effects on this mob + let i = who.status.length + while (i--) { + if (who.status[i].type === "stun") who.status.splice(i, 1); + } + who.isStunned = true; + who.status.push({ + effect() { + who.seePlayer.yes = false; + who.seePlayer.recall = 0; + who.seePlayer.position = { + x: who.position.x + 100 * (Math.random() - 0.5), + y: who.position.y + 100 * (Math.random() - 0.5) + } + if (who.velocity.y < 2) who.force.y += who.mass * 0.0004 //extra gravity + + //draw health bar + const h = who.radius * 0.3; + const w = who.radius * 2; + const x = who.position.x - w / 2; + const y = who.position.y - w * 0.7; + ctx.fillStyle = "rgba(100, 100, 100, 0.3)"; + ctx.fillRect(x, y, w, h); + ctx.fillStyle = `rgba(${Math.floor(255*Math.random())},${Math.floor(255*Math.random())},${Math.floor(255*Math.random())},0.5)` + ctx.fillRect(x, y, w * who.health, h); + + //draw fill inside mob + ctx.beginPath(); + ctx.moveTo(who.vertices[0].x, who.vertices[0].y); + for (let j = 1, len = who.vertices.length; j < len; ++j) { + ctx.lineTo(who.vertices[j].x, who.vertices[j].y); + } + ctx.lineTo(who.vertices[0].x, who.vertices[0].y); + ctx.stroke(); + ctx.fill(); + + + }, + endEffect() { + who.isStunned = false + }, + type: "stun", + endCycle: game.cycle + cycles, + }) + } + }, + statusDoT(who, tickDamage, cycles = 180) { + if (!who.isShielded && !mech.isBodiesAsleep) { + who.status.push({ + effect() { + if ((game.cycle - this.startCycle) % 30 === 0) { + let dmg = b.dmgScale * tickDamage + who.damage(dmg); + game.drawList.push({ //add dmg to draw queue + x: who.position.x + (Math.random() - 0.5) * who.radius * 0.5, + y: who.position.y + (Math.random() - 0.5) * who.radius * 0.5, + radius: Math.log(2 * dmg + 1.1) * 40, + color: "rgba(0,80,80,0.9)", + time: game.drawTime + }); + } + if (true) { + //check for nearby mobs + + } + }, + endEffect() {}, + // type: "DoT", + endCycle: game.cycle + cycles, + startCycle: game.cycle + }) + } + }, + // statusBurn(who, tickDamage, cycles = 90 + Math.floor(90 * Math.random())) { + // if (!who.isShielded) { + // //remove other "burn" effects on this mob + // let i = who.status.length + // while (i--) { + // if (who.status[i].type === "burn") who.status.splice(i, 1); + // } + // who.status.push({ + // effect() { + // if ((game.cycle - this.startCycle) % 15 === 0) { + // let dmg = b.dmgScale * tickDamage * 0.5 * (1 + Math.random()) + // who.damage(dmg); + // game.drawList.push({ //add dmg to draw queue + // x: who.position.x, + // y: who.position.y, + // radius: Math.log(2 * dmg + 1.1) * 40, + // color: `rgba(255,${Math.floor(200*Math.random())},0,0.9)`, + // time: game.drawTime + // }); + // } + // }, + // type: "burn", + // endCycle: game.cycle + cycles, + // startCycle: game.cycle + // }) + // } + // }, + + //********************************************************************************************** + //********************************************************************************************** + 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: cat.mob, + mask: cat.player | cat.map | cat.body | cat.bullet | cat.mob + }, + onHit: undefined, + alive: true, + index: i, + health: mod.mobSpawnWithHealth, + showHealthBar: true, + accelMag: 0.001 * game.accelScale, + 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 + }, + status: [], // [ { effect(), endCycle } ] + checkStatus() { + let j = this.status.length; + while (j--) { + this.status[j].effect(); + if (this.status[j].endCycle < game.cycle) { + this.status[j].endEffect(); + this.status.splice(j, 1); + } + } + }, + isSlowed: false, + isStunned: false, + seeAtDistance2: Infinity, //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: Math.floor((30 + 30 * Math.random()) * game.lookFreqScale), //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; + }, + 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 && Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)) < this.alertRange2) { + mob[i].locatePlayer(); + } + } + }, + alwaysSeePlayer() { + if (!mech.isCloak) { + this.seePlayer.recall = true; + this.seePlayer.position.x = player.position.x; + this.seePlayer.position.y = player.position.y; + } + }, + 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 && + !mech.isCloak + ) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + }, + seePlayerCheckByDistance() { + if (!(game.cycle % this.seePlayerFreq)) { + if (this.distanceToPlayer2() < this.seeAtDistance2 && !mech.isCloak) { + 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)) && + !mech.isCloak + ) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + }, + isLookingAtPlayer(threshold) { + const diff = Vector.normalise(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 = 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 && + !mech.isCloak + ) { + 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 + }; + }, + // hacked() { //set this.hackedTarget variable before running this method + // //find a new target + // if (!(game.cycle % this.seePlayerFreq)) { + // this.hackedTarget = null + // for (let i = 0, len = mob.length; i < len; i++) { + // if (mob[i] !== this) { + // // const DIST = Vector.magnitude(Vector.sub(this.position, mob[j])); + // if (Matter.Query.ray(map, this.position, mob[i].position).length === 0 && + // Matter.Query.ray(body, this.position, mob[i].position).length === 0) { + // this.hackedTarget = mob[i] + // } + // } + // } + // } + // //acceleration towards targets + // if (this.hackedTarget) { + // this.force = Vector.mult(Vector.normalise(Vector.sub(this.hackedTarget.position, this.position)), this.mass * 0.0015) + // } + // }, + 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 (mech.immuneCycle < mech.cycle) mech.damage(0.0003 * game.dmgScale); + if (mech.energy > 0.1) mech.energy -= 0.003 + 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([]); + ctx.fillStyle = "rgba(255,0,170,0.03)"; + ctx.fill(); + } + }, + 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); + if (!mech.isCloak) vertexCollision(this.position, look, [player]); + // hitting player + if (best.who === player) { + if (mech.immuneCycle < mech.cycle) { + const dmg = 0.0012 * game.dmgScale; + mech.damage(dmg); + //draw damage + ctx.fillStyle = "#f00"; + ctx.beginPath(); + ctx.arc(best.x, best.y, dmg * 10000, 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() { + //draw the two dots on the end of the springs + 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.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 && + !mech.isCloak + ) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + }, + springAttack() { + // set new values of the ends of the spring constraints + const stepRange = 600 + if (this.seePlayer.recall && Matter.Query.ray(map, this.position, player.position).length === 0) { + if (!(game.cycle % (this.seePlayerFreq * 2))) { + const unit = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)) + const goal = Vector.add(this.position, Vector.mult(unit, stepRange)) + this.springTarget.x = goal.x; + this.springTarget.y = goal.y; + // 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 if (!(game.cycle % this.seePlayerFreq)) { + const unit = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)) + const goal = Vector.add(this.position, Vector.mult(unit, stepRange)) + this.springTarget2.x = goal.x; + this.springTarget2.y = goal.y; + // 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 { + this.torque = this.lookTorque * this.inertia; + //draw looking around arcs + 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] + }; + } + } + } + }; + //move to a random location + if (!(game.cycle % (this.seePlayerFreq * 5))) { + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const seeRange = 3000; + 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; + } + } + } + }, + curl(range = 1000, mag = -10) { + //cause all mobs, and bodies to rotate in a circle + applyCurl = function(center, array, isAntiGravity = true) { + for (let i = 0; i < array.length; ++i) { + const sub = Vector.sub(center, array[i].position) + const radius2 = 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 = Vector.mult(Vector.perp(Vector.normalise(sub)), mag) + //apply curl force + Matter.Body.setVelocity(array[i], { + x: array[i].velocity.x * 0.94 + curlVector.x * 0.06, + y: array[i].velocity.y * 0.94 + curlVector.y * 0.06 + }) + if (isAntiGravity) array[i].force.y -= 0.8 * game.g * array[i].mass + // //draw curl, for debugging + // ctx.beginPath(); + // ctx.moveTo(array[i].position.x, array[i].position.y); + // ctx.lineTo(array[i].position.x + curlVector.x * 10, array[i].position.y + curlVector.y * 10); + // ctx.lineWidth = 2; + // ctx.strokeStyle = "#000"; + // ctx.stroke(); + } + } + } + applyCurl(this.position, mob, false); + applyCurl(this.position, body); + applyCurl(this.position, powerUp); + // applyCurl(this.position, bullet); // too powerful, just stops all bullets need to write a curl function just for bullets + // applyCurl(this.position, [player]); + + //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 && Vector.magnitudeSquared(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 -= game.accelScale * 0.00113 * player.mass * Math.cos(angle) * (mech.onGround ? 2 : 1); + player.force.y -= game.accelScale * 0.00084 * player.mass * 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); + const force = Vector.mult(Vector.normalise(Vector.sub(this.seePlayer.position, this.position)), this.accelMag * this.mass) + this.force.x += force.x; + this.force.y += force.y; + } + }, + 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 + } + }, + 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 (!mech.isBodiesAsleep) { + 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.0005) { + 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 = Vector.sub(this.searchTarget, this.position); + if (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 = Vector.mult(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); + } + } + }, + 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 = Vector.sub(this.seePlayer.position, this.position); + const distMag = Vector.magnitude(dist); + const unitVector = Vector.normalise(dist); + const rando = (Math.random() - 0.5) * 50; + if (distMag < this.blinkLength) { + Matter.Body.translate(this, Vector.mult(unitVector, distMag + rando)); + } else { + Matter.Body.translate(this, 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 = Vector.sub(this.seePlayer.position, this.position); + const distMag = Vector.magnitude(dist); + const vector = Vector.mult(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.bomb(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() { + if (!mech.isBodiesAsleep) { + 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 = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); + this.fireDir.y -= Math.abs(this.seePlayer.position.x - this.position.x) / 2500; //gives the bullet an arc //was / 1600 + } + //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 + 3 * Math.random(), + y: this.velocity.y + this.fireDir.y * v + 3 * 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(); + // } + } + }, + // launch() { + // if (this.seePlayer.recall) { + // //fire + // spawn.seeker(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() + // }); + // // recoil + // this.force.x -= 0.005 * this.fireDir.x * this.mass; + // this.force.y -= 0.005 * this.fireDir.y * this.mass; + // } + // }, + 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 = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); + const angle = Math.atan2(unitVector.y, unitVector.x); + Matter.Body.setAngle(this, angle - Math.PI); + }, + explode(mass = this.mass) { + mech.damage(Math.min(Math.max(0.02 * Math.sqrt(mass), 0.01), 0.35) * game.dmgScale); + this.dropPowerUp = false; + this.death(); //death with no power up or body + }, + timeLimit() { + if (!mech.isBodiesAsleep) { + this.timeLeft--; + if (this.timeLeft < 0) { + this.dropPowerUp = false; + this.death(); //death with no power up + } + } + }, + healthBar() { //draw health by mob //most health bars are drawn in mobs.healthbar(); + if (this.seePlayer.recall) { + 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, isBypassShield = false) { + if (!this.isShielded || isBypassShield) { + dmg *= mod.damageFromMods() + //mobs specific damage changes + if (mod.isFarAwayDmg) dmg *= 1 + Math.sqrt(Math.max(500, Math.min(3000, this.distanceToPlayer())) - 500) * 0.0067 //up to 50% dmg at max range of 3500 + if (this.shield) dmg *= 0.075 + + //energy and heal drain should be calculated after damage boosts + if (mod.energySiphon && dmg !== Infinity && this.dropPowerUp) { + mech.energy += Math.min(this.health, dmg) * mod.energySiphon + // if (mech.energy > mech.maxEnergy) mech.energy = mech.maxEnergy + } + if (mod.healthDrain && dmg !== Infinity && this.dropPowerUp) { + mech.addHealth(Math.min(this.health, dmg) * mod.healthDrain) + if (mech.health > mech.maxHealth) mech.health = mech.maxHealth + } + dmg /= Math.sqrt(this.mass) + this.health -= dmg + //this.fill = this.color + this.health + ')'; + this.onDamage(dmg); //custom damage effects + if (this.health < 0.05 && this.alive) this.death(); + } + }, + 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; //triggers mob removal in mob[i].replace(i) + if (this.dropPowerUp) { + if (mod.isEnergyLoss) mech.energy *= 0.75; + powerUps.spawnRandomPowerUp(this.position.x, this.position.y); + mech.lastKillCycle = mech.cycle; //tracks the last time a kill was made, mostly used in game.checks() + if (Math.random() < mod.sporesOnDeath) { + const len = Math.min(25, Math.floor(2 + this.mass * (0.5 + 0.5 * Math.random()))) + for (let i = 0; i < len; i++) { + b.spore(this.position) + } + } + if (Math.random() < mod.isBotSpawner) { + b.randomBot(this.position, false) + bullet[bullet.length - 1].endCycle = game.cycle + 1000 + Math.floor(400 * Math.random()) + } + if (mod.isExplodeMob) b.explosion(this.position, Math.min(550, Math.sqrt(this.mass + 2.5) * 50)) + if (mod.nailsDeathMob) b.targetedNail(this.position, mod.nailsDeathMob, 40 + 7 * Math.random()) + } else if (mod.isShieldAmmo && this.shield) { + let type = "ammo" + if (Math.random() < 0.4) { + type = "heal" + } else if (Math.random() < 0.3 && !mod.isSuperDeterminism) { + type = "reroll" + } + for (let i = 0, len = Math.ceil(2 * Math.random()); i < len; i++) { + powerUps.spawn(this.position.x, this.position.y, type); + } + } + }, + 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 there are too many bodies don't turn into blocks to help performance + if (this.leaveBody && body.length < 60 && this.mass < 200 && this.radius > 18) { + const len = body.length; + const v = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) //might help with vertex collision issue, not sure + body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, v); + Matter.Body.setVelocity(body[len], Vector.mult(this.velocity, 0.5)); + Matter.Body.setAngularVelocity(body[len], this.angularVelocity); + body[len].collisionFilter.category = cat.body; + body[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet; + // if (body[len].mass > 10 || 45 + 10 * Math.random() < body.length) { + // body[len].collisionFilter.mask = cat.player | cat.bullet | cat.mob | cat.mobBullet; + // } + body[len].classType = "body"; + World.add(engine.world, body[len]); //add to world + + //large mobs shrink so they don't block paths + if (body[len].mass > 9) { + const shrink = function(that, massLimit) { + if (that.mass > massLimit) { + const scale = 0.95; + Matter.Body.scale(that, scale, scale); + setTimeout(shrink, 20, that, massLimit); + } + }; + shrink(body[len], 7 + 4 * Math.random()) + } + } + Matter.World.remove(engine.world, this); + mob.splice(i, 1); + } + }); + mob[i].alertRange2 = Math.pow(mob[i].radius * 3 + 550, 2); + World.add(engine.world, mob[i]); //add to world + } }; \ No newline at end of file diff --git a/js/mods.js b/js/mods.js index 48a415e..add0322 100644 --- a/js/mods.js +++ b/js/mods.js @@ -92,7 +92,7 @@ const mod = { if (mod.restDamage > 1 && player.speed < 1) dmg *= mod.restDamage if (mod.isEnergyDamage) dmg *= 1 + mech.energy / 5.5; if (mod.isDamageFromBulletCount) dmg *= 1 + bullet.length * 0.0038 - if (mod.isRerollDamage) dmg *= 1 + 0.06 * powerUps.reroll.rerolls + if (mod.isRerollDamage) dmg *= 1 + 0.05 * powerUps.reroll.rerolls if (mod.isOneGun && b.inventory.length < 2) dmg *= 1.25 if (mod.isNoFireDamage && mech.cycle > mech.fireCDcycle + 120) dmg *= 1.5 return dmg * mod.slowFire * mod.aimDamage @@ -245,7 +245,7 @@ const mod = { }, { name: "perturbation theory", - description: "increase damage by 6%
for each of your rerolls", + description: "increase damage by 5%
for each of your rerolls", maxCount: 1, count: 0, allowed() { @@ -934,7 +934,7 @@ const mod = { }, { name: "piezoelectricity", - description: "colliding with mobs fills your energy
reduce harm by 15%", + description: "colliding with mobs overfills energy by 300%
reduce harm by 15%", maxCount: 1, count: 0, allowed() { @@ -943,7 +943,7 @@ const mod = { requires: "not mass-energy equivalence", effect() { mod.isPiezo = true; - mech.energy = mech.maxEnergy; + if (mech.energy < mech.maxEnergy * 3) mech.energy = mech.maxEnergy * 3; }, remove() { mod.isPiezo = false; @@ -1042,7 +1042,7 @@ const mod = { }, { name: "energy conservation", - description: "10% of damage done recovered as energy", + description: "7% of damage done recovered as energy", maxCount: 9, count: 0, allowed() { @@ -1050,7 +1050,7 @@ const mod = { }, requires: "", effect() { - mod.energySiphon += 0.1; + mod.energySiphon += 0.07; }, remove() { mod.energySiphon = 0; @@ -1058,7 +1058,7 @@ const mod = { }, { name: "waste energy recovery", - description: "if a mob has died in the last 5 seconds
regen 6% of max energy every second", + description: "if a mob has died in the last 5 seconds
regen 5% of max energy every second", maxCount: 1, count: 0, allowed() { @@ -1182,7 +1182,7 @@ const mod = { powerUps.reroll.changeRerolls(0) }, 1000); }, - description: "instead of dying consume a reroll
and spawn 4 heal power ups", + description: "consume a reroll to avoid dying once a level
and spawn 6 heal power ups", maxCount: 1, count: 0, allowed() { @@ -1191,6 +1191,7 @@ const mod = { requires: "at least 1 reroll", effect() { mod.isDeathAvoid = true; + mod.isDeathAvoidedThisLevel = false; setTimeout(function() { powerUps.reroll.changeRerolls(0) }, 1000); @@ -2432,7 +2433,7 @@ const mod = { } }, { - name: "beam splitter", + name: "diffraction grating", description: `your laser gains 2 diverging beams
decrease laser damage by 10%`, maxCount: 9, count: 0, @@ -3098,6 +3099,7 @@ const mod = { isHealthRecovery: null, isEnergyLoss: null, isDeathAvoid: null, + isDeathAvoidedThisLevel: null, waveSpeedMap: null, waveSpeedBody: null, isSporeField: null, diff --git a/js/player.js b/js/player.js index 322057c..d51d2e4 100644 --- a/js/player.js +++ b/js/player.js @@ -498,11 +498,15 @@ const mech = { if (mod.isEnergyHealth) { mech.energy -= dmg; if (mech.energy < 0 || isNaN(mech.energy)) { //taking deadly damage - if (mod.isDeathAvoid && powerUps.reroll.rerolls) { + if (mod.isDeathAvoid && powerUps.reroll.rerolls && !mod.isDeathAvoidedThisLevel) { + mod.isDeathAvoidedThisLevel = true powerUps.reroll.changeRerolls(-1) game.makeTextLog(` death avoided
${powerUps.reroll.rerolls} rerolls left
`, 420) + for (let i = 0; i < 6; i++) { + powerUps.spawn(mech.pos.x, mech.pos.y, "heal", false); + } mech.energy = mech.maxEnergy - mech.immuneCycle = mech.cycle + 120 //disable this.immuneCycle bonus seconds + mech.immuneCycle = mech.cycle + 360 //disable this.immuneCycle bonus seconds game.wipe = function() { //set wipe to have trails ctx.fillStyle = "rgba(255,255,255,0.03)"; ctx.fillRect(0, 0, canvas.width, canvas.height); @@ -511,7 +515,7 @@ const mech = { game.wipe = function() { //set wipe to normal ctx.clearRect(0, 0, canvas.width, canvas.height); } - }, 2000); + }, 3000); return; } else { //death @@ -525,14 +529,15 @@ const mech = { dmg *= mech.harmReduction() mech.health -= dmg; if (mech.health < 0 || isNaN(mech.health)) { - if (mod.isDeathAvoid && powerUps.reroll.rerolls > 0) { //&& Math.random() < 0.5 + if (mod.isDeathAvoid && powerUps.reroll.rerolls > 0 && !mod.isDeathAvoidedThisLevel) { //&& Math.random() < 0.5 + mod.isDeathAvoidedThisLevel = true mech.health = 0.05 powerUps.reroll.changeRerolls(-1) game.makeTextLog(` death avoided
${powerUps.reroll.rerolls} rerolls left
`, 420) - for (let i = 0; i < 4; i++) { + for (let i = 0; i < 6; i++) { powerUps.spawn(mech.pos.x, mech.pos.y, "heal", false); } - mech.immuneCycle = mech.cycle + 120 //disable this.immuneCycle bonus seconds + mech.immuneCycle = mech.cycle + 360 //disable this.immuneCycle bonus seconds // game.makeTextLog(" death avoided
1 reroll consumed
", 420) game.wipe = function() { //set wipe to have trails @@ -543,7 +548,7 @@ const mech = { game.wipe = function() { //set wipe to normal ctx.clearRect(0, 0, canvas.width, canvas.height); } - }, 2000); + }, 3000); } else { mech.health = 0; mech.death(); @@ -1038,7 +1043,7 @@ const mech = { if (mech.energy < 0) { mech.energy = 0; } - if (mech.energy > mech.maxEnergy) mech.energy = mech.maxEnergy; + // if (mech.energy > mech.maxEnergy) mech.energy = mech.maxEnergy; if (mod.blockDmg) { who.damage(mod.blockDmg * b.dmgScale) @@ -1709,7 +1714,7 @@ const mech = { mech.grabPowerUp(); mech.lookForPickUp(180); - const DRAIN = 0.0008 + const DRAIN = 0.0011 if (mech.energy > DRAIN) { mech.energy -= DRAIN; if (mech.energy < DRAIN) { @@ -1719,11 +1724,9 @@ const mech = { } //draw field everywhere ctx.globalCompositeOperation = "saturation" - // ctx.fillStyle = "rgba(100,200,230," + (0.25 + 0.06 * Math.random()) + ")"; ctx.fillStyle = "#ccc"; ctx.fillRect(-100000, -100000, 200000, 200000) ctx.globalCompositeOperation = "source-over" - //stop time mech.isBodiesAsleep = true; diff --git a/js/spawn.js b/js/spawn.js index 4791d92..63d8513 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -94,17 +94,17 @@ const spawn = { me.frictionAir = 0.01; me.memory = Infinity; me.locatePlayer(); - const density = 5 + const density = 1 Matter.Body.setDensity(me, density); //extra dense //normal is 0.001 //makes effective life much larger - spawn.shield(me, x, y, 1); + // spawn.shield(me, x, y, 1); me.onDeath = function() { level.bossKilled = true; level.exit.x = 5500; level.exit.y = -330; }; me.onDamage = function() {}; - me.cycle = 300; - me.endCycle = 600; + me.cycle = 420; + me.endCycle = 720; me.mode = 0; me.do = function() { //hold position @@ -118,56 +118,54 @@ const spawn = { }); this.modeDo(); //this does different things based on the mode this.checkStatus(); - if (!mech.isBodiesAsleep) { - this.cycle++; //switch modes - if (this.health > 0.33) { - if (this.cycle > this.endCycle) { - this.cycle = 0; - this.mode++ - if (this.mode > 2) { - this.mode = 0; - this.fill = "#50f"; - this.rotateVelocity = Math.abs(this.rotateVelocity) * (player.position.x > this.position.x ? 1 : -1) //rotate so that the player can get away - this.modeDo = this.modeLasers - //push blocks and player away, since this is the end of suck, and suck causes blocks to fall on the boss and stun it - Matter.Body.scale(this, 10, 10); - Matter.Body.setDensity(me, density); //extra dense //normal is 0.001 //makes effective life much larger - if (!this.isShielded) spawn.shield(this, x, y, 1); // regen shield to also prevent stun - for (let i = 0, len = body.length; i < len; ++i) { - if (body[i].position.x > this.position.x) { - body[i].force.x = 0.5 - } else { - body[i].force.x = -0.5 - } - + this.cycle++; //switch modes÷ + // if (!mech.isBodiesAsleep) { + if (this.health > 0.25) { + if (this.cycle > this.endCycle) { + this.cycle = 0; + this.mode++ + if (this.mode > 2) { + this.mode = 0; + this.fill = "#50f"; + this.rotateVelocity = Math.abs(this.rotateVelocity) * (player.position.x > this.position.x ? 1 : -1) //rotate so that the player can get away + this.modeDo = this.modeLasers + //push blocks and player away, since this is the end of suck, and suck causes blocks to fall on the boss and stun it + Matter.Body.scale(this, 10, 10); + Matter.Body.setDensity(me, density); //extra dense //normal is 0.001 //makes effective life much larger + if (!this.isShielded) spawn.shield(this, x, y, 1); // regen shield to also prevent stun + for (let i = 0, len = body.length; i < len; ++i) { + if (body[i].position.x > this.position.x) { + body[i].force.x = 0.5 + } else { + body[i].force.x = -0.5 } - } else if (this.mode === 1) { - this.fill = "rgb(150,150,255)"; - this.endCycle = 360 - this.modeDo = this.modeSpawns - } else if (this.mode === 2) { - this.fill = "#000"; - this.endCycle = 720 - this.modeDo = this.modeSuck - Matter.Body.scale(this, 0.1, 0.1); - Matter.Body.setDensity(me, 100 * density); //extra dense //normal is 0.001 //makes effective life much larger } + } else if (this.mode === 1) { + this.fill = "#50f"; // this.fill = "rgb(150,150,255)"; + this.modeDo = this.modeSpawns + } else if (this.mode === 2) { + this.fill = "#000"; + this.modeDo = this.modeSuck + Matter.Body.scale(this, 0.1, 0.1); + Matter.Body.setDensity(me, 100 * density); //extra dense //normal is 0.001 //makes effective life much larger } - } else if (this.mode !== 3) { //all three modes at once - Matter.Body.setDensity(me, density); //extra dense //normal is 0.001 //makes effective life much larger - if (this.mode === 2) { - Matter.Body.scale(this, 5, 5); - } else { - Matter.Body.scale(this, 0.5, 0.5); - } - this.mode = 3 - this.fill = "#000"; - this.eventHorizon = 1200 - this.rotateVelocity = Math.abs(this.rotateVelocity) * (player.position.x > this.position.x ? 1 : -1) //rotate so that the player can get away - if (!this.isShielded) spawn.shield(this, x, y, 1); //regen shield here ? - this.modeDo = this.modeAll } + } else if (this.mode !== 3) { //all three modes at once + this.cycle = 0; + Matter.Body.setDensity(me, density); //extra dense //normal is 0.001 //makes effective life much larger + if (this.mode === 2) { + Matter.Body.scale(this, 5, 5); + } else { + Matter.Body.scale(this, 0.5, 0.5); + } + this.mode = 3 + this.fill = "#000"; + this.eventHorizon = 1200 + this.rotateVelocity = Math.abs(this.rotateVelocity) * (player.position.x > this.position.x ? 1 : -1) //rotate so that the player can get away + if (!this.isShielded) spawn.shield(this, x, y, 1); //regen shield here ? + this.modeDo = this.modeAll } + // } }; me.modeDo = function() {} me.modeAll = function() { @@ -176,25 +174,32 @@ const spawn = { this.modeLasers() } me.modeSpawns = function() { - if (!(this.cycle % 320) && !mech.isBodiesAsleep && mob.length < 40) { - Matter.Body.setAngularVelocity(this, 0.11) + if ((this.cycle === 2 || this.cycle === 300) && !mech.isBodiesAsleep && mob.length < 40) { + Matter.Body.setAngularVelocity(this, 0.1) //fire a bullet from each vertex for (let i = 0, len = this.vertices.length; i < len; i++) { let whoSpawn = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]; spawn[whoSpawn](this.vertices[i].x, this.vertices[i].y); //give the bullet a rotational velocity as if they were attached to a vertex - const velocity = Vector.mult(Vector.perp(Vector.normalise(Vector.sub(this.position, this.vertices[i]))), -20) + const velocity = Vector.mult(Vector.perp(Vector.normalise(Vector.sub(this.position, this.vertices[i]))), -18) Matter.Body.setVelocity(mob[mob.length - 1], { x: this.velocity.x + velocity.x, y: this.velocity.y + velocity.y }); } + if (game.difficulty > 60) { + spawn.randomLevelBoss(3000, -1100) + if (game.difficulty > 100) { + spawn.randomLevelBoss(3000, -1300) + } + } } } - me.eventHorizon = 1400 + me.eventHorizon = 1300 + me.eventHorizonCycleRate = 4 * Math.PI / me.endCycle me.modeSuck = function() { //eventHorizon waves in and out - eventHorizon = this.eventHorizon * (1 + 0.2 * Math.sin(game.cycle * 0.015)) + if (!mech.isBodiesAsleep) eventHorizon = this.eventHorizon * (1 - 0.25 * Math.cos(this.cycle * this.eventHorizonCycleRate)) //0.014 //draw darkness ctx.beginPath(); ctx.arc(this.position.x, this.position.y, eventHorizon * 0.2, 0, 2 * Math.PI); @@ -242,58 +247,56 @@ const spawn = { me.rotateVelocity = 0.0025 me.rotateCount = 0; me.modeLasers = function() { - if (!this.isStunned) { - if (!mech.isBodiesAsleep) { - let slowed = false //check if slowed - for (let i = 0; i < this.status.length; i++) { - if (this.status[i].type === "slow") { - slowed = true - break - } - } - if (!slowed) { - this.rotateCount++ - Matter.Body.setAngle(this, this.rotateCount * this.rotateVelocity) - Matter.Body.setAngularVelocity(this, 0) - Matter + if (!mech.isBodiesAsleep && !this.isStunned) { + let slowed = false //check if slowed + for (let i = 0; i < this.status.length; i++) { + if (this.status[i].type === "slow") { + slowed = true + break } } - if (this.cycle < 180) { //damage scales up over 2 seconds to give player time to move - const scale = this.cycle / 180 - const dmg = 0.14 * game.dmgScale * scale - ctx.beginPath(); - this.laser(this.vertices[0], this.angle + Math.PI / 6, dmg); - this.laser(this.vertices[1], this.angle + 3 * Math.PI / 6, dmg); - this.laser(this.vertices[2], this.angle + 5 * Math.PI / 6, dmg); - this.laser(this.vertices[3], this.angle + 7 * Math.PI / 6, dmg); - this.laser(this.vertices[4], this.angle + 9 * Math.PI / 6, dmg); - this.laser(this.vertices[5], this.angle + 11 * Math.PI / 6, dmg); - ctx.strokeStyle = "#50f"; - ctx.lineWidth = 1.5 * scale; - ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); - ctx.stroke(); // Draw it - ctx.setLineDash([0, 0]); - ctx.lineWidth = 20; - ctx.strokeStyle = `rgba(80,0,255,${0.07*scale})`; - ctx.stroke(); // Draw it - } else { - ctx.beginPath(); - this.laser(this.vertices[0], this.angle + Math.PI / 6); - this.laser(this.vertices[1], this.angle + 3 * Math.PI / 6); - this.laser(this.vertices[2], this.angle + 5 * Math.PI / 6); - this.laser(this.vertices[3], this.angle + 7 * Math.PI / 6); - this.laser(this.vertices[4], this.angle + 9 * Math.PI / 6); - this.laser(this.vertices[5], this.angle + 11 * Math.PI / 6); - ctx.strokeStyle = "#50f"; - ctx.lineWidth = 1.5; - ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); - ctx.stroke(); // Draw it - ctx.setLineDash([0, 0]); - ctx.lineWidth = 20; - ctx.strokeStyle = "rgba(80,0,255,0.07)"; - ctx.stroke(); // Draw it + if (!slowed) { + this.rotateCount++ + Matter.Body.setAngle(this, this.rotateCount * this.rotateVelocity) + Matter.Body.setAngularVelocity(this, 0) + Matter } } + if (this.cycle < 240) { //damage scales up over 2 seconds to give player time to move + const scale = this.cycle / 180 + const dmg = 0.14 * game.dmgScale * scale + ctx.beginPath(); + this.laser(this.vertices[0], this.angle + Math.PI / 6, dmg); + this.laser(this.vertices[1], this.angle + 3 * Math.PI / 6, dmg); + this.laser(this.vertices[2], this.angle + 5 * Math.PI / 6, dmg); + this.laser(this.vertices[3], this.angle + 7 * Math.PI / 6, dmg); + this.laser(this.vertices[4], this.angle + 9 * Math.PI / 6, dmg); + this.laser(this.vertices[5], this.angle + 11 * Math.PI / 6, dmg); + ctx.strokeStyle = "#50f"; + ctx.lineWidth = 1.5 * scale; + ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([0, 0]); + ctx.lineWidth = 20; + ctx.strokeStyle = `rgba(80,0,255,${0.07*scale})`; + ctx.stroke(); // Draw it + } else { + ctx.beginPath(); + this.laser(this.vertices[0], this.angle + Math.PI / 6); + this.laser(this.vertices[1], this.angle + 3 * Math.PI / 6); + this.laser(this.vertices[2], this.angle + 5 * Math.PI / 6); + this.laser(this.vertices[3], this.angle + 7 * Math.PI / 6); + this.laser(this.vertices[4], this.angle + 9 * Math.PI / 6); + this.laser(this.vertices[5], this.angle + 11 * Math.PI / 6); + ctx.strokeStyle = "#50f"; + ctx.lineWidth = 1.5; + ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([0, 0]); + ctx.lineWidth = 20; + ctx.strokeStyle = "rgba(80,0,255,0.07)"; + ctx.stroke(); // Draw it + } me.laser = function(where, angle, dmg = 0.14 * game.dmgScale) { const vertexCollision = function(v1, v1End, domain) { for (let i = 0; i < domain.length; ++i) { diff --git a/todo.txt b/todo.txt index ef7b6b2..1f5a84b 100644 --- a/todo.txt +++ b/todo.txt @@ -1,31 +1,34 @@ -missile moves slightly differently - it used to slow when locked on to a target - now it slows when turning -missiles explode when near any mob -wormhole mod: cosmic string - now stuns mobs and applies radiation damage -mod time dilation: - quadruple your default energy regeneration +update to mod: anthropic principle - only works once per level + but gives 6 seconds of damage immunity and 2 extra heal power ups -added final boss level, it's still in progress so I'd love some feedback -also the game loops back to the intro level after the boss - I'll be working on the ending in the next patch, so the intro level is just a placeholder +most energy regeneration effects now overfill energy above the max by default +piezo electricity over fills energy by 300% (was 100%) ************** TODO - n-gon ************** -final boss has elements of other bosses - laser mode - if player is on left rotate counter clockwise - if player is on right rotate clockwise - start of laser mode - push block either left or right, not away - vibrating shape - grow and shrink - oscillate elliptical deformation (not sure how) +add an ending to the game + maybe the game ending should ask you to open the console and type in some commands + mirror ending (if no cheats) + level after final boss battle is the intro level, but flipped left right, with a fake player + damage the fake player to end the game + message about go outside + no ending (if cheats) + game goes on forever + also game goes on if player attacks, the fake player + game never ends if you have used cheats + +laser mod - your laser beam fires from your last position, not your current position + or apply to all guns? + +mod: While in the air, time is slowed. + or something else while in air a bot that eats ammo and converts them into rerolls or 2 ammo power ups = 1 reroll been getting some fps slow down after playing for a few minutes + this seems to be caused by capping the fps at 60, but 60 fps shouldn't have any slowdown new status effect: fear - push mob away from player for a time @@ -47,17 +50,6 @@ mod - explosions apply radiation damage over time mod self destruct - drones explode when they die drones lose extra time on collisions -add an ending to the game - add a final boss battle level - mirror ending (if no cheats) - level after final boss battle is the intro level, but flipped left right, with a fake player - damage the fake player to end the game - message about go outside - no ending (if cheats) - game goes on forever - also game goes on if player attacks, the fake player - game never ends if you have used cheats - foam or spore bullet on dmg shrink effect it might mess with the foam position of other bullets on the mob shrink when foam bullets end while attached, or shrink on collision?