let bullet = [];
const b = {
dmgScale: null, //scales all gun damage from momentum, but not raw .dmg //set in levels.setDifficulty
gravity: 0.0006, //most other bodies have gravity = 0.001
//variables use for gun mod upgrades
modCount: null,
modFireRate: null,
modExplosionRadius: null,
modBulletSize: null,
modEnergySiphon: null,
modHealthDrain: null,
modNoAmmo: null,
isModBulletsLastLonger: null,
isModImmortal: null,
modSpores: null,
isModImmuneExplosion: null,
isModDroneOnDamage: null,
modExtraDmg: null,
annihilation: null,
modRecursiveHealing: null,
modSquirrelFx: null,
isModCrit: null,
isModBayesian: null,
isModLowHealthDmg: null,
isModFarAwayDmg: null,
isModEntanglement: null,
isModMassEnergy: null,
isModFourOptions: null,
modLaserBotCount: null,
modNailBotCount: null,
modCollisionImmuneCycles: null,
modBlockDmg: null,
isModPiezo: null,
setModDefaults() {
b.modCount = 0;
b.modFireRate = 1;
b.modExplosionRadius = 1;
b.isModImmuneExplosion = false;
b.modBulletSize = 1;
b.isModDroneOnDamage = false;
b.modEnergySiphon = 0;
b.modHealthDrain = 0;
b.modNoAmmo = 0;
b.isModBulletsLastLonger = 1;
b.isModImmortal = false;
b.modSpores = 0;
b.modExtraDmg = 0;
b.isModAnnihilation = false;
b.modRecursiveHealing = 1;
b.modSquirrelFx = 1;
b.isModCrit = false;
b.isModBayesian = 0;
b.isModFourOptions = false;
b.isModLowHealthDmg = false;
b.isModFarAwayDmg = false;
b.isModEntanglement = false;
b.isModMassEnergy = false;
b.modLaserBotCount = 0;
b.modNailBotCount = 0;
b.modCollisionImmuneCycles = 30;
b.modBlockDmg = 0;
b.isModPiezo = false;
mech.Fx = 0.015;
mech.jumpForce = 0.38;
mech.maxHealth = 1;
mech.fieldEnergyMax = 1;
for (let i = 0; i < b.mods.length; i++) {
b.mods[i].count = 0
}
},
mods: [{
name: "depleted uranium rounds", //0
description: `your bullets are +11% larger
increased mass and physical damage`,
count: 0,
maxCount: 4,
effect() {
b.modBulletSize += 0.11
}
},
{
name: "fluoroantimonic acid", //1
description: "each bullet does extra chemical damage
instant damage, unaffected by momentum",
maxCount: 4,
count: 0,
effect() {
b.modExtraDmg += 0.25
game.playerDmgColor = "rgba(0,80,80,0.9)"
}
},
{
name: "fracture analysis", //2
description: "5x physical damage to unaware enemies
unaware enemies don't have a health bar",
maxCount: 1,
count: 0,
effect() {
b.isModCrit = true;
}
},
{
name: "kinetic bombardment", //3
description: "do up to 33% more damage at a distance
increase starts at about 6 steps away",
maxCount: 1,
count: 0,
effect() {
b.isModFarAwayDmg = true; //used in mob.damage()
// game.drawList.push({ //draw range
// //add dmg to draw queue
// x: player.position.x,
// y: player.position.y,
// radius: 3000,
// color: "rgba(255,0,0,0.05)",
// time: 120
// });
// game.drawList.push({ //draw range
// //add dmg to draw queue
// x: player.position.x,
// y: player.position.y,
// radius: 500,
// color: "rgba(0,0,0,0.2)",
// time: 120
// });
}
},
{
name: "quasistatic equilibrium", //4
description: "do extra damage at low health
up to 50% increase when near death",
maxCount: 1,
count: 0,
effect() {
b.isModLowHealthDmg = true; //used in mob.damage()
}
},
{
name: "high explosives", //15
description: "the radius of explosions are +20% larger
immune to harm from explosions",
maxCount: 4,
count: 0,
effect: () => {
b.modExplosionRadius += 0.2;
b.isModImmuneExplosion = true;
}
},
{
name: "auto-loading heuristics", //5
description: "your delay after firing is +12% shorter",
maxCount: 4,
count: 0,
effect() {
b.modFireRate *= 0.88
}
},
{
name: "desublimated ammunition", //6
description: "use 50% less ammo when crouching",
maxCount: 1,
count: 0,
effect() {
b.modNoAmmo = 1
}
},
{
name: "Lorentzian topology", //7
description: "your bullets last +33% longer",
maxCount: 4,
count: 0,
effect() {
b.isModBulletsLastLonger += 0.33
}
},
{
name: "zoospore vector", //8
description: "enemies discharge spores on death
+11% chance",
maxCount: 4,
count: 0,
effect() {
b.modSpores += 0.11;
for (let i = 0; i < 10; i++) {
b.spore(player) //spawn drone
}
}
},
{
name: "laser-bot", //10
description: "a bot protects the space around you
uses a short range laser that drains energy",
maxCount: 4,
count: 0,
effect() {
b.modLaserBotCount++;
b.laserBot();
}
},
{
name: "nail-bot", //11
description: "a bot protects the space around you
fires a nail at targets in range",
maxCount: 4,
count: 0,
effect() {
b.modNailBotCount++;
b.nailBot();
}
},
{
name: "ablative synthesis", //9
description: "rebuild your broken parts as drones
chance to occur after being harmed",
maxCount: 1,
count: 0,
effect() {
b.isModDroneOnDamage = true;
for (let i = 0; i < 3; i++) {
b.drone() //spawn drone
}
}
},
{
name: "bremsstrahlung radiation", //13
description: "when your field blocks it also does damage",
maxCount: 4,
count: 0,
effect() {
b.modBlockDmg += 0.7
}
},
{
name: "entanglement", //16
description: "using your first gun reduces harm
scales by 10% for each gun in your inventory",
maxCount: 1,
count: 0,
effect() {
b.isModEntanglement = true
}
},
{
name: "squirrel-cage rotor", //27
description: "jump higher and move faster
reduced harm from falling ",
maxCount: 1,
count: 0,
effect() { // good with melee builds, content skipping builds
b.modSquirrelFx = 1.2;
mech.Fx = 0.015 * b.modSquirrelFx;
mech.jumpForce = 0.38 * 1.1;
}
},
{
name: "Pauli exclusion", //12
description: "unable to collide with enemies for +2 seconds
activates after being harmed from a collision",
maxCount: 1,
count: 0,
effect() {
b.modCollisionImmuneCycles += 120;
mech.collisionImmune = mech.cycle + b.modCollisionImmuneCycles; //player is immune to collision damage for 30 cycles
}
},
{
name: "annihilation", //14
description: "after touching enemies, they are annihilated",
maxCount: 1,
count: 0,
effect() {
b.isModAnnihilation = true
}
},
{
name: "piezoelectricity", //17
description: "colliding with enemies fills your energy",
maxCount: 1,
count: 0,
effect() {
b.isModPiezo = true;
mech.fieldMeter = mech.fieldEnergyMax;
}
},
{
name: "energy conservation", //18
description: "gain energy proportional to damage done",
maxCount: 4,
count: 0,
effect() {
b.modEnergySiphon += 0.15;
mech.fieldMeter = mech.fieldEnergyMax
}
},
{
name: "entropy exchange", //19
description: "heal proportional to damage done",
maxCount: 4,
count: 0,
effect() {
b.modHealthDrain += 0.015;
}
},
{
name: "overcharge", //20
description: "charge energy +33% beyond your maximum",
maxCount: 4,
count: 0,
effect() {
mech.fieldEnergyMax += 0.33
mech.fieldMeter += 0.33
}
},
{
name: "supersaturation", //21
description: "heal +33% beyond your max health",
maxCount: 4,
count: 0,
effect() {
mech.maxHealth += 0.33
mech.addHealth(0.33)
}
},
{
name: "recursive healing", //22
description: "healing power ups trigger an extra time.",
maxCount: 4,
count: 0,
effect() {
b.modRecursiveHealing += 1
}
},
{
name: "mass-energy equivalence", //23
description: "convert the mass of power ups into energy
power ups fill your energy and heal for +5%",
maxCount: 1,
count: 0,
effect: () => {
b.isModMassEnergy = true // used in mech.usePowerUp
mech.fieldMeter = mech.fieldEnergyMax
}
},
{
name: "quantum immortality", //28
description: "after dying, continue in an alternate reality
guns, ammo, field, and mods are randomized",
maxCount: 1,
count: 0,
effect() {
b.isModImmortal = true;
}
},
{
name: "+1 cardinality", //24
description: "one extra choice when selecting power ups",
maxCount: 1,
count: 0,
effect: () => {
b.isModFourOptions = true;
}
},
{
name: "Bayesian inference", //25
description: "20% chance for double power ups to drop
one fewer choice when selecting power ups",
maxCount: 1,
count: 0,
effect: () => {
b.isModBayesian = 0.20;
}
},
{
name: "Born rule", //26
description: "remove all current mods
spawn new mods to replace them",
maxCount: 1,
count: 0,
effect: () => {
for (let i = 0; i < b.modCount; i++) { // spawn new mods
powerUps.spawn(mech.pos.x, mech.pos.y, "mod");
}
b.setModDefaults(); // remove all mods
//have state is checked in mech.death()
}
},
],
giveMod(index = 'random') {
if (index === 'random') {
let options = [];
for (let i = 0; i < b.mods.length; i++) {
if (b.mods[i].count < b.mods[i].maxCount) options.push(i);
}
// give a random mod from the mods I don't have
if (options.length > 0) {
let newMod = options[Math.floor(Math.random() * options.length)]
b.giveMod(newMod)
}
} else {
b.mods[index].effect(); //give specific mod
b.mods[index].count++
b.modCount++ //used in power up randomization
game.updateModHUD();
}
},
activeGun: null, //current gun in use by player
inventoryGun: 0,
inventory: [], //list of what guns player has // 0 starts with basic gun
fire() {
if (game.mouseDown && mech.fireCDcycle < mech.cycle && (!(keys[32] || game.mouseDownRight) || mech.fieldFire) && b.inventory.length) {
if (b.guns[b.activeGun].ammo > 0) {
b.guns[b.activeGun].fire();
if (b.modNoAmmo && mech.crouch) {
if (b.modNoAmmo % 2) {
b.guns[b.activeGun].ammo--;
game.updateGunHUD();
}
b.modNoAmmo++ //makes the no ammo toggle off and on
} else {
b.guns[b.activeGun].ammo--;
game.updateGunHUD();
}
} else {
mech.fireCDcycle = mech.cycle + 30; //cooldown
// game.makeTextLog("
Q, E, and mouse wheel change weapons
", 200); } if (mech.holdingTarget) { mech.drop(); } } }, bulletActions() { //run in main loop //remove bullet if at end cycle for that bullet let i = bullet.length; while (i--) { if (bullet[i].endCycle < game.cycle) { bullet[i].onEnd(i); //some bullets do stuff on end if (bullet[i]) { Matter.World.remove(engine.world, bullet[i]); bullet.splice(i, 1); } else { break; //if bullet[i] doesn't exist don't complete the for loop, because the game probably reset } } } //draw ctx.beginPath(); for (let i = 0, len = bullet.length; i < len; i++) { let vertices = bullet[i].vertices; ctx.moveTo(vertices[0].x, vertices[0].y); for (let j = 1; j < vertices.length; j += 1) { ctx.lineTo(vertices[j].x, vertices[j].y); } ctx.lineTo(vertices[0].x, vertices[0].y); } ctx.fillStyle = "#000"; ctx.fill(); //do bullet things for (let i = 0, len = bullet.length; i < len; i++) { bullet[i].do(); } }, fireProps(cd, speed, dir, me) { mech.fireCDcycle = mech.cycle + Math.floor(cd * b.modFireRate); // cool down Matter.Body.setVelocity(bullet[me], { x: mech.Vx / 2 + speed * Math.cos(dir), y: mech.Vy / 2 + speed * Math.sin(dir) }); World.add(engine.world, bullet[me]); //add bullet to world }, fireAttributes(dir, rotate = true) { if (rotate) { return { // density: 0.0015, //frictionAir: 0.01, //restitution: 0, angle: dir, friction: 0.5, frictionAir: 0, dmg: 0, //damage done in addition to the damage from momentum classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield }, minDmgSpeed: 10, onDmg() {}, //this.endCycle = 0 //triggers despawn onEnd() {} }; } else { return { // density: 0.0015, //frictionAir: 0.01, //restitution: 0, inertia: Infinity, //prevents rotation angle: dir, friction: 0.5, frictionAir: 0, dmg: 0, //damage done in addition to the damage from momentum classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield }, minDmgSpeed: 10, onDmg() {}, //this.endCycle = 0 //triggers despawn onEnd() {} }; } }, muzzleFlash(radius = 10) { ctx.fillStyle = "#fb0"; ctx.beginPath(); ctx.arc(mech.pos.x + 35 * Math.cos(mech.angle), mech.pos.y + 35 * Math.sin(mech.angle), radius, 0, 2 * Math.PI); ctx.fill(); }, removeConsBB(me) { for (let i = 0, len = consBB.length; i < len; ++i) { if (consBB[i].bodyA === me) { consBB[i].bodyA = consBB[i].bodyB; consBB.splice(i, 1); // b.removeConsBB(me); break; } else if (consBB[i].bodyB === me) { consBB[i].bodyB = consBB[i].bodyA; consBB.splice(i, 1); // b.removeConsBB(me); break; } } }, onCollision(event) { const pairs = event.pairs; for (let i = 0, j = pairs.length; i != j; i++) { //map + bullet collisions if (pairs[i].bodyA.collisionFilter.category === cat.map && pairs[i].bodyB.collisionFilter.category === cat.bullet) { collideBulletStatic(pairs[i].bodyB) } else if (pairs[i].bodyB.collisionFilter.category === cat.map && pairs[i].bodyA.collisionFilter.category === cat.bullet) { collideBulletStatic(pairs[i].bodyA) } function collideBulletStatic(obj) { if (obj.onWallHit) obj.onWallHit(); } } }, explosion(where, radius) { radius *= b.modExplosionRadius // typically explode is used for some bullets with .onEnd //add dmg to draw queue game.drawList.push({ x: where.x, y: where.y, radius: radius, color: "rgba(255,25,0,0.6)", time: game.drawTime }); let dist, sub, knock; let dmg = b.dmgScale * radius * 0.009; const alertRange = 100 + radius * 2; //alert range //add alert to draw queue game.drawList.push({ x: where.x, y: where.y, radius: alertRange, color: "rgba(100,20,0,0.03)", time: game.drawTime }); //player damage and knock back sub = Vector.sub(where, player.position); dist = Vector.magnitude(sub); if (dist < radius) { if (!b.isModImmuneExplosion) mech.damage(radius * 0.0002); knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * player.mass / 30); player.force.x += knock.x; player.force.y += knock.y; mech.drop(); } else if (dist < alertRange) { knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * player.mass / 55); player.force.x += knock.x; player.force.y += knock.y; mech.drop(); } //body knock backs for (let i = 0, len = body.length; i < len; ++i) { sub = Vector.sub(where, body[i].position); dist = Vector.magnitude(sub); if (dist < radius) { knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) / 18); body[i].force.x += knock.x; body[i].force.y += knock.y; } else if (dist < alertRange) { knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) / 40); body[i].force.x += knock.x; body[i].force.y += knock.y; } } //power up knock backs for (let i = 0, len = powerUp.length; i < len; ++i) { sub = Vector.sub(where, powerUp[i].position); dist = Vector.magnitude(sub); if (dist < radius) { knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) / 30); powerUp[i].force.x += knock.x; powerUp[i].force.y += knock.y; } else if (dist < alertRange) { knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) / 45); powerUp[i].force.x += knock.x; powerUp[i].force.y += knock.y; } } //mob damage and knock back with alert let damageScale = 1.5; // reduce dmg for each new target to limit total AOE damage for (let i = 0, len = mob.length; i < len; ++i) { if (mob[i].alive && !mob[i].isShielded) { sub = Vector.sub(where, mob[i].position); dist = Vector.magnitude(sub) - mob[i].radius; if (dist < radius) { if (mob[i].shield) dmg *= 3 //balancing explosion dmg to shields mob[i].damage(dmg * damageScale); mob[i].locatePlayer(); knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) / 50); mob[i].force.x += knock.x; mob[i].force.y += knock.y; radius *= 0.93 //reduced range for each additional explosion target damageScale *= 0.8 //reduced damage for each additional explosion target } else if (!mob[i].seePlayer.recall && dist < alertRange) { mob[i].locatePlayer(); knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) / 80); mob[i].force.x += knock.x; mob[i].force.y += knock.y; } } } }, explode(me) { // typically explode is used for some bullets with .onEnd let radius = bullet[me].explodeRad * b.modExplosionRadius //add dmg to draw queue game.drawList.push({ x: bullet[me].position.x, y: bullet[me].position.y, radius: radius, color: "rgba(255,25,0,0.6)", time: game.drawTime }); let dist, sub, knock; let dmg = b.dmgScale * radius * 0.009; const alertRange = 100 + radius * 2; //alert range //add alert to draw queue game.drawList.push({ x: bullet[me].position.x, y: bullet[me].position.y, radius: alertRange, color: "rgba(100,20,0,0.03)", time: game.drawTime }); //player damage and knock back sub = Vector.sub(bullet[me].position, player.position); dist = Vector.magnitude(sub); if (dist < radius) { if (!b.isModImmuneExplosion) mech.damage(radius * 0.0002); knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * player.mass / 30); player.force.x += knock.x; player.force.y += knock.y; mech.drop(); } else if (dist < alertRange) { knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * player.mass / 55); player.force.x += knock.x; player.force.y += knock.y; mech.drop(); } //body knock backs for (let i = 0, len = body.length; i < len; ++i) { sub = Vector.sub(bullet[me].position, body[i].position); dist = Vector.magnitude(sub); if (dist < radius) { knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) / 18); body[i].force.x += knock.x; body[i].force.y += knock.y; } else if (dist < alertRange) { knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * body[i].mass) / 40); body[i].force.x += knock.x; body[i].force.y += knock.y; } } //power up knock backs for (let i = 0, len = powerUp.length; i < len; ++i) { sub = Vector.sub(bullet[me].position, powerUp[i].position); dist = Vector.magnitude(sub); if (dist < radius) { knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) / 30); powerUp[i].force.x += knock.x; powerUp[i].force.y += knock.y; } else if (dist < alertRange) { knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg) * powerUp[i].mass) / 45); powerUp[i].force.x += knock.x; powerUp[i].force.y += knock.y; } } //mob damage and knock back with alert let damageScale = 1.5; // reduce dmg for each new target to limit total AOE damage for (let i = 0, len = mob.length; i < len; ++i) { if (mob[i].alive && !mob[i].isShielded) { sub = Vector.sub(bullet[me].position, mob[i].position); dist = Vector.magnitude(sub) - mob[i].radius; if (dist < radius) { if (mob[i].shield) dmg *= 3 //balancing explosion dmg to shields mob[i].damage(dmg * damageScale); mob[i].locatePlayer(); knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) / 50); mob[i].force.x += knock.x; mob[i].force.y += knock.y; radius *= 0.93 //reduced range for each additional explosion target damageScale *= 0.8 //reduced damage for each additional explosion target } else if (!mob[i].seePlayer.recall && dist < alertRange) { mob[i].locatePlayer(); knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) / 80); mob[i].force.x += knock.x; mob[i].force.y += knock.y; } } } }, mine(where, velocity, angle = 0) { const bIndex = bullet.length; bullet[bIndex] = Bodies.rectangle(where.x, where.y, 45 * b.modBulletSize, 16 * b.modBulletSize, { angle: angle, friction: 1, frictionAir: 0, restitution: 0, dmg: 0, //damage done in addition to the damage from momentum classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield | cat.bullet }, minDmgSpeed: 5, stillCount: 0, isArmed: false, endCycle: game.cycle + 2000 + 360 * Math.random(), lookFrequency: 41 + Math.floor(23 * Math.random()), range: 700, onDmg() {}, do() { this.force.y += this.mass * 0.002; //extra gravity let collide = Matter.Query.collides(this, map) //check if collides with map if (collide.length > 0) { for (let i = 0; i < collide.length; i++) { if (collide[i].bodyA.collisionFilter.category === cat.map || collide[i].bodyB.collisionFilter.category === cat.map) { // console.log(collide) const angle = Matter.Vector.angle(collide[i].normal, { x: 1, y: 0 }) if (angle > -0.2 || angle < -1.5) { //don't stick to level ground Matter.Body.setAngle(this, Math.atan2(collide[i].tangent.y, collide[i].tangent.x)) //move until touching map again after rotation for (let j = 0; j < 10; j++) { if (Matter.Query.collides(this, map).length > 0) { Matter.Body.setStatic(this, true) //don't set to static if not touching map this.arm(); //sometimes the mine can't attach to map and it just needs to explode const that = this setTimeout(function () { if (Matter.Query.collides(that, map).length === 0) { that.endCycle = 0 // if not touching map explode that.isArmed = false b.mine(that.position, that.velocity, that.angle) } }, 100, that); break } //move until you are touching the wall Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(collide[i].normal, 2))) } } else if (this.speed < 1 && this.angularSpeed < 0.01 && !mech.isBodiesAsleep) { this.stillCount += 2 } } } } else { if (this.speed < 1 && this.angularSpeed < 0.01 && !mech.isBodiesAsleep) { this.stillCount++ } } if (this.stillCount > 35) this.arm(); }, arm() { this.isArmed = true game.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, radius: 10, color: "#f00", time: 4 }); this.do = function () { //overwrite the do method for this bullet this.force.y += this.mass * 0.002; //extra gravity if (!(game.cycle % this.lookFrequency)) { //find mob targets for (let i = 0, len = mob.length; i < len; ++i) { if (Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)) < 500000 && mob[i].dropPowerUp && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0) { this.endCycle = 0 //end life if mob is near and visible } } } } }, onEnd() { if (this.isArmed) { const targets = [] //target nearby mobs for (let i = 0, len = mob.length; i < len; i++) { if (mob[i].dropPowerUp) { const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); if (dist < 1440000 && //1200*1200 Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0) { targets.push(Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist) / 60))) //predict where the mob will be in a few cycles } } } for (let i = 0; i < 16; i++) { const speed = 53 + 10 * Math.random() if (targets.length > 0) { // aim near a random target in array const index = Math.floor(Math.random() * targets.length) const SPREAD = 150 / targets.length const WHERE = { x: targets[index].x + SPREAD * (Math.random() - 0.5), y: targets[index].y + SPREAD * (Math.random() - 0.5) } b.nail(this.position, Vector.mult(Vector.normalise(Vector.sub(WHERE, this.position)), speed), 1) } else { // aim in random direction const ANGLE = 2 * Math.PI * Math.random() b.nail(this.position, { x: speed * Math.cos(ANGLE), y: speed * Math.sin(ANGLE) }) } } } } }); bullet[bIndex].torque += bullet[bIndex].inertia * 0.0001 * (0.5 - Math.random()) Matter.Body.setVelocity(bullet[bIndex], velocity); World.add(engine.world, bullet[bIndex]); //add bullet to world }, spore(who) { //used with the mod upgrade in mob.death() const bIndex = bullet.length; const side = 4 * b.modBulletSize; bullet[bIndex] = Bodies.polygon(who.position.x, who.position.y, 5, side, { // density: 0.0015, //frictionAir: 0.01, inertia: Infinity, restitution: 0.5, angle: Math.random() * 2 * Math.PI, friction: 0, frictionAir: 0.025, dmg: 2.5, //damage done in addition to the damage from momentum classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.mob | cat.mobBullet | cat.mobShield //no collide with body }, endCycle: game.cycle + Math.floor((660 + Math.floor(Math.random() * 240)) * b.isModBulletsLastLonger), minDmgSpeed: 0, onDmg() { this.endCycle = 0; //bullet ends cycle after doing damage }, onEnd() {}, lookFrequency: 97 + Math.floor(77 * Math.random()), do() { //find mob targets if (!(game.cycle % this.lookFrequency)) { this.closestTarget = null; this.lockedOn = null; let closeDist = Infinity; for (let i = 0, len = mob.length; i < len; ++i) { if (mob[i].dropPowerUp && Matter.Query.ray(map, this.position, mob[i].position).length === 0) { // Matter.Query.ray(body, this.position, mob[i].position).length === 0 const targetVector = Vector.sub(this.position, mob[i].position) const dist = Vector.magnitude(targetVector); if (dist < closeDist) { this.closestTarget = mob[i].position; closeDist = dist; this.lockedOn = mob[i] //Vector.normalise(targetVector); if (0.3 > Math.random()) break //doesn't always target the closest mob } } } } //accelerate towards mobs const THRUST = 0.0004 if (this.lockedOn && this.lockedOn.alive) { this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, this.lockedOn.position)), -this.mass * THRUST) // this.force.x -= THRUST * this.lockedOn.x // this.force.y -= THRUST * this.lockedOn.y } else { this.force.y += this.mass * 0.0001; //gravity } }, }); const SPEED = 8 + 3 * Math.random(); const ANGLE = 2 * Math.PI * Math.random() Matter.Body.setVelocity(bullet[bIndex], { x: SPEED * Math.cos(ANGLE), y: SPEED * Math.sin(ANGLE) }); World.add(engine.world, bullet[bIndex]); //add bullet to world }, drone(speed = 1) { const me = bullet.length; const THRUST = 0.0015 const dir = mech.angle + 0.2 * (Math.random() - 0.5); const RADIUS = (4.5 + 3 * Math.random()) * b.modBulletSize bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 8, RADIUS, { angle: dir, inertia: Infinity, friction: 0.05, frictionAir: 0.0005, restitution: 1, dmg: 0.13, //damage done in addition to the damage from momentum lookFrequency: 83 + Math.floor(41 * Math.random()), endCycle: game.cycle + Math.floor((1200 + 420 * Math.random()) * b.isModBulletsLastLonger), classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield //self collide }, minDmgSpeed: 0, lockedOn: null, isFollowMouse: true, onDmg() { this.lockedOn = null if (this.endCycle > game.cycle + 180) { this.endCycle -= 60 if (game.cycle + 180 > this.endCycle) this.endCycle = game.cycle + 180 } }, onEnd() {}, do() { if (game.cycle + 180 > this.endCycle) { //fall and die this.force.y += this.mass * 0.0012; this.restitution = 0.2; } else { this.force.y += this.mass * 0.0002; //find mob targets if (!(game.cycle % this.lookFrequency)) { this.lockedOn = null; let closeDist = Infinity; for (let i = 0, len = mob.length; i < len; ++i) { if ( mob[i].dropPowerUp && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0 ) { const TARGET_VECTOR = Vector.sub(this.position, mob[i].position) const DIST = Vector.magnitude(TARGET_VECTOR); if (DIST < closeDist) { closeDist = DIST; this.lockedOn = mob[i] } } } if (!this.lockedOn) { //grab a power up if it is (ammo) or (a heal when player is low) let closeDist = Infinity; for (let i = 0, len = powerUp.length; i < len; ++i) { if ( ((powerUp[i].name !== "field" && powerUp[i].name !== "heal") || (powerUp[i].name === "heal" && mech.health < 0.8)) && Matter.Query.ray(map, this.position, powerUp[i].position).length === 0 && Matter.Query.ray(body, this.position, powerUp[i].position).length === 0 ) { const TARGET_VECTOR = Vector.sub(this.position, powerUp[i].position) const DIST = Vector.magnitude(TARGET_VECTOR); if (DIST < closeDist) { if (DIST < 50) { //eat the power up if close enough powerUp[i].effect(); Matter.World.remove(engine.world, powerUp[i]); powerUp.splice(i, 1); break; } closeDist = DIST; this.lockedOn = powerUp[i] } } } } } if (this.lockedOn) { //accelerate towards mobs this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, this.lockedOn.position)), -this.mass * THRUST) } else { //accelerate towards mouse this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, game.mouseInGame)), -this.mass * THRUST) } // speed cap instead of friction to give more agility if (this.speed > 6) { Matter.Body.setVelocity(this, { x: this.velocity.x * 0.97, y: this.velocity.y * 0.97 }); } } } }) World.add(engine.world, bullet[me]); //add bullet to world Matter.Body.setVelocity(bullet[me], { x: speed * Math.cos(dir), y: speed * Math.sin(dir) }); }, nail(pos, velocity, dmg = 0) { const me = bullet.length; bullet[me] = Bodies.rectangle(pos.x, pos.y, 25 * b.modBulletSize, 2 * b.modBulletSize, b.fireAttributes(Math.atan2(velocity.y, velocity.x))); Matter.Body.setVelocity(bullet[me], velocity); World.add(engine.world, bullet[me]); //add bullet to world bullet[me].endCycle = game.cycle + 60 + 18 * Math.random(); bullet[me].dmg = dmg bullet[me].do = function () {}; }, nailBot(speed = 1) { const me = bullet.length; const dir = mech.angle; const RADIUS = (10 + 5 * Math.random()) * b.modBulletSize bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 4, RADIUS, { angle: dir, friction: 0, frictionStatic: 0, restitution: 0.4 + 0.5 * Math.random(), dmg: 0, // 0.14 //damage done in addition to the damage from momentum minDmgSpeed: 2, lookFrequency: 56 + Math.floor(17 * Math.random()), acceleration: 0.0025 + 0.001 * Math.random(), range: 300 + Math.floor(70 * Math.random()), endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield }, lockedOn: null, onDmg() { this.lockedOn = null }, onEnd() {}, do() { if (!(game.cycle % this.lookFrequency)) { let target for (let i = 0, len = mob.length; i < len; i++) { const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); if (dist < 2000000 && //1400*1400 Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0) { target = Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist) / 60)) const SPEED = 50 b.nail(this.position, Vector.mult(Vector.normalise(Vector.sub(target, this.position)), SPEED), 0.5) break; } } } const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) if (distanceToPlayer > this.range * 0.2) { //if far away move towards player this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) this.frictionAir = 0.04 } else { //close to player this.frictionAir = 0.005 //add player's velocity Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 1), Vector.mult(player.velocity, 0.02))); } } }) World.add(engine.world, bullet[me]); //add bullet to world Matter.Body.setVelocity(bullet[me], { x: speed * Math.cos(dir), y: speed * Math.sin(dir) }); }, laserBot(speed = 1) { const me = bullet.length; const dir = mech.angle; const RADIUS = (14 + 6 * Math.random()) * b.modBulletSize bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 3, RADIUS, { angle: dir, friction: 0, frictionStatic: 0, restitution: 0.5 + 0.5 * Math.random(), dmg: 0, // 0.14 //damage done in addition to the damage from momentum minDmgSpeed: 2, lookFrequency: 31 + Math.floor(17 * Math.random()), acceleration: 0.0015 + 0.0013 * Math.random(), range: 500 + Math.floor(200 * Math.random()), endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield }, lockedOn: null, onDmg() { this.lockedOn = null }, onEnd() {}, do() { if (!(game.cycle % this.lookFrequency)) { this.lockedOn = null; let closeDist = this.range; for (let i = 0, len = mob.length; i < len; ++i) { const DIST = Vector.magnitude(Vector.sub(this.vertices[0], mob[i].position)); if (DIST - mob[i].radius < closeDist && !mob[i].isShielded && Matter.Query.ray(map, this.vertices[0], mob[i].position).length === 0 && Matter.Query.ray(body, this.vertices[0], mob[i].position).length === 0) { closeDist = DIST; this.lockedOn = mob[i] } } } if (this.lockedOn && this.lockedOn.alive && mech.fieldMeter > 0.15) { //hit target with laser mech.fieldMeter -= 0.0012 //make sure you can still see vertex const DIST = Vector.magnitude(Vector.sub(this.vertices[0], this.lockedOn.position)); if (DIST - this.lockedOn.radius < this.range + 150 && Matter.Query.ray(map, this.vertices[0], this.lockedOn.position).length === 0 && Matter.Query.ray(body, this.vertices[0], this.lockedOn.position).length === 0) { //find the closest vertex let bestVertexDistance = Infinity let bestVertex = null for (let i = 0; i < this.lockedOn.vertices.length; i++) { const dist = Vector.magnitude(Vector.sub(this.vertices[0], this.lockedOn.vertices[i])); if (dist < bestVertexDistance) { bestVertex = i bestVertexDistance = dist } } const dmg = b.dmgScale * 0.05; this.lockedOn.damage(dmg); this.lockedOn.locatePlayer(); ctx.beginPath(); //draw laser ctx.moveTo(this.vertices[0].x, this.vertices[0].y); ctx.lineTo(this.lockedOn.vertices[bestVertex].x, this.lockedOn.vertices[bestVertex].y); ctx.strokeStyle = "#f00"; ctx.lineWidth = "2" ctx.lineDashOffset = 300 * Math.random() ctx.setLineDash([50 + 100 * Math.random(), 100 * Math.random()]); ctx.stroke(); ctx.setLineDash([0, 0]); ctx.beginPath(); ctx.arc(this.lockedOn.vertices[bestVertex].x, this.lockedOn.vertices[bestVertex].y, Math.sqrt(dmg) * 100, 0, 2 * Math.PI); ctx.fillStyle = "#f00"; ctx.fill(); } } const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) if (distanceToPlayer > this.range * 0.2) { //if far away move towards player this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) this.frictionAir = 0.02 } else { //close to player this.frictionAir = 0 //add player's velocity Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 1), Vector.mult(player.velocity, 0.02))); } } }) World.add(engine.world, bullet[me]); //add bullet to world Matter.Body.setVelocity(bullet[me], { x: speed * Math.cos(dir), y: speed * Math.sin(dir) }); }, giveGuns(gun = "random", ammoPacks = 6) { if (gun === "random") { //find what guns player doesn't have options = [] for (let i = 0, len = b.guns.length; i < len; i++) { if (!b.guns[i].have) options.push(i) } if (options.length === 0) return //randomly pick from list of possible guns gun = options[Math.floor(Math.random() * options.length)] } if (gun === "all") { b.activeGun = 0; b.inventoryGun = 0; for (let i = 0; i < b.guns.length; i++) { b.inventory[i] = i; b.guns[i].have = true; b.guns[i].ammo = b.guns[i].ammoPack * ammoPacks; } } else { if (!b.guns[gun].have) b.inventory.push(gun); if (b.activeGun === null) b.activeGun = gun //if no active gun switch to new gun b.guns[gun].have = true; b.guns[gun].ammo = b.guns[gun].ammoPack * ammoPacks; } game.makeGunHUD(); }, guns: [{ name: "minigun", //0 description: "rapidly fire a stream of small bullets", ammo: 0, ammoPack: 55, have: false, isStarterGun: true, fire() { const me = bullet.length; b.muzzleFlash(15); // if (Math.random() > 0.2) mobs.alert(500); const dir = mech.angle + (Math.random() - 0.5) * ((mech.crouch) ? 0.03 : 0.1); bullet[me] = Bodies.rectangle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 20 * b.modBulletSize, 6 * b.modBulletSize, b.fireAttributes(dir)); b.fireProps(mech.crouch ? 8 : 4, mech.crouch ? 52 : 38, dir, me); //cd , speed bullet[me].endCycle = game.cycle + 70; bullet[me].dmg = 0.07; bullet[me].frictionAir = mech.crouch ? 0.007 : 0.01; bullet[me].do = function () { this.force.y += this.mass * 0.0005; }; } }, { name: "shotgun", //1 description: "fire a burst of short range bullets