From cea1c64c6e4057a6e9ec1a9d84b56f1d4eec61e1 Mon Sep 17 00:00:00 2001 From: landgreen Date: Tue, 24 Sep 2024 19:42:12 -0700 Subject: [PATCH] testChamber2 new level testChamber2 New camera flip effect new laser level element now has collisions with blocks elevators are less deadly to mobs at low speeds difficulty level progression reworked no constraints on final boss new constraint - healing disabled quenching 0.3->0.4x overheal converted to max health tungsten carbide 400->500 extra max health paradigm shift's health loss is no longer reduced by damage taken reduction coherence no longer remembers tech that is set to zero frequency, like removed tech JUNK tech: pet the bot - lets you pet your bots JUNK tech: the upside down - flip everything bug prevented possible duplicate choices with coherence tech fixed issues with showing and hiding health bars on that constraint fixed crash from autonomous defense mass-energy mode wasn't getting any benefit from damage taken reduction it now gets square root of damage taken reduction --- js/engine.js | 7 +- js/index.js | 43 +-- js/level.js | 879 ++++++++++++++++++++++++++++++++++++++++------- js/lore.js | 4 - js/mob.js | 3 +- js/player.js | 12 +- js/powerup.js | 184 +++++----- js/simulation.js | 142 +++++--- js/spawn.js | 18 +- js/tech.js | 75 +++- style.css | 20 +- todo.txt | 112 +++--- 12 files changed, 1126 insertions(+), 373 deletions(-) diff --git a/js/engine.js b/js/engine.js index 5a8f544..c60ec97 100644 --- a/js/engine.js +++ b/js/engine.js @@ -162,8 +162,7 @@ function collisionChecks(event) { const maxCount = 10 + 3 * tech.extraHarpoons //scale the number of hooks fired let count = maxCount - 1 const angle = Math.atan2(mob[k].position.y - player.position.y, mob[k].position.x - player.position.x); - - const mass = 0.75 * (tech.isLargeHarpoon ? 1 + 0.05 * Math.sqrt(this.ammo) : 1) + const mass = 0.75 * ((tech.isLargeHarpoon) ? 1 + Math.min(0.05 * Math.sqrt(b.guns[9].ammo), 10) : 1) b.harpoon(m.pos, mob[k], angle, mass, true, 7) // harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true, thrust = 0.1) { bullet[bullet.length - 1].drain = 0 for (; count > 0; count--) { @@ -222,9 +221,9 @@ function collisionChecks(event) { return; } //mob + body collisions - if (obj.classType === "body" && obj.speed > 6) { + if (obj.classType === "body" && obj.speed > 9) { const v = Vector.magnitude(Vector.sub(mob[k].velocity, obj.velocity)); - if (v > 9) { + if (v > 11) { if (tech.blockDmg) { //electricity Matter.Body.setVelocity(mob[k], { x: 0.5 * mob[k].velocity.x, y: 0.5 * mob[k].velocity.y }); if (tech.isBlockRadiation && !mob[k].isShielded && !mob[k].isMobBullet) { diff --git a/js/index.js b/js/index.js index ad9d3fd..b5d8f87 100644 --- a/js/index.js +++ b/js/index.js @@ -324,6 +324,11 @@ function setupCanvas() { ctx.lineJoin = "round"; ctx.lineCap = "round"; simulation.setZoom(); + + if (simulation.isInvertedVertical) { + ctx.translate(0, canvas.height); // Move the origin down to the bottom + ctx.scale(1, -1); // Flip vertically + } } setupCanvas(); window.onresize = () => { @@ -519,16 +524,17 @@ ${simulation.isCheating ? "

lore disabled" : ""}
difficulty parameters
- ${simulation.difficultyMode > 0 ? `
0.87x damage, 1.2x damage taken per level
+1 boss on each level
` : " "} - ${simulation.difficultyMode > 1 ? `
more mob per level
faster mobs per level
` : " "} - ${simulation.difficultyMode > 2 ? `
0.87x damage, 1.2x damage taken per level
+1 random constraint on each level
` : " "} - ${simulation.difficultyMode > 3 ? `
+1 boss on each level
bosses spawn 1 fewer ${powerUps.orb.tech()}
` : " "} - ${simulation.difficultyMode > 4 ? `
0.87x damage, 1.2x damage taken per level
+1 random constraint on each level
` : " "} + ${simulation.difficultyMode > 0 ? `
0.85x damage per level
1.25x damage taken per level
` : " "} + ${simulation.difficultyMode > 1 ? `
spawn more mobs
mobs move faster
` : " "} + ${simulation.difficultyMode > 2 ? `
spawn a 2nd boss each level
bosses spawn 0.5x power ups
` : " "} + ${simulation.difficultyMode > 3 ? `
0.85x damage per level
1.25x damage taken per level
` : " "} + ${simulation.difficultyMode > 4 ? `
+1 random constraint each level
fewer initial power ups
` : " "} ${simulation.difficultyMode > 5 ? `
0.5x initial damage
2x initial damage taken
` : " "} + ${simulation.difficultyMode > 6 ? `
+1 random constraint each level
fewer ${powerUps.orb.tech()} spawn
` : " "}
-${simulation.difficultyMode > 2 ? `
active constraints
${level.constraintDescription1}
${level.constraintDescription2}
` : ""} - ` +${simulation.difficultyMode > 4 ? `
active constraints
${level.constraintDescription1}
${level.constraintDescription2}
` : ""} +` if (!localSettings.isHideHUD) text += `
console log @@ -754,7 +760,7 @@ ${simulation.difficultyMode > 2 ? `
2 ? `
2 ? `
{ - // simulation.difficultyMode = Number(document.getElementById("difficulty-select-experiment").value) - // lore.setTechGoal() - // localSettings.difficultyMode = Number(document.getElementById("difficulty-select-experiment").value) - // document.getElementById("difficulty-select").value = document.getElementById("difficulty-select-experiment").value - // if (localSettings.isAllowed) localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage - // }); //add tooltips for (let i = 0, len = tech.tech.length; i < len; i++) { if (document.getElementById(`tech-${i}`)) { @@ -1066,8 +1063,8 @@ ${simulation.difficultyMode > 2 ? `
{ +function mouseMoveDefault(e) { simulation.mouse.x = e.clientX; simulation.mouse.y = e.clientY; +} +let mouseMove = mouseMoveDefault +document.body.addEventListener("mousemove", (e) => { + mouseMove(e) }); document.body.addEventListener("mouseup", (e) => { @@ -1835,7 +1836,7 @@ if (localSettings.isAllowed && !localSettings.isEmpty) { document.getElementById("hide-hud").checked = localSettings.isHideHUD if (localSettings.difficultyCompleted === undefined) { - localSettings.difficultyCompleted = [null, false, false, false, false, false, false] //null because there isn't a difficulty zero + localSettings.difficultyCompleted = [null, false, false, false, false, false, false, false] //null because there isn't a difficulty zero localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage } @@ -1857,7 +1858,7 @@ if (localSettings.isAllowed && !localSettings.isEmpty) { isJunkExperiment: false, isCommunityMaps: false, difficultyMode: '2', - difficultyCompleted: [null, false, false, false, false, false, false], + difficultyCompleted: [null, false, false, false, false, false, false, false], fpsCapDefault: 'max', runCount: 0, isTrainingNotAttempted: true, diff --git a/js/level.js b/js/level.js index d990d5b..3824917 100644 --- a/js/level.js +++ b/js/level.js @@ -9,7 +9,7 @@ const level = { onLevel: -1, levelsCleared: 0, //see level.populateLevels: (initial, ... , reservoir or factory, reactor, ... , subway, final) added later - playableLevels: ["labs", "rooftops", "skyscrapers", "warehouse", "highrise", "office", "aerie", "satellite", "sewers", "testChamber", "pavilion", "lock", "towers", "flocculation"], + playableLevels: ["labs", "rooftops", "skyscrapers", "warehouse", "highrise", "office", "aerie", "satellite", "sewers", "testChamber", "pavilion", "lock", "towers", "flocculation", "testChamber2"], communityLevels: ["gauntlet", "stronghold", "basement", "crossfire", "vats", "run", "ngon", "house", "perplex", "coliseum", "tunnel", "islands", "temple", "dripp", "biohazard", "stereoMadness", "yingYang", "staircase", "fortress", "commandeer", "clock", "buttonbutton", "downpour", "superNgonBros", "underpass", "cantilever", "tlinat", "ruins", "ace", "crimsonTowers", "LaunchSite", "shipwreck", "unchartedCave", "dojo", "arena", "soft", "flappyGon", "rings", "trial"], trainingLevels: ["walk", "crouch", "jump", "hold", "throw", "throwAt", "deflect", "heal", "fire", "nailGun", "shotGun", "superBall", "matterWave", "missile", "stack", "mine", "grenades", "harpoon"], levels: [], @@ -19,7 +19,7 @@ const level = { // simulation.isHorizontalFlipped = true // spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns // spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns - // level.levelsCleared = 11 + // level.levelsCleared = 10 // level.updateDifficulty() // tech.giveTech("performance") // m.maxHealth = m.health = 1//00000000 @@ -30,7 +30,7 @@ const level = { // tech.tech[297].frequency = 100 // tech.addJunkTechToPool(0.5) // m.couplingChange(10) - // m.setField("grappling hook") //1 standing wave 2 perfect diamagnetism 3 negative mass 4 molecular assembler 5 plasma torch 6 time dilation 7 metamaterial cloaking 8 pilot wave 9 wormhole 10 grappling hook + // m.setField("time dilation") //1 standing wave 2 perfect diamagnetism 3 negative mass 4 molecular assembler 5 plasma torch 6 time dilation 7 metamaterial cloaking 8 pilot wave 9 wormhole 10 grappling hook // m.energy = 0 // powerUps.research.count = 3 // tech.isHookWire = true @@ -45,9 +45,9 @@ const level = { // b.guns[8].ammo = 100000000 // requestAnimationFrame(() => { tech.giveTech("stimulated emission") }); - // tech.giveTech("Hilbert space") + // tech.giveTech("mass-energy equivalence") // tech.addJunkTechToPool(0.5) - // for (let i = 0; i < 1; ++i) tech.giveTech("coherence") + // for (let i = 0; i < 1; ++i) tech.giveTech("the upside down") // for (let i = 0; i < 1; ++i) tech.giveTech("nitinol") // m.skin.egg(); @@ -59,12 +59,13 @@ const level = { // for (let i = 0; i < 4; i++) powerUps.directSpawn(450, -50, "tech"); // for (let i = 0; i < 7; i++) powerUps.directSpawn(m.pos.x + 200, m.pos.y - 250, "research", false); // spawn.bodyRect(575, -700, 150, 150); //block mob line of site on testing - // level.subway(); + // level.testChamber2(); level[simulation.isTraining ? "walk" : "initial"]() //normal starting level ************************************************** + // for (let i = 0; i < 1; ++i) spawn.revolutionBoss(1900, -500) - // for (let i = 0; i < 3; i++) spawn.starter(1900, -500) //ghosters need to spawn after the map loads + // for (let i = 0; i < 1; i++) spawn.starter(1900, -500, 20) // for (let i = 0; i < 1; ++i) powerUps.directSpawn(m.pos.x + 50 * Math.random(), m.pos.y + 50 * Math.random(), "entanglement"); // for (let i = 0; i < 2; ++i) powerUps.directSpawn(m.pos.x + 450, m.pos.y + 50 * Math.random(), "boost"); @@ -89,7 +90,7 @@ const level = { // tech.giveTech("tinker"); //show junk tech in experiment mode // m.storeTech() // powerUps.spawn(m.pos.x, m.pos.y, "entanglement", false); - // for (let i = 0; i < 6; i++) localSettings.difficultyCompleted[i] = false + // for (let i = 0; i < 4; i++) localSettings.difficultyCompleted[i] = true // localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage } else { spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns @@ -101,6 +102,7 @@ const level = { if (localSettings.isAllowed) localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage } } + setupCanvas() simulation.setupCamera(player.position); simulation.setZoom(); level.addToWorld(); //add bodies to game engine @@ -147,7 +149,7 @@ const level = { //grow and get bright document.getElementById("right-HUD-constraint").style.opacity = 1 document.getElementById("right-HUD-constraint").style.fontSize = "23px" - document.getElementById("right-HUD-constraint").style.top = simulation.difficultyMode > 4 ? "6px" : "9px" + document.getElementById("right-HUD-constraint").style.top = simulation.difficultyMode > 6 ? "6px" : "9px" setTimeout(() => { if (m.alive) { //fade to background @@ -158,7 +160,6 @@ const level = { }, 5000); }); } - }, newLevelOrPhase() { //runs on each new level but also on final boss phases //used for generalist and pigeonhole principle @@ -256,20 +257,13 @@ const level = { updateDifficulty() { simulation.difficulty = level.levelsCleared * simulation.difficultyMode if (simulation.isTraining) simulation.difficulty = 1 - - let scale = 1 - if (simulation.difficultyMode > 3) { - scale = 3 - } else if (simulation.difficultyMode > 1) { - scale = 2 - } - m.dmgScale = Math.pow(0.87, level.levelsCleared * scale) - simulation.dmgScale = Math.max(0.1, 0.22 * level.levelsCleared * scale) //damage done by mobs scales with total levels //a bigger number means the player takes more damage - if (simulation.difficultyMode === 6) { + const scale = simulation.difficultyMode > 3 ? 2 : 1 + m.dmgScale = Math.pow(0.85, level.levelsCleared * scale) + simulation.dmgScale = Math.max(0.1, 0.25 * level.levelsCleared * scale) //damage done by mobs scales with total levels //a bigger number means the player takes more damage + if (simulation.difficultyMode > 5) { m.dmgScale *= 0.5 simulation.dmgScale *= 2 } - simulation.healScale = 1 / (1 + simulation.difficulty * 0.043) //a higher denominator makes for lower heals // m.health += heal * simulation.healScale; if (simulation.difficultyMode === 1) { simulation.accelScale = 1.1 @@ -288,28 +282,21 @@ const level = { level.constraint[i].remove() possible.push(i) } - if (level.levels[level.onLevel] !== "null" && level.levels[level.onLevel] !== "initial" && !simulation.isTraining && m.alive && level.levelsCleared) { - if (simulation.difficultyMode > 2 && possible.length) { + if (level.levels[level.onLevel] !== "final" && level.levels[level.onLevel] !== "null" && level.levels[level.onLevel] !== "initial" && !simulation.isTraining && m.alive && level.levelsCleared) { + if (simulation.difficultyMode > 4 && possible.length) { //choose a random constraint from possible array and remove it from that array - // const index = possible[Math.floor(possible.length * Math.random())] - // const index = level.constraintIndex - // level.constraintIndex = 0 //REMOVE THIS FROM LIVE GAME, FOR TESTING ONLY!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // level.constraintIndex = 0 //REMOVE THIS FROM LIVE GAME level.constraint[level.constraintIndex].effect() possible.splice(level.constraintIndex, 1) //generate text to describe the active constraints for the pause menu level.constraintDescription1 = level.constraint[level.constraintIndex].description - // simulation.inGameConsole(`${level.constraint[level.constraintIndex].description}`, 900); - level.constraintIndex++ if (level.constraintIndex > level.constraint.length - 1) level.constraintIndex = 0 - if (simulation.difficultyMode > 4 && possible.length) { - // const index = possible[Math.floor(possible.length * Math.random())] + if (simulation.difficultyMode > 6 && possible.length) { level.constraint[level.constraintIndex].effect() possible.splice(level.constraintIndex, 1) level.constraintDescription2 += level.constraint[level.constraintIndex].description - // simulation.inGameConsole(`${level.constraint[level.constraintIndex].description}`, 900); - level.constraintIndex++ if (level.constraintIndex > level.constraint.length - 1) level.constraintIndex = 0 } @@ -322,7 +309,7 @@ const level = { } //update HUD with constraints let text = `${level.constraintDescription1}` - if (simulation.difficultyMode > 4 && level.constraintDescription2) { + if (simulation.difficultyMode > 6 && level.constraintDescription2) { text += `
${level.constraintDescription2}` } document.getElementById("right-HUD-constraint").innerHTML = text @@ -339,6 +326,15 @@ const level = { constraintDescription1: "", //used in pause menu and console constraintDescription2: "", constraint: [ + { + description: "healing disabled", + effect() { + level.isNoHeal = true + }, + remove() { + level.isNoHeal = false + } + }, { description: "no pause while choosing", effect() { @@ -359,8 +355,13 @@ const level = { remove() { mobs.healthBar = mobs.defaultHealthBar level.isHideHealth = false - document.getElementById("health").style.display = "inline" - document.getElementById("health-bg").style.display = "inline" + if (tech.isEnergyHealth) { + document.getElementById("health").style.display = "none" + document.getElementById("health-bg").style.display = "none" + } else if (!level.isHideHealth) { + document.getElementById("health").style.display = "inline" + document.getElementById("health-bg").style.display = "inline" + } } }, { @@ -592,6 +593,7 @@ const level = { isReducedRegen: 1, isHideHealth: false, isNoPause: false, + isNoHeal: false, levelAnnounce() { const cheating = simulation.isCheating ? "(testing)" : "" if (level.levelsCleared === 0) { @@ -807,6 +809,28 @@ const level = { } level.exit.x = -level.exit.x - 100 //minus the 100 because of the width of the graphic }, + flipVertical() { + const flipY = (who) => { + for (let i = 0, len = who.length; i < len; i++) { + Matter.Body.setPosition(who[i], { x: who[i].position.x, y: -who[i].position.y - player.position.y }) + } + } + flipY(map) + flipY(body) + flipY(mob) + flipY(powerUp) + Matter.Body.setPosition(player, { x: player.position.x, y: -2 * player.position.y }) + + // for (let i = 0, len = cons.length; i < len; i++) { + // cons[i].pointA.x *= -1 + // cons[i].pointB.x *= -1 + // } + // for (let i = 0, len = consBB.length; i < len; i++) { + // consBB[i].pointA.x *= -1 + // consBB[i].pointB.x *= -1 + // } + // level.exit.x = -level.exit.x - 100 //minus the 100 because of the width of the graphic + }, exitCount: 0, setPosToSpawn(xPos, yPos) { m.spawnPos.x = m.pos.x = xPos; @@ -1011,6 +1035,7 @@ const level = { x += width / 2 y += height / 2 const who = body[body.length] = Bodies.rectangle(x, y, width, height, { + isRotor: true, collisionFilter: { category: cat.body, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet @@ -1020,7 +1045,7 @@ const level = { friction: 1, frictionStatic: 1, restitution: 0, - rotationForce: rotationForce + rotationForce: rotationForce, }); Matter.Body.setAngle(who, angle) Matter.Body.setAngularVelocity(who, angularVelocity); @@ -1215,14 +1240,8 @@ const level = { this.isUp = false this.frictionAir = friction.down //adds a hard jerk at the top of vertical motion because it's fun - Matter.Body.setPosition(this, { - x: this.holdX, - y: maxHeight - }); - Matter.Body.setVelocity(this, { - x: 0, - y: 0 - }); + Matter.Body.setPosition(this, { x: this.holdX, y: maxHeight }); + Matter.Body.setVelocity(this, { x: 0, y: 0 }); } } else if (this.position.y + 10 * this.velocity.y > y) { //free falling down, with only air friction Matter.Body.setVelocity(this, { //slow down early to avoid a jerky stop that can pass through blocks @@ -1238,21 +1257,12 @@ const level = { } //edge limits if (this.position.y < maxHeight) { - Matter.Body.setPosition(this, { - x: this.holdX, - y: maxHeight - }); + Matter.Body.setPosition(this, { x: this.holdX, y: maxHeight }); } else if (this.position.y > y) { - Matter.Body.setPosition(this, { - x: this.holdX, - y: y - }); + Matter.Body.setPosition(this, { x: this.holdX, y: y }); } // hold horizontal position - Matter.Body.setPosition(this, { - x: this.holdX, - y: this.position.y - }); + Matter.Body.setPosition(this, { x: this.holdX, y: this.position.y }); }, moveOnTouch() { if (!m.isBodiesAsleep) { @@ -1538,9 +1548,13 @@ const level = { }, } }, - button(x, y, width = 126, isSpawnBase = true) { + button(x, y, width = 126, isSpawnBase = true, isInvertedVertical = false) { if (isSpawnBase) { - spawn.mapVertex(x + 65, y + 2, "100 10 -100 10 -70 -10 70 -10"); + if (isInvertedVertical) { + spawn.mapVertex(x + 65, y - 3, "100 -10 -100 -10 -70 10 70 10"); + } else { + spawn.mapVertex(x + 65, y + 2, "100 10 -100 10 -70 -10 70 -10"); + } map[map.length - 1].restitution = 0; map[map.length - 1].friction = 1; map[map.length - 1].frictionStatic = 1; @@ -1548,63 +1562,124 @@ const level = { // const buttonSensor = Bodies.rectangle(x + 35, y - 1, 70, 20, { // isSensor: true // }); - - return { - isUp: false, - min: { - x: x + 2, - y: y - 11 - }, - max: { - x: x + width, - y: y - 10 - }, - width: width, - height: 20, - query() { - if (Matter.Query.region(body, this).length === 0 && Matter.Query.region([player], this).length === 0) { - this.isUp = true; - } else { - if (this.isUp === true) { - const list = Matter.Query.region(body, this) //are any blocks colliding with this - if (list.length > 0) { - if (list[0].bounds.max.x - list[0].bounds.min.x < 150 && list[0].bounds.max.y - list[0].bounds.min.y < 150) { //not too big of a block - Matter.Body.setPosition(list[0], { //teleport block to the center of the button - x: this.min.x + width / 2, - y: list[0].position.y - }) + if (isInvertedVertical) { + return { + isUp: false, + min: { + x: x + 2, + y: y - 1 + }, + max: { + x: x + width, + y: y + }, + width: width, + height: 20, + query() { + if (Matter.Query.region(body, this).length === 0 && Matter.Query.region([player], this).length === 0) { + this.isUp = true; + } else { + if (this.isUp === true) { + const list = Matter.Query.region(body, this) //are any blocks colliding with this + if (list.length > 0) { + if (list[0].bounds.max.x - list[0].bounds.min.x < 150 && list[0].bounds.max.y - list[0].bounds.min.y < 150) { //not too big of a block + Matter.Body.setPosition(list[0], { //teleport block to the center of the button + x: this.min.x + width / 2, + y: list[0].position.y + }) + } + Matter.Body.setVelocity(list[0], { x: 0, y: 0 }); } - Matter.Body.setVelocity(list[0], { x: 0, y: 0 }); } + this.isUp = false; } - this.isUp = false; - } - }, - query() { - if (Matter.Query.region(body, this).length === 0 && Matter.Query.region([player], this).length === 0) { - this.isUp = true; - } else { - if (this.isUp === true) { - const list = Matter.Query.region(body, this) //are any blocks colliding with this - if (list.length > 0) { - if (list[0].bounds.max.x - list[0].bounds.min.x < 150 && list[0].bounds.max.y - list[0].bounds.min.y < 150) { //not too big of a block - Matter.Body.setPosition(list[0], { //teleport block to the center of the button - x: this.min.x + width / 2, - y: list[0].position.y - }) + }, + query() { + if (Matter.Query.region(body, this).length === 0 && Matter.Query.region([player], this).length === 0) { + this.isUp = true; + } else { + if (this.isUp === true) { + const list = Matter.Query.region(body, this) //are any blocks colliding with this + if (list.length > 0) { + if (list[0].bounds.max.x - list[0].bounds.min.x < 150 && list[0].bounds.max.y - list[0].bounds.min.y < 150) { //not too big of a block + Matter.Body.setPosition(list[0], { //teleport block to the center of the button + x: this.min.x + width / 2, + y: list[0].position.y + }) + } + Matter.Body.setVelocity(list[0], { x: 0, y: 0 }); } - Matter.Body.setVelocity(list[0], { x: 0, y: 0 }); } + this.isUp = false; + } + }, + draw() { + ctx.fillStyle = "hsl(0, 100%, 70%)" + if (this.isUp) { + ctx.fillRect(this.min.x, this.min.y, this.width, 20) + } else { + ctx.fillRect(this.min.x, this.min.y - 12, this.width, 25) } - this.isUp = false; } - }, - draw() { - ctx.fillStyle = "hsl(0, 100%, 70%)" - if (this.isUp) { - ctx.fillRect(this.min.x, this.min.y - 10, this.width, 20) - } else { - ctx.fillRect(this.min.x, this.min.y - 3, this.width, 25) + } + } else { + return { + isUp: false, + min: { + x: x + 2, + y: y - 11 + }, + max: { + x: x + width, + y: y - 10 + }, + width: width, + height: 20, + query() { + if (Matter.Query.region(body, this).length === 0 && Matter.Query.region([player], this).length === 0) { + this.isUp = true; + } else { + if (this.isUp === true) { + const list = Matter.Query.region(body, this) //are any blocks colliding with this + if (list.length > 0) { + if (list[0].bounds.max.x - list[0].bounds.min.x < 150 && list[0].bounds.max.y - list[0].bounds.min.y < 150) { //not too big of a block + Matter.Body.setPosition(list[0], { //teleport block to the center of the button + x: this.min.x + width / 2, + y: list[0].position.y + }) + } + Matter.Body.setVelocity(list[0], { x: 0, y: 0 }); + } + } + this.isUp = false; + } + }, + query() { + if (Matter.Query.region(body, this).length === 0 && Matter.Query.region([player], this).length === 0) { + this.isUp = true; + } else { + if (this.isUp === true) { + const list = Matter.Query.region(body, this) //are any blocks colliding with this + if (list.length > 0) { + if (list[0].bounds.max.x - list[0].bounds.min.x < 150 && list[0].bounds.max.y - list[0].bounds.min.y < 150) { //not too big of a block + Matter.Body.setPosition(list[0], { //teleport block to the center of the button + x: this.min.x + width / 2, + y: list[0].position.y + }) + } + Matter.Body.setVelocity(list[0], { x: 0, y: 0 }); + } + } + this.isUp = false; + } + }, + draw() { + ctx.fillStyle = "hsl(0, 100%, 70%)" + if (this.isUp) { + ctx.fillRect(this.min.x, this.min.y - 10, this.width, 20) + } else { + ctx.fillRect(this.min.x, this.min.y - 3, this.width, 25) + } } } } @@ -2110,6 +2185,46 @@ const level = { } } }, + laser(p1, p2, damage = 0.14, color = "#f00") { + return { + isOn: true, + position: p1, + look: p2, + color: color, + query() { + let best = { x: null, y: null, dist2: Infinity, who: null, v1: null, v2: null } + best = vertexCollision(this.position, this.look, m.isCloak ? [map, body] : [map, body, [playerBody, playerHead]]); + // hitting player + if ((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 + const dmg = damage * simulation.dmgScale; + m.damage(dmg); + simulation.drawList.push({ //add dmg to draw queue + x: best.x, + y: best.y, + radius: dmg * 1500, + color: "rgba(255,0,0,0.5)", + time: 20 + }); + } + //draw + if (best.dist2 === Infinity) best = this.look; + ctx.beginPath(); + ctx.moveTo(this.position.x, this.position.y); + ctx.lineTo(best.x, best.y); + ctx.strokeStyle = this.color; + ctx.lineWidth = 5; + ctx.setLineDash([50 + 200 * Math.random(), 50 * Math.random()]); + ctx.stroke(); + ctx.setLineDash([]); + + // ctx.beginPath(); + // ctx.arc(this.position.x, this.position.y, 3, 0, 2 * Math.PI); + // ctx.fillStyle = this.color;; + // ctx.fill(); + }, + } + }, isHazardRise: false, hazard(x, y, width, height, damage = 0.002) { return { @@ -2764,7 +2879,7 @@ const level = { powerUps.spawn(2095 + 20 * (Math.random() - 0.5), -2060, "research", false); powerUps.spawn(2095 + 20 * (Math.random() - 0.5), -2120, "research", false); powerUps.spawn(2095 + 20 * (Math.random() - 0.5), -2075, "research", false); - } else if (simulation.difficultyMode === 6) { + } else if (simulation.difficultyMode > 4) { } else { powerUps.spawn(2095 + 20 * (Math.random() - 0.5), -2300, "heal", false); @@ -2783,7 +2898,7 @@ const level = { }, initial() { if (level.levelsCleared === 0) { //if this is the 1st level of the game - if (simulation.difficultyMode > 2) spawn.setSpawnList() // hard and why difficulty don't begin with starter mobs + if (simulation.difficultyMode > 2) spawn.setSpawnList() // don't begin with starter mobs level.initialPowerUps() if (level.levelsCleared === 0) powerUps.directSpawn(-60, -950, "difficulty", false); @@ -2817,6 +2932,7 @@ const level = { }, 2000 + 500 * i); } } + const wires = new Path2D() //pre-draw the complex lighting path to save processing wires.moveTo(-150, -275) wires.lineTo(80, -275) @@ -2913,7 +3029,16 @@ const level = { //push around power ups stuck in the tube wall if (!(simulation.cycle % 30)) { for (let i = 0, len = powerUp.length; i < len; i++) { - if (powerUp[i].position.y < -1000) powerUp[i].force.x += 0.01 * (Math.random() - 0.5) * powerUp[i].mass + if (powerUp[i].name === "instructions") { + if (simulation.isCheating) { + Matter.Composite.remove(engine.world, powerUp[i]); + powerUp.splice(i, 1); + break + } + } else if (powerUp[i].position.y < -1000) { + powerUp[i].force.x += 0.01 * (Math.random() - 0.5) * powerUp[i].mass + } + } } //draw binary number @@ -2971,8 +3096,10 @@ const level = { //draw shade for ceiling tech ctx.fillStyle = "rgba(68, 68, 68,0.95)" ctx.fillRect(2030, -2800, 150, 1800); - ctx.fillStyle = "rgba(68, 68, 68,0.95)" ctx.fillRect(2030, 0, 150, 1800); + ctx.fillStyle = "rgba(68, 68, 68,0.98)" + // ctx.fillRect(-2750, -300, 2600, 125); + ctx.fillRect(-2925, -2800, 2775, 2650); }; level.setPosToSpawn(460, -100); //normal spawn // level.enter.x = -1000000; //hide enter graphic for first level by moving to the far left @@ -2984,16 +3111,23 @@ const level = { simulation.zoomTransition(level.defaultZoom, 1) document.body.style.backgroundColor = "#e1e1e1"; - spawn.mapRect(-2750, -2800, 2600, 4600); //left wall - spawn.mapRect(3000, -2800, 2600, 4600); //right wall + // spawn.mapRect(-2750, -2800, 2600, 4600); //left wall + spawn.mapRect(-2750, -2800, 2600, 2515); + spawn.mapRect(-3275, -185, 3125, 1985); + powerUps.directSpawn(-2315, -3050, "instructions", false); + spawn.mapRect(-3275, -2800, 400, 3250); + spawn.mapRect(-2775, -575, 50, 25); + spawn.mapRect(-2775, -950, 50, 25); + spawn.mapRect(-2775, -1325, 50, 25); + spawn.mapRect(-2775, -1700, 50, 25); + spawn.mapRect(-2775, -2075, 50, 25); + spawn.mapRect(-2775, -2450, 50, 25); + spawn.mapRect(3000, -2800, 2600, 4600); //right wall // spawn.mapRect(-250, 0, 3600, 1800); //ground spawn.mapRect(-250, 0, 2300, 1800); //ground - Matter.Body.setVelocity(map[map.length - 1], { - x: 10, - y: -10 - }); + // Matter.Body.setVelocity(map[map.length - 1], { x: 10, y: -10 }); spawn.mapRect(2150, 0, 1200, 1800); //ground spawn.mapRect(2025, -3, 25, 15); //lip on power up chamber spawn.mapRect(2150, -3, 25, 15); //lip on power up chamber @@ -3015,7 +3149,7 @@ const level = { spawn.bodyRect(2425, -120, 70, 50); spawn.bodyRect(2400, -100, 100, 60); - spawn.bodyRect(2500, -150, 100, 150); //exit step + spawn.bodyRect(2500, -150, 100, 130); //exit step }, final() { // color.map = "rgba(0,0,0,0.8)" @@ -3046,7 +3180,7 @@ const level = { document.body.style.backgroundColor = "#ddd"; for (let i = 0; i < 16; i++) powerUps.spawn(4600 + 40 * i, -30, "ammo"); - if (simulation.difficultyMode > 4) for (let i = 0; i < 8; i++) powerUps.spawn(4600 + 40 * i, -30, "ammo"); //extra ammo on why difficulty + if (simulation.difficultyMode > 5) for (let i = 0; i < 8; i++) powerUps.spawn(4600 + 40 * i, -30, "ammo"); //extra ammo on why difficulty spawn.mapRect(-1950, 0, 8200, 1800); //ground spawn.mapRect(-1950, -1500, 1800, 1900); //left wall @@ -3302,7 +3436,7 @@ const level = { if (isExitOpen) { level.exit.x = x - 50; level.exit.y = -260; - if (simulation.difficultyMode < 6) powerUps.spawn(level.exit.x, level.exit.y - 100, "tech"); + if (simulation.difficultyMode < 7) powerUps.spawn(level.exit.x, level.exit.y - 100, "tech"); } else { var gateButton = level.button(x - 62, -237, 125, false) //x, y, width = 126, isSpawnBase = true gateButton.isUp = true @@ -4704,7 +4838,7 @@ const level = { // powerUps.spawnBossPowerUp(-3600, -100) powerUps.spawn(-3650, -50, "tech") powerUps.spawn(-3650, -150, "tech") - if (simulation.difficultyMode < 6) powerUps.spawn(-3650, -300, "tech") + if (simulation.difficultyMode < 7) powerUps.spawn(-3650, -300, "tech") } } }; @@ -4754,7 +4888,7 @@ const level = { isDoorsLocked = true for (let i = 0; i < 9; ++i) powerUps.spawn(1200 + 550 * Math.random(), -1700, "ammo") for (let i = 0; i < 3; ++i) powerUps.spawn(1200 + 550 * Math.random(), -1700, "heal"); - if (simulation.difficultyMode > 4) for (let i = 0; i < 8; i++) powerUps.spawn(1200 + 550 * Math.random(), -1700, "ammo"); //extra ammo on why difficulty + if (simulation.difficultyMode > 5) for (let i = 0; i < 8; i++) powerUps.spawn(1200 + 550 * Math.random(), -1700, "ammo"); //extra ammo on why difficulty const scale = Math.pow(simulation.difficulty, 0.7) //hard around 30, why around 54 if (mobs.mobDeaths < level.levelsCleared && !simulation.isCheating) { for (let i = 0; i < 250; i++) spawn.starter(300 + 2400 * Math.random(), -1300 - 500 * Math.random()) @@ -5459,7 +5593,7 @@ const level = { spawn.bodyRect(x + 500, y - 100, 125, 100, 0.25); spawn.bodyRect(x + 200, y - 150, 100, 150, 0.25); spawn.bodyRect(x + 1075, y - 1075, 100, 125, 0.25); - const density = 0.0015 //+ (simulation.difficultyMode < 5 ? 0.0035 : 0) + const density = 0.0015 const angle = Math.PI / 2 const variance = 0 //Math.PI const frictionAir = 0.03 @@ -5513,7 +5647,7 @@ const level = { spawn.bodyRect(x + 1025, y + -50, 50, 50); if (Math.random() > 0.5) { - const density = 0.0012 //+ (simulation.difficultyMode < 5 ? 0.003 : 0) + const density = 0.0012 const angle = Math.PI / 2 const variance = 0.2 //Math.PI const frictionAir = 0.015 @@ -5539,7 +5673,7 @@ const level = { } ) } else { - const density = 0.001 //+ (simulation.difficultyMode < 5 ? 0.003 : 0) + const density = 0.001 const angle = Math.PI / 2 const variance = Math.PI const frictionAir = 0.015 @@ -6891,6 +7025,499 @@ const level = { } }, + testChamber2() { + mobs.maxMobBody = 20 //normally 40, but set to 10 to avoid too much clutter + simulation.fallHeight = 4000 + level.announceMobTypes() + level.setPosToSpawn(-1825, 1950); //lower start + level.exit.x = -1875 + level.exit.y = 1355 + level.defaultZoom = 2300 + simulation.zoomTransition(level.defaultZoom) + document.body.style.backgroundColor = "#d0d5d5"; + color.map = "#444" + + let buttons = [] + let lasers = [] + let balance = [] + let isFlipped = false; + let isFlipping = false; + let isSpawned = false + const flipAnimationCycles = 120 + + let elevator = body[body.length] = Bodies.rectangle(800, 500, 300, 50, { + collisionFilter: { + category: cat.body, //cat.map, + mask: cat.player | cat.body | cat.bullet | cat.mob | cat.mobBullet //| cat.powerUp + }, + density: 0.1, + inertia: Infinity, //prevents rotation + isNotHoldable: true, + friction: 1, + frictionStatic: 1, + restitution: 0, + frictionAir: 1, + classType: "body", + holdX: 1762, + maxHeight: -1600, + minHeight: 100, + verticalForce: 0.03, + isUp: false, + drag: 0.01, + move() { + this.force.y -= this.mass * simulation.g; //undo gravity + if (!m.isBodiesAsleep) { + if (isFlipped) { + ctx.fillStyle = "#ccc" + ctx.fillRect(this.holdX, -this.maxHeight, 5, this.maxHeight - this.minHeight) //draw path + + if (elevator.isUp) { + elevator.force.y += elevator.verticalForce * elevator.mass + if (elevator.position.y > -elevator.maxHeight) { + elevator.isUp = false + Matter.Body.setPosition(elevator, { x: elevator.holdX, y: -elevator.maxHeight }); + Matter.Body.setVelocity(elevator, { x: 0, y: 0 }); + } + } else { + elevator.force.y -= (elevator.verticalForce) * elevator.mass + if (elevator.position.y < -elevator.minHeight) { + elevator.isUp = true + Matter.Body.setPosition(elevator, { x: elevator.holdX, y: -elevator.minHeight }); + Matter.Body.setVelocity(elevator, { x: 0, y: 0 }); + } + } + //vertical position limits + if (this.position.y > -elevator.maxHeight) { + Matter.Body.setPosition(elevator, { x: elevator.holdX, y: -elevator.maxHeight }); + } else if (this.position.y < -elevator.minHeight) { + Matter.Body.setPosition(elevator, { x: elevator.holdX, y: -elevator.minHeight }); + } + } else { + ctx.fillStyle = "#ccc" + ctx.fillRect(this.holdX, this.maxHeight, 5, this.minHeight - this.maxHeight) //draw path + + if (elevator.isUp) { + elevator.force.y -= elevator.verticalForce * elevator.mass + if (elevator.position.y < elevator.maxHeight) { + elevator.isUp = false + Matter.Body.setPosition(elevator, { x: elevator.holdX, y: elevator.maxHeight }); + Matter.Body.setVelocity(elevator, { x: 0, y: 0 }); + } + } else { + elevator.force.y += (elevator.verticalForce) * elevator.mass + if (elevator.position.y > elevator.minHeight) { + elevator.isUp = true + Matter.Body.setPosition(elevator, { x: elevator.holdX, y: elevator.minHeight }); + Matter.Body.setVelocity(elevator, { x: 0, y: 0 }); + } + } + //vertical position limits + if (this.position.y < elevator.maxHeight) { + Matter.Body.setPosition(elevator, { x: elevator.holdX, y: elevator.maxHeight }); + } else if (this.position.y > elevator.minHeight) { + Matter.Body.setPosition(elevator, { x: elevator.holdX, y: elevator.minHeight }); + } + } + } + Matter.Body.setVelocity(elevator, { x: 0, y: elevator.velocity.y * this.drag }); //zero horizontal velocity and drag + Matter.Body.setPosition(elevator, { x: elevator.holdX, y: elevator.position.y }); //hold horizontal position + }, + }); + Composite.add(engine.world, elevator); //add to world + + let buildMapOutline = function () { + //boxes center on zero,zero with deep walls to hide background + spawn.mapRect(2000, -2000, 2000, 4000); //right map wall + spawn.mapRect(-4000, -2000, 2000, 4000); //left map wall + spawn.mapRect(-4000, -5000, 8000, 3000); //map ceiling + spawn.mapRect(-4000, 2000, 8000, 3000); //floor + } + let buildNormalMap = function () { + buttons.push(level.button(-1895, -1600)) + buttons[buttons.length - 1].isUp = false + spawn.mapRect(-1675, -2025, 50, 250); + + simulation.ephemera.push({ + name: "buttons up", + count: flipAnimationCycles, + do() { + this.count-- + if (this.count < 0) { + for (let i = 0; i < buttons.length; i++) buttons[i].isUp = true + simulation.removeEphemera(this.name); + isFlipping = false + } + }, + }) + + lasers.push(level.laser({ x: -1100, y: 1990 }, { x: -1100, y: -2000 })) ////x, y, width, height, damage = 0.002) + spawn.mapRect(-1112, 1990, 25, 25); //laser entrance + lasers.push(level.laser({ x: -600, y: 1990 }, { x: -600, y: -2000 })) ////x, y, width, height, damage = 0.002) + spawn.mapRect(-612, 1990, 25, 25); //laser entrance + lasers.push(level.laser({ x: -100, y: 1990 }, { x: -100, y: -2000 })) ////x, y, width, height, damage = 0.002) + spawn.mapRect(-112, 1990, 25, 25); //laser entrance + + balance.push(level.rotor(-1250, 1700, 400, 25, 0.01, 0, 0.5)) //balance(x, y, width, height, density = 0.001, angle = 0, frictionAir = 0.001, angularVelocity = 0, rotationForce = 0.0005) { + balance.push(level.rotor(-750, 1700, 400, 25, 0.01, Math.PI / 2, 0.5)) + balance.push(level.rotor(-275, 1650, 550, 32, 0.01, 0, 0.5)) + + //left side + //level entrance + spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); + spawn.mapRect(level.exit.x, level.exit.y - 40, 100, 20); + spawn.mapRect(-2025, 1650, 400, 50); + spawn.mapRect(-2100, -1600, 475, 2925); + spawn.mapRect(-1675, 1500, 50, 325); + + // spawn.mapRect(-1625, 365, 500, 950); + // spawn.mapRect(-1075, 390, 450, 900); + // spawn.mapRect(-575, 415, 500, 850); + + spawn.mapVertex(-1375, 835, "-250 -475 0 -500 250 -475 250 475 -250 475"); + spawn.mapVertex(-850, 840, "-225 -475 0 -500 225 -475 225 475 -225 475"); + spawn.mapVertex(-350, 840, "-225 -475 0 -500 225 -475 225 475 -225 475"); + + + //lower right side + //far right wall ledges + spawn.mapRect(1925, -1700, 200, 200); + spawn.mapRect(1925, -1200, 200, 200); + spawn.mapRect(1925, -700, 200, 200); + spawn.mapRect(1925, -200, 200, 200); + spawn.mapRect(1925, 300, 200, 200); + spawn.mapRect(1925, 800, 200, 200); + spawn.mapRect(1925, 1300, 200, 200); + + spawn.mapRect(1400, 1650, 200, 25); + spawn.mapRect(1400, 1125, 200, 25); + spawn.mapRect(1400, 600, 200, 25); + spawn.mapRect(1400, 75, 200, 25); + + spawn.mapRect(650, 1287, 475, 50); + spawn.mapRect(650, 800, 500, 125); + spawn.mapRect(650, 150, 500, 225); + spawn.mapRect(350, 1300, 800, 375); + spawn.mapRect(350, 950, 150, 400); + spawn.mapRect(175, 950, 325, 100); + spawn.mapRect(150, 525, 350, 200); + spawn.mapRect(150, 75, 350, 200); + spawn.mapRect(475, 1987, 550, 50); + + //ceiling zone + spawn.mapRect(1200, -1625, 400, 25); + spawn.mapRect(-75, -1625, 1075, 25); + spawn.mapRect(-575, -1625, 450, 25); + spawn.mapRect(-1075, -1850, 450, 25); + spawn.mapRect(325, -1825, 575, 25); + spawn.mapRect(-1075, -1425, 450, 25); + spawn.mapRect(-1675, -1588, 550, 25); + + } + let buildVerticalFLippedMap = function () { // flip Y with this -> spawn.mapRect(x, -y - h, w, h); + buttons.push(level.button(-1895, 1600, 126, true, true)) + buttons[buttons.length - 1].isUp = false + spawn.mapRect(-1675, 2025 - 250, 50, 250); + + simulation.ephemera.push({ + name: "buttons up", + count: flipAnimationCycles, + do() { + this.count-- + if (this.count < 0) { + for (let i = 0; i < buttons.length; i++) buttons[i].isUp = true + simulation.removeEphemera(this.name); + isFlipping = false + } + }, + }) + + lasers.push(level.laser({ x: -1100, y: -1990 }, { x: -1100, y: 2000 })) ////x, y, width, height, damage = 0.002) + spawn.mapRect(-1112, -1990 - 25, 25, 25); //laser entrance + lasers.push(level.laser({ x: -600, y: -1990 }, { x: -600, y: 2000 })) ////x, y, width, height, damage = 0.002) + spawn.mapRect(-612, -1990 - 25, 25, 25); //laser entrance + lasers.push(level.laser({ x: -100, y: -1990 }, { x: -100, y: 2000 })) ////x, y, width, height, damage = 0.002) + spawn.mapRect(-112, -1990 - 25, 25, 25); //laser entrance + + balance.push(level.rotor(-1250, -1700 - 25, 400, 25, 0.01, 0, 0.5)) //balance(x, y, width, height, density = 0.001, angle = 0, frictionAir = 0.001, angularVelocity = 0, rotationForce = 0.0005) { + balance.push(level.rotor(-750, -1700 - 25, 400, 25, 0.01, Math.PI / 2, 0.5)) + balance.push(level.rotor(-250, -1650 - 32, 500, 32, 0.01, 0, 0.5)) + + //left side + //level entrance + spawn.mapRect(level.enter.x, level.enter.y - 20 - 20, 100, 20); + spawn.mapRect(level.exit.x, level.exit.y + 40 - 20, 100, 20); + spawn.mapRect(-2025, -1650 - 50, 400, 50); + spawn.mapRect(-2100, +1600 - 2925, 475, 2925); + spawn.mapRect(-1675, -1500 - 325, 50, 325); + + spawn.mapVertex(-1375, -835, "-250 -475 250 -475 250 475 0 500 -250 475"); + spawn.mapVertex(-850, -835, "-225 -475 225 -475 225 475 0 500 -225 475"); + spawn.mapVertex(-350, -835, "-225 -475 225 -475 225 475 0 500 -225 475"); + + //far right wall ledges + spawn.mapRect(1925, 1700 - 200, 200, 200); + spawn.mapRect(1925, 1200 - 200, 200, 200); + spawn.mapRect(1925, 700 - 200, 200, 200); + spawn.mapRect(1925, 200 - 200, 200, 200); + spawn.mapRect(1925, -300 - 200, 200, 200); + spawn.mapRect(1925, -800 - 200, 200, 200); + spawn.mapRect(1925, -1300 - 200, 200, 200); + + spawn.mapRect(1400, -1650 - 25, 200, 25); + spawn.mapRect(1400, -1125 - 25, 200, 25); + spawn.mapRect(1400, -600 - 25, 200, 25); + spawn.mapRect(1400, -75 - 25, 200, 25); + + spawn.mapRect(650, -1287 - 50, 475, 50); + spawn.mapRect(650, -800 - 125, 500, 125); + spawn.mapRect(650, -150 - 225, 500, 225); + spawn.mapRect(350, -1300 - 375, 800, 375); + spawn.mapRect(350, -950 - 400, 150, 400); + spawn.mapRect(175, -950 - 100, 325, 100); + spawn.mapRect(150, -525 - 200, 350, 200); + spawn.mapRect(150, -75 - 200, 350, 200); + spawn.mapRect(475, -1987 - 50, 550, 50); + + //ceiling zone + spawn.mapRect(1200, 1625 - 25, 400, 25); + spawn.mapRect(-75, 1625 - 25, 1075, 25); + spawn.mapRect(-575, 1625 - 25, 450, 25); + spawn.mapRect(-1075, 1850 - 25, 450, 25); + spawn.mapRect(325, 1825 - 25, 575, 25); + spawn.mapRect(-1075, 1425 - 25, 450, 25); + spawn.mapRect(-1675, 1588 - 25, 550, 25); + } + let flipAndRemove = function () { + simulation.translatePlayerAndCamera({ x: player.position.x, y: -player.position.y }) + level.enter.y = -level.enter.y + level.exit.y = -level.exit.y + for (let i = body.length - 1; i > -1; i--) { + if (body[i].isRotor) body.splice(i, 1); + } + + function removeAll(array) { + for (let i = 0; i < array.length; ++i) Matter.Composite.remove(engine.world, array[i]); + } + removeAll(map); + map = []; + removeAll(balance); + balance = [] + removeAll(buttons); + buttons = [] + lasers = [] + + function invertVertical(array) { + for (let i = 0; i < array.length; ++i) { + Matter.Body.setPosition(array[i], { x: array[i].position.x, y: -array[i].position.y }) + } + } + invertVertical(body); + invertVertical(powerUp); + invertVertical(bullet); + invertVertical(mob); + //fields + if (m.fieldMode === 9 && m.hole.isOn) { + m.hole.pos1.y *= -1 + m.hole.pos2.y *= -1 + } else if (m.fieldMode === 2) { + m.fieldPosition.y *= -1 + m.fieldAngle *= -1 + } + //history + for (let i = 0; i < m.history.length; i++) { + m.history[i].position.y *= -1 + m.history[i].angle *= -1 + m.history[i].velocity.y *= -1 + } + //stun to wipe history of all mobs, so they don't get confused about player position vertical swap + for (let i = 0; i < mob.length; i++) mobs.statusStun(mob[i], 1) + } + buildMapOutline() + buildNormalMap() + level.custom = () => { + elevator.move() + for (let i = 0; i < buttons.length; i++) { + buttons[i].draw() + if (buttons[i].isUp && !isFlipping) { + buttons[i].query(); + if (!buttons[i].isUp) { + isFlipping = true + if (isFlipped) { + const normalMap = function () { + isFlipped = false + flipAndRemove() + buildMapOutline() + buildNormalMap(); //rewrite flipped version of map + simulation.draw.setPaths() //update map graphics + level.addToWorld() + } + simulation.unFlipCameraVertical(flipAnimationCycles, normalMap) + } else { + const flipMap = function () { + isFlipped = true + flipAndRemove() + buildMapOutline() + buildVerticalFLippedMap(); //rewrite flipped version of map + simulation.draw.setPaths() //update map graphics + level.addToWorld() + if (!isSpawned) { + isSpawned = true + //spawn second wave of flipped mobs only once + spawn.randomMob(-1500, -1425, 0); + spawn.randomMob(-950, -1425, 0); + spawn.randomMob(-800, -1475, 0.1); + spawn.randomMob(-425, -1425, 0.1); + spawn.randomMob(850, -1750, 0.2); + spawn.randomMob(325, -850, 0.2); + spawn.randomMob(400, -400, 0.3); + spawn.randomMob(825, -475, 0.3); + spawn.randomMob(875, -1050, 0.4); + spawn.randomMob(1425, 1425, 0.5); + spawn.randomMob(675, 1450, 0.7); + spawn.randomMob(225, 1475, 0.9); + spawn.randomMob(-275, 1425, 1); + spawn.randomMob(-800, 1375, 1); + + spawn.secondaryBossChance(700, 1100) + } + } + simulation.flipCameraVertical(flipAnimationCycles, flipMap) + } + break + } + } + } + + if (isFlipped) { + //background structure + ctx.fillStyle = "#c3c7c7" + ctx.fillRect(1487, -75 - 1925, 25, 1925); + ctx.fillRect(1925, -2050, 125, 4100); + + //exit room + ctx.fillStyle = "#d4f4f4" + ctx.fillRect(-2000, -1325 - 350, 375, 350) + level.exit.drawAndCheck(); + //draw flipped entrance + // ctx.translate(0, -3940) + ctx.beginPath(); + ctx.moveTo(level.enter.x, level.enter.y - 30); + ctx.lineTo(level.enter.x, level.enter.y + 80); + ctx.bezierCurveTo(level.enter.x, level.enter.y + 170, level.enter.x + 100, level.enter.y + 170, level.enter.x + 100, level.enter.y + 80); + ctx.lineTo(level.enter.x + 100, level.enter.y - 30); + ctx.lineTo(level.enter.x, level.enter.y - 30); + ctx.fillStyle = "#ccc"; + ctx.fill(); + // ctx.translate(0, 3940) + } else { + //background structure + ctx.fillStyle = "#c5c9c9" + ctx.fillRect(1487, 75, 25, 1925); + ctx.fillRect(1925, -2050, 125, 4100); + + //draw flipped exit + ctx.fillStyle = "#d4f4f4" + ctx.fillRect(-2000, 1325, 375, 350) + ctx.beginPath(); + ctx.moveTo(level.exit.x, level.exit.y - 30); + ctx.lineTo(level.exit.x, level.exit.y + 80); + ctx.bezierCurveTo(level.exit.x, level.exit.y + 170, level.exit.x + 100, level.exit.y + 170, level.exit.x + 100, level.exit.y + 80); + ctx.lineTo(level.exit.x + 100, level.exit.y - 30); + ctx.lineTo(level.exit.x, level.exit.y - 30); + ctx.fillStyle = "#0ff"; + ctx.fill(); + level.enter.draw(); + } + }; + level.customTopLayer = () => { + for (let i = 0; i < lasers.length; i++) lasers[i].query() + ctx.fillStyle = "#233" //balances center dot + ctx.beginPath(); + for (let i = 0; i < balance.length; i++) { + ctx.moveTo(balance[i].center.x, balance[i].center.y) + ctx.arc(balance[i].center.x, balance[i].center.y, 9, 0, 2 * Math.PI); + //rotor spins and stops at vertical and horizontal angles + if ((simulation.cycle % 90) < 15) { + balance[i].torque = 0.0002 * balance[i].inertia + } else if (Math.floor(10 * (balance[i].angle % (Math.PI / 2))) === 0) { + Matter.Body.setAngularVelocity(balance[i], balance[i].angularVelocity * 0.1) + } + } + ctx.fill(); + ctx.fillStyle = `rgba(255,255,255,${0 + 0.3 * Math.random()})` //balances center dot + if (isFlipped) { + ctx.fillRect(-2025, 2025 - 450, 400, 450); + //shadows + ctx.fillStyle = "rgba(0,0,0,0.08)" + ctx.fillRect(175, -250 - 725, 325, 725); + ctx.fillRect(650, -350 - 975, 475, 975); + ctx.fillRect(375, -1650 - 400, 750, 400); + //ceiling + ctx.fillStyle = "rgba(0,0,0,0.04)" + ctx.fillRect(1225, 2025 - 425, 350, 425); + ctx.fillRect(-50, 2025 - 425, 1025, 425); + ctx.fillRect(-550, 2025 - 425, 400, 425); + ctx.fillRect(-1050, 2025 - 625, 400, 625); + ctx.fillRect(-1625, 2025 - 450, 475, 450); + } else { + ctx.fillRect(-2025, -2025, 400, 450); + //shadows + ctx.fillStyle = "rgba(0,0,0,0.08)" + ctx.fillRect(175, 250, 325, 725); + ctx.fillRect(650, 350, 475, 975); + ctx.fillRect(375, 1650, 750, 400); + //ceiling + ctx.fillStyle = "rgba(0,0,0,0.04)" + ctx.fillRect(1225, -2025, 350, 425); + ctx.fillRect(-50, -2025, 1025, 425); + ctx.fillRect(-550, -2025, 400, 425); + ctx.fillRect(-1050, -2025, 400, 625); + ctx.fillRect(-1625, -2025, 475, 450); + } + }; + + spawn.bodyRect(-1662, 1325, 25, 175); + spawn.bodyRect(-1662, 1825, 25, 175); + // spawn.bodyRect(-1662, -1825, 25, 225); + + spawn.bodyRect(1900, 1875, 100, 125, 0.5); + spawn.bodyRect(400, 1925, 225, 50, 0.1); + spawn.bodyRect(950, 750, 75, 50, 0.1); + spawn.bodyRect(200, -25, 150, 100, 0.1); + spawn.bodyRect(300, 900, 75, 50, 0.1); + spawn.bodyRect(1475, 1025, 100, 100, 0.1); + spawn.bodyRect(250, 450, 75, 75, 0.1); + spawn.bodyRect(775, 75, 75, 75, 0.1); + spawn.bodyRect(1200, 1900, 125, 100, 0.1); + + spawn.randomMob(125, -1900, 0); + spawn.randomMob(-375, -1875, 0); + spawn.randomMob(-1350, -1750, 0); + spawn.randomMob(-875, -1575, 0); + spawn.randomMob(500, -1875, 0); + spawn.randomMob(350, 825, 0.1); + spawn.randomMob(375, 400, 0.1); + spawn.randomMob(1500, -25, 0.2); + spawn.randomMob(650, -1950, 0.3); + spawn.randomMob(775, 700, 0.3); + spawn.randomMob(275, -50, 0.4); + spawn.randomMob(75, -1750, 0.5); + spawn.randomMob(1750, -1425, 0.5); + spawn.randomMob(950, 50, 0.6); + spawn.randomMob(-1375, 175, 0.6); + spawn.randomMob(-350, 175, 0.7); + spawn.randomMob(725, 1175, 0.7); + spawn.randomMob(-850, -1950, 0.8); + spawn.randomMob(-1400, -1725, 0.9); + spawn.randomMob(1400, -1700, 0.9); + spawn.randomMob(-800, 200, 0.9); + spawn.randomMob(1475, 1550, 1); + spawn.randomMob(1475, 500, 1); + + powerUps.spawnStartingPowerUps(-875, -1925); + spawn.randomLevelBoss(-875, -200); + powerUps.addResearchToLevel() //needs to run after mobs are spawned + }, lock() { level.announceMobTypes() level.setPosToSpawn(0, -65); //lower start diff --git a/js/lore.js b/js/lore.js index 16bccb3..9278246 100644 --- a/js/lore.js +++ b/js/lore.js @@ -358,7 +358,6 @@ const lore = { setInterval(() => { if (Math.random() < 0.5) { spawn[spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]](1000 * (Math.random() - 0.5), -500 + 200 * (Math.random() - 0.5)); - // level.difficultyIncrease(simulation.difficultyMode) } else { spawn.randomLevelBoss(500 * (Math.random() - 0.5), -500 + 200 * (Math.random() - 0.5)) } @@ -693,9 +692,6 @@ const lore = { setInterval(() => { spawn[spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]](1000 * (Math.random() - 0.5), -500 + 200 * (Math.random() - 0.5)); }, 500); //every 1/2 seconds - // setInterval(() => { - // level.difficultyIncrease(simulation.difficultyMode) - // }, 5000); //every 5 seconds }, () => { lore.talkingColor = "#dff"; diff --git a/js/mob.js b/js/mob.js index 71ca03d..2826db4 100644 --- a/js/mob.js +++ b/js/mob.js @@ -1146,6 +1146,7 @@ const mobs = { // ctx.stroke(); // }, leaveBody: true, + maxMobBody: 40, isDropPowerUp: true, death() { if (tech.collidePowerUps && this.isDropPowerUp) powerUps.randomize(this.position) //needs to run before onDeath spawns power ups @@ -1406,7 +1407,7 @@ const mobs = { //replace dead mob with a regular body replace(i) { //if there are too many bodies don't turn into blocks to help performance - if (this.leaveBody && body.length < 40 && this.mass < 200 && this.radius > 18) { + if (this.leaveBody && body.length < mobs.maxMobBody && this.mass < 200 && this.radius > 18) { let v = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) //might help with vertex collision issue, not sure if (v.length > 5 && body.length < 35 && Math.random() < 0.25) { const cutPoint = 3 + Math.floor((v.length - 6) * Math.random()) //Math.floor(v.length / 2) diff --git a/js/player.js b/js/player.js index 6b0cb91..6398a3e 100644 --- a/js/player.js +++ b/js/player.js @@ -528,7 +528,7 @@ const m = { // } }, addHealth(heal) { - if (!tech.isEnergyHealth) { + if (!tech.isEnergyHealth && !level.isNoHeal) { m.health += heal * simulation.healScale; if (m.health > m.maxHealth) m.health = m.maxHealth; m.displayHealth(); @@ -536,7 +536,7 @@ const m = { }, baseHealth: 1, setMaxHealth(isMessage) { - m.maxHealth = m.baseHealth + tech.extraMaxHealth + 4 * tech.isFallingDamage + m.maxHealth = m.baseHealth + tech.extraMaxHealth + 5 * tech.isFallingDamage if (level.isReducedHealth) { level.reducedHealthLost = Math.max(0, m.health - m.maxHealth * 0.5) m.maxHealth *= 0.5 @@ -683,7 +683,7 @@ const m = { } }, collisionImmuneCycles: 30, - damage(dmg) { + damage(dmg, isDefense = true) { if (tech.isRewindAvoidDeath && (m.energy + 0.05) > Math.min(0.95, m.maxEnergy) && dmg > 0.01) { const steps = Math.floor(Math.min(299, 150 * m.energy)) simulation.inGameConsole(`m.rewind(${steps})`) @@ -701,6 +701,7 @@ const m = { } } if (tech.isEnergyHealth) { + if (isDefense) dmg *= Math.pow(m.defense(), 0.5) m.energy -= 0.9 * dmg / Math.sqrt(simulation.healScale) //scale damage with heal reduction difficulty if (m.energy < 0 || isNaN(m.energy)) { //taking deadly damage if (tech.isDeathAvoid && powerUps.research.count && !tech.isDeathAvoidedThisLevel) { @@ -727,15 +728,14 @@ const m = { return; } } else { - dmg *= m.defense() + if (isDefense) dmg *= m.defense() m.health -= dmg; if (m.health < 0 || isNaN(m.health)) { if (tech.isDeathAvoid && powerUps.research.count > 0 && !tech.isDeathAvoidedThisLevel) { //&& Math.random() < 0.5 tech.isDeathAvoidedThisLevel = true m.health = 0.05 powerUps.research.changeRerolls(-1) - simulation.inGameConsole(`m.research-- -
${powerUps.research.count}`) + simulation.inGameConsole(`m.research--
${powerUps.research.count}`) for (let i = 0; i < 16; i++) powerUps.spawn(m.pos.x + 100 * (Math.random() - 0.5), m.pos.y + 100 * (Math.random() - 0.5), "heal", false); if (m.immuneCycle < m.cycle + 300) m.immuneCycle = m.cycle + 300 //disable this.immuneCycle bonus seconds simulation.wipe = function () { //set wipe to have trails diff --git a/js/powerup.js b/js/powerup.js index 6931d74..677f82a 100644 --- a/js/powerup.js +++ b/js/powerup.js @@ -347,6 +347,75 @@ const powerUps = { }) }, + instructions: { + name: "instructions", + color: "rgba(100,125,140,0.35)", + size() { + return 150 + }, + effect() { + requestAnimationFrame(() => { //add a background behind the power up menu + ctx.fillStyle = `rgba(150,150,150,0.9)`; + ctx.fillRect(0, 0, canvas.width, canvas.height); + }); + powerUps.animatePowerUpGrab('rgba(0, 0, 0,0.6)') + + if (!simulation.paused) { + simulation.paused = true; + simulation.isChoosing = true; //stops p from un pausing on key down + document.body.style.cursor = "auto"; + document.getElementById("choose-grid").style.pointerEvents = "auto"; + document.getElementById("choose-grid").style.transitionDuration = "0s"; + } + //build level info + document.getElementById("choose-grid").classList.add('choose-grid-no-images'); + document.getElementById("choose-grid").classList.remove('choose-grid'); + document.getElementById("choose-grid").style.gridTemplateColumns = "800px"//adjust this to increase the width of the whole menu, but mostly the center column + let lore = localSettings.loreCount > 0 ? "lore.unlockTesting() //press T to enter testing" : "" + let text = `
 //console commands
+ powerUps.instructions.effect()     //reproduce this message
+ tech.giveTech("name")              //replace "name" with tech name
+ m.setField("name")                 //standing wave  perfect diamagnetism  negative mass  molecular assembler  plasma torch  time dilation  metamaterial cloaking  pilot wave  wormhole  grappling hook
+ b.giveGuns("name")                 //nail gun  shotgun  super balls  wave  missiles  grenades  spores  drones  foam  harpoon  mine  laser
+ tech.damage *= 2                   //2x damage
+ m.immuneCycle = Infinity           //immune to damage            
+ m.energy = 0                       //set energy
+ m.health = 1                       //set health
+ m.maxHealth = 1                    //set max health
+ m.energy = 1                       //set energy
+ m.maxEnergy = 1                    //set max energy
+ simulation.enableConstructMode()   //press T to build with mouse
+ ${lore}
+
+ //tech gun field heal ammo research coupling boost instructions entanglement
+ powerUps.spawn(m.pos.x, m.pos.y, "name")
+ Matter.Body.setPosition(player, simulation.mouseInGame);
+ spawn.bodyRect(simulation.mouseInGame.x, simulation.mouseInGame.y, 50, 50)
+ spawn.randomLevelBoss(simulation.mouseInGame.x, simulation.mouseInGame.y)
+
+             chrome                     firefox
+ Win/Linux: Ctrl + Shift + J        Ctrl + Shift + J
+       Mac: Cmd + Option + J        Cmd + Shift + J
+
exit
` + document.getElementById("choose-grid").innerHTML = text + //show level info + document.getElementById("choose-grid").style.opacity = "1" + document.getElementById("choose-grid").style.transitionDuration = "0.3s"; //how long is the fade in on + document.getElementById("choose-grid").style.visibility = "visible" + document.getElementById("exit").addEventListener("click", () => { + level.unPause() + document.body.style.cursor = "none"; + //reset hide image style + if (localSettings.isHideImages) { + document.getElementById("choose-grid").classList.add('choose-grid-no-images'); + document.getElementById("choose-grid").classList.remove('choose-grid'); + } else { + document.getElementById("choose-grid").classList.add('choose-grid'); + document.getElementById("choose-grid").classList.remove('choose-grid-no-images'); + } + }); + }, + }, difficulty: { name: "difficulty", color: "#000", @@ -371,12 +440,19 @@ const powerUps = { //build level info document.getElementById("choose-grid").classList.add('choose-grid-no-images'); document.getElementById("choose-grid").classList.remove('choose-grid'); - document.getElementById("choose-grid").style.gridTemplateColumns = "505px" //adjust this to increase the width of the whole menu, but mostly the center column + document.getElementById("choose-grid").style.gridTemplateColumns = "390px" //adjust this to increase the width of the whole menu, but mostly the center column + + //
0.87x damage, 1.22x damage taken per level
+1 boss on each level
+ //
more mobs per level
faster mobs per level
+ //
0.87x damage, 1.22x damage taken per level
+1 random constraint on each level
+ //
+1 boss on each level
bosses spawn 1 fewer ${powerUps.orb.tech()}
+ //
0.87x damage, 1.22x damage taken per level
+1 random constraint on each level
+ //
0.5x initial damage
2x initial damage taken
let text = `
- + @@ -384,23 +460,26 @@ const powerUps = { +
-
0.87x damage, 1.22x damage taken per level
+1 boss on each level
-
more mob per level
faster mobs per level
-
0.87x damage, 1.22x damage taken per level
+1 random constraint on each level
-
+1 boss on each level
bosses spawn 1 fewer ${powerUps.orb.tech()}
-
0.87x damage, 1.22x damage taken per level
+1 random constraint on each level
+
0.85x damage per level
1.25x damage taken per level
+
spawn more mobs
mobs move faster
+
spawn a 2nd boss each level
bosses spawn 0.5x power ups
+
0.85x damage per level
1.25x damage taken per level
+
+1 random constraint each level
fewer initial power ups
0.5x initial damage
2x initial damage taken
+
+1 random constraint each level
fewer ${powerUps.orb.tech()} spawn
${localSettings.difficultyCompleted[1] ? "⚆" : " "}
${localSettings.difficultyCompleted[2] ? "⚆" : " "}
${localSettings.difficultyCompleted[3] ? "⚆" : " "}
${localSettings.difficultyCompleted[4] ? "⚆" : " "}
-
${localSettings.difficultyCompleted[5] ? "⚇" : " "}
+
${localSettings.difficultyCompleted[5] ? "⚆" : " "}
${localSettings.difficultyCompleted[6] ? "⚇" : " "}
+
${localSettings.difficultyCompleted[7] ? "⚇" : " "}
@@ -433,7 +512,7 @@ const powerUps = { }); let setDifficultyText = function (isReset = true) { - for (let i = 1; i < 7; i++) { + for (let i = 1; i < 8; i++) { const id = document.getElementById("constraint-" + i) if (simulation.difficultyMode < i) { id.style.opacity = "0.15" @@ -456,7 +535,7 @@ const powerUps = { setDifficultyText() level.setConstraints() }); - for (let i = 1; i < 7; i++) { + for (let i = 1; i < 8; i++) { document.getElementById("constraint-" + i).addEventListener("click", () => { simulation.difficultyMode = i document.getElementById("difficulty-slider").value = simulation.difficultyMode @@ -632,7 +711,7 @@ const powerUps = { m.addHealth(heal); if (healOutput > 0) simulation.inGameConsole(`
  m.health += ${(healOutput).toFixed(3)}`) //
${m.health.toFixed(3)} if (tech.isOverHeal && overHeal > 0) { //tech quenching - tech.extraMaxHealth += 0.3 * overHeal //increase max health + tech.extraMaxHealth += 0.4 * overHeal //increase max health m.setMaxHealth(); simulation.inGameConsole(`
  m.maxHealth += ${(0.3 * overHeal).toFixed(3)}`) simulation.drawList.push({ //add dmg to draw queue @@ -925,78 +1004,6 @@ const powerUps = {           ${tech.tech[choose].name} ${techCountText}
${tech.tech[choose].descriptionFunction ? tech.tech[choose].descriptionFunction() : tech.tech[choose].description}
` }, - // junkTechText(choose, click) { //old code with yahoo images - // const techCountText = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count + 1}x)` : ""; - // const style = localSettings.isHideImages ? powerUps.hideStyle : `style="background-size: contain;background-repeat: no-repeat;background-image: url('img/junk.webp');"` - // if (!localSettings.isHideImages) { - // setTimeout(() => { //delay so that the html element exists - // if (tech.tech[choose].url === undefined) { //if on url has been set yet - // const url = "https://images.search.yahoo.com/search/images?p=" + tech.tech[choose].name; - // fetch(url, { signal: AbortSignal.timeout(1000) }) //give up if it takes over 1 second - // .then((response) => response.text()) - // .then((html) => { - // const parser = new DOMParser(); - // const doc = parser.parseFromString(html, "text/html"); - // const elements = doc.getElementsByClassName("ld"); - // // console.log(i, elements[i].getAttribute("data"), JSON.parse(elements[i].getAttribute("data")).iurl) - // const index = Math.floor(Math.random() * 4) //randomly choose from the first 4 images - // if (parseInt(JSON.parse(elements[index].getAttribute("data")).s.slice(0, -2)) < 500) { //make sure it isn't too big - // tech.tech[choose].url = JSON.parse(elements[index].getAttribute("data")).iurl //store the url - // document.getElementById(`junk-${choose}`).style.backgroundImage = `url('${tech.tech[choose].url}')` //make the url the background image - // } else if (parseInt(JSON.parse(elements[index + 1].getAttribute("data")).s.slice(0, -2)) < 500) { //try a different images and see if it is smaller - // tech.tech[choose].url = JSON.parse(elements[index + 1].getAttribute("data")).iurl - // document.getElementById(`junk-${choose}`).style.backgroundImage = `url('${tech.tech[choose].url}')` - // } else if (parseInt(JSON.parse(elements[index + 2].getAttribute("data")).s.slice(0, -2)) < 500) { //try a different images and see if it is smaller - // tech.tech[choose].url = JSON.parse(elements[index + 2].getAttribute("data")).iurl - // document.getElementById(`junk-${choose}`).style.backgroundImage = `url('${tech.tech[choose].url}')` - // } - // }); - // } else { - // document.getElementById(`junk-${choose}`).style.backgroundImage = `url('${tech.tech[choose].url}')` - // } - // }, 1); - // } - // return `
- //
- //
  ${tech.tech[choose].name} ${techCountText}
- // ${tech.tech[choose].descriptionFunction ? tech.tech[choose].descriptionFunction() : tech.tech[choose].description}
` - // }, - // junkTechText(choose, click) { - // const techCountText = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count + 1}x)` : ""; - // const style = localSettings.isHideImages ? powerUps.hideStyle : `style="background-size: contain;background-repeat: no-repeat;background-image: url('img/junk.webp');"` - // if (!localSettings.isHideImages) { - // setTimeout(() => { //delay so that the html element exists - // if (tech.tech[choose].url === undefined) { //if on url has been set yet - // const url = "https://images.search.yahoo.com/search/images?p=" + tech.tech[choose].name; - // fetch(url, { signal: AbortSignal.timeout(1000) }) //give up if it takes over 1 second - // .then((response) => response.text()) - // .then((html) => { - // const parser = new DOMParser(); - // const doc = parser.parseFromString(html, "text/html"); - // const elements = doc.getElementsByClassName("ld"); - // // console.log(i, elements[i].getAttribute("data"), JSON.parse(elements[i].getAttribute("data")).iurl) - // const index = Math.floor(Math.random() * 4) //randomly choose from the first 4 images - // if (parseInt(JSON.parse(elements[index].getAttribute("data")).s.slice(0, -2)) < 500) { //make sure it isn't too big - // tech.tech[choose].url = JSON.parse(elements[index].getAttribute("data")).iurl //store the url - // document.getElementById(`junk-${choose}`).style.backgroundImage = `url('${tech.tech[choose].url}')` //make the url the background image - // } else if (parseInt(JSON.parse(elements[index + 1].getAttribute("data")).s.slice(0, -2)) < 500) { //try a different images and see if it is smaller - // tech.tech[choose].url = JSON.parse(elements[index + 1].getAttribute("data")).iurl - // document.getElementById(`junk-${choose}`).style.backgroundImage = `url('${tech.tech[choose].url}')` - // } else if (parseInt(JSON.parse(elements[index + 2].getAttribute("data")).s.slice(0, -2)) < 500) { //try a different images and see if it is smaller - // tech.tech[choose].url = JSON.parse(elements[index + 2].getAttribute("data")).iurl - // document.getElementById(`junk-${choose}`).style.backgroundImage = `url('${tech.tech[choose].url}')` - // } - // }); - // } else { - // document.getElementById(`junk-${choose}`).style.backgroundImage = `url('${tech.tech[choose].url}')` - // } - // }, 1); - // } - // return `
- //
- //
  ${tech.tech[choose].name} ${techCountText}
- // ${tech.tech[choose].descriptionFunction ? tech.tech[choose].descriptionFunction() : tech.tech[choose].description}
` - // }, junkTechText(choose, click) { const techCountText = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count + 1}x)` : ""; const style = localSettings.isHideImages ? powerUps.hideStyle : `style="background-size: contain;background-repeat: no-repeat;background-image: url('img/junk.webp');"` @@ -1249,7 +1256,7 @@ const powerUps = { for (let i = 0, len = powerUps.retainList.length; i < len; i++) { //find index from name and add tech to options for (let j = 0, len = tech.tech.length; j < len; j++) { - if (tech.tech[j].name === powerUps.retainList[i] && tech.tech[j].count < tech.tech[j].maxCount && tech.tech[j].allowed()) { //&& !tech.tech[j].isRecentlyShown + if (tech.tech[j].name === powerUps.retainList[i] && tech.tech[j].count < tech.tech[j].maxCount && tech.tech[j].allowed() && tech.tech[j].frequency > 0) { //&& !tech.tech[j].isRecentlyShown addTech(j) } } @@ -1276,7 +1283,7 @@ const powerUps = { //this flag prevents this option from being shown the next time you pick up a tech power up //check if not extra choices from "path integral" tech.tech[choose].isRecentlyShown = true - if (tech.isRetain) powerUps.retainList.push(tech.tech[choose].name) + if (tech.isRetain && !powerUps.retainList.includes(tech.tech[choose].name)) powerUps.retainList.push(tech.tech[choose].name) addTech(choose) } } @@ -1504,7 +1511,7 @@ const powerUps = { } else { powerUpChance() } - if (simulation.difficultyMode < 4) {//don't spawn second power up on difficulties with a second boss + if (simulation.difficultyMode < 3) {//don't spawn second power up on difficulties with a second boss powerUpChance() } function powerUpChance() { @@ -1550,7 +1557,6 @@ const powerUps = { } }, addResearchToLevel() { //add a random power up to a location that has a mob, mostly used to give each level a research - // if (simulation.difficultyMode < 4 && mob.length) { //don't spawn on higher difficulty settings if ((level.levelsCleared < 17 - simulation.difficultyMode * 3) && mob.length) { //don't spawn late game const index = Math.floor(Math.random() * mob.length) powerUps.spawn(mob[index].position.x, mob[index].position.y, "research"); @@ -1558,7 +1564,7 @@ const powerUps = { }, spawnStartingPowerUps(x, y) { //used for map specific power ups, mostly to give player a starting gun if (level.levelsCleared < 4) { //runs on first 4 levels on all difficulties - if (level.levelsCleared > 1 && simulation.difficultyMode < 6) powerUps.spawn(x, y, "tech") + if (level.levelsCleared > 1 && simulation.difficultyMode < 7) powerUps.spawn(x, y, "tech") if (b.inventory.length === 0) { powerUps.spawn(x, y, "gun", false); //first gun } else if (tech.totalCount === 0) { //first tech @@ -1638,7 +1644,7 @@ const powerUps = { // } tech.tech[index].frequency = 0 //banish tech powerUps.ejectTech(index) - if (m.immuneCycle < m.cycle) m.damage(tech.pauseEjectTech * 0.01) + if (m.immuneCycle < m.cycle) m.damage(tech.pauseEjectTech * 0.01, false) tech.pauseEjectTech *= 1.3 document.getElementById(`${index}-pause-tech`).style.textDecoration = "line-through" document.getElementById(`${index}-pause-tech`).style.animation = "" diff --git a/js/simulation.js b/js/simulation.js index 86dc5e9..fd68623 100644 --- a/js/simulation.js +++ b/js/simulation.js @@ -431,48 +431,6 @@ const simulation = { } } document.getElementById("right-HUD").innerHTML = text - - - // let text = "" - // if (simulation.difficultyMode > 2 && level.constraintDescription1) { - // text += `${level.constraintDescription1}` - // // text += `${level.constraintDescription1}` - // } - // if (simulation.difficultyMode > 4 && level.constraintDescription2) { - // text += `
${level.constraintDescription2}` - // } - // for (let i = 0, len = tech.tech.length; i < len; i++) { //add tech - // if (tech.tech[i].isLost) { - // if (text) text += "
" //add a new line, but not on the first line - // text += `${tech.tech[i].name}` - // } else if (tech.tech[i].count > 0 && !tech.tech[i].isInstant) { - // if (text) text += "
" //add a new line, but not on the first line - // text += tech.tech[i].name - // if (tech.tech[i].count > 1) text += ` (${tech.tech[i].count}x)` - // } - // } - // document.getElementById("right-HUD").innerHTML = text - - // let constraints = "" - // if (simulation.difficultyMode > 2 && level.constraintDescription1) { - // constraints += `${level.constraintDescription1}` - // // text += `${level.constraintDescription1}` - // } - // if (simulation.difficultyMode > 4 && level.constraintDescription2) { - // constraints += `
${level.constraintDescription2}` - // } - // let text = "" - // for (let i = 0, len = tech.tech.length; i < len; i++) { //add tech - // if (tech.tech[i].isLost) { - // if (text) text += "
" //add a new line, but not on the first line - // text += `${tech.tech[i].name}` - // } else if (tech.tech[i].count > 0 && !tech.tech[i].isInstant) { - // if (text) text += "
" //add a new line, but not on the first line - // text += tech.tech[i].name - // if (tech.tech[i].count > 1) text += ` (${tech.tech[i].count}x)` - // } - // } - // document.getElementById("right-HUD").innerHTML = constraints + `
` + text + `
` }, lastLogTime: 0, isTextLogOpen: true, @@ -585,6 +543,97 @@ const simulation = { }) } }, + isInvertedVertical: false, + flipCameraVertical(frames = 1, passFunction = () => { }) { + if (!simulation.isInvertedVertical) { + if (frames > 0) { + let count = 0 + const loop = () => { + if (m.alive) { + if (simulation.paused) { + requestAnimationFrame(loop); + } else { + count++ + ctx.setTransform(1, 0, 0, 1, 0, 0); ///reset to avoid build up of transformations + if (count === frames) { + // Flip the canvas vertically + ctx.translate(0, canvas.height); // Move the origin down to the bottom + ctx.scale(1, -1); // Flip vertically + simulation.isInvertedVertical = true + //flip mouse Y again to make sure it caught + mouseMove = function (e) { + simulation.mouse.x = e.clientX; + simulation.mouse.y = window.innerHeight - e.clientY; + } + } else { + requestAnimationFrame(loop); + ctx.translate(0, canvas.height * count / frames); + ctx.scale(1, 1 - 2 * count / frames); + } + if (count === Math.floor(frames / 2)) { + //flip mouse Y at the 1/2 way point + mouseMove = function (e) { + simulation.mouse.x = e.clientX; + simulation.mouse.y = window.innerHeight - e.clientY; + } + //passFunction probably flips the map elements + passFunction() + } + } + } + } + requestAnimationFrame(loop); + } else { + // Flip the canvas vertically + ctx.translate(0, canvas.height); // Move the origin down to the bottom + ctx.scale(1, -1); // Flip vertically + //flip mouse Y + simulation.isInvertedVertical = true + mouseMove = function (e) { + simulation.mouse.x = e.clientX; + simulation.mouse.y = window.innerHeight - e.clientY; + } + } + } + }, + unFlipCameraVertical(frames = 0, passFunction = () => { }) { + if (frames) { + let count = 0 + const loop = () => { + if (m.alive) { + if (simulation.paused) { + requestAnimationFrame(loop); + } else { + count++ + ctx.setTransform(1, 0, 0, 1, 0, 0); ///reset to avoid build up of transformations + if (count === frames) { + // requestAnimationFrame(() => { ctx.reset(); }); + // ctx.translate(0, 0); + // ctx.scale(1, 1); + simulation.isInvertedVertical = false + //flip mouse Y again to make sure it caught + mouseMove = mouseMoveDefault + + } else { + requestAnimationFrame(loop); + ctx.translate(0, canvas.height - canvas.height * count / frames); + ctx.scale(1, -1 + 2 * count / frames); + } + if (count === Math.floor(frames / 2)) { + mouseMove = mouseMoveDefault//flip mouse Y at the 1/2 way point + passFunction()//passFunction probably draws new map elements + } + } + } + } + requestAnimationFrame(loop); + } else { + ctx.reset(); + ctx.font = "25px Arial"; + simulation.isInvertedVertical = false + mouseMove = mouseMoveDefault + } + }, translatePlayerAndCamera(where) { //infinite falling. teleport to sky after falling const before = { x: player.position.x, y: player.position.y, } @@ -595,6 +644,7 @@ const simulation = { m.transY += change.y simulation.mouseInGame.x = (simulation.mouse.x - canvas.width2) / simulation.zoom * simulation.edgeZoomOutSmooth + canvas.width2 - m.transX; simulation.mouseInGame.y = (simulation.mouse.y - canvas.height2) / simulation.zoom * simulation.edgeZoomOutSmooth + canvas.height2 - m.transY; + m.angle = Math.atan2(simulation.mouseInGame.y - m.pos.y, simulation.mouseInGame.x - m.pos.x); //is there a reason to update m.pos here? @@ -800,6 +850,8 @@ const simulation = { document.getElementById("pause-grid-left").style.opacity = "1" ctx.globalCompositeOperation = "source-over" ctx.shadowBlur = 0; + + mouseMove = mouseMoveDefault requestAnimationFrame(() => { ctx.setTransform(1, 0, 0, 1, 0, 0); //reset warp effect ctx.setLineDash([]) //reset stroke dash effect @@ -812,7 +864,7 @@ const simulation = { } else { Composite.add(engine.world, [player]) } - shuffle(level.constraint) + // shuffle(level.constraint) level.populateLevels() input.endKeySensing(); simulation.ephemera = [] @@ -993,8 +1045,6 @@ const simulation = { } else { //get hurt and go to start Matter.Body.setVelocity(player, { x: 0, y: 0 }); Matter.Body.setPosition(player, { x: level.enter.x + 50, y: level.enter.y - 20 }); - // m.damage(0.02 * simulation.difficultyMode); - // m.energy -= 0.02 * simulation.difficultyMode // move bots for (let i = 0; i < bullet.length; i++) { if (bullet[i].botType) { @@ -1137,6 +1187,7 @@ const simulation = { clearMap() { level.isProcedural = false; level.fallMode = ""; + simulation.unFlipCameraVertical() ctx.setTransform(1, 0, 0, 1, 0, 0); if (m.alive) { if (tech.isLongitudinal) b.guns[3].waves = []; //empty array of wave bullets @@ -1199,6 +1250,7 @@ const simulation = { m.drop(); m.hole.isOn = false; simulation.drawList = []; + mobs.maxMobBody = 40 if (tech.isHealAttract && m.alive) { //send health power ups to the next level let healCount = 0 diff --git a/js/spawn.js b/js/spawn.js index b0f4207..96b35c9 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -48,7 +48,7 @@ const spawn = { // spawn.pickList.push(spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]); }, spawnChance(chance) { - const difficultyChance = simulation.difficultyMode === 1 ? 1 : simulation.difficulty + const difficultyChance = (simulation.difficultyMode === 1) ? 1 : simulation.difficulty return (Math.random() < chance + 0.07 * difficultyChance) && (mob.length < -1 + 16 * Math.log10(simulation.difficulty + 1)) }, randomMob(x, y, chance = 1) { @@ -114,7 +114,7 @@ const spawn = { } }, secondaryBossChance(x, y) { - if (simulation.difficultyMode > 3 && level.levelsCleared > 1) { + if (simulation.difficultyMode > 2 && level.levelsCleared > 1) { spawn.randomLevelBoss(x, y); powerUps.directSpawn(x - 30, y, "ammo"); powerUps.directSpawn(x + 30, y, "ammo"); @@ -977,7 +977,7 @@ const spawn = { if (!simulation.paused && !simulation.onTitlePage) { count++ if (count < 660) { - if (count === 1 && simulation.difficultyMode < 5) simulation.inGameConsole(`//enter testing mode to set level.levels.length to Infinite`); + if (count === 1 && simulation.difficultyMode < 6) simulation.inGameConsole(`//enter testing mode to set level.levels.length to Infinite`); if (!(count % 60)) simulation.inGameConsole(`simulation.analysis = ${((count / 60 - Math.random()) * 0.1).toFixed(3)}`); } else if (count === 660) { simulation.inGameConsole(`simulation.analysis = 1 //analysis complete`); @@ -2966,6 +2966,16 @@ const spawn = { requestAnimationFrame(() => { requestAnimationFrame(() => { ctx.setTransform(1, 0, 0, 1, 0, 0); //reset warp effect + if (simulation.isInvertedVertical) { //redo vertical camera effects + ctx.translate(0, canvas.height); // Move the origin down to the bottom + ctx.scale(1, -1); // Flip vertically + //flip mouse Y + simulation.isInvertedVertical = true + mouseMove = function (e) { + simulation.mouse.x = e.clientX; + simulation.mouse.y = window.innerHeight - e.clientY; + } + } ctx.setLineDash([]) //reset stroke dash effect }) }) @@ -4647,7 +4657,7 @@ const spawn = { if (simulation.cycle % this.laserInterval > this.laserInterval / 2) { const seeRange = 8000; - best = { + let best = { x: null, y: null, dist2: Infinity, diff --git a/js/tech.js b/js/tech.js index 239e2ee..66ad7b7 100644 --- a/js/tech.js +++ b/js/tech.js @@ -349,7 +349,7 @@ const tech = { }, tech: [{ name: "tungsten carbide", - description: "+400 maximum health
lose health after hard landings", + description: "+500 maximum health
lose health after hard landings", maxCount: 1, count: 0, frequency: 1, @@ -362,7 +362,7 @@ const tech = { effect() { tech.isFallingDamage = true; m.setMaxHealth(); - m.addHealth(3 / simulation.healScale) + m.addHealth(5 / simulation.healScale) m.skin.tungsten() }, remove() { @@ -535,8 +535,13 @@ const tech = { remove() { if (this.count > 0) { tech.isEnergyHealth = false; - document.getElementById("health").style.display = "inline" - document.getElementById("health-bg").style.display = "inline" + if (tech.isEnergyHealth) { + document.getElementById("health").style.display = "none" + document.getElementById("health-bg").style.display = "none" + } else if (!level.isHideHealth) { + document.getElementById("health").style.display = "inline" + document.getElementById("health-bg").style.display = "inline" + } document.getElementById("dmg").style.backgroundColor = "#f67"; m.health = Math.max(Math.min(m.maxHealth, m.energy), 0.1); simulation.mobDmgColor = "rgba(255,0,0,0.7)" @@ -878,7 +883,7 @@ const tech = { }, requires: "at least 2 guns", effect() { - const delay = 20 + const delay = 30 let i = (b.inventory.length) * delay let gunIndex = -1 let cycle = () => { @@ -3325,7 +3330,7 @@ const tech = { { name: "quenching", descriptionFunction() { - return `0.3x of ${powerUps.orb.heal()} overhealing
is added to maximum health` + return `0.4x of ${powerUps.orb.heal()} overhealing
is added to maximum health` }, maxCount: 1, count: 0, @@ -10699,6 +10704,24 @@ const tech = { }, remove() { } }, + { + name: "the upside down", + description: `Flip the universe until the end of the level.
I'll give you 1.1x damage as well.`, + maxCount: 1, + count: 0, + frequency: 0, + isInstant: true, + isJunk: true, + allowed() { + return true + }, + requires: "", + effect() { + simulation.flipCameraVertical(900) + tech.damage *= 1.1 + }, + remove() { } + }, { name: "rewind", description: "every 10 seconds rewind 2 seconds", @@ -11134,6 +11157,46 @@ const tech = { }, remove() { } }, + { + name: "pet bots", + description: "pet your bots", + maxCount: 1, + count: 0, + frequency: 0, + isJunk: true, + isInstant: true, + allowed() { + return b.totalBots() + }, + requires: "", + effect() { + simulation.ephemera.push({ + name: "pet", + count: 0, + do() { + this.count++ + if (!(this.count % 420)) { + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType && Math.random() < 0.3) { + simulation.inGameConsole(`${bullet[i].botType}-bot.pet()`) + if (m.onGround && !m.crouch) { + m.yOffGoal = m.yOffWhen.crouch; + setTimeout(() => { + if (!m.crouch) m.yOffGoal = m.yOffWhen.stand; + }, 1000); + if (m.immuneCycle < m.cycle + 90) m.immuneCycle = m.cycle + 90 + } + if (Math.random() < 0.3) break + } + } + + } + } + }) + }, + remove() { + } + }, { name: "assimilation", description: "all your bots are converted to the same random model", diff --git a/style.css b/style.css index f751233..3f0602b 100644 --- a/style.css +++ b/style.css @@ -1561,12 +1561,13 @@ summary { .far-right-column { display: grid; - grid-template-rows: repeat(6, 1fr); + grid-template-rows: repeat(7, 1fr); font-size: 2rem; font-family: monospace; align-self: center; justify-self: center; width: 22px; + /* padding: 0rem; */ } .far-right-column>div { @@ -1575,19 +1576,20 @@ summary { #difficulty-slider { margin-top: 2.3rem; - height: 26.3rem; + /* height: 26.3rem; */ + height: 31.4rem; width: 2rem; writing-mode: vertical-lr; direction: ltr; } .left-column { - grid-row: 1 / span 6 + grid-row: 1 / span 7 } .right-column { display: grid; - grid-template-rows: repeat(6, 1fr); + grid-template-rows: repeat(7, 1fr); /* to adjust the width of this column edit the "gridTemplateColumns" in the difficulty power up js code*/ } @@ -1630,6 +1632,10 @@ summary { #constraint-6 { background-color: hsl(240, 18%, 77%); +} + +#constraint-7 { + background-color: hsl(240, 18%, 73%); border-radius: 0 0 7px 7px; } @@ -1669,6 +1675,12 @@ summary { padding: 9px; } +#constraint-7:hover { + background-color: hsl(240, 18%, 71%); + border: 1px #444 solid; + padding: 9px; +} + #choose-difficulty { text-align: center; font-size: 1.1em; diff --git a/todo.txt b/todo.txt index 3fa073a..447102b 100644 --- a/todo.txt +++ b/todo.txt @@ -1,24 +1,32 @@ ******************************************************** NEXT PATCH ************************************************** -subway - 2 new subway stations - more visible button graphics on subway +new level testChamber2 + New camera flip effect + new laser level element now has collisions with blocks + elevators are less deadly to mobs at low speeds -new constraints - no health bars - no pause while choosing +difficulty level progression reworked +no constraints on final boss +new constraint - healing disabled -tech: coherence - past choices are added to all future tech - requires decoherence - research and cancel buttons have a sticky scroll positioning -eternalism: you can't pause while choosing, but you can otherwise pause now - 1.25->1.3 damage +quenching 0.3->0.4x overheal converted to max health +tungsten carbide 400->500 extra max health +paradigm shift's health loss is no longer reduced by damage taken reduction +coherence no longer remembers tech that is set to zero frequency, like removed tech -bugs - MIRV missiles now interact with time dilation properly +JUNK tech: pet the bot - lets you pet your bots +JUNK tech: the upside down - flip everything + +bug + prevented possible duplicate choices with coherence tech + fixed issues with showing and hiding health bars on that constraint + fixed crash from autonomous defense + mass-energy mode wasn't getting any benefit from damage taken reduction + it now gets square root of damage taken reduction ******************************************************** BUGS ******************************************************** + figure out why seeded random isn't making runs the same: shuffle is being used for a wide variety of things that don't need a seeded random make two shuffle functions? @@ -47,30 +55,36 @@ player can become crouched while not touching the ground if they exit the ground *********************************************************** TODO ***************************************************** +new level based laser element + !!update new version into other levels + +level technique: pairs of touch activated elevators jump on one to get high enough to jump on the next one + +flip player upside down + fieldTech: negative mass? + JUNK tech? + final boss effect + maybe just flip player camera and mouse + level effect flip map vertically also + new subway station + new full level + needs a device that will flip gravity + update to old level so it is more surreal + levels with few effects, just blocks and map + levels with a roof or infinite fall + towers, lab + + + + + +constraints should show future constraints in pause menu + pre calculate all constraints for up to 13 levels? + loop constraints after that + procedural animation https://www.youtube.com/watch?v=qlfh_rv6khY -maybe no constraints on final boss and reactor? - -constraints balance - 40% JUNK chance - -1 choice - no health bars - 4x shielded mob chance - +33% chance for mobs to respawn - 2x ammo costs - too hard - 0 duplication - power ups in stasis - 0.1x damage after a power up - 0.5x energy regen - 50% max energy - 50% max health - bots follow slow - periodically spawn WIMPs - mob death heals mobs - mobs heal for your lost health - not implemented random constraint ideas________________________ mob death spawns something mob bullets @@ -83,21 +97,10 @@ not implemented random constraint ideas________________________ can't have more then 15 bullets how to code? remove 2 random tech and return them next level - too niche - player damage is 0.25x while player is invulnerable - all hazards: lasers and slime do 3x damage - player lasers and radiation do 0.5x damage - explosions do 0.5x damage - freeze effects last 0.25x time tech: ice-VII - 1.5x duration for ice-IX -tech: - freezing grenades/explosions - -tech: - randomize constraints somehow - in pause interface or power up selection menu? - each time you research the current constraints also randomize? - only allowed if difficulty is high enough +tech: - freezing grenades/explosions tech: - when you get a bot, get a second bot @@ -107,8 +110,6 @@ tech: - after killing a Boss new level - rework testChamber -skin with wheel instead of legs - Boss (or mob) that quickly moves towards player, but they moves perpendicularly to player, like dodging could respond to when player presses fire key or to when it takes damage @@ -155,9 +156,6 @@ increase mass and movement speed at the same time possible player.mass bad interactions grapple -JUNK tech - player takes damage from block collisions - is this gonna contribute to lag? - bullets should trigger shrinking platforms level element? level element - player activated elevators @@ -167,18 +165,6 @@ level element - player activated elevators rework energy and health HUD make both diegetic? how? not sure there is a good way to do this... - should health be red or green? - -flip player upside down - how - rotate player in matter.js - make sure floor sensor works - flip player crouch direction - redraw legs, orb - flip gravity - when to use? - fieldTech: negative mass? - effect in level tech - after a power up is duplicated update text to random effect after choosing tech, or after each trigger, or on first display of tech