diff --git a/img/dark star.webp b/img/dark star.webp new file mode 100644 index 0000000..ca23c34 Binary files /dev/null and b/img/dark star.webp differ diff --git a/index.html b/index.html index ecc9d7f..22b8a85 100644 --- a/index.html +++ b/index.html @@ -396,7 +396,7 @@ - + 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 ********************************************************