"use strict"; /* TODO: ******************************************* ***************************************************** add difficulty slider to custom? phase decoherence energy drain is proportional to player speed add recursive mod counts to pause screen key required to open the exit to some levels must hold key like a block could the key be any block? css transition for pause menu mod: remove all ammo drops from the game, but double player damage mod: like Born rule, but for guns field that pushes everything back, and can destroy smaller blocks converts blocks into ammo power ups mod: make player invisible when... use the flag from phase field field: a larger radius that attracted enemies still deflected them near the robot convert the health of mobs into energy when they are being attracted mod: chance to not die from fatal damage also push mobs and bodies away? also heal? mod: bot that fires minigun bullets only fires when your mouse is held down mod: do extra damage based on your speed do more damage when not moving? take less damage when not moving? gun/field: portals use the code from mines to get them to stick to walls or lasers alternate red and blue portals missiles don't explode reliably enough they can bounce, which is cool, but they should still explode right after a bounce weekly random challenge where everyone playing each week gets the same random setup. The randomness would be determined by the date so it would sync everyone. power ups still drop, but the drops are determined by a preset list that changes each week. mod: do something at the end of each level heal to full should still be effected by the heal reduction at higher difficulty give ammo to current gun give goals/quests for each level how to track goals? take no damage don't shoot mod: if you fire when out of ammo you gain 1 ammo pack at the cost of 10% max health 20% of your current health gun: Spirit Bomb (singularity) use charge up like rail gun electricity graphics like plasma torch suck in nearby mobs, power ups?, blocks? sucked in stuff increase size uses energy hold above the player's head atmosphere levels large rotating fan that the player has to move through give the user a rest, between combat low combat nonaggressive mobs one mob attacking the passive mobs more graphics Boss levels sensor that locks you in after you enter the boss room boss that eats other mobs and gains stats from them chance to spawn on any level (past level 5) add a key that player picks up and needs to set on the exit door to open it make power ups keep moving to player if the pickup field is turned off before they get picked up not sure how to do this without adding a constant check animate new level spawn by having the map aspects randomly fly into place new map with repeating endlessness get ideas from Manifold Garden game if falling, get teleported above the map I tried it, but had trouble getting the camera to adjust to the teleportation this can apply to blocks mobs, and power ups as well field power up effects field allows player to hold and throw living mobs and hack mobs give mobs more animal-like behaviors like rain world give mobs something to do when they don't see player explore map eat power ups drop power up (if killed after eating one) mobs some times aren't aggressive when low on life or after taking a large hit mobs can fight each other this might be hard to code isolated mobs try to group up. game mechanics mechanics that support the physics engine add rope/constraint get ideas from game: limbo / inside environmental hazards laser lava button / switch door fizzler moving platform map zones water low friction ground bouncy ground */ //collision groups // cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet | cat.mobShield const cat = { player: 0x1, map: 0x10, body: 0x100, bullet: 0x1000, powerUp: 0x10000, mob: 0x100000, mobBullet: 0x1000000, mobShield: 0x10000000, } //build build grid display const build = { isShowingBuilds: false, list: [], choosePowerUp(who, index, type) { if (type === "field" || type === "gun") { let isDeselect = false //if already click, toggle off for (let i = 0; i < build.list.length; i++) { if (build.list[i].index === index && build.list[i].type === type) { build.list.splice(i, 1); who.style.backgroundColor = "#fff" isDeselect = true break } } //check if trying to get a second field if (type === "field") { for (let i = 0; i < build.list.length; i++) { if (build.list[i].type === "field") { //if already click, toggle off build.list[i].who.style.backgroundColor = "#fff" build.list.splice(i, 1); } } } if (!isDeselect) { who.style.backgroundColor = "#919ba8" //"#868f9a" build.list[build.list.length] = { who: who, index: index, type: type, } } } else if (type === "mod") { if (who.style.backgroundColor !== "#919ba8") who.style.backgroundColor = "#919ba8" //"#868f9a" //if already clicked graphically indicate recursive clicks let count = 0 for (let i = 0; i < build.list.length; i++) { if (build.list[i].type === "mod" && build.list[i].index === index) { count++ } } if (count < b.mods[index].maxCount) { //add mod to build list build.list[build.list.length] = { who: who, index: index, type: type, } count++ //display mod count in grid box text if (count > 1) who.innerHTML = `
  ${b.mods[index].name} (${count}x)
