From c162f1074ef360b8af1c78ac6b8512db0d226469 Mon Sep 17 00:00:00 2001 From: landgreen Date: Wed, 5 Oct 2022 09:33:08 -0700 Subject: [PATCH] finalBoss rework 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 guns and field power ups show 3 -> 2 options bug fixes: --- js/bullet.js | 8 +- js/level.js | 41 +- js/mob.js | 25 +- js/player.js | 36 +- js/powerup.js | 34 +- js/simulation.js | 1 + js/spawn.js | 1281 ++++++++++++++++++++++++++++++++++------------ js/tech.js | 8 +- todo.txt | 221 +++----- 9 files changed, 1103 insertions(+), 552 deletions(-) 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 +