From 99bd1c876e747e2c8a267e40672a25583cd578f8 Mon Sep 17 00:00:00 2001 From: landgreen Date: Mon, 21 Aug 2023 21:21:43 -0700 Subject: [PATCH] dark star community map crimsonTowers by Richard0820 new MACHO animation tech: dark star - MACHO is bigger and damages mobs --- img/dark star.webp | Bin 0 -> 32162 bytes index.html | 2 +- js/bullet.js | 13 +- js/level.js | 1145 +++++++++++++++++++++++++++++++++++++++++++- js/spawn.js | 45 +- js/tech.js | 19 + todo.txt | 17 +- 7 files changed, 1207 insertions(+), 34 deletions(-) create mode 100644 img/dark star.webp diff --git a/img/dark star.webp b/img/dark star.webp new file mode 100644 index 0000000000000000000000000000000000000000..ca23c34914626c763c682b6fc78d80c875de5799 GIT binary patch literal 32162 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 - + diff --git a/js/bullet.js b/js/bullet.js index 5170077..a2b2300 100644 --- a/js/bullet.js +++ b/js/bullet.js @@ -2241,7 +2241,7 @@ const b = { ctx.stroke(); ctx.lineJoin = "round" ctx.miterLimit = 10 - ctx.strokeStyle = "#000" + ctx.fillStyle = "#000" ctx.fill(); }, drawString() { @@ -2855,21 +2855,14 @@ const b = { 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 - }; + 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 - }; + 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. diff --git a/js/level.js b/js/level.js index 8f9c58f..2cfcf9b 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", "ace"], + 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", "crimsonTowers"], trainingLevels: ["walk", "crouch", "jump", "hold", "throw", "throwAt", "deflect", "heal", "fire", "nailGun", "shotGun", "superBall", "matterWave", "missile", "stack", "mine", "grenades", "harpoon", "diamagnetism"], levels: [], start() { @@ -34,9 +34,9 @@ const level = { // b.giveGuns("super balls") //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.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") + // requestAnimationFrame(() => { tech.giveTech("MACHO") }); // 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("dark star") // for (let i = 0; i < 1; ++i) tech.giveTech("foam-bot") // for (let i = 0; i < 1; ++i) tech.giveTech("nail-bot") // for (let i = 0; i < 1; ++i) tech.giveTech("sound-bot upgrade") @@ -49,7 +49,8 @@ const level = { // 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.testing(); - // for (let i = 0; i < 1; ++i) spawn.slasher(1900, -500) + + // for (let i = 0; i < 10; ++i) spawn.sniper(1900, -500) // for (let i = 0; i < 1; ++i) spawn.slasher2(1900, -500) // for (let i = 0; i < 1; ++i) spawn.shooterBoss(1900, -2500) // spawn.suckerBoss(1900, -500, 25) @@ -19460,8 +19461,9 @@ const level = { slime2.query(); slime3.query(); slime4.query(); - spawn.mapRect(4873, -2512, 800, 75); - spawn.mapRect(4473, -2212, 800, 75); + + // spawn.mapRect(4873, -2512, 800, 75); + // spawn.mapRect(4473, -2212, 800, 75); //setTimeout(function(){/*YourCode*/},1000); //water falling/flowing effect @@ -28621,6 +28623,1137 @@ const level = { } } }, + crimsonTowers() { + simulation.makeTextLog(`crimsonTowers by Richard0820. Thank you desboot for the video: Source`) + 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(0, -50); + color.map = "crimson"; + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + spawn.mapRect(0, 0, 1, 1); + level.defaultZoom = 1800; + simulation.zoomTransition(level.defaultZoom); + document.body.style.backgroundColor = "#d8dadf"; + const isSus = Math.random() < 0.001; //A very lucky person gets rickrolled + const mediaSource = isSus ? "https://ia801509.us.archive.org/10/items/Rick_Astley_Never_Gonna_Give_You_Up/Rick_Astley_Never_Gonna_Give_You_Up.ogv" : "https://cdn.glitch.me/b559a783-c0cb-4369-92e3-0c0a5556ba01/n-gon%20evangelion%20-%20Made%20with%20Clipchamp%20(8).mp4?v=1692134040246" + let videoContainer; + let video = document.createElement("video"); + video.src = mediaSource; + video.autoPlay = true; + video.loop = true; + video.muted = true; + videoContainer = { + video: video, + ready: true, + }; + video.play(); + const boost1 = level.boost(8835, -3675, 7500); + const boost2 = level.boost(-8935, -3675, 7500); + ace.slash(0, -15000 + 1800); + function Raindrop(minX, minY, maxX, maxY) { + this.x = minX + Math.random() * (maxX - minX); + this.y = minY + Math.random() * (maxY - minY); + this.speed = Math.random() * 5 + 25; + this.length = Math.random() * 20 + 30; + } + function forceField(x, y, width, height) { + return { + min: { x: x, y: y }, + max: { x: x + width, y: y + height }, + width: width, + height: height, + maxHeight: height, + raindrops: [], + drawRaindrop(drop) { + if (Math.sqrt(Math.pow(player.position.x - drop.x, 2) + Math.pow(player.position.y - drop.y, 2)) + Math.PI < 5000) { + ctx.beginPath(); + ctx.moveTo(drop.x, drop.y); + ctx.lineTo(drop.x, drop.y + drop.length); + ctx.strokeStyle = '#00FFFF'; + ctx.lineWidth = 10; + ctx.lineCap = 'butt'; + ctx.stroke(); + } + }, + updateRaindrop(drop) { + drop.y += drop.speed; + if ((Matter.Query.ray(map, { x: drop.x, y: drop.y }, { x: drop.x, y: drop.y - drop.length }).length === 0) == false) { + simulation.drawList.push({ + x: drop.x, + y: drop.y - drop.length, + radius: 10, + color: "rgb(0,100,250,0.3)", + time: 8 + }); + do { + drop.y = this.min.y + this.height * Math.random(); + drop.x = this.min.x + this.width * Math.random(); + } while (drop.x > this.min.x && drop.x < this.max.x && drop.y > this.min.y && drop.y < this.max.y) + } + }, + isOn: true, + query() { + if (this.isOn) { + ctx.fillStyle = `rgba(200, 20, 10, 0.55)` + ctx.fillRect(this.min.x, this.min.y, this.width, this.height) + if (this.height > 0 && Matter.Query.region([player], this).length) { + player.force.y -= 0.015; + m.energy = m.maxEnergy; + } + // if(this.raindrops.length < 300) { // too many (like 900) can cause a little bit of lag minus 5 ~ 10 fps, but it really just depends on your computer + // this.raindrops.push(new Raindrop()); + // } + // for (let i = 0; i < this.raindrops.length; i++) { + // const drop = this.raindrops[i]; + // this.drawRaindrop(drop); + // this.updateRaindrop(drop); + // } + } + }, + } + } + const forceField1 = forceField(-750, -30000, 1500, 20000); + level.custom = () => { + if (player.position.y < -20000) { + level.nextLevel(); + } + forceField1.query(); + boost1.query(); + boost2.query(); + level.exit.drawAndCheck(); + level.enter.draw(); + ctx.beginPath(); + ctx.strokeStyle = "rgba(220, 20, 10, 0.55)"; + ctx.lineWidth = 1500; + ctx.lineJoin = "miter" + ctx.miterLimit = 100; + ctx.moveTo(map[272].vertices[0].x, map[272].vertices[0].y); + for (let i = 0; i < map[272].vertices.length; i++) { + ctx.lineTo(map[272].vertices[i].x, map[272].vertices[i].y); + } + ctx.closePath(); + ctx.stroke(); + }; + let checkVid = () => { + if (simulation.paused && !videoContainer.paused) { + videoContainer.paused = true; + video.pause(); + } else if (!simulation.paused && videoContainer.paused) { + videoContainer.paused = false; + video.play(); + } + requestAnimationFrame(checkVid); + } + checkVid(); + simulation.ephemera.push({ + name: "vid", + do() { + if (level.levels[level.onLevel] !== "crimsonTowers") simulation.removeEphemera(this.name); + if (mediaSource && !isSus) { + ctx.drawImage(videoContainer.video, -1600, -15000, 3200, 1800); + } else if (mediaSource) { + ctx.drawImage(videoContainer.video, -1920 / 2, -15000, 1920, 1080); + } + } + }); + level.customTopLayer = () => { + ctx.fillStyle = "rgba(220, 20, 10, 0.1)"; + ctx.fillRect(-6725, -3500, 475, 2925); + ctx.fillRect(-8725, -3700, 450, 2925); + ctx.fillRect(-4725, -3300, 450, 2925); + ctx.fillRect(-2725, -3100, 450, 2925); + ctx.fillRect(-725, -2900, 450, 2925); + ctx.fillRect(275, -2900, 450, 2925); + ctx.fillRect(2275, -3100, 450, 2925); + ctx.fillRect(4275, -3300, 450, 2925); + ctx.fillRect(6275, -3500, 450, 2925); + ctx.fillRect(8275, -3700, 450, 2925); + }; + spawn.mapRect(-10000, 0, 20000, 2000); + spawn.mapRect(-9050, -3650, 350, 50); + spawn.mapRect(8700, -3650, 350, 50); + spawn.mapRect(-275, -2825, 550, 50); + spawn.mapRect(-225, -500, 450, 50); + spawn.mapRect(-250, -1575, 500, 50); + function spawnTower(index, y = 0) { + const x = index - 1325; + spawn.mapRect(x + 1025, y + -950, 125, 750); + spawn.mapRect(x + 1125, y + -225, 50, 50); + spawn.mapRect(x + 1500, y + -950, 125, 750); + spawn.mapRect(x + 1475, y + -225, 50, 50); + spawn.mapRect(x + 1600, y + -225, 50, 50); + spawn.mapRect(x + 1000, y + -225, 50, 50); + spawn.mapRect(x + 1475, y + -475, 50, 50); + spawn.mapRect(x + 1125, y + -750, 50, 50); + spawn.mapRect(x + 1050, y + -2025, 100, 1125); + spawn.mapRect(x + 1500, y + -2025, 100, 1125); + spawn.mapRect(x + 1475, y + -1050, 50, 50); + spawn.mapRect(x + 1125, y + -1325, 50, 50); + spawn.mapRect(x + 1475, y + -1550, 50, 50); + spawn.mapRect(x + 1125, y + -1875, 50, 50); + spawn.mapRect(x + 1075, y + -2900, 75, 925); + spawn.mapRect(x + 1500, y + -2900, 75, 925); + spawn.mapRect(x + 1475, y + -2150, 50, 50); + spawn.mapRect(x + 1125, y + -2475, 50, 50); + spawn.mapRect(x + 1475, y + -2800, 50, 50); + spawn.mapRect(x + 1000, y + -975, 50, 50); + spawn.mapRect(x + 1025, y + -2050, 50, 50); + spawn.mapRect(x + 1050, y + -2925, 50, 50); + spawn.mapRect(x + 1550, y + -2925, 50, 50); + spawn.mapRect(x + 1600, y + -975, 50, 50); + spawn.mapRect(x + 1575, y + -2050, 50, 50); + for (let i = 0; i < 5; i++) { + if (Math.random() > 0.5) { ace.slasher2(index, y - (i * 500) - 500) } else { ace.slasher3(index, y - (i * 500) - 500) }; + } + } + // ace.slash(0, -5000); + function spawnChain(x, y, x1, y1, length = 39) { + const angle = Math.atan2(y1 - y, x1 - x); + chain(x, y, angle, true, length); + } + function chain(x, y, angle = 0, isAttached = true, len = 15, radius = 20, stiffness = 1, damping = 1) { + const gap = 2 * radius + const unit = { + x: Math.cos(angle), + y: Math.sin(angle) + } + for (let i = 0; i < len; i++) { + bullet[bullet.length] = Bodies.polygon(x + gap * unit.x * i, y + gap * unit.y * i, 12, radius, { + inertia: Infinity, + isNotHoldable: true + }); + const who = bullet[bullet.length - 1]; + who.do = () => { }; + who.collisionFilter.category = cat.body; + who.collisionFilter.mask = cat.player | cat.bullet | cat.body | cat.bullet | cat.bullet | cat.bulletBullet + Composite.add(engine.world, who); //add to world + who.classType = "bullet" + } + for (let i = 1; i < len; i++) { + consBB[consBB.length] = Constraint.create({ + bodyA: bullet[bullet.length - i], + bodyB: bullet[bullet.length - i - 1], + stiffness: stiffness, + damping: damping + }); + Composite.add(engine.world, consBB[consBB.length - 1]); + } + cons[cons.length] = Constraint.create({ + pointA: { + x: x, + y: y + }, + bodyB: bullet[bullet.length - len], + stiffness: 1, + damping: damping + }); + Composite.add(engine.world, cons[cons.length - 1]); + if (isAttached) { + cons[cons.length] = Constraint.create({ + pointA: { + x: x + gap * unit.x * (len - 1), + y: y + gap * unit.y * (len - 1) + }, + bodyB: bullet[bullet.length - 1], + stiffness: 1, + damping: damping + }); + Composite.add(engine.world, cons[cons.length - 1]); + } + } + spawnChain(-2250, -3100, -750, -2900); + spawnChain(-4250, -3300, -2750, -3100); + spawnChain(-6250, -3500, -4750, -3300); + spawnChain(-8250, -3700, -6750, -3500); + spawnChain(750, -2900, 2250, -3100); + spawnChain(2750, -3100, 4250, -3300); + spawnChain(4750, -3300, 6250, -3500); + spawnChain(6750, -3500, 8250, -3700); + // spawnChain(-3000, -30000, -9500, -20400, 291); + // spawnChain(3000, -30000, 9500, -20400, 291); + spawnTower(500); + spawnTower(2500, -200); + spawn.mapRect(2000, -200, 7000, 300); + spawnTower(4500, -400); + spawn.mapRect(4000, -400, 5000, 300); + spawnTower(6500, -600); + spawn.mapRect(6000, -600, 5000, 300); + spawnTower(8500, -800); + spawn.mapRect(8000, -800, 3000, 300); + spawnTower(-500); + spawnTower(-2500, -200); + spawn.mapRect(-10000, -200, 8000, 300); + spawnTower(-4500, -400); + spawn.mapRect(-10000, -400, 6000, 300); + spawnTower(-6500, -600); + spawn.mapRect(-10000, -600, 4000, 300); + spawnTower(-8500, -800); + spawn.mapRect(-10000, -800, 2000, 300); + spawn.mapVertex(10000, -9450, "-1000 0 1000 0 1000 -10000 500 -20000 -500 -20000 -1000 -10000"); + spawn.mapVertex(-10000, -9450, "-1000 0 1000 0 1000 -10000 500 -20000 -500 -20000 -1000 -10000"); + spawn.mapRect(-11000, -675, 2000, 2675); + spawn.mapRect(9000, -675, 2000, 2675); + spawn.mapVertex(0, -30000, "0 0 3000 -10000 6000 0 3000 10000"); + spawn.mapRect(-8750, -10000, 8000, 100); + spawn.mapRect(750, -10000, 8000, 100); + spawn.mapVertex(0, -10020, "-1000 0 -5000 300 5000 300 1000 0"); + spawn.mapRect(-800, -10250, 100, 350); + spawn.mapRect(700, -10250, 100, 350); + const a = 200; + const maxTheta = 10 * Math.PI; + const spiralX = (theta) => a * theta * Math.cos(theta); + const spiralY = (theta) => a * theta * Math.sin(theta); + for (let i = 1; i <= maxTheta; i += 0.2) { + const x = spiralX(i); + const y = spiralY(i) + (-15000 + 1800 / 2); + spawn.mapRect(x, y, 100, 100); + } + level.exit.y = map[272].position.y; + level.exit.x = map[272].position.x; + }, // ******************************************************************************************************** // ******************************************************************************************************** // ***************************************** training levels ********************************************** diff --git a/js/spawn.js b/js/spawn.js index 0dabc8c..b834826 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -150,7 +150,7 @@ const spawn = { me.do = function () { if (!simulation.isTimeSkipping) { const sine = Math.sin(simulation.cycle * 0.015) - this.radius = 370 * (1 + 0.1 * sine) + this.radius = 55 * tech.isDarkStar + 370 * (1 + 0.1 * sine) //chase player const sub = Vector.sub(player.position, this.position) const mag = Vector.magnitude(sub) @@ -188,6 +188,34 @@ const spawn = { ctx.strokeStyle = "#000" ctx.lineWidth = 1; ctx.stroke(); + if (tech.isDarkStar && !m.isCloak) { //&& !m.isBodiesAsleep + ctx.fillStyle = "rgba(10,0,40,0.4)" + ctx.fill() + //damage mobs + for (let i = 0, len = mob.length; i < len; ++i) { + if (mob[i].alive && !mob[i].isShielded) { + if (Vector.magnitude(Vector.sub(this.position, mob[i].position)) - mob[i].radius < this.radius) { + mob[i].damage(0.02 * m.dmgScale); + // mob[i].locatePlayer();// + + simulation.drawList.push({ //add dmg to draw queue + x: mob[i].position.x, + y: mob[i].position.y, + radius: mob[i].radius + 8, + color: `rgba(10,0,40,0.1)`, // random hue, but not red + time: 4 + }); + } + } + } + } + //draw growing and fading out ring around the arc + ctx.beginPath(); + const rate = 150 + const r = simulation.cycle % rate + ctx.arc(this.position.x, this.position.y, 15 + this.radius + 0.3 * r, 0, 2 * Math.PI); + ctx.strokeStyle = `rgba(0,0,0,${0.5 * Math.max(0, 1 - 1.4 * r / rate)})` + ctx.stroke(); } } }, @@ -3889,10 +3917,7 @@ const spawn = { me.frictionStatic = 0; me.friction = 0; me.lookTorque = 0.0000055 * (Math.random() > 0.5 ? -1 : 1) * (1 + 0.1 * Math.sqrt(simulation.difficulty)) - me.fireDir = { - x: 0, - y: 0 - } + me.fireDir = { x: 0, y: 0 } Matter.Body.setDensity(me, 0.01); //extra dense //normal is 0.001 //makes effective life much larger spawn.shield(me, x, y, 1); spawn.spawnOrbitals(me, radius + 200 + 300 * Math.random()) @@ -5627,7 +5652,7 @@ const spawn = { mobs.spawn(x, y, 6, radius, "rgb(180,199,245)"); let me = mob[mob.length - 1]; Matter.Body.rotate(me, 2 * Math.PI * Math.random()); - me.accelMag = 0.0009 * simulation.accelScale; + me.accelMag = 0.001 * simulation.accelScale; me.torqueMagnitude = -0.000012 * me.inertia //* (Math.random() > 0.5 ? -1 : 1); me.frictionStatic = 0; me.friction = 0; @@ -5636,7 +5661,7 @@ const spawn = { me.cd = 0; me.swordRadius = 0; me.swordVertex = 1 - me.swordRadiusMax = 275 + 3.5 * simulation.difficulty; + me.swordRadiusMax = 320 + 3.6 * simulation.difficulty; me.swordRadiusGrowRate = me.swordRadiusMax * (0.011 + 0.0002 * simulation.difficulty) me.isSlashing = false; me.swordDamage = 0.03 * simulation.dmgScale @@ -5744,7 +5769,7 @@ const spawn = { mobs.spawn(x, y, sides, radius, "rgb(180,215,235)"); let me = mob[mob.length - 1]; Matter.Body.rotate(me, 2 * Math.PI * Math.random()); - me.accelMag = 0.0005 * simulation.accelScale; + me.accelMag = 0.00055 * simulation.accelScale; me.frictionStatic = 0; me.friction = 0; me.frictionAir = 0.02; @@ -5754,11 +5779,11 @@ const spawn = { me.swordVertex = 1 me.swordRadiusInitial = radius / 2; me.swordRadius = me.swordRadiusInitial; - me.swordRadiusMax = 750 + 6 * simulation.difficulty; + me.swordRadiusMax = 800 + 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.swordDamage = 0.03 * simulation.dmgScale me.laserAngle = 3 * Math.PI / 5 const seeDistance2 = me.swordRadiusMax * me.swordRadiusMax spawn.shield(me, x, y); diff --git a/js/tech.js b/js/tech.js index c173744..4c05f0f 100644 --- a/js/tech.js +++ b/js/tech.js @@ -2487,6 +2487,24 @@ const tech = { tech.isAxion = false } }, + { + name: "dark star", + description: "mobs inside the MACHO are damaged
increase MACHO radius by 15%", + maxCount: 1, + count: 0, + frequency: 2, + frequencyDefault: 2, + allowed() { + return tech.isMACHO + }, + requires: "MACHO", + effect() { + tech.isDarkStar = true + }, + remove() { + tech.isDarkStar = false + } + }, { name: "ablative drones", description: "after losing health there is a chance
to rebuild your broken parts as drones", @@ -11677,6 +11695,7 @@ const tech = { isRewindField: null, isCrouchRegen: null, isAxion: null, + isDarkStar: null, isWormholeMapIgnore: null, isLessDamageReduction: null, needleTunnel: null, diff --git a/todo.txt b/todo.txt index b457b04..baabcab 100644 --- a/todo.txt +++ b/todo.txt @@ -1,12 +1,10 @@ ******************************************************** NEXT PATCH ************************************************** -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 +community map crimsonTowers by Richard0820 -inflation: 85->90% defense, 300->200% larger blocks -buckling: can spawn boosts or coupling in addition to heals, ammo, and research +new MACHO animation + +tech: dark star - MACHO is bigger and damages mobs *********************************************************** TODO ***************************************************** @@ -23,6 +21,10 @@ more (all) bosses need to be made of parts "sneakBoss" could get sneaker adds after each hide phase "laserTargetingBoss" could have close range stabbers constrained, a few long range shooters, and a few laser shooters +defense power up - a short term defense boost, like the ones for damage. + Or maybe it would last until you take one hit. + Or last until you lost a total of 20 health. + 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 @@ -1104,7 +1106,8 @@ possible names for tech memetics magnetorquers - produce spin by pushing on earth's magnetic field Josephson junction - superconducting junction - + Pyroelectricity - voltage from temp changes - upgrade from piezoelectricity + dark star - upgrade to WIMPs ******************************************************** CARS IMAGES ********************************************************