landgreen / n-gon

2-d physics rogue-lite platformer shooter
https://landgreen.github.io/n-gon/
GNU General Public License v3.0
154 stars 225 forks source link

New Community map: crimsonTowers #77

Closed Whyisthisnotavalable closed 1 year ago

Whyisthisnotavalable commented 1 year ago
crimsonTowers() {
    simulation.makeTextLog(`crimsonTowers by Richard0820. Thank you desboot for this video: <a href="https://www.youtube.com/watch?v=hkdY0mDF2SY&feature=youtu.be&ab_channel=DesBoot">Source</a>`)
    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);
        level.chain(x, y, angle, true, length);
    }
    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;

},
landgreen commented 1 year ago

looks great

couple things: I had lag when I used my field. I'm guessing that's because the large number of blocks forming the chains. The evangelion video didn't cause lag and was pretty funny. I had a really hard time climbing at the start, maybe the series of small ledges could be a bit wider. I think you go over the limit of power ups. It's capped at 3 max, but I got 4 from 2 bosses.

landgreen commented 1 year ago

I just noticed the video continues to play after the level ends.

Whyisthisnotavalable commented 1 year ago

Yeah I dunno how to fix that

Whyisthisnotavalable commented 1 year ago

The video is fine, right

landgreen commented 1 year ago

the video is fine as long as it ends at the end of the level

Whyisthisnotavalable commented 1 year ago

if(!level.levels[level.onLevel] == "crimsonTowers") simulation.removeEphemera(this.name);

this doesn't work

landgreen commented 1 year ago
            if (level.levels[level.onLevel] !== "crimsonTowers") simulation.removeEphemera(this.name);

fixed it

Whyisthisnotavalable commented 1 year ago
crimsonTowers() {
    simulation.makeTextLog(`crimsonTowers by Richard0820. Thank you desboot for this video: <a href="https://www.youtube.com/watch?v=hkdY0mDF2SY&feature=youtu.be&ab_channel=DesBoot">Source</a>`)
    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;
},
Whyisthisnotavalable commented 1 year ago

Everything should be fixed here.

landgreen commented 1 year ago

ok I'll add to next patch

For performance I removed // spawnChain(-3000, -30000, -9500, -20400, 291); // spawnChain(3000, -30000, 9500, -20400, 291); since the player never sees those chains and chains are poorly optimized even as bullets. Hope you don't mind.

Whyisthisnotavalable commented 1 year ago

Sure.