diff --git a/js/bullet.js b/js/bullet.js index ff80835..294f84a 100644 --- a/js/bullet.js +++ b/js/bullet.js @@ -4925,7 +4925,7 @@ const b = { return tech.dynamoBotCount + tech.foamBotCount + tech.soundBotCount + tech.nailBotCount + tech.laserBotCount + tech.boomBotCount + tech.orbitBotCount + tech.plasmaBotCount + tech.missileBotCount }, hasBotUpgrade() { - return tech.isNailBotUpgrade + tech.isFoamBotUpgrade + tech.isBoomBotUpgrade + tech.isLaserBotUpgrade + tech.isOrbitBotUpgrade + tech.isDynamoBotUpgrade + return tech.isNailBotUpgrade + tech.isFoamBotUpgrade + tech.isBoomBotUpgrade + tech.isLaserBotUpgrade + tech.isOrbitBotUpgrade + tech.isDynamoBotUpgrade + tech.isSoundBotUpgrade }, convertBotsTo(type) { //type can be a string like "dynamoBotCount" const totalPermanentBots = b.totalBots() diff --git a/js/index.js b/js/index.js index 6d5d999..b852be9 100644 --- a/js/index.js +++ b/js/index.js @@ -1372,7 +1372,7 @@ window.addEventListener("keydown", function (event) { } break } - if (b.inventory.length > 1 && !simulation.testing) { + if (b.inventory.length > 1 && !simulation.testing && !tech.isGunCycle) { switch (event.code) { case "Digit1": simulation.switchToGunInInventory(0); diff --git a/js/level.js b/js/level.js index 83ee29c..1f9928b 100644 --- a/js/level.js +++ b/js/level.js @@ -10,7 +10,7 @@ const level = { // playableLevels: ["pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion", "pavilion"], //see level.populateLevels: (intro, ... , reservoir or factory, reactor, ... , subway, final) added later playableLevels: ["labs", "rooftops", "skyscrapers", "warehouse", "highrise", "office", "aerie", "satellite", "sewers", "testChamber", "pavilion", "lock"], - 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", "dojo", "tlinat", "ruins", "ace", "crimsonTowers"], + 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"], trainingLevels: ["walk", "crouch", "jump", "hold", "throw", "throwAt", "deflect", "heal", "fire", "nailGun", "shotGun", "superBall", "matterWave", "missile", "stack", "mine", "grenades", "harpoon", "diamagnetism"], levels: [], start() { @@ -27,7 +27,7 @@ const level = { // m.immuneCycle = Infinity //you can't take damage // tech.tech[297].frequency = 100 // m.couplingChange(10) - // m.setField("molecular assembler") //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 + // m.setField("standing wave") //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 // m.energy = 0 // simulation.molecularMode = 2 // m.damage(0.1); @@ -48,10 +48,10 @@ const level = { // for (let i = 0; i < 3; i++) powerUps.directSpawn(450, -50, "tech"); // for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "research"); // for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "coupling"); - // level.testing(); - // for (let i = 0; i < 10; ++i) spawn.sniper(1900, -500) - // for (let i = 0; i < 1; ++i) spawn.slasher2(1900, -500) + // level.testing(); + // for (let i = 0; i < 4; ++i) spawn.hopMother(1900, -500) + // for (let i = 0; i < 0; ++i) spawn.hopper(1900, -500) // for (let i = 0; i < 1; ++i) spawn.shooterBoss(1900, -2500) // spawn.suckerBoss(1900, -500, 25) // spawn.slasher2(2000, -1150) @@ -1915,7 +1915,7 @@ const level = { const mover = level.mover(2800, -300, 1000, 25); //x,y,width.height,VxGoal,force const train = level.transport(2900, -500, 500, 25, 8); //x,y,width.height,VxGoal,force - spawn.bodyRect(1900, -550, 50, 50); + // spawn.bodyRect(1900, -550, 50, 50); const button = level.button(2535, -200) // spawn.bodyRect(250, -450, 50, 50); //block on button @@ -5026,15 +5026,15 @@ const level = { powerUps.directSpawn(x + 50, y - 1525, "ammo"); powerUps.directSpawn(x + 1950, y - 1525, "ammo"); powerUps.directSpawn(x + 1900, y - 1525, "ammo"); - spawn.hopMomBoss(x + 800, y + -2200) + spawn.hopMotherBoss(x + 800, y + -2200) for (let i = 0; i < 6; ++i) spawn.hopBullet(x + 150 + 750 * Math.random(), y + -1600) for (let i = 0; i < 6; ++i) spawn.hopBullet(x + 1100 + 750 * Math.random(), y + -1600) spawn.hopper(x + 1550, y + -775); spawn.hopper(x + 500, y + -775); - spawn.hopper(x + 1400, y + -775); - spawn.hopper(x + 550, y + -775); - spawn.hopper(x + 525, y + -1475); - spawn.hopper(x + 1550, y + -1500); + spawn.hopMother(x + 1400, y + -775); + spawn.hopMother(x + 550, y + -775); + spawn.hopMother(x + 525, y + -1475); + spawn.hopMother(x + 1550, y + -1500); } } } @@ -25602,456 +25602,6 @@ const level = { }; powerUps.addResearchToLevel(); //needs to run after mobs are spawned }, - dojo() { // By - simulation.makeTextLog(`underpass by weird_pusheen`); - - const vanishes = []; - const smoofes = []; - const leftRotor = level.rotor(-550, 900, 950, 25); - leftRotor.frictionAir = 0.01; - var leftSchwoof = level.boost(-20, -60, -2000); - var rightSchwoof = level.button(2550, -50); - var rightSchwoofState = false; - var rightSchwoofLive = true; - spawn.mapRect(2513, -39, 200, 100); - var pathPoints = [ - [0, 0], // Index 0 is owned by M and is set to M's position during play - // this means that occasionally the boss will bonk M on the way to somewhere else, which gives it a chance to hurt M and gives the player a chance to hurt it - [250, -750], /* Left bases */ - [250, -2500], - [350, -1500], // Left doorway - [1150, -1500], // Home base - [1150, -2750], // Upper base - [1950, -1500], // Right doorway - [2050, -750], /* Right bases */ - [2050, -2500], - [-150, -250], // Left porthole - ]; - function isntIn(point, array) { - for (var x = 0; x < array.length; x++) { - if (point[0] == array[x][0] && point[1] == array[x][1]) { - return false; - } - } - return true; - } - function isObstructed(v1, v2) { - var ret = Matter.Query.ray(map, - { - x: v1[0], - y: v1[1], - }, - { - x: v2[0], - y: v2[1] - }).length != 0; - return ret; // Kinda-ish stolen from mob.js - } - function pythag(p1, p2) { - var dx = p1[0] - p2[0]; - var dy = p1[1] - p2[1]; - return Math.sqrt(dx * dx + dy * dy); - } - var path = undefined; // This is a stupid way to go about pathfinding code. I might even clean it up! - function pathFind(goalPoint, startPoint, curPath = []) { - var myPoint = startPoint; - if (curPath.length) { - myPoint = curPath[curPath.length - 1]; - } - if (path && (curPath.length >= path.length)) { // If we've already found a shorter or equal path, no reason to continue and waste CPU time - return; // Minimizes for HOP COUNT, not PATH LENGTH - path length was buggy - } - if (!isObstructed(myPoint, goalPoint)) { // If the line to the goal point ain't blocked by a map object, we've arrived! - path = [...curPath]; - path.push(goalPoint); - return; - } - pathPoints.forEach(testPoint => { - if (isntIn(testPoint, curPath)) { // If it's reusing points, there's clearly something wrong - if (!isObstructed(myPoint, testPoint)) { // If the line to the test point ain't blocked by a map object - var thing = [...curPath]; - thing.push(testPoint); - pathFind(goalPoint, startPoint, thing); // Branch to a valid test point - } - } - }); - } - level.setPosToSpawn(1200, 500); - level.exit.x = 51500; - level.exit.y = -1875; - spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); - level.defaultZoom = 1500; - simulation.zoomTransition(level.defaultZoom) - document.body.style.backgroundColor = "#d8dadf"; - - spawn.mapRect(-500, 0, 3300, 300); // Floor - spawn.mapRect(-100, -3000, 2500, 100); // Ceiling - spawn.mapRect(-200, -3000, 100, 2600); // Left wall - spawn.mapRect(2400, -3000, 100, 3000); // Right wall - - spawn.mapRect(500, -1000, 100, 500); /* obstruction blocks */ - smoofes.push(map[map.length - 1]); - spawn.mapRect(500, -2500, 100, 500); - smoofes.push(map[map.length - 1]); - spawn.mapRect(1700, -1000, 100, 500); - smoofes.push(map[map.length - 1]); - spawn.mapRect(1700, -2500, 100, 500); - smoofes.push(map[map.length - 1]); - - spawn.mapRect(-1000, 550, 200, 50); // Left chonky stepppp low - spawn.mapRect(-800, 300, 200, 50); // Left chonky stepppp high - spawn.mapVertex(-1000, 1200, "0 0 100 0 700 500 700 700 0 700"); // Left chonky - spawn.mapRect(3100, 550, 200, 50); // Right chonky stepppp low - spawn.mapRect(2900, 300, 200, 50); // Right chonky stepppp high - spawn.mapVertex(3300, 1200, "0 0 -100 0 -700 500 -700 700 0 700"); // Right chonky - const leftElevator = level.elevator(-1400 - 300, 1450, 300, 100, 500); - const rightElevator = level.elevator(-1400 + 5100, 1450, 300, 100, 500); - - spawn.mapRect(-150, -1700, 200, 50); - spawn.mapRect(400, -2050, 200, 50); - spawn.mapRect(1600, -1000, 200, 50); - - spawn.randomMob(1200, 700); - spawn.randomMob(600, 1000); - spawn.randomMob(1800, 1000); - spawn.randomMob(3200, 400); - spawn.randomMob(3000, 200); - spawn.randomMob(-900, 400); - spawn.randomMob(-700, 200); - spawn.randomMob(1200, 1000); - for (var i = 0; i < 4; i++) { - spawn.randomSmallMob(Math.random() * 600 - 600, Math.random() * 3000 - 400); - } - spawn.grenadier(-300, -1000); - spawn.grenadier(2600, -1000); - - spawn.mapRect(-1400, 1450, 5100, 100); // The True Floor - - const slime = level.hazard(-1250, 1400, 4800, 50); - slime.maxHeight = 600; - simulation.draw.body = function () { - ctx.beginPath(); - for (let i = 0, len = body.length; i < len; ++i) { - if (!body[i].hidden) { - let vertices = body[i].vertices; - ctx.moveTo(vertices[0].x, vertices[0].y); - for (let j = 1; j < vertices.length; j++) { - ctx.lineTo(vertices[j].x, vertices[j].y); - } - ctx.lineTo(vertices[0].x, vertices[0].y); - } - } - ctx.lineWidth = 2; - ctx.fillStyle = color.block; - ctx.fill(); - ctx.strokeStyle = color.blockS; - ctx.stroke(); - } // Override the old draw code to allow intelligent hiding of blocks - preferably this becomes official code because it's just a single added if statement and makes a lot of things cleaner and more intelligent - - const vanish = function (x, y, width, height) { // normal vanishes don't work well on my map for some reason, so I rewrote - x += width / 2; - y += height / 2; - const getVertices = function (bX, bY, bW, bH) { return [{ x: bX, y: bY, index: 0, isInternal: false }, { x: bX + bW, y: bY, index: 1, isInternal: false }, { x: bX + bW, y: bY + bH, index: 4, isInternal: false }, { x: bX, y: bY + bH, index: 3, isInternal: false }] }; - const cMask = cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet; - const vertices = getVertices(x, y, width, height); - const block = body[body.length] = Bodies.fromVertices(x, y, vertices, { - collisionFilter: { - category: cat.map, - mask: cMask - }, - isNoSetCollision: true, - inertia: Infinity, //prevents rotation - isNotHoldable: true, - isNonStick: true, //this keep sporangium from sticking - isTouched: false, - cWidth: width, - hiddenCycle: 0, - isStatic: true, - query() { - if (this.cWidth <= 0) { - if (this.cWidth > -100) { - this.cWidth = -100; - Matter.Body.setVertices(this, vertices); - } - this.isTouched = false; - this.collisionFilter.mask = undefined; - this.hidden = true; - this.hiddenCycle++; - if (this.hiddenCycle > 100) { - if (Matter.Query.collides(this, [player]).length) { - this.hiddenCycle = 50; - } - else { - this.hiddenCycle = 0; - this.cWidth = width; - this.collisionFilter.mask = cMask; - this.hidden = false; - } - } - } - else if (this.isTouched) { - Matter.Body.setVertices(this, getVertices(x, y, this.cWidth, height * (this.cWidth / width))); - this.cWidth -= 3; - } - else if (Matter.Query.collides(this, [player]).length) { // Elseif short circuit avoids expensive collision detection - this.isTouched = true; - } - } - }); - return block; - }; - - vanishes.push(vanish(800, 800, 800, 50)); - vanishes.push(vanish(400, 1100, 400, 50)); - vanishes.push(vanish(1600, 1100, 400, 50)); - spawn.bodyRect(1700, 812, 300, 25, 1, { - collisionFilter: { - category: cat.body, - mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet | cat.map - }, - isNoSetCollision: true, - isNotHoldable: true, - isNonStick: true, //this keep sporangium from sticking - restitution: 1, - friction: 0, - frictionAir: 0, - frictionStatic: 0, - query() { - Matter.Body.setAngularVelocity(this, 0); - Matter.Body.applyForce(this, this.position, { - x: 0, - y: -(this.position.y - 812) * 0.002 - }); - } - }); - const zigzag = body[body.length - 1]; - Matter.Body.applyForce(zigzag, zigzag.position, { - x: 0.1, - y: 0 - }); - var buttonWasDown = false; - level.customTopLayer = () => { - - } - level.custom = () => { - rightSchwoof.isUp = false; - level.exit.drawAndCheck(); - leftSchwoof.query(); - level.enter.draw(); - pathPoints[0][0] = m.pos.x; - pathPoints[0][1] = m.pos.y; - leftElevator.move(); - rightElevator.move(); - slime.query(); - zigzag.query(); - slime.levelRise(0.2); - for (var i = 0; i < vanishes.length; i++) { - vanishes[i].query(); - } - if (!rightSchwoofState) { - var math = m.pos.y < leftRotor.position.y; - Matter.Body.setAngularVelocity(leftRotor, (math ? 1 : -1) * Math.PI / 45); - } - if (rightSchwoofLive) { - rightSchwoof.query(); - rightSchwoof.draw(); - if (rightSchwoofState) { - ctx.fillStyle = "lightgreen"; - } - else { - ctx.fillStyle = "red"; - } - ctx.beginPath(); - ctx.arc(2615, -220, 40, 0, Math.PI * 2); - ctx.fill(); - } - if (rightSchwoof.isUp) { - buttonWasDown = true; - } - else if (buttonWasDown) { - buttonWasDown = false; - rightSchwoofState = !rightSchwoofState; - } - if (Matter.Query.collides(player, smoofes).length) { - Matter.Body.applyForce(player, player.position, { - x: 0, - y: -0.015 - }); - } - }; - - mobs.spawn(500, -500, 10, 100, "yellow"); /* TacticalBoss - Modes: - Spawn: - Pathfinds to a point above M and starts dropping mobs. Learns which mobs to drop to cause the most damage, of course. - Occasionally strikes at M. - Hide: - Pathfinds to the point furthest from M - Strike: - Pathfind really, really fast to M - Recharge: - Stop moving for a bit to "recharge" (this is so the player has a chance to hit it) - - It must always Hide or Recharge after Spawning or Striking. Which one it does is based on some factor I'll figure out. - Pathfinding is a hypersimplified algorithm with hard-coded "points" that it can travel between. M is one of these. - */ - var boss = mob[mob.length - 1]; - boss.isBoss = true; - boss.damageReduction = 0.2 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1) - boss.onDeath = function () { - powerUps.spawnBossPowerUp(this.position.x, this.position.y); - level.exit.x = 2560; - level.exit.y = -90; - rightSchwoofLive = false; - }; - var spawnables = {}; - ["hopper", "stabber", "springer", "striker", "sneaker", "grower"].forEach((m) => { /* Used to be spawn.fullPickList, but some of those mobs don't do collision-only damage and would thus never be properly selected for */ - if (spawn[m]) { - spawnables[m] = { - fun: spawn[m], - name: m, - weight: 1 - } - } - }); - boss.stabCycle = 0; - boss.spawnCycle = 0; - function spawny() { - var totalWeight = 0; - Object.keys(spawnables).forEach(key => { - totalWeight += spawnables[key].weight; - }); - var cursorWeight = 0; - var choice = Math.random(); - var mC = undefined; - Object.values(spawnables).forEach((thing) => { - var lower = cursorWeight / totalWeight; - cursorWeight += thing.weight; - var upper = cursorWeight / totalWeight; - if ((choice > lower && choice <= upper) || !mC) { - mC = thing; - } - }); - mC.fun(boss.position.x, boss.position.y); - var sp = mob[mob.length - 1]; - sp.typeName = mC.name; - sp.onHit = () => { - spawnables[sp.typeName].weight += 1; - }; - var oldFun = sp.onDeath; - sp.onDeath = () => { /* Mobs that die are worth less */ - oldFun.call(sp); - spawnables[sp.typeName].weight -= 0.3; /* But not too much less */ - }; - } - boss.spawnDelay = 40; - boss.mode = "hide"; - boss.modeSwitch = -1; // Randomize mode immediately - boss.damageReduction = 0.1; - var oldOnHit = boss.onHit; - boss.onHit = () => { - boss.modeSwitch = -1; // After striking the player, always switch modes - oldOnHit.call(boss); - }; - boss.do = () => { - path = undefined; - var pfGoal = [0, 0]; - boss.modeSwitch--; - if (boss.modeSwitch < 0) { - if (!boss.isShielded) { - spawn.shield(boss, boss.position.x, boss.position.y, 0.75); // Every time the mode switches, have a 75% chance to gain a new shield - } - if (boss.mode == "hide" || boss.mode == "recharge") { - if (Math.random() > 0.5) { - boss.mode = "spawn"; - } - else { - boss.mode = "strike"; - } - boss.modeSwitch = 600; - } - else { - if (boss.mode == "strike") { - boss.mode = "hide"; // Always hides after striking - } - else { - if (Math.random() > 0.5) { - boss.mode = "hide"; - } - else { - boss.mode = "recharge"; // same when it goes into recharge mode - spawn.shield(boss, boss.position.x, boss.position.y, 1); - } - } - boss.modeSwitch = 200; - } - } - if (boss.mode == "hide") { /* Find the furthest point from M and get to it */ - var longest = 0; - pathPoints.forEach(item => { - if (item[0] == 1150) { - return; - } - var iL = pythag(item, [m.pos.x, m.pos.y]); - if (iL > longest) { - longest = iL; - pfGoal = item; - } - }); - } - else if (boss.mode == "strike") { - pfGoal = pathPoints[0]; // Target M - } - else if (boss.mode == "spawn") { - pfGoal = pathPoints[4]; // Go to Home Base to spawn - } - if (boss.mode != "recharge") { - if (m.pos.x > 2350 || m.pos.x < -150 || m.pos.y > 50) { - boss.mode = "hide"; - } - pathFind(pfGoal, [boss.position.x, boss.position.y]); - if (!path) { - return; // If it couldn't pathfind, just drift - } - var goalX = path[0][0]; - var goalY = path[0][1]; - - var dX = goalX - boss.position.x; - var dY = goalY - boss.position.y; - var hyp = Math.sqrt(dX * dX + dY * dY); - Matter.Body.applyForce(boss, { - x: goalX, - y: goalY - }, { - x: dX / hyp * 0.04 * (boss.mode == "strike" ? 2 : 1), - y: dY / hyp * 0.04 * (boss.mode == "strike" ? 2 : 1)// - 0.005 - }); - } - if (boss.mode == "spawn") { - boss.stabCycle++; - if (boss.stabCycle > 25) { - if (Math.abs(dX) < 200 && dY > 0) { - Matter.Body.applyForce(boss, { - x: player.position.x, - y: player.position.y - }, { - x: 0, - y: 5 - }); - } - boss.stabCycle = 0; - } - boss.spawnCycle++; - if (boss.spawnCycle > boss.spawnDelay) { - spawny(); - boss.spawnDelay += 4; - boss.spawnCycle = 0; - } - } - }; - boss.showHealthBar = true; - powerUps.addResearchToLevel() //needs to run after mobs are spawned - }, tlinat() { // _Destined_ formerly Richard0820#2652 simulation.makeTextLog(`tlinat by Richard0820`); simulation.fallHeight = 1 / 0, level.setPosToSpawn(0, -1e3), level.exit.x = 5100, level.exit.y = 3770, spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20), spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 20), level.defaultZoom = 3000, simulation.zoomTransition(level.defaultZoom), document.body.style.backgroundColor = "#d8dadf"; diff --git a/js/powerup.js b/js/powerup.js index da882e1..8fef7cb 100644 --- a/js/powerup.js +++ b/js/powerup.js @@ -733,6 +733,9 @@ const powerUps = { if (localSettings.isHideImages) { document.getElementById("choose-grid").style.gridTemplateColumns = width text += powerUps.researchAndCancelText(type) + } else if (totalChoices === 0) { + document.getElementById("choose-grid").style.gridTemplateColumns = width + text += powerUps.researchAndCancelText(type) } else if (totalChoices === 1 || canvas.width < 1200) { document.getElementById("choose-grid").style.gridTemplateColumns = width text += powerUps.researchAndCancelText(type) @@ -903,39 +906,39 @@ const powerUps = { if (b.guns[i].isRecentlyShown) removeOption(i) } for (let i = 0; i < b.guns.length; i++) b.guns[i].isRecentlyShown = false //reset recently shown back to zero - if (options.length > 0) { - let text = powerUps.buildColumns(totalChoices, "gun") - for (let i = 0; i < totalChoices; i++) { - const choose = options[Math.floor(Math.seededRandom(0, options.length))] //pick an element from the array of options - // text += `
  ${b.guns[choose].name}
${b.guns[choose].description}
` - text += powerUps.gunText(choose, `powerUps.choose('gun',${choose})`) + // if (options.length > 0) { + let text = powerUps.buildColumns(totalChoices, "gun") + for (let i = 0; i < totalChoices; i++) { + const choose = options[Math.floor(Math.seededRandom(0, options.length))] //pick an element from the array of options + // text += `
  ${b.guns[choose].name}
${b.guns[choose].description}
` + text += powerUps.gunText(choose, `powerUps.choose('gun',${choose})`) - b.guns[choose].isRecentlyShown = true - removeOption(choose) - if (options.length < 1) break + b.guns[choose].isRecentlyShown = true + removeOption(choose) + if (options.length < 1) break + } + if (tech.isExtraBotOption) { + const botTech = [] //make an array of bot options + for (let i = 0, len = tech.tech.length; i < len; i++) { + if (tech.tech[i].isBotTech && tech.tech[i].count < tech.tech[i].maxCount && tech.tech[i].allowed()) botTech.push(i) } - if (tech.isExtraBotOption) { - const botTech = [] //make an array of bot options - for (let i = 0, len = tech.tech.length; i < len; i++) { - if (tech.tech[i].isBotTech && tech.tech[i].count < tech.tech[i].maxCount && tech.tech[i].allowed()) botTech.push(i) - } - if (botTech.length > 0) { //pick random bot tech - // const choose = botTech[Math.floor(Math.random() * botTech.length)]; - // const isCount = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count+1}x)` : ""; - // text += `
⭓▸●■   ${tech.tech[choose].name} ${isCount}
${tech.tech[choose].descriptionFunction ? tech.tech[choose].descriptionFunction() : tech.tech[choose].description}
` - const choose = botTech[Math.floor(Math.random() * botTech.length)]; - const techCountText = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count + 1}x)` : ""; - const style = localSettings.isHideImages ? powerUps.hideStyle : `style="background-image: url('img/${tech.tech[choose].name}.webp');"` - text += `
+ if (botTech.length > 0) { //pick random bot tech + // const choose = botTech[Math.floor(Math.random() * botTech.length)]; + // const isCount = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count+1}x)` : ""; + // text += `
⭓▸●■   ${tech.tech[choose].name} ${isCount}
${tech.tech[choose].descriptionFunction ? tech.tech[choose].descriptionFunction() : tech.tech[choose].description}
` + const choose = botTech[Math.floor(Math.random() * botTech.length)]; + const techCountText = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count + 1}x)` : ""; + const style = localSettings.isHideImages ? powerUps.hideStyle : `style="background-image: url('img/${tech.tech[choose].name}.webp');"` + text += `
⭓▸●■   ${tech.tech[choose].name} ${techCountText}
${tech.tech[choose].descriptionFunction ? tech.tech[choose].descriptionFunction() : tech.tech[choose].description}
` - } } - if (tech.isOneGun && b.inventory.length > 0) text += `
replaces your current gun
` - document.getElementById("choose-grid").innerHTML = text - powerUps.showDraft(); } + if (tech.isOneGun && b.inventory.length > 0) text += `
replaces your current gun
` + document.getElementById("choose-grid").innerHTML = text + powerUps.showDraft(); + // } } }, }, diff --git a/js/spawn.js b/js/spawn.js index c03b02d..01ef73c 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -1,7 +1,7 @@ //main object for spawning things in a level const spawn = { nonCollideBossList: ["cellBossCulture", "bomberBoss", "powerUpBoss", "growBossCulture"], - // other bosses: suckerBoss, laserBoss, tetherBoss, bounceBoss, sprayBoss, mineBoss, hopMomBoss //these need a particular level to work so they are not included in the random pool + // other bosses: suckerBoss, laserBoss, tetherBoss, bounceBoss, sprayBoss, mineBoss, hopMotherBoss //these need a particular level to work so they are not included in the random pool randomBossList: [ "orbitalBoss", "historyBoss", "shooterBoss", "cellBossCulture", "bomberBoss", "spiderBoss", "launcherBoss", "laserTargetingBoss", "powerUpBoss", "powerUpBossBaby", "streamBoss", "pulsarBoss", "spawnerBossCulture", "grenadierBoss", "growBossCulture", "blinkBoss", @@ -21,8 +21,8 @@ const spawn = { pickList: ["starter", "starter"], fullPickList: [ "slasher", "slasher", "slasher2", "slasher3", + "hopper", "hopper", "hopMother", "hopMother", "flutter", "flutter", "flutter", - "hopper", "hopper", "hopper", "stabber", "stabber", "stabber", "springer", "springer", "springer", "shooter", "shooter", @@ -2343,10 +2343,7 @@ const spawn = { const springStiffness = 0.00014; const springDampening = 0.0005; - me.springTarget = { - x: me.position.x, - y: me.position.y - }; + me.springTarget = { x: me.position.x, y: me.position.y }; const len = cons.length; cons[len] = Constraint.create({ pointA: me.springTarget, @@ -2359,10 +2356,7 @@ const spawn = { cons[len].length = 100 + 1.5 * radius; me.cons = cons[len]; - me.springTarget2 = { - x: me.position.x, - y: me.position.y - }; + me.springTarget2 = { x: me.position.x, y: me.position.y }; const len2 = cons.length; cons[len2] = Constraint.create({ pointA: me.springTarget2, @@ -2379,15 +2373,15 @@ const spawn = { this.checkStatus(); this.springAttack(); }; - me.onDeath = function () { this.removeCons(); }; spawn.shield(me, x, y); }, - hopper(x, y, radius = 30 + Math.ceil(Math.random() * 30)) { + hopper(x, y, radius = 35 + Math.ceil(Math.random() * 30)) { mobs.spawn(x, y, 5, radius, "rgb(0,200,180)"); let me = mob[mob.length - 1]; + Matter.Body.setDensity(me, 0.0015); //normal is 0.001 me.accelMag = 0.05; me.g = 0.0032; //required if using this.gravity me.frictionAir = 0.01; @@ -2425,6 +2419,116 @@ const spawn = { } }; }, + hopMother(x, y, radius = 20 + Math.ceil(Math.random() * 20)) { + mobs.spawn(x, y, 5, radius, "rgb(50,170,200)"); + let me = mob[mob.length - 1]; + Matter.Body.setDensity(me, 0.0008); //normal is 0.001 + me.accelMag = 0.05; + me.g = 0.0032; //required if using this.gravity + me.frictionAir = 0.01; + me.friction = 1 + me.frictionStatic = 1 + me.restitution = 0; + me.delay = 120 + 110 * simulation.CDScale; + me.randomHopFrequency = 300 + Math.floor(Math.random() * 150); + me.randomHopCD = simulation.cycle + me.randomHopFrequency; + Matter.Body.rotate(me, Math.random()); + spawn.shield(me, x, y); + me.dropEgg = function () { + if (mob.length < 360) { + let where = { x: this.position.x, y: this.position.y + 0.3 * radius } + for (let i = 0; i < 30; i++) { //find the ground + if (Matter.Query.point(map, where).length > 0 || Matter.Query.point(body, where).length > 0) break + where.y += 1 + } + spawn.hopEgg(where.x, where.y - 10) + } + } + me.do = function () { + this.gravity(); + this.seePlayerCheck(); + this.checkStatus(); + if (this.seePlayer.recall) { + if (this.cd < simulation.cycle && (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length)) { + this.cd = simulation.cycle + this.delay; + const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass; + const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x); + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle) - (Math.random() * 0.06 + 0.1) * this.mass; //antigravity + this.dropEgg(); + } + } else { + //randomly hob if not aware of player + if (this.randomHopCD < simulation.cycle && (Matter.Query.collides(this, map).length || Matter.Query.collides(this, body).length)) { + this.randomHopCD = simulation.cycle + this.randomHopFrequency; + //slowly change randomHopFrequency after each hop + this.randomHopFrequency = Math.max(100, this.randomHopFrequency + (0.5 - Math.random()) * 200); + const forceMag = (this.accelMag + this.accelMag * Math.random()) * this.mass * (0.1 + Math.random() * 0.3); + const angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI; + this.force.x += forceMag * Math.cos(angle); + this.force.y += forceMag * Math.sin(angle) - 0.07 * this.mass; //antigravity + if (Math.random() < 0.2) this.dropEgg(); + } + } + }; + }, + hopEgg(x, y) { + mobs.spawn(x, y, 10, 9 + Math.floor(3 * Math.random()), "rgba(50, 150, 150,0.3)"); //"rgb(100,170,150)" //"rgb(61, 125, 121)" + let me = mob[mob.length - 1]; + me.stroke = "transparent"; + Matter.Body.setDensity(me, 0.0001); //normal is 0.001 + // Matter.Body.setStatic(me, true); //make static (disables taking damage) + me.frictionAir = 1 + me.damageReduction = 2 + me.collisionFilter.mask = cat.bullet //| cat.body + // me.collisionFilter.category = cat.mobBullet; + // me.collisionFilter.mask = cat.bullet | cat.body // | cat.player + me.isMine = true + me.leaveBody = false; + me.isDropPowerUp = false; + me.isBadTarget = true; + me.isMobBullet = true; + me.isUnstable = true; //dies when blocked + me.showHealthBar = false; + me.explodeRange = 210 + 140 * Math.random() + me.isExploding = false + me.countDown = Math.ceil(4 * Math.random()) + me.hatchTimer = 600 + Math.floor(120 * Math.random()) + me.isInvulnerable = true //not actually invulnerable, just prevents block + ice-9 interaction + me.do = function () { + this.checkStatus(); + this.hatchTimer-- + if (this.hatchTimer < 1) { + spawn.hopBullet(this.position.x, this.position.y) + this.death(); + } + if (Matter.Query.collides(this, [player]).length > 0) this.isExploding = true + if (this.isExploding) { + if (this.countDown-- < 0) { //explode + this.death(); + //hit player + if (Vector.magnitude(Vector.sub(this.position, player.position)) < this.explodeRange && m.immuneCycle < m.cycle) { + m.damage(0.01 * simulation.dmgScale * (tech.isRadioactiveResistance ? 0.25 : 1)); + m.energy -= 0.1 * (tech.isRadioactiveResistance ? 0.25 : 1) + if (m.energy < 0) m.energy = 0 + } + const range = this.explodeRange + 50 //mines get a slightly larger range to explode + for (let i = 0, len = mob.length; i < len; ++i) { + if (mob[i].alive && Vector.magnitude(Vector.sub(this.position, mob[i].position)) < range && mob[i].isMine) { + mob[i].isExploding = true //explode other mines + } + } + simulation.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: this.explodeRange, + color: "rgba(50,180,180,0.45)", + time: 16 + }); + } + } + }; + }, hopBullet(x, y, radius = 10 + Math.ceil(Math.random() * 8)) { mobs.spawn(x, y, 5, radius, "rgb(0,200,180)"); let me = mob[mob.length - 1]; @@ -2434,7 +2538,7 @@ const spawn = { // me.isBadTarget = true; me.isMobBullet = true; me.showHealthBar = false; - me.timeLeft = 1200 + Math.floor(600 * Math.random()); + me.timeLeft = 1140 + Math.floor(480 * Math.random()); me.isRandomMove = Math.random() < 0.3 //most chase player, some don't me.accelMag = 0.01; //jump height @@ -2448,7 +2552,7 @@ const spawn = { me.collisionFilter.category = cat.mobBullet; me.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet; me.onHit = function () { - this.explode(this.mass); + this.explode(0.5 * this.mass); }; me.do = function () { this.gravity(); @@ -2465,18 +2569,18 @@ const spawn = { this.timeLimit(); }; }, - hopMomBoss(x, y, radius = 120) { + hopMotherBoss(x, y, radius = 120) { mobs.spawn(x, y, 5, radius, "rgb(0,200,180)"); let me = mob[mob.length - 1]; me.isBoss = true; - me.damageReduction = 0.08 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1) + me.damageReduction = 0.09 / (tech.isScaleMobsWithDuplication ? 1 + tech.duplicationChance() : 1) me.accelMag = 0.05; //jump height me.g = 0.003; //required if using this.gravity me.frictionAir = 0.01; me.friction = 1 me.frictionStatic = 1 me.restitution = 0; - me.delay = 120 + 40 * simulation.CDScale; + me.delay = 130 + 40 * simulation.CDScale; Matter.Body.rotate(me, Math.random() * Math.PI); spawn.shield(me, x, y, 1); me.onDeath = function () { diff --git a/todo.txt b/todo.txt index ee0e928..010889f 100644 --- a/todo.txt +++ b/todo.txt @@ -1,24 +1,14 @@ ******************************************************** NEXT PATCH ************************************************** -missile Bot: +10% bigger explosions, +10% damage, +7% fire rate -plasma Bot: +40% damage, and drains 2% less energy -1st ionization energy: 8->11 energy per heal -mass-energy: defense reduction factor 0.13->0.33 -neutron bomb: +25% damage -non-renewables: 78->88% damage -junk DNA: applies to all damage, not just spores -pseudoscience: adds (1-4)->(1-3) JUNK to tech pool per free research -futures exchange: 4.1->4.3 duplication per cancel - -default skin has slightly more narrow legs - -added ammo to gun descriptions - calculate the ammo drop rate for each gun live based on ammo tech? - need to make a generic function +new mob type: hopMother - hoppers that drop eggs that explode on contact and after several seconds they hatch into baby hoppers + regular hopper mobs have more life and more damage +a few bug fixes *********************************************************** TODO ***************************************************** +a mob/boss? that drops the mines from reactor and final boss + more (all) bosses need to be made of parts good examples: spiderBoss, dragonFlyBoss, beetleBoss methods: