landgreen / n-gon

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

Dojo map (fixed) #139

Closed kgurchiek closed 6 months ago

kgurchiek commented 7 months ago
dojo() { // By weird_pusheen
        simulation.makeTextLog(`<strong>dojo</strong> by <span class='color-var'>werid_pusheen</span>, fixed by <span class='color-var'>Cornbread 2100</span>`)
        const vanishes = [];
        const smoofes = [];
        const leftRotor = level.rotor(-550, 900, 950, 25);
        leftRotor.frictionAir = 0.01;
        var leftSchwoof = level.boost(-20, -60, -2000);
        var rightSchwoof = level.button(2550, -50);
        var rightSchwoofState = false;
        var rightSchwoofLive = true;
        spawn.mapRect(2513, -39, 200, 100);
        var pathPoints = [
            [0, 0], // Index 0 is owned by M and is set to M's position during play
            // this means that occasionally the boss will bonk M on the way to somewhere else, which gives it a chance to hurt M and gives the player a chance to hurt it
            [250, -750], /* Left bases */
            [250, -2500],
            [350, -1500],  // Left doorway
            [1150, -1500], // Home base
            [1150, -2750], // Upper base
            [1950, -1500], // Right doorway
            [2050, -750], /* Right bases */
            [2050, -2500],
            [-150, -250], // Left porthole
        ];
        function isntIn(point, array) {
            for (var x = 0; x < array.length; x++){
                if (point[0] == array[x][0] && point[1] == array[x][1]) {
                    return false;
                }
            }
            return true;
        }
        function isObstructed(v1, v2) {
            var ret = Matter.Query.ray(map,
                {
                    x: v1[0],
                    y: v1[1],
                },
                {
                    x: v2[0],
                    y: v2[1]
                }).length != 0;
            return ret; // Kinda-ish stolen from mob.js
        }
        function pythag(p1, p2) {
            var dx = p1[0] - p2[0];
            var dy = p1[1] - p2[1];
            return Math.sqrt(dx * dx + dy * dy);
        }
        var path = undefined; // This is a stupid way to go about pathfinding code. I might even clean it up!
        function pathFind(goalPoint, startPoint, curPath = []) {
            var myPoint = startPoint;
            if (curPath.length) {
                myPoint = curPath[curPath.length - 1];
            }
            if (path && (curPath.length >= path.length)) { // If we've already found a shorter or equal path, no reason to continue and waste CPU time
                return; // Minimizes for HOP COUNT, not PATH LENGTH - path length was buggy
            }
            if (!isObstructed(myPoint, goalPoint)) { // If the line to the goal point ain't blocked by a map object, we've arrived!
                path = [...curPath];
                path.push(goalPoint);
                return;
            }
            pathPoints.forEach(testPoint => {
                if (isntIn(testPoint, curPath)) { // If it's reusing points, there's clearly something wrong
                    if (!isObstructed(myPoint, testPoint)) { // If the line to the test point ain't blocked by a map object
                        var thing = [...curPath];
                        thing.push(testPoint);
                        pathFind(goalPoint, startPoint, thing); // Branch to a valid test point
                    }
                }
            });
        }
        level.setPosToSpawn(1200, 500);
        level.exit.x = 51500;
        level.exit.y = -1875;
        spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20);
        level.defaultZoom = 1500;
        simulation.zoomTransition(level.defaultZoom)
        document.body.style.backgroundColor = "#d8dadf";

        spawn.mapRect(-500, 0, 3300, 300); // Floor
        spawn.mapRect(-100, -3000, 2500, 100); // Ceiling
        spawn.mapRect(-200, -3000, 100, 2600); // Left wall
        spawn.mapRect(2400, -3000, 100, 3000); // Right wall

        spawn.mapRect(500, -1000, 100, 500); /* obstruction blocks */
        smoofes.push(map[map.length - 1]);
        spawn.mapRect(500, -2500, 100, 500);
        smoofes.push(map[map.length - 1]);
        spawn.mapRect(1700, -1000, 100, 500);
        smoofes.push(map[map.length - 1]);
        spawn.mapRect(1700, -2500, 100, 500);
        smoofes.push(map[map.length - 1]);

        spawn.mapRect(-1000, 550, 200, 50); // Left chonky stepppp low
        spawn.mapRect(-800, 300, 200, 50); // Left chonky stepppp high
        spawn.mapVertex(-1000, 1200, "0 0  100 0  700 500  700 700  0 700"); // Left chonky
        spawn.mapRect(3100, 550, 200, 50); // Right chonky stepppp low
        spawn.mapRect(2900, 300, 200, 50); // Right chonky stepppp high
        spawn.mapVertex(3300, 1200, "0 0  -100 0  -700 500  -700 700  0 700"); // Right chonky
        const leftElevator = level.elevator(-1400 - 300,   1450, 300, 100, 500);
        const rightElevator = level.elevator(-1400 + 5100, 1450, 300, 100, 500);

        spawn.mapRect(-150, -1700, 200, 50);
        spawn.mapRect(400, -2050, 200, 50);
        spawn.mapRect(1600, -1000, 200, 50);

        spawn.randomMob(1200, 700);
        spawn.randomMob(600, 1000);
        spawn.randomMob(1800, 1000);
        spawn.randomMob(3200, 400);
        spawn.randomMob(3000, 200);
        spawn.randomMob(-900, 400);
        spawn.randomMob(-700, 200);
        spawn.randomMob(1200, 1000);
        for (var i = 0; i < 4; i++){
            spawn.randomSmallMob(Math.random() * 600 - 600, Math.random() * 3000 - 400);
        }
        spawn.grenadier(-300, -1000);
        spawn.grenadier(2600, -1000);

        spawn.mapRect(-1400, 1450, 5100, 100); // The True Floor

        const slime = level.hazard(-1250, 1400, 4800, 50);
        slime.maxHeight = 600;
        simulation.draw.body = function() {
            ctx.beginPath();
            for (let i = 0, len = body.length; i < len; ++i) {
                if (!body[i].hidden) {
                    let vertices = body[i].vertices;
                    ctx.moveTo(vertices[0].x, vertices[0].y);
                    for (let j = 1; j < vertices.length; j++) {
                        ctx.lineTo(vertices[j].x, vertices[j].y);
                    }
                    ctx.lineTo(vertices[0].x, vertices[0].y);
                }
            }
            ctx.lineWidth = 2;
            ctx.fillStyle = color.block;
            ctx.fill();
            ctx.strokeStyle = color.blockS;
            ctx.stroke();
        } // Override the old draw code to allow intelligent hiding of blocks - preferably this becomes official code because it's just a single added if statement and makes a lot of things cleaner and more intelligent

        const vanish = function(x, y, width, height) { // normal vanishes don't work well on my map for some reason, so I rewrote
            x += width / 2;
            y += height / 2;
            const getVertices = function (bX, bY, bW, bH) { return [{ x: bX, y: bY, index: 0, isInternal: false }, { x: bX + bW, y: bY, index: 1, isInternal: false }, { x: bX + bW, y: bY + bH, index: 4, isInternal: false }, { x: bX, y: bY + bH, index: 3, isInternal: false }] };
            const cMask = cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet;
            const vertices = getVertices(x, y, width, height);
            const block = body[body.length] = Bodies.fromVertices(x, y, vertices, {
                collisionFilter: {
                    category: cat.map,
                    mask: cMask
                },
                isNoSetCollision: true,
                inertia: Infinity, //prevents rotation
                isNotHoldable: true,
                isNonStick: true, //this keep sporangium from sticking
                isTouched: false,
                cWidth: width,
                hiddenCycle: 0,
                isStatic: true,
                query() {
                    if (this.cWidth <= 0) {
                        if (this.cWidth > -100) {
                            this.cWidth = -100;
                            Matter.Body.setVertices(this, vertices);
                        }
                        this.isTouched = false;
                        this.collisionFilter.mask = undefined;
                        this.hidden = true;
                        this.hiddenCycle++;
                        if (this.hiddenCycle > 100) {
                            if (Matter.Query.collides(this, [player]).length) {
                                this.hiddenCycle = 50;
                            }
                            else {
                                this.hiddenCycle = 0;
                                this.cWidth = width;
                                this.collisionFilter.mask = cMask;
                                this.hidden = false;
                            }
                        }
                    }
                    else if (this.isTouched) {
                        Matter.Body.setVertices(this, getVertices(x, y, this.cWidth, height * (this.cWidth/width)));
                        this.cWidth -= 3;
                    }
                    else if (Matter.Query.collides(this, [player]).length) { // Elseif short circuit avoids expensive collision detection
                        this.isTouched = true;
                    }
                }
            });
            return block;
        };

        vanishes.push(vanish(800, 800, 800, 50));
        vanishes.push(vanish(400, 1100, 400, 50));
        vanishes.push(vanish(1600, 1100, 400, 50));
        for (const vanishBlock of vanishes) Composite.add(engine.world, vanishBlock);
        spawn.bodyRect(1700, 812, 300, 25, 1, {
            collisionFilter: {
                category: cat.body,
                mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet | cat.map
            },
            isNoSetCollision: true,
            isNotHoldable: true,
            isNonStick: true, //this keep sporangium from sticking
            restitution: 1,
            friction: 0,
            frictionAir: 0,
            frictionStatic: 0,
            query() {
                Matter.Body.setAngularVelocity(this, 0);
                Matter.Body.applyForce(this, this.position, {
                    x: 0,
                    y: -(this.position.y - 812) * 0.002
                });
            }
        });
        const zigzag = body[body.length - 1];
        Matter.Body.applyForce(zigzag, zigzag.position, {
            x: 0.1,
            y: 0
        });
        var buttonWasDown = false;
        level.customTopLayer = () => {

        }
        level.custom = () => {
            rightSchwoof.isUp = false;
            level.exit.drawAndCheck();
            leftSchwoof.query();
            level.enter.draw();
            pathPoints[0][0] = m.pos.x;
            pathPoints[0][1] = m.pos.y;
            leftElevator.move();
            rightElevator.move();
            slime.query();
            zigzag.query();
            slime.levelRise(0.2);
            for (var i = 0; i < vanishes.length; i++){
                vanishes[i].query();
            }
            if (!rightSchwoofState) {
                var math = m.pos.y < leftRotor.position.y;
                Matter.Body.setAngularVelocity(leftRotor, (math ? 1 : -1) * Math.PI / 45);
            }
            if (rightSchwoofLive) {
                rightSchwoof.query();
                rightSchwoof.draw();
                if (rightSchwoofState) {
                    ctx.fillStyle = "lightgreen";
                }
                else {
                    ctx.fillStyle = "red";
                }
                ctx.beginPath();
                ctx.arc(2615, -220, 40, 0, Math.PI * 2);
                ctx.fill();
            }
            if (rightSchwoof.isUp) {
                buttonWasDown = true;
            }
            else if (buttonWasDown){
                buttonWasDown = false;
                rightSchwoofState = !rightSchwoofState;
            }
            if (Matter.Query.collides(player, smoofes).length) {
                Matter.Body.applyForce(player, player.position, {
                    x: 0,
                    y: -0.015
                });
            }
        };

        mobs.spawn(500, -500, 10, 100, "yellow"); /* TacticalBoss
            Modes:
                Spawn:
                    Pathfinds to a point above M and starts dropping mobs. Learns which mobs to drop to cause the most damage, of course.
                    Occasionally strikes at M.
                Hide:
                    Pathfinds to the point furthest from M
                Strike:
                    Pathfind really, really fast to M
                Recharge:
                    Stop moving for a bit to "recharge" (this is so the player has a chance to hit it)

            It must always Hide or Recharge after Spawning or Striking. Which one it does is based on some factor I'll figure out.
            Pathfinding is a hypersimplified algorithm with hard-coded "points" that it can travel between. M is one of these.
        */
        var boss = mob[mob.length - 1];
        boss.isBoss = true;
        boss.damageReduction = 0.2 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1)
        boss.onDeath = function () {
            powerUps.spawnBossPowerUp(this.position.x, this.position.y);
            level.exit.x = 2560;
            level.exit.y = -90;
            rightSchwoofLive = false;
        };
        var spawnables = {};
        ["hopper", "stabber", "springer", "striker", "sneaker", "grower"].forEach((m) => { /* Used to be spawn.fullPickList, but some of those mobs don't do collision-only damage and would thus never be properly selected for */
            if (spawn[m]) {
                spawnables[m] = {
                    fun: spawn[m],
                    name: m,
                    weight: 1
                }
            }
        });
        boss.stabCycle = 0;
        boss.spawnCycle = 0;
        function spawny() {
            var totalWeight = 0;
            Object.keys(spawnables).forEach(key => {
                totalWeight += spawnables[key].weight;
            });
            var cursorWeight = 0;
            var choice = Math.random();
            var mC = undefined;
            Object.values(spawnables).forEach((thing) => {
                var lower = cursorWeight / totalWeight;
                cursorWeight += thing.weight;
                var upper = cursorWeight / totalWeight;
                if ((choice > lower && choice <= upper) || !mC) {
                    mC = thing;
                }
            });
            mC.fun(boss.position.x, boss.position.y);
            var sp = mob[mob.length - 1];
            sp.typeName = mC.name;
            sp.onHit = () => {
                spawnables[sp.typeName].weight += 1;
            };
            var oldFun = sp.onDeath;
            sp.onDeath = () => { /* Mobs that die are worth less */
                oldFun.call(sp);
                spawnables[sp.typeName].weight -= 0.3; /* But not too much less */
            };
        }
        boss.spawnDelay = 40;
        boss.mode = "hide";
        boss.modeSwitch = -1; // Randomize mode immediately
        boss.damageReduction = 0.1;
        var oldOnHit = boss.onHit;
        boss.onHit = () => {
            boss.modeSwitch = -1; // After striking the player, always switch modes
            oldOnHit.call(boss);
        };
        boss.do = () => {
            path = undefined;
            var pfGoal = [0, 0];
            boss.modeSwitch--;
            if (boss.modeSwitch < 0) {
                if (!boss.isShielded) {
                    spawn.shield(boss, boss.position.x, boss.position.y, 0.75); // Every time the mode switches, have a 75% chance to gain a new shield
                }
                if (boss.mode == "hide" || boss.mode == "recharge") {
                    if (Math.random() > 0.5) {
                        boss.mode = "spawn"; 
                    }
                    else {
                        boss.mode = "strike";
                    }
                    boss.modeSwitch = 600;
                }
                else {
                    if (boss.mode == "strike") {
                        boss.mode = "hide"; // Always hides after striking
                    }
                    else {
                        if (Math.random() > 0.5) {
                            boss.mode = "hide";
                        }
                        else {
                            boss.mode = "recharge"; // same when it goes into recharge mode
                            spawn.shield(boss, boss.position.x, boss.position.y, 1);
                        }
                    }
                    boss.modeSwitch = 200;
                }
            }
            if (boss.mode == "hide") { /* Find the furthest point from M and get to it */
                var longest = 0;
                pathPoints.forEach(item => {
                    if (item[0] == 1150) {
                        return;
                    }
                    var iL = pythag(item, [m.pos.x, m.pos.y]);
                    if (iL > longest) {
                        longest = iL;
                        pfGoal = item;
                    }
                });
            }
            else if (boss.mode == "strike") {
                pfGoal = pathPoints[0]; // Target M
            }
            else if (boss.mode == "spawn") {
                pfGoal = pathPoints[4]; // Go to Home Base to spawn
            }
            if (boss.mode != "recharge") {
                if (m.pos.x > 2350 || m.pos.x < -150 || m.pos.y > 50) {
                    boss.mode = "hide";
                }
                pathFind(pfGoal, [boss.position.x, boss.position.y]);
                if (!path) {
                    return; // If it couldn't pathfind, just drift
                }
                var goalX = path[0][0];
                var goalY = path[0][1];

                var dX = goalX - boss.position.x;
                var dY = goalY - boss.position.y;
                var hyp = Math.sqrt(dX * dX + dY * dY);
                Matter.Body.applyForce(boss, {
                    x: goalX,
                    y: goalY
                }, {
                    x: dX / hyp * 0.04 * (boss.mode == "strike" ? 2 : 1),
                    y: dY / hyp * 0.04 * (boss.mode == "strike" ? 2 : 1)// - 0.005
                });
            }
            if (boss.mode == "spawn") {
                boss.stabCycle++;
                if (boss.stabCycle > 25) {
                    if (Math.abs(dX) < 200 && dY > 0) {
                        Matter.Body.applyForce(boss, {
                            x: player.position.x,
                            y: player.position.y
                        }, {
                            x: 0,
                            y: 5
                        });
                    }
                    boss.stabCycle = 0;
                }
                boss.spawnCycle++;
                if (boss.spawnCycle > boss.spawnDelay) {
                    spawny();
                    boss.spawnDelay += 4;
                    boss.spawnCycle = 0;
                }
            }
        };
        boss.showHealthBar = true;
        powerUps.addResearchToLevel() //needs to run after mobs are spawned
    },
landgreen commented 6 months ago

ok cool

landgreen commented 6 months ago

I'm getting a bug. Not sure if it matters though.

Screenshot 2024-01-01 at 3 56 31 PM

It occurs every time the boss hits the player. It must have to do with the .call

Screenshot 2024-01-01 at 3 58 33 PM
kgurchiek commented 6 months ago

Are there any tech or anything that changes all mobs' onHit? If not, there isn't a default onHit, so that line could just be removed. If there is any way it could possibly be overwritten, then just change it to if (oldOnHit) oldOnHit.call(boss)