diff --git a/.DS_Store b/.DS_Store
index dc334b0..812769a 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/js/bullet.js b/js/bullet.js
index e680101..627b9e5 100644
--- a/js/bullet.js
+++ b/js/bullet.js
@@ -1109,9 +1109,9 @@ const b = {
b.grenade = grenadeDefault
}
},
- harpoon(where, target, angle = m.angle, scale = 1, isReturn = false, ropeLength = 15) {
+ harpoon(where, target, angle = m.angle, harpoonLength = 1, isReturn = false, totalCycles = 15) {
const me = bullet.length;
- bullet[me] = Bodies.fromVertices(where.x, where.y, [{ x: -40 * scale, y: 2, index: 0, isInternal: false }, { x: -40 * scale, y: -2, index: 1, isInternal: false }, { x: 50 * scale, y: -3, index: 3, isInternal: false }, { x: 30 * scale, y: 2, index: 4, isInternal: false }], {
+ bullet[me] = Bodies.fromVertices(where.x, where.y, [{ x: -40 * harpoonLength, y: 2, index: 0, isInternal: false }, { x: -40 * harpoonLength, y: -2, index: 1, isInternal: false }, { x: 50 * harpoonLength, y: -3, index: 3, isInternal: false }, { x: 30 * harpoonLength, y: 2, index: 4, isInternal: false }], {
cycle: 0,
angle: angle,
friction: 1,
@@ -1122,14 +1122,14 @@ const b = {
drawStringFlip: (Math.round(Math.random()) ? 1 : -1),
dmg: 0, //damage done in addition to the damage from momentum
classType: "bullet",
- endCycle: simulation.cycle + ropeLength * 2.5 + 15,
+ endCycle: simulation.cycle + totalCycles * 2.5 + 15,
collisionFilter: {
category: cat.bullet,
mask: tech.isNeedleShieldPierce ? cat.map | cat.body | cat.mob | cat.mobBullet : cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield,
},
minDmgSpeed: 0,
lookFrequency: Math.floor(7 + Math.random() * 3),
- density: 0.005, //0.001 is normal
+ density: tech.harpoonDensity, //0.001 is normal for blocks, 0.005 is normal for harpoon, 0.035 when buffed
beforeDmg(who) {
if (tech.isNeedleShieldPierce && who.isShielded) { //disable shields
who.isShielded = false
@@ -1168,6 +1168,7 @@ const b = {
this.caughtPowerUp.effect();
Matter.Composite.remove(engine.world, this.caughtPowerUp);
powerUp.splice(index, 1);
+ if (tech.isHarpoonPowerUp) tech.harpoonDensity = 0.035 //0.005 is normal
} else {
this.dropCaughtPowerUp()
}
@@ -1246,7 +1247,7 @@ const b = {
if (!m.isBodiesAsleep) {
this.cycle++
if (isReturn) {
- if (this.cycle > ropeLength) {
+ if (this.cycle > totalCycles) {
if (m.energy < 0.05) { //snap rope if not enough energy
const returnForce = Vector.mult(Vector.normalise(Vector.sub(this.position, m.pos)), 3 * this.thrustMag * this.mass)
this.force.x -= returnForce.x
@@ -1317,6 +1318,40 @@ const b = {
// }
}
this.drawString()
+ if (tech.isHarpoonPowerUp && this.density > 0.01) {
+ this.drawString = () => {
+ ctx.beginPath();
+ ctx.moveTo(this.vertices[0].x, this.vertices[0].y);
+ for (let j = 1, len = this.vertices.length; j < len; j += 1) {
+ ctx.lineTo(this.vertices[j].x, this.vertices[j].y);
+ }
+ ctx.lineTo(this.vertices[0].x, this.vertices[0].y);
+ ctx.lineWidth = 10;
+ ctx.strokeStyle = "#000";
+ ctx.lineJoin = "miter"
+ ctx.miterLimit = 100;
+ ctx.stroke();
+ ctx.lineJoin = "round"
+ ctx.miterLimit = 10
+
+ if (isReturn) {
+ const where = {
+ x: m.pos.x + 30 * Math.cos(m.angle),
+ y: m.pos.y + 30 * Math.sin(m.angle)
+ }
+ const sub = Vector.sub(where, this.vertices[0])
+ const perpendicular = Vector.mult(Vector.normalise(Vector.perp(sub)), this.drawStringFlip * Math.min(80, 10 + this.drawStringControlMagnitude / (10 + Vector.magnitude(sub))))
+ const controlPoint = Vector.add(Vector.add(where, Vector.mult(sub, -0.5)), perpendicular)
+ ctx.strokeStyle = "#000" // "#0ce"
+ ctx.lineWidth = 0.5
+ ctx.beginPath();
+ ctx.moveTo(where.x, where.y);
+ ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, this.vertices[0].x, this.vertices[0].y)
+ // ctx.lineTo(this.vertices[0].x, this.vertices[0].y);
+ ctx.stroke();
+ }
+ }
+ }
},
});
if (!isReturn && !target) {
@@ -2759,7 +2794,7 @@ const b = {
inertia: Infinity,
frictionAir: 0.003,
dmg: 0, //damage on impact
- damage: (tech.isFastFoam ? 0.039 : 0.011) * (tech.isFoamTeleport ? 1.5 : 1), //damage done over time
+ damage: (tech.isFastFoam ? 0.039 : 0.011) * (tech.isBulletTeleport ? 1.5 : 1), //damage done over time
scale: 1 - 0.006 / tech.isBulletsLastLonger * (tech.isFastFoam ? 1.65 : 1),
classType: "bullet",
collisionFilter: {
@@ -2898,7 +2933,7 @@ const b = {
}
}
}
- if (this.nextPortCycle < simulation.cycle) { //teleport around if you have tech.isFoamTeleport
+ if (this.nextPortCycle < simulation.cycle) { //teleport around if you have tech.isBulletTeleport
this.nextPortCycle = simulation.cycle + this.portFrequency
const range = 15 * Math.sqrt(this.radius) * Math.random()
Matter.Body.setPosition(this, Vector.add(this.position, Vector.rotate({ x: range, y: 0 }, 2 * Math.PI * Math.random())))
@@ -2906,7 +2941,7 @@ const b = {
}
}
});
- if (tech.isFoamTeleport) bullet[me].nextPortCycle = simulation.cycle + bullet[me].portFrequency
+ if (tech.isBulletTeleport) bullet[me].nextPortCycle = simulation.cycle + bullet[me].portFrequency
Composite.add(engine.world, bullet[me]); //add bullet to world
Matter.Body.setVelocity(bullet[me], velocity);
},
@@ -4236,7 +4271,7 @@ const b = {
name: "super balls",
description: "fire 3 balls in a wide arc
balls bounce with no momentum loss",
ammo: 0,
- ammoPack: 11,
+ ammoPack: 10,
have: false,
// num: 5,
do() {},
@@ -4382,7 +4417,7 @@ const b = {
ctx.lineWidth = 2 * tech.wavePacketDamage
ctx.beginPath();
const end = 700 * Math.sqrt(tech.isBulletsLastLonger) / Math.sqrt(tech.waveReflections * 0.5) //should equal about 1060
- const damage = 2 * b.dmgScale * tech.wavePacketDamage * tech.waveBeamDamage //damage is lower for large radius mobs, since they feel the waves longer
+ const damage = 2 * b.dmgScale * tech.wavePacketDamage * tech.waveBeamDamage * (tech.isBulletTeleport ? 1.5 : 1) //damage is lower for large radius mobs, since they feel the waves longer
for (let i = this.waves.length - 1; i > -1; i--) {
//draw wave
@@ -4390,6 +4425,14 @@ const b = {
ctx.arc(this.waves[i].position.x, this.waves[i].position.y, this.waves[i].radius, 0, 2 * Math.PI);
// collisions
if (!m.isBodiesAsleep) {
+
+
+ if (tech.isBulletTeleport && Math.random() < 0.04) {
+ const scale = 400 * Math.random()
+ this.waves[i].position = Vector.add(this.waves[i].position, { x: scale * (Math.random() - 0.5), y: scale * (Math.random() - 0.5) })
+ }
+
+
for (let j = 0, len = mob.length; j < len; j++) {
const dist = Vector.magnitude(Vector.sub(this.waves[i].position, mob[j].position))
const r = mob[j].radius + 30
@@ -4464,7 +4507,7 @@ const b = {
ctx.lineWidth = 2 * tech.wavePacketDamage
ctx.beginPath();
const end = 1100 * tech.isBulletsLastLonger / Math.sqrt(tech.waveReflections * 0.5) //should equal about 1767
- const damage = 2 * b.dmgScale * tech.wavePacketDamage * tech.waveBeamDamage //damage is lower for large radius mobs, since they feel the waves longer
+ const damage = 2 * b.dmgScale * tech.wavePacketDamage * tech.waveBeamDamage * (tech.isBulletTeleport ? 1.5 : 1) //damage is lower for large radius mobs, since they feel the waves longer
for (let i = this.waves.length - 1; i > -1; i--) {
const v1 = Vector.add(this.waves[i].position, Vector.mult(this.waves[i].unit1, this.waves[i].radius))
@@ -4475,6 +4518,19 @@ const b = {
// collisions
//using small angle linear approximation of circle arc, this will not work if the arc gets large // https://stackoverflow.com/questions/13652518/efficiently-find-points-inside-a-circle-sector
if (!m.isBodiesAsleep) {
+ if (tech.isBulletTeleport && Math.random() < 0.05) {
+ if (Math.random() < 0.5) {
+ // const scale = 500 * Math.random()
+ // this.waves[i].position = Vector.add(this.waves[i].position, { x: scale * (Math.random() - 0.5), y: scale * (Math.random() - 0.5) })
+ } else {
+ this.waves[i].arc *= 1 + 1 * (Math.random() - 0.5)
+ const halfArc = this.waves[i].arc / 2
+ const angle = m.angle + 0.5 * (Math.random() - 0.5)
+ this.waves[i].angle = angle - halfArc
+ this.waves[i].unit1 = { x: Math.cos(angle - halfArc), y: Math.sin(angle - halfArc) }
+ this.waves[i].unit2 = { x: Math.cos(angle + halfArc), y: Math.sin(angle + halfArc) }
+ }
+ }
let hits = Matter.Query.ray(mob, v1, v2, 50) //Matter.Query.ray(bodies, startPoint, endPoint, [rayWidth])
for (let j = 0; j < hits.length; j++) {
const who = hits[j].body
@@ -4566,7 +4622,7 @@ const b = {
slow: 0,
amplitude: (input.down ? 5 : 10) * ((this.wavePacketCycle % 2) ? -1 : 1) * Math.sin((this.wavePacketCycle + 1) * 0.088), //0.0968 //0.1012 //0.11 //0.088 //shorten wave packet
minDmgSpeed: 0,
- dmg: b.dmgScale * tech.waveBeamDamage * tech.wavePacketDamage, //also control damage when you divide by mob.mass
+ dmg: b.dmgScale * tech.waveBeamDamage * tech.wavePacketDamage * (tech.isBulletTeleport ? 1.5 : 1), //also control damage when you divide by mob.mass
classType: "bullet",
collisionFilter: {
category: 0,
@@ -4613,7 +4669,24 @@ const b = {
Matter.Body.setPosition(this, Vector.add(this.position, where))
}
});
+ if (tech.isBulletTeleport) {
+ bullet[me].wiggle = function() {
+ this.cycle++
+ const where = Vector.mult(transverse, this.amplitude * Math.cos(this.cycle * tech.waveFrequency))
+ if (Math.random() < 0.005) {
+ if (Math.random() < 0.33) { //randomize position
+ const scale = 500 * Math.random()
+ Matter.Body.setPosition(this, Vector.add({ x: scale * (Math.random() - 0.5), y: scale * (Math.random() - 0.5) }, Vector.add(this.position, where)))
+ } else { //randomize position in velocity direction
+ const velocityScale = Vector.mult(this.velocity, 50 * (Math.random() - 0.5))
+ Matter.Body.setPosition(this, Vector.add(velocityScale, Vector.add(this.position, where)))
+ }
+ } else {
+ Matter.Body.setPosition(this, Vector.add(this.position, where))
+ }
+ }
+ }
let waveSpeedMap = 0.1
let waveSpeedBody = 0.25
if (tech.isPhaseVelocity) {
@@ -4977,7 +5050,7 @@ const b = {
setTimeout(() => {
if (!simulation.paused) {
b.foam(position, Vector.rotate(velocity, spread), radius)
- // (tech.isFastFoam ? 0.044 : 0.011) * (tech.isFoamTeleport ? 1.60 : 1)
+ // (tech.isFastFoam ? 0.044 : 0.011) * (tech.isBulletTeleport ? 1.60 : 1)
bullet[bullet.length - 1].damage *= (1 + 0.7 * tech.foamFutureFire)
}
}, 250 * tech.foamFutureFire);
@@ -5028,7 +5101,7 @@ const b = {
}
}
}
- b.harpoon(where, closest.target, m.angle, length, false)
+ b.harpoon(where, closest.target, m.angle, length, false, 15)
m.fireCDcycle = m.cycle + 40 * b.fireCDscale; // cool down
} else if (tech.extraHarpoons) {
const range = 560 * (tech.isFilament ? 1 + this.ammo / 33 : 1)
@@ -5071,6 +5144,7 @@ const b = {
const recoil = Vector.mult(Vector.normalise(Vector.sub(where, m.pos)), input.down ? 0.015 : 0.035)
player.force.x -= recoil.x
player.force.y -= recoil.y
+ tech.harpoonDensity = 0.005
}
},
{
diff --git a/js/index.js b/js/index.js
index 6fd93dd..39b4819 100644
--- a/js/index.js
+++ b/js/index.js
@@ -155,7 +155,7 @@ function setupCanvas() {
canvas.height = window.innerHeight;
canvas.width2 = canvas.width / 2; //precalculated because I use this often (in mouse look)
canvas.height2 = canvas.height / 2;
- canvas.diagonal = Math.sqrt(canvas.width2 * canvas.width2 + canvas.height2 * canvas.height2);
+ // canvas.diagonal = Math.sqrt(canvas.width2 * canvas.width2 + canvas.height2 * canvas.height2);
// ctx.font = "18px Arial";
// ctx.textAlign = "center";
ctx.font = "25px Arial";
@@ -590,9 +590,25 @@ const build = {
}
removeOne();
}
- simulation.isCheating = true;
+ // simulation.isCheating = true;
for (let i = 0, len = tech.tech.length; i < len; i++) {
- if (tech.tech[i].isLore) tech.tech[i].frequency = 0;
+ // if ((tech.tech[i].isLore && tech.tech[i].count === 0) || (!tech.tech[i].isLore && tech.tech[i].count > 0)) { //don't remove lore frequency if you only have lore tech
+ // tech.tech[i].frequency = 0; //remove lore power up chance
+ // }
+ if (!simulation.isCheating && tech.tech[i].count > 0 && !tech.tech[i].isLore && !tech.tech[i].isExperimentalMode) {
+ simulation.isCheating = true;
+ }
+ if (tech.tech[i].isLore) {
+ tech.tech[i].frequency = 0; //remove lore power up chance
+ }
+ }
+ //if you have no tech (not cheating) remove all power ups that might have spawned from tech
+ if (!simulation.isCheating) {
+ function removeAll(array) {
+ for (let i = 0; i < array.length; ++i) Matter.Composite.remove(engine.world, array[i]);
+ }
+ removeAll(powerUp);
+ powerUp = [];
}
document.body.style.cursor = "none";
document.body.style.overflow = "hidden"
diff --git a/js/level.js b/js/level.js
index 5514b23..e8876e0 100644
--- a/js/level.js
+++ b/js/level.js
@@ -17,11 +17,9 @@ const level = {
// simulation.isHorizontalFlipped = true
// m.setField("time dilation")
// b.giveGuns("harpoon")
- // tech.giveTech("filament")
- // tech.giveTech("unaaq")
- // tech.giveTech("reticulum")
- // tech.giveTech("reticulum")
- // tech.giveTech("reticulum")
+ // tech.giveTech("toggling harpoon")
+ // tech.giveTech("phonon")
+ // tech.giveTech("isotropic radiator")
// tech.giveTech("necrophage")
// for (let i = 0; i < 3; i++) tech.giveTech("super sized")
// for (let i = 0; i < 9; i++) tech.giveTech("MIRV")
@@ -31,7 +29,7 @@ const level = {
// level.template(); //not in rotation, blank start new map development
// level.final() //final boss level
// level.gauntlet(); //before final boss level
- // level.labs(); //always before gauntlet level
+ // level.labs();
// level.testChamber()
// level.sewers();
// level.satellite();
@@ -40,7 +38,7 @@ const level = {
// level.rooftops();
// level.warehouse();
// level.highrise();
- // level.office();
+ // level.office();
// level.gauntlet(); //only fighting, very simple map, before final boss
// level.house() //community level
// level.detours() //community level
@@ -1119,8 +1117,13 @@ const level = {
const Xoffset2 = 1650 + Math.floor(300 * Math.random())
const hazard4 = level.hazard(x + Xoffset2, y - 240, 10, 250, 0.4) //laser
spawn.mapRect(x + Xoffset2 - 5, y - 250, 20, 20); //laser nose
-
- let isSpawnedMobs = false
+ spawn.randomMob(x + 150, y + -1100, mobSpawnChance);
+ spawn.randomMob(x + 175, y + -775, mobSpawnChance);
+ spawn.randomMob(x + 150, y + -350, mobSpawnChance);
+ spawn.randomMob(x + 150, y + -75, mobSpawnChance);
+ spawn.randomMob(x + 650, y + -125, mobSpawnChance);
+ spawn.randomMob(x + 1200, y + -75, mobSpawnChance);
+ // let isSpawnedMobs = false
doCustomTopLayer.push(
() => {
toggle.query();
@@ -1132,15 +1135,15 @@ const level = {
hazard2.opticalQuery();
hazard3.opticalQuery();
hazard4.opticalQuery();
- if (!isSpawnedMobs && !toggle.isOn) {
- isSpawnedMobs = true
- spawn.randomMob(x + 150, y + -1100, mobSpawnChance);
- spawn.randomMob(x + 175, y + -775, mobSpawnChance);
- spawn.randomMob(x + 150, y + -350, mobSpawnChance);
- spawn.randomMob(x + 150, y + -75, mobSpawnChance);
- spawn.randomMob(x + 650, y + -125, mobSpawnChance);
- spawn.randomMob(x + 1200, y + -75, mobSpawnChance);
- }
+ // if (!isSpawnedMobs && !toggle.isOn) {
+ // isSpawnedMobs = true
+ // spawn.randomMob(x + 150, y + -1100, mobSpawnChance);
+ // spawn.randomMob(x + 175, y + -775, mobSpawnChance);
+ // spawn.randomMob(x + 150, y + -350, mobSpawnChance);
+ // spawn.randomMob(x + 150, y + -75, mobSpawnChance);
+ // spawn.randomMob(x + 650, y + -125, mobSpawnChance);
+ // spawn.randomMob(x + 1200, y + -75, mobSpawnChance);
+ // }
}
)
},
@@ -1164,11 +1167,11 @@ const level = {
const frictionAir = 0.03
const angularVelocity = 0 //0.01
const spinVariance = 0 //0.02
- balance1 = level.spinner(x + 200, y - 500, 25, 350, density, angle + variance * (Math.random() - 0.5), frictionAir, angularVelocity + spinVariance * (Math.random() - 0.5)) // spinner(x, y, width, height, density = 0.001, angle=0,frictionAir=0.001,angularVelocity=0) {
- balance2 = level.spinner(x + 200, y - 950, 25, 350, density, angle + variance * (Math.random() - 0.5), frictionAir, angularVelocity + spinVariance * (Math.random() - 0.5))
- balance3 = level.spinner(x + 650, y - 650, 25, 350, density, angle + variance * (Math.random() - 0.5), frictionAir, angularVelocity + spinVariance * (Math.random() - 0.5))
- balance4 = level.spinner(x + 750, y - 1050, 25, 350, density, angle + variance * (Math.random() - 0.5), frictionAir, angularVelocity + spinVariance * (Math.random() - 0.5))
- balance5 = level.spinner(x + 1250, y - 1100, 25, 350, density, angle + variance * (Math.random() - 0.5), frictionAir, angularVelocity + spinVariance * (Math.random() - 0.5))
+ balance1 = level.spinner(x + 200, y - 500, 30, 400, density, angle + variance * (Math.random() - 0.5), frictionAir, angularVelocity + spinVariance * (Math.random() - 0.5)) // spinner(x, y, width, height, density = 0.001, angle=0,frictionAir=0.001,angularVelocity=0) {
+ balance2 = level.spinner(x + 200, y - 950, 30, 400, density, angle + variance * (Math.random() - 0.5), frictionAir, angularVelocity + spinVariance * (Math.random() - 0.5))
+ balance3 = level.spinner(x + 650, y - 750, 30, 400, density, angle + variance * (Math.random() - 0.5), frictionAir, angularVelocity + spinVariance * (Math.random() - 0.5))
+ // balance4 = level.spinner(x + 750, y - 1050, 25, 350, density, angle + variance * (Math.random() - 0.5), frictionAir, angularVelocity + spinVariance * (Math.random() - 0.5))
+ balance4 = level.spinner(x + 1250, y - 1000, 30, 400, density, angle + variance * (Math.random() - 0.5), frictionAir, angularVelocity + spinVariance * (Math.random() - 0.5))
let isInRoom = false
doCustom.push(
@@ -1196,8 +1199,6 @@ const level = {
ctx.arc(balance3.pointA.x, balance3.pointA.y, 9, 0, 2 * Math.PI);
ctx.moveTo(balance4.pointA.x, balance4.pointA.y)
ctx.arc(balance4.pointA.x, balance4.pointA.y, 9, 0, 2 * Math.PI);
- ctx.moveTo(balance5.pointA.x, balance5.pointA.y)
- ctx.arc(balance5.pointA.x, balance5.pointA.y, 9, 0, 2 * Math.PI);
ctx.fill();
}
)
@@ -1220,11 +1221,26 @@ const level = {
const variance = 0.2 //Math.PI
const frictionAir = 0.015
const height = 35
- balance1 = level.spinner(x + 1300, y - 450, height, 410, density, angle + variance * (Math.random() - 0.5), frictionAir) // spinner(x, y, width, height, density = 0.001, angle=0,frictionAir=0.001,angularVelocity=0) {
- balance3 = level.spinner(x + 750, y - 600, height, 410, density, angle + variance * (Math.random() - 0.5), frictionAir)
- balance2 = level.spinner(x + 300, y - 850, height, 410, density, angle + variance * (Math.random() - 0.5), frictionAir)
- balance4 = level.spinner(x + 850, y - 1100, height, 410, density, angle + variance * (Math.random() - 0.5), frictionAir)
- balance5 = level.spinner(x + 1300, y - 1145, height, 410, density, angle + variance * (Math.random() - 0.5), frictionAir)
+ balance1 = level.spinner(x + 1300, y - 425, height, 410, density, angle + variance * (Math.random() - 0.5), frictionAir) // spinner(x, y, width, height, density = 0.001, angle=0,frictionAir=0.001,angularVelocity=0) {
+ balance3 = level.spinner(x + 750, y - 650, height, 410, density, angle + variance * (Math.random() - 0.5), frictionAir)
+ balance2 = level.spinner(x + 300, y - 425, height, 410, density, angle + variance * (Math.random() - 0.5), frictionAir)
+ balance4 = level.spinner(x + 1250, y - 950, 50, 550, density, angle, 0.1)
+ const rotatingBlock = body[body.length - 1]
+ doCustom.push(
+ () => {
+ if (!isInRoom && m.pos.x > x - 100 && m.pos.x < x + 2700 && m.pos.y > y - 1300 && m.pos.y < y) { //check if player is in this room and run code once
+ isInRoom = true
+ spawn.randomMob(x + 1175, y - 725, mobSpawnChance);
+ spawn.randomMob(x + 1450, y - 725, mobSpawnChance);
+ spawn.randomMob(x + 425, y - 100, mobSpawnChance);
+ spawn.randomMob(x + 1200, y - 125, mobSpawnChance);
+ spawn.randomMob(x + 1300, y - 375, mobSpawnChance);
+ }
+ ctx.fillStyle = "#d4f4f4"
+ ctx.fillRect(x + 1600, y - 1300, 400, 350)
+ rotatingBlock.torque += rotatingBlock.inertia * 0.000005
+ }
+ )
} else {
const density = 0.001 //+ (simulation.difficultyMode < 5 ? 0.003 : 0)
const angle = Math.PI / 2
@@ -1233,27 +1249,26 @@ const level = {
const width = 200
const height = 200
const spinVariance = 0.05
- balance1 = level.spinner(x + 150, y - 300, height, width, density, angle + variance * (Math.random() - 0.5), frictionAir, spinVariance * (Math.random() - 0.5)) // spinner(x, y, width, height, density = 0.001, angle=0,frictionAir=0.001,angularVelocity=0) {
- balance4 = level.spinner(x + 435, y - 525, height, width, density, angle + variance * (Math.random() - 0.5), frictionAir, spinVariance * (Math.random() - 0.5))
- balance3 = level.spinner(x + 735, y - 700, height, width, density, angle + variance * (Math.random() - 0.5), frictionAir, spinVariance * (Math.random() - 0.5))
- balance5 = level.spinner(x + 1040, y - 850, height, width, density, angle + variance * (Math.random() - 0.5), frictionAir, spinVariance * (Math.random() - 0.5))
- balance2 = level.spinner(x + 1380, y - 750, height, width, density, angle + variance * (Math.random() - 0.5), frictionAir, spinVariance * (Math.random() - 0.5))
+ balance1 = level.spinner(x + 175, y - 300, height, width, density, angle + variance * (Math.random() - 0.5), frictionAir, spinVariance * (Math.random() - 0.5)) // spinner(x, y, width, height, density = 0.001, angle=0,frictionAir=0.001,angularVelocity=0) {
+ balance2 = level.spinner(x + 500, y - 525, height, width, density, angle + variance * (Math.random() - 0.5), frictionAir, spinVariance * (Math.random() - 0.5))
+ balance3 = level.spinner(x + 850, y - 700, height, width, density, angle + variance * (Math.random() - 0.5), frictionAir, spinVariance * (Math.random() - 0.5))
+ balance4 = level.spinner(x + 1250, y - 850, height, width, density, angle + variance * (Math.random() - 0.5), frictionAir, spinVariance * (Math.random() - 0.5))
+ doCustom.push(
+ () => {
+ if (!isInRoom && m.pos.x > x - 100 && m.pos.x < x + 2700 && m.pos.y > y - 1300 && m.pos.y < y) { //check if player is in this room and run code once
+ isInRoom = true
+ spawn.randomMob(x + 1175, y - 725, mobSpawnChance);
+ spawn.randomMob(x + 1450, y - 725, mobSpawnChance);
+ spawn.randomMob(x + 425, y - 100, mobSpawnChance);
+ spawn.randomMob(x + 1200, y - 125, mobSpawnChance);
+ spawn.randomMob(x + 1300, y - 375, mobSpawnChance);
+ }
+ ctx.fillStyle = "#d4f4f4"
+ ctx.fillRect(x + 1600, y - 1300, 400, 350)
+ }
+ )
}
let isInRoom = false
- doCustom.push(
- () => {
- if (!isInRoom && m.pos.x > x - 100 && m.pos.x < x + 2700 && m.pos.y > y - 1300 && m.pos.y < y) { //check if player is in this room and run code once
- isInRoom = true
- spawn.randomMob(x + 1175, y - 725, mobSpawnChance);
- spawn.randomMob(x + 1450, y - 725, mobSpawnChance);
- spawn.randomMob(x + 425, y - 100, mobSpawnChance);
- spawn.randomMob(x + 1200, y - 125, mobSpawnChance);
- spawn.randomMob(x + 1300, y - 375, mobSpawnChance);
- }
- ctx.fillStyle = "#d4f4f4"
- ctx.fillRect(x + 1600, y - 1300, 400, 350)
- }
- )
doCustomTopLayer.push(
() => {
ctx.fillStyle = "#233"
@@ -1265,8 +1280,6 @@ const level = {
ctx.arc(balance3.pointA.x, balance3.pointA.y, 9, 0, 2 * Math.PI);
ctx.moveTo(balance4.pointA.x, balance4.pointA.y)
ctx.arc(balance4.pointA.x, balance4.pointA.y, 9, 0, 2 * Math.PI);
- ctx.moveTo(balance5.pointA.x, balance5.pointA.y)
- ctx.arc(balance5.pointA.x, balance5.pointA.y, 9, 0, 2 * Math.PI);
ctx.fill();
}
)
@@ -1293,7 +1306,7 @@ const level = {
button.isReadyToFire = true
} else if (button.isReadyToFire && !button.isUp) {
button.isReadyToFire = false
- b.pulse(100, Math.PI, { x: x + 2000 - 560, y: y - 150 })
+ b.pulse(90, Math.PI, { x: x + 2000 - 560, y: y - 150 })
}
}
)
@@ -1324,7 +1337,7 @@ const level = {
button.isReadyToFire = true
} else if (button.isReadyToFire && !button.isUp) {
button.isReadyToFire = false
- b.pulse(100, 0, { x: x + 560, y: y - 150 })
+ b.pulse(90, 0, { x: x + 560, y: y - 150 })
}
}
)
@@ -1995,7 +2008,7 @@ const level = {
// upDown = upDownOptions[1] //controls what level spawns for map designing building //********************************* DO !NOT! RUN THIS LINE IN THE FINAL VERSION ***************************************
//3x2: 4 short rooms (3000x1500), 1 double tall room (3000x3000)
//rooms
- let rooms = ["loot", "enter", "empty", "exit"]
+ let rooms = ["exit", "loot", "enter", "empty"]
rooms = shuffle(rooms); //shuffles array order
//look... you and I both know there is a better way to do this, but it works so I'm gonna focus on other things
while ( //makes sure that the exit and entrance aren't both on the same floor
@@ -2276,7 +2289,7 @@ const level = {
spawn.mapRect(5050, -100, 50, 150);
spawn.mapRect(4850, -275, 50, 175);
// level.difficultyIncrease(30) //30 is near max on hard //60 is near max on why
- // spawn.starter(1900, -500, 200) //big boy
+ spawn.starter(1900, -500, 200) //big boy
// spawn.blockGroup(1900, -500)
// for (let i = 0; i < 10; ++i) spawn.bodyRect(1600 + 5, -500, 30, 40);
// spawn.laserBombingBoss(1900, -500)
@@ -2297,9 +2310,9 @@ const level = {
// spawn.laserTargetingBoss(1600, -500)
// spawn.laserBoss(1600, -500)
// spawn.cellBossCulture(1600, -500)
- spawn.nodeGroup(1200, -500, "grenadier")
- spawn.nodeGroup(1800, -500, "grenadier")
- spawn.nodeGroup(1200, 0, "grenadier")
+ // spawn.nodeGroup(1200, -500, "grenadier")
+ // spawn.nodeGroup(1800, -500, "grenadier")
+ // spawn.nodeGroup(1200, 0, "grenadier")
// spawn.snakeBoss(1200, -500)
// spawn.suckerBoss(2900, -500)
// spawn.randomMob(1600, -500)
@@ -4583,8 +4596,8 @@ const level = {
spawn.mapRect(600, -1000, 500, 50); //2nd floor
spawn.spawnStairs(-600, -1000, 4, 250, 350); //stairs 2nd
spawn.mapRect(375, -600, 350, 150); //center table
- spawn.mapRect(-600 + 300, -2000 * 0.25, 2000 - 300, 50); //1st floor
- spawn.spawnStairs(-600 + 2000 - 50, -500, 4, 250, 350, true); //stairs 1st
+ spawn.mapRect(-300, -2000 * 0.25, 1690, 50); //1st floor
+ spawn.spawnStairs(-610 + 2000 - 50, -500, 4, 250, 350, true); //stairs
spawn.spawnStairs(-600, 0, 4, 250, 350); //stairs ground
spawn.bodyRect(700, -200, 100, 100); //center block under wall
spawn.bodyRect(700, -300, 100, 100); //center block under wall
@@ -4604,7 +4617,7 @@ const level = {
// spawn.mapVertex(3160, -525, "625 0 300 0 300 -140 500 -140"); //entrance/exit ramp
spawn.mapRect(3000, -2000 * 0.5, 700, 50); //exit roof
- spawn.mapRect(3000, -2000 * 0.25, 2000 - 300, 50); //1st floor
+ spawn.mapRect(3010, -2000 * 0.25, 1690, 50); //1st floor
spawn.spawnStairs(3000 + 2000 - 50, 0, 4, 250, 350, true); //stairs ground
spawn.randomSmallMob(4575, -560, 1);
spawn.randomSmallMob(1315, -880, 1);
diff --git a/js/powerup.js b/js/powerup.js
index 5e0aadd..5336da8 100644
--- a/js/powerup.js
+++ b/js/powerup.js
@@ -319,22 +319,25 @@ const powerUps = {
}
}
if (tech.isRerollBots) {
- for (const cost = 4; powerUps.research.count > cost - 1; powerUps.research.count -= cost) {
+ for (const cost = 2 + Math.floor(0.2 * b.totalBots()); powerUps.research.count > cost - 1; powerUps.research.count -= cost) {
b.randomBot()
if (tech.renormalization) {
for (let i = 0; i < cost; i++) {
- if (Math.random() < 0.4) {
+ if (Math.random() < tech.regularization) {
m.fieldCDcycle = m.cycle + 20;
powerUps.spawn(m.pos.x, m.pos.y, "research");
}
}
}
}
+ for (let i = 0, len = tech.tech.length; i < len; i++) {
+ if (tech.tech[i].name === "bot fabrication") tech.tech[i].description = `if you collect ${powerUps.orb.research(2 + Math.floor(0.2 * b.totalBots()))}use them to build a
random bot (+1 cost every 5 bots)`
+ }
}
if (tech.isDeathAvoid && document.getElementById("tech-anthropic")) {
document.getElementById("tech-anthropic").innerHTML = `-${powerUps.research.count}`
}
- if (tech.renormalization && Math.random() < 0.4 && amount < 0) {
+ if (tech.renormalization && Math.random() < tech.regularization && amount < 0) {
for (let i = 0, len = -amount; i < len; i++) powerUps.spawn(m.pos.x, m.pos.y, "research");
}
if (tech.isRerollHaste) {
diff --git a/js/tech.js b/js/tech.js
index 1495410..e92d166 100644
--- a/js/tech.js
+++ b/js/tech.js
@@ -135,6 +135,7 @@
tech.tech[i].count = 0;
}
}
+ console.log('cheating')
sound.tone(250)
sound.tone(300)
sound.tone(375)
@@ -186,7 +187,7 @@
if (tech.isEnergyDamage) dmg *= 1 + m.energy / 11;
if (tech.isDamageFromBulletCount) dmg *= 1 + bullet.length * 0.005
if (tech.isRerollDamage) dmg *= 1 + 0.037 * powerUps.research.count
- if (tech.isOneGun && b.inventory.length < 2) dmg *= 1.23
+ if (tech.isOneGun && b.inventory.length < 2) dmg *= 1.1995
if (tech.isNoFireDamage && m.cycle > m.fireCDcycle + 120) dmg *= 2
if (tech.isSpeedDamage) dmg *= 1 + Math.min(0.66, player.speed * 0.0165)
if (tech.isBotDamage) dmg *= 1 + 0.06 * b.totalBots()
@@ -248,7 +249,7 @@
},
tech: [{
name: "integrated armament",
- description: `increase damage by 23%
your inventory can only hold 1 gun`,
+ description: `increase damage by 19.95%
your inventory can only hold 1 gun`,
maxCount: 1,
count: 0,
frequency: 2,
@@ -413,7 +414,7 @@
},
{
name: "logistics",
- description: `${powerUps.orb.ammo()} give 80% more ammo
but it's only added to your current gun`,
+ description: `${powerUps.orb.ammo()} give 80% more ammo, but
it's only added to your current gun`,
maxCount: 1,
count: 0,
frequency: 1,
@@ -452,7 +453,7 @@
},
{
name: "cache",
- description: `${powerUps.orb.ammo()} gives 11x more ammo, but
you can't store any more ammo than that`,
+ description: `${powerUps.orb.ammo()} give 11x more ammo, but
you can't store any more ammo than that`,
maxCount: 1,
count: 0,
frequency: 1,
@@ -471,7 +472,7 @@
},
{
name: "catabolism",
- description: `firing while out of ammo spawns ${powerUps.orb.ammo(4)}
and reduces your maximum health by 1`,
+ description: `firing while out of ammo spawns ${powerUps.orb.ammo(4)}
but it reduces your maximum health by 1`,
maxCount: 1,
count: 0,
frequency: 1,
@@ -932,7 +933,7 @@
frequency: 1,
frequencyDefault: 1,
allowed() {
- return ((m.fieldUpgrades[m.fieldMode].name === "nano-scale manufacturing" && !(tech.isDroneTeleport || tech.isDroneRadioactive || tech.isSporeField || tech.isMissileField || tech.isIceField)) || (tech.haveGunCheck("drones") && !tech.isDroneRadioactive && !tech.isDroneTeleport) || tech.haveGunCheck("super balls") || tech.haveGunCheck("shotgun")) && !tech.isNailShot && !tech.isIceShot && !tech.isFoamShot && !tech.isWormShot && !tech.isNeedles
+ return ((m.fieldUpgrades[m.fieldMode].name === "nano-scale manufacturing" && !(tech.isDroneTeleport || tech.isDroneRadioactive || tech.isSporeField || tech.isMissileField || tech.isIceField)) || (tech.haveGunCheck("drones") && !tech.isDroneRadioactive && !tech.isDroneTeleport) || tech.haveGunCheck("super balls") || tech.haveGunCheck("shotgun")) && !tech.isNailShot && !tech.isIceShot && !tech.isFoamShot && !tech.isWormShot && !tech.isNeedleShot
},
requires: "super balls, basic or slug shotgun, drones, not irradiated drones or burst drones",
effect() {
@@ -1464,16 +1465,17 @@
},
{
name: "bot fabrication",
- description: `anytime you collect ${powerUps.orb.research(4)}
use them to build a random bot`,
+ //-----------description is overwritten in powerUps.research.changeRerolls------------
+ description: `if you collect ${powerUps.orb.research(2)}use them to build a
random bot (+1 cost every 5 bots)`,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
isBotTech: true,
allowed() {
- return powerUps.research.count > 3 || build.isExperimentSelection
+ return powerUps.research.count > 2 || build.isExperimentSelection
},
- requires: "at least 4 research",
+ requires: "at least 3 research",
effect() {
tech.isRerollBots = true;
powerUps.research.changeRerolls(0)
@@ -2904,12 +2906,34 @@
},
requires: "at least 3 research and not superdeterminism",
effect() {
- tech.renormalization = true;
+ tech.renormalization = true; //40% set in regularization tech
},
remove() {
tech.renormalization = false;
}
},
+ {
+ name: "regularization",
+ description: `increase renormalization chance by 10%
use ${powerUps.orb.research(6)}`,
+ maxCount: 3,
+ count: 0,
+ frequency: 2,
+ frequencyDefault: 2,
+ allowed() {
+ return tech.renormalization && (powerUps.research.count > 5 || build.isExperimentSelection)
+ },
+ requires: "renormalization",
+ effect() {
+ tech.regularization += 0.1
+ for (let i = 0; i < 6; i++) {
+ if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1)
+ }
+ for (let i = 0; i < 6; i++) powerUps.spawn(m.pos.x + 120 * (Math.random() - 0.5), m.pos.y + 120 * (Math.random() - 0.5), "research", false);
+ },
+ remove() {
+ tech.regularization = 0.4
+ }
+ },
{
name: "perturbation theory",
description: `66% decreased delay after firing
when you have no ${powerUps.orb.research(1)} in your inventory`,
@@ -3021,7 +3045,7 @@
},
{
name: "abiogenesis",
- description: `at the start of a level spawn a 2nd boss
use ${powerUps.orb.research(4)} or add 49 JUNK to the tech pool`,
+ description: `at the start of a level spawn a 2nd boss
use ${powerUps.orb.research(4)}or add 49 JUNK to the tech pool`,
maxCount: 1,
count: 0,
frequency: 2,
@@ -4256,9 +4280,9 @@
frequency: 4,
frequencyDefault: 4,
allowed() {
- return tech.haveGunCheck("wave beam") && !tech.isPhaseVelocity
+ return tech.haveGunCheck("wave beam") && !tech.isPhaseVelocity && !tech.isBulletTeleport
},
- requires: "wave beam, not phase velocity ",
+ requires: "wave beam, not phase velocity, uncertainty principle",
effect() {
tech.isLongitudinal = true;
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
@@ -4887,7 +4911,7 @@
frequency: 2,
frequencyDefault: 2,
allowed() {
- return !tech.isFoamTeleport && (tech.haveGunCheck("foam") || tech.foamBotCount > 1 || tech.isFoamShot)
+ return !tech.isBulletTeleport && (tech.haveGunCheck("foam") || tech.foamBotCount > 1 || tech.isFoamShot)
},
requires: "foam, not uncertainty",
effect() {
@@ -4899,21 +4923,21 @@
},
{
name: "uncertainty principle",
- description: "foam bubbles randomly change position
increase foam damage per second by 50%",
+ description: "foam and wave particle positions are random
increase their damage by 50%",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
- return !tech.isFoamAttract && (tech.haveGunCheck("foam") || tech.foamBotCount > 1 || tech.isFoamShot)
+ return (!tech.isFoamAttract && (tech.haveGunCheck("foam") || tech.foamBotCount > 1 || tech.isFoamShot)) || (tech.haveGunCheck("wave beam") && !tech.isLongitudinal)
},
- requires: "foam, not electrostatic induction",
+ requires: "foam, not electrostatic induction, wave beam, not phonon",
effect() {
- tech.isFoamTeleport = true
+ tech.isBulletTeleport = true
},
remove() {
- tech.isFoamTeleport = false;
+ tech.isBulletTeleport = false;
}
},
{
@@ -5032,6 +5056,26 @@
tech.isLargeHarpoon = false;
}
},
+ {
+ name: "toggling harpoon",
+ description: "increase the damage of your next harpoon
by 600% after using it to collect a power up",
+ isGunTech: true,
+ maxCount: 1,
+ count: 0,
+ frequency: 2,
+ frequencyDefault: 2,
+ allowed() {
+ return tech.haveGunCheck("harpoon")
+ },
+ requires: "harpoon",
+ effect() {
+ tech.isHarpoonPowerUp = true
+ },
+ remove() {
+ tech.isHarpoonPowerUp = false
+ tech.harpoonDensity = 0.005
+ }
+ },
{
name: "reticulum",
description: "fire +1 harpoon
when there are multiple targets in range",
@@ -6302,6 +6346,7 @@
requires: "",
effect() {
m.shipMode()
+ for (let i = 0; i < 7; i++) tech.giveTech("undefined")
},
remove() {}
},
@@ -6325,6 +6370,8 @@
simulation.trails()
}
}, 20000); //every 20 seconds
+ for (let i = 0; i < 7; i++) tech.giveTech("undefined")
+
},
remove() {
if (this.count > 0) clearTimeout(this.interval);
@@ -6350,6 +6397,7 @@
}
}
}, 5000); //every 5 seconds
+ for (let i = 0; i < 7; i++) tech.giveTech("undefined")
},
interval: undefined,
remove() {
@@ -6377,6 +6425,7 @@
m.transX += (m.transSmoothX - m.transX) * 0.07;
m.transY += (m.transSmoothY - m.transY) * 0.07;
}
+ for (let i = 0; i < 7; i++) tech.giveTech("undefined")
},
remove() {
if (this.count > 0) m.look = m.lookDefault()
@@ -6405,6 +6454,7 @@
}
}
}, 1000); //every 1 seconds
+ for (let i = 0; i < 7; i++) tech.giveTech("undefined")
},
interval: undefined,
remove() {
@@ -6425,6 +6475,7 @@
requires: "",
effect() {
tech.deathSpawns = 0.2
+ for (let i = 0; i < 7; i++) tech.giveTech("undefined")
},
remove() {
tech.deathSpawns = 0
@@ -6443,7 +6494,8 @@
},
requires: "",
effect() {
- tech.wimpExperiment = 3
+ tech.wimpExperiment = 5
+ for (let i = 0; i < 7; i++) tech.giveTech("undefined")
},
remove() {
tech.wimpExperiment = 0
@@ -7820,14 +7872,14 @@
if (lore.techCount === lore.techGoal) {
// tech.removeLoreTechFromPool();
this.frequency = 0;
- this.description = `null is open`
+ this.description = `null is open at level.final()`
} else {
this.frequency += lore.techGoal
// for (let i = 0; i < tech.tech.length; i++) { //set name for all unchosen copies of this tech
// if (tech.tech[i].isLore && tech.tech[i].count === 0) tech.tech[i].description = `${lore.techCount+1}/${lore.techGoal}
add copies of this to the potential tech pool`
// }
// for (let i = 0, len = 10; i < len; i++) tech.addLoreTechToPool()
- this.description = `uncaught error:
${lore.techGoal-lore.techCount} more required for access to null`
+ this.description = `uncaught error:
${Math.max(0,lore.techGoal-lore.techCount)} more required for access to null`
}
}, 1);
},
@@ -8108,7 +8160,7 @@
droneRadioDamage: null,
isDroneTeleport: null,
isDroneFastLook: null,
- isFoamTeleport: null,
+ isBulletTeleport: null,
isResearchBoss: null,
isJunkResearch: null,
junkResearchNumber: null,
@@ -8140,5 +8192,8 @@
// isSpear: null,
isLargeHarpoon: null,
extraHarpoons: null,
- ammoCap: null
+ ammoCap: null,
+ regularization: null,
+ isHarpoonPowerUp: null,
+ harpoonDensity: null
}
\ No newline at end of file
diff --git a/style.css b/style.css
index 0ce7060..46b8bd5 100644
--- a/style.css
+++ b/style.css
@@ -261,7 +261,7 @@ summary {
}
.experiment-grid-module {
- margin: -1px;
+ margin: -0.5px;
padding: 10px;
line-height: 170%;
/* border-radius: 6px; */
@@ -734,10 +734,10 @@ summary {
height: 13px;
border-radius: 50%;
display: inline-block;
- margin-bottom: -2.5px;
background-color: #f7b;
border: 0.5px #fff solid;
opacity: 0.85;
+ margin-bottom: -2.5px;
}
.ammo-circle {
@@ -756,10 +756,10 @@ summary {
height: 14px;
border-radius: 50%;
display: inline-block;
- margin-bottom: -3px;
background-color: #0d9;
border: 0.5px #fff solid;
opacity: 0.85;
+ margin-bottom: -3px;
}
.circle-grid-shadow {
diff --git a/todo.txt b/todo.txt
index a37f92f..8090bb1 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,17 +1,24 @@
******************************************************** NEXT PATCH **************************************************
-tech: cache - ammo power ups give 11x ammo, but you can't hold over 11x ammo
+tech: toggling harpoon - after picking up a power up with the harpoon, your next harpoon is 7x more dense
+ this probably needs to be balanced in the next patch
+tech: regularization - use 6 research to increase renormalization by 10%
+ (renormalization is 40% chance to get research when you use research)
+tech: bot fabrication uses 2 research to build a bot (+1 cost every 5 bots)
+tech: uncertainty principle now applies to wave beam in addition to foam
+tech: integrated armament gives 19.95% damage (was 23%)
-harpoon
- grabs 1 power up on the way out, or in
- harpooned power ups are predictable
- they attach to the harpoon instead of using physics to move towards player
-
-bugs fixes
- lasers were broke, but I fixed them
+level: labs - platforming rooms have been simplified
+start with 7/7 undefinded tech if you choose an -experiment- and no other tech
******************************************************** TODO ********************************************************
+Tech: Make player smol
+
+Tech: For the particle waves gun, Incompatible with phonon
+ shooting creates small particles that randomly jump around the map, often randomly changing their position completely instead of randomly jumping
+ combine with foam uncertainty?
+
"Interstellar Disturbance": Cosmic String applies to mobs who cross the wormhole's path, even after initial wormholing, but at reduced damage and stun time.
disable zoom progress when paused
@@ -23,8 +30,6 @@ harpoon tech
tech that buffs alt fire:
remove the string, all shots are alt fire
alt fire has a 50% chance to not use ammo?
- ammo power ups are 10% more likely to spawn from dead mobs
- ammo power ups give the harpoon 2x more ammo
holding down fire lets the string extend farther,
this can overwrite crouch mode
can't have 2+ harpoons
@@ -33,13 +38,9 @@ harpoon tech
2+ harpoons, with separate CDs
can't have extended string?
grappling hook?
- remove string in all modes, why?
- increase ammo
tracking so good harpoon can hit a target, circle around and hit it again
doesn't seem to be good physics
-level:lab too much walking around and too much platforming
-
tech - explode after getting hit, but while you are immune to harm
on mouse down wormhole shows a possible wormhole
@@ -475,7 +476,7 @@ possible names for tech
gnarl
SQUID (for superconducting quantum interference device) is a very sensitive magnetometer used to measure extremely subtle magnetic fields, based on superconducting loops containing Josephson junctions.
nuclear pasta - hard matter in neutron star
-
+
a tutorial / lore intro
needs to be optional so it doesn't slow experienced players
put something on the intro map