From c9a5ab91b8186ab95d63e599a8d24cf932098525 Mon Sep 17 00:00:00 2001 From: landgreen Date: Sat, 18 Jan 2025 17:00:10 -0800 Subject: [PATCH] combos mantisBoss flashes for a second before it drops invulnerability removed parasitism - it's too similar to invulnerability tech invariant no longer drains energy while wormhole time is paused added 1 research cost added secret combo to change molecular assembler mode bug fixes issue with constraint: "mob death heals mobs" mob health was becoming NaN, this was infecting other values like player energy entering a seed in settings wasn't giving the same results as a randomly generated seeds also removed some random code that was using seeded shuffle, but didn't need to converted it to non seeded random shuffle with .sort(() => Math.random() - 0.5); --- img/parasitism.webp | Bin 53360 -> 0 bytes js/bullet.js | 4 +- js/index.js | 8 +- js/level.js | 28 +- js/mob.js | 8 +- js/player.js | 4385 ++++++++++++++++++++++--------------------- js/powerup.js | 1 + js/simulation.js | 2 +- js/spawn.js | 35 +- js/tech.js | 96 +- todo.txt | 143 +- 11 files changed, 2347 insertions(+), 2363 deletions(-) delete mode 100644 img/parasitism.webp diff --git a/img/parasitism.webp b/img/parasitism.webp deleted file mode 100644 index 3db9826d8c644ec9fdb12e6e429feb9345251f1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53360 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

#ejCb%^0Sc{@fGfL%ETu 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 0.93 * m.maxHealth && !tech.isDroneGrab && powerUp[i].name === "heal") || + (m.health > 0.94 * m.maxHealth && !tech.isOverHeal && !tech.isDroneGrab && powerUp[i].name === "heal") || (tech.isSuperDeterminism && powerUp[i].name === "field") || ((tech.isEnergyNoAmmo || b.inventory.length === 0) && powerUp[i].name === "ammo") ) @@ -3192,7 +3192,7 @@ const b = { let closeDist = Infinity; for (let i = 0, len = powerUp.length; i < len; ++i) { if (!( - (m.health > 0.93 * m.maxHealth && !tech.isDroneGrab && powerUp[i].name === "heal") || + (m.health > 0.94 * m.maxHealth && !tech.isOverHeal && !tech.isDroneGrab && powerUp[i].name === "heal") || (tech.isSuperDeterminism && powerUp[i].name === "field") || ((tech.isEnergyNoAmmo || b.inventory.length === 0) && powerUp[i].name === "ammo") )) { diff --git a/js/index.js b/js/index.js index e36a298..768bd67 100644 --- a/js/index.js +++ b/js/index.js @@ -28,11 +28,11 @@ Math.seededRandom = function (min = 0, max = 1) { // in order to work 'Math.seed // console.log(Math.seed) -function shuffle(array) { +function seededShuffle(array) { var currentIndex = array.length, temporaryValue, randomIndex; - // While there remain elements to shuffle... + // While there remain elements while (0 !== currentIndex) { // Pick a remaining element... // randomIndex = Math.floor(Math.random() * currentIndex); @@ -542,7 +542,7 @@ ${simulation.difficultyMode > 4 ? `

console log
-
${document.getElementById("text-log").innerHTML}
+
${document.getElementById("text-log").innerHTML}
` @@ -1205,6 +1205,7 @@ const input = { left: false, right: false, isPauseKeyReady: true, + // lastDown: null, key: { fire: "KeyF", field: "Space", @@ -1379,6 +1380,7 @@ window.addEventListener("keyup", function (event) { }); window.addEventListener("keydown", function (event) { + // input.lastDown = event.code // console.log(event.code) switch (event.code) { case input.key.right: diff --git a/js/level.js b/js/level.js index 85eb04b..7c70f51 100644 --- a/js/level.js +++ b/js/level.js @@ -22,7 +22,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 = 10 + // level.levelsCleared = 9 // level.updateDifficulty() // tech.giveTech("performance") // m.maxHealth = m.health = 100000000 @@ -33,7 +33,7 @@ const level = { // tech.tech[297].frequency = 100 // tech.addJunkTechToPool(0.5) // m.couplingChange(10) - // m.setField("negative mass") //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("wormhole") //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 @@ -58,7 +58,7 @@ const level = { // requestAnimationFrame(() => { for (let i = 0; i < 1; i++) tech.giveTech("interest") }); // for (let i = 0; i < 1; i++) tech.giveTech("interest") // m.lastKillCycle = m.cycle - // for (let i = 0; i < 7; i++) powerUps.directSpawn(450, -50, "tech"); + // for (let i = 0; i < 7; i++) powerUps.directSpawn(450, -50, "field"); // 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.testing(); @@ -810,14 +810,14 @@ const level = { if (document.getElementById("seed").value) { //check for player entered seed in settings Math.initialSeed = String(document.getElementById("seed").value) - Math.seed = Math.abs(Math.hash(Math.initialSeed)) //update randomizer seed in case the player changed it } + Math.seed = Math.abs(Math.hash(Math.initialSeed)) //update randomizer seed if (simulation.isTraining) { simulation.isHorizontalFlipped = false level.levels = level.trainingLevels.slice(0) //copy array, not by just by assignment if (simulation.isCommunityMaps) level.trainingLevels.push("diamagnetism") - } else { //add remove and shuffle levels for the normal game (not training levels) + } else { level.levels = level.playableLevels.slice(0) //copy array, not by just by assignment if (simulation.isCommunityMaps) { level.levels = level.levels.concat(level.communityLevels) @@ -825,7 +825,7 @@ const level = { } else { simulation.isHorizontalFlipped = (Math.seededRandom() < 0.5) ? true : false //if true, some maps are flipped horizontally } - level.levels = shuffle(level.levels); //shuffles order of maps with seeded random + level.levels = seededShuffle(level.levels); //shuffles order of maps with seeded random level.levels.length = 9 //remove any extra levels past 9 pick = ["interferometer", "factory", "reservoir"] level.levels.splice(Math.floor(Math.seededRandom(level.levels.length * 0.6, level.levels.length)), 0, pick[Math.floor(Math.random() * pick.length)]); //add level to the back half of the randomized levels list @@ -3533,7 +3533,7 @@ const level = { const stationList = [] //use to randomize station order for (let i = 1, totalNumberOfStations = 10; i < totalNumberOfStations; ++i) stationList.push(i) //!!!! update station number when you add a new station - shuffle(stationList); + stationList.sort(() => Math.random() - 0.5); stationList.splice(0, 3); //remove some stations to keep it to 4 stations stationList.unshift(0) //add index zero to the front of the array @@ -6690,14 +6690,14 @@ const level = { //3x2: 4 short rooms (3000x1500), 1 double tall room (3000x3000) //rooms let rooms = ["exit", "loot", "enter", "empty"] - rooms = shuffle(rooms); //shuffles array order + rooms.sort(() => Math.random() - 0.5); //look... you and I both know there is a better way to do this, but it works so I'm gonna focus on other things while ( //makes sure that the exit and entrance aren't both on the same floor (rooms[0] === "enter" && rooms[2] === "exit") || (rooms[2] === "enter" && rooms[0] === "exit") || (rooms[1] === "enter" && rooms[3] === "exit") || (rooms[3] === "enter" && rooms[1] === "exit") - ) rooms = shuffle(rooms); //shuffles array order + ) rooms.sort(() => Math.random() - 0.5); for (let i = 0; i < rooms.length; i++) { if (rooms[i] === "enter") rooms[i] = enter if (rooms[i] === "exit") rooms[i] = exit @@ -6764,7 +6764,7 @@ const level = { rooms[3]() }, ] - columns = shuffle(columns) //********************************* RUN THIS LINE IN THE FINAL VERSION *************************************** + columns.sort(() => Math.random() - 0.5); for (let i = 0; i < 3; i++) { if (i === 0) { isDoorLeft = false @@ -7054,7 +7054,7 @@ const level = { }; powerUps.spawnStartingPowerUps(1875, -3075); - const powerUpPos = shuffle([{ //no debris on this level but 2 random spawn instead + const powerUpPos = [{ //no debris on this level but 2 random spawn instead x: -150, y: -1775 }, { @@ -7066,7 +7066,8 @@ const level = { }, { x: 1325, y: -150 - }]); + }]; + powerUpPos.sort(() => Math.random() - 0.5); powerUps.chooseRandomPowerUp(powerUpPos[0].x, powerUpPos[0].y); powerUps.chooseRandomPowerUp(powerUpPos[1].x, powerUpPos[1].y); //outer wall @@ -8858,7 +8859,8 @@ const level = { // spawn.bodyRect(312, -100, 25, 100); spawn.bodyRect(1450, -300, 150, 50); - const xPos = shuffle([600, 1250, 2000]); + const xPos = [600, 1250, 2000]; + xPos.sort(() => Math.random() - 0.5); spawn.mapRect(xPos[0], -200, 300, 100); spawn.mapRect(xPos[1], -250, 300, 300); spawn.mapRect(xPos[2], -150, 300, 200); diff --git a/js/mob.js b/js/mob.js index df145f9..9027446 100644 --- a/js/mob.js +++ b/js/mob.js @@ -1069,7 +1069,11 @@ const mobs = { if (tech.isFarAwayDmg) dmg *= 1 + Math.sqrt(Math.max(500, Math.min(3000, this.distanceToPlayer())) - 500) * 0.0067 //up to 33% dmg at max range of 3000 dmg *= this.damageReduction //energy and heal drain should be calculated after damage boosts - if (tech.energySiphon && dmg !== Infinity && this.isDropPowerUp && m.immuneCycle < m.cycle) m.energy += Math.min(this.health, dmg) * tech.energySiphon * level.isReducedRegen + if (tech.energySiphon && this.isDropPowerUp && m.immuneCycle < m.cycle) { + //dmg !== Infinity && + const regen = Math.min(this.health, dmg) * tech.energySiphon * level.isReducedRegen + if (!isNaN(regen) && regen !== Infinity) m.energy += regen + } dmg /= Math.sqrt(this.mass) } @@ -1129,7 +1133,7 @@ const mobs = { for (let i = 0; i < mob.length; i++) { if (Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)) < 500000 && mob[i].alive) { //700 if (mob[i].health < 1) { - mob[i].health += 0.33 + this.isBoss + mob[i].health += 0.33 if (mob[i].health > 1) mob[i].health = 1 simulation.drawList.push({ x: mob[i].position.x, diff --git a/js/player.js b/js/player.js index 39a41bc..1484d82 100644 --- a/js/player.js +++ b/js/player.js @@ -1226,11 +1226,7 @@ const m = { m.isAltSkin = true m.yOffWhen.stand = 52 m.yOffWhen.jump = 72 - m.coyoteCycles = 11 - m.hardLandCDScale = 0.5 - m.hardLanding = 160 - m.squirrelFx = 1.4; - m.squirrelJump = 1.16; + m.squirrelJump = 1.15; m.setMovement() m.draw = function () { @@ -3631,6 +3627,9 @@ const m = { // if ((m.fieldMode === 0 || m.fieldMode === 9) && !build.isExperimentSelection && !simulation.isTextLogOpen) simulation.circleFlare(0.4); }, setField(index) { + // console.log("field mode: ", index) + window.removeEventListener("keydown", m.fieldEvent); + if (isNaN(index)) { //find index by name let found = false for (let i = 0; i < m.fieldUpgrades.length; i++) { @@ -3647,1520 +3646,684 @@ const m = { m.setHoldDefaults(); m.fieldUpgrades[index].effect(); simulation.inGameConsole(`
  m.setField("${m.fieldUpgrades[m.fieldMode].name}")
