From f338184869d5a1662413db76379a6c7f37f196f3 Mon Sep 17 00:00:00 2001 From: landgreen Date: Sun, 24 May 2020 18:45:53 -0700 Subject: [PATCH] sniper mob balance, bot replication mod --- js/bullet.js | 22 +++++-- js/engine.js | 19 ++++++ js/level.js | 11 ++-- js/mods.js | 61 ++++++++++++----- js/player.js | 5 +- js/spawn.js | 180 +++++++++++++++++++++++++++++++++++++++++++++------ todo.txt | 5 ++ 7 files changed, 254 insertions(+), 49 deletions(-) diff --git a/js/bullet.js b/js/bullet.js index d940d8e..3e807d8 100644 --- a/js/bullet.js +++ b/js/bullet.js @@ -2077,7 +2077,7 @@ const b = { isEasyToAim: true, fire() { if (mech.crouch) { - b.iceIX(20, 0.3) + b.iceIX(10, 0.3) mech.fireCDcycle = mech.cycle + Math.floor(10 * mod.fireRate); // cool down } else { b.iceIX(2) @@ -2160,13 +2160,23 @@ const b = { bullet[me].endCycle = Infinity bullet[me].charge = 0; bullet[me].do = function () { - if ((!game.mouseDown && this.charge > 0.6)) { //fire on mouse release - //normal bullet behavior occurs after firing, overwrite this function - this.do = function () { - this.force.y += this.mass * 0.0003 / this.charge; // low gravity that scales with charge + if ((!game.mouseDown && this.charge > 0.6) || mech.energy < 0.005) { //fire on mouse release + if (mech.energy < 0.005) { + this.charge = 0.1; + mech.fireCDcycle = mech.cycle + 120; // cool down if out of energy + //normal bullet behavior occurs after firing, overwrite this function + this.do = function () { + this.force.y += this.mass * 0.001; //normal gravity + } + } else { + mech.fireCDcycle = mech.cycle + 2; // set fire cool down + //normal bullet behavior occurs after firing, overwrite this function + this.do = function () { + this.force.y += this.mass * 0.0003 / this.charge; // low gravity that scales with charge + } } - mech.fireCDcycle = mech.cycle + 2; // set fire cool down + Matter.Body.scale(this, 8000, 8000) // show the bullet by scaling it up (don't judge me... I know this is a bad way to do it) this.endCycle = game.cycle + 140 this.collisionFilter.category = cat.bullet diff --git a/js/engine.js b/js/engine.js index a389cda..c5f48a8 100644 --- a/js/engine.js +++ b/js/engine.js @@ -136,6 +136,24 @@ function collisionChecks(event) { function collideMob(obj) { //player + mob collision if (mech.immuneCycle < mech.cycle && (obj === playerBody || obj === playerHead)) { + // const a = Object.values(event.pairs[0].contacts) + // contains = Matter.Bounds.contains({ + // max: { + // x: player.position.x + 60, + // y: player.position.y + 120 + // }, + // min: { + // x: player.position.x - 60, + // y: player.position.y + 40 + // } + // }, { + // x: a[0].vertex.x, + // y: a[0].vertex.y + // }) + // // Matter.Query.point([jumpSensor], point) + // console.log(contains) + // if (!contains) { + mech.immuneCycle = mech.cycle + mod.collisionImmuneCycles; //player is immune to collision damage for 30 cycles mob[k].foundPlayer(); let dmg = Math.min(Math.max(0.025 * Math.sqrt(mob[k].mass), 0.05), 0.3) * game.dmgScale; //player damage is capped at 0.3*dmgScale of 1.0 @@ -179,6 +197,7 @@ function collisionChecks(event) { } return; + // } } //mob + bullet collisions if (obj.classType === "bullet" && obj.speed > obj.minDmgSpeed) { diff --git a/js/level.js b/js/level.js index 9f79a41..4df99eb 100644 --- a/js/level.js +++ b/js/level.js @@ -23,8 +23,8 @@ const level = { // mech.setField("pilot wave") // mech.setField("phase decoherence field") - level.intro(); //starting level - // level.testing(); + // level.intro(); //starting level + level.testing(); // level.stronghold() // level.bosses(); // level.satellite(); @@ -183,9 +183,10 @@ const level = { spawn.boost(1500, 0, 900); // spawn.bomberBoss(2900, -500) - spawn.launcherBoss(1200, -500) - spawn.launcher(1600, -400) - // spawn.spawner(1600, -500) + // spawn.launcherBoss(1200, -500) + // spawn.sniper(1600, -400) + // spawn.sneaker(1600, -500) + spawn.sniper(1700, -120) // spawn.cellBossCulture(1600, -500) // spawn.shooter(1600, -500) // spawn.striker(1600, -500) diff --git a/js/mods.js b/js/mods.js index c6af864..8d38775 100644 --- a/js/mods.js +++ b/js/mods.js @@ -330,7 +330,7 @@ const mod = { maxCount: 6, count: 0, allowed() { - return mod.foamBotCount > 0 || mod.nailBotCount > 0 || mod.laserBotCount > 0 + return mod.foamBotCount + mod.nailBotCount + mod.laserBotCount > 0 }, requires: "a bot", effect() { @@ -340,6 +340,38 @@ const mod = { mod.isBotSpawner = 0; } }, + { + name: "self-replication", + description: "duplicate your permanent bots
remove all your ammo", + maxCount: 1, + count: 0, + isNonRefundable: true, + allowed() { + return mod.foamBotCount + mod.nailBotCount + mod.laserBotCount > 1 + }, + requires: "2 or more bots", + effect() { + //remove ammo + for (let i = 0, len = b.guns.length; i < len; ++i) { + if (b.guns[i].ammo != Infinity) b.guns[i].ammo = 0; + } + + //double bots + for (let i = 0; i < mod.nailBotCount; i++) { + b.nailBot(); + } + mod.nailBotCount *= 2 + for (let i = 0; i < mod.laserBotCount; i++) { + b.laserBot(); + } + mod.laserBotCount *= 2 + for (let i = 0; i < mod.foamBotCount; i++) { + b.foamBot(); + } + mod.foamBotCount *= 2 + }, + remove() {} + }, { name: "ablative mines", description: "rebuild your broken parts as a mine
chance to occur after being harmed", @@ -769,6 +801,7 @@ const mod = { description: "spawn 5 mods
power ups are limited to one choice", maxCount: 1, count: 0, + // isNonRefundable: true, allowed() { return !mod.isExtraChoice }, @@ -786,7 +819,7 @@ const mod = { }, { name: "many-worlds", - description: "if you have zero rerolls, spawn a reroll
after choosing a gun, field, or mod", + description: "after choosing a gun, field, or mod
spawn a reroll, if you have none", maxCount: 1, count: 0, allowed() { @@ -844,6 +877,7 @@ const mod = { description: "remove all current mods
spawn new mods to replace them", maxCount: 1, count: 0, + isNonRefundable: true, allowed() { return (mod.totalCount > 6) && !build.isCustomSelection }, @@ -853,23 +887,22 @@ const mod = { for (let i = 0; i < bullet.length; ++i) Matter.World.remove(engine.world, bullet[i]); bullet = []; - let count = mod.totalCount - if (mod.isDeterminism) count -= 4 //remove the 5 bonus mods when getting rid of determinism - for (let i = 0; i < count; i++) { // spawn new mods + let count = mod.totalCount + 1 + if (mod.isDeterminism) count -= 5 //remove the 5 bonus mods when getting rid of determinism + mod.setupAllMods(); // remove all mods + for (let i = 0; i < count; i++) { // spawn new mods power ups powerUps.spawn(mech.pos.x, mech.pos.y, "mod"); } - mod.setupAllMods(); // remove all mods //have state is checked in mech.death() }, - remove() { - //nothing to undo - } + remove() {} }, { name: "reallocation", description: "convert 1 random mod into 2 new guns
recursive mods lose all stacks", maxCount: 1, count: 0, + isNonRefundable: true, allowed() { return (mod.totalCount > 0) && !build.isCustomSelection }, @@ -889,9 +922,7 @@ const mod = { if (Math.random() < mod.bayesian) powerUps.spawn(mech.pos.x, mech.pos.y, "gun"); } }, - remove() { - //nothing to remove - } + remove() {} }, //************************************************** //************************************************** gun @@ -1178,7 +1209,7 @@ const mod = { } }, { - name: "self-replication", + name: "recursion", description: "after missiles explode
they launch +1 smaller missile", maxCount: 9, count: 0, @@ -1674,7 +1705,7 @@ const mod = { }, { name: "renormalization", - description: "phase decoherence has increased visibility
and 5x less energy drain when firing", + description: "phase decoherence adds visibility to bullets
5x less energy drain when firing", maxCount: 1, count: 0, allowed() { @@ -1691,7 +1722,7 @@ const mod = { { name: "superposition", // description: "phase decoherence field applies a stun
to unshielded mobs for 2 seconds", - description: "apply a 4 second stun to unshielded mobs
that overlap with phase decoherence field", + description: "while phase decoherence field is active
mobs that overlap with the player are stunned", maxCount: 1, count: 0, allowed() { diff --git a/js/player.js b/js/player.js index b558784..8cb63c7 100644 --- a/js/player.js +++ b/js/player.js @@ -372,11 +372,8 @@ const mech = { let options = []; for (let i = 0, len = mod.mods.length; i < len; i++) { if (mod.mods[i].count < mod.mods[i].maxCount && + !mod.mods[i].isNonRefundable && mod.mods[i].name !== "quantum immortality" && - mod.mods[i].name !== "Born rule" && - mod.mods[i].name !== "determinism" && - mod.mods[i].name !== "reallocation" && - mod.mods[i].name !== "many-worlds" && mod.mods[i].allowed() ) options.push(i); } diff --git a/js/spawn.js b/js/spawn.js index 8b8bfc4..5552d2c 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -2,25 +2,26 @@ const spawn = { pickList: ["starter", "starter"], fullPickList: [ - "shooter", "shooter", "shooter", "shooter", - "hopper", "hopper", "hopper", "hopper", - "chaser", "chaser", "chaser", - "striker", "striker", - "laser", "laser", - "exploder", "exploder", - "stabber", "stabber", - "launcher", "launcher", - "spinner", - "grower", - "springer", - "beamer", - "focuser", - "sucker", - "spawner", - "ghoster", - "sneaker", + // "hopper", "hopper", "hopper", "hopper", + // "shooter", "shooter", "shooter", + // "chaser", "chaser", + // "striker", "striker", + // "laser", "laser", + // "exploder", "exploder", + // "stabber", "stabber", + // "launcher", "launcher", + "sniper", + // "spinner", + // "grower", + // "springer", + // "beamer", + // "focuser", + // "sucker", + // "spawner", + // "ghoster", + // "sneaker", ], - allowedBossList: ["chaser", "spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter", "launcher", "stabber"], + allowedBossList: ["chaser", "spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter", "launcher", "stabber", "sniper"], setSpawnList() { //this is run at the start of each new level to determine the possible mobs for the level //each level has 2 mobs: one new mob and one from the last level spawn.pickList.splice(0, 1); @@ -1353,7 +1354,8 @@ 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.clockwiseSort(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.vertices = 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) @@ -1437,6 +1439,146 @@ const spawn = { this.timeLimit(); }; }, + sniper(x, y, radius = 35 + Math.ceil(Math.random() * 30)) { + mobs.spawn(x, y, 3, radius, "transparent"); //"rgb(25,0,50)") + 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 + // Matter.Body.rotate(me, Math.PI) + me.stroke = "transparent"; //used for drawSneaker + me.alpha = 1; //used in drawSneaker + me.showHealthBar = false; + + me.canTouchPlayer = false; //used in drawSneaker + me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player + + me.memory = 60 //140; + me.fireFreq = 0.006 + Math.random() * 0.002; + me.noseLength = 0; + me.fireAngle = 0; + me.accelMag = 0.0005 * game.accelScale; + me.frictionAir = 0.05; + me.torque = 0.0001 * me.inertia; + me.fireDir = { + 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(); + this.seePlayerCheck(); + this.checkStatus(); + + if (!mech.isBodiesAsleep) { + const setNoseShape = () => { + const mag = this.radius + this.radius * this.noseLength; + this.vertices[1].x = this.position.x + Math.cos(this.angle) * mag; + this.vertices[1].y = this.position.y + Math.sin(this.angle) * mag; + }; + //throw a mob/bullet at player + if (this.seePlayer.recall) { + //set direction to turn to fire + if (!(game.cycle % this.seePlayerFreq)) { + this.fireDir = Vector.normalise(Vector.sub(this.seePlayer.position, this.position)); + // this.fireDir.y -= Math.abs(this.seePlayer.position.x - this.position.x) / 1600; //gives the bullet an arc + } + //rotate towards fireAngle + const angle = this.angle + Math.PI / 2; + c = Math.cos(angle) * this.fireDir.x + Math.sin(angle) * this.fireDir.y; + const threshold = 0.2; + if (c > threshold) { + this.torque += 0.000004 * this.inertia; + } else if (c < -threshold) { + this.torque -= 0.000004 * this.inertia; + } else if (this.noseLength > 1.5) { + //fire + spawn.sniperBullet(this.vertices[1].x, this.vertices[1].y, 5 + Math.ceil(this.radius / 15), 4); + const v = 20 * game.accelScale; + Matter.Body.setVelocity(mob[mob.length - 1], { + x: this.velocity.x + this.fireDir.x * v + Math.random(), + y: this.velocity.y + this.fireDir.y * v + Math.random() + }); + this.noseLength = 0; + // recoil + this.force.x -= 0.005 * this.fireDir.x * this.mass; + this.force.y -= 0.005 * this.fireDir.y * this.mass; + } else { + this.torque += 0.000001 * this.inertia; // + } + if (this.noseLength < 1.5) this.noseLength += this.fireFreq; + setNoseShape(); + } else if (this.noseLength > 0.1) { + this.noseLength -= this.fireFreq / 2; + setNoseShape(); + } + // else if (this.noseLength < -0.1) { + // this.noseLength += this.fireFreq / 4; + // setNoseShape(); + // } + + if (this.seePlayer.recall) { + if (this.alpha < 1) this.alpha += 0.01; + } else { + if (this.alpha > 0) this.alpha -= 0.03; + } + } + //draw + if (this.alpha > 0) { + if (this.alpha > 0.95) { + this.healthBar(); + if (!this.canTouchPlayer) { + this.canTouchPlayer = true; + this.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob; //can touch player + } + } + //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(25,0,50,${this.alpha * this.alpha})`; + ctx.fill(); + } else if (this.canTouchPlayer) { + this.canTouchPlayer = false; + this.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob //can't touch player + } + + }; + }, + sniperBullet(x, y, radius = 6, sides = 4) { + //bullets + mobs.spawn(x, y, sides, radius, "rgb(190,0,255)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + me.onHit = function () { + this.explode(this.mass * 10); + }; + Matter.Body.setDensity(me, 0.0001); //normal is 0.001 + me.timeLeft = 240; + me.g = 0.001; //required if using 'gravity' + me.frictionAir = 0; + me.restitution = 0; + me.leaveBody = false; + me.dropPowerUp = false; + me.showHealthBar = false; + me.collisionFilter.category = cat.mobBullet; + me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet; + me.do = function () { + // this.gravity(); + this.timeLimit(); + if (Matter.Query.collides(this, map).length > 0 || Matter.Query.collides(this, body).length > 0) { + // this.timeLeft = 0 + this.dropPowerUp = false; + this.death(); //death with no power up + } + }; + }, launcher(x, y, radius = 30 + Math.ceil(Math.random() * 40)) { mobs.spawn(x, y, 3, radius, "rgb(150,150,255)"); let me = mob[mob.length - 1]; diff --git a/todo.txt b/todo.txt index 69455a1..5154eba 100644 --- a/todo.txt +++ b/todo.txt @@ -1,6 +1,11 @@ +new mod: doubles current bots ************** TODO - n-gon ************** +shielded mobs don't knock back the same as unshielded mobs + +buff harmonic field to cover legs + movement fluidity let legs jump on mobs, but player will still take damage like: ori and the blind forest, celeste