From 77cafe3cf047e6ffd9155ce87d4705f280d849c7 Mon Sep 17 00:00:00 2001 From: landgreen Date: Wed, 2 Aug 2023 13:25:44 -0700 Subject: [PATCH] additive manufacturing community map ace by Richard0820 field tech: additive manufacturing - crouch while activating your field to print a throwable block blocks 80% faster and 80% more dense/more damage molecular assembler, pilot wave inflation: 85->90% defense, 300->200% larger blocks buckling: can spawn boosts or coupling in addition to heals, ammo, and research --- img/additive manufacturing.webp | Bin 0 -> 49934 bytes js/engine.js | 10 +- js/level.js | 1279 ++++++++++++++++++++++++++++++- js/player.js | 94 ++- js/spawn.js | 2 +- js/tech.js | 94 ++- todo.txt | 31 +- 7 files changed, 1429 insertions(+), 81 deletions(-) create mode 100644 img/additive manufacturing.webp diff --git a/img/additive manufacturing.webp b/img/additive manufacturing.webp new file mode 100644 index 0000000000000000000000000000000000000000..3a7b15a8da0a14d3a7df05754f537f3e3e557b59 GIT binary patch literal 49934 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 m.cycle) { - let type = tech.isEnergyNoAmmo ? "heal" : "ammo" - if (Math.random() < 0.4) { - type = "heal" - } else if (Math.random() < 0.4 && !tech.isSuperDeterminism) { - type = "research" - } - powerUps.spawn(mob[k].position.x, mob[k].position.y, type); + options = ["coupling", "boost", "heal", "research"] + if (!tech.isEnergyNoAmmo) options.push("ammo") + powerUps.spawn(mob[k].position.x, mob[k].position.y, options[Math.floor(Math.random() * options.length)]); } const stunTime = dmg / Math.sqrt(obj.mass) diff --git a/js/level.js b/js/level.js index 9b6f7b4..264868f 100644 --- a/js/level.js +++ b/js/level.js @@ -10,7 +10,7 @@ const level = { // playableLevels: ["pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion"], //see level.populateLevels: (intro, ... , reservoir or factory, reactor, ... , gauntlet, final) added later playableLevels: ["labs", "rooftops", "skyscrapers", "warehouse", "highrise", "office", "aerie", "satellite", "sewers", "testChamber", "pavilion", "lock"], - communityLevels: ["gauntlet", "stronghold", "basement", "crossfire", "vats", "run", "ngon", "house", "perplex", "coliseum", "tunnel", "islands", "temple", "dripp", "biohazard", "stereoMadness", "yingYang", "staircase", "fortress", "commandeer", "clock", "buttonbutton", "downpour", "superNgonBros", "underpass", "cantilever", "dojo", "tlinat", "ruins"], + communityLevels: ["gauntlet", "stronghold", "basement", "crossfire", "vats", "run", "ngon", "house", "perplex", "coliseum", "tunnel", "islands", "temple", "dripp", "biohazard", "stereoMadness", "yingYang", "staircase", "fortress", "commandeer", "clock", "buttonbutton", "downpour", "superNgonBros", "underpass", "cantilever", "dojo", "tlinat", "ruins", "ace"], trainingLevels: ["walk", "crouch", "jump", "hold", "throw", "throwAt", "deflect", "heal", "fire", "nailGun", "shotGun", "superBall", "matterWave", "missile", "stack", "mine", "grenades", "harpoon", "diamagnetism"], levels: [], start() { @@ -18,7 +18,7 @@ const level = { // simulation.enableConstructMode() //tech.giveTech('motion sickness') //used to build maps in testing mode // simulation.isHorizontalFlipped = true // tech.giveTech("performance") - // level.difficultyIncrease(1 * 4) //30 is near max on hard //60 is near max on why + // level.difficultyIncrease(3 * 4) //30 is near max on hard //60 is near max on why // spawn.setSpawnList(); // spawn.setSpawnList(); // m.maxHealth = m.health = 100 @@ -27,7 +27,7 @@ const level = { // m.immuneCycle = Infinity //you can't take damage // tech.tech[297].frequency = 100 // m.couplingChange(10) - // m.setField("time dilation") //1 standing wave 2 perfect diamagnetism 3 negative mass 4 molecular assembler 5 plasma torch 6 time dilation 7 metamaterial cloaking 8 pilot wave 9 wormhole + // m.setField("molecular assembler") //1 standing wave 2 perfect diamagnetism 3 negative mass 4 molecular assembler 5 plasma torch 6 time dilation 7 metamaterial cloaking 8 pilot wave 9 wormhole // m.energy = 0 // simulation.molecularMode = 2 // m.damage(0.1); @@ -35,7 +35,7 @@ const level = { // b.giveGuns("drones") //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 // b.guns[3].ammo = 100000000 // tech.giveTech("von Neumann probe") - // for (let i = 0; i < 1; ++i) tech.giveTech("mass production") + // for (let i = 0; i < 1; ++i) tech.giveTech("additive manufacturing") // for (let i = 0; i < 2; ++i) tech.giveTech("sound-bot") // for (let i = 0; i < 1; ++i) tech.giveTech("foam-bot") // for (let i = 0; i < 1; ++i) tech.giveTech("nail-bot") @@ -48,7 +48,7 @@ const level = { // for (let i = 0; i < 3; i++) powerUps.directSpawn(450, -50, "tech"); // for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "research"); // for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "coupling"); - // level.diamagnetism(); + // level.ace(); // for (let i = 0; i < 1; ++i) spawn.slasher(1900, -500) // for (let i = 0; i < 1; ++i) spawn.slasher2(1900, -500) // for (let i = 0; i < 1; ++i) spawn.shooterBoss(1900, -2500) @@ -27352,6 +27352,1275 @@ const level = { spawn.mapRect(-100, 0, 1000, 100); powerUps.addResearchToLevel() //needs to run after mobs are spawned }, + ace() { //join us at discord.gg/Q8gY4WeUcm + simulation.makeTextLog(`ace by Richard0820`); + let isDestroyed = false; + const ace = { + spawnOrbitals(who, radius, chance = Math.min(0.25 + simulation.difficulty * 0.005)) { + if (Math.random() < chance) { + // simulation.difficulty = 50 + const len = Math.floor(Math.min(15, 3 + Math.sqrt(simulation.difficulty))) // simulation.difficulty = 40 on hard mode level 10 + const speed = (0.003 + 0.004 * Math.random() + 0.002 * Math.sqrt(simulation.difficulty)) * ((Math.random() < 0.5) ? 1 : -1) + const offSet = 6.28 * Math.random() + for (let i = 0; i < len; i++) ace.orbital(who, radius, i / len * 2 * Math.PI + offSet, speed) + } + }, + orbital(who, radius, phase, speed) { + // for (let i = 0, len = 7; i < len; i++) spawn.orbital(me, radius + 250, 2 * Math.PI / len * i) + mobs.spawn(who.position.x, who.position.y, 8, 12, "rgb(0,0,0)"); + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + Matter.Body.setDensity(me, 0.01); //normal is 0.001 + me.leaveBody = false; + me.isDropPowerUp = false; + me.isBadTarget = true; + me.isUnstable = true; //dies when blocked + me.showHealthBar = false; + me.isOrbital = true; + // me.isShielded = true + me.collisionFilter.category = cat.mobBullet; + me.collisionFilter.mask = cat.bullet; //cat.player | cat.map | cat.body + me.do = function () { + //if host is gone + if (!who || !who.alive) { + this.death(); + return + } + //set orbit + const time = simulation.cycle * speed + phase + const orbit = { + x: Math.cos(time), + y: Math.sin(time) + } + Matter.Body.setPosition(this, Vector.add(Vector.add(who.position, who.velocity), Vector.mult(orbit, radius))) + //damage player + if (Matter.Query.collides(this, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles; //player is immune to damage for 30 cycles + const dmg = 0.03 * simulation.dmgScale + m.damage(dmg); + simulation.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: Math.sqrt(dmg) * 200, + color: simulation.mobDmgColor, + time: simulation.drawTime + }); + this.death(); + } + }; + }, + shield(target, x, y, chance = Math.min(0.02 + simulation.difficulty * 0.005, 0.2) + tech.duplicationChance(), isExtraShield = false) { + if (this.allowShields && Math.random() < chance) { + mobs.spawn(x, y, 9, target.radius + 30, "rgba(255,255,255,0.9)"); + let me = mob[mob.length - 1]; + me.stroke = "rgb(0,0,0)"; + Matter.Body.setDensity(me, 0.00001) //very low density to not mess with the original mob's motion + me.shield = true; + me.damageReduction = 0.05 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1) + me.isUnblockable = true + me.isExtraShield = isExtraShield //this prevents spamming with tech.isShieldAmmo + me.collisionFilter.category = cat.mobShield + me.collisionFilter.mask = cat.bullet; + consBB[consBB.length] = Constraint.create({ + bodyA: me, + bodyB: target, //attach shield to target + stiffness: 0.4, + damping: 0.1 + }); + Composite.add(engine.world, consBB[consBB.length - 1]); + + me.onDamage = function () { + //make sure the mob that owns the shield can tell when damage is done + this.alertNearByMobs(); + this.fill = `rgba(255,255,255,${0.3 + 0.6 * this.health})` + }; + me.leaveBody = false; + me.isDropPowerUp = false; + me.showHealthBar = false; + + me.shieldTargetID = target.id + target.isShielded = true; + target.shieldID = me.id + me.onDeath = function () { + //clear isShielded status from target + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].id === this.shieldTargetID) mob[i].isShielded = false; + } + }; + me.do = function () { + this.checkStatus(); + }; + + mob.unshift(me); //move shield to the front of the array, so that mob is behind shield graphically + + //swap order of shield and mob, so that mob is behind shield graphically + // mob[mob.length - 1] = mob[mob.length - 2]; + // mob[mob.length - 2] = me; + } + }, + groupShield(targets, x, y, radius, stiffness = 0.4) { + const nodes = targets.length + mobs.spawn(x, y, 9, radius, "rgba(255,255,255,0.9)"); + let me = mob[mob.length - 1]; + me.stroke = "rgb(0,0,0)"; + Matter.Body.setDensity(me, 0.00001) //very low density to not mess with the original mob's motion + me.frictionAir = 0; + me.shield = true; + me.damageReduction = 0.075 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1) + me.collisionFilter.category = cat.mobShield + me.collisionFilter.mask = cat.bullet; + for (let i = 0; i < nodes; ++i) { + mob[mob.length - i - 2].isShielded = true; + //constrain to all mob nodes in group + consBB[consBB.length] = Constraint.create({ + bodyA: me, + bodyB: mob[mob.length - i - 2], + stiffness: stiffness, + damping: 0.1 + }); + Composite.add(engine.world, consBB[consBB.length - 1]); + } + me.onDamage = function () { + this.alertNearByMobs(); //makes sure the mob that owns the shield can tell when damage is done + this.fill = `rgba(255,255,255,${0.3 + 0.6 * this.health})` + }; + me.onDeath = function () { + //clear isShielded status from target + for (let j = 0; j < targets.length; j++) { + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].id === targets[j]) mob[i].isShielded = false; + } + } + }; + me.leaveBody = false; + me.isDropPowerUp = false; + me.showHealthBar = false; + mob[mob.length - 1] = mob[mob.length - 1 - nodes]; + mob[mob.length - 1 - nodes] = me; + me.do = function () { + this.checkStatus(); + }; + }, + slasher2(x, y, radius = 33 + Math.ceil(Math.random() * 30)) { + mobs.spawn(x, y, 6, radius, "rgb(0,0,0)"); + let me = mob[mob.length - 1]; + Matter.Body.rotate(me, 2 * Math.PI * Math.random()); + me.accelMag = 0.0009 * simulation.accelScale; + me.torqueMagnitude = 0.000012 * me.inertia //* (Math.random() > 0.5 ? -1 : 1); + me.frictionStatic = 0; + me.friction = 0; + me.frictionAir = 0.035; + me.delay = 140 * simulation.CDScale; + me.cd = 0; + me.swordRadius = 0; + me.swordVertex = 1 + me.swordRadiusMax = 275 + 3.5 * simulation.difficulty; + me.swordRadiusGrowRate = me.swordRadiusMax * (0.011 + 0.0002 * simulation.difficulty) + me.isSlashing = false; + me.swordDamage = 0.03 * simulation.dmgScale + me.laserAngle = 3 * Math.PI / 5 + const seeDistance2 = 200000 + ace.shield(me, x, y); + me.onDamage = function () { }; + me.do = function () { + this.checkStatus(); + this.seePlayerByHistory(15); + this.attraction(); + this.sword() //does various things depending on what stage of the sword swing + }; + me.swordWaiting = function () { + if ( + this.seePlayer.recall && + this.cd < simulation.cycle && + this.distanceToPlayer2() < seeDistance2 && + Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0 && + Matter.Query.ray(body, this.position, this.playerPosRandomY()).length === 0 + ) { + this.laserAngle = -Math.PI / 6 + this.sword = this.swordGrow + this.accelMag = 0 + } + } + me.sword = me.swordWaiting //base function that changes during different aspects of the sword swing + me.swordGrow = function () { + this.laserSword(this.vertices[0], this.angle + this.laserAngle); + this.laserSword(this.vertices[1], this.angle + this.laserAngle + (Math.PI / 3)); + this.laserSword(this.vertices[2], this.angle + this.laserAngle + (Math.PI * 2 / 3)); + this.laserSword(this.vertices[3], this.angle + this.laserAngle + Math.PI); + this.laserSword(this.vertices[4], this.angle + this.laserAngle + (Math.PI * 4 / 3)); + this.laserSword(this.vertices[5], this.angle + this.laserAngle + (Math.PI * 5 / 3)); + this.swordRadius += this.swordRadiusGrowRate + if (this.swordRadius > this.swordRadiusMax || this.isStunned) { + this.sword = this.swordSlash + this.spinCount = 0 + } + } + me.swordSlash = function () { + this.laserSword(this.vertices[0], this.angle + this.laserAngle); + this.laserSword(this.vertices[1], this.angle + this.laserAngle + (Math.PI / 3)); + this.laserSword(this.vertices[2], this.angle + this.laserAngle + (Math.PI * 2 / 3)); + this.laserSword(this.vertices[3], this.angle + this.laserAngle + Math.PI); + this.laserSword(this.vertices[4], this.angle + this.laserAngle + (Math.PI * 4 / 3)); + this.laserSword(this.vertices[5], this.angle + this.laserAngle + (Math.PI * 5 / 3)); + + this.torque += this.torqueMagnitude; + this.spinCount++ + if (this.spinCount > 100 || this.isStunned) { + this.sword = this.swordWaiting + this.swordRadius = 0 + this.accelMag = 0.001 * simulation.accelScale; + this.cd = simulation.cycle + this.delay; + } + } + me.laserSword = function (where, angle) { + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let v = domain[i].vertices; + const len = v.length - 1; + for (let j = 0; j < len; j++) { + results = simulation.checkLineIntersection(v1, v1End, v[j], v[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: v[j], v2: v[j + 1] }; + } + } + results = simulation.checkLineIntersection(v1, v1End, v[0], v[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: v[0], v2: v[len] }; + } + } + }; + best = { x: null, y: null, dist2: Infinity, who: null, v1: null, v2: null }; + const look = { x: where.x + this.swordRadius * Math.cos(angle), y: where.y + this.swordRadius * Math.sin(angle) }; + vertexCollision(where, look, body); // vertexCollision(where, look, mob); + vertexCollision(where, look, map); + if (!m.isCloak) vertexCollision(where, look, [playerBody, playerHead]); + if (best.who && (best.who === playerBody || best.who === playerHead) && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles + 60; //player is immune to damage for an extra second + m.damage(this.swordDamage); + simulation.drawList.push({ //add dmg to draw queue + x: best.x, + y: best.y, + radius: this.swordDamage * 1500, + color: "rgba(80,0,255,0.5)", + time: 20 + }); + } + if (best.dist2 === Infinity) best = look; + ctx.beginPath(); //draw beam + ctx.moveTo(where.x, where.y); + ctx.lineTo(best.x, best.y); + ctx.strokeStyle = "rgba(0,0,0,0.1)"; // 0 path + ctx.lineWidth = 15; + ctx.stroke(); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; // 0 path + ctx.lineWidth = 4; + ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([]); + } + }, + slasher3(x, y, radius = 33 + Math.ceil(Math.random() * 30)) { + const sides = 6 + mobs.spawn(x, y, sides, radius, "rgb(0,0,0)"); + let me = mob[mob.length - 1]; + Matter.Body.rotate(me, 2 * Math.PI * Math.random()); + me.accelMag = 0.0005 * simulation.accelScale; + me.frictionStatic = 0; + me.friction = 0; + me.frictionAir = 0.02; + me.delay = 150 * simulation.CDScale; + me.cd = 0; + me.cycle = 0; + me.swordVertex = 1 + me.swordRadiusInitial = radius / 2; + me.swordRadius = me.swordRadiusInitial; + me.swordRadiusMax = 750 + 6 * simulation.difficulty; + me.swordRadiusGrowRateInitial = 1.08 + me.swordRadiusGrowRate = me.swordRadiusGrowRateInitial//me.swordRadiusMax * (0.009 + 0.0002 * simulation.difficulty) + me.isSlashing = false; + me.swordDamage = 0.04 * simulation.dmgScale + me.laserAngle = 3 * Math.PI / 5 + const seeDistance2 = me.swordRadiusMax * me.swordRadiusMax + ace.shield(me, x, y); + me.onDamage = function () { }; + me.do = function () { + this.checkStatus(); + this.seePlayerByHistory(15); + this.sword() //does various things depending on what stage of the sword swing + }; + me.swordWaiting = function () { + this.attraction(); + if ( + this.seePlayer.recall && + this.cd < simulation.cycle && + this.distanceToPlayer2() < seeDistance2 && + Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0 && + Matter.Query.ray(body, this.position, this.playerPosRandomY()).length === 0 + ) { + //find vertex closest to the player + let dist = Infinity + for (let i = 0, len = this.vertices.length; i < len; i++) { + const D = Vector.magnitudeSquared(Vector.sub({ x: this.vertices[i].x, y: this.vertices[i].y }, m.pos)) + if (D < dist) { + dist = D + this.swordVertex = i + } + } + this.laserAngle = this.swordVertex / sides * 2 * Math.PI + Math.PI / sides + this.sword = this.swordGrow + this.cycle = 0 + this.swordRadius = this.swordRadiusInitial + //slow velocity but don't stop + Matter.Body.setVelocity(this, Vector.mult(this.velocity, 0.5)) + //set angular velocity to 50% + // Matter.Body.setAngularVelocity(this, this.angularVelocity * 0.5) + //gently rotate towards the player with a torque, use cross product to decided clockwise or counterclockwise + const laserStartVector = Vector.sub(this.position, this.vertices[this.swordVertex]) + const playerVector = Vector.sub(this.position, m.pos) + const cross = Matter.Vector.cross(laserStartVector, playerVector) + this.torque = 0.00002 * this.inertia * (cross > 0 ? 1 : -1) + } + } + me.sword = me.swordWaiting //base function that changes during different aspects of the sword swing + me.swordGrow = function () { + const angle = this.angle + this.laserAngle; + const end = { + x: this.vertices[this.swordVertex].x + this.swordRadiusMax * Math.cos(angle), + y: this.vertices[this.swordVertex].y + this.swordRadiusMax * Math.sin(angle) + }; + + const dx = end.x - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].x; + const dy = end.y - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].y; + const angle1 = Math.atan2(dy, dx) * (180 / Math.PI); + + const dx1 = end.x - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].x; + const dy1 = end.y - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].y; + const angle2 = Math.atan2(dy1, dx1) * (180 / Math.PI); + + this.laserSpear(this.vertices[this.swordVertex], this.angle + this.laserAngle); + this.laserSpear(this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1], angle1 * (Math.PI / 180)) + this.laserSpear(this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1], angle2 * (Math.PI / 180)) + + Matter.Body.setVelocity(this, Vector.mult(this.velocity, 0.9)) + // this.swordRadius += this.swordRadiusGrowRate + this.cycle++ + // this.swordRadius = this.swordRadiusMax * Math.sin(this.cycle * 0.03) + this.swordRadius *= this.swordRadiusGrowRate + + if (this.swordRadius > this.swordRadiusMax) this.swordRadiusGrowRate = 1 / this.swordRadiusGrowRateInitial + // if (this.swordRadius > this.swordRadiusMax) this.swordRadiusGrowRate = -Math.abs(this.swordRadiusGrowRate) + if (this.swordRadius < this.swordRadiusInitial || this.isStunned) { + // this.swordRadiusGrowRate = Math.abs(this.swordRadiusGrowRate) + this.swordRadiusGrowRate = this.swordRadiusGrowRateInitial + this.sword = this.swordWaiting + this.swordRadius = 0 + this.cd = simulation.cycle + this.delay; + } + } + me.laserSpear = function (where, angle) { + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let v = domain[i].vertices; + const len = v.length - 1; + for (let j = 0; j < len; j++) { + results = simulation.checkLineIntersection(v1, v1End, v[j], v[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: v[j], v2: v[j + 1] }; + } + } + results = simulation.checkLineIntersection(v1, v1End, v[0], v[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: v[0], v2: v[len] }; + } + } + }; + best = { x: null, y: null, dist2: Infinity, who: null, v1: null, v2: null }; + const look = { x: where.x + this.swordRadius * Math.cos(angle), y: where.y + this.swordRadius * Math.sin(angle) }; + vertexCollision(where, look, body); // vertexCollision(where, look, mob); + vertexCollision(where, look, map); + if (!m.isCloak) vertexCollision(where, look, [playerBody, playerHead]); + if (best.who && (best.who === playerBody || best.who === playerHead)) { + this.swordRadiusGrowRate = 1 / this.swordRadiusGrowRateInitial //!!!! this retracts the sword if it hits the player + + if (m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles + 60; //player is immune to damage for an extra second + m.damage(this.swordDamage); + simulation.drawList.push({ //add dmg to draw queue + x: best.x, + y: best.y, + radius: this.swordDamage * 1500, + color: "rgba(80,0,255,0.5)", + time: 20 + }); + } + } + if (best.dist2 === Infinity) best = look; + ctx.beginPath(); //draw beam + ctx.moveTo(where.x, where.y); + ctx.lineTo(best.x, best.y); + ctx.strokeStyle = "rgba(0,0,0,0.1)"; // 0 path + ctx.lineWidth = 15; + ctx.stroke(); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; // 0 path + ctx.lineWidth = 4; + ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([]); + } + }, + stabber(x, y, radius = 25 + Math.ceil(Math.random() * 12), spikeMax = 7) { + if (radius > 80) radius = 65; + mobs.spawn(x, y, 6, radius, "rgb(0,0,0)"); //can't have sides above 6 or collision events don't work (probably because of a convex problem) + let me = mob[mob.length - 1]; + me.isVerticesChange = true + me.accelMag = 0.0006 * simulation.accelScale; + // me.g = 0.0002; //required if using this.gravity + me.isInvulnerable = false + me.delay = 360 * simulation.CDScale; + me.spikeVertex = 0; + me.spikeLength = 0; + me.isSpikeGrowing = false; + me.spikeGrowth = 0; + me.isSpikeReset = true; + me.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.player //can't touch other mobs + Matter.Body.rotate(me, Math.PI * 0.1); + ace.shield(me, x, y); + // me.onDamage = function () {}; + // me.onHit = function() { //run this function on hitting player + // }; + me.onDeath = function () { + if (this.spikeLength > 4) { + this.spikeLength = 4 + const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[this.spikeVertex], this.position)), this.radius * this.spikeLength) + this.vertices[this.spikeVertex].x = this.position.x + spike.x + this.vertices[this.spikeVertex].y = this.position.y + spike.y + // this.vertices = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) + } + }; + me.do = function () { + this.seePlayerByLookingAt(); + this.checkStatus(); + this.attraction(); + if (this.isSpikeReset) { + if (this.seePlayer.recall) { + const dist = Vector.sub(this.seePlayer.position, this.position); + const distMag = Vector.magnitude(dist); + if (distMag < radius * spikeMax) { + //find nearest vertex + let nearestDistance = Infinity + for (let i = 0, len = this.vertices.length; i < len; i++) { + //find distance to player for each vertex + const dist = Vector.sub(this.seePlayer.position, this.vertices[i]); + const distMag = Vector.magnitude(dist); + //save the closest distance + if (distMag < nearestDistance) { + this.spikeVertex = i + nearestDistance = distMag + } + } + this.spikeLength = 1 + this.isSpikeGrowing = true; + this.isSpikeReset = false; + Matter.Body.setAngularVelocity(this, 0) + } + me.isInvulnerable = false + } + } else { + if (this.isSpikeGrowing) { + this.spikeLength += Math.pow(this.spikeGrowth += 0.02, 8) + // if (this.spikeLength < 2) { + // this.spikeLength += 0.035 + // } else { + // this.spikeLength += 1 + // } + if (this.spikeLength > spikeMax) { + this.isSpikeGrowing = false; + this.spikeGrowth = 0 + } + } else { + Matter.Body.setAngularVelocity(this, this.angularVelocity * 0.8) //reduce rotation + this.spikeLength -= 0.3 + if (this.spikeLength < 1) { + this.spikeLength = 1 + this.isSpikeReset = true + this.radius = radius + } + } + const spike = Vector.mult(Vector.normalise(Vector.sub(this.vertices[this.spikeVertex], this.position)), radius * this.spikeLength) + this.vertices[this.spikeVertex].x = this.position.x + spike.x + this.vertices[this.spikeVertex].y = this.position.y + spike.y + me.isInvulnerable = true + // this.radius = radius * this.spikeLength; + } + if (this.isInvulnerable) { + ctx.beginPath(); + let vertices = this.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 = 13 + 5 * Math.random(); + ctx.strokeStyle = `rgba(255,255,255,${0.5 + 0.2 * Math.random()})`; + ctx.stroke(); + me.damageReduction = 0; + } else { + me.damageReduction = 1; + } + }; + }, + slash(x, y, radius = 80) { + let targets = [] + const sides = 6; + mobs.spawn(x, y, 6, radius, "#000000"); + let me = mob[mob.length - 1]; + Matter.Body.rotate(me, 2 * Math.PI * Math.random()); + targets.push(me.id) //add to shield protection + const nodeBalance = Math.random() + const nodes2 = Math.min(15, Math.floor(2 + 4 * nodeBalance + 0.75 * Math.sqrt(simulation.difficulty))) + me.isBoss = true; + me.isSlashBoss = true; + me.showHealthBar = false; + me.damageReduction = 0.1 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1) + me.startingDamageReduction = me.damageReduction + me.isInvulnerable = false + me.frictionAir = 0.02 + me.seeAtDistance2 = 1000000; + me.accelMag = 0.0004 + 0.00015 * simulation.accelScale; + Matter.Body.setDensity(me, 0.0005); //normal is 0.001 + me.collisionFilter.mask = cat.bullet | cat.player | cat.body | cat.map + me.memory = Infinity; + me.seePlayerFreq = 20 + me.lockedOn = null; + me.laserRange = 500; + me.torqueMagnitude = 0.00024 * me.inertia * (Math.random() > 0.5 ? -1 : 1); + me.delay = 70 + 70 * simulation.CDScale; + me.cd = 0; + me.swordRadius = 0; + me.swordVertex = 1 + me.swordRadiusMax = 1100 + 20 * simulation.difficulty; + me.swordRadiusGrowRate = me.swordRadiusMax * (0.005 + 0.0003 * simulation.difficulty) + me.isSlashing = false; + me.swordDamage = 0.07 * simulation.dmgScale + me.laserAngle = 3 * Math.PI / 5 + me.eventHorizon = 550; + const seeDistance2 = 200000 + ace.shield(me, x, y); + const rangeInnerVsOuter = Math.random() + let speed = (0.006 + 0.001 * Math.sqrt(simulation.difficulty)) * ((Math.random() < 0.5) ? 1 : -1) + let range = radius + 350 + 200 * rangeInnerVsOuter + nodes2 * 7 + for (let i = 0; i < nodes2; i++) ace.orbital(me, range, i / nodes2 * 2 * Math.PI, speed) + const orbitalIndexes = [] //find indexes for all the current nodes2 + for (let i = 0; i < nodes2; i++) orbitalIndexes.push(mob.length - 1 - i) + // add orbitals for each orbital + range = Math.max(60, 100 + 100 * Math.random() - nodes2 * 3 - rangeInnerVsOuter * 80) + speed = speed * (1.25 + 2 * Math.random()) + const subNodes = Math.max(2, Math.floor(6 - 5 * nodeBalance + 0.5 * Math.sqrt(simulation.difficulty))) + for (let j = 0; j < nodes2; j++) { + for (let i = 0, len = subNodes; i < len; i++) ace.orbital(mob[orbitalIndexes[j]], range, i / len * 2 * Math.PI, speed) + } + for (let i = 0, len = 3 + 0.5 * Math.sqrt(simulation.difficulty); i < len; i++) ace.spawnOrbitals(me, radius + 40 + 10 * i, 1); + + const springStiffness = 0.00014; + const springDampening = 0.0005; + + me.springTarget = { + x: me.position.x, + y: me.position.y + }; + const len = cons.length; + cons[len] = Constraint.create({ + pointA: me.springTarget, + bodyB: me, + stiffness: springStiffness, + damping: springDampening + }); + Composite.add(engine.world, cons[cons.length - 1]); + cons[len].length = 100 + 1.5 * radius; + me.cons = cons[len]; + + me.springTarget2 = { + x: me.position.x, + y: me.position.y + }; + const len2 = cons.length; + cons[len2] = Constraint.create({ + pointA: me.springTarget2, + bodyB: me, + stiffness: springStiffness, + damping: springDampening, + length: 0 + }); + Composite.add(engine.world, cons[cons.length - 1]); + cons[len2].length = 100 + 1.5 * radius; + me.cons2 = cons[len2]; + me.onDamage = function () { }; + me.onDeath = function () { + isDestroyed = true; + this.removeCons(); + powerUps.spawnBossPowerUp(this.position.x, this.position.y); + }; + me.do = function () { + for (let i = 0; i < this.vertices.length; i++) { + this.harmField(this.vertices[i].x, this.vertices[i].y); + } + this.seePlayerByHistory(40); + this.springAttack(); + this.checkStatus(); + this.sword() //does various things depending on what stage of the sword swing + const eventHorizon = this.eventHorizon * (1 + 0.2 * Math.sin(simulation.cycle * 0.008)) + me.laserRange = eventHorizon; + }; + me.swordWaiting = function () { + if ( + this.seePlayer.recall && + this.cd < simulation.cycle && + this.distanceToPlayer2() < seeDistance2 && + !m.isCloak && + Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0 && + Matter.Query.ray(body, this.position, this.playerPosRandomY()).length === 0 + ) { + //find vertex farthest away from player + let dist = 0 + for (let i = 0, len = this.vertices.length; i < len; i++) { + const D = Vector.magnitudeSquared(Vector.sub({ x: this.vertices[i].x, y: this.vertices[i].y }, m.pos)) + if (D > dist) { + dist = D + this.swordVertex = i + } + } + this.laserAngle = this.swordVertex / 6 * 2 * Math.PI + 0.6283 + this.sword = this.swordGrow + Matter.Body.setAngularVelocity(this, 0) + this.accelMag = 0.0004 + 0.00015 * simulation.accelScale; + this.damageReduction = 0 + this.isInvulnerable = true + this.frictionAir = 1 + } + } + me.sword = me.swordWaiting //base function that changes during different aspects of the sword swing + me.swordGrow = function () { + const angle = this.angle + this.laserAngle; + const end = { + x: this.vertices[this.swordVertex].x + this.swordRadiusMax * Math.cos(angle), + y: this.vertices[this.swordVertex].y + this.swordRadiusMax * Math.sin(angle) + }; + + const dx = end.x - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].x; + const dy = end.y - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].y; + const angle1 = Math.atan2(dy, dx) * (180 / Math.PI); + + const dx1 = end.x - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].x; + const dy1 = end.y - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].y; + const angle2 = Math.atan2(dy1, dx1) * (180 / Math.PI); + + this.laserSword(this.vertices[this.swordVertex], this.angle + this.laserAngle); + this.laserSword(this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1], angle1 * (Math.PI / 180)) + this.laserSword(this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1], angle2 * (Math.PI / 180)) + this.swordRadius += this.swordRadiusGrowRate + if (this.swordRadius > this.swordRadiusMax) { + this.sword = this.swordSlash + this.spinCount = 0 + } + + ctx.beginPath(); + let vertices = this.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 = 13 + 5 * Math.random(); + ctx.strokeStyle = `rgba(255,255,255,${0.5 + 0.2 * Math.random()})`; + ctx.stroke(); + } + me.swordSlash = function () { + const angle = this.angle + this.laserAngle; + const end = { + x: this.vertices[this.swordVertex].x + this.swordRadiusMax * Math.cos(angle), + y: this.vertices[this.swordVertex].y + this.swordRadiusMax * Math.sin(angle) + }; + + const dx = end.x - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].x; + const dy = end.y - this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1].y; + const angle1 = Math.atan2(dy, dx) * (180 / Math.PI); + + const dx1 = end.x - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].x; + const dy1 = end.y - this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1].y; + const angle2 = Math.atan2(dy1, dx1) * (180 / Math.PI); + + this.laserSword(this.vertices[this.swordVertex], this.angle + this.laserAngle); + this.laserSword(this.vertices[this.swordVertex + 1 > (sides - 1) ? 0 : this.swordVertex + 1], angle1 * (Math.PI / 180)) + this.laserSword(this.vertices[this.swordVertex - 1 < 0 ? (sides - 1) : this.swordVertex - 1], angle2 * (Math.PI / 180)) + this.torque += this.torqueMagnitude; + this.spinCount++ + if (this.spinCount > 80) { + this.sword = this.swordWaiting + this.swordRadius = 0 + this.accelMag = 0.0004 + 0.00015 * simulation.accelScale; + this.cd = simulation.cycle + this.delay; + this.damageReduction = this.startingDamageReduction + this.isInvulnerable = false + this.frictionAir = 0.01 + } + ctx.beginPath(); + let vertices = this.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 = 13 + 5 * Math.random(); + ctx.strokeStyle = `rgba(255,255,255,${0.5 + 0.2 * Math.random()})`; + ctx.stroke(); + } + me.laserSword = function (where, angle) { + const vertexCollision = function (v1, v1End, domain) { + for (let i = 0; i < domain.length; ++i) { + let v = domain[i].vertices; + const len = v.length - 1; + for (let j = 0; j < len; j++) { + results = simulation.checkLineIntersection(v1, v1End, v[j], v[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: v[j], v2: v[j + 1] }; + } + } + results = simulation.checkLineIntersection(v1, v1End, v[0], v[len]); + if (results.onLine1 && results.onLine2) { + const dx = v1.x - results.x; + const dy = v1.y - results.y; + const dist2 = dx * dx + dy * dy; + if (dist2 < best.dist2) best = { x: results.x, y: results.y, dist2: dist2, who: domain[i], v1: v[0], v2: v[len] }; + } + } + }; + best = { x: null, y: null, dist2: Infinity, who: null, v1: null, v2: null }; + const look = { x: where.x + this.swordRadius * Math.cos(angle), y: where.y + this.swordRadius * Math.sin(angle) }; + vertexCollision(where, look, body); // vertexCollision(where, look, mob); + vertexCollision(where, look, map); + if (!m.isCloak) vertexCollision(where, look, [playerBody, playerHead]); + if (best.who && (best.who === playerBody || best.who === playerHead) && m.immuneCycle < m.cycle) { + m.immuneCycle = m.cycle + m.collisionImmuneCycles + 60; //player is immune to damage for an extra second + m.damage(this.swordDamage); + simulation.drawList.push({ //add dmg to draw queue + x: best.x, + y: best.y, + radius: this.swordDamage * 1500, + color: "rgba(0,0,0,0.5)", + time: 20 + }); + } + if (best.dist2 === Infinity) best = look; + ctx.beginPath(); //draw beam + ctx.moveTo(where.x, where.y); + ctx.lineTo(best.x, best.y); + ctx.strokeStyle = "rgba(0,0,0,0.1)"; // Black path + ctx.lineWidth = 25; + ctx.stroke(); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; // Black path + ctx.lineWidth = 5; + ctx.setLineDash([70 + 300 * Math.random(), 55 * Math.random()]); + ctx.stroke(); // Draw it + ctx.setLineDash([]); + } + me.harmField = function (x, y) { + ctx.setLineDash([125 * Math.random(), 125 * Math.random()]); + // ctx.lineDashOffset = 6*(simulation.cycle % 215); + if (this.distanceToPlayer3(x, y) < this.laserRange) { + if (m.immuneCycle < m.cycle) { + m.damage(0.0003 * simulation.dmgScale); + if (m.energy > 0.1) m.energy -= 0.003 + } + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(m.pos.x, m.pos.y); + ctx.lineTo(m.pos.x + (Math.random() - 0.5) * 3000, m.pos.y + (Math.random() - 0.5) * 3000); + ctx.lineWidth = 2; + ctx.strokeStyle = "rgb(0,0,0)"; + ctx.stroke(); + + ctx.beginPath(); + ctx.arc(m.pos.x, m.pos.y, 40, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,0,0,0.15)"; + ctx.fill(); + } + ctx.beginPath(); + ctx.arc(x, y, this.laserRange * 0.9, 0, 2 * Math.PI); + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.lineWidth = 1; + ctx.stroke(); + ctx.setLineDash([]); + ctx.fillStyle = "rgba(0,0,0,0.03)"; + ctx.fill(); + } + me.distanceToPlayer3 = function (x, y) { + const dx = x - player.position.x; + const dy = y - player.position.y; + return Math.sqrt(dx * dx + dy * dy); + } + radius = 22 // radius of each node mob + const sideLength = 100 // distance between each node mob + const nodes = 6 + const angle = 2 * Math.PI / nodes + + spawn.allowShields = false; //don't want shields on individual mobs + + for (let i = 0; i < nodes; ++i) { + ace.stabber(x + sideLength * Math.sin(i * angle), y + sideLength * Math.cos(i * angle), radius, 12); + Matter.Body.setDensity(mob[mob.length - 1], 0.003); //extra dense //normal is 0.001 //makes effective life much larger + mob[mob.length - 1].damageReduction = 0.12 + mob[mob.length - 1].showHealthBar = false; + mob[mob.length - 1].isBoss = true; + targets.push(mob[mob.length - 1].id) //track who is in the node boss, for shields + } + + const attachmentStiffness = 0.02 + spawn.constrain2AdjacentMobs(nodes, attachmentStiffness, true); //loop mobs together + + for (let i = 0; i < nodes; ++i) { //attach to center mob + consBB[consBB.length] = Constraint.create({ + bodyA: me, + bodyB: mob[mob.length - i - 1], + stiffness: attachmentStiffness, + damping: 0.03 + }); + Composite.add(engine.world, consBB[consBB.length - 1]); + } + //spawn shield around all nodes + ace.groupShield(targets, x, y, sideLength + 1 * radius + nodes * 5 - 25); + spawn.allowShields = true; + }, + } + level.setPosToSpawn(-625, -100); //normal spawn + level.exit.x = -23650; + level.exit.y = 11100; + simulation.fallHeight = 20000; + const door = level.door(350, -200, 25, 225, 225, 10) + const door2 = level.door(6325, -200, 25, 225, 225, 10); + // spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); //bump for level entrance + spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20); //bump for level exit + level.defaultZoom = 1800 + simulation.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#d8dadf"; + ace.stabber(425, -100); + ace.stabber(725, -100); + ace.stabber(1000, -100); + ace.stabber(1300, -100); + ace.stabber(1550, -100); + ace.stabber(1850, -100); + ace.stabber(2125, -100); + ace.stabber(2400, -100); + ace.stabber(2675, -100); + ace.stabber(2975, -100); + ace.stabber(3225, -100); + ace.stabber(3525, -100); + ace.stabber(3800, -100); + ace.stabber(4100, -100); + ace.stabber(4375, -100); + ace.stabber(4650, -100); + ace.stabber(4925, -100); + ace.stabber(5200, -100); + ace.stabber(5500, -100); + ace.stabber(5775, -100); + spawn.mapRect(-200, -450, 2825, 75); + spawn.mapRect(-200, 0, 2825, 75); + spawn.mapRect(-300, -400, 150, 50); + spawn.mapRect(-575, -375, 325, 50); + spawn.mapRect(-1175, 0, 700, 75); + spawn.mapRect(-1100, 50, 675, 75); + spawn.mapRect(-1100, -50, 225, 75); + spawn.mapRect(-1025, -75, 200, 50); + spawn.mapRect(-875, -50, 150, 75); + spawn.mapRect(-700, -350, 325, 50); + spawn.mapRect(-950, -100, 150, 50); + spawn.mapRect(-675, -50, 125, 25); + spawn.mapRect(-575, -150, 25, 125); + spawn.mapRect(-650, -50, 25, 75); + spawn.mapRect(-600, -50, 25, 75); + spawn.mapRect(-800, -325, 250, 50); + spawn.mapRect(-1450, 50, 500, 75); + spawn.mapRect(-1550, 100, 475, 100); + spawn.mapRect(-1650, 175, 525, 375); + spawn.mapRect(-1700, 275, 200, 175); + spawn.mapRect(-1550, 525, 475, 100); + spawn.mapRect(-1475, 600, 4125, 100); + spawn.mapRect(-50, 50, 75, 75); + level.chain(-450, 75, 0, false, 13) + level.chain(7475, 600, -0.5498531827, false, 200) + spawn.mapRect(325, -425, 75, 275); + spawn.mapRect(-850, -300, 100, 50); + spawn.mapRect(-900, -125, 75, 50); + spawn.mapRect(-875, -275, 50, 50); + spawn.mapRect(425, -400, 125, 100); + spawn.mapRect(600, -400, 125, 100); + spawn.mapRect(775, -400, 125, 100); + spawn.mapRect(950, -400, 125, 100); + spawn.mapRect(375, -350, 2250, 25); + spawn.mapRect(1125, -400, 125, 100); + spawn.mapRect(1300, -400, 125, 100); + spawn.mapRect(1475, -400, 125, 100); + spawn.mapRect(1650, -400, 125, 100); + spawn.mapRect(1825, -400, 125, 100); + spawn.mapRect(2000, -400, 125, 100); + spawn.mapRect(2175, -400, 125, 100); + spawn.mapRect(2350, -400, 125, 100); + spawn.mapRect(2525, -400, 125, 100); + spawn.mapRect(-1350, 650, 4000, 150); + spawn.mapRect(-1400, 675, 125, 75); + spawn.mapRect(-1325, 25, 200, 50); + spawn.mapRect(2600, 600, 4350, 200); + spawn.mapRect(2550, 0, 4400, 75); + spawn.mapRect(6875, 0, 875, 75); + spawn.mapRect(2500, -450, 5300, 50); + spawn.mapRect(7575, -425, 575, 475); + spawn.mapRect(6825, 600, 650, 125); + spawn.mapRect(6875, 675, 475, 100); + spawn.mapRect(7050, -525, 1175, 175); + spawn.mapRect(7650, -25, 575, 150); + spawn.mapRect(6075, -500, 1125, 75); + spawn.mapRect(2550, -350, 3450, 25); + spawn.mapRect(2700, -400, 125, 100); + spawn.mapRect(2875, -400, 125, 100); + spawn.mapRect(3050, -400, 125, 100); + spawn.mapRect(3225, -400, 125, 100); + spawn.mapRect(3400, -400, 125, 100); + spawn.mapRect(3575, -400, 125, 100); + spawn.mapRect(3750, -400, 125, 100); + spawn.mapRect(3925, -400, 125, 100); + spawn.mapRect(4100, -400, 125, 100); + spawn.mapRect(4275, -400, 125, 100); + spawn.mapRect(4450, -400, 125, 100); + spawn.mapRect(4625, -400, 125, 100); + spawn.mapRect(4800, -400, 125, 100); + spawn.mapRect(4975, -400, 125, 100); + spawn.mapRect(5150, -400, 125, 100); + spawn.mapRect(5325, -400, 125, 100); + spawn.mapRect(5500, -400, 125, 100); + spawn.mapRect(5675, -400, 125, 100); + spawn.mapRect(5850, -400, 125, 100); + spawn.mapRect(6000, -400, 125, 100); + spawn.mapRect(6100, -425, 125, 100); + spawn.mapRect(6150, -450, 200, 75); + spawn.mapRect(2575, -400, 3575, 25); + spawn.mapRect(6300, -425, 75, 250); + spawn.mapRect(-200, -175, 50, 50); + spawn.bodyRect(-950, 475, 150, 125); + spawn.bodyRect(-650, 475, 150, 125); + spawn.bodyRect(-1000, 350, 550, 125); + spawn.bodyRect(-650, 150, 225, 200); + spawn.bodyRect(-1050, 150, 400, 200); + spawn.bodyRect(-1125, 225, 25, 275); + spawn.bodyRect(-1100, 350, 125, 200); + spawn.bodyRect(-800, 475, 150, 125); + spawn.bodyRect(-500, 475, 125, 125); + spawn.mapRect(-3325, -50, 600, 75); + spawn.mapRect(-3250, 0, 450, 75); + spawn.mapRect(-2950, -100, 350, 75); + spawn.mapRect(-2975, -150, 325, 75); + spawn.mapRect(-3150, -250, 125, 25); + spawn.mapRect(-3050, -225, 100, 25); + spawn.mapRect(-3000, -200, 125, 25); + spawn.mapRect(-3425, -75, 325, 50); + spawn.mapRect(-3250, -225, 125, 25); + spawn.mapRect(-3325, -200, 100, 25); + spawn.mapRect(-3100, -300, 25, 50); + spawn.mapRect(-3725, -325, 1300, 25); + spawn.mapRect(-2925, -175, 125, 25); + spawn.mapRect(-3375, -175, 100, 25); + spawn.mapRect(-3550, -150, 250, 75); + spawn.mapRect(-3725, -150, 250, 50); + spawn.mapRect(-3725, -200, 125, 75); + spawn.mapRect(-3625, -175, 50, 25); + spawn.mapRect(-3750, -125, 50, 25); + spawn.mapRect(-3750, -50, 125, 50); + spawn.mapRect(-3650, -125, 75, 100); + spawn.mapRect(-2750, 0, 100, 75); + spawn.mapRect(-2675, 25, 175, 25); + spawn.mapRect(-2850, 0, 150, 50); + spawn.mapRect(-3150, 50, 25, 75); + spawn.mapRect(-2900, 50, 25, 75); + spawn.mapRect(-3300, 100, 575, 25); + spawn.mapRect(-24300, 11125, 51525, 6250); + spawn.mapVertex(7900, -675, "0 0 500 -200 500 300 -450 300") + spawn.mapRect(-24300, 9575, 475, 1750); + spawn.mapRect(26800, 9575, 425, 1750); + spawn.hopper(-3100, -150); + spawn.mapRect(11625, 8375, 11200, 225); + spawn.mapRect(22600, 8375, 225, 3225); + spawn.mapRect(11625, 8375, 225, 1800); + spawn.mapRect(12425, 9825, 225, 875); + spawn.mapRect(13150, 10725, 225, 575); + spawn.mapRect(14125, 10450, 5025, 200); + spawn.mapRect(21775, 10625, 1050, 225); + spawn.mapRect(20325, 10925, 1300, 200); + spawn.mapRect(20825, 10250, 750, 225); + spawn.mapRect(19500, 10000, 1000, 225); + + + ace.slasher2(-22725, 10325); + ace.slasher3(-23425, 10250); + ace.slasher2(-23350, 10700); + ace.slasher3(-21725, 11075); + ace.slasher2(-21525, 10025); + ace.slasher3(-20950, 9750); + ace.slasher2(-19975, 9700); + ace.slasher3(-18850, 9650); + ace.slasher2(-18675, 9700); + ace.slasher3(-18250, 9125); + ace.slasher2(-17775, 8925); + ace.slasher3(-16975, 8875); + ace.slasher2(-16475, 9125); + ace.slasher3(-16125, 9275); + ace.slasher2(-15650, 9225); + ace.slasher3(-15200, 9175); + ace.slasher2(-16800, 9325); + ace.slasher3(-17450, 9525); + ace.slasher2(-18375, 9625); + ace.slasher3(-19650, 9375); + ace.slasher2(-20600, 9225); + ace.slasher3(-21625, 9400); + ace.slasher2(-22450, 9775); + ace.slasher3(-22900, 10000); + ace.slasher2(-23275, 9300); + ace.slasher3(-23125, 9150); + ace.slasher2(2800, 9350); + ace.slasher3(4925, 9825); + ace.slasher2(3725, 10525); + ace.slasher3(1850, 10450); + ace.slash(16850, 10075); + spawn.mapVertex(-14325, 11000, "0 0 4000 -2000 10000 -3000 16000 -2000 20000 0"); + + let q = Matter.Bodies.rectangle(7525 + (1100 / 2), 10675 + (150 / 2), 1100, 150, { + density: 0.05, + isNotHoldable: false, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + let qq = Matter.Bodies.rectangle(7375 + (150 / 2), 10550 + (200 / 2), 150, 200, { + density: 0.05, + isNotHoldable: true, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + let qqq = Matter.Bodies.rectangle(7450 + (1250 / 2), 10500 + (100 / 2), 1250, 100, { + density: 0.05, + isNotHoldable: true, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + let qqqq = Matter.Bodies.rectangle(8625 + (150 / 2), 10550 + (200 / 2), 150, 200, { + density: 0.05, + isNotHoldable: true, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + let qqqqq = Matter.Bodies.rectangle(7600 + (100 / 2), 10350 + (200 / 2), 100, 200, { + density: 0.05, + isNotHoldable: true, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + let qqqqqq = Matter.Bodies.rectangle(8475 + (100 / 2), 10350 + (200 / 2), 100, 200, { + density: 0.05, + isNotHoldable: true, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + let qqqqqqq = Matter.Bodies.rectangle(7650 + (725 / 2), 10325 + (75 / 2), 725, 75, { + density: 0.05, + isNotHoldable: true, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + let qqqqqqqq = Matter.Bodies.rectangle(8000 + 100, 10200 + (150 / 2), 200, 150, { + density: 0.05, + isNotHoldable: true, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + let qqqqqqqqq = Matter.Bodies.rectangle(6975 + (1125 / 2), 10250 + 25, 1125, 50, { + density: 0.05, + isNotHoldable: true, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + let qqqqqqqqqq = Matter.Bodies.rectangle(7600 + 50, 10575 + (125 / 2), 100, 125, { + density: 0.05, + isNotHoldable: true, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + let qqqqqqqqqqq = Matter.Bodies.rectangle(8475 + 50, 10575 + (125 / 2), 100, 125, { + density: 0.05, + isNotHoldable: true, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + let qqqqqqqqqqqq = Matter.Bodies.rectangle(8025 + 50, 10575 + (125 / 2), 100, 125, { + density: 0.05, + isNotHoldable: true, + restitution: 1.05, + isStatic: false + }, true, [true], 0); + + + wasd = Matter.Body.create({ + parts: [q, qq, qqq, qqqq, qqqqq, qqqqqq, qqqqqqq, qqqqqqqq, qqqqqqqqq, qqqqqqqqqq, qqqqqqqqqqq, qqqqqqqqqqqq] + }); + + body[body.length] = q; + body[body.length] = qq; + body[body.length] = qqq; + body[body.length] = qqqq; + body[body.length] = qqqqq; + body[body.length] = qqqqqq; + body[body.length] = qqqqqqq; + body[body.length] = qqqqqqqq; + body[body.length] = qqqqqqqqq; + body[body.length] = qqqqqqqqqq; + body[body.length] = qqqqqqqqqqq; + body[body.length] = qqqqqqqqqqqq; + // body[body.length] = wasd; + + Matter.Composite.add(engine.world, wasd) + composite[composite.length] = wasd; + // wasd.friction -= 0.5 + setTimeout(function () { + wasd.collisionFilter.category = cat.map; + wasd.collisionFilter.mask = cat.body | cat.player | cat.bullet | cat.mobBullet | cat.mob | cat.map + }, 100); + let Vx = 0; + var gradient = ctx.createLinearGradient(0, 0, 10975 / 2, 0); + gradient.addColorStop(0, "#00000000"); + gradient.addColorStop(1, "#686868"); + level.custom = () => { + wasd.force.y += simulation.g * wasd.mass; + if (Matter.Query.collides(wasd, [player]).length > 0 && !(m.isCloak && tech.isIntangible) && input.down && isDestroyed) { + wasd.force.x += Math.cos(m.angle) * 75; + Matter.Body.setVelocity(player, wasd.velocity) + m.Vx = player.velocity.x - wasd.velocity.x; + } + for (let i = 0; i < mob.length; i++) { + if (Matter.Query.collides(wasd, [mob[i]]).length > 0 && !mob[i].isBoss && isDestroyed) { + const dmg = 1; + mob[i].damage(dmg, true); + simulation.drawList.push({ //add dmg to draw queue + x: mob[i].position.x, + y: mob[i].position.y, + radius: Math.sqrt(dmg) * 50, + color: simulation.mobDmgColor, + time: simulation.drawTime + }); + break + } + } + Vx = wasd.velocity.x / 5; + level.exit.drawAndCheck(); + drawSeats(475, -50, 5600, 125, 20, "darkgray"); + door.openClose() + door2.openClose() + if (player.position.y < 25) { + door.isClosing = false; + door2.isClosing = false; + } else { + door.isClosing = true; + door2.isClosing = true; + } + ctx.fillStyle = "red"; + ctx.fillRect(-825, -75, 50, 50); + + b.pulse(30, 0, { x: -2500, y: (25 + (25 / 2)) }); + ctx.save() + ctx.translate(11750, 8475) + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, 10975, 2800); + ctx.restore() + drawHead(7400, 0, Math.PI * 0.1); + drawHead(7460, 0, Math.PI * 0.5); + drawHead(7520, 0, Math.PI * 0.3); + drawHead(22400, 11125, Math.PI * 0.3); + drawHead(21925, 10625, Math.PI * 0.5); + drawHead(21175, 10250, Math.PI * 0.1); + drawHead(22525, 10625, Math.PI * 0.7); + drawHead(22525, 11125, Math.PI * 0.9); + drawHead(22225, 11125, Math.PI * 1.5); + }; + level.customTopLayer = () => { + drawSeats(500, -50, 5600, 125); + + ctx.strokeStyle = 'red'; + ctx.lineWidth = 20; + ctx.beginPath(); + ctx.setLineDash([40, 40]); + ctx.lineDashOffset = (-simulation.cycle * Vx) % 80; + + ctx.moveTo(q.vertices[0].x, q.vertices[0].y); + for (let i = 1; i < q.vertices.length; i++) { + ctx.lineTo(q.vertices[i].x, q.vertices[i].y); + } + ctx.lineTo(q.vertices[0].x, q.vertices[0].y); + ctx.closePath(); + ctx.stroke(); + ctx.setLineDash([0, 0]); + }; + function drawSeats(x, y, w, h, num = 20, c = "gray") { + const seatWidth = w / num; + const seatHeight = h / num; + + for (let i = 0; i < num; i++) { + const seatX = x + i * seatWidth; + const seatY = y; + + // Draw the seat parts + ctx.fillStyle = c; + ctx.fillRect(seatX - 100, seatY, 125, 25); + ctx.fillRect(seatX, seatY - 125, 25, 150); + ctx.fillRect(seatX - 75, seatY, 25, 75); + ctx.fillRect(seatX - 25, seatY, 25, 75); + } + } + function drawHead(x, y, angle) { + ctx.save(); + ctx.translate(x, y - 30); + ctx.rotate(angle); + ctx.beginPath(); + ctx.arc(0, 0, 30, 0, 2 * Math.PI); + ctx.fillStyle = m.bodyGradient + ctx.fill(); + ctx.arc(15, 0, 4, 0, 2 * Math.PI); + ctx.strokeStyle = "#333"; + ctx.lineWidth = 2; + ctx.stroke(); + ctx.restore(); + } + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].isSlashBoss) { + simulation.ephemera.push({ + name: "bossBar", + do() { + if (level.levels[level.onLevel] == "ace" && !isDestroyed) { + ctx.save(); + ctx.setTransform(1, 0, -0.5, 1, 0, 0); //slanted + ctx.fillStyle = "rgba(100, 100, 100, 0.3)"; + ctx.fillRect(canvas.width2 / 2, canvas.height2 / 10, canvas.width2, 30); + ctx.fillStyle = "rgba(0,0,0,0.7)"; + ctx.fillRect(canvas.width2 / 2, canvas.height2 / 10, canvas.width2 * mob[i].health, 30); + ctx.restore(); + } + }, + }) + } + } + }, // ******************************************************************************************************** // ******************************************************************************************************** // ***************************************** training levels ********************************************** diff --git a/js/player.js b/js/player.js index 1c79fd5..2045371 100644 --- a/js/player.js +++ b/js/player.js @@ -307,6 +307,23 @@ const m = { if (player.velocity.x < m.airSpeedLimit / player.mass / player.mass) player.force.x += m.FxAir; //move player right / d } }, + printBlock() { + const sides = Math.floor(4 + 6 * Math.random() * Math.random()) + body[body.length] = Matter.Bodies.polygon(m.pos.x, m.pos.y, sides, 8, { + friction: 0.05, + frictionAir: 0.001, + collisionFilter: { category: 0, mask: 0 }, //no collision because player is holding + classType: "body", + isPrinted: true, + radius: 10, //used to grow and warp the shape of the block + density: 0.002, //double density for 2x damage + }); + const who = body[body.length - 1] + Composite.add(engine.world, who); //add to world + m.throwCharge = 4; + m.holdingTarget = who + m.isHolding = true; + }, alive: false, switchWorlds() { powerUps.boost.endCycle = 0 @@ -552,7 +569,7 @@ const m = { if (tech.isLowHealthDefense) dmg *= 1 - Math.max(0, 1 - m.health) * 0.8 if (tech.isHarmReduceNoKill && m.lastKillCycle + 300 < m.cycle) dmg *= 0.33 if (tech.squirrelFx !== 1) dmg *= 0.78//Math.pow(0.78, (tech.squirrelFx - 1) / 0.4) - if (tech.isAddBlockMass && m.isHolding) dmg *= 0.15 + if (tech.isAddBlockMass && m.isHolding) dmg *= 0.1 if (tech.isSpeedHarm && player.speed > 0.1) dmg *= 1 - Math.min(player.speed * 0.0165, 0.66) if (tech.isHarmReduce && input.field && m.fieldCDcycle < m.cycle) dmg *= 0.25 if (tech.isNeutronium && input.field && m.fieldCDcycle < m.cycle) dmg *= 0.1 @@ -1972,11 +1989,11 @@ const m = { m.fieldRegen *= 0.66 } }, - regenEnergy: function () { //used in drawRegenEnergy // rewritten by some tech + regenEnergy() { //used in drawRegenEnergy // rewritten by some tech if (m.immuneCycle < m.cycle && m.fieldCDcycle < m.cycle) m.energy += m.fieldRegen; if (m.energy < 0) m.energy = 0 }, - regenEnergyDefault: function () { + regenEnergyDefault() { if (m.immuneCycle < m.cycle && m.fieldCDcycle < m.cycle) m.energy += m.fieldRegen; if (m.energy < 0) m.energy = 0 }, @@ -2068,7 +2085,6 @@ const m = { if (m.fireCDcycle < m.cycle) m.fireCDcycle = m.cycle if (tech.isCapacitor && m.throwCharge < 4) m.throwCharge = 4 m.throwCharge += 0.5 / m.holdingTarget.mass / b.fireCDscale - if (m.throwCharge < 6) m.energy -= 0.001 / b.fireCDscale; // m.throwCharge caps at 5 //trajectory path prediction @@ -2116,11 +2132,8 @@ const m = { //trajectory prediction const cycles = 30 const charge = Math.min(m.throwCharge / 5, 1) - const speed = 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.25)); - const v = { - x: speed * Math.cos(m.angle), - y: speed * Math.sin(m.angle) - } //m.Vy / 2 + removed to make the path less jerky + const speed = (tech.isPrinter ? 15 + 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.1)) : 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.25))) + const v = { x: speed * Math.cos(m.angle), y: speed * Math.sin(m.angle) } ctx.beginPath() for (let i = 1, len = 10; i < len + 1; i++) { const time = cycles * i / len @@ -2134,8 +2147,11 @@ const m = { m.drop() } } else if (m.throwCharge > 0) { //Matter.Query.region(mob, player.bounds) + if (m.holdingTarget.isPrinted) m.holdingTarget.isPrinted = undefined //throw the body - m.fieldCDcycle = m.cycle + 15; + m.fieldCDcycle = m.cycle + 20; + m.fireCDcycle = m.cycle + 20; + m.isHolding = false; if (tech.isTokamak && m.throwCharge > 4) { //remove the block body and pulse in the direction you are facing @@ -2176,7 +2192,9 @@ const m = { const charge = Math.min(m.throwCharge / 5, 1) //***** scale throw speed with the first number, 80 ***** - let speed = 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.25)); + // let speed = 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.25)); + let speed = (tech.isPrinter ? 15 + 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.1)) : 80 * charge * Math.min(0.85, 0.8 / Math.pow(m.holdingTarget.mass, 0.25))) + if (Matter.Query.collides(m.holdingTarget, map).length !== 0) { speed *= 0.7 //drop speed by 30% if touching map if (Matter.Query.ray(map, m.holdingTarget.position, m.pos).length !== 0) speed = 0 //drop to zero if the center of the block can't see the center of the player through the map @@ -2196,7 +2214,7 @@ const m = { if (tech.isAddBlockMass) { const expand = function (that, massLimit) { if (that.mass < massLimit) { - const scale = 1.05; + const scale = 1.04; Matter.Body.scale(that, scale, scale); setTimeout(expand, 20, that, massLimit); } @@ -3105,10 +3123,10 @@ const m = { }, { name: "molecular assembler", - description: `excess energy used to build ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
generate 12 energy per second`, + description: `excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
generate 12 energy per second`, // simulation.molecularMode: Math.floor(4 * Math.random()), //0 spores, 1 missile, 2 ice IX, 3 drones setDescription() { - return `excess energy used to build ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
generate 12 energy per second` + return `excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
use energy to deflect mobs
generate 12 energy per second` }, effect: () => { m.fieldMeterColor = "#ff0" @@ -3195,12 +3213,28 @@ const m = { if (m.isHolding) { m.drawHold(m.holdingTarget); m.holding(); + if (tech.isPrinter && m.holdingTarget.isPrinted && input.field) { + // if (Math.random() < 0.004 && m.holdingTarget.vertices.length < 12) m.holdingTarget.vertices.push({ x: 0, y: 0 }) //small chance to increase the number of vertices + m.holdingTarget.radius += Math.min(1.1, 1.3 / m.holdingTarget.mass) //grow up to a limit + const r1 = m.holdingTarget.radius * (1 + 0.12 * Math.sin(m.cycle * 0.11)) + const r2 = m.holdingTarget.radius * (1 + 0.12 * Math.cos(m.cycle * 0.11)) + let angle = (m.cycle * 0.01) % (2 * Math.PI) //rotate the object + let vertices = [] + for (let i = 0, len = m.holdingTarget.vertices.length; i < len; i++) { + angle += 2 * Math.PI / len + vertices.push({ x: m.holdingTarget.position.x + r1 * Math.cos(angle), y: m.holdingTarget.position.y + r2 * Math.sin(angle) }) + } + Matter.Body.setVertices(m.holdingTarget, vertices) + m.definePlayerMass(m.defaultMass + m.holdingTarget.mass * m.holdingMassScale) + } m.throwBlock(); } else if ((input.field && m.fieldCDcycle < m.cycle)) { //not hold but field button is pressed if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen m.grabPowerUp(); m.lookForPickUp(); - if (m.energy > m.minEnergyToDeflect) { + if (tech.isPrinter && input.down) { + m.printBlock(); + } else if (m.energy > m.minEnergyToDeflect) { m.drawField(); m.pushMobsFacing(); } @@ -3993,6 +4027,36 @@ const m = { m.fieldRadius = 0; m.drop(); m.hold = function () { + if (tech.isPrinter) { + //spawn blocks if field and crouch + if (input.field && m.fieldCDcycle < m.cycle && input.down && !m.isHolding) { + m.printBlock() + } + //if holding block grow it + if (m.isHolding) { + m.drawHold(m.holdingTarget); + m.holding(); + if (tech.isPrinter && m.holdingTarget.isPrinted && input.field) { + // if (Math.random() < 0.004 && m.holdingTarget.vertices.length < 12) m.holdingTarget.vertices.push({ x: 0, y: 0 }) //small chance to increase the number of vertices + m.holdingTarget.radius += Math.min(1.1, 1.3 / m.holdingTarget.mass) //grow up to a limit + const r1 = m.holdingTarget.radius * (1 + 0.12 * Math.sin(m.cycle * 0.11)) + const r2 = m.holdingTarget.radius * (1 + 0.12 * Math.cos(m.cycle * 0.11)) + let angle = (m.cycle * 0.01) % (2 * Math.PI) //rotate the object + let vertices = [] + for (let i = 0, len = m.holdingTarget.vertices.length; i < len; i++) { + angle += 2 * Math.PI / len + vertices.push({ x: m.holdingTarget.position.x + r1 * Math.cos(angle), y: m.holdingTarget.position.y + r2 * Math.sin(angle) }) + } + Matter.Body.setVertices(m.holdingTarget, vertices) + m.definePlayerMass(m.defaultMass + m.holdingTarget.mass * m.holdingMassScale) + } + m.throwBlock() + } else { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + //if releasing field throw it + + } if (input.field) { if (m.fieldCDcycle < m.cycle) { const scale = 25 diff --git a/js/spawn.js b/js/spawn.js index c7dbe55..0dabc8c 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -8371,7 +8371,7 @@ const spawn = { Composite.add(engine.world, who); //add to world if (isRedrawMap) simulation.draw.setPaths() }, - mapVertexNow(x, y, vector, properties) { //adds shape to map array in the middle of a level + mapVertexNow(x, y, vector, properties, isRedrawMap = true) { //adds shape to map array in the middle of a level map[map.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties); const who = map[map.length - 1] who.collisionFilter.category = cat.map; diff --git a/js/tech.js b/js/tech.js index a23769c..c173744 100644 --- a/js/tech.js +++ b/js/tech.js @@ -2044,9 +2044,9 @@ const tech = { frequency: 1, frequencyDefault: 1, allowed() { - return m.fieldMode !== 9 + return m.fieldMode !== 9 && !tech.isTokamak }, - requires: "not wormhole", + requires: "not wormhole, tokamak", effect() { tech.blockDamage = 0.3 }, @@ -2057,13 +2057,13 @@ const tech = { { name: "inflation", link: `inflation`, - description: "if holding a block +85% defense
after throwing a block it expands 300%", + description: "if holding a block +90% defense
after throwing a block it expands 200%", maxCount: 1, count: 0, frequency: 3, frequencyDefault: 3, allowed() { - return tech.blockDamage > 0.075 && m.fieldMode !== 8 && m.fieldMode !== 9 && !tech.isTokamak + return (tech.blockDamage > 0.075 || tech.isPrinter) && m.fieldMode !== 8 && m.fieldMode !== 9 && !tech.isTokamak }, requires: "mass driver, not pilot wave, tokamak, wormhole", effect() { @@ -2081,7 +2081,7 @@ const tech = { frequency: 3, frequencyDefault: 3, allowed() { - return tech.blockDamage > 0.075 && m.fieldUpgrades[m.fieldMode].name !== "pilot wave" && m.fieldUpgrades[m.fieldMode].name !== "wormhole" && !tech.isTokamak + return (tech.blockDamage > 0.075 || tech.isPrinter) && m.fieldUpgrades[m.fieldMode].name !== "pilot wave" && m.fieldUpgrades[m.fieldMode].name !== "wormhole" && !tech.isTokamak }, requires: "mass driver, not pilot wave, tokamak, wormhole", effect() { @@ -2099,7 +2099,7 @@ const tech = { frequency: 3, frequencyDefault: 3, allowed() { - return tech.blockDamage > 0.075 && !tech.nailsDeathMob && !tech.sporesOnDeath && !tech.isExplodeMob && !tech.botSpawner && !tech.iceIXOnDeath + return (tech.blockDamage > 0.075 || tech.isPrinter) && !tech.nailsDeathMob && !tech.sporesOnDeath && !tech.isExplodeMob && !tech.botSpawner && !tech.iceIXOnDeath }, requires: "mass driver, no other mob death tech", effect() { @@ -2130,14 +2130,14 @@ const tech = { { name: "buckling", descriptionFunction() { - return `if a block you threw kills a mob
spawn either ${powerUps.orb.heal()}, ${powerUps.orb.ammo()}, or ${powerUps.orb.research(1)}` + return `if a block you threw kills a mob
spawn either ${powerUps.orb.coupling(1)}, ${powerUps.orb.boost(1)}, ${powerUps.orb.heal()}, ${powerUps.orb.ammo()}, or ${powerUps.orb.research(1)}` }, maxCount: 1, count: 0, frequency: 3, frequencyDefault: 3, allowed() { - return tech.blockDamage > 0.075 && m.fieldUpgrades[m.fieldMode].name !== "pilot wave" && !tech.isTokamak + return (tech.blockDamage > 0.075 || tech.isPrinter) && m.fieldUpgrades[m.fieldMode].name !== "pilot wave" && !tech.isTokamak }, requires: "mass driver, not pilot wave, tokamak", effect() { @@ -7796,32 +7796,7 @@ const tech = { // }, { name: "bot manufacturing", - description: `use ${powerUps.orb.research(1)} to build
3 random bots`, - isFieldTech: true, - maxCount: 1, - count: 0, - frequency: 1, - frequencyDefault: 1, - isBotTech: true, - isNonRefundable: true, - allowed() { - return powerUps.research.count > 0 && (m.fieldMode === 4 || m.fieldMode === 8) - }, - requires: "molecular assembler, pilot wave", - effect() { - for (let i = 0; i < 1; i++) { - if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1) - } - m.energy = 0.01; - b.randomBot() - b.randomBot() - b.randomBot() - }, - remove() { } - }, - { - name: "bot prototypes", - description: `use ${powerUps.orb.research(2)}to build 2 random bots
and upgrade all bots to that type`, + description: `use ${powerUps.orb.research(2)} to build
3 random bots`, isFieldTech: true, maxCount: 1, count: 0, @@ -7837,6 +7812,31 @@ const tech = { for (let i = 0; i < 2; i++) { if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1) } + m.energy = 0.01; + b.randomBot() + b.randomBot() + b.randomBot() + }, + remove() { } + }, + { + name: "bot prototypes", + description: `use ${powerUps.orb.research(3)}to build 2 random bots
and upgrade all bots to that type`, + isFieldTech: true, + maxCount: 1, + count: 0, + frequency: 1, + frequencyDefault: 1, + isBotTech: true, + isNonRefundable: true, + allowed() { + return powerUps.research.count > 2 && (m.fieldMode === 4 || m.fieldMode === 8) + }, + requires: "molecular assembler, pilot wave", + effect() { + for (let i = 0; i < 3; i++) { + if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1) + } //fill array of available bots const notUpgradedBots = [] const num = 2 @@ -7983,6 +7983,27 @@ const tech = { // if (this.count > 0) powerUps.research.changeRerolls(1) // } // }, + { + name: "additive manufacturing", + description: "hold crouch and use your field to print a block
with +80% density, damage, and launch speed", + // description: "simultaneously fire and activate your field to make
molecular assembler print a throwable block
+80% block throwing speed", + isFieldTech: true, + maxCount: 1, + count: 0, + frequency: 2, + frequencyDefault: 2, + allowed() { + return (m.fieldMode === 4 || m.fieldMode === 8) && !tech.isTokamak + }, + requires: "molecular assembler, pilot wave, not tokamak", + effect() { + tech.isPrinter = true; + }, + remove() { + if (this.count > 0) m.holdingTarget = null; + tech.isPrinter = false; + } + }, { name: "pair production", description: "after picking up a power up
+200 energy", @@ -8055,9 +8076,9 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return m.fieldMode === 5 || m.fieldMode === 4 + return (m.fieldMode === 5 || m.fieldMode === 4) && !tech.isPrinter }, - requires: "plasma torch, molecular assembler", + requires: "plasma torch, molecular assembler, not printer", effect() { tech.isTokamak = true; }, @@ -11724,4 +11745,5 @@ const tech = { isLaserField: null, isHealBrake: null, isMassProduction: null, + isPrinter: null, } \ No newline at end of file diff --git a/todo.txt b/todo.txt index c95e84f..8de3a67 100644 --- a/todo.txt +++ b/todo.txt @@ -1,23 +1,21 @@ ******************************************************** NEXT PATCH ************************************************** +community map ace by Richard0820 -new community training level diamagnetism by Richard0820 - it's at the end of the training levels - start training by click the top right button at the load screen +field tech: additive manufacturing - crouch while activating your field to print a throwable block + blocks 80% faster and 80% more dense/more damage + molecular assembler, pilot wave -slasher mob variant with 2 laser swords -slasher mob variant with a laser spear - -suckerBoss pulls in powerUps and makes them orbit better -shooterBoss fires faster and larger bullets - -wormhole 4 -> 5% duplication, and a 10% reduction in energy cost -drones fire faster and aim more accurately -quenching gives 10% more max health and harm -snake tail mobs have 15% less health -tungsten carbide properly gives 222 health in addition to the bonus max health +inflation: 85->90% defense, 300->200% larger blocks +buckling: can spawn boosts or coupling in addition to heals, ammo, and research *********************************************************** TODO ***************************************************** +add a player to the load screen + static SVG image of player + canvas image + interactable? + full matter.js level? + use cross product rotation for other mobs? snipers, shooters? //gently rotate towards the player with a torque, use cross product to decided clockwise or counterclockwise @@ -26,9 +24,6 @@ use cross product rotation for other mobs? const cross = Matter.Vector.cross(laserStartVector, playerVector) this.torque = 0.00002 * this.inertia * (cross > 0 ? 1 : -1) -block manufacturing - molecular assembler tech -Holding r-click will create a slowly increasing in size block, which will be thrown on release - super-bot: fires super balls tech - only allow 1,2 turrets at time. spawning a new mine removes the oldest mine @@ -1074,6 +1069,7 @@ possible names for tech eternal inflation hypergraph SQUID (for superconducting quantum interference device) is a very sensitive magnetometer used to measure extremely subtle magnetic fields, based on superconducting loops containing Josephson junctions. + Josephson junction - superconducting junction used in SQUIDS and quantum computers nuclear pasta - hard matter in neutron star nonlocal, nonlocality: maybe use for pilot wave fine-tuned universe @@ -1100,6 +1096,7 @@ possible names for tech negative entropy memetics magnetorquers - produce spin by pushing on earth's magnetic field + Josephson junction - superconducting junction ******************************************************** CARS IMAGES ********************************************************