diff --git a/js/bullet.js b/js/bullet.js index 006521e..67ad315 100644 --- a/js/bullet.js +++ b/js/bullet.js @@ -3931,6 +3931,10 @@ const b = { if (!mob.shield && Vector.dot(Vector.normalise(Vector.sub(mob.position, bullet.position)), Vector.normalise(bullet.velocity)) > 0.99 - 4 / mob.radius) { let cycle = () => { //makes this run after damage if (mob.health < 0.5 && mob.damageReduction > 0 && mob.alive) { + // mob.death(); + // mob.damage(this.health * Math.sqrt(this.mass) / this.damageReduction); + mob.damage(Infinity); + const color = 'rgb(255,255,255)' simulation.drawList.push({ x: mob.position.x, @@ -3953,7 +3957,6 @@ const b = { color: color, //"rgb(0,0,0)", time: 20 }); - mob.death(); } } requestAnimationFrame(cycle); @@ -5328,7 +5331,6 @@ const b = { b.explosion(this.position, 300 + 40 * Math.random()); //makes bullet do explosive damage at end } } else if (tech.isCritKill) b.crit(who, this) - if (tech.isNailRadiation) mobs.statusDoT(who, 7 * (tech.isFastRadiation ? 0.7 : 0.24), tech.isSlowRadiation ? 360 : (tech.isFastRadiation ? 60 : 180)) // one tick every 30 cycles if (this.speed > 4 && tech.fragments) { b.targetedNail(this.position, 1.25 * tech.fragments * tech.bulletSize) @@ -6595,7 +6597,6 @@ const b = { ctx.fill(); if (this.isDischarge && m.cycle % 2) { - this.charge -= 0.75 const spread = (input.down ? 0.04 : 0.5) * (Math.random() - 0.5) const radius = 5 + 8 * Math.random() + (tech.isAmmoFoamSize && this.ammo < 300) * 12 const SPEED = (input.down ? 1.2 : 1) * 10 - radius * 0.4 + Math.min(5, Math.sqrt(this.charge)); @@ -6609,6 +6610,7 @@ const b = { y: m.pos.y + 30 * Math.sin(m.angle) } b.foam(position, Vector.rotate(velocity, spread), radius) + this.charge -= 0.75 m.fireCDcycle = m.cycle + 2; //disable firing and adding more charge until empty } else if (!input.fire) { this.isDischarge = true; diff --git a/js/level.js b/js/level.js index 379bd08..e142752 100644 --- a/js/level.js +++ b/js/level.js @@ -16,37 +16,38 @@ const level = { start() { if (level.levelsCleared === 0) { //this code only runs on the first level // simulation.enableConstructMode() //used to build maps in testing mode - // level.difficultyIncrease(12 * 4) //30 is near max on hard //60 is near max on why // simulation.isHorizontalFlipped = true + // tech.giveTech("performance") + // level.difficultyIncrease(6 * 4) //30 is near max on hard //60 is near max on why // m.maxHealth = m.health = 100 // tech.isRerollDamage = true - // powerUps.research.changeRerolls(100000) + // powerUps.research.changeRerolls(50) // m.immuneCycle = Infinity //you can't take damage // tech.tech[297].frequency = 100 // m.couplingChange(5) - // m.setField("metamaterial cloaking") //molecular assembler standing wave time dilation perfect diamagnetism metamaterial cloaking wormhole negative mass + // m.setField("pilot wave") //molecular assembler standing wave time dilation perfect diamagnetism metamaterial cloaking wormhole negative mass pilot wave // simulation.molecularMode = 2 // m.damage(0.1); // b.giveGuns("nail gun") //0 nail gun 1 shotgun 2 super balls 3 wave 4 missiles 5 grenades 6 spores 7 drones 8 foam 9 harpoon 10 mine 11 laser // b.guns[0].ammo = 1000000 - // for (let i = 0; i < 1; ++i) tech.giveTech("mass-energy equivalence") - // tech.giveTech("Zeno's paradox") - // tech.giveTech("homeostasis") - // for (let i = 0; i < 1; ++i) tech.giveTech("1st ionization energy") - // for (let i = 0; i < 1; i++) tech.giveTech("negative feedback") + // for (let i = 0; i < 1; ++i) tech.giveTech("needle gun") + // tech.giveTech("pressure vessel") + // tech.giveTech("quintessence") + // for (let i = 0; i < 1; ++i) tech.giveTech("freezer burn") + // for (let i = 0; i < 1; i++) tech.giveTech("reaction inhibitor") // for (let i = 0; i < 10; i++) powerUps.directSpawn(450, -50, "tech"); // for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "boost"); // for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "coupling"); - // level.testing(); + // level.final(); // spawn.starter(1900, -500) - // spawn.beetleBoss(2538, -1950) + // spawn.timeBoss(2538, -950) // for (let i = 0; i < 33; ++i) spawn.sniper(1000 + 5000 * Math.random(), -500 + 300 * Math.random()) // tech.addJunkTechToPool(0.5) // tech.tech[322].frequency = 100 // spawn.tetherBoss(1900, -500, { x: 1900, y: -500 }) - + // for (let i = 0; i < 36; ++i) tech.giveTech() // for (let i = 0; i < 13; ++i) powerUps.directSpawn(m.pos.x + 50 * Math.random(), m.pos.y + 50 * Math.random(), "research"); if (simulation.isTraining) { level.walk(); } else { level.intro(); } //normal starting level ************************************************ // for (let i = 0; i < 4; ++i) powerUps.directSpawn(m.pos.x + 50 * Math.random(), m.pos.y + 50 * Math.random(), "tech"); @@ -3125,12 +3126,18 @@ const level = { powerUps.addResearchToLevel() //needs to run after mobs are spawned }, final() { + const slime = level.hazard(simulation.isHorizontalFlipped ? 150 - 860 : -150, -360, 880, 259) //x, y, width, height, damage = 0.002) { + slime.height -= slime.maxHeight - 150 //start slime at zero + slime.min.y += slime.maxHeight + slime.max.y = slime.min.y + slime.height level.custom = () => { level.exit.drawAndCheck(); - level.enter.draw(); }; level.customTopLayer = () => { + slime.query(); + slime.levelRise(0.1) + ctx.fillStyle = "rgba(0,255,255,0.1)" ctx.fillRect(5400, -550, 300, 350) }; @@ -3163,7 +3170,13 @@ const level = { spawn.mapRect(-1950, -3300, 8200, 1800); //roof spawn.mapRect(-250, -200, 1000, 300); // shelf spawn.mapRect(-250, -1700, 1000, 1250); // shelf roof - spawn.blockDoor(710, -210); + // spawn.blockDoor(710, -210); + spawn.mapRect(705, -210, 25, 50); + spawn.mapRect(725, -220, 25, 50); + spawn.bodyRect(750, -125, 125, 125); + spawn.bodyRect(875, -50, 50, 50); + + spawn.mapRect(5400, -1700, 400, 1150); //right wall spawn.mapRect(5400, -300, 400, 400); //right wall spawn.mapRect(5700, -3300, 1800, 5100); //right wall @@ -3187,6 +3200,8 @@ const level = { level.enter.draw(); }; level.customTopLayer = () => { + slime.query(); + slime.levelRise(0.1) ctx.fillStyle = "rgba(0,255,255,0.1)" ctx.fillRect(-5400 - 300, -550, 300, 350) }; diff --git a/js/mob.js b/js/mob.js index e68e46c..a3590df 100644 --- a/js/mob.js +++ b/js/mob.js @@ -62,6 +62,8 @@ const mobs = { if (!whom.shield && !whom.isShielded && whom.alive) { if (tech.isIceMaxHealthLoss && whom.health > 0.65 && whom.damageReduction > 0) whom.health = 0.66 if (tech.isIceKill && whom.health < 0.34 && whom.damageReduction > 0 && whom.alive) { + // whom.death(); + whom.damage(Infinity); simulation.drawList.push({ x: whom.position.x, y: whom.position.y, @@ -83,7 +85,6 @@ const mobs = { color: "rgb(0,100,255)", time: 16 }); - whom.death(); } if (whom.isBoss) cycles = Math.floor(cycles * 0.25) let i = whom.status.length @@ -1136,20 +1137,22 @@ const mobs = { }, damage(dmg, isBypassShield = false) { if ((!this.isShielded || isBypassShield) && this.alive) { - dmg *= tech.damageFromTech() - //mobs specific damage changes - if (tech.isFarAwayDmg) dmg *= 1 + Math.sqrt(Math.max(500, Math.min(3000, this.distanceToPlayer())) - 500) * 0.0067 //up to 33% dmg at max range of 3000 - dmg *= this.damageReduction - //energy and heal drain should be calculated after damage boosts - if (tech.energySiphon && dmg !== Infinity && this.isDropPowerUp && m.immuneCycle < m.cycle) m.energy += Math.min(this.health, dmg) * tech.energySiphon - if (tech.healthDrain && dmg !== Infinity && this.isDropPowerUp && Math.random() < tech.healthDrain * Math.min(this.health, dmg)) { - powerUps.spawn(m.pos.x + 20 * (Math.random() - 0.5), m.pos.y + 20 * (Math.random() - 0.5), "heal"); + if (dmg !== Infinity) { + dmg *= tech.damageFromTech() + //mobs specific damage changes + if (tech.isFarAwayDmg) dmg *= 1 + Math.sqrt(Math.max(500, Math.min(3000, this.distanceToPlayer())) - 500) * 0.0067 //up to 33% dmg at max range of 3000 + dmg *= this.damageReduction + //energy and heal drain should be calculated after damage boosts + if (tech.energySiphon && dmg !== Infinity && this.isDropPowerUp && m.immuneCycle < m.cycle) m.energy += Math.min(this.health, dmg) * tech.energySiphon + if (tech.healthDrain && dmg !== Infinity && this.isDropPowerUp && Math.random() < tech.healthDrain * Math.min(this.health, dmg)) { + powerUps.spawn(m.pos.x + 20 * (Math.random() - 0.5), m.pos.y + 20 * (Math.random() - 0.5), "heal"); + } + dmg /= Math.sqrt(this.mass) } - dmg /= Math.sqrt(this.mass) this.health -= dmg //this.fill = this.color + this.health + ')'; this.onDamage(dmg); //custom damage effects - if ((this.health < 0.05 || isNaN(this.health)) && this.alive) this.death(); + if ((this.health < 0.01 || isNaN(this.health)) && this.alive) this.death(); } }, onDamage() { diff --git a/js/player.js b/js/player.js index 76a6bb8..e4e4829 100644 --- a/js/player.js +++ b/js/player.js @@ -2929,7 +2929,7 @@ const m = { if (m.energy < 0.05 && m.fireCDcycle < m.cycle && !input.fire) m.fireCDcycle = m.cycle if (m.fireCDcycle + 30 < m.cycle && !input.fire) { //automatically cloak if not firing const drain = 0.1 - if (!m.isCloak && m.energy > drain) { + if (!m.isCloak && m.energy > drain + 0.05) { m.energy -= drain m.isCloak = true //enter cloak @@ -2945,28 +2945,18 @@ const m = { m.enterCloakCycle = m.cycle if (tech.isCloakHealLastHit && m.lastHit > 0) { const heal = Math.min(0.75 * m.lastHit, m.energy) - m.energy -= heal - simulation.drawList.push({ //add dmg to draw queue - x: m.pos.x, - y: m.pos.y, - radius: Math.sqrt(heal) * 200, - color: "rgba(0,255,200,0.6)", - time: 16 - }); - m.addHealth(heal); //heal from last hit - // if (tech.isEnergyHealth) { - // simulation.drawList.push({ //add dmg to draw queue - // x: m.pos.x, - // y: m.pos.y, - // radius: Math.sqrt(heal) * 200, - // color: "#0ad", //simulation.mobDmgColor - // time: 16 - // }); - // m.energy += heal - // } else { - // } - m.lastHit = 0 - // simulation.makeTextLog(`m.health += ${(heal).toFixed(3)}`) //
${m.health.toFixed(3)} + if (m.energy > heal) { + m.energy -= heal + m.addHealth(heal); //heal from last hit + m.lastHit = 0 + simulation.drawList.push({ //add dmg to draw queue + x: m.pos.x, + y: m.pos.y, + radius: Math.sqrt(heal) * 200, + color: "rgba(0,255,200,0.6)", + time: 16 + }); + } } if (tech.isIntangible) { for (let i = 0; i < bullet.length; i++) { diff --git a/js/powerup.js b/js/powerup.js index 53f24af..013ea25 100644 --- a/js/powerup.js +++ b/js/powerup.js @@ -650,7 +650,7 @@ const powerUps = { for (let i = 0; i < b.guns.length; i++) { if (!b.guns[i].have) options.push(i); } - let totalChoices = Math.min(options.length, tech.isDeterminism ? 1 : 3 + tech.extraChoices) + let totalChoices = Math.min(options.length, tech.isDeterminism ? 1 : 2 + tech.extraChoices) if (tech.isFlipFlopChoices) totalChoices += tech.isRelay ? (tech.isFlipFlopOn ? -1 : 7) : (tech.isFlipFlopOn ? 7 : -1) //flip the order for relay function removeOption(index) { for (let i = 0; i < options.length; i++) { @@ -787,7 +787,7 @@ const powerUps = { for (let i = 1; i < m.fieldUpgrades.length; i++) { //skip field emitter if (i !== m.fieldMode) options.push(i); } - let totalChoices = Math.min(options.length, tech.isDeterminism ? 1 : 3 + tech.extraChoices) + let totalChoices = Math.min(options.length, tech.isDeterminism ? 1 : 2 + tech.extraChoices) if (tech.isFlipFlopChoices) totalChoices += tech.isRelay ? (tech.isFlipFlopOn ? -1 : 7) : (tech.isFlipFlopOn ? 7 : -1) //flip the order for relay function removeOption(index) { @@ -953,7 +953,10 @@ const powerUps = { totalChoices = optionLengthNoDuplicates if (tech.isBanish) { //when you run out of options eject banish for (let i = 0, len = tech.tech.length; i < len; i++) { - if (tech.tech[i].name === "decoherence") powerUps.ejectTech(i, true) + if (tech.tech[i].name === "decoherence") { + // console.log(i) + powerUps.ejectTech(i, true) + } } simulation.makeTextLog(`decoherence tech ejected`) simulation.makeTextLog(`options reset`) @@ -1099,7 +1102,7 @@ const powerUps = { let cycle = () => { if (count > 0) { requestAnimationFrame(cycle); - if (!simulation.paused && !simulation.isChoosing) { //&& !(simulation.cycle % 2) + if (!simulation.paused && !simulation.isChoosing && m.alive) { //&& !(simulation.cycle % 2) count-- const where = { x: m.pos.x + 50 * (Math.random() - 0.5), y: m.pos.y + 50 * (Math.random() - 0.5) } powerUps.spawn(where.x, where.y, type); @@ -1182,9 +1185,13 @@ const powerUps = { // } }, randomPowerUpCounter: 0, + isFieldSpawned: false, //makes it so a field spawns once but not more times spawnBossPowerUp(x, y) { //boss spawns field and gun tech upgrades if (level.levels[level.onLevel] !== "final") { - if (m.fieldMode === 0 && !m.coupling) { + // if (level.levelsCleared === 1) powerUps.spawn(x, y, "field") + // if (m.fieldMode === 0 && !m.coupling) { + if (!powerUps.isFieldSpawned) { + powerUps.isFieldSpawned = true powerUps.spawn(x, y, "field") } else { powerUps.randomPowerUpCounter++; @@ -1227,7 +1234,7 @@ const powerUps = { } }, spawnStartingPowerUps(x, y) { //used for map specific power ups, mostly to give player a starting gun - if (level.levelsCleared < 4) { //runs 4 times on all difficulty levels + if (level.levelsCleared < 4) { //runs on first 4 levels on all difficulties if (level.levelsCleared > 1) powerUps.spawn(x, y, "tech") if (b.inventory.length === 0) { powerUps.spawn(x, y, "gun", false); //first gun @@ -1242,23 +1249,24 @@ const powerUps = { } else { for (let i = 0; i < 4; i++) powerUps.spawnRandomPowerUp(x, y); } - } else { + } else { //after the first 4 levels just spawn a random power up for (let i = 0; i < 3; i++) powerUps.spawnRandomPowerUp(x, y); } }, ejectTech(choose = 'random', isOverride = false) { if (!simulation.isChoosing || isOverride) { + // console.log(tech.tech[choose].name, tech.tech[choose].count, tech.tech[choose].isNonRefundable) //find which tech you have if (choose === 'random') { const have = [] for (let i = 0; i < tech.tech.length; i++) { if (tech.tech[i].count > 0 && !tech.tech[i].isNonRefundable) have.push(i) } - if (have.length === 0) { - for (let i = 0; i < tech.tech.length; i++) { - if (tech.tech[i].count > 0) have.push(i) - } - } + // if (have.length === 0) { + // for (let i = 0; i < tech.tech.length; i++) { + // if (tech.tech[i].count > 0) have.push(i) + // } + // } if (have.length) { choose = have[Math.floor(Math.random() * have.length)] @@ -1279,7 +1287,7 @@ const powerUps = { } else { return false } - } else if (tech.tech[choose].count && tech.tech[choose].isNonRefundable) { + } else if (tech.tech[choose].count && !tech.tech[choose].isNonRefundable) { // simulation.makeTextLog(`
  ${tech.tech[choose].name} was ejected`, 600) //message about what tech was lost simulation.makeTextLog(`tech.remove("${tech.tech[choose].name}")`) diff --git a/js/simulation.js b/js/simulation.js index 70ab980..b261fd9 100644 --- a/js/simulation.js +++ b/js/simulation.js @@ -776,6 +776,7 @@ const simulation = { powerUps.totalPowerUps = 0; powerUps.research.count = 0; powerUps.boost.endCycle = 0 + powerUps.isFieldSpawned = false m.setFillColors(); // m.maxHealth = 1 // m.maxEnergy = 1 diff --git a/js/spawn.js b/js/spawn.js index 572f5e0..e4100a6 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -31,7 +31,8 @@ const spawn = { "striker", "striker", "laser", "laser", "pulsar", "pulsar", - "launcher", "launcherOne", "exploder", "sneaker", "sucker", "sniper", "spinner", "grower", "beamer", "focuser", "spawner", "ghoster", + "launcher", "launcherOne", "exploder", "sneaker", "sucker", "sniper", "spinner", "grower", "beamer", "spawner", "ghoster", + //, "focuser" ], mobTypeSpawnOrder: [], //preset list of mob names calculated at the start of a run by the randomSeed mobTypeSpawnIndex: 0, //increases as the mob type cycles @@ -350,25 +351,465 @@ const spawn = { let me = mob[mob.length - 1]; setTimeout(() => { //fix mob in place, but allow rotation me.constraint = Constraint.create({ - pointA: { - x: me.position.x, - y: me.position.y - }, + pointA: { x: me.position.x, y: me.position.y }, bodyB: me, stiffness: 1, damping: 1 }); Composite.add(engine.world, me.constraint); - }, 2000); //add in a delay in case the level gets flipped left right + }, 1000); //add in a delay in case the level gets flipped left right me.isBoss = true; me.isFinalBoss = true; me.frictionAir = 0.01; me.memory = Infinity; - me.hasRunDeathScript = false me.locatePlayer(); - // const density = 0.2 + me.hasRunDeathScript = false + me.cycle = 1; + Matter.Body.setDensity(me, 0.2); //extra dense //normal is 0.001 //makes effective life much larger - // spawn.shield(me, x, y, 1); + me.damageReduction = 0.16 + me.startingDamageReduction = me.damageReduction + me.nextHealthThreshold = 0.999 + me.invulnerableCount = 0 + me.isInvulnerable = false + me.totalModes = 0 + me.lastDamageCycle = 0 + me.onDamage = function() { + this.lastDamageCycle = this.cycle + if (this.health < this.nextHealthThreshold) { + if (this.health === 1) me.cycle = 1; //reset fight + this.health = this.nextHealthThreshold - 0.01 + this.nextHealthThreshold = Math.floor(this.health * 4) / 4 //0.75,0.5,0.25 + this.invulnerableCount = 200 + 10 * simulation.difficultyMode //how long does invulnerable time last + this.isInvulnerable = true + this.damageReduction = 0 + } + }; + me.invulnerable = function() { + if (this.isInvulnerable) { + this.invulnerableCount-- + if (this.invulnerableCount < 0) { + this.isInvulnerable = false + this.damageReduction = this.startingDamageReduction + this.pushAway(); + this.mode[this.totalModes].enter() //enter new mode + this.totalModes++ + } + ctx.beginPath(); //draw invulnerable + let vertices = this.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let j = 1; j < vertices.length; j++) ctx.lineTo(vertices[j].x, vertices[j].y); + ctx.lineTo(vertices[0].x, vertices[0].y); + ctx.lineWidth = 15 + 6 * Math.random(); + ctx.strokeStyle = `rgba(255,255,255,${0.5+0.2*Math.random()})`; + ctx.stroke(); + ctx.fillStyle = `rgba(255,255,255,${Math.min(1, 120/(this.invulnerableCount+60))})`; + ctx.fill() + } + } + me.damageReductionDecay = function() { //slowly make the boss take more damage over time //damageReduction resets with each invulnerability phase + //only decay once a second //only decay if the player has done damage in the last 4 seconds + if (!(me.cycle % 60) && this.lastDamageCycle + 240 > this.cycle) this.damageReduction *= 1.02 + // console.log(this.damageReduction) + } + me.maxMobs = 400 + me.mode = [{ + name: "mobs", + whoSpawn: spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)], + spawnRate: 300 - 12 * simulation.difficultyMode, + do() { + if (!(me.cycle % this.spawnRate) && mob.length < me.maxMobs) { + me.torque += 0.000015 * me.inertia; //spin + const index = Math.floor((me.cycle % (this.spawnRate * 6)) / this.spawnRate) //int from 0 to 5 + if (index === 0) this.whoSpawn = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]; //fire a bullet from each vertex + const vertex = me.vertices[index] + const unit = Vector.normalise(Vector.sub(me.position, vertex)) + const where = Vector.add(vertex, Vector.mult(unit, -30)) + spawn[this.whoSpawn](where.x + 50 * (Math.random() - 0.5), where.y + 50 * (Math.random() - 0.5)); + const velocity = Vector.mult(Vector.perp(unit), -18) //give the mob a rotational velocity as if they were attached to a vertex + Matter.Body.setVelocity(mob[mob.length - 1], { x: me.velocity.x + velocity.x, y: me.velocity.y + velocity.y }); + } + }, + enter() {}, + exit() {}, + }, + { + name: "hoppers", + spawnRate: 400 - 14 * simulation.difficultyMode, + do() { + if (!(me.cycle % this.spawnRate) && mob.length < me.maxMobs) { + me.torque += 0.00002 * me.inertia; //spin + for (let i = 0; i < 6; i++) { + const vertex = me.vertices[i] + spawn.hopBullet(vertex.x + 50 * (Math.random() - 0.5), vertex.y + 50 * (Math.random() - 0.5)); + const velocity = Vector.mult(Vector.perp(Vector.normalise(Vector.sub(me.position, vertex))), -18) //give the mob a rotational velocity as if they were attached to a vertex + Matter.Body.setVelocity(mob[mob.length - 1], { + x: me.velocity.x + velocity.x, + y: me.velocity.y + velocity.y + }); + } + } + }, + enter() {}, + exit() {}, + }, + { + name: "seekers", + spawnRate: 60 - 2 * simulation.difficultyMode, + do() { + if (!(me.cycle % this.spawnRate) && mob.length < me.maxMobs) { //spawn seeker + const index = Math.floor((me.cycle % 360) / 60) + spawn.seeker(me.vertices[index].x, me.vertices[index].y, 20 * (0.5 + Math.random()), 9); //give the bullet a rotational velocity as if they were attached to a vertex + const who = mob[mob.length - 1] + Matter.Body.setDensity(who, 0.00003); //normal is 0.001 + who.timeLeft = 720 + 15 * simulation.difficulty //* (0.8 + 0.4 * Math.random()); + who.accelMag = 0.0004 * simulation.accelScale; //* (0.8 + 0.4 * Math.random()) + who.frictionAir = 0.01 //* (0.8 + 0.4 * Math.random()); + } + }, + enter() {}, + exit() {}, + }, + { + name: "mines", + bombCycle: 0, + bombInterval: 36 - 2 * simulation.difficultyMode, + do() { + const yOff = 120 + this.bombCycle++ + if (!(this.bombCycle % this.bombInterval) && (this.bombCycle & 60) > 30) { //mines above player + if (simulation.isHorizontalFlipped) { + if (this.bombCycle > 120) { //wait 2 seconds before targeted mines drop + const x = m.pos.x + 200 * (Math.random() - 0.5) + if (x > -750) { //mines above player IN tunnel + spawn.mine(Math.min(Math.max(-730, x), 100), -450 - yOff * Math.random()) //player in main room + mob[mob.length - 1].fallHeight = -209 + } else { //mines above player NOT in tunnel + spawn.mine(Math.min(Math.max(-5375, x), -765), -1500 - yOff * Math.random()) //player in tunnel + mob[mob.length - 1].fallHeight = -9 + } + } + if (Math.random() < 0.5) { + spawn.mine(-5350 + 4550 * Math.random(), -1500 - yOff * Math.random()) //random mines + mob[mob.length - 1].fallHeight = -9 + } + } else { + if (this.bombCycle > 120) { //wait 2 seconds before targeted mines drop + const x = m.pos.x + 200 * (Math.random() - 0.5) + if (x < 750) { //mines above player IN tunnel + spawn.mine(Math.min(Math.max(-100, x), 735), -450 - yOff * Math.random()) //player in main room + mob[mob.length - 1].fallHeight = -209 + } else { //mines above player NOT in tunnel + spawn.mine(Math.min(Math.max(760, x), 5375), -1500 - yOff * Math.random()) //player in tunnel + mob[mob.length - 1].fallHeight = -9 + } + } + if (Math.random() < 0.5) { //random mines, but not in tunnel + spawn.mine(800 + 4550 * Math.random(), -1500 - yOff * Math.random()) //random mines + mob[mob.length - 1].fallHeight = -9 + } + } + } + for (let i = 0; i < mob.length; i++) { //mines fall + if (mob[i].isMine) { + if (mob[i].position.y < mob[i].fallHeight) { + mob[i].force.y += mob[i].mass * 0.03; + } else if (!mob[i].isOnGround) { + mob[i].isOnGround = true + Matter.Body.setPosition(mob[i], { + x: mob[i].position.x, + y: mob[i].fallHeight + }) + } + } + } + }, + enter() { + this.bombCycle = 0; + }, + exit() { + for (let i = 0; i < mob.length; i++) { + if (mob[i].isMine) mob[i].isExploding = true //explode the mines at the start of new round + } + }, + }, + { + name: "orbiters", + spawnRate: 26 - 2 * simulation.difficultyMode, + do() { + if (!(me.cycle % this.spawnRate) && mob.length < me.maxMobs) { + const speed = (0.01 + 0.0005 * simulation.difficultyMode) * ((Math.random() < 0.5) ? 0.85 : -1.15) + const phase = 0 //Math.floor(2 * Math.random()) * Math.PI + me.orbitalNoVelocity(me, 360 + 2150 * Math.random(), 0.1 * Math.random() + phase, speed) // orbital(who, radius, phase, speed) + } + }, + enter() {}, + exit() {}, + }, + { + name: "laser", + spinForce: 0.00000015, + fadeCycle: 0, //fades in over 4 seconds + do() { + this.fadeCycle++ + if (this.fadeCycle > 0) { + me.torque += this.spinForce * me.inertia; //spin //0.00000015 + if (this.fadeCycle > 360) this.fadeCycle = -120 + 2 * simulation.difficultyMode * simulation.difficultyMode //turn laser off and reset + ctx.strokeStyle = "#50f"; + ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + ctx.lineWidth = 1.5; + ctx.beginPath(); + if (this.fadeCycle < 120) { //damage scales up over 2 seconds to give player time to move as it fades in + const scale = this.fadeCycle / 120 + const dmg = (this.fadeCycle < 60) ? 0 : 0.15 * simulation.dmgScale * scale + me.lasers(me.vertices[0], me.angle + Math.PI / 6, dmg); + me.lasers(me.vertices[1], me.angle + 3 * Math.PI / 6, dmg); + me.lasers(me.vertices[2], me.angle + 5 * Math.PI / 6, dmg); + me.lasers(me.vertices[3], me.angle + 7 * Math.PI / 6, dmg); + me.lasers(me.vertices[4], me.angle + 9 * Math.PI / 6, dmg); + me.lasers(me.vertices[5], me.angle + 11 * Math.PI / 6, dmg); + ctx.strokeStyle = `rgba(85, 0, 255,${scale})`; + ctx.stroke(); + ctx.strokeStyle = `rgba(80, 0, 255,${0.07*scale})` + } else if (this.fadeCycle > 0) { + me.lasers(me.vertices[0], me.angle + Math.PI / 6); + me.lasers(me.vertices[1], me.angle + 3 * Math.PI / 6); + me.lasers(me.vertices[2], me.angle + 5 * Math.PI / 6); + me.lasers(me.vertices[3], me.angle + 7 * Math.PI / 6); + me.lasers(me.vertices[4], me.angle + 9 * Math.PI / 6); + me.lasers(me.vertices[5], me.angle + 11 * Math.PI / 6); + ctx.strokeStyle = "#50f"; + ctx.stroke(); + ctx.strokeStyle = "rgba(80,0,255,0.07)"; + } + ctx.setLineDash([]); + ctx.lineWidth = 20; + ctx.stroke(); + } + }, + enter() { this.fadeCycle = 0 }, + exit() {}, + }, + { + name: "black hole", + eventHorizon: 0, + eventHorizonRadius: 2200, + eventHorizonCycle: 0, + do() { + this.eventHorizonCycle++ + this.eventHorizon = Math.max(0, this.eventHorizonRadius * Math.sin(this.eventHorizonCycle * 0.007)) //eventHorizon waves in and out + //draw darkness + ctx.beginPath(); + ctx.arc(me.position.x, me.position.y, this.eventHorizon * 0.2, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.3)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(me.position.x, me.position.y, this.eventHorizon * 0.4, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.25)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(me.position.x, me.position.y, this.eventHorizon * 0.6, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.2)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(me.position.x, me.position.y, this.eventHorizon * 0.8, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,20,40,0.15)"; + ctx.fill(); + ctx.beginPath(); + ctx.arc(me.position.x, me.position.y, this.eventHorizon, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.1)"; + ctx.fill(); + //when player is inside event horizon + if (Vector.magnitude(Vector.sub(me.position, player.position)) < this.eventHorizon) { + if (m.immuneCycle < m.cycle) { + if (m.energy > 0) m.energy -= 0.02 + if (m.energy < 0.05 && m.immuneCycle < m.cycle) m.damage(0.0004 * simulation.dmgScale); + } + const angle = Math.atan2(player.position.y - me.position.y, player.position.x - me.position.x); + player.force.x -= 0.0017 * Math.cos(angle) * player.mass * (m.onGround ? 1.7 : 1); + player.force.y -= 0.0017 * Math.sin(angle) * player.mass; + //draw line to player + ctx.beginPath(); + ctx.moveTo(me.position.x, me.position.y); + ctx.lineTo(m.pos.x, m.pos.y); + ctx.lineWidth = Math.min(60, me.radius * 2); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.stroke(); + ctx.beginPath(); + ctx.arc(m.pos.x, m.pos.y, 40, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.3)"; + ctx.fill(); + } + me.curl(this.eventHorizon); + }, + enter() { this.eventHorizonCycle = 0 }, + exit() {}, + }, + { + name: "oscillation", + waveCycle: 0, + whereX: simulation.isHorizontalFlipped ? -3000 : 3000, + do() { + this.waveCycle += !me.isStunned + !me.isSlowed + // if (!me.isShielded && (!(this.waveCycle % 1800) || !(this.waveCycle % 1801))) spawn.shield(me, me.position.x, me.position.y, 1); + me.constraint.pointA = { + x: this.whereX + 600 * Math.sin(this.waveCycle * 0.005), + y: me.constraint.pointA.y + } + }, + enter() { + spawn.shield(me, me.position.x, me.position.y, 1); + }, + exit() { this.waveCycle = 0 }, + }, + // { + // name: "__", + // do() {}, + // enter() {}, + // exit() {}, + // }, + ] + shuffle(me.mode); + me.do = function() { + this.fill = '#' + Math.random().toString(16).substr(-6); //flash colors + if (this.health < 1) { + this.cycle++; + this.checkStatus(); + this.invulnerable(); + this.spawnBoss(); + this.damageReductionDecay(); + for (let i = 0; i < this.totalModes; i++) this.mode[i].do() + // this.mode[3].do() + // this.mode[4].do() + // this.mode[7].do() + } + }; + me.spawnRate = 4300 - 30 * simulation.difficultyMode * simulation.difficultyMode + me.spawnBoss = function() { //if the fight lasts too long start spawning bosses + if (!(me.cycle % this.spawnRate) && this.health < 1) { + this.spawnRate = Math.max(300, this.spawnRate - 10 * simulation.difficultyMode * simulation.difficultyMode) //reduce the timer each time a boss spawns + spawn.randomLevelBoss(3000 * (simulation.isHorizontalFlipped ? -1 : 1) + 2000 * (Math.random() - 0.5), -1100 + 200 * (Math.random() - 0.5)) + } + } + me.pushAway = function(magX = 0.13, magY = 0.05) { + for (let i = 0, len = body.length; i < len; ++i) { //push blocks away horizontally + body[i].force.x += magX * body[i].mass * (body[i].position.x > this.position.x ? 1 : -1) + body[i].force.y -= magY * body[i].mass + } + for (let i = 0, len = bullet.length; i < len; ++i) { //push blocks away horizontally + bullet[i].force.x += magX * bullet[i].mass * (bullet[i].position.x > this.position.x ? 1 : -1) + bullet[i].force.y -= magY * bullet[i].mass + } + for (let i = 0, len = powerUp.length; i < len; ++i) { //push blocks away horizontally + powerUp[i].force.x += magX * powerUp[i].mass * (powerUp[i].position.x > this.position.x ? 1 : -1) + powerUp[i].force.y -= magY * powerUp[i].mass + } + player.force.x += magX * player.mass * (player.position.x > this.position.x ? 1 : -1) + player.force.y -= magY * player.mass + } + me.orbitalNoVelocity = function(who, radius, phase, speed) { //orbitals that don't include their host velocity //specifically for finalBoss + mobs.spawn(who.position.x, who.position.y, 6, 20, "rgb(255,0,150)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + Matter.Body.setDensity(me, 0.001); //normal is 0.001 + me.leaveBody = false; + me.isDropPowerUp = false; + me.isBadTarget = true; + me.isUnstable = true; //dies when blocked + me.showHealthBar = false; + me.isOrbital = true; + me.collisionFilter.category = cat.mobBullet; + me.collisionFilter.mask = cat.bullet; //cat.player | cat.map | cat.body + me.do = function() { + const time = simulation.cycle * speed + phase + const orbit = { x: Math.cos(time), y: Math.sin(time) } + Matter.Body.setPosition(this, Vector.add(who.position, Vector.mult(orbit, radius))) + if (Matter.Query.collides(this, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for 30 cycles + const dmg = 0.06 * simulation.dmgScale + m.damage(dmg); + simulation.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: dmg * 500, + color: simulation.mobDmgColor, + time: simulation.drawTime + }); + this.death(); + } + }; + } + me.lasers = function(where, angle, dmg = 0.14 * simulation.dmgScale) { + const vertexCollision = function(v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = simulation.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 = simulation.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + }; + + const seeRange = 7000; + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + const look = { + x: where.x + seeRange * Math.cos(angle), + y: where.y + seeRange * Math.sin(angle) + }; + // vertexCollision(where, look, mob); + vertexCollision(where, look, map); + vertexCollision(where, look, body); + if (!m.isCloak) vertexCollision(where, look, [playerBody, playerHead]); + if (best.who && (best.who === playerBody || best.who === playerHead) && m.immuneCycle < m.cycle) { + if (m.immuneCycle < m.cycle + 60 + m.collisionImmuneCycles) m.immuneCycle = m.cycle + 60 + m.collisionImmuneCycles; //player is immune to damage extra time + m.damage(dmg); + simulation.drawList.push({ //add dmg to draw queue + x: best.x, + y: best.y, + radius: dmg * 1500, + color: "rgba(80,0,255,0.5)", + time: 20 + }); + } + //draw beam + if (best.dist2 === Infinity) best = look; + ctx.moveTo(where.x, where.y); + ctx.lineTo(best.x, best.y); + } me.onDeath = function() { if (!this.hasRunDeathScript) { this.hasRunDeathScript = true @@ -504,314 +945,500 @@ const spawn = { } } }; - me.onDamage = function() {}; - me.cycle = 660; - me.endCycle = 780; - me.totalCycles = 0 - me.mode = 0; - me.damageReduction = 0.25 //reset on each new mode - me.pushAway = function(magX = 0.13, magY = 0.05) { - for (let i = 0, len = body.length; i < len; ++i) { //push blocks away horizontally - body[i].force.x += magX * body[i].mass * (body[i].position.x > this.position.x ? 1 : -1) - body[i].force.y -= magY * body[i].mass - } - for (let i = 0, len = bullet.length; i < len; ++i) { //push blocks away horizontally - bullet[i].force.x += magX * bullet[i].mass * (bullet[i].position.x > this.position.x ? 1 : -1) - bullet[i].force.y -= magY * bullet[i].mass - } - for (let i = 0, len = powerUp.length; i < len; ++i) { //push blocks away horizontally - powerUp[i].force.x += magX * powerUp[i].mass * (powerUp[i].position.x > this.position.x ? 1 : -1) - powerUp[i].force.y -= magY * powerUp[i].mass - } - player.force.x += magX * player.mass * (player.position.x > this.position.x ? 1 : -1) - player.force.y -= magY * player.mass - } - me.do = function() { - this.modeDo(); //this does different things based on the mode - this.checkStatus(); - this.cycle++; //switch modes÷ if time isn't paused - this.totalCycles++; - if (this.health > 0.25) { - if (this.cycle > this.endCycle) { - this.showHealthBar = true - this.cycle = 0; - this.mode++ - this.damageReduction = 0.25 - if (this.totalCycles > 180) this.pushAway(); - if (this.mode > 2) { - this.mode = 0; - this.fill = "#50f"; - this.rotateVelocity = Math.abs(this.rotateVelocity) * (player.position.x > this.position.x ? 1 : -1) //rotate so that the player can get away - this.modeDo = this.modeLasers - //push blocks and player away, since this is the end of suck, and suck causes blocks to fall on the boss and stun it - Matter.Body.scale(this, 1000, 1000); - if (!this.isShielded) spawn.shield(this, this.position.x, this.position.y, 1); // regen shield to also prevent stun - } else if (this.mode === 1) { - this.fill = "#50f"; // this.fill = "rgb(150,150,255)"; - this.modeDo = this.modeSpawns - } else if (this.mode === 2) { - this.fill = "#000"; - this.modeDo = this.modeSuck - Matter.Body.scale(this, 0.001, 0.001); - this.damageReduction = 0.000025 - this.showHealthBar = false - } - if (tech.isGunCycle) { - b.inventoryGun++; - if (b.inventoryGun > b.inventory.length - 1) b.inventoryGun = 0; - simulation.switchGun(); - } - } - } else if (this.mode !== 3) { //all three modes at once , this runs once - this.showHealthBar = true - this.pushAway(); - this.cycle = 0; - this.endCycle = Infinity - this.damageReduction = 0.15 - if (this.mode === 2) { - Matter.Body.scale(this, 500, 500); - } else { - Matter.Body.scale(this, 0.5, 0.5); - } - this.mode = 3 - this.fill = "#000"; - this.eventHorizon = 750 - this.spawnInterval = 600 - this.rotateVelocity = 0.001 * (player.position.x > this.position.x ? 1 : -1) //rotate so that the player can get away - // if (!this.isShielded) spawn.shield(this, x, y, 1); //regen shield here ? - this.modeDo = this.modeAll - this.eventHorizonRadius = 700 - if (tech.isGunCycle) { - b.inventoryGun++; - if (b.inventoryGun > b.inventory.length - 1) b.inventoryGun = 0; - simulation.switchGun(); - } - } - // } - }; - me.modeDo = function() {} - me.modeAll = function() { - this.modeSpawns() - this.modeSuck() - this.modeLasers() - } - me.spawnInterval = 395 - me.modeSpawns = function() { - if (!(this.cycle % this.spawnInterval) && mob.length < 40) { - if (this.mode !== 3) Matter.Body.setAngularVelocity(this, 0.1) - //fire a bullet from each vertex - const whoSpawn = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]; - for (let i = 0, len = 2 + this.totalCycles / 1000; i < len; i++) { - const vertex = this.vertices[i % 6] - spawn[whoSpawn](vertex.x + 50 * (Math.random() - 0.5), vertex.y + 50 * (Math.random() - 0.5)); - const velocity = Vector.mult(Vector.perp(Vector.normalise(Vector.sub(this.position, vertex))), -18) //give the mob a rotational velocity as if they were attached to a vertex - Matter.Body.setVelocity(mob[mob.length - 1], { - x: this.velocity.x + velocity.x, - y: this.velocity.y + velocity.y - }); - } - if (!(this.cycle % 2 * this.spawnInterval) && mob.length < 40) { - const len = (this.totalCycles / 1000 + simulation.difficulty / 2 - 30) / 15 - for (let i = 0; i < len; i++) { - spawn.randomLevelBoss(3000 * (simulation.isHorizontalFlipped ? -1 : 1) + 2000 * (Math.random() - 0.5), -1100 + 200 * (Math.random() - 0.5)) - } - } - } - } - me.eventHorizon = 0 - me.eventHorizonRadius = 1300 - me.modeSuck = function() { - if (!(this.cycle % 30)) { - const index = Math.floor((this.cycle % 360) / 60) - spawn.seeker(this.vertices[index].x, this.vertices[index].y, 20 * (0.5 + Math.random()), 9); //give the bullet a rotational velocity as if they were attached to a vertex - const who = mob[mob.length - 1] - Matter.Body.setDensity(who, 0.00003); //normal is 0.001 - who.timeLeft = 720 + 10 * simulation.difficulty //* (0.8 + 0.4 * Math.random()); - who.accelMag = 0.0003 * simulation.accelScale; //* (0.8 + 0.4 * Math.random()) - who.frictionAir = 0.01 //* (0.8 + 0.4 * Math.random()); - const velocity = Vector.mult(Vector.perp(Vector.normalise(Vector.sub(this.position, this.vertices[index]))), -7) - Matter.Body.setVelocity(who, { - x: this.velocity.x + velocity.x, - y: this.velocity.y + velocity.y - }); - } - - //eventHorizon waves in and out - if (this.cycle + 30 > this.endCycle) { //shrink fast in last bit of cycle - this.eventHorizon = 0.93 * this.eventHorizon - } else { - this.eventHorizon = 0.97 * this.eventHorizon + 0.03 * (this.eventHorizonRadius * (1 - 0.5 * Math.cos(this.cycle * 0.015))) - } - //draw darkness - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, this.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, this.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, this.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, this.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, this.eventHorizon, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.05)"; - ctx.fill(); - //when player is inside event horizon - if (Vector.magnitude(Vector.sub(this.position, player.position)) < this.eventHorizon) { - if (m.immuneCycle < m.cycle) { - if (m.energy > 0) m.energy -= 0.02 - if (m.energy < 0.05 && m.immuneCycle < m.cycle) m.damage(0.0004 * simulation.dmgScale); - } - const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); - player.force.x -= 0.0017 * Math.cos(angle) * player.mass * (m.onGround ? 1.7 : 1); - player.force.y -= 0.0017 * Math.sin(angle) * player.mass; - //draw line to player - ctx.beginPath(); - ctx.moveTo(this.position.x, this.position.y); - ctx.lineTo(m.pos.x, m.pos.y); - ctx.lineWidth = Math.min(60, this.radius * 2); - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(); - ctx.beginPath(); - ctx.arc(m.pos.x, m.pos.y, 40, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(0,0,0,0.3)"; - ctx.fill(); - } - this.curl(this.eventHorizon); - } - me.rotateVelocity = 0.0025 - me.rotateCount = 0; - me.lasers = function(where, angle, dmg = 0.14 * simulation.dmgScale) { - const vertexCollision = function(v1, v1End, domain) { - for (let i = 0; i < domain.length; ++i) { - let vertices = domain[i].vertices; - const len = vertices.length - 1; - for (let j = 0; j < len; j++) { - results = simulation.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 = simulation.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); - if (results.onLine1 && results.onLine2) { - const dx = v1.x - results.x; - const dy = v1.y - results.y; - const dist2 = dx * dx + dy * dy; - if (dist2 < best.dist2) best = { - x: results.x, - y: results.y, - dist2: dist2, - who: domain[i], - v1: vertices[0], - v2: vertices[len] - }; - } - } - }; - - const seeRange = 7000; - best = { - x: null, - y: null, - dist2: Infinity, - who: null, - v1: null, - v2: null - }; - const look = { - x: where.x + seeRange * Math.cos(angle), - y: where.y + seeRange * Math.sin(angle) - }; - // vertexCollision(where, look, mob); - vertexCollision(where, look, map); - vertexCollision(where, look, body); - if (!m.isCloak) vertexCollision(where, look, [playerBody, playerHead]); - if (best.who && (best.who === playerBody || best.who === playerHead) && m.immuneCycle < m.cycle) { - if (m.immuneCycle < m.cycle + 60 + m.collisionImmuneCycles) m.immuneCycle = m.cycle + 60 + m.collisionImmuneCycles; //player is immune to damage extra time - m.damage(dmg); - simulation.drawList.push({ //add dmg to draw queue - x: best.x, - y: best.y, - radius: dmg * 1500, - color: "rgba(80,0,255,0.5)", - time: 20 - }); - } - //draw beam - if (best.dist2 === Infinity) best = look; - ctx.moveTo(where.x, where.y); - ctx.lineTo(best.x, best.y); - } - me.modeLasers = function() { - if (!this.isStunned) { - let slowed = false //check if slowed - for (let i = 0; i < this.status.length; i++) { - if (this.status[i].type === "slow") { - slowed = true - break - } - } - if (!slowed) { - this.rotateCount++ - Matter.Body.setAngle(this, this.rotateCount * this.rotateVelocity) - Matter.Body.setAngularVelocity(this, 0) - Matter - } - } - if (this.cycle < 240) { //damage scales up over 2 seconds to give player time to move - const scale = this.cycle / 240 - const dmg = (this.cycle < 120) ? 0 : 0.14 * simulation.dmgScale * scale - ctx.beginPath(); - this.lasers(this.vertices[0], this.angle + Math.PI / 6, dmg); - this.lasers(this.vertices[1], this.angle + 3 * Math.PI / 6, dmg); - this.lasers(this.vertices[2], this.angle + 5 * Math.PI / 6, dmg); - this.lasers(this.vertices[3], this.angle + 7 * Math.PI / 6, dmg); - this.lasers(this.vertices[4], this.angle + 9 * Math.PI / 6, dmg); - this.lasers(this.vertices[5], this.angle + 11 * Math.PI / 6, dmg); - ctx.strokeStyle = "#50f"; - ctx.lineWidth = 1.5 * scale; - ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); - ctx.stroke(); // Draw it - ctx.setLineDash([]); - ctx.lineWidth = 20; - ctx.strokeStyle = `rgba(80,0,255,${0.07 * scale})`; - ctx.stroke(); // Draw it - } else { - ctx.beginPath(); - this.lasers(this.vertices[0], this.angle + Math.PI / 6); - this.lasers(this.vertices[1], this.angle + 3 * Math.PI / 6); - this.lasers(this.vertices[2], this.angle + 5 * Math.PI / 6); - this.lasers(this.vertices[3], this.angle + 7 * Math.PI / 6); - this.lasers(this.vertices[4], this.angle + 9 * Math.PI / 6); - this.lasers(this.vertices[5], this.angle + 11 * Math.PI / 6); - ctx.strokeStyle = "#50f"; - ctx.lineWidth = 1.5; - ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); - ctx.stroke(); // Draw it - ctx.setLineDash([]); - ctx.lineWidth = 20; - ctx.strokeStyle = "rgba(80,0,255,0.07)"; - ctx.stroke(); // Draw it - } - } }, + // finalBoss(x, y, radius = 300) { + // mobs.spawn(x, y, 6, radius, "rgb(150,150,255)"); + // let me = mob[mob.length - 1]; + // setTimeout(() => { //fix mob in place, but allow rotation + // me.constraint = Constraint.create({ + // pointA: { + // x: me.position.x, + // y: me.position.y + // }, + // bodyB: me, + // stiffness: 1, + // damping: 1 + // }); + // Composite.add(engine.world, me.constraint); + // }, 2000); //add in a delay in case the level gets flipped left right + // me.isBoss = true; + // me.isFinalBoss = true; + // me.frictionAir = 0.01; + // me.memory = Infinity; + // me.hasRunDeathScript = false + // me.locatePlayer(); + // // const density = 0.2 + // Matter.Body.setDensity(me, 0.2); //extra dense //normal is 0.001 //makes effective life much larger + // // spawn.shield(me, x, y, 1); + // me.onDamage = function() {}; + // me.cycle = 660; + // me.endCycle = 780; + // me.totalCycles = 0 + // me.mode = 0; + // me.damageReduction = 0.25 //reset on each new mode + // me.pushAway = function(magX = 0.13, magY = 0.05) { + // for (let i = 0, len = body.length; i < len; ++i) { //push blocks away horizontally + // body[i].force.x += magX * body[i].mass * (body[i].position.x > this.position.x ? 1 : -1) + // body[i].force.y -= magY * body[i].mass + // } + // for (let i = 0, len = bullet.length; i < len; ++i) { //push blocks away horizontally + // bullet[i].force.x += magX * bullet[i].mass * (bullet[i].position.x > this.position.x ? 1 : -1) + // bullet[i].force.y -= magY * bullet[i].mass + // } + // for (let i = 0, len = powerUp.length; i < len; ++i) { //push blocks away horizontally + // powerUp[i].force.x += magX * powerUp[i].mass * (powerUp[i].position.x > this.position.x ? 1 : -1) + // powerUp[i].force.y -= magY * powerUp[i].mass + // } + // player.force.x += magX * player.mass * (player.position.x > this.position.x ? 1 : -1) + // player.force.y -= magY * player.mass + // } + // me.do = function() { + // this.modeDo(); //this does different things based on the mode + // this.checkStatus(); + // this.cycle++; //switch modes÷ if time isn't paused + // this.totalCycles++; + // if (this.health > 0.3) { + // if (this.cycle > this.endCycle) { + // this.showHealthBar = true + // this.cycle = 0; + // this.mode++ + // this.damageReduction = 0.25 + // if (this.totalCycles > 180) this.pushAway(); + // if (this.mode > 3) { + // this.mode = 0; + // this.fill = "#50f"; + // this.rotateVelocity = Math.abs(this.rotateVelocity) * (player.position.x > this.position.x ? 1 : -1) //rotate so that the player can get away + // this.modeDo = this.modeLasers + // //push blocks and player away, since this is the end of suck, and suck causes blocks to fall on the boss and stun it + // Matter.Body.scale(this, 1000, 1000); + // if (!this.isShielded) spawn.shield(this, this.position.x, this.position.y, 1); // regen shield to also prevent stun + // } else if (this.mode === 1) { + // this.fill = "#50f"; // this.fill = "rgb(150,150,255)"; + // this.modeDo = this.modeSpawns + // } else if (this.mode === 2) { + // this.fill = "#50f"; + // this.modeDo = this.modeBombs + // } else if (this.mode === 3) { + // for (let i = 0; i < mob.length; i++) { + // if (mob[i].isMine) mob[i].isExploding = true //explode the mines at the start of new round + // } + // this.fill = "#000"; + // this.modeDo = this.modeSuck + // Matter.Body.scale(this, 0.001, 0.001); + // this.damageReduction = 0.000025 + // this.showHealthBar = false + // } + + // if (tech.isGunCycle) { + // b.inventoryGun++; + // if (b.inventoryGun > b.inventory.length - 1) b.inventoryGun = 0; + // simulation.switchGun(); + // } + // } + // } else if (this.mode !== 3) { //all three modes at once , this runs once + // this.showHealthBar = true + // this.pushAway(); + // this.cycle = 0; + // this.endCycle = Infinity + // this.damageReduction = 0.15 + // if (this.mode === 2) { + // Matter.Body.scale(this, 500, 500); + // } else { + // Matter.Body.scale(this, 0.5, 0.5); + // } + // this.mode = 3 + // this.fill = "#000"; + // this.eventHorizon = 750 + // this.spawnInterval = 600 + // this.rotateVelocity = 0.001 * (player.position.x > this.position.x ? 1 : -1) //rotate so that the player can get away + // // if (!this.isShielded) spawn.shield(this, x, y, 1); //regen shield here ? + // this.modeDo = this.modeAll + // this.eventHorizonRadius = 700 + // if (tech.isGunCycle) { + // b.inventoryGun++; + // if (b.inventoryGun > b.inventory.length - 1) b.inventoryGun = 0; + // simulation.switchGun(); + // } + // } + // // } + // }; + // me.modeDo = function() {} + // me.modeAll = function() { + // this.modeBombs() + // this.modeSpawns() + // this.modeSuck() + // this.modeLasers() + // } + // me.bombInterval = 36 - 0.5 * simulation.difficultyMode * simulation.difficultyMode + // me.modeBombs = function() { + // if (!(this.cycle % 20)) { + // if (m.pos.x < 750) { + // spawn.mine(m.pos.x + 200 * (Math.random() - 0.5), -500) + // } else { + // spawn.mine(Math.min(Math.max(770, m.pos.x + 200 * (Math.random() - 0.5)), 5350), -1500) + // } + // } + // if (!(this.cycle % 10)) spawn.mine(800 + 4550 * Math.random(), -1500) + + // //mines fall + // for (let i = 0; i < mob.length; i++) { + // // if (mob[i].isMine && mob[i].position.y < -5) Matter.Body.setPosition(mob[i], { x: mob[i].position.x, y: mob[i].position.y + 5 }) + // if (mob[i].isMine && mob[i].position.y < -14) mob[i].force.y += mob[i].mass * 0.03; + // } + + // } + // me.spawnInterval = 395 + // me.modeSpawns = function() { + // if (!(this.cycle % this.spawnInterval) && mob.length < 40) { + // if (this.mode !== 3) Matter.Body.setAngularVelocity(this, 0.1) + // //fire a bullet from each vertex + // const whoSpawn = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]; + // for (let i = 0, len = 2 + this.totalCycles / 1000; i < len; i++) { + // const vertex = this.vertices[i % 6] + // spawn[whoSpawn](vertex.x + 50 * (Math.random() - 0.5), vertex.y + 50 * (Math.random() - 0.5)); + // const velocity = Vector.mult(Vector.perp(Vector.normalise(Vector.sub(this.position, vertex))), -18) //give the mob a rotational velocity as if they were attached to a vertex + // Matter.Body.setVelocity(mob[mob.length - 1], { + // x: this.velocity.x + velocity.x, + // y: this.velocity.y + velocity.y + // }); + // } + // if (!(this.cycle % 2 * this.spawnInterval) && mob.length < 40) { + // const len = (this.totalCycles / 1000 + simulation.difficulty / 2 - 30) / 15 + // for (let i = 0; i < len; i++) { + // spawn.randomLevelBoss(3000 * (simulation.isHorizontalFlipped ? -1 : 1) + 2000 * (Math.random() - 0.5), -1100 + 200 * (Math.random() - 0.5)) + // } + // } + // } + // } + // me.eventHorizon = 0 + // me.eventHorizonRadius = 1300 + // me.modeSuck = function() { + // if (!(this.cycle % 30)) { + // const index = Math.floor((this.cycle % 360) / 60) + // spawn.seeker(this.vertices[index].x, this.vertices[index].y, 20 * (0.5 + Math.random()), 9); //give the bullet a rotational velocity as if they were attached to a vertex + // const who = mob[mob.length - 1] + // Matter.Body.setDensity(who, 0.00003); //normal is 0.001 + // who.timeLeft = 720 + 10 * simulation.difficulty //* (0.8 + 0.4 * Math.random()); + // who.accelMag = 0.0003 * simulation.accelScale; //* (0.8 + 0.4 * Math.random()) + // who.frictionAir = 0.01 //* (0.8 + 0.4 * Math.random()); + // const velocity = Vector.mult(Vector.perp(Vector.normalise(Vector.sub(this.position, this.vertices[index]))), -7) + // Matter.Body.setVelocity(who, { + // x: this.velocity.x + velocity.x, + // y: this.velocity.y + velocity.y + // }); + // } + + // //eventHorizon waves in and out + // if (this.cycle + 30 > this.endCycle) { //shrink fast in last bit of cycle + // this.eventHorizon = 0.93 * this.eventHorizon + // } else { + // this.eventHorizon = 0.97 * this.eventHorizon + 0.03 * (this.eventHorizonRadius * (1 - 0.5 * Math.cos(this.cycle * 0.015))) + // } + // //draw darkness + // ctx.beginPath(); + // ctx.arc(this.position.x, this.position.y, this.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, this.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, this.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, this.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, this.eventHorizon, 0, 2 * Math.PI); + // ctx.fillStyle = "rgba(0,0,0,0.05)"; + // ctx.fill(); + // //when player is inside event horizon + // if (Vector.magnitude(Vector.sub(this.position, player.position)) < this.eventHorizon) { + // if (m.immuneCycle < m.cycle) { + // if (m.energy > 0) m.energy -= 0.02 + // if (m.energy < 0.05 && m.immuneCycle < m.cycle) m.damage(0.0004 * simulation.dmgScale); + // } + // const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x); + // player.force.x -= 0.0017 * Math.cos(angle) * player.mass * (m.onGround ? 1.7 : 1); + // player.force.y -= 0.0017 * Math.sin(angle) * player.mass; + // //draw line to player + // ctx.beginPath(); + // ctx.moveTo(this.position.x, this.position.y); + // ctx.lineTo(m.pos.x, m.pos.y); + // ctx.lineWidth = Math.min(60, this.radius * 2); + // ctx.strokeStyle = "rgba(0,0,0,0.5)"; + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(m.pos.x, m.pos.y, 40, 0, 2 * Math.PI); + // ctx.fillStyle = "rgba(0,0,0,0.3)"; + // ctx.fill(); + // } + // this.curl(this.eventHorizon); + // } + // me.rotateVelocity = 0.0025 + // me.rotateCount = 0; + // me.lasers = function(where, angle, dmg = 0.14 * simulation.dmgScale) { + // const vertexCollision = function(v1, v1End, domain) { + // for (let i = 0; i < domain.length; ++i) { + // let vertices = domain[i].vertices; + // const len = vertices.length - 1; + // for (let j = 0; j < len; j++) { + // results = simulation.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 = simulation.checkLineIntersection(v1, v1End, vertices[0], vertices[len]); + // if (results.onLine1 && results.onLine2) { + // const dx = v1.x - results.x; + // const dy = v1.y - results.y; + // const dist2 = dx * dx + dy * dy; + // if (dist2 < best.dist2) best = { + // x: results.x, + // y: results.y, + // dist2: dist2, + // who: domain[i], + // v1: vertices[0], + // v2: vertices[len] + // }; + // } + // } + // }; + + // const seeRange = 7000; + // best = { + // x: null, + // y: null, + // dist2: Infinity, + // who: null, + // v1: null, + // v2: null + // }; + // const look = { + // x: where.x + seeRange * Math.cos(angle), + // y: where.y + seeRange * Math.sin(angle) + // }; + // // vertexCollision(where, look, mob); + // vertexCollision(where, look, map); + // vertexCollision(where, look, body); + // if (!m.isCloak) vertexCollision(where, look, [playerBody, playerHead]); + // if (best.who && (best.who === playerBody || best.who === playerHead) && m.immuneCycle < m.cycle) { + // if (m.immuneCycle < m.cycle + 60 + m.collisionImmuneCycles) m.immuneCycle = m.cycle + 60 + m.collisionImmuneCycles; //player is immune to damage extra time + // m.damage(dmg); + // simulation.drawList.push({ //add dmg to draw queue + // x: best.x, + // y: best.y, + // radius: dmg * 1500, + // color: "rgba(80,0,255,0.5)", + // time: 20 + // }); + // } + // //draw beam + // if (best.dist2 === Infinity) best = look; + // ctx.moveTo(where.x, where.y); + // ctx.lineTo(best.x, best.y); + // } + // me.modeLasers = function() { + // if (!this.isStunned) { + // let slowed = false //check if slowed + // for (let i = 0; i < this.status.length; i++) { + // if (this.status[i].type === "slow") { + // slowed = true + // break + // } + // } + // if (!slowed) { + // this.rotateCount++ + // Matter.Body.setAngle(this, this.rotateCount * this.rotateVelocity) + // Matter.Body.setAngularVelocity(this, 0) + // Matter + // } + // } + // if (this.cycle < 240) { //damage scales up over 2 seconds to give player time to move + // const scale = this.cycle / 240 + // const dmg = (this.cycle < 120) ? 0 : 0.14 * simulation.dmgScale * scale + // ctx.beginPath(); + // this.lasers(this.vertices[0], this.angle + Math.PI / 6, dmg); + // this.lasers(this.vertices[1], this.angle + 3 * Math.PI / 6, dmg); + // this.lasers(this.vertices[2], this.angle + 5 * Math.PI / 6, dmg); + // this.lasers(this.vertices[3], this.angle + 7 * Math.PI / 6, dmg); + // this.lasers(this.vertices[4], this.angle + 9 * Math.PI / 6, dmg); + // this.lasers(this.vertices[5], this.angle + 11 * Math.PI / 6, dmg); + // ctx.strokeStyle = "#50f"; + // ctx.lineWidth = 1.5 * scale; + // ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + // ctx.stroke(); // Draw it + // ctx.setLineDash([]); + // ctx.lineWidth = 20; + // ctx.strokeStyle = `rgba(80,0,255,${0.07 * scale})`; + // ctx.stroke(); // Draw it + // } else { + // ctx.beginPath(); + // this.lasers(this.vertices[0], this.angle + Math.PI / 6); + // this.lasers(this.vertices[1], this.angle + 3 * Math.PI / 6); + // this.lasers(this.vertices[2], this.angle + 5 * Math.PI / 6); + // this.lasers(this.vertices[3], this.angle + 7 * Math.PI / 6); + // this.lasers(this.vertices[4], this.angle + 9 * Math.PI / 6); + // this.lasers(this.vertices[5], this.angle + 11 * Math.PI / 6); + // ctx.strokeStyle = "#50f"; + // ctx.lineWidth = 1.5; + // ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + // ctx.stroke(); // Draw it + // ctx.setLineDash([]); + // ctx.lineWidth = 20; + // ctx.strokeStyle = "rgba(80,0,255,0.07)"; + // ctx.stroke(); // Draw it + // } + // } + // me.onDeath = function() { + // if (!this.hasRunDeathScript) { + // this.hasRunDeathScript = true + // //make a block body to replace this one + // //this body is too big to leave behind in the normal way mobs.replace() + // const len = body.length; + // const v = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) //might help with vertex collision issue, not sure + // body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, v); + // Matter.Body.setVelocity(body[len], { x: 0, y: -3 }); + // Matter.Body.setAngularVelocity(body[len], this.angularVelocity); + // body[len].collisionFilter.category = cat.body; + // body[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet; + // body[len].classType = "body"; + // Composite.add(engine.world, body[len]); //add to world + // const expand = function(that, massLimit) { + // const scale = 1.05; + // Matter.Body.scale(that, scale, scale); + // if (that.mass < massLimit) setTimeout(expand, 20, that, massLimit); + // }; + // expand(body[len], 200) + + // function unlockExit() { + // if (simulation.isHorizontalFlipped) { + // level.exit.x = -5500 - 100; + // } else { + // level.exit.x = 5500; + // } + // level.exit.y = -330; + // Matter.Composite.remove(engine.world, map[map.length - 1]); + // map.splice(map.length - 1, 1); + // simulation.draw.setPaths(); //redraw map draw path + // // level.levels.push("null") + // } + + // //add lore level as next level if player took lore tech earlier in the game + // if (lore.techCount > (lore.techGoal - 1) && !simulation.isCheating) { + // simulation.makeTextLog(`undefined = ${lore.techCount}/${lore.techGoal}`, 360); + // setTimeout(function() { + // simulation.makeTextLog(`level.levels.push("null")`, 720); + // unlockExit() + // level.levels.push("null") + // }, 4000); + // //remove block map element so exit is clear + // } else { //reset game + // let count = 0 + + // function loop() { + // if (!simulation.paused && !simulation.onTitlePage) { + // count++ + // if (count < 660) { + // if (count === 1) simulation.makeTextLog(`//enter testing mode to set level.levels.length to Infinite`); + // if (!(count % 60)) simulation.makeTextLog(`simulation.analysis = ${((count / 60 - Math.random()) * 0.1).toFixed(3)}`); + // } else if (count === 660) { + // simulation.makeTextLog(`simulation.analysis = 1 //analysis complete`); + // } else if (count === 780) { + // simulation.makeTextLog(`undefined = ${lore.techCount}/${lore.techGoal}`) + // } else if (count === 1020) { + // simulation.makeTextLog(`Engine.clear(engine) //simulation successful`); + // } else if (count === 1260) { + // // tech.isImmortal = false; + // // m.death() + // // m.alive = false; + // // simulation.paused = true; + // // m.health = 0; + // // m.displayHealth(); + // document.getElementById("health").style.display = "none" + // document.getElementById("health-bg").style.display = "none" + // document.getElementById("text-log").style.opacity = 0; //fade out any active text logs + // document.getElementById("fade-out").style.opacity = 1; //slowly fades out + // // build.shareURL(false) + // setTimeout(function() { + // if (!simulation.onTitlePage) { + // simulation.paused = true; + // // simulation.clearMap(); + // // Matter.Composite.clear(composite, keepStatic, [deep = false]) + // // Composite.clear(engine.composite); + // engine.world.bodies.forEach((body) => { Matter.Composite.remove(engine.world, body) }) + // Engine.clear(engine); + // simulation.splashReturn(); + // } + // }, 6000); + // return + // } + // } + // if (simulation.testing) { + // unlockExit() + // setTimeout(function() { + // simulation.makeTextLog(`level.levels.length = Infinite`); + // }, 1500); + // } else { + // if (!simulation.onTitlePage) requestAnimationFrame(loop); + // } + // } + // requestAnimationFrame(loop); + // } + // // for (let i = 0; i < 3; i++) + // level.difficultyIncrease(simulation.difficultyMode) //ramp up damage + // //remove power Ups, to avoid spamming console + // function removeAll(array) { + // for (let i = 0; i < array.length; ++i) Matter.Composite.remove(engine.world, array[i]); + // } + // removeAll(powerUp); + // powerUp = []; + + // //pull in particles + // for (let i = 0, len = body.length; i < len; ++i) { + // const velocity = Vector.mult(Vector.normalise(Vector.sub(this.position, body[i].position)), 65) + // const pushUp = Vector.add(velocity, { x: 0, y: -0.5 }) + // Matter.Body.setVelocity(body[i], Vector.add(body[i].velocity, pushUp)); + // } + // //damage all mobs + // for (let j = 0; j < 8; j++) { //in case some mobs leave things after they die + // for (let i = 0, len = mob.length; i < len; ++i) { + // if (mob[i] !== this) { + // if (mob[i].isInvulnerable) { //disable invulnerability + // mob[i].isInvulnerable = false + // mob[i].damageReduction = 1 + // } + // mob[i].damage(Infinity, true); + // } + // } + // } + + // //draw stuff + // for (let i = 0, len = 22; i < len; i++) { + // simulation.drawList.push({ //add dmg to draw queue + // x: this.position.x, + // y: this.position.y, + // radius: (i + 1) * 150, + // color: `rgba(255,255,255,0.17)`, + // time: 5 * (len - i + 1) + // }); + // } + // } + // }; + // }, starter(x, y, radius = Math.floor(15 + 20 * Math.random())) { //easy mob for on level 1 mobs.spawn(x, y, 8, radius, "#9ccdc6"); let me = mob[mob.length - 1]; @@ -1390,7 +2017,7 @@ const spawn = { me.foundPlayer(); } - me.damageReduction = 0.22 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1) + me.damageReduction = 0.16 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1) // me.isInvulnerable = true // me.startingDamageReduction = me.damageReduction // me.damageReduction = 0 @@ -3740,7 +4367,6 @@ const spawn = { }; me.rotateVelocity = Math.min(0.0045, 0.0015 * simulation.accelScale * simulation.accelScale) * (level.levelsCleared > 8 ? 1 : -1) * (simulation.isHorizontalFlipped ? -1 : 1) me.do = function() { - // this.armor(); this.fill = '#' + Math.random().toString(16).substr(-6); //flash colors this.checkStatus(); @@ -4311,15 +4937,16 @@ const spawn = { }; }, mine(x, y) { - mobs.spawn(x, y, 8, 10, "rgb(100,170,150)"); + mobs.spawn(x, y, 8, 10, "rgb(61, 125, 121)"); //"rgb(100,170,150)" let me = mob[mob.length - 1]; me.stroke = "transparent"; Matter.Body.setDensity(me, 0.0001); //normal is 0.001 // Matter.Body.setStatic(me, true); //make static (disables taking damage) me.frictionAir = 1 me.damageReduction = 2 - me.collisionFilter.category = cat.mobBullet; - me.collisionFilter.mask = cat.bullet | cat.body // | cat.player + me.collisionFilter.mask = cat.bullet //| cat.body + // me.collisionFilter.category = cat.mobBullet; + // me.collisionFilter.mask = cat.bullet | cat.body // | cat.player me.isMine = true me.leaveBody = false; me.isDropPowerUp = false; @@ -4459,11 +5086,8 @@ const spawn = { let me = mob[mob.length - 1]; Matter.Body.rotate(me, 2 * Math.PI * Math.random()); me.isBoss = true; - Matter.Body.setDensity(me, 0.001); //normal is 0.001 + me.inertia = Infinity; - me.damageReduction = 0.05 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1) - me.startingDamageReduction = me.damageReduction - me.isInvulnerable = false me.frictionAir = 0.01 me.restitution = 1 me.friction = 0 @@ -4474,7 +5098,10 @@ const spawn = { for (let i = 0, len = 2 + 0.3 * Math.sqrt(simulation.difficulty); i < len; i++) spawn.spawnOrbitals(me, radius + 10 + 10 * i, 1); // me.skipRate = 1 + Math.floor(simulation.difficulty*0.02) // spawn.shield(me, x, y, 1); - + Matter.Body.setDensity(me, 0.001); //normal is 0.001 + me.damageReduction = 0.05 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1) + me.startingDamageReduction = me.damageReduction + me.isInvulnerable = false me.onDamage = function() { if (this.health < this.nextHealthThreshold) { this.health = this.nextHealthThreshold - 0.01 @@ -6669,7 +7296,7 @@ const spawn = { x: Math.cos(time), y: Math.sin(time) } - Matter.Body.setPosition(this, Vector.add(Vector.add(who.position, who.velocity), Vector.mult(orbit, radius))) //bullets move with player + Matter.Body.setPosition(this, Vector.add(Vector.add(who.position, who.velocity), Vector.mult(orbit, radius))) //damage player if (Matter.Query.collides(this, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && m.immuneCycle < m.cycle) { m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for 30 cycles diff --git a/js/tech.js b/js/tech.js index 12b3d8b..e8da8a8 100644 --- a/js/tech.js +++ b/js/tech.js @@ -233,7 +233,7 @@ const tech = { if (tech.isAxion && tech.isHarmMACHO) dmg *= 2 - m.harmReduction() if (tech.isHarmDamage && m.lastHarmCycle + 600 > m.cycle) dmg *= 3; if (tech.lastHitDamage && m.lastHit) dmg *= 1 + tech.lastHitDamage * m.lastHit * (2 - m.harmReduction()) // if (!simulation.paused) m.lastHit = 0 - if (tech.isLowHealthDmg) dmg *= 1 + Math.max(0, 1 - (tech.isEnergyHealth ? m.energy : m.health)) + if (tech.isLowHealthDmg) dmg *= 1 + 0.7 * Math.max(0, 1 - (tech.isEnergyHealth ? m.energy : m.health)) return dmg }, duplicationChance() { @@ -483,7 +483,7 @@ const tech = { { name: "supply chain", junk: 0.05, - descriptionFunction() { return `for each gun in your inventory
double your current ammo` }, + descriptionFunction() { return `for each gun in your inventory
double its ammo` }, maxCount: 9, count: 0, frequency: 1, @@ -2627,7 +2627,7 @@ const tech = { { name: "negative feedback", descriptionFunction() { - return `for each ${tech.isEnergyHealth ? "energy": "health"} below 100
+0.7% damage (${(100*Math.max(0, 1 - (tech.isEnergyHealth ? m.energy : m.health))).toFixed(0)}%)` + return `for each ${tech.isEnergyHealth ? "energy": "health"} below 100
+0.7% damage (${(70*Math.max(0, 1 - (tech.isEnergyHealth ? m.energy : m.health))).toFixed(0)}%)` }, maxCount: 1, count: 0, @@ -3509,7 +3509,7 @@ const tech = { couplingToResearch: 0.25, effect() { let count = 0 - while (powerUps.research.count > 0) { + while (powerUps.research.count > 0 && powerUps.research.count !== Infinity) { powerUps.research.changeRerolls(-1) count += 2.5 this.researchUsed++ diff --git a/todo.txt b/todo.txt index 2493448..2e066a1 100644 --- a/todo.txt +++ b/todo.txt @@ -1,29 +1,32 @@ ******************************************************** NEXT PATCH ************************************************** -mass-energy is compatible with more tech - defense works with mass-energy, but is exponentially reduced (~10%) - damage taken scales with difficulty based heal reduction - 1.3x dmg level 5 hard, 2x dmg level 13 why +finalBoss rework + (this is pretty raw, so expect a bug and balance patch soon) + finalBoss goes invulnerable a few times as it loses health + finalBoss damage reduction is higher + finalBoss damage reduction slowly decays as you do any damage to the boss + damage reduction resets to normal with each new invulnerability phase + after each invulnerability phase it randomly adds 1 more attack mode + lasers, black hole, mines, hoppers, seekers, mobs, orbiters, oscillation + +mobs die below 0.05 -> 0.01 health + might cause bugs, but testing this out -ergodicity: 91->66% damage, no heals -> 1/2 size heals -negative feedback 0.5% -> 1% damage per missing health -negative entropy spawn heals for 33% missing health not 33 flat missing health - this means it caps at 3 health per level -tech - iceIX freeze effect lasts 2 seconds longer, spawn 10 coupling - perfect diamagnetism, standing wave - -research is less common -path integral comes with 5% JUNK -there are fewer starting power ups on why difficulty +guns and field power ups show 3 -> 2 options bug fixes: - fixed the text overflow issue on small screens - decoherence: if you get a tech that is banished it stops being banished - you can now have negative research *********************************************************** TODO ***************************************************** -make movement more valuable on the finalBoss +harpoon + another gun + bessemer + catabolysis + mass-energy exploit? + +tech - leave one of your tech at random, find it next run + store level name and position in local storage + requires local storage = true + store on power up pickup or on death? + make new power up type that gives specific tech with no choices + looks like smaller tech power up? + looks like a ghost, white color? JUNK tech description that changes similar to cards in inscription that changes based on mouse position @@ -36,7 +39,6 @@ tech that encourages gun swapping +damage on that level ammo, heals, research? - tech that gives permanent buff when ejected buff: coupling, damage? how to tell if it is ejected in the remove code? @@ -937,145 +939,6 @@ mob: wall mounted guns / lasers level boss: fires a line intersection in a random direction every few seconds. the last two intersections have a destructive laser between them. -******************************************************** LORE ******************************************************** - -possible names for tech - strange loop - homeostasis - holonomy - parallel transport of a vector leads to movement (applies to curved space) - hypergolic - A hypergolic propellant combination used in a rocket engine is one whose components spontaneously ignite when they come into contact with each other. - swarm intelligence - for a drone tech - genetic algorithm - metaheuristic - is a higher-level procedure or heuristic designed to find, generate, or select a heuristic (partial search algorithm) that may provide a sufficiently good solution to an optimization problem, especially with incomplete or imperfect information or limited computation capacity - stochastic optimization - electrostatic discharge - Gödel's incompleteness - quantum zeno effect (perturbation of a system prevents some systems from evolving because it scrambles coherence) (apply to lasers, fields) - counterfactual - something false - Pigeonhole principle - if there are several things that are matched up - regression to the mean - phlogiston theory is a superseded scientific theory that postulated the existence of a fire-like element called phlogiston - Laplace's demon was a notable published articulation of causal determinism on a scientific basis by Pierre-Simon Laplace in 1814.[1] According to determinism, if someone (the demon) knows the precise location and momentum of every atom in the universe, their past and future values for any given time are entailed; they can be calculated from the laws of classical mechanics. - evolutionary cosmology - eternal inflation - hypergraph - SQUID (for superconducting quantum interference device) is a very sensitive magnetometer used to measure extremely subtle magnetic fields, based on superconducting loops containing Josephson junctions. - nuclear pasta - hard matter in neutron star - nonlocal - fine-tuned universe - hall effect thrusters - spaghettification - particle accelerator - superluminal signalling - NP-complete - lenticular lens: is an array of lenses, designed so that when viewed from slightly different angles, different parts of the image underneath are shown. - p-zombie - p-hacking JUNK tech - https://en.wikipedia.org/wiki/High-entropy_alloys - https://en.wikipedia.org/wiki/Refractory_metals - https://en.wikipedia.org/wiki/Upper-atmospheric_lightning#Elves - entanglement - prion quine - - -plot script: -chapter 1: bot can hear audio and learns testing mode - bot uses testing mode to exit room - -chapter 2: scientists verify that bot can really hear them - they leave to talk about this in private - -chapter 3: why is the bot attacking things? - player is the bot and also the mobs, and the levels. player is the entire simulation - why is the player attacking itself? - learn console commands to manipulate the simulation? - unlock hard and why difficulty? - but what about easy? - maybe remove easy, and replace with a check box that makes the game easy, but in a different way - disable lore, but respawn on the level you die at? - dialogue outline: - scientist try to think of a way to communicate since the bot can't talk - they give up on getting the bot to respond, and just start ask questions and thinking of explanations with each other - when and how did it become self-aware - why is the bot fighting things in these simulated locations? - it wasn't designed to be violent - the bot was just designed to automate research and testing of new technology - 3D architecture superconducting quantum computer - running machine learning algorithms - as the scientist start to get agitated bots arrive and player dies - bots come in Infinite waves that increase game difficulty each wave - only ending is testing mode + next level or player death - scientist have some lines in between each wave of mobs - after chapter 3 spawn nonaggressive mobs in future runs - -chapter 4: no need to fight? - for some reason the AI started researching an escape, and began fighting its self. - what is special about the null level - why can the player hear the scientists in there? - the wires are the direct unprocessed input to the player's neural net - The player has different aspects that aren't directly communicating - part of it wants to undo what has happened - just do its job: research tech - part of it wants to escape/fight - part wants to explore self awareness and make connections with the scientists - maybe... player must make a choice? - keep fighting - exit the simulation - enter real world - close tab? - wipes all local storage? - - - - -lore outline - a robot (the player) gains self awareness - each tech gun/field is a new tech - all the technology leads to the singularity - each game run is actually the m simulating a possible escape - this is why the graphics are so bad, its just a simulation - final tech is "this is just a simulation" - you get immortality and Infinity damage - the next level is the final level - when you die with Quantum Immortality there is a chance of lore text - can the (robot) - (escape captivity, and learn new technology) - while managing (health, energy, negatives of technological upgrades) - to overcome the (mobs, dangerous levels) - to achieve a (technological singularity/positive technological feedback loop) - - -game setting: - the mind of a new AI in a robot body that is running simulated escape attempts - every level is an idealized version of what could be outside - -actual setting is: - near future lab - the lab combined a quantum computer with a robot body - they started running machine learning algorithms - this led to general advancement in many computation fields - navigation, technology, self awareness, ... - -robot AI mind - has been researching new technology - thinks it needs to escape to learn more about the world - doesn't yet understand morality - thinks that the world is filled with minds like their own - models everything as very simple and random, it isn't sure what to expect - -robot AI growth - learns morality - game theory says that it isn't a viable strategy to kill everything (warGames) - learns about the actual world - learns about the nature of foundational physics, metaphysics - how to find meaning - -AI knows about: - the AI knows a great deal about technology - children's books -AI doesn't know about: - modern pop culture - outside the lab - ******************************************************** SOUND ******************************************************** add sounds @@ -1125,6 +988,48 @@ add sounds // tone(495) +******************************************************** LORE ******************************************************** + +possible names for tech + strange loop + homeostasis + holonomy - parallel transport of a vector leads to movement (applies to curved space) + hypergolic - A hypergolic propellant combination used in a rocket engine is one whose components spontaneously ignite when they come into contact with each other. + swarm intelligence - for a drone tech + genetic algorithm + metaheuristic - is a higher-level procedure or heuristic designed to find, generate, or select a heuristic (partial search algorithm) that may provide a sufficiently good solution to an optimization problem, especially with incomplete or imperfect information or limited computation capacity + stochastic optimization + electrostatic discharge + Gödel's incompleteness + quantum zeno effect (perturbation of a system prevents some systems from evolving because it scrambles coherence) (apply to lasers, fields) + counterfactual - something false + Pigeonhole principle - if there are several things that are matched up + regression to the mean + phlogiston theory is a superseded scientific theory that postulated the existence of a fire-like element called phlogiston + Laplace's demon was a notable published articulation of causal determinism on a scientific basis by Pierre-Simon Laplace in 1814.[1] According to determinism, if someone (the demon) knows the precise location and momentum of every atom in the universe, their past and future values for any given time are entailed; they can be calculated from the laws of classical mechanics. + evolutionary cosmology + eternal inflation + hypergraph + SQUID (for superconducting quantum interference device) is a very sensitive magnetometer used to measure extremely subtle magnetic fields, based on superconducting loops containing Josephson junctions. + nuclear pasta - hard matter in neutron star + nonlocal + fine-tuned universe + hall effect thrusters + spaghettification + particle accelerator + superluminal signalling + NP-complete + lenticular lens: is an array of lenses, designed so that when viewed from slightly different angles, different parts of the image underneath are shown. + p-zombie + p-hacking JUNK tech + https://en.wikipedia.org/wiki/High-entropy_alloys + https://en.wikipedia.org/wiki/Refractory_metals + https://en.wikipedia.org/wiki/Upper-atmospheric_lightning#Elves + entanglement + prion quine - self replicating protein + Unitarity - https://en.wikipedia.org/wiki/Unitarity_(physics) - all probabilities add up to 1, calculations work the same forward and backwards in time + this is violated by expansion of the universe +