let bullet = []; const b = { // dmgScale: null, //scales all damage, 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 setFireMethod() { if (tech.isFireMoveLock) { b.fire = b.fireFloat // } else if (tech.isFireNotMove) { // if (tech.isAlwaysFire) { // b.fire = b.fireAlwaysFire // } else { // b.fire = b.fireNotMove // } } else if (tech.isAlwaysFire) { b.fire = b.fireAlwaysFire } else { b.fire = b.fireNormal } }, fire() { }, fireNormal() { if (b.inventory.length && b.activeGun !== null) { if (input.fire && m.fireCDcycle < m.cycle && (!input.field || m.fieldFire)) { if (b.guns[b.activeGun].ammo > 0) { b.fireWithAmmo() } else { b.outOfAmmo() } if (m.holdingTarget) m.drop(); } b.guns[b.activeGun].do(); } }, fireNotMove() { //added && player.speed < 0.5 && m.onGround if (b.inventory.length && b.activeGun !== null) { if (input.fire && m.fireCDcycle < m.cycle && (!input.field || m.fieldFire) && player.speed < 2.5 && m.onGround && Math.abs(m.yOff - m.yOffGoal) < 1) { if (b.guns[b.activeGun].ammo > 0) { b.fireWithAmmo() } else { b.outOfAmmo() } if (m.holdingTarget) m.drop(); } b.guns[b.activeGun].do(); } }, fireAlwaysFire() { //added && player.speed < 0.5 && m.onGround //removed input.fire && (!input.field || m.fieldFire) if (b.inventory.length && b.activeGun !== null) { if (m.fireCDcycle < m.cycle && player.speed < 0.5 && m.onGround && Math.abs(m.yOff - m.yOffGoal) < 1) { if (b.guns[b.activeGun].ammo > 0) { b.fireWithAmmo() } if (m.holdingTarget) m.drop(); } b.guns[b.activeGun].do(); } }, fireFloat() { //added && player.speed < 0.5 && m.onGround if (b.inventory.length && b.activeGun !== null) { if (input.fire && (!input.field || m.fieldFire)) { if (m.fireCDcycle < m.cycle) { if (b.guns[b.activeGun].ammo > 0) { b.fireWithAmmo() } else { b.outOfAmmo() } if (m.holdingTarget) m.drop(); } Matter.Body.setVelocity(player, { x: 0, y: -55 * player.mass * simulation.g //undo gravity before it is added }); player.force.x = 0 player.force.y = 0 } b.guns[b.activeGun].do(); } }, fireWithAmmo() { //triggers after firing when you have ammo b.guns[b.activeGun].fire(); if (tech.crouchAmmoCount && m.crouch) { if (tech.crouchAmmoCount % 2) { b.guns[b.activeGun].ammo--; simulation.updateGunHUD(); } tech.crouchAmmoCount++ //makes the no ammo toggle off and on } else { b.guns[b.activeGun].ammo--; simulation.updateGunHUD(); } }, outOfAmmo() { //triggers after firing when you have NO ammo simulation.makeTextLog(`${b.guns[b.activeGun].name}.ammo: 0`); m.fireCDcycle = m.cycle + 30; //fire cooldown if (tech.isAmmoFromHealth) { const amount = 0.02 if (tech.isEnergyHealth) { if (m.maxEnergy > amount * 2) { tech.healMaxEnergyBonus -= amount * 2 m.setMaxEnergy(); for (let i = 0; i < 4; i++) powerUps.spawn(m.pos.x + 50 * (Math.random() - 0.5), m.pos.y + 50 * (Math.random() - 0.5), "ammo"); } } else { if (m.health > amount) { tech.extraMaxHealth -= amount //decrease max health m.setMaxHealth(); for (let i = 0; i < 4; i++) powerUps.spawn(m.pos.x + 50 * (Math.random() - 0.5), m.pos.y + 50 * (Math.random() - 0.5), "ammo"); } } } }, refundAmmo() { //triggers after firing when you removed ammo for a gun, but didn't need to if (tech.crouchAmmoCount && m.crouch && b.activeGun !== null) { tech.crouchAmmoCount-- if ((tech.crouchAmmoCount) % 2) { b.guns[b.activeGun].ammo++; simulation.updateGunHUD(); } } else { b.guns[b.activeGun].ammo++; simulation.updateGunHUD(); } }, // returnGunAmmo(name) { // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun // if (b.guns[i].name === name) return b.guns[i].ammo // } // }, giveGuns(gun = "random", ammoPacks = 10) { if (tech.ammoCap) ammoPacks = 0.45 * tech.ammoCap if (tech.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") { for (let i = 0; i < b.guns.length; i++) { b.inventory[i] = i; b.guns[i].have = true; if (b.guns[i].ammo !== Infinity) b.guns[i].ammo = Math.ceil(b.guns[i].ammoPack * ammoPacks); } b.inventoryGun = 0; b.activeGun = b.inventory[0]; } 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; if (b.guns[gun].ammo !== Infinity) b.guns[gun].ammo = Math.ceil(b.guns[gun].ammoPack * ammoPacks); if (b.activeGun === null) { b.inventoryGun = 0; b.activeGun = b.inventory[0] //if no active gun switch to new gun if (b.guns[b.activeGun].charge) b.guns[b.activeGun].charge = 0; //set foam charge to zero if foam is a new gun } } simulation.makeGunHUD(); simulation.switchGun(); b.setFireCD(); if (tech.isOneGun && b.inventory > 0) { //count how many gun tech you have and remove them let gunTechCount = 0 //2 bonus gun tech for (let i = 0, len = tech.tech.length; i < len; i++) { if (tech.tech[i].isGunTech && tech.tech[i].count > 0 && !tech.tech[i].isNonRefundable && !tech.tech[i].isRemoveGun) { const remove = tech.removeTech(i) gunTechCount += remove } } //get a random gun tech for your gun for (let i = 0; i < gunTechCount; i++) { const gunTechPool = [] for (let j = 0, len = tech.tech.length; j < len; j++) { if (tech.tech[j].isGunTech && tech.tech[j].allowed() && !tech.tech[i].isRemoveGun && !tech.tech[j].isJunk && !tech.tech[j].isBadRandomOption && tech.tech[j].count < tech.tech[j].maxCount) { const regex = tech.tech[j].requires.search(b.guns[b.activeGun].name) //get string index of gun name const not = tech.tech[j].requires.search(' not ') //get string index of ' not ' //look for the gun name in the requirements, but the gun name needs to show up before the word ' not ' if (regex !== -1 && (not === -1 || not > regex)) gunTechPool.push(j) } } if (gunTechPool.length) { const index = Math.floor(Math.random() * gunTechPool.length) tech.giveTech(gunTechPool[index]) // choose from the gun pool simulation.makeTextLog(`tech.giveTech("${tech.tech[gunTechPool[index]].name}")`) } else { tech.giveTech() //get normal tech if you can't find any gun tech } } } }, removeGun(gunName) { for (let i = 0; i < b.guns.length; i++) { if (b.guns[i].name === gunName && b.guns[i].have) { b.guns[i].have = false for (let j = 0; j < b.inventory.length; j++) { if (b.inventory[j] === i) { b.inventory.splice(j, 1) break } } if (b.inventory.length > 0) { b.activeGun = b.inventory[0]; } else { b.activeGun = null; } b.inventoryGun = 0; simulation.makeGunHUD(); break } } b.setFireCD(); }, 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; } tech.buffedGun = 0 b.activeGun = null; b.inventoryGun = 0; simulation.drawCursor = simulation.drawCursorBasic //set cross hairs }, bulletRemove() { //run in main loop //remove bullet if at end cycle for that bullet let i = bullet.length; while (i--) { if (bullet[i].endCycle < simulation.cycle) { bullet[i].onEnd(i); //some bullets do stuff on end if (bullet[i]) { Matter.Composite.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 = color.bullet; ctx.fill(); }, bulletDo() { for (let i = 0, len = bullet.length; i < len; i++) { bullet[i].do(); } }, fireProps(cd, speed, dir, me) { m.fireCDcycle = m.cycle + Math.floor(cd * b.fireCDscale); // cool down Matter.Body.setVelocity(bullet[me], { x: m.Vx / 2 + speed * Math.cos(dir), y: m.Vy / 2 + speed * Math.sin(dir) }); Composite.add(engine.world, bullet[me]); //add bullet to world }, fireCDscale: 1, setFireCD() { b.fireCDscale = tech.fireRate * tech.slowFire * tech.researchHaste * tech.aimDamage if (m.fieldMode === 6) b.fireCDscale *= 0.8 if (tech.isFastTime) b.fireCDscale *= 0.5 if (tech.isFireRateForGuns) b.fireCDscale *= Math.pow(0.82, Math.max(0, b.inventory.length - 1)) if (tech.isFireMoveLock) b.fireCDscale *= 0.55 }, 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 = 30) { // ctx.fillStyle = "#fb0"; // ctx.beginPath(); // ctx.arc(m.pos.x + 35 * Math.cos(m.angle), m.pos.y + 35 * Math.sin(m.angle), radius, 0, 2 * Math.PI); // ctx.fill(); simulation.drawList.push({ //add dmg to draw queue x: m.pos.x + 20 * Math.cos(m.angle), y: m.pos.y + 20 * Math.sin(m.angle), radius: radius, color: "#fb0", time: 1 }); }, 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(); } } }, explosionRange() { return tech.explosiveRadius * (tech.isExplosionHarm ? 1.7 : 1) * (tech.isSmallExplosion ? 0.66 : 1) * (tech.isExplodeRadio ? 1.25 : 1) }, explosion(where, radius, color = "rgba(255,25,0,0.6)") { // typically explode is used for some bullets with .onEnd radius *= tech.explosiveRadius let dist, sub, knock; let dmg = radius * 0.019 if (tech.isExplosionHarm) radius *= 1.7 // 1/sqrt(2) radius -> area if (tech.isSmallExplosion) { // color = "rgba(255,0,30,0.7)" radius *= 0.66 dmg *= 1.66 } if (tech.isExplodeRadio) { //radiation explosion radius *= 1.25; //alert range if (tech.isSmartRadius) radius = Math.max(Math.min(radius, Vector.magnitude(Vector.sub(where, player.position)) - 25), 1) color = "rgba(25,139,170,0.25)" simulation.drawList.push({ //add dmg to draw queue x: where.x, y: where.y, radius: radius, color: color, time: simulation.drawTime * 2 }); //player damage if (Vector.magnitude(Vector.sub(where, player.position)) < radius) { const DRAIN = (tech.isExplosionHarm ? 0.6 : 0.45) * (tech.isRadioactiveResistance ? 0.25 : 1) if (m.immuneCycle < m.cycle) m.energy -= DRAIN if (m.energy < 0) { m.energy = 0 if (simulation.dmgScale) m.damage(tech.radioactiveDamage * 0.03 * (tech.isRadioactiveResistance ? 0.25 : 1)); } } //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 mobs.statusDoT(mob[i], dmg * damageScale * 0.25, 240) //apply radiation damage status effect on direct hits if (tech.isStun) mobs.statusStun(mob[i], 30) mob[i].locatePlayer(); damageScale *= 0.87 //reduced damage for each additional explosion target } } } } else { //normal explosions if (tech.isSmartRadius) radius = Math.max(Math.min(radius, Vector.magnitude(Vector.sub(where, player.position)) - 25), 1) simulation.drawList.push({ //add dmg to draw queue x: where.x, y: where.y, radius: radius, color: color, time: simulation.drawTime }); const alertRange = 100 + radius * 2; //alert range simulation.drawList.push({ //add alert to draw queue x: where.x, y: where.y, radius: alertRange, color: "rgba(100,20,0,0.03)", time: simulation.drawTime }); //player damage and knock back if (m.immuneCycle < m.cycle) { sub = Vector.sub(where, player.position); dist = Vector.magnitude(sub); if (dist < radius) { if (simulation.dmgScale) { const harm = tech.isExplosionHarm ? 0.067 : 0.05 if (tech.isImmuneExplosion && m.energy > 0.25) { // const mitigate = Math.min(1, Math.max(1 - m.energy * 0.5, 0)) m.energy -= 0.25 // m.damage(0.01 * harm); //remove 99% of the damage 1-0.99 knock = Vector.mult(Vector.normalise(sub), -0.6 * player.mass * Math.max(0, Math.min(0.15 - 0.002 * player.speed, 0.15))); player.force.x = knock.x; // not += so crazy forces can't build up with MIRV player.force.y = knock.y - 0.3; //some extra vertical kick } else { if (simulation.dmgScale) m.damage(harm); knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * player.mass * 0.013); 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.005); player.force.x += knock.x; player.force.y += knock.y; } } //body knock backs for (let i = body.length - 1; i > -1; i--) { if (!body[i].isNotHoldable) { 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; if (tech.isBlockExplode) { if (body[i] === m.holdingTarget) m.drop() const size = 20 + 300 * Math.pow(body[i].mass, 0.25) const where = body[i].position const onLevel = level.onLevel //prevent explosions in the next level Matter.Composite.remove(engine.world, body[i]); body.splice(i, 1); setTimeout(() => { if (onLevel === level.onLevel) b.explosion(where, size); //makes bullet do explosive damage at end }, 250 + 300 * Math.random()); } } else if (dist < alertRange) { knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg) * body[i].mass * 0.011); 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.013); 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.007); 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 * m.dmgScale); mob[i].locatePlayer(); knock = Vector.mult(Vector.normalise(sub), -Math.sqrt(dmg * damageScale) * mob[i].mass * (mob[i].isBoss ? 0.003 : 0.01)); if (tech.isStun) { mobs.statusStun(mob[i], 30) } else if (!mob[i].isInvulnerable) { 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 * (mob[i].isBoss ? 0 : 0.006)); if (tech.isStun) { mobs.statusStun(mob[i], 30) } else if (!mob[i].isInvulnerable) { mob[i].force.x += knock.x; mob[i].force.y += knock.y; } } } } } }, pulse(charge, angle = m.angle, where = m.pos) { let best; let explosionRadius = 5.5 * charge let range = 5000 const path = [{ x: where.x + 20 * Math.cos(angle), y: where.y + 20 * Math.sin(angle) }, { x: where.x + range * Math.cos(angle), y: where.y + range * Math.sin(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 = simulation.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 = simulation.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 }; // if (tech.isPulseAim && !m.crouch) { //find mobs in line of sight // let dist = 2200 // for (let i = 0, len = mob.length; i < len; i++) { // const newDist = Vector.magnitude(Vector.sub(path[0], mob[i].position)) // if ( // explosionRadius < newDist && // newDist < dist && // !mob[i].isBadTarget && // Matter.Query.ray(map, path[0], mob[i].position).length === 0 && // Matter.Query.ray(body, path[0], mob[i].position).length === 0 && // !mob[i].isInvulnerable // ) { // dist = newDist // best.who = mob[i] // path[path.length - 1] = mob[i].position // } // } // } if (!best.who) { 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) { b.explosion(path[1], explosionRadius) const off = explosionRadius * 1.2 b.explosion({ x: path[1].x + off * (Math.random() - 0.5), y: path[1].y + off * (Math.random() - 0.5) }, explosionRadius) b.explosion({ x: path[1].x + off * (Math.random() - 0.5), y: path[1].y + off * (Math.random() - 0.5) }, explosionRadius) } //draw laser beam ctx.beginPath(); ctx.moveTo(path[0].x, path[0].y); ctx.lineTo(path[1].x, path[1].y); if (charge > 50) { ctx.strokeStyle = "rgba(255,0,0,0.10)" ctx.lineWidth = 70 ctx.stroke(); } ctx.strokeStyle = "rgba(255,0,0,0.25)" ctx.lineWidth = 20 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.0005 * charge); i < len; i++) { const dist = Math.random() simulation.drawList.push({ x: path[0].x + sub.x * dist + 10 * (Math.random() - 0.5), y: path[0].y + sub.y * dist + 10 * (Math.random() - 0.5), radius: 1.5 + 5 * Math.random(), color: "rgba(255,0,0,0.5)", time: Math.floor(9 + 25 * Math.random() * Math.random()) }); } }, // photon(where, angle = m.angle) { // let best; // const path = [{ // x: m.pos.x + 20 * Math.cos(angle), // y: m.pos.y + 20 * Math.sin(angle) // }, // { // x: m.pos.x + range * Math.cos(angle), // y: m.pos.y + range * Math.sin(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 = simulation.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 = simulation.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 // }; // if (tech.isPulseAim) { //find mobs in line of sight // let dist = 2200 // for (let i = 0, len = mob.length; i < len; i++) { // const newDist = Vector.magnitude(Vector.sub(path[0], mob[i].position)) // if (explosionRadius < newDist && // newDist < dist && // Matter.Query.ray(map, path[0], mob[i].position).length === 0 && // Matter.Query.ray(body, path[0], mob[i].position).length === 0) { // dist = newDist // best.who = mob[i] // path[path.length - 1] = mob[i].position // } // } // } // if (!best.who) { // 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) b.explosion(path[1], explosionRadius) // //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() // simulation.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()) // }); // } // }, fireworks(where, size) { //can occur after grenades detonate const cycle = () => { if (m.alive) { if (simulation.paused || m.isBodiesAsleep) { requestAnimationFrame(cycle) } else { count++ if (count < 110) requestAnimationFrame(cycle); if (!(count % 10)) { const unit = Vector.rotate({ x: 1, y: 0 }, 6.28 * Math.random()) b.explosion(Vector.add(where, Vector.mult(unit, size * (count * 0.01 + 0.02 * Math.random()))), size * (0.4 + Math.random() * 0.35), `hsla(${360 * Math.random()},100%,66%,0.6)`); //makes bullet do explosive damage at end } } } } let count = 7 requestAnimationFrame(cycle); }, starburst(where, size) { //can occur after grenades detonate const color = `hsla(${360 * Math.random()},100%,66%,0.6)` const cycle = () => { if (m.alive) { if (simulation.paused || m.isBodiesAsleep) { requestAnimationFrame(cycle) } else { count++ if (count < 21) requestAnimationFrame(cycle); if (count % 2) { const unit = Vector.rotate({ x: 1, y: 0 }, curl * 6.28 * count / 18 + off) b.explosion(Vector.add(where, Vector.mult(unit, size * 0.75)), size * 0.7, color); //makes bullet do explosive damage at end } } } } const off = 6 * Math.random() const curl = Math.random() < 0.5 ? -1 : 1; let count = 0 requestAnimationFrame(cycle); }, fireFlower(where, size) { //can occur after grenades detonate // size *= b.explosionRange() const range = size * Math.sqrt(b.explosionRange()) const cycle = () => { if (m.alive) { if (simulation.paused || m.isBodiesAsleep) { requestAnimationFrame(cycle) } else { if (count < 30 && m.alive) requestAnimationFrame(cycle); if (count === 0) { const color = `hsla(${360 * Math.random()},100%,66%,0.6)` b.explosion(where, size * 0.8, color); } if (count === 8) { const color = `hsla(${360 * Math.random()},100%,66%,0.6)` for (let i = 0, len = 6; i < len; i++) { const unit = Vector.rotate({ x: 1, y: 0 }, 6.28 * i / len) b.explosion(Vector.add(where, Vector.mult(unit, 1.1 * range)), size * 0.6, color); //makes bullet do explosive damage at end } } if (count === 16) { const color = `hsla(${360 * Math.random()},100%,66%,0.6)` for (let i = 0, len = 10; i < len; i++) { const unit = Vector.rotate({ x: 1, y: 0 }, 6.28 * i / len) b.explosion(Vector.add(where, Vector.mult(unit, 1.4 * range)), size * 0.45, color); //makes bullet do explosive damage at end } } count++ } } } let count = 0 requestAnimationFrame(cycle); }, grenadeEnd() { if (tech.isCircleExplode) { b.starburst(this.position, this.explodeRad) } else if (tech.isPetalsExplode) { b.fireFlower(this.position, this.explodeRad) } else if (tech.isClusterExplode) { b.fireworks(this.position, this.explodeRad) } else { b.explosion(this.position, this.explodeRad); //makes bullet do explosive damage at end } if (tech.fragments) b.targetedNail(this.position, tech.fragments * Math.floor(2 + 1.5 * Math.random())) }, grenade() { }, setGrenadeMode() { grenadeDefault = function (where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }, angle = m.angle, size = 1) { const me = bullet.length; bullet[me] = Bodies.circle(where.x, where.y, 15, b.fireAttributes(angle, false)); Matter.Body.setDensity(bullet[me], 0.0003); bullet[me].explodeRad = 300 * size + 100 * tech.isBlockExplode; bullet[me].onEnd = b.grenadeEnd bullet[me].minDmgSpeed = 1; bullet[me].beforeDmg = function () { this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion }; speed = m.crouch ? 43 : 32 Matter.Body.setVelocity(bullet[me], { x: m.Vx / 2 + speed * Math.cos(angle), y: m.Vy / 2 + speed * Math.sin(angle) }); bullet[me].endCycle = simulation.cycle + Math.floor(m.crouch ? 120 : 80) * tech.bulletsLastLonger; bullet[me].restitution = 0.4; bullet[me].do = function () { this.force.y += this.mass * 0.0025; //extra gravity for harder arcs }; Composite.add(engine.world, bullet[me]); //add bullet to world } grenadeRPG = function (where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }, angle = m.angle, size = 1) { const me = bullet.length; bullet[me] = Bodies.circle(where.x, where.y, 15, b.fireAttributes(angle, false)); Matter.Body.setDensity(bullet[me], 0.0003); bullet[me].explodeRad = 300 * size + 100 * tech.isBlockExplode; bullet[me].onEnd = b.grenadeEnd bullet[me].minDmgSpeed = 1; bullet[me].beforeDmg = function () { this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion }; speed = m.crouch ? 46 : 32 Matter.Body.setVelocity(bullet[me], { x: m.Vx / 2 + speed * Math.cos(angle), y: m.Vy / 2 + speed * Math.sin(angle) }); Composite.add(engine.world, bullet[me]); //add bullet to world bullet[me].endCycle = simulation.cycle + 70 * tech.bulletsLastLonger; bullet[me].frictionAir = 0.07; const MAG = 0.015 bullet[me].thrust = { x: bullet[me].mass * MAG * Math.cos(angle), y: bullet[me].mass * MAG * Math.sin(angle) } bullet[me].do = function () { this.force.x += this.thrust.x; this.force.y += this.thrust.y; if (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length) { this.endCycle = 0; //explode if touching map or blocks } }; } grenadeRPGVacuum = function (where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }, angle = m.angle, size = 1) { const me = bullet.length; bullet[me] = Bodies.circle(where.x, where.y, 15, b.fireAttributes(angle, false)); Matter.Body.setDensity(bullet[me], 0.0003); bullet[me].explodeRad = 350 * size + Math.floor(Math.random() * 50) + tech.isBlockExplode * 100 bullet[me].onEnd = b.grenadeEnd bullet[me].minDmgSpeed = 1; bullet[me].beforeDmg = function () { this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion }; speed = m.crouch ? 46 : 32 Matter.Body.setVelocity(bullet[me], { x: m.Vx / 2 + speed * Math.cos(angle), y: m.Vy / 2 + speed * Math.sin(angle) }); Composite.add(engine.world, bullet[me]); //add bullet to world bullet[me].endCycle = simulation.cycle + 70 * tech.bulletsLastLonger; bullet[me].frictionAir = 0.07; bullet[me].suckCycles = 40 const MAG = 0.015 bullet[me].thrust = { x: bullet[me].mass * MAG * Math.cos(angle), y: bullet[me].mass * MAG * Math.sin(angle) } bullet[me].suck = function () { const suck = (who, radius = this.explodeRad * 3.2) => { for (i = 0, len = who.length; i < len; i++) { const sub = Vector.sub(this.position, who[i].position); const dist = Vector.magnitude(sub); if (dist < radius && dist > 150 && !who.isInvulnerable && who[i] !== this) { 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; } } } let mag = 0.1 if (simulation.cycle > this.endCycle - 5) { mag = -0.22 suck(mob, this.explodeRad * 3) suck(body, this.explodeRad * 2) suck(powerUp, this.explodeRad * 1.5) suck(bullet, this.explodeRad * 1.5) suck([player], this.explodeRad * 1.3) } else { mag = 0.11 suck(mob, this.explodeRad * 3) suck(body, this.explodeRad * 2) suck(powerUp, this.explodeRad * 1.5) suck(bullet, this.explodeRad * 1.5) suck([player], this.explodeRad * 1.3) } Matter.Body.setVelocity(this, { x: 0, y: 0 }); //keep bomb in place //draw suck const radius = 2.75 * this.explodeRad * (this.endCycle - simulation.cycle) / this.suckCycles 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(); } bullet[me].do = function () { if (simulation.cycle > this.endCycle - this.suckCycles) { //suck this.do = this.suck } else if (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length) { Matter.Body.setPosition(this, Vector.sub(this.position, this.velocity)) //undo last movement this.do = this.suck } else { this.force.x += this.thrust.x; this.force.y += this.thrust.y; } }; } grenadeVacuum = function (where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }, angle = m.angle, size = 1) { const me = bullet.length; bullet[me] = Bodies.circle(where.x, where.y, 20, b.fireAttributes(angle, false)); Matter.Body.setDensity(bullet[me], 0.0002); bullet[me].explodeRad = 350 * size + Math.floor(Math.random() * 50) + tech.isBlockExplode * 100 bullet[me].onEnd = b.grenadeEnd bullet[me].beforeDmg = function () { this.endCycle = 0; //bullet ends cycle after doing damage //this also triggers explosion }; bullet[me].restitution = 0.4; bullet[me].do = function () { this.force.y += this.mass * 0.0025; //extra gravity for harder arcs const suckCycles = 40 if (simulation.cycle > this.endCycle - suckCycles) { //suck const that = this function suck(who, radius = that.explodeRad * 3.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 && !who.isInvulnerable) { 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; } } } let mag = 0.1 if (simulation.cycle > this.endCycle - 5) { mag = -0.22 suck(mob, this.explodeRad * 3) suck(body, this.explodeRad * 2) suck(powerUp, this.explodeRad * 1.5) suck(bullet, this.explodeRad * 1.5) suck([player], this.explodeRad * 1.3) } else { mag = 0.11 suck(mob, this.explodeRad * 3) suck(body, this.explodeRad * 2) suck(powerUp, this.explodeRad * 1.5) suck(bullet, this.explodeRad * 1.5) suck([player], this.explodeRad * 1.3) } //keep bomb in place Matter.Body.setVelocity(this, { x: 0, y: 0 }); //draw suck const radius = 2.75 * this.explodeRad * (this.endCycle - simulation.cycle) / suckCycles 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(); } }; speed = 35 // speed = m.crouch ? 43 : 32 bullet[me].endCycle = simulation.cycle + 70 * tech.bulletsLastLonger; if (m.crouch) { speed += 9 bullet[me].endCycle += 20; } Matter.Body.setVelocity(bullet[me], { x: m.Vx / 2 + speed * Math.cos(angle), y: m.Vy / 2 + speed * Math.sin(angle) }); Composite.add(engine.world, bullet[me]); //add bullet to world } grenadeNeutron = function (where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }, angle = m.angle, size = 1) { const me = bullet.length; bullet[me] = Bodies.polygon(where.x, where.y, 10, 4, b.fireAttributes(angle, false)); b.fireProps((m.crouch ? 45 : 25) / Math.pow(0.92, tech.missileCount), m.crouch ? 35 : 20, angle, me); //cd , speed Matter.Body.setDensity(bullet[me], 0.000001); bullet[me].endCycle = 500 + simulation.cycle; bullet[me].frictionAir = 0; bullet[me].friction = 1; bullet[me].frictionStatic = 1; bullet[me].restitution = 0; bullet[me].minDmgSpeed = 0; bullet[me].damageRadius = 100; bullet[me].maxDamageRadius = 450 * size + 130 * tech.isNeutronSlow //+ 150 * Math.random() bullet[me].radiusDecay = (0.81 + 0.15 * tech.isNeutronSlow) / tech.bulletsLastLonger bullet[me].stuckTo = null; bullet[me].stuckToRelativePosition = null; if (tech.isRPG) { const SCALE = 2 Matter.Body.scale(bullet[me], SCALE, SCALE); speed = m.crouch ? 25 : 15 // speed = m.crouch ? 43 : 32 Matter.Body.setVelocity(bullet[me], { x: m.Vx / 2 + speed * Math.cos(angle), y: m.Vy / 2 + speed * Math.sin(angle) }); const MAG = 0.005 bullet[me].thrust = { x: bullet[me].mass * MAG * Math.cos(angle), y: bullet[me].mass * MAG * Math.sin(angle) } } bullet[me].beforeDmg = function () { }; bullet[me].stuck = function () { }; bullet[me].do = function () { const onCollide = () => { this.collisionFilter.mask = 0; //non collide with everything Matter.Body.setVelocity(this, { x: 0, y: 0 }); if (tech.isRPG) this.thrust = { x: 0, y: 0 } this.do = this.radiationMode; } const mobCollisions = Matter.Query.collides(this, mob) if (mobCollisions.length) { onCollide() this.stuckTo = mobCollisions[0].bodyA mobs.statusDoT(this.stuckTo, 0.5, 360) //apply radiation damage status effect on direct hits if (this.stuckTo.isVerticesChange) { this.stuckToRelativePosition = { x: 0, y: 0 } } else { //find the relative position for when the mob is at angle zero by undoing the mobs rotation this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) } this.stuck = function () { if (this.stuckTo && this.stuckTo.alive) { const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck } else { this.collisionFilter.mask = cat.map | cat.body | cat.player | cat.mob; //non collide with everything but map this.stuck = function () { this.force.y += this.mass * 0.001; } } } } else { const bodyCollisions = Matter.Query.collides(this, body) if (bodyCollisions.length) { if (!bodyCollisions[0].bodyA.isNotHoldable) { onCollide() this.stuckTo = bodyCollisions[0].bodyA //find the relative position for when the mob is at angle zero by undoing the mobs rotation this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) } else { this.do = this.radiationMode; } this.stuck = function () { if (this.stuckTo) { const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) // Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck } else { this.force.y += this.mass * 0.001; } } } else { if (Matter.Query.collides(this, map).length) { onCollide() } else if (tech.isRPG) { //if colliding with nothing this.force.x += this.thrust.x; this.force.y += this.thrust.y; } else { this.force.y += this.mass * 0.001; } } } } bullet[me].radiationMode = function () { //the do code after the bullet is stuck on something, projects a damaging radiation field this.stuck(); //runs different code based on what the bullet is stuck to this.damageRadius = this.damageRadius * 0.85 + 0.15 * this.maxDamageRadius //smooth radius towards max this.maxDamageRadius -= this.radiusDecay if (this.damageRadius < 15) { this.endCycle = 0; } else { //aoe damage to player if (Vector.magnitude(Vector.sub(player.position, this.position)) < this.damageRadius) { const DRAIN = (tech.isRadioactiveResistance ? 0.0025 * 0.25 : 0.0025) if (m.energy > DRAIN) { if (m.immuneCycle < m.cycle) m.energy -= DRAIN } else { m.energy = 0; if (simulation.dmgScale) m.damage((tech.isRadioactiveResistance ? 0.00016 * 0.25 : 0.00016) * tech.radioactiveDamage) //0.00015 } } //aoe damage to mobs let dmg = m.dmgScale * 0.11 * tech.radioactiveDamage for (let i = 0, len = mob.length; i < len; i++) { if (Vector.magnitude(Vector.sub(mob[i].position, this.position)) < this.damageRadius + mob[i].radius) { if (Matter.Query.ray(map, mob[i].position, this.position).length > 0) dmg *= 0.25 //reduce damage if a wall is in the way mob[i].damage(mob[i].shield ? dmg * 3 : dmg); mob[i].locatePlayer(); if (tech.isNeutronSlow && mob[i].speed > 4) { Matter.Body.setVelocity(mob[i], { x: mob[i].velocity.x * 0.97, y: mob[i].velocity.y * 0.97 }); } } } ctx.beginPath(); ctx.arc(this.position.x, this.position.y, this.damageRadius, 0, 2 * Math.PI); ctx.globalCompositeOperation = "lighter" ctx.fillStyle = `rgba(25,139,170,${0.2 + 0.06 * Math.random()})`; ctx.fill(); ctx.globalCompositeOperation = "source-over" if (tech.isNeutronSlow) { let slow = (who, radius = this.explodeRad * 3.2) => { for (i = 0, len = who.length; i < len; i++) { const sub = Vector.sub(this.position, who[i].position); const dist = Vector.magnitude(sub); if (dist < radius) { Matter.Body.setVelocity(who[i], { x: who[i].velocity.x * 0.975, y: who[i].velocity.y * 0.975 }); } } } slow(body, this.damageRadius) slow([player], this.damageRadius) } } } } if (tech.isNeutronBomb) { b.grenade = grenadeNeutron if (tech.isRPG) { b.guns[5].do = function () { } } else { b.guns[5].do = function () { if (!input.field && m.crouch) { const cycles = 80 const speed = m.crouch ? 35 : 20 //m.crouch ? 43 : 32 const g = m.crouch ? 0.137 : 0.135 const v = { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) } ctx.strokeStyle = "rgba(68, 68, 68, 0.2)" //color.map ctx.lineWidth = 2 ctx.beginPath() for (let i = 1, len = 19; i < len + 1; i++) { const time = cycles * i / len ctx.lineTo(m.pos.x + time * v.x, m.pos.y + time * v.y + g * time * time) } ctx.stroke() } } } } else if (tech.isRPG) { b.guns[5].do = function () { } if (tech.isVacuumBomb) { b.grenade = grenadeRPGVacuum } else { b.grenade = grenadeRPG } } else if (tech.isVacuumBomb) { b.grenade = grenadeVacuum b.guns[5].do = function () { if (!input.field && m.crouch) { const cycles = Math.floor(m.crouch ? 50 : 30) //30 const speed = m.crouch ? 44 : 35 const v = { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) } ctx.strokeStyle = "rgba(68, 68, 68, 0.2)" //color.map ctx.lineWidth = 2 ctx.beginPath() for (let i = 1.6, len = 19; i < len + 1; i++) { const time = cycles * i / len ctx.lineTo(m.pos.x + time * v.x, m.pos.y + time * v.y + 0.34 * time * time) } ctx.stroke() } } } else { b.grenade = grenadeDefault b.guns[5].do = function () { if (!input.field && m.crouch) { const cycles = Math.floor(m.crouch ? 120 : 80) //30 const speed = m.crouch ? 43 : 32 const v = { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) } //m.Vy / 2 + removed to make the path less jerky ctx.strokeStyle = "rgba(68, 68, 68, 0.2)" //color.map ctx.lineWidth = 2 ctx.beginPath() for (let i = 0.5, len = 19; i < len + 1; i++) { const time = cycles * i / len ctx.lineTo(m.pos.x + time * v.x, m.pos.y + time * v.y + 0.34 * time * time) } ctx.stroke() } } } }, // dart(where, angle = m.angle, size = 0.8) { // //find a target // const closest = { // score: 10000, // position: null // } // for (let i = 0, len = mob.length; i < len; ++i) { // if (mob[i].alive && !mob[i].isBadTarget && Matter.Query.ray(map, where, mob[i].position).length === 0) { // const dot = Vector.dot({ x: Math.cos(angle), y: Math.sin(angle) }, Vector.normalise(Vector.sub(mob[i].position, where))) //the dot product of diff and dir will return how much over lap between the vectors // const dist = Vector.magnitude(Vector.sub(where, mob[i].position)) // // if (dist < closest.score && ((dist > 500 && dot > 0) || (dot > 0.9))) { //target closest mob that player is looking at and isn't too close to target // if (dist < closest.score && dot > 0.9 - 0.0004 * dist) { //target closest mob that player is looking at and isn't too close to target // closest.score = dist // closest.position = mob[i].position // } // } // } // if (!closest.position) { // // const unit = Vector.mult(sub(simulation.mouseInGame, where), 10000) // closest.position = Vector.mult(Vector.sub(simulation.mouseInGame, where), 10000) // } // const me = bullet.length; // bullet[me] = Bodies.fromVertices(where.x, where.y, [{ x: -20 * size, y: 2 * size, index: 0, isInternal: false }, { x: -20 * size, y: -2 * size, index: 1, isInternal: false }, { x: 5 * size, y: -2 * size, index: 4, isInternal: false }, { x: 20 * size, y: 0, index: 3, isInternal: false }, { x: 5 * size, y: 2 * size, index: 4, isInternal: false }], { // cycle: 0, // angle: angle, // friction: 1, // frictionAir: 0.15, // thrustMag: 0.03, // turnRate: 0.15, //0.015 // drawStringControlMagnitude: 3000 + 5000 * Math.random(), // drawStringFlip: (Math.round(Math.random()) ? 1 : -1), // dmg: 7, //damage done in addition to the damage from momentum // classType: "bullet", // endCycle: simulation.cycle + 120, // collisionFilter: { // category: cat.bullet, // mask: tech.isShieldPierce ? cat.body | cat.mob | cat.mobBullet : cat.body | cat.mob | cat.mobBullet | cat.mobShield, // }, // minDmgSpeed: 0, // lookFrequency: Math.floor(7 + Math.random() * 3), // density: 0.001, //0.001 is normal for blocks, 0.008 is normal for harpoon, 0.008*6 when buffed // beforeDmg(who) { // if (tech.isShieldPierce && who.isShielded) { //disable shields // who.isShielded = false // requestAnimationFrame(() => { who.isShielded = true }); // } // if (tech.fragments) { // b.targetedNail(this.vertices[2], tech.fragments * Math.floor(2 + 1.5 * Math.random())) // this.endCycle = 0; // } // if (!who.isBadTarget) { // this.frictionAir = 0.01 // this.do = this.doNoTargeting // } // }, // onEnd() {}, // doNoTargeting: function() { // // this.force.y += this.mass * 0.001; // if (Matter.Query.collides(this, map).length) { //stick in walls // this.collisionFilter.mask = 0; // Matter.Body.setAngularVelocity(this, 0) // Matter.Body.setVelocity(this, { // x: 0, // y: 0 // }); // this.do = () => { // // if (!Matter.Query.collides(this, map).length) this.force.y += this.mass * 0.001; // } // } // }, // do() { // this.cycle++ // // if (this.cycle > 40) { // // this.frictionAir = 0.003 // // this.do = this.doNoTargeting // // } // // if (closest.target) { //rotate towards the target // const face = { x: Math.cos(this.angle), y: Math.sin(this.angle) }; // const vectorGoal = Vector.normalise(Vector.sub(this.position, closest.position)); // const cross = Vector.cross(vectorGoal, face) // if (cross > 0.01) { // Matter.Body.rotate(this, this.turnRate * Math.sqrt(cross)); // } else if (cross < 0.01) { // Matter.Body.rotate(this, -this.turnRate * Math.sqrt(Math.abs(cross))); // } // this.force.x += this.thrustMag * this.mass * Math.cos(this.angle); // this.force.y += this.thrustMag * this.mass * Math.sin(this.angle); // // } // if (Matter.Query.collides(this, map).length) { //stick in walls // this.collisionFilter.mask = 0; // Matter.Body.setAngularVelocity(this, 0) // Matter.Body.setVelocity(this, { // x: 0, // y: 0 // }); // this.do = this.doNoTargeting // } // // else if (!(this.cycle % 2)) { //look for a target if you don't have one // // simulation.drawList.push({ //add dmg to draw queue // // x: this.position.x, // // y: this.position.y, // // radius: 10, // // color: simulation.mobDmgColor, // // time: simulation.drawTime // // }); // // let closest = { // // distance: 2000, // // target: null // // } // // const dir = Vector.normalise(this.velocity) //make a vector for direction of length 1 // // for (let i = 0, len = mob.length; i < len; ++i) { // // if ( // // mob[i].alive && !mob[i].isBadTarget && // // Matter.Query.ray(map, this.position, mob[i].position).length === 0 && //check for map in Line of sight // // Vector.dot(dir, Vector.normalise(Vector.sub(mob[i].position, this.position))) > 0.55 //the dot product of diff and dir will return how much over lap between the vectors // // ) { // // const dist = Vector.magnitude(Vector.sub(this.position, mob[i].position)) // // if (dist < closest.distance) { // // closest.distance = dist // // closest.target = mob[i] // // } // // } // // } // // if (closest.target) { // // target = closest.target // // this.turnRate = 0.05 // // this.frictionAir = 0.8 // // } // // } // }, // }); // Matter.Body.setVelocity(bullet[me], { // x: m.Vx / 2 + 40 * Math.cos(bullet[me].angle), // y: m.Vy / 2 + 40 * Math.sin(bullet[me].angle) // }); // // if (!closest.target) { // // bullet[me].frictionAir = 0.002 // // bullet[me].do = bullet[me].doNoTargeting // // } // Composite.add(engine.world, bullet[me]); //add bullet to world // }, grapple(where, angle = m.angle, harpoonSize = 1) { const me = bullet.length; const returnRadius = 100 * Math.sqrt(harpoonSize) bullet[me] = Bodies.fromVertices(where.x, where.y, [{ x: -50 * harpoonSize, y: 2 * harpoonSize, index: 0, isInternal: false }, { x: -50 * harpoonSize, y: -2 * harpoonSize, index: 1, isInternal: false }, { x: 45 * harpoonSize, y: -3 * harpoonSize, index: 2, isInternal: false }, { x: 50 * harpoonSize, y: 0, index: 3, isInternal: false }, { x: 45 * harpoonSize, y: 3 * harpoonSize, index: 4, isInternal: false }], { angle: angle, friction: 1, frictionAir: 0.4, thrustMag: 0.1, dmg: 6, //damage done in addition to the damage from momentum classType: "bullet", endCycle: simulation.cycle + 70, collisionFilter: { category: cat.bullet, mask: tech.isShieldPierce ? cat.body | cat.mob | cat.mobBullet : cat.body | cat.mob | cat.mobBullet | cat.mobShield, }, minDmgSpeed: 4, lookFrequency: Math.floor(7 + Math.random() * 3), density: tech.harpoonDensity, //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed drain: tech.isRailEnergy ? 0.0006 : 0.006, beforeDmg(who) { if (tech.isShieldPierce && who.isShielded) { //disable shields who.isShielded = false requestAnimationFrame(() => { who.isShielded = true }); } if (tech.fragments) { b.targetedNail(this.vertices[2], tech.fragments * Math.floor(2 + Math.random())) } if (tech.isFoamBall) { for (let i = 0, len = 3 * this.mass; i < len; i++) { const radius = 5 + 8 * Math.random() const velocity = { x: Math.max(0.5, 2 - radius * 0.1), y: 0 } b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius) } // this.endCycle = 0; } }, caughtPowerUp: null, dropCaughtPowerUp() { if (this.caughtPowerUp) { this.caughtPowerUp.collisionFilter.category = cat.powerUp this.caughtPowerUp.collisionFilter.mask = cat.map | cat.powerUp this.caughtPowerUp = null } }, onEnd() { if (this.caughtPowerUp && !simulation.isChoosing && (this.caughtPowerUp.name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal)) { let index = null //find index for (let i = 0, len = powerUp.length; i < len; ++i) { if (powerUp[i] === this.caughtPowerUp) index = i } if (index !== null) { powerUps.onPickUp(this.caughtPowerUp); this.caughtPowerUp.effect(); Matter.Composite.remove(engine.world, this.caughtPowerUp); powerUp.splice(index, 1); if (tech.isHarpoonPowerUp) tech.harpoonDensity = 0.004 * 6 //0.005 is normal } else { this.dropCaughtPowerUp() } } else { this.dropCaughtPowerUp() } }, draw() { const where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } const sub = Vector.sub(where, this.vertices[0]) const controlPoint = Vector.add(where, Vector.mult(sub, -0.5)) ctx.strokeStyle = "#000" // "#0ce" ctx.lineWidth = 0.5 ctx.beginPath(); ctx.moveTo(where.x, where.y); ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, this.vertices[0].x, this.vertices[0].y) // ctx.lineTo(this.vertices[0].x, this.vertices[0].y); ctx.stroke(); //draw harpoon spikes const spikeLength = 2 ctx.beginPath(); const spike1 = Vector.add(this.vertices[1], Vector.mult(Vector.sub(this.vertices[1], this.vertices[2]), spikeLength)) ctx.moveTo(this.vertices[2].x, this.vertices[2].y); ctx.lineTo(spike1.x, spike1.y); ctx.lineTo(this.vertices[3].x, this.vertices[3].y); const spike2 = Vector.add(this.vertices[3], Vector.mult(Vector.sub(this.vertices[3], this.vertices[2]), spikeLength)) ctx.moveTo(this.vertices[2].x, this.vertices[2].y); ctx.lineTo(spike2.x, spike2.y); ctx.lineTo(this.vertices[1].x, this.vertices[1].y); ctx.fillStyle = '#000' ctx.fill(); }, returnToPlayer() { if (Vector.magnitude(Vector.sub(this.position, m.pos)) < returnRadius) { //near player this.endCycle = 0; // if (m.energy < 0.05) { // m.fireCDcycle = m.cycle + 120; //fire cooldown // } else if (m.cycle + 15 * b.fireCDscale < m.fireCDcycle) { // m.fireCDcycle = m.cycle + 15 * b.fireCDscale //lower cd to 25 if it is above 25 // } if (m.energy < 0.05) this.dropCaughtPowerUp() //recoil on catching const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) player.force.x += momentum.x player.force.y += momentum.y // refund ammo b.guns[9].ammo++; simulation.updateGunHUD(); // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun // if (b.guns[i].name === "harpoon") { // b.guns[i].ammo++; // simulation.updateGunHUD(); // break; // } // } } else { if (m.energy > this.drain) m.energy -= this.drain const sub = Vector.sub(this.position, m.pos) const rangeScale = 1 + 0.000001 * Vector.magnitude(sub) * Vector.magnitude(sub) //return faster when far from player const returnForce = Vector.mult(Vector.normalise(sub), rangeScale * this.thrustMag * this.mass) this.force.x -= returnForce.x this.force.y -= returnForce.y this.grabPowerUp() } this.draw(); }, grabPowerUp() { //grab power ups near the tip of the harpoon if (this.caughtPowerUp) { Matter.Body.setPosition(this.caughtPowerUp, Vector.add(this.vertices[2], this.velocity)) Matter.Body.setVelocity(this.caughtPowerUp, { x: 0, y: 0 }) } else { //&& simulation.cycle % 2 for (let i = 0, len = powerUp.length; i < len; ++i) { const radius = powerUp[i].circleRadius + 50 if (Vector.magnitudeSquared(Vector.sub(this.vertices[2], powerUp[i].position)) < radius * radius) { if (powerUp[i].name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal) { this.caughtPowerUp = powerUp[i] Matter.Body.setVelocity(powerUp[i], { x: 0, y: 0 }) Matter.Body.setPosition(powerUp[i], this.vertices[2]) powerUp[i].collisionFilter.category = 0 powerUp[i].collisionFilter.mask = 0 this.thrustMag *= 0.6 this.endCycle += 0.5 //it pulls back slower, so this prevents it from ending early break //just pull 1 power up if possible } } } } }, do() { if (input.fire) { //&& !Matter.Query.collides(this, body).length this.grabPowerUp() if (this.endCycle < simulation.cycle + 1) { //if at end of lifespan, but player is holding down fire, force retraction this.endCycle = simulation.cycle + 60 // m.fireCDcycle = m.cycle + 120 // cool down this.do = this.returnToPlayer Matter.Body.setDensity(this, 0.0005); //reduce density on return if (this.angularSpeed < 0.5) this.torque += this.inertia * 0.001 * (Math.random() - 0.5) //(Math.round(Math.random()) ? 1 : -1) this.collisionFilter.mask = cat.map | cat.mob | cat.mobBullet | cat.mobShield // | cat.body } } else { //if not enough energy if (m.energy < 0.05) this.dropCaughtPowerUp() // const returnForce = Vector.mult(Vector.normalise(Vector.sub(this.position, m.pos)), 3 * this.thrustMag * this.mass) // this.force.x -= returnForce.x // this.force.y -= returnForce.y // this.frictionAir = 0.002 // this.do = () => { // if (this.speed < 20) this.force.y += 0.0005 * this.mass; // } // } else { //return to player this.do = this.returnToPlayer this.endCycle = simulation.cycle + 60 Matter.Body.setDensity(this, 0.0005); //reduce density on return if (this.angularSpeed < 0.5) this.torque += this.inertia * 0.001 * (Math.random() - 0.5) //(Math.round(Math.random()) ? 1 : -1) this.collisionFilter.mask = cat.map | cat.mob | cat.mobBullet | cat.mobShield // | cat.body //recoil on catching const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) player.force.x += momentum.x player.force.y += momentum.y // } } //grappling hook if (input.fire && Matter.Query.collides(this, map).length) { Matter.Body.setPosition(this, Vector.add(this.position, { x: 20 * Math.cos(this.angle), y: 20 * Math.sin(this.angle) })) if (Matter.Query.collides(this, map).length) { Matter.Body.setVelocity(this, { x: 0, y: 0 }); Matter.Sleeping.set(this, true) this.endCycle = simulation.cycle + 5 this.dropCaughtPowerUp() this.do = () => { //between player nose and the grapple const sub = Vector.sub(this.vertices[0], { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }) let dist = Vector.magnitude(sub) if (input.fire) { // m.fireCDcycle = m.cycle + 30; // cool down if out of energy m.fireCDcycle = m.cycle + 5 + 40 * b.fireCDscale + 60 * (m.energy < 0.05) this.endCycle = simulation.cycle + 10 if (input.down) { //down dist = 0 player.force.y += 5 * player.mass * simulation.g; } if (m.energy > this.drain) { Matter.Body.setVelocity(player, { x: player.velocity.x * 0.8, y: player.velocity.y * 0.8 }); //need to scale the friction differently based on distance? // if (dist > 500) { const pull = Vector.mult(Vector.normalise(sub), 0.0008 * Math.min(Math.max(15, dist), 200)) player.force.x += pull.x player.force.y += pull.y // } if (dist > 500) { m.energy -= this.drain if (m.energy < 0) { this.endCycle = 0; if (m.cycle + 50 < m.fireCDcycle) m.fireCDcycle = m.cycle + 50 // refund ammo b.guns[9].ammo++; simulation.updateGunHUD(); // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun // if (b.guns[i].name === "harpoon") { // break; // } // } } } } if (tech.isImmuneGrapple && m.immuneCycle < m.cycle + 10) { m.immuneCycle = m.cycle + 10; if (m.energy > 0.001) { m.energy -= 0.001 } else { //out of energy Matter.Sleeping.set(this, false) this.collisionFilter.category = 0 this.collisionFilter.mask = 0 this.do = this.returnToPlayer this.endCycle = simulation.cycle + 60 m.fireCDcycle = m.cycle + 120; //fire cooldown //recoil on catching const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) player.force.x += momentum.x player.force.y += momentum.y } } } else { Matter.Sleeping.set(this, false) this.collisionFilter.category = 0 this.collisionFilter.mask = 0 this.do = this.returnToPlayer this.endCycle = simulation.cycle + 60 //recoil on catching const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) player.force.x += momentum.x player.force.y += momentum.y } this.draw(); } } } this.force.x += this.thrustMag * this.mass * Math.cos(this.angle); this.force.y += this.thrustMag * this.mass * Math.sin(this.angle); this.draw() }, }); Composite.add(engine.world, bullet[me]); //add bullet to world }, // grapple(where, angle = m.angle, harpoonSize = 1) { // const me = bullet.length; // const returnRadius = 100 * Math.sqrt(harpoonSize) // bullet[me] = Bodies.fromVertices(where.x, where.y, [{ // x: -50 * harpoonSize, // y: 2 * harpoonSize, // index: 0, // isInternal: false // }, { // x: -50 * harpoonSize, // y: -2 * harpoonSize, // index: 1, // isInternal: false // }, { // x: 45 * harpoonSize, // y: -3 * harpoonSize, // index: 2, // isInternal: false // }, { // x: 50 * harpoonSize, // y: 0, // index: 3, // isInternal: false // }, { // x: 45 * harpoonSize, // y: 3 * harpoonSize, // index: 4, // isInternal: false // }], { // angle: angle, // friction: 1, // frictionAir: 0.4, // thrustMag: 0.1, // dmg: 6, //damage done in addition to the damage from momentum // classType: "bullet", // endCycle: simulation.cycle + 70, // collisionFilter: { // category: cat.bullet, // mask: tech.isShieldPierce ? cat.body | cat.mob | cat.mobBullet : cat.body | cat.mob | cat.mobBullet | cat.mobShield, // }, // minDmgSpeed: 4, // lookFrequency: Math.floor(7 + Math.random() * 3), // density: tech.harpoonDensity, //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed // drain: tech.isRailEnergy ? 0.0006 : 0.006, // beforeDmg(who) { // if (tech.isShieldPierce && who.isShielded) { //disable shields // who.isShielded = false // requestAnimationFrame(() => { // who.isShielded = true // }); // } // if (tech.fragments) { // b.targetedNail(this.vertices[2], tech.fragments * Math.floor(2 + Math.random())) // } // if (tech.isFoamBall) { // for (let i = 0, len = 4 * this.mass; i < len; i++) { // const radius = 5 + 8 * Math.random() // const velocity = { // x: Math.max(0.5, 2 - radius * 0.1), // y: 0 // } // b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius) // } // // this.endCycle = 0; // } // }, // caughtPowerUp: null, // dropCaughtPowerUp() { // if (this.caughtPowerUp) { // this.caughtPowerUp.collisionFilter.category = cat.powerUp // this.caughtPowerUp.collisionFilter.mask = cat.map | cat.powerUp // this.caughtPowerUp = null // } // }, // onEnd() { // if (this.caughtPowerUp && !simulation.isChoosing && (this.caughtPowerUp.name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal)) { // let index = null //find index // for (let i = 0, len = powerUp.length; i < len; ++i) { // if (powerUp[i] === this.caughtPowerUp) index = i // } // if (index !== null) { // powerUps.onPickUp(this.caughtPowerUp); // this.caughtPowerUp.effect(); // Matter.Composite.remove(engine.world, this.caughtPowerUp); // powerUp.splice(index, 1); // if (tech.isHarpoonPowerUp) tech.harpoonDensity = 0.004 * 6 //0.005 is normal // } else { // this.dropCaughtPowerUp() // } // } else { // this.dropCaughtPowerUp() // } // }, // draw() { // const where = { // x: m.pos.x + 30 * Math.cos(m.angle), // y: m.pos.y + 30 * Math.sin(m.angle) // } // const sub = Vector.sub(where, this.vertices[0]) // const controlPoint = Vector.add(where, Vector.mult(sub, -0.5)) // ctx.strokeStyle = "#000" // "#0ce" // ctx.lineWidth = 0.5 // ctx.beginPath(); // ctx.moveTo(where.x, where.y); // ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, this.vertices[0].x, this.vertices[0].y) // // ctx.lineTo(this.vertices[0].x, this.vertices[0].y); // ctx.stroke(); // //draw harpoon spikes // const spikeLength = 2 // ctx.beginPath(); // const spike1 = Vector.add(this.vertices[1], Vector.mult(Vector.sub(this.vertices[1], this.vertices[2]), spikeLength)) // ctx.moveTo(this.vertices[2].x, this.vertices[2].y); // ctx.lineTo(spike1.x, spike1.y); // ctx.lineTo(this.vertices[3].x, this.vertices[3].y); // const spike2 = Vector.add(this.vertices[3], Vector.mult(Vector.sub(this.vertices[3], this.vertices[2]), spikeLength)) // ctx.moveTo(this.vertices[2].x, this.vertices[2].y); // ctx.lineTo(spike2.x, spike2.y); // ctx.lineTo(this.vertices[1].x, this.vertices[1].y); // ctx.fillStyle = '#000' // ctx.fill(); // }, // returnToPlayer() { // if (Vector.magnitude(Vector.sub(this.position, m.pos)) < returnRadius) { //near player // this.endCycle = 0; // // if (m.energy < 0.05) { // // m.fireCDcycle = m.cycle + 120; //fire cooldown // // } else if (m.cycle + 15 * b.fireCDscale < m.fireCDcycle) { // // m.fireCDcycle = m.cycle + 15 * b.fireCDscale //lower cd to 25 if it is above 25 // // } // if (m.energy < 0.05) this.dropCaughtPowerUp() // //recoil on catching // const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) // player.force.x += momentum.x // player.force.y += momentum.y // // refund ammo // b.guns[9].ammo++; // simulation.updateGunHUD(); // // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun // // if (b.guns[i].name === "harpoon") { // // b.guns[i].ammo++; // // simulation.updateGunHUD(); // // break; // // } // // } // } else { // if (m.energy > this.drain) m.energy -= this.drain // const sub = Vector.sub(this.position, m.pos) // const rangeScale = 1 + 0.000001 * Vector.magnitude(sub) * Vector.magnitude(sub) //return faster when far from player // const returnForce = Vector.mult(Vector.normalise(sub), rangeScale * this.thrustMag * this.mass) // this.force.x -= returnForce.x // this.force.y -= returnForce.y // this.grabPowerUp() // } // this.draw(); // }, // grabPowerUp() { //grab power ups near the tip of the harpoon // if (this.caughtPowerUp) { // Matter.Body.setPosition(this.caughtPowerUp, Vector.add(this.vertices[2], this.velocity)) // Matter.Body.setVelocity(this.caughtPowerUp, { // x: 0, // y: 0 // }) // } else { //&& simulation.cycle % 2 // for (let i = 0, len = powerUp.length; i < len; ++i) { // const radius = powerUp[i].circleRadius + 50 // if (Vector.magnitudeSquared(Vector.sub(this.vertices[2], powerUp[i].position)) < radius * radius) { // if (powerUp[i].name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal) { // this.caughtPowerUp = powerUp[i] // Matter.Body.setVelocity(powerUp[i], { // x: 0, // y: 0 // }) // Matter.Body.setPosition(powerUp[i], this.vertices[2]) // powerUp[i].collisionFilter.category = 0 // powerUp[i].collisionFilter.mask = 0 // this.thrustMag *= 0.6 // this.endCycle += 0.5 //it pulls back slower, so this prevents it from ending early // break //just pull 1 power up if possible // } // } // } // } // }, // do() { // if (input.fire) { //&& !Matter.Query.collides(this, body).length // this.grabPowerUp() // if (this.endCycle < simulation.cycle + 1) { //if at end of lifespan, but player is holding down fire, force retraction // this.endCycle = simulation.cycle + 60 // // m.fireCDcycle = m.cycle + 120 // cool down // this.do = this.returnToPlayer // Matter.Body.setDensity(this, 0.0005); //reduce density on return // if (this.angularSpeed < 0.5) this.torque += this.inertia * 0.001 * (Math.random() - 0.5) //(Math.round(Math.random()) ? 1 : -1) // this.collisionFilter.mask = cat.map | cat.mob | cat.mobBullet | cat.mobShield // | cat.body // } // } else { // //if not enough energy // if (m.energy < 0.05) this.dropCaughtPowerUp() // // const returnForce = Vector.mult(Vector.normalise(Vector.sub(this.position, m.pos)), 3 * this.thrustMag * this.mass) // // this.force.x -= returnForce.x // // this.force.y -= returnForce.y // // this.frictionAir = 0.002 // // this.do = () => { // // if (this.speed < 20) this.force.y += 0.0005 * this.mass; // // } // // } else { // //return to player // this.do = this.returnToPlayer // this.endCycle = simulation.cycle + 60 // Matter.Body.setDensity(this, 0.0005); //reduce density on return // if (this.angularSpeed < 0.5) this.torque += this.inertia * 0.001 * (Math.random() - 0.5) //(Math.round(Math.random()) ? 1 : -1) // this.collisionFilter.mask = cat.map | cat.mob | cat.mobBullet | cat.mobShield // | cat.body // //recoil on catching // const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) // player.force.x += momentum.x // player.force.y += momentum.y // // } // } // //grappling hook // if (input.fire && Matter.Query.collides(this, map).length) { // Matter.Body.setPosition(this, Vector.add(this.position, { // x: 20 * Math.cos(this.angle), // y: 20 * Math.sin(this.angle) // })) // if (Matter.Query.collides(this, map).length) { // Matter.Body.setVelocity(this, { // x: 0, // y: 0 // }); // Matter.Sleeping.set(this, true) // this.endCycle = simulation.cycle + 5 // this.dropCaughtPowerUp() // this.do = () => { // //between player nose and the grapple // const sub = Vector.sub(this.vertices[0], { // x: m.pos.x + 30 * Math.cos(m.angle), // y: m.pos.y + 30 * Math.sin(m.angle) // }) // let dist = Vector.magnitude(sub) // if (input.fire) { // // m.fireCDcycle = m.cycle + 30; // cool down if out of energy // m.fireCDcycle = m.cycle + 5 + 40 * b.fireCDscale + 60 * (m.energy < 0.05) // this.endCycle = simulation.cycle + 10 // if (input.down) { //down // dist = 0 // player.force.y += 5 * player.mass * simulation.g; // } // if (m.energy > this.drain) { // Matter.Body.setVelocity(player, { // x: player.velocity.x * 0.8, // y: player.velocity.y * 0.8 // }); // const pull = Vector.mult(Vector.normalise(sub), 0.0008 * Math.min(Math.max(15, dist), 200)) // player.force.x += pull.x // player.force.y += pull.y // if (dist > 500) { // m.energy -= this.drain // if (m.energy < 0) { // this.endCycle = 0; // if (m.cycle + 50 < m.fireCDcycle) m.fireCDcycle = m.cycle + 50 // // refund ammo // b.guns[9].ammo++; // simulation.updateGunHUD(); // // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun // // if (b.guns[i].name === "harpoon") { // // break; // // } // // } // } // } // } // if (tech.isImmuneGrapple && m.immuneCycle < m.cycle + 10) { // m.immuneCycle = m.cycle + 10; // if (m.energy > 0.001) { // m.energy -= 0.001 // } else { //out of energy // Matter.Sleeping.set(this, false) // this.collisionFilter.category = 0 // this.collisionFilter.mask = 0 // this.do = this.returnToPlayer // this.endCycle = simulation.cycle + 60 // m.fireCDcycle = m.cycle + 120; //fire cooldown // //recoil on catching // const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) // player.force.x += momentum.x // player.force.y += momentum.y // } // } // } else { // Matter.Sleeping.set(this, false) // this.collisionFilter.category = 0 // this.collisionFilter.mask = 0 // this.do = this.returnToPlayer // this.endCycle = simulation.cycle + 60 // //recoil on catching // const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) // player.force.x += momentum.x // player.force.y += momentum.y // } // this.draw(); // } // } // } // this.force.x += this.thrustMag * this.mass * Math.cos(this.angle); // this.force.y += this.thrustMag * this.mass * Math.sin(this.angle); // this.draw() // }, // }); // Composite.add(engine.world, bullet[me]); //add bullet to world // }, harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true, thrust = 0.1) { const me = bullet.length; const returnRadius = 100 * Math.sqrt(harpoonSize) bullet[me] = Bodies.fromVertices(where.x, where.y, [{ x: -40 * harpoonSize, y: 2 * harpoonSize, index: 0, isInternal: false }, { x: -40 * harpoonSize, y: -2 * harpoonSize, index: 1, isInternal: false }, { x: 50 * harpoonSize, y: -3 * harpoonSize, index: 3, isInternal: false }, { x: 30 * harpoonSize, y: 2 * harpoonSize, index: 4, isInternal: false }], { cycle: 0, angle: angle, friction: 1, frictionAir: 0.4, // thrustMag: 0.1, drain: tech.isRailEnergy ? 0.0006 : 0.006, turnRate: isReturn ? 0.1 : 0.03, //0.015 drawStringControlMagnitude: 3000 + 5000 * Math.random(), drawStringFlip: (Math.round(Math.random()) ? 1 : -1), dmg: 6, //damage done in addition to the damage from momentum classType: "bullet", endCycle: simulation.cycle + totalCycles * 2.5 + 40, collisionFilter: { category: cat.bullet, mask: tech.isShieldPierce ? cat.map | cat.body | cat.mob | cat.mobBullet : cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield, }, minDmgSpeed: 4, lookFrequency: Math.floor(7 + Math.random() * 3), density: tech.harpoonDensity, //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed beforeDmg(who) { if (tech.isShieldPierce && who.isShielded) { //disable shields who.isShielded = false requestAnimationFrame(() => { who.isShielded = true }); } if (tech.fragments) { b.targetedNail(this.vertices[2], tech.fragments * Math.floor(2 + Math.random())) if (!isReturn) this.endCycle = 0; } if (!who.isBadTarget) { if (isReturn) { this.do = this.returnToPlayer } else { this.frictionAir = 0.01 this.do = () => { this.force.y += this.mass * 0.003; //gravity this.draw(); } } } if (tech.isFoamBall) { for (let i = 0, len = Math.min(50, 2.5 + 3 * Math.sqrt(this.mass)); i < len; i++) { const radius = 5 + 8 * Math.random() const velocity = { x: Math.max(0.5, 2 - radius * 0.1), y: 0 } b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius) } } }, caughtPowerUp: null, dropCaughtPowerUp() { if (this.caughtPowerUp) { this.caughtPowerUp.collisionFilter.category = cat.powerUp this.caughtPowerUp.collisionFilter.mask = cat.map | cat.powerUp this.caughtPowerUp = null } }, onEnd() { if (this.caughtPowerUp && !simulation.isChoosing && (this.caughtPowerUp.name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal)) { let index = null //find index for (let i = 0, len = powerUp.length; i < len; ++i) { if (powerUp[i] === this.caughtPowerUp) index = i } if (index !== null) { powerUps.onPickUp(this.caughtPowerUp); this.caughtPowerUp.effect(); Matter.Composite.remove(engine.world, this.caughtPowerUp); powerUp.splice(index, 1); if (tech.isHarpoonPowerUp) tech.harpoonDensity = 0.004 * 6 //0.006 is normal } else { this.dropCaughtPowerUp() } } else { this.dropCaughtPowerUp() } }, drawToggleHarpoon() { ctx.beginPath(); ctx.moveTo(this.vertices[0].x, this.vertices[0].y); for (let j = 1, len = this.vertices.length; j < len; j += 1) ctx.lineTo(this.vertices[j].x, this.vertices[j].y); ctx.lineTo(this.vertices[0].x, this.vertices[0].y); ctx.lineJoin = "miter" ctx.miterLimit = 100; ctx.lineWidth = 60; ctx.strokeStyle = "rgba(0,255,255,0.25)"; ctx.stroke(); ctx.lineWidth = 20; ctx.strokeStyle = "rgb(0,255,255)"; ctx.stroke(); ctx.lineJoin = "round" ctx.miterLimit = 10 ctx.fillStyle = "#000" ctx.fill(); }, drawString() { const where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } const sub = Vector.sub(where, this.vertices[0]) const perpendicular = Vector.mult(Vector.normalise(Vector.perp(sub)), this.drawStringFlip * Math.min(80, 10 + this.drawStringControlMagnitude / (10 + Vector.magnitude(sub)))) const controlPoint = Vector.add(Vector.add(where, Vector.mult(sub, -0.5)), perpendicular) ctx.strokeStyle = "#000" // "#0ce" ctx.lineWidth = 0.5 ctx.beginPath(); ctx.moveTo(where.x, where.y); ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, this.vertices[0].x, this.vertices[0].y) // ctx.lineTo(this.vertices[0].x, this.vertices[0].y); ctx.stroke(); }, draw() { }, returnToPlayer() { if (Vector.magnitude(Vector.sub(this.position, m.pos)) < returnRadius) { //near player this.endCycle = 0; // if (m.energy < 0.05) { // m.fireCDcycle = m.cycle + 80 * b.fireCDscale; //fire cooldown is much longer when out of energy // } else if (m.cycle + 20 * b.fireCDscale < m.fireCDcycle) { // if (m.energy > 0.05) m.fireCDcycle = m.cycle + 20 * b.fireCDscale //lower cd to 25 if it is above 25 // } //recoil on catching const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) player.force.x += momentum.x player.force.y += momentum.y // refund ammo if (isReturnAmmo) { b.guns[9].ammo++; simulation.updateGunHUD(); // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun // if (b.guns[i].name === "harpoon") { // break; // } // } } } else { const sub = Vector.sub(this.position, m.pos) const rangeScale = 1 + 0.000001 * Vector.magnitude(sub) * Vector.magnitude(sub) //return faster when far from player const returnForce = Vector.mult(Vector.normalise(sub), rangeScale * thrust * this.mass) if (m.energy > this.drain) m.energy -= this.drain if (m.energy < 0.05) { this.force.x -= returnForce.x * 0.15 this.force.y -= returnForce.y * 0.15 } else { //if (m.cycle + 20 * b.fireCDscale < m.fireCDcycle) this.force.x -= returnForce.x this.force.y -= returnForce.y } this.grabPowerUp() } this.draw(); }, grabPowerUp() { //grab power ups near the tip of the harpoon if (this.caughtPowerUp) { Matter.Body.setPosition(this.caughtPowerUp, Vector.add(this.vertices[2], this.velocity)) Matter.Body.setVelocity(this.caughtPowerUp, { x: 0, y: 0 }) } else { //&& simulation.cycle % 2 for (let i = 0, len = powerUp.length; i < len; ++i) { const radius = powerUp[i].circleRadius + 50 if (Vector.magnitudeSquared(Vector.sub(this.vertices[2], powerUp[i].position)) < radius * radius && !powerUp[i].isGrabbed) { if (powerUp[i].name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal) { powerUp[i].isGrabbed = true this.caughtPowerUp = powerUp[i] Matter.Body.setVelocity(powerUp[i], { x: 0, y: 0 }) Matter.Body.setPosition(powerUp[i], this.vertices[2]) powerUp[i].collisionFilter.category = 0 powerUp[i].collisionFilter.mask = 0 thrust *= 0.6 this.endCycle += 0.5 //it pulls back slower, so this prevents it from ending early break //just pull 1 power up if possible } } } } }, do() { this.cycle++ if (isReturn || target) { if (isReturn) { if (this.cycle > totalCycles) { //return to player //|| !input.fire this.do = this.returnToPlayer if (this.angularSpeed < 0.5) this.torque += this.inertia * 0.001 * (Math.random() - 0.5) //(Math.round(Math.random()) ? 1 : -1) Matter.Sleeping.set(this, false) this.endCycle = simulation.cycle + 240 const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.00015 : 0.0003)) //recoil on jerking line player.force.x += momentum.x player.force.y += momentum.y requestAnimationFrame(() => { //delay this for 1 cycle to get the proper hit graphics this.collisionFilter.category = 0 this.collisionFilter.mask = 0 }); } else { this.grabPowerUp() } } if (target) { //rotate towards the target const face = { x: Math.cos(this.angle), y: Math.sin(this.angle) }; const vectorGoal = Vector.normalise(Vector.sub(this.position, target.position)); if (Vector.cross(vectorGoal, face) > 0) { Matter.Body.rotate(this, this.turnRate); } else { Matter.Body.rotate(this, -this.turnRate); } } this.force.x += thrust * this.mass * Math.cos(this.angle); this.force.y += thrust * this.mass * Math.sin(this.angle); } this.draw() }, }); if (!isReturn && !target) { Matter.Body.setVelocity(bullet[me], { x: m.Vx / 2 + 600 * thrust * Math.cos(bullet[me].angle), y: m.Vy / 2 + 600 * thrust * Math.sin(bullet[me].angle) }); bullet[me].frictionAir = 0.002 bullet[me].do = function () { if (this.speed < 20) this.force.y += 0.0005 * this.mass; this.draw(); } } if (tech.isHarpoonPowerUp && bullet[me].density > 0.01) { if (isReturn) { bullet[me].draw = function () { this.drawToggleHarpoon() this.drawString() } } else { bullet[me].draw = function () { this.drawToggleHarpoon() } } } else if (isReturn) { bullet[me].draw = function () { this.drawString() } } Composite.add(engine.world, bullet[me]); //add bullet to world }, missile(where, angle, speed, size = 1) { if (tech.isMissileBig) { size *= 1.55 if (tech.isMissileBiggest) { size *= 1.55 } } 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: simulation.cycle + Math.floor((230 + 40 * Math.random()) * tech.bulletsLastLonger + 120 * tech.isMissileBiggest + 60 * tech.isMissileBig), collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield }, minDmgSpeed: 10, lookFrequency: Math.floor(10 + Math.random() * 3), explodeRad: (tech.isMissileBig ? 230 : 180) + 60 * Math.random(), density: 0.02, //0.001 is normal beforeDmg() { Matter.Body.setDensity(this, 0.0001); //reduce density to normal this.tryToLockOn(); this.endCycle = 0; //bullet ends cycle after doing damage // also triggers explosion }, onEnd() { b.explosion(this.position, this.explodeRad * size); //makes bullet do explosive damage at end if (tech.fragments) b.targetedNail(this.position, tech.fragments * Math.floor(2 + 1.5 * Math.random())) }, lockedOn: null, tryToLockOn() { let closeDist = Infinity; const futurePos = Vector.add(this.position, Vector.mult(this.velocity, 30)) //look for closest target to where the missile will be in 30 cycles 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].isBadTarget && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable ) { 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(m.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(m.dmgScale * 4 * size); //does extra damage to target } }, do() { if (!(m.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)); const dot = Vector.dot(target, face) const aim = Math.min(0.08, (1 + dot) * 1) if (Vector.cross(target, face) > 0) { Matter.Body.rotate(this, aim); } else { Matter.Body.rotate(this, -aim); } this.frictionAir = Math.min(0.1, Math.max(0.04, 1 + dot)) //0.08; //extra friction if turning } //accelerate in direction bullet is facing const dir = this.angle; this.force.x += thrust * Math.cos(dir); this.force.y += thrust * Math.sin(dir); 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(); }, }); const thrust = 0.0066 * bullet[me].mass * (tech.isMissileBig ? (tech.isMissileBiggest ? 0.3 : 0.7) : 1); Matter.Body.setVelocity(bullet[me], { x: m.Vx / 2 + speed * Math.cos(angle), y: m.Vy / 2 + speed * Math.sin(angle) }); Composite.add(engine.world, bullet[me]); //add bullet to world }, lastAngle: 0, wasExtruderOn: false, isExtruderOn: false, didExtruderDrain: false, canExtruderFire: true, extruder() { const DRAIN = 0.0012 if (m.energy > DRAIN && b.canExtruderFire) { m.energy -= DRAIN if (m.energy < 0) { m.fieldCDcycle = m.cycle + 120; m.energy = 0; } b.isExtruderOn = true const SPEED = 8 + 12 * tech.isPlasmaRange const me = bullet.length; const where = Vector.add(m.pos, player.velocity) bullet[me] = Bodies.polygon(where.x + 20 * Math.cos(m.angle), where.y + 20 * Math.sin(m.angle), 4, 0.01, { cycle: -0.5, isWave: true, endCycle: simulation.cycle + 40, // + 30 * tech.isPlasmaRange, inertia: Infinity, frictionAir: 0, isInHole: true, //this keeps the bullet from entering wormholes minDmgSpeed: 0, dmg: m.dmgScale * 2.7, //damage also changes when you divide by mob.mass on in .do() classType: "bullet", isBranch: false, restitution: 0, collisionFilter: { // category: 0, // mask: 0, //cat.mob | cat.mobBullet | cat.mobShield category: 0, //cat.bullet, mask: 0, //cat.map, //cat.mob | cat.mobBullet | cat.mobShield }, beforeDmg() { }, onEnd() { }, do() { if (this.endCycle < simulation.cycle + 1) this.isWave = false if (Matter.Query.point(map, this.position).length) { //check if inside map //|| Matter.Query.point(body, this.position).length this.isBranch = true; this.do = () => { if (this.endCycle < simulation.cycle + 1) this.isWave = false } } else { //check if inside a mob for (let i = 0, len = mob.length; i < len; i++) { const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)) const radius = mob[i].radius + tech.extruderRange / 2 if (dist < radius * radius) { if (mob[i].speed > 2) { if (mob[i].isBoss || mob[i].isShielded) { Matter.Body.setVelocity(mob[i], { x: mob[i].velocity.x * 0.95, y: mob[i].velocity.y * 0.95 }); } else { Matter.Body.setVelocity(mob[i], { x: mob[i].velocity.x * 0.25, y: mob[i].velocity.y * 0.25 }); } } // Matter.Body.setPosition(this, Vector.add(this.position, mob[i].velocity)) //move with the medium let dmg = this.dmg / Math.min(10, mob[i].mass) mob[i].damage(dmg); if (mob[i].alive) mob[i].foundPlayer(); } } } this.cycle++ const wiggleMag = (m.crouch ? 6 : 12) * Math.cos(simulation.cycle * 0.09) const wiggle = Vector.mult(transverse, wiggleMag * Math.cos(this.cycle * 0.36)) //+ wiggleMag * Math.cos(simulation.cycle * 0.3)) const velocity = Vector.mult(player.velocity, 0.4) //move with player Matter.Body.setPosition(this, Vector.add(velocity, Vector.add(this.position, wiggle))) } }); Composite.add(engine.world, bullet[me]); //add bullet to world Matter.Body.setVelocity(bullet[me], { x: SPEED * Math.cos(m.angle), y: SPEED * Math.sin(m.angle) }); const transverse = Vector.normalise(Vector.perp(bullet[me].velocity)) if (180 - Math.abs(Math.abs(b.lastAngle - m.angle) - 180) > 0.13 || !b.wasExtruderOn) { bullet[me].isBranch = true; //don't draw stroke for this bullet bullet[me].do = function () { if (this.endCycle < simulation.cycle + 1) this.isWave = false } } b.lastAngle = m.angle //track last angle for the above angle difference calculation } else { b.canExtruderFire = false; } }, plasma() { const DRAIN = 0.00075 if (m.energy > DRAIN) { m.energy -= DRAIN; if (m.energy < 0) { m.fieldCDcycle = m.cycle + 120; m.energy = 0; } //calculate laser collision let best; let range = tech.isPlasmaRange * (120 + (m.crouch ? 400 : 300) * Math.sqrt(Math.random())) //+ 100 * Math.sin(m.cycle * 0.3); // const dir = m.angle // + 0.04 * (Math.random() - 0.5) const path = [{ x: m.pos.x + 20 * Math.cos(m.angle), y: m.pos.y + 20 * Math.sin(m.angle) }, { x: m.pos.x + range * Math.cos(m.angle), y: m.pos.y + range * Math.sin(m.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 = simulation.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 = simulation.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.9 * m.dmgScale; //********** SCALE DAMAGE HERE ********************* best.who.damage(dmg); best.who.locatePlayer(); //push mobs away const force = Vector.mult(Vector.normalise(Vector.sub(m.pos, path[1])), -0.01 * Math.min(5, best.who.mass)) Matter.Body.applyForce(best.who, path[1], force) if (best.who.speed > 4) { Matter.Body.setVelocity(best.who, { //friction x: best.who.velocity.x * 0.9, y: best.who.velocity.y * 0.9 }); } //draw mob damage circle simulation.drawList.push({ x: path[1].x, y: path[1].y, radius: Math.sqrt(2000 * dmg * best.who.damageReduction), color: "rgba(255,0,255,0.2)", time: simulation.drawTime * 4 }); } else if (!best.who.isStatic) { //push blocks away const force = Vector.mult(Vector.normalise(Vector.sub(m.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 const Dx = Math.cos(m.angle); const Dy = Math.sin(m.angle); let x = m.pos.x + 20 * Dx; let y = m.pos.y + 20 * Dy; ctx.beginPath(); ctx.moveTo(x, y); const step = Vector.magnitude(Vector.sub(path[0], path[1])) / 10 for (let i = 0; i < 8; i++) { x += step * (Dx + 1.5 * (Math.random() - 0.5)) y += step * (Dy + 1.5 * (Math.random() - 0.5)) ctx.lineTo(x, y); } ctx.lineWidth = 2 * Math.random(); ctx.stroke(); } }, laser(where = { x: m.pos.x + 20 * Math.cos(m.angle), y: m.pos.y + 20 * Math.sin(m.angle) }, whereEnd = { x: where.x + 3000 * Math.cos(m.angle), y: where.y + 3000 * Math.sin(m.angle) }, dmg = tech.laserDamage, reflections = tech.laserReflections, isThickBeam = false, push = 1) { const reflectivity = 1 - 1 / (reflections * 3) let damage = m.dmgScale * dmg let best = { x: 1, y: 1, dist2: Infinity, who: null, v1: 1, v2: 1 }; 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 = simulation.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 = simulation.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: 1, y: 1, dist2: Infinity, who: null, v1: 1, v2: 1 }; 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.locatePlayer(); if (best.who.damageReduction) { if ( //iridescence tech.laserCrit && !best.who.shield && Vector.dot(Vector.normalise(Vector.sub(best.who.position, path[path.length - 1])), Vector.normalise(Vector.sub(path[path.length - 1], path[path.length - 2]))) > 0.999 - 0.5 / best.who.radius ) { damage *= 1 + tech.laserCrit simulation.drawList.push({ //add dmg to draw queue x: path[path.length - 1].x, y: path[path.length - 1].y, radius: Math.sqrt(2500 * damage * best.who.damageReduction) + 5, color: `hsla(${60 + 283 * Math.random()},100%,70%,0.5)`, // random hue, but not red time: 16 }); } else { simulation.drawList.push({ //add dmg to draw queue x: path[path.length - 1].x, y: path[path.length - 1].y, radius: Math.sqrt(2000 * damage * best.who.damageReduction) + 2, color: tech.laserColorAlpha, time: simulation.drawTime }); } best.who.damage(damage); } if (tech.isLaserPush) { //push mobs away const index = path.length - 1 Matter.Body.setVelocity(best.who, { x: best.who.velocity.x * 0.97, y: best.who.velocity.y * 0.97 }); const force = Vector.mult(Vector.normalise(Vector.sub(path[index], path[Math.max(0, index - 1)])), 0.003 * push * Math.min(6, best.who.mass)) Matter.Body.applyForce(best.who, path[index], force) } } else if (tech.isLaserPush && best.who.classType === "body") { const index = path.length - 1 Matter.Body.setVelocity(best.who, { x: best.who.velocity.x * 0.97, y: best.who.velocity.y * 0.97 }); const force = Vector.mult(Vector.normalise(Vector.sub(path[index], path[Math.max(0, index - 1)])), 0.003 * push * Math.min(6, best.who.mass)) Matter.Body.applyForce(best.who, path[index], force) } }; 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 rid 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 if (tech.isLaserLens && b.guns[11].lensDamage !== 1) { ctx.strokeStyle = tech.laserColor; ctx.lineWidth = 2 ctx.lineDashOffset = 900 * 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([]); // ctx.globalAlpha = 1; //glow ctx.lineWidth = 9 + 2 * b.guns[11].lensDamageOn ctx.globalAlpha = 0.13 ctx.beginPath(); 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); } ctx.stroke(); ctx.globalAlpha = 1; } else { ctx.strokeStyle = tech.laserColor; ctx.lineWidth = 2 ctx.lineDashOffset = 900 * 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([]); ctx.globalAlpha = 1; } }, AoEStunEffect(where, range, cycles = 120 + 60 * Math.random()) { for (let i = 0, len = mob.length; i < len; ++i) { if (mob[i].alive && !mob[i].isShielded && !mob[i].shield && !mob[i].isBadTarget) { if (Vector.magnitude(Vector.sub(where, mob[i].position)) - mob[i].radius < range) mobs.statusStun(mob[i], cycles) } } simulation.drawList.push({ x: where.x, y: where.y, radius: range, color: "rgba(0,0,0,0.1)", time: 15 }); }, laserMine(position, velocity = { x: 0, y: -8 }) { const me = bullet.length; bullet[me] = Bodies.polygon(position.x, position.y, 3, 25, { bulletType: "laser mine", angle: m.angle, friction: 0, frictionAir: 0.025, restitution: 0.5, dmg: 0, // 0.14 //damage done in addition to the damage from momentum minDmgSpeed: 2, lookFrequency: 67 + Math.floor(7 * Math.random()), drain: 0.7 * tech.laserDrain, isDetonated: false, torqueMagnitude: 0.000003 * (Math.round(Math.random()) ? 1 : -1), range: 1500, endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield }, beforeDmg() { }, onEnd() { }, do() { if (!(simulation.cycle % this.lookFrequency) && m.energy > this.drain) { //find mob targets for (let i = 0, len = mob.length; i < len; ++i) { if ( Vector.magnitude(Vector.sub(this.position, mob[i].position)) < 1300 && !mob[i].isBadTarget && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0 ) { if (tech.isStun) b.AoEStunEffect(this.position, 1300); //AoEStunEffect(where, range, cycles = 90 + 60 * Math.random()) { this.do = this.laserSpin if (this.angularSpeed < 0.5) this.torque += this.inertia * this.torqueMagnitude * 200 //spin this.endCycle = simulation.cycle + 360 + 120 // if (this.angularSpeed < 0.01) this.torque += this.inertia * this.torqueMagnitude * 5 //spin this.isDetonated = true break } } } }, reflections: Math.max(0, tech.laserReflections - 2), laserSpin() { //drain energy if (m.energy > this.drain) { m.energy -= this.drain if (this.angularSpeed < 0.05) this.torque += this.inertia * this.torqueMagnitude //spin //fire lasers ctx.strokeStyle = tech.laserColor; ctx.lineWidth = 1.5 // ctx.globalAlpha = 1; ctx.beginPath(); for (let i = 0; i < 3; i++) { const where = this.vertices[i] const endPoint = Vector.add(where, Vector.mult(Vector.normalise(Vector.sub(where, this.position)), 2500)) b.laser(where, endPoint, tech.laserDamage * 13, this.reflections, true) } ctx.stroke(); // ctx.globalAlpha = 1; } if (this.endCycle - 60 < simulation.cycle) { this.do = () => { } //no nothing, no laser, no spin } }, }) Matter.Body.setVelocity(bullet[me], velocity); Composite.add(engine.world, bullet[me]); //add bullet to world }, mine(where, velocity, angle = 0) { 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 //doesn't collide with other bullets until it lands (was crashing into bots) }, minDmgSpeed: 5, stillCount: 0, isArmed: false, endCycle: Infinity, lookFrequency: 0, range: 700 - 300 * tech.isFoamMine, beforeDmg() { }, onEnd() { if (this.isArmed && !tech.isMineSentry) { if (tech.isFoamMine) { //send 14 in random directions slowly for (let i = 0; i < 12; i++) { const radius = 13 + 8 * Math.random() const velocity = { x: 0.5 + 5.5 * Math.random(), y: 0 } b.foam(this.position, Vector.rotate(velocity, this.angle + 1.57 + 3 * (Math.random() - 0.5)), radius) //6.28 * Math.random() } //send 40 targeted let count = 0 let cycle = () => { if (count < 50) { if (!simulation.paused && !simulation.isChoosing) { //!(simulation.cycle % 1) && count++ b.targetedFoam(this.position) } requestAnimationFrame(cycle); } } requestAnimationFrame(cycle) } else if (tech.isSuperMine) { b.targetedBall(this.position, 22 + 2 * tech.extraSuperBalls) } else { b.targetedNail(this.position, 22, 40 + 10 * Math.random(), 1200, true, 2.2) //targetedNail(position, num = 1, speed = 40 + 10 * Math.random(), range = 1200, isRandomAim = true, damage = 1.4) { } } }, 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)) for (let j = 0; j < 10; j++) { //move until touching map again after rotation 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.setVelocity(this, { x: 0, y: 0 }); Matter.Body.setStatic(this, true) //don't set to static if not touching map this.collisionFilter.category = 0 this.collisionFilter.mask = 0 //cat.map | cat.bullet } 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 setTimeout(() => { if (Matter.Query.collides(this, map).length === 0 || Matter.Query.point(map, this.position).length > 0) { this.endCycle = 0 // if not touching map explode this.isArmed = false b.mine(this.position, this.velocity, this.angle) } }, 100); break } Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(collide[i].normal, 2))) //move until you are touching the wall } break } } } else { if (this.speed < 1 && this.angularSpeed < 0.01) this.stillCount++ } if (this.stillCount > 25) this.arm(); }, arm() { this.collisionFilter.mask = cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield | cat.bullet //can now collide with other bullets this.lookFrequency = simulation.cycle + 60 this.do = function () { //overwrite the do method for this bullet this.force.y += this.mass * 0.002; //extra gravity if (simulation.cycle > this.lookFrequency) { this.isArmed = true this.lookFrequency = 55 + Math.floor(22 * Math.random()) simulation.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 //make mobs think the mine is where the player is // for (let i = 0; i < mob.length; i++) { // mob[i].seePlayer.recall = mob[i].memory + Math.round(mob[i].memory * Math.random()); //cycles before mob falls a sleep // mob[i].seePlayer.position.x = this.position.x; // mob[i].seePlayer.position.y = this.position.y; // mob[i].seePlayer.yes = true; // } this.force.y += this.mass * 0.002; //extra gravity if (!(simulation.cycle % this.lookFrequency)) { //find mob targets const random = 300 * Math.random() for (let i = 0, len = mob.length; i < len; ++i) { if ( !mob[i].isBadTarget && Vector.magnitude(Vector.sub(this.position, mob[i].position)) < this.range + mob[i].radius + random && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0 ) { if (tech.isStun) b.AoEStunEffect(this.position, this.range + mob[i].radius + random); //AoEStunEffect(where, range, cycles = 90 + 60 * Math.random()) { if (tech.isMineSentry) { this.lookFrequency = Math.floor(7 + 7 * b.fireCDscale + 10 * (tech.oneSuperBall && tech.isSuperMine) + Math.floor(3 * Math.random())) // this.endCycle = Infinity this.shots = tech.sentryAmmo this.do = function () { //overwrite the do method for this bullet this.force.y += this.mass * 0.002; //extra gravity if (!(simulation.cycle % this.lookFrequency)) { //find mob targets if (tech.isFoamMine) { this.shots -= 0.6 * b.targetedFoam(this.position, 1, 21 + 7 * Math.random(), 1200, false) b.targetedFoam(this.position, 1, 21 + 7 * Math.random(), 1200, false) } else if (tech.isSuperMine) { const cost = tech.oneSuperBall ? 2 : 0.7 this.shots -= cost * b.targetedBall(this.position, 1, 42 + 12 * Math.random(), 1200, false) for (let i = 0, len = tech.extraSuperBalls / 4; i < len; i++) { if (Math.random() < 0.33) b.targetedBall(this.position, 1, 42 + 12 * Math.random(), 1200, false) } } else { this.shots -= b.targetedNail(this.position, 1, 45 + 5 * Math.random(), 1100, false, 2.3) //targetedNail(position, num = 1, speed = 40 + 10 * Math.random(), range = 1200, isRandomAim = true, damage = 1.4) { } if (this.shots < 0) this.endCycle = 0 if (!(simulation.cycle % (this.lookFrequency * 6))) { simulation.drawList.push({ x: this.position.x, y: this.position.y, radius: 8, color: "#fe0", time: 4 }); } } } break } else { this.endCycle = 0 //end life if mob is near and visible break } } } } } } } }, }); bullet[bIndex].torque += bullet[bIndex].inertia * 0.0002 * (0.5 - Math.random()) Matter.Body.setVelocity(bullet[bIndex], velocity); Composite.add(engine.world, bullet[bIndex]); //add bullet to world }, worm(where, isFreeze = tech.isSporeFreeze) { //used with the tech upgrade in mob.death() const bIndex = bullet.length; const wormSize = 6 + tech.wormSize * 4.2 * Math.random() if (bIndex < 500) { //can't make over 500 spores bullet[bIndex] = Bodies.polygon(where.x, where.y, 3, 3, { inertia: Infinity, isFreeze: isFreeze, restitution: 0.5, // angle: Math.random() * 2 * Math.PI, friction: 0, frictionAir: 0.025, thrust: (tech.isSporeFollow ? 0.0012 : 0.00055) * (1 + 0.5 * (Math.random() - 0.5)), wormSize: wormSize, wormTail: 1 + Math.max(4, Math.min(wormSize - 2 * tech.wormSize, 30)), dmg: (tech.isMutualism ? 9.5 : 3.2) * wormSize * (tech.isJunkDNA ? 1 + 0.53 * tech.junkCount : 1), lookFrequency: 100 + Math.floor(37 * Math.random()), classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.mob | cat.mobBullet | cat.mobShield //no collide with body }, endCycle: simulation.cycle + Math.floor((600 + Math.floor(Math.random() * 420)) * tech.bulletsLastLonger), minDmgSpeed: 0, playerOffPosition: { //used when moving towards player to keep spores separate x: 100 * (Math.random() - 0.5), y: 100 * (Math.random() - 0.5) }, beforeDmg(who) { if (tech.isSpawnBulletsOnDeath && who.alive && who.isDropPowerUp) { setTimeout(() => { if (!who.alive) { for (let i = 0; i < 3; i++) { //spawn 3 more b.worm(this.position) bullet[bullet.length - 1].endCycle = Math.min(simulation.cycle + Math.floor(420 * tech.bulletsLastLonger), this.endCycle + 180 + Math.floor(60 * Math.random())) //simulation.cycle + Math.floor(420 * tech.bulletsLastLonger) } } this.endCycle = 0; //bullet ends cycle after doing damage }, 1); } else { this.endCycle = 0; //bullet ends cycle after doing damage } if (this.isFreeze) mobs.statusSlow(who, 90) }, onEnd() { if (tech.isMutualism && this.isMutualismActive && !tech.isEnergyHealth) { m.health += 0.02 if (m.health > m.maxHealth) m.health = m.maxHealth; m.displayHealth(); } }, tailCycle: 6.28 * Math.random(), do() { this.tailCycle += this.speed * 0.025 ctx.beginPath(); //draw nematode ctx.moveTo(this.position.x, this.position.y); // const dir = Vector.mult(Vector.normalise(this.velocity), -Math.min(100, this.wormTail * this.speed)) const speed = Math.min(7, this.speed) const dir = Vector.mult(Vector.normalise(this.velocity), -0.6 * this.wormTail * speed) const tail = Vector.add(this.position, dir) const wiggle = Vector.add(Vector.add(tail, dir), Vector.rotate(dir, Math.sin(this.tailCycle))) // const wiggle = Vector.add(tail, Vector.rotate(dir, Math.sin((m.cycle - this.endCycle) * 0.03 * this.speed))) ctx.quadraticCurveTo(tail.x, tail.y, wiggle.x, wiggle.y) // ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, this.vertices[0].x, this.vertices[0].y) // ctx.lineTo(tail.x, tail.y); ctx.lineWidth = this.wormSize; ctx.strokeStyle = "#000"; ctx.stroke(); 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 (!(simulation.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].isBadTarget && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable) { 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 (tech.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 - m.pos.x; const dy = this.position.y - m.pos.y; if (dx * dx + dy * dy > 10000) { this.force = Vector.mult(Vector.normalise(Vector.sub(m.pos, Vector.add(this.playerOffPosition, this.position))), this.mass * this.thrust) } } else { const unit = Vector.normalise(this.velocity) const force = Vector.mult(Vector.rotate(unit, 0.005 * this.playerOffPosition.x), 0.000003) this.force.x += force.x this.force.y += force.y } } }, }); const SPEED = 2 + 1 * Math.random(); const ANGLE = 2 * Math.PI * Math.random() Matter.Body.setVelocity(bullet[bIndex], { x: SPEED * Math.cos(ANGLE), y: SPEED * Math.sin(ANGLE) }); Composite.add(engine.world, bullet[bIndex]); //add bullet to world if (tech.isMutualism && m.health > 0.04) { m.health -= 0.02 m.displayHealth(); bullet[bIndex].isMutualismActive = true } } }, spore(where, isFreeze = tech.isSporeFreeze) { //used with the tech upgrade in mob.death() const bIndex = bullet.length; const size = 4 if (bIndex < 500) { //can't make over 500 spores bullet[bIndex] = Bodies.polygon(where.x, where.y, size, size, { // 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: (tech.isSporeFollow ? 0.0011 : 0.0005) * (1 + 0.3 * (Math.random() - 0.5)), dmg: (tech.isMutualism ? 20 : 7) * (tech.isJunkDNA ? 1 + 0.53 * tech.junkCount : 1), //bonus damage from tech.isMutualism lookFrequency: 100 + Math.floor(117 * Math.random()), classType: "bullet", isSpore: true, collisionFilter: { category: cat.bullet, mask: cat.map | cat.mob | cat.mobBullet | cat.mobShield //no collide with body }, endCycle: simulation.cycle + Math.floor((540 + Math.floor(Math.random() * 420)) * tech.bulletsLastLonger), minDmgSpeed: 0, playerOffPosition: { //used when moving towards 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, 90) }, onEnd() { if (tech.isMutualism && this.isMutualismActive && !tech.isEnergyHealth) { m.health += 0.01 if (m.health > m.maxHealth) m.health = m.maxHealth; m.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 (!(simulation.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].isBadTarget && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable) { 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 (tech.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 - m.pos.x; const dy = this.position.y - m.pos.y; if (dx * dx + dy * dy > 10000) { this.force = Vector.mult(Vector.normalise(Vector.sub(m.pos, Vector.add(this.playerOffPosition, this.position))), this.mass * this.thrust) } } else { this.force.y += this.mass * 0.0001; //gravity } } // if (!this.lockedOn && !(simulation.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].isDropPowerUp && 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 (tech.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 - m.pos.x; // const dy = this.position.y - m.pos.y; // if (dx * dx + dy * dy > 10000) { // this.force = Vector.mult(Vector.normalise(Vector.sub(m.pos, Vector.add(this.playerOffPosition, this.position))), this.mass * this.thrust) // } // // this.force = Vector.mult(Vector.normalise(Vector.sub(m.pos, this.position)), this.mass * this.thrust) // } else { // this.force.y += this.mass * 0.0001; //gravity // } // if (this.nextPortCycle < simulation.cycle) { //teleport around if you have tech.isBulletTeleport // this.nextPortCycle = simulation.cycle + this.portFrequency // const range = 50 * Math.random() // Matter.Body.setPosition(this, Vector.add(this.position, Vector.rotate({ x: range, y: 0 }, 2 * Math.PI * Math.random()))) // } }, }); // if (tech.isBulletTeleport) { // bullet[bIndex].portFrequency = 10 + Math.floor(5 * Math.random()) // bullet[bIndex].nextPortCycle = simulation.cycle + bullet[bIndex].portFrequency // } 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) }); Composite.add(engine.world, bullet[bIndex]); //add bullet to world if (tech.isMutualism && m.health > 0.01) { m.health -= 0.01 m.displayHealth(); bullet[bIndex].isMutualismActive = true } } }, iceIX(speed = 0, dir = m.angle + Math.PI * 2 * Math.random(), where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }) { const me = bullet.length; const THRUST = 0.0018 const RADIUS = 18 const SCALE = 1 - 0.12 / tech.bulletsLastLonger bullet[me] = Bodies.polygon(where.x, where.y, 3, RADIUS, { angle: dir - Math.PI, // inertia: Infinity, spin: 0.00004 * (0.1 + Math.random()) * (Math.round(Math.random()) ? 1 : -1), friction: 0, frictionAir: 0.02, restitution: 0.9, dmg: 1.3, //damage done in addition to the damage from momentum lookFrequency: 14 + Math.floor(8 * Math.random()), endCycle: simulation.cycle + 65 * tech.bulletsLastLonger + Math.floor(25 * Math.random()), classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield //self collide }, minDmgSpeed: 0, lockedOn: null, beforeDmg(who) { if (tech.iceEnergy && !who.shield && !who.isShielded && who.isDropPowerUp && who.alive && m.immuneCycle < m.cycle) { setTimeout(() => { if (!who.alive) m.energy += tech.iceEnergy * 0.8 }, 10); } mobs.statusSlow(who, tech.iceIXFreezeTime) this.endCycle = simulation.cycle // if (tech.isHeavyWater) mobs.statusDoT(who, 0.15, 300) }, onEnd() { }, do() { // this.force.y += this.mass * 0.0002; //find mob targets if (!(simulation.cycle % this.lookFrequency)) { 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].isBadTarget && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable ) { 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.lockedOn.position, this.position)), this.mass * THRUST) } else { this.force = Vector.mult(Vector.normalise(this.velocity), this.mass * THRUST) } this.torque += this.inertia * this.spin } }) Composite.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.setAngularVelocity(bullet[me], 3000 * bullet[me].spin); // Matter.Body.setVelocity(bullet[me], { // x: m.Vx / 2 + speed * Math.cos(dir), // y: m.Vy / 2 + speed * Math.sin(dir) // }); }, flea(where, velocity, radius = 6 + 3 * Math.random() + 10 * tech.wormSize * Math.random()) { const me = bullet.length; bullet[me] = Bodies.polygon(where.x, where.y, 5, radius, { isFlea: true, angle: 0.5 * Math.random(), friction: 1, frictionStatic: 1, frictionAir: 0, //0.01, restitution: 0, density: 0.0005, // 0.001 is normal density lookFrequency: 19 + Math.floor(7 * Math.random()), endCycle: simulation.cycle + Math.floor((900 * tech.bulletsLastLonger + 420 * Math.random()) + Math.max(0, 150 - bullet.length)), // 13 - 19s classType: "bullet", collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield }, minDmgSpeed: 0, lockedOn: null, delay: 50, cd: simulation.cycle + 10, dmg: 0, setDamage() { //dmg is set to zero after doing damage once, and set back to normal after jumping this.dmg = radius * (tech.isMutualism ? 2.9 : 1) * (tech.isJunkDNA ? 1 + 0.53 * tech.junkCount : 1) //damage done in addition to the damage from momentum //spores do 7 dmg, worms do 18 }, beforeDmg(who) { Matter.Body.setVelocity(this, Vector.mult(Vector.normalise(Vector.sub(this.position, who.position)), 10 + 10 * Math.random())); //push away from target this.endCycle -= 130 this.cd = simulation.cycle + this.delay; if (tech.isSporeFreeze) mobs.statusSlow(who, 90) if (tech.isSpawnBulletsOnDeath && who.alive && who.isDropPowerUp) { setTimeout(() => { if (!who.alive) { for (let i = 0; i < 2; i++) { //spawn 2 more const speed = 10 + 5 * Math.random() const angle = 2 * Math.PI * Math.random() b.flea(this.position, { x: speed * Math.cos(angle), y: speed * Math.sin(angle) }) } } this.endCycle = 0; }, 1); } setTimeout(() => { this.dmg = 0 }) }, onEnd() { if (tech.isMutualism && this.isMutualismActive && !tech.isEnergyHealth) { m.health += 0.02 if (m.health > m.maxHealth) m.health = m.maxHealth; m.displayHealth(); } }, gravity: 0.002 + 0.002 * tech.isSporeFollow, do() { this.force.y += this.gravity * this.mass if (this.cd < simulation.cycle && (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length)) { //if on the ground and not on jump cooldown // this.cd = simulation.cycle + this.delay; this.lockedOn = null; //find a target let closeDist = Infinity; for (let i = 0, len = mob.length; i < len; ++i) { if ( !mob[i].isBadTarget && !mob[i].isInvulnerable && mob[i].alive && this.position.y - mob[i].position.y < 1500 && //this is about how high fleas can jump with capMaxY = 0.12 + 0.04 * Math.random() this.position.y - mob[i].position.y > -300 && //not too far below the flea (note that fleas should be on the ground most of the time when doing this check) 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 (tech.isSporeFollow && !this.lockedOn && Matter.Query.ray(map, this.position, m.pos).length === 0) { this.lockedOn = { //make target player if there are no mobs to target position: m.pos, velocity: { x: 0, y: 0 } } } if (this.lockedOn) { //hop towards mob target const where = Vector.add(this.lockedOn.position, Vector.mult(this.lockedOn.velocity, 5)) //estimate where the mob will be in 5 cycles const Dy = Math.max(0, this.position.y - where.y) //can't be negative because you can't hop down const Dx = this.position.x - where.x const Vx = -0.06 * Dx / Math.sqrt(2 * Dy / this.gravity) //calibrated to hit target, don't mess with this const Vy = 0.085 * Math.sqrt(this.gravity * Dy) //calibrated to hit target, don't mess with this const capX = 0.07 + 0.02 * tech.isSporeFollow const capMaxY = 0.12 + 0.04 * Math.random() + 0.05 * tech.isSporeFollow const capMinY = closeDist > 500 ? 0.05 + 0.02 * Math.random() : 0.02 + 0.01 * Math.random() //don't jump super low, unless you are very close to mob target this.force.x = Math.max(-capX, Math.min(capX, Vx)) * this.mass; this.force.y = -Math.max(capMinY, Math.min(capMaxY, Vy)) * this.mass } else { //random hops if (Math.random() < 0.5) { //chance to continue in the same horizontal direction this.force.x = (0.01 + 0.03 * Math.random()) * this.mass * (this.velocity.x > 0 ? 1 : -1); //random move } else { this.force.x = (0.01 + 0.03 * Math.random()) * this.mass * (Math.random() < 0.5 ? 1 : -1); //random move } this.force.y = -(0.03 + 0.08 * Math.random()) * this.mass } Matter.Body.setVelocity(this, { x: 0, y: 0 }); this.setDamage() //after jumping damage is no longer zero } } }) Composite.add(engine.world, bullet[me]); //add bullet to world Matter.Body.setVelocity(bullet[me], velocity); if (tech.isMutualism && m.health > 0.01) { m.health -= 0.01 m.displayHealth(); bullet[bullet.length - 1].isMutualismActive = true } }, delayDrones(where, droneCount = 1) { let respawnDrones = () => { if (droneCount > 0) { requestAnimationFrame(respawnDrones); if (!simulation.paused && !simulation.isChoosing && m.alive) { droneCount-- if (tech.isDroneRadioactive) { b.droneRadioactive({ x: where.x + 50 * (Math.random() - 0.5), y: where.y + 50 * (Math.random() - 0.5) }, 0) } else { b.drone({ x: where.x + 50 * (Math.random() - 0.5), y: where.y + 50 * (Math.random() - 0.5) }, 0) if (tech.isDroneGrab && deliveryCount > 0) { const who = bullet[bullet.length - 1] who.isImproved = true; const SCALE = 2.25 Matter.Body.scale(who, SCALE, SCALE); who.lookFrequency = 30 + Math.floor(11 * Math.random()); who.endCycle += 3000 * tech.droneCycleReduction * tech.bulletsLastLonger deliveryCount-- } } } } } requestAnimationFrame(respawnDrones); }, drone(where = { x: m.pos.x + 30 * Math.cos(m.angle) + 20 * (Math.random() - 0.5), y: m.pos.y + 30 * Math.sin(m.angle) + 20 * (Math.random() - 0.5) }, speed = 1) { const me = bullet.length; const THRUST = 0.0015 const dir = m.angle + 0.2 * (Math.random() - 0.5); const RADIUS = (4.5 + 3 * Math.random()) bullet[me] = Bodies.polygon(where.x, where.y, 8, RADIUS, { angle: dir, inertia: Infinity, friction: 0.05, frictionAir: 0, restitution: 1, density: 0.0005, // 0.001 is normal density dmg: 0.34 + 0.12 * tech.isDroneTeleport + 0.15 * tech.isDroneFastLook, //damage done in addition to the damage from momentum lookFrequency: (tech.isDroneFastLook ? 20 : 70) + Math.floor(17 * Math.random()), endCycle: simulation.cycle + Math.floor((950 + 400 * Math.random()) * tech.bulletsLastLonger * tech.droneCycleReduction) + 5 * RADIUS + Math.max(0, 150 - bullet.length), classType: "bullet", isDrone: true, collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield //self collide }, minDmgSpeed: 0, lockedOn: null, deathCycles: 110 + RADIUS * 5, isImproved: false, beforeDmg(who) { if (tech.isIncendiary && simulation.cycle + this.deathCycles < this.endCycle && !tech.isForeverDrones) { const max = Math.max(Math.min(this.endCycle - simulation.cycle - this.deathCycles, 1500), 0) b.explosion(this.position, max * 0.1 + this.isImproved * 110 + 60 * Math.random()); //makes bullet do explosive damage at end if (tech.isForeverDrones) { this.endCycle = 0 b.drone({ x: m.pos.x + 30 * (Math.random() - 0.5), y: m.pos.y + 30 * (Math.random() - 0.5) }, 5) bullet[bullet.length - 1].endCycle = Infinity } else { this.endCycle -= max } } else { //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 > simulation.cycle + this.deathCycles) { this.endCycle -= 60 if (simulation.cycle + this.deathCycles > this.endCycle) this.endCycle = simulation.cycle + this.deathCycles } } }, onEnd() { if (tech.isDroneRespawn) { //are there any nearby bodies nearby that aren't blocked by map? const canSee = body.filter(a => Matter.Query.ray(map, this.position, a.position).length === 0 && !a.isNotHoldable && Vector.magnitude(Vector.sub(this.position, a.position)) < 70 + 30 * a.mass) if (canSee.length) { //find the closest body to the drone from the canSee array const found = canSee.reduce((a, b) => { const distA = Vector.magnitude(Vector.sub(this.position, a.position)) const distB = Vector.magnitude(Vector.sub(this.position, b.position)) return distA < distB ? a : b }) if (found && m.energy > 0.05) { m.energy -= 0.05 //remove the body and spawn a new drone Composite.remove(engine.world, found) body.splice(body.indexOf(found), 1) b.delayDrones(found.position, 0.7 * Math.sqrt(found.mass)) //draw a line from the drone to the body on the canvas ctx.beginPath(); ctx.moveTo(this.position.x, this.position.y); ctx.lineTo(found.position.x, found.position.y); ctx.strokeStyle = "#000"; ctx.lineWidth = 2; ctx.stroke(); //animate the block fading away simulation.ephemera.push({ name: "droneRespawn", count: 60, //cycles before it self removes do() { this.count-- if (this.count < 0) simulation.removeEphemera(this.name) ctx.beginPath(); let vertices = found.vertices; ctx.moveTo(vertices[0].x, vertices[0].y); for (let j = 1; j < vertices.length; j++) { ctx.lineTo(vertices[j].x, vertices[j].y); } ctx.lineTo(vertices[0].x, vertices[0].y); ctx.lineWidth = 2; ctx.strokeStyle = `rgba(0,0,0,${this.count / 60})` ctx.stroke(); }, }) } } } }, doRespawning() { //fall shrink and die const scale = 0.995; Matter.Body.scale(this, scale, scale); if (this.bodyTarget) { this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, this.bodyTarget.position)), -this.mass * THRUST) } else { this.force.y += this.mass * 0.0012; } }, doDieing() { //fall shrink and die this.force.y += this.mass * 0.0012; const scale = 0.995; Matter.Body.scale(this, scale, scale); }, do() { if (simulation.cycle + this.deathCycles > this.endCycle) { this.restitution = 0.2; if (tech.isDroneRespawn) { this.do = this.doRespawning //make a list of all elements of array body that a ray can be drawn to from the drone const canSee = body.filter(a => Matter.Query.ray(map, this.position, a.position).length === 0 && !a.isNotHoldable) if (canSee.length) { //find the closest body to the drone from the canSee array const found = canSee.reduce((a, b) => { const distA = Vector.magnitude(Vector.sub(this.position, a.position)) const distB = Vector.magnitude(Vector.sub(this.position, b.position)) return distA < distB ? a : b }) if (found) this.bodyTarget = found } } else { this.do = this.doDieing } } this.force.y += this.mass * 0.0002; if (!(simulation.cycle % this.lookFrequency)) { //find mob targets this.lockedOn = null; let closeDist = Infinity; for (let i = 0, len = mob.length; i < len; ++i) { if ( !mob[i].isBadTarget && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable ) { 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] } } } //blink towards mobs if (tech.isDroneTeleport && this.lockedOn) { const sub = Vector.sub(this.lockedOn.position, this.position); const distMag = Vector.magnitude(sub); const unit = Vector.normalise(sub) Matter.Body.setVelocity(this, Vector.mult(unit, Math.max(20, this.speed * 1.5))); ctx.beginPath(); ctx.moveTo(this.position.x, this.position.y); Matter.Body.translate(this, Vector.mult(unit, Math.min(350, distMag - this.lockedOn.radius + 10))); ctx.lineTo(this.position.x, this.position.y); ctx.lineWidth = RADIUS * 2; ctx.strokeStyle = "rgba(0,0,0,0.5)"; ctx.stroke(); } //power ups if (!this.isImproved && !simulation.isChoosing) { if (this.lockedOn) { for (let i = 0, len = powerUp.length; i < len; ++i) { //grab, but don't lock onto nearby power up if ( Vector.magnitudeSquared(Vector.sub(this.position, powerUp[i].position)) < 20000 && (powerUp[i].name !== "heal" || m.health < 0.93 * m.maxHealth || tech.isDroneGrab) && (powerUp[i].name !== "field" || !tech.isSuperDeterminism) // &&(b.inventory.length > 1 || powerUp[i].name !== "ammo" || b.guns[b.activeGun].ammo !== Infinity || tech.isDroneGrab) ) { //draw pickup for a single cycle ctx.beginPath(); ctx.moveTo(this.position.x, this.position.y); ctx.lineTo(powerUp[i].position.x, powerUp[i].position.y); ctx.strokeStyle = "#000" ctx.lineWidth = 4 ctx.stroke(); //pick up nearby power ups powerUps.onPickUp(powerUp[i]); powerUp[i].effect(); Matter.Composite.remove(engine.world, powerUp[i]); powerUp.splice(i, 1); if (tech.isDroneGrab) { this.isImproved = true; const SCALE = 2.25 Matter.Body.scale(this, SCALE, SCALE); this.lookFrequency = 30 + Math.floor(11 * Math.random()); this.endCycle += 3000 * tech.droneCycleReduction * tech.bulletsLastLonger } break; } } } else { //look for power ups to lock onto let closeDist = Infinity; for (let i = 0, len = powerUp.length; i < len; ++i) { if ( (powerUp[i].name !== "heal" || m.health < 0.93 * m.maxHealth || tech.isDroneGrab) && (powerUp[i].name !== "field" || !tech.isSuperDeterminism) // &&(b.inventory.length > 1 || powerUp[i].name !== "ammo" || b.guns[b.activeGun].ammo !== Infinity || tech.isDroneGrab) ) { if (Vector.magnitudeSquared(Vector.sub(this.position, powerUp[i].position)) < 20000 && !simulation.isChoosing) { //draw pickup for a single cycle ctx.beginPath(); ctx.moveTo(this.position.x, this.position.y); ctx.lineTo(powerUp[i].position.x, powerUp[i].position.y); ctx.strokeStyle = "#000" ctx.lineWidth = 4 ctx.stroke(); //pick up nearby power ups powerUps.onPickUp(powerUp[i]); powerUp[i].effect(); Matter.Composite.remove(engine.world, powerUp[i]); powerUp.splice(i, 1); if (tech.isDroneGrab) { this.isImproved = true; const SCALE = 2.25 Matter.Body.scale(this, SCALE, SCALE); this.lookFrequency = 30 + Math.floor(11 * Math.random()); this.endCycle += 3000 * tech.droneCycleReduction * tech.bulletsLastLonger // 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, simulation.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 }); } } }) Composite.add(engine.world, bullet[me]); //add bullet to world Matter.Body.setVelocity(bullet[me], { x: speed * Math.cos(dir), y: speed * Math.sin(dir) }); }, droneRadioactive(where = { x: m.pos.x + 30 * Math.cos(m.angle) + 20 * (Math.random() - 0.5), y: m.pos.y + 30 * Math.sin(m.angle) + 20 * (Math.random() - 0.5) }, speed = 1) { const me = bullet.length; const THRUST = (tech.isFastDrones ? 0.003 : 0.0012) + 0.0005 * (Math.random() - 0.5) const dir = m.angle + 0.4 * (Math.random() - 0.5); const RADIUS = 3 bullet[me] = Bodies.polygon(where.x, where.y, 8, RADIUS, { angle: dir, inertia: Infinity, friction: 0, frictionAir: 0, restitution: 0.4 + 0.199 * Math.random(), dmg: 0, //0.24 damage done in addition to the damage from momentum and radiation lookFrequency: 120 + Math.floor(23 * Math.random()), endCycle: simulation.cycle + Math.floor((900 + 110 * Math.random()) * tech.bulletsLastLonger / tech.droneRadioDamage) + 5 * RADIUS + Math.max(0, 150 - 2 * bullet.length), classType: "bullet", isDrone: true, collisionFilter: { category: cat.bullet, mask: cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield //self collide }, minDmgSpeed: 0, speedCap: 5 + 2 * Math.random(), //6 is normal lockedOn: null, deathCycles: 110 + RADIUS * 5, isImproved: false, radioRadius: 0, maxRadioRadius: 270 + Math.floor(90 * Math.random()), beforeDmg() { }, onEnd() { if (tech.isDroneRespawn) { //are there any nearby bodies nearby that aren't blocked by map? const canSee = body.filter(a => Matter.Query.ray(map, this.position, a.position).length === 0 && !a.isNotHoldable && Vector.magnitude(Vector.sub(this.position, a.position)) < 70 + 30 * a.mass) if (canSee.length) { //find the closest body to the drone from the canSee array const found = canSee.reduce((a, b) => { const distA = Vector.magnitude(Vector.sub(this.position, a.position)) const distB = Vector.magnitude(Vector.sub(this.position, b.position)) return distA < distB ? a : b }) if (found && m.energy > 0.05) { m.energy -= 0.1 //remove the body and spawn a new drone Composite.remove(engine.world, found) body.splice(body.indexOf(found), 1) b.delayDrones(found.position, 0.35 * Math.sqrt(found.mass)) //draw a line from the drone to the body on the canvas ctx.beginPath(); ctx.moveTo(this.position.x, this.position.y); ctx.lineTo(found.position.x, found.position.y); ctx.strokeStyle = "#000"; ctx.lineWidth = 2; ctx.stroke(); //animate the block fading away simulation.ephemera.push({ name: "droneRespawn", count: 60, //cycles before it self removes do() { this.count-- if (this.count < 0) simulation.removeEphemera(this.name) ctx.beginPath(); let vertices = found.vertices; ctx.moveTo(vertices[0].x, vertices[0].y); for (let j = 1; j < vertices.length; j++) { ctx.lineTo(vertices[j].x, vertices[j].y); } ctx.lineTo(vertices[0].x, vertices[0].y); ctx.lineWidth = 2; ctx.strokeStyle = `rgba(0,0,0,${this.count / 60})` ctx.stroke(); }, }) } } } }, do() { //radioactive zone this.radioRadius = this.radioRadius * 0.993 + 0.007 * this.maxRadioRadius //smooth radius towards max //aoe damage to player if (Vector.magnitude(Vector.sub(player.position, this.position)) < this.radioRadius) { const DRAIN = tech.isRadioactiveResistance ? 0.001 : 0.004 if (m.energy > DRAIN) { if (m.immuneCycle < m.cycle) m.energy -= DRAIN } else { m.energy = 0; if (simulation.dmgScale) m.damage((tech.isRadioactiveResistance ? 0.00005 : 0.0002) * tech.radioactiveDamage) //0.00015 } } //aoe damage to mobs let dmg = (0.12 + 0.04 * tech.isFastDrones) * m.dmgScale * tech.droneRadioDamage * tech.radioactiveDamage for (let i = 0, len = mob.length; i < len; i++) { if (Vector.magnitude(Vector.sub(mob[i].position, this.position)) < this.radioRadius + mob[i].radius) { if (Matter.Query.ray(map, mob[i].position, this.position).length > 0) dmg *= 0.25 //reduce damage if a wall is in the way mob[i].damage(mob[i].shield ? dmg * 3 : dmg); mob[i].locatePlayer(); } } //draw ctx.beginPath(); ctx.arc(this.position.x, this.position.y, this.radioRadius, 0, 2 * Math.PI); ctx.globalCompositeOperation = "lighter" // ctx.fillStyle = `rgba(25,139,170,${0.15+0.05*Math.random()})`; // ctx.fillStyle = `rgba(36, 207, 255,${0.1+0.05*Math.random()})`; ctx.fillStyle = `rgba(28, 175, 217,${0.13 + 0.07 * Math.random()})`; ctx.fill(); ctx.globalCompositeOperation = "source-over" //normal drone actions if (simulation.cycle + this.deathCycles > this.endCycle) { //fall shrink and die this.force.y += this.mass * 0.0012; this.restitution = 0.2; const scale = 0.995; Matter.Body.scale(this, scale, scale); this.maxRadioRadius = 0 this.radioRadius = this.radioRadius * 0.98 //let radioactivity decrease } else { this.force.y += this.mass * 0.0002; //gravity if (!(simulation.cycle % this.lookFrequency)) { //find mob targets this.lockedOn = null; let closeDist = Infinity; for (let i = 0, len = mob.length; i < len; ++i) { if ( !mob[i].isBadTarget && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable ) { 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] } } } //power ups if (!this.isImproved && !simulation.isChoosing) { if (this.lockedOn) { //grab, but don't lock onto nearby power up for (let i = 0, len = powerUp.length; i < len; ++i) { if ( Vector.magnitudeSquared(Vector.sub(this.position, powerUp[i].position)) < 20000 && (powerUp[i].name !== "heal" || m.health < 0.93 * m.maxHealth || tech.isDroneGrab) && (powerUp[i].name !== "field" || !tech.isSuperDeterminism) // &&(powerUp[i].name !== "ammo" || b.guns[b.activeGun].ammo !== Infinity || tech.isDroneGrab) ) { //draw pickup for a single cycle ctx.beginPath(); ctx.moveTo(this.position.x, this.position.y); ctx.lineTo(powerUp[i].position.x, powerUp[i].position.y); ctx.strokeStyle = "#000" ctx.lineWidth = 4 ctx.stroke(); //pick up nearby power ups powerUps.onPickUp(powerUp[i]); powerUp[i].effect(); Matter.Composite.remove(engine.world, powerUp[i]); powerUp.splice(i, 1); if (tech.isDroneGrab) { this.isImproved = true; const SCALE = 2.25 Matter.Body.scale(this, SCALE, SCALE); this.lookFrequency = 30 + Math.floor(11 * Math.random()); this.endCycle += 1000 * tech.bulletsLastLonger this.maxRadioRadius *= 1.25 } break; } } } else { //look for power ups to lock onto let closeDist = Infinity; for (let i = 0, len = powerUp.length; i < len; ++i) { if ( (powerUp[i].name !== "heal" || m.health < 0.93 * m.maxHealth || tech.isDroneGrab) && (powerUp[i].name !== "field" || !tech.isSuperDeterminism) // &&(powerUp[i].name !== "ammo" || b.guns[b.activeGun].ammo !== Infinity || tech.isDroneGrab) ) { if (Vector.magnitudeSquared(Vector.sub(this.position, powerUp[i].position)) < 20000 && !simulation.isChoosing) { //draw pickup for a single cycle ctx.beginPath(); ctx.moveTo(this.position.x, this.position.y); ctx.lineTo(powerUp[i].position.x, powerUp[i].position.y); ctx.strokeStyle = "#000" ctx.lineWidth = 4 ctx.stroke(); //pick up nearby power ups powerUps.onPickUp(powerUp[i]); powerUp[i].effect(); Matter.Composite.remove(engine.world, powerUp[i]); powerUp.splice(i, 1); if (tech.isDroneGrab) { this.isImproved = true; const SCALE = 2.25 Matter.Body.scale(this, SCALE, SCALE); this.lookFrequency = 30 + Math.floor(11 * Math.random()); this.endCycle += 1000 * tech.bulletsLastLonger this.maxRadioRadius *= 1.25 } 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, simulation.mouseInGame)), -this.mass * THRUST) } // speed cap instead of friction to give more agility if (this.speed > this.speedCap) { Matter.Body.setVelocity(this, { x: this.velocity.x * 0.97, y: this.velocity.y * 0.97 }); } } } }) Composite.add(engine.world, bullet[me]); //add bullet to world Matter.Body.setVelocity(bullet[me], { x: speed * Math.cos(dir), y: speed * Math.sin(dir) }); }, superBall(where, velocity, radius) { let dir = m.angle const me = bullet.length; bullet[me] = Bodies.polygon(where.x, where.y, 12, radius, b.fireAttributes(dir, false)); Composite.add(engine.world, bullet[me]); //add bullet to world Matter.Body.setVelocity(bullet[me], velocity); bullet[me].calcDensity = () => 0.0007 + 0.00055 * tech.isSuperHarm + 0.0004 * tech.isBulletTeleport Matter.Body.setDensity(bullet[me], bullet[me].calcDensity()); bullet[me].endCycle = simulation.cycle + Math.floor(270 + 90 * Math.random()); bullet[me].minDmgSpeed = 0; bullet[me].restitution = 1; bullet[me].frictionAir = 0; bullet[me].friction = 0; bullet[me].frictionStatic = 0; if (tech.isSuperHarm) { bullet[me].collidePlayerDo = function () { this.force.y += this.mass * 0.001; if (Matter.Query.collides(this, [player]).length) { this.endCycle = 0 m.energy -= m.energy * 0.25 simulation.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, radius: radius, color: "#0ad", time: 15 }); } } bullet[me].cycle = 0 bullet[me].do = function () { this.cycle++ if (this.cycle > 2) this.do = this.collidePlayerDo this.force.y += this.mass * 0.001; }; } else if (tech.isBulletTeleport) { bullet[me].portFrequency = 25 + Math.floor(10 * Math.random()) bullet[me].nextPortCycle = simulation.cycle + bullet[me].portFrequency bullet[me].do = function () { this.force.y += this.mass * 0.001; if (this.nextPortCycle < simulation.cycle) { //teleport around if you have tech.isBulletTeleport this.nextPortCycle = simulation.cycle + this.portFrequency const range = 33 * Math.sqrt(radius) * Math.random() Matter.Body.setPosition(this, Vector.add(this.position, Vector.rotate({ x: range, y: 0 }, 2 * Math.PI * Math.random()))) Matter.Body.setVelocity(this, Vector.rotate(this.velocity, 2 * (Math.random() * Math.random() - 0.25))) } }; } else { bullet[me].do = function () { this.force.y += this.mass * 0.001; }; } bullet[me].beforeDmg = function (who) { if (tech.oneSuperBall) mobs.statusStun(who, 120) // (2.3) * 2 / 14 ticks (2x damage over 7 seconds) if (tech.isFoamBall) { for (let i = 0, len = 5 * this.mass; i < len; i++) { const radius = 5 + 8 * Math.random() const velocity = { x: Math.max(0.5, 2 - radius * 0.1), y: 0 } b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius) } this.endCycle = 0 } if (tech.isIncendiary) { b.explosion(this.position, this.mass * 280); //makes bullet do explosive damage at end this.endCycle = 0 } else if (tech.isSuperBounce) { const cycle = () => { Matter.Body.setDensity(bullet[me], bullet[me].calcDensity() * 1.33);//33% more density and damage this.endCycle = simulation.cycle + Math.floor(300 + 90 * Math.random()); //reset to full duration of time Matter.Body.setVelocity(this, Vector.mult(Vector.normalise(this.velocity), 60)); //reset to high velocity let count = 5 const wait = () => { count-- if (count > 0) requestAnimationFrame(wait); simulation.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, radius: radius, color: 'rgba(255, 0, 0, 0.33)', time: 8 }); } requestAnimationFrame(wait); simulation.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, radius: radius, color: 'rgba(255, 0, 0, 0.33)', time: 8 }); } requestAnimationFrame(cycle); } }; }, targetedBall(position, num = 1, speed = 42 + 12 * Math.random(), range = 1200, isRandomAim = true) { let shotsFired = 0 const targets = [] //target nearby mobs for (let i = 0, len = mob.length; i < len; i++) { const dist = Vector.magnitude(Vector.sub(position, mob[i].position)); if ( dist < range + mob[i].radius && !mob[i].isBadTarget && Matter.Query.ray(map, position, mob[i].position).length === 0 && Matter.Query.ray(body, position, mob[i].position).length === 0 && !mob[i].isInvulnerable ) { targets.push(Vector.add(mob[i].position, Vector.mult(mob[i].velocity, dist / 60))) //predict where the mob will be in a few cycles } } const radius = (11 + 9 * tech.oneSuperBall) * tech.bulletSize 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 = 160 / targets.length const WHERE = { x: targets[index].x + SPREAD * (Math.random() - 0.5), y: targets[index].y + SPREAD * (Math.random() - 0.5) } b.superBall(position, Vector.mult(Vector.normalise(Vector.sub(WHERE, position)), speed), radius) shotsFired++ } else if (isRandomAim) { // aim in random direction const ANGLE = 2 * Math.PI * Math.random() b.superBall(position, { x: speed * Math.cos(ANGLE), y: speed * Math.sin(ANGLE) }, radius) } } return shotsFired }, targetedFoam(position, num = 1, speed = 21 + 7 * Math.random(), range = 1200, isRandomAim = true) { let shotsFired = 0 const targets = [] //target nearby mobs for (let i = 0, len = mob.length; i < len; i++) { const dist = Vector.magnitude(Vector.sub(position, mob[i].position)); if ( dist < range + mob[i].radius && !mob[i].isBadTarget && //|| mob[i].isMobBullet Matter.Query.ray(map, position, mob[i].position).length === 0 && !mob[i].isInvulnerable ) { 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 SPREAD = 160 / targets.length const index = Math.floor(Math.random() * targets.length) const radius = 11 + 12 * Math.random() const where = { x: targets[index].x + SPREAD * (Math.random() - 0.5), y: targets[index].y + SPREAD * (Math.random() - 0.5) } b.foam(position, Vector.mult(Vector.normalise(Vector.sub(where, position)), speed - radius * 0.25), radius) shotsFired++ } else if (isRandomAim) { // aim in random direction const ANGLE = 2 * Math.PI * Math.random() b.foam(position, { x: speed * Math.cos(ANGLE), y: speed * Math.sin(ANGLE) }, 8 + 11 * Math.random()) } } return shotsFired }, // plasmaBall(position, velocity, radius) { // // radius *= Math.sqrt(tech.bulletSize) // const me = bullet.length; // bullet[me] = Bodies.polygon(position.x, position.y, 20, radius, { // density: 0.000001, // 0.001 is normal density // inertia: Infinity, // frictionAir: 0.003, // dmg: 0, //damage on impact // damage: 0, //damage done over time // scale: 1 - 0.006 / tech.bulletsLastLonger, // classType: "bullet", // collisionFilter: { // category: cat.bullet, // mask: 0 //cat.mob | cat.mobBullet // cat.map | cat.body | cat.mob | cat.mobShield // }, // minDmgSpeed: 0, // endCycle: Infinity, // count: 0, // radius: radius, // portFrequency: 5 + Math.floor(5 * Math.random()), // nextPortCycle: Infinity, //disabled unless you have the teleport tech // 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 (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)) // } // if (this.target.isBoss) { // if (this.target.speed > 8) Matter.Body.setVelocity(this.target, Vector.mult(this.target.velocity, 0.98)) // } else { // if (this.target.speed > 4) Matter.Body.setVelocity(this.target, Vector.mult(this.target.velocity, 0.95)) // } // 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(m.dmgScale * this.damage, true); //shield damage bypass // const SCALE = 1 - 0.004 / tech.bulletsLastLonger //shrink if mob is shielded // Matter.Body.scale(this, SCALE, SCALE); // this.radius *= SCALE; // } else { // this.target.damage(m.dmgScale * this.damage); // } // } else if (this.target !== null) { //look for a new target // this.collisionFilter.category = cat.bullet; // this.collisionFilter.mask = cat.mob //| cat.mobShield //cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield // if (tech.isSpawnBulletsOnDeath && bullet.length < 180 && !this.target.isMobBullet) { // 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, 9) // const len = bullet.length < 80 ? 2 : 1 // for (let i = 0; i < len; i++) { // if (targets.length - i > 0) { // const index = Math.floor(Math.random() * targets.length) // const speed = 6 + 6 * 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) // } // } // } // this.target = null // } 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 * tech.foamGravity; //gravity // if (tech.isFoamAttract) { // for (let i = 0, len = mob.length; i < len; i++) { // if (!mob[i].isBadTarget && Vector.magnitude(Vector.sub(mob[i].position, this.position)) < 375 && mob[i].alive && Matter.Query.ray(map, this.position, mob[i].position).length === 0) { // this.force = Vector.mult(Vector.normalise(Vector.sub(mob[i].position, this.position)), this.mass * 0.004) // const slow = 0.9 // Matter.Body.setVelocity(this, { // x: this.velocity.x * slow, // y: this.velocity.y * slow // }); // break // } // } // } // } // if (this.nextPortCycle < simulation.cycle) { //teleport around if you have tech.isBulletTeleport // this.nextPortCycle = simulation.cycle + this.portFrequency // const range = 15 * Math.sqrt(this.radius) * Math.random() // Matter.Body.setPosition(this, Vector.add(this.position, Vector.rotate({ x: range, y: 0 }, 2 * Math.PI * Math.random()))) // } // } // }); // if (tech.isBulletTeleport) bullet[me].nextPortCycle = simulation.cycle + bullet[me].portFrequency // Composite.add(engine.world, bullet[me]); //add bullet to world // Matter.Body.setVelocity(bullet[me], velocity); // }, foam(position, velocity, radius) { if (tech.isFoamCavitation && Math.random() < 0.25) { velocity = Vector.mult(velocity, 1.35) radius = 1.2 * radius + 13 } // radius *= Math.sqrt(tech.bulletSize) const me = bullet.length; bullet[me] = Bodies.polygon(position.x, position.y, 20, radius, { density: 0.000001, // 0.001 is normal density inertia: Infinity, frictionAir: 0.003, dmg: 0, //damage on impact damage: tech.foamDamage * (tech.isFastFoam ? 2.8 : 1) * (tech.isBulletTeleport ? 1.53 : 1), //damage done over time scale: 1 - 0.006 / tech.bulletsLastLonger * (tech.isFastFoam ? 1.65 : 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, portFrequency: 7 + Math.floor(5 * Math.random()), nextPortCycle: Infinity, //disabled unless you have the teleport tech 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 Matter.Body.setVelocity(this, { x: 0, y: 0 }); } }, onEnd() { }, do() { 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)) } if (this.target.isBoss) { if (this.target.speed > 6.5) Matter.Body.setVelocity(this.target, Vector.mult(this.target.velocity, 0.975)) } else { if (this.target.speed > 2.5) Matter.Body.setVelocity(this.target, Vector.mult(this.target.velocity, 0.94)) } 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(m.dmgScale * this.damage, true); //shield damage bypass const SCALE = 1 - 0.004 / tech.bulletsLastLonger //shrink if mob is shielded Matter.Body.scale(this, SCALE, SCALE); this.radius *= SCALE; } else { this.target.damage(m.dmgScale * this.damage); } } else if (this.target !== null) { //look for a new target this.collisionFilter.category = cat.bullet; this.collisionFilter.mask = cat.mob //| cat.mobShield //cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield Matter.Body.setVelocity(this, { x: this.target.velocity.x, y: this.target.velocity.y }); if (tech.isSpawnBulletsOnDeath && bullet.length < 180 && !this.target.isMobBullet) { 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, 9) const len = bullet.length < 80 ? 2 : 1 for (let i = 0; i < len; i++) { if (targets.length - i > 0) { const index = Math.floor(Math.random() * targets.length) const speed = 6 + 6 * 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) } } } this.target = null } else if (Matter.Query.point(map, this.position).length > 0) { //slow when touching map const slow = 0.87 Matter.Body.setVelocity(this, { x: this.velocity.x * slow, y: this.velocity.y * slow }); const SCALE = 0.97 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) { //slow when touching blocks const slow = 0.94 Matter.Body.setVelocity(this, { x: this.velocity.x * slow, y: this.velocity.y * slow }); const SCALE = 0.99 Matter.Body.scale(this, SCALE, SCALE); this.radius *= SCALE; } else { this.force.y += this.mass * tech.foamGravity; //gravity if (tech.isFoamAttract) { for (let i = 0, len = mob.length; i < len; i++) { if ( !mob[i].isBadTarget && Vector.magnitude(Vector.sub(mob[i].position, this.position)) < 375 && mob[i].alive && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable ) { this.force = Vector.mult(Vector.normalise(Vector.sub(mob[i].position, this.position)), this.mass * 0.004) const slow = 0.9 Matter.Body.setVelocity(this, { x: this.velocity.x * slow, y: this.velocity.y * slow }); break } } } } if (this.nextPortCycle < simulation.cycle) { //teleport around if you have tech.isBulletTeleport this.nextPortCycle = simulation.cycle + this.portFrequency const range = 13 * Math.sqrt(this.radius) * Math.random() Matter.Body.setPosition(this, Vector.add(this.position, Vector.rotate({ x: range, y: 0 }, 2 * Math.PI * Math.random()))) } } }); if (tech.isBulletTeleport) bullet[me].nextPortCycle = simulation.cycle + bullet[me].portFrequency Composite.add(engine.world, bullet[me]); //add bullet to world Matter.Body.setVelocity(bullet[me], velocity); }, targetedBlock(who, speed = 50 - Math.min(20, who.mass * 2), range = 1600) { let closestMob, dist for (let i = 0, len = mob.length; i < len; i++) { if (who !== mob[i] && !mob[i].isBadTarget && !mob[i].isInvulnerable) { dist = Vector.magnitude(Vector.sub(who.position, mob[i].position)); if (dist < range && Matter.Query.ray(map, who.position, mob[i].position).length === 0) { //&& Matter.Query.ray(body, position, mob[i].position).length === 0 closestMob = mob[i] range = dist } } } if (closestMob) { const where = Vector.add(closestMob.position, Vector.mult(closestMob.velocity, dist / 60)) const velocity = Vector.mult(Vector.normalise(Vector.sub(where, who.position)), speed) velocity.y -= Math.abs(who.position.x - closestMob.position.x) / 150; //gives an arc, but not a good one Matter.Body.setVelocity(who, velocity); } }, targetedNail(position, num = 1, speed = 40 + 10 * Math.random(), range = 1200, isRandomAim = true, damage = 1.4) { let shotsFired = 0 const targets = [] //target nearby mobs for (let i = 0, len = mob.length; i < len; i++) { const dist = Vector.magnitude(Vector.sub(position, mob[i].position)); if ( dist < range + mob[i].radius && !mob[i].isBadTarget && //|| mob[i].isMobBullet Matter.Query.ray(map, position, mob[i].position).length === 0 && Matter.Query.ray(body, position, mob[i].position).length === 0 && !mob[i].isInvulnerable ) { 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), damage) shotsFired++ } 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) }, damage) shotsFired++ } } return shotsFired }, crit(mob, bullet) { if (!mob.shield && Vector.dot(Vector.normalise(Vector.sub(mob.position, bullet.position)), Vector.normalise(bullet.velocity)) > 0.999 - 1 / mob.radius) { if (mob.isFinalBoss && !(Vector.dot(Vector.normalise(Vector.sub(mob.position, bullet.position)), Vector.normalise(bullet.velocity)) > 0.999999)) return let cycle = () => { //makes this run after damage if (mob.health < 0.5 && mob.damageReduction > 0 && mob.alive) { // mob.death(); // mob.damage(this.health * Math.sqrt(this.mass) / this.damageReduction); mob.damage(Infinity); const color = 'rgb(255,255,255)' simulation.drawList.push({ x: mob.position.x, y: mob.position.y, radius: mob.radius * 1.2, color: color, //"rgba(0,0,0,0.6)", time: 8 }); simulation.drawList.push({ x: mob.position.x, y: mob.position.y, radius: mob.radius * 0.75, color: color, //"rgba(0,0,0,0.85)", time: 15 }); simulation.drawList.push({ x: mob.position.x, y: mob.position.y, radius: mob.radius * 0.4, color: color, //"rgb(0,0,0)", time: 20 }); } } requestAnimationFrame(cycle); } }, nail(pos, velocity, dmg = 1) { dmg *= tech.bulletSize const me = bullet.length; bullet[me] = Bodies.rectangle(pos.x, pos.y, 25 * tech.bulletSize, 2 * tech.bulletSize, b.fireAttributes(Math.atan2(velocity.y, velocity.x))); Matter.Body.setVelocity(bullet[me], velocity); Composite.add(engine.world, bullet[me]); //add bullet to world bullet[me].endCycle = simulation.cycle + 60 + 18 * Math.random(); bullet[me].dmg = tech.isNailRadiation ? 0 : dmg bullet[me].beforeDmg = function (who) { //beforeDmg is rewritten with ice crystal tech if (tech.isNailRadiation) mobs.statusDoT(who, dmg * (tech.isFastRadiation ? 1.3 : 0.44), tech.isSlowRadiation ? 360 : (tech.isFastRadiation ? 60 : 180)) // one tick every 30 cycles if (tech.isNailCrit) { //makes bullet do explosive damage if it hits center if (!who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.97 - 1 / who.radius) { b.explosion(this.position, 80 + 90 * (b.activeGun === 0) + 30 * Math.random()); //larger explosions for human aimed nail gun, smaller for auto aimed sources, like bots, and mine } } this.ricochet(who) }; bullet[me].ricochet = function (who) { //use for normal nails, and ice crystal nails if (tech.isRicochet) { const targets = [] //target nearby mobs for (let i = 0, len = mob.length; i < len; i++) { const dist = Vector.magnitude(Vector.sub(this.position, mob[i].position)); if ( mob[i] !== who && dist < 2500 + mob[i].radius && !mob[i].isBadTarget && //|| mob[i].isMobBullet !mob[i].isInvulnerable && Matter.Query.ray(body, this.position, mob[i].position).length === 0 && Matter.Query.ray(map, this.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 } } if (targets.length > 0) { // aim near a random target in array const index = Math.floor(Math.random() * targets.length) Matter.Body.setVelocity(this, Vector.mult(Vector.normalise(Vector.sub(targets[index], this.position)), 45)); Matter.Body.setAngle(this, Math.atan2(this.velocity.y, this.velocity.x)) Matter.Body.setAngularVelocity(this, 0); } this.dmg += 2 } } bullet[me].do = function () { }; }, needle(angle = m.angle) { const me = bullet.length; bullet[me] = Bodies.rectangle(m.pos.x + 40 * Math.cos(m.angle), m.pos.y + 40 * Math.sin(m.angle), 75 * tech.bulletSize, 0.75 * tech.bulletSize, b.fireAttributes(angle)); Matter.Body.setDensity(bullet[me], 0.00001); //0.001 is normal bullet[me].immuneList = [] bullet[me].dmg = 6 if (tech.needleTunnel) { bullet[me].dmg *= 1.2 bullet[me].endCycle = simulation.cycle + 300; bullet[me].collisionFilter.mask = tech.isShieldPierce ? 0 : cat.mobShield // bullet[me].turnRate = 0.005 * (Math.random() - 0.5) bullet[me].isInMap = false bullet[me].do = function () { const whom = Matter.Query.collides(this, mob) if (whom.length && this.speed > 20) { //if touching a mob for (let i = 0, len = whom.length; i < len; i++) { who = whom[i].bodyA if (who && who.mob) { let immune = false for (let i = 0; i < this.immuneList.length; i++) { //check if this needle has hit this mob already if (this.immuneList[i] === who.id) { immune = true break } } if (!immune) { if (tech.isNailCrit) { if (!who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.97 - 1 / who.radius) { b.explosion(this.position, 220 + 50 * Math.random()); //makes bullet do explosive damage at end } } else if (tech.isCritKill) b.crit(who, this) this.immuneList.push(who.id) //remember that this needle has hit this mob once already let dmg = this.dmg * tech.bulletSize * m.dmgScale if (tech.isNailRadiation) { mobs.statusDoT(who, (tech.isFastRadiation ? 6 : 2) * tech.bulletSize, tech.isSlowRadiation ? 360 : (tech.isFastRadiation ? 60 : 180)) // one tick every 30 cycles dmg *= 0.25 } if (tech.isCrit && who.isStunned) dmg *= 4 who.damage(dmg, tech.isShieldPierce); if (who.alive) who.foundPlayer(); if (who.damageReduction) { simulation.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, radius: Math.log(dmg + 1.1) * 40 * who.damageReduction + 3, color: simulation.playerDmgColor, time: simulation.drawTime }); } } } } } else if (Matter.Query.collides(this, map).length) { //penetrate walls if (!this.isInMap) { //turn after entering the map, but only turn once this.isInMap = true Matter.Body.setVelocity(this, Vector.rotate(this.velocity, 0.25 * (Math.random() - 0.5))); Matter.Body.setAngle(this, Math.atan2(this.velocity.y, this.velocity.x)) } Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(this.velocity, -0.98))) //move back 1/2 your velocity = moving at 1/2 speed } else if (Matter.Query.collides(this, body).length) { //penetrate blocks Matter.Body.setAngularVelocity(this, 0) Matter.Body.setPosition(this, Vector.add(this.position, Vector.mult(this.velocity, -0.94))) //move back 1/2 your velocity = moving at 1/2 speed } else if (this.speed < 30) { this.force.y += this.mass * 0.001; //no gravity until it slows down to improve aiming } }; } else { bullet[me].endCycle = simulation.cycle + 100; bullet[me].collisionFilter.mask = tech.isShieldPierce ? cat.body : cat.body | cat.mobShield bullet[me].do = function () { const whom = Matter.Query.collides(this, mob) if (whom.length && this.speed > 20) { //if touching a mob for (let i = 0, len = whom.length; i < len; i++) { who = whom[i].bodyA if (who && who.mob) { let immune = false for (let i = 0; i < this.immuneList.length; i++) { //check if this needle has hit this mob already if (this.immuneList[i] === who.id) { immune = true break } } if (!immune) { if (tech.isNailCrit) { if (!who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.97 - 1 / who.radius) { b.explosion(this.position, 220 + 50 * Math.random()); //makes bullet do explosive damage at end } } else if (tech.isCritKill) b.crit(who, this) this.immuneList.push(who.id) //remember that this needle has hit this mob once already let dmg = this.dmg * tech.bulletSize * m.dmgScale if (tech.isNailRadiation) { mobs.statusDoT(who, (tech.isFastRadiation ? 6 : 2) * tech.bulletSize, tech.isSlowRadiation ? 360 : (tech.isFastRadiation ? 60 : 180)) // one tick every 30 cycles dmg *= 0.25 } if (tech.isCrit && who.isStunned) dmg *= 4 who.damage(dmg, tech.isShieldPierce); if (who.alive) who.foundPlayer(); if (who.damageReduction) { simulation.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, radius: Math.log(dmg + 1.1) * 40 * who.damageReduction + 3, color: simulation.playerDmgColor, time: simulation.drawTime }); } } } } } else if (Matter.Query.collides(this, map).length) { //stick in walls this.collisionFilter.mask = 0; Matter.Body.setAngularVelocity(this, 0) Matter.Body.setVelocity(this, { x: 0, y: 0 }); this.do = function () { if (!Matter.Query.collides(this, map).length) this.force.y += this.mass * 0.001; } if (tech.isNeedleIce) { b.iceIX(5 + 5 * Math.random(), 2 * Math.PI * Math.random(), this.position) // iceIX(speed = 0, dir = m.angle + Math.PI * 2 * Math.random(), where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }) { if (0.5 < Math.random()) b.iceIX(5 + 5 * Math.random(), 2 * Math.PI * Math.random(), this.position) } } else if (this.speed < 30) { this.force.y += this.mass * 0.001; //no gravity until it slows down to improve aiming } }; } const SPEED = 90 Matter.Body.setVelocity(bullet[me], { x: m.Vx / 2 + SPEED * Math.cos(angle), y: m.Vy / 2 + SPEED * Math.sin(angle) }); // Matter.Body.setDensity(bullet[me], 0.00001); Composite.add(engine.world, bullet[me]); //add bullet to world }, // ************************************************************************************************** // ************************************************************************************************** // ******************************** Bots ********************************************* // ************************************************************************************************** // ************************************************************************************************** totalBots() { return tech.dynamoBotCount + tech.foamBotCount + tech.soundBotCount + tech.nailBotCount + tech.laserBotCount + tech.boomBotCount + tech.orbitBotCount + tech.plasmaBotCount + tech.missileBotCount }, hasBotUpgrade() { return tech.isNailBotUpgrade + tech.isFoamBotUpgrade + tech.isBoomBotUpgrade + tech.isLaserBotUpgrade + tech.isOrbitBotUpgrade + tech.isDynamoBotUpgrade }, convertBotsTo(type) { //type can be a string like "dynamoBotCount" const totalPermanentBots = b.totalBots() //remove all bots techs and convert them to the new type so that tech refunds work correctly let totalTechToConvert = 0 //count how many tech need to be converted for (let i = 0; i < tech.tech.length; i++) { if (tech.tech[i].count && tech.tech[i].isBot) { totalTechToConvert += tech.tech[i].count tech.removeTech(i) } } //remove all bots b.zeroBotCount() b.clearPermanentBots() for (let i = 0; i < totalTechToConvert; i++) tech.giveTech(type) //spawn tech for the correct bot type //find index of new bot type tech effect let index = null for (let i = 0; i < tech.tech.length; i++) { if (tech.tech[i].name === type) { index = i break } } for (let i = 0, len = totalPermanentBots - totalTechToConvert; i < len; i++) tech.tech[index].effect(); //also convert any permanent bots that didn't come from a tech //in experiment mode set the unselect color for bot tech that was converted // if (build.isExperimentSelection) { } }, clearPermanentBots() { for (let i = 0; i < bullet.length; i++) { if (bullet[i].botType && bullet[i].endCycle === Infinity) bullet[i].endCycle = 0 //remove active bots, but don't remove temp bots } }, removeBot() { if (tech.nailBotCount > 1) { tech.nailBotCount-- return } if (tech.laserBotCount > 1) { tech.laserBotCount-- return } if (tech.foamBotCount > 1) { tech.foamBotCount-- return } if (tech.soundBotCount > 1) { tech.soundBotCount-- return } if (tech.boomBotCount > 1) { tech.boomBotCount-- return } if (tech.orbitBotCount > 1) { tech.orbitBotCount-- return } if (tech.dynamoBotCount > 1) { tech.dynamoBotCount-- return } if (tech.missileBotCount > 1) { tech.missileBotCount-- return } if (tech.plasmaBotCount > 1) { tech.plasmaBotCount-- return } }, zeroBotCount() { //remove all bots tech.dynamoBotCount = 0 tech.laserBotCount = 0 tech.nailBotCount = 0 tech.foamBotCount = 0 tech.soundBotCount = 0 tech.boomBotCount = 0 tech.orbitBotCount = 0 tech.missileBotCount = 0 }, respawnBots() { for (let i = 0; i < tech.dynamoBotCount; i++) b.dynamoBot({ x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, false) for (let i = 0; i < tech.laserBotCount; i++) b.laserBot({ x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, false) for (let i = 0; i < tech.nailBotCount; i++) b.nailBot({ x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, false) for (let i = 0; i < tech.foamBotCount; i++) b.foamBot({ x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, false) for (let i = 0; i < tech.soundBotCount; i++) b.soundBot({ x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, false) for (let i = 0; i < tech.boomBotCount; i++) b.boomBot({ x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, false) for (let i = 0; i < tech.orbitBotCount; i++) b.orbitBot({ x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, false) for (let i = 0; i < tech.plasmaBotCount; i++) b.plasmaBot({ x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, false) for (let i = 0; i < tech.missileBotCount; i++) b.missileBot({ x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, false) if (tech.isIntangible && m.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 = player.position, isKeep = true, isLaser = true) { if (Math.random() < 0.5) { //chance to match bot to your upgrade if (tech.isNailBotUpgrade) { //check for upgrades first b.nailBot(where, isKeep) if (isKeep) tech.nailBotCount++; } else if (tech.isFoamBotUpgrade) { b.foamBot(where, isKeep) if (isKeep) tech.foamBotCount++; } else if (tech.isSoundBotUpgrade) { b.soundBot(where, isKeep) if (isKeep) tech.soundBotCount++; } else if (tech.isBoomBotUpgrade) { b.boomBot(where, isKeep) if (isKeep) tech.boomBotCount++; } else if (tech.isLaserBotUpgrade) { b.laserBot(where, isKeep) if (isKeep) tech.laserBotCount++; } else if (tech.isOrbitBotUpgrade) { b.orbitBot(where, isKeep); if (isKeep) tech.orbitBotCount++; } else if (tech.isDynamoBotUpgrade) { b.dynamoBot(where, isKeep) if (isKeep) tech.dynamoBotCount++; } else if (Math.random() < 0.143) { //random b.soundBot(where, isKeep) if (isKeep) tech.soundBotCount++; } else if (Math.random() < 0.166 && isLaser) { b.laserBot(where, isKeep) if (isKeep) tech.laserBotCount++; } else if (Math.random() < 0.2) { b.dynamoBot(where, isKeep) if (isKeep) tech.dynamoBotCount++; } else if (Math.random() < 0.25) { b.orbitBot(where, isKeep); if (isKeep) tech.orbitBotCount++; } else if (Math.random() < 0.33) { b.nailBot(where, isKeep) if (isKeep) tech.nailBotCount++; } else if (Math.random() < 0.5) { b.foamBot(where, isKeep) if (isKeep) tech.foamBotCount++; } else { b.boomBot(where, isKeep) if (isKeep) tech.boomBotCount++; } } else { //else don't match bot to upgrade if (Math.random() < 0.143) { //random b.soundBot(where, isKeep) if (isKeep) tech.soundBotCount++; } else if (Math.random() < 0.166 && isLaser) { //random b.laserBot(where, isKeep) if (isKeep) tech.laserBotCount++; } else if (Math.random() < 0.2) { b.dynamoBot(where, isKeep) if (isKeep) tech.dynamoBotCount++; } else if (Math.random() < 0.25) { b.orbitBot(where, isKeep); if (isKeep) tech.orbitBotCount++; } else if (Math.random() < 0.33) { b.nailBot(where, isKeep) if (isKeep) tech.nailBotCount++; } else if (Math.random() < 0.5) { b.foamBot(where, isKeep) if (isKeep) tech.foamBotCount++; } else { b.boomBot(where, isKeep) if (isKeep) tech.boomBotCount++; } } }, setDynamoBotDelay() { //reorder orbital bot positions around a circle let total = 0 for (let i = 0; i < bullet.length; i++) { if (bullet[i].botType === 'dynamo') total++ } let count = 0 for (let i = 0; i < bullet.length; i++) { if (bullet[i].botType === 'dynamo') { count++ const step = Math.max(60 - 3 * total, 20) bullet[i].followDelay = (step * count) % 600 } } }, dynamoBot(position = player.position, isConsole = true) { if (isConsole) simulation.makeTextLog(`b.dynamoBot()`); const me = bullet.length; bullet[me] = Bodies.polygon(position.x, position.y, 5, 10, { isUpgraded: tech.isDynamoBotUpgrade, botType: "dynamo", friction: 0, frictionStatic: 0, frictionAir: 0.02, spin: 0.07 * (Math.random() < 0.5 ? -1 : 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() { b.setDynamoBotDelay() }, followDelay: 0, phase: Math.floor(60 * Math.random()), do() { // if (Vector.magnitude(Vector.sub(this.position, player.position)) < 150) { // ctx.fillStyle = "rgba(0,0,0,0.06)"; // ctx.beginPath(); // ctx.arc(this.position.x, this.position.y, 150, 0, 2 * Math.PI); // ctx.fill(); // } //check for damage if (m.immuneCycle < m.cycle && !((m.cycle + this.phase) % 30)) { //twice a second if (Vector.magnitude(Vector.sub(this.position, player.position)) < 250 && m.immuneCycle < m.cycle) { //give energy Matter.Body.setAngularVelocity(this, this.spin) if (this.isUpgraded) { m.energy += 0.12 simulation.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, radius: 10, color: m.fieldMeterColor, time: simulation.drawTime }); } else { m.energy += 0.04 simulation.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, radius: 5, color: m.fieldMeterColor, time: simulation.drawTime }); } } } if (!m.isCloak) { //if cloaking field isn't active 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++) { if (!q[i].isShielded) { Matter.Body.setAngularVelocity(this, this.spin) // mobs.statusStun(q[i], 180) // const dmg = 0.5 * m.dmgScale * (this.isUpgraded ? 2.5 : 1) const dmg = 0.5 * m.dmgScale q[i].damage(dmg); if (q[i].alive) q[i].foundPlayer(); if (q[i].damageReduction) { simulation.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, // radius: 600 * dmg * q[i].damageReduction, radius: Math.sqrt(2000 * dmg * q[i].damageReduction) + 2, color: 'rgba(0,0,0,0.4)', time: simulation.drawTime }); } } } } let history = m.history[(m.cycle - this.followDelay) % 600] Matter.Body.setPosition(this, { x: history.position.x, y: history.position.y - history.yOff + 24.2859 }) //bullets move with player } }) Composite.add(engine.world, bullet[me]); //add bullet to world b.setDynamoBotDelay() }, nailBot(position = { x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, isConsole = true) { if (isConsole) simulation.makeTextLog(`b.nailBot()`); const me = bullet.length; const dir = m.angle; const RADIUS = (12 + 4 * Math.random()) bullet[me] = Bodies.polygon(position.x, position.y, 4, RADIUS, { isUpgraded: tech.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: simulation.cycle + 60 * Math.random(), delay: Math.floor((tech.isNailBotUpgrade ? 18 : 85) * b.fireCDscale), acceleration: 0.005 * (1 + 0.5 * Math.random()), range: 60 * (1 + 0.3 * Math.random()) + 3 * b.totalBots(), endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: b.totalBots() < 50 ? cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield : cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield //if over 50 bots, they no longer collide with each other }, beforeDmg() { }, onEnd() { }, do() { const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, m.pos)) if (distanceToPlayer > this.range) { //if far away move towards player this.force = Vector.mult(Vector.normalise(Vector.sub(m.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 if (this.lastLookCycle < simulation.cycle && !m.isCloak) { this.lastLookCycle = simulation.cycle + this.delay for (let i = 0, len = mob.length; i < len; i++) { const dist = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); if ( !mob[i].isBadTarget && dist < 3000000 && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0 && !mob[i].isShielded && !mob[i].isInvulnerable ) { const unit = Vector.normalise(Vector.sub(Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist) / 60)), this.position)) if (this.isUpgraded) { const SPEED = 60 b.nail(this.position, Vector.mult(unit, SPEED)) this.force = Vector.mult(unit, -0.02 * this.mass) } else { const SPEED = 45 b.nail(this.position, Vector.mult(unit, SPEED)) this.force = Vector.mult(unit, -0.012 * this.mass) } break; } } } } } }) Composite.add(engine.world, bullet[me]); //add bullet to world }, missileBot(position = { x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, isConsole = true) { if (isConsole) simulation.makeTextLog(`b.missileBot()`); const me = bullet.length; bullet[me] = Bodies.rectangle(position.x, position.y, 28, 11, { botType: "missile", angle: m.angle, friction: 0, frictionStatic: 0, frictionAir: 0.04, restitution: 0.7, dmg: 0, // 0.14 //damage done in addition to the damage from momentum minDmgSpeed: 2, lookFrequency: 27 + Math.ceil(6 * Math.random()), cd: 0, delay: Math.floor(65 * b.fireCDscale), range: 70 + 3 * b.totalBots(), endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: b.totalBots() < 50 ? (cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield) : (cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield) //if over 50 bots, they no longer collide with each other }, beforeDmg() { }, onEnd() { }, do() { const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, m.pos)) if (distanceToPlayer > this.range) { //if far away move towards player this.force = Vector.mult(Vector.normalise(Vector.sub(m.pos, this.position)), this.mass * 0.006) } 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 if (this.cd < simulation.cycle && !(simulation.cycle % this.lookFrequency) && !m.isCloak) { for (let i = 0, len = mob.length; i < len; i++) { const dist2 = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); if ( mob[i].alive && !mob[i].isBadTarget && dist2 > 40000 && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable ) { this.cd = simulation.cycle + this.delay; const angle = Vector.angle(this.position, mob[i].position) Matter.Body.setAngle(this, angle) // Matter.Body.setAngularVelocity(this, 0.025) this.torque += this.inertia * 0.00004 * (Math.round(Math.random()) ? 1 : -1) this.force = Vector.mult(Vector.normalise(Vector.sub(this.position, mob[i].position)), this.mass * 0.02) if (tech.missileCount > 1) { const countReduction = Math.pow(0.85, tech.missileCount) const size = 0.9 * Math.sqrt(countReduction) const direction = { x: Math.cos(angle), y: Math.sin(angle) } const push = Vector.mult(Vector.perp(direction), 0.015 * countReduction / Math.sqrt(tech.missileCount)) for (let i = 0; i < tech.missileCount; i++) { setTimeout(() => { b.missile(this.position, angle, -8, size) bullet[bullet.length - 1].force.x += push.x * (i - (tech.missileCount - 1) / 2); bullet[bullet.length - 1].force.y += push.y * (i - (tech.missileCount - 1) / 2); }, 40 * tech.missileCount * Math.random()); } } else { b.missile(this.position, angle, -8, 0.9) } break; } } } } } }) Composite.add(engine.world, bullet[me]); //add bullet to world }, foamBot(position = { x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, isConsole = true) { if (isConsole) simulation.makeTextLog(`b.foamBot()`); const me = bullet.length; const dir = m.angle; const RADIUS = (10 + 5 * Math.random()) bullet[me] = Bodies.polygon(position.x, position.y, 6, RADIUS, { isUpgraded: tech.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()) - 50 * tech.isFoamBotUpgrade, cd: 0, fireCount: 0, fireLimit: 5 + 2 * tech.isFoamBotUpgrade, delay: Math.floor((150 + (tech.isFoamBotUpgrade ? 0 : 250)) * b.fireCDscale),// + 30 - 20 * tech.isFoamBotUpgrade,//20 + Math.floor(85 * b.fireCDscale) - 20 * tech.isFoamBotUpgrade, acceleration: 0.005 * (1 + 0.5 * Math.random()), range: 60 * (1 + 0.3 * Math.random()) + 3 * b.totalBots(), //how far from the player the bot will move endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: b.totalBots() < 50 ? cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield : cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield //if over 50 bots, they no longer collide with each other }, beforeDmg() { }, onEnd() { }, fireTarget: { x: 0, y: 0 }, fire() { this.fireCount++ if (this.fireCount > this.fireLimit) { this.fireCount = 0 this.cd = simulation.cycle + this.delay; } // else {this.cd = simulation.cycle + 1;} const radius = 5 + 3 * Math.random() const SPEED = Math.max(5, 25 - radius * 0.4); //(m.crouch ? 32 : 20) - radius * 0.7; const velocity = Vector.mult(Vector.normalise(Vector.sub(this.fireTarget, this.position)), SPEED) b.foam(this.position, Vector.rotate(velocity, 0.07 * (Math.random() - 0.5)), radius + 6 * this.isUpgraded) //recoil // const force = Vector.mult(Vector.normalise(velocity), 0.005 * this.mass * (tech.isFoamCavitation ? 2 : 1)) const force = Vector.mult(velocity, 0.0001 * this.mass * (tech.isFoamCavitation ? 2 : 1)) this.force.x -= force.x this.force.y -= force.y }, do() { if (this.fireCount === 0) { //passive mode: look for targets and following player const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, m.pos)) if (distanceToPlayer > this.range) { //if far away move towards player this.force = Vector.mult(Vector.normalise(Vector.sub(m.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 } if (this.cd < simulation.cycle && !m.isCloak && !(simulation.cycle % this.lookFrequency)) { for (let i = 0, len = mob.length; i < len; i++) { const dist2 = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); if (dist2 < 1600000 && !mob[i].isBadTarget && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable) { this.fireTarget = Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist2) / 60)) //set target to where the mob will be in 1 second this.fire() break; } } } } else { //fire mode: quickly fire at targets and doesn't follow player this.fire() } // const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, m.pos)) // if (distanceToPlayer > this.range) { //if far away move towards player // this.force = Vector.mult(Vector.normalise(Vector.sub(m.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 // //&& !(simulation.cycle % this.lookFrequency) // if (this.cd < simulation.cycle && !m.isCloak) { // let target // for (let i = 0, len = mob.length; i < len; i++) { // const dist2 = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); // if (dist2 < 2000000 && !mob[i].isBadTarget && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable) { // this.fireCount++ // if (this.fireCount > 5) { // this.fireCount = 0 // this.cd = simulation.cycle + this.delay; // } else { // // this.cd = simulation.cycle + 1; // } // target = Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist2) / 60)) // const radius = 6 + 7 * Math.random() // const SPEED = Math.max(5, 25 - radius * 0.4); //(m.crouch ? 32 : 20) - radius * 0.7; // const velocity = Vector.mult(Vector.normalise(Vector.sub(target, this.position)), SPEED) // b.foam(this.position, velocity, radius + 7.5 * this.isUpgraded) // //recoil // // const force = Vector.mult(Vector.normalise(velocity), 0.005 * this.mass * (tech.isFoamCavitation ? 2 : 1)) // const force = Vector.mult(velocity, 0.0003 * this.mass * (tech.isFoamCavitation ? 2 : 1)) // this.force.x -= force.x // this.force.y -= force.y // break; // } // } // } // } } }) Composite.add(engine.world, bullet[me]); //add bullet to world }, soundBot(position = { x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, isConsole = true) { if (isConsole) simulation.makeTextLog(`b.soundBot()`); const me = bullet.length; const dir = m.angle; bullet[me] = Bodies.rectangle(position.x, position.y, 12, 30, { isUpgraded: tech.isSoundBotUpgrade, botType: "sound", 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: 17 + Math.floor(7 * Math.random()) - 5 * tech.isSoundBotUpgrade, cd: 0, fireCount: 0, fireLimit: 5 + 2 * tech.isSoundBotUpgrade, delay: Math.floor((120 + (tech.isSoundBotUpgrade ? 0 : 70)) * b.fireCDscale),// + 30 - 20 * tech.isFoamBotUpgrade,//20 + Math.floor(85 * b.fireCDscale) - 20 * tech.isFoamBotUpgrade, acceleration: 0.005 * (1 + 0.5 * Math.random()), range: 60 * (1 + 0.3 * Math.random()) + 3 * b.totalBots(), //how far from the player the bot will move endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: b.totalBots() < 50 ? cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield : cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield //if over 50 bots, they no longer collide with each other }, beforeDmg() { }, onEnd() { }, fireTarget: { x: 0, y: 0 }, waves: [], phononWaveCD: 0, addWave(where, angle) { const halfArc = 0.2 * (tech.isBulletTeleport ? 0.66 + (Math.random() - 0.5) : 1) + 0.05 * tech.isSoundBotUpgrade //6.28 is a full circle, but these arcs needs to stay small because we are using small angle linear approximation, for collisions this.waves.push({ position: where, angle: angle - halfArc, //used in drawing ctx.arc unit1: { x: Math.cos(angle - halfArc), y: Math.sin(angle - halfArc) }, //used for collision unit2: { x: Math.cos(angle + halfArc), y: Math.sin(angle + halfArc) }, //used for collision arc: halfArc * 2, radius: 25, resonanceCount: 0, dmg: (tech.isUpgraded ? 4 : 1.5) * m.dmgScale * tech.wavePacketDamage * tech.waveBeamDamage * (tech.isBulletTeleport ? 1.5 : 1), }) }, fire() { if (!(simulation.cycle % (6 - 2 * tech.isSoundBotUpgrade))) { this.fireCount++ if (this.fireCount > this.fireLimit) { this.fireCount = 0 this.cd = simulation.cycle + this.delay; } this.addWave({ x: this.position.x, y: this.position.y }, Math.atan2(this.fireTarget.y - this.position.y, this.fireTarget.x - this.position.x) + tech.isBulletTeleport * 0.3 * (Math.random() - 0.5)) //add wave to waves array //face target Matter.Body.setAngle(this, Vector.angle(this.position, this.fireTarget)); } }, do() { if (this.fireCount === 0) { //passive mode: look for targets and following player const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, m.pos)) if (distanceToPlayer > this.range) { //if far away move towards player this.force = Vector.mult(Vector.normalise(Vector.sub(m.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 } if (this.cd < simulation.cycle && !m.isCloak && !(simulation.cycle % this.lookFrequency)) { for (let i = 0, len = mob.length; i < len; i++) { const dist2 = Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)); if (dist2 < 1300000 && !mob[i].isBadTarget && (Matter.Query.ray(map, this.position, mob[i].position).length === 0 || dist2 < 300000) && !mob[i].isInvulnerable) { this.fireTarget = Vector.add(mob[i].position, Vector.mult(mob[i].velocity, Math.sqrt(dist2) / 60)) //set target to where the mob will be in 1 second this.fire() break; } } } } else { //fire mode: quickly fire at targets and doesn't follow player this.fire() } if (!m.isBodiesAsleep) { //update current waves ctx.strokeStyle = "rgba(0,0,0,0.6)" //"000"; ctx.lineWidth = 2 * tech.wavePacketDamage ctx.beginPath(); const end = 1200 * Math.sqrt(tech.bulletsLastLonger) //this does less damage than the player phonon waves 2.3 -> 2 for (let i = this.waves.length - 1; i > -1; i--) { const v1 = Vector.add(this.waves[i].position, Vector.mult(this.waves[i].unit1, this.waves[i].radius)) const v2 = Vector.add(this.waves[i].position, Vector.mult(this.waves[i].unit2, this.waves[i].radius)) //draw wave ctx.moveTo(v1.x, v1.y) ctx.arc(this.waves[i].position.x, this.waves[i].position.y, this.waves[i].radius, this.waves[i].angle, this.waves[i].angle + this.waves[i].arc); //using small angle linear approximation of circle arc, this will not work if the arc gets large // https://stackoverflow.com/questions/13652518/efficiently-find-points-inside-a-circle-sector let hits = Matter.Query.ray(mob, v1, v2, 50) for (let j = 0; j < hits.length; j++) { const who = hits[j].body if (!who.isShielded) { who.force.x += 0.01 * (Math.random() - 0.5) * who.mass who.force.y += 0.01 * (Math.random() - 0.5) * who.mass Matter.Body.setVelocity(who, { x: who.velocity.x * 0.98, y: who.velocity.y * 0.98 }); let vertices = who.vertices; const vibe = 50 + who.radius * 0.15 ctx.moveTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); for (let j = 1; j < vertices.length; j++) ctx.lineTo(vertices[j].x + vibe * (Math.random() - 0.5), vertices[j].y + vibe * (Math.random() - 0.5)); ctx.lineTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); who.locatePlayer(); who.damage(this.waves[i].dmg / Math.pow(who.radius, 0.33)); if (tech.isPhononWave && this.phononWaveCD < m.cycle) { this.phononWaveCD = m.cycle + 10 * (1 + this.waves[i].resonanceCount) let closestMob, dist let range = end - 30 * this.waves[i].resonanceCount for (let i = 0, len = mob.length; i < len; i++) { if (who !== mob[i] && !mob[i].isBadTarget && !mob[i].isInvulnerable) { dist = Vector.magnitude(Vector.sub(who.position, mob[i].position)); if (dist < range) { closestMob = mob[i] range = dist } } } if (closestMob) { //add wave to waves array this.addWave(who.position, Math.atan2(closestMob.position.y - who.position.y, closestMob.position.x - who.position.x) + tech.isBulletTeleport * 0.3 * (Math.random() - 0.5)) } else { this.addWave(who.position, Math.random() * Math.PI) } this.waves[this.waves.length - 1].resonanceCount = this.waves[i].resonanceCount + 1 break } } } hits = Matter.Query.ray(body, v1, v2, 50) //Matter.Query.ray(bodies, startPoint, endPoint, [rayWidth]) for (let j = 0, len = Math.min(30, hits.length); j < len; j++) { const who = hits[j].body //make them shake around who.force.x += 0.005 * (Math.random() - 0.5) * who.mass who.force.y += (0.005 * (Math.random() - 0.5) - simulation.g * 0.1) * who.mass //remove force of gravity let vertices = who.vertices; const vibe = 25 ctx.moveTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); for (let j = 1; j < vertices.length; j++) { ctx.lineTo(vertices[j].x + vibe * (Math.random() - 0.5), vertices[j].y + vibe * (Math.random() - 0.5)); } ctx.lineTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); if (tech.isPhononBlock && !who.isNotHoldable && who.speed < 5 && who.angularSpeed < 0.1) { if (Math.random() < 0.5) b.targetedBlock(who, 50 - Math.min(25, who.mass * 3)) // targetedBlock(who, speed = 50 - Math.min(20, who.mass * 2), range = 1600) { // Matter.Body.setAngularVelocity(who, (0.25 + 0.12 * Math.random()) * (Math.random() < 0.5 ? -1 : 1)); who.torque += who.inertia * 0.001 * (Math.random() - 0.5) } } this.waves[i].radius += tech.waveBeamSpeed * 2 if (this.waves[i].radius > end - 30 * this.waves[i].resonanceCount) { this.waves.splice(i, 1) //end } } ctx.stroke(); } } }) Composite.add(engine.world, bullet[me]); //add bullet to world }, laserBot(position = { x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, isConsole = true) { if (isConsole) simulation.makeTextLog(`b.laserBot()`); const me = bullet.length; const dir = m.angle; const RADIUS = (14 + 6 * Math.random()) bullet[me] = Bodies.polygon(position.x, position.y, 3, RADIUS, { botType: "laser", angle: dir, friction: 0, frictionStatic: 0, frictionAir: 0.008 * (1 + 0.3 * Math.random()), restitution: 0.5 * (1 + 0.5 * Math.random()), acceleration: 0.0015 * (1 + 0.3 * Math.random()), playerRange: 140 + Math.floor(30 * Math.random()) + 2 * b.totalBots(), offPlayer: { x: 0, y: 0, }, dmg: 0, //damage done in addition to the damage from momentum minDmgSpeed: 2, lookFrequency: 20 + Math.floor(7 * Math.random()) - 13 * tech.isLaserBotUpgrade, range: (600 + 375 * tech.isLaserBotUpgrade) * (1 + 0.12 * Math.random()), drainThreshold: 0.15 + 0.5 * Math.random() + (tech.isEnergyHealth ? 0.3 : 0),// laser bot will not attack if the player is below this energy drain: (0.57 - 0.43 * tech.isLaserBotUpgrade) * tech.laserDrain, laserDamage: 0.75 + 0.75 * tech.isLaserBotUpgrade, endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: b.totalBots() < 50 ? cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield : cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield //if over 50 bots, they no longer collide with each other }, lockedOn: null, beforeDmg() { this.lockedOn = null }, onEnd() { }, do() { const playerPos = Vector.add(Vector.add(this.offPlayer, m.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.playerRange) //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 (!(simulation.cycle % this.lookFrequency)) { this.lockedOn = null; if (!m.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 && (!mob[i].isBadTarget || mob[i].isMobBullet) && Matter.Query.ray(map, this.vertices[0], mob[i].position).length === 0 && Matter.Query.ray(body, this.vertices[0], mob[i].position).length === 0 && !mob[i].isInvulnerable ) { closeDist = DIST; this.lockedOn = mob[i] } } } //randomize position relative to player if (Math.random() < 0.15) { const range = 110 + 4 * b.totalBots() this.offPlayer = { x: range * (Math.random() - 0.5), y: range * (Math.random() - 0.5) - 20, } } } //hit target with laser if (this.lockedOn && this.lockedOn.alive && m.energy > this.drainThreshold) { m.energy -= this.drain this.laser(); // b.laser(this.vertices[0], this.lockedOn.position, m.dmgScale * this.laserDamage * tech.laserDamage, tech.laserReflections, false, 0.4) //tech.laserDamage = 0.16 } }, laser() { const push = 0.4 const reflectivity = 1 - 1 / (tech.laserReflections * 3) let damage = m.dmgScale * this.laserDamage * tech.laserDamage //make the laser wiggle as it aims at the target let best = { x: 1, y: 1, dist2: Infinity, who: null, v1: 1, v2: 1 }; const perp2 = Vector.mult(Vector.rotate({ x: 1, y: 0 }, m.angle + Math.PI / 2), 0.6 * this.lockedOn.radius * Math.sin(simulation.cycle / this.lookFrequency)) const path = [{ x: this.vertices[0].x, y: this.vertices[0].y }, Vector.add(this.lockedOn.position, perp2)]; 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 = simulation.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 = simulation.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: 1, y: 1, dist2: Infinity, who: null, v1: 1, v2: 1 }; 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.locatePlayer(); if (best.who.damageReduction) { if ( //iridescence tech.laserCrit && !best.who.shield && Vector.dot(Vector.normalise(Vector.sub(best.who.position, path[path.length - 1])), Vector.normalise(Vector.sub(path[path.length - 1], path[path.length - 2]))) > 0.999 - 0.5 / best.who.radius ) { damage *= 1 + tech.laserCrit simulation.drawList.push({ //add dmg to draw queue x: path[path.length - 1].x, y: path[path.length - 1].y, radius: Math.sqrt(2500 * damage * best.who.damageReduction) + 5, color: `hsla(${60 + 283 * Math.random()},100%,70%,0.5)`, // random hue, but not red time: 16 }); } else { simulation.drawList.push({ //add dmg to draw queue x: path[path.length - 1].x, y: path[path.length - 1].y, radius: Math.sqrt(2000 * damage * best.who.damageReduction) + 2, color: tech.laserColorAlpha, time: simulation.drawTime }); } best.who.damage(damage); } if (tech.isLaserPush) { //push mobs away const index = path.length - 1 Matter.Body.setVelocity(best.who, { x: best.who.velocity.x * 0.97, y: best.who.velocity.y * 0.97 }); const force = Vector.mult(Vector.normalise(Vector.sub(path[index], path[Math.max(0, index - 1)])), 0.003 * push * Math.min(6, best.who.mass)) Matter.Body.applyForce(best.who, path[index], force) } } else if (tech.isLaserPush && best.who.classType === "body") { const index = path.length - 1 Matter.Body.setVelocity(best.who, { x: best.who.velocity.x * 0.97, y: best.who.velocity.y * 0.97 }); const force = Vector.mult(Vector.normalise(Vector.sub(path[index], path[Math.max(0, index - 1)])), 0.003 * push * Math.min(6, best.who.mass)) Matter.Body.applyForce(best.who, path[index], force) } }; 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 < tech.laserReflections; 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 rid 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.strokeStyle = tech.laserColor; ctx.lineWidth = 2 ctx.lineDashOffset = 900 * 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([]); ctx.globalAlpha = 1; } }) Composite.add(engine.world, bullet[me]); //add bullet to world }, boomBot(position = { x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, isConsole = true) { if (isConsole) simulation.makeTextLog(`b.boomBot()`); const me = bullet.length; const dir = m.angle; const RADIUS = (7 + 2 * Math.random()) bullet[me] = Bodies.polygon(position.x, position.y, 4, RADIUS, { isUpgraded: tech.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()) - 13 * tech.isBoomBotUpgrade, acceleration: 0.005 * (1 + 0.5 * Math.random()), attackAcceleration: 0.012 + 0.006 * tech.isBoomBotUpgrade, range: 500 * (1 + 0.1 * Math.random()) + 350 * tech.isBoomBotUpgrade, endCycle: Infinity, classType: "bullet", collisionFilter: { category: cat.bullet, mask: b.totalBots() < 50 ? cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield : cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield //if over 50 bots, they no longer collide with each other }, lockedOn: null, explode: 0, beforeDmg() { if (this.lockedOn) { const explosionRadius = Math.min(136 + 230 * this.isUpgraded, Vector.magnitude(Vector.sub(this.position, m.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, m.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() { const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, player.position)) if (distanceToPlayer > 100) { //if far away move towards player if (this.explode) { // if (tech.isImmuneExplosion && m.energy > 1.43) { // b.explosion(this.position, this.explode); // } else { // } b.explosion(this.position, Math.max(0, Math.min(this.explode, (distanceToPlayer - 70) / b.explosionRange()))); this.explode = 0; } this.force = Vector.mult(Vector.normalise(Vector.sub(player.position, 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 (!(simulation.cycle % this.lookFrequency) && !m.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].isBadTarget && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable ) { closeDist = DIST; this.lockedOn = mob[i] } } } } //punch target if (this.lockedOn && this.lockedOn.alive && !m.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)), this.attackAcceleration * this.mass)) } } } }) Composite.add(engine.world, bullet[me]); //add bullet to world }, plasmaBot(position = { x: player.position.x + 50 * (Math.random() - 0.5), y: player.position.y + 50 * (Math.random() - 0.5) }, isConsole = true) { if (isConsole) simulation.makeTextLog(`b.plasmaBot()`); const me = bullet.length; const dir = m.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, drainThreshold: tech.isEnergyHealth ? 0.5 : 0.05, classType: "bullet", collisionFilter: { category: cat.bullet, mask: b.totalBots() < 50 ? cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield : cat.map | cat.body | cat.mob | cat.mobBullet | cat.mobShield //if over 50 bots, they no longer collide with each other }, lockedOn: null, beforeDmg() { this.lockedOn = null }, onEnd() { }, do() { const distanceToPlayer = Vector.magnitude(Vector.sub(this.position, m.pos)) if (distanceToPlayer > 150) this.force = Vector.mult(Vector.normalise(Vector.sub(m.pos, this.position)), this.mass * this.acceleration) //if far away move towards player Matter.Body.setVelocity(this, Vector.add(Vector.mult(this.velocity, 0.90), Vector.mult(player.velocity, 0.17))); //add player's velocity if (!(simulation.cycle % this.lookFrequency)) { //find closest this.lockedOn = null; if (!m.isCloak) { let closeDist = tech.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 && (!mob[i].isBadTarget || mob[i].isMobBullet) && Matter.Query.ray(map, this.position, mob[i].position).length === 0 && Matter.Query.ray(body, this.position, mob[i].position).length === 0 && !mob[i].isInvulnerable ) { closeDist = DIST; this.lockedOn = mob[i] } } } } //fire plasma at target if (this.lockedOn && this.lockedOn.alive && m.fieldCDcycle < m.cycle) { const sub = Vector.sub(this.lockedOn.position, this.position) const DIST = Vector.magnitude(sub); const unit = Vector.normalise(sub) if (DIST < tech.isPlasmaRange * 450 && m.energy > this.drainThreshold) { m.energy -= 0.00135 //0.004; //normal plasma field is 0.00008 + m.fieldRegen = 0.00108 // if (m.energy < 0) { // m.fieldCDcycle = m.cycle + 120; // m.energy = 0; // } //calculate laser collision let best; let range = tech.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 = simulation.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 = simulation.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.9 * m.dmgScale; //********** SCALE DAMAGE HERE ********************* best.who.damage(dmg); best.who.locatePlayer(); //push mobs away const force = Vector.mult(Vector.normalise(Vector.sub(m.pos, path[1])), -0.01 * Math.min(5, best.who.mass)) Matter.Body.applyForce(best.who, path[1], force) if (best.who.speed > 3) { Matter.Body.setVelocity(best.who, { //friction x: best.who.velocity.x * 0.7, y: best.who.velocity.y * 0.7 }); } //draw mob damage circle if (best.who.damageReduction) { simulation.drawList.push({ x: path[1].x, y: path[1].y, // radius: Math.sqrt(dmg) * 50 * mob[k].damageReduction, // radius: 600 * dmg * best.who.damageReduction, radius: Math.sqrt(2000 * dmg * best.who.damageReduction) + 2, color: "rgba(255,0,255,0.2)", time: simulation.drawTime * 4 }); } } else if (!best.who.isStatic) { //push blocks away const force = Vector.mult(Vector.normalise(Vector.sub(m.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.beginPath(); ctx.moveTo(path[0].x, path[0].y); ctx.lineTo(path[1].x, path[1].y); ctx.strokeStyle = "rgba(255,0,255,0.1)" ctx.lineWidth = 14 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(); } } } }) Composite.add(engine.world, bullet[me]); //add bullet to world }, orbitBot(position = player.position, isConsole = true) { if (isConsole) simulation.makeTextLog(`b.orbitBot()`); const me = bullet.length; bullet[me] = Bodies.polygon(position.x, position.y, 9, 12, { isUpgraded: tech.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 + 130 * tech.isOrbitBotUpgrade, //range is set in bot upgrade too! orbitalSpeed: 0, phase: 2 * Math.PI * Math.random(), do() { if (!m.isCloak) { //if time dilation isn't active 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++) { if (!q[i].isShielded) { mobs.statusStun(q[i], 210 + 90 * this.isUpgraded) const dmg = 0.4 * m.dmgScale * (this.isUpgraded ? 4.5 : 1) * (tech.isCrit ? 4 : 1) q[i].damage(dmg); if (q[i].alive) q[i].foundPlayer(); if (q[i].damageReduction) { simulation.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, // radius: 600 * dmg * q[i].damageReduction, radius: Math.sqrt(2000 * dmg * q[i].damageReduction) + 2, color: 'rgba(0,0,0,0.4)', time: simulation.drawTime }); } } } } //orbit player const time = simulation.cycle * this.orbitalSpeed + this.phase const orbit = { x: Math.cos(time), y: Math.sin(time) //*1.1 } Matter.Body.setPosition(this, Vector.add(m.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 / tech.orbitBotCount) * 2 * Math.PI Composite.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 ********************************************* // ************************************************************************************************** // ************************************************************************************************** //0 nail gun //1 shotgun //2 super balls //3 wave //4 missiles //5 grenades //6 spores //7 drones //8 foam //9 harpoon //10 mine //11 laser guns: [{ name: "nail gun", // 0 description: "use compressed air to fire a stream of nails
delay after firing decreases as you shoot", ammo: 0, ammoPack: 60, defaultAmmoPack: 60, recordedAmmo: 0, have: false, nextFireCycle: 0, //use to remember how longs its been since last fire, used to reset count startingHoldCycle: 0, chooseFireMethod() { //set in simulation.startGame if (tech.nailRecoil) { if (tech.isRivets) { this.fire = this.fireRecoilRivets } else { this.fire = this.fireRecoilNails } } else if (tech.isRivets) { this.fire = this.fireRivets } else if (tech.isNeedles) { this.fire = this.fireNeedles } else if (tech.nailInstantFireRate) { this.fire = this.fireInstantFireRate // } else if (tech.nailFireRate) { // this.fire = this.fireNailFireRate } else { this.fire = this.fireNormal } }, do() { }, fire() { }, fireRecoilNails() { if (this.nextFireCycle + 1 < m.cycle) this.startingHoldCycle = m.cycle //reset if not constantly firing const CD = Math.max(11 - 0.06 * (m.cycle - this.startingHoldCycle), 0.99) //CD scales with cycles fire is held down this.nextFireCycle = m.cycle + CD * b.fireCDscale //predict next fire cycle if the fire button is held down m.fireCDcycle = m.cycle + Math.floor(CD * b.fireCDscale); // cool down this.baseFire(m.angle + (Math.random() - 0.5) * (m.crouch ? 0.04 : 0.13) / CD, 45 + 6 * Math.random()) //very complex recoil system if (m.onGround) { if (m.crouch) { const KNOCK = 0.006 player.force.x -= KNOCK * Math.cos(m.angle) player.force.y -= KNOCK * Math.sin(m.angle) //reduce knock back in vertical direction to stop super jumps Matter.Body.setVelocity(player, { x: player.velocity.x * 0.5, y: player.velocity.y * 0.5 }); } else { const KNOCK = 0.03 player.force.x -= KNOCK * Math.cos(m.angle) player.force.y -= KNOCK * Math.sin(m.angle) //reduce knock back in vertical direction to stop super jumps Matter.Body.setVelocity(player, { x: player.velocity.x * 0.8, y: player.velocity.y * 0.8 }); } } else { player.force.x -= 0.06 * Math.cos(m.angle) * Math.min(1, 3 / (0.1 + Math.abs(player.velocity.x))) player.force.y -= 0.006 * Math.sin(m.angle) //reduce knock back in vertical direction to stop super jumps } }, fireNormal() { if (this.nextFireCycle + 1 < m.cycle) this.startingHoldCycle = m.cycle //reset if not constantly firing const CD = Math.max(11 - 0.06 * (m.cycle - this.startingHoldCycle), 1) //CD scales with cycles fire is held down this.nextFireCycle = m.cycle + CD * b.fireCDscale //predict next fire cycle if the fire button is held down m.fireCDcycle = m.cycle + Math.floor(CD * b.fireCDscale); // cool down this.baseFire(m.angle + (Math.random() - 0.5) * (m.crouch ? 0.05 : 0.3) / CD) }, fireNeedles() { if (m.crouch) { m.fireCDcycle = m.cycle + 30 * b.fireCDscale; // cool down b.needle() function cycle() { if (simulation.paused || m.isBodiesAsleep) { requestAnimationFrame(cycle) } else { count++ if (count % 2) b.needle() if (count < 7 && m.alive) requestAnimationFrame(cycle); } } let count = -1 requestAnimationFrame(cycle); } else { m.fireCDcycle = m.cycle + 22 * b.fireCDscale; // cool down b.needle() function cycle() { if (simulation.paused || m.isBodiesAsleep) { requestAnimationFrame(cycle) } else { count++ if (count % 2) b.needle() if (count < 3 && m.alive) requestAnimationFrame(cycle); } } let count = -1 requestAnimationFrame(cycle); } }, fireRivets() { m.fireCDcycle = m.cycle + Math.floor((m.crouch ? 22 : 14) * b.fireCDscale); // cool down const me = bullet.length; const size = tech.bulletSize * 8 bullet[me] = Bodies.rectangle(m.pos.x + 35 * Math.cos(m.angle), m.pos.y + 35 * Math.sin(m.angle), 5 * size, size, b.fireAttributes(m.angle)); bullet[me].dmg = tech.isNailRadiation ? 0 : 2.75 Matter.Body.setDensity(bullet[me], 0.002); Composite.add(engine.world, bullet[me]); //add bullet to world const SPEED = m.crouch ? 60 : 44 Matter.Body.setVelocity(bullet[me], { x: SPEED * Math.cos(m.angle), y: SPEED * Math.sin(m.angle) }); bullet[me].endCycle = simulation.cycle + 180 bullet[me].beforeDmg = function (who) { //beforeDmg is rewritten with ice crystal tech if (tech.isIncendiary) { this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion b.explosion(this.position, 100 + (Math.random() - 0.5) * 20); //makes bullet do explosive damage at end } if (tech.isNailCrit) { if (!who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.97 - 1 / who.radius) { b.explosion(this.position, 300 + 40 * Math.random()); //makes bullet do explosive damage at end } } else if (tech.isCritKill) b.crit(who, this) if (tech.isNailRadiation) mobs.statusDoT(who, 7 * (tech.isFastRadiation ? 0.7 : 0.24), tech.isSlowRadiation ? 360 : (tech.isFastRadiation ? 60 : 180)) // one tick every 30 cycles if (this.speed > 4 && tech.fragments) { b.targetedNail(this.position, 1.25 * tech.fragments * tech.bulletSize) this.endCycle = 0 //triggers despawn } }; bullet[me].minDmgSpeed = 10 bullet[me].frictionAir = 0.006; bullet[me].rotateToVelocity = function () { //rotates bullet to face current velocity? if (this.speed > 7) { const facing = { x: Math.cos(this.angle), y: Math.sin(this.angle) } const mag = 0.002 * this.mass if (Vector.cross(Vector.normalise(this.velocity), facing) < 0) { this.torque += mag } else { this.torque -= mag } } }; if (tech.isIncendiary) { bullet[me].do = function () { this.force.y += this.mass * 0.0008 this.rotateToVelocity() //collide with map if (Matter.Query.collides(this, map).length) { //penetrate walls this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion b.explosion(this.position, 300 + 40 * Math.random()); //makes bullet do explosive damage at end } }; } else { bullet[me].do = function () { this.force.y += this.mass * 0.0008 this.rotateToVelocity() }; } b.muzzleFlash(); //very complex recoil system if (m.onGround) { if (m.crouch) { const KNOCK = 0.01 player.force.x -= KNOCK * Math.cos(m.angle) player.force.y -= KNOCK * Math.sin(m.angle) //reduce knock back in vertical direction to stop super jumps } else { const KNOCK = 0.02 player.force.x -= KNOCK * Math.cos(m.angle) player.force.y -= KNOCK * Math.sin(m.angle) //reduce knock back in vertical direction to stop super jumps } } else { const KNOCK = 0.01 player.force.x -= KNOCK * Math.cos(m.angle) player.force.y -= KNOCK * Math.sin(m.angle) * 0.5 //reduce knock back in vertical direction to stop super jumps } }, fireRecoilRivets() { // m.fireCDcycle = m.cycle + Math.floor((m.crouch ? 25 : 17) * b.fireCDscale); // cool down if (this.nextFireCycle + 1 < m.cycle) this.startingHoldCycle = m.cycle //reset if not constantly firing const CD = Math.max(25 - 0.14 * (m.cycle - this.startingHoldCycle), 5) //CD scales with cycles fire is held down this.nextFireCycle = m.cycle + CD * b.fireCDscale //predict next fire cycle if the fire button is held down m.fireCDcycle = m.cycle + Math.floor(CD * b.fireCDscale); // cool down const me = bullet.length; const size = tech.bulletSize * 8 bullet[me] = Bodies.rectangle(m.pos.x + 35 * Math.cos(m.angle), m.pos.y + 35 * Math.sin(m.angle), 5 * size, size, b.fireAttributes(m.angle)); bullet[me].dmg = tech.isNailRadiation ? 0 : 2.75 Matter.Body.setDensity(bullet[me], 0.002); Composite.add(engine.world, bullet[me]); //add bullet to world const SPEED = m.crouch ? 62 : 52 Matter.Body.setVelocity(bullet[me], { x: SPEED * Math.cos(m.angle), y: SPEED * Math.sin(m.angle) }); bullet[me].endCycle = simulation.cycle + 180 bullet[me].beforeDmg = function (who) { //beforeDmg is rewritten with ice crystal tech if (tech.isIncendiary) { this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion b.explosion(this.position, 100 + (Math.random() - 0.5) * 20); //makes bullet do explosive damage at end } if (tech.isNailCrit) { if (!who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.97 - 1 / who.radius) { b.explosion(this.position, 300 + 40 * Math.random()); //makes bullet do explosive damage at end } } else if (tech.isCritKill) b.crit(who, this) if (tech.isNailRadiation) mobs.statusDoT(who, 7 * (tech.isFastRadiation ? 0.7 : 0.24), tech.isSlowRadiation ? 360 : (tech.isFastRadiation ? 60 : 180)) // one tick every 30 cycles if (this.speed > 4 && tech.fragments) { b.targetedNail(this.position, 1.25 * tech.fragments * tech.bulletSize) this.endCycle = 0 //triggers despawn } }; bullet[me].minDmgSpeed = 10 bullet[me].frictionAir = 0.006; bullet[me].rotateToVelocity = function () { //rotates bullet to face current velocity? if (this.speed > 7) { const facing = { x: Math.cos(this.angle), y: Math.sin(this.angle) } const mag = 0.002 * this.mass if (Vector.cross(Vector.normalise(this.velocity), facing) < 0) { this.torque += mag } else { this.torque -= mag } } }; if (tech.isIncendiary) { bullet[me].do = function () { this.force.y += this.mass * 0.0008 this.rotateToVelocity() //collide with map if (Matter.Query.collides(this, map).length) { //penetrate walls this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion b.explosion(this.position, 100 + (Math.random() - 0.5) * 20); //makes bullet do explosive damage at end } }; } else { bullet[me].do = function () { this.force.y += this.mass * 0.0008 this.rotateToVelocity() }; } b.muzzleFlash(); //very complex recoil system if (m.onGround) { if (m.crouch) { const KNOCK = 0.03 player.force.x -= KNOCK * Math.cos(m.angle) player.force.y -= KNOCK * Math.sin(m.angle) //reduce knock back in vertical direction to stop super jumps Matter.Body.setVelocity(player, { x: player.velocity.x * 0.4, y: player.velocity.y * 0.4 }); } else { const KNOCK = 0.1 player.force.x -= KNOCK * Math.cos(m.angle) player.force.y -= KNOCK * Math.sin(m.angle) //reduce knock back in vertical direction to stop super jumps Matter.Body.setVelocity(player, { x: player.velocity.x * 0.7, y: player.velocity.y * 0.7 }); } } else { player.force.x -= 0.2 * Math.cos(m.angle) * Math.min(1, 3 / (0.1 + Math.abs(player.velocity.x))) // player.force.x -= 0.06 * Math.cos(m.angle) * Math.min(1, 3 / (0.1 + Math.abs(player.velocity.x))) player.force.y -= 0.02 * Math.sin(m.angle) //reduce knock back in vertical direction to stop super jumps } }, fireInstantFireRate() { m.fireCDcycle = m.cycle + Math.floor(1 * b.fireCDscale); // cool down this.baseFire(m.angle + (Math.random() - 0.5) * (Math.random() - 0.5) * (m.crouch ? 1.15 : 2) / 2) }, baseFire(angle, speed = 30 + 6 * Math.random()) { b.nail({ x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }, { x: m.Vx / 2 + speed * Math.cos(angle), y: m.Vy / 2 + speed * Math.sin(angle) }) //position, velocity, damage if (tech.isIceCrystals) { bullet[bullet.length - 1].beforeDmg = function (who) { mobs.statusSlow(who, 60) if (tech.isNailRadiation) mobs.statusDoT(who, 1 * (tech.isFastRadiation ? 1.3 : 0.44), tech.isSlowRadiation ? 360 : (tech.isFastRadiation ? 60 : 180)) // one tick every 30 cycles if (tech.isNailCrit) { if (!who.shield && Vector.dot(Vector.normalise(Vector.sub(who.position, this.position)), Vector.normalise(this.velocity)) > 0.97 - 1 / who.radius) { b.explosion(this.position, 150 + 30 * Math.random()); //makes bullet do explosive damage at end } } this.ricochet(who) }; if (m.energy < 0.01) { m.fireCDcycle = m.cycle + 60; // cool down } else { m.energy -= 0.01 } } }, }, { name: "shotgun", //1 description: "fire a wide burst of short range bullets
with a low fire rate", ammo: 0, ammoPack: 3.5, defaultAmmoPack: 3.5, have: false, do() { //fade cross hairs // draw loop around player head // const left = m.fireCDcycle !== Infinity ? 0.05 * Math.max(m.fireCDcycle - m.cycle, 0) : 0 // if (left > 0) { // ctx.beginPath(); // // ctx.arc(simulation.mouseInGame.x, simulation.mouseInGame.y, 30, 0, left); // ctx.arc(m.pos.x, m.pos.y, 28, m.angle - left, m.angle); // // ctx.fillStyle = "rgba(0,0,0,0.3)" //"#333" // // ctx.fill(); // ctx.strokeStyle = "#333"; // ctx.lineWidth = 2; // ctx.stroke(); // } //draw hip circle // ctx.beginPath(); // ctx.arc(m.pos.x + m.hip.x, m.pos.y + m.hip.y, 11, 0, 2 * Math.PI); // ctx.fillStyle = "rgba(0,0,0,0.3)" //"#333" // ctx.fill(); }, fire() { let knock, spread const coolDown = function () { if (m.crouch) { spread = 0.65 m.fireCDcycle = m.cycle + Math.floor((73 + 36 * tech.shotgunExtraShots) * b.fireCDscale) // cool down if (tech.isShotgunImmune && m.immuneCycle < m.cycle + Math.floor(60 * b.fireCDscale)) m.immuneCycle = m.cycle + Math.floor(60 * b.fireCDscale); //player is immune to damage for 30 cycles knock = 0.01 } else { m.fireCDcycle = m.cycle + Math.floor((56 + 28 * tech.shotgunExtraShots) * b.fireCDscale) // cool down if (tech.isShotgunImmune && m.immuneCycle < m.cycle + Math.floor(47 * b.fireCDscale)) m.immuneCycle = m.cycle + Math.floor(47 * b.fireCDscale); //player is immune to damage for 30 cycles spread = 1.3 knock = 0.1 } if (tech.isShotgunReversed) { player.force.x += 1.5 * knock * Math.cos(m.angle) player.force.y += 1.5 * knock * Math.sin(m.angle) - 3 * player.mass * simulation.g } else if (tech.isShotgunRecoil) { m.fireCDcycle -= 0.66 * (56 * b.fireCDscale) player.force.x -= 2 * knock * Math.cos(m.angle) player.force.y -= 2 * knock * Math.sin(m.angle) } else { player.force.x -= knock * Math.cos(m.angle) player.force.y -= knock * Math.sin(m.angle) * 0.5 //reduce knock back in vertical direction to stop super jumps } } const spray = (num) => { const side = 22 for (let i = 0; i < num; i++) { const me = bullet.length; const dir = m.angle + (Math.random() - 0.5) * spread bullet[me] = Bodies.rectangle(m.pos.x, m.pos.y, side, side, b.fireAttributes(dir)); Composite.add(engine.world, bullet[me]); //add bullet to world const SPEED = 52 + Math.random() * 8 Matter.Body.setVelocity(bullet[me], { x: SPEED * Math.cos(dir), y: SPEED * Math.sin(dir) }); bullet[me].endCycle = simulation.cycle + 40 * tech.bulletsLastLonger bullet[me].minDmgSpeed = 15 if (tech.isShotgunReversed) Matter.Body.setDensity(bullet[me], 0.0015) // bullet[me].restitution = 0.4 bullet[me].frictionAir = 0.034; bullet[me].do = function () { const scale = 1 - 0.034 / tech.bulletsLastLonger Matter.Body.scale(this, scale, scale); }; } } const chooseBulletType = function () { if (tech.isRivets) { const me = bullet.length; // const dir = m.angle + 0.02 * (Math.random() - 0.5) bullet[me] = Bodies.rectangle(m.pos.x + 35 * Math.cos(m.angle), m.pos.y + 35 * Math.sin(m.angle), 56 * tech.bulletSize, 25 * tech.bulletSize, b.fireAttributes(m.angle)); Matter.Body.setDensity(bullet[me], 0.005 * (tech.isShotgunReversed ? 1.5 : 1)); Composite.add(engine.world, bullet[me]); //add bullet to world const SPEED = (m.crouch ? 50 : 43) Matter.Body.setVelocity(bullet[me], { x: SPEED * Math.cos(m.angle), y: SPEED * Math.sin(m.angle) }); if (tech.isIncendiary) { bullet[me].endCycle = simulation.cycle + 60 bullet[me].onEnd = function () { b.explosion(this.position, 360 + (Math.random() - 0.5) * 60); //makes bullet do explosive damage at end } bullet[me].beforeDmg = function () { this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion }; } else { bullet[me].endCycle = simulation.cycle + 180 } bullet[me].minDmgSpeed = 7 // bullet[me].restitution = 0.4 bullet[me].frictionAir = 0.004; bullet[me].turnMag = 0.04 * Math.pow(tech.bulletSize, 3.75) bullet[me].do = function () { this.force.y += this.mass * 0.002 if (this.speed > 6) { //rotates bullet to face current velocity? const facing = { x: Math.cos(this.angle), y: Math.sin(this.angle) } if (Vector.cross(Vector.normalise(this.velocity), facing) < 0) { this.torque += this.turnMag } else { this.torque -= this.turnMag } } if (tech.isIncendiary && Matter.Query.collides(this, map).length) { this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion } }; bullet[me].beforeDmg = function (who) { if (this.speed > 4) { if (tech.fragments) { b.targetedNail(this.position, 6 * tech.fragments * tech.bulletSize) this.endCycle = 0 //triggers despawn } if (tech.isIncendiary) this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion if (tech.isCritKill) b.crit(who, this) } } spray(12); //fires normal shotgun bullets } else if (tech.isIncendiary) { spread *= 0.15 const END = Math.floor(m.crouch ? 8 : 5); const totalBullets = 9 const angleStep = (m.crouch ? 0.3 : 0.8) / totalBullets let dir = m.angle - angleStep * totalBullets / 2; for (let i = 0; i < totalBullets; i++) { //5 -> 7 dir += angleStep const me = bullet.length; bullet[me] = Bodies.rectangle(m.pos.x + 50 * Math.cos(m.angle), m.pos.y + 50 * Math.sin(m.angle), 17, 4, b.fireAttributes(dir)); const end = END + Math.random() * 4 bullet[me].endCycle = 2 * end * tech.bulletsLastLonger + simulation.cycle const speed = 25 * end / END const dirOff = dir + (Math.random() - 0.5) * spread Matter.Body.setVelocity(bullet[me], { x: speed * Math.cos(dirOff), y: speed * Math.sin(dirOff) }); bullet[me].onEnd = function () { b.explosion(this.position, 150 * (tech.isShotgunReversed ? 1.4 : 1) + (Math.random() - 0.5) * 30); //makes bullet do explosive damage at end } bullet[me].beforeDmg = function () { this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion }; bullet[me].do = function () { if (Matter.Query.collides(this, map).length) this.endCycle = 0; //bullet ends cycle after hitting a mob and triggers explosion } Composite.add(engine.world, bullet[me]); //add bullet to world } } else if (tech.isNailShot) { spread *= 0.65 const dmg = 2 * (tech.isShotgunReversed ? 1.5 : 1) if (m.crouch) { for (let i = 0; i < 17; i++) { speed = 38 + 15 * Math.random() const dir = m.angle + (Math.random() - 0.5) * spread const pos = { x: m.pos.x + 35 * Math.cos(m.angle) + 15 * (Math.random() - 0.5), y: m.pos.y + 35 * Math.sin(m.angle) + 15 * (Math.random() - 0.5) } b.nail(pos, { x: speed * Math.cos(dir), y: speed * Math.sin(dir) }, dmg) } } else { for (let i = 0; i < 17; i++) { speed = 38 + 15 * Math.random() const dir = m.angle + (Math.random() - 0.5) * spread const pos = { x: m.pos.x + 35 * Math.cos(m.angle) + 15 * (Math.random() - 0.5), y: m.pos.y + 35 * Math.sin(m.angle) + 15 * (Math.random() - 0.5) } b.nail(pos, { x: speed * Math.cos(dir), y: speed * Math.sin(dir) }, dmg) } } } else if (tech.isSporeFlea) { const where = { x: m.pos.x + 35 * Math.cos(m.angle), y: m.pos.y + 35 * Math.sin(m.angle) } const number = 2 * (tech.isShotgunReversed ? 1.5 : 1) for (let i = 0; i < number; i++) { const angle = m.angle + 0.2 * (Math.random() - 0.5) const speed = (m.crouch ? 35 * (1 + 0.05 * Math.random()) : 30 * (1 + 0.15 * Math.random())) b.flea(where, { x: speed * Math.cos(angle), y: speed * Math.sin(angle) }) bullet[bullet.length - 1].setDamage() } spray(10); //fires normal shotgun bullets } else if (tech.isSporeWorm) { const where = { x: m.pos.x + 35 * Math.cos(m.angle), y: m.pos.y + 35 * Math.sin(m.angle) } const spread = (m.crouch ? 0.02 : 0.07) const number = 3 * (tech.isShotgunReversed ? 1.5 : 1) let angle = m.angle - (number - 1) * spread * 0.5 for (let i = 0; i < number; i++) { b.worm(where) const SPEED = (30 + 10 * m.crouch) * (1 + 0.2 * Math.random()) Matter.Body.setVelocity(bullet[bullet.length - 1], { x: player.velocity.x * 0.5 + SPEED * Math.cos(angle), y: player.velocity.y * 0.5 + SPEED * Math.sin(angle) }); angle += spread } spray(7); //fires normal shotgun bullets } else if (tech.isIceShot) { const spread = (m.crouch ? 0.7 : 1.2) for (let i = 0, len = 10 * (tech.isShotgunReversed ? 1.5 : 1); i < len; i++) { b.iceIX(23 + 10 * Math.random(), m.angle + spread * (Math.random() - 0.5)) } spray(10); //fires normal shotgun bullets } else if (tech.isFoamShot) { const spread = (m.crouch ? 0.15 : 0.4) const where = { x: m.pos.x + 25 * Math.cos(m.angle), y: m.pos.y + 25 * Math.sin(m.angle) } const number = 16 * (tech.isShotgunReversed ? 1.5 : 1) for (let i = 0; i < number; i++) { const SPEED = 13 + 4 * Math.random(); const angle = m.angle + spread * (Math.random() - 0.5) b.foam(where, { x: SPEED * Math.cos(angle), y: SPEED * Math.sin(angle) }, 8 + 7 * Math.random()) } } else if (tech.isNeedles) { const number = 9 * (tech.isShotgunReversed ? 1.5 : 1) const spread = (m.crouch ? 0.03 : 0.05) let angle = m.angle - (number - 1) * spread * 0.5 for (let i = 0; i < number; i++) { b.needle(angle) angle += spread } } else { spray(16); //fires normal shotgun bullets } } coolDown(); b.muzzleFlash(35); chooseBulletType(); if (tech.shotgunExtraShots) { const delay = 7 let count = tech.shotgunExtraShots * delay function cycle() { count-- if (!(count % delay)) { coolDown(); b.muzzleFlash(35); chooseBulletType(); } if (count > 0) { requestAnimationFrame(cycle); } } requestAnimationFrame(cycle); } } }, { name: "super balls", //2 description: "fire 3 balls in a wide arc
balls bounce with no momentum loss", ammo: 0, ammoPack: 9, have: false, // num: 5, do() { }, foamBall() { }, fireOne() { m.fireCDcycle = m.cycle + Math.floor((m.crouch ? 27 : 19) * b.fireCDscale); // cool down const speed = m.crouch ? 43 : 36 b.superBall({ x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }, { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) }, 21 * tech.bulletSize) }, fireMulti() { m.fireCDcycle = m.cycle + Math.floor((m.crouch ? 23 : 15) * b.fireCDscale); // cool down const SPREAD = m.crouch ? 0.08 : 0.13 const num = 3 + Math.floor(tech.extraSuperBalls * Math.random()) const speed = m.crouch ? 43 : 36 let dir = m.angle - SPREAD * (num - 1) / 2; for (let i = 0; i < num; i++) { b.superBall({ x: m.pos.x + 30 * Math.cos(dir), y: m.pos.y + 30 * Math.sin(dir) }, { x: speed * Math.cos(dir), y: speed * Math.sin(dir) }, 11 * tech.bulletSize) dir += SPREAD; } }, fireQueue() { m.fireCDcycle = m.cycle + Math.floor((m.crouch ? 23 : 15) * b.fireCDscale); // cool down const num = 1 + 3 + Math.floor(tech.extraSuperBalls * Math.random()) //1 extra const speed = m.crouch ? 43 : 36 const delay = Math.floor((m.crouch ? 18 : 12) * b.fireCDscale) m.fireCDcycle = m.cycle + delay; // cool down function cycle() { count++ b.superBall({ x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }, { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) }, 11 * tech.bulletSize) if (count < num && m.alive) requestAnimationFrame(cycle); m.fireCDcycle = m.cycle + delay; // cool down } let count = 0 requestAnimationFrame(cycle); }, chooseFireMethod() { //set in simulation.startGame if (tech.oneSuperBall) { this.fire = this.fireOne } else if (tech.superBallDelay) { this.fire = this.fireQueue } else { this.fire = this.fireMulti } }, fire() { } }, { name: "wave", //3 description: "emit wave packets that propagate through solids
waves slow mobs", // of oscillating particles
ammo: 0, ammoPack: 115, defaultAmmoPack: 115, have: false, wavePacketCycle: 0, delay: 40, phononWaveCD: 0, waves: [], //used in longitudinal mode chooseFireMethod() { //set in simulation.startGame this.waves = []; if (tech.isLongitudinal) { if (tech.is360Longitudinal) { this.fire = this.fire360Longitudinal this.do = this.do360Longitudinal } else { this.fire = this.fireLongitudinal this.do = this.doLongitudinal } } else { this.fire = this.fireTransverse this.do = this.doTransverse } }, do() { }, do360Longitudinal() { if (!m.isBodiesAsleep) { ctx.strokeStyle = "rgba(0,0,0,0.6)" //"000"; ctx.lineWidth = 2 * tech.wavePacketDamage ctx.beginPath(); const end = 700 * Math.sqrt(tech.bulletsLastLonger) const damage = 2.3 * m.dmgScale * tech.wavePacketDamage * tech.waveBeamDamage * (tech.isBulletTeleport ? 1.43 : 1) * (tech.isInfiniteWaveAmmo ? 0.75 : 1) //damage is lower for large radius mobs, since they feel the waves longer for (let i = this.waves.length - 1; i > -1; i--) { //draw wave ctx.moveTo(this.waves[i].position.x + this.waves[i].radius, this.waves[i].position.y) ctx.arc(this.waves[i].position.x, this.waves[i].position.y, this.waves[i].radius, 0, 2 * Math.PI); // collisions // if (tech.isBulletTeleport && Math.random() < 0.04) { // const scale = 400 * Math.random() // this.waves[i].position = Vector.add(this.waves[i].position, { x: scale * (Math.random() - 0.5), y: scale * (Math.random() - 0.5) }) // } for (let j = 0, len = mob.length; j < len; j++) { if (!mob[j].isShielded) { const dist = Vector.magnitude(Vector.sub(this.waves[i].position, mob[j].position)) const r = mob[j].radius + 30 if (dist + r > this.waves[i].radius && dist - r < this.waves[i].radius) { //make them shake around if (!mob[j].isBadTarget) { mob[j].force.x += 0.01 * (Math.random() - 0.5) * mob[j].mass mob[j].force.y += 0.01 * (Math.random() - 0.5) * mob[j].mass } // if (!mob[j].isShielded) { Matter.Body.setVelocity(mob[j], { //friction x: mob[j].velocity.x * 0.95, y: mob[j].velocity.y * 0.95 }); //draw vibes let vertices = mob[j].vertices; const vibe = 50 + mob[j].radius * 0.15 ctx.moveTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); for (let k = 1; k < vertices.length; k++) { ctx.lineTo(vertices[k].x + vibe * (Math.random() - 0.5), vertices[k].y + vibe * (Math.random() - 0.5)); } ctx.lineTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); //damage mob[j].locatePlayer(); mob[j].damage(damage / Math.sqrt(mob[j].radius)); // } if (tech.isPhononWave && this.phononWaveCD < m.cycle) { this.phononWaveCD = m.cycle + 8 * (1 + this.waves[i].resonanceCount) this.waves.push({ position: mob[j].position, radius: 25, resonanceCount: this.waves[i].resonanceCount + 1, }) } } } } // for (let j = 0, len = body.length; j < len; j++) { for (let j = 0, len = Math.min(30, body.length); j < len; j++) { const dist = Vector.magnitude(Vector.sub(this.waves[i].position, body[j].position)) const r = 20 if (dist + r > this.waves[i].radius && dist - r < this.waves[i].radius) { const who = body[j] //make them shake around who.force.x += 0.01 * (Math.random() - 0.5) * who.mass who.force.y += (0.01 * (Math.random() - 0.5) - simulation.g * 0.25) * who.mass //remove force of gravity //draw vibes let vertices = who.vertices; const vibe = 25 ctx.moveTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); for (let k = 1; k < vertices.length; k++) { ctx.lineTo(vertices[k].x + vibe * (Math.random() - 0.5), vertices[k].y + vibe * (Math.random() - 0.5)); } ctx.lineTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); if (tech.isPhononBlock && !who.isNotHoldable && who.speed < 5 && who.angularSpeed < 0.1) { if (Math.random() < 0.5) b.targetedBlock(who, 50 - Math.min(25, who.mass * 3)) // targetedBlock(who, speed = 50 - Math.min(20, who.mass * 2), range = 1600) { // Matter.Body.setAngularVelocity(who, (0.25 + 0.1 * Math.random()) * (Math.random() < 0.5 ? -1 : 1)); who.torque += who.inertia * 0.001 * (Math.random() - 0.5) } } } this.waves[i].radius += 0.9 * tech.waveBeamSpeed //expand / move // if (this.waves[i].radius > end) this.waves.splice(i, 1) //end if (this.waves[i].radius > end - 30 * this.waves[i].resonanceCount) { //* Math.pow(0.9, this.waves[i].resonanceCount) this.waves.splice(i, 1) //end } } ctx.stroke(); } }, fire360Longitudinal() { m.fireCDcycle = m.cycle + Math.floor((m.crouch ? 4 : 8) * b.fireCDscale); // cool down this.waves.push({ position: { x: m.pos.x, y: m.pos.y, }, radius: 25, resonanceCount: 0 //used with tech.isPhononWave }) }, doLongitudinal() { if (!m.isBodiesAsleep) { ctx.strokeStyle = "rgba(0,0,0,0.6)" //"000"; ctx.lineWidth = 2 * tech.wavePacketDamage ctx.beginPath(); const end = 1100 * tech.bulletsLastLonger const damage = 2.3 * m.dmgScale * tech.wavePacketDamage * tech.waveBeamDamage * (tech.isBulletTeleport ? 1.4 : 1) * (tech.isInfiniteWaveAmmo ? 0.75 : 1) //damage is lower for large radius mobs, since they feel the waves longer for (let i = this.waves.length - 1; i > -1; i--) { const v1 = Vector.add(this.waves[i].position, Vector.mult(this.waves[i].unit1, this.waves[i].radius)) const v2 = Vector.add(this.waves[i].position, Vector.mult(this.waves[i].unit2, this.waves[i].radius)) //draw wave ctx.moveTo(v1.x, v1.y) ctx.arc(this.waves[i].position.x, this.waves[i].position.y, this.waves[i].radius, this.waves[i].angle, this.waves[i].angle + this.waves[i].arc); //using small angle linear approximation of circle arc, this will not work if the arc gets large // https://stackoverflow.com/questions/13652518/efficiently-find-points-inside-a-circle-sector let hits = Matter.Query.ray(mob, v1, v2, 50) //Matter.Query.ray(bodies, startPoint, endPoint, [rayWidth]) for (let j = 0; j < hits.length; j++) { const who = hits[j].body if (!who.isShielded) { who.force.x += 0.01 * (Math.random() - 0.5) * who.mass who.force.y += 0.01 * (Math.random() - 0.5) * who.mass Matter.Body.setVelocity(who, { x: who.velocity.x * 0.95, y: who.velocity.y * 0.95 }); let vertices = who.vertices; const vibe = 50 + who.radius * 0.15 ctx.moveTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); for (let j = 1; j < vertices.length; j++) ctx.lineTo(vertices[j].x + vibe * (Math.random() - 0.5), vertices[j].y + vibe * (Math.random() - 0.5)); ctx.lineTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); who.locatePlayer(); who.damage(damage / Math.sqrt(who.radius)); if (tech.isPhononWave && this.phononWaveCD < m.cycle) { this.phononWaveCD = m.cycle + 8 * (1 + this.waves[i].resonanceCount) const halfArc = 0.27 //6.28 is a full circle, but these arcs needs to stay small because we are using small angle linear approximation, for collisions let closestMob, dist let range = end - 30 * this.waves[i].resonanceCount for (let i = 0, len = mob.length; i < len; i++) { if (who !== mob[i] && !mob[i].isBadTarget && !mob[i].isInvulnerable) { dist = Vector.magnitude(Vector.sub(who.position, mob[i].position)); if (dist < range) { closestMob = mob[i] range = dist } } } if (closestMob) { const dir = Vector.normalise(Vector.sub(closestMob.position, who.position)) var angle = Math.atan2(dir.y, dir.x) } else { var angle = 2 * Math.PI * Math.random() } this.waves.push({ position: who.position, angle: angle - halfArc, //used in drawing ctx.arc unit1: { x: Math.cos(angle - halfArc), y: Math.sin(angle - halfArc) }, //used for collision unit2: { x: Math.cos(angle + halfArc), y: Math.sin(angle + halfArc) }, //used for collision arc: halfArc * 2, radius: 25, resonanceCount: this.waves[i].resonanceCount + 1 }) } } } hits = Matter.Query.ray(body, v1, v2, 50) //Matter.Query.ray(bodies, startPoint, endPoint, [rayWidth]) for (let j = 0, len = Math.min(30, hits.length); j < len; j++) { const who = hits[j].body //make them shake around who.force.x += 0.01 * (Math.random() - 0.5) * who.mass who.force.y += (0.01 * (Math.random() - 0.5) - simulation.g * 0.25) * who.mass //remove force of gravity let vertices = who.vertices; const vibe = 25 ctx.moveTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); for (let j = 1; j < vertices.length; j++) { ctx.lineTo(vertices[j].x + vibe * (Math.random() - 0.5), vertices[j].y + vibe * (Math.random() - 0.5)); } ctx.lineTo(vertices[0].x + vibe * (Math.random() - 0.5), vertices[0].y + vibe * (Math.random() - 0.5)); if (tech.isPhononBlock && !who.isNotHoldable && who.speed < 5 && who.angularSpeed < 0.1) { if (Math.random() < 0.5) b.targetedBlock(who, 50 - Math.min(25, who.mass * 3)) // targetedBlock(who, speed = 50 - Math.min(20, who.mass * 2), range = 1600) { // Matter.Body.setAngularVelocity(who, (0.25 + 0.12 * Math.random()) * (Math.random() < 0.5 ? -1 : 1)); who.torque += who.inertia * 0.001 * (Math.random() - 0.5) } } this.waves[i].radius += tech.waveBeamSpeed * 1.8 //expand / move if (this.waves[i].radius > end - 30 * this.waves[i].resonanceCount) { this.waves.splice(i, 1) //end } } ctx.stroke(); } }, fireLongitudinal() { m.fireCDcycle = m.cycle + Math.floor((m.crouch ? 4 : 8) * b.fireCDscale); // cool down const halfArc = (m.crouch ? 0.0785 : 0.275) * (tech.isBulletTeleport ? 0.66 + (Math.random() - 0.5) : 1) //6.28 is a full circle, but these arcs needs to stay small because we are using small angle linear approximation, for collisions const angle = m.angle + tech.isBulletTeleport * 0.3 * (Math.random() - 0.5) this.waves.push({ position: { x: m.pos.x + 25 * Math.cos(m.angle), y: m.pos.y + 25 * Math.sin(m.angle), }, angle: angle - halfArc, //used in drawing ctx.arc unit1: { x: Math.cos(angle - halfArc), y: Math.sin(angle - halfArc) }, //used for collision unit2: { x: Math.cos(angle + halfArc), y: Math.sin(angle + halfArc) }, //used for collision arc: halfArc * 2, radius: 25, resonanceCount: 0 }) }, doTransverse() { // if (this.wavePacketCycle && !input.fire) { // this.wavePacketCycle = 0; // m.fireCDcycle = m.cycle + Math.floor(this.delay * b.fireCDscale); // cool down // } }, fireTransverse() { totalCycles = Math.floor((3.5) * 35 * tech.waveReflections * tech.bulletsLastLonger / Math.sqrt(tech.waveReflections * 0.5)) const me = bullet.length; bullet[me] = Bodies.polygon(m.pos.x + 25 * Math.cos(m.angle), m.pos.y + 25 * Math.sin(m.angle), 5, 4, { angle: m.angle, cycle: -0.5, endCycle: simulation.cycle + totalCycles, inertia: Infinity, frictionAir: 0, slow: 0, // amplitude: (m.crouch ? 5 : 10) * ((this.wavePacketCycle % 2) ? -1 : 1) * Math.sin((this.wavePacketCycle + 1) * 0.088), //0.0968 //0.1012 //0.11 //0.088 //shorten wave packet amplitude: (m.crouch ? 6 : 12) * ((this.wavePacketCycle % 2) ? -1 : 1) * Math.sin(this.wavePacketCycle * 0.088) * Math.sin(this.wavePacketCycle * 0.04), //0.0968 //0.1012 //0.11 //0.088 //shorten wave packet minDmgSpeed: 0, dmg: m.dmgScale * tech.waveBeamDamage * tech.wavePacketDamage * (tech.isBulletTeleport ? 1.43 : 1) * (tech.isInfiniteWaveAmmo ? 0.75 : 1), //also control damage when you divide by mob.mass dmgCoolDown: 0, classType: "bullet", collisionFilter: { category: 0, mask: 0, //cat.mob | cat.mobBullet | cat.mobShield }, beforeDmg() { }, onEnd() { }, do() { }, query() { let slowCheck = 1 if (Matter.Query.point(map, this.position).length) { //check if inside map slowCheck = waveSpeedMap } else { //check if inside a body let q = Matter.Query.point(body, this.position) if (q.length) { slowCheck = waveSpeedBody Matter.Body.setPosition(this, Vector.add(this.position, q[0].velocity)) //move with the medium } } if (slowCheck !== this.slow) { //toggle velocity based on inside and outside status change this.slow = slowCheck Matter.Body.setVelocity(this, Vector.mult(Vector.normalise(this.velocity), tech.waveBeamSpeed * slowCheck)); } if (this.dmgCoolDown < 1) { q = Matter.Query.point(mob, this.position) // check if inside a mob for (let i = 0; i < q.length; i++) { this.dmgCoolDown = 5 + Math.floor(8 * Math.random() * b.fireCDscale); let dmg = this.dmg q[i].damage(dmg); if (q[i].alive) { q[i].foundPlayer(); Matter.Body.setVelocity(q[i], Vector.mult(q[i].velocity, 0.9)) } // this.endCycle = 0; //bullet ends cycle after doing damage if (q[i].damageReduction) { simulation.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, radius: Math.log(dmg + 1.1) * 40 * q[i].damageReduction + 3, color: 'rgba(0,0,0,0.4)', time: simulation.drawTime }); } } } else { this.dmgCoolDown-- } }, wiggle() { this.cycle++ const where = Vector.mult(transverse, this.amplitude * Math.cos(this.cycle * tech.waveFrequency)) Matter.Body.setPosition(this, Vector.add(this.position, where)) } }); if (tech.isBulletTeleport) { bullet[me].wiggle = function () { this.cycle++ const where = Vector.mult(transverse, this.amplitude * Math.cos(this.cycle * tech.waveFrequency)) if (Math.random() < 0.005) { if (Math.random() < 0.33) { //randomize position const scale = 500 * Math.random() Matter.Body.setPosition(this, Vector.add({ x: scale * (Math.random() - 0.5), y: scale * (Math.random() - 0.5) }, Vector.add(this.position, where))) } else { //randomize position in velocity direction const velocityScale = Vector.mult(this.velocity, 50 * (Math.random() - 0.5)) Matter.Body.setPosition(this, Vector.add(velocityScale, Vector.add(this.position, where))) } } else { Matter.Body.setPosition(this, Vector.add(this.position, where)) } } } let waveSpeedMap = 0.1 let waveSpeedBody = 0.25 if (tech.isPhaseVelocity) { waveSpeedMap = 3.5 waveSpeedBody = 2 bullet[me].dmg *= 1.4 } if (tech.waveReflections) { bullet[me].reflectCycle = totalCycles / tech.waveReflections //tech.waveLengthRange bullet[me].do = function () { this.query() if (this.cycle > this.reflectCycle) { this.reflectCycle += totalCycles / tech.waveReflections Matter.Body.setVelocity(this, Vector.mult(this.velocity, -1)); // if (this.reflectCycle > tech.waveLengthRange * (1 + tech.waveReflections)) this.endCycle = 0; } this.wiggle() } } else { bullet[me].do = function () { this.query() this.wiggle(); } } Composite.add(engine.world, bullet[me]); //add bullet to world Matter.Body.setVelocity(bullet[me], { x: tech.waveBeamSpeed * Math.cos(m.angle), y: tech.waveBeamSpeed * Math.sin(m.angle) }); const transverse = Vector.normalise(Vector.perp(bullet[me].velocity)) this.wavePacketCycle++ }, fire() { } }, { name: "missiles", //6 description: "launch homing missiles that target mobs
missiles explode on contact with mobs", ammo: 0, ammoPack: 5, have: false, fireCycle: 0, do() { }, fire() { const countReduction = Math.pow(0.86, tech.missileCount) // if (m.crouch) { // m.fireCDcycle = m.cycle + tech.missileFireCD * b.fireCDscale / countReduction; // cool down // // for (let i = 0; i < tech.missileCount; i++) { // // b.missile(where, -Math.PI / 2 + 0.2 * (Math.random() - 0.5) * Math.sqrt(tech.missileCount), -2, Math.sqrt(countReduction)) // // bullet[bullet.length - 1].force.x += 0.004 * countReduction * (i - (tech.missileCount - 1) / 2); // // } // if (tech.missileCount > 1) { // for (let i = 0; i < tech.missileCount; i++) { // setTimeout(() => { // const where = { x: m.pos.x, y: m.pos.y - 40 } // b.missile(where, -Math.PI / 2 + 0.2 * (Math.random() - 0.5) * Math.sqrt(tech.missileCount), -2, Math.sqrt(countReduction)) // bullet[bullet.length - 1].force.x += 0.025 * countReduction * (i - (tech.missileCount - 1) / 2); // }, 20 * tech.missileCount * Math.random()); // } // } else { // const where = { // x: m.pos.x, // y: m.pos.y - 40 // } // b.missile(where, -Math.PI / 2 + 0.2 * (Math.random() - 0.5), -2) // } // } else { m.fireCDcycle = m.cycle + tech.missileFireCD * b.fireCDscale / countReduction; // cool down const direction = { x: Math.cos(m.angle), y: Math.sin(m.angle) } // const where = { // x: m.pos.x + 30 * direction.x, // y: m.pos.y + 30 * direction.y // } if (tech.missileCount > 1) { const push = Vector.mult(Vector.perp(direction), 0.2 * countReduction / Math.sqrt(tech.missileCount)) const sqrtCountReduction = Math.sqrt(countReduction) // for (let i = 0; i < tech.missileCount; i++) { // setTimeout(() => { // if (m.crouch) { // b.missile(where, m.angle, 20, sqrtCountReduction) // // bullet[bullet.length - 1].force.x += 0.7 * push.x * (i - (tech.missileCount - 1) / 2); // // bullet[bullet.length - 1].force.y += 0.7 * push.y * (i - (tech.missileCount - 1) / 2); // } else { // b.missile(where, m.angle, -10, sqrtCountReduction) // bullet[bullet.length - 1].force.x += push.x * (i - (tech.missileCount - 1) / 2); // bullet[bullet.length - 1].force.y += 0.005 + push.y * (i - (tech.missileCount - 1) / 2); // } // }, 1 + i * 10 * tech.missileCount); // } const launchDelay = 4 let count = 0 const fireMissile = () => { if (m.crouch) { b.missile({ x: m.pos.x + 30 * direction.x, y: m.pos.y + 30 * direction.y }, m.angle, 20, sqrtCountReduction) bullet[bullet.length - 1].force.x += 0.5 * push.x * (Math.random() - 0.5) bullet[bullet.length - 1].force.y += 0.004 + 0.5 * push.y * (Math.random() - 0.5) } else { b.missile({ x: m.pos.x + 30 * direction.x, y: m.pos.y + 30 * direction.y }, m.angle, -15, sqrtCountReduction) bullet[bullet.length - 1].force.x += push.x * (Math.random() - 0.5) bullet[bullet.length - 1].force.y += 0.005 + push.y * (Math.random() - 0.5) } } const cycle = () => { if ((simulation.paused || m.isBodiesAsleep) && m.alive) { requestAnimationFrame(cycle) } else { count++ if (!(count % launchDelay)) { fireMissile() } if (count < tech.missileCount * launchDelay && m.alive) requestAnimationFrame(cycle); } } requestAnimationFrame(cycle); } else { if (m.crouch) { b.missile({ x: m.pos.x + 40 * direction.x, y: m.pos.y + 40 * direction.y }, m.angle, 25) } else { b.missile({ x: m.pos.x + 40 * direction.x, y: m.pos.y + 40 * direction.y }, m.angle, -12) bullet[bullet.length - 1].force.y += 0.04 * (Math.random() - 0.2) } } } }, { name: "grenades", //5 description: "lob a single bouncy projectile
explodes on contact or after one second", ammo: 0, ammoPack: 7, have: false, do() { }, //do is set in b.setGrenadeMode() fire() { const countReduction = Math.pow(0.93, tech.missileCount) m.fireCDcycle = m.cycle + Math.floor((m.crouch ? 35 : 27) * b.fireCDscale / countReduction); // cool down const where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } const SPREAD = m.crouch ? 0.12 : 0.2 let angle = m.angle - SPREAD * (tech.missileCount - 1) / 2; for (let i = 0; i < tech.missileCount; i++) { b.grenade(where, angle, countReduction) //function(where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }, angle = m.angle, size = 1) angle += SPREAD } }, }, { name: "spores", //6 description: "fire a sporangium that discharges spores
spores seek out nearby mobs", ammo: 0, ammoPack: 2.6, have: false, nameString(suffix = "") { if (tech.isSporeFlea) { return `flea${suffix}` } else if (tech.isSporeWorm) { return `worm${suffix}` } else { return `spore${suffix}` } }, do() { }, fire() { const me = bullet.length; const dir = m.angle; bullet[me] = Bodies.polygon(m.pos.x + 30 * Math.cos(m.angle), m.pos.y + 30 * Math.sin(m.angle), 20, 4.5, b.fireAttributes(dir, false)); b.fireProps(m.crouch ? 40 : 20, m.crouch ? 30 : 16, dir, me); //cd , speed Matter.Body.setDensity(bullet[me], 0.000001); bullet[me].endCycle = simulation.cycle + 480 + Math.max(0, 120 - 2 * bullet.length); bullet[me].frictionAir = 0; bullet[me].friction = 0.5; bullet[me].radius = 4.5; bullet[me].maxRadius = 30; bullet[me].restitution = 0.3; bullet[me].minDmgSpeed = 0; bullet[me].totalSpores = 8 + 2 * tech.isSporeFreeze + 4 * tech.isSporeColony bullet[me].stuck = function () { }; bullet[me].beforeDmg = function () { }; bullet[me].do = function () { function onCollide(that) { that.collisionFilter.mask = 0; //non collide with everything Matter.Body.setVelocity(that, { x: 0, y: 0 }); that.do = that.grow; } const mobCollisions = Matter.Query.collides(this, mob) if (mobCollisions.length) { onCollide(this) this.stuckTo = mobCollisions[0].bodyA if (tech.isZombieMobs) this.stuckTo.isSoonZombie = true if (this.stuckTo.isVerticesChange) { this.stuckToRelativePosition = { x: 0, y: 0 } } else { //find the relative position for when the mob is at angle zero by undoing the mobs rotation this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) } this.stuck = function () { if (this.stuckTo && this.stuckTo.alive) { const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck } else { this.collisionFilter.mask = cat.map; //non collide with everything but map this.stuck = function () { this.force.y += this.mass * 0.0006; } } } } else { const bodyCollisions = Matter.Query.collides(this, body) if (bodyCollisions.length) { if (!bodyCollisions[0].bodyA.isNonStick) { onCollide(this) this.stuckTo = bodyCollisions[0].bodyA //find the relative position for when the mob is at angle zero by undoing the mobs rotation this.stuckToRelativePosition = Vector.rotate(Vector.sub(this.position, this.stuckTo.position), -this.stuckTo.angle) } else { this.do = this.grow; } this.stuck = function () { if (this.stuckTo) { const rotate = Vector.rotate(this.stuckToRelativePosition, this.stuckTo.angle) //add in the mob's new angle to the relative position vector Matter.Body.setPosition(this, Vector.add(Vector.add(rotate, this.stuckTo.velocity), this.stuckTo.position)) // Matter.Body.setVelocity(this, this.stuckTo.velocity); //so that it will move properly if it gets unstuck } else { this.force.y += this.mass * 0.0006; } } } else { if (Matter.Query.collides(this, map).length) { onCollide(this) } else { //if colliding with nothing just fall this.force.y += this.mass * 0.0006; simulation.mouseInGame.x } } } //draw green glow ctx.fillStyle = "rgba(0,200,125,0.16)"; ctx.beginPath(); ctx.arc(this.position.x, this.position.y, this.maxRadius, 0, 2 * Math.PI); ctx.fill(); } bullet[me].grow = function () { this.stuck(); //runs different code based on what the bullet is stuck to let scale = 1.01 if (tech.isSporeGrowth && !(simulation.cycle % 40)) { //release a spore if (tech.isSporeFlea) { if (!(simulation.cycle % 80)) { const speed = 10 + 5 * Math.random() const angle = 2 * Math.PI * Math.random() b.flea(this.position, { x: speed * Math.cos(angle), y: speed * Math.sin(angle) }) } } else if (tech.isSporeWorm) { if (!(simulation.cycle % 80)) b.worm(this.position) } else { b.spore(this.position) } scale = 0.96 if (this.stuckTo && this.stuckTo.alive) scale = 0.9 Matter.Body.scale(this, scale, scale); this.radius *= scale } else { if (this.stuckTo && this.stuckTo.alive) scale = 1.03 Matter.Body.scale(this, scale, scale); this.radius *= scale if (this.radius > this.maxRadius) this.endCycle = 0; } //draw green glow ctx.fillStyle = "rgba(0,200,125,0.16)"; ctx.beginPath(); ctx.arc(this.position.x, this.position.y, this.maxRadius, 0, 2 * Math.PI); ctx.fill(); }; //spawn bullets on end bullet[me].onEnd = function () { let count = 0 //used in for loop below const things = [ () => { //spore b.spore(this.position) }, () => { //worm count++ //count as 2 things b.worm(this.position) }, () => { //flea count++ //count as 2 things const speed = 10 + 5 * Math.random() const angle = 2 * Math.PI * Math.random() b.flea(this.position, { x: speed * Math.cos(angle), y: speed * Math.sin(angle) }) }, () => { // drones b.drone(this.position) }, () => { // ice IX b.iceIX(1, Math.random() * 2 * Math.PI, this.position) }, () => { //missile count++ //count as 2 things b.missile(this.position, -Math.PI / 2 + 0.5 * (Math.random() - 0.5), 0, 1) }, () => { //nail b.targetedNail(this.position, 1, 39 + 6 * Math.random()) }, () => { //super ball const speed = 36 const angle = 2 * Math.PI * Math.random() b.superBall(this.position, { x: speed * Math.cos(angle), y: speed * Math.sin(angle) }, 11 * tech.bulletSize) }, ] for (len = this.totalSpores; count < len; count++) { if (tech.isSporeColony && Math.random() < 0.5) { things[Math.floor(Math.random() * things.length)]() } else if (tech.isSporeFlea) { things[2]() } else if (tech.isSporeWorm) { things[1]() } else { things[0]() //spores } } // } else if (tech.isSporeFlea) { // for (let i = 0, len = this.totalSpores; i < len; i++) things[2]() // } else if (tech.isSporeWorm) { // for (let i = 0, len = this.totalSpores; i < len; i++) things[1]() // } else { // for (let i = 0; i < this.totalSpores; i++) things[0]() // } if (tech.isStun) b.AoEStunEffect(this.position, 600, 270 + 120 * Math.random()); //AoEStunEffect(where, range, cycles = 120 + 60 * Math.random()) { } } }, { name: "drones", //7 description: "deploy autonomous drones that smash into mobs
and collect nearby power ups", //crashes reduce their lifespan by 1 second ammo: 0, ammoPack: 16, defaultAmmoPack: 16, have: false, do() { }, fire() { if (tech.isDroneRadioactive) { if (m.crouch) { b.droneRadioactive({ x: m.pos.x + 30 * Math.cos(m.angle) + 10 * (Math.random() - 0.5), y: m.pos.y + 30 * Math.sin(m.angle) + 10 * (Math.random() - 0.5) }, 45) m.fireCDcycle = m.cycle + Math.floor(45 * b.fireCDscale); // cool down } else { b.droneRadioactive({ x: m.pos.x + 30 * Math.cos(m.angle) + 10 * (Math.random() - 0.5), y: m.pos.y + 30 * Math.sin(m.angle) + 10 * (Math.random() - 0.5) }, 10) m.fireCDcycle = m.cycle + Math.floor(25 * b.fireCDscale); // cool down } } else { if (m.crouch) { b.drone({ x: m.pos.x + 30 * Math.cos(m.angle) + 5 * (Math.random() - 0.5), y: m.pos.y + 30 * Math.sin(m.angle) + 5 * (Math.random() - 0.5) }, 50) m.fireCDcycle = m.cycle + Math.floor(7 * b.fireCDscale); // cool down } else { b.drone({ x: m.pos.x + 30 * Math.cos(m.angle) + 10 * (Math.random() - 0.5), y: m.pos.y + 30 * Math.sin(m.angle) + 10 * (Math.random() - 0.5) }, 15) m.fireCDcycle = m.cycle + Math.floor(4 * b.fireCDscale); // cool down } } } }, { name: "foam", //8 description: "spray bubbly foam that sticks to mobs
slows mobs and does damage over time", ammo: 0, ammoPack: 24, have: false, charge: 0, isDischarge: false, knockBack: 0.0005, //set in tech: cavitation applyKnock(velocity) { player.force.x -= this.knockBack * velocity.x player.force.y -= 2 * this.knockBack * velocity.y }, chooseFireMethod() { if (tech.isFoamPressure) { this.do = this.doCharges this.fire = this.fireCharges } else { this.do = this.doStream this.fire = this.fireStream } }, doStream() { }, fireStream() { const spread = (m.crouch ? 0.04 * (Math.random() - 0.5) + 0.09 * Math.sin(m.cycle * 0.12) : 0.23 * (Math.random() - 0.5) + 0.15 * Math.sin(m.cycle * 0.12) ) const radius = 5 + 8 * Math.random() + (tech.isAmmoFoamSize && this.ammo < 300) * 12 const SPEED = (m.crouch ? 1.2 : 1) * Math.max(2, 14 - radius * 0.25) const dir = m.angle + 0.15 * (Math.random() - 0.5) const velocity = { x: SPEED * Math.cos(dir), y: SPEED * Math.sin(dir) } const position = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } b.foam(position, Vector.rotate(velocity, spread), radius) this.applyKnock(velocity) m.fireCDcycle = m.cycle + Math.floor(1.5 * b.fireCDscale); }, doCharges() { if (this.charge > 0) { //draw charge level ctx.fillStyle = "rgba(0,50,50,0.3)"; ctx.beginPath(); const radius = 5 * Math.sqrt(this.charge) const mag = 11 + radius ctx.arc(m.pos.x + mag * Math.cos(m.angle), m.pos.y + mag * Math.sin(m.angle), radius, 0, 2 * Math.PI); ctx.fill(); if (this.isDischarge && m.cycle % 2) { const spread = (m.crouch ? 0.04 : 0.5) * (Math.random() - 0.5) const radius = 5 + 8 * Math.random() + (tech.isAmmoFoamSize && this.ammo < 300) * 12 const SPEED = (m.crouch ? 1.2 : 1) * 10 - radius * 0.4 + Math.min(5, Math.sqrt(this.charge)); const dir = m.angle + 0.15 * (Math.random() - 0.5) const velocity = { x: SPEED * Math.cos(dir), y: SPEED * Math.sin(dir) } const position = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } b.foam(position, Vector.rotate(velocity, spread), radius) this.applyKnock(velocity) this.charge -= 0.75 m.fireCDcycle = m.cycle + 2; //disable firing and adding more charge until empty } else if (!input.fire) { this.isDischarge = true; } } else { if (this.isDischarge) { m.fireCDcycle = m.cycle + Math.floor(25 * b.fireCDscale); } this.isDischarge = false } }, fireCharges() { const spread = (m.crouch ? 0.04 * (Math.random() - 0.5) + 0.09 * Math.sin(m.cycle * 0.12) : 0.23 * (Math.random() - 0.5) + 0.15 * Math.sin(m.cycle * 0.12) ) const radius = 5 + 8 * Math.random() + (tech.isAmmoFoamSize && this.ammo < 300) * 12 const SPEED = (m.crouch ? 1.2 : 1) * Math.max(2, 14 - radius * 0.25) const dir = m.angle + 0.15 * (Math.random() - 0.5) const velocity = { x: SPEED * Math.cos(dir), y: SPEED * Math.sin(dir) } const position = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } // if (tech.foamFutureFire) { // simulation.drawList.push({ //add dmg to draw queue // x: position.x, // y: position.y, // radius: 5, // color: "rgba(0,50,50,0.3)", // time: 15 * tech.foamFutureFire // }); // setTimeout(() => { // if (!simulation.paused) { // b.foam(position, Vector.rotate(velocity, spread), radius) // bullet[bullet.length - 1].damage *= (1 + 0.7 * tech.foamFutureFire) // } // }, 210 * tech.foamFutureFire); // } else { // } b.foam(position, Vector.rotate(velocity, spread), radius) this.applyKnock(velocity) m.fireCDcycle = m.cycle + Math.floor(1.5 * b.fireCDscale); this.charge += 1 + tech.isCapacitor }, fire() { }, do() { }, }, { name: "harpoon", //9 description: "fire a self-steering harpoon that uses energy
to retract and refund its ammo cost", ammo: 0, ammoPack: 1.7, //update this in railgun tech have: false, fire() { }, do() { }, chooseFireMethod() { if (tech.isRailGun) { this.do = this.railDo this.fire = this.railFire } else if (tech.isGrapple) { this.do = () => { } this.fire = this.grappleFire } else { this.do = () => { } this.fire = this.harpoonFire } }, charge: 0, railDo() { if (this.charge > 0) { const DRAIN = (tech.isRailEnergy ? 0.0002 : 0.002) //exit railgun charging without firing if (m.energy < DRAIN) { // m.energy += 0.025 + this.charge * 22 * this.drain // m.energy -= this.drain m.fireCDcycle = m.cycle + 120; // cool down if out of energy this.endCycle = 0; this.charge = 0 b.refundAmmo() return } //fire if ((!input.fire && this.charge > 0.6)) { // tech.harpoonDensity = 0.0065 //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed const where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } const closest = { distance: 10000, target: null } //push away blocks and mobs const range = 600 + 500 * this.charge for (let i = 0, len = mob.length; i < len; ++i) { //push away mobs when firing if (!mob[i].isUnblockable) { const SUB = Vector.sub(mob[i].position, m.pos) const DISTANCE = Vector.magnitude(SUB) if (DISTANCE < range + mob[i].radius) { const DEPTH = 100 + Math.min(range - DISTANCE + mob[i].radius, 1500) const FORCE = Vector.mult(Vector.normalise(SUB), 0.0015 * Math.sqrt(DEPTH) * mob[i].mass) mob[i].force.x += FORCE.x; mob[i].force.y += FORCE.y; let dmg = m.dmgScale * (mob[i].isDropPowerUp ? 350 : 1100) * tech.harpoonDensity * this.charge simulation.drawList.push({ //add dmg to draw queue x: mob[i].position.x, y: mob[i].position.y, radius: Math.log(dmg + 1.1) * 40 * mob[i].damageReduction + 3, color: 'rgba(100, 0, 200, 0.4)', time: 15 }); mob[i].damage(dmg); } } } for (let i = 0, len = body.length; i < len; ++i) { //push away blocks when firing const SUB = Vector.sub(body[i].position, m.pos) const DISTANCE = Vector.magnitude(SUB) if (DISTANCE < range) { const DEPTH = Math.min(range - DISTANCE, 500) 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 * simulation.g * 1.5; //kick up a bit to give them some arc } } for (let i = 0, len = powerUp.length; i < len; ++i) { //push away blocks when firing const SUB = Vector.sub(powerUp[i].position, m.pos) const DISTANCE = Vector.magnitude(SUB) if (DISTANCE < range) { const DEPTH = Math.min(range - DISTANCE, 500) const FORCE = Vector.mult(Vector.normalise(SUB), 0.002 * Math.sqrt(DEPTH) * powerUp[i].mass) powerUp[i].force.x += FORCE.x; powerUp[i].force.y += FORCE.y - powerUp[i].mass * simulation.g * 1.5; //kick up a bit to give them some arc } } //draw little dots near the edge of range for (let i = 0, len = 10 + 25 * this.charge; i < len; i++) { const unit = Vector.rotate({ x: 1, y: 0 }, 6.28 * Math.random()) const where = Vector.add(m.pos, Vector.mult(unit, range * (0.6 + 0.3 * Math.random()))) simulation.drawList.push({ x: where.x, y: where.y, radius: 5 + 12 * Math.random(), color: "rgba(100, 0, 200, 0.1)", time: Math.floor(5 + 35 * Math.random()) }); } const recoil = Vector.mult(Vector.normalise(Vector.sub(where, m.pos)), m.crouch ? 0.03 : 0.06) player.force.x -= recoil.x player.force.y -= recoil.y // tech.harpoonDensity = 0.0065 //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed const harpoonSize = tech.isLargeHarpoon ? 1 + 0.1 * Math.sqrt(this.ammo) : 1 const thrust = 0.15 * (this.charge) if (tech.extraHarpoons) { let targetCount = 0 const SPREAD = 0.06 + 0.05 * (!m.crouch) let angle = m.angle - SPREAD * tech.extraHarpoons / 2; const dir = { x: Math.cos(angle), y: Math.sin(angle) }; //make a vector for the player's direction of length 1; used in dot product for (let i = 0, len = mob.length; i < len; ++i) { if (mob[i].alive && !mob[i].isBadTarget && !mob[i].shield && Matter.Query.ray(map, m.pos, mob[i].position).length === 0 && !mob[i].isInvulnerable) { const dot = Vector.dot(dir, Vector.normalise(Vector.sub(mob[i].position, m.pos))) //the dot product of diff and dir will return how much over lap between the vectors const dist = Vector.magnitude(Vector.sub(where, mob[i].position)) if (dot > 0.95 - Math.min(dist * 0.00015, 0.3)) { //lower dot product threshold for targeting then if you only have one harpoon //target closest mob that player is looking at and isn't too close to target // if (this.ammo > -1) { // this.ammo-- b.harpoon(where, m.crouch ? null : mob[i], angle, harpoonSize, false, 35, false, thrust) //harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true, thrust = 0.1) { angle += SPREAD targetCount++ if (targetCount > tech.extraHarpoons) break // } } } } //if more harpoons and no targets left if (targetCount < tech.extraHarpoons + 1) { const num = tech.extraHarpoons + 1 - targetCount for (let i = 0; i < num; i++) { b.harpoon(where, null, angle, harpoonSize, false, 35, false, thrust) angle += SPREAD } } simulation.updateGunHUD(); } else { //look for closest mob in player's LoS const dir = { x: Math.cos(m.angle), y: Math.sin(m.angle) }; //make a vector for the player's direction of length 1; used in dot product for (let i = 0, len = mob.length; i < len; ++i) { if (mob[i].alive && !mob[i].isBadTarget && Matter.Query.ray(map, m.pos, mob[i].position).length === 0 && !mob[i].isInvulnerable) { const dot = Vector.dot(dir, Vector.normalise(Vector.sub(mob[i].position, m.pos))) //the dot product of diff and dir will return how much over lap between the vectors const dist = Vector.magnitude(Vector.sub(where, mob[i].position)) if (dist < closest.distance && dot > 0.98 - Math.min(dist * 0.00014, 0.3)) { //target closest mob that player is looking at and isn't too close to target closest.distance = dist closest.target = mob[i] } } } b.harpoon(where, closest.target, m.angle, harpoonSize, false, 35, false, thrust) } this.charge = 0; } else { //charging if (tech.isFireMoveLock) { Matter.Body.setVelocity(player, { x: 0, y: -55 * player.mass * simulation.g //undo gravity before it is added }); player.force.x = 0 player.force.y = 0 } m.fireCDcycle = m.cycle + 10 //can't fire until mouse is released // const previousCharge = this.charge //small b.fireCDscale = faster shots, b.fireCDscale=1 = normal shot, big b.fireCDscale = slower chot // let smoothRate = tech.isCapacitor ? 0.85 : Math.min(0.998, 0.985 * (0.98 + 0.02 * b.fireCDscale)) const rate = Math.sqrt(b.fireCDscale) * tech.railChargeRate * (tech.isCapacitor ? 0.6 : 1) * (m.crouch ? 0.8 : 1) let smoothRate = Math.min(0.998, 0.94 + 0.05 * rate) this.charge = 1 - smoothRate + this.charge * smoothRate if (m.energy > DRAIN) m.energy -= DRAIN //draw magnetic field const X = m.pos.x const Y = m.pos.y const unitVector = { x: Math.cos(m.angle), y: Math.sin(m.angle) } 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)`; const magSize = 8 * this.charge * tech.railChargeRate ** 3 const arcSize = 6 * this.charge * tech.railChargeRate ** 3 for (let i = 3; i < 7; i++) { const MAG = magSize * i * i * (0.93 + 0.07 * Math.random()) const ARC = arcSize * i * i * (0.93 + 0.07 * Math.random()) ctx.beginPath(); magField(MAG, ARC) magField(MAG, -ARC) ctx.fill(); } } } }, railFire() { m.fireCDcycle = m.cycle + 10 //can't fire until mouse is released this.charge += 0.00001 }, grappleFire() { const harpoonSize = (tech.isLargeHarpoon ? 1 + 0.1 * Math.sqrt(this.ammo) : 1) //* (m.crouch ? 0.7 : 1) const where = { x: m.pos.x + harpoonSize * 40 * Math.cos(m.angle), y: m.pos.y + harpoonSize * 40 * Math.sin(m.angle) } const num = Math.min(this.ammo, tech.extraHarpoons + 1) if (!m.crouch && num > 1) { //multiple harpoons const SPREAD = 0.06 let angle = m.angle - SPREAD * num / 2; for (let i = 0; i < num; i++) { if (this.ammo > 0) { this.ammo-- b.grapple(where, angle, true, harpoonSize) angle += SPREAD } } this.ammo++ //make up for the ammo used up in fire() simulation.updateGunHUD(); m.fireCDcycle = m.cycle + Math.floor(75 * b.fireCDscale) // cool down // } else if (m.crouch) { // b.harpoon(where, null, m.angle, harpoonSize, false, 70) } else { if (tech.crouchAmmoCount) tech.crouchAmmoCount = 1 b.grapple(where, m.angle, harpoonSize) } // m.fireCDcycle = m.cycle + Math.floor(75 * b.fireCDscale) // cool down m.fireCDcycle = m.cycle + 5 + 40 * b.fireCDscale + 60 * (m.energy < 0.05) }, harpoonFire() { const where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } const closest = { distance: 10000, target: null } //look for closest mob in player's LoS const harpoonSize = (tech.isLargeHarpoon ? 1 + 0.1 * Math.sqrt(this.ammo) : 1) //* (m.crouch ? 0.7 : 1) const totalCycles = 6.5 * (tech.isFilament ? 1 + 0.013 * Math.min(110, this.ammo) : 1) * Math.sqrt(harpoonSize) if (tech.extraHarpoons && !m.crouch) { //multiple harpoons const SPREAD = 0.2 let angle = m.angle - SPREAD * tech.extraHarpoons / 2; const dir = { x: Math.cos(angle), y: Math.sin(angle) }; //make a vector for the player's direction of length 1; used in dot product const range = 450 * (tech.isFilament ? 1 + 0.012 * Math.min(110, this.ammo) : 1) let targetCount = 0 for (let i = 0, len = mob.length; i < len; ++i) { if (mob[i].alive && !mob[i].isBadTarget && !mob[i].shield && Matter.Query.ray(map, m.pos, mob[i].position).length === 0 && !mob[i].isInvulnerable) { const dot = Vector.dot(dir, Vector.normalise(Vector.sub(mob[i].position, m.pos))) //the dot product of diff and dir will return how much over lap between the vectors const dist = Vector.magnitude(Vector.sub(where, mob[i].position)) if (dist < range && dot > 0.9) { //lower dot product threshold for targeting then if you only have one harpoon //target closest mob that player is looking at and isn't too close to target if (this.ammo > 0) { this.ammo-- b.harpoon(where, mob[i], angle, harpoonSize, true, totalCycles) //Vector.angle(Vector.sub(where, mob[i].position), { x: 0, y: 0 }) angle += SPREAD targetCount++ if (targetCount > tech.extraHarpoons) break } } } } //if more harpoons and no targets left if (targetCount < tech.extraHarpoons + 1) { const num = tech.extraHarpoons - targetCount const delay = 1 //Math.floor(Math.max(4, 8 - 0.5 * tech.extraHarpoons)) let angle = m.angle - SPREAD * tech.extraHarpoons / 2; let count = -1 let harpoonDelay = () => { if (simulation.paused) { requestAnimationFrame(harpoonDelay) } else { count++ if (!(count % delay) && this.ammo > 0) { this.ammo-- b.harpoon({ x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }, null, angle, harpoonSize, true, totalCycles) angle += SPREAD tech.harpoonDensity = 0.004 //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed } if (count < num * delay && m.alive) requestAnimationFrame(harpoonDelay); } } requestAnimationFrame(harpoonDelay) } this.ammo++ //make up for the ammo used up in fire() simulation.updateGunHUD(); } else { //m.crouch makes a single harpoon with longer range const dir = { x: Math.cos(m.angle), y: Math.sin(m.angle) }; //make a vector for the player's direction of length 1; used in dot product for (let i = 0, len = mob.length; i < len; ++i) { if (mob[i].alive && !mob[i].isBadTarget && Matter.Query.ray(map, m.pos, mob[i].position).length === 0 && !mob[i].isInvulnerable) { const dot = Vector.dot(dir, Vector.normalise(Vector.sub(mob[i].position, m.pos))) //the dot product of diff and dir will return how much over lap between the vectors const dist = Vector.magnitude(Vector.sub(where, mob[i].position)) if (dist < closest.distance && dot > 0.98 - Math.min(dist * 0.00014, 0.3)) { //target closest mob that player is looking at and isn't too close to target closest.distance = dist closest.target = mob[i] } } } if (m.crouch && m.onGround) { b.harpoon(where, null, m.angle, harpoonSize, true, 1.6 * totalCycles, (m.crouch && tech.crouchAmmoCount && (tech.crouchAmmoCount - 1) % 2) ? false : true) // harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true) { } else { b.harpoon(where, closest.target, m.angle, harpoonSize, true, totalCycles) } tech.harpoonDensity = 0.004 //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed } m.fireCDcycle = m.cycle + 5 + 35 * b.fireCDscale + 60 * (m.energy < 0.05) + tech.extraHarpoons // cool down is set when harpoon bullet returns to player const recoil = Vector.mult(Vector.normalise(Vector.sub(where, m.pos)), m.crouch ? 0.015 : 0.035) player.force.x -= recoil.x player.force.y -= recoil.y }, }, { name: "mine", //10 description: "toss a proximity mine that sticks to walls
refund undetonated mines on exiting a level", //fires nails at mobs within range ammo: 0, ammoPack: 1.7, have: false, nameString(suffix = "") { if (tech.isFoamMine) { return `foam` } else if (tech.isSuperMine) { return `super ball${suffix}` } else { return `nail${suffix}` } }, do() { if (!input.field && m.crouch && !tech.isLaserMine) { const cycles = 60 //30 const speed = 40 const v = { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) } //m.Vy / 2 + removed to make the path less jerky ctx.strokeStyle = "rgba(68, 68, 68, 0.2)" //color.map ctx.lineWidth = 2 ctx.beginPath() for (let i = 1.5, len = 19; i < len + 1; i++) { const time = cycles * i / len ctx.lineTo(m.pos.x + time * v.x, m.pos.y + time * v.y + 0.34 * time * time) } ctx.stroke() } }, fire() { if (m.crouch) { if (tech.isLaserMine) { const speed = 30 const velocity = { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) } b.laserMine(m.pos, velocity) m.fireCDcycle = m.cycle + Math.floor(65 * b.fireCDscale); // cool down } else { const pos = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } let speed = 36 if (Matter.Query.point(map, pos).length > 0) speed = -2 //don't launch if mine will spawn inside map b.mine(pos, { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) }, 0) m.fireCDcycle = m.cycle + Math.floor(55 * b.fireCDscale); // cool down } } else { const pos = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } let speed = 23 if (Matter.Query.point(map, pos).length > 0) speed = -2 //don't launch if mine will spawn inside map b.mine(pos, { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) }, 0) m.fireCDcycle = m.cycle + Math.floor(35 * b.fireCDscale); // cool down } } }, { name: "laser", //11 description: "emit a beam of collimated coherent light
drains energy instead of ammunition", ammo: 0, ammoPack: Infinity, have: false, charge: 0, isStuckOn: false, angle: 0, isInsideArc(angle) { const mod = (a, n) => { return a - Math.floor(a / n) * n } let diff = mod(angle - this.angle + Math.PI, 2 * Math.PI) - Math.PI return Math.abs(diff) < this.arcRange }, arcRange: 0.78, //1.57, lensDamage: 1, lensDamageOn: 0, //set in tech lens() { this.stuckOn(); this.angle += 0.02 if (this.isInsideArc(m.angle)) { this.lensDamage = this.lensDamageOn ctx.lineWidth = 6 + this.lensDamageOn } else { this.lensDamage = 1 ctx.lineWidth = 2 } ctx.beginPath(); ctx.arc(m.pos.x, m.pos.y, 60, this.angle - this.arcRange, this.angle + this.arcRange); ctx.strokeStyle = '#fff' //'rgba(255,255,255,0.9)' //'hsl(189, 100%, 95%)' ctx.stroke(); // const a = { x: radius * Math.cos(this.angle + this.arcRange), y: radius * Math.sin(this.angle + this.arcRange) } // const b = Vector.add(m.pos, a) // ctx.lineTo(b.x, b.y) // ctx.fillStyle = '#fff' // ctx.fill() }, stuckOn() { if (tech.isStuckOn) { if (this.isStuckOn) { if (!input.fire) this.fire(); if (m.energy < tech.laserDrain) this.isStuckOn = false } else if (input.fire) { this.isStuckOn = true } } }, do() { }, fire() { }, chooseFireMethod() { this.lensDamage = 1 if (tech.isLaserLens) { this.do = this.lens } else { this.do = this.stuckOn } if (tech.isPulseLaser) { this.fire = () => { const drain = Math.min(0.9 * m.maxEnergy, 0.01 * (tech.isCapacitor ? 10 : 1) / b.fireCDscale) if (m.energy > drain && this.charge < 50 * m.maxEnergy) { m.energy -= drain this.charge += drain * 100 } } if (tech.historyLaser) { const len = 1 + tech.historyLaser const spacing = Math.ceil(30 - 2 * tech.historyLaser) this.do = () => { if (tech.isLaserLens) this.lens() if (this.charge > 0) { //draw charge level const mag = 4.1 * Math.sqrt(this.charge) ctx.beginPath(); for (let i = 0; i < len; i++) { const history = m.history[(m.cycle - i * spacing) % 600] const off = history.yOff - 24.2859 ctx.moveTo(history.position.x, history.position.y - off); ctx.ellipse(history.position.x, history.position.y - off, mag, mag * 0.65, history.angle, 0, 2 * Math.PI) } ctx.fillStyle = `rgba(255,0,0,${0.09 * Math.sqrt(this.charge)})`; ctx.fill(); //fire if (!input.fire) { if (this.charge > 5) { m.fireCDcycle = m.cycle + Math.floor(35 * b.fireCDscale); // cool down for (let i = 0; i < len; i++) { const history = m.history[(m.cycle - i * spacing) % 600] const off = history.yOff - 24.2859 b.pulse(1.65 * this.charge * this.lensDamage, history.angle, { x: history.position.x, y: history.position.y - off }) } } this.charge = 0; } } }; } else { this.do = () => { if (tech.isLaserLens) this.lens() if (this.charge > 0) { //draw charge level ctx.beginPath(); ctx.arc(m.pos.x, m.pos.y, 4.2 * Math.sqrt(this.charge), 0, 2 * Math.PI); // ctx.fillStyle = `rgba(255,0,0,${0.09 * Math.sqrt(this.charge)})`; ctx.fillStyle = `rgba(255,0,0,${0.09 * Math.sqrt(this.charge)})`; ctx.fill(); //fire if (!input.fire) { if (this.charge > 5) { m.fireCDcycle = m.cycle + Math.floor(35 * b.fireCDscale); // cool down if (tech.beamSplitter) { const divergence = m.crouch ? 0.15 : 0.35 const angle = m.angle - tech.beamSplitter * divergence / 2 for (let i = 0; i < 1 + tech.beamSplitter; i++) b.pulse(this.charge, angle + i * divergence) } else { b.pulse(1.8 * this.charge * this.lensDamage, m.angle) } } this.charge = 0; } } }; } } else if (tech.beamSplitter) { this.fire = this.fireSplit } else if (tech.historyLaser) { this.fire = this.fireHistory } else if (tech.isWideLaser) { this.fire = this.fireWideBeam } else { this.fire = this.fireLaser } // this.fire = this.firePhoton }, fireLaser() { const drain = 0.001 + tech.laserDrain / b.fireCDscale if (m.energy < drain) { m.fireCDcycle = m.cycle + 100; // cool down if out of energy } else { m.fireCDcycle = m.cycle m.energy -= drain const where = { x: m.pos.x + 20 * Math.cos(m.angle), y: m.pos.y + 20 * Math.sin(m.angle) } b.laser(where, { x: where.x + 3000 * Math.cos(m.angle), y: where.y + 3000 * Math.sin(m.angle) }, tech.laserDamage / b.fireCDscale * this.lensDamage); } }, firePulse() { }, fireSplit() { const drain = 0.001 + tech.laserDrain / b.fireCDscale if (m.energy < drain) { m.fireCDcycle = m.cycle + 100; // cool down if out of energy } else { m.fireCDcycle = m.cycle m.energy -= drain // const divergence = m.crouch ? 0.15 : 0.2 // const scale = Math.pow(0.9, tech.beamSplitter) // const pushScale = scale * scale let dmg = tech.laserDamage / b.fireCDscale * this.lensDamage // * scale //Math.pow(0.9, tech.laserDamage) const where = { x: m.pos.x + 20 * Math.cos(m.angle), y: m.pos.y + 20 * Math.sin(m.angle) } const divergence = m.crouch ? 0.15 : 0.35 const angle = m.angle - tech.beamSplitter * divergence / 2 for (let i = 0; i < 1 + tech.beamSplitter; i++) { b.laser(where, { x: where.x + 3000 * Math.cos(angle + i * divergence), y: where.y + 3000 * Math.sin(angle + i * divergence) }, dmg, tech.laserReflections, false) } } }, fireWideBeam() { const drain = 0.001 + tech.laserDrain / b.fireCDscale if (m.energy < drain) { m.fireCDcycle = m.cycle + 100; // cool down if out of energy } else { m.fireCDcycle = m.cycle m.energy -= drain const range = { x: 5000 * Math.cos(m.angle), y: 5000 * Math.sin(m.angle) } const rangeOffPlus = { x: 7.5 * Math.cos(m.angle + Math.PI / 2), y: 7.5 * Math.sin(m.angle + Math.PI / 2) } const rangeOffMinus = { x: 7.5 * Math.cos(m.angle - Math.PI / 2), y: 7.5 * Math.sin(m.angle - Math.PI / 2) } const dmg = 0.70 * tech.laserDamage / b.fireCDscale * this.lensDamage // 3.5 * 0.55 = 200% more damage const where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } const eye = { x: m.pos.x + 15 * Math.cos(m.angle), y: m.pos.y + 15 * Math.sin(m.angle) } ctx.strokeStyle = tech.laserColor; ctx.lineWidth = 8 ctx.globalAlpha = 0.5; ctx.beginPath(); if (Matter.Query.ray(map, eye, where).length === 0 && Matter.Query.ray(body, eye, where).length === 0) { b.laser(eye, { x: eye.x + range.x, y: eye.y + range.y }, dmg, 0, true, 0.3) } for (let i = 1; i < tech.wideLaser; i++) { let whereOff = Vector.add(where, { x: i * rangeOffPlus.x, y: i * rangeOffPlus.y }) if (Matter.Query.ray(map, eye, whereOff).length === 0 && Matter.Query.ray(body, eye, whereOff).length === 0) { ctx.moveTo(eye.x, eye.y) ctx.lineTo(whereOff.x, whereOff.y) b.laser(whereOff, { x: whereOff.x + range.x, y: whereOff.y + range.y }, dmg, 0, true, 0.3) } whereOff = Vector.add(where, { x: i * rangeOffMinus.x, y: i * rangeOffMinus.y }) if (Matter.Query.ray(map, eye, whereOff).length === 0 && Matter.Query.ray(body, eye, whereOff).length === 0) { ctx.moveTo(eye.x, eye.y) ctx.lineTo(whereOff.x, whereOff.y) b.laser(whereOff, { x: whereOff.x + range.x, y: whereOff.y + range.y }, dmg, 0, true, 0.3) } } ctx.stroke(); if (tech.isLaserLens && b.guns[11].lensDamage !== 1) { ctx.lineWidth = 20 + 3 * b.guns[11].lensDamageOn ctx.globalAlpha = 0.3 ctx.stroke(); } ctx.globalAlpha = 1; } }, fireHistory() { drain = 0.001 + tech.laserDrain / b.fireCDscale if (m.energy < drain) { m.fireCDcycle = m.cycle + 100; // cool down if out of energy } else { m.fireCDcycle = m.cycle m.energy -= drain const dmg = 0.5 * tech.laserDamage / b.fireCDscale * this.lensDamage // 3.5 * 0.55 = 200% more damage const spacing = Math.ceil(10 - 0.4 * tech.historyLaser) ctx.beginPath(); b.laser({ x: m.pos.x + 20 * Math.cos(m.angle), y: m.pos.y + 20 * Math.sin(m.angle) }, { x: m.pos.x + 3000 * Math.cos(m.angle), y: m.pos.y + 3000 * Math.sin(m.angle) }, dmg, 0, true, 0.2); for (let i = 1, len = 3 + tech.historyLaser * 3; i < len; i++) { const history = m.history[(m.cycle - i * spacing) % 600] const off = history.yOff - 24.2859 b.laser({ x: history.position.x + 20 * Math.cos(history.angle), y: history.position.y + 20 * Math.sin(history.angle) - off }, { x: history.position.x + 3000 * Math.cos(history.angle), y: history.position.y + 3000 * Math.sin(history.angle) - off }, dmg, 0, true, 0.2); } ctx.strokeStyle = tech.laserColor; ctx.lineWidth = 1 ctx.stroke(); if (tech.isLaserLens && b.guns[11].lensDamage !== 1) { ctx.strokeStyle = tech.laserColor; ctx.lineWidth = 10 + 2 * b.guns[11].lensDamageOn ctx.globalAlpha = 0.2 ctx.stroke(); //glow ctx.globalAlpha = 1; } } }, }, ], };