From d7ab196dc3e04d966df7536cd3fc663eaf539fbd Mon Sep 17 00:00:00 2001 From: landgreen Date: Sun, 1 Nov 2020 18:31:25 -0800 Subject: [PATCH] final boss 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 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 --- js/bullet.js | 6675 ++++++++++++++++++------------------ js/game.js | 2297 +++++++------ js/index.js | 1578 ++++----- js/level.js | 9241 ++++++++++++++++++++++++-------------------------- js/mods.js | 102 +- js/player.js | 4823 +++++++++++++------------- js/spawn.js | 5676 ++++++++++++++++--------------- todo.txt | 61 +- 8 files changed, 15114 insertions(+), 15339 deletions(-) diff --git a/js/bullet.js b/js/bullet.js index ce3441c..2d763f7 100644 --- a/js/bullet.js +++ b/js/bullet.js @@ -1,3373 +1,3396 @@ let bullet = []; const b = { - dmgScale: null, //scales all gun damage from momentum, but not raw .dmg //set in levels.setDifficulty - gravity: 0.0006, //most other bodies have gravity = 0.001 - activeGun: null, //current gun in use by player - inventoryGun: 0, - inventory: [], //list of what guns player has // 0 starts with basic gun - fire() { - if (input.fire && mech.fireCDcycle < mech.cycle && (!input.field || mech.fieldFire) && b.inventory.length) { - if (b.guns[b.activeGun].ammo > 0) { - b.guns[b.activeGun].fire(); - if (mod.isCrouchAmmo && mech.crouch) { - if (mod.isCrouchAmmo % 2) { - b.guns[b.activeGun].ammo--; - game.updateGunHUD(); - } - mod.isCrouchAmmo++ //makes the no ammo toggle off and on - } else { - b.guns[b.activeGun].ammo--; - game.updateGunHUD(); - } - } else { - if (mod.isAmmoFromHealth) { - if (mech.health > 2 * mod.isAmmoFromHealth * mech.maxHealth) { - mech.damage(mod.isAmmoFromHealth * mech.maxHealth / mech.harmReduction()); - powerUps.spawn(mech.pos.x, mech.pos.y, "ammo"); - } else { - game.replaceTextLog = true; - game.makeTextLog("not enough health for catabolism to produce ammo", 120); - } - } else { - game.replaceTextLog = true; - game.makeTextLog("
NO AMMO

Q, E, and mouse wheel change weapons

", 200); - } - mech.fireCDcycle = mech.cycle + 30; //fire cooldown - } - if (mech.holdingTarget) { - mech.drop(); - } - } - }, - removeAllGuns() { - b.inventory = []; //removes guns and ammo - for (let i = 0, len = b.guns.length; i < len; ++i) { - b.guns[i].count = 0; - b.guns[i].have = false; - if (b.guns[i].ammo != Infinity) b.guns[i].ammo = 0; - } - b.activeGun = null; - }, - bulletRemove() { //run in main loop - //remove bullet if at end cycle for that bullet - let i = bullet.length; - while (i--) { - if (bullet[i].endCycle < game.cycle) { - bullet[i].onEnd(i); //some bullets do stuff on end - if (bullet[i]) { - Matter.World.remove(engine.world, bullet[i]); - bullet.splice(i, 1); - } else { - break; //if bullet[i] doesn't exist don't complete the for loop, because the game probably reset - } - } - } - }, - bulletDraw() { - ctx.beginPath(); - for (let i = 0, len = bullet.length; i < len; i++) { - let vertices = bullet[i].vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j += 1) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - } - ctx.fillStyle = "#000"; - ctx.fill(); - }, - bulletDo() { - for (let i = 0, len = bullet.length; i < len; i++) { - bullet[i].do(); - } - }, - fireProps(cd, speed, dir, me) { - mech.fireCDcycle = mech.cycle + Math.floor(cd * b.fireCD); // cool down - Matter.Body.setVelocity(bullet[me], { - x: mech.Vx / 2 + speed * Math.cos(dir), - y: mech.Vy / 2 + speed * Math.sin(dir) - }); - World.add(engine.world, bullet[me]); //add bullet to world - }, - fireCD: 1, - setFireCD() { - b.fireCD = mod.fireRate * mod.slowFire * mod.rerollHaste * mod.aimDamage / mod.fastTime - }, - fireAttributes(dir, rotate = true) { - if (rotate) { - return { - // density: 0.0015, //frictionAir: 0.01, //restitution: 0, - angle: dir, - friction: 0.5, - frictionAir: 0, - dmg: 0, //damage done in addition to the damage from momentum - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield - }, - minDmgSpeed: 10, - beforeDmg() {}, //this.endCycle = 0 //triggers despawn - onEnd() {} - }; - } else { - return { - // density: 0.0015, //frictionAir: 0.01, //restitution: 0, - inertia: Infinity, //prevents rotation - angle: dir, - friction: 0.5, - frictionAir: 0, - dmg: 0, //damage done in addition to the damage from momentum - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield - }, - minDmgSpeed: 10, - beforeDmg() {}, //this.endCycle = 0 //triggers despawn - onEnd() {} - }; - } - }, - muzzleFlash(radius = 10) { - ctx.fillStyle = "#fb0"; - ctx.beginPath(); - ctx.arc(mech.pos.x + 35 * Math.cos(mech.angle), mech.pos.y + 35 * Math.sin(mech.angle), radius, 0, 2 * Math.PI); - ctx.fill(); - }, - removeConsBB(me) { - for (let i = 0, len = consBB.length; i < len; ++i) { - if (consBB[i].bodyA === me) { - consBB[i].bodyA = consBB[i].bodyB; - consBB.splice(i, 1); - break; - } else if (consBB[i].bodyB === me) { - consBB[i].bodyB = consBB[i].bodyA; - consBB.splice(i, 1); - break; - } - } - }, - onCollision(event) { - 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) - } - - function collideBulletStatic(obj) { - if (obj.onWallHit) obj.onWallHit(); - } - } - }, - explosion(where, radius) { // typically explode is used for some bullets with .onEnd - radius *= mod.explosiveRadius - let dist, sub, knock; - let dmg = radius * 0.013; - if (mod.isExplosionHarm) radius *= 1.8 // 1/sqrt(2) radius -> area - if (mod.isSmallExplosion) { - radius *= 0.8 - dmg *= 1.6 - } - - game.drawList.push({ //add dmg to draw queue - x: where.x, - y: where.y, - radius: radius, - color: "rgba(255,25,0,0.6)", - time: game.drawTime - }); - - const alertRange = 100 + radius * 2; //alert range - game.drawList.push({ //add alert to draw queue - x: where.x, - y: where.y, - radius: alertRange, - color: "rgba(100,20,0,0.03)", - time: game.drawTime - }); - - //player damage and knock back - sub = Vector.sub(where, player.position); - dist = Vector.magnitude(sub); - - if (dist < radius) { - - if (mod.isImmuneExplosion) { - const mitigate = Math.min(1, Math.max(1 - mech.energy * 0.7, 0)) - mech.damage(mitigate * radius * (mod.isExplosionHarm ? 0.0004 : 0.0001)); - } else { - mech.damage(radius * (mod.isExplosionHarm ? 0.0004 : 0.0001)); - } - - // if (!(mod.isImmuneExplosion && mech.energy > 0.97)) { - // if (mod.isExplosionHarm) { - // mech.damage(radius * 0.0004); //300% more player damage from explosions - // } else { - // mech.damage(radius * 0.0001); //normal player damage from explosions - // } - // mech.drop(); - // } - knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * player.mass * 0.015); - player.force.x += knock.x; - player.force.y += knock.y; - } else if (dist < alertRange) { - knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * player.mass * 0.008); - player.force.x += knock.x; - player.force.y += knock.y; - } - - //body knock backs - for (let i = 0, len = body.length; i < len; ++i) { - sub = Vector.sub(where, body[i].position); - dist = Vector.magnitude(sub); - if (dist < radius) { - knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) * 0.022); - body[i].force.x += knock.x; - body[i].force.y += knock.y; - } else if (dist < alertRange) { - knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) * 0.013); - body[i].force.x += knock.x; - body[i].force.y += knock.y; - } - } - - //power up knock backs - for (let i = 0, len = powerUp.length; i < len; ++i) { - sub = Vector.sub(where, powerUp[i].position); - dist = Vector.magnitude(sub); - if (dist < radius) { - knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) * 0.015); - powerUp[i].force.x += knock.x; - powerUp[i].force.y += knock.y; - } else if (dist < alertRange) { - knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) * 0.01); - powerUp[i].force.x += knock.x; - powerUp[i].force.y += knock.y; - } - } - - //mob damage and knock back with alert - let damageScale = 1.5; // reduce dmg for each new target to limit total AOE damage - for (let i = 0, len = mob.length; i < len; ++i) { - if (mob[i].alive && !mob[i].isShielded) { - sub = Vector.sub(where, mob[i].position); - dist = Vector.magnitude(sub) - mob[i].radius; - if (dist < radius) { - if (mob[i].shield) dmg *= 2.5 //balancing explosion dmg to shields - if (Matter.Query.ray(map, mob[i].position, where).length > 0) dmg *= 0.5 //reduce damage if a wall is in the way - mob[i].damage(dmg * damageScale * b.dmgScale); - mob[i].locatePlayer(); - knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) * 0.01); - mob[i].force.x += knock.x; - mob[i].force.y += knock.y; - radius *= 0.95 //reduced range for each additional explosion target - damageScale *= 0.87 //reduced damage for each additional explosion target - } else if (!mob[i].seePlayer.recall && dist < alertRange) { - mob[i].locatePlayer(); - knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) * 0.006); - mob[i].force.x += knock.x; - mob[i].force.y += knock.y; - } - } - } - }, - missile(where, angle, speed, size = 1, spawn = 0) { - const me = bullet.length; - bullet[me] = Bodies.rectangle(where.x, where.y, 30 * size, 4 * size, b.fireAttributes(angle)); - const thrust = 0.00417 * bullet[me].mass; - Matter.Body.setVelocity(bullet[me], { - x: mech.Vx / 2 + speed * Math.cos(angle), - y: mech.Vy / 2 + speed * Math.sin(angle) - }); - World.add(engine.world, bullet[me]); //add bullet to world - bullet[me].frictionAir = 0.023 - bullet[me].endCycle = game.cycle + Math.floor((280 + 40 * Math.random()) * mod.isBulletsLastLonger); - bullet[me].explodeRad = 170 + 60 * Math.random(); - bullet[me].lookFrequency = Math.floor(21 + Math.random() * 7); - bullet[me].onEnd = function () { - b.explosion(this.position, this.explodeRad * size); //makes bullet do explosive damage at end - if (spawn) { - for (let i = 0; i < mod.recursiveMissiles; i++) { - if (0.3 - 0.03 * i > Math.random()) { - b.missile(this.position, this.angle + Math.PI + 0.5 * (Math.random() - 0.5), 0, 0.33 + size, mod.recursiveMissiles) - break; - } - } - } - } - bullet[me].beforeDmg = function () { - this.tryToLockOn(); - this.endCycle = 0; //bullet ends cycle after doing damage // also triggers explosion - }; - bullet[me].lockedOn = null; - bullet[me].tryToLockOn = function () { - this.lockedOn = null; - let closeDist = Infinity; - - //look for closest target to where the missile will be in 30 cycles - const futurePos = Vector.add(this.position, Vector.mult(this.velocity, 30)) - for (let i = 0, len = mob.length; i < len; ++i) { - if ( - mob[i].alive && mob[i].dropPowerUp && - Matter.Query.ray(map, this.position, mob[i].position).length === 0 && - Matter.Query.ray(body, this.position, mob[i].position).length === 0 - ) { - const futureDist = Vector.magnitude(Vector.sub(futurePos, mob[i].position)); - if (futureDist < closeDist) { - closeDist = futureDist; - this.lockedOn = mob[i]; - this.frictionAir = 0.05; //extra friction once a target it locked - } - } - } - //explode when bullet is close enough to target - if (this.lockedOn && Vector.magnitude(Vector.sub(this.position, this.lockedOn.position)) < this.explodeRad) { - // console.log('hit') - this.endCycle = 0; //bullet ends cycle after doing damage //also triggers explosion - this.lockedOn.damage(b.dmgScale * 4 * size); //does extra damage to target - } - }; - bullet[me].do = function () { - if (!mech.isBodiesAsleep) { - if (!(mech.cycle % this.lookFrequency)) { - this.tryToLockOn(); - } - - //rotate missile towards the target - if (this.lockedOn) { - const face = { - x: Math.cos(this.angle), - y: Math.sin(this.angle) - }; - const target = Vector.normalise(Vector.sub(this.position, this.lockedOn.position)); - if (Vector.dot(target, face) > -0.98) { - if (Vector.cross(target, face) > 0) { - Matter.Body.rotate(this, 0.08); - } else { - Matter.Body.rotate(this, -0.08); - } - } - } - //accelerate in direction bullet is facing - const dir = this.angle; // + (Math.random() - 0.5); - this.force.x += Math.cos(dir) * thrust; - this.force.y += Math.sin(dir) * thrust; - - //draw rocket - ctx.beginPath(); - ctx.arc(this.position.x - Math.cos(this.angle) * (25 * size - 3) + (Math.random() - 0.5) * 4, - this.position.y - Math.sin(this.angle) * (25 * size - 3) + (Math.random() - 0.5) * 4, - 11 * size, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(255,155,0,0.5)"; - ctx.fill(); - } else { - //draw rocket with time stop - ctx.beginPath(); - ctx.arc(this.position.x - Math.cos(this.angle) * (30 * size - 3) + (Math.random() - 0.5) * 4, - this.position.y - Math.sin(this.angle) * (30 * size - 3) + (Math.random() - 0.5) * 4, - 2 + 9 * size, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(255,155,0,0.5)"; - ctx.fill(); - } - } - }, - laser(where = { - x: mech.pos.x + 20 * Math.cos(mech.angle), - y: mech.pos.y + 20 * Math.sin(mech.angle) - }, whereEnd = { - x: where.x + 3000 * Math.cos(mech.angle), - y: where.y + 3000 * Math.sin(mech.angle) - }, dmg = mod.laserDamage, reflections = mod.laserReflections, isThickBeam = false) { - const reflectivity = 1 - 1 / (reflections * 1.5) - let damage = b.dmgScale * dmg - let best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - const color = "#f00"; - const path = [{ - x: where.x, - y: where.y - }, - { - x: whereEnd.x, - y: whereEnd.y - } - ]; - const vertexCollision = function (v1, v1End, domain) { - for (let i = 0; i < domain.length; ++i) { - let vertices = domain[i].vertices; - const len = vertices.length - 1; - for (let j = 0; j < len; j++) { - results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[j], - v2: vertices[j + 1] - }; - } - } - } - results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[0], - v2: vertices[len] - }; - } - } - } - }; - - const checkForCollisions = function () { - best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - vertexCollision(path[path.length - 2], path[path.length - 1], mob); - vertexCollision(path[path.length - 2], path[path.length - 1], map); - vertexCollision(path[path.length - 2], path[path.length - 1], body); - }; - const laserHitMob = function () { - if (best.who.alive) { - best.who.damage(damage); - best.who.locatePlayer(); - game.drawList.push({ //add dmg to draw queue - x: path[path.length - 1].x, - y: path[path.length - 1].y, - radius: Math.sqrt(damage) * 100, - color: "rgba(255,0,0,0.5)", - time: game.drawTime - }); - } - // ctx.fillStyle = color; //draw mob damage circle - // ctx.beginPath(); - // ctx.arc(path[path.length - 1].x, path[path.length - 1].y, Math.sqrt(damage) * 100, 0, 2 * Math.PI); - // ctx.fill(); - }; - const reflection = function () { // https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector - const n = Vector.perp(Vector.normalise(Vector.sub(best.v1, best.v2))); - const d = Vector.sub(path[path.length - 1], path[path.length - 2]); - const nn = Vector.mult(n, 2 * Vector.dot(d, n)); - const r = Vector.normalise(Vector.sub(d, nn)); - path[path.length] = Vector.add(Vector.mult(r, 3000), path[path.length - 1]); - }; - - checkForCollisions(); - let lastBestOdd - let lastBestEven = best.who //used in hack below - if (best.dist2 !== Infinity) { //if hitting something - path[path.length - 1] = { - x: best.x, - y: best.y - }; - laserHitMob(); - for (let i = 0; i < reflections; i++) { - reflection(); - checkForCollisions(); - if (best.dist2 !== Infinity) { //if hitting something - lastReflection = best - - path[path.length - 1] = { - x: best.x, - y: best.y - }; - damage *= reflectivity - laserHitMob(); - //I'm not clear on how this works, but it gets ride of a bug where the laser reflects inside a block, often vertically. - //I think it checks to see if the laser is reflecting off a different part of the same block, if it is "inside" a block - if (i % 2) { - if (lastBestOdd === best.who) break - } else { - lastBestOdd = best.who - if (lastBestEven === best.who) break - } - } else { - break - } - } - } - if (isThickBeam) { - for (let i = 1, len = path.length; i < len; ++i) { - ctx.moveTo(path[i - 1].x, path[i - 1].y); - ctx.lineTo(path[i].x, path[i].y); - } - } else { - ctx.strokeStyle = color; - ctx.lineWidth = 2 - ctx.lineDashOffset = 300 * Math.random() - ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]); - for (let i = 1, len = path.length; i < len; ++i) { - ctx.beginPath(); - ctx.moveTo(path[i - 1].x, path[i - 1].y); - ctx.lineTo(path[i].x, path[i].y); - ctx.stroke(); - ctx.globalAlpha *= reflectivity; //reflections are less intense - } - ctx.setLineDash([0, 0]); - ctx.globalAlpha = 1; - } - }, - mine(where, velocity, angle = 0, isAmmoBack = false) { - const bIndex = bullet.length; - bullet[bIndex] = Bodies.rectangle(where.x, where.y, 45, 16, { - angle: angle, - friction: 1, - frictionStatic: 1, - frictionAir: 0, - restitution: 0, - dmg: 0, //damage done in addition to the damage from momentum - classType: "bullet", - bulletType: "mine", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield | cat.bullet - }, - minDmgSpeed: 5, - stillCount: 0, - isArmed: false, - endCycle: Infinity, - lookFrequency: 0, - range: 700, - beforeDmg() {}, - do() { - this.force.y += this.mass * 0.002; //extra gravity - let collide = Matter.Query.collides(this, map) //check if collides with map - if (collide.length > 0) { - for (let i = 0; i < collide.length; i++) { - if (collide[i].bodyA.collisionFilter.category === cat.map) { // || collide[i].bodyB.collisionFilter.category === cat.map) { - const angle = Vector.angle(collide[i].normal, { - x: 1, - y: 0 - }) - Matter.Body.setAngle(this, Math.atan2(collide[i].tangent.y, collide[i].tangent.x)) - //move until touching map again after rotation - for (let j = 0; j < 10; j++) { - if (Matter.Query.collides(this, map).length > 0) { //touching map - if (angle > -0.2 || angle < -1.5) { //don't stick to level ground - Matter.Body.setStatic(this, true) //don't set to static if not touching map - this.collisionFilter.mask = cat.map | cat.bullet - } else { - Matter.Body.setVelocity(this, { - x: 0, - y: 0 - }); - Matter.Body.setAngularVelocity(this, 0) - } - this.arm(); - - //sometimes the mine can't attach to map and it just needs to be reset - const that = this - setTimeout(function () { - if (Matter.Query.collides(that, map).length === 0 || Matter.Query.point(map, that.position).length > 0) { - // console.log(that) - that.endCycle = 0 // if not touching map explode - that.isArmed = false - b.mine(that.position, that.velocity, that.angle) + dmgScale: null, //scales all gun damage from momentum, but not raw .dmg //set in levels.setDifficulty + gravity: 0.0006, //most other bodies have gravity = 0.001 + activeGun: null, //current gun in use by player + inventoryGun: 0, + inventory: [], //list of what guns player has // 0 starts with basic gun + fire() { + if (input.fire && mech.fireCDcycle < mech.cycle && (!input.field || mech.fieldFire) && b.inventory.length) { + if (b.guns[b.activeGun].ammo > 0) { + b.guns[b.activeGun].fire(); + if (mod.isCrouchAmmo && mech.crouch) { + if (mod.isCrouchAmmo % 2) { + b.guns[b.activeGun].ammo--; + game.updateGunHUD(); } - }, 100, that); - break - } - //move until you are touching the wall - Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(collide[i].normal, 2))) - } - break - } - } - } else { - if (this.speed < 1 && this.angularSpeed < 0.01 && !mech.isBodiesAsleep) { - this.stillCount++ - } - } - if (this.stillCount > 25) this.arm(); - }, - arm() { - this.lookFrequency = game.cycle + 60 - this.do = function () { //overwrite the do method for this bullet - this.force.y += this.mass * 0.002; //extra gravity - - if (game.cycle > this.lookFrequency) { - this.isArmed = true - this.lookFrequency = 50 + Math.floor(27 * Math.random()) - game.drawList.push({ - //add dmg to draw queue - x: this.position.x, - y: this.position.y, - radius: 10, - color: "#f00", - time: 4 - }); - this.do = function () { //overwrite the do method for this bullet - this.force.y += this.mass * 0.002; //extra gravity - if (!(game.cycle % this.lookFrequency)) { //find mob targets - for (let i = 0, len = mob.length; i < len; ++i) { - if (Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)) < 500000 && - mob[i].dropPowerUp && - Matter.Query.ray(map, this.position, mob[i].position).length === 0 && - Matter.Query.ray(body, this.position, mob[i].position).length === 0) { - this.endCycle = 0 //end life if mob is near and visible - if (Math.random() < 0.8) isAmmoBack = false; //20% chance to get ammo back after detonation - } - } - } - } - } - } - }, - onEnd() { - if (this.isArmed) { - b.targetedNail(this.position, 15) - } - if (isAmmoBack) { //get ammo back from mod.isMineAmmoBack - for (i = 0, len = b.guns.length; i < len; i++) { //find which gun - if (b.guns[i].name === "mine") { - b.guns[i].ammo++ - game.updateGunHUD(); - break; - } - } - } - } - }); - bullet[bIndex].torque += bullet[bIndex].inertia * 0.0002 * (0.5 - Math.random()) - Matter.Body.setVelocity(bullet[bIndex], velocity); - World.add(engine.world, bullet[bIndex]); //add bullet to world - }, - spore(where, isFreeze = mod.isSporeFreeze) { //used with the mod upgrade in mob.death() - const bIndex = bullet.length; - const side = 4; - bullet[bIndex] = Bodies.polygon(where.x, where.y, 4, side, { - // density: 0.0015, //frictionAir: 0.01, - inertia: Infinity, - isFreeze: isFreeze, - restitution: 0.5, - angle: Math.random() * 2 * Math.PI, - friction: 0, - frictionAir: 0.025, - thrust: (mod.isFastSpores ? 0.001 : 0.0004) * (1 + 0.3 * (Math.random() - 0.5)), - dmg: mod.isMutualism ? 6 : 3, //2x bonus damage from mod.isMutualism - lookFrequency: 97 + Math.floor(117 * Math.random()), - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.mob | cat.mobBullet | cat.mobShield //no collide with body - }, - endCycle: game.cycle + Math.floor((540 + Math.floor(Math.random() * 360)) * mod.isBulletsLastLonger), - minDmgSpeed: 0, - playerOffPosition: { //used when following player to keep spores separate - x: 100 * (Math.random() - 0.5), - y: 100 * (Math.random() - 0.5) - }, - beforeDmg(who) { - this.endCycle = 0; //bullet ends cycle after doing damage - if (this.isFreeze) mobs.statusSlow(who, 60) - }, - onEnd() { - if (mod.isMutualism && this.isMutualismActive && !mod.isEnergyHealth) { - mech.health += 0.01 - if (mech.health > mech.maxHealth) mech.health = mech.maxHealth; - mech.displayHealth(); - } - }, - do() { - if (this.lockedOn && this.lockedOn.alive) { - this.force = Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), this.mass * this.thrust) - } else { - if (!(game.cycle % this.lookFrequency)) { //find mob targets - this.closestTarget = null; - this.lockedOn = null; - let closeDist = Infinity; - for (let i = 0, len = mob.length; i < len; ++i) { - if (mob[i].dropPowerUp && Matter.Query.ray(map, this.position, mob[i].position).length === 0) { - const targetVector = Vector.sub(this.position, mob[i].position) - const dist = Vector.magnitude(targetVector) * (Math.random() + 0.5); - if (dist < closeDist) { - this.closestTarget = mob[i].position; - closeDist = dist; - this.lockedOn = mob[i] - if (0.3 > Math.random()) break //doesn't always target the closest mob - } - } - } - } - if (mod.isSporeFollow && this.lockedOn === null) { //move towards player - //checking for null means that the spores don't go after the player until it has looked and not found a target - const dx = this.position.x - mech.pos.x; - const dy = this.position.y - mech.pos.y; - if (dx * dx + dy * dy > 10000) { - this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, Vector.add(this.playerOffPosition, this.position))), this.mass * this.thrust) - } - } else { - this.force.y += this.mass * 0.0001; //gravity - } - - } - - // if (!this.lockedOn && !(game.cycle % this.lookFrequency)) { //find mob targets - // this.closestTarget = null; - // this.lockedOn = null; - // let closeDist = Infinity; - // for (let i = 0, len = mob.length; i < len; ++i) { - // if (mob[i].dropPowerUp && Matter.Query.ray(map, this.position, mob[i].position).length === 0) { - // // Matter.Query.ray(body, this.position, mob[i].position).length === 0 - // const targetVector = Vector.sub(this.position, mob[i].position) - // const dist = Vector.magnitude(targetVector); - // if (dist < closeDist) { - // this.closestTarget = mob[i].position; - // closeDist = dist; - // this.lockedOn = mob[i] //Vector.normalise(targetVector); - // if (0.3 > Math.random()) break //doesn't always target the closest mob - // } - // } - // } - // } - // if (this.lockedOn && this.lockedOn.alive) { //accelerate towards mobs - // this.force = Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), this.mass * this.thrust) - // } else if (mod.isSporeFollow && this.lockedOn !== undefined) { //move towards player - // //checking for undefined means that the spores don't go after the player until it has looked and not found a target - // const dx = this.position.x - mech.pos.x; - // const dy = this.position.y - mech.pos.y; - // if (dx * dx + dy * dy > 10000) { - // this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, Vector.add(this.playerOffPosition, this.position))), this.mass * this.thrust) - // } - // // this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.thrust) - // } else { - // this.force.y += this.mass * 0.0001; //gravity - // } - }, - }); - const SPEED = 4 + 8 * Math.random(); - const ANGLE = 2 * Math.PI * Math.random() - Matter.Body.setVelocity(bullet[bIndex], { - x: SPEED * Math.cos(ANGLE), - y: SPEED * Math.sin(ANGLE) - }); - World.add(engine.world, bullet[bIndex]); //add bullet to world - - if (mod.isMutualism && mech.health > 0.02) { - mech.health -= 0.01 - mech.displayHealth(); - bullet[bIndex].isMutualismActive = true - } - }, - iceIX(speed = 0, spread = 2 * Math.PI) { - const me = bullet.length; - const THRUST = 0.004 - const dir = mech.angle + spread * (Math.random() - 0.5); - const RADIUS = 18 - bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 3, RADIUS, { - angle: dir - Math.PI, - inertia: Infinity, - friction: 0, - frictionAir: 0.10, - restitution: 0.3, - dmg: 0.15, //damage done in addition to the damage from momentum - lookFrequency: 10 + Math.floor(7 * Math.random()), - endCycle: game.cycle + 120 * mod.isBulletsLastLonger, //Math.floor((1200 + 420 * Math.random()) * mod.isBulletsLastLonger), - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield //self collide - }, - minDmgSpeed: 0, - lockedOn: null, - isFollowMouse: true, - beforeDmg(who) { - mobs.statusSlow(who, 60) - this.endCycle = game.cycle - if (mod.isHeavyWater) mobs.statusDoT(who, 0.15, 300) - if (mod.iceEnergy && !who.shield && !who.isShielded && who.dropPowerUp && who.alive) { - setTimeout(function () { - if (!who.alive) { - mech.energy += mod.iceEnergy * 0.66 * mech.maxEnergy - mech.addHealth(mod.iceEnergy * 0.04) - } - }, 10); - } - }, - onEnd() {}, - do() { - // this.force.y += this.mass * 0.0002; - //find mob targets - if (!(game.cycle % this.lookFrequency)) { - const scale = 1 - 0.09 / mod.isBulletsLastLonger //0.9 * mod.isBulletsLastLonger; - Matter.Body.scale(this, scale, scale); - this.lockedOn = null; - let closeDist = Infinity; - for (let i = 0, len = mob.length; i < len; ++i) { - if ( - mob[i].dropPowerUp && - Matter.Query.ray(map, this.position, mob[i].position).length === 0 && - Matter.Query.ray(body, this.position, mob[i].position).length === 0 - ) { - const TARGET_VECTOR = Vector.sub(this.position, mob[i].position) - const DIST = Vector.magnitude(TARGET_VECTOR); - if (DIST < closeDist) { - closeDist = DIST; - this.lockedOn = mob[i] - } - } - } - } - if (this.lockedOn) { //accelerate towards mobs - this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, this.lockedOn.position)), -this.mass * THRUST) - } else { - this.force = Vector.mult(Vector.normalise(this.velocity), this.mass * THRUST) - } - } - }) - - World.add(engine.world, bullet[me]); //add bullet to world - // Matter.Body.setAngularVelocity(bullet[me], 2 * (0.5 - Math.random())) //doesn't work due to high friction - Matter.Body.setVelocity(bullet[me], { - x: speed * Math.cos(dir), - y: speed * Math.sin(dir) - }); - // Matter.Body.setVelocity(bullet[me], { - // x: mech.Vx / 2 + speed * Math.cos(dir), - // y: mech.Vy / 2 + speed * Math.sin(dir) - // }); - }, - drone(speed = 1) { - const me = bullet.length; - const THRUST = mod.isFastDrones ? 0.0023 : 0.0015 - // const FRICTION = mod.isFastDrones ? 0.008 : 0.0005 - const dir = mech.angle + 0.4 * (Math.random() - 0.5); - const RADIUS = (4.5 + 3 * Math.random()) - bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle) + Math.random(), mech.pos.y + 30 * Math.sin(mech.angle) + Math.random(), 8, RADIUS, { - angle: dir, - inertia: Infinity, - friction: 0.05, - frictionAir: 0, - restitution: 1, - dmg: 0.24, //damage done in addition to the damage from momentum - lookFrequency: 80 + Math.floor(23 * Math.random()), - endCycle: game.cycle + Math.floor((1100 + 420 * Math.random()) * mod.isBulletsLastLonger), - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield //self collide - }, - minDmgSpeed: 0, - lockedOn: null, - isFollowMouse: true, - deathCycles: 110 + RADIUS * 5, - isImproved: false, - beforeDmg(who) { - //move away from target after hitting - const unit = Vector.mult(Vector.normalise(Vector.sub(this.position, who.position)), -20) - Matter.Body.setVelocity(this, { - x: unit.x, - y: unit.y - }); - - this.lockedOn = null - if (this.endCycle > game.cycle + this.deathCycles) { - this.endCycle -= 60 - if (game.cycle + this.deathCycles > this.endCycle) this.endCycle = game.cycle + this.deathCycles - } - }, - onEnd() {}, - do() { - if (game.cycle + this.deathCycles > this.endCycle) { //fall shrink and die - this.force.y += this.mass * 0.0012; - this.restitution = 0.2; - const scale = 0.99; - Matter.Body.scale(this, scale, scale); - } else { - this.force.y += this.mass * 0.0002; - //find mob targets - if (!(game.cycle % this.lookFrequency)) { - this.lockedOn = null; - let closeDist = Infinity; - for (let i = 0, len = mob.length; i < len; ++i) { - if ( - mob[i].dropPowerUp && - Matter.Query.ray(map, this.position, mob[i].position).length === 0 && - Matter.Query.ray(body, this.position, mob[i].position).length === 0 - ) { - const TARGET_VECTOR = Vector.sub(this.position, mob[i].position) - const DIST = Vector.magnitude(TARGET_VECTOR); - if (DIST < closeDist) { - closeDist = DIST; - this.lockedOn = mob[i] - } - } - } - if (!this.lockedOn && !mod.isArmorFromPowerUps && !this.isImproved) { //grab a power up - let closeDist = Infinity; - for (let i = 0, len = powerUp.length; i < len; ++i) { - if ( - (powerUp[i].name !== "heal" || mech.health < 0.9 * mech.maxHealth || mod.isDroneGrab) && - (powerUp[i].name !== "field" || !mod.isDeterminism) - ) { - //pick up nearby power ups - if (Vector.magnitudeSquared(Vector.sub(this.position, powerUp[i].position)) < 60000 && !game.isChoosing) { - powerUps.onPickUp(this.position); - powerUp[i].effect(); - Matter.World.remove(engine.world, powerUp[i]); - powerUp.splice(i, 1); - if (mod.isDroneGrab) { - this.isImproved = true; - const SCALE = 3 - Matter.Body.scale(this, SCALE, SCALE); - this.lookFrequency = 30; - this.endCycle += 2500 - this.frictionAir = 0 - } - break; - } - //look for power ups to lock onto - if ( - Matter.Query.ray(map, this.position, powerUp[i].position).length === 0 && - Matter.Query.ray(body, this.position, powerUp[i].position).length === 0 - ) { - const TARGET_VECTOR = Vector.sub(this.position, powerUp[i].position) - const DIST = Vector.magnitude(TARGET_VECTOR); - if (DIST < closeDist) { - closeDist = DIST; - this.lockedOn = powerUp[i] - } - } - } - } - } - } - if (this.lockedOn) { //accelerate towards mobs - this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, this.lockedOn.position)), -this.mass * THRUST) - } else { //accelerate towards mouse - this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, game.mouseInGame)), -this.mass * THRUST) - } - // speed cap instead of friction to give more agility - if (this.speed > 6) { - Matter.Body.setVelocity(this, { - x: this.velocity.x * 0.97, - y: this.velocity.y * 0.97 - }); - } - } - } - }) - World.add(engine.world, bullet[me]); //add bullet to world - Matter.Body.setVelocity(bullet[me], { - x: speed * Math.cos(dir), - y: speed * Math.sin(dir) - }); - }, - foam(position, velocity, radius) { - // radius *= Math.sqrt(mod.bulletSize) - const me = bullet.length; - bullet[me] = Bodies.polygon(position.x, position.y, 20, radius, { - // angle: 0, - density: 0.00005, // 0.001 is normal density - inertia: Infinity, - frictionAir: 0.003, - // friction: 0.2, - // restitution: 0.2, - dmg: mod.isFastFoam ? 0.02 : 0.0055, //damage done in addition to the damage from momentum - scale: 1 - 0.005 / mod.isBulletsLastLonger * (mod.isFastFoam ? 1.6 : 1), - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.mob | cat.mobBullet // cat.map | cat.body | cat.mob | cat.mobShield - }, - minDmgSpeed: 0, - endCycle: Infinity, - count: 0, - radius: radius, - target: null, - targetVertex: null, - targetRelativePosition: null, - beforeDmg(who) { - if (!this.target && who.alive) { - this.target = who; - if (who.radius < 20) { - this.targetRelativePosition = { - x: 0, - y: 0 - } //find relative position vector for zero mob rotation - } else if (Matter.Query.collides(this, [who]).length > 0) { - const normal = Matter.Query.collides(this, [who])[0].normal - this.targetRelativePosition = Vector.rotate(Vector.sub(Vector.sub(this.position, who.position), Vector.mult(normal, -this.radius)), -who.angle) //find relative position vector for zero mob rotation - } else { - this.targetRelativePosition = Vector.rotate(Vector.sub(this.position, who.position), -who.angle) //find relative position vector for zero mob rotation - } - this.collisionFilter.category = cat.body; - this.collisionFilter.mask = null; - - let bestVertexDistance = Infinity - let bestVertex = null - for (let i = 0; i < this.target.vertices.length; i++) { - const dist = Vector.magnitude(Vector.sub(this.position, this.target.vertices[i])); - if (dist < bestVertexDistance) { - bestVertex = i - bestVertexDistance = dist - } - } - this.targetVertex = bestVertex - } - }, - onEnd() {}, - do() { - if (!mech.isBodiesAsleep) { //if time dilation isn't active - if (this.count < 20) { - this.count++ - //grow - const SCALE = 1.06 - Matter.Body.scale(this, SCALE, SCALE); - this.radius *= SCALE; - } else { - //shrink - Matter.Body.scale(this, this.scale, this.scale); - this.radius *= this.scale; - if (this.radius < 8) this.endCycle = 0; - } - - if (this.target && this.target.alive) { //if stuck to a target - const rotate = Vector.rotate(this.targetRelativePosition, this.target.angle) //add in the mob's new angle to the relative position vector - if (this.target.isVerticesChange) { - Matter.Body.setPosition(this, this.target.vertices[this.targetVertex]) - } else { - Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.target.velocity), this.target.position)) - } - Matter.Body.setVelocity(this.target, Vector.mult(this.target.velocity, 0.9)) - Matter.Body.setAngularVelocity(this.target, this.target.angularVelocity * 0.9); - - // Matter.Body.setAngularVelocity(this.target, this.target.angularVelocity * 0.9) - if (this.target.isShielded) { - this.target.damage(b.dmgScale * this.dmg, true); //shield damage bypass - //shrink if mob is shielded - const SCALE = 1 - 0.018 / mod.isBulletsLastLonger - Matter.Body.scale(this, SCALE, SCALE); - this.radius *= SCALE; - } else { - this.target.damage(b.dmgScale * this.dmg); - } - } else if (this.target !== null) { //look for a new target - this.target = null - this.collisionFilter.category = cat.bullet; - this.collisionFilter.mask = cat.mob //| cat.mobShield //cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield - if (mod.isFoamGrowOnDeath && bullet.length < 300) { - let targets = [] - for (let i = 0, len = mob.length; i < len; i++) { - const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); - if (dist < 1000000) { - targets.push(mob[i]) - } - } - const radius = Math.min(this.radius * 0.5, 10) - for (let i = 0; i < 2; i++) { - if (targets.length - i > 0) { - const index = Math.floor(Math.random() * targets.length) - const speed = 10 + 10 * Math.random() - const velocity = Vector.mult(Vector.normalise(Vector.sub(targets[index].position, this.position)), speed) - b.foam(this.position, Vector.rotate(velocity, 0.5 * (Math.random() - 0.5)), radius) + mod.isCrouchAmmo++ //makes the no ammo toggle off and on } else { - b.foam(this.position, Vector.rotate({ - x: 15 + 10 * Math.random(), - y: 0 - }, 2 * Math.PI * Math.random()), radius) + b.guns[b.activeGun].ammo--; + game.updateGunHUD(); } - } + } else { + if (mod.isAmmoFromHealth) { + if (mech.health > 2 * mod.isAmmoFromHealth * mech.maxHealth) { + mech.damage(mod.isAmmoFromHealth * mech.maxHealth / mech.harmReduction()); + powerUps.spawn(mech.pos.x, mech.pos.y, "ammo"); + } else { + game.replaceTextLog = true; + game.makeTextLog("not enough health for catabolism to produce ammo", 120); + } + } else { + game.replaceTextLog = true; + game.makeTextLog("
NO AMMO

Q, E, and mouse wheel change weapons

", 200); + } + mech.fireCDcycle = mech.cycle + 30; //fire cooldown } - } else if (Matter.Query.point(map, this.position).length > 0) { //slow when touching map or blocks - const slow = 0.85 - Matter.Body.setVelocity(this, { - x: this.velocity.x * slow, - y: this.velocity.y * slow - }); - const SCALE = 0.96 - Matter.Body.scale(this, SCALE, SCALE); - this.radius *= SCALE; - // } else if (Matter.Query.collides(this, body).length > 0) { - } else if (Matter.Query.point(body, this.position).length > 0) { - const slow = 0.9 - Matter.Body.setVelocity(this, { - x: this.velocity.x * slow, - y: this.velocity.y * slow - }); - const SCALE = 0.96 - Matter.Body.scale(this, SCALE, SCALE); - this.radius *= SCALE; - } else { - this.force.y += this.mass * 0.00008; //gravity - } - } - } - }); - World.add(engine.world, bullet[me]); //add bullet to world - Matter.Body.setVelocity(bullet[me], velocity); - }, - targetedNail(position, num = 1, speed = 50 + 10 * Math.random(), range = 1200) { - const targets = [] //target nearby mobs - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].dropPowerUp) { - const dist = Vector.magnitude(Vector.sub(position, mob[i].position)); - if (dist < range && - Matter.Query.ray(map, position, mob[i].position).length === 0 && - Matter.Query.ray(body, position, mob[i].position).length === 0) { - targets.push(Vector.add(mob[i].position, Vector.mult(mob[i].velocity, dist / 60))) //predict where the mob will be in a few cycles - } - } - } - for (let i = 0; i < num; i++) { - if (targets.length > 0) { // aim near a random target in array - const index = Math.floor(Math.random() * targets.length) - const SPREAD = 150 / targets.length - const WHERE = { - x: targets[index].x + SPREAD * (Math.random() - 0.5), - y: targets[index].y + SPREAD * (Math.random() - 0.5) - } - b.nail(position, Vector.mult(Vector.normalise(Vector.sub(WHERE, position)), speed), 1.1) - } else { // aim in random direction - const ANGLE = 2 * Math.PI * Math.random() - b.nail(position, { - x: speed * Math.cos(ANGLE), - y: speed * Math.sin(ANGLE) - }) - } - } - }, - nail(pos, velocity, dmg = 0) { - const me = bullet.length; - bullet[me] = Bodies.rectangle(pos.x, pos.y, 25 * mod.biggerNails, 2 * mod.biggerNails, b.fireAttributes(Math.atan2(velocity.y, velocity.x))); - Matter.Body.setVelocity(bullet[me], velocity); - World.add(engine.world, bullet[me]); //add bullet to world - bullet[me].endCycle = game.cycle + 60 + 18 * Math.random(); - bullet[me].dmg = dmg - bullet[me].beforeDmg = function (who) { //beforeDmg is rewritten with ice crystal mod - if (mod.isNailPoison) mobs.statusDoT(who, dmg * 0.22, 120) // one tick every 30 cycles - if (mod.isNailCrit && !who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.99) this.dmg *= 5 //crit if hit near center - }; - bullet[me].do = function () {}; - }, - // ************************************************************************************************** - // ************************************************************************************************** - // ******************************** Bots ********************************************* - // ************************************************************************************************** - // ************************************************************************************************** - respawnBots() { - for (let i = 0; i < mod.laserBotCount; i++) b.laserBot() - for (let i = 0; i < mod.nailBotCount; i++) b.nailBot() - for (let i = 0; i < mod.foamBotCount; i++) b.foamBot() - for (let i = 0; i < mod.boomBotCount; i++) b.boomBot() - for (let i = 0; i < mod.plasmaBotCount; i++) b.plasmaBot() - for (let i = 0; i < mod.orbitBotCount; i++) b.orbitBot() - if (mod.isIntangible && mech.isCloak) { - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType) bullet[i].collisionFilter.mask = cat.map | cat.bullet | cat.mobBullet | cat.mobShield - } - } - }, - randomBot(where = mech.pos, isKeep = true) { - if (Math.random() < 0.2) { - b.orbitBot(); - if (isKeep) mod.orbitBotCount++; - } else if (Math.random() < 0.25) { - b.nailBot(where) - if (isKeep) mod.nailBotCount++; - } else if (Math.random() < 0.33) { - b.laserBot(where) - if (isKeep) mod.laserBotCount++; - } else if (Math.random() < 0.5) { - b.foamBot(where) - if (isKeep) mod.foamBotCount++; - } else { - b.boomBot(where) - if (isKeep) mod.boomBotCount++; - } - }, - nailBot(position = mech.pos) { - const me = bullet.length; - const dir = mech.angle; - const RADIUS = (12 + 4 * Math.random()) - bullet[me] = Bodies.polygon(position.x, position.y, 4, RADIUS, { - isUpgraded: mod.isNailBotUpgrade, - botType: "nail", - angle: dir, - friction: 0, - frictionStatic: 0, - frictionAir: 0.05, - restitution: 0.6 * (1 + 0.5 * Math.random()), - dmg: 0, // 0.14 //damage done in addition to the damage from momentum - minDmgSpeed: 2, - // lookFrequency: 56 + Math.floor(17 * Math.random()) - isUpgraded * 20, - lastLookCycle: game.cycle + 60 * Math.random(), - acceleration: 0.005 * (1 + 0.5 * Math.random()), - range: 70 * (1 + 0.3 * Math.random()), - endCycle: Infinity, - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield - }, - lockedOn: null, - beforeDmg() { - this.lockedOn = null - }, - onEnd() {}, - do() { - if (this.lastLookCycle < game.cycle && !mech.isCloak) { - this.lastLookCycle = game.cycle + 80 - this.isUpgraded * 65 - let target - for (let i = 0, len = mob.length; i < len; i++) { - const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); - if (dist < 3000000 && //1400*1400 - Matter.Query.ray(map, this.position, mob[i].position).length === 0 && - Matter.Query.ray(body, this.position, mob[i].position).length === 0) { - target = Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist) / 60)) - const SPEED = 50 - b.nail(this.position, Vector.mult(Vector.normalise(Vector.sub(target, this.position)), SPEED), 0.4) - break; + if (mech.holdingTarget) { + mech.drop(); } - } } - const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) - if (distanceToPlayer > this.range) { //if far away move towards player - this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) - } else { //close to player - Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity + }, + removeAllGuns() { + b.inventory = []; //removes guns and ammo + for (let i = 0, len = b.guns.length; i < len; ++i) { + b.guns[i].count = 0; + b.guns[i].have = false; + if (b.guns[i].ammo != Infinity) b.guns[i].ammo = 0; } - } - }) - World.add(engine.world, bullet[me]); //add bullet to world - }, - foamBot(position = mech.pos) { - const me = bullet.length; - const dir = mech.angle; - const RADIUS = (10 + 5 * Math.random()) - bullet[me] = Bodies.polygon(position.x, position.y, 6, RADIUS, { - isUpgraded: mod.isFoamBotUpgrade, - botType: "foam", - angle: dir, - friction: 0, - frictionStatic: 0, - frictionAir: 0.05, - restitution: 0.6 * (1 + 0.5 * Math.random()), - dmg: 0, // 0.14 //damage done in addition to the damage from momentum - minDmgSpeed: 2, - lookFrequency: 60 + Math.floor(17 * Math.random()) - 10 * mod.isFoamBotUpgrade, - cd: 0, - delay: 100, - acceleration: 0.005 * (1 + 0.5 * Math.random()), - range: 70 * (1 + 0.3 * Math.random()), - endCycle: Infinity, - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield - }, - lockedOn: null, - beforeDmg() { - this.lockedOn = null - }, - onEnd() {}, - do() { - if (this.cd < game.cycle && !(game.cycle % this.lookFrequency) && !mech.isCloak) { - let target - for (let i = 0, len = mob.length; i < len; i++) { - const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); - if (dist < 1000000 && Matter.Query.ray(map, this.position, mob[i].position).length === 0) { - this.cd = game.cycle + this.delay; - target = Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist) / 60)) - const radius = 6 + 7 * Math.random() - const SPEED = 29 - radius * 0.5; //(mech.crouch ? 32 : 20) - radius * 0.7; - const velocity = Vector.mult(Vector.normalise(Vector.sub(target, this.position)), SPEED) - b.foam(this.position, velocity, radius + 14 * this.isUpgraded) - break; + b.activeGun = null; + }, + bulletRemove() { //run in main loop + //remove bullet if at end cycle for that bullet + let i = bullet.length; + while (i--) { + if (bullet[i].endCycle < game.cycle) { + bullet[i].onEnd(i); //some bullets do stuff on end + if (bullet[i]) { + Matter.World.remove(engine.world, bullet[i]); + bullet.splice(i, 1); + } else { + break; //if bullet[i] doesn't exist don't complete the for loop, because the game probably reset + } } - } } - const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) - if (distanceToPlayer > this.range) { //if far away move towards player - this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) - } else { //close to player - Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity + }, + bulletDraw() { + ctx.beginPath(); + for (let i = 0, len = bullet.length; i < len; i++) { + let vertices = bullet[i].vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); } - } - }) - World.add(engine.world, bullet[me]); //add bullet to world - }, - laserBot(position = mech.pos) { - const me = bullet.length; - const dir = mech.angle; - const RADIUS = (14 + 6 * Math.random()) - bullet[me] = Bodies.polygon(position.x, position.y, 3, RADIUS, { - isUpgraded: mod.isLaserBotUpgrade, - botType: "laser", - angle: dir, - friction: 0, - frictionStatic: 0, - frictionAir: 0.008 * (1 + 0.3 * Math.random()), - restitution: 0.5 * (1 + 0.5 * Math.random()), - dmg: 0, // 0.14 //damage done in addition to the damage from momentum - minDmgSpeed: 2, - lookFrequency: 40 + Math.floor(7 * Math.random()), - acceleration: 0.0015 * (1 + 0.3 * Math.random()), - range: 700 * (1 + 0.1 * Math.random()) + 250 * mod.isLaserBotUpgrade, - followRange: 150 + Math.floor(30 * Math.random()), - offPlayer: { - x: 0, - y: 0, - }, - endCycle: Infinity, - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield - }, - lockedOn: null, - beforeDmg() { - this.lockedOn = null - }, - onEnd() {}, - do() { - const playerPos = Vector.add(Vector.add(this.offPlayer, mech.pos), Vector.mult(player.velocity, 20)) //also include an offset unique to this bot to keep many bots spread out - const farAway = Math.max(0, (Vector.magnitude(Vector.sub(this.position, playerPos))) / this.followRange) //linear bounding well - const mag = Math.min(farAway, 4) * this.mass * this.acceleration - this.force = Vector.mult(Vector.normalise(Vector.sub(playerPos, this.position)), mag) - //manual friction to not lose rotational velocity - Matter.Body.setVelocity(this, { - x: this.velocity.x * 0.95, - y: this.velocity.y * 0.95 + ctx.fillStyle = "#000"; + ctx.fill(); + }, + bulletDo() { + for (let i = 0, len = bullet.length; i < len; i++) { + bullet[i].do(); + } + }, + fireProps(cd, speed, dir, me) { + mech.fireCDcycle = mech.cycle + Math.floor(cd * b.fireCD); // cool down + Matter.Body.setVelocity(bullet[me], { + x: mech.Vx / 2 + speed * Math.cos(dir), + y: mech.Vy / 2 + speed * Math.sin(dir) }); - //find targets - if (!(game.cycle % this.lookFrequency)) { - this.lockedOn = null; - if (!mech.isCloak) { - let closeDist = this.range; - for (let i = 0, len = mob.length; i < len; ++i) { - const DIST = Vector.magnitude(Vector.sub(this.vertices[0], mob[i].position)); - if (DIST - mob[i].radius < closeDist && - !mob[i].isShielded && - Matter.Query.ray(map, this.vertices[0], mob[i].position).length === 0 && - Matter.Query.ray(body, this.vertices[0], mob[i].position).length === 0) { - closeDist = DIST; - this.lockedOn = mob[i] - } - } - } - //randomize position relative to player - if (Math.random() < 0.15) { - this.offPlayer = { - x: 120 * (Math.random() - 0.5), - y: 120 * (Math.random() - 0.5) - 20, - } - } + World.add(engine.world, bullet[me]); //add bullet to world + }, + fireCD: 1, + setFireCD() { + b.fireCD = mod.fireRate * mod.slowFire * mod.rerollHaste * mod.aimDamage / mod.fastTime + }, + fireAttributes(dir, rotate = true) { + if (rotate) { + return { + // density: 0.0015, //frictionAir: 0.01, //restitution: 0, + angle: dir, + friction: 0.5, + frictionAir: 0, + dmg: 0, //damage done in addition to the damage from momentum + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield + }, + minDmgSpeed: 10, + beforeDmg() {}, //this.endCycle = 0 //triggers despawn + onEnd() {} + }; + } else { + return { + // density: 0.0015, //frictionAir: 0.01, //restitution: 0, + inertia: Infinity, //prevents rotation + angle: dir, + friction: 0.5, + frictionAir: 0, + dmg: 0, //damage done in addition to the damage from momentum + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield + }, + minDmgSpeed: 10, + beforeDmg() {}, //this.endCycle = 0 //triggers despawn + onEnd() {} + }; } - //hit target with laser - if (this.lockedOn && this.lockedOn.alive && mech.energy > 0.15) { - mech.energy -= 0.0012 * mod.isLaserDiode - // const sub = Vector.sub(this.lockedOn.position, this.vertices[0]) - // const angle = Math.atan2(sub.y, sub.x); - b.laser(this.vertices[0], this.lockedOn.position, b.dmgScale * (0.06 + 0.1 * this.isUpgraded)) + }, + muzzleFlash(radius = 10) { + ctx.fillStyle = "#fb0"; + ctx.beginPath(); + ctx.arc(mech.pos.x + 35 * Math.cos(mech.angle), mech.pos.y + 35 * Math.sin(mech.angle), radius, 0, 2 * Math.PI); + ctx.fill(); + }, + removeConsBB(me) { + for (let i = 0, len = consBB.length; i < len; ++i) { + if (consBB[i].bodyA === me) { + consBB[i].bodyA = consBB[i].bodyB; + consBB.splice(i, 1); + break; + } else if (consBB[i].bodyB === me) { + consBB[i].bodyB = consBB[i].bodyA; + consBB.splice(i, 1); + break; + } + } + }, + onCollision(event) { + 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) + } - // //make sure you can still see vertex - // const DIST = Vector.magnitude(Vector.sub(this.vertices[0], this.lockedOn.position)); - // if (DIST - this.lockedOn.radius < this.range + 150 && - // Matter.Query.ray(map, this.vertices[0], this.lockedOn.position).length === 0 && - // Matter.Query.ray(body, this.vertices[0], this.lockedOn.position).length === 0) { - // //move towards the target - // this.force = Vector.add(this.force, Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), 0.0013)) - // //find the closest vertex - // let bestVertexDistance = Infinity - // let bestVertex = null - // for (let i = 0; i < this.lockedOn.vertices.length; i++) { - // const dist = Vector.magnitude(Vector.sub(this.vertices[0], this.lockedOn.vertices[i])); - // if (dist < bestVertexDistance) { - // bestVertex = i - // bestVertexDistance = dist - // } - // } - // const dmg = b.dmgScale * (0.06 + 0.08 * this.isUpgraded); - // this.lockedOn.damage(dmg); - // this.lockedOn.locatePlayer(); + function collideBulletStatic(obj) { + if (obj.onWallHit) obj.onWallHit(); + } + } + }, + explosion(where, radius) { // typically explode is used for some bullets with .onEnd + radius *= mod.explosiveRadius + let dist, sub, knock; + let dmg = radius * 0.013; + if (mod.isExplosionHarm) radius *= 1.8 // 1/sqrt(2) radius -> area + if (mod.isSmallExplosion) { + radius *= 0.8 + dmg *= 1.6 + } - // ctx.beginPath(); //draw laser - // ctx.moveTo(this.vertices[0].x, this.vertices[0].y); - // ctx.lineTo(this.lockedOn.vertices[bestVertex].x, this.lockedOn.vertices[bestVertex].y); - // ctx.strokeStyle = "#f00"; - // ctx.lineWidth = "2" - // ctx.lineDashOffset = 300 * Math.random() - // ctx.setLineDash([50 + 100 * Math.random(), 100 * Math.random()]); - // ctx.stroke(); - // ctx.setLineDash([0, 0]); - // ctx.beginPath(); - // ctx.arc(this.lockedOn.vertices[bestVertex].x, this.lockedOn.vertices[bestVertex].y, Math.sqrt(dmg) * 100, 0, 2 * Math.PI); - // ctx.fillStyle = "#f00"; - // ctx.fill(); - // } - } - } - }) - World.add(engine.world, bullet[me]); //add bullet to world - }, - boomBot(position = mech.pos) { - const me = bullet.length; - const dir = mech.angle; - const RADIUS = (7 + 2 * Math.random()) - bullet[me] = Bodies.polygon(position.x, position.y, 4, RADIUS, { - isUpgraded: mod.isBoomBotUpgrade, - botType: "boom", - angle: dir, - friction: 0, - frictionStatic: 0, - frictionAir: 0.05, - restitution: 1, - dmg: 0, - minDmgSpeed: 0, - lookFrequency: 43 + Math.floor(7 * Math.random()), - acceleration: 0.005 * (1 + 0.5 * Math.random()), - range: 500 * (1 + 0.1 * Math.random()) + 250 * mod.isBoomBotUpgrade, - endCycle: Infinity, - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield - }, - lockedOn: null, - explode: 0, - beforeDmg() { - if (this.lockedOn) { - const explosionRadius = Math.min(170 + 170 * this.isUpgraded, Vector.magnitude(Vector.sub(this.position, mech.pos)) - 30) - if (explosionRadius > 60) { - this.explode = explosionRadius - // - //push away from player, because normal explosion knock doesn't do much - // const sub = Vector.sub(this.lockedOn.position, mech.pos) - // mag = Math.min(35, 20 / Math.sqrt(this.lockedOn.mass)) - // Matter.Body.setVelocity(this.lockedOn, Vector.mult(Vector.normalise(sub), mag)) - } - this.lockedOn = null //lose target so bot returns to player - } - }, - onEnd() {}, - do() { - if (this.explode) { - b.explosion(this.position, this.explode); //makes bullet do explosive damage at end - this.explode = 0; - } - const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) - if (distanceToPlayer > 100) { //if far away move towards player - this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) - } else if (distanceToPlayer < 250) { //close to player - Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity - //find targets - if (!(game.cycle % this.lookFrequency) && !mech.isCloak) { - this.lockedOn = null; - let closeDist = this.range; - for (let i = 0, len = mob.length; i < len; ++i) { - const DIST = Vector.magnitude(Vector.sub(this.position, mob[i].position)) - mob[i].radius; - if (DIST < closeDist && mob[i].dropPowerUp && - Matter.Query.ray(map, this.position, mob[i].position).length === 0 && - Matter.Query.ray(body, this.position, mob[i].position).length === 0) { - closeDist = DIST; - this.lockedOn = mob[i] - } + game.drawList.push({ //add dmg to draw queue + x: where.x, + y: where.y, + radius: radius, + color: "rgba(255,25,0,0.6)", + time: game.drawTime + }); + + const alertRange = 100 + radius * 2; //alert range + game.drawList.push({ //add alert to draw queue + x: where.x, + y: where.y, + radius: alertRange, + color: "rgba(100,20,0,0.03)", + time: game.drawTime + }); + + //player damage and knock back + sub = Vector.sub(where, player.position); + dist = Vector.magnitude(sub); + + if (dist < radius) { + + if (mod.isImmuneExplosion) { + const mitigate = Math.min(1, Math.max(1 - mech.energy * 0.7, 0)) + mech.damage(mitigate * radius * (mod.isExplosionHarm ? 0.0004 : 0.0001)); + } else { + mech.damage(radius * (mod.isExplosionHarm ? 0.0004 : 0.0001)); } - } + + // if (!(mod.isImmuneExplosion && mech.energy > 0.97)) { + // if (mod.isExplosionHarm) { + // mech.damage(radius * 0.0004); //300% more player damage from explosions + // } else { + // mech.damage(radius * 0.0001); //normal player damage from explosions + // } + // mech.drop(); + // } + knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * player.mass * 0.015); + player.force.x += knock.x; + player.force.y += knock.y; + } else if (dist < alertRange) { + knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * player.mass * 0.008); + player.force.x += knock.x; + player.force.y += knock.y; } - //punch target - if (this.lockedOn && this.lockedOn.alive && !mech.isCloak) { - const DIST = Vector.magnitude(Vector.sub(this.vertices[0], this.lockedOn.position)); - if (DIST - this.lockedOn.radius < this.range && - Matter.Query.ray(map, this.position, this.lockedOn.position).length === 0) { - //move towards the target - this.force = Vector.add(this.force, Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), 0.012 * this.mass)) - } - } - } - }) - World.add(engine.world, bullet[me]); //add bullet to world - }, - plasmaBot(position = mech.pos) { - const me = bullet.length; - const dir = mech.angle; - const RADIUS = 21 - bullet[me] = Bodies.polygon(position.x, position.y, 5, RADIUS, { - botType: "plasma", - angle: dir, - friction: 0, - frictionStatic: 0, - frictionAir: 0.05, - restitution: 1, - dmg: 0, // 0.14 //damage done in addition to the damage from momentum - minDmgSpeed: 2, - lookFrequency: 25, - cd: 0, - acceleration: 0.009, - endCycle: Infinity, - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield - }, - lockedOn: null, - beforeDmg() { - this.lockedOn = null - }, - onEnd() {}, - do() { - const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) - if (distanceToPlayer > 150) { //if far away move towards player - this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) - } - Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity - //find closest - if (!(game.cycle % this.lookFrequency)) { - this.lockedOn = null; - if (!mech.isCloak) { - let closeDist = mod.isPlasmaRange * 1000; - for (let i = 0, len = mob.length; i < len; ++i) { - const DIST = Vector.magnitude(Vector.sub(this.position, mob[i].position)) - mob[i].radius; - if (DIST < closeDist && - Matter.Query.ray(map, this.position, mob[i].position).length === 0 && - Matter.Query.ray(body, this.position, mob[i].position).length === 0) { - closeDist = DIST; - this.lockedOn = mob[i] - } + + //body knock backs + for (let i = 0, len = body.length; i < len; ++i) { + sub = Vector.sub(where, body[i].position); + dist = Vector.magnitude(sub); + if (dist < radius) { + knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) * 0.022); + body[i].force.x += knock.x; + body[i].force.y += knock.y; + } else if (dist < alertRange) { + knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) * 0.013); + body[i].force.x += knock.x; + body[i].force.y += knock.y; } - } } - //fire plasma at target - if (this.lockedOn && this.lockedOn.alive && mech.fieldCDcycle < mech.cycle) { - const sub = Vector.sub(this.lockedOn.position, this.position) - const DIST = Vector.magnitude(sub); - const unit = Vector.normalise(sub) - const DRAIN = 0.0012 - if (DIST < mod.isPlasmaRange * 450 && mech.energy > DRAIN) { - mech.energy -= DRAIN; - if (mech.energy < 0) { - mech.fieldCDcycle = mech.cycle + 120; - mech.energy = 0; + + //power up knock backs + for (let i = 0, len = powerUp.length; i < len; ++i) { + sub = Vector.sub(where, powerUp[i].position); + dist = Vector.magnitude(sub); + if (dist < radius) { + knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) * 0.015); + powerUp[i].force.x += knock.x; + powerUp[i].force.y += knock.y; + } else if (dist < alertRange) { + knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) * 0.01); + powerUp[i].force.x += knock.x; + powerUp[i].force.y += knock.y; } - //calculate laser collision - let best; - let range = mod.isPlasmaRange * (120 + 300 * Math.sqrt(Math.random())) - const path = [{ - x: this.position.x, - y: this.position.y - }, - { - x: this.position.x + range * unit.x, - y: this.position.y + range * unit.y - } - ]; - const vertexCollision = function (v1, v1End, domain) { - for (let i = 0; i < domain.length; ++i) { + } + + //mob damage and knock back with alert + let damageScale = 1.5; // reduce dmg for each new target to limit total AOE damage + for (let i = 0, len = mob.length; i < len; ++i) { + if (mob[i].alive && !mob[i].isShielded) { + sub = Vector.sub(where, mob[i].position); + dist = Vector.magnitude(sub) - mob[i].radius; + if (dist < radius) { + if (mob[i].shield) dmg *= 2.5 //balancing explosion dmg to shields + if (Matter.Query.ray(map, mob[i].position, where).length > 0) dmg *= 0.5 //reduce damage if a wall is in the way + mob[i].damage(dmg * damageScale * b.dmgScale); + mob[i].locatePlayer(); + knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) * 0.01); + mob[i].force.x += knock.x; + mob[i].force.y += knock.y; + radius *= 0.95 //reduced range for each additional explosion target + damageScale *= 0.87 //reduced damage for each additional explosion target + } else if (!mob[i].seePlayer.recall && dist < alertRange) { + mob[i].locatePlayer(); + knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) * 0.006); + mob[i].force.x += knock.x; + mob[i].force.y += knock.y; + } + } + } + }, + missile(where, angle, speed, size = 1, spawn = 0) { + const me = bullet.length; + bullet[me] = Bodies.rectangle(where.x, where.y, 30 * size, 4 * size, { + angle: angle, + friction: 0.5, + frictionAir: 0.045, + dmg: 0, //damage done in addition to the damage from momentum + classType: "bullet", + endCycle: game.cycle + Math.floor((230 + 40 * Math.random()) * mod.isBulletsLastLonger), + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield + }, + minDmgSpeed: 10, + lookFrequency: Math.floor(15 + Math.random() * 3), + explodeRad: 170 + 60 * Math.random(), + beforeDmg() { + this.tryToLockOn(); + this.endCycle = 0; //bullet ends cycle after doing damage // also triggers explosion + }, //this.endCycle = 0 //triggers despawn + onEnd() { + b.explosion(this.position, this.explodeRad * size); //makes bullet do explosive damage at end + if (spawn) { + for (let i = 0; i < mod.recursiveMissiles; i++) { + if (0.2 - 0.02 * i > Math.random()) { + b.missile(this.position, this.angle + Math.PI + 0.5 * (Math.random() - 0.5), 0, 0.33 + size, mod.recursiveMissiles) + break; + } + } + } + }, + lockedOn: null, + tryToLockOn() { + let closeDist = Infinity; + //look for closest target to where the missile will be in 30 cycles + const futurePos = Vector.add(this.position, Vector.mult(this.velocity, 50)) + this.lockedOn = null; + // const futurePos = this.lockedOn ? :Vector.add(this.position, Vector.mult(this.velocity, 50)) + for (let i = 0, len = mob.length; i < len; ++i) { + if ( + mob[i].alive && mob[i].dropPowerUp && + Matter.Query.ray(map, this.position, mob[i].position).length === 0 && + Matter.Query.ray(body, this.position, mob[i].position).length === 0 + ) { + const futureDist = Vector.magnitude(Vector.sub(futurePos, mob[i].position)); + if (futureDist < closeDist) { + closeDist = futureDist; + this.lockedOn = mob[i]; + // this.frictionAir = 0.04; //extra friction once a target it locked + } + if (Vector.magnitude(Vector.sub(this.position, mob[i].position) < this.explodeRad)) { + this.endCycle = 0; //bullet ends cycle after doing damage //also triggers explosion + mob[i].lockedOn.damage(b.dmgScale * 2 * size); //does extra damage to target + } + } + } + //explode when bullet is close enough to target + if (this.lockedOn && Vector.magnitude(Vector.sub(this.position, this.lockedOn.position)) < this.explodeRad) { + this.endCycle = 0; //bullet ends cycle after doing damage //also triggers explosion + this.lockedOn.damage(b.dmgScale * 4 * size); //does extra damage to target + } + }, + do() { + if (!mech.isBodiesAsleep) { + if (!(mech.cycle % this.lookFrequency)) this.tryToLockOn(); + if (this.lockedOn) { //rotate missile towards the target + const face = { + x: Math.cos(this.angle), + y: Math.sin(this.angle) + }; + const target = Vector.normalise(Vector.sub(this.position, this.lockedOn.position)); + if (Vector.dot(target, face) > -0.98) { + if (Vector.cross(target, face) > 0) { + Matter.Body.rotate(this, 0.1); + } else { + Matter.Body.rotate(this, -0.1); + } + this.frictionAir = 0.06; //extra friction if turning + } else { + this.frictionAir = 0.025; //low friction if not turning + } + } + //accelerate in direction bullet is facing + const dir = this.angle; // + (Math.random() - 0.5); + this.force.x += Math.cos(dir) * thrust; + this.force.y += Math.sin(dir) * thrust; + + ctx.beginPath(); //draw rocket + ctx.arc(this.position.x - Math.cos(this.angle) * (25 * size - 3) + (Math.random() - 0.5) * 4, + this.position.y - Math.sin(this.angle) * (25 * size - 3) + (Math.random() - 0.5) * 4, + 11 * size, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(255,155,0,0.5)"; + ctx.fill(); + } else { + //draw rocket with time stop + ctx.beginPath(); + ctx.arc(this.position.x - Math.cos(this.angle) * (30 * size - 3) + (Math.random() - 0.5) * 4, + this.position.y - Math.sin(this.angle) * (30 * size - 3) + (Math.random() - 0.5) * 4, + 2 + 9 * size, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(255,155,0,0.5)"; + ctx.fill(); + } + }, + }); + const thrust = 0.0044 * bullet[me].mass; + Matter.Body.setVelocity(bullet[me], { + x: mech.Vx / 2 + speed * Math.cos(angle), + y: mech.Vy / 2 + speed * Math.sin(angle) + }); + World.add(engine.world, bullet[me]); //add bullet to world + }, + laser(where = { + x: mech.pos.x + 20 * Math.cos(mech.angle), + y: mech.pos.y + 20 * Math.sin(mech.angle) + }, whereEnd = { + x: where.x + 3000 * Math.cos(mech.angle), + y: where.y + 3000 * Math.sin(mech.angle) + }, dmg = mod.laserDamage, reflections = mod.laserReflections, isThickBeam = false) { + const reflectivity = 1 - 1 / (reflections * 1.5) + let damage = b.dmgScale * dmg + let best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const color = "#f00"; + const path = [{ + x: where.x, + y: where.y + }, + { + x: whereEnd.x, + y: whereEnd.y + } + ]; + 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) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { const dx = v1.x - results.x; const dy = v1.y - results.y; const dist2 = dx * dx + dy * dy; if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[j], - v2: vertices[j + 1] - }; - } - } - } - results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[0], - v2: vertices[len] - }; - } - } - } - }; - //check for collisions - best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - vertexCollision(path[0], path[1], mob); - vertexCollision(path[0], path[1], map); - vertexCollision(path[0], path[1], body); - if (best.dist2 != Infinity) { //if hitting something - path[path.length - 1] = { - x: best.x, - y: best.y - }; - if (best.who.alive) { - const dmg = 0.8 * b.dmgScale; //********** SCALE DAMAGE HERE ********************* - best.who.damage(dmg); - best.who.locatePlayer(); - //push mobs away - const force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, path[1])), -0.01 * Math.min(5, best.who.mass)) - Matter.Body.applyForce(best.who, path[1], force) - Matter.Body.setVelocity(best.who, { //friction - x: best.who.velocity.x * 0.7, - y: best.who.velocity.y * 0.7 - }); - //draw mob damage circle - game.drawList.push({ - x: path[1].x, - y: path[1].y, - radius: Math.sqrt(dmg) * 50, - color: "rgba(255,0,255,0.2)", - time: game.drawTime * 4 - }); - } else if (!best.who.isStatic) { - //push blocks away - const force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, path[1])), -0.007 * Math.sqrt(Math.sqrt(best.who.mass))) - Matter.Body.applyForce(best.who, path[1], force) - } - } - //draw blowtorch laser beam - ctx.strokeStyle = "rgba(255,0,255,0.1)" - ctx.lineWidth = 14 - ctx.beginPath(); - ctx.moveTo(path[0].x, path[0].y); - ctx.lineTo(path[1].x, path[1].y); - ctx.stroke(); - ctx.strokeStyle = "#f0f"; - ctx.lineWidth = 2 - ctx.stroke(); - //draw electricity - let x = this.position.x + 20 * unit.x; - let y = this.position.y + 20 * unit.y; - ctx.beginPath(); - ctx.moveTo(x, y); - const step = Vector.magnitude(Vector.sub(path[0], path[1])) / 5 - for (let i = 0; i < 4; i++) { - x += step * (unit.x + 1.5 * (Math.random() - 0.5)) - y += step * (unit.y + 1.5 * (Math.random() - 0.5)) - ctx.lineTo(x, y); - } - ctx.lineWidth = 2 * Math.random(); - ctx.stroke(); - } - } - } - }) - World.add(engine.world, bullet[me]); //add bullet to world - }, - orbitBot(position = mech.pos) { - const me = bullet.length; - bullet[me] = Bodies.polygon(position.x, position.y, 9, 12, { - isUpgraded: mod.isOrbitBotUpgrade, - botType: "orbit", - friction: 0, - frictionStatic: 0, - frictionAir: 1, - isStatic: true, - isSensor: true, - restitution: 0, - dmg: 0, // 0.14 //damage done in addition to the damage from momentum - minDmgSpeed: 0, - endCycle: Infinity, - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: 0 //cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield - }, - beforeDmg() {}, - onEnd() {}, - range: 190 + 50 * mod.isOrbitBotUpgrade, //range is set in bot upgrade too! //150 + (80 + 100 * mod.isOrbitBotUpgrade) * Math.random(), // + 5 * mod.orbitBotCount, - orbitalSpeed: 0, - phase: 2 * Math.PI * Math.random(), - do() { - //check for damage - if (!mech.isCloak && !mech.isBodiesAsleep) { //if time dilation isn't active - // q = Matter.Query.point(mob, this.position) - // q = Matter.Query.collides(this, mob) - const size = 33 - q = Matter.Query.region(mob, { - min: { - x: this.position.x - size, - y: this.position.y - size - }, - max: { - x: this.position.x + size, - y: this.position.y + size - } - }) - for (let i = 0; i < q.length; i++) { - mobs.statusStun(q[i], 180) - const dmg = 0.5 * b.dmgScale * (this.isUpgraded ? 2.25 : 1) * (mod.isCrit ? 4 : 1) - q[i].damage(dmg); - q[i].foundPlayer(); - game.drawList.push({ //add dmg to draw queue - x: this.position.x, - y: this.position.y, - radius: Math.log(2 * dmg + 1.1) * 40, - color: 'rgba(0,0,0,0.4)', - time: game.drawTime - }); - } - } - //orbit player - const time = game.cycle * this.orbitalSpeed + this.phase - const orbit = { - x: Math.cos(time), - y: Math.sin(time) //*1.1 - } - Matter.Body.setPosition(this, Vector.add(mech.pos, Vector.mult(orbit, this.range))) //bullets move with player - } - }) - // bullet[me].orbitalSpeed = Math.sqrt(0.7 / bullet[me].range) - bullet[me].orbitalSpeed = Math.sqrt(0.25 / bullet[me].range) //also set in bot upgrade too! - // bullet[me].phase = (index / mod.orbitBotCount) * 2 * Math.PI - - World.add(engine.world, bullet[me]); //add bullet to world - - //reorder orbital bot positions around a circle - let totalOrbitalBots = 0 - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType === 'orbit') totalOrbitalBots++ - } - let index = 0 - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType === 'orbit') { - bullet[i].phase = (index / totalOrbitalBots) * 2 * Math.PI - index++ - } - } - }, - // ************************************************************************************************** - // ************************************************************************************************** - // ******************************** Guns ********************************************* - // ************************************************************************************************** - // ************************************************************************************************** - giveGuns(gun = "random", ammoPacks = 10) { - if (mod.isOneGun) b.removeAllGuns(); - if (gun === "random") { - //find what guns player doesn't have - options = [] - for (let i = 0, len = b.guns.length; i < len; i++) { - if (!b.guns[i].have) options.push(i) - } - if (options.length === 0) return - //randomly pick from list of possible guns - gun = options[Math.floor(Math.random() * options.length)] - } - if (gun === "all") { - b.activeGun = 0; - b.inventoryGun = 0; - for (let i = 0; i < b.guns.length; i++) { - b.inventory[i] = i; - b.guns[i].have = true; - b.guns[i].ammo = Math.floor(b.guns[i].ammoPack * ammoPacks); - } - } else { - if (isNaN(gun)) { //find gun by name - let found = false; - for (let i = 0; i < b.guns.length; i++) { - if (gun === b.guns[i].name) { - gun = i - found = true; - break - } - } - if (!found) return //if no gun found don't give a gun - } - if (!b.guns[gun].have) b.inventory.push(gun); - b.guns[gun].have = true; - b.guns[gun].ammo = Math.floor(b.guns[gun].ammoPack * ammoPacks); - if (b.activeGun === null) b.activeGun = gun //if no active gun switch to new gun - } - game.makeGunHUD(); - }, - guns: [{ - name: "nail gun", - description: "use compressed air to fire a stream of nails
delay after firing decreases as you shoot", - ammo: 0, - ammoPack: 60, - defaultAmmoPack: 60, - recordedAmmo: 0, - have: false, - nextFireCycle: 0, //use to remember how longs its been since last fire, used to reset count - startingHoldCycle: 0, - fire() { - let CD - if (mod.nailFireRate) { //fire delay decreases as you hold fire, down to 3 from 15 - if (mod.nailInstantFireRate) { - CD = 2 - } else { - if (this.nextFireCycle + 1 < mech.cycle) this.startingHoldCycle = mech.cycle //reset if not constantly firing - CD = Math.max(7.5 - 0.06 * (mech.cycle - this.startingHoldCycle), 2) //CD scales with cycles fire is held down - this.nextFireCycle = mech.cycle + CD * b.fireCD //predict next fire cycle if the fire button is held down - } - } else { - if (this.nextFireCycle + 1 < mech.cycle) this.startingHoldCycle = mech.cycle //reset if not constantly firing - CD = Math.max(11 - 0.06 * (mech.cycle - this.startingHoldCycle), 2) //CD scales with cycles fire is held down - this.nextFireCycle = mech.cycle + CD * b.fireCD //predict next fire cycle if the fire button is held down - } - mech.fireCDcycle = mech.cycle + Math.floor(CD * b.fireCD); // cool down - const speed = 30 + 6 * Math.random() + 9 * mod.nailInstantFireRate - const angle = mech.angle + (Math.random() - 0.5) * (Math.random() - 0.5) * (mech.crouch ? 1.35 : 3.2) / CD - const dmg = 0.9 - b.nail({ - x: mech.pos.x + 30 * Math.cos(mech.angle), - y: mech.pos.y + 30 * Math.sin(mech.angle) - }, { - x: mech.Vx / 2 + speed * Math.cos(angle), - y: mech.Vy / 2 + speed * Math.sin(angle) - }, dmg) //position, velocity, damage - if (mod.isIceCrystals) { - bullet[bullet.length - 1].beforeDmg = function (who) { - mobs.statusSlow(who, 30) - if (mod.isNailPoison) mobs.statusDoT(who, dmg * 0.22, 120) // one tick every 30 cycles - if (mod.isNailCrit && !who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.99) this.dmg *= 5 //crit if hit near center - }; - mech.energy -= mech.fieldRegen + 0.008 - if (mech.energy < 0.02) mech.fireCDcycle = mech.cycle + 60; // cool down - } - } - }, - { - name: "shotgun", - description: "fire a burst of short range bullets
crouch to reduce recoil", - ammo: 0, - ammoPack: 6, - defaultAmmoPack: 6, - have: false, - fire() { - let knock, spread - if (mech.crouch) { - spread = 0.75 - mech.fireCDcycle = mech.cycle + Math.floor(55 * b.fireCD); // cool down - if (mod.isShotgunImmune) mech.immuneCycle = mech.cycle + Math.floor(58 * b.fireCD); //player is immune to collision damage for 30 cycles - knock = 0.01 - } else { - mech.fireCDcycle = mech.cycle + Math.floor(45 * b.fireCD); // cool down - if (mod.isShotgunImmune) mech.immuneCycle = mech.cycle + Math.floor(47 * b.fireCD); //player is immune to collision damage for 30 cycles - spread = 1.3 - knock = 0.1 - } - - if (mod.isShotgunRecoil) { - mech.fireCDcycle -= 0.66 * (45 * b.fireCD) - player.force.x -= 2 * knock * Math.cos(mech.angle) - player.force.y -= 2 * knock * Math.sin(mech.angle) //reduce knock back in vertical direction to stop super jumps - } else { - player.force.x -= knock * Math.cos(mech.angle) - player.force.y -= knock * Math.sin(mech.angle) * 0.3 //reduce knock back in vertical direction to stop super jumps - } - - b.muzzleFlash(35); - if (mod.isNailShot) { - for (let i = 0; i < 14; i++) { - const dir = mech.angle + (Math.random() - 0.5) * spread * 0.2 - const pos = { - x: mech.pos.x + 35 * Math.cos(mech.angle) + 15 * (Math.random() - 0.5), - y: mech.pos.y + 35 * Math.sin(mech.angle) + 15 * (Math.random() - 0.5) - } - speed = 35 + 15 * Math.random() - const velocity = { - x: speed * Math.cos(dir), - y: speed * Math.sin(dir) - } - b.nail(pos, velocity, 1.2) - } - } else { - const side = 22 - for (let i = 0; i < 17; i++) { - const me = bullet.length; - const dir = mech.angle + (Math.random() - 0.5) * spread - bullet[me] = Bodies.rectangle(mech.pos.x + 35 * Math.cos(mech.angle) + 15 * (Math.random() - 0.5), mech.pos.y + 35 * Math.sin(mech.angle) + 15 * (Math.random() - 0.5), side, side, b.fireAttributes(dir)); - World.add(engine.world, bullet[me]); //add bullet to world - const SPEED = 52 + Math.random() * 8 - Matter.Body.setVelocity(bullet[me], { - x: SPEED * Math.cos(dir), - y: SPEED * Math.sin(dir) - }); - bullet[me].endCycle = game.cycle + 40 - bullet[me].minDmgSpeed = 15 - // bullet[me].restitution = 0.4 - bullet[me].frictionAir = 0.034; - bullet[me].do = function () { - if (!mech.isBodiesAsleep) { - const scale = 1 - 0.034 / mod.isBulletsLastLonger - Matter.Body.scale(this, scale, scale); - } - }; - } - } - } - }, - { - name: "super balls", - description: "fire four balls in a wide arc
balls bounce with no momentum loss", - ammo: 0, - ammoPack: 12, - have: false, - num: 5, - fire() { - const SPEED = mech.crouch ? 43 : 32 - mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 25 : 18) * b.fireCD); // cool down - if (mod.oneSuperBall) { - let dir = mech.angle - const me = bullet.length; - bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 12, 20 * mod.bulletSize, b.fireAttributes(dir, false)); - World.add(engine.world, bullet[me]); //add bullet to world - Matter.Body.setVelocity(bullet[me], { - x: SPEED * Math.cos(dir), - y: SPEED * Math.sin(dir) - }); - // Matter.Body.setDensity(bullet[me], 0.0001); - bullet[me].endCycle = game.cycle + Math.floor((300 + 60 * Math.random()) * mod.isBulletsLastLonger); - bullet[me].minDmgSpeed = 0; - bullet[me].restitution = 1; - bullet[me].friction = 0; - bullet[me].do = function () { - this.force.y += this.mass * 0.001; - }; - bullet[me].beforeDmg = function (who) { - mobs.statusStun(who, 180) // (2.3) * 2 / 14 ticks (2x damage over 7 seconds) - }; - } else { - b.muzzleFlash(20); - const SPREAD = mech.crouch ? 0.08 : 0.15 - let dir = mech.angle - SPREAD * (mod.superBallNumber - 1) / 2; - for (let i = 0; i < mod.superBallNumber; i++) { - const me = bullet.length; - bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 12, 7.5 * mod.bulletSize, b.fireAttributes(dir, false)); - World.add(engine.world, bullet[me]); //add bullet to world - Matter.Body.setVelocity(bullet[me], { - x: SPEED * Math.cos(dir), - y: SPEED * Math.sin(dir) - }); - // Matter.Body.setDensity(bullet[me], 0.0001); - bullet[me].endCycle = game.cycle + Math.floor((300 + 60 * Math.random()) * mod.isBulletsLastLonger); - bullet[me].minDmgSpeed = 0; - bullet[me].restitution = 0.99; - bullet[me].friction = 0; - bullet[me].do = function () { - this.force.y += this.mass * 0.001; - }; - dir += SPREAD; - } - } - } - }, - { - name: "flechettes", - description: "fire a volley of uranium-235 needles
does radioactive damage over 3 seconds", - ammo: 0, - ammoPack: 55, - defaultAmmoPack: 55, - have: false, - count: 0, //used to track how many shots are in a volley before a big CD - lastFireCycle: 0, //use to remember how longs its been since last fire, used to reset count - fire() { - function makeFlechette(angle = mech.angle + 0.02 * (Math.random() - 0.5)) { - const me = bullet.length; - bullet[me] = Bodies.rectangle(mech.pos.x + 40 * Math.cos(mech.angle), mech.pos.y + 40 * Math.sin(mech.angle), 45, 1.4, b.fireAttributes(angle)); - bullet[me].collisionFilter.mask = mod.pierce ? 0 : cat.body; //cat.mobShield | //cat.map | cat.body | - Matter.Body.setDensity(bullet[me], 0.00001); //0.001 is normal - bullet[me].endCycle = game.cycle + 180; - bullet[me].dmg = 0; - bullet[me].immuneList = [] - bullet[me].do = function () { - const whom = Matter.Query.collides(this, mob) - if (whom.length && this.speed > 20) { //if touching a mob - who = whom[0].bodyA - if (who && who.mob) { - if (mod.pierce) { - let immune = false - for (let i = 0; i < this.immuneList.length; i++) { - if (this.immuneList[i] === who.id) immune = true - } - if (!immune) { - this.immuneList.push(who.id) - who.foundPlayer(); - if (mod.isFastDot) { - mobs.statusDoT(who, 4, 30) - } else { - mobs.statusDoT(who, 0.66, mod.isSlowDot ? 360 : 180) - } - game.drawList.push({ //add dmg to draw queue - x: this.position.x, - y: this.position.y, - radius: 40, - color: "rgba(0,80,80,0.3)", - time: game.drawTime - }); - } - } else { - this.endCycle = 0; - if (mod.isFlechetteExplode && !who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.975) { - // mobs.statusStun(who, 120) - this.explodeRad = 300 + 60 * Math.random(); - b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end - } - who.foundPlayer(); - if (mod.isFastDot) { - mobs.statusDoT(who, 3.78, 30) - } else { - mobs.statusDoT(who, 0.63, mod.isSlowDot ? 360 : 180) - } - game.drawList.push({ //add dmg to draw queue - x: this.position.x, - y: this.position.y, - radius: 40, - color: "rgba(0,80,80,0.3)", - time: game.drawTime - }); - } - } - } else if (Matter.Query.collides(this, map).length) { //stick in walls - this.collisionFilter.mask = 0; - Matter.Body.setAngularVelocity(this, 0) - Matter.Body.setVelocity(this, { - x: 0, - y: 0 - }); - this.do = function () {} - } else if (this.speed < 30) { - this.force.y += this.mass * 0.0007; //no gravity until it slows down to improve aiming - } - }; - const SPEED = 50 - Matter.Body.setVelocity(bullet[me], { - x: mech.Vx / 2 + SPEED * Math.cos(angle), - y: mech.Vy / 2 + SPEED * Math.sin(angle) - }); - World.add(engine.world, bullet[me]); //add bullet to world - } - makeFlechette() - if (mod.isFlechetteMultiShot) { - makeFlechette(mech.angle + 0.02 + 0.005 * Math.random()) - makeFlechette(mech.angle - 0.02 - 0.005 * Math.random()) - } - - const CD = (mech.crouch) ? 60 : 30 - if (this.lastFireCycle + CD < mech.cycle) this.count = 0 //reset count if it cycles past the CD - this.lastFireCycle = mech.cycle - if (this.count > ((mech.crouch) ? 7 : 1)) { - this.count = 0 - mech.fireCDcycle = mech.cycle + Math.floor(CD * b.fireCD); // cool down - const who = bullet[bullet.length - 1] - Matter.Body.setDensity(who, 0.00001); - } else { - this.count++ - mech.fireCDcycle = mech.cycle + Math.floor(2 * b.fireCD); // cool down - } - } - }, - { - name: "wave beam", - description: "emit a sine wave of oscillating particles
that can propagate through solids", - ammo: 0, - ammoPack: 70, - have: false, - fire() { - mech.fireCDcycle = mech.cycle + Math.floor(3 * b.fireCD); // cool down - const dir = mech.angle - const SPEED = 10 - let wiggleMag - if (mod.waveHelix === 2) { - wiggleMag = (mech.crouch ? 6 : 12) * (1 + Math.sin(mech.cycle * 0.1)) - } else { - wiggleMag = mech.crouch ? 6 : 12 - } - // const wiggleMag = mod.waveHelix ? (mech.crouch ? 6 + 6 * Math.sin(mech.cycle * 0.1) : 13 + 13 * Math.sin(mech.cycle * 0.1)) : (mech.crouch ? 6 : 12) - const size = 5 * (mod.waveHelix === 1 ? 1 : 0.7) - for (let i = 0; i < mod.waveHelix; i++) { - const me = bullet.length; - bullet[me] = Bodies.polygon(mech.pos.x + 25 * Math.cos(dir), mech.pos.y + 25 * Math.sin(dir), 7, size, { - angle: dir, - cycle: -0.5, - endCycle: game.cycle + Math.floor((mod.isWaveReflect ? 600 : 120) * mod.isBulletsLastLonger), - inertia: Infinity, - frictionAir: 0, - slow: 0, - minDmgSpeed: 0, - dmg: 0, - isJustReflected: false, - classType: "bullet", - collisionFilter: { - category: 0, - mask: 0, //cat.mob | cat.mobBullet | cat.mobShield - }, - beforeDmg() {}, - onEnd() {}, - do() { - if (!mech.isBodiesAsleep) { - if (mod.isWaveReflect) { - // check if inside a mob - q = Matter.Query.point(mob, this.position) - for (let i = 0; i < q.length; i++) { - let dmg = b.dmgScale * 0.36 / Math.sqrt(q[i].mass) * (mod.waveHelix === 1 ? 1 : 0.66) //1 - 0.4 = 0.6 for helix mod 40% damage reduction - q[i].damage(dmg); - q[i].foundPlayer(); - game.drawList.push({ //add dmg to draw queue - x: this.position.x, - y: this.position.y, - radius: Math.log(2 * dmg + 1.1) * 40, - color: 'rgba(0,0,0,0.4)', - time: game.drawTime - }); - } - Matter.Body.setPosition(this, Vector.add(this.position, player.velocity)) //bullets move with player - const sub = Vector.sub(this.position, mech.pos) - const range = 558 //93 * x - if (Vector.magnitude(sub) > range) { - // Matter.Body.setPosition(this, Vector.sub(this.position, Vector.mult(Vector.normalise(sub), 2 * range))) //teleport to opposite side - Matter.Body.setVelocity(this, Vector.mult(this.velocity, -1)); - Matter.Body.setPosition(this, Vector.add(mech.pos, Vector.mult(Vector.normalise(sub), range))) //reflect - } - } else { - let slowCheck = 1 - if (Matter.Query.point(map, this.position).length) { //check if inside map - slowCheck = mod.waveSpeedMap - } else { //check if inside a body - let q = Matter.Query.point(body, this.position) - if (q.length) { - slowCheck = mod.waveSpeedBody - Matter.Body.setPosition(this, Vector.add(this.position, q[0].velocity)) //move with the medium - } else { // check if inside a mob - q = Matter.Query.point(mob, this.position) - for (let i = 0; i < q.length; i++) { - slowCheck = 0.3; - Matter.Body.setPosition(this, Vector.add(this.position, q[i].velocity)) //move with the medium - let dmg = b.dmgScale * 0.36 / Math.sqrt(q[i].mass) * (mod.waveHelix === 1 ? 1 : 0.66) //1 - 0.4 = 0.6 for helix mod 40% damage reduction - q[i].damage(dmg); - q[i].foundPlayer(); - game.drawList.push({ //add dmg to draw queue - x: this.position.x, - y: this.position.y, - radius: Math.log(2 * dmg + 1.1) * 40, - color: 'rgba(0,0,0,0.4)', - time: game.drawTime - }); - } - } - } - if (slowCheck !== this.slow) { //toggle velocity based on inside and outside status change - this.slow = slowCheck - Matter.Body.setVelocity(this, Vector.mult(Vector.normalise(this.velocity), SPEED * slowCheck)); - } - } - this.cycle++ - const wiggle = Vector.mult(transverse, wiggleMag * Math.cos(this.cycle * 0.35) * ((i % 2) ? -1 : 1)) - Matter.Body.setPosition(this, Vector.add(this.position, wiggle)) - } - // if (mod.isWaveReflect) { //single reflection - // const sub = Vector.sub(this.position, mech.pos) - // if (Vector.magnitude(sub) > 630) { - // // Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(Vector.normalise(sub), -2 * POCKET_RANGE))) //teleport to opposite side - // if (!this.isJustReflected) { - // Matter.Body.setVelocity(this, Vector.mult(this.velocity, -1)); //reflect - // this.isJustReflected = true; - // } - // } - // } - - // if (mod.isWaveReflect) { - // Matter.Body.setPosition(this, Vector.add(this.position, player.velocity)) //bullets move with player - - // Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(Vector.normalise(sub), -2 * POCKET_RANGE))) //teleport to opposite side - - // const sub = Vector.sub(this.position, mech.pos) - // if (Vector.magnitude(sub) > 630) { - // if (!this.isJustReflected) { - // Matter.Body.setVelocity(this, Vector.mult(this.velocity, -1)); //reflect - // this.isJustReflected = true; - // } - // } else { - // this.isJustReflected = false - // } - // } - } - }); - World.add(engine.world, bullet[me]); //add bullet to world - Matter.Body.setVelocity(bullet[me], { - x: SPEED * Math.cos(dir), - y: SPEED * Math.sin(dir) - }); - const transverse = Vector.normalise(Vector.perp(bullet[me].velocity)) - } - } - }, - { - name: "missiles", - description: "launch missiles that accelerate towards mobs
explodes when near target", - ammo: 0, - ammoPack: 3, - have: false, - fireCycle: 0, - ammoLoaded: 0, - fire() { - if (mod.is3Missiles) { - if (mech.crouch) { - mech.fireCDcycle = mech.cycle + 60 * b.fireCD; // cool down - const direction = { - x: Math.cos(mech.angle), - y: Math.sin(mech.angle) - } - const push = Vector.mult(Vector.perp(direction), 0.0007) - for (let i = 0; i < 3; i++) { - //missile(where, dir, speed, size = 1, spawn = 0) { - b.missile({ - x: mech.pos.x + 40 * direction.x, - y: mech.pos.y + 40 * direction.y - }, mech.angle + 0.06 * (1 - i), 0, 0.7, mod.recursiveMissiles) - bullet[bullet.length - 1].force.x += push.x * (i - 1); - bullet[bullet.length - 1].force.y += push.y * (i - 1); - } - } else { - mech.fireCDcycle = mech.cycle + 45 * b.fireCD; // cool down - const direction = { - x: Math.cos(mech.angle), - y: Math.sin(mech.angle) - } - const push = Vector.mult(Vector.perp(direction), 0.0008) - for (let i = 0; i < 3; i++) { - //missile(where, dir, speed, size = 1, spawn = 0) { - b.missile({ - x: mech.pos.x + 40 * direction.x, - y: mech.pos.y + 40 * direction.y - }, mech.angle, 0, 0.7, mod.recursiveMissiles) - bullet[bullet.length - 1].force.x += push.x * (i - 1); - bullet[bullet.length - 1].force.y += push.y * (i - 1); - } - } - } else { - mech.fireCDcycle = mech.cycle + Math.floor(mech.crouch ? 45 : 30) * b.fireCD; // cool down - b.missile({ - x: mech.pos.x + 40 * Math.cos(mech.angle), - y: mech.pos.y + 40 * Math.sin(mech.angle) - 3 - }, - mech.angle + (0.5 - Math.random()) * (mech.crouch ? 0 : 0.2), - -3 * (0.5 - Math.random()) + (mech.crouch ? 25 : -8) * b.fireCD, - 1, mod.recursiveMissiles) - bullet[bullet.length - 1].force.y += 0.0006; //a small push down at first to make it seem like the missile is briefly falling - } - } - }, - { - name: "flak", - description: "fire a cluster of short range projectiles
explodes on contact or after half a second", - ammo: 0, - ammoPack: 4, - defaultAmmoPack: 4, //use to revert ammoPack after mod changes drop rate - have: false, - fire() { - mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 25 : 10) * b.fireCD); // cool down - b.muzzleFlash(30); - const SPEED = mech.crouch ? 29 : 25 - const END = Math.floor(mech.crouch ? 30 : 18); - const side1 = 17 - const side2 = 4 - const totalBullets = 6 - const angleStep = (mech.crouch ? 0.06 : 0.25) / totalBullets - let dir = mech.angle - angleStep * totalBullets / 2; - for (let i = 0; i < totalBullets; i++) { //5 -> 7 - dir += angleStep - const me = bullet.length; - bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), side1, side2, b.fireAttributes(dir)); - World.add(engine.world, bullet[me]); //add bullet to world - Matter.Body.setVelocity(bullet[me], { - x: (SPEED + 15 * Math.random() - 2 * i) * Math.cos(dir), - y: (SPEED + 15 * Math.random() - 2 * i) * Math.sin(dir) - }); - bullet[me].endCycle = 2 * i + game.cycle + END - bullet[me].restitution = 0; - bullet[me].friction = 1; - bullet[me].explodeRad = (mech.crouch ? 95 : 75) + (Math.random() - 0.5) * 50; - bullet[me].onEnd = function () { - b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end - } - bullet[me].beforeDmg = function () { - this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion - }; - bullet[me].do = function () { - // this.force.y += this.mass * 0.0004; - } - } - } - }, - { - name: "grenades", - description: "lob a single bouncy projectile
explodes on contact or after one second", - ammo: 0, - ammoPack: 5, - have: false, - fire() { - - }, - fireNormal() { - const me = bullet.length; - const dir = mech.angle; // + Math.random() * 0.05; - bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 20, b.fireAttributes(dir, false)); - Matter.Body.setDensity(bullet[me], 0.0005); - bullet[me].explodeRad = 275; - bullet[me].onEnd = function () { - b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end - if (mod.grenadeFragments) b.targetedNail(this.position, mod.grenadeFragments) - } - bullet[me].minDmgSpeed = 1; - bullet[me].beforeDmg = function () { - this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion - }; - if (mod.isRPG) { - b.fireProps(35, mech.crouch ? 60 : -15, dir, me); //cd , speed - bullet[me].endCycle = game.cycle + 70; - bullet[me].frictionAir = 0.07; - const MAG = 0.015 - bullet[me].thrust = { - x: bullet[me].mass * MAG * Math.cos(dir), - y: bullet[me].mass * MAG * Math.sin(dir) - } - bullet[me].do = function () { - this.force.x += this.thrust.x; - this.force.y += this.thrust.y; - if (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length) { - this.endCycle = 0; //explode if touching map or blocks - } - }; - } else { - b.fireProps(mech.crouch ? 40 : 30, mech.crouch ? 43 : 32, dir, me); //cd , speed - bullet[me].endCycle = game.cycle + Math.floor(mech.crouch ? 120 : 80); - bullet[me].restitution = 0.4; - bullet[me].do = function () { - this.force.y += this.mass * 0.0025; //extra gravity for harder arcs - }; - } - }, - fireVacuum() { - const me = bullet.length; - const dir = mech.angle; // + Math.random() * 0.05; - bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 20, b.fireAttributes(dir, false)); - Matter.Body.setDensity(bullet[me], 0.0003); - bullet[me].explodeRad = 350 + Math.floor(Math.random() * 50);; - bullet[me].onEnd = function () { - b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end - if (mod.grenadeFragments) b.targetedNail(this.position, mod.grenadeFragments) - } - bullet[me].beforeDmg = function () {}; - const cd = mech.crouch ? 90 : 75 - b.fireProps(cd, mech.crouch ? 46 : 35, dir, me); //cd , speed - bullet[me].endCycle = game.cycle + cd; - bullet[me].restitution = 0.4; - bullet[me].do = function () { - this.force.y += this.mass * 0.0025; //extra gravity for harder arcs - - const suckCycles = 40 - if (game.cycle > this.endCycle - suckCycles) { //suck - const that = this - - function suck(who, radius = that.explodeRad * 3.2) { - for (i = 0, len = who.length; i < len; i++) { - const sub = Vector.sub(that.position, who[i].position); - const dist = Vector.magnitude(sub); - if (dist < radius && dist > 150) { - knock = Vector.mult(Vector.normalise(sub), mag * who[i].mass / Math.sqrt(dist)); - who[i].force.x += knock.x; - who[i].force.y += knock.y; - } - } - } - let mag = 0.1 - if (game.cycle > this.endCycle - 5) { - mag = -0.22 - suck(mob, this.explodeRad * 3) - suck(body, this.explodeRad * 2) - suck(powerUp, this.explodeRad * 1.5) - suck(bullet, this.explodeRad * 1.5) - suck([player], this.explodeRad * 1.3) - } else { - mag = 0.11 - suck(mob, this.explodeRad * 3) - suck(body, this.explodeRad * 2) - suck(powerUp, this.explodeRad * 1.5) - suck(bullet, this.explodeRad * 1.5) - suck([player], this.explodeRad * 1.3) - } - //keep bomb in place - Matter.Body.setVelocity(this, { - x: 0, - y: 0 - }); - //draw suck - const radius = 2.75 * this.explodeRad * (this.endCycle - game.cycle) / suckCycles - ctx.fillStyle = "rgba(0,0,0,0.1)"; - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, radius, 0, 2 * Math.PI); - ctx.fill(); - } - }; - } - }, - { - name: "neutron bomb", - description: "toss a chunk of Cf-252 which emits neutrons
that do damage, harm, and energy drain", - ammo: 0, - ammoPack: 5, - have: false, - fire() { - const me = bullet.length; - const dir = mech.angle; - bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 10, 4, b.fireAttributes(dir, false)); - b.fireProps(mech.crouch ? 30 : 15, mech.crouch ? 28 : 18, dir, me); //cd , speed - Matter.Body.setDensity(bullet[me], 0.000001); - bullet[me].endCycle = Infinity; - bullet[me].frictionAir = 0; - bullet[me].friction = 1; - bullet[me].frictionStatic = 1; - bullet[me].restitution = 0; - bullet[me].minDmgSpeed = 0; - bullet[me].damageRadius = 100; - bullet[me].maxDamageRadius = (435 + 150 * Math.random()) * (mod.isNeutronImmune ? 1.2 : 1) - bullet[me].stuckTo = null; - bullet[me].stuckToRelativePosition = null; - bullet[me].beforeDmg = function () {}; - bullet[me].stuck = function () {}; - bullet[me].do = function () { - function onCollide(that) { - that.collisionFilter.mask = 0; //non collide with everything - Matter.Body.setVelocity(that, { - x: 0, - y: 0 - }); - // that.frictionAir = 1; - that.do = that.radiationMode; - - if (mod.isNeutronStun) { - //push blocks - const dist = that.maxDamageRadius * 0.9 - for (let i = 0, len = body.length; i < len; ++i) { - const SUB = Vector.sub(body[i].position, that.position) - const DISTANCE = Vector.magnitude(SUB) - if (DISTANCE < dist) { - const FORCE = Vector.mult(Vector.normalise(SUB), 0.04 * body[i].mass) - body[i].force.x += FORCE.x; - body[i].force.y += FORCE.y - body[i].mass * game.g * 5; //kick up a bit to give them some arc - } - } - //stun mobs - for (let i = 0, len = mob.length; i < len; ++i) { - if (Vector.magnitude(Vector.sub(mob[i].position, that.position)) < dist) { - mobs.statusStun(mob[i], mod.isNeutronStun) - } - } - } - } - - const mobCollisions = Matter.Query.collides(this, mob) - if (mobCollisions.length) { - onCollide(this) - this.stuckTo = mobCollisions[0].bodyA - - if (this.stuckTo.isVerticesChange) { - this.stuckToRelativePosition = { - x: 0, - y: 0 - } - } else { - //find the relative position for when the mob is at angle zero by undoing the mobs rotation - this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) - } - this.stuck = function () { - if (this.stuckTo && this.stuckTo.alive) { - const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector - Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) - Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck - } else { - this.collisionFilter.mask = cat.map | cat.body | cat.player | cat.mob; //non collide with everything but map - this.stuck = function () { - this.force.y += this.mass * 0.001; - } - } - } - } else { - const bodyCollisions = Matter.Query.collides(this, body) - if (bodyCollisions.length) { - if (!bodyCollisions[0].bodyA.isNotHoldable) { - onCollide(this) - this.stuckTo = bodyCollisions[0].bodyA - //find the relative position for when the mob is at angle zero by undoing the mobs rotation - this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) - } else { - this.do = this.radiationMode; - } - this.stuck = function () { - if (this.stuckTo) { - const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector - Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) - // Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck - } else { - this.force.y += this.mass * 0.001; - } - } - } else { - if (Matter.Query.collides(this, map).length) { - onCollide(this) - } else { //if colliding with nothing just fall - this.force.y += this.mass * 0.001; - } - } - } - } - bullet[me].radiationMode = function () { - this.stuck(); //runs different code based on what the bullet is stuck to - if (!mech.isBodiesAsleep) { - this.damageRadius = this.damageRadius * 0.85 + 0.15 * this.maxDamageRadius //smooth radius towards max - this.maxDamageRadius -= 0.8 / mod.isBulletsLastLonger //+ 0.5 * Math.sin(game.cycle * 0.1) //slowly shrink max radius - - if (this.damageRadius < 15) { - this.endCycle = 0; - } else { - //aoe damage to player - if (!mod.isNeutronImmune && Vector.magnitude(Vector.sub(player.position, this.position)) < this.damageRadius) { - const DRAIN = 0.0015 - if (mech.energy > DRAIN) { - mech.energy -= DRAIN - } else { - mech.energy = 0; - mech.damage(0.00015) - } - } - //aoe damage to mobs - for (let i = 0, len = mob.length; i < len; i++) { - if (Vector.magnitude(Vector.sub(mob[i].position, this.position)) < this.damageRadius) { - let dmg = b.dmgScale * 0.035 - if (Matter.Query.ray(map, mob[i].position, this.position).length > 0) dmg *= 0.3 //reduce damage if a wall is in the way - if (mob[i].shield) dmg *= 4 //x5 to make up for the /5 that shields normally take - mob[i].damage(dmg); - mob[i].locatePlayer(); - } - } - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.damageRadius, 0, 2 * Math.PI); - ctx.globalCompositeOperation = "lighter" - ctx.fillStyle = `rgba(25,139,170,${0.2+0.06*Math.random()})`; - ctx.fill(); - ctx.globalCompositeOperation = "source-over" - } - } - } - } - }, - { - name: "mine", - description: "toss a proximity mine that sticks to walls
fires nails at mobs within range", - ammo: 0, - ammoPack: 2.7, - have: false, - fire() { - const pos = { - x: mech.pos.x + 30 * Math.cos(mech.angle), - y: mech.pos.y + 30 * Math.sin(mech.angle) - } - let speed = mech.crouch ? 36 : 22 - if (Matter.Query.point(map, pos).length > 0) { //don't fire if mine will spawn inside map - speed = -2 - } - b.mine(pos, { - x: speed * Math.cos(mech.angle), - y: speed * Math.sin(mech.angle) - }, 0, mod.isMineAmmoBack) - mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 50 : 25) * b.fireCD); // cool down - } - }, - { - name: "spores", - description: "fire a sporangium that discharges spores
spores seek out nearby mobs", - ammo: 0, - ammoPack: 3, - have: false, - fire() { - const me = bullet.length; - const dir = mech.angle; - bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 20, 4.5, b.fireAttributes(dir, false)); - b.fireProps(mech.crouch ? 50 : 30, mech.crouch ? 30 : 16, dir, me); //cd , speed - Matter.Body.setDensity(bullet[me], 0.000001); - bullet[me].endCycle = Infinity; - bullet[me].frictionAir = 0; - bullet[me].friction = 0.5; - bullet[me].radius = 4.5; - bullet[me].maxRadius = 30; - bullet[me].restitution = 0.3; - bullet[me].minDmgSpeed = 0; - bullet[me].totalSpores = 8 + 2 * mod.isFastSpores + 2 * mod.isSporeFreeze - bullet[me].stuck = function () {}; - bullet[me].beforeDmg = function () {}; - bullet[me].do = function () { - function onCollide(that) { - that.collisionFilter.mask = 0; //non collide with everything - Matter.Body.setVelocity(that, { - x: 0, - y: 0 - }); - that.do = that.grow; - } - - const mobCollisions = Matter.Query.collides(this, mob) - if (mobCollisions.length) { - onCollide(this) - this.stuckTo = mobCollisions[0].bodyA - - if (this.stuckTo.isVerticesChange) { - this.stuckToRelativePosition = { - x: 0, - y: 0 - } - } else { - //find the relative position for when the mob is at angle zero by undoing the mobs rotation - this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) - } - this.stuck = function () { - if (this.stuckTo && this.stuckTo.alive) { - const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector - Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) - Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck - } else { - this.collisionFilter.mask = cat.map; //non collide with everything but map - this.stuck = function () { - this.force.y += this.mass * 0.0006; - } - } - } - } else { - const bodyCollisions = Matter.Query.collides(this, body) - if (bodyCollisions.length) { - if (!bodyCollisions[0].bodyA.isNotHoldable) { - onCollide(this) - this.stuckTo = bodyCollisions[0].bodyA - //find the relative position for when the mob is at angle zero by undoing the mobs rotation - this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) - } else { - this.do = this.grow; - } - this.stuck = function () { - if (this.stuckTo) { - const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector - Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) - // Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck - } else { - this.force.y += this.mass * 0.0006; - } - } - } else { - if (Matter.Query.collides(this, map).length) { - onCollide(this) - } else { //if colliding with nothing just fall - this.force.y += this.mass * 0.0006; - } - } - } - //draw green glow - ctx.fillStyle = "rgba(0,200,125,0.16)"; - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.maxRadius, 0, 2 * Math.PI); - ctx.fill(); - } - - bullet[me].grow = function () { - this.stuck(); //runs different code based on what the bullet is stuck to - if (!mech.isBodiesAsleep) { - let scale = 1.01 - if (mod.isSporeGrowth && !(game.cycle % 60)) { //release a spore - b.spore(this.position) - // this.totalSpores-- - scale = 0.94 - if (this.stuckTo && this.stuckTo.alive) scale = 0.88 - Matter.Body.scale(this, scale, scale); - this.radius *= scale - } else { - if (this.stuckTo && this.stuckTo.alive) scale = 1.03 - Matter.Body.scale(this, scale, scale); - this.radius *= scale - if (this.radius > this.maxRadius) this.endCycle = 0; - } - } - - // this.force.y += this.mass * 0.00045; - - //draw green glow - ctx.fillStyle = "rgba(0,200,125,0.16)"; - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.maxRadius, 0, 2 * Math.PI); - ctx.fill(); - }; - - //spawn bullets on end - bullet[me].onEnd = function () { - const NUM = this.totalSpores - for (let i = 0; i < NUM; i++) { - b.spore(this.position) - } - } - } - }, - { - name: "drones", - description: "deploy drones that crash into mobs
crashes reduce their lifespan by 1 second", - ammo: 0, - ammoPack: 14, - have: false, - fire() { - if (mech.crouch) { - b.drone(45) - mech.fireCDcycle = mech.cycle + Math.floor(13 * b.fireCD); // cool down - } else { - b.drone(1) - mech.fireCDcycle = mech.cycle + Math.floor(6 * b.fireCD); // cool down - } - } - }, - { - name: "ice IX", - description: "synthesize short-lived ice crystals
crystals seek out and freeze mobs", - ammo: 0, - ammoPack: 64, - have: false, - fire() { - if (mech.crouch) { - b.iceIX(10, 0.3) - mech.fireCDcycle = mech.cycle + Math.floor(8 * b.fireCD); // cool down - } else { - b.iceIX(2) - mech.fireCDcycle = mech.cycle + Math.floor(3 * b.fireCD); // cool down - } - - } - }, - { - name: "foam", - description: "spray bubbly foam that sticks to mobs
slows mobs and does damage over time", - ammo: 0, - ammoPack: 40, - have: false, - fire() { - mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 20 : 6) * b.fireCD); // cool down - const radius = (mech.crouch ? 10 + 7 * Math.random() : 4 + 6 * Math.random()) - const dir = mech.angle + 0.2 * (Math.random() - 0.5) - const position = { - x: mech.pos.x + 30 * Math.cos(mech.angle), - y: mech.pos.y + 30 * Math.sin(mech.angle) - } - const SPEED = 21 - radius * 0.7; //(mech.crouch ? 32 : 20) - radius * 0.7; - const velocity = { - x: SPEED * Math.cos(dir), - y: SPEED * Math.sin(dir) - } - b.foam(position, velocity, radius) - } - }, - { - name: "rail gun", - description: "use energy to launch a high-speed dense rod
hold left mouse to charge, release to fire", - ammo: 0, - ammoPack: 3.5, - have: false, - fire() { - if (mod.isCapacitor) { - if (mech.energy > 0.15) { - mech.energy -= 0.15 - mech.fireCDcycle = mech.cycle + Math.floor(30 * b.fireCD); - const me = bullet.length; - bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), 60, 14, { - density: 0.005, //0.001 is normal - restitution: 0, - frictionAir: 0, - angle: mech.angle, - dmg: 0, //damage done in addition to the damage from momentum - classType: "bullet", - collisionFilter: { - category: cat.bullet, - mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield - }, - minDmgSpeed: 5, - endCycle: game.cycle + 140, - beforeDmg(who) { - if (who.shield) { - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].id === who.shieldTargetID) { //apply some knock back to shield mob before shield breaks - Matter.Body.setVelocity(mob[i], Vector.mult(Vector.normalise(this.velocity), 10)); - break - } - } - Matter.Body.setVelocity(this, { - x: -0.5 * this.velocity.x, - y: -0.5 * this.velocity.y - }); - // Matter.Body.setDensity(this, 0.001); - } - if (mod.isRailNails && this.speed > 10) { - b.targetedNail(this.position, (Math.min(40, this.speed) - 10) * 0.6) // 0.6 as many nails as the normal rail gun - this.endCycle = 0 //triggers despawn - } - }, - onEnd() {}, - drawCycle: Math.floor(10 * b.fireCD), - do() { - this.force.y += this.mass * 0.0003; // low gravity that scales with charge - if (this.drawCycle > 0) { - this.drawCycle-- - //draw magnetic field - const X = mech.pos.x - const Y = mech.pos.y - const unitVector = Vector.normalise(Vector.sub(game.mouseInGame, mech.pos)) - const unitVectorPerp = Vector.perp(unitVector) - - function magField(mag, arc) { - ctx.moveTo(X, Y); - ctx.bezierCurveTo( - X + unitVector.x * mag, Y + unitVector.y * mag, - X + unitVector.x * mag + unitVectorPerp.x * arc, Y + unitVector.y * mag + unitVectorPerp.y * arc, - X + unitVectorPerp.x * arc, Y + unitVectorPerp.y * arc) - ctx.bezierCurveTo( - X - unitVector.x * mag + unitVectorPerp.x * arc, Y - unitVector.y * mag + unitVectorPerp.y * arc, - X - unitVector.x * mag, Y - unitVector.y * mag, - X, Y) - } - ctx.fillStyle = `rgba(50,0,100,0.05)`; - for (let i = 3; i < 7; i++) { - const MAG = 8 * i * i * (0.93 + 0.07 * Math.random()) * (0.95 + 0.1 * Math.random()) - const ARC = 6 * i * i * (0.93 + 0.07 * Math.random()) * (0.95 + 0.1 * Math.random()) - ctx.beginPath(); - magField(MAG, ARC) - magField(MAG, -ARC) - ctx.fill(); - } - } - } - }); - World.add(engine.world, bullet[me]); //add bullet to world - - const speed = 67 - Matter.Body.setVelocity(bullet[me], { - x: mech.Vx / 2 + speed * Math.cos(mech.angle), - y: mech.Vy / 2 + speed * Math.sin(mech.angle) - }); - - //knock back - const KNOCK = mech.crouch ? 0.08 : 0.34 - player.force.x -= KNOCK * Math.cos(mech.angle) - player.force.y -= KNOCK * Math.sin(mech.angle) * 0.35 //reduce knock back in vertical direction to stop super jumps - - //push away blocks when firing - let range = 450 - for (let i = 0, len = body.length; i < len; ++i) { - const SUB = Vector.sub(body[i].position, mech.pos) - const DISTANCE = Vector.magnitude(SUB) - - if (DISTANCE < range) { - const DEPTH = Math.min(range - DISTANCE, 300) - const FORCE = Vector.mult(Vector.normalise(SUB), 0.003 * Math.sqrt(DEPTH) * body[i].mass) - body[i].force.x += FORCE.x; - body[i].force.y += FORCE.y - body[i].mass * (game.g * 1.5); //kick up a bit to give them some arc - } - } - for (let i = 0, len = mob.length; i < len; ++i) { - const SUB = Vector.sub(mob[i].position, mech.pos) - const DISTANCE = Vector.magnitude(SUB) - if (DISTANCE < range) { - const DEPTH = Math.min(range - DISTANCE, 300) - const FORCE = Vector.mult(Vector.normalise(SUB), 0.003 * Math.sqrt(DEPTH) * mob[i].mass) - mob[i].force.x += 1.5 * FORCE.x; - mob[i].force.y += 1.5 * FORCE.y; - } - } - } else { - mech.fireCDcycle = mech.cycle + Math.floor(120); - } - } else { - const me = bullet.length; - bullet[me] = Bodies.rectangle(0, 0, 0.015, 0.0015, { - density: 0.008, //0.001 is normal - //frictionAir: 0.01, //restitution: 0, - // angle: 0, - // friction: 0.5, - restitution: 0, - frictionAir: 0, - dmg: 0, //damage done in addition to the damage from momentum - classType: "bullet", - collisionFilter: { - category: 0, - mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield - }, - minDmgSpeed: 5, - beforeDmg(who) { - if (who.shield) { - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].id === who.shieldTargetID) { //apply some knock back to shield mob before shield breaks - Matter.Body.setVelocity(mob[i], Vector.mult(Vector.normalise(this.velocity), 10)); - break - } - } - Matter.Body.setVelocity(this, { - x: -0.5 * this.velocity.x, - y: -0.5 * this.velocity.y - }); - // Matter.Body.setDensity(this, 0.001); - } - if (mod.isRailNails && this.speed > 10) { - b.targetedNail(this.position, Math.min(40, this.speed) - 10) - this.endCycle = 0 //triggers despawn - } - }, - onEnd() {} - }); - mech.fireCDcycle = Infinity; // cool down - World.add(engine.world, bullet[me]); //add bullet to world - bullet[me].endCycle = Infinity - bullet[me].charge = 0; - bullet[me].do = function () { - if (mech.energy < 0.005 && !mod.isRailTimeSlow) { - mech.energy += 0.05 + this.charge * 0.3 - mech.fireCDcycle = mech.cycle + 120; // cool down if out of energy - this.endCycle = 0; - return - } - - if ((!input.fire && this.charge > 0.6)) { //fire on mouse release or on low energy - mech.fireCDcycle = mech.cycle + 2; // set fire cool down - //normal bullet behavior occurs after firing, overwrites this function - this.do = function () { - this.force.y += this.mass * 0.0003 / this.charge; // low gravity that scales with charge - } - if (mod.isRailTimeSlow) { - game.fpsCap = game.fpsCapDefault - game.fpsInterval = 1000 / game.fpsCap; - } - - Matter.Body.scale(this, 8000, 8000) // show the bullet by scaling it up (don't judge me... I know this is a bad way to do it) - this.endCycle = game.cycle + 140 - this.collisionFilter.category = cat.bullet - Matter.Body.setPosition(this, { - x: mech.pos.x, - y: mech.pos.y - }) - Matter.Body.setAngle(this, mech.angle) - const speed = 90 - Matter.Body.setVelocity(this, { - x: mech.Vx / 2 + speed * this.charge * Math.cos(mech.angle), - y: mech.Vy / 2 + speed * this.charge * Math.sin(mech.angle) - }); - - //knock back - const KNOCK = ((mech.crouch) ? 0.1 : 0.5) * this.charge * this.charge - player.force.x -= KNOCK * Math.cos(mech.angle) - player.force.y -= KNOCK * Math.sin(mech.angle) * 0.35 //reduce knock back in vertical direction to stop super jumps - - //push away blocks when firing - let range = 900 * this.charge - for (let i = 0, len = body.length; i < len; ++i) { - const SUB = Vector.sub(body[i].position, mech.pos) - const DISTANCE = Vector.magnitude(SUB) - - if (DISTANCE < range) { - const DEPTH = Math.min(range - DISTANCE, 300) - const FORCE = Vector.mult(Vector.normalise(SUB), 0.003 * Math.sqrt(DEPTH) * body[i].mass) - body[i].force.x += FORCE.x; - body[i].force.y += FORCE.y - body[i].mass * (game.g * 1.5); //kick up a bit to give them some arc - } - } - for (let i = 0, len = mob.length; i < len; ++i) { - const SUB = Vector.sub(mob[i].position, mech.pos) - const DISTANCE = Vector.magnitude(SUB) - - if (DISTANCE < range) { - const DEPTH = Math.min(range - DISTANCE, 300) - const FORCE = Vector.mult(Vector.normalise(SUB), 0.003 * Math.sqrt(DEPTH) * mob[i].mass) - mob[i].force.x += 1.5 * FORCE.x; - mob[i].force.y += 1.5 * FORCE.y; - } - } - } else { // charging on mouse down - mech.fireCDcycle = Infinity //can't fire until mouse is released - const lastCharge = this.charge - let chargeRate = (mech.crouch) ? 0.98 : 0.984 - chargeRate *= Math.pow(b.fireCD, 0.03) - this.charge = this.charge * chargeRate + (1 - chargeRate) // this.charge converges to 1 - if (mod.isRailTimeSlow) { - game.fpsCap = 30 //new fps - game.fpsInterval = 1000 / game.fpsCap; - } else { - mech.energy -= (this.charge - lastCharge) * 0.28 //energy drain is proportional to charge gained, but doesn't stop normal mech.fieldRegen - } - - //draw targeting - let best; - let range = 3000 - const dir = mech.angle - const path = [{ - x: mech.pos.x + 20 * Math.cos(dir), - y: mech.pos.y + 20 * Math.sin(dir) - }, - { - x: mech.pos.x + range * Math.cos(dir), - y: mech.pos.y + range * Math.sin(dir) - } - ]; - 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) { best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[j], - v2: vertices[j + 1] + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] }; - } } - } - 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] - }; - } - } } - }; + } + }; - //check for collisions - best = { + const checkForCollisions = function() { + best = { x: null, y: null, dist2: Infinity, who: null, v1: null, v2: null - }; - vertexCollision(path[0], path[1], mob); - vertexCollision(path[0], path[1], map); - vertexCollision(path[0], path[1], body); - if (best.dist2 != Infinity) { //if hitting something - path[path.length - 1] = { - x: best.x, - y: best.y - }; - } - - //draw beam - ctx.beginPath(); - ctx.moveTo(path[0].x, path[0].y); - ctx.lineTo(path[1].x, path[1].y); - ctx.strokeStyle = `rgba(100,0,180,0.7)`; - ctx.lineWidth = this.charge * 1 - ctx.setLineDash([10, 20]); - ctx.stroke(); - ctx.setLineDash([0, 0]); - - //draw magnetic field - const X = mech.pos.x - const Y = mech.pos.y - const unitVector = Vector.normalise(Vector.sub(game.mouseInGame, mech.pos)) - const unitVectorPerp = Vector.perp(unitVector) - - function magField(mag, arc) { - ctx.moveTo(X, Y); - ctx.bezierCurveTo( - X + unitVector.x * mag, Y + unitVector.y * mag, - X + unitVector.x * mag + unitVectorPerp.x * arc, Y + unitVector.y * mag + unitVectorPerp.y * arc, - X + unitVectorPerp.x * arc, Y + unitVectorPerp.y * arc) - ctx.bezierCurveTo( - X - unitVector.x * mag + unitVectorPerp.x * arc, Y - unitVector.y * mag + unitVectorPerp.y * arc, - X - unitVector.x * mag, Y - unitVector.y * mag, - X, Y) - } - ctx.fillStyle = `rgba(50,0,100,0.05)`; - for (let i = 3; i < 7; i++) { - const MAG = 8 * i * i * this.charge * (0.93 + 0.07 * Math.random()) - const ARC = 6 * i * i * this.charge * (0.93 + 0.07 * Math.random()) - ctx.beginPath(); - magField(MAG, ARC) - magField(MAG, -ARC) - ctx.fill(); - } - } - } - } - } - }, - { - name: "laser", - description: "emit a beam of collimated coherent light
drains energy instead of ammunition", - ammo: 0, - ammoPack: Infinity, - have: false, - nextFireCycle: 0, //use to remember how longs its been since last fire, used to reset count - fire() { - - }, - fireLaser() { - if (mech.energy < mod.laserFieldDrain) { - mech.fireCDcycle = mech.cycle + 100; // cool down if out of energy - } else { - mech.fireCDcycle = mech.cycle - mech.energy -= mech.fieldRegen + mod.laserFieldDrain * mod.isLaserDiode - if (mod.wideLaser) { - ctx.strokeStyle = "#f00"; - ctx.lineWidth = 8 - ctx.globalAlpha = 0.5; - ctx.beginPath(); - - const off = 7.5 - const dmg = 0.55 * mod.laserDamage // 3.5 * 0.55 = 200% more damage - const where = { - x: mech.pos.x + 20 * Math.cos(mech.angle), - y: mech.pos.y + 20 * Math.sin(mech.angle) - } - b.laser(where, { - x: where.x + 3000 * Math.cos(mech.angle), - y: where.y + 3000 * Math.sin(mech.angle) - }, dmg, 0, true) - for (let i = 1; i < mod.wideLaser; i++) { - let whereOff = Vector.add(where, { - x: i * off * Math.cos(mech.angle + Math.PI / 2), - y: i * off * Math.sin(mech.angle + Math.PI / 2) - }) - b.laser(whereOff, { - x: whereOff.x + 3000 * Math.cos(mech.angle), - y: whereOff.y + 3000 * Math.sin(mech.angle) - }, dmg, 0, true) - whereOff = Vector.add(where, { - x: i * off * Math.cos(mech.angle - Math.PI / 2), - y: i * off * Math.sin(mech.angle - Math.PI / 2) - }) - b.laser(whereOff, { - x: whereOff.x + 3000 * Math.cos(mech.angle), - y: whereOff.y + 3000 * Math.sin(mech.angle) - }, dmg, 0, true) - } - ctx.stroke(); - ctx.globalAlpha = 1; - } else if (mod.beamSplitter) { - let dmg = mod.laserDamage * 0.9 - const where = { - x: mech.pos.x + 20 * Math.cos(mech.angle), - y: mech.pos.y + 20 * Math.sin(mech.angle) - } - b.laser(where, { - x: where.x + 3000 * Math.cos(mech.angle), - y: where.y + 3000 * Math.sin(mech.angle) - }, 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) - }, 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) - }, dmg) - dmg *= 0.9 - } - } else { - b.laser() - } - } - }, - firePulse() { - mech.fireCDcycle = mech.cycle + Math.floor((mod.isPulseAim ? 25 : 50) * b.fireCD); // cool down - let energy = 0.27 * Math.min(mech.energy, 1.5) - mech.energy -= energy * mod.isLaserDiode - - if (mod.beamSplitter) { - energy *= 0.66 - b.pulse(energy, mech.angle) - for (let i = 1; i < 1 + mod.beamSplitter; i++) { - energy *= 0.9 - b.pulse(energy, mech.angle - i * 0.27) - b.pulse(energy, mech.angle + i * 0.27) - } - } else { - b.pulse(energy, mech.angle) - } - }, - }, - ], - pulse(energy, angle = mech.angle) { - let best; - let explosionRange = 1560 * energy - let range = 3000 - const path = [{ - x: mech.pos.x + 20 * Math.cos(angle), - y: mech.pos.y + 20 * Math.sin(angle) - }, - { - x: mech.pos.x + range * Math.cos(angle), - y: mech.pos.y + range * Math.sin(angle) - } - ]; - const vertexCollision = function (v1, v1End, domain) { - for (let i = 0; i < domain.length; ++i) { - let vertices = domain[i].vertices; - const len = vertices.length - 1; - for (let j = 0; j < len; j++) { - results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[j], - v2: vertices[j + 1] - }; - } - } - } - results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[0], - v2: vertices[len] }; - } - } - } - }; - //check for collisions - best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - if (mod.isPulseAim) { //find mobs in line of sight - let dist = 2200 - for (let i = 0, len = mob.length; i < len; i++) { - const newDist = Vector.magnitude(Vector.sub(path[0], mob[i].position)) - if (explosionRange < newDist && - newDist < dist && - Matter.Query.ray(map, path[0], mob[i].position).length === 0 && - Matter.Query.ray(body, path[0], mob[i].position).length === 0) { - dist = newDist - best.who = mob[i] - path[path.length - 1] = mob[i].position - } - } - } - if (!best.who) { - vertexCollision(path[0], path[1], mob); - vertexCollision(path[0], path[1], map); - vertexCollision(path[0], path[1], body); - if (best.dist2 != Infinity) { //if hitting something - path[path.length - 1] = { - x: best.x, - y: best.y + vertexCollision(path[path.length - 2], path[path.length - 1], mob); + vertexCollision(path[path.length - 2], path[path.length - 1], map); + vertexCollision(path[path.length - 2], path[path.length - 1], body); + }; + const laserHitMob = function() { + if (best.who.alive) { + best.who.damage(damage); + best.who.locatePlayer(); + game.drawList.push({ //add dmg to draw queue + x: path[path.length - 1].x, + y: path[path.length - 1].y, + radius: Math.sqrt(damage) * 100, + color: "rgba(255,0,0,0.5)", + time: game.drawTime + }); + } + // ctx.fillStyle = color; //draw mob damage circle + // ctx.beginPath(); + // ctx.arc(path[path.length - 1].x, path[path.length - 1].y, Math.sqrt(damage) * 100, 0, 2 * Math.PI); + // ctx.fill(); + }; + const reflection = function() { // https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector + const n = Vector.perp(Vector.normalise(Vector.sub(best.v1, best.v2))); + const d = Vector.sub(path[path.length - 1], path[path.length - 2]); + const nn = Vector.mult(n, 2 * Vector.dot(d, n)); + const r = Vector.normalise(Vector.sub(d, nn)); + path[path.length] = Vector.add(Vector.mult(r, 3000), path[path.length - 1]); }; - } - } - if (best.who) b.explosion(path[1], explosionRange, true) - if (mod.isPulseStun) { - const range = 100 + 2000 * energy - for (let i = 0, len = mob.length; i < len; ++i) { - if (mob[i].alive && !mob[i].isShielded) { - dist = Vector.magnitude(Vector.sub(path[1], mob[i].position)) - mob[i].radius; - if (dist < range) mobs.statusStun(mob[i], 30 + Math.floor(energy * 60)) + checkForCollisions(); + let lastBestOdd + let lastBestEven = best.who //used in hack below + if (best.dist2 !== Infinity) { //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + laserHitMob(); + for (let i = 0; i < reflections; i++) { + reflection(); + checkForCollisions(); + if (best.dist2 !== Infinity) { //if hitting something + lastReflection = best + + path[path.length - 1] = { + x: best.x, + y: best.y + }; + damage *= reflectivity + laserHitMob(); + //I'm not clear on how this works, but it gets ride of a bug where the laser reflects inside a block, often vertically. + //I think it checks to see if the laser is reflecting off a different part of the same block, if it is "inside" a block + if (i % 2) { + if (lastBestOdd === best.who) break + } else { + lastBestOdd = best.who + if (lastBestEven === best.who) break + } + } else { + break + } + } } - } - } - //draw laser beam - ctx.beginPath(); - ctx.moveTo(path[0].x, path[0].y); - ctx.lineTo(path[1].x, path[1].y); - ctx.strokeStyle = "rgba(255,0,0,0.13)" - ctx.lineWidth = 60 * energy / 0.2 - ctx.stroke(); - ctx.strokeStyle = "rgba(255,0,0,0.2)" - ctx.lineWidth = 18 - ctx.stroke(); - ctx.strokeStyle = "#f00"; - ctx.lineWidth = 4 - ctx.stroke(); + if (isThickBeam) { + for (let i = 1, len = path.length; i < len; ++i) { + ctx.moveTo(path[i - 1].x, path[i - 1].y); + ctx.lineTo(path[i].x, path[i].y); + } + } else { + ctx.strokeStyle = color; + ctx.lineWidth = 2 + ctx.lineDashOffset = 300 * Math.random() + ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]); + for (let i = 1, len = path.length; i < len; ++i) { + ctx.beginPath(); + ctx.moveTo(path[i - 1].x, path[i - 1].y); + ctx.lineTo(path[i].x, path[i].y); + ctx.stroke(); + ctx.globalAlpha *= reflectivity; //reflections are less intense + } + ctx.setLineDash([0, 0]); + ctx.globalAlpha = 1; + } + }, + mine(where, velocity, angle = 0, isAmmoBack = false) { + const bIndex = bullet.length; + bullet[bIndex] = Bodies.rectangle(where.x, where.y, 45, 16, { + angle: angle, + friction: 1, + frictionStatic: 1, + frictionAir: 0, + restitution: 0, + dmg: 0, //damage done in addition to the damage from momentum + classType: "bullet", + bulletType: "mine", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield | cat.bullet + }, + minDmgSpeed: 5, + stillCount: 0, + isArmed: false, + endCycle: Infinity, + lookFrequency: 0, + range: 700, + beforeDmg() {}, + do() { + this.force.y += this.mass * 0.002; //extra gravity + let collide = Matter.Query.collides(this, map) //check if collides with map + if (collide.length > 0) { + for (let i = 0; i < collide.length; i++) { + if (collide[i].bodyA.collisionFilter.category === cat.map) { // || collide[i].bodyB.collisionFilter.category === cat.map) { + const angle = Vector.angle(collide[i].normal, { + x: 1, + y: 0 + }) + Matter.Body.setAngle(this, Math.atan2(collide[i].tangent.y, collide[i].tangent.x)) + //move until touching map again after rotation + for (let j = 0; j < 10; j++) { + if (Matter.Query.collides(this, map).length > 0) { //touching map + if (angle > -0.2 || angle < -1.5) { //don't stick to level ground + Matter.Body.setStatic(this, true) //don't set to static if not touching map + this.collisionFilter.mask = cat.map | cat.bullet + } else { + Matter.Body.setVelocity(this, { + x: 0, + y: 0 + }); + Matter.Body.setAngularVelocity(this, 0) + } + this.arm(); - //draw little dots along the laser path - const sub = Vector.sub(path[1], path[0]) - const mag = Vector.magnitude(sub) - for (let i = 0, len = Math.floor(mag * 0.03 * energy / 0.2); i < len; i++) { - const dist = Math.random() - game.drawList.push({ - x: path[0].x + sub.x * dist + 13 * (Math.random() - 0.5), - y: path[0].y + sub.y * dist + 13 * (Math.random() - 0.5), - radius: 1 + 4 * Math.random(), - color: "rgba(255,0,0,0.5)", - time: Math.floor(2 + 33 * Math.random() * Math.random()) - }); + //sometimes the mine can't attach to map and it just needs to be reset + const that = this + setTimeout(function() { + if (Matter.Query.collides(that, map).length === 0 || Matter.Query.point(map, that.position).length > 0) { + // console.log(that) + that.endCycle = 0 // if not touching map explode + that.isArmed = false + b.mine(that.position, that.velocity, that.angle) + } + }, 100, that); + break + } + //move until you are touching the wall + Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(collide[i].normal, 2))) + } + break + } + } + } else { + if (this.speed < 1 && this.angularSpeed < 0.01 && !mech.isBodiesAsleep) { + this.stillCount++ + } + } + if (this.stillCount > 25) this.arm(); + }, + arm() { + this.lookFrequency = game.cycle + 60 + this.do = function() { //overwrite the do method for this bullet + this.force.y += this.mass * 0.002; //extra gravity + + if (game.cycle > this.lookFrequency) { + this.isArmed = true + this.lookFrequency = 50 + Math.floor(27 * Math.random()) + game.drawList.push({ + //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: 10, + color: "#f00", + time: 4 + }); + this.do = function() { //overwrite the do method for this bullet + this.force.y += this.mass * 0.002; //extra gravity + if (!(game.cycle % this.lookFrequency)) { //find mob targets + for (let i = 0, len = mob.length; i < len; ++i) { + if (Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)) < 500000 && + mob[i].dropPowerUp && + Matter.Query.ray(map, this.position, mob[i].position).length === 0 && + Matter.Query.ray(body, this.position, mob[i].position).length === 0) { + this.endCycle = 0 //end life if mob is near and visible + if (Math.random() < 0.8) isAmmoBack = false; //20% chance to get ammo back after detonation + } + } + } + } + } + } + }, + onEnd() { + if (this.isArmed) { + b.targetedNail(this.position, 15) + } + if (isAmmoBack) { //get ammo back from mod.isMineAmmoBack + for (i = 0, len = b.guns.length; i < len; i++) { //find which gun + if (b.guns[i].name === "mine") { + b.guns[i].ammo++ + game.updateGunHUD(); + break; + } + } + } + } + }); + bullet[bIndex].torque += bullet[bIndex].inertia * 0.0002 * (0.5 - Math.random()) + Matter.Body.setVelocity(bullet[bIndex], velocity); + World.add(engine.world, bullet[bIndex]); //add bullet to world + }, + spore(where, isFreeze = mod.isSporeFreeze) { //used with the mod upgrade in mob.death() + const bIndex = bullet.length; + const side = 4; + bullet[bIndex] = Bodies.polygon(where.x, where.y, 4, side, { + // density: 0.0015, //frictionAir: 0.01, + inertia: Infinity, + isFreeze: isFreeze, + restitution: 0.5, + angle: Math.random() * 2 * Math.PI, + friction: 0, + frictionAir: 0.025, + thrust: (mod.isFastSpores ? 0.001 : 0.0004) * (1 + 0.3 * (Math.random() - 0.5)), + dmg: mod.isMutualism ? 6 : 3, //2x bonus damage from mod.isMutualism + lookFrequency: 97 + Math.floor(117 * Math.random()), + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.mob | cat.mobBullet | cat.mobShield //no collide with body + }, + endCycle: game.cycle + Math.floor((540 + Math.floor(Math.random() * 360)) * mod.isBulletsLastLonger), + minDmgSpeed: 0, + playerOffPosition: { //used when following player to keep spores separate + x: 100 * (Math.random() - 0.5), + y: 100 * (Math.random() - 0.5) + }, + beforeDmg(who) { + this.endCycle = 0; //bullet ends cycle after doing damage + if (this.isFreeze) mobs.statusSlow(who, 60) + }, + onEnd() { + if (mod.isMutualism && this.isMutualismActive && !mod.isEnergyHealth) { + mech.health += 0.01 + if (mech.health > mech.maxHealth) mech.health = mech.maxHealth; + mech.displayHealth(); + } + }, + do() { + if (this.lockedOn && this.lockedOn.alive) { + this.force = Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), this.mass * this.thrust) + } else { + if (!(game.cycle % this.lookFrequency)) { //find mob targets + this.closestTarget = null; + this.lockedOn = null; + let closeDist = Infinity; + for (let i = 0, len = mob.length; i < len; ++i) { + if (mob[i].dropPowerUp && Matter.Query.ray(map, this.position, mob[i].position).length === 0) { + const targetVector = Vector.sub(this.position, mob[i].position) + const dist = Vector.magnitude(targetVector) * (Math.random() + 0.5); + if (dist < closeDist) { + this.closestTarget = mob[i].position; + closeDist = dist; + this.lockedOn = mob[i] + if (0.3 > Math.random()) break //doesn't always target the closest mob + } + } + } + } + if (mod.isSporeFollow && this.lockedOn === null) { //move towards player + //checking for null means that the spores don't go after the player until it has looked and not found a target + const dx = this.position.x - mech.pos.x; + const dy = this.position.y - mech.pos.y; + if (dx * dx + dy * dy > 10000) { + this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, Vector.add(this.playerOffPosition, this.position))), this.mass * this.thrust) + } + } else { + this.force.y += this.mass * 0.0001; //gravity + } + + } + + // if (!this.lockedOn && !(game.cycle % this.lookFrequency)) { //find mob targets + // this.closestTarget = null; + // this.lockedOn = null; + // let closeDist = Infinity; + // for (let i = 0, len = mob.length; i < len; ++i) { + // if (mob[i].dropPowerUp && Matter.Query.ray(map, this.position, mob[i].position).length === 0) { + // // Matter.Query.ray(body, this.position, mob[i].position).length === 0 + // const targetVector = Vector.sub(this.position, mob[i].position) + // const dist = Vector.magnitude(targetVector); + // if (dist < closeDist) { + // this.closestTarget = mob[i].position; + // closeDist = dist; + // this.lockedOn = mob[i] //Vector.normalise(targetVector); + // if (0.3 > Math.random()) break //doesn't always target the closest mob + // } + // } + // } + // } + // if (this.lockedOn && this.lockedOn.alive) { //accelerate towards mobs + // this.force = Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), this.mass * this.thrust) + // } else if (mod.isSporeFollow && this.lockedOn !== undefined) { //move towards player + // //checking for undefined means that the spores don't go after the player until it has looked and not found a target + // const dx = this.position.x - mech.pos.x; + // const dy = this.position.y - mech.pos.y; + // if (dx * dx + dy * dy > 10000) { + // this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, Vector.add(this.playerOffPosition, this.position))), this.mass * this.thrust) + // } + // // this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.thrust) + // } else { + // this.force.y += this.mass * 0.0001; //gravity + // } + }, + }); + const SPEED = 4 + 8 * Math.random(); + const ANGLE = 2 * Math.PI * Math.random() + Matter.Body.setVelocity(bullet[bIndex], { + x: SPEED * Math.cos(ANGLE), + y: SPEED * Math.sin(ANGLE) + }); + World.add(engine.world, bullet[bIndex]); //add bullet to world + + if (mod.isMutualism && mech.health > 0.02) { + mech.health -= 0.01 + mech.displayHealth(); + bullet[bIndex].isMutualismActive = true + } + }, + iceIX(speed = 0, spread = 2 * Math.PI) { + const me = bullet.length; + const THRUST = 0.004 + const dir = mech.angle + spread * (Math.random() - 0.5); + const RADIUS = 18 + bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 3, RADIUS, { + angle: dir - Math.PI, + inertia: Infinity, + friction: 0, + frictionAir: 0.10, + restitution: 0.3, + dmg: 0.15, //damage done in addition to the damage from momentum + lookFrequency: 10 + Math.floor(7 * Math.random()), + endCycle: game.cycle + 120 * mod.isBulletsLastLonger, //Math.floor((1200 + 420 * Math.random()) * mod.isBulletsLastLonger), + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield //self collide + }, + minDmgSpeed: 0, + lockedOn: null, + isFollowMouse: true, + beforeDmg(who) { + mobs.statusSlow(who, 60) + this.endCycle = game.cycle + if (mod.isHeavyWater) mobs.statusDoT(who, 0.15, 300) + if (mod.iceEnergy && !who.shield && !who.isShielded && who.dropPowerUp && who.alive) { + setTimeout(function() { + if (!who.alive) { + mech.energy += mod.iceEnergy * 0.66 * mech.maxEnergy + mech.addHealth(mod.iceEnergy * 0.04) + } + }, 10); + } + }, + onEnd() {}, + do() { + // this.force.y += this.mass * 0.0002; + //find mob targets + if (!(game.cycle % this.lookFrequency)) { + const scale = 1 - 0.09 / mod.isBulletsLastLonger //0.9 * mod.isBulletsLastLonger; + Matter.Body.scale(this, scale, scale); + this.lockedOn = null; + let closeDist = Infinity; + for (let i = 0, len = mob.length; i < len; ++i) { + if ( + mob[i].dropPowerUp && + Matter.Query.ray(map, this.position, mob[i].position).length === 0 && + Matter.Query.ray(body, this.position, mob[i].position).length === 0 + ) { + const TARGET_VECTOR = Vector.sub(this.position, mob[i].position) + const DIST = Vector.magnitude(TARGET_VECTOR); + if (DIST < closeDist) { + closeDist = DIST; + this.lockedOn = mob[i] + } + } + } + } + if (this.lockedOn) { //accelerate towards mobs + this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, this.lockedOn.position)), -this.mass * THRUST) + } else { + this.force = Vector.mult(Vector.normalise(this.velocity), this.mass * THRUST) + } + } + }) + + World.add(engine.world, bullet[me]); //add bullet to world + // Matter.Body.setAngularVelocity(bullet[me], 2 * (0.5 - Math.random())) //doesn't work due to high friction + Matter.Body.setVelocity(bullet[me], { + x: speed * Math.cos(dir), + y: speed * Math.sin(dir) + }); + // Matter.Body.setVelocity(bullet[me], { + // x: mech.Vx / 2 + speed * Math.cos(dir), + // y: mech.Vy / 2 + speed * Math.sin(dir) + // }); + }, + drone(speed = 1) { + const me = bullet.length; + const THRUST = mod.isFastDrones ? 0.0023 : 0.0015 + // const FRICTION = mod.isFastDrones ? 0.008 : 0.0005 + const dir = mech.angle + 0.4 * (Math.random() - 0.5); + const RADIUS = (4.5 + 3 * Math.random()) + bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle) + Math.random(), mech.pos.y + 30 * Math.sin(mech.angle) + Math.random(), 8, RADIUS, { + angle: dir, + inertia: Infinity, + friction: 0.05, + frictionAir: 0, + restitution: 1, + dmg: 0.24, //damage done in addition to the damage from momentum + lookFrequency: 80 + Math.floor(23 * Math.random()), + endCycle: game.cycle + Math.floor((1100 + 420 * Math.random()) * mod.isBulletsLastLonger), + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield //self collide + }, + minDmgSpeed: 0, + lockedOn: null, + isFollowMouse: true, + deathCycles: 110 + RADIUS * 5, + isImproved: false, + beforeDmg(who) { + //move away from target after hitting + const unit = Vector.mult(Vector.normalise(Vector.sub(this.position, who.position)), -20) + Matter.Body.setVelocity(this, { + x: unit.x, + y: unit.y + }); + + this.lockedOn = null + if (this.endCycle > game.cycle + this.deathCycles) { + this.endCycle -= 60 + if (game.cycle + this.deathCycles > this.endCycle) this.endCycle = game.cycle + this.deathCycles + } + }, + onEnd() {}, + do() { + if (game.cycle + this.deathCycles > this.endCycle) { //fall shrink and die + this.force.y += this.mass * 0.0012; + this.restitution = 0.2; + const scale = 0.99; + Matter.Body.scale(this, scale, scale); + } else { + this.force.y += this.mass * 0.0002; + //find mob targets + if (!(game.cycle % this.lookFrequency)) { + this.lockedOn = null; + let closeDist = Infinity; + for (let i = 0, len = mob.length; i < len; ++i) { + if ( + mob[i].dropPowerUp && + Matter.Query.ray(map, this.position, mob[i].position).length === 0 && + Matter.Query.ray(body, this.position, mob[i].position).length === 0 + ) { + const TARGET_VECTOR = Vector.sub(this.position, mob[i].position) + const DIST = Vector.magnitude(TARGET_VECTOR); + if (DIST < closeDist) { + closeDist = DIST; + this.lockedOn = mob[i] + } + } + } + if (!this.lockedOn && !mod.isArmorFromPowerUps && !this.isImproved) { //grab a power up + let closeDist = Infinity; + for (let i = 0, len = powerUp.length; i < len; ++i) { + if ( + (powerUp[i].name !== "heal" || mech.health < 0.9 * mech.maxHealth || mod.isDroneGrab) && + (powerUp[i].name !== "field" || !mod.isDeterminism) + ) { + //pick up nearby power ups + if (Vector.magnitudeSquared(Vector.sub(this.position, powerUp[i].position)) < 60000 && !game.isChoosing) { + powerUps.onPickUp(this.position); + powerUp[i].effect(); + Matter.World.remove(engine.world, powerUp[i]); + powerUp.splice(i, 1); + if (mod.isDroneGrab) { + this.isImproved = true; + const SCALE = 3 + Matter.Body.scale(this, SCALE, SCALE); + this.lookFrequency = 30; + this.endCycle += 2500 + this.frictionAir = 0 + } + break; + } + //look for power ups to lock onto + if ( + Matter.Query.ray(map, this.position, powerUp[i].position).length === 0 && + Matter.Query.ray(body, this.position, powerUp[i].position).length === 0 + ) { + const TARGET_VECTOR = Vector.sub(this.position, powerUp[i].position) + const DIST = Vector.magnitude(TARGET_VECTOR); + if (DIST < closeDist) { + closeDist = DIST; + this.lockedOn = powerUp[i] + } + } + } + } + } + } + if (this.lockedOn) { //accelerate towards mobs + this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, this.lockedOn.position)), -this.mass * THRUST) + } else { //accelerate towards mouse + this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, game.mouseInGame)), -this.mass * THRUST) + } + // speed cap instead of friction to give more agility + if (this.speed > 6) { + Matter.Body.setVelocity(this, { + x: this.velocity.x * 0.97, + y: this.velocity.y * 0.97 + }); + } + } + } + }) + World.add(engine.world, bullet[me]); //add bullet to world + Matter.Body.setVelocity(bullet[me], { + x: speed * Math.cos(dir), + y: speed * Math.sin(dir) + }); + }, + foam(position, velocity, radius) { + // radius *= Math.sqrt(mod.bulletSize) + const me = bullet.length; + bullet[me] = Bodies.polygon(position.x, position.y, 20, radius, { + // angle: 0, + density: 0.00005, // 0.001 is normal density + inertia: Infinity, + frictionAir: 0.003, + // friction: 0.2, + // restitution: 0.2, + dmg: mod.isFastFoam ? 0.02 : 0.0055, //damage done in addition to the damage from momentum + scale: 1 - 0.005 / mod.isBulletsLastLonger * (mod.isFastFoam ? 1.6 : 1), + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.mob | cat.mobBullet // cat.map | cat.body | cat.mob | cat.mobShield + }, + minDmgSpeed: 0, + endCycle: Infinity, + count: 0, + radius: radius, + target: null, + targetVertex: null, + targetRelativePosition: null, + beforeDmg(who) { + if (!this.target && who.alive) { + this.target = who; + if (who.radius < 20) { + this.targetRelativePosition = { + x: 0, + y: 0 + } //find relative position vector for zero mob rotation + } else if (Matter.Query.collides(this, [who]).length > 0) { + const normal = Matter.Query.collides(this, [who])[0].normal + this.targetRelativePosition = Vector.rotate(Vector.sub(Vector.sub(this.position, who.position), Vector.mult(normal, -this.radius)), -who.angle) //find relative position vector for zero mob rotation + } else { + this.targetRelativePosition = Vector.rotate(Vector.sub(this.position, who.position), -who.angle) //find relative position vector for zero mob rotation + } + this.collisionFilter.category = cat.body; + this.collisionFilter.mask = null; + + let bestVertexDistance = Infinity + let bestVertex = null + for (let i = 0; i < this.target.vertices.length; i++) { + const dist = Vector.magnitude(Vector.sub(this.position, this.target.vertices[i])); + if (dist < bestVertexDistance) { + bestVertex = i + bestVertexDistance = dist + } + } + this.targetVertex = bestVertex + } + }, + onEnd() {}, + do() { + if (!mech.isBodiesAsleep) { //if time dilation isn't active + if (this.count < 20) { + this.count++ + //grow + const SCALE = 1.06 + Matter.Body.scale(this, SCALE, SCALE); + this.radius *= SCALE; + } else { + //shrink + Matter.Body.scale(this, this.scale, this.scale); + this.radius *= this.scale; + if (this.radius < 8) this.endCycle = 0; + } + + if (this.target && this.target.alive) { //if stuck to a target + const rotate = Vector.rotate(this.targetRelativePosition, this.target.angle) //add in the mob's new angle to the relative position vector + if (this.target.isVerticesChange) { + Matter.Body.setPosition(this, this.target.vertices[this.targetVertex]) + } else { + Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.target.velocity), this.target.position)) + } + Matter.Body.setVelocity(this.target, Vector.mult(this.target.velocity, 0.9)) + Matter.Body.setAngularVelocity(this.target, this.target.angularVelocity * 0.9); + + // Matter.Body.setAngularVelocity(this.target, this.target.angularVelocity * 0.9) + if (this.target.isShielded) { + this.target.damage(b.dmgScale * this.dmg, true); //shield damage bypass + //shrink if mob is shielded + const SCALE = 1 - 0.018 / mod.isBulletsLastLonger + Matter.Body.scale(this, SCALE, SCALE); + this.radius *= SCALE; + } else { + this.target.damage(b.dmgScale * this.dmg); + } + } else if (this.target !== null) { //look for a new target + this.target = null + this.collisionFilter.category = cat.bullet; + this.collisionFilter.mask = cat.mob //| cat.mobShield //cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield + if (mod.isFoamGrowOnDeath && bullet.length < 300) { + let targets = [] + for (let i = 0, len = mob.length; i < len; i++) { + const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); + if (dist < 1000000) { + targets.push(mob[i]) + } + } + const radius = Math.min(this.radius * 0.5, 10) + for (let i = 0; i < 2; i++) { + if (targets.length - i > 0) { + const index = Math.floor(Math.random() * targets.length) + const speed = 10 + 10 * Math.random() + const velocity = Vector.mult(Vector.normalise(Vector.sub(targets[index].position, this.position)), speed) + b.foam(this.position, Vector.rotate(velocity, 0.5 * (Math.random() - 0.5)), radius) + } else { + b.foam(this.position, Vector.rotate({ + x: 15 + 10 * Math.random(), + y: 0 + }, 2 * Math.PI * Math.random()), radius) + } + } + } + } else if (Matter.Query.point(map, this.position).length > 0) { //slow when touching map or blocks + const slow = 0.85 + Matter.Body.setVelocity(this, { + x: this.velocity.x * slow, + y: this.velocity.y * slow + }); + const SCALE = 0.96 + Matter.Body.scale(this, SCALE, SCALE); + this.radius *= SCALE; + // } else if (Matter.Query.collides(this, body).length > 0) { + } else if (Matter.Query.point(body, this.position).length > 0) { + const slow = 0.9 + Matter.Body.setVelocity(this, { + x: this.velocity.x * slow, + y: this.velocity.y * slow + }); + const SCALE = 0.96 + Matter.Body.scale(this, SCALE, SCALE); + this.radius *= SCALE; + } else { + this.force.y += this.mass * 0.00008; //gravity + } + } + } + }); + World.add(engine.world, bullet[me]); //add bullet to world + Matter.Body.setVelocity(bullet[me], velocity); + }, + targetedNail(position, num = 1, speed = 50 + 10 * Math.random(), range = 1200) { + const targets = [] //target nearby mobs + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].dropPowerUp) { + const dist = Vector.magnitude(Vector.sub(position, mob[i].position)); + if (dist < range && + Matter.Query.ray(map, position, mob[i].position).length === 0 && + Matter.Query.ray(body, position, mob[i].position).length === 0) { + targets.push(Vector.add(mob[i].position, Vector.mult(mob[i].velocity, dist / 60))) //predict where the mob will be in a few cycles + } + } + } + for (let i = 0; i < num; i++) { + if (targets.length > 0) { // aim near a random target in array + const index = Math.floor(Math.random() * targets.length) + const SPREAD = 150 / targets.length + const WHERE = { + x: targets[index].x + SPREAD * (Math.random() - 0.5), + y: targets[index].y + SPREAD * (Math.random() - 0.5) + } + b.nail(position, Vector.mult(Vector.normalise(Vector.sub(WHERE, position)), speed), 1.1) + } else { // aim in random direction + const ANGLE = 2 * Math.PI * Math.random() + b.nail(position, { + x: speed * Math.cos(ANGLE), + y: speed * Math.sin(ANGLE) + }) + } + } + }, + nail(pos, velocity, dmg = 0) { + const me = bullet.length; + bullet[me] = Bodies.rectangle(pos.x, pos.y, 25 * mod.biggerNails, 2 * mod.biggerNails, b.fireAttributes(Math.atan2(velocity.y, velocity.x))); + Matter.Body.setVelocity(bullet[me], velocity); + World.add(engine.world, bullet[me]); //add bullet to world + bullet[me].endCycle = game.cycle + 60 + 18 * Math.random(); + bullet[me].dmg = dmg + bullet[me].beforeDmg = function(who) { //beforeDmg is rewritten with ice crystal mod + if (mod.isNailPoison) mobs.statusDoT(who, dmg * 0.22, 120) // one tick every 30 cycles + if (mod.isNailCrit && !who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.99) this.dmg *= 5 //crit if hit near center + }; + bullet[me].do = function() {}; + }, + // ************************************************************************************************** + // ************************************************************************************************** + // ******************************** Bots ********************************************* + // ************************************************************************************************** + // ************************************************************************************************** + respawnBots() { + for (let i = 0; i < mod.laserBotCount; i++) b.laserBot() + for (let i = 0; i < mod.nailBotCount; i++) b.nailBot() + for (let i = 0; i < mod.foamBotCount; i++) b.foamBot() + for (let i = 0; i < mod.boomBotCount; i++) b.boomBot() + for (let i = 0; i < mod.plasmaBotCount; i++) b.plasmaBot() + for (let i = 0; i < mod.orbitBotCount; i++) b.orbitBot() + if (mod.isIntangible && mech.isCloak) { + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType) bullet[i].collisionFilter.mask = cat.map | cat.bullet | cat.mobBullet | cat.mobShield + } + } + }, + randomBot(where = mech.pos, isKeep = true) { + if (Math.random() < 0.2) { + b.orbitBot(); + if (isKeep) mod.orbitBotCount++; + } else if (Math.random() < 0.25) { + b.nailBot(where) + if (isKeep) mod.nailBotCount++; + } else if (Math.random() < 0.33) { + b.laserBot(where) + if (isKeep) mod.laserBotCount++; + } else if (Math.random() < 0.5) { + b.foamBot(where) + if (isKeep) mod.foamBotCount++; + } else { + b.boomBot(where) + if (isKeep) mod.boomBotCount++; + } + }, + nailBot(position = mech.pos) { + const me = bullet.length; + const dir = mech.angle; + const RADIUS = (12 + 4 * Math.random()) + bullet[me] = Bodies.polygon(position.x, position.y, 4, RADIUS, { + isUpgraded: mod.isNailBotUpgrade, + botType: "nail", + angle: dir, + friction: 0, + frictionStatic: 0, + frictionAir: 0.05, + restitution: 0.6 * (1 + 0.5 * Math.random()), + dmg: 0, // 0.14 //damage done in addition to the damage from momentum + minDmgSpeed: 2, + // lookFrequency: 56 + Math.floor(17 * Math.random()) - isUpgraded * 20, + lastLookCycle: game.cycle + 60 * Math.random(), + acceleration: 0.005 * (1 + 0.5 * Math.random()), + range: 70 * (1 + 0.3 * Math.random()), + endCycle: Infinity, + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield + }, + lockedOn: null, + beforeDmg() { + this.lockedOn = null + }, + onEnd() {}, + do() { + if (this.lastLookCycle < game.cycle && !mech.isCloak) { + this.lastLookCycle = game.cycle + 80 - this.isUpgraded * 65 + let target + for (let i = 0, len = mob.length; i < len; i++) { + const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); + if (dist < 3000000 && //1400*1400 + Matter.Query.ray(map, this.position, mob[i].position).length === 0 && + Matter.Query.ray(body, this.position, mob[i].position).length === 0) { + target = Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist) / 60)) + const SPEED = 50 + b.nail(this.position, Vector.mult(Vector.normalise(Vector.sub(target, this.position)), SPEED), 0.4) + break; + } + } + } + const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) + if (distanceToPlayer > this.range) { //if far away move towards player + this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) + } else { //close to player + Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity + } + } + }) + World.add(engine.world, bullet[me]); //add bullet to world + }, + foamBot(position = mech.pos) { + const me = bullet.length; + const dir = mech.angle; + const RADIUS = (10 + 5 * Math.random()) + bullet[me] = Bodies.polygon(position.x, position.y, 6, RADIUS, { + isUpgraded: mod.isFoamBotUpgrade, + botType: "foam", + angle: dir, + friction: 0, + frictionStatic: 0, + frictionAir: 0.05, + restitution: 0.6 * (1 + 0.5 * Math.random()), + dmg: 0, // 0.14 //damage done in addition to the damage from momentum + minDmgSpeed: 2, + lookFrequency: 60 + Math.floor(17 * Math.random()) - 10 * mod.isFoamBotUpgrade, + cd: 0, + delay: 100, + acceleration: 0.005 * (1 + 0.5 * Math.random()), + range: 70 * (1 + 0.3 * Math.random()), + endCycle: Infinity, + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield + }, + lockedOn: null, + beforeDmg() { + this.lockedOn = null + }, + onEnd() {}, + do() { + if (this.cd < game.cycle && !(game.cycle % this.lookFrequency) && !mech.isCloak) { + let target + for (let i = 0, len = mob.length; i < len; i++) { + const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); + if (dist < 1000000 && Matter.Query.ray(map, this.position, mob[i].position).length === 0) { + this.cd = game.cycle + this.delay; + target = Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist) / 60)) + const radius = 6 + 7 * Math.random() + const SPEED = 29 - radius * 0.5; //(mech.crouch ? 32 : 20) - radius * 0.7; + const velocity = Vector.mult(Vector.normalise(Vector.sub(target, this.position)), SPEED) + b.foam(this.position, velocity, radius + 9 * this.isUpgraded) + break; + } + } + } + const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) + if (distanceToPlayer > this.range) { //if far away move towards player + this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) + } else { //close to player + Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity + } + } + }) + World.add(engine.world, bullet[me]); //add bullet to world + }, + laserBot(position = mech.pos) { + const me = bullet.length; + const dir = mech.angle; + const RADIUS = (14 + 6 * Math.random()) + bullet[me] = Bodies.polygon(position.x, position.y, 3, RADIUS, { + isUpgraded: mod.isLaserBotUpgrade, + botType: "laser", + angle: dir, + friction: 0, + frictionStatic: 0, + frictionAir: 0.008 * (1 + 0.3 * Math.random()), + restitution: 0.5 * (1 + 0.5 * Math.random()), + dmg: 0, // 0.14 //damage done in addition to the damage from momentum + minDmgSpeed: 2, + lookFrequency: 40 + Math.floor(7 * Math.random()), + acceleration: 0.0015 * (1 + 0.3 * Math.random()), + range: 700 * (1 + 0.1 * Math.random()) + 250 * mod.isLaserBotUpgrade, + followRange: 150 + Math.floor(30 * Math.random()), + offPlayer: { + x: 0, + y: 0, + }, + endCycle: Infinity, + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield + }, + lockedOn: null, + beforeDmg() { + this.lockedOn = null + }, + onEnd() {}, + do() { + const playerPos = Vector.add(Vector.add(this.offPlayer, mech.pos), Vector.mult(player.velocity, 20)) //also include an offset unique to this bot to keep many bots spread out + const farAway = Math.max(0, (Vector.magnitude(Vector.sub(this.position, playerPos))) / this.followRange) //linear bounding well + const mag = Math.min(farAway, 4) * this.mass * this.acceleration + this.force = Vector.mult(Vector.normalise(Vector.sub(playerPos, this.position)), mag) + //manual friction to not lose rotational velocity + Matter.Body.setVelocity(this, { + x: this.velocity.x * 0.95, + y: this.velocity.y * 0.95 + }); + //find targets + if (!(game.cycle % this.lookFrequency)) { + this.lockedOn = null; + if (!mech.isCloak) { + let closeDist = this.range; + for (let i = 0, len = mob.length; i < len; ++i) { + const DIST = Vector.magnitude(Vector.sub(this.vertices[0], mob[i].position)); + if (DIST - mob[i].radius < closeDist && + !mob[i].isShielded && + Matter.Query.ray(map, this.vertices[0], mob[i].position).length === 0 && + Matter.Query.ray(body, this.vertices[0], mob[i].position).length === 0) { + closeDist = DIST; + this.lockedOn = mob[i] + } + } + } + //randomize position relative to player + if (Math.random() < 0.15) { + this.offPlayer = { + x: 120 * (Math.random() - 0.5), + y: 120 * (Math.random() - 0.5) - 20, + } + } + } + //hit target with laser + if (this.lockedOn && this.lockedOn.alive && mech.energy > 0.15) { + mech.energy -= 0.0012 * mod.isLaserDiode + // const sub = Vector.sub(this.lockedOn.position, this.vertices[0]) + // const angle = Math.atan2(sub.y, sub.x); + b.laser(this.vertices[0], this.lockedOn.position, b.dmgScale * (0.06 + 0.1 * this.isUpgraded)) + + // //make sure you can still see vertex + // const DIST = Vector.magnitude(Vector.sub(this.vertices[0], this.lockedOn.position)); + // if (DIST - this.lockedOn.radius < this.range + 150 && + // Matter.Query.ray(map, this.vertices[0], this.lockedOn.position).length === 0 && + // Matter.Query.ray(body, this.vertices[0], this.lockedOn.position).length === 0) { + // //move towards the target + // this.force = Vector.add(this.force, Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), 0.0013)) + // //find the closest vertex + // let bestVertexDistance = Infinity + // let bestVertex = null + // for (let i = 0; i < this.lockedOn.vertices.length; i++) { + // const dist = Vector.magnitude(Vector.sub(this.vertices[0], this.lockedOn.vertices[i])); + // if (dist < bestVertexDistance) { + // bestVertex = i + // bestVertexDistance = dist + // } + // } + // const dmg = b.dmgScale * (0.06 + 0.08 * this.isUpgraded); + // this.lockedOn.damage(dmg); + // this.lockedOn.locatePlayer(); + + // ctx.beginPath(); //draw laser + // ctx.moveTo(this.vertices[0].x, this.vertices[0].y); + // ctx.lineTo(this.lockedOn.vertices[bestVertex].x, this.lockedOn.vertices[bestVertex].y); + // ctx.strokeStyle = "#f00"; + // ctx.lineWidth = "2" + // ctx.lineDashOffset = 300 * Math.random() + // ctx.setLineDash([50 + 100 * Math.random(), 100 * Math.random()]); + // ctx.stroke(); + // ctx.setLineDash([0, 0]); + // ctx.beginPath(); + // ctx.arc(this.lockedOn.vertices[bestVertex].x, this.lockedOn.vertices[bestVertex].y, Math.sqrt(dmg) * 100, 0, 2 * Math.PI); + // ctx.fillStyle = "#f00"; + // ctx.fill(); + // } + } + } + }) + World.add(engine.world, bullet[me]); //add bullet to world + }, + boomBot(position = mech.pos) { + const me = bullet.length; + const dir = mech.angle; + const RADIUS = (7 + 2 * Math.random()) + bullet[me] = Bodies.polygon(position.x, position.y, 4, RADIUS, { + isUpgraded: mod.isBoomBotUpgrade, + botType: "boom", + angle: dir, + friction: 0, + frictionStatic: 0, + frictionAir: 0.05, + restitution: 1, + dmg: 0, + minDmgSpeed: 0, + lookFrequency: 43 + Math.floor(7 * Math.random()), + acceleration: 0.005 * (1 + 0.5 * Math.random()), + range: 500 * (1 + 0.1 * Math.random()) + 250 * mod.isBoomBotUpgrade, + endCycle: Infinity, + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield + }, + lockedOn: null, + explode: 0, + beforeDmg() { + if (this.lockedOn) { + const explosionRadius = Math.min(170 + 170 * this.isUpgraded, Vector.magnitude(Vector.sub(this.position, mech.pos)) - 30) + if (explosionRadius > 60) { + this.explode = explosionRadius + // + //push away from player, because normal explosion knock doesn't do much + // const sub = Vector.sub(this.lockedOn.position, mech.pos) + // mag = Math.min(35, 20 / Math.sqrt(this.lockedOn.mass)) + // Matter.Body.setVelocity(this.lockedOn, Vector.mult(Vector.normalise(sub), mag)) + } + this.lockedOn = null //lose target so bot returns to player + } + }, + onEnd() {}, + do() { + if (this.explode) { + b.explosion(this.position, this.explode); //makes bullet do explosive damage at end + this.explode = 0; + } + const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) + if (distanceToPlayer > 100) { //if far away move towards player + this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) + } else if (distanceToPlayer < 250) { //close to player + Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity + //find targets + if (!(game.cycle % this.lookFrequency) && !mech.isCloak) { + this.lockedOn = null; + let closeDist = this.range; + for (let i = 0, len = mob.length; i < len; ++i) { + const DIST = Vector.magnitude(Vector.sub(this.position, mob[i].position)) - mob[i].radius; + if (DIST < closeDist && mob[i].dropPowerUp && + Matter.Query.ray(map, this.position, mob[i].position).length === 0 && + Matter.Query.ray(body, this.position, mob[i].position).length === 0) { + closeDist = DIST; + this.lockedOn = mob[i] + } + } + } + } + //punch target + if (this.lockedOn && this.lockedOn.alive && !mech.isCloak) { + const DIST = Vector.magnitude(Vector.sub(this.vertices[0], this.lockedOn.position)); + if (DIST - this.lockedOn.radius < this.range && + Matter.Query.ray(map, this.position, this.lockedOn.position).length === 0) { + //move towards the target + this.force = Vector.add(this.force, Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), 0.012 * this.mass)) + } + } + } + }) + World.add(engine.world, bullet[me]); //add bullet to world + }, + plasmaBot(position = mech.pos) { + const me = bullet.length; + const dir = mech.angle; + const RADIUS = 21 + bullet[me] = Bodies.polygon(position.x, position.y, 5, RADIUS, { + botType: "plasma", + angle: dir, + friction: 0, + frictionStatic: 0, + frictionAir: 0.05, + restitution: 1, + dmg: 0, // 0.14 //damage done in addition to the damage from momentum + minDmgSpeed: 2, + lookFrequency: 25, + cd: 0, + acceleration: 0.009, + endCycle: Infinity, + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield + }, + lockedOn: null, + beforeDmg() { + this.lockedOn = null + }, + onEnd() {}, + do() { + const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) + if (distanceToPlayer > 150) { //if far away move towards player + this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) + } + Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity + //find closest + if (!(game.cycle % this.lookFrequency)) { + this.lockedOn = null; + if (!mech.isCloak) { + let closeDist = mod.isPlasmaRange * 1000; + for (let i = 0, len = mob.length; i < len; ++i) { + const DIST = Vector.magnitude(Vector.sub(this.position, mob[i].position)) - mob[i].radius; + if (DIST < closeDist && + Matter.Query.ray(map, this.position, mob[i].position).length === 0 && + Matter.Query.ray(body, this.position, mob[i].position).length === 0) { + closeDist = DIST; + this.lockedOn = mob[i] + } + } + } + } + //fire plasma at target + if (this.lockedOn && this.lockedOn.alive && mech.fieldCDcycle < mech.cycle) { + const sub = Vector.sub(this.lockedOn.position, this.position) + const DIST = Vector.magnitude(sub); + const unit = Vector.normalise(sub) + const DRAIN = 0.0012 + if (DIST < mod.isPlasmaRange * 450 && mech.energy > DRAIN) { + mech.energy -= DRAIN; + if (mech.energy < 0) { + mech.fieldCDcycle = mech.cycle + 120; + mech.energy = 0; + } + //calculate laser collision + let best; + let range = mod.isPlasmaRange * (120 + 300 * Math.sqrt(Math.random())) + const path = [{ + x: this.position.x, + y: this.position.y + }, + { + x: this.position.x + range * unit.x, + y: this.position.y + range * unit.y + } + ]; + const vertexCollision = function(v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + }; + //check for collisions + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + vertexCollision(path[0], path[1], mob); + vertexCollision(path[0], path[1], map); + vertexCollision(path[0], path[1], body); + if (best.dist2 != Infinity) { //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + if (best.who.alive) { + const dmg = 0.8 * b.dmgScale; //********** SCALE DAMAGE HERE ********************* + best.who.damage(dmg); + best.who.locatePlayer(); + //push mobs away + const force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, path[1])), -0.01 * Math.min(5, best.who.mass)) + Matter.Body.applyForce(best.who, path[1], force) + Matter.Body.setVelocity(best.who, { //friction + x: best.who.velocity.x * 0.7, + y: best.who.velocity.y * 0.7 + }); + //draw mob damage circle + game.drawList.push({ + x: path[1].x, + y: path[1].y, + radius: Math.sqrt(dmg) * 50, + color: "rgba(255,0,255,0.2)", + time: game.drawTime * 4 + }); + } else if (!best.who.isStatic) { + //push blocks away + const force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, path[1])), -0.007 * Math.sqrt(Math.sqrt(best.who.mass))) + Matter.Body.applyForce(best.who, path[1], force) + } + } + //draw blowtorch laser beam + ctx.strokeStyle = "rgba(255,0,255,0.1)" + ctx.lineWidth = 14 + ctx.beginPath(); + ctx.moveTo(path[0].x, path[0].y); + ctx.lineTo(path[1].x, path[1].y); + ctx.stroke(); + ctx.strokeStyle = "#f0f"; + ctx.lineWidth = 2 + ctx.stroke(); + //draw electricity + let x = this.position.x + 20 * unit.x; + let y = this.position.y + 20 * unit.y; + ctx.beginPath(); + ctx.moveTo(x, y); + const step = Vector.magnitude(Vector.sub(path[0], path[1])) / 5 + for (let i = 0; i < 4; i++) { + x += step * (unit.x + 1.5 * (Math.random() - 0.5)) + y += step * (unit.y + 1.5 * (Math.random() - 0.5)) + ctx.lineTo(x, y); + } + ctx.lineWidth = 2 * Math.random(); + ctx.stroke(); + } + } + } + }) + World.add(engine.world, bullet[me]); //add bullet to world + }, + orbitBot(position = mech.pos) { + const me = bullet.length; + bullet[me] = Bodies.polygon(position.x, position.y, 9, 12, { + isUpgraded: mod.isOrbitBotUpgrade, + botType: "orbit", + friction: 0, + frictionStatic: 0, + frictionAir: 1, + isStatic: true, + isSensor: true, + restitution: 0, + dmg: 0, // 0.14 //damage done in addition to the damage from momentum + minDmgSpeed: 0, + endCycle: Infinity, + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: 0 //cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield + }, + beforeDmg() {}, + onEnd() { + //reorder orbital bot positions around a circle + let totalOrbitalBots = 0 + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType === 'orbit' && bullet[i] !== this) totalOrbitalBots++ + } + let index = 0 + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType === 'orbit' && bullet[i] !== this) { + bullet[i].phase = (index / totalOrbitalBots) * 2 * Math.PI + index++ + } + } + }, + range: 190 + 60 * mod.isOrbitBotUpgrade, //range is set in bot upgrade too! //150 + (80 + 100 * mod.isOrbitBotUpgrade) * Math.random(), // + 5 * mod.orbitBotCount, + orbitalSpeed: 0, + phase: 2 * Math.PI * Math.random(), + do() { + //check for damage + if (!mech.isCloak && !mech.isBodiesAsleep) { //if time dilation isn't active + // q = Matter.Query.point(mob, this.position) + // q = Matter.Query.collides(this, mob) + const size = 33 + q = Matter.Query.region(mob, { + min: { + x: this.position.x - size, + y: this.position.y - size + }, + max: { + x: this.position.x + size, + y: this.position.y + size + } + }) + for (let i = 0; i < q.length; i++) { + mobs.statusStun(q[i], 180) + const dmg = 0.5 * b.dmgScale * (this.isUpgraded ? 2 : 1) * (mod.isCrit ? 4 : 1) + q[i].damage(dmg); + q[i].foundPlayer(); + game.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: Math.log(2 * dmg + 1.1) * 40, + color: 'rgba(0,0,0,0.4)', + time: game.drawTime + }); + } + } + //orbit player + const time = game.cycle * this.orbitalSpeed + this.phase + const orbit = { + x: Math.cos(time), + y: Math.sin(time) //*1.1 + } + Matter.Body.setPosition(this, Vector.add(mech.pos, Vector.mult(orbit, this.range))) //bullets move with player + } + }) + // bullet[me].orbitalSpeed = Math.sqrt(0.7 / bullet[me].range) + bullet[me].orbitalSpeed = Math.sqrt(0.25 / bullet[me].range) //also set in bot upgrade too! + // bullet[me].phase = (index / mod.orbitBotCount) * 2 * Math.PI + World.add(engine.world, bullet[me]); //add bullet to world + + //reorder orbital bot positions around a circle + let totalOrbitalBots = 0 + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType === 'orbit') totalOrbitalBots++ + } + let index = 0 + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType === 'orbit') { + bullet[i].phase = (index / totalOrbitalBots) * 2 * Math.PI + index++ + } + } + }, + // ************************************************************************************************** + // ************************************************************************************************** + // ******************************** Guns ********************************************* + // ************************************************************************************************** + // ************************************************************************************************** + giveGuns(gun = "random", ammoPacks = 10) { + if (mod.isOneGun) b.removeAllGuns(); + if (gun === "random") { + //find what guns player doesn't have + options = [] + for (let i = 0, len = b.guns.length; i < len; i++) { + if (!b.guns[i].have) options.push(i) + } + if (options.length === 0) return + //randomly pick from list of possible guns + gun = options[Math.floor(Math.random() * options.length)] + } + if (gun === "all") { + b.activeGun = 0; + b.inventoryGun = 0; + for (let i = 0; i < b.guns.length; i++) { + b.inventory[i] = i; + b.guns[i].have = true; + b.guns[i].ammo = Math.floor(b.guns[i].ammoPack * ammoPacks); + } + } else { + if (isNaN(gun)) { //find gun by name + let found = false; + for (let i = 0; i < b.guns.length; i++) { + if (gun === b.guns[i].name) { + gun = i + found = true; + break + } + } + if (!found) return //if no gun found don't give a gun + } + if (!b.guns[gun].have) b.inventory.push(gun); + b.guns[gun].have = true; + b.guns[gun].ammo = Math.floor(b.guns[gun].ammoPack * ammoPacks); + if (b.activeGun === null) b.activeGun = gun //if no active gun switch to new gun + } + game.makeGunHUD(); + }, + guns: [{ + name: "nail gun", + description: "use compressed air to fire a stream of nails
delay after firing decreases as you shoot", + ammo: 0, + ammoPack: 60, + defaultAmmoPack: 60, + recordedAmmo: 0, + have: false, + nextFireCycle: 0, //use to remember how longs its been since last fire, used to reset count + startingHoldCycle: 0, + fire() { + let CD + if (mod.nailFireRate) { //fire delay decreases as you hold fire, down to 3 from 15 + if (mod.nailInstantFireRate) { + CD = 2 + } else { + if (this.nextFireCycle + 1 < mech.cycle) this.startingHoldCycle = mech.cycle //reset if not constantly firing + CD = Math.max(7.5 - 0.06 * (mech.cycle - this.startingHoldCycle), 2) //CD scales with cycles fire is held down + this.nextFireCycle = mech.cycle + CD * b.fireCD //predict next fire cycle if the fire button is held down + } + } else { + if (this.nextFireCycle + 1 < mech.cycle) this.startingHoldCycle = mech.cycle //reset if not constantly firing + CD = Math.max(11 - 0.06 * (mech.cycle - this.startingHoldCycle), 2) //CD scales with cycles fire is held down + this.nextFireCycle = mech.cycle + CD * b.fireCD //predict next fire cycle if the fire button is held down + } + mech.fireCDcycle = mech.cycle + Math.floor(CD * b.fireCD); // cool down + const speed = 30 + 6 * Math.random() + 9 * mod.nailInstantFireRate + const angle = mech.angle + (Math.random() - 0.5) * (Math.random() - 0.5) * (mech.crouch ? 1.35 : 3.2) / CD + const dmg = 0.9 + b.nail({ + x: mech.pos.x + 30 * Math.cos(mech.angle), + y: mech.pos.y + 30 * Math.sin(mech.angle) + }, { + x: mech.Vx / 2 + speed * Math.cos(angle), + y: mech.Vy / 2 + speed * Math.sin(angle) + }, dmg) //position, velocity, damage + if (mod.isIceCrystals) { + bullet[bullet.length - 1].beforeDmg = function(who) { + mobs.statusSlow(who, 30) + if (mod.isNailPoison) mobs.statusDoT(who, dmg * 0.22, 120) // one tick every 30 cycles + if (mod.isNailCrit && !who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.99) this.dmg *= 5 //crit if hit near center + }; + mech.energy -= mech.fieldRegen + 0.008 + if (mech.energy < 0.02) mech.fireCDcycle = mech.cycle + 60; // cool down + } + } + }, + { + name: "shotgun", + description: "fire a burst of short range bullets
crouch to reduce recoil", + ammo: 0, + ammoPack: 6, + defaultAmmoPack: 6, + have: false, + fire() { + let knock, spread + if (mech.crouch) { + spread = 0.75 + mech.fireCDcycle = mech.cycle + Math.floor(55 * b.fireCD); // cool down + if (mod.isShotgunImmune) mech.immuneCycle = mech.cycle + Math.floor(58 * b.fireCD); //player is immune to collision damage for 30 cycles + knock = 0.01 + } else { + mech.fireCDcycle = mech.cycle + Math.floor(45 * b.fireCD); // cool down + if (mod.isShotgunImmune) mech.immuneCycle = mech.cycle + Math.floor(47 * b.fireCD); //player is immune to collision damage for 30 cycles + spread = 1.3 + knock = 0.1 + } + + if (mod.isShotgunRecoil) { + mech.fireCDcycle -= 0.66 * (45 * b.fireCD) + player.force.x -= 2 * knock * Math.cos(mech.angle) + player.force.y -= 2 * knock * Math.sin(mech.angle) //reduce knock back in vertical direction to stop super jumps + } else { + player.force.x -= knock * Math.cos(mech.angle) + player.force.y -= knock * Math.sin(mech.angle) * 0.3 //reduce knock back in vertical direction to stop super jumps + } + + b.muzzleFlash(35); + if (mod.isNailShot) { + for (let i = 0; i < 14; i++) { + const dir = mech.angle + (Math.random() - 0.5) * spread * 0.2 + const pos = { + x: mech.pos.x + 35 * Math.cos(mech.angle) + 15 * (Math.random() - 0.5), + y: mech.pos.y + 35 * Math.sin(mech.angle) + 15 * (Math.random() - 0.5) + } + speed = 35 + 15 * Math.random() + const velocity = { + x: speed * Math.cos(dir), + y: speed * Math.sin(dir) + } + b.nail(pos, velocity, 1.2) + } + } else { + const side = 22 + for (let i = 0; i < 17; i++) { + const me = bullet.length; + const dir = mech.angle + (Math.random() - 0.5) * spread + bullet[me] = Bodies.rectangle(mech.pos.x + 35 * Math.cos(mech.angle) + 15 * (Math.random() - 0.5), mech.pos.y + 35 * Math.sin(mech.angle) + 15 * (Math.random() - 0.5), side, side, b.fireAttributes(dir)); + World.add(engine.world, bullet[me]); //add bullet to world + const SPEED = 52 + Math.random() * 8 + Matter.Body.setVelocity(bullet[me], { + x: SPEED * Math.cos(dir), + y: SPEED * Math.sin(dir) + }); + bullet[me].endCycle = game.cycle + 40 + bullet[me].minDmgSpeed = 15 + // bullet[me].restitution = 0.4 + bullet[me].frictionAir = 0.034; + bullet[me].do = function() { + if (!mech.isBodiesAsleep) { + const scale = 1 - 0.034 / mod.isBulletsLastLonger + Matter.Body.scale(this, scale, scale); + } + }; + } + } + } + }, + { + name: "super balls", + description: "fire four balls in a wide arc
balls bounce with no momentum loss", + ammo: 0, + ammoPack: 12, + have: false, + num: 5, + fire() { + const SPEED = mech.crouch ? 43 : 32 + mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 25 : 18) * b.fireCD); // cool down + if (mod.oneSuperBall) { + let dir = mech.angle + const me = bullet.length; + bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 12, 20 * mod.bulletSize, b.fireAttributes(dir, false)); + World.add(engine.world, bullet[me]); //add bullet to world + Matter.Body.setVelocity(bullet[me], { + x: SPEED * Math.cos(dir), + y: SPEED * Math.sin(dir) + }); + // Matter.Body.setDensity(bullet[me], 0.0001); + bullet[me].endCycle = game.cycle + Math.floor((300 + 60 * Math.random()) * mod.isBulletsLastLonger); + bullet[me].minDmgSpeed = 0; + bullet[me].restitution = 1; + bullet[me].friction = 0; + bullet[me].do = function() { + this.force.y += this.mass * 0.001; + }; + bullet[me].beforeDmg = function(who) { + mobs.statusStun(who, 180) // (2.3) * 2 / 14 ticks (2x damage over 7 seconds) + }; + } else { + b.muzzleFlash(20); + const SPREAD = mech.crouch ? 0.08 : 0.15 + let dir = mech.angle - SPREAD * (mod.superBallNumber - 1) / 2; + for (let i = 0; i < mod.superBallNumber; i++) { + const me = bullet.length; + bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 12, 7.5 * mod.bulletSize, b.fireAttributes(dir, false)); + World.add(engine.world, bullet[me]); //add bullet to world + Matter.Body.setVelocity(bullet[me], { + x: SPEED * Math.cos(dir), + y: SPEED * Math.sin(dir) + }); + // Matter.Body.setDensity(bullet[me], 0.0001); + bullet[me].endCycle = game.cycle + Math.floor((300 + 60 * Math.random()) * mod.isBulletsLastLonger); + bullet[me].minDmgSpeed = 0; + bullet[me].restitution = 0.99; + bullet[me].friction = 0; + bullet[me].do = function() { + this.force.y += this.mass * 0.001; + }; + dir += SPREAD; + } + } + } + }, + { + name: "flechettes", + description: "fire a volley of uranium-235 needles
does radioactive damage over 3 seconds", + ammo: 0, + ammoPack: 55, + defaultAmmoPack: 55, + have: false, + count: 0, //used to track how many shots are in a volley before a big CD + lastFireCycle: 0, //use to remember how longs its been since last fire, used to reset count + fire() { + function makeFlechette(angle = mech.angle + 0.02 * (Math.random() - 0.5)) { + const me = bullet.length; + bullet[me] = Bodies.rectangle(mech.pos.x + 40 * Math.cos(mech.angle), mech.pos.y + 40 * Math.sin(mech.angle), 45, 1.4, b.fireAttributes(angle)); + bullet[me].collisionFilter.mask = mod.pierce ? 0 : cat.body; //cat.mobShield | //cat.map | cat.body | + Matter.Body.setDensity(bullet[me], 0.00001); //0.001 is normal + bullet[me].endCycle = game.cycle + 180; + bullet[me].dmg = 0; + bullet[me].immuneList = [] + bullet[me].do = function() { + const whom = Matter.Query.collides(this, mob) + if (whom.length && this.speed > 20) { //if touching a mob + who = whom[0].bodyA + if (who && who.mob) { + if (mod.pierce) { + let immune = false + for (let i = 0; i < this.immuneList.length; i++) { + if (this.immuneList[i] === who.id) immune = true + } + if (!immune) { + this.immuneList.push(who.id) + who.foundPlayer(); + if (mod.isFastDot) { + mobs.statusDoT(who, 4, 30) + } else { + mobs.statusDoT(who, 0.66, mod.isSlowDot ? 360 : 180) + } + game.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: 40, + color: "rgba(0,80,80,0.3)", + time: game.drawTime + }); + } + } else { + this.endCycle = 0; + if (mod.isFlechetteExplode && !who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.975) { + // mobs.statusStun(who, 120) + this.explodeRad = 300 + 60 * Math.random(); + b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end + } + who.foundPlayer(); + if (mod.isFastDot) { + mobs.statusDoT(who, 3.78, 30) + } else { + mobs.statusDoT(who, 0.63, mod.isSlowDot ? 360 : 180) + } + game.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: 40, + color: "rgba(0,80,80,0.3)", + time: game.drawTime + }); + } + } + } else if (Matter.Query.collides(this, map).length) { //stick in walls + this.collisionFilter.mask = 0; + Matter.Body.setAngularVelocity(this, 0) + Matter.Body.setVelocity(this, { + x: 0, + y: 0 + }); + this.do = function() {} + } else if (this.speed < 30) { + this.force.y += this.mass * 0.0007; //no gravity until it slows down to improve aiming + } + }; + const SPEED = 50 + Matter.Body.setVelocity(bullet[me], { + x: mech.Vx / 2 + SPEED * Math.cos(angle), + y: mech.Vy / 2 + SPEED * Math.sin(angle) + }); + World.add(engine.world, bullet[me]); //add bullet to world + } + makeFlechette() + if (mod.isFlechetteMultiShot) { + makeFlechette(mech.angle + 0.02 + 0.005 * Math.random()) + makeFlechette(mech.angle - 0.02 - 0.005 * Math.random()) + } + + const CD = (mech.crouch) ? 60 : 30 + if (this.lastFireCycle + CD < mech.cycle) this.count = 0 //reset count if it cycles past the CD + this.lastFireCycle = mech.cycle + if (this.count > ((mech.crouch) ? 7 : 1)) { + this.count = 0 + mech.fireCDcycle = mech.cycle + Math.floor(CD * b.fireCD); // cool down + const who = bullet[bullet.length - 1] + Matter.Body.setDensity(who, 0.00001); + } else { + this.count++ + mech.fireCDcycle = mech.cycle + Math.floor(2 * b.fireCD); // cool down + } + } + }, + { + name: "wave beam", + description: "emit a sine wave of oscillating particles
that can propagate through solids", + ammo: 0, + ammoPack: 70, + have: false, + fire() { + mech.fireCDcycle = mech.cycle + Math.floor(3 * b.fireCD); // cool down + const dir = mech.angle + const SPEED = 10 + let wiggleMag + if (mod.waveHelix === 2) { + wiggleMag = (mech.crouch ? 6 : 12) * (1 + Math.sin(mech.cycle * 0.1)) + } else { + wiggleMag = mech.crouch ? 6 : 12 + } + // const wiggleMag = mod.waveHelix ? (mech.crouch ? 6 + 6 * Math.sin(mech.cycle * 0.1) : 13 + 13 * Math.sin(mech.cycle * 0.1)) : (mech.crouch ? 6 : 12) + const size = 5 * (mod.waveHelix === 1 ? 1 : 0.7) + for (let i = 0; i < mod.waveHelix; i++) { + const me = bullet.length; + bullet[me] = Bodies.polygon(mech.pos.x + 25 * Math.cos(dir), mech.pos.y + 25 * Math.sin(dir), 7, size, { + angle: dir, + cycle: -0.5, + endCycle: game.cycle + Math.floor((mod.isWaveReflect ? 600 : 120) * mod.isBulletsLastLonger), + inertia: Infinity, + frictionAir: 0, + slow: 0, + minDmgSpeed: 0, + dmg: 0, + isJustReflected: false, + classType: "bullet", + collisionFilter: { + category: 0, + mask: 0, //cat.mob | cat.mobBullet | cat.mobShield + }, + beforeDmg() {}, + onEnd() {}, + do() { + if (!mech.isBodiesAsleep) { + if (mod.isWaveReflect) { + // check if inside a mob + q = Matter.Query.point(mob, this.position) + for (let i = 0; i < q.length; i++) { + let dmg = b.dmgScale * 0.36 / Math.sqrt(q[i].mass) * (mod.waveHelix === 1 ? 1 : 0.8) //1 - 0.4 = 0.6 for helix mod 40% damage reduction + q[i].damage(dmg); + q[i].foundPlayer(); + game.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: Math.log(2 * dmg + 1.1) * 40, + color: 'rgba(0,0,0,0.4)', + time: game.drawTime + }); + } + Matter.Body.setPosition(this, Vector.add(this.position, player.velocity)) //bullets move with player + const sub = Vector.sub(this.position, mech.pos) + const range = 558 //93 * x + if (Vector.magnitude(sub) > range) { + // Matter.Body.setPosition(this, Vector.sub(this.position, Vector.mult(Vector.normalise(sub), 2 * range))) //teleport to opposite side + Matter.Body.setVelocity(this, Vector.mult(this.velocity, -1)); + Matter.Body.setPosition(this, Vector.add(mech.pos, Vector.mult(Vector.normalise(sub), range))) //reflect + } + } else { + let slowCheck = 1 + if (Matter.Query.point(map, this.position).length) { //check if inside map + slowCheck = mod.waveSpeedMap + } else { //check if inside a body + let q = Matter.Query.point(body, this.position) + if (q.length) { + slowCheck = mod.waveSpeedBody + Matter.Body.setPosition(this, Vector.add(this.position, q[0].velocity)) //move with the medium + } else { // check if inside a mob + q = Matter.Query.point(mob, this.position) + for (let i = 0; i < q.length; i++) { + slowCheck = 0.3; + Matter.Body.setPosition(this, Vector.add(this.position, q[i].velocity)) //move with the medium + let dmg = b.dmgScale * 0.36 / Math.sqrt(q[i].mass) * (mod.waveHelix === 1 ? 1 : 0.8) //1 - 0.4 = 0.6 for helix mod 40% damage reduction + q[i].damage(dmg); + q[i].foundPlayer(); + game.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: Math.log(2 * dmg + 1.1) * 40, + color: 'rgba(0,0,0,0.4)', + time: game.drawTime + }); + } + } + } + if (slowCheck !== this.slow) { //toggle velocity based on inside and outside status change + this.slow = slowCheck + Matter.Body.setVelocity(this, Vector.mult(Vector.normalise(this.velocity), SPEED * slowCheck)); + } + } + this.cycle++ + const wiggle = Vector.mult(transverse, wiggleMag * Math.cos(this.cycle * 0.35) * ((i % 2) ? -1 : 1)) + Matter.Body.setPosition(this, Vector.add(this.position, wiggle)) + } + // if (mod.isWaveReflect) { //single reflection + // const sub = Vector.sub(this.position, mech.pos) + // if (Vector.magnitude(sub) > 630) { + // // Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(Vector.normalise(sub), -2 * POCKET_RANGE))) //teleport to opposite side + // if (!this.isJustReflected) { + // Matter.Body.setVelocity(this, Vector.mult(this.velocity, -1)); //reflect + // this.isJustReflected = true; + // } + // } + // } + + // if (mod.isWaveReflect) { + // Matter.Body.setPosition(this, Vector.add(this.position, player.velocity)) //bullets move with player + + // Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(Vector.normalise(sub), -2 * POCKET_RANGE))) //teleport to opposite side + + // const sub = Vector.sub(this.position, mech.pos) + // if (Vector.magnitude(sub) > 630) { + // if (!this.isJustReflected) { + // Matter.Body.setVelocity(this, Vector.mult(this.velocity, -1)); //reflect + // this.isJustReflected = true; + // } + // } else { + // this.isJustReflected = false + // } + // } + } + }); + World.add(engine.world, bullet[me]); //add bullet to world + Matter.Body.setVelocity(bullet[me], { + x: SPEED * Math.cos(dir), + y: SPEED * Math.sin(dir) + }); + const transverse = Vector.normalise(Vector.perp(bullet[me].velocity)) + } + } + }, + { + name: "missiles", + description: "launch missiles that accelerate towards mobs
explodes when near target", + ammo: 0, + ammoPack: 3, + have: false, + fireCycle: 0, + ammoLoaded: 0, + fire() { + if (mod.is3Missiles) { + if (mech.crouch) { + mech.fireCDcycle = mech.cycle + 50 * b.fireCD; // cool down + const direction = { + x: Math.cos(mech.angle), + y: Math.sin(mech.angle) + } + const push = Vector.mult(Vector.perp(direction), 0.0007) + for (let i = 0; i < 3; i++) { + //missile(where, dir, speed, size = 1, spawn = 0) { + b.missile({ + x: mech.pos.x + 40 * direction.x, + y: mech.pos.y + 40 * direction.y + }, mech.angle + 0.06 * (1 - i), 0, 0.7, mod.recursiveMissiles) + bullet[bullet.length - 1].force.x += push.x * (i - 1); + bullet[bullet.length - 1].force.y += push.y * (i - 1); + } + } else { + mech.fireCDcycle = mech.cycle + 35 * b.fireCD; // cool down + const direction = { + x: Math.cos(mech.angle), + y: Math.sin(mech.angle) + } + const push = Vector.mult(Vector.perp(direction), 0.0008) + for (let i = 0; i < 3; i++) { + //missile(where, dir, speed, size = 1, spawn = 0) { + b.missile({ + x: mech.pos.x + 40 * direction.x, + y: mech.pos.y + 40 * direction.y + }, mech.angle, 0, 0.7, mod.recursiveMissiles) + bullet[bullet.length - 1].force.x += push.x * (i - 1); + bullet[bullet.length - 1].force.y += push.y * (i - 1); + } + } + } else { + mech.fireCDcycle = mech.cycle + Math.floor(mech.crouch ? 40 : 25) * b.fireCD; // cool down + b.missile({ + x: mech.pos.x + 40 * Math.cos(mech.angle), + y: mech.pos.y + 40 * Math.sin(mech.angle) - 3 + }, + mech.angle + (0.5 - Math.random()) * (mech.crouch ? 0 : 0.2), + -3 * (0.5 - Math.random()) + (mech.crouch ? 25 : -8) * b.fireCD, + 1, mod.recursiveMissiles) + bullet[bullet.length - 1].force.y += 0.0006; //a small push down at first to make it seem like the missile is briefly falling + } + } + }, + { + name: "flak", + description: "fire a cluster of short range projectiles
explodes on contact or after half a second", + ammo: 0, + ammoPack: 4, + defaultAmmoPack: 4, //use to revert ammoPack after mod changes drop rate + have: false, + fire() { + mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 25 : 10) * b.fireCD); // cool down + b.muzzleFlash(30); + const SPEED = mech.crouch ? 29 : 25 + const END = Math.floor(mech.crouch ? 30 : 18); + const side1 = 17 + const side2 = 4 + const totalBullets = 6 + const angleStep = (mech.crouch ? 0.06 : 0.25) / totalBullets + let dir = mech.angle - angleStep * totalBullets / 2; + for (let i = 0; i < totalBullets; i++) { //5 -> 7 + dir += angleStep + const me = bullet.length; + bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), side1, side2, b.fireAttributes(dir)); + World.add(engine.world, bullet[me]); //add bullet to world + Matter.Body.setVelocity(bullet[me], { + x: (SPEED + 15 * Math.random() - 2 * i) * Math.cos(dir), + y: (SPEED + 15 * Math.random() - 2 * i) * Math.sin(dir) + }); + bullet[me].endCycle = 2 * i + game.cycle + END + bullet[me].restitution = 0; + bullet[me].friction = 1; + bullet[me].explodeRad = (mech.crouch ? 95 : 75) + (Math.random() - 0.5) * 50; + bullet[me].onEnd = function() { + b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end + } + bullet[me].beforeDmg = function() { + this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion + }; + bullet[me].do = function() { + // this.force.y += this.mass * 0.0004; + } + } + } + }, + { + name: "grenades", + description: "lob a single bouncy projectile
explodes on contact or after one second", + ammo: 0, + ammoPack: 5, + have: false, + fire() { + + }, + fireNormal() { + const me = bullet.length; + const dir = mech.angle; // + Math.random() * 0.05; + bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 20, b.fireAttributes(dir, false)); + Matter.Body.setDensity(bullet[me], 0.0005); + bullet[me].explodeRad = 275; + bullet[me].onEnd = function() { + b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end + if (mod.grenadeFragments) b.targetedNail(this.position, mod.grenadeFragments) + } + bullet[me].minDmgSpeed = 1; + bullet[me].beforeDmg = function() { + this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion + }; + if (mod.isRPG) { + b.fireProps(35, mech.crouch ? 60 : -15, dir, me); //cd , speed + bullet[me].endCycle = game.cycle + 70; + bullet[me].frictionAir = 0.07; + const MAG = 0.015 + bullet[me].thrust = { + x: bullet[me].mass * MAG * Math.cos(dir), + y: bullet[me].mass * MAG * Math.sin(dir) + } + bullet[me].do = function() { + this.force.x += this.thrust.x; + this.force.y += this.thrust.y; + if (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length) { + this.endCycle = 0; //explode if touching map or blocks + } + }; + } else { + b.fireProps(mech.crouch ? 40 : 30, mech.crouch ? 43 : 32, dir, me); //cd , speed + bullet[me].endCycle = game.cycle + Math.floor(mech.crouch ? 120 : 80); + bullet[me].restitution = 0.4; + bullet[me].do = function() { + this.force.y += this.mass * 0.0025; //extra gravity for harder arcs + }; + } + }, + fireVacuum() { + const me = bullet.length; + const dir = mech.angle; // + Math.random() * 0.05; + bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 20, b.fireAttributes(dir, false)); + Matter.Body.setDensity(bullet[me], 0.0003); + bullet[me].explodeRad = 350 + Math.floor(Math.random() * 50);; + bullet[me].onEnd = function() { + b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end + if (mod.grenadeFragments) b.targetedNail(this.position, mod.grenadeFragments) + } + bullet[me].beforeDmg = function() {}; + const cd = mech.crouch ? 90 : 75 + b.fireProps(cd, mech.crouch ? 46 : 35, dir, me); //cd , speed + bullet[me].endCycle = game.cycle + cd; + bullet[me].restitution = 0.4; + bullet[me].do = function() { + this.force.y += this.mass * 0.0025; //extra gravity for harder arcs + + const suckCycles = 40 + if (game.cycle > this.endCycle - suckCycles) { //suck + const that = this + + function suck(who, radius = that.explodeRad * 3.2) { + for (i = 0, len = who.length; i < len; i++) { + const sub = Vector.sub(that.position, who[i].position); + const dist = Vector.magnitude(sub); + if (dist < radius && dist > 150) { + knock = Vector.mult(Vector.normalise(sub), mag * who[i].mass / Math.sqrt(dist)); + who[i].force.x += knock.x; + who[i].force.y += knock.y; + } + } + } + let mag = 0.1 + if (game.cycle > this.endCycle - 5) { + mag = -0.22 + suck(mob, this.explodeRad * 3) + suck(body, this.explodeRad * 2) + suck(powerUp, this.explodeRad * 1.5) + suck(bullet, this.explodeRad * 1.5) + suck([player], this.explodeRad * 1.3) + } else { + mag = 0.11 + suck(mob, this.explodeRad * 3) + suck(body, this.explodeRad * 2) + suck(powerUp, this.explodeRad * 1.5) + suck(bullet, this.explodeRad * 1.5) + suck([player], this.explodeRad * 1.3) + } + //keep bomb in place + Matter.Body.setVelocity(this, { + x: 0, + y: 0 + }); + //draw suck + const radius = 2.75 * this.explodeRad * (this.endCycle - game.cycle) / suckCycles + ctx.fillStyle = "rgba(0,0,0,0.1)"; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, radius, 0, 2 * Math.PI); + ctx.fill(); + } + }; + } + }, + { + name: "neutron bomb", + description: "toss a chunk of Cf-252 which emits neutrons
that do damage, harm, and energy drain", + ammo: 0, + ammoPack: 5, + have: false, + fire() { + const me = bullet.length; + const dir = mech.angle; + bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 10, 4, b.fireAttributes(dir, false)); + b.fireProps(mech.crouch ? 30 : 15, mech.crouch ? 28 : 18, dir, me); //cd , speed + Matter.Body.setDensity(bullet[me], 0.000001); + bullet[me].endCycle = Infinity; + bullet[me].frictionAir = 0; + bullet[me].friction = 1; + bullet[me].frictionStatic = 1; + bullet[me].restitution = 0; + bullet[me].minDmgSpeed = 0; + bullet[me].damageRadius = 100; + bullet[me].maxDamageRadius = (435 + 150 * Math.random()) * (mod.isNeutronImmune ? 1.2 : 1) + bullet[me].stuckTo = null; + bullet[me].stuckToRelativePosition = null; + bullet[me].beforeDmg = function() {}; + bullet[me].stuck = function() {}; + bullet[me].do = function() { + function onCollide(that) { + that.collisionFilter.mask = 0; //non collide with everything + Matter.Body.setVelocity(that, { + x: 0, + y: 0 + }); + // that.frictionAir = 1; + that.do = that.radiationMode; + + if (mod.isNeutronStun) { + //push blocks + const dist = that.maxDamageRadius * 0.9 + for (let i = 0, len = body.length; i < len; ++i) { + const SUB = Vector.sub(body[i].position, that.position) + const DISTANCE = Vector.magnitude(SUB) + if (DISTANCE < dist) { + const FORCE = Vector.mult(Vector.normalise(SUB), 0.04 * body[i].mass) + body[i].force.x += FORCE.x; + body[i].force.y += FORCE.y - body[i].mass * game.g * 5; //kick up a bit to give them some arc + } + } + //stun mobs + for (let i = 0, len = mob.length; i < len; ++i) { + if (Vector.magnitude(Vector.sub(mob[i].position, that.position)) < dist) { + mobs.statusStun(mob[i], mod.isNeutronStun) + } + } + } + } + + const mobCollisions = Matter.Query.collides(this, mob) + if (mobCollisions.length) { + onCollide(this) + this.stuckTo = mobCollisions[0].bodyA + + if (this.stuckTo.isVerticesChange) { + this.stuckToRelativePosition = { + x: 0, + y: 0 + } + } else { + //find the relative position for when the mob is at angle zero by undoing the mobs rotation + this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) + } + this.stuck = function() { + if (this.stuckTo && this.stuckTo.alive) { + const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector + Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) + Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck + } else { + this.collisionFilter.mask = cat.map | cat.body | cat.player | cat.mob; //non collide with everything but map + this.stuck = function() { + this.force.y += this.mass * 0.001; + } + } + } + } else { + const bodyCollisions = Matter.Query.collides(this, body) + if (bodyCollisions.length) { + if (!bodyCollisions[0].bodyA.isNotHoldable) { + onCollide(this) + this.stuckTo = bodyCollisions[0].bodyA + //find the relative position for when the mob is at angle zero by undoing the mobs rotation + this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) + } else { + this.do = this.radiationMode; + } + this.stuck = function() { + if (this.stuckTo) { + const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector + Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) + // Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck + } else { + this.force.y += this.mass * 0.001; + } + } + } else { + if (Matter.Query.collides(this, map).length) { + onCollide(this) + } else { //if colliding with nothing just fall + this.force.y += this.mass * 0.001; + } + } + } + } + bullet[me].radiationMode = function() { + this.stuck(); //runs different code based on what the bullet is stuck to + if (!mech.isBodiesAsleep) { + this.damageRadius = this.damageRadius * 0.85 + 0.15 * this.maxDamageRadius //smooth radius towards max + this.maxDamageRadius -= 0.8 / mod.isBulletsLastLonger //+ 0.5 * Math.sin(game.cycle * 0.1) //slowly shrink max radius + + if (this.damageRadius < 15) { + this.endCycle = 0; + } else { + //aoe damage to player + if (!mod.isNeutronImmune && Vector.magnitude(Vector.sub(player.position, this.position)) < this.damageRadius) { + const DRAIN = 0.0015 + if (mech.energy > DRAIN) { + mech.energy -= DRAIN + } else { + mech.energy = 0; + mech.damage(0.00015) + } + } + //aoe damage to mobs + for (let i = 0, len = mob.length; i < len; i++) { + if (Vector.magnitude(Vector.sub(mob[i].position, this.position)) < this.damageRadius) { + let dmg = b.dmgScale * 0.035 + if (Matter.Query.ray(map, mob[i].position, this.position).length > 0) dmg *= 0.3 //reduce damage if a wall is in the way + if (mob[i].shield) dmg *= 4 //x5 to make up for the /5 that shields normally take + mob[i].damage(dmg); + mob[i].locatePlayer(); + } + } + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.damageRadius, 0, 2 * Math.PI); + ctx.globalCompositeOperation = "lighter" + ctx.fillStyle = `rgba(25,139,170,${0.2+0.06*Math.random()})`; + ctx.fill(); + ctx.globalCompositeOperation = "source-over" + } + } + } + } + }, + { + name: "mine", + description: "toss a proximity mine that sticks to walls
fires nails at mobs within range", + ammo: 0, + ammoPack: 2.7, + have: false, + fire() { + const pos = { + x: mech.pos.x + 30 * Math.cos(mech.angle), + y: mech.pos.y + 30 * Math.sin(mech.angle) + } + let speed = mech.crouch ? 36 : 22 + if (Matter.Query.point(map, pos).length > 0) { //don't fire if mine will spawn inside map + speed = -2 + } + b.mine(pos, { + x: speed * Math.cos(mech.angle), + y: speed * Math.sin(mech.angle) + }, 0, mod.isMineAmmoBack) + mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 50 : 25) * b.fireCD); // cool down + } + }, + { + name: "spores", + description: "fire a sporangium that discharges spores
spores seek out nearby mobs", + ammo: 0, + ammoPack: 3, + have: false, + fire() { + const me = bullet.length; + const dir = mech.angle; + bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 20, 4.5, b.fireAttributes(dir, false)); + b.fireProps(mech.crouch ? 50 : 30, mech.crouch ? 30 : 16, dir, me); //cd , speed + Matter.Body.setDensity(bullet[me], 0.000001); + bullet[me].endCycle = Infinity; + bullet[me].frictionAir = 0; + bullet[me].friction = 0.5; + bullet[me].radius = 4.5; + bullet[me].maxRadius = 30; + bullet[me].restitution = 0.3; + bullet[me].minDmgSpeed = 0; + bullet[me].totalSpores = 8 + 2 * mod.isFastSpores + 2 * mod.isSporeFreeze + bullet[me].stuck = function() {}; + bullet[me].beforeDmg = function() {}; + bullet[me].do = function() { + function onCollide(that) { + that.collisionFilter.mask = 0; //non collide with everything + Matter.Body.setVelocity(that, { + x: 0, + y: 0 + }); + that.do = that.grow; + } + + const mobCollisions = Matter.Query.collides(this, mob) + if (mobCollisions.length) { + onCollide(this) + this.stuckTo = mobCollisions[0].bodyA + + if (this.stuckTo.isVerticesChange) { + this.stuckToRelativePosition = { + x: 0, + y: 0 + } + } else { + //find the relative position for when the mob is at angle zero by undoing the mobs rotation + this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) + } + this.stuck = function() { + if (this.stuckTo && this.stuckTo.alive) { + const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector + Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) + Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck + } else { + this.collisionFilter.mask = cat.map; //non collide with everything but map + this.stuck = function() { + this.force.y += this.mass * 0.0006; + } + } + } + } else { + const bodyCollisions = Matter.Query.collides(this, body) + if (bodyCollisions.length) { + if (!bodyCollisions[0].bodyA.isNotHoldable) { + onCollide(this) + this.stuckTo = bodyCollisions[0].bodyA + //find the relative position for when the mob is at angle zero by undoing the mobs rotation + this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) + } else { + this.do = this.grow; + } + this.stuck = function() { + if (this.stuckTo) { + const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector + Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) + // Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck + } else { + this.force.y += this.mass * 0.0006; + } + } + } else { + if (Matter.Query.collides(this, map).length) { + onCollide(this) + } else { //if colliding with nothing just fall + this.force.y += this.mass * 0.0006; + } + } + } + //draw green glow + ctx.fillStyle = "rgba(0,200,125,0.16)"; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.maxRadius, 0, 2 * Math.PI); + ctx.fill(); + } + + bullet[me].grow = function() { + this.stuck(); //runs different code based on what the bullet is stuck to + if (!mech.isBodiesAsleep) { + let scale = 1.01 + if (mod.isSporeGrowth && !(game.cycle % 60)) { //release a spore + b.spore(this.position) + // this.totalSpores-- + scale = 0.94 + if (this.stuckTo && this.stuckTo.alive) scale = 0.88 + Matter.Body.scale(this, scale, scale); + this.radius *= scale + } else { + if (this.stuckTo && this.stuckTo.alive) scale = 1.03 + Matter.Body.scale(this, scale, scale); + this.radius *= scale + if (this.radius > this.maxRadius) this.endCycle = 0; + } + } + + // this.force.y += this.mass * 0.00045; + + //draw green glow + ctx.fillStyle = "rgba(0,200,125,0.16)"; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.maxRadius, 0, 2 * Math.PI); + ctx.fill(); + }; + + //spawn bullets on end + bullet[me].onEnd = function() { + const NUM = this.totalSpores + for (let i = 0; i < NUM; i++) { + b.spore(this.position) + } + } + } + }, + { + name: "drones", + description: "deploy drones that crash into mobs
crashes reduce their lifespan by 1 second", + ammo: 0, + ammoPack: 14, + have: false, + fire() { + if (mech.crouch) { + b.drone(45) + mech.fireCDcycle = mech.cycle + Math.floor(13 * b.fireCD); // cool down + } else { + b.drone(1) + mech.fireCDcycle = mech.cycle + Math.floor(6 * b.fireCD); // cool down + } + } + }, + { + name: "ice IX", + description: "synthesize short-lived ice crystals
crystals seek out and freeze mobs", + ammo: 0, + ammoPack: 64, + have: false, + fire() { + if (mech.crouch) { + b.iceIX(10, 0.3) + mech.fireCDcycle = mech.cycle + Math.floor(8 * b.fireCD); // cool down + } else { + b.iceIX(2) + mech.fireCDcycle = mech.cycle + Math.floor(3 * b.fireCD); // cool down + } + + } + }, + { + name: "foam", + description: "spray bubbly foam that sticks to mobs
slows mobs and does damage over time", + ammo: 0, + ammoPack: 40, + have: false, + fire() { + mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 20 : 6) * b.fireCD); // cool down + const radius = (mech.crouch ? 10 + 7 * Math.random() : 4 + 6 * Math.random()) + const dir = mech.angle + 0.2 * (Math.random() - 0.5) + const position = { + x: mech.pos.x + 30 * Math.cos(mech.angle), + y: mech.pos.y + 30 * Math.sin(mech.angle) + } + const SPEED = 21 - radius * 0.7; //(mech.crouch ? 32 : 20) - radius * 0.7; + const velocity = { + x: SPEED * Math.cos(dir), + y: SPEED * Math.sin(dir) + } + b.foam(position, velocity, radius) + } + }, + { + name: "rail gun", + description: "use energy to launch a high-speed dense rod
hold left mouse to charge, release to fire", + ammo: 0, + ammoPack: 3.5, + have: false, + fire() { + if (mod.isCapacitor) { + if (mech.energy > 0.15) { + mech.energy -= 0.15 + mech.fireCDcycle = mech.cycle + Math.floor(30 * b.fireCD); + const me = bullet.length; + bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), 60, 14, { + density: 0.005, //0.001 is normal + restitution: 0, + frictionAir: 0, + angle: mech.angle, + dmg: 0, //damage done in addition to the damage from momentum + classType: "bullet", + collisionFilter: { + category: cat.bullet, + mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield + }, + minDmgSpeed: 5, + endCycle: game.cycle + 140, + beforeDmg(who) { + if (who.shield) { + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].id === who.shieldTargetID) { //apply some knock back to shield mob before shield breaks + Matter.Body.setVelocity(mob[i], Vector.mult(Vector.normalise(this.velocity), 10)); + break + } + } + Matter.Body.setVelocity(this, { + x: -0.5 * this.velocity.x, + y: -0.5 * this.velocity.y + }); + // Matter.Body.setDensity(this, 0.001); + } + if (mod.isRailNails && this.speed > 10) { + b.targetedNail(this.position, (Math.min(40, this.speed) - 10) * 0.6) // 0.6 as many nails as the normal rail gun + this.endCycle = 0 //triggers despawn + } + }, + onEnd() {}, + drawCycle: Math.floor(10 * b.fireCD), + do() { + this.force.y += this.mass * 0.0003; // low gravity that scales with charge + if (this.drawCycle > 0) { + this.drawCycle-- + //draw magnetic field + const X = mech.pos.x + const Y = mech.pos.y + const unitVector = Vector.normalise(Vector.sub(game.mouseInGame, mech.pos)) + const unitVectorPerp = Vector.perp(unitVector) + + function magField(mag, arc) { + ctx.moveTo(X, Y); + ctx.bezierCurveTo( + X + unitVector.x * mag, Y + unitVector.y * mag, + X + unitVector.x * mag + unitVectorPerp.x * arc, Y + unitVector.y * mag + unitVectorPerp.y * arc, + X + unitVectorPerp.x * arc, Y + unitVectorPerp.y * arc) + ctx.bezierCurveTo( + X - unitVector.x * mag + unitVectorPerp.x * arc, Y - unitVector.y * mag + unitVectorPerp.y * arc, + X - unitVector.x * mag, Y - unitVector.y * mag, + X, Y) + } + ctx.fillStyle = `rgba(50,0,100,0.05)`; + for (let i = 3; i < 7; i++) { + const MAG = 8 * i * i * (0.93 + 0.07 * Math.random()) * (0.95 + 0.1 * Math.random()) + const ARC = 6 * i * i * (0.93 + 0.07 * Math.random()) * (0.95 + 0.1 * Math.random()) + ctx.beginPath(); + magField(MAG, ARC) + magField(MAG, -ARC) + ctx.fill(); + } + } + } + }); + World.add(engine.world, bullet[me]); //add bullet to world + + const speed = 67 + Matter.Body.setVelocity(bullet[me], { + x: mech.Vx / 2 + speed * Math.cos(mech.angle), + y: mech.Vy / 2 + speed * Math.sin(mech.angle) + }); + + //knock back + const KNOCK = mech.crouch ? 0.08 : 0.34 + player.force.x -= KNOCK * Math.cos(mech.angle) + player.force.y -= KNOCK * Math.sin(mech.angle) * 0.35 //reduce knock back in vertical direction to stop super jumps + + //push away blocks when firing + let range = 450 + for (let i = 0, len = body.length; i < len; ++i) { + const SUB = Vector.sub(body[i].position, mech.pos) + const DISTANCE = Vector.magnitude(SUB) + + if (DISTANCE < range) { + const DEPTH = Math.min(range - DISTANCE, 300) + const FORCE = Vector.mult(Vector.normalise(SUB), 0.003 * Math.sqrt(DEPTH) * body[i].mass) + body[i].force.x += FORCE.x; + body[i].force.y += FORCE.y - body[i].mass * (game.g * 1.5); //kick up a bit to give them some arc + } + } + for (let i = 0, len = mob.length; i < len; ++i) { + const SUB = Vector.sub(mob[i].position, mech.pos) + const DISTANCE = Vector.magnitude(SUB) + if (DISTANCE < range) { + const DEPTH = Math.min(range - DISTANCE, 300) + const FORCE = Vector.mult(Vector.normalise(SUB), 0.003 * Math.sqrt(DEPTH) * mob[i].mass) + mob[i].force.x += 1.5 * FORCE.x; + mob[i].force.y += 1.5 * FORCE.y; + } + } + } else { + mech.fireCDcycle = mech.cycle + Math.floor(120); + } + } else { + const me = bullet.length; + bullet[me] = Bodies.rectangle(0, 0, 0.015, 0.0015, { + density: 0.008, //0.001 is normal + //frictionAir: 0.01, //restitution: 0, + // angle: 0, + // friction: 0.5, + restitution: 0, + frictionAir: 0, + dmg: 0, //damage done in addition to the damage from momentum + classType: "bullet", + collisionFilter: { + category: 0, + mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield + }, + minDmgSpeed: 5, + beforeDmg(who) { + if (who.shield) { + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].id === who.shieldTargetID) { //apply some knock back to shield mob before shield breaks + Matter.Body.setVelocity(mob[i], Vector.mult(Vector.normalise(this.velocity), 10)); + break + } + } + Matter.Body.setVelocity(this, { + x: -0.5 * this.velocity.x, + y: -0.5 * this.velocity.y + }); + // Matter.Body.setDensity(this, 0.001); + } + if (mod.isRailNails && this.speed > 10) { + b.targetedNail(this.position, Math.min(40, this.speed) - 10) + this.endCycle = 0 //triggers despawn + } + }, + onEnd() {} + }); + mech.fireCDcycle = Infinity; // cool down + World.add(engine.world, bullet[me]); //add bullet to world + bullet[me].endCycle = Infinity + bullet[me].charge = 0; + bullet[me].do = function() { + if (mech.energy < 0.005 && !mod.isRailTimeSlow) { + mech.energy += 0.05 + this.charge * 0.3 + mech.fireCDcycle = mech.cycle + 120; // cool down if out of energy + this.endCycle = 0; + return + } + + if ((!input.fire && this.charge > 0.6)) { //fire on mouse release or on low energy + mech.fireCDcycle = mech.cycle + 2; // set fire cool down + //normal bullet behavior occurs after firing, overwrites this function + this.do = function() { + this.force.y += this.mass * 0.0003 / this.charge; // low gravity that scales with charge + } + if (mod.isRailTimeSlow) { + game.fpsCap = game.fpsCapDefault + game.fpsInterval = 1000 / game.fpsCap; + } + + Matter.Body.scale(this, 8000, 8000) // show the bullet by scaling it up (don't judge me... I know this is a bad way to do it) + this.endCycle = game.cycle + 140 + this.collisionFilter.category = cat.bullet + Matter.Body.setPosition(this, { + x: mech.pos.x, + y: mech.pos.y + }) + Matter.Body.setAngle(this, mech.angle) + const speed = 90 + Matter.Body.setVelocity(this, { + x: mech.Vx / 2 + speed * this.charge * Math.cos(mech.angle), + y: mech.Vy / 2 + speed * this.charge * Math.sin(mech.angle) + }); + + //knock back + const KNOCK = ((mech.crouch) ? 0.1 : 0.5) * this.charge * this.charge + player.force.x -= KNOCK * Math.cos(mech.angle) + player.force.y -= KNOCK * Math.sin(mech.angle) * 0.35 //reduce knock back in vertical direction to stop super jumps + + //push away blocks when firing + let range = 900 * this.charge + for (let i = 0, len = body.length; i < len; ++i) { + const SUB = Vector.sub(body[i].position, mech.pos) + const DISTANCE = Vector.magnitude(SUB) + + if (DISTANCE < range) { + const DEPTH = Math.min(range - DISTANCE, 300) + const FORCE = Vector.mult(Vector.normalise(SUB), 0.003 * Math.sqrt(DEPTH) * body[i].mass) + body[i].force.x += FORCE.x; + body[i].force.y += FORCE.y - body[i].mass * (game.g * 1.5); //kick up a bit to give them some arc + } + } + for (let i = 0, len = mob.length; i < len; ++i) { + const SUB = Vector.sub(mob[i].position, mech.pos) + const DISTANCE = Vector.magnitude(SUB) + + if (DISTANCE < range) { + const DEPTH = Math.min(range - DISTANCE, 300) + const FORCE = Vector.mult(Vector.normalise(SUB), 0.003 * Math.sqrt(DEPTH) * mob[i].mass) + mob[i].force.x += 1.5 * FORCE.x; + mob[i].force.y += 1.5 * FORCE.y; + } + } + } else { // charging on mouse down + mech.fireCDcycle = Infinity //can't fire until mouse is released + const lastCharge = this.charge + let chargeRate = (mech.crouch) ? 0.98 : 0.984 + chargeRate *= Math.pow(b.fireCD, 0.03) + this.charge = this.charge * chargeRate + (1 - chargeRate) // this.charge converges to 1 + if (mod.isRailTimeSlow) { + game.fpsCap = 30 //new fps + game.fpsInterval = 1000 / game.fpsCap; + } else { + mech.energy -= (this.charge - lastCharge) * 0.28 //energy drain is proportional to charge gained, but doesn't stop normal mech.fieldRegen + } + + //draw targeting + let best; + let range = 3000 + const dir = mech.angle + const path = [{ + x: mech.pos.x + 20 * Math.cos(dir), + y: mech.pos.y + 20 * Math.sin(dir) + }, + { + x: mech.pos.x + range * Math.cos(dir), + y: mech.pos.y + range * Math.sin(dir) + } + ]; + 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) { + 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] + }; + } + } + } + }; + + //check for collisions + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + vertexCollision(path[0], path[1], mob); + vertexCollision(path[0], path[1], map); + vertexCollision(path[0], path[1], body); + if (best.dist2 != Infinity) { //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + } + + //draw beam + ctx.beginPath(); + ctx.moveTo(path[0].x, path[0].y); + ctx.lineTo(path[1].x, path[1].y); + ctx.strokeStyle = `rgba(100,0,180,0.7)`; + ctx.lineWidth = this.charge * 1 + ctx.setLineDash([10, 20]); + ctx.stroke(); + ctx.setLineDash([0, 0]); + + //draw magnetic field + const X = mech.pos.x + const Y = mech.pos.y + const unitVector = Vector.normalise(Vector.sub(game.mouseInGame, mech.pos)) + const unitVectorPerp = Vector.perp(unitVector) + + function magField(mag, arc) { + ctx.moveTo(X, Y); + ctx.bezierCurveTo( + X + unitVector.x * mag, Y + unitVector.y * mag, + X + unitVector.x * mag + unitVectorPerp.x * arc, Y + unitVector.y * mag + unitVectorPerp.y * arc, + X + unitVectorPerp.x * arc, Y + unitVectorPerp.y * arc) + ctx.bezierCurveTo( + X - unitVector.x * mag + unitVectorPerp.x * arc, Y - unitVector.y * mag + unitVectorPerp.y * arc, + X - unitVector.x * mag, Y - unitVector.y * mag, + X, Y) + } + ctx.fillStyle = `rgba(50,0,100,0.05)`; + for (let i = 3; i < 7; i++) { + const MAG = 8 * i * i * this.charge * (0.93 + 0.07 * Math.random()) + const ARC = 6 * i * i * this.charge * (0.93 + 0.07 * Math.random()) + ctx.beginPath(); + magField(MAG, ARC) + magField(MAG, -ARC) + ctx.fill(); + } + } + } + } + } + }, + { + name: "laser", + description: "emit a beam of collimated coherent light
drains energy instead of ammunition", + ammo: 0, + ammoPack: Infinity, + have: false, + nextFireCycle: 0, //use to remember how longs its been since last fire, used to reset count + fire() { + + }, + fireLaser() { + if (mech.energy < mod.laserFieldDrain) { + mech.fireCDcycle = mech.cycle + 100; // cool down if out of energy + } else { + mech.fireCDcycle = mech.cycle + mech.energy -= mech.fieldRegen + mod.laserFieldDrain * mod.isLaserDiode + if (mod.wideLaser) { + ctx.strokeStyle = "#f00"; + ctx.lineWidth = 8 + ctx.globalAlpha = 0.5; + ctx.beginPath(); + + const off = 7.5 + const dmg = 0.55 * mod.laserDamage // 3.5 * 0.55 = 200% more damage + const where = { + x: mech.pos.x + 20 * Math.cos(mech.angle), + y: mech.pos.y + 20 * Math.sin(mech.angle) + } + b.laser(where, { + x: where.x + 3000 * Math.cos(mech.angle), + y: where.y + 3000 * Math.sin(mech.angle) + }, dmg, 0, true) + for (let i = 1; i < mod.wideLaser; i++) { + let whereOff = Vector.add(where, { + x: i * off * Math.cos(mech.angle + Math.PI / 2), + y: i * off * Math.sin(mech.angle + Math.PI / 2) + }) + b.laser(whereOff, { + x: whereOff.x + 3000 * Math.cos(mech.angle), + y: whereOff.y + 3000 * Math.sin(mech.angle) + }, dmg, 0, true) + whereOff = Vector.add(where, { + x: i * off * Math.cos(mech.angle - Math.PI / 2), + y: i * off * Math.sin(mech.angle - Math.PI / 2) + }) + b.laser(whereOff, { + x: whereOff.x + 3000 * Math.cos(mech.angle), + y: whereOff.y + 3000 * Math.sin(mech.angle) + }, dmg, 0, true) + } + ctx.stroke(); + ctx.globalAlpha = 1; + } else if (mod.beamSplitter) { + let dmg = mod.laserDamage * 0.9 + const where = { + x: mech.pos.x + 20 * Math.cos(mech.angle), + y: mech.pos.y + 20 * Math.sin(mech.angle) + } + b.laser(where, { + x: where.x + 3000 * Math.cos(mech.angle), + y: where.y + 3000 * Math.sin(mech.angle) + }, 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) + }, 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) + }, dmg) + dmg *= 0.9 + } + } else { + b.laser() + } + } + }, + firePulse() { + mech.fireCDcycle = mech.cycle + Math.floor((mod.isPulseAim ? 25 : 50) * b.fireCD); // cool down + let energy = 0.27 * Math.min(mech.energy, 1.5) + mech.energy -= energy * mod.isLaserDiode + + if (mod.beamSplitter) { + energy *= 0.66 + b.pulse(energy, mech.angle) + for (let i = 1; i < 1 + mod.beamSplitter; i++) { + energy *= 0.9 + b.pulse(energy, mech.angle - i * 0.27) + b.pulse(energy, mech.angle + i * 0.27) + } + } else { + b.pulse(energy, mech.angle) + } + }, + }, + ], + pulse(energy, angle = mech.angle) { + let best; + let explosionRange = 1560 * energy + let range = 3000 + const path = [{ + x: mech.pos.x + 20 * Math.cos(angle), + y: mech.pos.y + 20 * Math.sin(angle) + }, + { + x: mech.pos.x + range * Math.cos(angle), + y: mech.pos.y + range * Math.sin(angle) + } + ]; + const vertexCollision = function(v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + }; + //check for collisions + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + if (mod.isPulseAim) { //find mobs in line of sight + let dist = 2200 + for (let i = 0, len = mob.length; i < len; i++) { + const newDist = Vector.magnitude(Vector.sub(path[0], mob[i].position)) + if (explosionRange < newDist && + newDist < dist && + Matter.Query.ray(map, path[0], mob[i].position).length === 0 && + Matter.Query.ray(body, path[0], mob[i].position).length === 0) { + dist = newDist + best.who = mob[i] + path[path.length - 1] = mob[i].position + } + } + } + if (!best.who) { + vertexCollision(path[0], path[1], mob); + vertexCollision(path[0], path[1], map); + vertexCollision(path[0], path[1], body); + if (best.dist2 != Infinity) { //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + } + } + if (best.who) b.explosion(path[1], explosionRange, true) + + if (mod.isPulseStun) { + const range = 100 + 2000 * energy + for (let i = 0, len = mob.length; i < len; ++i) { + if (mob[i].alive && !mob[i].isShielded) { + dist = Vector.magnitude(Vector.sub(path[1], mob[i].position)) - mob[i].radius; + if (dist < range) mobs.statusStun(mob[i], 30 + Math.floor(energy * 60)) + } + } + } + //draw laser beam + ctx.beginPath(); + ctx.moveTo(path[0].x, path[0].y); + ctx.lineTo(path[1].x, path[1].y); + ctx.strokeStyle = "rgba(255,0,0,0.13)" + ctx.lineWidth = 60 * energy / 0.2 + ctx.stroke(); + ctx.strokeStyle = "rgba(255,0,0,0.2)" + ctx.lineWidth = 18 + ctx.stroke(); + ctx.strokeStyle = "#f00"; + ctx.lineWidth = 4 + ctx.stroke(); + + //draw little dots along the laser path + const sub = Vector.sub(path[1], path[0]) + const mag = Vector.magnitude(sub) + for (let i = 0, len = Math.floor(mag * 0.03 * energy / 0.2); i < len; i++) { + const dist = Math.random() + game.drawList.push({ + x: path[0].x + sub.x * dist + 13 * (Math.random() - 0.5), + y: path[0].y + sub.y * dist + 13 * (Math.random() - 0.5), + radius: 1 + 4 * Math.random(), + color: "rgba(255,0,0,0.5)", + time: Math.floor(2 + 33 * Math.random() * Math.random()) + }); + } } - } }; \ No newline at end of file diff --git a/js/game.js b/js/game.js index 4aab2b4..10b051b 100644 --- a/js/game.js +++ b/js/game.js @@ -1,1172 +1,1171 @@ // game Object ******************************************************** //********************************************************************* const game = { - loop() {}, //main game loop, gets se tto normal or testing loop - normalLoop() { - game.gravity(); - Engine.update(engine, game.delta); - game.wipe(); - game.textLog(); - if (mech.onGround) { - mech.groundControl() - } else { - mech.airControl() - } - // level.checkZones(); - level.checkQuery(); - mech.move(); - mech.look(); - game.checks(); - ctx.save(); - game.camera(); - level.drawFillBGs(); - level.exit.draw(); - level.enter.draw(); - level.custom(); - game.draw.powerUp(); - mobs.draw(); - game.draw.cons(); - game.draw.body(); - mobs.loop(); - mobs.healthBar(); - mech.draw(); - mech.hold(); - // v.draw(); //working on visibility work in progress - level.drawFills(); - level.customTopLayer(); - game.draw.drawMapPath(); - b.fire(); - b.bulletRemove(); - b.bulletDraw(); - b.bulletDo(); - game.drawCircle(); - // game.clip(); - ctx.restore(); - game.drawCursor(); - // game.pixelGraphics(); - }, - testingLoop() { - game.gravity(); - Engine.update(engine, game.delta); - game.wipe(); - game.textLog(); - if (mech.onGround) { - mech.groundControl() - } else { - mech.airControl() - } - // level.checkZones(); - level.custom(); - level.checkQuery(); - mech.move(); - mech.look(); - game.checks(); - ctx.save(); - game.camera(); - mech.draw(); - level.customTopLayer(); - game.draw.wireFrame(); - game.draw.cons(); - game.draw.testing(); - game.drawCircle(); - game.constructCycle() - ctx.restore(); - game.testingOutput(); - game.drawCursor(); - }, - isTimeSkipping: false, - timeSkip(cycles = 60) { - game.isTimeSkipping = true; - for (let i = 0; i < cycles; i++) { - game.cycle++; - mech.cycle++; - game.gravity(); - Engine.update(engine, game.delta); - if (mech.onGround) { - mech.groundControl() - } else { - mech.airControl() - } - - level.checkZones(); - level.checkQuery(); - mech.move(); - game.checks(); - mobs.loop(); - // mech.draw(); - mech.walk_cycle += mech.flipLegs * mech.Vx; - - mech.hold(); - b.fire(); - b.bulletRemove(); - b.bulletDo(); - } - game.isTimeSkipping = false; - }, - mouse: { - x: canvas.width / 2, - y: canvas.height / 2 - }, - mouseInGame: { - x: 0, - y: 0 - }, - g: 0.0024, // applies to player, bodies, and power ups (not mobs) - onTitlePage: true, - isCheating: false, - paused: false, - isChoosing: false, - testing: false, //testing mode: shows wire frame and some variables - cycle: 0, //total cycles, 60 per second - fpsCap: null, //limits frames per second to 144/2=72, on most monitors the fps is capped at 60fps by the hardware - fpsCapDefault: 72, //use to change fpsCap back to normal after a hit from a mob - isCommunityMaps: false, - cyclePaused: 0, - fallHeight: 3000, //below this y position the player dies - lastTimeStamp: 0, //tracks time stamps for measuring delta - delta: 1000 / 60, //speed of game engine //looks like it has to be 16 to match player input - buttonCD: 0, - levelsCleared: 0, - difficultyMode: 2, //normal difficulty is 2 - difficulty: 0, - dmgScale: null, //set in levels.setDifficulty - healScale: 1, - accelScale: null, //set in levels.setDifficulty - CDScale: null, //set in levels.setDifficulty - lookFreqScale: null, //set in levels.setDifficulty - isNoPowerUps: false, - // dropFPS(cap = 40, time = 15) { - // game.fpsCap = cap - // game.fpsInterval = 1000 / game.fpsCap; - // game.defaultFPSCycle = game.cycle + time - // const normalFPS = function () { - // if (game.defaultFPSCycle < game.cycle) { - // game.fpsCap = 72 - // game.fpsInterval = 1000 / game.fpsCap; - // } else { - // requestAnimationFrame(normalFPS); - // } - // }; - // requestAnimationFrame(normalFPS); - // }, - // clip() { - - // }, - pixelGraphics() { - //copy current canvas pixel data - let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); - let data = imgData.data; - //change pixel data - - - // const off = 4 * Math.floor(x) + 4 * canvas.width * Math.floor(y); - // multiple windows - for (let i = data.length / 2; i < data.length; i += 4) { - index = i % (canvas.width * canvas.height * 2) // + canvas.width*4*canvas.height - - data[i + 0] = data[index + 0]; // red - data[i + 1] = data[index + 1]; // red - data[i + 2] = data[index + 2]; // red - data[i + 3] = data[index + 3]; // red - } - - for (let x = 0; x < len; x++) { - - } - - - - // const startX = 2 * canvas.width + 2 * canvas.width * canvas.height - // const endX = 4 * canvas.width + 4 * canvas.width * canvas.height - // const startY = 2 * canvas.width + 2 * canvas.width * canvas.height - // const endY = 4 * canvas.width + 4 * canvas.width * canvas.height - // for (let x = startX; x < endX; x++) { - // for (let y = startY; y < endY; y++) { - - // } - // } - - - - - //strange draw offset - // const off = canvas.height * canvas.width * 4 / 2 - // for (let index = 0; index < data.length; index += 4) { - // data[index + 0] = data[index + 0 + off]; // red - // data[index + 1] = data[index + 1 + off]; // red - // data[index + 2] = data[index + 2 + off]; // red - // data[index + 3] = data[index + 3 + off]; // red - // } - - //change all pixels - // for (let index = 0; index < data.length; index += 4) { - // data[index + 0] = 255; // red - // data[index + 1] = 255; // green - // data[index + 2] = 255; // blue - // data[index + 3] = 255; // alpha - // } - - //change random pixels - // for (let i = 0, len = Math.floor(data.length / 10); i < len; ++i) { - // const index = Math.floor((Math.random() * data.length) / 4) * 4; - // data[index + 0] = 255; // red - // data[index + 1] = 0; // green - // data[index + 2] = 0; // blue - // data[index + 3] = 255 //Math.floor(Math.random() * Math.random() * 255); // alpha - // } - - // //change random pixels - // for (let i = 0, len = Math.floor(data.length / 1000); i < len; ++i) { - // const index = Math.floor((Math.random() * data.length) / 4) * 4; - // // data[index] = data[index] ^ 255; // Invert Red - // // data[index + 1] = data[index + 1] ^ 255; // Invert Green - // // data[index + 2] = data[index + 2] ^ 255; // Invert Blue - // data[index + 0] = 0; // red - // data[index + 1] = 0; // green - // data[index + 2] = 0; // blue - // // data[index + 3] = 255 //Math.floor(Math.random() * Math.random() * 255); // alpha - // } - - //draw new pixel data to canvas - ctx.putImageData(imgData, 0, 0); - }, - drawCursor() { - const size = 10; - ctx.beginPath(); - ctx.moveTo(game.mouse.x - size, game.mouse.y); - ctx.lineTo(game.mouse.x + size, game.mouse.y); - ctx.moveTo(game.mouse.x, game.mouse.y - size); - ctx.lineTo(game.mouse.x, game.mouse.y + size); - ctx.lineWidth = 2; - ctx.strokeStyle = "#000"; //'rgba(0,0,0,0.4)' - ctx.stroke(); // Draw it - }, - drawList: [], //so you can draw a first frame of explosions.. I know this is bad - drawTime: 8, //how long circles are drawn. use to push into drawlist.time - mobDmgColor: "rgba(255,0,0,0.7)", //used top push into drawList.color - playerDmgColor: "rgba(0,0,0,0.7)", //used top push into drawList.color - drawCircle() { - //draws a circle for two cycles, used for showing damage mostly - let i = game.drawList.length; - while (i--) { - ctx.beginPath(); //draw circle - ctx.arc(game.drawList[i].x, game.drawList[i].y, game.drawList[i].radius, 0, 2 * Math.PI); - ctx.fillStyle = game.drawList[i].color; - ctx.fill(); - if (game.drawList[i].time) { - //remove when timer runs out - game.drawList[i].time--; - } else { - game.drawList.splice(i, 1); - } - } - }, - lastLogTime: 0, - lastLogTimeBig: 0, - boldActiveGunHUD() { - if (b.inventory.length > 0) { - for (let i = 0, len = b.inventory.length; i < len; ++i) { - // document.getElementById(b.inventory[i]).style.fontSize = "25px"; - document.getElementById(b.inventory[i]).style.opacity = "0.3"; - } - // document.getElementById(b.activeGun).style.fontSize = "30px"; - if (document.getElementById(b.activeGun)) document.getElementById(b.activeGun).style.opacity = "1"; - } - - if (mod.isEntanglement && document.getElementById("mod-entanglement")) { - if (b.inventory[0] === b.activeGun) { - let lessDamage = 1 - for (let i = 0, len = b.inventory.length; i < len; i++) { - lessDamage *= 0.87 // 1 - 0.15 - } - document.getElementById("mod-entanglement").innerHTML = " " + ((1 - lessDamage) * 100).toFixed(0) + "%" - } else { - document.getElementById("mod-entanglement").innerHTML = " 0%" - } - } - }, - updateGunHUD() { - for (let i = 0, len = b.inventory.length; i < len; ++i) { - document.getElementById(b.inventory[i]).innerHTML = b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo; - } - }, - makeGunHUD() { - //remove all nodes - const myNode = document.getElementById("guns"); - while (myNode.firstChild) { - myNode.removeChild(myNode.firstChild); - } - //add nodes - for (let i = 0, len = b.inventory.length; i < len; ++i) { - const node = document.createElement("div"); - node.setAttribute("id", b.inventory[i]); - let textnode = document.createTextNode(b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo); - node.appendChild(textnode); - document.getElementById("guns").appendChild(node); - } - game.boldActiveGunHUD(); - }, - updateModHUD() { - let text = "" - for (let i = 0, len = mod.mods.length; i < len; i++) { //add mods - if (mod.mods[i].isLost) { - if (text) text += "
" //add a new line, but not on the first line - text += `${mod.mods[i].name}` - } else if (mod.mods[i].count > 0) { - if (text) text += "
" //add a new line, but not on the first line - text += mod.mods[i].name - if (mod.mods[i].nameInfo) { - text += mod.mods[i].nameInfo - mod.mods[i].addNameInfo(); - } - if (mod.mods[i].count > 1) text += ` (${mod.mods[i].count}x)` - } - } - document.getElementById("mods").innerHTML = text - }, - replaceTextLog: true, - // - // SVGleftMouse: ' ', - SVGrightMouse: ' ', - makeTextLog(text, time = 180) { - if (game.replaceTextLog) { - document.getElementById("text-log").innerHTML = text; - document.getElementById("text-log").style.opacity = 1; - game.lastLogTime = mech.cycle + time; - } - }, - textLog() { - if (game.lastLogTime && game.lastLogTime < mech.cycle) { - game.lastLogTime = 0; - game.replaceTextLog = true - // document.getElementById("text-log").innerHTML = " "; - document.getElementById("text-log").style.opacity = 0; - } - }, - nextGun() { - if (b.inventory.length > 0 && !mod.isGunCycle) { - b.inventoryGun++; - if (b.inventoryGun > b.inventory.length - 1) b.inventoryGun = 0; - game.switchGun(); - } - }, - previousGun() { - if (b.inventory.length > 0 && !mod.isGunCycle) { - b.inventoryGun--; - if (b.inventoryGun < 0) b.inventoryGun = b.inventory.length - 1; - game.switchGun(); - } - }, - switchGun() { - if (mod.isCrouchAmmo) mod.isCrouchAmmo = 1 //this prevents hacking the mod by switching guns - b.activeGun = b.inventory[b.inventoryGun]; - game.updateGunHUD(); - game.boldActiveGunHUD(); - // mech.drop(); - }, - zoom: null, - zoomScale: 1000, - isAutoZoom: true, - setZoom(zoomScale = game.zoomScale) { //use in window resize in index.js - game.zoomScale = zoomScale - game.zoom = canvas.height / zoomScale; //sets starting zoom scale - }, - zoomTransition(newZoomScale, step = 2) { - if (game.isAutoZoom) { - const isBigger = (newZoomScale - game.zoomScale > 0) ? true : false; - requestAnimationFrame(zLoop); - const currentLevel = level.onLevel - - function zLoop() { - if (currentLevel !== level.onLevel || game.isAutoZoom === false) return //stop the zoom if player goes to a new level - - if (isBigger) { - game.zoomScale += step - if (game.zoomScale >= newZoomScale) { - game.setZoom(newZoomScale); - return - } + loop() {}, //main game loop, gets se tto normal or testing loop + normalLoop() { + game.gravity(); + Engine.update(engine, game.delta); + game.wipe(); + game.textLog(); + if (mech.onGround) { + mech.groundControl() } else { - game.zoomScale -= step - if (game.zoomScale <= newZoomScale) { - game.setZoom(newZoomScale); - return - } + mech.airControl() } - - game.setZoom(); - requestAnimationFrame(zLoop); - } - } - }, - zoomInFactor: 0, - startZoomIn(time = 180) { - game.zoom = 0; - let count = 0; - requestAnimationFrame(zLoop); - - function zLoop() { - game.zoom += canvas.height / game.zoomScale / time; - count++; - if (count < time) { - requestAnimationFrame(zLoop); - } else { - game.setZoom(); - } - } - }, - noCameraScroll() { //makes the camera not scroll after changing locations - //only works if velocity is zero - mech.pos.x = player.position.x; - mech.pos.y = playerBody.position.y - mech.yOff; - const scale = 0.8; - mech.transSmoothX = canvas.width2 - mech.pos.x - (game.mouse.x - canvas.width2) * scale; - mech.transSmoothY = canvas.height2 - mech.pos.y - (game.mouse.y - canvas.height2) * scale; - mech.transX += (mech.transSmoothX - mech.transX) * 1; - mech.transY += (mech.transSmoothY - mech.transY) * 1; - }, - edgeZoomOutSmooth: 1, - camera() { - //zoom out when mouse gets near the edge of the window - const dx = game.mouse.x / window.innerWidth - 0.5 //x distance from mouse to window center scaled by window width - const dy = game.mouse.y / window.innerHeight - 0.5 //y distance from mouse to window center scaled by window height - const d = Math.max(dx * dx, dy * dy) - game.edgeZoomOutSmooth = (1 + 4 * d * d) * 0.04 + game.edgeZoomOutSmooth * 0.96 - - - ctx.save(); - ctx.translate(canvas.width2, canvas.height2); //center - ctx.scale(game.zoom / game.edgeZoomOutSmooth, game.zoom / game.edgeZoomOutSmooth); //zoom in once centered - ctx.translate(-canvas.width2 + mech.transX, -canvas.height2 + mech.transY); //translate - //calculate in game mouse position by undoing the zoom and translations - game.mouseInGame.x = (game.mouse.x - canvas.width2) / game.zoom * game.edgeZoomOutSmooth + canvas.width2 - mech.transX; - game.mouseInGame.y = (game.mouse.y - canvas.height2) / game.zoom * game.edgeZoomOutSmooth + canvas.height2 - mech.transY; - }, - restoreCamera() { - ctx.restore(); - }, - wipe() { - ctx.clearRect(0, 0, canvas.width, canvas.height); - }, - gravity() { - function addGravity(bodies, magnitude) { - for (var i = 0; i < bodies.length; i++) { - bodies[i].force.y += bodies[i].mass * magnitude; - } - } - addGravity(powerUp, game.g); - addGravity(body, game.g); - player.force.y += player.mass * game.g; - }, - reset() { //run on first run, and each later run after you die - input.endKeySensing(); - b.removeAllGuns(); - game.isNoPowerUps = false; - mod.setupAllMods(); //sets mods to default values - b.setFireCD(); - game.updateModHUD(); - powerUps.totalPowerUps = 0; - powerUps.reroll.rerolls = 0; - mech.setFillColors(); - mech.maxHealth = 1 - mech.maxEnergy = 1 - mech.energy = 1 - mech.hole.isOn = false - game.paused = false; - engine.timing.timeScale = 1; - game.fpsCap = game.fpsCapDefault; - game.isAutoZoom = true; - game.makeGunHUD(); - mech.drop(); - mech.holdingTarget = null - mech.addHealth(Infinity); - mech.alive = true; - level.onLevel = 0; - level.levelsCleared = 0; - - //resetting difficulty - game.dmgScale = 0; //increases in level.difficultyIncrease - b.dmgScale = 1; //decreases in level.difficultyIncrease - game.accelScale = 1; - game.lookFreqScale = 1; - game.CDScale = 1; - game.difficulty = 0; - game.difficultyMode = Number(document.getElementById("difficulty-select").value) - build.isCustomSelection = false; - // if (game.difficultyMode > 2) { - // level.difficultyIncrease(game.difficultyMode) - // level.difficultyIncrease(game.difficultyMode) - // } - - game.clearNow = true; - document.getElementById("text-log").style.opacity = 0; - document.getElementById("fade-out").style.opacity = 0; - document.title = "n-gon"; - //set to default field - mech.fieldMode = 0; - game.replaceTextLog = true; - game.makeTextLog(`${game.SVGrightMouse} ${mech.fieldUpgrades[mech.fieldMode].name}

${mech.fieldUpgrades[mech.fieldMode].description}`, 600); - mech.setField(mech.fieldMode) - //exit testing - if (game.testing) { - game.testing = false; - game.loop = game.normalLoop - if (game.isConstructionMode) { - document.getElementById("construct").style.display = 'none' - } - } - game.isCheating = false - }, - firstRun: true, - splashReturn() { - game.onTitlePage = true; - // document.getElementById('splash').onclick = 'run(this)'; - document.getElementById("splash").onclick = function () { - game.startGame(); - }; - document.getElementById("choose-grid").style.display = "none" - document.getElementById("info").style.display = "inline"; - document.getElementById("build-button").style.display = "inline" - document.getElementById("build-grid").style.display = "none" - document.getElementById("pause-grid-left").style.display = "none" - document.getElementById("pause-grid-right").style.display = "none" - document.getElementById("splash").style.display = "inline"; - document.getElementById("dmg").style.display = "none"; - document.getElementById("health-bg").style.display = "none"; - document.body.style.cursor = "auto"; - }, - fpsInterval: 0, //set in startGame - then: null, - startGame(isBuildRun = false) { - if (!isBuildRun) { //if a build run logic flow returns to "build-button").addEventListener - document.body.style.cursor = "none"; - document.body.style.overflow = "hidden" - } - game.onTitlePage = false; - document.getElementById("choose-grid").style.display = "none" - document.getElementById("build-grid").style.display = "none" - document.getElementById("info").style.display = "none"; - document.getElementById("build-button").style.display = "none"; - document.getElementById("splash").onclick = null; //removes the onclick effect so the function only runs once - document.getElementById("splash").style.display = "none"; //hides the element that spawned the function - document.getElementById("dmg").style.display = "inline"; - document.getElementById("health-bg").style.display = "inline"; - - if (game.firstRun) { - mech.spawn(); //spawns the player - if (game.isCommunityMaps) { - level.levels.push("stronghold"); - level.levels.push("basement"); - level.levels.push("detours"); - level.levels.push("house"); - } - level.levels = shuffle(level.levels); //shuffles order of maps - level.levels.unshift("bosses"); //add bosses level to the end of the randomized levels list - } - game.reset(); - game.firstRun = false; - - //setup FPS cap - game.fpsInterval = 1000 / game.fpsCap; - game.then = Date.now(); - requestAnimationFrame(cycle); //starts game loop - }, - clearNow: false, - clearMap() { - if (mod.isMineAmmoBack) { - let count = 0; - for (i = 0, len = bullet.length; i < len; i++) { //count mines left on map - if (bullet[i].bulletType === "mine") count++ - } - for (i = 0, len = b.guns.length; i < len; i++) { //find which gun is mine - if (b.guns[i].name === "mine") { - if (mod.isCrouchAmmo) count = Math.ceil(count / 2) - b.guns[i].ammo += count - game.updateGunHUD(); - break; + // level.checkZones(); + level.checkQuery(); + mech.move(); + mech.look(); + game.checks(); + ctx.save(); + game.camera(); + level.drawFillBGs(); + level.exit.draw(); + level.enter.draw(); + level.custom(); + game.draw.powerUp(); + mobs.draw(); + game.draw.cons(); + game.draw.body(); + mobs.loop(); + mobs.healthBar(); + mech.draw(); + mech.hold(); + // v.draw(); //working on visibility work in progress + level.drawFills(); + level.customTopLayer(); + game.draw.drawMapPath(); + b.fire(); + b.bulletRemove(); + b.bulletDraw(); + b.bulletDo(); + game.drawCircle(); + // game.clip(); + ctx.restore(); + game.drawCursor(); + // game.pixelGraphics(); + }, + testingLoop() { + game.gravity(); + Engine.update(engine, game.delta); + game.wipe(); + game.textLog(); + if (mech.onGround) { + mech.groundControl() + } else { + mech.airControl() } - } - } - - if (mod.isMutualism && !mod.isEnergyHealth) { - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].isMutualismActive) { - mech.health += 0.01 - if (mech.health > mech.maxHealth) mech.health = mech.maxHealth; - mech.displayHealth(); - } - } - } - - powerUps.totalPowerUps = powerUp.length - - let holdTarget; //if player is holding something this remembers it before it gets deleted - if (mech.holdingTarget) holdTarget = mech.holdingTarget; - - mech.fireCDcycle = 0 - mech.drop(); - mech.hole.isOn = false; - level.fill = []; - level.fillBG = []; - level.zones = []; - level.queryList = []; - game.drawList = []; - - function removeAll(array) { - for (let i = 0; i < array.length; ++i) Matter.World.remove(engine.world, array[i]); - } - removeAll(map); - map = []; - removeAll(body); - body = []; - removeAll(mob); - mob = []; - removeAll(powerUp); - powerUp = []; - removeAll(cons); - cons = []; - removeAll(consBB); - consBB = []; - removeAll(bullet); - bullet = []; - removeAll(composite); - composite = []; - // if player was holding something this makes a new copy to hold - if (holdTarget) { - len = body.length; - body[len] = Matter.Bodies.fromVertices(0, 0, holdTarget.vertices, { - friction: holdTarget.friction, - frictionAir: holdTarget.frictionAir, - frictionStatic: holdTarget.frictionStatic - }); - Matter.Body.setPosition(body[len], mech.pos); - mech.isHolding = true - mech.holdingTarget = body[len]; - mech.holdingTarget.collisionFilter.category = 0; - mech.holdingTarget.collisionFilter.mask = 0; - } - //set fps back to default - game.fpsCap = game.fpsCapDefault - game.fpsInterval = 1000 / game.fpsCap; - }, - // getCoords: { - // //used when building maps, outputs a draw rect command to console, only works in testing mode - // pos1: { - // x: 0, - // y: 0 - // }, - // pos2: { - // x: 0, - // y: 0 - // }, - // out() { - // if (keys[49]) { - // game.getCoords.pos1.x = Math.round(game.mouseInGame.x / 25) * 25; - // game.getCoords.pos1.y = Math.round(game.mouseInGame.y / 25) * 25; - // } - // if (keys[50]) { - // //press 1 in the top left; press 2 in the bottom right;copy command from console - // game.getCoords.pos2.x = Math.round(game.mouseInGame.x / 25) * 25; - // game.getCoords.pos2.y = Math.round(game.mouseInGame.y / 25) * 25; - // window.getSelection().removeAllRanges(); - // var range = document.createRange(); - // range.selectNode(document.getElementById("test")); - // window.getSelection().addRange(range); - // document.execCommand("copy"); - // window.getSelection().removeAllRanges(); - // console.log(`spawn.mapRect(${game.getCoords.pos1.x}, ${game.getCoords.pos1.y}, ${game.getCoords.pos2.x - game.getCoords.pos1.x}, ${game.getCoords.pos2.y - game.getCoords.pos1.y}); //`); - // } - // } - // }, - 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 - - 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 - }); - Matter.Body.setPosition(player, { - x: level.enter.x + 50, - y: level.enter.y - 20 - }); - // move bots - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType) { - Matter.Body.setPosition(bullet[i], Vector.add(player.position, { - x: 250 * (Math.random() - 0.5), - y: 250 * (Math.random() - 0.5) - })); - Matter.Body.setVelocity(bullet[i], { - x: 0, - y: 0 - }); - } - } - mech.damage(0.1 * game.difficultyMode); - mech.energy -= 0.1 * game.difficultyMode - } - - // if (mod.isEnergyDamage) { - // document.getElementById("mod-capacitor").innerHTML = `(+${(mech.energy/0.05).toFixed(0)}%)` - // } - // if (mod.restDamage) { - // if (player.speed < 1) { - // document.getElementById("mod-rest").innerHTML = `(+20%)` - // } else { - // document.getElementById("mod-rest").innerHTML = `(+0%)` - // } - // } - - 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.isHealthRecovery) mech.addHealth(0.01) - } - - if (!(game.cycle % 420)) { //once every 7 seconds - fallCheck = function (who, save = false) { - let i = who.length; - while (i--) { - if (who[i].position.y > game.fallHeight) { - if (save) { - Matter.Body.setVelocity(who[i], { - x: 0, - y: 0 - }); - Matter.Body.setPosition(who[i], { - x: level.exit.x + 30 * (Math.random() - 0.5), - y: level.exit.y + 30 * (Math.random() - 0.5) - }); - } else { - Matter.World.remove(engine.world, who[i]); - who.splice(i, 1); - } + // level.checkZones(); + level.custom(); + level.checkQuery(); + mech.move(); + mech.look(); + game.checks(); + ctx.save(); + game.camera(); + mech.draw(); + level.customTopLayer(); + game.draw.wireFrame(); + game.draw.cons(); + game.draw.testing(); + game.drawCircle(); + game.constructCycle() + ctx.restore(); + game.testingOutput(); + game.drawCursor(); + }, + isTimeSkipping: false, + timeSkip(cycles = 60) { + game.isTimeSkipping = true; + for (let i = 0; i < cycles; i++) { + game.cycle++; + mech.cycle++; + game.gravity(); + Engine.update(engine, game.delta); + if (mech.onGround) { + mech.groundControl() + } else { + mech.airControl() } - } - }; - fallCheck(mob); - fallCheck(body); - fallCheck(powerUp, true); - } - } - }, - testingOutput() { - ctx.fillStyle = "#000"; - if (!game.isConstructionMode) { - // ctx.textAlign = "right"; - ctx.fillText("T: exit testing mode", canvas.width / 2, canvas.height - 10); - // let line = 500; - // const x = canvas.width - 5; - // ctx.fillText("T: exit testing mode", x, line); - // line += 20; - // ctx.fillText("Y: give all mods", x, line); - // line += 20; - // ctx.fillText("R: teleport to mouse", x, line); - // line += 20; - // ctx.fillText("F: cycle field", x, line); - // line += 20; - // ctx.fillText("G: give all guns", x, line); - // line += 20; - // ctx.fillText("H: heal", x, line); - // line += 20; - // ctx.fillText("U: next level", x, line); - // line += 20; - // ctx.fillText("1-7: spawn things", x, line); - } - ctx.textAlign = "center"; - ctx.fillText(`(${game.mouseInGame.x.toFixed(1)}, ${game.mouseInGame.y.toFixed(1)})`, game.mouse.x, game.mouse.y - 20); - }, - draw: { - powerUp() { //is set by Bayesian mod - // ctx.globalAlpha = 0.4 * Math.sin(mech.cycle * 0.15) + 0.6; - // for (let i = 0, len = powerUp.length; i < len; ++i) { - // ctx.beginPath(); - // ctx.arc(powerUp[i].position.x, powerUp[i].position.y, powerUp[i].size, 0, 2 * Math.PI); - // ctx.fillStyle = powerUp[i].color; - // ctx.fill(); - // } - // ctx.globalAlpha = 1; - }, - powerUpNormal() { //back up in case power up draw gets changed - ctx.globalAlpha = 0.4 * Math.sin(mech.cycle * 0.15) + 0.6; - for (let i = 0, len = powerUp.length; i < len; ++i) { - ctx.beginPath(); - ctx.arc(powerUp[i].position.x, powerUp[i].position.y, powerUp[i].size, 0, 2 * Math.PI); - ctx.fillStyle = powerUp[i].color; - ctx.fill(); - } - ctx.globalAlpha = 1; - }, - powerUpBonus() { //draws crackle effect for bonus power ups - for (let i = 0, len = powerUp.length; i < len; ++i) { - ctx.globalAlpha = 0.4 * Math.sin(mech.cycle * 0.15) + 0.6; - for (let i = 0, len = powerUp.length; i < len; ++i) { - ctx.beginPath(); - ctx.arc(powerUp[i].position.x, powerUp[i].position.y, powerUp[i].size, 0, 2 * Math.PI); - ctx.fillStyle = powerUp[i].color; - ctx.fill(); - } - ctx.globalAlpha = 1; - if (powerUp[i].isBonus && Math.random() < 0.1) { - //draw electricity - const mag = 5 + powerUp[i].size / 5 - let unit = Vector.rotate({ - x: mag, - y: mag - }, 2 * Math.PI * Math.random()) - let path = { - x: powerUp[i].position.x + unit.x, - y: powerUp[i].position.y + unit.y - } - ctx.beginPath(); - ctx.moveTo(path.x, path.y); - for (let i = 0; i < 6; i++) { - unit = Vector.rotate(unit, 3 * (Math.random() - 0.5)) - path = Vector.add(path, unit) - ctx.lineTo(path.x, path.y); - } - ctx.lineWidth = 0.5 + 2 * Math.random(); - ctx.strokeStyle = "#000" - ctx.stroke(); - } - } - // ctx.globalAlpha = 1; - }, + level.checkZones(); + level.checkQuery(); + mech.move(); + game.checks(); + mobs.loop(); + // mech.draw(); + mech.walk_cycle += mech.flipLegs * mech.Vx; - // map: function() { - // ctx.beginPath(); - // for (let i = 0, len = map.length; i < len; ++i) { - // let vertices = map[i].vertices; - // ctx.moveTo(vertices[0].x, vertices[0].y); - // for (let j = 1; j < vertices.length; j += 1) { - // ctx.lineTo(vertices[j].x, vertices[j].y); - // } - // ctx.lineTo(vertices[0].x, vertices[0].y); + mech.hold(); + b.fire(); + b.bulletRemove(); + b.bulletDo(); + } + game.isTimeSkipping = false; + }, + mouse: { + x: canvas.width / 2, + y: canvas.height / 2 + }, + mouseInGame: { + x: 0, + y: 0 + }, + g: 0.0024, // applies to player, bodies, and power ups (not mobs) + onTitlePage: true, + isCheating: false, + paused: false, + isChoosing: false, + testing: false, //testing mode: shows wire frame and some variables + cycle: 0, //total cycles, 60 per second + fpsCap: null, //limits frames per second to 144/2=72, on most monitors the fps is capped at 60fps by the hardware + fpsCapDefault: 72, //use to change fpsCap back to normal after a hit from a mob + isCommunityMaps: false, + cyclePaused: 0, + fallHeight: 3000, //below this y position the player dies + lastTimeStamp: 0, //tracks time stamps for measuring delta + delta: 1000 / 60, //speed of game engine //looks like it has to be 16 to match player input + buttonCD: 0, + levelsCleared: 0, + difficultyMode: 2, //normal difficulty is 2 + difficulty: 0, + dmgScale: null, //set in levels.setDifficulty + healScale: 1, + accelScale: null, //set in levels.setDifficulty + CDScale: null, //set in levels.setDifficulty + lookFreqScale: null, //set in levels.setDifficulty + isNoPowerUps: false, + // dropFPS(cap = 40, time = 15) { + // game.fpsCap = cap + // game.fpsInterval = 1000 / game.fpsCap; + // game.defaultFPSCycle = game.cycle + time + // const normalFPS = function () { + // if (game.defaultFPSCycle < game.cycle) { + // game.fpsCap = 72 + // game.fpsInterval = 1000 / game.fpsCap; + // } else { + // requestAnimationFrame(normalFPS); // } - // ctx.fillStyle = "#444"; - // ctx.fill(); + // }; + // requestAnimationFrame(normalFPS); // }, - mapPath: null, //holds the path for the map to speed up drawing - setPaths() { - //runs at each new level to store the path for the map since the map doesn't change - game.draw.mapPath = new Path2D(); - for (let i = 0, len = map.length; i < len; ++i) { - let vertices = map[i].vertices; - game.draw.mapPath.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j += 1) { - game.draw.mapPath.lineTo(vertices[j].x, vertices[j].y); + // clip() { + + // }, + pixelGraphics() { + //copy current canvas pixel data + let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); + let data = imgData.data; + //change pixel data + + + // const off = 4 * Math.floor(x) + 4 * canvas.width * Math.floor(y); + // multiple windows + for (let i = data.length / 2; i < data.length; i += 4) { + index = i % (canvas.width * canvas.height * 2) // + canvas.width*4*canvas.height + + data[i + 0] = data[index + 0]; // red + data[i + 1] = data[index + 1]; // red + data[i + 2] = data[index + 2]; // red + data[i + 3] = data[index + 3]; // red } - game.draw.mapPath.lineTo(vertices[0].x, vertices[0].y); - } + + for (let x = 0; x < len; x++) { + + } + + + + // const startX = 2 * canvas.width + 2 * canvas.width * canvas.height + // const endX = 4 * canvas.width + 4 * canvas.width * canvas.height + // const startY = 2 * canvas.width + 2 * canvas.width * canvas.height + // const endY = 4 * canvas.width + 4 * canvas.width * canvas.height + // for (let x = startX; x < endX; x++) { + // for (let y = startY; y < endY; y++) { + + // } + // } + + + + + //strange draw offset + // const off = canvas.height * canvas.width * 4 / 2 + // for (let index = 0; index < data.length; index += 4) { + // data[index + 0] = data[index + 0 + off]; // red + // data[index + 1] = data[index + 1 + off]; // red + // data[index + 2] = data[index + 2 + off]; // red + // data[index + 3] = data[index + 3 + off]; // red + // } + + //change all pixels + // for (let index = 0; index < data.length; index += 4) { + // data[index + 0] = 255; // red + // data[index + 1] = 255; // green + // data[index + 2] = 255; // blue + // data[index + 3] = 255; // alpha + // } + + //change random pixels + // for (let i = 0, len = Math.floor(data.length / 10); i < len; ++i) { + // const index = Math.floor((Math.random() * data.length) / 4) * 4; + // data[index + 0] = 255; // red + // data[index + 1] = 0; // green + // data[index + 2] = 0; // blue + // data[index + 3] = 255 //Math.floor(Math.random() * Math.random() * 255); // alpha + // } + + // //change random pixels + // for (let i = 0, len = Math.floor(data.length / 1000); i < len; ++i) { + // const index = Math.floor((Math.random() * data.length) / 4) * 4; + // // data[index] = data[index] ^ 255; // Invert Red + // // data[index + 1] = data[index + 1] ^ 255; // Invert Green + // // data[index + 2] = data[index + 2] ^ 255; // Invert Blue + // data[index + 0] = 0; // red + // data[index + 1] = 0; // green + // data[index + 2] = 0; // blue + // // data[index + 3] = 255 //Math.floor(Math.random() * Math.random() * 255); // alpha + // } + + //draw new pixel data to canvas + ctx.putImageData(imgData, 0, 0); }, - mapFill: "#444", - bodyFill: "rgba(140,140,140,0.85)", //"#999", - bodyStroke: "#222", - drawMapPath() { - ctx.fillStyle = game.draw.mapFill; - ctx.fill(game.draw.mapPath); + drawCursor() { + const size = 10; + ctx.beginPath(); + ctx.moveTo(game.mouse.x - size, game.mouse.y); + ctx.lineTo(game.mouse.x + size, game.mouse.y); + ctx.moveTo(game.mouse.x, game.mouse.y - size); + ctx.lineTo(game.mouse.x, game.mouse.y + size); + ctx.lineWidth = 2; + ctx.strokeStyle = "#000"; //'rgba(0,0,0,0.4)' + ctx.stroke(); // Draw it }, - body() { - ctx.beginPath(); - for (let i = 0, len = body.length; i < len; ++i) { - let vertices = body[i].vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j++) { - ctx.lineTo(vertices[j].x, vertices[j].y); + drawList: [], //so you can draw a first frame of explosions.. I know this is bad + drawTime: 8, //how long circles are drawn. use to push into drawlist.time + mobDmgColor: "rgba(255,0,0,0.7)", //used top push into drawList.color + playerDmgColor: "rgba(0,0,0,0.7)", //used top push into drawList.color + drawCircle() { + //draws a circle for two cycles, used for showing damage mostly + let i = game.drawList.length; + while (i--) { + ctx.beginPath(); //draw circle + ctx.arc(game.drawList[i].x, game.drawList[i].y, game.drawList[i].radius, 0, 2 * Math.PI); + ctx.fillStyle = game.drawList[i].color; + ctx.fill(); + if (game.drawList[i].time) { + //remove when timer runs out + game.drawList[i].time--; + } else { + game.drawList.splice(i, 1); + } } - ctx.lineTo(vertices[0].x, vertices[0].y); - } - ctx.lineWidth = 2; - ctx.fillStyle = game.draw.bodyFill; - ctx.fill(); - ctx.strokeStyle = game.draw.bodyStroke; - ctx.stroke(); }, - cons() { - ctx.beginPath(); - for (let i = 0, len = cons.length; i < len; ++i) { - ctx.moveTo(cons[i].pointA.x, cons[i].pointA.y); - ctx.lineTo(cons[i].bodyB.position.x, cons[i].bodyB.position.y); - } - for (let i = 0, len = consBB.length; i < len; ++i) { - ctx.moveTo(consBB[i].bodyA.position.x, consBB[i].bodyA.position.y); - ctx.lineTo(consBB[i].bodyB.position.x, consBB[i].bodyB.position.y); - } - ctx.lineWidth = 2; - // ctx.strokeStyle = "#999"; - ctx.strokeStyle = "rgba(0,0,0,0.15)"; - ctx.stroke(); + lastLogTime: 0, + lastLogTimeBig: 0, + boldActiveGunHUD() { + if (b.inventory.length > 0) { + for (let i = 0, len = b.inventory.length; i < len; ++i) { + // document.getElementById(b.inventory[i]).style.fontSize = "25px"; + document.getElementById(b.inventory[i]).style.opacity = "0.3"; + } + // document.getElementById(b.activeGun).style.fontSize = "30px"; + if (document.getElementById(b.activeGun)) document.getElementById(b.activeGun).style.opacity = "1"; + } + + if (mod.isEntanglement && document.getElementById("mod-entanglement")) { + if (b.inventory[0] === b.activeGun) { + let lessDamage = 1 + for (let i = 0, len = b.inventory.length; i < len; i++) { + lessDamage *= 0.87 // 1 - 0.15 + } + document.getElementById("mod-entanglement").innerHTML = " " + ((1 - lessDamage) * 100).toFixed(0) + "%" + } else { + document.getElementById("mod-entanglement").innerHTML = " 0%" + } + } }, - wireFrame() { - // ctx.textAlign = "center"; - // ctx.textBaseline = "middle"; - // ctx.fillStyle = "#999"; - const bodies = Composite.allBodies(engine.world); - ctx.beginPath(); - for (let i = 0; i < bodies.length; ++i) { - //ctx.fillText(bodies[i].id,bodies[i].position.x,bodies[i].position.y); //shows the id of every body - let vertices = bodies[i].vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j += 1) { - ctx.lineTo(vertices[j].x, vertices[j].y); + updateGunHUD() { + for (let i = 0, len = b.inventory.length; i < len; ++i) { + document.getElementById(b.inventory[i]).innerHTML = b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo; } - ctx.lineTo(vertices[0].x, vertices[0].y); - } - ctx.lineWidth = 1; - ctx.strokeStyle = "#000"; - ctx.stroke(); }, - testing() { - //query zones - // ctx.beginPath(); - // for (let i = 0, len = level.queryList.length; i < len; ++i) { - // ctx.rect( - // level.queryList[i].bounds.max.x, - // level.queryList[i].bounds.max.y, - // level.queryList[i].bounds.min.x - level.queryList[i].bounds.max.x, - // level.queryList[i].bounds.min.y - level.queryList[i].bounds.max.y - // ); - // } - // ctx.fillStyle = "rgba(0, 0, 255, 0.2)"; - // ctx.fill(); - //jump - ctx.beginPath(); - let bodyDraw = jumpSensor.vertices; - ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); - for (let j = 1; j < bodyDraw.length; ++j) { - ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); - } - ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); - ctx.fillStyle = "rgba(255, 0, 0, 0.5)"; - ctx.fill(); - // ctx.strokeStyle = "#000"; - // ctx.stroke(); - //main body - ctx.beginPath(); - bodyDraw = playerBody.vertices; - ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); - for (let j = 1; j < bodyDraw.length; ++j) { - ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); - } - ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); - ctx.fillStyle = "rgba(0, 255, 255, 0.25)"; - ctx.fill(); - // ctx.stroke(); - //head - ctx.beginPath(); - bodyDraw = playerHead.vertices; - ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); - for (let j = 1; j < bodyDraw.length; ++j) { - ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); - } - ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); - ctx.fillStyle = "rgba(255, 255, 0, 0.4)"; - ctx.fill(); - // ctx.stroke(); - //head sensor - ctx.beginPath(); - bodyDraw = headSensor.vertices; - ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); - for (let j = 1; j < bodyDraw.length; ++j) { - ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); - } - ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); - ctx.fillStyle = "rgba(0, 0, 255, 0.25)"; - ctx.fill(); - // ctx.stroke(); - } - }, - checkLineIntersection(v1, v1End, v2, v2End) { - // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point - let denominator, a, b, numerator1, numerator2; - let result = { - x: null, - y: null, - onLine1: false, - onLine2: false - }; - denominator = (v2End.y - v2.y) * (v1End.x - v1.x) - (v2End.x - v2.x) * (v1End.y - v1.y); - if (denominator == 0) { - return result; - } - a = v1.y - v2.y; - b = v1.x - v2.x; - numerator1 = (v2End.x - v2.x) * a - (v2End.y - v2.y) * b; - numerator2 = (v1End.x - v1.x) * a - (v1End.y - v1.y) * b; - a = numerator1 / denominator; - b = numerator2 / denominator; - - // if we cast these lines infinitely in both directions, they intersect here: - result.x = v1.x + a * (v1End.x - v1.x); - result.y = v1.y + a * (v1End.y - v1.y); - // if line1 is a segment and line2 is infinite, they intersect if: - if (a > 0 && a < 1) result.onLine1 = true; - // if line2 is a segment and line1 is infinite, they intersect if: - if (b > 0 && b < 1) result.onLine2 = true; - // if line1 and line2 are segments, they intersect if both of the above are true - return result; - }, - copyToClipBoard(value) { - // Create a fake textarea - const textAreaEle = document.createElement('textarea'); - - // Reset styles - textAreaEle.style.border = '0'; - textAreaEle.style.padding = '0'; - textAreaEle.style.margin = '0'; - - // Set the absolute position - // User won't see the element - textAreaEle.style.position = 'absolute'; - textAreaEle.style.left = '-9999px'; - textAreaEle.style.top = `0px`; - - // Set the value - textAreaEle.value = value - - // Append the textarea to body - document.body.appendChild(textAreaEle); - - // Focus and select the text - textAreaEle.focus(); - textAreaEle.select(); - - // Execute the "copy" command - try { - document.execCommand('copy'); - } catch (err) { - // Unable to copy - console.log(err) - } finally { - // Remove the textarea - document.body.removeChild(textAreaEle); - } - }, - constructMouseDownPosition: { - x: 0, - y: 0 - }, - constructMapString: [], - constructCycle() { - if (game.isConstructionMode && game.constructMouseDownPosition) { - function round(num, round = 25) { - return Math.ceil(num / round) * round; - } - const x = round(game.constructMouseDownPosition.x) - const y = round(game.constructMouseDownPosition.y) - const dx = Math.max(25, round(game.mouseInGame.x) - x) - const dy = Math.max(25, round(game.mouseInGame.y) - y) - - ctx.strokeStyle = "#000" - ctx.lineWidth = 2; - ctx.strokeRect(x, y, dx, dy); - } - }, - outputMapString(string) { - if (string) game.constructMapString.push(string) //store command as a string in the next element of an array - let out = "" //combine set of map strings to one string - let outHTML = "" - for (let i = 0, len = game.constructMapString.length; i < len; i++) { - out += game.constructMapString[i]; - outHTML += "
" + game.constructMapString[i] + "
" - } - console.log(out) - game.copyToClipBoard(out) - document.getElementById("construct").innerHTML = outHTML - }, - enableConstructMode() { - game.isConstructionMode = true; - game.isAutoZoom = false; - game.zoomScale = 2600; - game.setZoom(); - - document.body.addEventListener("mouseup", (e) => { - if (game.testing && game.constructMouseDownPosition) { - function round(num, round = 25) { - return Math.ceil(num / round) * round; + makeGunHUD() { + //remove all nodes + const myNode = document.getElementById("guns"); + while (myNode.firstChild) { + myNode.removeChild(myNode.firstChild); } - //clean up positions - const x = round(game.constructMouseDownPosition.x) - const y = round(game.constructMouseDownPosition.y) - const dx = Math.max(25, round(game.mouseInGame.x) - x) - const dy = Math.max(25, round(game.mouseInGame.y) - y) - - if (e.which === 2) { - game.outputMapString(`spawn.randomMob(${x}, ${y},0.5);`); - } else if (game.mouseInGame.x > game.constructMouseDownPosition.x && game.mouseInGame.y > game.constructMouseDownPosition.y) { //make sure that the width and height are positive - if (e.which === 1) { //add map - game.outputMapString(`spawn.mapRect(${x}, ${y}, ${dx}, ${dy});`); - - //see map in world - spawn.mapRect(x, y, dx, dy); - len = map.length - 1 - map[len].collisionFilter.category = cat.map; - map[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; - Matter.Body.setStatic(map[len], true); //make static - World.add(engine.world, map[len]); //add to world - - game.draw.setPaths() //update map graphics - } else if (e.which === 3) { //add body - game.outputMapString(`spawn.bodyRect(${x}, ${y}, ${dx}, ${dy});`); - - //see map in world - spawn.bodyRect(x, y, dx, dy); - len = body.length - 1 - body[len].collisionFilter.category = cat.body; - body[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet - World.add(engine.world, body[len]); //add to world - body[len].classType = "body" - } + //add nodes + for (let i = 0, len = b.inventory.length; i < len; ++i) { + const node = document.createElement("div"); + node.setAttribute("id", b.inventory[i]); + let textnode = document.createTextNode(b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo); + node.appendChild(textnode); + document.getElementById("guns").appendChild(node); } - } - game.constructMouseDownPosition.x = undefined - game.constructMouseDownPosition.y = undefined - }); - game.constructMouseDownPosition.x = undefined - game.constructMouseDownPosition.y = undefined - document.body.addEventListener("mousedown", (e) => { - if (game.testing) { - game.constructMouseDownPosition.x = game.mouseInGame.x - game.constructMouseDownPosition.y = game.mouseInGame.y - } - }); - - document.body.addEventListener("keydown", (e) => { // e.keyCode z=90 m=77 b=66 shift = 16 c = 67 - if (game.testing && e.keyCode === 90 && game.constructMapString.length) { - if (game.constructMapString[game.constructMapString.length - 1][6] === 'm') { //remove map from current level - const index = map.length - 1 - Matter.World.remove(engine.world, map[index]); - map.splice(index, 1); - game.draw.setPaths() //update map graphics - } else if (game.constructMapString[game.constructMapString.length - 1][6] === 'b') { //remove body from current level - const index = body.length - 1 - Matter.World.remove(engine.world, body[index]); - body.splice(index, 1); + game.boldActiveGunHUD(); + }, + updateModHUD() { + let text = "" + for (let i = 0, len = mod.mods.length; i < len; i++) { //add mods + if (mod.mods[i].isLost) { + if (text) text += "
" //add a new line, but not on the first line + text += `${mod.mods[i].name}` + } else if (mod.mods[i].count > 0) { + if (text) text += "
" //add a new line, but not on the first line + text += mod.mods[i].name + if (mod.mods[i].nameInfo) { + text += mod.mods[i].nameInfo + mod.mods[i].addNameInfo(); + } + if (mod.mods[i].count > 1) text += ` (${mod.mods[i].count}x)` + } } - game.constructMapString.pop(); - game.outputMapString(); - } - }); - } + document.getElementById("mods").innerHTML = text + }, + replaceTextLog: true, + // + // SVGleftMouse: ' ', + SVGrightMouse: ' ', + makeTextLog(text, time = 180) { + if (game.replaceTextLog) { + document.getElementById("text-log").innerHTML = text; + document.getElementById("text-log").style.opacity = 1; + game.lastLogTime = mech.cycle + time; + } + }, + textLog() { + if (game.lastLogTime && game.lastLogTime < mech.cycle) { + game.lastLogTime = 0; + game.replaceTextLog = true + // document.getElementById("text-log").innerHTML = " "; + document.getElementById("text-log").style.opacity = 0; + } + }, + nextGun() { + if (b.inventory.length > 0 && !mod.isGunCycle) { + b.inventoryGun++; + if (b.inventoryGun > b.inventory.length - 1) b.inventoryGun = 0; + game.switchGun(); + } + }, + previousGun() { + if (b.inventory.length > 0 && !mod.isGunCycle) { + b.inventoryGun--; + if (b.inventoryGun < 0) b.inventoryGun = b.inventory.length - 1; + game.switchGun(); + } + }, + switchGun() { + if (mod.isCrouchAmmo) mod.isCrouchAmmo = 1 //this prevents hacking the mod by switching guns + b.activeGun = b.inventory[b.inventoryGun]; + game.updateGunHUD(); + game.boldActiveGunHUD(); + // mech.drop(); + }, + zoom: null, + zoomScale: 1000, + isAutoZoom: true, + setZoom(zoomScale = game.zoomScale) { //use in window resize in index.js + game.zoomScale = zoomScale + game.zoom = canvas.height / zoomScale; //sets starting zoom scale + }, + zoomTransition(newZoomScale, step = 2) { + if (game.isAutoZoom) { + const isBigger = (newZoomScale - game.zoomScale > 0) ? true : false; + requestAnimationFrame(zLoop); + const currentLevel = level.onLevel + + function zLoop() { + if (currentLevel !== level.onLevel || game.isAutoZoom === false) return //stop the zoom if player goes to a new level + + if (isBigger) { + game.zoomScale += step + if (game.zoomScale >= newZoomScale) { + game.setZoom(newZoomScale); + return + } + } else { + game.zoomScale -= step + if (game.zoomScale <= newZoomScale) { + game.setZoom(newZoomScale); + return + } + } + + game.setZoom(); + requestAnimationFrame(zLoop); + } + } + }, + zoomInFactor: 0, + startZoomIn(time = 180) { + game.zoom = 0; + let count = 0; + requestAnimationFrame(zLoop); + + function zLoop() { + game.zoom += canvas.height / game.zoomScale / time; + count++; + if (count < time) { + requestAnimationFrame(zLoop); + } else { + game.setZoom(); + } + } + }, + noCameraScroll() { //makes the camera not scroll after changing locations + //only works if velocity is zero + mech.pos.x = player.position.x; + mech.pos.y = playerBody.position.y - mech.yOff; + const scale = 0.8; + mech.transSmoothX = canvas.width2 - mech.pos.x - (game.mouse.x - canvas.width2) * scale; + mech.transSmoothY = canvas.height2 - mech.pos.y - (game.mouse.y - canvas.height2) * scale; + mech.transX += (mech.transSmoothX - mech.transX) * 1; + mech.transY += (mech.transSmoothY - mech.transY) * 1; + }, + edgeZoomOutSmooth: 1, + camera() { + //zoom out when mouse gets near the edge of the window + const dx = game.mouse.x / window.innerWidth - 0.5 //x distance from mouse to window center scaled by window width + const dy = game.mouse.y / window.innerHeight - 0.5 //y distance from mouse to window center scaled by window height + const d = Math.max(dx * dx, dy * dy) + game.edgeZoomOutSmooth = (1 + 4 * d * d) * 0.04 + game.edgeZoomOutSmooth * 0.96 + + + ctx.save(); + ctx.translate(canvas.width2, canvas.height2); //center + ctx.scale(game.zoom / game.edgeZoomOutSmooth, game.zoom / game.edgeZoomOutSmooth); //zoom in once centered + ctx.translate(-canvas.width2 + mech.transX, -canvas.height2 + mech.transY); //translate + //calculate in game mouse position by undoing the zoom and translations + game.mouseInGame.x = (game.mouse.x - canvas.width2) / game.zoom * game.edgeZoomOutSmooth + canvas.width2 - mech.transX; + game.mouseInGame.y = (game.mouse.y - canvas.height2) / game.zoom * game.edgeZoomOutSmooth + canvas.height2 - mech.transY; + }, + restoreCamera() { + ctx.restore(); + }, + wipe() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + }, + gravity() { + function addGravity(bodies, magnitude) { + for (var i = 0; i < bodies.length; i++) { + bodies[i].force.y += bodies[i].mass * magnitude; + } + } + addGravity(powerUp, game.g); + addGravity(body, game.g); + player.force.y += player.mass * game.g; + }, + // reset() { //run on first run, and each later run after you die + + // }, + firstRun: true, + splashReturn() { + game.onTitlePage = true; + // document.getElementById('splash').onclick = 'run(this)'; + document.getElementById("splash").onclick = function() { + game.startGame(); + }; + document.getElementById("choose-grid").style.display = "none" + document.getElementById("info").style.display = "inline"; + document.getElementById("build-button").style.display = "inline" + document.getElementById("build-grid").style.display = "none" + document.getElementById("pause-grid-left").style.display = "none" + document.getElementById("pause-grid-right").style.display = "none" + document.getElementById("splash").style.display = "inline"; + document.getElementById("dmg").style.display = "none"; + document.getElementById("health-bg").style.display = "none"; + document.body.style.cursor = "auto"; + }, + fpsInterval: 0, //set in startGame + then: null, + startGame(isBuildRun = false) { + if (!isBuildRun) { //if a build run logic flow returns to "build-button").addEventListener + document.body.style.cursor = "none"; + document.body.style.overflow = "hidden" + } + game.onTitlePage = false; + document.getElementById("choose-grid").style.display = "none" + document.getElementById("build-grid").style.display = "none" + document.getElementById("info").style.display = "none"; + document.getElementById("build-button").style.display = "none"; + document.getElementById("splash").onclick = null; //removes the onclick effect so the function only runs once + document.getElementById("splash").style.display = "none"; //hides the element that spawned the function + document.getElementById("dmg").style.display = "inline"; + document.getElementById("health-bg").style.display = "inline"; + + mech.spawn(); //spawns the player + + if (game.isCommunityMaps) { + level.levels.push("stronghold"); + level.levels.push("basement"); + level.levels.push("detours"); + level.levels.push("house"); + } + level.levels = shuffle(level.levels); //shuffles order of maps + level.levels.unshift("intro"); //add level to the start of the randomized levels list + level.levels.push("gauntlet"); //add level to the end of the randomized levels list + level.levels.push("finalBoss"); //add level to the end of the randomized levels list + + input.endKeySensing(); + b.removeAllGuns(); + game.isNoPowerUps = false; + mod.setupAllMods(); //sets mods to default values + b.setFireCD(); + game.updateModHUD(); + powerUps.totalPowerUps = 0; + powerUps.reroll.rerolls = 0; + mech.setFillColors(); + mech.maxHealth = 1 + mech.maxEnergy = 1 + mech.energy = 1 + mech.hole.isOn = false + game.paused = false; + engine.timing.timeScale = 1; + game.fpsCap = game.fpsCapDefault; + game.isAutoZoom = true; + game.makeGunHUD(); + mech.drop(); + mech.holdingTarget = null + mech.health = 0.25; + mech.displayHealth(); + mech.alive = true; + level.onLevel = 0; + level.levelsCleared = 0; + + //resetting difficulty + game.dmgScale = 0; //increases in level.difficultyIncrease + b.dmgScale = 1; //decreases in level.difficultyIncrease + game.accelScale = 1; + game.lookFreqScale = 1; + game.CDScale = 1; + game.difficulty = 0; + game.difficultyMode = Number(document.getElementById("difficulty-select").value) + build.isCustomSelection = false; + + game.clearNow = true; + document.getElementById("text-log").style.opacity = 0; + document.getElementById("fade-out").style.opacity = 0; + document.title = "n-gon"; + //set to default field + mech.fieldMode = 0; + game.replaceTextLog = true; + game.makeTextLog(`${game.SVGrightMouse} ${mech.fieldUpgrades[mech.fieldMode].name}

${mech.fieldUpgrades[mech.fieldMode].description}`, 600); + mech.setField(mech.fieldMode) + //exit testing + if (game.testing) { + game.testing = false; + game.loop = game.normalLoop + if (game.isConstructionMode) { + document.getElementById("construct").style.display = 'none' + } + } + game.isCheating = false + game.firstRun = false; + + //setup FPS cap + game.fpsInterval = 1000 / game.fpsCap; + game.then = Date.now(); + requestAnimationFrame(cycle); //starts game loop + }, + clearNow: false, + clearMap() { + if (mod.isMineAmmoBack) { + let count = 0; + for (i = 0, len = bullet.length; i < len; i++) { //count mines left on map + if (bullet[i].bulletType === "mine") count++ + } + for (i = 0, len = b.guns.length; i < len; i++) { //find which gun is mine + if (b.guns[i].name === "mine") { + if (mod.isCrouchAmmo) count = Math.ceil(count / 2) + b.guns[i].ammo += count + game.updateGunHUD(); + break; + } + } + } + + if (mod.isMutualism && !mod.isEnergyHealth) { + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].isMutualismActive) { + mech.health += 0.01 + if (mech.health > mech.maxHealth) mech.health = mech.maxHealth; + mech.displayHealth(); + } + } + } + + powerUps.totalPowerUps = powerUp.length + + let holdTarget; //if player is holding something this remembers it before it gets deleted + if (mech.holdingTarget) holdTarget = mech.holdingTarget; + + mech.fireCDcycle = 0 + mech.drop(); + mech.hole.isOn = false; + level.fill = []; + level.fillBG = []; + level.zones = []; + level.queryList = []; + game.drawList = []; + + function removeAll(array) { + for (let i = 0; i < array.length; ++i) Matter.World.remove(engine.world, array[i]); + } + removeAll(map); + map = []; + removeAll(body); + body = []; + removeAll(mob); + mob = []; + removeAll(powerUp); + powerUp = []; + removeAll(cons); + cons = []; + removeAll(consBB); + consBB = []; + removeAll(bullet); + bullet = []; + removeAll(composite); + composite = []; + // if player was holding something this makes a new copy to hold + if (holdTarget) { + len = body.length; + body[len] = Matter.Bodies.fromVertices(0, 0, holdTarget.vertices, { + friction: holdTarget.friction, + frictionAir: holdTarget.frictionAir, + frictionStatic: holdTarget.frictionStatic + }); + Matter.Body.setPosition(body[len], mech.pos); + mech.isHolding = true + mech.holdingTarget = body[len]; + mech.holdingTarget.collisionFilter.category = 0; + mech.holdingTarget.collisionFilter.mask = 0; + } + //set fps back to default + game.fpsCap = game.fpsCapDefault + game.fpsInterval = 1000 / game.fpsCap; + }, + // getCoords: { + // //used when building maps, outputs a draw rect command to console, only works in testing mode + // pos1: { + // x: 0, + // y: 0 + // }, + // pos2: { + // x: 0, + // y: 0 + // }, + // out() { + // if (keys[49]) { + // game.getCoords.pos1.x = Math.round(game.mouseInGame.x / 25) * 25; + // game.getCoords.pos1.y = Math.round(game.mouseInGame.y / 25) * 25; + // } + // if (keys[50]) { + // //press 1 in the top left; press 2 in the bottom right;copy command from console + // game.getCoords.pos2.x = Math.round(game.mouseInGame.x / 25) * 25; + // game.getCoords.pos2.y = Math.round(game.mouseInGame.y / 25) * 25; + // window.getSelection().removeAllRanges(); + // var range = document.createRange(); + // range.selectNode(document.getElementById("test")); + // window.getSelection().addRange(range); + // document.execCommand("copy"); + // window.getSelection().removeAllRanges(); + // console.log(`spawn.mapRect(${game.getCoords.pos1.x}, ${game.getCoords.pos1.y}, ${game.getCoords.pos2.x - game.getCoords.pos1.x}, ${game.getCoords.pos2.y - game.getCoords.pos1.y}); //`); + // } + // } + // }, + 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 + + 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 + }); + Matter.Body.setPosition(player, { + x: level.enter.x + 50, + y: level.enter.y - 20 + }); + // move bots + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType) { + Matter.Body.setPosition(bullet[i], Vector.add(player.position, { + x: 250 * (Math.random() - 0.5), + y: 250 * (Math.random() - 0.5) + })); + Matter.Body.setVelocity(bullet[i], { + x: 0, + y: 0 + }); + } + } + mech.damage(0.1 * game.difficultyMode); + mech.energy -= 0.1 * game.difficultyMode + } + + // if (mod.isEnergyDamage) { + // document.getElementById("mod-capacitor").innerHTML = `(+${(mech.energy/0.05).toFixed(0)}%)` + // } + // if (mod.restDamage) { + // if (player.speed < 1) { + // document.getElementById("mod-rest").innerHTML = `(+20%)` + // } else { + // document.getElementById("mod-rest").innerHTML = `(+0%)` + // } + // } + + 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.isHealthRecovery) mech.addHealth(0.01) + } + + if (!(game.cycle % 420)) { //once every 7 seconds + fallCheck = function(who, save = false) { + let i = who.length; + while (i--) { + if (who[i].position.y > game.fallHeight) { + if (save) { + Matter.Body.setVelocity(who[i], { + x: 0, + y: 0 + }); + Matter.Body.setPosition(who[i], { + x: level.exit.x + 30 * (Math.random() - 0.5), + y: level.exit.y + 30 * (Math.random() - 0.5) + }); + } else { + Matter.World.remove(engine.world, who[i]); + who.splice(i, 1); + } + } + } + }; + fallCheck(mob); + fallCheck(body); + fallCheck(powerUp, true); + } + } + }, + testingOutput() { + ctx.fillStyle = "#000"; + if (!game.isConstructionMode) { + // ctx.textAlign = "right"; + ctx.fillText("T: exit testing mode", canvas.width / 2, canvas.height - 10); + // let line = 500; + // const x = canvas.width - 5; + // ctx.fillText("T: exit testing mode", x, line); + // line += 20; + // ctx.fillText("Y: give all mods", x, line); + // line += 20; + // ctx.fillText("R: teleport to mouse", x, line); + // line += 20; + // ctx.fillText("F: cycle field", x, line); + // line += 20; + // ctx.fillText("G: give all guns", x, line); + // line += 20; + // ctx.fillText("H: heal", x, line); + // line += 20; + // ctx.fillText("U: next level", x, line); + // line += 20; + // ctx.fillText("1-7: spawn things", x, line); + } + ctx.textAlign = "center"; + ctx.fillText(`(${game.mouseInGame.x.toFixed(1)}, ${game.mouseInGame.y.toFixed(1)})`, game.mouse.x, game.mouse.y - 20); + }, + draw: { + powerUp() { //is set by Bayesian mod + // ctx.globalAlpha = 0.4 * Math.sin(mech.cycle * 0.15) + 0.6; + // for (let i = 0, len = powerUp.length; i < len; ++i) { + // ctx.beginPath(); + // ctx.arc(powerUp[i].position.x, powerUp[i].position.y, powerUp[i].size, 0, 2 * Math.PI); + // ctx.fillStyle = powerUp[i].color; + // ctx.fill(); + // } + // ctx.globalAlpha = 1; + }, + powerUpNormal() { //back up in case power up draw gets changed + ctx.globalAlpha = 0.4 * Math.sin(mech.cycle * 0.15) + 0.6; + for (let i = 0, len = powerUp.length; i < len; ++i) { + ctx.beginPath(); + ctx.arc(powerUp[i].position.x, powerUp[i].position.y, powerUp[i].size, 0, 2 * Math.PI); + ctx.fillStyle = powerUp[i].color; + ctx.fill(); + } + ctx.globalAlpha = 1; + }, + powerUpBonus() { //draws crackle effect for bonus power ups + for (let i = 0, len = powerUp.length; i < len; ++i) { + ctx.globalAlpha = 0.4 * Math.sin(mech.cycle * 0.15) + 0.6; + for (let i = 0, len = powerUp.length; i < len; ++i) { + ctx.beginPath(); + ctx.arc(powerUp[i].position.x, powerUp[i].position.y, powerUp[i].size, 0, 2 * Math.PI); + ctx.fillStyle = powerUp[i].color; + ctx.fill(); + } + ctx.globalAlpha = 1; + + if (powerUp[i].isBonus && Math.random() < 0.1) { + //draw electricity + const mag = 5 + powerUp[i].size / 5 + let unit = Vector.rotate({ + x: mag, + y: mag + }, 2 * Math.PI * Math.random()) + let path = { + x: powerUp[i].position.x + unit.x, + y: powerUp[i].position.y + unit.y + } + ctx.beginPath(); + ctx.moveTo(path.x, path.y); + for (let i = 0; i < 6; i++) { + unit = Vector.rotate(unit, 3 * (Math.random() - 0.5)) + path = Vector.add(path, unit) + ctx.lineTo(path.x, path.y); + } + ctx.lineWidth = 0.5 + 2 * Math.random(); + ctx.strokeStyle = "#000" + ctx.stroke(); + } + } + // ctx.globalAlpha = 1; + }, + + // map: function() { + // ctx.beginPath(); + // for (let i = 0, len = map.length; i < len; ++i) { + // let vertices = map[i].vertices; + // ctx.moveTo(vertices[0].x, vertices[0].y); + // for (let j = 1; j < vertices.length; j += 1) { + // ctx.lineTo(vertices[j].x, vertices[j].y); + // } + // ctx.lineTo(vertices[0].x, vertices[0].y); + // } + // ctx.fillStyle = "#444"; + // ctx.fill(); + // }, + mapPath: null, //holds the path for the map to speed up drawing + setPaths() { + //runs at each new level to store the path for the map since the map doesn't change + game.draw.mapPath = new Path2D(); + for (let i = 0, len = map.length; i < len; ++i) { + let vertices = map[i].vertices; + game.draw.mapPath.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + game.draw.mapPath.lineTo(vertices[j].x, vertices[j].y); + } + game.draw.mapPath.lineTo(vertices[0].x, vertices[0].y); + } + }, + mapFill: "#444", + bodyFill: "rgba(140,140,140,0.85)", //"#999", + bodyStroke: "#222", + drawMapPath() { + ctx.fillStyle = game.draw.mapFill; + ctx.fill(game.draw.mapPath); + }, + body() { + ctx.beginPath(); + for (let i = 0, len = body.length; i < len; ++i) { + let vertices = body[i].vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j++) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + } + ctx.lineWidth = 2; + ctx.fillStyle = game.draw.bodyFill; + ctx.fill(); + ctx.strokeStyle = game.draw.bodyStroke; + ctx.stroke(); + }, + cons() { + ctx.beginPath(); + for (let i = 0, len = cons.length; i < len; ++i) { + ctx.moveTo(cons[i].pointA.x, cons[i].pointA.y); + ctx.lineTo(cons[i].bodyB.position.x, cons[i].bodyB.position.y); + } + for (let i = 0, len = consBB.length; i < len; ++i) { + ctx.moveTo(consBB[i].bodyA.position.x, consBB[i].bodyA.position.y); + ctx.lineTo(consBB[i].bodyB.position.x, consBB[i].bodyB.position.y); + } + ctx.lineWidth = 2; + // ctx.strokeStyle = "#999"; + ctx.strokeStyle = "rgba(0,0,0,0.15)"; + ctx.stroke(); + }, + wireFrame() { + // ctx.textAlign = "center"; + // ctx.textBaseline = "middle"; + // ctx.fillStyle = "#999"; + const bodies = Composite.allBodies(engine.world); + ctx.beginPath(); + for (let i = 0; i < bodies.length; ++i) { + //ctx.fillText(bodies[i].id,bodies[i].position.x,bodies[i].position.y); //shows the id of every body + let vertices = bodies[i].vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + } + ctx.lineWidth = 1; + ctx.strokeStyle = "#000"; + ctx.stroke(); + }, + testing() { + //query zones + // ctx.beginPath(); + // for (let i = 0, len = level.queryList.length; i < len; ++i) { + // ctx.rect( + // level.queryList[i].bounds.max.x, + // level.queryList[i].bounds.max.y, + // level.queryList[i].bounds.min.x - level.queryList[i].bounds.max.x, + // level.queryList[i].bounds.min.y - level.queryList[i].bounds.max.y + // ); + // } + // ctx.fillStyle = "rgba(0, 0, 255, 0.2)"; + // ctx.fill(); + //jump + ctx.beginPath(); + let bodyDraw = jumpSensor.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(255, 0, 0, 0.5)"; + ctx.fill(); + // ctx.strokeStyle = "#000"; + // ctx.stroke(); + //main body + ctx.beginPath(); + bodyDraw = playerBody.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(0, 255, 255, 0.25)"; + ctx.fill(); + // ctx.stroke(); + //head + ctx.beginPath(); + bodyDraw = playerHead.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(255, 255, 0, 0.4)"; + ctx.fill(); + // ctx.stroke(); + //head sensor + ctx.beginPath(); + bodyDraw = headSensor.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(0, 0, 255, 0.25)"; + ctx.fill(); + // ctx.stroke(); + } + }, + checkLineIntersection(v1, v1End, v2, v2End) { + // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point + let denominator, a, b, numerator1, numerator2; + let result = { + x: null, + y: null, + onLine1: false, + onLine2: false + }; + denominator = (v2End.y - v2.y) * (v1End.x - v1.x) - (v2End.x - v2.x) * (v1End.y - v1.y); + if (denominator == 0) { + return result; + } + a = v1.y - v2.y; + b = v1.x - v2.x; + numerator1 = (v2End.x - v2.x) * a - (v2End.y - v2.y) * b; + numerator2 = (v1End.x - v1.x) * a - (v1End.y - v1.y) * b; + a = numerator1 / denominator; + b = numerator2 / denominator; + + // if we cast these lines infinitely in both directions, they intersect here: + result.x = v1.x + a * (v1End.x - v1.x); + result.y = v1.y + a * (v1End.y - v1.y); + // if line1 is a segment and line2 is infinite, they intersect if: + if (a > 0 && a < 1) result.onLine1 = true; + // if line2 is a segment and line1 is infinite, they intersect if: + if (b > 0 && b < 1) result.onLine2 = true; + // if line1 and line2 are segments, they intersect if both of the above are true + return result; + }, + copyToClipBoard(value) { + // Create a fake textarea + const textAreaEle = document.createElement('textarea'); + + // Reset styles + textAreaEle.style.border = '0'; + textAreaEle.style.padding = '0'; + textAreaEle.style.margin = '0'; + + // Set the absolute position + // User won't see the element + textAreaEle.style.position = 'absolute'; + textAreaEle.style.left = '-9999px'; + textAreaEle.style.top = `0px`; + + // Set the value + textAreaEle.value = value + + // Append the textarea to body + document.body.appendChild(textAreaEle); + + // Focus and select the text + textAreaEle.focus(); + textAreaEle.select(); + + // Execute the "copy" command + try { + document.execCommand('copy'); + } catch (err) { + // Unable to copy + console.log(err) + } finally { + // Remove the textarea + document.body.removeChild(textAreaEle); + } + }, + constructMouseDownPosition: { + x: 0, + y: 0 + }, + constructMapString: [], + constructCycle() { + if (game.isConstructionMode && game.constructMouseDownPosition) { + function round(num, round = 25) { + return Math.ceil(num / round) * round; + } + const x = round(game.constructMouseDownPosition.x) + const y = round(game.constructMouseDownPosition.y) + const dx = Math.max(25, round(game.mouseInGame.x) - x) + const dy = Math.max(25, round(game.mouseInGame.y) - y) + + ctx.strokeStyle = "#000" + ctx.lineWidth = 2; + ctx.strokeRect(x, y, dx, dy); + } + }, + outputMapString(string) { + if (string) game.constructMapString.push(string) //store command as a string in the next element of an array + let out = "" //combine set of map strings to one string + let outHTML = "" + for (let i = 0, len = game.constructMapString.length; i < len; i++) { + out += game.constructMapString[i]; + outHTML += "
" + game.constructMapString[i] + "
" + } + console.log(out) + game.copyToClipBoard(out) + document.getElementById("construct").innerHTML = outHTML + }, + enableConstructMode() { + game.isConstructionMode = true; + game.isAutoZoom = false; + game.zoomScale = 2600; + game.setZoom(); + + document.body.addEventListener("mouseup", (e) => { + if (game.testing && game.constructMouseDownPosition) { + function round(num, round = 25) { + return Math.ceil(num / round) * round; + } + //clean up positions + const x = round(game.constructMouseDownPosition.x) + const y = round(game.constructMouseDownPosition.y) + const dx = Math.max(25, round(game.mouseInGame.x) - x) + const dy = Math.max(25, round(game.mouseInGame.y) - y) + + if (e.which === 2) { + game.outputMapString(`spawn.randomMob(${x}, ${y},0.5);`); + } else if (game.mouseInGame.x > game.constructMouseDownPosition.x && game.mouseInGame.y > game.constructMouseDownPosition.y) { //make sure that the width and height are positive + if (e.which === 1) { //add map + game.outputMapString(`spawn.mapRect(${x}, ${y}, ${dx}, ${dy});`); + + //see map in world + spawn.mapRect(x, y, dx, dy); + len = map.length - 1 + map[len].collisionFilter.category = cat.map; + map[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; + Matter.Body.setStatic(map[len], true); //make static + World.add(engine.world, map[len]); //add to world + + game.draw.setPaths() //update map graphics + } else if (e.which === 3) { //add body + game.outputMapString(`spawn.bodyRect(${x}, ${y}, ${dx}, ${dy});`); + + //see map in world + spawn.bodyRect(x, y, dx, dy); + len = body.length - 1 + body[len].collisionFilter.category = cat.body; + body[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet + World.add(engine.world, body[len]); //add to world + body[len].classType = "body" + } + } + } + game.constructMouseDownPosition.x = undefined + game.constructMouseDownPosition.y = undefined + }); + game.constructMouseDownPosition.x = undefined + game.constructMouseDownPosition.y = undefined + document.body.addEventListener("mousedown", (e) => { + if (game.testing) { + game.constructMouseDownPosition.x = game.mouseInGame.x + game.constructMouseDownPosition.y = game.mouseInGame.y + } + }); + + document.body.addEventListener("keydown", (e) => { // e.keyCode z=90 m=77 b=66 shift = 16 c = 67 + if (game.testing && e.keyCode === 90 && game.constructMapString.length) { + if (game.constructMapString[game.constructMapString.length - 1][6] === 'm') { //remove map from current level + const index = map.length - 1 + Matter.World.remove(engine.world, map[index]); + map.splice(index, 1); + game.draw.setPaths() //update map graphics + } else if (game.constructMapString[game.constructMapString.length - 1][6] === 'b') { //remove body from current level + const index = body.length - 1 + Matter.World.remove(engine.world, body[index]); + body.splice(index, 1); + } + game.constructMapString.pop(); + game.outputMapString(); + } + }); + } }; \ No newline at end of file diff --git a/js/index.js b/js/index.js index b7bc7cf..11df08b 100644 --- a/js/index.js +++ b/js/index.js @@ -2,38 +2,38 @@ //collision groups // cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet | cat.mobShield | cat.phased const cat = { - player: 0x1, - map: 0x10, - body: 0x100, - bullet: 0x1000, - powerUp: 0x10000, - mob: 0x100000, - mobBullet: 0x1000000, - mobShield: 0x10000000, - phased: 0x100000000, + player: 0x1, + map: 0x10, + body: 0x100, + bullet: 0x1000, + powerUp: 0x10000, + mob: 0x100000, + mobBullet: 0x1000000, + mobShield: 0x10000000, + phased: 0x100000000, } function shuffle(array) { - var currentIndex = array.length, - temporaryValue, - randomIndex; - // While there remain elements to shuffle... - while (0 !== currentIndex) { - // Pick a remaining element... - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - // And swap it with the current element. - temporaryValue = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = temporaryValue; - } - return array; + var currentIndex = array.length, + temporaryValue, + randomIndex; + // While there remain elements to shuffle... + while (0 !== currentIndex) { + // Pick a remaining element... + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + // And swap it with the current element. + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } + return array; } // shrink power up selection menu to find window height if (screen.height < 800) { - document.getElementById("choose-grid").style.fontSize = "1em"; //1.3em is normal - if (screen.height < 600) document.getElementById("choose-grid").style.fontSize = "0.8em"; //1.3em is normal + document.getElementById("choose-grid").style.fontSize = "1em"; //1.3em is normal + if (screen.height < 600) document.getElementById("choose-grid").style.fontSize = "0.8em"; //1.3em is normal } @@ -52,64 +52,64 @@ if (screen.height < 800) { //use %20 for spaces //difficulty is 0 easy, 1 normal, 2 hard, 4 why function getUrlVars() { - let vars = {}; - window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, k, v) { - vars[k] = v; - }); - return vars; + let vars = {}; + window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m, k, v) { + vars[k] = v; + }); + return vars; } window.addEventListener('load', (event) => { - const set = getUrlVars() - if (Object.keys(set).length !== 0) { - openCustomBuildMenu(); - //add custom selections based on url - for (const property in set) { - set[property] = set[property].replace(/%20/g, " ") - set[property] = set[property].replace(/%CE%A8/g, "Ψ") - if (property === "field") { - let found = false - let index - for (let i = 0; i < mech.fieldUpgrades.length; i++) { - if (set[property] === mech.fieldUpgrades[i].name) { - index = i; - found = true; - break; - } + const set = getUrlVars() + if (Object.keys(set).length !== 0) { + openCustomBuildMenu(); + //add custom selections based on url + for (const property in set) { + set[property] = set[property].replace(/%20/g, " ") + set[property] = set[property].replace(/%CE%A8/g, "Ψ") + if (property === "field") { + let found = false + let index + for (let i = 0; i < mech.fieldUpgrades.length; i++) { + if (set[property] === mech.fieldUpgrades[i].name) { + index = i; + found = true; + break; + } + } + if (found) build.choosePowerUp(document.getElementById(`field-${index}`), index, 'field') + } + if (property.substring(0, 3) === "gun") { + let found = false + let index + for (let i = 0; i < b.guns.length; i++) { + if (set[property] === b.guns[i].name) { + index = i; + found = true; + break; + } + } + if (found) build.choosePowerUp(document.getElementById(`gun-${index}`), index, 'gun') + } + if (property.substring(0, 3) === "mod") { + for (let i = 0; i < mod.mods.length; i++) { + if (set[property] === mod.mods[i].name) { + build.choosePowerUp(document.getElementById(`mod-${i}`), i, 'mod', true) + break; + } + } + } + if (property === "difficulty") { + game.difficultyMode = Number(set[property]) + document.getElementById("difficulty-select-custom").value = Number(set[property]) + } + if (property === "level") { + document.getElementById("starting-level").value = Number(set[property]) + } + if (property === "noPower") { + document.getElementById("no-power-ups").checked = Number(set[property]) + } } - if (found) build.choosePowerUp(document.getElementById(`field-${index}`), index, 'field') - } - if (property.substring(0, 3) === "gun") { - let found = false - let index - for (let i = 0; i < b.guns.length; i++) { - if (set[property] === b.guns[i].name) { - index = i; - found = true; - break; - } - } - if (found) build.choosePowerUp(document.getElementById(`gun-${index}`), index, 'gun') - } - if (property.substring(0, 3) === "mod") { - for (let i = 0; i < mod.mods.length; i++) { - if (set[property] === mod.mods[i].name) { - build.choosePowerUp(document.getElementById(`mod-${i}`), i, 'mod', true) - break; - } - } - } - if (property === "difficulty") { - game.difficultyMode = Number(set[property]) - document.getElementById("difficulty-select-custom").value = Number(set[property]) - } - if (property === "level") { - document.getElementById("starting-level").value = Number(set[property]) - } - if (property === "noPower") { - document.getElementById("no-power-ups").checked = Number(set[property]) - } } - } }); @@ -122,62 +122,62 @@ const ctx = canvas.getContext("2d"); document.body.style.backgroundColor = "#fff"; //disable pop up menu on right click -document.oncontextmenu = function () { - return false; +document.oncontextmenu = function() { + return false; } function setupCanvas() { - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; - canvas.width2 = canvas.width / 2; //precalculated because I use this often (in mouse look) - canvas.height2 = canvas.height / 2; - canvas.diagonal = Math.sqrt(canvas.width2 * canvas.width2 + canvas.height2 * canvas.height2); - // ctx.font = "18px Arial"; - // ctx.textAlign = "center"; - ctx.font = "25px Arial"; - ctx.lineJoin = "round"; - ctx.lineCap = "round"; - // ctx.lineCap='square'; - game.setZoom(); + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + canvas.width2 = canvas.width / 2; //precalculated because I use this often (in mouse look) + canvas.height2 = canvas.height / 2; + canvas.diagonal = Math.sqrt(canvas.width2 * canvas.width2 + canvas.height2 * canvas.height2); + // ctx.font = "18px Arial"; + // ctx.textAlign = "center"; + ctx.font = "25px Arial"; + ctx.lineJoin = "round"; + ctx.lineCap = "round"; + // ctx.lineCap='square'; + game.setZoom(); } setupCanvas(); window.onresize = () => { - setupCanvas(); + setupCanvas(); }; //********************************************************************** // custom build grid display and pause //********************************************************************** const build = { - onLoadPowerUps() { - const set = getUrlVars() - if (Object.keys(set).length !== 0) { - for (const property in set) { - set[property] = set[property].replace(/%20/g, " ") - if (property.substring(0, 3) === "gun") b.giveGuns(set[property]) - if (property.substring(0, 3) === "mod") mod.giveMod(set[property]) - if (property === "field") mech.setField(set[property]) - if (property === "difficulty") { - game.difficultyMode = Number(set[property]) - document.getElementById("difficulty-select").value = Number(set[property]) + onLoadPowerUps() { + const set = getUrlVars() + if (Object.keys(set).length !== 0) { + for (const property in set) { + set[property] = set[property].replace(/%20/g, " ") + if (property.substring(0, 3) === "gun") b.giveGuns(set[property]) + if (property.substring(0, 3) === "mod") mod.giveMod(set[property]) + if (property === "field") mech.setField(set[property]) + if (property === "difficulty") { + game.difficultyMode = Number(set[property]) + document.getElementById("difficulty-select").value = Number(set[property]) + } + if (property === "level") { + level.levelsCleared += Number(set[property]); + level.difficultyIncrease(Number(set[property]) * game.difficultyMode) //increase difficulty based on modes + spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns + level.onLevel++ + } + } + for (let i = 0; i < bullet.length; ++i) Matter.World.remove(engine.world, bullet[i]); + bullet = []; //remove any bullets that might have spawned from mods + if (b.inventory.length > 0) { + b.activeGun = b.inventory[0] //set first gun to active gun + game.makeGunHUD(); + } } - if (property === "level") { - level.levelsCleared += Number(set[property]); - level.difficultyIncrease(Number(set[property]) * game.difficultyMode) //increase difficulty based on modes - spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns - level.onLevel++ - } - } - for (let i = 0; i < bullet.length; ++i) Matter.World.remove(engine.world, bullet[i]); - bullet = []; //remove any bullets that might have spawned from mods - if (b.inventory.length > 0) { - b.activeGun = b.inventory[0] //set first gun to active gun - game.makeGunHUD(); - } - } - }, - pauseGrid() { - let text = ` + }, + pauseGrid() { + let text = `
PAUSED               press P to resume
@@ -191,115 +191,115 @@ const build = {
position: (${player.position.x.toFixed(1)}, ${player.position.y.toFixed(1)})   velocity: (${player.velocity.x.toFixed(1)}, ${player.velocity.y.toFixed(1)})
mouse: (${game.mouseInGame.x.toFixed(1)}, ${game.mouseInGame.y.toFixed(1)})   mass: ${player.mass.toFixed(1)}
-
level: ${level.levelsCleared} - ${level.levelsCleared===0?"intro":level.levels[level.onLevel]} (${level.difficultyText()})   ${mech.cycle} cycles +
level: ${level.levels[level.onLevel]} (${level.difficultyText()})   ${mech.cycle} cycles
${mob.length} mobs,   ${body.length} blocks,   ${bullet.length} bullets,   ${powerUp.length} power ups
damage difficulty scale: ${(b.dmgScale*100).toFixed(2) }%
harm difficulty scale: ${(game.dmgScale*100).toFixed(0)}%
heal difficulty scale: ${(game.healScale*100).toFixed(1)}% `; - let countGuns = 0 - let countMods = 0 - for (let i = 0, len = b.guns.length; i < len; i++) { - if (b.guns[i].have) { - text += `
  ${b.guns[i].name}
${b.guns[i].description}
` - countGuns++ - } - } - let el = document.getElementById("pause-grid-left") - el.style.display = "grid" - el.innerHTML = text - text = ""; - text += `
  ${mech.fieldUpgrades[mech.fieldMode].name}
${mech.fieldUpgrades[mech.fieldMode].description}
` - for (let i = 0, len = mod.mods.length; i < len; i++) { - if (mod.mods[i].count > 0) { - if (mod.mods[i].count === 1) { - text += `
  ${mod.mods[i].name}
${mod.mods[i].description}
` - } else { - text += `
  ${mod.mods[i].name} (${mod.mods[i].count}x)
${mod.mods[i].description}
` + let countGuns = 0 + let countMods = 0 + for (let i = 0, len = b.guns.length; i < len; i++) { + if (b.guns[i].have) { + text += `
  ${b.guns[i].name}
${b.guns[i].description}
` + countGuns++ + } } - countMods++ - } - } - el = document.getElementById("pause-grid-right") - el.style.display = "grid" - el.innerHTML = text - if (countMods > 5 || countGuns > 6) { - document.body.style.overflowY = "scroll"; - document.body.style.overflowX = "hidden"; - } - }, - unPauseGrid() { - document.body.style.overflow = "hidden" - document.getElementById("pause-grid-left").style.display = "none" - document.getElementById("pause-grid-right").style.display = "none" - window.scrollTo(0, 0); - }, - isCustomSelection: true, - choosePowerUp(who, index, type, isAllowed = false) { - if (type === "gun") { - let isDeselect = false - for (let i = 0, len = b.inventory.length; i < len; i++) { //look for selection in inventory - if (b.guns[b.inventory[i]].name === b.guns[index].name) { //if already clicked, remove gun - isDeselect = true - who.classList.remove("build-gun-selected"); - //remove gun - b.inventory.splice(i, 1) - b.guns[index].count = 0; - b.guns[index].have = false; - if (b.guns[index].ammo != Infinity) b.guns[index].ammo = 0; - if (b.inventory.length === 0) b.activeGun = null; - game.makeGunHUD(); - break + let el = document.getElementById("pause-grid-left") + el.style.display = "grid" + el.innerHTML = text + text = ""; + text += `
  ${mech.fieldUpgrades[mech.fieldMode].name}
${mech.fieldUpgrades[mech.fieldMode].description}
` + for (let i = 0, len = mod.mods.length; i < len; i++) { + if (mod.mods[i].count > 0) { + if (mod.mods[i].count === 1) { + text += `
  ${mod.mods[i].name}
${mod.mods[i].description}
` + } else { + text += `
  ${mod.mods[i].name} (${mod.mods[i].count}x)
${mod.mods[i].description}
` + } + countMods++ + } } - } - if (!isDeselect) { //add gun - who.classList.add("build-gun-selected"); - b.giveGuns(index) - } - } else if (type === "field") { - if (mech.fieldMode !== index) { - document.getElementById("field-" + mech.fieldMode).classList.remove("build-field-selected"); - mech.setField(index) - who.classList.add("build-field-selected"); - } - } else if (type === "mod") { //remove mod if you have too many - if (mod.mods[index].count < mod.mods[index].maxCount) { - if (!who.classList.contains("build-mod-selected")) who.classList.add("build-mod-selected"); - mod.giveMod(index) - } else { - mod.removeMod(index); - who.classList.remove("build-mod-selected"); - } - } - //update mod text //disable not allowed mods - for (let i = 0, len = mod.mods.length; i < len; i++) { - const modID = document.getElementById("mod-" + i) - if (!mod.mods[i].isCustomHide) { - if (mod.mods[i].allowed() || isAllowed) { - if (mod.mods[i].count > 1) { - modID.innerHTML = `
  ${mod.mods[i].name} (${mod.mods[i].count}x)
${mod.mods[i].description}` - } else { - modID.innerHTML = `
  ${mod.mods[i].name}
${mod.mods[i].description}` - } + el = document.getElementById("pause-grid-right") + el.style.display = "grid" + el.innerHTML = text + if (countMods > 5 || countGuns > 6) { + document.body.style.overflowY = "scroll"; + document.body.style.overflowX = "hidden"; + } + }, + unPauseGrid() { + document.body.style.overflow = "hidden" + document.getElementById("pause-grid-left").style.display = "none" + document.getElementById("pause-grid-right").style.display = "none" + window.scrollTo(0, 0); + }, + isCustomSelection: true, + choosePowerUp(who, index, type, isAllowed = false) { + if (type === "gun") { + let isDeselect = false + for (let i = 0, len = b.inventory.length; i < len; i++) { //look for selection in inventory + if (b.guns[b.inventory[i]].name === b.guns[index].name) { //if already clicked, remove gun + isDeselect = true + who.classList.remove("build-gun-selected"); + //remove gun + b.inventory.splice(i, 1) + b.guns[index].count = 0; + b.guns[index].have = false; + if (b.guns[index].ammo != Infinity) b.guns[index].ammo = 0; + if (b.inventory.length === 0) b.activeGun = null; + game.makeGunHUD(); + break + } + } + if (!isDeselect) { //add gun + who.classList.add("build-gun-selected"); + b.giveGuns(index) + } + } else if (type === "field") { + if (mech.fieldMode !== index) { + document.getElementById("field-" + mech.fieldMode).classList.remove("build-field-selected"); + mech.setField(index) + who.classList.add("build-field-selected"); + } + } else if (type === "mod") { //remove mod if you have too many + if (mod.mods[index].count < mod.mods[index].maxCount) { + if (!who.classList.contains("build-mod-selected")) who.classList.add("build-mod-selected"); + mod.giveMod(index) + } else { + mod.removeMod(index); + who.classList.remove("build-mod-selected"); + } + } + //update mod text //disable not allowed mods + for (let i = 0, len = mod.mods.length; i < len; i++) { + const modID = document.getElementById("mod-" + i) + if (!mod.mods[i].isCustomHide) { + if (mod.mods[i].allowed() || isAllowed) { + if (mod.mods[i].count > 1) { + modID.innerHTML = `
  ${mod.mods[i].name} (${mod.mods[i].count}x)
${mod.mods[i].description}` + } else { + modID.innerHTML = `
  ${mod.mods[i].name}
${mod.mods[i].description}` + } - if (modID.classList.contains("build-grid-disabled")) { - modID.classList.remove("build-grid-disabled"); - modID.setAttribute("onClick", `javascript: build.choosePowerUp(this,${i},'mod')`); - } - } else { - modID.innerHTML = `
  ${mod.mods[i].name}
requires: ${mod.mods[i].requires}` - if (!modID.classList.contains("build-grid-disabled")) { - modID.classList.add("build-grid-disabled"); - modID.onclick = null - } - if (mod.mods[i].count > 0) mod.removeMod(i) - if (modID.classList.contains("build-mod-selected")) modID.classList.remove("build-mod-selected"); + if (modID.classList.contains("build-grid-disabled")) { + modID.classList.remove("build-grid-disabled"); + modID.setAttribute("onClick", `javascript: build.choosePowerUp(this,${i},'mod')`); + } + } else { + modID.innerHTML = `
  ${mod.mods[i].name}
requires: ${mod.mods[i].requires}` + if (!modID.classList.contains("build-grid-disabled")) { + modID.classList.add("build-grid-disabled"); + modID.onclick = null + } + if (mod.mods[i].count > 0) mod.removeMod(i) + if (modID.classList.contains("build-mod-selected")) modID.classList.remove("build-mod-selected"); + } + } } - } - } - }, - populateGrid() { - let text = ` + }, + populateGrid() { + let text = `
@@ -333,562 +333,562 @@ const build = {
` - for (let i = 0, len = mech.fieldUpgrades.length; i < len; i++) { - text += `
  ${mech.fieldUpgrades[i].name}
${mech.fieldUpgrades[i].description}
` - } - - for (let i = 0, len = b.guns.length; i < len; i++) { - text += `
  ${b.guns[i].name}
${b.guns[i].description}
` - } - for (let i = 0, len = mod.mods.length; i < len; i++) { - if (!mod.mods[i].isCustomHide) { - if (!mod.mods[i].allowed()) { // || mod.mods[i].name === "+1 cardinality") { //|| mod.mods[i].name === "leveraged investment" - text += `
  ${mod.mods[i].name}
requires: ${mod.mods[i].requires}
` - } else if (mod.mods[i].count > 1) { - text += `
  ${mod.mods[i].name} (${mod.mods[i].count}x)
${mod.mods[i].description}
` - } else { - text += `
  ${mod.mods[i].name}
${mod.mods[i].description}
` + for (let i = 0, len = mech.fieldUpgrades.length; i < len; i++) { + text += `
  ${mech.fieldUpgrades[i].name}
${mech.fieldUpgrades[i].description}
` } - } - } - document.getElementById("build-grid").innerHTML = text - document.getElementById("difficulty-select-custom").value = document.getElementById("difficulty-select").value - document.getElementById("difficulty-select-custom").addEventListener("input", () => { - game.difficultyMode = Number(document.getElementById("difficulty-select-custom").value) - localSettings.difficultyMode = Number(document.getElementById("difficulty-select-custom").value) - document.getElementById("difficulty-select").value = document.getElementById("difficulty-select-custom").value - localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage - }); - }, - reset() { - build.isCustomSelection = true; - mech.setField(0) - b.inventory = []; //removes guns and ammo - for (let i = 0, len = b.guns.length; i < len; ++i) { - b.guns[i].count = 0; - b.guns[i].have = false; - if (b.guns[i].ammo != Infinity) b.guns[i].ammo = 0; - } - b.activeGun = null; - game.makeGunHUD(); + for (let i = 0, len = b.guns.length; i < len; i++) { + text += `
  ${b.guns[i].name}
${b.guns[i].description}
` + } + for (let i = 0, len = mod.mods.length; i < len; i++) { + if (!mod.mods[i].isCustomHide) { + if (!mod.mods[i].allowed()) { // || mod.mods[i].name === "+1 cardinality") { //|| mod.mods[i].name === "leveraged investment" + text += `
  ${mod.mods[i].name}
requires: ${mod.mods[i].requires}
` + } else if (mod.mods[i].count > 1) { + text += `
  ${mod.mods[i].name} (${mod.mods[i].count}x)
${mod.mods[i].description}
` + } else { + text += `
  ${mod.mods[i].name}
${mod.mods[i].description}
` + } + } + } + document.getElementById("build-grid").innerHTML = text + document.getElementById("difficulty-select-custom").value = document.getElementById("difficulty-select").value + document.getElementById("difficulty-select-custom").addEventListener("input", () => { + game.difficultyMode = Number(document.getElementById("difficulty-select-custom").value) + localSettings.difficultyMode = Number(document.getElementById("difficulty-select-custom").value) + document.getElementById("difficulty-select").value = document.getElementById("difficulty-select-custom").value + localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage + }); + }, + reset() { + build.isCustomSelection = true; + mech.setField(0) - mod.setupAllMods(); - build.populateGrid(); - document.getElementById("field-0").classList.add("build-field-selected"); - document.getElementById("build-grid").style.display = "grid" - }, - shareURL() { - let url = "https://landgreen.github.io/sidescroller/index.html?" - let count = 0; + b.inventory = []; //removes guns and ammo + for (let i = 0, len = b.guns.length; i < len; ++i) { + b.guns[i].count = 0; + b.guns[i].have = false; + if (b.guns[i].ammo != Infinity) b.guns[i].ammo = 0; + } + b.activeGun = null; + game.makeGunHUD(); - for (let i = 0; i < b.inventory.length; i++) { - if (b.guns[b.inventory[i]].have) { - url += `&gun${count}=${encodeURIComponent(b.guns[b.inventory[i]].name.trim())}` - count++ - } - } + mod.setupAllMods(); + build.populateGrid(); + document.getElementById("field-0").classList.add("build-field-selected"); + document.getElementById("build-grid").style.display = "grid" + }, + shareURL() { + let url = "https://landgreen.github.io/sidescroller/index.html?" + let count = 0; - count = 0; - for (let i = 0; i < mod.mods.length; i++) { - for (let j = 0; j < mod.mods[i].count; j++) { - url += `&mod${count}=${encodeURIComponent(mod.mods[i].name.trim())}` - count++ - } - } - url += `&field=${encodeURIComponent(mech.fieldUpgrades[mech.fieldMode].name.trim())}` - url += `&difficulty=${game.difficultyMode}` - url += `&level=${Math.abs(Number(document.getElementById("starting-level").value))}` - url += `&noPower=${Number(document.getElementById("no-power-ups").checked)}` - console.log(url) - game.copyToClipBoard(url) - alert('n-gon build URL copied to clipboard.\nPaste into browser address bar.') - }, - startBuildRun() { - build.isCustomSelection = false; - spawn.setSpawnList(); //gives random mobs, not starter mobs - spawn.setSpawnList(); - if (b.inventory.length > 0) { - b.activeGun = b.inventory[0] //set first gun to active gun - game.makeGunHUD(); - } - for (let i = 0; i < bullet.length; ++i) Matter.World.remove(engine.world, bullet[i]); - bullet = []; //remove any bullets that might have spawned from mods - const levelsCleared = Math.abs(Number(document.getElementById("starting-level").value)) - level.difficultyIncrease(Math.min(99, levelsCleared * game.difficultyMode)) //increase difficulty based on modes - level.levelsCleared += levelsCleared; - game.isNoPowerUps = document.getElementById("no-power-ups").checked - if (game.isNoPowerUps) { //remove mods, guns, and fields - function removeOne() { //recursive remove one at a time to avoid array problems - for (let i = 0; i < powerUp.length; i++) { - if (powerUp[i].name === "mod" || powerUp[i].name === "gun" || powerUp[i].name === "field") { - Matter.World.remove(engine.world, powerUp[i]); - powerUp.splice(i, 1); + for (let i = 0; i < b.inventory.length; i++) { + if (b.guns[b.inventory[i]].have) { + url += `&gun${count}=${encodeURIComponent(b.guns[b.inventory[i]].name.trim())}` + count++ + } + } + + count = 0; + for (let i = 0; i < mod.mods.length; i++) { + for (let j = 0; j < mod.mods[i].count; j++) { + url += `&mod${count}=${encodeURIComponent(mod.mods[i].name.trim())}` + count++ + } + } + url += `&field=${encodeURIComponent(mech.fieldUpgrades[mech.fieldMode].name.trim())}` + url += `&difficulty=${game.difficultyMode}` + url += `&level=${Math.abs(Number(document.getElementById("starting-level").value))}` + url += `&noPower=${Number(document.getElementById("no-power-ups").checked)}` + console.log(url) + game.copyToClipBoard(url) + alert('n-gon build URL copied to clipboard.\nPaste into browser address bar.') + }, + startBuildRun() { + build.isCustomSelection = false; + spawn.setSpawnList(); //gives random mobs, not starter mobs + spawn.setSpawnList(); + if (b.inventory.length > 0) { + b.activeGun = b.inventory[0] //set first gun to active gun + game.makeGunHUD(); + } + for (let i = 0; i < bullet.length; ++i) Matter.World.remove(engine.world, bullet[i]); + bullet = []; //remove any bullets that might have spawned from mods + const levelsCleared = Math.abs(Number(document.getElementById("starting-level").value)) + level.difficultyIncrease(Math.min(99, levelsCleared * game.difficultyMode)) //increase difficulty based on modes + level.levelsCleared += levelsCleared; + game.isNoPowerUps = document.getElementById("no-power-ups").checked + if (game.isNoPowerUps) { //remove mods, guns, and fields + function removeOne() { //recursive remove one at a time to avoid array problems + for (let i = 0; i < powerUp.length; i++) { + if (powerUp[i].name === "mod" || powerUp[i].name === "gun" || powerUp[i].name === "field") { + Matter.World.remove(engine.world, powerUp[i]); + powerUp.splice(i, 1); + removeOne(); + break + } + } + } removeOne(); - break - } } - } - removeOne(); + game.isCheating = true; + document.body.style.cursor = "none"; + document.body.style.overflow = "hidden" + document.getElementById("build-grid").style.display = "none" + game.paused = false; + requestAnimationFrame(cycle); } - game.isCheating = true; - document.body.style.cursor = "none"; - document.body.style.overflow = "hidden" - document.getElementById("build-grid").style.display = "none" - game.paused = false; - requestAnimationFrame(cycle); - } } function openCustomBuildMenu() { - document.getElementById("build-button").style.display = "none"; - const el = document.getElementById("build-grid") - el.style.display = "grid" - document.body.style.overflowY = "scroll"; - document.body.style.overflowX = "hidden"; - document.getElementById("info").style.display = 'none' - game.startGame(true); //starts game, but pauses it - build.isCustomSelection = true; - game.paused = true; - build.reset(); + document.getElementById("build-button").style.display = "none"; + const el = document.getElementById("build-grid") + el.style.display = "grid" + document.body.style.overflowY = "scroll"; + document.body.style.overflowX = "hidden"; + document.getElementById("info").style.display = 'none' + game.startGame(true); //starts game, but pauses it + build.isCustomSelection = true; + game.paused = true; + build.reset(); } //record settings so they can be reproduced in the custom menu document.getElementById("build-button").addEventListener("click", () => { //setup build run - let field = 0; - let inventory = []; - let modList = []; - if (!game.firstRun) { - field = mech.fieldMode - inventory = [...b.inventory] - for (let i = 0; i < mod.mods.length; i++) { - modList.push(mod.mods[i].count) - } - } - openCustomBuildMenu(); - if (!game.firstRun) { //if player has already died once load that previous build - build.choosePowerUp(document.getElementById(`field-${field}`), field, 'field') - for (let i = 0; i < inventory.length; i++) { - build.choosePowerUp(document.getElementById(`gun-${inventory[i]}`), inventory[i], 'gun') - } - for (let i = 0; i < modList.length; i++) { - for (let j = 0; j < modList[i]; j++) { - build.choosePowerUp(document.getElementById(`mod-${i}`), i, 'mod', true) - } - } - //update mod text //disable not allowed mods - for (let i = 0, len = mod.mods.length; i < len; i++) { - const modID = document.getElementById("mod-" + i) - if (!mod.mods[i].isCustomHide) { - if (mod.mods[i].allowed() || mod.mods[i].count > 1) { - if (mod.mods[i].count > 1) { - modID.innerHTML = `
  ${mod.mods[i].name} (${mod.mods[i].count}x)
${mod.mods[i].description}` - } else { - modID.innerHTML = `
  ${mod.mods[i].name}
${mod.mods[i].description}` - } - if (modID.classList.contains("build-grid-disabled")) { - modID.classList.remove("build-grid-disabled"); - modID.setAttribute("onClick", `javascript: build.choosePowerUp(this,${i},'mod')`); - } - } else { - modID.innerHTML = `
  ${mod.mods[i].name}
requires: ${mod.mods[i].requires}` - if (!modID.classList.contains("build-grid-disabled")) { - modID.classList.add("build-grid-disabled"); - modID.onclick = null - } + let field = 0; + let inventory = []; + let modList = []; + if (!game.firstRun) { + field = mech.fieldMode + inventory = [...b.inventory] + for (let i = 0; i < mod.mods.length; i++) { + modList.push(mod.mods[i].count) + } + } + openCustomBuildMenu(); + if (!game.firstRun) { //if player has already died once load that previous build + build.choosePowerUp(document.getElementById(`field-${field}`), field, 'field') + for (let i = 0; i < inventory.length; i++) { + build.choosePowerUp(document.getElementById(`gun-${inventory[i]}`), inventory[i], 'gun') + } + for (let i = 0; i < modList.length; i++) { + for (let j = 0; j < modList[i]; j++) { + build.choosePowerUp(document.getElementById(`mod-${i}`), i, 'mod', true) + } + } + //update mod text //disable not allowed mods + for (let i = 0, len = mod.mods.length; i < len; i++) { + const modID = document.getElementById("mod-" + i) + if (!mod.mods[i].isCustomHide) { + if (mod.mods[i].allowed() || mod.mods[i].count > 1) { + if (mod.mods[i].count > 1) { + modID.innerHTML = `
  ${mod.mods[i].name} (${mod.mods[i].count}x)
${mod.mods[i].description}` + } else { + modID.innerHTML = `
  ${mod.mods[i].name}
${mod.mods[i].description}` + } + if (modID.classList.contains("build-grid-disabled")) { + modID.classList.remove("build-grid-disabled"); + modID.setAttribute("onClick", `javascript: build.choosePowerUp(this,${i},'mod')`); + } + } else { + modID.innerHTML = `
  ${mod.mods[i].name}
requires: ${mod.mods[i].requires}` + if (!modID.classList.contains("build-grid-disabled")) { + modID.classList.add("build-grid-disabled"); + modID.onclick = null + } + } + } } - } } - } }); // ************************************************************************************************ // inputs // ************************************************************************************************ const input = { - fire: false, // left mouse - field: false, // right mouse - up: false, // jump - down: false, // crouch - left: false, - right: false, - isPauseKeyReady: true, - key: { - // fire: "ShiftLeft", - field: "Space", - up: "KeyW", // jump - down: "KeyS", // crouch - left: "KeyA", - right: "KeyD", - pause: "KeyP", - nextGun: "KeyE", - previousGun: "KeyQ", - testing: "KeyT" - }, - setDefault() { - input.key = { - // fire: "ShiftLeft", - field: "Space", - up: "KeyW", // jump - down: "KeyS", // crouch - left: "KeyA", - right: "KeyD", - pause: "KeyP", - nextGun: "KeyE", - previousGun: "KeyQ", - testing: "KeyT" - } - input.controlTextUpdate() - }, - controlTextUpdate() { - function cleanText(text) { - return text.replace('Key', '').replace('Digit', '') - } - document.getElementById("key-field").innerHTML = cleanText(input.key.field) - document.getElementById("key-up").innerHTML = cleanText(input.key.up) - document.getElementById("key-down").innerHTML = cleanText(input.key.down) - document.getElementById("key-left").innerHTML = cleanText(input.key.left) - document.getElementById("key-right").innerHTML = cleanText(input.key.right) - document.getElementById("key-pause").innerHTML = cleanText(input.key.pause) - document.getElementById("key-next-gun").innerHTML = cleanText(input.key.nextGun) - document.getElementById("key-previous-gun").innerHTML = cleanText(input.key.previousGun) - document.getElementById("key-testing").innerHTML = cleanText(input.key.testing) + fire: false, // left mouse + field: false, // right mouse + up: false, // jump + down: false, // crouch + left: false, + right: false, + isPauseKeyReady: true, + key: { + // fire: "ShiftLeft", + field: "Space", + up: "KeyW", // jump + down: "KeyS", // crouch + left: "KeyA", + right: "KeyD", + pause: "KeyP", + nextGun: "KeyE", + previousGun: "KeyQ", + testing: "KeyT" + }, + setDefault() { + input.key = { + // fire: "ShiftLeft", + field: "Space", + up: "KeyW", // jump + down: "KeyS", // crouch + left: "KeyA", + right: "KeyD", + pause: "KeyP", + nextGun: "KeyE", + previousGun: "KeyQ", + testing: "KeyT" + } + input.controlTextUpdate() + }, + controlTextUpdate() { + function cleanText(text) { + return text.replace('Key', '').replace('Digit', '') + } + document.getElementById("key-field").innerHTML = cleanText(input.key.field) + document.getElementById("key-up").innerHTML = cleanText(input.key.up) + document.getElementById("key-down").innerHTML = cleanText(input.key.down) + document.getElementById("key-left").innerHTML = cleanText(input.key.left) + document.getElementById("key-right").innerHTML = cleanText(input.key.right) + document.getElementById("key-pause").innerHTML = cleanText(input.key.pause) + document.getElementById("key-next-gun").innerHTML = cleanText(input.key.nextGun) + document.getElementById("key-previous-gun").innerHTML = cleanText(input.key.previousGun) + document.getElementById("key-testing").innerHTML = cleanText(input.key.testing) - document.getElementById("splash-up").innerHTML = cleanText(input.key.up)[0] - document.getElementById("splash-down").innerHTML = cleanText(input.key.down)[0] - document.getElementById("splash-left").innerHTML = cleanText(input.key.left)[0] - document.getElementById("splash-right").innerHTML = cleanText(input.key.right)[0] - document.getElementById("splash-next-gun").innerHTML = cleanText(input.key.nextGun)[0] - document.getElementById("splash-previous-gun").innerHTML = cleanText(input.key.previousGun)[0] + document.getElementById("splash-up").innerHTML = cleanText(input.key.up)[0] + document.getElementById("splash-down").innerHTML = cleanText(input.key.down)[0] + document.getElementById("splash-left").innerHTML = cleanText(input.key.left)[0] + document.getElementById("splash-right").innerHTML = cleanText(input.key.right)[0] + document.getElementById("splash-next-gun").innerHTML = cleanText(input.key.nextGun)[0] + document.getElementById("splash-previous-gun").innerHTML = cleanText(input.key.previousGun)[0] - localSettings.key = input.key - localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage - }, - focus: null, - setTextFocus() { - const backgroundColor = "#fff" - document.getElementById("key-field").style.background = backgroundColor - document.getElementById("key-up").style.background = backgroundColor - document.getElementById("key-down").style.background = backgroundColor - document.getElementById("key-left").style.background = backgroundColor - document.getElementById("key-right").style.background = backgroundColor - document.getElementById("key-pause").style.background = backgroundColor - document.getElementById("key-next-gun").style.background = backgroundColor - document.getElementById("key-previous-gun").style.background = backgroundColor - document.getElementById("key-testing").style.background = backgroundColor - if (input.focus) input.focus.style.background = 'rgb(0, 200, 255)'; - }, - setKeys(event) { - //check for duplicate keys - if (event.code && !( - event.code === "ArrowRight" || - event.code === "ArrowLeft" || - event.code === "ArrowUp" || - event.code === "ArrowDown" || - event.code === input.key.field || - event.code === input.key.up || - event.code === input.key.down || - event.code === input.key.left || - event.code === input.key.right || - event.code === input.key.pause || - event.code === input.key.nextGun || - event.code === input.key.previousGun || - event.code === input.key.testing - )) { - switch (input.focus.id) { - case "key-field": - input.key.field = event.code - break; - case "key-up": - input.key.up = event.code - break; - case "key-down": - input.key.down = event.code - break; - case "key-left": - input.key.left = event.code - break; - case "key-right": - input.key.right = event.code - break; - case "key-pause": - input.key.pause = event.code - break; - case "key-next-gun": - input.key.nextGun = event.code - break; - case "key-previous-gun": - input.key.previousGun = event.code - break; - case "key-testing": - input.key.testing = event.code - break; - } + localSettings.key = input.key + localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage + }, + focus: null, + setTextFocus() { + const backgroundColor = "#fff" + document.getElementById("key-field").style.background = backgroundColor + document.getElementById("key-up").style.background = backgroundColor + document.getElementById("key-down").style.background = backgroundColor + document.getElementById("key-left").style.background = backgroundColor + document.getElementById("key-right").style.background = backgroundColor + document.getElementById("key-pause").style.background = backgroundColor + document.getElementById("key-next-gun").style.background = backgroundColor + document.getElementById("key-previous-gun").style.background = backgroundColor + document.getElementById("key-testing").style.background = backgroundColor + if (input.focus) input.focus.style.background = 'rgb(0, 200, 255)'; + }, + setKeys(event) { + //check for duplicate keys + if (event.code && !( + event.code === "ArrowRight" || + event.code === "ArrowLeft" || + event.code === "ArrowUp" || + event.code === "ArrowDown" || + event.code === input.key.field || + event.code === input.key.up || + event.code === input.key.down || + event.code === input.key.left || + event.code === input.key.right || + event.code === input.key.pause || + event.code === input.key.nextGun || + event.code === input.key.previousGun || + event.code === input.key.testing + )) { + switch (input.focus.id) { + case "key-field": + input.key.field = event.code + break; + case "key-up": + input.key.up = event.code + break; + case "key-down": + input.key.down = event.code + break; + case "key-left": + input.key.left = event.code + break; + case "key-right": + input.key.right = event.code + break; + case "key-pause": + input.key.pause = event.code + break; + case "key-next-gun": + input.key.nextGun = event.code + break; + case "key-previous-gun": + input.key.previousGun = event.code + break; + case "key-testing": + input.key.testing = event.code + break; + } + } + input.controlTextUpdate() + input.endKeySensing() + }, + endKeySensing() { + window.removeEventListener("keydown", input.setKeys); + input.focus = null + input.setTextFocus() } - input.controlTextUpdate() - input.endKeySensing() - }, - endKeySensing() { - window.removeEventListener("keydown", input.setKeys); - input.focus = null - input.setTextFocus() - } } document.getElementById("control-table").addEventListener('click', (event) => { - if (event.target.className === 'key-input') { - input.focus = event.target - input.setTextFocus() - window.addEventListener("keydown", input.setKeys); - } + if (event.target.className === 'key-input') { + input.focus = event.target + input.setTextFocus() + window.addEventListener("keydown", input.setKeys); + } }); -document.getElementById("control-details").addEventListener("toggle", function () { - input.controlTextUpdate() - input.endKeySensing(); +document.getElementById("control-details").addEventListener("toggle", function() { + input.controlTextUpdate() + input.endKeySensing(); }) document.getElementById("control-reset").addEventListener('click', input.setDefault); -window.addEventListener("keyup", function (event) { - switch (event.code) { - case input.key.right: - case "ArrowRight": - input.right = false - break; - case input.key.left: - case "ArrowLeft": - input.left = false - break; - case input.key.up: - case "ArrowUp": - input.up = false - break; - case input.key.down: - case "ArrowDown": - input.down = false - break; - case input.key.field: - input.field = false - break - } +window.addEventListener("keyup", function(event) { + switch (event.code) { + case input.key.right: + case "ArrowRight": + input.right = false + break; + case input.key.left: + case "ArrowLeft": + input.left = false + break; + case input.key.up: + case "ArrowUp": + input.up = false + break; + case input.key.down: + case "ArrowDown": + input.down = false + break; + case input.key.field: + input.field = false + break + } }); -window.addEventListener("keydown", function (event) { - switch (event.code) { - case input.key.right: - case "ArrowRight": - input.right = true - break; - case input.key.left: - case "ArrowLeft": - input.left = true - break; - case input.key.up: - case "ArrowUp": - input.up = true - break; - case input.key.down: - case "ArrowDown": - input.down = true - break; - case input.key.field: - input.field = true - break - case input.key.nextGun: - game.nextGun(); - break - case input.key.previousGun: - game.previousGun(); - break - case input.key.pause: - if (!game.isChoosing && input.isPauseKeyReady && mech.alive) { - input.isPauseKeyReady = false - setTimeout(function () { - input.isPauseKeyReady = true - }, 300); - if (game.paused) { - build.unPauseGrid() - game.paused = false; - level.levelAnnounce(); - document.body.style.cursor = "none"; - requestAnimationFrame(cycle); - } else { - game.paused = true; - game.replaceTextLog = true; - build.pauseGrid() - document.body.style.cursor = "auto"; - } - } - break - case input.key.testing: - if (mech.alive) { - if (game.testing) { - game.testing = false; - game.loop = game.normalLoop - if (game.isConstructionMode) { - document.getElementById("construct").style.display = 'none' - } - } else { //if (keys[191]) - game.testing = true; - game.isCheating = true; - if (game.isConstructionMode) { - document.getElementById("construct").style.display = 'inline' - } - game.loop = game.testingLoop - } - } - break - } - if (game.testing) { - switch (event.key) { - case "o": - game.isAutoZoom = false; - game.zoomScale /= 0.9; - game.setZoom(); - break; - case "i": - game.isAutoZoom = false; - game.zoomScale *= 0.9; - game.setZoom(); - break - case "`": - powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "reroll"); - break - case "1": - powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "heal"); - break - case "2": - powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "ammo"); - break - case "3": - powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "gun"); - break - case "4": - powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "field"); - break - case "5": - powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "mod"); - break - case "6": - const index = body.length - spawn.bodyRect(game.mouseInGame.x, game.mouseInGame.y, 50, 50); - body[index].collisionFilter.category = cat.body; - body[index].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet - body[index].classType = "body"; - World.add(engine.world, body[index]); //add to world - break - case "7": - const pick = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]; - spawn[pick](game.mouseInGame.x, game.mouseInGame.y); - break - case "8": - spawn.randomLevelBoss(game.mouseInGame.x, game.mouseInGame.y); - break - case "f": - const mode = (mech.fieldMode === mech.fieldUpgrades.length - 1) ? 0 : mech.fieldMode + 1 - mech.setField(mode) - break - case "g": - b.giveGuns("all", 1000) - break - case "h": - mech.addHealth(Infinity) - mech.energy = mech.maxEnergy; - break - case "y": - mod.giveMod() - break - case "r": - Matter.Body.setPosition(player, game.mouseInGame); - Matter.Body.setVelocity(player, { - x: 0, - y: 0 - }); - // move bots to follow player - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType) { - Matter.Body.setPosition(bullet[i], Vector.add(player.position, { - x: 250 * (Math.random() - 0.5), - y: 250 * (Math.random() - 0.5) - })); - Matter.Body.setVelocity(bullet[i], { - x: 0, - y: 0 - }); - } - } - break - case "u": - level.bossKilled = true; //if there is no boss this needs to be true to increase levels - level.nextLevel(); - break - case "X": //capital X to make it hard to die - mech.death(); - break +window.addEventListener("keydown", function(event) { + switch (event.code) { + case input.key.right: + case "ArrowRight": + input.right = true + break; + case input.key.left: + case "ArrowLeft": + input.left = true + break; + case input.key.up: + case "ArrowUp": + input.up = true + break; + case input.key.down: + case "ArrowDown": + input.down = true + break; + case input.key.field: + input.field = true + break + case input.key.nextGun: + game.nextGun(); + break + case input.key.previousGun: + game.previousGun(); + break + case input.key.pause: + if (!game.isChoosing && input.isPauseKeyReady && mech.alive) { + input.isPauseKeyReady = false + setTimeout(function() { + input.isPauseKeyReady = true + }, 300); + if (game.paused) { + build.unPauseGrid() + game.paused = false; + level.levelAnnounce(); + document.body.style.cursor = "none"; + requestAnimationFrame(cycle); + } else { + game.paused = true; + game.replaceTextLog = true; + build.pauseGrid() + document.body.style.cursor = "auto"; + } + } + break + case input.key.testing: + if (mech.alive) { + if (game.testing) { + game.testing = false; + game.loop = game.normalLoop + if (game.isConstructionMode) { + document.getElementById("construct").style.display = 'none' + } + } else { //if (keys[191]) + game.testing = true; + game.isCheating = true; + if (game.isConstructionMode) { + document.getElementById("construct").style.display = 'inline' + } + game.loop = game.testingLoop + } + } + break + } + if (game.testing) { + switch (event.key) { + case "o": + game.isAutoZoom = false; + game.zoomScale /= 0.9; + game.setZoom(); + break; + case "i": + game.isAutoZoom = false; + game.zoomScale *= 0.9; + game.setZoom(); + break + case "`": + powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "reroll"); + break + case "1": + powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "heal"); + break + case "2": + powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "ammo"); + break + case "3": + powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "gun"); + break + case "4": + powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "field"); + break + case "5": + powerUps.directSpawn(game.mouseInGame.x, game.mouseInGame.y, "mod"); + break + case "6": + const index = body.length + spawn.bodyRect(game.mouseInGame.x, game.mouseInGame.y, 50, 50); + body[index].collisionFilter.category = cat.body; + body[index].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet + body[index].classType = "body"; + World.add(engine.world, body[index]); //add to world + break + case "7": + const pick = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]; + spawn[pick](game.mouseInGame.x, game.mouseInGame.y); + break + case "8": + spawn.randomLevelBoss(game.mouseInGame.x, game.mouseInGame.y); + break + case "f": + const mode = (mech.fieldMode === mech.fieldUpgrades.length - 1) ? 0 : mech.fieldMode + 1 + mech.setField(mode) + break + case "g": + b.giveGuns("all", 1000) + break + case "h": + mech.addHealth(Infinity) + mech.energy = mech.maxEnergy; + break + case "y": + mod.giveMod() + break + case "r": + Matter.Body.setPosition(player, game.mouseInGame); + Matter.Body.setVelocity(player, { + x: 0, + y: 0 + }); + // move bots to follow player + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType) { + Matter.Body.setPosition(bullet[i], Vector.add(player.position, { + x: 250 * (Math.random() - 0.5), + y: 250 * (Math.random() - 0.5) + })); + Matter.Body.setVelocity(bullet[i], { + x: 0, + y: 0 + }); + } + } + break + case "u": + level.bossKilled = true; //if there is no boss this needs to be true to increase levels + level.nextLevel(); + break + case "X": //capital X to make it hard to die + mech.death(); + break + } } - } }); //mouse move input document.body.addEventListener("mousemove", (e) => { - game.mouse.x = e.clientX; - game.mouse.y = e.clientY; + game.mouse.x = e.clientX; + game.mouse.y = e.clientY; }); document.body.addEventListener("mouseup", (e) => { - // input.fire = false; - // console.log(e) - if (e.which === 3) { - input.field = false; - } else { - input.fire = false; - } + // input.fire = false; + // console.log(e) + if (e.which === 3) { + input.field = false; + } else { + input.fire = false; + } }); document.body.addEventListener("mousedown", (e) => { - if (e.which === 3) { - input.field = true; - } else { - input.fire = true; - } + if (e.which === 3) { + input.field = true; + } else { + input.fire = true; + } }); document.body.addEventListener("mouseenter", (e) => { //prevents mouse getting stuck when leaving the window - if (e.button === 1) { - input.fire = true; - } else { - input.fire = false; - } + if (e.button === 1) { + input.fire = true; + } else { + input.fire = false; + } - if (e.button === 3) { - input.field = true; - } else { - input.field = false; - } + if (e.button === 3) { + input.field = true; + } else { + input.field = false; + } }); document.body.addEventListener("mouseleave", (e) => { //prevents mouse getting stuck when leaving the window - if (e.button === 1) { - input.fire = true; - } else { - input.fire = false; - } + if (e.button === 1) { + input.fire = true; + } else { + input.fire = false; + } - if (e.button === 3) { - input.field = true; - } else { - input.field = false; - } + if (e.button === 3) { + input.field = true; + } else { + input.field = false; + } }); document.body.addEventListener("wheel", (e) => { - if (!game.paused) { - if (e.deltaY > 0) { - game.nextGun(); - } else { - game.previousGun(); + if (!game.paused) { + if (e.deltaY > 0) { + game.nextGun(); + } else { + game.previousGun(); + } } - } }, { - passive: true + passive: true }); //********************************************************************** @@ -896,38 +896,38 @@ document.body.addEventListener("wheel", (e) => { //********************************************************************** let localSettings = JSON.parse(localStorage.getItem("localSettings")); if (localSettings) { - if (localSettings.key) { - input.key = localSettings.key - } else { - input.setDefault() - } + if (localSettings.key) { + input.key = localSettings.key + } else { + input.setDefault() + } - game.isCommunityMaps = localSettings.isCommunityMaps - document.getElementById("community-maps").checked = localSettings.isCommunityMaps - game.difficultyMode = localSettings.difficultyMode - document.getElementById("difficulty-select").value = localSettings.difficultyMode - if (localSettings.fpsCapDefault === 'max') { - game.fpsCapDefault = 999999999; - } else { - game.fpsCapDefault = Number(localSettings.fpsCapDefault) - } - document.getElementById("fps-select").value = localSettings.fpsCapDefault + game.isCommunityMaps = localSettings.isCommunityMaps + document.getElementById("community-maps").checked = localSettings.isCommunityMaps + game.difficultyMode = localSettings.difficultyMode + document.getElementById("difficulty-select").value = localSettings.difficultyMode + if (localSettings.fpsCapDefault === 'max') { + game.fpsCapDefault = 999999999; + } else { + game.fpsCapDefault = Number(localSettings.fpsCapDefault) + } + document.getElementById("fps-select").value = localSettings.fpsCapDefault } else { - localSettings = { - isCommunityMaps: false, - difficultyMode: '2', - fpsCapDefault: 'max', - runCount: 0, - levelsClearedLastGame: 0, - key: undefined - }; - input.setDefault() - localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage - document.getElementById("community-maps").checked = localSettings.isCommunityMaps - game.isCommunityMaps = localSettings.isCommunityMaps - document.getElementById("difficulty-select").value = localSettings.difficultyMode - document.getElementById("fps-select").value = localSettings.fpsCapDefault + localSettings = { + isCommunityMaps: false, + difficultyMode: '2', + fpsCapDefault: 'max', + runCount: 0, + levelsClearedLastGame: 0, + key: undefined + }; + input.setDefault() + localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage + document.getElementById("community-maps").checked = localSettings.isCommunityMaps + game.isCommunityMaps = localSettings.isCommunityMaps + document.getElementById("difficulty-select").value = localSettings.difficultyMode + document.getElementById("fps-select").value = localSettings.fpsCapDefault } input.controlTextUpdate() @@ -935,28 +935,28 @@ input.controlTextUpdate() // settings //********************************************************************** document.getElementById("fps-select").addEventListener("input", () => { - let value = document.getElementById("fps-select").value - if (value === 'max') { - game.fpsCapDefault = 999999999; - } else { - game.fpsCapDefault = Number(value) - } - localSettings.fpsCapDefault = value - localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage + let value = document.getElementById("fps-select").value + if (value === 'max') { + game.fpsCapDefault = 999999999; + } else { + game.fpsCapDefault = Number(value) + } + localSettings.fpsCapDefault = value + localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage }); document.getElementById("community-maps").addEventListener("input", () => { - game.isCommunityMaps = document.getElementById("community-maps").checked - localSettings.isCommunityMaps = game.isCommunityMaps - localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage + game.isCommunityMaps = document.getElementById("community-maps").checked + localSettings.isCommunityMaps = game.isCommunityMaps + localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage }); // difficulty-select-custom event listener is set in build.makeGrid document.getElementById("difficulty-select").addEventListener("input", () => { - game.difficultyMode = Number(document.getElementById("difficulty-select").value) - localSettings.difficultyMode = game.difficultyMode - localSettings.levelsClearedLastGame = 0 //after changing difficulty, reset run history - localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage + game.difficultyMode = Number(document.getElementById("difficulty-select").value) + localSettings.difficultyMode = game.difficultyMode + localSettings.levelsClearedLastGame = 0 //after changing difficulty, reset run history + localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage }); //********************************************************************** @@ -965,31 +965,31 @@ document.getElementById("difficulty-select").addEventListener("input", () => { game.loop = game.normalLoop; function cycle() { - if (!game.paused) requestAnimationFrame(cycle); - const now = Date.now(); - const elapsed = now - game.then; // calc elapsed time since last loop - if (elapsed > game.fpsInterval) { // if enough time has elapsed, draw the next frame - game.then = now - (elapsed % game.fpsInterval); // Get ready for next frame by setting then=now. Also, adjust for fpsInterval not being multiple of 16.67 + if (!game.paused) requestAnimationFrame(cycle); + const now = Date.now(); + const elapsed = now - game.then; // calc elapsed time since last loop + if (elapsed > game.fpsInterval) { // if enough time has elapsed, draw the next frame + game.then = now - (elapsed % game.fpsInterval); // Get ready for next frame by setting then=now. Also, adjust for fpsInterval not being multiple of 16.67 - game.cycle++; //tracks game cycles - mech.cycle++; //tracks player cycles //used to alow time to stop for everything, but the player - if (game.clearNow) { - game.clearNow = false; - game.clearMap(); - level.start(); + game.cycle++; //tracks game cycles + mech.cycle++; //tracks player cycles //used to alow time to stop for everything, but the player + if (game.clearNow) { + game.clearNow = false; + game.clearMap(); + level.start(); + } + + game.loop(); + // if (isNaN(mech.health) || isNaN(mech.energy)) { + // console.log(`mech.health = ${mech.health}`) + // game.paused = true; + // game.replaceTextLog = true; + // build.pauseGrid() + // document.body.style.cursor = "auto"; + // alert("health is NaN, please report this bug to the discord \n https://discordapp.com/invite/2eC9pgJ") + // } + // for (let i = 0, len = loop.length; i < len; i++) { + // loop[i]() + // } } - - game.loop(); - // if (isNaN(mech.health) || isNaN(mech.energy)) { - // console.log(`mech.health = ${mech.health}`) - // game.paused = true; - // game.replaceTextLog = true; - // build.pauseGrid() - // document.body.style.cursor = "auto"; - // alert("health is NaN, please report this bug to the discord \n https://discordapp.com/invite/2eC9pgJ") - // } - // for (let i = 0, len = loop.length; i < len; i++) { - // loop[i]() - // } - } } \ No newline at end of file diff --git a/js/level.js b/js/level.js index 9988ec3..9f1242b 100644 --- a/js/level.js +++ b/js/level.js @@ -4,4885 +4,4494 @@ let cons = []; //all constraints between a point and a body let consBB = []; //all constraints between two bodies let composite = [] //rotors and other map elements that don't fit const level = { - defaultZoom: 1400, - onLevel: 0, - levelsCleared: 0, - bossKilled: false, - levels: ["skyscrapers", "rooftops", "warehouse", "highrise", "office", "aerie", "satellite", "sewers", "testChamber"], - start() { - if (level.levelsCleared === 0) { //this code only runs on the first level - // level.difficultyIncrease(8) - // game.enableConstructMode() //used to build maps in testing mode - // game.zoomScale = 1000; - // game.setZoom(); - // mech.isCloak = true; - // mech.setField("wormhole") - // b.giveGuns("grenades") - // for (let i = 0; i < 10; i++) { - // mod.giveMod("laser-bot"); - // } - // mod.giveMod("cardinality") + defaultZoom: 1400, + onLevel: -1, + levelsCleared: 0, + bossKilled: false, + levels: ["skyscrapers", "rooftops", "warehouse", "highrise", "office", "aerie", "satellite", "sewers", "testChamber"], + start() { + if (level.levelsCleared === 0) { //this code only runs on the first level + // level.difficultyIncrease(8) + // game.enableConstructMode() //used to build maps in testing mode + // game.zoomScale = 1000; + // game.setZoom(); + // mech.isCloak = true; + // mech.setField("wormhole") + // b.giveGuns("grenades") + // for (let i = 0; i < 10; i++) { + // mod.giveMod("laser-bot"); + // } + // mod.giveMod("cardinality") - level.intro(); //starting level - // level.testing(); //not in rotation - // level.template() //not in rotation - // level.testChamber() //less mobs, more puzzle - // level.sewers(); - // level.satellite(); - // level.skyscrapers(); - // level.aerie(); - // level.rooftops(); - // level.warehouse(); - // level.highrise(); - // level.office(); - // level.bosses(); //only fighting, very simple map - // level.house() //fan level - // level.detours() //fan level - // level.basement(); //fan level - // level.stronghold() //fan level - } else { - spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns - // spawn.pickList = ["focuser", "focuser"] - level[level.levels[level.onLevel]](); //picks the current map from the the levels array - if (!game.isCheating) { - localSettings.runCount += level.levelsCleared //track the number of total runs locally - localSettings.levelsClearedLastGame = level.levelsCleared - localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage - } - } - level.levelAnnounce(); - game.noCameraScroll(); - game.setZoom(); - level.addToWorld(); //add bodies to game engine - game.draw.setPaths(); - b.respawnBots(); - if (mod.isArmorFromPowerUps) { - mod.armorFromPowerUps += 0.05 * powerUps.totalPowerUps - mech.setMaxHealth(); - if (powerUps.totalPowerUps) game.makeTextLog(" max health increased by " + (0.05 * powerUps.totalPowerUps * 100).toFixed(0) + "%", 300) - } - if (mod.isHealLowHealth) { - const len = Math.floor((mech.maxHealth - mech.health) / 0.5) - for (let i = 0; i < len; i++) { - powerUps.spawn(mech.pos.x + 60 * (Math.random() - 0.5), mech.pos.y + 60 * (Math.random() - 0.5), "heal", false); - } - } - if (mod.isGunCycle) { - b.inventoryGun++; - if (b.inventoryGun > b.inventory.length - 1) b.inventoryGun = 0; - game.switchGun(); - } - }, - custom() {}, - customTopLayer() {}, - //****************************************************************************************************************** - //****************************************************************************************************************** - //****************************************************************************************************************** - //****************************************************************************************************************** - testing() { - const button = level.button(200, -700) - level.custom = () => { - button.query(); - button.draw(); - level.playerExitCheck(); - }; - level.customTopLayer = () => {}; + level.intro(); //starting level + // level.finalBoss() //final boss level + // level.gauntlet(); //before final boss level + // level.testing(); //not in rotation + // level.template() //not in rotation + // level.testChamber() //less mobs, more puzzle + // level.sewers(); + // level.satellite(); + // level.skyscrapers(); + // level.aerie(); + // level.rooftops(); + // level.warehouse(); + // level.highrise(); + // level.office(); + // level.gauntlet(); //only fighting, very simple map + // level.house() //fan level + // level.detours() //fan level + // level.basement(); //fan level + // level.stronghold() //fan level + } else { + spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns + // spawn.pickList = ["focuser", "focuser"] + level[level.levels[level.onLevel]](); //picks the current map from the the levels array + if (!game.isCheating) { + localSettings.runCount += level.levelsCleared //track the number of total runs locally + localSettings.levelsClearedLastGame = level.levelsCleared + localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage + } + } + level.levelAnnounce(); + game.noCameraScroll(); + game.setZoom(); + level.addToWorld(); //add bodies to game engine + game.draw.setPaths(); + b.respawnBots(); + if (mod.isArmorFromPowerUps) { + mod.armorFromPowerUps += 0.05 * powerUps.totalPowerUps + mech.setMaxHealth(); + if (powerUps.totalPowerUps) game.makeTextLog(" max health increased by " + (0.05 * powerUps.totalPowerUps * 100).toFixed(0) + "%", 300) + } + if (mod.isHealLowHealth) { + const len = Math.floor((mech.maxHealth - mech.health) / 0.5) + for (let i = 0; i < len; i++) { + powerUps.spawn(mech.pos.x + 60 * (Math.random() - 0.5), mech.pos.y + 60 * (Math.random() - 0.5), "heal", false); + } + } + if (mod.isGunCycle) { + b.inventoryGun++; + if (b.inventoryGun > b.inventory.length - 1) b.inventoryGun = 0; + game.switchGun(); + } + }, + custom() {}, + customTopLayer() {}, + //****************************************************************************************************************** + //****************************************************************************************************************** + //****************************************************************************************************************** + //****************************************************************************************************************** + testing() { + const button = level.button(200, -700) + level.custom = () => { + button.query(); + button.draw(); + level.playerExitCheck(); + }; + level.customTopLayer = () => {}; - level.setPosToSpawn(0, -750); //normal spawn - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - level.exit.x = 6500; - level.exit.y = -230; + level.setPosToSpawn(0, -750); //normal spawn + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + level.exit.x = 6500; + level.exit.y = -230; - // level.difficultyIncrease(14); //hard mode level 7 - spawn.setSpawnList(); - spawn.setSpawnList(); - level.defaultZoom = 1500 - game.zoomTransition(level.defaultZoom) - document.body.style.backgroundColor = "#ddd"; - // game.draw.mapFill = "#444" - // game.draw.bodyFill = "rgba(140,140,140,0.85)" - // game.draw.bodyStroke = "#222" + // level.difficultyIncrease(14); //hard mode level 7 + spawn.setSpawnList(); + spawn.setSpawnList(); + level.defaultZoom = 1500 + game.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#ddd"; + // game.draw.mapFill = "#444" + // game.draw.bodyFill = "rgba(140,140,140,0.85)" + // game.draw.bodyStroke = "#222" - level.fill.push({ - x: 6400, - y: -550, - width: 300, - height: 350, - color: "rgba(0,255,255,0.1)" - }); - - // level.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); - - spawn.mapRect(-950, 0, 8200, 800); //ground - spawn.mapRect(-950, -1200, 800, 1400); //left wall - spawn.mapRect(-950, -1800, 8200, 800); //roof - spawn.mapRect(-250, -700, 1000, 900); // shelf - spawn.mapRect(-250, -1200, 1000, 250); // shelf roof - // powerUps.spawnStartingPowerUps(600, -800); - powerUps.spawn(550, -800, "reroll", false); - - function blockDoor(x, y, blockSize = 58) { - spawn.mapRect(x, y - 290, 40, 60); // door lip - spawn.mapRect(x, y, 40, 50); // door lip - for (let i = 0; i < 4; ++i) { - spawn.bodyRect(x + 5, y - 260 + i * blockSize, 30, blockSize); - } - } - blockDoor(710, -710); - spawn.mapRect(2500, -1200, 200, 750); //right wall - blockDoor(2585, -210) - spawn.mapRect(2500, -200, 200, 300); //right wall - spawn.mapRect(4500, -1200, 200, 650); //right wall - blockDoor(4585, -310) - spawn.mapRect(4500, -300, 200, 400); //right wall - spawn.mapRect(6400, -1200, 400, 750); //right wall - spawn.mapRect(6400, -200, 400, 300); //right wall - spawn.mapRect(6700, -1800, 800, 2600); //right wall - spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump - // spawn.boost(1500, 0, 900); - - // spawn.starter(1600, -500) - // spawn.bomberBoss(2900, -500) - // spawn.launcherBoss(1200, -500) - // spawn.laserTargetingBoss(1600, -400) - // spawn.spawner(1600, -500) - // spawn.sniper(1700, -120, 50) - // spawn.bomberBoss(1400, -500) - spawn.launcher(1800, -120) - // spawn.cellBossCulture(1600, -500) - // spawn.powerUpBoss(1600, -500) - // spawn.sniper(1200, -500) - // spawn.shield(mob[mob.length - 1], 1200, -500, 1); - - // spawn.nodeBoss(1200, -500, "launcher") - // spawn.snakeBoss(1200, -500) - // spawn.timeSkipBoss(2900, -500) - // spawn.randomMob(1600, -500) - }, - template() { - level.custom = () => { - level.playerExitCheck(); - }; - level.customTopLayer = () => {}; - level.setPosToSpawn(0, -50); //normal spawn - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - level.exit.x = 1500; - level.exit.y = -1875; - level.defaultZoom = 1800 - game.zoomTransition(level.defaultZoom) - document.body.style.backgroundColor = "#dcdcde"; - // powerUps.spawnStartingPowerUps(1475, -1175); - // spawn.debris(750, -2200, 3700, 16); //16 debris per level - // level.fill.push({ //foreground - // x: 2500, - // y: -1100, - // width: 450, - // height: 250, - // color: "rgba(0,0,0,0.1)" - // }); - // level.fillBG.push({ //background - // x: 1300, - // y: -1800, - // width: 750, - // height: 1800, - // color: "#d4d4d7" - // }); - - spawn.mapRect(-100, 0, 1000, 100); - // spawn.bodyRect(1540, -1110, 300, 25, 0.9); - // spawn.boost(4150, 0, 1300); - // spawn.randomSmallMob(1300, -70); - // spawn.randomMob(2650, -975, 0.8); - // spawn.randomBoss(1700, -900, 0.4); - // if (game.difficulty > 3) spawn.randomLevelBoss(2200, -1300); - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - testChamber() { - level.setPosToSpawn(0, -50); //lower start - level.exit.y = level.enter.y - 550; - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - level.exit.x = level.enter.x; - spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); - level.defaultZoom = 2200 - game.zoomTransition(level.defaultZoom) - document.body.style.backgroundColor = "#d5d5d5"; - - const portal = level.portal({ - x: 2475, - y: -140 - }, Math.PI, { //left - x: 2475, - y: -3140 - }, Math.PI) //left - const portal2 = level.portal({ - x: 75, - y: -2150 - }, -Math.PI / 2, { //up - x: 1325, - y: -2150 - }, -Math.PI / 2) //up - const portal3 = level.portal({ - x: 1850, - y: -585 - }, -Math.PI / 2, { //up - x: 2425, - y: -600 - }, -2 * Math.PI / 3) //up left - - const hazard = level.hazard(350, -2025, 700, 10, 0.4, "hsl(0, 100%, 50%)", true) //laser - const hazard2 = level.hazard(1775, -2550, 150, 10, 0.4, "hsl(0, 100%, 50%)", true) //laser - const button = level.button(2100, -2600) - - - const buttonDoor = level.button(600, -550) - // spawn.mapRect(600, -600, 275, 75); - const door = level.door(312, -750, 25, 190, 185) - - level.custom = () => { - buttonDoor.query(); - buttonDoor.draw(); - if (buttonDoor.isUp) { - door.isOpen = true - } else { - door.isOpen = false - } - door.openClose(); - - portal[2].query() - portal[3].query() - portal2[2].query() - portal2[3].query() - portal3[2].query() - portal3[3].query() - hazard.query(); - hazard2.query(); - if (button.isUp) { - hazard.isOn = false; - hazard2.isOn = false; - } else { - hazard.isOn = true; - hazard2.isOn = true; - } - button.query(); - button.draw(); - level.playerExitCheck(); - }; - level.customTopLayer = () => { - door.draw(); - hazard.draw(); - hazard2.draw(); - portal[0].draw(); - portal[1].draw(); - portal[2].draw(); - portal[3].draw(); - portal2[0].draw(); - portal2[1].draw(); - portal2[2].draw(); - portal2[3].draw(); - portal3[0].draw(); - portal3[1].draw(); - portal3[2].draw(); - portal3[3].draw(); - }; - powerUps.spawnStartingPowerUps(1875, -3075); - - const powerUpPos = shuffle([{ //no debris on this level but 2 random spawn instead - x: -150, - y: -1775 - }, { - x: 2400, - y: -2650 - }, { - x: -175, - y: -1375 - }, { - x: 1325, - y: -150 - }]); - powerUps.chooseRandomPowerUp(powerUpPos[0].x, powerUpPos[0].y); - powerUps.chooseRandomPowerUp(powerUpPos[1].x, powerUpPos[1].y); - level.fillBG.push({ //exit room - x: -300, - y: -1000, - width: 650, - height: 500, - color: "#d4f4f4" - }); - //outer wall - spawn.mapRect(2500, -3700, 1200, 3800); //right map wall - spawn.mapRect(-1400, -3800, 1100, 3900); //left map wall - // spawn.mapRect(2500, -2975, 1200, 2825); //right map middle wall above right portal - // spawn.mapRect(2700, -3600, 1000, 3650); - // far far right wall right of portals - // spawn.mapRect(2500, -1425, 200, 1275); // below right portal - spawn.mapRect(-1400, -4800, 5100, 1200); //map ceiling - spawn.mapRect(-1400, 0, 5100, 1200); //floor - - //lower entrance /exit - // spawn.mapRect(300, -550, 50, 350); //right entrance wall - // spawn.mapRect(-400, -550, 1825, 50); //ceiling - // spawn.mapRect(1075, -100, 575, 200); - // spawn.bodyRect(312, -200, 25, 200); - // spawn.bodyRect(1775, -75, 100, 100); - spawn.mapRect(300, -375, 50, 225); - spawn.bodyRect(312, -150, 25, 140); - spawn.mapRect(300, -10, 50, 50); - - //upper entrance / exit - spawn.mapRect(-400, -1050, 750, 50); - spawn.mapRect(300, -1050, 50, 300); - // spawn.bodyRect(312, -750, 25, 190); - spawn.mapRect(300, -560, 50, 50); - - // spawn.mapRect(1400, -1025, 50, 300); - // spawn.mapRect(1400, -1025, 50, 825); - // spawn.mapRect(600, -600, 275, 75); - // spawn.mapRect(1075, -1050, 550, 400); - // spawn.mapRect(1150, -1000, 150, 575); - // spawn.mapRect(1600, -550, 175, 200); - spawn.bodyRect(750, -725, 125, 125); - spawn.mapRect(1150, -1050, 250, 575); - - spawn.mapRect(1725, -550, 50, 200); //walls around portal 3 - // spawn.mapRect(1925, -550, 50, 200); - spawn.mapRect(1925, -550, 500, 200); - spawn.mapRect(1750, -390, 200, 40); - // spawn.mapRect(2350, -550, 75, 200); - - spawn.mapRect(-400, -550, 1800, 200); - spawn.mapRect(-200, -1700, 150, 25); //platform above exit room - spawn.mapRect(-200, -1325, 350, 25); - - //portal 3 angled - // spawn.mapRect(1425, -550, 350, 250); - // spawn.mapRect(1925, -550, 500, 200); - spawn.mapRect(2425, -450, 100, 100); - - - //portal 1 bottom - // spawn.mapRect(2525, -200, 175, 250); //right portal back wall - // spawn.mapRect(2500, -50, 200, 100); - spawn.mapRect(2290, -12, 375, 100); - spawn.mapRect(2350, -24, 375, 100); - spawn.mapRect(2410, -36, 375, 100); - - //portal 1 top - spawn.mapRect(2290, -3012, 375, 50); - spawn.mapRect(2350, -3024, 375, 50); - spawn.mapRect(2410, -3036, 375, 50); - - spawn.mapRect(1400, -3000, 1300, 50); //floor - // spawn.mapRect(2500, -3700, 200, 565); //right portal wall - // spawn.mapRect(2525, -3200, 175, 250); //right portal back wall - spawn.mapRect(1750, -3050, 250, 75); - // spawn.bodyRect(1950, -3100, 50, 50); - spawn.mapRect(1400, -3625, 50, 200); - spawn.mapRect(350, -3625, 50, 225); - spawn.mapRect(350, -3260, 50, 60); - // spawn.bodyRect(362, -3400, 25, 140); - - spawn.mapRect(200, -3250, 1240, 50); - spawn.mapRect(1400, -3260, 50, 310); - spawn.bodyRect(1412, -3425, 25, 165); - - // spawn.mapRect(-150, -3000, 150, 25); - // spawn.mapRect(-350, -2925, 175, 25); - spawn.mapRect(-150, -2925, 150, 25); - - //portal 2 - spawn.mapRect(-300, -2600, 300, 675); //left platform - spawn.mapRect(1400, -2600, 375, 675); //right platform - spawn.mapRect(1925, -2600, 775, 675); //far right platform - spawn.bodyRect(2130, -2660, 50, 50); //button's block - - spawn.mapRect(0, -1975, 175, 50); - spawn.mapRect(1225, -1975, 175, 50); - spawn.mapRect(150, -2100, 200, 175); - spawn.mapRect(1050, -2100, 200, 175); - - //mobs - spawn.randomMob(1075, -3500, -0.3); - // spawn.randomMob(-75, -3425, 0.2); - spawn.randomMob(1475, -225, -0.3); - spawn.randomMob(2075, -150, -0.2); - spawn.randomMob(2175, -700, -0.2); - spawn.randomMob(-75, -850, -0.1); - spawn.randomMob(1300, -600, -0.1); - spawn.randomMob(550, -3400, 0); - if (game.difficulty > 50) { - spawn.randomMob(2300, -2775, -0.5); - spawn.randomMob(600, -925, -0.5); - spawn.randomMob(1550, -2750, -0.5); - spawn.randomMob(1350, -1150, -0.5); - spawn.randomMob(-75, -1475, 0); - spawn.randomBoss(600, -2600, 0); - } - if (game.difficulty < 32) { - spawn.randomMob(700, -1650, 0); - spawn.randomMob(600, -3500, 0.2); - spawn.randomMob(-75, -1175, 0.2); - powerUps.spawnBossPowerUp(-125, -1760); - } else { - if (Math.random() < 0.5) { - spawn.randomLevelBoss(700, -1550, ["shooterBoss", "launcherBoss", "laserTargetingBoss"]); - } else { - spawn.randomLevelBoss(675, -2775, ["shooterBoss", "launcherBoss", "laserTargetingBoss"]); - } - } - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - sewers() { - const rotor = level.rotor(5100, 2475, -0.001) - const button = level.button(6600, 2675) - const hazard = level.hazard(4550, 2750, 4550, 150) - const balance1 = level.spinner(300, -395, 25, 390, 0.001) //entrance - const balance2 = level.spinner(2605, 500, 390, 25, 0.001) //falling - const balance3 = level.spinner(2608, 1900, 584, 25, 0.001) //falling - const balance4 = level.spinner(9300, 2205, 25, 380, 0.001) //exit - - level.custom = () => { - button.query(); - button.draw(); - hazard.query(); - hazard.level(button.isUp) - rotor.rotate(); - level.playerExitCheck(); - }; - level.customTopLayer = () => { - ctx.fillStyle = "#233" - ctx.beginPath(); - ctx.arc(balance1.pointA.x, balance1.pointA.y, 9, 0, 2 * Math.PI); - ctx.moveTo(balance2.pointA.x, balance2.pointA.y) - ctx.arc(balance2.pointA.x, balance2.pointA.y, 9, 0, 2 * Math.PI); - ctx.moveTo(balance3.pointA.x, balance3.pointA.y) - ctx.arc(balance3.pointA.x, balance3.pointA.y, 9, 0, 2 * Math.PI); - ctx.moveTo(balance4.pointA.x, balance4.pointA.y) - ctx.arc(balance4.pointA.x, balance4.pointA.y, 9, 0, 2 * Math.PI); - ctx.fill(); - - hazard.draw(); - }; - - level.setPosToSpawn(0, -50); //normal spawn - - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - level.exit.x = 9700; - level.exit.y = 2560; - level.defaultZoom = 1800 - game.zoomTransition(level.defaultZoom) - document.body.style.backgroundColor = "hsl(138, 3%, 74%)"; - powerUps.spawnStartingPowerUps(3475, 1775); - spawn.debris(4575, 2550, 1600, 9); //16 debris per level - spawn.debris(7000, 2550, 2000, 7); //16 debris per level - - // level.fill.push({ - // x: 9325, - // y: 2200, - // width: 575, - // height: 400, - // color: "rgba(0,255,255,0.1)" - // }); - level.fillBG.push({ - x: 9300, - y: 2200, - width: 600, - height: 400, - color: "hsl(175, 15%, 76%)" //c4f4f4 - }); - - spawn.mapRect(-500, -600, 200, 800); //left entrance wall - spawn.mapRect(-400, -600, 3550, 200); //ceiling - spawn.mapRect(-400, 0, 3000, 200); //floor - // spawn.mapRect(300, -500, 50, 400); //right entrance wall - // spawn.bodyRect(312, -100, 25, 100); - spawn.bodyRect(1450, -300, 150, 50); - - const xPos = shuffle([600, 1250, 2000]); - spawn.mapRect(xPos[0], -200, 400, 100); - spawn.mapRect(xPos[1], -250, 300, 300); - spawn.mapRect(xPos[2], -150, 300, 200); - - spawn.bodyRect(3100, 410, 75, 100); - spawn.bodyRect(2450, -25, 250, 25); - - spawn.mapRect(3050, -600, 200, 800); //right down tube wall - spawn.mapRect(3100, 0, 1200, 200); //tube right exit ceiling - spawn.mapRect(4200, 0, 200, 1900); - - - spawn.mapVertex(3500, 1000, "-500 -500 -400 -600 400 -600 500 -500 500 500 400 600 -400 600 -500 500"); - spawn.mapVertex(3600, 1940, "-400 -40 -350 -90 350 -90 400 -40 400 40 350 90 -350 90 -400 40"); - spawn.mapRect(3925, 2288, 310, 50); - spawn.mapRect(3980, 2276, 200, 50); - - spawn.mapRect(2625, 2288, 650, 50); - spawn.mapRect(2700, 2276, 500, 50); - // spawn.mapRect(3000, 400, 1000, 1250); - // spawn.mapRect(3000, 1925, 1000, 150); - // spawn.mapRect(3100, 1875, 800, 100); - // spawn.mapRect(3100, 1600, 800, 100); - // spawn.mapRect(3100, 350, 800, 100); - // spawn.mapRect(3100, 2025, 800, 100); - - spawn.mapRect(2400, 0, 200, 1925); //left down tube wall - spawn.mapRect(600, 2300, 3750, 200); - spawn.bodyRect(3800, 275, 125, 125); - - spawn.mapRect(4200, 1700, 5000, 200); - spawn.mapRect(4150, 2300, 200, 400); - - spawn.mapRect(600, 1700, 2000, 200); //bottom left room ceiling - spawn.mapRect(500, 1700, 200, 800); //left wall - spawn.mapRect(675, 1875, 325, 150, 0.5); - - - spawn.mapRect(4450, 2900, 4900, 200); //boss room floor - spawn.mapRect(4150, 2600, 400, 500); - spawn.mapRect(6250, 2675, 700, 325); - spawn.mapRect(8000, 2600, 600, 400); - spawn.bodyRect(5875, 2725, 200, 200); - spawn.bodyRect(6800, 2490, 50, 50); - spawn.bodyRect(6800, 2540, 50, 50); - spawn.bodyRect(6800, 2590, 50, 50); - spawn.bodyRect(8225, 2225, 100, 100); - spawn.mapRect(6250, 1875, 700, 150); - spawn.mapRect(8000, 1875, 600, 150); - - spawn.mapRect(9100, 1700, 900, 500); //exit - spawn.mapRect(9100, 2600, 900, 500); - spawn.mapRect(9900, 1700, 200, 1400); //back wall - // spawn.mapRect(9300, 2150, 50, 250); - spawn.mapRect(9300, 2590, 650, 25); - spawn.mapRect(9700, 2580, 100, 50); - - spawn.randomBoss(1300, 2100, 0.5); - spawn.randomMob(8300, 2100, 0.2); - spawn.randomSmallMob(2575, -75, 0.2); //entrance - spawn.randomMob(8125, 2450, 0.25); - spawn.randomSmallMob(3200, 250, 0.3); - spawn.randomMob(2425, 2150, 0.3); - spawn.randomSmallMob(3500, 250, 0.4); - spawn.randomMob(3800, 2175, 0.4); - spawn.randomSmallMob(1100, -300, 0.4); //entrance - spawn.randomMob(4450, 2500, 0.5); - spawn.randomMob(6350, 2525, 0.5); - spawn.randomBoss(9200, 2400, 0.6); - spawn.randomSmallMob(1900, -250, 0.6); //entrance - spawn.randomMob(1500, 2100, 0.7); - spawn.randomSmallMob(1700, -150, 0.7); //entrance - spawn.randomMob(8800, 2725, 0.8); - spawn.randomMob(7300, 2200, 0.8); - spawn.randomMob(2075, 2025, 0.8); - spawn.randomMob(3475, 2175, 0.8); - if (game.difficulty > 3) spawn.randomLevelBoss(6000, 2300, ["spiderBoss", "launcherBoss", "laserTargetingBoss"]); - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - bosses() { - level.bossKilled = true; //if there is no boss this needs to be true to increase levels - level.custom = () => { - level.playerExitCheck(); - }; - level.customTopLayer = () => {}; - - level.setPosToSpawn(0, -750); //normal spawn - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - level.exit.x = 6500; - level.exit.y = -230; - - level.defaultZoom = 1500 - game.zoomTransition(level.defaultZoom) - document.body.style.backgroundColor = "#ddd"; - - level.fill.push({ - x: 6400, - y: -550, - width: 300, - height: 350, - color: "rgba(0,255,255,0.1)" - }); - - spawn.mapRect(-950, 0, 8200, 800); //ground - spawn.mapRect(-950, -1200, 800, 1400); //left wall - spawn.mapRect(-950, -1800, 8200, 800); //roof - spawn.mapRect(-250, -700, 1000, 900); // shelf - spawn.mapRect(-250, -1200, 1000, 250); // shelf roof - powerUps.spawnStartingPowerUps(600, -800); - - spawn.blockDoor(710, -710); - - spawn[spawn.pickList[0]](1500, -200, 150 + Math.random() * 30); - spawn.mapRect(2500, -1200, 200, 750); //right wall - spawn.blockDoor(2585, -210) - spawn.mapRect(2500, -200, 200, 300); //right wall - - spawn.nodeBoss(3500, -200, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); - spawn.mapRect(4500, -1200, 200, 750); //right wall - spawn.blockDoor(4585, -210) - spawn.mapRect(4500, -200, 200, 300); //right wall - - spawn.lineBoss(5000, -200, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); - spawn.mapRect(6400, -1200, 400, 750); //right wall - spawn.mapRect(6400, -200, 400, 300); //right wall - spawn.mapRect(6700, -1800, 800, 2600); //right wall - spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump - - for (let i = 0; i < 3; ++i) { - if (game.difficulty * Math.random() > 15 * i) { - spawn.randomBoss(2000 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); - } - if (game.difficulty * Math.random() > 10 * i) { - spawn.randomBoss(3500 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); - } - if (game.difficulty * Math.random() > 7 * i) { - spawn.randomBoss(5000 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); - } - } - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - intro() { - level.bossKilled = true; //if there is no boss this needs to be true to increase levels - level.custom = () => { - level.playerExitCheck(); - }; - level.customTopLayer = () => {}; - const binary = (localSettings.runCount >>> 0).toString(2) - const height = 25 - const thick = 2 - const color = "#aaa" - const xOff = -130 //2622 - const yOff = -45 //-580 - let xLetter = 0 - for (let i = 0; i < binary.length; i++) { - if (binary[i] === "0") { - zero(xOff + xLetter, yOff) - } else { - one(xOff + xLetter, yOff) - } - } - - function one(x, y) { - level.fillBG.push({ - x: x, - y: y, - width: thick, - height: height, - color: color - }); - xLetter += 10 - } - - function zero(x, y) { - const width = 10 - level.fillBG.push({ - x: x, - y: y, - width: thick, - height: height, - color: color - }); - level.fillBG.push({ - x: x + width, - y: y, - width: thick, - height: height, - color: color - }); - level.fillBG.push({ - x: x, - y: y, - width: width, - height: thick, - color: color - }); - level.fillBG.push({ - x: x, - y: y + height - thick, - width: width, - height: thick, - color: color - }); - xLetter += 10 + width - } - level.setPosToSpawn(460, -100); //normal spawn - level.enter.x = -1000000; //hide enter graphic for first level by moving to the far left - level.exit.x = 2800; - level.exit.y = -335; - spawn.mapRect(level.exit.x, level.exit.y + 25, 100, 100); //exit bump - game.zoomScale = 1000 //1400 is normal - level.defaultZoom = 1600 - game.zoomTransition(level.defaultZoom, 1) - document.body.style.backgroundColor = "#ddd"; - level.fill.push({ - x: 2600, - y: -600, - width: 400, - height: 500, - color: "rgba(0,255,255,0.05)" - }); - level.fillBG.push({ - x: 2600, - y: -600, - width: 400, - height: 500, - color: "#fff" - }); - const lineColor = "#ccc" - level.fillBG.push({ - x: 1600, - y: -500, - width: 100, - height: 100, - color: lineColor - }); - level.fillBG.push({ - x: -55, - y: -283, - width: 12, - height: 100, - color: lineColor - }); - - //faster way to draw a wire - function wallWire(x, y, width, height, front = false) { - if (front) { level.fill.push({ - x: x, - y: y, - width: width, - height: height, - color: lineColor + x: 6400, + y: -550, + width: 300, + height: 350, + color: "rgba(0,255,255,0.1)" + }); + + // level.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + spawn.mapRect(-950, 0, 8200, 800); //ground + spawn.mapRect(-950, -1200, 800, 1400); //left wall + spawn.mapRect(-950, -1800, 8200, 800); //roof + spawn.mapRect(-250, -700, 1000, 900); // shelf + spawn.mapRect(-250, -1200, 1000, 250); // shelf roof + // powerUps.spawnStartingPowerUps(600, -800); + powerUps.spawn(550, -800, "reroll", false); + + function blockDoor(x, y, blockSize = 58) { + spawn.mapRect(x, y - 290, 40, 60); // door lip + spawn.mapRect(x, y, 40, 50); // door lip + for (let i = 0; i < 4; ++i) { + spawn.bodyRect(x + 5, y - 260 + i * blockSize, 30, blockSize); + } + } + blockDoor(710, -710); + spawn.mapRect(2500, -1200, 200, 750); //right wall + blockDoor(2585, -210) + spawn.mapRect(2500, -200, 200, 300); //right wall + spawn.mapRect(4500, -1200, 200, 650); //right wall + blockDoor(4585, -310) + spawn.mapRect(4500, -300, 200, 400); //right wall + spawn.mapRect(6400, -1200, 400, 750); //right wall + spawn.mapRect(6400, -200, 400, 300); //right wall + spawn.mapRect(6700, -1800, 800, 2600); //right wall + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump + // spawn.boost(1500, 0, 900); + + // spawn.starter(1600, -500) + // spawn.bomberBoss(2900, -500) + // spawn.launcherBoss(1200, -500) + // spawn.laserTargetingBoss(1600, -400) + // spawn.spawner(1600, -500) + // spawn.sniper(1700, -120, 50) + // spawn.bomberBoss(1400, -500) + spawn.launcher(1800, -120) + // spawn.cellBossCulture(1600, -500) + // spawn.powerUpBoss(1600, -500) + // spawn.sniper(1200, -500) + // spawn.shield(mob[mob.length - 1], 1200, -500, 1); + + // spawn.nodeBoss(1200, -500, "launcher") + // spawn.snakeBoss(1200, -500) + // spawn.timeSkipBoss(2900, -500) + // spawn.randomMob(1600, -500) + }, + template() { + // level.bossKilled = false; // if a boss needs to be killed + level.custom = () => { + level.playerExitCheck(); + }; + level.customTopLayer = () => {}; + level.setPosToSpawn(0, -50); //normal spawn + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + level.exit.x = 1500; + level.exit.y = -1875; + level.defaultZoom = 1800 + game.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#dcdcde"; + // powerUps.spawnStartingPowerUps(1475, -1175); + // spawn.debris(750, -2200, 3700, 16); //16 debris per level + // level.fill.push({ //foreground + // x: 2500, + // y: -1100, + // width: 450, + // height: 250, + // color: "rgba(0,0,0,0.1)" + // }); + // level.fillBG.push({ //background + // x: 1300, + // y: -1800, + // width: 750, + // height: 1800, + // color: "#d4d4d7" + // }); + + spawn.mapRect(-100, 0, 1000, 100); + // spawn.bodyRect(1540, -1110, 300, 25, 0.9); + // spawn.boost(4150, 0, 1300); + // spawn.randomSmallMob(1300, -70); + // spawn.randomMob(2650, -975, 0.8); + // spawn.randomBoss(1700, -900, 0.4); + // if (game.difficulty > 3) spawn.randomLevelBoss(2200, -1300); + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + finalBoss() { + level.bossKilled = false; // if a boss needs to be killed + level.custom = () => { + level.playerExitCheck(); + }; + level.customTopLayer = () => {}; + + level.setPosToSpawn(0, -250); //normal spawn + spawn.mapRect(5500, -330 + 20, 100, 20); //spawn this because the real exit is in the wrong spot + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + level.exit.x = 550000; + level.exit.y = -330; + + level.defaultZoom = 2500 + game.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#ccc"; + + level.fill.push({ + x: 6400, + y: -550, + width: 300, + height: 350, + color: "rgba(0,255,255,0.1)" + }); + + spawn.mapRect(-950, 0, 7200, 800); //ground + spawn.mapRect(-950, -1500, 800, 1900); //left wall + spawn.mapRect(-950, -2300, 7200, 800); //roof + spawn.mapRect(-250, -200, 1000, 300); // shelf + spawn.mapRect(-250, -1700, 1000, 1250); // shelf roof + spawn.blockDoor(710, -210); + + spawn.finalBoss(3000, -750) + + spawn.mapRect(5400, -1700, 400, 1150); //right wall + spawn.mapRect(5400, -300, 400, 400); //right wall + spawn.mapRect(5700, -2300, 800, 3100); //right wall + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump + + }, + gauntlet() { + level.bossKilled = true; //if there is no boss this needs to be true to increase levels + level.custom = () => { + level.playerExitCheck(); + }; + level.customTopLayer = () => {}; + + level.setPosToSpawn(0, -750); //normal spawn + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + level.exit.x = 6500; + level.exit.y = -230; + + level.defaultZoom = 1500 + game.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#ddd"; + + level.fill.push({ + x: 6400, + y: -550, + width: 300, + height: 350, + color: "rgba(0,255,255,0.1)" + }); + + spawn.mapRect(-950, 0, 8200, 800); //ground + spawn.mapRect(-950, -1200, 800, 1400); //left wall + spawn.mapRect(-950, -1800, 8200, 800); //roof + spawn.mapRect(-250, -700, 1000, 900); // shelf + spawn.mapRect(-250, -1200, 1000, 250); // shelf roof + powerUps.spawnStartingPowerUps(600, -800); + + spawn.blockDoor(710, -710); + + spawn[spawn.pickList[0]](1500, -200, 150 + Math.random() * 30); + spawn.mapRect(2500, -1200, 200, 750); //right wall + spawn.blockDoor(2585, -210) + spawn.mapRect(2500, -200, 200, 300); //right wall + + spawn.nodeBoss(3500, -200, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); + spawn.mapRect(4500, -1200, 200, 750); //right wall + spawn.blockDoor(4585, -210) + spawn.mapRect(4500, -200, 200, 300); //right wall + + spawn.lineBoss(5000, -200, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); + spawn.mapRect(6400, -1200, 400, 750); //right wall + spawn.mapRect(6400, -200, 400, 300); //right wall + spawn.mapRect(6700, -1800, 800, 2600); //right wall + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump + + for (let i = 0; i < 3; ++i) { + if (game.difficulty * Math.random() > 15 * i) { + spawn.randomBoss(2000 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); + } + if (game.difficulty * Math.random() > 10 * i) { + spawn.randomBoss(3500 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); + } + if (game.difficulty * Math.random() > 7 * i) { + spawn.randomBoss(5000 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); + } + } + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + intro() { + level.bossKilled = true; //if there is no boss this needs to be true to increase levels + level.custom = () => { + level.playerExitCheck(); + }; + level.customTopLayer = () => {}; + const binary = (localSettings.runCount >>> 0).toString(2) + const height = 25 + const thick = 2 + const color = "#aaa" + const xOff = -130 //2622 + const yOff = -45 //-580 + let xLetter = 0 + for (let i = 0; i < binary.length; i++) { + if (binary[i] === "0") { + zero(xOff + xLetter, yOff) + } else { + one(xOff + xLetter, yOff) + } + } + + function one(x, y) { + level.fillBG.push({ + x: x, + y: y, + width: thick, + height: height, + color: color + }); + xLetter += 10 + } + + function zero(x, y) { + const width = 10 + level.fillBG.push({ + x: x, + y: y, + width: thick, + height: height, + color: color + }); + level.fillBG.push({ + x: x + width, + y: y, + width: thick, + height: height, + color: color + }); + level.fillBG.push({ + x: x, + y: y, + width: width, + height: thick, + color: color + }); + level.fillBG.push({ + x: x, + y: y + height - thick, + width: width, + height: thick, + color: color + }); + xLetter += 10 + width + } + level.setPosToSpawn(460, -100); //normal spawn + level.enter.x = -1000000; //hide enter graphic for first level by moving to the far left + level.exit.x = 2800; + level.exit.y = -335; + spawn.mapRect(level.exit.x, level.exit.y + 25, 100, 100); //exit bump + game.zoomScale = 1000 //1400 is normal + level.defaultZoom = 1600 + game.zoomTransition(level.defaultZoom, 1) + document.body.style.backgroundColor = "#ddd"; + level.fill.push({ + x: 2600, + y: -600, + width: 400, + height: 500, + color: "rgba(0,255,255,0.05)" }); - } else { level.fillBG.push({ - x: x, - y: y, - width: width, - height: height, - color: lineColor + x: 2600, + y: -600, + width: 400, + height: 500, + color: "#fff" + }); + const lineColor = "#ccc" + level.fillBG.push({ + x: 1600, + y: -500, + width: 100, + height: 100, + color: lineColor + }); + level.fillBG.push({ + x: -55, + y: -283, + width: 12, + height: 100, + color: lineColor }); - } - } - for (let i = 0; i < 3; i++) { - wallWire(100 - 10 * i, -1050 - 10 * i, 5, 800); - wallWire(100 - 10 * i, -255 - 10 * i, -300, 5); - } - for (let i = 0; i < 5; i++) { - wallWire(1000 + 10 * i, -1050 - 10 * i, 5, 600); - wallWire(1000 + 10 * i, -450 - 10 * i, 150, 5); - wallWire(1150 + 10 * i, -450 - 10 * i, 5, 500); - } - for (let i = 0; i < 3; i++) { - wallWire(2650 - 10 * i, -700 - 10 * i, -300, 5); - wallWire(2350 - 10 * i, -700 - 10 * i, 5, 800); - } - for (let i = 0; i < 5; i++) { - wallWire(1625 + 10 * i, -1050, 5, 1200); - } - for (let i = 0; i < 4; i++) { - wallWire(1650, -470 + i * 10, 670 - i * 10, 5); - wallWire(1650 + 670 - i * 10, -470 + i * 10, 5, 600); - } - for (let i = 0; i < 3; i++) { - wallWire(-200 - i * 10, -245 + i * 10, 1340, 5); - wallWire(1140 - i * 10, -245 + i * 10, 5, 300); - wallWire(-200 - i * 10, -215 + i * 10, 660, 5); - wallWire(460 - i * 10, -215 + i * 10, 5, 300); - } - spawn.mapRect(-250, 0, 3600, 1800); //ground - spawn.mapRect(-2750, -2800, 2600, 4600); //left wall - spawn.mapRect(3000, -2800, 2600, 4600); //right wall - spawn.mapRect(-250, -2800, 3600, 1800); //roof - spawn.mapRect(2600, -300, 500, 500); //exit shelf - spawn.mapRect(2600, -1200, 500, 600); //exit roof - spawn.mapRect(-95, -1100, 80, 110); //wire source - spawn.mapRect(410, -10, 90, 20); //small platform for player - spawn.bodyRect(2425, -120, 70, 50); - spawn.bodyRect(2400, -100, 100, 60); - spawn.bodyRect(2500, -150, 100, 150); //exit step - - mech.health = 0.25; - mech.displayHealth(); - // powerUps.spawn(-100, 0, "heal", false); //starting gun - powerUps.spawn(1900, -150, "heal", false); //starting gun - powerUps.spawn(2050, -150, "heal", false); //starting gun - // powerUps.spawn(2050, -150, "field", false); //starting gun - // localSettings.levelsClearedLastGame = 20 - if (localSettings.levelsClearedLastGame < 6) { - spawn.wireFoot(); - spawn.wireFootLeft(); - spawn.wireKnee(); - spawn.wireKneeLeft(); - spawn.wireHead(); - } else { - const say = [] - if (localSettings.runCount > 200) { //experienced - say.push( - "I've been here before...", - "How many times have I done this?", - ) - } else if (localSettings.runCount < 20) { //new - say.push( - "Am I still alive?", - "And I'm back here again...", - "Is this another simulation?", - "I'm alive...", - "Last time was a simulation. Is this one a simulation too?", - ) - } - if (game.difficultyMode < 4 && localSettings.levelsClearedLastGame > 10) { //too easy - say.push( - "That felt too easy.
Maybe I should increase the difficulty of the simulation.", - "That was fun, but maybe I should increase the difficulty of the simulation.", - "I should increase the difficulty of the simulation, that didn't feel realistic.", - ) - } else if (game.difficultyMode > 3 && localSettings.levelsClearedLastGame > 10) { //great run on a hard or why - say.push( - "What do I do after I escape?", - "I'm almost ready to stop these simulations and actually escape.", - "I think I'm getting closer to something, but what?", - "I'm getting stronger.", - "What happens after I escape?", - "I found a good combination of technology last time." - ) - } else { //resolve - say.push( - "I'll try some different mods this time.", - "I've got to escape.", - "I'll find a way out.", - "I keep forgetting that these are just simulated escapes." - ) - } - game.makeTextLog(say[Math.floor(say.length * Math.random())], 1000) - - const swapPeriod = 150 - const len = 30 - for (let i = 0; i < len; i++) { - setTimeout(function () { - game.wipe = function () { //set wipe to have trails - ctx.fillStyle = `rgba(221,221,221,${i*i*0.0005 +0.0025})`; - ctx.fillRect(0, 0, canvas.width, canvas.height); - } - }, (i) * swapPeriod); - } - - setTimeout(function () { - game.wipe = function () { //set wipe to normal - ctx.clearRect(0, 0, canvas.width, canvas.height); + //faster way to draw a wire + function wallWire(x, y, width, height, front = false) { + if (front) { + level.fill.push({ + x: x, + y: y, + width: width, + height: height, + color: lineColor + }); + } else { + level.fillBG.push({ + x: x, + y: y, + width: width, + height: height, + color: lineColor + }); + } } - }, len * swapPeriod); - } - powerUps.spawnStartingPowerUps(2300, -150); - }, - satellite() { - const elevator = level.platform(4210, -1325, 380, 30, -10) - level.custom = () => { - level.playerExitCheck(); - }; - level.customTopLayer = () => { - if (elevator.pauseUntilCycle < game.cycle && !mech.isBodiesAsleep) { //elevator move - if (elevator.pointA.y > -1275) { //bottom - elevator.plat.speed = -10 - elevator.pauseUntilCycle = game.cycle + 90 - } else if (elevator.pointA.y < -3455) { //top - elevator.plat.speed = 30 - elevator.pauseUntilCycle = game.cycle + 90 + for (let i = 0; i < 3; i++) { + wallWire(100 - 10 * i, -1050 - 10 * i, 5, 800); + wallWire(100 - 10 * i, -255 - 10 * i, -300, 5); } - elevator.pointA = { - x: elevator.pointA.x, - y: elevator.pointA.y + elevator.plat.speed + for (let i = 0; i < 5; i++) { + wallWire(1000 + 10 * i, -1050 - 10 * i, 5, 600); + wallWire(1000 + 10 * i, -450 - 10 * i, 150, 5); + wallWire(1150 + 10 * i, -450 - 10 * i, 5, 500); } - } - }; - - level.setPosToSpawn(-50, -50); //normal spawn - level.exit.x = -100; - level.exit.y = -425; - spawn.mapRect(level.exit.x, level.exit.y + 15, 100, 50); //exit bump - - level.defaultZoom = 1700 // 4500 // 1400 - game.zoomTransition(level.defaultZoom) - - powerUps.spawnStartingPowerUps(4900, -500); - spawn.debris(1000, 20, 1800, 3); //16 debris per level //but less here because a few mobs die from laser - spawn.debris(4830, -1330, 850, 3); //16 debris per level - spawn.debris(3035, -3900, 1500, 3); //16 debris per level - - document.body.style.backgroundColor = "#dbdcde"; - - //spawn start building - spawn.mapRect(-300, -800, 50, 800); - spawn.mapRect(-100, -20, 100, 30); - // spawn.mapRect(-300, -10, 500, 50); - spawn.mapRect(150, -510, 50, 365); - spawn.bodyRect(170, -130, 14, 145, 1, spawn.propsFriction); //door to starting room - // spawn.mapRect(-300, 0, 1000, 300); //ground - spawn.mapVertex(-18, 145, "625 0 0 0 0 -300 500 -300"); //entrance ramp - spawn.mapRect(-300, 250, 6300, 300); //deeper ground - spawn.bodyRect(2100, 50, 80, 80); - spawn.bodyRect(2000, 50, 60, 60); - // spawn.bodyRect(1650, 50, 300, 200); - // spawn.mapRect(1800, Math.floor(Math.random() * 200), 850, 300); //stops above body from moving to right - spawn.mapVertex(2225, 250, "575 0 -575 0 -450 -100 450 -100"); //base - - //exit building - // spawn.mapRect(-100, -410, 100, 30); - spawn.mapRect(-300, -800, 500, 50); - spawn.mapRect(150, -800, 50, 110); - spawn.bodyRect(170, -690, 14, 180, 1, spawn.propsFriction); //door to exit room - spawn.mapRect(-300, -400, 500, 100); //far left starting ceiling - level.fill.push({ - x: -250, - y: -400, - width: 1800, - height: 775, - color: "rgba(0,20,40,0.2)" - }); - level.fill.push({ - x: 1800, - y: -475, - width: 850, - height: 775, - color: "rgba(0,20,40,0.2)" - }); - level.fillBG.push({ - x: -250, - y: -750, - width: 420, - height: 450, - color: "#d4f4f4" - }); - - //tall platform above exit - spawn.mapRect(-500, -1900, 400, 50); //super high shade - spawn.mapRect(0, -1900, 400, 50); //super high shade - spawn.mapRect(-150, -1350, 200, 25); //super high shade - spawn.bodyRect(140, -2100, 150, 200); //shield from laser - - level.fillBG.push({ - x: -300, - y: -1900, - width: 500, - height: 1100, - color: "#d0d4d6" - }); - //tall platform - spawn.mapVertex(1125, -450, "325 0 250 80 -250 80 -325 0 -250 -80 250 -80"); //base - spawn.mapRect(150, -500, 1400, 100); //far left starting ceiling - spawn.mapRect(625, -2450, 1000, 50); //super high shade - spawn.bodyRect(1300, -3600, 150, 150); //shield from laser - level.fillBG.push({ - x: 900, - y: -2450, - width: 450, - height: 2050, - color: "#d0d4d6" - }); - //tall platform - spawn.mapVertex(2225, -450, "325 0 250 80 -250 80 -325 0 -250 -80 250 -80"); //base - spawn.mapRect(1725, -2800, 1000, 50); //super high shade - spawn.mapRect(1800, -500, 850, 100); //far left starting ceiling - spawn.bodyRect(2400, -2950, 150, 150); //shield from laser - level.fillBG.push({ - x: 2000, - y: -2800, - width: 450, - height: 2300, - color: "#d0d4d6" - }); - //tall platform - spawn.mapVertex(3350, 200, "375 0 -375 0 -250 -250 250 -250"); //base - spawn.bodyRect(3400, -150, 150, 150); - spawn.mapRect(2850, -3150, 1000, 50); //super high shade - spawn.bodyRect(3675, -3470, 525, 20); //plank - spawn.bodyRect(3600, -3450, 200, 300); //plank support block - level.fillBG.push({ - x: 3125, - y: -3100, - width: 450, - height: 3300, - color: "#d0d4d6" - }); - - //far right structure - spawn.mapRect(5200, -725, 100, 870); - spawn.mapRect(5300, -1075, 350, 1220); - spawn.boost(5825, 235, 1400); - level.fill.push({ - x: 5200, - y: 125, - width: 450, - height: 200, - color: "rgba(0,20,40,0.25)" - }); - - //structure bellow tall stairs - level.fill.push({ - x: 4000, - y: -1200, - width: 1050, - height: 1500, - color: "rgba(0,20,40,0.13)" - }); - spawn.mapRect(3925, -300, 425, 50); - spawn.mapRect(4700, -375, 425, 50); - // spawn.mapRect(4000, -1300, 1050, 100); - spawn.mapRect(4000, -1300, 200, 100); - spawn.mapRect(4600, -1300, 450, 100); - - //steep stairs - // spawn.mapRect(4100, -1700, 100, 100); - // spawn.mapRect(4200, -2050, 100, 450); - // spawn.mapRect(4300, -2400, 100, 800); - // spawn.mapRect(4400, -2750, 100, 1150); - // spawn.mapRect(4500, -3100, 100, 1500); - spawn.mapRect(4100, -2250, 100, 650); - spawn.mapRect(4100, -3450, 100, 650); //left top shelf - spawn.mapRect(4600, -3450, 100, 1850); - // spawn.mapRect(4200, -3450, 100, 400); //left top shelf - // spawn.mapRect(4300, -3450, 100, 100); //left top shelf - level.fill.push({ - x: 4100, - y: -3450, - width: 600, - height: 2250, - color: "rgba(0,20,40,0.13)" - }); - // level.fill.push({ - // x: 4100, - // y: -1600, - // width: 600, - // height: 300, - // color: "rgba(0,20,40,0.13)" - // }); - - spawn.randomSmallMob(4400, -3500); - spawn.randomSmallMob(4800, -800); - spawn.randomSmallMob(800, 150); - spawn.randomMob(700, -600, 0.8); - spawn.randomMob(3100, -3600, 0.7); - spawn.randomMob(3300, -1000, 0.7); - spawn.randomMob(4200, -250, 0.7); - spawn.randomMob(4900, -1500, 0.6); - spawn.randomMob(1200, 100, 0.4); - spawn.randomMob(5900, -1500, 0.4); - spawn.randomMob(4700, -800, 0.4); - spawn.randomMob(1400, -400, 0.3); - spawn.randomMob(1200, 100, 0.3); - spawn.randomMob(2550, -100, 0.2); - spawn.randomMob(2000, -2800, 0.2); - spawn.randomMob(2000, -500, 0.2); - spawn.randomMob(4475, -3550, 0.1); - spawn.randomBoss(5000, -2150, 1); - spawn.randomBoss(3700, -4100, 0.3); - spawn.randomBoss(2700, -1600, 0.1); - spawn.randomBoss(1600, -100, 0); - spawn.randomBoss(5000, -3900, -0.3); - if (game.difficulty > 3) { - if (Math.random() < 0.1) { - spawn.randomLevelBoss(2800, -1400); - } else if (Math.random() < 0.25) { - spawn.laserBoss(2900 + 300 * Math.random(), -2950 + 150 * Math.random()); - } else if (Math.random() < 0.33) { - spawn.laserBoss(1800 + 250 * Math.random(), -2600 + 150 * Math.random()); - } else if (Math.random() < 0.5) { - spawn.laserBoss(3500 + 250 * Math.random(), -2600 + 1000 * Math.random()); - } else { - spawn.laserBoss(600 + 200 * Math.random(), -2150 + 250 * Math.random()); - } - } - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - rooftops() { - const elevator = level.platform(1450, -1000, 235, 30, -2) - level.custom = () => { - ctx.fillStyle = "#ccc" - ctx.fillRect(1567, -1990, 5, 1020) - level.playerExitCheck(); - }; - level.customTopLayer = () => { - - if (elevator.pauseUntilCycle < game.cycle && !mech.isBodiesAsleep) { //elevator move - if (elevator.pointA.y > -980) { //bottom - elevator.plat.speed = -2 - elevator.pauseUntilCycle = game.cycle + 60 - } else if (elevator.pointA.y < -1980) { //top - elevator.plat.speed = 1 - elevator.pauseUntilCycle = game.cycle + 60 + for (let i = 0; i < 3; i++) { + wallWire(2650 - 10 * i, -700 - 10 * i, -300, 5); + wallWire(2350 - 10 * i, -700 - 10 * i, 5, 800); } - elevator.pointA = { - x: elevator.pointA.x, - y: elevator.pointA.y + elevator.plat.speed + for (let i = 0; i < 5; i++) { + wallWire(1625 + 10 * i, -1050, 5, 1200); } - } - }; + for (let i = 0; i < 4; i++) { + wallWire(1650, -470 + i * 10, 670 - i * 10, 5); + wallWire(1650 + 670 - i * 10, -470 + i * 10, 5, 600); + } + for (let i = 0; i < 3; i++) { + wallWire(-200 - i * 10, -245 + i * 10, 1340, 5); + wallWire(1140 - i * 10, -245 + i * 10, 5, 300); + wallWire(-200 - i * 10, -215 + i * 10, 660, 5); + wallWire(460 - i * 10, -215 + i * 10, 5, 300); + } + spawn.mapRect(-250, 0, 3600, 1800); //ground + spawn.mapRect(-2750, -2800, 2600, 4600); //left wall + spawn.mapRect(3000, -2800, 2600, 4600); //right wall + spawn.mapRect(-250, -2800, 3600, 1800); //roof + spawn.mapRect(2600, -300, 500, 500); //exit shelf + spawn.mapRect(2600, -1200, 500, 600); //exit roof + spawn.mapRect(-95, -1100, 80, 110); //wire source + spawn.mapRect(410, -10, 90, 20); //small platform for player - level.defaultZoom = 1700 - game.zoomTransition(level.defaultZoom) - document.body.style.backgroundColor = "#dcdcde"; + spawn.bodyRect(2425, -120, 70, 50); + spawn.bodyRect(2400, -100, 100, 60); + spawn.bodyRect(2500, -150, 100, 150); //exit step + + // localSettings.levelsClearedLastGame = 20 + if (level.levelsCleared === 0) { + // powerUps.spawn(-100, 0, "heal", false); //starting gun + powerUps.spawn(1900, -150, "heal", false); //starting gun + powerUps.spawn(2050, -150, "heal", false); //starting gun + // powerUps.spawn(2050, -150, "field", false); //starting gun + if (localSettings.levelsClearedLastGame < 6) { + spawn.wireFoot(); + spawn.wireFootLeft(); + spawn.wireKnee(); + spawn.wireKneeLeft(); + spawn.wireHead(); + } else { + const say = [] + if (localSettings.runCount > 200) { //experienced + say.push( + "I've been here before...", + "How many times have I done this?", + ) + } else if (localSettings.runCount < 20) { //new + say.push( + "Am I still alive?", + "And I'm back here again...", + "Is this another simulation?", + "I'm alive...", + "Last time was a simulation. Is this one a simulation too?", + ) + } + if (game.difficultyMode < 4 && localSettings.levelsClearedLastGame > 10) { //too easy + say.push( + "That felt too easy.
Maybe I should increase the difficulty of the simulation.", + "That was fun, but maybe I should increase the difficulty of the simulation.", + "I should increase the difficulty of the simulation, that didn't feel realistic.", + ) + } else if (game.difficultyMode > 3 && localSettings.levelsClearedLastGame > 10) { //great run on a hard or why + say.push( + "What do I do after I escape?", + "I'm almost ready to stop these simulations and actually escape.", + "I think I'm getting closer to something, but what?", + "I'm getting stronger.", + "What happens after I escape?", + "I found a good combination of technology last time." + ) + } else { //resolve + say.push( + "I'll try some different mods this time.", + "I've got to escape.", + "I'll find a way out.", + "I keep forgetting that these are just simulated escapes." + ) + } + game.makeTextLog(say[Math.floor(say.length * Math.random())], 1000) + + const swapPeriod = 150 + const len = 30 + for (let i = 0; i < len; i++) { + setTimeout(function() { + game.wipe = function() { //set wipe to have trails + ctx.fillStyle = `rgba(221,221,221,${i*i*0.0005 +0.0025})`; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + }, (i) * swapPeriod); + } + + setTimeout(function() { + game.wipe = function() { //set wipe to normal + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + }, len * swapPeriod); + } + } + powerUps.spawnStartingPowerUps(2300, -150); + }, + testChamber() { + level.setPosToSpawn(0, -50); //lower start + level.exit.y = level.enter.y - 550; + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + level.exit.x = level.enter.x; + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); + level.defaultZoom = 2200 + game.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#d5d5d5"; + + const portal = level.portal({ + x: 2475, + y: -140 + }, Math.PI, { //left + x: 2475, + y: -3140 + }, Math.PI) //left + const portal2 = level.portal({ + x: 75, + y: -2150 + }, -Math.PI / 2, { //up + x: 1325, + y: -2150 + }, -Math.PI / 2) //up + const portal3 = level.portal({ + x: 1850, + y: -585 + }, -Math.PI / 2, { //up + x: 2425, + y: -600 + }, -2 * Math.PI / 3) //up left + + const hazard = level.hazard(350, -2025, 700, 10, 0.4, "hsl(0, 100%, 50%)", true) //laser + const hazard2 = level.hazard(1775, -2550, 150, 10, 0.4, "hsl(0, 100%, 50%)", true) //laser + const button = level.button(2100, -2600) - if (Math.random() < 0.75) { - //normal direction start in top left - level.setPosToSpawn(-450, -2060); - level.exit.x = 3600; - level.exit.y = -300; - spawn.mapRect(3600, -285, 100, 50); //ground bump wall - //mobs that spawn in exit room - spawn.bodyRect(4850, -750, 300, 25, 0.6); // - spawn.randomSmallMob(4100, -100); - spawn.randomSmallMob(4600, -100); - spawn.randomMob(3765, -450, 0.3); - level.fill.push({ - x: -650, - y: -2300, - width: 440, - height: 300, - color: "rgba(0,0,0,0.15)" - }); - level.fillBG.push({ - x: 3460, - y: -700, - width: 1090, - height: 800, - color: "#d4f4f4" - }); - } else { - //reverse direction, start in bottom right - level.setPosToSpawn(3650, -325); - level.exit.x = -550; - level.exit.y = -2030; - spawn.mapRect(-550, -2015, 100, 50); //ground bump wall - spawn.boost(4950, 0, 1100); - level.fillBG.push({ - x: -650, - y: -2300, - width: 440, - height: 300, - color: "#d4f4f4" - }); - level.fill.push({ - x: 3460, - y: -700, - width: 1090, - height: 800, - color: "rgba(0,0,0,0.1)" - }); - } - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + const buttonDoor = level.button(600, -550) + // spawn.mapRect(600, -600, 275, 75); + const door = level.door(312, -750, 25, 190, 185) - spawn.debris(1650, -1800, 3800, 16); //16 debris per level - powerUps.spawnStartingPowerUps(2450, -1675); + level.custom = () => { + buttonDoor.query(); + buttonDoor.draw(); + if (buttonDoor.isUp) { + door.isOpen = true + } else { + door.isOpen = false + } + door.openClose(); - //foreground + portal[2].query() + portal[3].query() + portal2[2].query() + portal2[3].query() + portal3[2].query() + portal3[3].query() + hazard.query(); + hazard2.query(); + if (button.isUp) { + hazard.isOn = false; + hazard2.isOn = false; + } else { + hazard.isOn = true; + hazard2.isOn = true; + } + button.query(); + button.draw(); + level.playerExitCheck(); + }; + level.customTopLayer = () => { + door.draw(); + hazard.draw(); + hazard2.draw(); + portal[0].draw(); + portal[1].draw(); + portal[2].draw(); + portal[3].draw(); + portal2[0].draw(); + portal2[1].draw(); + portal2[2].draw(); + portal2[3].draw(); + portal3[0].draw(); + portal3[1].draw(); + portal3[2].draw(); + portal3[3].draw(); + }; + powerUps.spawnStartingPowerUps(1875, -3075); - level.fill.push({ - x: 3460, - y: -1250, - width: 1080, - height: 550, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 4550, - y: -725, - width: 900, - height: 725, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 3400, - y: 100, - width: 2150, - height: 900, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: -700, - y: -1950, - width: 2100, - height: 2950, - color: "rgba(0,0,0,0.1)" - }); + const powerUpPos = shuffle([{ //no debris on this level but 2 random spawn instead + x: -150, + y: -1775 + }, { + x: 2400, + y: -2650 + }, { + x: -175, + y: -1375 + }, { + x: 1325, + y: -150 + }]); + powerUps.chooseRandomPowerUp(powerUpPos[0].x, powerUpPos[0].y); + powerUps.chooseRandomPowerUp(powerUpPos[1].x, powerUpPos[1].y); + level.fillBG.push({ //exit room + x: -300, + y: -1000, + width: 650, + height: 500, + color: "#d4f4f4" + }); + //outer wall + spawn.mapRect(2500, -3700, 1200, 3800); //right map wall + spawn.mapRect(-1400, -3800, 1100, 3900); //left map wall + // spawn.mapRect(2500, -2975, 1200, 2825); //right map middle wall above right portal + // spawn.mapRect(2700, -3600, 1000, 3650); + // far far right wall right of portals + // spawn.mapRect(2500, -1425, 200, 1275); // below right portal + spawn.mapRect(-1400, -4800, 5100, 1200); //map ceiling + spawn.mapRect(-1400, 0, 5100, 1200); //floor - level.fill.push({ - x: 1860, - y: -1950, - width: 630, - height: 350, - color: "rgba(0,0,0,0.1)" - }); + //lower entrance /exit + // spawn.mapRect(300, -550, 50, 350); //right entrance wall + // spawn.mapRect(-400, -550, 1825, 50); //ceiling + // spawn.mapRect(1075, -100, 575, 200); + // spawn.bodyRect(312, -200, 25, 200); + // spawn.bodyRect(1775, -75, 100, 100); + spawn.mapRect(300, -375, 50, 225); + spawn.bodyRect(312, -150, 25, 140); + spawn.mapRect(300, -10, 50, 50); - level.fill.push({ - x: 1735, - y: -1550, - width: 1405, - height: 550, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 1735, - y: -900, - width: 1515, - height: 1900, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 3510, - y: -1550, - width: 330, - height: 300, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 710, - y: -2225, - width: 580, - height: 225, - color: "rgba(0,0,0,0.1)" - }); + //upper entrance / exit + spawn.mapRect(-400, -1050, 750, 50); + spawn.mapRect(300, -1050, 50, 300); + // spawn.bodyRect(312, -750, 25, 190); + spawn.mapRect(300, -560, 50, 50); - //spawn.mapRect(-700, 0, 6250, 100); //ground - spawn.mapRect(3400, 0, 2150, 100); //ground - spawn.mapRect(-700, -2000, 2125, 50); //Top left ledge - spawn.bodyRect(1300, -2125, 50, 125, 0.8); - spawn.bodyRect(1307, -2225, 50, 100, 0.8); - spawn.mapRect(-700, -2350, 50, 400); //far left starting left wall - spawn.mapRect(-700, -2010, 500, 50); //far left starting ground - spawn.mapRect(-700, -2350, 500, 50); //far left starting ceiling - spawn.mapRect(-250, -2350, 50, 200); //far left starting right part of wall - spawn.bodyRect(-240, -2150, 30, 36); //door to starting room - spawn.bodyRect(-240, -2115, 30, 36); //door to starting room - spawn.bodyRect(-240, -2080, 30, 35); //door to starting room - spawn.bodyRect(-240, -2045, 30, 35); //door to starting room - spawn.mapRect(1850, -2000, 650, 50); - spawn.bodyRect(200, -2150, 80, 220, 0.8); - spawn.mapRect(700, -2275, 600, 50); - spawn.mapRect(1000, -1350, 410, 50); - spawn.bodyRect(1050, -2350, 30, 30, 0.8); - // spawn.boost(1800, -1000, 1200); - // spawn.bodyRect(1625, -1100, 100, 75); - // spawn.bodyRect(1350, -1025, 400, 25); // ground plank - spawn.mapRect(-725, -1000, 2150, 100); //lower left ledge - spawn.bodyRect(350, -1100, 200, 100, 0.8); - spawn.bodyRect(370, -1200, 100, 100, 0.8); - spawn.bodyRect(360, -1300, 100, 100, 0.8); - spawn.bodyRect(950, -1050, 300, 50, 0.8); - spawn.bodyRect(-575, -1150, 125, 150, 0.8); - spawn.mapRect(1710, -1000, 1565, 100); //middle ledge - spawn.mapRect(3400, -1000, 75, 25); - spawn.bodyRect(2600, -1950, 100, 250, 0.8); - spawn.bodyRect(2700, -1125, 125, 125, 0.8); - spawn.bodyRect(2710, -1250, 125, 125, 0.8); - spawn.bodyRect(2705, -1350, 75, 100, 0.8); - spawn.mapRect(3500, -1600, 350, 50); - spawn.mapRect(1725, -1600, 1435, 50); - spawn.bodyRect(3100, -1015, 375, 15); - spawn.bodyRect(3500, -850, 75, 125, 0.8); - spawn.mapRect(3450, -1000, 50, 580); //left building wall - spawn.bodyRect(3460, -420, 30, 144); - spawn.mapRect(5450, -775, 100, 875); //right building wall - spawn.bodyRect(3925, -1400, 100, 150, 0.8); - spawn.mapRect(3450, -1250, 1090, 50); - // spawn.mapRect(3450, -1225, 50, 75); - spawn.mapRect(4500, -1250, 50, 415); - spawn.mapRect(3450, -725, 1500, 50); - spawn.mapRect(5100, -725, 400, 50); - spawn.mapRect(4500, -735, 50, 635); - spawn.bodyRect(4500, -100, 50, 100); - spawn.mapRect(4500, -885, 100, 50); - spawn.spawnStairs(3800, 0, 3, 150, 206); //stairs top exit - spawn.mapRect(3400, -275, 450, 275); //exit platform + // spawn.mapRect(1400, -1025, 50, 300); + // spawn.mapRect(1400, -1025, 50, 825); + // spawn.mapRect(600, -600, 275, 75); + // spawn.mapRect(1075, -1050, 550, 400); + // spawn.mapRect(1150, -1000, 150, 575); + // spawn.mapRect(1600, -550, 175, 200); + spawn.bodyRect(750, -725, 125, 125); + spawn.mapRect(1150, -1050, 250, 575); - spawn.randomSmallMob(2200, -1775); - spawn.randomSmallMob(4000, -825); - spawn.randomSmallMob(-350, -2400); - spawn.randomMob(4250, -1350, 0.8); - spawn.randomMob(2550, -1350, 0.8); - spawn.randomMob(1225, -2400, 0.3); - spawn.randomMob(1120, -1200, 0.3); - spawn.randomMob(3000, -1150, 0.2); - spawn.randomMob(3200, -1150, 0.3); - spawn.randomMob(3300, -1750, 0.3); - spawn.randomMob(3650, -1350, 0.3); - spawn.randomMob(3600, -1800, 0.1); - spawn.randomMob(5200, -100, 0.3); - spawn.randomMob(5275, -900, 0.2); - spawn.randomMob(900, -2125, 0.3); - spawn.randomBoss(600, -1575, 0); - spawn.randomBoss(2225, -1325, 0.4); - spawn.randomBoss(4900, -1200, 0); - if (game.difficulty > 3) spawn.randomLevelBoss(3200, -2050); - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - aerie() { - // const elevator = level.platform(4112, -2300, 280, 50) - // game.g = 0.0012 //0.0024 - level.custom = () => { - level.playerExitCheck(); - }; - level.customTopLayer = () => { - // elevator.move() - }; + spawn.mapRect(1725, -550, 50, 200); //walls around portal 3 + // spawn.mapRect(1925, -550, 50, 200); + spawn.mapRect(1925, -550, 500, 200); + spawn.mapRect(1750, -390, 200, 40); + // spawn.mapRect(2350, -550, 75, 200); - // game.difficulty = 4; //for testing to simulate possible mobs spawns - level.defaultZoom = 2100 - game.zoomTransition(level.defaultZoom) + spawn.mapRect(-400, -550, 1800, 200); + spawn.mapRect(-200, -1700, 150, 25); //platform above exit room + spawn.mapRect(-200, -1325, 350, 25); - const backwards = (Math.random() < 0.25 && game.difficulty > 8) ? true : false; - if (backwards) { - level.setPosToSpawn(4000, -3300); //normal spawn - level.exit.x = -100; - level.exit.y = -1025; - } else { - level.setPosToSpawn(-50, -1050); //normal spawn - level.exit.x = 3950; - level.exit.y = -3275; - } + //portal 3 angled + // spawn.mapRect(1425, -550, 350, 250); + // spawn.mapRect(1925, -550, 500, 200); + spawn.mapRect(2425, -450, 100, 100); - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - spawn.mapRect(level.exit.x, level.exit.y + 15, 100, 20); - powerUps.spawnStartingPowerUps(1075, -550); - document.body.style.backgroundColor = "#dcdcde"; + //portal 1 bottom + // spawn.mapRect(2525, -200, 175, 250); //right portal back wall + // spawn.mapRect(2500, -50, 200, 100); + spawn.mapRect(2290, -12, 375, 100); + spawn.mapRect(2350, -24, 375, 100); + spawn.mapRect(2410, -36, 375, 100); - //foreground - level.fill.push({ - x: -100, - y: -1000, - width: 1450, - height: 1400, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 2000, - y: -1110, - width: 450, - height: 1550, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 3700, - y: -3150, - width: 1100, - height: 950, - color: "rgba(0,0,0,0.1)" - }); + //portal 1 top + spawn.mapRect(2290, -3012, 375, 50); + spawn.mapRect(2350, -3024, 375, 50); + spawn.mapRect(2410, -3036, 375, 50); - //background - level.fillBG.push({ - x: 4200, - y: -2200, - width: 100, - height: 2600, - color: "#c7c7ca" - }); - if (!backwards) { - level.fillBG.push({ - x: 3750, - y: -3650, - width: 550, - height: 400, - color: "#d4f4f4" - }); - level.fill.push({ - x: -275, - y: -1275, - width: 425, - height: 300, - color: "rgba(0,0,0,0.1)" - }); - } else { - level.fill.push({ - x: 3750, - y: -3650, - width: 550, - height: 400, - color: "rgba(0,0,0,0.1)" - }); - level.fillBG.push({ - x: -275, - y: -1275, - width: 425, - height: 300, - color: "#d4f4f4" - }); - } + spawn.mapRect(1400, -3000, 1300, 50); //floor + // spawn.mapRect(2500, -3700, 200, 565); //right portal wall + // spawn.mapRect(2525, -3200, 175, 250); //right portal back wall + spawn.mapRect(1750, -3050, 250, 75); + // spawn.bodyRect(1950, -3100, 50, 50); + spawn.mapRect(1400, -3625, 50, 200); + spawn.mapRect(350, -3625, 50, 225); + spawn.mapRect(350, -3260, 50, 60); + // spawn.bodyRect(362, -3400, 25, 140); - // starting room - spawn.mapRect(-300, -1000, 600, 100); - spawn.mapRect(-300, -1300, 450, 50); - spawn.mapRect(-300, -1300, 50, 350); - if (!backwards && game.difficulty > 1) spawn.bodyRect(100, -1250, 200, 240); //remove on backwards - //left building - spawn.mapRect(-100, -975, 100, 975); - spawn.mapRect(-500, 100, 1950, 400); - spawn.boost(-425, 100, 1400); - spawn.mapRect(600, -1000, 750, 100); - spawn.mapRect(900, -500, 550, 100); - spawn.mapRect(1250, -975, 100, 375); - spawn.bodyRect(1250, -600, 100, 100, 0.7); - spawn.mapRect(1250, -450, 100, 450); - if (!backwards) spawn.bodyRect(1250, -1225, 100, 200); //remove on backwards - if (!backwards) spawn.bodyRect(1200, -1025, 350, 25); //remove on backwards - //middle super tower - if (backwards) { - spawn.bodyRect(2000, -800, 700, 35); - } else { - spawn.bodyRect(1750, -800, 700, 35); - } - spawn.mapVertex(2225, -2100, "0 0 450 0 300 -2500 150 -2500") - spawn.mapRect(2000, -700, 450, 300); - spawn.bodyRect(2360, -450, 100, 300, 0.6); - spawn.mapRect(2000, -75, 450, 275); - spawn.bodyRect(2450, 150, 150, 150, 0.4); - spawn.mapRect(1550, 300, 4600, 200); //ground - spawn.boost(5350, 275, 2850); - // spawn.mapRect(6050, -700, 450, 1200); - spawn.mapRect(6050, -1060, 450, 1560); - spawn.mapVertex(6275, -2100, "0 0 450 0 300 -2500 150 -2500") + spawn.mapRect(200, -3250, 1240, 50); + spawn.mapRect(1400, -3260, 50, 310); + spawn.bodyRect(1412, -3425, 25, 165); - //right tall tower - spawn.mapRect(3700, -3200, 100, 800); - spawn.mapRect(4700, -2910, 100, 510); - spawn.mapRect(3700, -2600, 300, 50); - spawn.mapRect(4100, -2900, 900, 50); - spawn.mapRect(3450, -2300, 750, 100); - spawn.mapRect(4300, -2300, 750, 100); - spawn.mapRect(4150, -1600, 200, 25); - spawn.mapRect(4150, -700, 200, 25); - //exit room on top of tower - spawn.mapRect(3700, -3700, 600, 50); - spawn.mapRect(3700, -3700, 50, 500); - spawn.mapRect(4250, -3700, 50, 300); - spawn.mapRect(3700, -3250, 1100, 100); + // spawn.mapRect(-150, -3000, 150, 25); + // spawn.mapRect(-350, -2925, 175, 25); + spawn.mapRect(-150, -2925, 150, 25); - spawn.randomBoss(350, -500, 1) - spawn.randomSmallMob(-225, 25); - spawn.randomSmallMob(1000, -1100); - spawn.randomSmallMob(4000, -250); - spawn.randomSmallMob(4450, -3000); - spawn.randomSmallMob(5600, 100); - spawn.randomMob(4275, -2600, 0.8); - spawn.randomMob(1050, -700, 0.8) - spawn.randomMob(6050, -850, 0.7); - spawn.randomMob(2150, -300, 0.6) - spawn.randomMob(3900, -2700, 0.8); - spawn.randomMob(3600, -500, 0.8); - spawn.randomMob(3400, -200, 0.8); - spawn.randomMob(1650, -1300, 0.7) - spawn.randomMob(4100, -50, 0.7); - spawn.randomMob(4100, -50, 0.5); - spawn.randomMob(1700, -50, 0.3) - spawn.randomMob(2350, -900, 0.3) - spawn.randomMob(4700, -150, 0.2); - spawn.randomBoss(4000, -350, 0.6); - spawn.randomBoss(2750, -550, 0.1); - if (game.difficulty > 3) { - if (Math.random() < 0.1) { // tether ball - spawn.tetherBoss(4250, 0) + //portal 2 + spawn.mapRect(-300, -2600, 300, 675); //left platform + spawn.mapRect(1400, -2600, 375, 675); //right platform + spawn.mapRect(1925, -2600, 775, 675); //far right platform + spawn.bodyRect(2130, -2660, 50, 50); //button's block + + spawn.mapRect(0, -1975, 175, 50); + spawn.mapRect(1225, -1975, 175, 50); + spawn.mapRect(150, -2100, 200, 175); + spawn.mapRect(1050, -2100, 200, 175); + + //mobs + spawn.randomMob(1075, -3500, -0.3); + // spawn.randomMob(-75, -3425, 0.2); + spawn.randomMob(1475, -225, -0.3); + spawn.randomMob(2075, -150, -0.2); + spawn.randomMob(2175, -700, -0.2); + spawn.randomMob(-75, -850, -0.1); + spawn.randomMob(1300, -600, -0.1); + spawn.randomMob(550, -3400, 0); + if (game.difficulty > 50) { + spawn.randomMob(2300, -2775, -0.5); + spawn.randomMob(600, -925, -0.5); + spawn.randomMob(1550, -2750, -0.5); + spawn.randomMob(1350, -1150, -0.5); + spawn.randomMob(-75, -1475, 0); + spawn.randomBoss(600, -2600, 0); + } + if (game.difficulty < 32) { + spawn.randomMob(700, -1650, 0); + spawn.randomMob(600, -3500, 0.2); + spawn.randomMob(-75, -1175, 0.2); + powerUps.spawnBossPowerUp(-125, -1760); + } else { + if (Math.random() < 0.5) { + spawn.randomLevelBoss(700, -1550, ["shooterBoss", "launcherBoss", "laserTargetingBoss"]); + } else { + spawn.randomLevelBoss(675, -2775, ["shooterBoss", "launcherBoss", "laserTargetingBoss"]); + } + } + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + sewers() { + level.bossKilled = false; // if a boss needs to be killed + const rotor = level.rotor(5100, 2475, -0.001) + const button = level.button(6600, 2675) + const hazard = level.hazard(4550, 2750, 4550, 150) + const balance1 = level.spinner(300, -395, 25, 390, 0.001) //entrance + const balance2 = level.spinner(2605, 500, 390, 25, 0.001) //falling + const balance3 = level.spinner(2608, 1900, 584, 25, 0.001) //falling + const balance4 = level.spinner(9300, 2205, 25, 380, 0.001) //exit + + level.custom = () => { + button.query(); + button.draw(); + hazard.query(); + hazard.level(button.isUp) + rotor.rotate(); + level.playerExitCheck(); + }; + level.customTopLayer = () => { + ctx.fillStyle = "#233" + ctx.beginPath(); + ctx.arc(balance1.pointA.x, balance1.pointA.y, 9, 0, 2 * Math.PI); + ctx.moveTo(balance2.pointA.x, balance2.pointA.y) + ctx.arc(balance2.pointA.x, balance2.pointA.y, 9, 0, 2 * Math.PI); + ctx.moveTo(balance3.pointA.x, balance3.pointA.y) + ctx.arc(balance3.pointA.x, balance3.pointA.y, 9, 0, 2 * Math.PI); + ctx.moveTo(balance4.pointA.x, balance4.pointA.y) + ctx.arc(balance4.pointA.x, balance4.pointA.y, 9, 0, 2 * Math.PI); + ctx.fill(); + + hazard.draw(); + }; + + level.setPosToSpawn(0, -50); //normal spawn + + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + level.exit.x = 9700; + level.exit.y = 2560; + level.defaultZoom = 1800 + game.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "hsl(138, 3%, 74%)"; + powerUps.spawnStartingPowerUps(3475, 1775); + spawn.debris(4575, 2550, 1600, 9); //16 debris per level + spawn.debris(7000, 2550, 2000, 7); //16 debris per level + + // level.fill.push({ + // x: 9325, + // y: 2200, + // width: 575, + // height: 400, + // color: "rgba(0,255,255,0.1)" + // }); + level.fillBG.push({ + x: 9300, + y: 2200, + width: 600, + height: 400, + color: "hsl(175, 15%, 76%)" //c4f4f4 + }); + + spawn.mapRect(-500, -600, 200, 800); //left entrance wall + spawn.mapRect(-400, -600, 3550, 200); //ceiling + spawn.mapRect(-400, 0, 3000, 200); //floor + // spawn.mapRect(300, -500, 50, 400); //right entrance wall + // spawn.bodyRect(312, -100, 25, 100); + spawn.bodyRect(1450, -300, 150, 50); + + const xPos = shuffle([600, 1250, 2000]); + spawn.mapRect(xPos[0], -200, 400, 100); + spawn.mapRect(xPos[1], -250, 300, 300); + spawn.mapRect(xPos[2], -150, 300, 200); + + spawn.bodyRect(3100, 410, 75, 100); + spawn.bodyRect(2450, -25, 250, 25); + + spawn.mapRect(3050, -600, 200, 800); //right down tube wall + spawn.mapRect(3100, 0, 1200, 200); //tube right exit ceiling + spawn.mapRect(4200, 0, 200, 1900); + + + spawn.mapVertex(3500, 1000, "-500 -500 -400 -600 400 -600 500 -500 500 500 400 600 -400 600 -500 500"); + spawn.mapVertex(3600, 1940, "-400 -40 -350 -90 350 -90 400 -40 400 40 350 90 -350 90 -400 40"); + spawn.mapRect(3925, 2288, 310, 50); + spawn.mapRect(3980, 2276, 200, 50); + + spawn.mapRect(2625, 2288, 650, 50); + spawn.mapRect(2700, 2276, 500, 50); + // spawn.mapRect(3000, 400, 1000, 1250); + // spawn.mapRect(3000, 1925, 1000, 150); + // spawn.mapRect(3100, 1875, 800, 100); + // spawn.mapRect(3100, 1600, 800, 100); + // spawn.mapRect(3100, 350, 800, 100); + // spawn.mapRect(3100, 2025, 800, 100); + + spawn.mapRect(2400, 0, 200, 1925); //left down tube wall + spawn.mapRect(600, 2300, 3750, 200); + spawn.bodyRect(3800, 275, 125, 125); + + spawn.mapRect(4200, 1700, 5000, 200); + spawn.mapRect(4150, 2300, 200, 400); + + spawn.mapRect(600, 1700, 2000, 200); //bottom left room ceiling + spawn.mapRect(500, 1700, 200, 800); //left wall + spawn.mapRect(675, 1875, 325, 150, 0.5); + + + spawn.mapRect(4450, 2900, 4900, 200); //boss room floor + spawn.mapRect(4150, 2600, 400, 500); + spawn.mapRect(6250, 2675, 700, 325); + spawn.mapRect(8000, 2600, 600, 400); + spawn.bodyRect(5875, 2725, 200, 200); + spawn.bodyRect(6800, 2490, 50, 50); + spawn.bodyRect(6800, 2540, 50, 50); + spawn.bodyRect(6800, 2590, 50, 50); + spawn.bodyRect(8225, 2225, 100, 100); + spawn.mapRect(6250, 1875, 700, 150); + spawn.mapRect(8000, 1875, 600, 150); + + spawn.mapRect(9100, 1700, 900, 500); //exit + spawn.mapRect(9100, 2600, 900, 500); + spawn.mapRect(9900, 1700, 200, 1400); //back wall + // spawn.mapRect(9300, 2150, 50, 250); + spawn.mapRect(9300, 2590, 650, 25); + spawn.mapRect(9700, 2580, 100, 50); + + spawn.randomBoss(1300, 2100, 0.5); + spawn.randomMob(8300, 2100, 0.2); + spawn.randomSmallMob(2575, -75, 0.2); //entrance + spawn.randomMob(8125, 2450, 0.25); + spawn.randomSmallMob(3200, 250, 0.3); + spawn.randomMob(2425, 2150, 0.3); + spawn.randomSmallMob(3500, 250, 0.4); + spawn.randomMob(3800, 2175, 0.4); + spawn.randomSmallMob(1100, -300, 0.4); //entrance + spawn.randomMob(4450, 2500, 0.5); + spawn.randomMob(6350, 2525, 0.5); + spawn.randomBoss(9200, 2400, 0.6); + spawn.randomSmallMob(1900, -250, 0.6); //entrance + spawn.randomMob(1500, 2100, 0.7); + spawn.randomSmallMob(1700, -150, 0.7); //entrance + spawn.randomMob(8800, 2725, 0.8); + spawn.randomMob(7300, 2200, 0.8); + spawn.randomMob(2075, 2025, 0.8); + spawn.randomMob(3475, 2175, 0.8); + if (game.difficulty > 3) spawn.randomLevelBoss(6000, 2300, ["spiderBoss", "launcherBoss", "laserTargetingBoss"]); + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + satellite() { + level.bossKilled = false; // if a boss needs to be killed + const elevator = level.platform(4210, -1325, 380, 30, -10) + level.custom = () => { + level.playerExitCheck(); + }; + level.customTopLayer = () => { + if (elevator.pauseUntilCycle < game.cycle && !mech.isBodiesAsleep) { //elevator move + if (elevator.pointA.y > -1275) { //bottom + elevator.plat.speed = -10 + elevator.pauseUntilCycle = game.cycle + 90 + } else if (elevator.pointA.y < -3455) { //top + elevator.plat.speed = 30 + elevator.pauseUntilCycle = game.cycle + 90 + } + elevator.pointA = { + x: elevator.pointA.x, + y: elevator.pointA.y + elevator.plat.speed + } + } + }; + + level.setPosToSpawn(-50, -50); //normal spawn + level.exit.x = -100; + level.exit.y = -425; + spawn.mapRect(level.exit.x, level.exit.y + 15, 100, 50); //exit bump + + level.defaultZoom = 1700 // 4500 // 1400 + game.zoomTransition(level.defaultZoom) + + powerUps.spawnStartingPowerUps(4900, -500); + spawn.debris(1000, 20, 1800, 3); //16 debris per level //but less here because a few mobs die from laser + spawn.debris(4830, -1330, 850, 3); //16 debris per level + spawn.debris(3035, -3900, 1500, 3); //16 debris per level + + document.body.style.backgroundColor = "#dbdcde"; + + //spawn start building + spawn.mapRect(-300, -800, 50, 800); + spawn.mapRect(-100, -20, 100, 30); + // spawn.mapRect(-300, -10, 500, 50); + spawn.mapRect(150, -510, 50, 365); + spawn.bodyRect(170, -130, 14, 145, 1, spawn.propsFriction); //door to starting room + // spawn.mapRect(-300, 0, 1000, 300); //ground + spawn.mapVertex(-18, 145, "625 0 0 0 0 -300 500 -300"); //entrance ramp + spawn.mapRect(-300, 250, 6300, 300); //deeper ground + spawn.bodyRect(2100, 50, 80, 80); + spawn.bodyRect(2000, 50, 60, 60); + // spawn.bodyRect(1650, 50, 300, 200); + // spawn.mapRect(1800, Math.floor(Math.random() * 200), 850, 300); //stops above body from moving to right + spawn.mapVertex(2225, 250, "575 0 -575 0 -450 -100 450 -100"); //base + + //exit building + // spawn.mapRect(-100, -410, 100, 30); + spawn.mapRect(-300, -800, 500, 50); + spawn.mapRect(150, -800, 50, 110); + spawn.bodyRect(170, -690, 14, 180, 1, spawn.propsFriction); //door to exit room + spawn.mapRect(-300, -400, 500, 100); //far left starting ceiling + level.fill.push({ + x: -250, + y: -400, + width: 1800, + height: 775, + color: "rgba(0,20,40,0.2)" + }); + level.fill.push({ + x: 1800, + y: -475, + width: 850, + height: 775, + color: "rgba(0,20,40,0.2)" + }); + level.fillBG.push({ + x: -250, + y: -750, + width: 420, + height: 450, + color: "#d4f4f4" + }); + + //tall platform above exit + spawn.mapRect(-500, -1900, 400, 50); //super high shade + spawn.mapRect(0, -1900, 400, 50); //super high shade + spawn.mapRect(-150, -1350, 200, 25); //super high shade + spawn.bodyRect(140, -2100, 150, 200); //shield from laser + + level.fillBG.push({ + x: -300, + y: -1900, + width: 500, + height: 1100, + color: "#d0d4d6" + }); + //tall platform + spawn.mapVertex(1125, -450, "325 0 250 80 -250 80 -325 0 -250 -80 250 -80"); //base + spawn.mapRect(150, -500, 1400, 100); //far left starting ceiling + spawn.mapRect(625, -2450, 1000, 50); //super high shade + spawn.bodyRect(1300, -3600, 150, 150); //shield from laser + level.fillBG.push({ + x: 900, + y: -2450, + width: 450, + height: 2050, + color: "#d0d4d6" + }); + //tall platform + spawn.mapVertex(2225, -450, "325 0 250 80 -250 80 -325 0 -250 -80 250 -80"); //base + spawn.mapRect(1725, -2800, 1000, 50); //super high shade + spawn.mapRect(1800, -500, 850, 100); //far left starting ceiling + spawn.bodyRect(2400, -2950, 150, 150); //shield from laser + level.fillBG.push({ + x: 2000, + y: -2800, + width: 450, + height: 2300, + color: "#d0d4d6" + }); + //tall platform + spawn.mapVertex(3350, 200, "375 0 -375 0 -250 -250 250 -250"); //base + spawn.bodyRect(3400, -150, 150, 150); + spawn.mapRect(2850, -3150, 1000, 50); //super high shade + spawn.bodyRect(3675, -3470, 525, 20); //plank + spawn.bodyRect(3600, -3450, 200, 300); //plank support block + level.fillBG.push({ + x: 3125, + y: -3100, + width: 450, + height: 3300, + color: "#d0d4d6" + }); + + //far right structure + spawn.mapRect(5200, -725, 100, 870); + spawn.mapRect(5300, -1075, 350, 1220); + spawn.boost(5825, 235, 1400); + level.fill.push({ + x: 5200, + y: 125, + width: 450, + height: 200, + color: "rgba(0,20,40,0.25)" + }); + + //structure bellow tall stairs + level.fill.push({ + x: 4000, + y: -1200, + width: 1050, + height: 1500, + color: "rgba(0,20,40,0.13)" + }); + spawn.mapRect(3925, -300, 425, 50); + spawn.mapRect(4700, -375, 425, 50); + // spawn.mapRect(4000, -1300, 1050, 100); + spawn.mapRect(4000, -1300, 200, 100); + spawn.mapRect(4600, -1300, 450, 100); + + //steep stairs + // spawn.mapRect(4100, -1700, 100, 100); + // spawn.mapRect(4200, -2050, 100, 450); + // spawn.mapRect(4300, -2400, 100, 800); + // spawn.mapRect(4400, -2750, 100, 1150); + // spawn.mapRect(4500, -3100, 100, 1500); + spawn.mapRect(4100, -2250, 100, 650); + spawn.mapRect(4100, -3450, 100, 650); //left top shelf + spawn.mapRect(4600, -3450, 100, 1850); + // spawn.mapRect(4200, -3450, 100, 400); //left top shelf + // spawn.mapRect(4300, -3450, 100, 100); //left top shelf + level.fill.push({ + x: 4100, + y: -3450, + width: 600, + height: 2250, + color: "rgba(0,20,40,0.13)" + }); + // level.fill.push({ + // x: 4100, + // y: -1600, + // width: 600, + // height: 300, + // color: "rgba(0,20,40,0.13)" + // }); + + spawn.randomSmallMob(4400, -3500); + spawn.randomSmallMob(4800, -800); + spawn.randomSmallMob(800, 150); + spawn.randomMob(700, -600, 0.8); + spawn.randomMob(3100, -3600, 0.7); + spawn.randomMob(3300, -1000, 0.7); + spawn.randomMob(4200, -250, 0.7); + spawn.randomMob(4900, -1500, 0.6); + spawn.randomMob(1200, 100, 0.4); + spawn.randomMob(5900, -1500, 0.4); + spawn.randomMob(4700, -800, 0.4); + spawn.randomMob(1400, -400, 0.3); + spawn.randomMob(1200, 100, 0.3); + spawn.randomMob(2550, -100, 0.2); + spawn.randomMob(2000, -2800, 0.2); + spawn.randomMob(2000, -500, 0.2); + spawn.randomMob(4475, -3550, 0.1); + spawn.randomBoss(5000, -2150, 1); + spawn.randomBoss(3700, -4100, 0.3); + spawn.randomBoss(2700, -1600, 0.1); + spawn.randomBoss(1600, -100, 0); + spawn.randomBoss(5000, -3900, -0.3); + if (game.difficulty > 3) { + if (Math.random() < 0.1) { + spawn.randomLevelBoss(2800, -1400); + } else if (Math.random() < 0.25) { + spawn.laserBoss(2900 + 300 * Math.random(), -2950 + 150 * Math.random()); + } else if (Math.random() < 0.33) { + spawn.laserBoss(1800 + 250 * Math.random(), -2600 + 150 * Math.random()); + } else if (Math.random() < 0.5) { + spawn.laserBoss(3500 + 250 * Math.random(), -2600 + 1000 * Math.random()); + } else { + spawn.laserBoss(600 + 200 * Math.random(), -2150 + 250 * Math.random()); + } + } + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + rooftops() { + level.bossKilled = false; // if a boss needs to be killed + const elevator = level.platform(1450, -1000, 235, 30, -2) + level.custom = () => { + ctx.fillStyle = "#ccc" + ctx.fillRect(1567, -1990, 5, 1020) + level.playerExitCheck(); + }; + level.customTopLayer = () => { + + if (elevator.pauseUntilCycle < game.cycle && !mech.isBodiesAsleep) { //elevator move + if (elevator.pointA.y > -980) { //bottom + elevator.plat.speed = -2 + elevator.pauseUntilCycle = game.cycle + 60 + } else if (elevator.pointA.y < -1980) { //top + elevator.plat.speed = 1 + elevator.pauseUntilCycle = game.cycle + 60 + } + elevator.pointA = { + x: elevator.pointA.x, + y: elevator.pointA.y + elevator.plat.speed + } + } + }; + + level.defaultZoom = 1700 + game.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#dcdcde"; + + + if (Math.random() < 0.75) { + //normal direction start in top left + level.setPosToSpawn(-450, -2060); + level.exit.x = 3600; + level.exit.y = -300; + spawn.mapRect(3600, -285, 100, 50); //ground bump wall + //mobs that spawn in exit room + spawn.bodyRect(4850, -750, 300, 25, 0.6); // + spawn.randomSmallMob(4100, -100); + spawn.randomSmallMob(4600, -100); + spawn.randomMob(3765, -450, 0.3); + level.fill.push({ + x: -650, + y: -2300, + width: 440, + height: 300, + color: "rgba(0,0,0,0.15)" + }); + level.fillBG.push({ + x: 3460, + y: -700, + width: 1090, + height: 800, + color: "#d4f4f4" + }); + } else { + //reverse direction, start in bottom right + level.setPosToSpawn(3650, -325); + level.exit.x = -550; + level.exit.y = -2030; + spawn.mapRect(-550, -2015, 100, 50); //ground bump wall + spawn.boost(4950, 0, 1100); + level.fillBG.push({ + x: -650, + y: -2300, + width: 440, + height: 300, + color: "#d4f4f4" + }); + level.fill.push({ + x: 3460, + y: -700, + width: 1090, + height: 800, + color: "rgba(0,0,0,0.1)" + }); + } + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + + spawn.debris(1650, -1800, 3800, 16); //16 debris per level + powerUps.spawnStartingPowerUps(2450, -1675); + + //foreground + + level.fill.push({ + x: 3460, + y: -1250, + width: 1080, + height: 550, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 4550, + y: -725, + width: 900, + height: 725, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3400, + y: 100, + width: 2150, + height: 900, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: -700, + y: -1950, + width: 2100, + height: 2950, + color: "rgba(0,0,0,0.1)" + }); + + level.fill.push({ + x: 1860, + y: -1950, + width: 630, + height: 350, + color: "rgba(0,0,0,0.1)" + }); + + level.fill.push({ + x: 1735, + y: -1550, + width: 1405, + height: 550, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 1735, + y: -900, + width: 1515, + height: 1900, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3510, + y: -1550, + width: 330, + height: 300, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 710, + y: -2225, + width: 580, + height: 225, + color: "rgba(0,0,0,0.1)" + }); + + //spawn.mapRect(-700, 0, 6250, 100); //ground + spawn.mapRect(3400, 0, 2150, 100); //ground + spawn.mapRect(-700, -2000, 2125, 50); //Top left ledge + spawn.bodyRect(1300, -2125, 50, 125, 0.8); + spawn.bodyRect(1307, -2225, 50, 100, 0.8); + spawn.mapRect(-700, -2350, 50, 400); //far left starting left wall + spawn.mapRect(-700, -2010, 500, 50); //far left starting ground + spawn.mapRect(-700, -2350, 500, 50); //far left starting ceiling + spawn.mapRect(-250, -2350, 50, 200); //far left starting right part of wall + spawn.bodyRect(-240, -2150, 30, 36); //door to starting room + spawn.bodyRect(-240, -2115, 30, 36); //door to starting room + spawn.bodyRect(-240, -2080, 30, 35); //door to starting room + spawn.bodyRect(-240, -2045, 30, 35); //door to starting room + spawn.mapRect(1850, -2000, 650, 50); + spawn.bodyRect(200, -2150, 80, 220, 0.8); + spawn.mapRect(700, -2275, 600, 50); + spawn.mapRect(1000, -1350, 410, 50); + spawn.bodyRect(1050, -2350, 30, 30, 0.8); + // spawn.boost(1800, -1000, 1200); + // spawn.bodyRect(1625, -1100, 100, 75); + // spawn.bodyRect(1350, -1025, 400, 25); // ground plank + spawn.mapRect(-725, -1000, 2150, 100); //lower left ledge + spawn.bodyRect(350, -1100, 200, 100, 0.8); + spawn.bodyRect(370, -1200, 100, 100, 0.8); + spawn.bodyRect(360, -1300, 100, 100, 0.8); + spawn.bodyRect(950, -1050, 300, 50, 0.8); + spawn.bodyRect(-575, -1150, 125, 150, 0.8); + spawn.mapRect(1710, -1000, 1565, 100); //middle ledge + spawn.mapRect(3400, -1000, 75, 25); + spawn.bodyRect(2600, -1950, 100, 250, 0.8); + spawn.bodyRect(2700, -1125, 125, 125, 0.8); + spawn.bodyRect(2710, -1250, 125, 125, 0.8); + spawn.bodyRect(2705, -1350, 75, 100, 0.8); + spawn.mapRect(3500, -1600, 350, 50); + spawn.mapRect(1725, -1600, 1435, 50); + spawn.bodyRect(3100, -1015, 375, 15); + spawn.bodyRect(3500, -850, 75, 125, 0.8); + spawn.mapRect(3450, -1000, 50, 580); //left building wall + spawn.bodyRect(3460, -420, 30, 144); + spawn.mapRect(5450, -775, 100, 875); //right building wall + spawn.bodyRect(3925, -1400, 100, 150, 0.8); + spawn.mapRect(3450, -1250, 1090, 50); + // spawn.mapRect(3450, -1225, 50, 75); + spawn.mapRect(4500, -1250, 50, 415); + spawn.mapRect(3450, -725, 1500, 50); + spawn.mapRect(5100, -725, 400, 50); + spawn.mapRect(4500, -735, 50, 635); + spawn.bodyRect(4500, -100, 50, 100); + spawn.mapRect(4500, -885, 100, 50); + spawn.spawnStairs(3800, 0, 3, 150, 206); //stairs top exit + spawn.mapRect(3400, -275, 450, 275); //exit platform + + spawn.randomSmallMob(2200, -1775); + spawn.randomSmallMob(4000, -825); + spawn.randomSmallMob(-350, -2400); + spawn.randomMob(4250, -1350, 0.8); + spawn.randomMob(2550, -1350, 0.8); + spawn.randomMob(1225, -2400, 0.3); + spawn.randomMob(1120, -1200, 0.3); + spawn.randomMob(3000, -1150, 0.2); + spawn.randomMob(3200, -1150, 0.3); + spawn.randomMob(3300, -1750, 0.3); + spawn.randomMob(3650, -1350, 0.3); + spawn.randomMob(3600, -1800, 0.1); + spawn.randomMob(5200, -100, 0.3); + spawn.randomMob(5275, -900, 0.2); + spawn.randomMob(900, -2125, 0.3); + spawn.randomBoss(600, -1575, 0); + spawn.randomBoss(2225, -1325, 0.4); + spawn.randomBoss(4900, -1200, 0); + if (game.difficulty > 3) spawn.randomLevelBoss(3200, -2050); + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + aerie() { + level.bossKilled = false; // if a boss needs to be killed + // const elevator = level.platform(4112, -2300, 280, 50) + // game.g = 0.0012 //0.0024 + level.custom = () => { + level.playerExitCheck(); + }; + level.customTopLayer = () => { + // elevator.move() + }; + + // game.difficulty = 4; //for testing to simulate possible mobs spawns + level.defaultZoom = 2100 + game.zoomTransition(level.defaultZoom) + + const backwards = (Math.random() < 0.25 && game.difficulty > 8) ? true : false; + if (backwards) { + level.setPosToSpawn(4000, -3300); //normal spawn + level.exit.x = -100; + level.exit.y = -1025; + } else { + level.setPosToSpawn(-50, -1050); //normal spawn + level.exit.x = 3950; + level.exit.y = -3275; + } + + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + spawn.mapRect(level.exit.x, level.exit.y + 15, 100, 20); + + powerUps.spawnStartingPowerUps(1075, -550); + document.body.style.backgroundColor = "#dcdcde"; + + //foreground + level.fill.push({ + x: -100, + y: -1000, + width: 1450, + height: 1400, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 2000, + y: -1110, + width: 450, + height: 1550, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3700, + y: -3150, + width: 1100, + height: 950, + color: "rgba(0,0,0,0.1)" + }); + + //background + level.fillBG.push({ + x: 4200, + y: -2200, + width: 100, + height: 2600, + color: "#c7c7ca" + }); + if (!backwards) { + level.fillBG.push({ + x: 3750, + y: -3650, + width: 550, + height: 400, + color: "#d4f4f4" + }); + level.fill.push({ + x: -275, + y: -1275, + width: 425, + height: 300, + color: "rgba(0,0,0,0.1)" + }); + } else { + level.fill.push({ + x: 3750, + y: -3650, + width: 550, + height: 400, + color: "rgba(0,0,0,0.1)" + }); + level.fillBG.push({ + x: -275, + y: -1275, + width: 425, + height: 300, + color: "#d4f4f4" + }); + } + + // starting room + spawn.mapRect(-300, -1000, 600, 100); + spawn.mapRect(-300, -1300, 450, 50); + spawn.mapRect(-300, -1300, 50, 350); + if (!backwards && game.difficulty > 1) spawn.bodyRect(100, -1250, 200, 240); //remove on backwards + //left building + spawn.mapRect(-100, -975, 100, 975); + spawn.mapRect(-500, 100, 1950, 400); + spawn.boost(-425, 100, 1400); + spawn.mapRect(600, -1000, 750, 100); + spawn.mapRect(900, -500, 550, 100); + spawn.mapRect(1250, -975, 100, 375); + spawn.bodyRect(1250, -600, 100, 100, 0.7); + spawn.mapRect(1250, -450, 100, 450); + if (!backwards) spawn.bodyRect(1250, -1225, 100, 200); //remove on backwards + if (!backwards) spawn.bodyRect(1200, -1025, 350, 25); //remove on backwards + //middle super tower + if (backwards) { + spawn.bodyRect(2000, -800, 700, 35); + } else { + spawn.bodyRect(1750, -800, 700, 35); + } + spawn.mapVertex(2225, -2100, "0 0 450 0 300 -2500 150 -2500") + spawn.mapRect(2000, -700, 450, 300); + spawn.bodyRect(2360, -450, 100, 300, 0.6); + spawn.mapRect(2000, -75, 450, 275); + spawn.bodyRect(2450, 150, 150, 150, 0.4); + spawn.mapRect(1550, 300, 4600, 200); //ground + spawn.boost(5350, 275, 2850); + // spawn.mapRect(6050, -700, 450, 1200); + spawn.mapRect(6050, -1060, 450, 1560); + spawn.mapVertex(6275, -2100, "0 0 450 0 300 -2500 150 -2500") + + //right tall tower + spawn.mapRect(3700, -3200, 100, 800); + spawn.mapRect(4700, -2910, 100, 510); + spawn.mapRect(3700, -2600, 300, 50); + spawn.mapRect(4100, -2900, 900, 50); + spawn.mapRect(3450, -2300, 750, 100); + spawn.mapRect(4300, -2300, 750, 100); + spawn.mapRect(4150, -1600, 200, 25); + spawn.mapRect(4150, -700, 200, 25); + //exit room on top of tower + spawn.mapRect(3700, -3700, 600, 50); + spawn.mapRect(3700, -3700, 50, 500); + spawn.mapRect(4250, -3700, 50, 300); + spawn.mapRect(3700, -3250, 1100, 100); + + spawn.randomBoss(350, -500, 1) + spawn.randomSmallMob(-225, 25); + spawn.randomSmallMob(1000, -1100); + spawn.randomSmallMob(4000, -250); + spawn.randomSmallMob(4450, -3000); + spawn.randomSmallMob(5600, 100); + spawn.randomMob(4275, -2600, 0.8); + spawn.randomMob(1050, -700, 0.8) + spawn.randomMob(6050, -850, 0.7); + spawn.randomMob(2150, -300, 0.6) + spawn.randomMob(3900, -2700, 0.8); + spawn.randomMob(3600, -500, 0.8); + spawn.randomMob(3400, -200, 0.8); + spawn.randomMob(1650, -1300, 0.7) + spawn.randomMob(4100, -50, 0.7); + spawn.randomMob(4100, -50, 0.5); + spawn.randomMob(1700, -50, 0.3) + spawn.randomMob(2350, -900, 0.3) + spawn.randomMob(4700, -150, 0.2); + spawn.randomBoss(4000, -350, 0.6); + spawn.randomBoss(2750, -550, 0.1); + if (game.difficulty > 3) { + if (Math.random() < 0.1) { // tether ball + spawn.tetherBoss(4250, 0) + cons[cons.length] = Constraint.create({ + pointA: { + x: 4250, + y: -675 + }, + bodyB: mob[mob.length - 1], + stiffness: 0.00007 + }); + World.add(engine.world, cons[cons.length - 1]); + + if (game.difficulty > 4) spawn.nodeBoss(4250, 0, "spawns", 8, 20, 105); //chance to spawn a ring of exploding mobs around this boss + } else if (Math.random() < 0.15) { + spawn.randomLevelBoss(4250, -250); + spawn.debris(-250, 50, 1650, 2); //16 debris per level + spawn.debris(2475, 0, 750, 2); //16 debris per level + spawn.debris(3450, 0, 2000, 16); //16 debris per level + spawn.debris(3500, -2350, 1500, 2); //16 debris per level + } else { + powerUps.chooseRandomPowerUp(4000, 200); + powerUps.chooseRandomPowerUp(4000, 200); + //floor below right tall tower + spawn.bodyRect(3000, 50, 150, 250, 0.9); + spawn.bodyRect(4500, -500, 300, 250, 0.7); + spawn.bodyRect(3500, -100, 100, 150, 0.7); + spawn.bodyRect(4200, -500, 110, 30, 0.7); + spawn.bodyRect(3800, -500, 150, 130, 0.7); + spawn.bodyRect(4000, 50, 200, 150, 0.9); + spawn.bodyRect(4500, 50, 300, 200, 0.9); + spawn.bodyRect(4200, -350, 200, 50, 0.9); + spawn.bodyRect(4700, -350, 50, 200, 0.9); + spawn.bodyRect(4900, -100, 300, 300, 0.7); + spawn.suckerBoss(4500, -400); + } + } + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + skyscrapers() { + level.bossKilled = false; // if a boss needs to be killed + level.custom = () => { + level.playerExitCheck(); + }; + level.customTopLayer = () => {}; + + level.setPosToSpawn(-50, -60); //normal spawn + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + level.exit.x = 1500; + level.exit.y = -1875; + + level.defaultZoom = 2000 + game.zoomTransition(level.defaultZoom) + + //level.setPosToSpawn(1550, -1200); //spawn left high + //level.setPosToSpawn(1800, -2000); //spawn near exit + + powerUps.spawnStartingPowerUps(1475, -1175); + spawn.debris(750, -2200, 3700, 16); //16 debris per level + document.body.style.backgroundColor = "#dcdcde"; + // game.draw.mapFill = "#444" + // game.draw.bodyFill = "rgba(140,140,140,0.85)" + // game.draw.bodyStroke = "#222" + + //foreground + level.fill.push({ + x: 2500, + y: -1100, + width: 450, + height: 250, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 2400, + y: -550, + width: 600, + height: 150, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 2550, + y: -1650, + width: 250, + height: 200, + color: "rgba(0,0,0,0.1)" + }); + //level.fill.push({ x: 1350, y: -2100, width: 400, height: 250, color: "rgba(0,255,255,0.1)" }); + level.fill.push({ + x: 700, + y: -110, + width: 400, + height: 110, + color: "rgba(0,0,0,0.2)" + }); + level.fill.push({ + x: 3600, + y: -110, + width: 400, + height: 110, + color: "rgba(0,0,0,0.2)" + }); + level.fill.push({ + x: -250, + y: -300, + width: 450, + height: 300, + color: "rgba(0,0,0,0.15)" + }); + + //background + level.fillBG.push({ + x: 1300, + y: -1800, + width: 750, + height: 1800, + color: "#d4d4d7" + }); + level.fillBG.push({ + x: 3350, + y: -1325, + width: 50, + height: 1325, + color: "#d4d4d7" + }); + level.fillBG.push({ + x: 1350, + y: -2100, + width: 400, + height: 250, + color: "#d4f4f4" + }); + + spawn.mapRect(-300, 0, 5000, 300); //***********ground + spawn.mapRect(-300, -350, 50, 400); //far left starting left wall + spawn.mapRect(-300, -10, 500, 50); //far left starting ground + spawn.mapRect(-300, -350, 500, 50); //far left starting ceiling + spawn.mapRect(150, -350, 50, 200); //far left starting right part of wall + spawn.bodyRect(170, -130, 14, 140, 1, spawn.propsFriction); //door to starting room + spawn.boost(475, 0, 1300); + spawn.mapRect(700, -1100, 400, 990); //far left building + spawn.mapRect(1600, -400, 1500, 500); //long center building + spawn.mapRect(1345, -1100, 250, 25); //left platform + spawn.mapRect(1755, -1100, 250, 25); //right platform + spawn.mapRect(1300, -1850, 780, 50); //left higher platform + spawn.mapRect(1300, -2150, 50, 350); //left higher platform left edge wall + spawn.mapRect(1300, -2150, 450, 50); //left higher platform roof + spawn.mapRect(1500, -1860, 100, 50); //ground bump wall + spawn.mapRect(2400, -850, 600, 300); //center floating large square + //spawn.bodyRect(2500, -1100, 25, 250); //wall before chasers + spawn.mapRect(2500, -1450, 450, 350); //higher center floating large square + spawn.mapRect(2500, -1675, 50, 300); //left wall on higher center floating large square + spawn.mapRect(2500, -1700, 300, 50); //roof on higher center floating large square + spawn.mapRect(3300, -850, 150, 25); //ledge by far right building + spawn.mapRect(3300, -1350, 150, 25); //higher ledge by far right building + spawn.mapRect(3600, -1100, 400, 990); //far right building + spawn.boost(4150, 0, 1300); + + spawn.bodyRect(3200, -1375, 300, 25, 0.9); + spawn.bodyRect(1825, -1875, 400, 25, 0.9); + // spawn.bodyRect(1800, -575, 250, 150, 0.8); + spawn.bodyRect(1800, -600, 110, 150, 0.8); + spawn.bodyRect(2557, -450, 35, 55, 0.7); + spawn.bodyRect(2957, -450, 30, 15, 0.7); + spawn.bodyRect(2900, -450, 60, 45, 0.7); + spawn.bodyRect(915, -1200, 60, 100, 0.95); + spawn.bodyRect(925, -1300, 50, 100, 0.95); + if (Math.random() < 0.9) { + spawn.bodyRect(2300, -1720, 400, 20); + spawn.bodyRect(2590, -1780, 80, 80); + } + spawn.bodyRect(2925, -1100, 25, 250, 0.8); + spawn.bodyRect(3325, -1550, 50, 200, 0.3); + if (Math.random() < 0.8) { + spawn.bodyRect(1400, -75, 200, 75); //block to get up ledge from ground + spawn.bodyRect(1525, -125, 50, 50); //block to get up ledge from ground + } + spawn.bodyRect(1025, -1110, 400, 25, 0.9); //block on far left building + spawn.bodyRect(1425, -1110, 115, 25, 0.9); //block on far left building + spawn.bodyRect(1540, -1110, 300, 25, 0.9); //block on far left building + + spawn.randomSmallMob(1300, -70); + spawn.randomSmallMob(3200, -100); + spawn.randomSmallMob(4450, -100); + spawn.randomSmallMob(2700, -475); + spawn.randomMob(2650, -975, 0.8); + spawn.randomMob(2650, -1550, 0.8); + spawn.randomMob(4150, -200, 0.15); + spawn.randomMob(1700, -1300, 0.2); + spawn.randomMob(1850, -1950, 0.25); + spawn.randomMob(2610, -1880, 0.25); + spawn.randomMob(3350, -950, 0.25); + spawn.randomMob(1690, -2250, 0.25); + spawn.randomMob(2200, -600, 0.2); + spawn.randomMob(850, -1300, 0.25); + spawn.randomMob(-100, -900, -0.2); + spawn.randomBoss(3700, -1500, 0.4); + spawn.randomBoss(1700, -900, 0.4); + if (game.difficulty > 3) spawn.randomLevelBoss(2200, -1300); + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + highrise() { + level.bossKilled = false; // if a boss needs to be killed + level.custom = () => { + level.playerExitCheck(); + }; + level.customTopLayer = () => {}; + + level.setPosToSpawn(0, -700); //normal spawn + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + level.exit.x = -4275; + level.exit.y = -2805; + + level.defaultZoom = 1500 + game.zoomTransition(level.defaultZoom) + + powerUps.spawnStartingPowerUps(-2550, -700); + document.body.style.backgroundColor = "#dcdcde" //"#fafcff"; + + spawn.debris(-2325, -1825, 2400); //16 debris per level + spawn.debris(-2625, -600, 600, 5); //16 debris per level + spawn.debris(-2000, -60, 1200, 5); //16 debris per level + + //background + level.fillBG.push({ + x: -4425, + y: -3050, + width: 425, + height: 275, + color: "#cff" + }); + level.fillBG.push({ + x: -3375, + y: -2875, + width: 25, + height: 725, + color: "#d0d0d2" + }); + level.fillBG.push({ + x: -2975, + y: -2750, + width: 25, + height: 600, + color: "#d0d0d2" + }); + level.fillBG.push({ + x: -2475, + y: -2450, + width: 25, + height: 750, + color: "#d0d0d2" + }); + + //3 platforms that lead to exit + spawn.mapRect(-3440, -2875, 155, 25); + spawn.mapRect(-3025, -2775, 125, 25); + spawn.mapRect(-2525, -2475, 125, 25); + spawn.bodyRect(-2600, -2500, 225, 20, 0.7); + spawn.bodyRect(-3350, -2900, 25, 25, 0.5); + spawn.bodyRect(-3400, -2950, 50, 75, 0.5); + + + //foreground + level.fill.push({ + x: -1650, + y: -1575, + width: 550, + height: 425, + color: "rgba(0,0,0,0.12)" + }); + level.fill.push({ + x: -2600, + y: -1675, + width: 450, + height: 1125, + color: "rgba(0,0,0,0.12)" + }); + + level.fill.push({ + x: -3425, + y: -2150, + width: 525, + height: 1550, + color: "rgba(0,0,0,0.12)" + }); + level.fill.push({ + x: -1850, + y: -1150, + width: 2025, + height: 1150, + color: "rgba(0,0,0,0.12)" + }); + + //hidden zone + level.fill.push({ + x: -4450, + y: -955, + width: 1025, + height: 360, + color: "rgba(64,64,64,0.97)" + }); + + powerUps.spawn(-4300, -700, "heal"); + powerUps.spawn(-4200, -700, "ammo"); + powerUps.spawn(-4000, -700, "ammo"); + spawn.mapRect(-4450, -1000, 100, 500); + spawn.bodyRect(-3576, -750, 150, 150); + + //building 1 + spawn.bodyRect(-1000, -675, 25, 25); + spawn.mapRect(-2225, 0, 2475, 150); + spawn.mapRect(175, -1000, 75, 1100); + + spawn.mapRect(-175, -985, 25, 175); + spawn.bodyRect(-170, -810, 14, 160, 1, spawn.propsFriction); //door to starting room + spawn.mapRect(-600, -650, 825, 50); + spawn.mapRect(-1300, -650, 500, 50); + spawn.mapRect(-175, -250, 425, 300); + spawn.bodyRect(-75, -300, 50, 50); + + // spawn.boost(-750, 0, 0, -0.01); + spawn.boost(-750, 0, 1700); + spawn.bodyRect(-425, -1375, 400, 225); + spawn.mapRect(-1125, -1575, 50, 475); + spawn.bodyRect(-1475, -1275, 250, 125); + spawn.bodyRect(-825, -1160, 250, 10); + spawn.mapRect(-1650, -1575, 400, 50); + spawn.mapRect(-600, -1150, 850, 175); + spawn.mapRect(-1850, -1150, 1050, 175); + spawn.bodyRect(-1907, -1600, 550, 25); + if (game.difficulty < 4) { + spawn.bodyRect(-1600, -125, 125, 125); + spawn.bodyRect(-1560, -200, 75, 75); + } else { + spawn.bodyRect(-1200, -125, 125, 125); + spawn.bodyRect(-1160, -200, 75, 75); + } + //building 2 + spawn.mapRect(-4450, -600, 2300, 750); + spawn.mapRect(-2225, -500, 175, 550); + // spawn.mapRect(-2600, -975, 450, 50); + spawn.boost(-2800, -600, 1700); + spawn.mapRect(-3450, -1325, 550, 50); + spawn.mapRect(-3425, -2200, 525, 50); + spawn.mapRect(-2600, -1700, 450, 50); + // spawn.mapRect(-2600, -2450, 450, 50); + spawn.bodyRect(-2275, -2700, 50, 60); + spawn.bodyRect(-2600, -1925, 250, 225); + spawn.bodyRect(-3415, -1425, 100, 100); + spawn.bodyRect(-3400, -1525, 100, 100); + spawn.bodyRect(-3305, -1425, 100, 100); + //building 3 + spawn.mapRect(-4450, -1750, 1025, 1000); + spawn.mapRect(-3750, -2000, 175, 275); + spawn.mapRect(-4000, -2350, 275, 675); + // spawn.mapRect(-4450, -2650, 475, 1000); + spawn.mapRect(-4450, -2775, 475, 1125); + spawn.bodyRect(-3715, -2050, 50, 50); + spawn.bodyRect(-3570, -1800, 50, 50); + spawn.bodyRect(-2970, -2250, 50, 50); + spawn.bodyRect(-3080, -2250, 40, 40); + spawn.bodyRect(-3420, -650, 50, 50); + + + //exit + spawn.mapRect(-4450, -3075, 25, 300); + spawn.mapRect(-4450, -3075, 450, 25); + spawn.mapRect(-4025, -3075, 25, 100); + spawn.mapRect(-4275, -2785, 100, 25); + if (game.difficulty < 4) spawn.bodyRect(-3760, -2400, 50, 50); + + //mobs + spawn.randomMob(-2500, -2700, 1); + spawn.randomMob(-3200, -750, 1); + spawn.randomMob(-1875, -775, 0.2); + spawn.randomMob(-950, -1675, 0.2); + spawn.randomMob(-1525, -1750, 0.2); + spawn.randomMob(-1375, -1400, 0.2); + spawn.randomMob(-1625, -1275, 0.2); + spawn.randomMob(-1900, -1250, 0.2); + spawn.randomMob(-2250, -1850, 0.2); + spawn.randomMob(-2475, -2200, 0.2); + spawn.randomMob(-3000, -1475, 0.2); + spawn.randomMob(-3850, -2500, 0.2); + spawn.randomMob(-3650, -2125, 0.2); + spawn.randomMob(-4010, -3200, 0.2); + spawn.randomMob(-3500, -1825, 0.2); + spawn.randomMob(-975, -100, 0); + spawn.randomMob(-1050, -725, 0.2); + spawn.randomMob(-1525, -100, 0); + spawn.randomMob(-525, -1700, -0.1); + spawn.randomMob(-125, -1500, -0.1); + spawn.randomMob(-325, -1900, -0.1); + spawn.randomMob(-550, -100, -0.1); + spawn.randomBoss(-3250, -2700, 0.2); + spawn.randomBoss(-2450, -1100, 0); + + if (game.difficulty > 3) spawn.randomLevelBoss(-2400, -3000); + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + warehouse() { + level.bossKilled = false; // if a boss needs to be killed + level.custom = () => { + level.playerExitCheck(); + }; + level.customTopLayer = () => {}; + + level.setPosToSpawn(25, -55); //normal spawn + level.exit.x = 425; + level.exit.y = -30; + + level.defaultZoom = 1300 + game.zoomTransition(level.defaultZoom) + + spawn.debris(-2250, 1330, 3000, 6); //16 debris per level + spawn.debris(-3000, -800, 3280, 6); //16 debris per level + spawn.debris(-1400, 410, 2300, 5); //16 debris per level + powerUps.spawnStartingPowerUps(25, 500); + document.body.style.backgroundColor = "#dcdcde" //"#f2f5f3"; + + //background + const BGColor = "rgba(0,0,0,0.1)"; + level.fill.push({ + x: -3025, + y: 50, + width: 4125, + height: 1350, + color: BGColor + }); + level.fill.push({ + x: -1800, + y: -500, + width: 1625, + height: 550, + color: BGColor + }); + level.fill.push({ + x: -175, + y: -250, + width: 350, + height: 300, + color: BGColor + }); + level.fill.push({ + x: -2600, + y: -150, + width: 700, + height: 200, + color: BGColor + }); + level.fillBG.push({ + x: 300, + y: -250, + width: 350, + height: 250, + color: "#cff" + }); + spawn.mapRect(-1500, 0, 2750, 100); + spawn.mapRect(175, -270, 125, 300); + spawn.mapRect(-1900, -600, 1775, 100); + spawn.mapRect(-1900, -550, 100, 1250); + //house + spawn.mapRect(-175, -550, 50, 400); + spawn.mapRect(-175, -10, 350, 50); + spawn.mapRect(-25, -20, 100, 50); + + //exit house + spawn.mapRect(300, -10, 350, 50); + spawn.mapRect(-150, -300, 800, 50); + spawn.mapRect(600, -275, 50, 75); + spawn.mapRect(425, -20, 100, 25); + // spawn.mapRect(-1900, 600, 2700, 100); + spawn.mapRect(1100, 0, 150, 1500); + spawn.mapRect(-2850, 1400, 4100, 100); + spawn.mapRect(-2375, 875, 1775, 75); + spawn.mapRect(-1450, 865, 75, 435); + spawn.mapRect(-1450, 662, 75, 100); + spawn.bodyRect(-1418, 773, 11, 102, 1, spawn.propsFriction); //blocking path + spawn.mapRect(-2950, 1250, 175, 250); + spawn.mapRect(-3050, 1100, 150, 400); + spawn.mapRect(-3150, 50, 125, 1450); + spawn.mapRect(-2375, 600, 3175, 100); + spawn.mapRect(-2125, 400, 250, 275); + // spawn.mapRect(-1950, -400, 100, 25); + spawn.mapRect(-3150, 50, 775, 100); + spawn.mapRect(-2600, -250, 775, 100); + spawn.bodyRect(-1450, -125, 125, 125, 1, spawn.propsSlide); //weight + spawn.bodyRect(-1800, 0, 300, 100, 1, spawn.propsHoist); //hoist cons[cons.length] = Constraint.create({ - pointA: { - x: 4250, - y: -675 - }, - bodyB: mob[mob.length - 1], - stiffness: 0.00007 + pointA: { + x: -1650, + y: -500 + }, + bodyB: body[body.length - 1], + stiffness: 0.0001815, + length: 1 }); World.add(engine.world, cons[cons.length - 1]); - if (game.difficulty > 4) spawn.nodeBoss(4250, 0, "spawns", 8, 20, 105); //chance to spawn a ring of exploding mobs around this boss - } else if (Math.random() < 0.15) { - spawn.randomLevelBoss(4250, -250); - spawn.debris(-250, 50, 1650, 2); //16 debris per level - spawn.debris(2475, 0, 750, 2); //16 debris per level - spawn.debris(3450, 0, 2000, 16); //16 debris per level - spawn.debris(3500, -2350, 1500, 2); //16 debris per level - } else { - powerUps.chooseRandomPowerUp(4000, 200); - powerUps.chooseRandomPowerUp(4000, 200); - //floor below right tall tower - spawn.bodyRect(3000, 50, 150, 250, 0.9); - spawn.bodyRect(4500, -500, 300, 250, 0.7); - spawn.bodyRect(3500, -100, 100, 150, 0.7); - spawn.bodyRect(4200, -500, 110, 30, 0.7); - spawn.bodyRect(3800, -500, 150, 130, 0.7); - spawn.bodyRect(4000, 50, 200, 150, 0.9); - spawn.bodyRect(4500, 50, 300, 200, 0.9); - spawn.bodyRect(4200, -350, 200, 50, 0.9); - spawn.bodyRect(4700, -350, 50, 200, 0.9); - spawn.bodyRect(4900, -100, 300, 300, 0.7); - spawn.suckerBoss(4500, -400); - } - } - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - skyscrapers() { - level.custom = () => { - level.playerExitCheck(); - }; - level.customTopLayer = () => {}; - - level.setPosToSpawn(-50, -60); //normal spawn - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - level.exit.x = 1500; - level.exit.y = -1875; - - level.defaultZoom = 2000 - game.zoomTransition(level.defaultZoom) - - //level.setPosToSpawn(1550, -1200); //spawn left high - //level.setPosToSpawn(1800, -2000); //spawn near exit - - powerUps.spawnStartingPowerUps(1475, -1175); - spawn.debris(750, -2200, 3700, 16); //16 debris per level - document.body.style.backgroundColor = "#dcdcde"; - // game.draw.mapFill = "#444" - // game.draw.bodyFill = "rgba(140,140,140,0.85)" - // game.draw.bodyStroke = "#222" - - //foreground - level.fill.push({ - x: 2500, - y: -1100, - width: 450, - height: 250, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 2400, - y: -550, - width: 600, - height: 150, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 2550, - y: -1650, - width: 250, - height: 200, - color: "rgba(0,0,0,0.1)" - }); - //level.fill.push({ x: 1350, y: -2100, width: 400, height: 250, color: "rgba(0,255,255,0.1)" }); - level.fill.push({ - x: 700, - y: -110, - width: 400, - height: 110, - color: "rgba(0,0,0,0.2)" - }); - level.fill.push({ - x: 3600, - y: -110, - width: 400, - height: 110, - color: "rgba(0,0,0,0.2)" - }); - level.fill.push({ - x: -250, - y: -300, - width: 450, - height: 300, - color: "rgba(0,0,0,0.15)" - }); - - //background - level.fillBG.push({ - x: 1300, - y: -1800, - width: 750, - height: 1800, - color: "#d4d4d7" - }); - level.fillBG.push({ - x: 3350, - y: -1325, - width: 50, - height: 1325, - color: "#d4d4d7" - }); - level.fillBG.push({ - x: 1350, - y: -2100, - width: 400, - height: 250, - color: "#d4f4f4" - }); - - spawn.mapRect(-300, 0, 5000, 300); //***********ground - spawn.mapRect(-300, -350, 50, 400); //far left starting left wall - spawn.mapRect(-300, -10, 500, 50); //far left starting ground - spawn.mapRect(-300, -350, 500, 50); //far left starting ceiling - spawn.mapRect(150, -350, 50, 200); //far left starting right part of wall - spawn.bodyRect(170, -130, 14, 140, 1, spawn.propsFriction); //door to starting room - spawn.boost(475, 0, 1300); - spawn.mapRect(700, -1100, 400, 990); //far left building - spawn.mapRect(1600, -400, 1500, 500); //long center building - spawn.mapRect(1345, -1100, 250, 25); //left platform - spawn.mapRect(1755, -1100, 250, 25); //right platform - spawn.mapRect(1300, -1850, 780, 50); //left higher platform - spawn.mapRect(1300, -2150, 50, 350); //left higher platform left edge wall - spawn.mapRect(1300, -2150, 450, 50); //left higher platform roof - spawn.mapRect(1500, -1860, 100, 50); //ground bump wall - spawn.mapRect(2400, -850, 600, 300); //center floating large square - //spawn.bodyRect(2500, -1100, 25, 250); //wall before chasers - spawn.mapRect(2500, -1450, 450, 350); //higher center floating large square - spawn.mapRect(2500, -1675, 50, 300); //left wall on higher center floating large square - spawn.mapRect(2500, -1700, 300, 50); //roof on higher center floating large square - spawn.mapRect(3300, -850, 150, 25); //ledge by far right building - spawn.mapRect(3300, -1350, 150, 25); //higher ledge by far right building - spawn.mapRect(3600, -1100, 400, 990); //far right building - spawn.boost(4150, 0, 1300); - - spawn.bodyRect(3200, -1375, 300, 25, 0.9); - spawn.bodyRect(1825, -1875, 400, 25, 0.9); - // spawn.bodyRect(1800, -575, 250, 150, 0.8); - spawn.bodyRect(1800, -600, 110, 150, 0.8); - spawn.bodyRect(2557, -450, 35, 55, 0.7); - spawn.bodyRect(2957, -450, 30, 15, 0.7); - spawn.bodyRect(2900, -450, 60, 45, 0.7); - spawn.bodyRect(915, -1200, 60, 100, 0.95); - spawn.bodyRect(925, -1300, 50, 100, 0.95); - if (Math.random() < 0.9) { - spawn.bodyRect(2300, -1720, 400, 20); - spawn.bodyRect(2590, -1780, 80, 80); - } - spawn.bodyRect(2925, -1100, 25, 250, 0.8); - spawn.bodyRect(3325, -1550, 50, 200, 0.3); - if (Math.random() < 0.8) { - spawn.bodyRect(1400, -75, 200, 75); //block to get up ledge from ground - spawn.bodyRect(1525, -125, 50, 50); //block to get up ledge from ground - } - spawn.bodyRect(1025, -1110, 400, 25, 0.9); //block on far left building - spawn.bodyRect(1425, -1110, 115, 25, 0.9); //block on far left building - spawn.bodyRect(1540, -1110, 300, 25, 0.9); //block on far left building - - spawn.randomSmallMob(1300, -70); - spawn.randomSmallMob(3200, -100); - spawn.randomSmallMob(4450, -100); - spawn.randomSmallMob(2700, -475); - spawn.randomMob(2650, -975, 0.8); - spawn.randomMob(2650, -1550, 0.8); - spawn.randomMob(4150, -200, 0.15); - spawn.randomMob(1700, -1300, 0.2); - spawn.randomMob(1850, -1950, 0.25); - spawn.randomMob(2610, -1880, 0.25); - spawn.randomMob(3350, -950, 0.25); - spawn.randomMob(1690, -2250, 0.25); - spawn.randomMob(2200, -600, 0.2); - spawn.randomMob(850, -1300, 0.25); - spawn.randomMob(-100, -900, -0.2); - spawn.randomBoss(3700, -1500, 0.4); - spawn.randomBoss(1700, -900, 0.4); - if (game.difficulty > 3) spawn.randomLevelBoss(2200, -1300); - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - highrise() { - level.custom = () => { - level.playerExitCheck(); - }; - level.customTopLayer = () => {}; - - level.setPosToSpawn(0, -700); //normal spawn - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - level.exit.x = -4275; - level.exit.y = -2805; - - level.defaultZoom = 1500 - game.zoomTransition(level.defaultZoom) - - powerUps.spawnStartingPowerUps(-2550, -700); - document.body.style.backgroundColor = "#dcdcde" //"#fafcff"; - - spawn.debris(-2325, -1825, 2400); //16 debris per level - spawn.debris(-2625, -600, 600, 5); //16 debris per level - spawn.debris(-2000, -60, 1200, 5); //16 debris per level - - //background - level.fillBG.push({ - x: -4425, - y: -3050, - width: 425, - height: 275, - color: "#cff" - }); - level.fillBG.push({ - x: -3375, - y: -2875, - width: 25, - height: 725, - color: "#d0d0d2" - }); - level.fillBG.push({ - x: -2975, - y: -2750, - width: 25, - height: 600, - color: "#d0d0d2" - }); - level.fillBG.push({ - x: -2475, - y: -2450, - width: 25, - height: 750, - color: "#d0d0d2" - }); - - //3 platforms that lead to exit - spawn.mapRect(-3440, -2875, 155, 25); - spawn.mapRect(-3025, -2775, 125, 25); - spawn.mapRect(-2525, -2475, 125, 25); - spawn.bodyRect(-2600, -2500, 225, 20, 0.7); - spawn.bodyRect(-3350, -2900, 25, 25, 0.5); - spawn.bodyRect(-3400, -2950, 50, 75, 0.5); - - - //foreground - level.fill.push({ - x: -1650, - y: -1575, - width: 550, - height: 425, - color: "rgba(0,0,0,0.12)" - }); - level.fill.push({ - x: -2600, - y: -1675, - width: 450, - height: 1125, - color: "rgba(0,0,0,0.12)" - }); - - level.fill.push({ - x: -3425, - y: -2150, - width: 525, - height: 1550, - color: "rgba(0,0,0,0.12)" - }); - level.fill.push({ - x: -1850, - y: -1150, - width: 2025, - height: 1150, - color: "rgba(0,0,0,0.12)" - }); - - //hidden zone - level.fill.push({ - x: -4450, - y: -955, - width: 1025, - height: 360, - color: "rgba(64,64,64,0.97)" - }); - - powerUps.spawn(-4300, -700, "heal"); - powerUps.spawn(-4200, -700, "ammo"); - powerUps.spawn(-4000, -700, "ammo"); - spawn.mapRect(-4450, -1000, 100, 500); - spawn.bodyRect(-3576, -750, 150, 150); - - //building 1 - spawn.bodyRect(-1000, -675, 25, 25); - spawn.mapRect(-2225, 0, 2475, 150); - spawn.mapRect(175, -1000, 75, 1100); - - spawn.mapRect(-175, -985, 25, 175); - spawn.bodyRect(-170, -810, 14, 160, 1, spawn.propsFriction); //door to starting room - spawn.mapRect(-600, -650, 825, 50); - spawn.mapRect(-1300, -650, 500, 50); - spawn.mapRect(-175, -250, 425, 300); - spawn.bodyRect(-75, -300, 50, 50); - - // spawn.boost(-750, 0, 0, -0.01); - spawn.boost(-750, 0, 1700); - spawn.bodyRect(-425, -1375, 400, 225); - spawn.mapRect(-1125, -1575, 50, 475); - spawn.bodyRect(-1475, -1275, 250, 125); - spawn.bodyRect(-825, -1160, 250, 10); - spawn.mapRect(-1650, -1575, 400, 50); - spawn.mapRect(-600, -1150, 850, 175); - spawn.mapRect(-1850, -1150, 1050, 175); - spawn.bodyRect(-1907, -1600, 550, 25); - if (game.difficulty < 4) { - spawn.bodyRect(-1600, -125, 125, 125); - spawn.bodyRect(-1560, -200, 75, 75); - } else { - spawn.bodyRect(-1200, -125, 125, 125); - spawn.bodyRect(-1160, -200, 75, 75); - } - //building 2 - spawn.mapRect(-4450, -600, 2300, 750); - spawn.mapRect(-2225, -500, 175, 550); - // spawn.mapRect(-2600, -975, 450, 50); - spawn.boost(-2800, -600, 1700); - spawn.mapRect(-3450, -1325, 550, 50); - spawn.mapRect(-3425, -2200, 525, 50); - spawn.mapRect(-2600, -1700, 450, 50); - // spawn.mapRect(-2600, -2450, 450, 50); - spawn.bodyRect(-2275, -2700, 50, 60); - spawn.bodyRect(-2600, -1925, 250, 225); - spawn.bodyRect(-3415, -1425, 100, 100); - spawn.bodyRect(-3400, -1525, 100, 100); - spawn.bodyRect(-3305, -1425, 100, 100); - //building 3 - spawn.mapRect(-4450, -1750, 1025, 1000); - spawn.mapRect(-3750, -2000, 175, 275); - spawn.mapRect(-4000, -2350, 275, 675); - // spawn.mapRect(-4450, -2650, 475, 1000); - spawn.mapRect(-4450, -2775, 475, 1125); - spawn.bodyRect(-3715, -2050, 50, 50); - spawn.bodyRect(-3570, -1800, 50, 50); - spawn.bodyRect(-2970, -2250, 50, 50); - spawn.bodyRect(-3080, -2250, 40, 40); - spawn.bodyRect(-3420, -650, 50, 50); - - - //exit - spawn.mapRect(-4450, -3075, 25, 300); - spawn.mapRect(-4450, -3075, 450, 25); - spawn.mapRect(-4025, -3075, 25, 100); - spawn.mapRect(-4275, -2785, 100, 25); - if (game.difficulty < 4) spawn.bodyRect(-3760, -2400, 50, 50); - - //mobs - spawn.randomMob(-2500, -2700, 1); - spawn.randomMob(-3200, -750, 1); - spawn.randomMob(-1875, -775, 0.2); - spawn.randomMob(-950, -1675, 0.2); - spawn.randomMob(-1525, -1750, 0.2); - spawn.randomMob(-1375, -1400, 0.2); - spawn.randomMob(-1625, -1275, 0.2); - spawn.randomMob(-1900, -1250, 0.2); - spawn.randomMob(-2250, -1850, 0.2); - spawn.randomMob(-2475, -2200, 0.2); - spawn.randomMob(-3000, -1475, 0.2); - spawn.randomMob(-3850, -2500, 0.2); - spawn.randomMob(-3650, -2125, 0.2); - spawn.randomMob(-4010, -3200, 0.2); - spawn.randomMob(-3500, -1825, 0.2); - spawn.randomMob(-975, -100, 0); - spawn.randomMob(-1050, -725, 0.2); - spawn.randomMob(-1525, -100, 0); - spawn.randomMob(-525, -1700, -0.1); - spawn.randomMob(-125, -1500, -0.1); - spawn.randomMob(-325, -1900, -0.1); - spawn.randomMob(-550, -100, -0.1); - spawn.randomBoss(-3250, -2700, 0.2); - spawn.randomBoss(-2450, -1100, 0); - - if (game.difficulty > 3) spawn.randomLevelBoss(-2400, -3000); - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - warehouse() { - level.custom = () => { - level.playerExitCheck(); - }; - level.customTopLayer = () => {}; - - level.setPosToSpawn(25, -55); //normal spawn - level.exit.x = 425; - level.exit.y = -30; - - level.defaultZoom = 1300 - game.zoomTransition(level.defaultZoom) - - spawn.debris(-2250, 1330, 3000, 6); //16 debris per level - spawn.debris(-3000, -800, 3280, 6); //16 debris per level - spawn.debris(-1400, 410, 2300, 5); //16 debris per level - powerUps.spawnStartingPowerUps(25, 500); - document.body.style.backgroundColor = "#dcdcde" //"#f2f5f3"; - - //background - const BGColor = "rgba(0,0,0,0.1)"; - level.fill.push({ - x: -3025, - y: 50, - width: 4125, - height: 1350, - color: BGColor - }); - level.fill.push({ - x: -1800, - y: -500, - width: 1625, - height: 550, - color: BGColor - }); - level.fill.push({ - x: -175, - y: -250, - width: 350, - height: 300, - color: BGColor - }); - level.fill.push({ - x: -2600, - y: -150, - width: 700, - height: 200, - color: BGColor - }); - level.fillBG.push({ - x: 300, - y: -250, - width: 350, - height: 250, - color: "#cff" - }); - spawn.mapRect(-1500, 0, 2750, 100); - spawn.mapRect(175, -270, 125, 300); - spawn.mapRect(-1900, -600, 1775, 100); - spawn.mapRect(-1900, -550, 100, 1250); - //house - spawn.mapRect(-175, -550, 50, 400); - spawn.mapRect(-175, -10, 350, 50); - spawn.mapRect(-25, -20, 100, 50); - - //exit house - spawn.mapRect(300, -10, 350, 50); - spawn.mapRect(-150, -300, 800, 50); - spawn.mapRect(600, -275, 50, 75); - spawn.mapRect(425, -20, 100, 25); - // spawn.mapRect(-1900, 600, 2700, 100); - spawn.mapRect(1100, 0, 150, 1500); - spawn.mapRect(-2850, 1400, 4100, 100); - spawn.mapRect(-2375, 875, 1775, 75); - spawn.mapRect(-1450, 865, 75, 435); - spawn.mapRect(-1450, 662, 75, 100); - spawn.bodyRect(-1418, 773, 11, 102, 1, spawn.propsFriction); //blocking path - spawn.mapRect(-2950, 1250, 175, 250); - spawn.mapRect(-3050, 1100, 150, 400); - spawn.mapRect(-3150, 50, 125, 1450); - spawn.mapRect(-2375, 600, 3175, 100); - spawn.mapRect(-2125, 400, 250, 275); - // spawn.mapRect(-1950, -400, 100, 25); - spawn.mapRect(-3150, 50, 775, 100); - spawn.mapRect(-2600, -250, 775, 100); - spawn.bodyRect(-1450, -125, 125, 125, 1, spawn.propsSlide); //weight - spawn.bodyRect(-1800, 0, 300, 100, 1, spawn.propsHoist); //hoist - cons[cons.length] = Constraint.create({ - pointA: { - x: -1650, - y: -500 - }, - bodyB: body[body.length - 1], - stiffness: 0.0001815, - length: 1 - }); - World.add(engine.world, cons[cons.length - 1]); - - spawn.bodyRect(600, 525, 125, 125, 1, spawn.propsSlide); //weight - spawn.bodyRect(800, 600, 300, 100, 1, spawn.propsHoist); //hoist - cons[cons.length] = Constraint.create({ - pointA: { - x: 950, - y: 100 - }, - bodyB: body[body.length - 1], - stiffness: 0.0001815, - length: 1 - }); - World.add(engine.world, cons[cons.length - 1]); - - spawn.bodyRect(-2700, 1150, 100, 160, 1, spawn.propsSlide); //weight - spawn.bodyRect(-2550, 1150, 200, 100, 1, spawn.propsSlide); //weight - spawn.bodyRect(-2775, 1300, 400, 100, 1, spawn.propsHoist); //hoist - cons[cons.length] = Constraint.create({ - pointA: { - x: -2575, - y: 150 - }, - bodyB: body[body.length - 1], - stiffness: 0.0005, - length: 566 - }); - World.add(engine.world, cons[cons.length - 1]); - - //blocks - spawn.bodyRect(-165, -150, 30, 35, 1); - spawn.bodyRect(-165, -115, 30, 35, 1); - spawn.bodyRect(-165, -80, 30, 35, 1); - spawn.bodyRect(-165, -45, 30, 35, 1); - - spawn.bodyRect(-750, 400, 150, 150, 0.5); - spawn.bodyRect(-400, 1175, 100, 250, 1); //block to get to top path on bottom level - - spawn.bodyRect(-2525, -50, 145, 100, 0.5); - spawn.bodyRect(-2325, -300, 150, 100, 0.5); - spawn.bodyRect(-1275, -750, 200, 150, 0.5); //roof block - spawn.bodyRect(-525, -700, 125, 100, 0.5); //roof block - - //mobs - spawn.randomSmallMob(-1125, 550); - spawn.randomSmallMob(-2325, 800); - spawn.randomSmallMob(-2950, -50); - spawn.randomSmallMob(825, 300); - spawn.randomSmallMob(-900, 825); - spawn.randomMob(-2025, 175, 0.6); - spawn.randomMob(-2325, 450, 0.6); - spawn.randomMob(-2925, 675, 0.5); - spawn.randomMob(-2700, 300, 0.2); - spawn.randomMob(-2500, 300, 0.2); - spawn.randomMob(-2075, -425, 0.2); - spawn.randomMob(-1550, -725, 0.2); - spawn.randomMob(375, 1100, 0.1); - spawn.randomMob(-1425, -100, 0.1); - spawn.randomMob(-800, -750, 0); - spawn.randomMob(400, -350, 0); - spawn.randomMob(650, 1300, 0); - spawn.randomMob(-750, -150, 0); - spawn.randomMob(475, 300, 0); - spawn.randomMob(-75, -700, 0); - spawn.randomMob(900, -200, -0.1); - spawn.randomBoss(-125, 275, -0.2); - spawn.randomBoss(-825, 1000, 0.2); - spawn.randomBoss(-1300, -1100, -0.3); - - if (game.difficulty > 3) { - if (Math.random() < 0.1) { - spawn.randomLevelBoss(-800, -1300) - } else { - spawn.snakeBoss(-1300 + Math.random() * 2000, -2200); //boss snake with head - } - } - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - office() { - let button, door - if (Math.random() < 0.75) { //normal direction start in top left - button = level.button(525, 0) - door = level.door(1362, -200, 25, 200, 195) - level.setPosToSpawn(1375, -1550); //normal spawn - level.exit.x = 3250; - level.exit.y = -530; - // spawn.randomSmallMob(3550, -550); - level.fillBG.push({ - x: 3050, - y: -950, - width: 625, - height: 500, - color: "#dff" - }); - } else { //reverse direction, start in bottom right - button = level.button(4300, 0) - door = level.door(3012, -200, 25, 200, 195) - level.setPosToSpawn(3250, -550); //normal spawn - level.exit.x = 1375; - level.exit.y = -1530; - // spawn.bodyRect(3655, -650, 40, 150); //door - level.fillBG.push({ - x: 725, - y: -1950, - width: 825, - height: 450, - color: "#dff" - }); - } - - - level.custom = () => { - button.query(); - button.draw(); - if (button.isUp) { - door.isOpen = true - } else { - door.isOpen = false - } - door.openClose(); - level.playerExitCheck(); - }; - level.customTopLayer = () => { - door.draw(); - }; - - level.defaultZoom = 1400 - game.zoomTransition(level.defaultZoom) - spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 50); //ground bump wall - - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - - document.body.style.backgroundColor = "#e0e5e0"; - - // foreground - level.fill.push({ - x: -550, - y: -1700, - width: 1300, - height: 1700, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 750, - y: -1450, - width: 650, - height: 1450, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 750, - y: -1950, - width: 800, - height: 450, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 3000, - y: -1000, - width: 650, - height: 1000, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 3650, - y: -1300, - width: 1300, - height: 1300, - color: "rgba(0,0,0,0.1)" - }); - - spawn.debris(-300, -200, 1000, 4); //ground debris //16 debris per level - spawn.debris(3500, -200, 800, 4); //ground debris //16 debris per level - spawn.debris(-300, -650, 1200, 4); //1st floor debris //16 debris per level - spawn.debris(3500, -650, 800, 5); //1st floor debris //16 debris per level - powerUps.spawnStartingPowerUps(-525, -700); - - spawn.mapRect(-600, 0, 2000, 325); //ground - spawn.mapRect(1400, 25, 1600, 300); //ground - spawn.mapRect(3000, 0, 2000, 325); //ground - spawn.mapRect(-600, -1700, 50, 2000 - 100); //left wall - spawn.bodyRect(-295, -1540, 40, 40); //center block under wall - spawn.bodyRect(-298, -1580, 40, 40); //center block under wall - spawn.bodyRect(1500, -1540, 30, 30); //left of entrance - spawn.mapRect(1550, -2000, 50, 550); //right wall - spawn.mapRect(1350, -2000 + 505, 50, 1295); //right wall - spawn.mapRect(-600, -2000 + 250, 2000 - 700, 50); //roof left - spawn.mapRect(-600 + 1300, -2000, 50, 300); //right roof wall - spawn.mapRect(-600 + 1300, -2000, 900, 50); //center wall - - map[map.length] = Bodies.polygon(725, -1700, 0, 15); //circle above door - spawn.bodyRect(720, -1675, 15, 170, 1, spawn.propsDoor); // door - body[body.length - 1].isNotHoldable = true; - //makes door swing - consBB[consBB.length] = Constraint.create({ - bodyA: body[body.length - 1], - pointA: { - x: 0, - y: -90 - }, - bodyB: map[map.length - 1], - stiffness: 1 - }); - World.add(engine.world, consBB[consBB.length - 1]); - spawn.mapRect(-600 + 300, -2000 * 0.75, 1900, 50); //3rd floor - spawn.mapRect(-600 + 2000 * 0.7, -2000 * 0.74, 50, 375); //center wall - spawn.bodyRect(-600 + 2000 * 0.7, -2000 * 0.5 - 106, 50, 106); //center block under wall - spawn.mapRect(-600, -1000, 1100, 50); //2nd floor - spawn.mapRect(600, -1000, 500, 50); //2nd floor - spawn.spawnStairs(-600, -1000, 4, 250, 350); //stairs 2nd - spawn.mapRect(375, -600, 350, 150); //center table - spawn.mapRect(-600 + 300, -2000 * 0.25, 2000 - 300, 50); //1st floor - spawn.spawnStairs(-600 + 2000 - 50, -500, 4, 250, 350, true); //stairs 1st - spawn.spawnStairs(-600, 0, 4, 250, 350); //stairs ground - spawn.bodyRect(700, -200, 100, 100); //center block under wall - spawn.bodyRect(700, -300, 100, 100); //center block under wall - spawn.bodyRect(700, -400, 100, 100); //center block under wall - spawn.mapRect(1390, 13, 30, 20); //step left - spawn.mapRect(2980, 13, 30, 20); //step right - spawn.bodyRect(4250, -700, 50, 100); - spawn.mapRect(3000, -1000, 50, 800); //left wall - spawn.mapRect(3000 + 2000 - 50, -1300, 50, 1100); //right wall - spawn.mapRect(4150, -600, 350, 150); //table - spawn.mapRect(3650, -1300, 50, 650); //exit wall - spawn.mapRect(3650, -1300, 1350, 50); //exit wall - spawn.bodyRect(3665, -650, 20, 150); //door - - - spawn.mapRect(3000, -2000 * 0.5, 700, 50); //exit roof - spawn.mapRect(3000, -2000 * 0.25, 2000 - 300, 50); //1st floor - spawn.spawnStairs(3000 + 2000 - 50, 0, 4, 250, 350, true); //stairs ground - - spawn.randomSmallMob(4575, -560, 1); - spawn.randomSmallMob(1315, -880, 1); - spawn.randomSmallMob(800, -600); - spawn.randomSmallMob(-100, -1600); - spawn.randomMob(4100, -225, 0.8); - spawn.randomMob(-250, -700, 0.8); - spawn.randomMob(4500, -225, 0.15); - spawn.randomMob(3250, -225, 0.15); - spawn.randomMob(-100, -225, 0.1); - spawn.randomMob(1150, -225, 0.15); - spawn.randomMob(2000, -225, 0.15); - spawn.randomMob(450, -225, 0.15); - spawn.randomMob(100, -1200, 1); - spawn.randomMob(950, -1150, -0.1); - spawn.randomBoss(1800, -800, -0.2); - spawn.randomBoss(4150, -1000, 0.6); - - if (game.difficulty > 3) { - if (Math.random() < 0.65) { - // tether ball - level.fillBG.push({ - x: 2495, - y: -500, - width: 10, - height: 525, - color: "#ccc" - }); - spawn.tetherBoss(2850, -80) + spawn.bodyRect(600, 525, 125, 125, 1, spawn.propsSlide); //weight + spawn.bodyRect(800, 600, 300, 100, 1, spawn.propsHoist); //hoist cons[cons.length] = Constraint.create({ - pointA: { - x: 2500, - y: -500 - }, - bodyB: mob[mob.length - 1], - stiffness: 0.00012 + pointA: { + x: 950, + y: 100 + }, + bodyB: body[body.length - 1], + stiffness: 0.0001815, + length: 1 + }); + World.add(engine.world, cons[cons.length - 1]); + + spawn.bodyRect(-2700, 1150, 100, 160, 1, spawn.propsSlide); //weight + spawn.bodyRect(-2550, 1150, 200, 100, 1, spawn.propsSlide); //weight + spawn.bodyRect(-2775, 1300, 400, 100, 1, spawn.propsHoist); //hoist + cons[cons.length] = Constraint.create({ + pointA: { + x: -2575, + y: 150 + }, + bodyB: body[body.length - 1], + stiffness: 0.0005, + length: 566 + }); + World.add(engine.world, cons[cons.length - 1]); + + //blocks + spawn.bodyRect(-165, -150, 30, 35, 1); + spawn.bodyRect(-165, -115, 30, 35, 1); + spawn.bodyRect(-165, -80, 30, 35, 1); + spawn.bodyRect(-165, -45, 30, 35, 1); + + spawn.bodyRect(-750, 400, 150, 150, 0.5); + spawn.bodyRect(-400, 1175, 100, 250, 1); //block to get to top path on bottom level + + spawn.bodyRect(-2525, -50, 145, 100, 0.5); + spawn.bodyRect(-2325, -300, 150, 100, 0.5); + spawn.bodyRect(-1275, -750, 200, 150, 0.5); //roof block + spawn.bodyRect(-525, -700, 125, 100, 0.5); //roof block + + //mobs + spawn.randomSmallMob(-1125, 550); + spawn.randomSmallMob(-2325, 800); + spawn.randomSmallMob(-2950, -50); + spawn.randomSmallMob(825, 300); + spawn.randomSmallMob(-900, 825); + spawn.randomMob(-2025, 175, 0.6); + spawn.randomMob(-2325, 450, 0.6); + spawn.randomMob(-2925, 675, 0.5); + spawn.randomMob(-2700, 300, 0.2); + spawn.randomMob(-2500, 300, 0.2); + spawn.randomMob(-2075, -425, 0.2); + spawn.randomMob(-1550, -725, 0.2); + spawn.randomMob(375, 1100, 0.1); + spawn.randomMob(-1425, -100, 0.1); + spawn.randomMob(-800, -750, 0); + spawn.randomMob(400, -350, 0); + spawn.randomMob(650, 1300, 0); + spawn.randomMob(-750, -150, 0); + spawn.randomMob(475, 300, 0); + spawn.randomMob(-75, -700, 0); + spawn.randomMob(900, -200, -0.1); + spawn.randomBoss(-125, 275, -0.2); + spawn.randomBoss(-825, 1000, 0.2); + spawn.randomBoss(-1300, -1100, -0.3); + + if (game.difficulty > 3) { + if (Math.random() < 0.1) { + spawn.randomLevelBoss(-800, -1300) + } else { + spawn.snakeBoss(-1300 + Math.random() * 2000, -2200); //boss snake with head + } + } + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + office() { + level.bossKilled = false; // if a boss needs to be killed + let button, door + if (Math.random() < 0.75) { //normal direction start in top left + button = level.button(525, 0) + door = level.door(1362, -200, 25, 200, 195) + level.setPosToSpawn(1375, -1550); //normal spawn + level.exit.x = 3250; + level.exit.y = -530; + // spawn.randomSmallMob(3550, -550); + level.fillBG.push({ + x: 3050, + y: -950, + width: 625, + height: 500, + color: "#dff" + }); + } else { //reverse direction, start in bottom right + button = level.button(4300, 0) + door = level.door(3012, -200, 25, 200, 195) + level.setPosToSpawn(3250, -550); //normal spawn + level.exit.x = 1375; + level.exit.y = -1530; + // spawn.bodyRect(3655, -650, 40, 150); //door + level.fillBG.push({ + x: 725, + y: -1950, + width: 825, + height: 450, + color: "#dff" + }); + } + + + level.custom = () => { + button.query(); + button.draw(); + if (button.isUp) { + door.isOpen = true + } else { + door.isOpen = false + } + door.openClose(); + level.playerExitCheck(); + }; + level.customTopLayer = () => { + door.draw(); + }; + + level.defaultZoom = 1400 + game.zoomTransition(level.defaultZoom) + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 50); //ground bump wall + + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + + document.body.style.backgroundColor = "#e0e5e0"; + + // foreground + level.fill.push({ + x: -550, + y: -1700, + width: 1300, + height: 1700, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 750, + y: -1450, + width: 650, + height: 1450, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 750, + y: -1950, + width: 800, + height: 450, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3000, + y: -1000, + width: 650, + height: 1000, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3650, + y: -1300, + width: 1300, + height: 1300, + color: "rgba(0,0,0,0.1)" + }); + + spawn.debris(-300, -200, 1000, 4); //ground debris //16 debris per level + spawn.debris(3500, -200, 800, 4); //ground debris //16 debris per level + spawn.debris(-300, -650, 1200, 4); //1st floor debris //16 debris per level + spawn.debris(3500, -650, 800, 5); //1st floor debris //16 debris per level + powerUps.spawnStartingPowerUps(-525, -700); + + spawn.mapRect(-600, 0, 2000, 325); //ground + spawn.mapRect(1400, 25, 1600, 300); //ground + spawn.mapRect(3000, 0, 2000, 325); //ground + spawn.mapRect(-600, -1700, 50, 2000 - 100); //left wall + spawn.bodyRect(-295, -1540, 40, 40); //center block under wall + spawn.bodyRect(-298, -1580, 40, 40); //center block under wall + spawn.bodyRect(1500, -1540, 30, 30); //left of entrance + spawn.mapRect(1550, -2000, 50, 550); //right wall + spawn.mapRect(1350, -2000 + 505, 50, 1295); //right wall + spawn.mapRect(-600, -2000 + 250, 2000 - 700, 50); //roof left + spawn.mapRect(-600 + 1300, -2000, 50, 300); //right roof wall + spawn.mapRect(-600 + 1300, -2000, 900, 50); //center wall + + map[map.length] = Bodies.polygon(725, -1700, 0, 15); //circle above door + spawn.bodyRect(720, -1675, 15, 170, 1, spawn.propsDoor); // door + body[body.length - 1].isNotHoldable = true; + //makes door swing + consBB[consBB.length] = Constraint.create({ + bodyA: body[body.length - 1], + pointA: { + x: 0, + y: -90 + }, + bodyB: map[map.length - 1], + stiffness: 1 + }); + World.add(engine.world, consBB[consBB.length - 1]); + spawn.mapRect(-600 + 300, -2000 * 0.75, 1900, 50); //3rd floor + spawn.mapRect(-600 + 2000 * 0.7, -2000 * 0.74, 50, 375); //center wall + spawn.bodyRect(-600 + 2000 * 0.7, -2000 * 0.5 - 106, 50, 106); //center block under wall + spawn.mapRect(-600, -1000, 1100, 50); //2nd floor + spawn.mapRect(600, -1000, 500, 50); //2nd floor + spawn.spawnStairs(-600, -1000, 4, 250, 350); //stairs 2nd + spawn.mapRect(375, -600, 350, 150); //center table + spawn.mapRect(-600 + 300, -2000 * 0.25, 2000 - 300, 50); //1st floor + spawn.spawnStairs(-600 + 2000 - 50, -500, 4, 250, 350, true); //stairs 1st + spawn.spawnStairs(-600, 0, 4, 250, 350); //stairs ground + spawn.bodyRect(700, -200, 100, 100); //center block under wall + spawn.bodyRect(700, -300, 100, 100); //center block under wall + spawn.bodyRect(700, -400, 100, 100); //center block under wall + spawn.mapRect(1390, 13, 30, 20); //step left + spawn.mapRect(2980, 13, 30, 20); //step right + spawn.bodyRect(4250, -700, 50, 100); + spawn.mapRect(3000, -1000, 50, 800); //left wall + spawn.mapRect(3000 + 2000 - 50, -1300, 50, 1100); //right wall + spawn.mapRect(4150, -600, 350, 150); //table + spawn.mapRect(3650, -1300, 50, 650); //exit wall + spawn.mapRect(3650, -1300, 1350, 50); //exit wall + spawn.bodyRect(3665, -650, 20, 150); //door + + + spawn.mapRect(3000, -2000 * 0.5, 700, 50); //exit roof + spawn.mapRect(3000, -2000 * 0.25, 2000 - 300, 50); //1st floor + spawn.spawnStairs(3000 + 2000 - 50, 0, 4, 250, 350, true); //stairs ground + + spawn.randomSmallMob(4575, -560, 1); + spawn.randomSmallMob(1315, -880, 1); + spawn.randomSmallMob(800, -600); + spawn.randomSmallMob(-100, -1600); + spawn.randomMob(4100, -225, 0.8); + spawn.randomMob(-250, -700, 0.8); + spawn.randomMob(4500, -225, 0.15); + spawn.randomMob(3250, -225, 0.15); + spawn.randomMob(-100, -225, 0.1); + spawn.randomMob(1150, -225, 0.15); + spawn.randomMob(2000, -225, 0.15); + spawn.randomMob(450, -225, 0.15); + spawn.randomMob(100, -1200, 1); + spawn.randomMob(950, -1150, -0.1); + spawn.randomBoss(1800, -800, -0.2); + spawn.randomBoss(4150, -1000, 0.6); + + if (game.difficulty > 3) { + if (Math.random() < 0.65) { + // tether ball + level.fillBG.push({ + x: 2495, + y: -500, + width: 10, + height: 525, + color: "#ccc" + }); + spawn.tetherBoss(2850, -80) + cons[cons.length] = Constraint.create({ + pointA: { + x: 2500, + y: -500 + }, + bodyB: mob[mob.length - 1], + stiffness: 0.00012 + }); + World.add(engine.world, cons[cons.length - 1]); + //chance to spawn a ring of exploding mobs around this boss + if (game.difficulty > 6) spawn.nodeBoss(2850, -80, "spawns", 8, 20, 105); + } else { + spawn.randomLevelBoss(2200, -650) + } + } + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + stronghold() { // player made level by Francois 👑 from discord + level.custom = () => { + level.playerExitCheck(); + }; + level.customTopLayer = () => {}; + + level.setPosToSpawn(1900, -40); //normal spawn + level.exit.x = -350; + level.exit.y = -1250; + + level.defaultZoom = 1400 + game.zoomTransition(level.defaultZoom) + + spawn.mapRect(level.exit.x, level.exit.y + 25, 100, 20); //exit bump + spawn.debris(3800, -1480, 300, 12); + spawn.debris(3600, -1130, 200, 2); + document.body.style.backgroundColor = "#dbdcde"; + // game.draw.mapFill = "#444" + // game.draw.bodyFill = "rgba(140,140,140,0.85)" + // game.draw.bodyStroke = "#222" + + level.fillBG.push({ + x: -500, + y: -1220, + width: 550, + height: -480, + color: "#edf9f9" + }); + level.fillBG.push({ + x: 0, + y: -700, + width: 1050, + height: 700, + color: "rgba(0,0,0,0.1)" + }); + level.fillBG.push({ + x: -550, + y: -1170, + width: 550, + height: 1170, + color: "rgba(0,0,0,0.1)" + }); + + level.fillBG.push({ + x: 1150, + y: -1700, + width: 250, + height: 1700, + color: "rgba(0,0,0,0.1)" + }); + level.fillBG.push({ + x: 1100, + y: -1700, + width: 50, + height: 450, + color: "rgba(0,0,0,0.1)" + }); + level.fillBG.push({ + x: 1050, + y: -1200, + width: 100, + height: 1200, + color: "rgba(0,0,0,0.1)" + }); + level.fillBG.push({ + x: 1400, + y: -250, + width: 200, + height: -1500, + color: "rgba(0,0,0,0.1)" + }); + level.fillBG.push({ + x: 1600, + y: -550, + width: 600, + height: -1150, + color: "rgba(0,0,0,0.1)" + }); + level.fillBG.push({ + x: 2530, + y: -550, + width: 430, + height: -1450, + color: "rgba(0,0,0,0.1)" + }); + level.fillBG.push({ + x: 3270, + y: -1700, + width: 80, + height: 600, + color: "rgba(0,0,0,0.1)" + }); + level.fillBG.push({ + x: 3350, + y: -1350, + width: 700, + height: 230, + color: "rgba(0,0,0,0.1)" + }); + + level.fillBG.push({ + x: 4050, + y: -1700, + width: 600, + height: 1290, + color: "rgba(0,0,0,0.1)" + }); + level.fillBG.push({ + x: 3650, + y: -110, + width: 1000, + height: 170, + color: "rgba(0,0,0,0.1)" + }); + + + // __________________________________________________________________________________________________ + // Spawn Box + spawn.mapRect(1600, -500, 50, 500); //Left Wall + spawn.mapRect(1600, -550, 1500, 50); //Roof + spawn.mapRect(2300, -500, 50, 300); //Right Wall + + spawn.mapRect(-550, 0, 4300, 200); //ground + spawn.mapRect(3700, 55, 1300, 145); //2nd ground + spawn.mapRect(5000, 0, 50, 200); //Last small part of the ground + spawn.mapRect(3100, -1070, 50, 570); // vertical 2nd roof + spawn.mapRect(3100, -1120, 950, 50); // Horizontal 2nd Roof + spawn.mapRect(4050, -1750, 600, 50); // Roof after lift + spawn.mapRect(4600, -1700, 50, 100); // Petit retour de toit, après ascenseur + + //Spawn "Upstairs" + spawn.mapRect(3650, -160, 400, 50); //Thin Walk + spawn.mapRect(4050, -410, 600, 300); //Large staircase block + spawn.mapRect(4600, -1120, 50, 710); //Left Wall Wall upstairs + spawn.mapRect(4550, -1170, 100, 50); //Bloque ascenseur + spawn.mapVertex(3700, 35, "0 0 450 0 300 -60 150 -60"); //first slope + spawn.mapVertex(4850, 35, "0 0 370 0 370 -65 150 -65"); //second slope + spawn.boost(4865, 0, 1800); // right boost + spawn.bodyRect(3950, -280, 170, 120); //Bloc Marche Pour Monter À Ascenseur + // spawn.bodyRect(-2700, 1150, 100, 160, 1, spawn.propsSlide); //weight + // spawn.bodyRect(-2550, 1150, 200, 100, 1, spawn.propsSlide); //weight + spawn.bodyRect(4050, -500, 275, 100, 1, spawn.propsSlide); //weight + spawn.bodyRect(4235, -500, 275, 100, 1, spawn.propsSlide); //weight + // spawn.bodyRect(-2775, 1300, 400, 100, 1, spawn.propsHoist); //hoist + spawn.bodyRect(4025, -450, 550, 100, 1, spawn.propsHoist); //hoist + cons[cons.length] = Constraint.create({ + pointA: { + x: 4325, + y: -1700, + }, + bodyB: body[body.length - 1], + stiffness: 0.0002, //1217, + length: 200 + }); + World.add(engine.world, cons[cons.length - 1]); + + spawn.bodyRect(2799, -870, 310, 290); //Gros bloc angle toit + spawn.mapRect(4000, -1750, 50, 400); //Right Wall Cuve + spawn.mapRect(3400, -1400, 600, 50); // Bottom Cuve + spawn.mapRect(3350, -1750, 50, 400); // Left Wall Cuve + spawn.bodyRect(3400, -1470, 110, 70); //Moyen bloc dans la cuve + spawn.mapRect(3270, -1750, 80, 50); // Rebord gauche cuve + + spawn.mapRect(2530, -2000, 430, 50); //First Plateforme + spawn.mapRect(1600, -1750, 600, 50); // Middle plateforme + spawn.mapRect(1100, -1750, 300, 50); //Derniere plateforme // Toit petite boite en [ + spawn.bodyRect(1830, -1980, 190, 230); // Fat bloc plateforme middle + spawn.bodyRect(1380, -1770, 250, 20) // Pont last plateforme + + spawn.mapRect(1000, -1250, 400, 50); //Sol de la petite boite en [ + spawn.mapRect(1100, -1550, 50, 190); //Mur gauche petite boite en [ + spawn.bodyRect(1100, -1380, 48, 109); //Bloc-porte petite boite en [ + + spawn.mapRect(-100, -750, 1100, 50); //Sol last salle + spawn.mapRect(1000, -1200, 50, 500) // Mur droit last salle + spawn.mapRect(50, -1550, 1050, 50); // Toit last salle + spawn.bodyRect(1, -900, 48, 150); //Bloc porte last salle + spawn.mapRect(0, -1170, 50, 270); //Mur gauche en bas last salle + spawn.bodyRect(920, -900, 120, 120); //Gros bloc last salle + + spawn.mapRect(0, -1700, 50, 320); // Mur droit salle exit / Mur gauche last salle + spawn.mapRect(-550, -1220, 600, 50); // Sol exit room + spawn.mapRect(-500, -1750, 550, 50); // Toit exit room + spawn.mapRect(-550, -1750, 50, 530); // Mur gauche exit room + spawn.bodyRect(-503, -1250, 30, 30); // Petit bloc exit room + + spawn.mapRect(500, -700, 100, 590); //Bloc noir un dessous last salle + spawn.mapRect(1350, -250, 250, 250); //Black Block left from the spawn + spawn.boost(1470, -250, 1080); + + spawn.boost(-370, 0, 800); + + map[map.length] = Bodies.polygon(2325, -205, 0, 15); //circle above door + spawn.bodyRect(2325, -180, 15, 170, 1, spawn.propsDoor); // door + body[body.length - 1].isNotHoldable = true; + //makes door swing + consBB[consBB.length] = Constraint.create({ + bodyA: body[body.length - 1], + pointA: { + x: 0, + y: -90 + }, + bodyB: map[map.length - 1], + stiffness: 1 + }); + World.add(engine.world, consBB[consBB.length - 1]); + spawn.bodyRect(650, 50, 70, 50); + spawn.bodyRect(300, 0, 100, 60); + spawn.bodyRect(400, 0, 100, 150); + spawn.bodyRect(2545, -50, 70, 50); + spawn.bodyRect(2550, 0, 100, 30); + + spawn.randomSmallMob(200, -1300, 0.5); + spawn.randomSmallMob(300, -1300, 0.9); + spawn.randomSmallMob(470, -650, 1); + spawn.randomSmallMob(1000, -400, 1); + spawn.randomSmallMob(2550, -560, 1); + spawn.randomSmallMob(3350, -900, 1); + spawn.randomSmallMob(3600, -1210, 1); + spawn.randomSmallMob(700, -1950, 0.2); + spawn.randomSmallMob(5050, -550); + spawn.randomMob(-250, -250, 0.8); + spawn.randomMob(-300, -600, 0.6); + spawn.randomMob(350, -900, 0.5); + spawn.randomMob(770, -950, 0.8) + spawn.randomMob(900, -160, 1); + spawn.randomMob(2360, -820, 0.8); + spawn.randomMob(2700, -2020, 0.8); + spawn.randomMob(3050, -1650, 0.8); + spawn.randomMob(3350, -600, 0.8); + spawn.randomMob(4400, -50, 1); + spawn.randomBoss(1500, -1900, 0.5); + spawn.randomBoss(2350, -850, 1); + spawn.randomBoss(100, -450, 0.9); + + if (game.difficulty > 3) spawn.randomLevelBoss(1850, -1400); + powerUps.addRerollToLevel() //needs to run after mobs are spawned + }, + basement() { // player made level by Francois 👑 from discord + let button, door, buttonDoor, buttonPlateformEnd, doorPlateform + let isLevelReversed = Math.random(); + if (isLevelReversed < 0.7) { + isLevelReversed = false; + } else { + isLevelReversed = true; + } + const elevator = level.platform(4545, -200, 110, 30, -20) + const hazard = level.hazard(1675, -1050, 800, 150); + const portal = level.portal({ + x: -620, + y: -257 + }, Math.PI / 2, { //down + x: 500, + y: 2025 + }, -Math.PI / 2) //up + spawn.mapRect(350, 2025, 300, 300); //Bloc portail n°2 + + if (isLevelReversed === false) { /// Normal Spawn + button = level.button(2700, -1150); + level.setPosToSpawn(2600, -2050); //normal spawn + level.exit.x = level.enter.x + 4510; + level.exit.y = level.enter.y + 600; + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + } else { /// Reversed spawn + button = level.button(1450, -1150); + buttonPlateformEnd = level.button(3530, -1150); + buttonDoor = level.button(8033, -3625); + door = level.door(7700, -3905, 25, 184, 184); + doorPlateform = level.door(3200, -1225, 299, 80, 525); + level.setPosToSpawn(7110, -1450); //normal spawn + level.exit.x = level.enter.x - 4510; + level.exit.y = level.enter.y - 600; + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + spawn.mapRect(7675, -3935, 75, 25); + spawn.mapRect(7675, -3715, 75, 25); + spawn.bodyRect(8075, -3675, 50, 25); + } + level.custom = () => { + level.playerExitCheck(); + portal[2].query() + portal[3].query() + button.query(); + button.draw(); + if (isLevelReversed === true) { ///Reversed spawn + buttonDoor.draw(); + buttonDoor.query(); + buttonPlateformEnd.draw(); + buttonPlateformEnd.query(); + // hazard.query(); //bug reported from discord? + if (buttonDoor.isUp) { + door.isOpen = false + } else { + door.isOpen = true + } + door.openClose(); + if (buttonPlateformEnd.isUp) { + doorPlateform.isOpen = true; + } else { + doorPlateform.isOpen = false; + } + door.openClose(); + doorPlateform.openClose(); + } + hazard.level(button.isUp) + }; + + level.customTopLayer = () => { + if (isLevelReversed === true) { + door.draw(); + doorPlateform.draw(); + } + portal[0].draw(); + portal[1].draw(); + portal[2].draw(); + portal[3].draw(); + hazard.draw(); + //elevator + if (elevator.pauseUntilCycle < game.cycle && !mech.isBodiesAsleep) { + if (elevator.plat.position.y > -200) { //bottom + elevator.plat.speed = -20 + elevator.pauseUntilCycle = game.cycle + 90 + } else if (elevator.plat.position.y < -3000) { //top + elevator.plat.speed = 30 + elevator.pauseUntilCycle = game.cycle + 90 + } + elevator.plat.position = { + x: elevator.plat.position.x, + y: elevator.plat.position.y + elevator.plat.speed + } + elevator.pointA = elevator.plat.position + } + }; + + level.defaultZoom = 1300 + game.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#c7c7c7"; + + // GROUND // + spawn.mapRect(-400, -2000, 400, 1430); //Gros left wall + spawn.mapRect(3700, -3000, 700, 2650); //Gros right wall //Puit + spawn.mapRect(-400, -2000, 3700, 250); //Ground + spawn.mapRect(2475, -1150, 1225, 250); + spawn.mapRect(500, -1150, 1175, 250); //Ground level 3 + spawn.mapRect(350, -180, 4600, 1255); // Last ground + spawn.mapRect(-400, -458, 750, 3337); //mur left sous-sol + spawn.mapRect(-2850, -3375, 5300, 1375); + spawn.mapRect(-2850, -4200, 8000, 825); + spawn.mapRect(3700, -3375, 550, 375); + spawn.mapRect(-2850, -5200, 10200, 1000); + spawn.mapRect(5600, -1250, 3550, 2000); + spawn.mapRect(9150, -5200, 1725, 5800); + // SPAWN BOX // + spawn.mapRect(2300, -3375, 950, 1000); + spawn.mapRect(3550, -3375, 150, 1625); + spawn.mapVertex(2020, -791, " 250 250 -860 250 -2200 0 250 0"); //map vertex en haut + spawn.mapVertex(690, -295, "1700 0 -200 0 -200 -284 500 -284"); //map vertex en bas + spawn.mapRect(2950, -900, 750, 250); //Extension ground apres map vertex + if (isLevelReversed === false) { + spawn.mapRect(3250, -1800, 50, 150); //Petit picot en haut, à gauche + spawn.mapRect(3400, -1800, 50, 150); //Petit picot en haut, à droite + spawn.mapRect(3150, -1300, 50, 200) //Petit picot en bas, à gauche + spawn.mapRect(3500, -1300, 50, 200) //Petit picot en bas, à droite + spawn.mapRect(3050, -3375, 500, 1260); + spawn.mapRect(3400, -2265, 150, 515); //Mur fond tunnel + spawn.bodyRect(3625, -1225, 75, 75); //Pitit bloc à droite en bas spawn + } else { + spawn.mapRect(3050, -3375, 500, 1000); + spawn.mapRect(3400, -2400, 150, 650); //Mur fond tunnel + spawn.bodyRect(3425, -1515, 75, 75); //Petit en bas spawn + spawn.mapRect(3200, -1275, 300, 175); + } + + // TRAMPOLING // + if (isLevelReversed === false) { /// Normal spawn + spawn.bodyRect(0, -1000, 500, 120, 1, spawn.propsHoist); //hoist + cons[cons.length] = Constraint.create({ + pointA: { + x: 250, + y: -1750, + }, + bodyB: body[body.length - 1], + stiffness: 0.00014, + length: 120 + }); + World.add(engine.world, cons[cons.length - 1]); + spawn.bodyRect(0, -1250, 240, 190) //Fat cube ascenseur + } else { /// Reversed spawn + spawn.bodyRect(0, -650, 225, 175); + spawn.mapRect(425, -950, 175, 50); + spawn.mapRect(-25, -1150, 100, 50); + } + // PUIT // + spawn.mapVertex(4200, -1810, "0 0 450 0 600 -2500 0 -2500") + spawn.mapVertex(5000, -1809, "0 0 450 0 450 -2500 -150 -2500") + spawn.mapRect(4800, -3000, 800, 5875); //big right Puit + // BOSS AREA // + spawn.mapRect(4800, -3150, 50, 200); //Premiere barriere + spawn.mapRect(5100, -3530, 50, 380); //2nd barriere + spawn.mapRect(5100, -3200, 150, 50); //Marche en dessous mapVertex 1 + spawn.mapVertex(5450, -3650, "220 0 200 30 -200 30 -220 0 -200 -30 200 -30"); + spawn.mapVertex(6225, -3350, "275 0 250 50 -250 50 -275 0 -250 -50 250 -50"); + spawn.mapRect(5600, -3000, 1600, 725); //ground Boss Area + //Ouverture right boss area + spawn.mapRect(7300, -3325, 50, 50); //petite marche pour accéder à l'ouverture + spawn.mapRect(7350, -4075, 850, 50); //Bouche + spawn.mapRect(7400, -4050, 800, 50); //Bouche + spawn.mapRect(7450, -4025, 750, 50); //Bouche + spawn.mapRect(7500, -4000, 700, 50); //Bouche + spawn.mapRect(7550, -3975, 650, 50); //Bouche + spawn.mapRect(7350, -3600, 850, 50); //Bouche + spawn.mapRect(7400, -3625, 800, 50); //Bouche + spawn.mapRect(7450, -3650, 575, 50); //Bouche + spawn.mapRect(7500, -3675, 525, 50); //Bouche + spawn.mapRect(7550, -3700, 475, 50); //Bouche + spawn.boost(8290, -2100, 1800); + //Murs + spawn.mapRect(7350, -5200, 1800, 1125); + spawn.mapRect(8475, -4075, 675, 2825); + spawn.mapRect(7300, -2100, 1175, 850); + spawn.mapRect(7350, -3550, 850, 1275); + //Escaliers + spawn.mapRect(6600, -2100, 200, 75); //escaliers + spawn.mapRect(6750, -2100, 750, 250); //escaliers + spawn.mapRect(6950, -1850, 550, 200); //escaliers + spawn.mapRect(6750, -1400, 750, 150); //escaliers + spawn.mapRect(6550, -1625, 250, 375); //escaliers + spawn.mapRect(6350, -1800, 250, 550); //escaliers + spawn.mapRect(5600, -2275, 800, 1025); //escaliers + // BLOCS + if (isLevelReversed === false) { /// Normal spawn + spawn.bodyRect(1350, -1175, 225, 25); + spawn.bodyRect(1450, -1200, 25, 25); + } else { /// Reversed spawn + spawn.bodyRect(700, -1175, 225, 25); + spawn.bodyRect(800, -1200, 25, 25); + } + spawn.bodyRect(1100, -1375, 225, 225); + spawn.bodyRect(1775, -925, 75, 25); + spawn.bodyRect(2225, -950, 75, 50); + spawn.bodyRect(2000, -1000, 50, 100); + spawn.bodyRect(3100, -1175, 50, 25); + spawn.bodyRect(2200, -375, 50, 50); + spawn.bodyRect(2200, -425, 50, 50); + spawn.bodyRect(2200, -475, 50, 50); + spawn.bodyRect(2200, -525, 50, 50); + spawn.bodyRect(1050, -400, 50, 25); + spawn.mapRect(2200, -650, 50, 125); + spawn.mapRect(2200, -325, 50, 150); + spawn.mapRect(2875, -225, 250, 50); + spawn.mapRect(2050, -1225, 75, 100); //Plateforme over acid + // MOBS + if (isLevelReversed === false) { ///Normal spawn + if (game.difficulty > 2) { + if (Math.random() < 0.2) { + // tether ball + spawn.tetherBoss(7000, -3300) + cons[cons.length] = Constraint.create({ + pointA: { + x: 7300, + y: -3300 + }, + bodyB: mob[mob.length - 1], + stiffness: 0.00006 + }); + World.add(engine.world, cons[cons.length - 1]); + if (game.difficulty > 4) spawn.nodeBoss(7000, -3300, "spawns", 8, 20, 105); + } else if (game.difficulty > 3) { + spawn.randomLevelBoss(6100, -3600, ["shooterBoss", "launcherBoss", "laserTargetingBoss", "spiderBoss", "laserBoss"]); + } + } + } else { /// Reversed spawn + if (game.difficulty > 2) { + if (Math.random() < 0.2) { + // tether ball + spawn.tetherBoss(2300, -1300) + cons[cons.length] = Constraint.create({ + pointA: { + x: 2300, + y: -1750 + }, + bodyB: mob[mob.length - 1], + stiffness: 0.00036 + }); + World.add(engine.world, cons[cons.length - 1]); + if (game.difficulty > 4) spawn.nodeBoss(2350, -1300, "spawns", 8, 20, 105); + } else if (game.difficulty > 3) { + spawn.randomLevelBoss(2300, -1400, ["shooterBoss", "launcherBoss", "laserTargetingBoss", "spiderBoss", "laserBoss", "snakeBoss"]); + } + } + } + spawn.randomSmallMob(100, -1000, 1); + spawn.randomSmallMob(1340, -675, 1); + spawn.randomSmallMob(7000, -3750, 1); + spawn.randomSmallMob(6050, -3200, 1); + spawn.randomMob(1970 + 10 * Math.random(), -1150 + 20 * Math.random(), 1); + spawn.randomMob(3500, -525, 0.8); + spawn.randomMob(6700, -3700, 0.8); + spawn.randomMob(2600, -1300, 0.7); + spawn.randomMob(600, -1250, 0.7); + spawn.randomMob(2450, -250, 0.6); + spawn.randomMob(6200, -3200, 0.6); + spawn.randomMob(900, -700, 0.5); + spawn.randomMob(1960, -400, 0.5); + spawn.randomMob(5430, -3520, 0.5); + spawn.randomMob(400, -700, 0.5); + spawn.randomMob(6500, -4000, 0.4); + spawn.randomMob(3333, -400, 0.4); + spawn.randomMob(3050, -1220, 0.4); + spawn.randomMob(800, 1200, 0.3); + spawn.randomMob(7200, -4000, 0.3); + spawn.randomMob(250, -1550, 0.3); + spawn.randomBoss(900, -1450, 0.3); + spawn.randomBoss(2980, -400, 0.3); + spawn.randomBoss(5750, -3860, 0.4); + spawn.randomBoss(1130, 1300, 0.1); + powerUps.addRerollToLevel() //needs to run after mobs are spawned + powerUps.spawn(1900, -940, "heal"); + powerUps.spawn(3000, -230, "heal"); + powerUps.spawn(5450, -3675, "ammo"); + + // SECRET BOSS AREA // + //hidden zone + level.fill.push({ + x: -750, + y: -900, + width: 750, + height: 450, + color: "rgba(61,62,62,0.95)" + }); + //hidden house + spawn.mapRect(-850, -2000, 600, 1150); //Toit hidden house + spawn.mapRect(-2850, -2000, 2150, 4880); //Mur gauche hidden house + spawn.mapRect(-850, -458, 500, 3340); //Bloc sol hidden house + // + spawn.mapRect(-400, 2025, 3450, 850); //Sol secret boss area + spawn.mapRect(625, 1300, 225, 50); //Plateforme horizontale n°1 + spawn.mapRect(850, 1775, 470, 50); //Plateforme horizontale n°2 + spawn.mapRect(1000, 1625, 100, 150); //Plateforme vertiale n°1 + spawn.mapRect(1400, 1275, 100, 100); //Plateforme carrée + spawn.mapRect(1700, 1675, 75, 450); //Plateforme verticale n°2 + spawn.mapRect(2100, 1375, 450, 50); //Plateforme accroche boss + spawn.mapRect(2900, 900, 175, 325); //Débord de toit droite haut + spawn.mapRect(2900, 1675, 150, 350); //Muret en bas à droite + spawn.mapRect(2900, 1225, 75, 100); //Picot haut entrée salle trésor + spawn.mapRect(2900, 1575, 75, 100); //Picot bas entrée salle trésor + spawn.mapRect(2800, 1575, 100, 25); //Plongeoir sortie salle trésor + spawn.mapRect(3050, 1675, 400, 1200); //Sol sallle trésor + spawn.mapRect(3075, 1075, 375, 150); //Plafond salle trésor + spawn.mapRect(3300, 1075, 1500, 1800); //Mur droite salle trésor + // tether ball + spawn.tetherBoss(2330, 1850) + cons[cons.length] = Constraint.create({ + pointA: { + x: 2330, + y: 1425 + }, + bodyB: mob[mob.length - 1], + stiffness: 0.00017 }); World.add(engine.world, cons[cons.length - 1]); //chance to spawn a ring of exploding mobs around this boss - if (game.difficulty > 6) spawn.nodeBoss(2850, -80, "spawns", 8, 20, 105); - } else { - spawn.randomLevelBoss(2200, -650) - } - } - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - stronghold() { // player made level by Francois 👑 from discord - level.custom = () => { - level.playerExitCheck(); - }; - level.customTopLayer = () => {}; + if (game.difficulty > 4) spawn.nodeBoss(2330, 1850, "spawns", 8, 20, 105); + powerUps.chooseRandomPowerUp(3100, 1630); + }, + detours() { + level.setPosToSpawn(0, 0); //lower start + level.exit.y = 150; + spawn.mapRect(level.enter.x, 45, 100, 20); + level.exit.x = 10625; + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); + level.defaultZoom = 1400; + game.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#d5d5d5"; + const BGColor = "rgba(0,0,0,0.1)"; + level.fill.push({ + x: -150, + y: -250, + width: 625, + height: 325, + color: BGColor + }); + level.fill.push({ + x: 475, + y: -520, + width: 5375, + height: 875, + color: BGColor + }); + level.fill.push({ + x: 5850, + y: -1275, + width: 2800, + height: 2475, + color: BGColor + }); + level.fill.push({ + x: 8650, + y: -500, + width: 1600, + height: 750, + color: BGColor + }); + level.fill.push({ + x: 10250, + y: -700, + width: 900, + height: 950, + color: BGColor + }); + const balance = level.spinner(5500, -412.5, 25, 660) //entrance + const rotor = level.rotor(7000, 580, -0.001); + const doorSortieSalle = level.door(8590, -520, 20, 800, 750) + let buttonSortieSalle + let portalEnBas + let portalEnHaut + let door3isOpen = false; - level.setPosToSpawn(1900, -40); //normal spawn - level.exit.x = -350; - level.exit.y = -1250; - - level.defaultZoom = 1400 - game.zoomTransition(level.defaultZoom) - - spawn.mapRect(level.exit.x, level.exit.y + 25, 100, 20); //exit bump - spawn.debris(3800, -1480, 300, 12); - spawn.debris(3600, -1130, 200, 2); - document.body.style.backgroundColor = "#dbdcde"; - // game.draw.mapFill = "#444" - // game.draw.bodyFill = "rgba(140,140,140,0.85)" - // game.draw.bodyStroke = "#222" - - level.fillBG.push({ - x: -500, - y: -1220, - width: 550, - height: -480, - color: "#edf9f9" - }); - level.fillBG.push({ - x: 0, - y: -700, - width: 1050, - height: 700, - color: "rgba(0,0,0,0.1)" - }); - level.fillBG.push({ - x: -550, - y: -1170, - width: 550, - height: 1170, - color: "rgba(0,0,0,0.1)" - }); - - level.fillBG.push({ - x: 1150, - y: -1700, - width: 250, - height: 1700, - color: "rgba(0,0,0,0.1)" - }); - level.fillBG.push({ - x: 1100, - y: -1700, - width: 50, - height: 450, - color: "rgba(0,0,0,0.1)" - }); - level.fillBG.push({ - x: 1050, - y: -1200, - width: 100, - height: 1200, - color: "rgba(0,0,0,0.1)" - }); - level.fillBG.push({ - x: 1400, - y: -250, - width: 200, - height: -1500, - color: "rgba(0,0,0,0.1)" - }); - level.fillBG.push({ - x: 1600, - y: -550, - width: 600, - height: -1150, - color: "rgba(0,0,0,0.1)" - }); - level.fillBG.push({ - x: 2530, - y: -550, - width: 430, - height: -1450, - color: "rgba(0,0,0,0.1)" - }); - level.fillBG.push({ - x: 3270, - y: -1700, - width: 80, - height: 600, - color: "rgba(0,0,0,0.1)" - }); - level.fillBG.push({ - x: 3350, - y: -1350, - width: 700, - height: 230, - color: "rgba(0,0,0,0.1)" - }); - - level.fillBG.push({ - x: 4050, - y: -1700, - width: 600, - height: 1290, - color: "rgba(0,0,0,0.1)" - }); - level.fillBG.push({ - x: 3650, - y: -110, - width: 1000, - height: 170, - color: "rgba(0,0,0,0.1)" - }); - - - // __________________________________________________________________________________________________ - // Spawn Box - spawn.mapRect(1600, -500, 50, 500); //Left Wall - spawn.mapRect(1600, -550, 1500, 50); //Roof - spawn.mapRect(2300, -500, 50, 300); //Right Wall - - spawn.mapRect(-550, 0, 4300, 200); //ground - spawn.mapRect(3700, 55, 1300, 145); //2nd ground - spawn.mapRect(5000, 0, 50, 200); //Last small part of the ground - spawn.mapRect(3100, -1070, 50, 570); // vertical 2nd roof - spawn.mapRect(3100, -1120, 950, 50); // Horizontal 2nd Roof - spawn.mapRect(4050, -1750, 600, 50); // Roof after lift - spawn.mapRect(4600, -1700, 50, 100); // Petit retour de toit, après ascenseur - - //Spawn "Upstairs" - spawn.mapRect(3650, -160, 400, 50); //Thin Walk - spawn.mapRect(4050, -410, 600, 300); //Large staircase block - spawn.mapRect(4600, -1120, 50, 710); //Left Wall Wall upstairs - spawn.mapRect(4550, -1170, 100, 50); //Bloque ascenseur - spawn.mapVertex(3700, 35, "0 0 450 0 300 -60 150 -60"); //first slope - spawn.mapVertex(4850, 35, "0 0 370 0 370 -65 150 -65"); //second slope - spawn.boost(4865, 0, 1800); // right boost - spawn.bodyRect(3950, -280, 170, 120); //Bloc Marche Pour Monter À Ascenseur - // spawn.bodyRect(-2700, 1150, 100, 160, 1, spawn.propsSlide); //weight - // spawn.bodyRect(-2550, 1150, 200, 100, 1, spawn.propsSlide); //weight - spawn.bodyRect(4050, -500, 275, 100, 1, spawn.propsSlide); //weight - spawn.bodyRect(4235, -500, 275, 100, 1, spawn.propsSlide); //weight - // spawn.bodyRect(-2775, 1300, 400, 100, 1, spawn.propsHoist); //hoist - spawn.bodyRect(4025, -450, 550, 100, 1, spawn.propsHoist); //hoist - cons[cons.length] = Constraint.create({ - pointA: { - x: 4325, - y: -1700, - }, - bodyB: body[body.length - 1], - stiffness: 0.0002, //1217, - length: 200 - }); - World.add(engine.world, cons[cons.length - 1]); - - spawn.bodyRect(2799, -870, 310, 290); //Gros bloc angle toit - spawn.mapRect(4000, -1750, 50, 400); //Right Wall Cuve - spawn.mapRect(3400, -1400, 600, 50); // Bottom Cuve - spawn.mapRect(3350, -1750, 50, 400); // Left Wall Cuve - spawn.bodyRect(3400, -1470, 110, 70); //Moyen bloc dans la cuve - spawn.mapRect(3270, -1750, 80, 50); // Rebord gauche cuve - - spawn.mapRect(2530, -2000, 430, 50); //First Plateforme - spawn.mapRect(1600, -1750, 600, 50); // Middle plateforme - spawn.mapRect(1100, -1750, 300, 50); //Derniere plateforme // Toit petite boite en [ - spawn.bodyRect(1830, -1980, 190, 230); // Fat bloc plateforme middle - spawn.bodyRect(1380, -1770, 250, 20) // Pont last plateforme - - spawn.mapRect(1000, -1250, 400, 50); //Sol de la petite boite en [ - spawn.mapRect(1100, -1550, 50, 190); //Mur gauche petite boite en [ - spawn.bodyRect(1100, -1380, 48, 109); //Bloc-porte petite boite en [ - - spawn.mapRect(-100, -750, 1100, 50); //Sol last salle - spawn.mapRect(1000, -1200, 50, 500) // Mur droit last salle - spawn.mapRect(50, -1550, 1050, 50); // Toit last salle - spawn.bodyRect(1, -900, 48, 150); //Bloc porte last salle - spawn.mapRect(0, -1170, 50, 270); //Mur gauche en bas last salle - spawn.bodyRect(920, -900, 120, 120); //Gros bloc last salle - - spawn.mapRect(0, -1700, 50, 320); // Mur droit salle exit / Mur gauche last salle - spawn.mapRect(-550, -1220, 600, 50); // Sol exit room - spawn.mapRect(-500, -1750, 550, 50); // Toit exit room - spawn.mapRect(-550, -1750, 50, 530); // Mur gauche exit room - spawn.bodyRect(-503, -1250, 30, 30); // Petit bloc exit room - - spawn.mapRect(500, -700, 100, 590); //Bloc noir un dessous last salle - spawn.mapRect(1350, -250, 250, 250); //Black Block left from the spawn - spawn.boost(1470, -250, 1080); - - spawn.boost(-370, 0, 800); - - map[map.length] = Bodies.polygon(2325, -205, 0, 15); //circle above door - spawn.bodyRect(2325, -180, 15, 170, 1, spawn.propsDoor); // door - body[body.length - 1].isNotHoldable = true; - //makes door swing - consBB[consBB.length] = Constraint.create({ - bodyA: body[body.length - 1], - pointA: { - x: 0, - y: -90 - }, - bodyB: map[map.length - 1], - stiffness: 1 - }); - World.add(engine.world, consBB[consBB.length - 1]); - spawn.bodyRect(650, 50, 70, 50); - spawn.bodyRect(300, 0, 100, 60); - spawn.bodyRect(400, 0, 100, 150); - spawn.bodyRect(2545, -50, 70, 50); - spawn.bodyRect(2550, 0, 100, 30); - - spawn.randomSmallMob(200, -1300, 0.5); - spawn.randomSmallMob(300, -1300, 0.9); - spawn.randomSmallMob(470, -650, 1); - spawn.randomSmallMob(1000, -400, 1); - spawn.randomSmallMob(2550, -560, 1); - spawn.randomSmallMob(3350, -900, 1); - spawn.randomSmallMob(3600, -1210, 1); - spawn.randomSmallMob(700, -1950, 0.2); - spawn.randomSmallMob(5050, -550); - spawn.randomMob(-250, -250, 0.8); - spawn.randomMob(-300, -600, 0.6); - spawn.randomMob(350, -900, 0.5); - spawn.randomMob(770, -950, 0.8) - spawn.randomMob(900, -160, 1); - spawn.randomMob(2360, -820, 0.8); - spawn.randomMob(2700, -2020, 0.8); - spawn.randomMob(3050, -1650, 0.8); - spawn.randomMob(3350, -600, 0.8); - spawn.randomMob(4400, -50, 1); - spawn.randomBoss(1500, -1900, 0.5); - spawn.randomBoss(2350, -850, 1); - spawn.randomBoss(100, -450, 0.9); - - if (game.difficulty > 3) spawn.randomLevelBoss(1850, -1400); - powerUps.addRerollToLevel() //needs to run after mobs are spawned - }, - basement() { // player made level by Francois 👑 from discord - let button, door, buttonDoor, buttonPlateformEnd, doorPlateform - let isLevelReversed = Math.random(); - if (isLevelReversed < 0.7) { - isLevelReversed = false; - } else { - isLevelReversed = true; - } - const elevator = level.platform(4545, -200, 110, 30, -20) - const hazard = level.hazard(1675, -1050, 800, 150); - const portal = level.portal({ - x: -620, - y: -257 - }, Math.PI / 2, { //down - x: 500, - y: 2025 - }, -Math.PI / 2) //up - spawn.mapRect(350, 2025, 300, 300); //Bloc portail n°2 - - if (isLevelReversed === false) { /// Normal Spawn - button = level.button(2700, -1150); - level.setPosToSpawn(2600, -2050); //normal spawn - level.exit.x = level.enter.x + 4510; - level.exit.y = level.enter.y + 600; - spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - } else { /// Reversed spawn - button = level.button(1450, -1150); - buttonPlateformEnd = level.button(3530, -1150); - buttonDoor = level.button(8033, -3625); - door = level.door(7700, -3905, 25, 184, 184); - doorPlateform = level.door(3200, -1225, 299, 80, 525); - level.setPosToSpawn(7110, -1450); //normal spawn - level.exit.x = level.enter.x - 4510; - level.exit.y = level.enter.y - 600; - spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - spawn.mapRect(7675, -3935, 75, 25); - spawn.mapRect(7675, -3715, 75, 25); - spawn.bodyRect(8075, -3675, 50, 25); - } - level.custom = () => { - level.playerExitCheck(); - portal[2].query() - portal[3].query() - button.query(); - button.draw(); - if (isLevelReversed === true) { ///Reversed spawn - buttonDoor.draw(); - buttonDoor.query(); - buttonPlateformEnd.draw(); - buttonPlateformEnd.query(); - // hazard.query(); //bug reported from discord? - if (buttonDoor.isUp) { - door.isOpen = false - } else { - door.isOpen = true + function drawOnTheMapMapRect(x, y, dx, dy) { + spawn.mapRect(x, y, dx, dy); + len = map.length - 1 + map[len].collisionFilter.category = cat.map; + map[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; + Matter.Body.setStatic(map[len], true); //make static + World.add(engine.world, map[len]); //add to world + game.draw.setPaths() //update map graphics } - door.openClose(); - if (buttonPlateformEnd.isUp) { - doorPlateform.isOpen = true; - } else { - doorPlateform.isOpen = false; + + function drawOnTheMapBodyRect(x, y, dx, dy) { + spawn.bodyRect(x, y, dx, dy); + len = body.length - 1 + body[len].collisionFilter.category = cat.body; + body[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet + World.add(engine.world, body[len]); //add to world + body[len].classType = "body" } - door.openClose(); - doorPlateform.openClose(); - } - hazard.level(button.isUp) - }; - level.customTopLayer = () => { - if (isLevelReversed === true) { - door.draw(); - doorPlateform.draw(); - } - portal[0].draw(); - portal[1].draw(); - portal[2].draw(); - portal[3].draw(); - hazard.draw(); - //elevator - if (elevator.pauseUntilCycle < game.cycle && !mech.isBodiesAsleep) { - if (elevator.plat.position.y > -200) { //bottom - elevator.plat.speed = -20 - elevator.pauseUntilCycle = game.cycle + 90 - } else if (elevator.plat.position.y < -3000) { //top - elevator.plat.speed = 30 - elevator.pauseUntilCycle = game.cycle + 90 + function spawnCouloirEnHaut() { + level.fill.push({ + x: 2575, + y: -1150, + width: 2550, + height: 630, + color: BGColor + }); + level.fill.push({ + x: 1900, + y: -2300, + width: 1650, + height: 1150, + color: BGColor + }); + level.fill.push({ + x: 3550, + y: -1625, + width: 1650, + height: 475, + color: BGColor + }); + drawOnTheMapMapRect(3800, -270, 75, 75); + drawOnTheMapMapRect(3900, -895, 500, 75); + drawOnTheMapMapRect(3900, -1195, 75, 375); + drawOnTheMapMapRect(3525, -1195, 450, 75); + drawOnTheMapMapRect(3525, -1995, 50, 1575); + drawOnTheMapMapRect(3325, -1995, 50, 1575); + drawOnTheMapMapRect(3525, -1670, 1675, 75); + drawOnTheMapMapRect(5100, -1670, 100, 1250); + drawOnTheMapMapRect(1800, -1195, 1575, 75); + drawOnTheMapMapRect(1800, -1520, 375, 400); + drawOnTheMapMapRect(1800, -2370, 100, 1250); + drawOnTheMapMapRect(2375, -1845, 375, 250); + drawOnTheMapMapRect(2700, -1745, 650, 75); + drawOnTheMapMapRect(1800, -2370, 1775, 100); + drawOnTheMapMapRect(3525, -2370, 50, 775); + drawOnTheMapMapRect(4650, -1220, 550, 75); + drawOnTheMapBodyRect(3225, -1845, 100, 100); + drawOnTheMapBodyRect(3575, 1255, 125, 25); + drawOnTheMapBodyRect(2450, 2255, 25, 25); + drawOnTheMapBodyRect(3975, -945, 175, 50); + drawOnTheMapBodyRect(4825, -1295, 50, 75); + drawOnTheMapBodyRect(4850, -720, 250, 200); + drawOnTheMapBodyRect(4050, -970, 25, 25); + drawOnTheMapBodyRect(3075, -1245, 50, 50); + buttonSortieSalle = level.button(3000, -1745) + spawn.mapVertex(3065, -1745, "100 10 -100 10 -70 -10 70 -10"); + len = map.length - 1 + map[len].collisionFilter.category = cat.map; + map[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; + Matter.Body.setStatic(map[len], true); //make static + World.add(engine.world, map[len]); //add to world + game.draw.setPaths() //update map graphics + + portalEnHaut = level.portal({ + x: 3650, + y: -1470 + }, Math.PI / 2, { + x: 3250, + y: -1473 + }, Math.PI / 2) + + spawn.randomSmallMob(2500, -2070 + Math.random(), 1); + spawn.randomSmallMob(5000, -1370, 1); + spawn.randomMob(5000, -645, 0.9); + spawn.randomMob(4050, 970, 0.9); + spawn.randomSmallMob(2800, -1620, 0.7); + spawn.randomMob(2400, -1370, 0.5); + spawn.randomMob(3725, -1320, 0.3); + spawn.randomBoss(2115, -2020, 0.1) + + powerUps.spawn(5000, -1275, "heal"); + levelCustom2(); } - elevator.plat.position = { - x: elevator.plat.position.x, - y: elevator.plat.position.y + elevator.plat.speed - } - elevator.pointA = elevator.plat.position - } - }; - - level.defaultZoom = 1300 - game.zoomTransition(level.defaultZoom) - document.body.style.backgroundColor = "#c7c7c7"; - - // GROUND // - spawn.mapRect(-400, -2000, 400, 1430); //Gros left wall - spawn.mapRect(3700, -3000, 700, 2650); //Gros right wall //Puit - spawn.mapRect(-400, -2000, 3700, 250); //Ground - spawn.mapRect(2475, -1150, 1225, 250); - spawn.mapRect(500, -1150, 1175, 250); //Ground level 3 - spawn.mapRect(350, -180, 4600, 1255); // Last ground - spawn.mapRect(-400, -458, 750, 3337); //mur left sous-sol - spawn.mapRect(-2850, -3375, 5300, 1375); - spawn.mapRect(-2850, -4200, 8000, 825); - spawn.mapRect(3700, -3375, 550, 375); - spawn.mapRect(-2850, -5200, 10200, 1000); - spawn.mapRect(5600, -1250, 3550, 2000); - spawn.mapRect(9150, -5200, 1725, 5800); - // SPAWN BOX // - spawn.mapRect(2300, -3375, 950, 1000); - spawn.mapRect(3550, -3375, 150, 1625); - spawn.mapVertex(2020, -791, " 250 250 -860 250 -2200 0 250 0"); //map vertex en haut - spawn.mapVertex(690, -295, "1700 0 -200 0 -200 -284 500 -284"); //map vertex en bas - spawn.mapRect(2950, -900, 750, 250); //Extension ground apres map vertex - if (isLevelReversed === false) { - spawn.mapRect(3250, -1800, 50, 150); //Petit picot en haut, à gauche - spawn.mapRect(3400, -1800, 50, 150); //Petit picot en haut, à droite - spawn.mapRect(3150, -1300, 50, 200) //Petit picot en bas, à gauche - spawn.mapRect(3500, -1300, 50, 200) //Petit picot en bas, à droite - spawn.mapRect(3050, -3375, 500, 1260); - spawn.mapRect(3400, -2265, 150, 515); //Mur fond tunnel - spawn.bodyRect(3625, -1225, 75, 75); //Pitit bloc à droite en bas spawn - } else { - spawn.mapRect(3050, -3375, 500, 1000); - spawn.mapRect(3400, -2400, 150, 650); //Mur fond tunnel - spawn.bodyRect(3425, -1515, 75, 75); //Petit en bas spawn - spawn.mapRect(3200, -1275, 300, 175); - } - - // TRAMPOLING // - if (isLevelReversed === false) { /// Normal spawn - spawn.bodyRect(0, -1000, 500, 120, 1, spawn.propsHoist); //hoist - cons[cons.length] = Constraint.create({ - pointA: { - x: 250, - y: -1750, - }, - bodyB: body[body.length - 1], - stiffness: 0.00014, - length: 120 - }); - World.add(engine.world, cons[cons.length - 1]); - spawn.bodyRect(0, -1250, 240, 190) //Fat cube ascenseur - } else { /// Reversed spawn - spawn.bodyRect(0, -650, 225, 175); - spawn.mapRect(425, -950, 175, 50); - spawn.mapRect(-25, -1150, 100, 50); - } - // PUIT // - spawn.mapVertex(4200, -1810, "0 0 450 0 600 -2500 0 -2500") - spawn.mapVertex(5000, -1809, "0 0 450 0 450 -2500 -150 -2500") - spawn.mapRect(4800, -3000, 800, 5875); //big right Puit - // BOSS AREA // - spawn.mapRect(4800, -3150, 50, 200); //Premiere barriere - spawn.mapRect(5100, -3530, 50, 380); //2nd barriere - spawn.mapRect(5100, -3200, 150, 50); //Marche en dessous mapVertex 1 - spawn.mapVertex(5450, -3650, "220 0 200 30 -200 30 -220 0 -200 -30 200 -30"); - spawn.mapVertex(6225, -3350, "275 0 250 50 -250 50 -275 0 -250 -50 250 -50"); - spawn.mapRect(5600, -3000, 1600, 725); //ground Boss Area - //Ouverture right boss area - spawn.mapRect(7300, -3325, 50, 50); //petite marche pour accéder à l'ouverture - spawn.mapRect(7350, -4075, 850, 50); //Bouche - spawn.mapRect(7400, -4050, 800, 50); //Bouche - spawn.mapRect(7450, -4025, 750, 50); //Bouche - spawn.mapRect(7500, -4000, 700, 50); //Bouche - spawn.mapRect(7550, -3975, 650, 50); //Bouche - spawn.mapRect(7350, -3600, 850, 50); //Bouche - spawn.mapRect(7400, -3625, 800, 50); //Bouche - spawn.mapRect(7450, -3650, 575, 50); //Bouche - spawn.mapRect(7500, -3675, 525, 50); //Bouche - spawn.mapRect(7550, -3700, 475, 50); //Bouche - spawn.boost(8290, -2100, 1800); - //Murs - spawn.mapRect(7350, -5200, 1800, 1125); - spawn.mapRect(8475, -4075, 675, 2825); - spawn.mapRect(7300, -2100, 1175, 850); - spawn.mapRect(7350, -3550, 850, 1275); - //Escaliers - spawn.mapRect(6600, -2100, 200, 75); //escaliers - spawn.mapRect(6750, -2100, 750, 250); //escaliers - spawn.mapRect(6950, -1850, 550, 200); //escaliers - spawn.mapRect(6750, -1400, 750, 150); //escaliers - spawn.mapRect(6550, -1625, 250, 375); //escaliers - spawn.mapRect(6350, -1800, 250, 550); //escaliers - spawn.mapRect(5600, -2275, 800, 1025); //escaliers - // BLOCS - if (isLevelReversed === false) { /// Normal spawn - spawn.bodyRect(1350, -1175, 225, 25); - spawn.bodyRect(1450, -1200, 25, 25); - } else { /// Reversed spawn - spawn.bodyRect(700, -1175, 225, 25); - spawn.bodyRect(800, -1200, 25, 25); - } - spawn.bodyRect(1100, -1375, 225, 225); - spawn.bodyRect(1775, -925, 75, 25); - spawn.bodyRect(2225, -950, 75, 50); - spawn.bodyRect(2000, -1000, 50, 100); - spawn.bodyRect(3100, -1175, 50, 25); - spawn.bodyRect(2200, -375, 50, 50); - spawn.bodyRect(2200, -425, 50, 50); - spawn.bodyRect(2200, -475, 50, 50); - spawn.bodyRect(2200, -525, 50, 50); - spawn.bodyRect(1050, -400, 50, 25); - spawn.mapRect(2200, -650, 50, 125); - spawn.mapRect(2200, -325, 50, 150); - spawn.mapRect(2875, -225, 250, 50); - spawn.mapRect(2050, -1225, 75, 100); //Plateforme over acid - // MOBS - if (isLevelReversed === false) { ///Normal spawn - if (game.difficulty > 2) { - if (Math.random() < 0.2) { - // tether ball - spawn.tetherBoss(7000, -3300) - cons[cons.length] = Constraint.create({ - pointA: { - x: 7300, - y: -3300 - }, - bodyB: mob[mob.length - 1], - stiffness: 0.00006 - }); - World.add(engine.world, cons[cons.length - 1]); - if (game.difficulty > 4) spawn.nodeBoss(7000, -3300, "spawns", 8, 20, 105); - } else if (game.difficulty > 3) { - spawn.randomLevelBoss(6100, -3600, ["shooterBoss", "launcherBoss", "laserTargetingBoss", "spiderBoss", "laserBoss"]); - } - } - } else { /// Reversed spawn - if (game.difficulty > 2) { - if (Math.random() < 0.2) { - // tether ball - spawn.tetherBoss(2300, -1300) - cons[cons.length] = Constraint.create({ - pointA: { - x: 2300, - y: -1750 - }, - bodyB: mob[mob.length - 1], - stiffness: 0.00036 - }); - World.add(engine.world, cons[cons.length - 1]); - if (game.difficulty > 4) spawn.nodeBoss(2350, -1300, "spawns", 8, 20, 105); - } else if (game.difficulty > 3) { - spawn.randomLevelBoss(2300, -1400, ["shooterBoss", "launcherBoss", "laserTargetingBoss", "spiderBoss", "laserBoss", "snakeBoss"]); - } - } - } - spawn.randomSmallMob(100, -1000, 1); - spawn.randomSmallMob(1340, -675, 1); - spawn.randomSmallMob(7000, -3750, 1); - spawn.randomSmallMob(6050, -3200, 1); - spawn.randomMob(1970 + 10 * Math.random(), -1150 + 20 * Math.random(), 1); - spawn.randomMob(3500, -525, 0.8); - spawn.randomMob(6700, -3700, 0.8); - spawn.randomMob(2600, -1300, 0.7); - spawn.randomMob(600, -1250, 0.7); - spawn.randomMob(2450, -250, 0.6); - spawn.randomMob(6200, -3200, 0.6); - spawn.randomMob(900, -700, 0.5); - spawn.randomMob(1960, -400, 0.5); - spawn.randomMob(5430, -3520, 0.5); - spawn.randomMob(400, -700, 0.5); - spawn.randomMob(6500, -4000, 0.4); - spawn.randomMob(3333, -400, 0.4); - spawn.randomMob(3050, -1220, 0.4); - spawn.randomMob(800, 1200, 0.3); - spawn.randomMob(7200, -4000, 0.3); - spawn.randomMob(250, -1550, 0.3); - spawn.randomBoss(900, -1450, 0.3); - spawn.randomBoss(2980, -400, 0.3); - spawn.randomBoss(5750, -3860, 0.4); - spawn.randomBoss(1130, 1300, 0.1); - powerUps.addRerollToLevel() //needs to run after mobs are spawned - powerUps.spawn(1900, -940, "heal"); - powerUps.spawn(3000, -230, "heal"); - powerUps.spawn(5450, -3675, "ammo"); - - // SECRET BOSS AREA // - //hidden zone - level.fill.push({ - x: -750, - y: -900, - width: 750, - height: 450, - color: "rgba(61,62,62,0.95)" - }); - //hidden house - spawn.mapRect(-850, -2000, 600, 1150); //Toit hidden house - spawn.mapRect(-2850, -2000, 2150, 4880); //Mur gauche hidden house - spawn.mapRect(-850, -458, 500, 3340); //Bloc sol hidden house - // - spawn.mapRect(-400, 2025, 3450, 850); //Sol secret boss area - spawn.mapRect(625, 1300, 225, 50); //Plateforme horizontale n°1 - spawn.mapRect(850, 1775, 470, 50); //Plateforme horizontale n°2 - spawn.mapRect(1000, 1625, 100, 150); //Plateforme vertiale n°1 - spawn.mapRect(1400, 1275, 100, 100); //Plateforme carrée - spawn.mapRect(1700, 1675, 75, 450); //Plateforme verticale n°2 - spawn.mapRect(2100, 1375, 450, 50); //Plateforme accroche boss - spawn.mapRect(2900, 900, 175, 325); //Débord de toit droite haut - spawn.mapRect(2900, 1675, 150, 350); //Muret en bas à droite - spawn.mapRect(2900, 1225, 75, 100); //Picot haut entrée salle trésor - spawn.mapRect(2900, 1575, 75, 100); //Picot bas entrée salle trésor - spawn.mapRect(2800, 1575, 100, 25); //Plongeoir sortie salle trésor - spawn.mapRect(3050, 1675, 400, 1200); //Sol sallle trésor - spawn.mapRect(3075, 1075, 375, 150); //Plafond salle trésor - spawn.mapRect(3300, 1075, 1500, 1800); //Mur droite salle trésor - // tether ball - spawn.tetherBoss(2330, 1850) - cons[cons.length] = Constraint.create({ - pointA: { - x: 2330, - y: 1425 - }, - bodyB: mob[mob.length - 1], - stiffness: 0.00017 - }); - World.add(engine.world, cons[cons.length - 1]); - //chance to spawn a ring of exploding mobs around this boss - if (game.difficulty > 4) spawn.nodeBoss(2330, 1850, "spawns", 8, 20, 105); - powerUps.chooseRandomPowerUp(3100, 1630); - }, - // detours() { - // level.setPosToSpawn(0, 0); //lower start - // level.exit.y = 150; - // spawn.mapRect(level.enter.x, 45, 100, 20); - // level.exit.x = 10625; - // spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); - // level.defaultZoom = 1400; - // game.zoomTransition(level.defaultZoom) - // document.body.style.backgroundColor = "#d5d5d5"; - // const BGColor = "rgba(0,0,0,0.1)"; - // level.fill.push({ - // x: -150, - // y: -250, - // width: 625, - // height: 325, - // color: BGColor - // }); - // level.fill.push({ - // x: 475, - // y: -520, - // width: 5375, - // height: 875, - // color: BGColor - // }); - // level.fill.push({ - // x: 5850, - // y: -1275, - // width: 2800, - // height: 2475, - // color: BGColor - // }); - // level.fill.push({ - // x: 8650, - // y: -500, - // width: 1600, - // height: 750, - // color: BGColor - // }); - // level.fill.push({ - // x: 10250, - // y: -700, - // width: 900, - // height: 950, - // color: BGColor - // }); - // const rotor = level.rotor(7000, 580, -0.001); - // const buttonCouloirEnBas = level.button(5000, 255); - // const door = level.door(5825, -430, 25, 690, 700); - // const doorSortieSalle = level.door(8590, -520, 20, 800, 750) - // let doorPortal1Center - // let isAlreadySpawned = false; - // let door12isOpen = true; - // let door3isOpen = true; - // let buttonPorteSalle - // let buttonSortieSalle - // let portalEnBas - // let portalEnHaut - - // function drawOnTheMapMapRect(x, y, dx, dy) { - // spawn.mapRect(x, y, dx, dy); - // len = map.length - 1 - // map[len].collisionFilter.category = cat.map; - // map[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; - // Matter.Body.setStatic(map[len], true); //make static - // World.add(engine.world, map[len]); //add to world - // game.draw.setPaths() //update map graphics - // } - - // function drawOnTheMapBodyRect(x, y, dx, dy) { - // spawn.bodyRect(x, y, dx, dy); - // len = body.length - 1 - // body[len].collisionFilter.category = cat.body; - // body[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet - // World.add(engine.world, body[len]); //add to world - // body[len].classType = "body" - // } - - // function spawnCouloirEnBas() { - // isAlreadySpawned = true; - // level.fill.push({ - // x: 1950, - // y: 355, - // width: 2025, - // height: 2120, - // color: BGColor - // }); - // drawOnTheMapMapRect(1925, 255, 125, 2250) - // drawOnTheMapMapRect(2575, 255, 1550, 100); - // drawOnTheMapMapRect(2570, 255, 80, 400); - // drawOnTheMapMapRect(1925, 1030, 1325, 75); - // drawOnTheMapMapRect(3900, 255, 100, 2250); - // drawOnTheMapMapRect(1925, 2380, 2075, 125); - // drawOnTheMapMapRect(2600, 1680, 1400, 75); - // drawOnTheMapMapRect(1925, 1880, 225, 625); - // drawOnTheMapMapRect(2050, 1980, 200, 525); - // drawOnTheMapMapRect(2150, 2080, 200, 425); - // drawOnTheMapMapRect(2250, 2180, 200, 325); - // drawOnTheMapMapRect(2350, 2280, 200, 225); - // drawOnTheMapMapRect(3800, 1080, 200, 675); - // drawOnTheMapMapRect(3700, 1180, 200, 575); - // drawOnTheMapMapRect(3600, 1280, 200, 475); - // drawOnTheMapMapRect(3500, 1380, 200, 375); - // drawOnTheMapMapRect(3400, 1480, 200, 275); - // drawOnTheMapMapRect(3300, 1580, 200, 175); - // buttonPorteSalle = level.button(3050, 2380); - // spawn.mapVertex(3115, 2382, "100 10 -100 10 -70 -10 70 -10"); - // len = map.length - 1 - // map[len].collisionFilter.category = cat.map; - // map[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; - // Matter.Body.setStatic(map[len], true); //make static - // World.add(engine.world, map[len]); //add to world - // game.draw.setPaths() //update map graphics - // drawOnTheMapMapRect(3775, 1680, 225, 825); - // drawOnTheMapMapRect(2250, -370, 200, 350); - // drawOnTheMapMapRect(2250, -370, 400, 50); - // drawOnTheMapMapRect(2575, -845, 75, 425); - // drawOnTheMapMapRect(1975, -520, 675, 100); - // drawOnTheMapBodyRect(2950, 1530, 150, 175); - // drawOnTheMapBodyRect(2925, 1665, 25, 25); - // drawOnTheMapBodyRect(2775, 1605, 75, 75); - // drawOnTheMapBodyRect(2050, 905, 125, 125); - // drawOnTheMapBodyRect(2650, 980, 50, 50); - // drawOnTheMapBodyRect(3375, 2280, 100, 100); - // drawOnTheMapBodyRect(2550, 2355, 75, 25); - // drawOnTheMapBodyRect(3575, 1255, 125, 25); - // drawOnTheMapBodyRect(2450, 2255, 25, 25); - // doorPortal1Center = level.door(2595, -345, 20, 280, 360) - // len = body.length - 1 - // body[len].collisionFilter.category = cat.body; - // body[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet - // World.add(engine.world, body[len]); //add to world - // body[len].classType = "body" - // portalEnBas = level.portal({ - // x: 3750, - // y: 2280 - // }, Math.PI, { - // x: 2500, - // y: -199 - // }, 0) - // spawn.randomSmallMob(3000, 830, 1); - // spawn.randomSmallMob(2800, 1930, 1); - // spawn.randomMob(3400, 1980, 0.9); - // spawn.randomMob(2750, 1330, 0.8); - // spawn.randomMob(2800, 515, 0.6); - // spawn.randomMob(2500, 1230, 0.5) - // spawn.randomMob(2450, 2030, 0.3) - // spawn.randomMob(3300, 1980, 0.3) - // levelCustom2(); - // if (game.difficulty > 2) { - // if (Math.random() < 0.2) { - // // tether ball - // spawn.tetherBoss(8000, 630) - // let me = mob[mob.length - 1]; - // me.onDeath = function () { - // this.removeCons(); //remove constraint - // spawnCouloirEnHaut() - // }; - // cons[cons.length] = Constraint.create({ - // pointA: { - // x: 8550, - // y: 680 - // }, - // bodyB: mob[mob.length - 1], - // stiffness: 0.00015 - // }); - // World.add(engine.world, cons[cons.length - 1]); - // if (game.difficulty > 4) spawn.nodeBoss(8000, 630, "spawns", 8, 20, 105); - // } else if (game.difficulty > 3) { - // spawn.randomLevelBoss(8000, 630, ["shooterBoss", "launcherBoss", "laserTargetingBoss", "spiderBoss", "laserBoss", "bomberBoss"]); - // let me = mob[mob.length - 1]; - // me.onDeath = function () { - // this.removeCons(); //remove constraint - // spawnCouloirEnHaut() - // }; - // } - // } else { - // spawn.randomLevelBoss(8000, 630, ["shooterBoss"]); - // let me = mob[mob.length - 1]; - // me.onDeath = function () { - // spawnCouloirEnHaut() - // }; - // } - // } - - // function spawnCouloirEnHaut() { - // level.fill.push({ - // x: 2575, - // y: -1150, - // width: 2550, - // height: 630, - // color: BGColor - // }); - // level.fill.push({ - // x: 1900, - // y: -2300, - // width: 1650, - // height: 1150, - // color: BGColor - // }); - // level.fill.push({ - // x: 3550, - // y: -1625, - // width: 1650, - // height: 475, - // color: BGColor - // }); - // drawOnTheMapMapRect(3800, -270, 75, 75); - // drawOnTheMapMapRect(3900, -895, 500, 75); - // drawOnTheMapMapRect(3900, -1195, 75, 375); - // drawOnTheMapMapRect(3525, -1195, 450, 75); - // drawOnTheMapMapRect(3525, -1995, 50, 1575); - // drawOnTheMapMapRect(3325, -1995, 50, 1575); - // drawOnTheMapMapRect(3525, -1670, 1675, 75); - // drawOnTheMapMapRect(5100, -1670, 100, 1250); - // drawOnTheMapMapRect(1800, -1195, 1575, 75); - // drawOnTheMapMapRect(1800, -1520, 375, 400); - // drawOnTheMapMapRect(1800, -2370, 100, 1250); - // drawOnTheMapMapRect(2375, -1845, 375, 250); - // drawOnTheMapMapRect(2700, -1745, 650, 75); - // drawOnTheMapMapRect(1800, -2370, 1775, 100); - // drawOnTheMapMapRect(3525, -2370, 50, 775); - // drawOnTheMapMapRect(4650, -1220, 550, 75); - // drawOnTheMapBodyRect(3225, -1845, 100, 100); - // drawOnTheMapBodyRect(3575, 1255, 125, 25); - // drawOnTheMapBodyRect(2450, 2255, 25, 25); - // drawOnTheMapBodyRect(3975, -945, 175, 50); - // drawOnTheMapBodyRect(4825, -1295, 50, 75); - // drawOnTheMapBodyRect(4850, -720, 250, 200); - // drawOnTheMapBodyRect(4050, -970, 25, 25); - // drawOnTheMapBodyRect(3075, -1245, 50, 50); - // buttonSortieSalle = level.button(3000, -1745) - // spawn.mapVertex(3065, -1745, "100 10 -100 10 -70 -10 70 -10"); - // len = map.length - 1 - // map[len].collisionFilter.category = cat.map; - // map[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; - // Matter.Body.setStatic(map[len], true); //make static - // World.add(engine.world, map[len]); //add to world - // game.draw.setPaths() //update map graphics - - // portalEnHaut = level.portal({ - // x: 3650, - // y: -1470 - // }, Math.PI / 2, { - // x: 3250, - // y: -1473 - // }, Math.PI / 2) - - // spawn.randomSmallMob(2500, -2070 + Math.random(), 1); - // spawn.randomSmallMob(5000, -1370, 1); - // spawn.randomMob(5000, -645, 0.9); - // spawn.randomMob(4050, 970, 0.9); - // spawn.randomSmallMob(2800, -1620, 0.7); - // spawn.randomMob(2400, -1370, 0.5); - // spawn.randomMob(3725, -1320, 0.3); - // spawn.randomBoss(2115, -2020, 0.1) - - // powerUps.spawn(5000, -1275, "heal"); - // levelCustom3(); - // } - - // // //////////////////////////////////////// - // level.custom = () => { - // buttonCouloirEnBas.query(); - // buttonCouloirEnBas.draw(); - // if (buttonCouloirEnBas.isUp) {} else { - // if (isAlreadySpawned == false) { - // spawnCouloirEnBas() - // } - // } - // level.playerExitCheck(); - // rotor.rotate(); - // }; - // level.customTopLayer = () => { - // door.draw(); - // doorSortieSalle.draw(); - // }; - // // //////////////////////////////////////// - - // function levelCustom2() { - // level.custom = () => { - // portalEnBas[2].query() - // portalEnBas[3].query() - // rotor.rotate(); - // buttonPorteSalle.query(); - // buttonPorteSalle.draw(); - // buttonCouloirEnBas.query(); - // buttonCouloirEnBas.draw(); - // //////// - // if (buttonCouloirEnBas.isUp) {} else { - // if (isAlreadySpawned == false) { - // spawnCouloirEnBas() - // } - // } - // //////////// - // if (buttonPorteSalle.isUp) { - // door.isOpen = door12isOpen; - // doorPortal1Center.isOpen = door12isOpen; - // } else { - // door.isOpen = false; - // doorPortal1Center.isOpen = false; - // door12isOpen = false; - // } - // door.openClose(); - // doorPortal1Center.openClose(); - // level.playerExitCheck(); - // }; - // // ////////////////////////////////////// - // level.customTopLayer = () => { - // door.draw(); - // doorSortieSalle.draw(); - // doorPortal1Center.draw(); - // portalEnBas[0].draw(); - // portalEnBas[1].draw(); - // portalEnBas[2].draw(); - // portalEnBas[3].draw(); - // }; - // } - // // ////////////////////////////////////// - // function levelCustom3() { - // level.custom = () => { - // portalEnBas[2].query() - // portalEnBas[3].query() - // portalEnHaut[2].query(); - // portalEnHaut[3].query(); - // rotor.rotate(); - // buttonPorteSalle.query(); - // buttonPorteSalle.draw(); - // buttonSortieSalle.query(); - // buttonSortieSalle.draw(); - // buttonCouloirEnBas.query(); - // buttonCouloirEnBas.draw(); - // //////////// - // if (buttonPorteSalle.isUp) { - // door.isOpen = door12isOpen; - // doorPortal1Center.isOpen = door12isOpen; - // } else { - // door.isOpen = false; - // doorPortal1Center.isOpen = false; - // door12isOpen = false; - // } - // door.openClose(); - // doorPortal1Center.openClose(); - // if (buttonSortieSalle.isUp) { - // doorSortieSalle.isOpen = door3isOpen; - // } else { - // doorSortieSalle.isOpen = false; - // door3isOpen = false; - // } - // doorSortieSalle.openClose(); - // level.playerExitCheck(); - // }; - // // ////////////////////////////////////// - // level.customTopLayer = () => { - // door.draw(); - // doorPortal1Center.draw(); - // doorSortieSalle.draw(); - // portalEnBas[0].draw(); - // portalEnBas[1].draw(); - // portalEnBas[2].draw(); - // portalEnBas[3].draw(); - // portalEnHaut[0].draw(); - // portalEnHaut[1].draw(); - // portalEnHaut[2].draw(); - // portalEnHaut[3].draw(); - // }; - // } - // //spawn box - // spawn.mapRect(-200, -295, 75, 425); - // spawn.mapRect(-200, 55, 700, 75); - // spawn.mapRect(-200, -295, 700, 75); - // spawn.bodyRect(470, -220, 25, 275); //porte spawn box - // //couloir - // spawn.mapRect(450, -520, 50, 300); //muret gauche haut - // spawn.mapRect(450, 55, 50, 300); //muret gauche bas - // spawn.mapRect(1700, -520, 50, 325); //muret 2 haut - // spawn.mapRect(1700, 55, 50, 300); //muret 2 bas - // spawn.mapRect(4375, 55, 50, 300); - // spawn.mapRect(4575, 55, 50, 300); - // spawn.bodyRect(4625, 155, 75, 100); - // spawn.bodyRect(4725, 230, 50, 25); - // if (Math.random() > 0.5) { - // powerUps.chooseRandomPowerUp(4500, 200); - // } else { - // powerUps.chooseRandomPowerUp(8350, -630); - // } - // //blocs - // spawn.bodyRect(7475, 1055, 50, 75); - // spawn.bodyRect(7775, 1105, 25, 25); - // spawn.bodyRect(6925, 1105, 125, 25); - // spawn.bodyRect(6375, 380, 50, 50); - // spawn.bodyRect(6425, -220, 125, 150); - // spawn.bodyRect(6475, -245, 125, 25); - // spawn.bodyRect(7675, -245, 100, 50); - // spawn.bodyRect(7075, -520, 50, 100); - // spawn.bodyRect(8400, -595, 100, 75); - // spawn.bodyRect(1700, 5, 50, 50); - // spawn.bodyRect(1700, -45, 50, 50); - // spawn.bodyRect(1700, -95, 50, 50); - // spawn.bodyRect(1700, -145, 50, 50); - // spawn.bodyRect(1700, -195, 50, 50); - // spawn.mapRect(450, -520, 1600, 100); //plafond 1 - // spawn.mapRect(450, 255, 1600, 100); //sol 1 - // spawn.mapRect(2250, -95, 1450, 75); //entresol - // spawn.mapRect(3900, -520, 2000, 100); //plafond 2 - // spawn.mapRect(3900, 255, 2000, 100); //sol 2 - // //grande salle - // spawn.bodyRect(5900, 830, 325, 300); //bloc en bas à gauche - // spawn.mapRect(5775, -1295, 2900, 100); - // spawn.mapRect(5775, 1130, 2900, 100); //plancher + sol grande salle - // spawn.mapRect(5925, -70, 650, 50); //plateforme middle entrée - // spawn.mapRect(7575, -520, 1100, 100); //sol salle en haut à droite - // spawn.mapRect(6800, -420, 450, 50); //petite plateforme transition vers salle en haut - // spawn.mapRect(7750, -1295, 75, 575); //mur gauche salle en haut à droite - // spawn.mapRect(6100, 430, 375, 50); //plateforme en bas, gauche rotor - // spawn.mapRect(7450, -195, 1225, 75); //longue plateforme - // //murs grande salle - // spawn.mapRect(5775, -1295, 125, 875); - // spawn.mapRect(5775, 255, 125, 975); - // spawn.mapRect(8550, -1295, 125, 875); - // spawn.mapRect(8550, 180, 125, 1050); - // //couloir 2 - // spawn.mapRect(8875, -520, 1425, 325); - // spawn.mapRect(8550, -520, 1750, 100); - // spawn.mapRect(8550, 180, 2625, 100); - // spawn.mapRect(10175, -745, 125, 325); - // spawn.mapRect(10175, -745, 1000, 125); - // spawn.mapRect(11050, -745, 125, 1025); - // spawn.mapRect(8875, 80, 1425, 200); - // //MOBS - // spawn.randomSmallMob(900, -70, 1); - // spawn.randomMob(4300, 95, 1); - // spawn.randomSmallMob(6250, 630, 1); - // spawn.randomMob(6255, -835, 0.9); - // spawn.randomMob(8200, -900, 0.7); - // spawn.randomMob(5700, -270, 0.7); - // spawn.randomMob(8275, -320, 0.7); - // spawn.randomMob(2700, -270, 0.7); - // spawn.randomMob(7575, 950, 0.5); - // spawn.randomMob(7000, -695, 0.4); - // spawn.randomMob(1850, -345, 0.3); - // spawn.randomMob(3600, -270, 0.3); - // spawn.randomMob(1500, -270, 0.2); - // spawn.randomMob(1250, 55, 0.2); - // spawn.randomMob(8800, -45, 0.2); - // spawn.randomBoss(8025, -845, 0.2); - // }, - detours() { - level.setPosToSpawn(0, 0); //lower start - level.exit.y = 150; - spawn.mapRect(level.enter.x, 45, 100, 20); - level.exit.x = 10625; - spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); - level.defaultZoom = 1400; - game.zoomTransition(level.defaultZoom) - document.body.style.backgroundColor = "#d5d5d5"; - const BGColor = "rgba(0,0,0,0.1)"; - level.fill.push({ - x: -150, - y: -250, - width: 625, - height: 325, - color: BGColor - }); - level.fill.push({ - x: 475, - y: -520, - width: 5375, - height: 875, - color: BGColor - }); - level.fill.push({ - x: 5850, - y: -1275, - width: 2800, - height: 2475, - color: BGColor - }); - level.fill.push({ - x: 8650, - y: -500, - width: 1600, - height: 750, - color: BGColor - }); - level.fill.push({ - x: 10250, - y: -700, - width: 900, - height: 950, - color: BGColor - }); - const balance = level.spinner(5500, -412.5, 25, 660) //entrance - const rotor = level.rotor(7000, 580, -0.001); - const doorSortieSalle = level.door(8590, -520, 20, 800, 750) - let buttonSortieSalle - let portalEnBas - let portalEnHaut - let door3isOpen = false; - - function drawOnTheMapMapRect(x, y, dx, dy) { - spawn.mapRect(x, y, dx, dy); - len = map.length - 1 - map[len].collisionFilter.category = cat.map; - map[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; - Matter.Body.setStatic(map[len], true); //make static - World.add(engine.world, map[len]); //add to world - game.draw.setPaths() //update map graphics - } - - function drawOnTheMapBodyRect(x, y, dx, dy) { - spawn.bodyRect(x, y, dx, dy); - len = body.length - 1 - body[len].collisionFilter.category = cat.body; - body[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet - World.add(engine.world, body[len]); //add to world - body[len].classType = "body" - } - - function spawnCouloirEnHaut() { - level.fill.push({ - x: 2575, - y: -1150, - width: 2550, - height: 630, - color: BGColor - }); - level.fill.push({ - x: 1900, - y: -2300, - width: 1650, - height: 1150, - color: BGColor - }); - level.fill.push({ - x: 3550, - y: -1625, - width: 1650, - height: 475, - color: BGColor - }); - drawOnTheMapMapRect(3800, -270, 75, 75); - drawOnTheMapMapRect(3900, -895, 500, 75); - drawOnTheMapMapRect(3900, -1195, 75, 375); - drawOnTheMapMapRect(3525, -1195, 450, 75); - drawOnTheMapMapRect(3525, -1995, 50, 1575); - drawOnTheMapMapRect(3325, -1995, 50, 1575); - drawOnTheMapMapRect(3525, -1670, 1675, 75); - drawOnTheMapMapRect(5100, -1670, 100, 1250); - drawOnTheMapMapRect(1800, -1195, 1575, 75); - drawOnTheMapMapRect(1800, -1520, 375, 400); - drawOnTheMapMapRect(1800, -2370, 100, 1250); - drawOnTheMapMapRect(2375, -1845, 375, 250); - drawOnTheMapMapRect(2700, -1745, 650, 75); - drawOnTheMapMapRect(1800, -2370, 1775, 100); - drawOnTheMapMapRect(3525, -2370, 50, 775); - drawOnTheMapMapRect(4650, -1220, 550, 75); - drawOnTheMapBodyRect(3225, -1845, 100, 100); - drawOnTheMapBodyRect(3575, 1255, 125, 25); - drawOnTheMapBodyRect(2450, 2255, 25, 25); - drawOnTheMapBodyRect(3975, -945, 175, 50); - drawOnTheMapBodyRect(4825, -1295, 50, 75); - drawOnTheMapBodyRect(4850, -720, 250, 200); - drawOnTheMapBodyRect(4050, -970, 25, 25); - drawOnTheMapBodyRect(3075, -1245, 50, 50); - buttonSortieSalle = level.button(3000, -1745) - spawn.mapVertex(3065, -1745, "100 10 -100 10 -70 -10 70 -10"); - len = map.length - 1 - map[len].collisionFilter.category = cat.map; - map[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; - Matter.Body.setStatic(map[len], true); //make static - World.add(engine.world, map[len]); //add to world - game.draw.setPaths() //update map graphics - - portalEnHaut = level.portal({ - x: 3650, - y: -1470 - }, Math.PI / 2, { - x: 3250, - y: -1473 - }, Math.PI / 2) - - spawn.randomSmallMob(2500, -2070 + Math.random(), 1); - spawn.randomSmallMob(5000, -1370, 1); - spawn.randomMob(5000, -645, 0.9); - spawn.randomMob(4050, 970, 0.9); - spawn.randomSmallMob(2800, -1620, 0.7); - spawn.randomMob(2400, -1370, 0.5); - spawn.randomMob(3725, -1320, 0.3); - spawn.randomBoss(2115, -2020, 0.1) - - powerUps.spawn(5000, -1275, "heal"); - levelCustom2(); - } - ////////////////////////////////////////// - level.custom = () => { - level.playerExitCheck(); - rotor.rotate(); - // rotor2.rotate() - }; - level.customTopLayer = () => { - doorSortieSalle.draw(); - ctx.fillStyle = "#233" - ctx.beginPath(); - ctx.arc(balance.pointA.x, balance.pointA.y, 9, 0, 2 * Math.PI); - ctx.fill(); - }; - //////////////////////////////////////// - function levelCustom2() { - level.custom = () => { - portalEnHaut[2].query(); - portalEnHaut[3].query(); - rotor.rotate(); - // rotor2.rotate - buttonSortieSalle.query(); - buttonSortieSalle.draw(); - //////////// - if (buttonSortieSalle.isUp) { - doorSortieSalle.isOpen = door3isOpen; - } else { - doorSortieSalle.isOpen = false; - door3isOpen = false; - } - doorSortieSalle.openClose(); - level.playerExitCheck(); - }; - // ////////////////////////////////////// - level.customTopLayer = () => { - doorSortieSalle.draw(); - portalEnHaut[0].draw(); - portalEnHaut[1].draw(); - portalEnHaut[2].draw(); - portalEnHaut[3].draw(); - ctx.fillStyle = "#233" - ctx.beginPath(); - ctx.arc(balance.pointA.x, balance.pointA.y, 9, 0, 2 * Math.PI); - ctx.fill(); - }; - } - //spawn box - spawn.mapRect(-200, -295, 75, 425); - spawn.mapRect(-200, 55, 700, 75); - spawn.mapRect(-200, -295, 700, 75); - spawn.bodyRect(470, -220, 25, 275); //porte spawn box - //couloir - spawn.mapRect(450, -520, 50, 300); //muret gauche haut - spawn.mapRect(450, 55, 50, 300); //muret gauche bas - spawn.mapRect(1700, -520, 50, 325); //muret 2 haut - spawn.mapRect(1700, 55, 50, 300); //muret 2 bas - spawn.mapRect(4375, 55, 50, 300); - spawn.mapRect(4575, 55, 50, 300); - spawn.bodyRect(4625, 155, 75, 100); - spawn.bodyRect(4725, 230, 50, 25); - if (Math.random() > 0.5) { - powerUps.chooseRandomPowerUp(4500, 200); - } else { - powerUps.chooseRandomPowerUp(8350, -630); - } - //blocs - spawn.bodyRect(7475, 1055, 50, 75); - spawn.bodyRect(7775, 1105, 25, 25); - spawn.bodyRect(6925, 1105, 125, 25); - spawn.bodyRect(6375, 380, 50, 50); - spawn.bodyRect(6425, -220, 125, 150); - spawn.bodyRect(6475, -245, 125, 25); - spawn.bodyRect(7675, -245, 100, 50); - spawn.bodyRect(7075, -520, 50, 100); - spawn.bodyRect(8400, -595, 100, 75); - spawn.bodyRect(1700, 5, 50, 50); - spawn.bodyRect(1700, -45, 50, 50); - spawn.bodyRect(1700, -95, 50, 50); - spawn.bodyRect(1700, -145, 50, 50); - spawn.bodyRect(1700, -195, 50, 50); - spawn.mapRect(450, -520, 1600, 100); //plafond 1 - spawn.mapRect(450, 255, 1600, 100); //sol 1 - spawn.mapRect(2250, -95, 1450, 75); //entresol - spawn.mapRect(3900, -520, 2000, 100); //plafond 2 - spawn.mapRect(3900, 255, 2000, 100); //sol 2 - //grande salle - spawn.bodyRect(5900, 830, 325, 300); //bloc en bas à gauche - spawn.mapRect(5775, -1295, 2900, 100); - spawn.mapRect(5775, 1130, 2900, 100); //plancher + sol grande salle - spawn.mapRect(5925, -70, 650, 50); //plateforme middle entrée - spawn.mapRect(7575, -520, 1100, 100); //sol salle en haut à droite - spawn.mapRect(6800, -420, 450, 50); //petite plateforme transition vers salle en haut - spawn.mapRect(7750, -1295, 75, 575); //mur gauche salle en haut à droite - spawn.mapRect(6100, 430, 375, 50); //plateforme en bas, gauche rotor - spawn.mapRect(7450, -195, 1225, 75); //longue plateforme - //murs grande salle - spawn.mapRect(5775, -1295, 125, 875); - spawn.mapRect(5775, 255, 125, 975); - spawn.mapRect(8550, -1295, 125, 875); - spawn.mapRect(8550, 180, 125, 1050); - //couloir 2 - spawn.mapRect(8875, -520, 1425, 325); - spawn.mapRect(8550, -520, 1750, 100); - spawn.mapRect(8550, 180, 2625, 100); - spawn.mapRect(10175, -745, 125, 325); - spawn.mapRect(10175, -745, 1000, 125); - spawn.mapRect(11050, -745, 125, 1025); - spawn.mapRect(8875, 80, 1425, 200); - //MOBS - spawn.randomSmallMob(900, -70, 1); - spawn.randomMob(4300, 95, 1); - spawn.randomSmallMob(6250, 630, 1); - spawn.randomMob(6255, -835, 0.9); - spawn.randomMob(8200, -900, 0.7); - spawn.randomMob(5700, -270, 0.7); - spawn.randomMob(8275, -320, 0.7); - spawn.randomMob(2700, -270, 0.7); - spawn.randomMob(7575, 950, 0.5); - spawn.randomMob(7000, -695, 0.4); - spawn.randomMob(1850, -345, 0.3); - spawn.randomMob(3600, -270, 0.3); - spawn.randomMob(1500, -270, 0.2); - spawn.randomMob(1250, 55, 0.2); - spawn.randomMob(8800, -45, 0.2); - spawn.randomBoss(8025, -845, 0.2); - - if (game.difficulty > 2) { - if (Math.random() < 0.2) { - // tether ball - spawn.tetherBoss(8000, 630) - let me = mob[mob.length - 1]; - me.onDeath = function () { - this.removeCons(); //remove constraint - spawnCouloirEnHaut() + ////////////////////////////////////////// + level.custom = () => { + level.playerExitCheck(); + rotor.rotate(); + // rotor2.rotate() }; - cons[cons.length] = Constraint.create({ - pointA: { - x: 8550, - y: 680 - }, - bodyB: mob[mob.length - 1], - stiffness: 0.00015 - }); - World.add(engine.world, cons[cons.length - 1]); - if (game.difficulty > 4) spawn.nodeBoss(8000, 630, "spawns", 8, 20, 105); - } else if (game.difficulty > 3) { - spawn.randomLevelBoss(8000, 630, ["shooterBoss", "launcherBoss", "laserTargetingBoss", "spiderBoss", "laserBoss", "bomberBoss"]); - let me = mob[mob.length - 1]; - me.onDeath = function () { - this.removeCons(); //remove constraint - spawnCouloirEnHaut() + level.customTopLayer = () => { + doorSortieSalle.draw(); + ctx.fillStyle = "#233" + ctx.beginPath(); + ctx.arc(balance.pointA.x, balance.pointA.y, 9, 0, 2 * Math.PI); + ctx.fill(); }; - } - } else { - spawn.randomLevelBoss(8000, 630, ["shooterBoss"]); - let me = mob[mob.length - 1]; - me.onDeath = function () { - spawnCouloirEnHaut() - }; - } - }, - house() { - const rotor = level.rotor(4315, -315, -0.0002, 120, 20, 200); - const hazard = level.hazard(4350, -1000, 300, 110); - const doorBedroom = level.door(1152, -1150, 25, 250, 250); - const doorGrenier = level.door(1152, -1625, 25, 150, 160); - const buttonBedroom = level.button(1250, -850); - const voletLucarne1 = level.door(1401, -2150, 20, 26, 28); - const voletLucarne2 = level.door(1401, -2125, 20, 26, 53); - const voletLucarne3 = level.door(1401, -2100, 20, 26, 78); - const voletLucarne4 = level.door(1401, -2075, 20, 26, 103); - const voletLucarne5 = level.door(1401, -2050, 20, 26, 128); - const voletLucarne6 = level.door(1401, -2025, 20, 26, 153); - let hasAlreadyBeenActivated = false; - let grd - - level.setPosToSpawn(0, -50); //normal spawn - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - level.exit.x = 3100; - level.exit.y = -2480; - spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); - level.defaultZoom = 1800 - game.zoomTransition(level.defaultZoom) - document.body.style.backgroundColor = "rgb(170 170 170)" - - level.custom = () => { - hazard.query(); - buttonBedroom.query(); - buttonBedroom.draw(); - if (buttonBedroom.isUp) { - if (hasAlreadyBeenActivated == false) { - doorBedroom.isOpen = true; - doorGrenier.isOpen = true; - voletLucarne1.isOpen = true; - voletLucarne2.isOpen = true; - voletLucarne3.isOpen = true; - voletLucarne4.isOpen = true; - voletLucarne5.isOpen = true; - voletLucarne6.isOpen = true; - } - } else { - doorBedroom.isOpen = false; - doorGrenier.isOpen = false; - voletLucarne1.isOpen = false; - voletLucarne2.isOpen = false; - voletLucarne3.isOpen = false; - voletLucarne4.isOpen = false; - voletLucarne5.isOpen = false; - voletLucarne6.isOpen = false; - if (hasAlreadyBeenActivated == false) { - hasAlreadyBeenActivated = true; - } - } - doorBedroom.openClose(); - doorGrenier.openClose(); - voletLucarne1.openClose(); - voletLucarne2.openClose(); - voletLucarne3.openClose(); - voletLucarne4.openClose(); - voletLucarne5.openClose(); - voletLucarne6.openClose(); - rotor.rotate(); - /// - grd = ctx.createRadialGradient(512.5, -1025, 5, 512.5, -1025, 100); - grd.addColorStop(0, "rgb(255, 199, 43)"); - grd.addColorStop(1, "rgb(170 170 170)"); - ctx.fillStyle = grd; - ctx.fillRect(450, -1025, 125, 100); - /// - grd = ctx.createRadialGradient(762.5, -1025, 5, 762.5, -1025, 100); - grd.addColorStop(0, "rgb(255, 199, 43, 1)"); - grd.addColorStop(1, "rgb(170 170 170)"); - ctx.fillStyle = grd; - ctx.fillRect(700, -1025, 125, 100); - /// - ctx.lineWidth = 7; - ctx.strokeStyle = "#444444" - ctx.strokeRect(1650, -1300, 175, 150); - - chair.force.y += chair.mass * game.g; - chair2.force.y += chair2.mass * game.g; - person.force.y += person.mass * game.g; - level.playerExitCheck(); - }; - level.customTopLayer = () => { - hazard.draw(); - doorBedroom.draw(); - doorGrenier.draw(); - voletLucarne1.draw(); - voletLucarne2.draw(); - voletLucarne3.draw(); - voletLucarne4.draw(); - voletLucarne5.draw(); - voletLucarne6.draw(); - }; - - //colors - level.fillBG.push({ - x: 1175, - y: -1425, - width: 4000, - height: 1200, - color: "rgb(221, 221, 221)" - }) - level.fillBG.push({ - x: 1650, - y: -1300, - width: 175, - height: 150, - color: "rgb(170 170 170)" - }) - level.fillBG.push({ //lampadaire - x: 624, - y: -1150, - width: 28, - height: 1075, - color: "rgb(77, 76, 76)" - }); - //tele - level.fillBG.push({ //zone 1 - x: 3420, - y: -380, - width: 285, - height: 40, - color: "#ababab" - }) - level.fillBG.push({ //poignée 1 - x: 3555, - y: -367.5, - width: 15, - height: 15, - color: "#474747" - }) - level.fillBG.push({ //entre-deux 1 - x: 3418, - y: -344, - width: 288, - height: 8, - color: "#474747" - }) - level.fillBG.push({ //zone 2 - x: 3420, - y: -340, - width: 285, - height: 40, - color: "#ababab" - }) - level.fillBG.push({ //poignée 2 - x: 3555, - y: -327.5, - width: 15, - height: 15, - color: "#474747" - }) - level.fillBG.push({ //entre-deux 2 - x: 3418, - y: -304, - width: 288, - height: 8, - color: "#474747" - }) - level.fillBG.push({ //zone 3 - x: 3420, - y: -300, - width: 285, - height: 45, - color: "#ababab" - }) - level.fillBG.push({ //poignée 3 - x: 3555, - y: -285, - width: 15, - height: 15, - color: "#474747" - }) - level.fillBG.push({ //door bathroom - x: 3800, - y: -1275, - width: 250, - height: 425, - color: "rgba(141, 141, 141,1)", - }) - level.fillBG.push({ //door bathroom //top border - x: 3800, - y: -1275, - width: 250, - height: 3, - color: "#000", - }) - level.fillBG.push({ //door bathroom //right border - x: 4048, - y: -1275, - width: 3, - height: 425, - color: "#000", - }) - level.fillBG.push({ //door bathroom //left border - x: 3800, - y: -1275, - width: 3, - height: 425, - color: "#000", - }) - level.fillBG.push({ //poignée door bathroom - x: 3830, - y: -1050, - width: 35, - height: 10, - color: "#000", - }) - level.fillBG.push({ //background bathroom - x: 4050, - y: -1425, - width: 1125, - height: 600, - // color:"#c1d7db" - color: "rgba(225, 242, 245,0.6)" - }) - level.fillBG.push({ //window - x: 1736, - y: -1300, - width: 3, - height: 150, - color: "#444" - }) - level.fillBG.push({ //window - x: 1650, - y: -1224, - width: 175, - height: 3, - color: "#444" - }) - let color = Math.random().toString(16).substr(-6); - level.fillBG.push({ //écran - x: 3375, - y: -625, - width: 375, - height: 175, - color: '#' + color - }) - level.fill.push({ //hidden zone - x: 2800, - y: -400, - width: 275, - height: 175, - color: "rgba(64,64,64,0.97)" - }) - - - function drawCarreaux(x, y, width, height) { - level.fillBG.push({ //carreaux - x: x, - y: y, - width: width, - height: height, - color: "rgba(166, 166, 166,0.8)" - }) - } - for (let i = 0; i < 28; i++) { - drawCarreaux(4050 + i * 40, -1425, 1, 600); - } - for (let i = 0; i < 15; i++) { - drawCarreaux(4050, -1425 + i * 40, 1125, 2); - } - - //chairs - const part1 = Matter.Bodies.rectangle(4525, -255, 25, 200, { - density: 0.0005, - isNotHoldable: true, - }); - const part2 = Matter.Bodies.rectangle(4562, -235, 100, 25, { - density: 0.0005, - isNotHoldable: true, - }); - const part3 = Matter.Bodies.rectangle(4600, -202, 25, 91.5, { - density: 0.0005, - isNotHoldable: true, - }); - const part4 = Matter.Bodies.rectangle(5100, -255, 25, 200, { - density: 0.0005, - isNotHoldable: true, - }); - const part5 = Matter.Bodies.rectangle(5063, -235, 100, 25, { - density: 0.0005, - isNotHoldable: true, - }); - const part6 = Matter.Bodies.rectangle(5025, -202, 25, 91.5, { - density: 0.0005, - isNotHoldable: true, - }); - chair = Body.create({ - parts: [part1, part2, part3], - }); - chair2 = Body.create({ - parts: [part4, part5, part6], - }); - World.add(engine.world, [chair]); - World.add(engine.world, [chair2]); - composite[composite.length] = chair; - composite[composite.length] = chair2; - body[body.length] = part1; - body[body.length] = part2; - body[body.length] = part3; - body[body.length] = part4; - body[body.length] = part5; - body[body.length] = part6; - setTimeout(function () { - chair.collisionFilter.category = cat.body; - chair.collisionFilter.mask = cat.body | cat.player | cat.bullet | cat.mob | cat.mobBullet | cat.map - }, 1000); - setTimeout(function () { - chair2.collisionFilter.category = cat.body; - chair2.collisionFilter.mask = cat.body | cat.player | cat.bullet | cat.mob | cat.mobBullet | cat.map - }, 1000); - var head = Matter.Bodies.rectangle(300, -200 - 60, 34, 40, { - isNotHoldable: true, - }); - var chest = Matter.Bodies.rectangle(300, -200, 55, 80, { - isNotHoldable: true, - }); - var rightUpperArm = Matter.Bodies.rectangle(300 + 39, -200 - 15, 20, 40, { - isNotHoldable: true, - }); - var rightLowerArm = Matter.Bodies.rectangle(300 + 39, -200 + 25, 20, 60, { - isNotHoldable: true, - }); - var leftUpperArm = Matter.Bodies.rectangle(300 - 39, -200 - 15, 20, 40, { - isNotHoldable: true, - }); - var leftLowerArm = Matter.Bodies.rectangle(300 - 39, -200 + 25, 20, 60, { - isNotHoldable: true, - }); - var leftUpperLeg = Matter.Bodies.rectangle(300 - 20, -200 + 57, 20, 40, { - isNotHoldable: true, - }); - var leftLowerLeg = Matter.Bodies.rectangle(300 - 20, -200 + 97, 20, 60, { - isNotHoldable: true, - }); - var rightUpperLeg = Matter.Bodies.rectangle(300 + 20, -200 + 57, 20, 40, { - isNotHoldable: true, - }); - var rightLowerLeg = Matter.Bodies.rectangle(300 + 20, -200 + 97, 20, 60, { - isNotHoldable: true, - }); - - //man - var person = Body.create({ - parts: [chest, head, leftLowerArm, leftUpperArm, - rightLowerArm, rightUpperArm, leftLowerLeg, - rightLowerLeg, leftUpperLeg, rightUpperLeg - ], - }); - World.add(engine.world, [person]); - composite[composite.length] = person - body[body.length] = chest - body[body.length] = head - body[body.length] = part3 - body[body.length] = leftLowerLeg - body[body.length] = leftUpperLeg - body[body.length] = leftUpperArm - body[body.length] = leftLowerArm - body[body.length] = rightLowerLeg - body[body.length] = rightUpperLeg - body[body.length] = rightLowerArm - body[body.length] = rightUpperArm - setTimeout(function () { - person.collisionFilter.category = cat.body; - person.collisionFilter.mask = cat.body | cat.player | cat.bullet | cat.mob | cat.mobBullet | cat.map - }, 1000); - - //rez de chaussée - spawn.mapRect(-200, 0, 5400, 100); //ground - spawn.mapRect(1150, -255, 4050, 355); //additionnal ground - spawn.mapRect(800, -255, 400, 90); //1st step - spawn.mapRect(650, -170, 550, 90); //2nd step - spawn.mapRect(500, -85, 700, 90); //3rd step - spawn.mapRect(1150, -850, 50, 175); //porte entrée - spawn.bodyRect(1162.5, -675, 25, 420) //porte entrée - spawn.mapRect(1150, -850, 1500, 50); //plafond 1 - spawn.mapRect(3025, -850, 2175, 50); //plafond 2 - spawn.mapRect(5150, -850, 50, 650); //mur cuisine - //lave-vaisselle - spawn.mapRect(4225, -400, 25, 150); - spawn.mapRect(4225, -400, 175, 25); - spawn.mapRect(4375, -400, 25, 150); - spawn.bodyRect(4350, -350, 20, 40); - spawn.bodyRect(4325, -325, 20, 20); - spawn.bodyRect(4325, -275, 20, 20); - //escalier - spawn.mapRect(3025, -850, 50, 225); - spawn.mapRect(2925, -775, 150, 150); - spawn.mapRect(2800, -700, 275, 75); - spawn.mapRect(2575, -400, 175, 175); - spawn.mapRect(2475, -325, 175, 100); - spawn.mapRect(2675, -475, 400, 100); - spawn.mapRect(2675, -475, 150, 250); - //cuisine - spawn.mapRect(4025, -850, 50, 175); //porte cuisine - spawn.mapRect(4025, -375, 50, 125); //porte cuisine - - map[map.length] = Bodies.polygon(4050, -675, 0, 15); //circle above door - spawn.bodyRect(4040, -650, 20, 260, 1, spawn.propsDoor); // door - body[body.length - 1].isNotHoldable = true; - //makes door swing - consBB[consBB.length] = Constraint.create({ - bodyA: body[body.length - 1], - pointA: { - x: 0, - y: -130 - }, - bodyB: map[map.length - 1], - stiffness: 1 - }); - World.add(engine.world, consBB[consBB.length - 1]); - - //table + chaises - spawn.mapRect(4025, -850, 50, 175); - spawn.mapRect(4650, -375, 325, 25); - spawn.mapRect(4700, -350, 25, 100); - spawn.mapRect(4900, -350, 25, 100); - spawn.bodyRect(4875, -400, 75, 25); - spawn.bodyRect(4700, -400, 75, 25); - - //murs télé - spawn.mapRect(3400, -400, 20, 150); - spawn.mapRect(3705, -400, 20, 150); - spawn.mapRect(3400, -400, 325, 20); - //socle écran - spawn.mapRect(3500, -415, 125, 17); - spawn.mapRect(3550, -450, 25, 50); - // ??? - spawn.bodyRect(3075, -375, 125, 125); - spawn.bodyRect(3075, -400, 50, 25); - spawn.bodyRect(3725, -325, 100, 75); - spawn.bodyRect(3375, -275, 25, 25); - // premier étage - spawn.mapRect(1150, -1450, 4050, 50); - spawn.mapRect(5150, -1450, 50, 650); - spawn.mapRect(1150, -1450, 50, 300); - spawn.mapRect(1150, -900, 50, 100); - spawn.mapVertex(1066, -730, "-200 60 0 -60 100 -60 100 60") - //chambre - spawn.mapRect(2350, -1450, 50, 175); //porte chambre - //lit - spawn.mapRect(1475, -1025, 25, 225); //pied de lit 1 - spawn.mapRect(1850, -925, 25, 125); //pied de lit 2 - spawn.mapRect(1475, -925, 400, 50); //sommier - spawn.bodyRect(1500, -950, 375, 25); //matelat - spawn.bodyRect(1500, -1000, 75, 50); //oreiller - //table - spawn.bodyRect(1950, -1000, 30, 150); //pied table - spawn.bodyRect(2250, -1000, 30, 150); //pied table - spawn.bodyRect(1920, -1025, 390, 25); //table - //salle de bain - spawn.mapRect(4025, -1450, 50, 175); //porte salle de bain - map[map.length] = Bodies.polygon(5050, -925, 0, 35.4); - spawn.mapRect(5015, -960, 125, 40); - spawn.mapRect(5050, -925, 90, 35.4); - spawn.mapVertex(5086.5, -875, "100 60 -30 60 20 0 100 0") - spawn.mapRect(5125, -1070, 15, 120) - spawn.bodyRect(5016, -965, 108, 15) - //baignoire - spawn.mapVertex(4316, -965, "30 100 0 100 -80 -50 30 -50") //bord 1 - spawn.mapVertex(4675, -961.5, "30 100 0 100 0 -50 80 -50") //bord 2 - spawn.mapVertex(4400, -860, "0 -20 -20 20 20 20 0 -20") //pied 1 - spawn.mapVertex(4600, -860, "0 -20 -20 20 20 20 0 -20") //pied 2 - spawn.mapRect(4325, -900, 350, 25); //fond baignoire - spawn.mapRect(4300, -1175, 25, 175); - spawn.mapRect(4300, -1175, 125, 25); - spawn.mapRect(4400, -1175, 25, 50); //pied pommeau de douche - spawn.mapVertex(4412.5, -1105, "-20 -20 -30 40 30 40 20 -20") //pommeau de douche - - //grenier - spawn.mapRect(1150, -1475, 50, 50); - spawn.mapRect(1150, -1800, 50, 175); - spawn.mapRect(5150, -1800, 50, 400); //murs - spawn.mapVertex(1300, -1900, "-150 200 -200 200 50 0 100 0"); - spawn.mapVertex(1800, -2300, "-150 200 -200 200 175 -100 225 -100"); - spawn.mapRect(1390, -2180, 250, 30); //lucarne - spawn.mapVertex(5050, -1900, "150 200 200 200 -50 0 -100 0"); - spawn.mapVertex(4550, -2300, "150 200 200 200 -175 -100 -225 -100"); - spawn.mapRect(4710, -2175, 250, 25); //lucarne 2 - spawn.mapRect(5150, -1450, 200, 50); - //obstacles - spawn.mapRect(3775, -1800, 99, 50); - spawn.mapRect(2425, -2150, 50, 425); - spawn.mapRect(2150, -1775, 325, 50); - spawn.mapRect(3825, -2150, 50, 750); - spawn.mapRect(3826, -2150, 149, 50); - spawn.mapRect(4125, -2150, 149, 50); - spawn.mapRect(4225, -2150, 50, 450); - spawn.mapRect(4225, -1750, 250, 50); - level.chain(2495, -2130, 0, true, 10); - - spawn.bodyRect(2950, -375, 120, 120) //bloc hidden zone - spawn.bodyRect(2350, -1850, 75, 75); - spawn.bodyRect(4275, -1900, 75, 100); - spawn.bodyRect(4825, -1650, 325, 200); - spawn.bodyRect(5025, -1725, 25, 25); - spawn.bodyRect(4900, -1700, 200, 75); - spawn.mapVertex(2950, -2096, "-75 -50 75 -50 75 0 0 100 -75 0") - - /*cheminée + roof*/ - spawn.mapRect(1963, -2450, 2425, 35); - spawn.mapRect(2925, -2900, 125, 480); - spawn.mapRect(2900, -2900, 175, 75); - spawn.mapRect(2900, -2975, 25, 100); - spawn.mapRect(3050, -2975, 25, 100); - spawn.mapRect(2875, -3000, 225, 25); - // lampadaire + jump - spawn.mapRect(1000, -1450, 200, 25); - spawn.mapRect(500, -1150, 275, 25); - spawn.mapRect(750, -1150, 25, 75); - spawn.mapRect(500, -1150, 25, 75); - spawn.mapRect(450, -1075, 125, 50); - spawn.mapRect(700, -1075, 125, 50); - spawn.mapRect(2985, -4600, 0.1, 1700) - - //bodyRects ~= debris - spawn.bodyRect(1740, -475, 80, 220) - spawn.bodyRect(1840, -290, 38, 23) - spawn.bodyRect(1200 + 1475 * Math.random(), -350, 15 + 110 * Math.random(), 15 + 110 * Math.random()); - spawn.bodyRect(1200 + 1475 * Math.random(), -350, 15 + 110 * Math.random(), 15 + 110 * Math.random()); - spawn.bodyRect(3070 + 600 * Math.random(), -1100, 20 + 50 * Math.random(), 150 + 100 * Math.random()) - spawn.bodyRect(3050 + 1000 * Math.random(), -920, 30 + 100 * Math.random(), 15 + 65 * Math.random()); - spawn.bodyRect(1600 + 250 * Math.random(), -1540, 80, 220) //boss room - spawn.debris(3070, -900, 1000, 3); //16 debris per level - spawn.debris(1200, -350, 1475, 4); //16 debris per level - spawn.debris(1250, -1550, 3565, 9); //16 debris per level - - powerUps.chooseRandomPowerUp(2860, -270); - // Mobs - - spawn.randomSmallMob(1385, -600, 1); - spawn.randomSmallMob(5000, -680, 1); - spawn.randomSmallMob(4750, -925, 1); - spawn.randomSmallMob(2300, -1830, 1); - spawn.randomMob(3170, -720, 0.8); - spawn.randomMob(3700, -975, 0.8); - spawn.randomMob(2625, -1150, 0.7); - spawn.randomMob(4175, -750, 0.7); - spawn.randomMob(2100, -370, 0.7); - spawn.randomMob(2000, -1230, 0.7); - spawn.randomMob(4175, -1075, 0.6); - spawn.randomMob(3965, -1650, 0.6) - spawn.randomMob(4650, -1750, 0.6); - spawn.randomMob(830, -1170, 0.5); - spawn.randomBoss(3730, -1100, 0.5); - spawn.randomMob(2650, -2250, 0.3); - spawn.randomMob(1615, -2270, 0.3); - spawn.randomMob(1380, -1280, 0.25); - spawn.randomMob(2280, -650, 0.2); - spawn.randomBoss(2450, -2650, 0.2); - spawn.randomMob(3800, -580, 0.2); - spawn.randomMob(4630, -425, 0.1); - spawn.randomBoss(630, -1300, -0.1); - spawn.randomBoss(3450, -2880, -0.2) - - if (game.difficulty > 3) { - if (Math.random() < 0.16) { - spawn.tetherBoss(3380, -1775) - cons[cons.length] = Constraint.create({ - pointA: { - x: 3775, - y: -1775 - }, - bodyB: mob[mob.length - 1], - stiffness: 0.00018 + 0.000007 * level.levelsCleared - }); - World.add(engine.world, cons[cons.length - 1]); - if (game.difficulty > 4) spawn.nodeBoss(3380, -1775, "spawns", 8, 20, 105); //chance to spawn a ring of exploding mobs around this boss - - } else { - spawn.randomLevelBoss(3100, -1850, ["shooterBoss", "spiderBoss", "launcherBoss", "laserTargetingBoss", "snakeBoss", "laserBoss"]); - } - } - }, - //****************************************************************************************************************** - //****************************************************************************************************************** - //****************************************************************************************************************** - //****************************************************************************************************************** - difficultyIncrease(num = 1) { - for (let i = 0; i < num; i++) { - game.difficulty++ - b.dmgScale *= 0.93; //damage done by player decreases each level - if (game.accelScale < 5) game.accelScale *= 1.02 //mob acceleration increases each level - if (game.lookFreqScale > 0.2) game.lookFreqScale *= 0.98 //mob cycles between looks decreases each level - if (game.CDScale > 0.2) game.CDScale *= 0.97 //mob CD time decreases each level - } - game.dmgScale = 0.38 * game.difficulty //damage done by mobs increases each level - game.healScale = 1 / (1 + game.difficulty * 0.06) //a higher denominator makes for lower heals // mech.health += heal * game.healScale; - }, - difficultyDecrease(num = 1) { //used in easy mode for game.reset() - for (let i = 0; i < num; i++) { - game.difficulty-- - b.dmgScale /= 0.93; //damage done by player decreases each level - if (game.accelScale > 0.2) game.accelScale /= 1.02 //mob acceleration increases each level - if (game.lookFreqScale < 5) game.lookFreqScale /= 0.98 //mob cycles between looks decreases each level - if (game.CDScale < 5) game.CDScale /= 0.97 //mob CD time decreases each level - } - if (game.difficulty < 1) game.difficulty = 0; - game.dmgScale = 0.38 * game.difficulty //damage done by mobs increases each level - if (game.dmgScale < 0.1) game.dmgScale = 0.1; - game.healScale = 1 / (1 + game.difficulty * 0.06) - }, - difficultyText() { - if (game.difficultyMode === 1) { - return "easy" - } else if (game.difficultyMode === 2) { - return "normal" - } else if (game.difficultyMode === 4) { - return "hard" - } else if (game.difficultyMode === 6) { - return "why" - } - }, - levelAnnounce() { - if (level.levelsCleared === 0) { - document.title = "n-gon: intro (" + level.difficultyText() + ")"; - } else { - document.title = "n-gon: L" + (level.levelsCleared) + " " + level.levels[level.onLevel] + " (" + level.difficultyText() + ")"; - } - }, - custom() {}, //each level runs it's own custom code (level exits, ...) - nextLevel() { - if (level.bossKilled || level.levelsCleared < level.levels.length) { - level.levelsCleared++; - level.onLevel++; //cycles map to next level - if (level.onLevel > level.levels.length - 1) level.onLevel = 0; - } - 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.bossKilled = false; - //reset lost mod display - for (let i = 0; i < mod.mods.length; i++) { - if (mod.mods[i].isLost) mod.mods[i].isLost = false; - } - game.updateModHUD(); - game.clearNow = true; //triggers in game.clearMap to remove all physics bodies and setup for new map - }, - playerExitCheck() { - if ( - player.position.x > level.exit.x && - player.position.x < level.exit.x + 100 && - player.position.y > level.exit.y - 150 && - player.position.y < level.exit.y - 40 && - player.velocity.y < 0.1 - ) { - level.nextLevel() - } - }, - setPosToSpawn(xPos, yPos) { - mech.spawnPos.x = mech.pos.x = xPos; - mech.spawnPos.y = mech.pos.y = yPos; - level.enter.x = mech.spawnPos.x - 50; - level.enter.y = mech.spawnPos.y + 20; - mech.transX = mech.transSmoothX = canvas.width2 - mech.pos.x; - mech.transY = mech.transSmoothY = canvas.height2 - mech.pos.y; - mech.Vx = mech.spawnVel.x; - mech.Vy = mech.spawnVel.y; - player.force.x = 0; - player.force.y = 0; - Matter.Body.setPosition(player, mech.spawnPos); - Matter.Body.setVelocity(player, mech.spawnVel); - }, - enter: { - x: 0, - y: 0, - draw() { - ctx.beginPath(); - ctx.moveTo(level.enter.x, level.enter.y + 30); - ctx.lineTo(level.enter.x, level.enter.y - 80); - ctx.bezierCurveTo(level.enter.x, level.enter.y - 170, level.enter.x + 100, level.enter.y - 170, level.enter.x + 100, level.enter.y - 80); - ctx.lineTo(level.enter.x + 100, level.enter.y + 30); - ctx.lineTo(level.enter.x, level.enter.y + 30); - ctx.fillStyle = "#ccc"; - ctx.fill(); - } - }, - exit: { - x: 0, - y: 0, - draw() { - ctx.beginPath(); - ctx.moveTo(level.exit.x, level.exit.y + 30); - ctx.lineTo(level.exit.x, level.exit.y - 80); - ctx.bezierCurveTo(level.exit.x, level.exit.y - 170, level.exit.x + 100, level.exit.y - 170, level.exit.x + 100, level.exit.y - 80); - ctx.lineTo(level.exit.x + 100, level.exit.y + 30); - ctx.lineTo(level.exit.x, level.exit.y + 30); - ctx.fillStyle = "#0ff"; - ctx.fill(); - } - }, - fillBG: [], - drawFillBGs() { - for (let i = 0, len = level.fillBG.length; i < len; ++i) { - const f = level.fillBG[i]; - ctx.fillStyle = f.color; - ctx.fillRect(f.x, f.y, f.width, f.height); - } - }, - fill: [], - drawFills() { - for (let i = 0, len = level.fill.length; i < len; ++i) { - const f = level.fill[i]; - ctx.fillStyle = f.color; - ctx.fillRect(f.x, f.y, f.width, f.height); - } - }, - queryList: [], //queries do actions on many objects in regions - checkQuery() { - let bounds, action, info; - - function isInZone(targetArray) { - let results = Matter.Query.region(targetArray, bounds); - for (let i = 0, len = results.length; i < len; ++i) { - level.queryActions[action](results[i], info); - } - } - for (let i = 0, len = level.queryList.length; i < len; ++i) { - bounds = level.queryList[i].bounds; - action = level.queryList[i].action; - info = level.queryList[i].info; - for (let j = 0, l = level.queryList[i].groups.length; j < l; ++j) { - isInZone(level.queryList[i].groups[j]); - } - } - }, - //oddly query regions can't get smaller than 50 width? - addQueryRegion(x, y, width, height, action, groups = [ - [player], body, mob, powerUp, bullet - ], info) { - level.queryList[level.queryList.length] = { - bounds: { - min: { - x: x, - y: y - }, - max: { - x: x + width, - y: y + height - } - }, - action: action, - groups: groups, - info: info - }; - }, - queryActions: { - bounce(target, info) { - //jerky fling upwards - Matter.Body.setVelocity(target, { - x: info.Vx + (Math.random() - 0.5) * 6, - y: info.Vy - }); - target.torque = (Math.random() - 0.5) * 2 * target.mass; - }, - boost(target, yVelocity) { - mech.buttonCD_jump = 0; // reset short jump counter to prevent short jumps on boosts - mech.hardLandCD = 0 // disable hard landing - if (target.velocity.y > 30) { - Matter.Body.setVelocity(target, { - x: target.velocity.x + (Math.random() - 0.5) * 2, - y: -15 //gentle bounce if coming down super fast - }); - } else { - Matter.Body.setVelocity(target, { - x: target.velocity.x + (Math.random() - 0.5) * 2, - y: yVelocity - }); - } - - }, - force(target, info) { - if (target.velocity.y < 0) { //gently force up if already on the way up - target.force.x += info.Vx * target.mass; - target.force.y += info.Vy * target.mass; - } else { - target.force.y -= 0.0007 * target.mass; //gently fall in on the way down - } - }, - antiGrav(target) { - target.force.y -= 0.0011 * target.mass; - }, - death(target) { - target.death(); - } - }, - addToWorld() { //needs to be run to put bodies into the world - for (let i = 0; i < body.length; i++) { - //body[i].collisionFilter.group = 0; - if (body[i] !== mech.holdingTarget) { - body[i].collisionFilter.category = cat.body; - body[i].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet - } - body[i].classType = "body"; - World.add(engine.world, body[i]); //add to world - } - for (let i = 0; i < map.length; i++) { - //map[i].collisionFilter.group = 0; - map[i].collisionFilter.category = cat.map; - map[i].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; - Matter.Body.setStatic(map[i], true); //make static - World.add(engine.world, map[i]); //add to world - } - // for (let i = 0; i < cons.length; i++) { - // World.add(engine.world, cons[i]); - // } - - }, - spinner(x, y, width, height, density = 0.001) { - x = x + width / 2 - y = y + height / 2 - const who = body[body.length] = Bodies.rectangle(x, y, width, height, { - collisionFilter: { - category: cat.body, - mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet - }, - isNotHoldable: true, - frictionAir: 0.001, - friction: 1, - frictionStatic: 1, - restitution: 0, - }); - - Matter.Body.setDensity(who, density) - const constraint = Constraint.create({ //fix rotor in place, but allow rotation - pointA: { - x: x, - y: y - }, - bodyB: who, - stiffness: 1, - damping: 1 - }); - World.add(engine.world, constraint); - return constraint - }, - platform(x, y, width, height, speed = 0, density = 0.001) { - x = x + width / 2 - y = y + height / 2 - const who = body[body.length] = Bodies.rectangle(x, y, width, height, { - collisionFilter: { - category: cat.body, - mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet - }, - inertia: Infinity, //prevents rotation - isNotHoldable: true, - friction: 1, - frictionStatic: 1, - restitution: 0, - }); - - Matter.Body.setDensity(who, density) - const constraint = Constraint.create({ //fix rotor in place, but allow rotation - pointA: { - x: x, - y: y - }, - bodyB: who, - stiffness: 0.1, - damping: 0.3 - }); - World.add(engine.world, constraint); - constraint.plat = { - position: who.position, - speed: speed, - } - constraint.pauseUntilCycle = 0 //to to pause platform at top and bottom - return constraint - }, - rotor(x, y, rotate = 0, radius = 800, width = 40, density = 0.0005) { - const rotor1 = Matter.Bodies.rectangle(x, y, width, radius, { - density: density, - isNotHoldable: true - }); - const rotor2 = Matter.Bodies.rectangle(x, y, width, radius, { - angle: Math.PI / 2, - density: density, - isNotHoldable: true - }); - rotor = Body.create({ //combine rotor1 and rotor2 - parts: [rotor1, rotor2], - restitution: 0, - collisionFilter: { - category: cat.body, - mask: cat.body | cat.mob | cat.mobBullet | cat.mobShield | cat.powerUp | cat.player | cat.bullet - }, - }); - Matter.Body.setPosition(rotor, { - x: x, - y: y - }); - World.add(engine.world, [rotor]); - body[body.length] = rotor1 - body[body.length] = rotor2 - - setTimeout(function () { - rotor.collisionFilter.category = cat.body; - rotor.collisionFilter.mask = cat.body | cat.player | cat.bullet | cat.mob | cat.mobBullet //| cat.map - }, 1000); - - const constraint = Constraint.create({ //fix rotor in place, but allow rotation - pointA: { - x: x, - y: y - }, - bodyB: rotor - }); - World.add(engine.world, constraint); - - if (rotate) { - rotor.rotate = function () { - if (!mech.isBodiesAsleep) { - Matter.Body.applyForce(rotor, { - x: rotor.position.x + 100, - y: rotor.position.y + 100 - }, { - x: rotate * rotor.mass, - y: 0 - }) - } else { - Matter.Body.setAngularVelocity(rotor, 0); - } - } - } - composite[composite.length] = rotor - return rotor - }, - button(x, y, width = 126) { - spawn.mapVertex(x + 65, y + 2, "100 10 -100 10 -70 -10 70 -10"); - map[map.length - 1].restitution = 0; - map[map.length - 1].friction = 1; - map[map.length - 1].frictionStatic = 1; - - // const buttonSensor = Bodies.rectangle(x + 35, y - 1, 70, 20, { - // isSensor: true - // }); - - return { - isUp: false, - min: { - x: x + 2, - y: y - 11 - }, - max: { - x: x + width, - y: y - 10 - }, - width: width, - height: 20, - query() { - if (Matter.Query.region(body, this).length === 0 && Matter.Query.region([player], this).length === 0) { - this.isUp = true; - } else { - // if (this.isUp === true) { - // const list = Matter.Query.region(body, this) - // console.log(list) - // if (list.length > 0) { - // Matter.Body.setPosition(list[0], { - // x: this.min.x + width / 2, - // y: list[0].position.y - // }) - // Matter.Body.setVelocity(list[0], { - // x: 0, - // y: 0 - // }); - // } - // } - this.isUp = false; - } - }, - draw() { - ctx.fillStyle = "hsl(0, 100%, 70%)" - if (this.isUp) { - ctx.fillRect(this.min.x, this.min.y - 10, this.width, 20) - } else { - ctx.fillRect(this.min.x, this.min.y - 3, this.width, 25) - } - //draw sensor zone - // ctx.beginPath(); - // sensor = buttonSensor.vertices; - // ctx.moveTo(sensor[0].x, sensor[0].y); - // for (let i = 1; i < sensor.length; ++i) { - // ctx.lineTo(sensor[i].x, sensor[i].y); - // } - // ctx.lineTo(sensor[0].x, sensor[0].y); - // ctx.fillStyle = "rgba(255, 255, 0, 0.3)"; - // ctx.fill(); - } - } - }, - door(x, y, width, height, distance) { - x = x + width / 2 - y = y + height / 2 - const doorBlock = body[body.length] = Bodies.rectangle(x, y, width, height, { - collisionFilter: { - category: cat.body, - mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet - }, - inertia: Infinity, //prevents rotation - isNotHoldable: true, - friction: 1, - frictionStatic: 1, - restitution: 0, - isOpen: false, - openClose() { - if (!mech.isBodiesAsleep) { - if (!this.isOpen) { - if (this.position.y > y - distance) { //try to open - const position = { - x: this.position.x, - y: this.position.y - 1 - } - Matter.Body.setPosition(this, position) - } - } else { - if (this.position.y < y) { //try to close - if ( - Matter.Query.collides(this, [player]).length === 0 && - Matter.Query.collides(this, body).length < 2 && - Matter.Query.collides(this, mob).length === 0 - ) { - const position = { - x: this.position.x, - y: this.position.y + 1 + //////////////////////////////////////// + function levelCustom2() { + level.custom = () => { + portalEnHaut[2].query(); + portalEnHaut[3].query(); + rotor.rotate(); + // rotor2.rotate + buttonSortieSalle.query(); + buttonSortieSalle.draw(); + //////////// + if (buttonSortieSalle.isUp) { + doorSortieSalle.isOpen = door3isOpen; + } else { + doorSortieSalle.isOpen = false; + door3isOpen = false; } - Matter.Body.setPosition(this, position) - } - } - } + doorSortieSalle.openClose(); + level.playerExitCheck(); + }; + // ////////////////////////////////////// + level.customTopLayer = () => { + doorSortieSalle.draw(); + portalEnHaut[0].draw(); + portalEnHaut[1].draw(); + portalEnHaut[2].draw(); + portalEnHaut[3].draw(); + ctx.fillStyle = "#233" + ctx.beginPath(); + ctx.arc(balance.pointA.x, balance.pointA.y, 9, 0, 2 * Math.PI); + ctx.fill(); + }; } - }, - draw() { - ctx.fillStyle = "#555" - ctx.beginPath(); - const v = this.vertices; - ctx.moveTo(v[0].x, v[0].y); - for (let i = 1; i < v.length; ++i) { - ctx.lineTo(v[i].x, v[i].y); - } - ctx.lineTo(v[0].x, v[0].y); - ctx.fill(); - } - }); - Matter.Body.setStatic(doorBlock, true); //make static - return doorBlock - }, - portal(centerA, angleA, centerB, angleB) { - const width = 50 - const height = 150 - const mapWidth = 200 - const unitA = Matter.Vector.rotate({ - x: 1, - y: 0 - }, angleA) - const unitB = Matter.Vector.rotate({ - x: 1, - y: 0 - }, angleB) - - draw = function () { - ctx.beginPath(); //portal - let v = this.vertices; - ctx.moveTo(v[0].x, v[0].y); - for (let i = 1; i < v.length; ++i) { - ctx.lineTo(v[i].x, v[i].y); - } - ctx.fillStyle = this.color - ctx.fill(); - } - query = function () { - if (Matter.Query.collides(this, [player]).length === 0) { //not touching player - if (player.isInPortal === this) player.isInPortal = null - } else if (player.isInPortal !== this) { //touching player - if (mech.buttonCD_jump === mech.cycle) player.force.y = 0 // undo a jump right before entering the portal - mech.buttonCD_jump = 0 //disable short jumps when letting go of jump key - player.isInPortal = this.portalPair - //teleport - if (this.portalPair.angle % (Math.PI / 2)) { //if left, right up or down - Matter.Body.setPosition(player, this.portalPair.portal.position); - } else { //if at some odd angle - Matter.Body.setPosition(player, this.portalPair.position); - } - //rotate velocity - let mag - if (this.portalPair.angle !== 0 && this.portalPair.angle !== Math.PI) { //portal that fires the player up - mag = Math.max(10, Math.min(50, player.velocity.y * 0.8)) + 11 + //spawn box + spawn.mapRect(-200, -295, 75, 425); + spawn.mapRect(-200, 55, 700, 75); + spawn.mapRect(-200, -295, 700, 75); + spawn.bodyRect(470, -220, 25, 275); //porte spawn box + //couloir + spawn.mapRect(450, -520, 50, 300); //muret gauche haut + spawn.mapRect(450, 55, 50, 300); //muret gauche bas + spawn.mapRect(1700, -520, 50, 325); //muret 2 haut + spawn.mapRect(1700, 55, 50, 300); //muret 2 bas + spawn.mapRect(4375, 55, 50, 300); + spawn.mapRect(4575, 55, 50, 300); + spawn.bodyRect(4625, 155, 75, 100); + spawn.bodyRect(4725, 230, 50, 25); + if (Math.random() > 0.5) { + powerUps.chooseRandomPowerUp(4500, 200); } else { - mag = Math.max(6, Math.min(50, Vector.magnitude(player.velocity))) + powerUps.chooseRandomPowerUp(8350, -630); } - let v = Vector.mult(this.portalPair.unit, mag) - Matter.Body.setVelocity(player, v); - // move bots to follow player - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType) { - // Matter.Body.setPosition(bullet[i], this.portalPair.portal.position); - Matter.Body.setPosition(bullet[i], Vector.add(this.portalPair.portal.position, { - x: 250 * (Math.random() - 0.5), - y: 250 * (Math.random() - 0.5) - })); - Matter.Body.setVelocity(bullet[i], { - x: 0, - y: 0 + //blocs + spawn.bodyRect(7475, 1055, 50, 75); + spawn.bodyRect(7775, 1105, 25, 25); + spawn.bodyRect(6925, 1105, 125, 25); + spawn.bodyRect(6375, 380, 50, 50); + spawn.bodyRect(6425, -220, 125, 150); + spawn.bodyRect(6475, -245, 125, 25); + spawn.bodyRect(7675, -245, 100, 50); + spawn.bodyRect(7075, -520, 50, 100); + spawn.bodyRect(8400, -595, 100, 75); + spawn.bodyRect(1700, 5, 50, 50); + spawn.bodyRect(1700, -45, 50, 50); + spawn.bodyRect(1700, -95, 50, 50); + spawn.bodyRect(1700, -145, 50, 50); + spawn.bodyRect(1700, -195, 50, 50); + spawn.mapRect(450, -520, 1600, 100); //plafond 1 + spawn.mapRect(450, 255, 1600, 100); //sol 1 + spawn.mapRect(2250, -95, 1450, 75); //entresol + spawn.mapRect(3900, -520, 2000, 100); //plafond 2 + spawn.mapRect(3900, 255, 2000, 100); //sol 2 + //grande salle + spawn.bodyRect(5900, 830, 325, 300); //bloc en bas à gauche + spawn.mapRect(5775, -1295, 2900, 100); + spawn.mapRect(5775, 1130, 2900, 100); //plancher + sol grande salle + spawn.mapRect(5925, -70, 650, 50); //plateforme middle entrée + spawn.mapRect(7575, -520, 1100, 100); //sol salle en haut à droite + spawn.mapRect(6800, -420, 450, 50); //petite plateforme transition vers salle en haut + spawn.mapRect(7750, -1295, 75, 575); //mur gauche salle en haut à droite + spawn.mapRect(6100, 430, 375, 50); //plateforme en bas, gauche rotor + spawn.mapRect(7450, -195, 1225, 75); //longue plateforme + //murs grande salle + spawn.mapRect(5775, -1295, 125, 875); + spawn.mapRect(5775, 255, 125, 975); + spawn.mapRect(8550, -1295, 125, 875); + spawn.mapRect(8550, 180, 125, 1050); + //couloir 2 + spawn.mapRect(8875, -520, 1425, 325); + spawn.mapRect(8550, -520, 1750, 100); + spawn.mapRect(8550, 180, 2625, 100); + spawn.mapRect(10175, -745, 125, 325); + spawn.mapRect(10175, -745, 1000, 125); + spawn.mapRect(11050, -745, 125, 1025); + spawn.mapRect(8875, 80, 1425, 200); + //MOBS + spawn.randomSmallMob(900, -70, 1); + spawn.randomMob(4300, 95, 1); + spawn.randomSmallMob(6250, 630, 1); + spawn.randomMob(6255, -835, 0.9); + spawn.randomMob(8200, -900, 0.7); + spawn.randomMob(5700, -270, 0.7); + spawn.randomMob(8275, -320, 0.7); + spawn.randomMob(2700, -270, 0.7); + spawn.randomMob(7575, 950, 0.5); + spawn.randomMob(7000, -695, 0.4); + spawn.randomMob(1850, -345, 0.3); + spawn.randomMob(3600, -270, 0.3); + spawn.randomMob(1500, -270, 0.2); + spawn.randomMob(1250, 55, 0.2); + spawn.randomMob(8800, -45, 0.2); + spawn.randomBoss(8025, -845, 0.2); + + // if (game.difficulty > 2) { + if (Math.random() < 0.2) { + // tether ball + spawn.tetherBoss(8000, 630) + let me = mob[mob.length - 1]; + me.onDeath = function() { + this.removeCons(); //remove constraint + spawnCouloirEnHaut() + }; + cons[cons.length] = Constraint.create({ + pointA: { + x: 8550, + y: 680 + }, + bodyB: mob[mob.length - 1], + stiffness: 0.00015 }); - } + World.add(engine.world, cons[cons.length - 1]); + if (game.difficulty > 4) spawn.nodeBoss(8000, 630, "spawns", 8, 20, 105); + } else if (game.difficulty > 3) { + spawn.randomLevelBoss(8000, 630, ["shooterBoss", "launcherBoss", "laserTargetingBoss", "spiderBoss", "laserBoss", "bomberBoss"]); + let me = mob[mob.length - 1]; + me.onDeath = function() { + this.removeCons(); //remove constraint + spawnCouloirEnHaut() + }; } - } - // if (body.length) { - for (let i = 0, len = body.length; i < len; i++) { - if (body[i] !== mech.holdingTarget) { - // body[i].bounds.max.x - body[i].bounds.min.x < 100 && body[i].bounds.max.y - body[i].bounds.min.y < 100 - if (Matter.Query.collides(this, [body[i]]).length === 0) { - if (body[i].isInPortal === this) body[i].isInPortal = null - } else if (body[i].isInPortal !== this) { - body[i].isInPortal = this.portalPair - //teleport - if (this.portalPair.angle % (Math.PI / 2)) { //if left, right up or down - Matter.Body.setPosition(body[i], this.portalPair.portal.position); - } else { //if at some odd angle - Matter.Body.setPosition(body[i], this.portalPair.position); - } - //rotate velocity - let mag - if (this.portalPair.angle !== 0 && this.portalPair.angle !== Math.PI) { //portal that fires the player up - mag = Math.max(10, Math.min(50, body[i].velocity.y * 0.8)) + 11 + // } else { + // spawn.randomLevelBoss(8000, 630, ["shooterBoss"]); + // let me = mob[mob.length - 1]; + // me.onDeath = function () { + // spawnCouloirEnHaut() + // }; + // } + }, + house() { + const rotor = level.rotor(4315, -315, -0.0002, 120, 20, 200); + const hazard = level.hazard(4350, -1000, 300, 110); + const doorBedroom = level.door(1152, -1150, 25, 250, 250); + const doorGrenier = level.door(1152, -1625, 25, 150, 160); + const buttonBedroom = level.button(1250, -850); + const voletLucarne1 = level.door(1401, -2150, 20, 26, 28); + const voletLucarne2 = level.door(1401, -2125, 20, 26, 53); + const voletLucarne3 = level.door(1401, -2100, 20, 26, 78); + const voletLucarne4 = level.door(1401, -2075, 20, 26, 103); + const voletLucarne5 = level.door(1401, -2050, 20, 26, 128); + const voletLucarne6 = level.door(1401, -2025, 20, 26, 153); + let hasAlreadyBeenActivated = false; + let grd + + level.setPosToSpawn(0, -50); //normal spawn + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + level.exit.x = 3100; + level.exit.y = -2480; + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); + level.defaultZoom = 1800 + game.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "rgb(170 170 170)" + + level.custom = () => { + hazard.query(); + buttonBedroom.query(); + buttonBedroom.draw(); + if (buttonBedroom.isUp) { + if (hasAlreadyBeenActivated == false) { + doorBedroom.isOpen = true; + doorGrenier.isOpen = true; + voletLucarne1.isOpen = true; + voletLucarne2.isOpen = true; + voletLucarne3.isOpen = true; + voletLucarne4.isOpen = true; + voletLucarne5.isOpen = true; + voletLucarne6.isOpen = true; + } } else { - mag = Math.max(6, Math.min(50, Vector.magnitude(body[i].velocity))) + doorBedroom.isOpen = false; + doorGrenier.isOpen = false; + voletLucarne1.isOpen = false; + voletLucarne2.isOpen = false; + voletLucarne3.isOpen = false; + voletLucarne4.isOpen = false; + voletLucarne5.isOpen = false; + voletLucarne6.isOpen = false; + if (hasAlreadyBeenActivated == false) { + hasAlreadyBeenActivated = true; + } } - let v = Vector.mult(this.portalPair.unit, mag) - Matter.Body.setVelocity(body[i], v); - } + doorBedroom.openClose(); + doorGrenier.openClose(); + voletLucarne1.openClose(); + voletLucarne2.openClose(); + voletLucarne3.openClose(); + voletLucarne4.openClose(); + voletLucarne5.openClose(); + voletLucarne6.openClose(); + rotor.rotate(); + /// + grd = ctx.createRadialGradient(512.5, -1025, 5, 512.5, -1025, 100); + grd.addColorStop(0, "rgb(255, 199, 43)"); + grd.addColorStop(1, "rgb(170 170 170)"); + ctx.fillStyle = grd; + ctx.fillRect(450, -1025, 125, 100); + /// + grd = ctx.createRadialGradient(762.5, -1025, 5, 762.5, -1025, 100); + grd.addColorStop(0, "rgb(255, 199, 43, 1)"); + grd.addColorStop(1, "rgb(170 170 170)"); + ctx.fillStyle = grd; + ctx.fillRect(700, -1025, 125, 100); + /// + ctx.lineWidth = 7; + ctx.strokeStyle = "#444444" + ctx.strokeRect(1650, -1300, 175, 150); + + chair.force.y += chair.mass * game.g; + chair2.force.y += chair2.mass * game.g; + person.force.y += person.mass * game.g; + level.playerExitCheck(); + }; + level.customTopLayer = () => { + hazard.draw(); + doorBedroom.draw(); + doorGrenier.draw(); + voletLucarne1.draw(); + voletLucarne2.draw(); + voletLucarne3.draw(); + voletLucarne4.draw(); + voletLucarne5.draw(); + voletLucarne6.draw(); + }; + + //colors + level.fillBG.push({ + x: 1175, + y: -1425, + width: 4000, + height: 1200, + color: "rgb(221, 221, 221)" + }) + level.fillBG.push({ + x: 1650, + y: -1300, + width: 175, + height: 150, + color: "rgb(170 170 170)" + }) + level.fillBG.push({ //lampadaire + x: 624, + y: -1150, + width: 28, + height: 1075, + color: "rgb(77, 76, 76)" + }); + //tele + level.fillBG.push({ //zone 1 + x: 3420, + y: -380, + width: 285, + height: 40, + color: "#ababab" + }) + level.fillBG.push({ //poignée 1 + x: 3555, + y: -367.5, + width: 15, + height: 15, + color: "#474747" + }) + level.fillBG.push({ //entre-deux 1 + x: 3418, + y: -344, + width: 288, + height: 8, + color: "#474747" + }) + level.fillBG.push({ //zone 2 + x: 3420, + y: -340, + width: 285, + height: 40, + color: "#ababab" + }) + level.fillBG.push({ //poignée 2 + x: 3555, + y: -327.5, + width: 15, + height: 15, + color: "#474747" + }) + level.fillBG.push({ //entre-deux 2 + x: 3418, + y: -304, + width: 288, + height: 8, + color: "#474747" + }) + level.fillBG.push({ //zone 3 + x: 3420, + y: -300, + width: 285, + height: 45, + color: "#ababab" + }) + level.fillBG.push({ //poignée 3 + x: 3555, + y: -285, + width: 15, + height: 15, + color: "#474747" + }) + level.fillBG.push({ //door bathroom + x: 3800, + y: -1275, + width: 250, + height: 425, + color: "rgba(141, 141, 141,1)", + }) + level.fillBG.push({ //door bathroom //top border + x: 3800, + y: -1275, + width: 250, + height: 3, + color: "#000", + }) + level.fillBG.push({ //door bathroom //right border + x: 4048, + y: -1275, + width: 3, + height: 425, + color: "#000", + }) + level.fillBG.push({ //door bathroom //left border + x: 3800, + y: -1275, + width: 3, + height: 425, + color: "#000", + }) + level.fillBG.push({ //poignée door bathroom + x: 3830, + y: -1050, + width: 35, + height: 10, + color: "#000", + }) + level.fillBG.push({ //background bathroom + x: 4050, + y: -1425, + width: 1125, + height: 600, + // color:"#c1d7db" + color: "rgba(225, 242, 245,0.6)" + }) + level.fillBG.push({ //window + x: 1736, + y: -1300, + width: 3, + height: 150, + color: "#444" + }) + level.fillBG.push({ //window + x: 1650, + y: -1224, + width: 175, + height: 3, + color: "#444" + }) + let color = Math.random().toString(16).substr(-6); + level.fillBG.push({ //écran + x: 3375, + y: -625, + width: 375, + height: 175, + color: '#' + color + }) + level.fill.push({ //hidden zone + x: 2800, + y: -400, + width: 275, + height: 175, + color: "rgba(64,64,64,0.97)" + }) + + + function drawCarreaux(x, y, width, height) { + level.fillBG.push({ //carreaux + x: x, + y: y, + width: width, + height: height, + color: "rgba(166, 166, 166,0.8)" + }) + } + for (let i = 0; i < 28; i++) { + drawCarreaux(4050 + i * 40, -1425, 1, 600); + } + for (let i = 0; i < 15; i++) { + drawCarreaux(4050, -1425 + i * 40, 1125, 2); } - } - // } - //remove block if touching - // if (body.length) { - // touching = Matter.Query.collides(this, body) - // for (let i = 0; i < touching.length; i++) { - // if (touching[i].bodyB !== mech.holdingTarget) { - // for (let j = 0, len = body.length; j < len; j++) { - // if (body[j] === touching[i].bodyB) { - // body.splice(j, 1); - // len-- - // Matter.World.remove(engine.world, touching[i].bodyB); - // break; - // } - // } - // } - // } - // } + //chairs + const part1 = Matter.Bodies.rectangle(4525, -255, 25, 200, { + density: 0.0005, + isNotHoldable: true, + }); + const part2 = Matter.Bodies.rectangle(4562, -235, 100, 25, { + density: 0.0005, + isNotHoldable: true, + }); + const part3 = Matter.Bodies.rectangle(4600, -202, 25, 91.5, { + density: 0.0005, + isNotHoldable: true, + }); + const part4 = Matter.Bodies.rectangle(5100, -255, 25, 200, { + density: 0.0005, + isNotHoldable: true, + }); + const part5 = Matter.Bodies.rectangle(5063, -235, 100, 25, { + density: 0.0005, + isNotHoldable: true, + }); + const part6 = Matter.Bodies.rectangle(5025, -202, 25, 91.5, { + density: 0.0005, + isNotHoldable: true, + }); + chair = Body.create({ + parts: [part1, part2, part3], + }); + chair2 = Body.create({ + parts: [part4, part5, part6], + }); + World.add(engine.world, [chair]); + World.add(engine.world, [chair2]); + composite[composite.length] = chair; + composite[composite.length] = chair2; + body[body.length] = part1; + body[body.length] = part2; + body[body.length] = part3; + body[body.length] = part4; + body[body.length] = part5; + body[body.length] = part6; + setTimeout(function() { + chair.collisionFilter.category = cat.body; + chair.collisionFilter.mask = cat.body | cat.player | cat.bullet | cat.mob | cat.mobBullet | cat.map + }, 1000); + setTimeout(function() { + chair2.collisionFilter.category = cat.body; + chair2.collisionFilter.mask = cat.body | cat.player | cat.bullet | cat.mob | cat.mobBullet | cat.map + }, 1000); + var head = Matter.Bodies.rectangle(300, -200 - 60, 34, 40, { + isNotHoldable: true, + }); + var chest = Matter.Bodies.rectangle(300, -200, 55, 80, { + isNotHoldable: true, + }); + var rightUpperArm = Matter.Bodies.rectangle(300 + 39, -200 - 15, 20, 40, { + isNotHoldable: true, + }); + var rightLowerArm = Matter.Bodies.rectangle(300 + 39, -200 + 25, 20, 60, { + isNotHoldable: true, + }); + var leftUpperArm = Matter.Bodies.rectangle(300 - 39, -200 - 15, 20, 40, { + isNotHoldable: true, + }); + var leftLowerArm = Matter.Bodies.rectangle(300 - 39, -200 + 25, 20, 60, { + isNotHoldable: true, + }); + var leftUpperLeg = Matter.Bodies.rectangle(300 - 20, -200 + 57, 20, 40, { + isNotHoldable: true, + }); + var leftLowerLeg = Matter.Bodies.rectangle(300 - 20, -200 + 97, 20, 60, { + isNotHoldable: true, + }); + var rightUpperLeg = Matter.Bodies.rectangle(300 + 20, -200 + 57, 20, 40, { + isNotHoldable: true, + }); + var rightLowerLeg = Matter.Bodies.rectangle(300 + 20, -200 + 97, 20, 60, { + isNotHoldable: true, + }); - // if (touching.length !== 0 && touching[0].bodyB !== mech.holdingTarget) { - // if (body.length) { - // for (let i = 0; i < body.length; i++) { - // if (body[i] === touching[0].bodyB) { - // body.splice(i, 1); - // break; - // } - // } - // } - // Matter.World.remove(engine.world, touching[0].bodyB); - // } - } + //man + var person = Body.create({ + parts: [chest, head, leftLowerArm, leftUpperArm, + rightLowerArm, rightUpperArm, leftLowerLeg, + rightLowerLeg, leftUpperLeg, rightUpperLeg + ], + }); + World.add(engine.world, [person]); + composite[composite.length] = person + body[body.length] = chest + body[body.length] = head + body[body.length] = part3 + body[body.length] = leftLowerLeg + body[body.length] = leftUpperLeg + body[body.length] = leftUpperArm + body[body.length] = leftLowerArm + body[body.length] = rightLowerLeg + body[body.length] = rightUpperLeg + body[body.length] = rightLowerArm + body[body.length] = rightUpperArm + setTimeout(function() { + person.collisionFilter.category = cat.body; + person.collisionFilter.mask = cat.body | cat.player | cat.bullet | cat.mob | cat.mobBullet | cat.map + }, 1000); - const portalA = composite[composite.length] = Bodies.rectangle(centerA.x, centerA.y, width, height, { - isSensor: true, - angle: angleA, - color: "hsla(197, 100%, 50%,0.7)", - draw: draw, - }); - const portalB = composite[composite.length] = Bodies.rectangle(centerB.x, centerB.y, width, height, { - isSensor: true, - angle: angleB, - color: "hsla(29, 100%, 50%, 0.7)", - draw: draw - }); - const mapA = composite[composite.length] = Bodies.rectangle(centerA.x - 0.5 * unitA.x * mapWidth, centerA.y - 0.5 * unitA.y * mapWidth, mapWidth, height + 10, { - collisionFilter: { - category: cat.map, - mask: cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet - }, - unit: unitA, - angle: angleA, - color: game.draw.mapFill, - draw: draw, - query: query, - lastPortalCycle: 0 - }); - Matter.Body.setStatic(mapA, true); //make static - World.add(engine.world, mapA); //add to world + //rez de chaussée + spawn.mapRect(-200, 0, 5400, 100); //ground + spawn.mapRect(1150, -255, 4050, 355); //additionnal ground + spawn.mapRect(800, -255, 400, 90); //1st step + spawn.mapRect(650, -170, 550, 90); //2nd step + spawn.mapRect(500, -85, 700, 90); //3rd step + spawn.mapRect(1150, -850, 50, 175); //porte entrée + spawn.bodyRect(1162.5, -675, 25, 420) //porte entrée + spawn.mapRect(1150, -850, 1500, 50); //plafond 1 + spawn.mapRect(3025, -850, 2175, 50); //plafond 2 + spawn.mapRect(5150, -850, 50, 650); //mur cuisine + //lave-vaisselle + spawn.mapRect(4225, -400, 25, 150); + spawn.mapRect(4225, -400, 175, 25); + spawn.mapRect(4375, -400, 25, 150); + spawn.bodyRect(4350, -350, 20, 40); + spawn.bodyRect(4325, -325, 20, 20); + spawn.bodyRect(4325, -275, 20, 20); + //escalier + spawn.mapRect(3025, -850, 50, 225); + spawn.mapRect(2925, -775, 150, 150); + spawn.mapRect(2800, -700, 275, 75); + spawn.mapRect(2575, -400, 175, 175); + spawn.mapRect(2475, -325, 175, 100); + spawn.mapRect(2675, -475, 400, 100); + spawn.mapRect(2675, -475, 150, 250); + //cuisine + spawn.mapRect(4025, -850, 50, 175); //porte cuisine + spawn.mapRect(4025, -375, 50, 125); //porte cuisine - const mapB = composite[composite.length] = Bodies.rectangle(centerB.x - 0.5 * unitB.x * mapWidth, centerB.y - 0.5 * unitB.y * mapWidth, mapWidth, height + 10, { - collisionFilter: { - category: cat.map, - mask: cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet - }, - unit: unitB, - angle: angleB, - color: game.draw.mapFill, - draw: draw, - query: query, - lastPortalCycle: 0, - }); - Matter.Body.setStatic(mapB, true); //make static - World.add(engine.world, mapB); //add to world + map[map.length] = Bodies.polygon(4050, -675, 0, 15); //circle above door + spawn.bodyRect(4040, -650, 20, 260, 1, spawn.propsDoor); // door + body[body.length - 1].isNotHoldable = true; + //makes door swing + consBB[consBB.length] = Constraint.create({ + bodyA: body[body.length - 1], + pointA: { + x: 0, + y: -130 + }, + bodyB: map[map.length - 1], + stiffness: 1 + }); + World.add(engine.world, consBB[consBB.length - 1]); - mapA.portal = portalA - mapB.portal = portalB - mapA.portalPair = mapB - mapB.portalPair = mapA - return [portalA, portalB, mapA, mapB] - }, - hazard(x, y, width, height, damage = 0.0005, color = "hsla(160, 100%, 35%,0.75)", isOptical = false) { - return { - min: { - x: x, - y: y - }, - max: { - x: x + width, - y: y + height - }, - width: width, - height: height, - maxHeight: height, - isOn: true, - query() { - if (this.isOn && this.height > 0 && Matter.Query.region([player], this).length && !(mech.isCloak && isOptical)) { - if (damage < 0.02) { - mech.damage(damage) - } else if (mech.immuneCycle < mech.cycle) { - mech.immuneCycle = mech.cycle + mod.collisionImmuneCycles; - mech.damage(damage) - game.drawList.push({ //add dmg to draw queue - x: player.position.x, - y: player.position.y, - radius: damage * 1500, - color: game.mobDmgColor, - time: 20 + //table + chaises + spawn.mapRect(4025, -850, 50, 175); + spawn.mapRect(4650, -375, 325, 25); + spawn.mapRect(4700, -350, 25, 100); + spawn.mapRect(4900, -350, 25, 100); + spawn.bodyRect(4875, -400, 75, 25); + spawn.bodyRect(4700, -400, 75, 25); + + //murs télé + spawn.mapRect(3400, -400, 20, 150); + spawn.mapRect(3705, -400, 20, 150); + spawn.mapRect(3400, -400, 325, 20); + //socle écran + spawn.mapRect(3500, -415, 125, 17); + spawn.mapRect(3550, -450, 25, 50); + // ??? + spawn.bodyRect(3075, -375, 125, 125); + spawn.bodyRect(3075, -400, 50, 25); + spawn.bodyRect(3725, -325, 100, 75); + spawn.bodyRect(3375, -275, 25, 25); + // premier étage + spawn.mapRect(1150, -1450, 4050, 50); + spawn.mapRect(5150, -1450, 50, 650); + spawn.mapRect(1150, -1450, 50, 300); + spawn.mapRect(1150, -900, 50, 100); + spawn.mapVertex(1066, -730, "-200 60 0 -60 100 -60 100 60") + //chambre + spawn.mapRect(2350, -1450, 50, 175); //porte chambre + //lit + spawn.mapRect(1475, -1025, 25, 225); //pied de lit 1 + spawn.mapRect(1850, -925, 25, 125); //pied de lit 2 + spawn.mapRect(1475, -925, 400, 50); //sommier + spawn.bodyRect(1500, -950, 375, 25); //matelat + spawn.bodyRect(1500, -1000, 75, 50); //oreiller + //table + spawn.bodyRect(1950, -1000, 30, 150); //pied table + spawn.bodyRect(2250, -1000, 30, 150); //pied table + spawn.bodyRect(1920, -1025, 390, 25); //table + //salle de bain + spawn.mapRect(4025, -1450, 50, 175); //porte salle de bain + map[map.length] = Bodies.polygon(5050, -925, 0, 35.4); + spawn.mapRect(5015, -960, 125, 40); + spawn.mapRect(5050, -925, 90, 35.4); + spawn.mapVertex(5086.5, -875, "100 60 -30 60 20 0 100 0") + spawn.mapRect(5125, -1070, 15, 120) + spawn.bodyRect(5016, -965, 108, 15) + //baignoire + spawn.mapVertex(4316, -965, "30 100 0 100 -80 -50 30 -50") //bord 1 + spawn.mapVertex(4675, -961.5, "30 100 0 100 0 -50 80 -50") //bord 2 + spawn.mapVertex(4400, -860, "0 -20 -20 20 20 20 0 -20") //pied 1 + spawn.mapVertex(4600, -860, "0 -20 -20 20 20 20 0 -20") //pied 2 + spawn.mapRect(4325, -900, 350, 25); //fond baignoire + spawn.mapRect(4300, -1175, 25, 175); + spawn.mapRect(4300, -1175, 125, 25); + spawn.mapRect(4400, -1175, 25, 50); //pied pommeau de douche + spawn.mapVertex(4412.5, -1105, "-20 -20 -30 40 30 40 20 -20") //pommeau de douche + + //grenier + spawn.mapRect(1150, -1475, 50, 50); + spawn.mapRect(1150, -1800, 50, 175); + spawn.mapRect(5150, -1800, 50, 400); //murs + spawn.mapVertex(1300, -1900, "-150 200 -200 200 50 0 100 0"); + spawn.mapVertex(1800, -2300, "-150 200 -200 200 175 -100 225 -100"); + spawn.mapRect(1390, -2180, 250, 30); //lucarne + spawn.mapVertex(5050, -1900, "150 200 200 200 -50 0 -100 0"); + spawn.mapVertex(4550, -2300, "150 200 200 200 -175 -100 -225 -100"); + spawn.mapRect(4710, -2175, 250, 25); //lucarne 2 + spawn.mapRect(5150, -1450, 200, 50); + //obstacles + spawn.mapRect(3775, -1800, 99, 50); + spawn.mapRect(2425, -2150, 50, 425); + spawn.mapRect(2150, -1775, 325, 50); + spawn.mapRect(3825, -2150, 50, 750); + spawn.mapRect(3826, -2150, 149, 50); + spawn.mapRect(4125, -2150, 149, 50); + spawn.mapRect(4225, -2150, 50, 450); + spawn.mapRect(4225, -1750, 250, 50); + level.chain(2495, -2130, 0, true, 10); + + spawn.bodyRect(2950, -375, 120, 120) //bloc hidden zone + spawn.bodyRect(2350, -1850, 75, 75); + spawn.bodyRect(4275, -1900, 75, 100); + spawn.bodyRect(4825, -1650, 325, 200); + spawn.bodyRect(5025, -1725, 25, 25); + spawn.bodyRect(4900, -1700, 200, 75); + spawn.mapVertex(2950, -2096, "-75 -50 75 -50 75 0 0 100 -75 0") + + /*cheminée + roof*/ + spawn.mapRect(1963, -2450, 2425, 35); + spawn.mapRect(2925, -2900, 125, 480); + spawn.mapRect(2900, -2900, 175, 75); + spawn.mapRect(2900, -2975, 25, 100); + spawn.mapRect(3050, -2975, 25, 100); + spawn.mapRect(2875, -3000, 225, 25); + // lampadaire + jump + spawn.mapRect(1000, -1450, 200, 25); + spawn.mapRect(500, -1150, 275, 25); + spawn.mapRect(750, -1150, 25, 75); + spawn.mapRect(500, -1150, 25, 75); + spawn.mapRect(450, -1075, 125, 50); + spawn.mapRect(700, -1075, 125, 50); + spawn.mapRect(2985, -4600, 0.1, 1700) + + //bodyRects ~= debris + spawn.bodyRect(1740, -475, 80, 220) + spawn.bodyRect(1840, -290, 38, 23) + spawn.bodyRect(1200 + 1475 * Math.random(), -350, 15 + 110 * Math.random(), 15 + 110 * Math.random()); + spawn.bodyRect(1200 + 1475 * Math.random(), -350, 15 + 110 * Math.random(), 15 + 110 * Math.random()); + spawn.bodyRect(3070 + 600 * Math.random(), -1100, 20 + 50 * Math.random(), 150 + 100 * Math.random()) + spawn.bodyRect(3050 + 1000 * Math.random(), -920, 30 + 100 * Math.random(), 15 + 65 * Math.random()); + spawn.bodyRect(1600 + 250 * Math.random(), -1540, 80, 220) //boss room + spawn.debris(3070, -900, 1000, 3); //16 debris per level + spawn.debris(1200, -350, 1475, 4); //16 debris per level + spawn.debris(1250, -1550, 3565, 9); //16 debris per level + + powerUps.chooseRandomPowerUp(2860, -270); + // Mobs + + spawn.randomSmallMob(1385, -600, 1); + spawn.randomSmallMob(5000, -680, 1); + spawn.randomSmallMob(4750, -925, 1); + spawn.randomSmallMob(2300, -1830, 1); + spawn.randomMob(3170, -720, 0.8); + spawn.randomMob(3700, -975, 0.8); + spawn.randomMob(2625, -1150, 0.7); + spawn.randomMob(4175, -750, 0.7); + spawn.randomMob(2100, -370, 0.7); + spawn.randomMob(2000, -1230, 0.7); + spawn.randomMob(4175, -1075, 0.6); + spawn.randomMob(3965, -1650, 0.6) + spawn.randomMob(4650, -1750, 0.6); + spawn.randomMob(830, -1170, 0.5); + spawn.randomBoss(3730, -1100, 0.5); + spawn.randomMob(2650, -2250, 0.3); + spawn.randomMob(1615, -2270, 0.3); + spawn.randomMob(1380, -1280, 0.25); + spawn.randomMob(2280, -650, 0.2); + spawn.randomBoss(2450, -2650, 0.2); + spawn.randomMob(3800, -580, 0.2); + spawn.randomMob(4630, -425, 0.1); + spawn.randomBoss(630, -1300, -0.1); + spawn.randomBoss(3450, -2880, -0.2) + + if (game.difficulty > 3) { + if (Math.random() < 0.16) { + spawn.tetherBoss(3380, -1775) + cons[cons.length] = Constraint.create({ + pointA: { + x: 3775, + y: -1775 + }, + bodyB: mob[mob.length - 1], + stiffness: 0.00018 + 0.000007 * level.levelsCleared + }); + World.add(engine.world, cons[cons.length - 1]); + if (game.difficulty > 4) spawn.nodeBoss(3380, -1775, "spawns", 8, 20, 105); //chance to spawn a ring of exploding mobs around this boss + + } else { + spawn.randomLevelBoss(3100, -1850, ["shooterBoss", "spiderBoss", "launcherBoss", "laserTargetingBoss", "snakeBoss", "laserBoss"]); + } + } + }, + //****************************************************************************************************************** + //****************************************************************************************************************** + //****************************************************************************************************************** + //****************************************************************************************************************** + difficultyIncrease(num = 1) { + for (let i = 0; i < num; i++) { + game.difficulty++ + b.dmgScale *= 0.93; //damage done by player decreases each level + if (game.accelScale < 5) game.accelScale *= 1.02 //mob acceleration increases each level + if (game.lookFreqScale > 0.2) game.lookFreqScale *= 0.98 //mob cycles between looks decreases each level + if (game.CDScale > 0.2) game.CDScale *= 0.97 //mob CD time decreases each level + } + game.dmgScale = 0.38 * game.difficulty //damage done by mobs increases each level + game.healScale = 1 / (1 + game.difficulty * 0.06) //a higher denominator makes for lower heals // mech.health += heal * game.healScale; + }, + difficultyDecrease(num = 1) { //used in easy mode for game.reset() + for (let i = 0; i < num; i++) { + game.difficulty-- + b.dmgScale /= 0.93; //damage done by player decreases each level + if (game.accelScale > 0.2) game.accelScale /= 1.02 //mob acceleration increases each level + if (game.lookFreqScale < 5) game.lookFreqScale /= 0.98 //mob cycles between looks decreases each level + if (game.CDScale < 5) game.CDScale /= 0.97 //mob CD time decreases each level + } + if (game.difficulty < 1) game.difficulty = 0; + game.dmgScale = 0.38 * game.difficulty //damage done by mobs increases each level + if (game.dmgScale < 0.1) game.dmgScale = 0.1; + game.healScale = 1 / (1 + game.difficulty * 0.06) + }, + difficultyText() { + if (game.difficultyMode === 1) { + return "easy" + } else if (game.difficultyMode === 2) { + return "normal" + } else if (game.difficultyMode === 4) { + return "hard" + } else if (game.difficultyMode === 6) { + return "why" + } + }, + levelAnnounce() { + if (level.levelsCleared === 0) { + document.title = "n-gon: intro (" + level.difficultyText() + ")"; + } else { + document.title = "n-gon: L" + (level.levelsCleared) + " " + level.levels[level.onLevel] + " (" + level.difficultyText() + ")"; + } + }, + 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 + } + + + 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; + } + game.updateModHUD(); + game.clearNow = true; //triggers in game.clearMap to remove all physics bodies and setup for new map + }, + playerExitCheck() { + if ( + player.position.x > level.exit.x && + player.position.x < level.exit.x + 100 && + player.position.y > level.exit.y - 150 && + player.position.y < level.exit.y - 40 && + player.velocity.y < 0.1 + ) { + level.nextLevel() + } + }, + setPosToSpawn(xPos, yPos) { + mech.spawnPos.x = mech.pos.x = xPos; + mech.spawnPos.y = mech.pos.y = yPos; + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + mech.transX = mech.transSmoothX = canvas.width2 - mech.pos.x; + mech.transY = mech.transSmoothY = canvas.height2 - mech.pos.y; + mech.Vx = mech.spawnVel.x; + mech.Vy = mech.spawnVel.y; + player.force.x = 0; + player.force.y = 0; + Matter.Body.setPosition(player, mech.spawnPos); + Matter.Body.setVelocity(player, mech.spawnVel); + }, + enter: { + x: 0, + y: 0, + draw() { + ctx.beginPath(); + ctx.moveTo(level.enter.x, level.enter.y + 30); + ctx.lineTo(level.enter.x, level.enter.y - 80); + ctx.bezierCurveTo(level.enter.x, level.enter.y - 170, level.enter.x + 100, level.enter.y - 170, level.enter.x + 100, level.enter.y - 80); + ctx.lineTo(level.enter.x + 100, level.enter.y + 30); + ctx.lineTo(level.enter.x, level.enter.y + 30); + ctx.fillStyle = "#ccc"; + ctx.fill(); + } + }, + exit: { + x: 0, + y: 0, + draw() { + ctx.beginPath(); + ctx.moveTo(level.exit.x, level.exit.y + 30); + ctx.lineTo(level.exit.x, level.exit.y - 80); + ctx.bezierCurveTo(level.exit.x, level.exit.y - 170, level.exit.x + 100, level.exit.y - 170, level.exit.x + 100, level.exit.y - 80); + ctx.lineTo(level.exit.x + 100, level.exit.y + 30); + ctx.lineTo(level.exit.x, level.exit.y + 30); + ctx.fillStyle = "#0ff"; + ctx.fill(); + } + }, + fillBG: [], + drawFillBGs() { + for (let i = 0, len = level.fillBG.length; i < len; ++i) { + const f = level.fillBG[i]; + ctx.fillStyle = f.color; + ctx.fillRect(f.x, f.y, f.width, f.height); + } + }, + fill: [], + drawFills() { + for (let i = 0, len = level.fill.length; i < len; ++i) { + const f = level.fill[i]; + ctx.fillStyle = f.color; + ctx.fillRect(f.x, f.y, f.width, f.height); + } + }, + queryList: [], //queries do actions on many objects in regions + checkQuery() { + let bounds, action, info; + + function isInZone(targetArray) { + let results = Matter.Query.region(targetArray, bounds); + for (let i = 0, len = results.length; i < len; ++i) { + level.queryActions[action](results[i], info); + } + } + for (let i = 0, len = level.queryList.length; i < len; ++i) { + bounds = level.queryList[i].bounds; + action = level.queryList[i].action; + info = level.queryList[i].info; + for (let j = 0, l = level.queryList[i].groups.length; j < l; ++j) { + isInZone(level.queryList[i].groups[j]); + } + } + }, + //oddly query regions can't get smaller than 50 width? + addQueryRegion(x, y, width, height, action, groups = [ + [player], body, mob, powerUp, bullet + ], info) { + level.queryList[level.queryList.length] = { + bounds: { + min: { + x: x, + y: y + }, + max: { + x: x + width, + y: y + height + } + }, + action: action, + groups: groups, + info: info + }; + }, + queryActions: { + bounce(target, info) { + //jerky fling upwards + Matter.Body.setVelocity(target, { + x: info.Vx + (Math.random() - 0.5) * 6, + y: info.Vy }); - } - const drain = 0.005 - if (mech.energy > drain) mech.energy -= drain - } - }, - draw() { - if (this.isOn) { - ctx.fillStyle = color - ctx.fillRect(this.min.x, this.min.y, this.width, this.height) - } - }, - level(isFill) { - if (!mech.isBodiesAsleep) { - const growSpeed = 1 - if (isFill) { - if (this.height < this.maxHeight) { - this.height += growSpeed - this.min.y -= growSpeed - this.max.y = this.min.y + this.height - } - } else if (this.height > 0) { - this.height -= growSpeed - this.min.y += growSpeed - this.max.y = this.min.y + this.height - } - } - } - } - }, - chain(x, y, angle = 0, isAttached = true, len = 15, radius = 20, stiffness = 1, damping = 1) { - const gap = 2 * radius - const unit = { - x: Math.cos(angle), - y: Math.sin(angle) - } - for (let i = 0; i < len; i++) { - body[body.length] = Bodies.polygon(x + gap * unit.x * i, y + gap * unit.y * i, 12, radius, { - inertia: Infinity, - isNotHoldable: true - }); - } - for (let i = 1; i < len; i++) { //attach blocks to each other - consBB[consBB.length] = Constraint.create({ - bodyA: body[body.length - i], - bodyB: body[body.length - i - 1], - stiffness: stiffness, - damping: damping - }); - World.add(engine.world, consBB[consBB.length - 1]); - } - cons[cons.length] = Constraint.create({ //pin first block to a point in space - pointA: { - x: x, - y: y - }, - bodyB: body[body.length - len], - stiffness: 1, - damping: damping - }); - World.add(engine.world, cons[cons.length - 1]); - if (isAttached) { - cons[cons.length] = Constraint.create({ //pin last block to a point in space - pointA: { - x: x + gap * unit.x * (len - 1), - y: y + gap * unit.y * (len - 1) + target.torque = (Math.random() - 0.5) * 2 * target.mass; }, - bodyB: body[body.length - 1], - stiffness: 1, - damping: damping - }); - World.add(engine.world, cons[cons.length - 1]); - } - }, + boost(target, yVelocity) { + mech.buttonCD_jump = 0; // reset short jump counter to prevent short jumps on boosts + mech.hardLandCD = 0 // disable hard landing + if (target.velocity.y > 30) { + Matter.Body.setVelocity(target, { + x: target.velocity.x + (Math.random() - 0.5) * 2, + y: -15 //gentle bounce if coming down super fast + }); + } else { + Matter.Body.setVelocity(target, { + x: target.velocity.x + (Math.random() - 0.5) * 2, + y: yVelocity + }); + } + + }, + force(target, info) { + if (target.velocity.y < 0) { //gently force up if already on the way up + target.force.x += info.Vx * target.mass; + target.force.y += info.Vy * target.mass; + } else { + target.force.y -= 0.0007 * target.mass; //gently fall in on the way down + } + }, + antiGrav(target) { + target.force.y -= 0.0011 * target.mass; + }, + death(target) { + target.death(); + } + }, + addToWorld() { //needs to be run to put bodies into the world + for (let i = 0; i < body.length; i++) { + //body[i].collisionFilter.group = 0; + if (body[i] !== mech.holdingTarget) { + body[i].collisionFilter.category = cat.body; + body[i].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet + } + body[i].classType = "body"; + World.add(engine.world, body[i]); //add to world + } + for (let i = 0; i < map.length; i++) { + //map[i].collisionFilter.group = 0; + map[i].collisionFilter.category = cat.map; + map[i].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; + Matter.Body.setStatic(map[i], true); //make static + World.add(engine.world, map[i]); //add to world + } + // for (let i = 0; i < cons.length; i++) { + // World.add(engine.world, cons[i]); + // } + + }, + spinner(x, y, width, height, density = 0.001) { + x = x + width / 2 + y = y + height / 2 + const who = body[body.length] = Bodies.rectangle(x, y, width, height, { + collisionFilter: { + category: cat.body, + mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet + }, + isNotHoldable: true, + frictionAir: 0.001, + friction: 1, + frictionStatic: 1, + restitution: 0, + }); + + Matter.Body.setDensity(who, density) + const constraint = Constraint.create({ //fix rotor in place, but allow rotation + pointA: { + x: x, + y: y + }, + bodyB: who, + stiffness: 1, + damping: 1 + }); + World.add(engine.world, constraint); + return constraint + }, + platform(x, y, width, height, speed = 0, density = 0.001) { + x = x + width / 2 + y = y + height / 2 + const who = body[body.length] = Bodies.rectangle(x, y, width, height, { + collisionFilter: { + category: cat.body, + mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet + }, + inertia: Infinity, //prevents rotation + isNotHoldable: true, + friction: 1, + frictionStatic: 1, + restitution: 0, + }); + + Matter.Body.setDensity(who, density) + const constraint = Constraint.create({ //fix rotor in place, but allow rotation + pointA: { + x: x, + y: y + }, + bodyB: who, + stiffness: 0.1, + damping: 0.3 + }); + World.add(engine.world, constraint); + constraint.plat = { + position: who.position, + speed: speed, + } + constraint.pauseUntilCycle = 0 //to to pause platform at top and bottom + return constraint + }, + rotor(x, y, rotate = 0, radius = 800, width = 40, density = 0.0005) { + const rotor1 = Matter.Bodies.rectangle(x, y, width, radius, { + density: density, + isNotHoldable: true + }); + const rotor2 = Matter.Bodies.rectangle(x, y, width, radius, { + angle: Math.PI / 2, + density: density, + isNotHoldable: true + }); + rotor = Body.create({ //combine rotor1 and rotor2 + parts: [rotor1, rotor2], + restitution: 0, + collisionFilter: { + category: cat.body, + mask: cat.body | cat.mob | cat.mobBullet | cat.mobShield | cat.powerUp | cat.player | cat.bullet + }, + }); + Matter.Body.setPosition(rotor, { + x: x, + y: y + }); + World.add(engine.world, [rotor]); + body[body.length] = rotor1 + body[body.length] = rotor2 + + setTimeout(function() { + rotor.collisionFilter.category = cat.body; + rotor.collisionFilter.mask = cat.body | cat.player | cat.bullet | cat.mob | cat.mobBullet //| cat.map + }, 1000); + + const constraint = Constraint.create({ //fix rotor in place, but allow rotation + pointA: { + x: x, + y: y + }, + bodyB: rotor + }); + World.add(engine.world, constraint); + + if (rotate) { + rotor.rotate = function() { + if (!mech.isBodiesAsleep) { + Matter.Body.applyForce(rotor, { + x: rotor.position.x + 100, + y: rotor.position.y + 100 + }, { + x: rotate * rotor.mass, + y: 0 + }) + } else { + Matter.Body.setAngularVelocity(rotor, 0); + } + } + } + composite[composite.length] = rotor + return rotor + }, + button(x, y, width = 126) { + spawn.mapVertex(x + 65, y + 2, "100 10 -100 10 -70 -10 70 -10"); + map[map.length - 1].restitution = 0; + map[map.length - 1].friction = 1; + map[map.length - 1].frictionStatic = 1; + + // const buttonSensor = Bodies.rectangle(x + 35, y - 1, 70, 20, { + // isSensor: true + // }); + + return { + isUp: false, + min: { + x: x + 2, + y: y - 11 + }, + max: { + x: x + width, + y: y - 10 + }, + width: width, + height: 20, + query() { + if (Matter.Query.region(body, this).length === 0 && Matter.Query.region([player], this).length === 0) { + this.isUp = true; + } else { + // if (this.isUp === true) { + // const list = Matter.Query.region(body, this) + // console.log(list) + // if (list.length > 0) { + // Matter.Body.setPosition(list[0], { + // x: this.min.x + width / 2, + // y: list[0].position.y + // }) + // Matter.Body.setVelocity(list[0], { + // x: 0, + // y: 0 + // }); + // } + // } + this.isUp = false; + } + }, + draw() { + ctx.fillStyle = "hsl(0, 100%, 70%)" + if (this.isUp) { + ctx.fillRect(this.min.x, this.min.y - 10, this.width, 20) + } else { + ctx.fillRect(this.min.x, this.min.y - 3, this.width, 25) + } + //draw sensor zone + // ctx.beginPath(); + // sensor = buttonSensor.vertices; + // ctx.moveTo(sensor[0].x, sensor[0].y); + // for (let i = 1; i < sensor.length; ++i) { + // ctx.lineTo(sensor[i].x, sensor[i].y); + // } + // ctx.lineTo(sensor[0].x, sensor[0].y); + // ctx.fillStyle = "rgba(255, 255, 0, 0.3)"; + // ctx.fill(); + } + } + }, + door(x, y, width, height, distance) { + x = x + width / 2 + y = y + height / 2 + const doorBlock = body[body.length] = Bodies.rectangle(x, y, width, height, { + collisionFilter: { + category: cat.body, + mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet + }, + inertia: Infinity, //prevents rotation + isNotHoldable: true, + friction: 1, + frictionStatic: 1, + restitution: 0, + isOpen: false, + openClose() { + if (!mech.isBodiesAsleep) { + if (!this.isOpen) { + if (this.position.y > y - distance) { //try to open + const position = { + x: this.position.x, + y: this.position.y - 1 + } + Matter.Body.setPosition(this, position) + } + } else { + if (this.position.y < y) { //try to close + if ( + Matter.Query.collides(this, [player]).length === 0 && + Matter.Query.collides(this, body).length < 2 && + Matter.Query.collides(this, mob).length === 0 + ) { + const position = { + x: this.position.x, + y: this.position.y + 1 + } + Matter.Body.setPosition(this, position) + } + } + } + } + }, + draw() { + ctx.fillStyle = "#555" + ctx.beginPath(); + const v = this.vertices; + ctx.moveTo(v[0].x, v[0].y); + for (let i = 1; i < v.length; ++i) { + ctx.lineTo(v[i].x, v[i].y); + } + ctx.lineTo(v[0].x, v[0].y); + ctx.fill(); + } + }); + Matter.Body.setStatic(doorBlock, true); //make static + return doorBlock + }, + portal(centerA, angleA, centerB, angleB) { + const width = 50 + const height = 150 + const mapWidth = 200 + const unitA = Matter.Vector.rotate({ + x: 1, + y: 0 + }, angleA) + const unitB = Matter.Vector.rotate({ + x: 1, + y: 0 + }, angleB) + + draw = function() { + ctx.beginPath(); //portal + let v = this.vertices; + ctx.moveTo(v[0].x, v[0].y); + for (let i = 1; i < v.length; ++i) { + ctx.lineTo(v[i].x, v[i].y); + } + ctx.fillStyle = this.color + ctx.fill(); + } + query = function() { + if (Matter.Query.collides(this, [player]).length === 0) { //not touching player + if (player.isInPortal === this) player.isInPortal = null + } else if (player.isInPortal !== this) { //touching player + if (mech.buttonCD_jump === mech.cycle) player.force.y = 0 // undo a jump right before entering the portal + mech.buttonCD_jump = 0 //disable short jumps when letting go of jump key + player.isInPortal = this.portalPair + //teleport + if (this.portalPair.angle % (Math.PI / 2)) { //if left, right up or down + Matter.Body.setPosition(player, this.portalPair.portal.position); + } else { //if at some odd angle + Matter.Body.setPosition(player, this.portalPair.position); + } + //rotate velocity + let mag + if (this.portalPair.angle !== 0 && this.portalPair.angle !== Math.PI) { //portal that fires the player up + mag = Math.max(10, Math.min(50, player.velocity.y * 0.8)) + 11 + } else { + mag = Math.max(6, Math.min(50, Vector.magnitude(player.velocity))) + } + let v = Vector.mult(this.portalPair.unit, mag) + Matter.Body.setVelocity(player, v); + // move bots to follow player + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType) { + // Matter.Body.setPosition(bullet[i], this.portalPair.portal.position); + Matter.Body.setPosition(bullet[i], Vector.add(this.portalPair.portal.position, { + x: 250 * (Math.random() - 0.5), + y: 250 * (Math.random() - 0.5) + })); + Matter.Body.setVelocity(bullet[i], { + x: 0, + y: 0 + }); + } + } + } + // if (body.length) { + for (let i = 0, len = body.length; i < len; i++) { + if (body[i] !== mech.holdingTarget) { + // body[i].bounds.max.x - body[i].bounds.min.x < 100 && body[i].bounds.max.y - body[i].bounds.min.y < 100 + if (Matter.Query.collides(this, [body[i]]).length === 0) { + if (body[i].isInPortal === this) body[i].isInPortal = null + } else if (body[i].isInPortal !== this) { + body[i].isInPortal = this.portalPair + //teleport + if (this.portalPair.angle % (Math.PI / 2)) { //if left, right up or down + Matter.Body.setPosition(body[i], this.portalPair.portal.position); + } else { //if at some odd angle + Matter.Body.setPosition(body[i], this.portalPair.position); + } + //rotate velocity + let mag + if (this.portalPair.angle !== 0 && this.portalPair.angle !== Math.PI) { //portal that fires the player up + mag = Math.max(10, Math.min(50, body[i].velocity.y * 0.8)) + 11 + } else { + mag = Math.max(6, Math.min(50, Vector.magnitude(body[i].velocity))) + } + let v = Vector.mult(this.portalPair.unit, mag) + Matter.Body.setVelocity(body[i], v); + } + } + } + // } + + //remove block if touching + // if (body.length) { + // touching = Matter.Query.collides(this, body) + // for (let i = 0; i < touching.length; i++) { + // if (touching[i].bodyB !== mech.holdingTarget) { + // for (let j = 0, len = body.length; j < len; j++) { + // if (body[j] === touching[i].bodyB) { + // body.splice(j, 1); + // len-- + // Matter.World.remove(engine.world, touching[i].bodyB); + // break; + // } + // } + // } + // } + // } + + // if (touching.length !== 0 && touching[0].bodyB !== mech.holdingTarget) { + // if (body.length) { + // for (let i = 0; i < body.length; i++) { + // if (body[i] === touching[0].bodyB) { + // body.splice(i, 1); + // break; + // } + // } + // } + // Matter.World.remove(engine.world, touching[0].bodyB); + // } + } + + const portalA = composite[composite.length] = Bodies.rectangle(centerA.x, centerA.y, width, height, { + isSensor: true, + angle: angleA, + color: "hsla(197, 100%, 50%,0.7)", + draw: draw, + }); + const portalB = composite[composite.length] = Bodies.rectangle(centerB.x, centerB.y, width, height, { + isSensor: true, + angle: angleB, + color: "hsla(29, 100%, 50%, 0.7)", + draw: draw + }); + const mapA = composite[composite.length] = Bodies.rectangle(centerA.x - 0.5 * unitA.x * mapWidth, centerA.y - 0.5 * unitA.y * mapWidth, mapWidth, height + 10, { + collisionFilter: { + category: cat.map, + mask: cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet + }, + unit: unitA, + angle: angleA, + color: game.draw.mapFill, + draw: draw, + query: query, + lastPortalCycle: 0 + }); + Matter.Body.setStatic(mapA, true); //make static + World.add(engine.world, mapA); //add to world + + const mapB = composite[composite.length] = Bodies.rectangle(centerB.x - 0.5 * unitB.x * mapWidth, centerB.y - 0.5 * unitB.y * mapWidth, mapWidth, height + 10, { + collisionFilter: { + category: cat.map, + mask: cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet + }, + unit: unitB, + angle: angleB, + color: game.draw.mapFill, + draw: draw, + query: query, + lastPortalCycle: 0, + }); + Matter.Body.setStatic(mapB, true); //make static + World.add(engine.world, mapB); //add to world + + mapA.portal = portalA + mapB.portal = portalB + mapA.portalPair = mapB + mapB.portalPair = mapA + return [portalA, portalB, mapA, mapB] + }, + hazard(x, y, width, height, damage = 0.0005, color = "hsla(160, 100%, 35%,0.75)", isOptical = false) { + return { + min: { + x: x, + y: y + }, + max: { + x: x + width, + y: y + height + }, + width: width, + height: height, + maxHeight: height, + isOn: true, + query() { + if (this.isOn && this.height > 0 && Matter.Query.region([player], this).length && !(mech.isCloak && isOptical)) { + if (damage < 0.02) { + mech.damage(damage) + } else if (mech.immuneCycle < mech.cycle) { + mech.immuneCycle = mech.cycle + mod.collisionImmuneCycles; + mech.damage(damage) + game.drawList.push({ //add dmg to draw queue + x: player.position.x, + y: player.position.y, + radius: damage * 1500, + color: game.mobDmgColor, + time: 20 + }); + } + const drain = 0.005 + if (mech.energy > drain) mech.energy -= drain + } + }, + draw() { + if (this.isOn) { + ctx.fillStyle = color + ctx.fillRect(this.min.x, this.min.y, this.width, this.height) + } + }, + level(isFill) { + if (!mech.isBodiesAsleep) { + const growSpeed = 1 + if (isFill) { + if (this.height < this.maxHeight) { + this.height += growSpeed + this.min.y -= growSpeed + this.max.y = this.min.y + this.height + } + } else if (this.height > 0) { + this.height -= growSpeed + this.min.y += growSpeed + this.max.y = this.min.y + this.height + } + } + } + } + }, + chain(x, y, angle = 0, isAttached = true, len = 15, radius = 20, stiffness = 1, damping = 1) { + const gap = 2 * radius + const unit = { + x: Math.cos(angle), + y: Math.sin(angle) + } + for (let i = 0; i < len; i++) { + body[body.length] = Bodies.polygon(x + gap * unit.x * i, y + gap * unit.y * i, 12, radius, { + inertia: Infinity, + isNotHoldable: true + }); + } + for (let i = 1; i < len; i++) { //attach blocks to each other + consBB[consBB.length] = Constraint.create({ + bodyA: body[body.length - i], + bodyB: body[body.length - i - 1], + stiffness: stiffness, + damping: damping + }); + World.add(engine.world, consBB[consBB.length - 1]); + } + cons[cons.length] = Constraint.create({ //pin first block to a point in space + pointA: { + x: x, + y: y + }, + bodyB: body[body.length - len], + stiffness: 1, + damping: damping + }); + World.add(engine.world, cons[cons.length - 1]); + if (isAttached) { + cons[cons.length] = Constraint.create({ //pin last block to a point in space + pointA: { + x: x + gap * unit.x * (len - 1), + y: y + gap * unit.y * (len - 1) + }, + bodyB: body[body.length - 1], + stiffness: 1, + damping: damping + }); + World.add(engine.world, cons[cons.length - 1]); + } + }, }; \ No newline at end of file diff --git a/js/mods.js b/js/mods.js index 9252f20..48a415e 100644 --- a/js/mods.js +++ b/js/mods.js @@ -87,12 +87,12 @@ const mod = { if (mod.isDamageForGuns) dmg *= 1 + 0.07 * b.inventory.length if (mod.isLowHealthDmg) dmg *= 1 + 0.6 * Math.max(0, 1 - mech.health) if (mod.isHarmDamage && mech.lastHarmCycle + 600 > mech.cycle) dmg *= 2; - if (mod.isEnergyLoss) dmg *= 1.43; + if (mod.isEnergyLoss) dmg *= 1.5; if (mod.isAcidDmg && mech.health > 1) dmg *= 1.4; 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.05 * powerUps.reroll.rerolls + if (mod.isRerollDamage) dmg *= 1 + 0.06 * 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 @@ -149,7 +149,7 @@ const mod = { }, { name: "acute stress response", - description: "increase damage by 43%
if a mob dies drain stored energy by 25%", + description: "increase damage by 50%
if a mob dies drain stored energy by 25%", maxCount: 1, count: 0, allowed() { @@ -245,7 +245,7 @@ const mod = { }, { name: "perturbation theory", - description: "increase damage by 5%
for each of your rerolls", + description: "increase damage by 6%
for each of your rerolls", maxCount: 1, count: 0, allowed() { @@ -259,26 +259,6 @@ const mod = { mod.isRerollDamage = false; } }, - { - name: "Ψ(t) collapse", - description: "60% decreased delay after firing
if you have no rerolls", - maxCount: 1, - count: 0, - allowed() { - return powerUps.reroll.rerolls === 0 && !mod.manyWorlds - }, - requires: "no rerolls", - effect() { - mod.isRerollHaste = true; - mod.rerollHaste = 0.4; - b.setFireCD(); - }, - remove() { - mod.isRerollHaste = false; - mod.rerollHaste = 1; - b.setFireCD(); - } - }, { name: "electrostatic discharge", description: "increase damage by 20%
20% increased delay after firing", @@ -296,6 +276,26 @@ const mod = { b.setFireCD(); } }, + { + name: "Ψ(t) collapse", + description: "66% decreased delay after firing
if you have no rerolls", + maxCount: 1, + count: 0, + allowed() { + return powerUps.reroll.rerolls === 0 && !mod.manyWorlds + }, + requires: "no rerolls", + effect() { + mod.isRerollHaste = true; + mod.rerollHaste = 0.33; + b.setFireCD(); + }, + remove() { + mod.isRerollHaste = false; + mod.rerollHaste = 1; + b.setFireCD(); + } + }, { name: "auto-loading heuristics", description: "30% decreased delay after firing", @@ -558,7 +558,7 @@ const mod = { }, { name: "foam-bot upgrade", - description: "200% increased foam size
applies to all current and future foam-bots", + description: "150% increased foam size
applies to all current and future foam-bots", maxCount: 1, count: 0, allowed() { @@ -675,7 +675,7 @@ const mod = { }, { name: "orbital-bot upgrade", - description: "125% increased damage
applies to all current and future orbit-bots", + description: "increase damage by 100% and radius by 30%
applies to all current and future orbit-bots", maxCount: 1, count: 0, allowed() { @@ -684,15 +684,24 @@ const mod = { requires: "2 or more orbital bots", effect() { mod.isOrbitBotUpgrade = true + const range = 190 + 60 * mod.isOrbitBotUpgrade for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType === 'orbit') bullet[i].isUpgraded = true + if (bullet[i].botType === 'orbit') { + bullet[i].isUpgraded = true + bullet[i].range = range + bullet[i].orbitalSpeed = Math.sqrt(0.25 / range) + } } }, remove() { mod.isOrbitBotUpgrade = false + const range = 190 + 60 * mod.isOrbitBotUpgrade for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType === 'orbit') bullet[i].isUpgraded = false + if (bullet[i].botType === 'orbit') { + bullet[i].range = range + bullet[i].orbitalSpeed = Math.sqrt(0.25 / range) + } } } }, @@ -946,7 +955,7 @@ const mod = { maxCount: 1, count: 0, allowed() { - return mod.isPiezo + return mod.isPiezo && !mod.timeEnergyRegen }, requires: "piezoelectricity", effect: () => { @@ -1169,7 +1178,7 @@ const mod = { name: "anthropic principle", nameInfo: "", addNameInfo() { - setTimeout(function () { + setTimeout(function() { powerUps.reroll.changeRerolls(0) }, 1000); }, @@ -1182,7 +1191,7 @@ const mod = { requires: "at least 1 reroll", effect() { mod.isDeathAvoid = true; - setTimeout(function () { + setTimeout(function() { powerUps.reroll.changeRerolls(0) }, 1000); }, @@ -1254,7 +1263,7 @@ const mod = { name: "entanglement", nameInfo: "", addNameInfo() { - setTimeout(function () { + setTimeout(function() { game.boldActiveGunHUD(); }, 1000); }, @@ -1267,7 +1276,7 @@ const mod = { requires: "at least 2 guns", effect() { mod.isEntanglement = true - setTimeout(function () { + setTimeout(function() { game.boldActiveGunHUD(); }, 1000); @@ -1893,7 +1902,7 @@ const mod = { }, { name: "wave packet", - description: "wave beam emits two oscillating particles
decrease wave damage by 33%", + description: "wave beam emits two oscillating particles
decrease wave damage by 20%", maxCount: 1, count: 0, allowed() { @@ -1943,7 +1952,7 @@ const mod = { }, { name: "recursion", - description: "after missiles explode they have a
30% chance to launch a larger missile", + description: "after missiles explode they have a
20% chance to launch a larger missile", maxCount: 6, count: 0, allowed() { @@ -2622,6 +2631,24 @@ const mod = { b.setFireCD(); } }, + { + name: "time crystals", + description: "quadruple your default energy regeneration", + maxCount: 1, + count: 0, + allowed() { + return mech.fieldUpgrades[mech.fieldMode].name === "time dilation field" + }, + requires: "time dilation field", + effect: () => { + mod.energyRegen = 0.004; + mech.fieldRegen = mod.energyRegen; + }, + remove() { + mod.energyRegen = 0.001; + mech.fieldRegen = mod.energyRegen; + } + }, { name: "plasma jet", description: "increase plasma torch's range by 27%", @@ -2871,7 +2898,7 @@ const mod = { }, { name: "cosmic string", - description: "when you tunnel through a wormhole
mobs between the endpoints take damage", + description: "stun and do radioactive damage to mobs
if you tunnel through them with a wormhole", maxCount: 1, count: 0, allowed() { @@ -3178,5 +3205,6 @@ const mod = { isWormBullets: null, isWideLaser: null, wideLaser: null, - isPulseLaser: null + isPulseLaser: null, + timeEnergyRegen: null } \ No newline at end of file diff --git a/js/player.js b/js/player.js index 7d2ed4a..322057c 100644 --- a/js/player.js +++ b/js/player.js @@ -3,2495 +3,2462 @@ let player, jumpSensor, playerBody, playerHead, headSensor; // player Object Prototype ********************************************* const mech = { - spawn() { - //load player in matter.js physic engine - // let vector = Vertices.fromPath("0 40 50 40 50 115 0 115 30 130 20 130"); //player as a series of vertices - let vertices = Vertices.fromPath("0,40, 50,40, 50,115, 30,130, 20,130, 0,115, 0,40"); //player as a series of vertices - playerBody = Matter.Bodies.fromVertices(0, 0, vertices); - jumpSensor = Bodies.rectangle(0, 46, 36, 6, { - //this sensor check if the player is on the ground to enable jumping - sleepThreshold: 99999999999, - isSensor: true - }); - vertices = Vertices.fromPath("16 -82 2 -66 2 -37 43 -37 43 -66 30 -82"); - playerHead = Matter.Bodies.fromVertices(0, -55, vertices); //this part of the player lowers on crouch - headSensor = Bodies.rectangle(0, -57, 48, 45, { - //senses if the player's head is empty and can return after crouching - sleepThreshold: 99999999999, - isSensor: true - }); - player = Body.create({ - //combine jumpSensor and playerBody - parts: [playerBody, playerHead, jumpSensor, headSensor], - inertia: Infinity, //prevents player rotation - friction: 0.002, - frictionAir: 0.001, - //frictionStatic: 0.5, - restitution: 0, - sleepThreshold: Infinity, - collisionFilter: { - group: 0, - category: cat.player, - mask: cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield - }, - death() { - mech.death(); - } - }); - Matter.Body.setMass(player, mech.mass); - World.add(engine.world, [player]); - - mech.holdConstraint = Constraint.create({ - //holding body constraint - pointA: { - x: 0, - y: 0 - }, - bodyB: jumpSensor, //setting constraint to jump sensor because it has to be on something until the player picks up things - stiffness: 0.4 - }); - World.add(engine.world, mech.holdConstraint); - }, - cycle: 0, - lastKillCycle: 0, - lastHarmCycle: 0, - width: 50, - radius: 30, - fillColor: "#fff", - fillColorDark: "#ccc", - color: { - hue: 0, - sat: 0, - light: 100, - }, - setFillColors() { - // console.log(mech.color) - this.fillColor = `hsl(${mech.color.hue},${mech.color.sat}%,${mech.color.light}%)` - this.fillColorDark = `hsl(${mech.color.hue},${mech.color.sat}%,${mech.color.light-20}%)` - }, - height: 42, - yOffWhen: { - crouch: 22, - stand: 49, - jump: 70 - }, - defaultMass: 5, - mass: 5, - FxNotHolding: 0.015, - Fx: 0.016, //run Force on ground // - jumpForce: 0.42, - setMovement() { - mech.Fx = 0.016 * mod.squirrelFx * mod.fastTime; - mech.jumpForce = 0.42 * mod.squirrelJump * mod.fastTimeJump; - }, - FxAir: 0.016, // 0.4/5/5 run Force in Air - yOff: 70, - yOffGoal: 70, - onGround: false, //checks if on ground or in air - standingOn: undefined, - numTouching: 0, - crouch: false, - // isHeadClear: true, - spawnPos: { - x: 0, - y: 0 - }, - spawnVel: { - x: 0, - y: 0 - }, - pos: { - x: 0, - y: 0 - }, - Sy: 0, //adds a smoothing effect to vertical only - Vx: 0, - Vy: 0, - friction: { - ground: 0.01, - air: 0.0025 - }, - airSpeedLimit: 125, // 125/mass/mass = 5 - angle: 0, - walk_cycle: 0, - stepSize: 0, - flipLegs: -1, - hip: { - x: 12, - y: 24 - }, - knee: { - x: 0, - y: 0, - x2: 0, - y2: 0 - }, - foot: { - x: 0, - y: 0 - }, - legLength1: 55, - legLength2: 45, - transX: 0, - transY: 0, - move() { - mech.pos.x = player.position.x; - mech.pos.y = playerBody.position.y - mech.yOff; - mech.Vx = player.velocity.x; - mech.Vy = player.velocity.y; - }, - transSmoothX: 0, - transSmoothY: 0, - lastGroundedPositionY: 0, - // mouseZoom: 0, - look() { - //always on mouse look - mech.angle = Math.atan2( - game.mouseInGame.y - mech.pos.y, - game.mouseInGame.x - mech.pos.x - ); - //smoothed mouse look translations - const scale = 0.8; - mech.transSmoothX = canvas.width2 - mech.pos.x - (game.mouse.x - canvas.width2) * scale; - mech.transSmoothY = canvas.height2 - mech.pos.y - (game.mouse.y - canvas.height2) * scale; - - mech.transX += (mech.transSmoothX - mech.transX) * 0.07; - mech.transY += (mech.transSmoothY - mech.transY) * 0.07; - }, - doCrouch() { - if (!mech.crouch) { - mech.crouch = true; - mech.yOffGoal = mech.yOffWhen.crouch; - Matter.Body.translate(playerHead, { - x: 0, - y: 40 - }); - } - }, - undoCrouch() { - if (mech.crouch) { - mech.crouch = false; - mech.yOffGoal = mech.yOffWhen.stand; - Matter.Body.translate(playerHead, { - x: 0, - y: -40 - }); - } - }, - hardLandCD: 0, - checkHeadClear() { - if (Matter.Query.collides(headSensor, map).length > 0) { - return false - } else { - return true - } - }, - enterAir() { - //triggered in engine.js on collision - mech.onGround = false; - mech.hardLandCD = 0 // disable hard landing - if (mech.checkHeadClear()) { - if (mech.crouch) { - mech.undoCrouch(); - } - mech.yOffGoal = mech.yOffWhen.jump; - } - }, - //triggered in engine.js on collision - enterLand() { - mech.onGround = true; - if (mech.crouch) { - if (mech.checkHeadClear()) { - mech.undoCrouch(); - } else { - mech.yOffGoal = mech.yOffWhen.crouch; - } - } else { - //sets a hard land where player stays in a crouch for a bit and can't jump - //crouch is forced in groundControl below - const momentum = player.velocity.y * player.mass //player mass is 5 so this triggers at 26 down velocity, unless the player is holding something - if (momentum > 130) { - mech.doCrouch(); - mech.yOff = mech.yOffWhen.jump; - mech.hardLandCD = mech.cycle + Math.min(momentum / 6.5 - 6, 40) - } else { - mech.yOffGoal = mech.yOffWhen.stand; - } - } - }, - buttonCD_jump: 0, //cool down for player buttons - groundControl() { - //check for crouch or jump - if (mech.crouch) { - if (!(input.down) && mech.checkHeadClear() && mech.hardLandCD < mech.cycle) mech.undoCrouch(); - } else if (input.down || mech.hardLandCD > mech.cycle) { - mech.doCrouch(); //on ground && not crouched and pressing s or down - } else if ((input.up) && mech.buttonCD_jump + 20 < mech.cycle && mech.yOffWhen.stand > 23) { - mech.buttonCD_jump = mech.cycle; //can't jump again until 20 cycles pass - //apply a fraction of the jump force to the body the player is jumping off of - Matter.Body.applyForce(mech.standingOn, mech.pos, { - x: 0, - y: mech.jumpForce * 0.12 * Math.min(mech.standingOn.mass, 5) - }); - player.force.y = -mech.jumpForce; //player jump force - Matter.Body.setVelocity(player, { //zero player y-velocity for consistent jumps - x: player.velocity.x, - y: 0 - }); - } - - if (input.left) { - if (player.velocity.x > -2) { - player.force.x -= mech.Fx * 1.5 - } else { - player.force.x -= mech.Fx - } - // } - } else if (input.right) { - if (player.velocity.x < 2) { - player.force.x += mech.Fx * 1.5 - } else { - player.force.x += mech.Fx - } - } else { - const stoppingFriction = 0.92; - Matter.Body.setVelocity(player, { - x: player.velocity.x * stoppingFriction, - y: player.velocity.y * stoppingFriction - }); - } - //come to a stop if fast or if no move key is pressed - if (player.speed > 4) { - const stoppingFriction = (mech.crouch) ? 0.65 : 0.89; // this controls speed when crouched - Matter.Body.setVelocity(player, { - x: player.velocity.x * stoppingFriction, - y: player.velocity.y * stoppingFriction - }); - } - }, - airControl() { - //check for short jumps //moving up //recently pressed jump //but not pressing jump key now - if (mech.buttonCD_jump + 60 > mech.cycle && !(input.up) && mech.Vy < 0) { - Matter.Body.setVelocity(player, { - //reduce player y-velocity every cycle - x: player.velocity.x, - y: player.velocity.y * 0.94 - }); - } - - if (input.left) { - if (player.velocity.x > -mech.airSpeedLimit / player.mass / player.mass) player.force.x -= mech.FxAir; // move player left / a - } else if (input.right) { - if (player.velocity.x < mech.airSpeedLimit / player.mass / player.mass) player.force.x += mech.FxAir; //move player right / d - } - }, - alive: false, - death() { - if (mod.isImmortal) { //if player has the immortality buff, spawn on the same level with randomized stats - - //count mods - let totalMods = 0; - for (let i = 0; i < mod.mods.length; i++) { - if (!mod.mods[i].isNonRefundable) totalMods += mod.mods[i].count - } - if (mod.isDeterminism) totalMods -= 3 //remove the bonus mods - if (mod.isSuperDeterminism) totalMods -= 2 //remove the bonus mods - totalMods = totalMods * 1.15 + 1 // a few extra to make it stronger - const totalGuns = b.inventory.length //count guns - - function randomizeMods() { - for (let i = 0; i < totalMods; i++) { - //find what mods I don't have - let options = []; - for (let i = 0, len = mod.mods.length; i < len; i++) { - if (mod.mods[i].count < mod.mods[i].maxCount && - !mod.mods[i].isNonRefundable && - mod.mods[i].name !== "quantum immortality" && - mod.mods[i].name !== "Born rule" && - mod.mods[i].allowed() - ) options.push(i); - } - //add a new mod - if (options.length > 0) { - const choose = Math.floor(Math.random() * options.length) - let newMod = options[choose] - mod.giveMod(newMod) - options.splice(choose, 1); - } - } - game.updateModHUD(); - } - - function randomizeField() { - mech.setField(Math.ceil(Math.random() * (mech.fieldUpgrades.length - 1))) - } - - function randomizeHealth() { - mech.health = 0.7 + Math.random() - if (mech.health > 1) mech.health = 1; - mech.displayHealth(); - } - - function randomizeGuns() { - //removes guns and ammo - b.inventory = []; - b.activeGun = null; - b.inventoryGun = 0; - for (let i = 0, len = b.guns.length; i < len; ++i) { - b.guns[i].have = false; - if (b.guns[i].ammo !== Infinity) b.guns[i].ammo = 0; - } - //give random guns - for (let i = 0; i < totalGuns; i++) b.giveGuns() - //randomize ammo - for (let i = 0, len = b.inventory.length; i < len; i++) { - if (b.guns[b.inventory[i]].ammo !== Infinity) { - b.guns[b.inventory[i]].ammo = Math.max(0, Math.floor(6 * b.guns[b.inventory[i]].ammo * Math.sqrt(Math.random()))) - } - } - game.makeGunHUD(); //update gun HUD - } - - - function pixelWindows() { - - //pixel graphics - let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); //copy current canvas pixel data - let data = imgData.data; - //change random pixels - //strange draw offset - // const off = canvas.height * canvas.width * 4 / 16 - for (let i = 0; i < data.length; i += 4) { - index = i % canvas.width - data[index + 0] = data[index + 0]; // red - data[index + 1] = data[index + 1]; // red - data[index + 2] = data[index + 2]; // red - data[index + 3] = data[index + 3]; // red - } - ctx.putImageData(imgData, 0, 0); //draw new pixel data to canvas - - } - - - game.wipe = function () { //set wipe to have trails - ctx.fillStyle = "rgba(255,255,255,0)"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - // pixelWindows() - } - - function randomizeEverything() { - spawn.setSpawnList(); //new mob types - game.clearNow = true; //triggers a map reset - - mod.setupAllMods(); //remove all mods - for (let i = 0; i < bullet.length; ++i) Matter.World.remove(engine.world, bullet[i]); - bullet = []; //remove all bullets - randomizeHealth() - randomizeField() - randomizeGuns() - randomizeMods() - } - - randomizeEverything() - const swapPeriod = 1000 - for (let i = 0, len = 5; i < len; i++) { - setTimeout(function () { - randomizeEverything() - game.replaceTextLog = true; - game.makeTextLog(`probability amplitude will synchronize in ${len-i-1} seconds`, swapPeriod); - game.wipe = function () { //set wipe to have trails - ctx.fillStyle = `rgba(255,255,255,${(i+1)*(i+1)*0.006})`; - ctx.fillRect(0, 0, canvas.width, canvas.height); - // pixelWindows() - } - }, (i + 1) * swapPeriod); - } - - setTimeout(function () { - game.wipe = function () { //set wipe to normal - ctx.clearRect(0, 0, canvas.width, canvas.height); - } - game.replaceTextLog = true; - game.makeTextLog("your quantum probability has stabilized", 1000); - }, 6 * swapPeriod); - - } else if (mech.alive) { //normal death code here - mech.alive = false; - game.paused = true; - mech.health = 0; - mech.displayHealth(); - document.getElementById("text-log").style.opacity = 0; //fade out any active text logs - document.getElementById("fade-out").style.opacity = 1; //slowly fades out - setTimeout(function () { - game.splashReturn(); - }, 3000); - } - }, - health: 0, - maxHealth: 1, //set in game.reset() - drawHealth() { - if (mech.health < 1) { - ctx.fillStyle = "rgba(100, 100, 100, 0.5)"; - ctx.fillRect(mech.pos.x - mech.radius, mech.pos.y - 50, 60, 10); - ctx.fillStyle = "#f00"; - ctx.fillRect( - mech.pos.x - mech.radius, - mech.pos.y - 50, - 60 * mech.health, - 10 - ); - } - }, - displayHealth() { - id = document.getElementById("health"); - // health display follows a x^1.5 rule to make it seem like the player has lower health, this makes the player feel more excitement - id.style.width = Math.floor(300 * Math.pow(mech.health, 1.5)) + "px"; - //css animation blink if health is low - if (mech.health < 0.3) { - id.classList.add("low-health"); - } else { - id.classList.remove("low-health"); - } - }, - addHealth(heal) { - if (!mod.isEnergyHealth) { - mech.health += heal * game.healScale; - if (mech.health > mech.maxHealth) mech.health = mech.maxHealth; - mech.displayHealth(); - } - }, - baseHealth: 1, - setMaxHealth() { - mech.maxHealth = mech.baseHealth + mod.bonusHealth + mod.armorFromPowerUps - if (mech.health > mech.maxHealth) mech.health = mech.maxHealth; - mech.displayHealth(); - }, - - defaultFPSCycle: 0, //tracks when to return to normal fps - immuneCycle: 0, //used in engine - harmReduction() { - let dmg = 1 - dmg *= mech.fieldHarmReduction - if (mod.isSlowFPS) dmg *= 0.85 - if (mod.isPiezo) dmg *= 0.85 - if (mod.isHarmReduce && mech.fieldUpgrades[mech.fieldMode].name === "negative mass field" && mech.isFieldActive) dmg *= 0.6 - if (mod.isBotArmor) dmg *= 0.95 ** mod.totalBots() - if (mod.isHarmArmor && mech.lastHarmCycle + 600 > mech.cycle) dmg *= 0.5; - if (mod.isNoFireDefense && mech.cycle > mech.fireCDcycle + 120) dmg *= 0.6 - if (mod.energyRegen === 0) dmg *= 0.5 //0.22 + 0.78 * mech.energy //77% damage reduction at zero energy - if (mod.isTurret && mech.crouch) dmg *= 0.5; - if (mod.isEntanglement && b.inventory[0] === b.activeGun) { - for (let i = 0, len = b.inventory.length; i < len; i++) { - dmg *= 0.85 // 1 - 0.15 - } - } - return dmg - }, - damage(dmg) { - mech.lastHarmCycle = mech.cycle - if (mod.isDroneOnDamage) { //chance to build a drone on damage from mod - const len = Math.min((dmg - 0.06 * Math.random()) * 40, 40) - for (let i = 0; i < len; i++) { - if (Math.random() < 0.5) b.drone() //spawn drone - } - } - if (mod.isEnergyHealth) { - mech.energy -= dmg; - if (mech.energy < 0 || isNaN(mech.energy)) { //taking deadly damage - if (mod.isDeathAvoid && powerUps.reroll.rerolls) { - powerUps.reroll.changeRerolls(-1) - game.makeTextLog(` death avoided
${powerUps.reroll.rerolls} rerolls left
`, 420) - mech.energy = mech.maxEnergy - mech.immuneCycle = mech.cycle + 120 //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); - } - setTimeout(function () { - game.wipe = function () { //set wipe to normal - ctx.clearRect(0, 0, canvas.width, canvas.height); + spawn() { + //load player in matter.js physic engine + // let vector = Vertices.fromPath("0 40 50 40 50 115 0 115 30 130 20 130"); //player as a series of vertices + let vertices = Vertices.fromPath("0,40, 50,40, 50,115, 30,130, 20,130, 0,115, 0,40"); //player as a series of vertices + playerBody = Matter.Bodies.fromVertices(0, 0, vertices); + jumpSensor = Bodies.rectangle(0, 46, 36, 6, { + //this sensor check if the player is on the ground to enable jumping + sleepThreshold: 99999999999, + isSensor: true + }); + vertices = Vertices.fromPath("16 -82 2 -66 2 -37 43 -37 43 -66 30 -82"); + playerHead = Matter.Bodies.fromVertices(0, -55, vertices); //this part of the player lowers on crouch + headSensor = Bodies.rectangle(0, -57, 48, 45, { + //senses if the player's head is empty and can return after crouching + sleepThreshold: 99999999999, + isSensor: true + }); + player = Body.create({ + //combine jumpSensor and playerBody + parts: [playerBody, playerHead, jumpSensor, headSensor], + inertia: Infinity, //prevents player rotation + friction: 0.002, + frictionAir: 0.001, + //frictionStatic: 0.5, + restitution: 0, + sleepThreshold: Infinity, + collisionFilter: { + group: 0, + category: cat.player, + mask: cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield + }, + death() { + mech.death(); } - }, 2000); + }); + Matter.Body.setMass(player, mech.mass); + World.add(engine.world, [player]); - return; - } else { //death - mech.health = 0; - mech.energy = 0; - mech.death(); - return; - } - } - } else { - dmg *= mech.harmReduction() - mech.health -= dmg; - if (mech.health < 0 || isNaN(mech.health)) { - if (mod.isDeathAvoid && powerUps.reroll.rerolls > 0) { //&& Math.random() < 0.5 - 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++) { - powerUps.spawn(mech.pos.x, mech.pos.y, "heal", false); - } - mech.immuneCycle = mech.cycle + 120 //disable this.immuneCycle bonus seconds - // game.makeTextLog(" death avoided
1 reroll consumed
", 420) - - game.wipe = function () { //set wipe to have trails - ctx.fillStyle = "rgba(255,255,255,0.03)"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - } - setTimeout(function () { - game.wipe = function () { //set wipe to normal - ctx.clearRect(0, 0, canvas.width, canvas.height); - } - }, 2000); - } else { - mech.health = 0; - mech.death(); - return; - } - } - mech.displayHealth(); - document.getElementById("dmg").style.transition = "opacity 0s"; - document.getElementById("dmg").style.opacity = 0.1 + Math.min(0.6, dmg * 4); - } - - if (dmg > 0.06 / mech.holdingMassScale) mech.drop(); //drop block if holding - - const normalFPS = function () { - if (mech.defaultFPSCycle < mech.cycle) { //back to default values - game.fpsCap = game.fpsCapDefault - game.fpsInterval = 1000 / game.fpsCap; - document.getElementById("dmg").style.transition = "opacity 1s"; - document.getElementById("dmg").style.opacity = "0"; - } else { - requestAnimationFrame(normalFPS); - } - }; - - if (mech.defaultFPSCycle < mech.cycle) requestAnimationFrame(normalFPS); - if (mod.isSlowFPS) { // slow game - game.fpsCap = 30 //new fps - game.fpsInterval = 1000 / game.fpsCap; - //how long to wait to return to normal fps - mech.defaultFPSCycle = mech.cycle + 20 + Math.min(90, Math.floor(200 * dmg)) - if (mod.isHarmFreeze) { //freeze all mobs - for (let i = 0, len = mob.length; i < len; i++) { - mobs.statusSlow(mob[i], 240) - } - } - } else { - if (dmg > 0.05) { // freeze game for high damage hits - game.fpsCap = 4 //40 - Math.min(25, 100 * dmg) - game.fpsInterval = 1000 / game.fpsCap; - } else { - game.fpsCap = game.fpsCapDefault - game.fpsInterval = 1000 / game.fpsCap; - } - mech.defaultFPSCycle = mech.cycle - } - // if (!noTransition) { - // document.getElementById("health").style.transition = "width 0s ease-out" - // } else { - // document.getElementById("health").style.transition = "width 1s ease-out" - // } - }, - hitMob(i, dmg) { - //prevents damage happening too quick - }, - buttonCD: 0, //cool down for player buttons - drawLeg(stroke) { - // if (game.mouseInGame.x > mech.pos.x) { - if (mech.angle > -Math.PI / 2 && mech.angle < Math.PI / 2) { - mech.flipLegs = 1; - } else { - mech.flipLegs = -1; - } - ctx.save(); - ctx.scale(mech.flipLegs, 1); //leg lines - ctx.beginPath(); - ctx.moveTo(mech.hip.x, mech.hip.y); - ctx.lineTo(mech.knee.x, mech.knee.y); - ctx.lineTo(mech.foot.x, mech.foot.y); - ctx.strokeStyle = stroke; - ctx.lineWidth = 7; - ctx.stroke(); - - //toe lines - ctx.beginPath(); - ctx.moveTo(mech.foot.x, mech.foot.y); - ctx.lineTo(mech.foot.x - 15, mech.foot.y + 5); - ctx.moveTo(mech.foot.x, mech.foot.y); - ctx.lineTo(mech.foot.x + 15, mech.foot.y + 5); - ctx.lineWidth = 4; - ctx.stroke(); - - //hip joint - ctx.beginPath(); - ctx.arc(mech.hip.x, mech.hip.y, 11, 0, 2 * Math.PI); - //knee joint - ctx.moveTo(mech.knee.x + 7, mech.knee.y); - ctx.arc(mech.knee.x, mech.knee.y, 7, 0, 2 * Math.PI); - //foot joint - ctx.moveTo(mech.foot.x + 6, mech.foot.y); - ctx.arc(mech.foot.x, mech.foot.y, 6, 0, 2 * Math.PI); - ctx.fillStyle = mech.fillColor; - ctx.fill(); - ctx.lineWidth = 2; - ctx.stroke(); - ctx.restore(); - }, - calcLeg(cycle_offset, offset) { - mech.hip.x = 12 + offset; - mech.hip.y = 24 + offset; - //stepSize goes to zero if Vx is zero or not on ground (make mech transition cleaner) - mech.stepSize = 0.8 * mech.stepSize + 0.2 * (7 * Math.sqrt(Math.min(9, Math.abs(mech.Vx))) * mech.onGround); - //changes to stepsize are smoothed by adding only a percent of the new value each cycle - const stepAngle = 0.034 * mech.walk_cycle + cycle_offset; - mech.foot.x = 2.2 * mech.stepSize * Math.cos(stepAngle) + offset; - mech.foot.y = offset + 1.2 * mech.stepSize * Math.sin(stepAngle) + mech.yOff + mech.height; - const Ymax = mech.yOff + mech.height; - if (mech.foot.y > Ymax) mech.foot.y = Ymax; - - //calculate knee position as intersection of circle from hip and foot - const d = Math.sqrt((mech.hip.x - mech.foot.x) * (mech.hip.x - mech.foot.x) + (mech.hip.y - mech.foot.y) * (mech.hip.y - mech.foot.y)); - const l = (mech.legLength1 * mech.legLength1 - mech.legLength2 * mech.legLength2 + d * d) / (2 * d); - const h = Math.sqrt(mech.legLength1 * mech.legLength1 - l * l); - mech.knee.x = (l / d) * (mech.foot.x - mech.hip.x) - (h / d) * (mech.foot.y - mech.hip.y) + mech.hip.x + offset; - mech.knee.y = (l / d) * (mech.foot.y - mech.hip.y) + (h / d) * (mech.foot.x - mech.hip.x) + mech.hip.y; - }, - draw() { - ctx.fillStyle = mech.fillColor; - mech.walk_cycle += mech.flipLegs * mech.Vx; - - //draw body - ctx.save(); - ctx.globalAlpha = (mech.immuneCycle < mech.cycle) ? 1 : 0.7 - ctx.translate(mech.pos.x, mech.pos.y); - mech.calcLeg(Math.PI, -3); - mech.drawLeg("#4a4a4a"); - mech.calcLeg(0, 0); - mech.drawLeg("#333"); - ctx.rotate(mech.angle); - - ctx.beginPath(); - ctx.arc(0, 0, 30, 0, 2 * Math.PI); - let grd = ctx.createLinearGradient(-30, 0, 30, 0); - grd.addColorStop(0, mech.fillColorDark); - grd.addColorStop(1, mech.fillColor); - ctx.fillStyle = grd; - ctx.fill(); - ctx.arc(15, 0, 4, 0, 2 * Math.PI); - ctx.strokeStyle = "#333"; - ctx.lineWidth = 2; - ctx.stroke(); - // ctx.beginPath(); - // ctx.arc(15, 0, 3, 0, 2 * Math.PI); - // ctx.fillStyle = '#9cf' //'#0cf'; - // ctx.fill() - ctx.restore(); - mech.yOff = mech.yOff * 0.85 + mech.yOffGoal * 0.15; //smoothly move leg height towards height goal - }, - // ********************************************* - // **************** fields ********************* - // ********************************************* - closest: { - dist: 1000, - index: 0 - }, - isHolding: false, - isCloak: false, - throwCharge: 0, - fireCDcycle: 0, - fieldCDcycle: 0, - fieldMode: 0, //basic field mode before upgrades - maxEnergy: 1, //can be increased by a mod - holdingTarget: null, - timeSkipLastCycle: 0, - // these values are set on reset by setHoldDefaults() - grabPowerUpRange2: 0, - isFieldActive: false, - fieldRange: 155, - fieldShieldingScale: 1, - fieldDamage: 1, - energy: 0, - fieldRegen: 0, - fieldMode: 0, - fieldFire: false, - fieldHarmReduction: 1, - holdingMassScale: 0, - hole: { - isOn: false, - isReady: true, - pos1: { - x: 0, - y: 0 + mech.holdConstraint = Constraint.create({ + //holding body constraint + pointA: { + x: 0, + y: 0 + }, + bodyB: jumpSensor, //setting constraint to jump sensor because it has to be on something until the player picks up things + stiffness: 0.4 + }); + World.add(engine.world, mech.holdConstraint); }, - pos2: { - x: 0, - y: 0 + cycle: 0, + lastKillCycle: 0, + lastHarmCycle: 0, + width: 50, + radius: 30, + fillColor: "#fff", + fillColorDark: "#ccc", + color: { + hue: 0, + sat: 0, + light: 100, }, - }, - fieldArc: 0, - fieldThreshold: 0, - calculateFieldThreshold() { - mech.fieldThreshold = Math.cos(mech.fieldArc * Math.PI) - }, - setHoldDefaults() { - if (mech.energy < mech.maxEnergy) mech.energy = mech.maxEnergy; - mech.fieldRegen = mod.energyRegen; //0.001 - mech.fieldMeterColor = "#0cf" - mech.fieldShieldingScale = 1; - mech.fieldBlockCD = 10; - mech.fieldHarmReduction = 1; - mech.fieldDamage = 1 - mech.grabPowerUpRange2 = 156000; - mech.fieldRange = 155; - mech.fieldFire = false; - mech.fieldCDcycle = 0; - mech.isCloak = false; - player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield - mech.airSpeedLimit = 125 - mech.drop(); - mech.holdingMassScale = 0.5; - mech.isFieldActive = false; //only being used by negative mass field - mech.fieldArc = 0.2; //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) - mech.calculateFieldThreshold(); //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) - mech.isBodiesAsleep = true; - mech.wakeCheck(); - mech.setMaxEnergy(); - mech.hole = { - isOn: false, - isReady: true, - pos1: { + setFillColors() { + // console.log(mech.color) + this.fillColor = `hsl(${mech.color.hue},${mech.color.sat}%,${mech.color.light}%)` + this.fillColorDark = `hsl(${mech.color.hue},${mech.color.sat}%,${mech.color.light-20}%)` + }, + height: 42, + yOffWhen: { + crouch: 22, + stand: 49, + jump: 70 + }, + defaultMass: 5, + mass: 5, + FxNotHolding: 0.015, + Fx: 0.016, //run Force on ground // + jumpForce: 0.42, + setMovement() { + mech.Fx = 0.016 * mod.squirrelFx * mod.fastTime; + mech.jumpForce = 0.42 * mod.squirrelJump * mod.fastTimeJump; + }, + FxAir: 0.016, // 0.4/5/5 run Force in Air + yOff: 70, + yOffGoal: 70, + onGround: false, //checks if on ground or in air + standingOn: undefined, + numTouching: 0, + crouch: false, + // isHeadClear: true, + spawnPos: { x: 0, y: 0 - }, - pos2: { + }, + spawnVel: { x: 0, y: 0 - }, - } - }, - setMaxEnergy() { - mech.maxEnergy = 1 + mod.bonusEnergy + mod.healMaxEnergyBonus - }, - fieldMeterColor: "#0cf", - drawFieldMeter(bgColor = "rgba(0, 0, 0, 0.4)", range = 60) { - if (mech.energy < mech.maxEnergy) { - mech.energy += mech.fieldRegen; - ctx.fillStyle = bgColor; - const xOff = mech.pos.x - mech.radius * mech.maxEnergy - const yOff = mech.pos.y - 50 - ctx.fillRect(xOff, yOff, range * mech.maxEnergy, 10); - ctx.fillStyle = mech.fieldMeterColor; - ctx.fillRect(xOff, yOff, range * mech.energy, 10); - if (mech.energy < 0) mech.energy = 0 - } else if (mech.energy > mech.maxEnergy + 0.05) { - ctx.fillStyle = bgColor; - const xOff = mech.pos.x - mech.radius * mech.energy - const yOff = mech.pos.y - 50 - // ctx.fillRect(xOff, yOff, range * mech.maxEnergy, 10); - ctx.fillStyle = mech.fieldMeterColor; - ctx.fillRect(xOff, yOff, range * mech.energy, 10); - } - // else { - // mech.energy = mech.maxEnergy - // } - }, - lookingAt(who) { - //calculate a vector from body to player and make it length 1 - const diff = Vector.normalise(Vector.sub(who.position, mech.pos)); - //make a vector for the player's direction of length 1 - const dir = { - x: Math.cos(mech.angle), - y: Math.sin(mech.angle) - }; - //the dot product of diff and dir will return how much over lap between the vectors - // console.log(Vector.dot(dir, diff)) - if (Vector.dot(dir, diff) > mech.fieldThreshold) { - return true; - } - return false; - }, - drop() { - if (mech.isHolding) { - mech.fieldCDcycle = mech.cycle + 15; - mech.isHolding = false; - mech.throwCharge = 0; - mech.definePlayerMass() - if (mech.holdingTarget) { - mech.holdingTarget.collisionFilter.category = cat.body; - mech.holdingTarget.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet - mech.holdingTarget = null; - } - } - }, - definePlayerMass(mass = mech.defaultMass) { - Matter.Body.setMass(player, mass); - //reduce air and ground move forces - mech.Fx = 0.08 / mass * mod.squirrelFx //base player mass is 5 - mech.FxAir = 0.4 / mass / mass //base player mass is 5 - //make player stand a bit lower when holding heavy masses - mech.yOffWhen.stand = Math.max(mech.yOffWhen.crouch, Math.min(49, 49 - (mass - 5) * 6)) - if (mech.onGround && !mech.crouch) mech.yOffGoal = mech.yOffWhen.stand; - }, - drawHold(target, stroke = true) { - if (target) { - const eye = 15; - const len = target.vertices.length - 1; - ctx.fillStyle = "rgba(110,170,200," + (0.2 + 0.4 * Math.random()) + ")"; - ctx.lineWidth = 1; - ctx.strokeStyle = "#000"; - ctx.beginPath(); - ctx.moveTo( - mech.pos.x + eye * Math.cos(mech.angle), - mech.pos.y + eye * Math.sin(mech.angle) - ); - ctx.lineTo(target.vertices[len].x, target.vertices[len].y); - ctx.lineTo(target.vertices[0].x, target.vertices[0].y); - ctx.fill(); - if (stroke) ctx.stroke(); - for (let i = 0; i < len; i++) { - ctx.beginPath(); - ctx.moveTo( - mech.pos.x + eye * Math.cos(mech.angle), - mech.pos.y + eye * Math.sin(mech.angle) + }, + pos: { + x: 0, + y: 0 + }, + Sy: 0, //adds a smoothing effect to vertical only + Vx: 0, + Vy: 0, + friction: { + ground: 0.01, + air: 0.0025 + }, + airSpeedLimit: 125, // 125/mass/mass = 5 + angle: 0, + walk_cycle: 0, + stepSize: 0, + flipLegs: -1, + hip: { + x: 12, + y: 24 + }, + knee: { + x: 0, + y: 0, + x2: 0, + y2: 0 + }, + foot: { + x: 0, + y: 0 + }, + legLength1: 55, + legLength2: 45, + transX: 0, + transY: 0, + move() { + mech.pos.x = player.position.x; + mech.pos.y = playerBody.position.y - mech.yOff; + mech.Vx = player.velocity.x; + mech.Vy = player.velocity.y; + }, + transSmoothX: 0, + transSmoothY: 0, + lastGroundedPositionY: 0, + // mouseZoom: 0, + look() { + //always on mouse look + mech.angle = Math.atan2( + game.mouseInGame.y - mech.pos.y, + game.mouseInGame.x - mech.pos.x ); - ctx.lineTo(target.vertices[i].x, target.vertices[i].y); - ctx.lineTo(target.vertices[i + 1].x, target.vertices[i + 1].y); - ctx.fill(); - if (stroke) ctx.stroke(); - } - } - }, - holding() { - if (mech.fireCDcycle < mech.cycle) mech.fireCDcycle = mech.cycle - 1 - if (mech.holdingTarget) { - mech.energy -= mech.fieldRegen; - if (mech.energy < 0) mech.energy = 0; - Matter.Body.setPosition(mech.holdingTarget, { - x: mech.pos.x + 70 * Math.cos(mech.angle), - y: mech.pos.y + 70 * Math.sin(mech.angle) - }); - Matter.Body.setVelocity(mech.holdingTarget, player.velocity); - Matter.Body.rotate(mech.holdingTarget, 0.01 / mech.holdingTarget.mass); //gently spin the block - } else { - mech.isHolding = false - } - }, - throwBlock() { - if (mech.holdingTarget) { - if (input.field) { - if (mech.energy > 0.001) { - if (mech.fireCDcycle < mech.cycle) mech.fireCDcycle = mech.cycle - mech.energy -= 0.001 / mod.throwChargeRate; - mech.throwCharge += 0.5 * mod.throwChargeRate / mech.holdingTarget.mass - //draw charge - const x = mech.pos.x + 15 * Math.cos(mech.angle); - const y = mech.pos.y + 15 * Math.sin(mech.angle); - const len = mech.holdingTarget.vertices.length - 1; - const edge = mech.throwCharge * mech.throwCharge * mech.throwCharge; - const grd = ctx.createRadialGradient(x, y, edge, x, y, edge + 5); - grd.addColorStop(0, "rgba(255,50,150,0.3)"); - grd.addColorStop(1, "transparent"); - ctx.fillStyle = grd; - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(mech.holdingTarget.vertices[len].x, mech.holdingTarget.vertices[len].y); - ctx.lineTo(mech.holdingTarget.vertices[0].x, mech.holdingTarget.vertices[0].y); - ctx.fill(); - for (let i = 0; i < len; i++) { - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(mech.holdingTarget.vertices[i].x, mech.holdingTarget.vertices[i].y); - ctx.lineTo(mech.holdingTarget.vertices[i + 1].x, mech.holdingTarget.vertices[i + 1].y); - ctx.fill(); - } - } else { - mech.drop() + //smoothed mouse look translations + const scale = 0.8; + mech.transSmoothX = canvas.width2 - mech.pos.x - (game.mouse.x - canvas.width2) * scale; + mech.transSmoothY = canvas.height2 - mech.pos.y - (game.mouse.y - canvas.height2) * scale; + + mech.transX += (mech.transSmoothX - mech.transX) * 0.07; + mech.transY += (mech.transSmoothY - mech.transY) * 0.07; + }, + doCrouch() { + if (!mech.crouch) { + mech.crouch = true; + mech.yOffGoal = mech.yOffWhen.crouch; + Matter.Body.translate(playerHead, { + x: 0, + y: 40 + }); } - } else if (mech.throwCharge > 0) { //Matter.Query.region(mob, player.bounds) - //throw the body - mech.fieldCDcycle = mech.cycle + 15; - mech.isHolding = false; - //bullet-like collisions - mech.holdingTarget.collisionFilter.category = cat.body; //cat.bullet; - mech.holdingTarget.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield; - //check every second to see if player is away from thrown body, and make solid - const solid = function (that) { - const dx = that.position.x - player.position.x; - const dy = that.position.y - player.position.y; - if (dx * dx + dy * dy > 10000 && that !== mech.holdingTarget) { - // that.collisionFilter.category = cat.body; //make solid - that.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet; //can hit player now - } else { - setTimeout(solid, 25, that); - } - }; - setTimeout(solid, 150, mech.holdingTarget); - - const charge = Math.min(mech.throwCharge / 5, 1) - //***** scale throw speed with the first number, 80 ***** - let speed = 80 * charge * Math.min(1, 0.8 / Math.pow(mech.holdingTarget.mass, 0.25)); - - if (Matter.Query.collides(mech.holdingTarget, map).length !== 0) { - speed *= 0.7 //drop speed by 30% if touching map - if (Matter.Query.ray(map, mech.holdingTarget.position, mech.pos).length !== 0) speed = 0 //drop to zero if the center of the block can't see the center of the player through the map - //|| Matter.Query.ray(body, mech.holdingTarget.position, mech.pos).length > 1 - } - - mech.throwCharge = 0; - Matter.Body.setVelocity(mech.holdingTarget, { - x: player.velocity.x * 0.5 + Math.cos(mech.angle) * speed, - y: player.velocity.y * 0.5 + Math.sin(mech.angle) * speed - }); - //player recoil //stronger in x-dir to prevent jump hacking - - Matter.Body.setVelocity(player, { - x: player.velocity.x - Math.cos(mech.angle) * speed / (mech.crouch ? 30 : 10) * Math.sqrt(mech.holdingTarget.mass), - y: player.velocity.y - Math.sin(mech.angle) * speed / 30 * Math.sqrt(mech.holdingTarget.mass) - }); - mech.definePlayerMass() //return to normal player mass - } - } else { - mech.isHolding = false - } - }, - drawField() { - if (mech.holdingTarget) { - ctx.fillStyle = "rgba(110,170,200," + (mech.energy * (0.05 + 0.05 * Math.random())) + ")"; - ctx.strokeStyle = "rgba(110, 200, 235, " + (0.3 + 0.08 * Math.random()) + ")" //"#9bd" //"rgba(110, 200, 235, " + (0.5 + 0.1 * Math.random()) + ")" - } else { - ctx.fillStyle = "rgba(110,170,200," + (0.02 + mech.energy * (0.15 + 0.15 * Math.random())) + ")"; - ctx.strokeStyle = "rgba(110, 200, 235, " + (0.6 + 0.2 * Math.random()) + ")" //"#9bd" //"rgba(110, 200, 235, " + (0.5 + 0.1 * Math.random()) + ")" - } - // const off = 2 * Math.cos(game.cycle * 0.1) - const range = mech.fieldRange; - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, range, mech.angle - Math.PI * mech.fieldArc, mech.angle + Math.PI * mech.fieldArc, false); - ctx.lineWidth = 2; - ctx.lineCap = "butt" - ctx.stroke(); - let eye = 13; - let aMag = 0.75 * Math.PI * mech.fieldArc - let a = mech.angle + aMag - let cp1x = mech.pos.x + 0.6 * range * Math.cos(a) - let cp1y = mech.pos.y + 0.6 * range * Math.sin(a) - ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle)) - a = mech.angle - aMag - cp1x = mech.pos.x + 0.6 * range * Math.cos(a) - cp1y = mech.pos.y + 0.6 * range * Math.sin(a) - ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + 1 * range * Math.cos(mech.angle - Math.PI * mech.fieldArc), mech.pos.y + 1 * range * Math.sin(mech.angle - Math.PI * mech.fieldArc)) - ctx.fill(); - // ctx.lineTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle)); - - //draw random lines in field for cool effect - let offAngle = mech.angle + 1.7 * Math.PI * mech.fieldArc * (Math.random() - 0.5); - ctx.beginPath(); - eye = 15; - ctx.moveTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle)); - ctx.lineTo(mech.pos.x + range * Math.cos(offAngle), mech.pos.y + range * Math.sin(offAngle)); - ctx.strokeStyle = "rgba(120,170,255,0.6)"; - ctx.lineWidth = 1; - ctx.stroke(); - }, - grabPowerUp() { //look for power ups to grab with field - if (mech.fireCDcycle < mech.cycle) mech.fireCDcycle = mech.cycle - 1 - for (let i = 0, len = powerUp.length; i < len; ++i) { - const dxP = mech.pos.x - powerUp[i].position.x; - const dyP = mech.pos.y - powerUp[i].position.y; - const dist2 = dxP * dxP + dyP * dyP; - // float towards player if looking at and in range or if very close to player - if (dist2 < mech.grabPowerUpRange2 && - (mech.lookingAt(powerUp[i]) || dist2 < 16000) && - !(mech.health === mech.maxHealth && powerUp[i].name === "heal") && - Matter.Query.ray(map, powerUp[i].position, mech.pos).length === 0 - ) { - powerUp[i].force.x += 0.05 * (dxP / Math.sqrt(dist2)) * powerUp[i].mass; - powerUp[i].force.y += 0.05 * (dyP / Math.sqrt(dist2)) * powerUp[i].mass - powerUp[i].mass * game.g; //negate gravity - //extra friction - Matter.Body.setVelocity(powerUp[i], { - x: powerUp[i].velocity.x * 0.11, - y: powerUp[i].velocity.y * 0.11 - }); - if (dist2 < 5000 && !game.isChoosing) { //use power up if it is close enough - powerUps.onPickUp(mech.pos); - Matter.Body.setVelocity(player, { //player knock back, after grabbing power up - x: player.velocity.x + powerUp[i].velocity.x / player.mass * 5, - y: player.velocity.y + powerUp[i].velocity.y / player.mass * 5 - }); - powerUp[i].effect(); - Matter.World.remove(engine.world, powerUp[i]); - powerUp.splice(i, 1); - return; //because the array order is messed up after splice - } - } - } - }, - pushMass(who) { - const speed = Vector.magnitude(Vector.sub(who.velocity, player.velocity)) - const fieldBlockCost = (0.03 + Math.sqrt(who.mass) * speed * 0.003) * mech.fieldShieldingScale; - const unit = Vector.normalise(Vector.sub(player.position, who.position)) - - if (mech.energy > fieldBlockCost * 0.2) { //shield needs at least some of the cost to block - mech.energy -= fieldBlockCost - if (mech.energy < 0) { - mech.energy = 0; - } - if (mech.energy > mech.maxEnergy) mech.energy = mech.maxEnergy; - - if (mod.blockDmg) { - who.damage(mod.blockDmg * b.dmgScale) - //draw electricity - const step = 40 - ctx.beginPath(); - for (let i = 0, len = 2.5 * mod.blockDmg; i < len; i++) { - let x = mech.pos.x - 20 * unit.x; - let y = mech.pos.y - 20 * unit.y; - ctx.moveTo(x, y); - for (let i = 0; i < 8; i++) { - x += step * (-unit.x + 1.5 * (Math.random() - 0.5)) - y += step * (-unit.y + 1.5 * (Math.random() - 0.5)) - ctx.lineTo(x, y); - } - } - ctx.lineWidth = 3; - ctx.strokeStyle = "#f0f"; - ctx.stroke(); - } else { - mech.drawHold(who); - } - // mech.holdingTarget = null - //knock backs - if (mech.fieldShieldingScale > 0) { - const massRoot = Math.sqrt(Math.min(12, Math.max(0.15, who.mass))); // masses above 12 can start to overcome the push back - Matter.Body.setVelocity(who, { - x: player.velocity.x - (15 * unit.x) / massRoot, - y: player.velocity.y - (15 * unit.y) / massRoot - }); - mech.fieldCDcycle = mech.cycle + mech.fieldBlockCD; + }, + undoCrouch() { if (mech.crouch) { - Matter.Body.setVelocity(player, { - x: player.velocity.x + 0.4 * unit.x * massRoot, - y: player.velocity.y + 0.4 * unit.y * massRoot - }); + mech.crouch = false; + mech.yOffGoal = mech.yOffWhen.stand; + Matter.Body.translate(playerHead, { + x: 0, + y: -40 + }); + } + }, + hardLandCD: 0, + checkHeadClear() { + if (Matter.Query.collides(headSensor, map).length > 0) { + return false } else { - Matter.Body.setVelocity(player, { - x: player.velocity.x + 5 * unit.x * massRoot, - y: player.velocity.y + 5 * unit.y * massRoot - }); + return true } - } else { - if (mod.isStunField && mech.fieldUpgrades[mech.fieldMode].name === "perfect diamagnetism") mobs.statusStun(who, mod.isStunField) - // mobs.statusSlow(who, mod.isStunField) - const massRoot = Math.sqrt(Math.max(0.15, who.mass)); // masses above 12 can start to overcome the push back - Matter.Body.setVelocity(who, { - x: player.velocity.x - (20 * unit.x) / massRoot, - y: player.velocity.y - (20 * unit.y) / massRoot - }); - if (who.dropPowerUp && player.speed < 12) { - const massRootCap = Math.sqrt(Math.min(10, Math.max(0.4, who.mass))); // masses above 12 can start to overcome the push back - Matter.Body.setVelocity(player, { - x: 0.9 * player.velocity.x + 0.6 * unit.x * massRootCap, - y: 0.9 * player.velocity.y + 0.6 * unit.y * massRootCap - }); - } - } - } - }, - pushMobsFacing() { // find mobs in range and in direction looking - for (let i = 0, len = mob.length; i < len; ++i) { - if ( - Vector.magnitude(Vector.sub(mob[i].position, player.position)) - mob[i].radius < mech.fieldRange && - mech.lookingAt(mob[i]) && - Matter.Query.ray(map, mob[i].position, mech.pos).length === 0 - ) { - mob[i].locatePlayer(); - mech.pushMass(mob[i]); - } - } - }, - pushMobs360(range = mech.fieldRange * 0.75) { // find mobs in range in any direction - for (let i = 0, len = mob.length; i < len; ++i) { - if ( - Vector.magnitude(Vector.sub(mob[i].position, mech.pos)) < range && - Matter.Query.ray(map, mob[i].position, mech.pos).length === 0 - ) { - mob[i].locatePlayer(); - mech.pushMass(mob[i]); - } - } - }, - // pushBodyFacing() { // push all body in range and in direction looking - // for (let i = 0, len = body.length; i < len; ++i) { - // if ( - // body[i].speed > 12 && body[i].mass > 2 && - // Vector.magnitude(Vector.sub(body[i].position, mech.pos)) < mech.fieldRange && - // mech.lookingAt(body[i]) && - // Matter.Query.ray(map, body[i].position, mech.pos).length === 0 - // ) { - // mech.pushMass(body[i]); - // } - // } - // }, - // pushBody360(range = mech.fieldRange * 0.75) { // push all body in range and in direction looking - // for (let i = 0, len = body.length; i < len; ++i) { - // if ( - // body[i].speed > 12 && body[i].mass > 2 && - // Vector.magnitude(Vector.sub(body[i].position, mech.pos)) < range && - // mech.lookingAt(body[i]) && - // Matter.Query.ray(map, body[i].position, mech.pos).length === 0 && - // body[i].collisionFilter.category === cat.body - // ) { - // mech.pushMass(body[i]); - // } - // } - // }, - lookForPickUp() { //find body to pickup - if (mech.energy > mech.fieldRegen) mech.energy -= mech.fieldRegen; - const grabbing = { - targetIndex: null, - targetRange: 150, - // lookingAt: false //false to pick up object in range, but not looking at - }; - for (let i = 0, len = body.length; i < len; ++i) { - if (Matter.Query.ray(map, body[i].position, mech.pos).length === 0) { - //is mech next body a better target then my current best - const dist = Vector.magnitude(Vector.sub(body[i].position, mech.pos)); - const looking = mech.lookingAt(body[i]); - // if (dist < grabbing.targetRange && (looking || !grabbing.lookingAt) && !body[i].isNotHoldable) { - if (dist < grabbing.targetRange && looking && !body[i].isNotHoldable) { - grabbing.targetRange = dist; - grabbing.targetIndex = i; - // grabbing.lookingAt = looking; - } - } - } - // set pick up target for when mouse is released - if (body[grabbing.targetIndex]) { - mech.holdingTarget = body[grabbing.targetIndex]; - // - ctx.beginPath(); //draw on each valid body - let vertices = mech.holdingTarget.vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j += 1) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - ctx.fillStyle = "rgba(190,215,230," + (0.3 + 0.7 * Math.random()) + ")"; - ctx.fill(); - - ctx.globalAlpha = 0.2; - mech.drawHold(mech.holdingTarget); - ctx.globalAlpha = 1; - } else { - mech.holdingTarget = null; - } - }, - pickUp() { - //triggers when a hold target exits and field button is released - mech.isHolding = true; - //conserve momentum when player mass changes - totalMomentum = Vector.add(Vector.mult(player.velocity, player.mass), Vector.mult(mech.holdingTarget.velocity, mech.holdingTarget.mass)) - Matter.Body.setVelocity(player, Vector.mult(totalMomentum, 1 / (mech.defaultMass + mech.holdingTarget.mass))); - - mech.definePlayerMass(mech.defaultMass + mech.holdingTarget.mass * mech.holdingMassScale) - //make block collide with nothing - mech.holdingTarget.collisionFilter.category = 0; - mech.holdingTarget.collisionFilter.mask = 0; - }, - wakeCheck() { - if (mech.isBodiesAsleep) { - mech.isBodiesAsleep = false; - - function wake(who) { - for (let i = 0, len = who.length; i < len; ++i) { - Matter.Sleeping.set(who[i], false) - if (who[i].storeVelocity) { - Matter.Body.setVelocity(who[i], { - x: who[i].storeVelocity.x, - y: who[i].storeVelocity.y - }) - Matter.Body.setAngularVelocity(who[i], who[i].storeAngularVelocity) - } - } - } - wake(mob); - wake(body); - wake(bullet); - for (let i = 0, len = cons.length; i < len; i++) { - if (cons[i].stiffness === 0) { - cons[i].stiffness = cons[i].storeStiffness - } - } - // wake(powerUp); - } - }, - hold() {}, - setField(index) { - if (isNaN(index)) { //find index by name - let found = false - for (let i = 0; i < mech.fieldUpgrades.length; i++) { - if (index === mech.fieldUpgrades[i].name) { - index = i; - found = true; - break; - } - } - if (!found) return //if you can't find the field don't give a field to avoid game crash - } - mech.fieldMode = index; - document.getElementById("field").innerHTML = mech.fieldUpgrades[index].name - mech.setHoldDefaults(); - mech.fieldUpgrades[index].effect(); - }, - fieldUpgrades: [{ - name: "field emitter", - description: "use energy to block mobs
throw blocks to damage mobs
pick up power ups", - effect: () => { - game.replaceTextLog = true; //allow text over write - mech.hold = function () { - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throwBlock(); - } else if ((input.field && mech.fieldCDcycle < mech.cycle)) { //not hold but field button is pressed - mech.grabPowerUp(); - mech.lookForPickUp(); - if (mech.energy > 0.05) { - mech.drawField(); - mech.pushMobsFacing(); - } - } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released - mech.pickUp(); - } else { - mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - mech.drawFieldMeter() - } - } }, - { - name: "standing wave harmonics", - description: "3 oscillating shields are permanently active
blocking drains energy
blocking has no cool down", - effect: () => { - // mech.fieldHarmReduction = 0.80; - mech.fieldBlockCD = 0; - mech.hold = function () { - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throwBlock(); - } else if ((input.field) && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed - mech.grabPowerUp(); - mech.lookForPickUp(); - } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released - mech.pickUp(); - } else { - mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - if (mech.energy > 0.1 && mech.fieldCDcycle < mech.cycle) { - const fieldRange1 = (0.7 + 0.3 * Math.sin(mech.cycle / 23)) * mech.fieldRange - const fieldRange2 = (0.63 + 0.37 * Math.sin(mech.cycle / 37)) * mech.fieldRange - const fieldRange3 = (0.65 + 0.35 * Math.sin(mech.cycle / 47)) * mech.fieldRange - const netfieldRange = Math.max(fieldRange1, fieldRange2, fieldRange3) - ctx.fillStyle = "rgba(110,170,200," + (0.04 + mech.energy * (0.12 + 0.13 * Math.random())) + ")"; - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, fieldRange1, 0, 2 * Math.PI); - ctx.fill(); - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, fieldRange2, 0, 2 * Math.PI); - ctx.fill(); - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, fieldRange3, 0, 2 * Math.PI); - ctx.fill(); - mech.pushMobs360(netfieldRange); - // mech.pushBody360(netfieldRange); //can't throw block when pushhing blocks away - } - mech.drawFieldMeter() - } - } - }, - { - name: "perfect diamagnetism", - // description: "gain energy when blocking
no recoil when blocking", - description: "blocking does not drain energy
blocking has no cool down and less recoil
attract power ups from far away", - effect: () => { - mech.fieldShieldingScale = 0; - mech.grabPowerUpRange2 = 10000000 - // mech.holdingMassScale = 0.03; //can hold heavier blocks with lower cost to jumping - // mech.fieldMeterColor = "#0af" - // mech.fieldArc = 0.3; //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) - // mech.calculateFieldThreshold(); - mech.hold = function () { - const wave = Math.sin(mech.cycle * 0.022); - mech.fieldRange = 170 + 12 * wave - mech.fieldArc = 0.33 + 0.045 * wave //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) - mech.calculateFieldThreshold(); - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throwBlock(); - } else if ((input.field && mech.fieldCDcycle < mech.cycle)) { //not hold but field button is pressed - mech.grabPowerUp(); - mech.lookForPickUp(); - if (mech.energy > 0.05) { - //draw field - if (mech.holdingTarget) { - ctx.fillStyle = "rgba(110,170,200," + (0.06 + 0.03 * Math.random()) + ")"; - ctx.strokeStyle = "rgba(110, 200, 235, " + (0.35 + 0.05 * Math.random()) + ")" - } else { - ctx.fillStyle = "rgba(110,170,200," + (0.27 + 0.2 * Math.random() - 0.1 * wave) + ")"; - ctx.strokeStyle = "rgba(110, 200, 235, " + (0.4 + 0.5 * Math.random()) + ")" - } - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, mech.fieldRange, mech.angle - Math.PI * mech.fieldArc, mech.angle + Math.PI * mech.fieldArc, false); - ctx.lineWidth = 2.5 - 1.5 * wave; - ctx.lineCap = "butt" - ctx.stroke(); - const curve = 0.57 + 0.04 * wave - const aMag = (1 - curve * 1.2) * Math.PI * mech.fieldArc - let a = mech.angle + aMag - let cp1x = mech.pos.x + curve * mech.fieldRange * Math.cos(a) - let cp1y = mech.pos.y + curve * mech.fieldRange * Math.sin(a) - ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle)) - a = mech.angle - aMag - cp1x = mech.pos.x + curve * mech.fieldRange * Math.cos(a) - cp1y = mech.pos.y + curve * mech.fieldRange * Math.sin(a) - ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + 1 * mech.fieldRange * Math.cos(mech.angle - Math.PI * mech.fieldArc), mech.pos.y + 1 * mech.fieldRange * Math.sin(mech.angle - Math.PI * mech.fieldArc)) - ctx.fill(); - mech.pushMobsFacing(); + enterAir() { + //triggered in engine.js on collision + mech.onGround = false; + mech.hardLandCD = 0 // disable hard landing + if (mech.checkHeadClear()) { + if (mech.crouch) { + mech.undoCrouch(); } - } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released - mech.pickUp(); - } else { - mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - mech.drawFieldMeter() - - if (mod.isPerfectBrake) { //cap mob speed around player - const range = 400 + 120 * wave - for (let i = 0; i < mob.length; i++) { - const distance = Vector.magnitude(Vector.sub(mech.pos, mob[i].position)) - if (distance < range) { - const cap = mob[i].isShielded ? 8 : 4 - if (mob[i].speed > cap && Vector.dot(mob[i].velocity, Vector.sub(mech.pos, mob[i].position)) > 0) { // if velocity is directed towards player - Matter.Body.setVelocity(mob[i], Vector.mult(Vector.normalise(mob[i].velocity), cap)); //set velocity to cap, but keep the direction - } - } - } - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, range, 0, 2 * Math.PI); - ctx.fillStyle = "hsla(200,50%,61%,0.08)"; - ctx.fill(); - } + mech.yOffGoal = mech.yOffWhen.jump; } - } }, - { - name: "nano-scale manufacturing", - description: "use energy to block mobs
excess energy used to build drones
increase energy regeneration by 100%", - effect: () => { - mech.hold = function () { - if (mech.energy > mech.maxEnergy - 0.02 && mech.fieldCDcycle < mech.cycle) { - if (mod.isSporeField) { - // mech.fieldCDcycle = mech.cycle + 10; // set cool down to prevent +energy from making huge numbers of drones - const len = Math.floor(6 + 4 * Math.random()) - mech.energy -= len * 0.09; - for (let i = 0; i < len; i++) { - b.spore(mech.pos) - } - } else if (mod.isMissileField) { - // mech.fieldCDcycle = mech.cycle + 10; // set cool down to prevent +energy from making huge numbers of drones - mech.energy -= 0.5; - b.missile({ - x: mech.pos.x + 40 * Math.cos(mech.angle), - y: mech.pos.y + 40 * Math.sin(mech.angle) - 3 - }, - mech.angle + (0.5 - Math.random()) * (mech.crouch ? 0 : 0.2), - -3 * (0.5 - Math.random()) + (mech.crouch ? 25 : -8) * b.fireCD, - 1, mod.recursiveMissiles) - } else if (mod.isIceField) { - // mech.fieldCDcycle = mech.cycle + 17; // set cool down to prevent +energy from making huge numbers of drones - mech.energy -= 0.04; - b.iceIX(1) + //triggered in engine.js on collision + enterLand() { + mech.onGround = true; + if (mech.crouch) { + if (mech.checkHeadClear()) { + mech.undoCrouch(); } else { - // mech.fieldCDcycle = mech.cycle + 10; // set cool down to prevent +energy from making huge numbers of drones - mech.energy -= 0.33; - b.drone(1) + mech.yOffGoal = mech.yOffWhen.crouch; } - } - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throwBlock(); - } else if ((input.field && mech.fieldCDcycle < mech.cycle)) { //not hold but field button is pressed - mech.grabPowerUp(); - mech.lookForPickUp(); - if (mech.energy > 0.05) { - mech.drawField(); - mech.pushMobsFacing(); + } else { + //sets a hard land where player stays in a crouch for a bit and can't jump + //crouch is forced in groundControl below + const momentum = player.velocity.y * player.mass //player mass is 5 so this triggers at 26 down velocity, unless the player is holding something + if (momentum > 130) { + mech.doCrouch(); + mech.yOff = mech.yOffWhen.jump; + mech.hardLandCD = mech.cycle + Math.min(momentum / 6.5 - 6, 40) + } else { + mech.yOffGoal = mech.yOffWhen.stand; } - } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released - mech.pickUp(); - } else { - mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - mech.energy += mech.fieldRegen; - mech.drawFieldMeter() } - } }, - { - name: "negative mass field", - description: "use energy to nullify  gravity
reduce harm by 40%
blocks held by the field have a lower mass", - fieldDrawRadius: 0, - effect: () => { - mech.fieldFire = true; - mech.holdingMassScale = 0.03; //can hold heavier blocks with lower cost to jumping - mech.fieldMeterColor = "#000" - mech.fieldHarmReduction = 0.6; - mech.fieldDrawRadius = 0; + buttonCD_jump: 0, //cool down for player buttons + groundControl() { + //check for crouch or jump + if (mech.crouch) { + if (!(input.down) && mech.checkHeadClear() && mech.hardLandCD < mech.cycle) mech.undoCrouch(); + } else if (input.down || mech.hardLandCD > mech.cycle) { + mech.doCrouch(); //on ground && not crouched and pressing s or down + } else if ((input.up) && mech.buttonCD_jump + 20 < mech.cycle && mech.yOffWhen.stand > 23) { + mech.buttonCD_jump = mech.cycle; //can't jump again until 20 cycles pass + //apply a fraction of the jump force to the body the player is jumping off of + Matter.Body.applyForce(mech.standingOn, mech.pos, { + x: 0, + y: mech.jumpForce * 0.12 * Math.min(mech.standingOn.mass, 5) + }); + player.force.y = -mech.jumpForce; //player jump force + Matter.Body.setVelocity(player, { //zero player y-velocity for consistent jumps + x: player.velocity.x, + y: 0 + }); + } - mech.hold = function () { - mech.airSpeedLimit = 125 //5 * player.mass * player.mass - mech.FxAir = 0.016 - mech.isFieldActive = false; - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throwBlock(); - } else if (input.field && mech.fieldCDcycle < mech.cycle) { //push away - mech.grabPowerUp(); - mech.lookForPickUp(); - const DRAIN = 0.00035 - if (mech.energy > DRAIN) { - mech.isFieldActive = true; //used with mod.isHarmReduce - mech.airSpeedLimit = 400 // 7* player.mass * player.mass - mech.FxAir = 0.005 - // mech.pushMobs360(); + if (input.left) { + if (player.velocity.x > -2) { + player.force.x -= mech.Fx * 1.5 + } else { + player.force.x -= mech.Fx + } + // } + } else if (input.right) { + if (player.velocity.x < 2) { + player.force.x += mech.Fx * 1.5 + } else { + player.force.x += mech.Fx + } + } else { + const stoppingFriction = 0.92; + Matter.Body.setVelocity(player, { + x: player.velocity.x * stoppingFriction, + y: player.velocity.y * stoppingFriction + }); + } + //come to a stop if fast or if no move key is pressed + if (player.speed > 4) { + const stoppingFriction = (mech.crouch) ? 0.65 : 0.89; // this controls speed when crouched + Matter.Body.setVelocity(player, { + x: player.velocity.x * stoppingFriction, + y: player.velocity.y * stoppingFriction + }); + } + }, + airControl() { + //check for short jumps //moving up //recently pressed jump //but not pressing jump key now + if (mech.buttonCD_jump + 60 > mech.cycle && !(input.up) && mech.Vy < 0) { + Matter.Body.setVelocity(player, { + //reduce player y-velocity every cycle + x: player.velocity.x, + y: player.velocity.y * 0.94 + }); + } - //repulse mobs - // for (let i = 0, len = mob.length; i < len; ++i) { - // sub = Vector.sub(mob[i].position, mech.pos); - // dist2 = Vector.magnitudeSquared(sub); - // if (dist2 < this.fieldDrawRadius * this.fieldDrawRadius && mob[i].speed > 6) { - // const force = Vector.mult(Vector.perp(Vector.normalise(sub)), 0.00004 * mob[i].speed * mob[i].mass) - // mob[i].force.x = force.x - // mob[i].force.y = force.y - // } - // } - //look for nearby objects to make zero-g - function zeroG(who, range, mag = 1.06) { - for (let i = 0, len = who.length; i < len; ++i) { - sub = Vector.sub(who[i].position, mech.pos); - dist = Vector.magnitude(sub); - if (dist < range) { - who[i].force.y -= who[i].mass * (game.g * mag); //add a bit more then standard gravity - } + if (input.left) { + if (player.velocity.x > -mech.airSpeedLimit / player.mass / player.mass) player.force.x -= mech.FxAir; // move player left / a + } else if (input.right) { + if (player.velocity.x < mech.airSpeedLimit / player.mass / player.mass) player.force.x += mech.FxAir; //move player right / d + } + }, + alive: false, + death() { + if (mod.isImmortal) { //if player has the immortality buff, spawn on the same level with randomized stats + + //count mods + let totalMods = 0; + for (let i = 0; i < mod.mods.length; i++) { + if (!mod.mods[i].isNonRefundable) totalMods += mod.mods[i].count + } + if (mod.isDeterminism) totalMods -= 3 //remove the bonus mods + if (mod.isSuperDeterminism) totalMods -= 2 //remove the bonus mods + totalMods = totalMods * 1.15 + 1 // a few extra to make it stronger + const totalGuns = b.inventory.length //count guns + + function randomizeMods() { + for (let i = 0; i < totalMods; i++) { + //find what mods I don't have + let options = []; + for (let i = 0, len = mod.mods.length; i < len; i++) { + if (mod.mods[i].count < mod.mods[i].maxCount && + !mod.mods[i].isNonRefundable && + mod.mods[i].name !== "quantum immortality" && + mod.mods[i].name !== "Born rule" && + mod.mods[i].allowed() + ) options.push(i); + } + //add a new mod + if (options.length > 0) { + const choose = Math.floor(Math.random() * options.length) + let newMod = options[choose] + mod.giveMod(newMod) + options.splice(choose, 1); + } } - } - // zeroG(bullet); //works fine, but not that noticeable and maybe not worth the possible performance hit - // zeroG(mob); //mobs are too irregular to make this work? + game.updateModHUD(); + } - if (input.down) { //down - player.force.y -= 0.5 * player.mass * game.g; - this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 400 * 0.03; - zeroG(powerUp, this.fieldDrawRadius, 0.7); - zeroG(body, this.fieldDrawRadius, 0.7); - } else if (input.up) { //up - mech.energy -= 5 * DRAIN; - this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 850 * 0.03; - player.force.y -= 1.45 * player.mass * game.g; - zeroG(powerUp, this.fieldDrawRadius, 1.38); - zeroG(body, this.fieldDrawRadius, 1.38); - } else { - mech.energy -= DRAIN; - this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 650 * 0.03; - player.force.y -= 1.07 * player.mass * game.g; // slow upward drift - zeroG(powerUp, this.fieldDrawRadius); - zeroG(body, this.fieldDrawRadius); - } - if (mech.energy < 0.001) { - mech.fieldCDcycle = mech.cycle + 120; - mech.energy = 0; - } - //add extra friction for horizontal motion - if (input.down || input.up || input.left || input.right) { - Matter.Body.setVelocity(player, { - x: player.velocity.x * 0.99, - y: player.velocity.y * 0.98 - }); - } else { //slow rise and fall - Matter.Body.setVelocity(player, { - x: player.velocity.x * 0.99, - y: player.velocity.y * 0.98 - }); - } - if (mod.isFreezeMobs) { - const ICE_DRAIN = 0.0005 + function randomizeField() { + mech.setField(Math.ceil(Math.random() * (mech.fieldUpgrades.length - 1))) + } + + function randomizeHealth() { + mech.health = 0.7 + Math.random() + if (mech.health > 1) mech.health = 1; + mech.displayHealth(); + } + + function randomizeGuns() { + //removes guns and ammo + b.inventory = []; + b.activeGun = null; + b.inventoryGun = 0; + for (let i = 0, len = b.guns.length; i < len; ++i) { + b.guns[i].have = false; + if (b.guns[i].ammo !== Infinity) b.guns[i].ammo = 0; + } + //give random guns + for (let i = 0; i < totalGuns; i++) b.giveGuns() + //randomize ammo + for (let i = 0, len = b.inventory.length; i < len; i++) { + if (b.guns[b.inventory[i]].ammo !== Infinity) { + b.guns[b.inventory[i]].ammo = Math.max(0, Math.floor(6 * b.guns[b.inventory[i]].ammo * Math.sqrt(Math.random()))) + } + } + game.makeGunHUD(); //update gun HUD + } + + + function pixelWindows() { + + //pixel graphics + let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); //copy current canvas pixel data + let data = imgData.data; + //change random pixels + //strange draw offset + // const off = canvas.height * canvas.width * 4 / 16 + for (let i = 0; i < data.length; i += 4) { + index = i % canvas.width + data[index + 0] = data[index + 0]; // red + data[index + 1] = data[index + 1]; // red + data[index + 2] = data[index + 2]; // red + data[index + 3] = data[index + 3]; // red + } + ctx.putImageData(imgData, 0, 0); //draw new pixel data to canvas + + } + + + game.wipe = function() { //set wipe to have trails + ctx.fillStyle = "rgba(255,255,255,0)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + // pixelWindows() + } + + function randomizeEverything() { + spawn.setSpawnList(); //new mob types + game.clearNow = true; //triggers a map reset + + mod.setupAllMods(); //remove all mods + for (let i = 0; i < bullet.length; ++i) Matter.World.remove(engine.world, bullet[i]); + bullet = []; //remove all bullets + randomizeHealth() + randomizeField() + randomizeGuns() + randomizeMods() + } + + randomizeEverything() + const swapPeriod = 1000 + for (let i = 0, len = 5; i < len; i++) { + setTimeout(function() { + randomizeEverything() + game.replaceTextLog = true; + game.makeTextLog(`probability amplitude will synchronize in ${len-i-1} seconds`, swapPeriod); + game.wipe = function() { //set wipe to have trails + ctx.fillStyle = `rgba(255,255,255,${(i+1)*(i+1)*0.006})`; + ctx.fillRect(0, 0, canvas.width, canvas.height); + // pixelWindows() + } + }, (i + 1) * swapPeriod); + } + + setTimeout(function() { + game.wipe = function() { //set wipe to normal + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + game.replaceTextLog = true; + game.makeTextLog("your quantum probability has stabilized", 1000); + }, 6 * swapPeriod); + + } else if (mech.alive) { //normal death code here + mech.alive = false; + game.paused = true; + mech.health = 0; + mech.displayHealth(); + document.getElementById("text-log").style.opacity = 0; //fade out any active text logs + document.getElementById("fade-out").style.opacity = 1; //slowly fades out + setTimeout(function() { + game.splashReturn(); + }, 3000); + } + }, + health: 0, + maxHealth: 1, //set in game.reset() + drawHealth() { + if (mech.health < 1) { + ctx.fillStyle = "rgba(100, 100, 100, 0.5)"; + ctx.fillRect(mech.pos.x - mech.radius, mech.pos.y - 50, 60, 10); + ctx.fillStyle = "#f00"; + ctx.fillRect( + mech.pos.x - mech.radius, + mech.pos.y - 50, + 60 * mech.health, + 10 + ); + } + }, + displayHealth() { + id = document.getElementById("health"); + // health display follows a x^1.5 rule to make it seem like the player has lower health, this makes the player feel more excitement + id.style.width = Math.floor(300 * Math.pow(mech.health, 1.5)) + "px"; + //css animation blink if health is low + if (mech.health < 0.3) { + id.classList.add("low-health"); + } else { + id.classList.remove("low-health"); + } + }, + addHealth(heal) { + if (!mod.isEnergyHealth) { + mech.health += heal * game.healScale; + if (mech.health > mech.maxHealth) mech.health = mech.maxHealth; + mech.displayHealth(); + } + }, + baseHealth: 1, + setMaxHealth() { + mech.maxHealth = mech.baseHealth + mod.bonusHealth + mod.armorFromPowerUps + if (mech.health > mech.maxHealth) mech.health = mech.maxHealth; + mech.displayHealth(); + }, + + defaultFPSCycle: 0, //tracks when to return to normal fps + immuneCycle: 0, //used in engine + harmReduction() { + let dmg = 1 + dmg *= mech.fieldHarmReduction + if (mod.isSlowFPS) dmg *= 0.85 + if (mod.isPiezo) dmg *= 0.85 + if (mod.isHarmReduce && mech.fieldUpgrades[mech.fieldMode].name === "negative mass field" && mech.isFieldActive) dmg *= 0.6 + if (mod.isBotArmor) dmg *= 0.95 ** mod.totalBots() + if (mod.isHarmArmor && mech.lastHarmCycle + 600 > mech.cycle) dmg *= 0.5; + if (mod.isNoFireDefense && mech.cycle > mech.fireCDcycle + 120) dmg *= 0.6 + if (mod.energyRegen === 0) dmg *= 0.5 //0.22 + 0.78 * mech.energy //77% damage reduction at zero energy + if (mod.isTurret && mech.crouch) dmg *= 0.5; + if (mod.isEntanglement && b.inventory[0] === b.activeGun) { + for (let i = 0, len = b.inventory.length; i < len; i++) { + dmg *= 0.85 // 1 - 0.15 + } + } + return dmg + }, + damage(dmg) { + mech.lastHarmCycle = mech.cycle + if (mod.isDroneOnDamage) { //chance to build a drone on damage from mod + const len = Math.min((dmg - 0.06 * Math.random()) * 40, 40) + for (let i = 0; i < len; i++) { + if (Math.random() < 0.5) b.drone() //spawn drone + } + } + if (mod.isEnergyHealth) { + mech.energy -= dmg; + if (mech.energy < 0 || isNaN(mech.energy)) { //taking deadly damage + if (mod.isDeathAvoid && powerUps.reroll.rerolls) { + powerUps.reroll.changeRerolls(-1) + game.makeTextLog(` death avoided
${powerUps.reroll.rerolls} rerolls left
`, 420) + mech.energy = mech.maxEnergy + mech.immuneCycle = mech.cycle + 120 //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); + } + setTimeout(function() { + game.wipe = function() { //set wipe to normal + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + }, 2000); + + return; + } else { //death + mech.health = 0; + mech.energy = 0; + mech.death(); + return; + } + } + } else { + dmg *= mech.harmReduction() + mech.health -= dmg; + if (mech.health < 0 || isNaN(mech.health)) { + if (mod.isDeathAvoid && powerUps.reroll.rerolls > 0) { //&& Math.random() < 0.5 + 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++) { + powerUps.spawn(mech.pos.x, mech.pos.y, "heal", false); + } + mech.immuneCycle = mech.cycle + 120 //disable this.immuneCycle bonus seconds + // game.makeTextLog(" death avoided
1 reroll consumed
", 420) + + game.wipe = function() { //set wipe to have trails + ctx.fillStyle = "rgba(255,255,255,0.03)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + setTimeout(function() { + game.wipe = function() { //set wipe to normal + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + }, 2000); + } else { + mech.health = 0; + mech.death(); + return; + } + } + mech.displayHealth(); + document.getElementById("dmg").style.transition = "opacity 0s"; + document.getElementById("dmg").style.opacity = 0.1 + Math.min(0.6, dmg * 4); + } + + if (dmg > 0.06 / mech.holdingMassScale) mech.drop(); //drop block if holding + + const normalFPS = function() { + if (mech.defaultFPSCycle < mech.cycle) { //back to default values + game.fpsCap = game.fpsCapDefault + game.fpsInterval = 1000 / game.fpsCap; + document.getElementById("dmg").style.transition = "opacity 1s"; + document.getElementById("dmg").style.opacity = "0"; + } else { + requestAnimationFrame(normalFPS); + } + }; + + if (mech.defaultFPSCycle < mech.cycle) requestAnimationFrame(normalFPS); + if (mod.isSlowFPS) { // slow game + game.fpsCap = 30 //new fps + game.fpsInterval = 1000 / game.fpsCap; + //how long to wait to return to normal fps + mech.defaultFPSCycle = mech.cycle + 20 + Math.min(90, Math.floor(200 * dmg)) + if (mod.isHarmFreeze) { //freeze all mobs for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].distanceToPlayer() + mob[i].radius < this.fieldDrawRadius && !mob[i].shield && !mob[i].isShielded) { - if (mech.energy > ICE_DRAIN * 2) { - mech.energy -= ICE_DRAIN; - this.fieldDrawRadius -= 2; - mobs.statusSlow(mob[i], 45) - } else { - break; - } - } + mobs.statusSlow(mob[i], 240) } - } - - //draw zero-G range - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, this.fieldDrawRadius, 0, 2 * Math.PI); - ctx.fillStyle = "#f5f5ff"; - ctx.globalCompositeOperation = "difference"; - ctx.fill(); - ctx.globalCompositeOperation = "source-over"; } - } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released - mech.pickUp(); - this.fieldDrawRadius = 0 - } else { - mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - this.fieldDrawRadius = 0 - } - mech.drawFieldMeter("rgba(0,0,0,0.2)") - } - } - }, - { - name: "plasma torch", - description: "use energy to emit short range plasma
damages and pushes mobs away", - effect() { - mech.fieldMeterColor = "#f0f" - mech.hold = function () { - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throwBlock(); - } else if (input.field && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed - mech.grabPowerUp(); - mech.lookForPickUp(); - const DRAIN = 0.0012 - if (mech.energy > DRAIN) { - mech.energy -= DRAIN; - if (mech.energy < 0) { - mech.fieldCDcycle = mech.cycle + 120; - mech.energy = 0; - } - //calculate laser collision - let best; - let range = mod.isPlasmaRange * (120 + (mech.crouch ? 400 : 300) * Math.sqrt(Math.random())) //+ 100 * Math.sin(mech.cycle * 0.3); - // const dir = mech.angle // + 0.04 * (Math.random() - 0.5) - const path = [{ - x: mech.pos.x + 20 * Math.cos(mech.angle), - y: mech.pos.y + 20 * Math.sin(mech.angle) - }, - { - x: mech.pos.x + range * Math.cos(mech.angle), - y: mech.pos.y + range * Math.sin(mech.angle) - } - ]; - const vertexCollision = function (v1, v1End, domain) { - for (let i = 0; i < domain.length; ++i) { - let vertices = domain[i].vertices; - const len = vertices.length - 1; - for (let j = 0; j < len; j++) { - results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[j], - v2: vertices[j + 1] - }; - } - } - } - results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[0], - v2: vertices[len] - }; - } - } - } - }; - - //check for collisions - best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - vertexCollision(path[0], path[1], mob); - vertexCollision(path[0], path[1], map); - vertexCollision(path[0], path[1], body); - if (best.dist2 != Infinity) { //if hitting something - path[path.length - 1] = { - x: best.x, - y: best.y - }; - if (best.who.alive) { - const dmg = 0.8 * b.dmgScale; //********** SCALE DAMAGE HERE ********************* - best.who.damage(dmg); - best.who.locatePlayer(); - - //push mobs away - const force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, path[1])), -0.01 * Math.min(5, best.who.mass)) - Matter.Body.applyForce(best.who, path[1], force) - Matter.Body.setVelocity(best.who, { //friction - x: best.who.velocity.x * 0.7, - y: best.who.velocity.y * 0.7 - }); - //draw mob damage circle - game.drawList.push({ - x: path[1].x, - y: path[1].y, - radius: Math.sqrt(dmg) * 50, - color: "rgba(255,0,255,0.2)", - time: game.drawTime * 4 - }); - } else if (!best.who.isStatic) { - //push blocks away - const force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, path[1])), -0.007 * Math.sqrt(Math.sqrt(best.who.mass))) - Matter.Body.applyForce(best.who, path[1], force) - } - } - - //draw blowtorch laser beam - ctx.strokeStyle = "rgba(255,0,255,0.1)" - ctx.lineWidth = 14 - ctx.beginPath(); - ctx.moveTo(path[0].x, path[0].y); - ctx.lineTo(path[1].x, path[1].y); - ctx.stroke(); - ctx.strokeStyle = "#f0f"; - ctx.lineWidth = 2 - ctx.stroke(); - - //draw electricity - const Dx = Math.cos(mech.angle); - const Dy = Math.sin(mech.angle); - let x = mech.pos.x + 20 * Dx; - let y = mech.pos.y + 20 * Dy; - ctx.beginPath(); - ctx.moveTo(x, y); - const step = Vector.magnitude(Vector.sub(path[0], path[1])) / 10 - for (let i = 0; i < 8; i++) { - x += step * (Dx + 1.5 * (Math.random() - 0.5)) - y += step * (Dy + 1.5 * (Math.random() - 0.5)) - ctx.lineTo(x, y); - } - ctx.lineWidth = 2 * Math.random(); - ctx.stroke(); - } - } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released - mech.pickUp(); - } else { - mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - mech.drawFieldMeter("rgba(0, 0, 0, 0.2)") - - } - } - }, - { - name: "time dilation field", - description: "use energy to stop time
move and fire while time is stopped", - effect: () => { - // mech.fieldMeterColor = "#000" - mech.fieldFire = true; - mech.isBodiesAsleep = false; - mech.hold = function () { - if (mech.isHolding) { - mech.wakeCheck(); - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throwBlock(); - } else if (input.field && mech.fieldCDcycle < mech.cycle) { - mech.grabPowerUp(); - mech.lookForPickUp(180); - - const DRAIN = 0.0006 - if (mech.energy > DRAIN) { - mech.energy -= DRAIN; - if (mech.energy < DRAIN) { - mech.fieldCDcycle = mech.cycle + 120; - mech.energy = 0; - mech.wakeCheck(); - } - //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; - - function sleep(who) { - for (let i = 0, len = who.length; i < len; ++i) { - if (!who[i].isSleeping) { - who[i].storeVelocity = who[i].velocity - who[i].storeAngularVelocity = who[i].angularVelocity - } - Matter.Sleeping.set(who[i], true) - } - } - sleep(mob); - sleep(body); - sleep(bullet); - //doesn't really work, just slows down constraints - for (let i = 0, len = cons.length; i < len; i++) { - if (cons[i].stiffness !== 0) { - cons[i].storeStiffness = cons[i].stiffness; - cons[i].stiffness = 0; - } - } - - game.cycle--; //pause all functions that depend on game cycle increasing - if (mod.isTimeSkip) { - mech.immuneCycle = mech.cycle + 10; - game.isTimeSkipping = true; - mech.cycle++; - game.gravity(); - Engine.update(engine, game.delta); - // level.checkZones(); - // level.checkQuery(); - mech.move(); - game.checks(); - // mobs.loop(); - // mech.draw(); - mech.walk_cycle += mech.flipLegs * mech.Vx; - // mech.hold(); - // mech.energy += DRAIN; // 1 to undo the energy drain from time speed up, 0.5 to cut energy drain in half - b.fire(); - // b.bulletRemove(); - b.bulletDo(); - game.isTimeSkipping = false; - } - // game.cycle--; //pause all functions that depend on game cycle increasing - // if (mod.isTimeSkip && !game.isTimeSkipping) { //speed up the rate of time - // game.timeSkip(1) - // mech.energy += 1.5 * DRAIN; //x1 to undo the energy drain from time speed up, x1.5 to cut energy drain in half - // } - } - } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released - mech.wakeCheck(); - mech.pickUp(); - } else { - mech.wakeCheck(); - mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - mech.drawFieldMeter() - } - } - }, - { - name: "metamaterial cloaking", //"weak photonic coupling" "electromagnetically induced transparency" "optical non-coupling" "slow light field" "electro-optic transparency" - description: "cloak after not using your gun or field
while cloaked mobs can't see you
increase damage by 66%", - effect: () => { - mech.fieldFire = true; - mech.fieldMeterColor = "#fff"; - mech.fieldPhase = 0; - mech.isCloak = false - mech.fieldDamage = 1.66 - mech.fieldDrawRadius = 0 - const drawRadius = 1000 - - mech.hold = function () { - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throwBlock(); - } else if (input.field && mech.fieldCDcycle < mech.cycle) { //not hold and field button is pressed - mech.grabPowerUp(); - mech.lookForPickUp(); - } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding target exists, and field button is not pressed - mech.pickUp(); - } else { - mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - - //120 cycles after shooting (or using field) enable cloak - if (mech.energy < 0.05 && mech.fireCDcycle < mech.cycle) mech.fireCDcycle = mech.cycle - if (mech.fireCDcycle + 50 < mech.cycle) { - if (!mech.isCloak) { - mech.isCloak = true //enter cloak - if (mod.isIntangible) { - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType && bullet[i].botType !== "orbit") bullet[i].collisionFilter.mask = cat.map | cat.bullet | cat.mobBullet | cat.mobShield - } - } - } - } else if (mech.isCloak) { //exit cloak - mech.isCloak = false - if (mod.isIntangible) { - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType && bullet[i].botType !== "orbit") bullet[i].collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield - } - } - if (mod.isCloakStun) { //stun nearby mobs after exiting cloak - let isMobsAround = false - const stunRange = mech.fieldDrawRadius * 1.15 - const drain = 0.3 * mech.energy - for (let i = 0, len = mob.length; i < len; ++i) { - if ( - Vector.magnitude(Vector.sub(mob[i].position, mech.pos)) < stunRange && - Matter.Query.ray(map, mob[i].position, mech.pos).length === 0 - ) { - isMobsAround = true - mobs.statusStun(mob[i], 30 + drain * 300) - } - } - if (isMobsAround && mech.energy > drain) { - mech.energy -= drain - game.drawList.push({ - x: mech.pos.x, - y: mech.pos.y, - radius: stunRange, - color: "hsla(0,50%,100%,0.6)", - time: 4 - }); - // ctx.beginPath(); - // ctx.arc(mech.pos.x, mech.pos.y, 800, 0, 2 * Math.PI); - // ctx.fillStyle = "#000" - // ctx.fill(); - } - } - } - - function drawField() { - mech.fieldPhase += 0.007 + 0.07 * (1 - energy) - const wiggle = 0.15 * Math.sin(mech.fieldPhase * 0.5) - ctx.beginPath(); - ctx.ellipse(mech.pos.x, mech.pos.y, mech.fieldDrawRadius * (1 - wiggle), mech.fieldDrawRadius * (1 + wiggle), mech.fieldPhase, 0, 2 * Math.PI); - if (mech.fireCDcycle > mech.cycle && (input.field)) { - ctx.lineWidth = 5; - ctx.strokeStyle = `rgba(0, 204, 255,1)` - ctx.stroke() - } - ctx.fillStyle = "#fff" //`rgba(0,0,0,${0.5+0.5*mech.energy})`; - ctx.globalCompositeOperation = "destination-in"; //in or atop - ctx.fill(); - ctx.globalCompositeOperation = "source-over"; - ctx.clip(); - } - - const energy = Math.max(0.01, Math.min(mech.energy, 1)) - if (mech.isCloak) { - this.fieldRange = this.fieldRange * 0.9 + 0.1 * drawRadius - mech.fieldDrawRadius = this.fieldRange * Math.min(1, 0.3 + 0.5 * Math.min(1, energy * energy)); - drawField() - } else { - if (this.fieldRange < 3000) { - this.fieldRange += 200 - mech.fieldDrawRadius = this.fieldRange * Math.min(1, 0.3 + 0.5 * Math.min(1, energy * energy)); - drawField() - } - } - if (mod.isIntangible) { - if (mech.isCloak) { - player.collisionFilter.mask = cat.map - let inPlayer = Matter.Query.region(mob, player.bounds) - if (inPlayer.length > 0) { - for (let i = 0; i < inPlayer.length; i++) { - if (mech.energy > 0) { - if (inPlayer[i].shield) { //shields drain player energy - mech.energy -= 0.014; - } else { - mech.energy -= 0.004; - } - } - } - } + } else { + if (dmg > 0.05) { // freeze game for high damage hits + game.fpsCap = 4 //40 - Math.min(25, 100 * dmg) + game.fpsInterval = 1000 / game.fpsCap; } else { - player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield //normal collisions + game.fpsCap = game.fpsCapDefault + game.fpsInterval = 1000 / game.fpsCap; } - } + mech.defaultFPSCycle = mech.cycle + } + // if (!noTransition) { + // document.getElementById("health").style.transition = "width 0s ease-out" + // } else { + // document.getElementById("health").style.transition = "width 1s ease-out" + // } + }, + hitMob(i, dmg) { + //prevents damage happening too quick + }, + buttonCD: 0, //cool down for player buttons + drawLeg(stroke) { + // if (game.mouseInGame.x > mech.pos.x) { + if (mech.angle > -Math.PI / 2 && mech.angle < Math.PI / 2) { + mech.flipLegs = 1; + } else { + mech.flipLegs = -1; + } + ctx.save(); + ctx.scale(mech.flipLegs, 1); //leg lines + ctx.beginPath(); + ctx.moveTo(mech.hip.x, mech.hip.y); + ctx.lineTo(mech.knee.x, mech.knee.y); + ctx.lineTo(mech.foot.x, mech.foot.y); + ctx.strokeStyle = stroke; + ctx.lineWidth = 7; + ctx.stroke(); - if (mech.energy < mech.maxEnergy) { // replaces mech.drawFieldMeter() with custom code + //toe lines + ctx.beginPath(); + ctx.moveTo(mech.foot.x, mech.foot.y); + ctx.lineTo(mech.foot.x - 15, mech.foot.y + 5); + ctx.moveTo(mech.foot.x, mech.foot.y); + ctx.lineTo(mech.foot.x + 15, mech.foot.y + 5); + ctx.lineWidth = 4; + ctx.stroke(); + + //hip joint + ctx.beginPath(); + ctx.arc(mech.hip.x, mech.hip.y, 11, 0, 2 * Math.PI); + //knee joint + ctx.moveTo(mech.knee.x + 7, mech.knee.y); + ctx.arc(mech.knee.x, mech.knee.y, 7, 0, 2 * Math.PI); + //foot joint + ctx.moveTo(mech.foot.x + 6, mech.foot.y); + ctx.arc(mech.foot.x, mech.foot.y, 6, 0, 2 * Math.PI); + ctx.fillStyle = mech.fillColor; + ctx.fill(); + ctx.lineWidth = 2; + ctx.stroke(); + ctx.restore(); + }, + calcLeg(cycle_offset, offset) { + mech.hip.x = 12 + offset; + mech.hip.y = 24 + offset; + //stepSize goes to zero if Vx is zero or not on ground (make mech transition cleaner) + mech.stepSize = 0.8 * mech.stepSize + 0.2 * (7 * Math.sqrt(Math.min(9, Math.abs(mech.Vx))) * mech.onGround); + //changes to stepsize are smoothed by adding only a percent of the new value each cycle + const stepAngle = 0.034 * mech.walk_cycle + cycle_offset; + mech.foot.x = 2.2 * mech.stepSize * Math.cos(stepAngle) + offset; + mech.foot.y = offset + 1.2 * mech.stepSize * Math.sin(stepAngle) + mech.yOff + mech.height; + const Ymax = mech.yOff + mech.height; + if (mech.foot.y > Ymax) mech.foot.y = Ymax; + + //calculate knee position as intersection of circle from hip and foot + const d = Math.sqrt((mech.hip.x - mech.foot.x) * (mech.hip.x - mech.foot.x) + (mech.hip.y - mech.foot.y) * (mech.hip.y - mech.foot.y)); + const l = (mech.legLength1 * mech.legLength1 - mech.legLength2 * mech.legLength2 + d * d) / (2 * d); + const h = Math.sqrt(mech.legLength1 * mech.legLength1 - l * l); + mech.knee.x = (l / d) * (mech.foot.x - mech.hip.x) - (h / d) * (mech.foot.y - mech.hip.y) + mech.hip.x + offset; + mech.knee.y = (l / d) * (mech.foot.y - mech.hip.y) + (h / d) * (mech.foot.x - mech.hip.x) + mech.hip.y; + }, + draw() { + ctx.fillStyle = mech.fillColor; + mech.walk_cycle += mech.flipLegs * mech.Vx; + + //draw body + ctx.save(); + ctx.globalAlpha = (mech.immuneCycle < mech.cycle) ? 1 : 0.7 + ctx.translate(mech.pos.x, mech.pos.y); + mech.calcLeg(Math.PI, -3); + mech.drawLeg("#4a4a4a"); + mech.calcLeg(0, 0); + mech.drawLeg("#333"); + ctx.rotate(mech.angle); + + ctx.beginPath(); + ctx.arc(0, 0, 30, 0, 2 * Math.PI); + let grd = ctx.createLinearGradient(-30, 0, 30, 0); + grd.addColorStop(0, mech.fillColorDark); + grd.addColorStop(1, mech.fillColor); + ctx.fillStyle = grd; + ctx.fill(); + ctx.arc(15, 0, 4, 0, 2 * Math.PI); + ctx.strokeStyle = "#333"; + ctx.lineWidth = 2; + ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(15, 0, 3, 0, 2 * Math.PI); + // ctx.fillStyle = '#9cf' //'#0cf'; + // ctx.fill() + ctx.restore(); + mech.yOff = mech.yOff * 0.85 + mech.yOffGoal * 0.15; //smoothly move leg height towards height goal + }, + // ********************************************* + // **************** fields ********************* + // ********************************************* + closest: { + dist: 1000, + index: 0 + }, + isHolding: false, + isCloak: false, + throwCharge: 0, + fireCDcycle: 0, + fieldCDcycle: 0, + fieldMode: 0, //basic field mode before upgrades + maxEnergy: 1, //can be increased by a mod + holdingTarget: null, + timeSkipLastCycle: 0, + // these values are set on reset by setHoldDefaults() + grabPowerUpRange2: 0, + isFieldActive: false, + fieldRange: 155, + fieldShieldingScale: 1, + fieldDamage: 1, + energy: 0, + fieldRegen: 0, + fieldMode: 0, + fieldFire: false, + fieldHarmReduction: 1, + holdingMassScale: 0, + hole: { + isOn: false, + isReady: true, + pos1: { + x: 0, + y: 0 + }, + pos2: { + x: 0, + y: 0 + }, + }, + fieldArc: 0, + fieldThreshold: 0, + calculateFieldThreshold() { + mech.fieldThreshold = Math.cos(mech.fieldArc * Math.PI) + }, + setHoldDefaults() { + if (mech.energy < mech.maxEnergy) mech.energy = mech.maxEnergy; + mech.fieldRegen = mod.energyRegen; //0.001 + mech.fieldMeterColor = "#0cf" + mech.fieldShieldingScale = 1; + mech.fieldBlockCD = 10; + mech.fieldHarmReduction = 1; + mech.fieldDamage = 1 + mech.grabPowerUpRange2 = 156000; + mech.fieldRange = 155; + mech.fieldFire = false; + mech.fieldCDcycle = 0; + mech.isCloak = false; + player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield + mech.airSpeedLimit = 125 + mech.drop(); + mech.holdingMassScale = 0.5; + mech.isFieldActive = false; //only being used by negative mass field + mech.fieldArc = 0.2; //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) + mech.calculateFieldThreshold(); //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) + mech.isBodiesAsleep = true; + mech.wakeCheck(); + mech.setMaxEnergy(); + mech.hole = { + isOn: false, + isReady: true, + pos1: { + x: 0, + y: 0 + }, + pos2: { + x: 0, + y: 0 + }, + } + }, + setMaxEnergy() { + mech.maxEnergy = 1 + mod.bonusEnergy + mod.healMaxEnergyBonus + }, + fieldMeterColor: "#0cf", + drawFieldMeter(bgColor = "rgba(0, 0, 0, 0.4)", range = 60) { + if (mech.energy < mech.maxEnergy) { mech.energy += mech.fieldRegen; + ctx.fillStyle = bgColor; const xOff = mech.pos.x - mech.radius * mech.maxEnergy const yOff = mech.pos.y - 50 - ctx.fillStyle = "rgba(0, 0, 0, 0.3)"; - ctx.fillRect(xOff, yOff, 60 * mech.maxEnergy, 10); + ctx.fillRect(xOff, yOff, range * mech.maxEnergy, 10); ctx.fillStyle = mech.fieldMeterColor; - ctx.fillRect(xOff, yOff, 60 * mech.energy, 10); - ctx.beginPath() - ctx.rect(xOff, yOff, 60 * mech.maxEnergy, 10); - ctx.strokeStyle = "rgb(0, 0, 0)"; - ctx.lineWidth = 1; - ctx.stroke(); - } + ctx.fillRect(xOff, yOff, range * mech.energy, 10); + if (mech.energy < 0) mech.energy = 0 + } else if (mech.energy > mech.maxEnergy + 0.05) { + ctx.fillStyle = bgColor; + const xOff = mech.pos.x - mech.radius * mech.energy + const yOff = mech.pos.y - 50 + // ctx.fillRect(xOff, yOff, range * mech.maxEnergy, 10); + ctx.fillStyle = mech.fieldMeterColor; + ctx.fillRect(xOff, yOff, range * mech.energy, 10); } - } + // else { + // mech.energy = mech.maxEnergy + // } }, - // { - // name: "phase decoherence field", - // description: "use energy to become intangible
firing and touching shields drains energy
unable to see and be seen by mobs", - // effect: () => { - // mech.fieldFire = true; - // mech.fieldMeterColor = "#fff"; - // mech.fieldPhase = 0; - - // mech.hold = function () { - // function drawField(radius) { - // radius *= Math.min(4, 0.9 + 2.2 * mech.energy * mech.energy); - // const rotate = mech.cycle * 0.005; - // mech.fieldPhase += 0.5 - 0.5 * Math.sqrt(Math.max(0.01, Math.min(mech.energy, 1))); - // const off1 = 1 + 0.06 * Math.sin(mech.fieldPhase); - // const off2 = 1 - 0.06 * Math.sin(mech.fieldPhase); - // ctx.beginPath(); - // ctx.ellipse(mech.pos.x, mech.pos.y, radius * off1, radius * off2, rotate, 0, 2 * Math.PI); - // if (mech.fireCDcycle > mech.cycle && (input.field)) { - // ctx.lineWidth = 5; - // ctx.strokeStyle = `rgba(0, 204, 255,1)` - // ctx.stroke() - // } - // ctx.fillStyle = "#fff" //`rgba(0,0,0,${0.5+0.5*mech.energy})`; - // ctx.globalCompositeOperation = "destination-in"; //in or atop - // ctx.fill(); - // ctx.globalCompositeOperation = "source-over"; - // ctx.clip(); - // } - - // mech.isCloak = false //isCloak disables most uses of foundPlayer() - // player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield //normal collisions - // if (mech.isHolding) { - // if (this.fieldRange < 2000) { - // this.fieldRange += 100 - // drawField(this.fieldRange) - // } - // mech.drawHold(mech.holdingTarget); - // mech.holding(); - // mech.throwBlock(); - // } else if (input.field) { - // mech.grabPowerUp(); - // mech.lookForPickUp(); - - // if (mech.fieldCDcycle < mech.cycle) { - // // game.draw.bodyFill = "transparent" - // // game.draw.bodyStroke = "transparent" - - // const DRAIN = 0.00013 + (mech.fireCDcycle > mech.cycle ? 0.005 : 0) - // if (mech.energy > DRAIN) { - // mech.energy -= DRAIN; - // // if (mech.energy < 0.001) { - // // mech.fieldCDcycle = mech.cycle + 120; - // // mech.energy = 0; - // // mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - // // } - // this.fieldRange = this.fieldRange * 0.8 + 0.2 * 160 - // drawField(this.fieldRange) - - // mech.isCloak = true //isCloak disables most uses of foundPlayer() - // player.collisionFilter.mask = cat.map - - - // let inPlayer = Matter.Query.region(mob, player.bounds) - // if (inPlayer.length > 0) { - // for (let i = 0; i < inPlayer.length; i++) { - // if (inPlayer[i].shield) { - // mech.energy -= 0.005; //shields drain player energy - // //draw outline of shield - // ctx.fillStyle = `rgba(140,217,255,0.5)` - // ctx.fill() - // } else if (mod.superposition && inPlayer[i].dropPowerUp) { - // // inPlayer[i].damage(0.4 * b.dmgScale); //damage mobs inside the player - // // mech.energy += 0.005; - - // mobs.statusStun(inPlayer[i], 300) - // //draw outline of mob in a few random locations to show blurriness - // const vertices = inPlayer[i].vertices; - // const off = 30 - // for (let k = 0; k < 3; k++) { - // const xOff = off * (Math.random() - 0.5) - // const yOff = off * (Math.random() - 0.5) - // ctx.beginPath(); - // ctx.moveTo(xOff + vertices[0].x, yOff + vertices[0].y); - // for (let j = 1, len = vertices.length; j < len; ++j) { - // ctx.lineTo(xOff + vertices[j].x, yOff + vertices[j].y); - // } - // ctx.lineTo(xOff + vertices[0].x, yOff + vertices[0].y); - // ctx.fillStyle = "rgba(0,0,0,0.1)" - // ctx.fill() - // } - // break; - // } - // } - // } - // } else { - // mech.fieldCDcycle = mech.cycle + 120; - // mech.energy = 0; - // mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - // drawField(this.fieldRange) - // } - // } - // } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released - // mech.pickUp(); - // if (this.fieldRange < 2000) { - // this.fieldRange += 100 - // drawField(this.fieldRange) - // } - // } else { - // // this.fieldRange = 3000 - // if (this.fieldRange < 2000 && mech.holdingTarget === null) { - // this.fieldRange += 100 - // drawField(this.fieldRange) - // } - // mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - // } - - // if (mech.energy < mech.maxEnergy) { - // mech.energy += mech.fieldRegen; - // const xOff = mech.pos.x - mech.radius * mech.maxEnergy - // const yOff = mech.pos.y - 50 - // ctx.fillStyle = "rgba(0, 0, 0, 0.3)"; - // ctx.fillRect(xOff, yOff, 60 * mech.maxEnergy, 10); - // ctx.fillStyle = mech.fieldMeterColor; - // ctx.fillRect(xOff, yOff, 60 * mech.energy, 10); - // ctx.beginPath() - // ctx.rect(xOff, yOff, 60 * mech.maxEnergy, 10); - // ctx.strokeStyle = "rgb(0, 0, 0)"; - // ctx.lineWidth = 1; - // ctx.stroke(); - // } - // if (mech.energy < 0) mech.energy = 0 - // } - // } - // }, - { - name: "pilot wave", - description: "use energy to push blocks with your mouse
field radius decreases out of line of sight", - effect: () => { - game.replaceTextLog = true; //allow text over write - mech.fieldPhase = 0; - mech.fieldPosition = { - x: game.mouseInGame.x, - y: game.mouseInGame.y + lookingAt(who) { + //calculate a vector from body to player and make it length 1 + const diff = Vector.normalise(Vector.sub(who.position, mech.pos)); + //make a vector for the player's direction of length 1 + const dir = { + x: Math.cos(mech.angle), + y: Math.sin(mech.angle) + }; + //the dot product of diff and dir will return how much over lap between the vectors + // console.log(Vector.dot(dir, diff)) + if (Vector.dot(dir, diff) > mech.fieldThreshold) { + return true; } - mech.lastFieldPosition = { - x: game.mouseInGame.x, - y: game.mouseInGame.y + return false; + }, + drop() { + if (mech.isHolding) { + mech.fieldCDcycle = mech.cycle + 15; + mech.isHolding = false; + mech.throwCharge = 0; + mech.definePlayerMass() + if (mech.holdingTarget) { + mech.holdingTarget.collisionFilter.category = cat.body; + mech.holdingTarget.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet + mech.holdingTarget = null; + } } - mech.fieldOn = false; - mech.fieldRadius = 0; - mech.drop(); - mech.hold = function () { - if (input.field) { - if (mech.fieldCDcycle < mech.cycle) { - const scale = 25 - const bounds = { - min: { - x: mech.fieldPosition.x - scale, - y: mech.fieldPosition.y - scale - }, - max: { - x: mech.fieldPosition.x + scale, - y: mech.fieldPosition.y + scale + }, + definePlayerMass(mass = mech.defaultMass) { + Matter.Body.setMass(player, mass); + //reduce air and ground move forces + mech.Fx = 0.08 / mass * mod.squirrelFx //base player mass is 5 + mech.FxAir = 0.4 / mass / mass //base player mass is 5 + //make player stand a bit lower when holding heavy masses + mech.yOffWhen.stand = Math.max(mech.yOffWhen.crouch, Math.min(49, 49 - (mass - 5) * 6)) + if (mech.onGround && !mech.crouch) mech.yOffGoal = mech.yOffWhen.stand; + }, + drawHold(target, stroke = true) { + if (target) { + const eye = 15; + const len = target.vertices.length - 1; + ctx.fillStyle = "rgba(110,170,200," + (0.2 + 0.4 * Math.random()) + ")"; + ctx.lineWidth = 1; + ctx.strokeStyle = "#000"; + ctx.beginPath(); + ctx.moveTo( + mech.pos.x + eye * Math.cos(mech.angle), + mech.pos.y + eye * Math.sin(mech.angle) + ); + ctx.lineTo(target.vertices[len].x, target.vertices[len].y); + ctx.lineTo(target.vertices[0].x, target.vertices[0].y); + ctx.fill(); + if (stroke) ctx.stroke(); + for (let i = 0; i < len; i++) { + ctx.beginPath(); + ctx.moveTo( + mech.pos.x + eye * Math.cos(mech.angle), + mech.pos.y + eye * Math.sin(mech.angle) + ); + ctx.lineTo(target.vertices[i].x, target.vertices[i].y); + ctx.lineTo(target.vertices[i + 1].x, target.vertices[i + 1].y); + ctx.fill(); + if (stroke) ctx.stroke(); + } + } + }, + holding() { + if (mech.fireCDcycle < mech.cycle) mech.fireCDcycle = mech.cycle - 1 + if (mech.holdingTarget) { + mech.energy -= mech.fieldRegen; + if (mech.energy < 0) mech.energy = 0; + Matter.Body.setPosition(mech.holdingTarget, { + x: mech.pos.x + 70 * Math.cos(mech.angle), + y: mech.pos.y + 70 * Math.sin(mech.angle) + }); + Matter.Body.setVelocity(mech.holdingTarget, player.velocity); + Matter.Body.rotate(mech.holdingTarget, 0.01 / mech.holdingTarget.mass); //gently spin the block + } else { + mech.isHolding = false + } + }, + throwBlock() { + if (mech.holdingTarget) { + if (input.field) { + if (mech.energy > 0.001) { + if (mech.fireCDcycle < mech.cycle) mech.fireCDcycle = mech.cycle + mech.energy -= 0.001 / mod.throwChargeRate; + mech.throwCharge += 0.5 * mod.throwChargeRate / mech.holdingTarget.mass + //draw charge + const x = mech.pos.x + 15 * Math.cos(mech.angle); + const y = mech.pos.y + 15 * Math.sin(mech.angle); + const len = mech.holdingTarget.vertices.length - 1; + const edge = mech.throwCharge * mech.throwCharge * mech.throwCharge; + const grd = ctx.createRadialGradient(x, y, edge, x, y, edge + 5); + grd.addColorStop(0, "rgba(255,50,150,0.3)"); + grd.addColorStop(1, "transparent"); + ctx.fillStyle = grd; + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(mech.holdingTarget.vertices[len].x, mech.holdingTarget.vertices[len].y); + ctx.lineTo(mech.holdingTarget.vertices[0].x, mech.holdingTarget.vertices[0].y); + ctx.fill(); + for (let i = 0; i < len; i++) { + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(mech.holdingTarget.vertices[i].x, mech.holdingTarget.vertices[i].y); + ctx.lineTo(mech.holdingTarget.vertices[i + 1].x, mech.holdingTarget.vertices[i + 1].y); + ctx.fill(); + } + } else { + mech.drop() } - } - const isInMap = Matter.Query.region(map, bounds).length - // const isInMap = Matter.Query.point(map, mech.fieldPosition).length + } else if (mech.throwCharge > 0) { //Matter.Query.region(mob, player.bounds) + //throw the body + mech.fieldCDcycle = mech.cycle + 15; + mech.isHolding = false; + //bullet-like collisions + mech.holdingTarget.collisionFilter.category = cat.body; //cat.bullet; + mech.holdingTarget.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield; + //check every second to see if player is away from thrown body, and make solid + const solid = function(that) { + const dx = that.position.x - player.position.x; + const dy = that.position.y - player.position.y; + if (dx * dx + dy * dy > 10000 && that !== mech.holdingTarget) { + // that.collisionFilter.category = cat.body; //make solid + that.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet; //can hit player now + } else { + setTimeout(solid, 25, that); + } + }; + setTimeout(solid, 150, mech.holdingTarget); - if (!mech.fieldOn) { // if field was off, and it starting up, teleport to new mouse location - mech.fieldOn = true; - mech.fieldPosition = { //smooth the mouse position - x: game.mouseInGame.x, - y: game.mouseInGame.y - } - mech.lastFieldPosition = { //used to find velocity of field changes - x: mech.fieldPosition.x, - y: mech.fieldPosition.y - } - } else { //when field is on it smoothly moves towards the mouse - mech.lastFieldPosition = { //used to find velocity of field changes - x: mech.fieldPosition.x, - y: mech.fieldPosition.y - } - const smooth = isInMap ? 0.985 : 0.96; - mech.fieldPosition = { //smooth the mouse position - x: mech.fieldPosition.x * smooth + game.mouseInGame.x * (1 - smooth), - y: mech.fieldPosition.y * smooth + game.mouseInGame.y * (1 - smooth), - } - } + const charge = Math.min(mech.throwCharge / 5, 1) + //***** scale throw speed with the first number, 80 ***** + let speed = 80 * charge * Math.min(1, 0.8 / Math.pow(mech.holdingTarget.mass, 0.25)); - //grab power ups into the field - for (let i = 0, len = powerUp.length; i < len; ++i) { - const dxP = mech.fieldPosition.x - powerUp[i].position.x; - const dyP = mech.fieldPosition.y - powerUp[i].position.y; - const dist2 = dxP * dxP + dyP * dyP; - // float towards field if looking at and in range or if very close to player - if (dist2 < mech.fieldRadius * mech.fieldRadius && (mech.lookingAt(powerUp[i]) || dist2 < 16000) && !(mech.health === mech.maxHealth && powerUp[i].name === "heal")) { - powerUp[i].force.x += 7 * (dxP / dist2) * powerUp[i].mass; - powerUp[i].force.y += 7 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * game.g; //negate gravity - //extra friction - Matter.Body.setVelocity(powerUp[i], { + if (Matter.Query.collides(mech.holdingTarget, map).length !== 0) { + speed *= 0.7 //drop speed by 30% if touching map + if (Matter.Query.ray(map, mech.holdingTarget.position, mech.pos).length !== 0) speed = 0 //drop to zero if the center of the block can't see the center of the player through the map + //|| Matter.Query.ray(body, mech.holdingTarget.position, mech.pos).length > 1 + } + + mech.throwCharge = 0; + Matter.Body.setVelocity(mech.holdingTarget, { + x: player.velocity.x * 0.5 + Math.cos(mech.angle) * speed, + y: player.velocity.y * 0.5 + Math.sin(mech.angle) * speed + }); + //player recoil //stronger in x-dir to prevent jump hacking + + Matter.Body.setVelocity(player, { + x: player.velocity.x - Math.cos(mech.angle) * speed / (mech.crouch ? 30 : 10) * Math.sqrt(mech.holdingTarget.mass), + y: player.velocity.y - Math.sin(mech.angle) * speed / 30 * Math.sqrt(mech.holdingTarget.mass) + }); + mech.definePlayerMass() //return to normal player mass + } + } else { + mech.isHolding = false + } + }, + drawField() { + if (mech.holdingTarget) { + ctx.fillStyle = "rgba(110,170,200," + (mech.energy * (0.05 + 0.05 * Math.random())) + ")"; + ctx.strokeStyle = "rgba(110, 200, 235, " + (0.3 + 0.08 * Math.random()) + ")" //"#9bd" //"rgba(110, 200, 235, " + (0.5 + 0.1 * Math.random()) + ")" + } else { + ctx.fillStyle = "rgba(110,170,200," + (0.02 + mech.energy * (0.15 + 0.15 * Math.random())) + ")"; + ctx.strokeStyle = "rgba(110, 200, 235, " + (0.6 + 0.2 * Math.random()) + ")" //"#9bd" //"rgba(110, 200, 235, " + (0.5 + 0.1 * Math.random()) + ")" + } + // const off = 2 * Math.cos(game.cycle * 0.1) + const range = mech.fieldRange; + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, range, mech.angle - Math.PI * mech.fieldArc, mech.angle + Math.PI * mech.fieldArc, false); + ctx.lineWidth = 2; + ctx.lineCap = "butt" + ctx.stroke(); + let eye = 13; + let aMag = 0.75 * Math.PI * mech.fieldArc + let a = mech.angle + aMag + let cp1x = mech.pos.x + 0.6 * range * Math.cos(a) + let cp1y = mech.pos.y + 0.6 * range * Math.sin(a) + ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle)) + a = mech.angle - aMag + cp1x = mech.pos.x + 0.6 * range * Math.cos(a) + cp1y = mech.pos.y + 0.6 * range * Math.sin(a) + ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + 1 * range * Math.cos(mech.angle - Math.PI * mech.fieldArc), mech.pos.y + 1 * range * Math.sin(mech.angle - Math.PI * mech.fieldArc)) + ctx.fill(); + // ctx.lineTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle)); + + //draw random lines in field for cool effect + let offAngle = mech.angle + 1.7 * Math.PI * mech.fieldArc * (Math.random() - 0.5); + ctx.beginPath(); + eye = 15; + ctx.moveTo(mech.pos.x + eye * Math.cos(mech.angle), mech.pos.y + eye * Math.sin(mech.angle)); + ctx.lineTo(mech.pos.x + range * Math.cos(offAngle), mech.pos.y + range * Math.sin(offAngle)); + ctx.strokeStyle = "rgba(120,170,255,0.6)"; + ctx.lineWidth = 1; + ctx.stroke(); + }, + grabPowerUp() { //look for power ups to grab with field + if (mech.fireCDcycle < mech.cycle) mech.fireCDcycle = mech.cycle - 1 + for (let i = 0, len = powerUp.length; i < len; ++i) { + const dxP = mech.pos.x - powerUp[i].position.x; + const dyP = mech.pos.y - powerUp[i].position.y; + const dist2 = dxP * dxP + dyP * dyP; + // float towards player if looking at and in range or if very close to player + if (dist2 < mech.grabPowerUpRange2 && + (mech.lookingAt(powerUp[i]) || dist2 < 16000) && + !(mech.health === mech.maxHealth && powerUp[i].name === "heal") && + Matter.Query.ray(map, powerUp[i].position, mech.pos).length === 0 + ) { + powerUp[i].force.x += 0.05 * (dxP / Math.sqrt(dist2)) * powerUp[i].mass; + powerUp[i].force.y += 0.05 * (dyP / Math.sqrt(dist2)) * powerUp[i].mass - powerUp[i].mass * game.g; //negate gravity + //extra friction + Matter.Body.setVelocity(powerUp[i], { x: powerUp[i].velocity.x * 0.11, y: powerUp[i].velocity.y * 0.11 - }); - if (dist2 < 5000 && !game.isChoosing) { //use power up if it is close enough - powerUps.onPickUp(powerUp[i].position); + }); + if (dist2 < 5000 && !game.isChoosing) { //use power up if it is close enough + powerUps.onPickUp(mech.pos); + Matter.Body.setVelocity(player, { //player knock back, after grabbing power up + x: player.velocity.x + powerUp[i].velocity.x / player.mass * 5, + y: player.velocity.y + powerUp[i].velocity.y / player.mass * 5 + }); powerUp[i].effect(); Matter.World.remove(engine.world, powerUp[i]); powerUp.splice(i, 1); - // mech.fieldRadius += 50 - break; //because the array order is messed up after splice - } + return; //because the array order is messed up after splice } - } - //grab power ups normally too - mech.grabPowerUp(); + } + } + }, + pushMass(who) { + const speed = Vector.magnitude(Vector.sub(who.velocity, player.velocity)) + const fieldBlockCost = (0.03 + Math.sqrt(who.mass) * speed * 0.003) * mech.fieldShieldingScale; + const unit = Vector.normalise(Vector.sub(player.position, who.position)) - if (mech.energy > 0.01) { - //find mouse velocity - const diff = Vector.sub(mech.fieldPosition, mech.lastFieldPosition) - const speed = Vector.magnitude(diff) - const velocity = Vector.mult(Vector.normalise(diff), Math.min(speed, 45)) //limit velocity - let radius, radiusSmooth - if (Matter.Query.ray(map, mech.fieldPosition, player.position).length) { //is there something block the player's view of the field - radius = 0 - radiusSmooth = Math.max(0, isInMap ? 0.96 - 0.02 * speed : 0.995); //0.99 - } else { - radius = Math.max(50, 250 - 2 * speed) - radiusSmooth = 0.97 - } - mech.fieldRadius = mech.fieldRadius * radiusSmooth + radius * (1 - radiusSmooth) + if (mech.energy > fieldBlockCost * 0.2) { //shield needs at least some of the cost to block + mech.energy -= fieldBlockCost + if (mech.energy < 0) { + mech.energy = 0; + } + if (mech.energy > mech.maxEnergy) mech.energy = mech.maxEnergy; - for (let i = 0, len = body.length; i < len; ++i) { - if (Vector.magnitude(Vector.sub(body[i].position, mech.fieldPosition)) < mech.fieldRadius && !body[i].isNotHoldable) { - const DRAIN = speed * body[i].mass * 0.000013 - if (mech.energy > DRAIN) { - mech.energy -= DRAIN; - Matter.Body.setVelocity(body[i], velocity); //give block mouse velocity - Matter.Body.setAngularVelocity(body[i], body[i].angularVelocity * 0.8) - // body[i].force.y -= body[i].mass * game.g; //remove gravity effects - //blocks drift towards center of pilot wave - const sub = Vector.sub(mech.fieldPosition, body[i].position) - const unit = Vector.mult(Vector.normalise(sub), 0.00005 * Vector.magnitude(sub)) - body[i].force.x += unit.x - body[i].force.y += unit.y - body[i].mass * game.g //remove gravity effects - } else { - mech.fieldCDcycle = mech.cycle + 120; - mech.fieldOn = false - mech.fieldRadius = 0 - break + if (mod.blockDmg) { + who.damage(mod.blockDmg * b.dmgScale) + //draw electricity + const step = 40 + ctx.beginPath(); + for (let i = 0, len = 2.5 * mod.blockDmg; i < len; i++) { + let x = mech.pos.x - 20 * unit.x; + let y = mech.pos.y - 20 * unit.y; + ctx.moveTo(x, y); + for (let i = 0; i < 8; i++) { + x += step * (-unit.x + 1.5 * (Math.random() - 0.5)) + y += step * (-unit.y + 1.5 * (Math.random() - 0.5)) + ctx.lineTo(x, y); } - } } - - if (mod.isPilotFreeze) { - for (let i = 0, len = mob.length; i < len; ++i) { - if (Vector.magnitude(Vector.sub(mob[i].position, mech.fieldPosition)) < mech.fieldRadius) { - mobs.statusSlow(mob[i], 120) - } - } - } - - ctx.beginPath(); - const rotate = mech.cycle * 0.008; - mech.fieldPhase += 0.2 // - 0.5 * Math.sqrt(Math.min(mech.energy, 1)); - const off1 = 1 + 0.06 * Math.sin(mech.fieldPhase); - const off2 = 1 - 0.06 * Math.sin(mech.fieldPhase); - ctx.beginPath(); - ctx.ellipse(mech.fieldPosition.x, mech.fieldPosition.y, 1.2 * mech.fieldRadius * off1, 1.2 * mech.fieldRadius * off2, rotate, 0, 2 * Math.PI); - ctx.globalCompositeOperation = "exclusion"; //"exclusion" "difference"; - ctx.fillStyle = "#fff"; //"#eef"; - ctx.fill(); - ctx.globalCompositeOperation = "source-over"; - ctx.beginPath(); - ctx.ellipse(mech.fieldPosition.x, mech.fieldPosition.y, 1.2 * mech.fieldRadius * off1, 1.2 * mech.fieldRadius * off2, rotate, 0, mech.energy * 2 * Math.PI); - ctx.strokeStyle = "#000"; - ctx.lineWidth = 4; + ctx.lineWidth = 3; + ctx.strokeStyle = "#f0f"; ctx.stroke(); - } else { - mech.fieldCDcycle = mech.cycle + 120; - mech.fieldOn = false - mech.fieldRadius = 0 - } } else { - mech.grabPowerUp(); + mech.drawHold(who); } - } else { - mech.fieldOn = false - mech.fieldRadius = 0 - } - mech.drawFieldMeter() - } - } - }, - { - name: "wormhole", - description: "use energy to tunnel through a wormhole
wormholes attract blocks and power ups", //
bullets may also traverse wormholes - effect: () => { - game.replaceTextLog = true; //allow text over write - mech.drop(); - // mech.hole = { //this is reset with each new field, but I'm leaving it here for reference - // isOn: false, - // isReady: true, - // pos1: {x: 0,y: 0}, - // pos2: {x: 0,y: 0}, - // angle: 0, - // unit:{x:0,y:0}, - // } - mech.hold = function () { - if (mech.hole.isOn) { - // draw holes - mech.fieldRange = 0.97 * mech.fieldRange + 0.03 * (50 + 10 * Math.sin(game.cycle * 0.025)) - const semiMajorAxis = mech.fieldRange + 30 - const edge1a = Vector.add(Vector.mult(mech.hole.unit, semiMajorAxis), mech.hole.pos1) - const edge1b = Vector.add(Vector.mult(mech.hole.unit, -semiMajorAxis), mech.hole.pos1) - const edge2a = Vector.add(Vector.mult(mech.hole.unit, semiMajorAxis), mech.hole.pos2) - const edge2b = Vector.add(Vector.mult(mech.hole.unit, -semiMajorAxis), mech.hole.pos2) - ctx.beginPath(); - ctx.moveTo(edge1a.x, edge1a.y) - ctx.bezierCurveTo(mech.hole.pos1.x, mech.hole.pos1.y, mech.hole.pos2.x, mech.hole.pos2.y, edge2a.x, edge2a.y); - ctx.lineTo(edge2b.x, edge2b.y) - ctx.bezierCurveTo(mech.hole.pos2.x, mech.hole.pos2.y, mech.hole.pos1.x, mech.hole.pos1.y, edge1b.x, edge1b.y); - ctx.fillStyle = `rgba(255,255,255,${200 / mech.fieldRange / mech.fieldRange})` //"rgba(0,0,0,0.1)" - ctx.fill(); - ctx.beginPath(); - ctx.ellipse(mech.hole.pos1.x, mech.hole.pos1.y, mech.fieldRange, semiMajorAxis, mech.hole.angle, 0, 2 * Math.PI) - ctx.ellipse(mech.hole.pos2.x, mech.hole.pos2.y, mech.fieldRange, semiMajorAxis, mech.hole.angle, 0, 2 * Math.PI) - ctx.fillStyle = `rgba(255,255,255,${32 / mech.fieldRange})` - ctx.fill(); - - //suck power ups - for (let i = 0, len = powerUp.length; i < len; ++i) { - //which hole is closer - const dxP1 = mech.hole.pos1.x - powerUp[i].position.x; - const dyP1 = mech.hole.pos1.y - powerUp[i].position.y; - const dxP2 = mech.hole.pos2.x - powerUp[i].position.x; - const dyP2 = mech.hole.pos2.y - powerUp[i].position.y; - let dxP, dyP, dist2 - if (dxP1 * dxP1 + dyP1 * dyP1 < dxP2 * dxP2 + dyP2 * dyP2) { - dxP = dxP1 - dyP = dyP1 - } else { - dxP = dxP2 - dyP = dyP2 - } - dist2 = dxP * dxP + dyP * dyP; - if (dist2 < 600000 && !(mech.health === mech.maxHealth && powerUp[i].name === "heal")) { - powerUp[i].force.x += 4 * (dxP / dist2) * powerUp[i].mass; // float towards hole - powerUp[i].force.y += 4 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * game.g; //negate gravity - Matter.Body.setVelocity(powerUp[i], { //extra friction - x: powerUp[i].velocity.x * 0.05, - y: powerUp[i].velocity.y * 0.05 + // mech.holdingTarget = null + //knock backs + if (mech.fieldShieldingScale > 0) { + const massRoot = Math.sqrt(Math.min(12, Math.max(0.15, who.mass))); // masses above 12 can start to overcome the push back + Matter.Body.setVelocity(who, { + x: player.velocity.x - (15 * unit.x) / massRoot, + y: player.velocity.y - (15 * unit.y) / massRoot }); - if (dist2 < 1000 && !game.isChoosing) { //use power up if it is close enough - mech.fieldRange *= 0.8 - powerUps.onPickUp(powerUp[i].position); - powerUp[i].effect(); - Matter.World.remove(engine.world, powerUp[i]); - powerUp.splice(i, 1); - break; //because the array order is messed up after splice - } - } - } - //suck and shrink blocks - const suckRange = 500 - const shrinkRange = 100 - const shrinkScale = 0.97; - const slowScale = 0.9 - for (let i = 0, len = body.length; i < len; i++) { - if (!body[i].isNotHoldable) { - const dist1 = Vector.magnitude(Vector.sub(mech.hole.pos1, body[i].position)) - const dist2 = Vector.magnitude(Vector.sub(mech.hole.pos2, body[i].position)) - if (dist1 < dist2) { - if (dist1 < suckRange) { - const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos1, body[i].position)), 1) - const slow = Vector.mult(body[i].velocity, slowScale) - Matter.Body.setVelocity(body[i], Vector.add(slow, pull)); - //shrink - if (Vector.magnitude(Vector.sub(mech.hole.pos1, body[i].position)) < shrinkRange) { - Matter.Body.scale(body[i], shrinkScale, shrinkScale); - if (body[i].mass < 0.05) { - Matter.World.remove(engine.world, body[i]); - body.splice(i, 1); - mech.fieldRange *= 0.8 - if (mod.isWormholeEnergy && mech.energy < mech.maxEnergy * 2) mech.energy = mech.maxEnergy * 2 - if (mod.isWormSpores) { //pandimensionalspermia - b.spore(Vector.add(mech.hole.pos2, Vector.rotate({ - x: mech.fieldRange, - y: 0 - }, 2 * Math.PI * Math.random()))) - Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(mech.hole.unit, -Math.PI / 2), 15)); - } - break - } - } - } - } else if (dist2 < suckRange) { - const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos2, body[i].position)), 1) - const slow = Vector.mult(body[i].velocity, slowScale) - Matter.Body.setVelocity(body[i], Vector.add(slow, pull)); - //shrink - if (Vector.magnitude(Vector.sub(mech.hole.pos2, body[i].position)) < shrinkRange) { - Matter.Body.scale(body[i], shrinkScale, shrinkScale); - if (body[i].mass < 0.05) { - Matter.World.remove(engine.world, body[i]); - body.splice(i, 1); - mech.fieldRange *= 0.8 - if (mod.isWormholeEnergy && mech.energy < mech.maxEnergy * 2) mech.energy = mech.maxEnergy * 2 - if (mod.isWormSpores) { //pandimensionalspermia - b.spore(Vector.add(mech.hole.pos1, Vector.rotate({ - x: mech.fieldRange, - y: 0 - }, 2 * Math.PI * Math.random()))) - Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(mech.hole.unit, Math.PI / 2), 15)); - - } - break - } - } - } - } - } - if (mod.isWormBullets) { - //teleport bullets - for (let i = 0, len = bullet.length; i < len; ++i) { //teleport bullets from hole1 to hole2 - if (!bullet[i].botType && !bullet[i].isInHole) { //don't teleport bots - if (Vector.magnitude(Vector.sub(mech.hole.pos1, bullet[i].position)) < mech.fieldRange) { //find if bullet is touching hole1 - Matter.Body.setPosition(bullet[i], Vector.add(mech.hole.pos2, Vector.sub(mech.hole.pos1, bullet[i].position))); - mech.fieldRange += 5 - bullet[i].isInHole = true - } else if (Vector.magnitude(Vector.sub(mech.hole.pos2, bullet[i].position)) < mech.fieldRange) { //find if bullet is touching hole1 - Matter.Body.setPosition(bullet[i], Vector.add(mech.hole.pos1, Vector.sub(mech.hole.pos2, bullet[i].position))); - mech.fieldRange += 5 - bullet[i].isInHole = true - } - } - } - // mobs get pushed away - for (let i = 0, len = mob.length; i < len; i++) { - if (Vector.magnitude(Vector.sub(mech.hole.pos1, mob[i].position)) < 200) { - const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos1, mob[i].position)), -0.07) - Matter.Body.setVelocity(mob[i], Vector.add(mob[i].velocity, pull)); - } - if (Vector.magnitude(Vector.sub(mech.hole.pos2, mob[i].position)) < 200) { - const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos2, mob[i].position)), -0.07) - Matter.Body.setVelocity(mob[i], Vector.add(mob[i].velocity, pull)); - } - } - } - } - - if (input.field && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed - const justPastMouse = Vector.add(Vector.mult(Vector.normalise(Vector.sub(game.mouseInGame, mech.pos)), 50), game.mouseInGame) - const scale = 60 - // console.log(Matter.Query.region(map, bounds)) - if (mech.hole.isReady && - ( - Matter.Query.region(map, { - min: { - x: game.mouseInGame.x - scale, - y: game.mouseInGame.y - scale - }, - max: { - x: game.mouseInGame.x + scale, - y: game.mouseInGame.y + scale - } - }).length === 0 && - Matter.Query.ray(map, mech.pos, justPastMouse).length === 0 - // Matter.Query.ray(map, mech.pos, game.mouseInGame).length === 0 && - // Matter.Query.ray(map, player.position, game.mouseInGame).length === 0 && - // Matter.Query.ray(map, player.position, justPastMouse).length === 0 - ) - ) { - const sub = Vector.sub(game.mouseInGame, mech.pos) - const mag = Vector.magnitude(sub) - const drain = 0.04 + 0.007 * Math.sqrt(mag) - if (mech.energy > drain && mag > 300) { - mech.energy -= drain - mech.hole.isReady = false; - mech.fieldRange = 0 - Matter.Body.setPosition(player, game.mouseInGame); - const velocity = Vector.mult(Vector.normalise(sub), 18) - Matter.Body.setVelocity(player, { - x: velocity.x, - y: velocity.y - 4 //an extra vertical kick so the player hangs in place longer - }); - mech.immuneCycle = mech.cycle + 15; //player is immune to collision damage - // move bots to follow player - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType) { - Matter.Body.setPosition(bullet[i], Vector.add(player.position, { - x: 250 * (Math.random() - 0.5), - y: 250 * (Math.random() - 0.5) - })); - Matter.Body.setVelocity(bullet[i], { - x: 0, - y: 0 + mech.fieldCDcycle = mech.cycle + mech.fieldBlockCD; + if (mech.crouch) { + Matter.Body.setVelocity(player, { + x: player.velocity.x + 0.4 * unit.x * massRoot, + y: player.velocity.y + 0.4 * unit.y * massRoot + }); + } else { + Matter.Body.setVelocity(player, { + x: player.velocity.x + 5 * unit.x * massRoot, + y: player.velocity.y + 5 * unit.y * massRoot }); - } } - - //set holes - mech.hole.isOn = true; - mech.hole.pos1.x = mech.pos.x - mech.hole.pos1.y = mech.pos.y - mech.hole.pos2.x = player.position.x - mech.hole.pos2.y = player.position.y - mech.hole.angle = Math.atan2(sub.y, sub.x) - mech.hole.unit = Vector.perp(Vector.normalise(sub)) - - if (mod.isWormholeDamage) { - who = Matter.Query.ray(mob, mech.pos, game.mouseInGame, 60) - for (let i = 0; i < who.length; i++) { - if (who[i].body.alive) { - const dmg = b.dmgScale * 6 - who[i].body.damage(dmg); - who[i].body.locatePlayer(); - game.drawList.push({ //add dmg to draw queue - x: who[i].body.position.x, - y: who[i].body.position.y, - radius: Math.log(2 * dmg + 1.1) * 40, - color: game.playerDmgColor, - time: game.drawTime - }); - } - } - } - } else { - mech.grabPowerUp(); - } } else { - mech.grabPowerUp(); + if (mod.isStunField && mech.fieldUpgrades[mech.fieldMode].name === "perfect diamagnetism") mobs.statusStun(who, mod.isStunField) + // mobs.statusSlow(who, mod.isStunField) + const massRoot = Math.sqrt(Math.max(0.15, who.mass)); // masses above 12 can start to overcome the push back + Matter.Body.setVelocity(who, { + x: player.velocity.x - (20 * unit.x) / massRoot, + y: player.velocity.y - (20 * unit.y) / massRoot + }); + if (who.dropPowerUp && player.speed < 12) { + const massRootCap = Math.sqrt(Math.min(10, Math.max(0.4, who.mass))); // masses above 12 can start to overcome the push back + Matter.Body.setVelocity(player, { + x: 0.9 * player.velocity.x + 0.6 * unit.x * massRootCap, + y: 0.9 * player.velocity.y + 0.6 * unit.y * massRootCap + }); + } } - } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released - mech.pickUp(); - } else { - mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - mech.hole.isReady = true; - } - mech.drawFieldMeter() } - } }, - ], + pushMobsFacing() { // find mobs in range and in direction looking + for (let i = 0, len = mob.length; i < len; ++i) { + if ( + Vector.magnitude(Vector.sub(mob[i].position, player.position)) - mob[i].radius < mech.fieldRange && + mech.lookingAt(mob[i]) && + Matter.Query.ray(map, mob[i].position, mech.pos).length === 0 + ) { + mob[i].locatePlayer(); + mech.pushMass(mob[i]); + } + } + }, + pushMobs360(range = mech.fieldRange * 0.75) { // find mobs in range in any direction + for (let i = 0, len = mob.length; i < len; ++i) { + if ( + Vector.magnitude(Vector.sub(mob[i].position, mech.pos)) < range && + Matter.Query.ray(map, mob[i].position, mech.pos).length === 0 + ) { + mob[i].locatePlayer(); + mech.pushMass(mob[i]); + } + } + }, + lookForPickUp() { //find body to pickup + if (mech.energy > mech.fieldRegen) mech.energy -= mech.fieldRegen; + const grabbing = { + targetIndex: null, + targetRange: 150, + // lookingAt: false //false to pick up object in range, but not looking at + }; + for (let i = 0, len = body.length; i < len; ++i) { + if (Matter.Query.ray(map, body[i].position, mech.pos).length === 0) { + //is mech next body a better target then my current best + const dist = Vector.magnitude(Vector.sub(body[i].position, mech.pos)); + const looking = mech.lookingAt(body[i]); + // if (dist < grabbing.targetRange && (looking || !grabbing.lookingAt) && !body[i].isNotHoldable) { + if (dist < grabbing.targetRange && looking && !body[i].isNotHoldable) { + grabbing.targetRange = dist; + grabbing.targetIndex = i; + // grabbing.lookingAt = looking; + } + } + } + // set pick up target for when mouse is released + if (body[grabbing.targetIndex]) { + mech.holdingTarget = body[grabbing.targetIndex]; + // + ctx.beginPath(); //draw on each valid body + let vertices = mech.holdingTarget.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.fillStyle = "rgba(190,215,230," + (0.3 + 0.7 * Math.random()) + ")"; + ctx.fill(); + + ctx.globalAlpha = 0.2; + mech.drawHold(mech.holdingTarget); + ctx.globalAlpha = 1; + } else { + mech.holdingTarget = null; + } + }, + pickUp() { + //triggers when a hold target exits and field button is released + mech.isHolding = true; + //conserve momentum when player mass changes + totalMomentum = Vector.add(Vector.mult(player.velocity, player.mass), Vector.mult(mech.holdingTarget.velocity, mech.holdingTarget.mass)) + Matter.Body.setVelocity(player, Vector.mult(totalMomentum, 1 / (mech.defaultMass + mech.holdingTarget.mass))); + + mech.definePlayerMass(mech.defaultMass + mech.holdingTarget.mass * mech.holdingMassScale) + //make block collide with nothing + mech.holdingTarget.collisionFilter.category = 0; + mech.holdingTarget.collisionFilter.mask = 0; + }, + wakeCheck() { + if (mech.isBodiesAsleep) { + mech.isBodiesAsleep = false; + + function wake(who) { + for (let i = 0, len = who.length; i < len; ++i) { + Matter.Sleeping.set(who[i], false) + if (who[i].storeVelocity) { + Matter.Body.setVelocity(who[i], { + x: who[i].storeVelocity.x, + y: who[i].storeVelocity.y + }) + Matter.Body.setAngularVelocity(who[i], who[i].storeAngularVelocity) + } + } + } + wake(mob); + wake(body); + wake(bullet); + for (let i = 0, len = cons.length; i < len; i++) { + if (cons[i].stiffness === 0) { + cons[i].stiffness = cons[i].storeStiffness + } + } + // wake(powerUp); + } + }, + hold() {}, + setField(index) { + if (isNaN(index)) { //find index by name + let found = false + for (let i = 0; i < mech.fieldUpgrades.length; i++) { + if (index === mech.fieldUpgrades[i].name) { + index = i; + found = true; + break; + } + } + if (!found) return //if you can't find the field don't give a field to avoid game crash + } + mech.fieldMode = index; + document.getElementById("field").innerHTML = mech.fieldUpgrades[index].name + mech.setHoldDefaults(); + mech.fieldUpgrades[index].effect(); + }, + fieldUpgrades: [{ + name: "field emitter", + description: "use energy to block mobs
throw blocks to damage mobs
pick up power ups", + effect: () => { + game.replaceTextLog = true; //allow text over write + mech.hold = function() { + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throwBlock(); + } else if ((input.field && mech.fieldCDcycle < mech.cycle)) { //not hold but field button is pressed + mech.grabPowerUp(); + mech.lookForPickUp(); + if (mech.energy > 0.05) { + mech.drawField(); + mech.pushMobsFacing(); + } + } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + mech.drawFieldMeter() + } + } + }, + { + name: "standing wave harmonics", + description: "3 oscillating shields are permanently active
blocking drains energy
blocking has no cool down", + effect: () => { + // mech.fieldHarmReduction = 0.80; + mech.fieldBlockCD = 0; + mech.hold = function() { + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throwBlock(); + } else if ((input.field) && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed + mech.grabPowerUp(); + mech.lookForPickUp(); + } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + if (mech.energy > 0.1 && mech.fieldCDcycle < mech.cycle) { + const fieldRange1 = (0.7 + 0.3 * Math.sin(mech.cycle / 23)) * mech.fieldRange + const fieldRange2 = (0.63 + 0.37 * Math.sin(mech.cycle / 37)) * mech.fieldRange + const fieldRange3 = (0.65 + 0.35 * Math.sin(mech.cycle / 47)) * mech.fieldRange + const netfieldRange = Math.max(fieldRange1, fieldRange2, fieldRange3) + ctx.fillStyle = "rgba(110,170,200," + (0.04 + mech.energy * (0.12 + 0.13 * Math.random())) + ")"; + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, fieldRange1, 0, 2 * Math.PI); + ctx.fill(); + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, fieldRange2, 0, 2 * Math.PI); + ctx.fill(); + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, fieldRange3, 0, 2 * Math.PI); + ctx.fill(); + mech.pushMobs360(netfieldRange); + // mech.pushBody360(netfieldRange); //can't throw block when pushhing blocks away + } + mech.drawFieldMeter() + } + } + }, + { + name: "perfect diamagnetism", + // description: "gain energy when blocking
no recoil when blocking", + description: "blocking does not drain energy
blocking has no cool down and less recoil
attract power ups from far away", + effect: () => { + mech.fieldShieldingScale = 0; + mech.grabPowerUpRange2 = 10000000 + // mech.holdingMassScale = 0.03; //can hold heavier blocks with lower cost to jumping + // mech.fieldMeterColor = "#0af" + // mech.fieldArc = 0.3; //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) + // mech.calculateFieldThreshold(); + mech.hold = function() { + const wave = Math.sin(mech.cycle * 0.022); + mech.fieldRange = 170 + 12 * wave + mech.fieldArc = 0.33 + 0.045 * wave //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) + mech.calculateFieldThreshold(); + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throwBlock(); + } else if ((input.field && mech.fieldCDcycle < mech.cycle)) { //not hold but field button is pressed + mech.grabPowerUp(); + mech.lookForPickUp(); + if (mech.energy > 0.05) { + //draw field + if (mech.holdingTarget) { + ctx.fillStyle = "rgba(110,170,200," + (0.06 + 0.03 * Math.random()) + ")"; + ctx.strokeStyle = "rgba(110, 200, 235, " + (0.35 + 0.05 * Math.random()) + ")" + } else { + ctx.fillStyle = "rgba(110,170,200," + (0.27 + 0.2 * Math.random() - 0.1 * wave) + ")"; + ctx.strokeStyle = "rgba(110, 200, 235, " + (0.4 + 0.5 * Math.random()) + ")" + } + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, mech.fieldRange, mech.angle - Math.PI * mech.fieldArc, mech.angle + Math.PI * mech.fieldArc, false); + ctx.lineWidth = 2.5 - 1.5 * wave; + ctx.lineCap = "butt" + ctx.stroke(); + const curve = 0.57 + 0.04 * wave + const aMag = (1 - curve * 1.2) * Math.PI * mech.fieldArc + let a = mech.angle + aMag + let cp1x = mech.pos.x + curve * mech.fieldRange * Math.cos(a) + let cp1y = mech.pos.y + curve * mech.fieldRange * Math.sin(a) + ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle)) + a = mech.angle - aMag + cp1x = mech.pos.x + curve * mech.fieldRange * Math.cos(a) + cp1y = mech.pos.y + curve * mech.fieldRange * Math.sin(a) + ctx.quadraticCurveTo(cp1x, cp1y, mech.pos.x + 1 * mech.fieldRange * Math.cos(mech.angle - Math.PI * mech.fieldArc), mech.pos.y + 1 * mech.fieldRange * Math.sin(mech.angle - Math.PI * mech.fieldArc)) + ctx.fill(); + mech.pushMobsFacing(); + } + } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + mech.drawFieldMeter() + + if (mod.isPerfectBrake) { //cap mob speed around player + const range = 350 + 140 * wave + for (let i = 0; i < mob.length; i++) { + const distance = Vector.magnitude(Vector.sub(mech.pos, mob[i].position)) + if (distance < range) { + const cap = mob[i].isShielded ? 8.5 : 4.5 + if (mob[i].speed > cap && Vector.dot(mob[i].velocity, Vector.sub(mech.pos, mob[i].position)) > 0) { // if velocity is directed towards player + Matter.Body.setVelocity(mob[i], Vector.mult(Vector.normalise(mob[i].velocity), cap)); //set velocity to cap, but keep the direction + } + } + } + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, range, 0, 2 * Math.PI); + ctx.fillStyle = "hsla(200,50%,61%,0.08)"; + ctx.fill(); + } + } + } + }, + { + name: "nano-scale manufacturing", + description: "use energy to block mobs
excess energy used to build drones
double your default energy regeneration", + effect: () => { + mech.hold = function() { + if (mech.energy > mech.maxEnergy - 0.02 && mech.fieldCDcycle < mech.cycle) { + if (mod.isSporeField) { + // mech.fieldCDcycle = mech.cycle + 10; // set cool down to prevent +energy from making huge numbers of drones + const len = Math.floor(6 + 4 * Math.random()) + mech.energy -= len * 0.09; + for (let i = 0; i < len; i++) { + b.spore(mech.pos) + } + } else if (mod.isMissileField) { + // mech.fieldCDcycle = mech.cycle + 10; // set cool down to prevent +energy from making huge numbers of drones + mech.energy -= 0.5; + b.missile({ + x: mech.pos.x + 40 * Math.cos(mech.angle), + y: mech.pos.y + 40 * Math.sin(mech.angle) - 3 + }, + mech.angle + (0.5 - Math.random()) * (mech.crouch ? 0 : 0.2), + -3 * (0.5 - Math.random()) + (mech.crouch ? 25 : -8) * b.fireCD, + 1, mod.recursiveMissiles) + } else if (mod.isIceField) { + // mech.fieldCDcycle = mech.cycle + 17; // set cool down to prevent +energy from making huge numbers of drones + mech.energy -= 0.04; + b.iceIX(1) + } else { + // mech.fieldCDcycle = mech.cycle + 10; // set cool down to prevent +energy from making huge numbers of drones + mech.energy -= 0.33; + b.drone(1) + } + } + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throwBlock(); + } else if ((input.field && mech.fieldCDcycle < mech.cycle)) { //not hold but field button is pressed + mech.grabPowerUp(); + mech.lookForPickUp(); + if (mech.energy > 0.05) { + mech.drawField(); + mech.pushMobsFacing(); + } + } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + mech.energy += mech.fieldRegen; + mech.drawFieldMeter() + } + } + }, + { + name: "negative mass field", + description: "use energy to nullify  gravity
reduce harm by 40%
blocks held by the field have a lower mass", + fieldDrawRadius: 0, + effect: () => { + mech.fieldFire = true; + mech.holdingMassScale = 0.03; //can hold heavier blocks with lower cost to jumping + mech.fieldMeterColor = "#000" + mech.fieldHarmReduction = 0.6; + mech.fieldDrawRadius = 0; + + mech.hold = function() { + mech.airSpeedLimit = 125 //5 * player.mass * player.mass + mech.FxAir = 0.016 + mech.isFieldActive = false; + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throwBlock(); + } else if (input.field && mech.fieldCDcycle < mech.cycle) { //push away + mech.grabPowerUp(); + mech.lookForPickUp(); + const DRAIN = 0.00035 + if (mech.energy > DRAIN) { + mech.isFieldActive = true; //used with mod.isHarmReduce + mech.airSpeedLimit = 400 // 7* player.mass * player.mass + mech.FxAir = 0.005 + // mech.pushMobs360(); + + //repulse mobs + // for (let i = 0, len = mob.length; i < len; ++i) { + // sub = Vector.sub(mob[i].position, mech.pos); + // dist2 = Vector.magnitudeSquared(sub); + // if (dist2 < this.fieldDrawRadius * this.fieldDrawRadius && mob[i].speed > 6) { + // const force = Vector.mult(Vector.perp(Vector.normalise(sub)), 0.00004 * mob[i].speed * mob[i].mass) + // mob[i].force.x = force.x + // mob[i].force.y = force.y + // } + // } + //look for nearby objects to make zero-g + function zeroG(who, range, mag = 1.06) { + for (let i = 0, len = who.length; i < len; ++i) { + sub = Vector.sub(who[i].position, mech.pos); + dist = Vector.magnitude(sub); + if (dist < range) { + who[i].force.y -= who[i].mass * (game.g * mag); //add a bit more then standard gravity + } + } + } + // zeroG(bullet); //works fine, but not that noticeable and maybe not worth the possible performance hit + // zeroG(mob); //mobs are too irregular to make this work? + + if (input.down) { //down + player.force.y -= 0.5 * player.mass * game.g; + this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 400 * 0.03; + zeroG(powerUp, this.fieldDrawRadius, 0.7); + zeroG(body, this.fieldDrawRadius, 0.7); + } else if (input.up) { //up + mech.energy -= 5 * DRAIN; + this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 850 * 0.03; + player.force.y -= 1.45 * player.mass * game.g; + zeroG(powerUp, this.fieldDrawRadius, 1.38); + zeroG(body, this.fieldDrawRadius, 1.38); + } else { + mech.energy -= DRAIN; + this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 650 * 0.03; + player.force.y -= 1.07 * player.mass * game.g; // slow upward drift + zeroG(powerUp, this.fieldDrawRadius); + zeroG(body, this.fieldDrawRadius); + } + if (mech.energy < 0.001) { + mech.fieldCDcycle = mech.cycle + 120; + mech.energy = 0; + } + //add extra friction for horizontal motion + if (input.down || input.up || input.left || input.right) { + Matter.Body.setVelocity(player, { + x: player.velocity.x * 0.99, + y: player.velocity.y * 0.98 + }); + } else { //slow rise and fall + Matter.Body.setVelocity(player, { + x: player.velocity.x * 0.99, + y: player.velocity.y * 0.98 + }); + } + if (mod.isFreezeMobs) { + const ICE_DRAIN = 0.0005 + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].distanceToPlayer() + mob[i].radius < this.fieldDrawRadius && !mob[i].shield && !mob[i].isShielded) { + if (mech.energy > ICE_DRAIN * 2) { + mech.energy -= ICE_DRAIN; + this.fieldDrawRadius -= 2; + mobs.statusSlow(mob[i], 45) + } else { + break; + } + } + } + } + + //draw zero-G range + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, this.fieldDrawRadius, 0, 2 * Math.PI); + ctx.fillStyle = "#f5f5ff"; + ctx.globalCompositeOperation = "difference"; + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + } + } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released + mech.pickUp(); + this.fieldDrawRadius = 0 + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + this.fieldDrawRadius = 0 + } + mech.drawFieldMeter("rgba(0,0,0,0.2)") + } + } + }, + { + name: "plasma torch", + description: "use energy to emit short range plasma
damages and pushes mobs away", + effect() { + mech.fieldMeterColor = "#f0f" + mech.hold = function() { + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throwBlock(); + } else if (input.field && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed + mech.grabPowerUp(); + mech.lookForPickUp(); + const DRAIN = 0.0012 + if (mech.energy > DRAIN) { + mech.energy -= DRAIN; + if (mech.energy < 0) { + mech.fieldCDcycle = mech.cycle + 120; + mech.energy = 0; + } + //calculate laser collision + let best; + let range = mod.isPlasmaRange * (120 + (mech.crouch ? 400 : 300) * Math.sqrt(Math.random())) //+ 100 * Math.sin(mech.cycle * 0.3); + // const dir = mech.angle // + 0.04 * (Math.random() - 0.5) + const path = [{ + x: mech.pos.x + 20 * Math.cos(mech.angle), + y: mech.pos.y + 20 * Math.sin(mech.angle) + }, + { + x: mech.pos.x + range * Math.cos(mech.angle), + y: mech.pos.y + range * Math.sin(mech.angle) + } + ]; + const vertexCollision = function(v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + }; + + //check for collisions + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + vertexCollision(path[0], path[1], mob); + vertexCollision(path[0], path[1], map); + vertexCollision(path[0], path[1], body); + if (best.dist2 != Infinity) { //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + if (best.who.alive) { + const dmg = 0.8 * b.dmgScale; //********** SCALE DAMAGE HERE ********************* + best.who.damage(dmg); + best.who.locatePlayer(); + + //push mobs away + const force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, path[1])), -0.01 * Math.min(5, best.who.mass)) + Matter.Body.applyForce(best.who, path[1], force) + Matter.Body.setVelocity(best.who, { //friction + x: best.who.velocity.x * 0.7, + y: best.who.velocity.y * 0.7 + }); + //draw mob damage circle + game.drawList.push({ + x: path[1].x, + y: path[1].y, + radius: Math.sqrt(dmg) * 50, + color: "rgba(255,0,255,0.2)", + time: game.drawTime * 4 + }); + } else if (!best.who.isStatic) { + //push blocks away + const force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, path[1])), -0.007 * Math.sqrt(Math.sqrt(best.who.mass))) + Matter.Body.applyForce(best.who, path[1], force) + } + } + + //draw blowtorch laser beam + ctx.strokeStyle = "rgba(255,0,255,0.1)" + ctx.lineWidth = 14 + ctx.beginPath(); + ctx.moveTo(path[0].x, path[0].y); + ctx.lineTo(path[1].x, path[1].y); + ctx.stroke(); + ctx.strokeStyle = "#f0f"; + ctx.lineWidth = 2 + ctx.stroke(); + + //draw electricity + const Dx = Math.cos(mech.angle); + const Dy = Math.sin(mech.angle); + let x = mech.pos.x + 20 * Dx; + let y = mech.pos.y + 20 * Dy; + ctx.beginPath(); + ctx.moveTo(x, y); + const step = Vector.magnitude(Vector.sub(path[0], path[1])) / 10 + for (let i = 0; i < 8; i++) { + x += step * (Dx + 1.5 * (Math.random() - 0.5)) + y += step * (Dy + 1.5 * (Math.random() - 0.5)) + ctx.lineTo(x, y); + } + ctx.lineWidth = 2 * Math.random(); + ctx.stroke(); + } + } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + mech.drawFieldMeter("rgba(0, 0, 0, 0.2)") + + } + } + }, + { + name: "time dilation field", + description: "use energy to stop time
move and fire while time is stopped", + effect: () => { + // mech.fieldMeterColor = "#000" + mech.fieldFire = true; + mech.isBodiesAsleep = false; + mech.hold = function() { + if (mech.isHolding) { + mech.wakeCheck(); + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throwBlock(); + } else if (input.field && mech.fieldCDcycle < mech.cycle) { + mech.grabPowerUp(); + mech.lookForPickUp(180); + + const DRAIN = 0.0008 + if (mech.energy > DRAIN) { + mech.energy -= DRAIN; + if (mech.energy < DRAIN) { + mech.fieldCDcycle = mech.cycle + 120; + mech.energy = 0; + mech.wakeCheck(); + } + //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; + + function sleep(who) { + for (let i = 0, len = who.length; i < len; ++i) { + if (!who[i].isSleeping) { + who[i].storeVelocity = who[i].velocity + who[i].storeAngularVelocity = who[i].angularVelocity + } + Matter.Sleeping.set(who[i], true) + } + } + sleep(mob); + sleep(body); + sleep(bullet); + //doesn't really work, just slows down constraints + for (let i = 0, len = cons.length; i < len; i++) { + if (cons[i].stiffness !== 0) { + cons[i].storeStiffness = cons[i].stiffness; + cons[i].stiffness = 0; + } + } + + game.cycle--; //pause all functions that depend on game cycle increasing + if (mod.isTimeSkip) { + mech.immuneCycle = mech.cycle + 10; + game.isTimeSkipping = true; + mech.cycle++; + game.gravity(); + Engine.update(engine, game.delta); + // level.checkZones(); + // level.checkQuery(); + mech.move(); + game.checks(); + // mobs.loop(); + // mech.draw(); + mech.walk_cycle += mech.flipLegs * mech.Vx; + // mech.hold(); + // mech.energy += DRAIN; // 1 to undo the energy drain from time speed up, 0.5 to cut energy drain in half + b.fire(); + // b.bulletRemove(); + b.bulletDo(); + game.isTimeSkipping = false; + } + // game.cycle--; //pause all functions that depend on game cycle increasing + // if (mod.isTimeSkip && !game.isTimeSkipping) { //speed up the rate of time + // game.timeSkip(1) + // mech.energy += 1.5 * DRAIN; //x1 to undo the energy drain from time speed up, x1.5 to cut energy drain in half + // } + } + } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released + mech.wakeCheck(); + mech.pickUp(); + } else { + mech.wakeCheck(); + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + mech.drawFieldMeter() + } + } + }, + { + name: "metamaterial cloaking", //"weak photonic coupling" "electromagnetically induced transparency" "optical non-coupling" "slow light field" "electro-optic transparency" + description: "cloak after not using your gun or field
while cloaked mobs can't see you
increase damage by 66%", + effect: () => { + mech.fieldFire = true; + mech.fieldMeterColor = "#fff"; + mech.fieldPhase = 0; + mech.isCloak = false + mech.fieldDamage = 1.66 + mech.fieldDrawRadius = 0 + const drawRadius = 1000 + + mech.hold = function() { + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throwBlock(); + } else if (input.field && mech.fieldCDcycle < mech.cycle) { //not hold and field button is pressed + mech.grabPowerUp(); + mech.lookForPickUp(); + } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding target exists, and field button is not pressed + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + + //120 cycles after shooting (or using field) enable cloak + if (mech.energy < 0.05 && mech.fireCDcycle < mech.cycle) mech.fireCDcycle = mech.cycle + if (mech.fireCDcycle + 50 < mech.cycle) { + if (!mech.isCloak) { + mech.isCloak = true //enter cloak + if (mod.isIntangible) { + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType && bullet[i].botType !== "orbit") bullet[i].collisionFilter.mask = cat.map | cat.bullet | cat.mobBullet | cat.mobShield + } + } + } + } else if (mech.isCloak) { //exit cloak + mech.isCloak = false + if (mod.isIntangible) { + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType && bullet[i].botType !== "orbit") bullet[i].collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield + } + } + if (mod.isCloakStun) { //stun nearby mobs after exiting cloak + let isMobsAround = false + const stunRange = mech.fieldDrawRadius * 1.15 + const drain = 0.3 * mech.energy + for (let i = 0, len = mob.length; i < len; ++i) { + if ( + Vector.magnitude(Vector.sub(mob[i].position, mech.pos)) < stunRange && + Matter.Query.ray(map, mob[i].position, mech.pos).length === 0 + ) { + isMobsAround = true + mobs.statusStun(mob[i], 30 + drain * 300) + } + } + if (isMobsAround && mech.energy > drain) { + mech.energy -= drain + game.drawList.push({ + x: mech.pos.x, + y: mech.pos.y, + radius: stunRange, + color: "hsla(0,50%,100%,0.6)", + time: 4 + }); + // ctx.beginPath(); + // ctx.arc(mech.pos.x, mech.pos.y, 800, 0, 2 * Math.PI); + // ctx.fillStyle = "#000" + // ctx.fill(); + } + } + } + + function drawField() { + mech.fieldPhase += 0.007 + 0.07 * (1 - energy) + const wiggle = 0.15 * Math.sin(mech.fieldPhase * 0.5) + ctx.beginPath(); + ctx.ellipse(mech.pos.x, mech.pos.y, mech.fieldDrawRadius * (1 - wiggle), mech.fieldDrawRadius * (1 + wiggle), mech.fieldPhase, 0, 2 * Math.PI); + if (mech.fireCDcycle > mech.cycle && (input.field)) { + ctx.lineWidth = 5; + ctx.strokeStyle = `rgba(0, 204, 255,1)` + ctx.stroke() + } + ctx.fillStyle = "#fff" //`rgba(0,0,0,${0.5+0.5*mech.energy})`; + ctx.globalCompositeOperation = "destination-in"; //in or atop + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + ctx.clip(); + } + + const energy = Math.max(0.01, Math.min(mech.energy, 1)) + if (mech.isCloak) { + this.fieldRange = this.fieldRange * 0.9 + 0.1 * drawRadius + mech.fieldDrawRadius = this.fieldRange * Math.min(1, 0.3 + 0.5 * Math.min(1, energy * energy)); + drawField() + } else { + if (this.fieldRange < 3000) { + this.fieldRange += 200 + mech.fieldDrawRadius = this.fieldRange * Math.min(1, 0.3 + 0.5 * Math.min(1, energy * energy)); + drawField() + } + } + if (mod.isIntangible) { + if (mech.isCloak) { + player.collisionFilter.mask = cat.map + let inPlayer = Matter.Query.region(mob, player.bounds) + if (inPlayer.length > 0) { + for (let i = 0; i < inPlayer.length; i++) { + if (mech.energy > 0) { + if (inPlayer[i].shield) { //shields drain player energy + mech.energy -= 0.014; + } else { + mech.energy -= 0.004; + } + } + } + } + } else { + player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield //normal collisions + } + } + + if (mech.energy < mech.maxEnergy) { // replaces mech.drawFieldMeter() with custom code + mech.energy += mech.fieldRegen; + const xOff = mech.pos.x - mech.radius * mech.maxEnergy + const yOff = mech.pos.y - 50 + ctx.fillStyle = "rgba(0, 0, 0, 0.3)"; + ctx.fillRect(xOff, yOff, 60 * mech.maxEnergy, 10); + ctx.fillStyle = mech.fieldMeterColor; + ctx.fillRect(xOff, yOff, 60 * mech.energy, 10); + ctx.beginPath() + ctx.rect(xOff, yOff, 60 * mech.maxEnergy, 10); + ctx.strokeStyle = "rgb(0, 0, 0)"; + ctx.lineWidth = 1; + ctx.stroke(); + } + } + } + }, + // { + // name: "phase decoherence field", + // description: "use energy to become intangible
firing and touching shields drains energy
unable to see and be seen by mobs", + // effect: () => { + // mech.fieldFire = true; + // mech.fieldMeterColor = "#fff"; + // mech.fieldPhase = 0; + + // mech.hold = function () { + // function drawField(radius) { + // radius *= Math.min(4, 0.9 + 2.2 * mech.energy * mech.energy); + // const rotate = mech.cycle * 0.005; + // mech.fieldPhase += 0.5 - 0.5 * Math.sqrt(Math.max(0.01, Math.min(mech.energy, 1))); + // const off1 = 1 + 0.06 * Math.sin(mech.fieldPhase); + // const off2 = 1 - 0.06 * Math.sin(mech.fieldPhase); + // ctx.beginPath(); + // ctx.ellipse(mech.pos.x, mech.pos.y, radius * off1, radius * off2, rotate, 0, 2 * Math.PI); + // if (mech.fireCDcycle > mech.cycle && (input.field)) { + // ctx.lineWidth = 5; + // ctx.strokeStyle = `rgba(0, 204, 255,1)` + // ctx.stroke() + // } + // ctx.fillStyle = "#fff" //`rgba(0,0,0,${0.5+0.5*mech.energy})`; + // ctx.globalCompositeOperation = "destination-in"; //in or atop + // ctx.fill(); + // ctx.globalCompositeOperation = "source-over"; + // ctx.clip(); + // } + + // mech.isCloak = false //isCloak disables most uses of foundPlayer() + // player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield //normal collisions + // if (mech.isHolding) { + // if (this.fieldRange < 2000) { + // this.fieldRange += 100 + // drawField(this.fieldRange) + // } + // mech.drawHold(mech.holdingTarget); + // mech.holding(); + // mech.throwBlock(); + // } else if (input.field) { + // mech.grabPowerUp(); + // mech.lookForPickUp(); + + // if (mech.fieldCDcycle < mech.cycle) { + // // game.draw.bodyFill = "transparent" + // // game.draw.bodyStroke = "transparent" + + // const DRAIN = 0.00013 + (mech.fireCDcycle > mech.cycle ? 0.005 : 0) + // if (mech.energy > DRAIN) { + // mech.energy -= DRAIN; + // // if (mech.energy < 0.001) { + // // mech.fieldCDcycle = mech.cycle + 120; + // // mech.energy = 0; + // // mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + // // } + // this.fieldRange = this.fieldRange * 0.8 + 0.2 * 160 + // drawField(this.fieldRange) + + // mech.isCloak = true //isCloak disables most uses of foundPlayer() + // player.collisionFilter.mask = cat.map + + + // let inPlayer = Matter.Query.region(mob, player.bounds) + // if (inPlayer.length > 0) { + // for (let i = 0; i < inPlayer.length; i++) { + // if (inPlayer[i].shield) { + // mech.energy -= 0.005; //shields drain player energy + // //draw outline of shield + // ctx.fillStyle = `rgba(140,217,255,0.5)` + // ctx.fill() + // } else if (mod.superposition && inPlayer[i].dropPowerUp) { + // // inPlayer[i].damage(0.4 * b.dmgScale); //damage mobs inside the player + // // mech.energy += 0.005; + + // mobs.statusStun(inPlayer[i], 300) + // //draw outline of mob in a few random locations to show blurriness + // const vertices = inPlayer[i].vertices; + // const off = 30 + // for (let k = 0; k < 3; k++) { + // const xOff = off * (Math.random() - 0.5) + // const yOff = off * (Math.random() - 0.5) + // ctx.beginPath(); + // ctx.moveTo(xOff + vertices[0].x, yOff + vertices[0].y); + // for (let j = 1, len = vertices.length; j < len; ++j) { + // ctx.lineTo(xOff + vertices[j].x, yOff + vertices[j].y); + // } + // ctx.lineTo(xOff + vertices[0].x, yOff + vertices[0].y); + // ctx.fillStyle = "rgba(0,0,0,0.1)" + // ctx.fill() + // } + // break; + // } + // } + // } + // } else { + // mech.fieldCDcycle = mech.cycle + 120; + // mech.energy = 0; + // mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + // drawField(this.fieldRange) + // } + // } + // } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released + // mech.pickUp(); + // if (this.fieldRange < 2000) { + // this.fieldRange += 100 + // drawField(this.fieldRange) + // } + // } else { + // // this.fieldRange = 3000 + // if (this.fieldRange < 2000 && mech.holdingTarget === null) { + // this.fieldRange += 100 + // drawField(this.fieldRange) + // } + // mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + // } + + // if (mech.energy < mech.maxEnergy) { + // mech.energy += mech.fieldRegen; + // const xOff = mech.pos.x - mech.radius * mech.maxEnergy + // const yOff = mech.pos.y - 50 + // ctx.fillStyle = "rgba(0, 0, 0, 0.3)"; + // ctx.fillRect(xOff, yOff, 60 * mech.maxEnergy, 10); + // ctx.fillStyle = mech.fieldMeterColor; + // ctx.fillRect(xOff, yOff, 60 * mech.energy, 10); + // ctx.beginPath() + // ctx.rect(xOff, yOff, 60 * mech.maxEnergy, 10); + // ctx.strokeStyle = "rgb(0, 0, 0)"; + // ctx.lineWidth = 1; + // ctx.stroke(); + // } + // if (mech.energy < 0) mech.energy = 0 + // } + // } + // }, + { + name: "pilot wave", + description: "use energy to push blocks with your mouse
field radius decreases out of line of sight", + effect: () => { + game.replaceTextLog = true; //allow text over write + mech.fieldPhase = 0; + mech.fieldPosition = { + x: game.mouseInGame.x, + y: game.mouseInGame.y + } + mech.lastFieldPosition = { + x: game.mouseInGame.x, + y: game.mouseInGame.y + } + mech.fieldOn = false; + mech.fieldRadius = 0; + mech.drop(); + mech.hold = function() { + if (input.field) { + if (mech.fieldCDcycle < mech.cycle) { + const scale = 25 + const bounds = { + min: { + x: mech.fieldPosition.x - scale, + y: mech.fieldPosition.y - scale + }, + max: { + x: mech.fieldPosition.x + scale, + y: mech.fieldPosition.y + scale + } + } + const isInMap = Matter.Query.region(map, bounds).length + // const isInMap = Matter.Query.point(map, mech.fieldPosition).length + + if (!mech.fieldOn) { // if field was off, and it starting up, teleport to new mouse location + mech.fieldOn = true; + mech.fieldPosition = { //smooth the mouse position + x: game.mouseInGame.x, + y: game.mouseInGame.y + } + mech.lastFieldPosition = { //used to find velocity of field changes + x: mech.fieldPosition.x, + y: mech.fieldPosition.y + } + } else { //when field is on it smoothly moves towards the mouse + mech.lastFieldPosition = { //used to find velocity of field changes + x: mech.fieldPosition.x, + y: mech.fieldPosition.y + } + const smooth = isInMap ? 0.985 : 0.96; + mech.fieldPosition = { //smooth the mouse position + x: mech.fieldPosition.x * smooth + game.mouseInGame.x * (1 - smooth), + y: mech.fieldPosition.y * smooth + game.mouseInGame.y * (1 - smooth), + } + } + + //grab power ups into the field + for (let i = 0, len = powerUp.length; i < len; ++i) { + const dxP = mech.fieldPosition.x - powerUp[i].position.x; + const dyP = mech.fieldPosition.y - powerUp[i].position.y; + const dist2 = dxP * dxP + dyP * dyP; + // float towards field if looking at and in range or if very close to player + if (dist2 < mech.fieldRadius * mech.fieldRadius && (mech.lookingAt(powerUp[i]) || dist2 < 16000) && !(mech.health === mech.maxHealth && powerUp[i].name === "heal")) { + powerUp[i].force.x += 7 * (dxP / dist2) * powerUp[i].mass; + powerUp[i].force.y += 7 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * game.g; //negate gravity + //extra friction + Matter.Body.setVelocity(powerUp[i], { + x: powerUp[i].velocity.x * 0.11, + y: powerUp[i].velocity.y * 0.11 + }); + if (dist2 < 5000 && !game.isChoosing) { //use power up if it is close enough + powerUps.onPickUp(powerUp[i].position); + powerUp[i].effect(); + Matter.World.remove(engine.world, powerUp[i]); + powerUp.splice(i, 1); + // mech.fieldRadius += 50 + break; //because the array order is messed up after splice + } + } + } + //grab power ups normally too + mech.grabPowerUp(); + + if (mech.energy > 0.01) { + //find mouse velocity + const diff = Vector.sub(mech.fieldPosition, mech.lastFieldPosition) + const speed = Vector.magnitude(diff) + const velocity = Vector.mult(Vector.normalise(diff), Math.min(speed, 45)) //limit velocity + let radius, radiusSmooth + if (Matter.Query.ray(map, mech.fieldPosition, player.position).length) { //is there something block the player's view of the field + radius = 0 + radiusSmooth = Math.max(0, isInMap ? 0.96 - 0.02 * speed : 0.995); //0.99 + } else { + radius = Math.max(50, 250 - 2 * speed) + radiusSmooth = 0.97 + } + mech.fieldRadius = mech.fieldRadius * radiusSmooth + radius * (1 - radiusSmooth) + + for (let i = 0, len = body.length; i < len; ++i) { + if (Vector.magnitude(Vector.sub(body[i].position, mech.fieldPosition)) < mech.fieldRadius && !body[i].isNotHoldable) { + const DRAIN = speed * body[i].mass * 0.000013 + if (mech.energy > DRAIN) { + mech.energy -= DRAIN; + Matter.Body.setVelocity(body[i], velocity); //give block mouse velocity + Matter.Body.setAngularVelocity(body[i], body[i].angularVelocity * 0.8) + // body[i].force.y -= body[i].mass * game.g; //remove gravity effects + //blocks drift towards center of pilot wave + const sub = Vector.sub(mech.fieldPosition, body[i].position) + const unit = Vector.mult(Vector.normalise(sub), 0.00005 * Vector.magnitude(sub)) + body[i].force.x += unit.x + body[i].force.y += unit.y - body[i].mass * game.g //remove gravity effects + } else { + mech.fieldCDcycle = mech.cycle + 120; + mech.fieldOn = false + mech.fieldRadius = 0 + break + } + } + } + + if (mod.isPilotFreeze) { + for (let i = 0, len = mob.length; i < len; ++i) { + if (Vector.magnitude(Vector.sub(mob[i].position, mech.fieldPosition)) < mech.fieldRadius) { + mobs.statusSlow(mob[i], 120) + } + } + } + + ctx.beginPath(); + const rotate = mech.cycle * 0.008; + mech.fieldPhase += 0.2 // - 0.5 * Math.sqrt(Math.min(mech.energy, 1)); + const off1 = 1 + 0.06 * Math.sin(mech.fieldPhase); + const off2 = 1 - 0.06 * Math.sin(mech.fieldPhase); + ctx.beginPath(); + ctx.ellipse(mech.fieldPosition.x, mech.fieldPosition.y, 1.2 * mech.fieldRadius * off1, 1.2 * mech.fieldRadius * off2, rotate, 0, 2 * Math.PI); + ctx.globalCompositeOperation = "exclusion"; //"exclusion" "difference"; + ctx.fillStyle = "#fff"; //"#eef"; + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + ctx.beginPath(); + ctx.ellipse(mech.fieldPosition.x, mech.fieldPosition.y, 1.2 * mech.fieldRadius * off1, 1.2 * mech.fieldRadius * off2, rotate, 0, mech.energy * 2 * Math.PI); + ctx.strokeStyle = "#000"; + ctx.lineWidth = 4; + ctx.stroke(); + } else { + mech.fieldCDcycle = mech.cycle + 120; + mech.fieldOn = false + mech.fieldRadius = 0 + } + } else { + mech.grabPowerUp(); + } + } else { + mech.fieldOn = false + mech.fieldRadius = 0 + } + mech.drawFieldMeter() + } + } + }, + { + name: "wormhole", + description: "use energy to tunnel through a wormhole
wormholes attract blocks and power ups", //
bullets may also traverse wormholes + effect: () => { + game.replaceTextLog = true; //allow text over write + mech.drop(); + // mech.hole = { //this is reset with each new field, but I'm leaving it here for reference + // isOn: false, + // isReady: true, + // pos1: {x: 0,y: 0}, + // pos2: {x: 0,y: 0}, + // angle: 0, + // unit:{x:0,y:0}, + // } + mech.hold = function() { + if (mech.hole.isOn) { + // draw holes + mech.fieldRange = 0.97 * mech.fieldRange + 0.03 * (50 + 10 * Math.sin(game.cycle * 0.025)) + const semiMajorAxis = mech.fieldRange + 30 + const edge1a = Vector.add(Vector.mult(mech.hole.unit, semiMajorAxis), mech.hole.pos1) + const edge1b = Vector.add(Vector.mult(mech.hole.unit, -semiMajorAxis), mech.hole.pos1) + const edge2a = Vector.add(Vector.mult(mech.hole.unit, semiMajorAxis), mech.hole.pos2) + const edge2b = Vector.add(Vector.mult(mech.hole.unit, -semiMajorAxis), mech.hole.pos2) + ctx.beginPath(); + ctx.moveTo(edge1a.x, edge1a.y) + ctx.bezierCurveTo(mech.hole.pos1.x, mech.hole.pos1.y, mech.hole.pos2.x, mech.hole.pos2.y, edge2a.x, edge2a.y); + ctx.lineTo(edge2b.x, edge2b.y) + ctx.bezierCurveTo(mech.hole.pos2.x, mech.hole.pos2.y, mech.hole.pos1.x, mech.hole.pos1.y, edge1b.x, edge1b.y); + ctx.fillStyle = `rgba(255,255,255,${200 / mech.fieldRange / mech.fieldRange})` //"rgba(0,0,0,0.1)" + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(mech.hole.pos1.x, mech.hole.pos1.y, mech.fieldRange, semiMajorAxis, mech.hole.angle, 0, 2 * Math.PI) + ctx.ellipse(mech.hole.pos2.x, mech.hole.pos2.y, mech.fieldRange, semiMajorAxis, mech.hole.angle, 0, 2 * Math.PI) + ctx.fillStyle = `rgba(255,255,255,${32 / mech.fieldRange})` + ctx.fill(); + + //suck power ups + for (let i = 0, len = powerUp.length; i < len; ++i) { + //which hole is closer + const dxP1 = mech.hole.pos1.x - powerUp[i].position.x; + const dyP1 = mech.hole.pos1.y - powerUp[i].position.y; + const dxP2 = mech.hole.pos2.x - powerUp[i].position.x; + const dyP2 = mech.hole.pos2.y - powerUp[i].position.y; + let dxP, dyP, dist2 + if (dxP1 * dxP1 + dyP1 * dyP1 < dxP2 * dxP2 + dyP2 * dyP2) { + dxP = dxP1 + dyP = dyP1 + } else { + dxP = dxP2 + dyP = dyP2 + } + dist2 = dxP * dxP + dyP * dyP; + if (dist2 < 600000 && !(mech.health === mech.maxHealth && powerUp[i].name === "heal")) { + powerUp[i].force.x += 4 * (dxP / dist2) * powerUp[i].mass; // float towards hole + powerUp[i].force.y += 4 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * game.g; //negate gravity + Matter.Body.setVelocity(powerUp[i], { //extra friction + x: powerUp[i].velocity.x * 0.05, + y: powerUp[i].velocity.y * 0.05 + }); + if (dist2 < 1000 && !game.isChoosing) { //use power up if it is close enough + mech.fieldRange *= 0.8 + powerUps.onPickUp(powerUp[i].position); + powerUp[i].effect(); + Matter.World.remove(engine.world, powerUp[i]); + powerUp.splice(i, 1); + break; //because the array order is messed up after splice + } + } + } + //suck and shrink blocks + const suckRange = 500 + const shrinkRange = 100 + const shrinkScale = 0.97; + const slowScale = 0.9 + for (let i = 0, len = body.length; i < len; i++) { + if (!body[i].isNotHoldable) { + const dist1 = Vector.magnitude(Vector.sub(mech.hole.pos1, body[i].position)) + const dist2 = Vector.magnitude(Vector.sub(mech.hole.pos2, body[i].position)) + if (dist1 < dist2) { + if (dist1 < suckRange) { + const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos1, body[i].position)), 1) + const slow = Vector.mult(body[i].velocity, slowScale) + Matter.Body.setVelocity(body[i], Vector.add(slow, pull)); + //shrink + if (Vector.magnitude(Vector.sub(mech.hole.pos1, body[i].position)) < shrinkRange) { + Matter.Body.scale(body[i], shrinkScale, shrinkScale); + if (body[i].mass < 0.05) { + Matter.World.remove(engine.world, body[i]); + body.splice(i, 1); + mech.fieldRange *= 0.8 + if (mod.isWormholeEnergy && mech.energy < mech.maxEnergy * 2) mech.energy = mech.maxEnergy * 2 + if (mod.isWormSpores) { //pandimensionalspermia + b.spore(Vector.add(mech.hole.pos2, Vector.rotate({ + x: mech.fieldRange, + y: 0 + }, 2 * Math.PI * Math.random()))) + Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(mech.hole.unit, -Math.PI / 2), 15)); + } + break + } + } + } + } else if (dist2 < suckRange) { + const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos2, body[i].position)), 1) + const slow = Vector.mult(body[i].velocity, slowScale) + Matter.Body.setVelocity(body[i], Vector.add(slow, pull)); + //shrink + if (Vector.magnitude(Vector.sub(mech.hole.pos2, body[i].position)) < shrinkRange) { + Matter.Body.scale(body[i], shrinkScale, shrinkScale); + if (body[i].mass < 0.05) { + Matter.World.remove(engine.world, body[i]); + body.splice(i, 1); + mech.fieldRange *= 0.8 + if (mod.isWormholeEnergy && mech.energy < mech.maxEnergy * 2) mech.energy = mech.maxEnergy * 2 + if (mod.isWormSpores) { //pandimensionalspermia + b.spore(Vector.add(mech.hole.pos1, Vector.rotate({ + x: mech.fieldRange, + y: 0 + }, 2 * Math.PI * Math.random()))) + Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(mech.hole.unit, Math.PI / 2), 15)); + + } + break + } + } + } + } + } + if (mod.isWormBullets) { + //teleport bullets + for (let i = 0, len = bullet.length; i < len; ++i) { //teleport bullets from hole1 to hole2 + if (!bullet[i].botType && !bullet[i].isInHole) { //don't teleport bots + if (Vector.magnitude(Vector.sub(mech.hole.pos1, bullet[i].position)) < mech.fieldRange) { //find if bullet is touching hole1 + Matter.Body.setPosition(bullet[i], Vector.add(mech.hole.pos2, Vector.sub(mech.hole.pos1, bullet[i].position))); + mech.fieldRange += 5 + bullet[i].isInHole = true + } else if (Vector.magnitude(Vector.sub(mech.hole.pos2, bullet[i].position)) < mech.fieldRange) { //find if bullet is touching hole1 + Matter.Body.setPosition(bullet[i], Vector.add(mech.hole.pos1, Vector.sub(mech.hole.pos2, bullet[i].position))); + mech.fieldRange += 5 + bullet[i].isInHole = true + } + } + } + // mobs get pushed away + for (let i = 0, len = mob.length; i < len; i++) { + if (Vector.magnitude(Vector.sub(mech.hole.pos1, mob[i].position)) < 200) { + const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos1, mob[i].position)), -0.07) + Matter.Body.setVelocity(mob[i], Vector.add(mob[i].velocity, pull)); + } + if (Vector.magnitude(Vector.sub(mech.hole.pos2, mob[i].position)) < 200) { + const pull = Vector.mult(Vector.normalise(Vector.sub(mech.hole.pos2, mob[i].position)), -0.07) + Matter.Body.setVelocity(mob[i], Vector.add(mob[i].velocity, pull)); + } + } + } + } + + if (input.field && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed + const justPastMouse = Vector.add(Vector.mult(Vector.normalise(Vector.sub(game.mouseInGame, mech.pos)), 50), game.mouseInGame) + const scale = 60 + // console.log(Matter.Query.region(map, bounds)) + if (mech.hole.isReady && + ( + Matter.Query.region(map, { + min: { + x: game.mouseInGame.x - scale, + y: game.mouseInGame.y - scale + }, + max: { + x: game.mouseInGame.x + scale, + y: game.mouseInGame.y + scale + } + }).length === 0 && + Matter.Query.ray(map, mech.pos, justPastMouse).length === 0 + // Matter.Query.ray(map, mech.pos, game.mouseInGame).length === 0 && + // Matter.Query.ray(map, player.position, game.mouseInGame).length === 0 && + // Matter.Query.ray(map, player.position, justPastMouse).length === 0 + ) + ) { + const sub = Vector.sub(game.mouseInGame, mech.pos) + const mag = Vector.magnitude(sub) + const drain = 0.04 + 0.007 * Math.sqrt(mag) + if (mech.energy > drain && mag > 300) { + mech.energy -= drain + mech.hole.isReady = false; + mech.fieldRange = 0 + Matter.Body.setPosition(player, game.mouseInGame); + const velocity = Vector.mult(Vector.normalise(sub), 18) + Matter.Body.setVelocity(player, { + x: velocity.x, + y: velocity.y - 4 //an extra vertical kick so the player hangs in place longer + }); + mech.immuneCycle = mech.cycle + 15; //player is immune to collision damage + // move bots to follow player + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType) { + Matter.Body.setPosition(bullet[i], Vector.add(player.position, { + x: 250 * (Math.random() - 0.5), + y: 250 * (Math.random() - 0.5) + })); + Matter.Body.setVelocity(bullet[i], { + x: 0, + y: 0 + }); + } + } + + //set holes + mech.hole.isOn = true; + mech.hole.pos1.x = mech.pos.x + mech.hole.pos1.y = mech.pos.y + mech.hole.pos2.x = player.position.x + mech.hole.pos2.y = player.position.y + mech.hole.angle = Math.atan2(sub.y, sub.x) + mech.hole.unit = Vector.perp(Vector.normalise(sub)) + + if (mod.isWormholeDamage) { + who = Matter.Query.ray(mob, mech.pos, game.mouseInGame, 80) + for (let i = 0; i < who.length; i++) { + if (who[i].body.alive) { + mobs.statusDoT(who[i].body, 0.6, 420) + mobs.statusStun(who[i].body, 240) + } + } + } + } else { + mech.grabPowerUp(); + } + } else { + mech.grabPowerUp(); + } + } else if (mech.holdingTarget && mech.fieldCDcycle < mech.cycle) { //holding, but field button is released + mech.pickUp(); + } else { + mech.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + mech.hole.isReady = true; + } + mech.drawFieldMeter() + } + } + }, + ], }; \ No newline at end of file diff --git a/js/spawn.js b/js/spawn.js index 7cf705a..4791d92 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -1,2809 +1,2933 @@ //main object for spawning things in a level const spawn = { - pickList: ["starter", "starter"], - fullPickList: [ - "hopper", "hopper", "hopper", "hopper", - "shooter", "shooter", "shooter", - "chaser", "chaser", - "striker", "striker", - "laser", "laser", - "exploder", "exploder", - "stabber", "stabber", - "launcher", "launcher", - "sniper", - "spinner", - "grower", - "springer", - "beamer", - "focuser", - "sucker", - "spawner", - "ghoster", - "sneaker", - ], - allowedBossList: ["chaser", "spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter", "launcher", "stabber", "sniper"], - setSpawnList() { //this is run at the start of each new level to determine the possible mobs for the level - //each level has 2 mobs: one new mob and one from the last level - spawn.pickList.splice(0, 1); - spawn.pickList.push(spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]); - }, - spawnChance(chance) { - return Math.random() < chance + 0.07 * game.difficulty && mob.length < -1 + 16 * Math.log10(game.difficulty + 1) - }, - randomMob(x, y, chance = 1) { - if (spawn.spawnChance(chance) || chance === Infinity) { - const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)]; - this[pick](x, y); - } - }, - randomSmallMob(x, y, - num = Math.max(Math.min(Math.round(Math.random() * game.difficulty * 0.2), 4), 0), - size = 16 + Math.ceil(Math.random() * 15), - chance = 1) { - if (spawn.spawnChance(chance)) { - for (let i = 0; i < num; ++i) { - const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)]; - this[pick](x + Math.round((Math.random() - 0.5) * 20) + i * size * 2.5, y + Math.round((Math.random() - 0.5) * 20), size); - } - } - }, - randomBoss(x, y, chance = 1) { - if (spawn.spawnChance(chance) && game.difficulty > 2 || chance == Infinity) { - //choose from the possible picklist - let pick = this.pickList[Math.floor(Math.random() * this.pickList.length)]; - //is the pick able to be a boss? - let canBeBoss = false; - for (let i = 0, len = this.allowedBossList.length; i < len; ++i) { - if (this.allowedBossList[i] === pick) { - canBeBoss = true; - break; + pickList: ["starter", "starter"], + fullPickList: [ + "hopper", "hopper", "hopper", "hopper", + "shooter", "shooter", "shooter", + "chaser", "chaser", + "striker", "striker", + "laser", "laser", + "exploder", "exploder", + "stabber", "stabber", + "launcher", "launcher", + "sniper", + "spinner", + "grower", + "springer", + "beamer", + "focuser", + "sucker", + "spawner", + "ghoster", + "sneaker", + ], + allowedBossList: ["chaser", "spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter", "launcher", "stabber", "sniper"], + setSpawnList() { //this is run at the start of each new level to determine the possible mobs for the level + //each level has 2 mobs: one new mob and one from the last level + spawn.pickList.splice(0, 1); + spawn.pickList.push(spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]); + }, + spawnChance(chance) { + return Math.random() < chance + 0.07 * game.difficulty && mob.length < -1 + 16 * Math.log10(game.difficulty + 1) + }, + randomMob(x, y, chance = 1) { + if (spawn.spawnChance(chance) || chance === Infinity) { + const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + this[pick](x, y); } - } - if (canBeBoss) { - if (Math.random() < 0.55) { - this.nodeBoss(x, y, pick); - } else { - this.lineBoss(x, y, pick); - } - } else { - if (Math.random() < 0.07) { - this[pick](x, y, 90 + Math.random() * 40); //one extra large mob - } else if (Math.random() < 0.35) { - this.groupBoss(x, y) //hidden grouping blocks - } else { - pick = (Math.random() < 0.5) ? "randomList" : "random"; - if (Math.random() < 0.55) { - this.nodeBoss(x, y, pick); - } else { - this.lineBoss(x, y, pick); - } - } - } - } - }, - randomLevelBoss(x, y, options = ["shooterBoss", "cellBossCulture", "bomberBoss", "spiderBoss", "launcherBoss", "laserTargetingBoss", "powerUpBoss", "snakeBoss"]) { - // other bosses: suckerBoss, laserBoss, tetherBoss, //all need a particular level to work so they are not included - spawn[options[Math.floor(Math.random() * options.length)]](x, y) - }, - //mob templates ********************************************************************************************* - //*********************************************************************************************************** - groupBoss(x, y, num = 3 + Math.random() * 8) { - for (let i = 0; i < num; i++) { - const radius = 25 + Math.floor(Math.random() * 20) - spawn.grouper(x + Math.random() * radius, y + Math.random() * radius, radius); - } - }, - grouper(x, y, radius = 25 + Math.floor(Math.random() * 20)) { - mobs.spawn(x, y, 4, radius, "#777"); - let me = mob[mob.length - 1]; - me.g = 0.00015; //required if using 'gravity' - me.accelMag = 0.0008 * game.accelScale; - me.groupingRangeMax = 250000 + Math.random() * 100000; - me.groupingRangeMin = (radius * 8) * (radius * 8); - me.groupingStrength = 0.0005 - me.memory = 200; - me.isGrouper = true; - - me.do = function () { - this.gravity(); - this.checkStatus(); - if (this.seePlayer.recall) { - this.seePlayerCheck(); - this.attraction(); - //tether to other blocks - ctx.beginPath(); - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].isGrouper && mob[i] != this && mob[i].dropPowerUp) { //don't tether to self, bullets, shields, ... - const distance2 = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)) - if (distance2 < this.groupingRangeMax) { - if (!mob[i].seePlayer.recall) mob[i].seePlayerCheck(); //wake up sleepy mobs - if (distance2 > this.groupingRangeMin) { - const angle = Math.atan2(mob[i].position.y - this.position.y, mob[i].position.x - this.position.x); - const forceMag = this.groupingStrength * mob[i].mass; - mob[i].force.x -= forceMag * Math.cos(angle); - mob[i].force.y -= forceMag * Math.sin(angle); - } - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(mob[i].position.x, mob[i].position.y); + }, + randomSmallMob(x, y, + num = Math.max(Math.min(Math.round(Math.random() * game.difficulty * 0.2), 4), 0), + size = 16 + Math.ceil(Math.random() * 15), + chance = 1) { + if (spawn.spawnChance(chance)) { + for (let i = 0; i < num; ++i) { + const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + this[pick](x + Math.round((Math.random() - 0.5) * 20) + i * size * 2.5, y + Math.round((Math.random() - 0.5) * 20), size); } - } } - ctx.strokeStyle = "#000"; - ctx.lineWidth = 1; - ctx.stroke(); - } - } - }, - starter(x, y, radius = Math.floor(20 + 20 * Math.random())) { - //easy mob for on level 1 - mobs.spawn(x, y, 8, radius, "#9ccdc6"); - let me = mob[mob.length - 1]; - // console.log(`mass=${me.mass}, radius = ${radius}`) - me.accelMag = 0.0005 * game.accelScale; - me.memory = 60; - me.seeAtDistance2 = 1400000 //1200 vision range - Matter.Body.setDensity(me, 0.0005) // normal density is 0.001 // this reduces life by half and decreases knockback - - me.do = function () { - this.seePlayerByLookingAt(); - this.attraction(); - this.checkStatus(); - }; - }, - cellBossCulture(x, y, radius = 20, num = 5) { - for (let i = 0; i < num; i++) { - spawn.cellBoss(x, y, radius) - } - }, - cellBoss(x, y, radius = 20) { - mobs.spawn(x + Math.random(), y + Math.random(), 20, radius * (1 + 1.2 * Math.random()), "rgba(0,150,155,0.7)"); - let me = mob[mob.length - 1]; - me.isBoss = true; - me.isCell = true; - me.accelMag = 0.00015 * game.accelScale; - me.memory = 40; - me.isVerticesChange = true - me.frictionAir = 0.012 - me.seePlayerFreq = Math.floor(11 + 7 * Math.random()) - me.seeAtDistance2 = 1400000; - me.cellMassMax = 70 - - me.collisionFilter.mask = cat.player | cat.bullet - Matter.Body.setDensity(me, 0.0005) // normal density is 0.001 // this reduces life by half and decreases knockback - // console.log(me.mass, me.radius) - const k = 642 //k=r^2/m - me.split = function () { - Matter.Body.scale(this, 0.4, 0.4); - this.radius = Math.sqrt(this.mass * k / Math.PI) - spawn.cellBoss(this.position.x, this.position.y, this.radius); - mob[mob.length - 1].health = this.health - } - me.onHit = function () { //run this function on hitting player - this.health = 1; - this.split(); - }; - me.onDamage = function (dmg) { - if (Math.random() < 0.33 * dmg * Math.sqrt(this.mass) && this.health > dmg) this.split(); - } - me.do = function () { - if (!mech.isBodiesAsleep) { - this.seePlayerByDistOrLOS(); - this.checkStatus(); - this.attraction(); - - if (this.seePlayer.recall && this.mass < this.cellMassMax) { //grow cell radius - const scale = 1 + 0.0002 * this.cellMassMax / this.mass; - Matter.Body.scale(this, scale, scale); - this.radius = Math.sqrt(this.mass * k / Math.PI) - } - if (!(game.cycle % this.seePlayerFreq)) { //move away from other mobs - const repelRange = 200 - const attractRange = 800 - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].isCell && mob[i].id !== this.id) { - const sub = Vector.sub(this.position, mob[i].position) - const dist = Vector.magnitude(sub) - if (dist < repelRange) { - this.force = Vector.mult(Vector.normalise(sub), this.mass * 0.006) - } else if (dist > attractRange) { - this.force = Vector.mult(Vector.normalise(sub), -this.mass * 0.004) - } - } - } - } - } - }; - me.onDeath = function () { - this.isCell = false; - let count = 0 //count other cells - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].isCell) count++ - } - if (count < 1) { //only drop a power up if this is the last cell - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - } else { - this.leaveBody = false; - this.dropPowerUp = false; - } - } - }, - powerUpBoss(x, y, vertices = 9, radius = 130) { - mobs.spawn(x, y, vertices, radius, "transparent"); - let me = mob[mob.length - 1]; - me.isBoss = true; - me.frictionAir = 0.01 - me.seeAtDistance2 = 1000000; - me.accelMag = 0.0005 * game.accelScale; - Matter.Body.setDensity(me, 0.0006); //normal is 0.001 - me.collisionFilter.mask = cat.bullet | cat.player - me.memory = Infinity; - me.seePlayerFreq = 30 - - me.lockedOn = null; - if (vertices === 9) { - //on primary spawn - powerUps.spawnBossPowerUp(me.position.x, me.position.y) - powerUps.spawn(me.position.x, me.position.y, "heal"); - powerUps.spawn(me.position.x, me.position.y, "ammo"); - } else if (!mech.isCloak) { - me.foundPlayer(); - } - me.onHit = function () { //run this function on hitting player - powerUps.ejectMod() - powerUps.spawn(mech.pos.x, mech.pos.y, "heal"); - powerUps.spawn(mech.pos.x, mech.pos.y, "heal"); - }; - me.onDeath = function () { - this.leaveBody = false; - this.dropPowerUp = false; - - if (vertices > 3) spawn.powerUpBoss(this.position.x, this.position.y, vertices - 1) - for (let i = 0; i < powerUp.length; i++) { - powerUp[i].collisionFilter.mask = cat.map | cat.powerUp - } - }; - me.do = function () { - this.stroke = `hsl(0,0%,${80+25*Math.sin(game.cycle*0.01)}%)` - - //steal all power ups - for (let i = 0; i < Math.min(powerUp.length, this.vertices.length); i++) { - powerUp[i].collisionFilter.mask = 0 - Matter.Body.setPosition(powerUp[i], this.vertices[i]) - Matter.Body.setVelocity(powerUp[i], { - x: 0, - y: 0 - }) - } - - this.seePlayerCheckByDistance(); - this.attraction(); - this.checkStatus(); - }; - }, - // powerUpBoss(x, y, vertices = 9, radius = 130) { - // mobs.spawn(x, y, vertices, radius, "transparent"); - // let me = mob[mob.length - 1]; - // me.isBoss = true; - // me.frictionAir = 0.025 - // me.seeAtDistance2 = 9000000; - // me.accelMag = 0.0005 * game.accelScale; - // Matter.Body.setDensity(me, 0.002); //normal is 0.001 - // me.collisionFilter.mask = cat.bullet | cat.player - // me.memory = Infinity; - // me.seePlayerFreq = 85 + Math.floor(10 * Math.random()) - - // me.lockedOn = null; - // if (vertices === 9) { - // //on primary spawn - // powerUps.spawnBossPowerUp(me.position.x, me.position.y) - // powerUps.spawn(me.position.x, me.position.y, "heal"); - // powerUps.spawn(me.position.x, me.position.y, "ammo"); - // } else if (!mech.isCloak) { - // me.foundPlayer(); - // } - - // me.onDeath = function () { - // this.leaveBody = false; - // this.dropPowerUp = false; - - // if (vertices > 3) spawn.powerUpBoss(this.position.x, this.position.y, vertices - 1) - // for (let i = 0; i < powerUp.length; i++) { - // powerUp[i].collisionFilter.mask = cat.map | cat.powerUp - // } - // }; - // me.do = function () { - // this.stroke = `hsl(0,0%,${80+25*Math.sin(game.cycle*0.01)}%)` - - // //steal all power ups - // for (let i = 0; i < Math.min(powerUp.length, this.vertices.length); i++) { - // powerUp[i].collisionFilter.mask = 0 - // Matter.Body.setPosition(powerUp[i], this.vertices[i]) - // Matter.Body.setVelocity(powerUp[i], { - // x: 0, - // y: 0 - // }) - // } - - // this.seePlayerCheckByDistance(); - // this.attraction(); - // this.checkStatus(); - // }; - // }, - // healer(x, y, radius = 20) { - // mobs.spawn(x, y, 3, radius, "rgba(50,255,200,0.4)"); - // let me = mob[mob.length - 1]; - // me.frictionAir = 0.02; - // me.accelMag = 0.0004 * game.accelScale; - // if (map.length) me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search - // me.lookFrequency = 160 + Math.floor(57 * Math.random()) - // me.lockedOn = null; - // Matter.Body.setDensity(me, 0.003) // normal density is 0.001 - - // me.do = function () { - - // if (!(game.cycle % this.lookFrequency)) { - // //slow self heal - // this.health += 0.02; - // if (this.health > 1) this.health = 1; - - // //target mobs with low health - // let closeDist = Infinity; - // for (let i = 0; i < mob.length; i++) { - // if (mob[i] != this && Matter.Query.ray(map, this.position, mob[i].position).length === 0) { - // const TARGET_VECTOR = Vector.sub(this.position, mob[i].position) - // const DIST = Vector.magnitude(TARGET_VECTOR) * mob[i].health * mob[i].health * mob[i].health; //distance is multiplied by mob health to prioritize low health mobs - // if (DIST < closeDist) { - // closeDist = DIST; - // this.lockedOn = mob[i] - // } - // } - // } - // } - - // //move away from player if too close - // if (this.distanceToPlayer2() < 400000) { - // const TARGET_VECTOR = Vector.sub(this.position, player.position) - // this.force = Vector.mult(Vector.normalise(TARGET_VECTOR), this.mass * this.accelMag * 1.4) - // if (this.lockedOn) this.lockedOn = null - // } else if (this.lockedOn && this.lockedOn.alive) { - // //move towards and heal locked on target - // const TARGET_VECTOR = Vector.sub(this.position, this.lockedOn.position) - // const DIST = Vector.magnitude(TARGET_VECTOR); - // if (DIST > 250) { - // this.force = Vector.mult(Vector.normalise(TARGET_VECTOR), -this.mass * this.accelMag) - // } else { - // if (this.lockedOn.health < 1) { - // this.lockedOn.health += 0.002; - // if (this.lockedOn.health > 1) this.lockedOn.health = 1; - // //spin when healing - // this.torque = 0.000005 * this.inertia; - // //draw heal - // ctx.beginPath(); - // ctx.moveTo(this.position.x, this.position.y); - // ctx.lineTo(this.lockedOn.position.x, this.lockedOn.position.y); - // ctx.lineWidth = 10 - // ctx.strokeStyle = "rgba(50,255,200,0.4)" - // ctx.stroke(); - // } - // } - // } else { - // //wander if no heal targets visible - // //be sure to declare searchTarget in mob spawn - // const newTarget = function (that) { - // that.searchTarget = mob[Math.floor(Math.random() * (mob.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.6 of normal acceleration - // this.force = Vector.mult(Vector.normalise(sub), this.accelMag * this.mass * 0.6); - // } else { - // //after reaching random target switch to new target - // newTarget(this); - // } - // //switch to a new target after a while - // if (!(game.cycle % (this.lookFrequency * 15))) { - // newTarget(this); - // } - - // } - // }; - // }, - chaser(x, y, radius = 35 + Math.ceil(Math.random() * 40)) { - mobs.spawn(x, y, 8, radius, "rgb(255,150,100)"); //"#2c9790" - let me = mob[mob.length - 1]; - // Matter.Body.setDensity(me, 0.0007); //extra dense //normal is 0.001 //makes effective life much lower - me.friction = 0; - me.frictionAir = 0; - me.accelMag = 0.001 * Math.sqrt(game.accelScale); - me.g = me.accelMag * 0.6; //required if using 'gravity' - me.memory = 50; - spawn.shield(me, x, y); - me.do = function () { - this.gravity(); - this.seePlayerCheck(); - this.checkStatus(); - this.attraction(); - }; - }, - grower(x, y, radius = 15) { - mobs.spawn(x, y, 7, radius, "hsl(144, 15%, 50%)"); - let me = mob[mob.length - 1]; - me.isVerticesChange = true - me.big = false; //required for grow - me.accelMag = 0.00045 * game.accelScale; - me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.player //can't touch other mobs - // me.onDeath = function () { //helps collisions functions work better after vertex have been changed - // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) - // } - me.do = function () { - this.seePlayerByLookingAt(); - this.checkStatus(); - this.attraction(); - this.grow(); - }; - }, - springer(x, y, radius = 20 + Math.ceil(Math.random() * 35)) { - mobs.spawn(x, y, 10, radius, "#b386e8"); - let me = mob[mob.length - 1]; - me.friction = 0; - me.frictionAir = 0.006; - me.lookTorque = 0.0000008; //controls spin while looking for player - me.g = 0.0002; //required if using 'gravity' - me.seePlayerFreq = Math.round((40 + 25 * Math.random()) * game.lookFreqScale); - const springStiffness = 0.00014; - const springDampening = 0.0005; - - me.springTarget = { - x: me.position.x, - y: me.position.y - }; - const len = cons.length; - cons[len] = Constraint.create({ - pointA: me.springTarget, - bodyB: me, - stiffness: springStiffness, - damping: springDampening - }); - World.add(engine.world, cons[cons.length - 1]); - - cons[len].length = 100 + 1.5 * radius; - me.cons = cons[len]; - - me.springTarget2 = { - x: me.position.x, - y: me.position.y - }; - const len2 = cons.length; - cons[len2] = Constraint.create({ - pointA: me.springTarget2, - bodyB: me, - stiffness: springStiffness, - damping: springDampening - }); - World.add(engine.world, cons[cons.length - 1]); - cons[len2].length = 100 + 1.5 * radius; - me.cons2 = cons[len2]; - - - me.onDeath = function () { - this.removeCons(); - }; - spawn.shield(me, x, y); - me.do = function () { - this.gravity(); - this.searchSpring(); - this.checkStatus(); - this.springAttack(); - }; - }, - hopper(x, y, radius = 30 + Math.ceil(Math.random() * 30)) { - mobs.spawn(x, y, 5, radius, "rgb(0,200,180)"); - let me = mob[mob.length - 1]; - me.accelMag = 0.04; - me.g = 0.0017; //required if using 'gravity' - me.frictionAir = 0.01; - me.restitution = 0; - me.delay = 120 * game.CDScale; - me.randomHopFrequency = 200 + Math.floor(Math.random() * 150); - me.randomHopCD = game.cycle + me.randomHopFrequency; - spawn.shield(me, x, y); - me.do = function () { - this.gravity(); - this.seePlayerCheck(); - this.checkStatus(); - if (this.seePlayer.recall) { - if (this.cd < game.cycle && (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length)) { - this.cd = game.cycle + this.delay; - const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass; - const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); - this.force.x += forceMag * Math.cos(angle); - this.force.y += forceMag * Math.sin(angle) - (Math.random() * 0.07 + 0.02) * this.mass; //antigravity - } - } else { - //randomly hob if not aware of player - if (this.randomHopCD < game.cycle && (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length)) { - this.randomHopCD = game.cycle + this.randomHopFrequency; - //slowly change randomHopFrequency after each hop - this.randomHopFrequency = Math.max(100, this.randomHopFrequency + (0.5 - Math.random()) * 200); - const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass * (0.1 + Math.random() * 0.3); - const angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI; - this.force.x += forceMag * Math.cos(angle); - this.force.y += forceMag * Math.sin(angle) - 0.05 * this.mass; //antigravity - } - } - }; - }, - spinner(x, y, radius = 30 + Math.ceil(Math.random() * 35)) { - mobs.spawn(x, y, 5, radius, "#000000"); - let me = mob[mob.length - 1]; - me.fill = "#28b"; - me.rememberFill = me.fill; - me.cd = 0; - me.burstDir = { - x: 0, - y: 0 - }; - me.frictionAir = 0.022; - me.lookTorque = 0.0000014; - me.restitution = 0; - spawn.shield(me, x, y); - me.look = function () { - this.seePlayerByLookingAt(); - this.checkStatus(); - if (this.seePlayer.recall && this.cd < game.cycle) { - this.burstDir = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); - this.cd = game.cycle + 40; - this.do = this.spin - } - } - me.do = me.look - me.spin = function () { - this.checkStatus(); - this.torque += 0.000035 * this.inertia; - this.fill = randomColor({ - hue: "blue" - }); - //draw attack vector - const mag = this.radius * 2.5 + 50; - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - ctx.lineWidth = 3; - ctx.setLineDash([10, 20]); //30 - const dir = Vector.add(this.position, Vector.mult(this.burstDir, mag)); - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(dir.x, dir.y); - ctx.stroke(); - ctx.setLineDash([]); - if (this.cd < game.cycle) { - this.fill = this.rememberFill; - this.cd = game.cycle + 180 * game.CDScale - this.do = this.look - this.force = Vector.mult(this.burstDir, this.mass * 0.25); - } - } - }, - sucker(x, y, radius = 30 + Math.ceil(Math.random() * 70)) { - radius = 9 + radius / 8; //extra small - mobs.spawn(x, y, 6, radius, "#000"); - let me = mob[mob.length - 1]; - me.stroke = "transparent"; //used for drawSneaker - me.eventHorizon = radius * 23; //required for blackhole - me.seeAtDistance2 = (me.eventHorizon + 500) * (me.eventHorizon + 500); //vision limit is event horizon - me.accelMag = 0.00009 * game.accelScale; - // me.frictionAir = 0.005; - me.memory = 600; - Matter.Body.setDensity(me, 0.004); //extra dense //normal is 0.001 //makes effective life much larger - me.do = function () { - //keep it slow, to stop issues from explosion knock backs - if (this.speed > 5) { - Matter.Body.setVelocity(this, { - x: this.velocity.x * 0.99, - y: this.velocity.y * 0.99 - }); - } - this.seePlayerByDistOrLOS(); - this.checkStatus(); - if (this.seePlayer.recall) { - //eventHorizon waves in and out - eventHorizon = this.eventHorizon * (0.93 + 0.17 * Math.sin(game.cycle * 0.011)) - - //accelerate towards the player - const forceMag = this.accelMag * this.mass; - const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); - this.force.x += forceMag * Math.cos(angle); - this.force.y += forceMag * Math.sin(angle); - - //draw darkness - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, eventHorizon * 0.25, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.9)"; - ctx.fill(); - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, eventHorizon * 0.55, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.5)"; - ctx.fill(); - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.1)"; - ctx.fill(); - - //when player is inside event horizon - if (Vector.magnitude(Vector.sub(this.position, player.position)) < eventHorizon) { - if (mech.energy > 0) mech.energy -= 0.004 - if (mech.energy < 0.1) { - mech.damage(0.00015 * game.dmgScale); - } - const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); - player.force.x -= 0.00125 * player.mass * Math.cos(angle) * (mech.onGround ? 1.8 : 1); - player.force.y -= 0.0001 * player.mass * Math.sin(angle); - //draw line to player - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(mech.pos.x, mech.pos.y); - ctx.lineWidth = Math.min(60, this.radius * 2); - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(); - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.3)"; - ctx.fill(); - } - } - } - }, - suckerBoss(x, y, radius = 25) { - mobs.spawn(x, y, 12, radius, "#000"); - let me = mob[mob.length - 1]; - me.isBoss = true; - me.stroke = "transparent"; //used for drawSneaker - me.eventHorizon = 1100; //required for black hole - me.seeAtDistance2 = (me.eventHorizon + 1000) * (me.eventHorizon + 1000); //vision limit is event horizon - me.accelMag = 0.00003 * game.accelScale; - me.collisionFilter.mask = cat.player | cat.bullet - // me.frictionAir = 0.005; - me.memory = 1600; - Matter.Body.setDensity(me, 0.05); //extra dense //normal is 0.001 //makes effective life much larger - me.onDeath = function () { - //applying forces to player doesn't seem to work inside this method, not sure why - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - if (game.difficulty > 5) { - //teleport everything to center - function toMe(who, where, range) { - for (let i = 0, len = who.length; i < len; i++) { - const SUB = Vector.sub(who[i].position, where) - const DISTANCE = Vector.magnitude(SUB) - if (DISTANCE < range) { - Matter.Body.setPosition(who[i], where) - } - } - } - toMe(body, this.position, this.eventHorizon) - toMe(mob, this.position, this.eventHorizon) - // toMe(bullet, this.position, this.eventHorizon) - } - }; - me.do = function () { - //keep it slow, to stop issues from explosion knock backs - if (this.speed > 1) { - Matter.Body.setVelocity(this, { - x: this.velocity.x * 0.95, - y: this.velocity.y * 0.95 - }); - } - this.seePlayerByDistOrLOS(); - this.checkStatus(); - if (this.seePlayer.recall) { - //accelerate towards the player - const forceMag = this.accelMag * this.mass; - const dx = this.seePlayer.position.x - this.position.x - const dy = this.seePlayer.position.y - this.position.y - const mag = Math.sqrt(dx * dx + dy * dy) - this.force.x += forceMag * dx / mag; - this.force.y += forceMag * dy / mag; - - //eventHorizon waves in and out - eventHorizon = this.eventHorizon * (1 + 0.2 * Math.sin(game.cycle * 0.008)) - // zoom camera in and out with the event horizon - - //draw darkness - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, eventHorizon * 0.2, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,20,40,0.6)"; - ctx.fill(); - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, eventHorizon * 0.4, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,20,40,0.4)"; - ctx.fill(); - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, eventHorizon * 0.6, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,20,40,0.3)"; - ctx.fill(); - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, eventHorizon * 0.8, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,20,40,0.2)"; - ctx.fill(); - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.05)"; - ctx.fill(); - //when player is inside event horizon - if (Vector.magnitude(Vector.sub(this.position, player.position)) < eventHorizon) { - if (mech.energy > 0) mech.energy -= 0.006 - if (mech.energy < 0.1) { - mech.damage(0.0002 * game.dmgScale); - } - const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); - player.force.x -= 0.0013 * Math.cos(angle) * player.mass * (mech.onGround ? 1.7 : 1); - player.force.y -= 0.0013 * Math.sin(angle) * player.mass; - //draw line to player - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(mech.pos.x, mech.pos.y); - ctx.lineWidth = Math.min(60, this.radius * 2); - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(); - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.3)"; - ctx.fill(); - } - this.curl(eventHorizon); - } - } - }, - spiderBoss(x, y, radius = 60 + Math.ceil(Math.random() * 10)) { - const isDaddyLongLegs = Math.random() < 0.25 - let targets = [] //track who is in the node boss, for shields - mobs.spawn(x, y, 6, radius, "#b386e8"); - let me = mob[mob.length - 1]; - me.isBoss = true; - targets.push(me.id) //add to shield protection - me.friction = 0; - me.frictionAir = 0.0065; - me.lookTorque = 0.0000008; //controls spin while looking for player - me.g = 0.00025; //required if using 'gravity' - me.seePlayerFreq = Math.round((30 + 20 * Math.random()) * game.lookFreqScale); - const springStiffness = isDaddyLongLegs ? 0.0001 : 0.000065; - const springDampening = isDaddyLongLegs ? 0 : 0.0006; - - me.springTarget = { - x: me.position.x, - y: me.position.y - }; - const len = cons.length; - cons[len] = Constraint.create({ - pointA: me.springTarget, - bodyB: me, - stiffness: springStiffness, - damping: springDampening - }); - cons[len].length = 100 + 1.5 * radius; - - me.cons = cons[len]; - - me.springTarget2 = { - x: me.position.x, - y: me.position.y - }; - const len2 = cons.length; - cons[len2] = Constraint.create({ - pointA: me.springTarget2, - bodyB: me, - stiffness: springStiffness, - damping: springDampening - }); - cons[len2].length = 100 + 1.5 * radius; - me.cons2 = cons[len2]; - if (isDaddyLongLegs) Matter.Body.setDensity(me, 0.017); //extra dense //normal is 0.001 //makes effective life much larger - - me.onDeath = function () { - this.removeCons(); - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - }; - me.do = function () { - this.gravity(); - this.searchSpring(); - this.checkStatus(); - this.springAttack(); - }; - - radius = 22 // radius of each node mob - const sideLength = 100 // distance between each node mob - const nodes = 6 - const angle = 2 * Math.PI / nodes - - spawn.allowShields = false; //don't want shields on individual boss mobs - - for (let i = 0; i < nodes; ++i) { - spawn.stabber(x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius, 12); - if (isDaddyLongLegs) Matter.Body.setDensity(mob[mob.length - 1], 0.01); //extra dense //normal is 0.001 //makes effective life much larger - targets.push(mob[mob.length - 1].id) //track who is in the node boss, for shields - } - //spawn shield around all nodes - if (!isDaddyLongLegs) spawn.bossShield(targets, x, y, sideLength + 1 * radius + nodes * 5 - 25); - spawn.allowShields = true; - - const attachmentStiffness = isDaddyLongLegs ? 0.0003 : 0.05 - if (!isDaddyLongLegs) spawn.constrain2AdjacentMobs(nodes + 2, attachmentStiffness, true); //loop mobs together - for (let i = 0; i < nodes; ++i) { //attach to center mob - consBB[consBB.length] = Constraint.create({ - bodyA: me, - bodyB: mob[mob.length - i - 1], - stiffness: attachmentStiffness, - damping: 0.01 - }); - World.add(engine.world, consBB[consBB.length - 1]); - } - }, - timeSkipBoss(x, y, radius = 55) { - mobs.spawn(x, y, 6, radius, '#000'); - let me = mob[mob.length - 1]; - me.isBoss = true; - // me.stroke = "transparent"; //used for drawSneaker - me.timeSkipLastCycle = 0 - me.eventHorizon = 1800; //required for black hole - me.seeAtDistance2 = (me.eventHorizon + 2000) * (me.eventHorizon + 2000); //vision limit is event horizon + 2000 - me.accelMag = 0.0004 * game.accelScale; - // me.frictionAir = 0.005; - // me.memory = 1600; - // Matter.Body.setDensity(me, 0.02); //extra dense //normal is 0.001 //makes effective life much larger - Matter.Body.setDensity(me, 0.0015 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger - spawn.shield(me, x, y, 1); - - - me.onDeath = function () { - //applying forces to player doesn't seem to work inside this method, not sure why - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - }; - me.do = function () { - //keep it slow, to stop issues from explosion knock backs - if (this.speed > 8) { - Matter.Body.setVelocity(this, { - x: this.velocity.x * 0.99, - y: this.velocity.y * 0.99 - }); - } - this.seePlayerCheck(); - this.checkStatus(); - this.attraction() - if (!game.isTimeSkipping) { - const compress = 1 - if (this.timeSkipLastCycle < game.cycle - compress && - Vector.magnitude(Vector.sub(this.position, player.position)) < this.eventHorizon) { - this.timeSkipLastCycle = game.cycle - game.timeSkip(compress) - - this.fill = `rgba(0,0,0,${0.4+0.6*Math.random()})` - this.stroke = "#014" - this.isShielded = false; - this.dropPowerUp = true; - this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob; //can't touch bullets - - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.eventHorizon, 0, 2 * Math.PI); - ctx.fillStyle = "#fff"; - ctx.globalCompositeOperation = "destination-in"; //in or atop - ctx.fill(); - ctx.globalCompositeOperation = "source-over"; - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.eventHorizon, 0, 2 * Math.PI); - ctx.clip(); - - // ctx.beginPath(); - // ctx.arc(this.position.x, this.position.y, 9999, 0, 2 * Math.PI); - // ctx.fillStyle = "#000"; - // ctx.fill(); - // ctx.strokeStyle = "#000"; - // ctx.stroke(); - - // ctx.beginPath(); - // ctx.arc(this.position.x, this.position.y, this.eventHorizon, 0, 2 * Math.PI); - // ctx.fillStyle = `rgba(0,0,0,${0.05*Math.random()})`; - // ctx.fill(); - // ctx.strokeStyle = "#000"; - // ctx.stroke(); - } else { - this.isShielded = true; - this.dropPowerUp = false; - this.seePlayer.recall = false - this.fill = "transparent" - this.stroke = "transparent" - this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.mob; //can't touch bullets - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.eventHorizon, 0, 2 * Math.PI); - ctx.fillStyle = `rgba(0,0,0,${0.05*Math.random()})`; - ctx.fill(); - } - } - } - }, - beamer(x, y, radius = 15 + Math.ceil(Math.random() * 15)) { - mobs.spawn(x, y, 4, radius, "rgb(255,0,190)"); - let me = mob[mob.length - 1]; - me.repulsionRange = 73000; //squared - me.laserRange = 370; - me.accelMag = 0.0005 * game.accelScale; - me.frictionStatic = 0; - me.friction = 0; - spawn.shield(me, x, y); - me.do = function () { - this.seePlayerByLookingAt(); - this.checkStatus(); - this.attraction(); - this.repulsion(); - //laser beam - this.laserBeam(); - }; - }, - focuser(x, y, radius = 30 + Math.ceil(Math.random() * 10)) { - radius = Math.ceil(radius * 0.7); - mobs.spawn(x, y, 4, radius, "rgb(0,0,255)"); - let me = mob[mob.length - 1]; - Matter.Body.setDensity(me, 0.003); //extra dense //normal is 0.001 - me.restitution = 0; - me.laserPos = me.position; //required for laserTracking - me.repulsionRange = 1200000; //squared - me.accelMag = 0.00009 * game.accelScale; - me.frictionStatic = 0; - me.friction = 0; - me.onDamage = function () { - this.laserPos = this.position; - }; - spawn.shield(me, x, y); - me.do = function () { - if (!mech.isBodiesAsleep) { - this.seePlayerByLookingAt(); - this.checkStatus(); - this.attraction(); - const dist2 = this.distanceToPlayer2(); - //laser Tracking - if (this.seePlayer.yes && dist2 < 4000000) { - const rangeWidth = 2000; //this is sqrt of 4000000 from above if() - //targeting laser will slowly move from the mob to the player's position - this.laserPos = Vector.add(this.laserPos, Vector.mult(Vector.sub(player.position, this.laserPos), 0.1)); - let targetDist = Vector.magnitude(Vector.sub(this.laserPos, mech.pos)); - const r = 12; - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - if (targetDist < r + 16) { - targetDist = r + 10; - //charge at player - const forceMag = this.accelMag * 30 * this.mass; - const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); - this.force.x += forceMag * Math.cos(angle); - this.force.y += forceMag * Math.sin(angle); - } else { - //high friction if can't lock onto player - // Matter.Body.setVelocity(this, { - // x: this.velocity.x * 0.98, - // y: this.velocity.y * 0.98 - // }); - } - if (dist2 > 80000) { - const laserWidth = 0.002; - let laserOffR = Vector.rotateAbout(this.laserPos, (targetDist - r) * laserWidth, this.position); - let sub = Vector.normalise(Vector.sub(laserOffR, this.position)); - laserOffR = Vector.add(laserOffR, Vector.mult(sub, rangeWidth)); - ctx.lineTo(laserOffR.x, laserOffR.y); - - let laserOffL = Vector.rotateAbout(this.laserPos, (targetDist - r) * -laserWidth, this.position); - sub = Vector.normalise(Vector.sub(laserOffL, this.position)); - laserOffL = Vector.add(laserOffL, Vector.mult(sub, rangeWidth)); - ctx.lineTo(laserOffL.x, laserOffL.y); - ctx.fillStyle = `rgba(0,0,255,${Math.max(0,0.3*r/targetDist)})` - ctx.fill(); - } - } else { - this.laserPos = this.position; - } - }; - } - }, - laserTargetingBoss(x, y, radius = 80) { - const color = "#05f" - mobs.spawn(x, y, 3, radius, color); - let me = mob[mob.length - 1]; - me.isBoss = true; - me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front - Matter.Body.rotate(me, Math.random() * Math.PI * 2); - me.accelMag = 0.0002 * Math.sqrt(game.accelScale); - me.seePlayerFreq = Math.floor(30 * game.lookFreqScale); - me.memory = 420; - me.restitution = 1; - me.frictionAir = 0.01; - me.frictionStatic = 0; - me.friction = 0; - - me.lookTorque = 0.000001 * (Math.random() > 0.5 ? -1 : 1); - - me.fireDir = { - x: 0, - y: 0 - } - Matter.Body.setDensity(me, 0.023); //extra dense //normal is 0.001 //makes effective life much larger - spawn.shield(me, x, y, 1); - me.onHit = function () { - //run this function on hitting player - // this.explode(); - }; - // spawn.shield(me, x, y, 1); //not working, not sure why - me.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - }; - me.do = function () { - this.seePlayerByLookingAt(); - this.checkStatus(); - this.attraction(); - - 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) / 1600; //gives the bullet an arc - } - - //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.4; - if (c > threshold) { - this.torque += 0.000004 * this.inertia; - } else if (c < -threshold) { - this.torque -= 0.000004 * this.inertia; - } - // if (Math.abs(c) < 0.3) { - // const mag = 0.05 - // this.force.x += mag * Math.cos(this.angle) - // this.force.y += mag * Math.sin(this.angle) - // } - - const vertexCollision = function (v1, v1End, domain) { - for (let i = 0; i < domain.length; ++i) { - let vertices = domain[i].vertices; - const len = vertices.length - 1; - for (let j = 0; j < len; j++) { - results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[j], - v2: vertices[j + 1] - }; + }, + randomBoss(x, y, chance = 1) { + if (spawn.spawnChance(chance) && game.difficulty > 2 || chance == Infinity) { + //choose from the possible picklist + let pick = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + //is the pick able to be a boss? + let canBeBoss = false; + for (let i = 0, len = this.allowedBossList.length; i < len; ++i) { + if (this.allowedBossList[i] === pick) { + canBeBoss = true; + break; } - } } - results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[0], - v2: vertices[len] - }; - } - } - } - }; - - const seeRange = 8000; - 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.001 * game.dmgScale; - mech.damage(dmg); - //draw damage - ctx.fillStyle = color; - 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.vertices[1].x, this.vertices[1].y); - ctx.lineTo(best.x, best.y); - ctx.strokeStyle = color; - ctx.lineWidth = 3; - ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]); - ctx.stroke(); - ctx.setLineDash([0, 0]); - } - }; - }, - laser(x, y, radius = 30) { - mobs.spawn(x, y, 3, radius, "#f00"); - let me = mob[mob.length - 1]; - me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front - Matter.Body.rotate(me, Math.random() * Math.PI * 2); - me.accelMag = 0.00007 * game.accelScale; - me.onHit = function () { - //run this function on hitting player - this.explode(); - }; - me.do = function () { - this.seePlayerByLookingAt(); - this.checkStatus(); - this.attraction(); - this.laser(); - }; - }, - laserBoss(x, y, radius = 30) { - mobs.spawn(x, y, 3, radius, "#f00"); - let me = mob[mob.length - 1]; - me.isBoss = true; - me.startingPosition = { - x: x, - y: y - } - me.count = 0; - me.frictionAir = 0.03; - // me.torque -= me.inertia * 0.002 - Matter.Body.setDensity(me, 0.03); //extra dense //normal is 0.001 //makes effective life much larger - // spawn.shield(me, x, y, 1); //not working, not sure why - me.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - }; - me.rotateVelocity = Math.min(0.0045, 0.0015 * game.accelScale * game.accelScale) * (level.levelsCleared > 8 ? 1 : -1) - me.do = function () { - this.fill = '#' + Math.random().toString(16).substr(-6); //flash colors - if (!mech.isBodiesAsleep) { - //check if slowed - let slowed = false - for (let i = 0; i < this.status.length; i++) { - if (this.status[i].type === "slow") { - slowed = true - break - } - } - if (!slowed) { - this.count++ - Matter.Body.setAngle(me, this.count * this.rotateVelocity) - } - } - // this.torque -= this.inertia * 0.0000025 / (4 + this.health); - Matter.Body.setVelocity(this, { - x: 0, - y: 0 - }); - Matter.Body.setPosition(this, this.startingPosition); - - if (!this.isStunned) { - ctx.beginPath(); - this.laser(this.vertices[0], this.angle + Math.PI / 3); - this.laser(this.vertices[1], this.angle + Math.PI); - this.laser(this.vertices[2], this.angle - Math.PI / 3); - 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 - } - // this.laser(this.vertices[2], this.angle + Math.PI / 3); - this.checkStatus(); - }; - me.laser = function (where, angle) { - const vertexCollision = function (v1, v1End, domain) { - for (let i = 0; i < domain.length; ++i) { - let vertices = domain[i].vertices; - const len = vertices.length - 1; - for (let j = 0; j < len; j++) { - results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[j], - v2: vertices[j + 1] - }; - } - } - results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2) best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[0], - v2: vertices[len] - }; - } - } - }; - - const seeRange = 7000; - best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - const look = { - x: where.x + seeRange * Math.cos(angle), - y: where.y + seeRange * Math.sin(angle) - }; - // vertexCollision(where, look, mob); - vertexCollision(where, look, map); - vertexCollision(where, look, body); - if (!mech.isCloak) vertexCollision(where, look, [player]); - if (best.who && best.who === player && mech.immuneCycle < mech.cycle) { - mech.immuneCycle = mech.cycle + mod.collisionImmuneCycles; //player is immune to collision damage for 30 cycles - const dmg = 0.14 * game.dmgScale; - mech.damage(dmg); - game.drawList.push({ //add dmg to draw queue - x: best.x, - y: best.y, - radius: dmg * 1500, - color: "rgba(80,0,255,0.5)", - time: 20 - }); - } - //draw beam - if (best.dist2 === Infinity) best = look; - ctx.moveTo(where.x, where.y); - ctx.lineTo(best.x, best.y); - } - }, - stabber(x, y, radius = 25 + Math.ceil(Math.random() * 12), spikeMax = 9) { - if (radius > 80) radius = 65; - mobs.spawn(x, y, 6, radius, "rgb(220,50,205)"); //can't have sides above 6 or collision events don't work (probably because of a convex problem) - let me = mob[mob.length - 1]; - me.isVerticesChange = true - me.accelMag = 0.0006 * game.accelScale; - // me.g = 0.0002; //required if using 'gravity' - me.delay = 360 * game.CDScale; - me.spikeVertex = 0; - me.spikeLength = 0; - me.isSpikeGrowing = false; - me.isSpikeReset = true; - me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.player //can't touch other mobs - Matter.Body.rotate(me, Math.PI * 0.1); - spawn.shield(me, x, y); - // me.onDamage = function () {}; - me.onDeath = function () { - if (this.spikeLength > 4) { - this.spikeLength = 4 - const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[this.spikeVertex], this.position)), this.radius * this.spikeLength) - this.vertices[this.spikeVertex].x = this.position.x + spike.x - this.vertices[this.spikeVertex].y = this.position.y + spike.y - // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) - } - }; - me.do = function () { - if (!mech.isBodiesAsleep) { - // this.gravity(); - this.seePlayerByLookingAt(); - this.checkStatus(); - this.attraction(); - - if (this.isSpikeReset) { - if (this.seePlayer.recall) { - const dist = Vector.sub(this.seePlayer.position, this.position); - const distMag = Vector.magnitude(dist); - if (distMag < this.radius * 7) { - //find nearest vertex - let nearestDistance = Infinity - for (let i = 0, len = this.vertices.length; i < len; i++) { - //find distance to player for each vertex - const dist = Vector.sub(this.seePlayer.position, this.vertices[i]); - const distMag = Vector.magnitude(dist); - //save the closest distance - if (distMag < nearestDistance) { - this.spikeVertex = i - nearestDistance = distMag + if (canBeBoss) { + if (Math.random() < 0.55) { + this.nodeBoss(x, y, pick); + } else { + this.lineBoss(x, y, pick); + } + } else { + if (Math.random() < 0.07) { + this[pick](x, y, 90 + Math.random() * 40); //one extra large mob + } else if (Math.random() < 0.35) { + this.groupBoss(x, y) //hidden grouping blocks + } else { + pick = (Math.random() < 0.5) ? "randomList" : "random"; + if (Math.random() < 0.55) { + this.nodeBoss(x, y, pick); + } else { + this.lineBoss(x, y, pick); + } } - } - this.spikeLength = 1 - this.isSpikeGrowing = true; - this.isSpikeReset = false; - Matter.Body.setAngularVelocity(this, 0) } - } - } else { - if (this.isSpikeGrowing) { - this.spikeLength += 1 - if (this.spikeLength > spikeMax) { - this.isSpikeGrowing = false; - } - } else { - - //reduce rotation - Matter.Body.setAngularVelocity(this, this.angularVelocity * 0.8) - - this.spikeLength -= 0.2 - if (this.spikeLength < 1) { - this.spikeLength = 1 - this.isSpikeReset = true - } - } - const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[this.spikeVertex], this.position)), this.radius * this.spikeLength) - this.vertices[this.spikeVertex].x = this.position.x + spike.x - this.vertices[this.spikeVertex].y = this.position.y + spike.y } - } - }; - }, - striker(x, y, radius = 14 + Math.ceil(Math.random() * 25)) { - mobs.spawn(x, y, 5, radius, "rgb(221,102,119)"); - let me = mob[mob.length - 1]; - me.accelMag = 0.0003 * game.accelScale; - me.g = 0.0002; //required if using 'gravity' - me.frictionStatic = 0; - me.friction = 0; - me.delay = 90 * game.CDScale; - me.cd = Infinity; - Matter.Body.rotate(me, Math.PI * 0.1); - spawn.shield(me, x, y); - me.onDamage = function () { - this.cd = game.cycle + this.delay; - }; - me.do = function () { - this.gravity(); - if (!(game.cycle % this.seePlayerFreq)) { // this.seePlayerCheck(); from mobs - 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(); - if (this.cd === Infinity) this.cd = game.cycle + this.delay * 0.7; - } else if (this.seePlayer.recall) { - this.lostPlayer(); - this.cd = Infinity - } - } - this.checkStatus(); - this.attraction(); - if (this.seePlayer.recall && this.cd < game.cycle) { - const dist = Vector.sub(this.seePlayer.position, this.position); - const distMag = Vector.magnitude(dist); - if (distMag < 400) { - this.cd = game.cycle + this.delay; - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - Matter.Body.translate(this, Vector.mult(Vector.normalise(dist), distMag - 20 - radius)); - ctx.lineTo(this.position.x, this.position.y); - ctx.lineWidth = radius * 2; - ctx.strokeStyle = this.fill; //"rgba(0,0,0,0.5)"; //'#000' - ctx.stroke(); - } - } - }; - }, - sneaker(x, y, radius = 15 + Math.ceil(Math.random() * 25)) { - let me; - mobs.spawn(x, y, 5, radius, "transparent"); - me = mob[mob.length - 1]; - me.accelMag = 0.0007 * game.accelScale; - me.g = 0.0002; //required if using 'gravity' - me.stroke = "transparent"; //used for drawSneaker - me.alpha = 1; //used in drawSneaker - // me.leaveBody = false; - me.canTouchPlayer = false; //used in drawSneaker - me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player - me.showHealthBar = false; - // me.memory = 420; - me.do = function () { - this.gravity(); - this.seePlayerCheck(); - this.checkStatus(); - this.attraction(); - //draw - if (!mech.isBodiesAsleep) { - if (this.seePlayer.yes) { - if (this.alpha < 1) this.alpha += 0.01; - } else { - if (this.alpha > 0) this.alpha -= 0.03; - } - } - if (this.alpha > 0) { - if (this.alpha > 0.95) { - this.healthBar(); - if (!this.canTouchPlayer) { - this.canTouchPlayer = true; - this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob; //can touch player - } - } - //draw body - ctx.beginPath(); - const vertices = this.vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1, len = vertices.length; j < len; ++j) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - ctx.fillStyle = `rgba(0,0,0,${this.alpha * this.alpha})`; - ctx.fill(); - } else if (this.canTouchPlayer) { - this.canTouchPlayer = false; - this.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player - } - }; - }, - ghoster(x, y, radius = 40 + Math.ceil(Math.random() * 100)) { - let me; - mobs.spawn(x, y, 7, radius, "transparent"); - me = mob[mob.length - 1]; - me.seeAtDistance2 = 300000; - me.accelMag = 0.00012 * game.accelScale; - if (map.length) me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search - Matter.Body.setDensity(me, 0.00065); //normal is 0.001 //makes effective life much lower - me.stroke = "transparent"; //used for drawGhost - me.alpha = 1; //used in drawGhost - me.canTouchPlayer = false; //used in drawGhost - // me.leaveBody = false; - me.collisionFilter.mask = cat.bullet - me.showHealthBar = false; - me.memory = 480; - me.do = function () { - //cap max speed - if (this.speed > 5) { - Matter.Body.setVelocity(this, { - x: this.velocity.x * 0.8, - y: this.velocity.y * 0.8 - }); - } - this.seePlayerCheckByDistance(); - this.checkStatus(); - this.attraction(); - this.search(); - //draw - if (this.distanceToPlayer2() - this.seeAtDistance2 < 0) { - if (this.alpha < 1) this.alpha += 0.004; - } else { - if (this.alpha > 0) this.alpha -= 0.03; - } - if (this.alpha > 0) { - if (this.alpha > 0.9 && this.seePlayer.recall) { - this.healthBar(); - if (!this.canTouchPlayer) { - this.canTouchPlayer = true; - this.collisionFilter.mask = cat.player | cat.bullet - } - } - //draw body - ctx.beginPath(); - const vertices = this.vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1, len = vertices.length; j < len; ++j) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - ctx.lineWidth = 1; - ctx.strokeStyle = `rgba(0,0,0,${this.alpha * this.alpha})`; - ctx.stroke(); - } else if (this.canTouchPlayer) { - this.canTouchPlayer = false; - this.collisionFilter.mask = cat.bullet; //can't touch player or walls - } - }; - }, - // blinker(x, y, radius = 45 + Math.ceil(Math.random() * 70)) { - // mobs.spawn(x, y, 6, radius, "transparent"); - // let me = mob[mob.length - 1]; - // Matter.Body.setDensity(me, 0.0005); //normal is 0.001 //makes effective life much lower - // me.stroke = "rgb(0,200,255)"; //used for drawGhost - // Matter.Body.rotate(me, Math.random() * 2 * Math.PI); - // me.blinkRate = 40 + Math.round(Math.random() * 60); //required for blink - // me.blinkLength = 150 + Math.round(Math.random() * 200); //required for blink - // me.isStatic = true; - // me.memory = 360; - // me.seePlayerFreq = Math.round((40 + 30 * Math.random()) * game.lookFreqScale); - // // me.isBig = false; - // // me.scaleMag = Math.max(5 - me.mass, 1.75); - // me.onDeath = function () { - // // if (this.isBig) { - // // Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag); - // // this.isBig = false; - // // } - // }; - // me.onHit = function () { - // game.timeSkip(120) - // }; - // me.do = function () { - // this.seePlayerCheck(); - // this.blink(); - // //strike by expanding - // // if (this.isBig) { - // // if (this.cd - this.delay + 15 < game.cycle) { - // // Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag); - // // this.isBig = false; - // // } - // // } else - // if (this.seePlayer.yes && this.cd < game.cycle) { - // const dist = Vector.sub(this.seePlayer.position, this.position); - // const distMag2 = Vector.magnitudeSquared(dist); - // if (distMag2 < 80000) { - // this.cd = game.cycle + this.delay; - - // // Matter.Body.scale(this, this.scaleMag, this.scaleMag); - // // this.isBig = true; - // } - // } - // }; - // }, - bomberBoss(x, y, radius = 88) { - //boss that drops bombs from above and holds a set distance from player - mobs.spawn(x, y, 3, radius, "transparent"); - let me = mob[mob.length - 1]; - me.isBoss = true; - Matter.Body.setDensity(me, 0.0014 + 0.0003 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger - - me.stroke = "rgba(255,0,200)"; //used for drawGhost - me.seeAtDistance2 = 1500000; - me.fireFreq = Math.floor(120 * game.CDScale); - me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search - me.hoverElevation = 460 + (Math.random() - 0.5) * 200; //squared - me.hoverXOff = (Math.random() - 0.5) * 100; - me.accelMag = Math.floor(10 * (Math.random() + 4.5)) * 0.00001 * game.accelScale; - me.g = 0.0002; //required if using 'gravity' // gravity called in hoverOverPlayer - me.frictionStatic = 0; - me.friction = 0; - me.frictionAir = 0.01; - // me.memory = 300; - // Matter.Body.setDensity(me, 0.0015); //extra dense //normal is 0.001 - me.collisionFilter.mask = cat.player | cat.bullet - spawn.shield(me, x, y, 1); - me.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - }; - me.do = function () { - this.seePlayerCheckByDistance(); - this.checkStatus(); - if (this.seePlayer.recall) { - this.hoverOverPlayer(); - this.bomb(); - this.search(); - } - }; - }, - shooter(x, y, radius = 25 + Math.ceil(Math.random() * 50)) { - mobs.spawn(x, y, 3, radius, "rgb(255,100,150)"); - let me = mob[mob.length - 1]; - // me.vertices = Matter.Vertices.clockwiseSort(Matter.Vertices.rotate(me.vertices, Math.PI, me.position)); //make the pointy side of triangle the front - me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front - me.isVerticesChange = true - // Matter.Body.rotate(me, Math.PI) - - me.memory = 120; - me.fireFreq = 0.007 + Math.random() * 0.005; - me.noseLength = 0; - me.fireAngle = 0; - me.accelMag = 0.0005 * game.accelScale; - me.frictionStatic = 0; - me.friction = 0; - me.frictionAir = 0.05; - me.lookTorque = 0.0000025 * (Math.random() > 0.5 ? -1 : 1); - me.fireDir = { - x: 0, - y: 0 - }; - me.onDeath = function () { //helps collisions functions work better after vertex have been changed - // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) - } - // spawn.shield(me, x, y); - me.do = function () { - this.seePlayerByLookingAt(); - this.checkStatus(); - this.fire(); - }; - }, - shooterBoss(x, y, radius = 110) { - mobs.spawn(x, y, 3, radius, "rgb(255,70,180)"); - let me = mob[mob.length - 1]; - me.isBoss = true; - me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front - me.isVerticesChange = true - me.memory = 240; - me.homePosition = { - x: x, - y: y - }; - me.fireFreq = 0.025; - me.noseLength = 0; - me.fireAngle = 0; - me.accelMag = 0.005 * game.accelScale; - me.frictionAir = 0.05; - me.lookTorque = 0.000007 * (Math.random() > 0.5 ? -1 : 1); - me.fireDir = { - x: 0, - y: 0 - }; - Matter.Body.setDensity(me, 0.03 + 0.0008 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger - me.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) //helps collisions functions work better after vertex have been changed - }; - - me.do = function () { - this.seePlayerByLookingAt(); - this.checkStatus(); - this.fire(); - //gently return to starting location - const sub = Vector.sub(this.homePosition, this.position) - const dist = Vector.magnitude(sub) - if (dist > 50) this.force = Vector.mult(Vector.normalise(sub), this.mass * 0.0002) - }; - }, - bullet(x, y, radius = 6, sides = 0) { - //bullets - mobs.spawn(x, y, sides, radius, "rgb(255,0,0)"); - let me = mob[mob.length - 1]; - me.stroke = "transparent"; - me.onHit = function () { - this.explode(this.mass * 20); - }; - Matter.Body.setDensity(me, 0.00005); //normal is 0.001 - me.timeLeft = 200; - me.g = 0.001; //required if using 'gravity' - me.frictionAir = 0; - me.restitution = 0.8; - me.leaveBody = false; - me.dropPowerUp = false; - me.showHealthBar = false; - me.collisionFilter.category = cat.mobBullet; - me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet; - me.do = function () { - this.gravity(); - this.timeLimit(); - }; - }, - bomb(x, y, radius = 6, sides = 5) { - mobs.spawn(x, y, sides, radius, "rgb(255,0,0)"); - let me = mob[mob.length - 1]; - me.stroke = "transparent"; - me.onHit = function () { - this.explode(this.mass * 120); - }; - me.onDeath = function () { - if (game.difficulty > 4) { - spawn.bullet(this.position.x, this.position.y, this.radius / 3, 5); - spawn.bullet(this.position.x, this.position.y, this.radius / 3, 5); - spawn.bullet(this.position.x, this.position.y, this.radius / 3, 5); - const mag = 8 - const v1 = Vector.rotate({ - x: 1, - y: 1 - }, 2 * Math.PI * Math.random()) - const v2 = Vector.rotate({ - x: 1, - y: 1 - }, 2 * Math.PI * Math.random()) - const v3 = Vector.normalise(Vector.add(v1, v2)) //last vector is opposite the sum of the other two to look a bit like momentum is conserved - - Matter.Body.setVelocity(mob[mob.length - 1], { - x: mag * v1.x, - y: mag * v1.y - }); - Matter.Body.setVelocity(mob[mob.length - 2], { - x: mag * v2.x, - y: mag * v2.y - }); - Matter.Body.setVelocity(mob[mob.length - 3], { - x: -mag * v3.x, - y: -mag * v3.y - }); - } - } - Matter.Body.setDensity(me, 0.00005); //normal is 0.001 - me.timeLeft = 140 + Math.floor(Math.random() * 30); - me.g = 0.001; //required if using 'gravity' - me.frictionAir = 0; - me.restitution = 1; - me.leaveBody = false; - me.dropPowerUp = false; - me.showHealthBar = false; - me.collisionFilter.category = cat.mobBullet; - me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet; - me.do = function () { - this.gravity(); - this.timeLimit(); - }; - }, - sniper(x, y, radius = 35 + Math.ceil(Math.random() * 30)) { - mobs.spawn(x, y, 3, radius, "transparent"); //"rgb(25,0,50)") - let me = mob[mob.length - 1]; - me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front - me.isVerticesChange = true - // Matter.Body.rotate(me, Math.PI) - me.stroke = "transparent"; //used for drawSneaker - me.alpha = 1; //used in drawSneaker - me.showHealthBar = false; - me.frictionStatic = 0; - me.friction = 0; - me.canTouchPlayer = false; //used in drawSneaker - me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player - - me.memory = 60 //140; - me.fireFreq = 0.006 + Math.random() * 0.002; - me.noseLength = 0; - me.fireAngle = 0; - me.accelMag = 0.0005 * game.accelScale; - me.frictionAir = 0.05; - me.torque = 0.0001 * me.inertia; - me.fireDir = { - x: 0, - y: 0 - }; - me.onDeath = function () { //helps collisions functions work better after vertex have been changed - // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) - } - // spawn.shield(me, x, y); - me.do = function () { - // this.seePlayerByLookingAt(); - this.seePlayerCheck(); - this.checkStatus(); - - 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; + }, + randomLevelBoss(x, y, options = ["shooterBoss", "cellBossCulture", "bomberBoss", "spiderBoss", "launcherBoss", "laserTargetingBoss", "powerUpBoss", "snakeBoss"]) { + // other bosses: suckerBoss, laserBoss, tetherBoss, //all need a particular level to work so they are not included + spawn[options[Math.floor(Math.random() * options.length)]](x, y) + }, + //mob templates ********************************************************************************************* + //*********************************************************************************************************** + finalBoss(x, y, radius = 300) { + mobs.spawn(x, y, 6, radius, "rgb(150,150,255)"); + let me = mob[mob.length - 1]; + me.isBoss = true; + me.frictionAir = 0.01; + me.memory = Infinity; + me.locatePlayer(); + const density = 5 + Matter.Body.setDensity(me, density); //extra dense //normal is 0.001 //makes effective life much larger + spawn.shield(me, x, y, 1); + me.onDeath = function() { + level.bossKilled = true; + level.exit.x = 5500; + level.exit.y = -330; }; - //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) / 1600; //gives the bullet an arc - } - //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.2; - 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.sniperBullet(this.vertices[1].x, this.vertices[1].y, 5 + Math.ceil(this.radius / 15), 4); - const v = 20 * game.accelScale; - 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() + me.onDamage = function() {}; + me.cycle = 300; + me.endCycle = 600; + me.mode = 0; + me.do = function() { + //hold position + Matter.Body.setPosition(this, { + x: x, + y: y }); - 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(); + Matter.Body.setVelocity(this, { + x: 0, + y: 0 + }); + 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 + } + + } + } 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 !== 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 + } + } + }; + me.modeDo = function() {} + me.modeAll = function() { + this.modeSpawns() + this.modeSuck() + this.modeLasers() } - // else if (this.noseLength < -0.1) { - // this.noseLength += this.fireFreq / 4; - // setNoseShape(); + me.modeSpawns = function() { + if (!(this.cycle % 320) && !mech.isBodiesAsleep && mob.length < 40) { + Matter.Body.setAngularVelocity(this, 0.11) + //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) + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + velocity.x, + y: this.velocity.y + velocity.y + }); + } + } + } + me.eventHorizon = 1400 + me.modeSuck = function() { + //eventHorizon waves in and out + eventHorizon = this.eventHorizon * (1 + 0.2 * Math.sin(game.cycle * 0.015)) + //draw darkness + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.2, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.6)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.4, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.4)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.6, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.3)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.8, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.2)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.05)"; + ctx.fill(); + //when player is inside event horizon + if (Vector.magnitude(Vector.sub(this.position, player.position)) < eventHorizon) { + if (mech.energy > 0) mech.energy -= 0.01 + if (mech.energy < 0.15) { + mech.damage(0.0004 * game.dmgScale); + } + const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + player.force.x -= 0.0017 * Math.cos(angle) * player.mass * (mech.onGround ? 1.7 : 1); + player.force.y -= 0.0017 * Math.sin(angle) * player.mass; + //draw line to player + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mech.pos.x, mech.pos.y); + ctx.lineWidth = Math.min(60, this.radius * 2); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.stroke(); + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.3)"; + ctx.fill(); + } + this.curl(eventHorizon); + } + 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 (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 + } + } + me.laser = function(where, angle, dmg = 0.14 * game.dmgScale) { + const vertexCollision = function(v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + }; + + const seeRange = 7000; + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const look = { + x: where.x + seeRange * Math.cos(angle), + y: where.y + seeRange * Math.sin(angle) + }; + // vertexCollision(where, look, mob); + vertexCollision(where, look, map); + vertexCollision(where, look, body); + if (!mech.isCloak) vertexCollision(where, look, [player]); + if (best.who && best.who === player && mech.immuneCycle < mech.cycle) { + mech.immuneCycle = mech.cycle + mod.collisionImmuneCycles; //player is immune to collision damage for 30 cycles + mech.damage(dmg); + game.drawList.push({ //add dmg to draw queue + x: best.x, + y: best.y, + radius: dmg * 1500, + color: "rgba(80,0,255,0.5)", + time: 20 + }); + } + //draw beam + if (best.dist2 === Infinity) best = look; + ctx.moveTo(where.x, where.y); + ctx.lineTo(best.x, best.y); + } + } + }, + groupBoss(x, y, num = 3 + Math.random() * 8) { + for (let i = 0; i < num; i++) { + const radius = 25 + Math.floor(Math.random() * 20) + spawn.grouper(x + Math.random() * radius, y + Math.random() * radius, radius); + } + }, + grouper(x, y, radius = 25 + Math.floor(Math.random() * 20)) { + mobs.spawn(x, y, 4, radius, "#777"); + let me = mob[mob.length - 1]; + me.g = 0.00015; //required if using 'gravity' + me.accelMag = 0.0008 * game.accelScale; + me.groupingRangeMax = 250000 + Math.random() * 100000; + me.groupingRangeMin = (radius * 8) * (radius * 8); + me.groupingStrength = 0.0005 + me.memory = 200; + me.isGrouper = true; + + me.do = function() { + this.gravity(); + this.checkStatus(); + if (this.seePlayer.recall) { + this.seePlayerCheck(); + this.attraction(); + //tether to other blocks + ctx.beginPath(); + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].isGrouper && mob[i] != this && mob[i].dropPowerUp) { //don't tether to self, bullets, shields, ... + const distance2 = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)) + if (distance2 < this.groupingRangeMax) { + if (!mob[i].seePlayer.recall) mob[i].seePlayerCheck(); //wake up sleepy mobs + if (distance2 > this.groupingRangeMin) { + const angle = Math.atan2(mob[i].position.y - this.position.y, mob[i].position.x - this.position.x); + const forceMag = this.groupingStrength * mob[i].mass; + mob[i].force.x -= forceMag * Math.cos(angle); + mob[i].force.y -= forceMag * Math.sin(angle); + } + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mob[i].position.x, mob[i].position.y); + } + } + } + ctx.strokeStyle = "#000"; + ctx.lineWidth = 1; + ctx.stroke(); + } + } + }, + starter(x, y, radius = Math.floor(20 + 20 * Math.random())) { + //easy mob for on level 1 + mobs.spawn(x, y, 8, radius, "#9ccdc6"); + let me = mob[mob.length - 1]; + // console.log(`mass=${me.mass}, radius = ${radius}`) + me.accelMag = 0.0005 * game.accelScale; + me.memory = 60; + me.seeAtDistance2 = 1400000 //1200 vision range + Matter.Body.setDensity(me, 0.0005) // normal density is 0.001 // this reduces life by half and decreases knockback + + me.do = function() { + this.seePlayerByLookingAt(); + this.attraction(); + this.checkStatus(); + }; + }, + cellBossCulture(x, y, radius = 20, num = 5) { + for (let i = 0; i < num; i++) { + spawn.cellBoss(x, y, radius) + } + }, + cellBoss(x, y, radius = 20) { + mobs.spawn(x + Math.random(), y + Math.random(), 20, radius * (1 + 1.2 * Math.random()), "rgba(0,150,155,0.7)"); + let me = mob[mob.length - 1]; + me.isBoss = true; + me.isCell = true; + me.accelMag = 0.00015 * game.accelScale; + me.memory = 40; + me.isVerticesChange = true + me.frictionAir = 0.012 + me.seePlayerFreq = Math.floor(11 + 7 * Math.random()) + me.seeAtDistance2 = 1400000; + me.cellMassMax = 70 + + me.collisionFilter.mask = cat.player | cat.bullet + Matter.Body.setDensity(me, 0.0005) // normal density is 0.001 // this reduces life by half and decreases knockback + // console.log(me.mass, me.radius) + const k = 642 //k=r^2/m + me.split = function() { + Matter.Body.scale(this, 0.4, 0.4); + this.radius = Math.sqrt(this.mass * k / Math.PI) + spawn.cellBoss(this.position.x, this.position.y, this.radius); + mob[mob.length - 1].health = this.health + } + me.onHit = function() { //run this function on hitting player + this.health = 1; + this.split(); + }; + me.onDamage = function(dmg) { + if (Math.random() < 0.33 * dmg * Math.sqrt(this.mass) && this.health > dmg) this.split(); + } + me.do = function() { + if (!mech.isBodiesAsleep) { + this.seePlayerByDistOrLOS(); + this.checkStatus(); + this.attraction(); + + if (this.seePlayer.recall && this.mass < this.cellMassMax) { //grow cell radius + const scale = 1 + 0.0002 * this.cellMassMax / this.mass; + Matter.Body.scale(this, scale, scale); + this.radius = Math.sqrt(this.mass * k / Math.PI) + } + if (!(game.cycle % this.seePlayerFreq)) { //move away from other mobs + const repelRange = 200 + const attractRange = 800 + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].isCell && mob[i].id !== this.id) { + const sub = Vector.sub(this.position, mob[i].position) + const dist = Vector.magnitude(sub) + if (dist < repelRange) { + this.force = Vector.mult(Vector.normalise(sub), this.mass * 0.006) + } else if (dist > attractRange) { + this.force = Vector.mult(Vector.normalise(sub), -this.mass * 0.004) + } + } + } + } + } + }; + me.onDeath = function() { + this.isCell = false; + let count = 0 //count other cells + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].isCell) count++ + } + if (count < 1) { //only drop a power up if this is the last cell + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + } else { + this.leaveBody = false; + this.dropPowerUp = false; + } + } + }, + powerUpBoss(x, y, vertices = 9, radius = 130) { + mobs.spawn(x, y, vertices, radius, "transparent"); + let me = mob[mob.length - 1]; + me.isBoss = true; + me.frictionAir = 0.01 + me.seeAtDistance2 = 1000000; + me.accelMag = 0.0005 * game.accelScale; + Matter.Body.setDensity(me, 0.0006); //normal is 0.001 + me.collisionFilter.mask = cat.bullet | cat.player + me.memory = Infinity; + me.seePlayerFreq = 30 + + me.lockedOn = null; + if (vertices === 9) { + //on primary spawn + powerUps.spawnBossPowerUp(me.position.x, me.position.y) + powerUps.spawn(me.position.x, me.position.y, "heal"); + powerUps.spawn(me.position.x, me.position.y, "ammo"); + } else if (!mech.isCloak) { + me.foundPlayer(); + } + me.onHit = function() { //run this function on hitting player + powerUps.ejectMod() + powerUps.spawn(mech.pos.x, mech.pos.y, "heal"); + powerUps.spawn(mech.pos.x, mech.pos.y, "heal"); + }; + me.onDeath = function() { + this.leaveBody = false; + this.dropPowerUp = false; + + if (vertices > 3) spawn.powerUpBoss(this.position.x, this.position.y, vertices - 1) + for (let i = 0; i < powerUp.length; i++) { + powerUp[i].collisionFilter.mask = cat.map | cat.powerUp + } + }; + me.do = function() { + this.stroke = `hsl(0,0%,${80+25*Math.sin(game.cycle*0.01)}%)` + + //steal all power ups + for (let i = 0; i < Math.min(powerUp.length, this.vertices.length); i++) { + powerUp[i].collisionFilter.mask = 0 + Matter.Body.setPosition(powerUp[i], this.vertices[i]) + Matter.Body.setVelocity(powerUp[i], { + x: 0, + y: 0 + }) + } + + this.seePlayerCheckByDistance(); + this.attraction(); + this.checkStatus(); + }; + }, + chaser(x, y, radius = 35 + Math.ceil(Math.random() * 40)) { + mobs.spawn(x, y, 8, radius, "rgb(255,150,100)"); //"#2c9790" + let me = mob[mob.length - 1]; + // Matter.Body.setDensity(me, 0.0007); //extra dense //normal is 0.001 //makes effective life much lower + me.friction = 0; + me.frictionAir = 0; + me.accelMag = 0.001 * Math.sqrt(game.accelScale); + me.g = me.accelMag * 0.6; //required if using 'gravity' + me.memory = 50; + spawn.shield(me, x, y); + me.do = function() { + this.gravity(); + this.seePlayerCheck(); + this.checkStatus(); + this.attraction(); + }; + }, + grower(x, y, radius = 15) { + mobs.spawn(x, y, 7, radius, "hsl(144, 15%, 50%)"); + let me = mob[mob.length - 1]; + me.isVerticesChange = true + me.big = false; //required for grow + me.accelMag = 0.00045 * game.accelScale; + me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.player //can't touch other mobs + // me.onDeath = function () { //helps collisions functions work better after vertex have been changed + // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) // } + me.do = function() { + this.seePlayerByLookingAt(); + this.checkStatus(); + this.attraction(); + this.grow(); + }; + }, + springer(x, y, radius = 20 + Math.ceil(Math.random() * 35)) { + mobs.spawn(x, y, 10, radius, "#b386e8"); + let me = mob[mob.length - 1]; + me.friction = 0; + me.frictionAir = 0.006; + me.lookTorque = 0.0000008; //controls spin while looking for player + me.g = 0.0002; //required if using 'gravity' + me.seePlayerFreq = Math.round((40 + 25 * Math.random()) * game.lookFreqScale); + const springStiffness = 0.00014; + const springDampening = 0.0005; - if (this.seePlayer.recall) { - if (this.alpha < 1) this.alpha += 0.01; + me.springTarget = { + x: me.position.x, + y: me.position.y + }; + const len = cons.length; + cons[len] = Constraint.create({ + pointA: me.springTarget, + bodyB: me, + stiffness: springStiffness, + damping: springDampening + }); + World.add(engine.world, cons[cons.length - 1]); + + cons[len].length = 100 + 1.5 * radius; + me.cons = cons[len]; + + me.springTarget2 = { + x: me.position.x, + y: me.position.y + }; + const len2 = cons.length; + cons[len2] = Constraint.create({ + pointA: me.springTarget2, + bodyB: me, + stiffness: springStiffness, + damping: springDampening + }); + World.add(engine.world, cons[cons.length - 1]); + cons[len2].length = 100 + 1.5 * radius; + me.cons2 = cons[len2]; + + + me.onDeath = function() { + this.removeCons(); + }; + spawn.shield(me, x, y); + me.do = function() { + this.gravity(); + this.searchSpring(); + this.checkStatus(); + this.springAttack(); + }; + }, + hopper(x, y, radius = 30 + Math.ceil(Math.random() * 30)) { + mobs.spawn(x, y, 5, radius, "rgb(0,200,180)"); + let me = mob[mob.length - 1]; + me.accelMag = 0.04; + me.g = 0.0017; //required if using 'gravity' + me.frictionAir = 0.01; + me.restitution = 0; + me.delay = 120 * game.CDScale; + me.randomHopFrequency = 200 + Math.floor(Math.random() * 150); + me.randomHopCD = game.cycle + me.randomHopFrequency; + spawn.shield(me, x, y); + me.do = function() { + this.gravity(); + this.seePlayerCheck(); + this.checkStatus(); + if (this.seePlayer.recall) { + if (this.cd < game.cycle && (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length)) { + this.cd = game.cycle + this.delay; + const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle) - (Math.random() * 0.07 + 0.02) * this.mass; //antigravity + } + } else { + //randomly hob if not aware of player + if (this.randomHopCD < game.cycle && (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length)) { + this.randomHopCD = game.cycle + this.randomHopFrequency; + //slowly change randomHopFrequency after each hop + this.randomHopFrequency = Math.max(100, this.randomHopFrequency + (0.5 - Math.random()) * 200); + const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass * (0.1 + Math.random() * 0.3); + const angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI; + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle) - 0.05 * this.mass; //antigravity + } + } + }; + }, + spinner(x, y, radius = 30 + Math.ceil(Math.random() * 35)) { + mobs.spawn(x, y, 5, radius, "#000000"); + let me = mob[mob.length - 1]; + me.fill = "#28b"; + me.rememberFill = me.fill; + me.cd = 0; + me.burstDir = { + x: 0, + y: 0 + }; + me.frictionAir = 0.022; + me.lookTorque = 0.0000014; + me.restitution = 0; + spawn.shield(me, x, y); + me.look = function() { + this.seePlayerByLookingAt(); + this.checkStatus(); + if (this.seePlayer.recall && this.cd < game.cycle) { + this.burstDir = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); + this.cd = game.cycle + 40; + this.do = this.spin + } + } + me.do = me.look + me.spin = function() { + this.checkStatus(); + this.torque += 0.000035 * this.inertia; + this.fill = randomColor({ + hue: "blue" + }); + //draw attack vector + const mag = this.radius * 2.5 + 50; + ctx.strokeStyle = "rgba(0,0,0,0.2)"; + ctx.lineWidth = 3; + ctx.setLineDash([10, 20]); //30 + const dir = Vector.add(this.position, Vector.mult(this.burstDir, mag)); + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(dir.x, dir.y); + ctx.stroke(); + ctx.setLineDash([]); + if (this.cd < game.cycle) { + this.fill = this.rememberFill; + this.cd = game.cycle + 180 * game.CDScale + this.do = this.look + this.force = Vector.mult(this.burstDir, this.mass * 0.25); + } + } + }, + sucker(x, y, radius = 30 + Math.ceil(Math.random() * 70)) { + radius = 9 + radius / 8; //extra small + mobs.spawn(x, y, 6, radius, "#000"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; //used for drawSneaker + me.eventHorizon = radius * 23; //required for blackhole + me.seeAtDistance2 = (me.eventHorizon + 500) * (me.eventHorizon + 500); //vision limit is event horizon + me.accelMag = 0.00009 * game.accelScale; + // me.frictionAir = 0.005; + me.memory = 600; + Matter.Body.setDensity(me, 0.004); //extra dense //normal is 0.001 //makes effective life much larger + me.do = function() { + //keep it slow, to stop issues from explosion knock backs + if (this.speed > 5) { + Matter.Body.setVelocity(this, { + x: this.velocity.x * 0.99, + y: this.velocity.y * 0.99 + }); + } + this.seePlayerByDistOrLOS(); + this.checkStatus(); + if (this.seePlayer.recall) { + //eventHorizon waves in and out + eventHorizon = this.eventHorizon * (0.93 + 0.17 * Math.sin(game.cycle * 0.011)) + + //accelerate towards the player + const forceMag = this.accelMag * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle); + + //draw darkness + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.25, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.9)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.55, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.5)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.1)"; + ctx.fill(); + + //when player is inside event horizon + if (Vector.magnitude(Vector.sub(this.position, player.position)) < eventHorizon) { + if (mech.energy > 0) mech.energy -= 0.004 + if (mech.energy < 0.1) { + mech.damage(0.00015 * game.dmgScale); + } + const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + player.force.x -= 0.00125 * player.mass * Math.cos(angle) * (mech.onGround ? 1.8 : 1); + player.force.y -= 0.0001 * player.mass * Math.sin(angle); + //draw line to player + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mech.pos.x, mech.pos.y); + ctx.lineWidth = Math.min(60, this.radius * 2); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.stroke(); + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.3)"; + ctx.fill(); + } + } + } + }, + suckerBoss(x, y, radius = 25) { + mobs.spawn(x, y, 12, radius, "#000"); + let me = mob[mob.length - 1]; + me.isBoss = true; + me.stroke = "transparent"; //used for drawSneaker + me.eventHorizon = 1100; //required for black hole + me.seeAtDistance2 = (me.eventHorizon + 1000) * (me.eventHorizon + 1000); //vision limit is event horizon + me.accelMag = 0.00003 * game.accelScale; + me.collisionFilter.mask = cat.player | cat.bullet + // me.frictionAir = 0.005; + me.memory = 1600; + Matter.Body.setDensity(me, 0.05); //extra dense //normal is 0.001 //makes effective life much larger + me.onDeath = function() { + //applying forces to player doesn't seem to work inside this method, not sure why + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + if (game.difficulty > 5) { + //teleport everything to center + function toMe(who, where, range) { + for (let i = 0, len = who.length; i < len; i++) { + const SUB = Vector.sub(who[i].position, where) + const DISTANCE = Vector.magnitude(SUB) + if (DISTANCE < range) { + Matter.Body.setPosition(who[i], where) + } + } + } + toMe(body, this.position, this.eventHorizon) + toMe(mob, this.position, this.eventHorizon) + // toMe(bullet, this.position, this.eventHorizon) + } + }; + me.do = function() { + //keep it slow, to stop issues from explosion knock backs + if (this.speed > 1) { + Matter.Body.setVelocity(this, { + x: this.velocity.x * 0.95, + y: this.velocity.y * 0.95 + }); + } + this.seePlayerByDistOrLOS(); + this.checkStatus(); + if (this.seePlayer.recall) { + //accelerate towards the player + const forceMag = this.accelMag * this.mass; + const dx = this.seePlayer.position.x - this.position.x + const dy = this.seePlayer.position.y - this.position.y + const mag = Math.sqrt(dx * dx + dy * dy) + this.force.x += forceMag * dx / mag; + this.force.y += forceMag * dy / mag; + + //eventHorizon waves in and out + eventHorizon = this.eventHorizon * (1 + 0.2 * Math.sin(game.cycle * 0.008)) + // zoom camera in and out with the event horizon + + //draw darkness + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.2, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.6)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.4, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.4)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.6, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.3)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.8, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.2)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.05)"; + ctx.fill(); + //when player is inside event horizon + if (Vector.magnitude(Vector.sub(this.position, player.position)) < eventHorizon) { + if (mech.energy > 0) mech.energy -= 0.006 + if (mech.energy < 0.1) { + mech.damage(0.0002 * game.dmgScale); + } + const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + player.force.x -= 0.0013 * Math.cos(angle) * player.mass * (mech.onGround ? 1.7 : 1); + player.force.y -= 0.0013 * Math.sin(angle) * player.mass; + //draw line to player + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mech.pos.x, mech.pos.y); + ctx.lineWidth = Math.min(60, this.radius * 2); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.stroke(); + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.3)"; + ctx.fill(); + } + this.curl(eventHorizon); + } + } + }, + spiderBoss(x, y, radius = 60 + Math.ceil(Math.random() * 10)) { + const isDaddyLongLegs = Math.random() < 0.25 + let targets = [] //track who is in the node boss, for shields + mobs.spawn(x, y, 6, radius, "#b386e8"); + let me = mob[mob.length - 1]; + me.isBoss = true; + targets.push(me.id) //add to shield protection + me.friction = 0; + me.frictionAir = 0.0065; + me.lookTorque = 0.0000008; //controls spin while looking for player + me.g = 0.00025; //required if using 'gravity' + me.seePlayerFreq = Math.round((30 + 20 * Math.random()) * game.lookFreqScale); + const springStiffness = isDaddyLongLegs ? 0.0001 : 0.000065; + const springDampening = isDaddyLongLegs ? 0 : 0.0006; + + me.springTarget = { + x: me.position.x, + y: me.position.y + }; + const len = cons.length; + cons[len] = Constraint.create({ + pointA: me.springTarget, + bodyB: me, + stiffness: springStiffness, + damping: springDampening + }); + cons[len].length = 100 + 1.5 * radius; + + me.cons = cons[len]; + + me.springTarget2 = { + x: me.position.x, + y: me.position.y + }; + const len2 = cons.length; + cons[len2] = Constraint.create({ + pointA: me.springTarget2, + bodyB: me, + stiffness: springStiffness, + damping: springDampening + }); + cons[len2].length = 100 + 1.5 * radius; + me.cons2 = cons[len2]; + if (isDaddyLongLegs) Matter.Body.setDensity(me, 0.017); //extra dense //normal is 0.001 //makes effective life much larger + + me.onDeath = function() { + this.removeCons(); + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + }; + me.do = function() { + this.gravity(); + this.searchSpring(); + this.checkStatus(); + this.springAttack(); + }; + + radius = 22 // radius of each node mob + const sideLength = 100 // distance between each node mob + const nodes = 6 + const angle = 2 * Math.PI / nodes + + spawn.allowShields = false; //don't want shields on individual boss mobs + + for (let i = 0; i < nodes; ++i) { + spawn.stabber(x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius, 12); + if (isDaddyLongLegs) Matter.Body.setDensity(mob[mob.length - 1], 0.01); //extra dense //normal is 0.001 //makes effective life much larger + targets.push(mob[mob.length - 1].id) //track who is in the node boss, for shields + } + //spawn shield around all nodes + if (!isDaddyLongLegs) spawn.bossShield(targets, x, y, sideLength + 1 * radius + nodes * 5 - 25); + spawn.allowShields = true; + + const attachmentStiffness = isDaddyLongLegs ? 0.0003 : 0.05 + if (!isDaddyLongLegs) spawn.constrain2AdjacentMobs(nodes + 2, attachmentStiffness, true); //loop mobs together + for (let i = 0; i < nodes; ++i) { //attach to center mob + consBB[consBB.length] = Constraint.create({ + bodyA: me, + bodyB: mob[mob.length - i - 1], + stiffness: attachmentStiffness, + damping: 0.01 + }); + World.add(engine.world, consBB[consBB.length - 1]); + } + }, + timeSkipBoss(x, y, radius = 55) { + mobs.spawn(x, y, 6, radius, '#000'); + let me = mob[mob.length - 1]; + me.isBoss = true; + // me.stroke = "transparent"; //used for drawSneaker + me.timeSkipLastCycle = 0 + me.eventHorizon = 1800; //required for black hole + me.seeAtDistance2 = (me.eventHorizon + 2000) * (me.eventHorizon + 2000); //vision limit is event horizon + 2000 + me.accelMag = 0.0004 * game.accelScale; + // me.frictionAir = 0.005; + // me.memory = 1600; + // Matter.Body.setDensity(me, 0.02); //extra dense //normal is 0.001 //makes effective life much larger + Matter.Body.setDensity(me, 0.0015 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger + spawn.shield(me, x, y, 1); + + + me.onDeath = function() { + //applying forces to player doesn't seem to work inside this method, not sure why + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + }; + me.do = function() { + //keep it slow, to stop issues from explosion knock backs + if (this.speed > 8) { + Matter.Body.setVelocity(this, { + x: this.velocity.x * 0.99, + y: this.velocity.y * 0.99 + }); + } + this.seePlayerCheck(); + this.checkStatus(); + this.attraction() + if (!game.isTimeSkipping) { + const compress = 1 + if (this.timeSkipLastCycle < game.cycle - compress && + Vector.magnitude(Vector.sub(this.position, player.position)) < this.eventHorizon) { + this.timeSkipLastCycle = game.cycle + game.timeSkip(compress) + + this.fill = `rgba(0,0,0,${0.4+0.6*Math.random()})` + this.stroke = "#014" + this.isShielded = false; + this.dropPowerUp = true; + this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob; //can't touch bullets + + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.eventHorizon, 0, 2 * Math.PI); + ctx.fillStyle = "#fff"; + ctx.globalCompositeOperation = "destination-in"; //in or atop + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.eventHorizon, 0, 2 * Math.PI); + ctx.clip(); + + // ctx.beginPath(); + // ctx.arc(this.position.x, this.position.y, 9999, 0, 2 * Math.PI); + // ctx.fillStyle = "#000"; + // ctx.fill(); + // ctx.strokeStyle = "#000"; + // ctx.stroke(); + + // ctx.beginPath(); + // ctx.arc(this.position.x, this.position.y, this.eventHorizon, 0, 2 * Math.PI); + // ctx.fillStyle = `rgba(0,0,0,${0.05*Math.random()})`; + // ctx.fill(); + // ctx.strokeStyle = "#000"; + // ctx.stroke(); + } else { + this.isShielded = true; + this.dropPowerUp = false; + this.seePlayer.recall = false + this.fill = "transparent" + this.stroke = "transparent" + this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.mob; //can't touch bullets + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.eventHorizon, 0, 2 * Math.PI); + ctx.fillStyle = `rgba(0,0,0,${0.05*Math.random()})`; + ctx.fill(); + } + } + } + }, + beamer(x, y, radius = 15 + Math.ceil(Math.random() * 15)) { + mobs.spawn(x, y, 4, radius, "rgb(255,0,190)"); + let me = mob[mob.length - 1]; + me.repulsionRange = 73000; //squared + me.laserRange = 370; + me.accelMag = 0.0005 * game.accelScale; + me.frictionStatic = 0; + me.friction = 0; + spawn.shield(me, x, y); + me.do = function() { + this.seePlayerByLookingAt(); + this.checkStatus(); + this.attraction(); + this.repulsion(); + //laser beam + this.laserBeam(); + }; + }, + focuser(x, y, radius = 30 + Math.ceil(Math.random() * 10)) { + radius = Math.ceil(radius * 0.7); + mobs.spawn(x, y, 4, radius, "rgb(0,0,255)"); + let me = mob[mob.length - 1]; + Matter.Body.setDensity(me, 0.003); //extra dense //normal is 0.001 + me.restitution = 0; + me.laserPos = me.position; //required for laserTracking + me.repulsionRange = 1200000; //squared + me.accelMag = 0.00009 * game.accelScale; + me.frictionStatic = 0; + me.friction = 0; + me.onDamage = function() { + this.laserPos = this.position; + }; + spawn.shield(me, x, y); + me.do = function() { + if (!mech.isBodiesAsleep) { + this.seePlayerByLookingAt(); + this.checkStatus(); + this.attraction(); + const dist2 = this.distanceToPlayer2(); + //laser Tracking + if (this.seePlayer.yes && dist2 < 4000000) { + const rangeWidth = 2000; //this is sqrt of 4000000 from above if() + //targeting laser will slowly move from the mob to the player's position + this.laserPos = Vector.add(this.laserPos, Vector.mult(Vector.sub(player.position, this.laserPos), 0.1)); + let targetDist = Vector.magnitude(Vector.sub(this.laserPos, mech.pos)); + const r = 12; + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + if (targetDist < r + 16) { + targetDist = r + 10; + //charge at player + const forceMag = this.accelMag * 30 * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle); + } else { + //high friction if can't lock onto player + // Matter.Body.setVelocity(this, { + // x: this.velocity.x * 0.98, + // y: this.velocity.y * 0.98 + // }); + } + if (dist2 > 80000) { + const laserWidth = 0.002; + let laserOffR = Vector.rotateAbout(this.laserPos, (targetDist - r) * laserWidth, this.position); + let sub = Vector.normalise(Vector.sub(laserOffR, this.position)); + laserOffR = Vector.add(laserOffR, Vector.mult(sub, rangeWidth)); + ctx.lineTo(laserOffR.x, laserOffR.y); + + let laserOffL = Vector.rotateAbout(this.laserPos, (targetDist - r) * -laserWidth, this.position); + sub = Vector.normalise(Vector.sub(laserOffL, this.position)); + laserOffL = Vector.add(laserOffL, Vector.mult(sub, rangeWidth)); + ctx.lineTo(laserOffL.x, laserOffL.y); + ctx.fillStyle = `rgba(0,0,255,${Math.max(0,0.3*r/targetDist)})` + ctx.fill(); + } + } else { + this.laserPos = this.position; + } + }; + } + }, + laserTargetingBoss(x, y, radius = 80) { + const color = "#05f" + mobs.spawn(x, y, 3, radius, color); + let me = mob[mob.length - 1]; + me.isBoss = true; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + Matter.Body.rotate(me, Math.random() * Math.PI * 2); + me.accelMag = 0.0002 * Math.sqrt(game.accelScale); + me.seePlayerFreq = Math.floor(30 * game.lookFreqScale); + me.memory = 420; + me.restitution = 1; + me.frictionAir = 0.01; + me.frictionStatic = 0; + me.friction = 0; + + me.lookTorque = 0.000001 * (Math.random() > 0.5 ? -1 : 1); + + me.fireDir = { + x: 0, + y: 0 + } + Matter.Body.setDensity(me, 0.023); //extra dense //normal is 0.001 //makes effective life much larger + spawn.shield(me, x, y, 1); + me.onHit = function() { + //run this function on hitting player + // this.explode(); + }; + // spawn.shield(me, x, y, 1); //not working, not sure why + me.onDeath = function() { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + }; + me.do = function() { + this.seePlayerByLookingAt(); + this.checkStatus(); + this.attraction(); + + 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) / 1600; //gives the bullet an arc + } + + //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.4; + if (c > threshold) { + this.torque += 0.000004 * this.inertia; + } else if (c < -threshold) { + this.torque -= 0.000004 * this.inertia; + } + // if (Math.abs(c) < 0.3) { + // const mag = 0.05 + // this.force.x += mag * Math.cos(this.angle) + // this.force.y += mag * Math.sin(this.angle) + // } + + const vertexCollision = function(v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + }; + + const seeRange = 8000; + 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.001 * game.dmgScale; + mech.damage(dmg); + //draw damage + ctx.fillStyle = color; + 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.vertices[1].x, this.vertices[1].y); + ctx.lineTo(best.x, best.y); + ctx.strokeStyle = color; + ctx.lineWidth = 3; + ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]); + ctx.stroke(); + ctx.setLineDash([0, 0]); + } + }; + }, + laser(x, y, radius = 30) { + mobs.spawn(x, y, 3, radius, "#f00"); + let me = mob[mob.length - 1]; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + Matter.Body.rotate(me, Math.random() * Math.PI * 2); + me.accelMag = 0.00007 * game.accelScale; + me.onHit = function() { + //run this function on hitting player + this.explode(); + }; + me.do = function() { + this.seePlayerByLookingAt(); + this.checkStatus(); + this.attraction(); + this.laser(); + }; + }, + laserBoss(x, y, radius = 30) { + mobs.spawn(x, y, 3, radius, "#f00"); + let me = mob[mob.length - 1]; + me.isBoss = true; + me.startingPosition = { + x: x, + y: y + } + me.count = 0; + me.frictionAir = 0.03; + // me.torque -= me.inertia * 0.002 + Matter.Body.setDensity(me, 0.03); //extra dense //normal is 0.001 //makes effective life much larger + // spawn.shield(me, x, y, 1); //not working, not sure why + me.onDeath = function() { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + }; + me.rotateVelocity = Math.min(0.0045, 0.0015 * game.accelScale * game.accelScale) * (level.levelsCleared > 8 ? 1 : -1) + me.do = function() { + this.fill = '#' + Math.random().toString(16).substr(-6); //flash colors + this.checkStatus(); + + if (!this.isStunned) { + if (!mech.isBodiesAsleep) { + //check if slowed + let slowed = false + for (let i = 0; i < this.status.length; i++) { + if (this.status[i].type === "slow") { + slowed = true + break + } + } + if (!slowed) { + this.count++ + Matter.Body.setAngle(this, this.count * this.rotateVelocity) + Matter.Body.setAngularVelocity(this, 0) + } + } + ctx.beginPath(); + this.laser(this.vertices[0], this.angle + Math.PI / 3); + this.laser(this.vertices[1], this.angle + Math.PI); + this.laser(this.vertices[2], this.angle - Math.PI / 3); + 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 + } + + + Matter.Body.setVelocity(this, { + x: 0, + y: 0 + }); + Matter.Body.setPosition(this, this.startingPosition); + + }; + me.laser = function(where, angle) { + const vertexCollision = function(v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + }; + + const seeRange = 7000; + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const look = { + x: where.x + seeRange * Math.cos(angle), + y: where.y + seeRange * Math.sin(angle) + }; + // vertexCollision(where, look, mob); + vertexCollision(where, look, map); + vertexCollision(where, look, body); + if (!mech.isCloak) vertexCollision(where, look, [player]); + if (best.who && best.who === player && mech.immuneCycle < mech.cycle) { + mech.immuneCycle = mech.cycle + mod.collisionImmuneCycles; //player is immune to collision damage for 30 cycles + const dmg = 0.14 * game.dmgScale; + mech.damage(dmg); + game.drawList.push({ //add dmg to draw queue + x: best.x, + y: best.y, + radius: dmg * 1500, + color: "rgba(80,0,255,0.5)", + time: 20 + }); + } + //draw beam + if (best.dist2 === Infinity) best = look; + ctx.moveTo(where.x, where.y); + ctx.lineTo(best.x, best.y); + } + }, + stabber(x, y, radius = 25 + Math.ceil(Math.random() * 12), spikeMax = 9) { + if (radius > 80) radius = 65; + mobs.spawn(x, y, 6, radius, "rgb(220,50,205)"); //can't have sides above 6 or collision events don't work (probably because of a convex problem) + let me = mob[mob.length - 1]; + me.isVerticesChange = true + me.accelMag = 0.0006 * game.accelScale; + // me.g = 0.0002; //required if using 'gravity' + me.delay = 360 * game.CDScale; + me.spikeVertex = 0; + me.spikeLength = 0; + me.isSpikeGrowing = false; + me.isSpikeReset = true; + me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.player //can't touch other mobs + Matter.Body.rotate(me, Math.PI * 0.1); + spawn.shield(me, x, y); + // me.onDamage = function () {}; + me.onDeath = function() { + if (this.spikeLength > 4) { + this.spikeLength = 4 + const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[this.spikeVertex], this.position)), this.radius * this.spikeLength) + this.vertices[this.spikeVertex].x = this.position.x + spike.x + this.vertices[this.spikeVertex].y = this.position.y + spike.y + // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) + } + }; + me.do = function() { + if (!mech.isBodiesAsleep) { + // this.gravity(); + this.seePlayerByLookingAt(); + this.checkStatus(); + this.attraction(); + + if (this.isSpikeReset) { + if (this.seePlayer.recall) { + const dist = Vector.sub(this.seePlayer.position, this.position); + const distMag = Vector.magnitude(dist); + if (distMag < this.radius * 7) { + //find nearest vertex + let nearestDistance = Infinity + for (let i = 0, len = this.vertices.length; i < len; i++) { + //find distance to player for each vertex + const dist = Vector.sub(this.seePlayer.position, this.vertices[i]); + const distMag = Vector.magnitude(dist); + //save the closest distance + if (distMag < nearestDistance) { + this.spikeVertex = i + nearestDistance = distMag + } + } + this.spikeLength = 1 + this.isSpikeGrowing = true; + this.isSpikeReset = false; + Matter.Body.setAngularVelocity(this, 0) + } + } + } else { + if (this.isSpikeGrowing) { + this.spikeLength += 1 + if (this.spikeLength > spikeMax) { + this.isSpikeGrowing = false; + } + } else { + + //reduce rotation + Matter.Body.setAngularVelocity(this, this.angularVelocity * 0.8) + + this.spikeLength -= 0.2 + if (this.spikeLength < 1) { + this.spikeLength = 1 + this.isSpikeReset = true + } + } + const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[this.spikeVertex], this.position)), this.radius * this.spikeLength) + this.vertices[this.spikeVertex].x = this.position.x + spike.x + this.vertices[this.spikeVertex].y = this.position.y + spike.y + } + } + }; + }, + striker(x, y, radius = 14 + Math.ceil(Math.random() * 25)) { + mobs.spawn(x, y, 5, radius, "rgb(221,102,119)"); + let me = mob[mob.length - 1]; + me.accelMag = 0.0003 * game.accelScale; + me.g = 0.0002; //required if using 'gravity' + me.frictionStatic = 0; + me.friction = 0; + me.delay = 90 * game.CDScale; + me.cd = Infinity; + Matter.Body.rotate(me, Math.PI * 0.1); + spawn.shield(me, x, y); + me.onDamage = function() { + this.cd = game.cycle + this.delay; + }; + me.do = function() { + this.gravity(); + if (!(game.cycle % this.seePlayerFreq)) { // this.seePlayerCheck(); from mobs + 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(); + if (this.cd === Infinity) this.cd = game.cycle + this.delay * 0.7; + } else if (this.seePlayer.recall) { + this.lostPlayer(); + this.cd = Infinity + } + } + this.checkStatus(); + this.attraction(); + if (this.seePlayer.recall && this.cd < game.cycle) { + const dist = Vector.sub(this.seePlayer.position, this.position); + const distMag = Vector.magnitude(dist); + if (distMag < 400) { + this.cd = game.cycle + this.delay; + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + Matter.Body.translate(this, Vector.mult(Vector.normalise(dist), distMag - 20 - radius)); + ctx.lineTo(this.position.x, this.position.y); + ctx.lineWidth = radius * 2; + ctx.strokeStyle = this.fill; //"rgba(0,0,0,0.5)"; //'#000' + ctx.stroke(); + } + } + }; + }, + sneaker(x, y, radius = 15 + Math.ceil(Math.random() * 25)) { + let me; + mobs.spawn(x, y, 5, radius, "transparent"); + me = mob[mob.length - 1]; + me.accelMag = 0.0007 * game.accelScale; + me.g = 0.0002; //required if using 'gravity' + me.stroke = "transparent"; //used for drawSneaker + me.alpha = 1; //used in drawSneaker + // me.leaveBody = false; + me.canTouchPlayer = false; //used in drawSneaker + me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player + me.showHealthBar = false; + // me.memory = 420; + me.do = function() { + this.gravity(); + this.seePlayerCheck(); + this.checkStatus(); + this.attraction(); + //draw + if (!mech.isBodiesAsleep) { + if (this.seePlayer.yes) { + if (this.alpha < 1) this.alpha += 0.01; + } else { + if (this.alpha > 0) this.alpha -= 0.03; + } + } + if (this.alpha > 0) { + if (this.alpha > 0.95) { + this.healthBar(); + if (!this.canTouchPlayer) { + this.canTouchPlayer = true; + this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob; //can touch player + } + } + //draw body + ctx.beginPath(); + const vertices = this.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1, len = vertices.length; j < len; ++j) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.fillStyle = `rgba(0,0,0,${this.alpha * this.alpha})`; + ctx.fill(); + } else if (this.canTouchPlayer) { + this.canTouchPlayer = false; + this.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player + } + }; + }, + ghoster(x, y, radius = 40 + Math.ceil(Math.random() * 100)) { + let me; + mobs.spawn(x, y, 7, radius, "transparent"); + me = mob[mob.length - 1]; + me.seeAtDistance2 = 300000; + me.accelMag = 0.00012 * game.accelScale; + if (map.length) me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search + Matter.Body.setDensity(me, 0.00065); //normal is 0.001 //makes effective life much lower + me.stroke = "transparent"; //used for drawGhost + me.alpha = 1; //used in drawGhost + me.canTouchPlayer = false; //used in drawGhost + // me.leaveBody = false; + me.collisionFilter.mask = cat.bullet + me.showHealthBar = false; + me.memory = 480; + me.do = function() { + //cap max speed + if (this.speed > 5) { + Matter.Body.setVelocity(this, { + x: this.velocity.x * 0.8, + y: this.velocity.y * 0.8 + }); + } + this.seePlayerCheckByDistance(); + this.checkStatus(); + this.attraction(); + this.search(); + //draw + if (this.distanceToPlayer2() - this.seeAtDistance2 < 0) { + if (this.alpha < 1) this.alpha += 0.004; + } else { + if (this.alpha > 0) this.alpha -= 0.03; + } + if (this.alpha > 0) { + if (this.alpha > 0.9 && this.seePlayer.recall) { + this.healthBar(); + if (!this.canTouchPlayer) { + this.canTouchPlayer = true; + this.collisionFilter.mask = cat.player | cat.bullet + } + } + //draw body + ctx.beginPath(); + const vertices = this.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1, len = vertices.length; j < len; ++j) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.lineWidth = 1; + ctx.strokeStyle = `rgba(0,0,0,${this.alpha * this.alpha})`; + ctx.stroke(); + } else if (this.canTouchPlayer) { + this.canTouchPlayer = false; + this.collisionFilter.mask = cat.bullet; //can't touch player or walls + } + }; + }, + // blinker(x, y, radius = 45 + Math.ceil(Math.random() * 70)) { + // mobs.spawn(x, y, 6, radius, "transparent"); + // let me = mob[mob.length - 1]; + // Matter.Body.setDensity(me, 0.0005); //normal is 0.001 //makes effective life much lower + // me.stroke = "rgb(0,200,255)"; //used for drawGhost + // Matter.Body.rotate(me, Math.random() * 2 * Math.PI); + // me.blinkRate = 40 + Math.round(Math.random() * 60); //required for blink + // me.blinkLength = 150 + Math.round(Math.random() * 200); //required for blink + // me.isStatic = true; + // me.memory = 360; + // me.seePlayerFreq = Math.round((40 + 30 * Math.random()) * game.lookFreqScale); + // // me.isBig = false; + // // me.scaleMag = Math.max(5 - me.mass, 1.75); + // me.onDeath = function () { + // // if (this.isBig) { + // // Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag); + // // this.isBig = false; + // // } + // }; + // me.onHit = function () { + // game.timeSkip(120) + // }; + // me.do = function () { + // this.seePlayerCheck(); + // this.blink(); + // //strike by expanding + // // if (this.isBig) { + // // if (this.cd - this.delay + 15 < game.cycle) { + // // Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag); + // // this.isBig = false; + // // } + // // } else + // if (this.seePlayer.yes && this.cd < game.cycle) { + // const dist = Vector.sub(this.seePlayer.position, this.position); + // const distMag2 = Vector.magnitudeSquared(dist); + // if (distMag2 < 80000) { + // this.cd = game.cycle + this.delay; + + // // Matter.Body.scale(this, this.scaleMag, this.scaleMag); + // // this.isBig = true; + // } + // } + // }; + // }, + bomberBoss(x, y, radius = 88) { + //boss that drops bombs from above and holds a set distance from player + mobs.spawn(x, y, 3, radius, "transparent"); + let me = mob[mob.length - 1]; + me.isBoss = true; + Matter.Body.setDensity(me, 0.0014 + 0.0003 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger + + me.stroke = "rgba(255,0,200)"; //used for drawGhost + me.seeAtDistance2 = 1500000; + me.fireFreq = Math.floor(120 * game.CDScale); + me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search + me.hoverElevation = 460 + (Math.random() - 0.5) * 200; //squared + me.hoverXOff = (Math.random() - 0.5) * 100; + me.accelMag = Math.floor(10 * (Math.random() + 4.5)) * 0.00001 * game.accelScale; + me.g = 0.0002; //required if using 'gravity' // gravity called in hoverOverPlayer + me.frictionStatic = 0; + me.friction = 0; + me.frictionAir = 0.01; + // me.memory = 300; + // Matter.Body.setDensity(me, 0.0015); //extra dense //normal is 0.001 + me.collisionFilter.mask = cat.player | cat.bullet + spawn.shield(me, x, y, 1); + me.onDeath = function() { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + }; + me.do = function() { + this.seePlayerCheckByDistance(); + this.checkStatus(); + if (this.seePlayer.recall) { + this.hoverOverPlayer(); + this.bomb(); + this.search(); + } + }; + }, + shooter(x, y, radius = 25 + Math.ceil(Math.random() * 50)) { + mobs.spawn(x, y, 3, radius, "rgb(255,100,150)"); + let me = mob[mob.length - 1]; + // me.vertices = Matter.Vertices.clockwiseSort(Matter.Vertices.rotate(me.vertices, Math.PI, me.position)); //make the pointy side of triangle the front + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + me.isVerticesChange = true + // Matter.Body.rotate(me, Math.PI) + + me.memory = 120; + me.fireFreq = 0.007 + Math.random() * 0.005; + me.noseLength = 0; + me.fireAngle = 0; + me.accelMag = 0.0005 * game.accelScale; + me.frictionStatic = 0; + me.friction = 0; + me.frictionAir = 0.05; + me.lookTorque = 0.0000025 * (Math.random() > 0.5 ? -1 : 1); + me.fireDir = { + x: 0, + y: 0 + }; + me.onDeath = function() { //helps collisions functions work better after vertex have been changed + // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) + } + // spawn.shield(me, x, y); + me.do = function() { + this.seePlayerByLookingAt(); + this.checkStatus(); + this.fire(); + }; + }, + shooterBoss(x, y, radius = 110) { + mobs.spawn(x, y, 3, radius, "rgb(255,70,180)"); + let me = mob[mob.length - 1]; + me.isBoss = true; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + me.isVerticesChange = true + me.memory = 240; + me.homePosition = { + x: x, + y: y + }; + me.fireFreq = 0.025; + me.noseLength = 0; + me.fireAngle = 0; + me.accelMag = 0.005 * game.accelScale; + me.frictionAir = 0.05; + me.lookTorque = 0.000007 * (Math.random() > 0.5 ? -1 : 1); + me.fireDir = { + x: 0, + y: 0 + }; + Matter.Body.setDensity(me, 0.03 + 0.0008 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger + me.onDeath = function() { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) //helps collisions functions work better after vertex have been changed + }; + + me.do = function() { + this.seePlayerByLookingAt(); + this.checkStatus(); + this.fire(); + //gently return to starting location + const sub = Vector.sub(this.homePosition, this.position) + const dist = Vector.magnitude(sub) + if (dist > 50) this.force = Vector.mult(Vector.normalise(sub), this.mass * 0.0002) + }; + }, + bullet(x, y, radius = 6, sides = 0) { + //bullets + mobs.spawn(x, y, sides, radius, "rgb(255,0,0)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + me.onHit = function() { + this.explode(this.mass * 20); + }; + Matter.Body.setDensity(me, 0.00005); //normal is 0.001 + me.timeLeft = 200; + me.g = 0.001; //required if using 'gravity' + me.frictionAir = 0; + me.restitution = 0.8; + me.leaveBody = false; + me.dropPowerUp = false; + me.showHealthBar = false; + me.collisionFilter.category = cat.mobBullet; + me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet; + me.do = function() { + this.gravity(); + this.timeLimit(); + }; + }, + bomb(x, y, radius = 6, sides = 5) { + mobs.spawn(x, y, sides, radius, "rgb(255,0,0)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + me.onHit = function() { + this.explode(this.mass * 120); + }; + me.onDeath = function() { + if (game.difficulty > 4) { + spawn.bullet(this.position.x, this.position.y, this.radius / 3, 5); + spawn.bullet(this.position.x, this.position.y, this.radius / 3, 5); + spawn.bullet(this.position.x, this.position.y, this.radius / 3, 5); + const mag = 8 + const v1 = Vector.rotate({ + x: 1, + y: 1 + }, 2 * Math.PI * Math.random()) + const v2 = Vector.rotate({ + x: 1, + y: 1 + }, 2 * Math.PI * Math.random()) + const v3 = Vector.normalise(Vector.add(v1, v2)) //last vector is opposite the sum of the other two to look a bit like momentum is conserved + + Matter.Body.setVelocity(mob[mob.length - 1], { + x: mag * v1.x, + y: mag * v1.y + }); + Matter.Body.setVelocity(mob[mob.length - 2], { + x: mag * v2.x, + y: mag * v2.y + }); + Matter.Body.setVelocity(mob[mob.length - 3], { + x: -mag * v3.x, + y: -mag * v3.y + }); + } + } + Matter.Body.setDensity(me, 0.00005); //normal is 0.001 + me.timeLeft = 140 + Math.floor(Math.random() * 30); + me.g = 0.001; //required if using 'gravity' + me.frictionAir = 0; + me.restitution = 1; + me.leaveBody = false; + me.dropPowerUp = false; + me.showHealthBar = false; + me.collisionFilter.category = cat.mobBullet; + me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet; + me.do = function() { + this.gravity(); + this.timeLimit(); + }; + }, + sniper(x, y, radius = 35 + Math.ceil(Math.random() * 30)) { + mobs.spawn(x, y, 3, radius, "transparent"); //"rgb(25,0,50)") + let me = mob[mob.length - 1]; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + me.isVerticesChange = true + // Matter.Body.rotate(me, Math.PI) + me.stroke = "transparent"; //used for drawSneaker + me.alpha = 1; //used in drawSneaker + me.showHealthBar = false; + me.frictionStatic = 0; + me.friction = 0; + me.canTouchPlayer = false; //used in drawSneaker + me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player + + me.memory = 60 //140; + me.fireFreq = 0.006 + Math.random() * 0.002; + me.noseLength = 0; + me.fireAngle = 0; + me.accelMag = 0.0005 * game.accelScale; + me.frictionAir = 0.05; + me.torque = 0.0001 * me.inertia; + me.fireDir = { + x: 0, + y: 0 + }; + me.onDeath = function() { //helps collisions functions work better after vertex have been changed + // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) + } + // spawn.shield(me, x, y); + me.do = function() { + // this.seePlayerByLookingAt(); + this.seePlayerCheck(); + this.checkStatus(); + + 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) / 1600; //gives the bullet an arc + } + //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.2; + 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.sniperBullet(this.vertices[1].x, this.vertices[1].y, 5 + Math.ceil(this.radius / 15), 4); + const v = 20 * game.accelScale; + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + this.fireDir.x * v + Math.random(), + y: this.velocity.y + this.fireDir.y * v + Math.random() + }); + this.noseLength = 0; + // recoil + this.force.x -= 0.005 * this.fireDir.x * this.mass; + this.force.y -= 0.005 * this.fireDir.y * this.mass; + } + if (this.noseLength < 1.5) this.noseLength += this.fireFreq; + setNoseShape(); + } else if (this.noseLength > 0.1) { + this.noseLength -= this.fireFreq / 2; + setNoseShape(); + } + // else if (this.noseLength < -0.1) { + // this.noseLength += this.fireFreq / 4; + // setNoseShape(); + // } + + if (this.seePlayer.recall) { + if (this.alpha < 1) this.alpha += 0.01; + } else { + if (this.alpha > 0) this.alpha -= 0.03; + } + } + //draw + if (this.alpha > 0) { + if (this.alpha > 0.95) { + this.healthBar(); + if (!this.canTouchPlayer) { + this.canTouchPlayer = true; + this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob; //can touch player + } + } + //draw body + ctx.beginPath(); + const vertices = this.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1, len = vertices.length; j < len; ++j) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.fillStyle = `rgba(25,0,50,${this.alpha * this.alpha})`; + ctx.fill(); + } else if (this.canTouchPlayer) { + this.canTouchPlayer = false; + this.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player + } + }; + }, + sniperBullet(x, y, radius = 6, sides = 4) { + //bullets + mobs.spawn(x, y, sides, radius, "rgb(255,0,155)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + me.onHit = function() { + this.explode(this.mass * 20); + }; + Matter.Body.setDensity(me, 0.00005); //normal is 0.001 + me.timeLeft = 240; + me.g = 0.001; //required if using 'gravity' + me.frictionAir = 0; + me.restitution = 0; + me.leaveBody = false; + me.dropPowerUp = false; + me.showHealthBar = false; + me.collisionFilter.category = cat.mobBullet; + me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet; + me.do = function() { + this.timeLimit(); + if (Matter.Query.collides(this, map).length > 0 || Matter.Query.collides(this, body).length > 0 && this.speed < 3) { + this.dropPowerUp = false; + this.death(); //death with no power up + } + }; + }, + launcher(x, y, radius = 30 + Math.ceil(Math.random() * 40)) { + mobs.spawn(x, y, 3, radius, "rgb(150,150,255)"); + let me = mob[mob.length - 1]; + me.accelMag = 0.00004 * game.accelScale; + me.fireFreq = Math.floor(420 + 90 * Math.random() * game.CDScale) + me.frictionStatic = 0; + me.friction = 0; + me.frictionAir = 0.02; + spawn.shield(me, x, y); + me.onDamage = function() {}; + me.do = function() { + this.seePlayerCheck(); + this.checkStatus(); + this.attraction(); + if (this.seePlayer.recall && !(game.cycle % this.fireFreq) && !mech.isBodiesAsleep) { + Matter.Body.setAngularVelocity(this, 0.14) + //fire a bullet from each vertex + for (let i = 0, len = this.vertices.length; i < len; i++) { + spawn.seeker(this.vertices[i].x, this.vertices[i].y, 6) + //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]))), -8) + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + velocity.x, + y: this.velocity.y + velocity.y + }); + } + } + }; + }, + launcherBoss(x, y, radius = 85) { + mobs.spawn(x, y, 6, radius, "rgb(150,150,255)"); + let me = mob[mob.length - 1]; + me.isBoss = true; + me.accelMag = 0.00008 * game.accelScale; + me.fireFreq = Math.floor(360 * game.CDScale) + me.frictionStatic = 0; + me.friction = 0; + me.frictionAir = 0.02; + me.memory = 420 * game.CDScale; + me.repulsionRange = 1200000; //squared + spawn.shield(me, x, y, 1); + Matter.Body.setDensity(me, 0.004 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger + me.onDeath = function() { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) //helps collisions functions work better after vertex have been changed + }; + me.onDamage = function() {}; + me.do = function() { + this.seePlayerCheck(); + this.checkStatus(); + this.attraction(); + this.repulsion(); + if (this.seePlayer.recall && !(game.cycle % this.fireFreq) && !mech.isBodiesAsleep) { + Matter.Body.setAngularVelocity(this, 0.11) + //fire a bullet from each vertex + for (let i = 0, len = this.vertices.length; i < len; i++) { + spawn.seeker(this.vertices[i].x, this.vertices[i].y, 7) + //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]))), -10) + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + velocity.x, + y: this.velocity.y + velocity.y + }); + } + } + }; + }, + seeker(x, y, radius = 5, sides = 0) { + //bullets + mobs.spawn(x, y, sides, radius, "rgb(150,150,255)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + me.onHit = function() { + this.explode(this.mass * 20); + }; + Matter.Body.setDensity(me, 0.00002); //normal is 0.001 + me.timeLeft = 420 * (0.8 + 0.4 * Math.random()); + me.accelMag = 0.00017 * (0.8 + 0.4 * Math.random()) * game.accelScale; + me.frictionAir = 0.01 * (0.8 + 0.4 * Math.random()); + me.restitution = 0.5; + me.leaveBody = false; + me.dropPowerUp = false; + me.showHealthBar = false; + me.collisionFilter.category = cat.mobBullet; + me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet; + me.do = function() { + // this.seePlayer.yes = false; + this.alwaysSeePlayer() + this.attraction(); + this.timeLimit(); + }; + }, + spawner(x, y, radius = 55 + Math.ceil(Math.random() * 50)) { + mobs.spawn(x, y, 4, radius, "rgb(255,150,0)"); + let me = mob[mob.length - 1]; + me.g = 0.0004; //required if using 'gravity' + me.leaveBody = false; + // me.dropPowerUp = false; + me.onDeath = function() { //run this function on death + for (let i = 0; i < Math.ceil(this.mass * 0.15 + Math.random() * 2.5); ++i) { + spawn.spawns(this.position.x + (Math.random() - 0.5) * radius * 2.5, this.position.y + (Math.random() - 0.5) * radius * 2.5); + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + (Math.random() - 0.5) * 15, + y: this.velocity.x + (Math.random() - 0.5) * 15 + }); + } + }; + spawn.shield(me, x, y); + me.do = function() { + this.gravity(); + this.seePlayerCheck(); + this.checkStatus(); + this.attraction(); + }; + }, + spawns(x, y, radius = 15 + Math.ceil(Math.random() * 5)) { + mobs.spawn(x, y, 4, radius, "rgb(255,0,0)"); + let me = mob[mob.length - 1]; + me.onHit = function() { + //run this function on hitting player + this.explode(); + }; + me.g = 0.0001; //required if using 'gravity' + me.accelMag = 0.0003 * game.accelScale; + me.memory = 30; + me.leaveBody = false; + me.seePlayerFreq = Math.round((80 + 50 * Math.random()) * game.lookFreqScale); + me.frictionAir = 0.002; + me.do = function() { + this.gravity(); + this.seePlayerCheck(); + this.checkStatus(); + this.attraction(); + }; + }, + exploder(x, y, radius = 40 + Math.ceil(Math.random() * 50)) { + mobs.spawn(x, y, 4, radius, "rgb(255,0,0)"); + let me = mob[mob.length - 1]; + me.onHit = function() { + //run this function on hitting player + this.explode(); + }; + me.g = 0.0004; //required if using 'gravity' + me.do = function() { + this.gravity(); + this.seePlayerCheck(); + this.checkStatus(); + this.attraction(); + }; + }, + snakeBoss(x, y, radius = 75) { //snake boss with a laser head + mobs.spawn(x, y, 8, radius, "rgb(255,50,130)"); + let me = mob[mob.length - 1]; + me.isBoss = true; + me.accelMag = 0.0011 * game.accelScale; + me.memory = 200; + me.laserRange = 500; + Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger + spawn.shield(me, x, y, 1); + me.onDeath = function() { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + }; + me.do = function() { + this.seePlayerCheck(); + this.checkStatus(); + this.attraction(); + this.laserBeam(); + }; + + //snake tail + const nodes = Math.min(3 + Math.ceil(Math.random() * game.difficulty + 2), 8) + spawn.lineBoss(x + 105, y, "spawns", nodes); + //constraint boss with first 3 mobs in lineboss + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - nodes], + bodyB: mob[mob.length - 1 - nodes], + stiffness: 0.05 + }); + World.add(engine.world, consBB[consBB.length - 1]); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - nodes + 1], + bodyB: mob[mob.length - 1 - nodes], + stiffness: 0.05 + }); + World.add(engine.world, consBB[consBB.length - 1]); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - nodes + 2], + bodyB: mob[mob.length - 1 - nodes], + stiffness: 0.05 + }); + World.add(engine.world, consBB[consBB.length - 1]); + + }, + tetherBoss(x, y, radius = 90) { + // constrained mob boss for the towers level + // often has a ring of mobs around it + mobs.spawn(x, y, 8, radius, "rgb(0,60,80)"); + let me = mob[mob.length - 1]; + me.isBoss = true; + me.g = 0.0001; //required if using 'gravity' + me.accelMag = 0.002 * game.accelScale; + me.memory = 20; + Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger + spawn.shield(me, x, y, 1); + me.onDeath = function() { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + this.removeCons(); //remove constraint + }; + me.do = function() { + this.gravity(); + this.seePlayerCheck(); + this.checkStatus(); + this.attraction(); + }; + }, + shield(target, x, y, chance = Math.min(0.02 + game.difficulty * 0.005, 0.2)) { + if (this.allowShields && Math.random() < chance) { + mobs.spawn(x, y, 9, target.radius + 30, "rgba(220,220,255,0.9)"); + let me = mob[mob.length - 1]; + me.stroke = "rgb(220,220,255)"; + Matter.Body.setDensity(me, 0.00001) //very low density to not mess with the original mob's motion + me.shield = true; + me.collisionFilter.category = cat.mobShield + me.collisionFilter.mask = cat.bullet; + consBB[consBB.length] = Constraint.create({ + bodyA: me, + bodyB: target, //attach shield to target + stiffness: 0.4, + damping: 0.1 + }); + World.add(engine.world, consBB[consBB.length - 1]); + + me.onDamage = function() { + //make sure the mob that owns the shield can tell when damage is done + this.alertNearByMobs(); + this.fill = `rgba(220,220,255,${0.3 + 0.6 *this.health})` + }; + me.leaveBody = false; + me.dropPowerUp = false; + me.showHealthBar = false; + + me.shieldTargetID = target.id + target.isShielded = true; + me.onDeath = function() { + //clear isShielded status from target + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].id === this.shieldTargetID) mob[i].isShielded = false; + } + }; + //swap order of shield and mob, so that mob is behind shield graphically + mob[mob.length - 1] = mob[mob.length - 2]; + mob[mob.length - 2] = me; + me.do = function() { + this.checkStatus(); + }; + } + }, + bossShield(targets, x, y, radius, stiffness = 0.4) { + const nodes = targets.length + mobs.spawn(x, y, 9, radius, "rgba(220,220,255,0.9)"); + let me = mob[mob.length - 1]; + me.stroke = "rgb(220,220,255)"; + Matter.Body.setDensity(me, 0.00001) //very low density to not mess with the original mob's motion + me.frictionAir = 0; + me.shield = true; + me.collisionFilter.category = cat.mobShield + me.collisionFilter.mask = cat.bullet; + for (let i = 0; i < nodes; ++i) { + mob[mob.length - i - 2].isShielded = true; + //constrain to all mob nodes in boss + consBB[consBB.length] = Constraint.create({ + bodyA: me, + bodyB: mob[mob.length - i - 2], + stiffness: stiffness, + damping: 0.1 + }); + World.add(engine.world, consBB[consBB.length - 1]); + } + me.onDamage = function() { + this.alertNearByMobs(); //makes sure the mob that owns the shield can tell when damage is done + this.fill = `rgba(220,220,255,${0.3 + 0.6 *this.health})` + }; + me.onDeath = function() { + //clear isShielded status from target + for (let j = 0; j < targets.length; j++) { + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].id === targets[j]) mob[i].isShielded = false; + } + } + }; + me.leaveBody = false; + me.dropPowerUp = false; + me.showHealthBar = false; + mob[mob.length - 1] = mob[mob.length - 1 - nodes]; + mob[mob.length - 1 - nodes] = me; + me.do = function() { + this.checkStatus(); + }; + }, + //fan made mobs ***************************************************************************************** + //******************************************************************************************************* + //complex constrained mob templates********************************************************************** + //******************************************************************************************************* + allowShields: true, + nodeBoss( + x, + y, + spawn = "striker", + nodes = Math.min(2 + Math.ceil(Math.random() * (game.difficulty + 2)), 8), + //Math.ceil(Math.random() * 3) + Math.min(4,Math.ceil(game.difficulty/2)), + radius = Math.ceil(Math.random() * 10) + 17, // radius of each node mob + sideLength = Math.ceil(Math.random() * 100) + 70, // distance between each node mob + stiffness = Math.random() * 0.03 + 0.005 + ) { + this.allowShields = false; //don't want shields on individual boss mobs + const angle = 2 * Math.PI / nodes + let targets = [] + for (let i = 0; i < nodes; ++i) { + let whoSpawn = spawn; + if (spawn === "random") { + whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)]; + } else if (spawn === "randomList") { + whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + } + this[whoSpawn](x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius); + targets.push(mob[mob.length - 1].id) //track who is in the node boss, for shields + } + if (Math.random() < 0.3) { + this.constrain2AdjacentMobs(nodes, stiffness * 2, true); } else { - if (this.alpha > 0) this.alpha -= 0.03; + this.constrainAllMobCombos(nodes, stiffness); } - } - //draw - if (this.alpha > 0) { - if (this.alpha > 0.95) { - this.healthBar(); - if (!this.canTouchPlayer) { - this.canTouchPlayer = true; - this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob; //can touch player - } + //spawn shield for entire boss + if (nodes > 2 && Math.random() < 0.998) { + this.bossShield(targets, x, y, sideLength + 2.5 * radius + nodes * 6 - 25); } - //draw body - ctx.beginPath(); - const vertices = this.vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1, len = vertices.length; j < len; ++j) { - ctx.lineTo(vertices[j].x, vertices[j].y); + this.allowShields = true; + }, + lineBoss( + x, + y, + spawn = "striker", + nodes = Math.min(3 + Math.ceil(Math.random() * game.difficulty + 2), 8), + //Math.ceil(Math.random() * 3) + Math.min(4,Math.ceil(game.difficulty/2)), + radius = Math.ceil(Math.random() * 10) + 17, + l = Math.ceil(Math.random() * 80) + 30, + stiffness = Math.random() * 0.06 + 0.01 + ) { + this.allowShields = false; //don't want shields on individual boss mobs + for (let i = 0; i < nodes; ++i) { + let whoSpawn = spawn; + if (spawn === "random") { + whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)]; + } else if (spawn === "randomList") { + whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + } + this[whoSpawn](x + i * radius + i * l, y, radius); } - ctx.lineTo(vertices[0].x, vertices[0].y); - ctx.fillStyle = `rgba(25,0,50,${this.alpha * this.alpha})`; - ctx.fill(); - } else if (this.canTouchPlayer) { - this.canTouchPlayer = false; - this.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player - } - }; - }, - sniperBullet(x, y, radius = 6, sides = 4) { - //bullets - mobs.spawn(x, y, sides, radius, "rgb(255,0,155)"); - let me = mob[mob.length - 1]; - me.stroke = "transparent"; - me.onHit = function () { - this.explode(this.mass * 20); - }; - Matter.Body.setDensity(me, 0.00005); //normal is 0.001 - me.timeLeft = 240; - me.g = 0.001; //required if using 'gravity' - me.frictionAir = 0; - me.restitution = 0; - me.leaveBody = false; - me.dropPowerUp = false; - me.showHealthBar = false; - me.collisionFilter.category = cat.mobBullet; - me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet; - me.do = function () { - this.timeLimit(); - if (Matter.Query.collides(this, map).length > 0 || Matter.Query.collides(this, body).length > 0 && this.speed < 3) { - this.dropPowerUp = false; - this.death(); //death with no power up - } - }; - }, - launcher(x, y, radius = 30 + Math.ceil(Math.random() * 40)) { - mobs.spawn(x, y, 3, radius, "rgb(150,150,255)"); - let me = mob[mob.length - 1]; - me.accelMag = 0.00004 * game.accelScale; - me.fireFreq = Math.floor(420 + 90 * Math.random() * game.CDScale) - me.frictionStatic = 0; - me.friction = 0; - me.frictionAir = 0.02; - spawn.shield(me, x, y); - me.onDamage = function () {}; - me.do = function () { - this.seePlayerCheck(); - this.checkStatus(); - this.attraction(); - if (this.seePlayer.recall && !(game.cycle % this.fireFreq) && !mech.isBodiesAsleep) { - Matter.Body.setAngularVelocity(this, 0.14) - //fire a bullet from each vertex - for (let i = 0, len = this.vertices.length; i < len; i++) { - spawn.seeker(this.vertices[i].x, this.vertices[i].y, 6) - //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]))), -8) - Matter.Body.setVelocity(mob[mob.length - 1], { - x: this.velocity.x + velocity.x, - y: this.velocity.y + velocity.y - }); + this.constrain2AdjacentMobs(nodes, stiffness); + this.allowShields = true; + }, + //constraints ************************************************************************************************ + //************************************************************************************************************* + constrainAllMobCombos(nodes, stiffness) { + //runs through every combination of last 'num' bodies and constrains them + for (let i = 1; i < nodes + 1; ++i) { + for (let j = i + 1; j < nodes + 1; ++j) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - i], + bodyB: mob[mob.length - j], + stiffness: stiffness + }); + World.add(engine.world, consBB[consBB.length - 1]); + } } - } - }; - }, - launcherBoss(x, y, radius = 85) { - mobs.spawn(x, y, 6, radius, "rgb(150,150,255)"); - let me = mob[mob.length - 1]; - me.isBoss = true; - me.accelMag = 0.00008 * game.accelScale; - me.fireFreq = Math.floor(360 * game.CDScale) - me.frictionStatic = 0; - me.friction = 0; - me.frictionAir = 0.02; - me.memory = 420 * game.CDScale; - me.repulsionRange = 1200000; //squared - spawn.shield(me, x, y, 1); - Matter.Body.setDensity(me, 0.004 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger - me.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) //helps collisions functions work better after vertex have been changed - }; - me.onDamage = function () {}; - me.do = function () { - this.seePlayerCheck(); - this.checkStatus(); - this.attraction(); - this.repulsion(); - if (this.seePlayer.recall && !(game.cycle % this.fireFreq) && !mech.isBodiesAsleep) { - Matter.Body.setAngularVelocity(this, 0.11) - //fire a bullet from each vertex - for (let i = 0, len = this.vertices.length; i < len; i++) { - spawn.seeker(this.vertices[i].x, this.vertices[i].y, 7) - //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]))), -10) - Matter.Body.setVelocity(mob[mob.length - 1], { - x: this.velocity.x + velocity.x, - y: this.velocity.y + velocity.y - }); + }, + constrain2AdjacentMobs(nodes, stiffness, loop = false) { + //runs through every combination of last 'num' bodies and constrains them + for (let i = 0; i < nodes - 1; ++i) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - i - 1], + bodyB: mob[mob.length - i - 2], + stiffness: stiffness + }); + World.add(engine.world, consBB[consBB.length - 1]); } - } - }; - }, - seeker(x, y, radius = 5, sides = 0) { - //bullets - mobs.spawn(x, y, sides, radius, "rgb(150,150,255)"); - let me = mob[mob.length - 1]; - me.stroke = "transparent"; - me.onHit = function () { - this.explode(this.mass * 20); - }; - Matter.Body.setDensity(me, 0.00002); //normal is 0.001 - me.timeLeft = 420 * (0.8 + 0.4 * Math.random()); - me.accelMag = 0.00017 * (0.8 + 0.4 * Math.random()) * game.accelScale; - me.frictionAir = 0.01 * (0.8 + 0.4 * Math.random()); - me.restitution = 0.5; - me.leaveBody = false; - me.dropPowerUp = false; - me.showHealthBar = false; - me.collisionFilter.category = cat.mobBullet; - me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet; - me.do = function () { - // this.seePlayer.yes = false; - this.alwaysSeePlayer() - this.attraction(); - this.timeLimit(); - }; - }, - spawner(x, y, radius = 55 + Math.ceil(Math.random() * 50)) { - mobs.spawn(x, y, 4, radius, "rgb(255,150,0)"); - let me = mob[mob.length - 1]; - me.g = 0.0004; //required if using 'gravity' - me.leaveBody = false; - // me.dropPowerUp = false; - me.onDeath = function () { //run this function on death - for (let i = 0; i < Math.ceil(this.mass * 0.15 + Math.random() * 2.5); ++i) { - spawn.spawns(this.position.x + (Math.random() - 0.5) * radius * 2.5, this.position.y + (Math.random() - 0.5) * radius * 2.5); - Matter.Body.setVelocity(mob[mob.length - 1], { - x: this.velocity.x + (Math.random() - 0.5) * 15, - y: this.velocity.x + (Math.random() - 0.5) * 15 + if (nodes > 2) { + for (let i = 0; i < nodes - 2; ++i) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - i - 1], + bodyB: mob[mob.length - i - 3], + stiffness: stiffness + }); + World.add(engine.world, consBB[consBB.length - 1]); + } + } + //optional connect the tail to head + if (loop && nodes > 3) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - 1], + bodyB: mob[mob.length - nodes], + stiffness: stiffness + }); + World.add(engine.world, consBB[consBB.length - 1]); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - 2], + bodyB: mob[mob.length - nodes], + stiffness: stiffness + }); + World.add(engine.world, consBB[consBB.length - 1]); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - 1], + bodyB: mob[mob.length - nodes + 1], + stiffness: stiffness + }); + World.add(engine.world, consBB[consBB.length - 1]); + } + }, + constraintPB(x, y, bodyIndex, stiffness) { + cons[cons.length] = Constraint.create({ + pointA: { + x: x, + y: y + }, + bodyB: body[bodyIndex], + stiffness: stiffness }); - } - }; - spawn.shield(me, x, y); - me.do = function () { - this.gravity(); - this.seePlayerCheck(); - this.checkStatus(); - this.attraction(); - }; - }, - spawns(x, y, radius = 15 + Math.ceil(Math.random() * 5)) { - mobs.spawn(x, y, 4, radius, "rgb(255,0,0)"); - let me = mob[mob.length - 1]; - me.onHit = function () { - //run this function on hitting player - this.explode(); - }; - me.g = 0.0001; //required if using 'gravity' - me.accelMag = 0.0003 * game.accelScale; - me.memory = 30; - me.leaveBody = false; - me.seePlayerFreq = Math.round((80 + 50 * Math.random()) * game.lookFreqScale); - me.frictionAir = 0.002; - me.do = function () { - this.gravity(); - this.seePlayerCheck(); - this.checkStatus(); - this.attraction(); - }; - }, - exploder(x, y, radius = 40 + Math.ceil(Math.random() * 50)) { - mobs.spawn(x, y, 4, radius, "rgb(255,0,0)"); - let me = mob[mob.length - 1]; - me.onHit = function () { - //run this function on hitting player - this.explode(); - }; - me.g = 0.0004; //required if using 'gravity' - me.do = function () { - this.gravity(); - this.seePlayerCheck(); - this.checkStatus(); - this.attraction(); - }; - }, - snakeBoss(x, y, radius = 75) { //snake boss with a laser head - mobs.spawn(x, y, 8, radius, "rgb(255,50,130)"); - let me = mob[mob.length - 1]; - me.isBoss = true; - me.accelMag = 0.0011 * game.accelScale; - me.memory = 200; - me.laserRange = 500; - Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger - spawn.shield(me, x, y, 1); - me.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - }; - me.do = function () { - this.seePlayerCheck(); - this.checkStatus(); - this.attraction(); - this.laserBeam(); - }; - - //snake tail - const nodes = Math.min(3 + Math.ceil(Math.random() * game.difficulty + 2), 8) - spawn.lineBoss(x + 105, y, "spawns", nodes); - //constraint boss with first 3 mobs in lineboss - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - nodes], - bodyB: mob[mob.length - 1 - nodes], - stiffness: 0.05 - }); - World.add(engine.world, consBB[consBB.length - 1]); - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - nodes + 1], - bodyB: mob[mob.length - 1 - nodes], - stiffness: 0.05 - }); - World.add(engine.world, consBB[consBB.length - 1]); - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - nodes + 2], - bodyB: mob[mob.length - 1 - nodes], - stiffness: 0.05 - }); - World.add(engine.world, consBB[consBB.length - 1]); - - }, - tetherBoss(x, y, radius = 90) { - // constrained mob boss for the towers level - // often has a ring of mobs around it - mobs.spawn(x, y, 8, radius, "rgb(0,60,80)"); - let me = mob[mob.length - 1]; - me.isBoss = true; - me.g = 0.0001; //required if using 'gravity' - me.accelMag = 0.002 * game.accelScale; - me.memory = 20; - Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger - spawn.shield(me, x, y, 1); - me.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - this.removeCons(); //remove constraint - }; - me.do = function () { - this.gravity(); - this.seePlayerCheck(); - this.checkStatus(); - this.attraction(); - }; - }, - shield(target, x, y, chance = Math.min(0.02 + game.difficulty * 0.005, 0.2)) { - if (this.allowShields && Math.random() < chance) { - mobs.spawn(x, y, 9, target.radius + 30, "rgba(220,220,255,0.9)"); - let me = mob[mob.length - 1]; - me.stroke = "rgb(220,220,255)"; - Matter.Body.setDensity(me, 0.00001) //very low density to not mess with the original mob's motion - me.shield = true; - me.collisionFilter.category = cat.mobShield - me.collisionFilter.mask = cat.bullet; - consBB[consBB.length] = Constraint.create({ - bodyA: me, - bodyB: target, //attach shield to target - stiffness: 0.4, - damping: 0.1 - }); - World.add(engine.world, consBB[consBB.length - 1]); - - me.onDamage = function () { - //make sure the mob that owns the shield can tell when damage is done - this.alertNearByMobs(); - this.fill = `rgba(220,220,255,${0.3 + 0.6 *this.health})` - }; - me.leaveBody = false; - me.dropPowerUp = false; - me.showHealthBar = false; - - me.shieldTargetID = target.id - target.isShielded = true; - me.onDeath = function () { - //clear isShielded status from target - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].id === this.shieldTargetID) mob[i].isShielded = false; - } - }; - //swap order of shield and mob, so that mob is behind shield graphically - mob[mob.length - 1] = mob[mob.length - 2]; - mob[mob.length - 2] = me; - me.do = function () { - this.checkStatus(); - }; - } - }, - bossShield(targets, x, y, radius, stiffness = 0.4) { - const nodes = targets.length - mobs.spawn(x, y, 9, radius, "rgba(220,220,255,0.9)"); - let me = mob[mob.length - 1]; - me.stroke = "rgb(220,220,255)"; - Matter.Body.setDensity(me, 0.00001) //very low density to not mess with the original mob's motion - me.frictionAir = 0; - me.shield = true; - me.collisionFilter.category = cat.mobShield - me.collisionFilter.mask = cat.bullet; - for (let i = 0; i < nodes; ++i) { - mob[mob.length - i - 2].isShielded = true; - //constrain to all mob nodes in boss - consBB[consBB.length] = Constraint.create({ - bodyA: me, - bodyB: mob[mob.length - i - 2], - stiffness: stiffness, - damping: 0.1 - }); - World.add(engine.world, consBB[consBB.length - 1]); - } - me.onDamage = function () { - this.alertNearByMobs(); //makes sure the mob that owns the shield can tell when damage is done - this.fill = `rgba(220,220,255,${0.3 + 0.6 *this.health})` - }; - me.onDeath = function () { - //clear isShielded status from target - for (let j = 0; j < targets.length; j++) { - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].id === targets[j]) mob[i].isShielded = false; - } - } - }; - me.leaveBody = false; - me.dropPowerUp = false; - me.showHealthBar = false; - mob[mob.length - 1] = mob[mob.length - 1 - nodes]; - mob[mob.length - 1 - nodes] = me; - me.do = function () { - this.checkStatus(); - }; - }, - //fan made mobs ***************************************************************************************** - //******************************************************************************************************* - // mobBloc(x, y, radius, color) { - // mobs.spawn(x, y, 4, radius, color); - // let me = mob[mob.length - 1]; - // me.stroke = "transparent"; - // me.startingPosition = { - // x: x, - // y: y - // } - // Matter.Body.setDensity(me, 0.002); - // me.leaveBody = false; - // me.isStatic = true; - // me.showHealthBar = false; - // me.collisionFilter.category = cat.map; - // me.collisionFilter.mask = cat.powerUp | cat.map | cat.player | cat.bullet | cat.body - // me.rotateVelocity = 0 - // me.do = function () { - // Matter.Body.setVelocity(this, { - // x: 0, - // y: 0 - // }); - // Matter.Body.setPosition(this, this.startingPosition); - // this.checkStatus(); - // }; - // }, - //complex constrained mob templates********************************************************************** - //******************************************************************************************************* - allowShields: true, - nodeBoss( - x, - y, - spawn = "striker", - nodes = Math.min(2 + Math.ceil(Math.random() * (game.difficulty + 2)), 8), - //Math.ceil(Math.random() * 3) + Math.min(4,Math.ceil(game.difficulty/2)), - radius = Math.ceil(Math.random() * 10) + 17, // radius of each node mob - sideLength = Math.ceil(Math.random() * 100) + 70, // distance between each node mob - stiffness = Math.random() * 0.03 + 0.005 - ) { - this.allowShields = false; //don't want shields on individual boss mobs - const angle = 2 * Math.PI / nodes - let targets = [] - for (let i = 0; i < nodes; ++i) { - let whoSpawn = spawn; - if (spawn === "random") { - whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)]; - } else if (spawn === "randomList") { - whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)]; - } - this[whoSpawn](x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius); - targets.push(mob[mob.length - 1].id) //track who is in the node boss, for shields - } - if (Math.random() < 0.3) { - this.constrain2AdjacentMobs(nodes, stiffness * 2, true); - } else { - this.constrainAllMobCombos(nodes, stiffness); - } - //spawn shield for entire boss - if (nodes > 2 && Math.random() < 0.998) { - this.bossShield(targets, x, y, sideLength + 2.5 * radius + nodes * 6 - 25); - } - this.allowShields = true; - }, - lineBoss( - x, - y, - spawn = "striker", - nodes = Math.min(3 + Math.ceil(Math.random() * game.difficulty + 2), 8), - //Math.ceil(Math.random() * 3) + Math.min(4,Math.ceil(game.difficulty/2)), - radius = Math.ceil(Math.random() * 10) + 17, - l = Math.ceil(Math.random() * 80) + 30, - stiffness = Math.random() * 0.06 + 0.01 - ) { - this.allowShields = false; //don't want shields on individual boss mobs - for (let i = 0; i < nodes; ++i) { - let whoSpawn = spawn; - if (spawn === "random") { - whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)]; - } else if (spawn === "randomList") { - whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)]; - } - this[whoSpawn](x + i * radius + i * l, y, radius); - } - this.constrain2AdjacentMobs(nodes, stiffness); - this.allowShields = true; - }, - //constraints ************************************************************************************************ - //************************************************************************************************************* - constrainAllMobCombos(nodes, stiffness) { - //runs through every combination of last 'num' bodies and constrains them - for (let i = 1; i < nodes + 1; ++i) { - for (let j = i + 1; j < nodes + 1; ++j) { + World.add(engine.world, cons[cons.length - 1]); + }, + constraintBB(bodyIndexA, bodyIndexB, stiffness) { consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - i], - bodyB: mob[mob.length - j], - stiffness: stiffness + bodyA: body[bodyIndexA], + bodyB: body[bodyIndexB], + stiffness: stiffness }); World.add(engine.world, consBB[consBB.length - 1]); - } - } - }, - constrain2AdjacentMobs(nodes, stiffness, loop = false) { - //runs through every combination of last 'num' bodies and constrains them - for (let i = 0; i < nodes - 1; ++i) { - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - i - 1], - bodyB: mob[mob.length - i - 2], - stiffness: stiffness - }); - World.add(engine.world, consBB[consBB.length - 1]); - } - if (nodes > 2) { - for (let i = 0; i < nodes - 2; ++i) { - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - i - 1], - bodyB: mob[mob.length - i - 3], - stiffness: stiffness + }, + // body and map spawns ****************************************************************************** + //********************************************************************************************** + wireHead() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1300 + mobs.spawn(breakingPoint, -100, 0, 7.5, "transparent"); + let me = mob[mob.length - 1]; + me.collisionFilter.category = cat.body; + me.collisionFilter.mask = cat.map; + me.inertia = Infinity; + me.g = 0.0004; //required for gravity + me.restitution = 0; + me.stroke = "transparent" + me.freeOfWires = false; + me.frictionStatic = 1; + me.friction = 1; + me.frictionAir = 0.01; + me.dropPowerUp = false; + me.showHealthBar = false; + + me.do = function() { + let wireX = -50; + let wireY = -1000; + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.fill = "#000" + this.force.x += -0.003; + player.force.x += 0.06; + // player.force.y -= 0.15; + } + + //player is extra heavy from wires + Matter.Body.setVelocity(player, { + x: player.velocity.x, + y: player.velocity.y + 0.3 + }) + + //player friction from the wires + if (mech.pos.x > 700 && player.velocity.x > -2) { + let wireFriction = 0.75 * Math.min(0.6, Math.max(0, 100 / (breakingPoint - mech.pos.x))); + if (!mech.onGround) wireFriction *= 3 + Matter.Body.setVelocity(player, { + x: player.velocity.x - wireFriction, + y: player.velocity.y + }) + } + //move to player + Matter.Body.setPosition(this, { + x: mech.pos.x + (42 * Math.cos(mech.angle + Math.PI)), + y: mech.pos.y + (42 * Math.sin(mech.angle + Math.PI)) + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + if (!this.freeOfWires) ctx.lineTo(mech.pos.x + (30 * Math.cos(mech.angle + Math.PI)), mech.pos.y + (30 * Math.sin(mech.angle + Math.PI))); + ctx.lineCap = "butt"; + ctx.lineWidth = 15; + ctx.strokeStyle = "#000"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + wireKnee() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1425 + mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); + let me = mob[mob.length - 1]; + //touch nothing + me.collisionFilter.category = cat.body; + me.collisionFilter.mask = cat.map; + me.g = 0.0003; //required for gravity + // me.restitution = 0; + me.stroke = "transparent" + // me.inertia = Infinity; + me.restitution = 0; + me.freeOfWires = false; + me.frictionStatic = 1; + me.friction = 1; + me.frictionAir = 0.01; + me.dropPowerUp = false; + me.showHealthBar = false; + + me.do = function() { + let wireX = -50 - 20; + let wireY = -1000; + + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.force.x -= 0.0004; + this.fill = "#222"; + } + //move mob to player + mech.calcLeg(0, 0); + Matter.Body.setPosition(this, { + x: mech.pos.x + mech.flipLegs * mech.knee.x - 5, + y: mech.pos.y + mech.knee.y + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + ctx.lineWidth = 5; + ctx.strokeStyle = "#222"; + ctx.lineCap = "butt"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + wireKneeLeft() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1400 + mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); + let me = mob[mob.length - 1]; + //touch nothing + me.collisionFilter.category = cat.body; + me.collisionFilter.mask = cat.map; + me.g = 0.0003; //required for gravity + // me.restitution = 0; + me.stroke = "transparent" + // me.inertia = Infinity; + me.restitution = 0; + me.freeOfWires = false; + me.frictionStatic = 1; + me.friction = 1; + me.frictionAir = 0.01; + me.dropPowerUp = false; + me.showHealthBar = false; + + me.do = function() { + let wireX = -50 - 35; + let wireY = -1000; + + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.force.x += -0.0003; + this.fill = "#333"; + } + //move mob to player + mech.calcLeg(Math.PI, -3); + Matter.Body.setPosition(this, { + x: mech.pos.x + mech.flipLegs * mech.knee.x - 5, + y: mech.pos.y + mech.knee.y + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + ctx.lineWidth = 5; + ctx.lineCap = "butt"; + ctx.strokeStyle = "#333"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + wireFoot() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1350 + mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); + let me = mob[mob.length - 1]; + //touch nothing + me.collisionFilter.category = cat.body; + me.collisionFilter.mask = cat.map; + me.g = 0.0003; //required for gravity + me.restitution = 0; + me.stroke = "transparent" + // me.inertia = Infinity; + me.freeOfWires = false; + // me.frictionStatic = 1; + // me.friction = 1; + me.frictionAir = 0.01; + me.dropPowerUp = false; + me.showHealthBar = false; + + me.do = function() { + let wireX = -50 + 16; + let wireY = -1000; + + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.force.x += -0.0006; + this.fill = "#111"; + } + //move mob to player + mech.calcLeg(0, 0); + Matter.Body.setPosition(this, { + x: mech.pos.x + mech.flipLegs * mech.foot.x - 5, + y: mech.pos.y + mech.foot.y - 1 + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + ctx.lineWidth = 5; + ctx.lineCap = "butt"; + ctx.strokeStyle = "#111"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + wireFootLeft() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1325 + mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); + let me = mob[mob.length - 1]; + //touch nothing + me.collisionFilter.category = cat.body; + me.collisionFilter.mask = cat.map; + me.g = 0.0003; //required for gravity + me.restitution = 0; + me.stroke = "transparent" + // me.inertia = Infinity; + me.freeOfWires = false; + // me.frictionStatic = 1; + // me.friction = 1; + me.frictionAir = 0.01; + me.dropPowerUp = false; + me.showHealthBar = false; + + me.do = function() { + let wireX = -50 + 26; + let wireY = -1000; + + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.force.x += -0.0005; + this.fill = "#222"; + } + //move mob to player + mech.calcLeg(Math.PI, -3); + Matter.Body.setPosition(this, { + x: mech.pos.x + mech.flipLegs * mech.foot.x - 5, + y: mech.pos.y + mech.foot.y - 1 + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + ctx.lineWidth = 5; + ctx.strokeStyle = "#222"; + ctx.lineCap = "butt"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + boost(x, y, height = 1000) { + spawn.mapVertex(x + 50, y + 35, "120 40 -120 40 -50 -40 50 -40"); + // level.addZone(x, y, 100, 30, "fling", {Vx:Vx, Vy: Vy}); + level.addQueryRegion(x, y - 20, 100, 20, "boost", [ + [player], body, mob, powerUp, bullet + ], -1.21 * Math.sqrt(Math.abs(height))); + let color = "rgba(200,0,255,"; + level.fillBG.push({ + x: x, + y: y - 25, + width: 100, + height: 25, + color: color + "0.2)" }); - World.add(engine.world, consBB[consBB.length - 1]); - } - } - //optional connect the tail to head - if (loop && nodes > 3) { - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - 1], - bodyB: mob[mob.length - nodes], - stiffness: stiffness - }); - World.add(engine.world, consBB[consBB.length - 1]); - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - 2], - bodyB: mob[mob.length - nodes], - stiffness: stiffness - }); - World.add(engine.world, consBB[consBB.length - 1]); - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - 1], - bodyB: mob[mob.length - nodes + 1], - stiffness: stiffness - }); - World.add(engine.world, consBB[consBB.length - 1]); - } - }, - constraintPB(x, y, bodyIndex, stiffness) { - cons[cons.length] = Constraint.create({ - pointA: { - x: x, - y: y - }, - bodyB: body[bodyIndex], - stiffness: stiffness - }); - World.add(engine.world, cons[cons.length - 1]); - }, - constraintBB(bodyIndexA, bodyIndexB, stiffness) { - consBB[consBB.length] = Constraint.create({ - bodyA: body[bodyIndexA], - bodyB: body[bodyIndexB], - stiffness: stiffness - }); - World.add(engine.world, consBB[consBB.length - 1]); - }, - // body and map spawns ****************************************************************************** - //********************************************************************************************** - wireHead() { - //not a mob, just a graphic for level 1 - const breakingPoint = 1300 - mobs.spawn(breakingPoint, -100, 0, 7.5, "transparent"); - let me = mob[mob.length - 1]; - me.collisionFilter.category = cat.body; - me.collisionFilter.mask = cat.map; - me.inertia = Infinity; - me.g = 0.0004; //required for gravity - me.restitution = 0; - me.stroke = "transparent" - me.freeOfWires = false; - me.frictionStatic = 1; - me.friction = 1; - me.frictionAir = 0.01; - me.dropPowerUp = false; - me.showHealthBar = false; - - me.do = function () { - let wireX = -50; - let wireY = -1000; - if (this.freeOfWires) { - this.gravity(); - } else { - if (mech.pos.x > breakingPoint) { - this.freeOfWires = true; - this.fill = "#000" - this.force.x += -0.003; - player.force.x += 0.06; - // player.force.y -= 0.15; + level.fillBG.push({ + x: x, + y: y - 55, + width: 100, + height: 55, + color: color + "0.1)" + }); + level.fillBG.push({ + x: x, + y: y - 120, + width: 100, + height: 120, + color: color + "0.05)" + }); + }, + laserZone(x, y, width, height, dmg) { + level.addZone(x, y, width, height, "laser", { + dmg + }); + level.fill.push({ + x: x, + y: y, + width: width, + height: height, + color: "#f00" + }); + }, + deathQuery(x, y, width, height) { + level.addQueryRegion(x, y, width, height, "death", [ + [player], mob + ]); + level.fill.push({ + x: x, + y: y, + width: width, + height: height, + color: "#f00" + }); + }, + platform(x, y, width, height) { + const size = 20; + spawn.mapRect(x, y + height, width, 30); + level.fillBG.push({ + x: x + width / 2 - size / 2, + y: y, + width: size, + height: height, + color: "#f0f0f3" + }); + }, + blockDoor(x, y, blockSize = 60) { + spawn.mapRect(x, y - 290, 40, 60); // door lip + spawn.mapRect(x, y, 40, 50); // door lip + for (let i = 0; i < 4; ++i) { + spawn.bodyRect(x + 5, y - 260 + i * blockSize + i * 3, 30, blockSize); } - - //player is extra heavy from wires - Matter.Body.setVelocity(player, { - x: player.velocity.x, - y: player.velocity.y + 0.3 - }) - - //player friction from the wires - if (mech.pos.x > 700 && player.velocity.x > -2) { - let wireFriction = 0.75 * Math.min(0.6, Math.max(0, 100 / (breakingPoint - mech.pos.x))); - if (!mech.onGround) wireFriction *= 3 - Matter.Body.setVelocity(player, { - x: player.velocity.x - wireFriction, - y: player.velocity.y - }) + }, + debris(x, y, width, number = Math.floor(2 + Math.random() * 9)) { + for (let i = 0; i < number; ++i) { + if (Math.random() < 0.15) { + powerUps.chooseRandomPowerUp(x + Math.random() * width, y); + } else { + const size = 18 + Math.random() * 25; + spawn.bodyRect(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random()), 1); + // body[body.length] = Bodies.rectangle(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random())); + } } - //move to player - Matter.Body.setPosition(this, { - x: mech.pos.x + (42 * Math.cos(mech.angle + Math.PI)), - y: mech.pos.y + (42 * Math.sin(mech.angle + Math.PI)) - }) - } - //draw wire - ctx.beginPath(); - ctx.moveTo(wireX, wireY); - ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); - if (!this.freeOfWires) ctx.lineTo(mech.pos.x + (30 * Math.cos(mech.angle + Math.PI)), mech.pos.y + (30 * Math.sin(mech.angle + Math.PI))); - ctx.lineCap = "butt"; - ctx.lineWidth = 15; - ctx.strokeStyle = "#000"; - ctx.stroke(); - ctx.lineCap = "round"; - }; - }, - wireKnee() { - //not a mob, just a graphic for level 1 - const breakingPoint = 1425 - mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); - let me = mob[mob.length - 1]; - //touch nothing - me.collisionFilter.category = cat.body; - me.collisionFilter.mask = cat.map; - me.g = 0.0003; //required for gravity - // me.restitution = 0; - me.stroke = "transparent" - // me.inertia = Infinity; - me.restitution = 0; - me.freeOfWires = false; - me.frictionStatic = 1; - me.friction = 1; - me.frictionAir = 0.01; - me.dropPowerUp = false; - me.showHealthBar = false; - - me.do = function () { - let wireX = -50 - 20; - let wireY = -1000; - - if (this.freeOfWires) { - this.gravity(); - } else { - if (mech.pos.x > breakingPoint) { - this.freeOfWires = true; - this.force.x -= 0.0004; - this.fill = "#222"; + }, + bodyRect(x, y, width, height, chance = 1, properties = { + friction: 0.05, + frictionAir: 0.001, + }) { + if (Math.random() < chance) body[body.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties); + }, + bodyVertex(x, y, vector, properties) { //adds shape to body array + body[body.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties); + }, + mapRect(x, y, width, height, properties) { //adds rectangle to map array + map[map.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties); + }, + mapVertex(x, y, vector, properties) { //adds shape to map array + map[map.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties); + }, + //complex map templates + spawnBuilding(x, y, w, h, leftDoor, rightDoor, walledSide) { + this.mapRect(x, y, w, 25); //roof + this.mapRect(x, y + h, w, 35); //ground + if (walledSide === "left") { + this.mapRect(x, y, 25, h); //wall left + } else { + this.mapRect(x, y, 25, h - 150); //wall left + if (leftDoor) { + this.bodyRect(x + 5, y + h - 150, 15, 150, this.propsFriction); //door left + } } - //move mob to player - mech.calcLeg(0, 0); - Matter.Body.setPosition(this, { - x: mech.pos.x + mech.flipLegs * mech.knee.x - 5, - y: mech.pos.y + mech.knee.y - }) - } - //draw wire - ctx.beginPath(); - ctx.moveTo(wireX, wireY); - ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); - ctx.lineWidth = 5; - ctx.strokeStyle = "#222"; - ctx.lineCap = "butt"; - ctx.stroke(); - ctx.lineCap = "round"; - }; - }, - wireKneeLeft() { - //not a mob, just a graphic for level 1 - const breakingPoint = 1400 - mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); - let me = mob[mob.length - 1]; - //touch nothing - me.collisionFilter.category = cat.body; - me.collisionFilter.mask = cat.map; - me.g = 0.0003; //required for gravity - // me.restitution = 0; - me.stroke = "transparent" - // me.inertia = Infinity; - me.restitution = 0; - me.freeOfWires = false; - me.frictionStatic = 1; - me.friction = 1; - me.frictionAir = 0.01; - me.dropPowerUp = false; - me.showHealthBar = false; - - me.do = function () { - let wireX = -50 - 35; - let wireY = -1000; - - if (this.freeOfWires) { - this.gravity(); - } else { - if (mech.pos.x > breakingPoint) { - this.freeOfWires = true; - this.force.x += -0.0003; - this.fill = "#333"; + if (walledSide === "right") { + this.mapRect(x - 25 + w, y, 25, h); //wall right + } else { + this.mapRect(x - 25 + w, y, 25, h - 150); //wall right + if (rightDoor) { + this.bodyRect(x + w - 20, y + h - 150, 15, 150, this.propsFriction); //door right + } } - //move mob to player - mech.calcLeg(Math.PI, -3); - Matter.Body.setPosition(this, { - x: mech.pos.x + mech.flipLegs * mech.knee.x - 5, - y: mech.pos.y + mech.knee.y - }) - } - //draw wire - ctx.beginPath(); - ctx.moveTo(wireX, wireY); - ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); - ctx.lineWidth = 5; - ctx.lineCap = "butt"; - ctx.strokeStyle = "#333"; - ctx.stroke(); - ctx.lineCap = "round"; - }; - }, - wireFoot() { - //not a mob, just a graphic for level 1 - const breakingPoint = 1350 - mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); - let me = mob[mob.length - 1]; - //touch nothing - me.collisionFilter.category = cat.body; - me.collisionFilter.mask = cat.map; - me.g = 0.0003; //required for gravity - me.restitution = 0; - me.stroke = "transparent" - // me.inertia = Infinity; - me.freeOfWires = false; - // me.frictionStatic = 1; - // me.friction = 1; - me.frictionAir = 0.01; - me.dropPowerUp = false; - me.showHealthBar = false; - - me.do = function () { - let wireX = -50 + 16; - let wireY = -1000; - - if (this.freeOfWires) { - this.gravity(); - } else { - if (mech.pos.x > breakingPoint) { - this.freeOfWires = true; - this.force.x += -0.0006; - this.fill = "#111"; + }, + spawnStairs(x, y, num, w, h, stepRight) { + w += 50; + if (stepRight) { + for (let i = 0; i < num; i++) { + this.mapRect(x - (w / num) * (1 + i), y - h + (i * h) / num, w / num + 50, h - (i * h) / num + 50); + } + } else { + for (let i = 0; i < num; i++) { + this.mapRect(x + (i * w) / num, y - h + (i * h) / num, w / num + 50, h - (i * h) / num + 50); + } } - //move mob to player - mech.calcLeg(0, 0); - Matter.Body.setPosition(this, { - x: mech.pos.x + mech.flipLegs * mech.foot.x - 5, - y: mech.pos.y + mech.foot.y - 1 - }) - } - //draw wire - ctx.beginPath(); - ctx.moveTo(wireX, wireY); - ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); - ctx.lineWidth = 5; - ctx.lineCap = "butt"; - ctx.strokeStyle = "#111"; - ctx.stroke(); - ctx.lineCap = "round"; - }; - }, - wireFootLeft() { - //not a mob, just a graphic for level 1 - const breakingPoint = 1325 - mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); - let me = mob[mob.length - 1]; - //touch nothing - me.collisionFilter.category = cat.body; - me.collisionFilter.mask = cat.map; - me.g = 0.0003; //required for gravity - me.restitution = 0; - me.stroke = "transparent" - // me.inertia = Infinity; - me.freeOfWires = false; - // me.frictionStatic = 1; - // me.friction = 1; - me.frictionAir = 0.01; - me.dropPowerUp = false; - me.showHealthBar = false; - - me.do = function () { - let wireX = -50 + 26; - let wireY = -1000; - - if (this.freeOfWires) { - this.gravity(); - } else { - if (mech.pos.x > breakingPoint) { - this.freeOfWires = true; - this.force.x += -0.0005; - this.fill = "#222"; - } - //move mob to player - mech.calcLeg(Math.PI, -3); - Matter.Body.setPosition(this, { - x: mech.pos.x + mech.flipLegs * mech.foot.x - 5, - y: mech.pos.y + mech.foot.y - 1 - }) - } - //draw wire - ctx.beginPath(); - ctx.moveTo(wireX, wireY); - ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); - ctx.lineWidth = 5; - ctx.strokeStyle = "#222"; - ctx.lineCap = "butt"; - ctx.stroke(); - ctx.lineCap = "round"; - }; - }, - boost(x, y, height = 1000) { - spawn.mapVertex(x + 50, y + 35, "120 40 -120 40 -50 -40 50 -40"); - // level.addZone(x, y, 100, 30, "fling", {Vx:Vx, Vy: Vy}); - level.addQueryRegion(x, y - 20, 100, 20, "boost", [ - [player], body, mob, powerUp, bullet - ], -1.21 * Math.sqrt(Math.abs(height))); - let color = "rgba(200,0,255,"; - level.fillBG.push({ - x: x, - y: y - 25, - width: 100, - height: 25, - color: color + "0.2)" - }); - level.fillBG.push({ - x: x, - y: y - 55, - width: 100, - height: 55, - color: color + "0.1)" - }); - level.fillBG.push({ - x: x, - y: y - 120, - width: 100, - height: 120, - color: color + "0.05)" - }); - }, - laserZone(x, y, width, height, dmg) { - level.addZone(x, y, width, height, "laser", { - dmg - }); - level.fill.push({ - x: x, - y: y, - width: width, - height: height, - color: "#f00" - }); - }, - deathQuery(x, y, width, height) { - level.addQueryRegion(x, y, width, height, "death", [ - [player], mob - ]); - level.fill.push({ - x: x, - y: y, - width: width, - height: height, - color: "#f00" - }); - }, - platform(x, y, width, height) { - const size = 20; - spawn.mapRect(x, y + height, width, 30); - level.fillBG.push({ - x: x + width / 2 - size / 2, - y: y, - width: size, - height: height, - color: "#f0f0f3" - }); - }, - blockDoor(x, y, blockSize = 58) { - spawn.mapRect(x, y - 290, 40, 60); // door lip - spawn.mapRect(x, y, 40, 50); // door lip - for (let i = 0; i < 4; ++i) { - spawn.bodyRect(x + 5, y - 260 + i * blockSize, 30, blockSize); + }, + //pre-made property options************************************************************************************* + //************************************************************************************************************* + //Object.assign({}, propsHeavy, propsBouncy, propsNoRotation) //will combine properties into a new object + propsFriction: { + friction: 0.5, + frictionAir: 0.02, + frictionStatic: 1 + }, + propsFrictionMedium: { + friction: 0.15, + frictionStatic: 1 + }, + propsBouncy: { + friction: 0, + frictionAir: 0, + frictionStatic: 0, + restitution: 1 + }, + propsSlide: { + friction: 0.003, + frictionStatic: 0.4, + restitution: 0, + density: 0.002 + }, + propsLight: { + density: 0.001 + }, + propsOverBouncy: { + friction: 0, + frictionAir: 0, + frictionStatic: 0, + restitution: 1.05 + }, + propsHeavy: { + density: 0.01 //default density is 0.001 + }, + propsIsNotHoldable: { + isNotHoldable: true + }, + propsNoRotation: { + inertia: Infinity //prevents rotation + }, + propsHoist: { + inertia: Infinity, //prevents rotation + frictionAir: 0.001, + friction: 0.0001, + frictionStatic: 0, + restitution: 0, + isNotHoldable: true + // density: 0.0001 + }, + propsDoor: { + density: 0.001, //default density is 0.001 + friction: 0, + frictionAir: 0.03, + frictionStatic: 0, + restitution: 0 + }, + sandPaper: { + friction: 1, + frictionStatic: 1, + restitution: 0 } - }, - debris(x, y, width, number = Math.floor(2 + Math.random() * 9)) { - for (let i = 0; i < number; ++i) { - if (Math.random() < 0.15) { - powerUps.chooseRandomPowerUp(x + Math.random() * width, y); - } else { - const size = 18 + Math.random() * 25; - spawn.bodyRect(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random()), 1); - // body[body.length] = Bodies.rectangle(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random())); - } - } - }, - bodyRect(x, y, width, height, chance = 1, properties = { - friction: 0.05, - frictionAir: 0.001, - }) { - if (Math.random() < chance) body[body.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties); - }, - bodyVertex(x, y, vector, properties) { //adds shape to body array - body[body.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties); - }, - mapRect(x, y, width, height, properties) { //adds rectangle to map array - map[map.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties); - }, - mapVertex(x, y, vector, properties) { //adds shape to map array - map[map.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties); - }, - //complex map templates - spawnBuilding(x, y, w, h, leftDoor, rightDoor, walledSide) { - this.mapRect(x, y, w, 25); //roof - this.mapRect(x, y + h, w, 35); //ground - if (walledSide === "left") { - this.mapRect(x, y, 25, h); //wall left - } else { - this.mapRect(x, y, 25, h - 150); //wall left - if (leftDoor) { - this.bodyRect(x + 5, y + h - 150, 15, 150, this.propsFriction); //door left - } - } - if (walledSide === "right") { - this.mapRect(x - 25 + w, y, 25, h); //wall right - } else { - this.mapRect(x - 25 + w, y, 25, h - 150); //wall right - if (rightDoor) { - this.bodyRect(x + w - 20, y + h - 150, 15, 150, this.propsFriction); //door right - } - } - }, - spawnStairs(x, y, num, w, h, stepRight) { - w += 50; - if (stepRight) { - for (let i = 0; i < num; i++) { - this.mapRect(x - (w / num) * (1 + i), y - h + (i * h) / num, w / num + 50, h - (i * h) / num + 50); - } - } else { - for (let i = 0; i < num; i++) { - this.mapRect(x + (i * w) / num, y - h + (i * h) / num, w / num + 50, h - (i * h) / num + 50); - } - } - }, - //pre-made property options************************************************************************************* - //************************************************************************************************************* - //Object.assign({}, propsHeavy, propsBouncy, propsNoRotation) //will combine properties into a new object - propsFriction: { - friction: 0.5, - frictionAir: 0.02, - frictionStatic: 1 - }, - propsFrictionMedium: { - friction: 0.15, - frictionStatic: 1 - }, - propsBouncy: { - friction: 0, - frictionAir: 0, - frictionStatic: 0, - restitution: 1 - }, - propsSlide: { - friction: 0.003, - frictionStatic: 0.4, - restitution: 0, - density: 0.002 - }, - propsLight: { - density: 0.001 - }, - propsOverBouncy: { - friction: 0, - frictionAir: 0, - frictionStatic: 0, - restitution: 1.05 - }, - propsHeavy: { - density: 0.01 //default density is 0.001 - }, - propsIsNotHoldable: { - isNotHoldable: true - }, - propsNoRotation: { - inertia: Infinity //prevents rotation - }, - propsHoist: { - inertia: Infinity, //prevents rotation - frictionAir: 0.001, - friction: 0.0001, - frictionStatic: 0, - restitution: 0, - isNotHoldable: true - // density: 0.0001 - }, - propsDoor: { - density: 0.001, //default density is 0.001 - friction: 0, - frictionAir: 0.03, - frictionStatic: 0, - restitution: 0 - }, - sandPaper: { - friction: 1, - frictionStatic: 1, - restitution: 0 - } }; \ No newline at end of file diff --git a/todo.txt b/todo.txt index df931ce..ef7b6b2 100644 --- a/todo.txt +++ b/todo.txt @@ -1,10 +1,46 @@ -neutron bomb does 60% more damage -powerUpBoss has a shorter vision range, and accelerates slower -custom mode has the option to disable mod, guns, and fields -several changes to community maps (by Francois 👑) +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 + +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 ************** 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) + +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 + +new status effect: fear - push mob away from player for a time + +new status effect - apply status effect to mobs that makes blocks attracted to them + only lasts a few cycles + +in hard and why have some mobs spawn in later in the level + where + at defined points in array levelSpawns = [{x:0,y:0},{x:0,y:0}] + store the locations of mobs when the level starts to use as respawn points + remove the locations that are close to player + when? + after some mobs are dead + after the boss is killed + mod - explosions apply radiation damage over time or spawn a neutron bomb with a short timer @@ -13,26 +49,15 @@ mod self destruct - drones explode when they die add an ending to the game add a final boss battle level - final boss has elements of other bosses - alternate between black hole aura and laser beams - fire seeker bullets - drop bombs - tail of shielded mobs - shield 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? + damage the fake player to end the game + message about go outside no ending (if cheats) game goes on forever - - around level 15 + also game goes on if player attacks, the fake player game never ends if you have used cheats -new status effect - push mob away from player for a time - -new status effect - apply status effect to mobs that makes blocks attracted to them - only lasts a few cycles - 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?