From 7508b52d917fabf21d0c8c0846bea62cae0c5b6c Mon Sep 17 00:00:00 2001 From: landgreen Date: Sat, 7 Dec 2019 13:38:39 -0800 Subject: [PATCH] added laser bot --- js/bullets.js | 3835 ++++++++++++++++++++++++------------------------ js/engine.js | 464 +++--- js/game.js | 2098 +++++++++++++------------- js/index.js | 692 ++++----- js/level.js | 3212 ++++++++++++++++++++-------------------- js/mobs.js | 2020 ++++++++++++------------- js/player.js | 3030 +++++++++++++++++++------------------- js/powerups.js | 476 +++--- js/spawn.js | 3384 +++++++++++++++++++++--------------------- 9 files changed, 9615 insertions(+), 9596 deletions(-) diff --git a/js/bullets.js b/js/bullets.js index 438d2df..cc1b806 100644 --- a/js/bullets.js +++ b/js/bullets.js @@ -1,1900 +1,1937 @@ -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 - //variables use for gun mod upgrades - modCount: null, - modFireRate: null, - modExplosionRadius: null, - modBulletSize: null, - modEnergySiphon: null, - modHealthDrain: null, - modNoAmmo: null, - isModBulletsLastLonger: null, - modIsImmortal: null, - modSpores: null, - isModTempResist: null, - isModDroneOnDamage: null, - modExtraDmg: null, - annihilation: null, - fullHeal: null, - modSquirrelFx: null, - modIsCrit: null, - modMoreDrops: null, - isModLowHealthDmg: null, - isModFarAwayDmg: null, - isModMonogamy: null, - setModDefaults() { - b.modCount = 0; - b.modFireRate = 1; - b.modExplosionRadius = 1; - b.isModTempResist = false; - b.modBulletSize = 1; - b.isModDroneOnDamage = false; - b.modEnergySiphon = 0; - b.modHealthDrain = 0; - b.modNoAmmo = 0; - b.isModBulletsLastLonger = 1; - b.modIsImmortal = false; - b.modSpores = 0; - b.modExtraDmg = 0; - b.modAnnihilation = false; - b.isModFullHeal = false; - b.modSquirrelFx = 1; - b.modIsCrit = false; - b.modMoreDrops = 0; - b.isModLowHealthDmg = false; - b.isModFarAwayDmg = false; - b.isModMonogamy = false; - mech.Fx = 0.015; - mech.jumpForce = 0.38; - mech.throwChargeRate = 2; - mech.throwChargeMax = 50; - for (let i = 0; i < b.mods.length; i++) { - b.mods[i].have = false; - } - }, - mods: [{ - name: "depleted uranium rounds", - description: "your bullets are 10% larger
increased mass and physical damage", - have: false, //0 - effect: () => { - //good for guns that do mostly projectile damage: - //testing at 1.08: spray(point blank)(+0.25), one shot(+0.16), wave beam(point blank)(+0.14) - b.modBulletSize = 1.1; - } - }, - { - name: "auto-loading heuristics", - description: "your delay after firing is 15% shorter", - have: false, //1 - effect: () => { //good for guns with extra ammo: needles, M80, rapid fire, flak, super balls - b.modFireRate = 0.85 - } - }, - { - name: "desublimated ammunition", - description: "use 50% less ammo when crouching", - have: false, //2 - effect: () => { //good with guns that have less ammo: one shot, grenades, missiles, super balls, spray - b.modNoAmmo = 1 - } - }, - { - name: "Lorentzian topology", - description: "your bullets last 40% longer", - have: false, //3 - effect: () => { //good with: drones, super balls, spore, missiles, wave beam(range), rapid fire(range), flak(range) - b.isModBulletsLastLonger = 1.40 - } - }, - { - name: "anti-matter cores", - description: "the radius of your explosions is doubled
be careful", - have: false, //4 - effect: () => { //at 1.4 gives a flat 40% increase, and increased range, balanced by limited guns and self damage - //testing at 1.3: grenade(+0.3), missiles, flak, M80 - b.modExplosionRadius = 1.8; //good for guns with explosions - } - }, - { - name: "ceramic plating", - description: "protection from to high temperatures
5x less damage from explosions and lasers", - have: false, //5 - effect: () => { - b.isModTempResist = true; //good for guns with explosions - } - }, - { - name: "ablative synthesis", - description: "rebuild your broken parts as drones
chance to occur after taking damage", - have: false, //6 - effect: () => { //makes dangerous situations more survivable - b.isModDroneOnDamage = true; - } - }, - { - name: "zoospore vector", - description: "enemies can discharge spores on death
spores seek out enemies", - have: false, //7 - effect: () => { //good late game maybe? - b.modSpores = 0.20; - } - }, - { - name: "energy transfer", - description: "gain energy proportional to damage done", - have: false, //8 - effect: () => { //good with laser, and all fields - b.modEnergySiphon = 0.2; - } - }, - { - name: "entropy transfer", - description: "heal proportional to damage done", - have: false, //9 - effect: () => { //good with guns that overkill: one shot, grenade - b.modHealthDrain = 0.015; - } - }, - { - name: "quantum immortality", - description: "after dying, continue in an alternate reality
guns, ammo, and field are randomized", - have: false, //10 - effect: () => { - b.modIsImmortal = true; - } - }, - { - name: "fluoroantimonic acid", - description: "each bullet does extra chemical damage", - have: false, //11 - effect: () => { //good with guns that fire many bullets at low speeds, minigun, drones, junk-bots, shotgun, superballs, wavebeam - b.modExtraDmg = 0.1 - } - }, - { - name: "annihilation", - description: "after touching enemies, they are annihilated", - have: false, //12 - effect: () => { //good with mods that heal: superconductive healing, entropy transfer - b.modAnnihilation = true - } - }, - { - name: "recursive healing", - description: "healing power ups bring you to full health", - have: false, //13 - effect: () => { // good with ablative synthesis, melee builds - b.isModFullHeal = true - } - }, - { - name: "Gauss rifle", - description: "launch blocks at much higher speeds
carry more massive blocks", - have: false, //14 - effect: () => { // good with guns that run out of ammo - mech.throwChargeRate = 4; - mech.throwChargeMax = 150; - mech.holdingMassScale = 0.05; //can hold heavier blocks with lower cost to jumping - } - }, - { - name: "squirrel-cage rotor", - description: "your legs produce 20% more force
jump higher and move faster", - have: false, //15 - effect: () => { // good with melee builds, content skipping builds - b.modSquirrelFx = 1.2; - mech.Fx = 0.015 * b.modSquirrelFx; - mech.jumpForce = 0.38 * 1.1; - } - }, - { - name: "fracture analysis", - description: "5x physical damage to unaware enemies
unaware enemies don't have a health bar", - have: false, //16 - effect: () => { // good with high damage guns that strike from a distance: rail gun, drones, flechettes, spores, grenade, vacuum bomb - b.modIsCrit = true; - } - }, - { - name: "kinetic bombardment", - description: "do extra damage from a distance
up to 50% increase at about 30 steps away", - have: false, //17 - effect: () => { // good with annihilation, melee builds - b.isModFarAwayDmg = true; //used in mob.damage() - } - }, - { - name: "quasistatic equilibrium", - description: "do extra damage at low health
up to 50% increase when near death", - have: false, //18 - effect: () => { // good with annihilation, melee builds - b.isModLowHealthDmg = true; //used in mob.damage() - } - }, - { - name: "Bayesian inference", - description: "20% chance for double power ups to drop", - have: false, //19 - effect: () => { // good with long term planning - b.modMoreDrops = 0.20; - } - }, - { - name: "entanglement", - description: "using your first gun reduces damage taken
scales by 7% for each gun in your inventory", - have: false, //20 - effect: () => { // good with long term planning - b.isModMonogamy = true - } - }, - ], - giveMod(i) { - b.mods[i].effect(); //give specific mod - b.modCount++ - b.mods[i].have = true - game.updateModHUD(); - }, - activeGun: null, //current gun in use by player - inventoryGun: 0, - inventory: [], //list of what guns player has // 0 starts with basic gun - giveGuns(gun = "all", ammoPacks = 2) { - if (gun === "all") { - b.activeGun = 0; - b.inventoryGun = 0; - for (let i = 0; i < b.guns.length; i++) { - b.guns[i].have = true; - b.guns[i].ammo = b.guns[i].ammoPack * ammoPacks; - b.inventory[i] = i; - } - } else { - if (!b.guns[gun].have) b.inventory.push(gun); - if (b.activeGun === null) b.activeGun = gun //if no active gun switch to new gun - b.guns[gun].have = true; - b.guns[gun].ammo = b.guns[gun].ammoPack * ammoPacks; - } - game.makeGunHUD(); - }, - fire() { - if (game.mouseDown && mech.fireCDcycle < mech.cycle && (!(keys[32] || game.mouseDownRight) || mech.fieldFire) && b.inventory.length) { - if (b.guns[b.activeGun].ammo > 0) { - b.guns[b.activeGun].fire(); - if (b.modNoAmmo && mech.crouch) { - if (b.modNoAmmo % 2) { - b.guns[b.activeGun].ammo--; - game.updateGunHUD(); - } - b.modNoAmmo++ //makes the no ammo toggle off and on - } else { - b.guns[b.activeGun].ammo--; - game.updateGunHUD(); - } - } else { - mech.fireCDcycle = mech.cycle + 30; //cooldown - // game.makeTextLog("
NO AMMO
E / Q", 200); - game.replaceTextLog = true; - game.makeTextLog("
NO AMMO

Q, E, and mouse wheel change weapons

", 200); - } - if (mech.isHolding) { - mech.drop(); - } - } - }, - draw() { - ctx.beginPath(); - let i = bullet.length; - while (i--) { - //draw - let vertices = bullet[i].vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j += 1) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - //remove bullet if at end cycle for that bullet - if (bullet[i].endCycle < game.cycle) { - bullet[i].onEnd(i); //some bullets do stuff on end - if (bullet[i]) { - Matter.World.remove(engine.world, bullet[i]); - bullet.splice(i, 1); - } else { - break; //if bullet[i] doesn't exist don't complete the for loop, because the game probably reset - } - } - } - ctx.fillStyle = "#000"; - ctx.fill(); - //do things - for (let i = 0, len = bullet.length; i < len; i++) { - bullet[i].do(); - } - }, - fireProps(cd, speed, dir, me) { - mech.fireCDcycle = mech.cycle + Math.floor(cd * b.modFireRate); // cool down - Matter.Body.setVelocity(bullet[me], { - x: mech.Vx / 2 + speed * Math.cos(dir), - y: mech.Vy / 2 + speed * Math.sin(dir) - }); - World.add(engine.world, bullet[me]); //add bullet to world - }, - fireAttributes(dir, rotate = true) { - if (rotate) { - return { - // density: 0.0015, //frictionAir: 0.01, //restitution: 0, - angle: dir, - friction: 0.5, - frictionAir: 0, - dmg: b.modExtraDmg, //damage done in addition to the damage from momentum - classType: "bullet", - collisionFilter: { - category: 0x000100, - mask: 0x010011 //mask: 0x000101, //for self collision - }, - minDmgSpeed: 10, - onDmg() {}, //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: 0x000100, - mask: 0x010011 //mask: 0x000101, //for self collision - }, - minDmgSpeed: 10, - onDmg() {}, //this.endCycle = 0 //triggers despawn - onEnd() {} - }; - } - }, - muzzleFlash(radius = 10) { - ctx.fillStyle = "#fb0"; - ctx.beginPath(); - ctx.arc(mech.pos.x + 35 * Math.cos(mech.angle), mech.pos.y + 35 * Math.sin(mech.angle), radius, 0, 2 * Math.PI); - ctx.fill(); - }, - drawOneBullet(vertices) { - ctx.beginPath(); - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j += 1) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - ctx.fillStyle = "#000"; - ctx.fill(); - }, - removeConsBB(me) { - for (let i = 0, len = consBB.length; i < len; ++i) { - if (consBB[i].bodyA === me) { - consBB[i].bodyA = consBB[i].bodyB; - consBB.splice(i, 1); - // b.removeConsBB(me); - break; - } else if (consBB[i].bodyB === me) { - consBB[i].bodyB = consBB[i].bodyA; - consBB.splice(i, 1); - // b.removeConsBB(me); - break; - } - } - }, - explode(me) { - // typically explode is used for some bullets with .onEnd - const radius = bullet[me].explodeRad * b.modExplosionRadius - //add dmg to draw queue - game.drawList.push({ - x: bullet[me].position.x, - y: bullet[me].position.y, - radius: radius, - color: "rgba(255,25,0,0.6)", - time: game.drawTime - }); - let dist, sub, knock; - const dmg = b.dmgScale * radius * 0.009; - - const alertRange = 100 + radius * 2; //alert range - //add alert to draw queue - game.drawList.push({ - x: bullet[me].position.x, - y: bullet[me].position.y, - radius: alertRange, - color: "rgba(100,20,0,0.03)", - time: game.drawTime - }); - - //player damage and knock back - sub = Matter.Vector.sub(bullet[me].position, player.position); - dist = Matter.Vector.magnitude(sub); - if (dist < radius) { - if (b.isModTempResist) { - mech.damage(radius * 0.00004); - } else { - mech.damage(radius * 0.0002); - } - knock = Matter.Vector.mult(Matter.Vector.normalise(sub), -Math.sqrt(dmg) * player.mass / 30); - player.force.x += knock.x; - player.force.y += knock.y; - mech.drop(); - } else if (dist < alertRange) { - knock = Matter.Vector.mult(Matter.Vector.normalise(sub), -Math.sqrt(dmg) * player.mass / 55); - player.force.x += knock.x; - player.force.y += knock.y; - mech.drop(); - } - - //body knock backs - for (let i = 0, len = body.length; i < len; ++i) { - sub = Matter.Vector.sub(bullet[me].position, body[i].position); - dist = Matter.Vector.magnitude(sub); - if (dist < radius) { - knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) / 18); - body[i].force.x += knock.x; - body[i].force.y += knock.y; - } else if (dist < alertRange) { - knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) / 40); - body[i].force.x += knock.x; - body[i].force.y += knock.y; - } - } - - //power up knock backs - for (let i = 0, len = powerUp.length; i < len; ++i) { - sub = Matter.Vector.sub(bullet[me].position, powerUp[i].position); - dist = Matter.Vector.magnitude(sub); - if (dist < radius) { - knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) / 26); - powerUp[i].force.x += knock.x; - powerUp[i].force.y += knock.y; - } else if (dist < alertRange) { - knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) / 40); - powerUp[i].force.x += knock.x; - powerUp[i].force.y += knock.y; - } - } - - //mob damage and knock back with alert - let damageScale = 1; // reduce dmg for each new target to limit total AOE damage - for (let i = 0, len = mob.length; i < len; ++i) { - if (mob[i].alive) { - sub = Matter.Vector.sub(bullet[me].position, mob[i].position); - dist = Matter.Vector.magnitude(sub) - mob[i].radius; - if (dist < radius) { - mob[i].damage(dmg * damageScale); - mob[i].locatePlayer(); - knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) / 18); - mob[i].force.x += knock.x; - mob[i].force.y += knock.y; - damageScale *= 0.8 //reduced damage for each additional explosion target - } else if (!mob[i].seePlayer.recall && dist < alertRange) { - mob[i].locatePlayer(); - knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) / 35); - mob[i].force.x += knock.x; - mob[i].force.y += knock.y; - } - } - } - - // Matter.Vector.magnitudeSquared(Matter.Vector.sub(bullet[me].position, mob[i].position)) - }, - spore(who) { //used with the mod upgrade in mob.death() - const bIndex = bullet.length; - const RADIUS = 3 * b.modBulletSize; - bullet[bIndex] = Bodies.circle(who.position.x, who.position.y, RADIUS, { - // density: 0.0015, //frictionAir: 0.01, - inertia: Infinity, - restitution: 0.5, - angle: Math.random() * 2 * Math.PI, - friction: 0, - frictionAir: 0.011, - dmg: 1.8, //damage done in addition to the damage from momentum - classType: "bullet", - collisionFilter: { - category: 0x000100, - mask: 0x000011 //no collide with body - }, - endCycle: game.cycle + Math.floor((360 + Math.floor(Math.random() * 240)) * b.isModBulletsLastLonger), - minDmgSpeed: 0, - onDmg() { - this.endCycle = 0; //bullet ends cycle after doing damage - }, - onEnd() {}, - lookFrequency: 67 + Math.floor(47 * Math.random()), - do() { - //find mob targets - if (!(game.cycle % this.lookFrequency)) { - this.closestTarget = null; - this.lockedOn = null; - let closeDist = Infinity; - for (let i = 0, len = mob.length; i < len; ++i) { - if (Matter.Query.ray(map, this.position, mob[i].position).length === 0) { - // Matter.Query.ray(body, this.position, mob[i].position).length === 0 - const targetVector = Matter.Vector.sub(this.position, mob[i].position) - const dist = Matter.Vector.magnitude(targetVector); - if (dist < closeDist) { - this.closestTarget = mob[i].position; - closeDist = dist; - this.lockedOn = Matter.Vector.normalise(targetVector); - if (0.3 > Math.random()) break //doesn't always target the closest mob - } - } - } - } - //accelerate towards mobs - const THRUST = this.mass * 0.0009 - if (this.lockedOn) { - this.force.x -= THRUST * this.lockedOn.x - this.force.y -= THRUST * this.lockedOn.y - } else { - this.force.y += this.mass * 0.00027; //gravity - } - }, - }); - const SPEED = 9; - 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 - }, - guns: [{ - name: "laser", //0 - description: "emit a beam of damaging coherent light
uses energy instead of ammunition", - ammo: 0, - // ammoPack: 350, - ammoPack: Infinity, - have: false, - isStarterGun: true, - fire() { - // mech.fireCDcycle = mech.cycle + 1 - //laser drains energy as well as bullets - const FIELD_DRAIN = 0.002 - const damage = 0.05 - if (mech.fieldMeter < FIELD_DRAIN) { - mech.fireCDcycle = mech.cycle + 100; // cool down if out of energy - } else { - mech.fieldMeter -= mech.fieldRegen + FIELD_DRAIN - let best; - const color = "#f00"; - const range = 3000; - const path = [{ - x: mech.pos.x + 20 * Math.cos(mech.angle), - y: mech.pos.y + 20 * Math.sin(mech.angle) - }, - { - x: mech.pos.x + range * Math.cos(mech.angle), - y: mech.pos.y + range * Math.sin(mech.angle) - } - ]; - const vertexCollision = function (v1, v1End, domain) { - for (let i = 0; i < domain.length; ++i) { - let vertices = domain[i].vertices; - const len = vertices.length - 1; - for (let j = 0; j < len; j++) { - results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[j], - v2: vertices[j + 1] - }; - } - } - } - results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[0], - v2: vertices[len] - }; - } - } - } - }; - const checkForCollisions = function () { - best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - vertexCollision(path[path.length - 2], path[path.length - 1], mob); - vertexCollision(path[path.length - 2], path[path.length - 1], map); - vertexCollision(path[path.length - 2], path[path.length - 1], body); - }; - const laserHitMob = function (dmg) { - if (best.who.alive) { - dmg *= b.dmgScale * damage; - best.who.damage(dmg); - best.who.locatePlayer(); - //draw mob damage circle - ctx.fillStyle = color; - ctx.beginPath(); - ctx.arc(path[path.length - 1].x, path[path.length - 1].y, Math.sqrt(dmg) * 100, 0, 2 * Math.PI); - ctx.fill(); - } - }; - - const reflection = function () { - // https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector - const n = Matter.Vector.perp(Matter.Vector.normalise(Matter.Vector.sub(best.v1, best.v2))); - const d = Matter.Vector.sub(path[path.length - 1], path[path.length - 2]); - const nn = Matter.Vector.mult(n, 2 * Matter.Vector.dot(d, n)); - const r = Matter.Vector.normalise(Matter.Vector.sub(d, nn)); - path[path.length] = Matter.Vector.add(Matter.Vector.mult(r, range), path[path.length - 1]); - }; - //beam before reflection - checkForCollisions(); - if (best.dist2 != Infinity) { - //if hitting something - path[path.length - 1] = { - x: best.x, - y: best.y - }; - laserHitMob(1); - - //1st reflection beam - reflection(); - //ugly bug fix: this stops the reflection on a bug where the beam gets trapped inside a body - let who = best.who; - checkForCollisions(); - if (best.dist2 != Infinity) { - //if hitting something - path[path.length - 1] = { - x: best.x, - y: best.y - }; - laserHitMob(0.75); - - //2nd reflection beam - //ugly bug fix: this stops the reflection on a bug where the beam gets trapped inside a body - if (who !== best.who) { - reflection(); - checkForCollisions(); - if (best.dist2 != Infinity) { - //if hitting something - path[path.length - 1] = { - x: best.x, - y: best.y - }; - laserHitMob(0.5); - } - } - } - } - ctx.fillStyle = color; - ctx.strokeStyle = color; - ctx.lineWidth = 2; - ctx.lineDashOffset = 300 * Math.random() - // ctx.setLineDash([200 * Math.random(), 250 * Math.random()]); - - ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]); - for (let i = 1, len = path.length; i < len; ++i) { - ctx.beginPath(); - ctx.moveTo(path[i - 1].x, path[i - 1].y); - ctx.lineTo(path[i].x, path[i].y); - ctx.stroke(); - ctx.globalAlpha *= 0.5; //reflections are less intense - // ctx.globalAlpha -= 0.1; //reflections are less intense - } - ctx.setLineDash([0, 0]); - ctx.globalAlpha = 1; - } - } - }, - { - name: "rail gun", //1 - description: "electro-magnetically launch a dense rod
hold left mouse to charge, release to fire", //and repel enemies - ammo: 0, - ammoPack: 7, - have: false, - isStarterGun: false, - fire() { - const me = bullet.length; - bullet[me] = Bodies.rectangle(0, 0, 0.012 * b.modBulletSize, 0.0025 * b.modBulletSize, { - density: 0.01, //0.001 is normal - //frictionAir: 0.01, //restitution: 0, - // angle: 0, - // friction: 0.5, - frictionAir: 0, - dmg: b.modExtraDmg, //damage done in addition to the damage from momentum - classType: "bullet", - collisionFilter: { - category: 0x000000, - mask: 0x010011 //mask: 0x000101, //for self collision - }, - minDmgSpeed: 5, - onDmg() {}, //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].isCharging = true; - bullet[me].charge = 0; - bullet[me].do = function () { - if (this.isCharging) { - if ((!game.mouseDown && this.charge > 0.6)) { //fire on mouse release - this.isCharging = false - mech.fireCDcycle = mech.cycle + 2; // set fire cool down - 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 + Math.floor(140 * b.isModBulletsLastLonger) - this.collisionFilter.category = 0x000100 - 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) * b.modBulletSize * b.modBulletSize * 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 = 450 * this.charge - for (let i = 0, len = body.length; i < len; ++i) { - const SUB = Matter.Vector.sub(body[i].position, mech.pos) - const DISTANCE = Matter.Vector.magnitude(SUB) - - if (DISTANCE < range) { - const DEPTH = Math.max(range - DISTANCE, 100) - const FORCE = Matter.Vector.mult(Matter.Vector.normalise(SUB), 0.005 * Math.sqrt(DEPTH) * Math.sqrt(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 = Matter.Vector.sub(mob[i].position, mech.pos) - const DISTANCE = Matter.Vector.magnitude(SUB) - - if (DISTANCE < range) { - const DEPTH = Math.max(range - DISTANCE, 100) - const FORCE = Matter.Vector.mult(Matter.Vector.normalise(SUB), 0.005 * Math.sqrt(DEPTH) * Math.sqrt(mob[i].mass)) - mob[i].force.x += 1.5 * FORCE.x; - mob[i].force.y += 1.5 * FORCE.y; - } - } - //push mobs around player when firing - // range = 600 * this.charge - // for (let i = 0, len = mob.length; i < len; ++i) { - // const SUB = Matter.Vector.sub(mob[i].position, mech.pos) - // const DISTANCE = Matter.Vector.magnitude(SUB) - // if (DISTANCE < range) { - // const DEPTH = range - DISTANCE - // const FORCE = Matter.Vector.mult(Matter.Vector.normalise(SUB), 0.00000001 * DEPTH * DEPTH * DEPTH * Math.sqrt(mob[i].mass)) - // mob[i].force.x += FORCE.x - // mob[i].force.y += FORCE.y - // } - // } - } else { // charging on mouse down - mech.fireCDcycle = Infinity //can't fire until mouse is released - if (mech.crouch) { - this.charge = this.charge * 0.97 + 0.03 // this.charge converges to 1 - } else { - this.charge = this.charge * 0.987 + 0.013 // this.charge converges to 1 - } - - //gently push away mobs while charging - // const RANGE = 270 * this.charge - // for (let i = 0, len = mob.length; i < len; ++i) { - // const SUB = Matter.Vector.sub(mob[i].position, mech.pos) - // const DISTANCE = Matter.Vector.magnitude(SUB) - // // if (DISTANCE < RANGE) { - // // Matter.Body.setVelocity(mob[i], Matter.Vector.rotate(mob[i].velocity, 0.1)) - // // } - // // const DRAIN = 0.0002 //&& mech.fieldMeter > DRAIN - // if (DISTANCE < RANGE) { - // // mech.fieldMeter -= DRAIN + mech.fieldRegen; - // const DEPTH = RANGE - DISTANCE - // const FORCE = Matter.Vector.mult(Matter.Vector.normalise(SUB), 0.000000001 * DEPTH * DEPTH * DEPTH * Math.sqrt(mob[i].mass)) - // mob[i].force.x += FORCE.x - // mob[i].force.y += FORCE.y - // } - // } - - //draw laser 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 laser 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 = Matter.Vector.normalise(Matter.Vector.sub(game.mouseInGame, mech.pos)) - const unitVectorPerp = Matter.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(); - } - } - } else { //normal bullet behavior - this.force.y += this.mass * 0.00015 / this.charge; // low gravity that scales with charge - } - } - } - }, - { - name: "minigun", //2 - description: "rapidly fire a stream of small bullets", - ammo: 0, - ammoPack: 105, - have: false, - isStarterGun: true, - fire() { - const me = bullet.length; - b.muzzleFlash(15); - // if (Math.random() > 0.2) mobs.alert(500); - const dir = mech.angle + (Math.random() - 0.5) * ((mech.crouch) ? 0.03 : 0.14); - bullet[me] = Bodies.rectangle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 17 * b.modBulletSize, 5 * b.modBulletSize, b.fireAttributes(dir)); - b.fireProps(mech.crouch ? 11 : 5, mech.crouch ? 44 : 36, dir, me); //cd , speed - bullet[me].endCycle = game.cycle + Math.floor(65 * b.isModBulletsLastLonger); - bullet[me].frictionAir = mech.crouch ? 0.007 : 0.01; - bullet[me].do = function () { - this.force.y += this.mass * 0.0005; - }; - } - }, { - name: "wave beam", //3 - description: "emit a sine wave of oscillating particles
particles propagate through walls", - ammo: 0, - ammoPack: 85, - have: false, - isStarterGun: true, - fire() { - const me = bullet.length; - const DIR = mech.angle - const SCALE = (mech.crouch ? 0.963 : 0.95) - const wiggleMag = ((mech.flipLegs === 1) ? 1 : -1) * ((mech.crouch) ? 0.004 : 0.005) - bullet[me] = Bodies.circle(mech.pos.x + 25 * Math.cos(DIR), mech.pos.y + 25 * Math.sin(DIR), 10 * b.modBulletSize, { - angle: DIR, - cycle: -0.43, //adjust this number until the bullets line up with the cross hairs - endCycle: game.cycle + Math.floor((mech.crouch ? 155 : 120) * b.isModBulletsLastLonger), - inertia: Infinity, - frictionAir: 0, - minDmgSpeed: 0, - dmg: 0.13 + b.modExtraDmg, //damage done in addition to the damage from momentum - classType: "bullet", - collisionFilter: { - category: 0x000100, - mask: 0x000010 - }, - onDmg() {}, - onEnd() {}, - do() { - if (!mech.isBodiesAsleep) { - this.cycle++ - const THRUST = wiggleMag * Math.cos(this.cycle * 0.3) - this.force = Matter.Vector.mult(Matter.Vector.normalise(this.direction), this.mass * THRUST) //wiggle - - if (this.cycle > 0 && !(Math.floor(this.cycle) % 6)) Matter.Body.scale(this, SCALE, SCALE); //shrink - } - } - }); - World.add(engine.world, bullet[me]); //add bullet to world - mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 8 : 4) * b.modFireRate); // cool down - const SPEED = mech.crouch ? 5.2 : 4.5; - Matter.Body.setVelocity(bullet[me], { - x: SPEED * Math.cos(DIR), - y: SPEED * Math.sin(DIR) - }); - bullet[me].direction = Matter.Vector.perp(bullet[me].velocity) - // if (mech.angle + Math.PI / 2 > 0) { - // bullet[me].direction = Matter.Vector.perp(bullet[me].velocity, true) - // } else { - // bullet[me].direction = Matter.Vector.perp(bullet[me].velocity) - // } - - World.add(engine.world, bullet[me]); //add bullet to world - } - }, { - name: "super balls", //4 - description: "fire balls that bounce with no momentum loss", - ammo: 0, - ammoPack: 11, - have: false, - isStarterGun: true, - fire() { - b.muzzleFlash(20); - // mobs.alert(450); - const SPREAD = mech.crouch ? 0.04 : 0.14 - let dir = mech.angle - SPREAD; - for (let i = 0; i < 3; i++) { - const me = bullet.length; - bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 7 * b.modBulletSize, b.fireAttributes(dir, false)); - b.fireProps(mech.crouch ? 40 : 20, mech.crouch ? 34 : 26, dir, me); //cd , speed - Matter.Body.setDensity(bullet[me], 0.0001); - bullet[me].endCycle = game.cycle + Math.floor(360 * b.isModBulletsLastLonger); - bullet[me].dmg = 0.5 + b.modExtraDmg; - bullet[me].minDmgSpeed = 0; - bullet[me].restitution = 0.96; - bullet[me].friction = 0; - bullet[me].do = function () { - this.force.y += this.mass * 0.001; - }; - dir += SPREAD; - } - } - }, { - name: "shotgun", //5 - description: "fire a burst of short range bullets
crouch to reduce recoil", - ammo: 0, - ammoPack: 8, - have: false, - isStarterGun: true, - fire() { - b.muzzleFlash(35); - // mobs.alert(650); - const side = 11 * b.modBulletSize - for (let i = 0; i < 9; i++) { - const me = bullet.length; - const dir = mech.angle + (Math.random() - 0.5) * (mech.crouch ? 0.22 : 0.7) - 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)); - b.fireProps(mech.crouch ? 60 : 30, 40 + Math.random() * 11, dir, me); //cd , speed - bullet[me].endCycle = game.cycle + Math.floor(55 * b.isModBulletsLastLonger); - bullet[me].frictionAir = 0.03; - bullet[me].do = function () { - this.force.y += this.mass * 0.001; - }; - } - - //knock back - const KNOCK = ((mech.crouch) ? 0.015 : 0.15) * b.modBulletSize * b.modBulletSize - 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 - } - }, { - name: "fléchettes", //6 - description: "fire a flight of needles
accurate at long range", - ammo: 0, - ammoPack: 25, - have: false, - isStarterGun: true, - fire() { - function spawnFlechette(dir = mech.angle, speed, size = 1) { - const me = bullet.length; - bullet[me] = Bodies.rectangle(mech.pos.x + 40 * Math.cos(dir), mech.pos.y + 40 * Math.sin(dir), 32 * size * b.modBulletSize, 0.8 * size * b.modBulletSize, b.fireAttributes(dir)); - bullet[me].endCycle = game.cycle + Math.floor(180 * b.isModBulletsLastLonger); - bullet[me].dmg = 0.15 * size + b.modExtraDmg; - b.drawOneBullet(bullet[me].vertices); - bullet[me].do = function () { - this.force.y += this.mass * 0.0002; //low gravity - }; - 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 - } - - if (mech.crouch) { - for (let i = 0; i < 3; i++) { - spawnFlechette(mech.angle + 0.02 * (Math.random() - 0.5), 35 + 4 * i, 1.55) - } - } else { - for (let i = 0; i < 9; i++) { - spawnFlechette(mech.angle + 0.12 * (Math.random() - 0.5), 30 + 8 * Math.random()) - } - } - mech.fireCDcycle = mech.cycle + Math.floor(40 * b.modFireRate); // cool down - } - }, { - name: "missiles", //7 - description: "fire missiles that accelerate towards enemies
explodes when near target", - ammo: 0, - ammoPack: 8, - have: false, - isStarterGun: false, - fireCycle: 0, - ammoLoaded: 0, - fire() { - const thrust = 0.0003; - let dir = mech.angle + (0.5 - Math.random()) * (mech.crouch ? 0 : 0.2); - const me = bullet.length; - bullet[me] = Bodies.rectangle(mech.pos.x + 40 * Math.cos(mech.angle), mech.pos.y + 40 * Math.sin(mech.angle) - 3, 30 * b.modBulletSize, 4 * b.modBulletSize, b.fireAttributes(dir)); - b.fireProps(mech.crouch ? 70 : 30, -3 * (0.5 - Math.random()) + (mech.crouch ? 25 : -8), dir, me); //cd , speed - - b.drawOneBullet(bullet[me].vertices); - // Matter.Body.setDensity(bullet[me], 0.01) //doesn't help with reducing explosion knock backs - bullet[me].force.y += 0.00045; //a small push down at first to make it seem like the missile is briefly falling - bullet[me].frictionAir = 0 - bullet[me].endCycle = game.cycle + Math.floor((265 + Math.random() * 20) * b.isModBulletsLastLonger); - bullet[me].explodeRad = 170 + 60 * Math.random(); - bullet[me].lookFrequency = Math.floor(8 + Math.random() * 7); - bullet[me].onEnd = b.explode; //makes bullet do explosive damage at end - bullet[me].onDmg = function () { - this.endCycle = 0; //bullet ends cycle after doing damage // also triggers explosion - }; - bullet[me].lockedOn = null; - bullet[me].do = function () { - if (!mech.isBodiesAsleep) { - if (!(mech.cycle % this.lookFrequency)) { - this.closestTarget = null; - this.lockedOn = null; - let closeDist = Infinity; - - //look for targets - for (let i = 0, len = mob.length; i < len; ++i) { - if ( - mob[i].alive && - mob[i].dropPowerUp && - Matter.Query.ray(map, this.position, mob[i].position).length === 0 && - Matter.Query.ray(body, this.position, mob[i].position).length === 0 - ) { - const dist = Matter.Vector.magnitude(Matter.Vector.sub(this.position, mob[i].position)); - if (dist < closeDist) { - this.closestTarget = mob[i].position; - closeDist = dist; - this.lockedOn = mob[i]; - } - } - } - //explode when bullet is close enough to target - if (this.closestTarget && closeDist < this.explodeRad) { - this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion - } - - if (this.lockedOn) { - this.frictionAir = 0.04; //extra friction - - //draw locked on targeting - ctx.beginPath(); - const vertices = this.lockedOn.vertices; - ctx.moveTo(this.position.x, this.position.y); - const mod = Math.floor((game.cycle / 3) % vertices.length); - ctx.lineTo(vertices[mod].x, vertices[mod].y); - ctx.strokeStyle = "rgba(0,0,155,0.35)"; //"#2f6"; - ctx.lineWidth = 1; - ctx.stroke(); - } - } - - //rotate missile towards the target - if (this.closestTarget) { - const face = { - x: Math.cos(this.angle), - y: Math.sin(this.angle) - }; - const target = Matter.Vector.normalise(Matter.Vector.sub(this.position, this.closestTarget)); - if (Matter.Vector.dot(target, face) > -0.98) { - if (Matter.Vector.cross(target, face) > 0) { - Matter.Body.rotate(this, 0.08); - } else { - Matter.Body.rotate(this, -0.08); - } - } - } - //accelerate in direction bullet is facing - const dir = this.angle; // + (Math.random() - 0.5); - this.force.x += Math.cos(dir) * thrust; - this.force.y += Math.sin(dir) * thrust; - - //draw rocket - ctx.beginPath(); - ctx.arc(this.position.x - Math.cos(this.angle) * 27 + (Math.random() - 0.5) * 4, this.position.y - Math.sin(this.angle) * 27 + (Math.random() - 0.5) * 4, 11, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(255,155,0,0.5)"; - ctx.fill(); - } else { - //draw rocket with time stop - ctx.beginPath(); - ctx.arc(this.position.x - Math.cos(this.angle) * 27, this.position.y - Math.sin(this.angle) * 27, 11, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(255,155,0,0.5)"; - ctx.fill(); - } - } - } - }, { - name: "flak", //8 - description: "fire a cluster of short range projectiles
explodes on contact or after half a second", - ammo: 0, - ammoPack: 20, - have: false, - isStarterGun: true, - fire() { - b.muzzleFlash(30); - const totalBullets = 5 - const angleStep = (mech.crouch ? 0.06 : 0.15) / totalBullets - const SPEED = mech.crouch ? 30 : 25 - const CD = mech.crouch ? 45 : 11 - const END = Math.floor((mech.crouch ? 30 : 18) * b.isModBulletsLastLonger); - let dir = mech.angle - angleStep * totalBullets / 2; - const side1 = 17 * b.modBulletSize - const side2 = 4 * b.modBulletSize - - 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)); - b.fireProps(CD, SPEED + 15 * Math.random() - 2 * i, dir, me); //cd , speed - // Matter.Body.setDensity(bullet[me], 0.005); - bullet[me].endCycle = 2 * i + game.cycle + END - bullet[me].restitution = 0; - bullet[me].friction = 1; - // bullet[me].dmg = 0.15; - bullet[me].explodeRad = (mech.crouch ? 70 : 45) + (Math.random() - 0.5) * 50; - bullet[me].onEnd = b.explode; - bullet[me].onDmg = function () { - this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion - }; - bullet[me].do = function () { - this.force.y += this.mass * 0.0004; - // if (this.speed < 10) { //if slow explode - // for (let i = 0, len = bullet.length; i < len; i++) { - // bullet[i].endCycle = 0 //all other bullets explode - // } - // } - } - } - } - }, { - name: "grenades", //9 - description: "lob a single bouncy projectile
explodes on contact or after one second", - ammo: 0, - ammoPack: 9, - have: false, - isStarterGun: false, - fire() { - const me = bullet.length; - const dir = mech.angle; // + Math.random() * 0.05; - bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 15 * b.modBulletSize, b.fireAttributes(dir, false)); - b.fireProps(mech.crouch ? 40 : 20, mech.crouch ? 43 : 32, dir, me); //cd , speed - b.drawOneBullet(bullet[me].vertices); - // Matter.Body.setDensity(bullet[me], 0.000001); - bullet[me].totalCycles = 100; - bullet[me].endCycle = game.cycle + Math.floor((mech.crouch ? 120 : 60) * b.isModBulletsLastLonger); - bullet[me].restitution = 0.5; - bullet[me].explodeRad = 210; - bullet[me].onEnd = b.explode; //makes bullet do explosive damage before despawn - bullet[me].minDmgSpeed = 1; - bullet[me].onDmg = function () { - this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion - }; - bullet[me].do = function () { - //extra gravity for harder arcs - this.force.y += this.mass * 0.002; - }; - } - }, { - name: "vacuum bomb", //10 - description: "fire a bomb that sucks before exploding
click left mouse again to detonate", - ammo: 0, - ammoPack: 5, - have: false, - isStarterGun: false, - fire() { - const me = bullet.length; - const dir = mech.angle; - bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 26 * b.modBulletSize, b.fireAttributes(dir, false)); - bullet[me].radius = 22; //used from drawing timer - b.fireProps(10, mech.crouch ? 42 : 26, dir, me); //cd , speed - - b.drawOneBullet(bullet[me].vertices); - bullet[me].endCycle = Infinity - // bullet[me].restitution = 0.3; - // bullet[me].frictionAir = 0.01; - // bullet[me].friction = 0.15; - bullet[me].inertia = Infinity; //prevents rotation - bullet[me].restitution = 0; - bullet[me].friction = 1; - - bullet[me].explodeRad = 380 + Math.floor(Math.random() * 60); - bullet[me].onEnd = b.explode; //makes bullet do explosive damage before despawn - bullet[me].onDmg = function () { - // this.endCycle = 0; //bullet ends cycle after doing damage //this triggers explosion - }; - bullet[me].isArmed = false; - bullet[me].isSucking = false; - bullet[me].do = function () { - //extra gravity for harder arcs - this.force.y += this.mass * 0.0022; - mech.fireCDcycle = mech.cycle + 10 //can't fire until after the explosion - - //set armed and sucking status - if (!this.isArmed && !game.mouseDown) { - this.isArmed = true - } else if (this.isArmed && game.mouseDown && !this.isSucking) { - this.isSucking = true; - this.endCycle = game.cycle + 35; - } - - if (this.isSucking) { - if (!mech.isBodiesAsleep) { - const that = this - let mag = 0.1 - - function suck(who, radius = that.explodeRad * 2) { - for (i = 0, len = who.length; i < len; i++) { - const sub = Matter.Vector.sub(that.position, who[i].position); - const dist = Matter.Vector.magnitude(sub); - if (dist < radius && dist > 150) { - knock = Matter.Vector.mult(Matter.Vector.normalise(sub), mag * who[i].mass / Math.sqrt(dist)); - who[i].force.x += knock.x; - who[i].force.y += knock.y; - } - } - } - if (game.cycle > this.endCycle - 5) { - mag = -0.22 - suck(body) - suck(mob) - suck(powerUp) - suck(bullet) - suck([player]) - } else { - mag = 0.1 - suck(body) - suck(mob) - suck(powerUp) - suck(bullet) - suck([player]) - } - //keep bomb in place - Matter.Body.setVelocity(this, { - x: 0, - y: 0 - }); - //draw suck - const radius = 2.5 * this.explodeRad * (this.endCycle - game.cycle) / 35 - 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(); - } - } else { - // flashing lights to show armed - if (!(game.cycle % 10)) { - if (this.isFlashOn) { - this.isFlashOn = false; - } else { - this.isFlashOn = true; - } - } - if (this.isFlashOn) { - ctx.fillStyle = "#000"; - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.radius, 0, 2 * Math.PI); - ctx.fill(); - //draw clock on timer - ctx.fillStyle = "#f04"; - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.radius * 0.5, 0, 2 * Math.PI); - ctx.fill(); - } - } - } - } - }, { - name: "ferro frag", //11 - description: "fire a grenade that ejects magnetized nails
nails are attracted to enemies", - ammo: 0, - ammoPack: 8, - have: false, - isStarterGun: false, - fire() { - const me = bullet.length; - const dir = mech.angle; - bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 15 * b.modBulletSize, b.fireAttributes(dir, false)); - b.fireProps(mech.crouch ? 40 : 30, mech.crouch ? 34 : 22, dir, me); //cd , speed - b.drawOneBullet(bullet[me].vertices); - bullet[me].endCycle = game.cycle + Math.floor(60 * b.isModBulletsLastLonger); - bullet[me].restitution = 0.3; - // bullet[me].frictionAir = 0.01; - // bullet[me].friction = 0.15; - // bullet[me].friction = 1; - bullet[me].onEnd = () => {} - bullet[me].do = function () { - this.force.y += this.mass * 0.0018; //extra gravity for grenades - - if (game.cycle > this.endCycle - 1) { - if (!mech.isBodiesAsleep) { - //target nearby mobs - const targets = [] - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].dropPowerUp) { - const sub = Matter.Vector.sub(this.position, mob[i].position); - const dist = Matter.Vector.magnitude(sub); - if (dist < 1400 && - Matter.Query.ray(map, this.position, mob[i].position).length === 0 && - Matter.Query.ray(body, this.position, mob[i].position).length === 0) { - targets.push( - Matter.Vector.add(mob[i].position, Matter.Vector.mult(mob[i].velocity, dist / 60)) - ) - } - } - } - for (let i = 0; i < 14; i++) { - const speed = 55 + 10 * Math.random() - if (targets.length > 0) { // aim near a random target - 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) - } - needle(this.position, Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(WHERE, this.position)), speed)) - } else { // aim in random direction - const ANGLE = 2 * Math.PI * Math.random() - needle(this.position, { - x: speed * Math.cos(ANGLE), - y: speed * Math.sin(ANGLE) - }) - } - - function needle(pos, velocity) { - const me = bullet.length; - bullet[me] = Bodies.rectangle(pos.x, pos.y, 25 * b.modBulletSize, 2 * b.modBulletSize, 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 + 15 * Math.random(); - // bullet[me].dmg = 1.1+b.modExtraDmg; - bullet[me].do = function () {}; - } - } - } - } - } - } - }, { - name: "spores", //12 - description: "fire orbs that discharge spores
spores seek out enemies", - ammo: 0, - ammoPack: 5, - have: false, - isStarterGun: 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 ? 75 : 55, mech.crouch ? 25 : 14, dir, me); //cd , speed - b.drawOneBullet(bullet[me].vertices); - Matter.Body.setDensity(bullet[me], 0.000001); - bullet[me].endCycle = game.cycle + 100; - bullet[me].frictionAir = 0; - bullet[me].friction = 0.5; - bullet[me].restitution = 0.3; - bullet[me].minDmgSpeed = 0; - bullet[me].onDmg = function () {}; - bullet[me].do = function () { - if (!mech.isBodiesAsleep) { - const SCALE = 1.017 - Matter.Body.scale(this, SCALE, SCALE); - this.frictionAir += 0.00023; - } - - 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, 26, 0, 2 * Math.PI); - ctx.fill(); - }; - - //spawn bullets on end - bullet[me].onEnd = function () { - const NUM = 9; - for (let i = 0; i < NUM; i++) { - const bIndex = bullet.length; - const RADIUS = 3 * b.modBulletSize; - bullet[bIndex] = Bodies.circle(this.position.x, this.position.y, RADIUS, { - // density: 0.0015, //frictionAir: 0.01, - inertia: Infinity, - restitution: 0.5, - angle: dir, - friction: 0, - frictionAir: 0.011, - dmg: 1.8 + b.modExtraDmg, //damage done in addition to the damage from momentum - classType: "bullet", - collisionFilter: { - category: 0x000100, - mask: 0x000011 //no collide with body - }, - endCycle: game.cycle + Math.floor((360 + Math.floor(Math.random() * 240)) * b.isModBulletsLastLonger), - minDmgSpeed: 0, - onDmg() { - this.endCycle = 0; //bullet ends cycle after doing damage - }, - onEnd() {}, - lookFrequency: 67 + Math.floor(47 * Math.random()), - do() { - //find mob targets - if (!(game.cycle % this.lookFrequency)) { - this.closestTarget = null; - this.lockedOn = null; - let closeDist = Infinity; - for (let i = 0, len = mob.length; i < len; ++i) { - if (Matter.Query.ray(map, this.position, mob[i].position).length === 0) { - // Matter.Query.ray(body, this.position, mob[i].position).length === 0 - const targetVector = Matter.Vector.sub(this.position, mob[i].position) - const dist = Matter.Vector.magnitude(targetVector); - if (dist < closeDist) { - this.closestTarget = mob[i].position; - closeDist = dist; - this.lockedOn = Matter.Vector.normalise(targetVector); - if (0.3 > Math.random()) break //doesn't always target the closest mob - } - } - } - } - //accelerate towards mobs - const THRUST = this.mass * 0.0009 - if (this.lockedOn) { - this.force.x -= THRUST * this.lockedOn.x - this.force.y -= THRUST * this.lockedOn.y - } else { - this.force.y += this.mass * 0.00025; //gravity - } - }, - }); - const SPEED = 9; - 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 - } - } - - } - }, - { - name: "drones", //13 - description: "deploy drones that seek out enemies
collisions reduce drone cycles by 1 second", - ammo: 0, - ammoPack: 20, - have: false, - isStarterGun: true, - fire() { - const THRUST = 0.0015 - const dir = mech.angle + 0.2 * (Math.random() - 0.5); - const me = bullet.length; - const RADIUS = (4.5 + 3 * Math.random()) * b.modBulletSize - bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), RADIUS, { - angle: dir, - inertia: Infinity, - friction: 0.05, - frictionAir: 0.0005, - restitution: 1, - dmg: 0.13 + b.modExtraDmg, //damage done in addition to the damage from momentum - lookFrequency: 83 + Math.floor(41 * Math.random()), - endCycle: game.cycle + Math.floor((1080 + 360 * Math.random()) * b.isModBulletsLastLonger), - classType: "bullet", - collisionFilter: { - category: 0x000100, - mask: 0x010111 //self collide - }, - minDmgSpeed: 0, - lockedOn: null, - isFollowMouse: true, - onDmg() { - this.lockedOn = null - if (this.endCycle > game.cycle + 180) { - this.endCycle -= 60 - if (game.cycle + 180 > this.endCycle) this.endCycle = game.cycle + 180 - } - }, - onEnd() {}, - do() { - if (game.cycle + 180 > this.endCycle) { //fall and die - this.force.y += this.mass * 0.0012; - this.restitution = 0.2; - } 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 ( - 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 = Matter.Vector.sub(this.position, mob[i].position) - const DIST = Matter.Vector.magnitude(TARGET_VECTOR); - if (DIST < closeDist) { - closeDist = DIST; - this.lockedOn = mob[i] - } - } - } - if (!this.lockedOn) { - //grab a power up if it is (ammo) or (a heal when player is low) - let closeDist = Infinity; - for (let i = 0, len = powerUp.length; i < len; ++i) { - if ( - ((powerUp[i].name !== "field" && powerUp[i].name !== "heal") || (powerUp[i].name === "heal" && mech.health < 0.8)) && - 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 = Matter.Vector.sub(this.position, powerUp[i].position) - const DIST = Matter.Vector.magnitude(TARGET_VECTOR); - if (DIST < closeDist) { - if (DIST < 50) { //eat the power up if close enough - powerUp[i].effect(); - Matter.World.remove(engine.world, powerUp[i]); - powerUp.splice(i, 1); - break; - } - closeDist = DIST; - this.lockedOn = powerUp[i] - } - } - } - } - } - if (this.lockedOn) { //accelerate towards mobs - this.force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(this.position, this.lockedOn.position)), -this.mass * THRUST) - } else { //accelerate towards mouse - this.force = Matter.Vector.mult(Matter.Vector.normalise(Matter.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 - }); - } - } - } - }) - b.fireProps(mech.crouch ? 14 : 10, mech.crouch ? 40 : 1, dir, me); //cd , speed - b.drawOneBullet(bullet[me].vertices); - } - }, - // { - // name: "laser-bot", //14 - // description: "deploy bots that fire lasers at nearby enemies
bots last for one level", - // ammo: 0, - // ammoPack: 1, - // have: false, - // isStarterGun: false, - // fire() { - // const THRUST = 0.004 - // const dir = mech.angle; - // const me = bullet.length; - // const RADIUS = (15 + 8 * Math.random()) * b.modBulletSize - // const LENGTH = 0.6 + 0.8 * Math.random() - - // bullet[me] = Bodies.rectangle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), RADIUS * LENGTH, RADIUS / LENGTH, { - // angle: dir, - // // inertia: Infinity, - // // friction: 0, - // density: 0.001, //normal is 0.001 - // frictionAir: 0.06, - // restitution: 0.8, - // dmg: b.modExtraDmg, // 0.14 //damage done in addition to the damage from momentum - // minDmgSpeed: 2, - // lookFrequency: 7 + Math.floor(17 * Math.random()), - // endCycle: Infinity, - // classType: "bullet", - // collisionFilter: { - // category: 0x000100, - // mask: 0x010111 //self, mob,map,body collide - // }, - // range: 300, - // lockedOn: null, - // onDmg() { - // this.lockedOn = null - // // this.endCycle -= 120; //lose 2 seconds after damage is done - // }, - // onEnd() {}, - // do() { - // if (!(game.cycle % this.lookFrequency)) { - // this.lockedOn = null; - // let closeDist = Infinity; - // for (let i = 0, len = mob.length; i < len; ++i) { - // const TARGET_VECTOR = Matter.Vector.sub(mech.pos, mob[i].position) - // const DIST = Matter.Vector.magnitude(TARGET_VECTOR); - // // DIST - mob[i].radius < this.range && - // if (DIST < closeDist && Matter.Query.ray(map, this.position, mob[i].position).length === 0) { - // closeDist = DIST; - // this.lockedOn = mob[i] - // } - // } - // } - - // const distanceToPlayer = Matter.Vector.magnitude(Matter.Vector.sub(this.position, mech.pos)) - // if (this.lockedOn) { //accelerate towards mobs - // this.force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(this.position, this.lockedOn.position)), -this.mass * THRUST) - // this.frictionAir = 0.06 - // } else if (distanceToPlayer > 100) { - // this.force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(this.position, mech.pos)), -this.mass * THRUST * 0.3) - // this.frictionAir = 0.02 - // } else { //must be close to player //add some random motion - // this.frictionAir = 0 - // } - // } - // }) - // b.fireProps(mech.crouch ? 5 : 10, 15, dir, me); //cd , speed - // b.drawOneBullet(bullet[me].vertices); - // } - // }, - // { - // name: "dwarf star", //14 - // description: "drop a mine that gravitational pulls in matter", - // ammo: 0, - // ammoPack: 1000, - // have: false, - // isStarterGun: false, - // fire() { - // const me = bullet.length; - // const dir = mech.angle - // const TOTAL_CYCLES = 1020 - // bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(dir), mech.pos.y + 30 * Math.sin(dir), 3 * b.modBulletSize, { - // density: 0.05, - // //frictionAir: 0.01, - // restitution: 0, - // angle: 0, - // friction: 1, - // // frictionAir: 1, - // endCycle: game.cycle + TOTAL_CYCLES, - // dmg: b.modExtraDmg, //damage done in addition to the damage from momentum - // classType: "bullet", - // collisionFilter: { - // category: 0x000100, - // mask: 0x010011 //mask: 0x000101, //for self collision - // }, - // minDmgSpeed: 5, - // range: 0, - // onDmg() { - // this.endCycle = 0; - // }, //this.endCycle = 0 //triggers despawn - // onEnd() {}, - // do() { - // this.force.y += this.mass * 0.005; - // this.range += 0.5 - - // //damage nearby mobs - // const dmg = b.dmgScale * 0.02 - // for (let i = 0, len = mob.length; i < len; ++i) { - // if (mob[i].alive) { - // sub = Matter.Vector.sub(this.position, mob[i].position); - // dist = Matter.Vector.magnitude(sub) - mob[i].radius; - // if (dist < this.range) { - // mob[i].damage(dmg); - // mob[i].locatePlayer(); - // } - // } - // } - - // //pull in body, and power ups?, and bullets? - // for (let i = 0, len = body.length; i < len; ++i) { - // sub = Matter.Vector.sub(this.position, body[i].position); - // dist = Matter.Vector.magnitude(sub) - // if (dist < this.range) { - // this.range += body[i].mass * 2 - // Matter.World.remove(engine.world, body[i]); - // body.splice(i, 1); - // break; - // } - // } - - // //draw - // const opacity = (this.endCycle - game.cycle) / TOTAL_CYCLES - // ctx.fillStyle = `rgba(170,220,255,${opacity})`; - // ctx.beginPath(); - // ctx.arc(this.position.x, this.position.y, this.range, 0, 2 * Math.PI); - // ctx.fill(); - // } - // }); - // b.fireProps(60, 0, dir, me); //cd , speed - // } - // }, - // { - // name: "kinetic slugs", //1 - // description: "fire a large rod that does excessive physical damage
high recoil", - // ammo: 0, - // ammoPack: 5, - // have: false, - // isStarterGun: true, - // fire() { - // b.muzzleFlash(45); - // // mobs.alert(800); - // const me = bullet.length; - // const dir = mech.angle; - // bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), 70 * b.modBulletSize, 30 * b.modBulletSize, b.fireAttributes(dir)); - // b.fireProps(mech.crouch ? 55 : 40, 50, dir, me); //cd , speed - // bullet[me].endCycle = game.cycle + Math.floor(180 * b.isModBulletsLastLonger); - // bullet[me].do = function () { - // this.force.y += this.mass * 0.0005; - // }; - - // //knock back - // const KNOCK = ((mech.crouch) ? 0.025 : 0.25) * b.modBulletSize * b.modBulletSize - // player.force.x -= KNOCK * Math.cos(dir) - // player.force.y -= KNOCK * Math.sin(dir) * 0.3 //reduce knock back in vertical direction to stop super jumps - // }, - // { - // name: "triboelectricty", //14 - // description: "release particles that quickly seek out targets", - // ammo: 0, - // ammoPack: 40, - // have: false, - // isStarterGun: true, - // fire() { - // const dir = mech.angle + 0.2 * (Math.random() - 0.5); - // const me = bullet.length; - // const RADIUS = 6 * b.modBulletSize - // bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), RADIUS, { - // angle: dir, - // inertia: Infinity, - // // friction: 0.05, - // // frictionAir: 0.05, - // restitution: 0.8, - // dmg: 0.14 + b.modExtraDmg, //damage done in addition to the damage from momentum - // lookFrequency: 3, - // endCycle: game.cycle + Math.floor(120 * b.isModBulletsLastLonger), - // classType: "bullet", - // collisionFilter: { - // category: 0x000100, - // mask: 0x010111 //self collide - // }, - // minDmgSpeed: 0, - // lockedOn: null, - // isFollowMouse: true, - // onDmg() { - // this.endCycle = 0; - // }, - // onEnd() {}, - // do() { - // if (this.lockedOn) { //accelerate towards mobs - // this.force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(this.position, this.lockedOn.position)), -this.mass * 0.01) - // Matter.Body.setVelocity(this, { - // x: this.velocity.x * 0.93, - // y: this.velocity.y * 0.93 - // }); - // } else { - // this.force.y += this.mass * 0.0004; - // } - // } - // }) - - // b.fireProps(mech.crouch ? 19 : 15, mech.crouch ? 45 : 30, dir, me); //cd , speed - // b.drawOneBullet(bullet[me].vertices); - - // //find mob targets - // let closeDist = Infinity; - // for (let i = 0, len = mob.length; i < len; ++i) { - // if ( - // Matter.Query.ray(map, bullet[me].position, mob[i].position).length === 0 && - // Matter.Query.ray(body, bullet[me].position, mob[i].position).length === 0 - // ) { - // const TARGET_VECTOR = Matter.Vector.sub(bullet[me].position, mob[i].position) - // const DIST = Matter.Vector.magnitude(TARGET_VECTOR); - // if (DIST < closeDist) { - // closeDist = DIST; - // bullet[me].lockedOn = mob[i] - // } - // } - // } - // } - // }, - // { - ] +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 + //variables use for gun mod upgrades + modCount: null, + modFireRate: null, + modExplosionRadius: null, + modBulletSize: null, + modEnergySiphon: null, + modHealthDrain: null, + modNoAmmo: null, + isModBulletsLastLonger: null, + modIsImmortal: null, + modSpores: null, + isModTempResist: null, + isModDroneOnDamage: null, + modExtraDmg: null, + annihilation: null, + fullHeal: null, + modSquirrelFx: null, + modIsCrit: null, + modMoreDrops: null, + isModLowHealthDmg: null, + isModFarAwayDmg: null, + isModMonogamy: null, + setModDefaults() { + b.modCount = 0; + b.modFireRate = 1; + b.modExplosionRadius = 1; + b.isModTempResist = false; + b.modBulletSize = 1; + b.isModDroneOnDamage = false; + b.modEnergySiphon = 0; + b.modHealthDrain = 0; + b.modNoAmmo = 0; + b.isModBulletsLastLonger = 1; + b.modIsImmortal = false; + b.modSpores = 0; + b.modExtraDmg = 0; + b.modAnnihilation = false; + b.isModFullHeal = false; + b.modSquirrelFx = 1; + b.modIsCrit = false; + b.modMoreDrops = 0; + b.isModLowHealthDmg = false; + b.isModFarAwayDmg = false; + b.isModMonogamy = false; + mech.Fx = 0.015; + mech.jumpForce = 0.38; + mech.throwChargeRate = 2; + mech.throwChargeMax = 50; + for (let i = 0; i < b.mods.length; i++) { + b.mods[i].have = false; + } + }, + mods: [{ + name: "depleted uranium rounds", + description: "your bullets are 10% larger
increased mass and physical damage", + have: false, //0 + effect: () => { + //good for guns that do mostly projectile damage: + //testing at 1.08: spray(point blank)(+0.25), one shot(+0.16), wave beam(point blank)(+0.14) + b.modBulletSize = 1.1; + } + }, + { + name: "auto-loading heuristics", + description: "your delay after firing is 15% shorter", + have: false, //1 + effect: () => { //good for guns with extra ammo: needles, M80, rapid fire, flak, super balls + b.modFireRate = 0.85 + } + }, + { + name: "desublimated ammunition", + description: "use 50% less ammo when crouching", + have: false, //2 + effect: () => { //good with guns that have less ammo: one shot, grenades, missiles, super balls, spray + b.modNoAmmo = 1 + } + }, + { + name: "Lorentzian topology", + description: "your bullets last 40% longer", + have: false, //3 + effect: () => { //good with: drones, super balls, spore, missiles, wave beam(range), rapid fire(range), flak(range) + b.isModBulletsLastLonger = 1.40 + } + }, + { + name: "anti-matter cores", + description: "the radius of your explosions is doubled
be careful", + have: false, //4 + effect: () => { //at 1.4 gives a flat 40% increase, and increased range, balanced by limited guns and self damage + //testing at 1.3: grenade(+0.3), missiles, flak, M80 + b.modExplosionRadius = 1.8; //good for guns with explosions + } + }, + { + name: "ceramic plating", + description: "protection from to high temperatures
5x less damage from explosions, lasers", + have: false, //5 + effect: () => { + b.isModTempResist = true; //good for guns with explosions + } + }, + { + name: "ablative synthesis", + description: "rebuild your broken parts as drones
chance to occur after taking damage", + have: false, //6 + effect: () => { //makes dangerous situations more survivable + b.isModDroneOnDamage = true; + } + }, + { + name: "zoospore vector", + description: "enemies can discharge spores on death
spores seek out enemies", + have: false, //7 + effect: () => { //good late game maybe? + b.modSpores = 0.20; + } + }, + { + name: "energy transfer", + description: "gain energy proportional to damage done", + have: false, //8 + effect: () => { //good with laser, and all fields + b.modEnergySiphon = 0.2; + } + }, + { + name: "entropy transfer", + description: "heal proportional to damage done", + have: false, //9 + effect: () => { //good with guns that overkill: one shot, grenade + b.modHealthDrain = 0.015; + } + }, + { + name: "quantum immortality", + description: "after dying, continue in an alternate reality
guns, ammo, and field are randomized", + have: false, //10 + effect: () => { + b.modIsImmortal = true; + } + }, + { + name: "fluoroantimonic acid", + description: "each bullet does extra chemical damage", + have: false, //11 + effect: () => { //good with guns that fire many bullets at low speeds, minigun, drones, junk-bots, shotgun, superballs, wavebeam + b.modExtraDmg = 0.1 + } + }, + { + name: "annihilation", + description: "after touching enemies, they are annihilated", + have: false, //12 + effect: () => { //good with mods that heal: superconductive healing, entropy transfer + b.modAnnihilation = true + } + }, + { + name: "recursive healing", + description: "healing power ups bring you to full health", + have: false, //13 + effect: () => { // good with ablative synthesis, melee builds + b.isModFullHeal = true + } + }, + { + name: "Gauss rifle", + description: "launch blocks at much higher speeds
carry more massive blocks", + have: false, //14 + effect: () => { // good with guns that run out of ammo + mech.throwChargeRate = 4; + mech.throwChargeMax = 150; + mech.holdingMassScale = 0.05; //can hold heavier blocks with lower cost to jumping + } + }, + { + name: "squirrel-cage rotor", + description: "your legs produce 20% more force
jump higher and move faster", + have: false, //15 + effect: () => { // good with melee builds, content skipping builds + b.modSquirrelFx = 1.2; + mech.Fx = 0.015 * b.modSquirrelFx; + mech.jumpForce = 0.38 * 1.1; + } + }, + { + name: "fracture analysis", + description: "5x physical damage to unaware enemies
unaware enemies don't have a health bar", + have: false, //16 + effect: () => { // good with high damage guns that strike from a distance: rail gun, drones, flechettes, spores, grenade, vacuum bomb + b.modIsCrit = true; + } + }, + { + name: "kinetic bombardment", + description: "do extra damage from a distance
up to 50% increase at about 30 steps away", + have: false, //17 + effect: () => { // good with annihilation, melee builds + b.isModFarAwayDmg = true; //used in mob.damage() + } + }, + { + name: "quasistatic equilibrium", + description: "do extra damage at low health
up to 50% increase when near death", + have: false, //18 + effect: () => { // good with annihilation, melee builds + b.isModLowHealthDmg = true; //used in mob.damage() + } + }, + { + name: "Bayesian inference", + description: "20% chance for double power ups to drop", + have: false, //19 + effect: () => { // good with long term planning + b.modMoreDrops = 0.20; + } + }, + { + name: "entanglement", + description: "using your first gun reduces damage taken
scales by 7% for each gun in your inventory", + have: false, //20 + effect: () => { // good with long term planning + b.isModMonogamy = true + } + }, + ], + giveMod(i) { + b.mods[i].effect(); //give specific mod + b.modCount++ + b.mods[i].have = true + game.updateModHUD(); + }, + activeGun: null, //current gun in use by player + inventoryGun: 0, + inventory: [], //list of what guns player has // 0 starts with basic gun + giveGuns(gun = "all", ammoPacks = 2) { + if (gun === "all") { + b.activeGun = 0; + b.inventoryGun = 0; + for (let i = 0; i < b.guns.length; i++) { + b.guns[i].have = true; + b.guns[i].ammo = b.guns[i].ammoPack * ammoPacks; + b.inventory[i] = i; + } + } else { + if (!b.guns[gun].have) b.inventory.push(gun); + if (b.activeGun === null) b.activeGun = gun //if no active gun switch to new gun + b.guns[gun].have = true; + b.guns[gun].ammo = b.guns[gun].ammoPack * ammoPacks; + } + game.makeGunHUD(); + }, + fire() { + if (game.mouseDown && mech.fireCDcycle < mech.cycle && (!(keys[32] || game.mouseDownRight) || mech.fieldFire) && b.inventory.length) { + if (b.guns[b.activeGun].ammo > 0) { + b.guns[b.activeGun].fire(); + if (b.modNoAmmo && mech.crouch) { + if (b.modNoAmmo % 2) { + b.guns[b.activeGun].ammo--; + game.updateGunHUD(); + } + b.modNoAmmo++ //makes the no ammo toggle off and on + } else { + b.guns[b.activeGun].ammo--; + game.updateGunHUD(); + } + } else { + mech.fireCDcycle = mech.cycle + 30; //cooldown + // game.makeTextLog("
NO AMMO
E / Q", 200); + game.replaceTextLog = true; + game.makeTextLog("
NO AMMO

Q, E, and mouse wheel change weapons

", 200); + } + if (mech.isHolding) { + mech.drop(); + } + } + }, + draw() { + ctx.beginPath(); + let i = bullet.length; + while (i--) { + //draw + let vertices = bullet[i].vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + //remove bullet if at end cycle for that bullet + if (bullet[i].endCycle < game.cycle) { + bullet[i].onEnd(i); //some bullets do stuff on end + if (bullet[i]) { + Matter.World.remove(engine.world, bullet[i]); + bullet.splice(i, 1); + } else { + break; //if bullet[i] doesn't exist don't complete the for loop, because the game probably reset + } + } + } + ctx.fillStyle = "#000"; + ctx.fill(); + //do things + for (let i = 0, len = bullet.length; i < len; i++) { + bullet[i].do(); + } + }, + fireProps(cd, speed, dir, me) { + mech.fireCDcycle = mech.cycle + Math.floor(cd * b.modFireRate); // cool down + Matter.Body.setVelocity(bullet[me], { + x: mech.Vx / 2 + speed * Math.cos(dir), + y: mech.Vy / 2 + speed * Math.sin(dir) + }); + World.add(engine.world, bullet[me]); //add bullet to world + }, + fireAttributes(dir, rotate = true) { + if (rotate) { + return { + // density: 0.0015, //frictionAir: 0.01, //restitution: 0, + angle: dir, + friction: 0.5, + frictionAir: 0, + dmg: b.modExtraDmg, //damage done in addition to the damage from momentum + classType: "bullet", + collisionFilter: { + category: 0x000100, + mask: 0x010011 //mask: 0x000101, //for self collision + }, + minDmgSpeed: 10, + onDmg() {}, //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: 0x000100, + mask: 0x010011 //mask: 0x000101, //for self collision + }, + minDmgSpeed: 10, + onDmg() {}, //this.endCycle = 0 //triggers despawn + onEnd() {} + }; + } + }, + muzzleFlash(radius = 10) { + ctx.fillStyle = "#fb0"; + ctx.beginPath(); + ctx.arc(mech.pos.x + 35 * Math.cos(mech.angle), mech.pos.y + 35 * Math.sin(mech.angle), radius, 0, 2 * Math.PI); + ctx.fill(); + }, + drawOneBullet(vertices) { + ctx.beginPath(); + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.fillStyle = "#000"; + ctx.fill(); + }, + removeConsBB(me) { + for (let i = 0, len = consBB.length; i < len; ++i) { + if (consBB[i].bodyA === me) { + consBB[i].bodyA = consBB[i].bodyB; + consBB.splice(i, 1); + // b.removeConsBB(me); + break; + } else if (consBB[i].bodyB === me) { + consBB[i].bodyB = consBB[i].bodyA; + consBB.splice(i, 1); + // b.removeConsBB(me); + break; + } + } + }, + explode(me) { + // typically explode is used for some bullets with .onEnd + const radius = bullet[me].explodeRad * b.modExplosionRadius + //add dmg to draw queue + game.drawList.push({ + x: bullet[me].position.x, + y: bullet[me].position.y, + radius: radius, + color: "rgba(255,25,0,0.6)", + time: game.drawTime + }); + let dist, sub, knock; + const dmg = b.dmgScale * radius * 0.009; + + const alertRange = 100 + radius * 2; //alert range + //add alert to draw queue + game.drawList.push({ + x: bullet[me].position.x, + y: bullet[me].position.y, + radius: alertRange, + color: "rgba(100,20,0,0.03)", + time: game.drawTime + }); + + //player damage and knock back + sub = Matter.Vector.sub(bullet[me].position, player.position); + dist = Matter.Vector.magnitude(sub); + if (dist < radius) { + if (b.isModTempResist) { + mech.damage(radius * 0.00004); + } else { + mech.damage(radius * 0.0002); + } + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), -Math.sqrt(dmg) * player.mass / 30); + player.force.x += knock.x; + player.force.y += knock.y; + mech.drop(); + } else if (dist < alertRange) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), -Math.sqrt(dmg) * player.mass / 55); + player.force.x += knock.x; + player.force.y += knock.y; + mech.drop(); + } + + //body knock backs + for (let i = 0, len = body.length; i < len; ++i) { + sub = Matter.Vector.sub(bullet[me].position, body[i].position); + dist = Matter.Vector.magnitude(sub); + if (dist < radius) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) / 18); + body[i].force.x += knock.x; + body[i].force.y += knock.y; + } else if (dist < alertRange) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) / 40); + body[i].force.x += knock.x; + body[i].force.y += knock.y; + } + } + + //power up knock backs + for (let i = 0, len = powerUp.length; i < len; ++i) { + sub = Matter.Vector.sub(bullet[me].position, powerUp[i].position); + dist = Matter.Vector.magnitude(sub); + if (dist < radius) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) / 26); + powerUp[i].force.x += knock.x; + powerUp[i].force.y += knock.y; + } else if (dist < alertRange) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) / 40); + powerUp[i].force.x += knock.x; + powerUp[i].force.y += knock.y; + } + } + + //mob damage and knock back with alert + let damageScale = 1; // reduce dmg for each new target to limit total AOE damage + for (let i = 0, len = mob.length; i < len; ++i) { + if (mob[i].alive) { + sub = Matter.Vector.sub(bullet[me].position, mob[i].position); + dist = Matter.Vector.magnitude(sub) - mob[i].radius; + if (dist < radius) { + mob[i].damage(dmg * damageScale); + mob[i].locatePlayer(); + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) / 18); + mob[i].force.x += knock.x; + mob[i].force.y += knock.y; + damageScale *= 0.8 //reduced damage for each additional explosion target + } else if (!mob[i].seePlayer.recall && dist < alertRange) { + mob[i].locatePlayer(); + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) / 35); + mob[i].force.x += knock.x; + mob[i].force.y += knock.y; + } + } + } + + // Matter.Vector.magnitudeSquared(Matter.Vector.sub(bullet[me].position, mob[i].position)) + }, + spore(who) { //used with the mod upgrade in mob.death() + const bIndex = bullet.length; + const RADIUS = 3 * b.modBulletSize; + bullet[bIndex] = Bodies.circle(who.position.x, who.position.y, RADIUS, { + // density: 0.0015, //frictionAir: 0.01, + inertia: Infinity, + restitution: 0.5, + angle: Math.random() * 2 * Math.PI, + friction: 0, + frictionAir: 0.011, + dmg: 1.8, //damage done in addition to the damage from momentum + classType: "bullet", + collisionFilter: { + category: 0x000100, + mask: 0x000011 //no collide with body + }, + endCycle: game.cycle + Math.floor((360 + Math.floor(Math.random() * 240)) * b.isModBulletsLastLonger), + minDmgSpeed: 0, + onDmg() { + this.endCycle = 0; //bullet ends cycle after doing damage + }, + onEnd() {}, + lookFrequency: 67 + Math.floor(47 * Math.random()), + do() { + //find mob targets + if (!(game.cycle % this.lookFrequency)) { + this.closestTarget = null; + this.lockedOn = null; + let closeDist = Infinity; + for (let i = 0, len = mob.length; i < len; ++i) { + if (Matter.Query.ray(map, this.position, mob[i].position).length === 0) { + // Matter.Query.ray(body, this.position, mob[i].position).length === 0 + const targetVector = Matter.Vector.sub(this.position, mob[i].position) + const dist = Matter.Vector.magnitude(targetVector); + if (dist < closeDist) { + this.closestTarget = mob[i].position; + closeDist = dist; + this.lockedOn = Matter.Vector.normalise(targetVector); + if (0.3 > Math.random()) break //doesn't always target the closest mob + } + } + } + } + //accelerate towards mobs + const THRUST = this.mass * 0.0009 + if (this.lockedOn) { + this.force.x -= THRUST * this.lockedOn.x + this.force.y -= THRUST * this.lockedOn.y + } else { + this.force.y += this.mass * 0.00027; //gravity + } + }, + }); + const SPEED = 9; + 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 + }, + guns: [{ + name: "laser", //0 + description: "emit a beam of damaging coherent light
uses energy instead of ammunition", + ammo: 0, + // ammoPack: 350, + ammoPack: Infinity, + have: false, + isStarterGun: true, + fire() { + // mech.fireCDcycle = mech.cycle + 1 + //laser drains energy as well as bullets + const FIELD_DRAIN = 0.002 + const damage = 0.05 + if (mech.fieldMeter < FIELD_DRAIN) { + mech.fireCDcycle = mech.cycle + 100; // cool down if out of energy + } else { + mech.fieldMeter -= mech.fieldRegen + FIELD_DRAIN + let best; + const color = "#f00"; + const range = 3000; + const path = [{ + x: mech.pos.x + 20 * Math.cos(mech.angle), + y: mech.pos.y + 20 * Math.sin(mech.angle) + }, + { + x: mech.pos.x + range * Math.cos(mech.angle), + y: mech.pos.y + range * Math.sin(mech.angle) + } + ]; + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + }; + const checkForCollisions = function () { + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + vertexCollision(path[path.length - 2], path[path.length - 1], mob); + vertexCollision(path[path.length - 2], path[path.length - 1], map); + vertexCollision(path[path.length - 2], path[path.length - 1], body); + }; + const laserHitMob = function (dmg) { + if (best.who.alive) { + dmg *= b.dmgScale * damage; + best.who.damage(dmg); + best.who.locatePlayer(); + //draw mob damage circle + ctx.fillStyle = color; + ctx.beginPath(); + ctx.arc(path[path.length - 1].x, path[path.length - 1].y, Math.sqrt(dmg) * 100, 0, 2 * Math.PI); + ctx.fill(); + } + }; + + const reflection = function () { + // https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector + const n = Matter.Vector.perp(Matter.Vector.normalise(Matter.Vector.sub(best.v1, best.v2))); + const d = Matter.Vector.sub(path[path.length - 1], path[path.length - 2]); + const nn = Matter.Vector.mult(n, 2 * Matter.Vector.dot(d, n)); + const r = Matter.Vector.normalise(Matter.Vector.sub(d, nn)); + path[path.length] = Matter.Vector.add(Matter.Vector.mult(r, range), path[path.length - 1]); + }; + //beam before reflection + checkForCollisions(); + if (best.dist2 != Infinity) { + //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + laserHitMob(1); + + //1st reflection beam + reflection(); + //ugly bug fix: this stops the reflection on a bug where the beam gets trapped inside a body + let who = best.who; + checkForCollisions(); + if (best.dist2 != Infinity) { + //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + laserHitMob(0.75); + + //2nd reflection beam + //ugly bug fix: this stops the reflection on a bug where the beam gets trapped inside a body + if (who !== best.who) { + reflection(); + checkForCollisions(); + if (best.dist2 != Infinity) { + //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + laserHitMob(0.5); + } + } + } + } + ctx.fillStyle = color; + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.lineDashOffset = 300 * Math.random() + // ctx.setLineDash([200 * Math.random(), 250 * Math.random()]); + + ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]); + for (let i = 1, len = path.length; i < len; ++i) { + ctx.beginPath(); + ctx.moveTo(path[i - 1].x, path[i - 1].y); + ctx.lineTo(path[i].x, path[i].y); + ctx.stroke(); + ctx.globalAlpha *= 0.5; //reflections are less intense + // ctx.globalAlpha -= 0.1; //reflections are less intense + } + ctx.setLineDash([0, 0]); + ctx.globalAlpha = 1; + } + } + }, + { + name: "rail gun", //1 + description: "electro-magnetically launch a dense rod
hold left mouse to charge, release to fire", //and repel enemies + ammo: 0, + ammoPack: 7, + have: false, + isStarterGun: false, + fire() { + const me = bullet.length; + bullet[me] = Bodies.rectangle(0, 0, 0.012 * b.modBulletSize, 0.0025 * b.modBulletSize, { + density: 0.01, //0.001 is normal + //frictionAir: 0.01, //restitution: 0, + // angle: 0, + // friction: 0.5, + frictionAir: 0, + dmg: b.modExtraDmg, //damage done in addition to the damage from momentum + classType: "bullet", + collisionFilter: { + category: 0x000000, + mask: 0x010011 //mask: 0x000101, //for self collision + }, + minDmgSpeed: 5, + onDmg() {}, //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].isCharging = true; + bullet[me].charge = 0; + bullet[me].do = function () { + if (this.isCharging) { + if ((!game.mouseDown && this.charge > 0.6)) { //fire on mouse release + this.isCharging = false + mech.fireCDcycle = mech.cycle + 2; // set fire cool down + 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 + Math.floor(140 * b.isModBulletsLastLonger) + this.collisionFilter.category = 0x000100 + 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) * b.modBulletSize * b.modBulletSize * 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 = 450 * this.charge + for (let i = 0, len = body.length; i < len; ++i) { + const SUB = Matter.Vector.sub(body[i].position, mech.pos) + const DISTANCE = Matter.Vector.magnitude(SUB) + + if (DISTANCE < range) { + const DEPTH = Math.max(range - DISTANCE, 100) + const FORCE = Matter.Vector.mult(Matter.Vector.normalise(SUB), 0.005 * Math.sqrt(DEPTH) * Math.sqrt(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 = Matter.Vector.sub(mob[i].position, mech.pos) + const DISTANCE = Matter.Vector.magnitude(SUB) + + if (DISTANCE < range) { + const DEPTH = Math.max(range - DISTANCE, 100) + const FORCE = Matter.Vector.mult(Matter.Vector.normalise(SUB), 0.005 * Math.sqrt(DEPTH) * Math.sqrt(mob[i].mass)) + mob[i].force.x += 1.5 * FORCE.x; + mob[i].force.y += 1.5 * FORCE.y; + } + } + //push mobs around player when firing + // range = 600 * this.charge + // for (let i = 0, len = mob.length; i < len; ++i) { + // const SUB = Matter.Vector.sub(mob[i].position, mech.pos) + // const DISTANCE = Matter.Vector.magnitude(SUB) + // if (DISTANCE < range) { + // const DEPTH = range - DISTANCE + // const FORCE = Matter.Vector.mult(Matter.Vector.normalise(SUB), 0.00000001 * DEPTH * DEPTH * DEPTH * Math.sqrt(mob[i].mass)) + // mob[i].force.x += FORCE.x + // mob[i].force.y += FORCE.y + // } + // } + } else { // charging on mouse down + mech.fireCDcycle = Infinity //can't fire until mouse is released + if (mech.crouch) { + this.charge = this.charge * 0.97 + 0.03 // this.charge converges to 1 + } else { + this.charge = this.charge * 0.987 + 0.013 // this.charge converges to 1 + } + + //gently push away mobs while charging + // const RANGE = 270 * this.charge + // for (let i = 0, len = mob.length; i < len; ++i) { + // const SUB = Matter.Vector.sub(mob[i].position, mech.pos) + // const DISTANCE = Matter.Vector.magnitude(SUB) + // // if (DISTANCE < RANGE) { + // // Matter.Body.setVelocity(mob[i], Matter.Vector.rotate(mob[i].velocity, 0.1)) + // // } + // // const DRAIN = 0.0002 //&& mech.fieldMeter > DRAIN + // if (DISTANCE < RANGE) { + // // mech.fieldMeter -= DRAIN + mech.fieldRegen; + // const DEPTH = RANGE - DISTANCE + // const FORCE = Matter.Vector.mult(Matter.Vector.normalise(SUB), 0.000000001 * DEPTH * DEPTH * DEPTH * Math.sqrt(mob[i].mass)) + // mob[i].force.x += FORCE.x + // mob[i].force.y += FORCE.y + // } + // } + + //draw laser 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 laser 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 = Matter.Vector.normalise(Matter.Vector.sub(game.mouseInGame, mech.pos)) + const unitVectorPerp = Matter.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(); + } + } + } else { //normal bullet behavior + this.force.y += this.mass * 0.00015 / this.charge; // low gravity that scales with charge + } + } + } + }, + { + name: "minigun", //2 + description: "rapidly fire a stream of small bullets", + ammo: 0, + ammoPack: 105, + have: false, + isStarterGun: true, + fire() { + const me = bullet.length; + b.muzzleFlash(15); + // if (Math.random() > 0.2) mobs.alert(500); + const dir = mech.angle + (Math.random() - 0.5) * ((mech.crouch) ? 0.03 : 0.14); + bullet[me] = Bodies.rectangle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 17 * b.modBulletSize, 5 * b.modBulletSize, b.fireAttributes(dir)); + b.fireProps(mech.crouch ? 11 : 5, mech.crouch ? 44 : 36, dir, me); //cd , speed + bullet[me].endCycle = game.cycle + Math.floor(65 * b.isModBulletsLastLonger); + bullet[me].frictionAir = mech.crouch ? 0.007 : 0.01; + bullet[me].do = function () { + this.force.y += this.mass * 0.0005; + }; + } + }, { + name: "wave beam", //3 + description: "emit a sine wave of oscillating particles
particles propagate through walls", + ammo: 0, + ammoPack: 85, + have: false, + isStarterGun: true, + fire() { + const me = bullet.length; + const DIR = mech.angle + const SCALE = (mech.crouch ? 0.963 : 0.95) + const wiggleMag = ((mech.flipLegs === 1) ? 1 : -1) * ((mech.crouch) ? 0.004 : 0.005) + bullet[me] = Bodies.circle(mech.pos.x + 25 * Math.cos(DIR), mech.pos.y + 25 * Math.sin(DIR), 10 * b.modBulletSize, { + angle: DIR, + cycle: -0.43, //adjust this number until the bullets line up with the cross hairs + endCycle: game.cycle + Math.floor((mech.crouch ? 155 : 120) * b.isModBulletsLastLonger), + inertia: Infinity, + frictionAir: 0, + minDmgSpeed: 0, + dmg: 0.13 + b.modExtraDmg, //damage done in addition to the damage from momentum + classType: "bullet", + collisionFilter: { + category: 0x000100, + mask: 0x000010 + }, + onDmg() {}, + onEnd() {}, + do() { + if (!mech.isBodiesAsleep) { + this.cycle++ + const THRUST = wiggleMag * Math.cos(this.cycle * 0.3) + this.force = Matter.Vector.mult(Matter.Vector.normalise(this.direction), this.mass * THRUST) //wiggle + + if (this.cycle > 0 && !(Math.floor(this.cycle) % 6)) Matter.Body.scale(this, SCALE, SCALE); //shrink + } + } + }); + World.add(engine.world, bullet[me]); //add bullet to world + mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 8 : 4) * b.modFireRate); // cool down + const SPEED = mech.crouch ? 5.2 : 4.5; + Matter.Body.setVelocity(bullet[me], { + x: SPEED * Math.cos(DIR), + y: SPEED * Math.sin(DIR) + }); + bullet[me].direction = Matter.Vector.perp(bullet[me].velocity) + // if (mech.angle + Math.PI / 2 > 0) { + // bullet[me].direction = Matter.Vector.perp(bullet[me].velocity, true) + // } else { + // bullet[me].direction = Matter.Vector.perp(bullet[me].velocity) + // } + + World.add(engine.world, bullet[me]); //add bullet to world + } + }, { + name: "super balls", //4 + description: "fire balls that bounce with no momentum loss", + ammo: 0, + ammoPack: 11, + have: false, + isStarterGun: true, + fire() { + b.muzzleFlash(20); + // mobs.alert(450); + const SPREAD = mech.crouch ? 0.04 : 0.14 + let dir = mech.angle - SPREAD; + for (let i = 0; i < 3; i++) { + const me = bullet.length; + bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 7 * b.modBulletSize, b.fireAttributes(dir, false)); + b.fireProps(mech.crouch ? 40 : 20, mech.crouch ? 34 : 26, dir, me); //cd , speed + Matter.Body.setDensity(bullet[me], 0.0001); + bullet[me].endCycle = game.cycle + Math.floor(360 * b.isModBulletsLastLonger); + bullet[me].dmg = 0.5 + b.modExtraDmg; + bullet[me].minDmgSpeed = 0; + bullet[me].restitution = 0.96; + bullet[me].friction = 0; + bullet[me].do = function () { + this.force.y += this.mass * 0.001; + }; + dir += SPREAD; + } + } + }, { + name: "shotgun", //5 + description: "fire a burst of short range bullets
crouch to reduce recoil", + ammo: 0, + ammoPack: 8, + have: false, + isStarterGun: true, + fire() { + b.muzzleFlash(35); + // mobs.alert(650); + const side = 11 * b.modBulletSize + for (let i = 0; i < 9; i++) { + const me = bullet.length; + const dir = mech.angle + (Math.random() - 0.5) * (mech.crouch ? 0.22 : 0.7) + 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)); + b.fireProps(mech.crouch ? 60 : 30, 40 + Math.random() * 11, dir, me); //cd , speed + bullet[me].endCycle = game.cycle + Math.floor(55 * b.isModBulletsLastLonger); + bullet[me].frictionAir = 0.03; + bullet[me].do = function () { + this.force.y += this.mass * 0.001; + }; + } + + //knock back + const KNOCK = ((mech.crouch) ? 0.015 : 0.15) * b.modBulletSize * b.modBulletSize + 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 + } + }, { + name: "fléchettes", //6 + description: "fire a flight of needles
accurate at long range", + ammo: 0, + ammoPack: 25, + have: false, + isStarterGun: true, + fire() { + function spawnFlechette(dir = mech.angle, speed, size = 1) { + const me = bullet.length; + bullet[me] = Bodies.rectangle(mech.pos.x + 40 * Math.cos(dir), mech.pos.y + 40 * Math.sin(dir), 32 * size * b.modBulletSize, 0.8 * size * b.modBulletSize, b.fireAttributes(dir)); + bullet[me].endCycle = game.cycle + Math.floor(180 * b.isModBulletsLastLonger); + bullet[me].dmg = 0.15 * size + b.modExtraDmg; + b.drawOneBullet(bullet[me].vertices); + bullet[me].do = function () { + this.force.y += this.mass * 0.0002; //low gravity + }; + 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 + } + + if (mech.crouch) { + for (let i = 0; i < 3; i++) { + spawnFlechette(mech.angle + 0.02 * (Math.random() - 0.5), 35 + 4 * i, 1.55) + } + } else { + for (let i = 0; i < 9; i++) { + spawnFlechette(mech.angle + 0.12 * (Math.random() - 0.5), 30 + 8 * Math.random()) + } + } + mech.fireCDcycle = mech.cycle + Math.floor(40 * b.modFireRate); // cool down + } + }, { + name: "missiles", //7 + description: "fire missiles that accelerate towards enemies
explodes when near target", + ammo: 0, + ammoPack: 8, + have: false, + isStarterGun: false, + fireCycle: 0, + ammoLoaded: 0, + fire() { + const thrust = 0.0003; + let dir = mech.angle + (0.5 - Math.random()) * (mech.crouch ? 0 : 0.2); + const me = bullet.length; + bullet[me] = Bodies.rectangle(mech.pos.x + 40 * Math.cos(mech.angle), mech.pos.y + 40 * Math.sin(mech.angle) - 3, 30 * b.modBulletSize, 4 * b.modBulletSize, b.fireAttributes(dir)); + b.fireProps(mech.crouch ? 70 : 30, -3 * (0.5 - Math.random()) + (mech.crouch ? 25 : -8), dir, me); //cd , speed + + b.drawOneBullet(bullet[me].vertices); + // Matter.Body.setDensity(bullet[me], 0.01) //doesn't help with reducing explosion knock backs + bullet[me].force.y += 0.00045; //a small push down at first to make it seem like the missile is briefly falling + bullet[me].frictionAir = 0 + bullet[me].endCycle = game.cycle + Math.floor((265 + Math.random() * 20) * b.isModBulletsLastLonger); + bullet[me].explodeRad = 170 + 60 * Math.random(); + bullet[me].lookFrequency = Math.floor(8 + Math.random() * 7); + bullet[me].onEnd = b.explode; //makes bullet do explosive damage at end + bullet[me].onDmg = function () { + this.endCycle = 0; //bullet ends cycle after doing damage // also triggers explosion + }; + bullet[me].lockedOn = null; + bullet[me].do = function () { + if (!mech.isBodiesAsleep) { + if (!(mech.cycle % this.lookFrequency)) { + this.closestTarget = null; + this.lockedOn = null; + let closeDist = Infinity; + + //look for targets + for (let i = 0, len = mob.length; i < len; ++i) { + if ( + mob[i].alive && + mob[i].dropPowerUp && + Matter.Query.ray(map, this.position, mob[i].position).length === 0 && + Matter.Query.ray(body, this.position, mob[i].position).length === 0 + ) { + const dist = Matter.Vector.magnitude(Matter.Vector.sub(this.position, mob[i].position)); + if (dist < closeDist) { + this.closestTarget = mob[i].position; + closeDist = dist; + this.lockedOn = mob[i]; + } + } + } + //explode when bullet is close enough to target + if (this.closestTarget && closeDist < this.explodeRad) { + this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion + } + + if (this.lockedOn) { + this.frictionAir = 0.04; //extra friction + + //draw locked on targeting + ctx.beginPath(); + const vertices = this.lockedOn.vertices; + ctx.moveTo(this.position.x, this.position.y); + const mod = Math.floor((game.cycle / 3) % vertices.length); + ctx.lineTo(vertices[mod].x, vertices[mod].y); + ctx.strokeStyle = "rgba(0,0,155,0.35)"; //"#2f6"; + ctx.lineWidth = 1; + ctx.stroke(); + } + } + + //rotate missile towards the target + if (this.closestTarget) { + const face = { + x: Math.cos(this.angle), + y: Math.sin(this.angle) + }; + const target = Matter.Vector.normalise(Matter.Vector.sub(this.position, this.closestTarget)); + if (Matter.Vector.dot(target, face) > -0.98) { + if (Matter.Vector.cross(target, face) > 0) { + Matter.Body.rotate(this, 0.08); + } else { + Matter.Body.rotate(this, -0.08); + } + } + } + //accelerate in direction bullet is facing + const dir = this.angle; // + (Math.random() - 0.5); + this.force.x += Math.cos(dir) * thrust; + this.force.y += Math.sin(dir) * thrust; + + //draw rocket + ctx.beginPath(); + ctx.arc(this.position.x - Math.cos(this.angle) * 27 + (Math.random() - 0.5) * 4, this.position.y - Math.sin(this.angle) * 27 + (Math.random() - 0.5) * 4, 11, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(255,155,0,0.5)"; + ctx.fill(); + } else { + //draw rocket with time stop + ctx.beginPath(); + ctx.arc(this.position.x - Math.cos(this.angle) * 27, this.position.y - Math.sin(this.angle) * 27, 11, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(255,155,0,0.5)"; + ctx.fill(); + } + } + } + }, { + name: "flak", //8 + description: "fire a cluster of short range projectiles
explodes on contact or after half a second", + ammo: 0, + ammoPack: 20, + have: false, + isStarterGun: true, + fire() { + b.muzzleFlash(30); + const totalBullets = 5 + const angleStep = (mech.crouch ? 0.06 : 0.15) / totalBullets + const SPEED = mech.crouch ? 30 : 25 + const CD = mech.crouch ? 45 : 11 + const END = Math.floor((mech.crouch ? 30 : 18) * b.isModBulletsLastLonger); + let dir = mech.angle - angleStep * totalBullets / 2; + const side1 = 17 * b.modBulletSize + const side2 = 4 * b.modBulletSize + + 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)); + b.fireProps(CD, SPEED + 15 * Math.random() - 2 * i, dir, me); //cd , speed + // Matter.Body.setDensity(bullet[me], 0.005); + bullet[me].endCycle = 2 * i + game.cycle + END + bullet[me].restitution = 0; + bullet[me].friction = 1; + // bullet[me].dmg = 0.15; + bullet[me].explodeRad = (mech.crouch ? 70 : 45) + (Math.random() - 0.5) * 50; + bullet[me].onEnd = b.explode; + bullet[me].onDmg = function () { + this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion + }; + bullet[me].do = function () { + this.force.y += this.mass * 0.0004; + // if (this.speed < 10) { //if slow explode + // for (let i = 0, len = bullet.length; i < len; i++) { + // bullet[i].endCycle = 0 //all other bullets explode + // } + // } + } + } + } + }, { + name: "grenades", //9 + description: "lob a single bouncy projectile
explodes on contact or after one second", + ammo: 0, + ammoPack: 9, + have: false, + isStarterGun: false, + fire() { + const me = bullet.length; + const dir = mech.angle; // + Math.random() * 0.05; + bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 15 * b.modBulletSize, b.fireAttributes(dir, false)); + b.fireProps(mech.crouch ? 40 : 20, mech.crouch ? 43 : 32, dir, me); //cd , speed + b.drawOneBullet(bullet[me].vertices); + // Matter.Body.setDensity(bullet[me], 0.000001); + bullet[me].totalCycles = 100; + bullet[me].endCycle = game.cycle + Math.floor((mech.crouch ? 120 : 60) * b.isModBulletsLastLonger); + bullet[me].restitution = 0.5; + bullet[me].explodeRad = 210; + bullet[me].onEnd = b.explode; //makes bullet do explosive damage before despawn + bullet[me].minDmgSpeed = 1; + bullet[me].onDmg = function () { + this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion + }; + bullet[me].do = function () { + //extra gravity for harder arcs + this.force.y += this.mass * 0.002; + }; + } + }, { + name: "vacuum bomb", //10 + description: "fire a bomb that sucks before exploding
click left mouse again to detonate", + ammo: 0, + ammoPack: 5, + have: false, + isStarterGun: false, + fire() { + const me = bullet.length; + const dir = mech.angle; + bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 26 * b.modBulletSize, b.fireAttributes(dir, false)); + bullet[me].radius = 22; //used from drawing timer + b.fireProps(10, mech.crouch ? 42 : 26, dir, me); //cd , speed + + b.drawOneBullet(bullet[me].vertices); + bullet[me].endCycle = Infinity + // bullet[me].restitution = 0.3; + // bullet[me].frictionAir = 0.01; + // bullet[me].friction = 0.15; + bullet[me].inertia = Infinity; //prevents rotation + bullet[me].restitution = 0; + bullet[me].friction = 1; + + bullet[me].explodeRad = 380 + Math.floor(Math.random() * 60); + bullet[me].onEnd = b.explode; //makes bullet do explosive damage before despawn + bullet[me].onDmg = function () { + // this.endCycle = 0; //bullet ends cycle after doing damage //this triggers explosion + }; + bullet[me].isArmed = false; + bullet[me].isSucking = false; + bullet[me].do = function () { + //extra gravity for harder arcs + this.force.y += this.mass * 0.0022; + mech.fireCDcycle = mech.cycle + 10 //can't fire until after the explosion + + //set armed and sucking status + if (!this.isArmed && !game.mouseDown) { + this.isArmed = true + } else if (this.isArmed && game.mouseDown && !this.isSucking) { + this.isSucking = true; + this.endCycle = game.cycle + 35; + } + + if (this.isSucking) { + if (!mech.isBodiesAsleep) { + const that = this + let mag = 0.1 + + function suck(who, radius = that.explodeRad * 2) { + for (i = 0, len = who.length; i < len; i++) { + const sub = Matter.Vector.sub(that.position, who[i].position); + const dist = Matter.Vector.magnitude(sub); + if (dist < radius && dist > 150) { + knock = Matter.Vector.mult(Matter.Vector.normalise(sub), mag * who[i].mass / Math.sqrt(dist)); + who[i].force.x += knock.x; + who[i].force.y += knock.y; + } + } + } + if (game.cycle > this.endCycle - 5) { + mag = -0.22 + suck(body) + suck(mob) + suck(powerUp) + suck(bullet) + suck([player]) + } else { + mag = 0.1 + suck(body) + suck(mob) + suck(powerUp) + suck(bullet) + suck([player]) + } + //keep bomb in place + Matter.Body.setVelocity(this, { + x: 0, + y: 0 + }); + //draw suck + const radius = 2.5 * this.explodeRad * (this.endCycle - game.cycle) / 35 + 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(); + } + } else { + // flashing lights to show armed + if (!(game.cycle % 10)) { + if (this.isFlashOn) { + this.isFlashOn = false; + } else { + this.isFlashOn = true; + } + } + if (this.isFlashOn) { + ctx.fillStyle = "#000"; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.radius, 0, 2 * Math.PI); + ctx.fill(); + //draw clock on timer + ctx.fillStyle = "#f04"; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.radius * 0.5, 0, 2 * Math.PI); + ctx.fill(); + } + } + } + } + }, { + name: "ferro frag", //11 + description: "fire a grenade that ejects magnetized nails
nails are attracted to enemies", + ammo: 0, + ammoPack: 8, + have: false, + isStarterGun: false, + fire() { + const me = bullet.length; + const dir = mech.angle; + bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 15 * b.modBulletSize, b.fireAttributes(dir, false)); + b.fireProps(mech.crouch ? 40 : 30, mech.crouch ? 34 : 22, dir, me); //cd , speed + b.drawOneBullet(bullet[me].vertices); + bullet[me].endCycle = game.cycle + Math.floor(60 * b.isModBulletsLastLonger); + bullet[me].restitution = 0.3; + // bullet[me].frictionAir = 0.01; + // bullet[me].friction = 0.15; + // bullet[me].friction = 1; + bullet[me].onEnd = () => {} + bullet[me].do = function () { + this.force.y += this.mass * 0.0018; //extra gravity for grenades + + if (game.cycle > this.endCycle - 1) { + if (!mech.isBodiesAsleep) { + //target nearby mobs + const targets = [] + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].dropPowerUp) { + const sub = Matter.Vector.sub(this.position, mob[i].position); + const dist = Matter.Vector.magnitude(sub); + if (dist < 1400 && + Matter.Query.ray(map, this.position, mob[i].position).length === 0 && + Matter.Query.ray(body, this.position, mob[i].position).length === 0) { + targets.push( + Matter.Vector.add(mob[i].position, Matter.Vector.mult(mob[i].velocity, dist / 60)) + ) + } + } + } + for (let i = 0; i < 14; i++) { + const speed = 55 + 10 * Math.random() + if (targets.length > 0) { // aim near a random target + 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) + } + needle(this.position, Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(WHERE, this.position)), speed)) + } else { // aim in random direction + const ANGLE = 2 * Math.PI * Math.random() + needle(this.position, { + x: speed * Math.cos(ANGLE), + y: speed * Math.sin(ANGLE) + }) + } + + function needle(pos, velocity) { + const me = bullet.length; + bullet[me] = Bodies.rectangle(pos.x, pos.y, 25 * b.modBulletSize, 2 * b.modBulletSize, 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 + 15 * Math.random(); + // bullet[me].dmg = 1.1+b.modExtraDmg; + bullet[me].do = function () {}; + } + } + } + } + } + } + }, { + name: "spores", //12 + description: "fire orbs that discharge spores
spores seek out enemies", + ammo: 0, + ammoPack: 5, + have: false, + isStarterGun: 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 ? 75 : 55, mech.crouch ? 25 : 14, dir, me); //cd , speed + b.drawOneBullet(bullet[me].vertices); + Matter.Body.setDensity(bullet[me], 0.000001); + bullet[me].endCycle = game.cycle + 100; + bullet[me].frictionAir = 0; + bullet[me].friction = 0.5; + bullet[me].restitution = 0.3; + bullet[me].minDmgSpeed = 0; + bullet[me].onDmg = function () {}; + bullet[me].do = function () { + if (!mech.isBodiesAsleep) { + const SCALE = 1.017 + Matter.Body.scale(this, SCALE, SCALE); + this.frictionAir += 0.00023; + } + + 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, 26, 0, 2 * Math.PI); + ctx.fill(); + }; + + //spawn bullets on end + bullet[me].onEnd = function () { + const NUM = 9; + for (let i = 0; i < NUM; i++) { + const bIndex = bullet.length; + const RADIUS = 3 * b.modBulletSize; + bullet[bIndex] = Bodies.circle(this.position.x, this.position.y, RADIUS, { + // density: 0.0015, //frictionAir: 0.01, + inertia: Infinity, + restitution: 0.5, + angle: dir, + friction: 0, + frictionAir: 0.011, + dmg: 1.8 + b.modExtraDmg, //damage done in addition to the damage from momentum + classType: "bullet", + collisionFilter: { + category: 0x000100, + mask: 0x000011 //no collide with body + }, + endCycle: game.cycle + Math.floor((360 + Math.floor(Math.random() * 240)) * b.isModBulletsLastLonger), + minDmgSpeed: 0, + onDmg() { + this.endCycle = 0; //bullet ends cycle after doing damage + }, + onEnd() {}, + lookFrequency: 67 + Math.floor(47 * Math.random()), + do() { + //find mob targets + if (!(game.cycle % this.lookFrequency)) { + this.closestTarget = null; + this.lockedOn = null; + let closeDist = Infinity; + for (let i = 0, len = mob.length; i < len; ++i) { + if (Matter.Query.ray(map, this.position, mob[i].position).length === 0) { + // Matter.Query.ray(body, this.position, mob[i].position).length === 0 + const targetVector = Matter.Vector.sub(this.position, mob[i].position) + const dist = Matter.Vector.magnitude(targetVector); + if (dist < closeDist) { + this.closestTarget = mob[i].position; + closeDist = dist; + this.lockedOn = Matter.Vector.normalise(targetVector); + if (0.3 > Math.random()) break //doesn't always target the closest mob + } + } + } + } + //accelerate towards mobs + const THRUST = this.mass * 0.0009 + if (this.lockedOn) { + this.force.x -= THRUST * this.lockedOn.x + this.force.y -= THRUST * this.lockedOn.y + } else { + this.force.y += this.mass * 0.00025; //gravity + } + }, + }); + const SPEED = 9; + 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 + } + } + + } + }, + { + name: "drones", //13 + description: "deploy drones that seek out enemies
collisions reduce drone cycles by 1 second", + ammo: 0, + ammoPack: 17, + have: false, + isStarterGun: true, + fire() { + const THRUST = 0.0015 + const dir = mech.angle + 0.2 * (Math.random() - 0.5); + const me = bullet.length; + const RADIUS = (4.5 + 3 * Math.random()) * b.modBulletSize + bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), RADIUS, { + angle: dir, + inertia: Infinity, + friction: 0.05, + frictionAir: 0.0005, + restitution: 1, + dmg: 0.13 + b.modExtraDmg, //damage done in addition to the damage from momentum + lookFrequency: 83 + Math.floor(41 * Math.random()), + endCycle: game.cycle + Math.floor((1080 + 360 * Math.random()) * b.isModBulletsLastLonger), + classType: "bullet", + collisionFilter: { + category: 0x000100, + mask: 0x010111 //self collide + }, + minDmgSpeed: 0, + lockedOn: null, + isFollowMouse: true, + onDmg() { + this.lockedOn = null + if (this.endCycle > game.cycle + 180) { + this.endCycle -= 60 + if (game.cycle + 180 > this.endCycle) this.endCycle = game.cycle + 180 + } + }, + onEnd() {}, + do() { + if (game.cycle + 180 > this.endCycle) { //fall and die + this.force.y += this.mass * 0.0012; + this.restitution = 0.2; + } 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 ( + 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 = Matter.Vector.sub(this.position, mob[i].position) + const DIST = Matter.Vector.magnitude(TARGET_VECTOR); + if (DIST < closeDist) { + closeDist = DIST; + this.lockedOn = mob[i] + } + } + } + if (!this.lockedOn) { + //grab a power up if it is (ammo) or (a heal when player is low) + let closeDist = Infinity; + for (let i = 0, len = powerUp.length; i < len; ++i) { + if ( + ((powerUp[i].name !== "field" && powerUp[i].name !== "heal") || (powerUp[i].name === "heal" && mech.health < 0.8)) && + 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 = Matter.Vector.sub(this.position, powerUp[i].position) + const DIST = Matter.Vector.magnitude(TARGET_VECTOR); + if (DIST < closeDist) { + if (DIST < 50) { //eat the power up if close enough + powerUp[i].effect(); + Matter.World.remove(engine.world, powerUp[i]); + powerUp.splice(i, 1); + break; + } + closeDist = DIST; + this.lockedOn = powerUp[i] + } + } + } + } + } + if (this.lockedOn) { //accelerate towards mobs + this.force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(this.position, this.lockedOn.position)), -this.mass * THRUST) + } else { //accelerate towards mouse + this.force = Matter.Vector.mult(Matter.Vector.normalise(Matter.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 + }); + } + } + } + }) + b.fireProps(mech.crouch ? 14 : 10, mech.crouch ? 40 : 1, dir, me); //cd , speed + b.drawOneBullet(bullet[me].vertices); + } + }, + { + name: "laser-bot", //14 + description: "deploy bots that defend against close threats
lasts one level, but drains energy", + ammo: 0, + ammoPack: 1, + have: false, + isStarterGun: false, + fire() { + const dir = mech.angle; + const me = bullet.length; + const RADIUS = (22 + 5 * Math.random()) * b.modBulletSize + const LENGTH = 0.7 + Math.random() + + bullet[me] = Bodies.rectangle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), RADIUS * LENGTH, RADIUS / LENGTH, { + angle: dir, + friction: 0, + frictionStatic: 0, + restitution: 0.8, + dmg: b.modExtraDmg, // 0.14 //damage done in addition to the damage from momentum + minDmgSpeed: 2, + lookFrequency: 47 + Math.floor(37 * Math.random()), + range: 450 + Math.floor(200 * Math.random()), + endCycle: Infinity, + modulus: Math.floor(2 * Math.random()), //offsets the modulus so the bullets don't all fire at the same time + classType: "bullet", + collisionFilter: { + category: 0x000100, + mask: 0x010111 //self, mob,map,body collide + }, + lockedOn: null, + onDmg() { + this.lockedOn = null + }, + onEnd() {}, + do() { + if (!(game.cycle % this.lookFrequency)) { + this.lockedOn = null; + let closeDist = this.range; + for (let i = 0, len = mob.length; i < len; ++i) { + const TARGET_VECTOR = Matter.Vector.sub(this.vertices[0], mob[i].position) + const DIST = Matter.Vector.magnitude(TARGET_VECTOR); + if (DIST - mob[i].radius < closeDist && + 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] + } + } + } + + if (!((game.cycle + this.modulus) % 2)) { + const FIELD_DRAIN = 0.006 + if (this.lockedOn && this.lockedOn.alive && mech.fieldMeter > FIELD_DRAIN) { //hit target with laser + mech.fieldMeter -= FIELD_DRAIN + + //make sure you can still see target + const TARGET_VECTOR = Matter.Vector.sub(this.vertices[0], this.lockedOn.position) + const DIST = Matter.Vector.magnitude(TARGET_VECTOR); + if (DIST - this.lockedOn.radius < this.range + 200 && + Matter.Query.ray(map, this.vertices[0], this.lockedOn.position).length === 0 && + Matter.Query.ray(body, this.vertices[0], this.lockedOn.position).length === 0) { + //find the closest vertex + let bestVertexDistance = Infinity + let bestVertex = null + for (let i = 0; i < this.lockedOn.vertices.length; i++) { + const dist = Matter.Vector.magnitude(Matter.Vector.sub(this.vertices[0], this.lockedOn.vertices[i])); + if (dist < bestVertexDistance) { + bestVertex = i + bestVertexDistance = dist + } + } + const dmg = b.dmgScale * 0.10; + this.lockedOn.damage(dmg); + this.lockedOn.locatePlayer(); + + //draw laser + ctx.beginPath(); + 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(); + } + } + } + const distanceToPlayer = Matter.Vector.magnitude(Matter.Vector.sub(this.position, mech.pos)) + if (distanceToPlayer > this.range * 0.25) { //if far away move towards player + this.force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(mech.pos, this.position)), this.mass * 0.002) + this.frictionAir = 0.02 + } else { //close to player + this.frictionAir = 0 + } + } + }) + b.fireProps(mech.crouch ? 60 : 30, 15, dir, me); //cd , speed + b.drawOneBullet(bullet[me].vertices); + } + }, + // { + // name: "dwarf star", //14 + // description: "drop a mine that gravitational pulls in matter", + // ammo: 0, + // ammoPack: 1000, + // have: false, + // isStarterGun: false, + // fire() { + // const me = bullet.length; + // const dir = mech.angle + // const TOTAL_CYCLES = 1020 + // bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(dir), mech.pos.y + 30 * Math.sin(dir), 3 * b.modBulletSize, { + // density: 0.05, + // //frictionAir: 0.01, + // restitution: 0, + // angle: 0, + // friction: 1, + // // frictionAir: 1, + // endCycle: game.cycle + TOTAL_CYCLES, + // dmg: b.modExtraDmg, //damage done in addition to the damage from momentum + // classType: "bullet", + // collisionFilter: { + // category: 0x000100, + // mask: 0x010011 //mask: 0x000101, //for self collision + // }, + // minDmgSpeed: 5, + // range: 0, + // onDmg() { + // this.endCycle = 0; + // }, //this.endCycle = 0 //triggers despawn + // onEnd() {}, + // do() { + // this.force.y += this.mass * 0.005; + // this.range += 0.5 + + // //damage nearby mobs + // const dmg = b.dmgScale * 0.02 + // for (let i = 0, len = mob.length; i < len; ++i) { + // if (mob[i].alive) { + // sub = Matter.Vector.sub(this.position, mob[i].position); + // dist = Matter.Vector.magnitude(sub) - mob[i].radius; + // if (dist < this.range) { + // mob[i].damage(dmg); + // mob[i].locatePlayer(); + // } + // } + // } + + // //pull in body, and power ups?, and bullets? + // for (let i = 0, len = body.length; i < len; ++i) { + // sub = Matter.Vector.sub(this.position, body[i].position); + // dist = Matter.Vector.magnitude(sub) + // if (dist < this.range) { + // this.range += body[i].mass * 2 + // Matter.World.remove(engine.world, body[i]); + // body.splice(i, 1); + // break; + // } + // } + + // //draw + // const opacity = (this.endCycle - game.cycle) / TOTAL_CYCLES + // ctx.fillStyle = `rgba(170,220,255,${opacity})`; + // ctx.beginPath(); + // ctx.arc(this.position.x, this.position.y, this.range, 0, 2 * Math.PI); + // ctx.fill(); + // } + // }); + // b.fireProps(60, 0, dir, me); //cd , speed + // } + // }, + // { + // name: "kinetic slugs", //1 + // description: "fire a large rod that does excessive physical damage
high recoil", + // ammo: 0, + // ammoPack: 5, + // have: false, + // isStarterGun: true, + // fire() { + // b.muzzleFlash(45); + // // mobs.alert(800); + // const me = bullet.length; + // const dir = mech.angle; + // bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), 70 * b.modBulletSize, 30 * b.modBulletSize, b.fireAttributes(dir)); + // b.fireProps(mech.crouch ? 55 : 40, 50, dir, me); //cd , speed + // bullet[me].endCycle = game.cycle + Math.floor(180 * b.isModBulletsLastLonger); + // bullet[me].do = function () { + // this.force.y += this.mass * 0.0005; + // }; + + // //knock back + // const KNOCK = ((mech.crouch) ? 0.025 : 0.25) * b.modBulletSize * b.modBulletSize + // player.force.x -= KNOCK * Math.cos(dir) + // player.force.y -= KNOCK * Math.sin(dir) * 0.3 //reduce knock back in vertical direction to stop super jumps + // }, + // { + // name: "triboelectricty", //14 + // description: "release particles that quickly seek out targets", + // ammo: 0, + // ammoPack: 40, + // have: false, + // isStarterGun: true, + // fire() { + // const dir = mech.angle + 0.2 * (Math.random() - 0.5); + // const me = bullet.length; + // const RADIUS = 6 * b.modBulletSize + // bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), RADIUS, { + // angle: dir, + // inertia: Infinity, + // // friction: 0.05, + // // frictionAir: 0.05, + // restitution: 0.8, + // dmg: 0.14 + b.modExtraDmg, //damage done in addition to the damage from momentum + // lookFrequency: 3, + // endCycle: game.cycle + Math.floor(120 * b.isModBulletsLastLonger), + // classType: "bullet", + // collisionFilter: { + // category: 0x000100, + // mask: 0x010111 //self collide + // }, + // minDmgSpeed: 0, + // lockedOn: null, + // isFollowMouse: true, + // onDmg() { + // this.endCycle = 0; + // }, + // onEnd() {}, + // do() { + // if (this.lockedOn) { //accelerate towards mobs + // this.force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(this.position, this.lockedOn.position)), -this.mass * 0.01) + // Matter.Body.setVelocity(this, { + // x: this.velocity.x * 0.93, + // y: this.velocity.y * 0.93 + // }); + // } else { + // this.force.y += this.mass * 0.0004; + // } + // } + // }) + + // b.fireProps(mech.crouch ? 19 : 15, mech.crouch ? 45 : 30, dir, me); //cd , speed + // b.drawOneBullet(bullet[me].vertices); + + // //find mob targets + // let closeDist = Infinity; + // for (let i = 0, len = mob.length; i < len; ++i) { + // if ( + // Matter.Query.ray(map, bullet[me].position, mob[i].position).length === 0 && + // Matter.Query.ray(body, bullet[me].position, mob[i].position).length === 0 + // ) { + // const TARGET_VECTOR = Matter.Vector.sub(bullet[me].position, mob[i].position) + // const DIST = Matter.Vector.magnitude(TARGET_VECTOR); + // if (DIST < closeDist) { + // closeDist = DIST; + // bullet[me].lockedOn = mob[i] + // } + // } + // } + // } + // }, + // { + ] }; \ No newline at end of file diff --git a/js/engine.js b/js/engine.js index 9d5ab2b..285480a 100644 --- a/js/engine.js +++ b/js/engine.js @@ -1,233 +1,233 @@ -//matter.js *********************************************************** -// module aliases -const Engine = Matter.Engine, - World = Matter.World, - Events = Matter.Events, - Composites = Matter.Composites, - Composite = Matter.Composite, - Constraint = Matter.Constraint, - Vertices = Matter.Vertices, - Query = Matter.Query, - Body = Matter.Body, - Bodies = Matter.Bodies; - -// create an engine -const engine = Engine.create(); -engine.world.gravity.scale = 0; //turn off gravity (it's added back in later) -// engine.velocityIterations = 100 -// engine.positionIterations = 100 -// engine.enableSleeping = true - -// matter events ********************************************************* -//************************************************************************ -//************************************************************************ -//************************************************************************ - -function playerOnGroundCheck(event) { - //runs on collisions events - function enter() { - mech.numTouching++; - if (!mech.onGround) mech.enterLand(); - } - const pairs = event.pairs; - for (let i = 0, j = pairs.length; i != j; ++i) { - let pair = pairs[i]; - if (pair.bodyA === jumpSensor) { - mech.standingOn = pair.bodyB; //keeping track to correctly provide recoil on jump - if (mech.standingOn.alive !== true) enter(); - } else if (pair.bodyB === jumpSensor) { - mech.standingOn = pair.bodyA; //keeping track to correctly provide recoil on jump - if (mech.standingOn.alive !== true) enter(); - } - } - mech.numTouching = 0; -} - -function playerOffGroundCheck(event) { - //runs on collisions events - function enter() { - if (mech.onGround && mech.numTouching === 0) mech.enterAir(); - } - const pairs = event.pairs; - for (let i = 0, j = pairs.length; i != j; ++i) { - if (pairs[i].bodyA === jumpSensor) { - enter(); - } else if (pairs[i].bodyB === jumpSensor) { - enter(); - } - } -} - -function playerHeadCheck(event) { - //runs on collisions events - if (mech.crouch) { - mech.isHeadClear = true; - const pairs = event.pairs; - for (let i = 0, j = pairs.length; i != j; ++i) { - if (pairs[i].bodyA === headSensor) { - mech.isHeadClear = false; - } else if (pairs[i].bodyB === headSensor) { - mech.isHeadClear = false; - } - } - } -} - -function mobCollisionChecks(event) { - const pairs = event.pairs; - for (let i = 0, j = pairs.length; i != j; i++) { - - //body + player collision - if (game.isBodyDamage && mech.damageImmune < mech.cycle) { - if (pairs[i].bodyA === playerBody || pairs[i].bodyA === playerHead) { - collidePlayer(pairs[i].bodyB) - } else if (pairs[i].bodyB === playerBody || pairs[i].bodyB === playerHead) { - collidePlayer(pairs[i].bodyA) - } - } - - function collidePlayer(obj, speedThreshold = 12, massThreshold = 2) { - if (obj.classType === "body" && obj.speed > speedThreshold && obj.mass > massThreshold) { //dmg from hitting a body - const v = Matter.Vector.magnitude(Matter.Vector.sub(player.velocity, obj.velocity)); - if (v > speedThreshold) { - mech.damageImmune = mech.cycle + 30; //player is immune to collision damage for 30 cycles - let dmg = Math.sqrt((v - speedThreshold + 0.1) * (obj.mass - massThreshold)) * 0.01; - dmg = Math.min(Math.max(dmg, 0.02), 0.15); - mech.damage(dmg); - game.drawList.push({ //add dmg to draw queue - x: pairs[i].activeContacts[0].vertex.x, - y: pairs[i].activeContacts[0].vertex.y, - radius: dmg * 500, - color: game.mobDmgColor, - time: game.drawTime - }); - return; - } - } - // else if (obj.isStatic && player.speed > speedThreshold * 2) { //falling dmg / hitting map dmg - // mech.damageImmune = mech.cycle + 30; - // console.log(player.speed) - // let dmg = Math.min(Math.max(player.speed * 0.001, 0.02), 0.2); - // mech.damage(dmg); - // game.drawList.push({ - // //add dmg to draw queue - // x: pairs[i].activeContacts[0].vertex.x, - // y: pairs[i].activeContacts[0].vertex.y, - // radius: dmg * 500, - // color: game.mobDmgColor, - // time: game.drawTime - // }); - // return; - // } - } - - //mob + (player,bullet,body) collisions - for (let k = 0; k < mob.length; k++) { - if (mob[k].alive && mech.alive) { - if (pairs[i].bodyA === mob[k]) { - collideMob(pairs[i].bodyB); - break; - } else if (pairs[i].bodyB === mob[k]) { - collideMob(pairs[i].bodyA); - break; - } - - function collideMob(obj) { - //player + mob collision - if (obj === playerBody || obj === playerHead) { - if (mech.damageImmune < mech.cycle) { - mech.damageImmune = mech.cycle + 30; //player is immune to collision damage for 30 cycles - mob[k].foundPlayer(); - let dmg = Math.min(Math.max(0.025 * Math.sqrt(mob[k].mass), 0.05), 0.3) * game.dmgScale; //player damage is capped at 0.3*dmgScale of 1.0 - mech.damage(dmg); - if (mob[k].onHit) mob[k].onHit(k); - if (b.modAnnihilation && mob[k].dropPowerUp) { - mob[k].death(); - game.drawList.push({ - //add dmg to draw queue - x: pairs[i].activeContacts[0].vertex.x, - y: pairs[i].activeContacts[0].vertex.y, - radius: dmg * 2000, - color: "rgba(255,0,255,0.2)", - time: game.drawTime - }); - } else { - game.drawList.push({ - //add dmg to draw queue - x: pairs[i].activeContacts[0].vertex.x, - y: pairs[i].activeContacts[0].vertex.y, - radius: dmg * 500, - color: game.mobDmgColor, - time: game.drawTime - }); - - } - } - //extra kick between player and mob //this section would be better with forces but they don't work... - let angle = Math.atan2(player.position.y - mob[k].position.y, player.position.x - mob[k].position.x); - Matter.Body.setVelocity(player, { - x: player.velocity.x + 8 * Math.cos(angle), - y: player.velocity.y + 8 * Math.sin(angle) - }); - Matter.Body.setVelocity(mob[k], { - x: mob[k].velocity.x - 8 * Math.cos(angle), - y: mob[k].velocity.y - 8 * Math.sin(angle) - }); - return; - } - //mob + bullet collisions - if (obj.classType === "bullet" && obj.speed > obj.minDmgSpeed) { - // const dmg = b.dmgScale * (obj.dmg + 0.15 * obj.mass * Matter.Vector.magnitude(Matter.Vector.sub(mob[k].velocity, obj.velocity))); - let dmg = b.dmgScale * (obj.dmg + b.modExtraDmg + 0.15 * obj.mass * Matter.Vector.magnitude(Matter.Vector.sub(mob[k].velocity, obj.velocity))) - if (b.modIsCrit && !mob[k].seePlayer.recall) dmg *= 5 - mob[k].foundPlayer(); - mob[k].damage(dmg); - obj.onDmg(); //some bullets do actions when they hits things, like despawn - game.drawList.push({ - //add dmg to draw queue - x: pairs[i].activeContacts[0].vertex.x, - y: pairs[i].activeContacts[0].vertex.y, - // radius: Math.sqrt(dmg) * 40, - radius: Math.log(2 * dmg + 1.1) * 40, - color: game.playerDmgColor, - time: game.drawTime - }); - return; - } - //mob + body collisions - if (obj.classType === "body" && obj.speed > 5) { - const v = Matter.Vector.magnitude(Matter.Vector.sub(mob[k].velocity, obj.velocity)); - if (v > 8) { - let dmg = b.dmgScale * v * Math.sqrt(obj.mass) * 0.05; - mob[k].damage(dmg); - if (mob[k].distanceToPlayer2() < 1000000) mob[k].foundPlayer(); - game.drawList.push({ - //add dmg to draw queue - x: pairs[i].activeContacts[0].vertex.x, - y: pairs[i].activeContacts[0].vertex.y, - radius: Math.sqrt(dmg) * 40, - color: game.playerDmgColor, - time: game.drawTime - }); - return; - } - } - } - } - } - } -} - -//determine if player is on the ground -Events.on(engine, "collisionStart", function (event) { - playerOnGroundCheck(event); - playerHeadCheck(event); - mobCollisionChecks(event); -}); -Events.on(engine, "collisionActive", function (event) { - playerOnGroundCheck(event); - playerHeadCheck(event); -}); -Events.on(engine, "collisionEnd", function (event) { - playerOffGroundCheck(event); +//matter.js *********************************************************** +// module aliases +const Engine = Matter.Engine, + World = Matter.World, + Events = Matter.Events, + Composites = Matter.Composites, + Composite = Matter.Composite, + Constraint = Matter.Constraint, + Vertices = Matter.Vertices, + Query = Matter.Query, + Body = Matter.Body, + Bodies = Matter.Bodies; + +// create an engine +const engine = Engine.create(); +engine.world.gravity.scale = 0; //turn off gravity (it's added back in later) +// engine.velocityIterations = 100 +// engine.positionIterations = 100 +// engine.enableSleeping = true + +// matter events ********************************************************* +//************************************************************************ +//************************************************************************ +//************************************************************************ + +function playerOnGroundCheck(event) { + //runs on collisions events + function enter() { + mech.numTouching++; + if (!mech.onGround) mech.enterLand(); + } + const pairs = event.pairs; + for (let i = 0, j = pairs.length; i != j; ++i) { + let pair = pairs[i]; + if (pair.bodyA === jumpSensor) { + mech.standingOn = pair.bodyB; //keeping track to correctly provide recoil on jump + if (mech.standingOn.alive !== true) enter(); + } else if (pair.bodyB === jumpSensor) { + mech.standingOn = pair.bodyA; //keeping track to correctly provide recoil on jump + if (mech.standingOn.alive !== true) enter(); + } + } + mech.numTouching = 0; +} + +function playerOffGroundCheck(event) { + //runs on collisions events + function enter() { + if (mech.onGround && mech.numTouching === 0) mech.enterAir(); + } + const pairs = event.pairs; + for (let i = 0, j = pairs.length; i != j; ++i) { + if (pairs[i].bodyA === jumpSensor) { + enter(); + } else if (pairs[i].bodyB === jumpSensor) { + enter(); + } + } +} + +function playerHeadCheck(event) { + //runs on collisions events + if (mech.crouch) { + mech.isHeadClear = true; + const pairs = event.pairs; + for (let i = 0, j = pairs.length; i != j; ++i) { + if (pairs[i].bodyA === headSensor) { + mech.isHeadClear = false; + } else if (pairs[i].bodyB === headSensor) { + mech.isHeadClear = false; + } + } + } +} + +function mobCollisionChecks(event) { + const pairs = event.pairs; + for (let i = 0, j = pairs.length; i != j; i++) { + + //body + player collision + if (game.isBodyDamage && mech.damageImmune < mech.cycle) { + if (pairs[i].bodyA === playerBody || pairs[i].bodyA === playerHead) { + collidePlayer(pairs[i].bodyB) + } else if (pairs[i].bodyB === playerBody || pairs[i].bodyB === playerHead) { + collidePlayer(pairs[i].bodyA) + } + } + + function collidePlayer(obj, speedThreshold = 12, massThreshold = 2) { + if (obj.classType === "body" && obj.speed > speedThreshold && obj.mass > massThreshold) { //dmg from hitting a body + const v = Matter.Vector.magnitude(Matter.Vector.sub(player.velocity, obj.velocity)); + if (v > speedThreshold) { + mech.damageImmune = mech.cycle + 30; //player is immune to collision damage for 30 cycles + let dmg = Math.sqrt((v - speedThreshold + 0.1) * (obj.mass - massThreshold)) * 0.01; + dmg = Math.min(Math.max(dmg, 0.02), 0.15); + mech.damage(dmg); + game.drawList.push({ //add dmg to draw queue + x: pairs[i].activeContacts[0].vertex.x, + y: pairs[i].activeContacts[0].vertex.y, + radius: dmg * 500, + color: game.mobDmgColor, + time: game.drawTime + }); + return; + } + } + // else if (obj.isStatic && player.speed > speedThreshold * 2) { //falling dmg / hitting map dmg + // mech.damageImmune = mech.cycle + 30; + // console.log(player.speed) + // let dmg = Math.min(Math.max(player.speed * 0.001, 0.02), 0.2); + // mech.damage(dmg); + // game.drawList.push({ + // //add dmg to draw queue + // x: pairs[i].activeContacts[0].vertex.x, + // y: pairs[i].activeContacts[0].vertex.y, + // radius: dmg * 500, + // color: game.mobDmgColor, + // time: game.drawTime + // }); + // return; + // } + } + + //mob + (player,bullet,body) collisions + for (let k = 0; k < mob.length; k++) { + if (mob[k].alive && mech.alive) { + if (pairs[i].bodyA === mob[k]) { + collideMob(pairs[i].bodyB); + break; + } else if (pairs[i].bodyB === mob[k]) { + collideMob(pairs[i].bodyA); + break; + } + + function collideMob(obj) { + //player + mob collision + if (obj === playerBody || obj === playerHead) { + if (mech.damageImmune < mech.cycle) { + mech.damageImmune = mech.cycle + 30; //player is immune to collision damage for 30 cycles + mob[k].foundPlayer(); + let dmg = Math.min(Math.max(0.025 * Math.sqrt(mob[k].mass), 0.05), 0.3) * game.dmgScale; //player damage is capped at 0.3*dmgScale of 1.0 + mech.damage(dmg); + if (mob[k].onHit) mob[k].onHit(k); + if (b.modAnnihilation && mob[k].dropPowerUp) { + mob[k].death(); + game.drawList.push({ + //add dmg to draw queue + x: pairs[i].activeContacts[0].vertex.x, + y: pairs[i].activeContacts[0].vertex.y, + radius: dmg * 2000, + color: "rgba(255,0,255,0.2)", + time: game.drawTime + }); + } else { + game.drawList.push({ + //add dmg to draw queue + x: pairs[i].activeContacts[0].vertex.x, + y: pairs[i].activeContacts[0].vertex.y, + radius: dmg * 500, + color: game.mobDmgColor, + time: game.drawTime + }); + + } + } + //extra kick between player and mob //this section would be better with forces but they don't work... + let angle = Math.atan2(player.position.y - mob[k].position.y, player.position.x - mob[k].position.x); + Matter.Body.setVelocity(player, { + x: player.velocity.x + 8 * Math.cos(angle), + y: player.velocity.y + 8 * Math.sin(angle) + }); + Matter.Body.setVelocity(mob[k], { + x: mob[k].velocity.x - 8 * Math.cos(angle), + y: mob[k].velocity.y - 8 * Math.sin(angle) + }); + return; + } + //mob + bullet collisions + if (obj.classType === "bullet" && obj.speed > obj.minDmgSpeed) { + // const dmg = b.dmgScale * (obj.dmg + 0.15 * obj.mass * Matter.Vector.magnitude(Matter.Vector.sub(mob[k].velocity, obj.velocity))); + let dmg = b.dmgScale * (obj.dmg + b.modExtraDmg + 0.15 * obj.mass * Matter.Vector.magnitude(Matter.Vector.sub(mob[k].velocity, obj.velocity))) + if (b.modIsCrit && !mob[k].seePlayer.recall) dmg *= 5 + mob[k].foundPlayer(); + mob[k].damage(dmg); + obj.onDmg(); //some bullets do actions when they hits things, like despawn + game.drawList.push({ + //add dmg to draw queue + x: pairs[i].activeContacts[0].vertex.x, + y: pairs[i].activeContacts[0].vertex.y, + // radius: Math.sqrt(dmg) * 40, + radius: Math.log(2 * dmg + 1.1) * 40, + color: game.playerDmgColor, + time: game.drawTime + }); + return; + } + //mob + body collisions + if (obj.classType === "body" && obj.speed > 5) { + const v = Matter.Vector.magnitude(Matter.Vector.sub(mob[k].velocity, obj.velocity)); + if (v > 8) { + let dmg = b.dmgScale * v * Math.sqrt(obj.mass) * 0.05; + mob[k].damage(dmg); + if (mob[k].distanceToPlayer2() < 1000000) mob[k].foundPlayer(); + game.drawList.push({ + //add dmg to draw queue + x: pairs[i].activeContacts[0].vertex.x, + y: pairs[i].activeContacts[0].vertex.y, + radius: Math.sqrt(dmg) * 40, + color: game.playerDmgColor, + time: game.drawTime + }); + return; + } + } + } + } + } + } +} + +//determine if player is on the ground +Events.on(engine, "collisionStart", function (event) { + playerOnGroundCheck(event); + playerHeadCheck(event); + mobCollisionChecks(event); +}); +Events.on(engine, "collisionActive", function (event) { + playerOnGroundCheck(event); + playerHeadCheck(event); +}); +Events.on(engine, "collisionEnd", function (event) { + playerOffGroundCheck(event); }); \ No newline at end of file diff --git a/js/game.js b/js/game.js index d638485..b6bdb20 100644 --- a/js/game.js +++ b/js/game.js @@ -1,1050 +1,1050 @@ -// game Object ******************************************************** -//********************************************************************* -const game = { - loop() { - 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.gravity(); - Engine.update(engine, game.delta); - game.wipe(); - game.textLog(); - mech.keyMove(); - level.checkZones(); - level.checkQuery(); - mech.move(); - mech.look(); - game.fallChecks(); - ctx.save(); - game.camera(); - if (game.testing) { - mech.draw(); - game.draw.wireFrame(); - game.draw.cons(); - game.draw.testing(); - game.drawCircle(); - ctx.restore(); - game.getCoords.out(); - game.testingOutput(); - } else { - level.drawFillBGs(); - level.exit.draw(); - level.enter.draw(); - game.draw.powerUp(); - mobs.draw(); - game.draw.cons(); - game.draw.body(); - mobs.loop(); - mech.draw(); - mech.hold(); - level.drawFills(); - game.draw.drawMapPath(); - b.draw(); - b.fire(); - game.drawCircle(); - ctx.restore(); - } - game.drawCursor(); - }, - mouse: { - x: canvas.width / 2, - y: canvas.height / 2 - }, - mouseInGame: { - x: 0, - y: 0 - }, - levelsCleared: 0, - g: 0.001, - dmgScale: null, //set in levels.setDifficulty - accelScale: null, //set in levels.setDifficulty - CDScale: null, //set in levels.setDifficulty - lookFreqScale: null, //set in levels.setDifficulty - onTitlePage: true, - paused: false, - testing: false, //testing mode: shows wireframe 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 - 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, - isBodyDamage: true, - isEasyMode: false, - difficulty: null, - // dropFPS(cap = 40, time = 15) { - // game.fpsCap = cap - // game.fpsInterval = 1000 / game.fpsCap; - // game.defaultFPSCycle = game.cycle + time - // const normalFPS = function () { - // if (game.defaultFPSCycle < game.cycle) { - // game.fpsCap = 72 - // game.fpsInterval = 1000 / game.fpsCap; - // } else { - // requestAnimationFrame(normalFPS); - // } - // }; - // requestAnimationFrame(normalFPS); - // }, - drawCursor() { - const size = 10; - ctx.beginPath(); - ctx.moveTo(game.mouse.x - size, game.mouse.y); - ctx.lineTo(game.mouse.x + size, game.mouse.y); - ctx.moveTo(game.mouse.x, game.mouse.y - size); - ctx.lineTo(game.mouse.x, game.mouse.y + size); - ctx.lineWidth = 2; - ctx.strokeStyle = "#000"; //'rgba(0,0,0,0.4)' - ctx.stroke(); // Draw it - }, - drawList: [], //so you can draw a first frame of explosions.. I know this is bad - drawTime: 8, //how long circles are drawn. use to push into drawlist.time - mobDmgColor: "rgba(255,0,0,0.7)", //used top push into drawList.color - playerDmgColor: "rgba(0,0,0,0.7)", //used top push into drawList.color - drawCircle() { - //draws a circle for two cycles, used for showing damage mostly - let i = this.drawList.length; - while (i--) { - ctx.beginPath(); //draw circle - ctx.arc(this.drawList[i].x, this.drawList[i].y, this.drawList[i].radius, 0, 2 * Math.PI); - ctx.fillStyle = this.drawList[i].color; - ctx.fill(); - if (this.drawList[i].time) { - //remove when timer runs out - this.drawList[i].time--; - } else { - this.drawList.splice(i, 1); - } - } - }, - lastLogTime: 0, - lastLogTimeBig: 0, - boldActiveGunHUD() { - if (b.inventory.length > 0) { - for (let i = 0, len = b.inventory.length; i < len; ++i) { - // document.getElementById(b.inventory[i]).style.fontSize = "25px"; - document.getElementById(b.inventory[i]).style.opacity = "0.3"; - } - // document.getElementById(b.activeGun).style.fontSize = "30px"; - if (document.getElementById(b.activeGun)) document.getElementById(b.activeGun).style.opacity = "1"; - } - }, - updateGunHUD() { - for (let i = 0, len = b.inventory.length; i < len; ++i) { - document.getElementById(b.inventory[i]).innerHTML = b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo; - } - }, - makeGunHUD() { - //remove all nodes - const myNode = document.getElementById("guns"); - while (myNode.firstChild) { - myNode.removeChild(myNode.firstChild); - } - //add nodes - for (let i = 0, len = b.inventory.length; i < len; ++i) { - const node = document.createElement("div"); - node.setAttribute("id", b.inventory[i]); - let textnode = document.createTextNode(b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo); - node.appendChild(textnode); - document.getElementById("guns").appendChild(node); - } - game.boldActiveGunHUD(); - }, - updateModHUD() { - let text = "" - for (let i = 0, len = b.mods.length; i < len; i++) { //add mods - if (b.mods[i].have) { - if (text) text += "
" //add a new line, but not on the first line - text += b.mods[i].name - } - } - 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) { - b.inventoryGun++; - if (b.inventoryGun > b.inventory.length - 1) b.inventoryGun = 0; - game.switchGun(); - } - }, - previousGun() { - if (b.inventory.length > 0) { - b.inventoryGun--; - if (b.inventoryGun < 0) b.inventoryGun = b.inventory.length - 1; - game.switchGun(); - } - }, - switchGun() { - b.activeGun = b.inventory[b.inventoryGun]; - game.updateGunHUD(); - game.boldActiveGunHUD(); - // mech.drop(); - }, - // tips = [ - // "You can throw blocks at dangerous speeds by holding the right mouse or spacebar.", - // "You can use your field to block damage. (right mouse or spacebar)", - // "Explosive weapons, like the grenade are good at clearing groups.", - // "Larger and faster bullets do more damage.", - // "You take more damage from colliding with larger baddies", - // "Holding large blocks slows down the player.", - // "holding blocks prevents the field energy from regenerating.", - // `There are ${mech.fieldUpgrades.length-1} possible field upgrades.`, - // `There are ${b.guns.length} different guns.`, - // "You keep field upgrades after you die.", - // "Unique level bosses always drop a field upgrade if you don't have one.", - // "You jump higher if you hold down the jump button.", - // "Crouching while firing makes bullets go faster, but slows the rate of fire.", - // ] - keyPress() { - if (keys[189]) { - // - key - game.zoomScale /= 0.9; - game.setZoom(); - } else if (keys[187]) { - // = key - game.zoomScale *= 0.9; - game.setZoom(); - } - - //full screen toggle - // if (keys[13]) { - // //enter key - // var doc = window.document; - // var docEl = doc.documentElement; - - // var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen; - // var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen; - - // if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) { - // requestFullScreen.call(docEl); - // } else { - // cancelFullScreen.call(doc); - // } - // setupCanvas(); - // } - - - if (keys[69]) { - // e swap to next active gun - game.nextGun(); - } else if (keys[81]) { - //q swap to previous active gun - game.previousGun(); - } - - if (keys[80]) { - //p for pause - if (game.paused) { - game.paused = false; - requestAnimationFrame(cycle); - } else { - game.paused = true; - game.replaceTextLog = true; - game.makeTextLog("

PAUSED

", 1); - // let text = "

PAUSED


" - // //output current mod, field, and gun info when paused - // if (mech.fieldMode !== 0) text += "

" + mech.fieldUpgrades[mech.fieldMode].name + "
" + mech.fieldUpgrades[mech.fieldMode].description + "

" - // if (b.mod !== null) text += "

" + b.mods[b.mod].name + "
" + b.mods[b.mod].description + "

" - // if (b.activeGun !== null) text += "

" + b.guns[b.activeGun].name + "
" + b.guns[b.activeGun].description + "

" - // text += "
" - // game.makeTextLog(text, 1); - } - } - - //toggle testing mode - if (keys[84]) { - // 84 = t - if (this.testing) { - this.testing = false; - } else { - this.testing = true; - } - } else if (this.testing) { - //only in testing mode - - if (keys[70]) { //cycle fields with F - if (mech.fieldMode === mech.fieldUpgrades.length - 1) { - mech.fieldUpgrades[0].effect() - } else { - mech.fieldUpgrades[mech.fieldMode + 1].effect() - } - } - if (keys[71]) { // give all guns with G - // b.giveGuns("all", 1000) - powerUps.gun.effect() - } - if (keys[72]) { // power ups with H - powerUps.spawn(game.mouseInGame.x, game.mouseInGame.y, "gun"); - powerUps.spawn(game.mouseInGame.x, game.mouseInGame.y, "ammo"); - powerUps.spawn(game.mouseInGame.x, game.mouseInGame.y, "field"); - powerUps.spawn(game.mouseInGame.x, game.mouseInGame.y, "heal"); - powerUps.spawn(game.mouseInGame.x, game.mouseInGame.y, "mod"); - } - if (keys[89]) { //add all mods with y - powerUps.mod.effect() - } - if (keys[82]) { // teleport to mouse with R - Matter.Body.setPosition(player, this.mouseInGame); - Matter.Body.setVelocity(player, { - x: 0, - y: 0 - }); - } - } - }, - zoom: null, - zoomScale: 1000, - setZoom(zoomScale = game.zoomScale) { //use in window resize in index.js - game.zoomScale = zoomScale - game.zoom = canvas.height / zoomScale; //sets starting zoom scale - }, - zoomTransition(newZoomScale, step = 2) { - const isBigger = (newZoomScale - game.zoomScale > 0) ? true : false; - requestAnimationFrame(zLoop); - const currentLevel = level.onLevel - - function zLoop() { - if (currentLevel != level.onLevel) return //stop the zoom if player goes to a new level - - if (isBigger) { - game.zoomScale += step - if (game.zoomScale >= newZoomScale) { - game.setZoom(newZoomScale); - return - } - } else { - game.zoomScale -= step - if (game.zoomScale <= newZoomScale) { - game.setZoom(newZoomScale); - return - } - } - - game.setZoom(); - requestAnimationFrame(zLoop); - } - }, - camera() { - ctx.translate(canvas.width2, canvas.height2); //center - ctx.scale(game.zoom, game.zoom); //zoom in once centered - ctx.translate(-canvas.width2 + mech.transX, -canvas.height2 + mech.transY); //translate - //calculate in game mouse position by undoing the zoom and translations - game.mouseInGame.x = (game.mouse.x - canvas.width2) / game.zoom + canvas.width2 - mech.transX; - game.mouseInGame.y = (game.mouse.y - canvas.height2) / game.zoom + canvas.height2 - mech.transY; - }, - zoomInFactor: 0, - startZoomIn(time = 180) { - game.zoom = 0; - let count = 0; - requestAnimationFrame(zLoop); - - function zLoop() { - game.zoom += canvas.height / game.zoomScale / time; - count++; - if (count < time) { - requestAnimationFrame(zLoop); - } else { - game.setZoom(); - } - } - }, - wipe() { - ctx.clearRect(0, 0, canvas.width, canvas.height); - - // ctx.fillStyle = "rgba(255,255,255,1)"; - // ctx.globalCompositeOperation = "difference"; - // ctx.fillRect(0, 0, canvas.width, canvas.height); - // ctx.globalCompositeOperation = "source-over"; - - // ctx.globalAlpha = (mech.health < 0.7) ? (mech.health+0.3)*(mech.health+0.3) : 1 - // if (mech.health < 0.7) { - // ctx.globalAlpha= 0.3 + mech.health - // ctx.fillStyle = document.body.style.backgroundColor - // ctx.fillRect(0, 0, canvas.width, canvas.height); - // ctx.globalAlpha=1; - // } else { - // ctx.clearRect(0, 0, canvas.width, canvas.height); - // } - //ctx.fillStyle = "rgba(255,255,255," + (1 - Math.sqrt(player.speed)*0.1) + ")"; - //ctx.fillStyle = "rgba(255,255,255,0.4)"; - //ctx.fillRect(0, 0, canvas.width, canvas.height); - }, - gravity() { - function addGravity(bodies, magnitude) { - for (var i = 0; i < bodies.length; i++) { - bodies[i].force.y += bodies[i].mass * magnitude; - } - } - addGravity(powerUp, game.g); - addGravity(body, game.g); - player.force.y += player.mass * mech.gravity; - }, - reset() { //run on first run, and each later run after you die - b.inventory = []; //removes guns and ammo - 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; - } - b.activeGun = null; - b.setModDefaults(); //remove mods - game.updateModHUD(); - mech.fieldUpgrades[0].effect(); //set to default field - game.paused = false; - build.isShowingBuilds = false - engine.timing.timeScale = 1; - game.fpsCap = game.fpsCapDefault; - game.makeGunHUD(); - mech.drop(); - mech.addHealth(1); - mech.alive = true; - level.onLevel = 0; - level.levelsCleared = 0; - - //resetting difficulty - game.dmgScale = 1; - b.dmgScale = 0.7; - game.accelScale = 1; - game.lookFreqScale = 1; - game.CDScale = 1; - if (document.getElementById("difficulty-select").value === 'easy') { - game.difficulty = 0; - game.isEasyMode = true; - level.difficultyDecrease(6); - } else { - game.difficulty = parseInt(document.getElementById("difficulty-select").value) - level.difficultyIncrease(game.difficulty) - } - - game.clearNow = true; - document.getElementById("text-log").style.opacity = 0; - document.getElementById("fade-out").style.opacity = 0; - document.title = "n-gon"; - if (!mech.fieldMode) mech.fieldUpgrades[0].effect(); //reset to starting field? or let them keep the field - }, - firstRun: true, - splashReturn() { - game.onTitlePage = true; - // document.getElementById('splash').onclick = 'run(this)'; - document.getElementById("splash").onclick = function () { - game.startGame(); - }; - document.getElementById("controls").style.display = "inline"; - document.getElementById("build-button").style.display = "inline" - isShowingBuilds = false - document.getElementById("settings").style.display = "inline"; - document.getElementById("splash").style.display = "inline"; - document.getElementById("dmg").style.display = "none"; - document.getElementById("health-bg").style.display = "none"; - document.body.style.cursor = "auto"; - }, - fpsInterval: 0, //set in startGame - then: null, - startGame() { - game.onTitlePage = false; - document.body.style.overflow = "hidden" - document.getElementById("build-grid").style.display = "none" - document.getElementById("controls").style.display = "none"; - document.getElementById("settings").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"; - - // window.onmousedown = function (e) { - // //mouse up event in set in index.js - - // // game.mouseDown = true; - // if (e.which === 3) { - // game.mouseDownRight = true; - // } else { - // game.mouseDown = true; - // } - // // keep this disabled unless building maps - // // if (!game.mouseDown){ - // // game.getCoords.pos1.x = Math.round(game.mouseInGame.x / 25) * 25; - // // game.getCoords.pos1.y = Math.round(game.mouseInGame.y / 25) * 25; - // // } - - // // mech.throw(); - // }; - - document.body.style.cursor = "none"; - if (this.firstRun) { - mech.spawn(); //spawns the player - b.setModDefaults(); //doesn't run on reset so that gun mods carry over to new runs - 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 player is holding something this remembers it before it gets deleted - let holdTarget; - if (mech.holdingTarget) { - holdTarget = mech.holdingTarget; - } - mech.fireCDcycle = 0 - mech.drop(); - level.fill = []; - level.fillBG = []; - level.zones = []; - level.queryList = []; - this.drawList = []; - - function removeAll(array) { - for (let i = 0; i < array.length; ++i) Matter.World.remove(engine.world, array[i]); - } - removeAll(map); - map = []; - removeAll(body); - body = []; - removeAll(mob); - mob = []; - removeAll(powerUp); - powerUp = []; - removeAll(cons); - cons = []; - removeAll(consBB); - consBB = []; - removeAll(bullet); - bullet = []; - // if player was holding something this makes a new copy to hold - if (holdTarget) { - len = body.length; - body[len] = Matter.Bodies.fromVertices(0, 0, holdTarget.vertices, { - friction: holdTarget.friction, - frictionAir: holdTarget.frictionAir, - frictionStatic: holdTarget.frictionStatic - }); - mech.holdingTarget = body[len]; - } - }, - getCoords: { - //used when building maps, outputs a draw rect command to console, only works in testing mode - pos1: { - x: 0, - y: 0 - }, - pos2: { - x: 0, - y: 0 - }, - out() { - if (keys[49]) { - this.pos1.x = Math.round(game.mouseInGame.x / 25) * 25; - this.pos1.y = Math.round(game.mouseInGame.y / 25) * 25; - } - if (keys[50]) { - //press 1 in the top left; press 2 in the bottom right;copy command from console - this.pos2.x = Math.round(game.mouseInGame.x / 25) * 25; - this.pos2.y = Math.round(game.mouseInGame.y / 25) * 25; - window.getSelection().removeAllRanges(); - var range = document.createRange(); - range.selectNode(document.getElementById("test")); - window.getSelection().addRange(range); - document.execCommand("copy"); - window.getSelection().removeAllRanges(); - console.log(`spawn.mapRect(${this.pos1.x}, ${this.pos1.y}, ${this.pos2.x - this.pos1.x}, ${this.pos2.y - this.pos1.y}); //`); - } - } - }, - fallChecks() { - // if 4000px deep - if (mech.pos.y > game.fallHeight) { - mech.death(); - // if (b.modNonEuclidean) { - // Matter.Body.setPosition(player, { - // x: player.position.x, - // y: player.position.y - 12000 - // }); - // Matter.Body.setVelocity(player, { - // x: player.velocity.x, - // y: player.velocity.y * 0 - // }); - - // mech.pos.x = player.position.x; - // mech.pos.y = playerBody.position.y - mech.yOff; - - // //smoothed mouse look translations - // const scale = 10; - // 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.transY = mech.transSmoothY - - // } else { - // mech.death(); - // } - } - - if (!(mech.cycle % 420)) { - fallCheck = function (who) { - let i = who.length; - while (i--) { - if (who[i].position.y > game.fallHeight) { - Matter.World.remove(engine.world, who[i]); - who.splice(i, 1); - } - } - }; - fallCheck(mob); - fallCheck(body); - fallCheck(powerUp); - } - }, - testingOutput() { - ctx.textAlign = "right"; - ctx.fillStyle = "#000"; - let line = 140; - 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: spawn power ups", x, line); - line += 30; - - ctx.fillText("cycle: " + game.cycle, x, line); - line += 20; - ctx.fillText("player cycle: " + mech.cycle, x, line); - line += 20; - ctx.fillText("x: " + player.position.x.toFixed(0), x, line); - line += 20; - ctx.fillText("y: " + player.position.y.toFixed(0), x, line); - line += 20; - ctx.fillText("Vx: " + mech.Vx.toFixed(2), x, line); - line += 20; - ctx.fillText("Vy: " + mech.Vy.toFixed(2), x, line); - line += 20; - ctx.fillText("Fx: " + player.force.x.toFixed(3), x, line); - line += 20; - ctx.fillText("Fy: " + player.force.y.toFixed(3), x, line); - line += 20; - ctx.fillText("yOff: " + mech.yOff.toFixed(1), x, line); - line += 20; - ctx.fillText("mass: " + player.mass.toFixed(1), x, line); - line += 20; - ctx.fillText("onGround: " + mech.onGround, x, line); - line += 20; - ctx.fillText("crouch: " + mech.crouch, x, line); - line += 20; - ctx.fillText("isHeadClear: " + mech.isHeadClear, x, line); - line += 20; - ctx.fillText("frictionAir: " + player.frictionAir.toFixed(3), x, line); - line += 20; - ctx.fillText("stepSize: " + mech.stepSize.toFixed(2), x, line); - line += 20; - ctx.fillText("zoom: " + this.zoom.toFixed(4), x, line); - line += 20; - ctx.textAlign = "center"; - ctx.fillText(`(${this.mouseInGame.x.toFixed(1)}, ${this.mouseInGame.y.toFixed(1)})`, this.mouse.x, this.mouse.y - 20); - }, - draw: { - powerUp() { - // draw power up - // ctx.globalAlpha = 0.4 * Math.sin(mech.cycle * 0.15) + 0.6; - // for (let i = 0, len = powerUp.length; i < len; ++i) { - // let vertices = powerUp[i].vertices; - // ctx.beginPath(); - // ctx.moveTo(vertices[0].x, vertices[0].y); - // for (let j = 1; j < vertices.length; j += 1) { - // ctx.lineTo(vertices[j].x, vertices[j].y); - // } - // ctx.lineTo(vertices[0].x, vertices[0].y); - // ctx.fillStyle = powerUp[i].color; - // ctx.fill(); - // } - // ctx.globalAlpha = 1; - ctx.globalAlpha = 0.4 * Math.sin(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; - }, - // map: function() { - // ctx.beginPath(); - // for (let i = 0, len = map.length; i < len; ++i) { - // let vertices = map[i].vertices; - // ctx.moveTo(vertices[0].x, vertices[0].y); - // for (let j = 1; j < vertices.length; j += 1) { - // ctx.lineTo(vertices[j].x, vertices[j].y); - // } - // ctx.lineTo(vertices[0].x, vertices[0].y); - // } - // ctx.fillStyle = "#444"; - // ctx.fill(); - // }, - mapPath: null, //holds the path for the map to speed up drawing - setPaths() { - //runs at each new level to store the path for the map since the map doesn't change - this.mapPath = new Path2D(); - for (let i = 0, len = map.length; i < len; ++i) { - let vertices = map[i].vertices; - this.mapPath.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j += 1) { - this.mapPath.lineTo(vertices[j].x, vertices[j].y); - } - this.mapPath.lineTo(vertices[0].x, vertices[0].y); - } - }, - mapFill: "#444", - bodyFill: "rgba(140,140,140,0.85)", //"#999", - bodyStroke: "#222", - drawMapPath() { - ctx.fillStyle = this.mapFill; - ctx.fill(this.mapPath); - }, - // seeEdges() { - // const eye = { - // x: mech.pos.x + 20 * Math.cos(mech.angle), - // y: mech.pos.y + 20 * Math.sin(mech.angle) - // }; - // //find all vertex nodes in range and in LOS - // findNodes = function (domain, center) { - // let nodes = []; - // for (let i = 0; i < domain.length; ++i) { - // let vertices = domain[i].vertices; - - // for (let j = 0, len = vertices.length; j < len; j++) { - // //calculate distance to player - // const dx = vertices[j].x - center.x; - // const dy = vertices[j].y - center.y; - // if (dx * dx + dy * dy < 800 * 800 && Matter.Query.ray(domain, center, vertices[j]).length === 0) { - // nodes.push(vertices[j]); - // } - // } - // } - // return nodes; - // }; - // let nodes = findNodes(map, eye); - // //sort node list by angle to player - // nodes.sort(function (a, b) { - // //sub artan2 from player loc - // const dx = a.x - eye.x; - // const dy = a.y - eye.y; - // return Math.atan2(dy, dx) - Math.atan2(dy, dx); - // }); - // //draw nodes - // ctx.lineWidth = 2; - // ctx.strokeStyle = "#000"; - // ctx.beginPath(); - // for (let i = 0; i < nodes.length; ++i) { - // ctx.lineTo(nodes[i].x, nodes[i].y); - // } - // ctx.stroke(); - // }, - // see() { - // const vertexCollision = function ( - // v1, - // v1End, - // domain, - // best = { - // x: null, - // y: null, - // dist2: Infinity, - // who: null, - // v1: null, - // v2: null - // } - // ) { - // for (let i = 0; i < domain.length; ++i) { - // let vertices = domain[i].vertices; - // const len = vertices.length - 1; - // for (let j = 0; j < len; j++) { - // results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); - // if (results.onLine1 && results.onLine2) { - // const dx = v1.x - results.x; - // const dy = v1.y - results.y; - // const dist2 = dx * dx + dy * dy; - // if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - // best = { - // x: results.x, - // y: results.y, - // dist2: dist2, - // who: domain[i], - // v1: vertices[j], - // v2: vertices[j + 1] - // }; - // } - // } - // } - // results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); - // if (results.onLine1 && results.onLine2) { - // const dx = v1.x - results.x; - // const dy = v1.y - results.y; - // const dist2 = dx * dx + dy * dy; - // if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - // best = { - // x: results.x, - // y: results.y, - // dist2: dist2, - // who: domain[i], - // v1: vertices[0], - // v2: vertices[len] - // }; - // } - // } - // } - // return best; - // }; - // const range = 3000; - // ctx.beginPath(); - // for (let i = 0; i < Math.PI * 2; i += Math.PI / 2 / 100) { - // const cosAngle = Math.cos(mech.angle + i); - // const sinAngle = Math.sin(mech.angle + i); - - // const start = { - // x: mech.pos.x + 20 * cosAngle, - // y: mech.pos.y + 20 * sinAngle - // }; - // const end = { - // x: mech.pos.x + range * cosAngle, - // y: mech.pos.y + range * sinAngle - // }; - // let result = vertexCollision(start, end, map); - // result = vertexCollision(start, end, body, result); - // result = vertexCollision(start, end, mob, result); - - // if (result.dist2 < range * range) { - // // ctx.arc(result.x, result.y, 2, 0, 2 * Math.PI); - // ctx.lineTo(result.x, result.y); - // } else { - // // ctx.arc(end.x, end.y, 2, 0, 2 * Math.PI); - // ctx.lineTo(end.x, end.y); - // } - // } - // // ctx.lineWidth = 1; - // // ctx.strokeStyle = "#000"; - // // ctx.stroke(); - // ctx.fillStyle = "rgba(0,0,0,0.3)"; - // ctx.fillStyle = "#fff"; - // ctx.fill(); - // ctx.clip(); - // }, - body() { - ctx.beginPath(); - for (let i = 0, len = body.length; i < len; ++i) { - let vertices = body[i].vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j += 1) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - } - ctx.lineWidth = 2; - ctx.fillStyle = this.bodyFill; - ctx.fill(); - ctx.strokeStyle = this.bodyStroke; - ctx.stroke(); - }, - cons() { - ctx.beginPath(); - for (let i = 0, len = cons.length; i < len; ++i) { - ctx.moveTo(cons[i].pointA.x, cons[i].pointA.y); - ctx.lineTo(cons[i].bodyB.position.x, cons[i].bodyB.position.y); - } - for (let i = 0, len = consBB.length; i < len; ++i) { - ctx.moveTo(consBB[i].bodyA.position.x, consBB[i].bodyA.position.y); - ctx.lineTo(consBB[i].bodyB.position.x, consBB[i].bodyB.position.y); - } - ctx.lineWidth = 2; - // ctx.strokeStyle = "#999"; - ctx.strokeStyle = "rgba(0,0,0,0.15)"; - ctx.stroke(); - }, - wireFrame() { - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillStyle = "#999"; - const bodies = Composite.allBodies(engine.world); - ctx.beginPath(); - for (let i = 0; i < bodies.length; ++i) { - //ctx.fillText(bodies[i].id,bodies[i].position.x,bodies[i].position.y); //shows the id of every body - let vertices = bodies[i].vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j += 1) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - } - ctx.lineWidth = 1; - ctx.strokeStyle = "#000"; - ctx.stroke(); - }, - testing() { - //zones - ctx.beginPath(); - for (let i = 0, len = level.zones.length; i < len; ++i) { - ctx.rect(level.zones[i].x1, level.zones[i].y1 + 70, level.zones[i].x2 - level.zones[i].x1, level.zones[i].y2 - level.zones[i].y1); - } - ctx.fillStyle = "rgba(0, 255, 0, 0.3)"; - ctx.fill(); - //query zones - ctx.beginPath(); - for (let i = 0, len = level.queryList.length; i < len; ++i) { - ctx.rect( - level.queryList[i].bounds.max.x, - level.queryList[i].bounds.max.y, - level.queryList[i].bounds.min.x - level.queryList[i].bounds.max.x, - level.queryList[i].bounds.min.y - level.queryList[i].bounds.max.y - ); - } - ctx.fillStyle = "rgba(0, 0, 255, 0.2)"; - ctx.fill(); - //jump - ctx.beginPath(); - let bodyDraw = jumpSensor.vertices; - ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); - for (let j = 1; j < bodyDraw.length; ++j) { - ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); - } - ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); - ctx.fillStyle = "rgba(255, 0, 0, 0.3)"; - ctx.fill(); - ctx.strokeStyle = "#000"; - ctx.stroke(); - //main body - ctx.beginPath(); - bodyDraw = playerBody.vertices; - ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); - for (let j = 1; j < bodyDraw.length; ++j) { - ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); - } - ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); - ctx.fillStyle = "rgba(0, 255, 255, 0.3)"; - ctx.fill(); - ctx.stroke(); - //head - ctx.beginPath(); - bodyDraw = playerHead.vertices; - ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); - for (let j = 1; j < bodyDraw.length; ++j) { - ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); - } - ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); - ctx.fillStyle = "rgba(255, 255, 0, 0.3)"; - ctx.fill(); - ctx.stroke(); - //head sensor - ctx.beginPath(); - bodyDraw = headSensor.vertices; - ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); - for (let j = 1; j < bodyDraw.length; ++j) { - ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); - } - ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); - ctx.fillStyle = "rgba(0, 0, 255, 0.3)"; - ctx.fill(); - ctx.stroke(); - } - }, - checkLineIntersection(v1, v1End, v2, v2End) { - // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point - let denominator, a, b, numerator1, numerator2; - let result = { - x: null, - y: null, - onLine1: false, - onLine2: false - }; - denominator = (v2End.y - v2.y) * (v1End.x - v1.x) - (v2End.x - v2.x) * (v1End.y - v1.y); - if (denominator == 0) { - return result; - } - a = v1.y - v2.y; - b = v1.x - v2.x; - numerator1 = (v2End.x - v2.x) * a - (v2End.y - v2.y) * b; - numerator2 = (v1End.x - v1.x) * a - (v1End.y - v1.y) * b; - a = numerator1 / denominator; - b = numerator2 / denominator; - - // if we cast these lines infinitely in both directions, they intersect here: - result.x = v1.x + a * (v1End.x - v1.x); - result.y = v1.y + a * (v1End.y - v1.y); - // if line1 is a segment and line2 is infinite, they intersect if: - if (a > 0 && a < 1) result.onLine1 = true; - // if line2 is a segment and line1 is infinite, they intersect if: - if (b > 0 && b < 1) result.onLine2 = true; - // if line1 and line2 are segments, they intersect if both of the above are true - return result; - }, - //was used in level design - buildingUp(e) { - if (game.mouseDown) { - game.getCoords.pos2.x = Math.round(game.mouseInGame.x / 25) * 25; - game.getCoords.pos2.y = Math.round(game.mouseInGame.y / 25) * 25; - let out; - - //body rect mode - out = `spawn.mapRect(${game.getCoords.pos1.x}, ${game.getCoords.pos1.y}, ${game.getCoords.pos2.x - game.getCoords.pos1.x}, ${game.getCoords.pos2.y - - game.getCoords.pos1.y});`; - - //mob spawn - //out = `spawn.randomMob(${game.getCoords.pos1.x}, ${game.getCoords.pos1.y}, 0.3);` - - //draw foreground - //out = `level.fill.push({ x: ${game.getCoords.pos1.x}, y: ${game.getCoords.pos1.y}, width: ${game.getCoords.pos2.x-game.getCoords.pos1.x}, height: ${game.getCoords.pos2.y-game.getCoords.pos1.y}, color: "rgba(0,0,0,0.1)"});`; - - //draw background fill - //out = `level.fillBG.push({ x: ${game.getCoords.pos1.x}, y: ${game.getCoords.pos1.y}, width: ${game.getCoords.pos2.x-game.getCoords.pos1.x}, height: ${game.getCoords.pos2.y-game.getCoords.pos1.y}, color: "#ccc"});`; - - //svg mode - //out = 'rect x="'+game.getCoords.pos1.x+'" y="'+ game.getCoords.pos1.y+'" width="'+(game.getCoords.pos2.x-game.getCoords.pos1.x)+'" height="'+(game.getCoords.pos2.y-game.getCoords.pos1.y)+'"'; - - console.log(out); - // document.getElementById("copy-this").innerHTML = out - // - // window.getSelection().removeAllRanges(); - // var range = document.createRange(); - // range.selectNode(document.getElementById('copy-this')); - // window.getSelection().addRange(range); - // document.execCommand('copy') - // window.getSelection().removeAllRanges(); - } - } +// game Object ******************************************************** +//********************************************************************* +const game = { + loop() { + 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.gravity(); + Engine.update(engine, game.delta); + game.wipe(); + game.textLog(); + mech.keyMove(); + level.checkZones(); + level.checkQuery(); + mech.move(); + mech.look(); + game.fallChecks(); + ctx.save(); + game.camera(); + if (game.testing) { + mech.draw(); + game.draw.wireFrame(); + game.draw.cons(); + game.draw.testing(); + game.drawCircle(); + ctx.restore(); + game.getCoords.out(); + game.testingOutput(); + } else { + level.drawFillBGs(); + level.exit.draw(); + level.enter.draw(); + game.draw.powerUp(); + mobs.draw(); + game.draw.cons(); + game.draw.body(); + mobs.loop(); + mech.draw(); + mech.hold(); + level.drawFills(); + game.draw.drawMapPath(); + b.draw(); + b.fire(); + game.drawCircle(); + ctx.restore(); + } + game.drawCursor(); + }, + mouse: { + x: canvas.width / 2, + y: canvas.height / 2 + }, + mouseInGame: { + x: 0, + y: 0 + }, + levelsCleared: 0, + g: 0.001, + dmgScale: null, //set in levels.setDifficulty + accelScale: null, //set in levels.setDifficulty + CDScale: null, //set in levels.setDifficulty + lookFreqScale: null, //set in levels.setDifficulty + onTitlePage: true, + paused: false, + testing: false, //testing mode: shows wireframe 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 + 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, + isBodyDamage: true, + isEasyMode: false, + difficulty: null, + // dropFPS(cap = 40, time = 15) { + // game.fpsCap = cap + // game.fpsInterval = 1000 / game.fpsCap; + // game.defaultFPSCycle = game.cycle + time + // const normalFPS = function () { + // if (game.defaultFPSCycle < game.cycle) { + // game.fpsCap = 72 + // game.fpsInterval = 1000 / game.fpsCap; + // } else { + // requestAnimationFrame(normalFPS); + // } + // }; + // requestAnimationFrame(normalFPS); + // }, + drawCursor() { + const size = 10; + ctx.beginPath(); + ctx.moveTo(game.mouse.x - size, game.mouse.y); + ctx.lineTo(game.mouse.x + size, game.mouse.y); + ctx.moveTo(game.mouse.x, game.mouse.y - size); + ctx.lineTo(game.mouse.x, game.mouse.y + size); + ctx.lineWidth = 2; + ctx.strokeStyle = "#000"; //'rgba(0,0,0,0.4)' + ctx.stroke(); // Draw it + }, + drawList: [], //so you can draw a first frame of explosions.. I know this is bad + drawTime: 8, //how long circles are drawn. use to push into drawlist.time + mobDmgColor: "rgba(255,0,0,0.7)", //used top push into drawList.color + playerDmgColor: "rgba(0,0,0,0.7)", //used top push into drawList.color + drawCircle() { + //draws a circle for two cycles, used for showing damage mostly + let i = this.drawList.length; + while (i--) { + ctx.beginPath(); //draw circle + ctx.arc(this.drawList[i].x, this.drawList[i].y, this.drawList[i].radius, 0, 2 * Math.PI); + ctx.fillStyle = this.drawList[i].color; + ctx.fill(); + if (this.drawList[i].time) { + //remove when timer runs out + this.drawList[i].time--; + } else { + this.drawList.splice(i, 1); + } + } + }, + lastLogTime: 0, + lastLogTimeBig: 0, + boldActiveGunHUD() { + if (b.inventory.length > 0) { + for (let i = 0, len = b.inventory.length; i < len; ++i) { + // document.getElementById(b.inventory[i]).style.fontSize = "25px"; + document.getElementById(b.inventory[i]).style.opacity = "0.3"; + } + // document.getElementById(b.activeGun).style.fontSize = "30px"; + if (document.getElementById(b.activeGun)) document.getElementById(b.activeGun).style.opacity = "1"; + } + }, + updateGunHUD() { + for (let i = 0, len = b.inventory.length; i < len; ++i) { + document.getElementById(b.inventory[i]).innerHTML = b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo; + } + }, + makeGunHUD() { + //remove all nodes + const myNode = document.getElementById("guns"); + while (myNode.firstChild) { + myNode.removeChild(myNode.firstChild); + } + //add nodes + for (let i = 0, len = b.inventory.length; i < len; ++i) { + const node = document.createElement("div"); + node.setAttribute("id", b.inventory[i]); + let textnode = document.createTextNode(b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo); + node.appendChild(textnode); + document.getElementById("guns").appendChild(node); + } + game.boldActiveGunHUD(); + }, + updateModHUD() { + let text = "" + for (let i = 0, len = b.mods.length; i < len; i++) { //add mods + if (b.mods[i].have) { + if (text) text += "
" //add a new line, but not on the first line + text += b.mods[i].name + } + } + 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) { + b.inventoryGun++; + if (b.inventoryGun > b.inventory.length - 1) b.inventoryGun = 0; + game.switchGun(); + } + }, + previousGun() { + if (b.inventory.length > 0) { + b.inventoryGun--; + if (b.inventoryGun < 0) b.inventoryGun = b.inventory.length - 1; + game.switchGun(); + } + }, + switchGun() { + b.activeGun = b.inventory[b.inventoryGun]; + game.updateGunHUD(); + game.boldActiveGunHUD(); + // mech.drop(); + }, + // tips = [ + // "You can throw blocks at dangerous speeds by holding the right mouse or spacebar.", + // "You can use your field to block damage. (right mouse or spacebar)", + // "Explosive weapons, like the grenade are good at clearing groups.", + // "Larger and faster bullets do more damage.", + // "You take more damage from colliding with larger baddies", + // "Holding large blocks slows down the player.", + // "holding blocks prevents the field energy from regenerating.", + // `There are ${mech.fieldUpgrades.length-1} possible field upgrades.`, + // `There are ${b.guns.length} different guns.`, + // "You keep field upgrades after you die.", + // "Unique level bosses always drop a field upgrade if you don't have one.", + // "You jump higher if you hold down the jump button.", + // "Crouching while firing makes bullets go faster, but slows the rate of fire.", + // ] + keyPress() { + if (keys[189]) { + // - key + game.zoomScale /= 0.9; + game.setZoom(); + } else if (keys[187]) { + // = key + game.zoomScale *= 0.9; + game.setZoom(); + } + + //full screen toggle + // if (keys[13]) { + // //enter key + // var doc = window.document; + // var docEl = doc.documentElement; + + // var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen; + // var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen; + + // if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) { + // requestFullScreen.call(docEl); + // } else { + // cancelFullScreen.call(doc); + // } + // setupCanvas(); + // } + + + if (keys[69]) { + // e swap to next active gun + game.nextGun(); + } else if (keys[81]) { + //q swap to previous active gun + game.previousGun(); + } + + if (keys[80]) { + //p for pause + if (game.paused) { + game.paused = false; + requestAnimationFrame(cycle); + } else { + game.paused = true; + game.replaceTextLog = true; + game.makeTextLog("

PAUSED

", 1); + // let text = "

PAUSED


" + // //output current mod, field, and gun info when paused + // if (mech.fieldMode !== 0) text += "

" + mech.fieldUpgrades[mech.fieldMode].name + "
" + mech.fieldUpgrades[mech.fieldMode].description + "

" + // if (b.mod !== null) text += "

" + b.mods[b.mod].name + "
" + b.mods[b.mod].description + "

" + // if (b.activeGun !== null) text += "

" + b.guns[b.activeGun].name + "
" + b.guns[b.activeGun].description + "

" + // text += "
" + // game.makeTextLog(text, 1); + } + } + + //toggle testing mode + if (keys[84]) { + // 84 = t + if (this.testing) { + this.testing = false; + } else { + this.testing = true; + } + } else if (this.testing) { + //only in testing mode + + if (keys[70]) { //cycle fields with F + if (mech.fieldMode === mech.fieldUpgrades.length - 1) { + mech.fieldUpgrades[0].effect() + } else { + mech.fieldUpgrades[mech.fieldMode + 1].effect() + } + } + if (keys[71]) { // give all guns with G + // b.giveGuns("all", 1000) + powerUps.gun.effect() + } + if (keys[72]) { // power ups with H + powerUps.spawn(game.mouseInGame.x, game.mouseInGame.y, "gun"); + powerUps.spawn(game.mouseInGame.x, game.mouseInGame.y, "ammo"); + powerUps.spawn(game.mouseInGame.x, game.mouseInGame.y, "field"); + powerUps.spawn(game.mouseInGame.x, game.mouseInGame.y, "heal"); + powerUps.spawn(game.mouseInGame.x, game.mouseInGame.y, "mod"); + } + if (keys[89]) { //add all mods with y + powerUps.mod.effect() + } + if (keys[82]) { // teleport to mouse with R + Matter.Body.setPosition(player, this.mouseInGame); + Matter.Body.setVelocity(player, { + x: 0, + y: 0 + }); + } + } + }, + zoom: null, + zoomScale: 1000, + setZoom(zoomScale = game.zoomScale) { //use in window resize in index.js + game.zoomScale = zoomScale + game.zoom = canvas.height / zoomScale; //sets starting zoom scale + }, + zoomTransition(newZoomScale, step = 2) { + const isBigger = (newZoomScale - game.zoomScale > 0) ? true : false; + requestAnimationFrame(zLoop); + const currentLevel = level.onLevel + + function zLoop() { + if (currentLevel != level.onLevel) return //stop the zoom if player goes to a new level + + if (isBigger) { + game.zoomScale += step + if (game.zoomScale >= newZoomScale) { + game.setZoom(newZoomScale); + return + } + } else { + game.zoomScale -= step + if (game.zoomScale <= newZoomScale) { + game.setZoom(newZoomScale); + return + } + } + + game.setZoom(); + requestAnimationFrame(zLoop); + } + }, + camera() { + ctx.translate(canvas.width2, canvas.height2); //center + ctx.scale(game.zoom, game.zoom); //zoom in once centered + ctx.translate(-canvas.width2 + mech.transX, -canvas.height2 + mech.transY); //translate + //calculate in game mouse position by undoing the zoom and translations + game.mouseInGame.x = (game.mouse.x - canvas.width2) / game.zoom + canvas.width2 - mech.transX; + game.mouseInGame.y = (game.mouse.y - canvas.height2) / game.zoom + canvas.height2 - mech.transY; + }, + zoomInFactor: 0, + startZoomIn(time = 180) { + game.zoom = 0; + let count = 0; + requestAnimationFrame(zLoop); + + function zLoop() { + game.zoom += canvas.height / game.zoomScale / time; + count++; + if (count < time) { + requestAnimationFrame(zLoop); + } else { + game.setZoom(); + } + } + }, + wipe() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // ctx.fillStyle = "rgba(255,255,255,1)"; + // ctx.globalCompositeOperation = "difference"; + // ctx.fillRect(0, 0, canvas.width, canvas.height); + // ctx.globalCompositeOperation = "source-over"; + + // ctx.globalAlpha = (mech.health < 0.7) ? (mech.health+0.3)*(mech.health+0.3) : 1 + // if (mech.health < 0.7) { + // ctx.globalAlpha= 0.3 + mech.health + // ctx.fillStyle = document.body.style.backgroundColor + // ctx.fillRect(0, 0, canvas.width, canvas.height); + // ctx.globalAlpha=1; + // } else { + // ctx.clearRect(0, 0, canvas.width, canvas.height); + // } + //ctx.fillStyle = "rgba(255,255,255," + (1 - Math.sqrt(player.speed)*0.1) + ")"; + //ctx.fillStyle = "rgba(255,255,255,0.4)"; + //ctx.fillRect(0, 0, canvas.width, canvas.height); + }, + gravity() { + function addGravity(bodies, magnitude) { + for (var i = 0; i < bodies.length; i++) { + bodies[i].force.y += bodies[i].mass * magnitude; + } + } + addGravity(powerUp, game.g); + addGravity(body, game.g); + player.force.y += player.mass * mech.gravity; + }, + reset() { //run on first run, and each later run after you die + b.inventory = []; //removes guns and ammo + 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; + } + b.activeGun = null; + b.setModDefaults(); //remove mods + game.updateModHUD(); + mech.fieldUpgrades[0].effect(); //set to default field + game.paused = false; + build.isShowingBuilds = false + engine.timing.timeScale = 1; + game.fpsCap = game.fpsCapDefault; + game.makeGunHUD(); + mech.drop(); + mech.addHealth(1); + mech.alive = true; + level.onLevel = 0; + level.levelsCleared = 0; + + //resetting difficulty + game.dmgScale = 1; + b.dmgScale = 0.7; + game.accelScale = 1; + game.lookFreqScale = 1; + game.CDScale = 1; + if (document.getElementById("difficulty-select").value === 'easy') { + game.difficulty = 0; + game.isEasyMode = true; + level.difficultyDecrease(6); + } else { + game.difficulty = parseInt(document.getElementById("difficulty-select").value) + level.difficultyIncrease(game.difficulty) + } + + game.clearNow = true; + document.getElementById("text-log").style.opacity = 0; + document.getElementById("fade-out").style.opacity = 0; + document.title = "n-gon"; + if (!mech.fieldMode) mech.fieldUpgrades[0].effect(); //reset to starting field? or let them keep the field + }, + firstRun: true, + splashReturn() { + game.onTitlePage = true; + // document.getElementById('splash').onclick = 'run(this)'; + document.getElementById("splash").onclick = function () { + game.startGame(); + }; + document.getElementById("controls").style.display = "inline"; + document.getElementById("build-button").style.display = "inline" + isShowingBuilds = false + document.getElementById("settings").style.display = "inline"; + document.getElementById("splash").style.display = "inline"; + document.getElementById("dmg").style.display = "none"; + document.getElementById("health-bg").style.display = "none"; + document.body.style.cursor = "auto"; + }, + fpsInterval: 0, //set in startGame + then: null, + startGame() { + game.onTitlePage = false; + document.body.style.overflow = "hidden" + document.getElementById("build-grid").style.display = "none" + document.getElementById("controls").style.display = "none"; + document.getElementById("settings").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"; + + // window.onmousedown = function (e) { + // //mouse up event in set in index.js + + // // game.mouseDown = true; + // if (e.which === 3) { + // game.mouseDownRight = true; + // } else { + // game.mouseDown = true; + // } + // // keep this disabled unless building maps + // // if (!game.mouseDown){ + // // game.getCoords.pos1.x = Math.round(game.mouseInGame.x / 25) * 25; + // // game.getCoords.pos1.y = Math.round(game.mouseInGame.y / 25) * 25; + // // } + + // // mech.throw(); + // }; + + document.body.style.cursor = "none"; + if (this.firstRun) { + mech.spawn(); //spawns the player + b.setModDefaults(); //doesn't run on reset so that gun mods carry over to new runs + 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 player is holding something this remembers it before it gets deleted + let holdTarget; + if (mech.holdingTarget) { + holdTarget = mech.holdingTarget; + } + mech.fireCDcycle = 0 + mech.drop(); + level.fill = []; + level.fillBG = []; + level.zones = []; + level.queryList = []; + this.drawList = []; + + function removeAll(array) { + for (let i = 0; i < array.length; ++i) Matter.World.remove(engine.world, array[i]); + } + removeAll(map); + map = []; + removeAll(body); + body = []; + removeAll(mob); + mob = []; + removeAll(powerUp); + powerUp = []; + removeAll(cons); + cons = []; + removeAll(consBB); + consBB = []; + removeAll(bullet); + bullet = []; + // if player was holding something this makes a new copy to hold + if (holdTarget) { + len = body.length; + body[len] = Matter.Bodies.fromVertices(0, 0, holdTarget.vertices, { + friction: holdTarget.friction, + frictionAir: holdTarget.frictionAir, + frictionStatic: holdTarget.frictionStatic + }); + mech.holdingTarget = body[len]; + } + }, + getCoords: { + //used when building maps, outputs a draw rect command to console, only works in testing mode + pos1: { + x: 0, + y: 0 + }, + pos2: { + x: 0, + y: 0 + }, + out() { + if (keys[49]) { + this.pos1.x = Math.round(game.mouseInGame.x / 25) * 25; + this.pos1.y = Math.round(game.mouseInGame.y / 25) * 25; + } + if (keys[50]) { + //press 1 in the top left; press 2 in the bottom right;copy command from console + this.pos2.x = Math.round(game.mouseInGame.x / 25) * 25; + this.pos2.y = Math.round(game.mouseInGame.y / 25) * 25; + window.getSelection().removeAllRanges(); + var range = document.createRange(); + range.selectNode(document.getElementById("test")); + window.getSelection().addRange(range); + document.execCommand("copy"); + window.getSelection().removeAllRanges(); + console.log(`spawn.mapRect(${this.pos1.x}, ${this.pos1.y}, ${this.pos2.x - this.pos1.x}, ${this.pos2.y - this.pos1.y}); //`); + } + } + }, + fallChecks() { + // if 4000px deep + if (mech.pos.y > game.fallHeight) { + mech.death(); + // if (b.modNonEuclidean) { + // Matter.Body.setPosition(player, { + // x: player.position.x, + // y: player.position.y - 12000 + // }); + // Matter.Body.setVelocity(player, { + // x: player.velocity.x, + // y: player.velocity.y * 0 + // }); + + // mech.pos.x = player.position.x; + // mech.pos.y = playerBody.position.y - mech.yOff; + + // //smoothed mouse look translations + // const scale = 10; + // 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.transY = mech.transSmoothY + + // } else { + // mech.death(); + // } + } + + if (!(mech.cycle % 420)) { + fallCheck = function (who) { + let i = who.length; + while (i--) { + if (who[i].position.y > game.fallHeight) { + Matter.World.remove(engine.world, who[i]); + who.splice(i, 1); + } + } + }; + fallCheck(mob); + fallCheck(body); + fallCheck(powerUp); + } + }, + testingOutput() { + ctx.textAlign = "right"; + ctx.fillStyle = "#000"; + let line = 140; + 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: spawn power ups", x, line); + line += 30; + + ctx.fillText("cycle: " + game.cycle, x, line); + line += 20; + ctx.fillText("player cycle: " + mech.cycle, x, line); + line += 20; + ctx.fillText("x: " + player.position.x.toFixed(0), x, line); + line += 20; + ctx.fillText("y: " + player.position.y.toFixed(0), x, line); + line += 20; + ctx.fillText("Vx: " + mech.Vx.toFixed(2), x, line); + line += 20; + ctx.fillText("Vy: " + mech.Vy.toFixed(2), x, line); + line += 20; + ctx.fillText("Fx: " + player.force.x.toFixed(3), x, line); + line += 20; + ctx.fillText("Fy: " + player.force.y.toFixed(3), x, line); + line += 20; + ctx.fillText("yOff: " + mech.yOff.toFixed(1), x, line); + line += 20; + ctx.fillText("mass: " + player.mass.toFixed(1), x, line); + line += 20; + ctx.fillText("onGround: " + mech.onGround, x, line); + line += 20; + ctx.fillText("crouch: " + mech.crouch, x, line); + line += 20; + ctx.fillText("isHeadClear: " + mech.isHeadClear, x, line); + line += 20; + ctx.fillText("frictionAir: " + player.frictionAir.toFixed(3), x, line); + line += 20; + ctx.fillText("stepSize: " + mech.stepSize.toFixed(2), x, line); + line += 20; + ctx.fillText("zoom: " + this.zoom.toFixed(4), x, line); + line += 20; + ctx.textAlign = "center"; + ctx.fillText(`(${this.mouseInGame.x.toFixed(1)}, ${this.mouseInGame.y.toFixed(1)})`, this.mouse.x, this.mouse.y - 20); + }, + draw: { + powerUp() { + // draw power up + // ctx.globalAlpha = 0.4 * Math.sin(mech.cycle * 0.15) + 0.6; + // for (let i = 0, len = powerUp.length; i < len; ++i) { + // let vertices = powerUp[i].vertices; + // ctx.beginPath(); + // ctx.moveTo(vertices[0].x, vertices[0].y); + // for (let j = 1; j < vertices.length; j += 1) { + // ctx.lineTo(vertices[j].x, vertices[j].y); + // } + // ctx.lineTo(vertices[0].x, vertices[0].y); + // ctx.fillStyle = powerUp[i].color; + // ctx.fill(); + // } + // ctx.globalAlpha = 1; + ctx.globalAlpha = 0.4 * Math.sin(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; + }, + // map: function() { + // ctx.beginPath(); + // for (let i = 0, len = map.length; i < len; ++i) { + // let vertices = map[i].vertices; + // ctx.moveTo(vertices[0].x, vertices[0].y); + // for (let j = 1; j < vertices.length; j += 1) { + // ctx.lineTo(vertices[j].x, vertices[j].y); + // } + // ctx.lineTo(vertices[0].x, vertices[0].y); + // } + // ctx.fillStyle = "#444"; + // ctx.fill(); + // }, + mapPath: null, //holds the path for the map to speed up drawing + setPaths() { + //runs at each new level to store the path for the map since the map doesn't change + this.mapPath = new Path2D(); + for (let i = 0, len = map.length; i < len; ++i) { + let vertices = map[i].vertices; + this.mapPath.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + this.mapPath.lineTo(vertices[j].x, vertices[j].y); + } + this.mapPath.lineTo(vertices[0].x, vertices[0].y); + } + }, + mapFill: "#444", + bodyFill: "rgba(140,140,140,0.85)", //"#999", + bodyStroke: "#222", + drawMapPath() { + ctx.fillStyle = this.mapFill; + ctx.fill(this.mapPath); + }, + // seeEdges() { + // const eye = { + // x: mech.pos.x + 20 * Math.cos(mech.angle), + // y: mech.pos.y + 20 * Math.sin(mech.angle) + // }; + // //find all vertex nodes in range and in LOS + // findNodes = function (domain, center) { + // let nodes = []; + // for (let i = 0; i < domain.length; ++i) { + // let vertices = domain[i].vertices; + + // for (let j = 0, len = vertices.length; j < len; j++) { + // //calculate distance to player + // const dx = vertices[j].x - center.x; + // const dy = vertices[j].y - center.y; + // if (dx * dx + dy * dy < 800 * 800 && Matter.Query.ray(domain, center, vertices[j]).length === 0) { + // nodes.push(vertices[j]); + // } + // } + // } + // return nodes; + // }; + // let nodes = findNodes(map, eye); + // //sort node list by angle to player + // nodes.sort(function (a, b) { + // //sub artan2 from player loc + // const dx = a.x - eye.x; + // const dy = a.y - eye.y; + // return Math.atan2(dy, dx) - Math.atan2(dy, dx); + // }); + // //draw nodes + // ctx.lineWidth = 2; + // ctx.strokeStyle = "#000"; + // ctx.beginPath(); + // for (let i = 0; i < nodes.length; ++i) { + // ctx.lineTo(nodes[i].x, nodes[i].y); + // } + // ctx.stroke(); + // }, + // see() { + // const vertexCollision = function ( + // v1, + // v1End, + // domain, + // best = { + // x: null, + // y: null, + // dist2: Infinity, + // who: null, + // v1: null, + // v2: null + // } + // ) { + // for (let i = 0; i < domain.length; ++i) { + // let vertices = domain[i].vertices; + // const len = vertices.length - 1; + // for (let j = 0; j < len; j++) { + // results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + // if (results.onLine1 && results.onLine2) { + // const dx = v1.x - results.x; + // const dy = v1.y - results.y; + // const dist2 = dx * dx + dy * dy; + // if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + // best = { + // x: results.x, + // y: results.y, + // dist2: dist2, + // who: domain[i], + // v1: vertices[j], + // v2: vertices[j + 1] + // }; + // } + // } + // } + // results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + // if (results.onLine1 && results.onLine2) { + // const dx = v1.x - results.x; + // const dy = v1.y - results.y; + // const dist2 = dx * dx + dy * dy; + // if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + // best = { + // x: results.x, + // y: results.y, + // dist2: dist2, + // who: domain[i], + // v1: vertices[0], + // v2: vertices[len] + // }; + // } + // } + // } + // return best; + // }; + // const range = 3000; + // ctx.beginPath(); + // for (let i = 0; i < Math.PI * 2; i += Math.PI / 2 / 100) { + // const cosAngle = Math.cos(mech.angle + i); + // const sinAngle = Math.sin(mech.angle + i); + + // const start = { + // x: mech.pos.x + 20 * cosAngle, + // y: mech.pos.y + 20 * sinAngle + // }; + // const end = { + // x: mech.pos.x + range * cosAngle, + // y: mech.pos.y + range * sinAngle + // }; + // let result = vertexCollision(start, end, map); + // result = vertexCollision(start, end, body, result); + // result = vertexCollision(start, end, mob, result); + + // if (result.dist2 < range * range) { + // // ctx.arc(result.x, result.y, 2, 0, 2 * Math.PI); + // ctx.lineTo(result.x, result.y); + // } else { + // // ctx.arc(end.x, end.y, 2, 0, 2 * Math.PI); + // ctx.lineTo(end.x, end.y); + // } + // } + // // ctx.lineWidth = 1; + // // ctx.strokeStyle = "#000"; + // // ctx.stroke(); + // ctx.fillStyle = "rgba(0,0,0,0.3)"; + // ctx.fillStyle = "#fff"; + // ctx.fill(); + // ctx.clip(); + // }, + body() { + ctx.beginPath(); + for (let i = 0, len = body.length; i < len; ++i) { + let vertices = body[i].vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + } + ctx.lineWidth = 2; + ctx.fillStyle = this.bodyFill; + ctx.fill(); + ctx.strokeStyle = this.bodyStroke; + ctx.stroke(); + }, + cons() { + ctx.beginPath(); + for (let i = 0, len = cons.length; i < len; ++i) { + ctx.moveTo(cons[i].pointA.x, cons[i].pointA.y); + ctx.lineTo(cons[i].bodyB.position.x, cons[i].bodyB.position.y); + } + for (let i = 0, len = consBB.length; i < len; ++i) { + ctx.moveTo(consBB[i].bodyA.position.x, consBB[i].bodyA.position.y); + ctx.lineTo(consBB[i].bodyB.position.x, consBB[i].bodyB.position.y); + } + ctx.lineWidth = 2; + // ctx.strokeStyle = "#999"; + ctx.strokeStyle = "rgba(0,0,0,0.15)"; + ctx.stroke(); + }, + wireFrame() { + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillStyle = "#999"; + const bodies = Composite.allBodies(engine.world); + ctx.beginPath(); + for (let i = 0; i < bodies.length; ++i) { + //ctx.fillText(bodies[i].id,bodies[i].position.x,bodies[i].position.y); //shows the id of every body + let vertices = bodies[i].vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + } + ctx.lineWidth = 1; + ctx.strokeStyle = "#000"; + ctx.stroke(); + }, + testing() { + //zones + ctx.beginPath(); + for (let i = 0, len = level.zones.length; i < len; ++i) { + ctx.rect(level.zones[i].x1, level.zones[i].y1 + 70, level.zones[i].x2 - level.zones[i].x1, level.zones[i].y2 - level.zones[i].y1); + } + ctx.fillStyle = "rgba(0, 255, 0, 0.3)"; + ctx.fill(); + //query zones + ctx.beginPath(); + for (let i = 0, len = level.queryList.length; i < len; ++i) { + ctx.rect( + level.queryList[i].bounds.max.x, + level.queryList[i].bounds.max.y, + level.queryList[i].bounds.min.x - level.queryList[i].bounds.max.x, + level.queryList[i].bounds.min.y - level.queryList[i].bounds.max.y + ); + } + ctx.fillStyle = "rgba(0, 0, 255, 0.2)"; + ctx.fill(); + //jump + ctx.beginPath(); + let bodyDraw = jumpSensor.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(255, 0, 0, 0.3)"; + ctx.fill(); + ctx.strokeStyle = "#000"; + ctx.stroke(); + //main body + ctx.beginPath(); + bodyDraw = playerBody.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(0, 255, 255, 0.3)"; + ctx.fill(); + ctx.stroke(); + //head + ctx.beginPath(); + bodyDraw = playerHead.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(255, 255, 0, 0.3)"; + ctx.fill(); + ctx.stroke(); + //head sensor + ctx.beginPath(); + bodyDraw = headSensor.vertices; + ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y); + for (let j = 1; j < bodyDraw.length; ++j) { + ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y); + } + ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y); + ctx.fillStyle = "rgba(0, 0, 255, 0.3)"; + ctx.fill(); + ctx.stroke(); + } + }, + checkLineIntersection(v1, v1End, v2, v2End) { + // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point + let denominator, a, b, numerator1, numerator2; + let result = { + x: null, + y: null, + onLine1: false, + onLine2: false + }; + denominator = (v2End.y - v2.y) * (v1End.x - v1.x) - (v2End.x - v2.x) * (v1End.y - v1.y); + if (denominator == 0) { + return result; + } + a = v1.y - v2.y; + b = v1.x - v2.x; + numerator1 = (v2End.x - v2.x) * a - (v2End.y - v2.y) * b; + numerator2 = (v1End.x - v1.x) * a - (v1End.y - v1.y) * b; + a = numerator1 / denominator; + b = numerator2 / denominator; + + // if we cast these lines infinitely in both directions, they intersect here: + result.x = v1.x + a * (v1End.x - v1.x); + result.y = v1.y + a * (v1End.y - v1.y); + // if line1 is a segment and line2 is infinite, they intersect if: + if (a > 0 && a < 1) result.onLine1 = true; + // if line2 is a segment and line1 is infinite, they intersect if: + if (b > 0 && b < 1) result.onLine2 = true; + // if line1 and line2 are segments, they intersect if both of the above are true + return result; + }, + //was used in level design + buildingUp(e) { + if (game.mouseDown) { + game.getCoords.pos2.x = Math.round(game.mouseInGame.x / 25) * 25; + game.getCoords.pos2.y = Math.round(game.mouseInGame.y / 25) * 25; + let out; + + //body rect mode + out = `spawn.mapRect(${game.getCoords.pos1.x}, ${game.getCoords.pos1.y}, ${game.getCoords.pos2.x - game.getCoords.pos1.x}, ${game.getCoords.pos2.y - + game.getCoords.pos1.y});`; + + //mob spawn + //out = `spawn.randomMob(${game.getCoords.pos1.x}, ${game.getCoords.pos1.y}, 0.3);` + + //draw foreground + //out = `level.fill.push({ x: ${game.getCoords.pos1.x}, y: ${game.getCoords.pos1.y}, width: ${game.getCoords.pos2.x-game.getCoords.pos1.x}, height: ${game.getCoords.pos2.y-game.getCoords.pos1.y}, color: "rgba(0,0,0,0.1)"});`; + + //draw background fill + //out = `level.fillBG.push({ x: ${game.getCoords.pos1.x}, y: ${game.getCoords.pos1.y}, width: ${game.getCoords.pos2.x-game.getCoords.pos1.x}, height: ${game.getCoords.pos2.y-game.getCoords.pos1.y}, color: "#ccc"});`; + + //svg mode + //out = 'rect x="'+game.getCoords.pos1.x+'" y="'+ game.getCoords.pos1.y+'" width="'+(game.getCoords.pos2.x-game.getCoords.pos1.x)+'" height="'+(game.getCoords.pos2.y-game.getCoords.pos1.y)+'"'; + + console.log(out); + // document.getElementById("copy-this").innerHTML = out + // + // window.getSelection().removeAllRanges(); + // var range = document.createRange(); + // range.selectNode(document.getElementById('copy-this')); + // window.getSelection().addRange(range); + // document.execCommand('copy') + // window.getSelection().removeAllRanges(); + } + } }; \ No newline at end of file diff --git a/js/index.js b/js/index.js index d76063c..6b5b09d 100644 --- a/js/index.js +++ b/js/index.js @@ -1,347 +1,347 @@ -"use strict"; -/* TODO: ******************************************* -***************************************************** - -add builds with combinations of gun, field and mobs - use the pull down menu - -dynamically generate html about fields, guns and mods - -add grid check to improve queries over large body arrays - something about broad phase - having trouble with this, might give up - -gun: like drones, but fast moving and short lived - dies after doing damage - -gun: Spirit Bomb (singularity) - use charge up like rail gun - electricity graphics like plasma torch - suck in nearby mobs, power ups?, blocks? - sucked in stuff increase size - uses energy - -mod: auto pick up guns, heals, ammo - use the same rule for drones - maybe give some other bonus too? - -rework junk bot - it's behavior is too unpredictable - range is unclear - having the bullets last long after doing dmg isn't fun - we want a fun gun that acts like a melee weapon - -atmosphere levels - large rotating fan that the player has to move through - give the user a rest, between combat - low combat - nonaggressive mobs - one mob attacking the passive mobs - more graphics - -Boss levels - boss grows and spilt, if you don't kill it fast - sensor that locks you in after you enter the boss room - boss that eats other mobs and gains stats from them - chance to spawn on any level (past level 5) - boss that knows how to shoot (player) bullets that collide with player - overwrite custom engine collision bullet mob function. - -add a key that player picks up and needs to set on the exit door to open it - -make power ups keep moving to player if the pickup field is turned off before they get picked up - not sure how to do this without adding a constant check - -animate new level spawn by having the map aspects randomly fly into place - -new map with repeating endlessness - get ideas from Manifold Garden game - if falling, get teleported above the map - I tried it, but had trouble getting the camera to adjust to the teleportation - this can apply to blocks mobs, and power ups as well - -field power up effects - field allows player to hold and throw living mobs - -mod power ups ideas - double jump - bullet on mob damage effects - add to the array mob.do new mob behaviors - add a damage over time - add a freeze - -give mobs more animal-like behaviors - like rain world - give mobs something to do when they don't see player - explore map - eat power ups - drop power up (if killed after eating one) - mobs some times aren't aggressive - when low on life or after taking a large hit - mobs can fight each other - this might be hard to code - isolated mobs try to group up. - -game mechanics - mechanics that support the physics engine - add rope/constraint - get ideas from game: limbo / inside - environmental hazards - laser - lava - button / switch - door - fizzler - moving platform - map zones - water - low friction ground - bouncy ground - - -// collision info: - category mask -powerUp: 0x100000 0x100001 -body: 0x010000 0x011111 -player: 0x001000 0x010011 -bullet: 0x000100 0x010011 -mob: 0x000010 0x011111 -mobBullet: 0x000010 0x011101 -mobShield: 0x000010 0x001100 -map: 0x000001 0x111111 - - - - -*/ - -//build build grid display -const build = { - isShowingBuilds: false, - list: [], - choosePowerUp(who, index, type) { - //check if matching a current power up - for (let i = 0; i < build.list.length; i++) { - if (build.list[i].index === index && build.list[i].type === type) { //if already click, toggle off - build.list.splice(i, 1); - who.style.backgroundColor = "#fff" - return - } - } - - //check if trying to get a second field - if (type === "field") { - for (let i = 0; i < build.list.length; i++) { - if (build.list[i].type === "field") { //if already click, toggle off - build.list[i].who.style.backgroundColor = "#fff" - build.list.splice(i, 1); - } - } - } - - if (build.list.length < 5) { //add to build array - // who.style.border = "2px solid #333" - who.style.backgroundColor = "#919ba8" //"#868f9a" - build.list[build.list.length] = { - who: who, - index: index, - type: type - } - } - }, - startBuildRun() { - spawn.setSpawnList(); - spawn.setSpawnList(); - game.startGame(); - game.difficulty = 6; - level.isBuildRun = true; - for (let i = 0; i < build.list.length; i++) { - if (build.list[i].type === "field") { - mech.fieldUpgrades[build.list[i].index].effect(); - } else if (build.list[i].type === "gun") { - b.giveGuns(build.list[i].index) - } else if (build.list[i].type === "mod") { - b.giveMod(build.list[i].index) - } - } - } -} - -document.getElementById("build-button").addEventListener("click", () => { - document.getElementById("build-button").style.display = "none"; - const el = document.getElementById("build-grid") - if (build.isShowingBuilds) { - el.style.display = "none" - build.isShowingBuilds = false - document.body.style.overflow = "hidden" - document.getElementById("controls").style.display = 'inline' - document.getElementById("settings").style.display = 'inline' - } else { - build.list = [] - // let text = '

choose up to 5 powers

' - let text = - `
- - - start - - -
-
- Choose up to five power ups. Once you start, only health and ammo will drop, so pick carefully. -
` - for (let i = 1, 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 = b.mods.length; i < len; i++) { - text += `
  ${b.mods[i].name}
${b.mods[i].description}
` - } - el.innerHTML = text - el.style.display = "grid" - build.isShowingBuilds = true - document.body.style.overflowY = "scroll"; - document.body.style.overflowX = "hidden"; - document.getElementById("controls").style.display = 'none' - document.getElementById("settings").style.display = 'none' - } -}); - -//set up canvas -var canvas = document.getElementById("canvas"); -//using "const" causes problems in safari when an ID shares the same name. -const ctx = canvas.getContext("2d"); -document.body.style.backgroundColor = "#fff"; - -//disable pop up menu on right click -document.oncontextmenu = function () { - return false; -} - -function setupCanvas() { - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; - canvas.width2 = canvas.width / 2; //precalculated because I use this often (in mouse look) - canvas.height2 = canvas.height / 2; - canvas.diagonal = Math.sqrt(canvas.width2 * canvas.width2 + canvas.height2 * canvas.height2); - ctx.font = "15px Arial"; - ctx.lineJoin = "round"; - ctx.lineCap = "round"; - // ctx.lineCap='square'; - game.setZoom(); -} -setupCanvas(); -window.onresize = () => { - setupCanvas(); -}; - -//mouse move input -document.body.addEventListener("mousemove", (e) => { - game.mouse.x = e.clientX; - game.mouse.y = e.clientY; -}); - -document.body.addEventListener("mouseup", (e) => { - // game.buildingUp(e); //uncomment when building levels - // game.mouseDown = false; - // console.log(e) - if (e.which === 3) { - game.mouseDownRight = false; - } else { - game.mouseDown = false; - } -}); - -document.body.addEventListener("mousedown", (e) => { - if (e.which === 3) { - game.mouseDownRight = true; - } else { - game.mouseDown = true; - } -}); - -//keyboard input -const keys = []; -document.body.addEventListener("keydown", (e) => { - keys[e.keyCode] = true; - game.keyPress(); -}); - -document.body.addEventListener("keyup", (e) => { - keys[e.keyCode] = false; -}); - -document.body.addEventListener("wheel", (e) => { - if (e.deltaY > 0) { - game.nextGun(); - } else { - game.previousGun(); - } -}, { - passive: true -}); - -document.getElementById("fps-select").addEventListener("input", () => { - let value = document.getElementById("fps-select").value - if (value === 'max') { - game.fpsCapDefault = 999999999; - } else if (value === '72') { - game.fpsCapDefault = 72 - } else if (value === '60') { - game.fpsCapDefault = 60 - } else if (value === '45') { - game.fpsCapDefault = 45 - } else if (value === '30') { - game.fpsCapDefault = 30 - } else if (value === '15') { - game.fpsCapDefault = 15 - } -}); - -document.getElementById("body-damage").addEventListener("input", () => { - game.isBodyDamage = document.getElementById("body-damage").checked -}); - -// function playSound(id) { -// //play sound -// if (false) { -// //sounds are turned off for now -// // if (document.getElementById(id)) { -// var sound = document.getElementById(id); //setup audio -// sound.currentTime = 0; //reset position of playback to zero //sound.load(); -// sound.play(); -// } -// } - -function shuffle(array) { - var currentIndex = array.length, - temporaryValue, - randomIndex; - // While there remain elements to shuffle... - while (0 !== currentIndex) { - // Pick a remaining element... - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - // And swap it with the current element. - temporaryValue = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = temporaryValue; - } - return array; -} - - - -//main loop ************************************************************ -//********************************************************************** -function cycle() { - if (!game.paused) requestAnimationFrame(cycle); - const now = Date.now(); - const elapsed = now - game.then; // calc elapsed time since last loop - if (elapsed > game.fpsInterval) { // if enough time has elapsed, draw the next frame - game.then = now - (elapsed % game.fpsInterval); // Get ready for next frame by setting then=now. Also, adjust for fpsInterval not being multiple of 16.67 - game.loop(); - } +"use strict"; +/* TODO: ******************************************* +***************************************************** + +add builds with combinations of gun, field and mobs + use the pull down menu + +dynamically generate html about fields, guns and mods + +add grid check to improve queries over large body arrays + something about broad phase + having trouble with this, might give up + +gun: like drones, but fast moving and short lived + dies after doing damage + +gun: Spirit Bomb (singularity) + use charge up like rail gun + electricity graphics like plasma torch + suck in nearby mobs, power ups?, blocks? + sucked in stuff increase size + uses energy + +mod: auto pick up guns, heals, ammo + use the same rule for drones + maybe give some other bonus too? + +rework junk bot + it's behavior is too unpredictable + range is unclear + having the bullets last long after doing dmg isn't fun + we want a fun gun that acts like a melee weapon + +atmosphere levels + large rotating fan that the player has to move through + give the user a rest, between combat + low combat + nonaggressive mobs + one mob attacking the passive mobs + more graphics + +Boss levels + boss grows and spilt, if you don't kill it fast + sensor that locks you in after you enter the boss room + boss that eats other mobs and gains stats from them + chance to spawn on any level (past level 5) + boss that knows how to shoot (player) bullets that collide with player + overwrite custom engine collision bullet mob function. + +add a key that player picks up and needs to set on the exit door to open it + +make power ups keep moving to player if the pickup field is turned off before they get picked up + not sure how to do this without adding a constant check + +animate new level spawn by having the map aspects randomly fly into place + +new map with repeating endlessness + get ideas from Manifold Garden game + if falling, get teleported above the map + I tried it, but had trouble getting the camera to adjust to the teleportation + this can apply to blocks mobs, and power ups as well + +field power up effects + field allows player to hold and throw living mobs + +mod power ups ideas + double jump + bullet on mob damage effects + add to the array mob.do new mob behaviors + add a damage over time + add a freeze + +give mobs more animal-like behaviors + like rain world + give mobs something to do when they don't see player + explore map + eat power ups + drop power up (if killed after eating one) + mobs some times aren't aggressive + when low on life or after taking a large hit + mobs can fight each other + this might be hard to code + isolated mobs try to group up. + +game mechanics + mechanics that support the physics engine + add rope/constraint + get ideas from game: limbo / inside + environmental hazards + laser + lava + button / switch + door + fizzler + moving platform + map zones + water + low friction ground + bouncy ground + + +// collision info: + category mask +powerUp: 0x100000 0x100001 +body: 0x010000 0x011111 +player: 0x001000 0x010011 +bullet: 0x000100 0x010011 +mob: 0x000010 0x011111 +mobBullet: 0x000010 0x011101 +mobShield: 0x000010 0x001100 +map: 0x000001 0x111111 + + + + +*/ + +//build build grid display +const build = { + isShowingBuilds: false, + list: [], + choosePowerUp(who, index, type) { + //check if matching a current power up + for (let i = 0; i < build.list.length; i++) { + if (build.list[i].index === index && build.list[i].type === type) { //if already click, toggle off + build.list.splice(i, 1); + who.style.backgroundColor = "#fff" + return + } + } + + //check if trying to get a second field + if (type === "field") { + for (let i = 0; i < build.list.length; i++) { + if (build.list[i].type === "field") { //if already click, toggle off + build.list[i].who.style.backgroundColor = "#fff" + build.list.splice(i, 1); + } + } + } + + if (build.list.length < 5) { //add to build array + // who.style.border = "2px solid #333" + who.style.backgroundColor = "#919ba8" //"#868f9a" + build.list[build.list.length] = { + who: who, + index: index, + type: type + } + } + }, + startBuildRun() { + spawn.setSpawnList(); + spawn.setSpawnList(); + game.startGame(); + game.difficulty = 6; + level.isBuildRun = true; + for (let i = 0; i < build.list.length; i++) { + if (build.list[i].type === "field") { + mech.fieldUpgrades[build.list[i].index].effect(); + } else if (build.list[i].type === "gun") { + b.giveGuns(build.list[i].index) + } else if (build.list[i].type === "mod") { + b.giveMod(build.list[i].index) + } + } + } +} + +document.getElementById("build-button").addEventListener("click", () => { + document.getElementById("build-button").style.display = "none"; + const el = document.getElementById("build-grid") + if (build.isShowingBuilds) { + el.style.display = "none" + build.isShowingBuilds = false + document.body.style.overflow = "hidden" + document.getElementById("controls").style.display = 'inline' + document.getElementById("settings").style.display = 'inline' + } else { + build.list = [] + // let text = '

choose up to 5 powers

' + let text = + `
+ + + start + + +
+
+ Choose up to five power ups. Once you start, only health and ammo will drop, so pick carefully. +
` + for (let i = 1, 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 = b.mods.length; i < len; i++) { + text += `
  ${b.mods[i].name}
${b.mods[i].description}
` + } + el.innerHTML = text + el.style.display = "grid" + build.isShowingBuilds = true + document.body.style.overflowY = "scroll"; + document.body.style.overflowX = "hidden"; + document.getElementById("controls").style.display = 'none' + document.getElementById("settings").style.display = 'none' + } +}); + +//set up canvas +var canvas = document.getElementById("canvas"); +//using "const" causes problems in safari when an ID shares the same name. +const ctx = canvas.getContext("2d"); +document.body.style.backgroundColor = "#fff"; + +//disable pop up menu on right click +document.oncontextmenu = function () { + return false; +} + +function setupCanvas() { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + canvas.width2 = canvas.width / 2; //precalculated because I use this often (in mouse look) + canvas.height2 = canvas.height / 2; + canvas.diagonal = Math.sqrt(canvas.width2 * canvas.width2 + canvas.height2 * canvas.height2); + ctx.font = "15px Arial"; + ctx.lineJoin = "round"; + ctx.lineCap = "round"; + // ctx.lineCap='square'; + game.setZoom(); +} +setupCanvas(); +window.onresize = () => { + setupCanvas(); +}; + +//mouse move input +document.body.addEventListener("mousemove", (e) => { + game.mouse.x = e.clientX; + game.mouse.y = e.clientY; +}); + +document.body.addEventListener("mouseup", (e) => { + // game.buildingUp(e); //uncomment when building levels + // game.mouseDown = false; + // console.log(e) + if (e.which === 3) { + game.mouseDownRight = false; + } else { + game.mouseDown = false; + } +}); + +document.body.addEventListener("mousedown", (e) => { + if (e.which === 3) { + game.mouseDownRight = true; + } else { + game.mouseDown = true; + } +}); + +//keyboard input +const keys = []; +document.body.addEventListener("keydown", (e) => { + keys[e.keyCode] = true; + game.keyPress(); +}); + +document.body.addEventListener("keyup", (e) => { + keys[e.keyCode] = false; +}); + +document.body.addEventListener("wheel", (e) => { + if (e.deltaY > 0) { + game.nextGun(); + } else { + game.previousGun(); + } +}, { + passive: true +}); + +document.getElementById("fps-select").addEventListener("input", () => { + let value = document.getElementById("fps-select").value + if (value === 'max') { + game.fpsCapDefault = 999999999; + } else if (value === '72') { + game.fpsCapDefault = 72 + } else if (value === '60') { + game.fpsCapDefault = 60 + } else if (value === '45') { + game.fpsCapDefault = 45 + } else if (value === '30') { + game.fpsCapDefault = 30 + } else if (value === '15') { + game.fpsCapDefault = 15 + } +}); + +document.getElementById("body-damage").addEventListener("input", () => { + game.isBodyDamage = document.getElementById("body-damage").checked +}); + +// function playSound(id) { +// //play sound +// if (false) { +// //sounds are turned off for now +// // if (document.getElementById(id)) { +// var sound = document.getElementById(id); //setup audio +// sound.currentTime = 0; //reset position of playback to zero //sound.load(); +// sound.play(); +// } +// } + +function shuffle(array) { + var currentIndex = array.length, + temporaryValue, + randomIndex; + // While there remain elements to shuffle... + while (0 !== currentIndex) { + // Pick a remaining element... + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + // And swap it with the current element. + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } + return array; +} + + + +//main loop ************************************************************ +//********************************************************************** +function cycle() { + if (!game.paused) requestAnimationFrame(cycle); + const now = Date.now(); + const elapsed = now - game.then; // calc elapsed time since last loop + if (elapsed > game.fpsInterval) { // if enough time has elapsed, draw the next frame + game.then = now - (elapsed % game.fpsInterval); // Get ready for next frame by setting then=now. Also, adjust for fpsInterval not being multiple of 16.67 + game.loop(); + } } \ No newline at end of file diff --git a/js/level.js b/js/level.js index 0c69a98..4905c9f 100644 --- a/js/level.js +++ b/js/level.js @@ -1,1616 +1,1598 @@ -//global game variables -let body = []; //non static bodies -let map = []; //all static bodies -let cons = []; //all constraints between a point and a body -let consBB = []; //all constraints between two bodies -//main object for spawning levels -const level = { - maxJump: 390, - defaultZoom: 1400, - boostScale: 0.000023, - levels: ["skyscrapers", "rooftops", "warehouse", "highrise", "office", "aerie"], - onLevel: 0, - levelsCleared: 0, - start() { - if (level.levelsCleared === 0) { - // game.difficulty = 6; //for testing to simulate possible mobs spawns - // b.giveGuns(1) - // mech.fieldUpgrades[2].effect(); - // b.giveMod(5) - // spawn.pickList = ["ghoster", "ghoster"] - - this.intro(); //starting level - // this.testingMap(); - // this.bosses(); - // this.aerie(); - // this.rooftops(); - // this.warehouse(); - // this.highrise(); - // this.office(); - } else { - spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns - level[level.levels[level.onLevel]](); //picks the current map from the the levels array - level.levelAnnounce(); - } - game.setZoom(); - level.addToWorld(); //add bodies to game engine - game.draw.setPaths(); - }, - isBuildRun: false, - difficultyIncrease(num = 1) { - // if (level.isBuildRun) num++ - for (let i = 0; i < num; i++) { - game.dmgScale += 0.2; //damage done by mobs increases each level - b.dmgScale *= 0.95; //damage done by player decreases each level - game.accelScale *= 1.05 //mob acceleration increases each level - game.lookFreqScale *= 0.95 //mob cycles between looks decreases each level - game.CDScale *= 0.95 //mob CD time decreases each level - } - }, - difficultyDecrease(num = 1) { //used in easy mode for game.reset() - for (let i = 0; i < num; i++) { - game.dmgScale -= 0.2; //damage done by mobs increases each level - if (game.dmgScale < 0.1) game.dmgScale = 0.1; - b.dmgScale /= 0.95; //damage done by player decreases each level - game.accelScale /= 1.05 //mob acceleration increases each level - game.lookFreqScale /= 0.95 //mob cycles between looks decreases each level - game.CDScale /= 0.95 //mob CD time decreases each level - } - }, - //****************************************************************************************************************** - //****************************************************************************************************************** - testingMap() { - //start with all guns - game.zoomScale = 1400 //1400 is normal - spawn.setSpawnList(); - mech.setPosToSpawn(-75, -60); //normal spawn - level.enter.x = mech.spawnPos.x - 50; - level.enter.y = mech.spawnPos.y + 20; - - level.exit.x = 3500; - level.exit.y = -870; - this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); - document.body.style.backgroundColor = "#dcdcde"; - - - - - // this.addZone(250, -1000, 500, 1500, "laser"); - //spawn.debris(0, -900, 4500, 10); //15 debris per level - // setTimeout(function() { - // document.body.style.backgroundColor = "#eee"; - // }, 1); - // this.addQueryRegion(550, -25, 100, 50, "bounce", { Vx: 0, Vy: -25 }); - // level.fillBG.push({ x: 550, y: -25, width: 100, height: 50, color: "#ff0" }); - - spawn.mapRect(-1200, 0, 2200, 300); //left ground - spawn.mapRect(3500, -860, 100, 50); //ground bump wall - spawn.mapVertex(1250, 0, "0 0 0 300 -500 600 -500 300"); - spawn.mapRect(1500, -300, 2000, 300); //upper ground - spawn.mapVertex(3750, 0, "0 600 0 300 -500 0 -500 300"); - spawn.mapRect(4000, 0, 1000, 300); //right lower ground - spawn.mapRect(2200, -600, 600, 50); //center platform - spawn.mapRect(1300, -850, 700, 50); //center platform - spawn.mapRect(3000, -850, 700, 50); //center platform - // spawn.mapRect(0, -2000, 3000, 50); //center platform - spawn.spawnBuilding(-200, -250, 275, 240, false, true, "left"); //far left; player spawns in side - // spawn.boost(350, 0, -1000); - // for (let i = 0; i < 10; i++) { - // powerUps.spawn(950, -425, "gun", false); - // } - // for (let i = 0; i < 5; i++) { - // powerUps.spawn(2500 + i * 20, -1300, "gun", false); - // powerUps.spawn(2500 + i * 20, -1100, "ammo", false); - // } - // spawn.nodeBoss(-500, -600, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); - // spawn.lineBoss(-500, -600, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); - // spawn.bodyRect(-135, -50, 50, 50); - // spawn.bodyRect(-140, -100, 50, 50); - // spawn.bodyRect(-145, -150, 60, 50); - // spawn.bodyRect(-140, -200, 50, 50); - // spawn.bodyRect(-95, -50, 40, 50); - // spawn.bodyRect(-90, -100, 60, 50); - // spawn.bodyRect(300, -150, 140, 50); - // spawn.bodyRect(300, -150, 30, 30); - // spawn.bodyRect(300, -150, 20, 20); - // spawn.bodyRect(300, -150, 40, 100); - // spawn.bodyRect(300, -150, 40, 90); - // spawn.bodyRect(300, -150, 30, 60); - // spawn.bodyRect(300, -150, 40, 70); - // spawn.bodyRect(300, -150, 40, 60); - // spawn.bodyRect(300, -150, 20, 20); - // spawn.bodyRect(500, -150, 140, 110); - // spawn.bodyRect(600, -150, 140, 100); - // spawn.bodyRect(400, -150, 140, 160); - // spawn.bodyRect(500, -150, 110, 110); - // powerUps.spawn(340, -400, "heal", false); - // powerUps.spawn(370, -400, "gun", false); - // powerUps.spawn(400, -400, "field", false, 2); - // powerUps.spawn(420, -400, "ammo", false); - powerUps.spawn(450, -400, "mod", false, 6); - // powerUps.spawn(450, -400, "mod", false); - // spawn.bodyRect(-45, -100, 40, 50); - spawn.spawner(800, -1150); - spawn.groupBoss(-600, -550); - // spawn.hopper(800, -150); - // spawn.beamer(800, -150); - // spawn.grower(800, -250); - // spawn.blinker(800, -250, 40); - // spawn.groupBoss(900, -1070); - // for (let i = 0; i < 20; i++) { - // spawn.randomBoss(-100, -1470); - // } - }, - bosses() { - level.defaultZoom = 1500 - game.zoomTransition(level.defaultZoom) - - // spawn.setSpawnList(); - // spawn.setSpawnList(); - // game.difficulty = 7; //for testing to simulate all possible mobs spawns - // for (let i = 0; i < game.difficulty; i++) { - // game.dmgScale += 0.4; //damage done by mobs increases each level - // b.dmgScale *= 0.9; //damage done by player decreases each level - // } - - document.body.style.backgroundColor = "#ddd"; - - // level.fillBG.push({ - // x: -150, - // y: -1150, - // width: 7000, - // height: 1200, - // color: "#eee" - // }); - - level.fill.push({ - x: 6400, - y: -550, - width: 300, - height: 350, - color: "rgba(0,255,255,0.1)" - }); - - mech.setPosToSpawn(0, -750); //normal spawn - level.enter.x = mech.spawnPos.x - 50; - level.enter.y = mech.spawnPos.y + 20; - level.exit.x = 6500; - level.exit.y = -230; - this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); - - spawn.mapRect(-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); - - function blockDoor(x, y, blockSize = 58) { - spawn.mapRect(x, y - 290, 40, 60); // door lip - spawn.mapRect(x, y, 40, 50); // door lip - for (let i = 0; i < 4; ++i) { - spawn.bodyRect(x + 5, y - 260 + i * blockSize, 30, blockSize); - } - } - blockDoor(710, -710); - - spawn[spawn.pickList[0]](1500, -200, 100 + game.difficulty * 8); - spawn.mapRect(2500, -1200, 200, 750); //right wall - blockDoor(2585, -210) - spawn.mapRect(2500, -200, 200, 300); //right wall - - spawn.nodeBoss(3500, -200, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); - spawn.mapRect(4500, -1200, 200, 750); //right wall - blockDoor(4585, -210) - spawn.mapRect(4500, -200, 200, 300); //right wall - - spawn.lineBoss(5000, -200, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); - spawn.mapRect(6400, -1200, 400, 750); //right wall - spawn.mapRect(6400, -200, 400, 300); //right wall - spawn.mapRect(6700, -1800, 800, 2600); //right wall - spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump - - for (let i = 0; i < 5; ++i) { - if (game.difficulty * Math.random() > 3 * i) { - spawn.randomBoss(2000 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); - } - if (game.difficulty * Math.random() > 2.6 * i) { - spawn.randomBoss(3500 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); - } - if (game.difficulty * Math.random() > 2.4 * i) { - spawn.randomBoss(5000 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); - } - } - }, - intro() { - // b.giveGuns(0, 1000) - game.zoomScale = 1000 //1400 is normal - level.defaultZoom = 1600 - game.zoomTransition(level.defaultZoom, 1) - - mech.setPosToSpawn(460, -100); //normal spawn - level.enter.x = -1000000; //offscreen - level.enter.y = -400; - level.exit.x = 2800; - level.exit.y = -335; - this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); - document.body.style.backgroundColor = "#fff"; - level.fillBG.push({ - x: 2600, - y: -600, - width: 400, - height: 500, - color: "#edf9f9" - }); - - level.fillBG.push({ - x: 1600, - y: -500, - width: 100, - height: 100, - color: "#eee" - }); - - level.fillBG.push({ - x: -55, - y: -283, - width: 12, - height: 100, - color: "#eee" - }); - - //faster way to draw a wire - function wallWire(x, y, width, height, front = false) { - if (front) { - level.fill.push({ - x: x, - y: y, - width: width, - height: height, - color: "#aaa" - }); - } else { - level.fillBG.push({ - x: x, - y: y, - width: width, - height: height, - color: "#eee" - }); - } - } - - for (let i = 0; i < 3; i++) { - wallWire(100 - 10 * i, -1050 - 10 * i, 5, 800); - wallWire(100 - 10 * i, -255 - 10 * i, -300, 5); - } - for (let i = 0; i < 5; i++) { - wallWire(1000 + 10 * i, -1050 - 10 * i, 5, 600); - wallWire(1000 + 10 * i, -450 - 10 * i, 150, 5); - wallWire(1150 + 10 * i, -450 - 10 * i, 5, 500); - } - for (let i = 0; i < 3; i++) { - wallWire(2650 - 10 * i, -700 - 10 * i, -300, 5); - wallWire(2350 - 10 * i, -700 - 10 * i, 5, 800); - } - for (let i = 0; i < 5; i++) { - wallWire(1625 + 10 * i, -1050, 5, 1200); - } - for (let i = 0; i < 4; i++) { - wallWire(1650, -470 + i * 10, 670 - i * 10, 5); - wallWire(1650 + 670 - i * 10, -470 + i * 10, 5, 600); - } - for (let i = 0; i < 3; i++) { - wallWire(-200 - i * 10, -245 + i * 10, 1340, 5); - wallWire(1140 - i * 10, -245 + i * 10, 5, 300); - wallWire(-200 - i * 10, -215 + i * 10, 660, 5); - wallWire(460 - i * 10, -215 + i * 10, 5, 300); - } - spawn.mapRect(-250, 0, 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(level.exit.x, level.exit.y + 20, 100, 100); //exit bump - spawn.mapRect(-95, -1100, 80, 110); //wire source - spawn.mapRect(410, -10, 90, 20); //small platform for player - - // spawn.bodyRect(-35, -50, 50, 50); - // spawn.bodyRect(-40, -100, 50, 50); - // spawn.bodyRect(-45, -150, 60, 50); - // spawn.bodyRect(-40, -200, 50, 50); - // spawn.bodyRect(5, -50, 40, 50); - // spawn.bodyRect(10, -100, 60, 50); - // spawn.bodyRect(-10, -150, 40, 50); - // spawn.bodyRect(55, -100, 40, 50); - // spawn.bodyRect(-150, -300, 100, 100); - // spawn.bodyRect(-150, -200, 100, 100); - // spawn.bodyRect(-150, -100, 100, 100); - - // spawn.bodyRect(1790, -50, 40, 50); - // spawn.bodyRect(1875, -100, 200, 90); - spawn.bodyRect(2425, -120, 70, 50); - spawn.bodyRect(2400, -100, 100, 60); - spawn.bodyRect(2500, -150, 100, 150); //exit step - - mech.health = 0.25; - mech.displayHealth(); - // powerUps.spawn(-100, 0, "heal", false); //starting gun - powerUps.spawn(1900, -150, "heal", false); //starting gun - powerUps.spawn(2050, -150, "heal", false); //starting gun - // powerUps.spawn(2050, -150, "field", false); //starting gun - powerUps.spawn(2300, -150, "gun", false); //starting gun - if (game.isEasyMode) { - powerUps.spawn(2050, -150, "mod", false); //starting gun - powerUps.spawn(2050, -150, "mod", false); //starting gun - powerUps.spawn(-100, 0, "heal", false); //starting gun - } - spawn.wireFoot(); - spawn.wireFootLeft(); - spawn.wireKnee(); - spawn.wireKneeLeft(); - spawn.wireHead(); - // spawn.mapRect(1400, -700, 50, 300); //ground - // spawn.healer(1600, -500) - // spawn.healer(1600, -500) - // spawn.healer(1900, -500) - // spawn.healer(1000, -500) - // spawn.healer(1000, -400) - }, - rooftops() { - level.defaultZoom = 1700 - game.zoomTransition(level.defaultZoom) - document.body.style.backgroundColor = "#dcdcde"; - - if (Math.random() < 0.75) { - //normal direction start in top left - mech.setPosToSpawn(-450, -2050); - level.exit.x = 3600; - level.exit.y = -300; - spawn.mapRect(3600, -285, 100, 50); //ground bump wall - //mobs that spawn in exit room - spawn.randomSmallMob(4100, -100); - spawn.randomSmallMob(4600, -100); - spawn.randomMob(3765, -450, 0.3); - level.fill.push({ - x: -650, - y: -2300, - width: 450, - height: 300, - color: "rgba(0,0,0,0.15)" - }); - } else { - //reverse direction, start in bottom right - mech.setPosToSpawn(3650, -310); - level.exit.x = -550; - level.exit.y = -2030; - spawn.mapRect(-550, -2015, 100, 50); //ground bump wall - spawn.boost(4950, 0, 1600); - level.fillBG.push({ - x: -650, - y: -2300, - width: 450, - height: 300, - color: "#d4f4f4" - }); - } - level.enter.x = mech.spawnPos.x - 50; - level.enter.y = mech.spawnPos.y + 20; - this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); - - spawn.debris(1650, -1800, 3800, 20); //20 debris per level - powerUps.spawnStartingPowerUps(2450, -1675); - - //foreground - - level.fill.push({ - x: 3450, - y: -1250, - width: 1100, - height: 1250, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 4550, - y: -725, - width: 900, - height: 725, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 3400, - y: 100, - width: 2150, - height: 900, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: -700, - y: -1950, - width: 2100, - height: 2950, - color: "rgba(0,0,0,0.1)" - }); - - level.fill.push({ - x: 1950, - y: -1950, - width: 600, - height: 350, - color: "rgba(0,0,0,0.1)" - }); - - level.fill.push({ - x: 1950, - y: -1550, - width: 1025, - height: 550, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 1600, - y: -900, - width: 1650, - height: 1900, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 3450, - y: -1550, - width: 350, - height: 300, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 700, - y: -2225, - width: 700, - height: 225, - color: "rgba(0,0,0,0.1)" - }); - - //spawn.mapRect(-700, 0, 6250, 100); //ground - spawn.mapRect(3400, 0, 2150, 100); //ground - spawn.mapRect(-700, -2000, 2100, 50); //Top left ledge - spawn.bodyRect(1300, -2125, 50, 125, 0.8); - spawn.bodyRect(1307, -2225, 50, 100, 0.8); - spawn.mapRect(-700, -2350, 50, 400); //far left starting left wall - spawn.mapRect(-700, -2010, 500, 50); //far left starting ground - spawn.mapRect(-700, -2350, 500, 50); //far left starting ceiling - spawn.mapRect(-250, -2350, 50, 200); //far left starting right part of wall - spawn.bodyRect(-240, -2150, 30, 36); //door to starting room - spawn.bodyRect(-240, -2115, 30, 36); //door to starting room - spawn.bodyRect(-240, -2080, 30, 35); //door to starting room - spawn.bodyRect(-240, -2045, 30, 35); //door to starting room - spawn.mapRect(1950, -2000, 600, 50); - spawn.bodyRect(200, -2150, 200, 220, 0.8); - spawn.mapRect(700, -2275, 700, 50); - spawn.bodyRect(1050, -2350, 30, 30, 0.8); - spawn.boost(1800, -1000, 1200); - spawn.bodyRect(1625, -1100, 100, 75); - spawn.bodyRect(1350, -1025, 400, 25); // ground plank - spawn.mapRect(-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(-600, -1250, 400, 250, 0.8); - spawn.mapRect(1575, -1000, 1700, 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(3450, -1600, 350, 50); - spawn.mapRect(1950, -1600, 1025, 50); - spawn.bodyRect(3100, -1015, 375, 15); - spawn.bodyRect(3500, -850, 75, 125, 0.8); - spawn.mapRect(3450, -1000, 50, 580); //left building wall - spawn.bodyRect(3460, -420, 30, 144); - spawn.mapRect(5450, -775, 100, 875); //right building wall - spawn.bodyRect(4850, -750, 300, 25, 0.8); - spawn.bodyRect(3925, -1400, 100, 150, 0.8); - spawn.mapRect(3450, -1250, 1100, 50); - spawn.mapRect(3450, -1225, 50, 75); - spawn.mapRect(4500, -1225, 50, 390); - spawn.mapRect(3450, -725, 1500, 50); - spawn.mapRect(5100, -725, 400, 50); - spawn.mapRect(4500, -735, 50, 635); - spawn.bodyRect(4510, -100, 30, 100, 0.8); - 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); - //spawn.randomBoss(4850, -1250,0.7); - if (game.difficulty > 4) spawn.bomber(2500, -2400, 100); - }, - aerie() { - // game.setZoom(3000); - // game.difficulty = 4; //for testing to simulate possible mobs spawns - level.defaultZoom = 2100 - game.zoomTransition(level.defaultZoom) - - const backwards = (Math.random() < 0.75) ? false : true; - if (backwards) { - mech.setPosToSpawn(4000, -3300); //normal spawn - level.exit.x = -100; - level.exit.y = -1025; - } else { - mech.setPosToSpawn(-50, -1050); //normal spawn - level.exit.x = 3950; - level.exit.y = -3275; - } - // mech.setPosToSpawn(2250, -900); - // game.zoomTransition(1500) //1400 is normal - - level.enter.x = mech.spawnPos.x - 50; - level.enter.y = mech.spawnPos.y + 20; - this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); - powerUps.spawnStartingPowerUps(1075, -550); - spawn.debris(-250, 50, 1650, 2); //20 debris per level - spawn.debris(2475, 0, 750, 2); //20 debris per level - spawn.debris(3450, 0, 2000, 20); //20 debris per level - spawn.debris(3500, -2350, 1500, 2); //20 debris per level - 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: 900, - color: "rgba(0,0,0,0.1)" - }); - - //background - level.fillBG.push({ - x: 4200, - y: -2250, - 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(-100, -1010, 100, 30); - spawn.mapRect(-300, -1000, 600, 50); - 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, 50); - spawn.mapRect(900, -500, 550, 50); - 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 - //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.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.mapRect(3950, -3260, 100, 30); - - 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(350, -500, 1) - spawn.randomBoss(4000, -350, 0.6); - spawn.randomBoss(2750, -550, 0.1); - if (game.difficulty > 2) spawn.suckerBoss(4500, -400); - - //add mini boss, giant hopper? or a black hole that spawns hoppers? - }, - skyscrapers() { - level.defaultZoom = 2000 - game.zoomTransition(level.defaultZoom) - - mech.setPosToSpawn(-50, -50); //normal spawn - //mech.setPosToSpawn(1550, -1200); //spawn left high - //mech.setPosToSpawn(1800, -2000); //spawn near exit - level.enter.x = mech.spawnPos.x - 50; - level.enter.y = mech.spawnPos.y + 20; - level.exit.x = 1500; - level.exit.y = -1875; - this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); - - powerUps.spawnStartingPowerUps(1475, -1175); - spawn.debris(0, -2200, 4500, 20); //20 debris per level - document.body.style.backgroundColor = "#dcdcde"; - - //foreground - level.fill.push({ - x: 2500, - y: -1100, - width: 450, - height: 250, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 2400, - y: -550, - width: 600, - height: 150, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 2550, - y: -1650, - width: 250, - height: 200, - color: "rgba(0,0,0,0.1)" - }); - //level.fill.push({ x: 1350, y: -2100, width: 400, height: 250, color: "rgba(0,255,255,0.1)" }); - level.fill.push({ - x: 700, - y: -110, - width: 400, - height: 110, - color: "rgba(0,0,0,0.2)" - }); - level.fill.push({ - x: 3600, - y: -110, - width: 400, - height: 110, - color: "rgba(0,0,0,0.2)" - }); - level.fill.push({ - x: -250, - y: -300, - width: 450, - height: 300, - color: "rgba(0,0,0,0.15)" - }); - - //background - level.fillBG.push({ - x: 1300, - y: -1800, - width: 750, - height: 1800, - color: "#d4d4d7" - }); - level.fillBG.push({ - x: 3350, - y: -1325, - width: 50, - height: 1325, - color: "#d4d4d7" - }); - level.fillBG.push({ - x: 1350, - y: -2100, - width: 400, - height: 250, - color: "#d4f4f4" - }); - - spawn.mapRect(-300, 0, 5000, 300); //***********ground - spawn.mapRect(-300, -350, 50, 400); //far left starting left wall - spawn.mapRect(-300, -10, 500, 50); //far left starting ground - spawn.mapRect(-300, -350, 500, 50); //far left starting ceiling - spawn.mapRect(150, -350, 50, 200); //far left starting right part of wall - spawn.bodyRect(170, -130, 14, 140, 1, spawn.propsFriction); //door to starting room - spawn.boost(475, 0, 1300); - spawn.mapRect(700, -1100, 400, 990); //far left building - spawn.mapRect(1600, -400, 1500, 500); //long center building - spawn.mapRect(1345, -1100, 250, 25); //left platform - spawn.mapRect(1755, -1100, 250, 25); //right platform - spawn.mapRect(1300, -1850, 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, 250, 200, 0.8); - spawn.bodyRect(2557, -450, 35, 55, 0.7); - spawn.bodyRect(2957, -450, 30, 15, 0.7); - spawn.bodyRect(2900, -450, 60, 45, 0.7); - spawn.bodyRect(915, -1200, 60, 100, 0.95); - spawn.bodyRect(925, -1300, 50, 100, 0.95); - if (Math.random() < 0.9) { - spawn.bodyRect(2300, -1720, 400, 20); - spawn.bodyRect(2590, -1780, 80, 80); - } - spawn.bodyRect(2925, -1100, 25, 250, 0.8); - spawn.bodyRect(3325, -1550, 50, 200, 0.3); - if (Math.random() < 0.8) { - spawn.bodyRect(1400, -75, 200, 75); //block to get up ledge from ground - spawn.bodyRect(1525, -125, 50, 50); //block to get up ledge from ground - } - spawn.bodyRect(1025, -1110, 400, 25, 0.9); //block on far left building - spawn.bodyRect(1425, -1110, 115, 25, 0.9); //block on far left building - spawn.bodyRect(1540, -1110, 300, 25, 0.9); //block on far left building - - if (game.difficulty > 2) spawn.shooterBoss(2200, -1300); - spawn.randomSmallMob(1300, -70); - spawn.randomSmallMob(3200, -100); - spawn.randomSmallMob(4450, -100); - spawn.randomSmallMob(2700, -475); - spawn.randomMob(2650, -975, 0.8); - spawn.randomMob(2650, -1550, 0.8); - spawn.randomMob(4150, -200, 0.15); - spawn.randomMob(1700, -1300, 0.2); - spawn.randomMob(1850, -1950, 0.25); - spawn.randomMob(2610, -1880, 0.25); - spawn.randomMob(3350, -950, 0.25); - spawn.randomMob(1690, -2250, 0.25); - spawn.randomMob(2200, -600, 0.2); - spawn.randomMob(850, -1300, 0.25); - spawn.randomMob(-100, -900, -0.2); - spawn.randomBoss(3700, -1500, 0.4); - spawn.randomBoss(1700, -900, 0.4); - }, - highrise() { - level.defaultZoom = 1500 - game.zoomTransition(level.defaultZoom) - - document.body.style.backgroundColor = "#dcdcde" //"#fafcff"; - mech.setPosToSpawn(0, -700); //normal spawn - //mech.setPosToSpawn(-2000, -1700); // left ledge spawn - level.enter.x = mech.spawnPos.x - 50; - level.enter.y = mech.spawnPos.y + 20; - level.exit.x = -4275; - level.exit.y = -2805; - this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); - powerUps.spawnStartingPowerUps(-2550, -700); - - // spawn.laserZone(-550, -350, 10, 400, 0.3) - // spawn.deathQuery(-550, -350, 50, 400) - - // spawn.debris(-3950, -2575, 1050, 4); //20 debris per level - spawn.debris(-2325, -1825, 2400); //20 debris per level - spawn.debris(-2625, -600, 600, 6); //20 debris per level - spawn.debris(-2000, -60, 1200, 6); //20 debris per level - // if (!game.difficulty) powerUps.spawn(2450, -1675, "gun", false); - //background - level.fillBG.push({ - x: -4425, - y: -3050, - width: 425, - height: 275, - color: "#cff" - }); - //foreground - level.fill.push({ - x: -1650, - y: -1575, - width: 550, - height: 425, - color: "rgba(0,0,0,0.12)" - }); - level.fill.push({ - x: -2600, - y: -2400, - width: 450, - height: 1800, - color: "rgba(0,0,0,0.12)" - }); - level.fill.push({ - x: -3425, - y: -2150, - width: 525, - height: 1550, - color: "rgba(0,0,0,0.12)" - }); - level.fill.push({ - x: -1850, - y: -1150, - width: 2025, - height: 1150, - color: "rgba(0,0,0,0.12)" - }); - - //hidden zone - level.fill.push({ - x: -4450, - y: -955, - width: 1025, - height: 360, - color: "rgba(64,64,64,0.97)" - }); - // level.fill.push({ - // x: -4050, - // y: -955, - // width: 625, - // height: 360, - // color: "#444" - // }); - powerUps.spawn(-4300, -700, "heal"); - powerUps.spawn(-4200, -700, "ammo"); - powerUps.spawn(-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); - } - // spawn.bodyRect(-1100, -125, 150, 125); - - // spawn.bodyRect(-1200, -75, 75, 75); - - //building 2 - spawn.mapRect(-4450, -600, 2300, 750); - spawn.mapRect(-2225, -500, 175, 550); - spawn.boost(-2800, -600, 1150); - spawn.mapRect(-3450, -1325, 550, 50); - spawn.mapRect(-3425, -2200, 525, 50); - spawn.mapRect(-2600, -1750, 450, 50); - spawn.mapRect(-2600, -2450, 450, 50); - spawn.bodyRect(-2275, -2700, 50, 60); - spawn.bodyRect(-2600, -1975, 250, 225); - spawn.bodyRect(-3415, -1425, 100, 100); - spawn.bodyRect(-3400, -1525, 100, 100); - spawn.bodyRect(-3305, -1425, 100, 100); - - //building 3 - spawn.mapRect(-4450, -1750, 1025, 1000); - spawn.mapRect(-3750, -2000, 175, 275); - spawn.mapRect(-4000, -2350, 275, 675); - // spawn.mapRect(-4450, -2650, 475, 1000); - spawn.mapRect(-4450, -2775, 475, 1125); - spawn.bodyRect(-3715, -2050, 50, 50); - spawn.bodyRect(-3570, -1800, 50, 50); - spawn.bodyRect(-2970, -2250, 50, 50); - - if (game.difficulty < 4) spawn.bodyRect(-3760, -2400, 50, 50); - - spawn.bodyRect(-3080, -2250, 40, 40); - spawn.bodyRect(-3420, -650, 50, 50); - - //exit - spawn.mapRect(-4450, -3075, 25, 300); - spawn.mapRect(-4450, -3075, 450, 25); - spawn.mapRect(-4025, -3075, 25, 100); - spawn.mapRect(-4275, -2785, 100, 25); - - //mobs - spawn.randomMob(-2500, -2700, 1); - spawn.randomMob(-3200, -750, 1); - spawn.randomMob(-1875, -775, 0.2); - spawn.randomMob(-950, -1675, 0.2); - spawn.randomMob(-1525, -1750, 0.2); - spawn.randomMob(-1375, -1400, 0.2); - spawn.randomMob(-1625, -1275, 0.2); - spawn.randomMob(-1900, -1250, 0.2); - spawn.randomMob(-2250, -1850, 0.2); - spawn.randomMob(-2475, -2200, 0.2); - spawn.randomMob(-3000, -1475, 0.2); - spawn.randomMob(-3850, -2500, 0.2); - spawn.randomMob(-3650, -2125, 0.2); - spawn.randomMob(-4010, -3200, 0.2); - spawn.randomMob(-3500, -1825, 0.2); - spawn.randomMob(-975, -100, 0); - spawn.randomMob(-1050, -725, 0.2); - spawn.randomMob(-1525, -100, 0); - spawn.randomMob(-525, -1700, -0.1); - spawn.randomMob(-125, -1500, -0.1); - spawn.randomMob(-325, -1900, -0.1); - spawn.randomMob(-550, -100, -0.1); - spawn.randomBoss(-3250, -2700, 0.2); - spawn.randomBoss(-2450, -1100, 0); - }, - warehouse() { - level.defaultZoom = 1300 - game.zoomTransition(level.defaultZoom) - - document.body.style.backgroundColor = "#f2f5f3"; - mech.setPosToSpawn(25, -60); //normal spawn - //mech.setPosToSpawn(-2000, -1700); // left ledge spawn - level.enter.x = mech.spawnPos.x - 50; - level.enter.y = mech.spawnPos.y + 20; - level.exit.x = 425; - level.exit.y = -35; - this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); - //level.addQueryRegion(-600, -250, 180, 420, "death", [[player]],{}); - - spawn.debris(-2250, 1330, 3000, 7); //20 debris per level - spawn.debris(-3000, -800, 3280, 7); //20 debris per level - spawn.debris(-1400, 410, 2300, 6); //20 debris per level - powerUps.spawnStartingPowerUps(25, 500); - - //foreground - // level.fill.push({ x: -3025, y: 50, width: 4125, height: 1350, color: "rgba(0,0,0,0.05)"}); - // level.fill.push({ x: -1800, y: -500, width: 1975, height: 550, color: "rgba(0,0,0,0.05)"}); - // level.fill.push({ x: -2600, y: -150, width: 700, height: 200, color: "rgba(0,0,0,0.05)"}); - //background - const BGColor = "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, -600, 100, 1300); - //house - spawn.mapRect(-175, -550, 50, 400); - spawn.mapRect(-175, -15, 350, 50); - spawn.mapRect(-25, -25, 100, 50); - // spawn.mapRect(-175, -275, 350, 25); - // spawn.mapRect(-175, -250, 25, 75); - // spawn.bodyRect(-170, -175, 14, 160, 1, spawn.propsFriction); //door to starting room - //exit house - spawn.mapRect(300, -15, 350, 50); - spawn.mapRect(-150, -300, 800, 50); - spawn.mapRect(600, -275, 50, 75); - spawn.mapRect(425, -25, 100, 25); - // spawn.mapRect(-1900, 600, 2700, 100); - spawn.mapRect(1100, 0, 150, 1500); - spawn.mapRect(-2850, 1400, 4100, 100); - spawn.mapRect(-2375, 875, 1775, 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(-1350, -200, 200, 200, 1, spawn.propsSlide); //weight - spawn.bodyRect(-1800, 0, 300, 100, 1, spawn.propsHoist); //hoist - cons[cons.length] = Constraint.create({ - pointA: { - x: -1650, - y: -500 - }, - bodyB: body[body.length - 1], - stiffness: 0.000076, - length: 1 - }); - - spawn.bodyRect(400, 400, 200, 200, 1, spawn.propsSlide); //weight - spawn.bodyRect(800, 600, 300, 100, 1, spawn.propsHoist); //hoist - cons[cons.length] = Constraint.create({ - pointA: { - x: 950, - y: 100 - }, - bodyB: body[body.length - 1], - stiffness: 0.000076, - length: 1 - }); - - spawn.bodyRect(-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.0002, - length: 566 - }); - - //blocks - //spawn.bodyRect(-155, -150, 10, 140, 1, spawn.propsFriction); - spawn.bodyRect(-165, -150, 30, 35, 1); - spawn.bodyRect(-165, -115, 30, 35, 1); - spawn.bodyRect(-165, -80, 30, 35, 1); - spawn.bodyRect(-165, -45, 30, 35, 1); - - spawn.bodyRect(-750, 400, 150, 150, 0.5); - spawn.bodyRect(-400, 1175, 100, 250, 1); //block to get to top path on bottom level - // spawn.bodyRect(-1450, 737, 75, 103, 0.5); //blocking path - - spawn.bodyRect(-2525, -50, 145, 100, 0.5); - spawn.bodyRect(-2325, -300, 150, 100, 0.5); - spawn.bodyRect(-1275, -750, 200, 150, 0.5); //roof block - spawn.bodyRect(-525, -700, 125, 100, 0.5); //roof block - - //mobs - spawn.randomSmallMob(-1125, 550); - spawn.randomSmallMob(-2325, 800); - spawn.randomSmallMob(-2950, -50); - spawn.randomSmallMob(825, 300); - spawn.randomSmallMob(-900, 825); - spawn.randomMob(-2025, 175, 0.6); - spawn.randomMob(-2325, 450, 0.6); - spawn.randomMob(-2925, 675, 0.5); - spawn.randomMob(-2700, 300, 0.2); - spawn.randomMob(-2500, 300, 0.2); - spawn.randomMob(-2075, -425, 0.2); - spawn.randomMob(-1550, -725, 0.2); - spawn.randomMob(375, 1100, 0.1); - spawn.randomMob(-1425, -100, 0.1); - spawn.randomMob(-800, -750, 0); - spawn.randomMob(400, -350, 0); - spawn.randomMob(650, 1300, 0); - spawn.randomMob(-750, -150, 0); - spawn.randomMob(475, 300, 0); - spawn.randomMob(-75, -700, 0); - spawn.randomMob(900, -200, -0.1); - spawn.randomBoss(-125, 275, -0.2); - spawn.randomBoss(-825, 1000, 0.2); - spawn.randomBoss(-1300, -1100, -0.3); - //spawn.randomBoss(600, -1575, 0); - //spawn.randomMob(1120, -1200, 0.3); - //spawn.randomSmallMob(2200, -1775); - - if (game.difficulty > 2) spawn.snaker(-1300 + Math.random() * 2000, -2200); //boss snake with head - }, - office() { - level.defaultZoom = 1400 - game.zoomTransition(level.defaultZoom) - - if (Math.random() < 0.75) { - //normal direction start in top left - mech.setPosToSpawn(1375, -1550); //normal spawn - level.exit.x = 3250; - level.exit.y = -530; - // spawn.randomSmallMob(3550, -550); - } else { - //reverse direction, start in bottom right - mech.setPosToSpawn(3250, -530); //normal spawn - level.exit.x = 1375; - level.exit.y = -1530; - spawn.bodyRect(3655, -650, 40, 150); //door - } - spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 50); //ground bump wall - level.enter.x = mech.spawnPos.x - 50; - level.enter.y = mech.spawnPos.y + 20; - this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); - - document.body.style.backgroundColor = "#e0e5e0"; - //foreground - level.fill.push({ - x: -550, - y: -1700, - width: 1300, - height: 1700, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 750, - y: -1450, - width: 650, - height: 1450, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 750, - y: -1950, - width: 800, - height: 450, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 3000, - y: -1000, - width: 650, - height: 1000, - color: "rgba(0,0,0,0.1)" - }); - level.fill.push({ - x: 3650, - y: -1300, - width: 1300, - height: 1300, - color: "rgba(0,0,0,0.1)" - }); - - //mech.setPosToSpawn(600, -1200); //normal spawn - //mech.setPosToSpawn(525, -150); //ground first building - //mech.setPosToSpawn(3150, -700); //near exit spawn - spawn.debris(-300, -200, 1000, 5); //ground debris //20 debris per level - spawn.debris(3500, -200, 800, 5); //ground debris //20 debris per level - spawn.debris(-300, -650, 1200, 5); //1st floor debris //20 debris per level - spawn.debris(3500, -650, 800, 5); //1st floor debris //20 debris per level - powerUps.spawnStartingPowerUps(-525, -700); - - spawn.mapRect(-600, 25, 5600, 300); //ground - spawn.mapRect(-600, 0, 2000, 50); //ground - spawn.mapRect(-600, -1700, 50, 2000 - 100); //left wall - spawn.bodyRect(-295, -1540, 40, 40); //center block under wall - spawn.bodyRect(-298, -1580, 40, 40); //center block under wall - spawn.bodyRect(1500, -1540, 30, 30); //left of entrance - - spawn.mapRect(1550, -2000, 50, 550); //right wall - spawn.mapRect(1350, -2000 + 505, 50, 1295); //right wall - spawn.mapRect(-600, -2000 + 250, 2000 - 700, 50); //roof left - spawn.mapRect(-600 + 1300, -2000, 50, 300); //right roof wall - spawn.mapRect(-600 + 1300, -2000, 900, 50); //center wall - map[map.length] = Bodies.polygon(425, -1700, 0, 15); //circle above door - spawn.bodyRect(420, -1675, 15, 170, 1, spawn.propsDoor); // door - body[body.length - 1].isNotHoldable = true; - //makes door swing - consBB[consBB.length] = Constraint.create({ - bodyA: body[body.length - 1], - pointA: { - x: 0, - y: -90 - }, - bodyB: map[map.length - 1], - stiffness: 1 - }); - spawn.mapRect(-600 + 300, -2000 * 0.75, 1900, 50); //3rd floor - spawn.mapRect(-600 + 2000 * 0.7, -2000 * 0.74, 50, 375); //center wall - spawn.bodyRect(-600 + 2000 * 0.7, -2000 * 0.5 - 106, 50, 106); //center block under wall - spawn.mapRect(-600, -1000, 1100, 50); //2nd floor - spawn.mapRect(600, -1000, 500, 50); //2nd floor - spawn.spawnStairs(-600, -1000, 4, 250, 350); //stairs 2nd - spawn.mapRect(350, -600, 350, 150); //center table - spawn.mapRect(-600 + 300, -2000 * 0.25, 2000 - 300, 50); //1st floor - spawn.spawnStairs(-600 + 2000 - 50, -500, 4, 250, 350, true); //stairs 1st - spawn.spawnStairs(-600, 0, 4, 250, 350); //stairs ground - spawn.bodyRect(700, -200, 100, 100); //center block under wall - spawn.bodyRect(700, -300, 100, 100); //center block under wall - spawn.bodyRect(700, -400, 100, 100); //center block under wall - spawn.mapRect(1390, 13, 30, 20); //step left - spawn.mapRect(2980, 13, 30, 20); //step right - spawn.mapRect(3000, 0, 2000, 50); //ground - spawn.bodyRect(4250, -700, 50, 100); - spawn.bodyRect(3000, -200, 50, 200); //door - spawn.mapRect(3000, -1000, 50, 800); //left wall - spawn.mapRect(3000 + 2000 - 50, -1300, 50, 1100); //right wall - spawn.mapRect(4150, -600, 350, 150); //table - spawn.mapRect(3650, -1300, 50, 650); //exit wall - spawn.mapRect(3650, -1300, 1350, 50); //exit wall - - spawn.mapRect(3000, -2000 * 0.5, 700, 50); //exit roof - spawn.mapRect(3000, -2000 * 0.25, 2000 - 300, 50); //1st floor - spawn.spawnStairs(3000 + 2000 - 50, 0, 4, 250, 350, true); //stairs ground - - // tether ball - if (game.difficulty > 2) { - level.fillBG.push({ - x: 2495, - y: -500, - width: 10, - height: 525, - color: "#ccc" - }); - spawn.tether(2850, -80) - cons[cons.length] = Constraint.create({ - pointA: { - x: 2500, - y: -500 - }, - bodyB: mob[mob.length - 1], - stiffness: 0.00012 - }); - //chance to spawn a ring of exploding mobs around this boss - if (game.difficulty > 4) spawn.nodeBoss(2850, -80, "spawns", 8, 20, 105); - } - - spawn.randomSmallMob(4575, -560, 1); - spawn.randomSmallMob(1315, -880, 1); - spawn.randomSmallMob(800, -600); - spawn.randomSmallMob(-100, -1600); - spawn.randomMob(4100, -225, 0.8); - spawn.randomMob(-250, -700, 0.8); - spawn.randomMob(4500, -225, 0.15); - spawn.randomMob(3250, -225, 0.15); - spawn.randomMob(-100, -225, 0.1); - spawn.randomMob(1150, -225, 0.15); - spawn.randomMob(2000, -225, 0.15); - spawn.randomMob(450, -225, 0.15); - spawn.randomMob(100, -1200, 1); - spawn.randomMob(950, -1150, -0.1); - spawn.randomBoss(1800, -800, -0.2); - spawn.randomBoss(4150, -1000, 0.6); - }, - //***************************************************************************************************************** - //***************************************************************************************************************** - //***************************************************************************************************************** - //***************************************************************************************************************** - //***************************************************************************************************************** - //***************************************************************************************************************** - //***************************************************************************************************************** - enter: { - x: 0, - y: 0, - draw() { - ctx.beginPath(); - ctx.moveTo(this.x, this.y + 30); - ctx.lineTo(this.x, this.y - 80); - ctx.bezierCurveTo(this.x, this.y - 170, this.x + 100, this.y - 170, this.x + 100, this.y - 80); - ctx.lineTo(this.x + 100, this.y + 30); - ctx.lineTo(this.x, this.y + 30); - ctx.fillStyle = "#ccc"; - ctx.fill(); - } - }, - exit: { - x: 0, - y: 0, - draw() { - ctx.beginPath(); - ctx.moveTo(this.x, this.y + 30); - ctx.lineTo(this.x, this.y - 80); - ctx.bezierCurveTo(this.x, this.y - 170, this.x + 100, this.y - 170, this.x + 100, this.y - 80); - ctx.lineTo(this.x + 100, this.y + 30); - ctx.lineTo(this.x, this.y + 30); - ctx.fillStyle = "#0ff"; - ctx.fill(); - } - }, - fillBG: [], - drawFillBGs() { - for (let i = 0, len = level.fillBG.length; i < len; ++i) { - const f = level.fillBG[i]; - ctx.fillStyle = f.color; - ctx.fillRect(f.x, f.y, f.width, f.height); - } - }, - fill: [], - drawFills() { - for (let i = 0, len = level.fill.length; i < len; ++i) { - const f = level.fill[i]; - ctx.fillStyle = f.color; - ctx.fillRect(f.x, f.y, f.width, f.height); - } - }, - zones: [], //zone do actions when player is in a region // to effect everything use a query - checkZones() { - for (let i = 0, len = this.zones.length; i < len; ++i) { - if ( - player.position.x > this.zones[i].x1 && - player.position.x < this.zones[i].x2 && - player.position.y > this.zones[i].y1 && - player.position.y < this.zones[i].y2 - ) { - this.zoneActions[this.zones[i].action](i); - break; - } - } - }, - addZone(x, y, width, height, action, info) { - this.zones[this.zones.length] = { - x1: x, - y1: y - 150, - x2: x + width, - y2: y + height - 70, //-70 to adjust for player height - action: action, - info: info - }; - }, - zoneActions: { - fling(i) { - Matter.Body.setVelocity(player, { - x: level.zones[i].info.Vx, - y: level.zones[i].info.Vy - }); - }, - nextLevel() { - //enter when player isn't falling - if (player.velocity.y < 0.1) { - game.difficulty++; - if (game.difficulty > 1) { - level.difficultyIncrease() - if (level.isBuildRun) level.difficultyIncrease() - } - //cycles map to next level - level.levelsCleared++; - level.onLevel++; - if (level.onLevel > level.levels.length - 1) level.onLevel = 0; - - game.clearNow = true; //triggers in game.clearMap to remove all physics bodies and setup for new map - } - }, - death() { - mech.death(); - }, - laser(i) { - //draw these in game with spawn.background - mech.damage(level.zones[i].info.dmg); - }, - slow() { - Matter.Body.setVelocity(player, { - x: player.velocity.x * 0.5, - y: player.velocity.y * 0.5 - }); - } - }, - queryList: [], //queries do actions on many objects in regions (for only player use a zone) - checkQuery() { - let bounds, action, info; - - function isInZone(targetArray) { - let results = Matter.Query.region(targetArray, bounds); - for (let i = 0, len = results.length; i < len; ++i) { - level.queryActions[action](results[i], info); - } - } - for (let i = 0, len = level.queryList.length; i < len; ++i) { - bounds = level.queryList[i].bounds; - action = level.queryList[i].action; - info = level.queryList[i].info; - for (let j = 0, l = level.queryList[i].groups.length; j < l; ++j) { - isInZone(level.queryList[i].groups[j]); - } - } - }, - //oddly query regions can't get smaller than 50 width? - addQueryRegion(x, y, width, height, action, groups = [ - [player], body, mob, powerUp, bullet - ], info) { - this.queryList[this.queryList.length] = { - bounds: { - min: { - x: x, - y: y - }, - max: { - x: x + width, - y: y + height - } - }, - action: action, - groups: groups, - info: info - }; - }, - queryActions: { - bounce(target, info) { - //jerky fling upwards - Matter.Body.setVelocity(target, { - x: info.Vx + (Math.random() - 0.5) * 6, - y: info.Vy - }); - target.torque = (Math.random() - 0.5) * 2 * target.mass; - }, - boost(target, info) { - // if (target.velocity.y < 0) { - // mech.undoCrouch(); - // mech.enterAir(); - mech.buttonCD_jump = 0; // reset short jump counter to prevent short jumps on boosts - mech.hardLandCD = 0 // disable hard landing - Matter.Body.setVelocity(target, { - x: target.velocity.x + (Math.random() - 0.5) * 2, - y: info - }); - }, - force(target, info) { - if (target.velocity.y < 0) { - //gently force up if already on the way up - target.force.x += info.Vx * target.mass; - target.force.y += info.Vy * target.mass; - } else { - target.force.y -= 0.0007 * target.mass; //gently fall in on the way down - } - }, - antiGrav(target) { - target.force.y -= 0.0011 * target.mass; - }, - death(target) { - target.death(); - } - }, - levelAnnounce() { - document.title = "n-gon: L" + (level.levelsCleared) + " " + level.levels[level.onLevel]; - // game.makeTextLog(`
level ${game.difficulty}
${level.levels[level.onLevel]}
`, 300); - // if (game.difficulty === 0) text = ""; - // text = "Level " + (game.difficulty + 1) + ": " + spawn.pickList[0] + "s + " + spawn.pickList[1] + "s"; - - // text = text + " with population: "; - // for (let i = 0, len = spawn.pickList.length; i < len; ++i) { - // if (spawn.pickList[i] != spawn.pickList[i - 1]) { - // text += spawn.pickList[i] + ", "; - // } - // } - // this.speech(text); - // game.makeTextLog(text, 360); - }, - addToWorld(mapName) { - //needs to be run to put bodies into the world - for (let i = 0; i < body.length; i++) { - //body[i].collisionFilter.group = 0; - body[i].collisionFilter.category = 0x010000; - body[i].collisionFilter.mask = 0x111111; - body[i].classType = "body"; - World.add(engine.world, body[i]); //add to world - } - for (let i = 0; i < map.length; i++) { - //map[i].collisionFilter.group = 0; - map[i].collisionFilter.category = 0x000001; - map[i].collisionFilter.mask = 0x111111; - Matter.Body.setStatic(map[i], true); //make static - World.add(engine.world, map[i]); //add to world - } - for (let i = 0; i < cons.length; i++) { - World.add(engine.world, cons[i]); - } - for (let i = 0; i < consBB.length; i++) { - World.add(engine.world, consBB[i]); - } - } +//global game variables +let body = []; //non static bodies +let map = []; //all static bodies +let cons = []; //all constraints between a point and a body +let consBB = []; //all constraints between two bodies +//main object for spawning levels +const level = { + maxJump: 390, + defaultZoom: 1400, + boostScale: 0.000023, + levels: ["skyscrapers", "rooftops", "warehouse", "highrise", "office", "aerie"], + onLevel: 0, + levelsCleared: 0, + start() { + if (level.levelsCleared === 0) { + // game.difficulty = 6; //for testing to simulate possible mobs spawns + b.giveGuns(14) + // mech.fieldUpgrades[2].effect(); + // b.giveMod(5) + + this.intro(); //starting level + // this.testingMap(); + // this.bosses(); + // this.aerie(); + // this.rooftops(); + // this.warehouse(); + // this.highrise(); + // this.office(); + } 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 + level.levelAnnounce(); + } + game.setZoom(); + level.addToWorld(); //add bodies to game engine + game.draw.setPaths(); + }, + isBuildRun: false, + difficultyIncrease(num = 1) { + // if (level.isBuildRun) num++ + for (let i = 0; i < num; i++) { + game.dmgScale += 0.2; //damage done by mobs increases each level + b.dmgScale *= 0.95; //damage done by player decreases each level + game.accelScale *= 1.05 //mob acceleration increases each level + game.lookFreqScale *= 0.95 //mob cycles between looks decreases each level + game.CDScale *= 0.95 //mob CD time decreases each level + } + }, + difficultyDecrease(num = 1) { //used in easy mode for game.reset() + for (let i = 0; i < num; i++) { + game.dmgScale -= 0.2; //damage done by mobs increases each level + if (game.dmgScale < 0.1) game.dmgScale = 0.1; + b.dmgScale /= 0.95; //damage done by player decreases each level + game.accelScale /= 1.05 //mob acceleration increases each level + game.lookFreqScale /= 0.95 //mob cycles between looks decreases each level + game.CDScale /= 0.95 //mob CD time decreases each level + } + }, + //****************************************************************************************************************** + //****************************************************************************************************************** + testingMap() { + //start with all guns + game.zoomScale = 1400 //1400 is normal + spawn.setSpawnList(); + mech.setPosToSpawn(-75, -60); //normal spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + + level.exit.x = 3500; + level.exit.y = -870; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + document.body.style.backgroundColor = "#dcdcde"; + + + + + // this.addZone(250, -1000, 500, 1500, "laser"); + //spawn.debris(0, -900, 4500, 10); //15 debris per level + // setTimeout(function() { + // document.body.style.backgroundColor = "#eee"; + // }, 1); + // this.addQueryRegion(550, -25, 100, 50, "bounce", { Vx: 0, Vy: -25 }); + // level.fillBG.push({ x: 550, y: -25, width: 100, height: 50, color: "#ff0" }); + + spawn.mapRect(-1200, 0, 2200, 300); //left ground + spawn.mapRect(3500, -860, 100, 50); //ground bump wall + spawn.mapVertex(1250, 0, "0 0 0 300 -500 600 -500 300"); + spawn.mapRect(1500, -300, 2000, 300); //upper ground + spawn.mapVertex(3750, 0, "0 600 0 300 -500 0 -500 300"); + spawn.mapRect(4000, 0, 1000, 300); //right lower ground + spawn.mapRect(2200, -600, 600, 50); //center platform + spawn.mapRect(1300, -850, 700, 50); //center platform + spawn.mapRect(3000, -850, 700, 50); //center platform + // spawn.mapRect(0, -2000, 3000, 50); //center platform + spawn.spawnBuilding(-200, -250, 275, 240, false, true, "left"); //far left; player spawns in side + // spawn.boost(350, 0, -1000); + // for (let i = 0; i < 10; i++) { + // powerUps.spawn(950, -425, "gun", false); + // } + // for (let i = 0; i < 5; i++) { + // powerUps.spawn(2500 + i * 20, -1300, "gun", false); + // powerUps.spawn(2500 + i * 20, -1100, "ammo", false); + // } + // spawn.nodeBoss(-500, -600, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); + // spawn.lineBoss(-500, -600, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); + // spawn.bodyRect(-135, -50, 50, 50); + // spawn.bodyRect(-140, -100, 50, 50); + // powerUps.spawn(420, -400, "ammo", false); + powerUps.spawn(450, -400, "mod", false, 6); + // powerUps.spawn(450, -400, "mod", false); + // spawn.bodyRect(-45, -100, 40, 50); + spawn.spawner(800, -1150); + spawn.groupBoss(-600, -550); + // spawn.hopper(800, -150); + // spawn.beamer(800, -150); + // spawn.grower(800, -250); + // spawn.blinker(800, -250, 40); + // spawn.groupBoss(900, -1070); + // for (let i = 0; i < 20; i++) { + // spawn.randomBoss(-100, -1470); + // } + }, + bosses() { + level.defaultZoom = 1500 + game.zoomTransition(level.defaultZoom) + + // spawn.setSpawnList(); + // spawn.setSpawnList(); + // game.difficulty = 7; //for testing to simulate all possible mobs spawns + // for (let i = 0; i < game.difficulty; i++) { + // game.dmgScale += 0.4; //damage done by mobs increases each level + // b.dmgScale *= 0.9; //damage done by player decreases each level + // } + + document.body.style.backgroundColor = "#ddd"; + + // level.fillBG.push({ + // x: -150, + // y: -1150, + // width: 7000, + // height: 1200, + // color: "#eee" + // }); + + level.fill.push({ + x: 6400, + y: -550, + width: 300, + height: 350, + color: "rgba(0,255,255,0.1)" + }); + + mech.setPosToSpawn(0, -750); //normal spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = 6500; + level.exit.y = -230; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + spawn.mapRect(-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); + + function blockDoor(x, y, blockSize = 58) { + spawn.mapRect(x, y - 290, 40, 60); // door lip + spawn.mapRect(x, y, 40, 50); // door lip + for (let i = 0; i < 4; ++i) { + spawn.bodyRect(x + 5, y - 260 + i * blockSize, 30, blockSize); + } + } + blockDoor(710, -710); + + spawn[spawn.pickList[0]](1500, -200, 100 + game.difficulty * 8); + spawn.mapRect(2500, -1200, 200, 750); //right wall + blockDoor(2585, -210) + spawn.mapRect(2500, -200, 200, 300); //right wall + + spawn.nodeBoss(3500, -200, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); + spawn.mapRect(4500, -1200, 200, 750); //right wall + blockDoor(4585, -210) + spawn.mapRect(4500, -200, 200, 300); //right wall + + spawn.lineBoss(5000, -200, spawn.allowedBossList[Math.floor(Math.random() * spawn.allowedBossList.length)]); + spawn.mapRect(6400, -1200, 400, 750); //right wall + spawn.mapRect(6400, -200, 400, 300); //right wall + spawn.mapRect(6700, -1800, 800, 2600); //right wall + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump + + for (let i = 0; i < 5; ++i) { + if (game.difficulty * Math.random() > 3 * i) { + spawn.randomBoss(2000 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); + } + if (game.difficulty * Math.random() > 2.6 * i) { + spawn.randomBoss(3500 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); + } + if (game.difficulty * Math.random() > 2.4 * i) { + spawn.randomBoss(5000 + 500 * (Math.random() - 0.5), -800 + 200 * (Math.random() - 0.5), Infinity); + } + } + }, + intro() { + // b.giveGuns(0, 1000) + game.zoomScale = 1000 //1400 is normal + level.defaultZoom = 1600 + game.zoomTransition(level.defaultZoom, 1) + + mech.setPosToSpawn(460, -100); //normal spawn + level.enter.x = -1000000; //offscreen + level.enter.y = -400; + level.exit.x = 2800; + level.exit.y = -335; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + document.body.style.backgroundColor = "#fff"; + level.fillBG.push({ + x: 2600, + y: -600, + width: 400, + height: 500, + color: "#edf9f9" + }); + + level.fillBG.push({ + x: 1600, + y: -500, + width: 100, + height: 100, + color: "#eee" + }); + + level.fillBG.push({ + x: -55, + y: -283, + width: 12, + height: 100, + color: "#eee" + }); + + //faster way to draw a wire + function wallWire(x, y, width, height, front = false) { + if (front) { + level.fill.push({ + x: x, + y: y, + width: width, + height: height, + color: "#aaa" + }); + } else { + level.fillBG.push({ + x: x, + y: y, + width: width, + height: height, + color: "#eee" + }); + } + } + + for (let i = 0; i < 3; i++) { + wallWire(100 - 10 * i, -1050 - 10 * i, 5, 800); + wallWire(100 - 10 * i, -255 - 10 * i, -300, 5); + } + for (let i = 0; i < 5; i++) { + wallWire(1000 + 10 * i, -1050 - 10 * i, 5, 600); + wallWire(1000 + 10 * i, -450 - 10 * i, 150, 5); + wallWire(1150 + 10 * i, -450 - 10 * i, 5, 500); + } + for (let i = 0; i < 3; i++) { + wallWire(2650 - 10 * i, -700 - 10 * i, -300, 5); + wallWire(2350 - 10 * i, -700 - 10 * i, 5, 800); + } + for (let i = 0; i < 5; i++) { + wallWire(1625 + 10 * i, -1050, 5, 1200); + } + for (let i = 0; i < 4; i++) { + wallWire(1650, -470 + i * 10, 670 - i * 10, 5); + wallWire(1650 + 670 - i * 10, -470 + i * 10, 5, 600); + } + for (let i = 0; i < 3; i++) { + wallWire(-200 - i * 10, -245 + i * 10, 1340, 5); + wallWire(1140 - i * 10, -245 + i * 10, 5, 300); + wallWire(-200 - i * 10, -215 + i * 10, 660, 5); + wallWire(460 - i * 10, -215 + i * 10, 5, 300); + } + spawn.mapRect(-250, 0, 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(level.exit.x, level.exit.y + 20, 100, 100); //exit bump + spawn.mapRect(-95, -1100, 80, 110); //wire source + spawn.mapRect(410, -10, 90, 20); //small platform for player + + // spawn.bodyRect(-35, -50, 50, 50); + // spawn.bodyRect(-40, -100, 50, 50); + // spawn.bodyRect(-45, -150, 60, 50); + // spawn.bodyRect(-40, -200, 50, 50); + // spawn.bodyRect(5, -50, 40, 50); + // spawn.bodyRect(10, -100, 60, 50); + // spawn.bodyRect(-10, -150, 40, 50); + // spawn.bodyRect(55, -100, 40, 50); + // spawn.bodyRect(-150, -300, 100, 100); + // spawn.bodyRect(-150, -200, 100, 100); + // spawn.bodyRect(-150, -100, 100, 100); + + // spawn.bodyRect(1790, -50, 40, 50); + // spawn.bodyRect(1875, -100, 200, 90); + spawn.bodyRect(2425, -120, 70, 50); + spawn.bodyRect(2400, -100, 100, 60); + spawn.bodyRect(2500, -150, 100, 150); //exit step + + mech.health = 0.25; + mech.displayHealth(); + // powerUps.spawn(-100, 0, "heal", false); //starting gun + powerUps.spawn(1900, -150, "heal", false); //starting gun + powerUps.spawn(2050, -150, "heal", false); //starting gun + // powerUps.spawn(2050, -150, "field", false); //starting gun + powerUps.spawn(2300, -150, "gun", false); //starting gun + if (game.isEasyMode) { + // powerUps.spawn(2050, -150, "mod", false); //starting gun + // powerUps.spawn(2050, -150, "mod", false); //starting gun + // powerUps.spawn(-100, -150, "ammo", false); //starting gun + powerUps.spawn(-100, 0, "heal", false); //starting gun + } + + spawn.wireFoot(); + spawn.wireFootLeft(); + spawn.wireKnee(); + spawn.wireKneeLeft(); + spawn.wireHead(); + // spawn.mapRect(1400, -700, 50, 300); //ground + // spawn.healer(1600, -500) + // spawn.healer(1600, -500) + // spawn.healer(1900, -500) + // spawn.healer(1000, -500) + // spawn.healer(1000, -400) + }, + rooftops() { + level.defaultZoom = 1700 + game.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#dcdcde"; + + if (Math.random() < 0.75) { + //normal direction start in top left + mech.setPosToSpawn(-450, -2050); + level.exit.x = 3600; + level.exit.y = -300; + spawn.mapRect(3600, -285, 100, 50); //ground bump wall + //mobs that spawn in exit room + spawn.randomSmallMob(4100, -100); + spawn.randomSmallMob(4600, -100); + spawn.randomMob(3765, -450, 0.3); + level.fill.push({ + x: -650, + y: -2300, + width: 450, + height: 300, + color: "rgba(0,0,0,0.15)" + }); + } else { + //reverse direction, start in bottom right + mech.setPosToSpawn(3650, -310); + level.exit.x = -550; + level.exit.y = -2030; + spawn.mapRect(-550, -2015, 100, 50); //ground bump wall + spawn.boost(4950, 0, 1600); + level.fillBG.push({ + x: -650, + y: -2300, + width: 450, + height: 300, + color: "#d4f4f4" + }); + } + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + spawn.debris(1650, -1800, 3800, 20); //20 debris per level + powerUps.spawnStartingPowerUps(2450, -1675); + + //foreground + + level.fill.push({ + x: 3450, + y: -1250, + width: 1100, + height: 1250, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 4550, + y: -725, + width: 900, + height: 725, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3400, + y: 100, + width: 2150, + height: 900, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: -700, + y: -1950, + width: 2100, + height: 2950, + color: "rgba(0,0,0,0.1)" + }); + + level.fill.push({ + x: 1950, + y: -1950, + width: 600, + height: 350, + color: "rgba(0,0,0,0.1)" + }); + + level.fill.push({ + x: 1950, + y: -1550, + width: 1025, + height: 550, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 1600, + y: -900, + width: 1650, + height: 1900, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3450, + y: -1550, + width: 350, + height: 300, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 700, + y: -2225, + width: 700, + height: 225, + color: "rgba(0,0,0,0.1)" + }); + + //spawn.mapRect(-700, 0, 6250, 100); //ground + spawn.mapRect(3400, 0, 2150, 100); //ground + spawn.mapRect(-700, -2000, 2100, 50); //Top left ledge + spawn.bodyRect(1300, -2125, 50, 125, 0.8); + spawn.bodyRect(1307, -2225, 50, 100, 0.8); + spawn.mapRect(-700, -2350, 50, 400); //far left starting left wall + spawn.mapRect(-700, -2010, 500, 50); //far left starting ground + spawn.mapRect(-700, -2350, 500, 50); //far left starting ceiling + spawn.mapRect(-250, -2350, 50, 200); //far left starting right part of wall + spawn.bodyRect(-240, -2150, 30, 36); //door to starting room + spawn.bodyRect(-240, -2115, 30, 36); //door to starting room + spawn.bodyRect(-240, -2080, 30, 35); //door to starting room + spawn.bodyRect(-240, -2045, 30, 35); //door to starting room + spawn.mapRect(1950, -2000, 600, 50); + spawn.bodyRect(200, -2150, 200, 220, 0.8); + spawn.mapRect(700, -2275, 700, 50); + spawn.bodyRect(1050, -2350, 30, 30, 0.8); + spawn.boost(1800, -1000, 1200); + spawn.bodyRect(1625, -1100, 100, 75); + spawn.bodyRect(1350, -1025, 400, 25); // ground plank + spawn.mapRect(-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(-600, -1250, 400, 250, 0.8); + spawn.mapRect(1575, -1000, 1700, 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(3450, -1600, 350, 50); + spawn.mapRect(1950, -1600, 1025, 50); + spawn.bodyRect(3100, -1015, 375, 15); + spawn.bodyRect(3500, -850, 75, 125, 0.8); + spawn.mapRect(3450, -1000, 50, 580); //left building wall + spawn.bodyRect(3460, -420, 30, 144); + spawn.mapRect(5450, -775, 100, 875); //right building wall + spawn.bodyRect(4850, -750, 300, 25, 0.8); + spawn.bodyRect(3925, -1400, 100, 150, 0.8); + spawn.mapRect(3450, -1250, 1100, 50); + spawn.mapRect(3450, -1225, 50, 75); + spawn.mapRect(4500, -1225, 50, 390); + spawn.mapRect(3450, -725, 1500, 50); + spawn.mapRect(5100, -725, 400, 50); + spawn.mapRect(4500, -735, 50, 635); + spawn.bodyRect(4510, -100, 30, 100, 0.8); + 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); + //spawn.randomBoss(4850, -1250,0.7); + if (game.difficulty > 4) spawn.bomber(2500, -2400, 100); + }, + aerie() { + // game.setZoom(3000); + // game.difficulty = 4; //for testing to simulate possible mobs spawns + level.defaultZoom = 2100 + game.zoomTransition(level.defaultZoom) + + const backwards = (Math.random() < 0.75) ? false : true; + if (backwards) { + mech.setPosToSpawn(4000, -3300); //normal spawn + level.exit.x = -100; + level.exit.y = -1025; + } else { + mech.setPosToSpawn(-50, -1050); //normal spawn + level.exit.x = 3950; + level.exit.y = -3275; + } + // mech.setPosToSpawn(2250, -900); + // game.zoomTransition(1500) //1400 is normal + + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + powerUps.spawnStartingPowerUps(1075, -550); + spawn.debris(-250, 50, 1650, 2); //20 debris per level + spawn.debris(2475, 0, 750, 2); //20 debris per level + spawn.debris(3450, 0, 2000, 20); //20 debris per level + spawn.debris(3500, -2350, 1500, 2); //20 debris per level + 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: 900, + color: "rgba(0,0,0,0.1)" + }); + + //background + level.fillBG.push({ + x: 4200, + y: -2250, + 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(-100, -1010, 100, 30); + spawn.mapRect(-300, -1000, 600, 50); + 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, 50); + spawn.mapRect(900, -500, 550, 50); + 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 + //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.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.mapRect(3950, -3260, 100, 30); + + 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(350, -500, 1) + spawn.randomBoss(4000, -350, 0.6); + spawn.randomBoss(2750, -550, 0.1); + if (game.difficulty > 2) spawn.suckerBoss(4500, -400); + + //add mini boss, giant hopper? or a black hole that spawns hoppers? + }, + skyscrapers() { + level.defaultZoom = 2000 + game.zoomTransition(level.defaultZoom) + + mech.setPosToSpawn(-50, -50); //normal spawn + //mech.setPosToSpawn(1550, -1200); //spawn left high + //mech.setPosToSpawn(1800, -2000); //spawn near exit + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = 1500; + level.exit.y = -1875; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + powerUps.spawnStartingPowerUps(1475, -1175); + spawn.debris(0, -2200, 4500, 20); //20 debris per level + document.body.style.backgroundColor = "#dcdcde"; + + //foreground + level.fill.push({ + x: 2500, + y: -1100, + width: 450, + height: 250, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 2400, + y: -550, + width: 600, + height: 150, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 2550, + y: -1650, + width: 250, + height: 200, + color: "rgba(0,0,0,0.1)" + }); + //level.fill.push({ x: 1350, y: -2100, width: 400, height: 250, color: "rgba(0,255,255,0.1)" }); + level.fill.push({ + x: 700, + y: -110, + width: 400, + height: 110, + color: "rgba(0,0,0,0.2)" + }); + level.fill.push({ + x: 3600, + y: -110, + width: 400, + height: 110, + color: "rgba(0,0,0,0.2)" + }); + level.fill.push({ + x: -250, + y: -300, + width: 450, + height: 300, + color: "rgba(0,0,0,0.15)" + }); + + //background + level.fillBG.push({ + x: 1300, + y: -1800, + width: 750, + height: 1800, + color: "#d4d4d7" + }); + level.fillBG.push({ + x: 3350, + y: -1325, + width: 50, + height: 1325, + color: "#d4d4d7" + }); + level.fillBG.push({ + x: 1350, + y: -2100, + width: 400, + height: 250, + color: "#d4f4f4" + }); + + spawn.mapRect(-300, 0, 5000, 300); //***********ground + spawn.mapRect(-300, -350, 50, 400); //far left starting left wall + spawn.mapRect(-300, -10, 500, 50); //far left starting ground + spawn.mapRect(-300, -350, 500, 50); //far left starting ceiling + spawn.mapRect(150, -350, 50, 200); //far left starting right part of wall + spawn.bodyRect(170, -130, 14, 140, 1, spawn.propsFriction); //door to starting room + spawn.boost(475, 0, 1300); + spawn.mapRect(700, -1100, 400, 990); //far left building + spawn.mapRect(1600, -400, 1500, 500); //long center building + spawn.mapRect(1345, -1100, 250, 25); //left platform + spawn.mapRect(1755, -1100, 250, 25); //right platform + spawn.mapRect(1300, -1850, 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, 250, 200, 0.8); + spawn.bodyRect(2557, -450, 35, 55, 0.7); + spawn.bodyRect(2957, -450, 30, 15, 0.7); + spawn.bodyRect(2900, -450, 60, 45, 0.7); + spawn.bodyRect(915, -1200, 60, 100, 0.95); + spawn.bodyRect(925, -1300, 50, 100, 0.95); + if (Math.random() < 0.9) { + spawn.bodyRect(2300, -1720, 400, 20); + spawn.bodyRect(2590, -1780, 80, 80); + } + spawn.bodyRect(2925, -1100, 25, 250, 0.8); + spawn.bodyRect(3325, -1550, 50, 200, 0.3); + if (Math.random() < 0.8) { + spawn.bodyRect(1400, -75, 200, 75); //block to get up ledge from ground + spawn.bodyRect(1525, -125, 50, 50); //block to get up ledge from ground + } + spawn.bodyRect(1025, -1110, 400, 25, 0.9); //block on far left building + spawn.bodyRect(1425, -1110, 115, 25, 0.9); //block on far left building + spawn.bodyRect(1540, -1110, 300, 25, 0.9); //block on far left building + + if (game.difficulty > 2) spawn.shooterBoss(2200, -1300); + spawn.randomSmallMob(1300, -70); + spawn.randomSmallMob(3200, -100); + spawn.randomSmallMob(4450, -100); + spawn.randomSmallMob(2700, -475); + spawn.randomMob(2650, -975, 0.8); + spawn.randomMob(2650, -1550, 0.8); + spawn.randomMob(4150, -200, 0.15); + spawn.randomMob(1700, -1300, 0.2); + spawn.randomMob(1850, -1950, 0.25); + spawn.randomMob(2610, -1880, 0.25); + spawn.randomMob(3350, -950, 0.25); + spawn.randomMob(1690, -2250, 0.25); + spawn.randomMob(2200, -600, 0.2); + spawn.randomMob(850, -1300, 0.25); + spawn.randomMob(-100, -900, -0.2); + spawn.randomBoss(3700, -1500, 0.4); + spawn.randomBoss(1700, -900, 0.4); + }, + highrise() { + level.defaultZoom = 1500 + game.zoomTransition(level.defaultZoom) + + document.body.style.backgroundColor = "#dcdcde" //"#fafcff"; + mech.setPosToSpawn(0, -700); //normal spawn + //mech.setPosToSpawn(-2000, -1700); // left ledge spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = -4275; + level.exit.y = -2805; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + powerUps.spawnStartingPowerUps(-2550, -700); + + // spawn.laserZone(-550, -350, 10, 400, 0.3) + // spawn.deathQuery(-550, -350, 50, 400) + + // spawn.debris(-3950, -2575, 1050, 4); //20 debris per level + spawn.debris(-2325, -1825, 2400); //20 debris per level + spawn.debris(-2625, -600, 600, 6); //20 debris per level + spawn.debris(-2000, -60, 1200, 6); //20 debris per level + // if (!game.difficulty) powerUps.spawn(2450, -1675, "gun", false); + //background + level.fillBG.push({ + x: -4425, + y: -3050, + width: 425, + height: 275, + color: "#cff" + }); + //foreground + level.fill.push({ + x: -1650, + y: -1575, + width: 550, + height: 425, + color: "rgba(0,0,0,0.12)" + }); + level.fill.push({ + x: -2600, + y: -2400, + width: 450, + height: 1800, + color: "rgba(0,0,0,0.12)" + }); + level.fill.push({ + x: -3425, + y: -2150, + width: 525, + height: 1550, + color: "rgba(0,0,0,0.12)" + }); + level.fill.push({ + x: -1850, + y: -1150, + width: 2025, + height: 1150, + color: "rgba(0,0,0,0.12)" + }); + + //hidden zone + level.fill.push({ + x: -4450, + y: -955, + width: 1025, + height: 360, + color: "rgba(64,64,64,0.97)" + }); + // level.fill.push({ + // x: -4050, + // y: -955, + // width: 625, + // height: 360, + // color: "#444" + // }); + powerUps.spawn(-4300, -700, "heal"); + powerUps.spawn(-4200, -700, "ammo"); + powerUps.spawn(-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); + } + // spawn.bodyRect(-1100, -125, 150, 125); + + // spawn.bodyRect(-1200, -75, 75, 75); + + //building 2 + spawn.mapRect(-4450, -600, 2300, 750); + spawn.mapRect(-2225, -500, 175, 550); + spawn.boost(-2800, -600, 1150); + spawn.mapRect(-3450, -1325, 550, 50); + spawn.mapRect(-3425, -2200, 525, 50); + spawn.mapRect(-2600, -1750, 450, 50); + spawn.mapRect(-2600, -2450, 450, 50); + spawn.bodyRect(-2275, -2700, 50, 60); + spawn.bodyRect(-2600, -1975, 250, 225); + spawn.bodyRect(-3415, -1425, 100, 100); + spawn.bodyRect(-3400, -1525, 100, 100); + spawn.bodyRect(-3305, -1425, 100, 100); + + //building 3 + spawn.mapRect(-4450, -1750, 1025, 1000); + spawn.mapRect(-3750, -2000, 175, 275); + spawn.mapRect(-4000, -2350, 275, 675); + // spawn.mapRect(-4450, -2650, 475, 1000); + spawn.mapRect(-4450, -2775, 475, 1125); + spawn.bodyRect(-3715, -2050, 50, 50); + spawn.bodyRect(-3570, -1800, 50, 50); + spawn.bodyRect(-2970, -2250, 50, 50); + + if (game.difficulty < 4) spawn.bodyRect(-3760, -2400, 50, 50); + + spawn.bodyRect(-3080, -2250, 40, 40); + spawn.bodyRect(-3420, -650, 50, 50); + + //exit + spawn.mapRect(-4450, -3075, 25, 300); + spawn.mapRect(-4450, -3075, 450, 25); + spawn.mapRect(-4025, -3075, 25, 100); + spawn.mapRect(-4275, -2785, 100, 25); + + //mobs + spawn.randomMob(-2500, -2700, 1); + spawn.randomMob(-3200, -750, 1); + spawn.randomMob(-1875, -775, 0.2); + spawn.randomMob(-950, -1675, 0.2); + spawn.randomMob(-1525, -1750, 0.2); + spawn.randomMob(-1375, -1400, 0.2); + spawn.randomMob(-1625, -1275, 0.2); + spawn.randomMob(-1900, -1250, 0.2); + spawn.randomMob(-2250, -1850, 0.2); + spawn.randomMob(-2475, -2200, 0.2); + spawn.randomMob(-3000, -1475, 0.2); + spawn.randomMob(-3850, -2500, 0.2); + spawn.randomMob(-3650, -2125, 0.2); + spawn.randomMob(-4010, -3200, 0.2); + spawn.randomMob(-3500, -1825, 0.2); + spawn.randomMob(-975, -100, 0); + spawn.randomMob(-1050, -725, 0.2); + spawn.randomMob(-1525, -100, 0); + spawn.randomMob(-525, -1700, -0.1); + spawn.randomMob(-125, -1500, -0.1); + spawn.randomMob(-325, -1900, -0.1); + spawn.randomMob(-550, -100, -0.1); + spawn.randomBoss(-3250, -2700, 0.2); + spawn.randomBoss(-2450, -1100, 0); + }, + warehouse() { + level.defaultZoom = 1300 + game.zoomTransition(level.defaultZoom) + + document.body.style.backgroundColor = "#f2f5f3"; + mech.setPosToSpawn(25, -60); //normal spawn + //mech.setPosToSpawn(-2000, -1700); // left ledge spawn + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + level.exit.x = 425; + level.exit.y = -35; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + //level.addQueryRegion(-600, -250, 180, 420, "death", [[player]],{}); + + spawn.debris(-2250, 1330, 3000, 7); //20 debris per level + spawn.debris(-3000, -800, 3280, 7); //20 debris per level + spawn.debris(-1400, 410, 2300, 6); //20 debris per level + powerUps.spawnStartingPowerUps(25, 500); + + //foreground + // level.fill.push({ x: -3025, y: 50, width: 4125, height: 1350, color: "rgba(0,0,0,0.05)"}); + // level.fill.push({ x: -1800, y: -500, width: 1975, height: 550, color: "rgba(0,0,0,0.05)"}); + // level.fill.push({ x: -2600, y: -150, width: 700, height: 200, color: "rgba(0,0,0,0.05)"}); + //background + const BGColor = "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, -600, 100, 1300); + //house + spawn.mapRect(-175, -550, 50, 400); + spawn.mapRect(-175, -15, 350, 50); + spawn.mapRect(-25, -25, 100, 50); + // spawn.mapRect(-175, -275, 350, 25); + // spawn.mapRect(-175, -250, 25, 75); + // spawn.bodyRect(-170, -175, 14, 160, 1, spawn.propsFriction); //door to starting room + //exit house + spawn.mapRect(300, -15, 350, 50); + spawn.mapRect(-150, -300, 800, 50); + spawn.mapRect(600, -275, 50, 75); + spawn.mapRect(425, -25, 100, 25); + // spawn.mapRect(-1900, 600, 2700, 100); + spawn.mapRect(1100, 0, 150, 1500); + spawn.mapRect(-2850, 1400, 4100, 100); + spawn.mapRect(-2375, 875, 1775, 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(-1350, -200, 200, 200, 1, spawn.propsSlide); //weight + spawn.bodyRect(-1800, 0, 300, 100, 1, spawn.propsHoist); //hoist + cons[cons.length] = Constraint.create({ + pointA: { + x: -1650, + y: -500 + }, + bodyB: body[body.length - 1], + stiffness: 0.000076, + length: 1 + }); + + spawn.bodyRect(400, 400, 200, 200, 1, spawn.propsSlide); //weight + spawn.bodyRect(800, 600, 300, 100, 1, spawn.propsHoist); //hoist + cons[cons.length] = Constraint.create({ + pointA: { + x: 950, + y: 100 + }, + bodyB: body[body.length - 1], + stiffness: 0.000076, + length: 1 + }); + + spawn.bodyRect(-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.0002, + length: 566 + }); + + //blocks + //spawn.bodyRect(-155, -150, 10, 140, 1, spawn.propsFriction); + spawn.bodyRect(-165, -150, 30, 35, 1); + spawn.bodyRect(-165, -115, 30, 35, 1); + spawn.bodyRect(-165, -80, 30, 35, 1); + spawn.bodyRect(-165, -45, 30, 35, 1); + + spawn.bodyRect(-750, 400, 150, 150, 0.5); + spawn.bodyRect(-400, 1175, 100, 250, 1); //block to get to top path on bottom level + // spawn.bodyRect(-1450, 737, 75, 103, 0.5); //blocking path + + spawn.bodyRect(-2525, -50, 145, 100, 0.5); + spawn.bodyRect(-2325, -300, 150, 100, 0.5); + spawn.bodyRect(-1275, -750, 200, 150, 0.5); //roof block + spawn.bodyRect(-525, -700, 125, 100, 0.5); //roof block + + //mobs + spawn.randomSmallMob(-1125, 550); + spawn.randomSmallMob(-2325, 800); + spawn.randomSmallMob(-2950, -50); + spawn.randomSmallMob(825, 300); + spawn.randomSmallMob(-900, 825); + spawn.randomMob(-2025, 175, 0.6); + spawn.randomMob(-2325, 450, 0.6); + spawn.randomMob(-2925, 675, 0.5); + spawn.randomMob(-2700, 300, 0.2); + spawn.randomMob(-2500, 300, 0.2); + spawn.randomMob(-2075, -425, 0.2); + spawn.randomMob(-1550, -725, 0.2); + spawn.randomMob(375, 1100, 0.1); + spawn.randomMob(-1425, -100, 0.1); + spawn.randomMob(-800, -750, 0); + spawn.randomMob(400, -350, 0); + spawn.randomMob(650, 1300, 0); + spawn.randomMob(-750, -150, 0); + spawn.randomMob(475, 300, 0); + spawn.randomMob(-75, -700, 0); + spawn.randomMob(900, -200, -0.1); + spawn.randomBoss(-125, 275, -0.2); + spawn.randomBoss(-825, 1000, 0.2); + spawn.randomBoss(-1300, -1100, -0.3); + //spawn.randomBoss(600, -1575, 0); + //spawn.randomMob(1120, -1200, 0.3); + //spawn.randomSmallMob(2200, -1775); + + if (game.difficulty > 2) spawn.snaker(-1300 + Math.random() * 2000, -2200); //boss snake with head + }, + office() { + level.defaultZoom = 1400 + game.zoomTransition(level.defaultZoom) + + if (Math.random() < 0.75) { + //normal direction start in top left + mech.setPosToSpawn(1375, -1550); //normal spawn + level.exit.x = 3250; + level.exit.y = -530; + // spawn.randomSmallMob(3550, -550); + } else { + //reverse direction, start in bottom right + mech.setPosToSpawn(3250, -530); //normal spawn + level.exit.x = 1375; + level.exit.y = -1530; + spawn.bodyRect(3655, -650, 40, 150); //door + } + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 50); //ground bump wall + level.enter.x = mech.spawnPos.x - 50; + level.enter.y = mech.spawnPos.y + 20; + this.addZone(level.exit.x, level.exit.y, 100, 30, "nextLevel"); + + document.body.style.backgroundColor = "#e0e5e0"; + //foreground + level.fill.push({ + x: -550, + y: -1700, + width: 1300, + height: 1700, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 750, + y: -1450, + width: 650, + height: 1450, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 750, + y: -1950, + width: 800, + height: 450, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3000, + y: -1000, + width: 650, + height: 1000, + color: "rgba(0,0,0,0.1)" + }); + level.fill.push({ + x: 3650, + y: -1300, + width: 1300, + height: 1300, + color: "rgba(0,0,0,0.1)" + }); + + //mech.setPosToSpawn(600, -1200); //normal spawn + //mech.setPosToSpawn(525, -150); //ground first building + //mech.setPosToSpawn(3150, -700); //near exit spawn + spawn.debris(-300, -200, 1000, 5); //ground debris //20 debris per level + spawn.debris(3500, -200, 800, 5); //ground debris //20 debris per level + spawn.debris(-300, -650, 1200, 5); //1st floor debris //20 debris per level + spawn.debris(3500, -650, 800, 5); //1st floor debris //20 debris per level + powerUps.spawnStartingPowerUps(-525, -700); + + spawn.mapRect(-600, 25, 5600, 300); //ground + spawn.mapRect(-600, 0, 2000, 50); //ground + spawn.mapRect(-600, -1700, 50, 2000 - 100); //left wall + spawn.bodyRect(-295, -1540, 40, 40); //center block under wall + spawn.bodyRect(-298, -1580, 40, 40); //center block under wall + spawn.bodyRect(1500, -1540, 30, 30); //left of entrance + + spawn.mapRect(1550, -2000, 50, 550); //right wall + spawn.mapRect(1350, -2000 + 505, 50, 1295); //right wall + spawn.mapRect(-600, -2000 + 250, 2000 - 700, 50); //roof left + spawn.mapRect(-600 + 1300, -2000, 50, 300); //right roof wall + spawn.mapRect(-600 + 1300, -2000, 900, 50); //center wall + map[map.length] = Bodies.polygon(425, -1700, 0, 15); //circle above door + spawn.bodyRect(420, -1675, 15, 170, 1, spawn.propsDoor); // door + body[body.length - 1].isNotHoldable = true; + //makes door swing + consBB[consBB.length] = Constraint.create({ + bodyA: body[body.length - 1], + pointA: { + x: 0, + y: -90 + }, + bodyB: map[map.length - 1], + stiffness: 1 + }); + spawn.mapRect(-600 + 300, -2000 * 0.75, 1900, 50); //3rd floor + spawn.mapRect(-600 + 2000 * 0.7, -2000 * 0.74, 50, 375); //center wall + spawn.bodyRect(-600 + 2000 * 0.7, -2000 * 0.5 - 106, 50, 106); //center block under wall + spawn.mapRect(-600, -1000, 1100, 50); //2nd floor + spawn.mapRect(600, -1000, 500, 50); //2nd floor + spawn.spawnStairs(-600, -1000, 4, 250, 350); //stairs 2nd + spawn.mapRect(350, -600, 350, 150); //center table + spawn.mapRect(-600 + 300, -2000 * 0.25, 2000 - 300, 50); //1st floor + spawn.spawnStairs(-600 + 2000 - 50, -500, 4, 250, 350, true); //stairs 1st + spawn.spawnStairs(-600, 0, 4, 250, 350); //stairs ground + spawn.bodyRect(700, -200, 100, 100); //center block under wall + spawn.bodyRect(700, -300, 100, 100); //center block under wall + spawn.bodyRect(700, -400, 100, 100); //center block under wall + spawn.mapRect(1390, 13, 30, 20); //step left + spawn.mapRect(2980, 13, 30, 20); //step right + spawn.mapRect(3000, 0, 2000, 50); //ground + spawn.bodyRect(4250, -700, 50, 100); + spawn.bodyRect(3000, -200, 50, 200); //door + spawn.mapRect(3000, -1000, 50, 800); //left wall + spawn.mapRect(3000 + 2000 - 50, -1300, 50, 1100); //right wall + spawn.mapRect(4150, -600, 350, 150); //table + spawn.mapRect(3650, -1300, 50, 650); //exit wall + spawn.mapRect(3650, -1300, 1350, 50); //exit wall + + spawn.mapRect(3000, -2000 * 0.5, 700, 50); //exit roof + spawn.mapRect(3000, -2000 * 0.25, 2000 - 300, 50); //1st floor + spawn.spawnStairs(3000 + 2000 - 50, 0, 4, 250, 350, true); //stairs ground + + // tether ball + if (game.difficulty > 2) { + level.fillBG.push({ + x: 2495, + y: -500, + width: 10, + height: 525, + color: "#ccc" + }); + spawn.tether(2850, -80) + cons[cons.length] = Constraint.create({ + pointA: { + x: 2500, + y: -500 + }, + bodyB: mob[mob.length - 1], + stiffness: 0.00012 + }); + //chance to spawn a ring of exploding mobs around this boss + if (game.difficulty > 4) spawn.nodeBoss(2850, -80, "spawns", 8, 20, 105); + } + + spawn.randomSmallMob(4575, -560, 1); + spawn.randomSmallMob(1315, -880, 1); + spawn.randomSmallMob(800, -600); + spawn.randomSmallMob(-100, -1600); + spawn.randomMob(4100, -225, 0.8); + spawn.randomMob(-250, -700, 0.8); + spawn.randomMob(4500, -225, 0.15); + spawn.randomMob(3250, -225, 0.15); + spawn.randomMob(-100, -225, 0.1); + spawn.randomMob(1150, -225, 0.15); + spawn.randomMob(2000, -225, 0.15); + spawn.randomMob(450, -225, 0.15); + spawn.randomMob(100, -1200, 1); + spawn.randomMob(950, -1150, -0.1); + spawn.randomBoss(1800, -800, -0.2); + spawn.randomBoss(4150, -1000, 0.6); + }, + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + //***************************************************************************************************************** + enter: { + x: 0, + y: 0, + draw() { + ctx.beginPath(); + ctx.moveTo(this.x, this.y + 30); + ctx.lineTo(this.x, this.y - 80); + ctx.bezierCurveTo(this.x, this.y - 170, this.x + 100, this.y - 170, this.x + 100, this.y - 80); + ctx.lineTo(this.x + 100, this.y + 30); + ctx.lineTo(this.x, this.y + 30); + ctx.fillStyle = "#ccc"; + ctx.fill(); + } + }, + exit: { + x: 0, + y: 0, + draw() { + ctx.beginPath(); + ctx.moveTo(this.x, this.y + 30); + ctx.lineTo(this.x, this.y - 80); + ctx.bezierCurveTo(this.x, this.y - 170, this.x + 100, this.y - 170, this.x + 100, this.y - 80); + ctx.lineTo(this.x + 100, this.y + 30); + ctx.lineTo(this.x, this.y + 30); + ctx.fillStyle = "#0ff"; + ctx.fill(); + } + }, + fillBG: [], + drawFillBGs() { + for (let i = 0, len = level.fillBG.length; i < len; ++i) { + const f = level.fillBG[i]; + ctx.fillStyle = f.color; + ctx.fillRect(f.x, f.y, f.width, f.height); + } + }, + fill: [], + drawFills() { + for (let i = 0, len = level.fill.length; i < len; ++i) { + const f = level.fill[i]; + ctx.fillStyle = f.color; + ctx.fillRect(f.x, f.y, f.width, f.height); + } + }, + zones: [], //zone do actions when player is in a region // to effect everything use a query + checkZones() { + for (let i = 0, len = this.zones.length; i < len; ++i) { + if ( + player.position.x > this.zones[i].x1 && + player.position.x < this.zones[i].x2 && + player.position.y > this.zones[i].y1 && + player.position.y < this.zones[i].y2 + ) { + this.zoneActions[this.zones[i].action](i); + break; + } + } + }, + addZone(x, y, width, height, action, info) { + this.zones[this.zones.length] = { + x1: x, + y1: y - 150, + x2: x + width, + y2: y + height - 70, //-70 to adjust for player height + action: action, + info: info + }; + }, + zoneActions: { + fling(i) { + Matter.Body.setVelocity(player, { + x: level.zones[i].info.Vx, + y: level.zones[i].info.Vy + }); + }, + nextLevel() { + //enter when player isn't falling + if (player.velocity.y < 0.1) { + game.difficulty++; + if (game.difficulty > 1) { + level.difficultyIncrease() + if (level.isBuildRun) level.difficultyIncrease() + } + //cycles map to next level + level.levelsCleared++; + level.onLevel++; + if (level.onLevel > level.levels.length - 1) level.onLevel = 0; + + game.clearNow = true; //triggers in game.clearMap to remove all physics bodies and setup for new map + } + }, + death() { + mech.death(); + }, + laser(i) { + //draw these in game with spawn.background + mech.damage(level.zones[i].info.dmg); + }, + slow() { + Matter.Body.setVelocity(player, { + x: player.velocity.x * 0.5, + y: player.velocity.y * 0.5 + }); + } + }, + queryList: [], //queries do actions on many objects in regions (for only player use a zone) + checkQuery() { + let bounds, action, info; + + function isInZone(targetArray) { + let results = Matter.Query.region(targetArray, bounds); + for (let i = 0, len = results.length; i < len; ++i) { + level.queryActions[action](results[i], info); + } + } + for (let i = 0, len = level.queryList.length; i < len; ++i) { + bounds = level.queryList[i].bounds; + action = level.queryList[i].action; + info = level.queryList[i].info; + for (let j = 0, l = level.queryList[i].groups.length; j < l; ++j) { + isInZone(level.queryList[i].groups[j]); + } + } + }, + //oddly query regions can't get smaller than 50 width? + addQueryRegion(x, y, width, height, action, groups = [ + [player], body, mob, powerUp, bullet + ], info) { + this.queryList[this.queryList.length] = { + bounds: { + min: { + x: x, + y: y + }, + max: { + x: x + width, + y: y + height + } + }, + action: action, + groups: groups, + info: info + }; + }, + queryActions: { + bounce(target, info) { + //jerky fling upwards + Matter.Body.setVelocity(target, { + x: info.Vx + (Math.random() - 0.5) * 6, + y: info.Vy + }); + target.torque = (Math.random() - 0.5) * 2 * target.mass; + }, + boost(target, info) { + // if (target.velocity.y < 0) { + // mech.undoCrouch(); + // mech.enterAir(); + mech.buttonCD_jump = 0; // reset short jump counter to prevent short jumps on boosts + mech.hardLandCD = 0 // disable hard landing + Matter.Body.setVelocity(target, { + x: target.velocity.x + (Math.random() - 0.5) * 2, + y: info + }); + }, + force(target, info) { + if (target.velocity.y < 0) { + //gently force up if already on the way up + target.force.x += info.Vx * target.mass; + target.force.y += info.Vy * target.mass; + } else { + target.force.y -= 0.0007 * target.mass; //gently fall in on the way down + } + }, + antiGrav(target) { + target.force.y -= 0.0011 * target.mass; + }, + death(target) { + target.death(); + } + }, + levelAnnounce() { + document.title = "n-gon: L" + (level.levelsCleared) + " " + level.levels[level.onLevel]; + // game.makeTextLog(`
level ${game.difficulty}
${level.levels[level.onLevel]}
`, 300); + // if (game.difficulty === 0) text = ""; + // text = "Level " + (game.difficulty + 1) + ": " + spawn.pickList[0] + "s + " + spawn.pickList[1] + "s"; + + // text = text + " with population: "; + // for (let i = 0, len = spawn.pickList.length; i < len; ++i) { + // if (spawn.pickList[i] != spawn.pickList[i - 1]) { + // text += spawn.pickList[i] + ", "; + // } + // } + // this.speech(text); + // game.makeTextLog(text, 360); + }, + addToWorld(mapName) { + //needs to be run to put bodies into the world + for (let i = 0; i < body.length; i++) { + //body[i].collisionFilter.group = 0; + body[i].collisionFilter.category = 0x010000; + body[i].collisionFilter.mask = 0x111111; + body[i].classType = "body"; + World.add(engine.world, body[i]); //add to world + } + for (let i = 0; i < map.length; i++) { + //map[i].collisionFilter.group = 0; + map[i].collisionFilter.category = 0x000001; + map[i].collisionFilter.mask = 0x111111; + Matter.Body.setStatic(map[i], true); //make static + World.add(engine.world, map[i]); //add to world + } + for (let i = 0; i < cons.length; i++) { + World.add(engine.world, cons[i]); + } + for (let i = 0; i < consBB.length; i++) { + World.add(engine.world, consBB[i]); + } + } }; \ No newline at end of file diff --git a/js/mobs.js b/js/mobs.js index 3b4c630..e464407 100644 --- a/js/mobs.js +++ b/js/mobs.js @@ -1,1011 +1,1011 @@ -//create array of mobs -let mob = []; -//method to populate the array above -const mobs = { - loop() { - let i = mob.length; - while (i--) { - if (mob[i].alive) { - mob[i].do(); - } else { - mob[i].replace(i); //removing mob and replace with body, this is done here to avoid an array index bug with drawing I think - } - } - }, - draw() { - ctx.lineWidth = 2; - let i = mob.length; - while (i--) { - ctx.beginPath(); - const vertices = mob[i].vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1, len = vertices.length; j < len; ++j) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - ctx.fillStyle = mob[i].fill; - ctx.strokeStyle = mob[i].stroke; - ctx.fill(); - ctx.stroke(); - } - }, - // alert(range) { - // range = range * range; - // for (let i = 0; i < mob.length; i++) { - // if (mob[i].distanceToPlayer2() < range) mob[i].locatePlayer(); - // } - // }, - // startle(amount) { - // for (let i = 0; i < mob.length; i++) { - // if (!mob[i].seePlayer.yes) { - // mob[i].force.x += amount * mob[i].mass * (Math.random() - 0.5); - // mob[i].force.y += amount * mob[i].mass * (Math.random() - 0.5); - // } - // } - // }, - //********************************************************************************************** - //********************************************************************************************** - spawn(xPos, yPos, sides, radius, color) { - let i = mob.length; - mob[i] = Matter.Bodies.polygon(xPos, yPos, sides, radius, { - //inertia: Infinity, //prevents rotation - mob: true, - density: 0.001, - //friction: 0, - frictionAir: 0.005, - //frictionStatic: 0, - restitution: 0.5, - collisionFilter: { - group: 0, - category: 0x000010, - mask: 0x011111 - }, - onHit: undefined, - alive: true, - index: i, - health: 1, - accelMag: 0.001, - cd: 0, //game cycle when cooldown will be over - delay: 60, //static: time between cooldowns - fill: color, - stroke: "#000", - seePlayer: { - yes: false, - recall: 0, - position: { - x: xPos, - y: yPos - } - }, - radius: radius, - spawnPos: { - x: xPos, - y: yPos - }, - seeAtDistance2: 4000000, //sqrt(4000000) = 2000 = max seeing range - distanceToPlayer() { - const dx = this.position.x - player.position.x; - const dy = this.position.y - player.position.y; - return Math.sqrt(dx * dx + dy * dy); - }, - distanceToPlayer2() { - const dx = this.position.x - player.position.x; - const dy = this.position.y - player.position.y; - return dx * dx + dy * dy; - }, - gravity() { - this.force.y += this.mass * this.g; - }, - seePlayerFreq: Math.round((30 + 30 * Math.random()) * game.lookFreqScale), //how often NPC checks to see where player is, lower numbers have better vision - foundPlayer() { - this.locatePlayer(); - if (!this.seePlayer.yes) { - this.alertNearByMobs(); - this.seePlayer.yes = true; - } - }, - lostPlayer() { - this.seePlayer.yes = false; - this.seePlayer.recall -= this.seePlayerFreq; - if (this.seePlayer.recall < 0) this.seePlayer.recall = 0; - }, - memory: 120, //default time to remember player's location - locatePlayer() { - if (!mech.isStealth) { - // updates mob's memory of player location - this.seePlayer.recall = this.memory + Math.round(this.memory * Math.random()); //seconds before mob falls a sleep - this.seePlayer.position.x = player.position.x; - this.seePlayer.position.y = player.position.y; - } - }, - // locatePlayerByDist() { - // if (this.distanceToPlayer2() < this.locateRange) { - // this.locatePlayer(); - // } - // }, - seePlayerCheck() { - if (!(game.cycle % this.seePlayerFreq)) { - if ( - this.distanceToPlayer2() < this.seeAtDistance2 && - Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && - Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0 - ) { - this.foundPlayer(); - } else if (this.seePlayer.recall) { - this.lostPlayer(); - } - } - }, - seePlayerCheckByDistance() { - if (!(game.cycle % this.seePlayerFreq)) { - if (this.distanceToPlayer2() < this.seeAtDistance2) { - this.foundPlayer(); - } else if (this.seePlayer.recall) { - this.lostPlayer(); - } - } - }, - seePlayerByDistOrLOS() { - if (!(game.cycle % this.seePlayerFreq)) { - if ( - this.distanceToPlayer2() < this.seeAtDistance2 || - (Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0) - ) { - this.foundPlayer(); - } else if (this.seePlayer.recall) { - this.lostPlayer(); - } - } - }, - seePlayerByDistAndLOS() { - if (!(game.cycle % this.seePlayerFreq)) { - if ( - this.distanceToPlayer2() < this.seeAtDistance2 && - (Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0) - ) { - this.foundPlayer(); - } else if (this.seePlayer.recall) { - this.lostPlayer(); - } - } - }, - isLookingAtPlayer(threshold) { - const diff = Matter.Vector.normalise(Matter.Vector.sub(player.position, this.position)); - //make a vector for the mob's direction of length 1 - const dir = { - x: Math.cos(this.angle), - y: Math.sin(this.angle) - }; - //the dot product of diff and dir will return how much over lap between the vectors - const dot = Matter.Vector.dot(dir, diff); - // console.log(Math.cos(dot)*180/Math.PI) - if (dot > threshold) { - return true; - } else { - return false; - } - }, - lookRange: 0.2 + Math.random() * 0.2, - lookTorque: 0.0000004 * (Math.random() > 0.5 ? -1 : 1), - seePlayerByLookingAt() { - if (!(game.cycle % this.seePlayerFreq) && (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange))) { - if ( - this.distanceToPlayer2() < this.seeAtDistance2 && - Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && - Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0 - ) { - this.foundPlayer(); - } else if (this.seePlayer.recall) { - this.lostPlayer(); - } - } - //if you don't recall player location rotate and draw to show where you are looking - if (!this.seePlayer.recall) { - this.torque = this.lookTorque * this.inertia; - //draw - const range = Math.PI * this.lookRange; - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range); - ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true); - ctx.fillStyle = "rgba(0,0,0,0.07)"; - ctx.fill(); - } - }, - mechPosRange() { - return { - x: player.position.x, // + (Math.random() - 0.5) * 50, - y: player.position.y + (Math.random() - 0.5) * 110 - }; - //mob vision for testing - // ctx.beginPath(); - // ctx.lineWidth = "5"; - // ctx.strokeStyle = "#ff0"; - // ctx.moveTo(this.position.x, this.position.y); - // ctx.lineTo(targetPos.x, targetPos.y); - // ctx.stroke(); - // return targetPos; - }, - laserBeam() { - if (game.cycle % 7 && this.seePlayer.yes) { - ctx.setLineDash([125 * Math.random(), 125 * Math.random()]); - // ctx.lineDashOffset = 6*(game.cycle % 215); - if (this.distanceToPlayer() < this.laserRange) { - //if (Math.random()>0.2 && this.seePlayer.yes && this.distanceToPlayer2()<800000) { - if (b.isModTempResist) { - mech.damage(0.00006 * game.dmgScale); - } else { - mech.damage(0.0003 * game.dmgScale); - } - if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.004 - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(mech.pos.x, mech.pos.y); - ctx.lineTo(mech.pos.x + (Math.random() - 0.5) * 3000, mech.pos.y + (Math.random() - 0.5) * 3000); - ctx.lineWidth = 2; - ctx.strokeStyle = "rgb(255,0,170)"; - ctx.stroke(); - - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(255,0,170,0.15)"; - ctx.fill(); - - } - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.laserRange * 0.9, 0, 2 * Math.PI); - ctx.strokeStyle = "rgba(255,0,170,0.5)"; - ctx.lineWidth = 1; - ctx.stroke(); - ctx.setLineDash([]); - ctx.fillStyle = "rgba(255,0,170,0.03)"; - ctx.fill(); - } - }, - laser() { - const vertexCollision = function (v1, v1End, domain) { - for (let i = 0; i < domain.length; ++i) { - let vertices = domain[i].vertices; - const len = vertices.length - 1; - for (let j = 0; j < len; j++) { - results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[j], - v2: vertices[j + 1] - }; - } - } - } - results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[0], - v2: vertices[len] - }; - } - } - } - }; - if (this.seePlayer.recall) { - this.torque = this.lookTorque * this.inertia * 2; - - const seeRange = 2500; - best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - const look = { - x: this.position.x + seeRange * Math.cos(this.angle), - y: this.position.y + seeRange * Math.sin(this.angle) - }; - vertexCollision(this.position, look, map); - vertexCollision(this.position, look, body); - vertexCollision(this.position, look, [player]); - // hitting player - if (best.who === player) { - if (b.isModTempResist) { - dmg = 0.0008 * game.dmgScale; - } else { - dmg = 0.004 * game.dmgScale; - } - - mech.damage(dmg); - //draw damage - ctx.fillStyle = color; - ctx.beginPath(); - ctx.arc(best.x, best.y, dmg * 2000, 0, 2 * Math.PI); - ctx.fill(); - } - //draw beam - if (best.dist2 === Infinity) { - best = look; - } - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(best.x, best.y); - ctx.strokeStyle = "#f00"; // Purple path - ctx.lineWidth = 1; - ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]); - ctx.stroke(); // Draw it - ctx.setLineDash([0, 0]); - } - }, - searchSpring() { - ctx.beginPath(); - ctx.arc(this.cons.pointA.x, this.cons.pointA.y, 6, 0, 2 * Math.PI); - ctx.arc(this.cons2.pointA.x, this.cons2.pointA.y, 6, 0, 2 * Math.PI); - // ctx.arc(this.cons.bodyB.position.x, this.cons.bodyB.position.y,6,0,2*Math.PI); - ctx.fillStyle = "#222"; - ctx.fill(); - - if (!(game.cycle % this.seePlayerFreq)) { - if ( - (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange)) && - this.distanceToPlayer2() < this.seeAtDistance2 && - Matter.Query.ray(map, this.position, player.position).length === 0 && - Matter.Query.ray(body, this.position, player.position).length === 0 - ) { - this.foundPlayer(); - if (!(game.cycle % (this.seePlayerFreq * 2))) { - this.springTarget.x = this.seePlayer.position.x; - this.springTarget.y = this.seePlayer.position.y; - this.cons.length = -200; - this.cons2.length = 100 + 1.5 * this.radius; - } else { - this.springTarget2.x = this.seePlayer.position.x; - this.springTarget2.y = this.seePlayer.position.y; - this.cons.length = 100 + 1.5 * this.radius; - this.cons2.length = -200; - } - } else if (this.seePlayer.recall) { - this.lostPlayer(); - } - } - //if you don't recall player location rotate and draw to show where you are looking - if (!this.seePlayer.recall) { - this.torque = this.lookTorque * this.inertia; - //draw - const range = Math.PI * this.lookRange; - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range); - ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true); - ctx.fillStyle = "rgba(0,0,0,0.07)"; - ctx.fill(); - //spring to random place on map - const vertexCollision = function (v1, v1End, domain) { - for (let i = 0; i < domain.length; ++i) { - let vertices = domain[i].vertices; - const len = vertices.length - 1; - for (let j = 0; j < len; j++) { - results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[j], - v2: vertices[j + 1] - }; - } - } - } - results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2) { - best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[0], - v2: vertices[len] - }; - } - } - } - }; - const seeRange = 3000; - if (!(game.cycle % (this.seePlayerFreq * 10))) { - best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - const look = { - x: this.position.x + seeRange * Math.cos(this.angle), - y: this.position.y + seeRange * Math.sin(this.angle) - }; - vertexCollision(this.position, look, map); - if (best.dist2 != Infinity) { - this.springTarget.x = best.x; - this.springTarget.y = best.y; - this.cons.length = 100 + 1.5 * this.radius; - this.cons2.length = 100 + 1.5 * this.radius; - } - } - if (!((game.cycle + this.seePlayerFreq * 5) % (this.seePlayerFreq * 10))) { - best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - const look = { - x: this.position.x + seeRange * Math.cos(this.angle), - y: this.position.y + seeRange * Math.sin(this.angle) - }; - vertexCollision(this.position, look, map); - if (best.dist2 != Infinity) { - this.springTarget2.x = best.x; - this.springTarget2.y = best.y; - this.cons.length = 100 + 1.5 * this.radius; - this.cons2.length = 100 + 1.5 * this.radius; - } - } - } - }, - alertNearByMobs() { - //this.alertRange2 is set at the very bottom of this mobs, after mob is made - for (let i = 0; i < mob.length; i++) { - if (!mob[i].seePlayer.recall && Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, mob[i].position)) < this.alertRange2) { - mob[i].locatePlayer(); - } - } - //add alert to draw queue - // game.drawList.push({ - // x: this.position.x, - // y: this.position.y, - // radius: Math.sqrt(this.alertRange2), - // color: "rgba(0,0,0,0.02)", - // time: game.drawTime - // }); - }, - zoom() { - this.zoomMode--; - if (this.zoomMode > 150) { - this.drawTrail(); - if (this.seePlayer.recall) { - //attraction to player - const forceMag = this.accelMag * this.mass; - const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); - this.force.x += forceMag * Math.cos(angle); - this.force.y += forceMag * Math.sin(angle); - } - } else if (this.zoomMode < 0) { - this.zoomMode = 300; - this.setupTrail(); - } - }, - setupTrail() { - this.trail = []; - for (let i = 0; i < this.trailLength; ++i) { - this.trail.push({ - x: this.position.x, - y: this.position.y - }); - } - }, - drawTrail() { - //dont' forget to run setupTrail() after mob spawn - const t = this.trail; - const len = t.length; - t.pop(); - t.unshift({ - x: this.position.x, - y: this.position.y - }); - //draw - ctx.strokeStyle = this.trailFill; - ctx.beginPath(); - // ctx.moveTo(t[0].x, t[0].y); - // ctx.lineTo(t[0].x, t[0].y); - // ctx.globalAlpha = 0.2; - // ctx.lineWidth = this.radius * 3; - // ctx.stroke(); - ctx.globalAlpha = 0.5 / len; - ctx.lineWidth = this.radius * 1.95; - for (let i = 0; i < len; ++i) { - // ctx.lineWidth *= 0.96; - ctx.lineTo(t[i].x, t[i].y); - ctx.stroke(); - } - ctx.globalAlpha = 1; - }, - curl(range = 1000, mag = -10) { - //cause all mobs, and bodies to rotate in a circle - applyCurl = function (center, array) { - for (let i = 0; i < array.length; ++i) { - const sub = Matter.Vector.sub(center, array[i].position) - const radius2 = Matter.Vector.magnitudeSquared(sub); - - //if too close, like center mob or shield, don't curl // if too far don't curl - if (radius2 < range * range && radius2 > 10000) { - const curlVector = Matter.Vector.mult(Matter.Vector.perp(Matter.Vector.normalise(sub)), mag) - //apply curl force - Matter.Body.setVelocity(array[i], { - x: array[i].velocity.x * 0.94 + curlVector.x * 0.06, - y: array[i].velocity.y * 0.94 + curlVector.y * 0.06 - }) - // //draw curl - // ctx.beginPath(); - // ctx.moveTo(array[i].position.x, array[i].position.y); - // ctx.lineTo(array[i].position.x + curlVector.x * 10, array[i].position.y + curlVector.y * 10); - // ctx.lineWidth = 2; - // ctx.strokeStyle = "#000"; - // ctx.stroke(); - } - } - } - applyCurl(this.position, mob); - applyCurl(this.position, body); - applyCurl(this.position, powerUp); - // applyCurl(this.position, bullet); // too powerful, just stops all bullets need to write a curl function just for bullets - // applyCurl(this.position, [player]); - - //draw limit - // ctx.beginPath(); - // ctx.arc(this.position.x, this.position.y, range, 0, 2 * Math.PI); - // ctx.fillStyle = "rgba(55,255,255, 0.1)"; - // ctx.fill(); - }, - pullPlayer() { - if (this.seePlayer.yes && Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, player.position)) < 1000000) { - const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); - player.force.x -= game.accelScale * 1.13 * Math.cos(angle) * (mech.onGround ? 2 * player.mass * game.g : player.mass * game.g); - player.force.y -= game.accelScale * 0.84 * player.mass * game.g * Math.sin(angle); - - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(mech.pos.x, mech.pos.y); - ctx.lineWidth = Math.min(60, this.radius * 2); - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(); - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.3)"; - ctx.fill(); - } - }, - repelBullets() { - if (this.seePlayer.yes) { - ctx.lineWidth = "8"; - ctx.strokeStyle = this.fill; - ctx.beginPath(); - for (let i = 0, len = bullet.length; i < len; ++i) { - const dx = bullet[i].position.x - this.position.x; - const dy = bullet[i].position.y - this.position.y; - const dist = Math.sqrt(dx * dx + dy * dy); - if (dist < 500) { - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(bullet[i].position.x, bullet[i].position.y); - const angle = Math.atan2(dy, dx); - const mag = (1500 * bullet[i].mass * game.g) / dist; - bullet[i].force.x += mag * Math.cos(angle); - bullet[i].force.y += mag * Math.sin(angle); - } - } - ctx.stroke(); - } - }, - attraction() { - //accelerate towards the player - if (this.seePlayer.recall) { - // && dx * dx + dy * dy < 2000000) { - const forceMag = this.accelMag * this.mass; - const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); - this.force.x += forceMag * Math.cos(angle); - this.force.y += forceMag * Math.sin(angle); - } - }, - repulsionRange: 500000, - repulsion() { - //accelerate towards the player - if (this.seePlayer.recall && this.distanceToPlayer2() < this.repulsionRange) { - // && dx * dx + dy * dy < 2000000) { - const forceMag = this.accelMag * this.mass; - const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); - this.force.x -= 2 * forceMag * Math.cos(angle); - this.force.y -= 2 * forceMag * Math.sin(angle); // - 0.0007 * this.mass; //antigravity - } - }, - hop() { - //accelerate towards the player after a delay - if (this.cd < game.cycle && this.seePlayer.recall && this.speed < 1) { - this.cd = game.cycle + this.delay; - const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass; - const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); - this.force.x += forceMag * Math.cos(angle); - this.force.y += forceMag * Math.sin(angle) - 0.04 * this.mass; //antigravity - } - }, - hoverOverPlayer() { - if (this.seePlayer.recall) { - // vertical positioning - const rangeY = 250; - if (this.position.y > this.seePlayer.position.y - this.hoverElevation + rangeY) { - this.force.y -= this.accelMag * this.mass; - } else if (this.position.y < this.seePlayer.position.y - this.hoverElevation - rangeY) { - this.force.y += this.accelMag * this.mass; - } - // horizontal positioning - const rangeX = 150; - if (this.position.x > this.seePlayer.position.x + this.hoverXOff + rangeX) { - this.force.x -= this.accelMag * this.mass; - } else if (this.position.x < this.seePlayer.position.x + this.hoverXOff - rangeX) { - this.force.x += this.accelMag * this.mass; - } - } - // else { - // this.gravity(); - // } - }, - grow() { - if (!mech.isBodiesAsleep) { - if (this.seePlayer.recall) { - if (this.radius < 80) { - const scale = 1.01; - Matter.Body.scale(this, scale, scale); - this.radius *= scale; - // this.torque = -0.00002 * this.inertia; - this.fill = `hsl(144, ${this.radius}%, 50%)`; - } - } else { - if (this.radius > 15) { - const scale = 0.99; - Matter.Body.scale(this, scale, scale); - this.radius *= scale; - this.fill = `hsl(144, ${this.radius}%, 50%)`; - } - } - } - }, - search() { - //be sure to declare searchTarget in mob spawn - //accelerate towards the searchTarget - if (!this.seePlayer.recall) { - const newTarget = function (that) { - if (Math.random() < 0.025) { - that.searchTarget = player.position; //chance to target player - } else { - //target random body - that.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; - } - }; - - const sub = Matter.Vector.sub(this.searchTarget, this.position); - if (Matter.Vector.magnitude(sub) > this.radius * 2) { - // ctx.beginPath(); - // ctx.strokeStyle = "#aaa"; - // ctx.moveTo(this.position.x, this.position.y); - // ctx.lineTo(this.searchTarget.x,this.searchTarget.y); - // ctx.stroke(); - //accelerate at 0.1 of normal acceleration - this.force = Matter.Vector.mult(Matter.Vector.normalise(sub), this.accelMag * this.mass * 0.2); - } else { - //after reaching random target switch to new target - newTarget(this); - } - //switch to a new target after a while - if (!(game.cycle % (this.seePlayerFreq * 15))) { - newTarget(this); - } - } - }, - strike() { - //teleport to player when close enough on CD - if (this.seePlayer.recall && this.cd < game.cycle) { - const dist = Matter.Vector.sub(this.seePlayer.position, this.position); - const distMag = Matter.Vector.magnitude(dist); - if (distMag < 400) { - this.cd = game.cycle + this.delay; - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - Matter.Body.translate(this, Matter.Vector.mult(Matter.Vector.normalise(dist), distMag - 20 - radius)); - ctx.lineTo(this.position.x, this.position.y); - ctx.lineWidth = radius * 2; - ctx.strokeStyle = this.fill; //"rgba(0,0,0,0.5)"; //'#000' - ctx.stroke(); - } - } - }, - blink() { - //teleport towards player as a way to move - if (this.seePlayer.recall && !(game.cycle % this.blinkRate)) { - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - const dist = Matter.Vector.sub(this.seePlayer.position, this.position); - const distMag = Matter.Vector.magnitude(dist); - const unitVector = Matter.Vector.normalise(dist); - const rando = (Math.random() - 0.5) * 50; - if (distMag < this.blinkLength) { - Matter.Body.translate(this, Matter.Vector.mult(unitVector, distMag + rando)); - } else { - Matter.Body.translate(this, Matter.Vector.mult(unitVector, this.blinkLength + rando)); - } - ctx.lineTo(this.position.x, this.position.y); - ctx.lineWidth = radius * 2; - ctx.strokeStyle = this.stroke; //"rgba(0,0,0,0.5)"; //'#000' - ctx.stroke(); - } - }, - drift() { - //teleport towards player as a way to move - if (this.seePlayer.recall && !(game.cycle % this.blinkRate)) { - // && !mech.lookingAtMob(this,0.5)){ - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - const dist = Matter.Vector.sub(this.seePlayer.position, this.position); - const distMag = Matter.Vector.magnitude(dist); - const vector = Matter.Vector.mult(Matter.Vector.normalise(dist), this.blinkLength); - if (distMag < this.blinkLength) { - Matter.Body.setPosition(this, this.seePlayer.position); - Matter.Body.translate(this, { - x: (Math.random() - 0.5) * 50, - y: (Math.random() - 0.5) * 50 - }); - } else { - vector.x += (Math.random() - 0.5) * 200; - vector.y += (Math.random() - 0.5) * 200; - Matter.Body.translate(this, vector); - } - ctx.lineTo(this.position.x, this.position.y); - ctx.lineWidth = radius * 2; - ctx.strokeStyle = this.stroke; - ctx.stroke(); - } - }, - bomb() { - //throw a mob/bullet at player - if ( - !(game.cycle % this.fireFreq) && - Math.abs(this.position.x - this.seePlayer.position.x) < 400 && //above player - Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && //see player - Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0 - ) { - spawn.bullet(this.position.x, this.position.y + this.radius * 0.5, 10 + Math.ceil(this.radius / 15), 5); - //add spin and speed - Matter.Body.setAngularVelocity(mob[mob.length - 1], (Math.random() - 0.5) * 0.5); - Matter.Body.setVelocity(mob[mob.length - 1], { - x: this.velocity.x, - y: this.velocity.y - }); - //spin for mob as well - Matter.Body.setAngularVelocity(this, (Math.random() - 0.5) * 0.25); - } - }, - fire() { - 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 = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position)); - this.fireDir.y -= Math.abs(this.seePlayer.position.x - this.position.x) / 1600; //gives the bullet an arc - this.fireAngle = Math.atan2(this.fireDir.y, this.fireDir.x); - } - //rotate towards fireAngle - const angle = this.angle + Math.PI / 2; - c = Math.cos(angle) * this.fireDir.x + Math.sin(angle) * this.fireDir.y; - const threshold = 0.1; - if (c > threshold) { - this.torque += 0.000004 * this.inertia; - } else if (c < -threshold) { - this.torque -= 0.000004 * this.inertia; - } else if (this.noseLength > 1.5) { - //fire - spawn.bullet(this.vertices[1].x, this.vertices[1].y, 5 + Math.ceil(this.radius / 15), 5); - const v = 15; - Matter.Body.setVelocity(mob[mob.length - 1], { - x: this.velocity.x + this.fireDir.x * v + Math.random(), - y: this.velocity.y + this.fireDir.y * v + Math.random() - }); - this.noseLength = 0; - // recoil - this.force.x -= 0.005 * this.fireDir.x * this.mass; - this.force.y -= 0.005 * this.fireDir.y * this.mass; - } - if (this.noseLength < 1.5) this.noseLength += this.fireFreq; - setNoseShape(); - } else if (this.noseLength > 0.1) { - this.noseLength -= this.fireFreq / 2; - setNoseShape(); - } - // else if (this.noseLength < -0.1) { - // this.noseLength += this.fireFreq / 4; - // setNoseShape(); - // } - } - }, - turnToFacePlayer() { - //turn to face player - const dx = player.position.x - this.position.x; - const dy = -player.position.y + this.position.y; - const dist = this.distanceToPlayer(); - const angle = this.angle + Math.PI / 2; - c = Math.cos(angle) * dx - Math.sin(angle) * dy; - // if (c > 0.04) { - // Matter.Body.rotate(this, 0.01); - // } else if (c < 0.04) { - // Matter.Body.rotate(this, -0.01); - // } - if (c > 0.04 * dist) { - this.torque += 0.002 * this.mass; - } else if (c < 0.04) { - this.torque -= 0.002 * this.mass; - } - }, - facePlayer() { - const unitVector = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position)); - const angle = Math.atan2(unitVector.y, unitVector.x); - Matter.Body.setAngle(this, angle - Math.PI); - }, - explode() { - mech.damage(Math.min(Math.max(0.02 * Math.sqrt(this.mass), 0.01), 0.35) * game.dmgScale); - this.dropPowerUp = false; - this.death(); //death with no power up or body - }, - timeLimit() { - if (!mech.isBodiesAsleep) { - this.timeLeft--; - if (this.timeLeft < 0) { - this.dropPowerUp = false; - this.death(); //death with no power up - } - } - }, - healthBar() { - //draw health bar - if (this.seePlayer.recall) { - // && this.health < 1 - const h = this.radius * 0.3; - const w = this.radius * 2; - const x = this.position.x - w / 2; - const y = this.position.y - w * 0.7; - ctx.fillStyle = "rgba(100, 100, 100, 0.3)"; - ctx.fillRect(x, y, w, h); - ctx.fillStyle = "rgba(255,0,0,0.7)"; - ctx.fillRect(x, y, w * this.health, h); - } - }, - damage(dmg) { - dmg /= Math.sqrt(this.mass) - if (b.isModLowHealthDmg) dmg *= (3 / (2 + mech.health)) //up to 50% dmg at zero player health - if (b.isModFarAwayDmg) dmg *= 1 + Math.sqrt(Math.max(1000, Math.min(3500, this.distanceToPlayer())) - 1000) * 0.01 //up to 50% dmg at max range of 3500 - if (dmg !== Infinity) { - if (b.modEnergySiphon) mech.fieldMeter += Math.min(this.health, dmg) * b.modEnergySiphon - if (b.modHealthDrain) mech.addHealth(Math.min(this.health, dmg) * b.modHealthDrain) - } - this.health -= dmg - //this.fill = this.color + this.health + ')'; - if (this.health < 0.05) this.death(); - this.onDamage(this); //custom damage effects - }, - onDamage() { - // a placeholder for custom effects on mob damage - //to use declare custom method in mob spawn - }, - onDeath() { - // a placeholder for custom effects on mob death - // to use declare custom method in mob spawn - }, - leaveBody: true, - dropPowerUp: true, - death() { - this.onDeath(this); //custom death effects - this.removeConsBB(); - this.alive = false; - if (this.dropPowerUp) { - powerUps.spawnRandomPowerUp(this.position.x, this.position.y, this.mass, radius); - if (Math.random() < b.modSpores) { - for (let i = 0, len = Math.floor(3 + this.mass * Math.random()); i < len; i++) { - b.spore(this) //spawn drone - } - } - } - - }, - removeConsBB() { - for (let i = 0, len = consBB.length; i < len; ++i) { - if (consBB[i].bodyA === this) { - if (consBB[i].bodyB.shield) { - consBB[i].bodyB.do = function () { - this.death(); - }; - } - consBB[i].bodyA = consBB[i].bodyB; - consBB.splice(i, 1); - this.removeConsBB(); - break; - } else if (consBB[i].bodyB === this) { - if (consBB[i].bodyA.shield) { - consBB[i].bodyA.do = function () { - this.death(); - }; - } - consBB[i].bodyB = consBB[i].bodyA; - consBB.splice(i, 1); - this.removeConsBB(); - break; - } - } - }, - removeCons() { - for (let i = 0, len = cons.length; i < len; ++i) { - if (cons[i].bodyA === this) { - cons[i].bodyA = cons[i].bodyB; - cons.splice(i, 1); - this.removeCons(); - break; - } else if (cons[i].bodyB === this) { - cons[i].bodyB = cons[i].bodyA; - cons.splice(i, 1); - this.removeCons(); - break; - } - } - }, - //replace dead mob with a regular body - replace(i) { - if (this.leaveBody) { - const len = body.length; - body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, this.vertices); - Matter.Body.setVelocity(body[len], this.velocity); - Matter.Body.setAngularVelocity(body[len], this.angularVelocity); - body[len].collisionFilter.category = 0x010000; - body[len].collisionFilter.mask = 0x011111; - // body[len].collisionFilter.category = body[len].collisionFilter.category //0x000001; - // body[len].collisionFilter.mask = body[len].collisionFilter.mask //0x011111; - - //large mobs or too many bodies go intangible and fall until removed from game to help performance - if (body[len].mass > 10 || 40 + 30 * Math.random() < body.length) { - body[len].collisionFilter.mask = 0x001100; - } - body[len].classType = "body"; - World.add(engine.world, body[len]); //add to world - } - Matter.World.remove(engine.world, this); - mob.splice(i, 1); - } - }); - mob[i].alertRange2 = Math.pow(mob[i].radius * 3.5 + 550, 2); - World.add(engine.world, mob[i]); //add to world - } +//create array of mobs +let mob = []; +//method to populate the array above +const mobs = { + loop() { + let i = mob.length; + while (i--) { + if (mob[i].alive) { + mob[i].do(); + } else { + mob[i].replace(i); //removing mob and replace with body, this is done here to avoid an array index bug with drawing I think + } + } + }, + draw() { + ctx.lineWidth = 2; + let i = mob.length; + while (i--) { + ctx.beginPath(); + const vertices = mob[i].vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1, len = vertices.length; j < len; ++j) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.fillStyle = mob[i].fill; + ctx.strokeStyle = mob[i].stroke; + ctx.fill(); + ctx.stroke(); + } + }, + // alert(range) { + // range = range * range; + // for (let i = 0; i < mob.length; i++) { + // if (mob[i].distanceToPlayer2() < range) mob[i].locatePlayer(); + // } + // }, + // startle(amount) { + // for (let i = 0; i < mob.length; i++) { + // if (!mob[i].seePlayer.yes) { + // mob[i].force.x += amount * mob[i].mass * (Math.random() - 0.5); + // mob[i].force.y += amount * mob[i].mass * (Math.random() - 0.5); + // } + // } + // }, + //********************************************************************************************** + //********************************************************************************************** + spawn(xPos, yPos, sides, radius, color) { + let i = mob.length; + mob[i] = Matter.Bodies.polygon(xPos, yPos, sides, radius, { + //inertia: Infinity, //prevents rotation + mob: true, + density: 0.001, + //friction: 0, + frictionAir: 0.005, + //frictionStatic: 0, + restitution: 0.5, + collisionFilter: { + group: 0, + category: 0x000010, + mask: 0x011111 + }, + onHit: undefined, + alive: true, + index: i, + health: 1, + accelMag: 0.001, + cd: 0, //game cycle when cooldown will be over + delay: 60, //static: time between cooldowns + fill: color, + stroke: "#000", + seePlayer: { + yes: false, + recall: 0, + position: { + x: xPos, + y: yPos + } + }, + radius: radius, + spawnPos: { + x: xPos, + y: yPos + }, + seeAtDistance2: 4000000, //sqrt(4000000) = 2000 = max seeing range + distanceToPlayer() { + const dx = this.position.x - player.position.x; + const dy = this.position.y - player.position.y; + return Math.sqrt(dx * dx + dy * dy); + }, + distanceToPlayer2() { + const dx = this.position.x - player.position.x; + const dy = this.position.y - player.position.y; + return dx * dx + dy * dy; + }, + gravity() { + this.force.y += this.mass * this.g; + }, + seePlayerFreq: Math.round((30 + 30 * Math.random()) * game.lookFreqScale), //how often NPC checks to see where player is, lower numbers have better vision + foundPlayer() { + this.locatePlayer(); + if (!this.seePlayer.yes) { + this.alertNearByMobs(); + this.seePlayer.yes = true; + } + }, + lostPlayer() { + this.seePlayer.yes = false; + this.seePlayer.recall -= this.seePlayerFreq; + if (this.seePlayer.recall < 0) this.seePlayer.recall = 0; + }, + memory: 120, //default time to remember player's location + locatePlayer() { + if (!mech.isStealth) { + // updates mob's memory of player location + this.seePlayer.recall = this.memory + Math.round(this.memory * Math.random()); //seconds before mob falls a sleep + this.seePlayer.position.x = player.position.x; + this.seePlayer.position.y = player.position.y; + } + }, + // locatePlayerByDist() { + // if (this.distanceToPlayer2() < this.locateRange) { + // this.locatePlayer(); + // } + // }, + seePlayerCheck() { + if (!(game.cycle % this.seePlayerFreq)) { + if ( + this.distanceToPlayer2() < this.seeAtDistance2 && + Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && + Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0 + ) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + }, + seePlayerCheckByDistance() { + if (!(game.cycle % this.seePlayerFreq)) { + if (this.distanceToPlayer2() < this.seeAtDistance2) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + }, + seePlayerByDistOrLOS() { + if (!(game.cycle % this.seePlayerFreq)) { + if ( + this.distanceToPlayer2() < this.seeAtDistance2 || + (Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0) + ) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + }, + seePlayerByDistAndLOS() { + if (!(game.cycle % this.seePlayerFreq)) { + if ( + this.distanceToPlayer2() < this.seeAtDistance2 && + (Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0) + ) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + }, + isLookingAtPlayer(threshold) { + const diff = Matter.Vector.normalise(Matter.Vector.sub(player.position, this.position)); + //make a vector for the mob's direction of length 1 + const dir = { + x: Math.cos(this.angle), + y: Math.sin(this.angle) + }; + //the dot product of diff and dir will return how much over lap between the vectors + const dot = Matter.Vector.dot(dir, diff); + // console.log(Math.cos(dot)*180/Math.PI) + if (dot > threshold) { + return true; + } else { + return false; + } + }, + lookRange: 0.2 + Math.random() * 0.2, + lookTorque: 0.0000004 * (Math.random() > 0.5 ? -1 : 1), + seePlayerByLookingAt() { + if (!(game.cycle % this.seePlayerFreq) && (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange))) { + if ( + this.distanceToPlayer2() < this.seeAtDistance2 && + Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && + Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0 + ) { + this.foundPlayer(); + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + //if you don't recall player location rotate and draw to show where you are looking + if (!this.seePlayer.recall) { + this.torque = this.lookTorque * this.inertia; + //draw + const range = Math.PI * this.lookRange; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range); + ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true); + ctx.fillStyle = "rgba(0,0,0,0.07)"; + ctx.fill(); + } + }, + mechPosRange() { + return { + x: player.position.x, // + (Math.random() - 0.5) * 50, + y: player.position.y + (Math.random() - 0.5) * 110 + }; + //mob vision for testing + // ctx.beginPath(); + // ctx.lineWidth = "5"; + // ctx.strokeStyle = "#ff0"; + // ctx.moveTo(this.position.x, this.position.y); + // ctx.lineTo(targetPos.x, targetPos.y); + // ctx.stroke(); + // return targetPos; + }, + laserBeam() { + if (game.cycle % 7 && this.seePlayer.yes) { + ctx.setLineDash([125 * Math.random(), 125 * Math.random()]); + // ctx.lineDashOffset = 6*(game.cycle % 215); + if (this.distanceToPlayer() < this.laserRange) { + //if (Math.random()>0.2 && this.seePlayer.yes && this.distanceToPlayer2()<800000) { + if (b.isModTempResist) { + mech.damage(0.00006 * game.dmgScale); + } else { + mech.damage(0.0003 * game.dmgScale); + } + if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.004 + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mech.pos.x, mech.pos.y); + ctx.lineTo(mech.pos.x + (Math.random() - 0.5) * 3000, mech.pos.y + (Math.random() - 0.5) * 3000); + ctx.lineWidth = 2; + ctx.strokeStyle = "rgb(255,0,170)"; + ctx.stroke(); + + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(255,0,170,0.15)"; + ctx.fill(); + + } + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.laserRange * 0.9, 0, 2 * Math.PI); + ctx.strokeStyle = "rgba(255,0,170,0.5)"; + ctx.lineWidth = 1; + ctx.stroke(); + ctx.setLineDash([]); + ctx.fillStyle = "rgba(255,0,170,0.03)"; + ctx.fill(); + } + }, + laser() { + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + }; + if (this.seePlayer.recall) { + this.torque = this.lookTorque * this.inertia * 2; + + const seeRange = 2500; + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const look = { + x: this.position.x + seeRange * Math.cos(this.angle), + y: this.position.y + seeRange * Math.sin(this.angle) + }; + vertexCollision(this.position, look, map); + vertexCollision(this.position, look, body); + vertexCollision(this.position, look, [player]); + // hitting player + if (best.who === player) { + if (b.isModTempResist) { + dmg = 0.0008 * game.dmgScale; + } else { + dmg = 0.004 * game.dmgScale; + } + + mech.damage(dmg); + //draw damage + ctx.fillStyle = color; + ctx.beginPath(); + ctx.arc(best.x, best.y, dmg * 2000, 0, 2 * Math.PI); + ctx.fill(); + } + //draw beam + if (best.dist2 === Infinity) { + best = look; + } + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(best.x, best.y); + ctx.strokeStyle = "#f00"; // Purple path + ctx.lineWidth = 1; + ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([0, 0]); + } + }, + searchSpring() { + ctx.beginPath(); + ctx.arc(this.cons.pointA.x, this.cons.pointA.y, 6, 0, 2 * Math.PI); + ctx.arc(this.cons2.pointA.x, this.cons2.pointA.y, 6, 0, 2 * Math.PI); + // ctx.arc(this.cons.bodyB.position.x, this.cons.bodyB.position.y,6,0,2*Math.PI); + ctx.fillStyle = "#222"; + ctx.fill(); + + if (!(game.cycle % this.seePlayerFreq)) { + if ( + (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange)) && + this.distanceToPlayer2() < this.seeAtDistance2 && + Matter.Query.ray(map, this.position, player.position).length === 0 && + Matter.Query.ray(body, this.position, player.position).length === 0 + ) { + this.foundPlayer(); + if (!(game.cycle % (this.seePlayerFreq * 2))) { + this.springTarget.x = this.seePlayer.position.x; + this.springTarget.y = this.seePlayer.position.y; + this.cons.length = -200; + this.cons2.length = 100 + 1.5 * this.radius; + } else { + this.springTarget2.x = this.seePlayer.position.x; + this.springTarget2.y = this.seePlayer.position.y; + this.cons.length = 100 + 1.5 * this.radius; + this.cons2.length = -200; + } + } else if (this.seePlayer.recall) { + this.lostPlayer(); + } + } + //if you don't recall player location rotate and draw to show where you are looking + if (!this.seePlayer.recall) { + this.torque = this.lookTorque * this.inertia; + //draw + const range = Math.PI * this.lookRange; + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range); + ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true); + ctx.fillStyle = "rgba(0,0,0,0.07)"; + ctx.fill(); + //spring to random place on map + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[j], + v2: vertices[j + 1] + }; + } + } + } + results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + }; + const seeRange = 3000; + if (!(game.cycle % (this.seePlayerFreq * 10))) { + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const look = { + x: this.position.x + seeRange * Math.cos(this.angle), + y: this.position.y + seeRange * Math.sin(this.angle) + }; + vertexCollision(this.position, look, map); + if (best.dist2 != Infinity) { + this.springTarget.x = best.x; + this.springTarget.y = best.y; + this.cons.length = 100 + 1.5 * this.radius; + this.cons2.length = 100 + 1.5 * this.radius; + } + } + if (!((game.cycle + this.seePlayerFreq * 5) % (this.seePlayerFreq * 10))) { + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const look = { + x: this.position.x + seeRange * Math.cos(this.angle), + y: this.position.y + seeRange * Math.sin(this.angle) + }; + vertexCollision(this.position, look, map); + if (best.dist2 != Infinity) { + this.springTarget2.x = best.x; + this.springTarget2.y = best.y; + this.cons.length = 100 + 1.5 * this.radius; + this.cons2.length = 100 + 1.5 * this.radius; + } + } + } + }, + alertNearByMobs() { + //this.alertRange2 is set at the very bottom of this mobs, after mob is made + for (let i = 0; i < mob.length; i++) { + if (!mob[i].seePlayer.recall && Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, mob[i].position)) < this.alertRange2) { + mob[i].locatePlayer(); + } + } + //add alert to draw queue + // game.drawList.push({ + // x: this.position.x, + // y: this.position.y, + // radius: Math.sqrt(this.alertRange2), + // color: "rgba(0,0,0,0.02)", + // time: game.drawTime + // }); + }, + zoom() { + this.zoomMode--; + if (this.zoomMode > 150) { + this.drawTrail(); + if (this.seePlayer.recall) { + //attraction to player + const forceMag = this.accelMag * this.mass; + const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle); + } + } else if (this.zoomMode < 0) { + this.zoomMode = 300; + this.setupTrail(); + } + }, + setupTrail() { + this.trail = []; + for (let i = 0; i < this.trailLength; ++i) { + this.trail.push({ + x: this.position.x, + y: this.position.y + }); + } + }, + drawTrail() { + //dont' forget to run setupTrail() after mob spawn + const t = this.trail; + const len = t.length; + t.pop(); + t.unshift({ + x: this.position.x, + y: this.position.y + }); + //draw + ctx.strokeStyle = this.trailFill; + ctx.beginPath(); + // ctx.moveTo(t[0].x, t[0].y); + // ctx.lineTo(t[0].x, t[0].y); + // ctx.globalAlpha = 0.2; + // ctx.lineWidth = this.radius * 3; + // ctx.stroke(); + ctx.globalAlpha = 0.5 / len; + ctx.lineWidth = this.radius * 1.95; + for (let i = 0; i < len; ++i) { + // ctx.lineWidth *= 0.96; + ctx.lineTo(t[i].x, t[i].y); + ctx.stroke(); + } + ctx.globalAlpha = 1; + }, + curl(range = 1000, mag = -10) { + //cause all mobs, and bodies to rotate in a circle + applyCurl = function (center, array) { + for (let i = 0; i < array.length; ++i) { + const sub = Matter.Vector.sub(center, array[i].position) + const radius2 = Matter.Vector.magnitudeSquared(sub); + + //if too close, like center mob or shield, don't curl // if too far don't curl + if (radius2 < range * range && radius2 > 10000) { + const curlVector = Matter.Vector.mult(Matter.Vector.perp(Matter.Vector.normalise(sub)), mag) + //apply curl force + Matter.Body.setVelocity(array[i], { + x: array[i].velocity.x * 0.94 + curlVector.x * 0.06, + y: array[i].velocity.y * 0.94 + curlVector.y * 0.06 + }) + // //draw curl + // ctx.beginPath(); + // ctx.moveTo(array[i].position.x, array[i].position.y); + // ctx.lineTo(array[i].position.x + curlVector.x * 10, array[i].position.y + curlVector.y * 10); + // ctx.lineWidth = 2; + // ctx.strokeStyle = "#000"; + // ctx.stroke(); + } + } + } + applyCurl(this.position, mob); + applyCurl(this.position, body); + applyCurl(this.position, powerUp); + // applyCurl(this.position, bullet); // too powerful, just stops all bullets need to write a curl function just for bullets + // applyCurl(this.position, [player]); + + //draw limit + // ctx.beginPath(); + // ctx.arc(this.position.x, this.position.y, range, 0, 2 * Math.PI); + // ctx.fillStyle = "rgba(55,255,255, 0.1)"; + // ctx.fill(); + }, + pullPlayer() { + if (this.seePlayer.yes && Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, player.position)) < 1000000) { + const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + player.force.x -= game.accelScale * 1.13 * Math.cos(angle) * (mech.onGround ? 2 * player.mass * game.g : player.mass * game.g); + player.force.y -= game.accelScale * 0.84 * player.mass * game.g * Math.sin(angle); + + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mech.pos.x, mech.pos.y); + ctx.lineWidth = Math.min(60, this.radius * 2); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.stroke(); + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.3)"; + ctx.fill(); + } + }, + repelBullets() { + if (this.seePlayer.yes) { + ctx.lineWidth = "8"; + ctx.strokeStyle = this.fill; + ctx.beginPath(); + for (let i = 0, len = bullet.length; i < len; ++i) { + const dx = bullet[i].position.x - this.position.x; + const dy = bullet[i].position.y - this.position.y; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 500) { + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(bullet[i].position.x, bullet[i].position.y); + const angle = Math.atan2(dy, dx); + const mag = (1500 * bullet[i].mass * game.g) / dist; + bullet[i].force.x += mag * Math.cos(angle); + bullet[i].force.y += mag * Math.sin(angle); + } + } + ctx.stroke(); + } + }, + attraction() { + //accelerate towards the player + if (this.seePlayer.recall) { + // && dx * dx + dy * dy < 2000000) { + const forceMag = this.accelMag * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle); + } + }, + repulsionRange: 500000, + repulsion() { + //accelerate towards the player + if (this.seePlayer.recall && this.distanceToPlayer2() < this.repulsionRange) { + // && dx * dx + dy * dy < 2000000) { + const forceMag = this.accelMag * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x -= 2 * forceMag * Math.cos(angle); + this.force.y -= 2 * forceMag * Math.sin(angle); // - 0.0007 * this.mass; //antigravity + } + }, + hop() { + //accelerate towards the player after a delay + if (this.cd < game.cycle && this.seePlayer.recall && this.speed < 1) { + this.cd = game.cycle + this.delay; + const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle) - 0.04 * this.mass; //antigravity + } + }, + hoverOverPlayer() { + if (this.seePlayer.recall) { + // vertical positioning + const rangeY = 250; + if (this.position.y > this.seePlayer.position.y - this.hoverElevation + rangeY) { + this.force.y -= this.accelMag * this.mass; + } else if (this.position.y < this.seePlayer.position.y - this.hoverElevation - rangeY) { + this.force.y += this.accelMag * this.mass; + } + // horizontal positioning + const rangeX = 150; + if (this.position.x > this.seePlayer.position.x + this.hoverXOff + rangeX) { + this.force.x -= this.accelMag * this.mass; + } else if (this.position.x < this.seePlayer.position.x + this.hoverXOff - rangeX) { + this.force.x += this.accelMag * this.mass; + } + } + // else { + // this.gravity(); + // } + }, + grow() { + if (!mech.isBodiesAsleep) { + if (this.seePlayer.recall) { + if (this.radius < 80) { + const scale = 1.01; + Matter.Body.scale(this, scale, scale); + this.radius *= scale; + // this.torque = -0.00002 * this.inertia; + this.fill = `hsl(144, ${this.radius}%, 50%)`; + } + } else { + if (this.radius > 15) { + const scale = 0.99; + Matter.Body.scale(this, scale, scale); + this.radius *= scale; + this.fill = `hsl(144, ${this.radius}%, 50%)`; + } + } + } + }, + search() { + //be sure to declare searchTarget in mob spawn + //accelerate towards the searchTarget + if (!this.seePlayer.recall) { + const newTarget = function (that) { + if (Math.random() < 0.025) { + that.searchTarget = player.position; //chance to target player + } else { + //target random body + that.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; + } + }; + + const sub = Matter.Vector.sub(this.searchTarget, this.position); + if (Matter.Vector.magnitude(sub) > this.radius * 2) { + // ctx.beginPath(); + // ctx.strokeStyle = "#aaa"; + // ctx.moveTo(this.position.x, this.position.y); + // ctx.lineTo(this.searchTarget.x,this.searchTarget.y); + // ctx.stroke(); + //accelerate at 0.1 of normal acceleration + this.force = Matter.Vector.mult(Matter.Vector.normalise(sub), this.accelMag * this.mass * 0.2); + } else { + //after reaching random target switch to new target + newTarget(this); + } + //switch to a new target after a while + if (!(game.cycle % (this.seePlayerFreq * 15))) { + newTarget(this); + } + } + }, + strike() { + //teleport to player when close enough on CD + if (this.seePlayer.recall && this.cd < game.cycle) { + const dist = Matter.Vector.sub(this.seePlayer.position, this.position); + const distMag = Matter.Vector.magnitude(dist); + if (distMag < 400) { + this.cd = game.cycle + this.delay; + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + Matter.Body.translate(this, Matter.Vector.mult(Matter.Vector.normalise(dist), distMag - 20 - radius)); + ctx.lineTo(this.position.x, this.position.y); + ctx.lineWidth = radius * 2; + ctx.strokeStyle = this.fill; //"rgba(0,0,0,0.5)"; //'#000' + ctx.stroke(); + } + } + }, + blink() { + //teleport towards player as a way to move + if (this.seePlayer.recall && !(game.cycle % this.blinkRate)) { + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + const dist = Matter.Vector.sub(this.seePlayer.position, this.position); + const distMag = Matter.Vector.magnitude(dist); + const unitVector = Matter.Vector.normalise(dist); + const rando = (Math.random() - 0.5) * 50; + if (distMag < this.blinkLength) { + Matter.Body.translate(this, Matter.Vector.mult(unitVector, distMag + rando)); + } else { + Matter.Body.translate(this, Matter.Vector.mult(unitVector, this.blinkLength + rando)); + } + ctx.lineTo(this.position.x, this.position.y); + ctx.lineWidth = radius * 2; + ctx.strokeStyle = this.stroke; //"rgba(0,0,0,0.5)"; //'#000' + ctx.stroke(); + } + }, + drift() { + //teleport towards player as a way to move + if (this.seePlayer.recall && !(game.cycle % this.blinkRate)) { + // && !mech.lookingAtMob(this,0.5)){ + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + const dist = Matter.Vector.sub(this.seePlayer.position, this.position); + const distMag = Matter.Vector.magnitude(dist); + const vector = Matter.Vector.mult(Matter.Vector.normalise(dist), this.blinkLength); + if (distMag < this.blinkLength) { + Matter.Body.setPosition(this, this.seePlayer.position); + Matter.Body.translate(this, { + x: (Math.random() - 0.5) * 50, + y: (Math.random() - 0.5) * 50 + }); + } else { + vector.x += (Math.random() - 0.5) * 200; + vector.y += (Math.random() - 0.5) * 200; + Matter.Body.translate(this, vector); + } + ctx.lineTo(this.position.x, this.position.y); + ctx.lineWidth = radius * 2; + ctx.strokeStyle = this.stroke; + ctx.stroke(); + } + }, + bomb() { + //throw a mob/bullet at player + if ( + !(game.cycle % this.fireFreq) && + Math.abs(this.position.x - this.seePlayer.position.x) < 400 && //above player + Matter.Query.ray(map, this.position, this.mechPosRange()).length === 0 && //see player + Matter.Query.ray(body, this.position, this.mechPosRange()).length === 0 + ) { + spawn.bullet(this.position.x, this.position.y + this.radius * 0.5, 10 + Math.ceil(this.radius / 15), 5); + //add spin and speed + Matter.Body.setAngularVelocity(mob[mob.length - 1], (Math.random() - 0.5) * 0.5); + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x, + y: this.velocity.y + }); + //spin for mob as well + Matter.Body.setAngularVelocity(this, (Math.random() - 0.5) * 0.25); + } + }, + fire() { + 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 = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position)); + this.fireDir.y -= Math.abs(this.seePlayer.position.x - this.position.x) / 1600; //gives the bullet an arc + this.fireAngle = Math.atan2(this.fireDir.y, this.fireDir.x); + } + //rotate towards fireAngle + const angle = this.angle + Math.PI / 2; + c = Math.cos(angle) * this.fireDir.x + Math.sin(angle) * this.fireDir.y; + const threshold = 0.1; + if (c > threshold) { + this.torque += 0.000004 * this.inertia; + } else if (c < -threshold) { + this.torque -= 0.000004 * this.inertia; + } else if (this.noseLength > 1.5) { + //fire + spawn.bullet(this.vertices[1].x, this.vertices[1].y, 5 + Math.ceil(this.radius / 15), 5); + const v = 15; + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + this.fireDir.x * v + Math.random(), + y: this.velocity.y + this.fireDir.y * v + Math.random() + }); + this.noseLength = 0; + // recoil + this.force.x -= 0.005 * this.fireDir.x * this.mass; + this.force.y -= 0.005 * this.fireDir.y * this.mass; + } + if (this.noseLength < 1.5) this.noseLength += this.fireFreq; + setNoseShape(); + } else if (this.noseLength > 0.1) { + this.noseLength -= this.fireFreq / 2; + setNoseShape(); + } + // else if (this.noseLength < -0.1) { + // this.noseLength += this.fireFreq / 4; + // setNoseShape(); + // } + } + }, + turnToFacePlayer() { + //turn to face player + const dx = player.position.x - this.position.x; + const dy = -player.position.y + this.position.y; + const dist = this.distanceToPlayer(); + const angle = this.angle + Math.PI / 2; + c = Math.cos(angle) * dx - Math.sin(angle) * dy; + // if (c > 0.04) { + // Matter.Body.rotate(this, 0.01); + // } else if (c < 0.04) { + // Matter.Body.rotate(this, -0.01); + // } + if (c > 0.04 * dist) { + this.torque += 0.002 * this.mass; + } else if (c < 0.04) { + this.torque -= 0.002 * this.mass; + } + }, + facePlayer() { + const unitVector = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position)); + const angle = Math.atan2(unitVector.y, unitVector.x); + Matter.Body.setAngle(this, angle - Math.PI); + }, + explode() { + mech.damage(Math.min(Math.max(0.02 * Math.sqrt(this.mass), 0.01), 0.35) * game.dmgScale); + this.dropPowerUp = false; + this.death(); //death with no power up or body + }, + timeLimit() { + if (!mech.isBodiesAsleep) { + this.timeLeft--; + if (this.timeLeft < 0) { + this.dropPowerUp = false; + this.death(); //death with no power up + } + } + }, + healthBar() { + //draw health bar + if (this.seePlayer.recall) { + // && this.health < 1 + const h = this.radius * 0.3; + const w = this.radius * 2; + const x = this.position.x - w / 2; + const y = this.position.y - w * 0.7; + ctx.fillStyle = "rgba(100, 100, 100, 0.3)"; + ctx.fillRect(x, y, w, h); + ctx.fillStyle = "rgba(255,0,0,0.7)"; + ctx.fillRect(x, y, w * this.health, h); + } + }, + damage(dmg) { + dmg /= Math.sqrt(this.mass) + if (b.isModLowHealthDmg) dmg *= (3 / (2 + mech.health)) //up to 50% dmg at zero player health + if (b.isModFarAwayDmg) dmg *= 1 + Math.sqrt(Math.max(1000, Math.min(3500, this.distanceToPlayer())) - 1000) * 0.01 //up to 50% dmg at max range of 3500 + if (dmg !== Infinity) { + if (b.modEnergySiphon) mech.fieldMeter += Math.min(this.health, dmg) * b.modEnergySiphon + if (b.modHealthDrain) mech.addHealth(Math.min(this.health, dmg) * b.modHealthDrain) + } + this.health -= dmg + //this.fill = this.color + this.health + ')'; + if (this.health < 0.05) this.death(); + this.onDamage(this); //custom damage effects + }, + onDamage() { + // a placeholder for custom effects on mob damage + //to use declare custom method in mob spawn + }, + onDeath() { + // a placeholder for custom effects on mob death + // to use declare custom method in mob spawn + }, + leaveBody: true, + dropPowerUp: true, + death() { + this.onDeath(this); //custom death effects + this.removeConsBB(); + this.alive = false; + if (this.dropPowerUp) { + powerUps.spawnRandomPowerUp(this.position.x, this.position.y, this.mass, radius); + if (Math.random() < b.modSpores) { + for (let i = 0, len = Math.floor(3 + this.mass * Math.random()); i < len; i++) { + b.spore(this) //spawn drone + } + } + } + + }, + removeConsBB() { + for (let i = 0, len = consBB.length; i < len; ++i) { + if (consBB[i].bodyA === this) { + if (consBB[i].bodyB.shield) { + consBB[i].bodyB.do = function () { + this.death(); + }; + } + consBB[i].bodyA = consBB[i].bodyB; + consBB.splice(i, 1); + this.removeConsBB(); + break; + } else if (consBB[i].bodyB === this) { + if (consBB[i].bodyA.shield) { + consBB[i].bodyA.do = function () { + this.death(); + }; + } + consBB[i].bodyB = consBB[i].bodyA; + consBB.splice(i, 1); + this.removeConsBB(); + break; + } + } + }, + removeCons() { + for (let i = 0, len = cons.length; i < len; ++i) { + if (cons[i].bodyA === this) { + cons[i].bodyA = cons[i].bodyB; + cons.splice(i, 1); + this.removeCons(); + break; + } else if (cons[i].bodyB === this) { + cons[i].bodyB = cons[i].bodyA; + cons.splice(i, 1); + this.removeCons(); + break; + } + } + }, + //replace dead mob with a regular body + replace(i) { + if (this.leaveBody) { + const len = body.length; + body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, this.vertices); + Matter.Body.setVelocity(body[len], this.velocity); + Matter.Body.setAngularVelocity(body[len], this.angularVelocity); + body[len].collisionFilter.category = 0x010000; + body[len].collisionFilter.mask = 0x011111; + // body[len].collisionFilter.category = body[len].collisionFilter.category //0x000001; + // body[len].collisionFilter.mask = body[len].collisionFilter.mask //0x011111; + + //large mobs or too many bodies go intangible and fall until removed from game to help performance + if (body[len].mass > 10 || 40 + 30 * Math.random() < body.length) { + body[len].collisionFilter.mask = 0x001100; + } + body[len].classType = "body"; + World.add(engine.world, body[len]); //add to world + } + Matter.World.remove(engine.world, this); + mob.splice(i, 1); + } + }); + mob[i].alertRange2 = Math.pow(mob[i].radius * 3.5 + 550, 2); + World.add(engine.world, mob[i]); //add to world + } }; \ No newline at end of file diff --git a/js/player.js b/js/player.js index 11b80b6..a6bf5ae 100644 --- a/js/player.js +++ b/js/player.js @@ -1,1516 +1,1516 @@ -//global player variables for use in matter.js physics -let player, jumpSensor, playerBody, playerHead, headSensor; - -// player Object Prototype ********************************************* -const mech = { - spawn() { - //load player in matter.js physic engine - // let vector = Vertices.fromPath("0 40 50 40 50 115 0 115 30 130 20 130"); //player as a series of vertices - 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: 0x001000, - mask: 0x010011 - }, - death() { - mech.death(); - } - }); - Matter.Body.setMass(player, mech.mass); - World.add(engine.world, [player]); - - mech.holdConstraint = Constraint.create({ - //holding body constraint - pointA: { - x: 0, - y: 0 - }, - bodyB: jumpSensor, //setting constraint to jump sensor because it has to be on something until the player picks up things - stiffness: 0.4 - }); - World.add(engine.world, mech.holdConstraint); - }, - cycle: 0, - width: 50, - radius: 30, - fillColor: "#fff", - fillColorDark: "#ccc", - height: 42, - yOffWhen: { - crouch: 22, - stand: 49, - jump: 70 - }, - defaultMass: 5, - mass: 5, - FxNotHolding: 0.015, - Fx: 0.015, //run Force on ground //this is reset in b.setModDefaults() - FxAir: 0.015, //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 - }, - setPosToSpawn(xPos, yPos) { - this.spawnPos.x = this.pos.x = xPos; - this.spawnPos.y = this.pos.y = yPos; - this.transX = this.transSmoothX = canvas.width2 - this.pos.x; - this.transY = this.transSmoothY = canvas.height2 - this.pos.y; - this.Vx = this.spawnVel.x; - this.Vy = this.spawnVel.y; - player.force.x = 0; - player.force.y = 0; - Matter.Body.setPosition(player, this.spawnPos); - Matter.Body.setVelocity(player, this.spawnVel); - }, - Sy: 0, //adds a smoothing effect to vertical only - Vx: 0, - Vy: 0, - jumpForce: 0.38, //this is reset in b.setModDefaults() - gravity: 0.0019, - friction: { - ground: 0.01, - air: 0.0025 - }, - angle: 0, - walk_cycle: 0, - stepSize: 0, - flipLegs: -1, - hip: { - x: 12, - y: 24 - }, - knee: { - x: 0, - y: 0, - x2: 0, - y2: 0 - }, - foot: { - x: 0, - y: 0 - }, - legLength1: 55, - legLength2: 45, - transX: 0, - transY: 0, - move() { - this.pos.x = player.position.x; - this.pos.y = playerBody.position.y - this.yOff; - this.Vx = player.velocity.x; - this.Vy = player.velocity.y; - }, - transSmoothX: 0, - transSmoothY: 0, - lastGroundedPositionY: 0, - // mouseZoom: 0, - look() { - //always on mouse look - this.angle = Math.atan2( - game.mouseInGame.y - this.pos.y, - game.mouseInGame.x - this.pos.x - ); - //smoothed mouse look translations - const scale = 0.8; - this.transSmoothX = canvas.width2 - this.pos.x - (game.mouse.x - canvas.width2) * scale; - this.transSmoothY = canvas.height2 - this.pos.y - (game.mouse.y - canvas.height2) * scale; - - this.transX += (this.transSmoothX - this.transX) * 0.07; - this.transY += (this.transSmoothY - this.transY) * 0.07; - }, - doCrouch() { - if (!this.crouch) { - this.crouch = true; - this.yOffGoal = this.yOffWhen.crouch; - Matter.Body.translate(playerHead, { - x: 0, - y: 40 - }); - } - }, - undoCrouch() { - if (this.crouch) { - this.crouch = false; - this.yOffGoal = this.yOffWhen.stand; - Matter.Body.translate(playerHead, { - x: 0, - y: -40 - }); - } - }, - hardLandCD: 0, - enterAir() { - //triggered in engine.js on collision - this.onGround = false; - this.hardLandCD = 0 // disable hard landing - if (this.isHeadClear) { - if (this.crouch) { - this.undoCrouch(); - } - this.yOffGoal = this.yOffWhen.jump; - } - }, - //triggered in engine.js on collision - enterLand() { - this.onGround = true; - if (this.crouch) { - if (this.isHeadClear) { - this.undoCrouch(); - } else { - this.yOffGoal = this.yOffWhen.crouch; - } - } else { - //sets a hard land where player stays in a crouch for a bit and can't jump - //crouch is forced in keyMove() on ground section below - const momentum = player.velocity.y * player.mass //player mass is 5 so this triggers at 20 down velocity, unless the player is holding something - if (momentum > 100) { - this.doCrouch(); - this.yOff = this.yOffWhen.jump; - this.hardLandCD = mech.cycle + Math.min(momentum / 6 - 6, 40) - - if (game.isBodyDamage && player.velocity.y > 26 && momentum > 165) { //falling damage - mech.damageImmune = mech.cycle + 30; //player is immune to collision damage for 30 cycles - let dmg = Math.sqrt(momentum - 165) * 0.01 - dmg = Math.min(Math.max(dmg, 0.02), 0.20); - mech.damage(dmg); - } - } else { - this.yOffGoal = this.yOffWhen.stand; - } - } - }, - buttonCD_jump: 0, //cool down for player buttons - keyMove() { - if (this.onGround) { //on ground ********************** - if (this.crouch) { - if (!(keys[83] || keys[40]) && this.isHeadClear && this.hardLandCD < mech.cycle) this.undoCrouch(); - } else if (keys[83] || keys[40] || this.hardLandCD > mech.cycle) { - this.doCrouch(); //on ground && not crouched and pressing s or down - } else if ((keys[87] || keys[38]) && this.buttonCD_jump + 20 < mech.cycle && this.yOffWhen.stand > 23) { - this.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: this.jumpForce * 0.12 * Math.min(mech.standingOn.mass, 5) - }); - - player.force.y = -this.jumpForce; //player jump force - Matter.Body.setVelocity(player, { //zero player y-velocity for consistent jumps - x: player.velocity.x, - y: 0 - }); - } - - //horizontal move on ground - //apply a force to move - if (keys[65] || keys[37]) { //left / a - if (player.velocity.x > -2) { - player.force.x -= this.Fx * 1.5 - } else { - player.force.x -= this.Fx - } - } else if (keys[68] || keys[39]) { //right / d - if (player.velocity.x < 2) { - player.force.x += this.Fx * 1.5 - } else { - player.force.x += this.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 = (this.crouch) ? 0.65 : 0.89; // this controls speed when crouched - Matter.Body.setVelocity(player, { - x: player.velocity.x * stoppingFriction, - y: player.velocity.y * stoppingFriction - }); - } - - } else { // in air ********************************** - //check for short jumps - if ( - this.buttonCD_jump + 60 > mech.cycle && //just pressed jump - !(keys[87] || keys[38]) && //but not pressing jump key - this.Vy < 0 //moving up - ) { - Matter.Body.setVelocity(player, { - //reduce player y-velocity every cycle - x: player.velocity.x, - y: player.velocity.y * 0.94 - }); - } - const limit = 125 / player.mass / player.mass - if (keys[65] || keys[37]) { - if (player.velocity.x > -limit) player.force.x -= this.FxAir; // move player left / a - } else if (keys[68] || keys[39]) { - if (player.velocity.x < limit) player.force.x += this.FxAir; //move player right / d - } - } - - //smoothly move leg height towards height goal - this.yOff = this.yOff * 0.85 + this.yOffGoal * 0.15; - }, - alive: true, - death() { - if (b.modIsImmortal) { //if player has the immortality buff, spawn on the same level with randomized stats - spawn.setSpawnList(); //new mob types - game.clearNow = true; //triggers a map reset - - //count mods - let totalMods = -2; //lose the immortality mod and one more, so -2 - for (let i = 0; i < b.mods.length; i++) { - if (b.mods[i].have) totalMods++ - } - - function randomizeMods() { - b.setModDefaults(); //remove all mods - for (let i = 0; i < totalMods; i++) { - //find what mods I don't have - let options = []; - for (let i = 0; i < b.mods.length; i++) { - //can't get quantum immortality again - if (i !== 7 && !b.mods[i].have) options.push(i); - } - //add a new mod - if (options.length > 0) { - const choose = Math.floor(Math.random() * options.length) - let newMod = options[choose] - b.giveMod(newMod) - options.splice(choose, 1); - } - } - game.updateModHUD(); - } - - function randomizeField() { - if (game.difficulty * (Math.random() + 0.27) > 2) { - mech.fieldUpgrades[Math.floor(Math.random() * (mech.fieldUpgrades.length))].effect(); - } else { - mech.fieldUpgrades[0].effect(); - } - } - - function randomizeHealth() { - mech.health = 0.5 + Math.random() - if (mech.health > 1) mech.health = 1; - mech.displayHealth(); - } - - function randomizeGuns() { - const length = Math.round(b.inventory.length * (1 + 0.4 * (Math.random() - 0.5))) - //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; - } - for (let i = 0; i < length; i++) { - powerUps.gun.effect(); - } - - //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.random() - 0.3))) - } - } - game.makeGunHUD(); //update gun HUD - } - - game.wipe = function () { //set wipe to have trails - ctx.fillStyle = "rgba(255,255,255,0)"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - } - randomizeMods() - randomizeGuns() - randomizeField() - randomizeHealth() - for (let i = 0, len = 7; i < len; i++) { - setTimeout(function () { - randomizeMods() - randomizeGuns() - randomizeField() - randomizeHealth() - game.replaceTextLog = true; - game.makeTextLog(`probability amplitude will synchronize in ${len-i-1} seconds`, 1000); - 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); - } - }, (i + 1) * 1000); - } - - 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); - document.title = "n-gon: L" + (game.difficulty) + " " + level.levels[level.onLevel]; - }, 8000); - - } else if (this.alive) { //normal death code here - this.alive = false; - game.paused = true; - this.health = 0; - this.displayHealth(); - document.getElementById("text-log").style.opacity = 0; //fade out any active text logs - document.getElementById("fade-out").style.opacity = 1; //slowly fades out - setTimeout(function () { - game.splashReturn(); - }, 3000); - } - }, - health: 0, - drawHealth() { - if (this.health < 1) { - ctx.fillStyle = "rgba(100, 100, 100, 0.5)"; - ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, 60, 10); - ctx.fillStyle = "#f00"; - ctx.fillRect( - this.pos.x - this.radius, - this.pos.y - 50, - 60 * this.health, - 10 - ); - } - }, - displayHealth() { - id = document.getElementById("health"); - id.style.width = Math.floor(300 * this.health) + "px"; - //css animation blink if health is low - if (this.health < 0.3) { - id.classList.add("low-health"); - } else { - id.classList.remove("low-health"); - } - }, - addHealth(heal) { - this.health += heal; - if (this.health > 1 || b.isModFullHeal) this.health = 1; - this.displayHealth(); - }, - defaultFPSCycle: 0, //tracks when to return to normal fps - damage(dmg) { - if (b.isModMonogamy && b.inventory[0] === b.activeGun) { - for (let i = 0, len = b.inventory.length; i < len; i++) { - dmg *= 0.93 - } - } - this.health -= dmg; - if (this.health < 0) { - this.health = 0; - this.death(); - return; - } - this.displayHealth(); - document.getElementById("dmg").style.transition = "opacity 0s"; - document.getElementById("dmg").style.opacity = 0.1 + Math.min(0.6, dmg * 4); - - //chance to build a drone on damage from mod - if (b.isModDroneOnDamage) { - const len = (dmg - 0.08 + 0.05 * Math.random()) / 0.05 - for (let i = 0; i < len; i++) { - if (Math.random() < 0.6) b.guns[13].fire() //spawn drone - } - } - - // freeze game and display a full screen red color - if (dmg > 0.05) { - this.drop(); //drop block if holding - 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 - - 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); - } - }; - requestAnimationFrame(normalFPS); - - // // freeze game and display a full screen red color - // if (dmg > 0.05) { - // if (dmg > 0.07) { - // this.drop(); //drop block if holding - // } - - // 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 - - // 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); - // } - // }; - // requestAnimationFrame(normalFPS); - }, - damageImmune: 0, - hitMob(i, dmg) { - //prevents damage happening too quick - }, - buttonCD: 0, //cool down for player buttons - usePowerUp(i) { - powerUp[i].effect(); - Matter.World.remove(engine.world, powerUp[i]); - powerUp.splice(i, 1); - }, - drawLeg(stroke) { - // if (game.mouseInGame.x > this.pos.x) { - if (mech.angle > -Math.PI / 2 && mech.angle < Math.PI / 2) { - this.flipLegs = 1; - } else { - this.flipLegs = -1; - } - ctx.save(); - ctx.scale(this.flipLegs, 1); //leg lines - ctx.beginPath(); - ctx.moveTo(this.hip.x, this.hip.y); - ctx.lineTo(this.knee.x, this.knee.y); - ctx.lineTo(this.foot.x, this.foot.y); - ctx.strokeStyle = stroke; - ctx.lineWidth = 7; - ctx.stroke(); - - //toe lines - ctx.beginPath(); - ctx.moveTo(this.foot.x, this.foot.y); - ctx.lineTo(this.foot.x - 15, this.foot.y + 5); - ctx.moveTo(this.foot.x, this.foot.y); - ctx.lineTo(this.foot.x + 15, this.foot.y + 5); - ctx.lineWidth = 4; - ctx.stroke(); - - //hip joint - ctx.beginPath(); - ctx.arc(this.hip.x, this.hip.y, 11, 0, 2 * Math.PI); - //knee joint - ctx.moveTo(this.knee.x + 7, this.knee.y); - ctx.arc(this.knee.x, this.knee.y, 7, 0, 2 * Math.PI); - //foot joint - ctx.moveTo(this.foot.x + 6, this.foot.y); - ctx.arc(this.foot.x, this.foot.y, 6, 0, 2 * Math.PI); - ctx.fillStyle = this.fillColor; - ctx.fill(); - ctx.lineWidth = 2; - ctx.stroke(); - ctx.restore(); - }, - calcLeg(cycle_offset, offset) { - this.hip.x = 12 + offset; - this.hip.y = 24 + offset; - //stepSize goes to zero if Vx is zero or not on ground (make this transition cleaner) - this.stepSize = 0.8 * this.stepSize + 0.2 * (7 * Math.sqrt(Math.min(9, Math.abs(this.Vx))) * this.onGround); - //changes to stepsize are smoothed by adding only a percent of the new value each cycle - const stepAngle = 0.034 * this.walk_cycle + cycle_offset; - this.foot.x = 2.2 * this.stepSize * Math.cos(stepAngle) + offset; - this.foot.y = offset + 1.2 * this.stepSize * Math.sin(stepAngle) + this.yOff + this.height; - const Ymax = this.yOff + this.height; - if (this.foot.y > Ymax) this.foot.y = Ymax; - - //calculate knee position as intersection of circle from hip and foot - const d = Math.sqrt((this.hip.x - this.foot.x) * (this.hip.x - this.foot.x) + (this.hip.y - this.foot.y) * (this.hip.y - this.foot.y)); - const l = (this.legLength1 * this.legLength1 - this.legLength2 * this.legLength2 + d * d) / (2 * d); - const h = Math.sqrt(this.legLength1 * this.legLength1 - l * l); - this.knee.x = (l / d) * (this.foot.x - this.hip.x) - (h / d) * (this.foot.y - this.hip.y) + this.hip.x + offset; - this.knee.y = (l / d) * (this.foot.y - this.hip.y) + (h / d) * (this.foot.x - this.hip.x) + this.hip.y; - }, - draw() { - ctx.fillStyle = this.fillColor; - this.walk_cycle += this.flipLegs * this.Vx; - - //draw body - ctx.save(); - ctx.translate(this.pos.x, this.pos.y); - this.calcLeg(Math.PI, -3); - this.drawLeg("#4a4a4a"); - this.calcLeg(0, 0); - this.drawLeg("#333"); - ctx.rotate(this.angle); - - ctx.beginPath(); - ctx.arc(0, 0, 30, 0, 2 * Math.PI); - let grd = ctx.createLinearGradient(-30, 0, 30, 0); - grd.addColorStop(0, this.fillColorDark); - grd.addColorStop(1, this.fillColor); - ctx.fillStyle = grd; - ctx.fill(); - ctx.arc(15, 0, 4, 0, 2 * Math.PI); - ctx.strokeStyle = "#333"; - ctx.lineWidth = 2; - ctx.stroke(); - // ctx.beginPath(); - // ctx.arc(15, 0, 3, 0, 2 * Math.PI); - // ctx.fillStyle = '#9cf' //'#0cf'; - // ctx.fill() - ctx.restore(); - }, - // ********************************************* - // **************** holding ******************** - // ********************************************* - closest: { - dist: 1000, - index: 0 - }, - isHolding: false, - isStealth: false, - throwCharge: 0, - fireCDcycle: 0, - fieldCDcycle: 0, - fieldMode: 0, //basic field mode before upgrades - // these values are set on reset by setHoldDefaults() - fieldMeter: 0, - fieldRegen: 0, - fieldMode: 0, - fieldFire: false, - holdingMassScale: 0, - throwChargeRate: 0, - throwChargeMax: 0, - fieldFireCD: 0, - fieldShieldingScale: 0, - grabRange: 0, - fieldArc: 0, - fieldThreshold: 0, - calculateFieldThreshold() { - this.fieldThreshold = Math.cos(this.fieldArc * Math.PI) - }, - setHoldDefaults() { - this.fieldMeter = 1; - this.fieldRegen = 0.001; - this.fieldFire = false; - this.fieldCDcycle = 0; - this.isStealth = false; - player.collisionFilter.mask = 0x010011 //0x010011 is normal - this.holdingMassScale = 0.5; - this.fieldFireCD = 15; - this.fieldShieldingScale = 1; //scale energy loss after collision with mob - this.grabRange = 175; - this.fieldArc = 0.2; //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) - this.calculateFieldThreshold(); //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) - mech.isBodiesAsleep = true; - mech.wakeCheck(); - // this.phaseBlocks(0x011111) - }, - drawFieldMeter(range = 60) { - if (this.fieldMeter < 1) { - mech.fieldMeter += mech.fieldRegen; - ctx.fillStyle = "rgba(0, 0, 0, 0.4)"; - ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, range, 10); - ctx.fillStyle = "#0cf"; - ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, range * this.fieldMeter, 10); - } else { - mech.fieldMeter = 1 - } - }, - lookingAt(who) { - //calculate a vector from body to player and make it length 1 - const diff = Matter.Vector.normalise(Matter.Vector.sub(who.position, mech.pos)); - //make a vector for the player's direction of length 1 - const dir = { - x: Math.cos(mech.angle), - y: Math.sin(mech.angle) - }; - //the dot product of diff and dir will return how much over lap between the vectors - // console.log(Matter.Vector.dot(dir, diff)) - if (Matter.Vector.dot(dir, diff) > this.fieldThreshold) { - return true; - } - return false; - }, - drop() { - if (this.isHolding) { - this.isHolding = false; - this.definePlayerMass() - this.holdingTarget.collisionFilter.category = 0x010000; - this.holdingTarget.collisionFilter.mask = 0x011111; - this.holdingTarget = null; - this.throwCharge = 0; - } - }, - definePlayerMass(mass = mech.defaultMass) { - Matter.Body.setMass(player, mass); - //reduce air and ground move forces - this.Fx = 0.075 / mass * b.modSquirrelFx - this.FxAir = 0.375 / mass / mass - //make player stand a bit lower when holding heavy masses - this.yOffWhen.stand = Math.max(this.yOffWhen.crouch, Math.min(49, 49 - (mass - 5) * 6)) - if (this.onGround && !this.crouch) this.yOffGoal = this.yOffWhen.stand; - }, - drawHold(target, stroke = true) { - const eye = 15; - const len = target.vertices.length - 1; - ctx.fillStyle = "rgba(110,170,200," + (0.2 + 0.4 * Math.random()) + ")"; - ctx.lineWidth = 1; - ctx.strokeStyle = "#000"; - ctx.beginPath(); - ctx.moveTo( - mech.pos.x + eye * Math.cos(this.angle), - mech.pos.y + eye * Math.sin(this.angle) - ); - ctx.lineTo(target.vertices[len].x, target.vertices[len].y); - ctx.lineTo(target.vertices[0].x, target.vertices[0].y); - ctx.fill(); - if (stroke) ctx.stroke(); - for (let i = 0; i < len; i++) { - ctx.beginPath(); - ctx.moveTo( - mech.pos.x + eye * Math.cos(this.angle), - mech.pos.y + eye * Math.sin(this.angle) - ); - ctx.lineTo(target.vertices[i].x, target.vertices[i].y); - ctx.lineTo(target.vertices[i + 1].x, target.vertices[i + 1].y); - ctx.fill(); - if (stroke) ctx.stroke(); - } - }, - holding() { - this.fieldMeter -= this.fieldRegen; - if (this.fieldMeter < 0) this.fieldMeter = 0; - Matter.Body.setPosition(this.holdingTarget, { - x: mech.pos.x + 70 * Math.cos(this.angle), - y: mech.pos.y + 70 * Math.sin(this.angle) - }); - Matter.Body.setVelocity(this.holdingTarget, player.velocity); - Matter.Body.rotate(this.holdingTarget, 0.01 / this.holdingTarget.mass); //gently spin the block - }, - throw () { - if ((keys[32] || game.mouseDownRight)) { - if (this.fieldMeter > 0.0008) { - this.fieldMeter -= 0.0008; - this.throwCharge += this.throwChargeRate;; - //draw charge - const x = mech.pos.x + 15 * Math.cos(this.angle); - const y = mech.pos.y + 15 * Math.sin(this.angle); - const len = this.holdingTarget.vertices.length - 1; - const edge = this.throwCharge * this.throwCharge * 0.02; - const grd = ctx.createRadialGradient(x, y, edge, x, y, edge + 5); - grd.addColorStop(0, "rgba(255,50,150,0.3)"); - grd.addColorStop(1, "transparent"); - ctx.fillStyle = grd; - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(this.holdingTarget.vertices[len].x, this.holdingTarget.vertices[len].y); - ctx.lineTo(this.holdingTarget.vertices[0].x, this.holdingTarget.vertices[0].y); - ctx.fill(); - for (let i = 0; i < len; i++) { - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(this.holdingTarget.vertices[i].x, this.holdingTarget.vertices[i].y); - ctx.lineTo(this.holdingTarget.vertices[i + 1].x, this.holdingTarget.vertices[i + 1].y); - ctx.fill(); - } - } else { - this.drop() - } - } else if (this.throwCharge > 0) { - //throw the body - this.fireCDcycle = mech.cycle + this.fieldFireCD; - this.isHolding = false; - //bullet-like collisions - this.holdingTarget.collisionFilter.category = 0x000100; - this.holdingTarget.collisionFilter.mask = 0x110111; - //check every second to see if player is away from thrown body, and make solid - const solid = function (that) { - const dx = that.position.x - player.position.x; - const dy = that.position.y - player.position.y; - if (dx * dx + dy * dy > 10000 && that.speed < 3 && that !== mech.holdingTarget) { - that.collisionFilter.category = 0x010000; //make solid - that.collisionFilter.mask = 0x011111; - } else { - setTimeout(solid, 50, that); - } - }; - setTimeout(solid, 200, this.holdingTarget); - //throw speed scales a bit with mass - const speed = Math.min(85, Math.min(54 / this.holdingTarget.mass + 5, 48) * Math.min(this.throwCharge, this.throwChargeMax) / 50); - - this.throwCharge = 0; - Matter.Body.setVelocity(this.holdingTarget, { - x: player.velocity.x + Math.cos(this.angle) * speed, - y: player.velocity.y + Math.sin(this.angle) * speed - }); - //player recoil //stronger in x-dir to prevent jump hacking - Matter.Body.setVelocity(player, { - x: player.velocity.x - Math.cos(this.angle) * speed / 20 * Math.sqrt(this.holdingTarget.mass), - y: player.velocity.y - Math.sin(this.angle) * speed / 80 * Math.sqrt(this.holdingTarget.mass) - }); - this.definePlayerMass() //return to normal player mass - } - }, - drawField() { - if (mech.holdingTarget) { - ctx.fillStyle = "rgba(110,170,200," + (mech.fieldMeter * (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.fieldMeter * (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 = this.grabRange - 20; - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, range, mech.angle - Math.PI * mech.fieldArc, mech.angle + Math.PI * mech.fieldArc, false); - 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.fieldCDcycle < mech.cycle) { - const grabPowerUpRange2 = (this.grabRange + 220) * (this.grabRange + 220) - for (let i = 0, len = powerUp.length; i < len; ++i) { - const dxP = mech.pos.x - powerUp[i].position.x; - const dyP = mech.pos.y - powerUp[i].position.y; - const dist2 = dxP * dxP + dyP * dyP; - // float towards player if looking at and in range or if very close to player - if (dist2 < grabPowerUpRange2 && this.lookingAt(powerUp[i]) || dist2 < 16000) { - if (dist2 < 5000) { //use power up if it is close enough - Matter.Body.setVelocity(player, { //player knock back, after grabbing power up - x: player.velocity.x + ((powerUp[i].velocity.x * powerUp[i].mass) / player.mass) * 0.3, - y: player.velocity.y + ((powerUp[i].velocity.y * powerUp[i].mass) / player.mass) * 0.3 - }); - mech.usePowerUp(i); - return; - } - this.fieldMeter -= this.fieldRegen * 0.5; - powerUp[i].force.x += 7 * (dxP / dist2) * powerUp[i].mass; - powerUp[i].force.y += 7 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * game.g; //negate gravity - //extra friction - Matter.Body.setVelocity(powerUp[i], { - x: powerUp[i].velocity.x * 0.11, - y: powerUp[i].velocity.y * 0.11 - }); - } - } - } - }, - pushMass(who) { - const fieldBlockCost = Math.max(0.02, who.mass * 0.012) //0.012 - if (this.fieldMeter > fieldBlockCost) { - this.fieldMeter -= fieldBlockCost * this.fieldShieldingScale; - if (this.fieldMeter < 0) this.fieldMeter = 0; - this.drawHold(who); - //knock backs - const angle = Math.atan2(player.position.y - who.position.y, player.position.x - who.position.x); - const mass = Math.min(Math.sqrt(who.mass), 4); - Matter.Body.setVelocity(who, { - x: player.velocity.x - (15 * Math.cos(angle)) / mass, - y: player.velocity.y - (15 * Math.sin(angle)) / mass - }); - Matter.Body.setVelocity(player, { - x: player.velocity.x + 5 * Math.cos(angle) * mass, - y: player.velocity.y + 5 * Math.sin(angle) * mass - }); - } - }, - pushMobsFacing() { // find mobs in range and in direction looking - for (let i = 0, len = mob.length; i < len; ++i) { - if ( - Matter.Vector.magnitude(Matter.Vector.sub(mob[i].position, this.pos)) < this.grabRange && - this.lookingAt(mob[i]) && - Matter.Query.ray(map, mob[i].position, this.pos).length === 0 - ) { - mob[i].locatePlayer(); - mech.pushMass(mob[i]); - } - } - }, - pushMobs360(range = this.grabRange * 0.75) { // find mobs in range in any direction - for (let i = 0, len = mob.length; i < len; ++i) { - if ( - Matter.Vector.magnitude(Matter.Vector.sub(mob[i].position, this.pos)) < range && - Matter.Query.ray(map, mob[i].position, this.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 && - Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)) < this.grabRange && - this.lookingAt(body[i]) && - Matter.Query.ray(map, body[i].position, this.pos).length === 0 - ) { - mech.pushMass(body[i]); - } - } - }, - pushBody360(range = this.grabRange * 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 && - Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)) < range && - this.lookingAt(body[i]) && - Matter.Query.ray(map, body[i].position, this.pos).length === 0 && - body[i].collisionFilter.category === 0x010000 - ) { - mech.pushMass(body[i]); - } - } - }, - lookForPickUp(range = this.grabRange) { //find body to pickup - this.fieldMeter -= this.fieldRegen; - const grabbing = { - targetIndex: null, - targetRange: range, - // lookingAt: false //false to pick up object in range, but not looking at - }; - for (let i = 0, len = body.length; i < len; ++i) { - if (Matter.Query.ray(map, body[i].position, this.pos).length === 0) { - //is this next body a better target then my current best - const dist = Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)); - const looking = this.lookingAt(body[i]); - // if (dist < grabbing.targetRange && (looking || !grabbing.lookingAt) && !body[i].isNotHoldable) { - if (dist < grabbing.targetRange && looking && !body[i].isNotHoldable) { - grabbing.targetRange = dist; - grabbing.targetIndex = i; - // grabbing.lookingAt = looking; - } - } - } - // set pick up target for when mouse is released - if (body[grabbing.targetIndex]) { - this.holdingTarget = body[grabbing.targetIndex]; - // - ctx.beginPath(); //draw on each valid body - let vertices = this.holdingTarget.vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j += 1) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - ctx.fillStyle = "rgba(190,215,230," + (0.3 + 0.7 * Math.random()) + ")"; - ctx.fill(); - - ctx.globalAlpha = 0.2; - this.drawHold(this.holdingTarget); - ctx.globalAlpha = 1; - } else { - this.holdingTarget = null; - } - }, - pickUp() { - //triggers when a hold target exits and field button is released - this.isHolding = true; - this.definePlayerMass(mech.defaultMass + this.holdingTarget.mass * this.holdingMassScale) - //collide with nothing - this.holdingTarget.collisionFilter.category = 0x000000; - this.holdingTarget.collisionFilter.mask = 0x000000; - // if (this.holdingTarget) { - // this.holdingTarget.collisionFilter.category = 0x010000; - // this.holdingTarget.collisionFilter.mask = 0x011111; - // } - // combine momentum // this doesn't feel right in game - // const px = player.velocity.x * player.mass + this.holdingTarget.velocity.x * this.holdingTarget.mass; - // const py = player.velocity.y * player.mass + this.holdingTarget.velocity.y * this.holdingTarget.mass; - // Matter.Body.setVelocity(player, { - // x: px / (player.mass + this.holdingTarget.mass), - // y: py / (player.mass + this.holdingTarget.mass) - // }); - }, - 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() {}, - fieldText() { - game.replaceTextLog = true; - game.makeTextLog(`${game.SVGrightMouse} ${mech.fieldUpgrades[mech.fieldMode].name}

${mech.fieldUpgrades[mech.fieldMode].description}`, 1000); - game.replaceTextLog = false; - document.getElementById("field").innerHTML = mech.fieldUpgrades[mech.fieldMode].name //add field - }, - fieldUpgrades: [{ - name: "field emitter", - description: "use energy to shield yourself from damage
lets you pick up and throw objects", - effect: () => { - mech.fieldMode = 0; - mech.fieldText(); - game.replaceTextLog = true; //allow text over write - // game.makeTextLog("
(right click or space bar)

", 1200); - mech.setHoldDefaults(); - mech.hold = function () { - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throw(); - } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed - mech.drawField(); - mech.grabPowerUp(); - mech.pushMobsFacing(); - mech.pushBodyFacing(); - mech.lookForPickUp(); - } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //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: "time dilation field", - description: "use energy to stop time
can fire bullets while field is active", - effect: () => { - mech.fieldMode = 1; - mech.fieldText(); - mech.setHoldDefaults(); - mech.fieldFire = true; - mech.grabRange = 130 - mech.isBodiesAsleep = false; - mech.hold = function () { - if (mech.isHolding) { - mech.wakeCheck(); - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throw(); - } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { - const DRAIN = 0.0027 - if (mech.fieldMeter > DRAIN) { - mech.fieldMeter -= DRAIN; - - //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 - - mech.grabPowerUp(); - mech.lookForPickUp(180); - } else { - mech.wakeCheck(); - mech.fieldCDcycle = mech.cycle + 120; - } - } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //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() - if (mech.fieldMode !== 1) { - //wake up if this is no longer the current field mode, like after a new power up - mech.wakeCheck(); - - } - } - } - }, - { - name: "plasma torch", - description: "use energy to emit damaging plasma
decreased shield range and efficiency", - effect: () => { - mech.fieldMode = 2; - mech.fieldText(); - mech.setHoldDefaults(); - // mech.fieldShieldingScale = 2; - // mech.grabRange = 125; - mech.fieldArc = 0.1 //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) - mech.calculateFieldThreshold(); //run after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) - mech.hold = function () { - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throw(); - } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed - const DRAIN = 0.0006 - if (mech.fieldMeter > DRAIN) { - mech.fieldMeter -= DRAIN; - - //calculate laser collision - let best; - let range = 80 + (mech.crouch ? 500 : 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(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 && (!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.35 * b.dmgScale; //********** SCALE DAMAGE HERE ********************* - best.who.damage(dmg); - best.who.locatePlayer(); - - //push mobs away - const force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(mech.pos, path[1])), -0.01 * Math.sqrt(best.who.mass)) - Matter.Body.applyForce(best.who, path[1], force) - // const angle = Math.atan2(player.position.y - best.who.position.y, player.position.x - best.who.position.x); - // const mass = Math.min(Math.sqrt(best.who.mass), 6); - // Matter.Body.setVelocity(best.who, { - // x: best.who.velocity.x * 0.85 - 3 * Math.cos(angle) / mass, - // y: best.who.velocity.y * 0.85 - 3 * Math.sin(angle) / mass - // }); - - //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 = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(mech.pos, path[1])), -0.006 * 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.beginPath(); - ctx.moveTo(path[0].x, path[0].y); - ctx.lineTo(path[1].x, path[1].y); - 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 = range / 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(); - - mech.pushMobs360(110); - // mech.pushBody360(100); //disabled because doesn't work at short range - mech.grabPowerUp(); - mech.lookForPickUp(); - } else { - mech.fieldCDcycle = mech.cycle + 120; //if out of energy - } - } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //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: "negative mass field", - description: "use energy to nullify   gravity
can fire bullets while active", - effect: () => { - mech.fieldMode = 3; - mech.fieldText(); - mech.setHoldDefaults(); - mech.fieldFire = true; - - mech.hold = function () { - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throw(); - } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { //push away - const DRAIN = 0.0004 - if (mech.fieldMeter > DRAIN) { - mech.pushMobs360(170); - mech.pushBody360(180); - mech.grabPowerUp(); - mech.lookForPickUp(170); - //look for nearby objects to make zero-g - function zeroG(who, mag = 1.06) { - for (let i = 0, len = who.length; i < len; ++i) { - sub = Matter.Vector.sub(who[i].position, mech.pos); - dist = Matter.Vector.magnitude(sub); - if (dist < mech.grabRange) { - 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? - - Matter.Body.setVelocity(player, { - x: player.velocity.x, - y: player.velocity.y * 0.97 - }); - - if (keys[83] || keys[40]) { //down - player.force.y -= 0.8 * player.mass * mech.gravity; - mech.grabRange = mech.grabRange * 0.97 + 400 * 0.03; - zeroG(powerUp, 0.85); - zeroG(body, 0.85); - } else if (keys[87] || keys[38]) { //up - mech.fieldMeter -= 5 * DRAIN; - mech.grabRange = mech.grabRange * 0.97 + 750 * 0.03; - player.force.y -= 1.2 * player.mass * mech.gravity; - zeroG(powerUp, 1.13); - zeroG(body, 1.13); - } else { - mech.fieldMeter -= DRAIN; - mech.grabRange = mech.grabRange * 0.97 + 650 * 0.03; - player.force.y -= 1.07 * player.mass * mech.gravity; // slow upward drift - zeroG(powerUp); - zeroG(body); - } - - //add extra friction for horizontal motion - if (keys[65] || keys[68] || keys[37] || keys[39]) { - Matter.Body.setVelocity(player, { - x: player.velocity.x * 0.85, - y: player.velocity.y - }); - } - - //draw zero-G range - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, mech.grabRange, 0, 2 * Math.PI); - ctx.fillStyle = "#f5f5ff"; - ctx.globalCompositeOperation = "difference"; - ctx.fill(); - ctx.globalCompositeOperation = "source-over"; - } else { - //trigger cool down - mech.fieldCDcycle = mech.cycle + 120; - } - } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released - mech.pickUp(); - mech.grabRange = 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) - mech.grabRange = 0 - } - mech.drawFieldMeter() - } - } - }, - { - name: "standing wave harmonics", - description: "oscillating shields surround you constantly
decreased energy regeneration", - effect: () => { - mech.fieldMode = 4; - mech.fieldText(); - mech.setHoldDefaults(); - mech.fieldRegen *= 0.3; - - mech.hold = function () { - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throw(); - } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed - mech.grabPowerUp(); - mech.lookForPickUp(180); - } else if (mech.holdingTarget && mech.fireCDcycle < 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.fieldMeter > 0.1) { - const grabRange1 = 90 + 60 * Math.sin(mech.cycle / 23) - const grabRange2 = 85 + 70 * Math.sin(mech.cycle / 37) - const grabRange3 = 80 + 80 * Math.sin(mech.cycle / 47) - const netGrabRange = Math.max(grabRange1, grabRange2, grabRange3) - ctx.fillStyle = "rgba(110,170,200," + (0.04 + mech.fieldMeter * (0.12 + 0.13 * Math.random())) + ")"; - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, grabRange1, 0, 2 * Math.PI); - ctx.fill(); - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, grabRange2, 0, 2 * Math.PI); - ctx.fill(); - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, grabRange3, 0, 2 * Math.PI); - ctx.fill(); - mech.pushMobs360(netGrabRange); - mech.pushBody360(netGrabRange); - } - mech.drawFieldMeter() - } - } - }, - { - name: "nano-scale manufacturing", - description: "excess energy used to build drones
3x energy regeneration", - effect: () => { - let gunIndex = 13 //Math.random() < 0.5 ? 13 : 14 - mech.fieldMode = 5; - mech.fieldText(); - mech.setHoldDefaults(); - mech.fieldRegen *= 3; - mech.hold = function () { - if (mech.fieldMeter === 1) { - mech.fieldMeter -= 0.43; - b.guns[gunIndex].fire() //spawn drone - mech.fireCDcycle = mech.cycle + 25; // set fire cool down to prevent +energy from making huge numbers of drones - } - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throw(); - } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed - mech.pushMobsFacing(); - mech.pushBodyFacing(); - mech.drawField(); - mech.grabPowerUp(); - mech.lookForPickUp(); - } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //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: "phase decoherence field", - description: "use energy to to become intangible
can't see or be seen outside field", - effect: () => { - mech.fieldMode = 6; - mech.fieldText(); - mech.setHoldDefaults(); - // mech.grabRange = 230 - mech.hold = function () { - mech.isStealth = false //isStealth is checked in mob foundPlayer() - player.collisionFilter.mask = 0x010011 //0x010011 is normal - if (mech.isHolding) { - mech.drawHold(mech.holdingTarget); - mech.holding(); - mech.throw(); - } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { - const DRAIN = 0.0015 - if (mech.fieldMeter > DRAIN) { - mech.fieldMeter -= DRAIN; - - mech.isStealth = true //isStealth is checked in mob foundPlayer() - player.collisionFilter.mask = 0x000001 //0x010011 is normals - - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, mech.grabRange, 0, 2 * Math.PI); - ctx.globalCompositeOperation = "destination-in"; //in or atop - ctx.fillStyle = `rgba(255,255,255,${mech.fieldMeter*0.5})`; - ctx.fill(); - ctx.globalCompositeOperation = "source-over"; - ctx.strokeStyle = "#000" - ctx.lineWidth = 2; - ctx.stroke(); - - mech.grabPowerUp(); - mech.lookForPickUp(110); - } else { - mech.fieldCDcycle = mech.cycle + 120; - } - } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //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() - } - } - }, - ], +//global player variables for use in matter.js physics +let player, jumpSensor, playerBody, playerHead, headSensor; + +// player Object Prototype ********************************************* +const mech = { + spawn() { + //load player in matter.js physic engine + // let vector = Vertices.fromPath("0 40 50 40 50 115 0 115 30 130 20 130"); //player as a series of vertices + 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: 0x001000, + mask: 0x010011 + }, + death() { + mech.death(); + } + }); + Matter.Body.setMass(player, mech.mass); + World.add(engine.world, [player]); + + mech.holdConstraint = Constraint.create({ + //holding body constraint + pointA: { + x: 0, + y: 0 + }, + bodyB: jumpSensor, //setting constraint to jump sensor because it has to be on something until the player picks up things + stiffness: 0.4 + }); + World.add(engine.world, mech.holdConstraint); + }, + cycle: 0, + width: 50, + radius: 30, + fillColor: "#fff", + fillColorDark: "#ccc", + height: 42, + yOffWhen: { + crouch: 22, + stand: 49, + jump: 70 + }, + defaultMass: 5, + mass: 5, + FxNotHolding: 0.015, + Fx: 0.015, //run Force on ground //this is reset in b.setModDefaults() + FxAir: 0.015, //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 + }, + setPosToSpawn(xPos, yPos) { + this.spawnPos.x = this.pos.x = xPos; + this.spawnPos.y = this.pos.y = yPos; + this.transX = this.transSmoothX = canvas.width2 - this.pos.x; + this.transY = this.transSmoothY = canvas.height2 - this.pos.y; + this.Vx = this.spawnVel.x; + this.Vy = this.spawnVel.y; + player.force.x = 0; + player.force.y = 0; + Matter.Body.setPosition(player, this.spawnPos); + Matter.Body.setVelocity(player, this.spawnVel); + }, + Sy: 0, //adds a smoothing effect to vertical only + Vx: 0, + Vy: 0, + jumpForce: 0.38, //this is reset in b.setModDefaults() + gravity: 0.0019, + friction: { + ground: 0.01, + air: 0.0025 + }, + angle: 0, + walk_cycle: 0, + stepSize: 0, + flipLegs: -1, + hip: { + x: 12, + y: 24 + }, + knee: { + x: 0, + y: 0, + x2: 0, + y2: 0 + }, + foot: { + x: 0, + y: 0 + }, + legLength1: 55, + legLength2: 45, + transX: 0, + transY: 0, + move() { + this.pos.x = player.position.x; + this.pos.y = playerBody.position.y - this.yOff; + this.Vx = player.velocity.x; + this.Vy = player.velocity.y; + }, + transSmoothX: 0, + transSmoothY: 0, + lastGroundedPositionY: 0, + // mouseZoom: 0, + look() { + //always on mouse look + this.angle = Math.atan2( + game.mouseInGame.y - this.pos.y, + game.mouseInGame.x - this.pos.x + ); + //smoothed mouse look translations + const scale = 0.8; + this.transSmoothX = canvas.width2 - this.pos.x - (game.mouse.x - canvas.width2) * scale; + this.transSmoothY = canvas.height2 - this.pos.y - (game.mouse.y - canvas.height2) * scale; + + this.transX += (this.transSmoothX - this.transX) * 0.07; + this.transY += (this.transSmoothY - this.transY) * 0.07; + }, + doCrouch() { + if (!this.crouch) { + this.crouch = true; + this.yOffGoal = this.yOffWhen.crouch; + Matter.Body.translate(playerHead, { + x: 0, + y: 40 + }); + } + }, + undoCrouch() { + if (this.crouch) { + this.crouch = false; + this.yOffGoal = this.yOffWhen.stand; + Matter.Body.translate(playerHead, { + x: 0, + y: -40 + }); + } + }, + hardLandCD: 0, + enterAir() { + //triggered in engine.js on collision + this.onGround = false; + this.hardLandCD = 0 // disable hard landing + if (this.isHeadClear) { + if (this.crouch) { + this.undoCrouch(); + } + this.yOffGoal = this.yOffWhen.jump; + } + }, + //triggered in engine.js on collision + enterLand() { + this.onGround = true; + if (this.crouch) { + if (this.isHeadClear) { + this.undoCrouch(); + } else { + this.yOffGoal = this.yOffWhen.crouch; + } + } else { + //sets a hard land where player stays in a crouch for a bit and can't jump + //crouch is forced in keyMove() on ground section below + const momentum = player.velocity.y * player.mass //player mass is 5 so this triggers at 20 down velocity, unless the player is holding something + if (momentum > 100) { + this.doCrouch(); + this.yOff = this.yOffWhen.jump; + this.hardLandCD = mech.cycle + Math.min(momentum / 6 - 6, 40) + + if (game.isBodyDamage && player.velocity.y > 26 && momentum > 165) { //falling damage + mech.damageImmune = mech.cycle + 30; //player is immune to collision damage for 30 cycles + let dmg = Math.sqrt(momentum - 165) * 0.01 + dmg = Math.min(Math.max(dmg, 0.02), 0.20); + mech.damage(dmg); + } + } else { + this.yOffGoal = this.yOffWhen.stand; + } + } + }, + buttonCD_jump: 0, //cool down for player buttons + keyMove() { + if (this.onGround) { //on ground ********************** + if (this.crouch) { + if (!(keys[83] || keys[40]) && this.isHeadClear && this.hardLandCD < mech.cycle) this.undoCrouch(); + } else if (keys[83] || keys[40] || this.hardLandCD > mech.cycle) { + this.doCrouch(); //on ground && not crouched and pressing s or down + } else if ((keys[87] || keys[38]) && this.buttonCD_jump + 20 < mech.cycle && this.yOffWhen.stand > 23) { + this.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: this.jumpForce * 0.12 * Math.min(mech.standingOn.mass, 5) + }); + + player.force.y = -this.jumpForce; //player jump force + Matter.Body.setVelocity(player, { //zero player y-velocity for consistent jumps + x: player.velocity.x, + y: 0 + }); + } + + //horizontal move on ground + //apply a force to move + if (keys[65] || keys[37]) { //left / a + if (player.velocity.x > -2) { + player.force.x -= this.Fx * 1.5 + } else { + player.force.x -= this.Fx + } + } else if (keys[68] || keys[39]) { //right / d + if (player.velocity.x < 2) { + player.force.x += this.Fx * 1.5 + } else { + player.force.x += this.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 = (this.crouch) ? 0.65 : 0.89; // this controls speed when crouched + Matter.Body.setVelocity(player, { + x: player.velocity.x * stoppingFriction, + y: player.velocity.y * stoppingFriction + }); + } + + } else { // in air ********************************** + //check for short jumps + if ( + this.buttonCD_jump + 60 > mech.cycle && //just pressed jump + !(keys[87] || keys[38]) && //but not pressing jump key + this.Vy < 0 //moving up + ) { + Matter.Body.setVelocity(player, { + //reduce player y-velocity every cycle + x: player.velocity.x, + y: player.velocity.y * 0.94 + }); + } + const limit = 125 / player.mass / player.mass + if (keys[65] || keys[37]) { + if (player.velocity.x > -limit) player.force.x -= this.FxAir; // move player left / a + } else if (keys[68] || keys[39]) { + if (player.velocity.x < limit) player.force.x += this.FxAir; //move player right / d + } + } + + //smoothly move leg height towards height goal + this.yOff = this.yOff * 0.85 + this.yOffGoal * 0.15; + }, + alive: true, + death() { + if (b.modIsImmortal) { //if player has the immortality buff, spawn on the same level with randomized stats + spawn.setSpawnList(); //new mob types + game.clearNow = true; //triggers a map reset + + //count mods + let totalMods = -2; //lose the immortality mod and one more, so -2 + for (let i = 0; i < b.mods.length; i++) { + if (b.mods[i].have) totalMods++ + } + + function randomizeMods() { + b.setModDefaults(); //remove all mods + for (let i = 0; i < totalMods; i++) { + //find what mods I don't have + let options = []; + for (let i = 0; i < b.mods.length; i++) { + //can't get quantum immortality again + if (i !== 7 && !b.mods[i].have) options.push(i); + } + //add a new mod + if (options.length > 0) { + const choose = Math.floor(Math.random() * options.length) + let newMod = options[choose] + b.giveMod(newMod) + options.splice(choose, 1); + } + } + game.updateModHUD(); + } + + function randomizeField() { + if (game.difficulty * (Math.random() + 0.27) > 2) { + mech.fieldUpgrades[Math.floor(Math.random() * (mech.fieldUpgrades.length))].effect(); + } else { + mech.fieldUpgrades[0].effect(); + } + } + + function randomizeHealth() { + mech.health = 0.5 + Math.random() + if (mech.health > 1) mech.health = 1; + mech.displayHealth(); + } + + function randomizeGuns() { + const length = Math.round(b.inventory.length * (1 + 0.4 * (Math.random() - 0.5))) + //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; + } + for (let i = 0; i < length; i++) { + powerUps.gun.effect(); + } + + //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.random() - 0.3))) + } + } + game.makeGunHUD(); //update gun HUD + } + + game.wipe = function () { //set wipe to have trails + ctx.fillStyle = "rgba(255,255,255,0)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + randomizeMods() + randomizeGuns() + randomizeField() + randomizeHealth() + for (let i = 0, len = 7; i < len; i++) { + setTimeout(function () { + randomizeMods() + randomizeGuns() + randomizeField() + randomizeHealth() + game.replaceTextLog = true; + game.makeTextLog(`probability amplitude will synchronize in ${len-i-1} seconds`, 1000); + 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); + } + }, (i + 1) * 1000); + } + + 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); + document.title = "n-gon: L" + (game.difficulty) + " " + level.levels[level.onLevel]; + }, 8000); + + } else if (this.alive) { //normal death code here + this.alive = false; + game.paused = true; + this.health = 0; + this.displayHealth(); + document.getElementById("text-log").style.opacity = 0; //fade out any active text logs + document.getElementById("fade-out").style.opacity = 1; //slowly fades out + setTimeout(function () { + game.splashReturn(); + }, 3000); + } + }, + health: 0, + drawHealth() { + if (this.health < 1) { + ctx.fillStyle = "rgba(100, 100, 100, 0.5)"; + ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, 60, 10); + ctx.fillStyle = "#f00"; + ctx.fillRect( + this.pos.x - this.radius, + this.pos.y - 50, + 60 * this.health, + 10 + ); + } + }, + displayHealth() { + id = document.getElementById("health"); + id.style.width = Math.floor(300 * this.health) + "px"; + //css animation blink if health is low + if (this.health < 0.3) { + id.classList.add("low-health"); + } else { + id.classList.remove("low-health"); + } + }, + addHealth(heal) { + this.health += heal; + if (this.health > 1 || b.isModFullHeal) this.health = 1; + this.displayHealth(); + }, + defaultFPSCycle: 0, //tracks when to return to normal fps + damage(dmg) { + if (b.isModMonogamy && b.inventory[0] === b.activeGun) { + for (let i = 0, len = b.inventory.length; i < len; i++) { + dmg *= 0.93 + } + } + this.health -= dmg; + if (this.health < 0) { + this.health = 0; + this.death(); + return; + } + this.displayHealth(); + document.getElementById("dmg").style.transition = "opacity 0s"; + document.getElementById("dmg").style.opacity = 0.1 + Math.min(0.6, dmg * 4); + + //chance to build a drone on damage from mod + if (b.isModDroneOnDamage) { + const len = (dmg - 0.08 + 0.05 * Math.random()) / 0.05 + for (let i = 0; i < len; i++) { + if (Math.random() < 0.6) b.guns[13].fire() //spawn drone + } + } + + // freeze game and display a full screen red color + if (dmg > 0.05) { + this.drop(); //drop block if holding + 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 + + 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); + } + }; + requestAnimationFrame(normalFPS); + + // // freeze game and display a full screen red color + // if (dmg > 0.05) { + // if (dmg > 0.07) { + // this.drop(); //drop block if holding + // } + + // 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 + + // 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); + // } + // }; + // requestAnimationFrame(normalFPS); + }, + damageImmune: 0, + hitMob(i, dmg) { + //prevents damage happening too quick + }, + buttonCD: 0, //cool down for player buttons + usePowerUp(i) { + powerUp[i].effect(); + Matter.World.remove(engine.world, powerUp[i]); + powerUp.splice(i, 1); + }, + drawLeg(stroke) { + // if (game.mouseInGame.x > this.pos.x) { + if (mech.angle > -Math.PI / 2 && mech.angle < Math.PI / 2) { + this.flipLegs = 1; + } else { + this.flipLegs = -1; + } + ctx.save(); + ctx.scale(this.flipLegs, 1); //leg lines + ctx.beginPath(); + ctx.moveTo(this.hip.x, this.hip.y); + ctx.lineTo(this.knee.x, this.knee.y); + ctx.lineTo(this.foot.x, this.foot.y); + ctx.strokeStyle = stroke; + ctx.lineWidth = 7; + ctx.stroke(); + + //toe lines + ctx.beginPath(); + ctx.moveTo(this.foot.x, this.foot.y); + ctx.lineTo(this.foot.x - 15, this.foot.y + 5); + ctx.moveTo(this.foot.x, this.foot.y); + ctx.lineTo(this.foot.x + 15, this.foot.y + 5); + ctx.lineWidth = 4; + ctx.stroke(); + + //hip joint + ctx.beginPath(); + ctx.arc(this.hip.x, this.hip.y, 11, 0, 2 * Math.PI); + //knee joint + ctx.moveTo(this.knee.x + 7, this.knee.y); + ctx.arc(this.knee.x, this.knee.y, 7, 0, 2 * Math.PI); + //foot joint + ctx.moveTo(this.foot.x + 6, this.foot.y); + ctx.arc(this.foot.x, this.foot.y, 6, 0, 2 * Math.PI); + ctx.fillStyle = this.fillColor; + ctx.fill(); + ctx.lineWidth = 2; + ctx.stroke(); + ctx.restore(); + }, + calcLeg(cycle_offset, offset) { + this.hip.x = 12 + offset; + this.hip.y = 24 + offset; + //stepSize goes to zero if Vx is zero or not on ground (make this transition cleaner) + this.stepSize = 0.8 * this.stepSize + 0.2 * (7 * Math.sqrt(Math.min(9, Math.abs(this.Vx))) * this.onGround); + //changes to stepsize are smoothed by adding only a percent of the new value each cycle + const stepAngle = 0.034 * this.walk_cycle + cycle_offset; + this.foot.x = 2.2 * this.stepSize * Math.cos(stepAngle) + offset; + this.foot.y = offset + 1.2 * this.stepSize * Math.sin(stepAngle) + this.yOff + this.height; + const Ymax = this.yOff + this.height; + if (this.foot.y > Ymax) this.foot.y = Ymax; + + //calculate knee position as intersection of circle from hip and foot + const d = Math.sqrt((this.hip.x - this.foot.x) * (this.hip.x - this.foot.x) + (this.hip.y - this.foot.y) * (this.hip.y - this.foot.y)); + const l = (this.legLength1 * this.legLength1 - this.legLength2 * this.legLength2 + d * d) / (2 * d); + const h = Math.sqrt(this.legLength1 * this.legLength1 - l * l); + this.knee.x = (l / d) * (this.foot.x - this.hip.x) - (h / d) * (this.foot.y - this.hip.y) + this.hip.x + offset; + this.knee.y = (l / d) * (this.foot.y - this.hip.y) + (h / d) * (this.foot.x - this.hip.x) + this.hip.y; + }, + draw() { + ctx.fillStyle = this.fillColor; + this.walk_cycle += this.flipLegs * this.Vx; + + //draw body + ctx.save(); + ctx.translate(this.pos.x, this.pos.y); + this.calcLeg(Math.PI, -3); + this.drawLeg("#4a4a4a"); + this.calcLeg(0, 0); + this.drawLeg("#333"); + ctx.rotate(this.angle); + + ctx.beginPath(); + ctx.arc(0, 0, 30, 0, 2 * Math.PI); + let grd = ctx.createLinearGradient(-30, 0, 30, 0); + grd.addColorStop(0, this.fillColorDark); + grd.addColorStop(1, this.fillColor); + ctx.fillStyle = grd; + ctx.fill(); + ctx.arc(15, 0, 4, 0, 2 * Math.PI); + ctx.strokeStyle = "#333"; + ctx.lineWidth = 2; + ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(15, 0, 3, 0, 2 * Math.PI); + // ctx.fillStyle = '#9cf' //'#0cf'; + // ctx.fill() + ctx.restore(); + }, + // ********************************************* + // **************** holding ******************** + // ********************************************* + closest: { + dist: 1000, + index: 0 + }, + isHolding: false, + isStealth: false, + throwCharge: 0, + fireCDcycle: 0, + fieldCDcycle: 0, + fieldMode: 0, //basic field mode before upgrades + // these values are set on reset by setHoldDefaults() + fieldMeter: 0, + fieldRegen: 0, + fieldMode: 0, + fieldFire: false, + holdingMassScale: 0, + throwChargeRate: 0, + throwChargeMax: 0, + fieldFireCD: 0, + fieldShieldingScale: 0, + grabRange: 0, + fieldArc: 0, + fieldThreshold: 0, + calculateFieldThreshold() { + this.fieldThreshold = Math.cos(this.fieldArc * Math.PI) + }, + setHoldDefaults() { + this.fieldMeter = 1; + this.fieldRegen = 0.001; + this.fieldFire = false; + this.fieldCDcycle = 0; + this.isStealth = false; + player.collisionFilter.mask = 0x010011 //0x010011 is normal + this.holdingMassScale = 0.5; + this.fieldFireCD = 15; + this.fieldShieldingScale = 1; //scale energy loss after collision with mob + this.grabRange = 175; + this.fieldArc = 0.2; //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) + this.calculateFieldThreshold(); //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) + mech.isBodiesAsleep = true; + mech.wakeCheck(); + // this.phaseBlocks(0x011111) + }, + drawFieldMeter(range = 60) { + if (this.fieldMeter < 1) { + mech.fieldMeter += mech.fieldRegen; + ctx.fillStyle = "rgba(0, 0, 0, 0.4)"; + ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, range, 10); + ctx.fillStyle = "#0cf"; + ctx.fillRect(this.pos.x - this.radius, this.pos.y - 50, range * this.fieldMeter, 10); + } else { + mech.fieldMeter = 1 + } + }, + lookingAt(who) { + //calculate a vector from body to player and make it length 1 + const diff = Matter.Vector.normalise(Matter.Vector.sub(who.position, mech.pos)); + //make a vector for the player's direction of length 1 + const dir = { + x: Math.cos(mech.angle), + y: Math.sin(mech.angle) + }; + //the dot product of diff and dir will return how much over lap between the vectors + // console.log(Matter.Vector.dot(dir, diff)) + if (Matter.Vector.dot(dir, diff) > this.fieldThreshold) { + return true; + } + return false; + }, + drop() { + if (this.isHolding) { + this.isHolding = false; + this.definePlayerMass() + this.holdingTarget.collisionFilter.category = 0x010000; + this.holdingTarget.collisionFilter.mask = 0x011111; + this.holdingTarget = null; + this.throwCharge = 0; + } + }, + definePlayerMass(mass = mech.defaultMass) { + Matter.Body.setMass(player, mass); + //reduce air and ground move forces + this.Fx = 0.075 / mass * b.modSquirrelFx + this.FxAir = 0.375 / mass / mass + //make player stand a bit lower when holding heavy masses + this.yOffWhen.stand = Math.max(this.yOffWhen.crouch, Math.min(49, 49 - (mass - 5) * 6)) + if (this.onGround && !this.crouch) this.yOffGoal = this.yOffWhen.stand; + }, + drawHold(target, stroke = true) { + const eye = 15; + const len = target.vertices.length - 1; + ctx.fillStyle = "rgba(110,170,200," + (0.2 + 0.4 * Math.random()) + ")"; + ctx.lineWidth = 1; + ctx.strokeStyle = "#000"; + ctx.beginPath(); + ctx.moveTo( + mech.pos.x + eye * Math.cos(this.angle), + mech.pos.y + eye * Math.sin(this.angle) + ); + ctx.lineTo(target.vertices[len].x, target.vertices[len].y); + ctx.lineTo(target.vertices[0].x, target.vertices[0].y); + ctx.fill(); + if (stroke) ctx.stroke(); + for (let i = 0; i < len; i++) { + ctx.beginPath(); + ctx.moveTo( + mech.pos.x + eye * Math.cos(this.angle), + mech.pos.y + eye * Math.sin(this.angle) + ); + ctx.lineTo(target.vertices[i].x, target.vertices[i].y); + ctx.lineTo(target.vertices[i + 1].x, target.vertices[i + 1].y); + ctx.fill(); + if (stroke) ctx.stroke(); + } + }, + holding() { + this.fieldMeter -= this.fieldRegen; + if (this.fieldMeter < 0) this.fieldMeter = 0; + Matter.Body.setPosition(this.holdingTarget, { + x: mech.pos.x + 70 * Math.cos(this.angle), + y: mech.pos.y + 70 * Math.sin(this.angle) + }); + Matter.Body.setVelocity(this.holdingTarget, player.velocity); + Matter.Body.rotate(this.holdingTarget, 0.01 / this.holdingTarget.mass); //gently spin the block + }, + throw () { + if ((keys[32] || game.mouseDownRight)) { + if (this.fieldMeter > 0.0007) { + this.fieldMeter -= 0.0007; + this.throwCharge += this.throwChargeRate;; + //draw charge + const x = mech.pos.x + 15 * Math.cos(this.angle); + const y = mech.pos.y + 15 * Math.sin(this.angle); + const len = this.holdingTarget.vertices.length - 1; + const edge = this.throwCharge * this.throwCharge * 0.02; + const grd = ctx.createRadialGradient(x, y, edge, x, y, edge + 5); + grd.addColorStop(0, "rgba(255,50,150,0.3)"); + grd.addColorStop(1, "transparent"); + ctx.fillStyle = grd; + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(this.holdingTarget.vertices[len].x, this.holdingTarget.vertices[len].y); + ctx.lineTo(this.holdingTarget.vertices[0].x, this.holdingTarget.vertices[0].y); + ctx.fill(); + for (let i = 0; i < len; i++) { + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(this.holdingTarget.vertices[i].x, this.holdingTarget.vertices[i].y); + ctx.lineTo(this.holdingTarget.vertices[i + 1].x, this.holdingTarget.vertices[i + 1].y); + ctx.fill(); + } + } else { + this.drop() + } + } else if (this.throwCharge > 0) { + //throw the body + this.fireCDcycle = mech.cycle + this.fieldFireCD; + this.isHolding = false; + //bullet-like collisions + this.holdingTarget.collisionFilter.category = 0x000100; + this.holdingTarget.collisionFilter.mask = 0x110111; + //check every second to see if player is away from thrown body, and make solid + const solid = function (that) { + const dx = that.position.x - player.position.x; + const dy = that.position.y - player.position.y; + if (dx * dx + dy * dy > 10000 && that.speed < 3 && that !== mech.holdingTarget) { + that.collisionFilter.category = 0x010000; //make solid + that.collisionFilter.mask = 0x011111; + } else { + setTimeout(solid, 50, that); + } + }; + setTimeout(solid, 200, this.holdingTarget); + //throw speed scales a bit with mass + const speed = Math.min(85, Math.min(54 / this.holdingTarget.mass + 5, 48) * Math.min(this.throwCharge, this.throwChargeMax) / 50); + + this.throwCharge = 0; + Matter.Body.setVelocity(this.holdingTarget, { + x: player.velocity.x + Math.cos(this.angle) * speed, + y: player.velocity.y + Math.sin(this.angle) * speed + }); + //player recoil //stronger in x-dir to prevent jump hacking + Matter.Body.setVelocity(player, { + x: player.velocity.x - Math.cos(this.angle) * speed / 20 * Math.sqrt(this.holdingTarget.mass), + y: player.velocity.y - Math.sin(this.angle) * speed / 80 * Math.sqrt(this.holdingTarget.mass) + }); + this.definePlayerMass() //return to normal player mass + } + }, + drawField() { + if (mech.holdingTarget) { + ctx.fillStyle = "rgba(110,170,200," + (mech.fieldMeter * (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.fieldMeter * (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 = this.grabRange - 20; + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, range, mech.angle - Math.PI * mech.fieldArc, mech.angle + Math.PI * mech.fieldArc, false); + 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.fieldCDcycle < mech.cycle) { + const grabPowerUpRange2 = (this.grabRange + 220) * (this.grabRange + 220) + for (let i = 0, len = powerUp.length; i < len; ++i) { + const dxP = mech.pos.x - powerUp[i].position.x; + const dyP = mech.pos.y - powerUp[i].position.y; + const dist2 = dxP * dxP + dyP * dyP; + // float towards player if looking at and in range or if very close to player + if (dist2 < grabPowerUpRange2 && this.lookingAt(powerUp[i]) || dist2 < 16000) { + if (dist2 < 5000) { //use power up if it is close enough + Matter.Body.setVelocity(player, { //player knock back, after grabbing power up + x: player.velocity.x + ((powerUp[i].velocity.x * powerUp[i].mass) / player.mass) * 0.3, + y: player.velocity.y + ((powerUp[i].velocity.y * powerUp[i].mass) / player.mass) * 0.3 + }); + mech.usePowerUp(i); + return; + } + this.fieldMeter -= this.fieldRegen * 0.5; + powerUp[i].force.x += 7 * (dxP / dist2) * powerUp[i].mass; + powerUp[i].force.y += 7 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * game.g; //negate gravity + //extra friction + Matter.Body.setVelocity(powerUp[i], { + x: powerUp[i].velocity.x * 0.11, + y: powerUp[i].velocity.y * 0.11 + }); + } + } + } + }, + pushMass(who) { + const fieldBlockCost = Math.max(0.02, who.mass * 0.012) //0.012 + if (this.fieldMeter > fieldBlockCost) { + this.fieldMeter -= fieldBlockCost * this.fieldShieldingScale; + if (this.fieldMeter < 0) this.fieldMeter = 0; + this.drawHold(who); + //knock backs + const angle = Math.atan2(player.position.y - who.position.y, player.position.x - who.position.x); + const mass = Math.min(Math.sqrt(who.mass), 4); + Matter.Body.setVelocity(who, { + x: player.velocity.x - (15 * Math.cos(angle)) / mass, + y: player.velocity.y - (15 * Math.sin(angle)) / mass + }); + Matter.Body.setVelocity(player, { + x: player.velocity.x + 5 * Math.cos(angle) * mass, + y: player.velocity.y + 5 * Math.sin(angle) * mass + }); + } + }, + pushMobsFacing() { // find mobs in range and in direction looking + for (let i = 0, len = mob.length; i < len; ++i) { + if ( + Matter.Vector.magnitude(Matter.Vector.sub(mob[i].position, this.pos)) < this.grabRange && + this.lookingAt(mob[i]) && + Matter.Query.ray(map, mob[i].position, this.pos).length === 0 + ) { + mob[i].locatePlayer(); + mech.pushMass(mob[i]); + } + } + }, + pushMobs360(range = this.grabRange * 0.75) { // find mobs in range in any direction + for (let i = 0, len = mob.length; i < len; ++i) { + if ( + Matter.Vector.magnitude(Matter.Vector.sub(mob[i].position, this.pos)) < range && + Matter.Query.ray(map, mob[i].position, this.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 && + Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)) < this.grabRange && + this.lookingAt(body[i]) && + Matter.Query.ray(map, body[i].position, this.pos).length === 0 + ) { + mech.pushMass(body[i]); + } + } + }, + pushBody360(range = this.grabRange * 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 && + Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)) < range && + this.lookingAt(body[i]) && + Matter.Query.ray(map, body[i].position, this.pos).length === 0 && + body[i].collisionFilter.category === 0x010000 + ) { + mech.pushMass(body[i]); + } + } + }, + lookForPickUp(range = this.grabRange) { //find body to pickup + this.fieldMeter -= this.fieldRegen; + const grabbing = { + targetIndex: null, + targetRange: range, + // lookingAt: false //false to pick up object in range, but not looking at + }; + for (let i = 0, len = body.length; i < len; ++i) { + if (Matter.Query.ray(map, body[i].position, this.pos).length === 0) { + //is this next body a better target then my current best + const dist = Matter.Vector.magnitude(Matter.Vector.sub(body[i].position, this.pos)); + const looking = this.lookingAt(body[i]); + // if (dist < grabbing.targetRange && (looking || !grabbing.lookingAt) && !body[i].isNotHoldable) { + if (dist < grabbing.targetRange && looking && !body[i].isNotHoldable) { + grabbing.targetRange = dist; + grabbing.targetIndex = i; + // grabbing.lookingAt = looking; + } + } + } + // set pick up target for when mouse is released + if (body[grabbing.targetIndex]) { + this.holdingTarget = body[grabbing.targetIndex]; + // + ctx.beginPath(); //draw on each valid body + let vertices = this.holdingTarget.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j += 1) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.fillStyle = "rgba(190,215,230," + (0.3 + 0.7 * Math.random()) + ")"; + ctx.fill(); + + ctx.globalAlpha = 0.2; + this.drawHold(this.holdingTarget); + ctx.globalAlpha = 1; + } else { + this.holdingTarget = null; + } + }, + pickUp() { + //triggers when a hold target exits and field button is released + this.isHolding = true; + this.definePlayerMass(mech.defaultMass + this.holdingTarget.mass * this.holdingMassScale) + //collide with nothing + this.holdingTarget.collisionFilter.category = 0x000000; + this.holdingTarget.collisionFilter.mask = 0x000000; + // if (this.holdingTarget) { + // this.holdingTarget.collisionFilter.category = 0x010000; + // this.holdingTarget.collisionFilter.mask = 0x011111; + // } + // combine momentum // this doesn't feel right in game + // const px = player.velocity.x * player.mass + this.holdingTarget.velocity.x * this.holdingTarget.mass; + // const py = player.velocity.y * player.mass + this.holdingTarget.velocity.y * this.holdingTarget.mass; + // Matter.Body.setVelocity(player, { + // x: px / (player.mass + this.holdingTarget.mass), + // y: py / (player.mass + this.holdingTarget.mass) + // }); + }, + 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() {}, + fieldText() { + game.replaceTextLog = true; + game.makeTextLog(`${game.SVGrightMouse} ${mech.fieldUpgrades[mech.fieldMode].name}

${mech.fieldUpgrades[mech.fieldMode].description}`, 1000); + game.replaceTextLog = false; + document.getElementById("field").innerHTML = mech.fieldUpgrades[mech.fieldMode].name //add field + }, + fieldUpgrades: [{ + name: "field emitter", + description: "use energy to shield yourself from damage
lets you pick up and throw objects", + effect: () => { + mech.fieldMode = 0; + mech.fieldText(); + game.replaceTextLog = true; //allow text over write + // game.makeTextLog("
(right click or space bar)

", 1200); + mech.setHoldDefaults(); + mech.hold = function () { + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed + mech.drawField(); + mech.grabPowerUp(); + mech.pushMobsFacing(); + mech.pushBodyFacing(); + mech.lookForPickUp(); + } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //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: "time dilation field", + description: "use energy to stop time
can fire bullets while field is active", + effect: () => { + mech.fieldMode = 1; + mech.fieldText(); + mech.setHoldDefaults(); + mech.fieldFire = true; + mech.grabRange = 130 + mech.isBodiesAsleep = false; + mech.hold = function () { + if (mech.isHolding) { + mech.wakeCheck(); + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { + const DRAIN = 0.0027 + if (mech.fieldMeter > DRAIN) { + mech.fieldMeter -= DRAIN; + + //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 + + mech.grabPowerUp(); + mech.lookForPickUp(180); + } else { + mech.wakeCheck(); + mech.fieldCDcycle = mech.cycle + 120; + } + } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //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() + if (mech.fieldMode !== 1) { + //wake up if this is no longer the current field mode, like after a new power up + mech.wakeCheck(); + + } + } + } + }, + { + name: "plasma torch", + description: "use energy to emit damaging plasma
decreased shield range and efficiency", + effect: () => { + mech.fieldMode = 2; + mech.fieldText(); + mech.setHoldDefaults(); + // mech.fieldShieldingScale = 2; + // mech.grabRange = 125; + mech.fieldArc = 0.1 //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) + mech.calculateFieldThreshold(); //run after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) + mech.hold = function () { + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { //not hold but field button is pressed + const DRAIN = 0.0006 + if (mech.fieldMeter > DRAIN) { + mech.fieldMeter -= DRAIN; + + //calculate laser collision + let best; + let range = 80 + (mech.crouch ? 500 : 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(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 && (!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.35 * b.dmgScale; //********** SCALE DAMAGE HERE ********************* + best.who.damage(dmg); + best.who.locatePlayer(); + + //push mobs away + const force = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(mech.pos, path[1])), -0.01 * Math.sqrt(best.who.mass)) + Matter.Body.applyForce(best.who, path[1], force) + // const angle = Math.atan2(player.position.y - best.who.position.y, player.position.x - best.who.position.x); + // const mass = Math.min(Math.sqrt(best.who.mass), 6); + // Matter.Body.setVelocity(best.who, { + // x: best.who.velocity.x * 0.85 - 3 * Math.cos(angle) / mass, + // y: best.who.velocity.y * 0.85 - 3 * Math.sin(angle) / mass + // }); + + //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 = Matter.Vector.mult(Matter.Vector.normalise(Matter.Vector.sub(mech.pos, path[1])), -0.006 * 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.beginPath(); + ctx.moveTo(path[0].x, path[0].y); + ctx.lineTo(path[1].x, path[1].y); + 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 = range / 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(); + + mech.pushMobs360(110); + // mech.pushBody360(100); //disabled because doesn't work at short range + mech.grabPowerUp(); + mech.lookForPickUp(); + } else { + mech.fieldCDcycle = mech.cycle + 120; //if out of energy + } + } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //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: "negative mass field", + description: "use energy to nullify   gravity
can fire bullets while active", + effect: () => { + mech.fieldMode = 3; + mech.fieldText(); + mech.setHoldDefaults(); + mech.fieldFire = true; + + mech.hold = function () { + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { //push away + const DRAIN = 0.0004 + if (mech.fieldMeter > DRAIN) { + mech.pushMobs360(170); + mech.pushBody360(180); + mech.grabPowerUp(); + mech.lookForPickUp(170); + //look for nearby objects to make zero-g + function zeroG(who, mag = 1.06) { + for (let i = 0, len = who.length; i < len; ++i) { + sub = Matter.Vector.sub(who[i].position, mech.pos); + dist = Matter.Vector.magnitude(sub); + if (dist < mech.grabRange) { + 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? + + Matter.Body.setVelocity(player, { + x: player.velocity.x, + y: player.velocity.y * 0.97 + }); + + if (keys[83] || keys[40]) { //down + player.force.y -= 0.8 * player.mass * mech.gravity; + mech.grabRange = mech.grabRange * 0.97 + 400 * 0.03; + zeroG(powerUp, 0.85); + zeroG(body, 0.85); + } else if (keys[87] || keys[38]) { //up + mech.fieldMeter -= 5 * DRAIN; + mech.grabRange = mech.grabRange * 0.97 + 750 * 0.03; + player.force.y -= 1.2 * player.mass * mech.gravity; + zeroG(powerUp, 1.13); + zeroG(body, 1.13); + } else { + mech.fieldMeter -= DRAIN; + mech.grabRange = mech.grabRange * 0.97 + 650 * 0.03; + player.force.y -= 1.07 * player.mass * mech.gravity; // slow upward drift + zeroG(powerUp); + zeroG(body); + } + + //add extra friction for horizontal motion + if (keys[65] || keys[68] || keys[37] || keys[39]) { + Matter.Body.setVelocity(player, { + x: player.velocity.x * 0.85, + y: player.velocity.y + }); + } + + //draw zero-G range + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, mech.grabRange, 0, 2 * Math.PI); + ctx.fillStyle = "#f5f5ff"; + ctx.globalCompositeOperation = "difference"; + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + } else { + //trigger cool down + mech.fieldCDcycle = mech.cycle + 120; + } + } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //holding, but field button is released + mech.pickUp(); + mech.grabRange = 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) + mech.grabRange = 0 + } + mech.drawFieldMeter() + } + } + }, + { + name: "standing wave harmonics", + description: "oscillating shields surround you constantly
decreased energy regeneration", + effect: () => { + mech.fieldMode = 4; + mech.fieldText(); + mech.setHoldDefaults(); + mech.fieldRegen *= 0.3; + + mech.hold = function () { + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed + mech.grabPowerUp(); + mech.lookForPickUp(180); + } else if (mech.holdingTarget && mech.fireCDcycle < 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.fieldMeter > 0.1) { + const grabRange1 = 90 + 60 * Math.sin(mech.cycle / 23) + const grabRange2 = 85 + 70 * Math.sin(mech.cycle / 37) + const grabRange3 = 80 + 80 * Math.sin(mech.cycle / 47) + const netGrabRange = Math.max(grabRange1, grabRange2, grabRange3) + ctx.fillStyle = "rgba(110,170,200," + (0.04 + mech.fieldMeter * (0.12 + 0.13 * Math.random())) + ")"; + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, grabRange1, 0, 2 * Math.PI); + ctx.fill(); + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, grabRange2, 0, 2 * Math.PI); + ctx.fill(); + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, grabRange3, 0, 2 * Math.PI); + ctx.fill(); + mech.pushMobs360(netGrabRange); + mech.pushBody360(netGrabRange); + } + mech.drawFieldMeter() + } + } + }, + { + name: "nano-scale manufacturing", + description: "excess energy used to build drones
3x energy regeneration", + effect: () => { + let gunIndex = 13 //Math.random() < 0.5 ? 13 : 14 + mech.fieldMode = 5; + mech.fieldText(); + mech.setHoldDefaults(); + mech.fieldRegen *= 3; + mech.hold = function () { + if (mech.fieldMeter === 1) { + mech.fieldMeter -= 0.43; + b.guns[gunIndex].fire() //spawn drone + mech.fireCDcycle = mech.cycle + 25; // set fire cool down to prevent +energy from making huge numbers of drones + } + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if ((keys[32] || game.mouseDownRight && mech.fieldMeter > 0.1)) { //not hold but field button is pressed + mech.pushMobsFacing(); + mech.pushBodyFacing(); + mech.drawField(); + mech.grabPowerUp(); + mech.lookForPickUp(); + } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //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: "phase decoherence field", + description: "use energy to to become intangible
can't see or be seen outside field", + effect: () => { + mech.fieldMode = 6; + mech.fieldText(); + mech.setHoldDefaults(); + // mech.grabRange = 230 + mech.hold = function () { + mech.isStealth = false //isStealth is checked in mob foundPlayer() + player.collisionFilter.mask = 0x010011 //0x010011 is normal + if (mech.isHolding) { + mech.drawHold(mech.holdingTarget); + mech.holding(); + mech.throw(); + } else if ((keys[32] || game.mouseDownRight) && mech.fieldCDcycle < mech.cycle) { + const DRAIN = 0.0015 + if (mech.fieldMeter > DRAIN) { + mech.fieldMeter -= DRAIN; + + mech.isStealth = true //isStealth is checked in mob foundPlayer() + player.collisionFilter.mask = 0x000001 //0x010011 is normals + + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, mech.grabRange, 0, 2 * Math.PI); + ctx.globalCompositeOperation = "destination-in"; //in or atop + ctx.fillStyle = `rgba(255,255,255,${mech.fieldMeter*0.5})`; + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + ctx.strokeStyle = "#000" + ctx.lineWidth = 2; + ctx.stroke(); + + mech.grabPowerUp(); + mech.lookForPickUp(110); + } else { + mech.fieldCDcycle = mech.cycle + 120; + } + } else if (mech.holdingTarget && mech.fireCDcycle < mech.cycle && mech.fieldMeter > 0.05) { //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() + } + } + }, + ], }; \ No newline at end of file diff --git a/js/powerups.js b/js/powerups.js index 892acb9..d3cbed4 100644 --- a/js/powerups.js +++ b/js/powerups.js @@ -1,239 +1,239 @@ -let powerUp = []; - -const powerUps = { - heal: { - name: "heal", - color: "#0fb", - size() { - return 40 * Math.sqrt(0.1 + Math.random() * 0.5); - }, - effect() { - let heal = (this.size / 40) ** 2 - if (b.fullHeal) heal = Infinity - heal = Math.min(1 - mech.health, heal) - mech.addHealth(heal); - if (heal > 0) game.makeTextLog("
  heal " + (heal * 100).toFixed(0) + "%", 300) - } - }, - ammo: { - name: "ammo", - color: "#467", - size() { - return 17; - }, - effect() { - //only get ammo for guns player has - let target; - // console.log(b.inventory.length) - if (b.inventory.length > 0) { - //add ammo to a gun in inventory - target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]]; - //try 3 more times to give ammo to a gun with ammo, not Infinity - if (target.ammo === Infinity) { - target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]] - if (target.ammo === Infinity) { - target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]] - if (target.ammo === Infinity) target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]] - } - } - } else { - //if you don't have any guns just add ammo to a random gun you don't have yet - target = b.guns[Math.floor(Math.random() * b.guns.length)]; - } - if (target.ammo === Infinity) { - mech.fieldMeter = 1; - if (!game.lastLogTime) game.makeTextLog("+energy", 300); - } else { - //ammo given scales as mobs take more hits to kill - let ammo = Math.ceil((target.ammoPack * (0.45 + 0.06 * Math.random())) / Math.sqrt(b.dmgScale)); - if (level.isBuildRun) ammo = Math.floor(ammo * 1.2) - target.ammo += ammo; - game.updateGunHUD(); - game.makeTextLog("
  +" + ammo + " ammo for " + target.name + "", 300); - } - } - }, - field: { - name: "field", - color: "#0cf", - size() { - return 45; - }, - effect() { - const previousMode = mech.fieldMode - - if (!this.mode) { //this.mode is set if the power up has been ejected from player - mode = mech.fieldMode - while (mode === mech.fieldMode) { - mode = Math.ceil(Math.random() * (mech.fieldUpgrades.length - 1)) - } - mech.fieldUpgrades[mode].effect(); //choose random field upgrade that you don't already have - } else { - mech.fieldUpgrades[this.mode].effect(); //set a predetermined power up - } - //pop the old field out in case player wants to swap back - if (previousMode !== 0) { - mech.fieldCDcycle = mech.cycle + 40; //trigger fieldCD to stop power up grab automatic pick up of spawn - powerUps.spawn(mech.pos.x, mech.pos.y - 15, "field", false, previousMode); - } - } - }, - mod: { - name: "mod", - color: "#a8f", - size() { - return 42; - }, - effect() { - //find what mods I don't have - let options = []; - for (let i = 0; i < b.mods.length; i++) { - if (!b.mods[i].have) options.push(i); - } - //give a random mod from the mods I don't have - if (options.length > 0) { - let newMod = options[Math.floor(Math.random() * options.length)] - b.giveMod(newMod) - game.replaceTextLog = true; - game.makeTextLog(`
  ${b.mods[newMod].name}

${b.mods[newMod].description}`, 1000); - game.replaceTextLog = false; - } - } - }, - gun: { - name: "gun", - color: "#26a", - size() { - return 35; - }, - effect() { - //find what guns I don't have - let options = []; - if (b.activeGun === null && game.difficulty < 3) { - //choose the first gun to be one that is good for the early game - for (let i = 0; i < b.guns.length; ++i) { - if (!b.guns[i].have && b.guns[i].isStarterGun) options.push(i); - } - } else { - //choose a gun you don't have - for (let i = 0; i < b.guns.length; ++i) { - if (!b.guns[i].have) options.push(i); - } - } - //give player a gun they don't already have if possible - game.replaceTextLog = true; - if (options.length > 0) { - let newGun = options[Math.floor(Math.random() * options.length)]; - if (b.activeGun === null) b.activeGun = newGun //if no active gun switch to new gun - game.makeTextLog(`${game.SVGleftMouse} ${b.guns[newGun].name}

${b.guns[newGun].description}`, 900); - b.guns[newGun].have = true; - b.inventory.push(newGun); - b.guns[newGun].ammo += b.guns[newGun].ammoPack * 2; - game.makeGunHUD(); - } else { - //if you have all guns then get ammo - const ammoTarget = Math.floor(Math.random() * (b.guns.length)); - const ammo = Math.ceil(b.guns[ammoTarget].ammoPack * 2); - b.guns[ammoTarget].ammo += ammo; - game.updateGunHUD(); - game.makeTextLog("+" + ammo + " ammo for " + b.guns[ammoTarget].name + "", 300); - } - game.replaceTextLog = false - } - }, - spawnRandomPowerUp(x, y) { //mostly used after mob dies - if (Math.random() * Math.random() - 0.25 > Math.sqrt(mech.health) || Math.random() < 0.04) { //spawn heal chance is higher at low health - powerUps.spawn(x, y, "heal"); - if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "heal"); - return; - } - if (Math.random() < 0.2 && b.inventory.length > 0) { - powerUps.spawn(x, y, "ammo"); - if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "ammo"); - return; - } - if (Math.random() < 0.004 * (4 - b.inventory.length)) { //a new gun has a low chance for each not acquired gun to drop - powerUps.spawn(x, y, "gun"); - if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "gun"); - return; - } - if (Math.random() < 0.004 * (7 - b.modCount)) { - powerUps.spawn(x, y, "mod"); - if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "mod"); - return; - } - if (Math.random() < 0.005) { - powerUps.spawn(x, y, "field"); - if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field"); - return; - } - }, - spawnBossPowerUp(x, y) { //boss spawns field and gun mod upgrades - if (mech.fieldMode === 0) { - powerUps.spawn(x, y, "field") - if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field") - } else if (Math.random() < 0.3) { - powerUps.spawn(x, y, "mod") - if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "mod") - } else if (Math.random() < 0.3) { - powerUps.spawn(x, y, "field"); - if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field"); - } else if (Math.random() < 0.3) { - powerUps.spawn(x, y, "gun") - if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "gun") - } else if (mech.health < 0.6) { - powerUps.spawn(x, y, "heal"); - if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "heal"); - } else { - powerUps.spawn(x, y, "ammo"); - if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "ammo"); - } - }, - chooseRandomPowerUp(x, y) { //100% chance to drop a random power up //used in spawn.debris - if (Math.random() < 0.5) { - powerUps.spawn(x, y, "heal", false); - } else { - powerUps.spawn(x, y, "ammo", false); - } - }, - spawnStartingPowerUps(x, y) { //used for map specific power ups, mostly to give player a starting gun - if (b.inventory.length < 2 || game.isEasyMode) { - powerUps.spawn(x, y, "gun", false); //starting gun - } else { - powerUps.spawnRandomPowerUp(x, y); - powerUps.spawnRandomPowerUp(x, y); - powerUps.spawnRandomPowerUp(x, y); - powerUps.spawnRandomPowerUp(x, y); - } - }, - spawn(x, y, target, moving = true, mode = null) { - if (!level.isBuildRun || target === "heal" || target === "ammo") { - let i = powerUp.length; - target = powerUps[target]; - size = target.size(); - powerUp[i] = Matter.Bodies.polygon(x, y, 0, size, { - density: 0.001, - frictionAir: 0.01, - restitution: 0.8, - inertia: Infinity, //prevents rotation - collisionFilter: { - group: 0, - category: 0x100000, - mask: 0x100001 - }, - color: target.color, - effect: target.effect, - mode: mode, - name: target.name, - size: size - }); - if (moving) { - Matter.Body.setVelocity(powerUp[i], { - x: (Math.random() - 0.5) * 15, - y: Math.random() * -9 - 3 - }); - } - World.add(engine.world, powerUp[i]); //add to world - } - }, +let powerUp = []; + +const powerUps = { + heal: { + name: "heal", + color: "#0fb", + size() { + return 40 * Math.sqrt(0.1 + Math.random() * 0.5); + }, + effect() { + let heal = (this.size / 40) ** 2 + if (b.fullHeal) heal = Infinity + heal = Math.min(1 - mech.health, heal) + mech.addHealth(heal); + if (heal > 0) game.makeTextLog("
  heal " + (heal * 100).toFixed(0) + "%", 300) + } + }, + ammo: { + name: "ammo", + color: "#467", + size() { + return 17; + }, + effect() { + //only get ammo for guns player has + let target; + // console.log(b.inventory.length) + if (b.inventory.length > 0) { + //add ammo to a gun in inventory + target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]]; + //try 3 more times to give ammo to a gun with ammo, not Infinity + if (target.ammo === Infinity) { + target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]] + if (target.ammo === Infinity) { + target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]] + if (target.ammo === Infinity) target = b.guns[b.inventory[Math.floor(Math.random() * (b.inventory.length))]] + } + } + } else { + //if you don't have any guns just add ammo to a random gun you don't have yet + target = b.guns[Math.floor(Math.random() * b.guns.length)]; + } + if (target.ammo === Infinity) { + mech.fieldMeter = 1; + if (!game.lastLogTime) game.makeTextLog("+energy", 300); + } else { + //ammo given scales as mobs take more hits to kill + let ammo = Math.ceil((target.ammoPack * (0.45 + 0.06 * Math.random())) / Math.sqrt(b.dmgScale)); + if (level.isBuildRun) ammo = Math.floor(ammo * 1.2) + target.ammo += ammo; + game.updateGunHUD(); + game.makeTextLog("
  +" + ammo + " ammo for " + target.name + "", 300); + } + } + }, + field: { + name: "field", + color: "#0cf", + size() { + return 45; + }, + effect() { + const previousMode = mech.fieldMode + + if (!this.mode) { //this.mode is set if the power up has been ejected from player + mode = mech.fieldMode + while (mode === mech.fieldMode) { + mode = Math.ceil(Math.random() * (mech.fieldUpgrades.length - 1)) + } + mech.fieldUpgrades[mode].effect(); //choose random field upgrade that you don't already have + } else { + mech.fieldUpgrades[this.mode].effect(); //set a predetermined power up + } + //pop the old field out in case player wants to swap back + if (previousMode !== 0) { + mech.fieldCDcycle = mech.cycle + 40; //trigger fieldCD to stop power up grab automatic pick up of spawn + powerUps.spawn(mech.pos.x, mech.pos.y - 15, "field", false, previousMode); + } + } + }, + mod: { + name: "mod", + color: "#a8f", + size() { + return 42; + }, + effect() { + //find what mods I don't have + let options = []; + for (let i = 0; i < b.mods.length; i++) { + if (!b.mods[i].have) options.push(i); + } + //give a random mod from the mods I don't have + if (options.length > 0) { + let newMod = options[Math.floor(Math.random() * options.length)] + b.giveMod(newMod) + game.replaceTextLog = true; + game.makeTextLog(`
  ${b.mods[newMod].name}

${b.mods[newMod].description}`, 1000); + game.replaceTextLog = false; + } + } + }, + gun: { + name: "gun", + color: "#26a", + size() { + return 35; + }, + effect() { + //find what guns I don't have + let options = []; + if (b.activeGun === null && game.difficulty < 3) { + //choose the first gun to be one that is good for the early game + for (let i = 0; i < b.guns.length; ++i) { + if (!b.guns[i].have && b.guns[i].isStarterGun) options.push(i); + } + } else { + //choose a gun you don't have + for (let i = 0; i < b.guns.length; ++i) { + if (!b.guns[i].have) options.push(i); + } + } + //give player a gun they don't already have if possible + game.replaceTextLog = true; + if (options.length > 0) { + let newGun = options[Math.floor(Math.random() * options.length)]; + if (b.activeGun === null) b.activeGun = newGun //if no active gun switch to new gun + game.makeTextLog(`${game.SVGleftMouse} ${b.guns[newGun].name}

${b.guns[newGun].description}`, 900); + b.guns[newGun].have = true; + b.inventory.push(newGun); + b.guns[newGun].ammo += b.guns[newGun].ammoPack * 2; + game.makeGunHUD(); + } else { + //if you have all guns then get ammo + const ammoTarget = Math.floor(Math.random() * (b.guns.length)); + const ammo = Math.ceil(b.guns[ammoTarget].ammoPack * 2); + b.guns[ammoTarget].ammo += ammo; + game.updateGunHUD(); + game.makeTextLog("+" + ammo + " ammo for " + b.guns[ammoTarget].name + "", 300); + } + game.replaceTextLog = false + } + }, + spawnRandomPowerUp(x, y) { //mostly used after mob dies + if (Math.random() * Math.random() - 0.25 > Math.sqrt(mech.health) || Math.random() < 0.04) { //spawn heal chance is higher at low health + powerUps.spawn(x, y, "heal"); + if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "heal"); + return; + } + if (Math.random() < 0.2 && b.inventory.length > 0) { + powerUps.spawn(x, y, "ammo"); + if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "ammo"); + return; + } + if (Math.random() < 0.004 * (4 - b.inventory.length)) { //a new gun has a low chance for each not acquired gun to drop + powerUps.spawn(x, y, "gun"); + if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "gun"); + return; + } + if (Math.random() < 0.0035 * (7 - b.modCount)) { + powerUps.spawn(x, y, "mod"); + if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "mod"); + return; + } + if (Math.random() < 0.005) { + powerUps.spawn(x, y, "field"); + if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field"); + return; + } + }, + spawnBossPowerUp(x, y) { //boss spawns field and gun mod upgrades + if (mech.fieldMode === 0) { + powerUps.spawn(x, y, "field") + if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field") + } else if (Math.random() < 0.27) { + powerUps.spawn(x, y, "mod") + if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "mod") + } else if (Math.random() < 0.27) { + powerUps.spawn(x, y, "field"); + if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "field"); + } else if (Math.random() < 0.27) { + powerUps.spawn(x, y, "gun") + if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "gun") + } else if (mech.health < 0.6) { + powerUps.spawn(x, y, "heal"); + if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "heal"); + } else { + powerUps.spawn(x, y, "ammo"); + if (Math.random() < b.modMoreDrops) powerUps.spawn(x, y, "ammo"); + } + }, + chooseRandomPowerUp(x, y) { //100% chance to drop a random power up //used in spawn.debris + if (Math.random() < 0.5) { + powerUps.spawn(x, y, "heal", false); + } else { + powerUps.spawn(x, y, "ammo", false); + } + }, + spawnStartingPowerUps(x, y) { //used for map specific power ups, mostly to give player a starting gun + if (b.inventory.length < 2 || game.isEasyMode) { + powerUps.spawn(x, y, "gun", false); //starting gun + } else { + powerUps.spawnRandomPowerUp(x, y); + powerUps.spawnRandomPowerUp(x, y); + powerUps.spawnRandomPowerUp(x, y); + powerUps.spawnRandomPowerUp(x, y); + } + }, + spawn(x, y, target, moving = true, mode = null) { + if (!level.isBuildRun || target === "heal" || target === "ammo") { + let i = powerUp.length; + target = powerUps[target]; + size = target.size(); + powerUp[i] = Matter.Bodies.polygon(x, y, 0, size, { + density: 0.001, + frictionAir: 0.01, + restitution: 0.8, + inertia: Infinity, //prevents rotation + collisionFilter: { + group: 0, + category: 0x100000, + mask: 0x100001 + }, + color: target.color, + effect: target.effect, + mode: mode, + name: target.name, + size: size + }); + if (moving) { + Matter.Body.setVelocity(powerUp[i], { + x: (Math.random() - 0.5) * 15, + y: Math.random() * -9 - 3 + }); + } + World.add(engine.world, powerUp[i]); //add to world + } + }, }; \ No newline at end of file diff --git a/js/spawn.js b/js/spawn.js index c146b2e..39c666a 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -1,1693 +1,1693 @@ -//main object for spawning things in a level -const spawn = { - pickList: ["starter", "starter"], - fullPickList: [ - "chaser", "chaser", "chaser", - "striker", "striker", - "spinner", - "hopper", "hopper", "hopper", "hopper", - "grower", - "springer", - "shooter", "shooter", "shooter", "shooter", "shooter", - "beamer", - "focuser", - "laser", "laser", - // "blinker", //make blinker a boss - "sucker", - "exploder", "exploder", "exploder", - "spawner", - "ghoster", - "sneaker", - ], - allowedBossList: ["chaser", "spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter"], //"zoomer", - setSpawnList() { - //this is run at the start of each new level to determine the possible mobs for the level - //each level has 2 mobs: one new mob and one from the last level - spawn.pickList.splice(0, 1); - spawn.pickList.push(spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]); - }, - randomMob(x, y, chance = 1) { - if (Math.random() < chance + 0.09 * (game.difficulty - 1) && mob.length < 4 + game.difficulty * 1.7) { - 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 - 1) * 0.45 - 0.4), 4), 0), - size = 16 + Math.ceil(Math.random() * 15), - chance = 1) { - if (Math.random() < chance + (game.difficulty - 1) * 0.03 && mob.length < 4 + game.difficulty * 1.7) { - for (let i = 0; i < num; ++i) { - const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)]; - this[pick](x + Math.round((Math.random() - 0.5) * 20) + i * size * 2.5, y + Math.round((Math.random() - 0.5) * 20), size); - } - } - }, - randomBoss(x, y, chance = 1) { - if (Math.random() < chance + (game.difficulty - 1) * 0.14 && game.difficulty !== 1 && mob.length < 4 + 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; - } - } - if (canBeBoss) { - if (Math.random() < 0.55) { - this.nodeBoss(x, y, pick); - } else { - this.lineBoss(x, y, pick); - } - } else { - if (Math.random() < 0.07) { - this[pick](x, y, 90 + Math.random() * 40); //one extra large mob - } else if (Math.random() < 0.35) { - this.groupBoss(x, y) //hidden grouping blocks - } else { - pick = (Math.random() < 0.5) ? "randomList" : "random"; - if (Math.random() < 0.55) { - this.nodeBoss(x, y, pick); - } else { - this.lineBoss(x, y, pick); - } - } - } - } - }, - - //mob templates ********************************************************************************************* - //*********************************************************************************************************** - groupBoss(x, y, num = 5 + Math.random() * 8) { - for (let i = 0; i < num; i++) { - const radius = 25 + Math.floor(Math.random() * 20) - spawn.grouper(x + Math.random() * radius, y + Math.random() * radius, radius); - } - }, - grouper(x, y, radius = 27 + Math.floor(Math.random() * 10)) { - mobs.spawn(x, y, 4, radius, "#777"); - let me = mob[mob.length - 1]; - me.g = 0.0002; //required if using 'gravity' - me.accelMag = 0.0007 * game.accelScale; - me.groupingRangeMax = 250000 + Math.random() * 100000; - me.groupingRangeMin = (radius * 8) * (radius * 8); - me.groupingStrength = 0.0005 - me.memory = 200; - - me.do = function () { - this.gravity(); - if (this.seePlayer.recall) { - this.seePlayerByDistAndLOS(); - this.healthBar(); - this.attraction(); - //tether to other blocks - ctx.beginPath(); - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i] != this && mob[i].dropPowerUp) { //don't tether to self, bullets, shields, ... - const distance2 = Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, mob[i].position)) - if (distance2 < this.groupingRangeMax) { - if (!mob[i].seePlayer.recall) mob[i].seePlayerByDistAndLOS(); //wake up sleepy mobs - if (distance2 > this.groupingRangeMin) { - const angle = Math.atan2(mob[i].position.y - this.position.y, mob[i].position.x - this.position.x); - const forceMag = this.groupingStrength * mob[i].mass; - mob[i].force.x -= forceMag * Math.cos(angle); - mob[i].force.y -= forceMag * Math.sin(angle); - } - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(mob[i].position.x, mob[i].position.y); - } - } - } - ctx.strokeStyle = "#000"; - ctx.lineWidth = 1; - ctx.stroke(); - } - } - }, - starter(x, y, radius = 30) { - //easy mob for on level 1 - mobs.spawn(x, y, 8, radius, "#9ccdc6"); - let me = mob[mob.length - 1]; - me.accelMag = 0.00055 * game.accelScale; - me.memory = 60; - Matter.Body.setDensity(me, 0.0005) // normal density is 0.001 // this reduces life by half and decreases knockback - - me.do = function () { - this.healthBar(); - this.seePlayerByLookingAt(); - this.attraction(); - }; - }, - healer(x, y, radius = 20) { - //easy mob for on level 1 - 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 () { - this.healthBar(); - - 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 = Matter.Vector.sub(this.position, mob[i].position) - const DIST = Matter.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 = Matter.Vector.sub(this.position, player.position) - this.force = Matter.Vector.mult(Matter.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 = Matter.Vector.sub(this.position, this.lockedOn.position) - const DIST = Matter.Vector.magnitude(TARGET_VECTOR); - if (DIST > 250) { - this.force = Matter.Vector.mult(Matter.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 = Matter.Vector.sub(this.searchTarget, this.position); - if (Matter.Vector.magnitude(sub) > this.radius * 2) { - ctx.beginPath(); - ctx.strokeStyle = "#aaa"; - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(this.searchTarget.x, this.searchTarget.y); - ctx.stroke(); - //accelerate at 0.6 of normal acceleration - this.force = Matter.Vector.mult(Matter.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, "#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 * game.accelScale;; - me.g = me.accelMag * 0.6; //required if using 'gravity' - me.memory = 50; - if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); - me.do = function () { - this.healthBar(); - this.gravity(); - this.seePlayerCheck(); - this.attraction(); - }; - }, - grower(x, y, radius = 15) { - mobs.spawn(x, y, 7, radius, "hsl(144, 15%, 50%)"); - let me = mob[mob.length - 1]; - me.big = false; //required for grow - me.accelMag = 0.00045 * game.accelScale; - me.do = function () { - this.healthBar(); - this.seePlayerByLookingAt(); - this.attraction(); - this.grow(); - }; - }, - springer(x, y, radius = 20 + Math.ceil(Math.random() * 35)) { - mobs.spawn(x, y, 8, radius, "#b386e8"); - let me = mob[mob.length - 1]; - me.friction = 0; - me.frictionAir = 0.1; - me.lookTorque = 0.000005; - me.g = 0.0002; //required if using 'gravity' - me.seePlayerFreq = Math.round((40 + 25 * Math.random()) * game.lookFreqScale); - const springStiffness = 0.002; - const springDampening = 0.1; - - me.springTarget = { - x: me.position.x, - y: me.position.y - }; - const len = cons.length; - cons[len] = Constraint.create({ - pointA: me.springTarget, - bodyB: me, - stiffness: springStiffness, - damping: springDampening - }); - cons[len].length = 100 + 1.5 * radius; - me.cons = cons[len]; - - me.springTarget2 = { - x: me.position.x, - y: me.position.y - }; - const len2 = cons.length; - cons[len2] = Constraint.create({ - pointA: me.springTarget2, - bodyB: me, - stiffness: springStiffness, - damping: springDampening - }); - cons[len2].length = 100 + 1.5 * radius; - me.cons2 = cons[len2]; - - me.onDeath = function () { - this.removeCons(); - }; - if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); - me.do = function () { - this.healthBar(); - this.gravity(); - this.searchSpring(); - }; - }, - zoomer(x, y, radius = 20 + Math.ceil(Math.random() * 30)) { - mobs.spawn(x, y, 6, radius, "#ffe2fd"); - let me = mob[mob.length - 1]; - me.trailLength = 20; //required for trails - me.setupTrail(); //fill trails array up with the current position of mob - me.trailFill = "#ff00f0"; - me.g = 0.001; //required if using 'gravity' - me.frictionAir = 0.02; - me.accelMag = 0.004 * game.accelScale; - me.memory = 30; - me.zoomMode = 150; - me.onHit = function () { - this.zoomMode = 150; - }; - me.do = function () { - this.healthBar(); - this.seePlayerByDistAndLOS(); - this.zoom(); - this.gravity(); - }; - }, - hopper(x, y, radius = 30 + Math.ceil(Math.random() * 30)) { - mobs.spawn(x, y, 5, radius, "rgb(0,200,180)"); - let me = mob[mob.length - 1]; - me.accelMag = 0.04 * game.accelScale; - me.g = 0.0015; //required if using 'gravity' - me.frictionAir = 0.018; - me.restitution = 0; - me.delay = 110; - me.randomHopFrequency = 50 + Math.floor(Math.random() * 1000); - me.randomHopCD = game.cycle + me.randomHopFrequency; - me.do = function () { - this.healthBar(); - this.gravity(); - this.seePlayerCheck(); - this.hop(); - //randomly hob if not aware of player - if (this.randomHopCD < game.cycle && this.speed < 1 && !this.seePlayer.recall) { - this.randomHopCD = game.cycle + this.randomHopFrequency; - //slowly change randomHopFrequency after each hop - this.randomHopFrequency = Math.max(100, this.randomHopFrequency + (0.5 - Math.random()) * 200); - const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass * (0.1 + Math.random() * 0.3); - const angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI; - this.force.x += forceMag * Math.cos(angle); - this.force.y += forceMag * Math.sin(angle) - 0.04 * this.mass; //antigravity - } - }; - }, - spinner(x, y, radius = 30 + Math.ceil(Math.random() * 35)) { - mobs.spawn(x, y, 5, radius, "#000000"); - let me = mob[mob.length - 1]; - me.fill = "#28b"; - me.rememberFill = me.fill; - me.cdBurst1 = 0; //must add for burstAttraction - me.cdBurst2 = 0; //must add for burstAttraction - me.delay = 0; - me.burstDir = { - x: 0, - y: 0 - }; - me.accelMag = 0.16 * game.accelScale; - me.frictionAir = 0.022; - me.lookTorque = 0.0000014; - me.restitution = 0; - me.do = function () { - this.healthBar(); - this.seePlayerByLookingAt(); - //accelerate towards the player after a delay - if (this.seePlayer.recall) { - if (this.cdBurst2 < game.cycle && this.angularSpeed < 0.01) { - this.cdBurst2 = Infinity; - this.cdBurst1 = game.cycle + 40; - this.burstDir = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position)); - } else if (this.cdBurst1 < game.cycle) { - this.cdBurst2 = game.cycle + this.delay; - this.cdBurst1 = Infinity; - this.force = Matter.Vector.mult(this.burstDir, this.mass * 0.25); - this.fill = this.rememberFill; - } else if (this.cdBurst1 != Infinity) { - this.torque += 0.000035 * this.inertia; - this.fill = randomColor({ - hue: "blue" - }); - //draw attack vector - const mag = this.radius * 2 + 200; - const gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, mag); - gradient.addColorStop(0, "rgba(0,0,0,0.2)"); - gradient.addColorStop(1, "transparent"); - ctx.strokeStyle = gradient; - ctx.lineWidth = 5; - ctx.setLineDash([10, 20]); //30 - const dir = Matter.Vector.add(this.position, Matter.Vector.mult(this.burstDir, mag)); - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(dir.x, dir.y); - ctx.stroke(); - ctx.setLineDash([]); - } else { - this.fill = this.rememberFill; - } - } else { - this.cdBurst2 = 0; - } - }; - }, - sucker(x, y, radius = 30 + Math.ceil(Math.random() * 70)) { - radius = 9 + radius / 8; //extra small - mobs.spawn(x, y, 6, radius, "#000"); - let me = mob[mob.length - 1]; - me.stroke = "transparent"; //used for drawSneaker - me.eventHorizon = radius * 23; //required for blackhole - me.seeAtDistance2 = (me.eventHorizon + 500) * (me.eventHorizon + 500); //vision limit is event horizon - me.accelMag = 0.00009 * 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(); - if (this.seePlayer.recall) { - //eventHorizon waves in and out - eventHorizon = this.eventHorizon * (0.93 + 0.17 * Math.sin(game.cycle * 0.011)) - - //accelerate towards the player - const forceMag = this.accelMag * this.mass; - const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); - this.force.x += forceMag * Math.cos(angle); - this.force.y += forceMag * Math.sin(angle); - - //draw darkness - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, eventHorizon * 0.25, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.9)"; - ctx.fill(); - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, eventHorizon * 0.55, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.5)"; - ctx.fill(); - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.1)"; - ctx.fill(); - - this.healthBar(); - //when player is inside event horizon - if (Matter.Vector.magnitude(Matter.Vector.sub(this.position, player.position)) < eventHorizon) { - mech.damage(0.00015 * game.dmgScale); - if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.007 - - const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); - player.force.x -= 1.25 * Math.cos(angle) * player.mass * game.g * (mech.onGround ? 1.8 : 1); - player.force.y -= 0.96 * player.mass * game.g * Math.sin(angle); - //draw line to player - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(mech.pos.x, mech.pos.y); - ctx.lineWidth = Math.min(60, this.radius * 2); - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(); - ctx.beginPath(); - ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.3)"; - ctx.fill(); - } - } - } - }, - suckerBoss(x, y, radius = 20) { - mobs.spawn(x, y, 12, radius, "#000"); - let me = mob[mob.length - 1]; - 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 = 0x001100 - // 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) { - for (let i = 0; i < (game.difficulty - 3); ++i) { - spawn.sucker(this.position.x + (Math.random() - 0.5) * radius * 2, this.position.y + (Math.random() - 0.5) * radius * 2, 70 * Math.random()); - Matter.Body.setVelocity(mob[mob.length - 1], { - x: (Math.random() - 0.5) * 70, - y: (Math.random() - 0.5) * 70 - }); - } - } - }; - 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(); - 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 (Matter.Vector.magnitude(Matter.Vector.sub(this.position, player.position)) < eventHorizon) { - mech.damage(0.00015 * game.dmgScale); - if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.007 - const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); - player.force.x -= 1.3 * Math.cos(angle) * player.mass * game.g * (mech.onGround ? 1.7 : 1); - player.force.y -= 1.2 * Math.sin(angle) * player.mass * game.g; - //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.healthBar(); - this.curl(eventHorizon); - } - } - }, - 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; - if (Math.random() < Math.min(0.2 + (game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); - me.do = function () { - this.healthBar(); - this.seePlayerByLookingAt(); - this.attraction(); - this.repulsion(); - //laser beam - this.laserBeam(); - }; - }, - focuser(x, y, radius = 30 + Math.ceil(Math.random() * 10)) { - radius = Math.ceil(radius * 0.7); - mobs.spawn(x, y, 4, radius, "rgb(0,0,255)"); - let me = mob[mob.length - 1]; - Matter.Body.setDensity(me, 0.003); //extra dense //normal is 0.001 - me.restitution = 0; - me.laserPos = me.position; //required for laserTracking - me.repulsionRange = 1200000; //squared - me.accelMag = 0.0002 * game.accelScale; - me.frictionStatic = 0; - me.friction = 0; - me.onDamage = function () { - this.laserPos = this.position; - }; - // if (Math.random() < Math.min(0.2 + game.difficulty * 0.1, 0.7)) spawn.shield(me, x, y); - me.do = function () { - this.healthBar(); - if (!mech.isBodiesAsleep) { - this.seePlayerByLookingAt(); - const dist2 = this.distanceToPlayer2(); - //laser Tracking - if (this.seePlayer.yes && dist2 < 4000000 && !mech.isStealth) { - this.attraction(); - const rangeWidth = 2000; //this is sqrt of 4000000 from above if() - //targeting laser will slowly move from the mob to the player's position - this.laserPos = Matter.Vector.add(this.laserPos, Matter.Vector.mult(Matter.Vector.sub(player.position, this.laserPos), 0.1)); - let targetDist = Matter.Vector.magnitude(Matter.Vector.sub(this.laserPos, mech.pos)); - const r = 10; - - // ctx.setLineDash([15, 200]); - // ctx.lineDashOffset = 20*(game.cycle % 215); - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - if (targetDist < r + 15) { - // || dist2 < 80000 - targetDist = r + 10; - //charge at player - const forceMag = this.accelMag * 40 * this.mass; - const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); - this.force.x += forceMag * Math.cos(angle); - this.force.y += forceMag * Math.sin(angle); - } else { - //high friction if can't lock onto player - Matter.Body.setVelocity(this, { - x: this.velocity.x * 0.96, - y: this.velocity.y * 0.96 - }); - } - if (dist2 > 80000) { - const laserWidth = 0.002; - let laserOffR = Matter.Vector.rotateAbout(this.laserPos, (targetDist - r) * laserWidth, this.position); - let sub = Matter.Vector.normalise(Matter.Vector.sub(laserOffR, this.position)); - laserOffR = Matter.Vector.add(laserOffR, Matter.Vector.mult(sub, rangeWidth)); - ctx.lineTo(laserOffR.x, laserOffR.y); - - let laserOffL = Matter.Vector.rotateAbout(this.laserPos, (targetDist - r) * -laserWidth, this.position); - sub = Matter.Vector.normalise(Matter.Vector.sub(laserOffL, this.position)); - laserOffL = Matter.Vector.add(laserOffL, Matter.Vector.mult(sub, rangeWidth)); - ctx.lineTo(laserOffL.x, laserOffL.y); - // ctx.fillStyle = "rgba(0,0,255,0.15)"; - var gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, rangeWidth); - gradient.addColorStop(0, `rgba(0,0,255,${((r + 5) * (r + 5)) / (targetDist * targetDist)})`); - gradient.addColorStop(1, "transparent"); - ctx.fillStyle = gradient; - ctx.fill(); - } - } else { - this.laserPos = this.position; - } - }; - } - }, - laser(x, y, radius = 30) { - //only on level 1 - mobs.spawn(x, y, 3, radius, "#f00"); - let me = mob[mob.length - 1]; - me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front - Matter.Body.rotate(me, Math.random() * Math.PI * 2); - me.accelMag = 0.00007 * game.accelScale; - me.onHit = function () { - //run this function on hitting player - this.explode(); - }; - me.do = function () { - this.healthBar(); - this.seePlayerByLookingAt(); - this.attraction(); - this.laser(); - }; - }, - striker(x, y, radius = 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 = 100; - Matter.Body.rotate(me, Math.PI * 0.1); - me.onDamage = function () { - this.cd = game.cycle + this.delay; - }; - me.do = function () { - this.healthBar(); - this.seePlayerCheck(); - this.attraction(); - this.gravity(); - this.strike(); - }; - }, - sneaker(x, y, radius = 15 + Math.ceil(Math.random() * 25)) { - let me; - mobs.spawn(x, y, 5, radius, "transparent"); - me = mob[mob.length - 1]; - me.accelMag = 0.0007 * 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 = 0x010111; //can't touch player - // me.memory = 420; - me.do = function () { - - this.seePlayerCheck(); - this.attraction(); - this.gravity(); - //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 = 0x011111; //can touch player - } - } - //draw body - ctx.beginPath(); - const vertices = this.vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1, len = vertices.length; j < len; ++j) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - ctx.fillStyle = `rgba(0,0,0,${this.alpha * this.alpha})`; - ctx.fill(); - } else if (this.canTouchPlayer) { - this.canTouchPlayer = false; - this.collisionFilter.mask = 0x000111; //can't touch player - } - }; - }, - ghoster(x, y, radius = 40 + Math.ceil(Math.random() * 100)) { - let me; - mobs.spawn(x, y, 7, radius, "transparent"); - me = mob[mob.length - 1]; - me.seeAtDistance2 = 1000000; - me.accelMag = 0.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 = 0x000100; //move through walls and player - 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.attraction(); - this.search(); - //draw - if (this.distanceToPlayer2() - this.seeAtDistance2 < 0) { - if (this.alpha < 1) this.alpha += 0.004; - } else { - if (this.alpha > 0) this.alpha -= 0.03; - } - if (this.alpha > 0) { - if (this.alpha > 0.9) { - this.healthBar(); - if (!this.canTouchPlayer) { - this.canTouchPlayer = true; - this.collisionFilter.mask = 0x001100; //can touch player but not walls - } - } - //draw body - ctx.beginPath(); - const vertices = this.vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1, len = vertices.length; j < len; ++j) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - ctx.lineWidth = 1; - ctx.strokeStyle = `rgba(0,0,0,${this.alpha * this.alpha})`; - ctx.stroke(); - } else if (this.canTouchPlayer) { - this.canTouchPlayer = false; - this.collisionFilter.mask = 0x000100; //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.do = function () { - this.healthBar(); - this.seePlayerCheck(); - this.blink(); - //strike by expanding - if (this.isBig) { - if (this.cd - this.delay + 15 < game.cycle) { - Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag); - this.isBig = false; - } - } else if (this.seePlayer.yes && this.cd < game.cycle) { - const dist = Matter.Vector.sub(this.seePlayer.position, this.position); - const distMag2 = Matter.Vector.magnitudeSquared(dist); - if (distMag2 < 80000) { - this.cd = game.cycle + this.delay; - Matter.Body.scale(this, this.scaleMag, this.scaleMag); - this.isBig = true; - } - } - }; - }, - bomber(x, y, radius = 120 + Math.ceil(Math.random() * 70)) { - //boss that drops bombs from above and holds a set distance from player - mobs.spawn(x, y, 3, radius, "transparent"); - let me = mob[mob.length - 1]; - Matter.Body.setDensity(me, 0.0015 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger - - me.stroke = "rgba(255,0,200)"; //used for drawGhost - me.seeAtDistance2 = 2000000; - me.fireFreq = Math.ceil(30 + 2000 / radius); - me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search - me.hoverElevation = 400 + (Math.random() - 0.5) * 200; //squared - me.hoverXOff = (Math.random() - 0.5) * 100; - me.accelMag = Math.floor(10 * (Math.random() + 5)) * 0.00001 * 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 = 0x001100; //move through walls - spawn.shield(me, x, y); - me.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - }; - me.do = function () { - this.healthBar(); - this.seePlayerCheckByDistance(); - this.hoverOverPlayer(); - this.bomb(); - this.search(); - }; - }, - shooter(x, y, radius = 25 + Math.ceil(Math.random() * 50)) { - mobs.spawn(x, y, 3, radius, "rgb(255,100,150)"); - let me = mob[mob.length - 1]; - me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front - me.memory = 120; - me.fireFreq = 0.007 + Math.random() * 0.005; - me.noseLength = 0; - me.fireAngle = 0; - me.accelMag = 0.0005 * game.accelScale; - me.frictionAir = 0.05; - me.lookTorque = 0.0000025 * (Math.random() > 0.5 ? -1 : 1); - me.fireDir = { - x: 0, - y: 0 - }; - if (Math.random() < Math.min(0.15 + (game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); - me.do = function () { - this.healthBar(); - this.seePlayerByLookingAt(); - this.fire(); - }; - }, - shooterBoss(x, y, radius = 85 + Math.ceil(Math.random() * 50)) { - //boss spawns on skyscraper level - mobs.spawn(x, y, 3, radius, "rgb(255,70,180)"); - let me = mob[mob.length - 1]; - me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front - me.memory = 240; - me.fireFreq = 0.02; - me.noseLength = 0; - me.fireAngle = 0; - me.accelMag = 0.005 * game.accelScale; - me.frictionAir = 0.1; - me.lookTorque = 0.000005 * (Math.random() > 0.5 ? -1 : 1); - me.fireDir = { - x: 0, - y: 0 - }; - Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger - spawn.shield(me, x, y); - me.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - }; - me.do = function () { - this.healthBar(); - this.seePlayerByLookingAt(); - this.fire(); - }; - }, - bullet(x, y, radius = 6, sides = 0) { - //bullets - mobs.spawn(x, y, sides, radius, "rgb(255,0,0)"); - let me = mob[mob.length - 1]; - me.stroke = "transparent"; - me.onHit = function () { - this.explode(); - }; - Matter.Body.setDensity(me, 0.001); //normal is 0.001 - me.timeLeft = 240; - me.g = 0.001; //required if using 'gravity' - me.frictionAir = 0; - me.restitution = 0.8; - me.leaveBody = false; - me.dropPowerUp = false; - me.collisionFilter.category = 0x000010; - me.collisionFilter.mask = 0x011101; - me.do = function () { - this.gravity(); - this.timeLimit(); - }; - }, - spawner(x, y, radius = 55 + Math.ceil(Math.random() * 50)) { - mobs.spawn(x, y, 4, radius, "rgb(255,150,0)"); - let me = mob[mob.length - 1]; - me.g = 0.0004; //required if using 'gravity' - me.leaveBody = false; - // me.dropPowerUp = false; - me.onDeath = function () { //run this function on death - for (let i = 0; i < Math.ceil(this.mass * 0.2 + Math.random() * 3); ++i) { - spawn.spawns(this.position.x + (Math.random() - 0.5) * radius * 2.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 (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.5)) spawn.shield(me, x, y); - me.do = function () { - this.healthBar(); - this.gravity(); - this.seePlayerCheck(); - this.attraction(); - }; - }, - spawns(x, y, radius = 15 + Math.ceil(Math.random() * 5)) { - mobs.spawn(x, y, 4, radius, "rgb(255,0,0)"); - let me = mob[mob.length - 1]; - me.onHit = function () { - //run this function on hitting player - this.explode(); - }; - me.g = 0.0001; //required if using 'gravity' - me.accelMag = 0.0003 * 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.healthBar(); - this.gravity(); - this.seePlayerCheck(); - this.attraction(); - }; - }, - exploder(x, y, radius = 25 + Math.ceil(Math.random() * 50)) { - mobs.spawn(x, y, 4, radius, "rgb(255,0,0)"); - let me = mob[mob.length - 1]; - me.onHit = function () { - //run this function on hitting player - this.explode(); - }; - me.g = 0.0004; //required if using 'gravity' - me.do = function () { - this.healthBar(); - this.gravity(); - this.seePlayerCheck(); - this.attraction(); - }; - }, - snaker(x, y, radius = 80) { - //snake boss with a laser head - mobs.spawn(x, y, 8, radius, "rgb(255,50,130)"); - let me = mob[mob.length - 1]; - me.accelMag = 0.0012 * 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); - if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); - me.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - }; - me.do = function () { - this.healthBar(); - this.seePlayerCheck(); - 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 - }); - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - nodes + 1], - bodyB: mob[mob.length - 1 - nodes], - stiffness: 0.05 - }); - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - nodes + 2], - bodyB: mob[mob.length - 1 - nodes], - stiffness: 0.05 - }); - - }, - tether(x, y, radius = 90) { - // constrained mob boss for the towers level - // often has a ring of mobs around it - mobs.spawn(x, y, 8, radius, "rgb(0,60,80)"); - let me = mob[mob.length - 1]; - me.g = 0.0001; //required if using 'gravity' - me.accelMag = 0.002 * 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); - if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); - - me.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y) - this.removeCons(); //remove constraint - }; - me.do = function () { - this.healthBar(); - this.gravity(); - this.seePlayerCheck(); - this.attraction(); - }; - }, - shield(target, x, y, stiffness = 0.4) { - if (this.allowShields) { - mobs.spawn(x, y, 9, target.radius + 20, "rgba(220,220,255,0.6)"); - let me = mob[mob.length - 1]; - me.stroke = "rgb(220,220,255)"; - Matter.Body.setDensity(me, 0.0001) //very low density to not mess with the original mob's motion - me.shield = true; - me.collisionFilter.mask = 0x000100; //don't collide with bodies, map, player, and mobs, only bullets - consBB[consBB.length] = Constraint.create({ - //attach shield to last spawned mob - bodyA: me, - bodyB: target, - stiffness: stiffness, - damping: 0.1 - }); - me.onDamage = function () { - //make sure the mob that owns the shield can tell when damage is done - this.alertNearByMobs(); - }; - me.leaveBody = false; - me.dropPowerUp = false; - //swap order of shield and mob, so that mob is behind shield graphically - mob[mob.length - 1] = mob[mob.length - 2]; - mob[mob.length - 2] = me; - me.do = function () {}; - } - }, - bossShield(nodes, x, y, radius) { - mobs.spawn(x, y, 9, radius, "rgba(220,220,255,0.65)"); - let me = mob[mob.length - 1]; - me.stroke = "rgb(220,220,255)"; - Matter.Body.setDensity(me, 0.00005) //very low density to not mess with the original mob's motion - me.frictionAir = 0; - me.shield = true; - me.collisionFilter.mask = 0x000100; //don't collide with bodies, map, and mobs, only bullets and player - //constrain to all mob nodes in boss - for (let i = 0; i < nodes; ++i) { - consBB[consBB.length] = Constraint.create({ - bodyA: me, - bodyB: mob[mob.length - i - 2], - stiffness: 0.4, - damping: 0.1 - }); - } - me.onDamage = function () { - //make sure the mob that owns the shield can tell when damage is done - this.alertNearByMobs(); - }; - me.leaveBody = false; - me.dropPowerUp = false; - mob[mob.length - 1] = mob[mob.length - 1 - nodes]; - mob[mob.length - 1 - nodes] = me; - me.do = function () {}; - }, - //complex constrained mob templates********************************************************************** - //******************************************************************************************************* - allowShields: true, - nodeBoss( - x, - y, - spawn = "striker", - nodes = Math.min(2 + Math.ceil(Math.random() * (game.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 boss mobs - const angle = 2 * Math.PI / nodes - for (let i = 0; i < nodes; ++i) { - let whoSpawn = spawn; - if (spawn === "random") { - whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)]; - } else if (spawn === "randomList") { - whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)]; - } - this[whoSpawn](x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius); - } - if (Math.random() < 0.3) { - this.constrain2AdjacentMobs(nodes, stiffness * 2, true); - } else { - this.constrainAllMobCombos(nodes, stiffness); - } - //spawn shield for entire boss - if (nodes > 2 && Math.random() < 0.998) { - this.bossShield(nodes, x, y, sideLength + 2.5 * radius + nodes * 6 - 25); - // this.bossShield(nodes, x, y, sideLength / (2 * Math.sin(Math.PI / nodes))); - } - this.allowShields = true; - }, - 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; //dont' want shields on boss mobs - for (let i = 0; i < nodes; ++i) { - let whoSpawn = spawn; - if (spawn === "random") { - whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)]; - } else if (spawn === "randomList") { - whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)]; - } - this[whoSpawn](x + i * radius + i * l, y, radius); - } - this.constrain2AdjacentMobs(nodes, stiffness); - this.allowShields = true; - }, - //constraints ************************************************************************************************ - //************************************************************************************************************* - constrainAllMobCombos(nodes, stiffness) { - //runs through every combination of last 'num' bodies and constrains them - for (let i = 1; i < nodes + 1; ++i) { - for (let j = i + 1; j < nodes + 1; ++j) { - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - i], - bodyB: mob[mob.length - j], - stiffness: stiffness - }); - } - } - }, - constrain2AdjacentMobs(nodes, stiffness, loop = false) { - //runs through every combination of last 'num' bodies and constrains them - for (let i = 0; i < nodes - 1; ++i) { - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - i - 1], - bodyB: mob[mob.length - i - 2], - stiffness: stiffness - }); - } - if (nodes > 2) { - for (let i = 0; i < nodes - 2; ++i) { - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - i - 1], - bodyB: mob[mob.length - i - 3], - stiffness: stiffness - }); - } - } - //optional connect the tail to head - if (loop && nodes > 3) { - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - 1], - bodyB: mob[mob.length - nodes], - stiffness: stiffness - }); - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - 2], - bodyB: mob[mob.length - nodes], - stiffness: stiffness - }); - consBB[consBB.length] = Constraint.create({ - bodyA: mob[mob.length - 1], - bodyB: mob[mob.length - nodes + 1], - stiffness: stiffness - }); - } - }, - constraintPB(x, y, bodyIndex, stiffness) { - cons[cons.length] = Constraint.create({ - pointA: { - x: x, - y: y - }, - bodyB: body[bodyIndex], - stiffness: stiffness - }); - }, - constraintBB(bodyIndexA, bodyIndexB, stiffness) { - consBB[consBB.length] = Constraint.create({ - bodyA: body[bodyIndexA], - bodyB: body[bodyIndexB], - stiffness: stiffness - }); - }, - // body and map spawns ****************************************************************************** - //********************************************************************************************** - wireHead() { - //not a mob, just a graphic for level 1 - const breakingPoint = 1300 - mobs.spawn(breakingPoint, -100, 0, 7.5, "transparent"); - let me = mob[mob.length - 1]; - //touch nothing - me.collisionFilter.category = 0x010000; //act like a body - me.collisionFilter.mask = 0x000101; //only collide with 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.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 = 0x010000; //act like a body - me.collisionFilter.mask = 0x000101; //only collide with 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.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 = 0x010000; //act like a body - me.collisionFilter.mask = 0x000101; //only collide with 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.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 = 0x010000; //act like a body - me.collisionFilter.mask = 0x000101; //only collide with 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.do = function () { - let wireX = -50 + 16; - let wireY = -1000; - - if (this.freeOfWires) { - this.gravity(); - } else { - if (mech.pos.x > breakingPoint) { - this.freeOfWires = true; - this.force.x += -0.0006; - this.fill = "#222"; - } - //move mob to player - mech.calcLeg(0, 0); - Matter.Body.setPosition(this, { - x: mech.pos.x + mech.flipLegs * mech.foot.x - 5, - y: mech.pos.y + mech.foot.y - 1 - }) - } - //draw wire - ctx.beginPath(); - ctx.moveTo(wireX, wireY); - ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); - ctx.lineWidth = 5; - ctx.lineCap = "butt"; - ctx.strokeStyle = "#222"; - ctx.stroke(); - ctx.lineCap = "round"; - }; - }, - wireFootLeft() { - //not a mob, just a graphic for level 1 - const breakingPoint = 1325 - mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); - let me = mob[mob.length - 1]; - //touch nothing - me.collisionFilter.category = 0x010000; //act like a body - me.collisionFilter.mask = 0x000101; //only collide with 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.do = function () { - let wireX = -50 + 26; - let wireY = -1000; - - if (this.freeOfWires) { - this.gravity(); - } else { - if (mech.pos.x > breakingPoint) { - this.freeOfWires = true; - this.force.x += -0.0005; - this.fill = "#333"; - } - //move mob to player - mech.calcLeg(Math.PI, -3); - Matter.Body.setPosition(this, { - x: mech.pos.x + mech.flipLegs * mech.foot.x - 5, - y: mech.pos.y + mech.foot.y - 1 - }) - } - //draw wire - ctx.beginPath(); - ctx.moveTo(wireX, wireY); - ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); - ctx.lineWidth = 5; - ctx.strokeStyle = "#333"; - ctx.lineCap = "butt"; - ctx.stroke(); - ctx.lineCap = "round"; - }; - }, - boost(x, y, height = 1000) { - spawn.mapVertex(x + 50, y + 35, "120 40 -120 40 -50 -40 50 -40"); - // level.addZone(x, y, 100, 30, "fling", {Vx:Vx, Vy: Vy}); - level.addQueryRegion(x, y - 20, 100, 20, "boost", [ - [player], body, mob, powerUp, bullet - ], -1.1 * Math.sqrt(Math.abs(height))); - let color = "rgba(200,0,255,"; - level.fillBG.push({ - x: x, - y: y - 25, - width: 100, - height: 25, - color: color + "0.2)" - }); - level.fillBG.push({ - x: x, - y: y - 55, - width: 100, - height: 55, - color: color + "0.1)" - }); - level.fillBG.push({ - x: x, - y: y - 120, - width: 100, - height: 120, - color: color + "0.05)" - }); - }, - laserZone(x, y, width, height, dmg) { - level.addZone(x, y, width, height, "laser", { - dmg - }); - level.fill.push({ - x: x, - y: y, - width: width, - height: height, - color: "#f00" - }); - }, - deathQuery(x, y, width, height) { - level.addQueryRegion(x, y, width, height, "death", [ - [player], mob - ]); - level.fill.push({ - x: x, - y: y, - width: width, - height: height, - color: "#f00" - }); - }, - platform(x, y, width, height) { - const size = 20; - spawn.mapRect(x, y + height, width, 30); - level.fillBG.push({ - x: x + width / 2 - size / 2, - y: y, - width: size, - height: height, - color: "#f0f0f3" - }); - }, - debris(x, y, width, number = Math.floor(3 + Math.random() * 11)) { - for (let i = 0; i < number; ++i) { - if (Math.random() < 0.15) { - powerUps.chooseRandomPowerUp(x + Math.random() * width, y); - } else { - const size = 18 + Math.random() * 25; - spawn.bodyRect(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random()), 1); - // body[body.length] = Bodies.rectangle(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random())); - } - } - }, - bodyRect(x, y, width, height, chance = 1, properties = { - friction: 0.05, - frictionAir: 0.01 - }) { - if (Math.random() < chance) body[body.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties); - }, - bodyVertex(x, y, vector, properties) { //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 - } +//main object for spawning things in a level +const spawn = { + pickList: ["starter", "starter"], + fullPickList: [ + "chaser", "chaser", "chaser", + "striker", "striker", + "spinner", + "hopper", "hopper", "hopper", "hopper", + "grower", + "springer", + "shooter", "shooter", "shooter", "shooter", "shooter", + "beamer", + "focuser", + "laser", "laser", + // "blinker", //make blinker a boss + "sucker", + "exploder", "exploder", "exploder", + "spawner", + "ghoster", + "sneaker", + ], + allowedBossList: ["chaser", "spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter"], //"zoomer", + setSpawnList() { + //this is run at the start of each new level to determine the possible mobs for the level + //each level has 2 mobs: one new mob and one from the last level + spawn.pickList.splice(0, 1); + spawn.pickList.push(spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]); + }, + randomMob(x, y, chance = 1) { + if (Math.random() < chance + 0.09 * (game.difficulty - 1) && mob.length < 4 + game.difficulty * 1.7) { + 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 - 1) * 0.45 - 0.4), 4), 0), + size = 16 + Math.ceil(Math.random() * 15), + chance = 1) { + if (Math.random() < chance + (game.difficulty - 1) * 0.03 && mob.length < 4 + game.difficulty * 1.7) { + for (let i = 0; i < num; ++i) { + const pick = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + this[pick](x + Math.round((Math.random() - 0.5) * 20) + i * size * 2.5, y + Math.round((Math.random() - 0.5) * 20), size); + } + } + }, + randomBoss(x, y, chance = 1) { + if (Math.random() < chance + (game.difficulty - 1) * 0.14 && game.difficulty !== 1 && mob.length < 4 + 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; + } + } + if (canBeBoss) { + if (Math.random() < 0.55) { + this.nodeBoss(x, y, pick); + } else { + this.lineBoss(x, y, pick); + } + } else { + if (Math.random() < 0.07) { + this[pick](x, y, 90 + Math.random() * 40); //one extra large mob + } else if (Math.random() < 0.35) { + this.groupBoss(x, y) //hidden grouping blocks + } else { + pick = (Math.random() < 0.5) ? "randomList" : "random"; + if (Math.random() < 0.55) { + this.nodeBoss(x, y, pick); + } else { + this.lineBoss(x, y, pick); + } + } + } + } + }, + + //mob templates ********************************************************************************************* + //*********************************************************************************************************** + groupBoss(x, y, num = 5 + Math.random() * 8) { + for (let i = 0; i < num; i++) { + const radius = 25 + Math.floor(Math.random() * 20) + spawn.grouper(x + Math.random() * radius, y + Math.random() * radius, radius); + } + }, + grouper(x, y, radius = 27 + Math.floor(Math.random() * 10)) { + mobs.spawn(x, y, 4, radius, "#777"); + let me = mob[mob.length - 1]; + me.g = 0.0002; //required if using 'gravity' + me.accelMag = 0.0007 * game.accelScale; + me.groupingRangeMax = 250000 + Math.random() * 100000; + me.groupingRangeMin = (radius * 8) * (radius * 8); + me.groupingStrength = 0.0005 + me.memory = 200; + + me.do = function () { + this.gravity(); + if (this.seePlayer.recall) { + this.seePlayerByDistAndLOS(); + this.healthBar(); + this.attraction(); + //tether to other blocks + ctx.beginPath(); + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i] != this && mob[i].dropPowerUp) { //don't tether to self, bullets, shields, ... + const distance2 = Matter.Vector.magnitudeSquared(Matter.Vector.sub(this.position, mob[i].position)) + if (distance2 < this.groupingRangeMax) { + if (!mob[i].seePlayer.recall) mob[i].seePlayerByDistAndLOS(); //wake up sleepy mobs + if (distance2 > this.groupingRangeMin) { + const angle = Math.atan2(mob[i].position.y - this.position.y, mob[i].position.x - this.position.x); + const forceMag = this.groupingStrength * mob[i].mass; + mob[i].force.x -= forceMag * Math.cos(angle); + mob[i].force.y -= forceMag * Math.sin(angle); + } + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mob[i].position.x, mob[i].position.y); + } + } + } + ctx.strokeStyle = "#000"; + ctx.lineWidth = 1; + ctx.stroke(); + } + } + }, + starter(x, y, radius = 30) { + //easy mob for on level 1 + mobs.spawn(x, y, 8, radius, "#9ccdc6"); + let me = mob[mob.length - 1]; + me.accelMag = 0.00055 * game.accelScale; + me.memory = 60; + Matter.Body.setDensity(me, 0.0005) // normal density is 0.001 // this reduces life by half and decreases knockback + + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.attraction(); + }; + }, + healer(x, y, radius = 20) { + //easy mob for on level 1 + 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 () { + this.healthBar(); + + 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 = Matter.Vector.sub(this.position, mob[i].position) + const DIST = Matter.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 = Matter.Vector.sub(this.position, player.position) + this.force = Matter.Vector.mult(Matter.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 = Matter.Vector.sub(this.position, this.lockedOn.position) + const DIST = Matter.Vector.magnitude(TARGET_VECTOR); + if (DIST > 250) { + this.force = Matter.Vector.mult(Matter.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 = Matter.Vector.sub(this.searchTarget, this.position); + if (Matter.Vector.magnitude(sub) > this.radius * 2) { + ctx.beginPath(); + ctx.strokeStyle = "#aaa"; + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(this.searchTarget.x, this.searchTarget.y); + ctx.stroke(); + //accelerate at 0.6 of normal acceleration + this.force = Matter.Vector.mult(Matter.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, "#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 * game.accelScale;; + me.g = me.accelMag * 0.6; //required if using 'gravity' + me.memory = 50; + if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.attraction(); + }; + }, + grower(x, y, radius = 15) { + mobs.spawn(x, y, 7, radius, "hsl(144, 15%, 50%)"); + let me = mob[mob.length - 1]; + me.big = false; //required for grow + me.accelMag = 0.00045 * game.accelScale; + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.attraction(); + this.grow(); + }; + }, + springer(x, y, radius = 20 + Math.ceil(Math.random() * 35)) { + mobs.spawn(x, y, 8, radius, "#b386e8"); + let me = mob[mob.length - 1]; + me.friction = 0; + me.frictionAir = 0.1; + me.lookTorque = 0.000005; + me.g = 0.0002; //required if using 'gravity' + me.seePlayerFreq = Math.round((40 + 25 * Math.random()) * game.lookFreqScale); + const springStiffness = 0.002; + const springDampening = 0.1; + + me.springTarget = { + x: me.position.x, + y: me.position.y + }; + const len = cons.length; + cons[len] = Constraint.create({ + pointA: me.springTarget, + bodyB: me, + stiffness: springStiffness, + damping: springDampening + }); + cons[len].length = 100 + 1.5 * radius; + me.cons = cons[len]; + + me.springTarget2 = { + x: me.position.x, + y: me.position.y + }; + const len2 = cons.length; + cons[len2] = Constraint.create({ + pointA: me.springTarget2, + bodyB: me, + stiffness: springStiffness, + damping: springDampening + }); + cons[len2].length = 100 + 1.5 * radius; + me.cons2 = cons[len2]; + + me.onDeath = function () { + this.removeCons(); + }; + if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + this.gravity(); + this.searchSpring(); + }; + }, + zoomer(x, y, radius = 20 + Math.ceil(Math.random() * 30)) { + mobs.spawn(x, y, 6, radius, "#ffe2fd"); + let me = mob[mob.length - 1]; + me.trailLength = 20; //required for trails + me.setupTrail(); //fill trails array up with the current position of mob + me.trailFill = "#ff00f0"; + me.g = 0.001; //required if using 'gravity' + me.frictionAir = 0.02; + me.accelMag = 0.004 * game.accelScale; + me.memory = 30; + me.zoomMode = 150; + me.onHit = function () { + this.zoomMode = 150; + }; + me.do = function () { + this.healthBar(); + this.seePlayerByDistAndLOS(); + this.zoom(); + this.gravity(); + }; + }, + hopper(x, y, radius = 30 + Math.ceil(Math.random() * 30)) { + mobs.spawn(x, y, 5, radius, "rgb(0,200,180)"); + let me = mob[mob.length - 1]; + me.accelMag = 0.04 * game.accelScale; + me.g = 0.0015; //required if using 'gravity' + me.frictionAir = 0.018; + me.restitution = 0; + me.delay = 110; + me.randomHopFrequency = 50 + Math.floor(Math.random() * 1000); + me.randomHopCD = game.cycle + me.randomHopFrequency; + me.do = function () { + this.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.hop(); + //randomly hob if not aware of player + if (this.randomHopCD < game.cycle && this.speed < 1 && !this.seePlayer.recall) { + this.randomHopCD = game.cycle + this.randomHopFrequency; + //slowly change randomHopFrequency after each hop + this.randomHopFrequency = Math.max(100, this.randomHopFrequency + (0.5 - Math.random()) * 200); + const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass * (0.1 + Math.random() * 0.3); + const angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI; + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle) - 0.04 * this.mass; //antigravity + } + }; + }, + spinner(x, y, radius = 30 + Math.ceil(Math.random() * 35)) { + mobs.spawn(x, y, 5, radius, "#000000"); + let me = mob[mob.length - 1]; + me.fill = "#28b"; + me.rememberFill = me.fill; + me.cdBurst1 = 0; //must add for burstAttraction + me.cdBurst2 = 0; //must add for burstAttraction + me.delay = 0; + me.burstDir = { + x: 0, + y: 0 + }; + me.accelMag = 0.16 * game.accelScale; + me.frictionAir = 0.022; + me.lookTorque = 0.0000014; + me.restitution = 0; + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + //accelerate towards the player after a delay + if (this.seePlayer.recall) { + if (this.cdBurst2 < game.cycle && this.angularSpeed < 0.01) { + this.cdBurst2 = Infinity; + this.cdBurst1 = game.cycle + 40; + this.burstDir = Matter.Vector.normalise(Matter.Vector.sub(this.seePlayer.position, this.position)); + } else if (this.cdBurst1 < game.cycle) { + this.cdBurst2 = game.cycle + this.delay; + this.cdBurst1 = Infinity; + this.force = Matter.Vector.mult(this.burstDir, this.mass * 0.25); + this.fill = this.rememberFill; + } else if (this.cdBurst1 != Infinity) { + this.torque += 0.000035 * this.inertia; + this.fill = randomColor({ + hue: "blue" + }); + //draw attack vector + const mag = this.radius * 2 + 200; + const gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, mag); + gradient.addColorStop(0, "rgba(0,0,0,0.2)"); + gradient.addColorStop(1, "transparent"); + ctx.strokeStyle = gradient; + ctx.lineWidth = 5; + ctx.setLineDash([10, 20]); //30 + const dir = Matter.Vector.add(this.position, Matter.Vector.mult(this.burstDir, mag)); + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(dir.x, dir.y); + ctx.stroke(); + ctx.setLineDash([]); + } else { + this.fill = this.rememberFill; + } + } else { + this.cdBurst2 = 0; + } + }; + }, + sucker(x, y, radius = 30 + Math.ceil(Math.random() * 70)) { + radius = 9 + radius / 8; //extra small + mobs.spawn(x, y, 6, radius, "#000"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; //used for drawSneaker + me.eventHorizon = radius * 23; //required for blackhole + me.seeAtDistance2 = (me.eventHorizon + 500) * (me.eventHorizon + 500); //vision limit is event horizon + me.accelMag = 0.00009 * 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(); + if (this.seePlayer.recall) { + //eventHorizon waves in and out + eventHorizon = this.eventHorizon * (0.93 + 0.17 * Math.sin(game.cycle * 0.011)) + + //accelerate towards the player + const forceMag = this.accelMag * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle); + + //draw darkness + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.25, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.9)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon * 0.55, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.5)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, eventHorizon, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.1)"; + ctx.fill(); + + this.healthBar(); + //when player is inside event horizon + if (Matter.Vector.magnitude(Matter.Vector.sub(this.position, player.position)) < eventHorizon) { + mech.damage(0.00015 * game.dmgScale); + if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.007 + + const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + player.force.x -= 1.25 * Math.cos(angle) * player.mass * game.g * (mech.onGround ? 1.8 : 1); + player.force.y -= 0.96 * player.mass * game.g * Math.sin(angle); + //draw line to player + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(mech.pos.x, mech.pos.y); + ctx.lineWidth = Math.min(60, this.radius * 2); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.stroke(); + ctx.beginPath(); + ctx.arc(mech.pos.x, mech.pos.y, 40, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.3)"; + ctx.fill(); + } + } + } + }, + suckerBoss(x, y, radius = 20) { + mobs.spawn(x, y, 12, radius, "#000"); + let me = mob[mob.length - 1]; + 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 = 0x001100 + // 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) { + for (let i = 0; i < (game.difficulty - 3); ++i) { + spawn.sucker(this.position.x + (Math.random() - 0.5) * radius * 2, this.position.y + (Math.random() - 0.5) * radius * 2, 70 * Math.random()); + Matter.Body.setVelocity(mob[mob.length - 1], { + x: (Math.random() - 0.5) * 70, + y: (Math.random() - 0.5) * 70 + }); + } + } + }; + 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(); + 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 (Matter.Vector.magnitude(Matter.Vector.sub(this.position, player.position)) < eventHorizon) { + mech.damage(0.00015 * game.dmgScale); + if (mech.fieldMeter > 0.1) mech.fieldMeter -= 0.007 + const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + player.force.x -= 1.3 * Math.cos(angle) * player.mass * game.g * (mech.onGround ? 1.7 : 1); + player.force.y -= 1.2 * Math.sin(angle) * player.mass * game.g; + //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.healthBar(); + this.curl(eventHorizon); + } + } + }, + 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; + if (Math.random() < Math.min(0.2 + (game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.attraction(); + this.repulsion(); + //laser beam + this.laserBeam(); + }; + }, + focuser(x, y, radius = 30 + Math.ceil(Math.random() * 10)) { + radius = Math.ceil(radius * 0.7); + mobs.spawn(x, y, 4, radius, "rgb(0,0,255)"); + let me = mob[mob.length - 1]; + Matter.Body.setDensity(me, 0.003); //extra dense //normal is 0.001 + me.restitution = 0; + me.laserPos = me.position; //required for laserTracking + me.repulsionRange = 1200000; //squared + me.accelMag = 0.00009 * game.accelScale; + me.frictionStatic = 0; + me.friction = 0; + me.onDamage = function () { + this.laserPos = this.position; + }; + // if (Math.random() < Math.min(0.2 + game.difficulty * 0.1, 0.7)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + if (!mech.isBodiesAsleep) { + this.seePlayerByLookingAt(); + const dist2 = this.distanceToPlayer2(); + //laser Tracking + if (this.seePlayer.yes && dist2 < 4000000 && !mech.isStealth) { + this.attraction(); + const rangeWidth = 2000; //this is sqrt of 4000000 from above if() + //targeting laser will slowly move from the mob to the player's position + this.laserPos = Matter.Vector.add(this.laserPos, Matter.Vector.mult(Matter.Vector.sub(player.position, this.laserPos), 0.1)); + let targetDist = Matter.Vector.magnitude(Matter.Vector.sub(this.laserPos, mech.pos)); + const r = 10; + + // ctx.setLineDash([15, 200]); + // ctx.lineDashOffset = 20*(game.cycle % 215); + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + if (targetDist < r + 15) { + // || dist2 < 80000 + targetDist = r + 10; + //charge at player + const forceMag = this.accelMag * 40 * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle); + } else { + //high friction if can't lock onto player + Matter.Body.setVelocity(this, { + x: this.velocity.x * 0.96, + y: this.velocity.y * 0.96 + }); + } + if (dist2 > 80000) { + const laserWidth = 0.002; + let laserOffR = Matter.Vector.rotateAbout(this.laserPos, (targetDist - r) * laserWidth, this.position); + let sub = Matter.Vector.normalise(Matter.Vector.sub(laserOffR, this.position)); + laserOffR = Matter.Vector.add(laserOffR, Matter.Vector.mult(sub, rangeWidth)); + ctx.lineTo(laserOffR.x, laserOffR.y); + + let laserOffL = Matter.Vector.rotateAbout(this.laserPos, (targetDist - r) * -laserWidth, this.position); + sub = Matter.Vector.normalise(Matter.Vector.sub(laserOffL, this.position)); + laserOffL = Matter.Vector.add(laserOffL, Matter.Vector.mult(sub, rangeWidth)); + ctx.lineTo(laserOffL.x, laserOffL.y); + // ctx.fillStyle = "rgba(0,0,255,0.15)"; + var gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, rangeWidth); + gradient.addColorStop(0, `rgba(0,0,255,${((r + 5) * (r + 5)) / (targetDist * targetDist)})`); + gradient.addColorStop(1, "transparent"); + ctx.fillStyle = gradient; + ctx.fill(); + } + } else { + this.laserPos = this.position; + } + }; + } + }, + laser(x, y, radius = 30) { + //only on level 1 + mobs.spawn(x, y, 3, radius, "#f00"); + let me = mob[mob.length - 1]; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + Matter.Body.rotate(me, Math.random() * Math.PI * 2); + me.accelMag = 0.00007 * game.accelScale; + me.onHit = function () { + //run this function on hitting player + this.explode(); + }; + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.attraction(); + this.laser(); + }; + }, + striker(x, y, radius = 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 = 100; + Matter.Body.rotate(me, Math.PI * 0.1); + me.onDamage = function () { + this.cd = game.cycle + this.delay; + }; + me.do = function () { + this.healthBar(); + this.seePlayerCheck(); + this.attraction(); + this.gravity(); + this.strike(); + }; + }, + sneaker(x, y, radius = 15 + Math.ceil(Math.random() * 25)) { + let me; + mobs.spawn(x, y, 5, radius, "transparent"); + me = mob[mob.length - 1]; + me.accelMag = 0.0007 * 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 = 0x010111; //can't touch player + // me.memory = 420; + me.do = function () { + + this.seePlayerCheck(); + this.attraction(); + this.gravity(); + //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 = 0x011111; //can touch player + } + } + //draw body + ctx.beginPath(); + const vertices = this.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1, len = vertices.length; j < len; ++j) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.fillStyle = `rgba(0,0,0,${this.alpha * this.alpha})`; + ctx.fill(); + } else if (this.canTouchPlayer) { + this.canTouchPlayer = false; + this.collisionFilter.mask = 0x000111; //can't touch player + } + }; + }, + ghoster(x, y, radius = 40 + Math.ceil(Math.random() * 100)) { + let me; + mobs.spawn(x, y, 7, radius, "transparent"); + me = mob[mob.length - 1]; + me.seeAtDistance2 = 1000000; + me.accelMag = 0.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 = 0x000100; //move through walls and player + 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.attraction(); + this.search(); + //draw + if (this.distanceToPlayer2() - this.seeAtDistance2 < 0) { + if (this.alpha < 1) this.alpha += 0.004; + } else { + if (this.alpha > 0) this.alpha -= 0.03; + } + if (this.alpha > 0) { + if (this.alpha > 0.9) { + this.healthBar(); + if (!this.canTouchPlayer) { + this.canTouchPlayer = true; + this.collisionFilter.mask = 0x001100; //can touch player but not walls + } + } + //draw body + ctx.beginPath(); + const vertices = this.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1, len = vertices.length; j < len; ++j) { + ctx.lineTo(vertices[j].x, vertices[j].y); + } + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.lineWidth = 1; + ctx.strokeStyle = `rgba(0,0,0,${this.alpha * this.alpha})`; + ctx.stroke(); + } else if (this.canTouchPlayer) { + this.canTouchPlayer = false; + this.collisionFilter.mask = 0x000100; //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.do = function () { + this.healthBar(); + this.seePlayerCheck(); + this.blink(); + //strike by expanding + if (this.isBig) { + if (this.cd - this.delay + 15 < game.cycle) { + Matter.Body.scale(this, 1 / this.scaleMag, 1 / this.scaleMag); + this.isBig = false; + } + } else if (this.seePlayer.yes && this.cd < game.cycle) { + const dist = Matter.Vector.sub(this.seePlayer.position, this.position); + const distMag2 = Matter.Vector.magnitudeSquared(dist); + if (distMag2 < 80000) { + this.cd = game.cycle + this.delay; + Matter.Body.scale(this, this.scaleMag, this.scaleMag); + this.isBig = true; + } + } + }; + }, + bomber(x, y, radius = 120 + Math.ceil(Math.random() * 70)) { + //boss that drops bombs from above and holds a set distance from player + mobs.spawn(x, y, 3, radius, "transparent"); + let me = mob[mob.length - 1]; + Matter.Body.setDensity(me, 0.0015 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger + + me.stroke = "rgba(255,0,200)"; //used for drawGhost + me.seeAtDistance2 = 2000000; + me.fireFreq = Math.ceil(30 + 2000 / radius); + me.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position; //required for search + me.hoverElevation = 400 + (Math.random() - 0.5) * 200; //squared + me.hoverXOff = (Math.random() - 0.5) * 100; + me.accelMag = Math.floor(10 * (Math.random() + 5)) * 0.00001 * 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 = 0x001100; //move through walls + spawn.shield(me, x, y); + me.onDeath = function () { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + }; + me.do = function () { + this.healthBar(); + this.seePlayerCheckByDistance(); + this.hoverOverPlayer(); + this.bomb(); + this.search(); + }; + }, + shooter(x, y, radius = 25 + Math.ceil(Math.random() * 50)) { + mobs.spawn(x, y, 3, radius, "rgb(255,100,150)"); + let me = mob[mob.length - 1]; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + me.memory = 120; + me.fireFreq = 0.007 + Math.random() * 0.005; + me.noseLength = 0; + me.fireAngle = 0; + me.accelMag = 0.0005 * game.accelScale; + me.frictionAir = 0.05; + me.lookTorque = 0.0000025 * (Math.random() > 0.5 ? -1 : 1); + me.fireDir = { + x: 0, + y: 0 + }; + if (Math.random() < Math.min(0.15 + (game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.fire(); + }; + }, + shooterBoss(x, y, radius = 85 + Math.ceil(Math.random() * 50)) { + //boss spawns on skyscraper level + mobs.spawn(x, y, 3, radius, "rgb(255,70,180)"); + let me = mob[mob.length - 1]; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + me.memory = 240; + me.fireFreq = 0.02; + me.noseLength = 0; + me.fireAngle = 0; + me.accelMag = 0.005 * game.accelScale; + me.frictionAir = 0.1; + me.lookTorque = 0.000005 * (Math.random() > 0.5 ? -1 : 1); + me.fireDir = { + x: 0, + y: 0 + }; + Matter.Body.setDensity(me, 0.001 + 0.0005 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger + spawn.shield(me, x, y); + me.onDeath = function () { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + }; + me.do = function () { + this.healthBar(); + this.seePlayerByLookingAt(); + this.fire(); + }; + }, + bullet(x, y, radius = 6, sides = 0) { + //bullets + mobs.spawn(x, y, sides, radius, "rgb(255,0,0)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + me.onHit = function () { + this.explode(); + }; + Matter.Body.setDensity(me, 0.001); //normal is 0.001 + me.timeLeft = 240; + me.g = 0.001; //required if using 'gravity' + me.frictionAir = 0; + me.restitution = 0.8; + me.leaveBody = false; + me.dropPowerUp = false; + me.collisionFilter.category = 0x000010; + me.collisionFilter.mask = 0x011101; + me.do = function () { + this.gravity(); + this.timeLimit(); + }; + }, + spawner(x, y, radius = 55 + Math.ceil(Math.random() * 50)) { + mobs.spawn(x, y, 4, radius, "rgb(255,150,0)"); + let me = mob[mob.length - 1]; + me.g = 0.0004; //required if using 'gravity' + me.leaveBody = false; + // me.dropPowerUp = false; + me.onDeath = function () { //run this function on death + for (let i = 0; i < Math.ceil(this.mass * 0.2 + Math.random() * 3); ++i) { + spawn.spawns(this.position.x + (Math.random() - 0.5) * radius * 2.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 (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.5)) spawn.shield(me, x, y); + me.do = function () { + this.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.attraction(); + }; + }, + spawns(x, y, radius = 15 + Math.ceil(Math.random() * 5)) { + mobs.spawn(x, y, 4, radius, "rgb(255,0,0)"); + let me = mob[mob.length - 1]; + me.onHit = function () { + //run this function on hitting player + this.explode(); + }; + me.g = 0.0001; //required if using 'gravity' + me.accelMag = 0.0003 * 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.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.attraction(); + }; + }, + exploder(x, y, radius = 25 + Math.ceil(Math.random() * 50)) { + mobs.spawn(x, y, 4, radius, "rgb(255,0,0)"); + let me = mob[mob.length - 1]; + me.onHit = function () { + //run this function on hitting player + this.explode(); + }; + me.g = 0.0004; //required if using 'gravity' + me.do = function () { + this.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.attraction(); + }; + }, + snaker(x, y, radius = 80) { + //snake boss with a laser head + mobs.spawn(x, y, 8, radius, "rgb(255,50,130)"); + let me = mob[mob.length - 1]; + me.accelMag = 0.0012 * 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); + if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + me.onDeath = function () { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + }; + me.do = function () { + this.healthBar(); + this.seePlayerCheck(); + 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 + }); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - nodes + 1], + bodyB: mob[mob.length - 1 - nodes], + stiffness: 0.05 + }); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - nodes + 2], + bodyB: mob[mob.length - 1 - nodes], + stiffness: 0.05 + }); + + }, + tether(x, y, radius = 90) { + // constrained mob boss for the towers level + // often has a ring of mobs around it + mobs.spawn(x, y, 8, radius, "rgb(0,60,80)"); + let me = mob[mob.length - 1]; + me.g = 0.0001; //required if using 'gravity' + me.accelMag = 0.002 * 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); + if (Math.random() < Math.min((game.difficulty - 1) * 0.1, 0.7)) spawn.shield(me, x, y); + + me.onDeath = function () { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + this.removeCons(); //remove constraint + }; + me.do = function () { + this.healthBar(); + this.gravity(); + this.seePlayerCheck(); + this.attraction(); + }; + }, + shield(target, x, y, stiffness = 0.4) { + if (this.allowShields) { + mobs.spawn(x, y, 9, target.radius + 20, "rgba(220,220,255,0.6)"); + let me = mob[mob.length - 1]; + me.stroke = "rgb(220,220,255)"; + Matter.Body.setDensity(me, 0.0001) //very low density to not mess with the original mob's motion + me.shield = true; + me.collisionFilter.mask = 0x000100; //don't collide with bodies, map, player, and mobs, only bullets + consBB[consBB.length] = Constraint.create({ + //attach shield to last spawned mob + bodyA: me, + bodyB: target, + stiffness: stiffness, + damping: 0.1 + }); + me.onDamage = function () { + //make sure the mob that owns the shield can tell when damage is done + this.alertNearByMobs(); + }; + me.leaveBody = false; + me.dropPowerUp = false; + //swap order of shield and mob, so that mob is behind shield graphically + mob[mob.length - 1] = mob[mob.length - 2]; + mob[mob.length - 2] = me; + me.do = function () {}; + } + }, + bossShield(nodes, x, y, radius) { + mobs.spawn(x, y, 9, radius, "rgba(220,220,255,0.65)"); + let me = mob[mob.length - 1]; + me.stroke = "rgb(220,220,255)"; + Matter.Body.setDensity(me, 0.00005) //very low density to not mess with the original mob's motion + me.frictionAir = 0; + me.shield = true; + me.collisionFilter.mask = 0x000100; //don't collide with bodies, map, and mobs, only bullets and player + //constrain to all mob nodes in boss + for (let i = 0; i < nodes; ++i) { + consBB[consBB.length] = Constraint.create({ + bodyA: me, + bodyB: mob[mob.length - i - 2], + stiffness: 0.4, + damping: 0.1 + }); + } + me.onDamage = function () { + //make sure the mob that owns the shield can tell when damage is done + this.alertNearByMobs(); + }; + me.leaveBody = false; + me.dropPowerUp = false; + mob[mob.length - 1] = mob[mob.length - 1 - nodes]; + mob[mob.length - 1 - nodes] = me; + me.do = function () {}; + }, + //complex constrained mob templates********************************************************************** + //******************************************************************************************************* + allowShields: true, + nodeBoss( + x, + y, + spawn = "striker", + nodes = Math.min(2 + Math.ceil(Math.random() * (game.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 boss mobs + const angle = 2 * Math.PI / nodes + for (let i = 0; i < nodes; ++i) { + let whoSpawn = spawn; + if (spawn === "random") { + whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)]; + } else if (spawn === "randomList") { + whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + } + this[whoSpawn](x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius); + } + if (Math.random() < 0.3) { + this.constrain2AdjacentMobs(nodes, stiffness * 2, true); + } else { + this.constrainAllMobCombos(nodes, stiffness); + } + //spawn shield for entire boss + if (nodes > 2 && Math.random() < 0.998) { + this.bossShield(nodes, x, y, sideLength + 2.5 * radius + nodes * 6 - 25); + // this.bossShield(nodes, x, y, sideLength / (2 * Math.sin(Math.PI / nodes))); + } + this.allowShields = true; + }, + 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; //dont' want shields on boss mobs + for (let i = 0; i < nodes; ++i) { + let whoSpawn = spawn; + if (spawn === "random") { + whoSpawn = this.fullPickList[Math.floor(Math.random() * this.fullPickList.length)]; + } else if (spawn === "randomList") { + whoSpawn = this.pickList[Math.floor(Math.random() * this.pickList.length)]; + } + this[whoSpawn](x + i * radius + i * l, y, radius); + } + this.constrain2AdjacentMobs(nodes, stiffness); + this.allowShields = true; + }, + //constraints ************************************************************************************************ + //************************************************************************************************************* + constrainAllMobCombos(nodes, stiffness) { + //runs through every combination of last 'num' bodies and constrains them + for (let i = 1; i < nodes + 1; ++i) { + for (let j = i + 1; j < nodes + 1; ++j) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - i], + bodyB: mob[mob.length - j], + stiffness: stiffness + }); + } + } + }, + constrain2AdjacentMobs(nodes, stiffness, loop = false) { + //runs through every combination of last 'num' bodies and constrains them + for (let i = 0; i < nodes - 1; ++i) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - i - 1], + bodyB: mob[mob.length - i - 2], + stiffness: stiffness + }); + } + if (nodes > 2) { + for (let i = 0; i < nodes - 2; ++i) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - i - 1], + bodyB: mob[mob.length - i - 3], + stiffness: stiffness + }); + } + } + //optional connect the tail to head + if (loop && nodes > 3) { + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - 1], + bodyB: mob[mob.length - nodes], + stiffness: stiffness + }); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - 2], + bodyB: mob[mob.length - nodes], + stiffness: stiffness + }); + consBB[consBB.length] = Constraint.create({ + bodyA: mob[mob.length - 1], + bodyB: mob[mob.length - nodes + 1], + stiffness: stiffness + }); + } + }, + constraintPB(x, y, bodyIndex, stiffness) { + cons[cons.length] = Constraint.create({ + pointA: { + x: x, + y: y + }, + bodyB: body[bodyIndex], + stiffness: stiffness + }); + }, + constraintBB(bodyIndexA, bodyIndexB, stiffness) { + consBB[consBB.length] = Constraint.create({ + bodyA: body[bodyIndexA], + bodyB: body[bodyIndexB], + stiffness: stiffness + }); + }, + // body and map spawns ****************************************************************************** + //********************************************************************************************** + wireHead() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1300 + mobs.spawn(breakingPoint, -100, 0, 7.5, "transparent"); + let me = mob[mob.length - 1]; + //touch nothing + me.collisionFilter.category = 0x010000; //act like a body + me.collisionFilter.mask = 0x000101; //only collide with 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.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 = 0x010000; //act like a body + me.collisionFilter.mask = 0x000101; //only collide with 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.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 = 0x010000; //act like a body + me.collisionFilter.mask = 0x000101; //only collide with 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.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 = 0x010000; //act like a body + me.collisionFilter.mask = 0x000101; //only collide with 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.do = function () { + let wireX = -50 + 16; + let wireY = -1000; + + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.force.x += -0.0006; + this.fill = "#222"; + } + //move mob to player + mech.calcLeg(0, 0); + Matter.Body.setPosition(this, { + x: mech.pos.x + mech.flipLegs * mech.foot.x - 5, + y: mech.pos.y + mech.foot.y - 1 + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + ctx.lineWidth = 5; + ctx.lineCap = "butt"; + ctx.strokeStyle = "#222"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + wireFootLeft() { + //not a mob, just a graphic for level 1 + const breakingPoint = 1325 + mobs.spawn(breakingPoint, -100, 0, 2, "transparent"); + let me = mob[mob.length - 1]; + //touch nothing + me.collisionFilter.category = 0x010000; //act like a body + me.collisionFilter.mask = 0x000101; //only collide with 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.do = function () { + let wireX = -50 + 26; + let wireY = -1000; + + if (this.freeOfWires) { + this.gravity(); + } else { + if (mech.pos.x > breakingPoint) { + this.freeOfWires = true; + this.force.x += -0.0005; + this.fill = "#333"; + } + //move mob to player + mech.calcLeg(Math.PI, -3); + Matter.Body.setPosition(this, { + x: mech.pos.x + mech.flipLegs * mech.foot.x - 5, + y: mech.pos.y + mech.foot.y - 1 + }) + } + //draw wire + ctx.beginPath(); + ctx.moveTo(wireX, wireY); + ctx.quadraticCurveTo(wireX, 0, this.position.x, this.position.y); + ctx.lineWidth = 5; + ctx.strokeStyle = "#333"; + ctx.lineCap = "butt"; + ctx.stroke(); + ctx.lineCap = "round"; + }; + }, + boost(x, y, height = 1000) { + spawn.mapVertex(x + 50, y + 35, "120 40 -120 40 -50 -40 50 -40"); + // level.addZone(x, y, 100, 30, "fling", {Vx:Vx, Vy: Vy}); + level.addQueryRegion(x, y - 20, 100, 20, "boost", [ + [player], body, mob, powerUp, bullet + ], -1.1 * Math.sqrt(Math.abs(height))); + let color = "rgba(200,0,255,"; + level.fillBG.push({ + x: x, + y: y - 25, + width: 100, + height: 25, + color: color + "0.2)" + }); + level.fillBG.push({ + x: x, + y: y - 55, + width: 100, + height: 55, + color: color + "0.1)" + }); + level.fillBG.push({ + x: x, + y: y - 120, + width: 100, + height: 120, + color: color + "0.05)" + }); + }, + laserZone(x, y, width, height, dmg) { + level.addZone(x, y, width, height, "laser", { + dmg + }); + level.fill.push({ + x: x, + y: y, + width: width, + height: height, + color: "#f00" + }); + }, + deathQuery(x, y, width, height) { + level.addQueryRegion(x, y, width, height, "death", [ + [player], mob + ]); + level.fill.push({ + x: x, + y: y, + width: width, + height: height, + color: "#f00" + }); + }, + platform(x, y, width, height) { + const size = 20; + spawn.mapRect(x, y + height, width, 30); + level.fillBG.push({ + x: x + width / 2 - size / 2, + y: y, + width: size, + height: height, + color: "#f0f0f3" + }); + }, + debris(x, y, width, number = Math.floor(3 + Math.random() * 11)) { + for (let i = 0; i < number; ++i) { + if (Math.random() < 0.15) { + powerUps.chooseRandomPowerUp(x + Math.random() * width, y); + } else { + const size = 18 + Math.random() * 25; + spawn.bodyRect(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random()), 1); + // body[body.length] = Bodies.rectangle(x + Math.random() * width, y, size * (0.6 + Math.random()), size * (0.6 + Math.random())); + } + } + }, + bodyRect(x, y, width, height, chance = 1, properties = { + friction: 0.05, + frictionAir: 0.01 + }) { + if (Math.random() < chance) body[body.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties); + }, + bodyVertex(x, y, vector, properties) { //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