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