mod: causality bots - before you rewind, build bots that protect you for for 7 seconds

mod grenade: causality bombs - before you rewind, drop some grenades
This commit is contained in:
landgreen
2020-12-09 17:39:13 -08:00
parent 15f2c00fe8
commit 46fbb90d8d
6 changed files with 625 additions and 395 deletions

View File

@@ -274,6 +274,307 @@ const b = {
}
}
},
grenade() {
},
setGrenadeMode() {
grenadeDefault = function(where = { x: mech.pos.x + 30 * Math.cos(mech.angle), y: mech.pos.y + 30 * Math.sin(mech.angle) }, angle = mech.angle) {
const me = bullet.length;
bullet[me] = Bodies.circle(where.x, where.y, 15, b.fireAttributes(angle, false));
Matter.Body.setDensity(bullet[me], 0.0005);
bullet[me].explodeRad = 275;
bullet[me].onEnd = function() {
b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
if (mod.grenadeFragments) b.targetedNail(this.position, mod.grenadeFragments)
}
bullet[me].minDmgSpeed = 1;
bullet[me].beforeDmg = function() {
this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion
};
speed = mech.crouch ? 43 : 32
Matter.Body.setVelocity(bullet[me], {
x: mech.Vx / 2 + speed * Math.cos(angle),
y: mech.Vy / 2 + speed * Math.sin(angle)
});
bullet[me].endCycle = game.cycle + Math.floor(mech.crouch ? 120 : 80);
bullet[me].restitution = 0.4;
bullet[me].do = function() {
this.force.y += this.mass * 0.0025; //extra gravity for harder arcs
};
World.add(engine.world, bullet[me]); //add bullet to world
}
grenadeRPG = function(where = { x: mech.pos.x + 30 * Math.cos(mech.angle), y: mech.pos.y + 30 * Math.sin(mech.angle) }, angle = mech.angle) {
const me = bullet.length;
bullet[me] = Bodies.circle(where.x, where.y, 15, b.fireAttributes(angle, false));
Matter.Body.setDensity(bullet[me], 0.0005);
bullet[me].explodeRad = 275;
bullet[me].onEnd = function() {
b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
if (mod.grenadeFragments) b.targetedNail(this.position, mod.grenadeFragments)
}
bullet[me].minDmgSpeed = 1;
bullet[me].beforeDmg = function() {
this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion
};
speed = mech.crouch ? 43 : 32
Matter.Body.setVelocity(bullet[me], {
x: mech.Vx / 2 + speed * Math.cos(angle),
y: mech.Vy / 2 + speed * Math.sin(angle)
});
World.add(engine.world, bullet[me]); //add bullet to world
bullet[me].endCycle = game.cycle + 70;
bullet[me].frictionAir = 0.07;
const MAG = 0.015
bullet[me].thrust = {
x: bullet[me].mass * MAG * Math.cos(angle),
y: bullet[me].mass * MAG * Math.sin(angle)
}
bullet[me].do = function() {
this.force.x += this.thrust.x;
this.force.y += this.thrust.y;
if (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length) {
this.endCycle = 0; //explode if touching map or blocks
}
};
}
grenadeVacuum = function(where = { x: mech.pos.x + 30 * Math.cos(mech.angle), y: mech.pos.y + 30 * Math.sin(mech.angle) }, angle = mech.angle) {
const me = bullet.length;
bullet[me] = Bodies.circle(where.x, where.y, 20, b.fireAttributes(angle, false));
Matter.Body.setDensity(bullet[me], 0.0003);
bullet[me].explodeRad = 325 + Math.floor(Math.random() * 50);;
bullet[me].onEnd = function() {
b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
if (mod.grenadeFragments) b.targetedNail(this.position, mod.grenadeFragments)
}
bullet[me].beforeDmg = function() {};
bullet[me].restitution = 0.4;
bullet[me].do = function() {
this.force.y += this.mass * 0.0025; //extra gravity for harder arcs
const suckCycles = 40
if (game.cycle > this.endCycle - suckCycles) { //suck
const that = this
function suck(who, radius = that.explodeRad * 3.2) {
for (i = 0, len = who.length; i < len; i++) {
const sub = Vector.sub(that.position, who[i].position);
const dist = Vector.magnitude(sub);
if (dist < radius && dist > 150) {
knock = Vector.mult(Vector.normalise(sub), mag * who[i].mass / Math.sqrt(dist));
who[i].force.x += knock.x;
who[i].force.y += knock.y;
}
}
}
let mag = 0.1
if (game.cycle > this.endCycle - 5) {
mag = -0.22
suck(mob, this.explodeRad * 3)
suck(body, this.explodeRad * 2)
suck(powerUp, this.explodeRad * 1.5)
suck(bullet, this.explodeRad * 1.5)
suck([player], this.explodeRad * 1.3)
} else {
mag = 0.11
suck(mob, this.explodeRad * 3)
suck(body, this.explodeRad * 2)
suck(powerUp, this.explodeRad * 1.5)
suck(bullet, this.explodeRad * 1.5)
suck([player], this.explodeRad * 1.3)
}
//keep bomb in place
Matter.Body.setVelocity(this, {
x: 0,
y: 0
});
//draw suck
const radius = 2.75 * this.explodeRad * (this.endCycle - game.cycle) / suckCycles
ctx.fillStyle = "rgba(0,0,0,0.1)";
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, radius, 0, 2 * Math.PI);
ctx.fill();
}
};
speed = 35
bullet[me].endCycle = game.cycle + 70;
if (mech.crouch) {
speed += 9
bullet[me].endCycle += 20;
}
Matter.Body.setVelocity(bullet[me], {
x: mech.Vx / 2 + speed * Math.cos(angle),
y: mech.Vy / 2 + speed * Math.sin(angle)
});
World.add(engine.world, bullet[me]); //add bullet to world
}
grenadeNeutron = function(where = { x: mech.pos.x + 30 * Math.cos(mech.angle), y: mech.pos.y + 30 * Math.sin(mech.angle) }, angle = mech.angle) {
const me = bullet.length;
bullet[me] = Bodies.polygon(where.x, where.y, 10, 4, b.fireAttributes(angle, false));
b.fireProps(mech.crouch ? 45 : 25, mech.crouch ? 35 : 20, angle, me); //cd , speed
Matter.Body.setDensity(bullet[me], 0.000001);
bullet[me].endCycle = Infinity;
bullet[me].frictionAir = 0;
bullet[me].friction = 1;
bullet[me].frictionStatic = 1;
bullet[me].restitution = 0;
bullet[me].minDmgSpeed = 0;
bullet[me].damageRadius = 100;
bullet[me].maxDamageRadius = 450 + 130 * mod.isNeutronSlow + 130 * mod.isNeutronImmune //+ 150 * Math.random()
bullet[me].radiusDecay = (0.81 + 0.15 * mod.isNeutronSlow + 0.15 * mod.isNeutronImmune) / mod.isBulletsLastLonger
bullet[me].stuckTo = null;
bullet[me].stuckToRelativePosition = null;
bullet[me].vacuumSlow = 0.97;
if (mod.isRewindGrenade && input.down) {
Matter.Body.setVelocity(bullet[me], {
x: 0,
y: 0
});
bullet[me].maxDamageRadius *= 1.3
mech.rewind(200, false)
}
bullet[me].beforeDmg = function() {};
bullet[me].stuck = function() {};
bullet[me].do = function() {
function onCollide(that) {
that.collisionFilter.mask = 0; //non collide with everything
Matter.Body.setVelocity(that, {
x: 0,
y: 0
});
that.do = that.radiationMode;
}
const mobCollisions = Matter.Query.collides(this, mob)
if (mobCollisions.length) {
onCollide(this)
this.stuckTo = mobCollisions[0].bodyA
mobs.statusDoT(this.stuckTo, 0.5, 360) //apply radiation damage status effect on direct hits
if (this.stuckTo.isVerticesChange) {
this.stuckToRelativePosition = {
x: 0,
y: 0
}
} else {
//find the relative position for when the mob is at angle zero by undoing the mobs rotation
this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle)
}
this.stuck = function() {
if (this.stuckTo && this.stuckTo.alive) {
const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector
Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position))
Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck
} else {
this.collisionFilter.mask = cat.map | cat.body | cat.player | cat.mob; //non collide with everything but map
this.stuck = function() {
this.force.y += this.mass * 0.001;
}
}
}
} else {
const bodyCollisions = Matter.Query.collides(this, body)
if (bodyCollisions.length) {
if (!bodyCollisions[0].bodyA.isNotHoldable) {
onCollide(this)
this.stuckTo = bodyCollisions[0].bodyA
//find the relative position for when the mob is at angle zero by undoing the mobs rotation
this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle)
} else {
this.do = this.radiationMode;
}
this.stuck = function() {
if (this.stuckTo) {
const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector
Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position))
// Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck
} else {
this.force.y += this.mass * 0.001;
}
}
} else {
if (Matter.Query.collides(this, map).length) {
onCollide(this)
} else { //if colliding with nothing just fall
this.force.y += this.mass * 0.001;
}
}
}
}
bullet[me].radiationMode = function() { //the do code after the bullet is stuck on something, projects a damaging radiation field
this.stuck(); //runs different code based on what the bullet is stuck to
if (!mech.isBodiesAsleep) {
this.damageRadius = this.damageRadius * 0.85 + 0.15 * this.maxDamageRadius //smooth radius towards max
this.maxDamageRadius -= this.radiusDecay
if (this.damageRadius < 15) {
this.endCycle = 0;
} else {
//aoe damage to player
if (!mod.isNeutronImmune && Vector.magnitude(Vector.sub(player.position, this.position)) < this.damageRadius) {
const DRAIN = 0.0023
if (mech.energy > DRAIN) {
mech.energy -= DRAIN
} else {
mech.energy = 0;
mech.damage(0.00015)
}
}
//aoe damage to mobs
for (let i = 0, len = mob.length; i < len; i++) {
if (Vector.magnitude(Vector.sub(mob[i].position, this.position)) < this.damageRadius) {
let dmg = b.dmgScale * 0.082
if (Matter.Query.ray(map, mob[i].position, this.position).length > 0) dmg *= 0.25 //reduce damage if a wall is in the way
if (mob[i].shield) dmg *= 4 //x5 to make up for the /5 that shields normally take
mob[i].damage(dmg);
mob[i].locatePlayer();
if (mod.isNeutronSlow) {
Matter.Body.setVelocity(mob[i], {
x: mob[i].velocity.x * this.vacuumSlow,
y: mob[i].velocity.y * this.vacuumSlow
});
}
}
}
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.damageRadius, 0, 2 * Math.PI);
ctx.globalCompositeOperation = "lighter"
ctx.fillStyle = `rgba(25,139,170,${0.2+0.06*Math.random()})`;
ctx.fill();
ctx.globalCompositeOperation = "source-over"
if (mod.isNeutronSlow) {
const that = this
function slow(who, radius = that.explodeRad * 3.2) {
for (i = 0, len = who.length; i < len; i++) {
const sub = Vector.sub(that.position, who[i].position);
const dist = Vector.magnitude(sub);
if (dist < radius) {
Matter.Body.setVelocity(who[i], {
x: who[i].velocity.x * that.vacuumSlow,
y: who[i].velocity.y * that.vacuumSlow
});
}
}
}
slow(body, this.damageRadius)
slow([player], this.damageRadius)
}
}
}
}
}
if (mod.isNeutronBomb) {
b.grenade = grenadeNeutron
} else if (mod.isVacuumBomb) {
b.grenade = grenadeVacuum
} else if (mod.isRPG) {
b.grenade = grenadeRPG
} else {
b.grenade = grenadeDefault
}
},
missile(where, angle, speed, size = 1, spawn = 0) {
const me = bullet.length;
bullet[me] = Bodies.rectangle(where.x, where.y, 30 * size, 4 * size, {
@@ -1237,16 +1538,16 @@ const b = {
}
}
},
randomBot(where = mech.pos, isKeep = true) {
if (Math.random() < 0.2) {
b.orbitBot();
if (isKeep) mod.orbitBotCount++;
} else if (Math.random() < 0.25) {
b.nailBot(where)
if (isKeep) mod.nailBotCount++;
} else if (Math.random() < 0.33) {
randomBot(where = mech.pos, isKeep = true, isAll = true) {
if (Math.random() < 0.2 && isAll) {
b.laserBot(where)
if (isKeep) mod.laserBotCount++;
} else if (Math.random() < 0.25 && isAll) {
b.orbitBot();
if (isKeep) mod.orbitBotCount++;
} else if (Math.random() < 0.33) {
b.nailBot(where)
if (isKeep) mod.nailBotCount++;
} else if (Math.random() < 0.5) {
b.foamBot(where)
if (isKeep) mod.foamBotCount++;
@@ -2334,32 +2635,6 @@ const b = {
const wiggle = Vector.mult(transverse, wiggleMag * Math.cos(this.cycle * 0.35) * ((i % 2) ? -1 : 1))
Matter.Body.setPosition(this, Vector.add(this.position, wiggle))
}
// if (mod.isWaveReflect) { //single reflection
// const sub = Vector.sub(this.position, mech.pos)
// if (Vector.magnitude(sub) > 630) {
// // Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(Vector.normalise(sub), -2 * POCKET_RANGE))) //teleport to opposite side
// if (!this.isJustReflected) {
// Matter.Body.setVelocity(this, Vector.mult(this.velocity, -1)); //reflect
// this.isJustReflected = true;
// }
// }
// }
// if (mod.isWaveReflect) {
// Matter.Body.setPosition(this, Vector.add(this.position, player.velocity)) //bullets move with player
// Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(Vector.normalise(sub), -2 * POCKET_RANGE))) //teleport to opposite side
// const sub = Vector.sub(this.position, mech.pos)
// if (Vector.magnitude(sub) > 630) {
// if (!this.isJustReflected) {
// Matter.Body.setVelocity(this, Vector.mult(this.velocity, -1)); //reflect
// this.isJustReflected = true;
// }
// } else {
// this.isJustReflected = false
// }
// }
}
});
World.add(engine.world, bullet[me]); //add bullet to world
@@ -2431,48 +2706,6 @@ const b = {
}
}
},
// {
// name: "flak",
// description: "fire a <strong>cluster</strong> of short range <strong>projectiles</strong><br><strong class='color-e'>explodes</strong> on <strong>contact</strong> or after half a second",
// ammo: 0,
// ammoPack: 4,
// defaultAmmoPack: 4, //use to revert ammoPack after mod changes drop rate
// have: false,
// fire() {
// mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 25 : 10) * b.fireCD); // cool down
// b.muzzleFlash(30);
// const SPEED = mech.crouch ? 29 : 25
// const END = Math.floor(mech.crouch ? 30 : 18);
// const side1 = 17
// const side2 = 4
// const totalBullets = 6
// const angleStep = (mech.crouch ? 0.06 : 0.25) / totalBullets
// let dir = mech.angle - angleStep * totalBullets / 2;
// for (let i = 0; i < totalBullets; i++) { //5 -> 7
// dir += angleStep
// const me = bullet.length;
// bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), side1, side2, b.fireAttributes(dir));
// World.add(engine.world, bullet[me]); //add bullet to world
// Matter.Body.setVelocity(bullet[me], {
// x: (SPEED + 15 * Math.random() - 2 * i) * Math.cos(dir),
// y: (SPEED + 15 * Math.random() - 2 * i) * Math.sin(dir)
// });
// bullet[me].endCycle = 2 * i + game.cycle + END
// bullet[me].restitution = 0;
// bullet[me].friction = 1;
// bullet[me].explodeRad = (mech.crouch ? 95 : 75) + (Math.random() - 0.5) * 50;
// bullet[me].onEnd = function() {
// b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
// }
// bullet[me].beforeDmg = function() {
// this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion
// };
// bullet[me].do = function() {
// // this.force.y += this.mass * 0.0004;
// }
// }
// }
// },
{
name: "grenades",
description: "lob a single <strong>bouncy</strong> projectile<br><strong class='color-e'>explodes</strong> on <strong>contact</strong> or after one second",
@@ -2480,46 +2713,8 @@ const b = {
ammoPack: 5,
have: false,
fire() {
},
fireNormal() {
const me = bullet.length;
const dir = mech.angle; // + Math.random() * 0.05;
bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 15, b.fireAttributes(dir, false));
Matter.Body.setDensity(bullet[me], 0.0005);
bullet[me].explodeRad = 275;
bullet[me].onEnd = function() {
b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
if (mod.grenadeFragments) b.targetedNail(this.position, mod.grenadeFragments)
}
bullet[me].minDmgSpeed = 1;
bullet[me].beforeDmg = function() {
this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion
};
if (mod.isRPG) {
b.fireProps(35, mech.crouch ? 60 : -15, dir, me); //cd , speed
bullet[me].endCycle = game.cycle + 70;
bullet[me].frictionAir = 0.07;
const MAG = 0.015
bullet[me].thrust = {
x: bullet[me].mass * MAG * Math.cos(dir),
y: bullet[me].mass * MAG * Math.sin(dir)
}
bullet[me].do = function() {
this.force.x += this.thrust.x;
this.force.y += this.thrust.y;
if (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length) {
this.endCycle = 0; //explode if touching map or blocks
}
};
} else {
b.fireProps(mech.crouch ? 40 : 30, mech.crouch ? 43 : 32, dir, me); //cd , speed
bullet[me].endCycle = game.cycle + Math.floor(mech.crouch ? 120 : 80);
bullet[me].restitution = 0.4;
bullet[me].do = function() {
this.force.y += this.mass * 0.0025; //extra gravity for harder arcs
};
}
mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 40 : 30) * b.fireCD); // cool down
b.grenade()
},
fireNeutron() {
const me = bullet.length;
@@ -2539,6 +2734,14 @@ const b = {
bullet[me].stuckTo = null;
bullet[me].stuckToRelativePosition = null;
bullet[me].vacuumSlow = 0.97;
if (mod.isRewindGrenade && input.down) {
Matter.Body.setVelocity(bullet[me], {
x: 0,
y: 0
});
bullet[me].maxDamageRadius *= 1.3
mech.rewind(200, false)
}
bullet[me].beforeDmg = function() {};
bullet[me].stuck = function() {};
bullet[me].do = function() {
@@ -2669,69 +2872,6 @@ const b = {
}
}
},
fireVacuum() {
const me = bullet.length;
const dir = mech.angle; // + Math.random() * 0.05;
bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 20, b.fireAttributes(dir, false));
Matter.Body.setDensity(bullet[me], 0.0003);
bullet[me].explodeRad = 350 + Math.floor(Math.random() * 50);;
bullet[me].onEnd = function() {
b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
if (mod.grenadeFragments) b.targetedNail(this.position, mod.grenadeFragments)
}
bullet[me].beforeDmg = function() {};
const cd = mech.crouch ? 90 : 75
b.fireProps(cd, mech.crouch ? 46 : 35, dir, me); //cd , speed
bullet[me].endCycle = game.cycle + cd;
bullet[me].restitution = 0.4;
bullet[me].do = function() {
this.force.y += this.mass * 0.0025; //extra gravity for harder arcs
const suckCycles = 40
if (game.cycle > this.endCycle - suckCycles) { //suck
const that = this
function suck(who, radius = that.explodeRad * 3.2) {
for (i = 0, len = who.length; i < len; i++) {
const sub = Vector.sub(that.position, who[i].position);
const dist = Vector.magnitude(sub);
if (dist < radius && dist > 150) {
knock = Vector.mult(Vector.normalise(sub), mag * who[i].mass / Math.sqrt(dist));
who[i].force.x += knock.x;
who[i].force.y += knock.y;
}
}
}
let mag = 0.1
if (game.cycle > this.endCycle - 5) {
mag = -0.22
suck(mob, this.explodeRad * 3)
suck(body, this.explodeRad * 2)
suck(powerUp, this.explodeRad * 1.5)
suck(bullet, this.explodeRad * 1.5)
suck([player], this.explodeRad * 1.3)
} else {
mag = 0.11
suck(mob, this.explodeRad * 3)
suck(body, this.explodeRad * 2)
suck(powerUp, this.explodeRad * 1.5)
suck(bullet, this.explodeRad * 1.5)
suck([player], this.explodeRad * 1.3)
}
//keep bomb in place
Matter.Body.setVelocity(this, {
x: 0,
y: 0
});
//draw suck
const radius = 2.75 * this.explodeRad * (this.endCycle - game.cycle) / suckCycles
ctx.fillStyle = "rgba(0,0,0,0.1)";
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, radius, 0, 2 * Math.PI);
ctx.fill();
}
};
}
},
{
name: "mine",
@@ -2959,7 +3099,7 @@ const b = {
if (mod.isRailAreaDamage) {
mob[i].force.x += 2 * FORCE.x;
mob[i].force.y += 2 * FORCE.y;
const damage = b.dmgScale * 0.1 * Math.sqrt(DEPTH)
const damage = b.dmgScale * 0.13 * Math.sqrt(DEPTH)
mob[i].damage(damage);
mob[i].locatePlayer();
game.drawList.push({ //add dmg to draw queue

View File

@@ -17,11 +17,11 @@ const level = {
// game.zoomScale = 1000;
// game.setZoom();
// mech.setField("wormhole")
// b.giveGuns("shotgun")
// b.giveGuns("grenades")
// mod.isIncendiary = true
// mod.is3Missiles = true
// mod.giveMod("shotgun slug")
// mod.giveMod("diffuse beam")
// mod.giveMod("CPT reversal")
// mod.giveMod("causality bombs")
level.intro(); //starting level
// level.testing(); //not in rotation

View File

@@ -108,21 +108,6 @@ const mod = {
return mod.foamBotCount + mod.nailBotCount + mod.laserBotCount + mod.boomBotCount + mod.plasmaBotCount + mod.orbitBotCount
},
mods: [{
name: "integrated armament",
description: "increase <strong class='color-d'>damage</strong> by <strong>25%</strong><br>your inventory can only hold <strong>1 gun</strong>",
maxCount: 1,
count: 0,
allowed() {
return b.inventory.length < 2
},
requires: "no more than 1 gun",
effect() {
mod.isOneGun = true;
},
remove() {
mod.isOneGun = false;
}
}, {
name: "capacitor",
description: "increase <strong class='color-d'>damage</strong> by <strong>1%</strong><br>for every <strong>7</strong> stored <strong class='color-f'>energy</strong>",
maxCount: 1,
@@ -176,7 +161,7 @@ const mod = {
maxCount: 1,
count: 0,
allowed() {
return mod.isEnergyLoss && mech.maxEnergy === 1 && !mod.isMissileField && !mod.isSporeField && !mod.isTimeAvoidDeath
return mod.isEnergyLoss && mech.maxEnergy === 1 && !mod.isMissileField && !mod.isSporeField && !mod.isRewindAvoidDeath
},
requires: "heat engine, not max energy increase, CPT, missile or spore nano-scale",
effect() {
@@ -210,9 +195,9 @@ const mod = {
maxCount: 6,
count: 0,
allowed() {
return true
return mech.Fx === 0.016
},
requires: "",
requires: "base movement speed",
effect: () => {
mod.restDamage += 0.25
},
@@ -252,6 +237,22 @@ const mod = {
mod.isAcidDmg = false;
}
},
{
name: "integrated armament",
description: "increase <strong class='color-d'>damage</strong> by <strong>25%</strong><br>your inventory can only hold <strong>1 gun</strong>",
maxCount: 1,
count: 0,
allowed() {
return b.inventory.length < 2
},
requires: "no more than 1 gun",
effect() {
mod.isOneGun = true;
},
remove() {
mod.isOneGun = false;
}
},
{
name: "negative feedback",
description: "increase <strong class='color-d'>damage</strong> by <strong>6%</strong><br>for every <strong>10</strong> missing base <strong>health</strong>",
@@ -371,78 +372,6 @@ const mod = {
mod.throwChargeRate = 1
}
},
{
name: "reaction inhibitor",
description: "mobs spawn with <strong>12%</strong> less <strong>health</strong>",
maxCount: 3,
count: 0,
allowed() {
return mod.nailsDeathMob || mod.sporesOnDeath || mod.isExplodeMob
},
requires: "zoospore vector or impact shear or thermal runaway",
effect: () => {
mod.mobSpawnWithHealth *= 0.88
//set all mobs at full health to 0.85
for (let i = 0; i < mob.length; i++) {
if (mob.health > mod.mobSpawnWithHealth) mob.health = mod.mobSpawnWithHealth
}
},
remove() {
mod.mobSpawnWithHealth = 1;
}
},
{
name: "zoospore vector",
description: "mobs produce <strong class='color-p' style='letter-spacing: 2px;'>spores</strong> when they <strong>die</strong><br><strong>9%</strong> chance",
maxCount: 9,
count: 0,
allowed() {
return !mod.nailsDeathMob && !mod.isExplodeMob
},
requires: "not impact shear or thermal runaway",
effect() {
mod.sporesOnDeath += 0.09;
for (let i = 0; i < 8; i++) {
b.spore(mech.pos)
}
},
remove() {
mod.sporesOnDeath = 0;
}
},
{
name: "impact shear",
description: "mobs release a <strong>nail</strong> when they <strong>die</strong><br>nails target nearby mobs",
maxCount: 9,
count: 0,
allowed() {
return !mod.sporesOnDeath && !mod.isExplodeMob
},
requires: "not zoospore vector or thermal runaway",
effect: () => {
mod.nailsDeathMob++
},
remove() {
mod.nailsDeathMob = 0;
}
},
{
name: "thermal runaway",
description: "mobs <strong class='color-e'>explode</strong> when they <strong>die</strong><br><em>be careful</em>",
maxCount: 1,
count: 0,
allowed() {
return (mod.haveGunCheck("missiles") || mod.isIncendiary || (mod.haveGunCheck("grenades") && !mod.isNeutronBomb) || mod.haveGunCheck("vacuum bomb") || mod.isPulseLaser || mod.isMissileField || mod.boomBotCount > 1 || mod.isFlechetteExplode) && !mod.sporesOnDeath && !mod.nailsDeathMob
},
requires: "an explosive damage source, not zoospore vector or impact shear",
effect: () => {
mod.isExplodeMob = true;
},
remove() {
mod.isExplodeMob = false;
}
},
{
name: "ammonium nitrate",
description: "increase <strong class='color-e'>explosive</strong> <strong class='color-d'>damage</strong> by <strong>20%</strong><br>increase <strong class='color-e'>explosive</strong> <strong>radius</strong> by <strong>20%</strong>",
@@ -508,15 +437,87 @@ const mod = {
mod.isImmuneExplosion = false;
}
},
{
name: "thermal runaway",
description: "mobs <strong class='color-e'>explode</strong> when they <strong>die</strong><br><em>be careful</em>",
maxCount: 1,
count: 0,
allowed() {
return (mod.haveGunCheck("missiles") || mod.isIncendiary || (mod.haveGunCheck("grenades") && !mod.isNeutronBomb) || mod.haveGunCheck("vacuum bomb") || mod.isPulseLaser || mod.isMissileField || mod.boomBotCount > 1 || mod.isFlechetteExplode) && !mod.sporesOnDeath && !mod.nailsDeathMob && !mod.isBotSpawner
},
requires: "an explosive damage source, no other mob death mods",
effect: () => {
mod.isExplodeMob = true;
},
remove() {
mod.isExplodeMob = false;
}
},
{
name: "reaction inhibitor",
description: "mobs spawn with <strong>12%</strong> less <strong>health</strong>",
maxCount: 3,
count: 0,
allowed() {
return mod.nailsDeathMob || mod.sporesOnDeath || mod.isExplodeMob || mod.isBotSpawner
},
requires: "any mob death mod",
effect: () => {
mod.mobSpawnWithHealth *= 0.88
//set all mobs at full health to 0.85
for (let i = 0; i < mob.length; i++) {
if (mob.health > mod.mobSpawnWithHealth) mob.health = mod.mobSpawnWithHealth
}
},
remove() {
mod.mobSpawnWithHealth = 1;
}
},
{
name: "zoospore vector",
description: "mobs produce <strong class='color-p' style='letter-spacing: 2px;'>spores</strong> when they <strong>die</strong><br><strong>9%</strong> chance",
maxCount: 9,
count: 0,
allowed() {
return !mod.nailsDeathMob && !mod.isExplodeMob && !mod.isBotSpawner
},
requires: "no other mob death mods",
effect() {
mod.sporesOnDeath += 0.09;
for (let i = 0; i < 8; i++) {
b.spore(mech.pos)
}
},
remove() {
mod.sporesOnDeath = 0;
}
},
{
name: "impact shear",
description: "mobs release a <strong>nail</strong> when they <strong>die</strong><br>nails target nearby mobs",
maxCount: 9,
count: 0,
allowed() {
return !mod.sporesOnDeath && !mod.isExplodeMob && !mod.isBotSpawner
},
requires: "no other mob death mods",
effect: () => {
mod.nailsDeathMob++
},
remove() {
mod.nailsDeathMob = 0;
}
},
{
name: "scrap bots",
description: "<strong>20%</strong> chance to build a <strong>bot</strong> after killing a mob<br>the bot lasts for about <strong>20</strong> seconds",
maxCount: 3,
count: 0,
allowed() {
return mod.totalBots() > 0
return mod.totalBots() > 0 && !mod.sporesOnDeath && !mod.nailsDeathMob && !mod.isExplodeMob
},
requires: "a bot",
requires: "a bot and no other mob death mods",
effect() {
mod.isBotSpawner += 0.20;
},
@@ -524,23 +525,6 @@ const mod = {
mod.isBotSpawner = 0;
}
},
{
name: "bot fabrication",
description: "anytime you collect <strong>5</strong> <strong class='color-r'>rerolls</strong><br>use them to build a <strong>random bot</strong>",
maxCount: 1,
count: 0,
allowed() {
return powerUps.reroll.rerolls > 5 || build.isCustomSelection
},
requires: "at least 6 rerolls",
effect() {
mod.isRerollBots = true;
powerUps.reroll.changeRerolls(0)
},
remove() {
mod.isRerollBots = false;
}
},
{
name: "nail-bot",
description: "a bot fires <strong>nails</strong> at targets in line of sight",
@@ -664,9 +648,9 @@ const mod = {
maxCount: 9,
count: 0,
allowed() {
return true
return mech.maxEnergy > 0.5
},
requires: "",
requires: "maximum energy above 50%",
effect() {
mod.laserBotCount++;
b.laserBot();
@@ -746,6 +730,23 @@ const mod = {
}
}
},
{
name: "bot fabrication",
description: "anytime you collect <strong>5</strong> <strong class='color-r'>rerolls</strong><br>use them to build a <strong>random bot</strong>",
maxCount: 1,
count: 0,
allowed() {
return powerUps.reroll.rerolls > 5 || build.isCustomSelection
},
requires: "at least 6 rerolls",
effect() {
mod.isRerollBots = true;
powerUps.reroll.changeRerolls(0)
},
remove() {
mod.isRerollBots = false;
}
},
{
name: "perimeter defense",
description: "reduce <strong class='color-harm'>harm</strong> by <strong>3%</strong><br>for each of your permanent <strong>bots</strong>",
@@ -1039,18 +1040,50 @@ const mod = {
},
{
name: "CPT reversal",
description: "<strong>rewind 1.5 - 5</strong> seconds to avoid <strong class='color-harm'>harm</strong><br>drains <strong>66 - 220</strong> <strong class='color-f'>energy</strong>",
description: "<strong class='color-rewind'>rewind</strong> <strong>1.5 - 5</strong> seconds to avoid <strong class='color-harm'>harm</strong><br>drains <strong>66 - 220</strong> <strong class='color-f'>energy</strong>",
maxCount: 1,
count: 0,
allowed() {
return mech.maxEnergy > 0.99 && (mech.fieldUpgrades[mech.fieldMode].name !== "nano-scale manufacturing" || mech.maxEnergy > 1) && mech.fieldUpgrades[mech.fieldMode].name !== "standing wave harmonics" && !mod.isEnergyHealth && !mod.isEnergyLoss && !mod.isPiezo
allowed() { //&& (mech.fieldUpgrades[mech.fieldMode].name !== "nano-scale manufacturing" || mech.maxEnergy > 1)
return mech.maxEnergy > 0.99 && mech.fieldUpgrades[mech.fieldMode].name !== "standing wave harmonics" && !mod.isEnergyHealth && !mod.isEnergyLoss && !mod.isPiezo
},
requires: "not nano-scale, mass-energy, standing wave, acute stress, piezoelectricity",
effect() {
mod.isTimeAvoidDeath = true;
mod.isRewindAvoidDeath = true;
},
remove() {
mod.isTimeAvoidDeath = false;
mod.isRewindAvoidDeath = false;
}
},
{
name: "causality bots",
description: "when you <strong class='color-rewind'>rewind</strong>, build some <strong>bots</strong><br>that protect you for about <strong>7</strong> seconds",
maxCount: 3,
count: 0,
allowed() {
return mod.isRewindAvoidDeath || mod.isRewindEnergy
},
requires: "CPT",
effect() {
mod.isRewindBot++;
},
remove() {
mod.isRewindBot = 0;
}
},
{
name: "causality bombs",
description: "before you <strong class='color-rewind'>rewind</strong> drop some <strong>grenades</strong>",
maxCount: 1,
count: 0,
allowed() {
return mod.isRewindAvoidDeath
},
requires: "CPT",
effect() {
mod.isRewindGrenade = true;
},
remove() {
mod.isRewindGrenade = false;
}
},
{
@@ -1059,7 +1092,7 @@ const mod = {
maxCount: 1,
count: 0,
allowed() {
return !mod.isEnergyHealth && !mod.isTimeAvoidDeath
return !mod.isEnergyHealth && !mod.isRewindAvoidDeath
},
requires: "not mass-energy equivalence, CPT reversal",
effect() {
@@ -1094,7 +1127,7 @@ const mod = {
maxCount: 1,
count: 0,
allowed() {
return !mod.isEnergyLoss && !mod.isPiezo && !mod.isTimeAvoidDeath && !mod.isSpeedHarm && mech.fieldUpgrades[mech.fieldMode].name !== "negative mass field"
return !mod.isEnergyLoss && !mod.isPiezo && !mod.isRewindAvoidDeath && !mod.isSpeedHarm && mech.fieldUpgrades[mech.fieldMode].name !== "negative mass field"
},
requires: "not piezoelectricity, acute stress response, 1st law, negative mass field",
effect: () => {
@@ -1163,7 +1196,7 @@ const mod = {
},
{
name: "energy conservation",
description: "<strong>7%</strong> of <strong class='color-d'>damage</strong> done recovered as <strong class='color-f'>energy</strong>",
description: "<strong>6%</strong> of <strong class='color-d'>damage</strong> done recovered as <strong class='color-f'>energy</strong>",
maxCount: 9,
count: 0,
allowed() {
@@ -1171,7 +1204,7 @@ const mod = {
},
requires: "some increased damage",
effect() {
mod.energySiphon += 0.07;
mod.energySiphon += 0.06;
},
remove() {
mod.energySiphon = 0;
@@ -2355,12 +2388,14 @@ const mod = {
allowed() {
return mod.haveGunCheck("grenades") && !mod.isVacuumBomb && !mod.isNeutronBomb
},
requires: "grenades, not vacuum bomb",
requires: "grenades, not vacuum bomb, neutron",
effect() {
mod.isRPG = true;
b.setGrenadeMode()
},
remove() {
mod.isRPG = false;
b.setGrenadeMode()
}
},
{
@@ -2374,17 +2409,11 @@ const mod = {
requires: "grenades, not rocket-propelled",
effect() {
mod.isVacuumBomb = true;
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "grenades") b.guns[i].fire = b.guns[i].fireVacuum
}
b.setGrenadeMode()
},
remove() {
mod.isVacuumBomb = false;
if (!mod.isNeutronBomb) {
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "grenades") b.guns[i].fire = (mod.isNeutronBomb) ? b.guns[i].fireNeutron : b.guns[i].fireNormal
}
}
b.setGrenadeMode()
}
},
{
@@ -2398,15 +2427,11 @@ const mod = {
requires: "grenades, not rocket-propelled or fragmentation",
effect() {
mod.isNeutronBomb = true;
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "grenades") b.guns[i].fire = b.guns[i].fireNeutron
}
b.setGrenadeMode()
},
remove() {
mod.isNeutronBomb = false;
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "grenades") b.guns[i].fire = (mod.isVacuumBomb) ? b.guns[i].fireVacuum : b.guns[i].fireNormal
}
b.setGrenadeMode()
}
},
{
@@ -3671,5 +3696,7 @@ const mod = {
isBotDamage: null,
isBanish: null,
isMaxEnergyMod: null,
isLowEnergyDamage: null
isLowEnergyDamage: null,
isRewindBot: null,
isRewindGrenade: null
}

View File

@@ -485,65 +485,104 @@ const mech = {
if (mod.energyRegen === 0) dmg *= 0.4 //0.22 + 0.78 * mech.energy //77% damage reduction at zero energy
if (mod.isTurret && mech.crouch) dmg *= 0.5;
if (mod.isEntanglement && b.inventory[0] === b.activeGun) {
for (let i = 0, len = b.inventory.length; i < len; i++) {
dmg *= 0.85 // 1 - 0.15
}
for (let i = 0, len = b.inventory.length; i < len; i++) dmg *= 0.85 // 1 - 0.15
}
return dmg
},
damage(dmg) {
if (mod.isTimeAvoidDeath && mech.energy > 0.66) {
const steps = Math.floor(Math.min(299, 137 * mech.energy)) //go back 2 seconds at 100% energy
let history = mech.history[(mech.cycle - steps) % 300]
Matter.Body.setPosition(player, history.position);
Matter.Body.setVelocity(player, { x: history.velocity.x, y: history.velocity.y });
// move bots to follow player
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType) {
Matter.Body.setPosition(bullet[i], Vector.add(player.position, {
x: 250 * (Math.random() - 0.5),
y: 250 * (Math.random() - 0.5)
}));
Matter.Body.setVelocity(bullet[i], {
x: 0,
y: 0
});
rewind(steps, isDrain = true) {
let history = mech.history[(mech.cycle - steps) % 300]
Matter.Body.setPosition(player, history.position);
Matter.Body.setVelocity(player, { x: history.velocity.x, y: history.velocity.y });
// move bots to follow player
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].botType) {
Matter.Body.setPosition(bullet[i], Vector.add(player.position, {
x: 250 * (Math.random() - 0.5),
y: 250 * (Math.random() - 0.5)
}));
Matter.Body.setVelocity(bullet[i], {
x: 0,
y: 0
});
}
}
if (isDrain) {
mech.energy = Math.max(mech.energy - steps / 136, 0.01)
}
mech.immuneCycle = mech.cycle + 30; //player is immune to collision damage for 30 cycles
let isDrawPlayer = true
const shortPause = function() {
if (mech.defaultFPSCycle < mech.cycle) { //back to default values
game.fpsCap = game.fpsCapDefault
game.fpsInterval = 1000 / game.fpsCap;
document.getElementById("dmg").style.transition = "opacity 1s";
document.getElementById("dmg").style.opacity = "0";
} else {
requestAnimationFrame(shortPause);
if (isDrawPlayer) {
isDrawPlayer = false
ctx.save();
ctx.translate(canvas.width2, canvas.height2); //center
ctx.scale(game.zoom / game.edgeZoomOutSmooth, game.zoom / game.edgeZoomOutSmooth); //zoom in once centered
ctx.translate(-canvas.width2 + mech.transX, -canvas.height2 + mech.transY); //translate
for (let i = 1; i < steps; i++) {
history = mech.history[(mech.cycle - i) % 300]
mech.pos.x = history.position.x
mech.pos.y = history.position.y
mech.draw();
}
ctx.restore();
mech.resetHistory()
}
}
};
mech.energy = Math.max(mech.energy - steps / 136, 0.01)
mech.immuneCycle = mech.cycle + 30; //player is immune to collision damage for 30 cycles
let isDrawPlayer = true
const shortPause = function() {
if (mech.defaultFPSCycle < mech.cycle) { //back to default values
game.fpsCap = game.fpsCapDefault
game.fpsInterval = 1000 / game.fpsCap;
} else {
requestAnimationFrame(shortPause);
if (isDrawPlayer) {
isDrawPlayer = false
ctx.save();
ctx.translate(canvas.width2, canvas.height2); //center
ctx.scale(game.zoom / game.edgeZoomOutSmooth, game.zoom / game.edgeZoomOutSmooth); //zoom in once centered
ctx.translate(-canvas.width2 + mech.transX, -canvas.height2 + mech.transY); //translate
for (let i = 1; i < steps; i++) {
history = mech.history[(mech.cycle - i) % 300]
mech.pos.x = history.position.x
mech.pos.y = history.position.y
mech.draw();
}
ctx.restore();
mech.resetHistory()
if (mech.defaultFPSCycle < mech.cycle) requestAnimationFrame(shortPause);
game.fpsCap = (isDrain ? 3 : 5) //1 is longest pause, 4 is standard
game.fpsInterval = 1000 / game.fpsCap;
mech.defaultFPSCycle = mech.cycle
if (mod.isRewindBot) {
const len = (isDrain ? steps * 0.042 : 2) * mod.isRewindBot
for (let i = 0; i < len; i++) {
where = mech.history[(mech.cycle - i * 40) % 300].position //spread out spawn locations along past history
b.randomBot({
x: where.x + 100 * (Math.random() - 0.5),
y: where.y + 100 * (Math.random() - 0.5)
}, false, false)
bullet[bullet.length - 1].endCycle = game.cycle + 360 + Math.floor(180 * Math.random()) //6-9 seconds
}
}
},
damage(dmg) {
if (mod.isRewindAvoidDeath && mech.energy > 0.66) {
const steps = Math.floor(Math.min(299, 137 * mech.energy)) //go back 2 seconds at 100% energy
if (mod.isRewindGrenade) {
for (let i = 1, len = Math.floor(3 + steps / 60); i < len; i++) {
b.grenade(Vector.add(mech.pos, { x: 10 * (Math.random() - 0.5), y: 10 * (Math.random() - 0.5) }), -i * Math.PI / len) //fire different angles for each grenade
const who = bullet[bullet.length - 1]
if (mod.isVacuumBomb) {
Matter.Body.setVelocity(who, {
x: who.velocity.x * 0.5,
y: who.velocity.y * 0.5
});
} else if (mod.isRPG) {
who.endCycle = (who.endCycle - game.cycle) * 0.2 + game.cycle
} else if (mod.isNeutronBomb) {
Matter.Body.setVelocity(who, {
x: who.velocity.x * 0.3,
y: who.velocity.y * 0.3
});
} else {
Matter.Body.setVelocity(who, {
x: who.velocity.x * 0.5,
y: who.velocity.y * 0.5
});
who.endCycle = (who.endCycle - game.cycle) * 0.5 + game.cycle
}
}
};
if (mech.defaultFPSCycle < mech.cycle) requestAnimationFrame(shortPause);
game.fpsCap = 3 //1 is shortest pause, 4 is standard
game.fpsInterval = 1000 / game.fpsCap;
mech.defaultFPSCycle = mech.cycle
}
mech.rewind(steps)
return
}
mech.lastHarmCycle = mech.cycle
@@ -1765,7 +1804,7 @@ const mech = {
mech.grabPowerUp();
mech.lookForPickUp(180);
const DRAIN = 0.0011
const DRAIN = 0.0013
if (mech.energy > DRAIN) {
mech.energy -= DRAIN;
if (mech.energy < DRAIN) {

View File

@@ -517,13 +517,20 @@ em {
.color-harm {
/* color: */
/* text-shadow: #FC0 1px 0 10px; */
background-color: hsla(325, 100%, 85%, 0.15);
background-color: hsla(51, 100%, 71%, 0.187);
padding: 2px;
border-radius: 4px;
letter-spacing: 1px;
font-weight: 100;
}
/* .color-rewind {
background-image: linear-gradient(to left, #fff, #bbb);
border-radius: 5px;
padding: 2px;
letter-spacing: 1px;
} */
.color-r {
color: #f7b;
letter-spacing: 1px;

View File

@@ -1,21 +1,19 @@
*********** NEXT PATCH ***********
******************************************************** NEXT PATCH ********************************************************
added more requirements to various mods
mod: causality bots - before you rewind, build bots that protect you for for 7 seconds
CPT reversal is more flexible with energy
1.5-5 seconds of rewind drains 66% - 220% energy
mod grenade: causality bombs - before you rewind, drop some grenades
mod: exothermic process - renamed acute stress response
mod: heat engine - reduce max energy by 50 increase damage by 40%
mod: Gibbs free energy - gain 5% damage for every 10 energy below 100
************** BUGS **************
******************************************************** BUGS ********************************************************
(fixed) red square mobs no longer die on contact
might be side effects (we put the player invincibility after mob on damage code)
mod and mob are too similar
(always) make it so that when you are immune to harm you can either jump on mobs or you pass through them
(always) is there a way to check if the player is stuck inside the map or block
trigger a short term non-collide if that occurs
(4+ reports before potential fix) bug - crouch and worm hole? -> crouch locked in
players have extra gravity
might be from the short jump code
@@ -31,20 +29,39 @@ mod: Gibbs free energy - gain 5% damage for every 10 energy below 100
(repeatable almost every time) bug - mines spawn extra mines when fired at thin map wall while jumping
************** TODO **************
******************************************************** TODO ********************************************************
mod that gives a bonus for low energy
damage again or something different
requires heat engine
combine fragmentation grenades that that mod that makes railguns fragment into nails into one single mod, and let it apply to shotgun slug
CPT like mods
delayed rewind: after taking damage, play for 2 seconds, then rewind to just before you took damage
and return lost health
this might need code to run in a field to work
time dilation field?
gain a scrap bot after rewinding
use ammo to rewind instead of energy
explode the area where you were hit before rewinding
mod: power up magnetism - power ups drift towards player
where would this code go?
super balls start at 3, not 4
have to balance damage
RPG might need a buff
retrocausality bomb should fire 3 grenades at once that spread out a small bit before they explode?
this means you'd have to make grenades a method.
make different move methods
mod: crouch charge jump
mod: double jump
mod: when mobs are at full health you do 40% to them
mechanic: failed technology - add mods to the mod pool with a dumb effect
don't show up in custom?
negative effect (one time effects are better to avoid code clutter)
remove all your energy
eject all your rerolls (not bad with dup)
teleport to the start of the level
remove your bots (requires you to have some bots)
your bots are changed to random bots
mod - move super fast, go intangible, drain energy very fast
this is like a dodge roll
mod for standing wave?, cloaking?
spawn a few power ups on the final boss level
@@ -279,7 +296,7 @@ n-gon outreach ideas
javascript:(function(){var script=document.createElement('script');script.onload=function(){var stats=new Stats();document.body.appendChild(stats.dom);requestAnimationFrame(function loop(){stats.update();requestAnimationFrame(loop)});};script.src='//mrdoob.github.io/stats.js/build/stats.min.js';document.head.appendChild(script);})()
************** LORE **************
******************************************************** LORE ********************************************************
lore - a robot (the player) gains self awareness
each mod/gun/field is a new tech