not a full patch, just syncing between my 2 computers

This commit is contained in:
landgreen
2022-12-28 12:35:03 -08:00
parent 3cbc2c7941
commit feb8824bc7
12 changed files with 473 additions and 313 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -1857,17 +1857,12 @@ const b = {
}
}
}
if (tech.isFoamBall) {
for (let i = 0, len = 4 * this.mass; i < len; i++) {
for (let i = 0, len = Math.min(50, 3 + 4 * Math.sqrt(this.mass)); i < len; i++) {
const radius = 5 + 8 * Math.random()
const velocity = {
x: Math.max(0.5, 2 - radius * 0.1),
y: 0
}
const velocity = {x: Math.max(0.5, 2 - radius * 0.1),y: 0}
b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius)
}
// this.endCycle = 0;
}
},
caughtPowerUp: null,
@@ -3768,6 +3763,72 @@ const b = {
y: speed * Math.sin(dir)
});
},
superBall(where, velocity, radius){
let dir = m.angle
const me = bullet.length;
bullet[me] = Bodies.polygon(where.x,where.y, 12, radius, b.fireAttributes(dir, false));
Composite.add(engine.world, bullet[me]); //add bullet to world
Matter.Body.setVelocity(bullet[me], velocity);
Matter.Body.setDensity(bullet[me], 0.0001 + 0.001 * tech.isSuperHarm);
bullet[me].endCycle = simulation.cycle + Math.floor(300 + 90 * Math.random());
bullet[me].minDmgSpeed = 0;
bullet[me].restitution = 1;
bullet[me].friction = 0;
if (tech.isIncendiary) {
bullet[me].do = function() {
this.force.y += this.mass * 0.0012;
if (Matter.Query.collides(this, map).length) {
b.explosion(this.position, this.mass * 280); //makes bullet do explosive damage at end
this.endCycle = 0
}
};
} else if (tech.isSuperHarm){
bullet[me].collidePlayerDo = function(){
if (Matter.Query.collides(this, [player]).length) {
this.endCycle = 0
let dmg = 0.03
m.damage(dmg);
simulation.drawList.push({ //add dmg to draw queue
x: this.position.x,
y: this.position.y,
radius: Math.sqrt(dmg) * 200,
color: simulation.mobDmgColor,
time: simulation.drawTime*2
});
}
}
bullet[me].cycle= 0
bullet[me].do = function() {
this.cycle++
if (this.cycle > 60) this.do = this.collidePlayerDo
this.force.y += this.mass * 0.0012;
};
} else {
bullet[me].do = function() {
this.cycle++
this.force.y += this.mass * 0.0012;
};
}
bullet[me].beforeDmg = function(who) {
if (tech.oneSuperBall) mobs.statusStun(who, 120) // (2.3) * 2 / 14 ticks (2x damage over 7 seconds)
// if (tech.isIncendiary) {
// b.explosion(this.position, this.mass * (240+70 * Math.random()) ); //makes bullet do explosive damage at end
// this.endCycle = 0
// }
if (tech.isFoamBall) {
for (let i = 0, len = 6 * this.mass; i < len; i++) {
const radius = 5 + 8 * Math.random()
// const velocity = { x: Math.max(2, 10 - radius * 0.25), y: 0 }
const velocity = {
x: Math.max(0.5, 2 - radius * 0.1),
y: 0
}
b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius)
}
this.endCycle = 0
}
};
},
// plasmaBall(position, velocity, radius) {
// // radius *= Math.sqrt(tech.bulletSize)
// const me = bullet.length;
@@ -6110,181 +6171,59 @@ const b = {
do() {},
foamBall() {
},
fireOne() {
const SPEED = input.down ? 40 : 33
m.fireCDcycle = m.cycle + Math.floor((input.down ? 27 : 19) * b.fireCDscale); // cool down
let dir = m.angle
const me = bullet.length;
bullet[me] = Bodies.polygon(m.pos.x + 30 * Math.cos(m.angle), m.pos.y + 30 * Math.sin(m.angle), 12, 21 * tech.bulletSize, b.fireAttributes(dir, false));
Composite.add(engine.world, bullet[me]); //add bullet to world
Matter.Body.setVelocity(bullet[me], {
x: SPEED * Math.cos(dir),
y: SPEED * Math.sin(dir)
});
// Matter.Body.setDensity(bullet[me], 0.0001);
bullet[me].endCycle = simulation.cycle + Math.floor(300 + 90 * Math.random());
bullet[me].minDmgSpeed = 0;
bullet[me].restitution = 1;
bullet[me].friction = 0;
bullet[me].do = function() {
this.force.y += this.mass * 0.0012;
};
if (tech.isIncendiary) {
bullet[me].do = function() {
this.force.y += this.mass * 0.0012;
if (Matter.Query.collides(this, map).length) {
b.explosion(this.position, this.mass * 280); //makes bullet do explosive damage at end
this.endCycle = 0
}
};
} else {
bullet[me].do = function() {
this.force.y += this.mass * 0.0012;
};
}
bullet[me].beforeDmg = function(who) {
mobs.statusStun(who, 120) // (2.3) * 2 / 14 ticks (2x damage over 7 seconds)
if (tech.isIncendiary) {
b.explosion(this.position, this.mass * 280); //makes bullet do explosive damage at end
this.endCycle = 0
}
if (tech.isFoamBall) {
for (let i = 0, len = 6 * this.mass; i < len; i++) {
const radius = 5 + 8 * Math.random()
// const velocity = { x: Math.max(2, 10 - radius * 0.25), y: 0 }
const velocity = {
x: Math.max(0.5, 2 - radius * 0.1),
y: 0
}
b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius)
}
this.endCycle = 0
}
};
const speed = input.down ? 43 : 36
b.superBall({
x:m.pos.x + 30 * Math.cos(m.angle),
y:m.pos.y + 30 * Math.sin(m.angle)
},{
x: speed * Math.cos(m.angle),
y: speed * Math.sin(m.angle)
}, 21 * tech.bulletSize)
},
fireMulti() {
const SPEED = input.down ? 43 : 36
m.fireCDcycle = m.cycle + Math.floor((input.down ? 23 : 15) * b.fireCDscale); // cool down
const SPREAD = input.down ? 0.08 : 0.13
const num = 3 + Math.floor(tech.extraSuperBalls * Math.random())
const radius = 11 * tech.bulletSize
const speed = input.down ? 43 : 36
let dir = m.angle - SPREAD * (num - 1) / 2;
for (let i = 0; i < num; i++) {
const me = bullet.length;
bullet[me] = Bodies.polygon(m.pos.x + 30 * Math.cos(m.angle), m.pos.y + 30 * Math.sin(m.angle), 12, radius, b.fireAttributes(dir, false));
Composite.add(engine.world, bullet[me]); //add bullet to world
Matter.Body.setVelocity(bullet[me], {
x: SPEED * Math.cos(dir),
y: SPEED * Math.sin(dir)
});
// Matter.Body.setDensity(bullet[me], 0.0001);
bullet[me].endCycle = simulation.cycle + Math.floor((300 + 90 * Math.random()) * tech.isBulletsLastLonger);
bullet[me].minDmgSpeed = 0;
bullet[me].restitution = 0.99;
bullet[me].friction = 0;
if (tech.isIncendiary) {
bullet[me].do = function() {
this.force.y += this.mass * 0.0012;
if (Matter.Query.collides(this, map).length) {
b.explosion(this.position, this.mass * 280); //makes bullet do explosive damage at end
this.endCycle = 0
}
};
} else {
bullet[me].do = function() {
this.force.y += this.mass * 0.0012;
};
}
bullet[me].beforeDmg = function() {
if (tech.isIncendiary) {
b.explosion(this.position, this.mass * 320 + 70 * Math.random()); //makes bullet do explosive damage at end
this.endCycle = 0
}
if (tech.isFoamBall) {
for (let i = 0, len = 6 * this.mass; i < len; i++) {
const radius = 5 + 8 * Math.random()
const velocity = {
x: Math.max(0.5, 2 - radius * 0.1),
y: 0
}
b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius)
}
this.endCycle = 0
// this.mass = 0 //prevent damage
}
};
b.superBall({
x:m.pos.x + 30 * Math.cos(dir),
y:m.pos.y + 30 * Math.sin(dir)
},{
x: speed * Math.cos(dir),
y: speed * Math.sin(dir)
}, 11 * tech.bulletSize)
dir += SPREAD;
}
},
fireQueue() {
// const dir = m.angle
// const x = m.pos.x
// const y = m.pos.y
const SPEED = input.down ? 43 : 36
m.fireCDcycle = m.cycle + Math.floor((input.down ? 23 : 15) * b.fireCDscale); // cool down
const num = 1 + 3 + Math.floor(tech.extraSuperBalls * Math.random()) //1 extra
const speed = input.down ? 43 : 36
const delay = Math.floor((input.down ? 18 : 12) * b.fireCDscale)
m.fireCDcycle = m.cycle + delay; // cool down
const fireBall = () => {
const me = bullet.length;
bullet[me] = Bodies.polygon(m.pos.x, m.pos.y, 12, 11 * tech.bulletSize, b.fireAttributes(m.angle, false));
Composite.add(engine.world, bullet[me]); //add bullet to world
Matter.Body.setVelocity(bullet[me], {
x: SPEED * Math.cos(m.angle),
y: SPEED * Math.sin(m.angle)
});
bullet[me].endCycle = simulation.cycle + Math.floor(330 * tech.isBulletsLastLonger);
bullet[me].minDmgSpeed = 0;
bullet[me].restitution = 0.99;
bullet[me].friction = 0;
if (tech.isIncendiary) {
bullet[me].do = function() {
this.force.y += this.mass * 0.0012;
if (Matter.Query.collides(this, map).length) {
b.explosion(this.position, this.mass * 280); //makes bullet do explosive damage at end
this.endCycle = 0
}
};
} else {
bullet[me].do = function() {
this.force.y += this.mass * 0.0012;
};
}
bullet[me].beforeDmg = function() {
if (tech.isIncendiary) {
b.explosion(this.position, this.mass * 320 + 70 * Math.random()); //makes bullet do explosive damage at end
this.endCycle = 0
}
if (tech.isFoamBall) {
for (let i = 0, len = 6 * this.mass; i < len; i++) {
const radius = 5 + 8 * Math.random()
const velocity = {
x: Math.max(0.5, 2 - radius * 0.1),
y: 0
}
b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius)
}
this.endCycle = 0
// this.mass = 0 //prevent damage
}
};
m.fireCDcycle = m.cycle + delay; // cool down
}
function cycle() {
// if (simulation.paused || m.isBodiesAsleep) {
// requestAnimationFrame(cycle)
// } else {
count++
// if (count % 2)
fireBall()
b.superBall({
x:m.pos.x + 30 * Math.cos(m.angle),
y:m.pos.y + 30 * Math.sin(m.angle)
},{
x: speed * Math.cos(m.angle),
y: speed * Math.sin(m.angle)
}, 11 * tech.bulletSize)
if (count < num && m.alive) requestAnimationFrame(cycle);
// }
m.fireCDcycle = m.cycle + delay; // cool down
}
let count = 0
requestAnimationFrame(cycle);
// fireBall();
},
chooseFireMethod() { //set in simulation.startGame
if (tech.oneSuperBall) {
@@ -6871,17 +6810,13 @@ const b = {
});
that.do = that.grow;
}
const mobCollisions = Matter.Query.collides(this, mob)
if (mobCollisions.length) {
onCollide(this)
this.stuckTo = mobCollisions[0].bodyA
if (tech.isZombieMobs) this.stuckTo.isSoonZombie = true
if (this.stuckTo.isVerticesChange) {
this.stuckToRelativePosition = {
x: 0,
y: 0
}
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)
@@ -6968,7 +6903,6 @@ const b = {
};
//spawn bullets on end
bullet[me].onEnd = function() {
let count = 0 //used in for loop below
const things = [
() => { //spore
@@ -7000,6 +6934,14 @@ const b = {
() => { //nail
b.targetedNail(this.position, 1, 39 + 6 * Math.random())
},
() => { //super ball
const speed = 36
const angle = 2*Math.PI*Math.random()
b.superBall(this.position,{
x: speed * Math.cos(angle),
y: speed * Math.sin(angle)
}, 11 * tech.bulletSize)
},
]
for (len = this.totalSpores; count < len; count++) {

View File

@@ -588,7 +588,7 @@ ${simulation.isCheating ? "<br><br><em>lore disabled</em>": ""}
},
populateGrid() { //background-color:var(--build-bg-color);
let text = `
<div class="experiment-grid-module" style="position: sticky; top:0; z-index: 10; align-self: start; width: 160px; font-size: 1.00em; line-height: 170%; background-color: #fafcfd;display: flex; flex-direction: column; justify-content: center; align-items: center;border: 1.5px #333 solid;border-radius:10px;">
<div class="experiment-grid-module" style="position: sticky; top:0; z-index: 10; align-self: start; width: 165px; font-size: 1.00em; line-height: 170%; background-color: #fafcfd;display: flex; flex-direction: column; justify-content: center; align-items: center;border: 1.5px #333 solid;border-radius:10px;">
<div>
<svg class="SVG-button" onclick="build.startExperiment()" width="150" height="68" >
<g stroke='none' fill='#333' stroke-width="2" font-size="60px" font-family="Ariel, sans-serif">

View File

@@ -31,19 +31,21 @@ const level = {
// b.giveGuns("nail gun") //0 nail gun 1 shotgun 2 super balls 3 wave 4 missiles 5 grenades 6 spores 7 drones 8 foam 9 harpoon 10 mine 11 laser
// b.giveGuns("spores") //0 nail gun 1 shotgun 2 super balls 3 wave 4 missiles 5 grenades 6 spores 7 drones 8 foam 9 harpoon 10 mine 11 laser
// b.guns[0].ammo = 10000
// tech.giveTech("alternator")
// tech.giveTech("posture")
// for (let i = 0; i < 1; ++i) tech.giveTech("Sleipnir")
// tech.giveTech("Zectron")
// tech.giveTech("cordyceps")
// for (let i = 0; i < 1; ++i) tech.giveTech("super ball")
// tech.isFoamBall = true
// for (let i = 0; i < 9; ++i) tech.giveTech("emergence")
// for (let i = 0; i < 1; ++i) tech.giveTech("incendiary ammunition")
// for (let i = 0; i < 2; i++) tech.giveTech("unified field theory")
// for (let i = 0; i < 9; i++) tech.giveTech("replication")
// for (let i = 0; i < 1; i++) tech.giveTech("colony")
// for (let i = 0; i < 10; i++) powerUps.directSpawn(450, -50, "tech");
// for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "boost");
// for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "coupling");
// level.testing();
// spawn.shooter(1900, -500, 200)
// spawn.starter(1900, -500)
// spawn.sneakBoss(1900, -500)
// spawn.starter(1900, -500, 25)
// spawn.sneaker(1900, -500, 25)
// spawn.hopper(2538, -950)
// for (let i = 0; i < 2; ++i) spawn.starter(1000 + 1000 * Math.random(), -500 + 300 * Math.random())
// tech.addJunkTechToPool(2)

View File

@@ -1192,23 +1192,21 @@ const mobs = {
this.alive = false; //triggers mob removal in mob[i].replace(i)
if (this.isDropPowerUp) {
// if (true) { //spawn zombie on death
// // console.log(this)
// this.leaveBody = false;
// let count = 45 //delay spawn cycles
// let cycle = () => {
// if (count > 0) {
// if (m.alive) requestAnimationFrame(cycle);
// if (!simulation.paused && !simulation.isChoosing) {
// count--
// }
// } else {
// spawn.zombie(this.position.x, this.position.y, this.radius, this.vertices.length, this.fill) // zombie(x, y, radius, sides, color)
// }
// }
// requestAnimationFrame(cycle);
// }
if (this.isSoonZombie) { //spawn zombie on death
this.leaveBody = false;
let count = 45 //delay spawn cycles
let cycle = () => {
if (count > 0) {
if (m.alive) requestAnimationFrame(cycle);
if (!simulation.paused && !simulation.isChoosing) {
count--
}
} else {
spawn.zombie(this.position.x, this.position.y, this.radius, this.vertices.length, this.fill) // zombie(x, y, radius, sides, color)
}
}
requestAnimationFrame(cycle);
}
@@ -1248,19 +1246,19 @@ const mobs = {
mobs.mobDeaths++
if (Math.random() < tech.sporesOnDeath) {
const amount = Math.min(25, Math.floor(2 + this.mass * (0.5 + 0.5 * Math.random())))
if (tech.isSporeFlea) {
const len = Math.min(25, Math.floor(2 + this.mass * (0.5 + 0.5 * Math.random()))) / 2
const len = amount / 2
for (let i = 0; i < len; i++) {
const speed = 10 + 5 * Math.random()
const angle = 2 * Math.PI * Math.random()
b.flea(this.position, { x: speed * Math.cos(angle), y: speed * Math.sin(angle) })
}
} else if (tech.isSporeWorm) {
const len = Math.min(25, Math.floor(2 + this.mass * (0.5 + 0.5 * Math.random()))) / 2
const len = amount / 2
for (let i = 0; i < len; i++) b.worm(this.position)
} else {
const len = Math.min(25, Math.floor(2 + this.mass * (0.5 + 0.5 * Math.random())))
for (let i = 0; i < len; i++) b.spore(this.position)
for (let i = 0; i < amount; i++) b.spore(this.position)
}
} else if (tech.isExplodeMob) {
b.explosion(this.position, Math.min(700, Math.sqrt(this.mass + 6) * (30 + 60 * Math.random())))

View File

@@ -549,7 +549,7 @@ const m = {
if (tech.isZeno) dmg *= 0.15
if (tech.isFieldHarmReduction) dmg *= 0.5
if (tech.isHarmMACHO) dmg *= 0.4
if (tech.isImmortal) dmg *= 0.66
if (tech.isImmortal) dmg *= 0.67
if (tech.isSlowFPS) dmg *= 0.8
if (tech.energyRegen === 0) dmg *= 0.34
if (tech.healthDrain) dmg *= 1 + 3.33 * tech.healthDrain //tech.healthDrain = 0.03 at one stack //cause more damage

View File

@@ -636,7 +636,7 @@ const powerUps = {
} else if (powerUps.research.count > 0) {
text += `<div onclick="powerUps.research.use('${type}')" class='choose-grid-module research-card' >` // style = "margin-left: 192px; margin-right: -192px;"
text += `<div><div><span style="position:relative;">`
for (let i = 0, len = Math.min(powerUps.research.count, 30); i < len; i++) text += `<div class="circle-grid research" style="position:absolute; top:0; left:${(18 - len*0.21)*i}px ;opacity:0.8; border: 1px #fff solid;"></div>`
for (let i = 0, len = Math.min(powerUps.research.count, 30); i < len; i++) text += `<div class="circle-grid research" style="font-size:0.82em; position:absolute; top:0; left:${(18 - len*0.21)*i}px ;opacity:0.8; border: 1px #fff solid;"></div>`
text += `</span>&nbsp; <span class='research-select'>${tech.isResearchReality?"<span class='alt'>alternate reality</span>": "research"}</span></div></div></div>`
} else {
text += `<div></div>`
@@ -1300,7 +1300,6 @@ const powerUps = {
if (have.length) {
choose = have[Math.floor(Math.random() * have.length)]
// simulation.makeTextLog(`<div class='circle tech'></div> &nbsp; <strong>${tech.tech[choose].name}</strong> was ejected`, 600) //message about what tech was lost
simulation.makeTextLog(`<span class='color-var'>tech</span>.remove("<span class='color-text'>${tech.tech[choose].name}</span>")`)
for (let i = 0; i < tech.tech[choose].count; i++) {
@@ -1318,7 +1317,6 @@ const powerUps = {
return false
}
} else if (tech.tech[choose].count && !tech.tech[choose].isNonRefundable) {
// simulation.makeTextLog(`<div class='circle tech'></div> &nbsp; <strong>${tech.tech[choose].name}</strong> was ejected`, 600) //message about what tech was lost
simulation.makeTextLog(`<span class='color-var'>tech</span>.remove("<span class='color-text'>${tech.tech[choose].name}</span>")`)
for (let i = 0; i < tech.tech[choose].count; i++) {

View File

@@ -895,7 +895,7 @@ const simulation = {
if (tech.isMutualism && !tech.isEnergyHealth) {
for (let i = 0; i < bullet.length; i++) {
if (bullet[i].isMutualismActive) {
m.health += 0.01 + 0.01 * (bullet[i].isSpore || bullet[i].isFlea)
m.health += 0.01 + 0.01 * ((bullet[i].isSpore || bullet[i].isFlea) ? 0: 1)
if (m.health > m.maxHealth) m.health = m.maxHealth;
m.displayHealth();
}

View File

@@ -6,7 +6,7 @@ const spawn = {
"orbitalBoss", "historyBoss", "shooterBoss", "cellBossCulture", "bomberBoss", "spiderBoss", "launcherBoss", "laserTargetingBoss",
"powerUpBoss", "powerUpBossBaby", "streamBoss", "pulsarBoss", "spawnerBossCulture", "grenadierBoss", "growBossCulture", "blinkBoss",
"snakeSpitBoss", "laserBombingBoss", "blockBoss", "revolutionBoss", "slashBoss", "shieldingBoss",
"timeSkipBoss", "dragonFlyBoss", "beetleBoss"
"timeSkipBoss", "dragonFlyBoss", "beetleBoss", "sneakBoss"
],
bossTypeSpawnOrder: [], //preset list of boss names calculated at the start of a run by the randomSeed
bossTypeSpawnIndex: 0, //increases as the boss type cycles
@@ -31,7 +31,8 @@ const spawn = {
"striker", "striker",
"laser", "laser",
"pulsar", "pulsar",
"launcher", "launcherOne", "exploder", "sneaker", "sucker", "sniper", "spinner", "grower", "beamer", "spawner", "ghoster",
"sneaker", "sneaker",
"launcher", "launcherOne", "exploder", "sucker", "sniper", "spinner", "grower", "beamer", "spawner", "ghoster",
//, "focuser"
],
mobTypeSpawnOrder: [], //preset list of mob names calculated at the start of a run by the randomSeed
@@ -1541,8 +1542,7 @@ const spawn = {
zombie(x, y, radius, sides, color) { //mob that attacks other mobs
mobs.spawn(x, y, sides, radius, color);
let me = mob[mob.length - 1];
me.damageReduction = 0.5 //take less damage
// Matter.Body.setDensity(me, 0.0005) // normal density is 0.001 // this reduces life by half and decreases knockback
me.damageReduction = 0 //take NO damage until targeting player, but also slowly lose health
me.isZombie = true
me.isBadTarget = true;
me.isDropPowerUp = false;
@@ -1550,9 +1550,24 @@ const spawn = {
me.stroke = "#83a"
me.accelMag = 0.0015
me.frictionAir = 0.01
// me.collisionFilter.mask = cat.player | cat.map | cat.body
// me.memory = 120;
me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.mob
me.seeAtDistance2 = 1000000 //1000 vision range
// me.onDeath = function() {
// const amount = Math.min(10, Math.ceil(this.mass * 0.5))
// if (tech.isSporeFlea) {
// const len = amount / 2
// for (let i = 0; i < len; i++) {
// const speed = 10 + 5 * Math.random()
// const angle = 2 * Math.PI * Math.random()
// b.flea(this.position, { x: speed * Math.cos(angle), y: speed * Math.sin(angle) })
// }
// } else if (tech.isSporeWorm) {
// const len = amount / 2
// for (let i = 0; i < len; i++) b.worm(this.position)
// } else {
// for (let i = 0; i < amount; i++) b.spore(this.position)
// }
// }
me.do = function() {
this.zombieHealthBar();
this.lookForMobTargets();
@@ -1572,9 +1587,11 @@ const spawn = {
(Vector.magnitudeSquared(Vector.sub(this.position, mob[this.mobSearchIndex].position)) < this.seeAtDistance2 && Matter.Query.ray(map, this.position, mob[this.mobSearchIndex].position).length === 0)
) {
this.target = mob[this.mobSearchIndex]
} else if (Math.random() < 0.05 && (Vector.magnitudeSquared(Vector.sub(this.position, player.position)) < this.seeAtDistance2 || Matter.Query.ray(map, this.position, player.position).length === 0)) {
} else if (Math.random() < 0.005 * player.speed && (Vector.magnitudeSquared(Vector.sub(this.position, player.position)) < this.seeAtDistance2 || Matter.Query.ray(map, this.position, player.position).length === 0)) {
this.target = player
this.isBadTarget = false;
this.damageReduction = 0.5
me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob
}
}
}
@@ -1585,13 +1602,13 @@ const spawn = {
Vector.magnitudeSquared(Vector.sub(this.position, this.target.position)) > this.seeAtDistance2 ||
Matter.Query.ray(map, this.position, this.target.position).length !== 0
) {
if (this.target === player) this.isBadTarget = true
this.target = null
}
}
}
me.zombieHealthBar = function() {
this.damage(0.001); //decay
this.health -= 0.0005 //decay
if ((this.health < 0.01 || isNaN(this.health)) && this.alive) this.death();
const h = this.radius * 0.3;
const w = this.radius * 2;
@@ -1624,7 +1641,7 @@ const spawn = {
this.force.y -= force.y;
this.target = null //look for a new target
const dmg = 0.3 * m.dmgScale
const dmg = 0.2 * m.dmgScale
who.damage(dmg);
who.locatePlayer();
simulation.drawList.push({
@@ -1647,7 +1664,6 @@ const spawn = {
}
// me.onDamage = function(dmg) {
// }
},
starter(x, y, radius = Math.floor(15 + 20 * Math.random())) { //easy mob for on level 1
mobs.spawn(x, y, 8, radius, "#9ccdc6");
@@ -5703,21 +5719,132 @@ const spawn = {
ctx.setLineDash([]);
}
},
sneaker(x, y, radius = 15 + Math.ceil(Math.random() * 10)) {
sneakBoss(x, y, radius = 70) {
mobs.spawn(x, y, 5, radius, "transparent");
let me = mob[mob.length - 1];
Matter.Body.setDensity(me, 0.002); //extra dense //normal is 0.001 //makes effective life much larger
me.isBoss = true;
me.damageReduction = 0.4 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1)
me.accelMag = 0.0017 * Math.sqrt(simulation.accelScale);
me.frictionAir = 0.01;
me.g = 0.0001; //required if using this.gravity
me.stroke = "transparent"; //used for drawSneaker
me.alpha = 1; //used in drawSneaker
me.isCloaked = true; //used in drawSneaker
me.isBadTarget = true;
me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player
me.showHealthBar = false;
me.memory = 30;
me.vanishesLeft = 2+simulation.difficultyMode
me.onDamage = function() {
if (this.vanishesLeft>0 && this.health < 0.1){ //if health is below 10% teleport to a random spot on player history, heal, and cloak
this.vanishesLeft--
// const scale = 0.95;
// Matter.Body.scale(this, scale, scale);
// this.radius *= scale;
//flash screen to hide vanish
for(let i=0; i<8; i++){
simulation.drawList.push({
x: this.position.x,
y: this.position.y,
radius: 3000,
color: `rgba(0, 0, 0,${1-0.1*i})`,
time: (i+1)*3
});
}
//teleport to near the end of player history
const index = Math.floor( (m.history.length-1)*(0.66+0.2*Math.random() ))
let history = m.history[(m.cycle - index) % 600]
Matter.Body.setPosition(this, history.position)
Matter.Body.setVelocity(this, {x: 0,y: 0});
this.seePlayer.recall = 0
this.cloak();
this.health = 1;
}
};
me.cloak = function() {
if (!this.isCloaked) { //stealth
this.alpha = 0;
this.isCloaked = true;
this.isBadTarget = true;
this.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player
this.damageReduction = 0.04 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1)
}
}
me.deCloak = function() {
if (this.isCloaked) {
this.damageReduction = 0.4 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1)
this.isCloaked = false;
this.isBadTarget = false;
this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob; //can touch player
}
}
me.do = function() {
this.gravity();
this.seePlayerByHistory(55);
this.checkStatus();
this.attraction();
//draw
if (this.seePlayer.recall) {
if (this.alpha < 1) this.alpha += 0.005 + 0.003 / simulation.CDScale;
} else {
if (this.alpha > 0) this.alpha -= 0.04;
}
if (this.alpha > 0) {
if (this.alpha > 0.7) {
this.healthBar();
this.deCloak()
}
//draw body
ctx.beginPath();
const vertices = this.vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let j = 1, len = vertices.length; j < len; ++j) ctx.lineTo(vertices[j].x, vertices[j].y);
ctx.lineTo(vertices[0].x, vertices[0].y);
ctx.fillStyle = `rgba(0,0,0,${this.alpha * this.alpha})`;
ctx.fill();
} else {
this.cloak()
}
};
},
sneaker(x, y, radius = 15 + Math.ceil(Math.random() * 10)) {
mobs.spawn(x, y, 5, radius, "transparent");
let me = mob[mob.length - 1];
// Matter.Body.setDensity(me, 0.001); //extra dense //normal is 0.001 //makes effective life much larger
me.accelMag = 0.001 * Math.sqrt(simulation.accelScale);
me.frictionAir = 0.01;
me.g = 0.0002; //required if using this.gravity
me.stroke = "transparent"; //used for drawSneaker
me.alpha = 1; //used in drawSneaker
// me.leaveBody = false;
me.canTouchPlayer = false; //used in drawSneaker
me.isNotCloaked = false; //used in drawSneaker
me.isBadTarget = true;
me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player
me.showHealthBar = false;
me.memory = 240;
me.isVanished = false;
me.onDamage = function() {
if (!this.isVanished && this.health < 0.1){ //if health is below 10% teleport to a random spot on player history, heal, and cloak
this.health = 1;
this.isVanished = true
this.cloak();
//teleport to near the end of player history
Matter.Body.setPosition(this, m.history[Math.floor((m.history.length-1)*(0.66+0.33*Math.random()))].position)
Matter.Body.setVelocity(this, {x: 0,y: 0});
}
};
me.cloak = function() {
if (this.isNotCloaked) { //stealth
this.alpha = 0;
this.isNotCloaked = false;
this.isBadTarget = true;
this.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player
}
}
me.do = function() {
this.gravity();
this.seePlayerByHistory(15);
@@ -5732,8 +5859,8 @@ const spawn = {
if (this.alpha > 0) {
if (this.alpha > 0.7) {
this.healthBar();
if (!this.canTouchPlayer) {
this.canTouchPlayer = true;
if (!this.isNotCloaked) {
this.isNotCloaked = true;
this.isBadTarget = false;
this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob; //can touch player
}
@@ -5748,10 +5875,8 @@ const spawn = {
ctx.lineTo(vertices[0].x, vertices[0].y);
ctx.fillStyle = `rgba(0,0,0,${this.alpha * this.alpha})`;
ctx.fill();
} else if (this.canTouchPlayer) { //stealth
this.canTouchPlayer = false;
this.isBadTarget = true;
this.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player
} else {
this.cloak()
}
};
},
@@ -5764,7 +5889,7 @@ const spawn = {
Matter.Body.setDensity(me, 0.0015); //normal is 0.001 //makes effective life much lower
me.stroke = "transparent"; //used for drawGhost
me.alpha = 1; //used in drawGhost
me.canTouchPlayer = false; //used in drawGhost
me.isNotCloaked = false; //used in drawGhost
me.isBadTarget = true;
// me.leaveBody = false;
me.collisionFilter.mask = cat.bullet //| cat.body
@@ -5791,8 +5916,8 @@ const spawn = {
if (this.alpha > 0) {
if (this.alpha > 0.8 && this.seePlayer.recall) {
this.healthBar();
if (!this.canTouchPlayer) {
this.canTouchPlayer = true;
if (!this.isNotCloaked) {
this.isNotCloaked = true;
this.isBadTarget = false;
this.collisionFilter.mask = cat.player | cat.bullet
}
@@ -5808,8 +5933,8 @@ const spawn = {
ctx.lineWidth = 1;
ctx.fillStyle = `rgba(255,255,255,${this.alpha * this.alpha})`;
ctx.fill();
} else if (this.canTouchPlayer) {
this.canTouchPlayer = false;
} else if (this.isNotCloaked) {
this.isNotCloaked = false;
this.isBadTarget = true;
this.collisionFilter.mask = cat.bullet; //can't touch player or walls
}
@@ -6110,7 +6235,7 @@ const spawn = {
me.showHealthBar = false;
me.frictionStatic = 0;
me.friction = 0;
me.canTouchPlayer = false; //used in drawSneaker
me.isNotCloaked = false; //used in drawSneaker
me.isBadTarget = true;
me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player
@@ -6192,8 +6317,8 @@ const spawn = {
if (this.alpha > 0) {
if (this.alpha > 0.95) {
this.healthBar();
if (!this.canTouchPlayer) {
this.canTouchPlayer = true;
if (!this.isNotCloaked) {
this.isNotCloaked = true;
this.isBadTarget = false;
this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob; //can touch player
}
@@ -6208,8 +6333,8 @@ const spawn = {
ctx.lineTo(vertices[0].x, vertices[0].y);
ctx.fillStyle = `rgba(25,0,50,${this.alpha * this.alpha})`;
ctx.fill();
} else if (this.canTouchPlayer) {
this.canTouchPlayer = false;
} else if (this.isNotCloaked) {
this.isNotCloaked = false;
this.isBadTarget = true
this.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player
}
@@ -6373,7 +6498,7 @@ const spawn = {
// // Matter.Body.setDensity(me, 0.001); //normal is 0.001 //makes effective life much lower
// me.stroke = "transparent"; //used for drawGhost
// me.alpha = 1; //used in drawGhost
// me.canTouchPlayer = false; //used in drawGhost
// me.isNotCloaked = false; //used in drawGhost
// me.isBadTarget = true;
// // me.leaveBody = false;
// me.collisionFilter.mask = cat.bullet //| cat.body
@@ -6414,8 +6539,8 @@ const spawn = {
// if (this.alpha > 0) {
// if (this.alpha > 0.8 && this.seePlayer.recall) {
// this.healthBar();
// if (!this.canTouchPlayer) {
// this.canTouchPlayer = true;
// if (!this.isNotCloaked) {
// this.isNotCloaked = true;
// this.isBadTarget = false;
// this.collisionFilter.mask = cat.player | cat.bullet
// }
@@ -6452,8 +6577,8 @@ const spawn = {
// }
// } else if (this.canTouchPlayer) {
// this.canTouchPlayer = false;
// } else if (this.isNotCloaked) {
// this.isNotCloaked = false;
// this.isBadTarget = true;
// this.collisionFilter.mask = cat.bullet; //can't touch player or walls
// }

View File

@@ -4669,9 +4669,9 @@ const tech = {
frequency: 1,
frequencyDefault: 1,
allowed() {
return (tech.haveGunCheck("shotgun") && !tech.isNailShot && !tech.isIceShot && !tech.isRivets && !tech.isFoamShot && !tech.isSporeWorm && !tech.isSporeFlea && !tech.isNeedles) || (tech.haveGunCheck("super balls") && !tech.isFoamBall) || (tech.isRivets && !tech.isNailCrit) || (m.fieldUpgrades[m.fieldMode].name === "molecular assembler" && simulation.molecularMode === 3) || (tech.haveGunCheck("drones") && !tech.isForeverDrones && !tech.isDroneRadioactive && !tech.isDroneTeleport)
return (tech.haveGunCheck("shotgun") && !tech.isNailShot && !tech.isIceShot && !tech.isRivets && !tech.isFoamShot && !tech.isSporeWorm && !tech.isSporeFlea && !tech.isNeedles) || (tech.haveGunCheck("super balls") && !tech.isFoamBall && !tech.isSuperHarm) || (tech.isRivets && !tech.isNailCrit) || (m.fieldUpgrades[m.fieldMode].name === "molecular assembler" && simulation.molecularMode === 3) || (tech.haveGunCheck("drones") && !tech.isForeverDrones && !tech.isDroneRadioactive && !tech.isDroneTeleport)
},
requires: "shotgun, super balls, rivets, drones, not irradiated drones, burst drones, polyurethane",
requires: "shotgun, super balls, rivets, drones, not irradiated drones, burst drones, polyurethane, Zectron",
effect() {
tech.isIncendiary = true
},
@@ -4706,6 +4706,25 @@ const tech = {
}
}
},
{
name: "Zectron",
description: `<strong>+100%</strong> <strong>super ball</strong> density and <strong class='color-d'>damage</strong><br>after colliding with <strong>super balls</strong> <strong>lose</strong> <strong class='color-h'>health</strong>`,
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("super balls") && !tech.isIncendiary
},
requires: "super balls not incendiary ammunition",
effect() {
tech.isSuperHarm = true
},
remove() {
tech.isSuperHarm = false
}
},
{
name: "super duper",
description: `randomly fire <strong>+0</strong>, <strong>+1</strong>, or <strong>+2</strong> extra <strong>super balls</strong><br>&nbsp;`,
@@ -5016,7 +5035,7 @@ const tech = {
},
{
name: "launch system",
description: `<strong>+500%</strong> <strong>missile</strong> <strong><em>fire rate</em></strong><br><strong>+20%</strong> missile <strong class='color-ammo'>ammo</strong> per ${powerUps.orb.ammo(1)}`,
description: `<strong>+500%</strong> <strong>missile</strong> <strong class='color-g'>gun</strong> <strong><em>fire rate</em></strong><br><strong>+20%</strong> missile <strong class='color-ammo'>ammo</strong> per ${powerUps.orb.ammo(1)}`,
isGunTech: true,
maxCount: 1,
count: 0,
@@ -5607,6 +5626,25 @@ const tech = {
tech.isSporeGrowth = false
}
},
{
name: "cordyceps",
description: "mobs infected by <strong class='color-p' style='letter-spacing: 2px;'>sporangium</strong><br><strong>resurrect</strong> and attack other mobs",
isGunTech: true,
maxCount: 1,
count: 0,
frequency: 2,
frequencyDefault: 2,
allowed() {
return tech.haveGunCheck("spores")
},
requires: "spores",
effect() {
tech.isZombieMobs = true
},
remove() {
tech.isZombieMobs = false
}
},
{
name: "colony",
description: "<strong>+50%</strong> <strong class='color-p' style='letter-spacing: 2px;'>sporangium</strong> discharge<br><strong>40%</strong> chance to discharge something different",
@@ -6314,28 +6352,18 @@ const tech = {
ammoBonus: 9,
effect() {
tech.isRailGun = true;
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "harpoon") {
b.guns[i].chooseFireMethod()
b.guns[i].ammoPack = 5;
b.guns[i].ammo = b.guns[i].ammo * 6;
simulation.updateGunHUD();
break
}
}
b.guns[9].chooseFireMethod()
b.guns[9].ammoPack = 5;
b.guns[9].ammo = b.guns[9].ammo * 6;
simulation.updateGunHUD();
},
remove() {
if (tech.isRailGun) {
tech.isRailGun = false;
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun
if (b.guns[i].name === "harpoon") {
b.guns[i].chooseFireMethod()
b.guns[i].ammoPack = 1.7;
b.guns[i].ammo = Math.ceil(b.guns[i].ammo / 6);
simulation.updateGunHUD();
break
}
}
b.guns[9].chooseFireMethod()
b.guns[9].ammoPack = 1.7;
b.guns[9].ammo = Math.ceil(b.guns[9].ammo / 6);
simulation.updateGunHUD();
}
}
},
@@ -9288,6 +9316,28 @@ const tech = {
if (this.count) m.look = m.lookDefault
}
},
{
name: "p-zombie",
description: "set your <strong class='color-h'>health</strong> to <strong>1</strong><br>all mobs die and <strong>resurrect</strong> as zombies",
maxCount: 1,
count: 0,
frequency: 0,
isNonRefundable: true,
isJunk: true,
allowed() {return true},
requires: "",
effect() {
m.health = 0.01 //set health to 1
m.displayHealth();
for (let i = mob.length - 1; i > -1; i--) { //replace mobs with zombies
if (mob[i].isDropPowerUp && !mob[i].isBoss && mob[i].alive) {
mob[i].isSoonZombie = true
mob[i].death()
}
}
},
remove() {}
},
{
name: "decomposers",
description: "after they die <strong>mobs</strong> leave behind <strong>spawns</strong><br>&nbsp;",
@@ -11256,4 +11306,6 @@ const tech = {
buffedGun: 0,
isGunChoice: null,
railChargeRate: null,
isSuperHarm: null,
isZombieMobs: null
}

125
style.css
View File

@@ -192,7 +192,7 @@ summary {
.choose-grid-module {
/* padding: 5px; */
line-height: 170%;
line-height: 160%;
/* border-radius: 8px; */
background-color: #fafcfd;
font-size: 0.75em;
@@ -259,13 +259,13 @@ summary {
.pause-grid-module {
/* margin: -1px;
padding: 10px;
line-height: 170%;
line-height: 160%;
border: 1px #333 solid;
border-radius: 8px;
background-color: #fff;
font-size: 0.65em; */
padding: 5px;
line-height: 170%;
line-height: 160%;
/* border-radius: 8px; */
background-color: #fafcfd;
font-size: 0.75em;
@@ -308,7 +308,7 @@ summary {
.experiment-grid-module {
padding: 5px;
line-height: 170%;
line-height: 160%;
/* border-radius: 8px; */
background-color: #fafcfd;
font-size: 0.75em;
@@ -331,8 +331,9 @@ summary {
}
.card-background {
/* width:288; */
height:340px;
width:288;
background-size: contain;
background-repeat: no-repeat;
display: flex;
justify-content:flex-end;
@@ -370,6 +371,53 @@ summary {
}
/* media rules for smaller screens
4->3 at 1550
*/
@media (width < 0px) {
.experiment-grid-module {
line-height: 140%;
font-size: 0.65em;
}
.choose-grid-module{
line-height: 140%;
font-size: 0.65em;
}
.pause-grid-module {
line-height: 140%;
font-size: 0.65em;
}
.card-background{
height:270px;
}
#experiment-grid{
grid-template-columns: repeat(auto-fit, 360px);
}
#choose-grid{
grid-template-columns: repeat(auto-fit, 360px);
}
.pause-grid{
grid-template-columns: repeat(auto-fit, 360px);
}
}
/* default
.experiment-grid-module {
line-height: 160%;
font-size: 0.75em;
}
.card-background {
height:340px;
}
#experiment-grid{
grid-template-columns: repeat(auto-fit, 384px);
}
*/
/* .no-image-cards{
border: 1px solid #444;
} */
@@ -581,7 +629,7 @@ summary {
color: #222;
text-align: right;
opacity: 0.35;
line-height: 130%;
line-height: 120%;
background-color: rgba(255, 255, 255, 0.4);
user-select: none;
pointer-events: none;
@@ -811,7 +859,7 @@ summary {
padding: 4px;
border-radius: 4px;
font-weight: 800;
line-height: 170%;
line-height: 160%;
font-size: 1em;
}
@@ -851,98 +899,89 @@ summary {
font-size: 90%;
}
.circle {
width: 20px;
height: 20px;
border-radius: 50%;
display: inline-block;
margin-bottom: -2px;
}
.circle-grid {
width: 27px;
height: 27px;
width: 1.35em;
height: 1.35em;
border-radius: 50%;
display: inline-block;
margin-bottom: -7px;
/* opacity: 0; */
/* transition: opacity 0.5s ease-in; */
margin-bottom: -0.3em;
}
.research-circle {
width: 13px;
height: 13px;
width: 0.9em;
height: 0.9em;
border-radius: 50%;
display: inline-block;
background-color: #f7b;
border: 0.5px #fff solid;
border: 0.065em #fff solid;
opacity: 0.85;
margin-bottom: -2.5px;
margin-bottom: -0.1em;
}
.ammo-circle {
width: 11px;
height: 11px;
width: 0.75em;
height: 0.75em;
border-radius: 50%;
display: inline-block;
background-color: #467;
border: 0.5px #fff solid;
border: 0.05em #fff solid;
opacity: 0.95;
margin-bottom: -1.5px;
}
.heal-circle {
width: 14px;
height: 14px;
width: 0.95em;
height: 0.95em;
border-radius: 50%;
display: inline-block;
background-color: #0d9;
border: 0.5px #fff solid;
border: 0.05em #fff solid;
opacity: 0.85;
margin-bottom: -3px;
}
.heal-circle-energy {
width: 14px;
height: 14px;
width: 0.95em;
height: 0.95em;
border-radius: 50%;
display: inline-block;
background-color: #ff0;
border: 0.5px #000 solid;
border: 0.05em #000 solid;
opacity: 0.85;
margin-bottom: -3px;
}
.coupling-circle {
width: 10px;
height: 10px;
width: 0.7em;
height: 0.7em;
border-radius: 50%;
display: inline-block;
background-color: #0ae;
border: 0.5px #fff solid;
border: 0.05em #fff solid;
margin-bottom: -0.5px;
}
.boost-circle {
width: 10px;
height: 10px;
width: 0.7em;
height: 0.7em;
border-radius: 50%;
display: inline-block;
background-color: #f03;
border: 0.5px #fff solid;
border: 0.05em #fff solid;
opacity: 0.9;
margin-bottom: -0.5px;
}
.circle-grid-shadow {
/* .circle-grid-shadow {
width: 43px;
height: 43px;
border-radius: 50%;
display: inline-block;
margin-bottom: -15px;
}
} */
.circle-gun-tech {
/* .circle-gun-tech {
box-shadow: 0 0 0 3px #025;
}
} */
.junk {
background-color: hsl(254, 44%, 75%);

View File

@@ -1,39 +1,43 @@
******************************************************** NEXT PATCH **************************************************
adjust card size based on window width for smaller screens
switched from px to em css units for many elements
some image updates
single column power up choice for small screens or no image setting
tech Zectron - super ball can damage you, but they do more damage to mobs
superBall bullets are converted to run off generic b.superBall()
!!still needs testing for balance
tech: cordyceps - sporangium infect mobs, making them fight for you
zombies might attack player if there are no mob targets
!!still needs testing for balance
sneakers - after taking damage if sneakers are low on life they teleport to a random point on the player history and sneak attack again
sneakBoss - a large sneaker that can hide several times before it dies
bug fixes
*********************************************************** TODO *****************************************************
maybe harpoon should start with less ammo?
tech: p-zombie - sporangium infect mobs, making them fight for you
zombies should attack player if there are no mob targets
name: cordyceps, zombie, p-zombie?
infected mobs get a status debuff. when they die they return as zombie mob type
zombie mobs run code similar to drones
they inherit color, sides, radius from host
tech super balls - super ball can damage you, but it does more damage to mobs
tech - all mobs return as zombies?
set media rules for smaller screens
should fit 4 tech per row on my laptop
smaller card size
set font to 0.8em
set card width to 384*0.8
and set background images to fit
bug reactor boss + harpoon foam production seems to make too much foam
set mob health bar colors based on status effects?
make mob damage immunity a mob status effect?
physics notes: add link to double slit content
https://www.youtube.com/watch?v=v_uBaBuarEM
seekers after taking damage if seekers are below 1/2 life they teleport to a random point on the player history and sneak attack again
make sure they don't teleport on top of the player
tech: railgun area damage effect, but for all harpoon mode
tech: rail gun area damage effect, but for all harpoon mode
laser momentum pushed back on player?
might just be annoying