From 5f68bc687f56791b78710402d950b1f5f0780bb5 Mon Sep 17 00:00:00 2001 From: landgreen Date: Sun, 7 Mar 2021 05:59:10 -0800 Subject: [PATCH] pulsar 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 --- .DS_Store | Bin 6148 -> 6148 bytes js/bullet.js | 128 ++++++++++++++++++++++- js/engine.js | 2 +- js/index.js | 2 + js/level.js | 10 +- js/player.js | 64 +++++++++--- js/powerup.js | 6 +- js/simulation.js | 6 +- js/spawn.js | 119 ++++++++++++++++++++-- js/tech.js | 259 ++++++++++++++++++++++++++--------------------- todo.txt | 22 +++- 11 files changed, 462 insertions(+), 156 deletions(-) diff --git a/.DS_Store b/.DS_Store index 3b660c680e8bbc9dcc5561170b753ce9176f2191..83d170ea35918a043c0b1380316f113e280cef86 100644 GIT binary patch delta 21 ccmZoMXffEJ#msbg>0}*dKSqVk)y$nD08R!5uK)l5 delta 21 ccmZoMXffEJ#muy0!(<(1KSqPi)y$nD08U#5zW@LL 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: