Files
n-gon-improved/js/bullets.js
2020-02-21 06:02:52 -08:00

2967 lines
110 KiB
JavaScript

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,
isModExplodeMob: null,
isModDroneOnDamage: null,
modAcidDmg: null,
isModAcidDmg: 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,
isModDroneCollide: null,
isModFastSpores: null,
isModStomp: null,
modSuperBallNumber: null,
modLaserReflections: null,
modLaserDamage: null,
modLaserFieldDrain: null,
isModNoAmmo: null,
isModAmmoFromHealth: null,
modMobDieAtHealth: null,
isModEnergyRecovery: null,
isModHealthRecovery: null,
isModEnergyLoss: null,
isModFoamShieldHit: null,
isModDeathAvoid: null,
isModDeathAvoidOnCD: null,
modOnHealthChange() { //used with acid mod
if (b.isModAcidDmg && mech.health > 0.8) {
game.playerDmgColor = "rgba(0,80,80,0.9)"
b.modAcidDmg = 0.9
} else {
game.playerDmgColor = "rgba(0,0,0,0.7)"
b.modAcidDmg = 0
}
},
mods: [{
name: "depleted uranium rounds",
description: `your <strong>bullets</strong> are +13% larger<br>increased mass and physical <strong class='color-d'>damage</strong>`,
count: 0,
maxCount: 9,
allowed() {
return b.haveGunCheck("minigun") || b.haveGunCheck("shotgun") || b.haveGunCheck("super balls") || b.haveGunCheck("foam")
},
requires: "minigun, shotgun, super balls, foam",
effect() {
b.modBulletSize += 0.13
},
remove() {
b.modBulletSize = 1;
}
},
{
name: "fluoroantimonic acid",
description: "each <strong>bullet</strong> does extra chemical <strong class='color-d'>damage</strong><br>only <strong>active</strong> when you are above <strong>80% health</strong>",
maxCount: 1,
count: 0,
allowed() {
return mech.health > 0.8 || build.isCustomSelection
},
requires: "health above 80%",
effect() {
b.isModAcidDmg = true;
b.modOnHealthChange();
},
remove() {
b.modAcidDmg = 0;
b.isModAcidDmg = false;
game.playerDmgColor = "rgba(0,0,0,0.7)"
}
},
{
name: "kinetic bombardment",
description: "do up to 33% more <strong class='color-d'>damage</strong> at a distance<br><em>increase maxes out at about 40 steps away</em>",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.isModFarAwayDmg = true; //used in mob.damage()
},
remove() {
b.isModFarAwayDmg = false;
}
},
{
name: "fracture analysis",
description: "<strong>5x</strong> physical <strong class='color-d'>damage</strong> to unaware enemies<br><em>unaware enemies don't have a health bar</em>",
maxCount: 1,
count: 0,
allowed() {
return b.isModFarAwayDmg
},
requires: "kinetic bombardment",
effect() {
b.isModCrit = true;
},
remove() {
b.isModCrit = false;
}
},
{
name: "quasistatic equilibrium",
description: "do extra <strong class='color-d'>damage</strong> at low health<br><em>up to <strong>50%</strong> increase when near death</em>",
maxCount: 1,
count: 0,
allowed() {
return mech.health < 0.8 || build.isCustomSelection
},
requires: "health below 80%",
effect() {
b.isModLowHealthDmg = true; //used in mob.damage()
},
remove() {
b.isModLowHealthDmg = false;
}
},
{
name: "reaction inhibitor",
description: "mobs <strong>die</strong> if their life goes below <strong>12%</strong>",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect: () => {
b.modMobDieAtHealth = 0.15
},
remove() {
b.modMobDieAtHealth = 0.05;
}
},
{
name: "thermal runaway",
description: "mobs <strong class='color-e'>explode</strong> when they <strong>die</strong>",
maxCount: 1,
count: 0,
allowed() {
return b.modMobDieAtHealth > 0.05
},
requires: "reaction inhibitor",
effect: () => {
b.isModExplodeMob = true;
},
remove() {
b.isModExplodeMob = false;
}
},
{
name: "high explosives",
description: "<strong class='color-e'>explosions</strong> do <strong>+20%</strong> more <strong class='color-d'>damage</strong><br><strong class='color-e'>explosive</strong> area is +44% <strong>larger</strong>",
maxCount: 3,
count: 0,
allowed() {
return b.haveGunCheck("missiles") || b.haveGunCheck("flak") || b.haveGunCheck("grenades") || b.haveGunCheck("vacuum bomb") || b.haveGunCheck("pulse");
},
requires: "an explosive gun",
effect: () => {
b.modExplosionRadius += 0.2;
},
remove() {
b.modExplosionRadius = 1;
}
},
{
name: "electric reactive armour",
description: "<strong class='color-e'>explosions</strong> do no <strong>harm</strong><br> <strong class='color-e'>explosions</strong> drain <strong class='color-f'>energy</strong>",
maxCount: 1,
count: 0,
allowed() {
return b.modExplosionRadius > 1
},
requires: "high explosives",
effect: () => {
b.isModImmuneExplosion = true;
},
remove() {
b.isModImmuneExplosion = false;
}
},
{
name: "auto-loading heuristics",
description: "your <strong>delay</strong> after firing is <strong>+14% shorter</strong>",
maxCount: 9,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.modFireRate *= 0.86
},
remove() {
b.modFireRate = 1;
}
},
{
name: "desublimated ammunition",
description: "use <strong>50%</strong> less <strong>ammo</strong> when <strong>crouching</strong>",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.modNoAmmo = 1
},
remove() {
b.modNoAmmo = 0;
}
},
{
name: "Lorentzian topology",
description: "your <strong>bullets</strong> last <strong>+33% longer</strong>",
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")
},
requires: "drones, spores, super balls, or foam",
effect() {
b.isModBulletsLastLonger += 0.33
},
remove() {
b.isModBulletsLastLonger = 1;
}
},
{
name: "zoospore vector",
description: "enemies discharge <strong style='letter-spacing: 2px;'>spores</strong> on <strong>death</strong><br>+11% chance",
maxCount: 9,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.modSpores += 0.11;
for (let i = 0; i < 10; i++) {
b.spore(player)
}
},
remove() {
b.modSpores = 0;
}
},
{
name: "laser-bot",
description: "a bot <strong>defends</strong> the space around you<br>uses a <strong>short range</strong> laser that drains <strong class='color-f'>energy</strong>",
maxCount: 9,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.modLaserBotCount++;
b.laserBot();
},
remove() {
b.modLaserBotCount = 0;
}
},
{
name: "nail-bot",
description: "a bot fires <strong>nails</strong> at targets in line of sight",
maxCount: 9,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.modNailBotCount++;
b.nailBot();
},
remove() {
b.modNailBotCount = 0;
}
},
{
name: "ablative synthesis",
description: "rebuild your broken parts as <strong>drones</strong><br>chance to occur after being <strong>harmed</strong>",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.isModDroneOnDamage = true;
for (let i = 0; i < 4; i++) {
b.drone() //spawn drone
}
},
remove() {
b.isModDroneOnDamage = false;
}
},
{
name: "bremsstrahlung radiation",
description: "when your <strong>field blocks</strong> it also does <strong class='color-d'>damage</strong>",
maxCount: 9,
count: 0,
allowed() {
return mech.fieldUpgrades[mech.fieldMode].name !== "time dilation field" && mech.fieldUpgrades[mech.fieldMode].name !== "phase decoherence field"
},
requires: "not time dilation field<br><strong>requires</strong> not phase decoherence field",
effect() {
b.modBlockDmg += 0.7 //if you change this value also update the for loop in the electricity graphics in mech.pushMass
},
remove() {
b.modBlockDmg = 0;
}
},
{
name: "field superposition",
description: "increase your <strong>field radius</strong> by <strong>40%</strong>",
maxCount: 1,
count: 0,
allowed() {
return mech.fieldUpgrades[mech.fieldMode].name !== "time dilation field" && mech.fieldUpgrades[mech.fieldMode].name !== "phase decoherence field"
},
requires: "not time dilation field<br><strong>requires</strong> not phase decoherence field",
effect() {
mech.fieldRange = 175 * 1.4
},
remove() {
mech.fieldRange = 175;
}
},
{
name: "waste energy recovery",
description: "regen <strong>7%</strong> of max <strong class='color-f'>energy</strong> every second<br>active for <strong>5 seconds</strong> after a mob <strong>dies</strong>",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.isModEnergyRecovery = true;
},
remove() {
b.isModEnergyRecovery = false;
}
},
{
name: "scrap recycling",
description: "<strong class='color-h'>heal</strong> up to <strong>1%</strong> of max health every second<br>active for <strong>5 seconds</strong> after a mob <strong>dies</strong>",
maxCount: 1,
count: 0,
allowed() {
return b.isModEnergyRecovery
},
requires: "waste energy recovery",
effect() {
b.isModHealthRecovery = true;
},
remove() {
b.isModHealthRecovery = false;
}
},
{
name: "acute stress response",
description: "increase <strong class='color-d'>damage</strong> by <strong>50%</strong><br>no <strong class='color-f'>energy</strong> for <strong>5 seconds</strong> after a mob <strong>dies</strong>",
maxCount: 1,
count: 0,
allowed() {
return b.isModEnergyRecovery
},
requires: "waste energy recovery",
effect() {
b.isModEnergyLoss = true;
},
remove() {
b.isModEnergyLoss = false;
}
},
{
name: "squirrel-cage rotor",
description: "<strong>jump</strong> higher and <strong>move</strong> faster<br>reduced <strong>harm</strong> from <strong>falling</strong> ",
maxCount: 9,
count: 0,
allowed() {
return true
},
requires: "",
effect() { // good with melee builds, content skipping builds
b.modSquirrelFx += 0.2;
mech.Fx = 0.016 * b.modSquirrelFx;
mech.jumpForce += 0.038;
},
remove() {
b.modSquirrelFx = 1;
mech.Fx = 0.016; //if this changes update the values in definePlayerMass
mech.jumpForce = 0.42; //was 0.38 at 0.0019 gravity
}
},
{
name: "basidio-stomp",
description: "hard landings disrupt <strong style='letter-spacing: 2px;'>spores</strong> from the ground<br>immune to <strong>harm</strong> from <strong>falling</strong>",
maxCount: 1,
count: 0,
allowed() {
return b.modSquirrelFx > 1
},
requires: "squirrel-cage rotor",
effect() {
b.isModStomp = true
},
remove() {
b.isModStomp = false;
}
},
{
name: "entanglement",
description: "only when your <strong>first gun</strong> is equipped<br>reduce <strong>harm</strong> by <strong>10%</strong> for each gun you have",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.isModEntanglement = true
},
remove() {
b.isModEntanglement = false;
}
},
{
name: "Pauli exclusion",
description: `unable to <strong>collide</strong> with enemies for <strong>+2</strong> seconds<br>activates after being <strong>harmed</strong> from a collision`,
maxCount: 9,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.modCollisionImmuneCycles += 120;
mech.collisionImmune = mech.cycle + b.modCollisionImmuneCycles; //player is immune to collision damage for 30 cycles
},
remove() {
b.modCollisionImmuneCycles = 30;
}
},
{
name: "annihilation",
description: "after <strong>touching</strong> enemies, they are <strong>annihilated</strong>",
maxCount: 1,
count: 0,
allowed() {
return b.modCollisionImmuneCycles > 120
},
requires: "Pauli exclusion",
effect() {
b.isModAnnihilation = true
},
remove() {
b.isModAnnihilation = false;
}
},
{
name: "quantum immortality",
description: "after <strong>dying</strong>, continue in an <strong>alternate reality</strong><br><em>guns, ammo, field, and mods are randomized</em>",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.isModImmortal = true;
},
remove() {
b.isModImmortal = false;
}
},
{
name: "weak anthropic principle",
description: "<strong>avoid harm</strong> that should be <strong>fatal</strong><br>can occur once every <strong>3</strong> seconds",
maxCount: 1,
count: 0,
allowed() {
return b.isModImmortal
},
requires: "quantum immortality",
effect() {
b.isModDeathAvoid = true;
b.isModDeathAvoidOnCD = false;
},
remove() {
b.isModDeathAvoid = false;
b.isModDeathAvoidOnCD = false;
}
},
{
name: "piezoelectricity",
description: "<strong>colliding</strong> with enemies charges your <strong class='color-f'>energy</strong>",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.isModPiezo = true;
mech.fieldMeter = mech.fieldEnergyMax;
},
remove() {
b.isModPiezo = false;
}
},
{
name: "energy conservation",
description: "gain <strong class='color-f'>energy</strong> proportional to <strong class='color-d'>damage</strong> done",
maxCount: 9,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.modEnergySiphon += 0.15;
mech.fieldMeter = mech.fieldEnergyMax
},
remove() {
b.modEnergySiphon = 0;
}
},
{
name: "entropy exchange",
description: "<strong class='color-h'>heal</strong> proportional to <strong class='color-d'>damage</strong> done",
maxCount: 9,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
b.modHealthDrain += 0.015;
},
remove() {
b.modHealthDrain = 0;
}
},
{
name: "overcharge",
description: "increase your <strong>maximum</strong> <strong class='color-f'>energy</strong> by <strong>+50%</strong>",
maxCount: 9,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
mech.fieldEnergyMax += 0.5
mech.fieldMeter += 0.5
},
remove() {
mech.fieldEnergyMax = 1;
}
},
{
name: "supersaturation",
description: "increase your <strong>maximum</strong> <strong class='color-h'>health</strong> by <strong>+50%</strong>",
maxCount: 9,
count: 0,
allowed() {
return true
},
requires: "",
effect() {
mech.maxHealth += 0.50
mech.addHealth(0.50)
},
remove() {
mech.maxHealth = 1;
}
},
{
name: "recursive healing",
description: "<strong class='color-h'>healing</strong> power ups trigger one extra time",
maxCount: 9,
count: 0,
allowed() {
return mech.health < 0.7 || build.isCustomSelection
},
requires: "health below 70%",
effect() {
b.modRecursiveHealing += 1
},
remove() {
b.modRecursiveHealing = 1;
}
},
{
name: "mass-energy equivalence",
description: "<strong>power ups</strong> overfill your <strong class='color-f'>energy</strong><br>temporarily gain <strong>50%</strong> above your max",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect: () => {
b.isModMassEnergy = true // used in mech.grabPowerUp
mech.fieldMeter = mech.fieldEnergyMax * 2
},
remove() {
b.isModMassEnergy = false;
}
},
{
name: "Bayesian inference",
description: "<strong>20%</strong> chance for double <strong>power ups</strong> to drop<br>one fewer <strong>choice</strong> when selecting <strong>power ups</strong>",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect: () => {
b.isModBayesian = 0.20;
},
remove() {
b.isModBayesian = 0;
}
},
{
name: "catabolism",
description: "convert <strong>3%</strong> of current health into <strong>ammo</strong><br>when you <strong>fire</strong> while <strong>out</strong> of <strong>ammo</strong>",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect: () => {
b.isModAmmoFromHealth = 0.03;
},
remove() {
b.isModAmmoFromHealth = 0;
}
},
{
name: "leveraged investment",
description: "<strong>remove</strong> all future <strong>ammo</strong> power ups<br>spawn 6 <strong class='color-m'>mods</strong> and 3 <strong class='color-h'>healing</strong> power ups",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect: () => {
b.isModNoAmmo = true;
for (let i = 0; i < 6; i++) { //if you change the six also change it in Born rule
powerUps.spawn(mech.pos.x, mech.pos.y, "mod");
if (Math.random() < b.isModBayesian) powerUps.spawn(mech.pos.x, mech.pos.y, "mod");
}
for (let i = 0; i < 3; i++) { // spawn new mods
powerUps.spawn(mech.pos.x, mech.pos.y, "heal");
if (Math.random() < b.isModBayesian) powerUps.spawn(mech.pos.x, mech.pos.y, "heal");
}
},
remove() {
b.isModNoAmmo = false;
}
},
{
name: "+1 cardinality",
description: "one extra <strong>choice</strong> when selecting <strong>power ups</strong>",
maxCount: 1,
count: 0,
allowed() {
return true
},
requires: "",
effect: () => {
b.isModFourOptions = true;
},
remove() {
b.isModFourOptions = false;
}
},
{
name: "Born rule",
description: "<strong>remove</strong> all current <strong class='color-m'>mods</strong><br>spawn new <strong class='color-m'>mods</strong> to replace them",
maxCount: 1,
count: 0,
allowed() {
return (b.modCount > 6) && !build.isCustomSelection
},
requires: "more than 6 mods",
effect: () => {
let count = b.modCount
if (b.isModNoAmmo) count - 6 //remove the 6 bonus mods when getting rid of leveraged investment
for (let i = 0; i < count; i++) { // spawn new mods
powerUps.spawn(mech.pos.x, mech.pos.y, "mod");
}
b.setupAllMods(); // remove all mods
//have state is checked in mech.death()
},
remove() {
//nothing to undo
}
},
{
name: "redundant systems",
description: "<strong>drone</strong> collisions no longer reduce their <strong>lifespan</strong>",
maxCount: 1,
count: 0,
allowed() {
return b.haveGunCheck("drones") || mech.fieldUpgrades[mech.fieldMode].name === "nano-scale manufacturing"
},
requires: "drones",
effect() {
b.isModDroneCollide = true
},
remove() {
b.isModDroneCollide = true;
}
},
{
name: "tinsellated flagella",
description: "your <strong style='letter-spacing: 2px;'>spores</strong> accelerate <strong>33% faster</strong>",
maxCount: 1,
count: 0,
allowed() {
return b.haveGunCheck("spores") || b.modSpores > 0 || b.isModStomp
},
requires: "spores",
effect() {
b.isModFastSpores = true
},
remove() {
b.isModFastSpores = false
}
},
{
name: "super duper",
description: "you fire <strong>+1</strong> additional <strong>super ball</strong>",
maxCount: 9,
count: 0,
allowed() {
return b.haveGunCheck("super balls")
},
requires: "super balls",
effect() {
b.modSuperBallNumber++
},
remove() {
b.modSuperBallNumber = 4;
}
},
{
name: "specular reflection",
description: "your <strong>laser</strong> gains <strong>+1</strong> reflection<br><strong>+30%</strong> laser <strong class='color-d'>damage</strong> and <strong class='color-f'>energy</strong> drain",
maxCount: 9,
count: 0,
allowed() {
return b.haveGunCheck("laser")
},
requires: "laser",
effect() {
b.modLaserReflections++;
b.modLaserDamage += 0.015; //base is 0.05
b.modLaserFieldDrain += 0.0006 //base is 0.002
},
remove() {
b.modLaserReflections = 2;
b.modLaserDamage = 0.05;
b.modLaserFieldDrain = 0.002;
}
},
{
name: "optimized shell packing",
description: "<strong>flak</strong> ammo drops contain <strong>2x</strong> more shells",
maxCount: 3,
count: 0,
allowed() {
return b.haveGunCheck("flak")
},
requires: "flak",
effect() {
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun is flak
if (b.guns[i].name === "flak") b.guns[i].ammoPack = b.guns[i].defaultAmmoPack * (2 + this.count);
}
},
remove() {
for (i = 0, len = b.guns.length; i < len; i++) { //find which gun is flak
if (b.guns[i].name === "flak") b.guns[i].ammoPack = b.guns[i].defaultAmmoPack;
}
}
},
{
name: "foam stabilization",
description: "<strong>foam</strong> can stick to shields",
maxCount: 1,
count: 0,
allowed() {
return b.haveGunCheck("foam")
},
requires: "foam",
effect() {
b.isModFoamShieldHit = true;
},
remove() {
b.isModFoamShieldHit = false;
}
},
// {
// name: "super mines",
// description: "mines fire super balls when triggered",
// maxCount: 1,
// count: 0,
// allowed() {
// return b.haveGunCheck("mines")
// },
// requires: "",
// effect() {
// }
// },
],
removeMod(index) {
b.mods[index].remove();
b.mods[index].count = 0;
game.updateModHUD();
},
setupAllMods() {
for (let i = 0, len = b.mods.length; i < len; i++) {
b.mods[i].remove();
b.mods[i].count = 0
}
b.modCount = 0;
game.updateModHUD();
},
// setupAllMods() {
// for (let i = 0, len = b.mods.length; i < len; i++) {
// if (b.mods[i].count) b.mods[i].remove();
// b.mods[i].count = 0
// }
// b.modCount = 0;
// game.updateModHUD();
// },
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 && b.mods[i].allowed())
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 {
if (isNaN(index)) { //find index by name
for (let i = 0; i < b.mods.length; i++) {
if (index === b.mods[i].name) index = i
}
}
b.mods[index].effect(); //give specific mod
b.mods[index].count++
b.modCount++ //used in power up randomization
game.updateModHUD();
}
},
haveGunCheck(name) {
for (i = 0, len = b.inventory.length; i < len; i++) {
if (b.guns[b.inventory[i]].name === name) return true
}
return false
},
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 {
if (b.isModAmmoFromHealth && mech.health > b.isModAmmoFromHealth) {
mech.damage(b.isModAmmoFromHealth * mech.health);
powerUps.spawn(mech.pos.x, mech.pos.y, "ammo");
if (Math.random() < b.isModBayesian) powerUps.spawn(mech.pos.x, mech.pos.y, "ammo");
}
mech.fireCDcycle = mech.cycle + 30; //fire cooldown
// game.makeTextLog("<div style='font-size:140%;'>NO AMMO</div><strong class = 'box'>E</strong> / <strong class = 'box'>Q</strong>", 200);
game.replaceTextLog = true;
game.makeTextLog("<div style='font-size:140%;'>NO AMMO</div> <p style='font-size:90%;'><strong>Q</strong>, <strong>E</strong>, and <strong>mouse wheel</strong> change weapons</p>", 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) {
const drain = Math.max(radius * 0.0006, 0.2)
if (mech.fieldMeter > drain) {
mech.fieldMeter -= drain
} else {
mech.damage(radius * 0.0001); //do half damage if have the mod, but out of mana
}
} else {
mech.damage(radius * 0.0002); //normal player damage from explosions
}
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;
}
}
}
},
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,
frictionStatic: 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) {
const angle = Matter.Vector.angle(collide[i].normal, {
x: 1,
y: 0
})
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) {
if (angle > -0.2 || angle < -1.5) { //don't stick to level ground
Matter.Body.setStatic(this, true) //don't set to static if not touching map
} else {
Matter.Body.setVelocity(this, {
x: 0,
y: 0
});
Matter.Body.setAngularVelocity(this, 0)
}
this.arm();
//sometimes the mine can't attach to map and it just needs to be reset
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++
}
}
if (this.stillCount > 25) 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.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.0002 * (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,
thrust: b.isModFastSpores ? 0.0008 : 0.0004,
dmg: 2.2, //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
if (this.lockedOn && this.lockedOn.alive) {
this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, this.lockedOn.position)), -this.mass * this.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 = 4 + 8 * 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 && b.isModDroneCollide) {
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,
frictionAir: 0.05,
restitution: 0.6 * (1 + 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.005 * (1 + 0.5 * Math.random()),
range: 70 * (1 + 0.3 * 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) && !mech.isStealth) {
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 < 3000000 && //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.4)
break;
}
}
}
const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos))
if (distanceToPlayer > this.range) { //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.1
} else { //close to player
// this.frictionAir = 0
//add player's velocity
Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17)));
}
}
})
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,
frictionAir: 0.008 * (1 + 0.3 * Math.random()),
restitution: 0.5 * (1 + 0.5 * Math.random()),
dmg: 0, // 0.14 //damage done in addition to the damage from momentum
minDmgSpeed: 2,
lookFrequency: 27 + Math.floor(17 * Math.random()),
acceleration: 0.0015 * (1 + 0.3 * Math.random()),
range: 600 * (1 + 0.2 * Math.random()),
followRange: 150 + Math.floor(30 * Math.random()),
offPlayer: {
x: 0,
y: 0,
},
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() {
//move in a circle
// const radius = 1.5
// this.offPlayer.x -= radius * Math.cos(game.cycle * 0.02)
// this.offPlayer.y -= radius * Math.sin(game.cycle * 0.02)
const playerPos = Vector.add(Vector.add(this.offPlayer, mech.pos), Vector.mult(player.velocity, 20)) //also include an offset unique to this bot to keep many bots spread out
const farAway = Math.max(0, (Vector.magnitude(Vector.sub(this.position, playerPos))) / this.followRange) //linear bounding well
const mag = Math.min(farAway, 4) * this.mass * this.acceleration
this.force = Vector.mult(Vector.normalise(Vector.sub(playerPos, this.position)), mag)
//manual friction to not lose rotational velocity
Matter.Body.setVelocity(this, {
x: this.velocity.x * 0.95,
y: this.velocity.y * 0.95
});
//find targets
if (!(game.cycle % this.lookFrequency) && !mech.isStealth) {
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]
}
}
//randomize position relative to player
if (Math.random() < 0.15) {
this.offPlayer = {
x: 100 * (Math.random() - 0.5),
y: 90 * (Math.random() - 0.5),
}
}
}
//hit target with laser
if (this.lockedOn && this.lockedOn.alive && mech.fieldMeter > 0.15) {
mech.fieldMeter -= 0.0014
//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) {
//move towards the target
this.force = Vector.add(this.force, Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), 0.0013))
//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();
}
}
}
})
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 = Math.floor(b.guns[i].ammoPack * ammoPacks);
}
} else {
if (isNaN(gun)) { //find gun by name
for (let i = 0; i < b.guns.length; i++) {
if (gun === b.guns[i].name) gun = i
}
}
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 = Math.floor(b.guns[gun].ammoPack * ammoPacks);
}
game.makeGunHUD();
},
guns: [{
name: "minigun", //0
description: "rapidly fire a stream of small <strong>bullets</strong>",
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 <strong>burst</strong> of short range bullets<br><em>crouch to reduce recoil</em>",
ammo: 0,
ammoPack: 8,
have: false,
isStarterGun: true,
fire() {
mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 55 : 30) * b.modFireRate); // cool down
b.muzzleFlash(35);
// mobs.alert(650);
const side = 13 * b.modBulletSize
for (let i = 0; i < 12; i++) {
const me = bullet.length;
const dir = mech.angle + (Math.random() - 0.5) * (mech.crouch ? 0.40 : 1.2)
bullet[me] = Bodies.rectangle(mech.pos.x + 35 * Math.cos(mech.angle) + 15 * (Math.random() - 0.5), mech.pos.y + 35 * Math.sin(mech.angle) + 15 * (Math.random() - 0.5), side, side, b.fireAttributes(dir));
World.add(engine.world, bullet[me]); //add bullet to world
const SPEED = 50 + Math.random() * 10
Matter.Body.setVelocity(bullet[me], {
x: SPEED * Math.cos(dir),
y: SPEED * Math.sin(dir)
});
bullet[me].endCycle = game.cycle + 55
bullet[me].frictionAir = 0.04;
bullet[me].do = function () {
this.force.y += this.mass * 0.001;
};
}
//knock back
const KNOCK = ((mech.crouch) ? 0.01 : 0.07) * b.modBulletSize * b.modBulletSize
player.force.x -= KNOCK * Math.cos(mech.angle)
player.force.y -= KNOCK * Math.sin(mech.angle) * 0.3 //reduce knock back in vertical direction to stop super jumps
}
},
{
name: "super balls", //2
description: "fire <strong>five</strong> balls in a wide arc<br>balls <strong>bounce</strong> with no momentum loss",
ammo: 0,
ammoPack: 8,
have: false,
num: 5,
isStarterGun: true,
fire() {
mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 25 : 20) * b.modFireRate); // cool down
b.muzzleFlash(20);
// mobs.alert(450);
const SPEED = mech.crouch ? 40 : 30
const SPREAD = mech.crouch ? 0.08 : 0.15
let dir = mech.angle - SPREAD * (b.modSuperBallNumber - 1) / 2;
for (let i = 0; i < b.modSuperBallNumber; i++) {
const me = bullet.length;
bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 10, 7 * b.modBulletSize, b.fireAttributes(dir, false));
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)
});
// Matter.Body.setDensity(bullet[me], 0.0001);
bullet[me].endCycle = game.cycle + Math.floor((300 + 60 * Math.random()) * b.isModBulletsLastLonger);
bullet[me].dmg = 0;
bullet[me].minDmgSpeed = 0;
bullet[me].restitution = 0.99;
bullet[me].friction = 0;
bullet[me].do = function () {
this.force.y += this.mass * 0.001;
};
dir += SPREAD;
}
}
},
{
name: "fléchettes", //3
description: "fire a volley of <strong>precise</strong> high velocity needles",
ammo: 0,
ammoPack: 22,
have: false,
isStarterGun: true,
count: 0, //used to track how many shots are in a volley before a big CD
lastFireCycle: 0, //use to remember how longs its been since last fire, used to reset count
fire() {
const CD = (mech.crouch) ? 45 : 25
if (this.lastFireCycle + CD < mech.cycle) this.count = 0 //reset count if it cycles past the CD
this.lastFireCycle = mech.cycle
if (this.count > ((mech.crouch) ? 6 : 1)) {
this.count = 0
mech.fireCDcycle = mech.cycle + Math.floor(CD * b.modFireRate); // cool down
} else {
this.count++
mech.fireCDcycle = mech.cycle + Math.floor(2 * b.modFireRate); // cool down
}
const me = bullet.length;
bullet[me] = Bodies.rectangle(mech.pos.x + 40 * Math.cos(mech.angle), mech.pos.y + 40 * Math.sin(mech.angle), 45 * b.modBulletSize, 1.4 * b.modBulletSize, b.fireAttributes(mech.angle));
bullet[me].endCycle = game.cycle + 180;
bullet[me].dmg = 1.15;
bullet[me].do = function () {
if (this.speed < 10) this.force.y += this.mass * 0.0003; //no gravity until it slows don to improve aiming
};
const SPEED = 50
Matter.Body.setVelocity(bullet[me], {
x: mech.Vx / 2 + SPEED * Math.cos(mech.angle),
y: mech.Vy / 2 + SPEED * Math.sin(mech.angle)
});
World.add(engine.world, bullet[me]); //add bullet to world
}
},
{
name: "wave beam", //4
description: "emit a <strong>sine wave</strong> of oscillating particles<br>particles propagate through <strong>walls</strong>",
ammo: 0,
ammoPack: 65,
have: false,
isStarterGun: true,
fire() {
const me = bullet.length;
const dir = mech.angle
bullet[me] = Bodies.polygon(mech.pos.x + 25 * Math.cos(dir), mech.pos.y + 25 * Math.sin(dir), 7, 5 * b.modBulletSize, {
angle: dir,
cycle: -0.43, //adjust this number until the bullets line up with the cross hairs
endCycle: game.cycle + Math.floor(100 * b.isModBulletsLastLonger),
inertia: Infinity,
frictionAir: 0,
minDmgSpeed: 0,
dmg: 0,
classType: "bullet",
collisionFilter: {
category: cat.bullet,
mask: 0, //cat.mob | cat.mobBullet | cat.mobShield
},
onDmg() {},
onEnd() {},
do() {
if (!mech.isBodiesAsleep) {
this.cycle++
this.force = Vector.mult(Vector.normalise(this.direction), wiggleMag * Math.cos(this.cycle * 0.35)) //wiggle
//check if inside a mob
const q = Matter.Query.point(mob, this.position)
for (let i = 0; i < q.length; i++) {
let dmg = b.dmgScale * 0.5
q[i].foundPlayer();
q[i].damage(dmg);
game.drawList.push({ //add dmg to draw queue
x: this.position.x,
y: this.position.y,
radius: Math.log(2 * dmg + 1.1) * 40,
color: 'rgba(0,0,0,0.4)',
time: game.drawTime
});
}
}
}
});
World.add(engine.world, bullet[me]); //add bullet to world
mech.fireCDcycle = mech.cycle + Math.floor(3 * b.modFireRate); // cool down
const wiggleMag = bullet[me].mass * ((mech.crouch) ? 0.01 : 0.02) * ((mech.flipLegs === 1) ? 1 : -1)
const SPEED = 8;
Matter.Body.setVelocity(bullet[me], {
x: SPEED * Math.cos(dir),
y: SPEED * Math.sin(dir)
});
bullet[me].direction = Vector.perp(bullet[me].velocity)
}
},
{
name: "missiles",
description: "fire missiles that accelerate towards enemies<br><strong class='color-e'>explodes</strong> when near target",
ammo: 0,
ammoPack: 4,
have: false,
isStarterGun: false,
fireCycle: 0,
ammoLoaded: 0,
fire() {
let dir = mech.angle + (0.5 - Math.random()) * (mech.crouch ? 0 : 0.2);
const me = bullet.length;
bullet[me] = Bodies.rectangle(mech.pos.x + 40 * Math.cos(mech.angle), mech.pos.y + 40 * Math.sin(mech.angle) - 3, 30 * b.modBulletSize, 4 * b.modBulletSize, b.fireAttributes(dir));
const thrust = 0.00417 * bullet[me].mass;
b.fireProps(mech.crouch ? 50 : 25, -3 * (0.5 - Math.random()) + (mech.crouch ? 25 : -8), dir, me); //cd , speed
// bullet[me].collisionFilter.mask = cat.map | cat.body | cat.mobBullet
// Matter.Body.setDensity(bullet[me], 0.01) //doesn't help with reducing explosion knock backs
bullet[me].force.y += 0.0005; //a small push down at first to make it seem like the missile is briefly falling
bullet[me].frictionAir = 0.023
bullet[me].endCycle = game.cycle + Math.floor((280 + 40 * Math.random()) * b.isModBulletsLastLonger);
bullet[me].explodeRad = 170 + 60 * Math.random();
bullet[me].lookFrequency = Math.floor(21 + Math.random() * 7);
bullet[me].onEnd = function () {
b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
}
bullet[me].onDmg = function () {
this.tryToLockOn();
// this.endCycle = 0; //bullet ends cycle after doing damage // also triggers explosion
};
bullet[me].lockedOn = null;
bullet[me].tryToLockOn = function () {
this.lockedOn = null;
let closeDist = Infinity;
//look for closest target to where the missile will be in 30 cycles
const futurePos = Vector.add(this.position, Vector.mult(this.velocity, 30))
for (let i = 0, len = mob.length; i < len; ++i) {
if (
mob[i].alive && 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 futureDist = Vector.magnitude(Vector.sub(futurePos, mob[i].position));
if (futureDist < closeDist) {
closeDist = futureDist;
this.lockedOn = mob[i];
this.frictionAir = 0.05; //extra friction once a target it locked
}
}
}
//explode when bullet is close enough to target
if (this.lockedOn && Vector.magnitude(Vector.sub(this.position, this.lockedOn.position)) < this.explodeRad) {
// console.log('hit')
this.endCycle = 0; //bullet ends cycle after doing damage //also triggers explosion
const dmg = b.dmgScale * 5;
this.lockedOn.damage(dmg); //does extra damage to target
}
};
bullet[me].do = function () {
if (!mech.isBodiesAsleep) {
if (!(mech.cycle % this.lookFrequency)) {
this.tryToLockOn();
}
//rotate missile towards the target
if (this.lockedOn) {
const face = {
x: Math.cos(this.angle),
y: Math.sin(this.angle)
};
const target = Vector.normalise(Vector.sub(this.position, this.lockedOn.position));
if (Vector.dot(target, face) > -0.98) {
if (Vector.cross(target, face) > 0) {
Matter.Body.rotate(this, 0.08);
} else {
Matter.Body.rotate(this, -0.08);
}
}
}
//accelerate in direction bullet is facing
const dir = this.angle; // + (Math.random() - 0.5);
this.force.x += Math.cos(dir) * thrust;
this.force.y += Math.sin(dir) * thrust;
//draw rocket
ctx.beginPath();
ctx.arc(this.position.x - Math.cos(this.angle) * 27 + (Math.random() - 0.5) * 4, this.position.y - Math.sin(this.angle) * 27 + (Math.random() - 0.5) * 4, 11, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255,155,0,0.5)";
ctx.fill();
} else {
//draw rocket with time stop
ctx.beginPath();
ctx.arc(this.position.x - Math.cos(this.angle) * 27, this.position.y - Math.sin(this.angle) * 27, 11, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255,155,0,0.5)";
ctx.fill();
}
}
}
},
{
name: "flak",
description: "fire a cluster of short range projectiles<br><strong class='color-e'>explodes</strong> on contact or after half a second",
ammo: 0,
ammoPack: 6,
defaultAmmoPack: 6, //use to revert ammoPack after mod changes drop rate
have: false,
isStarterGun: true,
fire() {
mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 25 : 10) * b.modFireRate); // cool down
b.muzzleFlash(30);
const SPEED = mech.crouch ? 29 : 25
const END = Math.floor(mech.crouch ? 30 : 18);
const side1 = 17 * b.modBulletSize
const side2 = 4 * b.modBulletSize
const totalBullets = 5
const angleStep = (mech.crouch ? 0.06 : 0.25) / totalBullets
let dir = mech.angle - angleStep * totalBullets / 2;
for (let i = 0; i < totalBullets; i++) { //5 -> 7
dir += angleStep
const me = bullet.length;
bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), side1, side2, b.fireAttributes(dir));
World.add(engine.world, bullet[me]); //add bullet to world
Matter.Body.setVelocity(bullet[me], {
x: (SPEED + 15 * Math.random() - 2 * i) * Math.cos(dir),
y: (SPEED + 15 * Math.random() - 2 * i) * Math.sin(dir)
});
bullet[me].endCycle = 2 * i + game.cycle + END
bullet[me].restitution = 0;
bullet[me].friction = 1;
bullet[me].explodeRad = (mech.crouch ? 85 : 60) + (Math.random() - 0.5) * 50;
bullet[me].onEnd = function () {
b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
}
bullet[me].onDmg = function () {
this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion
};
bullet[me].do = function () {
this.force.y += this.mass * 0.0004;
}
}
}
},
{
name: "grenades", //7
description: "lob a single bouncy projectile<br><strong class='color-e'>explodes</strong> on contact or after one second",
ammo: 0,
ammoPack: 7,
have: false,
isStarterGun: false,
fire() {
const me = bullet.length;
const dir = mech.angle; // + Math.random() * 0.05;
bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 20 * b.modBulletSize, b.fireAttributes(dir, true));
b.fireProps(mech.crouch ? 30 : 20, mech.crouch ? 43 : 32, dir, me); //cd , speed
Matter.Body.setDensity(bullet[me], 0.0005);
bullet[me].totalCycles = 100;
bullet[me].endCycle = game.cycle + Math.floor(mech.crouch ? 120 : 80);
bullet[me].restitution = 0.2;
bullet[me].explodeRad = 275;
bullet[me].onEnd = function () {
b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
}
bullet[me].minDmgSpeed = 1;
bullet[me].onDmg = function () {
this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion
};
bullet[me].do = function () {
//extra gravity for harder arcs
this.force.y += this.mass * 0.0025;
};
}
},
{
name: "vacuum bomb", //8
description: "fire a bomb that <strong>sucks</strong> before <strong class='color-e'>exploding</strong><br>click left mouse again to <strong>detonate</strong>",
ammo: 0,
ammoPack: 2,
have: false,
isStarterGun: false,
fire() {
const me = bullet.length;
const dir = mech.angle;
bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 35 * b.modBulletSize, b.fireAttributes(dir, false));
b.fireProps(10, mech.crouch ? 42 : 26, dir, me); //cd , speed
Matter.Body.setDensity(bullet[me], 0.0002);
bullet[me].restitution = 0.2;
bullet[me].friction = 0.3;
bullet[me].endCycle = Infinity
bullet[me].explodeRad = 400 + Math.floor(Math.random() * 60);
bullet[me].onEnd = function () {
b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end
}
bullet[me].onDmg = function () {
// this.endCycle = 0; //bullet ends cycle after doing damage //this triggers explosion
};
bullet[me].radius = 22; //used from drawing timer
bullet[me].isArmed = false;
bullet[me].isSucking = false;
bullet[me].do = function () {
//extra gravity for harder arcs
this.force.y += this.mass * 0.0022;
mech.fireCDcycle = mech.cycle + 10 //can't fire until after the explosion
//set armed and sucking status
if (!this.isArmed && !game.mouseDown) {
this.isArmed = true
} else if (this.isArmed && game.mouseDown && !this.isSucking) {
this.isSucking = true;
this.endCycle = game.cycle + 35;
}
if (this.isSucking) {
if (!mech.isBodiesAsleep) {
const that = this
let mag = 0.1
function suck(who, radius = that.explodeRad * 2) {
for (i = 0, len = who.length; i < len; i++) {
const sub = Vector.sub(that.position, who[i].position);
const dist = Vector.magnitude(sub);
if (dist < radius && dist > 150) {
knock = Vector.mult(Vector.normalise(sub), mag * who[i].mass / Math.sqrt(dist));
who[i].force.x += knock.x;
who[i].force.y += knock.y;
}
}
}
if (game.cycle > this.endCycle - 5) {
mag = -0.22
suck(body)
suck(mob)
suck(powerUp)
suck(bullet)
suck([player])
} else {
mag = 0.1
suck(body)
suck(mob)
suck(powerUp)
suck(bullet)
suck([player])
}
//keep bomb in place
Matter.Body.setVelocity(this, {
x: 0,
y: 0
});
//draw suck
const radius = 2.5 * this.explodeRad * (this.endCycle - game.cycle) / 35
ctx.fillStyle = "rgba(0,0,0,0.1)";
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, radius, 0, 2 * Math.PI);
ctx.fill();
}
} else {
// flashing lights to show armed
if (!(game.cycle % 10)) {
if (this.isFlashOn) {
this.isFlashOn = false;
} else {
this.isFlashOn = true;
}
}
if (this.isFlashOn) {
ctx.fillStyle = "#000";
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
//draw clock on timer
ctx.fillStyle = "#f04";
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius * 0.7, 0, 2 * Math.PI);
ctx.fill();
}
}
}
}
},
{
name: "mine", //9
description: "toss a <strong>proximity</strong> mine that <strong>sticks</strong> to walls<br>fires <strong>nails</strong> at enemies within range",
ammo: 0,
ammoPack: (game.difficultyMode > 3) ? 2 : 3,
have: false,
isStarterGun: false,
fire() {
const speed = mech.crouch ? 36 : 22
b.mine({
x: mech.pos.x + 30 * Math.cos(mech.angle),
y: mech.pos.y + 30 * Math.sin(mech.angle)
}, {
x: speed * Math.cos(mech.angle),
y: speed * Math.sin(mech.angle)
})
mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 70 : 45) * b.modFireRate); // cool down
}
},
{
name: "spores", //10
description: "fire orbs that discharge <strong style='letter-spacing: 2px;'>spores</strong><br><strong style='letter-spacing: 2px;'>spores</strong> seek out enemies",
ammo: 0,
ammoPack: (game.difficultyMode > 3) ? 3 : 4,
have: false,
isStarterGun: false,
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), 20, 4.5, b.fireAttributes(dir, false));
b.fireProps(mech.crouch ? 60 : 40, mech.crouch ? 28 : 14, dir, me); //cd , speed
Matter.Body.setDensity(bullet[me], 0.000001);
bullet[me].endCycle = game.cycle + 80;
bullet[me].frictionAir = 0;
bullet[me].friction = 0.5;
bullet[me].restitution = 0.3;
bullet[me].minDmgSpeed = 0;
bullet[me].onDmg = function () {};
bullet[me].do = function () {
if (!mech.isBodiesAsleep) {
const SCALE = 1.022
Matter.Body.scale(this, SCALE, SCALE);
this.frictionAir += 0.00023;
}
this.force.y += this.mass * 0.00045;
//draw green glow
ctx.fillStyle = "rgba(0,200,125,0.16)";
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, 26, 0, 2 * Math.PI);
ctx.fill();
};
//spawn bullets on end
bullet[me].onEnd = function () {
const NUM = 10;
for (let i = 0; i < NUM; i++) {
b.spore(this)
}
}
}
},
{
name: "drones", //11
description: "deploy drones that <strong>crash</strong> into enemies<br>collisions reduce their <strong>lifespan</strong> by 1 second",
ammo: 0,
ammoPack: (game.difficultyMode > 3) ? 8 : 10,
have: false,
isStarterGun: true,
fire() {
b.drone(mech.crouch ? 45 : 1)
mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 25 : 5) * b.modFireRate); // cool down
}
},
{
name: "foam", //12
description: "spray bubbly foam that <strong>sticks</strong> to enemies<br>does <strong class='color-d'>damage</strong> over time and <strong>slows</strong> movement",
ammo: 0,
ammoPack: 35,
have: false,
isStarterGun: true,
fire() {
mech.fireCDcycle = mech.cycle + Math.floor((mech.crouch ? 12 : 5) * b.modFireRate); // cool down
const me = bullet.length;
const dir = mech.angle + 0.2 * (Math.random() - 0.5)
const RADIUS = (8 + 16 * Math.random()) * b.modBulletSize
bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 25, RADIUS, {
angle: dir,
density: 0.00005, // 0.001 is normal density
inertia: Infinity,
frictionAir: 0.003,
friction: 0.2,
restitution: 0.2,
dmg: 0.1, //damage done in addition to the damage from momentum
classType: "bullet",
collisionFilter: {
category: cat.bullet,
mask: cat.map | cat.body | cat.mob | cat.mobShield
},
minDmgSpeed: 0,
endCycle: Infinity,
count: 0,
radius: RADIUS,
target: null,
targetVertex: null,
onDmg(who) {
if (!this.target && who.alive && (who.dropPowerUp || b.isModFoamShieldHit) && (!who.isShielded || b.isModFoamShieldHit)) {
this.target = who;
this.collisionFilter.category = cat.body;
this.collisionFilter.mask = null;
let bestVertexDistance = Infinity
let bestVertex = null
for (let i = 0; i < this.target.vertices.length; i++) {
const dist = Vector.magnitude(Vector.sub(this.position, this.target.vertices[i]));
if (dist < bestVertexDistance) {
bestVertex = i
bestVertexDistance = dist
}
}
this.targetVertex = bestVertex
}
},
onEnd() {},
do() {
// ctx.beginPath() //draw white circle
// ctx.arc(this.position.x, this.position.y, this.radius * 0.97 - 1.6, 0, 2 * Math.PI);
// ctx.fillStyle = "#fff"
// ctx.fill()
if (!mech.isBodiesAsleep) { //if time dilation isn't active
this.force.y += this.mass * 0.00006; //gravity
if (this.count < 17) {
this.count++
//grow
const SCALE = 1.08
Matter.Body.scale(this, SCALE, SCALE);
this.radius *= SCALE;
} else {
//shrink
const SCALE = 1 - 0.0035 / b.isModBulletsLastLonger
Matter.Body.scale(this, SCALE, SCALE);
this.radius *= SCALE;
if (this.radius < 14) this.endCycle = 0;
}
if (this.target && this.target.alive) { //if stuck to a target
Matter.Body.setPosition(this, this.target.vertices[this.targetVertex])
Matter.Body.setVelocity(this.target, Vector.mult(this.target.velocity, 0.9))
Matter.Body.setAngularVelocity(this.target, this.target.angularVelocity * 0.9)
if (this.target.isShielded) {
this.target.damage(b.dmgScale * 0.001);
} else {
this.target.damage(b.dmgScale * 0.005);
}
} else if (this.target !== null) { //look for a new target
this.target = null
this.collisionFilter.category = cat.bullet;
this.collisionFilter.mask = cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield
}
}
}
});
World.add(engine.world, bullet[me]); //add bullet to world
const SPEED = mech.crouch ? 22 : 12 - RADIUS * 0.25;
Matter.Body.setVelocity(bullet[me], {
x: SPEED * Math.cos(dir),
y: SPEED * Math.sin(dir)
});
}
},
{
name: "rail gun", //13
description: "use <strong class='color-f'>energy</strong> to launch a high-speed <strong>dense</strong> rod<br><strong>hold</strong> left mouse to charge, <strong>release</strong> to fire",
ammo: 0,
ammoPack: 2.84,
have: false,
isStarterGun: false,
fire() {
const me = bullet.length;
bullet[me] = Bodies.rectangle(0, 0, 0.015 * b.modBulletSize, 0.0015 * b.modBulletSize, {
density: 0.01, //0.001 is normal
//frictionAir: 0.01, //restitution: 0,
// angle: 0,
// friction: 0.5,
frictionAir: 0,
dmg: 0, //damage done in addition to the damage from momentum
classType: "bullet",
collisionFilter: {
category: 0,
mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield
},
minDmgSpeed: 5,
onDmg(who) {
if (who.shield) {
Matter.Body.setVelocity(this, {
x: -0.1 * this.velocity.x,
y: -0.1 * this.velocity.y
});
Matter.Body.setDensity(this, 0.001);
// this.endCycle = 0;
}
}, //this.endCycle = 0 //triggers despawn
onEnd() {}
});
mech.fireCDcycle = Infinity; // cool down
World.add(engine.world, bullet[me]); //add bullet to world
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.00015 / 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
Matter.Body.setPosition(this, {
x: mech.pos.x,
y: mech.pos.y
})
Matter.Body.setAngle(this, mech.angle)
const speed = 90
Matter.Body.setVelocity(this, {
x: mech.Vx / 2 + speed * this.charge * Math.cos(mech.angle),
y: mech.Vy / 2 + speed * this.charge * Math.sin(mech.angle)
});
//knock back
const KNOCK = ((mech.crouch) ? 0.1 : 0.5) * b.modBulletSize * b.modBulletSize * this.charge * this.charge
player.force.x -= KNOCK * Math.cos(mech.angle)
player.force.y -= KNOCK * Math.sin(mech.angle) * 0.35 //reduce knock back in vertical direction to stop super jumps
//push away blocks when firing
let range = 700 * this.charge
for (let i = 0, len = body.length; i < len; ++i) {
const SUB = Vector.sub(body[i].position, mech.pos)
const DISTANCE = Vector.magnitude(SUB)
if (DISTANCE < range) {
const DEPTH = Math.min(range - DISTANCE, 300)
const FORCE = Vector.mult(Vector.normalise(SUB), 0.003 * Math.sqrt(DEPTH) * body[i].mass)
body[i].force.x += FORCE.x;
body[i].force.y += FORCE.y - body[i].mass * (game.g * 1.5); //kick up a bit to give them some arc
}
}
for (let i = 0, len = mob.length; i < len; ++i) {
const SUB = Vector.sub(mob[i].position, mech.pos)
const DISTANCE = Vector.magnitude(SUB)
if (DISTANCE < range) {
const DEPTH = Math.min(range - DISTANCE, 300)
const FORCE = Vector.mult(Vector.normalise(SUB), 0.003 * Math.sqrt(DEPTH) * mob[i].mass)
mob[i].force.x += 1.5 * FORCE.x;
mob[i].force.y += 1.5 * FORCE.y;
}
}
} else if (mech.fieldMeter > 0.005) { // charging on mouse down
mech.fireCDcycle = Infinity //can't fire until mouse is released
const lastCharge = this.charge
let chargeRate = (mech.crouch) ? 0.975 : 0.987
chargeRate *= Math.pow(b.modFireRate, 0.04)
this.charge = this.charge * chargeRate + (1 - chargeRate) // this.charge converges to 1
mech.fieldMeter -= (this.charge - lastCharge) * 0.28 //energy drain is proportional to charge gained, but doesn't stop normal mech.fieldRegen
//draw targeting
let best;
let range = 3000
const dir = mech.angle
const path = [{
x: mech.pos.x + 20 * Math.cos(dir),
y: mech.pos.y + 20 * Math.sin(dir)
},
{
x: mech.pos.x + range * Math.cos(dir),
y: mech.pos.y + range * Math.sin(dir)
}
];
const vertexCollision = function (v1, v1End, domain) {
for (let i = 0; i < domain.length; ++i) {
let vertices = domain[i].vertices;
const len = vertices.length - 1;
for (let j = 0; j < len; j++) {
results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]);
if (results.onLine1 && results.onLine2) {
const dx = v1.x - results.x;
const dy = v1.y - results.y;
const dist2 = dx * dx + dy * dy;
if (dist2 < best.dist2) {
best = {
x: results.x,
y: results.y,
dist2: dist2,
who: domain[i],
v1: vertices[j],
v2: vertices[j + 1]
};
}
}
}
results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]);
if (results.onLine1 && results.onLine2) {
const dx = v1.x - results.x;
const dy = v1.y - results.y;
const dist2 = dx * dx + dy * dy;
if (dist2 < best.dist2) {
best = {
x: results.x,
y: results.y,
dist2: dist2,
who: domain[i],
v1: vertices[0],
v2: vertices[len]
};
}
}
}
};
//check for collisions
best = {
x: null,
y: null,
dist2: Infinity,
who: null,
v1: null,
v2: null
};
vertexCollision(path[0], path[1], mob);
vertexCollision(path[0], path[1], map);
vertexCollision(path[0], path[1], body);
if (best.dist2 != Infinity) { //if hitting something
path[path.length - 1] = {
x: best.x,
y: best.y
};
}
//draw beam
ctx.beginPath();
ctx.moveTo(path[0].x, path[0].y);
ctx.lineTo(path[1].x, path[1].y);
ctx.strokeStyle = `rgba(100,0,180,0.7)`;
ctx.lineWidth = this.charge * 1
ctx.setLineDash([10, 20]);
ctx.stroke();
ctx.setLineDash([0, 0]);
//draw magnetic field
const X = mech.pos.x
const Y = mech.pos.y
const unitVector = Vector.normalise(Vector.sub(game.mouseInGame, mech.pos))
const unitVectorPerp = Vector.perp(unitVector)
function magField(mag, arc) {
ctx.moveTo(X, Y);
ctx.bezierCurveTo(
X + unitVector.x * mag, Y + unitVector.y * mag,
X + unitVector.x * mag + unitVectorPerp.x * arc, Y + unitVector.y * mag + unitVectorPerp.y * arc,
X + unitVectorPerp.x * arc, Y + unitVectorPerp.y * arc)
ctx.bezierCurveTo(
X - unitVector.x * mag + unitVectorPerp.x * arc, Y - unitVector.y * mag + unitVectorPerp.y * arc,
X - unitVector.x * mag, Y - unitVector.y * mag,
X, Y)
}
ctx.fillStyle = `rgba(50,0,100,0.05)`;
for (let i = 3; i < 7; i++) {
const MAG = 8 * i * i * this.charge * (0.93 + 0.07 * Math.random())
const ARC = 6 * i * i * this.charge * (0.93 + 0.07 * Math.random())
ctx.beginPath();
magField(MAG, ARC)
magField(MAG, -ARC)
ctx.fill();
}
}
}
}
},
{
name: "laser", //14
description: "emit a beam of collimated coherent <strong>light</strong><br>drains <strong class='color-f'>energy</strong> instead of ammunition",
ammo: 0,
ammoPack: Infinity,
have: false,
isStarterGun: true,
fire() {
const reflectivity = 1 - 1 / (b.modLaserReflections * 1.5)
let damage = b.dmgScale * b.modLaserDamage
if (mech.fieldMeter < b.modLaserFieldDrain) {
mech.fireCDcycle = mech.cycle + 100; // cool down if out of energy
} else {
mech.fieldMeter -= mech.fieldRegen + b.modLaserFieldDrain
let best = {
x: null,
y: null,
dist2: Infinity,
who: null,
v1: null,
v2: null
};
const color = "#f00";
const range = 3000;
const path = [{
x: mech.pos.x + 20 * Math.cos(mech.angle),
y: mech.pos.y + 20 * Math.sin(mech.angle)
},
{
x: mech.pos.x + range * Math.cos(mech.angle),
y: mech.pos.y + range * Math.sin(mech.angle)
}
];
const vertexCollision = function (v1, v1End, domain) {
for (let i = 0; i < domain.length; ++i) {
let vertices = domain[i].vertices;
const len = vertices.length - 1;
for (let j = 0; j < len; j++) {
results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]);
if (results.onLine1 && results.onLine2) {
const dx = v1.x - results.x;
const dy = v1.y - results.y;
const dist2 = dx * dx + dy * dy;
if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
best = {
x: results.x,
y: results.y,
dist2: dist2,
who: domain[i],
v1: vertices[j],
v2: vertices[j + 1]
};
}
}
}
results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]);
if (results.onLine1 && results.onLine2) {
const dx = v1.x - results.x;
const dy = v1.y - results.y;
const dist2 = dx * dx + dy * dy;
if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
best = {
x: results.x,
y: results.y,
dist2: dist2,
who: domain[i],
v1: vertices[0],
v2: vertices[len]
};
}
}
}
};
const checkForCollisions = function () {
best = {
x: null,
y: null,
dist2: Infinity,
who: null,
v1: null,
v2: null
};
vertexCollision(path[path.length - 2], path[path.length - 1], mob);
vertexCollision(path[path.length - 2], path[path.length - 1], map);
vertexCollision(path[path.length - 2], path[path.length - 1], body);
};
const laserHitMob = function () {
if (best.who.alive) {
best.who.damage(damage);
best.who.locatePlayer();
ctx.fillStyle = color; //draw mob damage circle
ctx.beginPath();
ctx.arc(path[path.length - 1].x, path[path.length - 1].y, Math.sqrt(damage) * 100, 0, 2 * Math.PI);
ctx.fill();
}
};
const reflection = function () { // https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector
const n = Vector.perp(Vector.normalise(Vector.sub(best.v1, best.v2)));
const d = Vector.sub(path[path.length - 1], path[path.length - 2]);
const nn = Vector.mult(n, 2 * Vector.dot(d, n));
const r = Vector.normalise(Vector.sub(d, nn));
path[path.length] = Vector.add(Vector.mult(r, range), path[path.length - 1]);
};
checkForCollisions();
let lastBestOdd
let lastBestEven = best.who //used in hack below
if (best.dist2 !== Infinity) {
//if hitting something
path[path.length - 1] = {
x: best.x,
y: best.y
};
laserHitMob();
for (let i = 0; i < b.modLaserReflections; i++) {
reflection();
checkForCollisions();
if (best.dist2 !== Infinity) { //if hitting something
lastReflection = best
path[path.length - 1] = {
x: best.x,
y: best.y
};
damage *= reflectivity
laserHitMob();
//I'm not clear on how this works, but it gets ride of a bug where the laser reflects inside a block, often vertically.
//I think it checks to see if the laser is reflecting off a different part of the same block, if it is "inside" a block
if (i % 2) {
if (lastBestOdd === best.who) break
} else {
lastBestOdd = best.who
if (lastBestEven === best.who) break
}
} else {
break
}
}
}
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.lineDashOffset = 300 * Math.random()
ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]);
for (let i = 1, len = path.length; i < len; ++i) {
ctx.beginPath();
ctx.moveTo(path[i - 1].x, path[i - 1].y);
ctx.lineTo(path[i].x, path[i].y);
ctx.stroke();
ctx.globalAlpha *= reflectivity; //reflections are less intense
}
ctx.setLineDash([0, 0]);
ctx.globalAlpha = 1;
}
}
},
{
name: "pulse", //15
description: "convert 25% of your <strong class='color-f'>energy</strong> into a pulsed laser<br>instantly initiates a fusion <strong class='color-e'>explosion</strong>",
ammo: 0,
ammoPack: Infinity,
have: false,
isStarterGun: true,
fire() {
//calculate laser collision
let best;
let range = 3000
const path = [{
x: mech.pos.x + 20 * Math.cos(mech.angle),
y: mech.pos.y + 20 * Math.sin(mech.angle)
},
{
x: mech.pos.x + range * Math.cos(mech.angle),
y: mech.pos.y + range * Math.sin(mech.angle)
}
];
const vertexCollision = function (v1, v1End, domain) {
for (let i = 0; i < domain.length; ++i) {
let vertices = domain[i].vertices;
const len = vertices.length - 1;
for (let j = 0; j < len; j++) {
results = game.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]);
if (results.onLine1 && results.onLine2) {
const dx = v1.x - results.x;
const dy = v1.y - results.y;
const dist2 = dx * dx + dy * dy;
if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
best = {
x: results.x,
y: results.y,
dist2: dist2,
who: domain[i],
v1: vertices[j],
v2: vertices[j + 1]
};
}
}
}
results = game.checkLineIntersection(v1, v1End, vertices[0], vertices[len]);
if (results.onLine1 && results.onLine2) {
const dx = v1.x - results.x;
const dy = v1.y - results.y;
const dist2 = dx * dx + dy * dy;
if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
best = {
x: results.x,
y: results.y,
dist2: dist2,
who: domain[i],
v1: vertices[0],
v2: vertices[len]
};
}
}
}
};
//check for collisions
best = {
x: null,
y: null,
dist2: Infinity,
who: null,
v1: null,
v2: null
};
vertexCollision(path[0], path[1], mob);
vertexCollision(path[0], path[1], map);
vertexCollision(path[0], path[1], body);
if (best.dist2 != Infinity) { //if hitting something
path[path.length - 1] = {
x: best.x,
y: best.y
};
}
//use energy to explode
const energy = 0.3 * Math.min(mech.fieldMeter, 1.75)
mech.fieldMeter -= energy
if (best.who) b.explosion(path[1], 1000 * energy)
mech.fireCDcycle = mech.cycle + Math.floor(60 * b.modFireRate); // cool down
//draw laser beam
ctx.beginPath();
ctx.moveTo(path[0].x, path[0].y);
ctx.lineTo(path[1].x, path[1].y);
ctx.strokeStyle = "rgba(255,0,0,0.13)"
ctx.lineWidth = 60 * energy / 0.2
ctx.stroke();
ctx.strokeStyle = "rgba(255,0,0,0.2)"
ctx.lineWidth = 18
ctx.stroke();
ctx.strokeStyle = "#f00";
ctx.lineWidth = 4
ctx.stroke();
//draw little dots along the laser path
const sub = Vector.sub(path[1], path[0])
const mag = Vector.magnitude(sub)
for (let i = 0, len = Math.floor(mag * 0.03 * energy / 0.2); i < len; i++) {
const dist = Math.random()
game.drawList.push({
x: path[0].x + sub.x * dist + 13 * (Math.random() - 0.5),
y: path[0].y + sub.y * dist + 13 * (Math.random() - 0.5),
radius: 1 + 4 * Math.random(),
color: "rgba(255,0,0,0.5)",
time: Math.floor(2 + 33 * Math.random() * Math.random())
});
}
}
},
// {
// name: "dwarf star", //14
// description: "drop a mine that gravitational pulls in matter",
// ammo: 0,
// ammoPack: 1000,
// have: false,
// isStarterGun: false,
// fire() {
// const me = bullet.length;
// const dir = mech.angle
// const TOTAL_CYCLES = 1020
// bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(dir), mech.pos.y + 30 * Math.sin(dir), 3 * b.modBulletSize, {
// density: 0.05,
// //frictionAir: 0.01,
// restitution: 0,
// angle: 0,
// friction: 1,
// // frictionAir: 1,
// endCycle: game.cycle + TOTAL_CYCLES,
// dmg: 0, //damage done in addition to the damage from momentum
// classType: "bullet",
// collisionFilter: {
// category: 0x000100,
// mask: 0x010011 //mask: 0x000101, //for self collision
// },
// minDmgSpeed: 5,
// range: 0,
// onDmg() {
// this.endCycle = 0;
// }, //this.endCycle = 0 //triggers despawn
// onEnd() {},
// do() {
// this.force.y += this.mass * 0.005;
// this.range += 0.5
// //damage nearby mobs
// const dmg = b.dmgScale * 0.02
// for (let i = 0, len = mob.length; i < len; ++i) {
// if (mob[i].alive) {
// sub = Vector.sub(this.position, mob[i].position);
// dist = Vector.magnitude(sub) - mob[i].radius;
// if (dist < this.range) {
// mob[i].damage(dmg);
// mob[i].locatePlayer();
// }
// }
// }
// //pull in body, and power ups?, and bullets?
// for (let i = 0, len = body.length; i < len; ++i) {
// sub = Vector.sub(this.position, body[i].position);
// dist = Vector.magnitude(sub)
// if (dist < this.range) {
// this.range += body[i].mass * 2
// Matter.World.remove(engine.world, body[i]);
// body.splice(i, 1);
// break;
// }
// }
// //draw
// const opacity = (this.endCycle - game.cycle) / TOTAL_CYCLES
// ctx.fillStyle = `rgba(170,220,255,${opacity})`;
// ctx.beginPath();
// ctx.arc(this.position.x, this.position.y, this.range, 0, 2 * Math.PI);
// ctx.fill();
// }
// });
// b.fireProps(60, 0, dir, me); //cd , speed
// }
// },
// {
// name: "kinetic slugs", //1
// description: "fire a large <strong>rod</strong> that does excessive physical <strong class='color-d'>damage</strong><br><em>high recoil</em>",
// ammo: 0,
// ammoPack: 5,
// have: false,
// isStarterGun: true,
// fire() {
// b.muzzleFlash(45);
// // mobs.alert(800);
// const me = bullet.length;
// const dir = mech.angle;
// bullet[me] = Bodies.rectangle(mech.pos.x + 50 * Math.cos(mech.angle), mech.pos.y + 50 * Math.sin(mech.angle), 70 * b.modBulletSize, 30 * b.modBulletSize, b.fireAttributes(dir));
// b.fireProps(mech.crouch ? 55 : 40, 50, dir, me); //cd , speed
// bullet[me].endCycle = game.cycle + Math.floor(180 * b.isModBulletsLastLonger);
// bullet[me].do = function () {
// this.force.y += this.mass * 0.0005;
// };
// //knock back
// const KNOCK = ((mech.crouch) ? 0.025 : 0.25) * b.modBulletSize * b.modBulletSize
// player.force.x -= KNOCK * Math.cos(dir)
// player.force.y -= KNOCK * Math.sin(dir) * 0.3 //reduce knock back in vertical direction to stop super jumps
// },
// {
// name: "triboelectricty", //14
// description: "release <strong>particles</strong> that quickly seek out targets",
// ammo: 0,
// ammoPack: 40,
// have: false,
// isStarterGun: true,
// fire() {
// const dir = mech.angle + 0.2 * (Math.random() - 0.5);
// const me = bullet.length;
// const RADIUS = 6 * b.modBulletSize
// bullet[me] = Bodies.circle(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), RADIUS, {
// angle: dir,
// inertia: Infinity,
// // friction: 0.05,
// // frictionAir: 0.05,
// restitution: 0.8,
// dmg: 0.14, //damage done in addition to the damage from momentum
// lookFrequency: 3,
// endCycle: game.cycle + Math.floor(120 * b.isModBulletsLastLonger),
// classType: "bullet",
// collisionFilter: {
// category: 0x000100,
// mask: 0x010111 //self collide
// },
// minDmgSpeed: 0,
// lockedOn: null,
// isFollowMouse: true,
// onDmg() {
// this.endCycle = 0;
// },
// onEnd() {},
// do() {
// if (this.lockedOn) { //accelerate towards mobs
// this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, this.lockedOn.position)), -this.mass * 0.01)
// Matter.Body.setVelocity(this, {
// x: this.velocity.x * 0.93,
// y: this.velocity.y * 0.93
// });
// } else {
// this.force.y += this.mass * 0.0004;
// }
// }
// })
// b.fireProps(mech.crouch ? 19 : 15, mech.crouch ? 45 : 30, dir, me); //cd , speed
// //find mob targets
// let closeDist = Infinity;
// for (let i = 0, len = mob.length; i < len; ++i) {
// if (
// Matter.Query.ray(map, bullet[me].position, mob[i].position).length === 0 &&
// Matter.Query.ray(body, bullet[me].position, mob[i].position).length === 0
// ) {
// const TARGET_VECTOR = Vector.sub(bullet[me].position, mob[i].position)
// const DIST = Vector.magnitude(TARGET_VECTOR);
// if (DIST < closeDist) {
// closeDist = DIST;
// bullet[me].lockedOn = mob[i]
// }
// }
// }
// }
// },
// {
]
};