${b.mods[index].description}` } else { //when above the mod limit remove all of that mod for (let i = build.list.length - 1; i > -1; i--) { if (build.list[i].index === index && build.list[i].type === type) { build.list.splice(i, 1); } } //and reset the text who.style.backgroundColor = "#fff" who.innerHTML = `
  ${b.mods[index].name}
${b.mods[index].description}` } } // document.title = `effective starting level: ${build.list.length * game.difficultyMode}` build.calculateCustomDifficulty() }, makeGrid() { let text = `
start reset
` for (let i = 1, len = mech.fieldUpgrades.length; i < len; i++) { text += `
  ${mech.fieldUpgrades[i].name}
${mech.fieldUpgrades[i].description}
` } for (let i = 0, len = b.guns.length; i < len; i++) { text += `
  ${b.guns[i].name}
${b.guns[i].description}
` } for (let i = 0, len = b.mods.length; i < len; i++) { if (b.mods[i].name === "Born rule" || b.mods[i].name === "+1 cardinality") { text += `
  ${b.mods[i].name}
${b.mods[i].description}
` } else { text += `
  ${b.mods[i].name}
${b.mods[i].description}
` } } const el = document.getElementById("build-grid") el.innerHTML = text el.style.display = "none" document.getElementById("difficulty-select-custom").addEventListener("input", () => { document.getElementById("difficulty-select").value = document.getElementById("difficulty-select-custom").value game.difficultyMode = Number(document.getElementById("difficulty-select-custom").value) localSettings.difficultyMode = game.difficultyMode localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage build.calculateCustomDifficulty() }); }, reset() { build.list = [] build.makeGrid(); document.getElementById("build-grid").style.display = "grid" build.calculateCustomDifficulty() document.getElementById("difficulty-select-custom").value = localSettings.difficultyMode }, pauseGrid() { // let text = `
` let text = `
PAUSED               press P to resume
`; //
// ${game.SVGleftMouse} fire gun // ${game.SVGrightMouse} use field //
let countGuns = 0 let countMods = 0 for (let i = 0, len = b.guns.length; i < len; i++) { if (b.guns[i].have) { text += `
  ${b.guns[i].name}
${b.guns[i].description}
` countGuns++ } } let el = document.getElementById("pause-grid-left") el.style.display = "grid" el.innerHTML = text text = ""; text += `
  ${mech.fieldUpgrades[mech.fieldMode].name}
${mech.fieldUpgrades[mech.fieldMode].description}
` for (let i = 0, len = b.mods.length; i < len; i++) { if (b.mods[i].count > 0) { text += `
  ${b.mods[i].name}