input.key.field: ["MouseRight"]`); + if (m.fieldMode === 4) simulation.inGameConsole(`simulation.molecularMode = ${m.fieldUpgrades[4].modeText()}     ↓↘→↓↙←↑↑↓`); }, - fieldUpgrades: [{ - name: "field emitter", - imageNumber: Math.floor(Math.random() * 26), //pick one of the 25 field emitter image files at random - description: `initial field
use energy to deflect mobs and throw blocks + fieldEvent: null, + fieldUpgrades: [ + { + name: "field emitter", + imageNumber: Math.floor(Math.random() * 26), //pick one of the 25 field emitter image files at random + description: `initial field
use energy to deflect mobs and throw blocks
4 energy per second`, //
100 max energy - effect: () => { - m.hold = function () { - if (m.isHolding) { - m.drawHold(m.holdingTarget); - m.holding(); - m.throwBlock(); - } else if ((input.field && m.fieldCDcycle < m.cycle)) { //not hold but field button is pressed - if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen - m.grabPowerUp(); - m.lookForPickUp(); - if (m.energy > m.minEnergyToDeflect) { - m.drawField(); - m.pushMobsFacing(); - } - } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released - m.pickUp(); - } else { - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - m.drawRegenEnergy() - } - } - }, - { - name: "standing wave", - //deflecting protects you in every direction - description: `3 oscillating shields are permanently active -
+150 max energy -
6 energy per second`, - drainCD: 0, - effect: () => { - m.fieldBlockCD = 0; - m.blockingRecoil = 1 //4 is normal - m.fieldRange = 185 - m.fieldShieldingScale = 1.6 * Math.pow(0.5, (tech.harmonics - 2)) - // m.fieldHarmReduction = 0.66; //33% reduction - - m.harmonic3Phase = () => { //normal standard 3 different 2-d circles - const fieldRange1 = (0.75 + 0.3 * Math.sin(m.cycle / 23)) * m.fieldRange * m.harmonicRadius - const fieldRange2 = (0.68 + 0.37 * Math.sin(m.cycle / 37)) * m.fieldRange * m.harmonicRadius - const fieldRange3 = (0.7 + 0.35 * Math.sin(m.cycle / 47)) * m.fieldRange * m.harmonicRadius - const netFieldRange = Math.max(fieldRange1, fieldRange2, fieldRange3) - ctx.fillStyle = "rgba(110,170,200," + Math.min(0.6, (0.04 + 0.7 * m.energy * (0.1 + 0.11 * Math.random()))) + ")"; - ctx.beginPath(); - ctx.arc(m.pos.x, m.pos.y, fieldRange1, 0, 2 * Math.PI); - ctx.fill(); - ctx.beginPath(); - ctx.arc(m.pos.x, m.pos.y, fieldRange2, 0, 2 * Math.PI); - ctx.fill(); - ctx.beginPath(); - ctx.arc(m.pos.x, m.pos.y, fieldRange3, 0, 2 * Math.PI); - ctx.fill(); - //360 block - for (let i = 0, len = mob.length; i < len; ++i) { - if (Vector.magnitude(Vector.sub(mob[i].position, m.pos)) - mob[i].radius < netFieldRange && !mob[i].isUnblockable) { // && Matter.Query.ray(map, mob[i].position, m.pos).length === 0 - mob[i].locatePlayer(); - if (this.drainCD > m.cycle) { - m.pushMass(mob[i], 0); - } else { - m.pushMass(mob[i]); - this.drainCD = m.cycle + 15 - } - } - } - } - m.harmonicRadius = 1 //for smoothing function when player holds mouse (for harmonicAtomic) - m.harmonicAtomic = () => { //several ellipses spinning about different axises - const rotation = simulation.cycle * 0.0031 - const phase = simulation.cycle * 0.023 - const radius = m.fieldRange * m.harmonicRadius - ctx.lineWidth = 1; - ctx.strokeStyle = "rgba(110,170,200,0.8)" - ctx.fillStyle = "rgba(110,170,200," + Math.min(0.6, 0.7 * m.energy * (0.11 + 0.1 * Math.random()) * (3 / tech.harmonics)) + ")"; - // ctx.fillStyle = "rgba(110,170,200," + Math.min(0.7, m.energy * (0.22 - 0.01 * tech.harmonics) * (0.5 + 0.5 * Math.random())) + ")"; - for (let i = 0; i < tech.harmonics; i++) { - ctx.beginPath(); - ctx.ellipse(m.pos.x, m.pos.y, radius * Math.abs(Math.sin(phase + i / tech.harmonics * Math.PI)), radius, rotation + i / tech.harmonics * Math.PI, 0, 2 * Math.PI); - ctx.fill(); - ctx.stroke(); - } - //360 block - for (let i = 0, len = mob.length; i < len; ++i) { - if (Vector.magnitude(Vector.sub(mob[i].position, m.pos)) - mob[i].radius < radius && !mob[i].isUnblockable) { // && Matter.Query.ray(map, mob[i].position, m.pos).length === 0 - mob[i].locatePlayer(); - if (this.drainCD > m.cycle) { - m.pushMass(mob[i], 0); - } else { - m.pushMass(mob[i]); - this.drainCD = m.cycle + 15 - } - } - } - } - if (tech.harmonics === 2) { - m.harmonicShield = m.harmonic3Phase - } else { - m.harmonicShield = m.harmonicAtomic - } - m.hold = function () { - if (m.isHolding) { - m.drawHold(m.holdingTarget); - m.holding(); - m.throwBlock(); - } else if ((input.field) && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed - if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen - m.grabPowerUp(); - m.lookForPickUp(); - } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released - m.pickUp(); - } else { - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - if (m.energy > m.minEnergyToDeflect && m.fieldCDcycle < m.cycle) { - if (tech.isStandingWaveExpand) { - if (input.field) { - // const oldHarmonicRadius = m.harmonicRadius - m.harmonicRadius = 0.99 * m.harmonicRadius + 0.01 * 4 - // m.energy -= 0.1 * (m.harmonicRadius - oldHarmonicRadius) - } else { - m.harmonicRadius = 0.994 * m.harmonicRadius + 0.006 - } - } - if (!simulation.isTimeSkipping) m.harmonicShield() - } - m.drawRegenEnergy() - } - } - }, - { - name: "perfect diamagnetism", - description: `deflecting does not drain energy
shield maintains functionality while inactive
5 energy per second`, - effect: () => { - m.fieldMeterColor = "#48f" //"#0c5" - m.eyeFillColor = m.fieldMeterColor - m.fieldShieldingScale = 0; - m.fieldBlockCD = 3; - m.grabPowerUpRange2 = 10000000 - m.fieldPosition = { x: m.pos.x, y: m.pos.y } - m.fieldAngle = m.angle - m.perfectPush = (isFree = false) => { - if (m.fieldCDcycle < m.cycle) { - for (let i = 0, len = mob.length; i < len; ++i) { - if ( - Vector.magnitude(Vector.sub(mob[i].position, m.fieldPosition)) - mob[i].radius < m.fieldRange && - !mob[i].isUnblockable && - Vector.dot({ x: Math.cos(m.fieldAngle), y: Math.sin(m.fieldAngle) }, Vector.normalise(Vector.sub(mob[i].position, m.fieldPosition))) > m.fieldThreshold && - Matter.Query.ray(map, mob[i].position, m.fieldPosition).length === 0 - ) { - mob[i].locatePlayer(); - const unit = Vector.normalise(Vector.sub(m.fieldPosition, mob[i].position)) - m.fieldCDcycle = m.cycle + m.fieldBlockCD + (mob[i].isShielded ? 10 : 0); - if (!mob[i].isInvulnerable && bullet.length < 250) { - for (let i = 0; i < m.coupling; i++) { - if (0.1 * m.coupling - i > Math.random()) { - const angle = m.fieldAngle + 4 * m.fieldArc * (Math.random() - 0.5) - const radius = m.fieldRange * (0.6 + 0.3 * Math.random()) - b.iceIX(6 + 6 * Math.random(), angle, Vector.add(m.fieldPosition, { - x: radius * Math.cos(angle), - y: radius * Math.sin(angle) - })) - } - } - } - if (tech.blockDmg) { //electricity - Matter.Body.setVelocity(mob[i], { x: 0.5 * mob[i].velocity.x, y: 0.5 * mob[i].velocity.y }); - if (mob[i].isShielded) { - for (let j = 0, len = mob.length; j < len; j++) { - if (mob[j].id === mob[i].shieldID) mob[j].damage(tech.blockDmg * m.dmgScale * (tech.isBlockRadiation ? 6 : 2), true) - } - } else if (tech.isBlockRadiation) { - if (mob[i].isMobBullet) { - mob[i].damage(tech.blockDmg * m.dmgScale * 3, true) - } else { - mobs.statusDoT(mob[i], tech.blockDmg * m.dmgScale * 0.42, 180) //200% increase -> x (1+2) //over 7s -> 360/30 = 12 half seconds -> 3/12 - } - } else { - mob[i].damage(tech.blockDmg * m.dmgScale, true) - } - // if (mob[i].isShielded) { - // for (let j = 0, len = mob.length; j < len; j++) { - // if (mob[j].id === mob[i].shieldID) mob[j].damage(tech.blockDmg * m.dmgScale * (tech.isBlockRadiation ? 3 : 1), true) - // } - // } else { - // if (tech.isBlockRadiation && !mob[i].isMobBullet) { - // mobs.statusDoT(mob[i], tech.blockDmg * m.dmgScale * 4 / 12, 360) //200% increase -> x (1+2) //over 7s -> 360/30 = 12 half seconds -> 3/12 - // } else { - // mob[i].damage(tech.blockDmg * m.dmgScale) - // } - // } - const step = 40 - ctx.beginPath(); - for (let i = 0, len = 0.5 * tech.blockDmg; i < len; i++) { - let x = m.fieldPosition.x - 20 * unit.x; - let y = m.fieldPosition.y - 20 * unit.y; - ctx.moveTo(x, y); - for (let i = 0; i < 8; i++) { - x += step * (-unit.x + 1.5 * (Math.random() - 0.5)) - y += step * (-unit.y + 1.5 * (Math.random() - 0.5)) - ctx.lineTo(x, y); - } - } - ctx.lineWidth = 3; - ctx.strokeStyle = "#f0f"; - ctx.stroke(); - } else if (isFree) { - ctx.lineWidth = 2; //when blocking draw this graphic - ctx.fillStyle = `rgba(110,150,220, ${0.2 + 0.4 * Math.random()})` - ctx.strokeStyle = "#000"; - const len = mob[i].vertices.length - 1; - const mag = mob[i].radius - ctx.beginPath(); - ctx.moveTo(mob[i].vertices[len].x + mag * (Math.random() - 0.5), mob[i].vertices[len].y + mag * (Math.random() - 0.5)) - for (let j = 0; j < len; j++) { - ctx.lineTo(mob[i].vertices[j].x + mag * (Math.random() - 0.5), mob[i].vertices[j].y + mag * (Math.random() - 0.5)); - } - ctx.lineTo(mob[i].vertices[len].x + mag * (Math.random() - 0.5), mob[i].vertices[len].y + mag * (Math.random() - 0.5)) - ctx.fill(); - ctx.stroke(); - } else { - - const eye = 15; //when blocking draw this graphic - const len = mob[i].vertices.length - 1; - ctx.lineWidth = 1; - ctx.fillStyle = `rgba(110,150,220, ${0.2 + 0.4 * Math.random()})` - ctx.strokeStyle = "#000"; - ctx.beginPath(); - ctx.moveTo(m.fieldPosition.x + eye * Math.cos(m.fieldAngle), m.fieldPosition.y + eye * Math.sin(m.fieldAngle)); - ctx.lineTo(mob[i].vertices[len].x, mob[i].vertices[len].y); - ctx.lineTo(mob[i].vertices[0].x, mob[i].vertices[0].y); - ctx.fill(); - ctx.stroke(); - for (let j = 0; j < len; j++) { - ctx.beginPath(); - ctx.moveTo(m.fieldPosition.x + eye * Math.cos(m.fieldAngle), m.fieldPosition.y + eye * Math.sin(m.fieldAngle)); - ctx.lineTo(mob[i].vertices[j].x, mob[i].vertices[j].y); - ctx.lineTo(mob[i].vertices[j + 1].x, mob[i].vertices[j + 1].y); - ctx.fill(); - ctx.stroke(); - } - } - m.bulletsToBlocks(mob[i]) - if (tech.isStunField) mobs.statusStun(mob[i], tech.isStunField) - //mob knock backs - const massRoot = Math.sqrt(Math.max(1, mob[i].mass)); - Matter.Body.setVelocity(mob[i], { - x: player.velocity.x - (30 * unit.x) / massRoot, - y: player.velocity.y - (30 * unit.y) / massRoot - }); - if (mob[i].isUnstable) { - if (m.fieldCDcycle < m.cycle + 10) m.fieldCDcycle = m.cycle + 6 - mob[i].death(); - } - if (!isFree) { //player knock backs - if (mob[i].isDropPowerUp && player.speed < 12) { - const massRootCap = Math.sqrt(Math.min(10, Math.max(0.2, mob[i].mass))); - Matter.Body.setVelocity(player, { - x: 0.9 * player.velocity.x + 0.6 * unit.x * massRootCap, - y: 0.9 * player.velocity.y + 0.6 * unit.y * massRootCap - }); - } - } - } - } - } - } - m.hold = function () { - const wave = Math.sin(m.cycle * 0.022); - m.fieldRange = 180 + 12 * wave + 100 * tech.isBigField - m.fieldArc = 0.35 + 0.045 * wave + 0.065 * tech.isBigField //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) - m.calculateFieldThreshold(); - if (m.isHolding) { - m.drawHold(m.holdingTarget); - m.holding(); - m.throwBlock(); - } else if (input.field) { //not hold but field button is pressed - //float while field is on - const angleReduction = 0.5 + 0.7 * (Math.PI / 2 - Math.min(Math.PI / 2, Math.abs(m.angle + Math.PI / 2))) - // console.log(angleReduction) - if (player.velocity.y > 1) { - player.force.y -= angleReduction * (tech.isBigField ? 0.95 : 0.5) * player.mass * simulation.g; - Matter.Body.setVelocity(player, { - x: player.velocity.x, - y: 0.98 * player.velocity.y - }); //set velocity to cap, but keep the direction - } - - // go invulnerable while field is active, but also drain energy - // if (true && m.energy > 2 * m.fieldRegen && m.immuneCycle < m.cycle + tech.cyclicImmunity) { - // m.immuneCycle = m.cycle + 1; //player is immune to damage for 60 cycles - // m.energy -= 2 * m.fieldRegen - // if (m.energy < m.fieldRegen) m.fieldCDcycle = m.cycle + 90; - // } - - if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen - m.grabPowerUp(); - m.lookForPickUp(); - m.fieldPosition = { x: m.pos.x, y: m.pos.y } - m.fieldAngle = m.angle - //draw field attached to player - if (m.holdingTarget) { - ctx.fillStyle = `rgba(110,150,220, ${0.06 + 0.03 * Math.random()})` - ctx.strokeStyle = `rgba(110,150,220, ${0.35 + 0.05 * Math.random()})` - } else { - ctx.fillStyle = `rgba(110,150,220, ${0.27 + 0.2 * Math.random() - 0.1 * wave})` - ctx.strokeStyle = `rgba(110,150,220, ${0.4 + 0.5 * Math.random()})` - } - ctx.beginPath(); - ctx.arc(m.pos.x, m.pos.y, m.fieldRange, m.angle - Math.PI * m.fieldArc, m.angle + Math.PI * m.fieldArc, false); - ctx.lineWidth = 2.5 - 1.5 * wave; - ctx.stroke(); - const curve = 0.57 + 0.04 * wave - const aMag = (1 - curve * 1.2) * Math.PI * m.fieldArc - let a = m.angle + aMag - let cp1x = m.pos.x + curve * m.fieldRange * Math.cos(a) - let cp1y = m.pos.y + curve * m.fieldRange * Math.sin(a) - ctx.quadraticCurveTo(cp1x, cp1y, m.pos.x + 30 * Math.cos(m.angle), m.pos.y + 30 * Math.sin(m.angle)) - a = m.angle - aMag - cp1x = m.pos.x + curve * m.fieldRange * Math.cos(a) - cp1y = m.pos.y + curve * m.fieldRange * Math.sin(a) - ctx.quadraticCurveTo(cp1x, cp1y, m.pos.x + 1 * m.fieldRange * Math.cos(m.angle - Math.PI * m.fieldArc), m.pos.y + 1 * m.fieldRange * Math.sin(m.angle - Math.PI * m.fieldArc)) - ctx.fill(); - m.perfectPush(); - } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released - m.pickUp(); - } else { - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - if (!input.field) { //&& tech.isFieldFree - //draw field free of player - ctx.fillStyle = `rgba(110,150,220, ${0.27 + 0.2 * Math.random() - 0.1 * wave})` - ctx.strokeStyle = `rgba(110,180,255, ${0.4 + 0.5 * Math.random()})` - ctx.beginPath(); - ctx.arc(m.fieldPosition.x, m.fieldPosition.y, m.fieldRange, m.fieldAngle - Math.PI * m.fieldArc, m.fieldAngle + Math.PI * m.fieldArc, false); - ctx.lineWidth = 2.5 - 1.5 * wave; - ctx.stroke(); - const curve = 0.8 + 0.06 * wave - const aMag = (1 - curve * 1.2) * Math.PI * m.fieldArc - let a = m.fieldAngle + aMag - ctx.quadraticCurveTo(m.fieldPosition.x + curve * m.fieldRange * Math.cos(a), m.fieldPosition.y + curve * m.fieldRange * Math.sin(a), m.fieldPosition.x + 1 * m.fieldRange * Math.cos(m.fieldAngle - Math.PI * m.fieldArc), m.fieldPosition.y + 1 * m.fieldRange * Math.sin(m.fieldAngle - Math.PI * m.fieldArc)) - ctx.fill(); - m.perfectPush(true); - } - } - // m.drawRegenEnergy() - m.drawRegenEnergy("rgba(0,0,0,0.2)") - if (tech.isPerfectBrake) { //cap mob speed around player - const range = 200 + 140 * wave + 150 * m.energy - for (let i = 0; i < mob.length; i++) { - const distance = Vector.magnitude(Vector.sub(m.pos, mob[i].position)) - if (distance < range) { - const cap = mob[i].isShielded ? 8 : 4 - if (mob[i].speed > cap && Vector.dot(mob[i].velocity, Vector.sub(m.pos, mob[i].position)) > 0) { // if velocity is directed towards player - Matter.Body.setVelocity(mob[i], Vector.mult(Vector.normalise(mob[i].velocity), cap)); //set velocity to cap, but keep the direction - } - } - } - ctx.beginPath(); - ctx.arc(m.pos.x, m.pos.y, range, 0, 2 * Math.PI); - ctx.fillStyle = "hsla(200,50%,61%,0.08)"; - ctx.fill(); - } - } - } - }, - { - name: "negative mass", - //
hold blocks as if they have a lower mass - description: `use energy to nullify  gravity
0.5x damage taken
6 energy per second`, - fieldDrawRadius: 0, - effect: () => { - m.fieldFire = true; - m.holdingMassScale = 0.01; //can hold heavier blocks with lower cost to jumping - m.fieldMeterColor = "#333" - m.eyeFillColor = m.fieldMeterColor - m.fieldHarmReduction = 0.5; - m.fieldDrawRadius = 0; - - m.hold = function () { - m.airSpeedLimit = 125 //5 * player.mass * player.mass - m.FxAir = 0.016 - if (m.isHolding) { - m.drawHold(m.holdingTarget); - m.holding(); - m.throwBlock(); - } else if (input.field) { //push away - if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen - m.grabPowerUp(); - m.lookForPickUp(); - if (m.energy > tech.negativeMassCost && m.fieldCDcycle < m.cycle) { - if (tech.isFlyFaster) { - //look for nearby objects to make zero-g - function moveThis(who, range, mag = 1.06) { - for (let i = 0, len = who.length; i < len; ++i) { - sub = Vector.sub(who[i].position, m.pos); - dist = Vector.magnitude(sub); - if (dist < range) { - who[i].force.y -= who[i].mass * (simulation.g * mag); //add a bit more then standard gravity - if (input.left) { //blocks move horizontally with the same force as the player - who[i].force.x -= m.FxAir * who[i].mass / 10; // move player left / a - } else if (input.right) { - who[i].force.x += m.FxAir * who[i].mass / 10; //move player right / d - } - //loose attraction to player - // const sub = Vector.sub(m.pos, body[i].position) - // const unit = Vector.mult(Vector.normalise(sub), who[i].mass * 0.0000002 * Vector.magnitude(sub)) - // body[i].force.x += unit.x - // body[i].force.y += unit.y - } - } - } - //control horizontal acceleration - m.airSpeedLimit = 1000 // 7* player.mass * player.mass - m.FxAir = 0.01 - //control vertical acceleration - if (input.down) { //down - player.force.y += 0.5 * player.mass * simulation.g; - this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 500 * 0.03; - moveThis(powerUp, this.fieldDrawRadius, 0); - moveThis(body, this.fieldDrawRadius, 0); - } else if (input.up) { //up - m.energy -= 5 * tech.negativeMassCost; - this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 1100 * 0.03; - player.force.y -= 2.25 * player.mass * simulation.g; - moveThis(powerUp, this.fieldDrawRadius, 1.8); - moveThis(body, this.fieldDrawRadius, 1.8); - } else { - m.energy -= tech.negativeMassCost; - this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 800 * 0.03; - player.force.y -= 1.07 * player.mass * simulation.g; // slow upward drift - moveThis(powerUp, this.fieldDrawRadius); - moveThis(body, this.fieldDrawRadius); - } - } else { - //look for nearby objects to make zero-g - function verticalForce(who, range, mag = 1.06) { - for (let i = 0, len = who.length; i < len; ++i) { - sub = Vector.sub(who[i].position, m.pos); - dist = Vector.magnitude(sub); - if (dist < range) { - who[i].force.y -= who[i].mass * (simulation.g * mag); //add a bit more then standard gravity - if (input.left) { //blocks move horizontally with the same force as the player - who[i].force.x -= m.FxAir * who[i].mass / 10; // move player left / a - } else if (input.right) { - who[i].force.x += m.FxAir * who[i].mass / 10; //move player right / d - } - } - - - - // sub = Vector.sub(who[i].position, m.pos); - // dist = Vector.magnitude(sub); - // if (dist < range) who[i].force.y -= who[i].mass * (simulation.g * mag); - } - } - //control horizontal acceleration - m.airSpeedLimit = 400 // 7* player.mass * player.mass - m.FxAir = 0.005 - //control vertical acceleration - if (input.down) { //down - player.force.y -= 0.5 * player.mass * simulation.g; - this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 400 * 0.03; - verticalForce(powerUp, this.fieldDrawRadius, 0.7); - verticalForce(body, this.fieldDrawRadius, 0.7); - } else if (input.up) { //up - m.energy -= 5 * tech.negativeMassCost; - this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 850 * 0.03; - player.force.y -= 1.45 * player.mass * simulation.g; - verticalForce(powerUp, this.fieldDrawRadius, 1.38); - verticalForce(body, this.fieldDrawRadius, 1.38); - } else { - m.energy -= tech.negativeMassCost; - this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 650 * 0.03; - player.force.y -= 1.07 * player.mass * simulation.g; // slow upward drift - verticalForce(powerUp, this.fieldDrawRadius); - verticalForce(body, this.fieldDrawRadius); - } - } - - if (m.energy < 0.001) { - m.fieldCDcycle = m.cycle + 120; - m.energy = 0; - } - //add extra friction for horizontal motion - if (input.down || input.up || input.left || input.right) { - Matter.Body.setVelocity(player, { x: player.velocity.x * 0.99, y: player.velocity.y * 0.98 }); - } else { //slow rise and fall - Matter.Body.setVelocity(player, { x: player.velocity.x * 0.99, y: player.velocity.y * 0.98 }); - } - // if (tech.isFreezeMobs) { - // const ICE_DRAIN = 0.0005 - // for (let i = 0, len = mob.length; i < len; i++) { - // if (!mob[i].isMobBullet && !mob[i].shield && !mob[i].isShielded && ((mob[i].distanceToPlayer() + mob[i].radius) < this.fieldDrawRadius)) { - // if (m.energy > ICE_DRAIN * 2) { - // m.energy -= ICE_DRAIN; - // this.fieldDrawRadius -= 2; - // mobs.statusSlow(mob[i], 60) - // } else { - // break; - // } - // } - // } - // } - //draw zero-G range - if (!simulation.isTimeSkipping) { - ctx.beginPath(); - ctx.arc(m.pos.x, m.pos.y, this.fieldDrawRadius, 0, 2 * Math.PI); - ctx.fillStyle = "#f5f5ff"; - ctx.globalCompositeOperation = "difference"; - ctx.fill(); - ctx.globalCompositeOperation = "source-over"; - } - } - } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released - m.pickUp(); - this.fieldDrawRadius = 0 - } else { - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - this.fieldDrawRadius = 0 - } - m.drawRegenEnergy("rgba(0,0,0,0.2)") - - - // if (tech.isHealAttract) { - // for (let i = 0; i < powerUp.length; i++) { - // if (powerUp[i].name === "heal") { - // //&& Vector.magnitudeSquared(Vector.sub(powerUp[i].position, m.pos)) < 500000 - // let attract = Vector.mult(Vector.normalise(Vector.sub(m.pos, powerUp[i].position)), 0.01 * powerUp[i].mass) - // powerUp[i].force.x += attract.x; - // powerUp[i].force.y += attract.y - powerUp[i].mass * simulation.g; //negate gravity - // Matter.Body.setVelocity(powerUp[i], Vector.mult(powerUp[i].velocity, 0.7)); - // } - // } - // } - - - // powerUp[i].force.x += 0.05 * (dxP / Math.sqrt(dist2)) * powerUp[i].mass; - // powerUp[i].force.y += 0.05 * (dyP / Math.sqrt(dist2)) * powerUp[i].mass - powerUp[i].mass * simulation.g; //negate gravity - // //extra friction - // Matter.Body.setVelocity(powerUp[i], { - // x: powerUp[i].velocity.x * 0.11, - // y: powerUp[i].velocity.y * 0.11 - // }); - - } - } - }, - { - name: "molecular assembler", - description: `use energy to deflect mobs
excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
12 energy per second`, - setDescription() { - return `use energy to deflect mobs
excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
12 energy per second` - }, - - effect: () => { - m.fieldMeterColor = "#ff0" - m.eyeFillColor = m.fieldMeterColor - m.hold = function () { - if (m.energy > m.maxEnergy - 0.02 && m.fieldCDcycle < m.cycle && !input.field && bullet.length < 300 && (m.cycle % 2)) { - if (simulation.molecularMode === 0) { - if (tech.isSporeFlea) { - const drain = 0.18 + (Math.max(bullet.length, 130) - 130) * 0.02 - if (m.energy > drain) { - m.energy -= drain - const speed = m.crouch ? 20 + 8 * Math.random() : 10 + 3 * Math.random() - b.flea({ - x: m.pos.x + 35 * Math.cos(m.angle), - y: m.pos.y + 35 * Math.sin(m.angle) - }, { - x: speed * Math.cos(m.angle), - y: speed * Math.sin(m.angle) - }) - } - } else if (tech.isSporeWorm) { - const drain = 0.18 + (Math.max(bullet.length, 130) - 130) * 0.02 - if (m.energy > drain) { - m.energy -= drain - b.worm({ - x: m.pos.x + 35 * Math.cos(m.angle), - y: m.pos.y + 35 * Math.sin(m.angle) - }) - const SPEED = 2 + 1 * Math.random(); - Matter.Body.setVelocity(bullet[bullet.length - 1], { - x: SPEED * Math.cos(m.angle), - y: SPEED * Math.sin(m.angle) - }); - } - } else { - const drain = 0.095 + (Math.max(bullet.length, 130) - 130) * 0.01 - for (let i = 0, len = Math.random() * 20; i < len; i++) { - if (m.energy > 3 * drain) { - m.energy -= drain - b.spore(m.pos) - } else { - break - } - } - } - } else if (simulation.molecularMode === 1) { - m.energy -= 0.33; - const direction = { x: Math.cos(m.angle), y: Math.sin(m.angle) } - const push = Vector.mult(Vector.perp(direction), 0.08) - b.missile({ x: m.pos.x + 30 * direction.x, y: m.pos.y + 30 * direction.y }, m.angle, -15) - bullet[bullet.length - 1].force.x += push.x * (Math.random() - 0.5) - bullet[bullet.length - 1].force.y += 0.005 + push.y * (Math.random() - 0.5) - // b.missile({ x: m.pos.x, y: m.pos.y - 40 }, -Math.PI / 2 + 0.5 * (Math.random() - 0.5), 0, 1) - } else if (simulation.molecularMode === 2) { - m.energy -= 0.044; - b.iceIX(1) - } else if (simulation.molecularMode === 3) { - if (tech.isDroneRadioactive) { - const drain = 0.8 + (Math.max(bullet.length, 50) - 50) * 0.01 - if (m.energy > drain) { - m.energy -= drain - b.droneRadioactive({ - x: m.pos.x + 30 * Math.cos(m.angle) + 10 * (Math.random() - 0.5), - y: m.pos.y + 30 * Math.sin(m.angle) + 10 * (Math.random() - 0.5) - }, 25) - } - } else { - //every bullet above 100 adds 0.005 to the energy cost per drone - //at 200 bullets the energy cost is 0.45 + 100*0.006 = 1.05 - const drain = (0.45 + (Math.max(bullet.length, 100) - 100) * 0.006) * tech.droneEnergyReduction - if (m.energy > drain) { - m.energy -= drain - b.drone() - } - } - } - } - if (m.isHolding) { - m.drawHold(m.holdingTarget); - m.holding(); - if (tech.isPrinter && m.holdingTarget.isPrinted && input.field) { - // if (Math.random() < 0.004 && m.holdingTarget.vertices.length < 12) m.holdingTarget.vertices.push({ x: 0, y: 0 }) //small chance to increase the number of vertices - m.holdingTarget.radius += Math.min(1.1, 1.3 / m.holdingTarget.mass) //grow up to a limit - const r1 = m.holdingTarget.radius * (1 + 0.12 * Math.sin(m.cycle * 0.11)) - const r2 = m.holdingTarget.radius * (1 + 0.12 * Math.cos(m.cycle * 0.11)) - let angle = (m.cycle * 0.01) % (2 * Math.PI) //rotate the object - let vertices = [] - for (let i = 0, len = m.holdingTarget.vertices.length; i < len; i++) { - angle += 2 * Math.PI / len - vertices.push({ x: m.holdingTarget.position.x + r1 * Math.cos(angle), y: m.holdingTarget.position.y + r2 * Math.sin(angle) }) - } - Matter.Body.setVertices(m.holdingTarget, vertices) - m.definePlayerMass(m.defaultMass + m.holdingTarget.mass * m.holdingMassScale) - } - m.throwBlock(); - } else if ((input.field && m.fieldCDcycle < m.cycle)) { //not hold but field button is pressed - if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen - m.grabPowerUp(); - m.lookForPickUp(); - if (tech.isPrinter && input.down) { - m.printBlock(); - } else if (m.energy > m.minEnergyToDeflect) { - m.drawField(); - m.pushMobsFacing(); - } - } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released - m.pickUp(); - } else { - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - m.drawRegenEnergy() - } - } - }, - { - name: "plasma torch", - description: "use energy to emit short range plasma
1.5x damage
10 energy per second", - set() { - b.isExtruderOn = false - m.fieldDamage = 1.5 - // m.fieldCDcycleAlternate = 0 - - if (m.plasmaBall) { - m.plasmaBall.reset() - Matter.Composite.remove(engine.world, m.plasmaBall); - } - if (tech.isPlasmaBall) { - const circleRadiusScale = 2 - m.plasmaBall = Bodies.circle(m.pos.x + 10 * Math.cos(m.angle), m.pos.y + 10 * Math.sin(m.angle), 1, { - // collisionFilter: { - // group: 0, - // category: 0, - // mask: 0 //cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield - // }, - isSensor: true, - frictionAir: 0, - alpha: 0.7, - isPopping: false, - isAttached: false, - isOn: false, - drain: 0.0017, - radiusLimit: 10, - damage: 0.8, - setPositionToNose() { - const nose = { x: m.pos.x + 10 * Math.cos(m.angle), y: m.pos.y + 10 * Math.sin(m.angle) } - Matter.Body.setPosition(this, Vector.add(nose, Vector.mult(Vector.normalise(Vector.sub(nose, m.pos)), circleRadiusScale * this.circleRadius))); - }, - fire() { - this.isAttached = false; - const speed = 10 //scale with mass? - Matter.Body.setVelocity(this, { - x: player.velocity.x * 0.4 + speed * Math.cos(m.angle), - y: speed * Math.sin(m.angle) - }); - m.plasmaBall.setPositionToNose() - if (this.circleRadius < 10) this.isPopping = true - }, - scale(scale) { - Matter.Body.scale(m.plasmaBall, scale, scale); //shrink fast - if (this.circleRadius < this.radiusLimit) this.reset() - }, - reset() { - // console.log(this.circleRadius) - const scale = 1 / m.plasmaBall.circleRadius - Matter.Body.scale(m.plasmaBall, scale, scale); //grow - // console.log(this.circleRadius) - // this.circleRadius = 0 - this.alpha = 0.7 - this.isOn = false - this.isPopping = false - // this.isAttached = true; - }, - do() { - if (this.isOn) { - //collisions with map - if (Matter.Query.collides(this, map).length > 0) { - if (this.isAttached) { - this.scale(Math.max(0.9, 0.998 - 0.1 / m.plasmaBall.circleRadius)) - } else { - this.isPopping = true - } - } - if (this.isPopping) { - this.alpha -= 0.03 - if (this.alpha < 0.1) { - this.reset() - } else { - const scale = 1.04 + 4 / Math.max(1, m.plasmaBall.circleRadius) - Matter.Body.scale(m.plasmaBall, scale, scale); //grow - } - // if (this.speed > 2.5) { - // const slow = 0.9 - // Matter.Body.setVelocity(this, { - // x: slow * this.velocity.x, - // y: slow * this.velocity.y - // }); - // } - } - //collisions with mobs - // const whom = Matter.Query.collides(this, mob) - // const dmg = this.damage * m.dmgScale - // for (let i = 0, len = whom.length; i < len; i++) { - // const mobHit = (who) => { - // if (who.alive) { - // if (!this.isAttached && !who.isMobBullet) this.isPopping = true - // who.damage(dmg); - // // if (who.shield) this.scale(Math.max(0.9, 0.99 - 0.5 / m.plasmaBall.circleRadius)) - // if (who.speed > 5) { - // Matter.Body.setVelocity(who, { //friction - // x: who.velocity.x * 0.6, - // y: who.velocity.y * 0.6 - // }); - // } else { - // Matter.Body.setVelocity(who, { //friction - // x: who.velocity.x * 0.93, - // y: who.velocity.y * 0.93 - // }); - // } - // } - // } - // mobHit(whom[i].bodyA) - // mobHit(whom[i].bodyB) - // } - - //damage nearby mobs - const dmg = this.damage * m.dmgScale - const arcList = [] - const damageRadius = circleRadiusScale * this.circleRadius - const dischargeRange = 150 + 1600 * tech.plasmaDischarge + 1.3 * damageRadius - for (let i = 0, len = mob.length; i < len; i++) { - if (mob[i].alive && (!mob[i].isBadTarget || mob[i].isMobBullet) && !mob[i].isInvulnerable) { - const sub = Vector.magnitude(Vector.sub(this.position, mob[i].position)) - if (sub < damageRadius + mob[i].radius) { - // if (!this.isAttached && !mob[i].isMobBullet) this.isPopping = true - mob[i].damage(dmg); - if (mob[i].speed > 5) { - Matter.Body.setVelocity(mob[i], { x: mob[i].velocity.x * 0.6, y: mob[i].velocity.y * 0.6 }); - } else { - Matter.Body.setVelocity(mob[i], { x: mob[i].velocity.x * 0.93, y: mob[i].velocity.y * 0.93 }); - } - } else if (sub < dischargeRange + mob[i].radius && Matter.Query.ray(map, mob[i].position, this.position).length === 0) { - arcList.push(mob[i]) //populate electrical arc list - } - } - } - for (let i = 0; i < arcList.length; i++) { - if (tech.plasmaDischarge > Math.random()) { - const who = arcList[Math.floor(Math.random() * arcList.length)] - who.damage(dmg * 4); - //draw arcs - const sub = Vector.sub(who.position, this.position) - const unit = Vector.normalise(sub) - let len = 12 - const step = Vector.magnitude(sub) / (len + 2) - let x = this.position.x - let y = this.position.y - ctx.beginPath(); - ctx.moveTo(x, y); - for (let i = 0; i < len; i++) { - x += step * (unit.x + (Math.random() - 0.5)) - y += step * (unit.y + (Math.random() - 0.5)) - ctx.lineTo(x, y); - } - ctx.lineTo(who.position.x, who.position.y); - ctx.strokeStyle = "#88f"; - ctx.lineWidth = 4 + 3 * Math.random(); - ctx.stroke(); - if (who.damageReduction) { - simulation.drawList.push({ - x: who.position.x, - y: who.position.y, - radius: 15, - color: "rgba(150,150,255,0.4)", - time: 15 - }); - } - } - } - - - //slowly slow down if too fast - if (this.speed > 10) { - const scale = 0.998 - Matter.Body.setVelocity(this, { x: scale * this.velocity.x, y: scale * this.velocity.y }); - } - - //graphics - const radius = circleRadiusScale * this.circleRadius * (0.99 + 0.02 * Math.random()) + 3 * Math.random() - const gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, radius); - const alpha = this.alpha + 0.1 * Math.random() - gradient.addColorStop(0, `rgba(255,255,255,${alpha})`); - gradient.addColorStop(0.35 + 0.1 * Math.random(), `rgba(255,150,255,${alpha})`); - gradient.addColorStop(1, `rgba(255,0,255,${alpha})`); - // gradient.addColorStop(1, `rgba(255,150,255,${alpha})`); - ctx.fillStyle = gradient - ctx.beginPath(); - ctx.arc(this.position.x, this.position.y, radius, 0, 2 * Math.PI); - ctx.fill(); - //draw arcs - const unit = Vector.rotate({ x: 1, y: 0 }, Math.random() * 6.28) - let len = 8 - const step = this.circleRadius / len - let x = this.position.x - let y = this.position.y - ctx.beginPath(); - if (Math.random() < 0.5) { - x += step * (unit.x + 6 * (Math.random() - 0.5)) - y += step * (unit.y + 6 * (Math.random() - 0.5)) - len -= 2 - } - if (Math.random() < 0.5) { - x += step * (unit.x + 6 * (Math.random() - 0.5)) - y += step * (unit.y + 6 * (Math.random() - 0.5)) - len -= 2 - } - ctx.moveTo(x, y); - - for (let i = 0; i < len; i++) { - x += step * (unit.x + 1.9 * (Math.random() - 0.5)) - y += step * (unit.y + 1.9 * (Math.random() - 0.5)) - ctx.lineTo(x, y); - } - ctx.strokeStyle = "#88f"; - ctx.lineWidth = 2 * Math.random(); - ctx.stroke(); - } - }, - }); - - Composite.add(engine.world, m.plasmaBall); - // m.plasmaBall.startingVertices = m.plasmaBall.vertices.slice(); + effect: () => { m.hold = function () { if (m.isHolding) { m.drawHold(m.holdingTarget); m.holding(); m.throwBlock(); - } else if (input.field) { //not hold but field button is pressed + } else if ((input.field && m.fieldCDcycle < m.cycle)) { //not hold but field button is pressed if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen m.grabPowerUp(); m.lookForPickUp(); - if (m.fieldCDcycle < m.cycle) { - //field is active - if (!m.plasmaBall.isAttached) { //return ball to player - if (m.plasmaBall.isOn) { - m.plasmaBall.isPopping = true - } else { - m.plasmaBall.isAttached = true - m.plasmaBall.isOn = true - m.plasmaBall.isPopping = false - m.plasmaBall.alpha = 0.7 - m.plasmaBall.setPositionToNose() - // m.plasmaBall.reset() - - } - } else if (m.energy > m.plasmaBall.drain) { //charge up when attached - if (tech.isCapacitor) { - m.energy -= m.plasmaBall.drain * 2; - const scale = 1 + 48 * Math.pow(Math.max(1, m.plasmaBall.circleRadius), -1.8) - Matter.Body.scale(m.plasmaBall, scale, scale); //grow - } else { - m.energy -= m.plasmaBall.drain; - const scale = 1 + 16 * Math.pow(Math.max(1, m.plasmaBall.circleRadius), -1.8) - Matter.Body.scale(m.plasmaBall, scale, scale); //grow - } - if (m.energy > m.maxEnergy) { - m.energy -= m.plasmaBall.drain * 2; - const scale = 1 + 16 * Math.pow(Math.max(1, m.plasmaBall.circleRadius), -1.8) - Matter.Body.scale(m.plasmaBall, scale, scale); //grow - } - m.plasmaBall.setPositionToNose() - - //add friction for player when holding ball, more friction in vertical - // const floatScale = Math.sqrt(m.plasmaBall.circleRadius) - // const friction = 0.0002 * floatScale - // const slowY = (player.velocity.y > 0) ? Math.max(0.8, 1 - friction * player.velocity.y * player.velocity.y) : Math.max(0.98, 1 - friction * Math.abs(player.velocity.y)) //down : up - // Matter.Body.setVelocity(player, { - // x: Math.max(0.95, 1 - friction * Math.abs(player.velocity.x)) * player.velocity.x, - // y: slowY * player.velocity.y - // }); - - // if (player.velocity.y > 7) player.force.y -= 0.95 * player.mass * simulation.g //less gravity when falling fast - // player.force.y -= Math.min(0.95, 0.05 * floatScale) * player.mass * simulation.g; //undo some gravity on up or down - - //float - const slowY = (player.velocity.y > 0) ? Math.max(0.8, 1 - 0.002 * player.velocity.y * player.velocity.y) : Math.max(0.98, 1 - 0.001 * Math.abs(player.velocity.y)) //down : up - Matter.Body.setVelocity(player, { - x: Math.max(0.95, 1 - 0.003 * Math.abs(player.velocity.x)) * player.velocity.x, - y: slowY * player.velocity.y - }); - if (player.velocity.y > 5) { - player.force.y -= 0.9 * player.mass * simulation.g //less gravity when falling fast - } else { - player.force.y -= 0.5 * player.mass * simulation.g; - } - } else { - m.fieldCDcycle = m.cycle + 90; - m.plasmaBall.fire() - } + if (m.energy > m.minEnergyToDeflect) { + m.drawField(); + m.pushMobsFacing(); } } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released m.pickUp(); - if (m.plasmaBall.isAttached) { - m.fieldCDcycle = m.cycle + 30; - m.plasmaBall.fire() - } } else { m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - if (m.plasmaBall.isAttached) { - m.fieldCDcycle = m.cycle + 30; - m.plasmaBall.fire() - } - } - m.drawRegenEnergy("rgba(0, 0, 0, 0.2)") - m.plasmaBall.do() - } - } else if (tech.isExtruder) { - m.hold = function () { - b.isExtruderOn = false - if (m.isHolding) { - m.drawHold(m.holdingTarget); - m.holding(); - m.throwBlock(); - } else if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed - if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen - m.grabPowerUp(); - m.lookForPickUp(); - b.extruder(); - } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released - m.pickUp(); - } else { - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - m.drawRegenEnergy("rgba(0, 0, 0, 0.2)") - if (input.field) { - b.wasExtruderOn = true - } else { - b.wasExtruderOn = false - b.canExtruderFire = true - } - ctx.beginPath(); //draw all the wave bullets - for (let i = 1, len = bullet.length; i < len; i++) { //skip the first bullet (which is is oldest bullet) - if (bullet[i].isWave) { - if (bullet[i].isBranch || bullet[i - 1].isBranch) { - ctx.moveTo(bullet[i].position.x, bullet[i].position.y) - } else { - ctx.lineTo(bullet[i].position.x, bullet[i].position.y) - } - } - } - if (b.wasExtruderOn && b.isExtruderOn) ctx.lineTo(m.pos.x + 15 * Math.cos(m.angle), m.pos.y + 15 * Math.sin(m.angle)) - ctx.lineWidth = 4; - ctx.strokeStyle = "#f07" - ctx.stroke(); - ctx.lineWidth = tech.extruderRange; - ctx.strokeStyle = "rgba(255,0,110,0.06)" - ctx.stroke(); - } - // } else if (true) { //plasma sword slash - // const plasmaSweepCycles = 30 - // m.plasmaSweep = 0 - // m.plasmaSlashDirection = m.flipLegs//Math.random() > 0.5 ? 1 : -1; - // m.hold = function () { - // if (m.isHolding) { - // m.drawHold(m.holdingTarget); - // m.holding(); - // m.throwBlock(); - // m.plasmaSweep = 0 - // // } else if (true) { //not hold but field button is pressed - // } else if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed - // if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen - // m.grabPowerUp(); - // m.lookForPickUp(); - - // //graphics - // if (m.plasmaSweep === 0) m.plasmaSlashDirection = m.flipLegs //Math.random() > 0.5 ? 1 : -1; - // const angle = m.angle //+ 1 * (m.plasmaSweep - plasmaSweepCycles / 2) / plasmaSweepCycles * m.plasmaSlashDirection - // const plasmaSweepCapped = Math.min(m.plasmaSweep, plasmaSweepCycles - 8) / plasmaSweepCycles - // const range = 100 * plasmaSweepCapped - // const arc = 1.3 - // const A = { x: m.pos.x + range * Math.cos(angle - arc), y: m.pos.y + range * Math.sin(angle - arc) } - // const B = { x: m.pos.x + range * Math.cos(angle + arc), y: m.pos.y + range * Math.sin(angle + arc) } - // const controlRange = 500 * plasmaSweepCapped - // const AC = { x: m.pos.x + controlRange * Math.cos(angle - arc / 2), y: m.pos.y + controlRange * Math.sin(angle - arc / 2) } - // const BC = { x: m.pos.x + controlRange * Math.cos(angle + arc / 2), y: m.pos.y + controlRange * Math.sin(angle + arc / 2) } - // const innerControlRange = 300 * plasmaSweepCapped - // const ACinner = { x: m.pos.x + innerControlRange * Math.cos(angle - arc / 2), y: m.pos.y + innerControlRange * Math.sin(angle - arc / 2) } - // const BCinner = { x: m.pos.x + innerControlRange * Math.cos(angle + arc / 2), y: m.pos.y + innerControlRange * Math.sin(angle + arc / 2) } - // ctx.beginPath(); - // ctx.moveTo(A.x, A.y) - // ctx.bezierCurveTo(AC.x, AC.y, BC.x, BC.y, B.x, B.y); //outer curve - // ctx.bezierCurveTo(BCinner.x, BCinner.y, ACinner.x, ACinner.y, A.x, A.y); //inner curve - // // ctx.strokeStyle = "#000" - // // ctx.stroke(); - // ctx.fillStyle = "rgba(255,0,255,0.5)" - // ctx.fill(); - - // //draw control points for graphics reference - // ctx.lineWidth = '0.5' - // ctx.beginPath(); - // ctx.arc(A.x, A.y, 5, 0, 2 * Math.PI); - // ctx.stroke(); - // ctx.beginPath(); - // ctx.arc(B.x, B.y, 5, 0, 2 * Math.PI); - // ctx.stroke(); - // ctx.beginPath(); - // ctx.arc(AC.x, AC.y, 5, 0, 2 * Math.PI); - // ctx.stroke(); - // ctx.beginPath(); - // ctx.arc(BC.x, BC.y, 5, 0, 2 * Math.PI); - // ctx.stroke(); - // ctx.beginPath(); - // ctx.arc(ACinner.x, ACinner.y, 5, 0, 2 * Math.PI); - // ctx.stroke(); - // ctx.beginPath(); - // ctx.arc(BCinner.x, BCinner.y, 5, 0, 2 * Math.PI); - // ctx.stroke(); - - // //mob collision detection - // collideRange = 160 - // const collideCenter = { - // x: m.pos.x + collideRange * Math.cos(angle), - // y: m.pos.y + collideRange * Math.sin(angle) - // } - // ctx.beginPath(); - // ctx.arc(collideCenter.x, collideCenter.y, 140, 0, 2 * Math.PI); - // ctx.stroke(); - - // //push mob away and slow them? - - - // //sweeping motion and cooldown - // m.plasmaSweep++ - // if (m.plasmaSweep > plasmaSweepCycles) { - // m.plasmaSweep = 0 - // if (m.fireCDcycle < m.cycle + 30) m.fieldCDcycle = m.cycle + 30 - // } - // } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released - // m.pickUp(); - // m.plasmaSweep = 0 - // } else { - // m.plasmaSweep = 0 - // m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - // } - // m.drawRegenEnergy("rgba(0, 0, 0, 0.2)") - // } - } else { - m.hold = function () { - if (m.isHolding) { - m.drawHold(m.holdingTarget); - m.holding(); - m.throwBlock(); - } else if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed - if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen - m.grabPowerUp(); - m.lookForPickUp(); - b.plasma(); - } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released - m.pickUp(); - } else { - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - m.drawRegenEnergy("rgba(0, 0, 0, 0.2)") - } - } - }, - effect() { - m.fieldMeterColor = "#f0f" - m.eyeFillColor = m.fieldMeterColor - this.set(); - } - }, - { - name: "time dilation", - description: `use energy to stop time
1.2x movement and fire rate
12 energy per second`, - set() { - // m.fieldMeterColor = "#0fc" - // m.fieldMeterColor = "#ff0" - m.fieldMeterColor = "#3fe" - m.eyeFillColor = m.fieldMeterColor - m.fieldFx = 1.25 - // m.fieldJump = 1.09 - m.setMovement(); - b.setFireCD() - const timeStop = () => { - m.immuneCycle = m.cycle + 10; //invulnerable to harm while time is stopped, this also disables regen - //draw field everywhere - ctx.globalCompositeOperation = "saturation" - ctx.fillStyle = "#ccc"; - ctx.fillRect(-50000, -50000, 100000, 100000) - ctx.globalCompositeOperation = "source-over" - //stop time - m.isTimeDilated = true; - - function sleep(who) { - for (let i = 0, len = who.length; i < len; ++i) { - if (!who[i].isSleeping) { - who[i].storeVelocity = who[i].velocity - who[i].storeAngularVelocity = who[i].angularVelocity - } - Matter.Sleeping.set(who[i], true) - } - } - sleep(mob); - sleep(body); - sleep(bullet); - simulation.cycle--; //pause all functions that depend on game cycle increasing - } - if (tech.isRewindField) { - this.rewindCount = 0 - m.grabPowerUpRange2 = 300000// m.grabPowerUpRange2 = 200000; - - m.hold = function () { - // console.log(m.fieldCDcycle) - m.grabPowerUp(); - // //grab power ups - // for (let i = 0, len = powerUp.length; i < len; ++i) { - // if ( - // Vector.magnitudeSquared(Vector.sub(m.pos, powerUp[i].position)) < 100000 && - // !simulation.isChoosing && - // (powerUp[i].name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal) - // ) { - // powerUps.onPickUp(powerUp[i]); - // powerUp[i].effect(); - // Matter.Composite.remove(engine.world, powerUp[i]); - // powerUp.splice(i, 1); - // break; //because the array order is messed up after splice - // } - // } - if (m.isHolding) { - m.drawHold(m.holdingTarget); - m.holding(); - m.throwBlock(); - m.wakeCheck(); - } else if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed - const drain = 0.0014 / (1 + 0.05 * m.coupling) - if (m.energy > drain) m.energy -= drain - m.grabPowerUp(); - if (this.rewindCount === 0) { - m.lookForPickUp(); - } - - if (!m.holdingTarget) { - if (this.rewindCount === 0) { //large upfront energy cost to enter rewind mode - if (m.energy > 0.3) { - m.energy -= 0.3 - } else { - this.rewindCount = 0; - m.resetHistory(); - if (m.fireCDcycle < m.cycle + 60) m.fieldCDcycle = m.cycle + 60 - m.immuneCycle = m.cycle //if you reach the end of the history disable harm immunity - } - } - this.rewindCount += 6; - const DRAIN = 0.003 - let history = m.history[(m.cycle - this.rewindCount) % 600] - if (this.rewindCount > 599 || m.energy < DRAIN) { - this.rewindCount = 0; - m.resetHistory(); - if (m.fireCDcycle < m.cycle + 60) m.fieldCDcycle = m.cycle + 60 - m.immuneCycle = m.cycle //if you reach the end of the history disable harm immunity - } else { - //draw field everywhere - ctx.globalCompositeOperation = "saturation" - ctx.fillStyle = "#ccc"; - ctx.fillRect(-100000, -100000, 200000, 200000) - ctx.globalCompositeOperation = "source-over" - // m.grabPowerUp(); //a second grab power up to make the power ups easier to grab, and they more fast which matches the time theme - m.energy -= DRAIN - if (m.immuneCycle < m.cycle + 5) m.immuneCycle = m.cycle + 5; //player is immune to damage for 5 cycles - Matter.Body.setPosition(player, history.position); - Matter.Body.setVelocity(player, { - x: history.velocity.x, - y: history.velocity.y - }); - if (m.health < history.health) { - m.health = history.health - if (m.health > m.maxHealth) m.health = m.maxHealth - m.displayHealth(); - } - m.yOff = history.yOff - if (m.yOff < 48) { - m.doCrouch() - } else { - m.undoCrouch() - } - if (tech.isRewindBot && !(this.rewindCount % 60)) { - for (let i = 0; i < tech.isRewindBot; i++) { - b.randomBot(m.pos, false, false) - bullet[bullet.length - 1].endCycle = simulation.cycle + 300 + Math.floor(180 * Math.random()) //8-9 seconds - } - } - if (tech.isRewindGrenade && !(this.rewindCount % 30)) { - b.grenade(m.pos, this.rewindCount) //Math.PI / 2 - const who = bullet[bullet.length - 1] - who.endCycle = simulation.cycle + 120 - } - } - } - m.wakeCheck(); - } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released - m.pickUp(); - this.rewindCount = 0; - m.wakeCheck(); - } else if (tech.isTimeStop && player.speed < 1 && m.onGround && !input.fire) { - timeStop(); - this.rewindCount = 0; - } else { - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - this.rewindCount = 0; - m.wakeCheck(); - } - m.drawRegenEnergy() // this calls m.regenEnergy(); also - } - } else { - m.fieldFire = true; - m.isTimeDilated = false; - m.hold = function () { - if (m.isHolding) { - m.wakeCheck(); - m.drawHold(m.holdingTarget); - m.holding(); - m.throwBlock(); - } else if (input.field && m.fieldCDcycle < m.cycle) { - const drain = 0.0026 / (1 + 0.03 * m.coupling) - if (m.energy > drain) m.energy -= drain - m.grabPowerUp(); - m.lookForPickUp(); //this drains energy 0.001 - if (m.energy > drain) { - timeStop(); - } else { //holding, but field button is released - m.fieldCDcycle = m.cycle + 120; - m.energy = 0; - m.wakeCheck(); - m.wakeCheck(); - } - } else if (tech.isTimeStop && player.speed < 1 && m.onGround && m.fireCDcycle < m.cycle && !input.fire) { - timeStop(); - //makes things move at 1/5 time rate, but has an annoying flicker for mob graphics, and other minor bugs - // if (!(m.cycle % 4)) { - // // requestAnimationFrame(() => { - // m.wakeCheck(); - // // simulation.timePlayerSkip(1) - // // }); //wrapping in animation frame prevents errors, probably - // ctx.globalCompositeOperation = "saturation" - // ctx.fillStyle = "#ccc"; - // ctx.fillRect(-100000, -100000, 200000, 200000) - // ctx.globalCompositeOperation = "source-over" - // } else { - // timeStop(); - // } - } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released - m.wakeCheck(); - m.pickUp(); - } else { - m.wakeCheck(); - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) } m.drawRegenEnergy() } } }, - effect() { - if (tech.isTimeStop) { - m.fieldHarmReduction = 0.6; - } else { - m.fieldHarmReduction = 1; - } - this.set(); - } - }, - { - name: "metamaterial cloaking", - description: `0.3x damage taken while cloaked
after decloaking 4.5x damage for 2 s
6 energy per second`, - effect: () => { - m.fieldFire = true; - m.fieldMeterColor = "#333"; - m.eyeFillColor = m.fieldMeterColor - m.fieldPhase = 0; - m.isCloak = false - m.fieldDrawRadius = 0 - m.isSneakAttack = true; - m.sneakAttackCycle = 0; - m.enterCloakCycle = 0; - m.drawCloakedM = function () { - m.walk_cycle -= m.flipLegs * m.Vx; - m.pos.x += 4 - m.draw(); - } - m.drawCloak = function () { - m.fieldPhase += 0.007 - const wiggle = 0.15 * Math.sin(m.fieldPhase * 0.5) - ctx.beginPath(); - ctx.ellipse(m.pos.x, m.pos.y, m.fieldDrawRadius * (1 - wiggle), m.fieldDrawRadius * (1 + wiggle), m.fieldPhase, 0, 2 * Math.PI); - ctx.fillStyle = "#fff" - ctx.lineWidth = 2; - ctx.strokeStyle = "#000" - // ctx.stroke() - ctx.globalCompositeOperation = "destination-in"; - ctx.fill(); - ctx.globalCompositeOperation = "source-over"; - ctx.clip(); - } - m.hold = function () { - if (m.isHolding) { - m.drawHold(m.holdingTarget); - m.holding(); - m.throwBlock(); - } else if (input.field && m.fieldCDcycle < m.cycle) { //not hold and field button is pressed - if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen - m.grabPowerUp(); - m.lookForPickUp(); - } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding target exists, and field button is not pressed - m.pickUp(); - } else { - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - } - //not shooting (or using field) enable cloak - if (m.energy < 0.05 && m.fireCDcycle < m.cycle && !input.fire) m.fireCDcycle = m.cycle - if (m.fireCDcycle + 10 < m.cycle && !input.fire) { //automatically cloak if not firing - // const drain = 0.02 - if (!m.isCloak) { //&& m.energy > drain + 0.03 - // m.energy -= drain - m.isCloak = true //enter cloak - m.fieldHarmReduction = 0.3; - m.enterCloakCycle = m.cycle - if (tech.isCloakHealLastHit && m.lastHit > 0) { - const heal = Math.min(0.75 * m.lastHit, m.energy) - m.addHealth(heal); //heal from last hit - m.lastHit = 0 - simulation.drawList.push({ //add dmg to draw queue - x: m.pos.x, - y: m.pos.y, - radius: Math.sqrt(heal) * 200, - color: "rgba(0,255,200,0.6)", - time: 16 - }); - } - if (tech.isIntangible) { - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType && bullet[i].botType !== "orbit") bullet[i].collisionFilter.mask = cat.map | cat.bullet | cat.mobBullet | cat.mobShield - } - } - } - } else if (m.isCloak) { //exit cloak - m.sneakAttackCycle = m.cycle - m.isCloak = false - m.fieldHarmReduction = 1 - - if (tech.isIntangible) { - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType && bullet[i].botType !== "orbit") bullet[i].collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield - } - } - if (tech.isCloakStun) { //stun nearby mobs after exiting cloak - // let isMobsAround = false - const stunRange = m.fieldDrawRadius * 1.25 - // const drain = 0.01 - // if (m.energy > drain) { - for (let i = 0, len = mob.length; i < len; ++i) { - if (Vector.magnitude(Vector.sub(mob[i].position, m.pos)) < stunRange && Matter.Query.ray(map, mob[i].position, m.pos).length === 0 && !mob[i].isBadTarget) { - isMobsAround = true - mobs.statusStun(mob[i], 120) - } - } - // if (isMobsAround) { - // m.energy -= drain - // simulation.drawList.push({ - // x: m.pos.x, - // y: m.pos.y, - // radius: stunRange, - // color: "hsla(0,50%,100%,0.7)", - // time: 7 - // }); - // } - // } - } - } - - if (m.isCloak) { - m.fieldRange = m.fieldRange * 0.85 + 130 - m.fieldDrawRadius = m.fieldRange * 1.1 //* 0.88 //* Math.min(1, 0.3 + 0.5 * Math.min(1, energy * energy)); - m.drawCloak() - // ctx.globalCompositeOperation = "lighter"; - // m.drawCloakedM() - // ctx.globalCompositeOperation = "source-over"; + { + name: "standing wave", + //deflecting protects you in every direction + description: `3 oscillating shields are permanently active +
+150 max energy +
6 energy per second`, + drainCD: 0, + effect: () => { + m.fieldBlockCD = 0; + m.blockingRecoil = 1 //4 is normal + m.fieldRange = 185 + m.fieldShieldingScale = 1.6 * Math.pow(0.5, (tech.harmonics - 2)) + // m.fieldHarmReduction = 0.66; //33% reduction + m.harmonic3Phase = () => { //normal standard 3 different 2-d circles + const fieldRange1 = (0.75 + 0.3 * Math.sin(m.cycle / 23)) * m.fieldRange * m.harmonicRadius + const fieldRange2 = (0.68 + 0.37 * Math.sin(m.cycle / 37)) * m.fieldRange * m.harmonicRadius + const fieldRange3 = (0.7 + 0.35 * Math.sin(m.cycle / 47)) * m.fieldRange * m.harmonicRadius + const netFieldRange = Math.max(fieldRange1, fieldRange2, fieldRange3) + ctx.fillStyle = "rgba(110,170,200," + Math.min(0.6, (0.04 + 0.7 * m.energy * (0.1 + 0.11 * Math.random()))) + ")"; ctx.beginPath(); - ctx.arc(m.pos.x, m.pos.y, 35, 0, 2 * Math.PI); - ctx.strokeStyle = "rgba(255,255,255,0.25)";//"rgba(0,0,0,0.7)";//"rgba(255,255,255,0.7)";//"rgba(255,0,100,0.7)"; - ctx.lineWidth = 10 - ctx.stroke(); - - } else if (m.fieldRange < 4000) { - m.fieldRange += 90 - m.fieldDrawRadius = m.fieldRange //* Math.min(1, 0.3 + 0.5 * Math.min(1, energy * energy)); - m.drawCloak() + ctx.arc(m.pos.x, m.pos.y, fieldRange1, 0, 2 * Math.PI); + ctx.fill(); + ctx.beginPath(); + ctx.arc(m.pos.x, m.pos.y, fieldRange2, 0, 2 * Math.PI); + ctx.fill(); + ctx.beginPath(); + ctx.arc(m.pos.x, m.pos.y, fieldRange3, 0, 2 * Math.PI); + ctx.fill(); + //360 block + for (let i = 0, len = mob.length; i < len; ++i) { + if (Vector.magnitude(Vector.sub(mob[i].position, m.pos)) - mob[i].radius < netFieldRange && !mob[i].isUnblockable) { // && Matter.Query.ray(map, mob[i].position, m.pos).length === 0 + mob[i].locatePlayer(); + if (this.drainCD > m.cycle) { + m.pushMass(mob[i], 0); + } else { + m.pushMass(mob[i]); + this.drainCD = m.cycle + 15 + } + } + } } - if (tech.isIntangible) { - if (m.isCloak) { - player.collisionFilter.mask = cat.map - let inPlayer = Matter.Query.region(mob, player.bounds) - if (inPlayer.length > 0) { - for (let i = 0; i < inPlayer.length; i++) { - if (m.energy > 0) { - if (!inPlayer[i].isUnblockable) m.energy -= 0.003; - if (inPlayer[i].shield) m.energy -= 0.011; + m.harmonicRadius = 1 //for smoothing function when player holds mouse (for harmonicAtomic) + m.harmonicAtomic = () => { //several ellipses spinning about different axises + const rotation = simulation.cycle * 0.0031 + const phase = simulation.cycle * 0.023 + const radius = m.fieldRange * m.harmonicRadius + ctx.lineWidth = 1; + ctx.strokeStyle = "rgba(110,170,200,0.8)" + ctx.fillStyle = "rgba(110,170,200," + Math.min(0.6, 0.7 * m.energy * (0.11 + 0.1 * Math.random()) * (3 / tech.harmonics)) + ")"; + // ctx.fillStyle = "rgba(110,170,200," + Math.min(0.7, m.energy * (0.22 - 0.01 * tech.harmonics) * (0.5 + 0.5 * Math.random())) + ")"; + for (let i = 0; i < tech.harmonics; i++) { + ctx.beginPath(); + ctx.ellipse(m.pos.x, m.pos.y, radius * Math.abs(Math.sin(phase + i / tech.harmonics * Math.PI)), radius, rotation + i / tech.harmonics * Math.PI, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + } + //360 block + for (let i = 0, len = mob.length; i < len; ++i) { + if (Vector.magnitude(Vector.sub(mob[i].position, m.pos)) - mob[i].radius < radius && !mob[i].isUnblockable) { // && Matter.Query.ray(map, mob[i].position, m.pos).length === 0 + mob[i].locatePlayer(); + if (this.drainCD > m.cycle) { + m.pushMass(mob[i], 0); + } else { + m.pushMass(mob[i]); + this.drainCD = m.cycle + 15 + } + } + } + } + if (tech.harmonics === 2) { + m.harmonicShield = m.harmonic3Phase + } else { + m.harmonicShield = m.harmonicAtomic + } + m.hold = function () { + if (m.isHolding) { + m.drawHold(m.holdingTarget); + m.holding(); + m.throwBlock(); + } else if ((input.field) && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed + if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen + m.grabPowerUp(); + m.lookForPickUp(); + } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released + m.pickUp(); + } else { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + if (m.energy > m.minEnergyToDeflect && m.fieldCDcycle < m.cycle) { + if (tech.isStandingWaveExpand) { + if (input.field) { + // const oldHarmonicRadius = m.harmonicRadius + m.harmonicRadius = 0.99 * m.harmonicRadius + 0.01 * 4 + // m.energy -= 0.1 * (m.harmonicRadius - oldHarmonicRadius) + } else { + m.harmonicRadius = 0.994 * m.harmonicRadius + 0.006 + } + } + if (!simulation.isTimeSkipping) m.harmonicShield() + } + m.drawRegenEnergy() + } + } + }, + { + name: "perfect diamagnetism", + description: `deflecting does not drain energy
shield maintains functionality while inactive
5 energy per second`, + effect: () => { + m.fieldMeterColor = "#48f" //"#0c5" + m.eyeFillColor = m.fieldMeterColor + m.fieldShieldingScale = 0; + m.fieldBlockCD = 3; + m.grabPowerUpRange2 = 10000000 + m.fieldPosition = { x: m.pos.x, y: m.pos.y } + m.fieldAngle = m.angle + m.perfectPush = (isFree = false) => { + if (m.fieldCDcycle < m.cycle) { + for (let i = 0, len = mob.length; i < len; ++i) { + if ( + Vector.magnitude(Vector.sub(mob[i].position, m.fieldPosition)) - mob[i].radius < m.fieldRange && + !mob[i].isUnblockable && + Vector.dot({ x: Math.cos(m.fieldAngle), y: Math.sin(m.fieldAngle) }, Vector.normalise(Vector.sub(mob[i].position, m.fieldPosition))) > m.fieldThreshold && + Matter.Query.ray(map, mob[i].position, m.fieldPosition).length === 0 + ) { + mob[i].locatePlayer(); + const unit = Vector.normalise(Vector.sub(m.fieldPosition, mob[i].position)) + m.fieldCDcycle = m.cycle + m.fieldBlockCD + (mob[i].isShielded ? 10 : 0); + if (!mob[i].isInvulnerable && bullet.length < 250) { + for (let i = 0; i < m.coupling; i++) { + if (0.1 * m.coupling - i > Math.random()) { + const angle = m.fieldAngle + 4 * m.fieldArc * (Math.random() - 0.5) + const radius = m.fieldRange * (0.6 + 0.3 * Math.random()) + b.iceIX(6 + 6 * Math.random(), angle, Vector.add(m.fieldPosition, { + x: radius * Math.cos(angle), + y: radius * Math.sin(angle) + })) + } + } + } + if (tech.blockDmg) { //electricity + Matter.Body.setVelocity(mob[i], { x: 0.5 * mob[i].velocity.x, y: 0.5 * mob[i].velocity.y }); + if (mob[i].isShielded) { + for (let j = 0, len = mob.length; j < len; j++) { + if (mob[j].id === mob[i].shieldID) mob[j].damage(tech.blockDmg * m.dmgScale * (tech.isBlockRadiation ? 6 : 2), true) + } + } else if (tech.isBlockRadiation) { + if (mob[i].isMobBullet) { + mob[i].damage(tech.blockDmg * m.dmgScale * 3, true) + } else { + mobs.statusDoT(mob[i], tech.blockDmg * m.dmgScale * 0.42, 180) //200% increase -> x (1+2) //over 7s -> 360/30 = 12 half seconds -> 3/12 + } + } else { + mob[i].damage(tech.blockDmg * m.dmgScale, true) + } + // if (mob[i].isShielded) { + // for (let j = 0, len = mob.length; j < len; j++) { + // if (mob[j].id === mob[i].shieldID) mob[j].damage(tech.blockDmg * m.dmgScale * (tech.isBlockRadiation ? 3 : 1), true) + // } + // } else { + // if (tech.isBlockRadiation && !mob[i].isMobBullet) { + // mobs.statusDoT(mob[i], tech.blockDmg * m.dmgScale * 4 / 12, 360) //200% increase -> x (1+2) //over 7s -> 360/30 = 12 half seconds -> 3/12 + // } else { + // mob[i].damage(tech.blockDmg * m.dmgScale) + // } + // } + const step = 40 + ctx.beginPath(); + for (let i = 0, len = 0.5 * tech.blockDmg; i < len; i++) { + let x = m.fieldPosition.x - 20 * unit.x; + let y = m.fieldPosition.y - 20 * unit.y; + ctx.moveTo(x, y); + for (let i = 0; i < 8; i++) { + x += step * (-unit.x + 1.5 * (Math.random() - 0.5)) + y += step * (-unit.y + 1.5 * (Math.random() - 0.5)) + ctx.lineTo(x, y); + } + } + ctx.lineWidth = 3; + ctx.strokeStyle = "#f0f"; + ctx.stroke(); + } else if (isFree) { + ctx.lineWidth = 2; //when blocking draw this graphic + ctx.fillStyle = `rgba(110,150,220, ${0.2 + 0.4 * Math.random()})` + ctx.strokeStyle = "#000"; + const len = mob[i].vertices.length - 1; + const mag = mob[i].radius + ctx.beginPath(); + ctx.moveTo(mob[i].vertices[len].x + mag * (Math.random() - 0.5), mob[i].vertices[len].y + mag * (Math.random() - 0.5)) + for (let j = 0; j < len; j++) { + ctx.lineTo(mob[i].vertices[j].x + mag * (Math.random() - 0.5), mob[i].vertices[j].y + mag * (Math.random() - 0.5)); + } + ctx.lineTo(mob[i].vertices[len].x + mag * (Math.random() - 0.5), mob[i].vertices[len].y + mag * (Math.random() - 0.5)) + ctx.fill(); + ctx.stroke(); + } else { + + const eye = 15; //when blocking draw this graphic + const len = mob[i].vertices.length - 1; + ctx.lineWidth = 1; + ctx.fillStyle = `rgba(110,150,220, ${0.2 + 0.4 * Math.random()})` + ctx.strokeStyle = "#000"; + ctx.beginPath(); + ctx.moveTo(m.fieldPosition.x + eye * Math.cos(m.fieldAngle), m.fieldPosition.y + eye * Math.sin(m.fieldAngle)); + ctx.lineTo(mob[i].vertices[len].x, mob[i].vertices[len].y); + ctx.lineTo(mob[i].vertices[0].x, mob[i].vertices[0].y); + ctx.fill(); + ctx.stroke(); + for (let j = 0; j < len; j++) { + ctx.beginPath(); + ctx.moveTo(m.fieldPosition.x + eye * Math.cos(m.fieldAngle), m.fieldPosition.y + eye * Math.sin(m.fieldAngle)); + ctx.lineTo(mob[i].vertices[j].x, mob[i].vertices[j].y); + ctx.lineTo(mob[i].vertices[j + 1].x, mob[i].vertices[j + 1].y); + ctx.fill(); + ctx.stroke(); + } + } + m.bulletsToBlocks(mob[i]) + if (tech.isStunField) mobs.statusStun(mob[i], tech.isStunField) + //mob knock backs + const massRoot = Math.sqrt(Math.max(1, mob[i].mass)); + Matter.Body.setVelocity(mob[i], { + x: player.velocity.x - (30 * unit.x) / massRoot, + y: player.velocity.y - (30 * unit.y) / massRoot + }); + if (mob[i].isUnstable) { + if (m.fieldCDcycle < m.cycle + 10) m.fieldCDcycle = m.cycle + 6 + mob[i].death(); + } + if (!isFree) { //player knock backs + if (mob[i].isDropPowerUp && player.speed < 12) { + const massRootCap = Math.sqrt(Math.min(10, Math.max(0.2, mob[i].mass))); + Matter.Body.setVelocity(player, { + x: 0.9 * player.velocity.x + 0.6 * unit.x * massRootCap, + y: 0.9 * player.velocity.y + 0.6 * unit.y * massRootCap + }); + } } } } - } else { - player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield //normal collisions } } - this.drawRegenEnergyCloaking() - if (m.isSneakAttack && m.sneakAttackCycle + Math.min(100, 0.66 * (m.cycle - m.enterCloakCycle)) > m.cycle) { //show sneak attack status - m.fieldDamage = 4.5 * (1 + 0.05 * m.coupling) - const timeLeft = (m.sneakAttackCycle + Math.min(100, 0.66 * (m.cycle - m.enterCloakCycle)) - m.cycle) * 0.5 - ctx.beginPath(); - ctx.arc(m.pos.x, m.pos.y, 32, 0, 2 * Math.PI); - ctx.strokeStyle = "rgba(180,30,70,0.5)";//"rgba(0,0,0,0.7)";//"rgba(255,255,255,0.7)";//"rgba(255,0,100,0.7)"; - ctx.lineWidth = Math.max(Math.min(10, timeLeft), 3); - ctx.stroke(); - // ctx.globalCompositeOperation = "multiply"; - // m.drawCloakedM() - // ctx.globalCompositeOperation = "source-over"; - } else { - m.fieldDamage = 1 - } - } - } - }, - { - name: "pilot wave", - description: `use energy to guide blocks
,
, and
have +3 choices
10 energy per second`, - effect: () => { - m.fieldMeterColor = "#333" - m.eyeFillColor = m.fieldMeterColor + m.hold = function () { + const wave = Math.sin(m.cycle * 0.022); + m.fieldRange = 180 + 12 * wave + 100 * tech.isBigField + m.fieldArc = 0.35 + 0.045 * wave + 0.065 * tech.isBigField //run calculateFieldThreshold after setting fieldArc, used for powerUp grab and mobPush with lookingAt(mob) + m.calculateFieldThreshold(); + if (m.isHolding) { + m.drawHold(m.holdingTarget); + m.holding(); + m.throwBlock(); + } else if (input.field) { //not hold but field button is pressed + //float while field is on + const angleReduction = 0.5 + 0.7 * (Math.PI / 2 - Math.min(Math.PI / 2, Math.abs(m.angle + Math.PI / 2))) + // console.log(angleReduction) + if (player.velocity.y > 1) { + player.force.y -= angleReduction * (tech.isBigField ? 0.95 : 0.5) * player.mass * simulation.g; + Matter.Body.setVelocity(player, { + x: player.velocity.x, + y: 0.98 * player.velocity.y + }); //set velocity to cap, but keep the direction + } - m.fieldPhase = 0; - m.fieldPosition = { - x: simulation.mouseInGame.x, - y: simulation.mouseInGame.y - } - m.lastFieldPosition = { - x: simulation.mouseInGame.x, - y: simulation.mouseInGame.y - } - m.fieldOn = false; - m.fieldRadius = 0; - m.drop(); - m.hold = function () { - if (tech.isPrinter) { - //spawn blocks if field and crouch - if (input.field && m.fieldCDcycle < m.cycle && input.down && !m.isHolding) { - m.printBlock() + // go invulnerable while field is active, but also drain energy + // if (true && m.energy > 2 * m.fieldRegen && m.immuneCycle < m.cycle + tech.cyclicImmunity) { + // m.immuneCycle = m.cycle + 1; //player is immune to damage for 60 cycles + // m.energy -= 2 * m.fieldRegen + // if (m.energy < m.fieldRegen) m.fieldCDcycle = m.cycle + 90; + // } + + if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen + m.grabPowerUp(); + m.lookForPickUp(); + m.fieldPosition = { x: m.pos.x, y: m.pos.y } + m.fieldAngle = m.angle + //draw field attached to player + if (m.holdingTarget) { + ctx.fillStyle = `rgba(110,150,220, ${0.06 + 0.03 * Math.random()})` + ctx.strokeStyle = `rgba(110,150,220, ${0.35 + 0.05 * Math.random()})` + } else { + ctx.fillStyle = `rgba(110,150,220, ${0.27 + 0.2 * Math.random() - 0.1 * wave})` + ctx.strokeStyle = `rgba(110,150,220, ${0.4 + 0.5 * Math.random()})` + } + ctx.beginPath(); + ctx.arc(m.pos.x, m.pos.y, m.fieldRange, m.angle - Math.PI * m.fieldArc, m.angle + Math.PI * m.fieldArc, false); + ctx.lineWidth = 2.5 - 1.5 * wave; + ctx.stroke(); + const curve = 0.57 + 0.04 * wave + const aMag = (1 - curve * 1.2) * Math.PI * m.fieldArc + let a = m.angle + aMag + let cp1x = m.pos.x + curve * m.fieldRange * Math.cos(a) + let cp1y = m.pos.y + curve * m.fieldRange * Math.sin(a) + ctx.quadraticCurveTo(cp1x, cp1y, m.pos.x + 30 * Math.cos(m.angle), m.pos.y + 30 * Math.sin(m.angle)) + a = m.angle - aMag + cp1x = m.pos.x + curve * m.fieldRange * Math.cos(a) + cp1y = m.pos.y + curve * m.fieldRange * Math.sin(a) + ctx.quadraticCurveTo(cp1x, cp1y, m.pos.x + 1 * m.fieldRange * Math.cos(m.angle - Math.PI * m.fieldArc), m.pos.y + 1 * m.fieldRange * Math.sin(m.angle - Math.PI * m.fieldArc)) + ctx.fill(); + m.perfectPush(); + } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released + m.pickUp(); + } else { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + if (!input.field) { //&& tech.isFieldFree + //draw field free of player + ctx.fillStyle = `rgba(110,150,220, ${0.27 + 0.2 * Math.random() - 0.1 * wave})` + ctx.strokeStyle = `rgba(110,180,255, ${0.4 + 0.5 * Math.random()})` + ctx.beginPath(); + ctx.arc(m.fieldPosition.x, m.fieldPosition.y, m.fieldRange, m.fieldAngle - Math.PI * m.fieldArc, m.fieldAngle + Math.PI * m.fieldArc, false); + ctx.lineWidth = 2.5 - 1.5 * wave; + ctx.stroke(); + const curve = 0.8 + 0.06 * wave + const aMag = (1 - curve * 1.2) * Math.PI * m.fieldArc + let a = m.fieldAngle + aMag + ctx.quadraticCurveTo(m.fieldPosition.x + curve * m.fieldRange * Math.cos(a), m.fieldPosition.y + curve * m.fieldRange * Math.sin(a), m.fieldPosition.x + 1 * m.fieldRange * Math.cos(m.fieldAngle - Math.PI * m.fieldArc), m.fieldPosition.y + 1 * m.fieldRange * Math.sin(m.fieldAngle - Math.PI * m.fieldArc)) + ctx.fill(); + m.perfectPush(true); + } + } + // m.drawRegenEnergy() + m.drawRegenEnergy("rgba(0,0,0,0.2)") + if (tech.isPerfectBrake) { //cap mob speed around player + const range = 200 + 140 * wave + 150 * m.energy + for (let i = 0; i < mob.length; i++) { + const distance = Vector.magnitude(Vector.sub(m.pos, mob[i].position)) + if (distance < range) { + const cap = mob[i].isShielded ? 8 : 4 + if (mob[i].speed > cap && Vector.dot(mob[i].velocity, Vector.sub(m.pos, mob[i].position)) > 0) { // if velocity is directed towards player + Matter.Body.setVelocity(mob[i], Vector.mult(Vector.normalise(mob[i].velocity), cap)); //set velocity to cap, but keep the direction + } + } + } + ctx.beginPath(); + ctx.arc(m.pos.x, m.pos.y, range, 0, 2 * Math.PI); + ctx.fillStyle = "hsla(200,50%,61%,0.08)"; + ctx.fill(); + } + } + } + }, + { + name: "negative mass", + //
hold blocks as if they have a lower mass + description: `use energy to nullify  gravity
0.5x damage taken
6 energy per second`, + fieldDrawRadius: 0, + effect: () => { + m.fieldFire = true; + m.holdingMassScale = 0.01; //can hold heavier blocks with lower cost to jumping + m.fieldMeterColor = "#333" + m.eyeFillColor = m.fieldMeterColor + m.fieldHarmReduction = 0.5; + m.fieldDrawRadius = 0; + + m.hold = function () { + m.airSpeedLimit = 125 //5 * player.mass * player.mass + m.FxAir = 0.016 + if (m.isHolding) { + m.drawHold(m.holdingTarget); + m.holding(); + m.throwBlock(); + } else if (input.field) { //push away + if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen + m.grabPowerUp(); + m.lookForPickUp(); + if (m.energy > tech.negativeMassCost && m.fieldCDcycle < m.cycle) { + if (tech.isFlyFaster) { + //look for nearby objects to make zero-g + function moveThis(who, range, mag = 1.06) { + for (let i = 0, len = who.length; i < len; ++i) { + sub = Vector.sub(who[i].position, m.pos); + dist = Vector.magnitude(sub); + if (dist < range) { + who[i].force.y -= who[i].mass * (simulation.g * mag); //add a bit more then standard gravity + if (input.left) { //blocks move horizontally with the same force as the player + who[i].force.x -= m.FxAir * who[i].mass / 10; // move player left / a + } else if (input.right) { + who[i].force.x += m.FxAir * who[i].mass / 10; //move player right / d + } + //loose attraction to player + // const sub = Vector.sub(m.pos, body[i].position) + // const unit = Vector.mult(Vector.normalise(sub), who[i].mass * 0.0000002 * Vector.magnitude(sub)) + // body[i].force.x += unit.x + // body[i].force.y += unit.y + } + } + } + //control horizontal acceleration + m.airSpeedLimit = 1000 // 7* player.mass * player.mass + m.FxAir = 0.01 + //control vertical acceleration + if (input.down) { //down + player.force.y += 0.5 * player.mass * simulation.g; + this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 500 * 0.03; + moveThis(powerUp, this.fieldDrawRadius, 0); + moveThis(body, this.fieldDrawRadius, 0); + } else if (input.up) { //up + m.energy -= 5 * tech.negativeMassCost; + this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 1100 * 0.03; + player.force.y -= 2.25 * player.mass * simulation.g; + moveThis(powerUp, this.fieldDrawRadius, 1.8); + moveThis(body, this.fieldDrawRadius, 1.8); + } else { + m.energy -= tech.negativeMassCost; + this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 800 * 0.03; + player.force.y -= 1.07 * player.mass * simulation.g; // slow upward drift + moveThis(powerUp, this.fieldDrawRadius); + moveThis(body, this.fieldDrawRadius); + } + } else { + //look for nearby objects to make zero-g + function verticalForce(who, range, mag = 1.06) { + for (let i = 0, len = who.length; i < len; ++i) { + sub = Vector.sub(who[i].position, m.pos); + dist = Vector.magnitude(sub); + if (dist < range) { + who[i].force.y -= who[i].mass * (simulation.g * mag); //add a bit more then standard gravity + if (input.left) { //blocks move horizontally with the same force as the player + who[i].force.x -= m.FxAir * who[i].mass / 10; // move player left / a + } else if (input.right) { + who[i].force.x += m.FxAir * who[i].mass / 10; //move player right / d + } + } + + + + // sub = Vector.sub(who[i].position, m.pos); + // dist = Vector.magnitude(sub); + // if (dist < range) who[i].force.y -= who[i].mass * (simulation.g * mag); + } + } + //control horizontal acceleration + m.airSpeedLimit = 400 // 7* player.mass * player.mass + m.FxAir = 0.005 + //control vertical acceleration + if (input.down) { //down + player.force.y -= 0.5 * player.mass * simulation.g; + this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 400 * 0.03; + verticalForce(powerUp, this.fieldDrawRadius, 0.7); + verticalForce(body, this.fieldDrawRadius, 0.7); + } else if (input.up) { //up + m.energy -= 5 * tech.negativeMassCost; + this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 850 * 0.03; + player.force.y -= 1.45 * player.mass * simulation.g; + verticalForce(powerUp, this.fieldDrawRadius, 1.38); + verticalForce(body, this.fieldDrawRadius, 1.38); + } else { + m.energy -= tech.negativeMassCost; + this.fieldDrawRadius = this.fieldDrawRadius * 0.97 + 650 * 0.03; + player.force.y -= 1.07 * player.mass * simulation.g; // slow upward drift + verticalForce(powerUp, this.fieldDrawRadius); + verticalForce(body, this.fieldDrawRadius); + } + } + + if (m.energy < 0.001) { + m.fieldCDcycle = m.cycle + 120; + m.energy = 0; + } + //add extra friction for horizontal motion + if (input.down || input.up || input.left || input.right) { + Matter.Body.setVelocity(player, { x: player.velocity.x * 0.99, y: player.velocity.y * 0.98 }); + } else { //slow rise and fall + Matter.Body.setVelocity(player, { x: player.velocity.x * 0.99, y: player.velocity.y * 0.98 }); + } + // if (tech.isFreezeMobs) { + // const ICE_DRAIN = 0.0005 + // for (let i = 0, len = mob.length; i < len; i++) { + // if (!mob[i].isMobBullet && !mob[i].shield && !mob[i].isShielded && ((mob[i].distanceToPlayer() + mob[i].radius) < this.fieldDrawRadius)) { + // if (m.energy > ICE_DRAIN * 2) { + // m.energy -= ICE_DRAIN; + // this.fieldDrawRadius -= 2; + // mobs.statusSlow(mob[i], 60) + // } else { + // break; + // } + // } + // } + // } + //draw zero-G range + if (!simulation.isTimeSkipping) { + ctx.beginPath(); + ctx.arc(m.pos.x, m.pos.y, this.fieldDrawRadius, 0, 2 * Math.PI); + ctx.fillStyle = "#f5f5ff"; + ctx.globalCompositeOperation = "difference"; + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + } + } + } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released + m.pickUp(); + this.fieldDrawRadius = 0 + } else { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + this.fieldDrawRadius = 0 + } + m.drawRegenEnergy("rgba(0,0,0,0.2)") + + + // if (tech.isHealAttract) { + // for (let i = 0; i < powerUp.length; i++) { + // if (powerUp[i].name === "heal") { + // //&& Vector.magnitudeSquared(Vector.sub(powerUp[i].position, m.pos)) < 500000 + // let attract = Vector.mult(Vector.normalise(Vector.sub(m.pos, powerUp[i].position)), 0.01 * powerUp[i].mass) + // powerUp[i].force.x += attract.x; + // powerUp[i].force.y += attract.y - powerUp[i].mass * simulation.g; //negate gravity + // Matter.Body.setVelocity(powerUp[i], Vector.mult(powerUp[i].velocity, 0.7)); + // } + // } + // } + + + // powerUp[i].force.x += 0.05 * (dxP / Math.sqrt(dist2)) * powerUp[i].mass; + // powerUp[i].force.y += 0.05 * (dyP / Math.sqrt(dist2)) * powerUp[i].mass - powerUp[i].mass * simulation.g; //negate gravity + // //extra friction + // Matter.Body.setVelocity(powerUp[i], { + // x: powerUp[i].velocity.x * 0.11, + // y: powerUp[i].velocity.y * 0.11 + // }); + + } + } + }, + { + name: "molecular assembler", + modeText() { + return `${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}` + }, + description: `use energy to deflect mobs
excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
12 energy per second ↓↘→↓↙←↑↑↓`, + setDescription() { + return `use energy to deflect mobs
excess energy used to print ${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}
12 energy per second ↓↘→↓↙←↑↑↓` + }, + keyLog: [], + effect: () => { + //store event function so it can be found and removed in m.setField() + m.fieldEvent = function (event) { + m.fieldUpgrades[4].keyLog.push(event.code) + + + // Helper function to compare arrays + function arraysEqual(arr1, arr2) { + if (arr1.length !== arr2.length) return false; + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] !== arr2[i]) return false; + } + return true; + } + + const pattern = [input.key.down, input.key.right, input.key.down, input.key.left, input.key.up, input.key.up, input.key.down] + //check if the newest key press is correct + if (event.code !== pattern[m.fieldUpgrades[4].keyLog.length - 1]) { + m.fieldUpgrades[4].keyLog = [] //pattern is wrong, reset log + } else if (arraysEqual(m.fieldUpgrades[4].keyLog, pattern)) { //pattern is complete + //cycle to next molecular mode + m.fieldUpgrades[4].keyLog = [] + const energy = m.energy //save current energy + if (simulation.molecularMode < 3) { + simulation.molecularMode++ + } else { + simulation.molecularMode = 0 + } + // m.setField((m.fieldMode === m.fieldUpgrades.length - 1) ? 1 : m.fieldMode + 1) //cycle to next field, skip field emitter + m.fieldUpgrades[4].description = m.fieldUpgrades[4].setDescription() + m.energy = energy //return to current energy + + const name = `${simulation.molecularMode === 0 ? "spores" : simulation.molecularMode === 1 ? "missiles" : simulation.molecularMode === 2 ? "ice IX" : "drones"}` + simulation.inGameConsole(`simulation.molecularMode = ${simulation.molecularMode} // ${name}   ↓↘→↓↙←↑↑↓`); + } + // console.log(m.fieldUpgrades[4].keyLog) + } + window.addEventListener("keydown", m.fieldEvent); + + m.fieldMeterColor = "#ff0" + m.eyeFillColor = m.fieldMeterColor + m.hold = function () { + if (m.energy > m.maxEnergy - 0.02 && m.fieldCDcycle < m.cycle && !input.field && bullet.length < 300 && (m.cycle % 2)) { + if (simulation.molecularMode === 0) { + if (tech.isSporeFlea) { + const drain = 0.18 + (Math.max(bullet.length, 130) - 130) * 0.02 + if (m.energy > drain) { + m.energy -= drain + const speed = m.crouch ? 20 + 8 * Math.random() : 10 + 3 * Math.random() + b.flea({ + x: m.pos.x + 35 * Math.cos(m.angle), + y: m.pos.y + 35 * Math.sin(m.angle) + }, { + x: speed * Math.cos(m.angle), + y: speed * Math.sin(m.angle) + }) + } + } else if (tech.isSporeWorm) { + const drain = 0.18 + (Math.max(bullet.length, 130) - 130) * 0.02 + if (m.energy > drain) { + m.energy -= drain + b.worm({ + x: m.pos.x + 35 * Math.cos(m.angle), + y: m.pos.y + 35 * Math.sin(m.angle) + }) + const SPEED = 2 + 1 * Math.random(); + Matter.Body.setVelocity(bullet[bullet.length - 1], { + x: SPEED * Math.cos(m.angle), + y: SPEED * Math.sin(m.angle) + }); + } + } else { + const drain = 0.095 + (Math.max(bullet.length, 130) - 130) * 0.01 + for (let i = 0, len = 5; i < len; i++) { + if (m.energy > 3 * drain) { + m.energy -= drain + b.spore(m.pos) + } else { + break + } + } + } + } else if (simulation.molecularMode === 1) { + m.energy -= 0.33; + const direction = { x: Math.cos(m.angle), y: Math.sin(m.angle) } + const push = Vector.mult(Vector.perp(direction), 0.08) + b.missile({ x: m.pos.x + 30 * direction.x, y: m.pos.y + 30 * direction.y }, m.angle, -15) + bullet[bullet.length - 1].force.x += push.x * (Math.random() - 0.5) + bullet[bullet.length - 1].force.y += 0.005 + push.y * (Math.random() - 0.5) + // b.missile({ x: m.pos.x, y: m.pos.y - 40 }, -Math.PI / 2 + 0.5 * (Math.random() - 0.5), 0, 1) + } else if (simulation.molecularMode === 2) { + m.energy -= 0.044; + b.iceIX(1) + } else if (simulation.molecularMode === 3) { + if (tech.isDroneRadioactive) { + const drain = 0.8 + (Math.max(bullet.length, 50) - 50) * 0.01 + if (m.energy > drain) { + m.energy -= drain + b.droneRadioactive({ + x: m.pos.x + 30 * Math.cos(m.angle) + 10 * (Math.random() - 0.5), + y: m.pos.y + 30 * Math.sin(m.angle) + 10 * (Math.random() - 0.5) + }, 25) + } + } else { + //every bullet above 100 adds 0.005 to the energy cost per drone + //at 200 bullets the energy cost is 0.45 + 100*0.006 = 1.05 + const drain = (0.45 + (Math.max(bullet.length, 100) - 100) * 0.006) * tech.droneEnergyReduction + if (m.energy > drain) { + m.energy -= drain + b.drone() + } + } + } } - //if holding block grow it if (m.isHolding) { m.drawHold(m.holdingTarget); m.holding(); @@ -5178,365 +4341,1245 @@ const m = { Matter.Body.setVertices(m.holdingTarget, vertices) m.definePlayerMass(m.defaultMass + m.holdingTarget.mass * m.holdingMassScale) } - m.throwBlock() + m.throwBlock(); + } else if ((input.field && m.fieldCDcycle < m.cycle)) { //not hold but field button is pressed + if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen + m.grabPowerUp(); + m.lookForPickUp(); + if (tech.isPrinter && input.down) { + m.printBlock(); + } else if (m.energy > m.minEnergyToDeflect) { + m.drawField(); + m.pushMobsFacing(); + } + } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released + m.pickUp(); } else { m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) } - //if releasing field throw it - + m.drawRegenEnergy() } - if (input.field) { - if (m.fieldCDcycle < m.cycle) { - const scale = 25 - const bounds = { - min: { - x: m.fieldPosition.x - scale, - y: m.fieldPosition.y - scale - }, - max: { - x: m.fieldPosition.x + scale, - y: m.fieldPosition.y + scale + } + }, + { + name: "plasma torch", + description: "use energy to emit short range plasma
1.5x damage
10 energy per second", + set() { + b.isExtruderOn = false + m.fieldDamage = 1.5 + // m.fieldCDcycleAlternate = 0 + + if (m.plasmaBall) { + m.plasmaBall.reset() + Matter.Composite.remove(engine.world, m.plasmaBall); + } + if (tech.isPlasmaBall) { + const circleRadiusScale = 2 + m.plasmaBall = Bodies.circle(m.pos.x + 10 * Math.cos(m.angle), m.pos.y + 10 * Math.sin(m.angle), 1, { + // collisionFilter: { + // group: 0, + // category: 0, + // mask: 0 //cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield + // }, + isSensor: true, + frictionAir: 0, + alpha: 0.7, + isPopping: false, + isAttached: false, + isOn: false, + drain: 0.0017, + radiusLimit: 10, + damage: 0.8, + setPositionToNose() { + const nose = { x: m.pos.x + 10 * Math.cos(m.angle), y: m.pos.y + 10 * Math.sin(m.angle) } + Matter.Body.setPosition(this, Vector.add(nose, Vector.mult(Vector.normalise(Vector.sub(nose, m.pos)), circleRadiusScale * this.circleRadius))); + }, + fire() { + this.isAttached = false; + const speed = 10 //scale with mass? + Matter.Body.setVelocity(this, { + x: player.velocity.x * 0.4 + speed * Math.cos(m.angle), + y: speed * Math.sin(m.angle) + }); + m.plasmaBall.setPositionToNose() + if (this.circleRadius < 10) this.isPopping = true + }, + scale(scale) { + Matter.Body.scale(m.plasmaBall, scale, scale); //shrink fast + if (this.circleRadius < this.radiusLimit) this.reset() + }, + reset() { + // console.log(this.circleRadius) + const scale = 1 / m.plasmaBall.circleRadius + Matter.Body.scale(m.plasmaBall, scale, scale); //grow + // console.log(this.circleRadius) + // this.circleRadius = 0 + this.alpha = 0.7 + this.isOn = false + this.isPopping = false + // this.isAttached = true; + }, + do() { + if (this.isOn) { + //collisions with map + if (Matter.Query.collides(this, map).length > 0) { + if (this.isAttached) { + this.scale(Math.max(0.9, 0.998 - 0.1 / m.plasmaBall.circleRadius)) + } else { + this.isPopping = true + } + } + if (this.isPopping) { + this.alpha -= 0.03 + if (this.alpha < 0.1) { + this.reset() + } else { + const scale = 1.04 + 4 / Math.max(1, m.plasmaBall.circleRadius) + Matter.Body.scale(m.plasmaBall, scale, scale); //grow + } + // if (this.speed > 2.5) { + // const slow = 0.9 + // Matter.Body.setVelocity(this, { + // x: slow * this.velocity.x, + // y: slow * this.velocity.y + // }); + // } + } + //collisions with mobs + // const whom = Matter.Query.collides(this, mob) + // const dmg = this.damage * m.dmgScale + // for (let i = 0, len = whom.length; i < len; i++) { + // const mobHit = (who) => { + // if (who.alive) { + // if (!this.isAttached && !who.isMobBullet) this.isPopping = true + // who.damage(dmg); + // // if (who.shield) this.scale(Math.max(0.9, 0.99 - 0.5 / m.plasmaBall.circleRadius)) + // if (who.speed > 5) { + // Matter.Body.setVelocity(who, { //friction + // x: who.velocity.x * 0.6, + // y: who.velocity.y * 0.6 + // }); + // } else { + // Matter.Body.setVelocity(who, { //friction + // x: who.velocity.x * 0.93, + // y: who.velocity.y * 0.93 + // }); + // } + // } + // } + // mobHit(whom[i].bodyA) + // mobHit(whom[i].bodyB) + // } + + //damage nearby mobs + const dmg = this.damage * m.dmgScale + const arcList = [] + const damageRadius = circleRadiusScale * this.circleRadius + const dischargeRange = 150 + 1600 * tech.plasmaDischarge + 1.3 * damageRadius + for (let i = 0, len = mob.length; i < len; i++) { + if (mob[i].alive && (!mob[i].isBadTarget || mob[i].isMobBullet) && !mob[i].isInvulnerable) { + const sub = Vector.magnitude(Vector.sub(this.position, mob[i].position)) + if (sub < damageRadius + mob[i].radius) { + // if (!this.isAttached && !mob[i].isMobBullet) this.isPopping = true + mob[i].damage(dmg); + if (mob[i].speed > 5) { + Matter.Body.setVelocity(mob[i], { x: mob[i].velocity.x * 0.6, y: mob[i].velocity.y * 0.6 }); + } else { + Matter.Body.setVelocity(mob[i], { x: mob[i].velocity.x * 0.93, y: mob[i].velocity.y * 0.93 }); + } + } else if (sub < dischargeRange + mob[i].radius && Matter.Query.ray(map, mob[i].position, this.position).length === 0) { + arcList.push(mob[i]) //populate electrical arc list + } + } + } + for (let i = 0; i < arcList.length; i++) { + if (tech.plasmaDischarge > Math.random()) { + const who = arcList[Math.floor(Math.random() * arcList.length)] + who.damage(dmg * 4); + //draw arcs + const sub = Vector.sub(who.position, this.position) + const unit = Vector.normalise(sub) + let len = 12 + const step = Vector.magnitude(sub) / (len + 2) + let x = this.position.x + let y = this.position.y + ctx.beginPath(); + ctx.moveTo(x, y); + for (let i = 0; i < len; i++) { + x += step * (unit.x + (Math.random() - 0.5)) + y += step * (unit.y + (Math.random() - 0.5)) + ctx.lineTo(x, y); + } + ctx.lineTo(who.position.x, who.position.y); + ctx.strokeStyle = "#88f"; + ctx.lineWidth = 4 + 3 * Math.random(); + ctx.stroke(); + if (who.damageReduction) { + simulation.drawList.push({ + x: who.position.x, + y: who.position.y, + radius: 15, + color: "rgba(150,150,255,0.4)", + time: 15 + }); + } + } + } + + + //slowly slow down if too fast + if (this.speed > 10) { + const scale = 0.998 + Matter.Body.setVelocity(this, { x: scale * this.velocity.x, y: scale * this.velocity.y }); + } + + //graphics + const radius = circleRadiusScale * this.circleRadius * (0.99 + 0.02 * Math.random()) + 3 * Math.random() + const gradient = ctx.createRadialGradient(this.position.x, this.position.y, 0, this.position.x, this.position.y, radius); + const alpha = this.alpha + 0.1 * Math.random() + gradient.addColorStop(0, `rgba(255,255,255,${alpha})`); + gradient.addColorStop(0.35 + 0.1 * Math.random(), `rgba(255,150,255,${alpha})`); + gradient.addColorStop(1, `rgba(255,0,255,${alpha})`); + // gradient.addColorStop(1, `rgba(255,150,255,${alpha})`); + ctx.fillStyle = gradient + ctx.beginPath(); + ctx.arc(this.position.x, this.position.y, radius, 0, 2 * Math.PI); + ctx.fill(); + //draw arcs + const unit = Vector.rotate({ x: 1, y: 0 }, Math.random() * 6.28) + let len = 8 + const step = this.circleRadius / len + let x = this.position.x + let y = this.position.y + ctx.beginPath(); + if (Math.random() < 0.5) { + x += step * (unit.x + 6 * (Math.random() - 0.5)) + y += step * (unit.y + 6 * (Math.random() - 0.5)) + len -= 2 + } + if (Math.random() < 0.5) { + x += step * (unit.x + 6 * (Math.random() - 0.5)) + y += step * (unit.y + 6 * (Math.random() - 0.5)) + len -= 2 + } + ctx.moveTo(x, y); + + for (let i = 0; i < len; i++) { + x += step * (unit.x + 1.9 * (Math.random() - 0.5)) + y += step * (unit.y + 1.9 * (Math.random() - 0.5)) + ctx.lineTo(x, y); + } + ctx.strokeStyle = "#88f"; + ctx.lineWidth = 2 * Math.random(); + ctx.stroke(); + } + }, + }); + + Composite.add(engine.world, m.plasmaBall); + // m.plasmaBall.startingVertices = m.plasmaBall.vertices.slice(); + m.hold = function () { + if (m.isHolding) { + m.drawHold(m.holdingTarget); + m.holding(); + m.throwBlock(); + } else if (input.field) { //not hold but field button is pressed + if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen + m.grabPowerUp(); + m.lookForPickUp(); + if (m.fieldCDcycle < m.cycle) { + //field is active + if (!m.plasmaBall.isAttached) { //return ball to player + if (m.plasmaBall.isOn) { + m.plasmaBall.isPopping = true + } else { + m.plasmaBall.isAttached = true + m.plasmaBall.isOn = true + m.plasmaBall.isPopping = false + m.plasmaBall.alpha = 0.7 + m.plasmaBall.setPositionToNose() + // m.plasmaBall.reset() + + } + } else if (m.energy > m.plasmaBall.drain) { //charge up when attached + if (tech.isCapacitor) { + m.energy -= m.plasmaBall.drain * 2; + const scale = 1 + 48 * Math.pow(Math.max(1, m.plasmaBall.circleRadius), -1.8) + Matter.Body.scale(m.plasmaBall, scale, scale); //grow + } else { + m.energy -= m.plasmaBall.drain; + const scale = 1 + 16 * Math.pow(Math.max(1, m.plasmaBall.circleRadius), -1.8) + Matter.Body.scale(m.plasmaBall, scale, scale); //grow + } + if (m.energy > m.maxEnergy) { + m.energy -= m.plasmaBall.drain * 2; + const scale = 1 + 16 * Math.pow(Math.max(1, m.plasmaBall.circleRadius), -1.8) + Matter.Body.scale(m.plasmaBall, scale, scale); //grow + } + m.plasmaBall.setPositionToNose() + + //add friction for player when holding ball, more friction in vertical + // const floatScale = Math.sqrt(m.plasmaBall.circleRadius) + // const friction = 0.0002 * floatScale + // const slowY = (player.velocity.y > 0) ? Math.max(0.8, 1 - friction * player.velocity.y * player.velocity.y) : Math.max(0.98, 1 - friction * Math.abs(player.velocity.y)) //down : up + // Matter.Body.setVelocity(player, { + // x: Math.max(0.95, 1 - friction * Math.abs(player.velocity.x)) * player.velocity.x, + // y: slowY * player.velocity.y + // }); + + // if (player.velocity.y > 7) player.force.y -= 0.95 * player.mass * simulation.g //less gravity when falling fast + // player.force.y -= Math.min(0.95, 0.05 * floatScale) * player.mass * simulation.g; //undo some gravity on up or down + + //float + const slowY = (player.velocity.y > 0) ? Math.max(0.8, 1 - 0.002 * player.velocity.y * player.velocity.y) : Math.max(0.98, 1 - 0.001 * Math.abs(player.velocity.y)) //down : up + Matter.Body.setVelocity(player, { + x: Math.max(0.95, 1 - 0.003 * Math.abs(player.velocity.x)) * player.velocity.x, + y: slowY * player.velocity.y + }); + if (player.velocity.y > 5) { + player.force.y -= 0.9 * player.mass * simulation.g //less gravity when falling fast + } else { + player.force.y -= 0.5 * player.mass * simulation.g; + } + } else { + m.fieldCDcycle = m.cycle + 90; + m.plasmaBall.fire() + } + } + } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released + m.pickUp(); + if (m.plasmaBall.isAttached) { + m.fieldCDcycle = m.cycle + 30; + m.plasmaBall.fire() + } + } else { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + if (m.plasmaBall.isAttached) { + m.fieldCDcycle = m.cycle + 30; + m.plasmaBall.fire() } } - const isInMap = Matter.Query.region(map, bounds).length - // const isInMap = Matter.Query.point(map, m.fieldPosition).length + m.drawRegenEnergy("rgba(0, 0, 0, 0.2)") + m.plasmaBall.do() + } + } else if (tech.isExtruder) { + m.hold = function () { + b.isExtruderOn = false + if (m.isHolding) { + m.drawHold(m.holdingTarget); + m.holding(); + m.throwBlock(); + } else if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed + if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen + m.grabPowerUp(); + m.lookForPickUp(); + b.extruder(); + } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released + m.pickUp(); + } else { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + m.drawRegenEnergy("rgba(0, 0, 0, 0.2)") + if (input.field) { + b.wasExtruderOn = true + } else { + b.wasExtruderOn = false + b.canExtruderFire = true + } + ctx.beginPath(); //draw all the wave bullets + for (let i = 1, len = bullet.length; i < len; i++) { //skip the first bullet (which is is oldest bullet) + if (bullet[i].isWave) { + if (bullet[i].isBranch || bullet[i - 1].isBranch) { + ctx.moveTo(bullet[i].position.x, bullet[i].position.y) + } else { + ctx.lineTo(bullet[i].position.x, bullet[i].position.y) + } + } + } + if (b.wasExtruderOn && b.isExtruderOn) ctx.lineTo(m.pos.x + 15 * Math.cos(m.angle), m.pos.y + 15 * Math.sin(m.angle)) + ctx.lineWidth = 4; + ctx.strokeStyle = "#f07" + ctx.stroke(); + ctx.lineWidth = tech.extruderRange; + ctx.strokeStyle = "rgba(255,0,110,0.06)" + ctx.stroke(); + } + // } else if (true) { //plasma sword slash + // const plasmaSweepCycles = 30 + // m.plasmaSweep = 0 + // m.plasmaSlashDirection = m.flipLegs//Math.random() > 0.5 ? 1 : -1; + // m.hold = function () { + // if (m.isHolding) { + // m.drawHold(m.holdingTarget); + // m.holding(); + // m.throwBlock(); + // m.plasmaSweep = 0 + // // } else if (true) { //not hold but field button is pressed + // } else if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed + // if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen + // m.grabPowerUp(); + // m.lookForPickUp(); - if (!m.fieldOn) { // if field was off, and it starting up, teleport to new mouse location - m.fieldOn = true; - // m.fieldPosition = { //smooth the mouse position, set to starting at player - // x: m.pos.x, - // y: m.pos.y + // //graphics + // if (m.plasmaSweep === 0) m.plasmaSlashDirection = m.flipLegs //Math.random() > 0.5 ? 1 : -1; + // const angle = m.angle //+ 1 * (m.plasmaSweep - plasmaSweepCycles / 2) / plasmaSweepCycles * m.plasmaSlashDirection + // const plasmaSweepCapped = Math.min(m.plasmaSweep, plasmaSweepCycles - 8) / plasmaSweepCycles + // const range = 100 * plasmaSweepCapped + // const arc = 1.3 + // const A = { x: m.pos.x + range * Math.cos(angle - arc), y: m.pos.y + range * Math.sin(angle - arc) } + // const B = { x: m.pos.x + range * Math.cos(angle + arc), y: m.pos.y + range * Math.sin(angle + arc) } + // const controlRange = 500 * plasmaSweepCapped + // const AC = { x: m.pos.x + controlRange * Math.cos(angle - arc / 2), y: m.pos.y + controlRange * Math.sin(angle - arc / 2) } + // const BC = { x: m.pos.x + controlRange * Math.cos(angle + arc / 2), y: m.pos.y + controlRange * Math.sin(angle + arc / 2) } + // const innerControlRange = 300 * plasmaSweepCapped + // const ACinner = { x: m.pos.x + innerControlRange * Math.cos(angle - arc / 2), y: m.pos.y + innerControlRange * Math.sin(angle - arc / 2) } + // const BCinner = { x: m.pos.x + innerControlRange * Math.cos(angle + arc / 2), y: m.pos.y + innerControlRange * Math.sin(angle + arc / 2) } + // ctx.beginPath(); + // ctx.moveTo(A.x, A.y) + // ctx.bezierCurveTo(AC.x, AC.y, BC.x, BC.y, B.x, B.y); //outer curve + // ctx.bezierCurveTo(BCinner.x, BCinner.y, ACinner.x, ACinner.y, A.x, A.y); //inner curve + // // ctx.strokeStyle = "#000" + // // ctx.stroke(); + // ctx.fillStyle = "rgba(255,0,255,0.5)" + // ctx.fill(); + + // //draw control points for graphics reference + // ctx.lineWidth = '0.5' + // ctx.beginPath(); + // ctx.arc(A.x, A.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(B.x, B.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(AC.x, AC.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(BC.x, BC.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(ACinner.x, ACinner.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(BCinner.x, BCinner.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + + // //mob collision detection + // collideRange = 160 + // const collideCenter = { + // x: m.pos.x + collideRange * Math.cos(angle), + // y: m.pos.y + collideRange * Math.sin(angle) + // } + // ctx.beginPath(); + // ctx.arc(collideCenter.x, collideCenter.y, 140, 0, 2 * Math.PI); + // ctx.stroke(); + + // //push mob away and slow them? + + + // //sweeping motion and cooldown + // m.plasmaSweep++ + // if (m.plasmaSweep > plasmaSweepCycles) { + // m.plasmaSweep = 0 + // if (m.fireCDcycle < m.cycle + 30) m.fieldCDcycle = m.cycle + 30 + // } + // } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released + // m.pickUp(); + // m.plasmaSweep = 0 + // } else { + // m.plasmaSweep = 0 + // m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + // } + // m.drawRegenEnergy("rgba(0, 0, 0, 0.2)") + // } + } else { + m.hold = function () { + if (m.isHolding) { + m.drawHold(m.holdingTarget); + m.holding(); + m.throwBlock(); + } else if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed + if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen + m.grabPowerUp(); + m.lookForPickUp(); + b.plasma(); + } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released + m.pickUp(); + } else { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + m.drawRegenEnergy("rgba(0, 0, 0, 0.2)") + } + } + }, + effect() { + m.fieldMeterColor = "#f0f" + m.eyeFillColor = m.fieldMeterColor + this.set(); + } + }, + { + name: "time dilation", + description: `use energy to stop time
1.2x movement and fire rate
12 energy per second`, + set() { + // m.fieldMeterColor = "#0fc" + // m.fieldMeterColor = "#ff0" + m.fieldMeterColor = "#3fe" + m.eyeFillColor = m.fieldMeterColor + m.fieldFx = 1.25 + // m.fieldJump = 1.09 + m.setMovement(); + b.setFireCD() + const timeStop = () => { + m.immuneCycle = m.cycle + 10; //invulnerable to harm while time is stopped, this also disables regen + //draw field everywhere + ctx.globalCompositeOperation = "saturation" + ctx.fillStyle = "#ccc"; + ctx.fillRect(-50000, -50000, 100000, 100000) + ctx.globalCompositeOperation = "source-over" + //stop time + m.isTimeDilated = true; + + function sleep(who) { + for (let i = 0, len = who.length; i < len; ++i) { + if (!who[i].isSleeping) { + who[i].storeVelocity = who[i].velocity + who[i].storeAngularVelocity = who[i].angularVelocity + } + Matter.Sleeping.set(who[i], true) + } + } + sleep(mob); + sleep(body); + sleep(bullet); + simulation.cycle--; //pause all functions that depend on game cycle increasing + } + if (tech.isRewindField) { + this.rewindCount = 0 + m.grabPowerUpRange2 = 300000// m.grabPowerUpRange2 = 200000; + + m.hold = function () { + // console.log(m.fieldCDcycle) + m.grabPowerUp(); + // //grab power ups + // for (let i = 0, len = powerUp.length; i < len; ++i) { + // if ( + // Vector.magnitudeSquared(Vector.sub(m.pos, powerUp[i].position)) < 100000 && + // !simulation.isChoosing && + // (powerUp[i].name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal) + // ) { + // powerUps.onPickUp(powerUp[i]); + // powerUp[i].effect(); + // Matter.Composite.remove(engine.world, powerUp[i]); + // powerUp.splice(i, 1); + // break; //because the array order is messed up after splice + // } + // } + if (m.isHolding) { + m.drawHold(m.holdingTarget); + m.holding(); + m.throwBlock(); + m.wakeCheck(); + } else if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed + const drain = 0.0014 / (1 + 0.05 * m.coupling) + if (m.energy > drain) m.energy -= drain + m.grabPowerUp(); + if (this.rewindCount === 0) { + m.lookForPickUp(); + } + + if (!m.holdingTarget) { + if (this.rewindCount === 0) { //large upfront energy cost to enter rewind mode + if (m.energy > 0.3) { + m.energy -= 0.3 + } else { + this.rewindCount = 0; + m.resetHistory(); + if (m.fireCDcycle < m.cycle + 60) m.fieldCDcycle = m.cycle + 60 + m.immuneCycle = m.cycle //if you reach the end of the history disable harm immunity + } + } + this.rewindCount += 6; + const DRAIN = 0.003 + let history = m.history[(m.cycle - this.rewindCount) % 600] + if (this.rewindCount > 599 || m.energy < DRAIN) { + this.rewindCount = 0; + m.resetHistory(); + if (m.fireCDcycle < m.cycle + 60) m.fieldCDcycle = m.cycle + 60 + m.immuneCycle = m.cycle //if you reach the end of the history disable harm immunity + } else { + //draw field everywhere + ctx.globalCompositeOperation = "saturation" + ctx.fillStyle = "#ccc"; + ctx.fillRect(-100000, -100000, 200000, 200000) + ctx.globalCompositeOperation = "source-over" + // m.grabPowerUp(); //a second grab power up to make the power ups easier to grab, and they more fast which matches the time theme + m.energy -= DRAIN + if (m.immuneCycle < m.cycle + 5) m.immuneCycle = m.cycle + 5; //player is immune to damage for 5 cycles + Matter.Body.setPosition(player, history.position); + Matter.Body.setVelocity(player, { + x: history.velocity.x, + y: history.velocity.y + }); + if (m.health < history.health) { + m.health = history.health + if (m.health > m.maxHealth) m.health = m.maxHealth + m.displayHealth(); + } + m.yOff = history.yOff + if (m.yOff < 48) { + m.doCrouch() + } else { + m.undoCrouch() + } + if (tech.isRewindBot && !(this.rewindCount % 60)) { + for (let i = 0; i < tech.isRewindBot; i++) { + b.randomBot(m.pos, false, false) + bullet[bullet.length - 1].endCycle = simulation.cycle + 300 + Math.floor(180 * Math.random()) //8-9 seconds + } + } + if (tech.isRewindGrenade && !(this.rewindCount % 30)) { + b.grenade(m.pos, this.rewindCount) //Math.PI / 2 + const who = bullet[bullet.length - 1] + who.endCycle = simulation.cycle + 120 + } + } + } + m.wakeCheck(); + } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released + m.pickUp(); + this.rewindCount = 0; + m.wakeCheck(); + } else if (tech.isTimeStop && player.speed < 1 && m.onGround && !input.fire) { + timeStop(); + this.rewindCount = 0; + } else { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + this.rewindCount = 0; + m.wakeCheck(); + } + m.drawRegenEnergy() // this calls m.regenEnergy(); also + } + } else { + m.fieldFire = true; + m.isTimeDilated = false; + m.hold = function () { + if (m.isHolding) { + m.wakeCheck(); + m.drawHold(m.holdingTarget); + m.holding(); + m.throwBlock(); + } else if (input.field && m.fieldCDcycle < m.cycle) { + const drain = 0.0026 / (1 + 0.03 * m.coupling) + if (m.energy > drain) m.energy -= drain + m.grabPowerUp(); + m.lookForPickUp(); //this drains energy 0.001 + if (m.energy > drain) { + timeStop(); + } else { //holding, but field button is released + m.fieldCDcycle = m.cycle + 120; + m.energy = 0; + m.wakeCheck(); + m.wakeCheck(); + } + } else if (tech.isTimeStop && player.speed < 1 && m.onGround && m.fireCDcycle < m.cycle && !input.fire) { + timeStop(); + //makes things move at 1/5 time rate, but has an annoying flicker for mob graphics, and other minor bugs + // if (!(m.cycle % 4)) { + // // requestAnimationFrame(() => { + // m.wakeCheck(); + // // simulation.timePlayerSkip(1) + // // }); //wrapping in animation frame prevents errors, probably + // ctx.globalCompositeOperation = "saturation" + // ctx.fillStyle = "#ccc"; + // ctx.fillRect(-100000, -100000, 200000, 200000) + // ctx.globalCompositeOperation = "source-over" + // } else { + // timeStop(); // } - m.fieldPosition = { //smooth the mouse position, set to mouse's current location - x: simulation.mouseInGame.x, - y: simulation.mouseInGame.y + } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released + m.wakeCheck(); + m.pickUp(); + } else { + m.wakeCheck(); + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + m.drawRegenEnergy() + } + } + }, + effect() { + if (tech.isTimeStop) { + m.fieldHarmReduction = 0.6; + } else { + m.fieldHarmReduction = 1; + } + this.set(); + } + }, + { + name: "metamaterial cloaking", + description: `0.3x damage taken while cloaked
after decloaking 4.5x damage for 2 s
6 energy per second`, + effect: () => { + m.fieldFire = true; + m.fieldMeterColor = "#333"; + m.eyeFillColor = m.fieldMeterColor + m.fieldPhase = 0; + m.isCloak = false + m.fieldDrawRadius = 0 + m.isSneakAttack = true; + m.sneakAttackCycle = 0; + m.enterCloakCycle = 0; + m.drawCloakedM = function () { + m.walk_cycle -= m.flipLegs * m.Vx; + m.pos.x += 4 + m.draw(); + } + m.drawCloak = function () { + m.fieldPhase += 0.007 + const wiggle = 0.15 * Math.sin(m.fieldPhase * 0.5) + ctx.beginPath(); + ctx.ellipse(m.pos.x, m.pos.y, m.fieldDrawRadius * (1 - wiggle), m.fieldDrawRadius * (1 + wiggle), m.fieldPhase, 0, 2 * Math.PI); + ctx.fillStyle = "#fff" + ctx.lineWidth = 2; + ctx.strokeStyle = "#000" + // ctx.stroke() + ctx.globalCompositeOperation = "destination-in"; + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + ctx.clip(); + } + m.hold = function () { + if (m.isHolding) { + m.drawHold(m.holdingTarget); + m.holding(); + m.throwBlock(); + } else if (input.field && m.fieldCDcycle < m.cycle) { //not hold and field button is pressed + if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen + m.grabPowerUp(); + m.lookForPickUp(); + } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding target exists, and field button is not pressed + m.pickUp(); + } else { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + //not shooting (or using field) enable cloak + if (m.energy < 0.05 && m.fireCDcycle < m.cycle && !input.fire) m.fireCDcycle = m.cycle + if (m.fireCDcycle + 10 < m.cycle && !input.fire) { //automatically cloak if not firing + // const drain = 0.02 + if (!m.isCloak) { //&& m.energy > drain + 0.03 + // m.energy -= drain + m.isCloak = true //enter cloak + m.fieldHarmReduction = 0.3; + m.enterCloakCycle = m.cycle + if (tech.isCloakHealLastHit && m.lastHit > 0) { + const heal = Math.min(0.75 * m.lastHit, m.energy) + m.addHealth(heal); //heal from last hit + m.lastHit = 0 + simulation.drawList.push({ //add dmg to draw queue + x: m.pos.x, + y: m.pos.y, + radius: Math.sqrt(heal) * 200, + color: "rgba(0,255,200,0.6)", + time: 16 + }); } - m.lastFieldPosition = { //used to find velocity of field changes - x: m.fieldPosition.x, - y: m.fieldPosition.y - } - } else { //when field is on it smoothly moves towards the mouse - m.lastFieldPosition = { //used to find velocity of field changes - x: m.fieldPosition.x, - y: m.fieldPosition.y - } - const smooth = isInMap ? 0.985 : 0.96; - m.fieldPosition = { //smooth the mouse position - x: m.fieldPosition.x * smooth + simulation.mouseInGame.x * (1 - smooth), - y: m.fieldPosition.y * smooth + simulation.mouseInGame.y * (1 - smooth), + if (tech.isIntangible) { + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType && bullet[i].botType !== "orbit") bullet[i].collisionFilter.mask = cat.map | cat.bullet | cat.mobBullet | cat.mobShield + } } } + } else if (m.isCloak) { //exit cloak + m.sneakAttackCycle = m.cycle + m.isCloak = false + m.fieldHarmReduction = 1 - //grab power ups into the field + if (tech.isIntangible) { + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType && bullet[i].botType !== "orbit") bullet[i].collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield + } + } + if (tech.isCloakStun) { //stun nearby mobs after exiting cloak + // let isMobsAround = false + const stunRange = m.fieldDrawRadius * 1.25 + // const drain = 0.01 + // if (m.energy > drain) { + for (let i = 0, len = mob.length; i < len; ++i) { + if (Vector.magnitude(Vector.sub(mob[i].position, m.pos)) < stunRange && Matter.Query.ray(map, mob[i].position, m.pos).length === 0 && !mob[i].isBadTarget) { + isMobsAround = true + mobs.statusStun(mob[i], 120) + } + } + // if (isMobsAround) { + // m.energy -= drain + // simulation.drawList.push({ + // x: m.pos.x, + // y: m.pos.y, + // radius: stunRange, + // color: "hsla(0,50%,100%,0.7)", + // time: 7 + // }); + // } + // } + } + } + + if (m.isCloak) { + m.fieldRange = m.fieldRange * 0.85 + 130 + m.fieldDrawRadius = m.fieldRange * 1.1 //* 0.88 //* Math.min(1, 0.3 + 0.5 * Math.min(1, energy * energy)); + m.drawCloak() + // ctx.globalCompositeOperation = "lighter"; + // m.drawCloakedM() + // ctx.globalCompositeOperation = "source-over"; + + ctx.beginPath(); + ctx.arc(m.pos.x, m.pos.y, 35, 0, 2 * Math.PI); + ctx.strokeStyle = "rgba(255,255,255,0.25)";//"rgba(0,0,0,0.7)";//"rgba(255,255,255,0.7)";//"rgba(255,0,100,0.7)"; + ctx.lineWidth = 10 + ctx.stroke(); + + } else if (m.fieldRange < 4000) { + m.fieldRange += 90 + m.fieldDrawRadius = m.fieldRange //* Math.min(1, 0.3 + 0.5 * Math.min(1, energy * energy)); + m.drawCloak() + } + if (tech.isIntangible) { + if (m.isCloak) { + player.collisionFilter.mask = cat.map + let inPlayer = Matter.Query.region(mob, player.bounds) + if (inPlayer.length > 0) { + for (let i = 0; i < inPlayer.length; i++) { + if (m.energy > 0) { + if (!inPlayer[i].isUnblockable) m.energy -= 0.003; + if (inPlayer[i].shield) m.energy -= 0.011; + } + } + } + } else { + player.collisionFilter.mask = cat.body | cat.map | cat.mob | cat.mobBullet | cat.mobShield //normal collisions + } + } + this.drawRegenEnergyCloaking() + if (m.isSneakAttack && m.sneakAttackCycle + Math.min(100, 0.66 * (m.cycle - m.enterCloakCycle)) > m.cycle) { //show sneak attack status + m.fieldDamage = 4.5 * (1 + 0.05 * m.coupling) + const timeLeft = (m.sneakAttackCycle + Math.min(100, 0.66 * (m.cycle - m.enterCloakCycle)) - m.cycle) * 0.5 + ctx.beginPath(); + ctx.arc(m.pos.x, m.pos.y, 32, 0, 2 * Math.PI); + ctx.strokeStyle = "rgba(180,30,70,0.5)";//"rgba(0,0,0,0.7)";//"rgba(255,255,255,0.7)";//"rgba(255,0,100,0.7)"; + ctx.lineWidth = Math.max(Math.min(10, timeLeft), 3); + ctx.stroke(); + // ctx.globalCompositeOperation = "multiply"; + // m.drawCloakedM() + // ctx.globalCompositeOperation = "source-over"; + } else { + m.fieldDamage = 1 + } + } + } + }, + { + name: "pilot wave", + description: `use energy to guide blocks
,
, and
have +3 choices
10 energy per second`, + effect: () => { + m.fieldMeterColor = "#333" + m.eyeFillColor = m.fieldMeterColor + + m.fieldPhase = 0; + m.fieldPosition = { + x: simulation.mouseInGame.x, + y: simulation.mouseInGame.y + } + m.lastFieldPosition = { + x: simulation.mouseInGame.x, + y: simulation.mouseInGame.y + } + m.fieldOn = false; + m.fieldRadius = 0; + m.drop(); + m.hold = function () { + if (tech.isPrinter) { + //spawn blocks if field and crouch + if (input.field && m.fieldCDcycle < m.cycle && input.down && !m.isHolding) { + m.printBlock() + } + //if holding block grow it + if (m.isHolding) { + m.drawHold(m.holdingTarget); + m.holding(); + if (tech.isPrinter && m.holdingTarget.isPrinted && input.field) { + // if (Math.random() < 0.004 && m.holdingTarget.vertices.length < 12) m.holdingTarget.vertices.push({ x: 0, y: 0 }) //small chance to increase the number of vertices + m.holdingTarget.radius += Math.min(1.1, 1.3 / m.holdingTarget.mass) //grow up to a limit + const r1 = m.holdingTarget.radius * (1 + 0.12 * Math.sin(m.cycle * 0.11)) + const r2 = m.holdingTarget.radius * (1 + 0.12 * Math.cos(m.cycle * 0.11)) + let angle = (m.cycle * 0.01) % (2 * Math.PI) //rotate the object + let vertices = [] + for (let i = 0, len = m.holdingTarget.vertices.length; i < len; i++) { + angle += 2 * Math.PI / len + vertices.push({ x: m.holdingTarget.position.x + r1 * Math.cos(angle), y: m.holdingTarget.position.y + r2 * Math.sin(angle) }) + } + Matter.Body.setVertices(m.holdingTarget, vertices) + m.definePlayerMass(m.defaultMass + m.holdingTarget.mass * m.holdingMassScale) + } + m.throwBlock() + } else { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + } + //if releasing field throw it + + } + if (input.field) { + if (m.fieldCDcycle < m.cycle) { + const scale = 25 + const bounds = { + min: { + x: m.fieldPosition.x - scale, + y: m.fieldPosition.y - scale + }, + max: { + x: m.fieldPosition.x + scale, + y: m.fieldPosition.y + scale + } + } + const isInMap = Matter.Query.region(map, bounds).length + // const isInMap = Matter.Query.point(map, m.fieldPosition).length + + if (!m.fieldOn) { // if field was off, and it starting up, teleport to new mouse location + m.fieldOn = true; + // m.fieldPosition = { //smooth the mouse position, set to starting at player + // x: m.pos.x, + // y: m.pos.y + // } + m.fieldPosition = { //smooth the mouse position, set to mouse's current location + x: simulation.mouseInGame.x, + y: simulation.mouseInGame.y + } + m.lastFieldPosition = { //used to find velocity of field changes + x: m.fieldPosition.x, + y: m.fieldPosition.y + } + } else { //when field is on it smoothly moves towards the mouse + m.lastFieldPosition = { //used to find velocity of field changes + x: m.fieldPosition.x, + y: m.fieldPosition.y + } + const smooth = isInMap ? 0.985 : 0.96; + m.fieldPosition = { //smooth the mouse position + x: m.fieldPosition.x * smooth + simulation.mouseInGame.x * (1 - smooth), + y: m.fieldPosition.y * smooth + simulation.mouseInGame.y * (1 - smooth), + } + } + + //grab power ups into the field + for (let i = 0, len = powerUp.length; i < len; ++i) { + if (tech.isEnergyNoAmmo && powerUp[i].name === "ammo") continue + + const dxP = m.fieldPosition.x - powerUp[i].position.x; + const dyP = m.fieldPosition.y - powerUp[i].position.y; + const dist2 = dxP * dxP + dyP * dyP + 200; + // float towards field if looking at and in range or if very close to player + if ( + dist2 < m.fieldRadius * m.fieldRadius && + (m.lookingAt(powerUp[i]) || dist2 < 16000) + ) { + powerUp[i].force.x += 0.05 * (dxP / Math.sqrt(dist2)) * powerUp[i].mass; + powerUp[i].force.y += 0.05 * (dyP / Math.sqrt(dist2)) * powerUp[i].mass - powerUp[i].mass * simulation.g; //negate gravity + //extra friction + Matter.Body.setVelocity(powerUp[i], { x: powerUp[i].velocity.x * 0.11, y: powerUp[i].velocity.y * 0.11 }); + if ( + dist2 < 5000 && + !simulation.isChoosing && + (tech.isOverHeal || powerUp[i].name !== "heal" || m.maxHealth - m.health > 0.01) + // (powerUp[i].name !== "heal" || m.health < 0.94 * m.maxHealth) + // (powerUp[i].name !== "ammo" || b.guns[b.activeGun].ammo !== Infinity) + ) { //use power up if it is close enough + powerUps.onPickUp(powerUp[i]); + powerUp[i].effect(); + Matter.Composite.remove(engine.world, powerUp[i]); + powerUp.splice(i, 1); + // m.fieldRadius += 50 + break; //because the array order is messed up after splice + } + } + } + //grab power ups normally too + m.grabPowerUp(); + + if (m.energy > 0.01) { + //find mouse velocity + const diff = Vector.sub(m.fieldPosition, m.lastFieldPosition) + const speed = Vector.magnitude(diff) + const velocity = Vector.mult(Vector.normalise(diff), Math.min(speed, 60)) //limit velocity + let radius, radiusSmooth + if (Matter.Query.ray(map, m.fieldPosition, player.position).length) { //is there something block the player's view of the field + radius = 0 + radiusSmooth = Math.max(0, isInMap ? 0.96 - 0.02 * speed : 0.995); //0.99 + } else { + radius = Math.max(50, 250 - 2 * speed) + radiusSmooth = 0.97 + } + m.fieldRadius = m.fieldRadius * radiusSmooth + radius * (1 - radiusSmooth) + + for (let i = 0, len = body.length; i < len; ++i) { + if (Vector.magnitude(Vector.sub(body[i].position, m.fieldPosition)) < m.fieldRadius && !body[i].isNotHoldable) { + const DRAIN = speed * body[i].mass * 0.0000035 // * (1 + m.energy * m.energy) //drain more energy when you have more energy + if (m.energy > DRAIN) { + m.energy -= DRAIN; + Matter.Body.setVelocity(body[i], velocity); //give block mouse velocity + Matter.Body.setAngularVelocity(body[i], body[i].angularVelocity * 0.8) + // body[i].force.y -= body[i].mass * simulation.g; //remove gravity effects + //blocks drift towards center of pilot wave + const sub = Vector.sub(m.fieldPosition, body[i].position) + const push = Vector.mult(Vector.normalise(sub), 0.0001 * body[i].mass * Vector.magnitude(sub)) + body[i].force.x += push.x + body[i].force.y += push.y - body[i].mass * simulation.g //remove gravity effects + // if (body[i].collisionFilter.category !== cat.bullet) { + // body[i].collisionFilter.category = cat.bullet; + // } + } else { + m.fieldCDcycle = m.cycle + 120; + m.fieldOn = false + m.fieldRadius = 0 + break + } + } + } + + + // m.holdingTarget.collisionFilter.category = cat.bullet; + // m.holdingTarget.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield; + // //check every second to see if player is away from thrown body, and make solid + // const solid = function(that) { + // const dx = that.position.x - player.position.x; + // const dy = that.position.y - player.position.y; + // if (that.speed < 3 && dx * dx + dy * dy > 10000 && that !== m.holdingTarget) { + // that.collisionFilter.category = cat.body; //make solid + // that.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet; //can hit player now + // } else { + // setTimeout(solid, 40, that); + // } + // }; + // setTimeout(solid, 200, m.holdingTarget); + + + + // if (tech.isFreezeMobs) { + // for (let i = 0, len = mob.length; i < len; ++i) { + // if (!mob[i].isMobBullet && !mob[i].shield && !mob[i].isShielded && Vector.magnitude(Vector.sub(mob[i].position, m.fieldPosition)) < m.fieldRadius + mob[i].radius) { + // const ICE_DRAIN = 0.0005 + // if (m.energy > ICE_DRAIN) m.energy -= ICE_DRAIN; + // mobs.statusSlow(mob[i], 180) + // } + // } + // } + + ctx.beginPath(); + const rotate = m.cycle * 0.008; + m.fieldPhase += 0.2 // - 0.5 * Math.sqrt(Math.min(m.energy, 1)); + const off1 = 1 + 0.06 * Math.sin(m.fieldPhase); + const off2 = 1 - 0.06 * Math.sin(m.fieldPhase); + ctx.beginPath(); + ctx.ellipse(m.fieldPosition.x, m.fieldPosition.y, 1.2 * m.fieldRadius * off1, 1.2 * m.fieldRadius * off2, rotate, 0, 2 * Math.PI); + ctx.globalCompositeOperation = "exclusion"; //"exclusion" "difference"; + ctx.fillStyle = "#fff"; //"#eef"; + ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + ctx.beginPath(); + ctx.ellipse(m.fieldPosition.x, m.fieldPosition.y, 1.2 * m.fieldRadius * off1, 1.2 * m.fieldRadius * off2, rotate, 0, 2 * Math.PI * m.energy / m.maxEnergy); + ctx.strokeStyle = "#000"; + ctx.lineWidth = 4; + ctx.stroke(); + } else { + m.fieldCDcycle = m.cycle + 120; + m.fieldOn = false + m.fieldRadius = 0 + } + } else { + m.grabPowerUp(); + } + } else { + m.fieldOn = false + m.fieldRadius = 0 + } + m.drawRegenEnergy("rgba(0,0,0,0.2)") + } + } + }, + { + name: "wormhole", + //wormholes attract blocks and power ups
+ description: "use energy to tunnel through a wormhole
+8% chance to duplicate spawned power ups
8 energy per second", //
bullets may also traverse wormholes + drain: 0, + effect: function () { + m.fieldMeterColor = "#bbf" //"#0c5" + m.eyeFillColor = m.fieldMeterColor + + m.duplicateChance = 0.08 + m.fieldRange = 0 + powerUps.setPowerUpMode(); //needed after adjusting duplication chance + + m.hold = function () { + // m.hole = { //this is reset with each new field, but I'm leaving it here for reference + // isOn: false, + // isReady: true, + // pos1: {x: 0,y: 0}, + // pos2: {x: 0,y: 0}, + // angle: 0, + // unit:{x:0,y:0}, + // } + if (m.hole.isOn) { + // draw holes + m.fieldRange = 0.97 * m.fieldRange + 0.03 * (50 + 10 * Math.sin(simulation.cycle * 0.025)) + const semiMajorAxis = m.fieldRange + 30 + const edge1a = Vector.add(Vector.mult(m.hole.unit, semiMajorAxis), m.hole.pos1) + const edge1b = Vector.add(Vector.mult(m.hole.unit, -semiMajorAxis), m.hole.pos1) + const edge2a = Vector.add(Vector.mult(m.hole.unit, semiMajorAxis), m.hole.pos2) + const edge2b = Vector.add(Vector.mult(m.hole.unit, -semiMajorAxis), m.hole.pos2) + ctx.beginPath(); + ctx.moveTo(edge1a.x, edge1a.y) + ctx.bezierCurveTo(m.hole.pos1.x, m.hole.pos1.y, m.hole.pos2.x, m.hole.pos2.y, edge2a.x, edge2a.y); + ctx.lineTo(edge2b.x, edge2b.y) + ctx.bezierCurveTo(m.hole.pos2.x, m.hole.pos2.y, m.hole.pos1.x, m.hole.pos1.y, edge1b.x, edge1b.y); + ctx.fillStyle = `rgba(255,255,255,${200 / m.fieldRange / m.fieldRange})` //"rgba(0,0,0,0.1)" + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(m.hole.pos1.x, m.hole.pos1.y, m.fieldRange, semiMajorAxis, m.hole.angle, 0, 2 * Math.PI) + ctx.ellipse(m.hole.pos2.x, m.hole.pos2.y, m.fieldRange, semiMajorAxis, m.hole.angle, 0, 2 * Math.PI) + ctx.fillStyle = `rgba(255,255,255,${32 / m.fieldRange})` + ctx.fill(); + + //suck power ups for (let i = 0, len = powerUp.length; i < len; ++i) { if (tech.isEnergyNoAmmo && powerUp[i].name === "ammo") continue - - const dxP = m.fieldPosition.x - powerUp[i].position.x; - const dyP = m.fieldPosition.y - powerUp[i].position.y; - const dist2 = dxP * dxP + dyP * dyP + 200; - // float towards field if looking at and in range or if very close to player - if ( - dist2 < m.fieldRadius * m.fieldRadius && - (m.lookingAt(powerUp[i]) || dist2 < 16000) - ) { - powerUp[i].force.x += 0.05 * (dxP / Math.sqrt(dist2)) * powerUp[i].mass; - powerUp[i].force.y += 0.05 * (dyP / Math.sqrt(dist2)) * powerUp[i].mass - powerUp[i].mass * simulation.g; //negate gravity - //extra friction - Matter.Body.setVelocity(powerUp[i], { x: powerUp[i].velocity.x * 0.11, y: powerUp[i].velocity.y * 0.11 }); - if ( - dist2 < 5000 && - !simulation.isChoosing && - (tech.isOverHeal || powerUp[i].name !== "heal" || m.maxHealth - m.health > 0.01) - // (powerUp[i].name !== "heal" || m.health < 0.94 * m.maxHealth) - // (powerUp[i].name !== "ammo" || b.guns[b.activeGun].ammo !== Infinity) - ) { //use power up if it is close enough + //which hole is closer + const dxP1 = m.hole.pos1.x - powerUp[i].position.x; + const dyP1 = m.hole.pos1.y - powerUp[i].position.y; + const dxP2 = m.hole.pos2.x - powerUp[i].position.x; + const dyP2 = m.hole.pos2.y - powerUp[i].position.y; + let dxP, dyP, dist2 + if (dxP1 * dxP1 + dyP1 * dyP1 < dxP2 * dxP2 + dyP2 * dyP2) { + dxP = dxP1 + dyP = dyP1 + } else { + dxP = dxP2 + dyP = dyP2 + } + dist2 = dxP * dxP + dyP * dyP; + if (dist2 < 600000) { //&& !(m.health === m.maxHealth && powerUp[i].name === "heal") + powerUp[i].force.x += 4 * (dxP / dist2) * powerUp[i].mass; // float towards hole + powerUp[i].force.y += 4 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * simulation.g; //negate gravity + Matter.Body.setVelocity(powerUp[i], { x: powerUp[i].velocity.x * 0.05, y: powerUp[i].velocity.y * 0.05 }); + if (dist2 < 1000 && !simulation.isChoosing) { //use power up if it is close enough + m.fieldRange *= 0.8 powerUps.onPickUp(powerUp[i]); powerUp[i].effect(); Matter.Composite.remove(engine.world, powerUp[i]); powerUp.splice(i, 1); - // m.fieldRadius += 50 break; //because the array order is messed up after splice } } } - //grab power ups normally too - m.grabPowerUp(); - - if (m.energy > 0.01) { - //find mouse velocity - const diff = Vector.sub(m.fieldPosition, m.lastFieldPosition) - const speed = Vector.magnitude(diff) - const velocity = Vector.mult(Vector.normalise(diff), Math.min(speed, 60)) //limit velocity - let radius, radiusSmooth - if (Matter.Query.ray(map, m.fieldPosition, player.position).length) { //is there something block the player's view of the field - radius = 0 - radiusSmooth = Math.max(0, isInMap ? 0.96 - 0.02 * speed : 0.995); //0.99 - } else { - radius = Math.max(50, 250 - 2 * speed) - radiusSmooth = 0.97 - } - m.fieldRadius = m.fieldRadius * radiusSmooth + radius * (1 - radiusSmooth) - - for (let i = 0, len = body.length; i < len; ++i) { - if (Vector.magnitude(Vector.sub(body[i].position, m.fieldPosition)) < m.fieldRadius && !body[i].isNotHoldable) { - const DRAIN = speed * body[i].mass * 0.0000035 // * (1 + m.energy * m.energy) //drain more energy when you have more energy - if (m.energy > DRAIN) { - m.energy -= DRAIN; - Matter.Body.setVelocity(body[i], velocity); //give block mouse velocity - Matter.Body.setAngularVelocity(body[i], body[i].angularVelocity * 0.8) - // body[i].force.y -= body[i].mass * simulation.g; //remove gravity effects - //blocks drift towards center of pilot wave - const sub = Vector.sub(m.fieldPosition, body[i].position) - const push = Vector.mult(Vector.normalise(sub), 0.0001 * body[i].mass * Vector.magnitude(sub)) - body[i].force.x += push.x - body[i].force.y += push.y - body[i].mass * simulation.g //remove gravity effects - // if (body[i].collisionFilter.category !== cat.bullet) { - // body[i].collisionFilter.category = cat.bullet; - // } - } else { - m.fieldCDcycle = m.cycle + 120; - m.fieldOn = false - m.fieldRadius = 0 - break + //suck and shrink blocks + const suckRange = 500 + const shrinkRange = 100 + const shrinkScale = 0.97; + const slowScale = 0.9 + for (let i = 0, len = body.length; i < len; i++) { + if (!body[i].isNotHoldable) { + const dist1 = Vector.magnitude(Vector.sub(m.hole.pos1, body[i].position)) + const dist2 = Vector.magnitude(Vector.sub(m.hole.pos2, body[i].position)) + if (dist1 < dist2) { + if (dist1 < suckRange) { + const pull = Vector.mult(Vector.normalise(Vector.sub(m.hole.pos1, body[i].position)), 1) + const slow = Vector.mult(body[i].velocity, slowScale) + Matter.Body.setVelocity(body[i], Vector.add(slow, pull)); + //shrink + if (Vector.magnitude(Vector.sub(m.hole.pos1, body[i].position)) < shrinkRange) { + Matter.Body.scale(body[i], shrinkScale, shrinkScale); + if (body[i].mass < 0.05) { + Matter.Composite.remove(engine.world, body[i]); + body.splice(i, 1); + m.fieldRange *= 0.8 + if ((m.fieldMode === 0 || m.fieldMode === 9) && m.immuneCycle < m.cycle) m.energy += 0.02 * m.coupling * level.isReducedRegen + if (tech.isWormholeWorms) { //pandimensional spermia + b.worm(Vector.add(m.hole.pos2, Vector.rotate({ x: m.fieldRange * 0.4, y: 0 }, 2 * Math.PI * Math.random()))) + Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(m.hole.unit, Math.PI / 2), -10)); + if (Math.random() < 0.5) { //chance for a second worm + b.worm(Vector.add(m.hole.pos2, Vector.rotate({ x: m.fieldRange * 0.4, y: 0 }, 2 * Math.PI * Math.random()))) + Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(m.hole.unit, Math.PI / 2), -10)); + } + } + break + } + } } - } - } - - - // m.holdingTarget.collisionFilter.category = cat.bullet; - // m.holdingTarget.collisionFilter.mask = cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet | cat.mobShield; - // //check every second to see if player is away from thrown body, and make solid - // const solid = function(that) { - // const dx = that.position.x - player.position.x; - // const dy = that.position.y - player.position.y; - // if (that.speed < 3 && dx * dx + dy * dy > 10000 && that !== m.holdingTarget) { - // that.collisionFilter.category = cat.body; //make solid - // that.collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet; //can hit player now - // } else { - // setTimeout(solid, 40, that); - // } - // }; - // setTimeout(solid, 200, m.holdingTarget); - - - - // if (tech.isFreezeMobs) { - // for (let i = 0, len = mob.length; i < len; ++i) { - // if (!mob[i].isMobBullet && !mob[i].shield && !mob[i].isShielded && Vector.magnitude(Vector.sub(mob[i].position, m.fieldPosition)) < m.fieldRadius + mob[i].radius) { - // const ICE_DRAIN = 0.0005 - // if (m.energy > ICE_DRAIN) m.energy -= ICE_DRAIN; - // mobs.statusSlow(mob[i], 180) - // } - // } - // } - - ctx.beginPath(); - const rotate = m.cycle * 0.008; - m.fieldPhase += 0.2 // - 0.5 * Math.sqrt(Math.min(m.energy, 1)); - const off1 = 1 + 0.06 * Math.sin(m.fieldPhase); - const off2 = 1 - 0.06 * Math.sin(m.fieldPhase); - ctx.beginPath(); - ctx.ellipse(m.fieldPosition.x, m.fieldPosition.y, 1.2 * m.fieldRadius * off1, 1.2 * m.fieldRadius * off2, rotate, 0, 2 * Math.PI); - ctx.globalCompositeOperation = "exclusion"; //"exclusion" "difference"; - ctx.fillStyle = "#fff"; //"#eef"; - ctx.fill(); - ctx.globalCompositeOperation = "source-over"; - ctx.beginPath(); - ctx.ellipse(m.fieldPosition.x, m.fieldPosition.y, 1.2 * m.fieldRadius * off1, 1.2 * m.fieldRadius * off2, rotate, 0, 2 * Math.PI * m.energy / m.maxEnergy); - ctx.strokeStyle = "#000"; - ctx.lineWidth = 4; - ctx.stroke(); - } else { - m.fieldCDcycle = m.cycle + 120; - m.fieldOn = false - m.fieldRadius = 0 - } - } else { - m.grabPowerUp(); - } - } else { - m.fieldOn = false - m.fieldRadius = 0 - } - m.drawRegenEnergy("rgba(0,0,0,0.2)") - } - } - }, - { - name: "wormhole", - //wormholes attract blocks and power ups
- description: "use energy to tunnel through a wormhole
+8% chance to duplicate spawned power ups
8 energy per second", //
bullets may also traverse wormholes - drain: 0, - effect: function () { - m.fieldMeterColor = "#bbf" //"#0c5" - m.eyeFillColor = m.fieldMeterColor - - m.duplicateChance = 0.08 - m.fieldRange = 0 - powerUps.setPowerUpMode(); //needed after adjusting duplication chance - - m.hold = function () { - // m.hole = { //this is reset with each new field, but I'm leaving it here for reference - // isOn: false, - // isReady: true, - // pos1: {x: 0,y: 0}, - // pos2: {x: 0,y: 0}, - // angle: 0, - // unit:{x:0,y:0}, - // } - if (m.hole.isOn) { - // draw holes - m.fieldRange = 0.97 * m.fieldRange + 0.03 * (50 + 10 * Math.sin(simulation.cycle * 0.025)) - const semiMajorAxis = m.fieldRange + 30 - const edge1a = Vector.add(Vector.mult(m.hole.unit, semiMajorAxis), m.hole.pos1) - const edge1b = Vector.add(Vector.mult(m.hole.unit, -semiMajorAxis), m.hole.pos1) - const edge2a = Vector.add(Vector.mult(m.hole.unit, semiMajorAxis), m.hole.pos2) - const edge2b = Vector.add(Vector.mult(m.hole.unit, -semiMajorAxis), m.hole.pos2) - ctx.beginPath(); - ctx.moveTo(edge1a.x, edge1a.y) - ctx.bezierCurveTo(m.hole.pos1.x, m.hole.pos1.y, m.hole.pos2.x, m.hole.pos2.y, edge2a.x, edge2a.y); - ctx.lineTo(edge2b.x, edge2b.y) - ctx.bezierCurveTo(m.hole.pos2.x, m.hole.pos2.y, m.hole.pos1.x, m.hole.pos1.y, edge1b.x, edge1b.y); - ctx.fillStyle = `rgba(255,255,255,${200 / m.fieldRange / m.fieldRange})` //"rgba(0,0,0,0.1)" - ctx.fill(); - ctx.beginPath(); - ctx.ellipse(m.hole.pos1.x, m.hole.pos1.y, m.fieldRange, semiMajorAxis, m.hole.angle, 0, 2 * Math.PI) - ctx.ellipse(m.hole.pos2.x, m.hole.pos2.y, m.fieldRange, semiMajorAxis, m.hole.angle, 0, 2 * Math.PI) - ctx.fillStyle = `rgba(255,255,255,${32 / m.fieldRange})` - ctx.fill(); - - //suck power ups - for (let i = 0, len = powerUp.length; i < len; ++i) { - if (tech.isEnergyNoAmmo && powerUp[i].name === "ammo") continue - //which hole is closer - const dxP1 = m.hole.pos1.x - powerUp[i].position.x; - const dyP1 = m.hole.pos1.y - powerUp[i].position.y; - const dxP2 = m.hole.pos2.x - powerUp[i].position.x; - const dyP2 = m.hole.pos2.y - powerUp[i].position.y; - let dxP, dyP, dist2 - if (dxP1 * dxP1 + dyP1 * dyP1 < dxP2 * dxP2 + dyP2 * dyP2) { - dxP = dxP1 - dyP = dyP1 - } else { - dxP = dxP2 - dyP = dyP2 - } - dist2 = dxP * dxP + dyP * dyP; - if (dist2 < 600000) { //&& !(m.health === m.maxHealth && powerUp[i].name === "heal") - powerUp[i].force.x += 4 * (dxP / dist2) * powerUp[i].mass; // float towards hole - powerUp[i].force.y += 4 * (dyP / dist2) * powerUp[i].mass - powerUp[i].mass * simulation.g; //negate gravity - Matter.Body.setVelocity(powerUp[i], { x: powerUp[i].velocity.x * 0.05, y: powerUp[i].velocity.y * 0.05 }); - if (dist2 < 1000 && !simulation.isChoosing) { //use power up if it is close enough - m.fieldRange *= 0.8 - powerUps.onPickUp(powerUp[i]); - powerUp[i].effect(); - Matter.Composite.remove(engine.world, powerUp[i]); - powerUp.splice(i, 1); - break; //because the array order is messed up after splice - } - } - } - //suck and shrink blocks - const suckRange = 500 - const shrinkRange = 100 - const shrinkScale = 0.97; - const slowScale = 0.9 - for (let i = 0, len = body.length; i < len; i++) { - if (!body[i].isNotHoldable) { - const dist1 = Vector.magnitude(Vector.sub(m.hole.pos1, body[i].position)) - const dist2 = Vector.magnitude(Vector.sub(m.hole.pos2, body[i].position)) - if (dist1 < dist2) { - if (dist1 < suckRange) { - const pull = Vector.mult(Vector.normalise(Vector.sub(m.hole.pos1, body[i].position)), 1) + } else if (dist2 < suckRange) { + const pull = Vector.mult(Vector.normalise(Vector.sub(m.hole.pos2, body[i].position)), 1) const slow = Vector.mult(body[i].velocity, slowScale) Matter.Body.setVelocity(body[i], Vector.add(slow, pull)); //shrink - if (Vector.magnitude(Vector.sub(m.hole.pos1, body[i].position)) < shrinkRange) { + if (Vector.magnitude(Vector.sub(m.hole.pos2, body[i].position)) < shrinkRange) { Matter.Body.scale(body[i], shrinkScale, shrinkScale); if (body[i].mass < 0.05) { Matter.Composite.remove(engine.world, body[i]); body.splice(i, 1); m.fieldRange *= 0.8 if ((m.fieldMode === 0 || m.fieldMode === 9) && m.immuneCycle < m.cycle) m.energy += 0.02 * m.coupling * level.isReducedRegen + if (m.fieldMode === 0 || m.fieldMode === 9) m.energy += 0.02 * m.coupling * level.isReducedRegen if (tech.isWormholeWorms) { //pandimensional spermia - b.worm(Vector.add(m.hole.pos2, Vector.rotate({ x: m.fieldRange * 0.4, y: 0 }, 2 * Math.PI * Math.random()))) - Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(m.hole.unit, Math.PI / 2), -10)); + b.worm(Vector.add(m.hole.pos1, Vector.rotate({ x: m.fieldRange * 0.4, y: 0 }, 2 * Math.PI * Math.random()))) + Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(m.hole.unit, Math.PI / 2), 5)); if (Math.random() < 0.5) { //chance for a second worm - b.worm(Vector.add(m.hole.pos2, Vector.rotate({ x: m.fieldRange * 0.4, y: 0 }, 2 * Math.PI * Math.random()))) - Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(m.hole.unit, Math.PI / 2), -10)); + b.worm(Vector.add(m.hole.pos1, Vector.rotate({ x: m.fieldRange * 0.4, y: 0 }, 2 * Math.PI * Math.random()))) + Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(m.hole.unit, Math.PI / 2), 5)); } } break } } } - } else if (dist2 < suckRange) { - const pull = Vector.mult(Vector.normalise(Vector.sub(m.hole.pos2, body[i].position)), 1) - const slow = Vector.mult(body[i].velocity, slowScale) - Matter.Body.setVelocity(body[i], Vector.add(slow, pull)); - //shrink - if (Vector.magnitude(Vector.sub(m.hole.pos2, body[i].position)) < shrinkRange) { - Matter.Body.scale(body[i], shrinkScale, shrinkScale); - if (body[i].mass < 0.05) { - Matter.Composite.remove(engine.world, body[i]); - body.splice(i, 1); - m.fieldRange *= 0.8 - if ((m.fieldMode === 0 || m.fieldMode === 9) && m.immuneCycle < m.cycle) m.energy += 0.02 * m.coupling * level.isReducedRegen - if (m.fieldMode === 0 || m.fieldMode === 9) m.energy += 0.02 * m.coupling * level.isReducedRegen - if (tech.isWormholeWorms) { //pandimensional spermia - b.worm(Vector.add(m.hole.pos1, Vector.rotate({ x: m.fieldRange * 0.4, y: 0 }, 2 * Math.PI * Math.random()))) - Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(m.hole.unit, Math.PI / 2), 5)); - if (Math.random() < 0.5) { //chance for a second worm - b.worm(Vector.add(m.hole.pos1, Vector.rotate({ x: m.fieldRange * 0.4, y: 0 }, 2 * Math.PI * Math.random()))) - Matter.Body.setVelocity(bullet[bullet.length - 1], Vector.mult(Vector.rotate(m.hole.unit, Math.PI / 2), 5)); - } - } - break + } + } + if (tech.isWormHoleBullets) { + //teleport bullets + for (let i = 0, len = bullet.length; i < len; ++i) { //teleport bullets from hole1 to hole2 + if (!bullet[i].botType && !bullet[i].isInHole) { //don't teleport bots + if (Vector.magnitude(Vector.sub(m.hole.pos1, bullet[i].position)) < m.fieldRange) { //find if bullet is touching hole1 + Matter.Body.setPosition(bullet[i], Vector.add(m.hole.pos2, Vector.sub(m.hole.pos1, bullet[i].position))); + m.fieldRange += 5 + bullet[i].isInHole = true + } else if (Vector.magnitude(Vector.sub(m.hole.pos2, bullet[i].position)) < m.fieldRange) { //find if bullet is touching hole1 + Matter.Body.setPosition(bullet[i], Vector.add(m.hole.pos1, Vector.sub(m.hole.pos2, bullet[i].position))); + m.fieldRange += 5 + bullet[i].isInHole = true } } } - } - } - if (tech.isWormHoleBullets) { - //teleport bullets - for (let i = 0, len = bullet.length; i < len; ++i) { //teleport bullets from hole1 to hole2 - if (!bullet[i].botType && !bullet[i].isInHole) { //don't teleport bots - if (Vector.magnitude(Vector.sub(m.hole.pos1, bullet[i].position)) < m.fieldRange) { //find if bullet is touching hole1 - Matter.Body.setPosition(bullet[i], Vector.add(m.hole.pos2, Vector.sub(m.hole.pos1, bullet[i].position))); - m.fieldRange += 5 - bullet[i].isInHole = true - } else if (Vector.magnitude(Vector.sub(m.hole.pos2, bullet[i].position)) < m.fieldRange) { //find if bullet is touching hole1 - Matter.Body.setPosition(bullet[i], Vector.add(m.hole.pos1, Vector.sub(m.hole.pos2, bullet[i].position))); - m.fieldRange += 5 - bullet[i].isInHole = true + // mobs get pushed away + for (let i = 0, len = mob.length; i < len; i++) { + if (Vector.magnitude(Vector.sub(m.hole.pos1, mob[i].position)) < 200) { + const pull = Vector.mult(Vector.normalise(Vector.sub(m.hole.pos1, mob[i].position)), -0.07) + Matter.Body.setVelocity(mob[i], Vector.add(mob[i].velocity, pull)); + } + if (Vector.magnitude(Vector.sub(m.hole.pos2, mob[i].position)) < 200) { + const pull = Vector.mult(Vector.normalise(Vector.sub(m.hole.pos2, mob[i].position)), -0.07) + Matter.Body.setVelocity(mob[i], Vector.add(mob[i].velocity, pull)); } } } - // mobs get pushed away - for (let i = 0, len = mob.length; i < len; i++) { - if (Vector.magnitude(Vector.sub(m.hole.pos1, mob[i].position)) < 200) { - const pull = Vector.mult(Vector.normalise(Vector.sub(m.hole.pos1, mob[i].position)), -0.07) - Matter.Body.setVelocity(mob[i], Vector.add(mob[i].velocity, pull)); - } - if (Vector.magnitude(Vector.sub(m.hole.pos2, mob[i].position)) < 200) { - const pull = Vector.mult(Vector.normalise(Vector.sub(m.hole.pos2, mob[i].position)), -0.07) - Matter.Body.setVelocity(mob[i], Vector.add(mob[i].velocity, pull)); - } - } } - } - if (m.fieldCDcycle < m.cycle) { - const scale = 60 - const justPastMouse = Vector.add(Vector.mult(Vector.normalise(Vector.sub(simulation.mouseInGame, m.pos)), 50), simulation.mouseInGame) - const sub = Vector.sub(simulation.mouseInGame, m.pos) - const mag = Vector.magnitude(sub) + if (m.fieldCDcycle < m.cycle) { + const scale = 60 + const justPastMouse = Vector.add(Vector.mult(Vector.normalise(Vector.sub(simulation.mouseInGame, m.pos)), 50), simulation.mouseInGame) + const sub = Vector.sub(simulation.mouseInGame, m.pos) + const mag = Vector.magnitude(sub) - if (input.field) { - if (tech.isWormHolePause) { - const drain = m.fieldRegen + 0.000035 - if (m.energy > drain) { - m.energy -= drain + if (input.field) { + if (tech.isWormHolePause) { + // const drain = m.fieldRegen + 0.000035 + // if (m.energy > drain) { + // m.energy -= drain if (m.immuneCycle < m.cycle + 1) m.immuneCycle = m.cycle + 1; //player is immune to damage for 1 cycle m.isTimeDilated = true; @@ -5559,399 +5602,395 @@ const m = { }); player.force.x = 0 player.force.y = 0 + // } else { + // m.wakeCheck(); + // m.energy = 0; + // } + } + + m.grabPowerUp(); + //draw possible wormhole + if (tech.isWormholeMapIgnore && Matter.Query.ray(map, m.pos, justPastMouse).length !== 0) { + this.drain = (0.05 + 0.005 * Math.sqrt(mag)) * 2 } else { - m.wakeCheck(); - m.energy = 0; + this.drain = tech.isFreeWormHole ? 0 : 0.05 + 0.005 * Math.sqrt(mag) } - } - - m.grabPowerUp(); - //draw possible wormhole - if (tech.isWormholeMapIgnore && Matter.Query.ray(map, m.pos, justPastMouse).length !== 0) { - this.drain = (0.05 + 0.005 * Math.sqrt(mag)) * 2 - } else { - this.drain = tech.isFreeWormHole ? 0 : 0.05 + 0.005 * Math.sqrt(mag) - } - const unit = Vector.perp(Vector.normalise(sub)) - const where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } - m.fieldRange = 0.97 * m.fieldRange + 0.03 * (50 + 10 * Math.sin(simulation.cycle * 0.025)) - const edge2a = Vector.add(Vector.mult(unit, 1.5 * m.fieldRange), simulation.mouseInGame) - const edge2b = Vector.add(Vector.mult(unit, -1.5 * m.fieldRange), simulation.mouseInGame) - ctx.beginPath(); - ctx.moveTo(where.x, where.y) - ctx.bezierCurveTo(where.x, where.y, simulation.mouseInGame.x, simulation.mouseInGame.y, edge2a.x, edge2a.y); - ctx.moveTo(where.x, where.y) - ctx.bezierCurveTo(where.x, where.y, simulation.mouseInGame.x, simulation.mouseInGame.y, edge2b.x, edge2b.y); - if ( - mag > 250 && m.energy > this.drain && - (tech.isWormholeMapIgnore || Matter.Query.ray(map, m.pos, justPastMouse).length === 0) && - Matter.Query.region(map, { - min: { - x: simulation.mouseInGame.x - scale, - y: simulation.mouseInGame.y - scale - }, - max: { - x: simulation.mouseInGame.x + scale, - y: simulation.mouseInGame.y + scale - } - }).length === 0 - ) { - m.hole.isReady = true; - // ctx.fillStyle = "rgba(255,255,255,0.5)" - // ctx.fill(); - ctx.lineWidth = 1 - ctx.strokeStyle = "#000" - ctx.stroke(); - } else { - m.hole.isReady = false; - ctx.lineWidth = 1 - ctx.strokeStyle = "#000" - ctx.lineDashOffset = 30 * Math.random() - ctx.setLineDash([20, 40]); - ctx.stroke(); - ctx.setLineDash([]); - } - } else { - if (tech.isWormHolePause && m.isTimeDilated) m.wakeCheck(); - - //make new wormhole - if ( - m.hole.isReady && mag > 250 && m.energy > this.drain && - (tech.isWormholeMapIgnore || Matter.Query.ray(map, m.pos, justPastMouse).length === 0) && - Matter.Query.region(map, { - min: { - x: simulation.mouseInGame.x - scale, - y: simulation.mouseInGame.y - scale - }, - max: { - x: simulation.mouseInGame.x + scale, - y: simulation.mouseInGame.y + scale - } - }).length === 0 - ) { - m.energy -= this.drain - m.hole.isReady = false; - m.fieldRange = 0 - Matter.Body.setPosition(player, simulation.mouseInGame); - // simulation.translatePlayerAndCamera(simulation.mouseInGame) //too jerky - m.buttonCD_jump = 0 //this might fix a bug with jumping - const velocity = Vector.mult(Vector.normalise(sub), 20) - Matter.Body.setVelocity(player, { - x: velocity.x, - y: velocity.y - 4 //an extra vertical kick so the player hangs in place longer - }); - if (m.immuneCycle < m.cycle + 5) m.immuneCycle = m.cycle + 5; //player is immune to damage for 1/4 seconds - // move bots to player - for (let i = 0; i < bullet.length; i++) { - if (bullet[i].botType) { - Matter.Body.setPosition(bullet[i], Vector.add(player.position, { - x: 250 * (Math.random() - 0.5), - y: 250 * (Math.random() - 0.5) - })); - Matter.Body.setVelocity(bullet[i], { - x: 0, - y: 0 - }); - } + const unit = Vector.perp(Vector.normalise(sub)) + const where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) } + m.fieldRange = 0.97 * m.fieldRange + 0.03 * (50 + 10 * Math.sin(simulation.cycle * 0.025)) + const edge2a = Vector.add(Vector.mult(unit, 1.5 * m.fieldRange), simulation.mouseInGame) + const edge2b = Vector.add(Vector.mult(unit, -1.5 * m.fieldRange), simulation.mouseInGame) + ctx.beginPath(); + ctx.moveTo(where.x, where.y) + ctx.bezierCurveTo(where.x, where.y, simulation.mouseInGame.x, simulation.mouseInGame.y, edge2a.x, edge2a.y); + ctx.moveTo(where.x, where.y) + ctx.bezierCurveTo(where.x, where.y, simulation.mouseInGame.x, simulation.mouseInGame.y, edge2b.x, edge2b.y); + if ( + mag > 250 && m.energy > this.drain && + (tech.isWormholeMapIgnore || Matter.Query.ray(map, m.pos, justPastMouse).length === 0) && + Matter.Query.region(map, { + min: { + x: simulation.mouseInGame.x - scale, + y: simulation.mouseInGame.y - scale + }, + max: { + x: simulation.mouseInGame.x + scale, + y: simulation.mouseInGame.y + scale + } + }).length === 0 + ) { + m.hole.isReady = true; + // ctx.fillStyle = "rgba(255,255,255,0.5)" + // ctx.fill(); + ctx.lineWidth = 1 + ctx.strokeStyle = "#000" + ctx.stroke(); + } else { + m.hole.isReady = false; + ctx.lineWidth = 1 + ctx.strokeStyle = "#000" + ctx.lineDashOffset = 30 * Math.random() + ctx.setLineDash([20, 40]); + ctx.stroke(); + ctx.setLineDash([]); } + } else { + if (tech.isWormHolePause && m.isTimeDilated) m.wakeCheck(); - //set holes - m.hole.isOn = true; - m.hole.pos1.x = m.pos.x - m.hole.pos1.y = m.pos.y - m.hole.pos2.x = player.position.x - m.hole.pos2.y = player.position.y - m.hole.angle = Math.atan2(sub.y, sub.x) - m.hole.unit = Vector.perp(Vector.normalise(sub)) + //make new wormhole + if ( + m.hole.isReady && mag > 250 && m.energy > this.drain && + (tech.isWormholeMapIgnore || Matter.Query.ray(map, m.pos, justPastMouse).length === 0) && + Matter.Query.region(map, { + min: { x: simulation.mouseInGame.x - scale, y: simulation.mouseInGame.y - scale }, + max: { x: simulation.mouseInGame.x + scale, y: simulation.mouseInGame.y + scale } + }).length === 0 + ) { + //tech.isWormHolePause + // console.log(this.drain) + m.energy -= this.drain + m.hole.isReady = false; + m.fieldRange = 0 + Matter.Body.setPosition(player, simulation.mouseInGame); + // simulation.translatePlayerAndCamera(simulation.mouseInGame) //too jerky + m.buttonCD_jump = 0 //this might fix a bug with jumping + const velocity = Vector.mult(Vector.normalise(sub), 20) + Matter.Body.setVelocity(player, { + x: velocity.x, + y: velocity.y - 4 //an extra vertical kick so the player hangs in place longer + }); + if (m.immuneCycle < m.cycle + 5) m.immuneCycle = m.cycle + 5; //player is immune to damage for 1/4 seconds + // move bots to player + for (let i = 0; i < bullet.length; i++) { + if (bullet[i].botType) { + Matter.Body.setPosition(bullet[i], Vector.add(player.position, { + x: 250 * (Math.random() - 0.5), + y: 250 * (Math.random() - 0.5) + })); + Matter.Body.setVelocity(bullet[i], { + x: 0, + y: 0 + }); + } + } - if (tech.isWormholeDamage) { - who = Matter.Query.ray(mob, m.pos, simulation.mouseInGame, 100) - for (let i = 0; i < who.length; i++) { - if (who[i].body.alive) { - mobs.statusDoT(who[i].body, 1, 420) - mobs.statusStun(who[i].body, 360) + //set holes + m.hole.isOn = true; + m.hole.pos1.x = m.pos.x + m.hole.pos1.y = m.pos.y + m.hole.pos2.x = player.position.x + m.hole.pos2.y = player.position.y + m.hole.angle = Math.atan2(sub.y, sub.x) + m.hole.unit = Vector.perp(Vector.normalise(sub)) + + if (tech.isWormholeDamage) { + who = Matter.Query.ray(mob, m.pos, simulation.mouseInGame, 100) + for (let i = 0; i < who.length; i++) { + if (who[i].body.alive) { + mobs.statusDoT(who[i].body, 1, 420) + mobs.statusStun(who[i].body, 360) + } } } } } + + // if (true && m.energy > 0.5) { //teleport away low mass mobs + // // && !(m.cycle % 1) + // const hit = Matter.Query.region(mob, { + // min: { + // x: m.pos.x - 80, + // y: m.pos.y - 80 + // }, + // max: { + // x: m.pos.x + 80, + // y: m.pos.y + 160 + // } + // }) + + // // find incoming mob with low mass + // for (let i = 0; i < hit.length; i++) { + // if (hit[i].mass < 4 && m.energy > hit[i].mass * 0.06) { + // //is the mob moving towards the player? + + // // console.log('found one', hit[i].mass) + // const unit = Vector.normalise(hit[i].velocity) + // const jump = Vector.mult(unit, 200) + // const where = Vector.add(hit[i].position, jump) + // if (Matter.Query.ray(map, hit[i].position, where).length === 0) { // check if space 180 from mob is clear of body and map + // // m.energy -= hit[i].mass * 0.06 + // // m.fieldCDcycle = m.cycle + 30; + // simulation.drawList.push({ x: hit[i].position.x, y: hit[i].position.y, radius: 20, color: "#fff", time: 16 }); + // Matter.Body.setPosition(hit[i], where); + // simulation.drawList.push({ x: hit[i].position.x, y: hit[i].position.y, radius: 20, color: "#fff", time: 16 }); + // } + // // break + // } + // } + // } } - - // if (true && m.energy > 0.5) { //teleport away low mass mobs - // // && !(m.cycle % 1) - // const hit = Matter.Query.region(mob, { - // min: { - // x: m.pos.x - 80, - // y: m.pos.y - 80 - // }, - // max: { - // x: m.pos.x + 80, - // y: m.pos.y + 160 - // } - // }) - - // // find incoming mob with low mass - // for (let i = 0; i < hit.length; i++) { - // if (hit[i].mass < 4 && m.energy > hit[i].mass * 0.06) { - // //is the mob moving towards the player? - - // // console.log('found one', hit[i].mass) - // const unit = Vector.normalise(hit[i].velocity) - // const jump = Vector.mult(unit, 200) - // const where = Vector.add(hit[i].position, jump) - // if (Matter.Query.ray(map, hit[i].position, where).length === 0) { // check if space 180 from mob is clear of body and map - // // m.energy -= hit[i].mass * 0.06 - // // m.fieldCDcycle = m.cycle + 30; - // simulation.drawList.push({ x: hit[i].position.x, y: hit[i].position.y, radius: 20, color: "#fff", time: 16 }); - // Matter.Body.setPosition(hit[i], where); - // simulation.drawList.push({ x: hit[i].position.x, y: hit[i].position.y, radius: 20, color: "#fff", time: 16 }); + // if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed + // const justPastMouse = Vector.add(Vector.mult(Vector.normalise(Vector.sub(simulation.mouseInGame, m.pos)), 50), simulation.mouseInGame) + // const scale = 60 + // const sub = Vector.sub(simulation.mouseInGame, m.pos) + // const mag = Vector.magnitude(sub) + // const drain = tech.isFreeWormHole ? 0 : 0.06 + 0.006 * Math.sqrt(mag) + // if (m.hole.isReady && mag > 250 && m.energy > drain) { + // if ( + // Matter.Query.region(map, { + // min: { + // x: simulation.mouseInGame.x - scale, + // y: simulation.mouseInGame.y - scale + // }, + // max: { + // x: simulation.mouseInGame.x + scale, + // y: simulation.mouseInGame.y + scale + // } + // }).length === 0 && + // Matter.Query.ray(map, m.pos, justPastMouse).length === 0 + // // Matter.Query.ray(map, m.pos, simulation.mouseInGame).length === 0 && + // // Matter.Query.ray(map, player.position, simulation.mouseInGame).length === 0 && + // // Matter.Query.ray(map, player.position, justPastMouse).length === 0 + // ) { + // m.energy -= drain + // m.hole.isReady = false; + // m.fieldRange = 0 + // Matter.Body.setPosition(player, simulation.mouseInGame); + // m.buttonCD_jump = 0 //this might fix a bug with jumping + // const velocity = Vector.mult(Vector.normalise(sub), 20) + // Matter.Body.setVelocity(player, { + // x: velocity.x, + // y: velocity.y - 4 //an extra vertical kick so the player hangs in place longer + // }); + // if (m.immuneCycle < m.cycle + 15) m.immuneCycle = m.cycle + 15; //player is immune to damage for 1/4 seconds + // // move bots to player + // for (let i = 0; i < bullet.length; i++) { + // if (bullet[i].botType) { + // Matter.Body.setPosition(bullet[i], Vector.add(player.position, { + // x: 250 * (Math.random() - 0.5), + // y: 250 * (Math.random() - 0.5) + // })); + // Matter.Body.setVelocity(bullet[i], { + // x: 0, + // y: 0 + // }); + // } // } - // // break + + // //set holes + // m.hole.isOn = true; + // m.hole.pos1.x = m.pos.x + // m.hole.pos1.y = m.pos.y + // m.hole.pos2.x = player.position.x + // m.hole.pos2.y = player.position.y + // m.hole.angle = Math.atan2(sub.y, sub.x) + // m.hole.unit = Vector.perp(Vector.normalise(sub)) + + // if (tech.isWormholeDamage) { + // who = Matter.Query.ray(mob, m.pos, simulation.mouseInGame, 100) + // for (let i = 0; i < who.length; i++) { + // if (who[i].body.alive) { + // mobs.statusDoT(who[i].body, 1, 420) + // mobs.statusStun(who[i].body, 360) + // } + // } + // } + // } else { + // //draw failed wormhole + // const unit = Vector.perp(Vector.normalise(Vector.sub(simulation.mouseInGame, m.pos))) + // const where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle), } + // m.fieldRange = 0.97 * m.fieldRange + 0.03 * (50 + 10 * Math.sin(simulation.cycle * 0.025)) + // const edge2a = Vector.add(Vector.mult(unit, 1.5 * m.fieldRange), simulation.mouseInGame) + // const edge2b = Vector.add(Vector.mult(unit, -1.5 * m.fieldRange), simulation.mouseInGame) + // ctx.beginPath(); + // ctx.moveTo(where.x, where.y) + // ctx.bezierCurveTo(where.x, where.y, simulation.mouseInGame.x, simulation.mouseInGame.y, edge2a.x, edge2a.y); + // ctx.lineTo(edge2b.x, edge2b.y) + // ctx.bezierCurveTo(simulation.mouseInGame.x, simulation.mouseInGame.y, where.x, where.y, where.x, where.y); + // // ctx.fillStyle = "rgba(255,255,255,0.5)" + // // ctx.fill(); + // ctx.lineWidth = 1 + // ctx.strokeStyle = "#000" + // ctx.lineDashOffset = 30 * Math.random() + // ctx.setLineDash([20, 40]); + // ctx.stroke(); + // ctx.setLineDash([]); // } // } + // m.grabPowerUp(); + // } else { + // m.hole.isReady = true; // } + m.drawRegenEnergy() } - // if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed - // const justPastMouse = Vector.add(Vector.mult(Vector.normalise(Vector.sub(simulation.mouseInGame, m.pos)), 50), simulation.mouseInGame) - // const scale = 60 - // const sub = Vector.sub(simulation.mouseInGame, m.pos) - // const mag = Vector.magnitude(sub) - // const drain = tech.isFreeWormHole ? 0 : 0.06 + 0.006 * Math.sqrt(mag) - // if (m.hole.isReady && mag > 250 && m.energy > drain) { - // if ( - // Matter.Query.region(map, { - // min: { - // x: simulation.mouseInGame.x - scale, - // y: simulation.mouseInGame.y - scale - // }, - // max: { - // x: simulation.mouseInGame.x + scale, - // y: simulation.mouseInGame.y + scale - // } - // }).length === 0 && - // Matter.Query.ray(map, m.pos, justPastMouse).length === 0 - // // Matter.Query.ray(map, m.pos, simulation.mouseInGame).length === 0 && - // // Matter.Query.ray(map, player.position, simulation.mouseInGame).length === 0 && - // // Matter.Query.ray(map, player.position, justPastMouse).length === 0 - // ) { - // m.energy -= drain - // m.hole.isReady = false; - // m.fieldRange = 0 - // Matter.Body.setPosition(player, simulation.mouseInGame); - // m.buttonCD_jump = 0 //this might fix a bug with jumping - // const velocity = Vector.mult(Vector.normalise(sub), 20) - // Matter.Body.setVelocity(player, { - // x: velocity.x, - // y: velocity.y - 4 //an extra vertical kick so the player hangs in place longer - // }); - // if (m.immuneCycle < m.cycle + 15) m.immuneCycle = m.cycle + 15; //player is immune to damage for 1/4 seconds - // // move bots to player - // for (let i = 0; i < bullet.length; i++) { - // if (bullet[i].botType) { - // Matter.Body.setPosition(bullet[i], Vector.add(player.position, { - // x: 250 * (Math.random() - 0.5), - // y: 250 * (Math.random() - 0.5) - // })); - // Matter.Body.setVelocity(bullet[i], { - // x: 0, - // y: 0 - // }); - // } - // } + }, - // //set holes - // m.hole.isOn = true; - // m.hole.pos1.x = m.pos.x - // m.hole.pos1.y = m.pos.y - // m.hole.pos2.x = player.position.x - // m.hole.pos2.y = player.position.y - // m.hole.angle = Math.atan2(sub.y, sub.x) - // m.hole.unit = Vector.perp(Vector.normalise(sub)) + // rewind: function() { + // if (input.down) { + // if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed + // const DRAIN = 0.01 + // if (this.rewindCount < 289 && m.energy > DRAIN) { + // m.energy -= DRAIN - // if (tech.isWormholeDamage) { - // who = Matter.Query.ray(mob, m.pos, simulation.mouseInGame, 100) - // for (let i = 0; i < who.length; i++) { - // if (who[i].body.alive) { - // mobs.statusDoT(who[i].body, 1, 420) - // mobs.statusStun(who[i].body, 360) - // } - // } - // } - // } else { - // //draw failed wormhole - // const unit = Vector.perp(Vector.normalise(Vector.sub(simulation.mouseInGame, m.pos))) - // const where = { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle), } - // m.fieldRange = 0.97 * m.fieldRange + 0.03 * (50 + 10 * Math.sin(simulation.cycle * 0.025)) - // const edge2a = Vector.add(Vector.mult(unit, 1.5 * m.fieldRange), simulation.mouseInGame) - // const edge2b = Vector.add(Vector.mult(unit, -1.5 * m.fieldRange), simulation.mouseInGame) - // ctx.beginPath(); - // ctx.moveTo(where.x, where.y) - // ctx.bezierCurveTo(where.x, where.y, simulation.mouseInGame.x, simulation.mouseInGame.y, edge2a.x, edge2a.y); - // ctx.lineTo(edge2b.x, edge2b.y) - // ctx.bezierCurveTo(simulation.mouseInGame.x, simulation.mouseInGame.y, where.x, where.y, where.x, where.y); - // // ctx.fillStyle = "rgba(255,255,255,0.5)" - // // ctx.fill(); - // ctx.lineWidth = 1 - // ctx.strokeStyle = "#000" - // ctx.lineDashOffset = 30 * Math.random() - // ctx.setLineDash([20, 40]); - // ctx.stroke(); - // ctx.setLineDash([]); - // } - // } - // m.grabPowerUp(); - // } else { - // m.hole.isReady = true; - // } - m.drawRegenEnergy() + + // if (this.rewindCount === 0) { + // const shortPause = function() { + // if (m.defaultFPSCycle < m.cycle) { //back to default values + // simulation.fpsCap = simulation.fpsCapDefault + // simulation.fpsInterval = 1000 / simulation.fpsCap; + // // document.getElementById("dmg").style.transition = "opacity 1s"; + // // document.getElementById("dmg").style.opacity = "0"; + // } else { + // requestAnimationFrame(shortPause); + // } + // }; + // if (m.defaultFPSCycle < m.cycle) requestAnimationFrame(shortPause); + // simulation.fpsCap = 4 //1 is longest pause, 4 is standard + // simulation.fpsInterval = 1000 / simulation.fpsCap; + // m.defaultFPSCycle = m.cycle + // } + + + // this.rewindCount += 10; + // simulation.wipe = function() { //set wipe to have trails + // // ctx.fillStyle = "rgba(255,255,255,0)"; + // ctx.fillStyle = `rgba(221,221,221,${0.004})`; + // ctx.fillRect(0, 0, canvas.width, canvas.height); + // } + // let history = m.history[(m.cycle - this.rewindCount) % 300] + // Matter.Body.setPosition(player, history.position); + // Matter.Body.setVelocity(player, { x: history.velocity.x, y: history.velocity.y }); + // if (history.health > m.health) { + // m.health = history.health + // m.displayHealth(); + // } + // //grab power ups + // for (let i = 0, len = powerUp.length; i < len; ++i) { + // const dxP = player.position.x - powerUp[i].position.x; + // const dyP = player.position.y - powerUp[i].position.y; + // if (dxP * dxP + dyP * dyP < 50000 && !simulation.isChoosing && !(m.health === m.maxHealth && powerUp[i].name === "heal")) { + // powerUps.onPickUp(player.position); + // powerUp[i].effect(); + // Matter.Composite.remove(engine.world, powerUp[i]); + // powerUp.splice(i, 1); + // const shortPause = function() { + // if (m.defaultFPSCycle < m.cycle) { //back to default values + // simulation.fpsCap = simulation.fpsCapDefault + // simulation.fpsInterval = 1000 / simulation.fpsCap; + // // document.getElementById("dmg").style.transition = "opacity 1s"; + // // document.getElementById("dmg").style.opacity = "0"; + // } else { + // requestAnimationFrame(shortPause); + // } + // }; + // if (m.defaultFPSCycle < m.cycle) requestAnimationFrame(shortPause); + // simulation.fpsCap = 3 //1 is longest pause, 4 is standard + // simulation.fpsInterval = 1000 / simulation.fpsCap; + // m.defaultFPSCycle = m.cycle + // break; //because the array order is messed up after splice + // } + // } + // m.immuneCycle = m.cycle + 5; //player is immune to damage for 30 cycles + // } else { + // m.fieldCDcycle = m.cycle + 30; + // // m.resetHistory(); + // } + // } else { + // if (this.rewindCount !== 0) { + // m.fieldCDcycle = m.cycle + 30; + // m.resetHistory(); + // this.rewindCount = 0; + // simulation.wipe = function() { //set wipe to normal + // ctx.clearRect(0, 0, canvas.width, canvas.height); + // } + // } + // m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + // } + // } + // m.drawRegenEnergy() + // }, + }, + { + name: "grappling hook", + description: `use energy to fire a hook that pulls you
0.5x damage taken
9 energy per second`, + effect: () => { + m.fieldFire = true; + // m.holdingMassScale = 0.01; //can hold heavier blocks with lower cost to jumping + // m.fieldMeterColor = "#789"//"#456" + m.eyeFillColor = m.fieldMeterColor + m.fieldHarmReduction = 0.5; //40% reduction + m.grabPowerUpRange2 = 300000 //m.grabPowerUpRange2 = 200000; + + m.hold = function () { + if (m.isHolding) { + m.drawHold(m.holdingTarget); + m.holding(); + m.throwBlock(); + } else if (input.field) { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + if (m.fieldCDcycle < m.cycle) { + if (m.energy > 0.02) m.energy -= 0.02 + b.grapple({ x: m.pos.x + 40 * Math.cos(m.angle), y: m.pos.y + 40 * Math.sin(m.angle) }, m.angle) + if (m.fieldCDcycle < m.cycle + 20) m.fieldCDcycle = m.cycle + 20 + } + m.grabPowerUp(); + } else { + m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + if (tech.isHookDefense && m.energy > 0.15 && m.fieldCDcycle < m.cycle) { + const range = 300 + for (let i = 0; i < mob.length; i++) { + if (!mob[i].isBadTarget && + !mob[i].isInvulnerable && + Vector.magnitude(Vector.sub(m.pos, mob[i].position)) < range && + Matter.Query.ray(map, m.pos, mob[i].position).length === 0 + ) { + m.energy -= 0.1 + if (m.fieldCDcycle < m.cycle + 30) m.fieldCDcycle = m.cycle + 30 + const angle = Math.atan2(mob[i].position.y - player.position.y, mob[i].position.x - player.position.x); + b.harpoon(m.pos, mob[i], angle, 0.75, true, 20) // harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true, thrust = 0.1) { + bullet[bullet.length - 1].drain = 0 + const maxCount = 6 + for (let j = maxCount - 1; j > 0; j--) { + b.harpoon(m.pos, mob[i], angle + j * 2 * Math.PI / maxCount, 0.75, true, 10) + bullet[bullet.length - 1].drain = 0 + } + break + } + } + ctx.beginPath(); + ctx.arc(m.pos.x, m.pos.y, range, 0, 2 * Math.PI); + ctx.strokeStyle = "#000"; + ctx.lineWidth = 0.25; + ctx.setLineDash([10, 30]); + ctx.stroke(); + ctx.setLineDash([]); + } + } + m.drawRegenEnergy() + //look for nearby mobs and fire harpoons at them + } } }, - - // rewind: function() { - // if (input.down) { - // if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed - // const DRAIN = 0.01 - // if (this.rewindCount < 289 && m.energy > DRAIN) { - // m.energy -= DRAIN - - - // if (this.rewindCount === 0) { - // const shortPause = function() { - // if (m.defaultFPSCycle < m.cycle) { //back to default values - // simulation.fpsCap = simulation.fpsCapDefault - // simulation.fpsInterval = 1000 / simulation.fpsCap; - // // document.getElementById("dmg").style.transition = "opacity 1s"; - // // document.getElementById("dmg").style.opacity = "0"; - // } else { - // requestAnimationFrame(shortPause); - // } - // }; - // if (m.defaultFPSCycle < m.cycle) requestAnimationFrame(shortPause); - // simulation.fpsCap = 4 //1 is longest pause, 4 is standard - // simulation.fpsInterval = 1000 / simulation.fpsCap; - // m.defaultFPSCycle = m.cycle - // } - - - // this.rewindCount += 10; - // simulation.wipe = function() { //set wipe to have trails - // // ctx.fillStyle = "rgba(255,255,255,0)"; - // ctx.fillStyle = `rgba(221,221,221,${0.004})`; - // ctx.fillRect(0, 0, canvas.width, canvas.height); - // } - // let history = m.history[(m.cycle - this.rewindCount) % 300] - // Matter.Body.setPosition(player, history.position); - // Matter.Body.setVelocity(player, { x: history.velocity.x, y: history.velocity.y }); - // if (history.health > m.health) { - // m.health = history.health - // m.displayHealth(); - // } - // //grab power ups - // for (let i = 0, len = powerUp.length; i < len; ++i) { - // const dxP = player.position.x - powerUp[i].position.x; - // const dyP = player.position.y - powerUp[i].position.y; - // if (dxP * dxP + dyP * dyP < 50000 && !simulation.isChoosing && !(m.health === m.maxHealth && powerUp[i].name === "heal")) { - // powerUps.onPickUp(player.position); - // powerUp[i].effect(); - // Matter.Composite.remove(engine.world, powerUp[i]); - // powerUp.splice(i, 1); - // const shortPause = function() { - // if (m.defaultFPSCycle < m.cycle) { //back to default values - // simulation.fpsCap = simulation.fpsCapDefault - // simulation.fpsInterval = 1000 / simulation.fpsCap; - // // document.getElementById("dmg").style.transition = "opacity 1s"; - // // document.getElementById("dmg").style.opacity = "0"; - // } else { - // requestAnimationFrame(shortPause); - // } - // }; - // if (m.defaultFPSCycle < m.cycle) requestAnimationFrame(shortPause); - // simulation.fpsCap = 3 //1 is longest pause, 4 is standard - // simulation.fpsInterval = 1000 / simulation.fpsCap; - // m.defaultFPSCycle = m.cycle - // break; //because the array order is messed up after splice - // } - // } - // m.immuneCycle = m.cycle + 5; //player is immune to damage for 30 cycles - // } else { - // m.fieldCDcycle = m.cycle + 30; - // // m.resetHistory(); - // } - // } else { - // if (this.rewindCount !== 0) { - // m.fieldCDcycle = m.cycle + 30; - // m.resetHistory(); - // this.rewindCount = 0; - // simulation.wipe = function() { //set wipe to normal - // ctx.clearRect(0, 0, canvas.width, canvas.height); - // } - // } - // m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - // } - // } - // m.drawRegenEnergy() - // }, - }, - { - name: "grappling hook", - description: `use energy to fire a hook that pulls you
0.5x damage taken
9 energy per second`, - effect: () => { - m.fieldFire = true; - // m.holdingMassScale = 0.01; //can hold heavier blocks with lower cost to jumping - // m.fieldMeterColor = "#789"//"#456" - m.eyeFillColor = m.fieldMeterColor - m.fieldHarmReduction = 0.5; //40% reduction - m.grabPowerUpRange2 = 300000 //m.grabPowerUpRange2 = 200000; - - m.hold = function () { - if (m.isHolding) { - m.drawHold(m.holdingTarget); - m.holding(); - m.throwBlock(); - } else if (input.field) { - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - if (m.fieldCDcycle < m.cycle) { - if (m.energy > 0.02) m.energy -= 0.02 - b.grapple({ x: m.pos.x + 40 * Math.cos(m.angle), y: m.pos.y + 40 * Math.sin(m.angle) }, m.angle) - if (m.fieldCDcycle < m.cycle + 20) m.fieldCDcycle = m.cycle + 20 - } - m.grabPowerUp(); - } else { - m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) - if (tech.isHookDefense && m.energy > 0.15 && m.fieldCDcycle < m.cycle) { - const range = 300 - for (let i = 0; i < mob.length; i++) { - if (!mob[i].isBadTarget && - !mob[i].isInvulnerable && - Vector.magnitude(Vector.sub(m.pos, mob[i].position)) < range && - Matter.Query.ray(map, m.pos, mob[i].position).length === 0 - ) { - m.energy -= 0.1 - if (m.fieldCDcycle < m.cycle + 30) m.fieldCDcycle = m.cycle + 30 - const angle = Math.atan2(mob[i].position.y - player.position.y, mob[i].position.x - player.position.x); - b.harpoon(m.pos, mob[i], angle, 0.75, true, 20) // harpoon(where, target, angle = m.angle, harpoonSize = 1, isReturn = false, totalCycles = 35, isReturnAmmo = true, thrust = 0.1) { - bullet[bullet.length - 1].drain = 0 - const maxCount = 6 - for (let j = maxCount - 1; j > 0; j--) { - b.harpoon(m.pos, mob[i], angle + j * 2 * Math.PI / maxCount, 0.75, true, 10) - bullet[bullet.length - 1].drain = 0 - } - break - } - } - ctx.beginPath(); - ctx.arc(m.pos.x, m.pos.y, range, 0, 2 * Math.PI); - ctx.strokeStyle = "#000"; - ctx.lineWidth = 0.25; - ctx.setLineDash([10, 30]); - ctx.stroke(); - ctx.setLineDash([]); - } - } - m.drawRegenEnergy() - //look for nearby mobs and fire harpoons at them - } - } - }, ], //************************************************************************************ //************************************************************************************ diff --git a/js/powerup.js b/js/powerup.js index b7722bc..559f5a7 100644 --- a/js/powerup.js +++ b/js/powerup.js @@ -1653,6 +1653,7 @@ const powerUps = { if (b.inventory.length === 0) { powerUps.spawn(x, y, "gun", false); //first gun } else if (tech.totalCount === 0) { //first tech + powerUps.spawn(x - 22, y - 50, "ammo", false); //some ammo powerUps.spawn(x, y, "tech", false); } else if (b.inventory.length === 1) { //second gun or extra ammo if (Math.random() < 0.4) { diff --git a/js/simulation.js b/js/simulation.js index df22584..ae76c3c 100644 --- a/js/simulation.js +++ b/js/simulation.js @@ -987,7 +987,7 @@ const simulation = { } else { Composite.add(engine.world, [player]) } - shuffle(level.constraint) + seededShuffle(level.constraint) level.populateLevels() input.endKeySensing(); simulation.ephemera = [] diff --git a/js/spawn.js b/js/spawn.js index 4f743bc..0aa972a 100644 --- a/js/spawn.js +++ b/js/spawn.js @@ -745,7 +745,7 @@ const spawn = { // exit() { }, // }, ] - shuffle(me.mode); //THIS SHOULD NOT BE COMMENTED OUT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + me.mode.sort(() => Math.random() - 0.5); me.do = function () { this.fill = `hsl(${360 * Math.sin(this.cycle * 0.011)},${80 + 20 * Math.sin(this.cycle * 0.004)}%,${60 + 20 * Math.sin(this.cycle * 0.009)}%)` if (this.health < 1) { @@ -1771,6 +1771,7 @@ const spawn = { powerUps.spawn(me.position.x, me.position.y, "heal"); powerUps.spawn(me.position.x, me.position.y, "ammo"); powerUps.spawn(me.position.x, me.position.y, "ammo"); + powerUps.spawn(me.position.x, me.position.y, "ammo"); } else if (!m.isCloak) { me.foundPlayer(); } @@ -2683,22 +2684,24 @@ const spawn = { this.seePlayerByHistory() this.invulnerabilityCountDown-- if (this.isInvulnerable) { - ctx.beginPath(); - let vertices = this.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); - for (let i = 0; i < this.babyList.length; i++) { - if (this.babyList[i].alive) { - let vertices = this.babyList[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); + if (this.invulnerabilityCountDown > 90 || this.invulnerabilityCountDown % 20 > 10) { + ctx.beginPath(); + let vertices = this.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); + for (let i = 0; i < this.babyList.length; i++) { + if (this.babyList[i].alive) { + let vertices = this.babyList[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 = 3 + 0.2 * Math.min(this.invulnerabilityCountDown, 90) + 5 * Math.random() + ctx.strokeStyle = `rgba(255,255,255,${0.4 + 0.4 * Math.random()})`; + ctx.stroke(); } - ctx.lineWidth = 13 + 5 * Math.random(); - ctx.strokeStyle = `rgba(255,255,255,${0.5 + 0.2 * Math.random()})`; - ctx.stroke(); if (this.invulnerabilityCountDown < 0) { this.invulnerabilityCountDown = 110 this.isInvulnerable = false @@ -2711,7 +2714,7 @@ const spawn = { } } } else if (this.invulnerabilityCountDown < 0) { - this.invulnerabilityCountDown = 120 + 9 * simulation.difficulty + this.invulnerabilityCountDown = 240 + 5 * simulation.difficulty this.isInvulnerable = true if (this.damageReduction) this.startingDamageReduction = this.damageReduction this.damageReduction = 0 diff --git a/js/tech.js b/js/tech.js index 954987c..59d5ea4 100644 --- a/js/tech.js +++ b/js/tech.js @@ -271,7 +271,6 @@ const tech = { if (tech.isImmunityDamage && m.immuneCycle > m.cycle) dmg *= 3 if (tech.isPowerUpDamage) dmg *= 1 + 0.07 * powerUp.length if (tech.isDamageCooldown) dmg *= m.lastKillCycle + tech.isDamageCooldownTime > m.cycle ? 0.4 : 4 - if (tech.isDamageAfterKillNoRegen && m.lastKillCycle + 300 > m.cycle) dmg *= 2 if (tech.isDivisor && b.activeGun !== undefined && b.activeGun !== null && b.guns[b.activeGun].ammo % 3 === 0) dmg *= 1.9 if (tech.offGroundDamage && !m.onGround) dmg *= tech.offGroundDamage if (tech.isDilate) dmg *= 1.9 + 1.1 * Math.sin(m.cycle * 0.01) @@ -1018,7 +1017,7 @@ const tech = { { name: "Pareto efficiency", descriptionFunction() { - return `all you ${powerUps.orb.gun()} randomly get
5x or 0.2x ammo per ${powerUps.orb.ammo(1)}` + return `all your ${powerUps.orb.gun()} randomly get
5x or 0.2x ammo per ${powerUps.orb.ammo(1)}` }, maxCount: 1, count: 0, @@ -1033,7 +1032,7 @@ const tech = { let options = [] for (let i = 0; i < b.inventory.length; i++) options.push(b.inventory[i]) - options = shuffle(options) + options.sort(() => Math.random() - 0.5); for (let i = 0; i < options.length; i++) { const index = options[i] const scale = (i < options.length / 2) ? 4 : 0.25 @@ -2758,7 +2757,7 @@ const tech = { }, { name: "Pauli exclusion", - description: `for 7 seconds after mob collisions
become invulnerable and inhibit energy regen`, + description: `for 7 seconds after mob collisions
gain invulnerbility and blocked energy regen`, maxCount: 9, count: 0, frequency: 1, @@ -2777,7 +2776,7 @@ const tech = { }, { name: "spin-statistics theorem", - description: `for 1.9 seconds out of every 7 seconds
become invulnerable and inhibit energy regen`, + description: `for 1.9 seconds out of every 7 seconds
gain invulnerbility and blocked energy regen`, maxCount: 3, count: 0, frequency: 1, @@ -2795,7 +2794,7 @@ const tech = { }, { name: "fermion", - description: `for 5 seconds after mobs die
become invulnerable and inhibit energy regen`, + description: `if a mob has died in the last 5 seconds
gain invulnerbility and blocked energy regen`, maxCount: 1, count: 0, frequency: 1, @@ -3009,9 +3008,9 @@ const tech = { frequency: 1, frequencyDefault: 1, allowed() { - return !tech.isDamageAfterKillNoRegen + return true }, - requires: "not parasitism", + requires: "", effect() { tech.isCrouchRegen = true; //only used to check for requirements m.regenEnergy = function () { @@ -3042,29 +3041,6 @@ const tech = { tech.energySiphon = 0; } }, - { - name: "parasitism", - description: "if a mob has died in the last 5 seconds
2x damage, no passive energy generation", - maxCount: 1, - count: 0, - frequency: 1, - frequencyDefault: 1, - allowed() { - return !tech.isCrouchRegen - }, - requires: "not inductive charging", - effect() { - tech.isDamageAfterKillNoRegen = true; - m.regenEnergy = function () { - if (m.immuneCycle < m.cycle && (m.lastKillCycle + 300 < m.cycle) && m.fieldCDcycle < m.cycle) m.energy += m.fieldRegen * level.isReducedRegen; - if (m.energy < 0) m.energy = 0 - } - }, - remove() { - if (this.count) m.regenEnergy = m.regenEnergyDefault - tech.isDamageAfterKillNoRegen = false; - } - }, { name: "waste heat recovery", description: "if a mob has died in the last 5 seconds
generate 0.05x maximum energy every second", @@ -4769,7 +4745,7 @@ const tech = { for (let i = 0, len = tech.tech.length; i < len; i++) { // spawn new tech power ups if (tech.tech[i].count && !tech.tech[i].isInstant) pool.push(i) } - pool = shuffle(pool); //shuffles order of maps + pool.sort(() => Math.random() - 0.5); let removeCount = 0 for (let i = 0, len = pool.length * 0.5; i < len; i++) removeCount += tech.removeTech(pool[i]) this.damage = this.damagePerRemoved * removeCount @@ -8965,22 +8941,31 @@ const tech = { }, { name: "invariant", - description: "while placing your wormhole
use energy to pause time", + cost: 1, + descriptionFunction() { + return `use ${powerUps.orb.research(this.cost)}
pause time while placing your wormhole` + }, isFieldTech: true, maxCount: 1, count: 0, frequency: 2, frequencyDefault: 2, allowed() { - return m.fieldMode === 9 && !tech.isNoDraftPause + return m.fieldMode === 9 && !tech.isNoDraftPause && (build.isExperimentSelection || powerUps.research.count > this.cost - 1) }, requires: "wormhole, not eternalism", effect() { tech.isWormHolePause = true + for (let i = 0; i < this.cost; i++) { + if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1) + } }, remove() { if (tech.isWormHolePause && m.isTimeDilated) m.wakeCheck(); tech.isWormHolePause = false + if (this.count) { + powerUps.research.changeRerolls(this.cost) + } } }, { @@ -9031,7 +9016,7 @@ const tech = { allowed() { return m.fieldMode === 9 && !tech.isFreeWormHole }, - requires: "wormhole, not charmed baryons", + requires: "wormhole, not holographic principle", effect() { tech.isWormholeMapIgnore = true }, @@ -10044,8 +10029,8 @@ const tech = { }, requires: "", effect() { - // const colors = shuffle(["#f7b", "#0eb", "#467", "#0cf", "hsl(246,100%,77%)", "#26a"]) - const colors = shuffle([powerUps.research.color, powerUps.heal.color, powerUps.ammo.color, powerUps.ammo.color, powerUps.field.color, powerUps.gun.color]) + const colors = [powerUps.research.color, powerUps.heal.color, powerUps.ammo.color, powerUps.ammo.color, powerUps.field.color, powerUps.gun.color] + colors.sort(() => Math.random() - 0.5); powerUps.research.color = colors[0] powerUps.heal.color = colors[1] powerUps.ammo.color = colors[2] @@ -10075,37 +10060,7 @@ const tech = { } } }, - remove() { - // const colors = ["#f7b", "#0eb", "#467", "#0cf", "hsl(246,100%,77%)", "#26a"] //no shuffle - // powerUps.research.color = colors[0] - // powerUps.heal.color = colors[1] - // powerUps.ammo.color = colors[2] - // powerUps.field.color = colors[3] - // powerUps.tech.color = colors[4] - // powerUps.gun.color = colors[5] - // for (let i = 0; i < powerUp.length; i++) { - // switch (powerUp[i].name) { - // case "research": - // powerUp[i].color = colors[0] - // break; - // case "heal": - // powerUp[i].color = colors[1] - // break; - // case "ammo": - // powerUp[i].color = colors[2] - // break; - // case "field": - // powerUp[i].color = colors[3] - // break; - // case "tech": - // powerUp[i].color = colors[4] - // break; - // case "gun": - // powerUp[i].color = colors[5] - // break; - // } - // } - } + remove() { } }, { name: "emergency broadcasting", @@ -10519,7 +10474,7 @@ const tech = { }, requires: "", effect() { - String.prototype.shuffle = function () { + String.prototype.shuf = function () { var a = this.split(""), n = a.length; @@ -10532,7 +10487,7 @@ const tech = { return a.join(""); } - for (let i = 0, len = tech.tech.length; i < len; i++) tech.tech[i].name = tech.tech[i].name.shuffle() + for (let i = 0, len = tech.tech.length; i < len; i++) tech.tech[i].name = tech.tech[i].name.shuf() }, remove() { } }, @@ -12190,7 +12145,6 @@ const tech = { isDuplicateMobs: null, isDynamoBotUpgrade: null, isBlockPowerUps: null, - isDamageAfterKillNoRegen: null, isHarmReduceNoKill: null, isSwitchReality: null, isResearchReality: null, diff --git a/todo.txt b/todo.txt index 2802010..4dc0196 100644 --- a/todo.txt +++ b/todo.txt @@ -1,98 +1,77 @@ ******************************************************** NEXT PATCH ************************************************** -tech: demineralization - after mobs die gain 0.85x damage taken, effect stacks, but fades 10% every second -tech: remineralization - after mobs die gain 1.08x damage, effect stacks, but fades 10% every second -tech: equivalence principle - negative mass field doesn't cost energy -new JUNK tech: aerodynamics - -interferometer - slower elevator and lasers - wider side ledges - large laser blocking blocks -flocculation - fewer mobs - it's easier to get out of the slime -pavilion - move vanish elements - easier traversal - secret tunnel - removed debris, but added power ups and blocks -corridor - limited to bosses that don't interact with the movers poorly -gravitron, substructure, corridor, interferometer - added more heal and ammo power ups to match other levels -because some newer levels are zoomed out more - laser max range is 3000->5000 - nails last 1/3 of a second longer -bosses spawn an extra ammo power up - and 2 extra ammo on the hardest 2 difficulties -slasher mob's laserSwords will now damage a cloaked player -constraint announcement text looks more like computer code style to match game theme - -foam recoil is back: 1->0.7x horizontal force and 2->4.3x vertical up force - this makes it less annoying to horizontally and easier to kinda fly/float -negative mass field damage reduction 0.4->0.5x -holographic principle no longer slows player movement - added 2 research cost -fermion gives 6->5 seconds of invulnerability after mobs die -stability 0.2->0.1x damage taken at max health -non-Newtonian armor 0.3->0.4x damage taken after collisions -Zeno's paradox 0.15->0.2x damage taken -annihilation energy cost 10->8 to destroy mobs after collisions -radiative equilibrium damage is 3->4x for 8->4 seconds -aerostat can be taken 1->3 times -dynamic equilibrium damage increased by 6->8x damage per last damage taken -aerostat no longer has 0.9x damage for being on the ground -launch system 1.2->1.3x ammo for missiles -research says that repeatedly entering alternate realities builds up some positive effects - Hilbert space 4x->3x damage - Ψ(t) collapse 6->4 research on boss death -transdimensional worms: 50% chance for a second worm per block in wormhole -wormhole 7->8 energy regen per second -hidden-variable theory 1.15->1.2 damage after choosing a field tech -ghoster mobs are less likely to get knocked far away from the player for long periods of time +mantisBoss flashes for a second before it drops invulnerability +removed parasitism - it's too similar to invulnerability tech +invariant no longer drains energy while wormhole time is paused + added 1 research cost +added secret combo to change molecular assembler mode bug fixes - dynamic equilibrium was set to 100 times higher frequency then normal - when constraints hide health bar's it's now hidden in the pause menu - mobs aiming at cloaked player - snakeBoss more intelligently chases player for a few seconds - pulsarBoss aims at player's history 3 seconds in past - pulsar will not stop firing - but it will still not fire at cloaked player + issue with constraint: "mob death heals mobs" + mob health was becoming NaN, this was infecting other values like player energy + entering a seed in settings wasn't giving the same results as a randomly generated seeds + also removed some random code that was using seeded shuffle, but didn't need to + converted it to non seeded random shuffle with .sort(() => Math.random() - 0.5); ******************************************************** 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? - check which of these is working - level order - is flipped - constraint order - mob type order - boss order - gun, field, tech options - make field options offered precalculated so it doesn't depend on player choices? - generate all constraints at the start of the game - doesn't seem to be determined by the seed... - display future constraints in pause menu? - player can become crouched while not touching the ground if they exit the ground while crouched *********************************************************** TODO ***************************************************** + +use ←↑→↓↖↗↘↙ combos to allow fields to have special actions + !!should this be wasd, arrows, or both? + how to limit spam? + on cooldown + timer or once per level + energy cost + research cost + toggle modes + combos are listed in white text in field description, and console message when getting field + shows up on highlight, and mouse over in experiment mode + field ideas: + standing wave + push mobs away are any distance + pull mobs towards you + perfect diamagnetism + negative mass + molecular assembler - done + plasma torch + time dilation + metamaterial cloaking + pilot wave + spawn blocks + wormhole + shoot out all the blocks that were sucked in this level (maybe cap at like 10?, cap with energy spent to fire) + are block sizes stored properly? because they shrink before they get eaten... + store vertices on the body object if one does already exists + after eating store the saved vertices + where to fire blocks from and in what direction? + target mobs? + fire from player (and draw a wormhole looking graphic) + grappling hook + +new level idea: escort mission + player has to stay near something that moves slowly through the level + maybe only a zone around the escort is safe + safe from? + lasers + slime + radiation + use line of site vision? + is it going to feel too slow? + where to put in level order? + random option instead of reactor? + normal level? + 4th hard level? + already too many hard options + you could put in 2 hard levels in the level list + make the style look like substructure, that level looks great + use the motion sense lasers + tech synergy ideas a tech that spawns mobs that the player can use to trigger mob death tech - -wormhole field needs a buff - it's good on wide open maps, but it needs a defensive effect on more closed maps - fix? - increase energy regen? - new tech? - a way to make more blocks - the block can feed into worms/coupling energy - tech: - remove the research costs of all tech there's about 15 tech with a research cost