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