diff --git a/.DS_Store b/.DS_Store index 3b660c6..83d170e 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/js/bullet.js b/js/bullet.js index e17a5d3..7dea5e7 100644 --- a/js/bullet.js +++ b/js/bullet.js @@ -574,6 +574,121 @@ const b = { }); } }, + photon(where, angle = m.angle) { + let best; + const path = [{ + x: m.pos.x + 20 * Math.cos(angle), + y: m.pos.y + 20 * Math.sin(angle) + }, + { + x: m.pos.x + range * Math.cos(angle), + y: m.pos.y + range * Math.sin(angle) + } + ]; + const vertexCollision = function(v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let vertices = domain[i].vertices; + const len = vertices.length - 1; + for (let j = 0; j < len; j++) { + results = 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 && (!domain[i].mob || domain[i].alive)) { + best = { + x: results.x, + y: results.y, + dist2: dist2, + who: domain[i], + v1: vertices[0], + v2: vertices[len] + }; + } + } + } + }; + //check for collisions + best = { + x: null, + y: null, + dist2: Infinity, + who: null, + v1: null, + v2: null + }; + if (tech.isPulseAim) { //find mobs in line of sight + let dist = 2200 + for (let i = 0, len = mob.length; i < len; i++) { + const newDist = Vector.magnitude(Vector.sub(path[0], mob[i].position)) + if (explosionRadius < newDist && + newDist < dist && + Matter.Query.ray(map, path[0], mob[i].position).length === 0 && + Matter.Query.ray(body, path[0], mob[i].position).length === 0) { + dist = newDist + best.who = mob[i] + path[path.length - 1] = mob[i].position + } + } + } + if (!best.who) { + vertexCollision(path[0], path[1], mob); + vertexCollision(path[0], path[1], map); + vertexCollision(path[0], path[1], body); + if (best.dist2 != Infinity) { //if hitting something + path[path.length - 1] = { + x: best.x, + y: best.y + }; + } + } + if (best.who) b.explosion(path[1], explosionRadius, true) + + //draw laser beam + ctx.beginPath(); + ctx.moveTo(path[0].x, path[0].y); + ctx.lineTo(path[1].x, path[1].y); + ctx.strokeStyle = "rgba(255,0,0,0.13)" + ctx.lineWidth = 60 * energy / 0.2 + ctx.stroke(); + ctx.strokeStyle = "rgba(255,0,0,0.2)" + ctx.lineWidth = 18 + ctx.stroke(); + ctx.strokeStyle = "#f00"; + ctx.lineWidth = 4 + ctx.stroke(); + + //draw little dots along the laser path + const sub = Vector.sub(path[1], path[0]) + const mag = Vector.magnitude(sub) + for (let i = 0, len = Math.floor(mag * 0.03 * energy / 0.2); i < len; i++) { + const dist = Math.random() + simulation.drawList.push({ + x: path[0].x + sub.x * dist + 13 * (Math.random() - 0.5), + y: path[0].y + sub.y * dist + 13 * (Math.random() - 0.5), + radius: 1 + 4 * Math.random(), + color: "rgba(255,0,0,0.5)", + time: Math.floor(2 + 33 * Math.random() * Math.random()) + }); + } + }, grenade() { }, @@ -3266,11 +3381,11 @@ const b = { if (m.crouch) { spread = 0.75 m.fireCDcycle = m.cycle + Math.floor(55 * b.fireCD); // cool down - if (tech.isShotgunImmune && m.immuneCycle < m.cycle + Math.floor(58 * b.fireCD)) m.immuneCycle = m.cycle + Math.floor(58 * b.fireCD); //player is immune to collision damage for 30 cycles + if (tech.isShotgunImmune && m.immuneCycle < m.cycle + Math.floor(58 * b.fireCD)) m.immuneCycle = m.cycle + Math.floor(58 * b.fireCD); //player is immune to damage for 30 cycles knock = 0.01 } else { m.fireCDcycle = m.cycle + Math.floor(45 * b.fireCD); // cool down - if (tech.isShotgunImmune && m.immuneCycle < m.cycle + Math.floor(47 * b.fireCD)) m.immuneCycle = m.cycle + Math.floor(47 * b.fireCD); //player is immune to collision damage for 30 cycles + if (tech.isShotgunImmune && m.immuneCycle < m.cycle + Math.floor(47 * b.fireCD)) m.immuneCycle = m.cycle + Math.floor(47 * b.fireCD); //player is immune to damage for 30 cycles spread = 1.3 knock = 0.1 } @@ -4319,6 +4434,12 @@ const b = { } else { this.fire = this.fireLaser } + + // this.fire = this.firePhoton + }, + firePhoton() { + m.fireCDcycle = m.cycle + Math.floor((tech.isPulseAim ? 25 : 50) * b.fireCD); // cool down + b.photon({ x: m.pos.x + 23 * Math.cos(m.angle), y: m.pos.y + 23 * Math.sin(m.angle) }, m.angle) }, fireLaser() { if (m.energy < tech.laserFieldDrain) { @@ -4329,7 +4450,6 @@ const b = { b.laser(); } }, - // laser(where = { // x: m.pos.x + 20 * Math.cos(m.angle), // y: m.pos.y + 20 * Math.sin(m.angle) @@ -4501,7 +4621,7 @@ const b = { m.fireCDcycle = m.cycle + Math.floor(120 * b.fireCD); // cool down } else { m.energy -= DRAIN - if (m.immuneCycle < m.cycle + 30) m.immuneCycle = m.cycle + 30; //player is immune to collision damage for 5 cycles + if (m.immuneCycle < m.cycle + 30) m.immuneCycle = m.cycle + 30; //player is immune to damage for 5 cycles Matter.Body.setPosition(player, history.position); Matter.Body.setVelocity(player, { x: history.velocity.x, y: history.velocity.y }); if (m.health !== history.health) { diff --git a/js/engine.js b/js/engine.js index 55e33f3..a5c02d7 100644 --- a/js/engine.js +++ b/js/engine.js @@ -126,7 +126,7 @@ function collisionChecks(event) { if (tech.isPiezo) m.energy += 20.48; if (tech.isBayesian) powerUps.ejectTech() if (mob[k].onHit) mob[k].onHit(k); - m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to collision damage for 30 cycles + m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to damage for 30 cycles //extra kick between player and mob //this section would be better with forces but they don't work... let angle = Math.atan2(player.position.y - mob[k].position.y, player.position.x - mob[k].position.x); Matter.Body.setVelocity(player, { diff --git a/js/index.js b/js/index.js index 57a3b5e..428ecf7 100644 --- a/js/index.js +++ b/js/index.js @@ -241,6 +241,8 @@ const build = { text += `
  ${tech.tech[i].name} ${isCount}
