From 394c0c46af4dbf2a11bfe05104cddc5060c79db7 Mon Sep 17 00:00:00 2001 From: mcpower Date: Sat, 26 Mar 2022 17:09:42 +1100 Subject: [PATCH] Fix use of undefined in Decimal operations at start of game Sometimes, at the start of the game, undefined values were being passed into Decimal methods. This implicitly converts them to be 0 in the current version of break_eternity.js, but that behaviour should not be relied on as the TypeScript types for Decimal methods state that they do not support undefined values. As a result, this commit fixes many instances where, at the start of the game, some multipliers are set to zero due to this issue. This commit intentionally uses ?? instead of || as the author does not know what types are used - if it's possible that the left hand side could be a number (not a Decimal) then || shouldn't be used (if the left hand side was zero). However, if [old browser support] is required, || should be used instead of ?? (assuming the types are correct). This commit also uses Decimal.dOne and Decimal.dZero when needed instead of constructing new Decimal instances. [old browser support]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator#browser_compatibility --- js/layers.js | 96 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/js/layers.js b/js/layers.js index 7e595e4982..1147edd157 100644 --- a/js/layers.js +++ b/js/layers.js @@ -19,7 +19,9 @@ addLayer("p", { if (hasUpgrade("b", 11)) mult = mult.times(upgradeEffect("b", 11)); if (hasUpgrade("g", 11)) mult = mult.times(upgradeEffect("g", 11)); if (player.t.unlocked) mult = mult.times(tmp.t.enEff); - if (player.e.unlocked) mult = mult.times(tmp.e.buyables[11].effect.first); + // At the start of the the game, effects may be uninitialised, + // returning Decimal.dOne instead of the expected object. + if (player.e.unlocked) mult = mult.times(tmp.e.buyables[11].effect.first ?? Decimal.dOne); if (player.s.unlocked) mult = mult.times(buyableEffect("s", 11)); if (hasUpgrade("e", 12)) mult = mult.times(upgradeEffect("e", 12)); if (hasUpgrade("b", 31)) mult = mult.times(upgradeEffect("b", 31)); @@ -310,7 +312,9 @@ addLayer("b", { if (hasUpgrade("b", 12)) base = base.plus(upgradeEffect("b", 12)); if (hasUpgrade("b", 13)) base = base.plus(upgradeEffect("b", 13)); if (hasUpgrade("t", 11)) base = base.plus(upgradeEffect("t", 11)); - if (hasUpgrade("e", 11)) base = base.plus(upgradeEffect("e", 11).b); + // At the start of the the game, upgradeEffects might be uninitialised, + // returning Decimal.dOne instead of the expected object. + if (hasUpgrade("e", 11)) base = base.plus(upgradeEffect("e", 11).b ?? Decimal.dZero); if (player.e.unlocked) base = base.plus(layers.e.buyables[11].effect().second); if (player.s.unlocked) base = base.plus(buyableEffect("s", 12)); if (hasUpgrade("t", 25)) base = base.plus(upgradeEffect("t", 25)); @@ -582,7 +586,9 @@ addLayer("g", { // ADD if (hasUpgrade("g", 12)) base = base.plus(upgradeEffect("g", 12)); if (hasUpgrade("g", 13)) base = base.plus(upgradeEffect("g", 13)); - if (hasUpgrade("e", 11)) base = base.plus(upgradeEffect("e", 11).g); + // At the start of the the game, upgradeEffects might be uninitialised, + // returning Decimal.dOne instead of the expected object. + if (hasUpgrade("e", 11)) base = base.plus(upgradeEffect("e", 11).g ?? Decimal.dZero); if (player.e.unlocked) base = base.plus(layers.e.buyables[11].effect().second); if (player.s.unlocked) base = base.plus(buyableEffect("s", 12)); @@ -909,7 +915,10 @@ addLayer("t", { autoExt: false, }}, color: "#006609", - requires() { return new Decimal(1e120).times(Decimal.pow("1e180", Decimal.pow(player[this.layer].unlockOrder, 1.415038))) }, // Can be a function that takes requirement increases into account + requires() { + // At the start of the game, unlockOrder might be undefined. + return new Decimal(1e120).times(Decimal.pow("1e180", Decimal.pow(player[this.layer].unlockOrder ?? 0, 1.415038))) + }, // Can be a function that takes requirement increases into account resource: "time capsules", // Name of prestige currency baseResource: "points", // Name of resource prestige is based on baseAmount() {return player.points}, // Get the current amount of baseResource @@ -2485,7 +2494,9 @@ addLayer("sb", { if (player.o.unlocked) base = base.times(buyableEffect("o", 12)); if (((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes('b'):false) && hasUpgrade("b", 12)) base = base.times(upgradeEffect("b", 12).max(1)); if (((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes('b'):false) && hasUpgrade("b", 13)) base = base.times(upgradeEffect("b", 13).max(1)); - base = base.times(tmp.n.dustEffs.blue); + // At the start of the the game, dustEffs might not be initialized, + // returning Decimal.dOne instead of the expected object. + base = base.times(tmp.n.dustEffs.blue ?? Decimal.dOne); if (((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes("h"):false) && hasChallenge("h", 12)) base = base.times(player.hs.points.plus(1)); if (player.en.unlocked) base = base.pow(tmp.en.swEff); if (player.c.unlocked && tmp.c) base = base.pow(tmp.c.eff5); @@ -2632,7 +2643,9 @@ addLayer("h", { exponent() { return new Decimal(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?.2:.125) }, // Prestige currency exponent gainMult() { // Calculate the multiplier for main currency from bonuses mult = new Decimal(1) - if (hasUpgrade("q", 14)) mult = mult.times(upgradeEffect("q", 14).h); + // At the start of the the game, upgradeEffects might be uninitialised, + // returning Decimal.dOne instead of the expected object. + if (hasUpgrade("q", 14)) mult = mult.times(upgradeEffect("q", 14).h ?? Decimal.dOne); if (player.m.unlocked) mult = mult.times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes("m"):false)?tmp.m.mainHexEff:tmp.m.hexEff); if (hasUpgrade("ba", 22)) mult = mult.times(tmp.ba.negBuff); return mult @@ -2808,7 +2821,9 @@ addLayer("h", { currencyInternalName: "points", rewardDescription() { return "Timeless completions boost Super Generator Power gain based on your time "+(hasUpgrade("ss", 33)?"playing this game.":"in this Row 4 reset.") }, rewardEffect() { - let eff = Decimal.div(9, Decimal.add((hasUpgrade("ss", 33)?(player.timePlayed||0):player.q.time), 1).cbrt().pow(hasUpgrade("ss", 23)?(-1):1)).plus(1).pow(challengeCompletions("h", 31)).times(tmp.n.realDustEffs2?tmp.n.realDustEffs2.blueOrange:new Decimal(1)).pow(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?5:1); + // At the start of the the game, dustEffs might be uninitialised, + // returning Decimal.dOne instead of the expected object. + let eff = Decimal.div(9, Decimal.add((hasUpgrade("ss", 33)?(player.timePlayed||0):player.q.time), 1).cbrt().pow(hasUpgrade("ss", 23)?(-1):1)).plus(1).pow(challengeCompletions("h", 31)).times(tmp.n.realDustEffs2.blueOrange ?? Decimal.dOne).pow(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?5:1); if (!eff.eq(eff)) eff = new Decimal(1); return eff; }, @@ -2851,7 +2866,9 @@ addLayer("h", { currencyInternalName: "points", rewardDescription: "Option D completions multiply the Time Energy gain base.", rewardEffect() { - let eff = softcap("option_d", Decimal.pow(100, Decimal.pow(challengeCompletions("h", 32), 2))).times(tmp.n.realDustEffs2?tmp.n.realDustEffs2.blueOrange:new Decimal(1)); + // At the start of the the game, dustEffs might be uninitialised, + // returning Decimal.dOne instead of the expected object. + let eff = softcap("option_d", Decimal.pow(100, Decimal.pow(challengeCompletions("h", 32), 2))).times(tmp.n.realDustEffs2.blueOrange ?? Decimal.dOne); if (!eff.eq(eff)) eff = new Decimal(1); return eff; }, @@ -2938,7 +2955,9 @@ addLayer("q", { exponent() { return new Decimal(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?.008:.0075) }, // Prestige currency exponent gainMult() { // Calculate the multiplier for main currency from bonuses mult = new Decimal(1) - if (hasUpgrade("q", 14)) mult = mult.times(upgradeEffect("q", 14).q); + // At the start of the the game, upgradeEffects might be uninitialised, + // returning Decimal.dOne instead of the expected object. + if (hasUpgrade("q", 14)) mult = mult.times(upgradeEffect("q", 14).q || Decimal.dOne); mult = mult.times(improvementEffect("q", 33)); if (player.m.unlocked) mult = mult.times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes("m"):false)?tmp.m.mainHexEff:tmp.m.hexEff); if (hasUpgrade("ba", 22)) mult = mult.times(tmp.ba.negBuff); @@ -3650,7 +3669,8 @@ addLayer("o", { title: "Solar Cores", gain() { return player.o.points.div(2).root(1.5).pow(tmp.o.buyableGainExp).floor() }, effect() { - let amt = player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables) + // At the start of the the game, multiplyBuyables might be undefined. + let amt = player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables ?? Decimal.dZero) amt = softcap("solCores2", softcap("solCores", amt)); return Decimal.pow(hasUpgrade("ss", 22)?(amt.plus(1).pow(tmp.o.solPow).cbrt()):(amt.plus(1).pow(tmp.o.solPow).log10().plus(1)), ((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.1:1) }, @@ -3678,7 +3698,10 @@ addLayer("o", { 12: { title: "Tachoclinal Plasma", gain() { return player.o.points.div(100).times(player.o.energy.div(2500)).root(3.5).pow(tmp.o.buyableGainExp).floor() }, - effect() { return Decimal.pow(hasUpgrade("p", 24)?Decimal.pow(10, player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables).plus(1).log10().cbrt()):(player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables).plus(1).pow(tmp.o.solPow).log10().plus(1).log10().times(10).plus(1)), ((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.1:1) }, + effect() { + // At the start of the game, multiplyBuyables might be undefined. + return Decimal.pow(hasUpgrade("p", 24)?Decimal.pow(10, player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables ?? Decimal.dZero).plus(1).log10().cbrt()):(player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables).plus(1).pow(tmp.o.solPow).log10().plus(1).log10().times(10).plus(1)), ((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.1:1) + }, display() { // Everything else displayed in the buyable button after the title let data = tmp[this.layer].buyables[this.id] let display = ("Sacrifice all of your Solarity & Solar Energy for "+formatWhole(tmp[this.layer].buyables[this.id].gain)+" Tachoclinal Plasma\n"+ @@ -3703,7 +3726,10 @@ addLayer("o", { 13: { title: "Convectional Energy", gain() { return player.o.points.div(1e3).times(player.o.energy.div(2e5)).times(player.ss.subspace.div(10)).root(6.5).pow(tmp.o.buyableGainExp).floor() }, - effect() { return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables).plus(1).pow(tmp.o.solPow).log10().plus(1).pow(2.5).pow(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?27.5:1) }, + effect() { + // At the start of the game, multiplyBuyables might be undefined. + return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables ?? Decimal.dZero).plus(1).pow(tmp.o.solPow).log10().plus(1).pow(2.5).pow(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?27.5:1) + }, display() { // Everything else displayed in the buyable button after the title let data = tmp[this.layer].buyables[this.id] let display = ("Sacrifice all of your Solarity, Solar Energy, & Subspace for "+formatWhole(tmp[this.layer].buyables[this.id].gain)+" Convectional Energy\n"+ @@ -3730,7 +3756,8 @@ addLayer("o", { title: "Coronal Waves", gain() { return player.o.points.div(1e5).root(5).times(player.o.energy.div(1e30).root(30)).times(player.ss.subspace.div(1e8).root(8)).times(player.q.energy.div("1e675").root(675)).pow(tmp.o.buyableGainExp).floor() }, effect() { - let eff = player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables).plus(1).pow(tmp.o.solPow).log10().plus(1).log10(); + // At the start of the game, multiplyBuyables might be undefined. + let eff = player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables ?? Decimal.dZero).plus(1).pow(tmp.o.solPow).log10().plus(1).log10(); eff = softcap("corona", eff); if (hasUpgrade("hn", 24)) eff = eff.times(2); if ((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false) eff = eff.times(1.4); @@ -3763,7 +3790,8 @@ addLayer("o", { title: "Noval Remnants", gain() { return player.o.buyables[11].div(1e150).pow(3).pow(tmp.o.buyableGainExp).floor() }, effect() { - return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables).plus(1).pow(tmp.o.solPow).log10().root(10).times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.4:1).plus(1) + // At the start of the game, multiplyBuyables might be undefined. + return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables ?? Decimal.dZero).plus(1).pow(tmp.o.solPow).log10().root(10).times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.4:1).plus(1) }, display() { let data = tmp[this.layer].buyables[this.id] @@ -3788,7 +3816,8 @@ addLayer("o", { title: "Nuclear Forges", gain() { return player.o.buyables[11].div(1e175).times(player.o.energy.div("1e2500").root(10)).pow(tmp.o.buyableGainExp).floor() }, effect() { - return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables).plus(1).pow(tmp.o.solPow).log10().plus(1).log10().root(2.5).times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.4:1) + // At the start of the game, multiplyBuyables might be undefined. + return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables ?? Decimal.dZero).plus(1).pow(tmp.o.solPow).log10().plus(1).log10().root(2.5).times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.4:1) }, display() { let data = tmp[this.layer].buyables[this.id] @@ -3814,7 +3843,8 @@ addLayer("o", { title: "Blueshifted Flares", gain() { return player.o.points.div("1e400").pow(10).pow(tmp.o.buyableGainExp).floor() }, effect() { - return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables).plus(1).pow(tmp.o.solPow).log10().plus(1).log10().root(5).div(10).times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.9:1) + // At the start of the game, multiplyBuyables might be undefined. + return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables ?? Decimal.dZero).plus(1).pow(tmp.o.solPow).log10().plus(1).log10().root(5).div(10).times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.9:1) }, display() { let data = tmp[this.layer].buyables[this.id] @@ -3839,7 +3869,8 @@ addLayer("o", { title: "Combustion Gas", gain() { return player.o.energy.div("1e200000").root(100).pow(tmp.o.buyableGainExp).floor() }, effect() { - return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables).plus(1).pow(tmp.o.solPow).log10().plus(1).log10().plus(1).log10().div(1.6).times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.9:1).plus(1) + // At the start of the game, multiplyBuyables might be undefined. + return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables ?? Decimal.dZero).plus(1).pow(tmp.o.solPow).log10().plus(1).log10().plus(1).log10().div(1.6).times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.9:1).plus(1) }, display() { let data = tmp[this.layer].buyables[this.id] @@ -3864,7 +3895,8 @@ addLayer("o", { title: "Thermonuclear Reactants", gain() { return player.o.points.div("1e500").pow(10).pow(tmp.o.buyableGainExp).floor() }, effect() { - return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables).plus(1).pow(tmp.o.solPow).log10().plus(1).log10().plus(1).log10().div(3).times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.9:1); + // At the start of the game, multiplyBuyables might be undefined. + return player[this.layer].buyables[this.id].times(tmp.o.multiplyBuyables || Decimal.dZero).plus(1).pow(tmp.o.solPow).log10().plus(1).log10().plus(1).log10().div(3).times(((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?1.9:1); }, display() { let data = tmp[this.layer].buyables[this.id] @@ -4161,7 +4193,9 @@ addLayer("m", { gainMult() { // Calculate the multiplier for main currency from bonuses mult = new Decimal(1); if (hasAchievement("a", 74)) mult = mult.times(challengeEffect("h", 32)); - return mult.times(tmp.n.realDustEffs2?tmp.n.realDustEffs2.purpleBlue:new Decimal(1)); + // At the start of the the game, dustEffs might be uninitialised, + // returning Decimal.dOne instead of the expected object. + return mult.times(tmp.n.realDustEffs2.purpleBlue ?? Decimal.dOne); }, gainExp() { // Calculate the exponent on main currency from bonuses return new Decimal(1) @@ -4560,7 +4594,9 @@ addLayer("ba", { dirBase() { return player.ba.points.times(10) }, posGainMult() { let mult = new Decimal(1); - if (hasUpgrade("ba", 24)) mult = mult.times(upgradeEffect("ba", 24).pos); + // At the start of the the game, upgradeEffects might be uninitialised, + // returning Decimal.dOne instead of the expected object. + if (hasUpgrade("ba", 24)) mult = mult.times(upgradeEffect("ba", 24).pos ?? Decimal.dOne); return mult; }, posGain() { return Decimal.pow(tmp.ba.dirBase, (hasMilestone("hn", 2)&&player.ma.current!="ba")?1:player.ba.allotted).times((hasMilestone("hn", 2)&&player.ma.current!="ba")?1:(player.ba.allotted)).times(tmp.ba.posGainMult) }, @@ -4575,7 +4611,15 @@ addLayer("ba", { posNerf() { return tmp.ba.noNerfs?new Decimal(1):(player.ba.pos.plus(1).sqrt().pow(inChallenge("h", 41)?100:1)) }, negGainMult() { let mult = new Decimal(1); - if (hasUpgrade("ba", 24)) mult = mult.times(upgradeEffect("ba", 24).neg); + // At the start of the the game, upgradeEffects might be uninitialised, + // returning Decimal.dOne instead of the expected object. + // That Decimal has a .neg method, which is easily confusable for the + // .neg property of the intended object! Only apply the effect if + // .neg _isn't_ a function. + if (hasUpgrade("ba", 24)) { + const effect = upgradeEffect("ba", 24).neg; + if (typeof effect !== "function") mult = mult.times(effect); + } return mult; }, negGain() { return Decimal.pow(tmp.ba.dirBase, (hasMilestone("hn", 2)&&player.ma.current!="ba")?1:(1-player.ba.allotted)).times((hasMilestone("hn", 2)&&player.ma.current!="ba")?1:(1-player.ba.allotted)).times(tmp.ba.negGainMult) }, @@ -4888,7 +4932,9 @@ addLayer("ps", { if (tmp.ps.buyables[11].effects.damned) mult = mult.times(tmp.ps.buyables[11].effects.damned||1); if (player.i.buyables[11].gte(1)) mult = mult.times(buyableEffect("s", 16)); if (player.c.unlocked) mult = mult.times(tmp.c.eff4); - return mult.times(tmp.n.dustEffs.purple); + // At the start of the the game, dustEffs might be uninitialised, + // returning Decimal.dOne instead of the expected object. + return mult.times(tmp.n.dustEffs.purple ?? Decimal.dOne); }, soulGain() { let gain = (((Array.isArray(tmp.ma.mastered))?tmp.ma.mastered.includes(this.layer):false)?Decimal.pow(tmp.ps.soulGainExp, player.ps.points):Decimal.pow(player.ps.points, tmp.ps.soulGainExp)).div(9.4).times(layers.ps.soulGainMult()); @@ -4912,7 +4958,11 @@ addLayer("ps", { let eff = player.ps.souls.plus(1).pow(layers.ps.soulEffExp()); return eff; }, - powerGain() { return player.ps.souls.plus(1).times(tmp.ps.buyables[21].effect).times(tmp.n.dustEffs.purple) }, + powerGain() { + // At the start of the the game, dustEffs might be uninitialised, + // returning Decimal.dOne instead of the expected object. + return player.ps.souls.plus(1).times(tmp.ps.buyables[21].effect).times(tmp.n.dustEffs.purple ?? Decimal.dOne) + }, powerExp() { return player.ps.points.sqrt().times(tmp.ps.buyables[21].effect) }, tabFormat: { "Main Tab": {