diff --git a/.DS_Store b/.DS_Store
index 2ff3b21..27c00d9 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/js/bullet.js b/js/bullet.js
index a5ac1fa..c43e8e9 100644
--- a/js/bullet.js
+++ b/js/bullet.js
@@ -1115,7 +1115,7 @@ const b = {
const cycles = 80
const speed = input.down ? 35 : 20 //input.down ? 43 : 32
const g = input.down ? 0.137 : 0.135
- const v = { x: m.Vx / 2 + speed * Math.cos(m.angle), y: m.Vy / 2 + speed * Math.sin(m.angle) }
+ const v = { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) }
ctx.strokeStyle = "rgba(68, 68, 68, 0.2)" //color.map
ctx.lineWidth = 2
ctx.beginPath()
@@ -1138,7 +1138,7 @@ const b = {
if (gunIndex) b.guns[gunIndex].do = function() {
const cycles = Math.floor(input.down ? 50 : 30) //30
const speed = input.down ? 44 : 35
- const v = { x: m.Vx / 2 + speed * Math.cos(m.angle), y: m.Vy / 2 + speed * Math.sin(m.angle) }
+ const v = { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) }
ctx.strokeStyle = "rgba(68, 68, 68, 0.2)" //color.map
ctx.lineWidth = 2
ctx.beginPath()
@@ -1153,7 +1153,7 @@ const b = {
if (gunIndex) b.guns[gunIndex].do = function() {
const cycles = Math.floor(input.down ? 120 : 80) //30
const speed = input.down ? 43 : 32
- const v = { x: m.Vx / 2 + speed * Math.cos(m.angle), y: m.Vy / 2 + speed * Math.sin(m.angle) }
+ const v = { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) } //m.Vy / 2 + removed to make the path less jerky
ctx.strokeStyle = "rgba(68, 68, 68, 0.2)" //color.map
ctx.lineWidth = 2
ctx.beginPath()
@@ -3457,10 +3457,13 @@ const b = {
this.lastLookCycle = simulation.cycle + (this.isUpgraded ? 21 : 110)
for (let i = 0, len = mob.length; i < len; i++) {
const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position));
- if (dist < 3000000 && //1400*1400
+ if (
+ !mob[i].isBadTarget &&
+ dist < 3000000 &&
Matter.Query.ray(map, this.position, mob[i].position).length === 0 &&
Matter.Query.ray(body, this.position, mob[i].position).length === 0 &&
- !mob[i].isShielded) {
+ !mob[i].isShielded
+ ) {
const SPEED = 35
const unit = Vector.normalise(Vector.sub(Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist) / 60)), this.position))
b.nail(this.position, Vector.mult(unit, SPEED))
diff --git a/js/index.js b/js/index.js
index caafb22..1062e1e 100644
--- a/js/index.js
+++ b/js/index.js
@@ -660,6 +660,7 @@ document.getElementById("experiment-button").addEventListener("click", () => { /
openExperimentMenu();
});
+
// ************************************************************************************************
// inputs
// ************************************************************************************************
diff --git a/js/level.js b/js/level.js
index c9247c9..52eb2d3 100644
--- a/js/level.js
+++ b/js/level.js
@@ -15,10 +15,10 @@ const level = {
// localSettings.levelsClearedLastGame = 10
// level.difficultyIncrease(30) //30 is near max on hard //60 is near max on why
// simulation.isHorizontalFlipped = true
- // m.setField("time dilation")
- // b.giveGuns("grenades")
- // tech.giveTech("neutron bomb")
- // tech.giveTech("vacuum bomb")
+ // m.setField("metamaterial cloaking")
+ b.giveGuns("laser")
+ // tech.giveTech("spherical harmonics")
+ tech.giveTech("relative permittivity")
// tech.giveTech("causality bombs")
// for (let i = 0; i < 2; i++) tech.giveTech("refractory metal")
// tech.giveTech("antiscience")
@@ -26,6 +26,11 @@ const level = {
// for (let i = 0; i < 2; i++) tech.giveTech("laser-bot")
// tech.isCancelDuplication = true
+
+
+
+
+
level.intro(); //starting level
// level.testing(); //not in rotation, used for testing
// level.template(); //not in rotation, blank start new map development
@@ -94,7 +99,7 @@ const level = {
}
}
if (tech.isExtraMaxEnergy) {
- tech.healMaxEnergyBonus += 0.03 * powerUps.totalPowerUps //Math.min(0.02 * powerUps.totalPowerUps, 0.51)
+ tech.healMaxEnergyBonus += 0.04 * powerUps.totalPowerUps //Math.min(0.02 * powerUps.totalPowerUps, 0.51)
m.setMaxEnergy();
}
if (tech.isGunCycle) {
diff --git a/js/planetesimals.js b/js/planetesimals.js
deleted file mode 100644
index 01eba97..0000000
--- a/js/planetesimals.js
+++ /dev/null
@@ -1,807 +0,0 @@
-/* http://brm.io/matter-js/docs/
-http://brm.io/matter-js/demo/#mixed
-https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js
-
-git hub gist:
-https://gist.github.com/lilgreenland/8f2a2c033fdf3d5546a0ca5d73a2ae11
-
- todo:
-can you render bullets a different color? // red?
-
-maybe add the ability to put up shields to survive collisions?
-maybe add durability regeneration
-
-fix new mass spawn so that it will pick a new location if the first spawn location already has a mass
-
- */
-function planetesimals() {
-
- //pause n-gon
- // if (!simulation.isChoosing && input.isPauseKeyReady && m.alive) {
- // input.isPauseKeyReady = false
- // setTimeout(function() {
- // input.isPauseKeyReady = true
- // }, 300);
- // if (simulation.paused) {
- // build.unPauseGrid()
- // simulation.paused = false;
- // // level.levelAnnounce();
- // document.body.style.cursor = "none";
- // requestAnimationFrame(cycle);
- // } else {
- // simulation.paused = true;
- // build.pauseGrid()
- // document.body.style.cursor = "auto";
-
- // if (tech.isGunSwitchField || simulation.testing) {
- // document.getElementById("pause-field").addEventListener("click", () => {
- // const energy = m.energy
- // m.setField((m.fieldMode === m.fieldUpgrades.length - 1) ? 1 : m.fieldMode + 1) //cycle to next field
- // m.energy = energy
- // document.getElementById("pause-field").innerHTML = `
${m.fieldUpgrades[m.fieldMode].name}
${m.fieldUpgrades[m.fieldMode].description}`
- // });
- // }
- // }
- // }
-
- "use strict"; //strict mode to catch errors
- //game objects values
- var game = {
- cycle: 0,
- width: 0,
- height: 0,
- scale: 0.5,
- gravity: 0.00011,
- totalMass: 0,
- massSize: 0,
- level: 1,
- startingMassValue: 0,
- massSegment: 0,
- clearThreshold: 0.2, // there must be less than this percent to move on to the next level
- currentMass: 0,
- explodeMin: 8000,
- HUD: true,
- };
-
- function levelScaling() {
- //game.gravity = 0.00011; // + 0.000012 * game.level;
- game.width = 2000 * game.level; //shapes walls and spawn locations
- game.height = 2000 * game.level; //shapes walls and spawn locations
- game.scale = 1.2 / (Math.log(game.level + 1)); //0.6 + 1.0 / (game.level); //controls map zoom
- game.totalMass = 3 + game.level * 1; //how many masses to spawn at start of level
- game.massSize = 3 + game.level * 3; //adds the average length of a segment on a masses's vertices
- game.massSegment = 0.1 + 0.1 / game.level;
- }
-
- //looks for key presses and logs them
- var keys = [];
- document.body.addEventListener("keydown", function(e) {
- keys[e.keyCode] = true;
- });
- document.body.addEventListener("keyup", function(e) {
- keys[e.keyCode] = false;
- });
-
- // module aliases
- var Engine = Matter.Engine,
- World = Matter.World,
- Events = Matter.Events,
- Composite = Matter.Composite,
- Vertices = Matter.Vertices,
- Body = Matter.Body,
- Bodies = Matter.Bodies;
-
- // create an engine
- var engine = Engine.create();
- //turn off gravity
- engine.world.gravity.y = 0;
- // run the engine
- // Engine.run(engine);
- Matter.Runner.run(engine)
-
- function addWalls() {
- //add the walls
- var wallSettings = {
- size: 200,
- isStatic: true,
- render: {
- restitution: 0,
- fillStyle: 'rgba(0, 0, 0, 0.0)',
- strokeStyle: '#00ffff'
- }
- };
- World.add(engine.world, [
- Bodies.rectangle(game.width * 0.5, -wallSettings.size * 0.5, game.width, wallSettings.size, wallSettings), //top
- Bodies.rectangle(game.width * 0.5, game.height + wallSettings.size * 0.5, game.width, wallSettings.size, wallSettings), //bottom
- Bodies.rectangle(-wallSettings.size * 0.5, game.height * 0.5, wallSettings.size, game.height + wallSettings.size * 2, wallSettings), //left
- Bodies.rectangle(game.width + wallSettings.size * 0.5, game.height * 0.5, wallSettings.size, game.height + wallSettings.size * 2, wallSettings) //right
- ]);
-
- }
- //add the masses
- var mass = [];
-
- function addPlayer() {
- //add the player object as the first mass in the array
- mass.push();
- //var arrow = Vertices.fromPath('100 0 75 50 100 100 25 100 0 50 25 0');
- var arrow = Vertices.fromPath('0 15 -10 -15 10 -15');
- mass[0] = Matter.Bodies.fromVertices(Math.random() * game.width, Math.random() * game.height, arrow, {
- //density: 0.001,
- alive: true,
- friction: 0,
- frictionStatic: 0,
- frictionAir: 0,
- restitution: 0, //bounce 1 = 100% elastic
- density: 0.003333,
- thrust: 0.0004, //forward acceleration, if mass goes up this needs to go up
- yaw: 0.00133, //angular acceleration, needs to be higher with larger mass
- rotationLimit: 0.05, //max acceleration for player in radians/cycle
- angularFriction: 0.98, // 1 = no friction, 0.9 = high friction
- durability: 1,
- fireCD: 0,
- lastPlayerVelocity: { //for keeping track of damamge from too much acceleration
- x: 0,
- y: 0
- },
- });
- World.add(engine.world, mass[0]);
- }
-
- function randomConvexPolygon(size) { //returns a string of vectors that make a convex polygon
- var polyVector = '';
- var x = 0;
- var y = 0;
- var r = 0;
- var angle = 0;
- for (var i = 1; i < 60; i++) {
- angle += 0.1 + Math.random() * game.massSegment; //change in angle in radians
- if (angle > 2 * Math.PI) {
- break; //stop before it becomes convex
- }
- r = 2 + Math.random() * 2;
- x = Math.round(x + r * Math.cos(angle));
- y = Math.round(y + r * Math.sin(angle));
- polyVector = polyVector.concat(x * size + ' ' + y * size + ' ');
- }
- return polyVector;
- }
-
- function addMassVector(x, y, Vx, Vy, size) {
- var verticies = [];
- var vector = Vertices.fromPath(randomConvexPolygon(size));
- var i = mass.length;
- mass.push();
- mass[i] = Matter.Bodies.fromVertices(x, y, vector, { // x,y,vectors,{options}
- friction: 0,
- frictionStatic: 0,
- frictionAir: 0,
- restitution: 1,
- angle: Math.random() * 2 * Math.PI
- });
- Matter.Body.setVelocity(mass[i], {
- x: Vx,
- y: Vy
- });
- Matter.Body.setAngularVelocity(mass[i], (Math.random() - 0.5) * 0.03);
- World.add(engine.world, mass[i]);
- }
-
- function addMass(x, y, r, sides, Vx, Vy) {
- var i = mass.length;
- mass.push();
- mass[i] = Bodies.polygon(x, y, sides, r, {
- friction: 0,
- frictionStatic: 0,
- frictionAir: 0,
- restitution: 1,
- });
- Matter.Body.setVelocity(mass[i], {
- x: Vx,
- y: Vy
- });
- Matter.Body.setAngularVelocity(mass[i], (Math.random() - 0.5) * 0.03);
- World.add(engine.world, mass[i]);
- }
-
- function clearMasses() {
- World.clear(engine.world, false);
- console.log('clear')
- mass = [];
- }
-
- function spawnSetup() {
- //make the level indicator more clear on a new level
- // document.getElementById("level").innerHTML = 'system ' + game.level;
- // document.getElementById("level").style.color = 'white';
- // document.getElementById("level").style.fontSize = '500%';
- // document.getElementById("level").style.left = '40%';
- // document.getElementById("level").style.position = 'absolute';
- // setTimeout(levelFontSize, 3000);
- //after 3 seconds return to normal style
- // function levelFontSize(size) {
- // document.getElementById("level").style.color = 'grey';
- // document.getElementById("level").style.position = '';
- // document.getElementById("level").style.left = '';
- // document.getElementById("level").style.fontSize = '100%';
- // }
- levelScaling();
- clearMasses();
- addWalls();
- addPlayer();
- //add other masses
- for (var j = 0; j < game.totalMass; j++) {
- // addMassVector(x,y,Vx,Vy,size)
- addMassVector(game.width * 0.2 + Math.random() * game.width * 0.6,
- game.height * 0.2 + Math.random() * game.height * 0.6,
- 0, //(0.5 - Math.random()) * 4,
- 0,
- Math.random() * 3 + game.massSize
- );
- }
- //determine how much mass is in the game at the start
- game.startingMassValue = 0;
- for (var i = 0; i < mass.length; i++) {
- game.startingMassValue += mass[i].mass;
- }
- game.currentMass = game.startingMassValue;
- }
-
- spawnSetup();
-
- function repopulateMasses() {
- game.currentMass = 0;
- for (var i = 0; i < mass.length; i++) {
- game.currentMass += mass[i].mass;
- }
- if (game.currentMass < game.startingMassValue * game.clearThreshold) {
- game.level++;
- spawnSetup();
- mass[0].durability = 1;
- }
- }
-
- var bullet = [];
-
- function fireBullet() { //addMass(x, y, r, sides, Vx, Vy)
- var i = bullet.length;
- var angle = mass[0].angle + Math.PI * 0.5;
- var speed = 9;
- var playerDist = 25;
- bullet.push();
- bullet[i] = Bodies.polygon(
- mass[0].position.x + playerDist * Math.cos(angle),
- mass[0].position.y + playerDist * Math.sin(angle),
- 3, //sides
- 2, { //radius
- angle: Math.random() * 6.28,
- friction: 0,
- frictionStatic: 0,
- frictionAir: 0,
- restitution: 1,
- endCycle: game.cycle + 90, // life span for a bullet (60 per second)
- });
- Matter.Body.setVelocity(bullet[i], {
- x: mass[0].velocity.x + speed * Math.cos(angle),
- y: mass[0].velocity.y + speed * Math.sin(angle)
- });
- Matter.Body.setAngularVelocity(bullet[i], (Math.random() - 0.5) * 1);
- World.add(engine.world, bullet[i]);
- }
-
- function bulletEndCycle() {
- for (var i = 0; i < bullet.length; i++) {
- if (bullet[i].endCycle < game.cycle) {
- Matter.World.remove(engine.world, bullet[i]);
- bullet.splice(i, 1);
- }
- }
- }
-
- function controls() {
- if (mass[0].alive) {
- if (keys[32] && mass[0].fireCD < game.cycle) {
- mass[0].fireCD = game.cycle + 10; // ?/60 seconds of cooldown before you can fire
- fireBullet();
- }
-
- if (keys[38] || keys[87]) { //forward thrust
- mass[0].force.x += mass[0].thrust * Math.cos(mass[0].angle + Math.PI * 0.5);
- mass[0].force.y += mass[0].thrust * Math.sin(mass[0].angle + Math.PI * 0.5);
- thrustGraphic();
- } else if (keys[40] || keys[83]) { //reverse thrust
- mass[0].force = {
- x: -mass[0].thrust * 0.5 * Math.cos(mass[0].angle + Math.PI * 0.5),
- y: -mass[0].thrust * 0.5 * Math.sin(mass[0].angle + Math.PI * 0.5)
- };
- torqueGraphic(-1);
- torqueGraphic(1);
- }
- //rotate left and right
- if ((keys[37] || keys[65])) { //&& mass[0].angularVelocity > -mass[0].rotationLimit) {
- mass[0].torque = -mass[0].yaw; //counter clockwise
- torqueGraphic(-1);
- } else if ((keys[39] || keys[68])) { //&& mass[0].angularVelocity < mass[0].rotationLimit) {
- mass[0].torque = mass[0].yaw; //clockwise
- torqueGraphic(1);
- }
- //angular friction if spinning too fast
- if (Math.abs(mass[0].angularVelocity) > mass[0].rotationLimit) {
- Matter.Body.setAngularVelocity(mass[0], mass[0].angularVelocity * mass[0].angularFriction);
- }
- }
- }
-
- function torqueGraphic(dir) { //thrust graphic when holding rotation keys
- ctx.save();
- //ctx.translate(0.5 * canvas.width, 0.5 * canvas.height)
- ctx.rotate(mass[0].angle - Math.PI * 0.6 * dir);
- ctx.translate(0, -23);
- var grd = ctx.createLinearGradient(0, 0, 0, 15);
- grd.addColorStop(0.1, 'rgba(0, 0, 0, 0)');
- grd.addColorStop(1, 'rgba(160, 192, 255, 1)');
- ctx.fillStyle = grd;
- ctx.beginPath();
- ctx.moveTo(dir * 6 * (Math.random() - 0.5) + 12 * dir, 6 * (Math.random() - 0.5));
- ctx.lineTo(dir * 8, 14);
- ctx.lineTo(dir * 12, 14);
- ctx.fill();
- ctx.restore();
- }
-
- function thrustGraphic() {
- //ctx.fillStyle= "#90b0ff";
- ctx.save();
- //ctx.translate(0.5 * canvas.width, 0.5 * canvas.height)
- ctx.rotate(mass[0].angle);
- ctx.translate(0, -33);
- var grd = ctx.createLinearGradient(0, 0, 0, 15);
- grd.addColorStop(0, 'rgba(0, 0, 0, 0)');
- grd.addColorStop(1, 'rgba(160, 192, 255, 1)');
- ctx.fillStyle = grd;
- ctx.beginPath();
- ctx.moveTo(10 * (Math.random() - 0.5), 10 * (Math.random() - 0.5));
- ctx.lineTo(7, 20);
- ctx.lineTo(-7, 20);
- ctx.fill();
- ctx.restore();
- }
-
- function gravity() {
- var length = mass.length;
- var Dx = 0;
- var Dy = 0;
- var force = 0;
- var angle = 0;
- var i = 0;
- var j = 0;
- //gravity for array masses, but not player: mass[0]
- for (i = 0; i < length; i++) {
- for (j = 0; j < length; j++) {
- if (i != j) {
- Dx = mass[j].position.x - mass[i].position.x;
- Dy = mass[j].position.y - mass[i].position.y;
- force = game.gravity * mass[j].mass * mass[i].mass / (Math.sqrt(Dx * Dx + Dy * Dy));
- angle = Math.atan2(Dy, Dx);
- mass[i].force.x += force * Math.cos(angle);
- mass[i].force.y += force * Math.sin(angle);
- }
- }
- }
- //gravity for bullets
- var Blength = bullet.length;
- for (i = 0; i < Blength; i++) {
- for (j = 0; j < length; j++) { //bullets only feel gravity, they don't create it
- Dx = mass[j].position.x - bullet[i].position.x;
- Dy = mass[j].position.y - bullet[i].position.y;
- force = game.gravity * mass[j].mass * bullet[i].mass / (Math.sqrt(Dx * Dx + Dy * Dy));
- angle = Math.atan2(Dy, Dx);
- bullet[i].force.x += force * Math.cos(angle);
- bullet[i].force.y += force * Math.sin(angle);
- }
- }
- }
-
- function damage() { //changes player health if velocity changes too much
- var limit2 = 9; //square of velocity damamge limit
- var dX = Math.abs(mass[0].lastPlayerVelocity.x - mass[0].velocity.x);
- var dY = Math.abs(mass[0].lastPlayerVelocity.y - mass[0].velocity.y);
- var dV2 = dX * dX + dY * dY; //we are skipping the square root
- if (dV2 > limit2) { //did velocity change enough to take damage
- mass[0].durability -= Math.sqrt(dV2 - limit2) * 0.02; //player takes damage
- if (mass[0].durability < 0 && mass[0].alive) { //player dead?
- mass[0].alive = false;
- //spawn player explosion debris
- for (var j = 0; j < 10; j++) { //addMass(x, y, r, sides, Vx, Vy)
- addMass(mass[0].position.x + 10 * (0.5 - Math.random()),
- mass[0].position.y + 10 * (0.5 - Math.random()),
- 5,
- 3,
- (0.5 - Math.random()) * 8 + mass[0].velocity.x,
- (0.5 - Math.random()) * 8 + mass[0].velocity.y);
- }
- //shrink player to match debris size
- Matter.Body.scale(mass[0], 0.4, 0.4);
- //reset masses after a few seconds
- window.setTimeout(spawnSetup, 6000);
- }
- }
- //keep track of last player velocity to calculate changes in velocity
- mass[0].lastPlayerVelocity.x = mass[0].velocity.x;
- mass[0].lastPlayerVelocity.y = mass[0].velocity.y;
- }
-
- //bullet collision event
- Events.on(engine, 'collisionStart', function(event) {
-
- //slice the polygon up into sections
- function slicePoly(m, start, end) { //cut a mass into two sectons
- //build new string vector array that matches some of the reference mass
- var polyVector = '';
- for (var i = start; i < end; i++) {
- polyVector = polyVector.concat(mass[m].vertices[i].x + ' ' + mass[m].vertices[i].y + ' ');
- }
- //buggy: making polygons noncolide. not sure why
- //if (end = mass[m].vertices.length) { //catch the first vertices if the polygon hits the last
- // polyVector = polyVector.concat(mass[m].vertices[0].x + ' ' + mass[m].vertices[0].y + ' '); }
- var verticies = []; //build the polygon in matter.js
- var vector = Vertices.fromPath(polyVector);
-
- //add string vector array to game as a polygon
- var len = mass.length;
- mass.push();
- mass[len] = Matter.Bodies.fromVertices(0, 0, vector, { // x,y,vectors,{options}
- friction: 0,
- frictionStatic: 0,
- frictionAir: 0,
- restitution: 1
- });
- World.add(engine.world, mass[len]);
- //scale down the polygon a bit to help with collisions
- Matter.Body.scale(mass[len], 0.9, 0.9);
- //move polygon into position
- var vectorPos = Matter.Vertices.centre(vector); //find the center of new polygon
- Matter.Body.translate(mass[len], {
- x: vectorPos.x,
- y: vectorPos.y
- });
- //give a velocity pointed away from the old mass's center so it explodes
- var angle = Math.atan2(mass[len].position.y - mass[m].position.y, mass[len].position.x - mass[m].position.x);
- Matter.Body.setVelocity(mass[len], {
- x: mass[m].velocity.x + 2 * Math.cos(angle),
- y: mass[m].velocity.y + 2 * Math.sin(angle)
- });
- //add some spin
- Matter.Body.setAngularVelocity(mass[len], (Math.random() - 0.5) * 0.1);
- }
-
- function hit(i, b, m) {
- //match the collisions pair id to the mass
- for (var j = 1; j < mass.length; j++) { //start at 1 to skip the player
- if (mass[j].id === m.id) {
- //remove bullet
- Matter.World.remove(engine.world, bullet[i]);
- bullet.splice(i, 1);
-
- // explosion graphics
- var driftSpeed = 1;
- var length = mass[j].vertices.length - 1;
- var dx = (mass[j].vertices[length].x - mass[j].position.x);
- var dy = (mass[j].vertices[length].y - mass[j].position.y);
- var r = Math.sqrt(dx * dx + dy * dy) * 1.5; // *1.5 give the explosion outward spread
- var angle = Math.atan2(dy, dx);
- boom.push({ //the line form the 1st and last vertex
- x1: mass[j].vertices[length].x,
- y1: mass[j].vertices[length].y,
- x2: mass[j].vertices[0].x,
- y2: mass[j].vertices[0].y,
- alpha: 1,
- driftVx: mass[j].velocity.x + (Math.random() - 0.5) * driftSpeed + r * mass[j].angularSpeed * Math.cos(angle),
- driftVy: mass[j].velocity.y + (Math.random() - 0.5) * driftSpeed + r * mass[j].angularSpeed * Math.sin(angle),
- });
-
- for (var n = 0; n < length; n++) {
- dx = (mass[j].vertices[n].x - mass[j].position.x);
- dy = (mass[j].vertices[n].y - mass[j].position.y);
- r = Math.sqrt(dx * dx + dy * dy);
- angle = Math.atan2(dy, dx);
- boom.push({
- x1: mass[j].vertices[n].x,
- y1: mass[j].vertices[n].y,
- x2: mass[j].vertices[n + 1].x,
- y2: mass[j].vertices[n + 1].y,
- alpha: 1,
- driftVx: mass[j].velocity.x + (Math.random() - 0.5) * driftSpeed + r * mass[j].angularSpeed * Math.cos(angle),
- driftVy: mass[j].velocity.y + (Math.random() - 0.5) * driftSpeed + r * mass[j].angularSpeed * Math.sin(angle),
- });
- }
-
- //choose to slice
- if (mass[j].vertices.length > 13) {
- var cut = 6 + Math.floor(Math.random() * 4);
- slicePoly(j, 0, cut);
- //sliceChoices(mass.length - 1);
- slicePoly(j, cut - 1, mass[j].vertices.length);
- //sliceChoices(mass.length - 1);
- Matter.World.remove(engine.world, mass[j]);
- mass.splice(j, 1);
- } else {
- Matter.World.remove(engine.world, mass[j]);
- mass.splice(j, 1);
-
- }
-
- return;
- }
- }
- }
- //check to see if one of the collisison pairs is a bullet
- var pairs = event.pairs;
- for (var i = 0, j = pairs.length; i != j; ++i) {
- var pair = pairs[i];
- for (var k = 0; k < bullet.length; k++) {
- if (pair.bodyA === bullet[k]) {
- hit(k, pair.bodyA, pair.bodyB);
- repopulateMasses();
- break;
- } else if (pair.bodyB === bullet[k]) {
- hit(k, pair.bodyB, pair.bodyA);
- repopulateMasses();
- break;
- }
- }
- }
- });
-
- var boom = [];
-
- function explosions() {
- var i = boom.length;
- ctx.lineWidth = 1.5;
- while (i--) {
- ctx.strokeStyle = 'rgba(255, 255, 255, ' + boom[i].alpha + ')';
- //drift vector lines around
- boom[i].x1 += boom[i].driftVx;
- boom[i].y1 += boom[i].driftVy;
- boom[i].x2 += boom[i].driftVx;
- boom[i].y2 += boom[i].driftVy;
- //draw vector lines
- ctx.beginPath();
- ctx.moveTo(boom[i].x1, boom[i].y1);
- ctx.lineTo(boom[i].x2, boom[i].y2);
- ctx.stroke();
- //remove vector lines if they are too old
- boom[i].alpha -= 0.03;
- if (boom[i].alpha < 0.01) {
- boom.splice(i, 1);
- }
- }
- }
-
- //set up render
- var canvas = document.createElement('canvas'),
- ctx = canvas.getContext('2d');
-
- //make canvas fill window
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
- document.body.appendChild(canvas);
- window.onresize = function(event) {
- ctx.canvas.width = window.innerWidth;
- ctx.canvas.height = window.innerHeight;
- starsMoveRandom();
- };
-
- (function cycle() { //render loop
- game.cycle++;
- //ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'; //trails simulate a old arcade cathode look
- //ctx.fillRect(0, 0, canvas.width, canvas.height);
- ctx.clearRect(0, 0, canvas.width, canvas.height); //clear canvas for new cycle
- bulletEndCycle();
-
- damage();
- gravity();
- //background graphics
- drawStars();
-
- HUD();
- ctx.save(); //move camera
-
- ctx.translate(window.innerWidth * 0.5, window.innerHeight * 0.5);
- ctx.scale(game.scale, game.scale);
- controls();
- ctx.translate(-mass[0].position.x, -mass[0].position.y);
- //primary graphics
- render();
- drawVectors();
- explosions();
- ctx.restore(); //undo previous translation
- //foreground graphics
- miniMap();
- completionBar();
- durabilityBar();
- window.requestAnimationFrame(cycle);
- })();
-
- function render() { //draw all the objects from matter.js physics engine
- var bodies = Composite.allBodies(engine.world); //draw every object
- //fill and stroke each body
- ctx.lineWidth = 1.5;
- ctx.strokeStyle = '#ffffff';
- ctx.fillStyle = '#050505'; //'#111'; //'#0a0606';
- ctx.beginPath();
- for (var i = 0; i < bodies.length; i++) {
- var vertices = bodies[i].vertices;
- ctx.moveTo(vertices[0].x, vertices[0].y);
- for (var j = 1; j < vertices.length; j++) {
- ctx.lineTo(vertices[j].x, vertices[j].y);
- }
- ctx.lineTo(vertices[0].x, vertices[0].y);
- }
- ctx.fill();
- ctx.stroke();
- }
-
- function drawVectors() { //render force and velocity vectors for each mass
- if (game.HUD) {
- var length = mass.length;
- ctx.lineWidth = 1;
- //velocity vector
- //ctx.strokeStyle = 'rgba(0, 255, 255, 1)';
- ctx.strokeStyle = '#00ffff';
- ctx.beginPath();
- for (var i = 0; i < length; i++) {
- ctx.moveTo(mass[i].position.x, mass[i].position.y);
- ctx.lineTo(mass[i].position.x + 10 * mass[i].velocity.x,
- mass[i].position.y + 10 * mass[i].velocity.y);
- }
- ctx.stroke();
- //force vector
- //ctx.strokeStyle = 'rgba(255, 0, 255, 1)';
- ctx.strokeStyle = '#ff00ff';
- ctx.beginPath();
- for (i = 0; i < length; i++) {
- ctx.moveTo(mass[i].position.x, mass[i].position.y);
- ctx.lineTo(mass[i].position.x + 600000 * mass[i].force.x / mass[i].mass,
- mass[i].position.y + 600000 * mass[i].force.y / mass[i].mass);
- }
- ctx.stroke();
- //angular motion vector
- /* ctx.strokeStyle = 'rgba(255, 255, 00, 0.5)';//'#ff00ff';
- ctx.beginPath();
- if (mass[0].angularVelocity>0){
- ctx.arc(mass[0].position.x,mass[0].position.y,20,Math.PI*0.5,53*mass[0].angularVelocity+Math.PI*0.5);
- }else{
- ctx.arc(mass[0].position.x,mass[0].position.y,20,53*mass[0].angularVelocity+Math.PI*0.5,Math.PI*0.5);
- }
- ctx.stroke(); */
- }
- }
-
- function HUD() { //player data
- // document.getElementById("level").innerHTML = 'system ' + game.level
- if (game.HUD) { //testing and development data
- document.getElementById("hud").innerHTML = ' ' +
- //' | #: | ' + mass.length + ' |
' +
- //' | level: | ' + game.level + ' |
' +
- ' | t: | ' + engine.timing.timestamp.toFixed(0) + ' |
' +
- ' | m: | ' + mass[0].mass.toFixed(2) + ' |
' +
- ' | θ: | ' + (Math.abs(mass[0].angle % (Math.PI * 2))).toFixed(2) + ' |
' +
- ' | dθ: | ' + mass[0].angularVelocity.toFixed(3) + ' |
' +
- ' | x: | ' + mass[0].position.x.toFixed(0) + ' |
' +
- ' | y: | ' + mass[0].position.y.toFixed(0) + ' |
' +
- ' | Vx: | ' + mass[0].velocity.x.toFixed(2) + ' |
' +
- ' | Vy: | ' + mass[0].velocity.y.toFixed(2) + ' |
' +
- ' | Fx: | ' + mass[0].force.x.toFixed(6) + ' |
' +
- ' | Fy: | ' + mass[0].force.y.toFixed(6) + ' |
' + '
';
- } else {
- document.getElementById("hud").innerHTML = "";
- }
- }
-
- function durabilityBar() { // player health bar
- var size = 200;
- var x = 6;
- var y = 6;
- //ctx.lineWidth = 2;
- //ctx.strokeStyle = '#999';
- //ctx.strokeRect(x - 1, y - 1, size + 2, 12); //draw outline
- //ctx.fillStyle = '#512';
- ctx.fillStyle = 'rgba(255, 85, 119, 0.3)'
- ctx.fillRect(x, y, size, 10); //draw bar
- if (mass[0].alive) { //only draw bar if player is alive
- ctx.fillStyle = 'rgba(255, 85, 119, 0.9)'
- ctx.fillRect(x, y, size * mass[0].durability, 10); //draw bar
- }
- }
-
- function completionBar() {
- var size = 152;
- var x = canvas.width - size - 4;
- var y = 5;
-
- //ctx.fillStyle = '#000007';
- //ctx.fillRect(x, y, size, 5); //draw bar
- ctx.fillStyle = 'rgba(85, 85, 170, 0.3)'
- //ctx.fillStyle = '#55a';
- ctx.fillRect(x, y, size, 5); //draw bar
- //ctx.fillStyle = '#55a';
- ctx.fillStyle = 'rgba(85, 85, 170, 0.9)'
- ctx.fillRect(x, y, size * game.currentMass / game.startingMassValue, 5); //draw bar
- //goal line
- ctx.fillStyle = '#ffff00';
- ctx.fillRect(x + size * game.clearThreshold, y, 1, 5); //draw bar
- //outline of bar
- //ctx.lineWidth = 1;
- //ctx.strokeStyle = '#88f';
- //ctx.strokeRect(x, y, size, 5); //draw outline
- }
-
- function miniMap() {
- ctx.lineWidth = 1;
- ctx.strokeStyle = '#88f';
- ctx.beginPath();
- var xOff = canvas.width - 5;
- var yOff = 10;
- var size = 150;
- var scale = size / game.width;
- ctx.fillStyle = 'rgba(0, 0, 50, 0.5)'
- ctx.rect(xOff, yOff, -size, size);
- ctx.stroke();
- ctx.fill();
- //draw dot for masses
- ctx.fillStyle = '#aaf';
- for (var i = 1; i < mass.length; i++) {
- ctx.fillRect(mass[i].position.x * scale + xOff - size, mass[i].position.y * scale + yOff, 3, 3);
- }
- //draw player's dot
- ctx.fillStyle = '#ffff00';
- ctx.fillRect(mass[0].position.x * scale + xOff - size, mass[0].position.y * scale + yOff, 5, 5);
- }
-
- var star = [];
- var totalStars = 100;
- for (var i = 0; i < totalStars; i++) {
- star.push({
- x: Math.random() * window.innerWidth,
- y: Math.random() * window.innerHeight
- });
- }
-
- function starsMoveRandom() {
- for (var i = 0; i < totalStars; i++) {
- star[i].x = Math.random() * window.innerWidth;
- star[i].y = Math.random() * window.innerHeight;
- }
- }
-
- function drawStars() {
- ctx.fillStyle = '#ffffff'; //'darkgrey'; //'rgba(255, 255, 255, 0.5)'
- var parallax = 1;
- var Vx = mass[0].velocity.x * game.scale;
- var Vy = mass[0].velocity.y * game.scale;
- var width = window.innerWidth;
- var height = window.innerHeight;
- for (var i = 0; i < totalStars; i++) {
- star[i].x -= Vx;
- star[i].y -= Vy;
- ctx.fillRect(star[i].x, star[i].y, 1, 1);
- if (star[i].x < 0) {
- star[i].x = width;
- star[i].y = Math.random() * height;
- }
- if (star[i].x > width) {
- star[i].x = 0;
- star[i].y = Math.random() * height;
- }
- if (star[i].y < 0) {
- star[i].y = height;
- star[i].x = Math.random() * width;
- }
- if (star[i].y > height) {
- star[i].y = 0;
- star[i].x = Math.random() * width;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/js/player.js b/js/player.js
index c53e79d..ed03a72 100644
--- a/js/player.js
+++ b/js/player.js
@@ -514,7 +514,7 @@ const m = {
if (tech.isHarmArmor && m.lastHarmCycle + 600 > m.cycle) dmg *= 0.33;
if (tech.isNoFireDefense && m.cycle > m.fireCDcycle + 120) dmg *= 0.3
if (tech.energyRegen === 0) dmg *= 0.34
- if (tech.isTurret && m.crouch) dmg *= 0.55;
+ if (tech.isTurret && m.crouch) dmg *= 0.4;
if (tech.isEntanglement && b.inventory[0] === b.activeGun) {
for (let i = 0, len = b.inventory.length; i < len; i++) dmg *= 0.87 // 1 - 0.15
}
@@ -1002,8 +1002,7 @@ const m = {
fieldMeterColor: "#0cf",
drawFieldMeter(bgColor = "rgba(0, 0, 0, 0.4)", range = 60) {
if (m.energy < m.maxEnergy) {
- if (m.immuneCycle < m.cycle) m.energy += m.fieldRegen;
- if (m.energy < 0) m.energy = 0
+ m.regenEnergy();
ctx.fillStyle = bgColor;
const xOff = m.pos.x - m.radius * m.maxEnergy
const yOff = m.pos.y - 50
@@ -1018,9 +1017,30 @@ const m = {
ctx.fillStyle = m.fieldMeterColor;
ctx.fillRect(xOff, yOff, range * m.energy, 10);
}
- // else {
- // m.energy = m.maxEnergy
- // }
+ },
+ drawFieldMeterCloaking: function() {
+ if (m.energy < m.maxEnergy) { // replaces m.drawFieldMeter() with custom code
+ m.regenEnergy();
+ const xOff = m.pos.x - m.radius * m.maxEnergy
+ const yOff = m.pos.y - 50
+ ctx.fillStyle = "rgba(0, 0, 0, 0.3)" //
+ ctx.fillRect(xOff, yOff, 60 * m.maxEnergy, 10);
+ ctx.fillStyle = "#fff" //m.cycle > m.lastKillCycle + 300 ? "#000" : "#fff" //"#fff";
+ ctx.fillRect(xOff, yOff, 60 * m.energy, 10);
+ ctx.beginPath()
+ ctx.rect(xOff, yOff, 60 * m.maxEnergy, 10);
+ ctx.strokeStyle = m.fieldMeterColor;
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ }
+ },
+ regenEnergy: function() { //used in drawFieldMeter // rewritten by some tech
+ if (m.immuneCycle < m.cycle) m.energy += m.fieldRegen;
+ if (m.energy < 0) m.energy = 0
+ },
+ regenEnergyDefault: function() {
+ if (m.immuneCycle < m.cycle) m.energy += m.fieldRegen;
+ if (m.energy < 0) m.energy = 0
},
lookingAt(who) {
//calculate a vector from body to player and make it length 1
@@ -1301,7 +1321,7 @@ const m = {
if (m.energy < 0) m.energy = 0;
m.fieldCDcycle = m.cycle + m.fieldBlockCD;
if (tech.blockingIce) {
- for (let i = 0; i < fieldBlockCost * 50 * tech.blockingIce; i++) b.iceIX(3, 2 * Math.PI * Math.random(), m.pos)
+ for (let i = 0; i < fieldBlockCost * 60 * tech.blockingIce; i++) b.iceIX(3, 2 * Math.PI * Math.random(), m.pos)
}
const unit = Vector.normalise(Vector.sub(player.position, who.position))
if (tech.blockDmg) {
@@ -1536,12 +1556,12 @@ const m = {
}
m.harmonicRadius = 1 //for smoothing function when player holds mouse (for harmonicAtomic)
m.harmonicAtomic = () => { //several ellipses spinning about different axises
- const rotation = simulation.cycle * 0.002
- const phase = simulation.cycle * 0.03
+ const rotation = simulation.cycle * 0.0031
+ const phase = simulation.cycle * 0.023
const radius = m.fieldRange * m.harmonicRadius
ctx.lineWidth = 1;
- ctx.strokeStyle = "rgba(110,170,200,0.9)"
- ctx.fillStyle = "rgba(110,170,200," + Math.min(0.7, m.energy * (0.13 + 0.15 * Math.random()) * (3 / tech.harmonics)) + ")";
+ ctx.strokeStyle = "rgba(110,170,200,0.8)"
+ ctx.fillStyle = "rgba(110,170,200," + Math.min(0.7, m.energy * (0.13 + 0.1 * Math.random()) * (3 / tech.harmonics)) + ")";
// ctx.fillStyle = "rgba(110,170,200," + Math.min(0.7, m.energy * (0.22 - 0.01 * tech.harmonics) * (0.5 + 0.5 * Math.random())) + ")";
for (let i = 0; i < tech.harmonics; i++) {
ctx.beginPath();
@@ -1584,9 +1604,9 @@ const m = {
if (m.energy > 0.1 && m.fieldCDcycle < m.cycle) {
if (tech.isStandingWaveExpand) {
if (input.field) {
- const oldHarmonicRadius = m.harmonicRadius
+ // const oldHarmonicRadius = m.harmonicRadius
m.harmonicRadius = 0.985 * m.harmonicRadius + 0.015 * 2.5
- m.energy -= 0.1 * (m.harmonicRadius - oldHarmonicRadius)
+ // m.energy -= 0.1 * (m.harmonicRadius - oldHarmonicRadius)
} else {
m.harmonicRadius = 0.995 * m.harmonicRadius + 0.005
}
@@ -2494,21 +2514,7 @@ const m = {
}
}
- if (m.energy < m.maxEnergy) { // replaces m.drawFieldMeter() with custom code
- if (m.immuneCycle < m.cycle) m.energy += m.fieldRegen;
- if (m.energy < 0) m.energy = 0
- const xOff = m.pos.x - m.radius * m.maxEnergy
- const yOff = m.pos.y - 50
- ctx.fillStyle = "rgba(0, 0, 0, 0.3)" //
- ctx.fillRect(xOff, yOff, 60 * m.maxEnergy, 10);
- ctx.fillStyle = "#fff" //m.cycle > m.lastKillCycle + 300 ? "#000" : "#fff" //"#fff";
- ctx.fillRect(xOff, yOff, 60 * m.energy, 10);
- ctx.beginPath()
- ctx.rect(xOff, yOff, 60 * m.maxEnergy, 10);
- ctx.strokeStyle = m.fieldMeterColor;
- ctx.lineWidth = 1;
- ctx.stroke();
- }
+ this.drawFieldMeterCloaking()
//show sneak attack status
if (m.cycle > m.lastKillCycle + 240) {
diff --git a/js/powerup.js b/js/powerup.js
index f6c9254..e8b91c1 100644
--- a/js/powerup.js
+++ b/js/powerup.js
@@ -443,7 +443,7 @@ const powerUps = {
}
if (tech.healGiveMaxEnergy) {
- tech.healMaxEnergyBonus += 0.06
+ tech.healMaxEnergyBonus += 0.07
m.setMaxEnergy();
}
},
diff --git a/js/tech.js b/js/tech.js
index 81b36f7..2300bf3 100644
--- a/js/tech.js
+++ b/js/tech.js
@@ -556,19 +556,18 @@
this.refundAmount = 0
}
}
-
},
{
name: "gun turret",
- description: "reduce harm by 55% when crouching",
+ description: "reduce harm by 60% when crouching",
maxCount: 1,
count: 0,
frequency: 3,
frequencyDefault: 3,
allowed() {
- return tech.isCrouchAmmo && !tech.isEnergyHealth
+ return (tech.isCrouchAmmo && !tech.isEnergyHealth) || tech.isCrouchRegen
},
- requires: "desublimated ammunition, not mass-energy",
+ requires: "relative permittivity, desublimated ammunition, not mass-energy",
effect() {
tech.isTurret = true
},
@@ -2332,7 +2331,7 @@
{
name: "1st ionization energy",
link: `1st ionization energy`,
- description: `each ${powerUps.orb.heal()} you collect
increases your maximum energy by 6`,
+ description: `each ${powerUps.orb.heal()} you collect
increases your maximum energy by 7`,
maxCount: 1,
count: 0,
frequency: 2,
@@ -2358,8 +2357,8 @@
}
},
{
- name: "inductive coupling",
- description: "each unused power up at the end of a level
adds 3 maximum energy", // (up to 51 health per level)",
+ name: "permittivity",
+ description: "each unused power up at the end of a level
adds 4 maximum energy", // (up to 51 health per level)",
maxCount: 1,
count: 0,
frequency: 1,
@@ -2385,7 +2384,7 @@
allowed() {
return tech.isExtraMaxEnergy
},
- requires: "inductive coupling",
+ requires: "permittivity",
effect() {
tech.isEndLevelPowerUp = true;
},
@@ -2535,6 +2534,29 @@
}
}
},
+ {
+ name: "inductive coupling",
+ description: "passive energy regen is increased by 500%
but you only regen when crouched",
+ maxCount: 1,
+ count: 0,
+ frequency: 1,
+ frequencyDefault: 1,
+ allowed() {
+ return tech.energyRegen !== 0
+ },
+ requires: "not ground state",
+ effect() {
+ tech.isCrouchRegen = true; //only used to check for requirements
+ m.regenEnergy = function() {
+ if (m.immuneCycle < m.cycle && m.crouch) m.energy += 5 * m.fieldRegen; //m.fieldRegen = 0.001
+ if (m.energy < 0) m.energy = 0
+ }
+ },
+ remove() {
+ tech.isCrouchRegen = false;
+ m.regenEnergy = m.regenEnergyDefault
+ }
+ },
{
name: "energy conservation",
description: "5% of damage done recovered as energy",
@@ -4890,7 +4912,7 @@
allowed() {
return !tech.isExtraMaxEnergy && (tech.haveGunCheck("drones") || tech.isForeverDrones || (m.fieldUpgrades[m.fieldMode].name === "molecular assembler" && !(tech.isSporeField || tech.isMissileField || tech.isIceField)))
},
- requires: "drones, not inductive coupling",
+ requires: "drones, not permittivity",
effect() {
tech.isDroneGrab = true
},
@@ -5655,7 +5677,7 @@
},
{
name: "expansion",
- description: "using standing wave field uses energy
to temporarily expand its radius",
+ description: "using standing wave field temporarily
expands its radius",
// description: "use energy to expand standing wave
the field slowly contracts when not used",
isFieldTech: true,
maxCount: 1,
@@ -6956,10 +6978,10 @@
// },
{
name: "planetesimals",
- description: `spawn a tech and play planetesimals,
an annoying asteroids game with Newtonian physics`,
+ description: `play planetesimals
(an annoying asteroids game with Newtonian physics)
clearing a level in planetesimals spawns a tech in n-gon
but, if you die in planetesimals you die in n-gon`,
maxCount: 1,
count: 0,
- frequency: 0,
+ frequency: 100,
isNonRefundable: true,
isExperimentHide: true,
isJunk: true,
@@ -6969,7 +6991,24 @@
requires: "",
effect() {
window.open('../../planetesimals/index.html', '_blank')
- powerUps.spawn(m.pos.x, m.pos.y, "tech");
+ // powerUps.spawn(m.pos.x, m.pos.y, "tech");
+
+ // for communicating to other tabs, like planetesimals
+ // Connection to a broadcast channel
+ const bc = new BroadcastChannel('planetesimals');
+ bc.activated = false
+
+ bc.onmessage = function(ev) {
+ if (ev.data === 'tech') powerUps.directSpawn(m.pos.x, m.pos.y, "tech");
+ if (ev.data === 'death') {
+ m.death()
+ bc.close(); //end session
+ }
+ if (ev.data === 'ready' && !bc.activated) {
+ bc.activated = true //prevents n-gon from activating multiple copies of planetesimals
+ bc.postMessage("activate");
+ }
+ }
},
remove() {}
},
@@ -7096,9 +7135,7 @@
ctx.beginPath();
const vertices = mob[i].vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
- for (let j = 1, len = vertices.length; j < len; ++j) {
- ctx.quadraticCurveTo(mob[i].position.x, mob[i].position.y, vertices[j].x, vertices[j].y);
- }
+ for (let j = 1, len = vertices.length; j < len; ++j) ctx.quadraticCurveTo(mob[i].position.x, mob[i].position.y, vertices[j].x, vertices[j].y);
ctx.quadraticCurveTo(mob[i].position.x, mob[i].position.y, vertices[0].x, vertices[0].y);
ctx.fillStyle = mob[i].fill;
ctx.strokeStyle = mob[i].stroke;
@@ -8824,5 +8861,6 @@
baseFx: null,
isNeutronium: null,
isFreeWormHole: null,
- isRewindField: null
+ isRewindField: null,
+ isCrouchRegen: null
}
\ No newline at end of file
diff --git a/todo.txt b/todo.txt
index 1177c6b..4271e35 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,13 +1,28 @@
******************************************************** NEXT PATCH **************************************************
-grenades display their trajectory, to help you aim
- I'm might get rid of it, but for now we'll try it out
+tech: inductive coupling - regen is increased by 500%, but you only regen when crouched
-several duplication tech give slightly lower duplication chance
-strange attractor now properly includes all your tech in duplication chance (it wasn't updated for recent duplication tech)
+tech gun turret gives 55% -> 60% harm reduction
+ also I fixed a bug where it was giving 45% not 55%
+
+old tech inductive coupling is renamed: permittivity
+permittivity gives 3 -> 4 max energy per unused power up
+1st ionization energy gives 6 -> 7 max energy per heal
+
+tech expansion - no longer costs energy to expand standing wave field
+
+JUNK tech planetesimals now can spawn tech in n-gon
+ or kill the player in n-gon
******************************************************** TODO ********************************************************
+tech: open a new tab for n-gon, spawn things in the original game based on events in new game
+ if you die in new die in original?
+ new is n-gon classic?
+ make a JUNK tech?
+if you die in original open a tab with a new n-gon that starts on a random level with a random load out. if you clear the level you come back to life in the original?
+
+
give all duplicated power ups a half life that scales with the duplication chance
metastability reduces the half life
how to communicate the half-life?