${tech.tech[i].description}
` } countTech++ + } else if (tech.tech[i].isLost) { + text += `
${tech.tech[i].name}
${tech.tech[i].description}
` } } el = document.getElementById("pause-grid-right") diff --git a/js/level.js b/js/level.js index c1a5df1..babfb06 100644 --- a/js/level.js +++ b/js/level.js @@ -16,7 +16,7 @@ const level = { // simulation.zoomScale = 1000; // simulation.setZoom(); // m.setField("nano-scale manufacturing") - // b.giveGuns("nail gun") + // b.giveGuns("laser") // tech.isExplodeRadio = true // for (let i = 0; i < 1; i++) tech.giveTech("dynamo-bot") // tech.giveTech("supercritical fission") @@ -1087,19 +1087,19 @@ const level = { spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump // spawn.boost(1500, 0, 900); + // simulation.difficulty = 30 // spawn.starter(1900, -500, 200) //big boy - // spawn.starter(1900, -500) + spawn.pulsar(1900, -500) // spawn.historyBoss(1900, -500) // spawn.ghoster(2900, -500) // spawn.launcherBoss(1200, -500) // spawn.laserTargetingBoss(1600, -400) // spawn.striker(1600, -500) - // spawn.shooter(1700, -120) + // spawn.laserTargetingBoss(1700, -120) // spawn.bomberBoss(1400, -500) // spawn.sniper(1800, -120) // spawn.streamBoss(1600, -500) - simulation.difficulty = 30 - spawn.orbitalBoss(1600, -500) + // spawn.orbitalBoss(1600, -500) // spawn.cellBossCulture(1600, -500) // spawn.shieldingBoss(1600, -500) // spawn.beamer(1200, -500) diff --git a/js/player.js b/js/player.js index a8f171e..78b6258 100644 --- a/js/player.js +++ b/js/player.js @@ -321,7 +321,7 @@ const m = { if ( !tech.tech[i].isNonRefundable && tech.tech[i].name !== "many-worlds" && - tech.tech[i].name !== "decoherence" + tech.tech[i].name !== "Ψ(t) collapse" ) { totalTech += tech.tech[i].count tech.tech[i].remove(); @@ -509,7 +509,7 @@ const m = { if (tech.healthDrain) dmg *= 1 + 2.667 * tech.healthDrain //tech.healthDrain = 0.03 at one stack //cause more damage if (tech.squirrelFx !== 1) dmg *= 1 + (tech.squirrelFx - 1) / 5 //cause more damage if (tech.isBlockHarm && m.isHolding) dmg *= 0.15 - if (tech.isSpeedHarm) dmg *= 1 - Math.min(player.speed * 0.0185, 0.55) + if (tech.isSpeedHarm) dmg *= 1 - Math.min(player.speed * 0.019, 0.60) if (tech.isSlowFPS) dmg *= 0.8 // if (tech.isPiezo) dmg *= 0.85 if (tech.isHarmReduce && m.fieldUpgrades[m.fieldMode].name === "negative mass field" && m.isFieldActive) dmg *= 0.5 @@ -581,7 +581,7 @@ const m = { } } m.energy = Math.max(m.energy - steps / 136, 0.01) - if (m.immuneCycle < m.cycle + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to collision damage for 30 cycles + if (m.immuneCycle < m.cycle + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to damage for 30 cycles let isDrawPlayer = true const shortPause = function() { @@ -834,7 +834,42 @@ const m = { ctx.stroke(); // draw eye; used in flip-flop // ctx.beginPath(); - // ctx.arc(15, 0, 3, 0, 2 * Math.PI); + // ctx.arc(15, 0, 3.5, 0, 2 * Math.PI); + // ctx.fillStyle = m.eyeFillColor; + // ctx.fill() + + ctx.restore(); + m.yOff = m.yOff * 0.85 + m.yOffGoal * 0.15; //smoothly move leg height towards height goal + }, + drawDefault() { + ctx.fillStyle = m.fillColor; + m.walk_cycle += m.flipLegs * m.Vx; + + //draw body + ctx.save(); + ctx.globalAlpha = (m.immuneCycle < m.cycle) ? 1 : 0.5 + ctx.translate(m.pos.x, m.pos.y); + + m.calcLeg(Math.PI, -3); + m.drawLeg("#4a4a4a"); + m.calcLeg(0, 0); + m.drawLeg("#333"); + + ctx.rotate(m.angle); + ctx.beginPath(); + ctx.arc(0, 0, 30, 0, 2 * Math.PI); + let grd = ctx.createLinearGradient(-30, 0, 30, 0); + grd.addColorStop(0, m.fillColorDark); + grd.addColorStop(1, m.fillColor); + ctx.fillStyle = grd; + ctx.fill(); + ctx.arc(15, 0, 4, 0, 2 * Math.PI); + ctx.strokeStyle = "#333"; + ctx.lineWidth = 2; + ctx.stroke(); + // draw eye; used in flip-flop + // ctx.beginPath(); + // ctx.arc(15, 0, 3.5, 0, 2 * Math.PI); // ctx.fillStyle = m.eyeFillColor; // ctx.fill() @@ -1571,7 +1606,7 @@ const m = { effect: () => { m.fieldFire = true; m.holdingMassScale = 0.03; //can hold heavier blocks with lower cost to jumping - m.fieldMeterColor = "#000" + m.fieldMeterColor = "#333" m.eyeFillColor = m.fieldMeterColor m.fieldHarmReduction = 0.5; m.fieldDrawRadius = 0; @@ -1834,8 +1869,9 @@ const m = { description: "cloak after not using your gun or field
while cloaked mobs can't see you
increase damage by 133%", effect: () => { m.fieldFire = true; - m.fieldMeterColor = "#000"; + m.fieldMeterColor = "#333"; m.eyeFillColor = m.fieldMeterColor + // m.eyeFillColor = '#333' m.fieldPhase = 0; m.isCloak = false m.fieldDamage = 2.33 // 1 + 111/100 @@ -2344,7 +2380,7 @@ const m = { // break; //because the array order is messed up after splice // } // } - // m.immuneCycle = m.cycle + 5; //player is immune to collision damage for 30 cycles + // m.immuneCycle = m.cycle + 5; //player is immune to damage for 30 cycles // } else { // m.fieldCDcycle = m.cycle + 30; // // m.resetHistory(); @@ -2447,7 +2483,7 @@ const m = { Matter.World.remove(engine.world, body[i]); body.splice(i, 1); m.fieldRange *= 0.8 - if (tech.isWormholeEnergy) m.energy += 0.5 + if (tech.isWormholeEnergy) m.energy += 0.63 if (tech.isWormSpores) { //pandimensionalspermia for (let i = 0, len = Math.ceil(3 * Math.random()); i < len; i++) { b.spore(Vector.add(m.hole.pos2, Vector.rotate({ @@ -2473,7 +2509,7 @@ const m = { body.splice(i, 1); m.fieldRange *= 0.8 // if (tech.isWormholeEnergy && m.energy < m.maxEnergy * 2) m.energy = m.maxEnergy * 2 - if (tech.isWormholeEnergy) m.energy += 0.5 + if (tech.isWormholeEnergy) m.energy += 0.63 if (tech.isWormSpores) { //pandimensionalspermia for (let i = 0, len = Math.ceil(3 * Math.random()); i < len; i++) { b.spore(Vector.add(m.hole.pos1, Vector.rotate({ @@ -2554,7 +2590,7 @@ const m = { x: velocity.x, y: velocity.y - 4 //an extra vertical kick so the player hangs in place longer }); - if (m.immuneCycle < m.cycle + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to collision damage + if (m.immuneCycle < m.cycle + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to damage // move bots to player for (let i = 0; i < bullet.length; i++) { if (bullet[i].botType) { @@ -2579,11 +2615,11 @@ const m = { m.hole.unit = Vector.perp(Vector.normalise(sub)) if (tech.isWormholeDamage) { - who = Matter.Query.ray(mob, m.pos, simulation.mouseInGame, 80) + who = Matter.Query.ray(mob, m.pos, simulation.mouseInGame, 100) for (let i = 0; i < who.length; i++) { if (who[i].body.alive) { - mobs.statusDoT(who[i].body, 0.6, 420) - mobs.statusStun(who[i].body, 240) + mobs.statusDoT(who[i].body, 1, 420) + mobs.statusStun(who[i].body, 360) } } } @@ -2830,7 +2866,7 @@ const m = { if (tech.isPiezo) m.energy += 20.48; if (tech.isBayesian) powerUps.ejectTech() if (mob[k].onHit) mob[k].onHit(k); - if (m.immuneCycle < m.cycle + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to collision damage for 30 cycles + if (m.immuneCycle < m.cycle + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to damage for 30 cycles //extra kick between player and mob //this section would be better with forces but they don't work... let angle = Math.atan2(player.position.y - mob[k].position.y, player.position.x - mob[k].position.x); Matter.Body.setVelocity(player, { diff --git a/js/powerup.js b/js/powerup.js index 4dda1de..4f5ba6d 100644 --- a/js/powerup.js +++ b/js/powerup.js @@ -70,7 +70,7 @@ const powerUps = { document.body.style.overflow = "hidden" simulation.paused = false; simulation.isChoosing = false; //stops p from un pausing on key down - if (m.immuneCycle < m.cycle + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to collision damage for 30 cycles + if (m.immuneCycle < m.cycle + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to damage for 30 cycles build.unPauseGrid() requestAnimationFrame(cycle); if (m.holdingTarget) m.drop(); @@ -379,11 +379,11 @@ const powerUps = { } else { if (tech.isBanish) { for (let i = 0, len = tech.tech.length; i < len; i++) { - if (tech.tech[i].name === "erase") powerUps.ejectTech(i) + if (tech.tech[i].name === "decoherence") powerUps.ejectTech(i) } // simulation.makeTextLog(`No tech left
erased tech have been recovered`) simulation.makeTextLog(`powerUps.tech.length: ${Math.max(0,powerUps.tech.lastTotalChoices - powerUps.tech.banishLog.length)}`) - powerUps.spawn(m.pos.x, m.pos.y, "tech"); + // powerUps.spawn(m.pos.x, m.pos.y, "tech"); powerUps.endDraft("tech"); } else { powerUps.giveRandomAmmo() diff --git a/js/simulation.js b/js/simulation.js index 4f1434c..dd0dc5c 100644 --- a/js/simulation.js +++ b/js/simulation.js @@ -509,7 +509,11 @@ const simulation = { document.getElementById("splash").style.display = "none"; //hides the element that spawned the function document.getElementById("dmg").style.display = "inline"; document.getElementById("health-bg").style.display = "inline"; + ctx.globalCompositeOperation = "source-over" + ctx.shadowBlur = 0; + // ctx.shadowColor = '#000'; if (!m.isShipMode) { + m.draw = m.drawDefault //set the play draw to normal, undoing some junk tech m.spawn(); //spawns the player } else { World.add(engine.world, [player]) @@ -828,7 +832,7 @@ const simulation = { if (!(simulation.cycle % 420)) { //once every 7 seconds - if (tech.cyclicImmunity && m.immuneCycle < m.cycle + tech.cyclicImmunity) m.immuneCycle = m.cycle + tech.cyclicImmunity; //player is immune to collision damage for 60 cycles + if (tech.cyclicImmunity && m.immuneCycle < m.cycle + tech.cyclicImmunity) m.immuneCycle = m.cycle + tech.cyclicImmunity; //player is immune to damage for 60 cycles fallCheck = function(who, save = false) { let i = who.length; diff --git a/js/spawn.js b/js/spawn.js index 82ca803..309fef4 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -11,6 +11,7 @@ const spawn = { "launcher", "launcher", "springer", "springer", "sucker", "sucker", + "pulsar", "pulsar", "pulsar", "pulsar", "pulsar", //briefly high chance to show from a few days "chaser", "sniper", "spinner", @@ -21,7 +22,7 @@ const spawn = { "ghoster", "sneaker", ], - allowedGroupList: ["chaser", "spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter", "launcher", "stabber", "sniper"], + allowedGroupList: ["chaser", "spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter", "launcher", "stabber", "sniper", "pulsar"], setSpawnList() { //this is run at the start of each new level to determine the possible mobs for the level //each level has 2 mobs: one new mob and one from the last level spawn.pickList.splice(0, 1); @@ -100,16 +101,21 @@ const spawn = { Matter.Body.setDensity(me, density); //extra dense //normal is 0.001 //makes effective life much larger // spawn.shield(me, x, y, 1); me.onDeath = function() { - //add lore level as next level if player took lore tech earlier in the game - if (lore.techCount > (lore.techGoal - 1) && !simulation.isCheating) { - level.levels.push("null") + + function unlockExit() { level.exit.x = 5500; level.exit.y = -330; - simulation.makeTextLog(`undefined = ${lore.techCount}/${lore.techGoal}
level.levels.push("null")`); - //remove block map element so exit is clear Matter.World.remove(engine.world, map[map.length - 1]); map.splice(map.length - 1, 1); simulation.draw.setPaths(); //redraw map draw path + } + + //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}
level.levels.push("null")`); + level.levels.push("null") + //remove block map element so exit is clear + unlockExit() } else { //reset game let count = 0 @@ -130,7 +136,12 @@ const spawn = { return } } - if (!simulation.testing) requestAnimationFrame(loop); + if (simulation.testing) { + simulation.makeTextLog(`level.levels.length = Infinite`); + unlockExit() + } else { + requestAnimationFrame(loop); + } } requestAnimationFrame(loop); } @@ -419,7 +430,7 @@ const spawn = { vertexCollision(where, look, body); if (!m.isCloak) vertexCollision(where, look, [player]); if (best.who && best.who === player && m.immuneCycle < m.cycle) { - if (m.immuneCycle < m.cycle + 60 + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + 60 + tech.collisionImmuneCycles; //player is immune to collision damage extra time + if (m.immuneCycle < m.cycle + 60 + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + 60 + tech.collisionImmuneCycles; //player is immune to damage extra time m.damage(dmg); simulation.drawList.push({ //add dmg to draw queue x: best.x, @@ -1470,6 +1481,93 @@ const spawn = { } }; }, + pulsar(x, y, radius = 30) { + mobs.spawn(x, y, 3, radius, "#f08"); + let me = mob[mob.length - 1]; + me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + Matter.Body.rotate(me, Math.random() * Math.PI * 2); + me.radius *= 2 + me.vertices[1].x = me.position.x + Math.cos(me.angle) * me.radius; //make one end of the triangle longer + me.vertices[1].y = me.position.y + Math.sin(me.angle) * me.radius; + me.fireCycle = 0 + me.fireTarget = { x: 0, y: 0 } + me.pulseRadius = Math.min(500, 300 + simulation.difficulty) + me.frictionAir = 0.01; + me.fireDelay = Math.min(90, 210 - simulation.difficulty) + me.isFiring = false + me.onHit = function() {}; + me.canSeeTarget = function() { + const diff = Vector.normalise(Vector.sub(this.fireTarget, this.position)); //make a vector for the mob's direction of length 1 + const dot = Vector.dot({ + x: Math.cos(this.angle), + y: Math.sin(this.angle) + }, diff); //the dot product of diff and dir will return how much over lap between the vectors + if (dot < 0.97 || Matter.Query.ray(map, this.fireTarget, this.position).length !== 0) { //if not looking at target + this.isFiring = false + return false + } else { + return true + } + } + me.do = function() { + this.seePlayerByLookingAt(); + this.checkStatus(); + if (!m.isBodiesAsleep && this.seePlayer.recall) { + + if (this.isFiring) { + if (this.fireCycle > this.fireDelay) { //fire + if (!this.canSeeTarget()) return + this.isFiring = false + //damage player if in range + if (Vector.magnitude(Vector.sub(player.position, this.fireTarget)) < this.pulseRadius && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to damage + m.damage(0.03 * simulation.dmgScale); + } + simulation.drawList.push({ //add dmg to draw queue + x: this.fireTarget.x, + y: this.fireTarget.y, + radius: this.pulseRadius, + color: "rgba(255,0,100,0.8)", + time: simulation.drawTime + }); + } else { //delay before firing + this.fireCycle++ + if (!(simulation.cycle % 3)) { + if (!this.canSeeTarget()) return //if can't see stop firing + + //draw explosion outline + ctx.beginPath(); + ctx.arc(this.fireTarget.x, this.fireTarget.y, this.pulseRadius, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(255,0,100,0.05)"; + ctx.fill(); + ctx.lineWidth = 2; + ctx.strokeStyle = "rgba(255,0,100,0.5)"; + ctx.stroke(); + } + } + } else { //aim at player + this.fireDir = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); //set direction to turn to fire + //rotate towards fireAngle + const angle = this.angle + Math.PI / 2; + const c = Math.cos(angle) * this.fireDir.x + Math.sin(angle) * this.fireDir.y; + const threshold = 0.03; + if (c > threshold) { + this.torque += 0.000001 * this.inertia; + } else if (c < -threshold) { + this.torque -= 0.000001 * this.inertia; + } else { //fire + this.fireTarget = { x: player.position.x, y: player.position.y } + if (!this.canSeeTarget()) return + Matter.Body.setAngularVelocity(this, 0) + this.fireLockCount = 0 + this.isFiring = true + this.fireCycle = 0 + + } + } + } + }; + }, laser(x, y, radius = 30) { mobs.spawn(x, y, 3, radius, "#f00"); let me = mob[mob.length - 1]; @@ -1603,7 +1701,7 @@ const spawn = { vertexCollision(where, look, body); if (!m.isCloak) vertexCollision(where, look, [player]); if (best.who && best.who === player && m.immuneCycle < m.cycle) { - m.immuneCycle = m.cycle + tech.collisionImmuneCycles + 60; //player is immune to collision damage for an extra second + m.immuneCycle = m.cycle + tech.collisionImmuneCycles + 60; //player is immune to damage for an extra second const dmg = 0.14 * simulation.dmgScale; m.damage(dmg); simulation.drawList.push({ //add dmg to draw queue @@ -2758,7 +2856,8 @@ const spawn = { } Matter.Body.setPosition(this, Vector.add(who.position, Vector.mult(orbit, radius))) //bullets move with player //damage player - if (Matter.Query.collides(this, [player]).length > 0 && !(m.isCloak && tech.isIntangible)) { + if (Matter.Query.collides(this, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to damage for 30 cycles m.damage(0.035 * simulation.dmgScale); this.death(); } diff --git a/js/tech.js b/js/tech.js index 92bce6e..e03d315 100644 --- a/js/tech.js +++ b/js/tech.js @@ -47,21 +47,16 @@ if (options.length) { for (let i = 0; i < num; i++) tech.tech[options[Math.floor(Math.random() * options.length)]].frequency++ } - // for (let i = 0; i < num; i++) { - // // find an index that doesn't have dups first - // let index = null - // for (let i = 0; i < tech.junk.length; i++) { - // if (tech.junk[i].numberInPool === 0) { - // index = i - // break - // } - // } - // if (index === null) index = Math.floor(Math.random() * tech.junk.length) //or just pick a random junk tech to add - - // tech.junk[index].numberInPool++ - // tech.tech.push(Object.assign({}, tech.junk[index])) // push a "clone" of the tech.junk into the pool - // if (tech.junk[index].numberInPool > 1) tech.tech[tech.tech.length - 1].name += ` - ${(tech.junk[index].numberInPool + 9).toString(36)}` //give it a unique name so it can be found - // } + }, + removeJunkTechFromPool(num = 1) { + for (let j = 0; j < num; j++) { + for (let i = 0; i < tech.tech.length; i++) { + if (tech.tech[i].isJunk && tech.tech[i].frequency > 0 && tech.tech[i].count < tech.tech[i].maxCount) { + tech.tech[i].frequency-- + break + } + } + } }, // removeJunkTechFromPool() { // for (let i = tech.tech.length - 1; i > 0; i--) { @@ -141,7 +136,7 @@ if (tech.isRerollDamage) dmg *= 1 + 0.039 * powerUps.research.count if (tech.isOneGun && b.inventory.length < 2) dmg *= 1.25 if (tech.isNoFireDamage && m.cycle > m.fireCDcycle + 120) dmg *= 2 - if (tech.isSpeedDamage) dmg *= 1 + Math.min(0.4, player.speed * 0.013) + if (tech.isSpeedDamage) dmg *= 1 + Math.min(0.43, player.speed * 0.015) if (tech.isBotDamage) dmg *= 1 + 0.06 * b.totalBots() return dmg * tech.slowFire * tech.aimDamage }, @@ -264,6 +259,28 @@ tech.isGunCycle = false; } }, + { + name: "gun technology", + description: "double the frequency of finding gun tech
spawn a gun", + maxCount: 1, + count: 0, + frequency: 1, + isNonRefundable: true, + // isExperimentHide: true, + // isBadRandomOption: true, + allowed() { + return !tech.isSuperDeterminism + }, + requires: "not superdeterminism", + effect() { + powerUps.spawn(m.pos.x, m.pos.y, "gun"); + // this.count-- + for (let i = 0, len = tech.tech.length; i < len; i++) { + if (tech.tech[i].isGunTech) tech.tech[i].frequency *= 2 + } + }, + remove() {} + }, { name: "specialist", description: "for every gun in your inventory spawn a
heal, research, field, ammo, or tech", @@ -483,7 +500,7 @@ }, { name: "Newton's 1st law", - description: "moving at high speeds reduces harm
by up to 50%", + description: "moving at high speeds reduces harm
by up to 60%", maxCount: 1, count: 0, frequency: 1, @@ -500,7 +517,7 @@ }, { name: "Newton's 2nd law", - description: "moving at high speeds increases damage
by up to 33%", + description: "moving at high speeds increases damage
by up to 43%", maxCount: 1, count: 0, frequency: 1, @@ -1103,19 +1120,27 @@ count: 0, frequency: 1, isBotTech: true, - isNonRefundable: true, + // isNonRefundable: true, allowed() { return (b.totalBots() > 1 && powerUps.research.count > 0) || build.isExperimentSelection }, requires: "at least 2 bots, 1 research", effect: () => { - powerUps.research.changeRerolls(-1) - b.randomBot() + if (powerUps.research.count > 0) { + powerUps.research.changeRerolls(-1) + b.randomBot() + } for (let i = 0, len = tech.tech.length; i < len; i++) { if (tech.tech[i].isBotTech) tech.tech[i].frequency *= 4 } }, - remove() {} + remove() { + if (this.count > 0) { + for (let i = 0, len = tech.tech.length; i < len; i++) { + if (tech.tech[i].isBotTech) tech.tech[i].frequency /= 4 + } + } + } }, { name: "perimeter defense", description: "reduce harm by 6%
for each of your permanent bots", @@ -1248,7 +1273,7 @@ requires: "", effect() { tech.collisionImmuneCycles += 45; - if (m.immuneCycle < m.cycle + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to collision damage for 30 cycles + if (m.immuneCycle < m.cycle + tech.collisionImmuneCycles) m.immuneCycle = m.cycle + tech.collisionImmuneCycles; //player is immune to damage for 30 cycles }, remove() { tech.collisionImmuneCycles = 30; @@ -1328,7 +1353,7 @@ ctx.stroke(); //draw eye ctx.beginPath(); - ctx.arc(15, 0, 3, 0, 2 * Math.PI); + ctx.arc(15, 0, 3.5, 0, 2 * Math.PI); ctx.fillStyle = m.eyeFillColor; ctx.fill() ctx.restore(); @@ -2002,6 +2027,27 @@ tech.largerHeals = 1; } }, + { + name: "healing technology", + description: "double the frequency of finding healing tech
spawn 12 heals", + maxCount: 1, + count: 0, + frequency: 1, + isNonRefundable: true, + // isExperimentHide: true, + // isBadRandomOption: true, + allowed() { + return true + }, + requires: "", + effect() { + for (let i = 0; i < 12; i++) powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "heal"); + for (let i = 0, len = tech.tech.length; i < len; i++) { + if (tech.tech[i].isHealTech) tech.tech[i].frequency *= 2 + } + }, + remove() {} + }, // { // name: "perpetual heals", // description: "find 3 heals at the start of each level", @@ -2225,6 +2271,7 @@ if (tech.isSuperDeterminism) count -= 2 //remove the bonus tech tech.setupAllTech(); // remove all tech + lore.techCount = 0; // tech.addLoreTechToPool(); for (let i = 0; i < count; i++) { // spawn new tech power ups powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "tech"); @@ -2268,7 +2315,7 @@ } }, { name: "stimulated emission", - description: "20% chance to duplicate spawned power ups
after a collision, eject 1 tech", + description: "20% chance to duplicate spawned power ups
but, after a collision eject 1 tech", maxCount: 1, count: 0, frequency: 1, @@ -2291,20 +2338,21 @@ maxCount: 9, count: 0, frequency: 1, - isNonRefundable: true, + // isNonRefundable: true, allowed() { return tech.duplicationChance() < 1 }, requires: "below 100% duplication chance", effect() { tech.duplicateChance += 0.075 + tech.maxDuplicationEvent() simulation.draw.powerUp = simulation.draw.powerUpBonus //change power up draw tech.addJunkTechToPool(12) - tech.maxDuplicationEvent() }, remove() { tech.duplicateChance = 0 if (tech.duplicationChance() === 0) simulation.draw.powerUp = simulation.draw.powerUpNormal + if (this.count > 1) tech.removeJunkTechFromPool(12) } }, { name: "futures exchange", @@ -2491,7 +2539,7 @@ name: "dark patterns", description: "reduce combat difficulty by 1 level
add 18 junk tech to the potential pool", maxCount: 1, - isNonRefundable: true, + // isNonRefundable: true, // isExperimentHide: true, count: 0, frequency: 1, @@ -2505,7 +2553,12 @@ tech.addJunkTechToPool(18) // for (let i = 0; i < tech.junk.length; i++) tech.tech.push(tech.junk[i]) }, - remove() {} + remove() { + if (this.count > 0) { + tech.removeJunkTechFromPool(18) + level.difficultyIncrease(simulation.difficultyMode) + } + } }, { name: "unified field theory", description: `in the pause menu, change your field
by clicking on your field's box`, @@ -2522,8 +2575,36 @@ remove() { tech.isGunSwitchField = false; } - }, { - name: "statistical ensemble", + }, + { + name: "field technology", + description: "double the frequency of finding field tech
spawn a field", + maxCount: 1, + count: 0, + frequency: 1, + // isNonRefundable: true, + // isExperimentHide: true, + // isBadRandomOption: true, + allowed() { + return !tech.isSuperDeterminism + }, + requires: "not superdeterminism", + effect() { + powerUps.spawn(m.pos.x, m.pos.y, "field"); + for (let i = 0, len = tech.tech.length; i < len; i++) { + if (tech.tech[i].isFieldTech) tech.tech[i].frequency *= 2 + } + }, + remove() { + if (this.count > 1) { + for (let i = 0, len = tech.tech.length; i < len; i++) { + if (tech.tech[i].isFieldTech) tech.tech[i].frequency /= 2 + } + } + } + }, + { + name: "reinforcement learning", description: "increase the frequency of finding copies of
recursive tech you already have by 10000%", maxCount: 1, count: 0, @@ -2535,10 +2616,14 @@ requires: "at least 10 tech", effect: () => { for (let i = 0, len = tech.tech.length; i < len; i++) { - if (tech.tech[i].count > 0) tech.tech[i].frequency += 100 + if (tech.tech[i].count > 0) tech.tech[i].frequency *= 100 } }, - remove() {} + remove() { + for (let i = 0, len = tech.tech.length; i < len; i++) { + if (tech.tech[i].count > 0) tech.tech[i].frequency /= 100 + } + } }, { name: "cardinality", description: "tech, fields, and guns have 5 choices", @@ -2596,88 +2681,28 @@ remove() { tech.isSuperDeterminism = false; } - }, { - name: "gun technology", - description: "double the frequency of finding gun tech
spawn a gun", - maxCount: 1, - count: 0, - frequency: 1, - isNonRefundable: true, - // isExperimentHide: true, - // isBadRandomOption: true, - allowed() { - return !tech.isSuperDeterminism - }, - requires: "not superdeterminism", - effect() { - powerUps.spawn(m.pos.x, m.pos.y, "gun"); - // this.count-- - for (let i = 0, len = tech.tech.length; i < len; i++) { - if (tech.tech[i].isGunTech) tech.tech[i].frequency *= 2 - } - }, - remove() {} - }, { - name: "ammo technology", - description: "double the frequency of finding gun tech
spawn 6 ammo", - maxCount: 1, - count: 0, - frequency: 1, - isNonRefundable: true, - // isExperimentHide: true, - // isBadRandomOption: true, - allowed() { - return !tech.isEnergyNoAmmo - }, - requires: "not exciton lattice", - effect() { - for (let i = 0; i < 6; i++) powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "ammo"); - for (let i = 0, len = tech.tech.length; i < len; i++) { - if (tech.tech[i].isGunTech) tech.tech[i].frequency *= 2 - } - }, - remove() {} - }, { - name: "field technology", - description: "double the frequency of finding field tech
spawn a field", - maxCount: 1, - count: 0, - frequency: 1, - isNonRefundable: true, - // isExperimentHide: true, - // isBadRandomOption: true, - allowed() { - return !tech.isSuperDeterminism - }, - requires: "not superdeterminism", - effect() { - powerUps.spawn(m.pos.x, m.pos.y, "field"); - for (let i = 0, len = tech.tech.length; i < len; i++) { - if (tech.tech[i].isFieldTech) tech.tech[i].frequency *= 2 - } - }, - remove() {} - }, { - name: "healing technology", - description: "double the frequency of finding healing tech
spawn 12 heals", - maxCount: 1, - count: 0, - frequency: 1, - isNonRefundable: true, - // isExperimentHide: true, - // isBadRandomOption: true, - allowed() { - return true - }, - requires: "", - effect() { - for (let i = 0; i < 12; i++) powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "heal"); - for (let i = 0, len = tech.tech.length; i < len; i++) { - if (tech.tech[i].isHealTech) tech.tech[i].frequency *= 200 - } - }, - remove() {} }, + // { + // name: "ammo technology", + // description: "double the frequency of finding gun tech
spawn 6 ammo", + // maxCount: 1, + // count: 0, + // frequency: 1, + // isNonRefundable: true, + // // isExperimentHide: true, + // // isBadRandomOption: true, + // allowed() { + // return !tech.isEnergyNoAmmo + // }, + // requires: "not exciton lattice", + // effect() { + // for (let i = 0; i < 6; i++) powerUps.spawn(m.pos.x + 60 * (Math.random() - 0.5), m.pos.y + 60 * (Math.random() - 0.5), "ammo"); + // for (let i = 0, len = tech.tech.length; i < len; i++) { + // if (tech.tech[i].isGunTech) tech.tech[i].frequency *= 2 + // } + // }, + // remove() {} + // }, //************************************************** //************************************************** gun //************************************************** tech @@ -2834,7 +2859,7 @@ count: 0, frequency: 1, allowed() { - return tech.haveGunCheck("nail gun") && !tech.nailFireRate && !tech.isIceCrystals && !tech.isRivets && !tech.isNailRadiation + return tech.haveGunCheck("nail gun") && !tech.nailFireRate && !tech.isIceCrystals && !tech.isRivets }, requires: "nail gun, not ice crystal, rivets, or pneumatic actuator", effect() { @@ -4513,7 +4538,7 @@ } }, { name: "Penrose process", - description: "after a block falls into a wormhole
you gain 50 energy", + description: "after a block falls into a wormhole
you gain 63 energy", isFieldTech: true, maxCount: 1, count: 0, @@ -4959,7 +4984,7 @@ }, requires: "", effect() { - for (let i = 0, len = Math.floor(m.energy * 40); i < len; i++) { + for (let i = 0, len = 40; i < len; i++) { setTimeout(() => { m.energy -= 1 / len const index = body.length diff --git a/todo.txt b/todo.txt index 81bf5bd..4983842 100644 --- a/todo.txt +++ b/todo.txt @@ -1,8 +1,15 @@ ******************************************************** NEXT PATCH ******************************************************** +new mob: pulsar - aims at player and does damage in an circle + (set to 3x chance to show up until the next patch) + +several tech that were nonrefundable now can be removed and refunded +added several bug fixes ******************************************************** BUGS ******************************************************** +micro-extruder is laggy? + mouse event e.which is deprecated fix door.isOpen actually meaning isClosed? @@ -45,6 +52,16 @@ use the floor of portal sensor on the player? to unstuck player ******************************************************** TODO ******************************************************** +remove pulsar from high chance to show on next patch + +mob bullets that blow up + draw outline of exploded region, 2 seconds later damage player in region and remove bullet + + +mob sniper: draw aim graphics before fire + +tech laser: photon - laser, but it can only move 100 pixels a cycle + mob - grows after taking damage mob - attack outwardly after taking damage @@ -328,7 +345,10 @@ possible names for tech 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. uncertainty principle - + 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 plot script: