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 activeGun: null, //current gun in use by player inventoryGun: 0, inventory: [], //list of what guns player has // 0 starts with basic gun fire() { if (input.fire && mech.fireCDcycle < mech.cycle && (!input.field || mech.fieldFire) && b.inventory.length) { if (b.guns[b.activeGun].ammo > 0) { b.guns[b.activeGun].fire(); if (mod.isCrouchAmmo && mech.crouch) { if (mod.isCrouchAmmo % 2) { b.guns[b.activeGun].ammo--; game.updateGunHUD(); } mod.isCrouchAmmo++ //makes the no ammo toggle off and on } else { b.guns[b.activeGun].ammo--; game.updateGunHUD(); } } else { if (mod.isAmmoFromHealth) { if (mech.health > 2 * mod.isAmmoFromHealth * mech.maxHealth) { mech.damage(mod.isAmmoFromHealth * mech.maxHealth / mech.harmReduction()); powerUps.spawn(mech.pos.x, mech.pos.y, "ammo"); } else { game.replaceTextLog = true; game.makeTextLog("not enough health for catabolism to produce ammo", 120); } } else { game.replaceTextLog = true; game.makeTextLog("
Q, E, and mouse wheel change weapons
", 200); } mech.fireCDcycle = mech.cycle + 30; //fire cooldown } if (mech.holdingTarget) { mech.drop(); } } }, removeAllGuns() { b.inventory = []; //removes guns and ammo for (let i = 0, len = b.guns.length; i < len; ++i) { b.guns[i].count = 0; b.guns[i].have = false; if (b.guns[i].ammo != Infinity) b.guns[i].ammo = 0; } b.activeGun = null; }, bulletRemove() { //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 } } } }, bulletDraw() { 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(); }, bulletDo() { 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.fireCD); // 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 }, fireCD: 1, setFireCD() { b.fireCD = mod.fireRate * mod.slowFire * mod.rerollHaste * mod.aimDamage / mod.fastTime }, 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, beforeDmg() {}, //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, beforeDmg() {}, //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); break; } else if (consBB[i].bodyB === me) { consBB[i].bodyB = consBB[i].bodyA; consBB.splice(i, 1); 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) { // typically explode is used for some bullets with .onEnd radius *= mod.explosiveRadius let dist, sub, knock; let dmg = radius * 0.013; if (mod.isExplosionHarm) radius *= 1.8 // 1/sqrt(2) radius -> area if (mod.isSmallExplosion) { radius *= 0.8 dmg *= 1.6 } game.drawList.push({ //add dmg to draw queue x: where.x, y: where.y, radius: radius, color: "rgba(255,25,0,0.6)", time: game.drawTime }); const alertRange = 100 + radius * 2; //alert range game.drawList.push({ //add alert to draw queue 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 (mod.isImmuneExplosion) { const mitigate = Math.min(1, Math.max(1 - mech.energy * 0.7, 0)) mech.damage(mitigate * radius * (mod.isExplosionHarm ? 0.0004 : 0.0001)); } else { mech.damage(radius * (mod.isExplosionHarm ? 0.0004 : 0.0001)); } // if (!(mod.isImmuneExplosion && mech.energy > 0.97)) { // if (mod.isExplosionHarm) { // mech.damage(radius * 0.0004); //300% more player damage from explosions // } else { // mech.damage(radius * 0.0001); //normal player damage from explosions // } // mech.drop(); // } knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * player.mass * 0.015); player.force.x += knock.x; player.force.y += knock.y; } else if (dist < alertRange) { knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * player.mass * 0.008); player.force.x += knock.x; player.force.y += knock.y; } //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) * 0.022); 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) * 0.013); 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) * 0.015); 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) * 0.01); 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 *= 2.5 //balancing explosion dmg to shields if (Matter.Query.ray(map, mob[i].position, where).length > 0) dmg *= 0.5 //reduce damage if a wall is in the way mob[i].damage(dmg * damageScale * b.dmgScale); mob[i].locatePlayer(); knock = Vector.mult(Vector.normalise(sub), (-Math.sqrt(dmg * damageScale) * mob[i].mass) * 0.01); mob[i].force.x += knock.x; mob[i].force.y += knock.y; radius *= 0.95 //reduced range for each additional explosion target damageScale *= 0.87 //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) * 0.006); mob[i].force.x += knock.x; mob[i].force.y += knock.y; } } } }, missile(where, angle, speed, size = 1, spawn = 0) { const me = bullet.length; bullet[me] = Bodies.rectangle(where.x, where.y, 30 * size, 4 * size, { angle: angle, friction: 0.5, frictionAir: 0.045, dmg: 0, //damage done in addition to the damage from momentum classType: "bullet", endCycle: game.cycle + Math.floor((230 + 40 * Math.random()) * mod.isBulletsLastLonger), collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield }, minDmgSpeed: 10, lookFrequency: Math.floor(15 + Math.random() * 3), explodeRad: 170 + 60 * Math.random(), beforeDmg() { this.tryToLockOn(); this.endCycle = 0; //bullet ends cycle after doing damage // also triggers explosion }, //this.endCycle = 0 //triggers despawn onEnd() { b.explosion(this.position, this.explodeRad * size); //makes bullet do explosive damage at end if (spawn) { for (let i = 0; i < mod.recursiveMissiles; i++) { if (0.2 - 0.02 * i > Math.random()) { b.missile(this.position, this.angle + Math.PI + 0.5 * (Math.random() - 0.5), 0, 0.33 + size, mod.recursiveMissiles) break; } } } }, lockedOn: null, tryToLockOn() { 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, 50)) this.lockedOn = null; // const futurePos = this.lockedOn ? :Vector.add(this.position, Vector.mult(this.velocity, 50)) 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.04; //extra friction once a target it locked } if (Vector.magnitude(Vector.sub(this.position, mob[i].position) < this.explodeRad)) { this.endCycle = 0; //bullet ends cycle after doing damage //also triggers explosion mob[i].lockedOn.damage(b.dmgScale * 2 * size); //does extra damage to target } } } //explode when bullet is close enough to target if (this.lockedOn && Vector.magnitude(Vector.sub(this.position, this.lockedOn.position)) < this.explodeRad) { this.endCycle = 0; //bullet ends cycle after doing damage //also triggers explosion this.lockedOn.damage(b.dmgScale * 4 * size); //does extra damage to target } }, do() { if (!mech.isBodiesAsleep) { if (!(mech.cycle % this.lookFrequency)) this.tryToLockOn(); if (this.lockedOn) { //rotate missile towards the target 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.1); } else { Matter.Body.rotate(this, -0.1); } this.frictionAir = 0.06; //extra friction if turning } else { this.frictionAir = 0.025; //low friction if not turning } } //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; ctx.beginPath(); //draw rocket ctx.arc(this.position.x - Math.cos(this.angle) * (25 * size - 3) + (Math.random() - 0.5) * 4, this.position.y - Math.sin(this.angle) * (25 * size - 3) + (Math.random() - 0.5) * 4, 11 * size, 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) * (30 * size - 3) + (Math.random() - 0.5) * 4, this.position.y - Math.sin(this.angle) * (30 * size - 3) + (Math.random() - 0.5) * 4, 2 + 9 * size, 0, 2 * Math.PI); ctx.fillStyle = "rgba(255,155,0,0.5)"; ctx.fill(); } }, }); const thrust = 0.0044 * bullet[me].mass; Matter.Body.setVelocity(bullet[me], { x: mech.Vx / 2 + speed * Math.cos(angle), y: mech.Vy / 2 + speed * Math.sin(angle) }); World.add(engine.world, bullet[me]); //add bullet to world }, laser(where = { x: mech.pos.x + 20 * Math.cos(mech.angle), y: mech.pos.y + 20 * Math.sin(mech.angle) }, whereEnd = { x: where.x + 3000 * Math.cos(mech.angle), y: where.y + 3000 * Math.sin(mech.angle) }, dmg = mod.laserDamage, reflections = mod.laserReflections, isThickBeam = false) { const reflectivity = 1 - 1 / (reflections * 1.5) let damage = b.dmgScale * dmg let best = { x: null, y: null, dist2: Infinity, who: null, v1: null, v2: null }; const color = "#f00"; const path = [{ x: where.x, y: where.y }, { x: whereEnd.x, y: whereEnd.y } ]; 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(); game.drawList.push({ //add dmg to draw queue x: path[path.length - 1].x, y: path[path.length - 1].y, radius: Math.sqrt(damage) * 100, color: "rgba(255,0,0,0.5)", time: game.drawTime }); } // 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, 3000), 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 < reflections; 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 } } } if (isThickBeam) { for (let i = 1, len = path.length; i < len; ++i) { ctx.moveTo(path[i - 1].x, path[i - 1].y); ctx.lineTo(path[i].x, path[i].y); } } else { 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; } }, mine(where, velocity, angle = 0, isAmmoBack = false) { const bIndex = bullet.length; bullet[bIndex] = Bodies.rectangle(where.x, where.y, 45, 16, { angle: angle, friction: 1, frictionStatic: 1, frictionAir: 0, restitution: 0, dmg: 0, //damage done in addition to the damage from momentum classType: "bullet", bulletType: "mine", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield | cat.bullet }, minDmgSpeed: 5, stillCount: 0, isArmed: false, endCycle: Infinity, lookFrequency: 0, range: 700, beforeDmg() {}, 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 = 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) { //touching map 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 this.collisionFilter.mask = cat.map | cat.bullet } else { Matter.Body.setVelocity(this, { x: 0, y: 0 }); Matter.Body.setAngularVelocity(this, 0) } if (mod.isMineSentry) { this.sentry(); } else { 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 || Matter.Query.point(map, that.position).length > 0) { // console.log(that) 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))) } break } } } else { if (this.speed < 1 && this.angularSpeed < 0.01 && !mech.isBodiesAsleep) { this.stillCount++ } } if (this.stillCount > 25) { if (mod.isMineSentry) { this.sentry(); } else { this.arm(); } } }, sentry() { this.lookFrequency = game.cycle + 60 this.endCycle = game.cycle + 1080 this.do = function() { //overwrite the do method for this bullet this.force.y += this.mass * 0.002; //extra gravity if (game.cycle > this.lookFrequency) { this.lookFrequency = 10 + Math.floor(3 * Math.random()) this.do = function() { //overwrite the do method for this bullet this.force.y += this.mass * 0.002; //extra gravity if (!(game.cycle % this.lookFrequency) && !mech.isBodiesAsleep) { //find mob targets this.endCycle -= 10 b.targetedNail(this.position, 1, 45 + 5 * Math.random(), 1100, false) if (!(game.cycle % (this.lookFrequency * 6))) { game.drawList.push({ x: this.position.x, y: this.position.y, radius: 8, color: "#fe0", time: 4 }); } } } } } }, arm() { this.lookFrequency = game.cycle + 60 this.do = function() { //overwrite the do method for this bullet this.force.y += this.mass * 0.002; //extra gravity if (game.cycle > this.lookFrequency) { this.isArmed = true this.lookFrequency = 50 + Math.floor(27 * Math.random()) game.drawList.push({ 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 if (Math.random() < 0.8) isAmmoBack = false; //20% chance to get ammo back after detonation } } } } } } }, onEnd() { if (this.isArmed) { b.targetedNail(this.position, 15) } if (isAmmoBack) { //get ammo back from mod.isMineAmmoBack for (i = 0, len = b.guns.length; i < len; i++) { //find which gun if (b.guns[i].name === "mine") { b.guns[i].ammo++ game.updateGunHUD(); break; } } } } }); 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(where, isFreeze = mod.isSporeFreeze) { //used with the mod upgrade in mob.death() const bIndex = bullet.length; const side = 4; bullet[bIndex] = Bodies.polygon(where.x, where.y, 4, side, { // density: 0.0015, //frictionAir: 0.01, inertia: Infinity, isFreeze: isFreeze, restitution: 0.5, angle: Math.random() * 2 * Math.PI, friction: 0, frictionAir: 0.025, thrust: (mod.isFastSpores ? 0.001 : 0.0004) * (1 + 0.3 * (Math.random() - 0.5)), dmg: mod.isMutualism ? 6 : 3, //2x bonus damage from mod.isMutualism lookFrequency: 97 + Math.floor(117 * Math.random()), classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.mob | cat.mobBullet | cat.mobShield //no collide with body }, endCycle: game.cycle + Math.floor((540 + Math.floor(Math.random() * 360)) * mod.isBulletsLastLonger), minDmgSpeed: 0, playerOffPosition: { //used when following player to keep spores separate x: 100 * (Math.random() - 0.5), y: 100 * (Math.random() - 0.5) }, beforeDmg(who) { this.endCycle = 0; //bullet ends cycle after doing damage if (this.isFreeze) mobs.statusSlow(who, 60) }, onEnd() { if (mod.isMutualism && this.isMutualismActive && !mod.isEnergyHealth) { mech.health += 0.01 if (mech.health > mech.maxHealth) mech.health = mech.maxHealth; mech.displayHealth(); } }, do() { if (this.lockedOn && this.lockedOn.alive) { this.force = Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), this.mass * this.thrust) } else { if (!(game.cycle % this.lookFrequency)) { //find mob targets 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) { const targetVector = Vector.sub(this.position, mob[i].position) const dist = Vector.magnitude(targetVector) * (Math.random() + 0.5); if (dist < closeDist) { this.closestTarget = mob[i].position; closeDist = dist; this.lockedOn = mob[i] if (0.3 > Math.random()) break //doesn't always target the closest mob } } } } if (mod.isSporeFollow && this.lockedOn === null) { //move towards player //checking for null means that the spores don't go after the player until it has looked and not found a target const dx = this.position.x - mech.pos.x; const dy = this.position.y - mech.pos.y; if (dx * dx + dy * dy > 10000) { this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, Vector.add(this.playerOffPosition, this.position))), this.mass * this.thrust) } } else { this.force.y += this.mass * 0.0001; //gravity } } // if (!this.lockedOn && !(game.cycle % this.lookFrequency)) { //find mob targets // 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 // } // } // } // } // if (this.lockedOn && this.lockedOn.alive) { //accelerate towards mobs // this.force = Vector.mult(Vector.normalise(Vector.sub(this.lockedOn.position, this.position)), this.mass * this.thrust) // } else if (mod.isSporeFollow && this.lockedOn !== undefined) { //move towards player // //checking for undefined means that the spores don't go after the player until it has looked and not found a target // const dx = this.position.x - mech.pos.x; // const dy = this.position.y - mech.pos.y; // if (dx * dx + dy * dy > 10000) { // this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, Vector.add(this.playerOffPosition, this.position))), this.mass * this.thrust) // } // // this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.thrust) // } 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 if (mod.isMutualism && mech.health > 0.02) { mech.health -= 0.01 mech.displayHealth(); bullet[bIndex].isMutualismActive = true } }, iceIX(speed = 0, spread = 2 * Math.PI) { const me = bullet.length; const THRUST = 0.004 const dir = mech.angle + spread * (Math.random() - 0.5); const RADIUS = 18 bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle), mech.pos.y + 30 * Math.sin(mech.angle), 3, RADIUS, { angle: dir - Math.PI, inertia: Infinity, friction: 0, frictionAir: 0.10, restitution: 0.3, dmg: 0.15, //damage done in addition to the damage from momentum lookFrequency: 10 + Math.floor(7 * Math.random()), endCycle: game.cycle + 120 * mod.isBulletsLastLonger, //Math.floor((1200 + 420 * Math.random()) * mod.isBulletsLastLonger), classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield //self collide }, minDmgSpeed: 0, lockedOn: null, isFollowMouse: true, beforeDmg(who) { mobs.statusSlow(who, 60) this.endCycle = game.cycle if (mod.isHeavyWater) mobs.statusDoT(who, 0.15, 300) if (mod.iceEnergy && !who.shield && !who.isShielded && who.dropPowerUp && who.alive) { setTimeout(function() { if (!who.alive) { mech.energy += mod.iceEnergy * 0.66 * mech.maxEnergy mech.addHealth(mod.iceEnergy * 0.04) } }, 10); } }, onEnd() {}, do() { // this.force.y += this.mass * 0.0002; //find mob targets if (!(game.cycle % this.lookFrequency)) { const scale = 1 - 0.09 / mod.isBulletsLastLonger //0.9 * mod.isBulletsLastLonger; Matter.Body.scale(this, scale, scale); 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) { //accelerate towards mobs this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, this.lockedOn.position)), -this.mass * THRUST) } else { this.force = Vector.mult(Vector.normalise(this.velocity), this.mass * THRUST) } } }) World.add(engine.world, bullet[me]); //add bullet to world // Matter.Body.setAngularVelocity(bullet[me], 2 * (0.5 - Math.random())) //doesn't work due to high friction Matter.Body.setVelocity(bullet[me], { x: speed * Math.cos(dir), y: speed * Math.sin(dir) }); // Matter.Body.setVelocity(bullet[me], { // x: mech.Vx / 2 + speed * Math.cos(dir), // y: mech.Vy / 2 + speed * Math.sin(dir) // }); }, drone(speed = 1) { const me = bullet.length; const THRUST = mod.isFastDrones ? 0.0023 : 0.0015 // const FRICTION = mod.isFastDrones ? 0.008 : 0.0005 const dir = mech.angle + 0.4 * (Math.random() - 0.5); const RADIUS = (4.5 + 3 * Math.random()) bullet[me] = Bodies.polygon(mech.pos.x + 30 * Math.cos(mech.angle) + Math.random(), mech.pos.y + 30 * Math.sin(mech.angle) + Math.random(), 8, RADIUS, { angle: dir, inertia: Infinity, friction: 0.05, frictionAir: 0, restitution: 1, dmg: 0.24, //damage done in addition to the damage from momentum lookFrequency: 80 + Math.floor(23 * Math.random()), endCycle: game.cycle + Math.floor((1100 + 420 * Math.random()) * mod.isBulletsLastLonger), 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, deathCycles: 110 + RADIUS * 5, isImproved: false, beforeDmg(who) { //move away from target after hitting const unit = Vector.mult(Vector.normalise(Vector.sub(this.position, who.position)), -20) Matter.Body.setVelocity(this, { x: unit.x, y: unit.y }); this.lockedOn = null if (this.endCycle > game.cycle + this.deathCycles) { this.endCycle -= 60 if (game.cycle + this.deathCycles > this.endCycle) this.endCycle = game.cycle + this.deathCycles } }, onEnd() {}, do() { if (game.cycle + this.deathCycles > this.endCycle) { //fall shrink and die this.force.y += this.mass * 0.0012; this.restitution = 0.2; const scale = 0.99; Matter.Body.scale(this, scale, scale); } 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 && !mod.isArmorFromPowerUps && !this.isImproved) { //grab a power up let closeDist = Infinity; for (let i = 0, len = powerUp.length; i < len; ++i) { if ( (powerUp[i].name !== "heal" || mech.health < 0.9 * mech.maxHealth || mod.isDroneGrab) && (powerUp[i].name !== "field" || !mod.isDeterminism) ) { //pick up nearby power ups if (Vector.magnitudeSquared(Vector.sub(this.position, powerUp[i].position)) < 60000 && !game.isChoosing) { powerUps.onPickUp(this.position); powerUp[i].effect(); Matter.World.remove(engine.world, powerUp[i]); powerUp.splice(i, 1); if (mod.isDroneGrab) { this.isImproved = true; const SCALE = 3 Matter.Body.scale(this, SCALE, SCALE); this.lookFrequency = 30; this.endCycle += 2500 this.frictionAir = 0 } break; } //look for power ups to lock onto if ( 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) { 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) }); }, foam(position, velocity, radius) { // radius *= Math.sqrt(mod.bulletSize) const me = bullet.length; bullet[me] = Bodies.polygon(position.x, position.y, 20, radius, { // angle: 0, density: 0.00005, // 0.001 is normal density inertia: Infinity, frictionAir: 0.003, // friction: 0.2, // restitution: 0.2, dmg: mod.isFastFoam ? 0.02 : 0.0055, //damage done in addition to the damage from momentum scale: 1 - 0.005 / mod.isBulletsLastLonger * (mod.isFastFoam ? 1.6 : 1), classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.mob | cat.mobBullet // cat.map | cat.body | cat.mob | cat.mobShield }, minDmgSpeed: 0, endCycle: Infinity, count: 0, radius: radius, target: null, targetVertex: null, targetRelativePosition: null, beforeDmg(who) { if (!this.target && who.alive) { this.target = who; if (who.radius < 20) { this.targetRelativePosition = { x: 0, y: 0 } //find relative position vector for zero mob rotation } else if (Matter.Query.collides(this, [who]).length > 0) { const normal = Matter.Query.collides(this, [who])[0].normal this.targetRelativePosition = Vector.rotate(Vector.sub(Vector.sub(this.position, who.position), Vector.mult(normal, -this.radius)), -who.angle) //find relative position vector for zero mob rotation } else { this.targetRelativePosition = Vector.rotate(Vector.sub(this.position, who.position), -who.angle) //find relative position vector for zero mob rotation } 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() { if (!mech.isBodiesAsleep) { //if time dilation isn't active if (this.count < 20) { this.count++ //grow const SCALE = 1.06 Matter.Body.scale(this, SCALE, SCALE); this.radius *= SCALE; } else { //shrink Matter.Body.scale(this, this.scale, this.scale); this.radius *= this.scale; if (this.radius < 8) this.endCycle = 0; } if (this.target && this.target.alive) { //if stuck to a target const rotate = Vector.rotate(this.targetRelativePosition, this.target.angle) //add in the mob's new angle to the relative position vector if (this.target.isVerticesChange) { Matter.Body.setPosition(this, this.target.vertices[this.targetVertex]) } else { Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.target.velocity), this.target.position)) } Matter.Body.setVelocity(this.target, Vector.mult(this.target.velocity, 0.9)) Matter.Body.setAngularVelocity(this.target, this.target.angularVelocity * 0.9); // Matter.Body.setAngularVelocity(this.target, this.target.angularVelocity * 0.9) if (this.target.isShielded) { this.target.damage(b.dmgScale * this.dmg, true); //shield damage bypass //shrink if mob is shielded const SCALE = 1 - 0.018 / mod.isBulletsLastLonger Matter.Body.scale(this, SCALE, SCALE); this.radius *= SCALE; } else { this.target.damage(b.dmgScale * this.dmg); } } else if (this.target !== null) { //look for a new target this.target = null this.collisionFilter.category = cat.bullet; this.collisionFilter.mask = cat.mob //| cat.mobShield //cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield if (mod.isFoamGrowOnDeath && bullet.length < 300) { let targets = [] for (let i = 0, len = mob.length; i < len; i++) { const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); if (dist < 1000000) { targets.push(mob[i]) } } const radius = Math.min(this.radius * 0.5, 10) for (let i = 0; i < 2; i++) { if (targets.length - i > 0) { const index = Math.floor(Math.random() * targets.length) const speed = 10 + 10 * Math.random() const velocity = Vector.mult(Vector.normalise(Vector.sub(targets[index].position, this.position)), speed) b.foam(this.position, Vector.rotate(velocity, 0.5 * (Math.random() - 0.5)), radius) } else { b.foam(this.position, Vector.rotate({ x: 15 + 10 * Math.random(), y: 0 }, 2 * Math.PI * Math.random()), radius) } } } } else if (Matter.Query.point(map, this.position).length > 0) { //slow when touching map or blocks const slow = 0.85 Matter.Body.setVelocity(this, { x: this.velocity.x * slow, y: this.velocity.y * slow }); const SCALE = 0.96 Matter.Body.scale(this, SCALE, SCALE); this.radius *= SCALE; // } else if (Matter.Query.collides(this, body).length > 0) { } else if (Matter.Query.point(body, this.position).length > 0) { const slow = 0.9 Matter.Body.setVelocity(this, { x: this.velocity.x * slow, y: this.velocity.y * slow }); const SCALE = 0.96 Matter.Body.scale(this, SCALE, SCALE); this.radius *= SCALE; } else { this.force.y += this.mass * 0.00008; //gravity } } } }); World.add(engine.world, bullet[me]); //add bullet to world Matter.Body.setVelocity(bullet[me], velocity); }, targetedNail(position, num = 1, speed = 50 + 10 * Math.random(), range = 1200, isRandomAim = true) { const targets = [] //target nearby mobs for (let i = 0, len = mob.length; i < len; i++) { if (mob[i].dropPowerUp) { const dist = Vector.magnitude(Vector.sub(position, mob[i].position)); if (dist < range && Matter.Query.ray(map, position, mob[i].position).length === 0 && Matter.Query.ray(body, position, mob[i].position).length === 0) { targets.push(Vector.add(mob[i].position, Vector.mult(mob[i].velocity, dist / 60))) //predict where the mob will be in a few cycles } } } for (let i = 0; i < num; i++) { 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(position, Vector.mult(Vector.normalise(Vector.sub(WHERE, position)), speed), 1.1) } else if (isRandomAim) { // aim in random direction const ANGLE = 2 * Math.PI * Math.random() b.nail(position, { x: speed * Math.cos(ANGLE), y: speed * Math.sin(ANGLE) }) } } }, nail(pos, velocity, dmg = 0) { const me = bullet.length; bullet[me] = Bodies.rectangle(pos.x, pos.y, 25 * mod.biggerNails, 2 * mod.biggerNails, 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].beforeDmg = function(who) { //beforeDmg is rewritten with ice crystal mod if (mod.isNailPoison) mobs.statusDoT(who, dmg * 0.22, 120) // one tick every 30 cycles if (mod.isNailCrit && !who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.99) this.dmg *= 5 //crit if hit near center }; bullet[me].do = function() {}; }, // ************************************************************************************************** // ************************************************************************************************** // ******************************** Bots ********************************************* // ************************************************************************************************** // ************************************************************************************************** respawnBots() { for (let i = 0; i < mod.laserBotCount; i++) b.laserBot() for (let i = 0; i < mod.nailBotCount; i++) b.nailBot() for (let i = 0; i < mod.foamBotCount; i++) b.foamBot() for (let i = 0; i < mod.boomBotCount; i++) b.boomBot() for (let i = 0; i < mod.plasmaBotCount; i++) b.plasmaBot() for (let i = 0; i < mod.orbitBotCount; i++) b.orbitBot() if (mod.isIntangible && mech.isCloak) { for (let i = 0; i < bullet.length; i++) { if (bullet[i].botType) bullet[i].collisionFilter.mask = cat.map | cat.bullet | cat.mobBullet | cat.mobShield } } }, randomBot(where = mech.pos, isKeep = true) { if (Math.random() < 0.2) { b.orbitBot(); if (isKeep) mod.orbitBotCount++; } else if (Math.random() < 0.25) { b.nailBot(where) if (isKeep) mod.nailBotCount++; } else if (Math.random() < 0.33) { b.laserBot(where) if (isKeep) mod.laserBotCount++; } else if (Math.random() < 0.5) { b.foamBot(where) if (isKeep) mod.foamBotCount++; } else { b.boomBot(where) if (isKeep) mod.boomBotCount++; } }, nailBot(position = mech.pos) { const me = bullet.length; const dir = mech.angle; const RADIUS = (12 + 4 * Math.random()) bullet[me] = Bodies.polygon(position.x, position.y, 4, RADIUS, { isUpgraded: mod.isNailBotUpgrade, botType: "nail", 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()) - isUpgraded * 20, lastLookCycle: game.cycle + 60 * 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, beforeDmg() { this.lockedOn = null }, onEnd() {}, do() { if (this.lastLookCycle < game.cycle && !mech.isCloak) { this.lastLookCycle = game.cycle + 80 - this.isUpgraded * 65 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) } else { //close to player Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity } } }) World.add(engine.world, bullet[me]); //add bullet to world }, foamBot(position = mech.pos) { const me = bullet.length; const dir = mech.angle; const RADIUS = (10 + 5 * Math.random()) bullet[me] = Bodies.polygon(position.x, position.y, 6, RADIUS, { isUpgraded: mod.isFoamBotUpgrade, botType: "foam", 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: 60 + Math.floor(17 * Math.random()) - 10 * mod.isFoamBotUpgrade, cd: 0, delay: 100, 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, beforeDmg() { this.lockedOn = null }, onEnd() {}, do() { if (this.cd < game.cycle && !(game.cycle % this.lookFrequency) && !mech.isCloak) { 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 < 1000000 && Matter.Query.ray(map, this.position, mob[i].position).length === 0) { this.cd = game.cycle + this.delay; target = Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist) / 60)) const radius = 6 + 7 * Math.random() const SPEED = 29 - radius * 0.5; //(mech.crouch ? 32 : 20) - radius * 0.7; const velocity = Vector.mult(Vector.normalise(Vector.sub(target, this.position)), SPEED) b.foam(this.position, velocity, radius + 9 * this.isUpgraded) 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) } else { //close to player Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity } } }) World.add(engine.world, bullet[me]); //add bullet to world }, laserBot(position = mech.pos) { const me = bullet.length; const dir = mech.angle; const RADIUS = (14 + 6 * Math.random()) bullet[me] = Bodies.polygon(position.x, position.y, 3, RADIUS, { isUpgraded: mod.isLaserBotUpgrade, botType: "laser", 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: 40 + Math.floor(7 * Math.random()), acceleration: 0.0015 * (1 + 0.3 * Math.random()), range: 700 * (1 + 0.1 * Math.random()) + 250 * mod.isLaserBotUpgrade, 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, beforeDmg() { this.lockedOn = null }, onEnd() {}, do() { 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)) { this.lockedOn = null; if (!mech.isCloak) { 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: 120 * (Math.random() - 0.5), y: 120 * (Math.random() - 0.5) - 20, } } } //hit target with laser if (this.lockedOn && this.lockedOn.alive && mech.energy > 0.15) { mech.energy -= 0.0012 * mod.isLaserDiode // const sub = Vector.sub(this.lockedOn.position, this.vertices[0]) // const angle = Math.atan2(sub.y, sub.x); b.laser(this.vertices[0], this.lockedOn.position, b.dmgScale * (0.06 + 0.1 * this.isUpgraded)) // //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.06 + 0.08 * this.isUpgraded); // 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 }, boomBot(position = mech.pos) { const me = bullet.length; const dir = mech.angle; const RADIUS = (7 + 2 * Math.random()) bullet[me] = Bodies.polygon(position.x, position.y, 4, RADIUS, { isUpgraded: mod.isBoomBotUpgrade, botType: "boom", angle: dir, friction: 0, frictionStatic: 0, frictionAir: 0.05, restitution: 1, dmg: 0, minDmgSpeed: 0, lookFrequency: 43 + Math.floor(7 * Math.random()), acceleration: 0.005 * (1 + 0.5 * Math.random()), range: 500 * (1 + 0.1 * Math.random()) + 250 * mod.isBoomBotUpgrade, endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield }, lockedOn: null, explode: 0, beforeDmg() { if (this.lockedOn) { const explosionRadius = Math.min(170 + 170 * this.isUpgraded, Vector.magnitude(Vector.sub(this.position, mech.pos)) - 30) if (explosionRadius > 60) { this.explode = explosionRadius // //push away from player, because normal explosion knock doesn't do much // const sub = Vector.sub(this.lockedOn.position, mech.pos) // mag = Math.min(35, 20 / Math.sqrt(this.lockedOn.mass)) // Matter.Body.setVelocity(this.lockedOn, Vector.mult(Vector.normalise(sub), mag)) } this.lockedOn = null //lose target so bot returns to player } }, onEnd() {}, do() { if (this.explode) { b.explosion(this.position, this.explode); //makes bullet do explosive damage at end this.explode = 0; } const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) if (distanceToPlayer > 100) { //if far away move towards player this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) } else if (distanceToPlayer < 250) { //close to player Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity //find targets if (!(game.cycle % this.lookFrequency) && !mech.isCloak) { this.lockedOn = null; let closeDist = this.range; for (let i = 0, len = mob.length; i < len; ++i) { const DIST = Vector.magnitude(Vector.sub(this.position, mob[i].position)) - mob[i].radius; if (DIST < closeDist && 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) { closeDist = DIST; this.lockedOn = mob[i] } } } } //punch target if (this.lockedOn && this.lockedOn.alive && !mech.isCloak) { const DIST = Vector.magnitude(Vector.sub(this.vertices[0], this.lockedOn.position)); if (DIST - this.lockedOn.radius < this.range && Matter.Query.ray(map, this.position, 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.012 * this.mass)) } } } }) World.add(engine.world, bullet[me]); //add bullet to world }, plasmaBot(position = mech.pos) { const me = bullet.length; const dir = mech.angle; const RADIUS = 21 bullet[me] = Bodies.polygon(position.x, position.y, 5, RADIUS, { botType: "plasma", angle: dir, friction: 0, frictionStatic: 0, frictionAir: 0.05, restitution: 1, dmg: 0, // 0.14 //damage done in addition to the damage from momentum minDmgSpeed: 2, lookFrequency: 25, cd: 0, acceleration: 0.009, endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield }, lockedOn: null, beforeDmg() { this.lockedOn = null }, onEnd() {}, do() { const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, mech.pos)) if (distanceToPlayer > 150) { //if far away move towards player this.force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, this.position)), this.mass * this.acceleration) } Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity //find closest if (!(game.cycle % this.lookFrequency)) { this.lockedOn = null; if (!mech.isCloak) { let closeDist = mod.isPlasmaRange * 1000; for (let i = 0, len = mob.length; i < len; ++i) { const DIST = Vector.magnitude(Vector.sub(this.position, mob[i].position)) - mob[i].radius; if (DIST < closeDist && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0) { closeDist = DIST; this.lockedOn = mob[i] } } } } //fire plasma at target if (this.lockedOn && this.lockedOn.alive && mech.fieldCDcycle < mech.cycle) { const sub = Vector.sub(this.lockedOn.position, this.position) const DIST = Vector.magnitude(sub); const unit = Vector.normalise(sub) const DRAIN = 0.0012 if (DIST < mod.isPlasmaRange * 450 && mech.energy > DRAIN) { mech.energy -= DRAIN; if (mech.energy < 0) { mech.fieldCDcycle = mech.cycle + 120; mech.energy = 0; } //calculate laser collision let best; let range = mod.isPlasmaRange * (120 + 300 * Math.sqrt(Math.random())) const path = [{ x: this.position.x, y: this.position.y }, { x: this.position.x + range * unit.x, y: this.position.y + range * unit.y } ]; 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 }; if (best.who.alive) { const dmg = 0.8 * b.dmgScale; //********** SCALE DAMAGE HERE ********************* best.who.damage(dmg); best.who.locatePlayer(); //push mobs away const force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, path[1])), -0.01 * Math.min(5, best.who.mass)) Matter.Body.applyForce(best.who, path[1], force) Matter.Body.setVelocity(best.who, { //friction x: best.who.velocity.x * 0.7, y: best.who.velocity.y * 0.7 }); //draw mob damage circle game.drawList.push({ x: path[1].x, y: path[1].y, radius: Math.sqrt(dmg) * 50, color: "rgba(255,0,255,0.2)", time: game.drawTime * 4 }); } else if (!best.who.isStatic) { //push blocks away const force = Vector.mult(Vector.normalise(Vector.sub(mech.pos, path[1])), -0.007 * Math.sqrt(Math.sqrt(best.who.mass))) Matter.Body.applyForce(best.who, path[1], force) } } //draw blowtorch laser beam ctx.strokeStyle = "rgba(255,0,255,0.1)" ctx.lineWidth = 14 ctx.beginPath(); ctx.moveTo(path[0].x, path[0].y); ctx.lineTo(path[1].x, path[1].y); ctx.stroke(); ctx.strokeStyle = "#f0f"; ctx.lineWidth = 2 ctx.stroke(); //draw electricity let x = this.position.x + 20 * unit.x; let y = this.position.y + 20 * unit.y; ctx.beginPath(); ctx.moveTo(x, y); const step = Vector.magnitude(Vector.sub(path[0], path[1])) / 5 for (let i = 0; i < 4; i++) { x += step * (unit.x + 1.5 * (Math.random() - 0.5)) y += step * (unit.y + 1.5 * (Math.random() - 0.5)) ctx.lineTo(x, y); } ctx.lineWidth = 2 * Math.random(); ctx.stroke(); } } } }) World.add(engine.world, bullet[me]); //add bullet to world }, orbitBot(position = mech.pos) { const me = bullet.length; bullet[me] = Bodies.polygon(position.x, position.y, 9, 12, { isUpgraded: mod.isOrbitBotUpgrade, botType: "orbit", friction: 0, frictionStatic: 0, frictionAir: 1, isStatic: true, isSensor: true, restitution: 0, dmg: 0, // 0.14 //damage done in addition to the damage from momentum minDmgSpeed: 0, endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: 0 //cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield }, beforeDmg() {}, onEnd() { //reorder orbital bot positions around a circle let totalOrbitalBots = 0 for (let i = 0; i < bullet.length; i++) { if (bullet[i].botType === 'orbit' && bullet[i] !== this) totalOrbitalBots++ } let index = 0 for (let i = 0; i < bullet.length; i++) { if (bullet[i].botType === 'orbit' && bullet[i] !== this) { bullet[i].phase = (index / totalOrbitalBots) * 2 * Math.PI index++ } } }, range: 190 + 60 * mod.isOrbitBotUpgrade, //range is set in bot upgrade too! //150 + (80 + 100 * mod.isOrbitBotUpgrade) * Math.random(), // + 5 * mod.orbitBotCount, orbitalSpeed: 0, phase: 2 * Math.PI * Math.random(), do() { //check for damage if (!mech.isCloak && !mech.isBodiesAsleep) { //if time dilation isn't active // q = Matter.Query.point(mob, this.position) // q = Matter.Query.collides(this, mob) const size = 33 q = Matter.Query.region(mob, { min: { x: this.position.x - size, y: this.position.y - size }, max: { x: this.position.x + size, y: this.position.y + size } }) for (let i = 0; i < q.length; i++) { mobs.statusStun(q[i], 180) const dmg = 0.5 * b.dmgScale * (this.isUpgraded ? 2 : 1) * (mod.isCrit ? 4 : 1) q[i].damage(dmg); q[i].foundPlayer(); 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 }); } } //orbit player const time = game.cycle * this.orbitalSpeed + this.phase const orbit = { x: Math.cos(time), y: Math.sin(time) //*1.1 } Matter.Body.setPosition(this, Vector.add(mech.pos, Vector.mult(orbit, this.range))) //bullets move with player } }) // bullet[me].orbitalSpeed = Math.sqrt(0.7 / bullet[me].range) bullet[me].orbitalSpeed = Math.sqrt(0.25 / bullet[me].range) //also set in bot upgrade too! // bullet[me].phase = (index / mod.orbitBotCount) * 2 * Math.PI World.add(engine.world, bullet[me]); //add bullet to world //reorder orbital bot positions around a circle let totalOrbitalBots = 0 for (let i = 0; i < bullet.length; i++) { if (bullet[i].botType === 'orbit') totalOrbitalBots++ } let index = 0 for (let i = 0; i < bullet.length; i++) { if (bullet[i].botType === 'orbit') { bullet[i].phase = (index / totalOrbitalBots) * 2 * Math.PI index++ } } }, // ************************************************************************************************** // ************************************************************************************************** // ******************************** Guns ********************************************* // ************************************************************************************************** // ************************************************************************************************** giveGuns(gun = "random", ammoPacks = 10) { if (mod.isOneGun) b.removeAllGuns(); 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 let found = false; for (let i = 0; i < b.guns.length; i++) { if (gun === b.guns[i].name) { gun = i found = true; break } } if (!found) return //if no gun found don't give a gun } if (!b.guns[gun].have) b.inventory.push(gun); b.guns[gun].have = true; b.guns[gun].ammo = Math.floor(b.guns[gun].ammoPack * ammoPacks); if (b.activeGun === null) b.activeGun = gun //if no active gun switch to new gun } game.makeGunHUD(); }, guns: [{ name: "nail gun", description: "use compressed air to fire a stream of nails