${b.mods[i].description}
` countMods++ } } el = document.getElementById("pause-grid-right") el.style.display = "grid" el.innerHTML = text if (countMods > 5 || countGuns > 6) { document.body.style.overflowY = "scroll"; document.body.style.overflowX = "hidden"; } }, unPauseGrid() { document.body.style.overflow = "hidden" document.getElementById("pause-grid-left").style.display = "none" document.getElementById("pause-grid-right").style.display = "none" }, calculateCustomDifficulty() { let difficulty = build.list.length * game.difficultyMode if (game.difficultyMode === 0) difficulty = build.list.length * 1 - 6 if (game.difficultyMode === 4) difficulty = build.list.length * 4 + 8 document.getElementById("starting-level").innerHTML = `starting difficulty: ${difficulty}` }, startBuildRun() { spawn.setSpawnList(); spawn.setSpawnList(); //gives random mobs, not starter game.startGame(); let difficulty = build.list.length * game.difficultyMode - 1 if (game.difficultyMode === 0) { difficulty = build.list.length * 1 - 6 - 1 game.isEasyMode = true; } if (game.difficultyMode === 4) level.difficultyIncrease(6) level.difficultyIncrease(difficulty) level.isBuildRun = true; for (let i = 0; i < build.list.length; i++) { if (build.list[i].type === "field") { mech.setField(build.list[i].index) } else if (build.list[i].type === "gun") { b.giveGuns(build.list[i].index) } else if (build.list[i].type === "mod") { b.giveMod(build.list[i].index) } } } } build.makeGrid(); document.getElementById("build-button").addEventListener("click", () => { document.getElementById("build-button").style.display = "none"; const el = document.getElementById("build-grid") if (build.isShowingBuilds) { el.style.display = "none" build.isShowingBuilds = false document.body.style.overflow = "hidden" document.getElementById("info").style.display = 'inline' } else { build.list = [] build.reset() // let text = '

The difficulty increases by one level for each power up you choose.

' build.isShowingBuilds = true el.style.display = "grid" document.body.style.overflowY = "scroll"; document.body.style.overflowX = "hidden"; document.getElementById("info").style.display = 'none' } build.calculateCustomDifficulty() }); // local storage let localSettings = JSON.parse(localStorage.getItem("localSettings")); // console.log(localSettings) if (localSettings) { game.isBodyDamage = localSettings.isBodyDamage document.getElementById("body-damage").checked = localSettings.isBodyDamage game.difficultyMode = localSettings.difficultyMode document.getElementById("difficulty-select").value = localSettings.difficultyMode document.getElementById("difficulty-select-custom").value = localSettings.difficultyMode if (localSettings.fpsCapDefault === 'max') { game.fpsCapDefault = 999999999; } else { game.fpsCapDefault = Number(localSettings.fpsCapDefault) } document.getElementById("fps-select").value = localSettings.fpsCapDefault } else { localSettings = { isBodyDamage: true, difficultyMode: '1', fpsCapDefault: '72', }; localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage document.getElementById("body-damage").checked = localSettings.isBodyDamage document.getElementById("difficulty-select").value = localSettings.difficultyMode document.getElementById("fps-select").value = localSettings.fpsCapDefault } //set up canvas var canvas = document.getElementById("canvas"); //using "const" causes problems in safari when an ID shares the same name. const ctx = canvas.getContext("2d"); document.body.style.backgroundColor = "#fff"; //disable pop up menu on right click document.oncontextmenu = function () { return false; } function setupCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; canvas.width2 = canvas.width / 2; //precalculated because I use this often (in mouse look) canvas.height2 = canvas.height / 2; canvas.diagonal = Math.sqrt(canvas.width2 * canvas.width2 + canvas.height2 * canvas.height2); ctx.font = "15px Arial"; ctx.lineJoin = "round"; ctx.lineCap = "round"; // ctx.lineCap='square'; game.setZoom(); } setupCanvas(); window.onresize = () => { setupCanvas(); }; //mouse move input document.body.addEventListener("mousemove", (e) => { game.mouse.x = e.clientX; game.mouse.y = e.clientY; }); document.body.addEventListener("mouseup", (e) => { // game.buildingUp(e); //uncomment when building levels // game.mouseDown = false; // console.log(e) if (e.which === 3) { game.mouseDownRight = false; } else { game.mouseDown = false; } }); document.body.addEventListener("mousedown", (e) => { if (e.which === 3) { game.mouseDownRight = true; } else { game.mouseDown = true; } }); //keyboard input const keys = []; document.body.addEventListener("keydown", (e) => { keys[e.keyCode] = true; if (mech.alive) game.keyPress(); }); document.body.addEventListener("keyup", (e) => { keys[e.keyCode] = false; }); document.body.addEventListener("wheel", (e) => { if (!game.paused) { if (e.deltaY > 0) { game.nextGun(); } else { game.previousGun(); } } }, { passive: true }); document.getElementById("fps-select").addEventListener("input", () => { let value = document.getElementById("fps-select").value if (value === 'max') { game.fpsCapDefault = 999999999; } else { game.fpsCapDefault = Number(value) } localSettings.fpsCapDefault = value localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage }); document.getElementById("body-damage").addEventListener("input", () => { game.isBodyDamage = document.getElementById("body-damage").checked localSettings.isBodyDamage = game.isBodyDamage localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage }); // difficulty-select-custom event listener is set in build.makeGrid document.getElementById("difficulty-select").addEventListener("input", () => { document.getElementById("difficulty-select-custom").value = document.getElementById("difficulty-select").value game.difficultyMode = Number(document.getElementById("difficulty-select").value) localSettings.difficultyMode = game.difficultyMode localStorage.setItem("localSettings", JSON.stringify(localSettings)); //update local storage }); //main loop ************************************************************ //********************************************************************** game.loop = game.normalLoop; function cycle() { if (!game.paused) requestAnimationFrame(cycle); const now = Date.now(); const elapsed = now - game.then; // calc elapsed time since last loop if (elapsed > game.fpsInterval) { // if enough time has elapsed, draw the next frame game.then = now - (elapsed % game.fpsInterval); // Get ready for next frame by setting then=now. Also, adjust for fpsInterval not being multiple of 16.67 game.cycle++; //tracks game cycles mech.cycle++; //tracks player cycles //used to alow time to stop for everything, but the player if (game.clearNow) { game.clearNow = false; game.clearMap(); level.start(); } game.loop(); // for (let i = 0, len = loop.length; i < len; i++) { // loop[i]() // } } }