From 38ef45a6516c7ff6b4b28d2a98c3fee4efd2143a Mon Sep 17 00:00:00 2001 From: landgreen Date: Tue, 19 Mar 2024 19:36:59 -0700 Subject: [PATCH] % converted to x text rework most numbers converted from "+50%" to "1.5x" renamed defense -> damage taken adjusted about 20 tech to round down or up their values to less decimals for a few I added research cost or JUNK chance to balance rounding new community map soft by Richard0820 tech: peer review - gain +damage each time you research self-assembly scales with health not health percent matter.js engine reverted back to 0.18 (to fix an issue with time dilation) --- img/peer review.webp | Bin 0 -> 74356 bytes index.html | 2 +- js/bullet.js | 7 +- js/index.js | 17 +- js/level.js | 571 ++++++++++++++++++++++++++++++++- js/mob.js | 2 +- js/player.js | 67 ++-- js/powerup.js | 34 +- js/spawn.js | 4 +- js/tech.js | 746 +++++++++++++++++-------------------------- todo.txt | 13 +- 11 files changed, 911 insertions(+), 552 deletions(-) create mode 100644 img/peer review.webp diff --git a/img/peer review.webp b/img/peer review.webp new file mode 100644 index 0000000000000000000000000000000000000000..0a91809a9c486cd4e9036aa39a51d47e90740872 GIT binary patch literal 74356 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 aOn`cvepayf`iyO=%%Kg9DIj#X0001!dXRts literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 0aa9e8c..f4b984a 100644 --- a/index.html +++ b/index.html @@ -62,7 +62,7 @@
- +
- +
-
damage: ${((tech.damageFromTech())).toPrecision(4)}     difficulty: ${((m.dmgScale)).toPrecision(4)} -
defense: ${(1 - m.defense()).toPrecision(5)}     difficulty: ${(1 / simulation.dmgScale).toPrecision(4)} +
damage: ${((tech.damageFromTech())).toPrecision(4)} difficulty: ${((m.dmgScale)).toPrecision(4)} +
damage taken: ${(m.defense()).toPrecision(4)} difficulty: ${(simulation.dmgScale).toPrecision(4)}
fire rate: ${((1 - b.fireCDscale) * 100).toFixed(b.fireCDscale < 0.1 ? 2 : 0)}% ${tech.duplicationChance() ? `
duplication: ${(tech.duplicationChance() * 100).toFixed(0)}%` : ""} ${m.coupling ? `
` + m.couplingDescription(m.coupling) + ` from ${(m.coupling).toFixed(0)} ${powerUps.orb.coupling(1)}` : ""} @@ -552,7 +552,7 @@ ${simulation.isCheating ? "

lore disabled" : ""} - + @@ -876,7 +876,9 @@ ${simulation.isCheating ? "

lore disabled" : ""} - + + + @@ -1602,10 +1604,7 @@ window.addEventListener("keydown", function (event) { case "r": m.resetHistory(); Matter.Body.setPosition(player, simulation.mouseInGame); - Matter.Body.setVelocity(player, { - x: 0, - y: 0 - }); + Matter.Body.setVelocity(player, { x: 0, y: 0 }); // move bots to player for (let i = 0; i < bullet.length; i++) { if (bullet[i].botType) { diff --git a/js/level.js b/js/level.js index 884f39a..150df6d 100644 --- a/js/level.js +++ b/js/level.js @@ -11,7 +11,7 @@ const level = { // playableLevels: ["pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion"], //see level.populateLevels: (initial, ... , reservoir or factory, reactor, ... , subway, final) added later playableLevels: ["labs", "rooftops", "skyscrapers", "warehouse", "highrise", "office", "aerie", "satellite", "sewers", "testChamber", "pavilion", "lock"], - communityLevels: ["gauntlet", "stronghold", "basement", "crossfire", "vats", "run", "ngon", "house", "perplex", "coliseum", "tunnel", "islands", "temple", "dripp", "biohazard", "stereoMadness", "yingYang", "staircase", "fortress", "commandeer", "clock", "buttonbutton", "downpour", "superNgonBros", "underpass", "cantilever", "tlinat", "ruins", "ace", "crimsonTowers", "LaunchSite", "shipwreck", "unchartedCave", "dojo", 'arena'], + communityLevels: ["gauntlet", "stronghold", "basement", "crossfire", "vats", "run", "ngon", "house", "perplex", "coliseum", "tunnel", "islands", "temple", "dripp", "biohazard", "stereoMadness", "yingYang", "staircase", "fortress", "commandeer", "clock", "buttonbutton", "downpour", "superNgonBros", "underpass", "cantilever", "tlinat", "ruins", "ace", "crimsonTowers", "LaunchSite", "shipwreck", "unchartedCave", "dojo", "arena", "soft"], trainingLevels: ["walk", "crouch", "jump", "hold", "throw", "throwAt", "deflect", "heal", "fire", "nailGun", "shotGun", "superBall", "matterWave", "missile", "stack", "mine", "grenades", "harpoon"], levels: [], start() { @@ -51,7 +51,7 @@ const level = { // for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "research"); // for (let i = 0; i < 100; i++) powerUps.directSpawn(1750, -500, "coupling"); // spawn.mapRect(575, -700, 25, 425); //block mob line of site on testing - // level.testChamber(); + // level.testing(); // for (let i = 0; i < 1; ++i) spawn.laserLayer(1400, -500) // Matter.Body.setPosition(player, { x: -200, y: -3330 }); @@ -82,9 +82,9 @@ const level = { // localSettings.isTrainingNotAttempted = true // simulation.isCheating = false //true; // for (let i = 0; i < 5; i++) tech.giveTech("undefined") - // lore.techCount = 2 + // lore.techCount = 1 // level.levelsCleared = 10 - // localSettings.loreCount = 5 //this sets what conversation is heard + // localSettings.loreCount = 2 //this sets what conversation is heard // if (localSettings.isAllowed) localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage // level.onLevel = -1 //this sets level.levels[level.onLevel] = undefined which is required to run the conversation // level.null() @@ -98,7 +98,6 @@ const level = { // powerUps.spawn(m.pos.x, m.pos.y, "entanglement", false); } else { spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns - // spawn.pickList = ["focuser", "focuser"] level[level.levels[level.onLevel]](); //picks the current map from the the levels array if (!simulation.isCheating && !build.isExperimentRun && !simulation.isTraining) { @@ -160,7 +159,7 @@ const level = { } if (tech.isGunChoice && Number.isInteger(tech.buffedGun) && b.inventory.length) { var gun = b.guns[b.inventory[tech.buffedGun]].name - simulation.makeTextLog(`pigeonhole principle: +${(31 * Math.max(0, b.inventory.length)).toFixed(0)}% damage for ${gun}`, 600); + simulation.makeTextLog(`pigeonhole principle: ${(1.3 * Math.max(0, b.inventory.length)).toFixed(2)}x damage for ${gun}`, 600); } if (tech.isSwitchReality && level.levelsCleared !== 0) { simulation.makeTextLog(`simulation.amplitude = ${Math.random()}`); @@ -170,9 +169,9 @@ const level = { } if (tech.isHealLowHealth) { if (tech.isEnergyHealth) { - var len = 4 * (1 - m.energy / m.maxEnergy) //as a percent + var len = 4 * Math.max(0, m.maxEnergy - m.energy) } else { - var len = 4 * (1 - m.health / m.maxHealth) //as a percent + var len = 4 * Math.max(0, m.maxHealth - m.health) } for (let i = 0; i < len; i++) powerUps.spawn(player.position.x + 90 * (Math.random() - 0.5), player.position.y + 90 * (Math.random() - 0.5), "heal", false); } @@ -31811,6 +31810,562 @@ const level = { } } }, + soft() { + simulation.makeTextLog(``); + simulation.makeTextLog(`soft by Richard0820`); + simulation.makeTextLog("The lasers deal less damage the higher level you are") + const portals = []; + portals.push(level.portal({ + x: -1525, + y: -250 + }, Math.PI / 2, { + x: 1100, + y: -1025 + }, Math.PI / 2)) + const soft = { + createCloth(x, y, radius, width, height, attachToPlayer = false, stayStill = false, options, touchPlayer = true, constrictionStrength = 0.001) { + const bodies = []; + const constraints = []; + const otherCons = []; + const bodyWidth = radius; + const bodyHeight = radius; + const numRows = Math.ceil(height / bodyHeight); + const numCols = Math.ceil(width / bodyWidth); + + for (let i = 0; i < numRows; i++) { + for (let j = 0; j < numCols; j++) { + const posX = x + j * bodyWidth + bodyWidth / 2; + const posY = y + i * bodyHeight + bodyHeight / 2; + + const rect = Matter.Bodies.circle(posX, posY, (bodyWidth + bodyHeight) / 4, options); + rect.collisionFilter.category = cat.body; + rect.collisionFilter.mask = (touchPlayer ? cat.player | cat.body | cat.bullet | cat.mob | cat.mobBullet : cat.body | cat.bullet | cat.mob | cat.mobBullet); + rect.classType = "body"; + + Composite.add(engine.world, rect); + + bodies.push(rect); + } + } + + for (let i = 0; i < numRows; i++) { + for (let j = 0; j < numCols; j++) { + const bodyIndexA = i * numCols + j; + if (j < numCols - 1) { + const bodyIndexB = i * numCols + (j + 1); + const constraint = Constraint.create({ + bodyA: bodies[bodyIndexA], + bodyB: bodies[bodyIndexB], + stiffness: 0.06, + damping: 0.001 + }); + Composite.add(engine.world, constraint); + constraints.push(constraint); + } + if (i < numRows - 1) { + const bodyIndexB = (i + 1) * numCols + j; + const constraint = Constraint.create({ + bodyA: bodies[bodyIndexA], + bodyB: bodies[bodyIndexB], + stiffness: 0.06, + damping: 0.001 + }); + Composite.add(engine.world, constraint); + constraints.push(constraint); + } + } + } + + for (let i = 0; i < numRows - 1; i++) { + for (let j = 0; j < numCols - 1; j++) { + const bodyA = bodies[i * numCols + j]; + const bodyB = bodies[(i + 1) * numCols + j + 1]; + const constraint = Constraint.create({ + bodyA: bodyA, + bodyB: bodyB, + stiffness: 0.02 + }); + constraints.push(constraint); + } + } + + for (let i = 0; i < numRows - 1; i++) { + for (let j = 1; j < numCols; j++) { + const bodyA = bodies[i * numCols + j]; + const bodyB = bodies[(i + 1) * numCols + j - 1]; + const constraint = Constraint.create({ + bodyA: bodyA, + bodyB: bodyB, + stiffness: 0.02 + }); + constraints.push(constraint); + } + } + if (stayStill) { + for (let i = 0; i < bodies.length; i++) { + const by = bodies[i]; + const spawnX = by.position.x + bodyWidth / 2; + const spawnY = by.position.y + bodyHeight / 2; + const isLastColumn = (i + 1) % numCols === 0; + const isFirstColumn = i % numCols === 0; + const stiffness = constrictionStrength * (isLastColumn || isFirstColumn ? 100 : 1); // Apply extra stiffness to first and last columns + + const cost = Constraint.create({ + bodyA: by, + pointB: { x: spawnX, y: spawnY }, + stiffness: stiffness, + length: 0 + }); + + Composite.add(engine.world, cost); + otherCons.push(cost); + } + } + if (attachToPlayer) { + for (let i = 0; i < bodies.length; i++) { + const cost = Constraint.create({ + bodyA: bodies[i], + pointB: player.position, + stiffness: 0.0005, + length: 0 + }); + Composite.add(engine.world, cost); + } + } + + return { bodies, constraints, otherCons }; + }, + clothOptions: { + frictionAir: 0.005, + }, + isOuterBoundary(body, bodies) { //unused + const neighbors = [ + { x: body.position.x + 1, y: body.position.y }, + { x: body.position.x - 1, y: body.position.y }, + { x: body.position.x, y: body.position.y + 1 }, + { x: body.position.x, y: body.position.y - 1 } + ]; + + for (let i = 0; i < neighbors.length; i++) { + const neighbor = neighbors[i]; + const isNeighbor = bodies.some(b => b.position.x === neighbor.x && b.position.y === neighbor.y); + if (!isNeighbor) { + return true; + } + } + return false; + }, + draw(cloth) { + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.strokeStyle = "rgba(0,0,0,0.3)"; + ctx.fillStyle = "black"; + for (let i = 0, len = cloth.constraints.length; i < len; ++i) { + const constraint = cloth.constraints[i]; + ctx.moveTo(constraint.bodyA.position.x, constraint.bodyA.position.y); + ctx.lineTo(constraint.bodyB.position.x, constraint.bodyB.position.y); + } + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + }, + addGravity(bodies, magnitude) { + for (var i = 0; i < bodies.length; i++) { + bodies[i].force.y += bodies[i].mass * magnitude; + } + }, + gravity(cloth) { + this.addGravity(cloth.bodies, simulation.g); + }, + breaker(cloth, percentage = 0.5) { + const totalConstraints = cloth.constraints.length; + const constraintsToRemove = Math.ceil(totalConstraints * percentage); + + for (let i = 0; i < constraintsToRemove; i++) { + const randomIndex = Math.floor(Math.random() * cloth.constraints.length); + + let removedConstraint = cloth.constraints.splice(randomIndex, 1)[0]; + Composite.remove(engine.world, removedConstraint); + } + }, + destroyer(cloth, percentage = 0.99999) { + const otherCons = cloth.otherCons.length; + const otherCons2Remove = Math.ceil(otherCons * percentage); + + for (let i = 0; i < otherCons2Remove; i++) { + const randomIndex = Math.floor(Math.random() * cloth.otherCons.length); + + let removedConstraint = cloth.otherCons.splice(randomIndex, 1)[0]; + Composite.remove(engine.world, removedConstraint); + } + }, + annihilate(cloth) { + const totalBodies = cloth.bodies.length; + for (let i = 0; i < totalBodies; i++) { + const removeBody = cloth.bodies[i]; + Composite.remove(engine.world, removeBody); + } + cloth.bodies.length = 0; // Clear the bodies array after removal + } + } + const clothArray = []; + clothArray.push(soft.createCloth(-100, 0, 50, 1000, 300, false, true, soft.clothOptions, true)) + clothArray.push(soft.createCloth(-2000, 2375, 50, 1525, 200, false, true, soft.clothOptions, true)) + clothArray.push(soft.createCloth(-3950, 125, 50, 1800, 125, false, true, soft.clothOptions, true)) + const annoyingStuff = { + lasers(where, 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) 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) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles + 60; //player is immune to damage for an extra second + const dmg = 0.5 / simulation.dmgScale; + 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); + }, + laserBoss(x, y, radius = 30) { + mobs.spawn(x, y, 6, radius, "#f00"); + 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.count = 0; + me.frictionAir = 0.03; + // me.torque -= me.inertia * 0.002 + spawn.spawnOrbitals(me, radius + 50 + 200 * Math.random()) + Matter.Body.setDensity(me, 0.03); //extra dense //normal is 0.001 //makes effective life much larger + me.damageReduction = 0.25 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1) + me.isBoss = true; + // spawn.shield(me, x, y, 1); //not working, not sure why + me.onDeath = function () { + powerUps.spawnBossPowerUp(this.position.x, this.position.y) + }; + me.rotateVelocity = -Math.min(0.0045, 0.0015 * simulation.accelScale * simulation.accelScale) * (level.levelsCleared > 8 ? 1 : -1) + me.do = function () { + this.fill = '#' + Math.random().toString(16).substr(-6); //flash colors + this.checkStatus(); + + if (!this.isStunned) { + //check if slowed + let slowed = false + for (let i = 0; i < this.status.length; i++) { + if (this.status[i].type === "slow") { + slowed = true + break + } + } + if (!slowed) { + this.count++ + Matter.Body.setAngle(this, this.count * this.rotateVelocity) + Matter.Body.setAngularVelocity(this, 0) + } + + ctx.beginPath(); + for (let i = 0; i < this.vertices.length; i++) { + if (Math.sin((2 * Math.PI * simulation.cycle) / (50 + i)) > 0) { + this.lasers(this.vertices[i], Math.atan2(this.vertices[i].y - this.position.y, this.vertices[i].x - this.position.x)); + } + } + 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.lasers = function (where, 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) 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) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles + 60; //player is immune to damage for an extra second + const dmg = 0.5 / simulation.dmgScale; + 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); + } + } + } + level.setPosToSpawn(-350, 0); + level.exit.x = 1075; + level.exit.y = 20; + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); //bump for level entrance + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); //bump for level exit + level.defaultZoom = 1800 + simulation.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#aaFFFF55"; + spawn.mapRect(900, 50, 425, 250); + // spawn.mapRect(900, -1050, 0.1, 1350); + spawn.mapRect(-475, 2375, 1800, 250); + spawn.mapRect(-4400, 2375, 2475, 250); + spawn.mapRect(-4400, -450, 250, 3075); + spawn.mapRect(-4400, -450, 2225, 250); + spawn.mapRect(-2425, -1300, 250, 1100); + spawn.mapRect(-2425, -1300, 3825, 250); + spawn.mapRect(1325, -1300, 250, 3925); + spawn.mapRect(-875, -1300, 250, 1375); + spawn.mapRect(-725, 50, 675, 250); + spawn.mapRect(-875, 175, 175, 125); + for (let i = 0; i < 6; i++) { + spawn.mapRect(-4175, 2000 - i * 375, 50, 125); + } + spawn.mapRect(-3925, 162.5, 50, 125); + spawn.mapRect(-2175, 162.5, 50, 125); + spawn.mapRect(300, 2025, 250, 600); + spawn.mapRect(-2150, 175, 50, 25); + spawn.mapRect(-2150, 250, 50, 25); + spawn.mapRect(-900, 175, 50, 25); + spawn.mapRect(-900, 250, 50, 25); + spawn.mapRect(-1600, 175, 50, 25); + spawn.mapRect(-1500, 175, 50, 25); + spawn.mapRect(-1600, 250, 50, 25); + spawn.mapRect(-1500, 250, 50, 25); + spawn.mapRect(-1925, 175, 50, 25); + spawn.mapRect(-1925, 250, 50, 25); + spawn.mapRect(-1200, 175, 50, 25); + spawn.mapRect(-1200, 250, 50, 25); + spawn.bodyRect(-2125, 200, 1250, 50); + spawn.debris(425, 200, 50); + spawn.debris(-650, 2100, 50); + spawn.debris(-3000, 1925, 50); + spawn.debris(-3825, 1550, 50); + spawn.debris(-2475, -50, 50); + + const bouncyBody = body[body.length - 1]; + bouncyBody.restitution = 0.9; + spawn.mapVertex(-2175 + 1300 / 2, -1050 + 1225 / 2, "0 -400 -100 -300 -100 0 100 0 100 -300"); + + spawn.mapVertex(-4150 + 1975 / 2, -200 + 2575 / 2, "0 -800 -200 -600 -200 0 0 200 200 0 200 -600 0 200"); + const mapWithVertex = map[map.length - 1]; + let index1 = 0; + level.custom = () => { + level.exit.drawAndCheck(); + + level.enter.draw(); + + if (player.position.x > 425 && index1 === 0) { + soft.breaker(clothArray[0], 0.7); + soft.destroyer(clothArray[0]); + index1++; + } + if (player.position.y > 1300 && index1 === 1) { + setTimeout(() => { + soft.breaker(clothArray[0], 1); + soft.annihilate(clothArray[0]); + clothArray.splice(0, 1); + }, 1000); //prevents bugs + simulation.makeTextLog("Couldn't be so simple, could it?", 2000 * Math.random()); + index1++; + } + }; + level.customTopLayer = () => { + for (let i = 0; i < portals.length; i++) { + portals[i][2].query(); + portals[i][3].query(); + portals[i][0].draw(); + portals[i][1].draw(); + portals[i][2].draw(); + portals[i][3].draw(); + } + ctx.beginPath(); + if (Math.sin((2 * Math.PI * simulation.cycle) / (50)) > 0) { + annoyingStuff.lasers(mapWithVertex.vertices[0], Math.atan2(mapWithVertex.vertices[0].y - mapWithVertex.position.y, mapWithVertex.vertices[0].x - mapWithVertex.position.x)); + annoyingStuff.lasers(mapWithVertex.vertices[3], Math.atan2(mapWithVertex.vertices[3].y - mapWithVertex.position.y, mapWithVertex.vertices[3].x - mapWithVertex.position.x)); + } + if (Math.sin((2 * Math.PI * simulation.cycle) / (51)) > 0) { + annoyingStuff.lasers(mapWithVertex.vertices[1], Math.atan2(mapWithVertex.vertices[1].y - mapWithVertex.position.y, mapWithVertex.vertices[1].x - mapWithVertex.position.x)); + annoyingStuff.lasers(mapWithVertex.vertices[4], Math.atan2(mapWithVertex.vertices[4].y - mapWithVertex.position.y, mapWithVertex.vertices[4].x - mapWithVertex.position.x)); + } + if (Math.sin((2 * Math.PI * simulation.cycle) / (52)) > 0) { + annoyingStuff.lasers(mapWithVertex.vertices[2], Math.atan2(mapWithVertex.vertices[2].y - mapWithVertex.position.y, mapWithVertex.vertices[2].x - mapWithVertex.position.x)); + annoyingStuff.lasers(mapWithVertex.vertices[5], Math.atan2(mapWithVertex.vertices[5].y - mapWithVertex.position.y, mapWithVertex.vertices[5].x - mapWithVertex.position.x)); + } + + ctx.strokeStyle = "#000"; + 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(0,0,0,0.07)"; + ctx.stroke(); // Draw it + + for (let i = 0; i < clothArray.length; i++) { + soft.draw(clothArray[i]); + soft.gravity(clothArray[i]); + } + + ctx.beginPath(); + ctx.fillStyle = "rgba(69, 69, 69, 0.1)"; + ctx.rect(-475, 175, 425, 2300); + ctx.rect(900, 175, 425, 2300); + ctx.rect(-875, 175, 400, 10000); + ctx.rect(-4200, -250, 2025, 2775); + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = (m.pos.x < -725 && m.pos.y < 175) ? `rgba(68, 68, 68, ${Math.max(0.3, Math.min((-775 - m.pos.x) / 100, 0.99))})` : color.map; + ctx.rect(-875, 50, 175, 150); + ctx.fill(); + + }; + annoyingStuff.laserBoss(-1525, 1025); + spawn.pulsar(-1525, -850); + spawn.pulsar(1125, 1600); + spawn.pulsar(-250, 1600); + spawn.pulsar(-1450, 1600); + spawn.pulsar(-2950, 1750); + spawn.pulsar(-3375, 1750); + spawn.pulsar(-3825, 1300); + spawn.pulsar(-3825, 850); + spawn.pulsar(-3450, 50); + spawn.pulsar(-2925, 50); + spawn.pulsar(-1900, -400); + spawn.pulsar(-1200, -400); + + powerUps.addResearchToLevel() + powerUps.directSpawn(-775, 125, "tech"); + powerUp[powerUp.length - 1].collisionFilter.mask = cat.map | cat.body | cat.powerUp + spawn.bodyRect(-875, 75, 25, 100); + let hardBody = body[body.length - 1]; + hardBody.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.powerUp + }, // ******************************************************************************************************** // ******************************************************************************************************** // ***************************************** training levels ********************************************** diff --git a/js/mob.js b/js/mob.js index 349d687..d3cf211 100644 --- a/js/mob.js +++ b/js/mob.js @@ -1078,7 +1078,7 @@ const mobs = { }) } } else if (tech.isMobLowHealth && this.health < 0.25) { - dmg *= 3.22 + dmg *= 3 simulation.ephemera.push({ name: "damage outline", diff --git a/js/player.js b/js/player.js index e65d5af..2897a0d 100644 --- a/js/player.js +++ b/js/player.js @@ -556,24 +556,23 @@ const m = { dmg *= m.fieldHarmReduction // if (!tech.isFlipFlopOn && tech.isFlipFlopHealth) dmg *= 0.5 // 1.25 + Math.sin(m.cycle * 0.01) - if (tech.isDiaphragm) dmg *= 0.56 + 0.36 * Math.sin(m.cycle * 0.0075); + if (tech.isDiaphragm) dmg *= 0.55 + 0.35 * Math.sin(m.cycle * 0.0075); if (tech.isZeno) dmg *= 0.15 - if (tech.isFieldHarmReduction) dmg *= 0.65 + if (tech.isFieldHarmReduction) dmg *= 0.6 if (tech.isHarmMACHO) dmg *= 0.4 if (tech.isImmortal) dmg *= 0.7 - if (tech.energyRegen === 0) dmg *= 0.34 if (m.fieldMode === 0 || m.fieldMode === 3) dmg *= 0.973 ** m.coupling if (tech.isLowHealthDefense) dmg *= 1 - Math.max(0, 1 - m.health) * 0.8 - if (tech.isHarmReduceNoKill && m.lastKillCycle + 300 < m.cycle) dmg *= 0.26 - if (tech.squirrelFx !== 1) dmg *= 0.78//Math.pow(0.78, (tech.squirrelFx - 1) / 0.4) + if (tech.isHarmReduceNoKill && m.lastKillCycle + 300 < m.cycle) dmg *= 0.3 + if (tech.squirrelFx !== 1) dmg *= 0.8//Math.pow(0.78, (tech.squirrelFx - 1) / 0.4) if (tech.isAddBlockMass && m.isHolding) dmg *= 0.1 if (tech.isSpeedHarm && player.speed > 0.1) dmg *= 1 - Math.min(player.speed * 0.0193, 0.88) //capped at speed of 55 - if (tech.isHarmReduce && input.field) dmg *= 0.12 + if (tech.isHarmReduce && input.field) dmg *= 0.1 if (tech.isNeutronium && input.field && m.fieldCDcycle < m.cycle) dmg *= 0.05 - if (tech.isBotArmor) dmg *= 0.94 ** b.totalBots() - if (tech.isHarmArmor && m.lastHarmCycle + 600 > m.cycle) dmg *= 0.33; - if (tech.isNoFireDefense && m.cycle > m.fireCDcycle + 120) dmg *= 0.27 - if (tech.isTurret && m.crouch) dmg *= 0.34; + if (tech.isBotArmor) dmg *= 0.95 ** b.totalBots() + if (tech.isHarmArmor && m.lastHarmCycle + 600 > m.cycle) dmg *= 0.3; + if (tech.isNoFireDefense && m.cycle > m.fireCDcycle + 120) dmg *= 0.3 + if (tech.isTurret && m.crouch) dmg *= 0.3; if (tech.isFirstDer && b.inventory[0] === b.activeGun) dmg *= 0.85 ** b.inventory.length return tech.isEnergyHealth ? Math.pow(dmg, 0.5) : dmg //defense has less effect }, @@ -2232,7 +2231,7 @@ const m = { } else { m.fieldRegen = 0.001 //6 energy per second } - if (m.fieldMode === 0 || m.fieldMode === 4) m.fieldRegen += 0.000133 * m.coupling //return `generate ${(6*couple).toFixed(0)} energy per second` + if (m.fieldMode === 0 || m.fieldMode === 4) m.fieldRegen += 0.000133 * m.coupling if (tech.isTimeCrystals) { m.fieldRegen *= 2.5 } else if (tech.isGroundState) { @@ -2861,17 +2860,17 @@ const m = { return `deflecting condenses ${(0.1 * couple).toFixed(2)} ice IX` // return `invulnerable +${2*couple} seconds post collision` case 3: //negative mass - return `+${(100 * (1 - 0.973 ** couple)).toFixed(1)}% defense` + return `${(100 * (1 - 0.973 ** couple)).toFixed(1)}% damage taken` case 4: //assembler - return `generate ${(0.8 * couple).toFixed(1)} energy per second` + return `+${(0.8 * couple).toFixed(1)} energy per second` case 5: //plasma - return `+${(1.5 * couple).toFixed(1)}% damage` + return `${(1 + 1.5 * couple).toFixed(3)}x damage` case 6: //time dilation return `+${(5 * couple).toFixed(0)}% longer stopped time` //movement, jumping, and case 7: //cloaking - return `+${(3.3 * couple).toFixed(1)}% ambush damage` + return `${(1 + 3.3 * couple).toFixed(3)}x ambush damage` case 8: //pilot wave - return `+${(4 * couple).toFixed(0)}% block collision damage` + return `+${(1 + 4 * couple).toFixed(2)}% block collision damage` case 9: //wormhole return `after eating blocks +${(2 * couple).toFixed(0)} energy` case 10: //grappling hook @@ -2923,7 +2922,7 @@ const m = { name: "field emitter", imageNumber: Math.floor(Math.random() * 26), //pick one of the 25 field emitter image files at random description: `initial field
use energy to deflect mobs and throw blocks -
generate 4 energy per second`, //
100 max energy +
4 energy per second`, //
100 max energy effect: () => { m.hold = function () { if (m.isHolding) { @@ -2952,7 +2951,7 @@ const m = { //deflecting protects you in every direction description: `3 oscillating shields are permanently active
+150 max energy -
generate 6 energy per second`, +
6 energy per second`, drainCD: 0, effect: () => { m.fieldBlockCD = 0; @@ -3054,10 +3053,7 @@ const m = { }, { name: "perfect diamagnetism", - description: "deflecting does not drain energy
maintains functionality while inactive
generate 5 energy per second", - //
attract power ups from far away - // description: "attract power ups from far away
deflecting doesn't drain energy
thrown blocks have", - // description: "gain energy when blocking
no recoil when blocking", + description: "deflecting does not drain energy
maintains functionality while inactive
5 energy per second", effect: () => { m.fieldMeterColor = "#48f" //"#0c5" m.eyeFillColor = m.fieldMeterColor @@ -3292,14 +3288,14 @@ const m = { { name: "negative mass", //
hold blocks as if they have a lower mass - description: "use energy to nullify  gravity
+55% defense
generate 6 energy per second", + description: "use energy to nullify  gravity
0.4x damage taken
6 energy per second", fieldDrawRadius: 0, effect: () => { m.fieldFire = true; m.holdingMassScale = 0.01; //can hold heavier blocks with lower cost to jumping m.fieldMeterColor = "#333" m.eyeFillColor = m.fieldMeterColor - m.fieldHarmReduction = 0.45; //55% reduction + m.fieldHarmReduction = 0.4; //55% reduction m.fieldDrawRadius = 0; m.hold = function () { @@ -3480,10 +3476,9 @@ const m = { }, { name: "molecular assembler", - description: `excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
generate 12 energy per second`, - // simulation.molecularMode: Math.floor(4 * Math.random()), //0 spores, 1 missile, 2 ice IX, 3 drones + description: `excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
12 energy per second`, setDescription() { - return `excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
generate 12 energy per second` + return `excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
12 energy per second` }, effect: () => { m.fieldMeterColor = "#ff0" @@ -3600,7 +3595,7 @@ const m = { }, { name: "plasma torch", - description: "use energy to emit short range plasma
damages and pushes mobs away
generate 10 energy per second", + description: "use energy to emit short range plasma
damages and pushes mobs away
10 energy per second", set() { b.isExtruderOn = false // m.fieldCDcycleAlternate = 0 @@ -4056,7 +4051,7 @@ const m = { }, { name: "time dilation", - description: "use energy to stop time
+20% movement and fire rate
generate 12 energy per second", + description: "use energy to stop time
1.2x movement and fire rate
12 energy per second", set() { // m.fieldMeterColor = "#0fc" // m.fieldMeterColor = "#ff0" @@ -4246,7 +4241,7 @@ const m = { }, effect() { if (tech.isTimeStop) { - m.fieldHarmReduction = 0.66; //33% reduction + m.fieldHarmReduction = 0.6; } else { m.fieldHarmReduction = 1; } @@ -4255,7 +4250,7 @@ const m = { }, { name: "metamaterial cloaking", - description: "+66% defense while cloaked
after decloaking +333% damage for 2 s
generate 6 energy per second", + description: "0.3x damage taken while cloaked
after decloaking 4.5x damage for 2 s
6 energy per second", effect: () => { m.fieldFire = true; m.fieldMeterColor = "#333"; @@ -4306,7 +4301,7 @@ const m = { if (!m.isCloak) { //&& m.energy > drain + 0.03 // m.energy -= drain m.isCloak = true //enter cloak - m.fieldHarmReduction = 0.33; //66% reduction + m.fieldHarmReduction = 0.3; m.enterCloakCycle = m.cycle if (tech.isCloakHealLastHit && m.lastHit > 0) { const heal = Math.min(0.75 * m.lastHit, m.energy) @@ -4417,7 +4412,7 @@ const m = { //
blocks can't collide with intangible mobs //field radius decreases out of line of sight //unlock tech from other fields - description: "use energy to guide blocks
tech, fields, and guns have +2 choices
generate 10 energy per second", + description: "use energy to guide blocks
tech, fields, and guns have +2 choices
10 energy per second", effect: () => { m.fieldMeterColor = "#333" m.eyeFillColor = m.fieldMeterColor @@ -4646,7 +4641,7 @@ const m = { { name: "wormhole", //wormholes attract blocks and power ups
- description: "use energy to tunnel through a wormhole
+7% chance to duplicate spawned power ups
generate 6 energy per second", //
bullets may also traverse wormholes + description: "use energy to tunnel through a wormhole
+7% chance to duplicate spawned power ups
6 energy per second", //
bullets may also traverse wormholes drain: 0, effect: function () { m.fieldMeterColor = "#bbf" //"#0c5" @@ -5194,15 +5189,13 @@ const m = { }, { name: "grappling hook", - // description: `use energy to pull yourself towards the map
generate 6 energy per second`, - description: `use energy to fire a hook that pulls player
damages mobs and grabs blocks
generate 9 energy per second`, + description: `use energy to fire a hook that pulls player
damages mobs and grabs blocks
9 energy per second`, effect: () => { m.fieldFire = true; // m.holdingMassScale = 0.01; //can hold heavier blocks with lower cost to jumping // m.fieldMeterColor = "#789"//"#456" m.eyeFillColor = m.fieldMeterColor m.grabPowerUpRange2 = 300000 //m.grabPowerUpRange2 = 200000; - // m.fieldHarmReduction = 0.45; //55% reduction m.hold = function () { if (m.isHolding) { diff --git a/js/powerup.js b/js/powerup.js index d70b30a..09dfc48 100644 --- a/js/powerup.js +++ b/js/powerup.js @@ -505,10 +505,14 @@ const powerUps = { currentRerollCount: 0, use(type) { //runs when you actually research a list of selections, type can be field, gun, or tech if (tech.isJunkResearch && powerUps.research.currentRerollCount < 3) { - tech.addJunkTechToPool(tech.junkResearchNumber * 0.01) + tech.addJunkTechToPool(0.01) } else { powerUps.research.changeRerolls(-1) } + if (tech.isResearchDamage) { + tech.damage *= 1.04 + simulation.makeTextLog(`1.04x damage from peer review`); + } powerUps.research.currentRerollCount++ // if (tech.isBanish && type === 'tech') { // banish researched tech // const banishLength = tech.isDeterminism ? 1 : 3 + tech.extraChoices * 2 @@ -699,11 +703,8 @@ const powerUps = { text += `
entanglement
` } else if (tech.isJunkResearch && powerUps.research.currentRerollCount < 3) { text += `
` // style = "margin-left: 192px; margin-right: -192px;" - tech.junkResearchNumber = Math.ceil(3 * Math.random()) text += `
` - for (let i = 0; i < tech.junkResearchNumber; i++) { - text += `
` - } + text += `
` text += `
  pseudoscience
` } else if (powerUps.research.count > 0) { text += `
` // style = "margin-left: 192px; margin-right: -192px;" @@ -721,11 +722,8 @@ const powerUps = { text += `entanglement` //‌ } else if (tech.isJunkResearch && powerUps.research.currentRerollCount < 3) { text += `` // style = "margin-left: 192px; margin-right: -192px;" - tech.junkResearchNumber = Math.ceil(3 * Math.random()) text += `
` - for (let i = 0, len = tech.junkResearchNumber; i < len; i++) { - text += `
` - } + text += `
` text += `
  ${tech.isResearchReality ? "alternate reality" : "research"}
` } else if (powerUps.research.count > 0) { text += `` // style = "margin-left: 192px; margin-right: -192px;" @@ -787,24 +785,6 @@ const powerUps = { } return text }, - // researchAndCancelText(type) { - // let text = "
" - // if (tech.isJunkResearch && powerUps.research.currentRerollCount < 3) { - // text += `
` // style = "margin-left: 192px; margin-right: -192px;" - // tech.junkResearchNumber = Math.ceil(4 * Math.random()) - // text += `
` - // for (let i = 0; i < tech.junkResearchNumber; i++) text += `
` - // text += `
  pseudoscience
` - // } else if (powerUps.research.count > 0) { - // text += `
` // style = "margin-left: 192px; margin-right: -192px;" - // text += `
` - // for (let i = 0, len = Math.min(powerUps.research.count, 30); i < len; i++) text += `
` - // text += `
  ${tech.isResearchReality?"alternate reality": "research"}
` - // } else { - // text += `
` - // } - // return text + '
' - // }, hideStyle: `style="height:auto; border: none; background-color: transparent;"`, gunText(choose, click) { const style = localSettings.isHideImages ? powerUps.hideStyle : `style="background-image: url('img/gun/${b.guns[choose].name}.webp');"` diff --git a/js/spawn.js b/js/spawn.js index 691729b..7f12bf2 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -974,7 +974,7 @@ const spawn = { 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 === 1 && simulation.difficultyMode < 5) 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`); @@ -1011,7 +1011,7 @@ const spawn = { return } } - if (simulation.testing) { + if (simulation.testing || simulation.difficultyMode > 4) { unlockExit() setTimeout(function () { simulation.makeTextLog(`level.levels.length = Infinite`); diff --git a/js/tech.js b/js/tech.js index acd5cda..6d68916 100644 --- a/js/tech.js +++ b/js/tech.js @@ -233,34 +233,34 @@ const tech = { // } // } if (tech.isPowerUpDamage) dmg *= 1 + 0.05 * powerUp.length - if (tech.isDamageCooldown) dmg *= m.lastKillCycle + tech.isDamageCooldownTime > m.cycle ? 0.45 : 4.33 - if (tech.isDamageAfterKillNoRegen && m.lastKillCycle + 300 > m.cycle) dmg *= 1.93 - if (tech.isDivisor && b.activeGun !== undefined && b.activeGun !== null && b.guns[b.activeGun].ammo % 3 === 0) dmg *= 1.77 + if (tech.isDamageCooldown) dmg *= m.lastKillCycle + tech.isDamageCooldownTime > m.cycle ? 0.5 : 4 + if (tech.isDamageAfterKillNoRegen && m.lastKillCycle + 300 > m.cycle) dmg *= 2 + if (tech.isDivisor && b.activeGun !== undefined && b.activeGun !== null && b.guns[b.activeGun].ammo % 3 === 0) dmg *= 1.8 if (tech.isNoGroundDamage) dmg *= m.onGround ? 0.85 : 2 - if (tech.isDilate) dmg *= 1.5 + 0.6 * Math.sin(m.cycle * 0.0075) - if (tech.isGunChoice && tech.buffedGun === b.inventoryGun) dmg *= 1 + 0.31 * b.inventory.length + if (tech.isDilate) dmg *= 1.5 + 0.5 * Math.sin(m.cycle * 0.0075) + if (tech.isGunChoice && tech.buffedGun === b.inventoryGun) dmg *= 1 + 0.3 * b.inventory.length if (powerUps.boost.endCycle > m.cycle) dmg *= 1 + powerUps.boost.damage if (m.coupling && (m.fieldMode === 0 || m.fieldMode === 5)) dmg *= 1 + 0.015 * m.coupling - if (m.isSneakAttack && m.sneakAttackCycle + Math.min(100, 0.66 * (m.cycle - m.enterCloakCycle)) > m.cycle) dmg *= 4.33 * (1 + 0.033 * m.coupling) + if (m.isSneakAttack && m.sneakAttackCycle + Math.min(100, 0.66 * (m.cycle - m.enterCloakCycle)) > m.cycle) dmg *= 4.5 * (1 + 0.033 * m.coupling) if (tech.deathSkipTime) dmg *= 1 + 0.6 * tech.deathSkipTime - if (tech.isTechDebt) dmg *= tech.totalCount > 20 ? Math.pow(0.85, tech.totalCount - 20) : 4 - 0.15 * tech.totalCount // if (tech.isTechDebt) dmg *= Math.min(Math.pow(0.85, tech.totalCount - 20), 4 - 0.15 * tech.totalCount) + if (tech.isTechDebt) dmg *= tech.totalCount > 20 ? Math.pow(0.85, tech.totalCount - 20) : 4 - 0.15 * tech.totalCount if (tech.isFlipFlopDamage && tech.isFlipFlopOn) dmg *= 1.555 - if (tech.isAnthropicDamage && tech.isDeathAvoidedThisLevel) dmg *= 2.3703599 + if (tech.isAnthropicDamage && tech.isDeathAvoidedThisLevel) dmg *= 2.71828 if (tech.isDupDamage) dmg *= 1 + Math.min(1, tech.duplicationChance()) if (tech.isDamageForGuns) dmg *= 1 + 0.22 * Math.max(0, b.inventory.length - 1) - if (tech.isOneGun && b.inventory.length < 2) dmg *= 1.25 + if (tech.isOneGun && b.inventory.length < 2) dmg *= 1.3 if (tech.isAcidDmg && m.health > 1) dmg *= 1.35; if (tech.isRerollDamage) dmg *= 1 + Math.max(0, 0.03 * powerUps.research.count) - if (tech.isBotDamage) dmg *= 1 + 0.06 * b.totalBots() + if (tech.isBotDamage) dmg *= 1 + 0.05 * b.totalBots() if (tech.restDamage > 1 && player.speed < 1) dmg *= tech.restDamage - if (tech.isLowEnergyDamage) dmg *= 1 + 0.7 * Math.max(0, 1 - m.energy) + if (tech.isLowEnergyDamage) dmg *= 1 + Math.max(0, 1 - m.energy) if (tech.energyDamage) dmg *= 1 + m.energy * 0.23 * tech.energyDamage; - if (tech.isDamageFromBulletCount) dmg *= 1 + bullet.length * 0.007 - if (tech.isNoFireDamage && m.cycle > m.fireCDcycle + 120) dmg *= 2.11 + if (tech.isDamageFromBulletCount) dmg *= 1 + bullet.length * 0.01 + if (tech.isNoFireDamage && m.cycle > m.fireCDcycle + 120) dmg *= 2 if (tech.isSpeedDamage) dmg *= 1 + Math.min(0.88, player.speed * 0.0193) - if (tech.isAxion && tech.isHarmMACHO) dmg *= 2 - m.defense() + if (tech.isAxion && tech.isHarmMACHO) dmg *= 2 if (tech.isHarmDamage && m.lastHarmCycle + 480 > m.cycle) dmg *= 3; - if (tech.lastHitDamage && m.lastHit) dmg *= 1 + tech.lastHitDamage * m.lastHit * (2 - m.defense()) // if (!simulation.paused) m.lastHit = 0 + if (tech.lastHitDamage && m.lastHit) dmg *= 1 + tech.lastHitDamage * m.lastHit if (tech.isLowHealthDmg) dmg *= 1 + 0.7 * Math.max(0, 1 - (tech.isEnergyHealth ? m.energy : m.health)) if (tech.isJunkDNA) dmg *= 1 + 2 * tech.junkChance return dmg @@ -343,7 +343,7 @@ const tech = { }, { name: "nitinol", - description: "+33% movement and jumping
+22% defense", + description: "1.3x movement and jumping
0.8x damage taken", maxCount: 1, count: 0, frequency: 1, @@ -370,7 +370,7 @@ const tech = { }, { name: "Higgs mechanism", - description: "+77% fire rate
while firing your position is fixed", + description: "1.8x fire rate
while firing your position is fixed", maxCount: 1, count: 0, frequency: 1, @@ -397,7 +397,7 @@ const tech = { }, { name: "Hilbert space", - description: "+300% damage
after a collision enter an alternate reality", + description: "4x damage
after a collision enter an alternate reality", maxCount: 1, count: 0, frequency: 1, @@ -408,7 +408,7 @@ const tech = { return !m.isAltSkin && !tech.isResearchReality && !tech.isSwitchReality }, requires: "not skinned, Ψ(t) collapse, many-worlds", - damage: 4, //1+300% + damage: 4, effect() { m.skin.anodize(); tech.damage *= this.damage @@ -421,7 +421,7 @@ const tech = { }, { name: "aperture", - description: "every 6 seconds your damage cycles
between -10% and +110% damage", + description: "every 6 seconds your damage cycles
between 1x and 2x damage", maxCount: 1, count: 0, frequency: 1, @@ -442,7 +442,7 @@ const tech = { }, { name: "diaphragm", - description: "every 6 seconds your defense cycles
between +8% and +80% defense", + description: "every 6 seconds your damage taken cycles
between 0.9x and 0.2x damage taken", maxCount: 1, count: 0, frequency: 2, @@ -464,8 +464,7 @@ const tech = { }, { name: "mass-energy equivalence", - // description: "energy protects you instead of health
√ of defense reduction reduces max energy", - description: `energy protects you instead of health
defensive upgrades reduced by about 50%`, + description: `energy protects you instead of health
damage taken effects 50% less effective`, maxCount: 1, count: 0, frequency: 1, @@ -536,10 +535,7 @@ const tech = { { name: "depolarization", descriptionFunction() { - // return `+300% damage or -50% damage
if a mob has died in the last 5 seconds` - // return `+333% damage if no mobs died in the last ${(tech.isDamageCooldownTime / 60).toFixed(1)} seconds
-55% damage if a mob died in the last ${(tech.isDamageCooldownTime / 60).toFixed(0)} seconds
` - // return `-55% damage if a mob died in the last ${(tech.isDamageCooldownTime / 60).toFixed(1)} seconds
otherwise do +333% damage
` - return `-55% damage for ${(tech.isDamageCooldownTime / 60).toFixed(1)} seconds after a mob dies
+333% damage otherwise
` + return `0.5x damage for ${(tech.isDamageCooldownTime / 60).toFixed(1)} seconds after a mob dies
4x damage otherwise
` }, maxCount: 1, count: 0, @@ -581,8 +577,6 @@ const tech = { }, { name: "CPT symmetry", - // description: "charge, parity, and time invert to undo defense
rewind (1.5—5) seconds for (66—220) energy", - // description: "after losing health, if you have full energy
rewind time for 44 energy per second", descriptionFunction() { return `after losing health, if you have above ${(85 * Math.min(100, m.maxEnergy)).toFixed(0)} energy
rewind time for 20 energy per second` }, @@ -646,7 +640,7 @@ const tech = { { name: "ternary", //"divisor", descriptionFunction() { - return `+77% damage while your current gun
has ammo divisible by 3` + return `1.8x damage while your current gun
has ammo divisible by 3` }, maxCount: 1, count: 0, @@ -664,7 +658,7 @@ const tech = { }, { name: "ordnance", - description: "double the frequency of finding guntech
spawn a gun and +6% JUNK to tech pool", + description: "double the frequency of finding guntech
spawn a gun and +6% JUNKtech chance", maxCount: 1, count: 0, frequency: 1, @@ -806,7 +800,7 @@ const tech = { { name: "arsenal", descriptionFunction() { - return `+22% damage per unequipped gun (${(22 * Math.max(0, b.inventory.length - 1)).toFixed(0)}%)` + return `1.22x damage per unequipped gun (${(22 * Math.max(0, b.inventory.length - 1)).toFixed(0)}%)` }, maxCount: 1, count: 0, @@ -824,7 +818,7 @@ const tech = { { name: "active cooling", descriptionFunction() { - return `+28% fire rate per unequipped gun (${(28 * Math.max(0, b.inventory.length - 1)).toFixed(0)}%)` + return `1.3x fire rate per unequipped gun` }, //
but not including your equipped gun` }, maxCount: 1, count: 0, @@ -847,11 +841,9 @@ const tech = { let info = "" if (this.count > 0 && Number.isInteger(tech.buffedGun) && b.inventory.length) { let gun = b.guns[b.inventory[tech.buffedGun]].name - info = `
this level: +${(31 * Math.max(0, b.inventory.length)).toFixed(0)}% damage for ${gun}` + info = `
this level: ${(1.3 * Math.max(0, b.inventory.length)).toFixed(2)}x damage for ${gun}` } - return ` - a new gun is chosen to be improved each level -
+31% damage per gun for the chosen gun${info}` + return `a new gun is chosen to be improved each level
1.3x damage per gun for the chosen gun${info}` }, maxCount: 1, count: 0, @@ -900,7 +892,7 @@ const tech = { }, { name: "logistics", - description: `${powerUps.orb.ammo()} give 80% more ammo, but
it's only added to your current gun`, + description: `${powerUps.orb.ammo()} give 1.8x ammo, but
it's only added to your current gun`, maxCount: 1, count: 0, frequency: 1, @@ -919,7 +911,7 @@ const tech = { { name: "cache", link: `cache`, - description: `${powerUps.orb.ammo()} give 1500% more ammo, but
you can't store any more ammo than that`, + description: `${powerUps.orb.ammo()} give 15x ammo, but
you can't store any more ammo than that`, // ammo powerups always max out your gun, // but the maximum ammo ti limited // description: `${powerUps.orb.ammo()} give 13x more ammo, but
you can't store any more ammo than that`, @@ -961,7 +953,7 @@ const tech = { }, { name: "non-renewables", - description: `+97% damage
${powerUps.orb.ammo()} can't spawn`, + description: `2x damage
${powerUps.orb.ammo()} can't spawn`, maxCount: 1, count: 0, frequency: 1, @@ -970,7 +962,7 @@ const tech = { return !tech.isAmmoFromHealth && !tech.isBoostReplaceAmmo }, requires: "not catabolism, quasiparticles", - damage: 1.97, + damage: 2, effect() { tech.damage *= this.damage tech.isEnergyNoAmmo = true; @@ -998,7 +990,7 @@ const tech = { }, { name: "gun turret", - description: "if crouching
+66% defense ", + description: "if crouching
0.3x damage taken", maxCount: 1, count: 0, frequency: 1, @@ -1016,7 +1008,7 @@ const tech = { }, { name: "dead reckoning", - description: "if your speed is 0
+50% damage", + description: "if your speed is 0
1.5x damage", maxCount: 9, count: 0, frequency: 1, @@ -1034,7 +1026,7 @@ const tech = { }, { name: "kinetic bombardment", - description: "far away mobs take more damage
up to +33% damage at 3000 displacement", + description: "far away mobs take more damage
up to 1.3x damage at 3000 displacement", maxCount: 1, count: 0, frequency: 1, @@ -1053,7 +1045,7 @@ const tech = { { name: "integrated armament", link: `integrated armament`, - description: `+25% damage, but new guns replace
your current gun and convert guntech
`, + description: `1.3x damage, but new guns replace
your current gun and convert guntech
`, maxCount: 1, count: 0, frequency: 1, @@ -1075,8 +1067,8 @@ const tech = { let damageTotal = 1 for (let i = 0; i < this.damageSoFar.length; i++) damageTotal *= this.damageSoFar[i] let currentDamage = "" - if (this.count) currentDamage = `
(+${(100 * (damageTotal - 1)).toFixed(0)}%)` - return `gain between +7% and +13% damage` + currentDamage + if (this.count) currentDamage = `
(${(damageTotal).toFixed(2)}x)` + return `randomly gain between 1x and 1.3x damage` + currentDamage }, maxCount: 9, count: 0, @@ -1087,7 +1079,7 @@ const tech = { damage: 1.1, damageSoFar: [], //tracks the random damage upgrades so it can be removed and in descriptionFunction effect() { - const damage = (Math.floor((Math.random() * 0.07 + 0.07 + 1) * 100)) / 100 + const damage = (Math.floor((Math.random() * 0.3 + 1) * 100)) / 100 tech.damage *= damage this.damageSoFar.push(damage) }, @@ -1126,7 +1118,7 @@ const tech = { // }, { name: "Newtons 1st law", - description: "defense is proportional to your speed
up to +88% defense at 55 speed", + description: "damage taken is proportional to your speed
up to 0.2x damage taken at 55 speed", maxCount: 1, count: 0, frequency: 1, @@ -1144,7 +1136,7 @@ const tech = { }, { name: "Newtons 2nd law", - description: "damage is proportional to your speed
up to +88% damage at 55 speed", + description: "damage is proportional to your speed
up to 2x damage at 55 speed", maxCount: 1, count: 0, frequency: 1, @@ -1163,25 +1155,33 @@ const tech = { { name: "microstates", link: `microstates`, - description: "for each active bullet or bot
+0.7% damage", + descriptionFunction() { + return `use ${powerUps.orb.research(3)}
1.01x damage per bullet or bot` + }, maxCount: 1, count: 0, frequency: 1, frequencyDefault: 1, allowed() { - return true + return powerUps.research.count > 2 || build.isExperimentSelection }, requires: "", effect() { tech.isDamageFromBulletCount = true + for (let i = 0; i < 3; i++) { + if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1) + } }, remove() { tech.isDamageFromBulletCount = false + if (this.count > 0) { + powerUps.research.changeRerolls(3) + } } }, { name: "regression", - description: "bullet collisions increase vulnerability to
damage by +5% for mobs (+0.25% for bosses)", + description: "bullet collisions increase vulnerability to
damage by 1.05x for mobs (+1.025x for bosses)", maxCount: 1, count: 0, frequency: 1, @@ -1199,7 +1199,7 @@ const tech = { }, { name: "simulated annealing", - description: "+20% damage
–20% fire rate", + description: "1.2x damage
0.8x fire rate", maxCount: 1, count: 0, frequency: 1, @@ -1221,7 +1221,7 @@ const tech = { }, { name: "dynamical systems", - description: `use ${powerUps.orb.research(2)}
+30% damage`, + description: `use ${powerUps.orb.research(2)}
1.3x damage`, // isFieldTech: true, maxCount: 1, count: 0, @@ -1253,7 +1253,7 @@ const tech = { }, { name: "heuristics", - description: "+22% fire rate
spawn a gun", + description: "1.2x fire rate
spawn a gun", maxCount: 9, count: 0, frequency: 1, @@ -1263,7 +1263,7 @@ const tech = { }, requires: "", effect() { - tech.fireRate *= 0.78 + tech.fireRate *= 0.8 b.setFireCD(); powerUps.spawn(m.pos.x, m.pos.y, "gun"); }, @@ -1275,7 +1275,7 @@ const tech = { { name: "anti-shear topology", link: `anti-shear topology`, - description: "your bullets last +30% longer", //
drone spore worm flea missile foam wave neutron ice", + description: "your bullets last 1.3x longer", //
drone spore worm flea missile foam wave neutron ice", maxCount: 3, count: 0, frequency: 1, @@ -1291,7 +1291,7 @@ const tech = { }, { name: "fracture analysis", - description: "if a mob is stunned it takes
+400% damage from bullet impacts", + description: "if a mob is stunned it takes
5x damage from bullet impacts", maxCount: 1, count: 0, frequency: 2, @@ -1347,7 +1347,7 @@ const tech = { name: "zoospore vector", link: `zoospore vector`, descriptionFunction() { - return `after mobs die there is a +10% chance
they grow ${b.guns[6].nameString('s')}` + return `after mobs die there is a 10% chance
they grow ${b.guns[6].nameString('s')}` }, // description: "after mobs die
they have a +10% chance to grow spores", maxCount: 9, @@ -1372,7 +1372,7 @@ const tech = { }, { name: "propagator", - description: "after mobs die advance time 0.5 seconds
+60% damage", + description: "after mobs die advance time 0.5 seconds
1.6x damage", maxCount: 3, count: 0, frequency: 1, @@ -1389,7 +1389,7 @@ const tech = { { name: "collider", descriptionFunction() { - return `after mobs die power ups
randomly collide to form different power ups` + return `after mobs die random power ups
collide to form different power ups` // return `after mobs die there is a +33% chance to convert
${powerUps.orb.heal()}, ${powerUps.orb.ammo()}, ${powerUps.orb.research(1)}, tech, field, gun into other types` }, @@ -1430,7 +1430,7 @@ const tech = { { name: "enthalpy", descriptionFunction() { - return `after mobs die
they have a +8% chance to spawn ${powerUps.orb.heal(1)}` + return `after mobs die
they have an 8% chance to spawn ${powerUps.orb.heal(1)}` }, maxCount: 9, count: 0, @@ -1448,27 +1448,9 @@ const tech = { tech.healSpawn = 0; } }, - // { - // name: "yield stress", - // description: "+55% damage
to mobs at maximum health", - // maxCount: 1, - // count: 0, - // frequency: 1, - // frequencyDefault: 1, - // allowed() { - // return m.fieldMode !== 7 && tech.mobSpawnWithHealth === 0 - // }, - // requires: "not cloaking, reaction inhibitor", - // effect() { - // tech.isMobFullHealth = true - // }, - // remove() { - // tech.isMobFullHealth = false - // } - // }, { name: "cascading failure", - description: "+222% damage
to mobs below 25% health", + description: "3x damage to mobs below 25% durability", maxCount: 1, count: 0, frequency: 3, @@ -1486,7 +1468,7 @@ const tech = { }, { name: "reaction inhibitor", - description: "mobs spawn with -12% initial health", + description: "mobs spawn with 0.88x initial durability", maxCount: 3, count: 0, frequency: 1, @@ -1498,7 +1480,6 @@ const tech = { effect() { tech.mobSpawnWithHealth++ mobs.setMobSpawnHealth() - //set all mobs at full health to 0.85 for (let i = 0; i < mob.length; i++) { if (mob.health > mobs.mobSpawnWithHealth) mob.health = mobs.mobSpawnWithHealth } @@ -1511,7 +1492,7 @@ const tech = { { name: "scrap bots", link: `scrap bots`, - description: "after mobs die you have a +33% chance
to build scrap bots that operate for 15 seconds", + description: "after mobs die you have a 33% chance
to build scrap bots that operate for 15 seconds", maxCount: 3, count: 0, frequency: 1, @@ -1577,7 +1558,7 @@ const tech = { { name: "nail-bot upgrade", link: `nail-bot upgrade`, - description: "convert your bots to nail-bots
+500% fire rate and +40% nail velocity", + description: "convert your bots to nail-bots
5x fire rate and 1.4x nail velocity", maxCount: 1, count: 0, frequency: 3, @@ -1635,7 +1616,7 @@ const tech = { { name: "foam-bot upgrade", link: `foam-bot upgrade`, - description: "convert your bots to foam-bots
+300% foam size and fire rate", + description: "convert your bots to foam-bots
3x foam size and fire rate", maxCount: 1, count: 0, frequency: 3, @@ -1691,7 +1672,7 @@ const tech = { { name: "sound-bot upgrade", link: `sound-bot upgrade`, - description: "convert your bots to sound-bots
+150% wave fire rate and +150% damage", + description: "convert your bots to sound-bots
2.5x wave fire rate and 2.5x damage", maxCount: 1, count: 0, frequency: 3, @@ -1749,7 +1730,7 @@ const tech = { { name: "boom-bot upgrade", link: `boom-bot upgrade`, - description: "convert your bots to boom-bots
+300% explosion damage and size", + description: "convert your bots to boom-bots
4x explosion damage and size", maxCount: 1, count: 0, frequency: 3, @@ -1807,7 +1788,7 @@ const tech = { { name: "laser-bot upgrade", link: `laser-bot upgrade`, - description: "convert your bots to laser-bots
+100% damage, efficiency, and range", + description: "convert your bots to laser-bots
2.00x damage, efficiency, and range", maxCount: 1, count: 0, frequency: 3, @@ -1865,7 +1846,7 @@ const tech = { { name: "orbital-bot upgrade", link: `orbital-bot upgrade`, - description: "convert your bots to orbital-bots
+300% orbital damage and +50% radius", + description: "convert your bots to orbital-bots
4x orbital damage and 1.5x radius", maxCount: 1, count: 0, frequency: 3, @@ -1906,7 +1887,7 @@ const tech = { { name: "dynamo-bot", link: `dynamo-bot`, - description: "a bot damages mobs while it traces your path
when it's near generate +8 energy per second", + description: "a bot damages mobs while it traces your path
+8 energy per second when nearby", maxCount: 9, count: 0, frequency: 1, @@ -1932,7 +1913,7 @@ const tech = { { name: "dynamo-bot upgrade", link: `dynamo-bot upgrade`, - description: "convert your bots to dynamo-bots
when it's near generate +24 energy per second", + description: "convert your bots to dynamo-bots
+24 energy per second when nearby", maxCount: 1, count: 0, frequency: 3, @@ -1963,7 +1944,7 @@ const tech = { }, { name: "perimeter defense", - description: "for each permanent bot
+6% defense", + description: "for each permanent bot
0.95x damage taken", maxCount: 1, count: 0, frequency: 2, @@ -1982,7 +1963,7 @@ const tech = { }, { name: "network effect", - description: "for each permanent bot
+6% damage", + description: "for each permanent bot
1.05x damage", maxCount: 1, count: 0, frequency: 2, @@ -2173,7 +2154,7 @@ const tech = { }, { name: "decorrelation", - description: "if your gun or field are unused for 2 seconds
+77% defense", + description: "if your gun or field are unused for 2 seconds
0.3x damage taken", maxCount: 1, count: 0, frequency: 1, @@ -2191,7 +2172,7 @@ const tech = { }, { name: "anticorrelation", - description: "if your gun or field are unused for 2 seconds
+111% damage", + description: "if your gun or field are unused for 2 seconds
2x damage", maxCount: 1, count: 0, frequency: 1, @@ -2209,7 +2190,7 @@ const tech = { }, { name: "mass driver", - description: "+300% block collision damage", + description: "4x block collision damage", maxCount: 1, count: 0, frequency: 1, @@ -2228,7 +2209,7 @@ const tech = { { name: "inflation", link: `inflation`, - description: "if holding a block +90% defense
after throwing a block it expands 200%", + description: "if holding a block 0.1x damage taken
after throwing a block it expands 3x", maxCount: 1, count: 0, frequency: 3, @@ -2246,7 +2227,7 @@ const tech = { }, { name: "restitution", - description: "+150% block collision damage
after throwing a block it becomes very bouncy", + description: "2.5x block collision damage
after throwing a block it becomes very bouncy", maxCount: 1, count: 0, frequency: 3, @@ -2264,7 +2245,7 @@ const tech = { }, { name: "flywheel", - description: "+150% block collision damage
after a mob dies its block is flung at mobs", + description: "2.5x block collision damage
after a mob dies its block is flung at mobs", maxCount: 1, count: 0, frequency: 3, @@ -2280,24 +2261,6 @@ const tech = { tech.isMobBlockFling = false } }, - // { - // name: "fermions", - // description: "blocks thrown by you or pilot wave will
collide with intangible mobs, but not you", - // maxCount: 1, - // count: 0, - // frequency: 2, - // frequencyDefault: 2, - // allowed() { - // return (tech.blockDamage > 0.075 || m.fieldMode === 8) && !tech.isTokamak - // }, - // requires: "mass driver or pilot wave, not tokamak", - // effect() { - // tech.isBlockBullets = true - // }, - // remove() { - // tech.isBlockBullets = false - // } - // }, { name: "buckling", descriptionFunction() { @@ -2420,7 +2383,7 @@ const tech = { }, { name: "NAND gate", - description: "if ON
+55.5% damage", + description: "if ON
1.555x damage", maxCount: 1, count: 0, frequency: 3, @@ -2600,7 +2563,7 @@ const tech = { { name: "first derivative", descriptionFunction() { - return `while your first gun is equipped
+15% defense per gun (${(100 * (1 - 0.85 ** b.inventory.length)).toFixed(0)}%)` + return `while your first gun is equipped
0.85x damage taken per gun (${(100 * (1 - 0.85 ** b.inventory.length)).toFixed(0)}%)` }, maxCount: 1, count: 0, @@ -2619,7 +2582,7 @@ const tech = { }, { name: "MACHO", - description: "a massive compact halo object follows you
+60% defense while you are inside the MACHO", + description: "a massive compact halo object follows you
0.4x damage taken inside the MACHO", maxCount: 1, count: 0, frequency: 1, @@ -2642,7 +2605,7 @@ const tech = { }, { name: "axion", - description: "while inside the MACHO
defense increases damage", + description: "while inside the MACHO
2x damage", maxCount: 1, count: 0, frequency: 2, @@ -2660,7 +2623,7 @@ const tech = { }, { name: "dark star", - description: "mobs inside the MACHO are damaged
increase MACHO radius by 15%", + description: "mobs inside the MACHO are damaged
1.2x MACHO radius", maxCount: 1, count: 0, frequency: 2, @@ -2696,7 +2659,7 @@ const tech = { { name: "non-Newtonian armor", link: `non-Newtonian armor`, - description: "after mob collisions
+66% defense for 10 seconds", + description: "after mob collisions
0.3x damage taken for 10 seconds", maxCount: 1, count: 0, frequency: 1, @@ -2714,9 +2677,7 @@ const tech = { }, { name: "tessellation", - description: `use ${powerUps.orb.research(2)}
+35% defense`, - // description: "use 4 research
reduce defense by 50%", - // isFieldTech: true, + description: `use ${powerUps.orb.research(2)}
0.6x damage taken`, maxCount: 1, count: 0, frequency: 1, @@ -2797,28 +2758,9 @@ const tech = { tech.isHarmFreeze = false; } }, - // { - // name: "clock gating", - // description: `after losing health slow time by 50%
+20% defense`, - // maxCount: 1, - // count: 0, - // frequency: 1, - // frequencyDefault: 1, - // allowed() { - // return simulation.fpsCapDefault > 45 - // }, - // requires: "FPS above 45", - // effect() { - // tech.isSlowFPS = true; - // }, - // remove() { - // tech.isSlowFPS = false; - // } - // }, - { name: "piezoelectricity", - description: "if you collide with a mob
generate +2048 energy", //
reduce defense by 15% + description: "if you collide with a mob
generate +2048 energy", maxCount: 1, count: 0, frequency: 1, @@ -2837,8 +2779,7 @@ const tech = { }, { name: "ground state", - description: "+266 maximum energy
–33% passive energy generation", - // description: "reduce defense by 66%
you no longer passively regenerate energy", + description: "+266 maximum energy
0.66x passive energy generation", maxCount: 1, count: 0, frequency: 1, @@ -2860,7 +2801,7 @@ const tech = { }, { name: "heat engine", - description: `+40% damage
–50 maximum energy`, + description: `1.4x damage
–50 maximum energy`, maxCount: 1, count: 0, frequency: 1, @@ -2881,7 +2822,7 @@ const tech = { }, { name: "exothermic process", - description: "+50% damage
after mobs die –20% energy", + description: "1.6x damage
after mobs die –20% energy", maxCount: 1, count: 0, frequency: 1, @@ -2890,7 +2831,7 @@ const tech = { return true }, requires: "", - damage: 1.55, + damage: 1.6, effect() { tech.damage *= this.damage tech.isEnergyLoss = true; @@ -2902,23 +2843,33 @@ const tech = { }, { name: "Gibbs free energy", - description: `for each energy below 100
+0.7% damage`, + descriptionFunction() { + return `use ${powerUps.orb.research(2)}
1.01x damage per energy below 100 (${(1 + Math.max(0, 1 - m.energy)).toFixed(2)}x)` + }, maxCount: 1, count: 0, frequency: 1, frequencyDefault: 1, - allowed: () => true, + allowed() { + return powerUps.research.count > 1 || build.isExperimentSelection + }, requires: "", effect() { tech.isLowEnergyDamage = true; + for (let i = 0; i < 2; i++) { + if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1) + } }, remove() { tech.isLowEnergyDamage = false; + if (this.count > 0) { + powerUps.research.changeRerolls(2) + } } }, { name: "overcharge", - description: "+88 maximum energy
+5% JUNK to tech pool", + description: "+88 maximum energy
+5% JUNKtech chance", maxCount: 9, count: 0, frequency: 1, @@ -2944,7 +2895,7 @@ const tech = { }, { name: "Maxwells demon", - description: "energy above max decays by 30% 1% per second
+5% JUNK to tech pool", + description: "energy above max decays 30x slower
+5% JUNKtech chance", maxCount: 1, count: 0, frequency: 2, @@ -2968,7 +2919,7 @@ const tech = { }, { name: "inductive charging", - description: "if crouched +600% passive energy generation
if not crouched energy generation is disabled", + description: "if crouched 7x passive energy generation
if not crouched energy generation is disabled", maxCount: 1, count: 0, frequency: 1, @@ -2991,7 +2942,7 @@ const tech = { }, { name: "energy conservation", - description: "4% of damage done recovered as energy", + description: "1.04x of damage done recovered as energy", maxCount: 9, count: 0, frequency: 1, @@ -3009,7 +2960,7 @@ const tech = { }, { name: "parasitism", - description: "if a mob has died in the last 5 seconds
+93% damage, no passive energy generation", + description: "if a mob has died in the last 5 seconds
2x damage, no passive energy generation", maxCount: 1, count: 0, frequency: 1, @@ -3032,7 +2983,7 @@ const tech = { }, { name: "waste heat recovery", - description: "if a mob has died in the last 5 seconds
generate 5% of max energy per second", + description: "if a mob has died in the last 5 seconds
generate 0.05x max energy per second", maxCount: 1, count: 0, frequency: 1, @@ -3051,7 +3002,7 @@ const tech = { { name: "recycling", descriptionFunction() { - return `if a mob has died in the last 5 seconds
recover 0.5% of max ${tech.isEnergyHealth ? "energy" : "health"} per second` + return `if a mob has died in the last 5 seconds
recover 0.005x max ${tech.isEnergyHealth ? "energy" : "health"} per second` }, description: "", maxCount: 1, @@ -3072,7 +3023,7 @@ const tech = { }, { name: "torpor", - description: "if a mob has not died in the last 5 seconds
+74% defense", + description: "if a mob has not died in the last 5 seconds
0.3x damage taken", maxCount: 1, count: 0, frequency: 1, @@ -3091,7 +3042,7 @@ const tech = { { name: "homeostasis", descriptionFunction() { - return `for each health below 100
+0.8% defense (${(100 * (Math.max(0, 1 - m.health) * 0.8)).toFixed(0)}%)` + return `0.8x damage taken per health below 100
(${(1 - Math.max(0, 1 - m.health) * 0.8).toFixed(2)}x)` }, maxCount: 1, count: 0, @@ -3111,7 +3062,7 @@ const tech = { { name: "negative feedback", descriptionFunction() { - 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)}%)` + return `1.007x damage per ${tech.isEnergyHealth ? "energy" : "health"} below 100
(${(1 + 0.7 * Math.max(0, 1 - (tech.isEnergyHealth ? m.energy : m.health))).toFixed(2)}x)` }, maxCount: 1, count: 0, @@ -3131,10 +3082,8 @@ const tech = { { name: "Zenos paradox", descriptionFunction() { - return `+85% defense
–5% of current ${tech.isEnergyHealth ? "energy" : "health"} every 5 seconds` + return `0.15x damage taken
–5% of current ${tech.isEnergyHealth ? "energy" : "health"} every 5 seconds` }, - // description: "+85% defense
–5% of current health every 5 seconds", - // description: "every 5 seconds remove 1/10 of your health
reduce defense by 90%", maxCount: 1, count: 0, frequency: 1, @@ -3153,9 +3102,8 @@ const tech = { { name: "antiscience", descriptionFunction() { - return `–10 ${tech.isEnergyHealth ? "energy" : "health"} after picking up a tech
+66% damage` + return `–10 ${tech.isEnergyHealth ? "energy" : "health"} after picking up a tech
1.7x damage` }, - // description: "+66% damage
–10 health after picking up a tech", maxCount: 1, count: 0, frequency: 1, @@ -3164,7 +3112,7 @@ const tech = { return true }, requires: "", - damage: 1.66, + damage: 1.7, effect() { tech.damage *= this.damage tech.isTechDamage = true; @@ -3177,7 +3125,7 @@ const tech = { { name: "ergodicity", descriptionFunction() { - return `${powerUps.orb.heal()} have -50% effect
+66% damage` + return `0.50x healing from ${powerUps.orb.heal()}
1.7x damage` }, maxCount: 1, count: 0, @@ -3187,7 +3135,7 @@ const tech = { return true }, requires: "", - damage: 1.66, + damage: 1.7, effect() { tech.damage *= this.damage tech.isHalfHeals = true; @@ -3215,7 +3163,7 @@ const tech = { }, { name: "fluoroantimonic acid", - description: "if your health is above 100
+35% damage", + description: "if your health is above 100
1.35x damage", maxCount: 1, count: 0, frequency: 2, @@ -3256,7 +3204,7 @@ const tech = { { name: "adiabatic healing", descriptionFunction() { - return `${powerUps.orb.heal()} have +100% effect
+4% JUNK to tech pool` + return `2x healing from ${powerUps.orb.heal()}
+4% JUNKtech chance` }, maxCount: 3, count: 0, @@ -3299,7 +3247,7 @@ const tech = { { name: "quenching", descriptionFunction() { - return `${powerUps.orb.heal()} over healing results in 200% health loss
and 200% max health increase` + return `${powerUps.orb.heal()} overhealing results in 2x health loss
and 2x max health increase` }, maxCount: 1, count: 0, @@ -3345,7 +3293,7 @@ const tech = { { name: "accretion disk", descriptionFunction() { - return `+5% damage (${5 * powerUp.length}%)
for each power up that exists on this level` + return `1.05x damage (${(1 + 0.05 * powerUp.length).toFixed(2)}x)
for each power up that exists on this level` }, maxCount: 1, count: 0, @@ -3366,7 +3314,7 @@ const tech = { { name: "self-assembly", descriptionFunction() { - return `at the start of each level
for every 25% missing ${tech.isEnergyHealth ? "energy" : "health"} spawn ${powerUps.orb.heal()}` + return `at the start of each level
for every 25 missing ${tech.isEnergyHealth ? "energy" : "health"} spawn ${powerUps.orb.heal()}` }, maxCount: 1, count: 0, @@ -3460,7 +3408,7 @@ const tech = { }, { name: "strong anthropic principle", - description: "after anthropic principle prevents your death
+137.03599% damage for that level", + description: "after anthropic principle prevents your death
2.71828x damage for that level", maxCount: 1, count: 0, frequency: 3, @@ -3478,7 +3426,7 @@ const tech = { }, { name: "quantum immortality", - description: "+30% defense
after dying, continue in an alternate reality", + description: "0.7x damage taken
after dying, continue in an alternate reality", maxCount: 1, count: 0, frequency: 1, @@ -3537,6 +3485,30 @@ const tech = { if (this.count > 0) powerUps.research.changeRerolls(-this.bonusResearch) } }, + { + name: "peer review", + description: `+3% JUNKtech chance
after you research gain 1.04x damage`, + maxCount: 1, + count: 0, + frequency: 1, + frequencyDefault: 1, + allowed() { + return (powerUps.research.count > 1 || build.isExperimentSelection) && !tech.isSuperDeterminism + }, + requires: "at least 2 research, not superdeterminism", + effect() { + tech.isResearchDamage = true; + this.refundAmount += tech.addJunkTechToPool(0.03) + }, + refundAmount: 0, + remove() { + tech.isResearchDamage = false; + if (this.count > 0 && this.refundAmount > 0) { + tech.removeJunkTechFromPool(this.refundAmount) + this.refundAmount = 0 + } + } + }, { name: "decoherence", description: `tech options you don't choose won't reoccur
spawn ${powerUps.orb.research(7)}`, @@ -3567,7 +3539,7 @@ const tech = { }, { name: "renormalization", - description: `47% chance to spawn ${powerUps.orb.research(1)} after consuming ${powerUps.orb.research(1)}
+5% JUNK to tech pool`, + description: `47% chance to spawn ${powerUps.orb.research(1)} after consuming ${powerUps.orb.research(1)}
+5% JUNKtech chance`, maxCount: 1, count: 0, frequency: 2, @@ -3592,10 +3564,7 @@ const tech = { }, { name: "perturbation theory", - description: `if you have no ${powerUps.orb.research(1)} in your inventory
+60% fire rate`, - // descriptionFunction() { - // return `+40% damage, but -10% damage
for each ${powerUps.orb.research(1)} in your inventory (${40 - 10 * powerUps.research.count}% damage)` - // }, + description: `if you have no ${powerUps.orb.research(1)} in your inventory
1.6x fire rate`, maxCount: 1, count: 0, frequency: 1, @@ -3635,9 +3604,8 @@ const tech = { }, { name: "Bayesian statistics", - // description: `for each ${powerUps.orb.research(1)} in your inventory
+3.8% damage`, descriptionFunction() { - return `spawn ${powerUps.orb.research(this.bonusResearch)}
+3% damage per ${powerUps.orb.research(1)} in your inventory (${(3 * powerUps.research.count).toFixed(0)}%)` + return `spawn ${powerUps.orb.research(this.bonusResearch)}
1.03x damage per ${powerUps.orb.research(1)} in your inventory (${(1.03 * powerUps.research.count).toFixed(2)}x)` }, maxCount: 1, count: 0, @@ -3743,15 +3711,15 @@ const tech = { }, { name: "pseudoscience", - description: "when selecting a power up, research 3 times
for free, but add 1-3% JUNK to the tech pool", + description: "research 3 times
for free, but
add 1% JUNKtech chance each time", maxCount: 1, count: 0, frequency: 1, frequencyDefault: 1, allowed() { - return !tech.isResearchReality && !tech.isSuperDeterminism //tech.isResearchBoss || tech.isMetaAnalysis || tech.isRerollBots || tech.isDeathAvoid || tech.isRerollDamage || build.isExperimentSelection + return !tech.isResearchReality && !tech.isSuperDeterminism }, - requires: "not Ψ(t) collapse, superdeterminism", //"abiogenesis, meta-analysis, bot fabrication, anthropic principle, or Bayesian statistics, not Ψ(t) collapse", + requires: "not Ψ(t) collapse, superdeterminism", effect() { tech.isJunkResearch = true; }, @@ -3800,8 +3768,7 @@ const tech = { }, { name: "emergence", - description: "tech, fields, and guns have +1 choice
+8% damage", - // description: "tech, fields, and guns have +2 choices
+3% JUNK to tech pool", + description: "tech, fields, and guns have +1 choice
1.08x damage", maxCount: 9, count: 0, frequency: 1, @@ -3828,7 +3795,7 @@ const tech = { { name: "path integral", link: `path integral`, - description: "your next tech choice has all possible options
+5% JUNK to tech pool", + description: "your next tech choice has all possible options
+5% JUNKtech chance", maxCount: 1, count: 0, frequency: 1, @@ -3900,8 +3867,7 @@ const tech = { { name: "technical debt", descriptionFunction() { - // return `+300% damage –15% damage
for each tech you have learned (+${(Math.floor(100 * (Math.min(Math.pow(0.85, tech.totalCount - 20), 4 - 0.15 * tech.totalCount))) - 100)}%)` - return `+300% damage –15% damage
for each tech you have learned (${(tech.totalCount > 20 ? 100 * (Math.pow(0.85, tech.totalCount - 20) - 1) : 100 * (3 - 0.15 * tech.totalCount)).toFixed(0)}%)` + return `4x damage but lose 0.15x damage
for each tech you have learned (${(tech.totalCount > 20 ? (Math.pow(0.85, tech.totalCount - 20)) : (4 - 0.15 * tech.totalCount)).toFixed(2)}x)` }, maxCount: 1, count: 0, @@ -3918,25 +3884,6 @@ const tech = { tech.isTechDebt = false; } }, - // { - // name: "abiogenesis", - // // description: `use ${powerUps.orb.research(4)}(or 49% JUNK to the tech pool if you can't) to add a 2nd boss to each level`, - // description: `as a level begins spawn a 2nd boss using ${powerUps.orb.research(3)}
(+49% JUNK to the tech pool if you can't pay)
`, - // maxCount: 1, - // count: 0, - // frequency: 2, - // frequencyDefault: 2, - // allowed() { - // return (build.isExperimentSelection || powerUps.research.count > 2) && !tech.isDuplicateMobs - // }, - // requires: "at least 3 research, not parthenogenesis", - // effect() { - // tech.isResearchBoss = true; - // }, - // remove() { - // tech.isResearchBoss = false; - // } - // }, { name: "meta-analysis", description: `if you choose a JUNKtech you instead get a
random normal tech and spawn ${powerUps.orb.research(2)}`, @@ -3957,7 +3904,7 @@ const tech = { }, { name: "dark patterns", - description: "+22% damage
+11% JUNK to tech pool", + description: "1.3x damage
+17% JUNKtech chance", maxCount: 9, count: 0, frequency: 1, @@ -3966,10 +3913,10 @@ const tech = { return true }, requires: "", - damage: 1.22, + damage: 1.3, effect() { tech.damage *= this.damage - this.refundAmount += tech.addJunkTechToPool(0.11) + this.refundAmount += tech.addJunkTechToPool(0.17) }, refundAmount: 0, remove() { @@ -3982,7 +3929,7 @@ const tech = { { name: "junk DNA", descriptionFunction() { - return `increase damage by twice the
JUNK tech pool percent (${(200 * tech.junkChance).toFixed(0)}%)` + return `increase damage by twice the
JUNKtech chance (${(1 + 2 * tech.junkChance).toFixed(2)}x)` }, maxCount: 1, count: 0, @@ -4002,7 +3949,7 @@ const tech = { { name: "exciton", descriptionFunction() { - return `after mobs die they have a 14% chance to
spawn ${powerUps.orb.boost(1)} that give +${(powerUps.boost.damage * 100).toFixed(0)}% damage for ${(powerUps.boost.duration / 60).toFixed(0)} seconds
` + return `after mobs die they have a 14% chance to
spawn ${powerUps.orb.boost(1)} that give ${(1 + powerUps.boost.damage).toFixed(2)}x damage for ${(powerUps.boost.duration / 60).toFixed(0)} seconds
` }, maxCount: 1, count: 0, @@ -4020,7 +3967,7 @@ const tech = { { name: "band gap", descriptionFunction() { - return `${powerUps.orb.boost(1)} give +77% damage
but their duration is reduced by 1 second` + return `${powerUps.orb.boost(1)} give 1.77x damage
but their duration is reduced by 1 second` }, maxCount: 9, count: 1, @@ -4041,7 +3988,7 @@ const tech = { }, { name: "eternalism", - description: "+24% damage
time can't be paused (time can be dilated)", + description: "1.25x damage
time can't be paused (time can be dilated)", maxCount: 1, count: 0, frequency: 1, @@ -4050,7 +3997,7 @@ const tech = { return !tech.isPauseSwitchField && !tech.isPauseEjectTech && !tech.isWormHolePause }, requires: "not unified field theory, paradigm shift, invariant", - damage: 1.24, + damage: 1.25, effect() { tech.damage *= this.damage tech.isNoDraftPause = true @@ -4322,7 +4269,7 @@ const tech = { }, { name: "replication", - description: "+10% chance to duplicate spawned power ups
+22% JUNK to tech pool", + description: "+10% chance to duplicate spawned power ups
+22% JUNKtech chance", maxCount: 9, count: 0, frequency: 1, @@ -4391,7 +4338,7 @@ const tech = { }, { name: "correlated damage", - description: "duplication increases damage", + description: "duplication increases damage by the same percent", maxCount: 1, count: 0, frequency: 1, @@ -4449,7 +4396,7 @@ const tech = { { name: "strange attractor", descriptionFunction() { - return `+7% damage
removing this increases duplication by +11%` + return `1.1x damage
removing this increases duplication by +11%` }, maxCount: 1, count: 0, @@ -4460,7 +4407,7 @@ const tech = { return true }, requires: "", - damage: 1.07, + damage: 1.1, effect() { tech.damage *= this.damage }, @@ -4476,7 +4423,7 @@ const tech = { { name: "strange loop", // description: `+11% damage
removing this doubles it's damage if you take it again`, - description: `+9% damage
removing this gives strange attractor and null hypothesis`, + description: `1.1x damage
removing this gives strange attractor and null hypothesis`, maxCount: 1, count: 0, frequency: 1, @@ -4486,7 +4433,7 @@ const tech = { return true }, requires: "", - damage: 1.09, + damage: 1.1, effect() { tech.damage *= this.damage }, @@ -4503,7 +4450,7 @@ const tech = { }, { name: "null hypothesis", - description: `+8% damage
removing this spawns ${powerUps.orb.research(15)}`, + description: `1.1x damage
removing this spawns ${powerUps.orb.research(15)}`, maxCount: 1, count: 0, frequency: 1, @@ -4513,7 +4460,7 @@ const tech = { return true }, requires: "", - damage: 1.08, + damage: 1.1, effect() { tech.damage *= this.damage }, @@ -4528,7 +4475,7 @@ const tech = { { name: "martingale", descriptionFunction() { - return `+${(100 * this.damage).toFixed(0)}% damage. After removing this there is a 50%
chance to get it back with double its damage
` + return `${(1 + this.damage).toFixed(2)}x damage. After removing this there is a 50%
chance to get it back with double its damage
` }, maxCount: 1, count: 0, @@ -4591,7 +4538,7 @@ const tech = { { name: "Occams razor", descriptionFunction() { - return `randomly remove half your tech
for each removed +${this.damagePerRemoved * 100}% damage (~${(this.count === 0) ? this.damagePerRemoved * 50 * tech.totalCount : this.damage * 100}%)` + return `randomly remove half your tech
for each removed ${(1 + this.damagePerRemoved).toFixed(2)}x damage (~${((this.count === 0) ? 1 + this.damagePerRemoved * 0.5 * tech.totalCount : this.damage).toFixed(2)}x)` }, maxCount: 1, count: 0, @@ -4741,7 +4688,7 @@ const tech = { }, { name: "nanowires", - description: `needles tunnel through blocks and map
+20% needle damage`, + description: `needles tunnel through blocks and map
1.2x needle damage`, isGunTech: true, maxCount: 1, count: 0, @@ -4871,7 +4818,7 @@ const tech = { }, { name: "pneumatic actuator", - description: "nail gun takes no time to ramp up
to its fastest fire rate", + description: "nail gun takes no time to ramp up
to its fastest fire rate", isGunTech: true, maxCount: 1, count: 0, @@ -4931,7 +4878,7 @@ const tech = { }, { name: "rotary cannon", - description: "nail gun has increased muzzle speed,
maximum fire rate, accuracy, and recoil", + description: "nail gun has increased muzzle speed,
maximum fire rate, accuracy, and recoil", isGunTech: true, maxCount: 1, count: 0, @@ -4958,7 +4905,7 @@ const tech = { }, { name: "gauge", - description: `rivets, needles, super balls, and nails
have +30% mass and physical damage`, + description: `rivets, needles, super balls, and nails
have 1.3x mass and physical damage`, isGunTech: true, maxCount: 9, count: 0, @@ -4997,7 +4944,7 @@ const tech = { { name: "irradiated nails", link: `irradiated nails`, - description: "nails, needles, and rivets are radioactive
+90% radioactive damage over 3 seconds", + description: "nails, needles, and rivets are radioactive
1.9x radioactive damage over 3 seconds", isGunTech: true, maxCount: 1, count: 0, @@ -5037,7 +4984,7 @@ const tech = { { name: "1s half-life", link: `1s half-life`, - description: "nails, needles, rivets are made of lithium-8
+300% radioactive damage for 1 second
", + description: "nails, needles, rivets are made of lithium-8
4x radioactive damage for 1 second
", isGunTech: true, maxCount: 1, count: 0, @@ -5096,7 +5043,7 @@ const tech = { }, { name: "Newtons 3rd law", - description: "+66% shotgun fire rate and recoil", + description: "1.7x shotgun fire rate and recoil", isGunTech: true, maxCount: 1, count: 0, @@ -5116,7 +5063,7 @@ const tech = { { name: "Noether violation", link: `Noether violation`, - description: "+50% shotgun damage
shotgun recoil is reversed", + description: "1.5x shotgun damage
shotgun recoil is reversed", isGunTech: true, maxCount: 1, count: 0, @@ -5135,7 +5082,7 @@ const tech = { }, { name: "repeater", - description: "shotgun immediately fires again for no ammo
-50% shotgun fire rate", + description: "shotgun immediately fires again for no ammo
reduced 0.5x shotgun fire rate", isGunTech: true, maxCount: 9, count: 0, @@ -5328,7 +5275,7 @@ const tech = { }, { name: "rebound", - description: `after they collide with a mob, super balls
gain speed, duration, and +33% damage`, + description: `after they collide with a mob, super balls
gain speed, duration, and 1.3x damage`, isGunTech: true, maxCount: 1, count: 0, @@ -5347,7 +5294,7 @@ const tech = { }, { name: "Zectron", - description: `+90% super ball density and damage, but
after colliding with super balls -25% energy`, + description: `1.9x super ball density and damage, but
after colliding with super balls -25% energy`, isGunTech: true, maxCount: 1, count: 0, @@ -5458,7 +5405,7 @@ const tech = { }, { name: "phase velocity", - description: "wave particles propagate faster as solids
+40% wave damage", + description: "wave particles propagate faster as solids
1.4x wave damage", isGunTech: true, maxCount: 1, count: 0, @@ -5477,7 +5424,7 @@ const tech = { }, { name: "amplitude", - description: "+37% wave damage
+37% wave particle amplitude", + description: "1.4x wave damage
1.4x wave bullet amplitude", isGunTech: true, maxCount: 3, count: 0, @@ -5489,7 +5436,7 @@ const tech = { requires: "wave", effect() { tech.waveFrequency *= 0.66 - tech.wavePacketDamage *= 1.37 + tech.wavePacketDamage *= 1.4 }, remove() { tech.waveFrequency = 0.2 @@ -5498,7 +5445,7 @@ const tech = { }, { name: "propagation", - description: "–25% wave packet propagation speed
+41% wave damage", + description: "0.75x wave propagation speed
1.4x wave damage", isGunTech: true, maxCount: 9, count: 0, @@ -5510,7 +5457,7 @@ const tech = { requires: "wave", effect() { tech.waveBeamSpeed *= 0.75; - tech.waveBeamDamage += 0.27 * 0.41 //this sets base wave damage + tech.waveBeamDamage += 0.27 * 0.4 //this sets base wave damage }, remove() { tech.waveBeamSpeed = 11; @@ -5519,7 +5466,7 @@ const tech = { }, { name: "bound state", - description: "wave packets reflect backwards 2 times
–33% range", + description: "wave packets reflect backwards 2 times
0.66x wave range", isGunTech: true, maxCount: 9, count: 0, @@ -5538,7 +5485,7 @@ const tech = { }, { name: "frequency", - description: `wave has unlimited ammo
-25% wave damage`, + description: `wave has unlimited ammo
0.75x wave damage`, isGunTech: true, maxCount: 1, count: 0, @@ -5604,7 +5551,7 @@ const tech = { }, { name: "isotropic", - description: "waves expand in all directions
–40% range and +50% damage", + description: "waves expand in all directions
0.6x range and 1.5x damage", isGunTech: true, maxCount: 1, count: 0, @@ -5663,7 +5610,7 @@ const tech = { }, { name: "cruise missile", - description: "+100% missile explosive damage, radius
–50% missile speed", + description: "2x missile explosive damage, radius
0.5x missile speed", isGunTech: true, maxCount: 1, count: 0, @@ -5682,7 +5629,7 @@ const tech = { }, { name: "ICBM", - description: "+75% missile explosive damage, radius
–50% missile speed", + description: "1.75x missile explosive damage, radius
0.5x missile speed", isGunTech: true, maxCount: 1, count: 0, @@ -5701,7 +5648,7 @@ const tech = { }, { name: "launch system", - description: `+500% missile gun fire rate
+20% missile ammo per ${powerUps.orb.ammo(1)}`, + description: `5x missile gun fire rate
1.2x missile ammo per ${powerUps.orb.ammo(1)}`, isGunTech: true, maxCount: 1, count: 0, @@ -5773,7 +5720,7 @@ const tech = { }, { name: "iridium-192", - description: "explosions release gamma radiation
+100% explosion damage over 4 seconds", + description: "explosions release gamma radiation
2x explosion damage over 4 seconds", isGunTech: true, maxCount: 1, count: 0, @@ -5811,7 +5758,7 @@ const tech = { }, { name: "ammonium nitrate", - description: "+24% explosive damage, radius", + description: "1.25x explosive damage, radius", isGunTech: true, maxCount: 9, count: 0, @@ -5822,7 +5769,7 @@ const tech = { }, requires: "an explosive damage source, not iridium-192", effect() { - tech.explosiveRadius += 0.24; + tech.explosiveRadius += 0.25; }, remove() { tech.explosiveRadius = 1; @@ -5830,7 +5777,7 @@ const tech = { }, { name: "nitroglycerin", - description: "+66% explosive damage
–33% explosive radius", + description: "1.66x explosive damage
0.66x smaller explosive radius", isGunTech: true, maxCount: 1, count: 0, @@ -5849,7 +5796,7 @@ const tech = { }, { name: "acetone peroxide", - description: "+70% explosive radius
–33% explosive defense", + description: "1.7x explosive radius
1.4x explosive damage taken", isGunTech: true, maxCount: 1, count: 0, @@ -5909,29 +5856,9 @@ const tech = { if (this.count > 0) powerUps.research.changeRerolls(3) } }, - // { - // name: "electric armor", - // // description: "explosions do no defense
while your energy is above 98%", - // description: "instead of causing health loss, explosions
drain 12 energy and have more knockback", - // isGunTech: true, - // maxCount: 1, - // count: 0, - // frequency: 2, - // frequencyDefault: 2, - // allowed() { - // return !tech.isSmartRadius && !tech.isExplodeRadio && tech.hasExplosiveDamageCheck() - // }, - // requires: "an explosive damage source, not iridium-192", - // effect() { - // tech.isImmuneExplosion = true; - // }, - // remove() { - // tech.isImmuneExplosion = false; - // } - // }, { name: "MIRV", - description: "fire +1 missile or grenade per shot
–12% explosion damage and radius", + description: "fire +1 missile or grenade per shot
0.88x explosion damage and radius", isGunTech: true, maxCount: 9, count: 0, @@ -5994,7 +5921,7 @@ const tech = { }, { name: "chain reaction", - description: "+33% grenade radius and damage
blocks caught in explosions also explode", + description: "1.33x grenade radius and damage
blocks caught in explosions also explode", isGunTech: true, maxCount: 1, count: 0, @@ -6091,7 +6018,7 @@ const tech = { }, { name: "vacuum permittivity", - description: "+20% radioactive range
objects in range of the bomb are slowed", + description: "1.2x radioactive range
objects in range of the bomb are slowed", isGunTech: true, maxCount: 1, count: 0, @@ -6129,7 +6056,7 @@ const tech = { }, { name: "nuclear transmutation", - description: "+47% radiation damage
nail, drone, neutron bomb, iridium, cosmic string, deflect", + description: "1.5x radiation damage
nail, drone, neutron bomb, iridium, cosmic string, deflect", isGunTech: true, maxCount: 9, count: 0, @@ -6140,7 +6067,7 @@ const tech = { }, requires: "radiation damage source", effect() { - tech.radioactiveDamage += 1.47 + tech.radioactiveDamage += 1.5 }, remove() { tech.radioactiveDamage = 1 @@ -6149,7 +6076,7 @@ const tech = { { name: "water shielding", link: `water shielding`, - description: "radioactive effects on you are reduced by 75%
neutron bomb, drones, explosions, slime", + description: "you are protected from 75% of radioactivity
neutron bomb, drones, explosions, slime", isGunTech: true, maxCount: 1, count: 0, @@ -6168,7 +6095,7 @@ const tech = { }, { name: "ricochet", - description: "after nails hit a mob they rebound towards
a new mob with +180% damage per bounce", + description: "after nails hit a mob they rebound towards
a new mob with 2.8x damage per bounce", isGunTech: true, maxCount: 1, count: 0, @@ -6189,7 +6116,7 @@ const tech = { }, { name: "booby trap", - description: "50% chance to drop a mine from power ups
+30% JUNK to tech pool", + description: "50% chance to drop a mine from power ups
+30% JUNKtech chance", isGunTech: true, maxCount: 1, count: 0, @@ -6276,7 +6203,7 @@ const tech = { { name: "sentry", descriptionFunction() { - return `mines fire one ${b.guns[10].nameString()} at a time
mines fire 50% more ${b.guns[10].nameString('s')}` + return `mines fire one ${b.guns[10].nameString()} at a time
mines fire 1.5x more ${b.guns[10].nameString('s')}` }, isGunTech: true, maxCount: 1, @@ -6297,7 +6224,7 @@ const tech = { { name: "extended magazine", descriptionFunction() { - return `sentry mines fire 50% more ${b.guns[10].nameString('s')}` + return `sentry mines fire 1.5x more ${b.guns[10].nameString('s')}` }, isGunTech: true, maxCount: 9, @@ -6318,7 +6245,7 @@ const tech = { { name: "mycelial fragmentation", link: `mycelial fragmentation`, - description: "during their growth phase
+70% sporangium discharge", + description: "during their growth phase
1.7x sporangium discharge", isGunTech: true, maxCount: 1, count: 0, @@ -6359,7 +6286,7 @@ const tech = { }, { name: "colony", - description: "+50% sporangium discharge
40% chance to discharge something different", + description: "1.5x sporangium discharge
40% chance to discharge something different", link: `colony`, isGunTech: true, maxCount: 1, @@ -6380,7 +6307,7 @@ const tech = { { name: "cryodesiccation", descriptionFunction() { - return `+25% sporangium discharge
${b.guns[6].nameString('s')} freeze mobs for 1.5 second` + return `1.25x sporangium discharge
${b.guns[6].nameString('s')} freeze mobs for 1.5 second` }, // description: "+25% sporangium discharge
spores freeze mobs for 1.5 second", isGunTech: true, @@ -6402,9 +6329,8 @@ const tech = { { name: "flagella", descriptionFunction() { - return `+50% ${b.guns[6].nameString()} acceleration
if they can't find a target ${b.guns[6].nameString('s')} follow you` + return `2x ${b.guns[6].nameString()} acceleration
if they can't find a target ${b.guns[6].nameString('s')} follow you` }, - // description: "+50% spore acceleration
if they can't find a target spores follow you", isGunTech: true, maxCount: 1, count: 0, @@ -6421,38 +6347,11 @@ const tech = { tech.isSporeFollow = false } }, - // { - // name: "junk DNA", - // //increase damage by 10% for each JUNK tech percent in the tech pool, remove all JUNK tech, - // descriptionFunction() { return `+50% ${b.guns[6].nameString()} damage
+15% JUNK to tech pool` }, - // isGunTech: true, - // maxCount: 1, - // count: 0, - // frequency: 3, - // frequencyDefault: 3, - // allowed() { - // return tech.haveGunCheck("spores") || tech.sporesOnDeath > 0 || (m.fieldMode === 4 && simulation.molecularMode === 0) || tech.isSporeWorm || tech.isSporeFlea - // }, - // requires: "spores", - // effect() { - // tech.isSporeWorm = true - // this.refundAmount += tech.addJunkTechToPool(0.15) - // }, - // refundAmount: 0, - // remove() { - // tech.isSporeWorm = false - // if (this.count > 0 && this.refundAmount > 0) { - // tech.removeJunkTechFromPool(this.refundAmount) - // this.refundAmount = 0 - // } - // } - // }, { name: "mutualism", descriptionFunction() { - return `+200% ${b.guns[6].nameString()} damage
${b.guns[6].nameString('s')} borrow 1 health until they die` + return `3x ${b.guns[6].nameString()} damage
${b.guns[6].nameString('s')} borrow 1 health until they die` }, - // description: `+150% ${b.guns[6].name()} damage
spores borrow 0.5 health until they die`, isGunTech: true, maxCount: 1, count: 0, @@ -6529,7 +6428,7 @@ const tech = { }, { name: "K-selection", - description: "+37% worm and flea damage", + description: "1.37x worm and flea damage", isGunTech: true, maxCount: 3, count: 0, @@ -6570,7 +6469,7 @@ const tech = { { name: "reduced tolerances", link: `reduced tolerances`, - description: `+66% drones per ${powerUps.orb.ammo()} and energy
–40% drone duration`, + description: `1.7x drones per ${powerUps.orb.ammo()} and energy
0.6x drone duration`, isGunTech: true, maxCount: 3, count: 0, @@ -6582,7 +6481,7 @@ const tech = { requires: "drones, not irradiated drones", effect() { tech.droneCycleReduction = Math.pow(0.6, 1 + this.count) - tech.droneEnergyReduction = Math.pow(0.333, 1 + this.count) + tech.droneEnergyReduction = Math.pow(0.3, 1 + this.count) for (i = 0, len = b.guns.length; i < len; i++) { //find which gun if (b.guns[i].name === "drones") { const scale = Math.pow(3, this.count + 1) @@ -6639,7 +6538,7 @@ const tech = { }, { name: "brushless motor", - description: "drones rapidly rush towards their target
+33% drone collision damage", + description: "drones rapidly rush towards their target
1.33x drone collision damage", isGunTech: true, maxCount: 1, count: 0, @@ -6658,7 +6557,7 @@ const tech = { }, { name: "axial flux motor", - description: "+66% drones rush frequency
+44% drone collision damage", + description: "1.66x drones rush frequency
1.44x drone collision damage", isGunTech: true, maxCount: 1, count: 0, @@ -6678,7 +6577,7 @@ const tech = { { name: "irradiated drones", link: `irradiated drones`, - description: `the space around drones is irradiated
–75% drones per ${powerUps.orb.ammo()} and energy`, + description: `the space around drones is irradiated
0.25x drones per ${powerUps.orb.ammo()} and energy`, isGunTech: true, maxCount: 1, count: 0, @@ -6713,7 +6612,7 @@ const tech = { }, { name: "beta radiation", //"control rod ejection", - description: "–50% drone duration
+100% drone radiation damage", + description: "0.5x drone duration
2x drone radiation damage", isGunTech: true, maxCount: 1, count: 0, @@ -6733,7 +6632,7 @@ const tech = { { name: "orthocyclic winding", link: `orthocyclic winding`, - description: "+66% drone acceleration
+33% radiation damage", + description: "1.66x drone acceleration
1.33x radiation damage", isGunTech: true, maxCount: 1, count: 0, @@ -6853,7 +6752,7 @@ const tech = { }, { name: "uncertainty principle", - description: "foam, wave, and super ball positions are erratic
+53% foam, wave, and super ball damage", + description: "foam, wave, and super ball positions are erratic
1.5x foam, wave, and super ball damage", isGunTech: true, maxCount: 1, count: 0, @@ -6872,7 +6771,7 @@ const tech = { }, { name: "aerogel", - description: "–50% foam duration and foam bubbles float
+180% foam damage", + description: "foam bubbles float with 0.5x foam duration
2.8x foam damage", isGunTech: true, maxCount: 1, count: 0, @@ -6893,7 +6792,7 @@ const tech = { }, { name: "surface tension", - description: "+43% foam damage", + description: "1.4x foam damage", isGunTech: true, maxCount: 9, count: 0, @@ -6904,7 +6803,7 @@ const tech = { }, requires: "foam", effect() { - tech.foamDamage += 0.01 * 0.43 + tech.foamDamage += 0.01 * 0.4 }, remove() { tech.foamDamage = 0.01; @@ -6912,7 +6811,7 @@ const tech = { }, { name: "cavitation", - description: "25% chance to discharge a huge foam bubble
increase foam recoil by 100%", + description: "25% chance to discharge a huge foam bubble
2x foam gun recoil", isGunTech: true, maxCount: 1, count: 0, @@ -6933,7 +6832,7 @@ const tech = { }, { name: "foam fractionation", - description: "if you have below 300 ammo
+100% foam gun bubble size", + description: "if you have below 300 ammo
2x foam gun bubble size", isGunTech: true, maxCount: 1, count: 0, @@ -6952,7 +6851,7 @@ const tech = { }, { name: "ideal gas law", - description: `remove all current foam ammo
+1200% foam ammo per ${powerUps.orb.ammo(1)}`, + description: `remove all current foam ammo
12x foam ammo per ${powerUps.orb.ammo(1)}`, isGunTech: true, maxCount: 1, count: 0, @@ -7023,7 +6922,7 @@ const tech = { { name: "Bitter electromagnet", descriptionFunction() { - return `railgun charges +33% slower
+100% harpoon density and damage` + return `0.66x railgun charge rate
2x harpoon density and damage` }, isGunTech: true, maxCount: 3, @@ -7076,52 +6975,6 @@ const tech = { } } }, - // { - // name: "grappling hook", - // description: `harpoons attach to the map and pull you
your rope extends while holding fire`, - // isGunTech: true, - // maxCount: 1, - // count: 0, - // frequency: 2, - // frequencyDefault: 2, - // allowed() { - // return tech.haveGunCheck("harpoon") && !tech.isFilament && !tech.isHarpoonPowerUp && !tech.isRailGun && !tech.isFireMoveLock - // }, - // requires: "harpoon, not railgun, UHMWPE, induction furnace, Higgs mechanism", - // effect() { - // tech.isGrapple = true; - // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun - // if (b.guns[i].name === "harpoon") b.guns[i].chooseFireMethod() - // } - // }, - // remove() { - // if (tech.isGrapple) { - // tech.isGrapple = false; - // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun - // if (b.guns[i].name === "harpoon") b.guns[i].chooseFireMethod() - // } - // } - // } - // }, - // { - // name: "bulk modulus", - // description: `while grappling become invulnerable
drain energy`, - // isGunTech: true, - // maxCount: 1, - // count: 0, - // frequency: 2, - // frequencyDefault: 2, - // allowed() { - // return tech.haveGunCheck("harpoon") && !tech.isRailEnergy - // }, - // requires: "not alternator", - // effect() { - // tech.isImmuneGrapple = true; - // }, - // remove() { - // tech.isImmuneGrapple = false - // } - // }, { name: "alternator", description: "harpoon no longer uses any energy", @@ -7163,7 +7016,7 @@ const tech = { { name: "Bessemer process", descriptionFunction() { - return `+${(10 * Math.sqrt(b.guns[9].ammo)).toFixed(0)}% harpoon size and damage
(effect scales by 1/10 √ harpoon ammo)` + return `${(1 + 0.1 * Math.sqrt(b.guns[9].ammo)).toFixed(2)}x harpoon size and damage
(effect scales by 1/10 √ harpoon ammo)` }, isGunTech: true, maxCount: 1, @@ -7229,7 +7082,7 @@ const tech = { { name: "UHMWPE", descriptionFunction() { - return `+${(b.guns[9].ammo * 1.25).toFixed(0)}% harpoon rope length
(effect scales by 1/80 of harpoon ammo)` + return `${(1 + b.guns[9].ammo * 0.0125).toFixed(2)}x harpoon rope length
(effect scales by 1/80 of harpoon ammo)` }, isGunTech: true, maxCount: 1, @@ -7249,7 +7102,7 @@ const tech = { }, { name: "induction furnace", - description: "after using harpoon/grapple to collect power ups
+77% harpoon or grapple damage for 8 seconds", + description: "after using harpoon/grapple to collect power ups
1.8x harpoon or grapple damage for 8 seconds", isGunTech: true, maxCount: 1, count: 0, @@ -7269,7 +7122,7 @@ const tech = { }, { name: "brittle", - description: "+111% harpoon/grapple damage
to mobs at maximum health", + description: "2.2x harpoon/grapple damage
to mobs at maximum durability", isGunTech: true, maxCount: 1, count: 0, @@ -7289,7 +7142,7 @@ const tech = { { name: "quasiparticles", descriptionFunction() { - return `convert current and future ${powerUps.orb.ammo(1)} into ${powerUps.orb.boost(1)} which
give +${(powerUps.boost.damage * 100).toFixed(0)}% damage for ${(powerUps.boost.duration / 60).toFixed(0)} seconds` + return `convert current and future ${powerUps.orb.ammo(1)} into ${powerUps.orb.boost(1)}
that give ${(1 + powerUps.boost.damage).toFixed(2)}x damage for ${(powerUps.boost.duration / 60).toFixed(0)} seconds` }, isGunTech: true, maxCount: 1, @@ -7398,8 +7251,7 @@ const tech = { }, { name: "iridescence", - // description: "if a laser hits a mob at a low angle of illumination
+66% laser damage", - description: "if laser beams hit mobs near their center
+100% laser damage", + description: "if laser beams hit mobs near their center
2x laser damage", isGunTech: true, maxCount: 9, count: 0, @@ -7418,7 +7270,7 @@ const tech = { }, { name: "lens", - description: "+150% laser gun damage if it passes
through a revolving 90° arc circular lens", //π / 2
+ description: "2.5x laser gun damage if it passes
through a revolving 90° arc circular lens", //π / 2
isGunTech: true, maxCount: 1, count: 0, @@ -7443,7 +7295,7 @@ const tech = { }, { name: "compound lens", - description: "+40% laser lens damage
+25° lens arc", + description: "1.4x laser lens damage
+25° lens arc", isGunTech: true, maxCount: 9, count: 0, @@ -7507,7 +7359,7 @@ const tech = { { name: "diffuse beam", link: `diffuse beam`, - description: "laser gun beam is wider and doesn't reflect
+220% laser damage", + description: "laser gun beam is wider and doesn't reflect
3.2x laser damage", isGunTech: true, maxCount: 1, count: 0, @@ -7532,7 +7384,7 @@ const tech = { }, { name: "output coupler", - description: "+30% laser gun beam width
+30% laser damage", + description: "1.3x laser gun beam width
1.3x laser damage", isGunTech: true, maxCount: 9, count: 0, @@ -7557,7 +7409,7 @@ const tech = { }, { name: "slow light", - description: "laser gun beam is spread into your recent past
+300% total beam damage", + description: "laser gun beam is spread into your recent past
4x full beam damage", isGunTech: true, maxCount: 9, count: 0, @@ -7581,7 +7433,7 @@ const tech = { }, { name: "infrared diode", - description: "+60% laser energy efficiency
infrared light is outside visual perception", + description: "0.4x laser energy cost
infrared light is outside visual perception", isGunTech: true, maxCount: 1, count: 0, @@ -7604,7 +7456,7 @@ const tech = { }, { name: "dye laser", - description: "+25% laser energy efficiency
+25% laser damage", + description: "0.75x laser energy cost
1.25x laser damage", isGunTech: true, maxCount: 1, count: 0, @@ -7629,7 +7481,7 @@ const tech = { }, { name: "free-electron laser", - description: "–250% laser energy efficiency
+200% laser damage", + description: "3.5x laser energy cost
3x laser damage", isGunTech: true, maxCount: 1, count: 0, @@ -7640,8 +7492,8 @@ const tech = { }, requires: "laser, not pulse, infrared diode", effect() { - tech.laserDrain *= 1 + 2.5 //250% more drain - tech.laserDamage *= 1 + 2 //190% more damage + tech.laserDrain *= 1 + 2.5 + tech.laserDamage *= 1 + 2 tech.laserColor = "#83f" tech.laserColorAlpha = "rgba(136, 51, 255,0.5)" }, @@ -7681,7 +7533,7 @@ const tech = { //************************************************** { name: "spherical harmonics", - description: "+50% standing wave deflection efficiency
shield deflection radius holds it's max range", //standing wave oscillates in a 3rd dimension
+ description: "1.5x standing wave deflection energy efficiency
shield deflection radius holds it's max range", //standing wave oscillates in a 3rd dimension
isFieldTech: true, maxCount: 9, count: 0, @@ -7773,7 +7625,7 @@ const tech = { { name: "electronegativity", descriptionFunction() { - return `+0.23% damage per current stored energy
(+${(27 * m.maxEnergy).toFixed(0)}% damage at max energy)` + return `1.0023x damage per energy
(${(1 + 0.23 * m.maxEnergy).toFixed(2)}x damage at max energy)` }, // description: "+1% damage per 8 stored energy", isFieldTech: true, @@ -7813,7 +7665,7 @@ const tech = { }, { name: "cherenkov radiation", //deflecting and blocks - description: "bremsstrahlung's effects are radioactive
+250% damage over 3 seconds", + description: "bremsstrahlung's effects are radioactive
3.5x damage over 3 seconds", isFieldTech: true, maxCount: 1, count: 0, @@ -7893,7 +7745,7 @@ const tech = { }, { name: "Meissner effect", - description: "+55% perfect diamagnetism radius
+22° perfect diamagnetism circular arc", + description: "1.55x perfect diamagnetism radius
+22° perfect diamagnetism circular arc", isFieldTech: true, maxCount: 1, count: 0, @@ -7913,9 +7765,8 @@ const tech = { { name: "radiative equilibrium", descriptionFunction() { - return `after losing ${tech.isEnergyHealth ? "energy" : "health"}
+200% damage for 8 seconds` + return `after losing ${tech.isEnergyHealth ? "energy" : "health"}
3x damage for 8 seconds` }, - // description: `after losing ${tech.isEnergyHealth ? "energy" : "health"}
+200% damage for 8 seconds`, isFieldTech: true, maxCount: 1, count: 0, @@ -7935,10 +7786,8 @@ const tech = { { name: "dynamic equilibrium", descriptionFunction() { - // return `increase damage by your defense and
5% of your last ${tech.isEnergyHealth ? "energy" : "health"} loss   (+${(100 * Math.max(5, tech.lastHitDamage) * m.lastHit * (2 - m.defense())).toFixed(0)}% damage)` - return `increase damage by your last ${tech.isEnergyHealth ? "energy" : "health"} loss
scales with defense   (+${(100 * Math.max(5, tech.lastHitDamage) * m.lastHit * (2 - m.defense())).toFixed(0)}% damage)` - }, // = +${10*m.defense()}% - // descriptionFunction() { return `increase damage by your last ${tech.isEnergyHealth ? "energy" : "health"} loss
(${(tech.lastHitDamage).toFixed(0)}%)(${(100*m.lastHit).toFixed(0)} ${tech.isEnergyHealth ? "energy" : "health"})(${2 - m.defense()} defense) = ${(100*tech.lastHitDamage * m.lastHit * (2 - m.defense())).toFixed(0)}% damage ` }, // = +${10*m.defense()}% + return `increase damage by your last ${tech.isEnergyHealth ? "energy" : "health"} loss
(${(1 + tech.lastHitDamage * m.lastHit).toFixed(2)}x damage)` + }, isFieldTech: true, maxCount: 9, count: 0, @@ -7949,7 +7798,7 @@ const tech = { }, requires: "negative mass, pilot wave", effect() { - tech.lastHitDamage += 4; + tech.lastHitDamage += 5; }, remove() { tech.lastHitDamage = 0; @@ -7957,7 +7806,7 @@ const tech = { }, { name: "neutronium", - description: `move and jump 20% slower
if your field is active +95% defense`, + description: `move and jump 20% slower
if your field is active 0.05x damage taken`, isFieldTech: true, maxCount: 1, count: 0, @@ -7985,7 +7834,7 @@ const tech = { }, { name: "aerostat", - description: `+100% damage while off the ground
-15% damage while on the ground`, + description: `2x damage while off the ground
0.85x damage while on the ground`, isFieldTech: true, maxCount: 1, count: 0, @@ -8250,7 +8099,7 @@ const tech = { // }, { name: "additive manufacturing", - description: "hold crouch and use your field to print a block
with +80% density, damage, and launch speed", + description: "hold crouch and use your field to print a block
with 1.8x density, damage, and launch speed", // description: "simultaneously fire and activate your field to make
molecular assembler print a throwable block
+80% block throwing speed", isFieldTech: true, maxCount: 1, @@ -8310,7 +8159,7 @@ const tech = { }, { name: "combinatorial optimization", - description: "+35% damage
–35% fire rate", + description: "1.35x damage
0.65x fire rate", isFieldTech: true, maxCount: 1, count: 0, @@ -8353,7 +8202,7 @@ const tech = { }, { name: "degenerate matter", - description: "if your field is active
+88% defense", + description: "if your field is active
0.1x damage taken", isFieldTech: true, maxCount: 1, count: 0, @@ -8416,7 +8265,7 @@ const tech = { { name: "plasma jet", link: `plasma jet`, - description: `use ${powerUps.orb.research(2)}
+50% plasma torch range`, + description: `use ${powerUps.orb.research(2)}
1.5x plasma torch range`, isFieldTech: true, maxCount: 1, count: 0, @@ -8439,7 +8288,7 @@ const tech = { }, { name: "extruder", - description: "extrude a thin hot wire of plasma
increases damage and energy drain", + description: "extrude a thin hot wire of plasma
increases damage and energy cost", isFieldTech: true, maxCount: 1, count: 0, @@ -8479,7 +8328,7 @@ const tech = { }, { name: "plasma ball", - description: "grow an expanding ball of plasma
increases damage and energy drain", + description: "grow an expanding ball of plasma
increases damage and energy cost", isFieldTech: true, maxCount: 1, count: 0, @@ -8541,7 +8390,7 @@ const tech = { }, { name: "frame-dragging", //"non-inertial frame", - description: "when not moving time dilation stops time
+33% defense", + description: "when not moving time dilation stops time
0.6x damage taken", isFieldTech: true, maxCount: 1, count: 0, @@ -8562,7 +8411,7 @@ const tech = { }, { name: "Lorentz transformation", - description: `use ${powerUps.orb.research(3)}
+50% movement, jumping, and fire rate`, + description: `use ${powerUps.orb.research(3)}
1.5x movement, jumping, and fire rate`, isFieldTech: true, maxCount: 1, count: 0, @@ -8589,9 +8438,8 @@ const tech = { }, { name: "time crystals", - // description: "+150% passive energy generation
${}", descriptionFunction() { - return `+150% passive energy generation
(+${(150 * m.fieldRegen * 60).toFixed(1)} energy per second)` + return `2.5x passive energy generation
(+${(150 * m.fieldRegen * 60).toFixed(1)} energy per second)` }, isFieldTech: true, maxCount: 1, @@ -8606,20 +8454,19 @@ const tech = { tech.isTimeCrystals = true m.setFieldRegen() this.descriptionFunction = function () { - return `+150% passive energy generation
(+${(60 * m.fieldRegen * 60).toFixed(1)} energy per second)` + return `2.5x passive energy generation
(+${(60 * m.fieldRegen * 60).toFixed(1)} energy per second)` } }, remove() { tech.isTimeCrystals = false m.setFieldRegen() this.descriptionFunction = function () { - return `+150% passive energy generation
(+${(150 * m.fieldRegen * 60).toFixed(1)} energy per second)` + return `2.5x passive energy generation
(+${(150 * m.fieldRegen * 60).toFixed(1)} energy per second)` } } }, { name: "no-cloning theorem", - // descriptionFunction() { return `+45% chance to duplicate spawned power ups
after a mob dies –2% duplication (${tech.duplicationChance()})` }, description: `+40% chance to duplicate spawned power ups
after a mob dies –1% duplication`, isFieldTech: true, maxCount: 1, @@ -8645,9 +8492,6 @@ const tech = { descriptionFunction() { return `for each mob left alive after you exit a level
there is a 25% chance to spawn a random power up` }, - // descriptionFunction() { - // return `for each mob left alive after you exit a level
` - // }, isFieldTech: true, maxCount: 1, count: 0, @@ -8750,7 +8594,7 @@ const tech = { }, { name: "topological defect", - description: "+111% damage
to mobs at maximum health", + description: "2.1x damage
to mobs at maximum durability", isFieldTech: true, maxCount: 1, count: 0, @@ -8767,25 +8611,6 @@ const tech = { tech.isMobFullHealthCloak = false } }, - // { - // name: "ambush", - // description: "metamaterial cloaking field damage effect
is increased from 333% to 555%", - // isFieldTech: true, - // maxCount: 1, - // count: 0, - // frequency: 2, - // frequencyDefault: 2, - // allowed() { - // return m.fieldMode === 7 - // }, - // requires: "metamaterial cloaking", - // effect() { - // tech.sneakAttackDmg = 6.55 //555% + 100% - // }, - // remove() { - // tech.sneakAttackDmg = 4.33 //333% + 100% - // } - // }, { name: "WIMPs", description: `at the end of each level spawn ${powerUps.orb.research(4)}
and a dangerous particle that slowly chases you`, @@ -8939,7 +8764,7 @@ const tech = { }, { name: "charmed baryons", - description: `–33% movement and jumping
wormholes drain zero energy`, + description: `0.66x movement and jumping
wormholes cost zero energy`, isFieldTech: true, maxCount: 1, count: 0, @@ -8967,7 +8792,7 @@ const tech = { }, { name: "affine connection", - description: "wormholes can tunnel through anything
for +200% energy drain", + description: "wormholes can tunnel through anything
for 2x energy cost", isFieldTech: true, maxCount: 1, count: 0, @@ -9025,7 +8850,7 @@ const tech = { }, { name: "reel", - description: "+400% block collision damage
up to +100 energy after reeling in blocks", + description: "5x block collision damage
up to +100 energy after reeling in blocks", isFieldTech: true, maxCount: 1, count: 0, @@ -9306,7 +9131,7 @@ const tech = { const loop = () => { if ((simulation.isChoosing) && m.alive && !build.isExperimentSelection) { const dmg = Math.floor(27 * Math.random()) * 0.01 - this.text = `+${(dmg * 100).toFixed(0).padStart(2, '0')}% damage` + this.text = `+${(1 + dmg).toFixed(2).padStart(2, '0')}x damage` this.damage = 1 + dmg if (document.getElementById(`damage-JUNK-id${this.id}`)) document.getElementById(`damage-JUNK-id${this.id}`).innerHTML = this.text setTimeout(() => { @@ -9362,7 +9187,7 @@ const tech = { count += 4.5 const waves = 2 * Math.sin(count * 0.0133) + Math.sin(count * 0.013) + 0.5 * Math.sin(count * 0.031) + 0.33 * Math.sin(count * 0.03) this.spawnCount = Math.floor(100 * Math.abs(waves)) - this.text = `spawn ${this.spawnCount.toLocaleString(undefined, { minimumIntegerDigits: 3 })} ${powerUps.orb.boost(1)}
that give +${(powerUps.boost.damage * 100).toFixed(0)}% damage for ${(powerUps.boost.duration / 60).toFixed(0)} seconds` + this.text = `spawn ${this.spawnCount.toLocaleString(undefined, { minimumIntegerDigits: 3 })} ${powerUps.orb.boost(1)}
that give ${(1 + powerUps.boost.damage).toFixed(2)}% damage for ${(powerUps.boost.duration / 60).toFixed(0)} seconds` if (document.getElementById(`boost-JUNK-id${this.id}`)) document.getElementById(`boost-JUNK-id${this.id}`).innerHTML = this.text setTimeout(() => { loop() @@ -9378,7 +9203,7 @@ const tech = { }, { name: "placebo", - description: "+777% damage
+777% defense", + description: "7.77x damage", maxCount: 1, count: 0, frequency: 0, @@ -10865,7 +10690,7 @@ const tech = { }, { name: "expert system", - description: "spawn a tech power up
+64% JUNK to tech pool", + description: "spawn a tech power up
+64% JUNKtech chance", maxCount: 9, count: 0, frequency: 0, @@ -11177,7 +11002,7 @@ const tech = { }, { name: "circular symmetry", - description: "turning the ship rotates the universe instead
+200% damage", + description: "turning the ship rotates the universe instead
2x damage", maxCount: 1, count: 0, frequency: 0, @@ -11603,7 +11428,7 @@ const tech = { }, { name: "beforeunload", - description: "75% of the time when you attempt to exit n-gon
you are prompted to cancel or continue.
Each time you cancel gain +25% damage.", + description: "75% of the time when you attempt to exit n-gon
you are prompted to cancel or continue.
Each time you cancel gain 1.25x damage.", maxCount: 1, count: 0, frequency: 1, @@ -11914,7 +11739,6 @@ const tech = { isBlockStun: null, isStunField: null, isHarmDamage: null, - energyRegen: null, isVacuumBomb: null, renormalization: null, fragments: null, @@ -12096,7 +11920,6 @@ const tech = { isBulletTeleport: null, isResearchBoss: null, isJunkResearch: null, - junkResearchNumber: null, laserColor: null, laserColorAlpha: null, isLongitudinal: null, @@ -12219,4 +12042,5 @@ const tech = { isDamageCooldownTime: null, isPowerUpDamage: null, isExitPrompt: null, + isResearchDamage: null, } \ No newline at end of file diff --git a/todo.txt b/todo.txt index d852bbd..234d30a 100644 --- a/todo.txt +++ b/todo.txt @@ -1,9 +1,19 @@ ******************************************************** NEXT PATCH ************************************************** -bug fix for experiment mode +text rework + most numbers converted from "+50%" to "1.5x" + renamed defense -> damage taken + adjusted about 20 tech to round down or up their values to less decimals + for a few I added research cost or JUNK chance to balance rounding + +new community map soft by Richard0820 +tech: peer review - gain +damage each time you research +self-assembly scales with health not health percent +matter.js engine reverted back to 0.18 (to fix an issue with time dilation) *********************************************************** TODO ***************************************************** +tech - destroys a random tech each new level and gains +damage each time List of ways to break the game CPT + high energy regen @@ -1085,7 +1095,6 @@ possible names for tech holographic - 2-D surface can predict the 3-D space behind it? I think 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