diff --git a/index.html b/index.html index a8c08a6..e9d2bb7 100644 --- a/index.html +++ b/index.html @@ -149,7 +149,7 @@ ZOOM - + / - + - / + or i / o PAUSE diff --git a/js/bullets.js b/js/bullets.js index 87068b9..ae58008 100644 --- a/js/bullets.js +++ b/js/bullets.js @@ -86,6 +86,7 @@ const b = { isModHarmReduce: null, modNailsDeathMob: null, isModSlowFPS: null, + isModNeutronStun: null, modOnHealthChange() { //used with acid mod if (b.isModAcidDmg && mech.health > 0.8) { b.modAcidDmg = 0.5 @@ -237,7 +238,7 @@ const b = { }, { name: "acute stress response", - description: "increase damage by 33%
but, after a mob dies lose 1/2 your energy", + description: "increase damage by 33%
but, after a mob dies lose 1/3 your energy", maxCount: 1, count: 0, allowed() { @@ -885,6 +886,22 @@ const b = { //nothing to undo } }, + { + name: "Lorentzian topology", + description: "your bullets last +33% longer", + maxCount: 3, + count: 0, + allowed() { + return mech.fieldUpgrades[mech.fieldMode].name === "nano-scale manufacturing" || b.haveGunCheck("spores") || b.haveGunCheck("drones") || b.haveGunCheck("super balls") || b.haveGunCheck("foam") || b.haveGunCheck("wave beam") || b.haveGunCheck("ice IX") || b.haveGunCheck("neutron bomb") + }, + requires: "drones, spores, super balls, foam
wave beam, ice IX, neutron bomb", + effect() { + b.isModBulletsLastLonger += 0.33 + }, + remove() { + b.isModBulletsLastLonger = 1; + } + }, { name: "depleted uranium rounds", description: `your bullets are +16% larger
increased mass and physical damage`, @@ -1212,6 +1229,22 @@ const b = { b.isModVacuumShield = false; } }, + { + name: "inertial confinement", + description: "neutron bomb's initial detonation
stuns nearby mobs for +1 seconds", + maxCount: 3, + count: 0, + allowed() { + return b.haveGunCheck("neutron bomb") + }, + requires: "neutron bomb", + effect() { + b.isModNeutronStun += 60; + }, + remove() { + b.isModNeutronStun = 0; + } + }, { name: "mine reclamation", description: "retrieve ammo from all undetonated mines
and 20% of mines after detonation", @@ -1276,22 +1309,6 @@ const b = { b.isModSporeFollow = false } }, - { - name: "Lorentzian topology", - description: "your bullets last +33% longer", - maxCount: 3, - count: 0, - allowed() { - return mech.fieldUpgrades[mech.fieldMode].name === "nano-scale manufacturing" || b.haveGunCheck("spores") || b.haveGunCheck("drones") || b.haveGunCheck("super balls") || b.haveGunCheck("foam") || b.haveGunCheck("wave beam") || b.haveGunCheck("ice IX") - }, - requires: "drones, spores, super balls,
foam, wave beam, or ice IX", - effect() { - b.isModBulletsLastLonger += 0.33 - }, - remove() { - b.isModBulletsLastLonger = 1; - } - }, { name: "redundant systems", description: "drone collisions no longer reduce their lifespan", @@ -3260,109 +3277,213 @@ const b = { }, { name: "neutron bomb", - description: "fire a bomb that emits neutron radiation
detonation occurs after any collision", + description: "toss a chunk of Cf-252 that emits neutrons
damages and drains energy in area of effect", ammo: 0, - ammoPack: 4, + ammoPack: 6, have: false, isStarterGun: false, isEasyToAim: true, fire() { const me = bullet.length; const dir = mech.angle; - bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 10, 6, b.fireAttributes(dir, false)); - b.fireProps(mech.crouch ? 30 : 20, mech.crouch ? 28 : 14, dir, me); //cd , speed + bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 10, 4, b.fireAttributes(dir, false)); + b.fireProps(mech.crouch ? 30 : 15, mech.crouch ? 28 : 18, dir, me); //cd , speed Matter.Body.setDensity(bullet[me], 0.000001); bullet[me].endCycle = Infinity; bullet[me].frictionAir = 0; - bullet[me].friction = 0.5; + bullet[me].friction = 1; + bullet[me].frictionStatic = 1; bullet[me].restitution = 0; bullet[me].minDmgSpeed = 0; bullet[me].damageRadius = 100; - bullet[me].maxDamageRadius = 600 + bullet[me].maxDamageRadius = 450 + 150 * Math.random() + bullet[me].stuckTo = null; + bullet[me].stuckToRelativePosition = null; bullet[me].onDmg = function () {}; - + bullet[me].stuck = function () {}; bullet[me].do = function () { - this.force.y += this.mass * 0.001; - if (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length || Matter.Query.collides(this, mob).length) { - //have the bullet stick to mobs and blocks? - //stun mobs in range - // for (let i = 0, len = mob.length; i < len; i++) { - // const dx = this.position.x - mob[i].position.x; - // const dy = this.position.y - mob[i].position.y; - // if (dx * dx + dy * dy < this.maxDamageRadius * this.maxDamageRadius) { - // mobs.statusStun(mob[i], 60) - // } - // } - - //push away blocks when firing - for (let i = 0, len = body.length; i < len; ++i) { - const SUB = Vector.sub(body[i].position, this.position) - const DISTANCE = Vector.magnitude(SUB) - if (DISTANCE < this.maxDamageRadius) { - const FORCE = Vector.mult(Vector.normalise(SUB), 0.01 * body[i].mass) - body[i].force.x += FORCE.x; - body[i].force.y += FORCE.y - body[i].mass * (game.g * 2); //kick up a bit to give them some arc - } - } - //stun mobs, but don't push away - for (let i = 0, len = mob.length; i < len; ++i) { - if (Vector.magnitude(Vector.sub(mob[i].position, this.position)) < this.maxDamageRadius) { - mobs.statusStun(mob[i], 60) - } - } - - //use a constraint? - Matter.Sleeping.set(this, true) //freeze in place - this.collisionFilter.mask = 0; - Matter.Body.setVelocity(this, { + function onCollide(that) { + that.collisionFilter.mask = 0; //non collide with everything + Matter.Body.setVelocity(that, { x: 0, y: 0 }); - const SCALE = 0.01 - Matter.Body.scale(this, SCALE, SCALE); - this.do = this.radiationMode; + // that.frictionAir = 1; + that.do = that.radiationMode; + + if (b.isModNeutronStun) { + //push blocks + const dist = that.maxDamageRadius * 0.9 + for (let i = 0, len = body.length; i < len; ++i) { + const SUB = Vector.sub(body[i].position, that.position) + const DISTANCE = Vector.magnitude(SUB) + if (DISTANCE < dist) { + const FORCE = Vector.mult(Vector.normalise(SUB), 0.04 * body[i].mass) + body[i].force.x += FORCE.x; + body[i].force.y += FORCE.y - body[i].mass * game.g * 5; //kick up a bit to give them some arc + } + } + //stun mobs + for (let i = 0, len = mob.length; i < len; ++i) { + if (Vector.magnitude(Vector.sub(mob[i].position, that.position)) < dist) { + mobs.statusStun(mob[i], b.isModNeutronStun) + } + } + } } + + + const mobCollisions = Matter.Query.collides(this, mob) + if (mobCollisions.length) { + onCollide(this) + this.stuckTo = mobCollisions[0].bodyA + + 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; //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) { + 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) + 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 { + if (Matter.Query.collides(this, map).length) { + onCollide(this) + + // this.stuck = function () { + // Matter.Body.setVelocity(this, { + // x: 0, + // y: 0 + // }); + // } + } else { //if colliding with nothing just fall + this.force.y += this.mass * 0.001; + } + } + } + + + + // if (Matter.Query.collides(this, map).length || bodyCollisions.length || mobCollisions.length) { + // if (mobCollisions.length) { + // this.isStuck = true; + // this.stuckTo = mobCollisions[0].bodyA + // this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), this.stuckTo.angle) + // } else if (bodyCollisions.length) { + // this.isStuck = true; + // this.stuckTo = bodyCollisions[0].bodyA + // this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), this.stuckTo.angle) + // } + + // if (b.isModNeutronStun) { + // //push blocks + // const dist = this.maxDamageRadius * 0.9 + // for (let i = 0, len = body.length; i < len; ++i) { + // const SUB = Vector.sub(body[i].position, this.position) + // const DISTANCE = Vector.magnitude(SUB) + // if (DISTANCE < dist) { + // const FORCE = Vector.mult(Vector.normalise(SUB), 0.04 * body[i].mass) + // body[i].force.x += FORCE.x; + // body[i].force.y += FORCE.y - body[i].mass * game.g * 5; //kick up a bit to give them some arc + // } + // } + // //stun mobs + // for (let i = 0, len = mob.length; i < len; ++i) { + // if (Vector.magnitude(Vector.sub(mob[i].position, this.position)) < dist) { + // mobs.statusStun(mob[i], b.isModNeutronStun) + // } + // } + // } + + // this.collisionFilter.mask = cat.map; //non collide with everything + // Matter.Body.setVelocity(this, { + // x: 0, + // y: 0 + // }); + // // this.frictionAir = 1; + // this.do = this.radiationMode; + // } else { + // this.force.y += this.mass * 0.001; + // } }; bullet[me].radiationMode = function () { - // if (this.endCycle - 360 < game.cycle) { - // this.damageRadius = this.damageRadius * 0.992 - // } else { - // this.damageRadius = this.damageRadius * 0.98 + 0.02 * this.maxDamageRadius - // this.damageRadius = Math.max(0, this.damageRadius + 2 * (Math.random() - 0.5)) + this.stuck(); //runs different code based on what the bullet is stuck to + // if (this.isStuck) { + // if (this.stuckTo.alive || this.stuckTo.classType === "body") { + // const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) + // Matter.Body.setPosition(this, Vector.add(rotate, this.stuckTo.position)) + // // Matter.Body.setPosition(this, Vector.add(this.stuckToRelativePosition, this.stuckTo.position)) + // Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck + // } else { + // this.isStuck = false; + // // this.force.y += this.mass * 0.5; + // } // } + if (!mech.isBodiesAsleep) { - this.damageRadius = this.damageRadius * 0.9 + 0.1 * this.maxDamageRadius //smooth radius towards max - this.maxDamageRadius = this.maxDamageRadius * 0.999 - 0.7 //slowly shrink max radius + this.damageRadius = this.damageRadius * 0.85 + 0.15 * this.maxDamageRadius //smooth radius towards max + this.maxDamageRadius -= 0.8 / b.isModBulletsLastLonger //+ 0.5 * Math.sin(game.cycle * 0.1) //slowly shrink max radius } if (this.damageRadius < 15) { this.endCycle = 0; } else { //aoe damage to player if (Vector.magnitude(Vector.sub(player.position, this.position)) < this.damageRadius) { - const DRAIN = 0.003 + const DRAIN = 0.0015 if (mech.energy > DRAIN) { mech.energy -= DRAIN } else { mech.energy = 0; - mech.damage(0.001) + 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) { if (mob[i].shield) { - mob[i].damage(5 * b.dmgScale * 0.02); + mob[i].damage(5 * b.dmgScale * 0.025); } else { - mob[i].damage(b.dmgScale * 0.02); + mob[i].damage(b.dmgScale * 0.025); } - mob[i].locatePlayer(); } } + + //draw it differently for the initial detonation to make the stun seem logical. + //make draw some electricity? + ctx.beginPath(); ctx.arc(this.position.x, this.position.y, this.damageRadius, 0, 2 * Math.PI); ctx.globalCompositeOperation = "lighter" - ctx.fillStyle = `rgba(0,100,120,${0.3+0.05*Math.random()})`; + // ctx.fillStyle = `hsla(189,60%,50%,${0.35+0.06*Math.random()})`; + ctx.fillStyle = `rgba(20,130,160,${0.35+0.06*Math.random()})`; ctx.fill(); ctx.globalCompositeOperation = "source-over" } diff --git a/js/game.js b/js/game.js index 89bd949..69729ad 100644 --- a/js/game.js +++ b/js/game.js @@ -263,12 +263,12 @@ const game = { // mech.drop(); }, keyPress() { //runs on key down event - if (keys[189]) { + if (keys[189] || keys[79]) { // - key game.isAutoZoom = false; game.zoomScale /= 0.9; game.setZoom(); - } else if (keys[187]) { + } else if (keys[187] || keys[73]) { // = key game.isAutoZoom = false; game.zoomScale *= 0.9; diff --git a/js/level.js b/js/level.js index b3e1a4f..a0edbfc 100644 --- a/js/level.js +++ b/js/level.js @@ -21,7 +21,7 @@ const level = { // b.giveMod("renormalization"); // b.giveMod("impact shear"); // b.giveMod("clock gating"); - b.giveGuns("neutron bomb") + // b.giveGuns("neutron bomb") // mech.setField("pilot wave") // mech.setField("perfect diamagnetism") @@ -102,7 +102,7 @@ const level = { //****************************************************************************************************************** testing() { - level.difficultyIncrease(9); + level.difficultyIncrease(14); //hard mode level 7 spawn.setSpawnList(); spawn.setSpawnList(); level.defaultZoom = 1500 @@ -157,8 +157,10 @@ const level = { // spawn.bomberBoss(2900, -500) // spawn.shooterBoss(1200, -500) - // spawn.spawns(1200, -500) - spawn.hopper(1600, -500) + // spawn.spinner(1200, -500) + // spawn.grower(1600, -500) + spawn.cellBossCulture(1600, -500) + // spawn.shooter(1600, -500) // spawn.shield(mob[mob.length - 1], 1200, -500, 1); // spawn.nodeBoss(1200, -500, "spiker") diff --git a/js/mobs.js b/js/mobs.js index 0d9aa61..3372100 100644 --- a/js/mobs.js +++ b/js/mobs.js @@ -972,7 +972,7 @@ const mobs = { this.removeConsBB(); this.alive = false; //triggers mob removal in mob[i].replace(i) if (this.dropPowerUp) { - if (b.isModEnergyLoss) mech.energy /= 2; + if (b.isModEnergyLoss) mech.energy /= 3; powerUps.spawnRandomPowerUp(this.position.x, this.position.y, this.mass, radius); mech.lastKillCycle = mech.cycle; //tracks the last time a kill was made, mostly used in game.checks() if (Math.random() < b.modSporesOnDeath) { @@ -1034,7 +1034,8 @@ const mobs = { //if there are too many bodies don't turn into blocks to help performance if (this.leaveBody && body.length < 60 && this.mass < 100) { const len = body.length; - body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, this.vertices); + const v = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) //might help with vertex collision issue, not sure + body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, v); Matter.Body.setVelocity(body[len], this.velocity); Matter.Body.setAngularVelocity(body[len], this.angularVelocity); body[len].collisionFilter.category = cat.body; diff --git a/js/player.js b/js/player.js index 01cfdc6..fcfff3a 100644 --- a/js/player.js +++ b/js/player.js @@ -1871,8 +1871,8 @@ const mech = { mech.lookForPickUp(); const DRAIN = 0.0004 + 0.0002 * player.speed + ((!b.modRenormalization && mech.fireCDcycle > mech.cycle) ? 0.005 : 0.0017) - mech.energy -= DRAIN; if (mech.energy > DRAIN) { + mech.energy -= DRAIN; // if (mech.energy < 0.001) { // mech.fieldCDcycle = mech.cycle + 120; // mech.energy = 0; diff --git a/js/spawn.js b/js/spawn.js index 4b661ac..7f9aa0d 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -160,6 +160,7 @@ const spawn = { me.isCell = true; me.accelMag = 0.00015 * game.accelScale; me.memory = 40; + me.isVerticesChange = true me.frictionAir = 0.012 me.seePlayerFreq = Math.floor(11 + 7 * Math.random()) me.seeAtDistance2 = 1400000; @@ -173,12 +174,14 @@ const spawn = { Matter.Body.scale(this, 0.4, 0.4); this.radius = Math.sqrt(this.mass * k / Math.PI) spawn.cellBoss(this.position.x, this.position.y, this.radius); + mob[mob.length - 1].health = this.health } me.onHit = function () { //run this function on hitting player + this.health = 1; this.split(); }; me.onDamage = function (dmg) { - if (Math.random() < 0.17 * dmg * Math.sqrt(this.mass) && this.health > dmg) this.split(); + if (Math.random() < 0.33 * dmg * Math.sqrt(this.mass) && this.health > dmg) this.split(); } me.do = function () { if (!mech.isBodiesAsleep) { @@ -326,9 +329,13 @@ const spawn = { grower(x, y, radius = 15) { mobs.spawn(x, y, 7, radius, "hsl(144, 15%, 50%)"); let me = mob[mob.length - 1]; + me.isVerticesChange = true me.big = false; //required for grow me.accelMag = 0.00045 * game.accelScale; me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.player //can't touch other mobs + // me.onDeath = function () { //helps collisions functions work better after vertex have been changed + // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) + // } me.do = function () { this.seePlayerByLookingAt(); this.checkStatus(); @@ -907,6 +914,7 @@ const spawn = { x: x, y: y } + me.count = 0; me.frictionAir = 0.03; // me.torque -= me.inertia * 0.002 Matter.Body.setDensity(me, 0.03); //extra dense //normal is 0.001 //makes effective life much larger @@ -928,7 +936,10 @@ const spawn = { break } } - if (!slowed) Matter.Body.setAngle(me, game.cycle * this.rotateVelocity) + if (!slowed) { + this.count++ + Matter.Body.setAngle(me, this.count * this.rotateVelocity) + } // this.torque -= this.inertia * 0.0000025 / (4 + this.health); Matter.Body.setVelocity(this, { @@ -1029,6 +1040,7 @@ const spawn = { if (radius > 80) radius = 65; mobs.spawn(x, y, 6, radius, "rgb(220,50,205)"); //can't have sides above 6 or collision events don't work (probably because of a convex problem) let me = mob[mob.length - 1]; + me.isVerticesChange = true me.accelMag = 0.0006 * game.accelScale; // me.g = 0.0002; //required if using 'gravity' me.delay = 360 * game.CDScale; @@ -1046,6 +1058,7 @@ const spawn = { const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[this.spikeVertex], this.position)), this.radius * this.spikeLength) this.vertices[this.spikeVertex].x = this.position.x + spike.x this.vertices[this.spikeVertex].y = this.position.y + spike.y + this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) } }; me.do = function () { @@ -1339,7 +1352,10 @@ const spawn = { shooter(x, y, radius = 25 + Math.ceil(Math.random() * 50)) { mobs.spawn(x, y, 3, radius, "rgb(255,100,150)"); let me = mob[mob.length - 1]; - me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + me.vertices = Matter.Vertices.clockwiseSort(Matter.Vertices.rotate(me.vertices, Math.PI, me.position)); //make the pointy side of triangle the front + me.isVerticesChange = true + // Matter.Body.rotate(me, Math.PI) + me.memory = 120; me.fireFreq = 0.007 + Math.random() * 0.005; me.noseLength = 0; @@ -1351,6 +1367,9 @@ const spawn = { x: 0, y: 0 }; + me.onDeath = function () { //helps collisions functions work better after vertex have been changed + this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) + } // spawn.shield(me, x, y); me.do = function () { this.seePlayerByLookingAt(); @@ -1359,10 +1378,10 @@ const spawn = { }; }, shooterBoss(x, y, radius = 130) { - //boss spawns on skyscraper level mobs.spawn(x, y, 3, radius, "rgb(255,70,180)"); let me = mob[mob.length - 1]; me.vertices = Matter.Vertices.rotate(me.vertices, Math.PI, me.position); //make the pointy side of triangle the front + me.isVerticesChange = true me.memory = 240; me.homePosition = { x: x, @@ -1381,7 +1400,9 @@ const spawn = { Matter.Body.setDensity(me, 0.02 + 0.0008 * Math.sqrt(game.difficulty)); //extra dense //normal is 0.001 //makes effective life much larger me.onDeath = function () { powerUps.spawnBossPowerUp(this.position.x, this.position.y) + // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) //helps collisions functions work better after vertex have been changed }; + me.do = function () { this.seePlayerByLookingAt(); this.checkStatus(); diff --git a/todo.txt b/todo.txt index 99492e0..fc5d2bb 100644 --- a/todo.txt +++ b/todo.txt @@ -1,15 +1,31 @@ +acute stress mod removes 1/3 (was 1/2) of your energy +zoom works with i and o now +cell boss has double the chance to spilt on damage. +After it splits, each daughter cell has the mother's reduced health + +new gun neutron bomb, persistent AoE damage + (might still have issues sticking to objects properly) +new mob neutron bomb stuns mobs + ************** TODO - n-gon ************** -frozen mobs take +33% damage +extend neutron mob sticking code to foam gun and mines? -a lasting AoE damage gun - reuse sporangium code - maybe mod for vacuum bomb where it does aoe damage before it explodes +phase field is kinda annoying + large vision range, + faster animation (instant?) + shrink vision range slowly over time, not with energy + also shrink when firing or moving? + +mod - frozen mobs take +33% damage bot that punches nearby mobs bot could have a regeneration phase, and a punching phase indicate phase by the size, shape of bot +mod heal to full at the end of each level + heal mods no longer drop? + possible names for mods Hypergolic - A hypergolic propellant combination used in a rocket engine is one whose components spontaneously ignite when they come into contact with each other. @@ -64,6 +80,8 @@ scrap drops that can be used to buy things in a shop scrap could be yellow shop could appear every 4 levels +mob - stuns, or slows player + boss mob - let it die multiple times and come back to life on death event spawns a new version of self, but with a decrementing counter