diff --git a/Makefile b/Makefile index 57df82629..59b0b8bf2 100644 --- a/Makefile +++ b/Makefile @@ -25,3 +25,6 @@ lintcode: ## Lint just the code lintcss: ## Lint just the CSS npm run csslint + +dev: ## Start esbuild:watch and open a live server on port 3000 + npm run dev diff --git a/Pictures/Achievements/Grouped/AcceleratorBoosts.png b/Pictures/Achievements/Grouped/AcceleratorBoosts.png new file mode 100644 index 000000000..df706896a Binary files /dev/null and b/Pictures/Achievements/Grouped/AcceleratorBoosts.png differ diff --git a/Pictures/Achievements/Grouped/Accelerators.png b/Pictures/Achievements/Grouped/Accelerators.png new file mode 100644 index 000000000..7236cee60 Binary files /dev/null and b/Pictures/Achievements/Grouped/Accelerators.png differ diff --git a/Pictures/Achievements/Grouped/AntCrumbs.png b/Pictures/Achievements/Grouped/AntCrumbs.png new file mode 100644 index 000000000..1cf02515e Binary files /dev/null and b/Pictures/Achievements/Grouped/AntCrumbs.png differ diff --git a/Pictures/Achievements/Grouped/AscensionCount.png b/Pictures/Achievements/Grouped/AscensionCount.png new file mode 100644 index 000000000..e9a8139d0 Binary files /dev/null and b/Pictures/Achievements/Grouped/AscensionCount.png differ diff --git a/Pictures/Achievements/Grouped/AscensionScore.png b/Pictures/Achievements/Grouped/AscensionScore.png new file mode 100644 index 000000000..d3a2ef8f1 Binary files /dev/null and b/Pictures/Achievements/Grouped/AscensionScore.png differ diff --git a/Pictures/Achievements/Grouped/CampaignTokens.png b/Pictures/Achievements/Grouped/CampaignTokens.png new file mode 100644 index 000000000..75453adaa Binary files /dev/null and b/Pictures/Achievements/Grouped/CampaignTokens.png differ diff --git a/Pictures/Achievements/Grouped/Challenge1.png b/Pictures/Achievements/Grouped/Challenge1.png new file mode 100644 index 000000000..e4dbc4896 Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge1.png differ diff --git a/Pictures/Achievements/Grouped/Challenge10.png b/Pictures/Achievements/Grouped/Challenge10.png new file mode 100644 index 000000000..4d5cd0416 Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge10.png differ diff --git a/Pictures/Achievements/Grouped/Challenge11.png b/Pictures/Achievements/Grouped/Challenge11.png new file mode 100644 index 000000000..c83778322 Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge11.png differ diff --git a/Pictures/Achievements/Grouped/Challenge12.png b/Pictures/Achievements/Grouped/Challenge12.png new file mode 100644 index 000000000..6bebe9e5f Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge12.png differ diff --git a/Pictures/Achievements/Grouped/Challenge13.png b/Pictures/Achievements/Grouped/Challenge13.png new file mode 100644 index 000000000..5f06d269b Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge13.png differ diff --git a/Pictures/Achievements/Grouped/Challenge14.png b/Pictures/Achievements/Grouped/Challenge14.png new file mode 100644 index 000000000..c3a887193 Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge14.png differ diff --git a/Pictures/Achievements/Grouped/Challenge2.png b/Pictures/Achievements/Grouped/Challenge2.png new file mode 100644 index 000000000..8f9c8e443 Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge2.png differ diff --git a/Pictures/Achievements/Grouped/Challenge3.png b/Pictures/Achievements/Grouped/Challenge3.png new file mode 100644 index 000000000..bfa92ccdc Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge3.png differ diff --git a/Pictures/Achievements/Grouped/Challenge4.png b/Pictures/Achievements/Grouped/Challenge4.png new file mode 100644 index 000000000..d418b7ea5 Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge4.png differ diff --git a/Pictures/Achievements/Grouped/Challenge5.png b/Pictures/Achievements/Grouped/Challenge5.png new file mode 100644 index 000000000..346e34cc0 Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge5.png differ diff --git a/Pictures/Achievements/Grouped/Challenge6.png b/Pictures/Achievements/Grouped/Challenge6.png new file mode 100644 index 000000000..9ed7bab40 Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge6.png differ diff --git a/Pictures/Achievements/Grouped/Challenge7.png b/Pictures/Achievements/Grouped/Challenge7.png new file mode 100644 index 000000000..371aff081 Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge7.png differ diff --git a/Pictures/Achievements/Grouped/Challenge8.png b/Pictures/Achievements/Grouped/Challenge8.png new file mode 100644 index 000000000..4ea9a3648 Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge8.png differ diff --git a/Pictures/Achievements/Grouped/Challenge9.png b/Pictures/Achievements/Grouped/Challenge9.png new file mode 100644 index 000000000..0395d38e4 Binary files /dev/null and b/Pictures/Achievements/Grouped/Challenge9.png differ diff --git a/Pictures/Achievements/Grouped/Constant.png b/Pictures/Achievements/Grouped/Constant.png new file mode 100644 index 000000000..b03ad3b87 Binary files /dev/null and b/Pictures/Achievements/Grouped/Constant.png differ diff --git a/Pictures/Achievements/Grouped/FifthOwnedCoin.png b/Pictures/Achievements/Grouped/FifthOwnedCoin.png new file mode 100644 index 000000000..9fe8f3bd2 Binary files /dev/null and b/Pictures/Achievements/Grouped/FifthOwnedCoin.png differ diff --git a/Pictures/Achievements/Grouped/FirstOwnedCoin.png b/Pictures/Achievements/Grouped/FirstOwnedCoin.png new file mode 100644 index 000000000..77f03498c Binary files /dev/null and b/Pictures/Achievements/Grouped/FirstOwnedCoin.png differ diff --git a/Pictures/Achievements/Grouped/FourthOwnedCoin.png b/Pictures/Achievements/Grouped/FourthOwnedCoin.png new file mode 100644 index 000000000..69a6f6ebf Binary files /dev/null and b/Pictures/Achievements/Grouped/FourthOwnedCoin.png differ diff --git a/Pictures/Achievements/Grouped/GroupedBorder.png b/Pictures/Achievements/Grouped/GroupedBorder.png new file mode 100644 index 000000000..28833a943 Binary files /dev/null and b/Pictures/Achievements/Grouped/GroupedBorder.png differ diff --git a/Pictures/Achievements/Grouped/Multipliers.png b/Pictures/Achievements/Grouped/Multipliers.png new file mode 100644 index 000000000..1e4c30520 Binary files /dev/null and b/Pictures/Achievements/Grouped/Multipliers.png differ diff --git a/Pictures/Achievements/Grouped/PrestigeCount.png b/Pictures/Achievements/Grouped/PrestigeCount.png new file mode 100644 index 000000000..c4cbde60b Binary files /dev/null and b/Pictures/Achievements/Grouped/PrestigeCount.png differ diff --git a/Pictures/Achievements/Grouped/PrestigePointGain.png b/Pictures/Achievements/Grouped/PrestigePointGain.png new file mode 100644 index 000000000..11da76ffa Binary files /dev/null and b/Pictures/Achievements/Grouped/PrestigePointGain.png differ diff --git a/Pictures/Achievements/Grouped/ReincarnationCount.png b/Pictures/Achievements/Grouped/ReincarnationCount.png new file mode 100644 index 000000000..dcd618e91 Binary files /dev/null and b/Pictures/Achievements/Grouped/ReincarnationCount.png differ diff --git a/Pictures/Achievements/Grouped/ReincarnationPointGain.png b/Pictures/Achievements/Grouped/ReincarnationPointGain.png new file mode 100644 index 000000000..623b43b7e Binary files /dev/null and b/Pictures/Achievements/Grouped/ReincarnationPointGain.png differ diff --git a/Pictures/Achievements/Grouped/RuneFreeLevel.png b/Pictures/Achievements/Grouped/RuneFreeLevel.png new file mode 100644 index 000000000..970b21e54 Binary files /dev/null and b/Pictures/Achievements/Grouped/RuneFreeLevel.png differ diff --git a/Pictures/Achievements/Grouped/RuneLevel.png b/Pictures/Achievements/Grouped/RuneLevel.png new file mode 100644 index 000000000..970b21e54 Binary files /dev/null and b/Pictures/Achievements/Grouped/RuneLevel.png differ diff --git a/Pictures/Achievements/Grouped/SacMult.png b/Pictures/Achievements/Grouped/SacMult.png new file mode 100644 index 000000000..e29668901 Binary files /dev/null and b/Pictures/Achievements/Grouped/SacMult.png differ diff --git a/Pictures/Achievements/Grouped/SecondOwnedCoin.png b/Pictures/Achievements/Grouped/SecondOwnedCoin.png new file mode 100644 index 000000000..3924d91d1 Binary files /dev/null and b/Pictures/Achievements/Grouped/SecondOwnedCoin.png differ diff --git a/Pictures/Achievements/Grouped/SingularityCount.png b/Pictures/Achievements/Grouped/SingularityCount.png new file mode 100644 index 000000000..d0c3d4c75 Binary files /dev/null and b/Pictures/Achievements/Grouped/SingularityCount.png differ diff --git a/Pictures/Achievements/Grouped/SpeedBlessing.png b/Pictures/Achievements/Grouped/SpeedBlessing.png new file mode 100644 index 000000000..fe1a6dec6 Binary files /dev/null and b/Pictures/Achievements/Grouped/SpeedBlessing.png differ diff --git a/Pictures/Achievements/Grouped/SpeedSpirit.png b/Pictures/Achievements/Grouped/SpeedSpirit.png new file mode 100644 index 000000000..9ce15a101 Binary files /dev/null and b/Pictures/Achievements/Grouped/SpeedSpirit.png differ diff --git a/Pictures/Achievements/Grouped/ThirdOwnedCoin.png b/Pictures/Achievements/Grouped/ThirdOwnedCoin.png new file mode 100644 index 000000000..5ea870999 Binary files /dev/null and b/Pictures/Achievements/Grouped/ThirdOwnedCoin.png differ diff --git a/Pictures/Achievements/Grouped/TranscendPointGain.png b/Pictures/Achievements/Grouped/TranscendPointGain.png new file mode 100644 index 000000000..0637c858e Binary files /dev/null and b/Pictures/Achievements/Grouped/TranscendPointGain.png differ diff --git a/Pictures/Achievements/Grouped/TranscensionCount.png b/Pictures/Achievements/Grouped/TranscensionCount.png new file mode 100644 index 000000000..a549b3a5d Binary files /dev/null and b/Pictures/Achievements/Grouped/TranscensionCount.png differ diff --git a/Pictures/Achievements/Milestones/AchievementTalismanEnhancement.png b/Pictures/Achievements/Milestones/AchievementTalismanEnhancement.png new file mode 100644 index 000000000..74b16fb84 Binary files /dev/null and b/Pictures/Achievements/Milestones/AchievementTalismanEnhancement.png differ diff --git a/Pictures/Achievements/Milestones/AchievementTalismanUnlock.png b/Pictures/Achievements/Milestones/AchievementTalismanUnlock.png new file mode 100644 index 000000000..06ba92335 Binary files /dev/null and b/Pictures/Achievements/Milestones/AchievementTalismanUnlock.png differ diff --git a/Pictures/Achievements/Milestones/AutoPrestige.png b/Pictures/Achievements/Milestones/AutoPrestige.png new file mode 100644 index 000000000..4504f565e Binary files /dev/null and b/Pictures/Achievements/Milestones/AutoPrestige.png differ diff --git a/Pictures/Achievements/Milestones/Border.png b/Pictures/Achievements/Milestones/Border.png new file mode 100644 index 000000000..02e64461d Binary files /dev/null and b/Pictures/Achievements/Milestones/Border.png differ diff --git a/Pictures/Achievements/Milestones/DuplicationRune.png b/Pictures/Achievements/Milestones/DuplicationRune.png new file mode 100644 index 000000000..062db9e1a Binary files /dev/null and b/Pictures/Achievements/Milestones/DuplicationRune.png differ diff --git a/Pictures/Achievements/Milestones/OfferingTimerScaling.png b/Pictures/Achievements/Milestones/OfferingTimerScaling.png new file mode 100644 index 000000000..37afab538 Binary files /dev/null and b/Pictures/Achievements/Milestones/OfferingTimerScaling.png differ diff --git a/Pictures/Achievements/Milestones/PrismRune.png b/Pictures/Achievements/Milestones/PrismRune.png new file mode 100644 index 000000000..5c5ffd98f Binary files /dev/null and b/Pictures/Achievements/Milestones/PrismRune.png differ diff --git a/Pictures/Achievements/Milestones/SIRune.png b/Pictures/Achievements/Milestones/SIRune.png new file mode 100644 index 000000000..9502839e6 Binary files /dev/null and b/Pictures/Achievements/Milestones/SIRune.png differ diff --git a/Pictures/Achievements/Milestones/SalvageChallengeBuff.png b/Pictures/Achievements/Milestones/SalvageChallengeBuff.png new file mode 100644 index 000000000..2ddf3d2ee Binary files /dev/null and b/Pictures/Achievements/Milestones/SalvageChallengeBuff.png differ diff --git a/Pictures/Achievements/Milestones/SpeedRune.png b/Pictures/Achievements/Milestones/SpeedRune.png new file mode 100644 index 000000000..9782b3feb Binary files /dev/null and b/Pictures/Achievements/Milestones/SpeedRune.png differ diff --git a/Pictures/Achievements/Milestones/ThriftRune.png b/Pictures/Achievements/Milestones/ThriftRune.png new file mode 100644 index 000000000..ca20622ac Binary files /dev/null and b/Pictures/Achievements/Milestones/ThriftRune.png differ diff --git a/Pictures/Achievements/Milestones/Tier1CrystalAutobuy.png b/Pictures/Achievements/Milestones/Tier1CrystalAutobuy.png new file mode 100644 index 000000000..cb685bf2b Binary files /dev/null and b/Pictures/Achievements/Milestones/Tier1CrystalAutobuy.png differ diff --git a/Pictures/Achievements/Milestones/Tier2CrystalAutobuy.png b/Pictures/Achievements/Milestones/Tier2CrystalAutobuy.png new file mode 100644 index 000000000..88330125a Binary files /dev/null and b/Pictures/Achievements/Milestones/Tier2CrystalAutobuy.png differ diff --git a/Pictures/Achievements/Milestones/Tier3CrystalAutobuy.png b/Pictures/Achievements/Milestones/Tier3CrystalAutobuy.png new file mode 100644 index 000000000..6b9417e20 Binary files /dev/null and b/Pictures/Achievements/Milestones/Tier3CrystalAutobuy.png differ diff --git a/Pictures/Achievements/Milestones/Tier4CrystalAutobuy.png b/Pictures/Achievements/Milestones/Tier4CrystalAutobuy.png new file mode 100644 index 000000000..5fe84279c Binary files /dev/null and b/Pictures/Achievements/Milestones/Tier4CrystalAutobuy.png differ diff --git a/Pictures/Achievements/Milestones/Tier5CrystalAutobuy.png b/Pictures/Achievements/Milestones/Tier5CrystalAutobuy.png new file mode 100644 index 000000000..bb57ef0a6 Binary files /dev/null and b/Pictures/Achievements/Milestones/Tier5CrystalAutobuy.png differ diff --git a/Pictures/Achievements/Progressive/AmbrosiaCount.png b/Pictures/Achievements/Progressive/AmbrosiaCount.png new file mode 100644 index 000000000..1222311a7 Binary files /dev/null and b/Pictures/Achievements/Progressive/AmbrosiaCount.png differ diff --git a/Pictures/Achievements/Progressive/Exalts.png b/Pictures/Achievements/Progressive/Exalts.png new file mode 100644 index 000000000..a1b44637a Binary files /dev/null and b/Pictures/Achievements/Progressive/Exalts.png differ diff --git a/Pictures/Achievements/Progressive/FreeRuneLevel.png b/Pictures/Achievements/Progressive/FreeRuneLevel.png new file mode 100644 index 000000000..7b95ebeee Binary files /dev/null and b/Pictures/Achievements/Progressive/FreeRuneLevel.png differ diff --git a/Pictures/Achievements/Progressive/OcteractUpgrades.png b/Pictures/Achievements/Progressive/OcteractUpgrades.png new file mode 100644 index 000000000..4bd12e118 Binary files /dev/null and b/Pictures/Achievements/Progressive/OcteractUpgrades.png differ diff --git a/Pictures/Achievements/Progressive/RedAmbrosiaCount.png b/Pictures/Achievements/Progressive/RedAmbrosiaCount.png new file mode 100644 index 000000000..998e91391 Binary files /dev/null and b/Pictures/Achievements/Progressive/RedAmbrosiaCount.png differ diff --git a/Pictures/Achievements/Progressive/RedAmbrosiaUpgrades.png b/Pictures/Achievements/Progressive/RedAmbrosiaUpgrades.png new file mode 100644 index 000000000..998e91391 Binary files /dev/null and b/Pictures/Achievements/Progressive/RedAmbrosiaUpgrades.png differ diff --git a/Pictures/Achievements/Progressive/RuneLevel.png b/Pictures/Achievements/Progressive/RuneLevel.png new file mode 100644 index 000000000..7b95ebeee Binary files /dev/null and b/Pictures/Achievements/Progressive/RuneLevel.png differ diff --git a/Pictures/Achievements/Progressive/SingularityCount.png b/Pictures/Achievements/Progressive/SingularityCount.png new file mode 100644 index 000000000..63c8396fe Binary files /dev/null and b/Pictures/Achievements/Progressive/SingularityCount.png differ diff --git a/Pictures/Achievements/Progressive/SingularityUpgrades.png b/Pictures/Achievements/Progressive/SingularityUpgrades.png new file mode 100644 index 000000000..7d73763f3 Binary files /dev/null and b/Pictures/Achievements/Progressive/SingularityUpgrades.png differ diff --git a/Pictures/Achievements/Progressive/TalismanRarities.png b/Pictures/Achievements/Progressive/TalismanRarities.png new file mode 100644 index 000000000..f0592505f Binary files /dev/null and b/Pictures/Achievements/Progressive/TalismanRarities.png differ diff --git a/Pictures/Achievements/Rewards/AchievementRewardBorder.aseprite b/Pictures/Achievements/Rewards/AchievementRewardBorder.aseprite new file mode 100644 index 000000000..ea7960db6 Binary files /dev/null and b/Pictures/Achievements/Rewards/AchievementRewardBorder.aseprite differ diff --git a/Pictures/Achievements/Rewards/AmbrosiaLuck.png b/Pictures/Achievements/Rewards/AmbrosiaLuck.png new file mode 100644 index 000000000..949ac2f9a Binary files /dev/null and b/Pictures/Achievements/Rewards/AmbrosiaLuck.png differ diff --git a/Pictures/Achievements/Rewards/Border.png b/Pictures/Achievements/Rewards/Border.png new file mode 100644 index 000000000..d9c418180 Binary files /dev/null and b/Pictures/Achievements/Rewards/Border.png differ diff --git a/Pictures/Achievements/Rewards/Obtainium.png b/Pictures/Achievements/Rewards/Obtainium.png new file mode 100644 index 000000000..f11408454 Binary files /dev/null and b/Pictures/Achievements/Rewards/Obtainium.png differ diff --git a/Pictures/Achievements/Rewards/Offerings.png b/Pictures/Achievements/Rewards/Offerings.png new file mode 100644 index 000000000..1e538820c Binary files /dev/null and b/Pictures/Achievements/Rewards/Offerings.png differ diff --git a/Pictures/Achievements/Rewards/Quarks.png b/Pictures/Achievements/Rewards/Quarks.png new file mode 100644 index 000000000..a998f6128 Binary files /dev/null and b/Pictures/Achievements/Rewards/Quarks.png differ diff --git a/Pictures/Achievements/Rewards/RedAmbrosiaLuck.png b/Pictures/Achievements/Rewards/RedAmbrosiaLuck.png new file mode 100644 index 000000000..c965a3b65 Binary files /dev/null and b/Pictures/Achievements/Rewards/RedAmbrosiaLuck.png differ diff --git a/Pictures/Achievements/Rewards/Salvage.png b/Pictures/Achievements/Rewards/Salvage.png new file mode 100644 index 000000000..7edee763a Binary files /dev/null and b/Pictures/Achievements/Rewards/Salvage.png differ diff --git a/Pictures/Achievements/Rewards/WowCubes.png b/Pictures/Achievements/Rewards/WowCubes.png new file mode 100644 index 000000000..d93330004 Binary files /dev/null and b/Pictures/Achievements/Rewards/WowCubes.png differ diff --git a/Pictures/Achievements/Rewards/WowHepteractCubes.png b/Pictures/Achievements/Rewards/WowHepteractCubes.png new file mode 100644 index 000000000..f9166cd3e Binary files /dev/null and b/Pictures/Achievements/Rewards/WowHepteractCubes.png differ diff --git a/Pictures/Achievements/Rewards/WowHyperCubes.png b/Pictures/Achievements/Rewards/WowHyperCubes.png new file mode 100644 index 000000000..aac96cc51 Binary files /dev/null and b/Pictures/Achievements/Rewards/WowHyperCubes.png differ diff --git a/Pictures/Achievements/Rewards/WowOcteracts.png b/Pictures/Achievements/Rewards/WowOcteracts.png new file mode 100644 index 000000000..abc86efe3 Binary files /dev/null and b/Pictures/Achievements/Rewards/WowOcteracts.png differ diff --git a/Pictures/Achievements/Rewards/WowPlatonicCubes.png b/Pictures/Achievements/Rewards/WowPlatonicCubes.png new file mode 100644 index 000000000..c617f8234 Binary files /dev/null and b/Pictures/Achievements/Rewards/WowPlatonicCubes.png differ diff --git a/Pictures/Achievements/Rewards/WowTesseracts.png b/Pictures/Achievements/Rewards/WowTesseracts.png new file mode 100644 index 000000000..e024cbb8e Binary files /dev/null and b/Pictures/Achievements/Rewards/WowTesseracts.png differ diff --git a/Pictures/Achievements/Ungrouped/Ascended.png b/Pictures/Achievements/Ungrouped/Ascended.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/Ascended.png differ diff --git a/Pictures/Achievements/Ungrouped/Chal1NoGen.png b/Pictures/Achievements/Ungrouped/Chal1NoGen.png new file mode 100644 index 000000000..19786c8fa Binary files /dev/null and b/Pictures/Achievements/Ungrouped/Chal1NoGen.png differ diff --git a/Pictures/Achievements/Ungrouped/Chal2NoGen.png b/Pictures/Achievements/Ungrouped/Chal2NoGen.png new file mode 100644 index 000000000..af3fa769d Binary files /dev/null and b/Pictures/Achievements/Ungrouped/Chal2NoGen.png differ diff --git a/Pictures/Achievements/Ungrouped/Chal3NoGen.png b/Pictures/Achievements/Ungrouped/Chal3NoGen.png new file mode 100644 index 000000000..5781b1c9d Binary files /dev/null and b/Pictures/Achievements/Ungrouped/Chal3NoGen.png differ diff --git a/Pictures/Achievements/Ungrouped/DiamondSearch.png b/Pictures/Achievements/Ungrouped/DiamondSearch.png new file mode 100644 index 000000000..6eb8a0041 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/DiamondSearch.png differ diff --git a/Pictures/Achievements/Ungrouped/ExtraChallenging.png b/Pictures/Achievements/Ungrouped/ExtraChallenging.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/ExtraChallenging.png differ diff --git a/Pictures/Achievements/Ungrouped/GenerationAch1.png b/Pictures/Achievements/Ungrouped/GenerationAch1.png new file mode 100644 index 000000000..b0c5d2c1a Binary files /dev/null and b/Pictures/Achievements/Ungrouped/GenerationAch1.png differ diff --git a/Pictures/Achievements/Ungrouped/GenerationAch2.png b/Pictures/Achievements/Ungrouped/GenerationAch2.png new file mode 100644 index 000000000..450c3649a Binary files /dev/null and b/Pictures/Achievements/Ungrouped/GenerationAch2.png differ diff --git a/Pictures/Achievements/Ungrouped/GenerationAch3.png b/Pictures/Achievements/Ungrouped/GenerationAch3.png new file mode 100644 index 000000000..11f1641d7 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/GenerationAch3.png differ diff --git a/Pictures/Achievements/Ungrouped/GenerationAch4.png b/Pictures/Achievements/Ungrouped/GenerationAch4.png new file mode 100644 index 000000000..5fd9857c3 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/GenerationAch4.png differ diff --git a/Pictures/Achievements/Ungrouped/HighlyBlessed.png b/Pictures/Achievements/Ungrouped/HighlyBlessed.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/HighlyBlessed.png differ diff --git a/Pictures/Achievements/Ungrouped/MetaChallenged.png b/Pictures/Achievements/Ungrouped/MetaChallenged.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/MetaChallenged.png differ diff --git a/Pictures/Achievements/Ungrouped/OneCubeOfMany.png b/Pictures/Achievements/Ungrouped/OneCubeOfMany.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/OneCubeOfMany.png differ diff --git a/Pictures/Achievements/Ungrouped/Overtaxed.png b/Pictures/Achievements/Ungrouped/Overtaxed.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/Overtaxed.png differ diff --git a/Pictures/Achievements/Ungrouped/ParticipationTrophy.png b/Pictures/Achievements/Ungrouped/ParticipationTrophy.png new file mode 100644 index 000000000..2a4337448 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/ParticipationTrophy.png differ diff --git a/Pictures/Achievements/Ungrouped/PrestigeNoAccelerator.png b/Pictures/Achievements/Ungrouped/PrestigeNoAccelerator.png new file mode 100644 index 000000000..30f3cd5ea Binary files /dev/null and b/Pictures/Achievements/Ungrouped/PrestigeNoAccelerator.png differ diff --git a/Pictures/Achievements/Ungrouped/PrestigeNoCoinUpgrade.png b/Pictures/Achievements/Ungrouped/PrestigeNoCoinUpgrade.png new file mode 100644 index 000000000..e636ae4be Binary files /dev/null and b/Pictures/Achievements/Ungrouped/PrestigeNoCoinUpgrade.png differ diff --git a/Pictures/Achievements/Ungrouped/PrestigeNoMult.png b/Pictures/Achievements/Ungrouped/PrestigeNoMult.png new file mode 100644 index 000000000..9cb37acfc Binary files /dev/null and b/Pictures/Achievements/Ungrouped/PrestigeNoMult.png differ diff --git a/Pictures/Achievements/Ungrouped/ReincarnationMinimumUpgrades.png b/Pictures/Achievements/Ungrouped/ReincarnationMinimumUpgrades.png new file mode 100644 index 000000000..77e50af89 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/ReincarnationMinimumUpgrades.png differ diff --git a/Pictures/Achievements/Ungrouped/ReincarnationNoAccelerator.png b/Pictures/Achievements/Ungrouped/ReincarnationNoAccelerator.png new file mode 100644 index 000000000..d1ea37e59 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/ReincarnationNoAccelerator.png differ diff --git a/Pictures/Achievements/Ungrouped/ReincarnationNoCoinDiamondMythosUpgrade.png b/Pictures/Achievements/Ungrouped/ReincarnationNoCoinDiamondMythosUpgrade.png new file mode 100644 index 000000000..8b3077213 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/ReincarnationNoCoinDiamondMythosUpgrade.png differ diff --git a/Pictures/Achievements/Ungrouped/ReincarnationNoCoinDiamondUpgrade.png b/Pictures/Achievements/Ungrouped/ReincarnationNoCoinDiamondUpgrade.png new file mode 100644 index 000000000..766626206 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/ReincarnationNoCoinDiamondUpgrade.png differ diff --git a/Pictures/Achievements/Ungrouped/ReincarnationNoCoinUpgrade.png b/Pictures/Achievements/Ungrouped/ReincarnationNoCoinUpgrade.png new file mode 100644 index 000000000..e59942001 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/ReincarnationNoCoinUpgrade.png differ diff --git a/Pictures/Achievements/Ungrouped/ReincarnationNoMult.png b/Pictures/Achievements/Ungrouped/ReincarnationNoMult.png new file mode 100644 index 000000000..a2e2137da Binary files /dev/null and b/Pictures/Achievements/Ungrouped/ReincarnationNoMult.png differ diff --git a/Pictures/Achievements/Ungrouped/ReincarnationNoMultiplier.png b/Pictures/Achievements/Ungrouped/ReincarnationNoMultiplier.png new file mode 100644 index 000000000..6393bc37f Binary files /dev/null and b/Pictures/Achievements/Ungrouped/ReincarnationNoMultiplier.png differ diff --git a/Pictures/Achievements/Ungrouped/SadisticAch.png b/Pictures/Achievements/Ungrouped/SadisticAch.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/SadisticAch.png differ diff --git a/Pictures/Achievements/Ungrouped/SeeingRed.png b/Pictures/Achievements/Ungrouped/SeeingRed.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/SeeingRed.png differ diff --git a/Pictures/Achievements/Ungrouped/SeeingRedNoBlue.png b/Pictures/Achievements/Ungrouped/SeeingRedNoBlue.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/SeeingRedNoBlue.png differ diff --git a/Pictures/Achievements/Ungrouped/Smith.png b/Pictures/Achievements/Ungrouped/Smith.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/Smith.png differ diff --git a/Pictures/Achievements/Ungrouped/ThousandMoons.png b/Pictures/Achievements/Ungrouped/ThousandMoons.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/ThousandMoons.png differ diff --git a/Pictures/Achievements/Ungrouped/ThousandSuns.png b/Pictures/Achievements/Ungrouped/ThousandSuns.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/ThousandSuns.png differ diff --git a/Pictures/Achievements/Ungrouped/TranscendNoAccelerator.png b/Pictures/Achievements/Ungrouped/TranscendNoAccelerator.png new file mode 100644 index 000000000..beb7b576c Binary files /dev/null and b/Pictures/Achievements/Ungrouped/TranscendNoAccelerator.png differ diff --git a/Pictures/Achievements/Ungrouped/TranscendNoCoinDiamondUpgrade.png b/Pictures/Achievements/Ungrouped/TranscendNoCoinDiamondUpgrade.png new file mode 100644 index 000000000..9b2cf6343 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/TranscendNoCoinDiamondUpgrade.png differ diff --git a/Pictures/Achievements/Ungrouped/TranscendNoCoinUpgrade.png b/Pictures/Achievements/Ungrouped/TranscendNoCoinUpgrade.png new file mode 100644 index 000000000..d0e9f07b9 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/TranscendNoCoinUpgrade.png differ diff --git a/Pictures/Achievements/Ungrouped/TranscendNoMult.png b/Pictures/Achievements/Ungrouped/TranscendNoMult.png new file mode 100644 index 000000000..186c4d4a5 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/TranscendNoMult.png differ diff --git a/Pictures/Achievements/Ungrouped/Unsmith.png b/Pictures/Achievements/Ungrouped/Unsmith.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/Unsmith.png differ diff --git a/Pictures/Achievements/Ungrouped/VeryFast.png b/Pictures/Achievements/Ungrouped/VeryFast.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/VeryFast.png differ diff --git a/Pictures/Achievements/Ungrouped/VerySlow.png b/Pictures/Achievements/Ungrouped/VerySlow.png new file mode 100644 index 000000000..d6f064764 Binary files /dev/null and b/Pictures/Achievements/Ungrouped/VerySlow.png differ diff --git a/Pictures/Default/BlueberryLuck4.png b/Pictures/Default/BlueberryLuck4.png new file mode 100644 index 000000000..a0f89de80 Binary files /dev/null and b/Pictures/Default/BlueberryLuck4.png differ diff --git a/Pictures/Default/BlueberryRuneOOMBonus.png b/Pictures/Default/BlueberryRuneOOMBonus.png new file mode 100644 index 000000000..ad90d0c1f Binary files /dev/null and b/Pictures/Default/BlueberryRuneOOMBonus.png differ diff --git a/Pictures/Default/BlueberryTalismanBonusRuneLevel.png b/Pictures/Default/BlueberryTalismanBonusRuneLevel.png new file mode 100644 index 000000000..dc85ebcf8 Binary files /dev/null and b/Pictures/Default/BlueberryTalismanBonusRuneLevel.png differ diff --git a/Pictures/Default/ExaltChalNoOfferingPower.png b/Pictures/Default/ExaltChalNoOfferingPower.png new file mode 100644 index 000000000..f3d6a67af Binary files /dev/null and b/Pictures/Default/ExaltChalNoOfferingPower.png differ diff --git a/Pictures/Default/ExaltChalNoRune.png b/Pictures/Default/ExaltChalNoRune.png new file mode 100644 index 000000000..f3d6a67af Binary files /dev/null and b/Pictures/Default/ExaltChalNoRune.png differ diff --git a/Pictures/Default/OcteractTalismanLevelCap1.png b/Pictures/Default/OcteractTalismanLevelCap1.png new file mode 100644 index 000000000..26a56f6ea Binary files /dev/null and b/Pictures/Default/OcteractTalismanLevelCap1.png differ diff --git a/Pictures/Default/OcteractTalismanLevelCap2.png b/Pictures/Default/OcteractTalismanLevelCap2.png new file mode 100644 index 000000000..b9b906ac3 Binary files /dev/null and b/Pictures/Default/OcteractTalismanLevelCap2.png differ diff --git a/Pictures/Default/OcteractTalismanLevelCap3.png b/Pictures/Default/OcteractTalismanLevelCap3.png new file mode 100644 index 000000000..9d218d006 Binary files /dev/null and b/Pictures/Default/OcteractTalismanLevelCap3.png differ diff --git a/Pictures/Default/OcteractTalismanLevelCap4.png b/Pictures/Default/OcteractTalismanLevelCap4.png new file mode 100644 index 000000000..0e55d49c2 Binary files /dev/null and b/Pictures/Default/OcteractTalismanLevelCap4.png differ diff --git a/Pictures/Default/ShopHorseShoe.png b/Pictures/Default/ShopHorseShoe.png new file mode 100644 index 000000000..a90bf0a2c Binary files /dev/null and b/Pictures/Default/ShopHorseShoe.png differ diff --git a/Pictures/Default/SingularityTalismanBonusRunes1.png b/Pictures/Default/SingularityTalismanBonusRunes1.png new file mode 100644 index 000000000..303aa8cc6 Binary files /dev/null and b/Pictures/Default/SingularityTalismanBonusRunes1.png differ diff --git a/Pictures/Default/SingularityTalismanBonusRunes2.png b/Pictures/Default/SingularityTalismanBonusRunes2.png new file mode 100644 index 000000000..0aef8ad53 Binary files /dev/null and b/Pictures/Default/SingularityTalismanBonusRunes2.png differ diff --git a/Pictures/Default/SingularityTalismanBonusRunes3.png b/Pictures/Default/SingularityTalismanBonusRunes3.png new file mode 100644 index 000000000..55a46ce16 Binary files /dev/null and b/Pictures/Default/SingularityTalismanBonusRunes3.png differ diff --git a/Pictures/Default/SingularityTalismanBonusRunes4.png b/Pictures/Default/SingularityTalismanBonusRunes4.png new file mode 100644 index 000000000..b961dcb4f Binary files /dev/null and b/Pictures/Default/SingularityTalismanBonusRunes4.png differ diff --git a/Pictures/Default/ach98.png b/Pictures/Default/ach98.png index e89803df7..922ad2070 100644 Binary files a/Pictures/Default/ach98.png and b/Pictures/Default/ach98.png differ diff --git a/Pictures/Default/perkdemeterHarvest.png b/Pictures/Default/perkdemeterHarvest.png new file mode 100644 index 000000000..a43d5f269 Binary files /dev/null and b/Pictures/Default/perkdemeterHarvest.png differ diff --git a/Pictures/Default/perkinfiniteRecycling.png b/Pictures/Default/perkinfiniteRecycling.png new file mode 100644 index 000000000..09e2c8af8 Binary files /dev/null and b/Pictures/Default/perkinfiniteRecycling.png differ diff --git a/Pictures/Default/perkrecycledContent.png b/Pictures/Default/perkrecycledContent.png new file mode 100644 index 000000000..167093e19 Binary files /dev/null and b/Pictures/Default/perkrecycledContent.png differ diff --git a/Pictures/Default/perkrecyclistsDesktop.png b/Pictures/Default/perkrecyclistsDesktop.png new file mode 100644 index 000000000..d095e356e Binary files /dev/null and b/Pictures/Default/perkrecyclistsDesktop.png differ diff --git a/Pictures/Default/perktaxReduction.png b/Pictures/Default/perktaxReduction.png new file mode 100644 index 000000000..897203344 Binary files /dev/null and b/Pictures/Default/perktaxReduction.png differ diff --git a/Pictures/Legacy/BlueberryLuck4.png b/Pictures/Legacy/BlueberryLuck4.png new file mode 100644 index 000000000..a0f89de80 Binary files /dev/null and b/Pictures/Legacy/BlueberryLuck4.png differ diff --git a/Pictures/Legacy/BlueberryRuneOOMBonus.png b/Pictures/Legacy/BlueberryRuneOOMBonus.png new file mode 100644 index 000000000..ad90d0c1f Binary files /dev/null and b/Pictures/Legacy/BlueberryRuneOOMBonus.png differ diff --git a/Pictures/Legacy/BlueberryTalismanBonusRuneLevel.png b/Pictures/Legacy/BlueberryTalismanBonusRuneLevel.png new file mode 100644 index 000000000..dc85ebcf8 Binary files /dev/null and b/Pictures/Legacy/BlueberryTalismanBonusRuneLevel.png differ diff --git a/Pictures/Legacy/OcteractTalismanLevelCap1.png b/Pictures/Legacy/OcteractTalismanLevelCap1.png new file mode 100644 index 000000000..26a56f6ea Binary files /dev/null and b/Pictures/Legacy/OcteractTalismanLevelCap1.png differ diff --git a/Pictures/Legacy/OcteractTalismanLevelCap2.png b/Pictures/Legacy/OcteractTalismanLevelCap2.png new file mode 100644 index 000000000..b9b906ac3 Binary files /dev/null and b/Pictures/Legacy/OcteractTalismanLevelCap2.png differ diff --git a/Pictures/Legacy/OcteractTalismanLevelCap3.png b/Pictures/Legacy/OcteractTalismanLevelCap3.png new file mode 100644 index 000000000..9d218d006 Binary files /dev/null and b/Pictures/Legacy/OcteractTalismanLevelCap3.png differ diff --git a/Pictures/Legacy/OcteractTalismanLevelCap4.png b/Pictures/Legacy/OcteractTalismanLevelCap4.png new file mode 100644 index 000000000..0e55d49c2 Binary files /dev/null and b/Pictures/Legacy/OcteractTalismanLevelCap4.png differ diff --git a/Pictures/Legacy/ShopHorseShoe.png b/Pictures/Legacy/ShopHorseShoe.png new file mode 100644 index 000000000..a90bf0a2c Binary files /dev/null and b/Pictures/Legacy/ShopHorseShoe.png differ diff --git a/Pictures/Legacy/SingularityTalismanBonusRunes1.png b/Pictures/Legacy/SingularityTalismanBonusRunes1.png new file mode 100644 index 000000000..303aa8cc6 Binary files /dev/null and b/Pictures/Legacy/SingularityTalismanBonusRunes1.png differ diff --git a/Pictures/Legacy/SingularityTalismanBonusRunes2.png b/Pictures/Legacy/SingularityTalismanBonusRunes2.png new file mode 100644 index 000000000..0aef8ad53 Binary files /dev/null and b/Pictures/Legacy/SingularityTalismanBonusRunes2.png differ diff --git a/Pictures/Legacy/SingularityTalismanBonusRunes3.png b/Pictures/Legacy/SingularityTalismanBonusRunes3.png new file mode 100644 index 000000000..55a46ce16 Binary files /dev/null and b/Pictures/Legacy/SingularityTalismanBonusRunes3.png differ diff --git a/Pictures/Legacy/SingularityTalismanBonusRunes4.png b/Pictures/Legacy/SingularityTalismanBonusRunes4.png new file mode 100644 index 000000000..b961dcb4f Binary files /dev/null and b/Pictures/Legacy/SingularityTalismanBonusRunes4.png differ diff --git a/Pictures/Legacy/perkdemeterHarvest.png b/Pictures/Legacy/perkdemeterHarvest.png new file mode 100644 index 000000000..a43d5f269 Binary files /dev/null and b/Pictures/Legacy/perkdemeterHarvest.png differ diff --git a/Pictures/Legacy/perkinfiniteRecycling.png b/Pictures/Legacy/perkinfiniteRecycling.png new file mode 100644 index 000000000..09e2c8af8 Binary files /dev/null and b/Pictures/Legacy/perkinfiniteRecycling.png differ diff --git a/Pictures/Legacy/perkrecycledContent.png b/Pictures/Legacy/perkrecycledContent.png new file mode 100644 index 000000000..167093e19 Binary files /dev/null and b/Pictures/Legacy/perkrecycledContent.png differ diff --git a/Pictures/Legacy/perkrecyclistsDesktop.png b/Pictures/Legacy/perkrecyclistsDesktop.png new file mode 100644 index 000000000..d095e356e Binary files /dev/null and b/Pictures/Legacy/perkrecyclistsDesktop.png differ diff --git a/Pictures/Legacy/perktaxReduction.png b/Pictures/Legacy/perktaxReduction.png new file mode 100644 index 000000000..897203344 Binary files /dev/null and b/Pictures/Legacy/perktaxReduction.png differ diff --git a/Pictures/Monotonous/BlueberryLuck4.png b/Pictures/Monotonous/BlueberryLuck4.png new file mode 100644 index 000000000..a0f89de80 Binary files /dev/null and b/Pictures/Monotonous/BlueberryLuck4.png differ diff --git a/Pictures/Monotonous/BlueberryRuneOOMBonus.png b/Pictures/Monotonous/BlueberryRuneOOMBonus.png new file mode 100644 index 000000000..ad90d0c1f Binary files /dev/null and b/Pictures/Monotonous/BlueberryRuneOOMBonus.png differ diff --git a/Pictures/Monotonous/BlueberryTalismanBonusRuneLevel.png b/Pictures/Monotonous/BlueberryTalismanBonusRuneLevel.png new file mode 100644 index 000000000..dc85ebcf8 Binary files /dev/null and b/Pictures/Monotonous/BlueberryTalismanBonusRuneLevel.png differ diff --git a/Pictures/Monotonous/OcteractTalismanLevelCap1.png b/Pictures/Monotonous/OcteractTalismanLevelCap1.png new file mode 100644 index 000000000..26a56f6ea Binary files /dev/null and b/Pictures/Monotonous/OcteractTalismanLevelCap1.png differ diff --git a/Pictures/Monotonous/OcteractTalismanLevelCap2.png b/Pictures/Monotonous/OcteractTalismanLevelCap2.png new file mode 100644 index 000000000..b9b906ac3 Binary files /dev/null and b/Pictures/Monotonous/OcteractTalismanLevelCap2.png differ diff --git a/Pictures/Monotonous/OcteractTalismanLevelCap3.png b/Pictures/Monotonous/OcteractTalismanLevelCap3.png new file mode 100644 index 000000000..9d218d006 Binary files /dev/null and b/Pictures/Monotonous/OcteractTalismanLevelCap3.png differ diff --git a/Pictures/Monotonous/OcteractTalismanLevelCap4.png b/Pictures/Monotonous/OcteractTalismanLevelCap4.png new file mode 100644 index 000000000..0e55d49c2 Binary files /dev/null and b/Pictures/Monotonous/OcteractTalismanLevelCap4.png differ diff --git a/Pictures/Monotonous/ShopHorseShoe.png b/Pictures/Monotonous/ShopHorseShoe.png new file mode 100644 index 000000000..a90bf0a2c Binary files /dev/null and b/Pictures/Monotonous/ShopHorseShoe.png differ diff --git a/Pictures/Monotonous/SingularityTalismanBonusRunes1.png b/Pictures/Monotonous/SingularityTalismanBonusRunes1.png new file mode 100644 index 000000000..303aa8cc6 Binary files /dev/null and b/Pictures/Monotonous/SingularityTalismanBonusRunes1.png differ diff --git a/Pictures/Monotonous/SingularityTalismanBonusRunes2.png b/Pictures/Monotonous/SingularityTalismanBonusRunes2.png new file mode 100644 index 000000000..0aef8ad53 Binary files /dev/null and b/Pictures/Monotonous/SingularityTalismanBonusRunes2.png differ diff --git a/Pictures/Monotonous/SingularityTalismanBonusRunes3.png b/Pictures/Monotonous/SingularityTalismanBonusRunes3.png new file mode 100644 index 000000000..55a46ce16 Binary files /dev/null and b/Pictures/Monotonous/SingularityTalismanBonusRunes3.png differ diff --git a/Pictures/Monotonous/SingularityTalismanBonusRunes4.png b/Pictures/Monotonous/SingularityTalismanBonusRunes4.png new file mode 100644 index 000000000..b961dcb4f Binary files /dev/null and b/Pictures/Monotonous/SingularityTalismanBonusRunes4.png differ diff --git a/Pictures/Monotonous/perkdemeterHarvest.png b/Pictures/Monotonous/perkdemeterHarvest.png new file mode 100644 index 000000000..a43d5f269 Binary files /dev/null and b/Pictures/Monotonous/perkdemeterHarvest.png differ diff --git a/Pictures/Monotonous/perkinfiniteRecycling.png b/Pictures/Monotonous/perkinfiniteRecycling.png new file mode 100644 index 000000000..09e2c8af8 Binary files /dev/null and b/Pictures/Monotonous/perkinfiniteRecycling.png differ diff --git a/Pictures/Monotonous/perkrecycledContent.png b/Pictures/Monotonous/perkrecycledContent.png new file mode 100644 index 000000000..167093e19 Binary files /dev/null and b/Pictures/Monotonous/perkrecycledContent.png differ diff --git a/Pictures/Monotonous/perkrecyclistsDesktop.png b/Pictures/Monotonous/perkrecyclistsDesktop.png new file mode 100644 index 000000000..d095e356e Binary files /dev/null and b/Pictures/Monotonous/perkrecyclistsDesktop.png differ diff --git a/Pictures/Monotonous/perktaxReduction.png b/Pictures/Monotonous/perktaxReduction.png new file mode 100644 index 000000000..897203344 Binary files /dev/null and b/Pictures/Monotonous/perktaxReduction.png differ diff --git a/Pictures/RedAmbrosia/RedAmbrosiaSalvageYinYang.png b/Pictures/RedAmbrosia/RedAmbrosiaSalvageYinYang.png new file mode 100644 index 000000000..530572c49 Binary files /dev/null and b/Pictures/RedAmbrosia/RedAmbrosiaSalvageYinYang.png differ diff --git a/Pictures/Runes/Antiquities.png b/Pictures/Runes/Antiquities.png new file mode 100644 index 000000000..0420a1f3c Binary files /dev/null and b/Pictures/Runes/Antiquities.png differ diff --git a/Pictures/Runes/Duplication.png b/Pictures/Runes/Duplication.png new file mode 100644 index 000000000..7d7d9a77c Binary files /dev/null and b/Pictures/Runes/Duplication.png differ diff --git a/Pictures/Runes/FiniteDescent.png b/Pictures/Runes/FiniteDescent.png new file mode 100644 index 000000000..7c14fdfe2 Binary files /dev/null and b/Pictures/Runes/FiniteDescent.png differ diff --git a/Pictures/Runes/HorseShoe.png b/Pictures/Runes/HorseShoe.png new file mode 100644 index 000000000..adcd0afb5 Binary files /dev/null and b/Pictures/Runes/HorseShoe.png differ diff --git a/Pictures/Runes/InfiniteAscent.png b/Pictures/Runes/InfiniteAscent.png new file mode 100644 index 000000000..3ff13e524 Binary files /dev/null and b/Pictures/Runes/InfiniteAscent.png differ diff --git a/Pictures/Runes/Prism.png b/Pictures/Runes/Prism.png new file mode 100644 index 000000000..82b919cc6 Binary files /dev/null and b/Pictures/Runes/Prism.png differ diff --git a/Pictures/Runes/Speed.png b/Pictures/Runes/Speed.png new file mode 100644 index 000000000..844ad3f6e Binary files /dev/null and b/Pictures/Runes/Speed.png differ diff --git a/Pictures/Runes/SuperiorIntellect.png b/Pictures/Runes/SuperiorIntellect.png new file mode 100644 index 000000000..4215a6af7 Binary files /dev/null and b/Pictures/Runes/SuperiorIntellect.png differ diff --git a/Pictures/Runes/Thrift.png b/Pictures/Runes/Thrift.png new file mode 100644 index 000000000..fc7bc1fa7 Binary files /dev/null and b/Pictures/Runes/Thrift.png differ diff --git a/Pictures/Simplified/BlueberryLuck4.png b/Pictures/Simplified/BlueberryLuck4.png new file mode 100644 index 000000000..a0f89de80 Binary files /dev/null and b/Pictures/Simplified/BlueberryLuck4.png differ diff --git a/Pictures/Simplified/BlueberryRuneOOMBonus.png b/Pictures/Simplified/BlueberryRuneOOMBonus.png new file mode 100644 index 000000000..ad90d0c1f Binary files /dev/null and b/Pictures/Simplified/BlueberryRuneOOMBonus.png differ diff --git a/Pictures/Simplified/BlueberryTalismanBonusRuneLevel.png b/Pictures/Simplified/BlueberryTalismanBonusRuneLevel.png new file mode 100644 index 000000000..dc85ebcf8 Binary files /dev/null and b/Pictures/Simplified/BlueberryTalismanBonusRuneLevel.png differ diff --git a/Pictures/Simplified/OcteractTalismanLevelCap1.png b/Pictures/Simplified/OcteractTalismanLevelCap1.png new file mode 100644 index 000000000..26a56f6ea Binary files /dev/null and b/Pictures/Simplified/OcteractTalismanLevelCap1.png differ diff --git a/Pictures/Simplified/OcteractTalismanLevelCap2.png b/Pictures/Simplified/OcteractTalismanLevelCap2.png new file mode 100644 index 000000000..b9b906ac3 Binary files /dev/null and b/Pictures/Simplified/OcteractTalismanLevelCap2.png differ diff --git a/Pictures/Simplified/OcteractTalismanLevelCap3.png b/Pictures/Simplified/OcteractTalismanLevelCap3.png new file mode 100644 index 000000000..9d218d006 Binary files /dev/null and b/Pictures/Simplified/OcteractTalismanLevelCap3.png differ diff --git a/Pictures/Simplified/OcteractTalismanLevelCap4.png b/Pictures/Simplified/OcteractTalismanLevelCap4.png new file mode 100644 index 000000000..0e55d49c2 Binary files /dev/null and b/Pictures/Simplified/OcteractTalismanLevelCap4.png differ diff --git a/Pictures/Simplified/ShopHorseShoe.png b/Pictures/Simplified/ShopHorseShoe.png new file mode 100644 index 000000000..a90bf0a2c Binary files /dev/null and b/Pictures/Simplified/ShopHorseShoe.png differ diff --git a/Pictures/Simplified/SingularityTalismanBonusRunes1.png b/Pictures/Simplified/SingularityTalismanBonusRunes1.png new file mode 100644 index 000000000..303aa8cc6 Binary files /dev/null and b/Pictures/Simplified/SingularityTalismanBonusRunes1.png differ diff --git a/Pictures/Simplified/SingularityTalismanBonusRunes2.png b/Pictures/Simplified/SingularityTalismanBonusRunes2.png new file mode 100644 index 000000000..0aef8ad53 Binary files /dev/null and b/Pictures/Simplified/SingularityTalismanBonusRunes2.png differ diff --git a/Pictures/Simplified/SingularityTalismanBonusRunes3.png b/Pictures/Simplified/SingularityTalismanBonusRunes3.png new file mode 100644 index 000000000..55a46ce16 Binary files /dev/null and b/Pictures/Simplified/SingularityTalismanBonusRunes3.png differ diff --git a/Pictures/Simplified/SingularityTalismanBonusRunes4.png b/Pictures/Simplified/SingularityTalismanBonusRunes4.png new file mode 100644 index 000000000..b961dcb4f Binary files /dev/null and b/Pictures/Simplified/SingularityTalismanBonusRunes4.png differ diff --git a/Pictures/Simplified/perkdemeterHarvest.png b/Pictures/Simplified/perkdemeterHarvest.png new file mode 100644 index 000000000..a43d5f269 Binary files /dev/null and b/Pictures/Simplified/perkdemeterHarvest.png differ diff --git a/Pictures/Simplified/perkinfiniteRecycling.png b/Pictures/Simplified/perkinfiniteRecycling.png new file mode 100644 index 000000000..09e2c8af8 Binary files /dev/null and b/Pictures/Simplified/perkinfiniteRecycling.png differ diff --git a/Pictures/Simplified/perkrecycledContent.png b/Pictures/Simplified/perkrecycledContent.png new file mode 100644 index 000000000..167093e19 Binary files /dev/null and b/Pictures/Simplified/perkrecycledContent.png differ diff --git a/Pictures/Simplified/perkrecyclistsDesktop.png b/Pictures/Simplified/perkrecyclistsDesktop.png new file mode 100644 index 000000000..d095e356e Binary files /dev/null and b/Pictures/Simplified/perkrecyclistsDesktop.png differ diff --git a/Pictures/Simplified/perktaxReduction.png b/Pictures/Simplified/perktaxReduction.png new file mode 100644 index 000000000..897203344 Binary files /dev/null and b/Pictures/Simplified/perktaxReduction.png differ diff --git a/Pictures/Talismans/Achievement.png b/Pictures/Talismans/Achievement.png new file mode 100644 index 000000000..753f8892d Binary files /dev/null and b/Pictures/Talismans/Achievement.png differ diff --git a/Pictures/Talismans/Chronos.png b/Pictures/Talismans/Chronos.png new file mode 100644 index 000000000..602e5b7fd Binary files /dev/null and b/Pictures/Talismans/Chronos.png differ diff --git a/Pictures/Talismans/CookieGrandma.png b/Pictures/Talismans/CookieGrandma.png new file mode 100644 index 000000000..a3d0ded95 Binary files /dev/null and b/Pictures/Talismans/CookieGrandma.png differ diff --git a/Pictures/Talismans/Exemption.png b/Pictures/Talismans/Exemption.png new file mode 100644 index 000000000..0d21458eb Binary files /dev/null and b/Pictures/Talismans/Exemption.png differ diff --git a/Pictures/Talismans/HorseShoe.png b/Pictures/Talismans/HorseShoe.png new file mode 100644 index 000000000..9b73b8c56 Binary files /dev/null and b/Pictures/Talismans/HorseShoe.png differ diff --git a/Pictures/Talismans/HorseShow.png b/Pictures/Talismans/HorseShow.png new file mode 100644 index 000000000..30388b299 Binary files /dev/null and b/Pictures/Talismans/HorseShow.png differ diff --git a/Pictures/Talismans/Metaphysics.png b/Pictures/Talismans/Metaphysics.png new file mode 100644 index 000000000..ec7fbe418 Binary files /dev/null and b/Pictures/Talismans/Metaphysics.png differ diff --git a/Pictures/Talismans/Midas.png b/Pictures/Talismans/Midas.png new file mode 100644 index 000000000..c73a32dab Binary files /dev/null and b/Pictures/Talismans/Midas.png differ diff --git a/Pictures/Talismans/Mortuus.png b/Pictures/Talismans/Mortuus.png new file mode 100644 index 000000000..2dc7f47a3 Binary files /dev/null and b/Pictures/Talismans/Mortuus.png differ diff --git a/Pictures/Talismans/Plastic.png b/Pictures/Talismans/Plastic.png new file mode 100644 index 000000000..aa2062b36 Binary files /dev/null and b/Pictures/Talismans/Plastic.png differ diff --git a/Pictures/Talismans/Polymath.png b/Pictures/Talismans/Polymath.png new file mode 100644 index 000000000..d0266b9fc Binary files /dev/null and b/Pictures/Talismans/Polymath.png differ diff --git a/Pictures/Talismans/WowSquare.png b/Pictures/Talismans/WowSquare.png new file mode 100644 index 000000000..c9643cc4a Binary files /dev/null and b/Pictures/Talismans/WowSquare.png differ diff --git a/README.md b/README.md index 99aec2779..b02b018d7 100644 --- a/README.md +++ b/README.md @@ -3,27 +3,27 @@ ## Contributing Before running any of these commands below, make sure to have installed: -VSCode - https://code.visualstudio.com/Download +NodeJS >= 24.0.0 - https://nodejs.org/en/ +git - https://git-scm.com/downloads + +## Recommended Software -NodeJS - https://nodejs.org/en/ (current, not LTS). +VSCode - https://code.visualstudio.com/Download --- 1. Fork this repository at https://github.com/Pseudo-Corp/SynergismOfficial/fork -2. Clone the repository you forked with `git clone https://github.com//SynergismOfficial` (make sure to change `` with your own Github username) -3. Open the repository you just downloaded in VSCode +2. Clone the repository you forked with `git clone https://github.com//SynergismOfficial` (make sure to change `` with your own GitHub username). +3. cd SynergismOfficial 4. Install the project dependencies, running `npm install` (or `make install` - If you intend to use `make` from here and on, make sure it is installed first) -5. Install the liveserver extension for VSCode at https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer (or by searching for `Live Server` inside the Extensions Marketplace in VSCode. You can open the Extensions Marketplace by pressing `Ctrl+Shift+X`) -6. Switch to a new branch with `git checkout -b "my-branch-name"` -7. Run `npm run watch:esbuild` (or `make watch`) -8. Open the liveserver (bottom right corner icon similar to an Antenna in VSCode) -9. Make your desired changes and test them. When you are done making changes, press `Ctrl+C` to exit from the previous command. -10. If any new files were created, run `git add /path/to/file` (or `git add -A` to add all) -11. Before commiting any changes you made, check and lint your code to see if everything you've done is good to go by doing steps 12 through 14 -12. Typecheck all your TypeScript files by running `npm run check:tsc` (or `make check`) -13. Lint the code by running `npm run lint` (or `make lintcode`) -14. Lint the CSS by running `npm run csslint` (or `make lintcss`) -15. If everything is good to go, commit your changes with `git commit -am "title of my commit"` -16. Push your changes to your personal Github Repository with `git push -u origin my-branch-name` -17. Open a Pull Request (PR) on the Official Synergism Repository at https://github.com/Pseudo-Corp/SynergismOfficial/pulls (make sure you click on `compare across forks` to select your fork and branch) +5. Switch to a new branch with `git checkout -b "my-branch-name"` +6. Run `node --run dev` (or `make dev`) +7. Make your desired changes and test them. +8. Typecheck all your TypeScript files by running `node --run check:tsc` (or `make check`). +9. Lint the code by running `node --run lint` (or `make lintcode`). +10. Lint the CSS by running `node --run csslint` (or `make lintcss`). +11. Run `git add /path/to/file` for every file updated or created (or `git add -A` for every file). +12. If everything is good to go, commit your changes with `git commit -m "title of my commit"` +13. Push your changes to your personal Github Repository with `git push -u origin my-branch-name` +14. Open a Pull Request (PR) on the Official Synergism Repository at https://github.com/Pseudo-Corp/SynergismOfficial/pulls (make sure you click on `compare across forks` to select your fork and branch) --- To get a list of available commands in the Makefile, run `make help` or just `make` diff --git a/Synergism.css b/Synergism.css index 1788542aa..29d67347b 100644 --- a/Synergism.css +++ b/Synergism.css @@ -289,6 +289,28 @@ body button:active:not(:disabled):not(.disable-hover-color) { background-color: var(--alert-color); } +#modal { + position: fixed; + display: none; + z-index: 6969; + width: 33%; + overflow: hidden; + border: 2px solid gold; + border-radius: 5px; + box-sizing: border-box; + font-size: min(1em, 24px); + color: white; + background-color: var(--alert-color); +} + +#modalContent { + width: 100%; + padding: 15px; + box-sizing: border-box; + justify-content: center; + align-items: center; +} + .slide-in { animation: slide-in 1000ms; animation-fill-mode: forwards; @@ -778,9 +800,9 @@ p#buildinghotkeys2 { #upgradesFlex > section > table { font-size: 0; - min-height: 182px; text-align: center; border-collapse: collapse; + margin: 3px; } #upgradesFlex > section > table td { @@ -1019,6 +1041,19 @@ p#promocodeinfo { font-size: min(1em, 20px); } +#coinInformation { + text-align: center; + margin: 10px; + padding: 10px; + font-size: 1.2em; +} + +#coinVanity { + text-align: center; + margin: 0; + padding: 0; +} + .buttonRow > div { display: flex; justify-content: flex-start; @@ -1110,6 +1145,46 @@ p#promocodeinfo { justify-content: center; } +#tieredAchievementsTable { + display: grid; + grid-template-columns: repeat(8, 48px); + grid-template-rows: repeat(5, 48px); + gap: 2px 2px; +} + +#tieredAchievementsTable > div { + width: 48px; + height: 48px; +} + +#ungroupedAchievementsTable { + display: grid; + grid-template-columns: repeat(12, 32px); + grid-template-rows: repeat(5, 32px); + gap: 2px 2px; +} + +#ungroupedAchievementsTable > div { + width: 32px; + height: 32px; +} + +#progressiveAchievementsTable { + display: grid; + grid-template-columns: repeat(8, 48px); + grid-template-rows: repeat(5, 48px); + gap: 2px 2px; +} + +#progressiveAchievementsTable > div { + width: 48px; + height: 48px; +} + +#achievementMultiline { + white-space: pre-line; +} + #achievementstable { font-size: 0; border-collapse: collapse; @@ -1119,6 +1194,23 @@ p#promocodeinfo { image-rendering: unset; } +#achievementHeader { + display: flex; + justify-self: center; + align-items: center; + flex-direction: column; + margin: 10px; +} + +#achievementHeader > p { + margin: 4px 0; + font-size: 1.2em; +} + +#achievementTNLText { + margin: 0; +} + .NotificationToggle { display: flex; justify-content: center; @@ -1127,7 +1219,7 @@ p#promocodeinfo { } .NotificationToggle > p { - margin: 15px 0; + margin: 2px 0; } .achNotification { @@ -1152,54 +1244,131 @@ p#promocodeinfo { margin-top: 10px; } -#buyamountoffering { +.tieredAchievementType > img, +.progressiveAchievementType > img, +.synergismLevelRewardType > img { + width: 48px; + height: 48px; +} + +.ungroupedAchievementType > img, +.synergismLevelMilestoneType > img { + width: 32px; + height: 32px; +} + +.tieredAchievementType img.dimmed, +.ungroupedAchievementType img.dimmed, +.progressiveAchievementType img.dimmed { + opacity: 0.3; +} + +.achievement-ap-overlay { position: absolute; - left: calc(50% + 350px); - top: 0; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 1em; + text-shadow: 2px 2px 4px #000; + z-index: 999; + color: white; + font-weight: bold; display: flex; flex-direction: column; align-items: center; - z-index: 1; + line-height: 0.8; } -.talismansCenter { - display: flex; - width: 70%; - flex-direction: column; - padding: 0 10px; - max-width: 900px; +.achievement-fraction-numerator { + border-bottom: 1px solid white; + padding-bottom: 1px; + min-width: 1em; + text-align: center; } -.talismansModify { +.achievement-fraction-denominator { + padding-top: 1px; + min-width: 1em; + text-align: center; +} + +.tieredAchievementType, +.ungroupedAchievementType, +.progressiveAchievementType { + position: relative; + display: inline-block; +} + +.relative-container { + position: relative; + display: inline-block; +} + +.achievement-ap-overlay.gold-text { + color: gold; +} + +#showAchievementProgress { + width: 48px; + cursor: pointer; + text-align: center; + border: 2px solid orange; +} + +#achievementContainer1, +#achievementContainer2 { display: flex; flex-direction: column; align-items: center; position: relative; } -.talismansModify p { - margin: 0; +#synergismLevelRewardsTable { + display: grid; + grid-template-columns: repeat(12, 48px); + grid-template-rows: repeat(1, 48px); + gap: 2px 2px; + align-items: center; + justify-content: center; } -#talismanSummary, -#talismanLevelUpSummary, -#talismanRespecSummary { - margin-bottom: 10px; +#synergismLevelRewardsTable > div { + width: 48px; + height: 48px; +} + +#synergismLevelMilestonesTable { + display: grid; + grid-template-columns: repeat(12, 32px); + grid-template-rows: repeat(4, 32px); + gap: 2px 2px; + align-items: center; + justify-content: center; +} + +#synergismLevelMilestonesTable > div { + width: 32px; + height: 32px; } -.talismanBuyAmountContainer { +#buyamountoffering { + left: calc(50% + 350px); + top: 0; display: flex; flex-direction: column; - align-items: flex-start; - width: 30%; - margin-top: 23px; + align-items: center; + z-index: 1; } -#buyamounttalisman { +.talismansModify { display: flex; flex-direction: column; align-items: center; - gap: 5px; + position: relative; +} + +.talismansModify p { + margin: 0; } #buyamountcoin, @@ -1281,6 +1450,14 @@ p#prestigehotkeys { padding: 0; } +#runesHeader { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + margin-bottom: 10px; +} + #runeContainer1, #runeContainer3, #runeContainer4 { @@ -1290,12 +1467,8 @@ p#prestigehotkeys { position: relative; } -#runeContainer2 { - margin-top: 20px; - justify-content: center; -} - -#runesToggle { +#runesToggle, +#achievementsToggle { display: flex; justify-content: center; flex-flow: wrap; @@ -1308,7 +1481,8 @@ p#prestigehotkeys { width: 20px; } -#runesToggle > button { +#runesToggle > button, +#achievementsToggle > button { min-width: 100px; text-align: center; } @@ -1326,24 +1500,33 @@ p#prestigehotkeys { margin: 0; } -#runeDetails { +#runeDetails, +#runeBlessings, +#runeSpirits { display: flex; - width: 100%; - max-width: calc(min(70em, 1600px)); - min-width: 1200px; - flex-direction: row; + position: relative; justify-content: center; - height: auto; - margin: 10px 0; + flex-wrap: wrap; + width: 66%; } .runeType { - min-width: 14%; display: flex; flex-direction: column; - justify-content: space-between; + width: 180px; align-items: center; - font-size: min(1em, 20px); + white-space: nowrap; + padding: 2px; + transition: box-shadow 0.3s ease-in-out, transform 0.3s ease-in-out; +} + +.runeType:hover { + transform: translateY(-2px); + box-shadow: + 0 0 15px var(--glow-color), + 0 0 30px var(--glow-color), + 0 0 45px var(--glow-color); + background-color: var(--box-color); } .runeType > button { @@ -1381,32 +1564,96 @@ img.runeTypeElement:hover { background-color: var(--purplehover-color); } -#toggleautofortify, -#toggleautoenhance { +.runeTypeLocked { + opacity: 0.5; +} + +.runeImageLocked { + filter: grayscale(50%); +} + +#runeContainer2 { + display: grid; + grid-template-columns: repeat(8, 1fr); + grid-template-rows: repeat(3, 1fr); + grid-column-gap: 0; + grid-row-gap: 0; + position: relative; +} + +.talismansCenter { + grid-area: 1 / 2 / 4 / 8; + display: flex; + flex-direction: column; + width: 100%; +} + +.talismanShardSection { + grid-area: 1 / 1 / 4 / 2; + display: flex; + flex-direction: column; + align-items: flex-end; + margin-left: 10px; + width: 100%; +} + +.talismanToggles { + grid-area: 1 / 8 / 4 / 9; + display: flex; + flex-direction: column; + align-items: flex-start; + width: 100%; +} + +.talismanSettingsContainer { + display: flex; + flex-direction: column; + width: 60%; + align-items: center; +} + +#rarityInfoTexts { + display: none; + width: 75%; + align-items: center; +} + +.rarityReqNum { + float: right; +} + +#toggleautofortify { min-width: 125px; border: 2px solid red; } #talismanFragmentCost { - width: 15em; - max-width: 250px; + width: 20em; text-align: center; - margin: 0 -30px 0 0; } -.talismanShardsContainer { +.talismanShardContainers { display: flex; flex-direction: column; - align-items: flex-end; - width: 30%; - margin-top: 23px; - z-index: 1; + width: 60%; + align-items: center; +} + +.talismanShardHeader { + display: flex; + flex-direction: column; + align-items: center; +} + +.talismanShardHeader > button { + width: 100px; + margin: 2px; } .talismanShardContainer { display: flex; align-items: center; - margin-bottom: 12px; + padding: 5px; } .talismanShardIcon { @@ -1417,7 +1664,7 @@ img.runeTypeElement:hover { width: 5em; min-width: 70px; margin-left: 4px; - font-size: 0.9em; + font-size: 1em; } .fragmentBtn { @@ -1436,19 +1683,6 @@ img.runeTypeElement:hover { min-width: 80px; } -#buyTalismanAll { - width: 5em; - min-width: 80px; - color: orange; -} - -#toggleautoBuyFragments { - width: 7em; - min-width: 94px; - margin-right: 10px; - color: orange; -} - .talisminBtnAvailable { background-color: var(--purplebtn-color); cursor: pointer; @@ -1461,52 +1695,50 @@ img.runeTypeElement:hover { .talismansContainer { display: flex; position: relative; - justify-content: space-around; - padding: 0 10px; - max-width: 900px; + justify-content: center; + flex-wrap: wrap; + width: 100%; } .talismanContainer { flex-direction: column; - min-width: 11%; - min-width: 102px; + width: 80px; align-items: center; white-space: nowrap; - font-size: min(1em, 20px); + padding: 2px; } .talismanIcon { + height: 64px; width: 64px; - border: 4px solid white; cursor: pointer; } +.talismanIcon > img { + position: relative; + width: calc(100% - 6px); + height: calc(100% - 6px); + object-fit: none; + background-color: var(--bg-color); + border-radius: 3px; +} + .talismanLevel { margin-top: 1px; margin-bottom: 3px; } -#respecAllTalismans { - position: absolute; - width: 7em; - max-width: 140px; - color: plum; - border: 2px solid white; - left: 135px; - top: 70px; - transform: translate(-100%); -} - #talismanEffect, -#talismanlevelup, -#talismanrespec { - margin-top: 40px; +#talismanLevelUpCost { + margin-top: 10px; text-align: center; } #talismanlevelup { - align-self: flex-start; - margin-left: 150px; + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; } #talismanlevelup table { @@ -1518,24 +1750,6 @@ img.runeTypeElement:hover { width: 5em; } -#talismanrespec > div { - display: flex; - flex-direction: column; - transform: translate(25%); - margin-top: 10px; -} - -#talismanrespec > div > div { - display: flex; - flex-direction: row; - gap: 20px; -} - -.talismanRespecBtn { - width: 200px; - text-align: center; -} - #transcension { position: relative; padding: 0; @@ -1827,6 +2041,14 @@ p#reincarnatehotkeys { padding: 0; } +.researchUnlocked { + display: block; +} + +.researchLocked { + display: none; +} + .researchContainer { display: flex; justify-content: center; @@ -2316,9 +2538,33 @@ p[id$="BlessingsTotal"] { } #cubeBlessingsTotal { color: orange; } + #tesseractBlessingsTotal { color: var(--orchid-text-color); } + #hypercubeBlessingsTotal { color: rgb(255 0 76); } -#platonicBlessingsTotal { color: orange; } + +#platonicBlessingsTotal { + color: orange; + margin: 0; +} + +#platonicFooter, +#cubeFooter { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-top: 20px; +} + +#platonicFooter p, +#cubeFooter p { + margin: 0; +} + +#platonicAsterisk { + font-size: 0.75em; +} #cubesWrapper { display: flex; @@ -2829,14 +3075,6 @@ p#ascendHotKeys { min-height: 50px; } -.runeBlessingRow { - display: flex; - width: 1200px; - align-items: center; - gap: 4px 15px; - margin-bottom: 10px; -} - .runeIcons { height: 64px; width: 64px; @@ -2938,7 +3176,9 @@ p#ascendHotKeys { } /* Credit: gwabo_ */ -.campaignIcon:not(.green-background) { +.campaignIcon:not(.green-background), +.tieredAchievementType img:not(.green-background), +.progressiveAchievementType img:not(.green-background) { --pct: 0; background-image: @@ -3745,6 +3985,11 @@ header #obtainiumDisplay { color: pink; } gap: 4px; } +.blueberryUpgradeTier > div { + width: 32px; + height: 32px; +} + .singularityUpgrade, .octeractUpgrade, .singularityChallenge { @@ -3817,6 +4062,24 @@ header #obtainiumDisplay { color: pink; } to { transform: rotate(360deg); } } +.rainbowText { + color: transparent; + background-image: linear-gradient(90deg, #ca001f, #ffb17f, #fff200, #22b14c, #767dda, #a349a4, #ca001f, #ffb17f); + background-clip: text; + background-size: 700%; + animation: rainbow-animation 5s linear infinite; +} + +@keyframes rainbow-animation { + 0% { + background-position: 0% 50%; + } + + 100% { + background-position: 100% 50%; + } +} + .singularityUpgrade > img, .octeractUpgrade > img, .singularityChallenge img { @@ -3895,7 +4158,7 @@ img#singularityPerksIcon { width: 100%; } -#testingMultiline, +#goldenQuarkMultiline, #singularityOcteractsMultiline, #singularityAmbrosiaMultiline { white-space: pre-line; @@ -4097,14 +4360,15 @@ img#singularityPerksIcon { border: 2px solid gold; } -.notInLoadout { - visibility: hidden; -} - /* Style for dimmed image */ .blueberryUpgrade img.dimmed, .redAmbrosiaUpgrade img.dimmed { - opacity: 0.5; /* Adjust the opacity to control the dimming effect */ + opacity: 0.3; +} + +.blueberryUpgrade img.superDimmed, +.redAmbrosiaUpgrade img.superDimmed { + opacity: 0; /* if not bought, fully dim */ } /* Style for level display */ @@ -4114,9 +4378,14 @@ img#singularityPerksIcon { top: 50%; left: 50%; transform: translate(-50%, -50%); - font-size: 1em; /* Adjust font size as needed */ - text-shadow: 2px 2px 4px #000; /* Add a text shadow for better readability */ - z-index: 999; /* Ensure the text is on top of the image */ + font-size: 1.2em; + text-shadow: 2px 2px 4px #000; + z-index: 999; + display: flex; + flex-direction: column; + align-items: center; + line-height: 0.8; + background-color: black; } /* Style for relative positioning */ @@ -4181,20 +4450,20 @@ img#singularityPerksIcon { /* // Stolen from https://codepen.io/mike-schultz/pen/NgQvGO */ -#accountSubTab > div.scrollbarX > div[id="button-holder"] { +#accountSubTab div#left.scrollbarX > div[id="button-holder"] { display: flex; flex-direction: row; justify-content: center; } -#accountSubTab > div.scrollbarX > div[id="button-holder"] > button { +#accountSubTab div#left.scrollbarX > div[id="button-holder"] > button { border: 2px solid red; min-width: 150px; min-height: 30px; margin: 2px; } -#accountSubTab > div.scrollbarX > div:is(#register, #login, #forgotpassword) { +#accountSubTab div#left.scrollbarX > div:is(#register, #login, #forgotpassword) { display: flex; flex-direction: column; border: 1px solid gold; @@ -4202,25 +4471,206 @@ img#singularityPerksIcon { margin: 0 auto; } -#accountSubTab > div.scrollbarX > div:is(#login, #forgotpassword) { +#accountSubTab div#left.scrollbarX > div:is(#login, #forgotpassword) { display: none; } /** Remove vertical spacing */ -#accountSubTab > div.scrollbarX > div > form { +#accountSubTab div#left.scrollbarX > div > form { font-size: 0; } -#accountSubTab > div.scrollbarX > div > form > * { +#accountSubTab div#left.scrollbarX > div > form > * { font-size: medium; margin: 2px 10px; } -#accountSubTab > div.scrollbarX > div > form > input[type="submit"] { +#accountSubTab div#left.scrollbarX > div > form > input[type="submit"] { border: 1px solid gold; padding: 3px 5px; } +.splitContainer { + display: flex; + flex-direction: row; +} + +.splitContainer > .scrollbarX { + width: 50%; +} + +#accountSubTab div#right.scrollbarX > #buttons { + display: flex; + justify-content: center; +} + +#accountSubTab div#right.scrollbarX #upload, +#accountSubTab div#right.scrollbarX #transfer { + min-width: 100px; + min-height: 30px; + border: 2px solid purple; + margin: 2px; +} + +#accountSubTab div#right.scrollbarX #transfer { + border: 2px solid pink; +} + +.spinner { + border: 2px solid #f3f3f3; + border-top: 2px solid #3498db; + border-radius: 50%; + width: 16px; + height: 16px; + animation: spin 1s linear infinite; + display: inline-block; + transform: translateY(-1px); /* Fine-tune this value */ + vertical-align: middle; +} + +@keyframes spin { + 0% { transform: translateY(-1px) rotate(0deg); } + 100% { transform: translateY(-1px) rotate(360deg); } +} + +#accountSubTab div#right.scrollbarX > #table { + white-space: nowrap; +} + +#accountSubTab div#right.scrollbarX > #table > .grid-table { + display: grid; + grid-template-columns: 80px 0.5fr 0.5fr; + width: 99%; + justify-content: center; +} + +#accountSubTab div#right.scrollbarX > #table > .grid-table > * { + display: flex; + align-items: center; + padding: 16px 0; +} + +#accountSubTab div#right.scrollbarX > #table > .grid-table > .grid-header { + font-weight: 600; + font-size: 14px; + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 1px solid white; + padding-bottom: 5px; + padding-top: 0; +} + +#accountSubTab div#right.scrollbarX > #table > .grid-table .grid-cell { + text-align: left; + padding: 5px; +} + +#accountSubTab div#right.scrollbarX > #table > .grid-table .grid-cell.alt-row { + background: var(--history-lines); +} + +#accountSubTab div#right.scrollbarX > #table > .grid-table .name-cell { + font-weight: 500; +} + +#accountSubTab div#right.scrollbarX > #table > .grid-table .date-cell { + font-size: 14px; +} + +#accountSubTab div#right.scrollbarX > #table > .grid-table .grid-details-row { + background: var(--alert-color); + border-left: 3px solid #007bff; + margin: 0; + padding: 0; +} + +/* Details content container */ +.details-content { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: 20px; + gap: 20px; +} + +/* Details actions section */ +.details-actions { + display: flex; + gap: 10px; + flex-shrink: 0; +} + +.details-actions button { + padding: 8px 16px; + border: none; + border-radius: 4px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.details-actions .btn-download { + background: var(--success-color, #28a745); + color: white; +} + +.details-actions .btn-download:hover { + background: var(--success-hover, #218838); +} + +.details-actions .btn-load { + background: var(--primary-color, #007bff); + color: white; +} + +.details-actions .btn-load:hover { + background: var(--primary-hover, #0056b3); +} + +.details-actions .btn-delete { + background: var(--danger-color, #dc3545); + color: white; +} + +.details-actions .btn-delete:hover { + background: var(--danger-hover, #c82333); +} + +/* Add hover effect to main rows to indicate they're clickable */ +#accountSubTab div#right.scrollbarX > #table > .grid-table .grid-row:hover .grid-cell { + background: var(--hover-color, rgb(0 123 255 / 10%)); +} + +/* Smooth animation for expanding rows */ +.grid-details-row { + animation: slide-down 0.2s ease-out; +} + +@keyframes slide-down { + from { + opacity: 0; + transform: translateY(-10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 768px) { + .details-content { + flex-direction: column; + gap: 15px; + } + + .details-actions { + justify-content: flex-start; + flex-wrap: wrap; + } +} + #pixelProgressBar { width: 25%; height: 30px; @@ -5070,6 +5520,15 @@ html[data-golden-quark3-upg="false"] .goldenQuark3Upg { display: none; } +.modalBtnBuy { + width: 60px; + height: 30px; + background-color: var(--button-color); + border: 2px solid gold; + margin: 5px auto; + text-align: center; +} + #patchnotes { cursor: pointer; } diff --git a/biome.json b/biome.json index e8257048c..aa75c358b 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json", + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", "organizeImports": { "enabled": true }, @@ -22,6 +22,9 @@ "complexity": { "noUselessTypeConstraint": "off", "noForEach": "off" + }, + "correctness": { + "noUndeclaredVariables": "error" } }, "ignore": ["./dist/*", "**/node_modules/*", "mockServiceWorker.js"] diff --git a/claude.md b/claude.md new file mode 100644 index 000000000..e90ffee16 --- /dev/null +++ b/claude.md @@ -0,0 +1,75 @@ +# Synergism Project Context for Claude + +## Project Overview +- **Name**: Synergism (idle game) +- **Tech Stack**: TypeScript, HTML, CSS +- **URL**: https://synergism.cc +- **Repository**: Primarily for frontend features of Synergism +- **Backend**: Connected via `src/login.ts` with mocking in `src/mock/` + +## Agent Role & Workflow +### Primary Tasks +- Implement frontend features +- Fix bugs and issues +- Architect new feature systems + +### Required Actions +1. **Always ask permission** before adding variables to `player` object (affects savefile size) +2. **Check back with user** after writing significant code +3. **Ask questions** when task requirements are unclear + +### Quality Assurance Commands +Run these after making changes: +```bash +node --run format # Format code +npx -p typescript tsc # TypeScript check +``` + +## File Structure Rules +``` +src/ # Core game logic +├── mock/ # Mock API responses +├── Purchases/ # Purchase-related logic +├── saves/ # Save system logic +├── types/ # TypeScript definitions +└── [FeatureName].ts # Individual game features + +index.html # Single HTML file - add new divs in body +Synergism.css # Single CSS file +translations/en.json # Required for all new text strings +``` + +## Development Patterns + +### Adding New Features +1. **File Location**: + - Use existing subfolders if feature fits + - Otherwise place directly in `src/` +2. **HTML**: Add new `
` elements one level into `` +3. **CSS**: Add styles to `Synergism.css` + +### String Internationalization +- **Required**: Add all user-facing text to `translations/en.json` +- **Format**: `i18n` library usage +- **Variables**: `{{variableName}}` for dynamic content +- **Styling**: `<>` for colored text + +### Save System Variables +**CRITICAL**: Before adding to `player` object: +1. Get explicit permission from user +2. Add to `src/types/Synergism.d.ts` +3. Add to `src/saves/PlayerSchema.ts` +4. Variable location: `player` in `src/Synergism.ts` + +## Code Conventions +- Follow existing TypeScript patterns in codebase +- Use established import/export structures +- Match existing naming conventions +- Maintain consistency with current architecture + + + + + + + diff --git a/index.html b/index.html index 49f1552a6..b7c569bb4 100644 --- a/index.html +++ b/index.html @@ -29,6 +29,10 @@

+
@@ -317,6 +321,10 @@ A main element with a class "buttonRow", then a list of
s with the element children inside. --> + +

+

+
Worker @@ -712,7 +720,7 @@

- +
@@ -730,15 +738,15 @@

+ - - + @@ -999,332 +1007,71 @@

Generation Upgrades
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+

+ Notification + + +
+

-

+
+

+
+
+
-
-

-

-

+
+ +
-
-

-

-

-

+
+
+
+ +
+
+ +
+
+ +
+
+
+

+
+
+
@@ -2440,61 +2033,61 @@

- + - + - + - + - + - + - + - + - + - +

@@ -2529,53 +2122,56 @@

- + - + - + - + - + - + - + - +

-

+
+

+

+
@@ -4206,135 +3878,147 @@

Artists

-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -4378,179 +4062,191 @@

Artists

-
- -
-
- -
-
- -
-
- -
-
- -
+ + + + +
-
- -
-
- -
-
- -
-
- -
-
- -
+ + + + +
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
+ + + + + + +
-
- -
-
- -
-
- -
-
- -
+ + + +
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
+ + + + + +
-
- -
-
- -
-
- -
-
- -
+ + + + +
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
+ + + + + + +
-
- -
-
- -
-
- -
-
- -
+ + + +
-
- -
-
- -
-
- -
-
- -
-
- -
+ + + + + +
-
- -
-
- -
-
- -
-
- -
-
- -
+ + + + +
diff --git a/package.json b/package.json index a52aeb58f..9a4379f70 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,15 @@ "version": "2.9.0", "description": "", "main": "dist/bundle.js", + "engines": { + "node": ">=24.0.0" + }, "dependencies": { "@paypal/paypal-js": "^8.2.0", "@ungap/custom-elements": "^1.3.0", "break_infinity.js": "^2.0.0", "clipboard": "^2.0.11", - "dompurify": "^3.2.5", + "dompurify": "^3.2.7", "fast-mersenne-twister": "^1.0.3", "i18next": "^22.4.9", "lz-string": "^1.4.4", @@ -21,9 +24,10 @@ "@types/cloudflare-turnstile": "^0.2.2", "@types/lodash.clonedeepwith": "^4.5.9", "@types/lz-string": "^1.3.34", + "concurrently": "^9.2.1", "deep-object-diff": "^1.1.9", "dprint": "^0.48.0", - "esbuild": "^0.17.15", + "esbuild": "^0.25.5", "htmlhint": "^1.1.4", "husky": "^8.0.3", "msw": "2.7.5", @@ -32,6 +36,7 @@ "typescript": "^5.0.3" }, "scripts": { + "dev": "concurrently \"npm run watch:esbuild\" \"npx live-server --port=3000\"", "lint": "npx @biomejs/biome lint .", "lint:fix": "npx @biomejs/biome lint --write .", "lint:fix-unsafe": "npx @biomejs/biome lint --write --unsafe .", @@ -46,7 +51,8 @@ "watch:tsc": "npx -p typescript tsc --watch", "prepare": "husky install", "cloudflare:build": "node scripts/needsBundling.js || npm run build:esbuild", - "msw:init": "npx -y msw init . --save" + "msw:init": "npx -y msw init . --save", + "check-circular": "madge --circular src/" }, "repository": { "type": "git", diff --git a/src/Achievements.ts b/src/Achievements.ts index 8790eed13..98881b68a 100644 --- a/src/Achievements.ts +++ b/src/Achievements.ts @@ -1,486 +1,3815 @@ import Decimal from 'break_infinity.js' import i18next from 'i18next' +import { antSacrificePointsToMultiplier } from './Ants' import { DOMCacheGetOrSet } from './Cache/DOM' -import { CalcCorruptionStuff, calculateGlobalSpeedMult } from './Calculate' -import { format, player } from './Synergism' -import { Alert, Notification, revealStuff } from './UpdateHTML' -import { sumContents } from './Utility' +import { CalcCorruptionStuff, calculateAscensionScore } from './Calculate' +import { campaignTokens } from './Campaign' +import { hepteracts } from './Hepteracts' +import { displayLevelStuff } from './Levels' +import { maxOcteractUpgradeAP, octeractUpgrades } from './Octeracts' +import { maxRedAmbrosiaUpgradeAP, redAmbrosiaUpgrades } from './RedAmbrosiaUpgrades' +import { runeBlessings } from './RuneBlessings' +import { runes, sumOfFreeRuneLevels, sumOfRuneLevels } from './Runes' +import { runeSpirits } from './RuneSpirits' +import { getGQUpgradeEffect, goldenQuarkUpgrades, maxGoldenQuarkUpgradeAP } from './singularity' +import { maxAPFromChallenges, type SingularityChallengeDataKeys } from './SingularityChallenges' +import { format, formatAsPercentIncrease, player } from './Synergism' +import { Tabs } from './Tabs' +import { maxTalismansRarityAP, talismans } from './Talismans' +import type { resetNames } from './types/Synergism' +import { Alert, CloseModal, Modal, Notification, revealStuff } from './UpdateHTML' +import { isMobile, sumContents } from './Utility' import { Globals as G } from './Variables' -// dprint-ignore -export const achievementpointvalues = [ - 0, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 2, 8, 10, 2, 8, 10, 10, - 2, 8, 10, 10, 10, 10, 10, - 2, 4, 6, 8, 10, 10, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 1, 2, 4, 6, 8, 9, 10, - 10, 10, 10, 10, 10, 10, 10, - 10, 10, 10, 10, 10, 10, 10, - 20, 20, 20, 40, 60, 60, 100, - 20, 20, 40, 40, 60, 60, 100, - 20, 20, 40, 40, 60, 60, 100, - 20, 40, 40, 40, 60, 60, 100, - 40, 40, 40, 60, 60, 100, 100, - 40, 40, 60, 60, 100, 100, 100, - 20, 40, 40, 60, 60, 100, 100, - 40, 60, 100, 60, 100, 100, 40, - 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 100, 100, 0, - 50, 75, 75, 75, 100, 100, 150, - 50, 75, 75, 75, 100, 100, 150, - 50, 75, 75, 75, 100, 100, 150, - 10, 10, 20, 20, 30, 40, 50 -] +export const resetAchievementCheck = (reset: resetNames) => { + if (reset === 'prestige') { + awardUngroupedAchievement('prestigeNoAccelerator') + awardUngroupedAchievement('prestigeNoMult') + awardUngroupedAchievement('prestigeNoCoinUpgrade') + awardAchievementGroup('prestigePointGain') + } + if (reset === 'transcension') { + awardUngroupedAchievement('transcendNoAccelerator') + awardUngroupedAchievement('transcendNoMult') + awardUngroupedAchievement('transcendNoCoinUpgrade') + awardUngroupedAchievement('transcendNoCoinDiamondUpgrade') + awardAchievementGroup('transcendPointGain') + } + if (reset === 'reincarnation') { + awardUngroupedAchievement('reincarnationNoAccelerator') + awardUngroupedAchievement('reincarnationNoMult') + awardUngroupedAchievement('reincarnationNoCoinUpgrade') + awardUngroupedAchievement('reincarnationNoCoinDiamondUpgrade') + awardUngroupedAchievement('reincarnationNoCoinDiamondMythosUpgrade') + awardUngroupedAchievement('reincarnationMinimumUpgrades') + awardAchievementGroup('reincarnationPointGain') + } +} -export const totalachievementpoints = achievementpointvalues.reduce((a, b) => a + b, 0) - -export const areward = (i: number): string => { - // May 22, 2021: Allow achievement bonus values display directly in the description - // Using areward as const object did not allow ${player object} - - // Effective score is 3rd index - const corr = CalcCorruptionStuff() - - const extra: Record> = { - 118: format( - Math.pow( - 0.9925, - player.challengecompletions[6] + player.challengecompletions[7] + player.challengecompletions[8] - + player.challengecompletions[9] + player.challengecompletions[10] - ), - 4 - ), - 169: format(Decimal.log(player.antPoints.add(10), 10), 2), - 174: format(0.4 * Decimal.log(player.antPoints.add(1), 10), 2), - 187: { - x: format(Math.max(1, Math.log10(corr[3] + 1) - 7), 2), - y: format(Math.min(100, player.ascensionCount / 10000), 2) - }, - 188: format(Math.min(100, player.ascensionCount / 50000), 2), - 189: format(Math.min(200, player.ascensionCount / 2.5e6), 2), - 193: format(Decimal.log(player.ascendShards.add(1), 10) / 4, 2), - 195: format(Math.min(25000, Decimal.log(player.ascendShards.add(1), 10) / 4), 2), - 196: format(Math.min(2000, Decimal.log(player.ascendShards.add(1), 10) / 50), 2), - 202: format(Math.min(200, player.ascensionCount / 5e6), 2), - 216: format(Math.min(200, player.ascensionCount / 1e7), 2), - 223: format(Math.min(200, player.ascensionCount / 13370000), 2), - 240: format(Math.min(1.5, 1 + Math.max(2, Math.log10(calculateGlobalSpeedMult())) / 20), 2), - 254: format(Math.min(15, Math.log10(corr[3] + 1) * 0.6), 2, true), - 255: format(Math.min(15, Math.log10(corr[3] + 1) * 0.6), 2, true), - 256: format(Math.min(15, Math.log10(corr[3] + 1) * 0.6), 2, true), - 257: format(Math.min(15, Math.log10(corr[3] + 1) * 0.6), 2, true), - 258: format(Math.min(15, Math.log10(corr[3] + 1) * 0.6), 2, true), - 262: format(Math.min(10, Math.log10(player.ascensionCount + 1)), 2), - 263: format(Math.min(10, Math.log10(player.ascensionCount + 1)), 2), - 264: format(Math.min(40, player.ascensionCount / 2e11), 2), - 265: format(Math.min(20, player.ascensionCount / 8e12), 2), - 266: format(Math.min(10, player.ascensionCount / 1e14), 2), - 267: format(Math.min(100, Decimal.log(player.ascendShards.add(1), 10) / 1000), 2), - 270: format(Math.min(100, Decimal.log(player.ascendShards.add(1), 10) / 10000), 2), - 271: format(Math.max(0, Math.min(1, (Decimal.log(player.ascendShards.add(1), 10) - 1e5) / 9e5)), 2, true) - } - - // dprint-ignore - const descs: number[] = [ - 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 17, 18, 19, - 20, 21, 24, 25, 26, 27, 28, 31, 32, 33, 34, 35, - 36, 37, 38, 43, 44, 45, 46, 47, 50, 51, 52, 53, - 57, 58, 59, 60, 61, 62, 71, 72, 73, 74, 75, 76, - 77, 78, 79, 80, 82, 84, 85, 86, 87, 89, 91, 92, - 93, 94, 96, 98, 99, 100, 101, 102, 103, 105, 106, - 107, 108, 110, 112, 115, 117, 119, 122, 124, 126, - 127, 128, 129, 131, 132, 133, 134, 135, 136, 137, - 140, 141, 147, 171, 172, 173, 176, 177, 178, 179, - 180, 181, 182, 197, 198, 199, 200, 201, 204, 205, - 206, 207, 208, 209, 211, 212, 213, 214, 215, 218, - 219, 220, 221, 222, 250, 251, 253, 259, 260, 261 - ] +export const challengeAchievementCheck = (i: number) => { + switch (i) { + case 1: + awardAchievementGroup('challenge1') + awardUngroupedAchievement('chal1NoGen') + break + case 2: + awardAchievementGroup('challenge2') + awardUngroupedAchievement('chal2NoGen') + break + case 3: + awardAchievementGroup('challenge3') + awardUngroupedAchievement('chal3NoGen') + break + case 4: + awardAchievementGroup('challenge4') + break + case 5: + awardAchievementGroup('challenge5') + awardUngroupedAchievement('diamondSearch') + break + case 6: + awardAchievementGroup('challenge6') + break + case 7: + awardAchievementGroup('challenge7') + break + case 8: + awardAchievementGroup('challenge8') + break + case 9: + awardAchievementGroup('challenge9') + break + case 10: + awardAchievementGroup('challenge10') + break + case 11: + awardAchievementGroup('challenge11') + if (player.challengecompletions[10] > 50 && player.corruptions.used.extinction >= 5) { + awardUngroupedAchievement('extraChallenging') + } + break + case 12: + awardAchievementGroup('challenge12') + break + case 13: + awardAchievementGroup('challenge13') + break + case 14: + awardAchievementGroup('challenge14') + break + case 15: + awardUngroupedAchievement('sadisticAch') + break + } +} - if (descs.includes(i) || i in extra) { - const obj = extra[i] - const map = typeof obj === 'object' ? obj : { x: obj } +export const buildingAchievementCheck = () => { + awardAchievementGroup('firstOwnedCoin') + awardAchievementGroup('secondOwnedCoin') + awardAchievementGroup('thirdOwnedCoin') + awardAchievementGroup('fourthOwnedCoin') + awardAchievementGroup('fifthOwnedCoin') +} - return i18next.t(`achievements.rewards.${i}`, map) +export const getAchievementQuarks = (i: number) => { + const globalQuarkMultiplier = player.worlds.applyBonus(1) + let actualMultiplier = globalQuarkMultiplier + if (actualMultiplier > 100) { + actualMultiplier = Math.pow(100, 0.6) * Math.pow(actualMultiplier, 0.4) } - return '' + return Math.floor(achievements[i].pointValue * actualMultiplier) } -export const achievementAlerts = async (num: number) => { - if (player.highestSingularityCount === 0) { - if (num === 36 || num === 38 || num === 255) { - return Alert(i18next.t(`achievements.alerts.${num}`)) - } +/* June 9, 2025 Achievements System Rewrite */ +export type AchievementGroups = + | 'firstOwnedCoin' + | 'secondOwnedCoin' + | 'thirdOwnedCoin' + | 'fourthOwnedCoin' + | 'fifthOwnedCoin' + | 'prestigePointGain' + | 'transcendPointGain' + | 'reincarnationPointGain' + | 'challenge1' + | 'challenge2' + | 'challenge3' + | 'challenge4' + | 'challenge5' + | 'challenge6' + | 'challenge7' + | 'challenge8' + | 'challenge9' + | 'challenge10' + | 'accelerators' + | 'acceleratorBoosts' + | 'multipliers' + | 'antCrumbs' + | 'sacMult' + | 'ascensionCount' + | 'constant' + | 'challenge11' + | 'challenge12' + | 'challenge13' + | 'challenge14' + | 'ascensionScore' + | 'speedBlessing' + | 'speedSpirit' + | 'singularityCount' + | 'runeLevel' + | 'runeFreeLevel' + | 'campaignTokens' + | 'prestigeCount' + | 'transcensionCount' + | 'reincarnationCount' + | 'ungrouped' + +export type AchievementRewards = + | 'acceleratorPower' + | 'accelerators' + | 'multipliers' + | 'accelBoosts' + | 'crystalMultiplier' + | 'taxReduction' + | 'particleGain' + | 'conversionExponent' + | 'chronosTalisman' + | 'midasTalisman' + | 'metaphysicsTalisman' + | 'polymathTalisman' + | 'talismanPower' + | 'sacrificeMult' + | 'antSpeed' + | 'antSacrificeUnlock' + | 'antAutobuyers' + | 'antUpgradeAutobuyers' + | 'antELOAdditive' + | 'antELOMultiplicative' + | 'wowSquareTalisman' + | 'ascensionCountMultiplier' + | 'ascensionCountAdditive' + | 'wowCubeGain' + | 'wowTesseractGain' + | 'wowHypercubeGain' + | 'wowPlatonicGain' + | 'quarkGain' + | 'wowHepteractGain' + | 'ascensionScore' + | 'constUpgrade1Buff' + | 'constUpgrade2Buff' + | 'platonicToHypercubes' + | 'statTracker' + | 'ascensionRewardScaling' + | 'overfluxConversionRate' + | 'diamondUpgrade18' + | 'diamondUpgrade19' + | 'diamondUpgrade20' + | 'prestigeCountMultiplier' + | 'transcensionCountMultiplier' + | 'reincarnationCountMultiplier' + | 'duplicationRuneUnlock' + | 'prismRuneUnlock' + | 'thriftRuneUnlock' + | 'salvage' + | 'offeringBonus' + | 'obtainiumBonus' + | 'transcendToPrestige' + | 'reincarnationToTranscend' + +export type AchievementReward = Partial number>> + +export interface Achievement { + pointValue: number + unlockCondition: () => boolean + group: AchievementGroups + reward?: AchievementReward + checkReset?: () => boolean +} + +export interface ProgressiveAchievementsObject { + cached: number + rewardedAP: number +} + +export interface ProgressiveAchievement { + maxPointValue: number + pointsAwarded: (cached: number) => number + updateValue: () => number // Number to compare to existing caches + useCachedValue: boolean + rewardedAP: number // Updating achievementPoints: pointsAwarded() - rewardedAP + extraI18n?: () => Record + displayOrder: number + displayCondition: () => boolean +} + +export const progressiveAchievements: Record = { + runeLevel: { + maxPointValue: 1000, + pointsAwarded: (cached: number) => { + return Math.min(200, Math.floor(cached / 1000)) + Math.min(400, Math.floor(cached / 2500)) + + Math.min(400, Math.floor(cached / 12500)) + }, + updateValue: () => { + return sumOfRuneLevels() + }, + useCachedValue: true, + rewardedAP: 0, + displayOrder: 1, + displayCondition: () => player.prestigeCount > 0 + }, + freeRuneLevel: { + maxPointValue: 500, + pointsAwarded: (cached: number) => { + return Math.min(100, Math.floor(cached / 250)) + Math.min(200, Math.floor(cached / 750)) + + Math.min(200, Math.floor(cached / 2500)) + }, + updateValue: () => { + return sumOfFreeRuneLevels() + }, + useCachedValue: true, + rewardedAP: 0, + displayOrder: 2, + displayCondition: () => player.prestigeCount > 0 + }, + singularityCount: { + maxPointValue: 3600, + pointsAwarded: (_cached: number) => { + return 9 * player.highestSingularityCount + + 3 * Math.max(0, player.highestSingularityCount - 100) + + 3 * Math.max(0, player.highestSingularityCount - 200) + }, + updateValue: () => { + return 0 + }, + useCachedValue: false, + rewardedAP: 0, + displayOrder: 4, + displayCondition: () => player.highestSingularityCount > 0 + }, + ambrosiaCount: { + maxPointValue: 800, + pointsAwarded: (cached: number) => { + return Math.min(200, Math.floor(cached / 100)) + + Math.min(200, Math.floor(cached / 10000)) + + Math.min(400, Math.floor(400 * Math.sqrt(cached / 1e8))) + }, + updateValue: () => { + return player.lifetimeAmbrosia + }, + useCachedValue: true, + rewardedAP: 0, + displayOrder: 8, + displayCondition: () => player.highestSingularityCount >= 25 + }, + redAmbrosiaCount: { + maxPointValue: 800, + pointsAwarded: (cached: number) => { + return Math.min(200, Math.floor(cached / 25)) + + Math.min(200, Math.floor(cached / 2500)) + + Math.min(400, Math.floor(400 * Math.sqrt(cached / 5e6))) + }, + updateValue: () => { + return player.lifetimeRedAmbrosia + }, + useCachedValue: true, + rewardedAP: 0, + displayOrder: 9, + displayCondition: () => player.highestSingularityCount >= 150 + }, + exalts: { + maxPointValue: maxAPFromChallenges, + pointsAwarded: (_cached: number) => { + let pointValue = 0 + for (const chal of Object.keys(player.singularityChallenges) as SingularityChallengeDataKeys[]) { + pointValue += player.singularityChallenges[chal].rewardAP + } + return pointValue + }, + updateValue: () => { + return 0 + }, + useCachedValue: false, + rewardedAP: 0, + extraI18n: () => { + return { + num1: player.singularityChallenges.noSingularityUpgrades.rewardAP, + cap1: player.singularityChallenges.noSingularityUpgrades.maxAP, + num2: player.singularityChallenges.oneChallengeCap.rewardAP, + cap2: player.singularityChallenges.oneChallengeCap.maxAP, + num3: player.singularityChallenges.limitedAscensions.rewardAP, + cap3: player.singularityChallenges.limitedAscensions.maxAP, + num4: player.singularityChallenges.noOcteracts.rewardAP, + cap4: player.singularityChallenges.noOcteracts.maxAP, + num5: player.singularityChallenges.noAmbrosiaUpgrades.rewardAP, + cap5: player.singularityChallenges.noAmbrosiaUpgrades.maxAP, + num6: player.singularityChallenges.limitedTime.rewardAP, + cap6: player.singularityChallenges.limitedTime.maxAP, + num7: player.singularityChallenges.sadisticPrequel.rewardAP, + cap7: player.singularityChallenges.sadisticPrequel.maxAP, + num8: player.singularityChallenges.taxmanLastStand.rewardAP, + cap8: player.singularityChallenges.taxmanLastStand.maxAP + } + }, + displayOrder: 7, + displayCondition: () => player.highestSingularityCount >= 25 + }, + singularityUpgrades: { + maxPointValue: maxGoldenQuarkUpgradeAP, + pointsAwarded: (_cached: number) => { + let pointValue = 0 + // Go through all sing upgrades. if the max level is NOT -1, add 5 points if the upgrade level equals max level + for (const upgrade of Object.values(goldenQuarkUpgrades)) { + if (upgrade.maxLevel !== -1 && upgrade.level >= upgrade.maxLevel) { + pointValue += 5 + } + } + return pointValue + }, + updateValue: () => { + return 0 + }, + useCachedValue: false, + rewardedAP: 0, + displayOrder: 5, + displayCondition: () => player.highestSingularityCount > 0 + }, + octeractUpgrades: { + maxPointValue: maxOcteractUpgradeAP, + pointsAwarded: (_cached: number) => { + let pointValue = 0 + // Go through all octeract upgrades. if the max level is NOT -1, add 8 points if the upgrade level equals max level + for (const upgrade of Object.values(octeractUpgrades)) { + if (upgrade.maxLevel !== -1 && upgrade.level >= upgrade.maxLevel) { + pointValue += 8 + } + } + return pointValue + }, + updateValue: () => { + return 0 + }, + useCachedValue: false, + rewardedAP: 0, + displayOrder: 6, + displayCondition: () => Boolean(getGQUpgradeEffect('octeractUnlock')) + }, + redAmbrosiaUpgrades: { + maxPointValue: maxRedAmbrosiaUpgradeAP, + pointsAwarded: () => { + let pointValue = 0 + for (const upgrade of Object.values(redAmbrosiaUpgrades)) { + if (upgrade.level >= upgrade.maxLevel) { + pointValue += 10 + } + } + return pointValue + }, + updateValue: () => { + return 0 + }, + useCachedValue: false, + rewardedAP: 0, + displayOrder: 10, + displayCondition: () => player.highestSingularityCount >= 150 + }, + talismanRarities: { + maxPointValue: maxTalismansRarityAP, + pointsAwarded: (cached: number) => { + return 5 * cached + }, + updateValue: () => { + return Object.values(talismans).reduce((acc, talisman) => { + acc += talisman.rarity + return acc + }, 0) + }, + useCachedValue: true, + rewardedAP: 0, + displayOrder: 3, + displayCondition: () => player.unlocks.talismans } } -// ${format(Decimal.log(player.ascendShards.add(1), 10) / 1000, 2)} (log(constant)/1000)%! -// TODO: clean this up -export const resetachievementcheck = (i: number) => { - if (i === 1) { - if (player.prestigenoaccelerator) { - achievementaward(60) +export const progressiveAchievementKeys = Object.keys(progressiveAchievements) as ProgressiveAchievements[] + +export const emptyProgressiveAchievements = Object + .fromEntries( + (Object.keys(progressiveAchievements)).map((key) => [key, { cached: 0, rewardedAP: 0 }]) + ) as Record + +export const achievements: Achievement[] = [ + { pointValue: 5, unlockCondition: () => true, group: 'ungrouped' }, // Free Achievement Perhaps? + { pointValue: 5, unlockCondition: () => player.firstOwnedCoin >= 1, group: 'firstOwnedCoin' }, + { pointValue: 10, unlockCondition: () => player.firstOwnedCoin >= 10, group: 'firstOwnedCoin' }, + { + pointValue: 15, + unlockCondition: () => player.firstOwnedCoin >= 100, + group: 'firstOwnedCoin', + reward: { acceleratorPower: () => 0.001 } + }, + { + pointValue: 20, + unlockCondition: () => player.firstOwnedCoin >= 1000, + group: 'firstOwnedCoin', + checkReset: () => player.highestSingularityCount >= 2 + }, + { + pointValue: 25, + unlockCondition: () => player.firstOwnedCoin >= 5000, + group: 'firstOwnedCoin', + reward: { accelerators: () => Math.floor(player.firstOwnedCoin / 500) } + }, + { + pointValue: 30, + unlockCondition: () => player.firstOwnedCoin >= 10000, + group: 'firstOwnedCoin', + reward: { multipliers: () => Math.floor(player.firstOwnedCoin / 1000) } + }, + { + pointValue: 35, + unlockCondition: () => player.firstOwnedCoin >= 20000, + group: 'firstOwnedCoin', + reward: { accelBoosts: () => Math.floor(player.firstOwnedCoin / 2000) } + }, + { pointValue: 5, unlockCondition: () => player.secondOwnedCoin >= 1, group: 'secondOwnedCoin' }, + { pointValue: 10, unlockCondition: () => player.secondOwnedCoin >= 10, group: 'secondOwnedCoin' }, + { + pointValue: 15, + unlockCondition: () => player.secondOwnedCoin >= 100, + group: 'secondOwnedCoin', + reward: { acceleratorPower: () => 0.0015 } + }, + { + pointValue: 20, + unlockCondition: () => player.secondOwnedCoin >= 500, + group: 'secondOwnedCoin', + checkReset: () => player.highestSingularityCount >= 2 + }, + { + pointValue: 25, + unlockCondition: () => player.secondOwnedCoin >= 5000, + group: 'secondOwnedCoin', + reward: { accelerators: () => Math.floor(player.secondOwnedCoin / 500) } + }, + { + pointValue: 30, + unlockCondition: () => player.secondOwnedCoin >= 10000, + group: 'secondOwnedCoin', + reward: { multipliers: () => Math.floor(player.secondOwnedCoin / 1000) } + }, + { + pointValue: 35, + unlockCondition: () => player.secondOwnedCoin >= 20000, + group: 'secondOwnedCoin', + reward: { accelBoosts: () => Math.floor(player.secondOwnedCoin / 2000) } + }, + { pointValue: 5, unlockCondition: () => player.thirdOwnedCoin >= 1, group: 'thirdOwnedCoin' }, + { pointValue: 10, unlockCondition: () => player.thirdOwnedCoin >= 10, group: 'thirdOwnedCoin' }, + { + pointValue: 15, + unlockCondition: () => player.thirdOwnedCoin >= 100, + group: 'thirdOwnedCoin', + reward: { acceleratorPower: () => 0.002 } + }, + { + pointValue: 20, + unlockCondition: () => player.thirdOwnedCoin >= 333, + group: 'thirdOwnedCoin', + checkReset: () => player.highestSingularityCount >= 2 + }, + { + pointValue: 25, + unlockCondition: () => player.thirdOwnedCoin >= 5000, + group: 'thirdOwnedCoin', + reward: { accelerators: () => Math.floor(player.thirdOwnedCoin / 500) } + }, + { + pointValue: 30, + unlockCondition: () => player.thirdOwnedCoin >= 10000, + group: 'thirdOwnedCoin', + reward: { multipliers: () => Math.floor(player.thirdOwnedCoin / 1000) } + }, + { + pointValue: 35, + unlockCondition: () => player.thirdOwnedCoin >= 20000, + group: 'thirdOwnedCoin', + reward: { accelBoosts: () => Math.floor(player.thirdOwnedCoin / 2000) } + }, + { pointValue: 5, unlockCondition: () => player.fourthOwnedCoin >= 1, group: 'fourthOwnedCoin' }, + { pointValue: 10, unlockCondition: () => player.fourthOwnedCoin >= 10, group: 'fourthOwnedCoin' }, + { + pointValue: 15, + unlockCondition: () => player.thirdOwnedCoin >= 100, + group: 'fourthOwnedCoin', + reward: { acceleratorPower: () => 0.002 } + }, + { + pointValue: 20, + unlockCondition: () => player.thirdOwnedCoin >= 333, + group: 'fourthOwnedCoin', + checkReset: () => player.highestSingularityCount >= 2 + }, + { + pointValue: 25, + unlockCondition: () => player.thirdOwnedCoin >= 5000, + group: 'fourthOwnedCoin', + reward: { accelerators: () => Math.floor(player.thirdOwnedCoin / 500) } + }, + { + pointValue: 30, + unlockCondition: () => player.thirdOwnedCoin >= 10000, + group: 'fourthOwnedCoin', + reward: { multipliers: () => Math.floor(player.thirdOwnedCoin / 1000) } + }, + { + pointValue: 35, + unlockCondition: () => player.thirdOwnedCoin >= 20000, + group: 'fourthOwnedCoin', + reward: { accelBoosts: () => Math.floor(player.thirdOwnedCoin / 2000) } + }, + { pointValue: 5, unlockCondition: () => player.fifthOwnedCoin >= 1, group: 'fifthOwnedCoin' }, + { pointValue: 10, unlockCondition: () => player.fifthOwnedCoin >= 10, group: 'fifthOwnedCoin' }, + { + pointValue: 15, + unlockCondition: () => player.fifthOwnedCoin >= 66, + group: 'fifthOwnedCoin', + reward: { acceleratorPower: () => 0.003 } + }, + { + pointValue: 20, + unlockCondition: () => player.fifthOwnedCoin >= 200, + group: 'fifthOwnedCoin', + checkReset: () => player.highestSingularityCount >= 2 + }, + { + pointValue: 25, + unlockCondition: () => player.fifthOwnedCoin >= 6666, + group: 'fifthOwnedCoin', + reward: { accelerators: () => Math.floor(player.fifthOwnedCoin / 500) } + }, + { + pointValue: 30, + unlockCondition: () => player.fifthOwnedCoin >= 17777, + group: 'fifthOwnedCoin', + reward: { multipliers: () => Math.floor(player.fifthOwnedCoin / 1000) } + }, + { + pointValue: 35, + unlockCondition: () => player.fifthOwnedCoin >= 42777, + group: 'fifthOwnedCoin', + reward: { accelBoosts: () => Math.floor(player.fifthOwnedCoin / 2000) } + }, + { + pointValue: 5, + unlockCondition: () => G.prestigePointGain.gte(1), + group: 'prestigePointGain', + checkReset: () => player.highestSingularityCount >= 2 + }, + { + pointValue: 10, + unlockCondition: () => G.prestigePointGain.gte(1e6), + group: 'prestigePointGain', + reward: { crystalMultiplier: () => Math.max(1, Decimal.log(player.prestigePoints, Math.E)) }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 15, + unlockCondition: () => G.prestigePointGain.gte(1e100), + group: 'prestigePointGain', + checkReset: () => player.highestSingularityCount >= 3 + }, + { pointValue: 20, unlockCondition: () => G.prestigePointGain.gte('1e1000'), group: 'prestigePointGain' }, + { pointValue: 25, unlockCondition: () => G.prestigePointGain.gte('1e10000'), group: 'prestigePointGain' }, + { pointValue: 30, unlockCondition: () => G.prestigePointGain.gte('1e77777'), group: 'prestigePointGain' }, + { pointValue: 35, unlockCondition: () => G.prestigePointGain.gte('1e250000'), group: 'prestigePointGain' }, + { + pointValue: 5, + unlockCondition: () => G.transcendPointGain.gte(1), + group: 'transcendPointGain', + checkReset: () => player.highestSingularityCount >= 2 + }, + { + pointValue: 10, + unlockCondition: () => G.transcendPointGain.gte(1e6), + group: 'transcendPointGain', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 15, + unlockCondition: () => G.transcendPointGain.gte(1e50), + group: 'transcendPointGain', + reward: { taxReduction: () => 0.95 } + }, + { + pointValue: 20, + unlockCondition: () => G.transcendPointGain.gte(1e308), + group: 'transcendPointGain', + reward: { taxReduction: () => 0.95 } + }, + { + pointValue: 25, + unlockCondition: () => G.transcendPointGain.gte('1e1500'), + group: 'transcendPointGain', + reward: { taxReduction: () => 0.9 } + }, + { pointValue: 30, unlockCondition: () => G.transcendPointGain.gte('1e25000'), group: 'transcendPointGain' }, + { pointValue: 35, unlockCondition: () => G.transcendPointGain.gte('1e100000'), group: 'transcendPointGain' }, + { + pointValue: 5, + unlockCondition: () => G.reincarnationPointGain.gte(1), + group: 'reincarnationPointGain', + reward: { particleGain: () => 2 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { pointValue: 10, unlockCondition: () => G.reincarnationPointGain.gte(1e5), group: 'reincarnationPointGain' }, + { pointValue: 15, unlockCondition: () => G.reincarnationPointGain.gte(1e30), group: 'reincarnationPointGain' }, + { + pointValue: 20, + unlockCondition: () => G.reincarnationPointGain.gte(1e200), + group: 'reincarnationPointGain' + }, + { + pointValue: 25, + unlockCondition: () => G.reincarnationPointGain.gte('1e1500'), + group: 'reincarnationPointGain' + }, + { + pointValue: 30, + unlockCondition: () => G.reincarnationPointGain.gte('1e5000'), + group: 'reincarnationPointGain' + }, + { + pointValue: 35, + unlockCondition: () => G.reincarnationPointGain.gte('1e7777'), + group: 'reincarnationPointGain' + }, + { + pointValue: 5, + unlockCondition: () => player.prestigenomultiplier, + group: 'ungrouped', + reward: { multipliers: () => 1 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 10, + unlockCondition: () => player.transcendnomultiplier, + group: 'ungrouped', + reward: { multipliers: () => 2 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 15, + unlockCondition: () => player.reincarnatenomultiplier, + group: 'ungrouped', + reward: { multipliers: () => 4 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 20, + unlockCondition: () => player.prestigenoaccelerator, + group: 'ungrouped', + reward: { accelerators: () => 2 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 25, + unlockCondition: () => player.transcendnoaccelerator, + group: 'ungrouped', + reward: { accelerators: () => 4 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 30, + unlockCondition: () => player.reincarnatenoaccelerator, + group: 'ungrouped', + reward: { accelerators: () => 8 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 35, + unlockCondition: () => { + return player.coinsThisTranscension.gte('1e120000') && player.acceleratorBought === 0 + && player.acceleratorBoostBought === 0 + }, + group: 'ungrouped', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 5, + unlockCondition: () => player.prestigenocoinupgrades, + group: 'ungrouped', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 10, + unlockCondition: () => player.transcendnocoinupgrades, + group: 'ungrouped', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 15, + unlockCondition: () => player.transcendnocoinorprestigeupgrades, + group: 'ungrouped', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 15, + unlockCondition: () => player.reincarnatenocoinupgrades, + group: 'ungrouped', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 20, + unlockCondition: () => player.reincarnatenocoinorprestigeupgrades, + group: 'ungrouped', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 30, + unlockCondition: () => player.reincarnatenocoinprestigeortranscendupgrades, + group: 'ungrouped', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 40, + unlockCondition: () => player.reincarnatenocoinprestigetranscendorgeneratorupgrades, + group: 'ungrouped', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 10, + unlockCondition: () => { + return sumContents(player.upgrades.slice(101, 106)) === 1 && player.upgrades[102] === 1 + }, + group: 'ungrouped', + reward: { conversionExponent: () => 0.01 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 10, + unlockCondition: () => { + return sumContents(player.upgrades.slice(101, 106)) === 1 && player.upgrades[103] === 1 + }, + group: 'ungrouped', + reward: { conversionExponent: () => 0.01 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 15, + unlockCondition: () => { + return sumContents(player.upgrades.slice(101, 106)) === 1 && player.upgrades[104] === 1 + }, + group: 'ungrouped', + reward: { conversionExponent: () => 0.01 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 20, + unlockCondition: () => { + return sumContents(player.upgrades.slice(101, 106)) === 1 && player.upgrades[105] === 1 + }, + group: 'ungrouped', + reward: { conversionExponent: () => 0.01 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 25, + unlockCondition: () => { + return player.currentChallenge.transcension === 1 && player.coinsThisTranscension.gte('1e1000') + && sumContents(player.upgrades.slice(101, 106)) === 0 + }, + group: 'ungrouped', + reward: { conversionExponent: () => 0.01 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 25, + unlockCondition: () => { + return player.currentChallenge.transcension === 2 && player.coinsThisTranscension.gte('1e1000') + && sumContents(player.upgrades.slice(101, 106)) === 0 + }, + group: 'ungrouped', + reward: { conversionExponent: () => 0.01 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 50, + unlockCondition: () => { + return player.currentChallenge.transcension === 3 && player.coinsThisTranscension.gte('1e99999') + && sumContents(player.upgrades.slice(101, 106)) === 0 + }, + group: 'ungrouped', + reward: { conversionExponent: () => 0.01 }, + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 5, + unlockCondition: () => player.challengecompletions[1] >= 1, + group: 'challenge1', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 10, + unlockCondition: () => player.challengecompletions[1] >= 3, + group: 'challenge1', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 15, + unlockCondition: () => player.challengecompletions[1] >= 5, + group: 'challenge1', + checkReset: () => player.highestSingularityCount >= 3 + }, + { pointValue: 20, unlockCondition: () => player.challengecompletions[1] >= 10, group: 'challenge1' }, + { + pointValue: 25, + unlockCondition: () => player.challengecompletions[1] >= 20, + group: 'challenge1', + reward: { taxReduction: () => 0.96 } + }, + { pointValue: 30, unlockCondition: () => player.challengecompletions[1] >= 50, group: 'challenge1' }, + { + pointValue: 35, + unlockCondition: () => player.challengecompletions[1] >= 75, + group: 'challenge1' + }, + { + pointValue: 5, + unlockCondition: () => player.challengecompletions[2] >= 1, + group: 'challenge2', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 10, + unlockCondition: () => player.challengecompletions[2] >= 3, + group: 'challenge2', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 15, + unlockCondition: () => player.challengecompletions[2] >= 5, + group: 'challenge2', + checkReset: () => player.highestSingularityCount >= 3 + }, + { pointValue: 20, unlockCondition: () => player.challengecompletions[2] >= 10, group: 'challenge2' }, + { + pointValue: 25, + unlockCondition: () => player.challengecompletions[2] >= 20, + group: 'challenge2', + reward: { taxReduction: () => 0.96 } + }, + { pointValue: 30, unlockCondition: () => player.challengecompletions[2] >= 50, group: 'challenge2' }, + { + pointValue: 35, + unlockCondition: () => player.challengecompletions[2] >= 75, + group: 'challenge2' + }, + { + pointValue: 5, + unlockCondition: () => player.challengecompletions[3] >= 1, + group: 'challenge3', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 10, + unlockCondition: () => player.challengecompletions[3] >= 3, + group: 'challenge3', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 15, + unlockCondition: () => player.challengecompletions[3] >= 5, + group: 'challenge3' + }, + { pointValue: 20, unlockCondition: () => player.challengecompletions[3] >= 10, group: 'challenge3' }, + { + pointValue: 25, + unlockCondition: () => player.challengecompletions[3] >= 20, + group: 'challenge3', + reward: { taxReduction: () => 0.96 } + }, + { pointValue: 30, unlockCondition: () => player.challengecompletions[3] >= 50, group: 'challenge3' }, + { + pointValue: 35, + unlockCondition: () => player.challengecompletions[3] >= 75, + group: 'challenge3' + }, + { + pointValue: 5, + unlockCondition: () => player.challengecompletions[4] >= 1, + group: 'challenge4', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 10, + unlockCondition: () => player.challengecompletions[4] >= 3, + group: 'challenge4', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 15, + unlockCondition: () => player.challengecompletions[4] >= 5, + group: 'challenge4' + }, + { + pointValue: 20, + unlockCondition: () => player.challengecompletions[4] >= 10, + group: 'challenge4' + }, + { + pointValue: 25, + unlockCondition: () => player.challengecompletions[4] >= 20, + group: 'challenge4', + reward: { taxReduction: () => 0.96 } + }, + { pointValue: 30, unlockCondition: () => player.challengecompletions[4] >= 50, group: 'challenge4' }, + { + pointValue: 35, + unlockCondition: () => player.challengecompletions[4] >= 75, + group: 'challenge4' + }, + { + pointValue: 5, + unlockCondition: () => player.challengecompletions[5] >= 1, + group: 'challenge5', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 10, + unlockCondition: () => player.challengecompletions[5] >= 3, + group: 'challenge5', + checkReset: () => player.highestSingularityCount >= 3 + }, + { + pointValue: 15, + unlockCondition: () => player.challengecompletions[5] >= 5, + group: 'challenge5' + }, + { pointValue: 20, unlockCondition: () => player.challengecompletions[5] >= 10, group: 'challenge5' }, + { + pointValue: 25, + unlockCondition: () => player.challengecompletions[5] >= 20, + group: 'challenge5', + reward: { taxReduction: () => 0.96 } + }, + { pointValue: 30, unlockCondition: () => player.challengecompletions[5] >= 50, group: 'challenge5' }, + { + pointValue: 35, + unlockCondition: () => player.challengecompletions[5] >= 75, + group: 'challenge5' + }, + { + pointValue: 5, + unlockCondition: () => player.challengecompletions[6] >= 1, + group: 'challenge6', + checkReset: () => player.highestSingularityCount >= 4 + }, + { pointValue: 10, unlockCondition: () => player.challengecompletions[6] >= 2, group: 'challenge6' }, + { + pointValue: 15, + unlockCondition: () => player.challengecompletions[6] >= 3, + group: 'challenge6' + }, + { pointValue: 20, unlockCondition: () => player.challengecompletions[6] >= 5, group: 'challenge6' }, + { + pointValue: 25, + unlockCondition: () => player.challengecompletions[6] >= 10, + group: 'challenge6', + reward: { taxReduction: () => 0.95 } + }, + { + pointValue: 30, + unlockCondition: () => player.challengecompletions[6] >= 20, + group: 'challenge6', + reward: { + taxReduction: () => + Math.pow( + 0.9925, + player.challengecompletions[6] + player.challengecompletions[7] + player.challengecompletions[8] + + player.challengecompletions[9] + player.challengecompletions[10] + ) } - if (player.prestigenomultiplier) { - achievementaward(57) + }, + { + pointValue: 35, + unlockCondition: () => player.challengecompletions[6] >= 25, + group: 'challenge6' + }, + { + pointValue: 5, + unlockCondition: () => player.challengecompletions[7] >= 1, + group: 'challenge7', + reward: { diamondUpgrade18: () => 0 }, + checkReset: () => player.highestSingularityCount >= 7 + }, + { pointValue: 10, unlockCondition: () => player.challengecompletions[7] >= 2, group: 'challenge7' }, + { + pointValue: 15, + unlockCondition: () => player.challengecompletions[7] >= 3, + group: 'challenge7' + }, + { pointValue: 20, unlockCondition: () => player.challengecompletions[7] >= 5, group: 'challenge7' }, + { + pointValue: 25, + unlockCondition: () => player.challengecompletions[7] >= 10, + group: 'challenge7', + reward: { taxReduction: () => 0.95 }, + checkReset: () => player.highestSingularityCount >= 10 + }, + { + pointValue: 30, + unlockCondition: () => player.challengecompletions[7] >= 20, + group: 'challenge7' + }, + { + pointValue: 35, + unlockCondition: () => player.challengecompletions[7] >= 25, + group: 'challenge7', + reward: { chronosTalisman: () => 1 } + }, + { + pointValue: 5, + unlockCondition: () => player.challengecompletions[8] >= 1, + group: 'challenge8', + reward: { diamondUpgrade19: () => 1 }, + checkReset: () => player.highestSingularityCount >= 10 + }, + { pointValue: 10, unlockCondition: () => player.challengecompletions[8] >= 2, group: 'challenge8' }, + { + pointValue: 15, + unlockCondition: () => player.challengecompletions[8] >= 3, + group: 'challenge8' + }, + { pointValue: 20, unlockCondition: () => player.challengecompletions[8] >= 5, group: 'challenge8' }, + { + pointValue: 25, + unlockCondition: () => player.challengecompletions[8] >= 10, + group: 'challenge8', + reward: { taxReduction: () => 0.95 } + }, + { + pointValue: 30, + unlockCondition: () => player.challengecompletions[8] >= 20, + group: 'challenge8' + }, + { + pointValue: 35, + unlockCondition: () => player.challengecompletions[8] >= 25, + group: 'challenge8', + reward: { midasTalisman: () => 1 } + }, + { + pointValue: 5, + unlockCondition: () => player.challengecompletions[9] >= 1, + group: 'challenge9', + reward: { diamondUpgrade20: () => 1 }, + checkReset: () => player.highestSingularityCount >= 20 + }, + { + pointValue: 10, + unlockCondition: () => player.challengecompletions[9] >= 2, + group: 'challenge9', + reward: { talismanPower: () => 0.02 } + }, + { + pointValue: 15, + unlockCondition: () => player.challengecompletions[9] >= 3, + group: 'challenge9', + reward: { talismanPower: () => 0.02 } + }, + { + pointValue: 20, + unlockCondition: () => player.challengecompletions[9] >= 5, + group: 'challenge9', + reward: { sacrificeMult: () => 1.25 } + }, + { pointValue: 25, unlockCondition: () => player.challengecompletions[9] >= 10, group: 'challenge9' }, + { + pointValue: 30, + unlockCondition: () => player.challengecompletions[9] >= 20, + group: 'challenge9' + }, + { + pointValue: 35, + unlockCondition: () => player.challengecompletions[9] >= 25, + group: 'challenge9', + reward: { metaphysicsTalisman: () => 1 } + }, + { + pointValue: 5, + unlockCondition: () => player.challengecompletions[10] >= 1, + group: 'challenge10' + }, + { pointValue: 10, unlockCondition: () => player.challengecompletions[10] >= 2, group: 'challenge10' }, + { + pointValue: 15, + unlockCondition: () => player.challengecompletions[10] >= 3, + group: 'challenge10' + }, + { + pointValue: 20, + unlockCondition: () => player.challengecompletions[10] >= 5, + group: 'challenge10', + reward: { talismanPower: () => 0.025 } + }, + { + pointValue: 25, + unlockCondition: () => player.challengecompletions[10] >= 10, + group: 'challenge10', + reward: { talismanPower: () => 0.025 } + }, + { + pointValue: 30, + unlockCondition: () => player.challengecompletions[10] >= 20, + group: 'challenge10' + }, + { + pointValue: 35, + unlockCondition: () => player.challengecompletions[10] >= 25, + group: 'challenge10', + reward: { polymathTalisman: () => 1 } + }, + { pointValue: 5, unlockCondition: () => player.acceleratorBought >= 5, group: 'accelerators' }, + { + pointValue: 10, + unlockCondition: () => player.acceleratorBought >= 25, + group: 'accelerators', + reward: { acceleratorPower: () => 0.01 } + }, + { pointValue: 15, unlockCondition: () => player.acceleratorBought >= 100, group: 'accelerators' }, + { + pointValue: 20, + unlockCondition: () => player.acceleratorBought >= 666, + group: 'accelerators', + reward: { accelerators: () => 5 } + }, + { + pointValue: 25, + unlockCondition: () => player.acceleratorBought >= 2000, + group: 'accelerators', + reward: { accelerators: () => 12 } + }, + { + pointValue: 30, + unlockCondition: () => player.acceleratorBought >= 12500, + group: 'accelerators', + reward: { accelerators: () => 25 } + }, + { + pointValue: 35, + unlockCondition: () => player.acceleratorBought >= 100000, + group: 'accelerators', + reward: { accelerators: () => 50 } + }, + { pointValue: 5, unlockCondition: () => player.multiplierBought >= 2, group: 'multipliers' }, + { + pointValue: 10, + unlockCondition: () => player.multiplierBought >= 20, + group: 'multipliers', + reward: { multipliers: () => 1 } + }, + { pointValue: 15, unlockCondition: () => player.multiplierBought >= 100, group: 'multipliers' }, + { + pointValue: 20, + unlockCondition: () => player.multiplierBought >= 500, + group: 'multipliers', + reward: { multipliers: () => 1 } + }, + { + pointValue: 25, + unlockCondition: () => player.multiplierBought >= 2000, + group: 'multipliers', + reward: { multipliers: () => 3 } + }, + { + pointValue: 30, + unlockCondition: () => player.multiplierBought >= 12500, + group: 'multipliers', + reward: { multipliers: () => 6 } + }, + { + pointValue: 35, + unlockCondition: () => player.multiplierBought >= 100000, + group: 'multipliers', + reward: { multipliers: () => 10 } + }, + { pointValue: 5, unlockCondition: () => player.acceleratorBoostBought >= 2, group: 'acceleratorBoosts' }, + { pointValue: 10, unlockCondition: () => player.acceleratorBoostBought >= 10, group: 'acceleratorBoosts' }, + { pointValue: 15, unlockCondition: () => player.acceleratorBoostBought >= 50, group: 'acceleratorBoosts' }, + { pointValue: 20, unlockCondition: () => player.acceleratorBoostBought >= 200, group: 'acceleratorBoosts' }, + { pointValue: 25, unlockCondition: () => player.acceleratorBoostBought >= 1000, group: 'acceleratorBoosts' }, + { pointValue: 30, unlockCondition: () => player.acceleratorBoostBought >= 5000, group: 'acceleratorBoosts' }, + { pointValue: 35, unlockCondition: () => player.acceleratorBoostBought >= 15000, group: 'acceleratorBoosts' }, + { + pointValue: 5, + unlockCondition: () => player.antPoints.gte(3), + group: 'antCrumbs', + reward: { antSpeed: () => Decimal.log(player.antPoints.plus(10), 10) } + }, + { pointValue: 10, unlockCondition: () => player.antPoints.gte(1e5), group: 'antCrumbs' }, + { + pointValue: 15, + unlockCondition: () => player.antPoints.gte(666666666), + group: 'antCrumbs', + reward: { antSpeed: () => 1.2 } + }, + { + pointValue: 20, + unlockCondition: () => player.antPoints.gte(1e20), + group: 'antCrumbs', + reward: { antSpeed: () => 1.25 } + }, + { + pointValue: 25, + unlockCondition: () => player.antPoints.gte(1e40), + group: 'antCrumbs', + reward: { antSpeed: () => 1.4, antSacrificeUnlock: () => 1, antAutobuyers: () => 1 } + }, + { + pointValue: 30, + unlockCondition: () => player.antPoints.gte('1e500'), + group: 'antCrumbs', + reward: { antSpeed: () => 1 + Math.log10(player.antSacrificePoints + 1) } + }, + { pointValue: 35, unlockCondition: () => player.antPoints.gte('1e2500'), group: 'antCrumbs' }, + { + pointValue: 5, + unlockCondition: () => + antSacrificePointsToMultiplier(player.antSacrificePoints).gte(2) && player.secondOwnedAnts > 0, + group: 'sacMult', + reward: { antAutobuyers: () => 1, antUpgradeAutobuyers: () => 2 }, + checkReset: () => player.highestSingularityCount >= 10 + }, + { + pointValue: 10, + unlockCondition: () => + antSacrificePointsToMultiplier(player.antSacrificePoints).gte(6) && player.thirdOwnedAnts > 0, + group: 'sacMult', + reward: { antAutobuyers: () => 1, antUpgradeAutobuyers: () => 1 }, + checkReset: () => player.highestSingularityCount >= 10 + }, + { + pointValue: 15, + unlockCondition: () => + antSacrificePointsToMultiplier(player.antSacrificePoints).gte(20) && player.fourthOwnedAnts > 0, + group: 'sacMult', + reward: { antAutobuyers: () => 1, antUpgradeAutobuyers: () => 2 }, + checkReset: () => player.highestSingularityCount >= 10 + }, + { + pointValue: 20, + unlockCondition: () => + antSacrificePointsToMultiplier(player.antSacrificePoints).gte(100) && player.fifthOwnedAnts > 0, + group: 'sacMult', + reward: { antAutobuyers: () => 1, antUpgradeAutobuyers: () => 1 }, + checkReset: () => player.highestSingularityCount >= 10 + }, + { + pointValue: 25, + unlockCondition: () => + antSacrificePointsToMultiplier(player.antSacrificePoints).gte(500) && player.sixthOwnedAnts > 0, + group: 'sacMult', + reward: { antAutobuyers: () => 1, antUpgradeAutobuyers: () => 2 }, + checkReset: () => player.highestSingularityCount >= 10 + }, + { + pointValue: 30, + unlockCondition: () => + antSacrificePointsToMultiplier(player.antSacrificePoints).gte(6666) && player.seventhOwnedAnts > 0, + group: 'sacMult', + reward: { antAutobuyers: () => 1, antUpgradeAutobuyers: () => 1 }, + checkReset: () => player.highestSingularityCount >= 10 + }, + { + pointValue: 35, + unlockCondition: () => + antSacrificePointsToMultiplier(player.antSacrificePoints).gte(77777) && player.eighthOwnedAnts > 0, + group: 'sacMult', + reward: { antAutobuyers: () => 1, antUpgradeAutobuyers: () => 2 }, + checkReset: () => player.highestSingularityCount >= 10 + }, + { pointValue: 5, unlockCondition: () => player.ascensionCount >= 1, group: 'ascensionCount' }, + { pointValue: 10, unlockCondition: () => player.ascensionCount >= 2, group: 'ascensionCount' }, + { pointValue: 15, unlockCondition: () => player.ascensionCount >= 10, group: 'ascensionCount' }, + { + pointValue: 20, + unlockCondition: () => player.ascensionCount >= 100, + group: 'ascensionCount', + reward: { wowSquareTalisman: () => 1 } + }, + { + pointValue: 25, + unlockCondition: () => player.ascensionCount >= 1000, + group: 'ascensionCount', + reward: { + ascensionCountMultiplier: () => Math.log10(calculateAscensionScore().effectiveScore + 100) - 1 } - if (player.prestigenocoinupgrades) { - achievementaward(64) + }, + { + pointValue: 30, + unlockCondition: () => player.ascensionCount >= 14142, + group: 'ascensionCount', + reward: { + ascensionCountAdditive: () => (player.ascensionCounter > 10) ? 100 : 0 } - if (G.prestigePointGain.gte(1)) { - achievementaward(36) + }, + { + pointValue: 35, + unlockCondition: () => player.ascensionCount >= 141421, + group: 'ascensionCount', + reward: { + ascensionCountAdditive: () => (player.ascensionCounter > 10) ? player.ascensionCounterReal * 2 : 0, + wowCubeGain: () => 1 + 2 * Math.min(1, player.ascensionCount / 5e8) } - if (G.prestigePointGain.gte(1e6)) { - achievementaward(37) + }, + { pointValue: 5, unlockCondition: () => player.ascendShards.gte(3.14), group: 'constant' }, + { pointValue: 10, unlockCondition: () => player.ascendShards.gte(1e6), group: 'constant' }, + { pointValue: 15, unlockCondition: () => player.ascendShards.gte(4.32e10), group: 'constant' }, + { + pointValue: 20, + unlockCondition: () => player.ascendShards.gte(6.9e21), + group: 'constant', + reward: { wowCubeGain: () => 1 + Decimal.log(player.ascendShards.add(1), 10) / 400 } + }, + { pointValue: 25, unlockCondition: () => player.ascendShards.gte(1.509e33), group: 'constant' }, + { + pointValue: 30, + unlockCondition: () => player.ascendShards.gte(1e66), + group: 'constant', + reward: { + wowCubeGain: () => 1 + 249 * Math.min(1, Decimal.log(player.ascendShards.plus(1), 10) / 100000), + wowTesseractGain: () => 1 + 249 * Math.min(1, Decimal.log(player.ascendShards.plus(1), 10) / 100000) } - if (G.prestigePointGain.gte(1e100)) { - achievementaward(38) + }, + { + pointValue: 35, + unlockCondition: () => player.ascendShards.gte('1.8e308'), + group: 'constant', + reward: { wowPlatonicGain: () => 1 + 19 * Math.min(1, Decimal.log(player.ascendShards.plus(1), 10) / 100000) } + }, + { + pointValue: 10, + unlockCondition: () => player.challengecompletions[11] >= 1, + group: 'challenge11', + reward: { statTracker: () => 1 } + }, + { + pointValue: 20, + unlockCondition: () => player.challengecompletions[11] >= 2, + group: 'challenge11' + }, + { + pointValue: 30, + unlockCondition: () => player.challengecompletions[11] >= 3, + group: 'challenge11' + }, + { + pointValue: 40, + unlockCondition: () => player.challengecompletions[11] >= 5, + group: 'challenge11' + }, + { + pointValue: 50, + unlockCondition: () => player.challengecompletions[11] >= 10, + group: 'challenge11' + }, + { + pointValue: 60, + unlockCondition: () => player.challengecompletions[11] >= 20, + group: 'challenge11', + reward: { ascensionCountAdditive: () => player.ascensionCounter * 2 } + }, + { + pointValue: 70, + unlockCondition: () => player.challengecompletions[11] >= 30, + group: 'challenge11', + reward: { talismanPower: () => 0.01 } + }, + { + pointValue: 10, + unlockCondition: () => player.challengecompletions[12] >= 1, + group: 'challenge12', + reward: { ascensionRewardScaling: () => 1 } + }, + { + pointValue: 20, + unlockCondition: () => player.challengecompletions[12] >= 2, + group: 'challenge12' + }, + { + pointValue: 30, + unlockCondition: () => player.challengecompletions[12] >= 3, + group: 'challenge12' + }, + { + pointValue: 40, + unlockCondition: () => player.challengecompletions[12] >= 5, + group: 'challenge12' + }, + { + pointValue: 50, + unlockCondition: () => player.challengecompletions[12] >= 10, + group: 'challenge12' + }, + { + pointValue: 60, + unlockCondition: () => player.challengecompletions[12] >= 20, + group: 'challenge12', + reward: { ascensionCountAdditive: () => player.ascensionCounter * 2 } + }, + { + pointValue: 70, + unlockCondition: () => player.challengecompletions[12] >= 30, + group: 'challenge12', + reward: { talismanPower: () => 0.01 } + }, + { + pointValue: 10, + unlockCondition: () => player.challengecompletions[13] >= 1, + group: 'challenge13' + }, + { + pointValue: 20, + unlockCondition: () => player.challengecompletions[13] >= 2, + group: 'challenge13' + }, + { + pointValue: 30, + unlockCondition: () => player.challengecompletions[13] >= 3, + group: 'challenge13' + }, + { + pointValue: 40, + unlockCondition: () => player.challengecompletions[13] >= 5, + group: 'challenge13' + }, + { + pointValue: 50, + unlockCondition: () => player.challengecompletions[13] >= 10, + group: 'challenge13' + }, + { + pointValue: 60, + unlockCondition: () => player.challengecompletions[13] >= 20, + group: 'challenge13', + reward: { ascensionCountAdditive: () => player.ascensionCounter * 2 } + }, + { + pointValue: 70, + unlockCondition: () => player.challengecompletions[13] >= 30, + group: 'challenge13', + reward: { talismanPower: () => 0.01 } + }, + { + pointValue: 10, + unlockCondition: () => player.challengecompletions[14] >= 1, + group: 'challenge14' + }, + { + pointValue: 20, + unlockCondition: () => player.challengecompletions[14] >= 2, + group: 'challenge14' + }, + { + pointValue: 30, + unlockCondition: () => player.challengecompletions[14] >= 3, + group: 'challenge14' + }, + { + pointValue: 40, + unlockCondition: () => player.challengecompletions[14] >= 5, + group: 'challenge14' + }, + { + pointValue: 50, + unlockCondition: () => player.challengecompletions[14] >= 10, + group: 'challenge14' + }, + { + pointValue: 60, + unlockCondition: () => player.challengecompletions[14] >= 20, + group: 'challenge14', + reward: { + ascensionCountAdditive: () => player.ascensionCounter * 2, + wowPlatonicGain: () => 1 + 2 * Math.min(1, player.ascensionCount / 2.674e9) } - if (G.prestigePointGain.gte('1e1000')) { - achievementaward(39) + }, + { pointValue: 70, unlockCondition: () => player.challengecompletions[14] >= 30, group: 'challenge14' }, + { pointValue: 5, unlockCondition: () => CalcCorruptionStuff()[3] >= 1e5, group: 'ascensionScore' }, + { pointValue: 10, unlockCondition: () => CalcCorruptionStuff()[3] >= 1e6, group: 'ascensionScore' }, + { pointValue: 15, unlockCondition: () => CalcCorruptionStuff()[3] >= 1e7, group: 'ascensionScore' }, + { pointValue: 20, unlockCondition: () => CalcCorruptionStuff()[3] >= 1e8, group: 'ascensionScore' }, + { pointValue: 25, unlockCondition: () => CalcCorruptionStuff()[3] >= 1e9, group: 'ascensionScore' }, + { pointValue: 30, unlockCondition: () => CalcCorruptionStuff()[3] >= 5e9, group: 'ascensionScore' }, + { pointValue: 35, unlockCondition: () => CalcCorruptionStuff()[3] >= 2.5e10, group: 'ascensionScore' }, + { pointValue: 10, unlockCondition: () => runeBlessings.speed.level >= 20, group: 'speedBlessing' }, + { + pointValue: 20, + unlockCondition: () => runeBlessings.speed.level >= 40, + group: 'speedBlessing' + }, + { pointValue: 30, unlockCondition: () => runeBlessings.speed.level >= 80, group: 'speedBlessing' }, + { + pointValue: 10, + unlockCondition: () => runeSpirits.speed.level >= 20, + group: 'speedSpirit' + }, + { pointValue: 20, unlockCondition: () => runeSpirits.speed.level >= 40, group: 'speedSpirit' }, + { + pointValue: 30, + unlockCondition: () => runeSpirits.speed.level >= 80, + group: 'speedSpirit' + }, + { + pointValue: 50, + unlockCondition: () => { + return player.currentChallenge.transcension !== 0 && player.currentChallenge.reincarnation !== 0 + && player.currentChallenge.ascension !== 0 + }, + group: 'ungrouped' + }, + { pointValue: 50, unlockCondition: () => player.mythicalFragments.gte(1e25), group: 'ungrouped' }, + { + pointValue: 50, + unlockCondition: () => player.ascensionCount >= 1414213, + group: 'ungrouped' + }, + // 241: Global speed is SLOW + { pointValue: 50, unlockCondition: () => true, group: 'ungrouped' }, + // 242: Global speed is FAST + { pointValue: 50, unlockCondition: () => true, group: 'ungrouped' }, + // 243: :unsmith: + { pointValue: 50, unlockCondition: () => true, group: 'ungrouped' }, + // 244: :smith: + { pointValue: 50, unlockCondition: () => true, group: 'ungrouped' }, + // 245: High Speed Blessing + { + pointValue: 50, + unlockCondition: () => runeBlessings.speed.level >= 380, + group: 'ungrouped' + }, + // 246: Open 1 cube with a ton of cube tributes already + { pointValue: 50, unlockCondition: () => true, group: 'ungrouped' }, + // 247: Extra challenging + { pointValue: 50, unlockCondition: () => true, group: 'ungrouped' }, + // 248: Seeing Red But Not Blue + { pointValue: 50, unlockCondition: () => true, group: 'ungrouped' }, + // 249: Overtaxed + { pointValue: 50, unlockCondition: () => true, group: 'ungrouped' }, + // 250: Thousand Suns + { + pointValue: 100, + unlockCondition: () => player.researches[200] === 1e5, + group: 'ungrouped', + reward: { + quarkGain: () => 1.05 } - if (G.prestigePointGain.gte('1e10000')) { - achievementaward(40) + }, + // 251: Thousand Moons + { + pointValue: 150, + unlockCondition: () => player.cubeUpgrades[50] === 1e5, + group: 'ungrouped', + reward: { + quarkGain: () => 1.05 } - if (G.prestigePointGain.gte('1e77777')) { - achievementaward(41) + }, + // 252: Sadistic II + { + pointValue: 50, + unlockCondition: () => G.challenge15Rewards.achievementUnlock.value === 1, + group: 'ungrouped' + }, + { + pointValue: 40, + unlockCondition: () => CalcCorruptionStuff()[3] >= 1e12, + group: 'ascensionScore', + reward: { wowHypercubeGain: () => 1.1 } + }, + { + pointValue: 45, + unlockCondition: () => CalcCorruptionStuff()[3] >= 1e14, + group: 'ascensionScore', + reward: { wowCubeGain: () => 1.1 } + }, + { + pointValue: 50, + unlockCondition: () => CalcCorruptionStuff()[3] >= 1e17, + group: 'ascensionScore', + reward: { wowTesseractGain: () => 1.1 } + }, + { + pointValue: 55, + unlockCondition: () => CalcCorruptionStuff()[3] >= 2e18, + group: 'ascensionScore', + reward: { wowPlatonicGain: () => 1.1, overfluxConversionRate: () => 1.05 } + }, + { + pointValue: 60, + unlockCondition: () => CalcCorruptionStuff()[3] >= 4e19, + group: 'ascensionScore', + reward: { overfluxConversionRate: () => 1.05 } + }, + { + pointValue: 65, + unlockCondition: () => CalcCorruptionStuff()[3] >= 1e21, + group: 'ascensionScore', + reward: { wowHepteractGain: () => 1.1 } + }, + { + pointValue: 70, + unlockCondition: () => CalcCorruptionStuff()[3] >= 1e23, + group: 'ascensionScore', + reward: { ascensionScore: () => Math.pow(1.01, hepteracts.abyss.TIMES_CAP_EXTENDED) } + }, + { + pointValue: 40, + unlockCondition: () => player.ascensionCount >= 1e7, + group: 'ascensionCount', + reward: { ascensionCountMultiplier: () => 1.1 } + }, + { + pointValue: 45, + unlockCondition: () => player.ascensionCount >= 1e8, + group: 'ascensionCount', + reward: { ascensionCountMultiplier: () => 1.1 } + }, + { + pointValue: 50, + unlockCondition: () => player.ascensionCount >= 2e9, + group: 'ascensionCount' + }, + { + pointValue: 55, + unlockCondition: () => player.ascensionCount >= 4e10, + group: 'ascensionCount' + }, + { + pointValue: 60, + unlockCondition: () => player.ascensionCount >= 8e11, + group: 'ascensionCount' + }, + { + pointValue: 65, + unlockCondition: () => player.ascensionCount >= 1.6e13, + group: 'ascensionCount' + }, + { + pointValue: 70, + unlockCondition: () => player.ascensionCount >= 1e14, + group: 'ascensionCount', + reward: { quarkGain: () => 1 + 0.1 * Math.min(player.ascensionCount / 1e15, 1) } + }, + { + pointValue: 40, + unlockCondition: () => player.ascendShards.gte('1e1000'), + group: 'constant', + reward: { ascensionScore: () => 1 + Math.min(Decimal.log(player.ascendShards.add(1), 10) / 1e5, 1) } + }, + { pointValue: 45, unlockCondition: () => player.ascendShards.gte('1e5000'), group: 'constant' }, + { pointValue: 50, unlockCondition: () => player.ascendShards.gte('1e15000'), group: 'constant' }, + { + pointValue: 55, + unlockCondition: () => player.ascendShards.gte('1e50000'), + group: 'constant', + reward: { + wowHepteractGain: () => 1 + Math.min(Decimal.log(player.ascendShards.add(1), 10) / 1e6, 1), + constUpgrade1Buff: () => 0.01, + constUpgrade2Buff: () => 0.01 } - if (G.prestigePointGain.gte('1e250000')) { - achievementaward(42) + }, + { + pointValue: 60, + unlockCondition: () => player.ascendShards.gte('1e100000'), + group: 'constant', + reward: { platonicToHypercubes: () => Math.min(1, Decimal.log(player.ascendShards.add(1), 10) / 1e6) } + }, + { pointValue: 65, unlockCondition: () => player.ascendShards.gte('1e300000'), group: 'constant' }, + { pointValue: 70, unlockCondition: () => player.ascendShards.gte('1e1000000'), group: 'constant' }, + { pointValue: 10, unlockCondition: () => player.highestSingularityCount >= 1, group: 'singularityCount' }, + { pointValue: 20, unlockCondition: () => player.highestSingularityCount >= 2, group: 'singularityCount' }, + { pointValue: 30, unlockCondition: () => player.highestSingularityCount >= 3, group: 'singularityCount' }, + { pointValue: 40, unlockCondition: () => player.highestSingularityCount >= 4, group: 'singularityCount' }, + { pointValue: 50, unlockCondition: () => player.highestSingularityCount >= 5, group: 'singularityCount' }, + { pointValue: 60, unlockCondition: () => player.highestSingularityCount >= 7, group: 'singularityCount' }, + { pointValue: 70, unlockCondition: () => player.highestSingularityCount >= 10, group: 'singularityCount' }, + { pointValue: 40, unlockCondition: () => player.firstOwnedCoin >= 1e5, group: 'firstOwnedCoin' }, + { pointValue: 45, unlockCondition: () => player.firstOwnedCoin >= 1e6, group: 'firstOwnedCoin' }, + { pointValue: 50, unlockCondition: () => player.firstOwnedCoin >= 1e8, group: 'firstOwnedCoin' }, + { pointValue: 40, unlockCondition: () => player.secondOwnedCoin >= 1e6, group: 'secondOwnedCoin' }, + { pointValue: 45, unlockCondition: () => player.secondOwnedCoin >= 1e8, group: 'secondOwnedCoin' }, + { pointValue: 50, unlockCondition: () => player.secondOwnedCoin >= 1e9, group: 'secondOwnedCoin' }, + { pointValue: 40, unlockCondition: () => player.thirdOwnedCoin >= 1e7, group: 'thirdOwnedCoin' }, + { pointValue: 45, unlockCondition: () => player.thirdOwnedCoin >= 1e8, group: 'thirdOwnedCoin' }, + { pointValue: 50, unlockCondition: () => player.thirdOwnedCoin >= 5e9, group: 'thirdOwnedCoin' }, + { pointValue: 40, unlockCondition: () => player.fourthOwnedCoin >= 1e8, group: 'fourthOwnedCoin' }, + { pointValue: 45, unlockCondition: () => player.fourthOwnedCoin >= 1e9, group: 'fourthOwnedCoin' }, + { pointValue: 50, unlockCondition: () => player.fourthOwnedCoin >= 2e10, group: 'fourthOwnedCoin' }, + { pointValue: 40, unlockCondition: () => player.fifthOwnedCoin >= 1e9, group: 'fifthOwnedCoin' }, + { pointValue: 45, unlockCondition: () => player.fifthOwnedCoin >= 2e10, group: 'fifthOwnedCoin' }, + { pointValue: 50, unlockCondition: () => player.fifthOwnedCoin >= 1e12, group: 'fifthOwnedCoin' }, + { pointValue: 40, unlockCondition: () => G.prestigePointGain.gte('1e10000000'), group: 'prestigePointGain' }, + { pointValue: 45, unlockCondition: () => G.prestigePointGain.gte('1e10000000000'), group: 'prestigePointGain' }, + { + pointValue: 50, + unlockCondition: () => G.prestigePointGain.gte('1e10000000000000'), + group: 'prestigePointGain' + }, + { pointValue: 40, unlockCondition: () => G.transcendPointGain.gte('1e2500000'), group: 'transcendPointGain' }, + { pointValue: 45, unlockCondition: () => G.transcendPointGain.gte('1e2500000000'), group: 'transcendPointGain' }, + { + pointValue: 50, + unlockCondition: () => G.transcendPointGain.gte('1e2500000000000'), + group: 'transcendPointGain' + }, + { + pointValue: 40, + unlockCondition: () => G.reincarnationPointGain.gte('1e100000'), + group: 'reincarnationPointGain' + }, + { + pointValue: 45, + unlockCondition: () => G.reincarnationPointGain.gte('1e100000000'), + group: 'reincarnationPointGain' + }, + { + pointValue: 50, + unlockCondition: () => G.reincarnationPointGain.gte('1e100000000000'), + group: 'reincarnationPointGain' + }, + { pointValue: 40, unlockCondition: () => player.challengecompletions[1] >= 1000, group: 'challenge1' }, + { pointValue: 45, unlockCondition: () => player.challengecompletions[1] >= 9000, group: 'challenge1' }, + { pointValue: 50, unlockCondition: () => player.challengecompletions[1] >= 9001, group: 'challenge1' }, + { pointValue: 40, unlockCondition: () => player.challengecompletions[2] >= 1000, group: 'challenge2' }, + { pointValue: 45, unlockCondition: () => player.challengecompletions[2] >= 9000, group: 'challenge2' }, + { pointValue: 50, unlockCondition: () => player.challengecompletions[2] >= 9001, group: 'challenge2' }, + { pointValue: 40, unlockCondition: () => player.challengecompletions[3] >= 1000, group: 'challenge3' }, + { pointValue: 45, unlockCondition: () => player.challengecompletions[3] >= 9000, group: 'challenge3' }, + { pointValue: 50, unlockCondition: () => player.challengecompletions[3] >= 9001, group: 'challenge3' }, + { pointValue: 40, unlockCondition: () => player.challengecompletions[4] >= 1000, group: 'challenge4' }, + { pointValue: 45, unlockCondition: () => player.challengecompletions[4] >= 9000, group: 'challenge4' }, + { pointValue: 50, unlockCondition: () => player.challengecompletions[4] >= 9001, group: 'challenge4' }, + { pointValue: 40, unlockCondition: () => player.challengecompletions[5] >= 1000, group: 'challenge5' }, + { pointValue: 45, unlockCondition: () => player.challengecompletions[5] >= 9000, group: 'challenge5' }, + { pointValue: 50, unlockCondition: () => player.challengecompletions[5] >= 9001, group: 'challenge5' }, + { pointValue: 40, unlockCondition: () => player.challengecompletions[6] >= 40, group: 'challenge6' }, + { pointValue: 45, unlockCondition: () => player.challengecompletions[6] >= 80, group: 'challenge6' }, + { pointValue: 50, unlockCondition: () => player.challengecompletions[6] >= 120, group: 'challenge6' }, + { pointValue: 40, unlockCondition: () => player.challengecompletions[7] >= 40, group: 'challenge7' }, + { pointValue: 45, unlockCondition: () => player.challengecompletions[7] >= 80, group: 'challenge7' }, + { pointValue: 50, unlockCondition: () => player.challengecompletions[7] >= 125, group: 'challenge7' }, + { pointValue: 40, unlockCondition: () => player.challengecompletions[8] >= 40, group: 'challenge8' }, + { pointValue: 45, unlockCondition: () => player.challengecompletions[8] >= 80, group: 'challenge8' }, + { pointValue: 50, unlockCondition: () => player.challengecompletions[8] >= 130, group: 'challenge8' }, + { pointValue: 40, unlockCondition: () => player.challengecompletions[9] >= 40, group: 'challenge9' }, + { pointValue: 45, unlockCondition: () => player.challengecompletions[9] >= 80, group: 'challenge9' }, + { pointValue: 50, unlockCondition: () => player.challengecompletions[9] >= 135, group: 'challenge9' }, + { pointValue: 40, unlockCondition: () => player.challengecompletions[10] >= 40, group: 'challenge10' }, + { pointValue: 45, unlockCondition: () => player.challengecompletions[10] >= 80, group: 'challenge10' }, + { pointValue: 50, unlockCondition: () => player.challengecompletions[10] >= 140, group: 'challenge10' }, + { pointValue: 40, unlockCondition: () => player.acceleratorBought >= 1e6, group: 'accelerators' }, + { pointValue: 45, unlockCondition: () => player.acceleratorBought >= 1e7, group: 'accelerators' }, + { pointValue: 50, unlockCondition: () => player.acceleratorBought >= 1e8, group: 'accelerators' }, + { pointValue: 40, unlockCondition: () => player.multiplierBought >= 3e6, group: 'multipliers' }, + { pointValue: 45, unlockCondition: () => player.multiplierBought >= 3e7, group: 'multipliers' }, + { pointValue: 50, unlockCondition: () => player.multiplierBought >= 3e8, group: 'multipliers' }, + { pointValue: 40, unlockCondition: () => player.acceleratorBoostBought >= 1e5, group: 'acceleratorBoosts' }, + { pointValue: 45, unlockCondition: () => player.acceleratorBoostBought >= 1e6, group: 'acceleratorBoosts' }, + { pointValue: 50, unlockCondition: () => player.acceleratorBoostBought >= 1e7, group: 'acceleratorBoosts' }, + { pointValue: 40, unlockCondition: () => player.antPoints.gte('1e25000'), group: 'antCrumbs' }, + { pointValue: 45, unlockCondition: () => player.antPoints.gte('1e125000'), group: 'antCrumbs' }, + { pointValue: 50, unlockCondition: () => player.antPoints.gte('1e1000000'), group: 'antCrumbs' }, + { + pointValue: 40, + unlockCondition: () => antSacrificePointsToMultiplier(player.antSacrificePoints).gte(1e40), + group: 'sacMult' + }, + { + pointValue: 45, + unlockCondition: () => antSacrificePointsToMultiplier(player.antSacrificePoints).gte(1e200), + group: 'sacMult' + }, + { + pointValue: 50, + unlockCondition: () => antSacrificePointsToMultiplier(player.antSacrificePoints).gte('1e1000'), + group: 'sacMult' + }, + { pointValue: 75, unlockCondition: () => player.ascensionCount >= 1e16, group: 'ascensionCount' }, + { pointValue: 80, unlockCondition: () => player.ascensionCount >= 1e20, group: 'ascensionCount' }, + { pointValue: 85, unlockCondition: () => player.ascensionCount >= 1e25, group: 'ascensionCount' }, + { pointValue: 90, unlockCondition: () => player.ascensionCount >= 1e35, group: 'ascensionCount' }, + { pointValue: 95, unlockCondition: () => player.ascensionCount >= 1e50, group: 'ascensionCount' }, + { pointValue: 100, unlockCondition: () => player.ascensionCount >= 1e75, group: 'ascensionCount' }, + { pointValue: 75, unlockCondition: () => player.ascendShards.gte('1e2000000'), group: 'constant' }, + { pointValue: 80, unlockCondition: () => player.ascendShards.gte('1e5000000'), group: 'constant' }, + { pointValue: 85, unlockCondition: () => player.ascendShards.gte('1e10000000'), group: 'constant' }, + { pointValue: 90, unlockCondition: () => player.ascendShards.gte('1e25000000'), group: 'constant' }, + { pointValue: 95, unlockCondition: () => player.ascendShards.gte('1e50000000'), group: 'constant' }, + { pointValue: 100, unlockCondition: () => player.ascendShards.gte('1e100000000'), group: 'constant' }, + { pointValue: 80, unlockCondition: () => player.challengecompletions[11] >= 40, group: 'challenge11' }, + { pointValue: 90, unlockCondition: () => player.challengecompletions[11] >= 50, group: 'challenge11' }, + { pointValue: 100, unlockCondition: () => player.challengecompletions[11] >= 60, group: 'challenge11' }, + { pointValue: 110, unlockCondition: () => player.challengecompletions[11] >= 65, group: 'challenge11' }, + { pointValue: 120, unlockCondition: () => player.challengecompletions[11] >= 70, group: 'challenge11' }, + { pointValue: 80, unlockCondition: () => player.challengecompletions[12] >= 40, group: 'challenge12' }, + { pointValue: 90, unlockCondition: () => player.challengecompletions[12] >= 50, group: 'challenge12' }, + { pointValue: 100, unlockCondition: () => player.challengecompletions[12] >= 60, group: 'challenge12' }, + { pointValue: 110, unlockCondition: () => player.challengecompletions[12] >= 65, group: 'challenge12' }, + { pointValue: 120, unlockCondition: () => player.challengecompletions[12] >= 70, group: 'challenge12' }, + { pointValue: 80, unlockCondition: () => player.challengecompletions[13] >= 40, group: 'challenge13' }, + { pointValue: 90, unlockCondition: () => player.challengecompletions[13] >= 50, group: 'challenge13' }, + { pointValue: 100, unlockCondition: () => player.challengecompletions[13] >= 60, group: 'challenge13' }, + { pointValue: 110, unlockCondition: () => player.challengecompletions[13] >= 70, group: 'challenge13' }, + { pointValue: 120, unlockCondition: () => player.challengecompletions[13] >= 72, group: 'challenge13' }, + { pointValue: 80, unlockCondition: () => player.challengecompletions[14] >= 40, group: 'challenge14' }, + { pointValue: 90, unlockCondition: () => player.challengecompletions[14] >= 50, group: 'challenge14' }, + { pointValue: 100, unlockCondition: () => player.challengecompletions[14] >= 60, group: 'challenge14' }, + { pointValue: 110, unlockCondition: () => player.challengecompletions[14] >= 70, group: 'challenge14' }, + { pointValue: 120, unlockCondition: () => player.challengecompletions[14] >= 72, group: 'challenge14' }, + { + pointValue: 40, + unlockCondition: () => runeBlessings.speed.level >= 200, + group: 'speedBlessing' + }, + { pointValue: 50, unlockCondition: () => runeBlessings.speed.level >= 400, group: 'speedBlessing' }, + { + pointValue: 60, + unlockCondition: () => runeBlessings.speed.level >= 800, + group: 'speedBlessing' + }, + { pointValue: 70, unlockCondition: () => runeBlessings.speed.level >= 1000, group: 'speedBlessing' }, + { + pointValue: 80, + unlockCondition: () => runeBlessings.speed.level >= 1200, + group: 'speedBlessing' + }, + { pointValue: 90, unlockCondition: () => runeBlessings.speed.level >= 1500, group: 'speedBlessing' }, + { + pointValue: 100, + unlockCondition: () => runeBlessings.speed.level >= 2000, + group: 'speedBlessing' + }, + { pointValue: 40, unlockCondition: () => runeSpirits.speed.level >= 160, group: 'speedSpirit' }, + { + pointValue: 50, + unlockCondition: () => runeSpirits.speed.level >= 320, + group: 'speedSpirit' + }, + { pointValue: 60, unlockCondition: () => runeSpirits.speed.level >= 640, group: 'speedSpirit' }, + { + pointValue: 70, + unlockCondition: () => runeSpirits.speed.level >= 960, + group: 'speedSpirit' + }, + { pointValue: 80, unlockCondition: () => runeSpirits.speed.level >= 1280, group: 'speedSpirit' }, + { + pointValue: 90, + unlockCondition: () => runeSpirits.speed.level >= 1600, + group: 'speedSpirit' + }, + { pointValue: 100, unlockCondition: () => runeSpirits.speed.level >= 2000, group: 'speedSpirit' }, + { + pointValue: 2, + unlockCondition: () => runes.speed.level >= 100, + group: 'runeLevel' + }, + { pointValue: 4, unlockCondition: () => runes.speed.level >= 250, group: 'runeLevel' }, + { + pointValue: 6, + unlockCondition: () => runes.speed.level >= 500, + group: 'runeLevel' + }, + { pointValue: 8, unlockCondition: () => runes.speed.level >= 1000, group: 'runeLevel' }, + { + pointValue: 10, + unlockCondition: () => runes.speed.level >= 2000, + group: 'runeLevel' + }, + { pointValue: 12, unlockCondition: () => runes.speed.level >= 5000, group: 'runeLevel' }, + { + pointValue: 14, + unlockCondition: () => runes.speed.level >= 10000, + group: 'runeLevel' + }, + { pointValue: 16, unlockCondition: () => runes.speed.level >= 20000, group: 'runeLevel' }, + { + pointValue: 18, + unlockCondition: () => runes.speed.level >= 50000, + group: 'runeLevel' + }, + { pointValue: 20, unlockCondition: () => runes.speed.level >= 100000, group: 'runeLevel' }, + { + pointValue: 22, + unlockCondition: () => runes.speed.level >= 200000, + group: 'runeLevel' + }, + { + pointValue: 24, + unlockCondition: () => runes.speed.level >= 300000, + group: 'runeLevel' + }, + { + pointValue: 26, + unlockCondition: () => runes.speed.level >= 500000, + group: 'runeLevel' + }, + { + pointValue: 28, + unlockCondition: () => runes.speed.level >= 750000, + group: 'runeLevel' + }, + { + pointValue: 30, + unlockCondition: () => runes.speed.level >= 1000000, + group: 'runeLevel' + }, + { + pointValue: 2, + unlockCondition: () => runes.speed.freeLevels() >= 10, + group: 'runeFreeLevel' + }, + { pointValue: 4, unlockCondition: () => runes.speed.freeLevels() >= 40, group: 'runeFreeLevel' }, + { + pointValue: 6, + unlockCondition: () => runes.speed.freeLevels() >= 125, + group: 'runeFreeLevel' + }, + { pointValue: 8, unlockCondition: () => runes.speed.freeLevels() >= 250, group: 'runeFreeLevel' }, + { + pointValue: 10, + unlockCondition: () => runes.speed.freeLevels() >= 500, + group: 'runeFreeLevel' + }, + { pointValue: 12, unlockCondition: () => runes.speed.freeLevels() >= 1000, group: 'runeFreeLevel' }, + { + pointValue: 14, + unlockCondition: () => runes.speed.freeLevels() >= 2000, + group: 'runeFreeLevel' + }, + { pointValue: 16, unlockCondition: () => runes.speed.freeLevels() >= 4000, group: 'runeFreeLevel' }, + { + pointValue: 18, + unlockCondition: () => runes.speed.freeLevels() >= 7500, + group: 'runeFreeLevel' + }, + { pointValue: 20, unlockCondition: () => runes.speed.freeLevels() >= 12500, group: 'runeFreeLevel' }, + { + pointValue: 22, + unlockCondition: () => runes.speed.freeLevels() >= 25000, + group: 'runeFreeLevel' + }, + { pointValue: 24, unlockCondition: () => runes.speed.freeLevels() >= 37500, group: 'runeFreeLevel' }, + { + pointValue: 26, + unlockCondition: () => runes.speed.freeLevels() >= 50000, + group: 'runeFreeLevel' + }, + { pointValue: 28, unlockCondition: () => runes.speed.freeLevels() >= 75000, group: 'runeFreeLevel' }, + { + pointValue: 30, + unlockCondition: () => runes.speed.freeLevels() >= 100000, + group: 'runeFreeLevel' + }, + { + pointValue: 5, + unlockCondition: () => campaignTokens >= 10, + group: 'campaignTokens' + }, + { + pointValue: 10, + unlockCondition: () => campaignTokens >= 20, + group: 'campaignTokens' + }, + { + pointValue: 15, + unlockCondition: () => campaignTokens >= 40, + group: 'campaignTokens' + }, + { + pointValue: 20, + unlockCondition: () => campaignTokens >= 80, + group: 'campaignTokens' + }, + { + pointValue: 25, + unlockCondition: () => campaignTokens >= 160, + group: 'campaignTokens' + }, + { + pointValue: 30, + unlockCondition: () => campaignTokens >= 320, + group: 'campaignTokens' + }, + { + pointValue: 35, + unlockCondition: () => campaignTokens >= 1000, + group: 'campaignTokens' + }, + { + pointValue: 40, + unlockCondition: () => campaignTokens >= 2000, + group: 'campaignTokens' + }, + { + pointValue: 45, + unlockCondition: () => campaignTokens >= 4000, + group: 'campaignTokens' + }, + { + pointValue: 50, + unlockCondition: () => campaignTokens >= 9000, + group: 'campaignTokens' + }, + { + pointValue: 2, + unlockCondition: () => player.prestigeCount >= 1, + group: 'prestigeCount' + }, + { + pointValue: 4, + unlockCondition: () => player.prestigeCount >= 10, + group: 'prestigeCount', + reward: { prestigeCountMultiplier: () => Math.max(1, 1 + Math.floor(Math.log10(player.prestigeCount))) } + }, + { + pointValue: 6, + unlockCondition: () => player.prestigeCount >= 100, + group: 'prestigeCount', + reward: { duplicationRuneUnlock: () => 1 } + }, + { + pointValue: 8, + unlockCondition: () => player.prestigeCount >= 1_000, + group: 'prestigeCount', + reward: { offeringBonus: () => 1 + 0.02 * Math.max(1, 1 + Math.floor(Math.log10(player.prestigeCount))) } + }, + { + pointValue: 10, + unlockCondition: () => player.prestigeCount >= 10_000, + group: 'prestigeCount' + }, + { + pointValue: 12, + unlockCondition: () => player.prestigeCount >= 100_000, + group: 'prestigeCount' + }, + { + pointValue: 14, + unlockCondition: () => player.prestigeCount >= 1_000_000, + group: 'prestigeCount', + reward: { transcendToPrestige: () => 1 } + }, + { + pointValue: 16, + unlockCondition: () => player.prestigeCount >= 10_000_000, + group: 'prestigeCount' + }, + { + pointValue: 18, + unlockCondition: () => player.prestigeCount >= 100_000_000, + group: 'prestigeCount', + reward: { transcensionCountMultiplier: () => Math.min(4, 1.25 + 2.75 * Math.floor(player.prestigecounter / 10)) } + }, + { + pointValue: 20, + unlockCondition: () => player.prestigeCount >= 1_000_000_000, + group: 'prestigeCount' + }, + { + pointValue: 22, + unlockCondition: () => player.prestigeCount >= 100_000_000_000, + group: 'prestigeCount' + }, + { + pointValue: 24, + unlockCondition: () => player.prestigeCount >= 10_000_000_000_000, + group: 'prestigeCount' + }, + { + pointValue: 26, + unlockCondition: () => player.prestigeCount >= 1e15, + group: 'prestigeCount' + }, + { + pointValue: 28, + unlockCondition: () => player.prestigeCount >= 1e17, + group: 'prestigeCount' + }, + { + pointValue: 30, + unlockCondition: () => player.prestigeCount >= 1e20, + group: 'prestigeCount' + }, + { + pointValue: 3, + unlockCondition: () => player.transcendCount >= 1, + group: 'transcensionCount' + }, + { + pointValue: 6, + unlockCondition: () => player.transcendCount >= 10, + group: 'transcensionCount', + reward: { transcensionCountMultiplier: () => Math.max(1, 1 + Math.floor(Math.log10(player.transcendCount))) } + }, + { + pointValue: 9, + unlockCondition: () => player.transcendCount >= 100, + group: 'transcensionCount', + reward: { salvage: () => 2 * Math.max(1, 1 + Math.floor(Math.log10(player.transcendCount))) } + }, + { + pointValue: 12, + unlockCondition: () => player.transcendCount >= 1_000, + group: 'transcensionCount', + reward: { prismRuneUnlock: () => 1 } + }, + { + pointValue: 15, + unlockCondition: () => player.transcendCount >= 10_000, + group: 'transcensionCount' + }, + { + pointValue: 18, + unlockCondition: () => player.transcendCount >= 100_000, + group: 'transcensionCount' + }, + { + pointValue: 21, + unlockCondition: () => player.transcendCount >= 1_000_000, + group: 'transcensionCount', + reward: { reincarnationToTranscend: () => 1 } + }, + { + pointValue: 24, + unlockCondition: () => player.transcendCount >= 10_000_000, + group: 'transcensionCount' + }, + { + pointValue: 27, + unlockCondition: () => player.transcendCount >= 100_000_000, + group: 'transcensionCount', + reward: { reincarnationCountMultiplier: () => Math.min(4, 1.25 + 2.75 * Math.floor(player.prestigecounter / 1000)) } + }, + { + pointValue: 30, + unlockCondition: () => player.transcendCount >= 1_000_000_000, + group: 'transcensionCount' + }, + { + pointValue: 33, + unlockCondition: () => player.transcendCount >= 30_000_000_000, + group: 'transcensionCount' + }, + { + pointValue: 36, + unlockCondition: () => player.transcendCount >= 900_000_000_000, + group: 'transcensionCount' + }, + { + pointValue: 39, + unlockCondition: () => player.transcendCount >= 27_000_000_000_000, + group: 'transcensionCount' + }, + { + pointValue: 42, + unlockCondition: () => player.transcendCount >= 810_000_000_000_000, + group: 'transcensionCount' + }, + { + pointValue: 45, + unlockCondition: () => player.transcendCount >= 1e17, + group: 'transcensionCount' + }, + { + pointValue: 4, + unlockCondition: () => player.reincarnationCount >= 1, + group: 'reincarnationCount' + }, + { + pointValue: 8, + unlockCondition: () => player.reincarnationCount >= 10, + group: 'reincarnationCount', + reward: { reincarnationCountMultiplier: () => Math.max(1, 1 + Math.floor(Math.log10(player.reincarnationCount))) } + }, + { + pointValue: 12, + unlockCondition: () => player.reincarnationCount >= 100, + group: 'reincarnationCount', + reward: { obtainiumBonus: () => 1 + 0.02 * Math.max(1, 1 + Math.floor(Math.log10(player.reincarnationCount))) } + }, + { + pointValue: 16, + unlockCondition: () => player.reincarnationCount >= 1_000, + group: 'reincarnationCount' + }, + { + pointValue: 20, + unlockCondition: () => player.reincarnationCount >= 10_000, + group: 'reincarnationCount', + reward: { thriftRuneUnlock: () => 1 } + }, + { + pointValue: 24, + unlockCondition: () => player.reincarnationCount >= 100_000, + group: 'reincarnationCount' + }, + { + pointValue: 28, + unlockCondition: () => player.reincarnationCount >= 1_000_000, + group: 'reincarnationCount' + }, + { + pointValue: 32, + unlockCondition: () => player.reincarnationCount >= 10_000_000, + group: 'reincarnationCount' + }, + { + pointValue: 36, + unlockCondition: () => player.reincarnationCount >= 100_000_000, + group: 'reincarnationCount', + reward: { + prestigeCountMultiplier: () => Math.min(4, 1.25 + 2.75 * Math.floor(player.prestigecounter / 1e6)), + ascensionCountMultiplier: () => Math.min(1.25, 1 + 0.25 * Math.floor(player.ascensionCounter / 1e6)) } + }, + { + pointValue: 40, + unlockCondition: () => player.reincarnationCount >= 1_000_000_000, + group: 'reincarnationCount' + }, + { + pointValue: 44, + unlockCondition: () => player.reincarnationCount >= 8_000_000_000, + group: 'reincarnationCount' + }, + { + pointValue: 48, + unlockCondition: () => player.reincarnationCount >= 100_000_000_000, + group: 'reincarnationCount' + }, + { + pointValue: 52, + unlockCondition: () => player.reincarnationCount >= 1_000_000_000_000, + group: 'reincarnationCount' + }, + { + pointValue: 56, + unlockCondition: () => player.reincarnationCount >= 13_131_313_131_313, + group: 'reincarnationCount' + }, + { + pointValue: 60, + unlockCondition: () => player.reincarnationCount >= 2e14, + group: 'reincarnationCount' } - if (i === 2) { - if (player.transcendnoaccelerator) { - achievementaward(61) - } - if (player.transcendnomultiplier) { - achievementaward(58) - } - if (player.transcendnocoinupgrades) { - achievementaward(65) - } - if (player.transcendnocoinorprestigeupgrades) { - achievementaward(66) - } - if (G.transcendPointGain.gte(1)) { - achievementaward(43) - } - if (G.transcendPointGain.gte(1e6)) { - achievementaward(44) - } - if (G.transcendPointGain.gte(1e50)) { - achievementaward(45) - } - if (G.transcendPointGain.gte(1e308)) { - achievementaward(46) - } - if (G.transcendPointGain.gte('1e1500')) { - achievementaward(47) - } - if (G.transcendPointGain.gte('1e25000')) { - achievementaward(48) - } - if (G.transcendPointGain.gte('1e100000')) { - achievementaward(49) - } +] + +export interface AchievementDisplayInfo { + order: number // Display achs in certain order + displayCondition: () => boolean +} + +export const groupedAchievementData: Record, AchievementDisplayInfo> = { + firstOwnedCoin: { + order: 0, + displayCondition: () => true + }, + secondOwnedCoin: { + order: 1, + displayCondition: () => true + }, + thirdOwnedCoin: { + order: 2, + displayCondition: () => true + }, + fourthOwnedCoin: { + order: 3, + displayCondition: () => true + }, + fifthOwnedCoin: { + order: 4, + displayCondition: () => true + }, + prestigeCount: { + order: 4.5, + displayCondition: () => player.prestigeCount > 0 + }, + prestigePointGain: { + order: 5, + displayCondition: () => player.prestigeCount > 0 + }, + accelerators: { + order: 6, + displayCondition: () => true + }, + multipliers: { + order: 7, + displayCondition: () => true + }, + acceleratorBoosts: { + order: 8, + displayCondition: () => player.prestigeCount > 0 + }, + runeLevel: { + order: 9, + displayCondition: () => player.prestigeCount > 0 + }, + transcensionCount: { + order: 9.5, + displayCondition: () => player.transcendCount > 0 + }, + transcendPointGain: { + order: 10, + displayCondition: () => player.transcendCount > 0 + }, + challenge1: { + order: 11, + displayCondition: () => player.transcendCount > 0 + }, + challenge2: { + order: 12, + displayCondition: () => player.transcendCount > 0 + }, + challenge3: { + order: 13, + displayCondition: () => player.transcendCount > 0 + }, + challenge4: { + order: 14, + displayCondition: () => player.transcendCount > 0 + }, + challenge5: { + order: 15, + displayCondition: () => player.transcendCount > 0 + }, + reincarnationCount: { + order: 15.5, + displayCondition: () => player.reincarnationCount > 0 + }, + reincarnationPointGain: { + order: 16, + displayCondition: () => player.reincarnationCount > 0 + }, + challenge6: { + order: 17, + displayCondition: () => player.reincarnationCount > 0 + }, + challenge7: { + order: 18, + displayCondition: () => player.highestchallengecompletions[6] > 0 + }, + challenge8: { + order: 19, + displayCondition: () => player.highestchallengecompletions[7] > 0 + }, + antCrumbs: { + order: 20, + displayCondition: () => player.highestchallengecompletions[8] > 0 + }, + sacMult: { + order: 21, + displayCondition: () => player.highestchallengecompletions[8] > 0 + }, + runeFreeLevel: { + order: 22, + displayCondition: () => player.highestchallengecompletions[8] > 0 + }, + challenge9: { + order: 23, + displayCondition: () => player.highestchallengecompletions[8] > 0 + }, + speedBlessing: { + order: 24, + displayCondition: () => player.highestchallengecompletions[9] > 0 + }, + challenge10: { + order: 25, + displayCondition: () => player.highestchallengecompletions[9] > 0 + }, + ascensionCount: { + order: 26, + displayCondition: () => player.ascensionCount > 0 + }, + constant: { + order: 27, + displayCondition: () => player.ascensionCount > 0 + }, + challenge11: { + order: 28, + displayCondition: () => player.ascensionCount > 0 + }, + campaignTokens: { + order: 28.5, + displayCondition: () => player.highestchallengecompletions[11] > 0 + }, + ascensionScore: { + order: 29, + displayCondition: () => player.highestchallengecompletions[11] > 0 + }, + challenge12: { + order: 30, + displayCondition: () => player.highestchallengecompletions[11] > 0 + }, + speedSpirit: { + order: 31, + displayCondition: () => player.highestchallengecompletions[12] > 0 + }, + challenge13: { + order: 32, + displayCondition: () => player.highestchallengecompletions[12] > 0 + }, + challenge14: { + order: 33, + displayCondition: () => player.highestchallengecompletions[13] > 0 + }, + singularityCount: { + order: 34, + displayCondition: () => player.singularityCount > 0 } - if (i === 3) { - if (player.reincarnatenoaccelerator) { - achievementaward(62) - } - if (player.reincarnatenomultiplier) { - achievementaward(59) - } - if (player.reincarnatenocoinupgrades) { - achievementaward(67) - } - if (player.reincarnatenocoinorprestigeupgrades) { - achievementaward(68) - } - if (player.reincarnatenocoinprestigeortranscendupgrades) { - achievementaward(69) - } - if (player.reincarnatenocoinprestigetranscendorgeneratorupgrades) { - achievementaward(70) - } - if (G.reincarnationPointGain.gte(1)) { - achievementaward(50) - } - if (G.reincarnationPointGain.gte(1e5)) { - achievementaward(51) - } - if (G.reincarnationPointGain.gte(1e30)) { - achievementaward(52) - } - if (G.reincarnationPointGain.gte(1e200)) { - achievementaward(53) +} + +export type UngroupedAchievementNames = + | 'participationTrophy' + | 'prestigeNoMult' + | 'transcendNoMult' + | 'reincarnationNoMult' + | 'prestigeNoAccelerator' + | 'transcendNoAccelerator' + | 'reincarnationNoAccelerator' + | 'diamondSearch' + | 'prestigeNoCoinUpgrade' + | 'transcendNoCoinUpgrade' + | 'reincarnationNoCoinUpgrade' + | 'transcendNoCoinDiamondUpgrade' + | 'reincarnationNoCoinDiamondUpgrade' + | 'reincarnationNoCoinDiamondMythosUpgrade' + | 'reincarnationMinimumUpgrades' + | 'generationAch1' + | 'generationAch2' + | 'generationAch3' + | 'generationAch4' + | 'chal1NoGen' + | 'chal2NoGen' + | 'chal3NoGen' + | 'metaChallenged' + | 'seeingRed' + | 'ascended' + | 'verySlow' + | 'veryFast' + | 'unsmith' + | 'smith' + | 'highlyBlessed' + | 'oneCubeOfMany' + | 'extraChallenging' + | 'seeingRedNoBlue' + | 'overtaxed' + | 'thousandSuns' + | 'thousandMoons' + | 'sadisticAch' + +interface UngroupedAchievementDisplayInfo extends AchievementDisplayInfo { + achievementID: number +} + +export const ungroupedAchievementData: Record = { + participationTrophy: { + order: 0, + displayCondition: () => true, + achievementID: 0 + }, + prestigeNoMult: { + order: 1, + displayCondition: () => player.prestigeCount > 0, + achievementID: 57 + }, + transcendNoMult: { + order: 3, + displayCondition: () => player.transcendCount > 0, + achievementID: 58 + }, + reincarnationNoMult: { + order: 5, + displayCondition: () => player.reincarnationCount > 0, + achievementID: 59 + }, + prestigeNoAccelerator: { + order: 2, + displayCondition: () => player.prestigeCount > 0, + achievementID: 60 + }, + transcendNoAccelerator: { + order: 4, + displayCondition: () => player.transcendCount > 0, + achievementID: 61 + }, + reincarnationNoAccelerator: { + order: 6, + displayCondition: () => player.reincarnationCount > 0, + achievementID: 62 + }, + diamondSearch: { + order: 7, + displayCondition: () => player.transcendCount > 0, + achievementID: 63 + }, + prestigeNoCoinUpgrade: { + order: 8, + displayCondition: () => player.prestigeCount > 0, + achievementID: 64 + }, + transcendNoCoinUpgrade: { + order: 9, + displayCondition: () => player.transcendCount > 0, + achievementID: 65 + }, + transcendNoCoinDiamondUpgrade: { + order: 10, + displayCondition: () => player.transcendCount > 0, + achievementID: 66 + }, + reincarnationNoCoinUpgrade: { + order: 11, + displayCondition: () => player.reincarnationCount > 0, + achievementID: 67 + }, + reincarnationNoCoinDiamondUpgrade: { + order: 12, + displayCondition: () => player.reincarnationCount > 0, + achievementID: 68 + }, + reincarnationNoCoinDiamondMythosUpgrade: { + order: 13, + displayCondition: () => player.reincarnationCount > 0, + achievementID: 69 + }, + reincarnationMinimumUpgrades: { + order: 14, + displayCondition: () => player.reincarnationCount > 0, + achievementID: 70 + }, + generationAch1: { + order: 15, + displayCondition: () => player.prestigeCount > 0, + achievementID: 71 + }, + generationAch2: { + order: 16, + displayCondition: () => player.prestigeCount > 0, + achievementID: 72 + }, + generationAch3: { + order: 17, + displayCondition: () => player.prestigeCount > 0, + achievementID: 73 + }, + generationAch4: { + order: 18, + displayCondition: () => player.prestigeCount > 0, + achievementID: 74 + }, + chal1NoGen: { + order: 19, + displayCondition: () => player.transcendCount > 0, + achievementID: 75 + }, + chal2NoGen: { + order: 20, + displayCondition: () => player.transcendCount > 0, + achievementID: 76 + }, + chal3NoGen: { + order: 21, + displayCondition: () => player.transcendCount > 0, + achievementID: 77 + }, + metaChallenged: { + order: 22, + displayCondition: () => player.ascensionCount > 0, + achievementID: 238 + }, + seeingRed: { + order: 23, + displayCondition: () => player.unlocks.talismans, + achievementID: 239 + }, + ascended: { + order: 24, + displayCondition: () => player.challengecompletions[14] > 0, + achievementID: 240 + }, + verySlow: { + order: 25, + displayCondition: () => player.challengecompletions[12] > 0, + achievementID: 241 + }, + veryFast: { + order: 26, + displayCondition: () => player.challengecompletions[12] > 0, + achievementID: 242 + }, + unsmith: { + order: 27, + displayCondition: () => true, + achievementID: 243 + }, + smith: { + order: 28, + displayCondition: () => true, + achievementID: 244 + }, + highlyBlessed: { + order: 29, + displayCondition: () => player.unlocks.blessings, + achievementID: 245 + }, + oneCubeOfMany: { + order: 30, + displayCondition: () => player.ascensionCount > 0, + achievementID: 246 + }, + extraChallenging: { + order: 31, + displayCondition: () => player.challengecompletions[11] >= 20, + achievementID: 247 + }, + seeingRedNoBlue: { + order: 32, + displayCondition: () => player.challengecompletions[14] > 0, + achievementID: 248 + }, + overtaxed: { + order: 33, + displayCondition: () => player.challengecompletions[12] > 0, + achievementID: 249 + }, + thousandSuns: { + order: 34, + displayCondition: () => player.challengecompletions[14] > 0, + achievementID: 250 + }, + thousandMoons: { + order: 35, + displayCondition: () => player.challengecompletions[14] > 0, + achievementID: 251 + }, + sadisticAch: { + order: 36, + displayCondition: () => player.challengecompletions[14] > 0, + achievementID: 252 + } +} + +export const ungroupedAchievementKeys = Object.keys(ungroupedAchievementData) as UngroupedAchievementNames[] + +export type ProgressiveAchievements = + | 'runeLevel' + | 'freeRuneLevel' + | 'talismanRarities' + | 'singularityCount' + | 'ambrosiaCount' + | 'redAmbrosiaCount' + | 'singularityUpgrades' + | 'octeractUpgrades' + | 'redAmbrosiaUpgrades' + | 'exalts' + +export const numAchievements = Object.keys(achievements).length +export const maxAchievementPoints = Object.values(achievements).reduce((sum, ach) => sum + ach.pointValue, 0) + + Object.values(progressiveAchievements) + .reduce((sum, ach) => sum + (ach.maxPointValue !== -1 ? ach.maxPointValue : 0), 0) + +export const achievementsByGroup: Record = achievements + .reduce((groups, achievement, index) => { + if (achievement.group) { + if (!groups[achievement.group]) { + groups[achievement.group] = [] + } + groups[achievement.group].push(Number(index)) } - if (G.reincarnationPointGain.gte('1e1500')) { - achievementaward(54) + return groups + }, {} as Record) + +export const achievementGroupKeys = Object.keys(achievementsByGroup) as AchievementGroups[] + +export const achievementsByReward: Record = achievements + .reduce((rewards, achievement, index) => { + if (achievement.reward) { + for (const rewardType of Object.keys(achievement.reward) as AchievementRewards[]) { + if (!rewards[rewardType]) { + rewards[rewardType] = [] + } + rewards[rewardType].push(Number(index)) + } } - if (G.reincarnationPointGain.gte('1e5000')) { - achievementaward(55) + return rewards + }, {} as Record) + +export const achRewards: Record number | boolean> = { + acceleratorPower: (): number => { + return achievementsByReward.acceleratorPower.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.acceleratorPower!() : 0), + 0 + ) + }, + accelerators: (): number => { + return achievementsByReward.accelerators.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.accelerators!() : 0), + 0 + ) + }, + multipliers: (): number => { + return achievementsByReward.multipliers.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.multipliers!() : 0), + 0 + ) + }, + accelBoosts: (): number => { + return achievementsByReward.accelBoosts.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.accelBoosts!() : 0), + 0 + ) + }, + crystalMultiplier: (): number => { + return achievementsByReward.crystalMultiplier.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.crystalMultiplier!() : 1), + 1 + ) + }, + quarkGain: (): number => { + return achievementsByReward.quarkGain.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.quarkGain!() : 1), + 1 + ) + }, + taxReduction: (): number => { + return achievementsByReward.taxReduction.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.taxReduction!() : 1), + 1 + ) + }, + particleGain: (): number => { + return achievementsByReward.particleGain.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.particleGain!() : 1), + 1 + ) + }, + chronosTalisman: (): boolean => { + return Boolean(player.achievements[achievementsByReward.chronosTalisman[0]]) + }, + midasTalisman: (): boolean => { + return Boolean(player.achievements[achievementsByReward.midasTalisman[0]]) + }, + metaphysicsTalisman: (): boolean => { + return Boolean(player.achievements[achievementsByReward.metaphysicsTalisman[0]]) + }, + polymathTalisman: (): boolean => { + return Boolean(player.achievements[achievementsByReward.polymathTalisman[0]]) + }, + wowSquareTalisman: (): boolean => { + return Boolean(player.achievements[achievementsByReward.wowSquareTalisman[0]]) + }, + conversionExponent: (): number => { + return achievementsByReward.conversionExponent.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.conversionExponent!() : 0), + 0 + ) + }, + talismanPower: (): number => { + return achievementsByReward.talismanPower.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.talismanPower!() : 0), + 0 + ) + }, + sacrificeMult: (): number => { + return achievementsByReward.sacrificeMult.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.sacrificeMult!() : 1), + 1 + ) + }, + antSpeed: (): number => { + return achievementsByReward.antSpeed.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.antSpeed!() : 1), + 1 + ) + }, + antSacrificeUnlock: (): boolean => { + return Boolean(player.achievements[achievementsByReward.antSacrificeUnlock[0]]) + }, + antAutobuyers: (): number => { + return achievementsByReward.antAutobuyers.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.antAutobuyers!() : 0), + 0 + ) + }, + antUpgradeAutobuyers: (): number => { + return achievementsByReward.antUpgradeAutobuyers.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.antUpgradeAutobuyers!() : 0), + 0 + ) + }, + antELOAdditive: (): number => { + return 0 + /* + return achievementsByReward.antELOAdditive.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.antELOAdditive!() : 0), + 0 + )*/ + }, + antELOMultiplicative: (): number => { + return 1 + /* + return achievementsByReward.antELOMultiplicative.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.antELOMultiplicative!() : 1), + 1 + )*/ + }, + ascensionCountMultiplier: (): number => { + return achievementsByReward.ascensionCountMultiplier.reduce( + (prod, index) => + prod * (player.achievements[index] ? achievements[index].reward!.ascensionCountMultiplier!() : 1), + 1 + ) + }, + ascensionCountAdditive: (): number => { + return achievementsByReward.ascensionCountAdditive.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.ascensionCountAdditive!() : 0), + 0 + ) + }, + wowCubeGain: (): number => { + return achievementsByReward.wowCubeGain.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.wowCubeGain!() : 1), + 1 + ) + }, + wowTesseractGain: (): number => { + return achievementsByReward.wowTesseractGain.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.wowTesseractGain!() : 1), + 1 + ) + }, + wowHypercubeGain: (): number => { + return achievementsByReward.wowHypercubeGain.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.wowHypercubeGain!() : 1), + 1 + ) + }, + wowPlatonicGain: (): number => { + return achievementsByReward.wowPlatonicGain.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.wowPlatonicGain!() : 1), + 1 + ) + }, + wowHepteractGain: (): number => { + return achievementsByReward.wowHepteractGain.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.wowHepteractGain!() : 1), + 1 + ) + }, + ascensionScore: (): number => { + return achievementsByReward.ascensionScore.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.ascensionScore!() : 1), + 1 + ) + }, + ascensionRewardScaling: (): boolean => { + return Boolean(player.achievements[achievementsByReward.ascensionRewardScaling[0]]) + }, + constUpgrade1Buff: (): number => { + return achievementsByReward.constUpgrade1Buff.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.constUpgrade1Buff!() : 0), + 0 + ) + }, + constUpgrade2Buff: (): number => { + return achievementsByReward.constUpgrade2Buff.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.constUpgrade2Buff!() : 0), + 0 + ) + }, + platonicToHypercubes: (): number => { + return achievementsByReward.platonicToHypercubes.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.platonicToHypercubes!() : 0), + 0 + ) + }, + statTracker: (): boolean => { + return Boolean(player.achievements[achievementsByReward.statTracker[0]]) + }, + overfluxConversionRate: (): number => { + return achievementsByReward.overfluxConversionRate.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.overfluxConversionRate!() : 1), + 1 + ) + }, + diamondUpgrade18: (): boolean => { + return Boolean(player.achievements[achievementsByReward.diamondUpgrade18[0]]) + }, + diamondUpgrade19: (): boolean => { + return Boolean(player.achievements[achievementsByReward.diamondUpgrade19[0]]) + }, + diamondUpgrade20: (): boolean => { + return Boolean(player.achievements[achievementsByReward.diamondUpgrade20[0]]) + }, + prestigeCountMultiplier: (): number => { + return achievementsByReward.prestigeCountMultiplier.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.prestigeCountMultiplier!() : 1), + 1 + ) + }, + transcensionCountMultiplier: (): number => { + return achievementsByReward.transcensionCountMultiplier.reduce( + (prod, index) => + prod * (player.achievements[index] ? achievements[index].reward!.transcensionCountMultiplier!() : 1), + 1 + ) + }, + reincarnationCountMultiplier: (): number => { + return achievementsByReward.reincarnationCountMultiplier.reduce( + (prod, index) => + prod * (player.achievements[index] ? achievements[index].reward!.reincarnationCountMultiplier!() : 1), + 1 + ) + }, + duplicationRuneUnlock: (): boolean => { + return Boolean(player.achievements[achievementsByReward.duplicationRuneUnlock[0]]) + }, + offeringBonus: (): number => { + return achievementsByReward.offeringBonus.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.offeringBonus!() : 1), + 1 + ) + }, + obtainiumBonus: (): number => { + return achievementsByReward.obtainiumBonus.reduce( + (prod, index) => prod * (player.achievements[index] ? achievements[index].reward!.obtainiumBonus!() : 1), + 1 + ) + }, + salvage: (): number => { + return achievementsByReward.salvage.reduce( + (sum, index) => sum + (player.achievements[index] ? achievements[index].reward!.salvage!() : 0), + 0 + ) + }, + prismRuneUnlock: (): boolean => { + return Boolean(player.achievements[achievementsByReward.prismRuneUnlock[0]]) + }, + thriftRuneUnlock: (): boolean => { + return Boolean(player.achievements[achievementsByReward.thriftRuneUnlock[0]]) + }, + transcendToPrestige: (): boolean => { + return Boolean(player.achievements[achievementsByReward.transcendToPrestige[0]]) + }, + reincarnationToTranscend: (): boolean => { + return Boolean(player.achievements[achievementsByReward.reincarnationToTranscend[0]]) + } +} + +export let achievementPoints = 0 +export let achievementLevel = 0 + +export const updateAchievementPoints = () => { + achievementPoints = 0 + + achievementPoints += achievements.reduce((sum, ach, index) => { + return sum + (player.achievements[index] ? ach.pointValue : 0) + }, 0) + + for (const k of Object.keys(progressiveAchievements) as ProgressiveAchievements[]) { + const pointsAwarded = progressiveAchievements[k].pointsAwarded(player.progressiveAchievements[k]) + achievementPoints += pointsAwarded + progressiveAchievements[k].rewardedAP = pointsAwarded + } + + const sourcedFromUpdatePoints = true + updateAchievementLevel(sourcedFromUpdatePoints) +} + +export const awardAchievement = (index: number) => { + if (player.achievements[index] === 1) { + return false + } + if (achievements[index].unlockCondition()) { + player.achievements[index] = 1 + achievementPoints += achievements[index].pointValue + updateAchievementLevel() + if (player.toggles[34]) { + const description = i18next.t(`achievements.descriptions.${index}`) + void Notification(i18next.t('achievements.notification', { m: description })) } - if (G.reincarnationPointGain.gte('1e7777')) { - achievementaward(56) + player.worlds.add(getAchievementQuarks(index), false) + revealStuff() + + // Update displays if we are on Achievements Tab + if (G.currentTab === Tabs.Achievements) { + if (achievements[index].group === 'ungrouped') { + updateUngroupedAchievementProgress(index) + } else { + updateGroupedAchievementProgress(achievements[index].group) + } } } } -/** - * Array of [index, bar to get achievement if greater than, achievement number] - */ -// dprint-ignore -const challengeCompletionsBar: [number, number, number][] = [ - [1, 0.5, 78], [1, 2.5, 79], [1, 4.5, 80], [1, 9.5, 81], [1, 19.5, 82], [1, 49.5, 83], [1, 74.5, 84], - [2, 0.5, 85], [2, 2.5, 86], [2, 4.5, 87], [2, 9.5, 88], [2, 19.5, 89], [2, 49.5, 90], [2, 74.5, 91], - [3, 0.5, 92], [3, 2.5, 93], [3, 4.5, 94], [3, 9.5, 95], [3, 19.5, 96], [3, 49.5, 97], [3, 74.5, 98], - [4, 0.5, 99], [4, 2.5, 100], [4, 4.5, 101], [4, 9.5, 102], [4, 19.5, 103], [4, 49.5, 104], [4, 74.5, 105], - [5, 0.5, 106], [5, 2.5, 107], [5, 4.5, 108], [5, 9.5, 109], [5, 19.5, 110], [5, 49.5, 111], [5, 74.5, 112], - [6, 0.5, 113], [6, 1.5, 114], [6, 2.5, 115], [6, 4.5, 116], [6, 9.5, 117], [6, 14.5, 118], [6, 24.5, 119], - [7, 0.5, 120], [7, 1.5, 121], [7, 2.5, 122], [7, 4.5, 123], [7, 9.5, 124], [7, 14.5, 125], [7, 24.5, 126], - [8, 0.5, 127], [8, 1.5, 128], [8, 2.5, 129], [8, 4.5, 130], [8, 9.5, 131], [8, 19.5, 132], [8, 24.5, 133], - [9, 0.5, 134], [9, 1.5, 135], [9, 2.5, 136], [9, 4.5, 137], [9, 9.5, 138], [9, 19.5, 139], [9, 24.5, 140], - [10, 0.5, 141], [10, 1.5, 142], [10, 2.5, 143], [10, 4.5, 144], [10, 9.5, 145], [10, 19.5, 146], [10, 24.5, 147], - [15, 0.5, 252] -] +export const awardUngroupedAchievement = (name: UngroupedAchievementNames) => { + const index = ungroupedAchievementData[name].achievementID + awardAchievement(index) +} -const challengeCompletionsNotAuto: Record = { - 1: ['1e1000', 75], - 2: ['1e1000', 76], - 3: ['1e99999', 77], - 5: ['1e120000', 63] +export const awardAchievementGroup = (group: AchievementGroups) => { + if (group === 'ungrouped') { + throw new Error('Cannot award ungrouped achievements') + } + + for (const index of achievementsByGroup[group]) { + awardAchievement(index) + } } -export const challengeachievementcheck = (i: number, auto?: boolean) => { - const generatorcheck = sumContents(player.upgrades.slice(101, 106)) +export const updateProgressiveAP = (ach: ProgressiveAchievements) => { + const oldPoints = progressiveAchievements[ach].rewardedAP + const pointsAwarded = progressiveAchievements[ach].pointsAwarded(player.progressiveAchievements[ach]) + achievementPoints += pointsAwarded - progressiveAchievements[ach].rewardedAP + progressiveAchievements[ach].rewardedAP = pointsAwarded - for (const [, bar, ach] of challengeCompletionsBar.filter(([o]) => o === i)) { - if (player.challengecompletions[i] > bar && player.achievements[ach] < 1) { - achievementaward(ach) + if (oldPoints !== pointsAwarded) { + updateProgressiveAchievementProgress(ach) + updateAchievementLevel() + } +} + +export const updateProgressiveCache = (ach: ProgressiveAchievements) => { + if (!progressiveAchievements[ach].useCachedValue) { + updateProgressiveAP(ach) + } + const oldVal = player.progressiveAchievements[ach] + player.progressiveAchievements[ach] = Math.max( + player.progressiveAchievements[ach], + progressiveAchievements[ach].updateValue() + ) + if (oldVal !== player.progressiveAchievements[ach]) { + updateProgressiveAP(ach) + } +} + +export const updateAchievementLevel = (fromUpdatePoints = false) => { + const oldLevel = achievementLevel + if (achievementPoints < 2500) { + achievementLevel = Math.floor(achievementPoints / 50) + } else { + achievementLevel = 50 + Math.floor((achievementPoints - 2500) / 100) + } + displayLevelStuff() + + if (oldLevel < achievementLevel) { + if (player.toggles[34] && !fromUpdatePoints) { + void Notification(i18next.t('achievements.levelUpNotification', { old: oldLevel, new: achievementLevel })) } } +} + +export const toNextAchievementLevelEXP = () => { + if (achievementPoints < 2500) { + return 50 - (achievementPoints % 50) + } else { + return 100 - (achievementPoints % 100) + } +} + +export const getAchievementReward = (rewardType: AchievementRewards): number | boolean => { + return achRewards[rewardType]() +} + +export const generateAchievementRewardSummary = () => { + const intro = i18next.t('achievements.rewardTypes.title') + let numericalTexts = '' + let booleanTexts = '' - // Challenges 1, 2, 3 check for not buying generators and getting X coins - // Challenge 5 check for not buying Acc/Acc Boosts and getting 1.00e120,000 coins - if ([1, 2, 3, 5].includes(i) && !auto) { - const [gte, ach] = challengeCompletionsNotAuto[i] - if (i === 5) { - if ( - player.coinsThisTranscension.gte(gte) && player.acceleratorBought === 0 && player.acceleratorBoostBought === 0 - ) { - achievementaward(ach) + for (const [rewardType, rewardFunction] of Object.entries(achRewards)) { + const typeKey = rewardType as AchievementRewards + const reward = rewardFunction() + if (typeof reward === 'number') { + if (typeKey === 'acceleratorPower') { + numericalTexts += `${ + i18next.t(`achievements.rewardTypes.${rewardType}`, { val: formatAsPercentIncrease(1 + reward, 2) }) + }\n` + } else if (typeKey === 'taxReduction') { + // Formatted such that it has negative displayed value + numericalTexts += `${ + i18next.t(`achievements.rewardTypes.${rewardType}`, { val: formatAsPercentIncrease(reward, 2) }) + }\n` + } else if (typeKey === 'talismanPower') { + numericalTexts += `${ + i18next.t(`achievements.rewardTypes.${rewardType}`, { val: formatAsPercentIncrease(1 + reward, 2) }) + }\n` + } else if (typeKey === 'conversionExponent') { + numericalTexts += `${ + i18next.t(`achievements.rewardTypes.${rewardType}`, { val: formatAsPercentIncrease(1 + reward, 0) }) + }\n` + } else { + numericalTexts += `${i18next.t(`achievements.rewardTypes.${rewardType}`, { val: format(reward, 2, false) })}\n` + } + } else if (typeof reward === 'boolean') { + if (reward) { + booleanTexts += `${ + i18next.t(`achievements.rewardTypes.${rewardType}`, { + unlock: i18next.t('achievements.rewardTypes.unlocked') + }) + }\n` } - } else if (player.coinsThisTranscension.gte(gte) && generatorcheck === 0) { - achievementaward(ach) } } - if (i >= 11 && i <= 14) { - const challengeArray = [0, 1, 2, 3, 5, 10, 20, 30] - for (let j = 1; j <= 7; j++) { - if (player.challengecompletions[i] >= challengeArray[j] && player.achievements[119 + 7 * i + j] < 1) { - achievementaward(119 + 7 * i + j) + return Alert(`${intro}\n${numericalTexts}\n${booleanTexts}`) +} + +export const createGroupedAchievementDescription = (group: AchievementGroups) => { + if (group === 'ungrouped') { + throw new Error('Cannot create description for ungrouped achievements') + } + + let groupName = i18next.t(`achievements.groupNames.${group}`) + let achTier = 0 + let currTier = 0 + let extraBonuses = '' + let earnedAP = 0 + let possibleAP = 0 + for (const index of achievementsByGroup[group]) { + const ach = achievements[index] + const hasAch = player.achievements[index] + const AP = ach.pointValue + possibleAP += AP + if (hasAch) { + achTier += 1 + earnedAP += AP + } + + if (ach.reward) { + if (extraBonuses === '') { + extraBonuses += `${i18next.t('achievements.tieredExtraRewards')}
` + } + for (const [rewardType, rewardFunction] of Object.entries(ach.reward)) { + const rewardGroup = rewardType as AchievementRewards + const rewardValue = achRewards[rewardGroup]() + + if (typeof rewardValue === 'boolean') { + extraBonuses += `Tier ${currTier + 1} • ${ + i18next.t(`achievements.achievementRewards.${index}.${rewardGroup}`, { + unlock: hasAch + ? i18next.t('achievements.rewardTypes.unlocked') + : i18next.t('achievements.rewardTypes.locked') + }) + }
` + } + if (typeof rewardValue === 'number') { + extraBonuses += `Tier ${ + currTier + 1 + } • ${ + i18next.t(`achievements.achievementRewards.${index}.${rewardGroup}`, { + val: format(rewardFunction(), 2, false) + }) + }
` + } } } + + /*if (ach.reward) { + for (const [rewardType, rewardFunction] of Object.entries(ach.reward)) { + const rewardGroup = rewardType as AchievementRewards + const rewardValue = getAchieveReward[rewardGroup](achievementManager.achievementMap) + + if (typeof rewardValue === 'boolean') { + extraBonuses += `Tier ${currTier + 1} ${ + i18next.t(`achievements.rewardTypes.${rewardType}`, { + unlock: rewardValue ? i18next.t('achievements.rewardTypes.unlocked') : i18next.t('achievements.rewardTypes.locked') + }) + }
` + } else if (typeof rewardValue === 'number') { + extraBonuses += `Tier ${currTier + 1} ${ + i18next.t(`achievements.rewardTypes.${rewardType}`, { + val: format(rewardFunction(), 2, false) + }) + }
` + } + } + } */ + currTier += 1 + } + if (achTier === currTier) { + groupName += `${ + i18next.t('achievements.complete', { + tier: achTier + }) + }` + groupName = `${groupName}` + } else { + groupName += `${ + i18next.t('achievements.tier', { + tier: achTier + }) + }` } - if ( - player.challengecompletions[10] >= 50 && i === 11 && player.corruptions.used.extinction >= 5 - && player.achievements[247] < 1 - ) { - achievementaward(247) - } -} - -// \) \{\n\s+achievementaward\(\d+\)\n\s+\} - -/** - * Requirements for each building achievement - * @type {(() => boolean)[]} - */ -const buildAchievementReq: (() => boolean)[] = [ - () => (player.firstOwnedCoin >= 1 && player.achievements[1] < 0.5), - () => (player.firstOwnedCoin >= 10 && player.achievements[2] < 0.5), - () => (player.firstOwnedCoin >= 100 && player.achievements[3] < 0.5), - () => (player.firstOwnedCoin >= 1000 && player.achievements[4] < 0.5), - () => (player.firstOwnedCoin >= 5000 && player.achievements[5] < 0.5), - () => (player.firstOwnedCoin >= 10000 && player.achievements[6] < 0.5), - () => (player.firstOwnedCoin >= 20000 && player.achievements[7] < 0.5), - () => (player.secondOwnedCoin >= 1 && player.achievements[8] < 0.5), - () => (player.secondOwnedCoin >= 10 && player.achievements[9] < 0.5), - () => (player.secondOwnedCoin >= 100 && player.achievements[10] < 0.5), - () => (player.secondOwnedCoin >= 1000 && player.achievements[11] < 0.5), - () => (player.secondOwnedCoin >= 5000 && player.achievements[12] < 0.5), - () => (player.secondOwnedCoin >= 10000 && player.achievements[13] < 0.5), - () => (player.secondOwnedCoin >= 20000 && player.achievements[14] < 0.5), - () => (player.thirdOwnedCoin >= 1 && player.achievements[15] < 0.5), - () => (player.thirdOwnedCoin >= 10 && player.achievements[16] < 0.5), - () => (player.thirdOwnedCoin >= 100 && player.achievements[17] < 0.5), - () => (player.thirdOwnedCoin >= 1000 && player.achievements[18] < 0.5), - () => (player.thirdOwnedCoin >= 5000 && player.achievements[19] < 0.5), - () => (player.thirdOwnedCoin >= 10000 && player.achievements[20] < 0.5), - () => (player.thirdOwnedCoin >= 20000 && player.achievements[21] < 0.5), - () => (player.fourthOwnedCoin >= 1 && player.achievements[22] < 0.5), - () => (player.fourthOwnedCoin >= 10 && player.achievements[23] < 0.5), - () => (player.fourthOwnedCoin >= 100 && player.achievements[24] < 0.5), - () => (player.fourthOwnedCoin >= 1000 && player.achievements[25] < 0.5), - () => (player.fourthOwnedCoin >= 5000 && player.achievements[26] < 0.5), - () => (player.fourthOwnedCoin >= 10000 && player.achievements[27] < 0.5), - () => (player.fourthOwnedCoin >= 20000 && player.achievements[28] < 0.5), - () => (player.fifthOwnedCoin >= 1 && player.achievements[29] < 0.5), - () => (player.fifthOwnedCoin >= 10 && player.achievements[30] < 0.5), - () => (player.fifthOwnedCoin >= 66 && player.achievements[31] < 0.5), - () => (player.fifthOwnedCoin >= 666 && player.achievements[32] < 0.5), - () => (player.fifthOwnedCoin >= 6666 && player.achievements[33] < 0.5), - () => (player.fifthOwnedCoin >= 17777 && player.achievements[34] < 0.5), - () => (player.fifthOwnedCoin >= 42777 && player.achievements[35] < 0.5) -] + const focusedIndex = achievementsByGroup[group][Math.min(achTier, currTier - 1)] + let tierText = i18next.t(`achievements.descriptions.${achievementsByGroup[group][Math.min(achTier, currTier - 1)]}`) + if (achTier < currTier) { + tierText += ` [+${achievements[focusedIndex].pointValue} AP]` + } -export const buildingAchievementCheck = () => { - for (const req of buildAchievementReq) { - if (req()) { - const idx = buildAchievementReq.indexOf(req) + 1 - achievementaward(idx) + const finalText = `${groupName}
+ ${earnedAP}/${possibleAP} AP
+ ${tierText}
+ ${extraBonuses}` + return finalText + // DOMCacheGetOrSet('achievementMultiLine').innerHTML = finalText +} + +export const generateUngroupedDescription = (name: UngroupedAchievementNames) => { + const index = ungroupedAchievementData[name].achievementID + const ach = achievements[index] + const achText = i18next.t(`achievements.descriptions.${index}`) + + const colonIndex = achText.indexOf(':') + let achName = achText.substring(0, colonIndex) + const requirement = achText.substring(colonIndex + 1) + let trimmedRequirement = requirement?.trim() || '' + + const value = ach.pointValue + let earnedValue = 0 + + const hasAch = player.achievements[index] === 1 + if (hasAch) { + achName = `${achName} - COMPLETE!` + earnedValue = value + } else { + trimmedRequirement += ` [+${ach.pointValue} AP]` + } + + let extraText = '' + if (ach.reward) { + extraText = `${i18next.t('achievements.ungroupedExtraRewards')}
` + for (const [rewardType, rewardFunction] of Object.entries(ach.reward)) { + const rewardGroup = rewardType as AchievementRewards + const rewardValue = achRewards[rewardGroup]() + + if (typeof rewardValue === 'boolean') { + extraText += `${ + i18next.t(`achievements.achievementRewards.${index}.${rewardType}`, { + unlock: i18next.t('achievements.rewardTypes.unlocked') + }) + }
` + } else if (typeof rewardValue === 'number') { + extraText += `${ + i18next.t(`achievements.achievementRewards.${index}.${rewardType}`, { + val: format(rewardFunction(), 2, false) + }) + }
` + } } } + + const finalText = `${achName}
+ ${earnedValue}/${value} AP
+ ${trimmedRequirement}
+ ${extraText}` + return finalText +} + +export const generateProgressiveAchievementDescription = (name: ProgressiveAchievements) => { + const ach = progressiveAchievements[name] + let achTitle = i18next.t(`achievements.progressiveAchievements.${name}.name`) + const achText = i18next.t(`achievements.progressiveAchievements.${name}.description`, { + x: format(player.progressiveAchievements[name], 0, true) + }) + + const i18nObject = ach.extraI18n ? ach.extraI18n() : {} + const achAPSourceText = i18next.t(`achievements.progressiveAchievements.${name}.apSource`, i18nObject) + + let APText = '' + + if (ach.maxPointValue === -1) { + APText = `${ach.rewardedAP} AP` + } else { + APText = `${ach.rewardedAP}/${ach.maxPointValue} AP` + } + + if (ach.rewardedAP === ach.maxPointValue) { + achTitle = `${achTitle}` + } + + const finalText = `${achTitle}
+ ${APText}
+ ${achText}
+ ${achAPSourceText}` + + return finalText } -export const ascensionAchievementCheck = (i: number, score = 0) => { - if (i === 1) { - // dprint-ignore - const ascendCountArray = [ - 0, 1, 2, 10, 100, 1000, 14142, 141421, 1414213, // Column 1 - 1e7, 1e8, 2e9, 4e10, 8e11, 1.6e13, 1e14 // Column 2 - ] +export const generateAchievementHTMLs = () => { + const alreadyGenerated = document.getElementsByClassName('tieredAchievementType').length > 0 - for (let j = 1; j <= 7; j++) { - if (player.ascensionCount >= ascendCountArray[j] && player.achievements[182 + j] < 1) { - achievementaward(182 + j) - } - if (player.ascensionCount >= ascendCountArray[j + 8] && player.achievements[259 + j] < 1) { - achievementaward(259 + j) + if (alreadyGenerated) { + return + } else { + const table = DOMCacheGetOrSet('tieredAchievementsTable') + const ungroupedTable = DOMCacheGetOrSet('ungroupedAchievementsTable') + const progressiveTable = DOMCacheGetOrSet('progressiveAchievementsTable') + + const sortedGroups = (Object.keys(achievementsByGroup) as AchievementGroups[]) + .filter((k) => k !== 'ungrouped') + .sort((a, b) => { + const orderA = groupedAchievementData[a as Exclude]?.order + ?? Number.POSITIVE_INFINITY + const orderB = groupedAchievementData[b as Exclude]?.order + ?? Number.POSITIVE_INFINITY + return orderA - orderB + }) + + for (const k of sortedGroups) { + const capitalizedName = k.charAt(0).toUpperCase() + k.slice(1) + // create a new image element for each group that is not ungrouped + + const div = document.createElement('div') + div.className = 'tieredAchievementType' + + const img = document.createElement('img') + img.id = `achievementGroup${capitalizedName}` + img.src = `Pictures/Achievements/Grouped/${capitalizedName}.png` + img.alt = i18next.t(`achievements.groupNames.${k}`) + img.style.cursor = 'pointer' + img.tabIndex = 0 + + if (!isMobile) { + img.onmousemove = (e: MouseEvent) => { + Modal(createGroupedAchievementDescription(k), e.clientX, e.clientY, { borderColor: 'cyan' }) + } + img.onfocus = () => { + const elm = img.getBoundingClientRect() + // Get x, y current based on the element's position + Modal(createGroupedAchievementDescription(k), elm.x, elm.y + elm.height / 2, { borderColor: 'cyan' }) + } + + img.onmouseout = () => { + CloseModal() + } + + img.onblur = () => { + CloseModal() + } + } else { + img.onclick = () => { + DOMCacheGetOrSet('achievementMultiLine').innerHTML = createGroupedAchievementDescription(k) + } } + + // attach to the table + div.appendChild(img) + table.appendChild(div) } - if (player.ascensionCount >= ascendCountArray[8] && player.achievements[240] < 1) { - achievementaward(240) - } - } - if (i === 2) { - // dprint-ignore - const constantArray = [ - 0, 3.14, 1e6, 4.32e10, 6.9e21, 1.509e33, 1e66, '1.8e308', // Column 1 - '1e1000', '1e5000', '1e15000', '1e50000', '1e100000', '1e300000', '1e1000000' // Column 2 - ] - for (let j = 1; j <= 7; j++) { - if (player.ascendShards.gte(constantArray[j]) && player.achievements[189 + j] < 1) { - achievementaward(189 + j) + const sortedUngrouped = (Object.keys(ungroupedAchievementData) as UngroupedAchievementNames[]) + .sort((a, b) => { + const orderA = ungroupedAchievementData[a]?.order ?? Number.POSITIVE_INFINITY + const orderB = ungroupedAchievementData[b]?.order ?? Number.POSITIVE_INFINITY + return orderA - orderB + }) + + for (const k of sortedUngrouped) { + const capitalizedName = k.charAt(0).toUpperCase() + k.slice(1) + // create a new image element for each ungrouped achievement + + const div = document.createElement('div') + div.className = 'ungroupedAchievementType' + + const img = document.createElement('img') + img.id = `ungroupedAchievement${capitalizedName}` + img.src = `Pictures/Achievements/Ungrouped/${capitalizedName}.png` + img.alt = i18next.t(`achievements.ungroupedNames.${k}`) + img.style.cursor = 'pointer' + img.tabIndex = 0 + + if (!isMobile) { + img.onmousemove = (e: MouseEvent) => { + Modal(generateUngroupedDescription(k as UngroupedAchievementNames), e.clientX, e.clientY, { + borderColor: 'white' + }) + } + + img.onfocus = () => { + const elm = img.getBoundingClientRect() + // Get x, y current based on the element's position + Modal(generateUngroupedDescription(k as UngroupedAchievementNames), elm.x, elm.y + elm.height / 2, { + borderColor: 'white' + }) + } + + img.onmouseout = () => { + CloseModal() + } + img.onblur = () => { + CloseModal() + } + } else { + img.onclick = () => { + DOMCacheGetOrSet('achievementMultiLine').innerHTML = generateUngroupedDescription( + k as UngroupedAchievementNames + ) + } } - if (player.ascendShards.gte(constantArray[j + 7]) && player.achievements[266 + j] < 1) { - achievementaward(266 + j) + + // attach to the table + div.appendChild(img) + ungroupedTable.appendChild(div) + } + + const sortedProgressive = (Object.keys(progressiveAchievements) as ProgressiveAchievements[]) + .sort((a, b) => { + const orderA = progressiveAchievements[a]?.displayOrder ?? Number.POSITIVE_INFINITY + const orderB = progressiveAchievements[b]?.displayOrder ?? Number.POSITIVE_INFINITY + return orderA - orderB + }) + + for (const k of sortedProgressive) { + const capitalizedName = k.charAt(0).toUpperCase() + k.slice(1) + // create a new image element for each progressive achievement + + const div = document.createElement('div') + div.className = 'progressiveAchievementType' + + const img = document.createElement('img') + img.id = `progressiveAchievement${capitalizedName}` + img.src = `Pictures/Achievements/Progressive/${capitalizedName}.png` + img.alt = i18next.t(`achievements.progressiveNames.${k}`) + img.style.cursor = 'pointer' + img.tabIndex = 0 + + if (!isMobile) { + img.onmousemove = (e: MouseEvent) => { + Modal(generateProgressiveAchievementDescription(k as ProgressiveAchievements), e.clientX, e.clientY, { + borderColor: 'turquoise' + }) + } + + img.onfocus = () => { + const elm = img.getBoundingClientRect() + // Get x, y current based on the element's position + Modal( + generateProgressiveAchievementDescription(k as ProgressiveAchievements), + elm.x, + elm.y + elm.height / 2, + { borderColor: 'turquoise' } + ) + } + + img.onmouseout = () => { + CloseModal() + } + + img.onblur = () => { + CloseModal() + } + } else { + img.onclick = () => { + DOMCacheGetOrSet('achievementMultiLine').innerHTML = generateProgressiveAchievementDescription( + k as ProgressiveAchievements + ) + } } + + // attach to the table + div.appendChild(img) + progressiveTable.appendChild(div) } } - if (i === 3) { - // dprint-ignore - const scoreArray = [ - 0, 1e5, 1e6, 1e7, 1e8, 1e9, 5e9, 2.5e10, // Column 1 - 1e12, 1e14, 1e17, 2e18, 4e19, 1e21, 1e23 // Column 2 - ] - for (let j = 1; j <= 7; j++) { - if (score >= scoreArray[j] && player.achievements[224 + j] < 1) { - achievementaward(224 + j) - } +} - if (score >= scoreArray[7 + j] && player.achievements[252 + j] < 1) { - achievementaward(252 + j) - } +export const updateGroupedAchievementProgress = (group: AchievementGroups) => { + if (group === 'ungrouped') { + throw new Error('Cannot update progress for ungrouped achievements in updateGroupedAchievementProgress') + } + + const capitalizedName = group.charAt(0).toUpperCase() + group.slice(1) + const img = DOMCacheGetOrSet(`achievementGroup${capitalizedName}`) as HTMLElement + + if (img) { + const totalAchievements = achievementsByGroup[group].length + const completedAchievements = achievementsByGroup[group].filter((id) => player.achievements[id] === 1).length + img.classList.remove('green-background', 'purple-background') + img.style.setProperty('border', 'none') + + // Optional: Add visual styling based on completion + img.classList.remove('green-background') + if (completedAchievements === totalAchievements) { + img.classList.add('green-background') } + + img.style.setProperty('--pct', `${completedAchievements}/${totalAchievements}`) } } -export const getAchievementQuarks = (i: number) => { - let multiplier = 1 - if (i >= 183) { - multiplier = 5 +export const updateAllGroupedAchievementProgress = () => { + for (const k of Object.keys(achievementsByGroup) as AchievementGroups[]) { + if (k === 'ungrouped') { + continue + } + updateGroupedAchievementProgress(k) } - if (i >= 253) { - multiplier = 40 +} + +export const updateUngroupedAchievementProgress = (id: number) => { + const capitalizedName = Object.keys(ungroupedAchievementData).find((k) => + ungroupedAchievementData[k as UngroupedAchievementNames].achievementID === id + ) + if (!capitalizedName) { + throw new Error(`Ungrouped achievement with ID ${id} not found`) } - const globalQuarkMultiplier = player.worlds.applyBonus(1) - let actualMultiplier = multiplier * globalQuarkMultiplier - if (actualMultiplier > 100) { - actualMultiplier = Math.pow(100, 0.6) * Math.pow(actualMultiplier, 0.4) + const img = DOMCacheGetOrSet( + `ungroupedAchievement${capitalizedName.charAt(0).toUpperCase() + capitalizedName.slice(1)}` + ) as HTMLElement + + if (img) { + const isCompleted = player.achievements[id] === 1 + img.classList.remove('green-background') + + if (isCompleted) { + img.classList.add('green-background') + } } +} - return Math.floor(achievementpointvalues[i] * actualMultiplier) +export const updateAllUngroupedAchievementProgress = () => { + for (const ach of Object.values(ungroupedAchievementData)) { + updateUngroupedAchievementProgress(ach.achievementID) + } } -export const achievementdescriptions = (i: number) => { - const y = i18next.t(`achievements.descriptions.${i}`) - const z = player.achievements[i] > 0.5 ? i18next.t('achievements.completed') : '' - const k = areward(i) +export const updateProgressiveAchievementProgress = (progAch: ProgressiveAchievements) => { + const capitalizedName = progAch.charAt(0).toUpperCase() + progAch.slice(1) + const img = DOMCacheGetOrSet(`progressiveAchievement${capitalizedName}`) as HTMLElement - DOMCacheGetOrSet('achievementdescription').textContent = y + z - DOMCacheGetOrSet('achievementreward').textContent = i18next.t('achievements.rewardGainMessage', { - x: achievementpointvalues[i], - y: format(getAchievementQuarks(i), 0, true), - z: k - }) + if (img) { + const achData = progressiveAchievements[progAch] - if (player.achievements[i] > 0.5) { - DOMCacheGetOrSet('achievementdescription').style.color = 'gold' - } else { - DOMCacheGetOrSet('achievementdescription').style.color = 'white' + // Infinite progression implies we cannot define a percentage + if (achData.maxPointValue === -1) { + return + } + + const currentAP = progressiveAchievements[progAch].rewardedAP + const maxAP = achData.maxPointValue + + img.classList.remove('green-background') + + // Add green background if fully completed + if (currentAP >= maxAP) { + img.classList.add('green-background') + } + + // Set progress percentage + img.style.setProperty('--pct', `${currentAP}/${maxAP}`) } } -export const achievementaward = (num: number) => { - if (player.achievements[num] < 1) { - if (player.toggles[34]) { - const description = i18next.t(`achievements.descriptions.${num}`) - void Notification(i18next.t('achievements.notification', { m: description })) +export const updateAllProgressiveAchievementProgress = () => { + for (const k of Object.keys(progressiveAchievements) as ProgressiveAchievements[]) { + updateProgressiveAchievementProgress(k) + } +} + +export const displayAchievementProgress = () => { + // Display Grouped Achievements AP + for (const k of achievementGroupKeys) { + if (k === 'ungrouped') { + continue + } + + // Do not display data on locked groups + if (!groupedAchievementData[k].displayCondition()) { + continue } - void achievementAlerts(num) - player.achievementPoints += achievementpointvalues[num] - player.worlds.add(getAchievementQuarks(num), false) + const capitalizedName = k.charAt(0).toUpperCase() + k.slice(1) + const img = DOMCacheGetOrSet(`achievementGroup${capitalizedName}`) as HTMLElement + const parent = img.parentElement! - DOMCacheGetOrSet('achievementprogress').textContent = i18next.t('achievements.totalPoints', { - x: format(player.achievementPoints), - y: format(totalachievementpoints), - z: (100 * player.achievementPoints / totalachievementpoints).toPrecision(4) - }) + if (img) { + // Calculate earned and total AP for this group + let earnedAP = 0 + let totalAP = 0 - DOMCacheGetOrSet('achievementQuarkBonus').innerHTML = i18next.t('achievements.quarkBonus', { - multiplier: format(1 + player.achievementPoints / 50000, 3, true) - }) + for (const achievementId of achievementsByGroup[k]) { + const pointValue = achievements[achievementId].pointValue + totalAP += pointValue + if (player.achievements[achievementId]) { + earnedAP += pointValue + } + } - player.achievements[num] = 1 - revealStuff() + img.classList.add('dimmed') + + // Remove any existing overlay first + const existingOverlay = parent.querySelector('.achievement-ap-overlay') + if (existingOverlay) { + existingOverlay.remove() + } + + // Create new AP overlay with fraction format + const apOverlay = document.createElement('div') + apOverlay.classList.add('achievement-ap-overlay') + + // Add gold text if AP is maxed + if (earnedAP === totalAP) { + apOverlay.classList.add('gold-text') + } + + const numerator = document.createElement('div') + numerator.classList.add('achievement-fraction-numerator') + numerator.textContent = earnedAP.toString() + + const denominator = document.createElement('div') + denominator.classList.add('achievement-fraction-denominator') + denominator.textContent = totalAP.toString() + + apOverlay.appendChild(numerator) + apOverlay.appendChild(denominator) + + parent.classList.add('relative-container') + parent.appendChild(apOverlay) + } } - DOMCacheGetOrSet(`ach${num}`).classList.add('green-background') + // Display Ungrouped Achievements AP + for (const k of ungroupedAchievementKeys) { + // Do not display data on locked ungrouped achievements + if (!ungroupedAchievementData[k].displayCondition()) { + continue + } + + const capitalizedName = k.charAt(0).toUpperCase() + k.slice(1) + const img = DOMCacheGetOrSet(`ungroupedAchievement${capitalizedName}`) as HTMLElement + const parent = img.parentElement! + + if (img) { + const achievementId = ungroupedAchievementData[k as UngroupedAchievementNames].achievementID + const isCompleted = player.achievements[achievementId] === 1 + const pointValue = achievements[achievementId].pointValue + const earnedAP = isCompleted ? pointValue : 0 + + img.classList.add('dimmed') + + // Remove any existing overlay first + const existingOverlay = parent.querySelector('.achievement-ap-overlay') + if (existingOverlay) { + existingOverlay.remove() + } + + // Create new AP overlay with simple number + const apOverlay = document.createElement('div') + apOverlay.classList.add('achievement-ap-overlay') + + // Add gold text if achievement is completed + if (isCompleted) { + apOverlay.classList.add('gold-text') + } + + apOverlay.textContent = earnedAP.toString() + + parent.classList.add('relative-container') + parent.appendChild(apOverlay) + } + } + + // Display Progressive Achievements AP + for (const k of progressiveAchievementKeys) { + // Do not display data on locked progressive achievements + if (!progressiveAchievements[k].displayCondition()) { + continue + } + + const capitalizedName = k.charAt(0).toUpperCase() + k.slice(1) + const img = DOMCacheGetOrSet(`progressiveAchievement${capitalizedName}`) as HTMLElement + const parent = img.parentElement! + + if (img) { + const achData = progressiveAchievements[k] + const currentAP = achData.rewardedAP + const maxAP = achData.maxPointValue + + img.classList.add('dimmed') + + // Remove any existing overlay first + const existingOverlay = parent.querySelector('.achievement-ap-overlay') + if (existingOverlay) { + existingOverlay.remove() + } + + // Create new AP overlay + const apOverlay = document.createElement('div') + apOverlay.classList.add('achievement-ap-overlay') + + if (maxAP === -1) { + // Simple number for infinite progression (no gold text possible) + apOverlay.textContent = currentAP.toString() + } else { + // Fraction format for finite progression + // Add gold text if AP is maxed + if (currentAP >= maxAP) { + apOverlay.classList.add('gold-text') + } + + const numerator = document.createElement('div') + numerator.classList.add('achievement-fraction-numerator') + numerator.textContent = currentAP.toString() + + const denominator = document.createElement('div') + denominator.classList.add('achievement-fraction-denominator') + denominator.textContent = maxAP.toString() + + apOverlay.appendChild(numerator) + apOverlay.appendChild(denominator) + } + + parent.classList.add('relative-container') + parent.appendChild(apOverlay) + } + } +} + +export const resetAchievementProgressDisplay = () => { + // Reset all achievement types + const selectors = [ + '.tieredAchievementType', + '.ungroupedAchievementType', + '.progressiveAchievementType' + ] + + selectors.forEach((selector) => { + const elements = document.querySelectorAll(selector) + elements.forEach((element) => { + const img = element.querySelector('img') + if (img) { + img.classList.remove('dimmed') + } + + // Remove the AP overlay if it exists + const apOverlay = element.querySelector('.achievement-ap-overlay') + if (apOverlay) { + apOverlay.remove() + element.classList.remove('relative-container') + } + }) + }) +} + +// You should really only use this when the game is reset. +export const resetAchievements = () => { + player.achievements.fill(0) + for (const k of Object.keys(progressiveAchievements) as ProgressiveAchievements[]) { + player.progressiveAchievements[k] = 0 + progressiveAchievements[k].rewardedAP = 0 + } + achievementPoints = 0 + achievementLevel = 0 } diff --git a/src/Ants.ts b/src/Ants.ts index 2b3820d1b..ce85af73f 100644 --- a/src/Ants.ts +++ b/src/Ants.ts @@ -1,8 +1,11 @@ import { calculateAnts, - calculateAntSacrificeELO, + calculateAntSacrificeMultiplier, + calculateAntSacrificeObtainiumMultiplier, + calculateAntSacrificeOfferingMultiplier, calculateAntSacrificeRewards, - calculateRuneLevels, + calculateBaseAntELO, + calculateEffectiveAntELO, calculateSigmoid, calculateSigmoidExponential } from './Calculate' @@ -12,11 +15,11 @@ import { Globals as G } from './Variables' import type { DecimalSource } from 'break_infinity.js' import Decimal from 'break_infinity.js' import i18next from 'i18next' -import { achievementaward } from './Achievements' +import { awardAchievementGroup, awardUngroupedAchievement, getAchievementReward } from './Achievements' import { DOMCacheGetOrSet } from './Cache/DOM' import { resetHistoryAdd, type ResetHistoryEntryAntSacrifice } from './History' -import { buyResearch } from './Research' -import { resetAnts } from './Reset' +import { reset, resetAnts } from './Reset' +import { offeringObtainiumTimeModifiers } from './Statistics' import { Tabs } from './Tabs' import { updateTalismanInventory } from './Talismans' import { clearInterval, setInterval } from './Timers' @@ -60,7 +63,7 @@ const antUpgradeTexts = [ () => format(Math.min(9999999, 3 * player.antUpgrades[7 - 1]! + 3 * G.bonusant7), 0, true), () => format(calculateSigmoidExponential(999, 1 / 10000 * Math.pow(player.antUpgrades[8 - 1]! + G.bonusant8, 1.1)), 3), - () => format(1 * Math.min(1e7, player.antUpgrades[9 - 1]! + G.bonusant9), 0, true), + () => format(1 * Math.min(3000, player.antUpgrades[9 - 1]! + G.bonusant9), 0, true), () => format(1 + 2 * Math.pow((player.antUpgrades[10 - 1]! + G.bonusant10) / 50, 0.75), 4), () => format(1 + 2 * (1 - Math.pow(2, -(player.antUpgrades[11 - 1]! + G.bonusant11) / 125)), 4), () => format(calculateSigmoid(2, player.antUpgrades[12 - 1]! + G.bonusant12, 69), 4) @@ -142,7 +145,7 @@ const getAntCost = (originalCost: Decimal, buyTo: number, index: number) => { return cost } -const getAntUpgradeCost = (originalCost: Decimal, buyTo: number, index: number) => { +export const getAntUpgradeCost = (originalCost: Decimal, buyTo: number, index: number) => { ;--buyTo const cost = originalCost.times(Decimal.pow(G.antUpgradeCostIncreases[index - 1], buyTo)) @@ -151,7 +154,6 @@ const getAntUpgradeCost = (originalCost: Decimal, buyTo: number, index: number) // Note to self: REWRITE THIS SHIT Kevin :3 export const buyAntProducers = (pos: FirstToEighth, originalCost: DecimalSource, index: number) => { - const sacrificeMult = antSacrificePointsToMultiplier(player.antSacrificePoints) // This is a fucking cool function. This will buymax ants cus why not // Things we need: the position of producers, the costvalues, and input var i @@ -198,18 +200,8 @@ export const buyAntProducers = (pos: FirstToEighth, originalCost: DecimalSource, if (player.antPoints.lt(0)) { player.antPoints = new Decimal('0') } - calculateAntSacrificeELO() - // Check if we award Achievement 176-182: Ant autobuy - const achRequirements = [2, 6, 20, 100, 500, 6666, 77777] - for (let j = 0; j < achRequirements.length; j++) { - if ( - player.achievements[176 + j] === 0 && sacrificeMult > achRequirements[j] - && player[`${G.ordinals[j + 1 as ZeroToSeven]}OwnedAnts` as const] > 0 - ) { - achievementaward(176 + j) - } - } + awardAchievementGroup('sacMult') if (player.firstOwnedAnts > 6.9e7) { player.firstOwnedAnts = 6.9e7 @@ -250,8 +242,6 @@ export const buyAntUpgrade = (originalCost: DecimalSource, auto: boolean, index: thisCost = getAntUpgradeCost(originalCost, buyFrom, index) } calculateAnts() - calculateRuneLevels() - calculateAntSacrificeELO() if (!auto) { antUpgradeDescription(index) } @@ -281,10 +271,7 @@ export const antUpgradeDescription = (i: number) => { x: format( Decimal.pow( G.antUpgradeCostIncreases[i - 1], - // NOTE: This seems to have always been broken, in the worst way - // This corruption was previously never used, so it was never noticed - // But now it will be used and thus have major balancing issues - player.antUpgrades[i - 1]! * G.extinctionMultiplier[player.corruptions.used.extinction] + player.antUpgrades[i - 1]! ).times(G.antUpgradeBaseCost[i - 1]) ) }) @@ -294,21 +281,28 @@ export const antUpgradeDescription = (i: number) => { } export const antSacrificePointsToMultiplier = (points: number) => { - let multiplier = Math.pow(1 + points / 5000, 2) - multiplier *= 1 + 0.2 * Math.log(1 + points) / Math.log(10) - if (player.achievements[174] > 0) { - multiplier *= 1 + 0.4 * Math.log(1 + points) / Math.log(10) + const base = 1 + points / 5000 + const maxExponent = 4 + const exponent = Math.min(maxExponent, 1.5 + 0.2 * Math.log(1 + points / 10000)) + if (points > 0) { + return Decimal.pow(base, exponent).times(2) } - return Math.min(1e300, multiplier) + return new Decimal(1) } export const showSacrifice = () => { const sacRewards = calculateAntSacrificeRewards() DOMCacheGetOrSet('antSacrificeSummary').style.display = 'block' + const baseELO = calculateBaseAntELO() + const effectiveELO = calculateEffectiveAntELO(baseELO) + + const timeMultiplier = offeringObtainiumTimeModifiers(player.antSacrificeTimer, true).reduce( + (a, b) => a * b.stat(), + 1 + ) DOMCacheGetOrSet('ELO').innerHTML = i18next.t('ants.yourAntELO', { - x: format(G.antELO, 2), - y: format(G.effectiveELO, 2, false) + x: format(effectiveELO, 2, true) }) DOMCacheGetOrSet('SacrificeMultiplier').innerHTML = i18next.t('ants.antSacMultiplier', { @@ -317,11 +311,30 @@ export const showSacrifice = () => { }) DOMCacheGetOrSet('SacrificeUpgradeMultiplier').innerHTML = i18next.t('ants.upgradeMultiplier', { - x: format(G.upgradeMultiplier, 3, true) + x: format(calculateAntSacrificeMultiplier(), 3, true) }) DOMCacheGetOrSet('SacrificeTimeMultiplier').innerHTML = i18next.t('ants.timeMultiplier', { - x: format(G.timeMultiplier, 3, true) + x: format(timeMultiplier, 3, true) + }) + + DOMCacheGetOrSet('immortalELO').innerHTML = i18next.t('ants.immortalELO', { + x: format(player.antSacrificePoints, 0, true) + }) + DOMCacheGetOrSet('immortalELOAntSpeed').innerHTML = i18next.t('ants.immortalELOAntSpeed', { + x: format(antSacrificePointsToMultiplier(player.antSacrificePoints), 3, false) + }) + + DOMCacheGetOrSet('immortalELOGain').innerHTML = i18next.t('ants.immortalELOGain', { + x: format(sacRewards.antSacrificePoints, 0, true) + }) + + DOMCacheGetOrSet('SacrificeOfferingMultiplier').innerHTML = i18next.t('ants.offeringMultiplier', { + x: format(calculateAntSacrificeOfferingMultiplier().plus(1), 3, true) + }) + + DOMCacheGetOrSet('SacrificeObtainiumMultiplier').innerHTML = i18next.t('ants.obtainiumMultiplier', { + x: format(calculateAntSacrificeObtainiumMultiplier().plus(1), 3, true) }) DOMCacheGetOrSet('antSacrificeOffering').textContent = `+${format(sacRewards.offerings)}` @@ -370,12 +383,15 @@ export const sacrificeAnts = async (auto = false) => { const sacRewards = calculateAntSacrificeRewards() player.antSacrificePoints += sacRewards.antSacrificePoints - player.runeshards += sacRewards.offerings + player.offerings = player.offerings.add(sacRewards.offerings) if (player.currentChallenge.ascension !== 14) { - player.researchPoints += sacRewards.obtainium + player.obtainium = player.obtainium.add(sacRewards.obtainium) } + const baseELO = calculateBaseAntELO() + const effectiveELO = calculateEffectiveAntELO(baseELO) + const historyEntry: ResetHistoryEntryAntSacrifice = { date: Date.now(), seconds: player.antSacrificeTimer, @@ -384,20 +400,20 @@ export const sacrificeAnts = async (auto = false) => { obtainium: sacRewards.obtainium, antSacrificePointsBefore, antSacrificePointsAfter: player.antSacrificePoints, - baseELO: G.antELO, - effectiveELO: G.effectiveELO, + baseELO: baseELO, + effectiveELO: effectiveELO, crumbs: player.antPoints.toString(), crumbsPerSecond: G.antOneProduce.toString() } if (player.challengecompletions[9] > 0) { - player.talismanShards = Math.min(1e300, player.talismanShards + sacRewards.talismanShards) - player.commonFragments = Math.min(1e300, player.commonFragments + sacRewards.commonFragments) - player.uncommonFragments = Math.min(1e300, player.uncommonFragments + sacRewards.uncommonFragments) - player.rareFragments = Math.min(1e300, player.rareFragments + sacRewards.rareFragments) - player.epicFragments = Math.min(1e300, player.epicFragments + sacRewards.epicFragments) - player.legendaryFragments = Math.min(1e300, player.legendaryFragments + sacRewards.legendaryFragments) - player.mythicalFragments = Math.min(1e300, player.mythicalFragments + sacRewards.mythicalFragments) + player.talismanShards = player.talismanShards.add(sacRewards.talismanShards) + player.commonFragments = player.commonFragments.add(sacRewards.commonFragments) + player.uncommonFragments = player.uncommonFragments.add(sacRewards.uncommonFragments) + player.rareFragments = player.rareFragments.add(sacRewards.rareFragments) + player.epicFragments = player.epicFragments.add(sacRewards.epicFragments) + player.legendaryFragments = player.legendaryFragments.add(sacRewards.legendaryFragments) + player.mythicalFragments = player.mythicalFragments.add(sacRewards.mythicalFragments) } // Now we're safe to reset the ants. @@ -405,18 +421,15 @@ export const sacrificeAnts = async (auto = false) => { player.antSacrificeTimer = 0 player.antSacrificeTimerReal = 0 updateTalismanInventory() - if (player.autoResearch > 0 && player.autoResearchToggle) { - const linGrowth = (player.autoResearch === 200) ? 0.01 : 0 - buyResearch(player.autoResearch, true, linGrowth) - } - calculateAntSacrificeELO() - resetHistoryAdd('ants', historyEntry) + + // v4: Perform a Reincarnation, as advertised. + reset('reincarnation', auto) } } - if (player.mythicalFragments >= 1e11 && player.currentChallenge.ascension === 14 && player.achievements[248] < 1) { - achievementaward(248) + if (player.mythicalFragments.gte(1e11) && player.currentChallenge.ascension === 14) { + awardUngroupedAchievement('seeingRedNoBlue') } } @@ -425,24 +438,23 @@ export const autoBuyAnts = () => { player.antPoints.gte( getAntUpgradeCost(new Decimal(G.antUpgradeBaseCost[x - 1]), player.antUpgrades[x - 1]! + 1, x).times(m) ) - const ach = [176, 176, 177, 178, 178, 179, 180, 180, 181, 182, 182, 145] + const cost = ['100', '100', '1000', '1000', '1e5', '1e6', '1e8', '1e11', '1e15', '1e20', '1e40', '1e100'] if (player.currentChallenge.ascension !== 11) { - for (let i = 1; i <= ach.length; i++) { - const check = i === 12 ? player.researches[ach[i - 1]] : player.achievements[ach[i - 1]] + for (let i = 1; i <= 12; i++) { + const check = i === 12 ? player.researches[145] > 0 : +getAchievementReward('antUpgradeAutobuyers') >= i if (check && canAffordUpgrade(i, 2)) { buyAntUpgrade(cost[i - 1], true, i) } } } - const _ach = [173, 176, 177, 178, 179, 180, 181, 182] const _cost = ['1e700', '3', '100', '10000', '1e12', '1e36', '1e100', '1e300'] - for (let i = 1; i <= _ach.length; i++) { + for (let i = 1; i <= 8; i++) { const res = i === 1 ? player.reincarnationPoints : player.antPoints const m = i === 1 ? 1 : 2 // no multiplier on the first ant cost because it costs particles if ( - player.achievements[_ach[i - 1]] + +getAchievementReward('antAutobuyers') >= i && res.gte(player[`${G.ordinals[i - 1 as ZeroToSeven]}CostAnts` as const].times(m)) ) { buyAntProducers( diff --git a/src/Automation.ts b/src/Automation.ts index 0b203f659..963eafd34 100644 --- a/src/Automation.ts +++ b/src/Automation.ts @@ -1,5 +1,5 @@ import Decimal from 'break_infinity.js' -import { achievementaward } from './Achievements' +import { awardUngroupedAchievement, getAchievementReward } from './Achievements' import { buyUpgrades } from './Buy' import { player } from './Synergism' import { clickUpgrades, upgradeupdate } from './Upgrades' @@ -18,21 +18,15 @@ export const buyGenerator = (i: number, state: boolean) => { } const cost = Decimal.pow(10, G.upgradeCosts[q]) - const achievementCheck = Math.max( - player.upgrades[101], - player.upgrades[102], - player.upgrades[103], - player.upgrades[104], - player.upgrades[105] - ) if (player.upgrades[q] === 0 && player[type].gte(cost)) { - if (achievementCheck === 0 && q >= 102 && q <= 105) { - achievementaward(q - 31) - } player[type] = player[type].sub(cost) player.upgrades[q] = 1 upgradeupdate(q, state) + awardUngroupedAchievement('generationAch1') + awardUngroupedAchievement('generationAch2') + awardUngroupedAchievement('generationAch3') + awardUngroupedAchievement('generationAch4') } } @@ -102,19 +96,19 @@ export const autoUpgrades = () => { } if ( player.upgrades[38] === 0 && player.prestigePoints.gte(Decimal.pow(10, 50000)) && player.shoptoggles.prestige - && player.achievements[120] === 1 + && getAchievementReward('diamondUpgrade18') ) { buyUpgrades(Upgrade.prestige, 38, true) } if ( player.upgrades[39] === 0 && player.prestigePoints.gte(Decimal.pow(10, 100000)) && player.shoptoggles.prestige - && player.achievements[127] === 1 + && getAchievementReward('diamondUpgrade19') ) { buyUpgrades(Upgrade.prestige, 39, true) } if ( player.upgrades[40] === 0 && player.prestigePoints.gte(Decimal.pow(10, 200000)) && player.shoptoggles.prestige - && player.achievements[134] === 1 + && getAchievementReward('diamondUpgrade20') ) { buyUpgrades(Upgrade.prestige, 40, true) } diff --git a/src/BlueberryUpgrades.ts b/src/BlueberryUpgrades.ts index 921b91fa5..c447611b0 100644 --- a/src/BlueberryUpgrades.ts +++ b/src/BlueberryUpgrades.ts @@ -1,501 +1,276 @@ import i18next from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' import { calculateAmbrosiaLuck, calculateBlueberryInventory } from './Calculate' -import { DynamicUpgrade } from './DynamicUpgrade' -import type { IUpgradeData } from './DynamicUpgrade' import { exportData, saveFilename } from './ImportExport' import { PCoinUpgradeEffects } from './PseudoCoinUpgrades' import { getQuarkBonus } from './Quark' -import { getRedAmbrosiaUpgrade } from './RedAmbrosiaUpgrades' -import { format, player } from './Synergism' -import type { Player } from './types/Synergism' +import { getRedAmbrosiaUpgradeEffects } from './RedAmbrosiaUpgrades' +import { format, formatAsPercentIncrease, player } from './Synergism' import { Alert, Confirm, Prompt } from './UpdateHTML' -import { visualUpdateAmbrosia } from './UpdateVisuals' -import { assert } from './Utility' - -export type blueberryUpgradeNames = - | 'ambrosiaTutorial' - | 'ambrosiaQuarks1' - | 'ambrosiaCubes1' - | 'ambrosiaLuck1' - | 'ambrosiaCubeLuck1' - | 'ambrosiaQuarkLuck1' - | 'ambrosiaQuarkCube1' - | 'ambrosiaLuckCube1' - | 'ambrosiaCubeQuark1' - | 'ambrosiaLuckQuark1' - | 'ambrosiaQuarks2' - | 'ambrosiaCubes2' - | 'ambrosiaLuck2' - | 'ambrosiaQuarks3' - | 'ambrosiaCubes3' - | 'ambrosiaLuck3' - | 'ambrosiaObtainium1' - | 'ambrosiaOffering1' - | 'ambrosiaBaseOffering1' - | 'ambrosiaBaseObtainium1' - | 'ambrosiaBaseOffering2' - | 'ambrosiaBaseObtainium2' - | 'ambrosiaHyperflux' - | 'ambrosiaSingReduction1' - | 'ambrosiaInfiniteShopUpgrades1' - | 'ambrosiaInfiniteShopUpgrades2' - | 'ambrosiaSingReduction2' - -export type BlueberryOpt = Partial> +import { assert, isMobile } from './Utility' + +export type BlueberryOpt = Partial> export type BlueberryLoadoutMode = 'saveTree' | 'loadTree' -export interface IBlueberryData extends Omit { - costFormula(this: void, level: number, baseCost: number): number - rewards(this: void, n: number): Record - extraLevelCalc: () => number - blueberryCost: number - ambrosiaInvested?: number - blueberriesInvested?: number - prerequisites?: BlueberryOpt - cacheUpdates?: (() => void)[] // TODO: Improve this type signature -Plat - ignoreEXALT?: boolean +type AmbrosiaUpgradeRewards = { + ambrosiaTutorial: { quarks: number; cubes: number } + ambrosiaQuarks1: { quarks: number } + ambrosiaCubes1: { cubes: number } + ambrosiaLuck1: { ambrosiaLuck: number } + ambrosiaQuarkCube1: { cubes: number } + ambrosiaLuckCube1: { cubes: number } + ambrosiaCubeQuark1: { quarks: number } + ambrosiaLuckQuark1: { quarks: number } + ambrosiaCubeLuck1: { ambrosiaLuck: number } + ambrosiaQuarkLuck1: { ambrosiaLuck: number } + ambrosiaQuarks2: { quarks: number } + ambrosiaCubes2: { cubes: number } + ambrosiaLuck2: { ambrosiaLuck: number } + ambrosiaQuarks3: { quarks: number } + ambrosiaCubes3: { cubes: number } + ambrosiaLuck3: { ambrosiaLuck: number } + ambrosiaLuck4: { ambrosiaLuckPercentage: number } + ambrosiaPatreon: { blueberryGeneration: number } + ambrosiaObtainium1: { luckMult: number; obtainiumMult: number } + ambrosiaOffering1: { luckMult: number; offeringMult: number } + ambrosiaHyperflux: { hyperFlux: number } + ambrosiaBaseOffering1: { offering: number } + ambrosiaBaseObtainium1: { obtainium: number } + ambrosiaBaseOffering2: { offering: number } + ambrosiaBaseObtainium2: { obtainium: number } + ambrosiaSingReduction1: { singularityReduction: number } + ambrosiaInfiniteShopUpgrades1: { freeLevels: number } + ambrosiaInfiniteShopUpgrades2: { freeLevels: number } + ambrosiaSingReduction2: { singularityReduction: number } + ambrosiaTalismanBonusRuneLevel: { talismanBonusRuneLevel: number } + ambrosiaRuneOOMBonus: { runeOOMBonus: number; infiniteAscentOOMBonus: number } } -export class BlueberryUpgrade extends DynamicUpgrade { - readonly costFormula: (level: number, baseCost: number) => number - readonly rewards: (n: number) => Record - public ambrosiaInvested = 0 - public blueberriesInvested = 0 - public blueberryCost: number - readonly preRequisites: BlueberryOpt | undefined - readonly cacheUpdates: (() => void)[] | undefined - readonly extraLevelCalc: () => number - readonly ignoreEXALT: boolean - #key: string - - constructor (data: IBlueberryData, key: string) { - const name = i18next.t(`ambrosia.data.${key}.name`) - const description = i18next.t(`ambrosia.data.${key}.description`) - - super({ ...data, name, description }) - this.blueberryCost = data.blueberryCost - this.costFormula = data.costFormula - this.rewards = data.rewards - this.extraLevelCalc = data.extraLevelCalc - this.ambrosiaInvested = data.ambrosiaInvested ?? 0 - this.blueberriesInvested = data.blueberriesInvested ?? 0 - this.preRequisites = data.prerequisites ?? undefined - this.cacheUpdates = data.cacheUpdates ?? undefined - this.ignoreEXALT = data.ignoreEXALT ?? false - this.#key = key - } - - getCostTNL (): number { - if (this.level === this.maxLevel) { - return 0 - } - return this.costFormula(this.level, this.costPerLevel) - } - - /** - * Buy levels up until togglebuy or maxed. - * @returns An alert indicating cannot afford, already maxed or purchased with how many - * levels purchased - */ - public async buyLevel (event: MouseEvent): Promise { - let purchased = 0 - let maxPurchasable = 1 - let ambrosiaBudget = player.ambrosia - - if (!this.checkPrerequisites()) { - return Alert(i18next.t('ambrosia.prereqNotMetAlert')) - } - - if (event.shiftKey) { - maxPurchasable = 1000000 - const buy = Number( - await Prompt( - i18next.t('ambrosia.ambrosiaBuyPrompt', { - amount: format(player.ambrosia, 0, true) - }) - ) - ) - - if (isNaN(buy) || !isFinite(buy) || !Number.isInteger(buy)) { - // nan + Infinity checks - return Alert(i18next.t('general.validation.finite')) - } - - if (buy === -1) { - ambrosiaBudget = player.ambrosia - } else if (buy <= 0) { - return Alert(i18next.t('octeract.buyLevel.cancelPurchase')) // For some reason this is in the Octeract section (???) - } else { - ambrosiaBudget = buy - } - ambrosiaBudget = Math.min(player.ambrosia, ambrosiaBudget) - } - - if (this.maxLevel > 0) { - maxPurchasable = Math.min(maxPurchasable, this.maxLevel - this.level) - } - - if (maxPurchasable === 0) { - return Alert(i18next.t('octeract.buyLevel.alreadyMax')) // Once again - } - - while (maxPurchasable > 0) { - const cost = this.getCostTNL() - if (player.ambrosia < cost || ambrosiaBudget < cost) { - break - } else { - if (this.level === 0) { - const availableBlueberries = calculateBlueberryInventory() - player.spentBlueberries - if (availableBlueberries < this.blueberryCost) { - return Alert(i18next.t('ambrosia.notEnoughBlueberries')) - } else { - player.spentBlueberries += this.blueberryCost - this.blueberriesInvested = this.blueberryCost - } - } - player.ambrosia -= cost - ambrosiaBudget -= cost - this.ambrosiaInvested += cost - this.level += 1 - purchased += 1 - maxPurchasable -= 1 - } - } - - if (purchased === 0) { - return Alert(i18next.t('octeract.buyLevel.cannotAfford')) - } - if (purchased > 1) { - return Alert( - `${i18next.t('octeract.buyLevel.multiBuy', { n: format(purchased) })}` - ) - } - - this.updateUpgradeHTML() - this.updateCaches() - } - - toString (): string { - const costNextLevel = this.getCostTNL() - const maxLevel = this.maxLevel === -1 ? '' : `/${format(this.maxLevel, 0, true)}` - const isMaxLevel = this.maxLevel === this.level - const color = isMaxLevel ? 'plum' : 'white' - - const freeLevelInfo = this.extraLevels > 0 - ? ` [+${ - format( - this.extraLevels, - 0, - true - ) - }]` - : '' - - const isAffordable = costNextLevel <= player.ambrosia - const affordableInfo = isMaxLevel - ? ` ${i18next.t('general.maxed')}` - : isAffordable - ? ` ${ - i18next.t( - 'general.affordable' - ) - }` - : ` ${ - i18next.t( - 'octeract.buyLevel.cannotAfford' - ) - }` - - let preReqText: string | undefined - - if (this.preRequisites !== undefined) { - preReqText = String(i18next.t('ambrosia.prerequisite')) - for (const [prereq, val] of Object.entries(this.preRequisites)) { - const k = prereq as keyof Player['blueberryUpgrades'] - const color = player.blueberryUpgrades[k].level >= val ? 'green' : 'red' - const met = player.blueberryUpgrades[k].level >= val - ? '' - : i18next.t('ambrosia.prereqNotMet') - preReqText = `${preReqText} ${ - player.blueberryUpgrades[k].name - } lv.${val} ${met} |` - } - - preReqText = preReqText.slice(0, -1) - } - - const costNextLevelStr = i18next.t('octeract.toString.costNextLevel', { - amount: `${format(costNextLevel, 0, true, true, true)}`, - resource: i18next.t('ambrosia.ambrosia') - }) - - return `${this.name} - ${ - i18next.t( - 'general.level' - ) - } ${format(this.level, 0, true)}${maxLevel}${freeLevelInfo}${preReqText ? `\n ${preReqText}` : ''}${ - this.ignoreEXALT ? `\n ${i18next.t('ambrosia.ignoreEXALT')}\n` : '\n' - }${this.description} - ${this.rewardDesc} - ${costNextLevelStr} ${affordableInfo} - ${ - i18next.t( - 'ambrosia.blueberryCost' - ) - } ${this.blueberryCost} - ${i18next.t('general.spent')} ${ - i18next.t( - 'ambrosia.ambrosia' - ) - }: ${ - format( - this.ambrosiaInvested, - 0, - true, - true, - true - ) - }` - } - - updateUpgradeHTML (): void { - DOMCacheGetOrSet('singularityAmbrosiaMultiline').innerHTML = this.toString() - visualUpdateAmbrosia() - } - - checkPrerequisites (): boolean { - if (this.preRequisites !== undefined) { - for (const [prereq, val] of Object.entries(this.preRequisites)) { - const k = prereq as keyof Player['blueberryUpgrades'] - if (player.blueberryUpgrades[k].level < val) { - return false - } - } - } - return true - } - - updateCaches (): void { - if (this.cacheUpdates !== undefined) { - for (const cache of this.cacheUpdates) { - cache() - } - } - } - - refund (): void { - player.ambrosia += this.ambrosiaInvested - this.ambrosiaInvested = 0 - this.level = 0 - - player.spentBlueberries -= this.blueberriesInvested - this.blueberriesInvested = 0 - } - - get extraLevels (): number { - return this.extraLevelCalc() - } - - get effectiveLevels (): number { - return ((player.singularityChallenges.noAmbrosiaUpgrades.enabled - || player.singularityChallenges.sadisticPrequel.enabled) && !this.ignoreEXALT) - ? 0 - : this.level + this.extraLevels - } - - public get rewardDesc (): string { - if ('desc' in this.rewards(0)) { - return String(this.rewards(this.effectiveLevels).desc) - } else { - return 'Contact Platonic or Khafra if you see this (should never occur!)' - } - } - - public get bonus () { - return this.rewards(this.effectiveLevels) - } - - valueOf (): IBlueberryData { - return { - blueberryCost: this.blueberryCost, - costFormula: this.costFormula, - costPerLevel: this.costPerLevel, - maxLevel: this.maxLevel, - rewards: this.rewards, - extraLevelCalc: this.extraLevelCalc, - ambrosiaInvested: this.ambrosiaInvested, - blueberriesInvested: this.blueberriesInvested, - cacheUpdates: this.cacheUpdates, - freeLevels: this.freeLevels, - level: this.level, - prerequisites: this.preRequisites, - toggleBuy: this.toggleBuy - } - } - - key () { - return this.#key - } +export type AmbrosiaUpgradeNames = keyof AmbrosiaUpgradeRewards + +export interface AmbrosiaUpgrade { + name: () => string + description: () => string + level: number + maxLevel: number + costPerLevel: number + costFormula: (level: number, baseCost: number) => number + effects: (n: number) => AmbrosiaUpgradeRewards[T] + effectsDescription: (n: number) => string + extraLevelCalc: () => number + ambrosiaInvested: number + blueberriesInvested: number + blueberryCost: number + prerequisites: BlueberryOpt + ignoreEXALT: boolean } -export const blueberryUpgradeData: Record< - keyof Player['blueberryUpgrades'], - IBlueberryData -> = { +export const ambrosiaUpgrades: { + [K in AmbrosiaUpgradeNames]: AmbrosiaUpgrade +} = { ambrosiaTutorial: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 10, costPerLevel: 1, blueberryCost: 0, + ignoreEXALT: false, + prerequisites: {}, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 2) - Math.pow(level, 2)) }, - rewards: (n: number) => { + effects: (n: number) => { const cubeAmount = 1 + 0.05 * n const quarkAmount = 1 + 0.01 * n return { quarks: quarkAmount, - cubes: cubeAmount, - desc: String( - i18next.t('ambrosia.data.ambrosiaTutorial.effect', { - cubeAmount: format(100 * (cubeAmount - 1), 0, true), - quarkAmount: format(100 * (quarkAmount - 1), 0, true) - }) - ) + cubes: cubeAmount } }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeTutorialLevels').bonus.freeLevels + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaTutorial.effect', { + cubeAmount: format(100 * (vals.cubes - 1), 0, true), + quarkAmount: format(100 * (vals.quarks - 1), 0, true) + }) + }, + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeTutorialLevels').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaTutorial.name'), + description: () => i18next.t('ambrosia.data.ambrosiaTutorial.description') }, ambrosiaQuarks1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 100, costPerLevel: 1, blueberryCost: 0, + ignoreEXALT: false, + prerequisites: { + ambrosiaTutorial: 10 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const quarkAmount = 1 + 0.01 * n return { - quarks: quarkAmount, - desc: String( - i18next.t('ambrosia.data.ambrosiaQuarks1.effect', { - amount: format(100 * (quarkAmount - 1), 0, true) - }) - ) + quarks: quarkAmount } }, - prerequisites: { - ambrosiaTutorial: 10 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaQuarks1.effect', { + amount: format(100 * (vals.quarks - 1), 0, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow2').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow2').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaQuarks1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaQuarks1.description') }, ambrosiaCubes1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 100, costPerLevel: 1, blueberryCost: 0, + ignoreEXALT: false, + prerequisites: { + ambrosiaTutorial: 10 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const cubeAmount = (1 + 0.05 * n) * Math.pow(1.1, Math.floor(n / 5)) return { - cubes: cubeAmount, - desc: String( - i18next.t('ambrosia.data.ambrosiaCubes1.effect', { - amount: format(100 * (cubeAmount - 1), 2, true) - }) - ) + cubes: cubeAmount } }, - prerequisites: { - ambrosiaTutorial: 10 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaCubes1.effect', { + amount: format(100 * (vals.cubes - 1), 2, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow2').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow2').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaCubes1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaCubes1.description') }, ambrosiaLuck1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 100, costPerLevel: 1, blueberryCost: 0, + ignoreEXALT: false, + prerequisites: { + ambrosiaTutorial: 10 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const val = 2 * n + 12 * Math.floor(n / 10) return { - ambrosiaLuck: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaLuck1.effect', { - amount: format(val) - }) - ) + ambrosiaLuck: val } }, - prerequisites: { - ambrosiaTutorial: 10 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaLuck1.effect', { + amount: format(vals.ambrosiaLuck) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow2').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow2').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaLuck1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaLuck1.description') }, ambrosiaQuarkCube1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 25, costPerLevel: 250, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: { + ambrosiaCubes1: 30, + ambrosiaQuarks1: 20 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const baseVal = 0.001 * n const val = 1 + baseVal * Math.floor(Math.pow(Math.log10(Number(player.worlds) + 1) + 1, 2)) return { - cubes: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaQuarkCube1.effect', { - amount: format(100 * (val - 1), 2, true) - }) - ) + cubes: val } }, - prerequisites: { - ambrosiaCubes1: 30, - ambrosiaQuarks1: 20 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaQuarkCube1.effect', { + amount: format(100 * (vals.cubes - 1), 2, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow3').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow3').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaQuarkCube1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaQuarkCube1.description') }, ambrosiaLuckCube1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 25, costPerLevel: 250, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: { + ambrosiaCubes1: 30, + ambrosiaLuck1: 20 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const baseVal = 0.0005 * n const val = 1 + baseVal * calculateAmbrosiaLuck() return { - cubes: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaLuckCube1.effect', { - amount: format(100 * (val - 1), 2, true) - }) - ) + cubes: val } }, - prerequisites: { - ambrosiaCubes1: 30, - ambrosiaLuck1: 20 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaLuckCube1.effect', { + amount: format(100 * (vals.cubes - 1), 2, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow3').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow3').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaLuckCube1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaLuckCube1.description') }, ambrosiaCubeQuark1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 25, costPerLevel: 500, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: { + ambrosiaQuarks1: 30, + ambrosiaCubes1: 20 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const baseVal = 0.0001 * n const val = 1 + baseVal @@ -507,59 +282,72 @@ export const blueberryUpgradeData: Record< + Math.floor(Math.log10(player.wowOcteracts + 1)) + 6) return { - quarks: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaCubeQuark1.effect', { - amount: format(100 * (val - 1), 2, true) - }) - ) + quarks: val } }, - prerequisites: { - ambrosiaQuarks1: 30, - ambrosiaCubes1: 20 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaCubeQuark1.effect', { + amount: format(100 * (vals.quarks - 1), 2, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow3').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow3').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaCubeQuark1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaCubeQuark1.description') }, ambrosiaLuckQuark1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 25, costPerLevel: 500, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: { + ambrosiaQuarks1: 30, + ambrosiaLuck1: 20 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const baseVal = 0.0001 * n const luck = calculateAmbrosiaLuck() const effectiveLuck = Math.min( luck, - Math.pow(1000, 0.5) - * Math.pow(luck, 0.5) + Math.pow(1000, 0.5) * Math.pow(luck, 0.5) ) const val = 1 + baseVal * effectiveLuck return { - quarks: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaLuckQuark1.effect', { - amount: format(100 * (val - 1), 2, true) - }) - ) + quarks: val } }, - prerequisites: { - ambrosiaQuarks1: 30, - ambrosiaLuck1: 20 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaLuckQuark1.effect', { + amount: format(100 * (vals.quarks - 1), 2, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow3').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow3').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaLuckQuark1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaLuckQuark1.description') }, ambrosiaCubeLuck1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 25, costPerLevel: 100, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: { + ambrosiaLuck1: 30, + ambrosiaCubes1: 20 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const baseVal = 0.02 * n const val = baseVal * (Math.floor(Math.log10(Number(player.wowCubes) + 1)) @@ -570,479 +358,950 @@ export const blueberryUpgradeData: Record< + Math.floor(Math.log10(player.wowOcteracts + 1)) + 6) return { - ambrosiaLuck: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaCubeLuck1.effect', { - amount: format(val, 2, true) - }) - ) + ambrosiaLuck: val } }, - prerequisites: { - ambrosiaLuck1: 30, - ambrosiaCubes1: 20 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaCubeLuck1.effect', { + amount: format(vals.ambrosiaLuck, 2, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow3').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow3').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaCubeLuck1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaCubeLuck1.description') }, ambrosiaQuarkLuck1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 25, costPerLevel: 100, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: { + ambrosiaLuck1: 30, + ambrosiaQuarks1: 20 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const baseVal = 0.02 * n - const val = baseVal - * Math.floor(Math.pow(Math.log10(Number(player.worlds) + 1) + 1, 2)) + const val = baseVal * Math.floor(Math.pow(Math.log10(Number(player.worlds) + 1) + 1, 2)) return { - ambrosiaLuck: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaQuarkLuck1.effect', { - amount: format(val, 2, true) - }) - ) + ambrosiaLuck: val } }, - prerequisites: { - ambrosiaLuck1: 30, - ambrosiaQuarks1: 20 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaQuarkLuck1.effect', { + amount: format(vals.ambrosiaLuck, 2, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow3').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow3').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaQuarkLuck1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaQuarkLuck1.description') }, ambrosiaQuarks2: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 100, costPerLevel: 500, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: { + ambrosiaQuarks1: 40 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 2) - Math.pow(level, 2)) }, - rewards: (n: number) => { + effects: (n: number) => { const quarkAmount = 1 + (0.01 - + Math.floor(player.blueberryUpgrades.ambrosiaQuarks1.effectiveLevels / 10) + + Math.floor(getAmbrosiaUpgradeEffectiveLevels('ambrosiaQuarks1') / 10) / 1000) * n return { - quarks: quarkAmount, - desc: String( - i18next.t('ambrosia.data.ambrosiaQuarks2.effect', { - amount: format(100 * (quarkAmount - 1), 0, true) - }) - ) + quarks: quarkAmount } }, - prerequisites: { - ambrosiaQuarks1: 40 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaQuarks2.effect', { + amount: format(100 * (vals.quarks - 1), 0, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow4').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow4').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaQuarks2.name'), + description: () => i18next.t('ambrosia.data.ambrosiaQuarks2.description') }, ambrosiaCubes2: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 100, costPerLevel: 500, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: { + ambrosiaCubes1: 40 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 2) - Math.pow(level, 2)) }, - rewards: (n: number) => { + effects: (n: number) => { const cubeAmount = (1 + (0.1 + 10 - * (Math.floor(player.blueberryUpgrades.ambrosiaCubes1.effectiveLevels / 10) + * (Math.floor(getAmbrosiaUpgradeEffectiveLevels('ambrosiaCubes1') / 10) / 1000)) * n) * Math.pow(1.15, Math.floor(n / 5)) return { - cubes: cubeAmount, - desc: String( - i18next.t('ambrosia.data.ambrosiaCubes2.effect', { - amount: format(100 * (cubeAmount - 1), 2, true) - }) - ) + cubes: cubeAmount } }, - prerequisites: { - ambrosiaCubes1: 40 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaCubes2.effect', { + amount: format(100 * (vals.cubes - 1), 2, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow4').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow4').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaCubes2.name'), + description: () => i18next.t('ambrosia.data.ambrosiaCubes2.description') }, ambrosiaLuck2: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 100, costPerLevel: 250, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: { + ambrosiaLuck1: 40 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 2) - Math.pow(level, 2)) }, - rewards: (n: number) => { + effects: (n: number) => { const val = (3 - + 0.3 * Math.floor(player.blueberryUpgrades.ambrosiaLuck1.effectiveLevels / 10)) + + 0.3 * Math.floor(getAmbrosiaUpgradeEffectiveLevels('ambrosiaLuck1') / 10)) * n + 40 * Math.floor(n / 10) return { - ambrosiaLuck: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaLuck2.effect', { - amount: format(val, 1, true) - }) - ) + ambrosiaLuck: val } }, - prerequisites: { - ambrosiaLuck1: 40 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaLuck2.effect', { + amount: format(vals.ambrosiaLuck, 1, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow4').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow4').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaLuck2.name'), + description: () => i18next.t('ambrosia.data.ambrosiaLuck2.description') }, ambrosiaQuarks3: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 10, costPerLevel: 750000, blueberryCost: 3, + ignoreEXALT: false, + prerequisites: { + ambrosiaQuarks1: 100, + ambrosiaQuarks2: 50 + }, costFormula: (level: number, baseCost: number): number => { return baseCost + 50000 * level }, - rewards: (n: number) => { - const quark2Mult = 1 + player.blueberryUpgrades.ambrosiaQuarks2.effectiveLevels / 100 + effects: (n: number) => { + const quark2Mult = 1 + getAmbrosiaUpgradeEffectiveLevels('ambrosiaQuarks2') / 100 const quark3Base = 0.05 * n const quarkAmount = 1 + quark3Base * quark2Mult return { - quarks: quarkAmount, - desc: String( - i18next.t('ambrosia.data.ambrosiaQuarks3.effect', { - amount: format(100 * (quarkAmount - 1), 0, true) - }) - ) + quarks: quarkAmount } }, - prerequisites: { - ambrosiaQuarks1: 100, - ambrosiaQuarks2: 50 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaQuarks3.effect', { + amount: format(100 * (vals.quarks - 1), 0, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow5').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow5').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaQuarks3.name'), + description: () => i18next.t('ambrosia.data.ambrosiaQuarks3.description') }, ambrosiaCubes3: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 100, costPerLevel: 75000, blueberryCost: 3, + ignoreEXALT: false, + prerequisites: { + ambrosiaCubes1: 100, + ambrosiaCubes2: 50 + }, costFormula: (level: number, baseCost: number): number => { return baseCost + 5000 * level }, - rewards: (n: number) => { - const cube2Multi = 1 + 3 * player.blueberryUpgrades.ambrosiaCubes2.effectiveLevels / 100 + effects: (n: number) => { + const cube2Multi = 1 + 3 * getAmbrosiaUpgradeEffectiveLevels('ambrosiaCubes2') / 100 const cube3Base = 0.2 * n const cube3Exponential = Math.pow(1.2, Math.floor(n / 5)) const cubeAmount = (1 + cube3Base * cube2Multi) * cube3Exponential return { - cubes: cubeAmount, - desc: String( - i18next.t('ambrosia.data.ambrosiaCubes3.effect', { - amount: format(100 * (cubeAmount - 1), 2, true) - }) - ) + cubes: cubeAmount } }, - prerequisites: { - ambrosiaCubes1: 100, - ambrosiaCubes2: 50 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaCubes3.effect', { + amount: format(100 * (vals.cubes - 1), 2, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow5').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow5').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaCubes3.name'), + description: () => i18next.t('ambrosia.data.ambrosiaCubes3.description') }, ambrosiaLuck3: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 100, costPerLevel: 50000, blueberryCost: 3, + ignoreEXALT: false, + prerequisites: { + ambrosiaLuck1: 90, + ambrosiaLuck2: 50 + }, costFormula: (level: number, baseCost: number): number => { return baseCost + 0 * level // Level has no effect }, - rewards: (n: number) => { + effects: (n: number) => { const perLevel = calculateBlueberryInventory() return { - ambrosiaLuck: perLevel * n, - desc: String( - i18next.t('ambrosia.data.ambrosiaLuck3.effect', { - amount: format(perLevel * n, 0, true) - }) - ) + ambrosiaLuck: perLevel * n } }, - prerequisites: { - ambrosiaLuck1: 90, - ambrosiaLuck2: 50 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaLuck3.effect', { + amount: format(vals.ambrosiaLuck, 0, true) + }) + }, + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow5').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaLuck3.name'), + description: () => i18next.t('ambrosia.data.ambrosiaLuck3.description') + }, + ambrosiaLuck4: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, + maxLevel: 50, + costPerLevel: 250000, + blueberryCost: 5, + ignoreEXALT: false, + prerequisites: {}, + costFormula: (level, baseCost): number => { + return baseCost + 20000 * level + }, + effects: (n: number) => { + const digits = Math.ceil(Math.log10(player.lifetimeRedAmbrosia + 1)) + + Math.ceil(Math.log10(player.lifetimeAmbrosia + 1)) + return { + ambrosiaLuckPercentage: 1 / 10000 * digits * n + } }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow5').bonus.freeLevels + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaLuck4.effect', { + amount: formatAsPercentIncrease(1 + vals.ambrosiaLuckPercentage, 2) + }) + }, + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow5').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaLuck4.name'), + description: () => i18next.t('ambrosia.data.ambrosiaLuck4.description') }, ambrosiaPatreon: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 1, costPerLevel: 1, blueberryCost: 0, + ignoreEXALT: false, + prerequisites: {}, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 2) - Math.pow(level, 2)) }, - rewards: (n: number) => { + effects: (n: number) => { const val = 1 + (n * getQuarkBonus()) / 100 return { - blueberryGeneration: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaPatreon.effect', { - amount: format(100 * (val - 1), 0, true) - }) - ) + blueberryGeneration: val } }, - extraLevelCalc: () => 0 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaPatreon.effect', { + amount: format(100 * (vals.blueberryGeneration - 1), 0, true) + }) + }, + extraLevelCalc: () => 0, + name: () => i18next.t('ambrosia.data.ambrosiaPatreon.name'), + description: () => i18next.t('ambrosia.data.ambrosiaPatreon.description') }, ambrosiaObtainium1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 2, costPerLevel: 50000, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: {}, costFormula: (level: number, baseCost: number): number => { return baseCost * Math.pow(25, level) }, - rewards: (n: number) => { + effects: (n: number) => { const luck = calculateAmbrosiaLuck() return { luckMult: n, - obtainiumMult: n * luck, - desc: String( - i18next.t('ambrosia.data.ambrosiaObtainium1.effect', { - amount: format((n * luck) / 10, 1, true) - }) - ) + obtainiumMult: n * luck } }, - extraLevelCalc: () => 0 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaObtainium1.effect', { + amount: format(vals.obtainiumMult / 10, 1, true) + }) + }, + extraLevelCalc: () => 0, + name: () => i18next.t('ambrosia.data.ambrosiaObtainium1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaObtainium1.description') }, ambrosiaOffering1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 2, costPerLevel: 50000, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: {}, costFormula: (level: number, baseCost: number): number => { return baseCost * Math.pow(25, level) }, - rewards: (n: number) => { + effects: (n: number) => { const luck = calculateAmbrosiaLuck() return { luckMult: n, - offeringMult: n * luck, - desc: String( - i18next.t('ambrosia.data.ambrosiaOffering1.effect', { - amount: format((n * luck) / 10, 1, true) - }) - ) + offeringMult: n * luck } }, - extraLevelCalc: () => 0 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaOffering1.effect', { + amount: format(vals.offeringMult / 10, 1, true) + }) + }, + extraLevelCalc: () => 0, + name: () => i18next.t('ambrosia.data.ambrosiaOffering1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaOffering1.description') }, ambrosiaHyperflux: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 7, costPerLevel: 33333, blueberryCost: 3, + ignoreEXALT: false, + prerequisites: {}, costFormula: (level: number, baseCost: number): number => { return (baseCost + 33333 * Math.min(4, level)) * Math.max(1, Math.pow(3, level - 4)) }, - rewards: (n: number) => { + effects: (n: number) => { const fourByFourBase = n return { hyperFlux: Math.pow( 1 + (1 / 100) * fourByFourBase, player.platonicUpgrades[19] - ), - desc: String( - i18next.t('ambrosia.data.ambrosiaHyperflux.effect', { - amount: format( - 100 - * (Math.pow( - 1 + fourByFourBase / 100, - player.platonicUpgrades[19] - ) - - 1) - ) - }) ) } }, - extraLevelCalc: () => 0 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaHyperflux.effect', { + amount: format(100 * (vals.hyperFlux - 1)) + }) + }, + extraLevelCalc: () => 0, + name: () => i18next.t('ambrosia.data.ambrosiaHyperflux.name'), + description: () => i18next.t('ambrosia.data.ambrosiaHyperflux.description') }, ambrosiaBaseOffering1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 40, costPerLevel: 5, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: {}, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { - const val = n + effects: (n: number) => { return { - offering: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaBaseOffering1.effect', { - amount: format(val, 0, true) - }) - ) + offering: n } }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow2').bonus.freeLevels + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaBaseOffering1.effect', { + amount: format(vals.offering, 0, true) + }) + }, + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow2').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaBaseOffering1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaBaseOffering1.description') }, ambrosiaBaseObtainium1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 20, costPerLevel: 40, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: {}, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const val = n return { - obtainium: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaBaseObtainium1.effect', { - amount: format(val, 0, true) - }) - ) + obtainium: val } }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow2').bonus.freeLevels + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaBaseObtainium1.effect', { + amount: format(vals.obtainium, 0, true) + }) + }, + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow2').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaBaseObtainium1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaBaseObtainium1.description') }, ambrosiaBaseOffering2: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 60, costPerLevel: 20, blueberryCost: 2, + ignoreEXALT: false, + prerequisites: { + ambrosiaBaseOffering1: 30, + ambrosiaBaseObtainium1: 10 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const val = n return { - offering: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaBaseOffering2.effect', { - amount: format(val, 0, true) - }) - ) + offering: val } }, - prerequisites: { - ambrosiaBaseOffering1: 30, - ambrosiaBaseObtainium1: 10 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaBaseOffering2.effect', { + amount: format(vals.offering, 0, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow4').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow4').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaBaseOffering2.name'), + description: () => i18next.t('ambrosia.data.ambrosiaBaseOffering2.description') }, ambrosiaBaseObtainium2: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 30, costPerLevel: 160, blueberryCost: 2, + ignoreEXALT: false, + prerequisites: { + ambrosiaBaseObtainium1: 15, + ambrosiaBaseOffering1: 20 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * (Math.pow(level + 1, 3) - Math.pow(level, 3)) }, - rewards: (n: number) => { + effects: (n: number) => { const val = n return { - obtainium: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaBaseObtainium2.effect', { - amount: format(val, 0, true) - }) - ) + obtainium: val } }, - prerequisites: { - ambrosiaBaseObtainium1: 15, - ambrosiaBaseOffering1: 20 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaBaseObtainium2.effect', { + amount: format(vals.obtainium, 0, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow4').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow4').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaBaseObtainium2.name'), + description: () => i18next.t('ambrosia.data.ambrosiaBaseObtainium2.description') }, ambrosiaSingReduction1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 2, costPerLevel: 100000, blueberryCost: 2, + ignoreEXALT: false, + prerequisites: { + ambrosiaHyperflux: 4 + }, costFormula: (level: number, baseCost: number): number => { return baseCost * Math.pow(99, level) }, - rewards: (n: number) => { + effects: (n: number) => { const val = (player.insideSingularityChallenge) ? 0 : n return { - singularityReduction: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaSingReduction1.effect', { - amount: format(val, 0, true) - }) - ) + singularityReduction: val } }, - prerequisites: { - ambrosiaHyperflux: 4 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaSingReduction1.effect', { + amount: format(vals.singularityReduction, 0, true) + }) }, - extraLevelCalc: () => 0 + extraLevelCalc: () => 0, + name: () => i18next.t('ambrosia.data.ambrosiaSingReduction1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaSingReduction1.description') }, ambrosiaInfiniteShopUpgrades1: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 20, costPerLevel: 25000, blueberryCost: 1, + ignoreEXALT: false, + prerequisites: { + ambrosiaCubes1: 70, + ambrosiaBaseOffering1: 20, + ambrosiaBaseObtainium1: 10 + }, costFormula: (level: number, baseCost: number): number => { return baseCost + 0 * level }, - rewards: (n: number) => { + effects: (n: number) => { const val = n return { - freeLevels: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaInfiniteShopUpgrades1.effect', { - amount: format(val, 0, true) - }) - ) + freeLevels: val } }, - prerequisites: { - 'ambrosiaCubes1': 70, - 'ambrosiaBaseOffering1': 20, - 'ambrosiaBaseObtainium1': 10 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaInfiniteShopUpgrades1.effect', { + amount: format(vals.freeLevels, 0, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow4').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow4').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaInfiniteShopUpgrades1.name'), + description: () => i18next.t('ambrosia.data.ambrosiaInfiniteShopUpgrades1.description') }, ambrosiaInfiniteShopUpgrades2: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 20, costPerLevel: 75000, blueberryCost: 2, + ignoreEXALT: false, + prerequisites: { + ambrosiaInfiniteShopUpgrades1: 20, + ambrosiaCubes2: 50, + ambrosiaBaseOffering2: 20, + ambrosiaBaseObtainium2: 10 + }, costFormula: (level: number, baseCost: number): number => { return baseCost + 0 * level }, - rewards: (n: number) => { + effects: (n: number) => { const val = n return { - freeLevels: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaInfiniteShopUpgrades2.effect', { - amount: format(val, 0, true) - }) - ) + freeLevels: val } }, - prerequisites: { - 'ambrosiaInfiniteShopUpgrades1': 20, - 'ambrosiaCubes2': 50, - 'ambrosiaBaseOffering2': 20, - 'ambrosiaBaseObtainium2': 10 + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaInfiniteShopUpgrades2.effect', { + amount: format(vals.freeLevels, 0, true) + }) }, - extraLevelCalc: () => getRedAmbrosiaUpgrade('freeLevelsRow5').bonus.freeLevels + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow5').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaInfiniteShopUpgrades2.name'), + description: () => i18next.t('ambrosia.data.ambrosiaInfiniteShopUpgrades2.description') }, ambrosiaSingReduction2: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, maxLevel: 2, costPerLevel: 1.25e7, blueberryCost: 4, + ignoreEXALT: true, + prerequisites: {}, costFormula: (level: number, baseCost: number): number => { return baseCost * Math.pow(3, level) }, - rewards: (n: number) => { + effects: (n: number) => { const val = (player.insideSingularityChallenge) ? n : 0 return { - singularityReduction: val, - desc: String( - i18next.t('ambrosia.data.ambrosiaSingReduction2.effect', { - amount: format(val, 0, true) - }) - ) + singularityReduction: val } }, + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaSingReduction2.effect', { + amount: format(vals.singularityReduction, 0, true) + }) + }, extraLevelCalc: () => 0, - ignoreEXALT: true + name: () => i18next.t('ambrosia.data.ambrosiaSingReduction2.name'), + description: () => i18next.t('ambrosia.data.ambrosiaSingReduction2.description') + }, + ambrosiaTalismanBonusRuneLevel: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, + maxLevel: 100, + costPerLevel: 100, + blueberryCost: 0, + ignoreEXALT: false, + prerequisites: {}, + costFormula: (level: number, baseCost: number): number => { + return baseCost * (Math.pow(level + 1, 2) - Math.pow(level, 2)) + }, + effects: (n: number) => { + const val = n / 200 + return { + talismanBonusRuneLevel: val + } + }, + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaTalismanBonusRuneLevel.effect', { + amount: formatAsPercentIncrease(1 + vals.talismanBonusRuneLevel, 2) + }) + }, + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow2').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaTalismanBonusRuneLevel.name'), + description: () => i18next.t('ambrosia.data.ambrosiaTalismanBonusRuneLevel.description') + }, + ambrosiaRuneOOMBonus: { + level: 0, + ambrosiaInvested: 0, + blueberriesInvested: 0, + maxLevel: 100, + costPerLevel: 2500, + blueberryCost: 0, + ignoreEXALT: false, + prerequisites: {}, + costFormula: (level: number, baseCost: number): number => { + return Math.ceil(baseCost * (Math.pow(level + 1, 1.5) - Math.pow(level, 1.5))) + }, + effects: (n: number) => { + const val = n + const val2 = n / 1000 + return { + runeOOMBonus: val, + infiniteAscentOOMBonus: val2 + } + }, + effectsDescription: function(n: number) { + const vals = this.effects(n) + return i18next.t('ambrosia.data.ambrosiaRuneOOMBonus.effect', { + amount: format(vals.runeOOMBonus, 0, false), + amount2: format(vals.infiniteAscentOOMBonus, 3, false) + }) + }, + extraLevelCalc: () => getRedAmbrosiaUpgradeEffects('freeLevelsRow4').freeLevels, + name: () => i18next.t('ambrosia.data.ambrosiaRuneOOMBonus.name'), + description: () => i18next.t('ambrosia.data.ambrosiaRuneOOMBonus.description') + } +} + +export const ambrosiaUpgradeNames = Object.keys(ambrosiaUpgrades) as AmbrosiaUpgradeNames[] + +export const blankAmbrosiaUpgradeObject: Record< + AmbrosiaUpgradeNames, + { ambrosiaInvested: number; blueberriesInvested: number } +> = Object.fromEntries( + Object.keys(ambrosiaUpgrades).map((key) => [ + key as AmbrosiaUpgradeNames, + { + ambrosiaInvested: 0, + blueberriesInvested: 0 + } + ]) +) as Record + +export const setAmbrosiaUpgradeLevels = () => { + for (const upgradeKey of Object.keys(ambrosiaUpgrades) as AmbrosiaUpgradeNames[]) { + const invested = ambrosiaUpgrades[upgradeKey].ambrosiaInvested + const upgradeCost = ambrosiaUpgrades[upgradeKey].costFormula + const perLevelCost = ambrosiaUpgrades[upgradeKey].costPerLevel + + let level = 0 + let budget = invested + + let nextCost = upgradeCost(level, perLevelCost) + + while (budget >= nextCost) { + budget -= nextCost + level += 1 + nextCost = upgradeCost(level, perLevelCost) + + if (level >= ambrosiaUpgrades[upgradeKey].maxLevel) { + break + } + } + + // If there is leftover budget, then the formulae has probably changed, or above max. + // We refund the remaining budget. + if (budget > 0) { + player.ambrosiaUpgrades[upgradeKey].ambrosiaInvested -= budget + player.ambrosia += budget + } + + ambrosiaUpgrades[upgradeKey].level = level + ambrosiaUpgrades[upgradeKey].ambrosiaInvested = invested - budget + } +} + +export const getAmbrosiaUpgradeEffectiveLevels = (upgradeKey: AmbrosiaUpgradeNames): number => { + const upgrade = ambrosiaUpgrades[upgradeKey] + return ((player.singularityChallenges.noAmbrosiaUpgrades.enabled + || player.singularityChallenges.sadisticPrequel.enabled) && !upgrade.ignoreEXALT) + ? 0 + : upgrade.level + upgrade.extraLevelCalc() +} + +export const getAmbrosiaUpgradeEffects = ( + upgradeKey: T +): AmbrosiaUpgradeRewards[T] => { + const effectiveLevels = getAmbrosiaUpgradeEffectiveLevels(upgradeKey) + return ambrosiaUpgrades[upgradeKey].effects(effectiveLevels) +} + +export const getAmbrosiaUpgradeEffectsDescription = (upgradeKey: AmbrosiaUpgradeNames): string => { + const effectiveLevels = getAmbrosiaUpgradeEffectiveLevels(upgradeKey) + return ambrosiaUpgrades[upgradeKey].effectsDescription(effectiveLevels) +} + +export const getAmbrosiaUpgradeCostTNL = (upgradeKey: AmbrosiaUpgradeNames): number => { + const upgrade = ambrosiaUpgrades[upgradeKey] + if (upgrade.level === upgrade.maxLevel) { + return 0 + } + return upgrade.costFormula(upgrade.level, upgrade.costPerLevel) +} + +export const checkAmbrosiaUpgradePrerequisites = (upgradeKey: AmbrosiaUpgradeNames): boolean => { + const upgrade = ambrosiaUpgrades[upgradeKey] + const prerequisites = upgrade.prerequisites + + for (const [prereq, val] of Object.entries(prerequisites)) { + const k = prereq as AmbrosiaUpgradeNames + if (ambrosiaUpgrades[k].level < val!) { + return false + } + } + return true +} + +export const ambrosiaUpgradeToString = (upgradeKey: AmbrosiaUpgradeNames): string => { + const upgrade = ambrosiaUpgrades[upgradeKey] + const costNextLevel = getAmbrosiaUpgradeCostTNL(upgradeKey) + const maxLevel = upgrade.maxLevel === -1 ? '' : `/${format(upgrade.maxLevel, 0, true)}` + const isMaxLevel = upgrade.maxLevel === upgrade.level + const color = isMaxLevel ? 'plum' : 'white' + + const extraLevels = upgrade.extraLevelCalc() + const freeLevelInfo = extraLevels > 0 + ? ` [+${format(extraLevels, 0, true)}]` + : '' + + let preReqText: string | undefined + + if (Object.keys(upgrade.prerequisites).length > 0) { + preReqText = String(i18next.t('ambrosia.prerequisite')) + for (const [prereq, val] of Object.entries(upgrade.prerequisites)) { + const k = prereq as AmbrosiaUpgradeNames + const color = ambrosiaUpgrades[k].level >= val! ? 'green' : 'red' + const met = ambrosiaUpgrades[k].level >= val! + ? '' + : i18next.t('ambrosia.prereqNotMet') + preReqText = `${preReqText} ${ambrosiaUpgrades[k].name()} lv.${val} ${met} |` + } + + preReqText = preReqText.slice(0, -1) + } + + const effectsDescription = getAmbrosiaUpgradeEffectsDescription(upgradeKey) + + const nameHTML = `${upgrade.name()}` + const levelHTML = ` ${i18next.t('general.level')} ${ + format(upgrade.level, 0, true) + }${maxLevel}${freeLevelInfo}` + const preReqHTML = preReqText ? `${preReqText}
` : '' + const descriptionHTML = `${upgrade.description()}` + const effectsHTML = `${effectsDescription}` + const costNextLevelHTML = `${ + i18next.t('ambrosia.ambrosiaCost', { + amount: format(costNextLevel, 0, true) + }) + }` + const blueberryCostHTML = `${ + i18next.t('ambrosia.blueberryCost') + } ${upgrade.blueberryCost}` + const spentAmbrosiaHTML = `${i18next.t('general.spent')} ${ + i18next.t('ambrosia.ambrosia') + }: ${format(upgrade.ambrosiaInvested, 0, true)}` + const ignoreEXALTHTML = upgrade.ignoreEXALT + ? `
${i18next.t('ambrosia.ignoreEXALT')}` + : '' + + return `${nameHTML}
${levelHTML}
${preReqHTML}${descriptionHTML}
${effectsHTML}
${costNextLevelHTML}
${blueberryCostHTML}
${spentAmbrosiaHTML}${ignoreEXALTHTML}` +} + +export const updateMobileAmbrosiaHTML = (k: AmbrosiaUpgradeNames) => { + const elm = DOMCacheGetOrSet('singularityAmbrosiaMultiline') + elm.innerHTML = ambrosiaUpgradeToString(k) + // MOBILE ONLY - Add a button for buying upgrades + if (isMobile) { + const buttonDiv = document.createElement('div') + + const buyOne = document.createElement('button') + const buyMax = document.createElement('button') + + buyOne.classList.add('modalBtnBuy') + buyOne.textContent = i18next.t('general.buyOne') + buyOne.addEventListener('click', (event: MouseEvent) => { + buyAmbrosiaUpgradeLevel(k, event, false) + updateMobileAmbrosiaHTML(k) + }) + + buyMax.classList.add('modalBtnBuy') + buyMax.textContent = i18next.t('general.buyMax') + buyMax.addEventListener('click', (event: MouseEvent) => { + buyAmbrosiaUpgradeLevel(k, event, true) + updateMobileAmbrosiaHTML(k) + }) + + buttonDiv.appendChild(buyOne) + buttonDiv.appendChild(buyMax) + elm.appendChild(buttonDiv) + } +} + +export const buyAmbrosiaUpgradeLevel = async ( + upgradeKey: AmbrosiaUpgradeNames, + event: MouseEvent, + buyMax = false +): Promise => { + const upgrade = ambrosiaUpgrades[upgradeKey] + let purchased = 0 + let maxPurchasable = 1 + let ambrosiaBudget = player.ambrosia + + if (!checkAmbrosiaUpgradePrerequisites(upgradeKey)) { + return Alert(i18next.t('ambrosia.prereqNotMetAlert')) + } + + if (event.shiftKey || buyMax) { + maxPurchasable = 1000000 + const buy = Number( + await Prompt( + i18next.t('ambrosia.ambrosiaBuyPrompt', { + amount: format(player.ambrosia, 0, true) + }) + ) + ) + + if (isNaN(buy) || !isFinite(buy) || !Number.isInteger(buy)) { + // nan + Infinity checks + return Alert(i18next.t('general.validation.finite')) + } + + if (buy === -1) { + ambrosiaBudget = player.ambrosia + } else if (buy <= 0) { + return Alert(i18next.t('octeract.buyLevel.cancelPurchase')) + } else { + ambrosiaBudget = buy + } + ambrosiaBudget = Math.min(player.ambrosia, ambrosiaBudget) + } + + if (upgrade.maxLevel > 0) { + maxPurchasable = Math.min(maxPurchasable, upgrade.maxLevel - upgrade.level) + } + + if (maxPurchasable === 0) { + return Alert(i18next.t('octeract.buyLevel.alreadyMax')) + } + + while (maxPurchasable > 0) { + const cost = getAmbrosiaUpgradeCostTNL(upgradeKey) + if (player.ambrosia < cost || ambrosiaBudget < cost) { + break + } else { + if (upgrade.level === 0) { + const availableBlueberries = calculateBlueberryInventory() - player.spentBlueberries + if (availableBlueberries < upgrade.blueberryCost) { + return Alert(i18next.t('ambrosia.notEnoughBlueberries')) + } else { + player.spentBlueberries += upgrade.blueberryCost + upgrade.blueberriesInvested = upgrade.blueberryCost + } + } + player.ambrosia -= cost + ambrosiaBudget -= cost + upgrade.ambrosiaInvested += cost + upgrade.level += 1 + purchased += 1 + maxPurchasable -= 1 + } + } + + if (purchased === 0) { + return Alert(i18next.t('octeract.buyLevel.cannotAfford')) + } + if (purchased > 1) { + return Alert( + `${i18next.t('octeract.buyLevel.multiBuy', { n: format(purchased) })}` + ) } } @@ -1060,12 +1319,16 @@ export const displayProperLoadoutCount = () => { } } -export const resetBlueberryTree = async (giveAlert = true) => { - for (const upgrade of Object.keys(player.blueberryUpgrades)) { - const k = upgrade as keyof Player['blueberryUpgrades'] - player.blueberryUpgrades[k].refund() - player.blueberryUpgrades[k].updateCaches() +export const resetBlueberryTree = (giveAlert = true) => { + for (const k of Object.keys(ambrosiaUpgrades) as AmbrosiaUpgradeNames[]) { + ambrosiaUpgrades[k].level = 0 + ambrosiaUpgrades[k].ambrosiaInvested = 0 + ambrosiaUpgrades[k].blueberriesInvested = 0 + player.ambrosiaUpgrades[k].ambrosiaInvested = 0 + player.ambrosiaUpgrades[k].blueberriesInvested = 0 } + player.ambrosia = player.lifetimeAmbrosia + player.spentBlueberries = 0 if (giveAlert) return Alert(i18next.t('ambrosia.refund')) } @@ -1086,11 +1349,11 @@ export const validateBlueberryTree = (modules: BlueberryOpt) => { let meetsBlueberries = true for (const [key, val] of Object.entries(modules)) { - const k = key as keyof Player['blueberryUpgrades'] + const k = key as AmbrosiaUpgradeNames // Nix malicious or bad values if ( - val < 0 + val! < 0 || !Number.isFinite(val) || !Number.isInteger(val) || Number.isNaN(val) @@ -1098,21 +1361,21 @@ export const validateBlueberryTree = (modules: BlueberryOpt) => { return false } // Nix nonexistent modules - if (player.blueberryUpgrades[k] === undefined) return false + if (ambrosiaUpgrades[k] === undefined) return false // Set val to max if it exceeds it, since it is possible module caps change over time. - const effectiveVal = Math.min(player.blueberryUpgrades[k].maxLevel, val) + const effectiveVal = Math.min(ambrosiaUpgrades[k].maxLevel, val!) // Check prereq for this specific module - const prereqs = player.blueberryUpgrades[k].preRequisites - if (prereqs !== undefined && val > 0) { + const prereqs = ambrosiaUpgrades[k].prerequisites + if (prereqs !== undefined && val! > 0) { for (const [key2, val2] of Object.entries(prereqs)) { const k2 = key2 as keyof BlueberryOpt const level = modules[k2] ?? -1 /* If undefined, this is saying 'We need to have module set to level val2 but it isn't even in our module loadout, so it cannot possibly satisfy prereqs'*/ - if (level < val2) { + if (level < val2!) { meetsPrerequisites = false } } @@ -1120,15 +1383,15 @@ export const validateBlueberryTree = (modules: BlueberryOpt) => { // Check blueberry costs if (effectiveVal > 0) { - spentBlueberries += player.blueberryUpgrades[k].blueberryCost + spentBlueberries += ambrosiaUpgrades[k].blueberryCost } // Check ambrosia costs if (effectiveVal > 0) { - const valFunc = player.blueberryUpgrades[k].costFormula - const baseCost = player.blueberryUpgrades[k].costPerLevel + const valFunc = ambrosiaUpgrades[k].costFormula + const baseCost = ambrosiaUpgrades[k].costPerLevel let tempCost = 0 - for (let i = 0; i < val; i++) { + for (let i = 0; i < val!; i++) { tempCost += valFunc(i, baseCost) } spentAmbrosia += tempCost @@ -1143,7 +1406,7 @@ export const validateBlueberryTree = (modules: BlueberryOpt) => { export const getBlueberryTree = () => { return Object.fromEntries( - Object.entries(player.blueberryUpgrades).map(([key, value]) => { + Object.entries(ambrosiaUpgrades).map(([key, value]) => { return [key, value.level] }) ) as BlueberryOpt @@ -1152,7 +1415,8 @@ export const getBlueberryTree = () => { export const fixBlueberryLevel = (modules: BlueberryOpt) => { return Object.fromEntries( Object.entries(modules).map(([key, value]) => { - return [key, Math.min(value, player.blueberryUpgrades[key].maxLevel)] + const k = key as AmbrosiaUpgradeNames + return [k, Math.min(value!, ambrosiaUpgrades[k].maxLevel)] }) ) } @@ -1164,7 +1428,7 @@ export const exportBlueberryTree = () => { void exportData(save, name) } -export const createBlueberryTree = async (modules: BlueberryOpt) => { +export const createBlueberryTree = (modules: BlueberryOpt) => { // Check to see if tree being created is valid. const isPossible = validateBlueberryTree(modules) if (!isPossible) { @@ -1174,38 +1438,37 @@ export const createBlueberryTree = async (modules: BlueberryOpt) => { // If valid, we will create the tree. // Refund (reset) the tree! - await resetBlueberryTree(false) // no alert; return type is undefined + resetBlueberryTree(false) // no alert; return type is undefined // Fix blueberry levels on a valid tree (not done by validation) const actualModules = fixBlueberryLevel(modules) for (const [key, val] of Object.entries(actualModules)) { - const k = key as keyof Player['blueberryUpgrades'] - const { costFormula, costPerLevel, blueberryCost } = player.blueberryUpgrades[k] + const k = key as AmbrosiaUpgradeNames + const { costFormula, costPerLevel, blueberryCost } = ambrosiaUpgrades[k] if (val > 0) { - player.blueberryUpgrades[k].blueberriesInvested = blueberryCost + ambrosiaUpgrades[k].blueberriesInvested = blueberryCost player.spentBlueberries += blueberryCost let tempCost = 0 for (let i = 0; i < val; i++) { tempCost += costFormula(i, costPerLevel) } player.ambrosia -= tempCost - player.blueberryUpgrades[k].ambrosiaInvested = tempCost - player.blueberryUpgrades[k].level = val - player.blueberryUpgrades[k].updateCaches() + ambrosiaUpgrades[k].ambrosiaInvested = tempCost + ambrosiaUpgrades[k].level = val } } void Alert(i18next.t('ambrosia.importTree.success')) } -export const importBlueberryTree = async (input: string | null) => { +export const importBlueberryTree = (input: string | null) => { if (typeof input !== 'string') { return Alert(i18next.t('importexport.unableImport')) } else { try { const modules = JSON.parse(input) as BlueberryOpt - await createBlueberryTree(modules) + createBlueberryTree(modules) createLoadoutDescription(0, modules) } catch (err) { return Alert(i18next.t('ambrosia.importTree.error')) @@ -1213,12 +1476,12 @@ export const importBlueberryTree = async (input: string | null) => { } } -export const loadoutHandler = async (n: number, modules: BlueberryOpt) => { +export const loadoutHandler = (n: number, modules: BlueberryOpt) => { if (player.blueberryLoadoutMode === 'saveTree') { - await saveBlueberryTree(n, modules) + saveBlueberryTree(n, modules) } if (player.blueberryLoadoutMode === 'loadTree') { - await createBlueberryTree(modules) + createBlueberryTree(modules) } } @@ -1249,8 +1512,8 @@ export const createLoadoutDescription = ( */ if (!val) continue - const k = key as keyof Player['blueberryUpgrades'] - const name = player.blueberryUpgrades[k].name + const k = key as AmbrosiaUpgradeNames + const name = ambrosiaUpgrades[k].name() str = `${str}${name} lv${val} | ` } @@ -1277,85 +1540,74 @@ export const updateBlueberryLoadoutCount = () => { } } -export const highlightPrerequisites = (k: blueberryUpgradeNames) => { - const preReq = blueberryUpgradeData[k].prerequisites +export const highlightPrerequisites = (k: AmbrosiaUpgradeNames) => { + const preReq = ambrosiaUpgrades[k].prerequisites if (preReq === undefined) return - for (const key of Object.keys(blueberryUpgradeData)) { - const k2 = key as blueberryUpgradeNames + for (const key of Object.keys(ambrosiaUpgrades)) { + const k2 = key as AmbrosiaUpgradeNames const elm = DOMCacheGetOrSet(k2) + const img = elm.querySelector('img') as HTMLImageElement if (preReq[k2] !== undefined) { - elm.classList.add('blueberryPrereq') + img.classList.add('blueberryPrereq') } else { - elm.classList.remove('blueberryPrereq') + img.classList.remove('blueberryPrereq') } } } export const resetHighlights = () => { - for (const key of Object.keys(blueberryUpgradeData)) { - const k = key as blueberryUpgradeNames + for (const key of Object.keys(ambrosiaUpgrades)) { + const k = key as AmbrosiaUpgradeNames const elm = DOMCacheGetOrSet(k) - elm.classList.remove('blueberryPrereq') + const img = elm.querySelector('img') as HTMLImageElement + img.classList.remove('blueberryPrereq') } } export const displayOnlyLoadout = (loadout: BlueberryOpt) => { const loadoutKeys = Object.keys(loadout) - for (const key of Object.keys(blueberryUpgradeData)) { - const k = key as blueberryUpgradeNames + for (const key of Object.keys(ambrosiaUpgrades)) { + const k = key as AmbrosiaUpgradeNames const elm = DOMCacheGetOrSet(k) + const img = elm.querySelector('img') as HTMLImageElement const level = loadout[k] || 0 // Get the level from the loadout, default to 0 if not present - const parent = elm.parentElement! - if (loadoutKeys.includes(k) && level > 0) { - elm.classList.remove('notInLoadout') - elm.classList.add('dimmed') // Apply the dimmed class - - // Create or get the level overlay element - let levelOverlay = parent.querySelector('.level-overlay') as HTMLDivElement - if (!levelOverlay) { - levelOverlay = document.createElement('p') - levelOverlay.classList.add('level-overlay') - - if (level === blueberryUpgradeData[k].maxLevel) { - levelOverlay.classList.add('maxBlueberryLevel') - } else { - levelOverlay.classList.add('notMaxBlueberryLevel') - } + let levelOverlay = elm.querySelector('.level-overlay') as HTMLDivElement + if (!levelOverlay) { + levelOverlay = document.createElement('div') // Changed from 'p' to 'div' + levelOverlay.classList.add('level-overlay') + elm.classList.add('relative-container') // Apply relative container to the element + elm.appendChild(levelOverlay) // Append to the element + } - parent.classList.add('relative-container') // Apply relative container to the element - parent.appendChild(levelOverlay) // Append to the element - } + if (loadoutKeys.includes(k) && level > 0) { + img.classList.add('dimmed') // Apply the dimmed class levelOverlay.textContent = String(level) // Set the level text - } else { - elm.classList.add('notInLoadout') - elm.classList.remove('dimmed') // Remove the dimmed class - - // Remove the level overlay if it exists - const levelOverlay = parent.querySelector('.level-overlay') - if (levelOverlay) { - levelOverlay.remove() - parent.classList.remove('relative-container') // Remove relative container + if (level === ambrosiaUpgrades[k].maxLevel) { + levelOverlay.classList.add('maxBlueberryLevel') } + } else { + img.classList.add('superDimmed') + levelOverlay!.textContent = '' } } } export const resetLoadoutOnlyDisplay = () => { - for (const key of Object.keys(blueberryUpgradeData)) { - const k = key as blueberryUpgradeNames + for (const key of Object.keys(ambrosiaUpgrades)) { + const k = key as AmbrosiaUpgradeNames const elm = DOMCacheGetOrSet(k) - const parent = elm.parentElement! - elm.classList.remove('notInLoadout') - elm.classList.remove('dimmed') // Remove the dimmed class + const img = elm.querySelector('img') as HTMLImageElement + img.classList.remove('dimmed') // Remove the dimmed class + img.classList.remove('superDimmed') // Remove the superDimmed class // Remove the level overlay if it exists - const levelOverlay = parent.querySelector('.level-overlay') + const levelOverlay = elm.querySelector('.level-overlay') if (levelOverlay) { levelOverlay.remove() - parent.classList.remove('relative-container') // Remove relative container + elm.classList.remove('relative-container') // Remove relative container } } } diff --git a/src/Buy.ts b/src/Buy.ts index 8ef047e82..b32448779 100644 --- a/src/Buy.ts +++ b/src/Buy.ts @@ -1,12 +1,11 @@ import type { DecimalSource } from 'break_infinity.js' import Decimal from 'break_infinity.js' -import i18next from 'i18next' -import { achievementaward } from './Achievements' -import { DOMCacheGetOrSet } from './Cache/DOM' -import { calculateRuneBonuses, calculateSummationLinear } from './Calculate' +import { awardAchievementGroup } from './Achievements' import { CalcECC } from './Challenges' import { reset } from './Reset' -import { format, player, updateAllMultiplier, updateAllTick } from './Synergism' +import { getRuneBlessingEffect } from './RuneBlessings' +import { getRuneEffects } from './Runes' +import { player, updateAllMultiplier, updateAllTick } from './Synergism' import type { FirstToFifth, OneToFive, ZeroToFour } from './types/Synergism' import { crystalupgradedescriptions, upgradeupdate } from './Upgrades' import { smallestInc } from './Utility' @@ -14,7 +13,7 @@ import { Globals as G, Upgrade } from './Variables' export const getReductionValue = () => { let reduction = 1 - reduction += Math.min(1e15, (G.rune4level * G.effectiveLevelMult) / 160) + reduction += getRuneEffects('thrift').costDelay reduction += (player.researches[56] + player.researches[57] + player.researches[58] + player.researches[59] + player.researches[60]) / 200 reduction += CalcECC('transcend', player.challengecompletions[4]) / 200 @@ -96,6 +95,7 @@ export const buyAccelerator = (autobuyer?: boolean) => { player.acceleratorBought = buyable player.acceleratorCost = thisCost + awardAchievementGroup('accelerators') return } @@ -137,35 +137,18 @@ export const buyAccelerator = (autobuyer?: boolean) => { thisCost = getCostAccelerator(buyFrom) player.acceleratorCost = thisCost if (buyFrom >= buymax) { - return + break } } - player.prestigenoaccelerator = false - player.transcendnoaccelerator = false - player.reincarnatenoaccelerator = false - updateAllTick() - if (player.acceleratorBought >= 5 && player.achievements[148] === 0) { - achievementaward(148) - } - if (player.acceleratorBought >= 25 && player.achievements[149] === 0) { - achievementaward(149) - } - if (player.acceleratorBought >= 100 && player.achievements[150] === 0) { - achievementaward(150) - } - if (player.acceleratorBought >= 666 && player.achievements[151] === 0) { - achievementaward(151) - } - if (player.acceleratorBought >= 2000 && player.achievements[152] === 0) { - achievementaward(152) - } - if (player.acceleratorBought >= 12500 && player.achievements[153] === 0) { - achievementaward(153) - } - if (player.acceleratorBought >= 100000 && player.achievements[154] === 0) { - achievementaward(154) + if (player.acceleratorBought > 0) { + player.prestigenoaccelerator = false + player.transcendnoaccelerator = false + player.reincarnatenoaccelerator = false } + + updateAllTick() + awardAchievementGroup('accelerators') } const getCostMultiplier = (buyingTo: number): Decimal => { @@ -239,6 +222,7 @@ export const buyMultiplier = (autobuyer?: boolean) => { player.multiplierBought = buyable player.multiplierCost = thisCost + awardAchievementGroup('multipliers') return } @@ -280,35 +264,18 @@ export const buyMultiplier = (autobuyer?: boolean) => { thisCost = getCostMultiplier(buyFrom) player.multiplierCost = thisCost if (buyFrom >= buymax) { - return + break } } - player.prestigenomultiplier = false - player.transcendnomultiplier = false - player.reincarnatenomultiplier = false - updateAllMultiplier() - if (player.multiplierBought >= 2 && player.achievements[155] === 0) { - achievementaward(155) - } - if (player.multiplierBought >= 20 && player.achievements[156] === 0) { - achievementaward(156) - } - if (player.multiplierBought >= 100 && player.achievements[157] === 0) { - achievementaward(157) - } - if (player.multiplierBought >= 500 && player.achievements[158] === 0) { - achievementaward(158) - } - if (player.multiplierBought >= 2000 && player.achievements[159] === 0) { - achievementaward(159) - } - if (player.multiplierBought >= 12500 && player.achievements[160] === 0) { - achievementaward(160) - } - if (player.multiplierBought >= 100000 && player.achievements[161] === 0) { - achievementaward(161) + if (player.multiplierBought > 0) { + player.prestigenomultiplier = false + player.transcendnomultiplier = false + player.reincarnatenomultiplier = false } + + updateAllMultiplier() + awardAchievementGroup('multipliers') } /* @@ -359,9 +326,7 @@ const known_log10s = (() => { // constructing all logs const obj: Record = {} for (const need of needed) { - if (typeof obj[need] === 'undefined') { - obj[need] = Math.log10(need) - } + obj[need] ??= Math.log10(need) } return obj })() @@ -603,7 +568,7 @@ export const buyProducer = ( const [tag, amounttype] = buyProducerTypes[type] const buythisamount = autobuyer ? 500 : player[`${amounttype}buyamount` as const] let r = 1 - r += (G.rune4level * G.effectiveLevelMult) / 160 + r += getRuneEffects('thrift').costDelay r += (player.researches[56] + player.researches[57] + player.researches[58] + player.researches[59] + player.researches[60]) / 200 r += CalcECC('transcend', player.challengecompletions[4]) / 200 @@ -690,9 +655,15 @@ export const buyUpgrades = (type: Upgrade, pos: number, state?: boolean) => { export const calculateCrystalBuy = (i: number) => { const u = i - 1 const exponent = Decimal.log(player.prestigeShards.add(1), 10) - + const exponentCostReduction = getRuneEffects('prism').costDivisorLog10 const toBuy = Math.floor( - Math.pow(Math.max(0, 2 * (exponent - G.crystalUpgradesCost[u]) / G.crystalUpgradeCostIncrement[u] + 1 / 4), 1 / 2) + Math.pow( + Math.max( + 0, + 2 * (exponent + exponentCostReduction - G.crystalUpgradesCost[u]) / G.crystalUpgradeCostIncrement[u] + 1 / 4 + ), + 1 / 2 + ) + 1 / 2 ) return toBuy @@ -702,11 +673,12 @@ export const buyCrystalUpgrades = (i: number, auto = false) => { const u = i - 1 let c = 0 - c += Math.floor(G.rune3level / 16 * G.effectiveLevelMult) * 100 / 100 if (player.upgrades[73] > 0.5 && player.currentChallenge.reincarnation !== 0) { c += 10 } + const costReduction = getRuneEffects('prism').costDivisorLog10 + const toBuy = calculateCrystalBuy(i) if (toBuy + c > player.crystalUpgrades[u]) { @@ -715,12 +687,15 @@ export const buyCrystalUpgrades = (i: number, auto = false) => { player.prestigeShards = player.prestigeShards.sub( Decimal.pow( 10, - G.crystalUpgradesCost[u] + G.crystalUpgradeCostIncrement[u] * (1 / 2 * Math.pow(toBuy - 1 / 2, 2) - 1 / 8) + G.crystalUpgradesCost[u] - costReduction + + G.crystalUpgradeCostIncrement[u] * (1 / 2 * Math.pow(toBuy - 1 / 2, 2) - 1 / 8) ) ) if (!auto) { crystalupgradedescriptions(i) } + // This can sometimes just happen... yeah pretty bad! + player.prestigeShards = player.prestigeShards.max(0) } } } @@ -738,12 +713,12 @@ export const boostAccelerator = (automated?: boolean) => { player.acceleratorBoostCost = player.acceleratorBoostCost.times(1e10).times( Decimal.pow(10, player.acceleratorBoostBought) ) - if (player.acceleratorBoostBought > (1000 * (1 + 2 * G.effectiveRuneBlessingPower[4]))) { + if (player.acceleratorBoostBought > (1000 * getRuneBlessingEffect('thrift').accelBoostCostDelay)) { player.acceleratorBoostCost = player.acceleratorBoostCost.times( Decimal.pow( 10, - Math.pow(player.acceleratorBoostBought - (1000 * (1 + 2 * G.effectiveRuneBlessingPower[4])), 2) - / (1 + 2 * G.effectiveRuneBlessingPower[4]) + Math.pow(player.acceleratorBoostBought - (1000 * getRuneBlessingEffect('thrift').accelBoostCostDelay), 2) + / getRuneBlessingEffect('thrift').accelBoostCostDelay ) ) } @@ -829,27 +804,7 @@ export const boostAccelerator = (automated?: boolean) => { } G.ticker = 0 - if (player.acceleratorBoostBought >= 2 && player.achievements[162] === 0) { - achievementaward(162) - } - if (player.acceleratorBoostBought >= 10 && player.achievements[163] === 0) { - achievementaward(163) - } - if (player.acceleratorBoostBought >= 50 && player.achievements[164] === 0) { - achievementaward(164) - } - if (player.acceleratorBoostBought >= 200 && player.achievements[165] === 0) { - achievementaward(165) - } - if (player.acceleratorBoostBought >= 1000 && player.achievements[166] === 0) { - achievementaward(166) - } - if (player.acceleratorBoostBought >= 5000 && player.achievements[167] === 0) { - achievementaward(167) - } - if (player.acceleratorBoostBought >= 15000 && player.achievements[168] === 0) { - achievementaward(168) - } + awardAchievementGroup('acceleratorBoosts') } const getAcceleratorBoostCost = (level = 1): Decimal => { @@ -857,7 +812,7 @@ const getAcceleratorBoostCost = (level = 1): Decimal => { level-- const buymax = Math.pow(10, 15) const base = new Decimal(1e3) - const eff = 1 + 2 * G.effectiveRuneBlessingPower[4] + const eff = getRuneBlessingEffect('thrift').accelBoostCostDelay const linSum = (n: number) => n * (n + 1) / 2 const sqrSum = (n: number) => n * (n + 1) * (2 * n + 1) / 6 let cost = base @@ -1204,7 +1159,7 @@ export const getTesseractCost = ( return [actualBuy, actualCost] } -export const buyTesseractBuilding = (index: OneToFive, amount = player.tesseractbuyamount) => { +export const buyTesseractBuilding = (index: OneToFive, amount: number = player.tesseractbuyamount) => { const intCost = tesseractBuildingCosts[index - 1] const ascendBuildingIndex = `ascendBuilding${index}` as const // Destructuring FTW! @@ -1214,113 +1169,3 @@ export const buyTesseractBuilding = (index: OneToFive, amount = player.tesseract player.wowTesseracts.sub(actualCost) player[ascendBuildingIndex].cost = intCost * Math.pow(1 + buyTo, 3) } - -export const buyRuneBonusLevels = (type: 'Blessings' | 'Spirits', index: number) => { - const unlocked = type === 'Spirits' ? player.challengecompletions[12] > 0 : player.achievements[134] === 1 - if (unlocked && isFinite(player.runeshards) && player.runeshards > 0) { - let baseCost: number - let baseLevels: number - let levelCap: number - if (type === 'Spirits') { - baseCost = G.spiritBaseCost - baseLevels = player.runeSpiritLevels[index] - levelCap = player.runeSpiritBuyAmount - } else { - baseCost = G.blessingBaseCost - baseLevels = player.runeBlessingLevels[index] - levelCap = player.runeBlessingBuyAmount - } - - const [level, cost] = calculateSummationLinear(baseLevels, baseCost, player.runeshards, levelCap) - if (type === 'Spirits') { - player.runeSpiritLevels[index] = level - } else { - player.runeBlessingLevels[index] = level - } - - player.runeshards -= cost - - if (player.runeshards < 0) { - player.runeshards = 0 - } - - updateRuneBlessing(type, index) - } -} - -export const updateRuneBlessing = (type: 'Blessings' | 'Spirits', index: number) => { - if (index === 1) { - const requirementArray = [0, 1e5, 1e8, 1e11] - for (let i = 1; i <= 3; i++) { - if (player.runeBlessingLevels[1] >= requirementArray[i] && player.achievements[231 + i] < 1) { - achievementaward(231 + i) - } - if (player.runeSpiritLevels[1] >= 10 * requirementArray[i] && player.achievements[234 + i] < 1) { - achievementaward(234 + i) - } - } - if (player.runeBlessingLevels[1] >= 1e22 && player.achievements[245] < 1) { - achievementaward(245) - } - } - - calculateRuneBonuses() - - if (type === 'Blessings') { - const blessingMultiplierArray = [0, 8, 10, 6.66, 2, 1] - const t = (index === 5) ? 1 : 0 - DOMCacheGetOrSet(`runeBlessingPower${index}Value1`).innerHTML = i18next.t('runes.blessings.blessingPower', { - reward: i18next.t(`runes.blessings.rewards.${index - 1}`), - value: format(G.runeBlessings[index]), - speed: format(1 - t + blessingMultiplierArray[index] * G.effectiveRuneBlessingPower[index], 4, true) - }) - } else if (type === 'Spirits') { - const spiritMultiplierArray = [0, 1, 1, 20, 1, 100] - spiritMultiplierArray[index] *= player.corruptions.used.totalCorruptionDifficultyScore / 400 - const t = (index === 3) ? 1 : 0 - - DOMCacheGetOrSet(`runeSpiritPower${index}Value1`).innerHTML = i18next.t('runes.spirits.spiritPower', { - reward: i18next.t(`runes.spirits.rewards.${index - 1}`), - value: format(G.runeSpirits[index]), - speed: format(1 - t + spiritMultiplierArray[index] * G.effectiveRuneSpiritPower[index], 4, true) - }) - } -} - -export const buyAllBlessings = (type: 'Blessings' | 'Spirits', percentage = 100, auto = false) => { - const unlocked = type === 'Spirits' ? player.challengecompletions[12] > 0 : player.achievements[134] === 1 - if (unlocked) { - const runeshards = Math.floor(player.runeshards / 100 * percentage / 5) - for (let index = 1; index < 6; index++) { - if (isFinite(player.runeshards) && player.runeshards > 0) { - let baseCost: number - let baseLevels: number - const levelCap = 1e300 - if (type === 'Spirits') { - baseCost = G.spiritBaseCost - baseLevels = player.runeSpiritLevels[index] - } else { - baseCost = G.blessingBaseCost - baseLevels = player.runeBlessingLevels[index] - } - - const [level, cost] = calculateSummationLinear(baseLevels, baseCost, runeshards, levelCap) - if (level > baseLevels && (!auto || (level - baseLevels) * 10000 > baseLevels)) { - if (type === 'Spirits') { - player.runeSpiritLevels[index] = level - } else { - player.runeBlessingLevels[index] = level - } - - player.runeshards -= cost - - if (player.runeshards < 0) { - player.runeshards = 0 - } - - updateRuneBlessing(type, index) - } - } - } - } -} diff --git a/src/Calculate.ts b/src/Calculate.ts index c90fe17b4..b15360b5a 100644 --- a/src/Calculate.ts +++ b/src/Calculate.ts @@ -1,16 +1,22 @@ import Decimal from 'break_infinity.js' import i18next from 'i18next' -import { achievementaward } from './Achievements' +import { awardUngroupedAchievement, getAchievementReward } from './Achievements' import { DOMCacheGetOrSet } from './Cache/DOM' import { CalcECC } from './Challenges' +import { calculateAntELOCubeBlessing, calculateAntSacrificeCubeBlessing, calculateObtainiumCubeBlessing } from './Cubes' import { BuffType, calculateEventSourceBuff } from './Event' import { addTimers, automaticTools } from './Helper' import { hepteractEffective } from './Hepteracts' import { disableHotkeys, enableHotkeys } from './Hotkeys' +import { getLevelMilestone } from './Levels' +import { getOcteractUpgradeEffect } from './Octeracts' +import { calculateAscensionScorePlatonicBlessing } from './PlatonicCubes' import { PCoinUpgradeEffects } from './PseudoCoinUpgrades' import { quarkHandler } from './Quark' -import { getRedAmbrosiaUpgrade } from './RedAmbrosiaUpgrades' -import { reset } from './Reset' +import { getRedAmbrosiaUpgradeEffects } from './RedAmbrosiaUpgrades' +import { reset, updatePrestigeCount, updateReincarnationCount, updateTranscensionCount } from './Reset' +import { getRuneEffects, sumOfRuneLevels } from './Runes' +import { getGQUpgradeEffect } from './singularity' import { allAdditiveLuckMultStats, allAmbrosiaBlueberryStats, @@ -40,14 +46,15 @@ import { allTesseractStats, allWowCubeStats, antSacrificeRewardStats, - antSacrificeTimeStats, - offeringObtainiumTimeModifiers + negativeSalvageStats, + offeringObtainiumTimeModifiers, + positiveSalvageStats } from './Statistics' import { format, getTimePinnedToLoadDate, player, resourceGain, saveSynergy, updateAll } from './Synergism' -import { toggleTalismanBuy, updateTalismanInventory } from './Talismans' +import { getTalismanEffects, toggleTalismanBuy, updateTalismanInventory } from './Talismans' import { clearInterval, setInterval } from './Timers' import { Alert, Prompt } from './UpdateHTML' -import { findInsertionIndex, productContents, sumContents } from './Utility' +import { findInsertionIndex, sumContents } from './Utility' import { Globals as G } from './Variables' const CASH_GRAB_ULTRA_QUARK = 0.08 @@ -95,43 +102,41 @@ export const calculateBaseOfferings = () => { return allBaseOfferingStats.reduce((a, b) => a + b.stat(), 0) } -export const calculateOfferings = (timeMultUsed = true, logMultOnly = false) => { +export const calculateOfferings = (timeMultUsed = true) => { const baseOfferings = calculateBaseOfferings() const timeMultiplier = timeMultUsed - ? offeringObtainiumTimeModifiers(player.prestigecounter, player.prestigeCount > 0).reduce( + ? offeringObtainiumTimeModifiers(player.prestigecounter, getLevelMilestone('offeringTimerScaling') === 1).reduce( (a, b) => a * b.stat(), 1 ) : 1 - const logMult = Decimal.log(calculateOfferingsDecimal(), 10) + const offeringMult = calculateOfferingsDecimal() - const totalLog = Math.log10(timeMultiplier) + logMult - - if (logMultOnly) { - return totalLog - } - - const effectivePowerOfTen = Math.pow(10, Math.min(300, totalLog)) - - // Update Offering Per Second Statistic (For Plat: is this still needed?) - if (timeMultUsed) { - if (player.prestigecounter === 0) { - player.offeringpersecond = 0 - } else { - player.offeringpersecond = effectivePowerOfTen / player.prestigecounter - } + // Exalt 8 2+ Effect + if ( + player.singularityChallenges.taxmanLastStand.enabled + && player.singularityChallenges.taxmanLastStand.completions >= 2 + ) { + return Decimal.min( + player.offerings.times(100).plus(1), + Decimal.max( + baseOfferings, + offeringMult.times(timeMultiplier) + ) + ) } - return Math.max(baseOfferings, effectivePowerOfTen) -} - -export const calculateOfferingToDecimal = (timeMultUsed = false) => { - return Decimal.pow(10, calculateOfferings(timeMultUsed, true)) + return Decimal.max( + baseOfferings, + offeringMult.times(timeMultiplier) + ) } // Ditto export const calculateObtainiumDecimal = () => { - return allObtainiumStats.reduce((a, b) => a.times(b.stat()), new Decimal(1)) + return allObtainiumStats.reduce((a, b) => a.times(b.stat()), new Decimal(1)).times( + calculateObtainiumCubeBlessing() + ) } export const calculateBaseObtainium = () => { @@ -147,7 +152,7 @@ export const calculateObtainiumDRIgnoreMult = () => { * @param logMultOnly Default false. If true, returns the log10 of the obtainium multiplier, possibly greater than 300. * @returns */ -export const calculateObtainium = (timeMultUsed = true, logMultOnly = false) => { +export const calculateObtainium = (timeMultUsed = true) => { // Base Obtainium const base = calculateBaseObtainium() @@ -168,7 +173,7 @@ export const calculateObtainium = (timeMultUsed = true, logMultOnly = false) => // Some large value (say, -99999). We're doing this to preserve multipliers past 1e300 // For purposes of corruption (It is okay to apply illiteracy to 1e600 if DR is 0.2, since you are left with 1e120) - const logMult = Decimal.log(calculateObtainiumDecimal(), 10) + const baseMults = calculateObtainiumDecimal() // Thanks to the logMult, we can treat the corruption effect as a multplier instead of an exponent. // The simplest formula for the effect on obtainium for is (Immaculate)^(1 - DR) * Mult^DR so logarithmic @@ -177,63 +182,44 @@ export const calculateObtainium = (timeMultUsed = true, logMultOnly = false) => // Why is this a thing? If DR = 0 (which is possible), then the calculation below will not catch chal 14 enabled. if (player.currentChallenge.ascension === 14) { - player.offeringpersecond = 0 - return 0 + return new Decimal('0') } - const logTotal = Math.log10(immaculate) + DR * logMult + Math.log10(timeMultiplier) - - if (logMultOnly) { - return logTotal - } - - const effectivePowerOfTen = Math.min( - 300, - logTotal + const total = new Decimal(immaculate).times(Decimal.pow(baseMults, DR)).times(timeMultiplier).times( + calculateObtainiumCubeBlessing() ) - // As of Statistics Update, you can never get less than your base Offerings per Reincarnation, no matter what. - const finalRawValue = Math.max(base, Math.pow(10, effectivePowerOfTen)) - - // Update OPS - if (timeMultUsed) { - if (player.reincarnationcounter === 0) { - player.obtainiumpersecond = 0 - } else { - player.obtainiumpersecond = finalRawValue / player.reincarnationcounter - player.maxobtainiumpersecond = Math.max(player.maxobtainiumpersecond, player.obtainiumpersecond) - } + if ( + player.singularityChallenges.taxmanLastStand.enabled + && player.singularityChallenges.taxmanLastStand.completions >= 2 + ) { + return Decimal.min( + player.obtainium.times(100).plus(1), + Decimal.max(base, total) + ) } - return finalRawValue -} - -/** - * @param timeMultUsed Default false. If true, gives proper time multiplier - * @returns Decimal of the obtainium multiplier, after all calculations. - */ -export const calculateObtainiumToDecimal = (timeMultUsed = false) => { - return Decimal.pow(10, calculateObtainium(timeMultUsed, true)) + // As of Statistics Update, you can never get less than your base Offerings per Reincarnation, no matter what. + return Decimal.max(base, total) } export const calculateFastForwardResourcesGlobal = ( resetTime: number, - fastForwardAmount: number, + fastForwardAmount: Decimal, resourceMult: Decimal, baseResource: number ) => { // We're going to use the log trick to account for the fact that resourceMult * timeMult can still be >1e300 // Even if timeMult is very small. - const logMult = Decimal.log10(resourceMult) - // Math to compute the change in multiplier based on time // The amount of offerings to give is proportional to the difference in // Time Multipliers. - let timeMultiplier: number + let timeMultiplier: Decimal = new Decimal('1') - const deltaTime = fastForwardAmount - * (player.singularityUpgrades.halfMind.getEffect().bonus ? 10 : calculateGlobalSpeedMult()) + const deltaTime = fastForwardAmount.times( + getGQUpgradeEffect('halfMind') ? 10 : calculateGlobalSpeedMult() + ) // Build approximations through direct computation of the derivative of time multiplier // And then multiplying by deltaTime, so basically a linear approximation (See: Calculus) @@ -243,58 +229,51 @@ export const calculateFastForwardResourcesGlobal = ( // two approximations: one with quadratic penalty (if less than threshold) and that of linear penalty // Use the derivative of the quadratic part - timeMultiplier = Math.min( - 2 * resetTime * deltaTime / Math.pow(resetTimeThreshold(), 2), - deltaTime / resetTimeThreshold() + timeMultiplier = Decimal.min( + deltaTime.times(2 * resetTime).div(resetTimeThreshold() ** 2), + deltaTime.div(resetTimeThreshold()) ) // Correct multiplier if half mind is purchased - timeMultiplier *= player.singularityUpgrades.halfMind.getEffect().bonus ? calculateGlobalSpeedMult() / 10 : 1 - - timeMultiplier = Math.min(1e300, timeMultiplier) - - const logTime = Math.log10(timeMultiplier) + timeMultiplier.times(getGQUpgradeEffect('halfMind') ? calculateGlobalSpeedMult() / 10 : 1) - return Math.min(1e300, Math.max(baseResource * fastForwardAmount, Math.pow(10, Math.min(300, logMult + logTime)))) + return Decimal.max(fastForwardAmount.times(baseResource), resourceMult.times(timeMultiplier)) } export const calculatePotionValue = (resetTime: number, resourceMult: Decimal, baseResource: number) => { - const potionTimeValue = 7200 + const potionTimeValue = new Decimal(7200) const fastForwardMult = calculateFastForwardResourcesGlobal(resetTime, potionTimeValue, resourceMult, baseResource) - const potionMultipliers = productContents([ - +player.singularityUpgrades.potionBuff.getEffect().bonus - * +player.singularityUpgrades.potionBuff2.getEffect().bonus - * +player.singularityUpgrades.potionBuff3.getEffect().bonus - * +player.octeractUpgrades.octeractAutoPotionEfficiency.getEffect().bonus - ]) + const potionMultipliers = getGQUpgradeEffect('potionBuff') + * getGQUpgradeEffect('potionBuff2') + * getGQUpgradeEffect('potionBuff3') + * getOcteractUpgradeEffect('octeractAutoPotionEfficiency') - return Math.min(1e300, fastForwardMult * potionMultipliers) + return fastForwardMult.times(potionMultipliers) } export const calculateResearchAutomaticObtainium = (deltaTime: number) => { if (player.currentChallenge.ascension === 14) { - return 0 + return new Decimal('0') } - const multiplier = productContents([ - 0.5 * player.researches[61] + 0.1 * player.researches[62], - 1 + 0.8 * player.cubeUpgrades[3] - ]) + const multiplier = (0.5 * player.researches[61] + 0.1 * player.researches[62]) + * (1 + 0.8 * player.cubeUpgrades[3]) if (multiplier === 0) { - return 0 + return new Decimal('0') } const baseObtainium = calculateBaseObtainium() - const resourceMult = calculateObtainiumToDecimal() + const resourceMult = calculateObtainium() const fastForwardMult = calculateFastForwardResourcesGlobal( player.reincarnationcounter, - deltaTime, + new Decimal(deltaTime), resourceMult, baseObtainium ) - return Math.min(1e300, fastForwardMult * multiplier) + + return fastForwardMult.times(multiplier) } export const calculateQuarkMultiplier = () => { @@ -302,49 +281,51 @@ export const calculateQuarkMultiplier = () => { } export const calculateAntSacrificeMultiplier = () => { - return antSacrificeRewardStats.reduce((a, b) => a * b.stat(), 1) + return antSacrificeRewardStats.reduce((a, b) => a.times(b.stat()), new Decimal(1)).times( + calculateAntSacrificeCubeBlessing() + ) } -export const calculateAntSacrificeObtainium = () => { +export const calculateAntSacrificeObtainiumMultiplier = () => { const base = 1 / 750 const antSacMult = calculateAntSacrificeMultiplier() - const obtainiumMult = calculateObtainiumToDecimal() - const baseObtainium = calculateBaseObtainium() - calculateAntSacrificeELO() - - const timeMult = antSacrificeTimeStats(player.antSacrificeTimer, player.achievements[177] > 0).reduce( - (a, b) => a * b.stat(), - 1 - ) - const deltaTime = Math.min( - 1e300, - base * antSacMult * G.effectiveELO * timeMult - ) - - return Math.max( - baseObtainium, - calculateFastForwardResourcesGlobal(player.reincarnationcounter, deltaTime, obtainiumMult, baseObtainium) - ) + const effectiveELO = calculateEffectiveAntELO() + return new Decimal(base).times(antSacMult).times(effectiveELO) } -export const calculateAntSacrificeOffering = () => { +export const calculateAntSacrificeOfferingMultiplier = () => { const base = 1 / 1200 const antSacMult = calculateAntSacrificeMultiplier() - const offeringMult = calculateOfferingToDecimal() - const baseOfferings = calculateBaseOfferings() - - calculateAntSacrificeELO() + const effectiveELO = calculateEffectiveAntELO() + return new Decimal(base).times(antSacMult).times(effectiveELO) +} - const timeMult = offeringObtainiumTimeModifiers(player.antSacrificeTimer, player.achievements[177] > 0).reduce( - (a, b) => a * b.stat(), - 1 - ) - const deltaTime = Math.min(1e300, base * antSacMult * G.effectiveELO * timeMult) +export const calculateAntSacrificeObtainium = () => { + if ( + player.singularityChallenges.taxmanLastStand.enabled + && player.singularityChallenges.taxmanLastStand.completions >= 2 + ) { + return Decimal.min( + player.obtainium.times(100).plus(1), + calculateObtainium().times(calculateAntSacrificeObtainiumMultiplier()) + ) + } else { + return calculateObtainium().times(calculateAntSacrificeObtainiumMultiplier()) + } +} - return Math.max( - baseOfferings, - calculateFastForwardResourcesGlobal(player.prestigecounter, deltaTime, offeringMult, baseOfferings) - ) +export const calculateAntSacrificeOffering = () => { + if ( + player.singularityChallenges.taxmanLastStand.enabled + && player.singularityChallenges.taxmanLastStand.completions >= 2 + ) { + return Decimal.min( + player.offerings.times(100).plus(1), + calculateOfferings().times(calculateAntSacrificeOfferingMultiplier()) + ) + } else { + return calculateOfferings().times(calculateAntSacrificeOfferingMultiplier()) + } } export const calculateGlobalSpeedDRIgnoreMult = () => { @@ -368,12 +349,12 @@ export const calculateGlobalSpeedMult = () => { const totalTimeMultiplier = normalMult * immaculateMult // Achievement Stuffs // One second in 100 years - if (totalTimeMultiplier < 1 / (3600 * 24 * 365 * 100) && player.achievements[241] < 1) { - achievementaward(241) + if (totalTimeMultiplier < 1 / (3600 * 24 * 365 * 100)) { + awardUngroupedAchievement('verySlow') } // One hour in a second - if (totalTimeMultiplier > 3600 && player.achievements[242] < 1) { - achievementaward(242) + if (totalTimeMultiplier > 3600) { + awardUngroupedAchievement('veryFast') } return totalTimeMultiplier @@ -470,32 +451,13 @@ export const calculateTotalAcceleratorBoost = () => { if (player.upgrades[31] > 0.5) { b += (Math.floor(G.totalCoinOwned / 2000) * 100) / 100 } - if (player.achievements[7] > 0.5) { - b += Math.floor(player.firstOwnedCoin / 2000) - } - if (player.achievements[14] > 0.5) { - b += Math.floor(player.secondOwnedCoin / 2000) - } - if (player.achievements[21] > 0.5) { - b += Math.floor(player.thirdOwnedCoin / 2000) - } - if (player.achievements[28] > 0.5) { - b += Math.floor(player.fourthOwnedCoin / 2000) - } - if (player.achievements[35] > 0.5) { - b += Math.floor(player.fifthOwnedCoin / 2000) - } + b += +getAchievementReward('accelBoosts') b += player.researches[93] * Math.floor( (1 / 20) - * (G.rune1level - + G.rune2level - + G.rune3level - + G.rune4level - + G.rune5level) + * sumOfRuneLevels() ) - b += Math.floor(((0.01 + G.rune1level) * G.effectiveLevelMult) / 20) b *= 1 + (1 / 5) * player.researches[3] @@ -529,9 +491,6 @@ export const calculateTotalAcceleratorBoost = () => { export const calculateAcceleratorMultiplier = () => { G.acceleratorMultiplier = 1 - G.acceleratorMultiplier *= 1 + player.achievements[60] / 100 - G.acceleratorMultiplier *= 1 + player.achievements[61] / 100 - G.acceleratorMultiplier *= 1 + player.achievements[62] / 100 G.acceleratorMultiplier *= 1 + (1 / 5) * player.researches[1] @@ -567,518 +526,60 @@ export const calculateAcceleratorMultiplier = () => { } } -export const calculateRecycleMultiplier = () => { - // Factors where recycle bonus comes from - const recycleFactors = sumContents([ - 0.05 * player.achievements[80], - 0.05 * player.achievements[87], - 0.05 * player.achievements[94], - 0.05 * player.achievements[101], - 0.05 * player.achievements[108], - 0.05 * player.achievements[115], - 0.075 * player.achievements[122], - 0.075 * player.achievements[129], - 0.05 * player.upgrades[61], - 0.25 * Math.min(1, G.rune4level / 400), - 0.005 * player.cubeUpgrades[2] - ]) - - return 1 / (1 - recycleFactors) -} - -export function calculateRuneExpGiven ( - runeIndex: number, - all: boolean, - runeLevel: number, - returnFactors: true -): number[] -export function calculateRuneExpGiven ( - runeIndex: number, - all: boolean, - runeLevel?: number, - returnFactors?: false -): number -export function calculateRuneExpGiven ( - runeIndex: number, - all = false, - runeLevel = player.runelevels[runeIndex], - returnFactors = false -) { - // recycleMult accounted for all recycle chance, but inversed so it's a multiplier instead - const recycleMultiplier = calculateRecycleMultiplier() - - // Rune multiplier that is summed instead of added - let allRuneExpAdditiveMultiplier: number | null = null - if (all) { - allRuneExpAdditiveMultiplier = sumContents([ - // Challenge 3 completions - (1 / 100) * player.highestchallengecompletions[3], - // Reincarnation 2x1 - 1 * player.upgrades[66] - ]) - } else { - allRuneExpAdditiveMultiplier = sumContents([ - // Base amount multiplied per offering - 1, - // +1 if C1 completion - Math.min(1, player.highestchallengecompletions[1]), - // +0.10 per C1 completion - (0.4 / 10) * player.highestchallengecompletions[1], - // Research 5x2 - 0.6 * player.researches[22], - // Research 5x3 - 0.3 * player.researches[23], - // Particle Upgrade 1x1 - 2 * player.upgrades[61], - // Particle upgrade 3x1 - (player.upgrades[71] * runeLevel) / 25 - ]) - } - - // Rune multiplier that gets applied to all runes - const allRuneExpMultiplier = productContents([ - // Research 4x16 - 1 + player.researches[91] / 20, - // Research 4x17 - 1 + player.researches[92] / 20, - // Ant 8 - calculateSigmoidExponential( - 999, - (1 / 10000) * Math.pow(player.antUpgrades[8 - 1]! + G.bonusant8, 1.1) - ), - // Cube Bonus - G.cubeBonusMultiplier[4], - // Cube Upgrade Bonus - 1 + (player.ascensionCounter / 1000) * player.cubeUpgrades[32], - // Constant Upgrade Multiplier - 1 + (1 / 10) * player.constantUpgrades[8], - // Challenge 15 reward multiplier - G.challenge15Rewards.runeExp.value - ]) - // Corruption Divisor - const droughtEffect = 1 - / Math.pow( - G.droughtMultiplier[player.corruptions.used.drought], - 1 - (1 / 2) * player.platonicUpgrades[13] - ) - - // Rune multiplier that gets applied to specific runes - const runeExpMultiplier = [ - productContents([ - 1 + player.researches[78] / 50, - 1 + player.researches[111] / 100, - 1 + CalcECC('reincarnation', player.challengecompletions[7]) / 10, - droughtEffect - ]), - productContents([ - 1 + player.researches[80] / 50, - 1 + player.researches[112] / 100, - 1 + CalcECC('reincarnation', player.challengecompletions[7]) / 10, - droughtEffect - ]), - productContents([ - 1 + player.researches[79] / 50, - 1 + player.researches[113] / 100, - 1 + CalcECC('reincarnation', player.challengecompletions[8]) / 5, - droughtEffect - ]), - productContents([ - 1 + player.researches[77] / 50, - 1 + player.researches[114] / 100, - 1 + CalcECC('reincarnation', player.challengecompletions[6]) / 10, - droughtEffect - ]), - productContents([ - 1 + player.researches[83] / 20, - 1 + player.researches[115] / 100, - 1 + CalcECC('reincarnation', player.challengecompletions[9]) / 5, - droughtEffect - ]), - productContents([1]), - productContents([1]) - ] - - const fact = [ - allRuneExpAdditiveMultiplier, - allRuneExpMultiplier, - recycleMultiplier, - runeExpMultiplier[runeIndex] - ] +export const calculatePositiveSalvageMultiplier = () => { + const posSalvagePerkSings = [230, 245, 260, 275, 290] + let multiplier = 1 + posSalvagePerkSings.filter((x) => x <= player.highestSingularityCount).length / 100 + multiplier += getTalismanEffects('achievement').positiveSalvageMult - return returnFactors ? fact : Math.min(1e200, productContents(fact)) + return multiplier } -export const lookupTableGen = (runeLevel: number) => { - // Rune exp required to level multipliers - const allRuneExpRequiredMultiplier = productContents([ - Math.pow((runeLevel + 1) / 2, 3), - (3.5 * runeLevel + 100) / 500, - Math.max(1, (runeLevel - 200) / 9), - Math.max(1, (runeLevel - 400) / 12), - Math.max(1, (runeLevel - 600) / 15), - Math.max(1, Math.pow(1.03, (runeLevel - 800) / 4)) - ]) - - return allRuneExpRequiredMultiplier +export const calculateRawPositiveSalvage = () => { + return positiveSalvageStats.reduce((a, b) => a + b.stat(), 0) } -let lookupTableRuneExp: number[] | null = null - -// Returns the amount of exp required to level a rune -export const calculateRuneExpToLevel = ( - runeIndex: number, - runeLevel = player.runelevels[runeIndex] -) => { - lookupTableRuneExp ??= Array.from({ length: 40000 + 1 }, (_, i) => lookupTableGen(i)) - - // For runes 6 and 7 we will apply a special multiplier - let multiplier = lookupTableRuneExp[runeLevel] - if (runeIndex === 5) { - multiplier = Math.pow(100, runeLevel) - } - if (runeIndex === 6) { - multiplier = Math.pow(1e25, runeLevel) * (player.highestSingularityCount + 1) - } - return multiplier * G.runeexpbase[runeIndex] -} - -export const calculateMaxRunes = (i: number) => { - let max = 1000 - - const increaseAll = 20 * (player.cubeUpgrades[16] + player.cubeUpgrades[37]) - + 3 * player.constantUpgrades[7] - + 80 * CalcECC('ascension', player.challengecompletions[11]) - + 200 * CalcECC('ascension', player.challengecompletions[14]) - + Math.floor(0.04 * player.researches[200] + 0.04 * player.cubeUpgrades[50]) - const increaseMaxLevel = [ - null, - 10 * (player.researches[78] + player.researches[111]) + increaseAll, - 10 * (player.researches[80] + player.researches[112]) + increaseAll, - 10 * (player.researches[79] + player.researches[113]) + increaseAll, - 10 * (player.researches[77] + player.researches[114]) + increaseAll, - 10 * player.researches[115] + increaseAll, - -901, - -999 - ] +export const calculatePositiveSalvage = () => { + if (player.singularityChallenges.taxmanLastStand.enabled) { + const baseSalvage = 100 + const positiveSalvage = calculateRawPositiveSalvage() - max = increaseMaxLevel[i]! > G.runeMaxLvl - ? G.runeMaxLvl - : max + increaseMaxLevel[i]! - return max + return baseSalvage + + (positiveSalvage * calculatePositiveSalvageMultiplier()) / Math.max(1, Math.log(positiveSalvage)) + } + return calculateRawPositiveSalvage() * calculatePositiveSalvageMultiplier() } -export const calculateEffectiveIALevel = () => { - let bonus = PCoinUpgradeEffects.INSTANT_UNLOCK_2 ? 6 : 0 - bonus += player.cubeUpgrades[73] - bonus += player.campaigns.bonusRune6 - const totalRawLevel = player.runelevels[5] + bonus - return ( - totalRawLevel - + Math.max(0, totalRawLevel - 74) - + Math.max(0, totalRawLevel - 98) - ) +export const calculateNegativeSalvageMultiplier = () => { + const negSalvagePerkSings = [75, 85, 105, 125, 155, 185, 215, 245, 260, 275] + let multiplier = 1 - negSalvagePerkSings.filter((x) => x <= player.highestSingularityCount).length / 100 + multiplier += getTalismanEffects('achievement').negativeSalvageMult + return multiplier } -// TODO: REFACTOR THIS - May 15, 2022. -export const calculateTalismanEffects = () => { - let positiveBonus = 0 - let negativeBonus = 0 - if (player.achievements[135] === 1) { - positiveBonus += 0.02 - } - if (player.achievements[136] === 1) { - positiveBonus += 0.02 - } - positiveBonus += 0.02 * (player.talismanRarity[4 - 1] - 1) - positiveBonus += (3 * player.researches[106]) / 100 - positiveBonus += (3 * player.researches[107]) / 100 - positiveBonus += (3 * player.researches[116]) / 200 - positiveBonus += (3 * player.researches[117]) / 200 - positiveBonus += G.cubeBonusMultiplier[9] - 1 - positiveBonus += 0.0004 * player.cubeUpgrades[50] - negativeBonus += 0.06 * player.researches[118] - negativeBonus += 0.0004 * player.cubeUpgrades[50] - - if (player.highestSingularityCount >= 7) { - positiveBonus += negativeBonus - negativeBonus = positiveBonus - } - - if (player.highestSingularityCount < 7) { - for (let i = 1; i <= 5; i++) { - if (player.talismanOne[i] === 1) { - G.talisman1Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[1 - 1]]! - + positiveBonus) - * player.talismanLevels[1 - 1] - * G.challenge15Rewards.talismanBonus.value - } else { - G.talisman1Effect[i] = (G.talismanNegativeModifier[player.talismanRarity[1 - 1]]! - - negativeBonus) - * player.talismanLevels[1 - 1] - * -1 - * G.challenge15Rewards.talismanBonus.value - } - - if (player.talismanTwo[i] === 1) { - G.talisman2Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[2 - 1]]! - + positiveBonus) - * player.talismanLevels[2 - 1] - * G.challenge15Rewards.talismanBonus.value - } else { - G.talisman2Effect[i] = (G.talismanNegativeModifier[player.talismanRarity[2 - 1]]! - - negativeBonus) - * player.talismanLevels[2 - 1] - * -1 - * G.challenge15Rewards.talismanBonus.value - } - - if (player.talismanThree[i] === 1) { - G.talisman3Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[3 - 1]]! - + positiveBonus) - * player.talismanLevels[3 - 1] - * G.challenge15Rewards.talismanBonus.value - } else { - G.talisman3Effect[i] = (G.talismanNegativeModifier[player.talismanRarity[3 - 1]]! - - negativeBonus) - * player.talismanLevels[3 - 1] - * -1 - * G.challenge15Rewards.talismanBonus.value - } - - if (player.talismanFour[i] === 1) { - G.talisman4Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[4 - 1]]! - + positiveBonus) - * player.talismanLevels[4 - 1] - * G.challenge15Rewards.talismanBonus.value - } else { - G.talisman4Effect[i] = (G.talismanNegativeModifier[player.talismanRarity[4 - 1]]! - - negativeBonus) - * player.talismanLevels[4 - 1] - * -1 - * G.challenge15Rewards.talismanBonus.value - } +export const calculateRawNegativeSalvage = () => { + return negativeSalvageStats.reduce((a, b) => a + b.stat(), 0) +} - if (player.talismanFive[i] === 1) { - G.talisman5Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[5 - 1]]! - + positiveBonus) - * player.talismanLevels[5 - 1] - * G.challenge15Rewards.talismanBonus.value - } else { - G.talisman5Effect[i] = (G.talismanNegativeModifier[player.talismanRarity[5 - 1]]! - - negativeBonus) - * player.talismanLevels[5 - 1] - * -1 - * G.challenge15Rewards.talismanBonus.value - } +export const calculateNegativeSalvage = () => { + return calculateRawNegativeSalvage() * calculateNegativeSalvageMultiplier() +} - if (player.talismanSix[i] === 1) { - G.talisman6Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[6 - 1]]! - + positiveBonus) - * player.talismanLevels[6 - 1] - * G.challenge15Rewards.talismanBonus.value - } else { - G.talisman6Effect[i] = (G.talismanNegativeModifier[player.talismanRarity[6 - 1]]! - - negativeBonus) - * player.talismanLevels[6 - 1] - * -1 - * G.challenge15Rewards.talismanBonus.value - } +export const calculateTotalSalvage = () => { + return calculatePositiveSalvage() + calculateNegativeSalvage() +} - if (player.talismanSeven[i] === 1) { - G.talisman7Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[7 - 1]]! - + positiveBonus) - * player.talismanLevels[7 - 1] - * G.challenge15Rewards.talismanBonus.value - } else { - G.talisman7Effect[i] = (G.talismanNegativeModifier[player.talismanRarity[7 - 1]]! - - negativeBonus) - * player.talismanLevels[7 - 1] - * -1 - * G.challenge15Rewards.talismanBonus.value - } - } - } else { - for (let i = 1; i <= 5; i++) { - G.talisman1Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[1 - 1]]! - + positiveBonus) - * player.talismanLevels[1 - 1] - * G.challenge15Rewards.talismanBonus.value - G.talisman2Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[2 - 1]]! - + positiveBonus) - * player.talismanLevels[2 - 1] - * G.challenge15Rewards.talismanBonus.value - G.talisman3Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[3 - 1]]! - + positiveBonus) - * player.talismanLevels[3 - 1] - * G.challenge15Rewards.talismanBonus.value - G.talisman4Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[4 - 1]]! - + positiveBonus) - * player.talismanLevels[4 - 1] - * G.challenge15Rewards.talismanBonus.value - G.talisman5Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[5 - 1]]! - + positiveBonus) - * player.talismanLevels[5 - 1] - * G.challenge15Rewards.talismanBonus.value - G.talisman6Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[6 - 1]]! - + positiveBonus) - * player.talismanLevels[6 - 1] - * G.challenge15Rewards.talismanBonus.value - G.talisman7Effect[i] = (G.talismanPositiveModifier[player.talismanRarity[7 - 1]]! - + positiveBonus) - * player.talismanLevels[7 - 1] - * G.challenge15Rewards.talismanBonus.value - } +export const calculateSalvageRuneEXPMultiplier = (salvageVal: number | undefined = undefined): Decimal => { + let salvage = salvageVal + // Factors where Salvage comes from + if (salvage === undefined) { + salvage = calculateTotalSalvage() } - const talismansEffects = [ - G.talisman1Effect, - G.talisman2Effect, - G.talisman3Effect, - G.talisman4Effect, - G.talisman5Effect, - G.talisman6Effect, - G.talisman7Effect - ] - const runesTalisman = [0, 0, 0, 0, 0, 0] - talismansEffects.forEach((talismanEffect) => { - talismanEffect.forEach((levels, runeNumber) => { - runesTalisman[runeNumber] += levels! - }) - }) - ;[ - , - G.rune1Talisman, - G.rune2Talisman, - G.rune3Talisman, - G.rune4Talisman, - G.rune5Talisman - ] = runesTalisman - G.talisman6Power = 0 - G.talisman7Quarks = 0 - if (player.talismanRarity[1 - 1] === 6) { - G.rune2Talisman += 400 - } - if (player.talismanRarity[2 - 1] === 6) { - G.rune1Talisman += 400 - } - if (player.talismanRarity[3 - 1] === 6) { - G.rune4Talisman += 400 - } - if (player.talismanRarity[4 - 1] === 6) { - G.rune3Talisman += 400 - } - if (player.talismanRarity[5 - 1] === 6) { - G.rune5Talisman += 400 - } - if (player.talismanRarity[6 - 1] === 6) { - G.talisman6Power = 2.5 - } - if (player.talismanRarity[7 - 1] === 6) { - G.talisman7Quarks = 2 - } -} - -export const calculateRuneLevels = () => { - calculateTalismanEffects() - if (player.currentChallenge.reincarnation !== 9) { - const antUpgrade8 = player.antUpgrades[8] ?? 0 - G.rune1level = Math.max( - 1, - player.runelevels[0] - + Math.min(1e7, antUpgrade8 + G.bonusant9) * 1 - + G.rune1Talisman - + 7 * player.constantUpgrades[7] - ) - G.rune2level = Math.max( - 1, - player.runelevels[1] - + Math.min(1e7, antUpgrade8 + G.bonusant9) * 1 - + G.rune2Talisman - + 7 * player.constantUpgrades[7] - ) - G.rune3level = Math.max( - 1, - player.runelevels[2] - + Math.min(1e7, antUpgrade8 + G.bonusant9) * 1 - + G.rune3Talisman - + 7 * player.constantUpgrades[7] - ) - G.rune4level = Math.max( - 1, - player.runelevels[3] - + Math.min(1e7, antUpgrade8 + G.bonusant9) * 1 - + G.rune4Talisman - + 7 * player.constantUpgrades[7] - ) - G.rune5level = Math.max( - 1, - player.runelevels[4] - + Math.min(1e7, antUpgrade8 + G.bonusant9) * 1 - + G.rune5Talisman - + 7 * player.constantUpgrades[7] - ) - } - - G.runeSum = sumContents([ - G.rune1level, - G.rune2level, - G.rune3level, - G.rune4level, - G.rune5level - ]) - calculateRuneBonuses() -} - -export const calculateRuneBonuses = () => { - G.blessingMultiplier = 1 - G.spiritMultiplier = 1 - - G.blessingMultiplier *= 1 + (6.9 * player.researches[134]) / 100 - G.blessingMultiplier *= 1 + (player.talismanRarity[3 - 1] - 1) / 10 - G.blessingMultiplier *= 1 + 0.1 * Math.log10(player.epicFragments + 1) * player.researches[174] - G.blessingMultiplier *= 1 + (2 * player.researches[194]) / 100 - if (player.researches[160] > 0) { - G.blessingMultiplier *= Math.pow(1.25, 8) - } - G.spiritMultiplier *= 1 + (8 * player.researches[164]) / 100 - if (player.researches[165] > 0 && player.currentChallenge.ascension !== 0) { - G.spiritMultiplier *= Math.pow(2, 8) - } - G.spiritMultiplier *= 1 - + 0.15 * Math.log10(player.legendaryFragments + 1) * player.researches[189] - G.spiritMultiplier *= 1 + (2 * player.researches[194]) / 100 - G.spiritMultiplier *= 1 + (player.talismanRarity[5 - 1] - 1) / 100 - - for (let i = 1; i <= 5; i++) { - G.runeBlessings[i] = G.blessingMultiplier - * player.runelevels[i - 1] - * player.runeBlessingLevels[i] - G.runeSpirits[i] = G.spiritMultiplier - * player.runelevels[i - 1] - * player.runeSpiritLevels[i] - } - - for (let i = 1; i <= 5; i++) { - if (G.runeBlessings[i] <= 1e30) { - G.effectiveRuneBlessingPower[i] = (Math.pow(G.runeBlessings[i], 1 / 8) / 75) - * G.challenge15Rewards.blessingBonus.value - } else if (G.runeBlessings[i] > 1e30) { - G.effectiveRuneBlessingPower[i] = ((Math.pow(10, 5 / 2) * Math.pow(G.runeBlessings[i], 1 / 24)) / 75) - * G.challenge15Rewards.blessingBonus.value - } - if (G.runeSpirits[i] <= 1e25) { - G.effectiveRuneSpiritPower[i] = (Math.pow(G.runeSpirits[i], 1 / 8) / 75) - * G.challenge15Rewards.spiritBonus.value - } else if (G.runeSpirits[i] > 1e25) { - G.effectiveRuneSpiritPower[i] = ((Math.pow(10, 25 / 12) * Math.pow(G.runeSpirits[i], 1 / 24)) / 75) - * G.challenge15Rewards.spiritBonus.value - } - } + return Decimal.pow(10, salvage / 30) } export const calculateAnts = () => { let bonusLevels = 0 - bonusLevels += 2 * (player.talismanRarity[6 - 1] - 1) bonusLevels += CalcECC('reincarnation', player.challengecompletions[9]) bonusLevels += 2 * player.constantUpgrades[6] bonusLevels += 12 * CalcECC('ascension', player.challengecompletions[11]) @@ -1194,214 +695,97 @@ export const calculateAnts = () => { ) } -export const calculateAntSacrificeELO = () => { - G.antELO = 0 - G.effectiveELO = 0 - const antUpgradeSum = sumContents(player.antUpgrades as number[]) +export const calculateBaseAntELO = () => { + let ELO = 0 + const antUpgradeSum = sumContents(player.antUpgrades) if (player.antPoints.gte('1e40')) { - G.antELO += Decimal.log(player.antPoints, 10) - G.antELO += (1 / 2) * antUpgradeSum - G.antELO += (1 / 10) * player.firstOwnedAnts - G.antELO += (1 / 5) * player.secondOwnedAnts - G.antELO += (1 / 3) * player.thirdOwnedAnts - G.antELO += (1 / 2) * player.fourthOwnedAnts - G.antELO += player.fifthOwnedAnts - G.antELO += 2 * player.sixthOwnedAnts - G.antELO += 4 * player.seventhOwnedAnts - G.antELO += 8 * player.eighthOwnedAnts - G.antELO += 666 * player.researches[178] - G.antELO *= 1 - + 0.01 * player.achievements[180] - + 0.02 * player.achievements[181] - + 0.03 * player.achievements[182] - G.antELO *= 1 + player.researches[110] / 100 - G.antELO *= 1 + (2.5 * player.researches[148]) / 100 - - if (player.achievements[176] === 1) { - G.antELO += 25 - } - if (player.achievements[177] === 1) { - G.antELO += 50 - } - if (player.achievements[178] === 1) { - G.antELO += 75 - } - if (player.achievements[179] === 1) { - G.antELO += 100 - } - G.antELO += 25 * player.researches[108] - G.antELO += 25 * player.researches[109] - G.antELO += 40 * player.researches[123] - G.antELO += 100 * CalcECC('reincarnation', player.challengecompletions[10]) - G.antELO += 75 * player.upgrades[80] - G.antELO = (1 / 10) * Math.floor(10 * G.antELO) - - G.effectiveELO += 0.5 * Math.min(3500, G.antELO) - G.effectiveELO += 0.1 * Math.min(4000, G.antELO) - G.effectiveELO += 0.1 * Math.min(6000, G.antELO) - G.effectiveELO += 0.1 * Math.min(10000, G.antELO) - G.effectiveELO += 0.2 * G.antELO - G.effectiveELO += G.cubeBonusMultiplier[8] - 1 - G.effectiveELO += 1 * player.cubeUpgrades[50] - G.effectiveELO *= 1 + 0.03 * player.upgrades[124] - } -} - -export const calculateAntSacrificeMultipliers = () => { - G.timeMultiplier = Math.min(1, Math.pow(player.antSacrificeTimer / 10, 2)) - if (player.achievements[177] === 0) { - G.timeMultiplier *= Math.min( - 1000, - Math.max(1, player.antSacrificeTimer / 10) - ) - } - if (player.achievements[177] > 0) { - G.timeMultiplier *= Math.max(1, player.antSacrificeTimer / 10) - } - G.timeMultiplier *= player.singularityUpgrades.halfMind.getEffect().bonus ? calculateGlobalSpeedMult() / 10 : 1 - - G.upgradeMultiplier = 1 - G.upgradeMultiplier *= 1 - + 2 * (1 - Math.pow(2, -(player.antUpgrades[11 - 1]! + G.bonusant11) / 125)) - G.upgradeMultiplier *= 1 + player.researches[103] / 20 - G.upgradeMultiplier *= 1 + player.researches[104] / 20 - if (player.achievements[132] === 1) { - G.upgradeMultiplier *= 1.25 - } - if (player.achievements[137] === 1) { - G.upgradeMultiplier *= 1.25 - } - G.upgradeMultiplier *= 1 + (20 / 3) * G.effectiveRuneBlessingPower[3] - G.upgradeMultiplier *= 1 + (1 / 50) * CalcECC('reincarnation', player.challengecompletions[10]) - G.upgradeMultiplier *= 1 + (1 / 50) * player.researches[122] - G.upgradeMultiplier *= 1 + (3 / 100) * player.researches[133] - G.upgradeMultiplier *= 1 + (2 / 100) * player.researches[163] - G.upgradeMultiplier *= 1 + (1 / 100) * player.researches[193] - G.upgradeMultiplier *= 1 + (1 / 10) * player.upgrades[79] - G.upgradeMultiplier *= 1 + (1 / 4) * player.upgrades[40] - G.upgradeMultiplier *= G.cubeBonusMultiplier[7] - G.upgradeMultiplier *= 1 + calculateEventBuff(BuffType.AntSacrifice) - G.upgradeMultiplier = Math.min(1e300, G.upgradeMultiplier) - return G.upgradeMultiplier + ELO += Decimal.log(player.antPoints, 10) + ELO += (1 / 2) * antUpgradeSum + ELO += (1 / 10) * player.firstOwnedAnts + ELO += (1 / 5) * player.secondOwnedAnts + ELO += (1 / 3) * player.thirdOwnedAnts + ELO += (1 / 2) * player.fourthOwnedAnts + ELO += player.fifthOwnedAnts + ELO += 2 * player.sixthOwnedAnts + ELO += 4 * player.seventhOwnedAnts + ELO += 8 * player.eighthOwnedAnts + ELO += 666 * player.researches[178] + ELO += +getAchievementReward('antELOAdditive') + ELO += 25 * player.researches[108] + ELO += 25 * player.researches[109] + ELO += 40 * player.researches[123] + ELO += 100 * CalcECC('reincarnation', player.challengecompletions[10]) + ELO += 75 * player.upgrades[80] + } + return ELO +} + +export const calculateEffectiveAntELO = (base?: number) => { + const baseELO = base ?? calculateBaseAntELO() + let effectiveELO = 0 + effectiveELO += 0.1 * Math.min(5000, baseELO) + effectiveELO += 0.2 * Math.min(7500, baseELO) + effectiveELO += 0.2 * Math.min(15000, baseELO) + effectiveELO += 0.2 * Math.min(50000, baseELO) + effectiveELO += 0.3 * baseELO + effectiveELO += 1 * player.cubeUpgrades[50] + effectiveELO *= 1 + 0.03 * player.upgrades[124] + effectiveELO *= calculateAntELOCubeBlessing() + effectiveELO *= +getAchievementReward('antELOMultiplicative') + effectiveELO *= 1 + player.researches[110] / 100 + effectiveELO *= 1 + (2.5 * player.researches[148]) / 100 + effectiveELO *= getTalismanEffects('mortuus').antBonus + return effectiveELO } interface IAntSacRewards { antSacrificePoints: number - offerings: number - obtainium: number - talismanShards: number - commonFragments: number - uncommonFragments: number - rareFragments: number - epicFragments: number - legendaryFragments: number - mythicalFragments: number + offerings: Decimal + obtainium: Decimal + talismanShards: Decimal + commonFragments: Decimal + uncommonFragments: Decimal + rareFragments: Decimal + epicFragments: Decimal + legendaryFragments: Decimal + mythicalFragments: Decimal } export const calculateAntSacrificeRewards = (): IAntSacRewards => { - calculateAntSacrificeELO() - calculateAntSacrificeMultipliers() + const effectiveELO = calculateEffectiveAntELO() + const sacRewardMult = calculateAntSacrificeMultiplier() + const timeMultiplier = offeringObtainiumTimeModifiers(player.antSacrificeTimer, true).reduce( + (a, b) => a * b.stat(), + 1 + ) - const halfMindModifier = player.singularityUpgrades.halfMind.getEffect().bonus - ? calculateGlobalSpeedMult() / 10 - : 1 + const rewardMult = sacRewardMult.times(timeMultiplier) - const maxCap = 1e300 - const rewardsMult = Math.min(maxCap, G.timeMultiplier * G.upgradeMultiplier * halfMindModifier) const rewards: IAntSacRewards = { - antSacrificePoints: Math.min(maxCap, (G.effectiveELO * rewardsMult) / 85), - offerings: Math.min( - maxCap, - calculateAntSacrificeOffering() - ), - obtainium: Math.min( - maxCap, - calculateAntSacrificeObtainium() - ), - talismanShards: G.antELO > 500 - ? Math.min( - maxCap, - Math.max( - 1, - Math.floor( - (rewardsMult / 210) - * Math.pow((1 / 4) * Math.max(0, G.effectiveELO - 500), 2) - ) - ) - ) - : 0, - commonFragments: G.antELO > 750 - ? Math.min( - maxCap, - Math.max( - 1, - Math.floor( - (rewardsMult / 110) - * Math.pow((1 / 9) * Math.max(0, G.effectiveELO - 750), 1.83) - ) - ) - ) - : 0, - uncommonFragments: G.antELO > 1000 - ? Math.min( - maxCap, - Math.max( - 1, - Math.floor( - (rewardsMult / 170) - * Math.pow((1 / 16) * Math.max(0, G.effectiveELO - 1000), 1.66) - ) - ) - ) - : 0, - rareFragments: G.antELO > 1500 - ? Math.min( - maxCap, - Math.max( - 1, - Math.floor( - (rewardsMult / 200) - * Math.pow((1 / 25) * Math.max(0, G.effectiveELO - 1500), 1.5) - ) - ) - ) - : 0, - epicFragments: G.antELO > 2000 - ? Math.min( - maxCap, - Math.max( - 1, - Math.floor( - (rewardsMult / 200) - * Math.pow((1 / 36) * Math.max(0, G.effectiveELO - 2000), 1.33) - ) - ) - ) - : 0, - legendaryFragments: G.antELO > 3000 - ? Math.min( - maxCap, - Math.max( - 1, - Math.floor( - (rewardsMult / 230) - * Math.pow((1 / 49) * Math.max(0, G.effectiveELO - 3000), 1.16) - ) - ) - ) - : 0, - mythicalFragments: G.antELO > 5000 - ? Math.min( - maxCap, - Math.max( - 1, - Math.floor( - (rewardsMult / 220) - * Math.pow((1 / 64) * Math.max(0, G.effectiveELO - 4150), 1) - ) - ) - ) - : 0 + antSacrificePoints: Decimal.min(rewardMult.div(1000).times(effectiveELO), 1e300).toNumber(), + offerings: calculateAntSacrificeOffering(), + obtainium: calculateAntSacrificeObtainium(), + talismanShards: effectiveELO > 500 + ? Decimal.floor(rewardMult.div(210).times(Math.pow((1 / 4) * Math.max(0, effectiveELO - 500), 2))) + : new Decimal(0), + commonFragments: effectiveELO > 750 + ? Decimal.floor(rewardMult.div(110).times(Math.pow((1 / 9) * Math.max(0, effectiveELO - 750), 11 / 6))) + : new Decimal(0), + uncommonFragments: effectiveELO > 1000 + ? Decimal.floor(rewardMult.div(170).times(Math.pow((1 / 16) * Math.max(0, effectiveELO - 1000), 10 / 6))) + : new Decimal(0), + rareFragments: effectiveELO > 1500 + ? Decimal.floor(rewardMult.div(200).times(Math.pow((1 / 25) * Math.max(0, effectiveELO - 1500), 9 / 6))) + : new Decimal(0), + epicFragments: effectiveELO > 2000 + ? Decimal.floor(rewardMult.div(250).times(Math.pow((1 / 36) * Math.max(0, effectiveELO - 2000), 8 / 6))) + : new Decimal(0), + legendaryFragments: effectiveELO > 3000 + ? Decimal.floor(rewardMult.div(230).times(Math.pow((1 / 49) * Math.max(0, effectiveELO - 3000), 7 / 6))) + : new Decimal(0), + mythicalFragments: effectiveELO > 5000 + ? Decimal.floor(rewardMult.div(220).times(Math.pow((1 / 64) * Math.max(0, effectiveELO - 5000), 1))) + : new Decimal(0) } return rewards @@ -1461,11 +845,17 @@ export const calculateOffline = (forceTime = 0, fromTips = false) => { const obtainiumGain = calculateResearchAutomaticObtainium(timeAdd) const resetAdd = { - prestige: timeAdd / Math.max(0.01, player.fastestprestige), + prestige: (player.prestigeCount > 0) ? timeAdd / Math.max(0.25, player.fastestprestige) : 0, offering: Math.floor(timeAdd), - transcension: timeAdd / Math.max(0.01, player.fastesttranscend), - reincarnation: timeAdd / Math.max(0.01, player.fastestreincarnate), - obtainium: timeAdd * obtainiumGain * G.timeMultiplier + transcension: (player.transcendCount > 0) ? timeAdd / Math.max(0.25, player.fastesttranscend) : 0, + reincarnation: (player.reincarnationCount > 0) ? timeAdd / Math.max(0.25, player.fastestreincarnate) : 0, + obtainium: obtainiumGain.times(timeAdd).times(G.timeMultiplier) + } + + const resetAddDisplay = { + prestige: player.prestigeCount, + transcension: player.transcendCount, + reincarnation: player.reincarnationCount } const timerAdd = { @@ -1490,14 +880,19 @@ export const calculateOffline = (forceTime = 0, fromTips = false) => { addTimers('ambrosia', timeAdd) addTimers('redAmbrosia', timeAdd) - player.prestigeCount += resetAdd.prestige - player.transcendCount += resetAdd.transcension - player.reincarnationCount += resetAdd.reincarnation + updatePrestigeCount(resetAdd.prestige) + updateTranscensionCount(resetAdd.transcension) + updateReincarnationCount(resetAdd.reincarnation) + timerAdd.ascension = player.ascensionCounter - timerAdd.ascension timerAdd.quarks = quarkHandler().gain - timerAdd.quarks timerAdd.ambrosia = player.lifetimeAmbrosia - timerAdd.ambrosia timerAdd.redAmbrosia = player.lifetimeRedAmbrosia - timerAdd.redAmbrosia + resetAddDisplay.prestige = player.prestigeCount - resetAddDisplay.prestige + resetAddDisplay.transcension = player.transcendCount - resetAddDisplay.transcension + resetAddDisplay.reincarnation = player.reincarnationCount - resetAddDisplay.reincarnation + // 200 simulated all ticks [July 12, 2021] const runOffline = setInterval(() => { G.timeMultiplier = calculateGlobalSpeedMult() @@ -1517,7 +912,7 @@ export const calculateOffline = (forceTime = 0, fromTips = false) => { } // Auto Ant Sacrifice Stuff - if (player.achievements[173] > 0) { + if (getAchievementReward('antSacrificeUnlock')) { automaticTools('antSacrifice', timeTick) } @@ -1544,7 +939,7 @@ export const calculateOffline = (forceTime = 0, fromTips = false) => { DOMCacheGetOrSet('offlinePrestigeCount').innerHTML = i18next.t( 'offlineProgress.prestigeCount', { - value: format(resetAdd.prestige, 0, true) + value: format(resetAddDisplay.prestige, 0, true) } ) DOMCacheGetOrSet('offlinePrestigeTimer').innerHTML = i18next.t( @@ -1562,7 +957,7 @@ export const calculateOffline = (forceTime = 0, fromTips = false) => { DOMCacheGetOrSet('offlineTranscensionCount').innerHTML = i18next.t( 'offlineProgress.transcensionCount', { - value: format(resetAdd.transcension, 0, true) + value: format(resetAddDisplay.transcension, 0, true) } ) DOMCacheGetOrSet('offlineTranscensionTimer').innerHTML = i18next.t( @@ -1574,7 +969,7 @@ export const calculateOffline = (forceTime = 0, fromTips = false) => { DOMCacheGetOrSet('offlineReincarnationCount').innerHTML = i18next.t( 'offlineProgress.reincarnationCount', { - value: format(resetAdd.reincarnation, 0, true) + value: format(resetAddDisplay.reincarnation, 0, true) } ) DOMCacheGetOrSet('offlineReincarnationTimer').innerHTML = i18next.t( @@ -1650,7 +1045,6 @@ export const calculateOffline = (forceTime = 0, fromTips = false) => { updateTalismanInventory() calculateObtainium() calculateAnts() - calculateRuneLevels() // allow aesthetic offline progress if (offlineDialog) { @@ -1690,61 +1084,6 @@ export const calculateSigmoidExponential = ( return 1 + (constant - 1) * (1 - Math.exp(-coefficient)) } -export const calculateCubeBlessings = () => { - // The visual updates are handled in visualUpdateCubes() - const cubeArray = [ - player.cubeBlessings.accelerator, - player.cubeBlessings.multiplier, - player.cubeBlessings.offering, - player.cubeBlessings.runeExp, - player.cubeBlessings.obtainium, - player.cubeBlessings.antSpeed, - player.cubeBlessings.antSacrifice, - player.cubeBlessings.antELO, - player.cubeBlessings.talismanBonus, - player.cubeBlessings.globalSpeed - ] - const powerBonus = [ - player.cubeUpgrades[45] / 100, - player.cubeUpgrades[35] / 100, - player.cubeUpgrades[24] / 100, - player.cubeUpgrades[14] / 100, - player.cubeUpgrades[40] / 100, - player.cubeUpgrades[22] / 40, - player.cubeUpgrades[15] / 100, - player.cubeUpgrades[25] / 100, - player.cubeUpgrades[44] / 100, - player.cubeUpgrades[34] / 100 - ] - - for (let i = 1; i <= 10; i++) { - let power = 1 - let mult = 1 - if (cubeArray[i - 1] >= 1000) { - power = G.blessingDRPower[i]! - mult *= Math.pow( - 1000, - (1 - G.blessingDRPower[i]!) * (1 + powerBonus[i - 1]) - ) - } - if (i === 6) { - power = 2.25 - mult = 1 - } - - G.cubeBonusMultiplier[i] = Math.min( - 1e300, - 1 - + mult - * G.blessingbase[i]! - * Math.pow(cubeArray[i - 1], power * (1 + powerBonus[i - 1])) - * G.tesseractBonusMultiplier[i]! - ) - } - calculateRuneLevels() - calculateAntSacrificeELO() -} - export const calculateTotalOcteractCubeBonus = () => { if (player.singularityChallenges.noOcteracts.enabled) { return 1 @@ -1812,10 +1151,6 @@ export const calculateSingularityQuarkMilestoneMultiplier = () => { } } - if (player.highestSingularityCount >= 200) { - multiplier *= Math.pow((player.highestSingularityCount - 179) / 20, 2) - } - return multiplier } @@ -1839,6 +1174,27 @@ export const calculateSummationLinear = ( return [buyToLevel, realCost] } +// If you want to sum from a baseline level i to the maximum buyable level n, what would the cost be and how many levels would you get? +export const calculateSummationLinearDecimal = ( + baseLevel: Decimal, + baseCost: Decimal, + resourceAvailable: Decimal, + differenceCap = new Decimal(1e9) +): [Decimal, Decimal] => { + const subtractCost = baseCost.times(baseLevel).times(baseLevel.plus(1)).div(2) + const buyToLevel = Decimal.min( + baseLevel.plus(differenceCap), + Decimal.floor( + Decimal.sqrt((resourceAvailable.plus(subtractCost)).times(2).div(baseCost).plus(1 / 4)).minus(1 / 2) + ) + ) + + const realCost = baseCost.times(buyToLevel).times(buyToLevel.plus(1)).div(2).minus(subtractCost) + // const realCost = (baseCost * buyToLevel * (1 + buyToLevel)) / 2 - subtractCost + + return [buyToLevel, realCost] +} + // If you want to sum from a baseline level baseLevel to some level where the cost per level is base * (1 + level * diffPerLevel), this finds out how many total levels you can buy. export const calculateSummationNonLinear = ( baseLevel: number, @@ -1981,8 +1337,9 @@ export const calculateCubicSumData = ( export const computeAscensionScoreBonusMultiplier = () => { let multiplier = 1 multiplier *= G.challenge15Rewards.score.value - multiplier *= G.platonicBonusMultiplier[6] + multiplier *= calculateAscensionScorePlatonicBlessing() multiplier *= player.campaigns.ascensionScoreMultiplier + multiplier *= getRuneEffects('finiteDescent').ascensionScore if (player.cubeUpgrades[21] > 0) { multiplier *= 1 + 0.05 * player.cubeUpgrades[21] } @@ -1992,16 +1349,7 @@ export const computeAscensionScoreBonusMultiplier = () => { if (player.cubeUpgrades[41] > 0) { multiplier *= 1 + 0.05 * player.cubeUpgrades[41] } - if (player.achievements[267] > 0) { - multiplier *= 1 - + Math.min(1, (1 / 100000) * Decimal.log(player.ascendShards.add(1), 10)) - } - if (player.achievements[259] > 0) { - multiplier *= Math.max( - 1, - Math.pow(1.01, Math.log2(player.hepteractCrafts.abyss.CAP)) - ) - } + multiplier *= +getAchievementReward('ascensionScore') if (G.isEvent) { multiplier *= 1 + calculateEventBuff(BuffType.AscensionScore) } @@ -2014,10 +1362,7 @@ export const calculateAscensionScore = () => { const corruptionMultiplier = player.corruptions.used.totalCorruptionAscensionMultiplier let effectiveScore = 0 - let bonusLevel = player.singularityUpgrades.corruptionFifteen.getEffect() - .bonus - ? 1 - : 0 + let bonusLevel = getGQUpgradeEffect('corruptionFifteen') bonusLevel += +player.singularityChallenges.oneChallengeCap.rewards.freeCorruptionLevel // Init Arrays with challenge values :) @@ -2092,7 +1437,7 @@ export const calculateAscensionScore = () => { player.highestchallengecompletions[10] ) // Corruption Multiplier is the product of all Corruption Score multipliers based on used corruptions - let bonusVal = player.singularityUpgrades.advancedPack.getEffect().bonus + let bonusVal = getGQUpgradeEffect('advancedPack') ? 0.33 : 0 bonusVal += +player.singularityChallenges.oneChallengeCap.rewards.corrScoreIncrease @@ -2105,7 +1450,7 @@ export const calculateAscensionScore = () => { effectiveScore = Math.pow(effectiveScore, 0.5) * Math.pow(1e23, 0.5) } - player.singularityUpgrades.expertPack.getEffect().bonus + getGQUpgradeEffect('expertPack') ? (effectiveScore *= 1.5) : (effectiveScore *= 1) @@ -2132,7 +1477,7 @@ export const CalcCorruptionStuff = () => { cubeBank += challengeModifier * player.highestchallengecompletions[i] } - const oneMindModifier = player.singularityUpgrades.oneMind.getEffect().bonus + const oneMindModifier = getGQUpgradeEffect('oneMind') ? calculateAscensionSpeedMult() / 10 : 1 @@ -2141,8 +1486,7 @@ export const CalcCorruptionStuff = () => { cubeGain *= calculateCubeMultiplier() cubeGain *= oneMindModifier - const bonusCubeExponent = player.singularityUpgrades.platonicTau.getEffect() - .bonus + const bonusCubeExponent = getGQUpgradeEffect('platonicTau') ? 1.01 : 1 cubeGain = Math.pow(cubeGain, bonusCubeExponent) @@ -2168,7 +1512,6 @@ export const CalcCorruptionStuff = () => { // Calculation of Hepteracts :))))) let hepteractGain = G.challenge15Rewards.hepteractsUnlocked.value && effectiveScore >= 1.666e17 - && player.achievements[255] > 0 ? 1 : 0 hepteractGain *= calculateHepteractMultiplier() @@ -2194,48 +1537,30 @@ export const CalcCorruptionStuff = () => { export const calcAscensionCount = () => { let ascCount = 1 + if (player.challengecompletions[10] === 0) { + return 0 + } + if (player.singularityChallenges.limitedAscensions.enabled) { return ascCount } - if (player.challengecompletions[10] > 0 && player.achievements[197] === 1) { - const { effectiveScore } = calculateAscensionScore() - - if (player.ascensionCounter >= resetTimeThreshold()) { - if (player.achievements[188] > 0) { - ascCount += 99 - } - - ascCount *= 1 - + (player.ascensionCounter / resetTimeThreshold() - 1) - * 0.2 - * (player.achievements[189] - + player.achievements[202] - + player.achievements[209] - + player.achievements[216] - + player.achievements[223]) - } - - ascCount *= player.achievements[187] && Math.floor(effectiveScore) > 1e8 - ? Math.log10(Math.floor(effectiveScore) + 1) - 1 - : 1 - ascCount *= G.challenge15Rewards.ascensions.value - ascCount *= player.achievements[260] > 0 ? 1.1 : 1 - ascCount *= player.achievements[261] > 0 ? 1.1 : 1 - ascCount *= player.platonicUpgrades[15] > 0 ? 2 : 1 - ascCount *= 1 + 0.02 * player.platonicUpgrades[16] - ascCount *= 1 - + 0.02 - * player.platonicUpgrades[16] - * Math.min(1, player.overfluxPowder / 100000) - ascCount *= 1 + player.singularityCount / 10 - ascCount *= +player.singularityUpgrades.ascensions.getEffect().bonus - ascCount *= +player.octeractUpgrades.octeractAscensions.getEffect().bonus - ascCount *= +player.octeractUpgrades.octeractAscensions2.getEffect().bonus - ascCount *= player.singularityUpgrades.oneMind.getEffect().bonus - ? calculateAscensionSpeedMult() / 10 - : 1 - } + ascCount += +getAchievementReward('ascensionCountAdditive') + ascCount *= +getAchievementReward('ascensionCountMultiplier') + ascCount *= G.challenge15Rewards.ascensions.value + ascCount *= player.platonicUpgrades[15] > 0 ? 2 : 1 + ascCount *= 1 + 0.02 * player.platonicUpgrades[16] + ascCount *= 1 + + 0.02 + * player.platonicUpgrades[16] + * Math.min(1, player.overfluxPowder / 100000) + ascCount *= 1 + player.singularityCount / 10 + ascCount *= getGQUpgradeEffect('ascensions') + ascCount *= getOcteractUpgradeEffect('octeractAscensions') + ascCount *= getOcteractUpgradeEffect('octeractAscensions2') + ascCount *= getGQUpgradeEffect('oneMind') + ? calculateAscensionSpeedMult() / 10 + : 1 return Math.floor(ascCount) } @@ -2338,69 +1663,53 @@ export const calculateSingularityAmbrosiaLuckMilestoneBonus = () => { } export const calculateAmbrosiaGenerationShopUpgrade = () => { - const multipliers = [ - 1 + player.shopUpgrades.shopAmbrosiaGeneration1 / 100, - 1 + player.shopUpgrades.shopAmbrosiaGeneration2 / 100, - 1 + player.shopUpgrades.shopAmbrosiaGeneration3 / 100, - 1 + player.shopUpgrades.shopAmbrosiaGeneration4 / 1000 - ] - - return productContents(multipliers) + return ( + (1 + player.shopUpgrades.shopAmbrosiaGeneration1 / 100) + * (1 + player.shopUpgrades.shopAmbrosiaGeneration2 / 100) + * (1 + player.shopUpgrades.shopAmbrosiaGeneration3 / 100) + * (1 + player.shopUpgrades.shopAmbrosiaGeneration4 / 1000) + ) } export const calculateAmbrosiaLuckShopUpgrade = () => { - const vals = [ - 2 * player.shopUpgrades.shopAmbrosiaLuck1, - 2 * player.shopUpgrades.shopAmbrosiaLuck2, - 2 * player.shopUpgrades.shopAmbrosiaLuck3, - 0.6 * player.shopUpgrades.shopAmbrosiaLuck4 - ] - - return sumContents(vals) + return ( + 2 * player.shopUpgrades.shopAmbrosiaLuck1 + + 2 * player.shopUpgrades.shopAmbrosiaLuck2 + + 2 * player.shopUpgrades.shopAmbrosiaLuck3 + + 0.6 * player.shopUpgrades.shopAmbrosiaLuck4 + ) } export const calculateAmbrosiaGenerationSingularityUpgrade = () => { - const vals = [ - +player.singularityUpgrades.singAmbrosiaGeneration.getEffect().bonus, - +player.singularityUpgrades.singAmbrosiaGeneration2.getEffect().bonus, - +player.singularityUpgrades.singAmbrosiaGeneration3.getEffect().bonus, - +player.singularityUpgrades.singAmbrosiaGeneration4.getEffect().bonus - ] - - return productContents(vals) + return getGQUpgradeEffect('singAmbrosiaGeneration') + * getGQUpgradeEffect('singAmbrosiaGeneration2') + * getGQUpgradeEffect('singAmbrosiaGeneration3') + * getGQUpgradeEffect('singAmbrosiaGeneration4') } export const calculateAmbrosiaLuckSingularityUpgrade = () => { - const vals = [ - +player.singularityUpgrades.singAmbrosiaLuck.getEffect().bonus, - +player.singularityUpgrades.singAmbrosiaLuck2.getEffect().bonus, - +player.singularityUpgrades.singAmbrosiaLuck3.getEffect().bonus, - +player.singularityUpgrades.singAmbrosiaLuck4.getEffect().bonus - ] - - return sumContents(vals) + return getGQUpgradeEffect('singAmbrosiaLuck') + + getGQUpgradeEffect('singAmbrosiaLuck2') + + getGQUpgradeEffect('singAmbrosiaLuck3') + + getGQUpgradeEffect('singAmbrosiaLuck4') } export const calculateAmbrosiaGenerationOcteractUpgrade = () => { - const vals = [ - +player.octeractUpgrades.octeractAmbrosiaGeneration.getEffect().bonus, - +player.octeractUpgrades.octeractAmbrosiaGeneration2.getEffect().bonus, - +player.octeractUpgrades.octeractAmbrosiaGeneration3.getEffect().bonus, - +player.octeractUpgrades.octeractAmbrosiaGeneration4.getEffect().bonus - ] - - return productContents(vals) + return ( + getOcteractUpgradeEffect('octeractAmbrosiaGeneration') + * getOcteractUpgradeEffect('octeractAmbrosiaGeneration2') + * getOcteractUpgradeEffect('octeractAmbrosiaGeneration3') + * getOcteractUpgradeEffect('octeractAmbrosiaGeneration4') + ) } export const calculateAmbrosiaLuckOcteractUpgrade = () => { - const vals = [ - +player.octeractUpgrades.octeractAmbrosiaLuck.getEffect().bonus, - +player.octeractUpgrades.octeractAmbrosiaLuck2.getEffect().bonus, - +player.octeractUpgrades.octeractAmbrosiaLuck3.getEffect().bonus, - +player.octeractUpgrades.octeractAmbrosiaLuck4.getEffect().bonus - ] - - return sumContents(vals) + return ( + getOcteractUpgradeEffect('octeractAmbrosiaLuck') + + getOcteractUpgradeEffect('octeractAmbrosiaLuck2') + + getOcteractUpgradeEffect('octeractAmbrosiaLuck3') + + getOcteractUpgradeEffect('octeractAmbrosiaLuck4') + ) } const digitReduction = 4 @@ -2435,6 +1744,12 @@ export const calculateRequiredBlueberryTime = () => { let val = G.TIME_PER_AMBROSIA // Currently 30 val += Math.floor(player.lifetimeAmbrosia / 500) + const exalt5Comps = player.singularityChallenges.noAmbrosiaUpgrades.completions + const acceleratorMult = 1 - 0.004 * exalt5Comps * player.shopUpgrades.shopAmbrosiaAccelerator + + val *= acceleratorMult + val = Math.ceil(val) + const thresholds = calculateNumberOfThresholds() const thresholdBase = 2 return Math.pow(thresholdBase, thresholds) * val @@ -2638,7 +1953,7 @@ export const isIARuneUnlocked = () => { } export const isShopTalismanUnlocked = () => { - return player.shopUpgrades.shopTalisman > 0 || PCoinUpgradeEffects.INSTANT_UNLOCK_1 + return Boolean(player.shopUpgrades.shopTalisman > 0 || PCoinUpgradeEffects.INSTANT_UNLOCK_1 > 0) } export const sing6Mult = () => { @@ -2741,13 +2056,9 @@ export const calculateObtainiumPotionBaseObtainium = () => { } export const calculateAscensionSpeedExponentSpread = () => { - const vals = [ - player.singularityUpgrades.singAscensionSpeed.getEffect().bonus ? 0.03 : 0, - +player.singularityUpgrades.singAscensionSpeed2.getEffect().bonus, - 0.001 * Math.floor((player.shopUpgrades.chronometerInfinity + calculateFreeShopInfinityUpgrades()) / 40) - ] - - return sumContents(vals) + return getGQUpgradeEffect('singAscensionSpeed') + + getGQUpgradeEffect('singAscensionSpeed2') + + 0.001 * Math.floor((player.shopUpgrades.chronometerInfinity + calculateFreeShopInfinityUpgrades()) / 40) } export const calculateCookieUpgrade29Luck = () => { @@ -2759,8 +2070,8 @@ export const calculateCookieUpgrade29Luck = () => { } export const calculateRedAmbrosiaCubes = () => { - if (getRedAmbrosiaUpgrade('redAmbrosiaCube').bonus.unlockedRedAmbrosiaCube) { - const exponent = 0.4 + getRedAmbrosiaUpgrade('redAmbrosiaCubeImprover').bonus.extraExponent + if (getRedAmbrosiaUpgradeEffects('redAmbrosiaCube').unlockedRedAmbrosiaCube) { + const exponent = 0.4 + getRedAmbrosiaUpgradeEffects('redAmbrosiaCubeImprover').extraExponent return 1 + Math.pow(player.lifetimeRedAmbrosia, exponent) / 100 } else { return 1 @@ -2768,7 +2079,7 @@ export const calculateRedAmbrosiaCubes = () => { } export const calculateRedAmbrosiaObtainium = () => { - if (getRedAmbrosiaUpgrade('redAmbrosiaObtainium').bonus.unlockRedAmbrosiaObtainium) { + if (getRedAmbrosiaUpgradeEffects('redAmbrosiaObtainium').unlockRedAmbrosiaObtainium) { return 1 + Math.pow(player.lifetimeRedAmbrosia, 0.6) / 100 } else { return 1 @@ -2776,7 +2087,7 @@ export const calculateRedAmbrosiaObtainium = () => { } export const calculateRedAmbrosiaOffering = () => { - if (getRedAmbrosiaUpgrade('redAmbrosiaOffering').bonus.unlockRedAmbrosiaOffering) { + if (getRedAmbrosiaUpgradeEffects('redAmbrosiaOffering').unlockRedAmbrosiaOffering) { return 1 + Math.pow(player.lifetimeRedAmbrosia, 0.6) / 100 } else { return 1 diff --git a/src/Campaign.ts b/src/Campaign.ts index aa362aa9e..66eccf2b3 100644 --- a/src/Campaign.ts +++ b/src/Campaign.ts @@ -1,4 +1,5 @@ import i18next from 'i18next' +import { awardAchievementGroup } from './Achievements' import { DOMCacheGetOrSet } from './Cache/DOM' import { inheritanceTokens, isIARuneUnlocked, singularityBonusTokenMult } from './Calculate' import { @@ -9,11 +10,16 @@ import { corruptionStatsUpdate, maxCorruptionLevel } from './Corruptions' +import { getOcteractUpgradeEffect } from './Octeracts' import { reset } from './Reset' -import { format, player } from './Synergism' +import { getGQUpgradeEffect } from './singularity' +import { format, formatAsPercentIncrease, player } from './Synergism' import { IconSets } from './Themes' import { Alert, Confirm, Notification } from './UpdateHTML' +export let campaignTokens = 0 +export let maxCampaignTokens = 0 + export type CampaignKeys = | 'first' | 'second' @@ -110,10 +116,6 @@ export interface ICampaignData { export class CampaignManager { #currentCampaign: CampaignKeys | undefined #campaigns: Record - #tokens = 0 - #maxTokens = 0 - #updatedTokens = false - #updatedMaxTokens = false constructor (campaignManagerData?: ICampaignManagerData) { this.#campaigns = { @@ -269,58 +271,19 @@ export class CampaignManager { } } - computeTotalCampaignTokens () { - this.#updatedTokens = false - let sum = 0 - for (const campaign of Object.values(this.#campaigns)) { - sum += campaign.tokens - } - - sum += inheritanceTokens() - sum += +player.singularityUpgrades.singBonusTokens4.getEffect().bonus - sum += +player.octeractUpgrades.octeractBonusTokens4.getEffect().bonus - return sum - } - computeMaxCampaignTokens () { - this.#updatedMaxTokens = false let sum = 0 for (const campaign of Object.values(this.#campaigns)) { sum += campaign.maxTokens } sum += inheritanceTokens() - sum += +player.singularityUpgrades.singBonusTokens4.getEffect().bonus - sum += +player.octeractUpgrades.octeractBonusTokens4.getEffect().bonus + sum += getGQUpgradeEffect('singBonusTokens4') + sum += getOcteractUpgradeEffect('octeractBonusTokens4') return sum } - get tokens () { - if (!this.#updatedTokens) { - this.#tokens = this.computeTotalCampaignTokens() - this.#updatedTokens = true - return this.#tokens - } else { - return this.#tokens - } - } - - get maxTokens () { - if (!this.#updatedMaxTokens) { - this.#maxTokens = this.computeMaxCampaignTokens() - this.#updatedMaxTokens = true - return this.#maxTokens - } else { - return this.#maxTokens - } - } - - updateCurrentTokens () { - this.#tokens = this.computeTotalCampaignTokens() - this.#maxTokens = this.computeMaxCampaignTokens() - } - get current () { return this.#currentCampaign } @@ -383,7 +346,8 @@ export class CampaignManager { this.#currentCampaign = undefined // Update Token Count for player - this.computeTotalCampaignTokens() + updateTokens() + updateMaxTokens() campaignTokenRewardHTMLUpdate() // Update Campaign Active Text @@ -399,38 +363,38 @@ export class CampaignManager { get tutorialBonus (): TutorialBonus { return { - cubeBonus: 1 + 0.1 * +(this.tokens > 0), - obtainiumBonus: 1 + 0.25 * +(this.tokens > 0), - offeringBonus: 1 + 0.25 * +(this.tokens > 0) + cubeBonus: 1 + 0.05 * +(campaignTokens > 0), + obtainiumBonus: 1 + 0.1 * +(campaignTokens > 0), + offeringBonus: 1 + 0.1 * +(campaignTokens > 0) } } get cubeBonus () { return 1 - + 0.25 * 1 / 25 * Math.min(this.tokens, 25) - + 0.75 * (1 - Math.exp(-Math.max(this.tokens - 25, 0) / 500)) - + 0.5 * (1 - Math.exp(-Math.max(this.tokens - 2500, 0) / 5000)) + + 0.1 * 1 / 25 * Math.min(campaignTokens, 25) + + 0.4 * (1 - Math.exp(-Math.max(campaignTokens - 25, 0) / 500)) + + 0.5 * (1 - Math.exp(-Math.max(campaignTokens - 2500, 0) / 5000)) } get obtainiumBonus () { return 1 - + 0.25 * 1 / 25 * Math.min(this.tokens, 25) - + 0.75 * (1 - Math.exp(-Math.max(this.tokens - 25, 0) / 500)) - + 0.5 * (1 - Math.exp(-Math.max(this.tokens - 2500, 0) / 5000)) + + 0.1 * 1 / 25 * Math.min(campaignTokens, 25) + + 0.4 * (1 - Math.exp(-Math.max(campaignTokens - 25, 0) / 500)) + + 0.5 * (1 - Math.exp(-Math.max(campaignTokens - 2500, 0) / 5000)) } get offeringBonus () { return 1 - + 0.25 * 1 / 25 * Math.min(this.tokens, 25) - + 0.75 * (1 - Math.exp(-Math.max(this.tokens - 25, 0) / 500)) - + 0.5 * (1 - Math.exp(-Math.max(this.tokens - 2500, 0) / 5000)) + + 0.1 * 1 / 25 * Math.min(campaignTokens, 25) + + 0.4 * (1 - Math.exp(-Math.max(campaignTokens - 25, 0) / 500)) + + 0.5 * (1 - Math.exp(-Math.max(campaignTokens - 2500, 0) / 5000)) } get ascensionScoreMultiplier () { return 1 - + 0.5 * 1 / 100 * Math.min(this.tokens, 100) - + 0.5 * (1 - Math.exp(-Math.max(this.tokens - 100, 0) / 1000)) - + 0.5 * (1 - Math.exp(-Math.max(this.tokens - 2500, 0) / 5000)) + + 0.2 * 1 / 100 * Math.min(campaignTokens, 100) + + 0.3 * (1 - Math.exp(-Math.max(campaignTokens - 100, 0) / 1000)) + + 0.5 * (1 - Math.exp(-Math.max(campaignTokens - 2500, 0) / 5000)) } /** * Returns the time threshold reduction for Prestige, Reincarnation and Ascension @@ -441,7 +405,7 @@ export class CampaignManager { get timeThresholdReduction () { const thresholdReqs = [20, 100, 250, 500, 1000, 2000, 3500, 5000] for (let i = 0; i < thresholdReqs.length; i++) { - if (this.tokens < thresholdReqs[i]) { + if (campaignTokens < thresholdReqs[i]) { return i / 4 } } @@ -449,80 +413,107 @@ export class CampaignManager { } get quarkBonus () { - if (this.tokens < 100) { + if (campaignTokens < 100) { return 1 } else { return 1 - + 0.1 * Math.min(this.tokens - 100, 100) / 100 - + 0.15 * (1 - Math.exp(-Math.max(this.tokens - 200, 0) / 3000)) - + 0.15 * (1 - Math.exp(-Math.max(this.tokens - 2500, 0) / 10000)) + + 0.05 * Math.min(campaignTokens - 100, 100) / 100 + + 0.05 * (1 - Math.exp(-Math.max(campaignTokens - 200, 0) / 3000)) + + 0.1 * (1 - Math.exp(-Math.max(campaignTokens - 2500, 0) / 10000)) } } get taxMultiplier () { - if (this.tokens < 250) { + if (campaignTokens < 250) { return 1 } return 1 - - 0.05 * 1 / 250 * Math.min(this.tokens - 250, 250) - - 0.15 * (1 - Math.exp(-Math.max(this.tokens - 500, 0) / 1250)) - - 0.05 * (1 - Math.exp(-Math.max(this.tokens - 4000, 0) / 5000)) + - 0.05 * 1 / 250 * Math.min(campaignTokens - 250, 250) + - 0.15 * (1 - Math.exp(-Math.max(campaignTokens - 500, 0) / 1250)) + - 0.05 * (1 - Math.exp(-Math.max(campaignTokens - 4000, 0) / 5000)) } get c15Bonus () { - if (this.tokens < 250) { + if (campaignTokens < 250) { return 1 } return 1 - + 0.1 * 1 / 250 * Math.min(this.tokens - 250, 250) - + 0.9 * (1 - Math.exp(-Math.max(this.tokens - 500, 0) / 1250)) + + 0.05 * 1 / 250 * Math.min(campaignTokens - 250, 250) + + 0.95 * (1 - Math.exp(-Math.max(campaignTokens - 500, 0) / 1250)) } get bonusRune6 () { - const thresholdReqs = [500, 2000, 5000] + const thresholdReqs = [500, 2000, 5000, 1000] for (let i = 0; i < thresholdReqs.length; i++) { - if (this.tokens < thresholdReqs[i]) { + if (campaignTokens < thresholdReqs[i]) { return i } } - return 3 + return 4 } get goldenQuarkBonus () { - if (this.tokens < 500) { + if (campaignTokens < 500) { return 1 } return 1 - + 0.1 * 1 / 500 * Math.min(this.tokens - 500, 500) - + 0.15 * (1 - Math.exp(-Math.max(this.tokens - 1000, 0) / 2500)) + + 0.05 * 1 / 500 * Math.min(campaignTokens - 500, 500) + + 0.05 * (1 - Math.exp(-Math.max(campaignTokens - 1000, 0) / 2500)) } get octeractBonus () { - if (this.tokens < 1000) { + if (campaignTokens < 1000) { return 1 } return 1 - + 0.1 * 1 / 1000 * Math.min(this.tokens - 1000, 1000) - + 0.4 * (1 - Math.exp(-Math.max(this.tokens - 2000, 0) / 4000)) + + 0.1 * 1 / 1000 * Math.min(campaignTokens - 1000, 1000) + + 0.15 * (1 - Math.exp(-Math.max(campaignTokens - 2000, 0) / 4000)) } get ambrosiaLuckBonus () { - if (this.tokens < 2000) { + if (campaignTokens < 2000) { return 0 } return 10 - + 40 * 1 / 2000 * Math.min(this.tokens - 2000, 2000) - + 50 * (1 - Math.exp(-Math.max(this.tokens - 4000, 0) / 2500)) + + 40 * 1 / 2000 * Math.min(campaignTokens - 2000, 2000) + + 50 * (1 - Math.exp(-Math.max(campaignTokens - 4000, 0) / 2500)) } get blueberrySpeedBonus () { - if (this.tokens < 2000) { + if (campaignTokens < 2000) { return 1 } return 1 - + 0.05 * 1 / 2000 * Math.min(this.tokens - 2000, 2000) - + 0.05 * (1 - Math.exp(-Math.max(this.tokens - 4000, 0) / 2000)) + + 0.02 * 1 / 2000 * Math.min(campaignTokens - 2000, 2000) + + 0.03 * (1 - Math.exp(-Math.max(campaignTokens - 4000, 0) / 2000)) + } +} + +export const updateTokens = () => { + let sum = 0 + for (const campaign of Object.values(player.campaigns.allCampaigns)) { + sum += campaign.tokens + } + + sum += inheritanceTokens() + sum += getGQUpgradeEffect('singBonusTokens4') + sum += getOcteractUpgradeEffect('octeractBonusTokens4') + campaignTokens = sum + + awardAchievementGroup('campaignTokens') +} + +export const updateMaxTokens = () => { + let sum = 0 + for (const campaign of Object.values(player.campaigns.allCampaigns)) { + sum += campaign.maxTokens } + + sum += inheritanceTokens() + sum += getGQUpgradeEffect('singBonusTokens4') + sum += getOcteractUpgradeEffect('octeractBonusTokens4') + + maxCampaignTokens = sum } export class Campaign { @@ -557,24 +548,24 @@ export class Campaign { if (player.highestSingularityCount >= 16) { additiveTotal += 5 } - additiveTotal += +player.singularityUpgrades.singBonusTokens1.getEffect().bonus - additiveTotal += +player.octeractUpgrades.octeractBonusTokens3.getEffect().bonus + additiveTotal += getGQUpgradeEffect('singBonusTokens1') + additiveTotal += getOcteractUpgradeEffect('octeractBonusTokens3') } if (completed === this.#limit) { if (player.highestSingularityCount >= 69) { additiveTotal += 10 } - additiveTotal += +player.singularityUpgrades.singBonusTokens3.getEffect().bonus - additiveTotal += +player.octeractUpgrades.octeractBonusTokens1.getEffect().bonus + additiveTotal += getGQUpgradeEffect('singBonusTokens3') + additiveTotal += getOcteractUpgradeEffect('octeractBonusTokens1') } let multiplier = 1 multiplier *= this.#isMeta ? 2 : 1 multiplier *= singularityBonusTokenMult() - multiplier *= +player.singularityUpgrades.singBonusTokens2.getEffect().bonus - multiplier *= +player.octeractUpgrades.octeractBonusTokens2.getEffect().bonus + multiplier *= getGQUpgradeEffect('singBonusTokens2') + multiplier *= getOcteractUpgradeEffect('octeractBonusTokens2') return Math.floor(additiveTotal * multiplier) } @@ -1417,10 +1408,6 @@ export const campaignDatas: Record = { } } -export const formatAsPercentIncrease = (n: number, accuracy = 2) => { - return `${format((n - 1) * 100, accuracy, true)}%` -} - // For icons, display them only if the player has enough tokens and fits the other requirements // This is more of a display thing, the actual reward is computed in the CampaignManager export const campaignTokenRewardDatas: Record = { @@ -1672,16 +1659,14 @@ export const campaignTokenRewardHTMLUpdate = () => { DOMCacheGetOrSet('campaignTokenRewardText').textContent = '' DOMCacheGetOrSet('campaignTokenCount').textContent = i18next.t('campaigns.tokens.count', { - count: player.campaigns.tokens, - maxCount: player.campaigns.maxTokens + count: campaignTokens, + maxCount: maxCampaignTokens }) - const tokenCount = player.campaigns.tokens - for (const [key, value] of Object.entries(campaignTokenRewardDatas)) { // Create a new Icon if the player has enough tokens and extra requirements are met if ( - tokenCount >= value.tokenRequirement + campaignTokens >= value.tokenRequirement && (value.otherUnlockRequirement === undefined || value.otherUnlockRequirement()) ) { const tokenIcon = document.createElement('img') @@ -1709,14 +1694,14 @@ export const campaignTokenRewardHTMLUpdate = () => { } // Create the final icon that displays the total sum of rewards in a popup. - if (tokenCount > 0) { + if (campaignTokens > 0) { const totalRewardIcon = document.createElement('img') totalRewardIcon.src = 'Pictures/Campaigns/sum.png' let popupText = '' for (const [key, value] of Object.entries(campaignTokenRewardDatas)) { if ( - tokenCount >= value.tokenRequirement + campaignTokens >= value.tokenRequirement && (value.otherUnlockRequirement === undefined || value.otherUnlockRequirement()) ) { if (typeof value.reward() === 'string') { diff --git a/src/Challenges.ts b/src/Challenges.ts index c0b066aad..824d78031 100644 --- a/src/Challenges.ts +++ b/src/Challenges.ts @@ -1,12 +1,10 @@ import Decimal from 'break_infinity.js' import i18next from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' -import { calculateRuneLevels } from './Calculate' import { hepteractEffective } from './Hepteracts' -import { autoResearchEnabled } from './Research' +import { getGQUpgradeEffect } from './singularity' import { format, player, resetCheck } from './Synergism' import { toggleAutoChallengeModeText, toggleChallenges } from './Toggles' -import { productContents } from './Utility' import { Globals as G } from './Variables' export type Challenge15Rewards = @@ -45,6 +43,7 @@ export type Challenge15Rewards = | 'multiplierHepteractUnlocked' | 'freeOrbs' | 'ascensionSpeed' + | 'achievementUnlock' export type Challenge15RewardsInformation = { value: number @@ -96,9 +95,9 @@ export const getMaxChallenges = (i: number) => { maxChallenge += 30 } - maxChallenge += 2 * +player.singularityUpgrades.singChallengeExtension.getEffect().bonus - maxChallenge += 2 * +player.singularityUpgrades.singChallengeExtension2.getEffect().bonus - maxChallenge += 2 * +player.singularityUpgrades.singChallengeExtension3.getEffect().bonus + maxChallenge += 2 * getGQUpgradeEffect('singChallengeExtension') + maxChallenge += 2 * getGQUpgradeEffect('singChallengeExtension2') + maxChallenge += 2 * getGQUpgradeEffect('singChallengeExtension3') maxChallenge += +player.singularityChallenges.oneChallengeCap.rewards.capIncrease maxChallenge += +player.singularityChallenges.oneChallengeCap.rewards.reinCapIncrease2 @@ -128,9 +127,9 @@ export const getMaxChallenges = (i: number) => { maxChallenge += 20 } - maxChallenge += +player.singularityUpgrades.singChallengeExtension.getEffect().bonus - maxChallenge += +player.singularityUpgrades.singChallengeExtension2.getEffect().bonus - maxChallenge += +player.singularityUpgrades.singChallengeExtension3.getEffect().bonus + maxChallenge += getGQUpgradeEffect('singChallengeExtension') + maxChallenge += getGQUpgradeEffect('singChallengeExtension2') + maxChallenge += getGQUpgradeEffect('singChallengeExtension3') maxChallenge += +player.singularityChallenges.oneChallengeCap.rewards.ascCapIncrease2 return maxChallenge } @@ -208,7 +207,8 @@ export const challengeDisplay = (i: number, changefocus = true) => { switch (i) { case 1: { - current1 = current2 = format(10 * CalcECC('transcend', player.challengecompletions[1])) + current1 = format(2 * CalcECC('transcend', player.challengecompletions[1])) + current2 = format(0.75 * CalcECC('transcend', player.challengecompletions[1]), 2, true) current3 = format(0.04 * CalcECC('transcend', player.challengecompletions[1]), 2, true) break } @@ -232,29 +232,31 @@ export const challengeDisplay = (i: number, changefocus = true) => { case 5: { current1 = format(0.5 + CalcECC('transcend', player.challengecompletions[5]) / 100, 2, true) current2 = format(Math.pow(10, CalcECC('transcend', player.challengecompletions[5]))) + current3 = format(5 * CalcECC('transcend', player.challengecompletions[5]), 2, true) break } case 6: { current1 = format(Math.pow(0.965, CalcECC('reincarnation', player.challengecompletions[6])), 3, true) - current2 = format(10 * CalcECC('reincarnation', player.challengecompletions[6])) + current2 = format(0.3 * CalcECC('reincarnation', player.challengecompletions[6]), 2, true) current3 = format(2 * CalcECC('reincarnation', player.challengecompletions[6])) break } case 7: { current1 = format(1 + 0.04 * CalcECC('reincarnation', player.challengecompletions[7]), 2, true) - current2 = current3 = format(10 * CalcECC('reincarnation', player.challengecompletions[7])) + current2 = format(0.3 * CalcECC('reincarnation', player.challengecompletions[7]), 2, true) + current3 = format(15 * CalcECC('reincarnation', player.challengecompletions[7]), 2, true) break } case 8: { current1 = format(0.25 * CalcECC('reincarnation', player.challengecompletions[8]), 2, true) - current2 = format(20 * CalcECC('reincarnation', player.challengecompletions[8]), 2, true) + current2 = format(0.4 * CalcECC('reincarnation', player.challengecompletions[8]), 2, true) current3 = format(4 * CalcECC('reincarnation', player.challengecompletions[8]), 2, true) break } case 9: { current1 = format(CalcECC('reincarnation', player.challengecompletions[9])) current2 = format(Math.pow(1.1, CalcECC('reincarnation', player.challengecompletions[9])), 2, true) - current3 = format(20 * CalcECC('reincarnation', player.challengecompletions[9]), 2, true) + current3 = format(0.5 * CalcECC('reincarnation', player.challengecompletions[9]), 2, true) break } case 10: { @@ -266,13 +268,13 @@ export const challengeDisplay = (i: number, changefocus = true) => { case 11: { current1 = format(12 * CalcECC('ascension', player.challengecompletions[11])) current2 = format(Decimal.pow(1e5, CalcECC('ascension', player.challengecompletions[11]))) - current3 = format(80 * CalcECC('ascension', player.challengecompletions[11])) + current3 = format(CalcECC('ascension', player.challengecompletions[11])) break } case 12: { current1 = format(50 * CalcECC('ascension', player.challengecompletions[12])) current2 = format(12 * CalcECC('ascension', player.challengecompletions[12])) - current3 = format(CalcECC('ascension', player.challengecompletions[12])) + current3 = format(20 * CalcECC('ascension', player.challengecompletions[12])) break } case 13: { @@ -283,8 +285,8 @@ export const challengeDisplay = (i: number, changefocus = true) => { } case 14: { current1 = format(50 * CalcECC('ascension', player.challengecompletions[14])) - current2 = format(1 * player.challengecompletions[14]) - current3 = format(200 * CalcECC('ascension', player.challengecompletions[14])) + current2 = format(CalcECC('ascension', player.challengecompletions[14])) + current3 = format(1.5 * CalcECC('ascension', player.challengecompletions[14])) break } } @@ -404,11 +406,6 @@ export const challengeDisplay = (i: number, changefocus = true) => { export const getChallengeConditions = (i?: number) => { if (player.currentChallenge.reincarnation === 9) { - G.rune1level = 1 - G.rune2level = 1 - G.rune3level = 1 - G.rune4level = 1 - G.rune5level = 1 player.crystalUpgrades = [0, 0, 0, 0, 0, 0, 0, 0] } G.prestigePointGain = new Decimal('0') @@ -420,7 +417,6 @@ export const getChallengeConditions = (i?: number) => { G.reincarnationPointGain = new Decimal('0') } } - calculateRuneLevels() } export const toggleRetryChallenges = () => { @@ -439,11 +435,6 @@ export const highestChallengeRewards = (chalNum: number, highestValue: number) = if (player.ascensionCount === 0) { player.worlds.add(1 + Math.floor(highestValue * multiplier) * 100 / 100) } - // Addresses a bug where auto research does not work even if you unlock research - if (autoResearchEnabled() && player.ascensionCount === 0 && chalNum >= 6 && chalNum <= 10) { - player.roombaResearchIndex = 0 - player.autoResearch = G.researchOrderByCost[player.roombaResearchIndex] - } } // Works to mitigate the difficulty of calculating challenge multipliers when considering softcapping @@ -749,10 +740,9 @@ export const autoAscensionChallengeSweepUnlock = () => { } export const challenge15ScoreMultiplier = () => { - const arr = [ - player.campaigns.c15Bonus, // Campaign Bonus to c15 - 1 + 5 / 10000 * hepteractEffective('challenge'), // Challenge Hepteract - 1 + 0.25 * player.platonicUpgrades[15] // Omega Upgrade - ] - return productContents(arr) + return ( + player.campaigns.c15Bonus // Campaign Bonus to c15 + * (1 + 5 / 10000 * hepteractEffective('challenge')) // Challenge Hepteract + * (1 + 0.25 * player.platonicUpgrades[15]) // Omega Upgrade + ) } diff --git a/src/Config.ts b/src/Config.ts index 50fd1654a..50bd126d5 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -1,7 +1,7 @@ declare const PROD: boolean | undefined declare const DEV: boolean | undefined -export const version = '3.3.4 June 29, 2025: The Statistics and Ambrosia Update' +export const version = '4.0.0 September 20, 2025: The Greater Reimagining' /** * If true, the version is marked as a testing version. diff --git a/src/Corruptions.ts b/src/Corruptions.ts index a199d3f24..0ac8065c0 100644 --- a/src/Corruptions.ts +++ b/src/Corruptions.ts @@ -1,8 +1,13 @@ +import Decimal from 'break_infinity.js' import i18next from 'i18next' import { z } from 'zod' import { DOMCacheGetOrSet } from './Cache/DOM' +import { getOcteractUpgradeEffect } from './Octeracts' import { PCoinUpgradeEffects } from './PseudoCoinUpgrades' +import { getRuneEffects } from './Runes' +import { getGQUpgradeEffect } from './singularity' import { format, player } from './Synergism' +import { getTalismanEffects } from './Talismans' import { IconSets } from './Themes' import { toggleCorruptionLevel } from './Toggles' import { Alert, Notification, Prompt } from './UpdateHTML' @@ -98,7 +103,7 @@ export class CorruptionLoadout { const corrKey = corr as keyof Corruptions if ( player.challengecompletions[corrChallengeMinimum(corrKey)] === 0 - && !player.singularityUpgrades.platonicTau.getEffect().bonus + && !getGQUpgradeEffect('platonicTau') ) { this.setLevel(corrKey, 0) } @@ -125,14 +130,14 @@ export class CorruptionLoadout { } public calculateIndividualRawMultiplier (corr: keyof Corruptions) { - let bonusVal = player.singularityUpgrades.advancedPack.getEffect().bonus + let bonusVal = getGQUpgradeEffect('advancedPack') ? 0.33 : 0 bonusVal += +player.singularityChallenges.oneChallengeCap.rewards.corrScoreIncrease bonusVal += 0.3 * player.cubeUpgrades[74] let bonusMult = 1 - if (this.#levels[corr] >= 14 && player.singularityUpgrades.masterPack.getEffect().bonus) { + if (this.#levels[corr] >= 14 && getGQUpgradeEffect('masterPack')) { bonusMult *= 1.1 } @@ -149,7 +154,8 @@ export class CorruptionLoadout { const portionAboveLevel = Math.ceil(totalLevel) - totalLevel return Math.pow( this.#corruptionScoreMults[Math.floor(totalLevel)] + bonusVal - + portionAboveLevel * this.#corruptionScoreMults[Math.ceil(totalLevel)], + + portionAboveLevel + * (this.#corruptionScoreMults[Math.ceil(totalLevel)] - this.#corruptionScoreMults[Math.floor(totalLevel)]), viscosityPower ) * bonusMult } else { @@ -168,7 +174,11 @@ export class CorruptionLoadout { } #droughtEffect () { - return G.droughtMultiplier[this.#levels.drought] + let baseSalvageReduction = G.droughtSalvage[this.#levels.drought] + if (player.platonicUpgrades[13] > 0) { + baseSalvageReduction *= 0.5 + } + return baseSalvageReduction } #deflationEffect () { @@ -181,8 +191,8 @@ export class CorruptionLoadout { #illiteracyEffect () { const base = G.illiteracyPower[this.#levels.illiteracy] - const multiplier = (player.researchPoints > 1) - ? 1 + (1 / 100) * player.platonicUpgrades[9] * Math.min(100, Math.log10(player.researchPoints)) + const multiplier = (player.obtainium.gte(1)) + ? 1 + (1 / 100) * player.platonicUpgrades[9] * Math.min(100, Decimal.log10(player.obtainium)) : 1 return Math.min(base * multiplier, 1) } @@ -236,8 +246,10 @@ export class CorruptionLoadout { } get bonusLevels () { - let bonusLevel = player.singularityUpgrades.corruptionFifteen.getEffect().bonus ? 1 : 0 + let bonusLevel = getGQUpgradeEffect('corruptionFifteen') bonusLevel += +player.singularityChallenges.oneChallengeCap.rewards.freeCorruptionLevel + bonusLevel += getTalismanEffects('cookieGrandma').freeCorruptionLevel + bonusLevel += getRuneEffects('finiteDescent').corruptionFreeLevels return bonusLevel } @@ -408,14 +420,14 @@ export const maxCorruptionLevel = () => { } // Overrides everything above. - if (player.singularityUpgrades.platonicTau.getEffect().bonus) { + if (getGQUpgradeEffect('platonicTau')) { max = Math.max(13, max) } - if (player.singularityUpgrades.corruptionFourteen.getEffect().bonus) { + if (getGQUpgradeEffect('corruptionFourteen')) { max += 1 } - max += +player.octeractUpgrades.octeractCorruption.getEffect().bonus + max += getOcteractUpgradeEffect('octeractCorruption') return max } @@ -480,7 +492,7 @@ export const corruptionDisplay = (corr: keyof Corruptions | 'exit') => { } DOMCacheGetOrSet('corruptionName').textContent = text.name - DOMCacheGetOrSet('corruptionDescription').textContent = text.description + DOMCacheGetOrSet('corruptionDescription').innerHTML = text.description DOMCacheGetOrSet('corruptionLevelCurrent').textContent = text.current DOMCacheGetOrSet('corruptionLevelPlanned').textContent = text.planned DOMCacheGetOrSet('corruptionMultiplierContribution').textContent = text.multiplier @@ -779,22 +791,22 @@ export const revealCorruptions = () => { const c13Unlocks = document.getElementsByClassName('chal13Corruption') as HTMLCollectionOf const c14Unlocks = document.getElementsByClassName('chal14Corruption') as HTMLCollectionOf - if (player.challengecompletions[11] > 0 || player.singularityUpgrades.platonicTau.getEffect().bonus) { + if (player.challengecompletions[11] > 0 || getGQUpgradeEffect('platonicTau')) { for (let i = 0; i < c11Unlocks.length; i++) { c11Unlocks[i].style.display = 'flex' } } - if (player.challengecompletions[12] > 0 || player.singularityUpgrades.platonicTau.getEffect().bonus) { + if (player.challengecompletions[12] > 0 || getGQUpgradeEffect('platonicTau')) { for (let i = 0; i < c12Unlocks.length; i++) { c12Unlocks[i].style.display = 'flex' } } - if (player.challengecompletions[13] > 0 || player.singularityUpgrades.platonicTau.getEffect().bonus) { + if (player.challengecompletions[13] > 0 || getGQUpgradeEffect('platonicTau')) { for (let i = 0; i < c13Unlocks.length; i++) { c13Unlocks[i].style.display = 'flex' } } - if (player.challengecompletions[14] > 0 || player.singularityUpgrades.platonicTau.getEffect().bonus) { + if (player.challengecompletions[14] > 0 || getGQUpgradeEffect('platonicTau')) { for (let i = 0; i < c14Unlocks.length; i++) { c14Unlocks[i].style.display = 'flex' } diff --git a/src/CubeExperimental.ts b/src/CubeExperimental.ts index 4e6a7fa85..266a7d74a 100644 --- a/src/CubeExperimental.ts +++ b/src/CubeExperimental.ts @@ -6,18 +6,13 @@ file without asking me first. You may edit this file as much as you want, though! Thank you! */ -import Decimal from 'break_infinity.js' import i18next from 'i18next' -import { achievementaward } from './Achievements' -import { calculateCubeBlessings } from './Calculate' -import { CalcECC } from './Challenges' -import { calculateHypercubeBlessings } from './Hypercubes' -import { calculatePlatonicBlessings } from './PlatonicCubes' +import { awardUngroupedAchievement, getAchievementReward } from './Achievements' import { quarkHandler } from './Quark' import { format, player } from './Synergism' -import { calculateTesseractBlessings } from './Tesseracts' import type { Player } from './types/Synergism' import { Alert, Prompt } from './UpdateHTML' +import { sumContents } from './Utility' /* Constants */ @@ -91,7 +86,7 @@ export abstract class Cube { async openCustom () { // TODO: Replace this with `this`? const thisInPlayer = player[this.key] as Cube - const amount = await Prompt(i18next.t('cubes.howManyCubesOpen', { x: format(thisInPlayer, 0, true) })) + const amount = await Prompt(i18next.t('cubes.howManyCubesOpen', { x: format(thisInPlayer.valueOf(), 0, true) })) if (amount === null) { return Alert(i18next.t('cubes.noCubesOpened')) @@ -178,8 +173,8 @@ export class WowCubes extends Cube { open (value: number, max = false, free = false) { let toSpend = max ? Number(this) : (free ? value : Math.min(Number(this), value)) - if (value === 1 && player.cubeBlessings.accelerator >= 2e11 && player.achievements[246] < 1) { - achievementaward(246) + if (value === 1 && player.cubeBlessings.accelerator >= 2e11) { + awardUngroupedAchievement('oneCubeOfMany') } if (!free) { @@ -193,11 +188,27 @@ export class WowCubes extends Cube { player.cubeQuarkDaily += actualQuarksGain player.worlds.add(actualQuarksGain, false) + const sumOfTributes = sumContents(Object.values(player.cubeBlessings)) + // if >= 1e300 totalTribute, do not award tributes + if (sumOfTributes >= 1e300) { + return + } + toSpend *= 1 + player.researches[138] / 1000 toSpend *= 1 + 0.8 * player.researches[168] / 1000 toSpend *= 1 + 0.6 * player.researches[198] / 1000 toSpend = Math.floor(toSpend) + + toSpend = Math.min(toSpend, 1e300 - sumOfTributes) + + if ( + player.singularityChallenges.taxmanLastStand.enabled + && player.singularityChallenges.taxmanLastStand.completions >= 5 + ) { + toSpend /= Math.pow(1 + Math.log(1 + sumOfTributes + toSpend), 3) + } + let toSpendModulo = toSpend % 20 let toSpendDiv20 = Math.floor(toSpend / 20) @@ -219,7 +230,6 @@ export class WowCubes extends Cube { // If you're opening more than 20 cubes, it will consume all cubes until remainder mod 20, giving expected values. for (const key of keys) { player.cubeBlessings[key] += blessings[key].weight * toSpendDiv20 - * (1 + Math.floor(CalcECC('ascension', player.challengecompletions[12]))) } // Then, the remaining cubes will be opened, simulating the probability [RNG Element] @@ -227,12 +237,10 @@ export class WowCubes extends Cube { const num = 100 * Math.random() for (const key of keys) { if (blessings[key].pdf(num)) { - player.cubeBlessings[key] += 1 + Math.floor(CalcECC('ascension', player.challengecompletions[12])) + player.cubeBlessings[key] += 1 } } } - - calculateCubeBlessings() } } @@ -272,8 +280,6 @@ export class WowTesseracts extends Cube { } } } - - calculateTesseractBlessings() const extraCubeBlessings = Math.floor(12 * toSpend * player.researches[153]) player.wowCubes.open(extraCubeBlessings, false, true) } @@ -315,8 +321,6 @@ export class WowHypercubes extends Cube { } } } - - calculateHypercubeBlessings() const extraTesseractBlessings = Math.floor(100 * toSpend * player.researches[183]) player.wowTesseracts.open(extraTesseractBlessings, false, true) } @@ -381,10 +385,11 @@ export class WowPlatonicCubes extends Cube { } } } - calculatePlatonicBlessings() - if (player.achievements[271] > 0) { + + const hyperCubesPerPlatonic = +getAchievementReward('platonicToHypercubes') + if (hyperCubesPerPlatonic > 0) { const extraHypercubes = Math.floor( - toSpend * Math.max(0, Math.min(1, (Decimal.log(player.ascendShards.add(1), 10) - 1e5) / 9e5)) + toSpend * hyperCubesPerPlatonic ) player.wowHypercubes.open(extraHypercubes, false, true) } diff --git a/src/Cubes.ts b/src/Cubes.ts index 46ae72fa8..18fa21cf6 100644 --- a/src/Cubes.ts +++ b/src/Cubes.ts @@ -1,12 +1,24 @@ +import Decimal from 'break_infinity.js' import i18next from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' -import { calculateCubeBlessings, calculateCubicSumData, calculateSummationNonLinear } from './Calculate' -import { updateResearchBG } from './Research' -import { calculateSingularityDebuff } from './singularity' +import { calculateCubicSumData, calculateSummationNonLinear } from './Calculate' +import { researchData, updateResearchBG } from './Research' +import { calculateSingularityDebuff, getGQUpgradeEffect } from './singularity' import { format, player } from './Synergism' +import { + calculateAcceleratorTesseractBlessing, + calculateAntELOTesseractBlessing, + calculateAntSacrificeTesseractBlessing, + calculateAntSpeedTesseractBlessing, + calculateGlobalSpeedTesseractBlessing, + calculateMultiplierTesseractBlessing, + calculateObtainiumTesseractBlessing, + calculateOfferingTesseractBlessing, + calculateRuneEffectivenessTesseractBlessing, + calculateSalvageTesseractBlessing +} from './Tesseracts' import { revealStuff } from './UpdateHTML' import { upgradeupdate } from './Upgrades' -import { Globals as G } from './Variables' export interface IMultiBuy { levelCanBuy: number @@ -151,10 +163,8 @@ export const awardAutosCookieUpgrade = () => { updateCubeUpgradeBG(i) } - calculateCubeBlessings() - for (const i of researchAutomationIndices) { - player.researches[i] = G.researchMaxLevels[i] + player.researches[i] = researchData[i].maxLevel updateResearchBG(i) } } @@ -162,11 +172,11 @@ export const awardAutosCookieUpgrade = () => { export const buyCubeUpgrades = (i: number, buyMax = player.cubeUpgradesBuyMaxToggle, auto = false) => { // Actually lock for HTML exploit if ( - (i > 50 && i <= 55 && !player.singularityUpgrades.cookies.getEffect().bonus) - || (i > 55 && i <= 60 && !player.singularityUpgrades.cookies2.getEffect().bonus) - || (i > 60 && i <= 65 && !player.singularityUpgrades.cookies3.getEffect().bonus) - || (i > 65 && i <= 70 && !player.singularityUpgrades.cookies4.getEffect().bonus) - || (i > 70 && !player.singularityUpgrades.cookies5.getEffect().bonus) + (i > 50 && i <= 55 && !getGQUpgradeEffect('cookies')) + || (i > 55 && i <= 60 && !getGQUpgradeEffect('cookies2')) + || (i > 60 && i <= 65 && !getGQUpgradeEffect('cookies3')) + || (i > 65 && i <= 70 && !getGQUpgradeEffect('cookies4')) + || (i > 70 && !getGQUpgradeEffect('cookies5')) ) { return } @@ -208,7 +218,6 @@ export const buyCubeUpgrades = (i: number, buyMax = player.cubeUpgradesBuyMaxTog if (!auto) { cubeUpgradeDesc(i) revealStuff() - calculateCubeBlessings() } updateCubeUpgradeBG(i) } @@ -250,8 +259,153 @@ export const autoBuyCubeUpgrades = () => { if (update) { revealStuff() - calculateCubeBlessings() } } } } + +export const calculateAcceleratorCubeBlessing = () => { + const DR = 1 / 3 + const effectPerBlessing = calculateAcceleratorTesseractBlessing() / 500 + const limit = 1000 + const DRIncrease = player.cubeUpgrades[45] / 300 + + if (player.cubeBlessings.accelerator < limit) { + return Math.pow(effectPerBlessing * player.cubeBlessings.accelerator, 1 + DRIncrease) + } else { + const limitMult = Math.pow(limit, 1 - DR + DRIncrease) + return effectPerBlessing * limitMult * Math.pow(player.cubeBlessings.accelerator, DR + DRIncrease) + } +} + +export const calculateMultiplierCubeBlessing = () => { + const DR = 1 / 3 + const effectPerBlessing = calculateMultiplierTesseractBlessing() / 5000 + const limit = 1000 + const DRIncrease = player.cubeUpgrades[35] / 300 + + if (player.cubeBlessings.multiplier < limit) { + return Math.pow(1 + effectPerBlessing * player.cubeBlessings.multiplier, 1 + DRIncrease) + } else { + const limitMult = Math.pow(limit, 1 - DR + DRIncrease) + return 1 + effectPerBlessing * limitMult * Math.pow(player.cubeBlessings.multiplier, DR + DRIncrease) + } +} + +export const calculateOfferingCubeBlessing = () => { + const DR = 2 / 3 + const effectPerBlessing = new Decimal(calculateOfferingTesseractBlessing()).div(2000) + const limit = 1000 + const DRIncrease = player.cubeUpgrades[24] * 2 / 300 + + if (player.cubeBlessings.offering < limit) { + return Decimal.min( + 1e300, + Decimal.pow(effectPerBlessing.times(player.cubeBlessings.offering).plus(1), 1 + DRIncrease) + ).toNumber() + } else { + const limitMult = Decimal.pow(limit, 1 - DR + DRIncrease) + return Decimal.min( + 1e300, + limitMult.times(effectPerBlessing).times(Math.pow(player.cubeBlessings.offering, DR + DRIncrease)).plus(1) + ).toNumber() + } +} + +export const calculateSalvageCubeBlessing = () => { + const limit = 1000 + const effectMultiplier = (1 + player.cubeUpgrades[14] / 100) * calculateSalvageTesseractBlessing() + + if (player.cubeBlessings.runeExp < limit) { + return effectMultiplier * (player.cubeBlessings.runeExp * 10 / limit) + } else { + const limitBonus = 10 + return effectMultiplier * (limitBonus + 10 * Math.log10(player.cubeBlessings.runeExp / limit)) + } +} + +export const calculateObtainiumCubeBlessing = () => { + const DR = 2 / 3 + const effectPerBlessing = new Decimal(calculateObtainiumTesseractBlessing()).div(2000) + const limit = 1000 + const DRIncrease = player.cubeUpgrades[40] * 2 / 300 + + if (player.cubeBlessings.obtainium < limit) { + return Decimal.min( + 1e300, + Decimal.pow(effectPerBlessing.times(player.cubeBlessings.obtainium).plus(1), 1 + DRIncrease) + ).toNumber() + } else { + const limitMult = Decimal.pow(limit, 1 - DR + DRIncrease) + return Decimal.min( + 1e300, + limitMult.times(effectPerBlessing).times(Math.pow(player.cubeBlessings.obtainium, DR + DRIncrease)).plus(1) + ).toNumber() + } +} + +export const calculateAntSpeedCubeBlessing = () => { + const effectPerBlessing = 1 / 10000 + const exponentIncrease = player.cubeUpgrades[22] / 40 + + return Decimal.pow(1 + effectPerBlessing * player.cubeBlessings.antSpeed, 2 + exponentIncrease) + .times(calculateAntSpeedTesseractBlessing()) +} + +export const calculateAntSacrificeCubeBlessing = (): Decimal => { + const DR = 2 / 3 + const effectPerBlessing = calculateAntSacrificeTesseractBlessing() / 5000 + const limit = 1000 + const DRIncrease = player.cubeUpgrades[15] / 50 + + if (player.cubeBlessings.antSacrifice < limit) { + return Decimal.pow(1 + effectPerBlessing * player.cubeBlessings.antSacrifice, 1 + DRIncrease) + } else { + const limitMult = Math.pow(limit, 1 - DR + DRIncrease) + return Decimal.pow(player.cubeBlessings.antSacrifice, DR + DRIncrease).times(effectPerBlessing).times(limitMult) + .add(1) + } +} + +export const calculateAntELOCubeBlessing = () => { + const effectExponent = 1 + player.cubeUpgrades[25] / 100 + + return Math.pow( + 1 + Math.log10(1 + player.cubeBlessings.antELO) / 100 * calculateAntELOTesseractBlessing(), + effectExponent + ) +} + +export const calculateRuneEffectivenessCubeBlessing = () => { + const DR = 1 / 16 + const effectPerBlessing = calculateRuneEffectivenessTesseractBlessing() / 10000 + const limit = 1000 + const DRIncrease = player.cubeUpgrades[44] / 1600 + + if (player.cubeBlessings.talismanBonus < limit) { + return Math.pow(1 + effectPerBlessing * player.cubeBlessings.talismanBonus, 1 + DRIncrease) + } else { + const limitMult = Math.pow(limit, 1 - DR + DRIncrease) + return Math.min( + 1e300, + 1 + limitMult * effectPerBlessing * Math.pow(player.cubeBlessings.talismanBonus, DR + DRIncrease) + ) + } +} + +export const calculateGlobalSpeedCubeBlessing = () => { + const DR = 1 / 16 + const effectPerBlessing = calculateGlobalSpeedTesseractBlessing() / 1000 + const limit = 1000 + const DRIncrease = player.cubeUpgrades[34] / 1600 + + if (player.cubeBlessings.globalSpeed < limit) { + return Math.pow(1 + effectPerBlessing * player.cubeBlessings.globalSpeed, 1 + DRIncrease) + } else { + const limitMult = Math.pow(limit, 1 - DR + DRIncrease) + return Math.min( + 1e300, + 1 + limitMult * effectPerBlessing * Math.pow(player.cubeBlessings.globalSpeed, DR + DRIncrease) + ) + } +} diff --git a/src/DynamicUpgrade.ts b/src/DynamicUpgrade.ts deleted file mode 100644 index d5a218deb..000000000 --- a/src/DynamicUpgrade.ts +++ /dev/null @@ -1,74 +0,0 @@ -import i18next from 'i18next' -import { format } from './Synergism' -import { Alert, Prompt } from './UpdateHTML' - -export interface IUpgradeData { - name: string - description: string - level?: number - maxLevel: number - costPerLevel: number - toggleBuy?: number - effect?(this: void, n: number): { bonus: number | boolean; desc: string } - freeLevels?: number -} - -export abstract class DynamicUpgrade { - public name: string - readonly description: string - public level = 0 - public freeLevels = 0 - readonly maxLevel: number // -1 = infinitely levelable - readonly costPerLevel: number - public toggleBuy = 1 // -1 = buy MAX (or 1000 in case of infinity levels!) - readonly effect: (n: number) => { bonus: number | boolean; desc: string } - - constructor (data: IUpgradeData) { - this.name = data.name - this.description = data.description - this.level = data.level ?? 0 - this.freeLevels = data.freeLevels ?? 0 - this.maxLevel = data.maxLevel - this.costPerLevel = data.costPerLevel - this.toggleBuy = data.toggleBuy ?? 1 - this.effect = data.effect ?? ((n: number) => ({ bonus: n, desc: 'WIP not implemented' })) - } - - public async changeToggle (): Promise { - // Is null unless given an explicit number - const newToggle = await Prompt(i18next.t('dynamicUpgrades.validation.setPurchaseAmount', { x: this.name })) - const newToggleAmount = Number(newToggle) - - if (newToggle === null) { - return Alert(i18next.t('dynamicUpgrades.validation.toggleKept', { x: format(this.toggleBuy) })) - } - - if (!Number.isInteger(newToggle)) { - return Alert(i18next.t('general.validation.fraction')) - } - if (newToggleAmount < -1) { - return Alert(i18next.t('dynamicUpgrades.validation.onlyNegativeOne')) - } - if (newToggleAmount === 0) { - return Alert(i18next.t('dynamicUpgrades.validation.notZero')) - } - - this.toggleBuy = newToggleAmount - const m = newToggleAmount === -1 - ? i18next.t('dynamicUpgrades.toggleMax') - : i18next.t('dynamicUpgrades.toggle', { x: format(this.toggleBuy) }) - - return Alert(m) - } - - public getEffect (): { bonus: number | boolean; desc: string } { - const effectiveLevel = this.level + Math.min(this.level, this.freeLevels) - + Math.sqrt(Math.max(0, this.freeLevels - this.level)) - return this.effect(effectiveLevel) - } - - abstract toString (): string - abstract updateUpgradeHTML (): void - abstract getCostTNL (): number - public abstract buyLevel (event: MouseEvent): Promise | void -} diff --git a/src/Event.ts b/src/Event.ts index 58bc9e721..395149e1b 100644 --- a/src/Event.ts +++ b/src/Event.ts @@ -1,5 +1,6 @@ import { DOMCacheGetOrSet } from './Cache/DOM' import { allDurableConsumables, type PseudoCoinConsumableNames } from './Login' +import { getGQUpgradeEffect } from './singularity' import { getTimePinnedToLoadDate, player } from './Synergism' import { revealStuff } from './UpdateHTML' import { timeReminingHours } from './Utility' @@ -131,7 +132,7 @@ export const getEventBuff = (buff: BuffType): number => { case BuffType.Octeract: return event.octeract case BuffType.OneMind: - return player.singularityUpgrades.oneMind.level > 0 ? event.oneMind : 0 + return getGQUpgradeEffect('oneMind') > 0 ? event.oneMind : 0 case BuffType.BlueberryTime: return event.blueberryTime case BuffType.AmbrosiaLuck: diff --git a/src/EventListeners.ts b/src/EventListeners.ts index 2e744eead..38a9f7365 100644 --- a/src/EventListeners.ts +++ b/src/EventListeners.ts @@ -1,5 +1,5 @@ import i18next from 'i18next' -import { achievementdescriptions, achievementpointvalues } from './Achievements' +import { displayAchievementProgress, resetAchievementProgressDisplay } from './Achievements' import { antRepeat, antUpgradeDescription, @@ -9,7 +9,10 @@ import { updateAntDescription } from './Ants' import { - type blueberryUpgradeNames, + type AmbrosiaUpgradeNames, + ambrosiaUpgrades, + ambrosiaUpgradeToString, + buyAmbrosiaUpgradeLevel, createLoadoutDescription, displayLevelsBlueberry, displayOnlyLoadout, @@ -19,17 +22,16 @@ import { loadoutHandler, resetBlueberryTree, resetHighlights, - resetLoadoutOnlyDisplay + resetLoadoutOnlyDisplay, + updateMobileAmbrosiaHTML } from './BlueberryUpgrades' import { boostAccelerator, buyAccelerator, - buyAllBlessings, buyCrystalUpgrades, buyMultiplier, buyParticleBuilding, buyProducer, - buyRuneBonusLevels, buyTesseractBuilding } from './Buy' import { DOMCacheGetOrSet } from './Cache/DOM' @@ -39,11 +41,15 @@ import { testing } from './Config' import { corruptionCleanseConfirm, corruptionDisplay } from './Corruptions' import { buyCubeUpgrades, cubeUpgradeDesc } from './Cubes' import { + craftHepteracts, + expandHepteracts, hepteractDescriptions, + hepteractKeys, hepteractToOverfluxOrbDescription, overfluxPowderDescription, overfluxPowderWarp, toggleAutoBuyOrbs, + toggleAutomaticHepteracts, tradeHepteractToOverfluxOrb } from './Hepteracts' import { resetHistoryTogglePerSecond } from './History' @@ -61,31 +67,62 @@ import { updateSaveString } from './ImportExport' import { exitFastForward, getTips, sendToWebsocket, setTips } from './Login' +import { + buyOcteractUpgradeLevel, + type OcteractDataKeys, + octeractUpgrades, + updateMobileOcteractHTML, + upgradeOcteractToString +} from './Octeracts' import { buyPlatonicUpgrades, createPlatonicDescription } from './Platonic' -import { displayRedAmbrosiaLevels, getRedAmbrosiaUpgrade, resetRedAmbrosiaDisplay } from './RedAmbrosiaUpgrades' -import { buyResearch, researchDescriptions } from './Research' +import { + buyRedAmbrosiaUpgradeLevel, + displayRedAmbrosiaLevels, + redAmbrosiaUpgradeToString, + resetRedAmbrosiaDisplay, + updateMobileRedAmbrosiaHTML +} from './RedAmbrosiaUpgrades' +import { buyResearch, researchDescriptions, updateResearchAuto } from './Research' import { resetrepeat, updateAutoCubesOpens, updateAutoReset, updateTesseractAutoBuyAmount } from './Reset' -import { displayRuneInformation, redeemShards } from './Runes' -import { buyShopUpgrades, resetShopUpgrades, shopData, shopDescriptions, shopUpgradeTypes, useConsumable } from './Shop' -import { buyGoldenQuarks, getLastUpgradeInfo, singularityPerks } from './singularity' +import { buyAllBlessingLevels, buyBlessingLevels, focusedRuneBlessingHTML, runeBlessingKeys } from './RuneBlessings' +import { focusedRuneHTML, focusedRuneLockedHTML, type RuneKeys, runes, runeToIndex, sacrificeOfferings } from './Runes' +import { buyAllSpiritLevels, buySpiritLevels, focusedRuneSpiritHTML, runeSpiritKeys } from './RuneSpirits' +import { + buyShopUpgrades, + resetShopUpgrades, + shopData, + shopDescriptions, + shopUpgradeTypes, + useConsumablePrompt +} from './Shop' +import { + buyGoldenQuarks, + buyGQUpgradeLevel, + getLastUpgradeInfo, + goldenQuarkUpgrades, + type SingularityDataKeys, + singularityPerks, + updateMobileGQHTML, + upgradeGQToString +} from './singularity' +import type { SingularityChallengeDataKeys } from './SingularityChallenges' import { displayStats } from './Statistics' import { generateExportSummary } from './Summary' import { player, resetCheck, saveSynergy } from './Synergism' import { changeSubTab, changeTab, Tabs } from './Tabs' import { buyAllTalismanResources, - buyTalismanEnhance, - buyTalismanLevels, + buyTalismanLevel, + buyTalismanLevelToMax, + buyTalismanLevelToRarityIncrease, buyTalismanResources, - changeTalismanModifier, - respecTalismanCancel, - respecTalismanConfirm, - showEnhanceTalismanPrices, - showRespecInformation, - showTalismanEffect, - showTalismanPrices, + type TalismanKeys, + talismanRarityInfo, + talismans, + talismanToStringHTML, toggleTalismanBuy, - updateTalismanCostDisplay + updateTalismanCostDisplay, + updateTalismanCostHTML } from './Talismans' import { IconSets, imgErrorHandler, toggleAnnotation, toggleIconSet, toggleTheme } from './Themes' import { @@ -99,7 +136,6 @@ import { toggleautobuytesseract, toggleAutoChallengeRun, toggleAutoChallengesIgnore, - toggleautoenhance, toggleautofortify, toggleautoopensCubes, toggleAutoResearch, @@ -123,7 +159,7 @@ import { updateRuneBlessingBuyAmount } from './Toggles' import type { FirstToEighth, FirstToFifth, OneToFive, Player } from './types/Synergism' -import { closeChangelog, Confirm, openChangelog, Prompt } from './UpdateHTML' +import { closeChangelog, CloseModal, Confirm, Modal, openChangelog, Prompt } from './UpdateHTML' import { shopMouseover } from './UpdateVisuals' import { buyConstantUpgrades, @@ -133,6 +169,7 @@ import { crystalupgradedescriptions, upgradedescriptions } from './Upgrades' +import { isMobile } from './Utility' import { Globals as G } from './Variables' /* STYLE GUIDE */ @@ -398,11 +435,22 @@ export const generateEventHandlers = () => { // ACHIEVEMENTS TAB // TODO: Remove 1 indexing - for (let index = 1; index <= achievementpointvalues.length - 1; index++) { + /*for (let index = 1; index <= achievementpointvalues.length - 1; index++) { // Onmouseover events (Achievement descriptions) const achievement = DOMCacheGetOrSet(`ach${index}`) achievement.addEventListener('mouseover', () => achievementdescriptions(index)) achievement.addEventListener('focus', () => achievementdescriptions(index)) + }*/ + DOMCacheGetOrSet('showAchievementProgress').addEventListener('mouseover', () => displayAchievementProgress()) + DOMCacheGetOrSet('showAchievementProgress').addEventListener('focus', () => displayAchievementProgress()) + DOMCacheGetOrSet('showAchievementProgress').addEventListener('mouseout', () => resetAchievementProgressDisplay()) + DOMCacheGetOrSet('showAchievementProgress').addEventListener('blur', () => resetAchievementProgressDisplay()) + + for (let index = 0; index < 2; index++) { + DOMCacheGetOrSet(`toggleAchievementSubTab${index + 1}`).addEventListener( + 'click', + () => changeSubTab(Tabs.Achievements, { page: index }) + ) } // RUNES TAB [And all corresponding subtabs] @@ -418,16 +466,51 @@ export const generateEventHandlers = () => { } // Part 1: Runes Subtab - for (let index = 0; index < 7; index++) { - const rune = DOMCacheGetOrSet(`rune${index + 1}`) - rune.addEventListener('mouseover', () => displayRuneInformation(index + 1)) - rune.addEventListener('focus', () => displayRuneInformation(index + 1)) - rune.addEventListener('click', () => toggleAutoSacrifice(index + 1)) - - const activateRune = DOMCacheGetOrSet(`activaterune${index + 1}`) - activateRune.addEventListener('mouseover', () => displayRuneInformation(index + 1)) - activateRune.addEventListener('focus', () => displayRuneInformation(index + 1)) - activateRune.addEventListener('click', () => redeemShards(index + 1)) + + const runeStats = Object.keys( + runes + ) as RuneKeys[] + for (const key of runeStats) { + const rune = DOMCacheGetOrSet(`${key}RuneContainer`) + rune.addEventListener( + 'mousemove', + (e: MouseEvent) => { + Modal(focusedRuneHTML(key), e.clientX, e.clientY, { borderColor: runes[key].runeHTMLStyle.borderColor }) + } + ) + rune.addEventListener('focus', () => { + const element = DOMCacheGetOrSet(`${key}Rune`) + const elmRect = element.getBoundingClientRect() + Modal(focusedRuneHTML(key), elmRect.x, elmRect.y + elmRect.height / 2, { + borderColor: runes[key].runeHTMLStyle.borderColor + }) + }) + rune.addEventListener('mouseout', () => CloseModal()) + + const runeIcon = DOMCacheGetOrSet(`${key}Rune`) + runeIcon.addEventListener('click', () => toggleAutoSacrifice(runeToIndex[key])) + + const activateRune = DOMCacheGetOrSet(`${key}RuneSacrifice`) + /*activateRune.addEventListener('mouseover', () => updateFocusedRuneHTML(key)) + activateRune.addEventListener('focus', () => updateFocusedRuneHTML(key))*/ + activateRune.addEventListener('click', () => sacrificeOfferings(key, player.offerings, false)) + + // Add event listeners for locked rune containers + const lockedRune = DOMCacheGetOrSet(`${key}RuneLocked`) + lockedRune.addEventListener( + 'mousemove', + (e: MouseEvent) => { + Modal(focusedRuneLockedHTML(key), e.clientX, e.clientY, { borderColor: 'gray' }) + } + ) + lockedRune.addEventListener('focus', () => { + const element = DOMCacheGetOrSet(`${key}RuneLockedContainer`) + const elmRect = element.getBoundingClientRect() + Modal(focusedRuneLockedHTML(key), elmRect.x, elmRect.y + elmRect.height / 2, { + borderColor: 'gray' + }) + }) + lockedRune.addEventListener('mouseout', () => CloseModal()) } // Part 2: Talismans Subtab @@ -441,7 +524,6 @@ export const generateEventHandlers = () => { } DOMCacheGetOrSet('toggleautoBuyFragments').addEventListener('click', () => toggleAutoBuyFragment()) - DOMCacheGetOrSet('toggleautoenhance').addEventListener('click', () => toggleautoenhance()) DOMCacheGetOrSet('toggleautofortify').addEventListener('click', () => toggleautofortify()) // Talisman Fragments/Shards @@ -456,62 +538,127 @@ export const generateEventHandlers = () => { ] as const for (let index = 0; index < talismanItemNames.length; index++) { const buyTalisman = DOMCacheGetOrSet(`buyTalismanItem${index + 1}`) - buyTalisman.addEventListener('mouseover', () => updateTalismanCostDisplay(talismanItemNames[index])) - buyTalisman.addEventListener('focus', () => updateTalismanCostDisplay(talismanItemNames[index])) - buyTalisman.addEventListener('click', () => buyTalismanResources(talismanItemNames[index])) + buyTalisman.addEventListener('mouseover', () => { + const obtainiumBudget = player.obtainium.mul(player.buyTalismanShardPercent / 100) + const offeringBudget = player.offerings.mul(player.buyTalismanShardPercent / 100) + updateTalismanCostDisplay(talismanItemNames[index], obtainiumBudget, offeringBudget) + }) + buyTalisman.addEventListener('focus', () => { + const obtainiumBudget = player.obtainium.mul(player.buyTalismanShardPercent / 100) + const offeringBudget = player.offerings.mul(player.buyTalismanShardPercent / 100) + updateTalismanCostDisplay(talismanItemNames[index], obtainiumBudget, offeringBudget) + }) + buyTalisman.addEventListener('click', () => { + const obtainiumBudget = player.obtainium.mul(player.buyTalismanShardPercent / 100) + const offeringBudget = player.offerings.mul(player.buyTalismanShardPercent / 100) + buyTalismanResources(talismanItemNames[index], obtainiumBudget, offeringBudget) + }) } const buyTalismanAll = DOMCacheGetOrSet('buyTalismanAll') - buyTalismanAll.addEventListener('mouseover', () => updateTalismanCostDisplay(null)) - buyTalismanAll.addEventListener('focus', () => updateTalismanCostDisplay(null)) + buyTalismanAll.addEventListener('mouseover', () => { + const obtainiumBudget = player.obtainium.mul(player.buyTalismanShardPercent / 100) + const offeringBudget = player.offerings.mul(player.buyTalismanShardPercent / 100) + updateTalismanCostDisplay(null, obtainiumBudget, offeringBudget) + }) + buyTalismanAll.addEventListener('focus', () => { + const obtainiumBudget = player.obtainium.mul(player.buyTalismanShardPercent / 100) + const offeringBudget = player.offerings.mul(player.buyTalismanShardPercent / 100) + updateTalismanCostDisplay(null, obtainiumBudget, offeringBudget) + }) buyTalismanAll.addEventListener('click', () => buyAllTalismanResources()) - for (let index = 0; index < 7; index++) { - DOMCacheGetOrSet(`talisman${index + 1}`).addEventListener('click', () => showTalismanEffect(index)) - - const levelTalisman = DOMCacheGetOrSet(`leveluptalisman${index + 1}`) - levelTalisman.addEventListener('mouseover', () => showTalismanPrices(index)) - levelTalisman.addEventListener('focus', () => showTalismanPrices(index)) - levelTalisman.addEventListener('click', () => buyTalismanLevels(index)) - - const enhanceTalisman = DOMCacheGetOrSet(`enhancetalisman${index + 1}`) - enhanceTalisman.addEventListener('mouseover', () => showEnhanceTalismanPrices(index)) - enhanceTalisman.addEventListener('focus', () => showEnhanceTalismanPrices(index)) - enhanceTalisman.addEventListener('click', () => buyTalismanEnhance(index)) - - DOMCacheGetOrSet(`respectalisman${index + 1}`).addEventListener( + const talismanStats = Object.keys( + talismans + ) as TalismanKeys[] + for (const key of talismanStats) { + DOMCacheGetOrSet(`${key}Talisman`).addEventListener( + 'mouseover', + () => { + talismanToStringHTML(key) + talismanRarityInfo(key) + } + ) + DOMCacheGetOrSet(`level${key}Once`).addEventListener( + 'click', + () => buyTalismanLevel(key) + ) + DOMCacheGetOrSet(`level${key}Once`).addEventListener( + 'mouseover', + () => updateTalismanCostHTML(key) + ) + DOMCacheGetOrSet(`level${key}ToRarityIncrease`).addEventListener( + 'click', + () => buyTalismanLevelToRarityIncrease(key) + ) + DOMCacheGetOrSet(`level${key}ToRarityIncrease`).addEventListener( + 'mouseover', + () => updateTalismanCostHTML(key) + ) + DOMCacheGetOrSet(`level${key}ToMax`).addEventListener( 'click', - () => showRespecInformation(index) + () => buyTalismanLevelToMax(key) + ) + DOMCacheGetOrSet(`level${key}ToMax`).addEventListener( + 'mouseover', + () => updateTalismanCostHTML(key) ) } - DOMCacheGetOrSet('respecAllTalismans').addEventListener('click', () => showRespecInformation(7)) - DOMCacheGetOrSet('confirmTalismanRespec').addEventListener('click', () => respecTalismanConfirm(G.talismanRespec)) - DOMCacheGetOrSet('cancelTalismanRespec').addEventListener('click', () => respecTalismanCancel(G.talismanRespec)) + // Part 3: Blessings and Spirits - for (let index = 0; index < 5; index++) { - DOMCacheGetOrSet(`talismanRespecButton${index + 1}`).addEventListener( + for (const key of runeBlessingKeys) { + const runeBlessing = DOMCacheGetOrSet(`${key}RuneBlessingContainer`) + runeBlessing.addEventListener( + 'mousemove', + (e: MouseEvent) => { + Modal(focusedRuneBlessingHTML(key), e.clientX, e.clientY, { borderColor: runes[key].runeHTMLStyle.borderColor }) + } + ) + runeBlessing.addEventListener('focus', () => { + const element = DOMCacheGetOrSet(`${key}RuneBlessing`) + const elmRect = element.getBoundingClientRect() + Modal(focusedRuneBlessingHTML(key), elmRect.x, elmRect.y + elmRect.height / 2, { + borderColor: runes[key].runeHTMLStyle.borderColor + }) + }) + runeBlessing.addEventListener('mouseout', () => CloseModal()) + + DOMCacheGetOrSet(`${key}RuneBlessingPurchase`).addEventListener( 'click', - () => changeTalismanModifier(index + 1) + () => buyBlessingLevels(key, player.offerings) ) } - // Part 3: Blessings and Spirits - for (let index = 0; index < 5; index++) { - DOMCacheGetOrSet(`runeBlessingPurchase${index + 1}`).addEventListener( - 'click', - () => buyRuneBonusLevels('Blessings', index + 1) + for (const key of runeSpiritKeys) { + const runeSpirit = DOMCacheGetOrSet(`${key}RuneSpiritContainer`) + runeSpirit.addEventListener( + 'mousemove', + (e: MouseEvent) => { + Modal(focusedRuneSpiritHTML(key), e.clientX, e.clientY, { borderColor: runes[key].runeHTMLStyle.borderColor }) + } ) - DOMCacheGetOrSet(`runeSpiritPurchase${index + 1}`).addEventListener( + runeSpirit.addEventListener('focus', () => { + const element = DOMCacheGetOrSet(`${key}RuneSpirit`) + const elmRect = element.getBoundingClientRect() + Modal(focusedRuneSpiritHTML(key), elmRect.x, elmRect.y + elmRect.height / 2, { + borderColor: runes[key].runeHTMLStyle.borderColor + }) + }) + + runeSpirit.addEventListener('mouseout', () => CloseModal()) + + DOMCacheGetOrSet(`${key}RuneSpiritPurchase`).addEventListener( 'click', - () => buyRuneBonusLevels('Spirits', index + 1) + () => buySpiritLevels(key, player.offerings) ) } + DOMCacheGetOrSet('buyRuneBlessingInput').addEventListener('blur', () => updateRuneBlessingBuyAmount(1)) DOMCacheGetOrSet('buyRuneSpiritInput').addEventListener('blur', () => updateRuneBlessingBuyAmount(2)) - DOMCacheGetOrSet('buyAllBlessings').addEventListener('click', () => buyAllBlessings('Blessings')) - DOMCacheGetOrSet('buyAllSpirits').addEventListener('click', () => buyAllBlessings('Spirits')) + DOMCacheGetOrSet('buyAllBlessings').addEventListener('click', () => buyAllBlessingLevels(player.offerings)) + DOMCacheGetOrSet('buyAllSpirits').addEventListener('click', () => buyAllSpiritLevels(player.offerings)) // CHALLENGES TAB // Part 1: Challenges @@ -559,17 +706,33 @@ export const generateEventHandlers = () => { // RESEARCH TAB // Part 1: Researches // There are 200 researches, ideally in rewrite 200 would instead be length of research list/array - for (let index = 1; index < 200; index++) { + for (let index = 1; index <= 200; index++) { const research = DOMCacheGetOrSet(`res${index}`) - research.addEventListener('click', () => buyResearch(index)) - research.addEventListener('mouseover', () => researchDescriptions(index)) - research.addEventListener('focus', () => researchDescriptions(index)) + research.addEventListener('click', () => { + const auto = false + const hover = false + buyResearch(index, auto, hover) + if (player.autoResearchMode === 'manual' && player.autoResearchToggle) { + updateResearchAuto(index) + } + }) + research.addEventListener('mouseover', () => { + if (player.toggles[38] && player.highestSingularityCount > 0) { + const auto = false + const hover = true + buyResearch(index, auto, hover) + } + researchDescriptions(index) + }) + research.addEventListener('focus', () => { + if (player.toggles[38] && player.highestSingularityCount > 0) { + const auto = false + const hover = true + buyResearch(index, auto, hover) + } + researchDescriptions(index) + }) } - // Research 200 is special, uses more params - const research200 = DOMCacheGetOrSet('res200') - research200.addEventListener('click', () => buyResearch(200, false, 0.01)) - research200.addEventListener('mouseover', () => researchDescriptions(200, false, 0.01)) - research200.addEventListener('focus', () => researchDescriptions(200, false, 0.01)) // Part 2: QoL buttons DOMCacheGetOrSet('toggleresearchbuy').addEventListener('click', () => toggleResearchBuy()) @@ -719,110 +882,24 @@ export const generateEventHandlers = () => { DOMCacheGetOrSet('toggleAutoPlatonicUpgrades').addEventListener('click', () => autoPlatonicUpgradesToggle()) // Part 4: Hepteract Subtab - DOMCacheGetOrSet('chronosHepteract').addEventListener('mouseover', () => hepteractDescriptions('chronos')) - DOMCacheGetOrSet('hyperrealismHepteract').addEventListener('mouseover', () => hepteractDescriptions('hyperrealism')) - DOMCacheGetOrSet('quarkHepteract').addEventListener('mouseover', () => hepteractDescriptions('quark')) - DOMCacheGetOrSet('challengeHepteract').addEventListener('mouseover', () => hepteractDescriptions('challenge')) - DOMCacheGetOrSet('abyssHepteract').addEventListener('mouseover', () => hepteractDescriptions('abyss')) - DOMCacheGetOrSet('acceleratorHepteract').addEventListener('mouseover', () => hepteractDescriptions('accelerator')) - DOMCacheGetOrSet('acceleratorBoostHepteract').addEventListener( - 'mouseover', - () => hepteractDescriptions('acceleratorBoost') - ) - DOMCacheGetOrSet('multiplierHepteract').addEventListener('mouseover', () => hepteractDescriptions('multiplier')) - DOMCacheGetOrSet('chronosHepteractCraft').addEventListener('click', () => player.hepteractCrafts.chronos.craft()) - DOMCacheGetOrSet('hyperrealismHepteractCraft').addEventListener( - 'click', - () => player.hepteractCrafts.hyperrealism.craft() - ) - DOMCacheGetOrSet('quarkHepteractCraft').addEventListener('click', () => player.hepteractCrafts.quark.craft()) - DOMCacheGetOrSet('challengeHepteractCraft').addEventListener('click', () => player.hepteractCrafts.challenge.craft()) - DOMCacheGetOrSet('abyssHepteractCraft').addEventListener('click', () => player.hepteractCrafts.abyss.craft()) - DOMCacheGetOrSet('acceleratorHepteractCraft').addEventListener( - 'click', - () => player.hepteractCrafts.accelerator.craft() - ) - DOMCacheGetOrSet('acceleratorBoostHepteractCraft').addEventListener( - 'click', - () => player.hepteractCrafts.acceleratorBoost.craft() - ) - DOMCacheGetOrSet('multiplierHepteractCraft').addEventListener( - 'click', - () => player.hepteractCrafts.multiplier.craft() - ) + for (const key of hepteractKeys) { + const moused = DOMCacheGetOrSet(`${key}Hepteract`) + moused.addEventListener('mouseover', () => hepteractDescriptions(key)) + moused.addEventListener('focus', () => hepteractDescriptions(key)) - DOMCacheGetOrSet('chronosHepteractCraftMax').addEventListener( - 'click', - () => player.hepteractCrafts.chronos.craft(true) - ) - DOMCacheGetOrSet('hyperrealismHepteractCraftMax').addEventListener( - 'click', - () => player.hepteractCrafts.hyperrealism.craft(true) - ) - DOMCacheGetOrSet('quarkHepteractCraftMax').addEventListener('click', () => player.hepteractCrafts.quark.craft(true)) - DOMCacheGetOrSet('challengeHepteractCraftMax').addEventListener( - 'click', - () => player.hepteractCrafts.challenge.craft(true) - ) - DOMCacheGetOrSet('abyssHepteractCraftMax').addEventListener('click', () => player.hepteractCrafts.abyss.craft(true)) - DOMCacheGetOrSet('acceleratorHepteractCraftMax').addEventListener( - 'click', - () => player.hepteractCrafts.accelerator.craft(true) - ) - DOMCacheGetOrSet('acceleratorBoostHepteractCraftMax').addEventListener( - 'click', - () => player.hepteractCrafts.acceleratorBoost.craft(true) - ) - DOMCacheGetOrSet('multiplierHepteractCraftMax').addEventListener( - 'click', - () => player.hepteractCrafts.multiplier.craft(true) - ) + const craft = DOMCacheGetOrSet(`${key}HepteractCraft`) + craft.addEventListener('click', () => craftHepteracts(key)) - DOMCacheGetOrSet('chronosHepteractCap').addEventListener('click', () => player.hepteractCrafts.chronos.expand()) - DOMCacheGetOrSet('hyperrealismHepteractCap').addEventListener( - 'click', - () => player.hepteractCrafts.hyperrealism.expand() - ) - DOMCacheGetOrSet('quarkHepteractCap').addEventListener('click', () => player.hepteractCrafts.quark.expand()) - DOMCacheGetOrSet('challengeHepteractCap').addEventListener('click', () => player.hepteractCrafts.challenge.expand()) - DOMCacheGetOrSet('abyssHepteractCap').addEventListener('click', () => player.hepteractCrafts.abyss.expand()) - DOMCacheGetOrSet('acceleratorHepteractCap').addEventListener( - 'click', - () => player.hepteractCrafts.accelerator.expand() - ) - DOMCacheGetOrSet('acceleratorBoostHepteractCap').addEventListener( - 'click', - () => player.hepteractCrafts.acceleratorBoost.expand() - ) - DOMCacheGetOrSet('multiplierHepteractCap').addEventListener('click', () => player.hepteractCrafts.multiplier.expand()) + const craftMax = DOMCacheGetOrSet(`${key}HepteractCraftMax`) + craftMax.addEventListener('click', () => craftHepteracts(key, true)) - DOMCacheGetOrSet('chronosHepteractAuto').addEventListener( - 'click', - () => player.hepteractCrafts.chronos.toggleAutomatic() - ) - DOMCacheGetOrSet('hyperrealismHepteractAuto').addEventListener( - 'click', - () => player.hepteractCrafts.hyperrealism.toggleAutomatic() - ) - DOMCacheGetOrSet('quarkHepteractAuto').addEventListener('click', () => player.hepteractCrafts.quark.toggleAutomatic()) - DOMCacheGetOrSet('challengeHepteractAuto').addEventListener( - 'click', - () => player.hepteractCrafts.challenge.toggleAutomatic() - ) - DOMCacheGetOrSet('abyssHepteractAuto').addEventListener('click', () => player.hepteractCrafts.abyss.toggleAutomatic()) - DOMCacheGetOrSet('acceleratorHepteractAuto').addEventListener( - 'click', - () => player.hepteractCrafts.accelerator.toggleAutomatic() - ) - DOMCacheGetOrSet('acceleratorBoostHepteractAuto').addEventListener( - 'click', - () => player.hepteractCrafts.acceleratorBoost.toggleAutomatic() - ) - DOMCacheGetOrSet('multiplierHepteractAuto').addEventListener( - 'click', - () => player.hepteractCrafts.multiplier.toggleAutomatic() - ) + const cap = DOMCacheGetOrSet(`${key}HepteractCap`) + cap.addEventListener('click', () => expandHepteracts(key)) + + const auto = DOMCacheGetOrSet(`${key}HepteractAuto`) + auto.addEventListener('click', () => toggleAutomaticHepteracts(key)) + } DOMCacheGetOrSet('hepteractToQuark').addEventListener('mouseover', () => hepteractToOverfluxOrbDescription()) DOMCacheGetOrSet('hepteractToQuarkTrade').addEventListener('click', () => tradeHepteractToOverfluxOrb()) @@ -936,7 +1013,7 @@ TODO: Fix this entire tab it's utter shit DOMCacheGetOrSet('useofferingpotion').addEventListener('mouseover', () => shopDescriptions('offeringPotion')) DOMCacheGetOrSet('buyofferingpotion').addEventListener('click', () => buyShopUpgrades('offeringPotion')) // DOMCacheGetOrSet('offeringPotions').addEventListener('click', () => buyShopUpgrades("offeringPotion")) //Allow clicking of image to buy also - DOMCacheGetOrSet('useofferingpotion').addEventListener('click', () => useConsumable('offeringPotion')) + DOMCacheGetOrSet('useofferingpotion').addEventListener('click', () => useConsumablePrompt('offeringPotion')) DOMCacheGetOrSet('toggle42').addEventListener('click', () => { player.autoPotionTimer = 0 }) @@ -947,7 +1024,7 @@ TODO: Fix this entire tab it's utter shit DOMCacheGetOrSet('useobtainiumpotion').addEventListener('mouseover', () => shopDescriptions('obtainiumPotion')) DOMCacheGetOrSet('buyobtainiumpotion').addEventListener('click', () => buyShopUpgrades('obtainiumPotion')) // DOMCacheGetOrSet('obtainiumPotions').addEventListener('click', () => buyShopUpgrades("obtainiumPotion")) //Allow clicking of image to buy also - DOMCacheGetOrSet('useobtainiumpotion').addEventListener('click', () => useConsumable('obtainiumPotion')) + DOMCacheGetOrSet('useobtainiumpotion').addEventListener('click', () => useConsumablePrompt('obtainiumPotion')) DOMCacheGetOrSet('toggle43').addEventListener('click', () => { player.autoPotionTimerObtainium = 0 }) @@ -967,21 +1044,50 @@ TODO: Fix this entire tab it's utter shit } DOMCacheGetOrSet('buySingularityQuarksButton').addEventListener('click', () => buyGoldenQuarks()) // SINGULARITY TAB - const singularityUpgrades = Object.keys( - player.singularityUpgrades - ) as (keyof Player['singularityUpgrades'])[] - for (const key of singularityUpgrades) { - if (key === 'offeringAutomatic') { - continue + const GQUpgrades = Object.keys(goldenQuarkUpgrades) as SingularityDataKeys[] + for (const key of GQUpgrades) { + if (!isMobile) { + if (key === 'offeringAutomatic') { + continue + } + DOMCacheGetOrSet(key).addEventListener( + 'mousemove', + (e: MouseEvent) => Modal(upgradeGQToString(key), e.clientX, e.clientY, { borderColor: 'gold' }) + ) + DOMCacheGetOrSet(key).addEventListener( + 'focus', + () => { + const element = DOMCacheGetOrSet(key) + const elmRect = element.getBoundingClientRect() + Modal(upgradeGQToString(key), elmRect.x, elmRect.y + elmRect.height / 2, { borderColor: 'gold' }) + } + ) + + DOMCacheGetOrSet(key).addEventListener( + 'mouseout', + () => CloseModal() + ) + DOMCacheGetOrSet(key).addEventListener( + 'blur', + () => CloseModal() + ) + + DOMCacheGetOrSet(key).addEventListener( + 'click', + (event) => { + buyGQUpgradeLevel(key, event) + Modal(upgradeGQToString(key), event.clientX, event.clientY, { borderColor: 'gold' }, true) + } + ) + } else { + if (key === 'offeringAutomatic') { + continue + } + DOMCacheGetOrSet(key).addEventListener( + 'click', + () => updateMobileGQHTML(key) + ) } - DOMCacheGetOrSet(`${String(key)}`).addEventListener( - 'mouseover', - () => player.singularityUpgrades[`${String(key)}`].updateUpgradeHTML() - ) - DOMCacheGetOrSet(`${String(key)}`).addEventListener( - 'click', - (event) => player.singularityUpgrades[`${String(key)}`].buyLevel(event) - ) } DOMCacheGetOrSet('actualSingularityUpgradeContainer').addEventListener( 'mouseover', @@ -1020,18 +1126,44 @@ TODO: Fix this entire tab it's utter shit } // Octeract Upgrades - const octeractUpgrades = Object.keys( - player.octeractUpgrades - ) as (keyof Player['octeractUpgrades'])[] - for (const key of octeractUpgrades) { - DOMCacheGetOrSet(`${String(key)}`).addEventListener( - 'mouseover', - () => player.octeractUpgrades[`${String(key)}`].updateUpgradeHTML() - ) - DOMCacheGetOrSet(`${String(key)}`).addEventListener( - 'click', - (event) => player.octeractUpgrades[`${String(key)}`].buyLevel(event) - ) + const octUpgrade = Object.keys(octeractUpgrades) as OcteractDataKeys[] + for (const key of octUpgrade) { + if (!isMobile) { + DOMCacheGetOrSet(key).addEventListener( + 'mousemove', + (e: MouseEvent) => Modal(upgradeOcteractToString(key), e.clientX, e.clientY, { borderColor: 'lightseagreen' }) + ) + DOMCacheGetOrSet(key).addEventListener( + 'focus', + () => { + const element = DOMCacheGetOrSet(key) + const elmRect = element.getBoundingClientRect() + Modal(upgradeOcteractToString(key), elmRect.x, elmRect.y + elmRect.height / 2, { + borderColor: 'lightseagreen' + }) + } + ) + DOMCacheGetOrSet(key).addEventListener( + 'mouseout', + () => CloseModal() + ) + DOMCacheGetOrSet(key).addEventListener( + 'blur', + () => CloseModal() + ) + DOMCacheGetOrSet(key).addEventListener( + 'click', + (event) => { + buyOcteractUpgradeLevel(key, event) + Modal(upgradeOcteractToString(key), event.clientX, event.clientY, { borderColor: 'lightseagreen' }, true) + } + ) + } else { + DOMCacheGetOrSet(key).addEventListener( + 'click', + () => updateMobileOcteractHTML(key) + ) + } } DOMCacheGetOrSet('octeractUpgradeContainer').addEventListener( @@ -1044,43 +1176,70 @@ TODO: Fix this entire tab it's utter shit ) // EXALT - const singularityChallenges = Object.keys( - player.singularityChallenges - ) as (keyof Player['singularityChallenges'])[] + const singularityChallenges = Object.keys(player.singularityChallenges) as SingularityChallengeDataKeys[] for (const key of singularityChallenges) { - DOMCacheGetOrSet(`${String(key)}`).addEventListener( + DOMCacheGetOrSet(key).addEventListener( 'mouseover', - () => player.singularityChallenges[`${String(key)}`].updateChallengeHTML() + () => player.singularityChallenges[key].updateChallengeHTML() ) - DOMCacheGetOrSet(`${String(key)}`).addEventListener( + DOMCacheGetOrSet(key).addEventListener( 'click', - () => player.singularityChallenges[`${String(key)}`].challengeEntryHandler() + () => player.singularityChallenges[key].challengeEntryHandler() ) } // BLUEBERRY UPGRADES const blueberryUpgrades = Object.keys( - player.blueberryUpgrades - ) as (keyof Player['blueberryUpgrades'])[] + ambrosiaUpgrades + ) as AmbrosiaUpgradeNames[] for (const key of blueberryUpgrades) { - const k = key as blueberryUpgradeNames - DOMCacheGetOrSet(`${String(key)}`).addEventListener( - 'mouseover', - () => { - player.blueberryUpgrades[`${String(key)}`].updateUpgradeHTML() - highlightPrerequisites(k) - } - ) - DOMCacheGetOrSet(`${String(key)}`).addEventListener( - 'mouseout', - () => { - resetHighlights() - } - ) - DOMCacheGetOrSet(`${String(key)}`).addEventListener( - 'click', - (event) => player.blueberryUpgrades[`${String(key)}`].buyLevel(event) - ) + if (!isMobile) { + DOMCacheGetOrSet(key).addEventListener( + 'mousemove', + (e: MouseEvent) => { + Modal(ambrosiaUpgradeToString(key), e.clientX, e.clientY, { borderColor: 'blue' }) + highlightPrerequisites(key) + } + ) + DOMCacheGetOrSet(key).addEventListener( + 'mouseout', + () => { + CloseModal() + resetHighlights() + } + ) + DOMCacheGetOrSet(key).addEventListener( + 'focus', + () => { + const element = DOMCacheGetOrSet(key) + const elmRect = element.getBoundingClientRect() + Modal(ambrosiaUpgradeToString(key), elmRect.x, elmRect.y + elmRect.height / 2, { borderColor: 'blue' }) + highlightPrerequisites(key) + } + ) + DOMCacheGetOrSet(key).addEventListener( + 'blur', + () => { + CloseModal() + resetHighlights() + } + ) + DOMCacheGetOrSet(key).addEventListener( + 'click', + (event) => { + buyAmbrosiaUpgradeLevel(key, event) + Modal(ambrosiaUpgradeToString(key), event.clientX, event.clientY, { borderColor: 'blue' }, true) + } + ) + } else { + DOMCacheGetOrSet(key).addEventListener( + 'click', + () => { + updateMobileAmbrosiaHTML(key) + highlightPrerequisites(key) + } + ) + } } // BLUEBERRY LOADOUTS @@ -1138,14 +1297,40 @@ TODO: Fix this entire tab it's utter shit ) as (keyof Player['redAmbrosiaUpgrades'])[] for (const key of redAmbrosiaUpgrades) { const capitalizedName = key.charAt(0).toUpperCase() + key.slice(1) - DOMCacheGetOrSet(`redAmbrosia${capitalizedName}`).addEventListener( - 'mouseover', - () => getRedAmbrosiaUpgrade(key).updateUpgradeHTML() - ) - DOMCacheGetOrSet(`redAmbrosia${capitalizedName}`).addEventListener( - 'click', - (event) => getRedAmbrosiaUpgrade(key).buyLevel(event) - ) + if (!isMobile) { + DOMCacheGetOrSet(`redAmbrosia${capitalizedName}`).addEventListener( + 'mousemove', + (e: MouseEvent) => Modal(redAmbrosiaUpgradeToString(key), e.clientX, e.clientY, { borderColor: 'red' }) + ) + DOMCacheGetOrSet(`redAmbrosia${capitalizedName}`).addEventListener( + 'mouseout', + () => CloseModal() + ) + DOMCacheGetOrSet(`redAmbrosia${capitalizedName}`).addEventListener( + 'focus', + () => { + const element = DOMCacheGetOrSet(`redAmbrosia${capitalizedName}`) + const elmRect = element.getBoundingClientRect() + Modal(redAmbrosiaUpgradeToString(key), elmRect.x, elmRect.y + elmRect.height / 2, { borderColor: 'red' }) + } + ) + DOMCacheGetOrSet(`redAmbrosia${capitalizedName}`).addEventListener( + 'blur', + () => CloseModal() + ) + DOMCacheGetOrSet(`redAmbrosia${capitalizedName}`).addEventListener( + 'click', + (event) => { + buyRedAmbrosiaUpgradeLevel(key, event) + Modal(redAmbrosiaUpgradeToString(key), event.clientX, event.clientY, { borderColor: 'red' }, true) + } + ) + } else { + DOMCacheGetOrSet(`redAmbrosia${capitalizedName}`).addEventListener( + 'click', + () => updateMobileRedAmbrosiaHTML(key) + ) + } } // Toggle subtabs of Singularity tab diff --git a/src/Helper.ts b/src/Helper.ts index 9e695418f..eb54c52dc 100644 --- a/src/Helper.ts +++ b/src/Helper.ts @@ -1,12 +1,11 @@ +import Decimal from 'break_infinity.js' import { sacrificeAnts } from './Ants' -import { buyAllBlessings } from './Buy' import { calculateAmbrosiaGenerationSpeed, calculateAmbrosiaLuck, calculateAscensionSpeedMult, calculateGlobalSpeedMult, calculateGoldenQuarks, - calculateMaxRunes, calculateOcteractMultiplier, calculateRedAmbrosiaGenerationSpeed, calculateRedAmbrosiaLuck, @@ -14,11 +13,15 @@ import { calculateRequiredRedAmbrosiaTime, calculateResearchAutomaticObtainium } from './Calculate' +import { getOcteractUpgradeEffect } from './Octeracts' import { quarkHandler } from './Quark' -import { getRedAmbrosiaUpgrade } from './RedAmbrosiaUpgrades' +import { getRedAmbrosiaUpgradeEffects } from './RedAmbrosiaUpgrades' import { Seed, seededRandom } from './RNG' -import { checkMaxRunes, redeemShards, unlockedRune } from './Runes' +import { buyAllBlessingLevels } from './RuneBlessings' +import { getNumberUnlockedRunes, indexToRune, type RuneKeys, runes, sacrificeOfferings } from './Runes' +import { buyAllSpiritLevels } from './RuneSpirits' import { useConsumable } from './Shop' +import { getGQUpgradeEffect } from './singularity' import { player } from './Synergism' import { Tabs } from './Tabs' import { buyAllTalismanResources } from './Talismans' @@ -44,7 +47,7 @@ type TimerInput = * @param time */ export const addTimers = (input: TimerInput, time = 0) => { - const globalTimeMultiplier = player.singularityUpgrades.halfMind.getEffect().bonus + const globalTimeMultiplier = getGQUpgradeEffect('halfMind') ? 10 : calculateGlobalSpeedMult() @@ -74,8 +77,7 @@ export const addTimers = (input: TimerInput, time = 0) => { } case 'ascension': { // Anything in here is affected by add code - const ascensionSpeedMulti = player.singularityUpgrades.oneMind.getEffect() - .bonus + const ascensionSpeedMulti = getGQUpgradeEffect('oneMind') ? 10 : calculateAscensionSpeedMult() player.ascensionCounter += time * timeMultiplier * ascensionSpeedMulti @@ -104,7 +106,7 @@ export const addTimers = (input: TimerInput, time = 0) => { break } case 'goldenQuarks': { - if (+player.singularityUpgrades.goldenQuarks3.getEffect().bonus === 0) { + if (getGQUpgradeEffect('goldenQuarks3') === 0) { return } else { player.goldenQuarksTimer += time * timeMultiplier @@ -115,7 +117,7 @@ export const addTimers = (input: TimerInput, time = 0) => { break } case 'octeracts': { - if (!player.singularityUpgrades.octeractUnlock.getEffect().bonus) { + if (!getGQUpgradeEffect('octeractUnlock')) { return } else { player.octeractTimer += time * timeMultiplier @@ -162,7 +164,7 @@ export const addTimers = (input: TimerInput, time = 0) => { player.autoPotionTimerObtainium += time * timeMultiplier const timerThreshold = (180 * Math.pow(1.03, -player.highestSingularityCount)) - / +player.octeractUpgrades.octeractAutoPotionSpeed.getEffect().bonus + / getOcteractUpgradeEffect('octeractAutoPotionSpeed') const effectiveOfferingThreshold = toggleOfferingOn ? Math.min(1, timerThreshold) / 20 @@ -176,7 +178,7 @@ export const addTimers = (input: TimerInput, time = 0) => { - (player.autoPotionTimer % effectiveOfferingThreshold)) / effectiveOfferingThreshold player.autoPotionTimer %= effectiveOfferingThreshold - void useConsumable( + useConsumable( 'offeringPotion', true, amountOfPotions, @@ -189,7 +191,7 @@ export const addTimers = (input: TimerInput, time = 0) => { - (player.autoPotionTimerObtainium % effectiveObtainiumThreshold)) / effectiveObtainiumThreshold player.autoPotionTimerObtainium %= effectiveObtainiumThreshold - void useConsumable( + useConsumable( 'obtainiumPotion', true, amountOfPotions, @@ -218,11 +220,6 @@ export const addTimers = (input: TimerInput, time = 0) => { let timeToAmbrosia = calculateRequiredBlueberryTime() - const maxAccelMultiplier = (1 / 2) - + (3 / 5 - 1 / 2) * +(player.singularityChallenges.noAmbrosiaUpgrades.completions >= 15) - + (2 / 3 - 3 / 5) * +(player.singularityChallenges.noAmbrosiaUpgrades.completions >= 19) - + (3 / 4 - 2 / 3) * +(player.singularityChallenges.noAmbrosiaUpgrades.completions >= 20) - while (player.blueberryTime >= timeToAmbrosia) { const RNG = seededRandom(Seed.Ambrosia) const ambrosiaMult = Math.floor(ambrosiaLuck / 100) @@ -235,13 +232,6 @@ export const addTimers = (input: TimerInput, time = 0) => { player.blueberryTime -= timeToAmbrosia timeToAmbrosia = calculateRequiredBlueberryTime() - const secondsToNextAmbrosia = timeToAmbrosia / calculateAmbrosiaGenerationSpeed() - - G.ambrosiaTimer += Math.min( - secondsToNextAmbrosia * maxAccelMultiplier, - ambrosiaToGain * 0.2 * player.shopUpgrades.shopAmbrosiaAccelerator - ) - timeToAmbrosia = calculateRequiredBlueberryTime() } visualUpdateAmbrosia() @@ -262,7 +252,7 @@ export const addTimers = (input: TimerInput, time = 0) => { let timeToRedAmbrosia = calculateRequiredRedAmbrosiaTime() let ambrosiaTimeToGrant = 0 - const timeCoeff = getRedAmbrosiaUpgrade('redAmbrosiaAccelerator').bonus.ambrosiaTimePerRedAmbrosia + const timeCoeff = getRedAmbrosiaUpgradeEffects('redAmbrosiaAccelerator').ambrosiaTimePerRedAmbrosia while (player.redAmbrosiaTime >= timeToRedAmbrosia) { const redAmbrosiaLuck = calculateRedAmbrosiaLuck() @@ -307,12 +297,19 @@ export const automaticTools = (input: AutoToolInput, time: number) => { break } - const obtainiumGain = calculateResearchAutomaticObtainium(time) + let obtainiumGain = calculateResearchAutomaticObtainium(time) + if ( + player.singularityChallenges.taxmanLastStand.enabled + && player.singularityChallenges.taxmanLastStand.completions >= 2 + ) { + obtainiumGain = Decimal.min( + obtainiumGain, + player.obtainium.times(100).plus(1) + ) + } + // Add Obtainium - player.researchPoints = Math.min( - 1e300, - player.researchPoints + obtainiumGain - ) + player.obtainium = player.obtainium.add(obtainiumGain) // Update visual displays if appropriate if (G.currentTab === Tabs.Research) { visualUpdateResearch() @@ -324,10 +321,7 @@ export const automaticTools = (input: AutoToolInput, time: number) => { // As well as cube upgrade 1x2 (2). G.autoOfferingCounter += time // Any time this exceeds 1 it adds an offering - player.runeshards = Math.min( - 1e300, - player.runeshards + Math.floor(G.autoOfferingCounter) - ) + player.offerings = player.offerings.add(Math.floor(G.autoOfferingCounter)) G.autoOfferingCounter %= 1 break case 'runeSacrifice': @@ -335,19 +329,15 @@ export const automaticTools = (input: AutoToolInput, time: number) => { player.sacrificeTimer += time if ( player.sacrificeTimer >= 1 - && isFinite(player.runeshards) - && player.runeshards > 0 + && player.offerings.gt(0) ) { // Automatic purchase of Blessings if (player.highestSingularityCount >= 15) { - let ratio = 4 if (player.toggles[36]) { - buyAllBlessings('Blessings', 100 / ratio, true) - ratio-- + buyAllBlessingLevels(player.offerings.div(2)) } if (player.toggles[37]) { - buyAllBlessings('Spirits', 100 / ratio, true) - ratio-- + buyAllSpiritLevels(player.offerings.div(2)) } } if ( @@ -360,36 +350,37 @@ export const automaticTools = (input: AutoToolInput, time: number) => { // If you bought cube upgrade 2x10 then it sacrifices to all runes equally if (player.cubeUpgrades[20] === 1) { - const maxi = player.highestSingularityCount >= 50 - ? 7 - : player.highestSingularityCount >= 30 - ? 6 - : 5 - const notMaxed = maxi - checkMaxRunes(maxi) - if (notMaxed > 0) { - const baseAmount = Math.floor(player.runeshards / notMaxed / 2) - for (let i = 0; i < maxi; i++) { - if ( - !( - !unlockedRune(i + 1) - || player.runelevels[i] >= calculateMaxRunes(i + 1) - ) - ) { - redeemShards(i + 1, true, baseAmount) - } - } + let numUnlocked = getNumberUnlockedRunes() + + // Do not purchase AoAG under s50 + if (player.highestSingularityCount < 50 && runes.antiquities.isUnlocked()) { + numUnlocked -= 1 + } + + // Do not purchase IA under s30 + if (player.highestSingularityCount < 30 && runes.infiniteAscent.isUnlocked()) { + numUnlocked -= 1 + } + + const offeringPerRune = Decimal.floor(player.offerings.mul(0.5).div(numUnlocked)) + + for (const key of Object.keys(player.runes)) { + const runeKey = key as RuneKeys + sacrificeOfferings(runeKey, offeringPerRune, true) } } else { // If you did not buy cube upgrade 2x10 it sacrifices to selected rune. const rune = player.autoSacrifice - redeemShards(rune, true, 0) + if (rune !== 0) { + sacrificeOfferings(indexToRune[rune], player.offerings, true) + } } // Modulo used in event of a large delta time (this could happen for a number of reasons) player.sacrificeTimer %= 1 } break case 'antSacrifice': { - const globalDelta = player.singularityUpgrades.halfMind.getEffect().bonus ? 10 : calculateGlobalSpeedMult() + const globalDelta = getGQUpgradeEffect('halfMind') ? 10 : calculateGlobalSpeedMult() player.antSacrificeTimer += time * globalDelta player.antSacrificeTimerReal += time diff --git a/src/Hepteracts.ts b/src/Hepteracts.ts index 7aae8f81f..e4a301e60 100644 --- a/src/Hepteracts.ts +++ b/src/Hepteracts.ts @@ -1,5 +1,4 @@ import Decimal from 'break_infinity.js' -import type { StringMap } from 'i18next' import i18next from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' import { @@ -10,492 +9,610 @@ import { forcedDailyReset } from './Calculate' import { Cube } from './CubeExperimental' -import { calculateSingularityDebuff } from './singularity' -import { format, player } from './Synergism' +import { getOcteractUpgradeEffect } from './Octeracts' +import { resetTiers } from './Reset' +import { calculateSingularityDebuff, getGQUpgradeEffect } from './singularity' +import { format, formatAsPercentIncrease, player } from './Synergism' import type { Player } from './types/Synergism' import { Alert, Confirm, Prompt } from './UpdateHTML' +import { isDecimal } from './Utility' +import { Globals } from './Variables' + +type HepteractTypeMap = { + chronos: { ascensionSpeed: number } + hyperrealism: { hypercubeMultiplier: number } + quark: { quarkMultiplier: number } + challenge: { c15ScoreMultiplier: number } + abyss: { salvage: number } + accelerator: { accelerators: number; acceleratorMultiplier: number } + acceleratorBoost: { acceleratorBoostMultiplier: number } + multiplier: { multiplier: number; multiplierMultiplier: number } +} + +export type HepteractKeys = keyof HepteractTypeMap + +export interface HepteractValues { + BAL: number + TIMES_CAP_EXTENDED: number + AUTO: boolean +} -export interface IHepteractCraft { +export interface HepteractData extends HepteractValues { BASE_CAP: number HEPTERACT_CONVERSION: number OTHER_CONVERSIONS: Record - HTML_STRING: string - AUTO?: boolean - UNLOCKED?: boolean - BAL?: number - CAP?: number - DISCOUNT?: number + UNLOCKED: () => boolean + EFFECTS: (hept: number) => HepteractTypeMap[K] + EFFECTSDESCRIPTION: (hept: number) => string + DESCRIPTION: () => string + RESET_TIER: keyof typeof resetTiers + LIMIT: number + DR: number + DR_INCREASE: () => number } -export const hepteractTypeList = [ - 'chronos', - 'hyperrealism', - 'quark', - 'challenge', - 'abyss', - 'accelerator', - 'acceleratorBoost', - 'multiplier' -] as const - -export type hepteractTypes = typeof hepteractTypeList[number] - -export class HepteractCraft { - /** - * Craft is unlocked or not (Default is locked) - */ - UNLOCKED = false - - /** - * Current Inventory (amount) of craft you possess - */ - BAL = 0 - - /** - * Maximum Inventory (amount) of craft you can hold - * base_cap is the smallest capacity for such item. - */ - CAP = 0 - BASE_CAP = 0 - - /** - * Conversion rate of hepteract to synthesized items - */ - HEPTERACT_CONVERSION = 0 - - /** - * Automatic crafting toggle. If on, allows crafting to be done automatically upon ascension. - */ - AUTO = false - - /** - * Conversion rate of additional items - * This is in the form of keys being player variables, - * values being the amount player has. - */ - OTHER_CONVERSIONS: { - [key in keyof Player]?: number - } - - /** - * Discount Factor (number from [0, 1)) - */ - DISCOUNT = 0 - - /** - * String Prefix used for HTML DOM manipulation - */ - HTML_STRING: string - - constructor (data: IHepteractCraft) { - this.BASE_CAP = data.BASE_CAP - this.HEPTERACT_CONVERSION = data.HEPTERACT_CONVERSION - this.OTHER_CONVERSIONS = data.OTHER_CONVERSIONS - this.HTML_STRING = data.HTML_STRING - this.UNLOCKED = data.UNLOCKED ?? false // This would basically always be true if this parameter is provided - this.BAL = data.BAL ?? 0 - this.CAP = data.CAP ?? this.BASE_CAP // This sets cap either as previous value or keeps it to default. - this.DISCOUNT = data.DISCOUNT ?? 0 - this.AUTO = data.AUTO ?? false - - void this.toggleAutomatic(this.AUTO) - } - - // Unlock a synthesizer craft - unlock = (hepteractName: string): this | Promise => { - if (this.UNLOCKED) { - return this - } - this.UNLOCKED = true - if (player.highestSingularityCount < 5) { - return Alert(i18next.t('hepteracts.unlockedCraft', { x: hepteractName })) - } else { - return this - } - } - - computeActualCap = (): number => { - let multiplier = 1 - multiplier *= (player.singularityChallenges.limitedAscensions.rewards.hepteractCap) ? 2 : 1 - - return this.CAP * multiplier - } - - // Add to balance through crafting. - craft = async (max = false): Promise => { - let craftAmount = null - const heptCap = this.computeActualCap() - const craftCostMulti = calculateSingularityDebuff('Hepteract Costs') - // If craft is unlocked, we return object - if (!this.UNLOCKED) { - return Alert(i18next.t('hepteracts.notUnlocked')) - } +export const defaultHepteractValues: HepteractValues = { + BAL: 0, + TIMES_CAP_EXTENDED: 0, + AUTO: false +} - if (heptCap - this.BAL <= 0) { - if (player.toggles[35]) { - return Alert(i18next.t('hepteracts.reachedCapacity', { x: format(heptCap, 0, true) })) +export const hepteracts: { [K in HepteractKeys]: HepteractData } = { + chronos: { + BAL: 0, + TIMES_CAP_EXTENDED: 0, + AUTO: false, + BASE_CAP: 1000, + HEPTERACT_CONVERSION: 1e4, + OTHER_CONVERSIONS: { obtainium: 1e115 }, + UNLOCKED: () => true, + EFFECTS: (hept) => { + return { + ascensionSpeed: 1 + 6 * hept / 10000 + } + }, + EFFECTSDESCRIPTION: (hept) => { + const effects = hepteracts.chronos.EFFECTS(hept) + return i18next.t('wowCubes.hepteractForge.descriptions.chronos.currentEffect', { + x: formatAsPercentIncrease(effects.ascensionSpeed, 2) + }) + }, + DESCRIPTION: () => i18next.t('wowCubes.hepteractForge.descriptions.chronos.effect'), + RESET_TIER: 'singularity', + LIMIT: 1000, + DR: 1 / 6, + DR_INCREASE: () => player.platonicUpgrades[19] / 750 + }, + hyperrealism: { + BAL: 0, + TIMES_CAP_EXTENDED: 0, + AUTO: false, + BASE_CAP: 1000, + HEPTERACT_CONVERSION: 1e4, + OTHER_CONVERSIONS: { offerings: 1e80 }, + UNLOCKED: () => true, + EFFECTS: (hept) => { + return { + hypercubeMultiplier: 1 + 6 * hept / 10000 } + }, + EFFECTSDESCRIPTION: (hept) => { + const effects = hepteracts.hyperrealism.EFFECTS(hept) + return i18next.t('wowCubes.hepteractForge.descriptions.hyperrealism.currentEffect', { + x: formatAsPercentIncrease(effects.hypercubeMultiplier, 2) + }) + }, + DESCRIPTION: () => i18next.t('wowCubes.hepteractForge.descriptions.hyperrealism.effect'), + RESET_TIER: 'singularity', + LIMIT: 1000, + DR: 1 / 3, + DR_INCREASE: () => 0 + }, + quark: { + BAL: 0, + TIMES_CAP_EXTENDED: 0, + AUTO: false, + BASE_CAP: 1000, + HEPTERACT_CONVERSION: 1e4, + OTHER_CONVERSIONS: { worlds: 100 }, + UNLOCKED: () => true, + EFFECTS: (hept) => { + const exponent = hepteracts.quark.DR + hepteracts.quark.DR_INCREASE() + if (hept <= hepteracts.quark.LIMIT) { + return { + quarkMultiplier: Math.pow(1 + 5 * hept / 10000, exponent) + } + } + return { + quarkMultiplier: Math.pow(1.5 + 0.2 * Math.log2(hept / 1000), exponent) + } + }, + EFFECTSDESCRIPTION: (hept) => { + const effects = hepteracts.quark.EFFECTS(hept) + return i18next.t('wowCubes.hepteractForge.descriptions.quark.currentEffect', { + x: formatAsPercentIncrease(effects.quarkMultiplier, 2) + }) + }, + DESCRIPTION: () => i18next.t('wowCubes.hepteractForge.descriptions.quark.effect'), + RESET_TIER: 'never', + LIMIT: 1000, + DR: 1, + DR_INCREASE: () => { + return getGQUpgradeEffect('singQuarkHepteract') + + getGQUpgradeEffect('singQuarkHepteract2') + + getGQUpgradeEffect('singQuarkHepteract3') + + getOcteractUpgradeEffect('octeractImprovedQuarkHept') + + player.shopUpgrades.improveQuarkHept / 100 + + player.shopUpgrades.improveQuarkHept2 / 100 + + player.shopUpgrades.improveQuarkHept3 / 100 + + player.shopUpgrades.improveQuarkHept4 / 100 + + player.shopUpgrades.improveQuarkHept5 / 100 } + }, + challenge: { + BAL: 0, + TIMES_CAP_EXTENDED: 0, + AUTO: false, + BASE_CAP: 1000, + HEPTERACT_CONVERSION: 5e4, + OTHER_CONVERSIONS: { wowPlatonicCubes: 1e11, wowCubes: 1e22 }, + UNLOCKED: () => { + const condition = Globals.challenge15Rewards.challengeHepteractUnlocked.value + return Boolean(condition) + }, + EFFECTS: (hept) => { + return { + c15ScoreMultiplier: 1 + 5 * hept / 10000 + } + }, + EFFECTSDESCRIPTION: (hept) => { + const effects = hepteracts.challenge.EFFECTS(hept) + return i18next.t('wowCubes.hepteractForge.descriptions.challenge.currentEffect', { + x: formatAsPercentIncrease(effects.c15ScoreMultiplier, 2) + }) + }, + DESCRIPTION: () => i18next.t('wowCubes.hepteractForge.descriptions.challenge.effect'), + RESET_TIER: 'singularity', + LIMIT: 1000, + DR: 1 / 6, + DR_INCREASE: () => 0 + }, + abyss: { + BAL: 0, + TIMES_CAP_EXTENDED: 0, + AUTO: false, + BASE_CAP: 1, + HEPTERACT_CONVERSION: 1e8, + OTHER_CONVERSIONS: { wowCubes: 69 }, + UNLOCKED: () => { + const condition = Globals.challenge15Rewards.abyssHepteractUnlocked.value + return Boolean(condition) + }, + EFFECTS: (hept) => { + return { + salvage: 0.1 * Math.floor(10 * Math.log2(Math.max(1, hept * 2))) + } + }, + EFFECTSDESCRIPTION: (hept) => { + const effects = hepteracts.abyss.EFFECTS(hept) + return i18next.t('wowCubes.hepteractForge.descriptions.abyss.currentEffect', { + x: format(effects.salvage, 1, true) + }) + }, + DESCRIPTION: () => i18next.t('wowCubes.hepteractForge.descriptions.abyss.effect'), + RESET_TIER: 'singularity', + LIMIT: 1, + DR: 1, + DR_INCREASE: () => 0 + }, + accelerator: { + BAL: 0, + TIMES_CAP_EXTENDED: 0, + AUTO: false, + BASE_CAP: 1000, + HEPTERACT_CONVERSION: 1e5, + OTHER_CONVERSIONS: { wowTesseracts: 1e14 }, + UNLOCKED: () => { + const condition = Globals.challenge15Rewards.acceleratorHepteractUnlocked.value + return Boolean(condition) + }, + EFFECTS: (hept) => { + return { + accelerators: 2000 * hept, + acceleratorMultiplier: 1 + 3 * hept / 10000 + } + }, + EFFECTSDESCRIPTION: (hept) => { + const effects = hepteracts.accelerator.EFFECTS(hept) + return i18next.t('wowCubes.hepteractForge.descriptions.accelerator.currentEffect', { + x: format(effects.accelerators, 0, true), + y: formatAsPercentIncrease(effects.acceleratorMultiplier, 2) + }) + }, + DESCRIPTION: () => i18next.t('wowCubes.hepteractForge.descriptions.accelerator.effect'), + RESET_TIER: 'singularity', + LIMIT: 1000, + DR: 1 / 5, + DR_INCREASE: () => 0 + }, + acceleratorBoost: { + BAL: 0, + TIMES_CAP_EXTENDED: 0, + AUTO: false, + BASE_CAP: 1000, + HEPTERACT_CONVERSION: 2e5, + OTHER_CONVERSIONS: { wowHypercubes: 1e10 }, + UNLOCKED: () => { + const condition = Globals.challenge15Rewards.acceleratorBoostHepteractUnlocked.value + return Boolean(condition) + }, + EFFECTS: (hept) => { + return { + acceleratorBoostMultiplier: 1 + hept / 1000 + } + }, + EFFECTSDESCRIPTION: (hept) => { + const effects = hepteracts.acceleratorBoost.EFFECTS(hept) + return i18next.t('wowCubes.hepteractForge.descriptions.acceleratorBoost.currentEffect', { + x: format(effects.acceleratorBoostMultiplier, 2, true) + }) + }, + DESCRIPTION: () => i18next.t('wowCubes.hepteractForge.descriptions.acceleratorBoost.effect'), + RESET_TIER: 'singularity', + LIMIT: 1000, + DR: 1 / 5, + DR_INCREASE: () => 0 + }, + multiplier: { + BAL: 0, + TIMES_CAP_EXTENDED: 0, + AUTO: false, + BASE_CAP: 1000, + HEPTERACT_CONVERSION: 3e5, + OTHER_CONVERSIONS: { obtainium: 1e130 }, + UNLOCKED: () => { + const condition = Globals.challenge15Rewards.multiplierHepteractUnlocked.value + return Boolean(condition) + }, + EFFECTS: (hept) => { + return { + multiplier: 1000 * hept, + multiplierMultiplier: 1 + 3 * hept / 10000 + } + }, + EFFECTSDESCRIPTION: (hept) => { + const effects = hepteracts.multiplier.EFFECTS(hept) + return i18next.t('wowCubes.hepteractForge.descriptions.multiplier.currentEffect', { + x: format(effects.multiplier, 0, true), + y: formatAsPercentIncrease(effects.multiplierMultiplier, 2) + }) + }, + DESCRIPTION: () => i18next.t('wowCubes.hepteractForge.descriptions.multiplier.effect'), + RESET_TIER: 'singularity', + LIMIT: 1000, + DR: 1 / 5, + DR_INCREASE: () => 0 + } +} - if (isNaN(player.wowAbyssals) || !isFinite(player.wowAbyssals) || player.wowAbyssals < 0) { - player.wowAbyssals = 0 - } +export const hepteractKeys = Object.keys(hepteracts) as HepteractKeys[] - // Calculate the largest craft amount possible, with an upper limit being craftAmount - const hepteractLimit = Math.floor( - (player.wowAbyssals / (this.HEPTERACT_CONVERSION * craftCostMulti)) * 1 / (1 - this.DISCOUNT) - ) +export const getHepteractEffects = (hept: K): HepteractTypeMap[K] => { + const heptAmount = hepteractEffective(hept) + return hepteracts[hept].EFFECTS(heptAmount) +} - // Create an array of how many we can craft using our conversion limits for additional items - const itemLimits: number[] = [] - for (const item in this.OTHER_CONVERSIONS) { - // The type of player[item] is number | Decimal | Cube. - if (item === 'worlds') { - itemLimits.push( - Math.floor((player[item as keyof Player] as number) / (this.OTHER_CONVERSIONS[item as keyof Player] ?? 1)) * 1 - / (1 - this.DISCOUNT) - ) - } else { - itemLimits.push( - Math.floor( - (player[item as keyof Player] as number) / (craftCostMulti * this.OTHER_CONVERSIONS[item as keyof Player]!) - ) * 1 / (1 - this.DISCOUNT) - ) - } - } +export const getHepteractCap = (hept: HepteractKeys): number => { + return Math.pow(2, hepteracts[hept].TIMES_CAP_EXTENDED) * hepteracts[hept].BASE_CAP +} - // Get the smallest of the array we created - const smallestItemLimit = Math.min(...itemLimits) +export const getFinalHepteractCap = (hept: HepteractKeys): number => { + const specialMultiplier = player.singularityChallenges.limitedAscensions.rewards.hepteractCap ? 2 : 1 + return getHepteractCap(hept) * specialMultiplier +} - let amountToCraft = Math.min(smallestItemLimit, hepteractLimit, heptCap, heptCap - this.BAL) +export const craftHepteracts = async (hept: HepteractKeys, max = false) => { + let craftAmount = null + const heptCap = getFinalHepteractCap(hept) + const craftCostMulti = calculateSingularityDebuff('Hepteract Costs') + // If craft is unlocked, we return object + if (!hepteracts[hept].UNLOCKED()) { + return Alert(i18next.t('hepteracts.notUnlocked')) + } - // Return if the material is not a calculable number - if (isNaN(amountToCraft) || !isFinite(amountToCraft)) { - return Alert(i18next.t('hepteracts.executionFailed')) + if (heptCap - hepteracts[hept].BAL <= 0) { + if (player.toggles[35]) { + return Alert(i18next.t('hepteracts.reachedCapacity', { x: format(heptCap, 0, true) })) } + } - // Prompt used here. Thank you Khafra for the already made code! -Platonic - if (!max) { - const craftingPrompt = await Prompt(i18next.t('hepteracts.craft', { - x: format(amountToCraft, 0, true), - y: Math.floor(amountToCraft / heptCap * 10000) / 100 - })) + if (isNaN(player.wowAbyssals) || !isFinite(player.wowAbyssals) || player.wowAbyssals < 0) { + player.wowAbyssals = 0 + } - if (craftingPrompt === null) { // Number(null) is 0. Yeah.. - if (player.toggles[35]) { - return Alert(i18next.t('hepteracts.cancelled')) - } else { - return // If no return, then it will just give another message - } - } - craftAmount = Number(craftingPrompt) + // Calculate the largest craft amount possible, with an upper limit being craftAmount + const hepteractLimit = Math.floor( + player.wowAbyssals / (hepteracts[hept].HEPTERACT_CONVERSION * craftCostMulti) + ) + + // Create an array of how many we can craft using our conversion limits for additional items + const itemLimits: number[] = [] + for (const item in hepteracts[hept].OTHER_CONVERSIONS) { + const indexableItem = item as keyof Player + // The type of player[item] is number | Decimal | Cube. + if (item === 'worlds') { + itemLimits.push( + Math.floor((player[indexableItem] as number) / (hepteracts[hept].OTHER_CONVERSIONS[indexableItem] ?? 1)) + ) + } else if (isDecimal(player[indexableItem])) { + itemLimits.push( + Decimal.min( + Decimal.floor( + (player[indexableItem] as Decimal).div(craftCostMulti * hepteracts[hept].OTHER_CONVERSIONS[indexableItem]!) + ), + 1e300 + ).toNumber() + ) } else { - craftAmount = heptCap + itemLimits.push( + Math.floor((player[indexableItem] as number) / (hepteracts[hept].OTHER_CONVERSIONS[indexableItem] ?? 1)) + ) } + } - // Check these lol - if (isNaN(craftAmount) || !isFinite(craftAmount) || !Number.isInteger(craftAmount)) { // nan + Infinity checks - return Alert(i18next.t('general.validation.finite')) - } else if (craftAmount <= 0) { // 0 or less selected - return Alert(i18next.t('general.validation.zeroOrLess')) - } + // Get the smallest of the array we created + const smallestItemLimit = Math.min(...itemLimits) - // Get the smallest of hepteract limit, limit found above and specified input - amountToCraft = Math.min(smallestItemLimit, hepteractLimit, craftAmount, heptCap - this.BAL) + let amountToCraft = Math.min(smallestItemLimit, hepteractLimit, heptCap, heptCap - hepteracts[hept].BAL) - if (max && player.toggles[35]) { - const craftYesPlz = await Confirm(i18next.t('hepteracts.craftMax', { - x: format(amountToCraft, 0, true), - y: Math.floor(amountToCraft / heptCap * 10000) / 100 - })) + // Return if the material is not a calculable number + if (isNaN(amountToCraft) || !isFinite(amountToCraft)) { + return Alert(i18next.t('hepteracts.executionFailed')) + } - if (!craftYesPlz) { + // Prompt used here. Thank you Khafra for the already made code! -Platonic + if (!max) { + const craftingPrompt = await Prompt(i18next.t('hepteracts.craft', { + x: format(amountToCraft, 0, true), + y: Math.floor(amountToCraft / heptCap * 10000) / 100 + })) + + if (craftingPrompt === null) { // Number(null) is 0. Yeah.. + if (player.toggles[35]) { return Alert(i18next.t('hepteracts.cancelled')) + } else { + return // If no return, then it will just give another message } } + craftAmount = Number(craftingPrompt) + } else { + craftAmount = heptCap + } - this.BAL = Math.min(heptCap, this.BAL + amountToCraft) - - // Subtract spent items from player - player.wowAbyssals -= amountToCraft * this.HEPTERACT_CONVERSION * craftCostMulti + // Check these lol + if (isNaN(craftAmount) || !isFinite(craftAmount) || !Number.isInteger(craftAmount)) { // nan + Infinity checks + return Alert(i18next.t('general.validation.finite')) + } else if (craftAmount <= 0) { // 0 or less selected + return Alert(i18next.t('general.validation.zeroOrLess')) + } - if (player.wowAbyssals < 0) { - player.wowAbyssals = 0 - } + // Get the smallest of hepteract limit, limit found above and specified input + amountToCraft = Math.min(smallestItemLimit, hepteractLimit, craftAmount, heptCap - hepteracts[hept].BAL) - for (const item of (Object.keys(this.OTHER_CONVERSIONS) as (keyof Player)[])) { - if (typeof player[item] === 'number') { - ;(player[item] as number) -= amountToCraft * craftCostMulti - * this.OTHER_CONVERSIONS[item]! - } + if (max && player.toggles[35]) { + const craftYesPlz = await Confirm(i18next.t('hepteracts.craftMax', { + x: format(amountToCraft, 0, true), + y: Math.floor(amountToCraft / heptCap * 10000) / 100 + })) - if ((player[item] as number) < 0) { - ;(player[item] as number) = 0 - } else if (player[item] instanceof Cube) { - ;(player[item] as Cube).sub( - amountToCraft * craftCostMulti * this.OTHER_CONVERSIONS[item]! - ) - } else if (item === 'worlds') { - player.worlds.sub(amountToCraft * this.OTHER_CONVERSIONS[item]!) - } + if (!craftYesPlz) { + return Alert(i18next.t('hepteracts.cancelled')) } + } - if (player.toggles[35]) { - if (!max) { - return Alert(i18next.t('hepteracts.craftedHepteracts', { x: format(amountToCraft, 0, true) })) - } + hepteracts[hept].BAL = Math.min(heptCap, hepteracts[hept].BAL + amountToCraft) - return Alert(i18next.t('hepteracts.craftedHepteractsMax', { x: format(amountToCraft, 0, true) })) - } + // Subtract spent items from player + player.wowAbyssals -= amountToCraft * hepteracts[hept].HEPTERACT_CONVERSION * craftCostMulti + + if (player.wowAbyssals < 0) { + player.wowAbyssals = 0 } - // Reduce balance through spending - spend (amount: number): this { - if (!this.UNLOCKED) { - return this + for (const item of (Object.keys(hepteracts[hept].OTHER_CONVERSIONS) as (keyof Player)[])) { + if (typeof player[item] === 'number') { + ;(player[item] as number) -= amountToCraft * craftCostMulti + * hepteracts[hept].OTHER_CONVERSIONS[item]! + } + + if ((player[item] as number) < 0) { + ;(player[item] as number) = 0 + } else if (player[item] instanceof Cube) { + ;(player[item] as Cube).sub( + amountToCraft * craftCostMulti * hepteracts[hept].OTHER_CONVERSIONS[item]! + ) + } else if (item === 'worlds') { + player.worlds.sub(amountToCraft * hepteracts[hept].OTHER_CONVERSIONS[item]!) + } else if (player[item] instanceof Decimal) { + ;(player[item] as Decimal).sub( + new Decimal(amountToCraft).times(craftCostMulti).times(hepteracts[hept].OTHER_CONVERSIONS[item]!) + ) } - - this.BAL -= amount - return this } - // Expand your capacity - /** - * Expansion can only happen if your current balance is full. - */ - expand = async (): Promise => { - const expandMultiplier = 2 - const currentBalance = this.BAL - const heptCap = this.computeActualCap() - const currHeptCapNoMulti = this.CAP - - if (!this.UNLOCKED) { - return Alert(i18next.t('hepteracts.notUnlocked')) + if (player.toggles[35]) { + if (!max) { + return Alert(i18next.t('hepteracts.craftedHepteracts', { x: format(amountToCraft, 0, true) })) } - // Below capacity - if (this.BAL < this.CAP) { - if (player.toggles[35]) { - return Alert(i18next.t('hepteracts.notEnough')) - } else { - return - } - } + return Alert(i18next.t('hepteracts.craftedHepteractsMax', { x: format(amountToCraft, 0, true) })) + } +} - const expandPrompt = await Confirm(i18next.t('hepteracts.expandPrompt', { - x: format(this.CAP), - y: format(heptCap), - z: format(heptCap * expandMultiplier), - a: format(expandMultiplier, 2, true) - })) +export const expandHepteracts = async (hept: HepteractKeys) => { + const expandMultiplier = 2 + const currentBalance = hepteracts[hept].BAL + const heptCap = getFinalHepteractCap(hept) + const currHeptCapNoMulti = getHepteractCap(hept) - if (!expandPrompt) { - return this - } + if (!hepteracts[hept].UNLOCKED()) { + return Alert(i18next.t('hepteracts.notUnlocked')) + } - // Avoid a double-expand exploit due to player waiting to confirm until after autocraft fires and expands - if (this.BAL !== currentBalance || this.CAP !== currHeptCapNoMulti) { - if (player.toggles[35]) { - return Alert(i18next.t('hepteracts.doubleSpent')) - } else { - return - } + // Below capacity + if (currentBalance < currHeptCapNoMulti) { + if (player.toggles[35]) { + return Alert(i18next.t('hepteracts.notEnough')) + } else { + return } + } - // Empties inventory in exchange for doubling maximum capacity. - this.BAL -= this.CAP - this.BAL = Math.max(0, this.BAL) + const expandPrompt = await Confirm(i18next.t('hepteracts.expandPrompt', { + x: format(currHeptCapNoMulti), + y: format(heptCap), + z: format(heptCap * expandMultiplier), + a: format(expandMultiplier, 2, true) + })) - this.CAP = Math.min(1e300, this.CAP * expandMultiplier) + if (!expandPrompt) { + return + } + // Avoid a double-expand exploit due to player waiting to confirm until after autocraft fires and expands + if (hepteracts[hept].BAL !== currentBalance) { if (player.toggles[35]) { - return Alert(i18next.t('hepteracts.expandedInventory', { - x: format(heptCap * expandMultiplier, 0, true) - })) + return Alert(i18next.t('hepteracts.doubleSpent')) + } else { + return } } - // Add some percentage points to your discount - /** - * Discount has boundaries [0, 1), and upper limit - * is defined by (1 - EPSILON). Craft amount is multiplied by 1 / (1 - Discount) - */ - addDiscount (amount: number): this { - // If amount would put Discount to 1 or higher set to upper limit - if (this.DISCOUNT + amount > (1 - Number.EPSILON)) { - this.DISCOUNT = 1 - Number.EPSILON - return this - } + // Empties inventory in exchange for doubling maximum capacity. + hepteracts[hept].BAL -= currHeptCapNoMulti + hepteracts[hept].BAL = Math.max(0, hepteracts[hept].BAL) + + hepteracts[hept].TIMES_CAP_EXTENDED += 1 - this.DISCOUNT += amount - return this + if (player.toggles[35]) { + return Alert(i18next.t('hepteracts.expandedInventory', { + x: format(heptCap * expandMultiplier, 0, true) + })) } +} - toggleAutomatic (newValue?: boolean): Promise | this { - const HTML = DOMCacheGetOrSet(`${this.HTML_STRING}HepteractAuto`) +export const autoCraftHepteracts = (hept: HepteractKeys, heptAmount: number) => { + const craftCostMulti = calculateSingularityDebuff('Hepteract Costs') + let baseCap = getHepteractCap(hept) + let heptCap = getFinalHepteractCap(hept) - // When newValue is empty, current value is toggled - this.AUTO = newValue ?? !this.AUTO + // Calculate the largest craft amount possible, with an upper limit being craftAmount + const hepteractLimitCraft = Math.floor( + heptAmount / (craftCostMulti * hepteracts[hept].HEPTERACT_CONVERSION) + ) - HTML.textContent = this.AUTO ? i18next.t('general.autoOnColon') : i18next.t('general.autoOffColon') - HTML.style.border = `2px solid ${this.AUTO ? 'green' : 'red'}` + // Create an array of how many we can craft using our conversion limits for additional items + const itemLimits: number[] = [] + const quarks = hepteracts[hept].OTHER_CONVERSIONS.worlds - return this + if (typeof quarks === 'number') { + // When Auto is turned on, only Quarks and hepteracts are consumed. + itemLimits.push( + Math.floor(player.worlds.valueOf() / quarks) + ) } - autoCraft (heptAmount: number): this { - const expandMultiplier = 2 - const craftCostMulti = calculateSingularityDebuff('Hepteract Costs') - let heptCap = this.computeActualCap() + // Get the smallest of the array we created [If Empty, this will be infinite] + const smallestItemLimit = Math.min(...itemLimits) - // Calculate the largest craft amount possible, with an upper limit being craftAmount - const hepteractLimitCraft = Math.floor( - (heptAmount / (craftCostMulti * this.HEPTERACT_CONVERSION)) * 1 / (1 - this.DISCOUNT) - ) + let amountToCraft = Math.min(smallestItemLimit, hepteractLimitCraft) + let amountCrafted = 0 - // Create an array of how many we can craft using our conversion limits for additional items - const itemLimits: number[] = [] - for (const item in this.OTHER_CONVERSIONS) { - // When Auto is turned on, only Quarks and hepteracts are consumed. - if (item === 'worlds') { - itemLimits.push( - Math.floor((player[item as keyof Player] as number) / this.OTHER_CONVERSIONS[item as keyof Player]!) * 1 - / (1 - this.DISCOUNT) - ) - } - } + let craft = Math.min(heptCap - hepteracts[hept].BAL, amountToCraft) // Always nonzero + hepteracts[hept].BAL += craft + amountCrafted += craft + amountToCraft -= craft + + while (hepteracts[hept].BAL >= heptCap && amountToCraft >= baseCap) { + hepteracts[hept].BAL -= baseCap + hepteracts[hept].TIMES_CAP_EXTENDED += 1 - // Get the smallest of the array we created [If Empty, this will be infinite] - const smallestItemLimit = Math.min(...itemLimits) + heptCap *= 2 + baseCap *= 2 - let amountToCraft = Math.min(smallestItemLimit, hepteractLimitCraft) - let amountCrafted = 0 + craft = Math.min(heptCap - hepteracts[hept].BAL, amountToCraft) - let craft = Math.min(heptCap - this.BAL, amountToCraft) // Always nonzero - this.BAL += craft + hepteracts[hept].BAL += craft amountCrafted += craft amountToCraft -= craft + } - while (this.BAL >= heptCap && amountToCraft >= this.CAP) { - this.BAL -= this.CAP - this.CAP *= expandMultiplier - heptCap *= expandMultiplier - craft = Math.min(heptCap - this.BAL, amountToCraft) - - this.BAL += craft - amountCrafted += craft - amountToCraft -= craft - } + if (typeof quarks === 'number') { + player.worlds.sub(amountCrafted * quarks) + } - for (const item in this.OTHER_CONVERSIONS) { - if (item === 'worlds') { - player.worlds.sub(amountCrafted * this.OTHER_CONVERSIONS[item]!) - } - } + player.wowAbyssals -= amountCrafted * craftCostMulti * hepteracts[hept].HEPTERACT_CONVERSION + if (player.wowAbyssals < 0) { + player.wowAbyssals = 0 + } +} - player.wowAbyssals -= amountCrafted * craftCostMulti * this.HEPTERACT_CONVERSION - if (player.wowAbyssals < 0) { - player.wowAbyssals = 0 - } +export const setAutomaticHepteractTexts = () => { + for (const hept of hepteractKeys) { + const HTML = DOMCacheGetOrSet(`${hept}HepteractAuto`) + hepteracts[hept].AUTO = player.hepteracts[hept].AUTO ?? hepteracts[hept].AUTO - return this + HTML.textContent = hepteracts[hept].AUTO ? i18next.t('general.autoOnColon') : i18next.t('general.autoOffColon') + HTML.style.border = `2px solid ${hepteracts[hept].AUTO ? 'green' : 'red'}` } +} - valueOf (): IHepteractCraft { - return { - BASE_CAP: this.BASE_CAP, - HEPTERACT_CONVERSION: this.HEPTERACT_CONVERSION, - OTHER_CONVERSIONS: this.OTHER_CONVERSIONS, - HTML_STRING: this.HTML_STRING, - UNLOCKED: this.UNLOCKED, - BAL: this.BAL, - CAP: this.CAP, - DISCOUNT: this.DISCOUNT, - AUTO: this.AUTO - } - } +export const toggleAutomaticHepteracts = (hept: HepteractKeys, newValue?: boolean) => { + const HTML = DOMCacheGetOrSet(`${hept}HepteractAuto`) + + // When newValue is empty, current value is toggled + hepteracts[hept].AUTO = newValue ?? !hepteracts[hept].AUTO + + HTML.textContent = hepteracts[hept].AUTO ? i18next.t('general.autoOnColon') : i18next.t('general.autoOffColon') + HTML.style.border = `2px solid ${hepteracts[hept].AUTO ? 'green' : 'red'}` } -const hepteractEffectiveValues = { - chronos: { - LIMIT: 1000, - DR: 1 / 6 - }, - hyperrealism: { - LIMIT: 1000, - DR: 0.33 - }, - quark: { - LIMIT: 1000, - DR: 0.5 - }, - challenge: { - LIMIT: 1000, - DR: 1 / 6 - }, - abyss: { - LIMIT: 1, - DR: 0 - }, - accelerator: { - LIMIT: 1000, - DR: 0.2 - }, - acceleratorBoost: { - LIMIT: 1000, - DR: 0.2 - }, - multiplier: { - LIMIT: 1000, - DR: 0.2 +export const resetHepteracts = (tier: keyof typeof resetTiers) => { + for (const key of hepteractKeys) { + if (resetTiers[tier] >= resetTiers[hepteracts[key].RESET_TIER]) { + hepteracts[key].BAL = 0 + hepteracts[key].TIMES_CAP_EXTENDED = 0 + + player.hepteracts[key] = { + BAL: 0, + TIMES_CAP_EXTENDED: 0, + AUTO: hepteracts[key].AUTO + } + } } } -export const hepteractEffective = (data: hepteractTypes) => { - let effectiveValue = Math.min(player.hepteractCrafts[data].BAL, hepteractEffectiveValues[data].LIMIT) - let exponentBoost = 0 - if (data === 'chronos') { - exponentBoost += 1 / 750 * player.platonicUpgrades[19] - } - if (data === 'quark') { - exponentBoost += +player.singularityUpgrades.singQuarkHepteract.getEffect().bonus - exponentBoost += +player.singularityUpgrades.singQuarkHepteract2.getEffect().bonus - exponentBoost += +player.singularityUpgrades.singQuarkHepteract3.getEffect().bonus - exponentBoost += +player.octeractUpgrades.octeractImprovedQuarkHept.getEffect().bonus - exponentBoost += player.shopUpgrades.improveQuarkHept / 100 - exponentBoost += player.shopUpgrades.improveQuarkHept2 / 100 - exponentBoost += player.shopUpgrades.improveQuarkHept3 / 100 - exponentBoost += player.shopUpgrades.improveQuarkHept4 / 100 - exponentBoost += player.shopUpgrades.improveQuarkHept5 / 5000 - - const amount = player.hepteractCrafts[data].BAL - if (1000 < amount && amount <= 1000 * Math.pow(2, 10)) { - return effectiveValue * Math.pow(amount / 1000, 1 / 2 + exponentBoost) - } else if (1000 * Math.pow(2, 10) < amount && amount <= 1000 * Math.pow(2, 18)) { - return effectiveValue * Math.pow(Math.pow(2, 10), 1 / 2 + exponentBoost) - * Math.pow(amount / (1000 * Math.pow(2, 10)), 1 / 4 + exponentBoost / 2) - } else if (1000 * Math.pow(2, 18) < amount && amount <= 1000 * Math.pow(2, 44)) { - return effectiveValue * Math.pow(Math.pow(2, 10), 1 / 2 + exponentBoost) - * Math.pow(Math.pow(2, 8), 1 / 4 + exponentBoost / 2) - * Math.pow(amount / (1000 * Math.pow(2, 18)), 1 / 6 + exponentBoost / 3) - } else if (1000 * Math.pow(2, 44) < amount) { - return effectiveValue * Math.pow(Math.pow(2, 10), 1 / 2 + exponentBoost) - * Math.pow(Math.pow(2, 8), 1 / 4 + exponentBoost / 2) - * Math.pow(Math.pow(2, 26), 1 / 6 + exponentBoost / 3) - * Math.pow(amount / (1000 * Math.pow(2, 44)), 1 / 12 + exponentBoost / 6) - } +export const hepteractEffective = (hept: HepteractKeys) => { + // Quark Hept now uses a custom (nonpolynomial) formula, so just return val + if (hept === 'quark') { + return hepteracts[hept].BAL } - if (player.hepteractCrafts[data].BAL > hepteractEffectiveValues[data].LIMIT) { + + const rawHeptAmount = hepteracts[hept].BAL + let effectiveValue = Math.min(rawHeptAmount, hepteracts[hept].LIMIT) + const exponent = hepteracts[hept].DR + hepteracts[hept].DR_INCREASE() + + if (rawHeptAmount > hepteracts[hept].LIMIT) { effectiveValue *= Math.pow( - player.hepteractCrafts[data].BAL / hepteractEffectiveValues[data].LIMIT, - hepteractEffectiveValues[data].DR + exponentBoost + rawHeptAmount / hepteracts[hept].LIMIT, + exponent ) } return effectiveValue } -export const hepteractDescriptions = (type: hepteractTypes) => { +export const hepteractDescriptions = (hept: HepteractKeys) => { DOMCacheGetOrSet('hepteractUnlockedText').style.display = 'block' DOMCacheGetOrSet('hepteractCurrentEffectText').style.display = 'block' DOMCacheGetOrSet('hepteractBalanceText').style.display = 'block' @@ -510,30 +627,28 @@ export const hepteractDescriptions = (type: hepteractTypes) => { const bonusCapacityText = DOMCacheGetOrSet('hepteractBonusCapacity') const craftCostMulti = calculateSingularityDebuff('Hepteract Costs') - const multiplier = player.hepteractCrafts[type].computeActualCap() / player.hepteractCrafts[type].CAP - bonusCapacityText.textContent = - (player.hepteractCrafts[type].computeActualCap() / player.hepteractCrafts[type].CAP > 1) - ? `Hepteract capacities are currently multiplied by ${multiplier}. Expansions cost what they would if this multiplier were 1.` - : '' - let currentEffectRecord!: StringMap + const multiplier = getHepteractCap(hept) / getFinalHepteractCap(hept) + bonusCapacityText.innerHTML = (multiplier > 1) + ? i18next.t('wowCubes.hepteractForge.multiplierText', { + multiplier: format(multiplier, 0, true) + }) + : '' + + const currentEffectRecord = hepteracts[hept].EFFECTSDESCRIPTION(hepteractEffective(hept)) let oneCost!: string | Record - switch (type) { + switch (hept) { case 'chronos': - currentEffectRecord = { x: format(hepteractEffective('chronos') * 6 / 100, 2, true) } oneCost = format(1e115 * craftCostMulti, 0, false) break case 'hyperrealism': - currentEffectRecord = { x: format(hepteractEffective('hyperrealism') * 6 / 100, 2, true) } oneCost = format(1e80 * craftCostMulti, 0, true) break case 'quark': - currentEffectRecord = { x: format(hepteractEffective('quark') * 5 / 100, 2, true) } oneCost = '100' break case 'challenge': - currentEffectRecord = { x: format(hepteractEffective('challenge') * 5 / 100, 2, true) } oneCost = { y: format(1e11 * craftCostMulti), z: format(1e22 * craftCostMulti) @@ -543,41 +658,30 @@ export const hepteractDescriptions = (type: hepteractTypes) => { oneCost = format(69 * craftCostMulti) break case 'accelerator': - currentEffectRecord = { - x: format(2000 * hepteractEffective('accelerator'), 2, true), - y: format(hepteractEffective('accelerator') * 3 / 100, 2, true) - } oneCost = format(1e14 * craftCostMulti) break case 'acceleratorBoost': - currentEffectRecord = { x: format(hepteractEffective('acceleratorBoost') / 10, 2, true) } oneCost = format(1e10 * craftCostMulti) break case 'multiplier': - currentEffectRecord = { - x: format(1000 * hepteractEffective('multiplier'), 2, true), - y: format(hepteractEffective('multiplier') * 3 / 100, 2, true) - } oneCost = format(1e130 * craftCostMulti) break } - effectText.textContent = i18next.t(`wowCubes.hepteractForge.descriptions.${type}.effect`) - currentEffectText.textContent = i18next.t( - `wowCubes.hepteractForge.descriptions.${type}.currentEffect`, - currentEffectRecord - ) + effectText.textContent = i18next.t(`wowCubes.hepteractForge.descriptions.${hept}.effect`) + currentEffectText.innerHTML = currentEffectRecord + balanceText.textContent = i18next.t('wowCubes.hepteractForge.inventory', { - x: format(player.hepteractCrafts[type].BAL, 0, true), - y: format(player.hepteractCrafts[type].computeActualCap(), 0, true) + x: format(hepteracts[hept].BAL, 0, true), + y: format(getFinalHepteractCap(hept), 0, true) }) const record = typeof oneCost === 'string' ? { y: oneCost } : oneCost - costText.textContent = i18next.t(`wowCubes.hepteractForge.descriptions.${type}.oneCost`, { - x: format(player.hepteractCrafts[type].HEPTERACT_CONVERSION * craftCostMulti, 0, true), + costText.textContent = i18next.t(`wowCubes.hepteractForge.descriptions.${hept}.oneCost`, { + x: format(hepteracts[hept].HEPTERACT_CONVERSION * craftCostMulti, 0, true), ...record }) - unlockedText.textContent = player.hepteractCrafts[type].UNLOCKED + unlockedText.textContent = hepteracts[hept].UNLOCKED() ? i18next.t('wowCubes.hepteractForge.unlocked') : i18next.t('wowCubes.hepteractForge.locked') } @@ -769,85 +873,3 @@ export const overfluxPowderWarp = async (auto: boolean) => { } } } - -/** - * Get the HepteractCrafts which are unlocked and auto = ON - * @returns Array of HepteractCraft - */ -export const getAutoHepteractCrafts = () => { - const autoHepteracts: HepteractCraft[] = [] - for (const craftName of Object.keys(player.hepteractCrafts)) { - const craftKey = craftName as keyof Player['hepteractCrafts'] - if (player.hepteractCrafts[craftKey].AUTO && player.hepteractCrafts[craftKey].UNLOCKED) { - autoHepteracts.push(player.hepteractCrafts[craftKey]) - } - } - return autoHepteracts -} - -// Hepteract of Chronos [UNLOCKED] -export const ChronosHepteract = new HepteractCraft({ - BASE_CAP: 1000, - HEPTERACT_CONVERSION: 1e4, - OTHER_CONVERSIONS: { researchPoints: 1e115 }, - HTML_STRING: 'chronos', - UNLOCKED: true -}) - -// Hepteract of Hyperrealism [UNLOCKED] -export const HyperrealismHepteract = new HepteractCraft({ - BASE_CAP: 1000, - HEPTERACT_CONVERSION: 1e4, - OTHER_CONVERSIONS: { runeshards: 1e80 }, - HTML_STRING: 'hyperrealism', - UNLOCKED: true -}) - -// Hepteract of Too Many Quarks [UNLOCKED] -export const QuarkHepteract = new HepteractCraft({ - BASE_CAP: 1000, - HEPTERACT_CONVERSION: 1e4, - OTHER_CONVERSIONS: { worlds: 100 }, - HTML_STRING: 'quark', - UNLOCKED: true -}) - -// Hepteract of Challenge [LOCKED] -export const ChallengeHepteract = new HepteractCraft({ - BASE_CAP: 1000, - HEPTERACT_CONVERSION: 5e4, - OTHER_CONVERSIONS: { wowPlatonicCubes: 1e11, wowCubes: 1e22 }, - HTML_STRING: 'challenge' -}) - -// Hepteract of The Abyssal [LOCKED] -export const AbyssHepteract = new HepteractCraft({ - BASE_CAP: 1, - HEPTERACT_CONVERSION: 1e8, - OTHER_CONVERSIONS: { wowCubes: 69 }, - HTML_STRING: 'abyss' -}) - -// Hepteract of Too Many Accelerator [LOCKED] -export const AcceleratorHepteract = new HepteractCraft({ - BASE_CAP: 1000, - HEPTERACT_CONVERSION: 1e5, - OTHER_CONVERSIONS: { wowTesseracts: 1e14 }, - HTML_STRING: 'accelerator' -}) - -// Hepteract of Too Many Accelerator Boost [LOCKED] -export const AcceleratorBoostHepteract = new HepteractCraft({ - BASE_CAP: 1000, - HEPTERACT_CONVERSION: 2e5, - OTHER_CONVERSIONS: { wowHypercubes: 1e10 }, - HTML_STRING: 'acceleratorBoost' -}) - -// Hepteract of Too Many Multiplier [LOCKED] -export const MultiplierHepteract = new HepteractCraft({ - BASE_CAP: 1000, - HEPTERACT_CONVERSION: 3e5, - OTHER_CONVERSIONS: { researchPoints: 1e130 }, - HTML_STRING: 'multiplier' -}) diff --git a/src/History.ts b/src/History.ts index 0e0cb04da..0e3d5b59e 100644 --- a/src/History.ts +++ b/src/History.ts @@ -4,6 +4,7 @@ import i18next from 'i18next' import { antSacrificePointsToMultiplier } from './Ants' import { DOMCacheGetOrSet } from './Cache/DOM' import { applyCorruptions, convertInputToCorruption, type Corruptions } from './Corruptions' +import { getGQUpgradeEffect } from './singularity' import { format, formatTimeShort, player } from './Synergism' import { IconSets } from './Themes' import { Notification } from './UpdateHTML' @@ -28,25 +29,25 @@ export type ResetHistoryEntryAntSacrifice = ResetHistoryEntryBase & { crumbs: string crumbsPerSecond: string effectiveELO: number - obtainium: number - offerings: number + obtainium: Decimal + offerings: Decimal kind: 'antsacrifice' } export type ResetHistoryEntryPrestige = ResetHistoryEntryBase & { - offerings: number + offerings: Decimal diamonds: string kind: 'prestige' } export type ResetHistoryEntryTranscend = ResetHistoryEntryBase & { - offerings: number + offerings: Decimal mythos: string kind: 'transcend' } export type ResetHistoryEntryReincarnate = ResetHistoryEntryBase & { - offerings: number + offerings: Decimal particles: string - obtainium: number + obtainium: Decimal kind: 'reincarnate' } @@ -214,7 +215,7 @@ const historyGains: Record< img: 'TinyWow7.png', formatter: conditionalFormatPerSecond, imgTitle: 'Hepteracts', - onlyif: () => player.achievements[255] > 0 + onlyif: () => G.challenge15Rewards.hepteractsUnlocked.value >= 1 }, singularityCount: { img: 'TinyS.png', @@ -262,7 +263,7 @@ const historyGains: Record< img: 'TinyWow8.png', formatter: formatDecimalSource, imgTitle: 'Octeracts', - onlyif: () => (player.singularityUpgrades.octeractUnlock.getEffect().bonus as number) > 0 + onlyif: () => getGQUpgradeEffect('octeractUnlock') > 0 }, c15Score: { img: 'TinyChallenge15.png', @@ -435,7 +436,7 @@ const resetHistoryRenderRow = ( if (data.kind === 'antsacrifice') { const oldMulti = antSacrificePointsToMultiplier(data.antSacrificePointsBefore) const newMulti = antSacrificePointsToMultiplier(data.antSacrificePointsAfter) - const diff = newMulti - oldMulti + const diff = newMulti.sub(oldMulti) extra.push( `${name}
+ ${minimumLevel}
+ ${description}
+ ${effectDesc} + ` +} + +export const generateLevelRewardHTMLs = () => { + const alreadyGenerated = document.getElementsByClassName('synergismLevelRewardType').length > 0 + if (alreadyGenerated) { + return + } + const rewardTable = DOMCacheGetOrSet('synergismLevelRewardsTable') + for (const reward of synergismLevelReward) { + const capitalizedName = reward.charAt(0).toUpperCase() + reward.slice(1) + + const div = document.createElement('div') + div.classList.add('synergismLevelRewardType') + + const img = document.createElement('img') + img.id = `synergismLevelReward${capitalizedName}` + img.src = `Pictures/Achievements/Rewards/${capitalizedName}.png` + img.alt = synergismLevelRewards[reward].name() + img.style.cursor = 'pointer' + + img.onclick = () => { + getLevelRewardDescription(reward) + } + img.onmouseover = () => { + getLevelRewardDescription(reward) + } + img.focus = () => { + getLevelRewardDescription(reward) + } + div.appendChild(img) + rewardTable.appendChild(div) + } +} + +export type SynergismLevelMilestones = + | 'offeringTimerScaling' + | 'speedRune' + | 'duplicationRune' + | 'prismRune' + | 'thriftRune' + | 'SIRune' + | 'autoPrestige' + | 'tier1CrystalAutobuy' + | 'tier2CrystalAutobuy' + | 'tier3CrystalAutobuy' + | 'tier4CrystalAutobuy' + | 'tier5CrystalAutobuy' + | 'achievementTalismanUnlock' + | 'achievementTalismanEnhancement' + | 'salvageChallengeBuff' + +interface SynergismLevelMilestoneData { + name: () => string + description: () => string + effect: () => number + defaultValue: number // If level is not reached. + effectDescription: () => string + levelReq: number + displayOrder: number +} + +export const synergismLevelMilestones: Record = { + offeringTimerScaling: { + name: () => i18next.t('achievements.levelMilestones.offeringTimerScaling.name'), + description: () => i18next.t('achievements.levelMilestones.offeringTimerScaling.description'), + effect: () => 1, + defaultValue: 0, + effectDescription: () => { + const mult = getLevelMilestone('offeringTimerScaling') === 1 + ? Math.max(1, player.prestigecounter / resetTimeThreshold()) + : 1 + return i18next.t('achievements.levelMilestones.offeringTimerScaling.effect', { + mult: formatAsPercentIncrease(mult, 2) + }) + }, + levelReq: 5, + displayOrder: 1 + }, + autoPrestige: { + name: () => i18next.t('achievements.levelMilestones.autoPrestige.name'), + description: () => i18next.t('achievements.levelMilestones.autoPrestige.description'), + effect: () => 1, + defaultValue: 0, + effectDescription: () => { + const autoPrestige = getLevelMilestone('autoPrestige') === 1 + return i18next.t('achievements.levelMilestones.autoPrestige.effect', { + autoPrestige: autoPrestige + ? i18next.t('achievements.rewardTypes.unlocked') + : i18next.t('achievements.rewardTypes.locked') + }) + }, + levelReq: 10, + displayOrder: 2 + }, + speedRune: { + name: () => i18next.t('achievements.levelMilestones.speedRune.name'), + description: () => i18next.t('achievements.levelMilestones.speedRune.description'), + effect: () => { + return 0.5 * (achievementLevel - 19) + }, + defaultValue: 0, + effectDescription: () => { + const speedRune = getLevelMilestone('speedRune') + return i18next.t('achievements.levelMilestones.speedRune.effect', { + speedRune: format(speedRune, 2, true) + }) + }, + levelReq: 20, + displayOrder: 3 + }, + duplicationRune: { + name: () => i18next.t('achievements.levelMilestones.duplicationRune.name'), + description: () => i18next.t('achievements.levelMilestones.duplicationRune.description'), + effect: () => { + return 0.4 * (achievementLevel - 39) + }, + defaultValue: 0, + effectDescription: () => { + const duplicationRune = getLevelMilestone('duplicationRune') + return i18next.t('achievements.levelMilestones.duplicationRune.effect', { + duplicationRune: format(duplicationRune, 2, true) + }) + }, + levelReq: 40, + displayOrder: 4 + }, + prismRune: { + name: () => i18next.t('achievements.levelMilestones.prismRune.name'), + description: () => i18next.t('achievements.levelMilestones.prismRune.description'), + effect: () => { + return 0.3 * (achievementLevel - 59) + }, + defaultValue: 0, + effectDescription: () => { + const prismRune = getLevelMilestone('prismRune') + return i18next.t('achievements.levelMilestones.prismRune.effect', { + prismRune: format(prismRune, 2, true) + }) + }, + levelReq: 60, + displayOrder: 5 + }, + thriftRune: { + name: () => i18next.t('achievements.levelMilestones.thriftRune.name'), + description: () => i18next.t('achievements.levelMilestones.thriftRune.description'), + effect: () => { + return 0.2 * (achievementLevel - 79) + }, + defaultValue: 0, + effectDescription: () => { + const thriftRune = getLevelMilestone('thriftRune') + return i18next.t('achievements.levelMilestones.thriftRune.effect', { + thriftRune: format(thriftRune, 2, true) + }) + }, + levelReq: 80, + displayOrder: 6 + }, + SIRune: { + name: () => i18next.t('achievements.levelMilestones.SIRune.name'), + description: () => i18next.t('achievements.levelMilestones.SIRune.description'), + effect: () => { + return 0.1 * (achievementLevel - 99) + }, + defaultValue: 0, + effectDescription: () => { + const siRune = getLevelMilestone('SIRune') + return i18next.t('achievements.levelMilestones.SIRune.effect', { + siRune: format(siRune, 2, true) + }) + }, + levelReq: 100, + displayOrder: 7 + }, + tier1CrystalAutobuy: { + name: () => i18next.t('achievements.levelMilestones.tier1CrystalAutobuy.name'), + description: () => i18next.t('achievements.levelMilestones.tier1CrystalAutobuy.description'), + effect: () => 1, + defaultValue: 0, + effectDescription: () => { + const autobuy = getLevelMilestone('tier1CrystalAutobuy') === 1 + return i18next.t('achievements.levelMilestones.tier1CrystalAutobuy.effect', { + autobuy: autobuy ? i18next.t('achievements.rewardTypes.unlocked') : i18next.t('achievements.rewardTypes.locked') + }) + }, + levelReq: 6, + displayOrder: 8 + }, + tier2CrystalAutobuy: { + name: () => i18next.t('achievements.levelMilestones.tier2CrystalAutobuy.name'), + description: () => i18next.t('achievements.levelMilestones.tier2CrystalAutobuy.description'), + effect: () => 1, + defaultValue: 0, + effectDescription: () => { + const autobuy = getLevelMilestone('tier2CrystalAutobuy') === 1 + return i18next.t('achievements.levelMilestones.tier2CrystalAutobuy.effect', { + autobuy: autobuy ? i18next.t('achievements.rewardTypes.unlocked') : i18next.t('achievements.rewardTypes.locked') + }) + }, + levelReq: 9, + displayOrder: 9 + }, + tier3CrystalAutobuy: { + name: () => i18next.t('achievements.levelMilestones.tier3CrystalAutobuy.name'), + description: () => i18next.t('achievements.levelMilestones.tier3CrystalAutobuy.description'), + effect: () => 1, + defaultValue: 0, + effectDescription: () => { + const autobuy = getLevelMilestone('tier3CrystalAutobuy') === 1 + return i18next.t('achievements.levelMilestones.tier3CrystalAutobuy.effect', { + autobuy: autobuy ? i18next.t('achievements.rewardTypes.unlocked') : i18next.t('achievements.rewardTypes.locked') + }) + }, + levelReq: 12, + displayOrder: 10 + }, + tier4CrystalAutobuy: { + name: () => i18next.t('achievements.levelMilestones.tier4CrystalAutobuy.name'), + description: () => i18next.t('achievements.levelMilestones.tier4CrystalAutobuy.description'), + effect: () => 1, + defaultValue: 0, + effectDescription: () => { + const autobuy = getLevelMilestone('tier4CrystalAutobuy') === 1 + return i18next.t('achievements.levelMilestones.tier4CrystalAutobuy.effect', { + autobuy: autobuy ? i18next.t('achievements.rewardTypes.unlocked') : i18next.t('achievements.rewardTypes.locked') + }) + }, + levelReq: 15, + displayOrder: 11 + }, + tier5CrystalAutobuy: { + name: () => i18next.t('achievements.levelMilestones.tier5CrystalAutobuy.name'), + description: () => i18next.t('achievements.levelMilestones.tier5CrystalAutobuy.description'), + effect: () => 1, + defaultValue: 0, + effectDescription: () => { + const autobuy = getLevelMilestone('tier5CrystalAutobuy') === 1 + return i18next.t('achievements.levelMilestones.tier5CrystalAutobuy.effect', { + autobuy: autobuy ? i18next.t('achievements.rewardTypes.unlocked') : i18next.t('achievements.rewardTypes.locked') + }) + }, + levelReq: 20, + displayOrder: 12 + }, + achievementTalismanUnlock: { + name: () => i18next.t('achievements.levelMilestones.achievementTalismanUnlock.name'), + description: () => i18next.t('achievements.levelMilestones.achievementTalismanUnlock.description'), + effect: () => 1, + defaultValue: 0, + effectDescription: () => { + const unlocked = getLevelMilestone('achievementTalismanUnlock') === 1 + return i18next.t('achievements.levelMilestones.achievementTalismanUnlock.effect', { + unlocked: unlocked + ? i18next.t('achievements.rewardTypes.unlocked') + : i18next.t('achievements.rewardTypes.locked') + }) + }, + levelReq: 100, + displayOrder: 13 + }, + achievementTalismanEnhancement: { + name: () => i18next.t('achievements.levelMilestones.achievementTalismanEnhancement.name'), + description: () => i18next.t('achievements.levelMilestones.achievementTalismanEnhancement.description'), + effect: () => achievementLevel, + defaultValue: 0, + effectDescription: () => { + const level = getLevelMilestone('achievementTalismanEnhancement') + return i18next.t('achievements.levelMilestones.achievementTalismanEnhancement.effect', { + level: format(level, 0, true) + }) + }, + levelReq: 160, + displayOrder: 14 + }, + salvageChallengeBuff: { + name: () => i18next.t('achievements.levelMilestones.salvageChallengeBuff.name'), + description: () => i18next.t('achievements.levelMilestones.salvageChallengeBuff.description'), + effect: () => { + let baseVal = 25 + if ( + player.currentChallenge.transcension !== 0 + || player.currentChallenge.reincarnation !== 0 + || player.currentChallenge.ascension !== 0 + ) { + baseVal *= 2 + } + if (player.currentChallenge.ascension === 15) { + baseVal *= 2 + } + if (player.insideSingularityChallenge) { + baseVal *= 3 + } + return baseVal + }, + defaultValue: 0, + effectDescription: () => { + const salvage = getLevelMilestone('salvageChallengeBuff') + return i18next.t('achievements.levelMilestones.salvageChallengeBuff.effect', { + salvage: format(salvage, 0, true) + }) + }, + levelReq: 180, + displayOrder: 15 + } +} + +export const synergismLevelMilestone = Object.keys(synergismLevelMilestones) as SynergismLevelMilestones[] + +export const getLevelMilestone = (milestone: SynergismLevelMilestones): number => { + if (achievementLevel >= synergismLevelMilestones[milestone].levelReq) { + return synergismLevelMilestones[milestone].effect() + } else { + return synergismLevelMilestones[milestone].defaultValue + } +} + +export const getLevelMilestoneDescription = (milestone: SynergismLevelMilestones) => { + const name = synergismLevelMilestones[milestone].name() + const description = synergismLevelMilestones[milestone].description() + const effectDesc = synergismLevelMilestones[milestone].effectDescription() + const minimumLevel = i18next.t('achievements.levelRewards.minLevel', { + level: synergismLevelMilestones[milestone].levelReq + }) + + DOMCacheGetOrSet('synergismLevelMultiLine').innerHTML = ` + ${name}
+ ${minimumLevel}
+ ${description}
+ ${effectDesc} + ` +} + +export const generateLevelMilestoneHTMLS = () => { + const alreadyGenerated = document.getElementsByClassName('synergismLevelMilestoneType').length > 0 + if (alreadyGenerated) { + return + } + const rewardTable = DOMCacheGetOrSet('synergismLevelMilestonesTable') + for (const milestone of synergismLevelMilestone) { + const capitalizedName = milestone.charAt(0).toUpperCase() + milestone.slice(1) + + const div = document.createElement('div') + div.classList.add('synergismLevelMilestoneType') + + const img = document.createElement('img') + img.id = `synergismLevelMilestone${capitalizedName}` + img.src = `Pictures/Achievements/Milestones/${capitalizedName}.png` + img.alt = synergismLevelMilestones[milestone].name() + img.style.cursor = 'pointer' + + img.onclick = () => { + getLevelMilestoneDescription(milestone) + } + img.onmouseover = () => { + getLevelMilestoneDescription(milestone) + } + img.focus = () => { + getLevelMilestoneDescription(milestone) + } + div.appendChild(img) + rewardTable.appendChild(div) + } + + displayLevelStuff() +} + +export const displayLevelStuff = () => { + for (const key of synergismLevelReward) { + const capitalizedName = key.charAt(0).toUpperCase() + key.slice(1) + const id = `synergismLevelReward${capitalizedName}` + const element = DOMCacheGetOrSet(id) + if (achievementLevel >= synergismLevelRewards[key].minLevel) { + element.style.display = 'inline-block' + } else { + element.style.display = 'none' + } + } + + for (const key of synergismLevelMilestone) { + const capitalizedName = key.charAt(0).toUpperCase() + key.slice(1) + const id = `synergismLevelMilestone${capitalizedName}` + const element = DOMCacheGetOrSet(id) + if (achievementLevel >= synergismLevelMilestones[key].levelReq) { + element.style.display = 'inline-block' + } else { + element.style.display = 'none' + } + } +} diff --git a/src/Login.ts b/src/Login.ts index b3c85344b..751bb299c 100644 --- a/src/Login.ts +++ b/src/Login.ts @@ -7,12 +7,13 @@ import { DOMCacheGetOrSet } from './Cache/DOM' import { calculateAmbrosiaGenerationSpeed, calculateOffline, calculateRedAmbrosiaGenerationSpeed } from './Calculate' import { updateGlobalsIsEvent } from './Event' import { addTimers, automaticTools } from './Helper' -import { importSynergism } from './ImportExport' +import { exportData, importSynergism, saveFilename } from './ImportExport' import { updatePseudoCoins } from './purchases/UpgradesSubtab' -import { QuarkHandler, setQuarkBonus } from './Quark' +import { QuarkHandler, refreshQuarkBonus, setQuarkBonus } from './Quark' +import { updatePrestigeCount, updateReincarnationCount, updateTranscensionCount } from './Reset' import { format, player, saveSynergy } from './Synergism' import { Alert, Notification } from './UpdateHTML' -import { assert } from './Utility' +import { assert, btoa } from './Utility' export type PseudoCoinConsumableNames = 'HAPPY_HOUR_BELL' @@ -34,6 +35,13 @@ interface Consumable { displayName: string } +interface Save { + id: number + name: string + uploadedAt: string + save: string +} + // Consts for Patreon Supporter Roles. const TRANSCENDED_BALLER = '756419583941804072' const REINCARNATED_BALLER = '758859750070026241' @@ -57,6 +65,8 @@ let ws: WebSocket | undefined let loggedIn = false let tips = 0 +const cloudSaves: Save[] = [] + export const isLoggedIn = () => loggedIn export const getTips = () => tips export const setTips = (newTips: number) => tips = newTips @@ -84,7 +94,12 @@ const messageSchema = z.preprocess( z.object({ type: z.literal('join') }), z.object({ type: z.literal('error'), message: z.string() }), /** Received after a consumable is redeemed (broadcasted to everyone) */ - z.object({ type: z.literal('consumed'), consumable: z.string(), startedAt: z.number().int() }), + z.object({ + type: z.literal('consumed'), + consumable: z.string(), + displayName: z.string(), + startedAt: z.number().int() + }), /** Received after a consumable ends (broadcasted to everyone) */ z.object({ type: z.literal('consumable-ended'), consumable: z.string(), endedAt: z.number().int() }), /** Information about all currently active consumables, received when the connection opens. */ @@ -187,8 +202,6 @@ interface SynergismUserAPIResponse { error?: unknown } -type CloudSave = null | { save: string } - const isDiscordAccount = ( account: SynergismUserAPIResponse ): account is SynergismUserAPIResponse<'discord'> => account.accountType === 'discord' @@ -203,7 +216,7 @@ const hasAccount = ( ): account is SynergismUserAPIResponse<'discord' | 'patreon' | 'email'> => account.accountType !== 'none' export async function handleLogin () { - const subtabElement = document.querySelector('#accountSubTab > div.scrollbarX')! + const subtabElement = document.querySelector('#accountSubTab div#left.scrollbarX')! const currentBonus = DOMCacheGetOrSet('currentBonus') const logoutElement = document.getElementById('logoutButton') @@ -232,7 +245,8 @@ export async function handleLogin () { const account = await response.json() as SynergismUserAPIResponse const { globalBonus, personalBonus, subscriptionTier } = account - setQuarkBonus(100 * (1 + globalBonus / 100) * (1 + personalBonus / 100) - 100) + setQuarkBonus(personalBonus, globalBonus) + setInterval(() => refreshQuarkBonus(), 1000 * 60 * 15) player.worlds = new QuarkHandler(Number(player.worlds)) loggedIn = hasAccount(account) @@ -342,28 +356,6 @@ export async function handleLogin () { --> PATREON <-- `.trim() - - const cloudSaveElement = document.createElement('button') - const loadCloudSaveElement = document.createElement('button') - - if (personalBonus > 1) { - cloudSaveElement.addEventListener('click', saveToCloud) - cloudSaveElement.style.cssText = 'border: 2px solid #5865F2; height: 25px; width: 150px;' - cloudSaveElement.textContent = 'Save to Cloud ☁' - - loadCloudSaveElement.addEventListener('click', getCloudSave) - loadCloudSaveElement.style.cssText = 'border: 2px solid #5865F2; height: 25px; width: 150px;' - loadCloudSaveElement.textContent = 'Load from Cloud ☽' - } - - const cloudSaveParent = document.createElement('div') - cloudSaveParent.style.cssText = - 'display: flex; flex-direction: row; justify-content: space-evenly; padding: 5px; width: 45%; margin: 0 auto;' - - cloudSaveParent.appendChild(cloudSaveElement) - cloudSaveParent.appendChild(loadCloudSaveElement) - - subtabElement.appendChild(cloudSaveParent) } else if (!hasAccount(account)) { // User is not logged in subtabElement.querySelector('#open-register')?.addEventListener('click', () => { @@ -393,6 +385,7 @@ export async function handleLogin () { if (loggedIn) { handleWebSocket() + handleCloudSaves() } } @@ -456,7 +449,8 @@ function handleWebSocket () { consumable.ends.push(data.startedAt + 3600 * 1000) consumable.amount++ - Notification(`Someone redeemed a(n) ${data.consumable}!`) + const article = /^[AEIOU]/i.test(data.displayName) ? 'an' : 'a' + Notification(`Someone redeemed ${article} ${data.displayName}!`) } else if (data.type === 'consumable-ended') { // Because of the invariant that the timestamps are sorted, we can just remove the first element const consumable = allDurableConsumables[data.consumable as PseudoCoinConsumableNames] @@ -538,37 +532,6 @@ async function logout () { location.reload() } -async function saveToCloud () { - const save = localStorage.getItem('Synergysave2') - - if (typeof save !== 'string') { - console.log('Yeah, no save here.') - return - } - - const body = new FormData() - body.set('savefile', new File([save], 'file.txt'), 'file.txt') - - const response = await fetch('https://synergism.cc/api/v1/saves/upload', { - method: 'POST', - body - }) - - if (!response.ok) { - await Alert(`Received an error: ${await response.text()}`) - return - } -} - -async function getCloudSave () { - const response = await fetch('https://synergism.cc/api/v1/saves/get') - const save = await response.json() as CloudSave - - if (save !== null) { - importSynergism(save.save) - } -} - const hasCaptcha = new WeakSet() export function renderCaptcha () { @@ -576,6 +539,7 @@ export function renderCaptcha () { const visible = captchaElements.find((el) => el.offsetParent !== null) if (visible && !hasCaptcha.has(visible)) { + // biome-ignore lint/correctness/noUndeclaredVariables: declared in types as a global turnstile.render(visible, { sitekey: visible.getAttribute('data-sitekey')!, 'error-callback' () {}, @@ -618,9 +582,9 @@ const createFastForward = (name: PseudoCoinTimeskipNames, minutes: number) => { addTimers('transcension', seconds) addTimers('reincarnation', seconds) automaticTools('antSacrifice', seconds) - player.prestigeCount += seconds / Math.max(0.01, player.fastestprestige) - player.transcendCount += seconds / Math.max(0.01, player.fastesttranscend) - player.reincarnationCount += seconds / Math.max(0.01, player.fastestreincarnate) + updatePrestigeCount(seconds / Math.max(0.25, player.fastestprestige)) + updateTranscensionCount(seconds / Math.max(0.25, player.fastesttranscend)) + updateReincarnationCount(seconds / Math.max(0.25, player.fastestreincarnate)) // Add Obt/Off, why not? automaticTools('addObtainium', seconds) @@ -758,3 +722,242 @@ const activateTimeSkip = (name: PseudoCoinTimeskipNames, minutes: number) => { break } } + +function handleCloudSaves () { + const subtabElement = document.querySelector('#accountSubTab div#right.scrollbarX')! + const table = subtabElement.querySelector('#table > #dataGrid')! + + const uploadButton = subtabElement.querySelector('button#upload') + const transferButton = subtabElement.querySelector('button#transfer') + + function populateTable () { + fetch('/saves/retrieve/all') + .then((response) => response.json()) + .then(($saves: Save[]) => { + cloudSaves.length = 0 + cloudSaves.push(...$saves) + + const existingRows = table.querySelectorAll('.grid-row') + existingRows.forEach((row) => row.remove()) + + const content = table.querySelector('.details-content') + content?.remove() + + if (cloudSaves.length === 0) { + const emptyDiv = document.createElement('div') + emptyDiv.className = 'grid-row empty-state' + emptyDiv.style.gridColumn = '1 / -1' + emptyDiv.textContent = i18next.t('account.noSaves') + table.appendChild(emptyDiv) + return + } + + cloudSaves.forEach(({ id, name, uploadedAt }, index) => { + const rowDiv = document.createElement('div') + rowDiv.className = 'grid-row' + rowDiv.style.display = 'contents' + + const idCell = document.createElement('div') + idCell.className = 'grid-cell id-cell' + idCell.textContent = `#${id}` + + const nameCell = document.createElement('div') + nameCell.className = 'grid-cell name-cell' + nameCell.textContent = name.length > 60 ? `${name.slice(0, 60)}...` : name + + const dateCell = document.createElement('div') + dateCell.className = 'grid-cell date-cell' + dateCell.textContent = new Date(uploadedAt).toLocaleString() + + rowDiv.appendChild(idCell) + rowDiv.appendChild(nameCell) + rowDiv.appendChild(dateCell) + + if (index % 2 === 0) { + idCell.classList.add('alt-row') + nameCell.classList.add('alt-row') + dateCell.classList.add('alt-row') + } + + // Create the expandable details row + const detailsRow = document.createElement('div') + detailsRow.className = 'grid-details-row' + detailsRow.style.display = 'none' + detailsRow.style.gridColumn = '1 / -1' + + const detailsContent = document.createElement('div') + detailsContent.className = 'details-content' + detailsContent.innerHTML = ` +
+ + + +
+ ` + + detailsRow.appendChild(detailsContent) + + rowDiv.addEventListener('click', () => { + const isVisible = detailsRow.style.display !== 'none' + + const allDetailsRows = table.querySelectorAll('.grid-details-row') + allDetailsRows.forEach((row) => { + if (row !== detailsRow) { + row.style.display = 'none' + } + }) + + detailsRow.style.display = isVisible ? 'none' : 'block' + }) + + detailsContent.addEventListener('click', (e) => { + e.stopPropagation() + + const target = e.target as HTMLElement + const saveId = Number(target.getAttribute('data-id')) + + if (target.classList.contains('btn-download')) { + handleDownload(saveId) + } else if (target.classList.contains('btn-load')) { + handleLoadSave(saveId) + } else if (target.classList.contains('btn-delete')) { + handleDeleteSave(saveId) + } + }) + + table.appendChild(rowDiv) + table.appendChild(detailsRow) + }) + + async function decodeSave (save: string) { + const decoded = atob(save) + const bytes = new Uint8Array(decoded.length) + for (let i = 0; i < decoded.length; i++) { + bytes[i] = decoded.charCodeAt(i) + } + + const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream('gzip')) + const textBody = await new Response(stream).text() + const encoder = new TextEncoder() + const jsonBytes = encoder.encode(textBody) + const final = btoa(String.fromCharCode(...jsonBytes)) + + return final + } + + async function handleDownload (saveId: number) { + const save = cloudSaves.find((save) => saveId === save.id) + + if (!save) { + Alert(i18next.t('account.noSaveFound')) + return + } + + const decoded = await decodeSave(save.save) + + if (decoded === null) { + Alert('Please send this to Khafra') + return + } + + await exportData(decoded, save.name) + Alert(i18next.t('account.downloadComplete')) + } + + async function handleLoadSave (saveId: number) { + const save = cloudSaves.find((save) => saveId === save.id) + + if (!save) { + Alert(i18next.t('account.noSaveFound')) + return + } + + const decoded = await decodeSave(save.save) + await importSynergism(decoded) + } + + async function handleDeleteSave (saveId: number) { + const save = cloudSaves.find((save) => saveId === save.id) + + if (!save) { + Alert(i18next.t('account.noSaveFound')) + return + } + + const response = await fetch('/saves/delete', { + method: 'DELETE', + body: JSON.stringify({ name: save.name }) + }) + + if (response.ok) { + Alert(i18next.t('account.deletedSave', { name: save.name })) + } else { + console.log(response) + Alert(i18next.t('account.notDeleted')) + } + + populateTable() + } + }) + } + + populateTable() + + // Handle uploading savefiles + uploadButton?.addEventListener('click', () => { + uploadButton.disabled = true + const originalText = uploadButton.textContent + uploadButton.innerHTML = ' Uploading...' + + const name = saveFilename() + const save = localStorage.getItem('Synergysave2') + assert(save !== null, 'no save') + + const fd = new FormData() + fd.set('file', new File([save], name)) + fd.set('name', name) + + fetch('/saves/upload', { + method: 'POST', + body: fd + }).then((response) => { + if (!response.ok) { + throw new TypeError(`Received status ${response.status}`) + } + + uploadButton.textContent = i18next.t('settings.cloud.uploadSuccess') + populateTable() + }).catch((e) => { + console.error(e) + uploadButton.textContent = i18next.t('settings.cloud.uploadFailed') + }).finally(() => { + setTimeout(() => { + uploadButton.disabled = false + uploadButton.textContent = originalText + }, 5000) + }) + }) + + transferButton?.addEventListener('click', () => { + transferButton.disabled = true + const originalText = transferButton.textContent + transferButton.innerHTML = ' Transferring...' + + fetch('/saves/transfer').then((response) => { + if (!response.ok) { + throw new TypeError(`Received status ${response.status}`) + } + + transferButton.textContent = i18next.t('settings.cloud.transferSuccess') + populateTable() + }).catch((e) => { + console.error(e) + transferButton.textContent = i18next.t('settings.cloud.transferFailed') + }).finally(() => { + setTimeout(() => { + transferButton.disabled = false + transferButton.textContent = originalText + }, 5000) + }) + }) +} diff --git a/src/Messages.ts b/src/Messages.ts new file mode 100644 index 000000000..67800047d --- /dev/null +++ b/src/Messages.ts @@ -0,0 +1,268 @@ +import { DOMCacheGetOrSet } from './Cache/DOM' + +export interface Message { + id: number + title: string + content: string + type: 'info' | 'warning' | 'error' | 'success' + priority: number + is_active: boolean + created_at: string + updated_at: string + expires_at?: string +} + +export interface MessageReadStatus { + id: number + user_id: string + message_id: number + read_at: string +} + +export interface ApiResponse { + success: boolean + data?: T + error?: string + id?: number +} + +export interface MessageListResponse extends ApiResponse { + success: true + data: Message[] +} + +export interface MessageResponse extends ApiResponse { + success: true + data: Message +} + +export interface SuccessResponse extends ApiResponse { + success: true +} + +export interface ErrorResponse extends ApiResponse { + success: false + error: string +} + +let unreadMessages: Message[] = [] + +export const fetchUnreadMessages = async (): Promise => { + try { + const response = await fetch('https://synergism.cc/messages/unread', { + credentials: 'include' + }) + + if (!response.ok) { + console.warn('Failed to fetch messages:', response.statusText) + return [] + } + + const result = await response.json() as MessageListResponse + + if (result.success && result.data) { + unreadMessages = result.data.sort((a, b) => b.priority - a.priority) + updateMessagesNotificationBadge() + return unreadMessages + } + + return [] + } catch (error) { + console.error('Error fetching unread messages:', error) + return [] + } +} + +export const markMessageAsRead = async (messageId: number): Promise => { + try { + const response = await fetch(`https://synergism.cc/messages/${messageId}/mark-read`, { + method: 'POST', + credentials: 'include' + }) + + if (!response.ok) { + console.warn('Failed to mark message as read:', response.statusText) + return false + } + + const result = await response.json() as SuccessResponse + + if (result.success) { + // Remove the message from local unread list + unreadMessages = unreadMessages.filter((msg) => msg.id !== messageId) + updateMessagesUI() + updateMessagesNotificationBadge() + return true + } + + return false + } catch (error) { + console.error('Error marking message as read:', error) + return false + } +} + +export const getUnreadMessages = (): Message[] => { + return unreadMessages +} + +export const hasUnreadMessages = (): boolean => { + return unreadMessages.length > 0 +} + +const getMessageTypeColor = (type: Message['type']): string => { + switch (type) { + case 'info': + return '#4A90E2' + case 'warning': + return '#F5A623' + case 'error': + return '#D0021B' + case 'success': + return '#7ED321' + default: + return '#4A90E2' + } +} + +const getMessageTypeIcon = (type: Message['type']): string => { + switch (type) { + case 'info': + return 'ℹ' + case 'warning': + return '⚠' + case 'error': + return '❌' + case 'success': + return '✅' + default: + return 'ℹ' + } +} + +const updateMessagesNotificationBadge = () => { + const messagesSubtabButton = DOMCacheGetOrSet('switchSettingSubTab10') + let badge = messagesSubtabButton.querySelector('.messages-notification-badge') as HTMLElement + + if (!badge) { + // Create the badge if it doesn't exist + badge = document.createElement('span') + badge.className = 'messages-notification-badge' + badge.style.cssText = ` + background-color: #fa3e3e; + border-radius: 2px; + color: white; + padding: 1px 3px; + font-size: 10px; + position: absolute; + top: 0; + right: 0; + display: none; + ` + + // Make the messages subtab button relative positioned to contain the absolute badge + messagesSubtabButton.style.position = 'relative' + messagesSubtabButton.appendChild(badge) + } + + const messageCount = unreadMessages.length + if (messageCount > 0) { + badge.textContent = messageCount.toString() + badge.style.display = 'block' + } else { + badge.style.display = 'none' + } +} + +export const updateMessagesUI = () => { + const messagesContainer = DOMCacheGetOrSet('messagesContainer') + + if (unreadMessages.length === 0) { + messagesContainer.innerHTML = ` +
+ No unread messages +
+ ` + return + } + + messagesContainer.innerHTML = unreadMessages + .map((message) => ` +
+
+
+ ${getMessageTypeIcon(message.type)} + + ${message.title} + +
+ +
+
+ ${message.content} +
+
+ Priority: ${message.priority} + ${new Date(message.created_at).toLocaleDateString()} +
+
+ `) + .join('') + + // Add event listeners for mark as read buttons + const markReadButtons = messagesContainer.querySelectorAll('.mark-read-btn') + markReadButtons.forEach((button) => { + button.addEventListener('click', async (e) => { + const messageId = Number.parseInt((e.target as HTMLElement).getAttribute('data-message-id')!) + const success = await markMessageAsRead(messageId) + + if (success) { + // Remove the message element from DOM + const messageItem = button.closest('.message-item') as HTMLElement + if (messageItem) { + messageItem.style.transition = 'opacity 0.3s ease-out' + messageItem.style.opacity = '0' + setTimeout(() => { + updateMessagesUI() + }, 300) + } + } + }) + }) +} + +// Initialize messages on load +export const initializeMessages = async () => { + await fetchUnreadMessages() + updateMessagesUI() + updateMessagesNotificationBadge() +} diff --git a/src/Octeracts.ts b/src/Octeracts.ts index 5f62d26fa..6d419132f 100644 --- a/src/Octeracts.ts +++ b/src/Octeracts.ts @@ -1,277 +1,136 @@ import i18next from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' import { calculateOcteractMultiplier } from './Calculate' -import { campaignTokenRewardHTMLUpdate } from './Campaign' -import type { IUpgradeData } from './DynamicUpgrade' -import { DynamicUpgrade } from './DynamicUpgrade' -import { format, formatTimeShort, player } from './Synergism' -import type { Player } from './types/Synergism' +import { updateMaxTokens, updateTokens } from './Campaign' +import { format, formatAsPercentIncrease, formatTimeShort, player } from './Synergism' import { Alert, Prompt } from './UpdateHTML' +import { isMobile } from './Utility' -export interface IOcteractData extends Omit { - costFormula(this: void, level: number, baseCost: number): number - cacheUpdates?: (() => void)[] // TODO: Improve this type signature -Plat - octeractsInvested?: number - qualityOfLife?: boolean -} - -export class OcteractUpgrade extends DynamicUpgrade { - readonly costFormula: (level: number, baseCost: number) => number - public octeractsInvested = 0 - public qualityOfLife: boolean - readonly cacheUpdates: (() => void)[] | undefined - #key: string - - constructor (data: IOcteractData, key: string) { - const name = i18next.t(`octeract.data.${key}.name`) - const description = i18next.t(`octeract.data.${key}.description`) - super({ ...data, name, description }) - this.costFormula = data.costFormula - this.octeractsInvested = data.octeractsInvested ?? 0 - this.qualityOfLife = data.qualityOfLife ?? false - this.cacheUpdates = data.cacheUpdates ?? undefined - this.#key = key - } - - getCostTNL (): number { - if (this.level === this.maxLevel) { - return 0 - } - - return this.costFormula(this.level, this.costPerLevel) - } - - /** - * Buy levels up until togglebuy or maxed. - * @returns An alert indicating cannot afford, already maxed or purchased with how many - * levels purchased - */ - public async buyLevel (event: MouseEvent): Promise { - let purchased = 0 - let maxPurchasable = 1 - let OCTBudget = player.wowOcteracts - - if (event.shiftKey) { - maxPurchasable = 1000000 - const buy = Number( - await Prompt(`${i18next.t('octeract.buyLevel.buyPrompt', { n: format(player.wowOcteracts, 0, true) })}`) - ) - - if (isNaN(buy) || !isFinite(buy) || !Number.isInteger(buy)) { // nan + Infinity checks - return Alert(i18next.t('general.validation.finite')) - } - - if (buy === -1) { - OCTBudget = player.wowOcteracts - } else if (buy <= 0) { - return Alert(i18next.t('octeract.buyLevel.cancelPurchase')) - } else { - OCTBudget = buy - } - OCTBudget = Math.min(player.wowOcteracts, OCTBudget) - } - - if (this.maxLevel > 0) { - maxPurchasable = Math.min(maxPurchasable, this.maxLevel - this.level) - } - - if (maxPurchasable === 0) { - return Alert(i18next.t('octeract.buyLevel.alreadyMax')) - } +export type OcteractDataKeys = + | 'octeractStarter' + | 'octeractGain' + | 'octeractGain2' + | 'octeractQuarkGain' + | 'octeractQuarkGain2' + | 'octeractCorruption' + | 'octeractGQCostReduce' + | 'octeractExportQuarks' + | 'octeractImprovedDaily' + | 'octeractImprovedDaily2' + | 'octeractImprovedDaily3' + | 'octeractImprovedQuarkHept' + | 'octeractImprovedGlobalSpeed' + | 'octeractImprovedAscensionSpeed' + | 'octeractImprovedAscensionSpeed2' + | 'octeractImprovedFree' + | 'octeractImprovedFree2' + | 'octeractImprovedFree3' + | 'octeractImprovedFree4' + | 'octeractSingUpgradeCap' + | 'octeractOfferings1' + | 'octeractObtainium1' + | 'octeractAscensions' + | 'octeractAscensions2' + | 'octeractAscensionsOcteractGain' + | 'octeractFastForward' + | 'octeractAutoPotionSpeed' + | 'octeractAutoPotionEfficiency' + | 'octeractOneMindImprover' + | 'octeractAmbrosiaLuck' + | 'octeractAmbrosiaLuck2' + | 'octeractAmbrosiaLuck3' + | 'octeractAmbrosiaLuck4' + | 'octeractAmbrosiaGeneration' + | 'octeractAmbrosiaGeneration2' + | 'octeractAmbrosiaGeneration3' + | 'octeractAmbrosiaGeneration4' + | 'octeractBonusTokens1' + | 'octeractBonusTokens2' + | 'octeractBonusTokens3' + | 'octeractBonusTokens4' + | 'octeractBlueberries' + | 'octeractInfiniteShopUpgrades' + | 'octeractTalismanLevelCap1' + | 'octeractTalismanLevelCap2' + | 'octeractTalismanLevelCap3' + | 'octeractTalismanLevelCap4' - while (maxPurchasable > 0) { - const cost = this.getCostTNL() - if (player.wowOcteracts < cost || OCTBudget < cost) { - break - } else { - player.wowOcteracts -= cost - OCTBudget -= cost - this.octeractsInvested += cost - this.level += 1 - purchased += 1 - maxPurchasable -= 1 - } - } - - if (purchased === 0) { - return Alert(i18next.t('octeract.buyLevel.cannotAfford')) - } - if (purchased > 1) { - return Alert(`${i18next.t('octeract.buyLevel.multiBuy', { n: format(purchased) })}`) - } - - this.updateCaches() - this.updateUpgradeHTML() - } - - /** - * Given an upgrade, give a concise information regarding its data. - * @returns A string that details the name, description, level statistic, and next level cost. - */ - toString (): string { - const costNextLevel = this.getCostTNL() - const maxLevel = this.maxLevel === -1 - ? '' - : `/${format(this.maxLevel, 0, true)}` - const isMaxLevel = this.maxLevel === this.level - const color = isMaxLevel ? 'plum' : 'white' - - let freeLevelInfo = this.freeLevels > 0 - ? ` [+${format(this.freeLevels, 1, true)}]` - : '' - - if (this.freeLevels > this.level) { - freeLevelInfo = `${freeLevelInfo}${ - i18next.t('general.softCapped') - }` - } - - const isAffordable = costNextLevel <= player.wowOcteracts - let affordTime = '' - if (!isMaxLevel && !isAffordable) { - const octPerSecond = calculateOcteractMultiplier() - affordTime = octPerSecond > 0 - ? formatTimeShort((costNextLevel - player.wowOcteracts) / octPerSecond) - : `${i18next.t('general.infinity')}` - } - const affordableInfo = isMaxLevel - ? ` ${i18next.t('general.maxed')}` - : isAffordable - ? ` ${i18next.t('general.affordable')}` - : ` ${i18next.t('octeract.toString.becomeAffordable', { n: affordTime })}` - - const costNextLevelStr = i18next.t('octeract.toString.costNextLevel', { - amount: format(costNextLevel, 2, true, true, true), - resource: i18next.t('tabs.singularity.octeracts') // TODO: dedicated i18n translation rather than stealing from tabs - }) - - const spentOcteractsStr = i18next.t('octeract.toString.spentOcteracts', { - amount: format(this.octeractsInvested, 2, true, true, true) - }) - - return `${this.name} - ${this.description} - ${i18next.t('general.level')} ${ - format(this.level, 0, true) - }${maxLevel}${freeLevelInfo} - ${this.getEffect().desc} - ${costNextLevelStr}${affordableInfo} - ${spentOcteractsStr}` - } - - public updateUpgradeHTML (): void { - DOMCacheGetOrSet('singularityOcteractsMultiline').innerHTML = this.toString() - DOMCacheGetOrSet('octeractAmount').innerHTML = i18next.t('octeract.amount', { - octeracts: format(player.wowOcteracts, 2, true, true, true) - }) - } - - public computeFreeLevelSoftcap (): number { - const freeLevelMult = 1 + 0.3 / 100 * player.cubeUpgrades[78] - return this.freeLevels * freeLevelMult - } - - public actualTotalLevels (): number { - if ( - (player.singularityChallenges.noOcteracts.enabled || player.singularityChallenges.sadisticPrequel.enabled) - && !this.qualityOfLife - ) { - return 0 - } - const actualFreeLevels = this.computeFreeLevelSoftcap() - - if (this.level >= actualFreeLevels) { - return actualFreeLevels + this.level - } else { - return 2 * Math.sqrt(actualFreeLevels * this.level) - } - } - - public getEffect (): { bonus: number | boolean; desc: string } { - return this.effect(this.actualTotalLevels()) - } - - public refund (): void { - player.wowOcteracts += this.octeractsInvested - this.level = 0 - this.octeractsInvested = 0 - } - - updateCaches (): void { - if (this.cacheUpdates !== undefined) { - for (const cache of this.cacheUpdates) { - cache() - } - } - } - - valueOf (): IOcteractData { - return { - costFormula: this.costFormula, - costPerLevel: this.costPerLevel, - maxLevel: this.maxLevel, - cacheUpdates: this.cacheUpdates, - effect: this.effect, - freeLevels: this.freeLevels, - level: this.level, - octeractsInvested: this.octeractsInvested, - qualityOfLife: this.qualityOfLife, - toggleBuy: this.toggleBuy - } - } - - key () { - return this.#key - } +export interface OcteractUpgrade { + level: number + freeLevel: number + octeractsInvested: number + maxLevel: number + qualityOfLife: boolean + costPerLevel: number + costFormula(this: void, level: number, baseCost: number): number + effect(n: number): number + effectDescription(n: number): string + name(): string + description(): string } -export const octeractData: Record = { +export const octeractUpgrades: Record = { octeractStarter: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, + maxLevel: 1, + costPerLevel: 1e-15, + qualityOfLife: false, costFormula: (level: number, baseCost: number) => { return baseCost * (level + 1) }, - maxLevel: 1, - costPerLevel: 1e-15, effect: (n: number) => { - return { - bonus: n > 0 ? 1.4 : 1, - get desc () { - return i18next.t('octeract.data.octeractStarter.effect', { n: (n > 0) ? '' : 'not' }) - } - } - } + return 1 + 0.4 * n + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractStarter.effect', { n: (n > 0) ? '' : 'not' }), + name: () => i18next.t('octeract.data.octeractStarter.name'), + description: () => i18next.t('octeract.data.octeractStarter.description') }, octeractGain: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, + maxLevel: 1e8, + costPerLevel: 1e-8, + qualityOfLife: false, costFormula: (level: number, baseCost: number) => { return baseCost * (Math.pow(level + 1, 6) - Math.pow(level, 6)) }, - maxLevel: 1e8, - costPerLevel: 1e-8, effect: (n: number) => { - return { - bonus: 1 + 0.011 * n, - get desc () { - return i18next.t('octeract.data.octeractGain.effect', { n: format(n, 0, true) }) - } - } - } + return 1 + 0.01 * n + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('octeract.data.octeractGain.effect', { n: formatAsPercentIncrease(effectValue, 2) }) + }, + name: () => i18next.t('octeract.data.octeractGain.name'), + description: () => i18next.t('octeract.data.octeractGain.description') }, octeractGain2: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(10, Math.pow(level, 0.5) / 3) }, maxLevel: -1, costPerLevel: 1e10, + qualityOfLife: false, effect: (n: number) => { - return { - bonus: 1 + 0.01 * n, - get desc () { - return i18next.t('octeract.data.octeractGain2.effect', { n: format(n, 0, true) }) - } - } - } + return 1 + 0.01 * n + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('octeract.data.octeractGain2.effect', { n: formatAsPercentIncrease(effectValue, 2) }) + }, + name: () => i18next.t('octeract.data.octeractGain2.name'), + description: () => i18next.t('octeract.data.octeractGain2.description') }, octeractQuarkGain: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { if (level < 1000) { return baseCost * (Math.pow(level + 1, 7) - Math.pow(level, 7)) @@ -284,262 +143,310 @@ export const octeractData: Record { - return { - bonus: 1 + 0.011 * n, - get desc () { - return i18next.t('octeract.data.octeractQuarkGain.effect', { n: format(1.1 * n, 0, true) }) - } - } - } + return 1 + 0.011 * n + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('octeract.data.octeractQuarkGain.effect', { n: formatAsPercentIncrease(effectValue, 2) }) + }, + name: () => i18next.t('octeract.data.octeractQuarkGain.name'), + description: () => i18next.t('octeract.data.octeractQuarkGain.description') }, octeractQuarkGain2: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(1e20, level) }, maxLevel: 5, costPerLevel: 1e22, + qualityOfLife: false, effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t('octeract.data.octeractQuarkGain2.effect', { n: n > 0 ? '' : 'NOT' }) - } - } - } + return n + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractQuarkGain2.effect', { n: n > 0 ? '' : 'NOT' }), + name: () => i18next.t('octeract.data.octeractQuarkGain2.name'), + description: () => i18next.t('octeract.data.octeractQuarkGain2.description') }, octeractCorruption: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(10, level * 10) }, maxLevel: 2, costPerLevel: 10, + qualityOfLife: false, effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('octeract.data.octeractCorruption.effect', { n }) - } - } - } + return n + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractCorruption.effect', { n }), + name: () => i18next.t('octeract.data.octeractCorruption.name'), + description: () => i18next.t('octeract.data.octeractCorruption.description') }, octeractGQCostReduce: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(2, level) }, maxLevel: 50, costPerLevel: 1e-9, + qualityOfLife: false, effect: (n: number) => { - return { - bonus: 1 - n / 100, - get desc () { - return i18next.t('octeract.data.octeractGQCostReduce.effect', { n }) - } - } - } + return 1 - n / 100 + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractGQCostReduce.effect', { n }), + name: () => i18next.t('octeract.data.octeractGQCostReduce.name'), + description: () => i18next.t('octeract.data.octeractGQCostReduce.description') }, octeractExportQuarks: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(level + 1, 3) }, maxLevel: 100, costPerLevel: 1, + qualityOfLife: false, effect: (n: number) => { - return { - bonus: 4 * n / 10 + 1, - get desc () { - return i18next.t('octeract.data.octeractExportQuarks.effect', { n: format(40 * n, 0, true) }) - } - } - } + return 4 * n / 10 + 1 + }, + effectDescription: (n: number) => + i18next.t('octeract.data.octeractExportQuarks.effect', { n: format(40 * n, 0, true) }), + name: () => i18next.t('octeract.data.octeractExportQuarks.name'), + description: () => i18next.t('octeract.data.octeractExportQuarks.description') }, octeractImprovedDaily: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(1.6, level) }, maxLevel: 50, costPerLevel: 1e-3, effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('octeract.data.octeractImprovedDaily.effect', { n }) - } - } + return n }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractImprovedDaily.effect', { n }), + name: () => i18next.t('octeract.data.octeractImprovedDaily.name'), + description: () => i18next.t('octeract.data.octeractImprovedDaily.description'), qualityOfLife: true }, octeractImprovedDaily2: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(2, level) }, maxLevel: 50, costPerLevel: 1e-2, effect: (n: number) => { - return { - bonus: 1 + 0.01 * n, - get desc () { - return i18next.t('octeract.data.octeractImprovedDaily2.effect', { n }) - } - } + return 1 + 0.01 * n + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('octeract.data.octeractImprovedDaily2.effect', { n: formatAsPercentIncrease(effectValue, 2) }) }, + name: () => i18next.t('octeract.data.octeractImprovedDaily2.name'), + description: () => i18next.t('octeract.data.octeractImprovedDaily2.description'), qualityOfLife: true }, octeractImprovedDaily3: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(20, level) }, maxLevel: -1, costPerLevel: 1e20, effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('octeract.data.octeractImprovedDaily3.effect', { n: `${n} +${0.5 * n}%` }) - } - } + return n }, + effectDescription: (n: number) => + i18next.t('octeract.data.octeractImprovedDaily3.effect', { n: `${n} +${0.5 * n}%` }), + name: () => i18next.t('octeract.data.octeractImprovedDaily3.name'), + description: () => i18next.t('octeract.data.octeractImprovedDaily3.description'), qualityOfLife: true }, octeractImprovedQuarkHept: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { - return baseCost * Math.pow(1e6, level) + return baseCost * Math.pow(1e3, level) }, - maxLevel: 3, + maxLevel: 25, costPerLevel: 1 / 10, effect: (n: number) => { - return { - bonus: n / 100, - get desc () { - return i18next.t('octeract.data.octeractImprovedQuarkHept.effect', { n: format(n / 100, 2, true) }) - } - } - } + return n / 50 + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('octeract.data.octeractImprovedQuarkHept.effect', { n: format(effectValue, 2, true) }) + }, + name: () => i18next.t('octeract.data.octeractImprovedQuarkHept.name'), + description: () => i18next.t('octeract.data.octeractImprovedQuarkHept.description'), + qualityOfLife: false }, octeractImprovedGlobalSpeed: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(level + 1, 3) }, maxLevel: 1000, costPerLevel: 1e-5, effect: (n: number) => { - return { - bonus: n / 100, - get desc () { - return i18next.t('octeract.data.octeractImprovedGlobalSpeed.effect', { n: format(n, 0, true) }) - } - } - } + return n / 100 + }, + effectDescription: (n: number) => + i18next.t('octeract.data.octeractImprovedGlobalSpeed.effect', { n: format(n, 0, true) }), + name: () => i18next.t('octeract.data.octeractImprovedGlobalSpeed.name'), + description: () => i18next.t('octeract.data.octeractImprovedGlobalSpeed.description'), + qualityOfLife: false }, octeractImprovedAscensionSpeed: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(1e9, level / 100) }, maxLevel: 100, costPerLevel: 100, effect: (n: number) => { - return { - bonus: n / 2000, - get desc () { - return i18next.t('octeract.data.octeractImprovedAscensionSpeed.effect', { n: format(n / 20, 2, true) }) - } - } - } + return n / 2000 + }, + effectDescription: (n: number) => + i18next.t('octeract.data.octeractImprovedAscensionSpeed.effect', { n: format(n / 20, 2, true) }), + name: () => i18next.t('octeract.data.octeractImprovedAscensionSpeed.name'), + description: () => i18next.t('octeract.data.octeractImprovedAscensionSpeed.description'), + qualityOfLife: false }, octeractImprovedAscensionSpeed2: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(1e12, level / 250) }, maxLevel: 250, costPerLevel: 1e5, effect: (n: number) => { - return { - bonus: n / 2000, - get desc () { - return i18next.t('octeract.data.octeractImprovedAscensionSpeed2.effect', { n: format(n / 50, 2, true) }) - } - } - } + return n / 2000 + }, + effectDescription: (n: number) => + i18next.t('octeract.data.octeractImprovedAscensionSpeed2.effect', { n: format(n / 50, 2, true) }), + name: () => i18next.t('octeract.data.octeractImprovedAscensionSpeed2.name'), + description: () => i18next.t('octeract.data.octeractImprovedAscensionSpeed2.description'), + qualityOfLife: false }, octeractImprovedFree: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(level + 1, 3) }, maxLevel: 1, costPerLevel: 100, effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t('octeract.data.octeractImprovedFree.effect', { n: (n > 0) ? '' : 'NOT' }) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t('octeract.data.octeractImprovedFree.effect', { n: (n > 0) ? '' : 'NOT' }), + name: () => i18next.t('octeract.data.octeractImprovedFree.name'), + description: () => i18next.t('octeract.data.octeractImprovedFree.description'), + qualityOfLife: false }, octeractImprovedFree2: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(level + 1, 3) }, maxLevel: 1, costPerLevel: 1e7, effect: (n: number) => { - return { - bonus: 0.05 * n, - get desc () { - return i18next.t('octeract.data.octeractImprovedFree2.effect', { n: format(n / 20, 2, true) }) - } - } - } + return 0.05 * n + }, + effectDescription: (n: number) => + i18next.t('octeract.data.octeractImprovedFree2.effect', { n: format(n / 20, 2, true) }), + name: () => i18next.t('octeract.data.octeractImprovedFree2.name'), + description: () => i18next.t('octeract.data.octeractImprovedFree2.description'), + qualityOfLife: false }, octeractImprovedFree3: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(level + 1, 3) }, maxLevel: 1, costPerLevel: 1e17, effect: (n: number) => { - return { - bonus: 0.05 * n, - get desc () { - return i18next.t('octeract.data.octeractImprovedFree3.effect', { n: format(n / 20, 2, true) }) - } - } - } + return 0.05 * n + }, + effectDescription: (n: number) => + i18next.t('octeract.data.octeractImprovedFree3.effect', { n: format(n / 20, 2, true) }), + name: () => i18next.t('octeract.data.octeractImprovedFree3.name'), + description: () => i18next.t('octeract.data.octeractImprovedFree3.description'), + qualityOfLife: false }, octeractImprovedFree4: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(1e20, level / 40) }, maxLevel: 40, costPerLevel: 1e20, effect: (n: number) => { - return { - bonus: 0.001 * n + ((n > 0) ? 0.01 : 0), - get desc () { - return i18next.t('octeract.data.octeractImprovedFree4.effect', { - n: format(0.001 * n + ((n > 0) ? 0.01 : 0), 3, true) - }) - } - } - } + return 0.001 * n + ((n > 0) ? 0.01 : 0) + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('octeract.data.octeractImprovedFree4.effect', { n: format(effectValue, 3, true) }) + }, + name: () => i18next.t('octeract.data.octeractImprovedFree4.name'), + description: () => i18next.t('octeract.data.octeractImprovedFree4.description'), + qualityOfLife: false }, octeractSingUpgradeCap: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(1e3, level) }, maxLevel: 10, costPerLevel: 1e10, effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('octeract.data.octeractSingUpgradeCap.effect', { n }) - } - } + return n }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractSingUpgradeCap.effect', { n }), + name: () => i18next.t('octeract.data.octeractSingUpgradeCap.name'), + description: () => i18next.t('octeract.data.octeractSingUpgradeCap.description'), qualityOfLife: true }, octeractOfferings1: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { if (level < 25) { return baseCost * Math.pow(level + 1, 5) @@ -550,15 +457,20 @@ export const octeractData: Record { - return { - bonus: 1 + 0.01 * n, - get desc () { - return i18next.t('octeract.data.octeractOfferings1.effect', { n: format(n) }) - } - } - } + return 1 + 0.01 * n + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('octeract.data.octeractOfferings1.effect', { n: formatAsPercentIncrease(effectValue, 2) }) + }, + name: () => i18next.t('octeract.data.octeractOfferings1.name'), + description: () => i18next.t('octeract.data.octeractOfferings1.description'), + qualityOfLife: false }, octeractObtainium1: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { if (level < 25) { return baseCost * Math.pow(level + 1, 5) @@ -569,109 +481,133 @@ export const octeractData: Record { - return { - bonus: 1 + 0.01 * n, - get desc () { - return i18next.t('octeract.data.octeractObtainium1.effect', { n: format(n) }) - } - } - } + return 1 + 0.01 * n + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('octeract.data.octeractObtainium1.effect', { n: formatAsPercentIncrease(effectValue, 2) }) + }, + name: () => i18next.t('octeract.data.octeractObtainium1.name'), + description: () => i18next.t('octeract.data.octeractObtainium1.description'), + qualityOfLife: false }, octeractAscensions: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(level + 1, 3) }, maxLevel: 1000000, costPerLevel: 1, effect: (n: number) => { - return { - bonus: (1 + n / 100) * (1 + 2 * Math.floor(n / 10) / 100), - get desc () { - return i18next.t('octeract.data.octeractAscensions.effect', { - n: format((100 + n) * (1 + 2 * Math.floor(n / 10) / 100) - 100, 1, true) - }) - } - } - } + return (1 + n / 100) * (1 + 2 * Math.floor(n / 10) / 100) + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('octeract.data.octeractAscensions.effect', { + n: format((effectValue - 1) * 100, 1, true) + }) + }, + name: () => i18next.t('octeract.data.octeractAscensions.name'), + description: () => i18next.t('octeract.data.octeractAscensions.description'), + qualityOfLife: false }, octeractAscensions2: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(10, Math.pow(level, 0.5) / 3) }, maxLevel: -1, costPerLevel: 1e12, effect: (n: number) => { - return { - bonus: (1 + n / 100) * (1 + 2 * Math.floor(n / 10) / 100), - get desc () { - return i18next.t('octeract.data.octeractAscensions2.effect', { - n: format((100 + n) * (1 + 2 * Math.floor(n / 10) / 100) - 100, 1, true) - }) - } - } - } + return (1 + n / 100) * (1 + 2 * Math.floor(n / 10) / 100) + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('octeract.data.octeractAscensions2.effect', { + n: format((effectValue - 1) * 100, 1, true) + }) + }, + name: () => i18next.t('octeract.data.octeractAscensions2.name'), + description: () => i18next.t('octeract.data.octeractAscensions2.description'), + qualityOfLife: false }, octeractAscensionsOcteractGain: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(40, level) }, maxLevel: -1, costPerLevel: 1000, effect: (n: number) => { - return { - bonus: n / 100, - get desc () { - return i18next.t('octeract.data.octeractAscensionsOcteractGain.effect', { n: format(n, 1, true) }) - } - } - } + return n / 100 + }, + effectDescription: (n: number) => + i18next.t('octeract.data.octeractAscensionsOcteractGain.effect', { n: format(n, 1, true) }), + name: () => i18next.t('octeract.data.octeractAscensionsOcteractGain.name'), + description: () => i18next.t('octeract.data.octeractAscensionsOcteractGain.description'), + qualityOfLife: false }, octeractFastForward: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(1e8, level) }, maxLevel: 2, costPerLevel: 1e8, effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('octeract.data.octeractFastForward.effect', { n100: 100 * n, n }) - } - } - } + return n + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractFastForward.effect', { n100: 100 * n, n }), + name: () => i18next.t('octeract.data.octeractFastForward.name'), + description: () => i18next.t('octeract.data.octeractFastForward.description'), + qualityOfLife: false }, octeractAutoPotionSpeed: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(10, level) }, maxLevel: -1, costPerLevel: 1e-10, effect: (n: number) => { - return { - bonus: 1 + 4 * n / 100, - get desc () { - return i18next.t('octeract.data.octeractAutoPotionSpeed.effect', { n: 4 * n }) - } - } - } + return 1 + 4 * n / 100 + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractAutoPotionSpeed.effect', { n: 4 * n }), + name: () => i18next.t('octeract.data.octeractAutoPotionSpeed.name'), + description: () => i18next.t('octeract.data.octeractAutoPotionSpeed.description'), + qualityOfLife: false }, octeractAutoPotionEfficiency: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(10, level) }, maxLevel: 100, costPerLevel: 1e-10 * Math.pow(10, 0.5), effect: (n: number) => { - return { - bonus: 1 + 2 * n / 100, - get desc () { - return i18next.t('octeract.data.octeractAutoPotionEfficiency.effect', { n: 2 * n }) - } - } - } + return 1 + 2 * n / 100 + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractAutoPotionEfficiency.effect', { n: 2 * n }), + name: () => i18next.t('octeract.data.octeractAutoPotionEfficiency.name'), + description: () => i18next.t('octeract.data.octeractAutoPotionEfficiency.description'), + qualityOfLife: false }, octeractOneMindImprover: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { const fasterMult = (level >= 10) ? (Math.pow(1e3, level - 10)) : 1 return baseCost * Math.pow(1e5, level) * fasterMult @@ -679,16 +615,20 @@ export const octeractData: Record { - return { - bonus: 0.55 + n / 150, - get desc () { - return i18next.t('octeract.data.octeractOneMindImprover.effect', { n: format(0.55 + n / 150, 3, true) }) - } - } + return 0.55 + n / 150 }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('octeract.data.octeractOneMindImprover.effect', { n: format(effectValue, 3, true) }) + }, + name: () => i18next.t('octeract.data.octeractOneMindImprover.name'), + description: () => i18next.t('octeract.data.octeractOneMindImprover.description'), qualityOfLife: true }, octeractAmbrosiaLuck: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { const useLevel = level + 1 return baseCost * (Math.pow(10, useLevel) - Math.pow(10, useLevel - 1)) @@ -696,48 +636,51 @@ export const octeractData: Record { - return { - bonus: 4 * n, - get desc () { - return i18next.t('octeract.data.octeractAmbrosiaLuck.effect', { n: format(4 * n) }) - } - } + return 4 * n }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractAmbrosiaLuck.effect', { n: format(4 * n) }), + name: () => i18next.t('octeract.data.octeractAmbrosiaLuck.name'), + description: () => i18next.t('octeract.data.octeractAmbrosiaLuck.description'), qualityOfLife: true }, octeractAmbrosiaLuck2: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (Math.pow(level + 1, 6) - Math.pow(level, 6)) }, maxLevel: 30, costPerLevel: 1, effect: (n: number) => { - return { - bonus: 2 * n, - get desc () { - return i18next.t('octeract.data.octeractAmbrosiaLuck2.effect', { n: format(2 * n) }) - } - } + return 2 * n }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractAmbrosiaLuck2.effect', { n: format(2 * n) }), + name: () => i18next.t('octeract.data.octeractAmbrosiaLuck2.name'), + description: () => i18next.t('octeract.data.octeractAmbrosiaLuck2.description'), qualityOfLife: true }, octeractAmbrosiaLuck3: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (Math.pow(level + 1, 8) - Math.pow(level, 8)) }, maxLevel: 30, costPerLevel: 1e30, effect: (n: number) => { - return { - bonus: 3 * n, - get desc () { - return i18next.t('octeract.data.octeractAmbrosiaLuck3.effect', { n: format(3 * n) }) - } - } + return 3 * n }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractAmbrosiaLuck3.effect', { n: format(3 * n) }), + name: () => i18next.t('octeract.data.octeractAmbrosiaLuck3.name'), + description: () => i18next.t('octeract.data.octeractAmbrosiaLuck3.description'), qualityOfLife: true }, octeractAmbrosiaLuck4: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { const useLevel = level + 1 return baseCost * (Math.pow(3, useLevel) - Math.pow(3, useLevel - 1)) @@ -745,16 +688,17 @@ export const octeractData: Record { - return { - bonus: 5 * n, - get desc () { - return i18next.t('octeract.data.octeractAmbrosiaLuck4.effect', { n: format(5 * n) }) - } - } + return 5 * n }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractAmbrosiaLuck4.effect', { n: format(5 * n) }), + name: () => i18next.t('octeract.data.octeractAmbrosiaLuck4.name'), + description: () => i18next.t('octeract.data.octeractAmbrosiaLuck4.description'), qualityOfLife: true }, octeractAmbrosiaGeneration: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { const useLevel = level + 1 return baseCost * (Math.pow(10, useLevel) - Math.pow(10, useLevel - 1)) @@ -762,48 +706,51 @@ export const octeractData: Record { - return { - bonus: 1 + n / 100, - get desc () { - return i18next.t('octeract.data.octeractAmbrosiaGeneration.effect', { n: format(n) }) - } - } + return 1 + n / 100 }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractAmbrosiaGeneration.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractAmbrosiaGeneration.name'), + description: () => i18next.t('octeract.data.octeractAmbrosiaGeneration.description'), qualityOfLife: true }, octeractAmbrosiaGeneration2: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (Math.pow(level + 1, 6) - Math.pow(level, 6)) }, maxLevel: 20, costPerLevel: 1, effect: (n: number) => { - return { - bonus: 1 + n / 100, - get desc () { - return i18next.t('octeract.data.octeractAmbrosiaGeneration2.effect', { n: format(n) }) - } - } + return 1 + n / 100 }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractAmbrosiaGeneration2.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractAmbrosiaGeneration2.name'), + description: () => i18next.t('octeract.data.octeractAmbrosiaGeneration2.description'), qualityOfLife: true }, octeractAmbrosiaGeneration3: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (Math.pow(level + 1, 8) - Math.pow(level, 8)) }, maxLevel: 35, costPerLevel: 1e30, effect: (n: number) => { - return { - bonus: 1 + n / 100, - get desc () { - return i18next.t('octeract.data.octeractAmbrosiaGeneration3.effect', { n: format(n) }) - } - } + return 1 + n / 100 }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractAmbrosiaGeneration3.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractAmbrosiaGeneration3.name'), + description: () => i18next.t('octeract.data.octeractAmbrosiaGeneration3.description'), qualityOfLife: true }, octeractAmbrosiaGeneration4: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { const useLevel = level + 1 return baseCost * (Math.pow(3, useLevel) - Math.pow(3, useLevel - 1)) @@ -811,100 +758,86 @@ export const octeractData: Record { - return { - bonus: 1 + 2 * n / 100, - get desc () { - return i18next.t('octeract.data.octeractAmbrosiaGeneration4.effect', { n: format(2 * n) }) - } - } + return 1 + 2 * n / 100 }, + effectDescription: (n: number) => + i18next.t('octeract.data.octeractAmbrosiaGeneration4.effect', { n: format(2 * n) }), + name: () => i18next.t('octeract.data.octeractAmbrosiaGeneration4.name'), + description: () => i18next.t('octeract.data.octeractAmbrosiaGeneration4.description'), qualityOfLife: true }, octeractBonusTokens1: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(1e2, level) }, maxLevel: 10, costPerLevel: 1e-5, effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('octeract.data.octeractBonusTokens1.effect', { n: format(n) }) - } - } + return n }, - cacheUpdates: [ - () => { - player.campaigns.updateCurrentTokens() - campaignTokenRewardHTMLUpdate() - } - ] + effectDescription: (n: number) => i18next.t('octeract.data.octeractBonusTokens1.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractBonusTokens1.name'), + description: () => i18next.t('octeract.data.octeractBonusTokens1.description'), + qualityOfLife: false }, octeractBonusTokens2: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(1e8, level) }, maxLevel: 5, costPerLevel: 1e8, effect: (n: number) => { - return { - bonus: 1 + n / 100, - get desc () { - return i18next.t('octeract.data.octeractBonusTokens2.effect', { n: format(n) }) - } - } + return 1 + n / 100 }, - cacheUpdates: [ - () => { - player.campaigns.updateCurrentTokens() - campaignTokenRewardHTMLUpdate() - } - ] + effectDescription: (n: number) => i18next.t('octeract.data.octeractBonusTokens2.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractBonusTokens2.name'), + description: () => i18next.t('octeract.data.octeractBonusTokens2.description'), + qualityOfLife: false }, octeractBonusTokens3: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(1e10, level) }, maxLevel: 5, costPerLevel: 1e40, effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('octeract.data.octeractBonusTokens3.effect', { n: format(n) }) - } - } + return n }, - cacheUpdates: [ - () => { - player.campaigns.updateCurrentTokens() - campaignTokenRewardHTMLUpdate() - } - ] + effectDescription: (n: number) => i18next.t('octeract.data.octeractBonusTokens3.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractBonusTokens3.name'), + description: () => i18next.t('octeract.data.octeractBonusTokens3.description'), + qualityOfLife: false }, octeractBonusTokens4: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(4, level) }, maxLevel: 50, costPerLevel: 1e75, effect: (n: number) => { - return { - bonus: 2 * n, - get desc () { - return i18next.t('octeract.data.octeractBonusTokens4.effect', { n: format(2 * n) }) - } - } + return 2 * n }, - cacheUpdates: [ - () => { - player.campaigns.updateCurrentTokens() - campaignTokenRewardHTMLUpdate() - } - ] + effectDescription: (n: number) => i18next.t('octeract.data.octeractBonusTokens4.effect', { n: format(2 * n) }), + name: () => i18next.t('octeract.data.octeractBonusTokens4.name'), + description: () => i18next.t('octeract.data.octeractBonusTokens4.description'), + qualityOfLife: false }, octeractBlueberries: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, maxLevel: 6, costPerLevel: 1, costFormula: (level: number, baseCost: number) => { @@ -916,27 +849,343 @@ export const octeractData: Record { - return { - bonus: n, - get desc () { - return i18next.t('octeract.data.octeractBlueberries.effect', { n: format(n) }) - } - } - } + return n + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractBlueberries.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractBlueberries.name'), + description: () => i18next.t('octeract.data.octeractBlueberries.description'), + qualityOfLife: false }, octeractInfiniteShopUpgrades: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, maxLevel: 80, costPerLevel: 1e30, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(16, level) }, effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('octeract.data.octeractInfiniteShopUpgrades.effect', { n: format(n) }) - } + return n + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractInfiniteShopUpgrades.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractInfiniteShopUpgrades.name'), + description: () => i18next.t('octeract.data.octeractInfiniteShopUpgrades.description'), + qualityOfLife: false + }, + octeractTalismanLevelCap1: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, + maxLevel: 25, + costPerLevel: 1e-5, + costFormula: (level: number, baseCost: number) => { + return baseCost * Math.pow(level + 1, 5) + }, + effect: (n: number) => { + return n + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractTalismanLevelCap1.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractTalismanLevelCap1.name'), + description: () => i18next.t('octeract.data.octeractTalismanLevelCap1.description'), + qualityOfLife: false + }, + octeractTalismanLevelCap2: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, + maxLevel: 35, + costPerLevel: 1e10, + costFormula: (level: number, baseCost: number) => { + return baseCost * Math.pow(level + 1, 10) + }, + effect: (n: number) => { + return n + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractTalismanLevelCap2.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractTalismanLevelCap2.name'), + description: () => i18next.t('octeract.data.octeractTalismanLevelCap2.description'), + qualityOfLife: false + }, + octeractTalismanLevelCap3: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, + maxLevel: 40, + costPerLevel: 1e20, + costFormula: (level: number, baseCost: number) => { + return baseCost * Math.pow(level + 1, 20) + }, + effect: (n: number) => { + return n + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractTalismanLevelCap3.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractTalismanLevelCap3.name'), + description: () => i18next.t('octeract.data.octeractTalismanLevelCap3.description'), + qualityOfLife: false + }, + octeractTalismanLevelCap4: { + level: 0, + freeLevel: 0, + octeractsInvested: 0, + maxLevel: -1, + costPerLevel: 1e40, + costFormula: (level: number, baseCost: number) => { + return baseCost * Math.pow(10, level) + }, + effect: (n: number) => { + return n + }, + effectDescription: (n: number) => i18next.t('octeract.data.octeractTalismanLevelCap4.effect', { n: format(n) }), + name: () => i18next.t('octeract.data.octeractTalismanLevelCap4.name'), + description: () => i18next.t('octeract.data.octeractTalismanLevelCap4.description'), + qualityOfLife: false + } +} + +export const maxOcteractUpgradeAP = Object.values(octeractUpgrades).reduce((acc, upgrade) => { + if (upgrade.maxLevel === -1) { + return acc + } + return acc + 8 +}, 0) + +export const blankOcteractLevelObject: Record< + OcteractDataKeys, + { level: number; freeLevel: number; octeractsInvested: number } +> = Object + .fromEntries( + Object.keys(octeractUpgrades).map((key) => [ + key as OcteractDataKeys, + { + level: 0, + freeLevel: 0, + octeractsInvested: 0 } + ]) + ) as Record + +export const getOcteractUpgradeCostTNL = (upgradeKey: OcteractDataKeys): number => { + const upgrade = octeractUpgrades[upgradeKey] + + if (upgrade.level === upgrade.maxLevel) { + return 0 + } + + return upgrade.costFormula(upgrade.level, upgrade.costPerLevel) +} + +const computeFreeLevelMultiplier = (): number => { + return 1 + 0.3 / 100 * player.cubeUpgrades[78] +} + +export const computeOcteractFreeLevelSoftcap = (upgradeKey: OcteractDataKeys): number => { + const freeLevelMult = computeFreeLevelMultiplier() + const upgrade = octeractUpgrades[upgradeKey] + return upgrade.freeLevel * freeLevelMult +} + +export const actualOcteractUpgradeTotalLevels = (upgradeKey: OcteractDataKeys): number => { + const upgrade = octeractUpgrades[upgradeKey] + + if ( + (player.singularityChallenges.noOcteracts.enabled || player.singularityChallenges.sadisticPrequel.enabled) + && !upgrade.qualityOfLife + ) { + return 0 + } + + const actualFreeLevels = computeOcteractFreeLevelSoftcap(upgradeKey) + + if (upgrade.level >= actualFreeLevels) { + return actualFreeLevels + upgrade.level + } else { + return 2 * Math.sqrt(actualFreeLevels * upgrade.level) + } +} + +export const upgradeOcteractToString = (upgradeKey: OcteractDataKeys): string => { + const upgrade = octeractUpgrades[upgradeKey] + const name = upgrade.name() + const costNextLevel = getOcteractUpgradeCostTNL(upgradeKey) + const freeLevelMult = computeFreeLevelMultiplier() + const freeLevelsWithMult = upgrade.freeLevel * freeLevelMult + const totalEffectiveLevels = actualOcteractUpgradeTotalLevels(upgradeKey) + + const maxLevel = upgrade.maxLevel === -1 + ? '' + : `/${format(upgrade.maxLevel, 0, true)}` + + const isMaxLevel = upgrade.maxLevel === upgrade.level + const color = isMaxLevel ? 'plum' : 'white' + + const nameHTML = `${name}` + const descriptionHTML = `${upgrade.description()}` + + const freeLevelMultText = freeLevelMult > 1 + ? ` (x${format(freeLevelMult, 2, true)})` + : '' + + let freeLevelText = upgrade.freeLevel > 0 + ? ` [+${format(upgrade.freeLevel, 1, true)}${freeLevelMultText}]` + : '' + + if (freeLevelsWithMult > upgrade.level) { + freeLevelText = `${freeLevelText} ${ + i18next.t('general.softCapped') + }` + } + + const effectiveLevelText = totalEffectiveLevels !== upgrade.level + upgrade.freeLevel + ? `
${ + i18next.t('general.effectiveLevel', { + level: format(totalEffectiveLevels, 2, true) + }) + }` + : '' + + const levelHTML = ` ${i18next.t('general.level')} ${ + format(upgrade.level, 0, true) + }${maxLevel}${freeLevelText}` + + const isAffordable = costNextLevel <= player.wowOcteracts + let affordTime = '' + if (!isMaxLevel && !isAffordable) { + const octPerSecond = calculateOcteractMultiplier() + affordTime = octPerSecond > 0 + ? formatTimeShort((costNextLevel - player.wowOcteracts) / octPerSecond) + : `${i18next.t('general.infinity')}` + } + + const affordableInfo = isMaxLevel + ? ` ${i18next.t('general.maxed')}` + : isAffordable + ? ` ${i18next.t('general.affordable')}` + : ` ${i18next.t('octeract.toString.becomeAffordable', { n: affordTime })}` + + const totalLevels = actualOcteractUpgradeTotalLevels(upgradeKey) + const effectHTML = `${upgrade.effectDescription(totalLevels)}` + + const costHTML = (upgrade.level === upgrade.maxLevel && upgrade.maxLevel !== -1) + ? '' + : `${ + i18next.t('octeract.toString.costNextLevel', { + amount: format(costNextLevel, 2, true, true, true) + }) + } ${affordableInfo}` + + const investedOcteractsHTML = upgrade.octeractsInvested > 0 + ? `
${ + i18next.t('octeract.toString.spentOcteracts', { + spent: format(upgrade.octeractsInvested, 2, true, true, true) + }) + }` + : '' + + const qualityOfLifeText = upgrade.qualityOfLife + ? `
${i18next.t('general.alwaysEnabled')}` + : '' + + return `${nameHTML}
${levelHTML}${effectiveLevelText}
${descriptionHTML}
${effectHTML}
${costHTML}${investedOcteractsHTML}${qualityOfLifeText}` +} + +export const updateMobileOcteractHTML = (upgradeKey: OcteractDataKeys): void => { + const elm = DOMCacheGetOrSet('singularityOcteractsMultiline') + elm.innerHTML = upgradeOcteractToString(upgradeKey) + + // MOBILE ONLY - Add a button for buying upgrades + if (isMobile) { + const buttonDiv = document.createElement('div') + + const buyOne = document.createElement('button') + const buyMax = document.createElement('button') + + buyOne.classList.add('modalBtnBuy') + buyOne.textContent = i18next.t('general.buyOne') + buyOne.addEventListener('click', (event: MouseEvent) => { + buyOcteractUpgradeLevel(upgradeKey, event, false) + updateMobileOcteractHTML(upgradeKey) + }) + + buyMax.classList.add('modalBtnBuy') + buyMax.textContent = i18next.t('general.buyMax') + buyMax.addEventListener('click', (event: MouseEvent) => { + buyOcteractUpgradeLevel(upgradeKey, event, true) + updateMobileOcteractHTML(upgradeKey) + }) + + buttonDiv.appendChild(buyOne) + buttonDiv.appendChild(buyMax) + elm.appendChild(buttonDiv) + } +} + +export const buyOcteractUpgradeLevel = async ( + upgradeKey: OcteractDataKeys, + event: MouseEvent, + buyMax = false +): Promise => { + const upgrade = octeractUpgrades[upgradeKey] + let purchased = 0 + let maxPurchasable = 1 + let OCTBudget = player.wowOcteracts + + if (event.shiftKey || buyMax) { + maxPurchasable = 100000000 + const buy = Number( + await Prompt(`${i18next.t('octeract.buyLevel.buyPrompt', { n: format(player.wowOcteracts, 0, true) })}`) + ) + + if (isNaN(buy) || !isFinite(buy) || !Number.isInteger(buy)) { + return Alert(i18next.t('general.validation.finite')) + } + + if (buy === -1) { + OCTBudget = player.wowOcteracts + } else if (buy <= 0) { + return Alert(i18next.t('octeract.buyLevel.cancelPurchase')) + } else { + OCTBudget = buy } + OCTBudget = Math.min(player.wowOcteracts, OCTBudget) } + + if (upgrade.maxLevel > 0) { + maxPurchasable = Math.min(maxPurchasable, upgrade.maxLevel - upgrade.level) + } + + if (maxPurchasable === 0) { + return Alert(i18next.t('octeract.buyLevel.alreadyMax')) + } + + while (maxPurchasable > 0) { + const cost = upgrade.costFormula(upgrade.level, upgrade.costPerLevel) + if (player.wowOcteracts < cost || OCTBudget < cost) { + break + } else { + player.wowOcteracts -= cost + upgrade.octeractsInvested += cost + OCTBudget -= cost + upgrade.level += 1 + purchased += 1 + maxPurchasable -= 1 + } + } + + if (purchased === 0) { + return Alert(i18next.t('octeract.buyLevel.cannotAfford')) + } + + if (purchased > 1) { + Alert(`${i18next.t('octeract.buyLevel.multiBuy', { n: format(purchased) })}`) + } + + updateTokens() + updateMaxTokens() +} + +export const getOcteractUpgradeEffect = (upgradeKey: OcteractDataKeys): number => { + const upgrade = octeractUpgrades[upgradeKey] + const totalLevels = actualOcteractUpgradeTotalLevels(upgradeKey) + return upgrade.effect(totalLevels) } diff --git a/src/Platonic.ts b/src/Platonic.ts index 3a56b26dc..f76b9f0c6 100644 --- a/src/Platonic.ts +++ b/src/Platonic.ts @@ -1,5 +1,6 @@ import i18next from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' +import { hepteracts } from './Hepteracts' import { calculateSingularityDebuff } from './singularity' import { format, player } from './Synergism' import { Alert, revealStuff } from './UpdateHTML' @@ -30,7 +31,7 @@ export const platUpgradeBaseCosts: Record = { }, 2: { obtainium: 3e70, - offerings: 2e45, + offerings: 3e45, cubes: 1e11, tesseracts: 1e8, hypercubes: 1e5, @@ -40,8 +41,8 @@ export const platUpgradeBaseCosts: Record = { priceMult: 2 }, 3: { - obtainium: 1e71, - offerings: 4e45, + obtainium: 1e74, + offerings: 1e46, cubes: 1e11, tesseracts: 1e6, hypercubes: 1e7, @@ -51,8 +52,8 @@ export const platUpgradeBaseCosts: Record = { priceMult: 2 }, 4: { - obtainium: 4e71, - offerings: 1e46, + obtainium: 3e74, + offerings: 3e46, cubes: 1e12, tesseracts: 1e7, hypercubes: 1e6, @@ -63,7 +64,7 @@ export const platUpgradeBaseCosts: Record = { }, 5: { obtainium: 1e80, - offerings: 1e60, + offerings: 1e59, cubes: 1e14, tesseracts: 1e9, hypercubes: 1e8, @@ -234,8 +235,8 @@ const checkPlatonicUpgrade = ( let checksum = 0 const resources = ['obtainium', 'offerings', 'cubes', 'tesseracts', 'hypercubes', 'platonics', 'abyssals'] as const const resourceNames = [ - 'researchPoints', - 'runeshards', + 'obtainium', + 'offerings', 'wowCubes', 'wowTesseracts', 'wowHypercubes', @@ -274,7 +275,7 @@ const checkPlatonicUpgrade = ( } if ( - player.hepteractCrafts.abyss.BAL >= Math.floor(platUpgradeBaseCosts[index].abyssals * priceMultiplier) + hepteracts.abyss.BAL >= Math.floor(platUpgradeBaseCosts[index].abyssals * priceMultiplier) || platUpgradeBaseCosts[index].abyssals === 0 ) { checksum++ @@ -303,7 +304,7 @@ export const createPlatonicDescription = (index: number) => { } priceMultiplier *= calculateSingularityDebuff('Platonic Costs') - DOMCacheGetOrSet('platonicUpgradeDescription').textContent = i18next.t( + DOMCacheGetOrSet('platonicUpgradeDescription').innerHTML = i18next.t( `wowCubes.platonicUpgrades.descriptions.${index}` ) DOMCacheGetOrSet('platonicUpgradeLevel').textContent = i18next.t(translationKey, { @@ -313,46 +314,46 @@ export const createPlatonicDescription = (index: number) => { DOMCacheGetOrSet('platonicOfferingCost').textContent = i18next.t( 'wowCubes.platonicUpgrades.descriptionBox.offeringCost', { - a: format(player.runeshards), + a: format(player.offerings), b: format(platUpgradeBaseCosts[index].offerings * priceMultiplier) } ) DOMCacheGetOrSet('platonicObtainiumCost').textContent = i18next.t( 'wowCubes.platonicUpgrades.descriptionBox.obtainiumCost', { - a: format(player.researchPoints), + a: format(player.obtainium), b: format(platUpgradeBaseCosts[index].obtainium * priceMultiplier) } ) DOMCacheGetOrSet('platonicCubeCost').textContent = i18next.t('wowCubes.platonicUpgrades.descriptionBox.cubeCost', { - a: format(player.wowCubes), + a: format(player.wowCubes.valueOf()), b: format(platUpgradeBaseCosts[index].cubes * priceMultiplier) }) DOMCacheGetOrSet('platonicTesseractCost').textContent = i18next.t( 'wowCubes.platonicUpgrades.descriptionBox.tesseractCost', { - a: format(player.wowTesseracts), + a: format(player.wowTesseracts.valueOf()), b: format(platUpgradeBaseCosts[index].tesseracts * priceMultiplier) } ) DOMCacheGetOrSet('platonicHypercubeCost').textContent = i18next.t( 'wowCubes.platonicUpgrades.descriptionBox.hypercubeCost', { - a: format(player.wowHypercubes), + a: format(player.wowHypercubes.valueOf()), b: format(platUpgradeBaseCosts[index].hypercubes * priceMultiplier) } ) DOMCacheGetOrSet('platonicPlatonicCost').textContent = i18next.t( 'wowCubes.platonicUpgrades.descriptionBox.platonicCost', { - a: format(player.wowPlatonicCubes), + a: format(player.wowPlatonicCubes.valueOf()), b: format(platUpgradeBaseCosts[index].platonics * priceMultiplier) } ) DOMCacheGetOrSet('platonicHepteractCost').textContent = i18next.t( 'wowCubes.platonicUpgrades.descriptionBox.hepteractCost', { - a: format(player.hepteractCrafts.abyss.BAL, 0, true), + a: format(hepteracts.abyss.BAL, 0, true), b: format(Math.floor(platUpgradeBaseCosts[index].abyssals * priceMultiplier), 0, true) } ) @@ -439,18 +440,18 @@ export const buyPlatonicUpgrades = (index: number, auto = false) => { player.platonicUpgrades[index] += 1 // Auto Platonic Upgrades no longer claim the cost of Offerings and Obtainiums if (!auto) { - player.researchPoints -= Math.floor(platUpgradeBaseCosts[index].obtainium * priceMultiplier) - player.runeshards -= Math.floor(platUpgradeBaseCosts[index].offerings * priceMultiplier) + player.obtainium = player.obtainium.sub(Math.floor(platUpgradeBaseCosts[index].obtainium * priceMultiplier)) + player.offerings = player.offerings.sub(Math.floor(platUpgradeBaseCosts[index].offerings * priceMultiplier)) } player.wowCubes.sub(Math.floor(platUpgradeBaseCosts[index].cubes * priceMultiplier)) player.wowTesseracts.sub(Math.floor(platUpgradeBaseCosts[index].tesseracts * priceMultiplier)) player.wowHypercubes.sub(Math.floor(platUpgradeBaseCosts[index].hypercubes * priceMultiplier)) player.wowPlatonicCubes.sub(Math.floor(platUpgradeBaseCosts[index].platonics * priceMultiplier)) - player.hepteractCrafts.abyss.spend(Math.floor(platUpgradeBaseCosts[index].abyssals * priceMultiplier)) + hepteracts.abyss.BAL -= Math.floor(platUpgradeBaseCosts[index].abyssals * priceMultiplier) if (index === 20 && !auto && player.singularityCount === 0) { void Alert( - 'While I strongly recommended you not to buy this, you did it anyway. For that, you have unlocked the rune of Grandiloquence, for you are a richass.' + i18next.t('wowCubes.platonicUpgrades.20Bought') ) } } else { diff --git a/src/PlatonicCubes.ts b/src/PlatonicCubes.ts index 1d377670a..1f7c0f02e 100644 --- a/src/PlatonicCubes.ts +++ b/src/PlatonicCubes.ts @@ -1,25 +1,111 @@ import { player } from './Synergism' -import { Globals as G } from './Variables' - -export const calculatePlatonicBlessings = () => { - // The visual updates are handled in visualUpdateCubes() - const platonicArray = Object.values(player.platonicBlessings) - const DRThreshold = [4e6, 4e6, 4e6, 8e4, 1e4, 1e4, 1e4, 1e4] - for (let i = 0; i < platonicArray.length; i++) { - let power = 1 - let mult = 1 - let effectiveAmount = platonicArray[i] - if (i === 5) { - effectiveAmount = Math.min(effectiveAmount, 1e20) - } - if (i === 6 && effectiveAmount >= 1e20) { - effectiveAmount = Math.pow(effectiveAmount, 0.5) * 1e10 - } - if (platonicArray[i] >= DRThreshold[i]) { - power = G.platonicDRPower[i] - mult *= Math.pow(DRThreshold[i], 1 - G.platonicDRPower[i]) - } - - G.platonicBonusMultiplier[i] = 1 + mult * G.platonicCubeBase[i] * Math.pow(effectiveAmount, power) + +export const calculateCubeMultiplierPlatonicBlessing = () => { + const DR = 1 / 5 + const effectPerBlessing = 2 / 4e6 + const limit = 4e6 + if (player.platonicBlessings.cubes < limit) { + return 1 + effectPerBlessing * player.platonicBlessings.cubes + } else { + const limitMult = Math.pow(limit, 1 - DR) + return 1 + effectPerBlessing * limitMult * Math.pow(player.platonicBlessings.cubes, DR) + } +} + +export const calculateTesseractMultiplierPlatonicBlessing = () => { + const DR = 1 / 5 + const effectPerBlessing = 1.5 / 4e6 + const limit = 4e6 + if (player.platonicBlessings.tesseracts < limit) { + return 1 + effectPerBlessing * player.platonicBlessings.tesseracts + } else { + const limitMult = Math.pow(limit, 1 - DR) + return 1 + effectPerBlessing * limitMult * Math.pow(player.platonicBlessings.tesseracts, DR) + } +} + +export const calculateHypercubeMultiplierPlatonicBlessing = () => { + const DR = 1 / 5 + const effectPerBlessing = 1 / 4e6 + const limit = 4e6 + if (player.platonicBlessings.hypercubes < limit) { + return 1 + effectPerBlessing * player.platonicBlessings.hypercubes + } else { + const limitMult = Math.pow(limit, 1 - DR) + return 1 + effectPerBlessing * limitMult * Math.pow(player.platonicBlessings.hypercubes, DR) + } +} + +export const calculatePlatonicMultiplierPlatonicBlessing = () => { + const DR = 1 / 5 + const effectPerBlessing = 1 / 8e4 + const limit = 8e4 + if (player.platonicBlessings.platonics < limit) { + return 1 + effectPerBlessing * player.platonicBlessings.platonics + } else { + const limitMult = Math.pow(limit, 1 - DR) + return 1 + effectPerBlessing * limitMult * Math.pow(player.platonicBlessings.platonics, DR) + } +} + +export const calculateHypercubeBlessingMultiplierPlatonicBlessing = () => { + const DR = 1 / 16 + const effectPerBlessing = 1 / 1e4 + const limit = 1e4 + if (player.platonicBlessings.hypercubeBonus < limit) { + return 1 + effectPerBlessing * player.platonicBlessings.hypercubeBonus + } else { + const limitMult = Math.pow(limit, 1 - DR) + return 1 + effectPerBlessing * limitMult * Math.pow(player.platonicBlessings.hypercubeBonus, DR) + } +} + +export const calculateTaxPlatonicBlessing = () => { + // 0 < effect < 1 + const factor = Math.pow(Math.log10(1 + player.platonicBlessings.taxes), 1.5) + return factor / (125 + factor) + + // Should this be improved? + /*const clippedAmount = Math.min(player.platonicBlessings.taxes, 1e20) + + if (player.platonicBlessings.taxes < limit) { + return 1 + effectPerBlessing * player.platonicBlessings.taxes + } + else { + const limitMult = Math.pow(limit, 1 - DR) + return effectPerBlessing * limitMult * Math.pow(player.platonicBlessings.taxes, DR) + }*/ +} + +export const calculateAscensionScorePlatonicBlessing = () => { + const DR1 = 1 / 4 + const DR2 = 1 / 8 + const limit1 = 1e4 + const limit2 = 1e20 + const effectPerBlessing = 1 / 1e4 + + if (player.platonicBlessings.globalSpeed < limit1) { + return 1 + effectPerBlessing * player.platonicBlessings.globalSpeed + } else if (limit1 <= player.platonicBlessings.globalSpeed && player.platonicBlessings.globalSpeed < limit2) { + const limitMult = Math.pow(limit1, 1 - DR1) + return 1 + effectPerBlessing * limitMult * Math.pow(player.platonicBlessings.globalSpeed, DR1) + } else { + // Can derive that this works using algebra (Platonic did it) + const limitMult1 = Math.pow(limit1, 1 - DR1) + const limitMult2 = Math.pow(limit2, DR1 - DR2) + return 1 + effectPerBlessing * limitMult1 * limitMult2 * Math.pow(player.platonicBlessings.globalSpeed, DR2) + } +} + +export const calculateGlobalSpeedPlatonicBlessing = () => { + const DR = 1 / 8 + const limit = 1e4 + const effectPerBlessing = 1 / 1e4 + + if (player.platonicBlessings.globalSpeed < limit) { + return 1 + effectPerBlessing * player.platonicBlessings.globalSpeed + } else { + const limitMult = Math.pow(limit, 1 - DR) + return 1 + effectPerBlessing * limitMult * Math.pow(player.platonicBlessings.globalSpeed, DR) } } diff --git a/src/Plugins/StatSymbols.ts b/src/Plugins/StatSymbols.ts new file mode 100644 index 000000000..5c1fe5047 --- /dev/null +++ b/src/Plugins/StatSymbols.ts @@ -0,0 +1,58 @@ +import type { PostProcessorModule } from 'i18next' + +export const showStatSymbol = true + +const KEYWORD_SYMBOLS: Record = { + 'Offering': '☤', + 'Obtainium': '❍', + 'Salvage': '♻', + 'Ambrosia Luck': '☘', + 'Red Luck': '⚅', + 'Ambrosia Bar Point': '◊', + 'Red Bar Point': '❖', + 'Blueberries': '☌', + 'Quark': '❂', + 'Cube': '⬢', + 'Tesseract': '⬢', + 'Hypercube': '⬢', + 'Hepteract': '⬢', + 'Octeract': '⬢', + 'Rune Power': '🜇', + 'Blessing Power': '🜆', + 'Spirit Power': '🜈', + 'Talisman Power': 'ל', + 'Rune Coefficient': 'Ɑ', + 'Global Speed': '⧖', + 'Ascension Speed': '⧗', + 'Research': '⚛', + 'Platonic': '✞', + 'Ant ELO': '☇', + 'Immortal ELO': '⛉', + 'Ant Speed': '≫', + 'Ant Sacrifice': '⤬' +} + +const reg = new RegExp(Object.keys(KEYWORD_SYMBOLS).join('|'), 'g') + +export default { + type: 'postProcessor', + name: 'StatSymbols', + process: (value: string): string => { + if (!showStatSymbol) { + return value + } + + const iterable = value.matchAll(reg) + let offset = 0 + + for (const iter of iterable) { + value = `${value.substring(0, iter.index + offset)}${KEYWORD_SYMBOLS[iter[0]]} ${ + value.slice(iter.index + offset) + }` + // Each time we replace the value, we add 2 characters (space and the symbol) but the indices are based on the original string + offset += 2 + } + + return value + } +} satisfies PostProcessorModule diff --git a/src/Quark.ts b/src/Quark.ts index d85c0b8ec..8d8ebcf84 100644 --- a/src/Quark.ts +++ b/src/Quark.ts @@ -1,4 +1,5 @@ import { calculateCubeQuarkMultiplier, calculateQuarkMultiplier } from './Calculate' +import { getOcteractUpgradeEffect } from './Octeracts' import { format, player } from './Synergism' export const quarkHandler = () => { @@ -15,7 +16,7 @@ export const quarkHandler = () => { baseQuarkPerHour += player.researches[el] } - baseQuarkPerHour *= +player.octeractUpgrades.octeractExportQuarks.getEffect().bonus + baseQuarkPerHour *= getOcteractUpgradeEffect('octeractExportQuarks') const quarkPerHour = baseQuarkPerHour @@ -38,8 +39,12 @@ export const quarkHandler = () => { } let bonus = 0 +let personalQuarkBonus = 0 -export const setQuarkBonus = (newBonus: number) => bonus = newBonus +export const setQuarkBonus = (personalBonus: number, globalBonus: number) => { + bonus = 100 * (1 + globalBonus / 100) * (1 + personalBonus / 100) - 100 + personalQuarkBonus = personalBonus +} export const getQuarkBonus = () => bonus export class QuarkHandler { @@ -89,3 +94,10 @@ export class QuarkHandler { [Symbol.toPrimitive] = (t: string) => t === 'number' ? this.QUARKS : null } + +export const refreshQuarkBonus = async () => { + const response = await fetch('https://synergism.cc/api/v1/quark-bonus') + const { bonus } = await response.json() as { bonus: number } + + setQuarkBonus(personalQuarkBonus, bonus) +} diff --git a/src/RedAmbrosiaUpgrades.ts b/src/RedAmbrosiaUpgrades.ts index 4d61e3d71..940b2efc5 100644 --- a/src/RedAmbrosiaUpgrades.ts +++ b/src/RedAmbrosiaUpgrades.ts @@ -1,657 +1,759 @@ import i18next from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' -import { formatAsPercentIncrease } from './Campaign' -import { DynamicUpgrade, type IUpgradeData } from './DynamicUpgrade' -import { format, player } from './Synergism' +import { format, formatAsPercentIncrease, player } from './Synergism' import { Alert, Prompt } from './UpdateHTML' -import { visualUpdateAmbrosia } from './UpdateVisuals' - -interface BaseReward { - desc: string -} - -interface TutorialReward extends BaseReward { - cubeMult: number - obtainiumMult: number - offeringMult: number -} - -interface ConversionImprovementReward extends BaseReward { - conversionImprovement: number -} - -interface FreeLevelReward extends BaseReward { - freeLevels: number -} - -interface BlueberrySpeedReward extends BaseReward { - blueberryGenerationSpeed: number -} - -interface AmbrosiaLuckReward extends BaseReward { - ambrosiaLuck: number -} - -interface RedAmbrosiaSpeedReward extends BaseReward { - redAmbrosiaGenerationSpeed: number -} - -interface RedAmbrosiaLuckReward extends BaseReward { - redAmbrosiaLuck: number -} - -interface UnlockedRedAmbrosiaCubeReward extends BaseReward { - unlockedRedAmbrosiaCube: number -} - -interface UnlockRedAmbrosiaObtainiumReward extends BaseReward { - unlockRedAmbrosiaObtainium: number -} - -interface UnlockRedAmbrosiaOfferingReward extends BaseReward { - unlockRedAmbrosiaOffering: number -} - -interface RedAmbrosiaCubeImproverReward extends BaseReward { - extraExponent: number +import { isMobile } from './Utility' + +type RedAmbrosiaUpgradeRewards = { + tutorial: { cubeMult: number; obtainiumMult: number; offeringMult: number } + conversionImprovement1: { conversionImprovement: number } + conversionImprovement2: { conversionImprovement: number } + conversionImprovement3: { conversionImprovement: number } + freeTutorialLevels: { freeLevels: number } + freeLevelsRow2: { freeLevels: number } + freeLevelsRow3: { freeLevels: number } + freeLevelsRow4: { freeLevels: number } + freeLevelsRow5: { freeLevels: number } + blueberryGenerationSpeed: { blueberryGenerationSpeed: number } + regularLuck: { ambrosiaLuck: number } + redGenerationSpeed: { redAmbrosiaGenerationSpeed: number } + redLuck: { redAmbrosiaLuck: number } + redAmbrosiaCube: { unlockedRedAmbrosiaCube: number } + redAmbrosiaObtainium: { unlockRedAmbrosiaObtainium: number } + redAmbrosiaOffering: { unlockRedAmbrosiaOffering: number } + redAmbrosiaCubeImprover: { extraExponent: number } + viscount: { roleUnlock: boolean; quarkBonus: number; luckBonus: number; redLuckBonus: number } + infiniteShopUpgrades: { freeLevels: number } + redAmbrosiaAccelerator: { ambrosiaTimePerRedAmbrosia: number } + regularLuck2: { ambrosiaLuck: number } + blueberryGenerationSpeed2: { blueberryGenerationSpeed: number } + salvageYinYang: { positiveSalvage: number; negativeSalvage: number } } -interface RedAmbrosiaAcceleratorReward extends BaseReward { - ambrosiaTimePerRedAmbrosia: number -} - -interface ViscountReward extends BaseReward { - roleUnlock: boolean - quarkBonus: number - luckBonus: number - redLuckBonus: number -} - -type RewardTypeMap = { - 'tutorial': TutorialReward - 'conversionImprovement1': ConversionImprovementReward - 'conversionImprovement2': ConversionImprovementReward - 'conversionImprovement3': ConversionImprovementReward - 'freeTutorialLevels': FreeLevelReward - 'freeLevelsRow2': FreeLevelReward - 'freeLevelsRow3': FreeLevelReward - 'freeLevelsRow4': FreeLevelReward - 'freeLevelsRow5': FreeLevelReward - 'blueberryGenerationSpeed': BlueberrySpeedReward - 'regularLuck': AmbrosiaLuckReward - 'redGenerationSpeed': RedAmbrosiaSpeedReward - 'redLuck': RedAmbrosiaLuckReward - 'redAmbrosiaCube': UnlockedRedAmbrosiaCubeReward - 'redAmbrosiaObtainium': UnlockRedAmbrosiaObtainiumReward - 'redAmbrosiaOffering': UnlockRedAmbrosiaOfferingReward - 'redAmbrosiaCubeImprover': RedAmbrosiaCubeImproverReward - 'viscount': ViscountReward - 'infiniteShopUpgrades': FreeLevelReward - 'redAmbrosiaAccelerator': RedAmbrosiaAcceleratorReward - 'regularLuck2': AmbrosiaLuckReward - 'blueberryGenerationSpeed2': BlueberrySpeedReward -} - -export type RedAmbrosiaKeys = keyof RewardTypeMap - -export interface IRedAmbrosiaData - extends Omit -{ - costFormula(this: void, level: number, baseCost: number): number - rewards(this: void, n: number): RewardTypeMap[K] - redAmbrosiaInvested?: number -} - -export class RedAmbrosiaUpgrade extends DynamicUpgrade { - readonly costFormula: (level: number, baseCost: number) => number - readonly rewards: (n: number) => RewardTypeMap[K] - public redAmbrosiaInvested = 0 - public level = 0 - #key: K - - constructor (data: IRedAmbrosiaData, key: K) { - const name = i18next.t(`redAmbrosia.data.${key}.name`) - const description = i18next.t(`redAmbrosia.data.${key}.description`) - - super({ ...data, name, description }) - this.costFormula = data.costFormula - this.rewards = data.rewards - this.redAmbrosiaInvested = data.redAmbrosiaInvested ?? 0 - this.#key = key - this.updateLevelFromInvested() - } - - updateLevelFromInvested (): void { - let level = 0 - let budget = this.redAmbrosiaInvested - - let nextCost = this.costFormula(level, this.costPerLevel) - - while (budget >= nextCost) { - budget -= nextCost - level += 1 - nextCost = this.costFormula(level, this.costPerLevel) - - if (level >= this.maxLevel) { - break - } - } - - // If there is leftover budget, then the formulae has probably changed, or above max. - // We refund the remaining budget. - this.refund(budget) - this.level = level - } - - getCostTNL (): number { - if (this.level === this.maxLevel) { - return 0 - } - return this.costFormula(this.level, this.costPerLevel) - } - - /** - * Buy levels up until togglebuy or maxed. - * @returns An alert indicating cannot afford, already maxed or purchased with how many - * levels purchased - */ - public async buyLevel (event: MouseEvent): Promise { - let purchased = 0 - let maxPurchasable = 1 - let redAmbrosiaBudget = player.redAmbrosia - - if (event.shiftKey) { - maxPurchasable = 1000000 - const buy = Number( - await Prompt( - i18next.t('redAmbrosia.redAmbrosiaBuyPrompt', { - amount: format(player.redAmbrosia, 0, true) - }) - ) - ) - - if (isNaN(buy) || !isFinite(buy) || !Number.isInteger(buy)) { - // nan + Infinity checks - return Alert(i18next.t('general.validation.finite')) - } - - if (buy === -1) { - redAmbrosiaBudget = player.redAmbrosia - } else if (buy <= 0) { - return Alert(i18next.t('octeract.buyLevel.cancelPurchase')) // For some reason this is in the Octeract section (???) - } else { - redAmbrosiaBudget = buy - } - redAmbrosiaBudget = Math.min(player.redAmbrosia, redAmbrosiaBudget) - } - - if (this.maxLevel > 0) { - maxPurchasable = Math.min(maxPurchasable, this.maxLevel - this.level) - } - - if (maxPurchasable === 0) { - return Alert(i18next.t('octeract.buyLevel.alreadyMax')) // Once again - } - - while (maxPurchasable > 0) { - const cost = this.getCostTNL() - if (player.redAmbrosia < cost || redAmbrosiaBudget < cost) { - break - } else { - player.redAmbrosia -= cost - redAmbrosiaBudget -= cost - this.redAmbrosiaInvested += cost - this.level += 1 - purchased += 1 - maxPurchasable -= 1 - - // This particular form of Dynamic Upgrade is NOT stored in the player - // We ONLY store the total invested, and apply dynamically. - player.redAmbrosiaUpgrades[this.#key] += cost - } - } - - if (purchased === 0) { - return Alert(i18next.t('octeract.buyLevel.cannotAfford')) - } - if (purchased > 1) { - return Alert( - `${i18next.t('octeract.buyLevel.multiBuy', { n: format(purchased) })}` - ) - } - - this.updateUpgradeHTML() - } - - toString (): string { - const costNextLevel = this.getCostTNL() - const maxLevel = this.maxLevel === -1 ? '' : `/${format(this.maxLevel, 0, true)}` - const isMaxLevel = this.maxLevel === this.level - const color = isMaxLevel ? 'plum' : 'white' - - const isAffordable = costNextLevel <= player.redAmbrosia - const affordableInfo = isMaxLevel - ? ` ${i18next.t('general.maxed')}` - : isAffordable - ? ` ${ - i18next.t( - 'general.affordable' - ) - }` - : ` ${ - i18next.t( - 'octeract.buyLevel.cannotAfford' - ) - }` - - const nameSpan = `${this.name}` - const levelSpan = ` ${ - i18next.t( - 'general.level' - ) - } ${format(this.level, 0, true)}${maxLevel}` - const descriptionSpan = `${this.description}` - const rewardDescSpan = `${this.rewardDesc}` - const costNextLevelSpan = i18next.t('octeract.toString.costNextLevel', { - amount: `${format(costNextLevel, 0, true, true, true)}`, - resource: i18next.t('redAmbrosia.redAmbrosia') - }) - const spentSpan = `${i18next.t('general.spent')} ${i18next.t('redAmbrosia.redAmbrosia')}: ${ - format(this.redAmbrosiaInvested, 0, true, true, true) - }` - const purchaseWarningSpan = `${i18next.t('redAmbrosia.purchaseWarning')}` - return `${nameSpan} \n ${levelSpan} \n ${descriptionSpan} \n ${rewardDescSpan} \n ${ - (!isMaxLevel) ? `${costNextLevelSpan} ${affordableInfo} \n` : '' - } ${spentSpan} \n ${purchaseWarningSpan}` - } - - updateUpgradeHTML (): void { - DOMCacheGetOrSet('singularityAmbrosiaMultiline').innerHTML = this.toString() - visualUpdateAmbrosia() - } - - refund (toRefund: number): void { - player.redAmbrosiaUpgrades[this.#key] -= toRefund - this.redAmbrosiaInvested -= toRefund - player.redAmbrosia += toRefund - } - - public get rewardDesc (): string { - const effectiveLevel = this.level - return this.rewards(effectiveLevel).desc - } - - public get bonus () { - const effectiveLevel = this.level - return this.rewards(effectiveLevel) - } +export type RedAmbrosiaNames = keyof RedAmbrosiaUpgradeRewards + +export interface RedAmbrosiaUpgrade { + name: () => string + description: () => string + level: number + maxLevel: number + costPerLevel: number + redAmbrosiaInvested: number + costFormula: (level: number, baseCost: number) => number + effects: (n: number) => RedAmbrosiaUpgradeRewards[T] + effectsDescription: (n: number) => string } -export const redAmbrosiaUpgradeData: { [K in RedAmbrosiaKeys]: IRedAmbrosiaData } = { +export const redAmbrosiaUpgrades: { [K in RedAmbrosiaNames]: RedAmbrosiaUpgrade } = { tutorial: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost + 0 * level // Level has no effect. }, - rewards: (n: number) => { + effects: (n: number) => { const val = Math.pow(1.01, n) return { - desc: i18next.t('redAmbrosia.data.tutorial.effect', { amount: formatAsPercentIncrease(val) }), cubeMult: val, obtainiumMult: val, offeringMult: val } }, + effectsDescription: (n: number) => { + const val = Math.pow(1.01, n) + return i18next.t('redAmbrosia.data.tutorial.effect', { + amount: formatAsPercentIncrease(val) + }) + }, maxLevel: 100, - costPerLevel: 1 + costPerLevel: 1, + name: () => i18next.t('redAmbrosia.data.tutorial.name'), + description: () => i18next.t('redAmbrosia.data.tutorial.description') }, conversionImprovement1: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(2, level) }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.conversionImprovement1.effect', { amount: n }), conversionImprovement: -n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.conversionImprovement1.effect', { amount: n }) + }, maxLevel: 5, - costPerLevel: 5 + costPerLevel: 5, + name: () => i18next.t('redAmbrosia.data.conversionImprovement1.name'), + description: () => i18next.t('redAmbrosia.data.conversionImprovement1.description') }, conversionImprovement2: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(4, level) }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.conversionImprovement2.effect', { amount: n }), conversionImprovement: -n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.conversionImprovement2.effect', { amount: n }) + }, maxLevel: 3, - costPerLevel: 200 + costPerLevel: 200, + name: () => i18next.t('redAmbrosia.data.conversionImprovement2.name'), + description: () => i18next.t('redAmbrosia.data.conversionImprovement2.description') }, conversionImprovement3: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(10, level) }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.conversionImprovement3.effect', { amount: n }), conversionImprovement: -n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.conversionImprovement3.effect', { amount: n }) + }, maxLevel: 2, - costPerLevel: 10000 + costPerLevel: 10000, + name: () => i18next.t('redAmbrosia.data.conversionImprovement3.name'), + description: () => i18next.t('redAmbrosia.data.conversionImprovement3.description') }, freeTutorialLevels: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost + level }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.freeTutorialLevels.effect', { amount: n }), freeLevels: n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.freeTutorialLevels.effect', { amount: n }) + }, maxLevel: 5, - costPerLevel: 1 + costPerLevel: 1, + name: () => i18next.t('redAmbrosia.data.freeTutorialLevels.name'), + description: () => i18next.t('redAmbrosia.data.freeTutorialLevels.description') }, freeLevelsRow2: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(2, level) }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.freeLevelsRow2.effect', { amount: n }), freeLevels: n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.freeLevelsRow2.effect', { amount: n }) + }, maxLevel: 5, - costPerLevel: 10 + costPerLevel: 10, + name: () => i18next.t('redAmbrosia.data.freeLevelsRow2.name'), + description: () => i18next.t('redAmbrosia.data.freeLevelsRow2.description') }, freeLevelsRow3: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(2, level) }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.freeLevelsRow3.effect', { amount: n }), freeLevels: n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.freeLevelsRow3.effect', { amount: n }) + }, maxLevel: 5, - costPerLevel: 250 + costPerLevel: 250, + name: () => i18next.t('redAmbrosia.data.freeLevelsRow3.name'), + description: () => i18next.t('redAmbrosia.data.freeLevelsRow3.description') }, freeLevelsRow4: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(2, level) }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.freeLevelsRow4.effect', { amount: n }), freeLevels: n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.freeLevelsRow4.effect', { amount: n }) + }, maxLevel: 5, - costPerLevel: 5000 + costPerLevel: 5000, + name: () => i18next.t('redAmbrosia.data.freeLevelsRow4.name'), + description: () => i18next.t('redAmbrosia.data.freeLevelsRow4.description') }, freeLevelsRow5: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * Math.pow(2, level) }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.freeLevelsRow5.effect', { amount: n }), freeLevels: n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.freeLevelsRow5.effect', { amount: n }) + }, maxLevel: 5, - costPerLevel: 50000 + costPerLevel: 50000, + name: () => i18next.t('redAmbrosia.data.freeLevelsRow5.name'), + description: () => i18next.t('redAmbrosia.data.freeLevelsRow5.description') }, blueberryGenerationSpeed: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (level + 1) }, - rewards: (n: number) => { + effects: (n: number) => { const val = 1 + n / 500 return { - desc: i18next.t('redAmbrosia.data.blueberryGenerationSpeed.effect', { amount: formatAsPercentIncrease(val) }), blueberryGenerationSpeed: val } }, + effectsDescription: (n: number) => { + const val = 1 + n / 500 + return i18next.t('redAmbrosia.data.blueberryGenerationSpeed.effect', { amount: formatAsPercentIncrease(val) }) + }, maxLevel: 100, - costPerLevel: 1 + costPerLevel: 1, + name: () => i18next.t('redAmbrosia.data.blueberryGenerationSpeed.name'), + description: () => i18next.t('redAmbrosia.data.blueberryGenerationSpeed.description') }, regularLuck: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (level + 1) }, - rewards: (n: number) => { + effects: (n: number) => { const val = 2 * n return { - desc: i18next.t('redAmbrosia.data.regularLuck.effect', { amount: val }), ambrosiaLuck: val } }, + effectsDescription: (n: number) => { + const val = 2 * n + return i18next.t('redAmbrosia.data.regularLuck.effect', { amount: val }) + }, maxLevel: 100, - costPerLevel: 1 + costPerLevel: 1, + name: () => i18next.t('redAmbrosia.data.regularLuck.name'), + description: () => i18next.t('redAmbrosia.data.regularLuck.description') }, redGenerationSpeed: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (level + 1) }, - rewards: (n: number) => { + effects: (n: number) => { const val = 1 + 3 * n / 1000 return { - desc: i18next.t('redAmbrosia.data.redGenerationSpeed.effect', { amount: formatAsPercentIncrease(val) }), redAmbrosiaGenerationSpeed: val } }, + effectsDescription: (n: number) => { + const val = 1 + 3 * n / 1000 + return i18next.t('redAmbrosia.data.redGenerationSpeed.effect', { amount: formatAsPercentIncrease(val) }) + }, maxLevel: 100, - costPerLevel: 12 + costPerLevel: 12, + name: () => i18next.t('redAmbrosia.data.redGenerationSpeed.name'), + description: () => i18next.t('redAmbrosia.data.redGenerationSpeed.description') }, redLuck: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (level + 1) }, - rewards: (n: number) => { + effects: (n: number) => { const val = n return { - desc: i18next.t('redAmbrosia.data.redLuck.effect', { amount: val }), redAmbrosiaLuck: val } }, + effectsDescription: (n: number) => { + const val = n + return i18next.t('redAmbrosia.data.redLuck.effect', { amount: val }) + }, maxLevel: 100, - costPerLevel: 4 + costPerLevel: 4, + name: () => i18next.t('redAmbrosia.data.redLuck.name'), + description: () => i18next.t('redAmbrosia.data.redLuck.description') }, redAmbrosiaCube: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (level + 1) }, - rewards: (n: number) => { - const exponent = 0.4 + getRedAmbrosiaUpgrade('redAmbrosiaCubeImprover').bonus.extraExponent + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.redAmbrosiaCube.effect', { - amount: n > 0, - exponent: format(exponent, 2, true) - }), unlockedRedAmbrosiaCube: n } }, + effectsDescription: (n: number) => { + const exponent = 0.4 + getRedAmbrosiaUpgradeEffects('redAmbrosiaCubeImprover').extraExponent + return i18next.t('redAmbrosia.data.redAmbrosiaCube.effect', { + amount: n > 0, + exponent: format(exponent, 2, true) + }) + }, maxLevel: 1, - costPerLevel: 500 + costPerLevel: 500, + name: () => i18next.t('redAmbrosia.data.redAmbrosiaCube.name'), + description: () => i18next.t('redAmbrosia.data.redAmbrosiaCube.description') }, redAmbrosiaObtainium: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (level + 1) }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.redAmbrosiaObtainium.effect', { amount: n > 0 }), unlockRedAmbrosiaObtainium: n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.redAmbrosiaObtainium.effect', { amount: n > 0 }) + }, maxLevel: 1, - costPerLevel: 1250 + costPerLevel: 1250, + name: () => i18next.t('redAmbrosia.data.redAmbrosiaObtainium.name'), + description: () => i18next.t('redAmbrosia.data.redAmbrosiaObtainium.description') }, redAmbrosiaOffering: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (level + 1) }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.redAmbrosiaOffering.effect', { amount: n > 0 }), unlockRedAmbrosiaOffering: n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.redAmbrosiaOffering.effect', { amount: n > 0 }) + }, maxLevel: 1, - costPerLevel: 4000 + costPerLevel: 4000, + name: () => i18next.t('redAmbrosia.data.redAmbrosiaOffering.name'), + description: () => i18next.t('redAmbrosia.data.redAmbrosiaOffering.description') }, redAmbrosiaCubeImprover: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (level + 1) }, - rewards: (n: number) => { + effects: (n: number) => { const val = 0.01 * n return { - desc: i18next.t('redAmbrosia.data.redAmbrosiaCubeImprover.effect', { newExponent: format(0.4 + val, 2, true) }), extraExponent: val } }, + effectsDescription: (n: number) => { + const val = 0.01 * n + return i18next.t('redAmbrosia.data.redAmbrosiaCubeImprover.effect', { newExponent: format(0.4 + val, 2, true) }) + }, maxLevel: 20, - costPerLevel: 100 + costPerLevel: 100, + name: () => i18next.t('redAmbrosia.data.redAmbrosiaCubeImprover.name'), + description: () => i18next.t('redAmbrosia.data.redAmbrosiaCubeImprover.description') }, viscount: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost * (level + 1) }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.viscount.effect', { mark: n > 0 ? '✔' : '❌' }), roleUnlock: n > 0, quarkBonus: 1 + 0.1 * n, luckBonus: 125 * n, redLuckBonus: 25 * n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.viscount.effect', { mark: n > 0 ? '✔' : '❌' }) + }, maxLevel: 1, - costPerLevel: 99999 + costPerLevel: 99999, + name: () => i18next.t('redAmbrosia.data.viscount.name'), + description: () => i18next.t('redAmbrosia.data.viscount.description') }, infiniteShopUpgrades: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost + 100 * level }, - rewards: (n: number) => { + effects: (n: number) => { return { - desc: i18next.t('redAmbrosia.data.infiniteShopUpgrades.effect', { amount: n }), freeLevels: n } }, + effectsDescription: (n: number) => { + return i18next.t('redAmbrosia.data.infiniteShopUpgrades.effect', { amount: n }) + }, maxLevel: 40, - costPerLevel: 200 + costPerLevel: 200, + name: () => i18next.t('redAmbrosia.data.infiniteShopUpgrades.name'), + description: () => i18next.t('redAmbrosia.data.infiniteShopUpgrades.description') }, redAmbrosiaAccelerator: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost + level * 0 }, - rewards: (n: number) => { + effects: (n: number) => { const val = 0.02 * n + ((n > 0) ? 1 : 0) return { - desc: i18next.t('redAmbrosia.data.redAmbrosiaAccelerator.effect', { amount: format(val, 2, true) }), ambrosiaTimePerRedAmbrosia: val } }, + effectsDescription: (n: number) => { + const val = 0.02 * n + ((n > 0) ? 1 : 0) + return i18next.t('redAmbrosia.data.redAmbrosiaAccelerator.effect', { amount: format(val, 2, true) }) + }, maxLevel: 100, - costPerLevel: 1000 + costPerLevel: 1000, + name: () => i18next.t('redAmbrosia.data.redAmbrosiaAccelerator.name'), + description: () => i18next.t('redAmbrosia.data.redAmbrosiaAccelerator.description') }, regularLuck2: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost + 0 * level }, - rewards: (n: number) => { + effects: (n: number) => { const val = 2 * n return { - desc: i18next.t('redAmbrosia.data.regularLuck2.effect', { amount: val }), ambrosiaLuck: val } }, - maxLevel: 500, - costPerLevel: 2000 + effectsDescription: (n: number) => { + const val = 2 * n + return i18next.t('redAmbrosia.data.regularLuck2.effect', { amount: val }) + }, + maxLevel: 250, + costPerLevel: 8000, + name: () => i18next.t('redAmbrosia.data.regularLuck2.name'), + description: () => i18next.t('redAmbrosia.data.regularLuck2.description') }, blueberryGenerationSpeed2: { + level: 0, + redAmbrosiaInvested: 0, costFormula: (level: number, baseCost: number) => { return baseCost + 0 * level }, - rewards: (n: number) => { + effects: (n: number) => { const val = 1 + n / 1000 return { - desc: i18next.t('redAmbrosia.data.blueberryGenerationSpeed2.effect', { amount: formatAsPercentIncrease(val) }), blueberryGenerationSpeed: val } }, - maxLevel: 500, - costPerLevel: 2000 + effectsDescription: (n: number) => { + const val = 1 + n / 1000 + return i18next.t('redAmbrosia.data.blueberryGenerationSpeed2.effect', { amount: formatAsPercentIncrease(val) }) + }, + maxLevel: 250, + costPerLevel: 8000, + name: () => i18next.t('redAmbrosia.data.blueberryGenerationSpeed2.name'), + description: () => i18next.t('redAmbrosia.data.blueberryGenerationSpeed2.description') + }, + salvageYinYang: { + level: 0, + redAmbrosiaInvested: 0, + costFormula: (level: number, baseCost: number) => { + return baseCost * (level + 1) + }, + effects: (n: number) => { + if (player.singularityChallenges.taxmanLastStand.enabled) { + return { + positiveSalvage: 0, + negativeSalvage: 0 + } + } + return { + positiveSalvage: 10 * n, + negativeSalvage: -10 * n + } + }, + effectsDescription: (n: number) => { + const bonus = player.singularityChallenges.taxmanLastStand.enabled ? 0 : 10 * n + return i18next.t('redAmbrosia.data.salvageYinYang.effect', { amount: bonus }) + }, + maxLevel: 100, + costPerLevel: 200, + name: () => i18next.t('redAmbrosia.data.salvageYinYang.name'), + description: () => i18next.t('redAmbrosia.data.salvageYinYang.description') } } -// Create an object that is NOT on the player, but can be used (once initialized). -export type RedAmbrosiaUpgradesMap = { - [K in RedAmbrosiaKeys]: RedAmbrosiaUpgrade -} -let redAmbrosiaUpgrades: RedAmbrosiaUpgradesMap | null = null +export const maxRedAmbrosiaUpgradeAP = Object.values(redAmbrosiaUpgrades).reduce((acc, upgrade) => { + if (upgrade.maxLevel === -1) { + return acc + } + return acc + 10 +}, 0) + +export const setRedAmbrosiaUpgradeLevels = (): void => { + for (const upgradeKey of Object.keys(redAmbrosiaUpgrades) as RedAmbrosiaNames[]) { + const upgrade = redAmbrosiaUpgrades[upgradeKey] + const invested = player.redAmbrosiaUpgrades[upgradeKey] || 0 -export function initRedAmbrosiaUpgrades (investments: Record) { - redAmbrosiaUpgrades = {} as RedAmbrosiaUpgradesMap - const keys = Object.keys(redAmbrosiaUpgradeData) as RedAmbrosiaKeys[] + let level = 0 + let budget = invested + + let nextCost = upgrade.costFormula(level, upgrade.costPerLevel) + + while (budget >= nextCost) { + budget -= nextCost + level += 1 + nextCost = upgrade.costFormula(level, upgrade.costPerLevel) - // Use type assertions after careful validation - for (const key of keys) { - const data = redAmbrosiaUpgradeData[key] - const invested = investments[key] + if (level >= upgrade.maxLevel) { + break + } + } - const dataWithInvestment = { - ...data, - redAmbrosiaInvested: invested + // If there is leftover budget, then the formulae has probably changed, or above max. + // We refund the remaining budget. + if (budget > 0) { + player.redAmbrosiaUpgrades[upgradeKey] -= budget + player.redAmbrosia += budget } - // Use a function that casts the result appropriately - const upgrade = new RedAmbrosiaUpgrade(dataWithInvestment, key) // Here we need to use type assertion because TypeScript can't track - // the relationship between the key and the generic parameter in the loop - redAmbrosiaUpgrades[key as 'tutorial'] = upgrade as RedAmbrosiaUpgrade<'tutorial'> + upgrade.level = level + upgrade.redAmbrosiaInvested = invested - budget } } -export function getRedAmbrosiaUpgrade (key: K): RedAmbrosiaUpgrade { - if (redAmbrosiaUpgrades === null) { - throw new Error('RedAmbrosiaUpgrades not initialized. Call initRedAmbrosiaUpgrades first.') +export const blankRedAmbrosiaUpgradeObject: Record = Object.fromEntries( + Object.keys(redAmbrosiaUpgrades).map((key) => [ + key as RedAmbrosiaNames, + 0 + ]) +) as Record + +export const getRedAmbrosiaUpgradeEffects = ( + upgradeKey: T +): RedAmbrosiaUpgradeRewards[T] => { + const currentLevel = redAmbrosiaUpgrades[upgradeKey].level + return redAmbrosiaUpgrades[upgradeKey].effects(currentLevel) +} + +export const getRedAmbrosiaUpgradeEffectsDescription = (upgradeKey: RedAmbrosiaNames): string => { + const currentLevel = redAmbrosiaUpgrades[upgradeKey].level + return redAmbrosiaUpgrades[upgradeKey].effectsDescription(currentLevel) +} + +export const getRedAmbrosiaUpgradeCostTNL = (upgradeKey: RedAmbrosiaNames): number => { + const upgrade = redAmbrosiaUpgrades[upgradeKey] + if (upgrade.level === upgrade.maxLevel) { + return 0 + } + return upgrade.costFormula(upgrade.level, upgrade.costPerLevel) +} + +export const refundRedAmbrosiaUpgrade = (upgradeKey: RedAmbrosiaNames): void => { + const upgrade = redAmbrosiaUpgrades[upgradeKey] + + player.redAmbrosia += upgrade.redAmbrosiaInvested + player.redAmbrosiaUpgrades[upgradeKey] = 0 + upgrade.redAmbrosiaInvested = 0 + upgrade.level = 0 +} + +export const redAmbrosiaUpgradeToString = (upgradeKey: RedAmbrosiaNames): string => { + const upgrade = redAmbrosiaUpgrades[upgradeKey] + const costNextLevel = getRedAmbrosiaUpgradeCostTNL(upgradeKey) + const maxLevel = upgrade.maxLevel === -1 ? '' : `/${format(upgrade.maxLevel, 0, true)}` + const isMaxLevel = upgrade.maxLevel === upgrade.level + const color = isMaxLevel ? 'plum' : 'white' + + const nameSpan = `${upgrade.name()}` + const levelSpan = ` ${i18next.t('general.level')} ${ + format(upgrade.level, 0, true) + }${maxLevel}` + const descriptionSpan = `${upgrade.description()}` + const rewardDescSpan = `${getRedAmbrosiaUpgradeEffectsDescription(upgradeKey)}` + + const costNextLevelSpan = `${ + i18next.t('redAmbrosia.redAmbrosiaCost', { + amount: format(costNextLevel, 0, true) + }) + }` + + const spentSpan = `${ + i18next.t('redAmbrosia.redAmbrosiaSpent', { + amount: format(upgrade.redAmbrosiaInvested, 0, true) + }) + }` + + const purchaseWarningSpan = `${i18next.t('redAmbrosia.purchaseWarning')}` + + return `${nameSpan}
${levelSpan}
${descriptionSpan}
${rewardDescSpan}
${ + (!isMaxLevel) ? `${costNextLevelSpan}
` : '' + } ${spentSpan}
${purchaseWarningSpan}` +} + +export const updateMobileRedAmbrosiaHTML = (k: RedAmbrosiaNames) => { + const elm = DOMCacheGetOrSet('singularityAmbrosiaMultiline') + elm.innerHTML = redAmbrosiaUpgradeToString(k) + // MOBILE ONLY - Add a button for buying upgrades + if (isMobile) { + const buttonDiv = document.createElement('div') + + const buyOne = document.createElement('button') + const buyMax = document.createElement('button') + + buyOne.classList.add('modalBtnBuy') + buyOne.textContent = i18next.t('general.buyOne') + buyOne.addEventListener('click', (event: MouseEvent) => { + buyRedAmbrosiaUpgradeLevel(k, event, false) + updateMobileRedAmbrosiaHTML(k) + }) + + buyMax.classList.add('modalBtnBuy') + buyMax.textContent = i18next.t('general.buyMax') + buyMax.addEventListener('click', (event: MouseEvent) => { + buyRedAmbrosiaUpgradeLevel(k, event, true) + updateMobileRedAmbrosiaHTML(k) + }) + + buttonDiv.appendChild(buyOne) + buttonDiv.appendChild(buyMax) + elm.appendChild(buttonDiv) + } +} + +export const buyRedAmbrosiaUpgradeLevel = async ( + upgradeKey: RedAmbrosiaNames, + event: MouseEvent, + buyMax = false +): Promise => { + const upgrade = redAmbrosiaUpgrades[upgradeKey] + let purchased = 0 + let maxPurchasable = 1 + let redAmbrosiaBudget = player.redAmbrosia + + if (event.shiftKey || buyMax) { + maxPurchasable = 100000000 + const buy = Number( + await Prompt( + i18next.t('redAmbrosia.redAmbrosiaBuyPrompt', { + amount: format(player.redAmbrosia, 0, true) + }) + ) + ) + + if (isNaN(buy) || !isFinite(buy) || !Number.isInteger(buy)) { + // nan + Infinity checks + return Alert(i18next.t('general.validation.finite')) + } + + if (buy === -1) { + redAmbrosiaBudget = player.redAmbrosia + } else if (buy <= 0) { + return Alert(i18next.t('octeract.buyLevel.cancelPurchase')) + } else { + redAmbrosiaBudget = buy + } + redAmbrosiaBudget = Math.min(player.redAmbrosia, redAmbrosiaBudget) + } + + if (upgrade.maxLevel > 0) { + maxPurchasable = Math.min(maxPurchasable, upgrade.maxLevel - upgrade.level) + } + + if (maxPurchasable === 0) { + return Alert(i18next.t('octeract.buyLevel.alreadyMax')) + } + + while (maxPurchasable > 0) { + const cost = getRedAmbrosiaUpgradeCostTNL(upgradeKey) + if (player.redAmbrosia < cost || redAmbrosiaBudget < cost) { + break + } else { + player.redAmbrosia -= cost + redAmbrosiaBudget -= cost + upgrade.redAmbrosiaInvested += cost + upgrade.level += 1 + purchased += 1 + maxPurchasable -= 1 + + // Update the player storage + player.redAmbrosiaUpgrades[upgradeKey] += cost + } + } + + if (purchased === 0) { + return Alert(i18next.t('octeract.buyLevel.cannotAfford')) + } + if (purchased > 1) { + return Alert( + `${i18next.t('octeract.buyLevel.multiBuy', { n: format(purchased) })}` + ) } - return redAmbrosiaUpgrades[key] } export const displayRedAmbrosiaLevels = () => { - for (const key of Object.keys(redAmbrosiaUpgradeData)) { - const k = key as RedAmbrosiaKeys + for (const key of Object.keys(redAmbrosiaUpgrades)) { + const k = key as RedAmbrosiaNames const capKey = key.charAt(0).toUpperCase() + key.slice(1) const name = `redAmbrosia${capKey}` const elm = DOMCacheGetOrSet(name) - const level = getRedAmbrosiaUpgrade(k).level || 0 // Get the level from the loadout, default to 0 if not present - const parent = elm.parentElement! + // There is an image in the elm. find it. + const img = elm.querySelector('img') as HTMLImageElement + const level = redAmbrosiaUpgrades[k].level || 0 - elm.classList.add('dimmed') - let levelOverlay = parent.querySelector('.level-overlay') as HTMLDivElement + img.classList.add('dimmed') + let levelOverlay = elm.querySelector('.level-overlay') as HTMLDivElement if (!levelOverlay) { - levelOverlay = document.createElement('p') + levelOverlay = document.createElement('div') levelOverlay.classList.add('level-overlay') - if (level === redAmbrosiaUpgradeData[k].maxLevel) { + if (level === redAmbrosiaUpgrades[k].maxLevel) { levelOverlay.classList.add('maxRedAmbrosiaLevel') } else { levelOverlay.classList.add('notMaxRedAmbrosiaLevel') } - parent.classList.add('relative-container') // Apply relative container to the element - parent.appendChild(levelOverlay) // Append to the element + elm.classList.add('relative-container') // Apply relative container to the element + elm.appendChild(levelOverlay) // Append to the element levelOverlay.textContent = String(level) // Set the level text } @@ -659,18 +761,18 @@ export const displayRedAmbrosiaLevels = () => { } export const resetRedAmbrosiaDisplay = () => { - for (const key of Object.keys(redAmbrosiaUpgradeData)) { + for (const key of Object.keys(redAmbrosiaUpgrades)) { const capKey = key.charAt(0).toUpperCase() + key.slice(1) const name = `redAmbrosia${capKey}` const elm = DOMCacheGetOrSet(name) - const parent = elm.parentElement! - elm.classList.remove('dimmed') // Remove the dimmed class + const img = elm.querySelector('img') as HTMLImageElement + img.classList.remove('dimmed') // Remove the dimmed class // Remove the level overlay if it exists - const levelOverlay = parent.querySelector('.level-overlay') + const levelOverlay = elm.querySelector('.level-overlay') if (levelOverlay) { levelOverlay.remove() - parent.classList.remove('relative-container') // Remove relative container + elm.classList.remove('relative-container') // Remove relative container } } } diff --git a/src/Research.ts b/src/Research.ts index 75fa5f154..1b13cced1 100644 --- a/src/Research.ts +++ b/src/Research.ts @@ -1,189 +1,384 @@ +import Decimal, { type DecimalSource } from 'break_infinity.js' import i18next from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' -import { calculateAnts, calculateRuneLevels, calculateSummationNonLinear } from './Calculate' -import type { IMultiBuy } from './Cubes' +import { calculateAnts } from './Calculate' import { getResetResearches } from './Reset' import { calculateSingularityDebuff } from './singularity' import { format, player } from './Synergism' import { revealStuff, updateChallengeDisplay } from './UpdateHTML' -import { updateClassList } from './Utility' -import { Globals as G } from './Variables' - -const getResearchCost = (index: number, buyAmount = 1, linGrowth = 0): IMultiBuy => { - buyAmount = Math.min(G.researchMaxLevels[index] - player.researches[index], buyAmount) - const metaData = calculateSummationNonLinear( - player.researches[index], - G.researchBaseCosts[index] * calculateSingularityDebuff('Researches'), - player.researchPoints, - linGrowth, - buyAmount - ) - return metaData -} - -export const updateAutoResearch = (index: number, auto: boolean) => { - /* If Cube Upgrade 9 (1x9) is purchased, then automation behaves differently. - If not purchased, then clicking on a research icon while auto toggled will update research for you.*/ - if (autoResearchEnabled() && auto && player.autoResearchMode === 'cheapest') { - player.autoResearch = G.researchOrderByCost[player.roombaResearchIndex] - - // Checks if this is maxed. If so we proceed to the next research. - if (isResearchMaxed(player.autoResearch)) { - DOMCacheGetOrSet(`res${player.autoResearch || 1}`).classList.remove('researchRoomba') - player.roombaResearchIndex = Math.min( - G.researchOrderByCost.length - 1, - player.roombaResearchIndex + 1 - ) +import { sortDecimalWithIndices, updateClassList } from './Utility' + +export interface IResearchData { + baseCost: Decimal + maxLevel: number + // Some research (e.g. 200) can have custom growth (i.e. nonconstant) + // may as well specify in general. + buyToLevel: (budget: Decimal, baseCost: Decimal, currLevel: number, maxLevel: number) => number + // A given levelsBuyable should correspond to a cost for that many levels + // It is generally difficult to calculate inverses since budget != cost for levels + // So we should choose functions for which it is easy to compute the inverse. + costForLevels: (baseCost: Decimal, currLevel: number, buyTo: number) => Decimal + unlocked: () => boolean +} + +// TODO: Maybe we should just manually create this map? I was a bit lazy to port all the values +// dprint-ignore +const researchBaseCosts: DecimalSource[] = [ + Number.POSITIVE_INFINITY, + 1, 1, 1, 1, 1, + 1, 1e2, 1e4, 1e6, 1e8, + 2, 2e2, 2e4, 2e6, 2e8, + 4e4, 4e8, 10, 1e5, 1e9, + 100, 100, 1e4, 2e3, 2e5, + 40, 200, 50, 5000, 20000000, + 777, 7777, 50000, 500000, 5000000, + 2e3, 2e6, 2e9, 1e5, 1e9, + 1, 1, 5, 25, 125, + 2, 5, 320, 1280, 2.5e9, + 10, 2e3, 4e5, 8e7, 2e9, + 5, 400, 1e4, 3e6, 9e8, + 100, 2500, 100, 2000, 2e5, + 1, 20, 3e3, 4e5, 5e7, + 10, 40, 160, 1000, 10000, + 4e9, 7e9, 1e10, 1.2e10, 1.5e10, + 1e12, 1e13, 3e12, 2e13, 2e13, + 2e14, 6e14, 2e15, 6e15, 2e16, + 1e16, 2e16, 2e17, 4e17, 1e18, + 1e13, 1e14, 1e15, 7.777e18, 7.777e20, + 1e16, 3e16, 1e17, 3e17, 1e20, + 1e18, 3e18, 1e19, 3e19, 1e20, + 1e20, 2e20, 4e20, 8e20, 1e21, + 2e21, 4e21, 8e21, 2e22, 4e22, + 3.2e21, 2e23, 4e23, 1e21, 7.777e32, + 5e8, 5e12, 5e16, 5e20, 5e24, /*ascension tier */ + 1e25, 2e25, 4e25, 8e25, 1e26, + 4e26, 8e26, 1e27, 2e27, 1e28, + 5e9, 5e15, 5e21, 5e27, 1e28, /*challenge 11 tier */ + 1e29, 2e29, 4e29, 8e29, 1e27, + 2e30, 4e30, 8e30, 1e31, 2e31, + 5e31, 1e32, 2e32, 4e32, 8e32, /*challenge 12 tier */ + 1e33, 2e33, 4e33, 8e33, 1e34, + 3e34, 1e35, 3e35, 6e35, 1e36, + 3e36, 1e37, 3e37, 1e38, 3e38, /*challenge 13 tier */ + 1e39, 3e39, 1e40, 3e40, 1e50, + 3e41, 1e42, 3e42, 6e42, 1e43, + 3e43, 1e44, 3e44, 1e45, 3e45, /*challenge 14 tier */ + 2e46, 6e46, 2e47, 6e47, 1e64, + 6e48, 2e49, 1e50, 1e51, 4e56 +] + +// dprint-ignore +const researchMaxLevels: DecimalSource[] = [ + 0, 1, 1, 1, 1, 1, + 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, + 10, 10, 1, 1, 1, + 25, 25, 25, 20, 20, + 10, 10, 10, 10, 10, + 12, 12, 10, 10, 10, + 10, 10, 10, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 10, 10, 10, 10, 10, + 20, 20, 20, 20, 20, + 1, 5, 4, 5, 5, + 10, 10, 10, 10, 10, + 1, 1, 1, 1, 1, + 10, 15, 15, 15, 15, + 10, 1, 20, 20, 20, + 20, 20, 20, 20, 10, + 20, 20, 20, 20, 1, + 20, 5, 5, 3, 2, + 10, 10, 10, 10, 1, + 10, 10, 20, 25, 25, + 15, 15, 15, 15, 30, + 10, 10, 10, 100, 100, + 25, 25, 25, 1, 5, + 10, 10, 10, 10, 1, + 10, 10, 10, 1, 1, + 25, 25, 25, 15, 1, + 10, 10, 10, 10, 1, + 10, 1, 6, 10, 1, + 25, 25, 1, 15, 1, + 10, 10, 10, 1, 1, + 10, 10, 10, 10, 1, + 25, 25, 25, 15, 1, + 10, 10, 10, 1, 1, + 10, 3, 6, 10, 5, + 25, 25, 1, 15, 1, + 20, 20, 20, 1, 1, + 20, 1, 50, 50, 10, + 25, 25, 25, 15, 100000 +] + +export interface IResearchData { + baseCost: Decimal + maxLevel: number + // Some research (e.g. 200) can have custom growth (i.e. nonconstant) + // may as well specify in general. + buyToLevel: (budget: Decimal, baseCost: Decimal, currLevel: number, maxLevel: number) => number + // A given levelsBuyable should correspond to a cost for that many levels + // It is generally difficult to calculate inverses since budget != cost for levels + // So we should choose functions for which the inverse is analytical (see below functions) + costForLevels: (baseCost: Decimal, currLevel: number, buyTo: number) => Decimal + unlocked: () => boolean +} + +// Requires degree != 0, should only be used for positive degree, though +const polyBuyToLevel = ( + degree: number +): (budget: Decimal, baseCost: Decimal, currLevel: number, maxLevel: number) => number => { + return (budget: Decimal, baseCost: Decimal, currLevel: number, maxLevel: number) => { + const effectiveBudget = budget.add(baseCost.times(Math.pow(currLevel, degree))) + return Math.min(maxLevel, Decimal.floor(Decimal.pow(effectiveBudget.div(baseCost), 1 / degree)).toNumber()) + } +} + +// Requires degree != 0, should only be used for positive degree, though +const polyCostForLevels = (degree: number): (baseCost: Decimal, currlevel: number, toBuy: number) => Decimal => { + return (baseCost: Decimal, currLevel: number, buyTo: number) => { + if (currLevel === buyTo) { + return new Decimal(0) } + return baseCost.times(Math.pow(buyTo, degree) - Math.pow(currLevel, degree)) + } +} + +type RangeLevelAndCost = { + range: [number, number] + level: (budget: Decimal, baseCost: Decimal, currLevel: number, maxLevel: number) => number + cost: (baseCost: Decimal, currLevel: number, buyTo: number) => Decimal +} + +// polyCostForLevels(1) implies constant cost per level, polyCostForLevels(2) implies linear growth in cost per level, etc. +const researchLevelCostRanges: RangeLevelAndCost[] = [ + { range: [0, 199], level: polyBuyToLevel(1), cost: polyCostForLevels(1) }, + { range: [200, 200], level: polyBuyToLevel(2), cost: polyCostForLevels(2) } +] + +type RangeCondition = { + range: [number, number] + condition: () => boolean +} + +const researchUnlockRanges: RangeCondition[] = [ + { range: [0, 0], condition: () => true }, // Not sure if needed! + { range: [1, 80], condition: () => player.unlocks.reincarnate }, + { range: [81, 100], condition: () => player.unlocks.anthill }, + { range: [101, 110], condition: () => player.unlocks.talismans }, + { range: [111, 125], condition: () => player.unlocks.ascensions }, + { range: [126, 140], condition: () => player.ascensionCount > 0 }, + { range: [141, 155], condition: () => player.highestchallengecompletions[11] > 0 }, + { range: [156, 170], condition: () => player.highestchallengecompletions[12] > 0 }, + { range: [171, 185], condition: () => player.highestchallengecompletions[13] > 0 }, + { range: [186, 200], condition: () => player.highestchallengecompletions[14] > 0 } +] - // Checks against researches invalid or not unlocked. - while (!isResearchUnlocked(player.autoResearch) && player.autoResearch < 200 && player.autoResearch >= 76) { - player.roombaResearchIndex += 1 - player.autoResearch = G.researchOrderByCost[player.roombaResearchIndex] +const createResearchDataMap = ( + rangeLC: RangeLevelAndCost[], + rangeU: RangeCondition[], + costs: DecimalSource[], + maxLevels: DecimalSource[] +): Record => { + const dataMap: Record = {} + + const unlockLookup: Record boolean> = {} + for (const { range, condition } of rangeU) { + const [start, end] = range + for (let i = start; i <= end; i++) { + unlockLookup[i] = condition } + } - // Researches that are unlocked work - if (isResearchUnlocked(player.autoResearch)) { - const idx = Math.max(G.researchOrderByCost[player.roombaResearchIndex], 1) - const doc = DOMCacheGetOrSet(`res${idx}`) - if (player.researches[player.autoResearch] < G.researchMaxLevels[player.autoResearch]) { - doc.classList.add('researchRoomba') - } + const levelCostLookup: Record = {} + for (const { range, level, cost } of rangeLC) { + const [start, end] = range + for (let i = start; i <= end; i++) { + levelCostLookup[i] = { level, cost } } + } - return - } else if (!auto && (!autoResearchEnabled() || player.autoResearchMode === 'manual')) { - /* We remove the old research HTML from the 'roomba' class and make the new index our 'roomba' - class. We then update the index and consequently the coloring of the background based - on what level (if any) the research has. This functionality is useless after - Cube Upgrade 9 (1x9) has been purchased. */ - DOMCacheGetOrSet(`res${player.autoResearch || 1}`).classList.remove('researchRoomba') - DOMCacheGetOrSet(`res${index}`).classList.add('researchRoomba') - player.autoResearch = index - - // Research is maxed - if (player.researches[index] >= G.researchMaxLevels[index]) { - updateClassList(`res${player.autoResearch}`, ['researchMaxed'], ['researchPurchased']) - } else if (player.researches[index] >= 1) { - // Research purchased above level 0 but not maxed - updateClassList(`res${player.autoResearch}`, ['researchPurchased'], ['researchMaxed']) - } else { - // Research has not been purchased yet - updateClassList(`res${player.autoResearch}`, [], ['researchPurchased', 'researchMaxed']) + for (let i = 0; i < costs.length && i < maxLevels.length; i++) { + const levelCostFunctions = levelCostLookup[i] + const unlockFunction = unlockLookup[i] + + if (levelCostFunctions && unlockFunction) { + dataMap[i] = { + baseCost: new Decimal(costs[i]), + maxLevel: Number(maxLevels[i]), + buyToLevel: levelCostFunctions.level, + costForLevels: levelCostFunctions.cost, + unlocked: unlockFunction + } } + } - return + return dataMap +} + +export const researchData = createResearchDataMap( + researchLevelCostRanges, + researchUnlockRanges, + researchBaseCosts, + researchMaxLevels +) + +export const isResearchUnlocked = (index: number): boolean => { + const unlockFunction = researchData[index].unlocked + return unlockFunction ? unlockFunction() : false +} + +export const getBuyableResearchLevel = (index: number): number => { + const buyToLevelFunc = researchData[index].buyToLevel + const baseCost = researchData[index].baseCost + const currLevel = player.researches[index] + const maxLevel = researchData[index].maxLevel + const budget = player.obtainium + + const researchCostMulti = calculateSingularityDebuff('Researches') + + return buyToLevelFunc(budget, baseCost.times(researchCostMulti), currLevel, maxLevel) +} + +export const getCostForResearchLevels = (index: number, buyTo: number): Decimal => { + const costForLevelsFunc = researchData[index].costForLevels + const baseCost = researchData[index].baseCost + const currLevel = player.researches[index] + + const researchCostMulti = calculateSingularityDebuff('Researches') + + return costForLevelsFunc(baseCost.times(researchCostMulti), currLevel, buyTo) +} + +export const researchOrderByCost: number[] = sortDecimalWithIndices(researchBaseCosts) + +// For mode 'manual' +export const updateResearchAuto = (index: number) => { + DOMCacheGetOrSet(`res${player.autoResearch || 1}`).classList.remove('researchRoomba') + DOMCacheGetOrSet(`res${index}`).classList.add('researchRoomba') + player.autoResearch = index + + // Research is maxed + if (isResearchMaxed(index)) { + updateClassList(`res${player.autoResearch}`, ['researchMaxed'], ['researchPurchased']) + } else if (player.researches[index] >= 1) { + // Research purchased above level 0 but not maxed + updateClassList(`res${player.autoResearch}`, ['researchPurchased'], ['researchMaxed']) } else { - return - } // There might be code needed here. I don't quite know yet. -Platonic + // Research has not been purchased yet + updateClassList(`res${player.autoResearch}`, [], ['researchPurchased', 'researchMaxed']) + } +} + +// For mode 'cheapest' and assumes you have Cube Upgrade 9 (1x9) purchased +export const updateResearchRoomba = () => { + if (isResearchMaxed(player.autoResearch) || !isResearchUnlocked(player.autoResearch)) { + DOMCacheGetOrSet(`res${player.autoResearch || 1}`).classList.remove('researchRoomba') + player.roombaResearchIndex = Math.min(researchOrderByCost.length - 1, player.roombaResearchIndex + 1) + player.autoResearch = researchOrderByCost[player.roombaResearchIndex] + } + // Edge Case? If we reach end of the list, but there is still unlockable research, + // we can loop around again. This should not affect performance that much, and stops + // a few of the more annoying bugs + if (player.roombaResearchIndex === 200 && !isResearchUnlocked(200)) { + player.roombaResearchIndex = 0 // Reset to the start if we reach the end + player.autoResearch = researchOrderByCost[player.roombaResearchIndex] + } + + DOMCacheGetOrSet(`res${player.autoResearch || 1}`).classList.add('researchRoomba') } /** - * Should the user have access to autoResearch + * Should the user have access to roomba autoResearch * @returns boolean */ -export const autoResearchEnabled = (): boolean => { +export const roombaResearchEnabled = (): boolean => { return (player.cubeUpgrades[9] === 1 || player.highestSingularityCount > 10) } /** * Attempts to buy the research of the index selected. This is hopefully an improvement over buyResearch. Fuck * @param index * @param auto - * @param linGrowth * @returns */ -export const buyResearch = (index: number, auto = false, linGrowth = 0, hover = false): boolean => { +export const buyResearch = (index: number, auto: boolean, hover: boolean) => { + if (isResearchMaxed(index) || !isResearchUnlocked(index)) { + return + } + // Get our costs, and determine if anything is purchasable. - const buyAmount = (player.researchBuyMaxToggle || auto || hover) ? 1e5 : 1 - const metaData = getResearchCost(index, buyAmount, linGrowth) /* Destructuring FTW! */ - const canBuy = player.researchPoints >= metaData.cost + const buyAmount = (player.researchBuyMaxToggle || auto || hover) ? Number.POSITIVE_INFINITY : 1 + const maxLevel = researchData[index].maxLevel - if (canBuy && isResearchUnlocked(index) && !isResearchMaxed(index)) { - player.researches[index] = metaData.levelCanBuy - player.researchPoints -= metaData.cost + let levelToBuy = getBuyableResearchLevel(index) + levelToBuy = Math.min(maxLevel, levelToBuy, player.researches[index] + buyAmount) + + const researchCost = getCostForResearchLevels(index, levelToBuy) + + // If the cost is 0, then we are only able to buy up to currentLevel, which is true + // when the cost to the next level is too prohibitive (getCost is cumulative) + const canBuy = researchCost.gt(0) + + if (canBuy) { + player.researches[index] = levelToBuy + player.obtainium = player.obtainium.sub(researchCost) // Quick check after upgrading for max. This is to update any automation regardless of auto state if (isResearchMaxed(index)) { DOMCacheGetOrSet(`res${player.autoResearch || 1}`).classList.remove('researchRoomba') } - // Update the progress description - G.researchfiller2 = `Level: ${player.researches[index]}/${G.researchMaxLevels[index]}` - researchDescriptions(index, auto, linGrowth) + researchDescriptions(index, auto) - // Handle special cases: Researches 47-50 (2x21-2x25) - // I love the ||= operator -Platonic - player.unlocks.rrow1 ||= true - player.unlocks.rrow2 ||= true - player.unlocks.rrow3 ||= true - player.unlocks.rrow4 ||= true if (index >= 47 && index <= 50) { + player.unlocks.rrow1 ||= player.researches[47] > 0 + player.unlocks.rrow2 ||= player.researches[48] > 0 + player.unlocks.rrow3 ||= player.researches[49] > 0 + player.unlocks.rrow4 ||= player.researches[50] > 0 revealStuff() } if ((index >= 66 && index <= 70) || index === 105) { updateChallengeDisplay() } - // Update ants and runes. - calculateRuneLevels() + // Update ants. calculateAnts() } - // Update HTML for auto stuff if auto research is ever toggled. - if (player.autoResearchToggle) { - updateAutoResearch(index, auto) - } - - // Note to anyone reading this code: I forget why this needs to return a Boolean. - // -Platonic - return canBuy + return } -/** - * Calculates the max research index for the research roomba - */ -export const maxRoombaResearchIndex = (p = player) => { - const base = p.ascensionCount > 0 ? 140 : 125 // 125 researches pre-A + 15 from A - const c11 = p.challengecompletions[11] > 0 ? 15 : 0 - const c12 = p.challengecompletions[12] > 0 ? 15 : 0 - const c13 = p.challengecompletions[13] > 0 ? 15 : 0 - const c14 = p.challengecompletions[14] > 0 ? 15 : 0 - return base + c11 + c12 + c13 + c14 -} - -export const isResearchUnlocked = (index: number) => { - // https://stackoverflow.com/questions/20477177/creating-an-array-of-cumulative-sum-in-javascript - const cumuSum = ((sum) => (value: number) => sum += value)(0) - const indices = [3 * 25, 5, 20, 10, 15, 15, 15, 15, 15, 15].map(cumuSum) - const chievos = [50, 124, 127, 134, 141, 183, 197, 204, 211, 218] - for (let i = 0; i < indices.length; i++) { - if (i === 3 && (index === 121 || index === 124 || index === 150)) { - return player.achievements[chievos[i]] > 0 - } - if (index <= indices[i]) { - return player.achievements[chievos[i]] > 0 - } - } - return false -} +export const isResearchMaxed = (index: number) => player.researches[index] >= researchData[index].maxLevel + +export const researchDescriptions = (index: number, auto = false) => { + const buyAmount = (player.researchBuyMaxToggle || auto) ? Number.POSITIVE_INFINITY : 1 -const isResearchMaxed = (index: number) => G.researchMaxLevels[index] <= player.researches[index] + const y = i18next.t(`researches.descriptions.${index}`) + const p = `res${index}` -export const researchDescriptions = (i: number, auto = false, linGrowth = 0) => { - const buyAmount = (player.researchBuyMaxToggle || auto) ? 100000 : 1 - const y = i18next.t(`researches.descriptions.${i}`) - const p = `res${i}` + let levelToBuy = getBuyableResearchLevel(index) + levelToBuy = Math.min(researchData[index].maxLevel, levelToBuy, player.researches[index] + buyAmount) - if (player.toggles[38] && player.singularityCount > 0) { - buyResearch(i, false, i === 200 ? 0.01 : 0, true) + let obtainiumCost = new Decimal(0) + + // If levelToBuy is = current level, either we've already maxxed the upgrade + // OR we cannot afford any levels. Check which one. + if (levelToBuy === player.researches[index]) { + // If max level, we don't actually need to change anything + // If not max level, we need to show the cost of the next level + if (!isResearchMaxed(index)) { + levelToBuy += 1 + obtainiumCost = getCostForResearchLevels(index, levelToBuy) + } + } else { + obtainiumCost = getCostForResearchLevels(index, levelToBuy) } - const metaData = getResearchCost(i, buyAmount, linGrowth) let z = i18next.t('researches.cost', { - x: format(metaData.cost, 0, false), - y: format(metaData.levelCanBuy - player.researches[i], 0, true) + x: format(obtainiumCost, 0, false), + y: format(levelToBuy - player.researches[index], 0, true) }) - if (player.researches[i] === (G.researchMaxLevels[i])) { + if (isResearchMaxed(index)) { DOMCacheGetOrSet('researchcost').style.color = 'Gold' DOMCacheGetOrSet('researchinfo3').style.color = 'plum' updateClassList(p, ['researchMaxed'], ['researchAvailable', 'researchPurchased', 'researchPurchasedAvailable']) @@ -191,7 +386,7 @@ export const researchDescriptions = (i: number, auto = false, linGrowth = 0) => } else { DOMCacheGetOrSet('researchcost').style.color = 'limegreen' DOMCacheGetOrSet('researchinfo3').style.color = 'white' - if (player.researches[i] > 0) { + if (player.researches[index] > 0) { updateClassList(p, ['researchPurchased', 'researchPurchasedAvailable'], [ 'researchAvailable', 'researchMaxed' @@ -201,20 +396,20 @@ export const researchDescriptions = (i: number, auto = false, linGrowth = 0) => } } - if (player.researchPoints < metaData.cost && player.researches[i] < (G.researchMaxLevels[i])) { + if (player.obtainium.lt(obtainiumCost) && !isResearchMaxed(index)) { DOMCacheGetOrSet('researchcost').style.color = 'var(--crimson-text-color)' updateClassList(p, [], ['researchMaxed', 'researchAvailable', 'researchPurchasedAvailable']) } - DOMCacheGetOrSet('researchinfo2').textContent = y + DOMCacheGetOrSet('researchinfo2').innerHTML = y DOMCacheGetOrSet('researchcost').textContent = z DOMCacheGetOrSet('researchinfo3').textContent = i18next.t('researches.level', { - x: player.researches[i], - y: G.researchMaxLevels[i] + x: player.researches[index], + y: researchData[index].maxLevel }) const resetInfo = DOMCacheGetOrSet('researchinfo4') - if (getResetResearches().includes(i)) { + if (getResetResearches().includes(index)) { resetInfo.textContent = i18next.t('researches.resets') resetInfo.classList.remove('crimsonText') } else { @@ -223,18 +418,30 @@ export const researchDescriptions = (i: number, auto = false, linGrowth = 0) => } } -export const updateResearchBG = (j: number) => { - if (player.researches[j] > G.researchMaxLevels[j]) { - player.researchPoints += (player.researches[j] - G.researchMaxLevels[j]) * G.researchBaseCosts[j] - player.researches[j] = G.researchMaxLevels[j] +// This should only happen in rare cases, when an update changes max levels +// We still need to handle this on each load, since very old savefiles likely have +// several overcaps +export const refundOvercapResearches = () => { + for (let i = 1; i <= player.researches.length - 1; i++) { + if (player.researches[i] > researchData[i].maxLevel) { + const overcapLevel = player.researches[i] + player.researches[i] = researchData[i].maxLevel + + // This works because this function computes the cost to get from current level + // (which is maxLevel at this point) to the overcapLevel, and it's a cumulative function. + const obtainiumSpentAboveCap = getCostForResearchLevels(i, overcapLevel) + player.obtainium = player.obtainium.add(obtainiumSpentAboveCap) + } } +} - const k = `res${j}` - if (player.researches[j] > 0.5 && player.researches[j] < G.researchMaxLevels[j]) { - updateClassList(k, ['researchPurchased'], ['researchMaxed']) - } else if (player.researches[j] > 0.5 && player.researches[j] >= G.researchMaxLevels[j]) { - updateClassList(k, ['researchMaxed'], ['researchPurchased']) +export const updateResearchBG = (index: number) => { + const id = `res${index}` + if (player.researches[index] > 0 && !isResearchMaxed(index)) { + updateClassList(id, ['researchPurchased'], ['researchMaxed']) + } else if (player.researches[index] > 0 && isResearchMaxed(index)) { + updateClassList(id, ['researchMaxed'], ['researchPurchased']) } else { - updateClassList(k, [], ['researchPurchased', 'researchMaxed']) + updateClassList(id, [], ['researchPurchased', 'researchMaxed']) } } diff --git a/src/Reset.ts b/src/Reset.ts index 854648776..a88788fa8 100644 --- a/src/Reset.ts +++ b/src/Reset.ts @@ -1,6 +1,6 @@ import Decimal from 'break_infinity.js' import i18next from 'i18next' -import { achievementaward, ascensionAchievementCheck, challengeachievementcheck } from './Achievements' +import { awardAchievementGroup, challengeAchievementCheck, getAchievementReward } from './Achievements' import type { BlueberryLoadoutMode } from './BlueberryUpgrades' import { buyTesseractBuilding, calculateTessBuildingsInBudget } from './Buy' import type { TesseractBuildings } from './Buy' @@ -9,14 +9,10 @@ import { calcAscensionCount, CalcCorruptionStuff, calculateAnts, - calculateAntSacrificeELO, - calculateCubeBlessings, calculateGoldenQuarks, calculateObtainium, calculateOfferings, - calculatePowderConversion, - calculateRuneLevels, - calculateTalismanEffects + calculatePowderConversion } from './Calculate' import { campaignCorruptionStatsHTMLReset, @@ -24,13 +20,15 @@ import { campaignIconHTMLUpdate, campaignIconHTMLUpdates, type CampaignKeys, - campaignTokenRewardHTMLUpdate + campaignTokenRewardHTMLUpdate, + updateMaxTokens, + updateTokens } from './Campaign' -import { challengeRequirement } from './Challenges' +import { CalcECC, challengeRequirement } from './Challenges' import { c15Corruptions, CorruptionLoadout, corruptionStatsUpdate, type SavedCorruption } from './Corruptions' import { WowCubes } from './CubeExperimental' import { autoBuyCubeUpgrades, awardAutosCookieUpgrade, updateCubeUpgradeBG } from './Cubes' -import { getAutoHepteractCrafts } from './Hepteracts' +import { autoCraftHepteracts, hepteractKeys, hepteracts, resetHepteracts } from './Hepteracts' import { resetHistoryAdd, type ResetHistoryEntryAscend, @@ -39,18 +37,24 @@ import { type ResetHistoryEntrySingularity, type ResetHistoryEntryTranscend } from './History' -import { calculateHypercubeBlessings } from './Hypercubes' import { importSynergism } from './ImportExport' +import { getLevelMilestone } from './Levels' import { autoBuyPlatonicUpgrades, updatePlatonicUpgradeBG } from './Platonic' -import { buyResearch, updateResearchBG } from './Research' -import { resetofferings } from './Runes' +import { isResearchMaxed, updateResearchBG } from './Research' +import { resetRuneBlessings } from './RuneBlessings' +import { resetOfferings, resetRunes, runes } from './Runes' +import { resetRuneSpirits } from './RuneSpirits' import { playerJsonSchema } from './saves/PlayerJsonSchema' import { forceResetShopUpgrades, shopData } from './Shop' -import { calculateSingularityDebuff, getFastForwardTotalMultiplier } from './singularity' -import { blankSave, deepClone, format, player, saveSynergy, updateAll, updateEffectiveLevelMult } from './Synergism' +import { + calculateSingularityDebuff, + getFastForwardTotalMultiplier, + getGQUpgradeEffect, + goldenQuarkUpgrades +} from './singularity' +import { blankSave, deepClone, format, player, saveSynergy, updateAll } from './Synergism' import { changeSubTab, changeTab, Tabs } from './Tabs' -import { updateTalismanAppearance, updateTalismanInventory } from './Talismans' -import { calculateTesseractBlessings } from './Tesseracts' +import { resetTalismanData, updateTalismanInventory } from './Talismans' import { IconSets } from './Themes' import { clearInterval, setInterval } from './Timers' import { toggleAutoChallengeModeText } from './Toggles' @@ -64,6 +68,15 @@ import { Globals as G } from './Variables' let repeatreset: number +export enum resetTiers { + prestige = 1, + transcension = 2, + reincarnation = 3, + ascension = 4, + singularity = 5, + never = 6 +} + export const resetrepeat = (input: resetNames) => { clearInterval(repeatreset) repeatreset = +setInterval(() => resetdetails(input), 50) @@ -85,7 +98,7 @@ export const resetdetails = (input: resetNames) => { const resetCurrencyGain = DOMCacheGetOrSet('resetcurrency2') if (input === 'reincarnation') { resetObtainiumImage.style.display = 'block' - resetObtainiumText.textContent = format(Math.floor(calculateObtainium())) + resetObtainiumText.textContent = format(Decimal.floor(calculateObtainium())) } else { resetObtainiumImage.style.display = 'none' resetObtainiumText.textContent = '' @@ -328,13 +341,55 @@ const resetAddHistoryEntry = (input: resetNames, from = 'unknown') => { } } +export const updatePrestigeCount = (count: number) => { + let multiplier = 1 + multiplier *= +getAchievementReward('prestigeCountMultiplier') + multiplier *= 1 + 0.05 * CalcECC('transcend', player.challengecompletions[5]) + + const prestigeToAdd = Math.floor(count * multiplier) + if (prestigeToAdd > 0) { + player.prestigeCount += prestigeToAdd + awardAchievementGroup('prestigeCount') + } +} + +export const updateTranscensionCount = (count: number) => { + let multiplier = 1 + multiplier *= +getAchievementReward('transcensionCountMultiplier') + multiplier *= 1 + 0.15 * CalcECC('reincarnation', player.challengecompletions[7]) + + const transcendToAdd = Math.floor(count * multiplier) + if (transcendToAdd > 0) { + player.transcendCount += transcendToAdd + awardAchievementGroup('transcensionCount') + } + if (getAchievementReward('transcendToPrestige')) { + updatePrestigeCount(transcendToAdd) + } +} + +export const updateReincarnationCount = (count: number) => { + let multiplier = 1 + multiplier *= +getAchievementReward('reincarnationCountMultiplier') + multiplier *= 1 + 0.2 * CalcECC('ascension', player.challengecompletions[12]) + + const reincarnationToAdd = Math.floor(count * multiplier) + if (reincarnationToAdd > 0) { + player.reincarnationCount += reincarnationToAdd + awardAchievementGroup('reincarnationCount') + } + if (getAchievementReward('reincarnationToTranscend')) { + updateTranscensionCount(reincarnationToAdd) + } +} + export const reset = (input: resetNames, fast = false, from = 'unknown') => { // Handle adding history entries before actually resetting data, to ensure optimal accuracy. resetAddHistoryEntry(input, from) const obtainiumToGain = calculateObtainium() - resetofferings() + resetOfferings() resetUpgrades(1) player.coins = new Decimal('102') player.coinsThisPrestige = new Decimal('100') @@ -363,7 +418,7 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { player.acceleratorCost = new Decimal('500') player.acceleratorBought = 0 - player.prestigeCount += 1 + updatePrestigeCount(1) player.prestigePoints = player.prestigePoints.add(G.prestigePointGain) player.prestigeShards = new Decimal('0') @@ -429,7 +484,8 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { player.acceleratorBoostBought = 0 player.acceleratorBoostCost = new Decimal('1e3') - player.transcendCount += 1 + updateTranscensionCount(1) + awardAchievementGroup('transcensionCount') player.prestigePoints = new Decimal('0') player.transcendPoints = player.transcendPoints.add(G.transcendPointGain) @@ -441,41 +497,22 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { G.transcendPointGain = new Decimal('0') - if (player.achievements[78] > 0.5) { + if (getLevelMilestone('tier1CrystalAutobuy') === 1) { player.firstOwnedDiamonds += 1 } - if (player.achievements[85] > 0.5) { + if (getLevelMilestone('tier2CrystalAutobuy') === 1) { player.secondOwnedDiamonds += 1 } - if (player.achievements[92] > 0.5) { + if (getLevelMilestone('tier3CrystalAutobuy') === 1) { player.thirdOwnedDiamonds += 1 } - if (player.achievements[99] > 0.5) { + if (getLevelMilestone('tier4CrystalAutobuy') === 1) { player.fourthOwnedDiamonds += 1 } - if (player.achievements[106] > 0.5) { + if (getLevelMilestone('tier5CrystalAutobuy') === 1) { player.fifthOwnedDiamonds += 1 } - if (player.achievements[4] > 0.5) { - player.upgrades[81] = 1 - } - if (player.achievements[11] > 0.5) { - player.upgrades[82] = 1 - } - if (player.achievements[18] > 0.5) { - player.upgrades[83] = 1 - } - if (player.achievements[25] > 0.5) { - player.upgrades[84] = 1 - } - if (player.achievements[32] > 0.5) { - player.upgrades[85] = 1 - } - if (player.achievements[80] > 0.5) { - player.upgrades[87] = 1 - } - if (player.transcendcounter < player.fastesttranscend && player.currentChallenge.transcension === 0) { player.fastesttranscend = player.transcendcounter } @@ -495,18 +532,10 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { || input === 'ascensionChallenge' || input === 'singularity' ) { // Fail safe if for some reason ascension achievement isn't awarded. hacky solution but am too tired to fix right now - if (player.ascensionCount > 0 && player.achievements[183] < 1) { - ascensionAchievementCheck(1) - } - player.researchPoints = Math.min(1e300, player.researchPoints + Math.floor(obtainiumToGain)) + awardAchievementGroup('ascensionCount') - if (player.reincarnationcounter > 0) { - const opscheck = obtainiumToGain / player.reincarnationcounter - if (opscheck > player.obtainiumpersecond) { - player.obtainiumpersecond = opscheck - } - } + player.obtainium = player.obtainium.add(obtainiumToGain) player.currentChallenge.transcension = 0 resetUpgrades(3) @@ -527,7 +556,8 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { player.fourthGeneratedParticles = new Decimal('0') player.fifthGeneratedParticles = new Decimal('0') - player.reincarnationCount += 1 + updateReincarnationCount(1) + awardAchievementGroup('reincarnationCount') player.transcendPoints = new Decimal('0') player.reincarnationPoints = player.reincarnationPoints.add(G.reincarnationPointGain) @@ -559,23 +589,16 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { player.fastestreincarnate = player.reincarnationcounter } - calculateCubeBlessings() player.reincarnationcounter = 0 G.autoResetTimers.reincarnation = 0 - if (player.autoResearchToggle && player.autoResearch > 0.5) { - const linGrowth = (player.autoResearch === 200) ? 0.01 : 0 - buyResearch(player.autoResearch, true, linGrowth) - } - - calculateRuneLevels() calculateAnts() } if (input === 'ascension' || input === 'ascensionChallenge' || input === 'singularity') { const metaData = CalcCorruptionStuff() if (player.challengecompletions[10] > 0) { - ascensionAchievementCheck(3, metaData[3]) + awardAchievementGroup('ascensionScore') } // reset auto challenges player.currentChallenge.transcension = 0 @@ -596,12 +619,9 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { // reset rest resetResearches() resetAnts() - resetTalismans() + resetTalismanData('ascension') player.reincarnationPoints = new Decimal('0') player.reincarnationShards = new Decimal('0') - player.obtainiumpersecond = 0 - player.maxobtainiumpersecond = 0 - player.offeringpersecond = 0 player.antSacrificePoints = 0 player.antSacrificeTimer = 0 player.antSacrificeTimerReal = 0 @@ -623,16 +643,10 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { player.thirdCostParticles = new Decimal('1e4') player.fourthCostParticles = new Decimal('1e8') player.fifthCostParticles = new Decimal('1e16') - player.runeexp = [0, 0, 0, 0, 0, player.runeexp[5], player.runeexp[6]] - player.runelevels = [0, 0, 0, 0, 0, player.runelevels[5], player.runelevels[6]] - player.runeshards = 0 + player.offerings = new Decimal('0') player.crystalUpgrades = [0, 0, 0, 0, 0, 0, 0, 0] - player.runelevels[0] = 3 * player.cubeUpgrades[26] - player.runelevels[1] = 3 * player.cubeUpgrades[26] - player.runelevels[2] = 3 * player.cubeUpgrades[26] - player.runelevels[3] = 3 * player.cubeUpgrades[26] - player.runelevels[4] = 3 * player.cubeUpgrades[26] + resetRunes('ascension') if (player.cubeUpgrades[27] === 1) { player.firstOwnedParticles = 1 @@ -675,21 +689,21 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { player.autoResearch = 1 for (let j = 1; j <= (200); j++) { - const k = `res${j}` - if (player.researches[j] > 0.5 && player.researches[j] < G.researchMaxLevels[j]) { - updateClassList(k, ['researchPurchased'], [ + const id = `res${j}` + if (player.researches[j] > 0 && isResearchMaxed(j)) { + updateClassList(id, ['researchPurchased'], [ 'researchAvailable', 'researchMaxed', 'researchPurchasedAvailable' ]) - } else if (player.researches[j] > 0.5 && player.researches[j] >= G.researchMaxLevels[j]) { - updateClassList(k, ['researchMaxed'], [ + } else if (player.researches[j] > 0 && isResearchMaxed(j)) { + updateClassList(id, ['researchMaxed'], [ 'researchAvailable', 'researchPurchased', 'researchPurchasedAvailable' ]) } else { - updateClassList(k, [], [ + updateClassList(id, [], [ 'researchAvailable', 'researchPurchased', 'researchPurchasedAvailable', @@ -699,27 +713,14 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { } calculateAnts() - calculateRuneLevels() - calculateAntSacrificeELO() - calculateTalismanEffects() calculateObtainium() - ascensionAchievementCheck(1) + awardAchievementGroup('ascensionCount') player.ascensionCounter = 0 player.ascensionCounterReal = 0 player.ascensionCounterRealReal = 0 updateTalismanInventory() - updateTalismanAppearance(0) - updateTalismanAppearance(1) - updateTalismanAppearance(2) - updateTalismanAppearance(3) - updateTalismanAppearance(4) - updateTalismanAppearance(5) - updateTalismanAppearance(6) - calculateCubeBlessings() - calculateTesseractBlessings() - calculateHypercubeBlessings() if (player.cubeUpgrades[4] === 1) { player.upgrades[94] = 1 @@ -759,8 +760,8 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { } campaignIconHTMLUpdate(campaignName) } - - player.campaigns.computeTotalCampaignTokens() + updateTokens() + updateMaxTokens() campaignTokenRewardHTMLUpdate() } @@ -780,15 +781,18 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { if (input === 'ascension' || input === 'ascensionChallenge') { // Hepteract Autocraft - const autoHepteractCrafts = getAutoHepteractCrafts() - const numberOfAutoCraftsAndOrbs = autoHepteractCrafts.length + (player.overfluxOrbsAutoBuy ? 1 : 0) + const numberOfAutoCraftsAndOrbs = Object.values(hepteracts).filter((v) => v.AUTO).length + + (player.overfluxOrbsAutoBuy ? 1 : 0) if (player.highestSingularityCount >= 1 && numberOfAutoCraftsAndOrbs > 0) { // Computes the max number of Hepteracts to spend on each auto Hepteract craft const heptAutoSpend = Math.floor( (player.wowAbyssals / numberOfAutoCraftsAndOrbs) * (player.hepteractAutoCraftPercentage / 100) ) - for (const craft of autoHepteractCrafts) { - craft.autoCraft(heptAutoSpend) + + for (const hept of hepteractKeys) { + if (player.hepteracts[hept].AUTO) { + autoCraftHepteracts(hept, heptAutoSpend) + } } if (player.overfluxOrbsAutoBuy) { @@ -877,6 +881,14 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { player.unlocks.rrow2 = false player.unlocks.rrow3 = false player.unlocks.rrow4 = false + player.unlocks.anthill = false + player.unlocks.talismans = false + player.unlocks.ascensions = false + player.unlocks.blessings = false + player.unlocks.tesseracts = false + player.unlocks.hypercubes = false + player.unlocks.spirits = false + player.unlocks.platonics = false player.ascendBuilding1.owned = 0 player.ascendBuilding1.generated = new Decimal('0') @@ -916,38 +928,18 @@ export const reset = (input: resetNames, fast = false, from = 'unknown') => { * Computes which achievements in 274-280 are achievable given current singularity number */ export const updateSingularityAchievements = (): void => { - if (player.highestSingularityCount >= 1) { - achievementaward(274) - } - if (player.highestSingularityCount >= 2) { - achievementaward(275) - } - if (player.highestSingularityCount >= 3) { - achievementaward(276) - } - if (player.highestSingularityCount >= 4) { - achievementaward(277) - } - if (player.highestSingularityCount >= 5) { - achievementaward(278) - } - if (player.highestSingularityCount >= 7) { - achievementaward(279) - } - if (player.highestSingularityCount >= 10) { - achievementaward(280) - } + awardAchievementGroup('singularityCount') } export const updateSingularityMilestoneAwards = (singularityReset = true): void => { // 1 transcension, 1001 mythos - if (player.achievements[275] > 0) { // Singularity 2 + if (player.highestSingularityCount >= 2) { // Singularity 2 if (singularityReset) { player.prestigeCount = 1 player.transcendCount = 1 } player.transcendPoints = new Decimal('1001') - + player.firstOwnedCoin = 1 player.unlocks.coinone = true player.unlocks.cointwo = true player.unlocks.cointhree = true @@ -955,13 +947,8 @@ export const updateSingularityMilestoneAwards = (singularityReset = true): void player.unlocks.prestige = true player.unlocks.generation = true player.unlocks.transcend = true - for (let i = 0; i < 5; i++) { - achievementaward(4 + 7 * i) - } - achievementaward(36) // 1 prestige - achievementaward(43) // 1 transcension } - if (player.achievements[276] > 0) { // Singularity 3 + if (player.highestSingularityCount >= 3) { // Singularity 3 if (player.currentChallenge.ascension !== 12) { if (singularityReset) { player.reincarnationCount = 1 @@ -971,68 +958,43 @@ export const updateSingularityMilestoneAwards = (singularityReset = true): void player.unlocks.reincarnate = true player.unlocks.rrow1 = true player.researches[47] = 1 - - for (let i = 0; i < 2; i++) { - for (let j = 0; j < 5; j++) { - achievementaward(78 + i + 7 * j) - } - } - - for (let i = 0; i < 7; i++) { - achievementaward(57 + i) - achievementaward(64 + i) - achievementaward(71 + i) - } - - achievementaward(37) - achievementaward(38) - achievementaward(44) - achievementaward(50) - achievementaward(80) - achievementaward(87) } - if (player.achievements[277] > 0) { // Singularity 4 + if (player.highestSingularityCount >= 4) { // Singularity 4 if (player.currentChallenge.ascension !== 14) { - player.researchPoints = Math.floor( + player.obtainium = new Decimal(Math.floor( 500 * calculateSingularityDebuff('Researches') - ) + )) } if (player.currentChallenge.ascension !== 12) { player.reincarnationPoints = new Decimal('1e16') } player.challengecompletions[6] = 1 player.highestchallengecompletions[6] = 1 - achievementaward(113) } const shopItemPerk_5 = ['offeringAuto', 'offeringEX', 'obtainiumAuto', 'obtainiumEX', 'antSpeed', 'cashGrab'] as const - const perk_5 = player.achievements[278] > 0 + const perk_5 = player.highestSingularityCount >= 5 if (perk_5 && singularityReset) { // Singularity 5 for (const key of shopItemPerk_5) { player.shopUpgrades[key] = 10 } player.cubeUpgrades[7] = 1 } - if (player.achievements[279] > 0) { // Singularity 7 + if (player.highestSingularityCount >= 7) { // Singularity 7 player.challengecompletions[7] = 1 player.highestchallengecompletions[7] = 1 - achievementaward(120) if (player.currentChallenge.ascension !== 12) { player.reincarnationPoints = new Decimal('1e100') } } - if (player.achievements[280] > 0) { // Singularity 10 - achievementaward(124) - achievementaward(127) + if (player.highestSingularityCount >= 10) { // Singularity 10 player.challengecompletions[8] = 1 player.highestchallengecompletions[8] = 1 player.cubeUpgrades[8] = 1 player.cubeUpgrades[4] = 1 // Adding these ones, player.cubeUpgrades[5] = 1 // so they wont reset player.cubeUpgrades[6] = 1 // on first Ascension + player.unlocks.anthill = true player.firstOwnedAnts = 1 - for (let i = 0; i < 7; i++) { - achievementaward(176 + i) - } } if (player.highestSingularityCount > 10) { // Must be the same as autoResearchEnabled() player.cubeUpgrades[9] = 1 @@ -1058,7 +1020,8 @@ export const updateSingularityMilestoneAwards = (singularityReset = true): void ] as const player.challengecompletions[9] = 1 player.highestchallengecompletions[9] = 1 - achievementaward(134) + player.unlocks.talismans = true + player.unlocks.blessings = true player.antPoints = new Decimal('1e100') player.antUpgrades[11] = 1 for (const key of shopItemPerk_20) { @@ -1083,14 +1046,14 @@ export const updateSingularityMilestoneAwards = (singularityReset = true): void player.cubeUpgrades[72] = 1 } - if (player.singularityUpgrades.platonicAlpha.getEffect().bonus && player.platonicUpgrades[5] === 0) { + if (getGQUpgradeEffect('platonicAlpha') && player.platonicUpgrades[5] === 0) { player.platonicUpgrades[5] = 1 updatePlatonicUpgradeBG(5) } if (singularityReset) { for (let j = 1; j <= 15; j++) { - challengeachievementcheck(j) + challengeAchievementCheck(j) } } resetUpgrades(3) @@ -1111,7 +1074,7 @@ export const updateSingularityMilestoneAwards = (singularityReset = true): void // updates singularity perks that do not get saved to player object // so that we can call on save load to fix game state export const updateSingularityGlobalPerks = () => { - const perk_5 = player.achievements[278] > 0 + const perk_5 = player.highestSingularityCount >= 5 const shopItemPerk_5 = ['offeringAuto', 'offeringEX', 'obtainiumAuto', 'obtainiumEX', 'antSpeed', 'cashGrab'] as const for (const key of shopItemPerk_5) { shopData[key].refundMinimumLevel = perk_5 ? 10 : key.endsWith('Auto') ? 1 : 0 @@ -1145,7 +1108,7 @@ export const updateSingularityGlobalPerks = () => { } export const singularity = (setSingNumber = -1) => { - if (player.runelevels[6] === 0 && setSingNumber === -1) { + if (runes.antiquities.level === 0 && setSingNumber === -1) { Alert( 'You nearly triggered a double singularity bug! Oh no! Luckily, our staff prevented this from happening.' ) @@ -1172,13 +1135,16 @@ export const singularity = (setSingNumber = -1) => { hyperTribs: sumContents(hypercubeArray), platTribs: sumContents(platonicArray), octeracts: player.totalWowOcteracts, - quarkHept: player.hepteractCrafts.quark.BAL, + quarkHept: hepteracts.quark.BAL, kind: 'singularity' } resetHistoryAdd('singularity', historyEntry) } - // reset the rune instantly to hopefully prevent a double singularity - player.runelevels[6] = 0 + + resetRunes('singularity') + resetRuneBlessings('singularity') + resetRuneSpirits('singularity') + resetTalismanData('singularity') player.goldenQuarks += calculateGoldenQuarks() @@ -1189,10 +1155,10 @@ export const singularity = (setSingNumber = -1) => { player.highestSingularityCount = player.singularityCount if (player.highestSingularityCount === 5) { - player.singularityUpgrades.goldenQuarks3.freeLevels += 1 + goldenQuarkUpgrades.goldenQuarks3.freeLevel += 1 } if (player.highestSingularityCount === 10) { - player.singularityUpgrades.goldenQuarks3.freeLevels += 2 + goldenQuarkUpgrades.goldenQuarks3.freeLevel += 2 } } } else { @@ -1214,6 +1180,9 @@ export const singularity = (setSingNumber = -1) => { changeSubTab(Tabs.Singularity, { page: 0 }) // set 'singularity main' changeSubTab(Tabs.Settings, { page: 0 }) // set 'statistics main' + hold.achievements = [...player.achievements] + hold.progressiveAchievements = { ...player.progressiveAchievements } + hold.history.singularity = player.history.singularity hold.totalQuarksEver = player.totalQuarksEver hold.singularityCount = player.singularityCount @@ -1222,6 +1191,10 @@ export const singularity = (setSingNumber = -1) => { hold.shopUpgrades = player.shopUpgrades hold.shopPotionsConsumed = player.shopPotionsConsumed + hold.runes = { ...player.runes } + hold.talismans = { ...player.talismans } + hold.cubeUpgrades[80] = player.cubeUpgrades[80] + if (!player.singularityChallenges.limitedTime.rewards.preserveQuarks) { player.worlds.reset() hold.worlds = Number(hold.worlds) @@ -1229,38 +1202,10 @@ export const singularity = (setSingNumber = -1) => { hold.worlds = Number(player.worlds) } - // Exclude potentially non-latin1 characters from the save - hold.singularityUpgrades = Object.fromEntries( - Object.entries(player.singularityUpgrades).map(([key, value]) => { - return [key, { - level: value.level, - goldenQuarksInvested: value.goldenQuarksInvested, - toggleBuy: value.toggleBuy, - freeLevels: value.freeLevels - }] - }) - ) as Player['singularityUpgrades'] - hold.octeractUpgrades = Object.fromEntries( - Object.entries(player.octeractUpgrades).map(([key, value]) => { - return [key, { - level: value.level, - octeractsInvested: value.octeractsInvested, - toggleBuy: value.toggleBuy, - freeLevels: value.freeLevels - }] - }) - ) as unknown as Player['octeractUpgrades'] - hold.blueberryUpgrades = Object.fromEntries( - Object.entries(player.blueberryUpgrades).map(([key, value]) => { - return [key, { - level: value.level, - ambrosiaInvested: value.ambrosiaInvested, - blueberriesInvested: value.blueberriesInvested, - toggleBuy: value.toggleBuy, - freeLevels: value.freeLevels - }] - }) - ) as unknown as Player['blueberryUpgrades'] + hold.goldenQuarkUpgrades = { ...player.goldenQuarkUpgrades } + hold.octUpgrades = { ...player.octUpgrades } + hold.ambrosiaUpgrades = { ...player.ambrosiaUpgrades } + hold.spentBlueberries = player.spentBlueberries hold.autoChallengeToggles = player.autoChallengeToggles hold.autoChallengeTimer = player.autoChallengeTimer @@ -1296,13 +1241,6 @@ export const singularity = (setSingNumber = -1) => { hold.prestigeamount = player.prestigeamount hold.transcendamount = player.transcendamount hold.reincarnationamount = player.reincarnationamount - hold.talismanOne = player.talismanOne - hold.talismanTwo = player.talismanTwo - hold.talismanThree = player.talismanThree - hold.talismanFour = player.talismanFour - hold.talismanFive = player.talismanFive - hold.talismanSix = player.talismanSix - hold.talismanSeven = player.talismanSeven hold.buyTalismanShardPercent = player.buyTalismanShardPercent hold.antMax = player.antMax hold.autoAntSacrifice = player.autoAntSacrifice @@ -1361,24 +1299,16 @@ export const singularity = (setSingNumber = -1) => { ) as Player['singularityChallenges'] hold.iconSet = player.iconSet - // Quark Hepteract craft is saved entirely. For other crafts we only save their auto setting - hold.hepteractCrafts.quark = player.hepteractCrafts.quark - for (const craftName of Object.keys(player.hepteractCrafts)) { - if (craftName !== 'quark') { - const craftKey = craftName as keyof Player['hepteractCrafts'] - hold.hepteractCrafts[craftKey].AUTO = player.hepteractCrafts[craftKey].AUTO - } - } + resetHepteracts('singularity') + // Hold hepteract data needed in player after resetHepteracts (preserving AUTO and Quark ValueOf) + hold.hepteracts = player.hepteracts + hold.ambrosia = player.ambrosia hold.lifetimeAmbrosia = player.lifetimeAmbrosia hold.visitedAmbrosiaSubtab = player.visitedAmbrosiaSubtab hold.blueberryTime = player.blueberryTime hold.blueberryLoadouts = player.blueberryLoadouts hold.blueberryLoadoutMode = player.blueberryLoadoutMode as BlueberryLoadoutMode - hold.wowCubes = Number(player.wowCubes) - hold.wowTesseracts = Number(player.wowTesseracts) - hold.wowHypercubes = Number(player.wowHypercubes) - hold.wowPlatonicCubes = Number(player.wowPlatonicCubes) const saveCode42 = player.codes.get(42) ?? false const saveCode43 = player.codes.get(43) ?? false @@ -1428,22 +1358,19 @@ const resetUpgrades = (i: number) => { player.upgrades[46] = 0 } - if (player.researches[41] < 0.5) { + if (player.researches[41] === 0) { player.upgrades[88] = 0 } - if (player.achievements[50] === 0) { - player.upgrades[89] = 0 - } - if (player.researches[42] < 0.5) { + if (player.researches[42] === 0) { player.upgrades[90] = 0 } - if (player.researches[43] < 0.5) { + if (player.researches[43] === 0) { player.upgrades[91] = 0 } - if (player.researches[44] < 0.5) { + if (player.researches[44] === 0) { player.upgrades[92] = 0 } - if (player.researches[45] < 0.5) { + if (player.researches[45] === 0) { player.upgrades[93] = 0 } @@ -1465,28 +1392,6 @@ const resetUpgrades = (i: number) => { } if (i > 1.5) { - if (player.achievements[4] < 0.5) { - player.upgrades[81] = 0 - } - if (player.achievements[11] < 0.5) { - player.upgrades[82] = 0 - } - if (player.achievements[18] < 0.5) { - player.upgrades[83] = 0 - } - if (player.achievements[25] < 0.5) { - player.upgrades[84] = 0 - } - if (player.achievements[32] < 0.5) { - player.upgrades[85] = 0 - } - if (player.achievements[87] < 0.5) { - player.upgrades[86] = 0 - } - if (player.achievements[80] < 0.5) { - player.upgrades[87] = 0 - } - player.upgrades[101] = 0 player.upgrades[102] = 0 player.upgrades[103] = 0 @@ -1510,20 +1415,13 @@ const resetUpgrades = (i: number) => { player.crystalUpgrades = [0, 0, 0, 0, 0, 0, 0, 0] player.crystalUpgradesCost = [7, 15, 20, 40, 100, 200, 500, 1000] - updateEffectiveLevelMult() // update before prism rune, fixes c15 bug - let m = 0 - m += Math.floor(G.rune3level * G.effectiveLevelMult / 16) * 100 / 100 if (player.upgrades[73] > 0.5 && player.currentChallenge.reincarnation !== 0) { m += 10 } player.crystalUpgrades = [m, m, m, m, m, m, m, m] } - if (player.achievements[87] > 0.5) { - player.upgrades[86] = 1 - } - for (let x = 1; x <= 125; x++) { upgradeupdate(x, true) } @@ -1571,7 +1469,6 @@ export const resetAnts = () => { } calculateAnts() - calculateRuneLevels() } export const getResetResearches = () => { @@ -1596,22 +1493,9 @@ export const getResetResearches = () => { } const resetResearches = () => { - player.researchPoints = 0 + player.obtainium = new Decimal(0) for (const item of getResetResearches()) { player.researches[item] = 0 } } - -const resetTalismans = () => { - player.talismanLevels = [0, 0, 0, 0, 0, 0, 0] - player.talismanRarity = [1, 1, 1, 1, 1, 1, 1] - - player.talismanShards = 0 - player.commonFragments = 0 - player.uncommonFragments = 0 - player.rareFragments = 0 - player.epicFragments = 0 - player.legendaryFragments = 0 - player.mythicalFragments = 0 -} diff --git a/src/RuneBlessings.ts b/src/RuneBlessings.ts new file mode 100644 index 000000000..00d1c476c --- /dev/null +++ b/src/RuneBlessings.ts @@ -0,0 +1,467 @@ +import Decimal from 'break_infinity.js' +import i18next from 'i18next' +import { awardAchievementGroup } from './Achievements' +import { DOMCacheGetOrSet } from './Cache/DOM' +import { calculateSalvageRuneEXPMultiplier } from './Calculate' +import { resetTiers } from './Reset' +import { type RuneKeys, runes } from './Runes' +import { format, formatAsPercentIncrease, player } from './Synergism' +import { Tabs } from './Tabs' +import { getTalismanEffects } from './Talismans' +import { IconSets } from './Themes' +import { assert } from './Utility' +import { Globals as G } from './Variables' + +type RuneBlessingTypeMap = { + speed: { globalSpeed: number } + duplication: { multiplierBoosts: number } + prism: { antSacrificeMult: number } + thrift: { accelBoostCostDelay: number } + superiorIntellect: { obtToAntExponent: number } +} + +export type RuneBlessingKeys = keyof RuneBlessingTypeMap + +export interface RuneBlessingData { + level: number + runeEXP: Decimal + costCoefficient: Decimal + levelsPerOOM: number + effectiveLevelMult: () => number + runeEXPPerOffering: () => Decimal + minimalResetTier: keyof typeof resetTiers + effects: (n: number) => RuneBlessingTypeMap[K] + effectsDescription: (n: number) => string + name: () => string + description: () => string + HTMLData: { + imgName: string + } +} + +const otherBlessingMultipliers = () => { + return (1 + (6.9 * player.researches[134]) / 100) + * (getTalismanEffects('midas').blessingBonus) + * (1 + 0.1 * Decimal.log(player.epicFragments.add(1), 10) * player.researches[174]) + * (1 + (2 * player.researches[194]) / 100) + * (1 + 0.25 * player.researches[160]) + * G.challenge15Rewards.blessingBonus.value +} + +const blessingMultiplier = (key: RuneKeys) => { + return ( + (runes[key].level + runes[key].freeLevels()) + * otherBlessingMultipliers() + ) +} + +export const runeBlessings: { [K in RuneBlessingKeys]: RuneBlessingData } = { + speed: { + level: 0, + runeEXP: new Decimal(0), + costCoefficient: new Decimal(1e8), + levelsPerOOM: 4, + effects: (level) => { + const globalSpeed = 1 + level / 1000000 + return { + globalSpeed + } + }, + effectsDescription: (level) => { + const globalSpeed = runeBlessings.speed.effects(level).globalSpeed + return i18next.t('runes.blessings.speed.globalSpeed', { + val: formatAsPercentIncrease(globalSpeed, 2) + }) + }, + effectiveLevelMult: () => blessingMultiplier('speed'), + runeEXPPerOffering: () => calculateSalvageRuneEXPMultiplier(), + minimalResetTier: 'singularity', + name: () => runes.speed.name(), + description: () => i18next.t('runes.blessings.speed.description'), + HTMLData: { + imgName: 'BlessingSpeed' + } + }, + duplication: { + level: 0, + runeEXP: new Decimal(0), + costCoefficient: new Decimal(1e10), + levelsPerOOM: 4, + effects: (level) => { + const multiplierBoosts = 1 + level / 1000000 + return { + multiplierBoosts + } + }, + effectsDescription: (level) => { + const multiplierBoosts = runeBlessings.duplication.effects(level).multiplierBoosts + return i18next.t('runes.blessings.duplication.multiplierBoosts', { + val: formatAsPercentIncrease(multiplierBoosts, 2) + }) + }, + effectiveLevelMult: () => blessingMultiplier('duplication'), + runeEXPPerOffering: () => calculateSalvageRuneEXPMultiplier(), + minimalResetTier: 'singularity', + name: () => runes.duplication.name(), + description: () => i18next.t('runes.blessings.duplication.description'), + HTMLData: { + imgName: 'BlessingDuplication' + } + }, + prism: { + level: 0, + runeEXP: new Decimal(0), + costCoefficient: new Decimal(1e13), + levelsPerOOM: 4, + effects: (level) => { + const antSacrificeMult = 1 + level / 1000000 + return { + antSacrificeMult + } + }, + effectsDescription: (level) => { + const antSacrificeMult = runeBlessings.prism.effects(level).antSacrificeMult + return i18next.t('runes.blessings.prism.antSacrifice', { + val: formatAsPercentIncrease(antSacrificeMult, 2) + }) + }, + effectiveLevelMult: () => blessingMultiplier('prism'), + runeEXPPerOffering: () => calculateSalvageRuneEXPMultiplier(), + minimalResetTier: 'singularity', + name: () => runes.prism.name(), + description: () => i18next.t('runes.blessings.prism.description'), + HTMLData: { + imgName: 'BlessingPrism' + } + }, + thrift: { + level: 0, + runeEXP: new Decimal(0), + costCoefficient: new Decimal(1e16), + levelsPerOOM: 4, + effects: (level) => { + const accelBoostCostDelay = 1 + level / 1000000 + return { + accelBoostCostDelay + } + }, + effectsDescription: (level) => { + const accelBoostCostDelay = runeBlessings.thrift.effects(level).accelBoostCostDelay + return i18next.t('runes.blessings.thrift.acceleratorBoostDelay', { + val: formatAsPercentIncrease(accelBoostCostDelay, 2) + }) + }, + effectiveLevelMult: () => blessingMultiplier('thrift'), + runeEXPPerOffering: () => calculateSalvageRuneEXPMultiplier(), + minimalResetTier: 'singularity', + name: () => runes.thrift.name(), + description: () => i18next.t('runes.blessings.thrift.description'), + HTMLData: { + imgName: 'BlessingThrift' + } + }, + superiorIntellect: { + level: 0, + runeEXP: new Decimal(0), + costCoefficient: new Decimal(1e20), + levelsPerOOM: 4, + effects: (level) => { + const obtToAntExponent = Math.log(1 + level / 1000000) + return { + obtToAntExponent + } + }, + effectsDescription: (level) => { + const obtToAntExponent = runeBlessings.superiorIntellect.effects(level).obtToAntExponent + const obtExponentHTML = i18next.t('runes.blessings.superiorIntellect.obtExponent', { + val: format(obtToAntExponent, 3, true) + }) + const antSpeedHTML = i18next.t('runes.blessings.superiorIntellect.antSpeed', { + val: format(Decimal.pow(player.obtainium, obtToAntExponent), 2, false) + }) + return `${obtExponentHTML}
${antSpeedHTML}` + }, + effectiveLevelMult: () => blessingMultiplier('superiorIntellect'), + runeEXPPerOffering: () => calculateSalvageRuneEXPMultiplier(), + minimalResetTier: 'singularity', + name: () => runes.superiorIntellect.name(), + description: () => i18next.t('runes.blessings.superiorIntellect.description'), + HTMLData: { + imgName: 'BlessingSI' + } + } +} + +export const runeBlessingKeys = Object.keys(runeBlessings) as RuneBlessingKeys[] + +export const getRuneBlessingPower = (bless: RuneBlessingKeys): number => { + const blessingPowerMult = runeBlessings[bless].effectiveLevelMult() + return runeBlessings[bless].level * blessingPowerMult +} + +export const getRuneBlessingEffect = (bless: T): RuneBlessingTypeMap[T] => { + return runeBlessings[bless].effects(getRuneBlessingPower(bless)) +} + +export const getRuneBlessingEXPPerOffering = (bless: RuneBlessingKeys): Decimal => { + return runeBlessings[bless].runeEXPPerOffering() +} + +const computeEXPToLevel = (bless: RuneBlessingKeys, level: number) => { + const levelPerOOM = runeBlessings[bless].levelsPerOOM + return runeBlessings[bless].costCoefficient.times(Decimal.pow(10, level / levelPerOOM).minus(1)) +} + +const computeEXPLeftToLevel = (bless: RuneBlessingKeys, level: number) => { + return Decimal.max(0, computeEXPToLevel(bless, level).minus(runeBlessings[bless].runeEXP)) +} + +export const getRuneBlessingTNL = (bless: RuneBlessingKeys) => { + return computeEXPLeftToLevel(bless, runeBlessings[bless].level + 1) +} + +export const buyBlessingLevels = (blessing: RuneBlessingKeys, budget: Decimal) => { + if (!player.unlocks.blessings) { + return + } + + const levelsToAdd = player.runeBlessingBuyAmount + + levelBlessing(blessing, levelsToAdd, budget) + updateLevelsFromEXP(blessing) +} + +export const buyAllBlessingLevels = (budget: Decimal) => { + const ratio = runeBlessingKeys.length + const budgetPerBlessing = budget.div(ratio).floor() + for (const key of runeBlessingKeys) { + buyBlessingLevels(key, budgetPerBlessing) + } + + if (player.offerings.lt(0)) { + // TODO: Figure out why this fucking happens so often + player.offerings = new Decimal(0) + } +} + +export const levelBlessing = (bless: RuneBlessingKeys, timesLeveled: number, budget: Decimal) => { + let budgetUsed: Decimal + + const expRequired = computeEXPLeftToLevel(bless, runeBlessings[bless].level + timesLeveled) + const runeEXPPerOffering = getRuneBlessingEXPPerOffering(bless) + const offeringsRequired = Decimal.max(1, expRequired.div(runeEXPPerOffering).ceil()) + + if (offeringsRequired.gt(budget)) { + runeBlessings[bless].runeEXP = runeBlessings[bless].runeEXP.add(budget.times(runeEXPPerOffering)) + budgetUsed = budget + } else { + runeBlessings[bless].runeEXP = computeEXPToLevel(bless, runeBlessings[bless].level + timesLeveled) + budgetUsed = offeringsRequired + } + + player.offerings = player.offerings.sub(budgetUsed) + + // this.updatePlayerEXP() + // this.updateRuneEffectHTML() +} + +export const setBlessingLevel = (bless: RuneBlessingKeys, level: number) => { + const exp = computeEXPToLevel(bless, level) + runeBlessings[bless].level = level + runeBlessings[bless].runeEXP = exp +} + +const updateLevelsFromEXP = (bless: RuneBlessingKeys) => { + const levelsPerOOM = runeBlessings[bless].levelsPerOOM + const levels = Math.floor( + levelsPerOOM * Decimal.log10(runeBlessings[bless].runeEXP.div(runeBlessings[bless].costCoefficient).plus(1)) + ) + // Floating point imprecision fix + if (computeEXPLeftToLevel(bless, levels + 1).eq(0)) { + runeBlessings[bless].level = levels + 1 + } else { + runeBlessings[bless].level = levels + } +} + +export const updateAllBlessingLevelsFromEXP = () => { + for (const bless of runeBlessingKeys) { + updateLevelsFromEXP(bless) + } + awardAchievementGroup('speedBlessing') +} + +// Gives levels to buy, total EXP to that level, and offerings required to reach that level +export const maxBlessingLevelPurchaseInformation = (bless: RuneBlessingKeys, budget: Decimal) => { + if (budget.lt(0)) { + return { levels: 0, expRequired: new Decimal(0), offerings: new Decimal(0) } + } + + const runeEXPPerOffering = getRuneBlessingEXPPerOffering(bless) + const totalEXPAvailable = budget.times(runeEXPPerOffering).add(runeBlessings[bless].runeEXP) + const levelsPerOOM = runeBlessings[bless].levelsPerOOM + const costCoeff = runeBlessings[bless].costCoefficient + + // Take into account the smallest increment to floating point + const minOfferingsToIncreaseEXP = Decimal.ceil( + runeBlessings[bless].runeEXP.div(runeEXPPerOffering.times(Number.MAX_SAFE_INTEGER)) + ) + + // Calculate max level we can reach with available EXP + // EXP formula: costCoeff * (10^(level/levelsPerOOM) - 1) + // Solving for level: level = levelsPerOOM * log10((EXP/costCoeff) + 1) + // Unlike Runes, we always call this function, BUT we have a set cap on levels we can buy at once + // (chosen by the player) + const upperLimit = player.runeBlessingBuyAmount + const maxLevel = Math.floor(levelsPerOOM * Decimal.log10(totalEXPAvailable.div(costCoeff).plus(1))) + const levelsGained = Math.min(upperLimit, Math.max(0, maxLevel - runeBlessings[bless].level)) + + if (levelsGained === 0) { + // Can't afford any levels, return next level stuff + const nextLevelEXP = computeEXPToLevel(bless, runeBlessings[bless].level + 1) + const offeringsRequired = Decimal.max( + minOfferingsToIncreaseEXP, + nextLevelEXP.minus(runeBlessings[bless].runeEXP).div(runeEXPPerOffering).ceil() + ) + return { levels: 1, expRequired: nextLevelEXP, offerings: offeringsRequired } + } + + // Return the levels we can gain and the EXP required for that many levels + const expRequired = computeEXPToLevel(bless, runeBlessings[bless].level + levelsGained) + // Need to be recomputed since offerings required is not necessarily equal to budget. + const offeringsRequired = Decimal.max( + minOfferingsToIncreaseEXP, + expRequired.minus(runeBlessings[bless].runeEXP).div(runeEXPPerOffering).ceil() + ) + return { levels: levelsGained, expRequired: expRequired, offerings: offeringsRequired } +} + +export const updateRuneBlessingHTML = (bless: RuneBlessingKeys) => { + assert(G.currentTab === Tabs.Runes, 'current tab is not Runes') + + DOMCacheGetOrSet(`${bless}RuneBlessingLevel`).innerHTML = i18next.t('runes.blessings.blessingLevel', { + amount: format(runeBlessings[bless].level, 0, true) + }) + + const { levels, offerings } = maxBlessingLevelPurchaseInformation(bless, player.offerings) + + DOMCacheGetOrSet(`${bless}RuneBlessingTNL`).innerHTML = i18next.t('runes.TNL', { + levelAmount: levels, + offerings: format(offerings, 2, false) + }) +} + +export const focusedRuneBlessingHTML = (bless: RuneBlessingKeys) => { + const name = runeBlessings[bless].name() + const nameHTML = `${name}` + + const description = runeBlessings[bless].description() + const descriptionHTML = `${description}` + + const blessingPowerHTML = i18next.t('runes.blessings.effectiveBlessingPower', { + power: format(getRuneBlessingPower(bless), 0, true) + }) + const baseBlessingPowerHTML = i18next.t('runes.blessings.baseBlessingPower', { + level: format(runeBlessings[bless].level, 0, true) + }) + const runePowerMultHTML = i18next.t('runes.blessings.runePowerMult', { + level: format(runes[bless as RuneKeys].level + runes[bless as RuneKeys].freeLevels(), 0, true), + name: runes[bless as RuneKeys].name() + }) + const otherPowerMultHTML = i18next.t('runes.blessings.otherPowerMult', { + mult: format(otherBlessingMultipliers(), 2, true) + }) + + const effectsDescriptionHTML = runeBlessings[bless].effectsDescription(getRuneBlessingPower(bless)) + + const coefficientHTML = i18next.t('runes.blessings.runeCoefficientText', { + val: format(runeBlessings[bless].levelsPerOOM, 0, true) + }) + const levelCostIncreaseHTML = i18next.t('runes.perLevelIncrease', { + x: format(Math.pow(10, 1 / runeBlessings[bless].levelsPerOOM), 4, false) + }) + + const experienceHTML = i18next.t('runes.blessings.blessingEXP', { + exp: format(runeBlessings[bless].runeEXP, 2, true), + perEXP: format(getRuneBlessingEXPPerOffering(bless), 2, true) + }) + + const { levels, expRequired, offerings } = maxBlessingLevelPurchaseInformation(bless, player.offerings) + const levelInfo = i18next.t('runes.EXPNeeded', { + level: format(levels, 0, true), + exp: format(expRequired, 2, true), + offerings: format(offerings, 0, true) + }) + + return `${nameHTML}
${descriptionHTML} +

${blessingPowerHTML}
${baseBlessingPowerHTML}
${runePowerMultHTML}
${otherPowerMultHTML} +

${effectsDescriptionHTML} +

${coefficientHTML}
${levelCostIncreaseHTML} +

${experienceHTML}
${levelInfo}` +} + +export function resetRuneBlessings (tier: keyof typeof resetTiers) { + for (const bless of runeBlessingKeys) { + if (resetTiers[tier] >= resetTiers[runeBlessings[bless].minimalResetTier]) { + runeBlessings[bless].level = 0 + runeBlessings[bless].runeEXP = new Decimal(0) + } + } +} + +export const generateBlessingsHTML = () => { + const blessingRow = DOMCacheGetOrSet('runeBlessings') + + for (const key of Object.keys(runeBlessings) as RuneBlessingKeys[]) { + // Create unlocked rune container + const runesDiv = document.createElement('div') + runesDiv.className = 'runeType' + runesDiv.id = `${key}RuneBlessingContainer` + runesDiv.style.border = `2px solid ${runes[key].runeHTMLStyle.borderColor}` + runesDiv.style.borderRadius = '8px' + runesDiv.style.margin = '2px' + runesDiv.style.setProperty( + '--glow-color', + `color-mix(in srgb, ${runes[key].runeHTMLStyle.borderColor} 50%, orchid 50%)` + ) + + const runeName = document.createElement('p') + runeName.className = 'runeTypeElement' + runeName.setAttribute('i18n', `runes.${key}.name`) + runeName.textContent = i18next.t(`runes.${key}.name`) + + runesDiv.appendChild(runeName) + + const runeIcon = document.createElement('img') + runeIcon.className = 'runeImage' + runeIcon.id = `${key}RuneBlessing` + runeIcon.alt = `${key} Rune` + runeIcon.src = `Pictures/${IconSets[player.iconSet][0]}/${runeBlessings[key].HTMLData.imgName}.png` + runeIcon.loading = 'lazy' + + runesDiv.appendChild(runeIcon) + + const runeLevel = document.createElement('span') + runeLevel.className = 'runeTypeElement' + runeLevel.id = `${key}RuneBlessingLevel` + runeLevel.textContent = 'Level 0/30' + + runesDiv.appendChild(runeLevel) + + const runeTNL = document.createElement('span') + runeTNL.className = 'runeTypeElement' + runeTNL.id = `${key}RuneBlessingTNL` + runeTNL.textContent = '0' + runesDiv.appendChild(runeTNL) + + const sacrificeButton = document.createElement('button') + sacrificeButton.className = 'runeTypeElement' + sacrificeButton.id = `${key}RuneBlessingPurchase` + sacrificeButton.setAttribute('i18n', 'general.blessCapital') + sacrificeButton.textContent = i18next.t('general.blessCapital') + + runesDiv.appendChild(sacrificeButton) + + blessingRow.appendChild(runesDiv) + } +} diff --git a/src/RuneSpirits.ts b/src/RuneSpirits.ts new file mode 100644 index 000000000..d2a0fabb7 --- /dev/null +++ b/src/RuneSpirits.ts @@ -0,0 +1,447 @@ +import Decimal from 'break_infinity.js' +import i18next from 'i18next' +import { awardAchievementGroup } from './Achievements' +import { DOMCacheGetOrSet } from './Cache/DOM' +import { calculateSalvageRuneEXPMultiplier } from './Calculate' +import { resetTiers } from './Reset' +import { type RuneBlessingKeys, runeBlessings } from './RuneBlessings' +import { type RuneKeys, runes } from './Runes' +import { format, formatAsPercentIncrease, player } from './Synergism' +import { Tabs } from './Tabs' +import { IconSets } from './Themes' +import { assert } from './Utility' +import { Globals as G } from './Variables' + +type RuneSpiritTypeMap = { + speed: { globalSpeed: number } + duplication: { wowCubes: number } + prism: { crystalCaps: number } + thrift: { offerings: number } + superiorIntellect: { obtainium: number } +} + +export type RuneSpiritKeys = keyof RuneSpiritTypeMap + +export interface RuneSpiritData { + level: number + runeEXP: Decimal + costCoefficient: Decimal + levelsPerOOM: number + effectiveLevelMult: () => number + runeEXPPerOffering: () => Decimal + minimalResetTier: keyof typeof resetTiers + effects: (n: number) => RuneSpiritTypeMap[K] + effectsDescription: (n: number) => string + name: () => string + description: () => string + HTMLData: { + imgName: string + } +} + +const otherSpiritMultipliers = () => { + return (1 + (8 * player.researches[164]) / 100) + * (player.researches[165] && player.currentChallenge.ascension !== 0 ? 2 : 1) + * (1 + 0.15 * Decimal.log(player.legendaryFragments.add(1), 10) * player.researches[189]) + * (1 + (2 * player.researches[194]) / 100) + * G.challenge15Rewards.spiritBonus.value + * player.corruptions.used.totalCorruptionDifficultyMultiplier +} + +const spiritMultiplier = (key: RuneKeys) => { + return ( + (runes[key].level + runes[key].freeLevels()) + * runeBlessings[key as RuneBlessingKeys].level + * otherSpiritMultipliers() + ) +} + +export const runeSpirits: { [K in RuneSpiritKeys]: RuneSpiritData } = { + speed: { + level: 0, + runeEXP: new Decimal(0), + costCoefficient: new Decimal(1e50), + levelsPerOOM: 2, + effects: (level) => { + const globalSpeed = 1 + level / 1e9 + return { globalSpeed } + }, + effectsDescription: (level) => { + const globalSpeed = runeSpirits.speed.effects(level).globalSpeed + return i18next.t('runes.spirits.speed.globalSpeed', { + val: formatAsPercentIncrease(globalSpeed, 2) + }) + }, + effectiveLevelMult: () => spiritMultiplier('speed'), + runeEXPPerOffering: () => calculateSalvageRuneEXPMultiplier(), + minimalResetTier: 'singularity', + name: () => runes.speed.name(), + description: () => i18next.t('runes.spirits.speed.description'), + HTMLData: { + imgName: 'SpiritSpeed' + } + }, + duplication: { + level: 0, + runeEXP: new Decimal(0), + costCoefficient: new Decimal(1e60), + levelsPerOOM: 2, + effects: (level) => { + const wowCubes = 1 + level / 1e9 + return { wowCubes } + }, + effectsDescription: (level) => { + const wowCubes = runeSpirits.duplication.effects(level).wowCubes + return i18next.t('runes.spirits.duplication.wowCubes', { + val: formatAsPercentIncrease(wowCubes, 2) + }) + }, + effectiveLevelMult: () => spiritMultiplier('duplication'), + runeEXPPerOffering: () => calculateSalvageRuneEXPMultiplier(), + minimalResetTier: 'singularity', + name: () => runes.duplication.name(), + description: () => i18next.t('runes.spirits.duplication.description'), + HTMLData: { + imgName: 'SpiritDuplication' + } + }, + prism: { + level: 0, + runeEXP: new Decimal(0), + costCoefficient: new Decimal(1e70), + levelsPerOOM: 2, + effects: (level) => { + const crystalCaps = 1 + level / 1e9 + return { crystalCaps } + }, + effectsDescription: (level) => { + const crystalCaps = runeSpirits.prism.effects(level).crystalCaps + return i18next.t('runes.spirits.prism.crystalCaps', { + val: formatAsPercentIncrease(crystalCaps, 2) + }) + }, + effectiveLevelMult: () => spiritMultiplier('prism'), + runeEXPPerOffering: () => calculateSalvageRuneEXPMultiplier(), + minimalResetTier: 'singularity', + name: () => runes.prism.name(), + description: () => i18next.t('runes.spirits.prism.description'), + HTMLData: { + imgName: 'SpiritPrism' + } + }, + thrift: { + level: 0, + runeEXP: new Decimal(0), + costCoefficient: new Decimal(1e85), + levelsPerOOM: 2, + effects: (level) => { + const offerings = 1 + level / 1e9 + return { offerings } + }, + effectsDescription: (level) => { + const offerings = runeSpirits.thrift.effects(level).offerings + return i18next.t('runes.spirits.thrift.offerings', { + val: formatAsPercentIncrease(offerings, 2) + }) + }, + effectiveLevelMult: () => spiritMultiplier('thrift'), + runeEXPPerOffering: () => calculateSalvageRuneEXPMultiplier(), + minimalResetTier: 'singularity', + name: () => runes.thrift.name(), + description: () => i18next.t('runes.spirits.thrift.description'), + HTMLData: { + imgName: 'SpiritThrift' + } + }, + superiorIntellect: { + level: 0, + runeEXP: new Decimal(0), + costCoefficient: new Decimal(1e100), + levelsPerOOM: 2, + effects: (level) => { + const obtainium = 1 + level / 1e9 + return { obtainium } + }, + effectsDescription: (level) => { + const obtainium = runeSpirits.superiorIntellect.effects(level).obtainium + return i18next.t('runes.spirits.superiorIntellect.obtainium', { + val: formatAsPercentIncrease(obtainium, 2) + }) + }, + effectiveLevelMult: () => spiritMultiplier('superiorIntellect'), + runeEXPPerOffering: () => calculateSalvageRuneEXPMultiplier(), + minimalResetTier: 'singularity', + name: () => runes.superiorIntellect.name(), + description: () => i18next.t('runes.spirits.superiorIntellect.description'), + HTMLData: { + imgName: 'SpiritSI' + } + } +} + +export const runeSpiritKeys = Object.keys(runeSpirits) as RuneSpiritKeys[] + +export const getRuneSpiritPower = (spirit: RuneSpiritKeys): number => { + const spiritpowerMult = runeSpirits[spirit].effectiveLevelMult() + return runeSpirits[spirit].level * spiritpowerMult +} + +export const getRuneSpiritEffect = (spirit: T): RuneSpiritTypeMap[T] => { + return runeSpirits[spirit].effects(getRuneSpiritPower(spirit)) +} + +export const getRuneSpiritEXPPerOffering = (spirit: RuneSpiritKeys): Decimal => { + return runeSpirits[spirit].runeEXPPerOffering() +} + +const computeEXPToLevel = (spirit: RuneSpiritKeys, level: number) => { + const levelPerOOM = runeSpirits[spirit].levelsPerOOM + return runeSpirits[spirit].costCoefficient.times(Decimal.pow(10, level / levelPerOOM).minus(1)) +} + +const computeEXPLeftToLevel = (spirit: RuneSpiritKeys, level: number) => { + return Decimal.max(0, computeEXPToLevel(spirit, level).minus(runeSpirits[spirit].runeEXP)) +} + +export const getRuneSpiritTNL = (spirit: RuneSpiritKeys) => { + return computeEXPLeftToLevel(spirit, runeSpirits[spirit].level + 1) +} + +export const buySpiritLevels = (spirit: RuneSpiritKeys, budget: Decimal) => { + if (!player.unlocks.spirits) { + return + } + + const levelsToAdd = player.runeSpiritBuyAmount + + levelSpirit(spirit, levelsToAdd, budget) + updateLevelsFromEXP(spirit) +} + +export const buyAllSpiritLevels = (budget: Decimal) => { + const ratio = runeSpiritKeys.length + for (const key of runeSpiritKeys) { + buySpiritLevels(key, Decimal.floor(budget.div(ratio))) + } + + if (player.offerings.lt(0)) { + // TODO: Figure out why this fucking happens so often + player.offerings = new Decimal(0) + } +} + +export const levelSpirit = (spirit: RuneSpiritKeys, timesLeveled: number, budget: Decimal) => { + let budgetUsed: Decimal + + const expRequired = computeEXPLeftToLevel(spirit, runeSpirits[spirit].level + timesLeveled) + const runeEXPPerOffering = getRuneSpiritEXPPerOffering(spirit) + const offeringsRequired = Decimal.max(1, expRequired.div(runeEXPPerOffering).ceil()) + + if (offeringsRequired.gt(budget)) { + runeSpirits[spirit].runeEXP = runeSpirits[spirit].runeEXP.add(budget.times(runeEXPPerOffering)) + budgetUsed = budget + } else { + runeSpirits[spirit].runeEXP = computeEXPToLevel(spirit, runeSpirits[spirit].level + timesLeveled) + budgetUsed = offeringsRequired + } + + player.offerings = player.offerings.sub(budgetUsed) +} + +export const setSpiritLevel = (spirit: RuneSpiritKeys, level: number) => { + const exp = computeEXPToLevel(spirit, level) + runeSpirits[spirit].level = level + runeSpirits[spirit].runeEXP = exp +} + +const updateLevelsFromEXP = (spirit: RuneSpiritKeys) => { + const levelsPerOOM = runeSpirits[spirit].levelsPerOOM + const levels = Math.floor( + levelsPerOOM * Decimal.log10(runeSpirits[spirit].runeEXP.div(runeSpirits[spirit].costCoefficient).plus(1)) + ) + // Floating point imprecision fix + if (computeEXPLeftToLevel(spirit, levels + 1).eq(0)) { + runeSpirits[spirit].level = levels + 1 + } else { + runeSpirits[spirit].level = levels + } +} + +export const updateAllSpiritLevelsFromEXP = () => { + for (const spirit of runeSpiritKeys) { + updateLevelsFromEXP(spirit) + } + awardAchievementGroup('speedSpirit') +} + +// Gives levels to buy, total EXP to that level, and offerings required to reach that level +export const maxSpiritLevelPurchaseInformation = (spirit: RuneSpiritKeys, budget: Decimal) => { + if (budget.lt(0)) { + return { levels: 0, expRequired: new Decimal(0), offerings: new Decimal(0) } + } + + const runeEXPPerOffering = getRuneSpiritEXPPerOffering(spirit) + const totalEXPAvailable = budget.times(runeEXPPerOffering).add(runeSpirits[spirit].runeEXP) + const levelsPerOOM = runeSpirits[spirit].levelsPerOOM + const costCoeff = runeSpirits[spirit].costCoefficient + + // Calculate max level we can reach with available EXP + // EXP formula: costCoeff * (10^(level/levelsPerOOM) - 1) + // Solving for level: level = levelsPerOOM * log10((EXP/costCoeff) + 1) + // Unlike Runes, we always call this function, BUT we have a set cap on levels we can buy at once + // (chosen by the player) + const upperLimit = player.runeSpiritBuyAmount + const maxLevel = Math.floor(levelsPerOOM * Decimal.log10(totalEXPAvailable.div(costCoeff).plus(1))) + const levelsGained = Math.min(upperLimit, Math.max(0, maxLevel - runeSpirits[spirit].level)) + + if (levelsGained === 0) { + // Can't afford any levels, return next level stuff + const nextLevelEXP = computeEXPToLevel(spirit, runeSpirits[spirit].level + 1) + const offeringsRequired = Decimal.max( + 1, + nextLevelEXP.minus(runeSpirits[spirit].runeEXP).div(runeEXPPerOffering).ceil() + ) + return { levels: 1, expRequired: nextLevelEXP, offerings: offeringsRequired } + } + + // Return the levels we can gain and the EXP required for that many levels + const expRequired = computeEXPToLevel(spirit, runeSpirits[spirit].level + levelsGained) + // Need to be recomputed since offerings required is not necessarily equal to budget. + const offeringsRequired = Decimal.max( + 1, + expRequired.minus(runeSpirits[spirit].runeEXP).div(runeEXPPerOffering).ceil() + ) + return { levels: levelsGained, expRequired: expRequired, offerings: offeringsRequired } +} + +export const updateRuneSpiritHTML = (spirit: RuneSpiritKeys) => { + assert(G.currentTab === Tabs.Runes, 'current tab is not Runes') + + DOMCacheGetOrSet(`${spirit}RuneSpiritLevel`).innerHTML = i18next.t('runes.spirits.spiritLevel', { + amount: format(runeSpirits[spirit].level, 0, true) + }) + + const { levels, offerings } = maxSpiritLevelPurchaseInformation(spirit, player.offerings) + DOMCacheGetOrSet(`${spirit}RuneSpiritTNL`).innerHTML = i18next.t('runes.TNL', { + levelAmount: levels, + offerings: format(offerings, 2, false) + }) +} + +export const focusedRuneSpiritHTML = (spirit: RuneSpiritKeys) => { + const name = runeSpirits[spirit].name() + const nameHTML = `${name}` + + const description = runeSpirits[spirit].description() + const descriptionHTML = `${description}` + + const spiritPowerHTML = i18next.t('runes.spirits.effectiveSpiritPower', { + power: format(getRuneSpiritPower(spirit), 0, true) + }) + const baseSpiritPowerHTML = i18next.t('runes.spirits.baseSpiritPower', { + level: format(runeSpirits[spirit].level, 0, true) + }) + const runePowerMultHTML = i18next.t('runes.spirits.runePowerMult', { + level: format(runes[spirit as RuneKeys].level + runes[spirit as RuneKeys].freeLevels(), 0, true), + name: runes[spirit as RuneKeys].name() + }) + const blessingPowerMultHTML = i18next.t('runes.spirits.blessingPowerMult', { + level: format(runeBlessings[spirit as RuneBlessingKeys].level, 0, true), + name: runeBlessings[spirit as RuneBlessingKeys].name() + }) + const otherPowerMultHTML = i18next.t('runes.spirits.otherPowerMult', { + mult: format(otherSpiritMultipliers(), 2, true) + }) + + const effectsDescriptionHTML = runeSpirits[spirit].effectsDescription(getRuneSpiritPower(spirit)) + + const coefficientHTML = i18next.t('runes.spirits.runeCoefficientText', { + val: format(runeSpirits[spirit].levelsPerOOM, 0, true) + }) + const levelCostIncreaseHTML = i18next.t('runes.perLevelIncrease', { + x: format(Math.pow(10, 1 / runeSpirits[spirit].levelsPerOOM), 4, false) + }) + + const experienceHTML = i18next.t('runes.spirits.spiritEXP', { + exp: format(runeSpirits[spirit].runeEXP, 2, true), + perEXP: format(getRuneSpiritEXPPerOffering(spirit), 2, true) + }) + + const { levels, expRequired, offerings } = maxSpiritLevelPurchaseInformation(spirit, player.offerings) + const levelInfo = i18next.t('runes.EXPNeeded', { + level: format(levels, 0, true), + exp: format(expRequired, 2, true), + offerings: format(offerings, 0, true) + }) + + return `${nameHTML}
${descriptionHTML} +

${spiritPowerHTML}
${baseSpiritPowerHTML}
${runePowerMultHTML}
${blessingPowerMultHTML}
${otherPowerMultHTML} +

${effectsDescriptionHTML} +

${coefficientHTML}
${levelCostIncreaseHTML} +

${experienceHTML}
${levelInfo}` +} + +export function resetRuneSpirits (tier: keyof typeof resetTiers) { + for (const spirit of runeSpiritKeys) { + if (resetTiers[tier] >= resetTiers[runeSpirits[spirit].minimalResetTier]) { + runeSpirits[spirit].level = 0 + runeSpirits[spirit].runeEXP = new Decimal(0) + } + } +} + +export const generateSpiritsHTML = () => { + const spiritRow = DOMCacheGetOrSet('runeSpirits') + + for (const key of runeSpiritKeys) { + const runesDiv = document.createElement('div') + runesDiv.className = 'runeType' + runesDiv.id = `${key}RuneSpiritContainer` + runesDiv.style.border = `2px solid ${runes[key as RuneKeys].runeHTMLStyle.borderColor}` + runesDiv.style.borderRadius = '8px' + runesDiv.style.margin = '2px' + runesDiv.style.setProperty( + '--glow-color', + `color-mix(in srgb, ${runes[key].runeHTMLStyle.borderColor} 50%, lightgoldenrodyellow 50%)` + ) + + const runeName = document.createElement('p') + runeName.className = 'runeTypeElement' + runeName.setAttribute('i18n', `runes.${key}.name`) + runeName.textContent = i18next.t(`runes.${key}.name`) + + runesDiv.appendChild(runeName) + + const runeIcon = document.createElement('img') + runeIcon.className = 'runeImage' + runeIcon.id = `${key}RuneSpirit` + runeIcon.alt = `${key} Rune` + runeIcon.src = `Pictures/${IconSets[player.iconSet][0]}/${runeSpirits[key].HTMLData.imgName}.png` + runeIcon.loading = 'lazy' + + runesDiv.appendChild(runeIcon) + + const runeLevel = document.createElement('span') + runeLevel.className = 'runeTypeElement' + runeLevel.id = `${key}RuneSpiritLevel` + runeLevel.textContent = 'Level 0/30' + + runesDiv.appendChild(runeLevel) + + const runeTNL = document.createElement('span') + runeTNL.className = 'runeTypeElement' + runeTNL.id = `${key}RuneSpiritTNL` + runeTNL.textContent = '0' + runesDiv.appendChild(runeTNL) + + const sacrificeButton = document.createElement('button') + sacrificeButton.className = 'runeTypeElement' + sacrificeButton.id = `${key}RuneSpiritPurchase` + sacrificeButton.setAttribute('i18n', 'general.spiritCapital') + sacrificeButton.textContent = i18next.t('general.spiritCapital') + + runesDiv.appendChild(sacrificeButton) + + spiritRow.appendChild(runesDiv) + } +} diff --git a/src/Runes.ts b/src/Runes.ts index c8e0f0741..97b04db4b 100644 --- a/src/Runes.ts +++ b/src/Runes.ts @@ -1,211 +1,1124 @@ import { - calculateEffectiveIALevel, - calculateMaxRunes, calculateOfferings, - calculateRuneExpGiven, - calculateRuneExpToLevel, - calculateRuneLevels, + calculateSalvageRuneEXPMultiplier, + calculateSigmoidExponential, isIARuneUnlocked } from './Calculate' -import { format, player } from './Synergism' +import { format, formatAsPercentIncrease, player } from './Synergism' import { Globals as G } from './Variables' import Decimal from 'break_infinity.js' -import i18next, { type StringMap } from 'i18next' +import i18next from 'i18next' +import { awardAchievementGroup, getAchievementReward } from './Achievements' +import { getAmbrosiaUpgradeEffects } from './BlueberryUpgrades' import { DOMCacheGetOrSet } from './Cache/DOM' +import { CalcECC } from './Challenges' +import { getLevelMilestone } from './Levels' +import { PCoinUpgradeEffects } from './PseudoCoinUpgrades' +import { firstFiveRuneEffectivenessStats, runeEffectivenessStatsSI } from './Statistics' +import { Tabs } from './Tabs' +import { getRuneBonusFromAllTalismans, getTalismanEffects } from './Talismans' +import { assert } from './Utility' -export const displayRuneInformation = (i: number, updatelevelup = true) => { - const m = G.effectiveLevelMult - const SILevelMult = 1 - + player.researches[84] / 200 - * (1 + 1 * G.effectiveRuneSpiritPower[5] * player.corruptions.used.totalCorruptionDifficultyMultiplier) - const amountPerOffering = calculateRuneExpGiven(i - 1, false, player.runelevels[i - 1]) +export enum resetTiers { + prestige = 1, + transcension = 2, + reincarnation = 3, + ascension = 4, + singularity = 5, + never = 6 +} + +export const indexToRune: Record = { + 1: 'speed', + 2: 'duplication', + 3: 'prism', + 4: 'thrift', + 5: 'superiorIntellect', + 6: 'infiniteAscent', + 7: 'antiquities' +} + +export const runeToIndex = Object.fromEntries( + Object.entries(indexToRune).map(([key, value]) => [value as RuneKeys, key as unknown]) +) as Record + +type RuneTypeMap = { + speed: { + acceleratorPower: number + multiplicativeAccelerators: number + globalSpeed: number + } + duplication: { + multiplierBoosts: number + multiplicativeMultipliers: number + taxReduction: number + } + prism: { + productionLog10: number + costDivisorLog10: number + } + thrift: { + costDelay: number + salvage: number + taxReduction: number + } + superiorIntellect: { + offeringMult: number + obtainiumMult: number + antSpeed: number + } + infiniteAscent: { + quarkMult: number + cubeMult: number + salvage: number + } + antiquities: { + addCodeCooldownReduction: number + offeringLog10: number + obtainiumLog10: number + } + horseShoe: { + ambrosiaLuck: number + redLuck: number + redLuckConversion: number + } + finiteDescent: { + ascensionScore: number + corruptionFreeLevels: number + infiniteAscentFreeLevel: number + } +} + +export type RuneKeys = keyof RuneTypeMap + +interface RuneHTMLStyle { + borderColor: string + nameColor: string +} + +export interface RuneData { + level: number + runeEXP: Decimal + costCoefficient: Decimal + levelsPerOOM: number + ignoreChal9: boolean + levelsPerOOMIncrease: () => number + effectiveLevelMult: () => number + freeLevels: () => number + runeEXPPerOffering: (purchasedLevels: number) => Decimal + isUnlocked: () => boolean + minimalResetTier: keyof typeof resetTiers + effects: (n: number) => RuneTypeMap[K] + effectsDescription: () => string + name: () => string + description: () => string + valueText: () => string + runeHTMLStyle: RuneHTMLStyle +} + +export const firstFiveFreeLevels = () => { + return ( + Math.min(1e3, (player.antUpgrades[8] ?? 0) + G.bonusant9) + + 7 * Math.min(player.constantUpgrades[7], 1000) + ) +} + +export const bonusRuneLevelsSpeed = () => { + return ( + getRuneBonusFromAllTalismans('speed') + + ( + player.upgrades[27] * (Math.min(50, Math.floor(Decimal.log(player.coins.add(1), 1e10))) + + Math.max(0, Math.min(50, Math.floor(Decimal.log(player.coins.add(1), 1e50)) - 10))) + ) + + player.upgrades[29] * Math.floor( + Math.min( + 100, + (player.firstOwnedCoin + player.secondOwnedCoin + player.thirdOwnedCoin + player.fourthOwnedCoin + + player.fifthOwnedCoin) / 400 + ) + ) + ) +} + +export const bonusRuneLevelsDuplication = () => { + return ( + getRuneBonusFromAllTalismans('duplication') + + player.upgrades[28] * Math.min( + 100, + Math.floor( + (player.firstOwnedCoin + player.secondOwnedCoin + player.thirdOwnedCoin + player.fourthOwnedCoin + + player.fifthOwnedCoin) / 400 + ) + ) + + ( + player.upgrades[30] * (Math.min(50, Math.floor(Decimal.log(player.coins.add(1), 1e30))) + + Math.min(50, Math.floor(Decimal.log(player.coins.add(1), 1e300)))) + ) + ) +} + +export const bonusRuneLevelsPrism = () => { + return ( + getRuneBonusFromAllTalismans('prism') + ) +} + +export const bonusRuneLevelsThrift = () => { + return ( + getRuneBonusFromAllTalismans('thrift') + ) +} + +export const bonusRuneLevelsSI = () => { + return ( + getRuneBonusFromAllTalismans('superiorIntellect') + ) +} + +export const bonusRuneLevelsIA = () => { + return ( + (PCoinUpgradeEffects.INSTANT_UNLOCK_2 ? 6 : 0) + + player.cubeUpgrades[73] + + player.campaigns.bonusRune6 + + getRuneBonusFromAllTalismans('infiniteAscent') + + getRuneEffects('finiteDescent').infiniteAscentFreeLevel + ) +} + +export const bonusRuneLevelsAntiquities = () => { + return getRuneBonusFromAllTalismans('antiquities') +} + +export const bonusRuneLevelsHorseShoe = () => { + return getRuneBonusFromAllTalismans('horseShoe') + + (player.shopUpgrades.shopHorseShoe > 0 ? 3 : 0) +} + +export const speedRuneOOMIncrease = () => { + return ( + player.upgrades[66] * 2 + + player.researches[77] + + player.researches[111] + + CalcECC('ascension', player.challengecompletions[11]) + + 1.5 * CalcECC('ascension', player.challengecompletions[14]) + + player.cubeUpgrades[16] + + getTalismanEffects('chronos').speedOOMBonus + + getAmbrosiaUpgradeEffects('ambrosiaRuneOOMBonus').runeOOMBonus + + getLevelMilestone('speedRune') + ) +} + +export const duplicationRuneOOMIncrease = () => { + return ( + 0.75 * CalcECC('transcend', player.challengecompletions[1]) + + player.upgrades[66] * 2 + + player.researches[78] + + player.researches[112] + + CalcECC('ascension', player.challengecompletions[11]) + + 1.5 * CalcECC('ascension', player.challengecompletions[14]) + + getTalismanEffects('exemption').duplicationOOMBonus + + getAmbrosiaUpgradeEffects('ambrosiaRuneOOMBonus').runeOOMBonus + + getLevelMilestone('duplicationRune') + ) +} + +export const prismRuneOOMIncrease = () => { + return ( + player.upgrades[66] * 2 + + player.researches[79] + + player.researches[113] + + CalcECC('ascension', player.challengecompletions[11]) + + 1.5 * CalcECC('ascension', player.challengecompletions[14]) + + player.cubeUpgrades[16] + + getTalismanEffects('mortuus').prismOOMBonus + + getAmbrosiaUpgradeEffects('ambrosiaRuneOOMBonus').runeOOMBonus + + getLevelMilestone('prismRune') + ) +} + +export const thriftRuneOOMIncrease = () => { + return ( + player.upgrades[66] * 2 + + player.researches[80] + + player.researches[114] + + CalcECC('ascension', player.challengecompletions[11]) + + 1.5 * CalcECC('ascension', player.challengecompletions[14]) + + player.cubeUpgrades[37] + + getTalismanEffects('midas').thriftOOMBonus + + getAmbrosiaUpgradeEffects('ambrosiaRuneOOMBonus').runeOOMBonus + + getLevelMilestone('thriftRune') + ) +} - let options: StringMap +export const superiorIntellectOOMIncrease = () => { + return ( + player.upgrades[66] * 2 + + player.researches[115] + + CalcECC('ascension', player.challengecompletions[11]) + + 1.5 * CalcECC('ascension', player.challengecompletions[14]) + + player.cubeUpgrades[37] + + getTalismanEffects('polymath').SIOOMBonus + + getAmbrosiaUpgradeEffects('ambrosiaRuneOOMBonus').runeOOMBonus + + getLevelMilestone('SIRune') + ) +} + +export const infiniteAscentOOMIncrease = () => { + return ( + getAmbrosiaUpgradeEffects('ambrosiaRuneOOMBonus').infiniteAscentOOMBonus + ) +} + +export const antiquitiesOOMIncrease = () => { + return ( + +player.singularityChallenges.taxmanLastStand.rewards.antiquityOOM + ) +} + +export const horseShoeOOMIncrease = () => { + return ( + +player.singularityChallenges.taxmanLastStand.rewards.horseShoeOOM + ) +} + +export const firstFiveEffectiveRuneLevelMult = () => { + return firstFiveRuneEffectivenessStats.reduce((x, y) => x * y.stat(), 1) +} + +export const SIEffectiveRuneLevelMult = () => { + return runeEffectivenessStatsSI.reduce((x, y) => x * y.stat(), 1) +} + +export const universalRuneEXPMult = (purchasedLevels: number): Decimal => { + // recycleMult accounted for all recycle chance, but inversed so it's a multiplier instead + const recycleMultiplier = calculateSalvageRuneEXPMultiplier() - if (i === 1) { - options = { - bonus: format(Math.floor(Math.pow(G.rune1level * m / 4, 1.25))), - percent: format(G.rune1level / 4 * m, 2, true), - boost: format(Math.floor(G.rune1level / 20 * m)) + // Rune multiplier that is summed instead of added + /* TODO: Replace the effects of these upgrades with new ones + const allRuneExpAdditiveMultiplier = sumContents([ + // Challenge 3 completions + (1 / 100) * player.highestchallengecompletions[3], + // Reincarnation 2x1 + 1 * player.upgrades[66] + ]) + }*/ + const allRuneExpAdditiveMultiplier = ( + // Base amount multiplied per offering + 1 + // +1 if C1 completion + + Math.min(1, player.highestchallengecompletions[1]) + // +0.10 per C1 completion + + (0.4 / 10) * player.highestchallengecompletions[1] + // Research 5x2 + + 0.6 * player.researches[22] + // Research 5x3 + + 0.3 * player.researches[23] + // Particle upgrade 3x1 + + (player.upgrades[71] * purchasedLevels) / 25 + ) + + // Rune multiplier that gets applied to all runes + const allRuneExpMultiplier = [ + // Research 4x16 + 1 + player.researches[91] / 20, + // Research 4x17 + 1 + player.researches[92] / 20, + // Ant 8 + calculateSigmoidExponential( + 999, + (1 / 10000) * Math.pow(player.antUpgrades[8 - 1]! + G.bonusant8, 1.1) + ), + // Cube Upgrade Bonus + 1 + (player.ascensionCounter / 1000) * player.cubeUpgrades[32], + // Constant Upgrade Multiplier + 1 + (1 / 10) * player.constantUpgrades[8], + // Challenge 15 reward multiplier + G.challenge15Rewards.runeExp.value + ].reduce((x, y) => x.times(y), new Decimal('1')) + + return allRuneExpMultiplier.times(allRuneExpAdditiveMultiplier).times(recycleMultiplier) +} + +export const runes: { [K in RuneKeys]: RuneData } = { + speed: { + level: 0, + runeEXP: new Decimal('0'), + costCoefficient: new Decimal(50), + levelsPerOOM: 150, + ignoreChal9: false, + levelsPerOOMIncrease: () => speedRuneOOMIncrease(), + effects: (n) => { + const acceleratorPower = 0.0002 * n + const multiplicativeAccelerators = 1 + n / 400 + const globalSpeed = 2 - Math.exp(-Math.cbrt(n) / 100) + return { + acceleratorPower: acceleratorPower, + multiplicativeAccelerators: multiplicativeAccelerators, + globalSpeed: globalSpeed + } + }, + effectsDescription: () => { + const effects = getRuneEffects('speed') + const acceleratorPowerText = i18next.t('runes.speed.acceleratorPower', { + val: format(100 * effects.acceleratorPower, 2) + }) + const multiplicativeAcceleratorsText = i18next.t('runes.speed.freeAccelerators', { + val: formatAsPercentIncrease(effects.multiplicativeAccelerators, 2) + }) + const globalSpeedText = i18next.t('runes.speed.globalSpeed', { + val: formatAsPercentIncrease(effects.globalSpeed, 3) + }) + return `${acceleratorPowerText}
${multiplicativeAcceleratorsText}
${globalSpeedText}` + }, + effectiveLevelMult: () => firstFiveEffectiveRuneLevelMult(), + freeLevels: () => firstFiveFreeLevels() + bonusRuneLevelsSpeed(), + runeEXPPerOffering: (purchasedLevels) => universalRuneEXPMult(purchasedLevels), + isUnlocked: () => true, + minimalResetTier: 'ascension', + name: () => i18next.t('runes.speed.name'), + description: () => i18next.t('runes.speed.description'), + valueText: () => i18next.t('runes.speed.values'), + runeHTMLStyle: { + borderColor: 'maroon', + nameColor: 'tomato' + } + }, + duplication: { + level: 0, + runeEXP: new Decimal('0'), + costCoefficient: new Decimal(20000), + levelsPerOOM: 120, + ignoreChal9: false, + levelsPerOOMIncrease: () => duplicationRuneOOMIncrease(), + effects: (n) => { + const multiplierBoosts = n / 5 + const multiplicativeMultipliers = 1 + n / 400 + const taxReduction = 0.001 + .999 * Math.exp(-Math.cbrt(n) / 10) + return { + multiplierBoosts: multiplierBoosts, + multiplicativeMultipliers: multiplicativeMultipliers, + taxReduction: taxReduction + } + }, + effectsDescription: () => { + const effect = getRuneEffects('duplication') + const multiplierPowerBoostsText = i18next.t('runes.duplication.multiplierPower', { + val: format(effect.multiplierBoosts, 2, true) + }) + const multiplicativeMultipliersText = i18next.t('runes.duplication.freeMultipliers', { + val: formatAsPercentIncrease(effect.multiplicativeMultipliers, 2) + }) + const taxReductionText = i18next.t('runes.duplication.taxReduction', { + val: format(100 * (1 - effect.taxReduction), 3, true) + }) + return `${multiplierPowerBoostsText}
${multiplicativeMultipliersText}
${taxReductionText}` + }, + effectiveLevelMult: () => firstFiveEffectiveRuneLevelMult(), + freeLevels: () => firstFiveFreeLevels() + bonusRuneLevelsDuplication(), + runeEXPPerOffering: (purchasedLevels) => universalRuneEXPMult(purchasedLevels), + isUnlocked: () => Boolean(getAchievementReward('duplicationRuneUnlock')), + minimalResetTier: 'ascension', + name: () => i18next.t('runes.duplication.name'), + description: () => i18next.t('runes.duplication.description'), + valueText: () => i18next.t('runes.duplication.values'), + runeHTMLStyle: { + borderColor: 'purple', + nameColor: 'orchid' + } + }, + prism: { + level: 0, + runeEXP: new Decimal('0'), + costCoefficient: new Decimal(5e5), + levelsPerOOM: 90, + ignoreChal9: false, + levelsPerOOMIncrease: () => prismRuneOOMIncrease(), + effects: (level) => { + const productionLog10 = Math.max(0, 2 * Math.log10(1 + level / 2) + (level / 2) * Math.log10(2) - Math.log10(256)) + const costDivisorLog10 = Math.floor(level / 10) + return { + productionLog10: productionLog10, + costDivisorLog10: costDivisorLog10 + } + }, + effectsDescription: () => { + const effect = getRuneEffects('prism') + const productionText = i18next.t('runes.prism.crystalProduction', { + val: format(Decimal.pow(10, effect.productionLog10), 2, true) + }) + const costReductionText = i18next.t('runes.prism.costDivisor', { + val: format(Decimal.pow(10, effect.costDivisorLog10), 0, true) + }) + return `${productionText}
${costReductionText}` + }, + effectiveLevelMult: () => firstFiveEffectiveRuneLevelMult(), + freeLevels: () => firstFiveFreeLevels() + bonusRuneLevelsPrism(), + runeEXPPerOffering: (purchasedLevels) => universalRuneEXPMult(purchasedLevels), + isUnlocked: () => Boolean(getAchievementReward('prismRuneUnlock')), + minimalResetTier: 'ascension', + name: () => i18next.t('runes.prism.name'), + description: () => i18next.t('runes.prism.description'), + valueText: () => i18next.t('runes.prism.values'), + runeHTMLStyle: { + borderColor: 'lightblue', + nameColor: 'cyan' + } + }, + thrift: { + level: 0, + runeEXP: new Decimal('0'), + costCoefficient: new Decimal(2.5e7), + levelsPerOOM: 60, + ignoreChal9: false, + levelsPerOOMIncrease: () => thriftRuneOOMIncrease(), + effects: (level) => { + const costDelay = Math.min(1e15, level / 125) + const salvage = 2.5 * Math.log(1 + level / 10) + const taxReduction = 0.01 + 0.99 * Math.exp(-Math.cbrt(level) / 20) + return { + costDelay: costDelay, + salvage: salvage, + taxReduction: taxReduction + } + }, + effectsDescription: () => { + const effect = getRuneEffects('thrift') + const costDelayText = i18next.t('runes.thrift.costDelay', { val: format(effect.costDelay, 0, true) }) + const salvageText = i18next.t('runes.thrift.salvage', { val: format(effect.salvage, 2, true) }) + const taxReductionText = i18next.t('runes.thrift.taxReduction', { + val: format(100 * (1 - effect.taxReduction), 3, true) + }) + return `${costDelayText}
${salvageText}
${taxReductionText}` + }, + effectiveLevelMult: () => firstFiveEffectiveRuneLevelMult(), + freeLevels: () => firstFiveFreeLevels() + bonusRuneLevelsThrift(), + runeEXPPerOffering: (purchasedLevels) => universalRuneEXPMult(purchasedLevels), + isUnlocked: () => Boolean(getAchievementReward('thriftRuneUnlock')), + minimalResetTier: 'ascension', + name: () => i18next.t('runes.thrift.name'), + description: () => i18next.t('runes.thrift.description'), + valueText: () => i18next.t('runes.thrift.values'), + runeHTMLStyle: { + borderColor: 'darkgreen', + nameColor: 'lime' } - } else if (i === 2) { - options = { - mult1: format(Math.floor(G.rune2level * m / 10) * Math.floor(1 + G.rune2level * m / 10) / 2), - mult2: format(m * G.rune2level / 4, 1, true), - tax: (99.9 * (1 - Math.pow(6, -(G.rune2level * m) / 1000))).toPrecision(4) + }, + superiorIntellect: { + level: 0, + runeEXP: new Decimal('0'), + costCoefficient: new Decimal(1e12), + levelsPerOOM: 30, + ignoreChal9: false, + levelsPerOOMIncrease: () => superiorIntellectOOMIncrease(), + effects: (level) => { + const offeringMult = 1 + level / 2000 + const obtainiumMult = 1 + level / 200 + const antSpeed = 1 + Math.pow(level, 2) / 2500 + return { + offeringMult: offeringMult, + obtainiumMult: obtainiumMult, + antSpeed: antSpeed + } + }, + effectsDescription: () => { + const effect = getRuneEffects('superiorIntellect') + const offeringMultText = i18next.t('runes.superiorIntellect.offeringMult', { + val: formatAsPercentIncrease(effect.offeringMult, 2) + }) + const obtainiumMultText = i18next.t('runes.superiorIntellect.obtainiumMult', { + val: formatAsPercentIncrease(effect.obtainiumMult, 2) + }) + const antSpeedText = i18next.t('runes.superiorIntellect.antSpeed', { val: format(effect.antSpeed, 2) }) + return `${offeringMultText}
${obtainiumMultText}
${antSpeedText}` + }, + effectiveLevelMult: () => firstFiveEffectiveRuneLevelMult() * SIEffectiveRuneLevelMult(), + freeLevels: () => firstFiveFreeLevels() + bonusRuneLevelsSI(), + runeEXPPerOffering: (purchasedLevels) => universalRuneEXPMult(purchasedLevels), + isUnlocked: () => player.researches[82] > 0, + minimalResetTier: 'ascension', + name: () => i18next.t('runes.superiorIntellect.name'), + description: () => i18next.t('runes.superiorIntellect.description'), + valueText: () => i18next.t('runes.superiorIntellect.values'), + runeHTMLStyle: { + borderColor: 'blue', + nameColor: 'turquoise' } - } else if (i === 3) { - options = { - mult: format(Decimal.pow(G.rune3level * m / 2, 2).times(Decimal.pow(2, G.rune3level * m / 2 - 8)).add(1), 3), - gain: format(Math.floor(G.rune3level / 16 * m)) + }, + infiniteAscent: { + level: 0, + runeEXP: new Decimal('0'), + costCoefficient: new Decimal(1e75), + levelsPerOOM: 1 / 2, + ignoreChal9: true, + levelsPerOOMIncrease: () => infiniteAscentOOMIncrease(), + effects: (level) => { + const quarkMult = 1 + level / 500 + (level > 0 ? 0.1 : 0) + const cubeMult = 1 + level / 100 + + const salvagePerkLevels = [30, 40, 61, 81, 111, 131, 161, 191, 236, 260] + const salvageCoefficient = 0.025 * salvagePerkLevels.filter((x) => x <= player.highestSingularityCount).length + const salvage = salvageCoefficient * level + + return { + quarkMult: quarkMult, + cubeMult: cubeMult, + salvage: salvage + } + }, + effectsDescription: () => { + const effectValues = getRuneEffects('infiniteAscent') + const quarkText = i18next.t('runes.infiniteAscent.quarkBonus', { + val: formatAsPercentIncrease(effectValues.quarkMult, 2) + }) + const cubeText = i18next.t('runes.infiniteAscent.cubeBonus', { + val: formatAsPercentIncrease(effectValues.cubeMult, 2) + }) + if (player.highestSingularityCount >= 30) { + const salvagePerkLevels = [30, 40, 61, 81, 111, 131, 161, 191, 236, 260] + const salvageCoefficient = 0.025 * salvagePerkLevels.filter((x) => x <= player.highestSingularityCount).length + const salvageText = `${ + i18next.t('runes.infiniteAscent.salvage', { + val: format(effectValues.salvage, 2, true), + val2: format(salvageCoefficient, 3, true) + }) + }` + return `${quarkText}
${cubeText}
${salvageText}` + } else { + return `${quarkText}
${cubeText}` + } + }, + effectiveLevelMult: () => 1, + freeLevels: () => bonusRuneLevelsIA(), + runeEXPPerOffering: (purchasedLevels) => universalRuneEXPMult(purchasedLevels), + isUnlocked: () => isIARuneUnlocked(), + minimalResetTier: 'singularity', + name: () => i18next.t('runes.infiniteAscent.name'), + description: () => i18next.t('runes.infiniteAscent.description'), + valueText: () => i18next.t('runes.infiniteAscent.values'), + runeHTMLStyle: { + borderColor: 'gold', + nameColor: 'lightgoldenrodyellow' } - } else if (i === 4) { - options = { - delay: (G.rune4level / 8 * m).toPrecision(3), - chance: Math.min(25, G.rune4level / 16), - tax: (99 * (1 - Math.pow(4, Math.min(0, (400 - G.rune4level) / 1100)))).toPrecision(4) + }, + antiquities: { + level: 0, + runeEXP: new Decimal('0'), + costCoefficient: new Decimal(1e206), + levelsPerOOM: 1 / 50, + ignoreChal9: true, + levelsPerOOMIncrease: () => antiquitiesOOMIncrease(), + effects: (level) => { + const addCodeCooldownReduction = level > 0 ? 0.8 - 0.3 * (level - 1) / (level + 10) : 1 + const offeringLog10 = level + const obtainiumLog10 = level + return { + addCodeCooldownReduction: addCodeCooldownReduction, + offeringLog10: offeringLog10, + obtainiumLog10: obtainiumLog10 + } + }, + effectsDescription: () => { + const effect = getRuneEffects('antiquities') + const singularText = i18next.t('runes.antiquities.singularityUnlock', { + symbol: runes.antiquities.level >= 1 ? '' : '' + }) + const offeringText = i18next.t('runes.antiquities.offeringBonus', { + val: format(Decimal.pow(10, effect.offeringLog10), 2, true) + }) + const obtainiumText = i18next.t('runes.antiquities.obtainiumBonus', { + val: format(Decimal.pow(10, effect.obtainiumLog10), 2, true) + }) + const addCodeCooldownReductionText = i18next.t('runes.antiquities.addCode', { + val: formatAsPercentIncrease(effect.addCodeCooldownReduction, 2) + }) + return `${singularText}
${offeringText}
${obtainiumText}
${addCodeCooldownReductionText}` + }, + effectiveLevelMult: () => 1, + freeLevels: () => bonusRuneLevelsAntiquities(), + runeEXPPerOffering: (purchasedLevels) => universalRuneEXPMult(purchasedLevels), + isUnlocked: () => player.platonicUpgrades[20] > 0, + minimalResetTier: 'singularity', + name: () => i18next.t('runes.antiquities.name'), + description: () => i18next.t('runes.antiquities.description'), + valueText: () => i18next.t('runes.antiquities.values'), + runeHTMLStyle: { + borderColor: 'white', + nameColor: 'gainsboro' } - } else if (i === 5) { - options = { - gain: format(1 + G.rune5level / 200 * m * SILevelMult, 2, true), - speed: format(1 + Math.pow(G.rune5level * m * SILevelMult, 2) / 2500), - offerings: format(G.rune5level * m * 0.005, 3, true) + }, + horseShoe: { + level: 0, + runeEXP: new Decimal('0'), + costCoefficient: new Decimal('1e500'), + levelsPerOOM: 1 / 20, + ignoreChal9: true, + levelsPerOOMIncrease: () => horseShoeOOMIncrease(), + effects: (level) => { + const ambrosiaLuck = level + const redLuck = level / 5 + const redLuckConversion = -0.5 * level / (level + 50) + return { + ambrosiaLuck: ambrosiaLuck, + redLuck: redLuck, + redLuckConversion: redLuckConversion + } + }, + effectsDescription: () => { + const effect = getRuneEffects('horseShoe') + const ambrosiaLuckText = i18next.t('runes.horseShoe.ambrosiaLuck', { val: format(effect.ambrosiaLuck, 2, true) }) + const redLuckText = i18next.t('runes.horseShoe.redLuck', { val: format(effect.redLuck, 2, true) }) + const redLuckConversionText = i18next.t('runes.horseShoe.luckConversion', { + val: format(effect.redLuckConversion, 3, true) + }) + return `${ambrosiaLuckText}
${redLuckText}
${redLuckConversionText}` + }, + effectiveLevelMult: () => 1, + freeLevels: () => bonusRuneLevelsHorseShoe(), + runeEXPPerOffering: (purchasedLevels) => universalRuneEXPMult(purchasedLevels), + isUnlocked: () => { + const condition = Boolean(player.singularityChallenges.taxmanLastStand.rewards.horseShoeUnlock) + return condition + }, + minimalResetTier: 'never', + name: () => i18next.t('runes.horseShoe.name'), + description: () => i18next.t('runes.horseShoe.description'), + valueText: () => i18next.t('runes.horseShoe.values'), + runeHTMLStyle: { + borderColor: 'saddlebrown', + nameColor: 'peru' } - } else if (i === 6) { - options = { - percent1: format(10 + 15 / 75 * calculateEffectiveIALevel(), 1, true), - percent2: format(1 * calculateEffectiveIALevel(), 0, true) + }, + finiteDescent: { + level: 0, + runeEXP: new Decimal('0'), + costCoefficient: new Decimal('1e-40'), + levelsPerOOM: 0.1, + ignoreChal9: true, + levelsPerOOMIncrease: () => 0, + effects: (level) => { + const ascensionScore = level >= 1 ? 1.04 + 0.96 * (level - 1) / (level + 25) : 1 + const corruptionFreeLevels = level >= 1 ? 0.01 + 0.14 * (level - 1) / (level + 16) : 0 + const infiniteAscentFreeLevel = Math.floor(level / 2) + return { + ascensionScore: ascensionScore, + corruptionFreeLevels: corruptionFreeLevels, + infiniteAscentFreeLevel: infiniteAscentFreeLevel + } + }, + effectsDescription: () => { + const effect = getRuneEffects('finiteDescent') + const corruptionFreeLevelsText = i18next.t('runes.finiteDescent.corruptionLevels', { + val: format(effect.corruptionFreeLevels, 3, true) + }) + const ascensionScoreText = i18next.t('runes.finiteDescent.ascensionScore', { + val: formatAsPercentIncrease(effect.ascensionScore, 3) + }) + const infiniteAscentFreeLevelText = i18next.t('runes.finiteDescent.infiniteAscentLevels', { + val: format(effect.infiniteAscentFreeLevel, 0, true) + }) + return `${corruptionFreeLevelsText}
${ascensionScoreText}
${infiniteAscentFreeLevelText}` + }, + effectiveLevelMult: () => 1, + freeLevels: () => 0, + runeEXPPerOffering: (purchasedLevels) => universalRuneEXPMult(purchasedLevels), + isUnlocked: () => player.shopUpgrades.shopSadisticRune > 0, + minimalResetTier: 'ascension', + name: () => i18next.t('runes.finiteDescent.name'), + description: () => i18next.t('runes.finiteDescent.description'), + valueText: () => i18next.t('runes.finiteDescent.values'), + runeHTMLStyle: { + borderColor: 'black', + nameColor: 'dimgray' } - } else if (i === 7 && updatelevelup) { - options = { exp: format(1e256 * (1 + player.singularityCount)) } } +} - if (updatelevelup) { - DOMCacheGetOrSet('runeshowlevelup').textContent = i18next.t(`runes.levelup.${i}`, options!) +export const getRuneEffectiveLevel = (rune: RuneKeys): number => { + if (!runes[rune].isUnlocked()) { + return 0 } + if (player.currentChallenge.reincarnation === 9 && !runes[rune].ignoreChal9) { + return 1 + } + const effectiveMult = runes[rune].effectiveLevelMult() + return (runes[rune].level + runes[rune].freeLevels()) * effectiveMult +} - DOMCacheGetOrSet(`runeshowpower${i}`).textContent = i18next.t(`runes.power.${i}`, options!) +export const getRuneEffects = (rune: T): RuneTypeMap[T] => { + return runes[rune].effects(getRuneEffectiveLevel(rune)) +} - if (updatelevelup) { - const arr = calculateOfferingsToLevelXTimes(i - 1, player.runelevels[i - 1], player.offeringbuyamount) - let offerings = 0 - let j = 0 - while (j < arr.length && (offerings + arr[j] <= player.runeshards || j === 0)) { - offerings += arr[j] - j++ - } - const s = j === 1 ? 'once' : `${j} times` +export const getLevelsPerOOM = (rune: RuneKeys): number => { + return runes[rune].levelsPerOOM + runes[rune].levelsPerOOMIncrease() +} + +export const getRuneEXPPerOffering = (rune: RuneKeys): Decimal => { + return runes[rune].runeEXPPerOffering(runes[rune].level) +} + +export const computeEXPToLevel = (rune: RuneKeys, level: number) => { + const levelPerOOM = getLevelsPerOOM(rune) + return runes[rune].costCoefficient.times(Decimal.pow(10, level / levelPerOOM).minus(1)) +} + +export const computeEXPLeftToLevel = (rune: RuneKeys, level: number) => { + return Decimal.max(0, computeEXPToLevel(rune, level).minus(runes[rune].runeEXP)) +} + +export const computeOfferingsToLevel = (rune: RuneKeys, level: number) => { + return Decimal.max(1, computeEXPLeftToLevel(rune, level).div(getRuneEXPPerOffering(rune)).ceil()) +} + +export const getRuneTNL = (rune: RuneKeys) => { + return computeEXPLeftToLevel(rune, runes[rune].level + 1) +} + +// Gives levels to buy, total EXP to that level, and offerings required to reach that level +export const maxRuneLevelPurchaseInformation = (rune: RuneKeys, budget: Decimal) => { + if (!runes[rune].isUnlocked() || budget.lt(0)) { + return { levels: 0, expRequired: new Decimal(0), offerings: new Decimal(0) } + } + + const runeEXPPerOffering = getRuneEXPPerOffering(rune) + const totalEXPAvailable = budget.times(runeEXPPerOffering).add(runes[rune].runeEXP) + const levelsPerOOM = getLevelsPerOOM(rune) + const costCoeff = runes[rune].costCoefficient + + // Calculate max level we can reach with available EXP + // EXP formula: costCoeff * (10^(level/levelsPerOOM) - 1) + // Solving for level: level = levelsPerOOM * log10((EXP/costCoeff) + 1) + const maxLevel = Math.floor(levelsPerOOM * Decimal.log10(totalEXPAvailable.div(costCoeff).plus(1))) + const levelsGained = Math.max(0, maxLevel - runes[rune].level) + + if (levelsGained === 0) { + // Can't afford any levels, return next level stuff + const nextLevelEXP = computeEXPToLevel(rune, runes[rune].level + 1) + const offeringsRequired = Decimal.max(1, nextLevelEXP.minus(runes[rune].runeEXP).div(runeEXPPerOffering).ceil()) + return { levels: 1, expRequired: nextLevelEXP, offerings: offeringsRequired } + } + + // Return the levels we can gain and the EXP required for that many levels + const expRequired = computeEXPToLevel(rune, runes[rune].level + levelsGained) + // Need to be recomputed since offerings required is not necessarily equal to budget. + const offeringsRequired = Decimal.max(1, expRequired.minus(runes[rune].runeEXP).div(runeEXPPerOffering).ceil()) + return { levels: levelsGained, expRequired: expRequired, offerings: offeringsRequired } +} + +export const levelRune = (rune: RuneKeys, timesLeveled: number, budget: Decimal) => { + let budgetUsed: Decimal + + const expRequired = computeEXPLeftToLevel(rune, runes[rune].level + timesLeveled) + const runeEXPPerOffering = getRuneEXPPerOffering(rune) + const offeringsRequired = Decimal.max(1, expRequired.div(runeEXPPerOffering).ceil()) + + if (offeringsRequired.gt(budget)) { + runes[rune].runeEXP = runes[rune].runeEXP.add(budget.times(runeEXPPerOffering)) + budgetUsed = budget + } else { + runes[rune].runeEXP = computeEXPToLevel(rune, runes[rune].level + timesLeveled) + budgetUsed = offeringsRequired + } + + player.offerings = player.offerings.sub(budgetUsed) + + // this.updatePlayerEXP() + // this.updateRuneEffectHTML() +} + +export const setRuneLevel = (rune: RuneKeys, level: number) => { + const exp = computeEXPToLevel(rune, level) + runes[rune].level = level + runes[rune].runeEXP = exp +} + +export const updateLevelsFromEXP = (rune: RuneKeys) => { + const levelsPerOOM = getLevelsPerOOM(rune) + const levels = Math.floor(levelsPerOOM * Decimal.log10(runes[rune].runeEXP.div(runes[rune].costCoefficient).plus(1))) + // Floating point imprecision fix + if (computeEXPLeftToLevel(rune, levels + 1).eq(0)) { + runes[rune].level = levels + 1 + } else { + runes[rune].level = levels + } +} + +export const updateAllRuneLevelsFromEXP = () => { + for (const rune of Object.keys(runes) as RuneKeys[]) { + updateLevelsFromEXP(rune) + } + awardAchievementGroup('runeLevel') +} + +export const updateRuneHTML = (rune: RuneKeys) => { + assert(G.currentTab === Tabs.Runes, 'current tab is not Runes') + + DOMCacheGetOrSet(`${rune}RuneLevel`).textContent = i18next.t('runes.level', { x: format(runes[rune].level, 0, true) }) + DOMCacheGetOrSet(`${rune}RuneFreeLevel`).innerHTML = i18next.t('runes.freeLevels', { + x: format(runes[rune].freeLevels(), 0, true) + }) - DOMCacheGetOrSet('runeDisplayInfo').textContent = i18next.t('runes.perOfferingText', { - exp: format(amountPerOffering), - x: format(offerings), - y: s + if (player.offeringbuyamount === 100000) { + const { levels, offerings } = maxRuneLevelPurchaseInformation(rune, player.offerings) + DOMCacheGetOrSet(`${rune}RuneTNL`).innerHTML = i18next.t('runes.TNL', { + levelAmount: format(levels, 0, true), + offerings: format(offerings, 2, false) }) + } else { + DOMCacheGetOrSet(`${rune}RuneTNL`).innerHTML = i18next.t('runes.TNL', { + levelAmount: format(player.offeringbuyamount, 0, true), + offerings: format(computeOfferingsToLevel(rune, runes[rune].level + player.offeringbuyamount), 2, false) + }) + } +} + +export const focusedRuneHTML = (rune: RuneKeys) => { + const name = i18next.t('runes.runeName', { + color: runes[rune].runeHTMLStyle.nameColor, + name: runes[rune].name() + }) + + const nameHTML = `${name}` + + const description = runes[rune].description() + const descriptionHTML = `${description}` + const effectiveLevelHTML = i18next.t('runes.runeEffectiveLevel', { + level: format(getRuneEffectiveLevel(rune), 2, false) + }) + const effectiveLevelCalcHTML = i18next.t('runes.runeEffectiveLevelCalc', { + purchased: format(runes[rune].level, 0, true), + free: format(runes[rune].freeLevels(), 2, true), + mult: format(runes[rune].effectiveLevelMult(), 2, true) + }) + + const effectText = runes[rune].effectsDescription() + const coefficientText = i18next.t('runes.runeCoefficientText', { + base: format(runes[rune].levelsPerOOM, 2, true), + bonus: format(runes[rune].levelsPerOOMIncrease(), 2, true), + total: format(getLevelsPerOOM(rune), 2, true) + }) + const levelCostIncreaseText = i18next.t('runes.perLevelIncrease', { + x: format(Math.pow(10, 1 / getLevelsPerOOM(rune)), 4, false) + }) + + const experienceInfo = i18next.t('runes.runeEXP', { + exp: format(runes[rune].runeEXP, 2, true), + perEXP: format(getRuneEXPPerOffering(rune), 2, true) + }) + + let levelInfo = '' + // For some dumb reason that old Platonic might be able to explain, 'MAX' was defined to + // be 100,000 levels. This retroactively corrects the display logic. + if (player.offeringbuyamount === 100000) { + const { levels, expRequired, offerings } = maxRuneLevelPurchaseInformation(rune, player.offerings) + levelInfo = i18next.t('runes.EXPNeeded', { + level: format(levels, 0, true), + exp: format(expRequired, 2, true), + offerings: format(offerings, 0, true) + }) + } else { + levelInfo = i18next.t('runes.EXPNeeded', { + level: format(player.offeringbuyamount, 0, true), + exp: format(computeEXPToLevel(rune, runes[rune].level + player.offeringbuyamount), 2, true), + offerings: format(computeOfferingsToLevel(rune, runes[rune].level + player.offeringbuyamount), 2, true) + }) + } + return `${nameHTML}
${descriptionHTML} +

${effectiveLevelHTML}
${effectiveLevelCalcHTML}
${effectText} +

${coefficientText}
${levelCostIncreaseText} +

${experienceInfo}
${levelInfo}` +} + +export const focusedRuneLockedHTML = (rune: RuneKeys) => { + const name = i18next.t('runes.lockedRuneModal') + const nameHTML = `${name}` + + const lockedDescription = i18next.t(`runes.${rune}.lockedDescription`) + const lockedDescriptionHTML = `${lockedDescription}` + + return `${nameHTML}
${lockedDescriptionHTML}` +} + +export const focusedRuneHTMLMobile = (rune: RuneKeys) => { + assert(G.currentTab === Tabs.Runes, 'current tab is not Runes') + + DOMCacheGetOrSet('focusedRuneName').textContent = runes[rune].name() + DOMCacheGetOrSet('focusedRuneDescription').innerHTML = runes[rune].description() + DOMCacheGetOrSet('focusedRuneValues').innerHTML = runes[rune].valueText() + DOMCacheGetOrSet('focusedRuneCoefficient').textContent = i18next.t('runes.runeCoefficientText', { + x: format(runes[rune].levelsPerOOM, 2, true), + y: format(runes[rune].levelsPerOOMIncrease(), 2, true), + z: format(getLevelsPerOOM(rune), 2, true) + }) + DOMCacheGetOrSet('focusedRuneLevelInfo').textContent = i18next.t('runes.offeringText', { + exp: format(getRuneEXPPerOffering(rune), 2, true), + offeringReq: format(computeOfferingsToLevel(rune, runes[rune].level + player.offeringbuyamount), 0, true), + levels: format(player.offeringbuyamount, 0, true) + }) +} + +/*export const updateRuneEffectHTML = (rune: RuneKeys) => { + if (G.currentTab === Tabs.Runes) { + DOMCacheGetOrSet(`${rune}RunePower`).innerHTML = runes[rune].effectsDescription() } +}*/ + +export const sumOfRuneLevels = () => { + return Object.values(runes).reduce((sum, rune) => sum + rune.level, 0) } -export const resetofferings = () => { - player.runeshards = Math.min(1e300, player.runeshards + calculateOfferings()) +export const sumOfFreeRuneLevels = () => { + return Object.values(runes).reduce((sum, rune) => sum + rune.freeLevels(), 0) } -export const unlockedRune = (runeIndexPlusOne: number) => { - // Whether or not a rune is unlocked array - const unlockedRune = [ - false, - true, - player.achievements[38] > 0.5, - player.achievements[44] > 0.5, - player.achievements[102] > 0.5, - player.researches[82] > 0.5, - isIARuneUnlocked(), - player.platonicUpgrades[20] > 0 - ] - return unlockedRune[runeIndexPlusOne] +export const getNumberUnlockedRunes = () => { + return Object.values(runes).filter((rune) => rune.isUnlocked()).length } -/** - * checkMaxRunes returns how many unique runes are at the maximum level. - * Does not take in params, returns a number equal to number of maxed runes. - */ -export const checkMaxRunes = (runeIndex: number) => { - let maxed = 0 - for (let i = 0; i < runeIndex; i++) { - if (!unlockedRune(i + 1) || player.runelevels[i] >= calculateMaxRunes(i + 1)) { - maxed++ +export function resetRunes (tier: keyof typeof resetTiers) { + if (runes === null) { + throw new Error('Runes not initialized. Call initRunes first.') + } + for (const rune of Object.keys(runes) as RuneKeys[]) { + if (resetTiers[tier] >= resetTiers[runes[rune].minimalResetTier]) { + runes[rune].level = 0 + runes[rune].runeEXP = new Decimal(0) + player.runes[rune] = new Decimal(0) + } + + if (resetTiers[tier] === resetTiers[runes[rune].minimalResetTier] && tier === 'ascension') { + setRuneLevel(rune, 3 * player.cubeUpgrades[26]) } } - return maxed } -export const redeemShards = (runeIndexPlusOne: number, auto = false, cubeUpgraded = 0) => { +export const resetOfferings = () => { + player.offerings = player.offerings.add(calculateOfferings()) +} + +export const sacrificeOfferings = (rune: RuneKeys, budget: Decimal, auto = false) => { // if automated && 2x10 cube upgrade bought, this will be >0. - // runeIndex, the rune being added to - const runeIndex = runeIndexPlusOne - 1 - let levelsToAdd = player.offeringbuyamount + if (!runes[rune].isUnlocked()) { + return + } + + if (auto && rune === 'infiniteAscent' && player.highestSingularityCount < 30) { + return + } + + if (auto && rune === 'antiquities' && player.highestSingularityCount < 50) { + return + } + + let levelsToAdd = player.offeringbuyamount as number if (auto) { - levelsToAdd = Math.pow(2, player.shopUpgrades.offeringAuto) - } - if (auto && cubeUpgraded > 0) { - levelsToAdd = Math.min(1e4, calculateMaxRunes(runeIndex + 1)) // limit to max 10k levels per call so the execution doesn't take too long if things get stuck - } - let levelsAdded = 0 - if ( - player.runeshards > 0 && player.runelevels[runeIndex] < calculateMaxRunes(runeIndex + 1) - && unlockedRune(runeIndex + 1) - ) { - let all = 0 - const maxLevel = calculateMaxRunes(runeIndex + 1) - const amountArr = calculateOfferingsToLevelXTimes(runeIndex, player.runelevels[runeIndex], levelsToAdd) - let toSpendTotal = Math.min(player.runeshards, amountArr.reduce((x, y) => x + y, 0)) - if (cubeUpgraded > 0) { - toSpendTotal = Math.min(player.runeshards, cubeUpgraded) - } - const fact = calculateRuneExpGiven(runeIndex, false, player.runelevels[runeIndex], true) - const a = player.upgrades[71] / 25 - const add = fact[0] - a * player.runelevels[runeIndex] - const mult = fact.slice(1, fact.length).reduce((x, y) => x * y, 1) - while (toSpendTotal > 0 && levelsAdded < levelsToAdd && player.runelevels[runeIndex] < maxLevel) { - const exp = calculateRuneExpToLevel(runeIndex, player.runelevels[runeIndex]) - player.runeexp[runeIndex] - const expPerOff = Math.min(1e300, (add + a * player.runelevels[runeIndex]) * mult) - let toSpend = Math.min(toSpendTotal, Math.ceil(exp / expPerOff)) - if (isNaN(toSpend)) { - toSpend = toSpendTotal - } - toSpendTotal -= toSpend - player.runeshards -= toSpend - player.runeexp[runeIndex] += toSpend * expPerOff - all += toSpend - while ( - player.runeexp[runeIndex] >= calculateRuneExpToLevel(runeIndex) && player.runelevels[runeIndex] < maxLevel - ) { - player.runelevels[runeIndex] += 1 - levelsAdded++ - } - } - for (let runeToUpdate = 0; runeToUpdate < 5; ++runeToUpdate) { - if (unlockedRune(runeToUpdate + 1)) { - if (runeToUpdate !== runeIndex) { - player.runeexp[runeToUpdate] += all * calculateRuneExpGiven(runeToUpdate, true) - } - while ( - player.runeexp[runeToUpdate] >= calculateRuneExpToLevel(runeToUpdate) - && player.runelevels[runeToUpdate] < calculateMaxRunes(runeToUpdate + 1) - ) { - player.runelevels[runeToUpdate] += 1 - } - } - } - if (!auto) { - displayRuneInformation(runeIndexPlusOne) - } + levelsToAdd = 20 * player.shopUpgrades.offeringAuto + } + if (auto && player.cubeUpgrades[20] > 0) { + levelsToAdd *= 20 + } + + if (player.offeringbuyamount !== 100000 || auto) { + levelRune(rune, levelsToAdd, budget) + } // If we have offeringbuyamount === 100000, try to buy max! Fuck you, old Platonic. + else { + levelsToAdd = maxRuneLevelPurchaseInformation(rune, budget).levels + levelRune(rune, levelsToAdd, budget) + } + + updateLevelsFromEXP(rune) + player.offerings = Decimal.max(0, player.offerings) +} + +export const generateRunesHTML = () => { + const runeContainer = DOMCacheGetOrSet('runeDetails') + + for (const key of Object.keys(runes) as RuneKeys[]) { + // Create unlocked rune container + const runesDiv = document.createElement('div') + runesDiv.className = 'runeType' + runesDiv.id = `${key}RuneContainer` + runesDiv.style.border = `2px solid ${runes[key].runeHTMLStyle.borderColor}` + runesDiv.style.borderRadius = '8px' + runesDiv.style.margin = '2px' + // Add custom property for glow color + runesDiv.style.setProperty( + '--glow-color', + `color-mix(in srgb, ${runes[key].runeHTMLStyle.borderColor} 50%, orange 50%)` + ) + + const runeName = document.createElement('p') + runeName.className = 'runeTypeElement' + runeName.setAttribute('i18n', `runes.${key}.name`) + runeName.textContent = i18next.t(`runes.${key}.name`) + + runesDiv.appendChild(runeName) + + const runeIcon = document.createElement('img') + runeIcon.className = 'runeImage' + runeIcon.id = `${key}Rune` + runeIcon.alt = `${key} Rune` + runeIcon.src = `Pictures/Runes/${key.charAt(0).toUpperCase() + key.slice(1)}.png` + runeIcon.loading = 'lazy' + + runesDiv.appendChild(runeIcon) + + const runeLevel = document.createElement('span') + runeLevel.className = 'runeTypeElement' + runeLevel.id = `${key}RuneLevel` + runeLevel.textContent = 'Level 0/30' + + runesDiv.appendChild(runeLevel) + + const runeFreeLevel = document.createElement('span') + runeFreeLevel.className = 'runeTypeElement' + runeFreeLevel.id = `${key}RuneFreeLevel` + runeFreeLevel.textContent = '0' + runeFreeLevel.style.color = 'white' + + runesDiv.appendChild(runeFreeLevel) + + const runeTNL = document.createElement('span') + runeTNL.className = 'runeTypeElement' + runeTNL.id = `${key}RuneTNL` + runeTNL.textContent = '0' + runesDiv.appendChild(runeTNL) + + const sacrificeButton = document.createElement('button') + sacrificeButton.className = 'runeTypeElement' + sacrificeButton.id = `${key}RuneSacrifice` + sacrificeButton.setAttribute('i18n', 'general.sacrificeCapital') + sacrificeButton.textContent = i18next.t('general.sacrificeCapital') + + runesDiv.appendChild(sacrificeButton) + + runeContainer.appendChild(runesDiv) + + // Create locked rune container + const lockedRunesDiv = document.createElement('div') + lockedRunesDiv.className = 'runeType runeTypeLocked' + lockedRunesDiv.id = `${key}RuneLockedContainer` + lockedRunesDiv.style.border = '2px solid lightgray' + lockedRunesDiv.style.borderRadius = '8px' + lockedRunesDiv.style.margin = '2px' + + const lockedRuneName = document.createElement('p') + lockedRuneName.className = 'runeTypeElement' + lockedRuneName.setAttribute('i18n', 'runes.lockedRune') + lockedRuneName.textContent = i18next.t('runes.lockedRune') + + lockedRunesDiv.appendChild(lockedRuneName) + + const lockedRuneIcon = document.createElement('img') + lockedRuneIcon.className = 'runeImage runeImageLocked' + lockedRuneIcon.id = `${key}RuneLocked` + lockedRuneIcon.alt = `${key} Rune - Locked` + lockedRuneIcon.src = `Pictures/Runes/${key.charAt(0).toUpperCase() + key.slice(1)}.png` + lockedRuneIcon.loading = 'lazy' + + lockedRunesDiv.appendChild(lockedRuneIcon) + + runeContainer.appendChild(lockedRunesDiv) } - calculateRuneLevels() - if (player.runeshards < 0 || !player.runeshards) { - player.runeshards = 0 - } -} - -export const calculateOfferingsToLevelXTimes = (runeIndex: number, runeLevel: number, levels: number) => { - let exp = calculateRuneExpToLevel(runeIndex, runeLevel) - player.runeexp[runeIndex] - const maxLevel = calculateMaxRunes(runeIndex + 1) - const arr = [] - let sum = 0 - const off = player.runeshards - let levelsAdded = 0 - const fact = calculateRuneExpGiven(runeIndex, false, runeLevel, true) - const a = player.upgrades[71] / 25 - const add = fact[0] - a * runeLevel - const mult = fact.slice(1, fact.length).reduce((x, y) => x * y, 1) - while (levelsAdded < levels && runeLevel + levelsAdded < maxLevel && sum < off) { - const expPerOff = (add + a * (runeLevel + levelsAdded)) * mult - const amount = Math.ceil(exp / expPerOff) - sum += amount - arr.push(amount) - levelsAdded += 1 - exp = calculateRuneExpToLevel(runeIndex, runeLevel + levelsAdded) - - calculateRuneExpToLevel(runeIndex, runeLevel + levelsAdded - 1) - } - return arr } diff --git a/src/Shop.ts b/src/Shop.ts index a99f32f6b..9ff43ef38 100644 --- a/src/Shop.ts +++ b/src/Shop.ts @@ -1,15 +1,15 @@ +import Decimal from 'break_infinity.js' import i18next from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' import { - calculateAmbrosiaGenerationSpeed, calculateBaseObtainium, calculateBaseOfferings, calculateCashGrabBlueberryBonus, calculateCashGrabCubeBonus, calculateCashGrabQuarkBonus, calculateFreeShopInfinityUpgrades, + calculateObtainium, calculateObtainiumPotionBaseObtainium, - calculateObtainiumToDecimal, calculateOfferingPotionBaseOfferings, calculateOfferingsDecimal, calculatePotionValue, @@ -19,7 +19,9 @@ import { } from './Calculate' import type { IMultiBuy } from './Cubes' import { PCoinUpgradeEffects } from './PseudoCoinUpgrades' -import { format, player } from './Synergism' +import { getRuneEffectiveLevel } from './Runes' +import { getGQUpgradeEffect } from './singularity' +import { format, formatAsPercentIncrease, player } from './Synergism' import type { Player } from './types/Synergism' import { Alert, Confirm, Prompt, revealStuff } from './UpdateHTML' import { Globals as G } from './Variables' @@ -52,6 +54,7 @@ type shopResetTier = | 'Exalt7x10' | 'Exalt7x20' | 'Exalt7x30' + | 'Exalt8x5' export interface IShopData { price: number @@ -210,8 +213,8 @@ export const shopData: Record = { }, seasonPass2: { tier: 'Ascension', - price: 2000, - priceIncrease: 200, + price: 2500, + priceIncrease: 250, maxLevel: 100, type: shopUpgradeTypes.UPGRADE, refundable: true, @@ -463,8 +466,8 @@ export const shopData: Record = { improveQuarkHept: { tier: 'Ascension', price: 2e5 - 1, - priceIncrease: 0, - maxLevel: 1, + priceIncrease: 19999, + maxLevel: 15, type: shopUpgradeTypes.UPGRADE, refundable: false, refundMinimumLevel: 0 @@ -472,8 +475,8 @@ export const shopData: Record = { improveQuarkHept2: { tier: 'Singularity', price: 2e7 - 1, - priceIncrease: 0, - maxLevel: 1, + priceIncrease: 2e6 - 1, + maxLevel: 15, type: shopUpgradeTypes.UPGRADE, refundable: false, refundMinimumLevel: 0 @@ -481,8 +484,8 @@ export const shopData: Record = { improveQuarkHept3: { tier: 'SingularityVol2', price: 2e9 - 1, - priceIncrease: 0, - maxLevel: 1, + priceIncrease: 2e9 - 1, + maxLevel: 15, type: shopUpgradeTypes.UPGRADE, refundable: false, refundMinimumLevel: 0 @@ -490,8 +493,8 @@ export const shopData: Record = { improveQuarkHept4: { tier: 'SingularityVol3', price: 2e11 - 1, - priceIncrease: 0, - maxLevel: 1, + priceIncrease: 2e11 - 1, + maxLevel: 15, type: shopUpgradeTypes.UPGRADE, refundable: false, refundMinimumLevel: 0 @@ -777,7 +780,7 @@ export const shopData: Record = { }, shopSadisticRune: { tier: 'Exalt7x30', - price: 4.44e24, + price: 2e27, priceIncrease: 0, maxLevel: 1, type: shopUpgradeTypes.UPGRADE, @@ -792,6 +795,15 @@ export const shopData: Record = { type: shopUpgradeTypes.UPGRADE, refundable: false, refundMinimumLevel: 0 + }, + shopHorseShoe: { + tier: 'Exalt8x5', + price: 5e26, + priceIncrease: 0, + maxLevel: 1, + type: shopUpgradeTypes.UPGRADE, + refundable: false, + refundMinimumLevel: 0 } } @@ -879,6 +891,7 @@ type ShopUpgradeNames = | 'shopRedLuck2' | 'shopRedLuck3' | 'shopInfiniteShopUpgrades' + | 'shopHorseShoe' export const getShopCosts = (input: ShopUpgradeNames) => { if ( @@ -921,7 +934,7 @@ export const shopDescriptions = (input: ShopUpgradeNames) => { case 'obtainiumPotion': lol.innerHTML = i18next.t('shop.upgradeEffects.obtainiumPotion', { amount: format( - calculatePotionValue(player.reincarnationcounter, calculateObtainiumToDecimal(), calculateBaseObtainium()), + calculatePotionValue(player.reincarnationcounter, calculateObtainium(), calculateBaseObtainium()), 2, true ), @@ -1189,22 +1202,22 @@ export const shopDescriptions = (input: ShopUpgradeNames) => { break case 'improveQuarkHept': lol.innerHTML = i18next.t('shop.upgradeEffects.improveQuarkHept', { - amount: 2 * player.shopUpgrades.improveQuarkHept + amount: format(player.shopUpgrades.improveQuarkHept / 100, 2) }) break case 'improveQuarkHept2': lol.innerHTML = i18next.t('shop.upgradeEffects.improveQuarkHept2', { - amount: 2 * player.shopUpgrades.improveQuarkHept2 + amount: format(player.shopUpgrades.improveQuarkHept2 / 100, 2) }) break case 'improveQuarkHept3': lol.innerHTML = i18next.t('shop.upgradeEffects.improveQuarkHept3', { - amount: 2 * player.shopUpgrades.improveQuarkHept3 + amount: format(player.shopUpgrades.improveQuarkHept3 / 100, 2) }) break case 'improveQuarkHept4': lol.innerHTML = i18next.t('shop.upgradeEffects.improveQuarkHept4', { - amount: 2 * player.shopUpgrades.improveQuarkHept4 + amount: format(player.shopUpgrades.improveQuarkHept4 / 100, 2) }) break case 'shopImprovedDaily': @@ -1262,7 +1275,7 @@ export const shopDescriptions = (input: ShopUpgradeNames) => { break case 'improveQuarkHept5': lol.innerHTML = i18next.t('shop.upgradeEffects.improveQuarkHept5', { - amount: format(player.shopUpgrades.improveQuarkHept5 / 25, 2, true) + amount: format(player.shopUpgrades.improveQuarkHept5 / 100, 2, true) }) break case 'seasonPassInfinity': @@ -1401,10 +1414,11 @@ export const shopDescriptions = (input: ShopUpgradeNames) => { break case 'shopAmbrosiaAccelerator': lol.innerHTML = i18next.t('shop.upgradeEffects.shopAmbrosiaAccelerator', { - amount: format(0.2 * player.shopUpgrades.shopAmbrosiaAccelerator, 1, true), - amount2: format( - player.shopUpgrades.shopAmbrosiaAccelerator * 0.2 * calculateAmbrosiaGenerationSpeed(), - 0, + amount: format(0.4 * player.shopUpgrades.shopAmbrosiaAccelerator, 1, true), + total: format( + 0.4 * player.shopUpgrades.shopAmbrosiaAccelerator + * player.singularityChallenges.noAmbrosiaUpgrades.completions, + 1, true ) }) @@ -1460,6 +1474,16 @@ export const shopDescriptions = (input: ShopUpgradeNames) => { }) break } + case 'shopHorseShoe': { + const horseShoeLevel = getRuneEffectiveLevel('horseShoe') + lol.innerHTML = i18next.t('shop.upgradeEffects.shopHorseShoe', { + amount1: player.shopUpgrades.shopHorseShoe > 0 ? 3 : 0, + amount2: formatAsPercentIncrease( + 1 - Math.min(300, horseShoeLevel * player.shopUpgrades.shopHorseShoe) / 1000, + 2 + ) + }) + } } } @@ -1547,7 +1571,8 @@ export const friendlyShopName = (input: ShopUpgradeNames) => { shopSingularitySpeedup: 'Singularity Timed-Perks Speedup', shopSingularityPotency: 'Singularity Passives Potency', shopSadisticRune: 'Sadistic Rune Unlock! Or does it?', - shopInfiniteShopUpgrades: 'Blue Infinity Shop Voucher' + shopInfiniteShopUpgrades: 'Blue Infinity Shop Voucher', + shopHorseShoe: 'A Horse Shoe Singularity Debuff' } return names[input] @@ -1723,64 +1748,87 @@ export const autoBuyConsumable = (input: ShopUpgradeNames) => { player.shopUpgrades[input] += maxBuyablePotions } -export const useConsumable = async ( +export const useConsumablePrompt = async ( + input: ShopUpgradeNames, + used = 1, + spend = true +) => { + const p = player.shopConfirmationToggle || await Confirm('Would you like to use some of this potion?') + + if (p) { + return useConsumable(input, false, used, spend) + } +} + +export const useConsumable = ( input: ShopUpgradeNames, automatic = false, used = 1, spend = true ) => { const infiniteAutoBrew = PCoinUpgradeEffects.AUTO_POTION_FREE_POTIONS_QOL - const p = player.shopConfirmationToggle && !automatic - ? await Confirm('Would you like to use some of this potion?') - : true - if (p) { - if (input === 'offeringPotion') { - const offeringPotionValue = calculatePotionValue( - player.prestigecounter, - calculateOfferingsDecimal(), - calculateBaseOfferings() + if (input === 'offeringPotion') { + let offeringPotionValue = calculatePotionValue( + player.prestigecounter, + calculateOfferingsDecimal(), + calculateBaseOfferings() + ) + + if ( + player.singularityChallenges.taxmanLastStand.enabled + && player.singularityChallenges.taxmanLastStand.completions >= 2 + ) { + offeringPotionValue = Decimal.min( + offeringPotionValue, + player.obtainium.times(100).plus(1) ) + } - if (infiniteAutoBrew && automatic) { - player.runeshards += offeringPotionValue * used - player.runeshards = Math.min(1e300, player.runeshards) - player.shopPotionsConsumed.offering += used - } else if (player.shopUpgrades.offeringPotion >= used || !spend) { - player.shopUpgrades.offeringPotion -= spend ? used : 0 - player.runeshards += offeringPotionValue * used - player.runeshards = Math.min(1e300, player.runeshards) - player.shopPotionsConsumed.offering += used - } + if (infiniteAutoBrew && automatic) { + player.offerings = player.offerings.add(offeringPotionValue.times(used)) + player.shopPotionsConsumed.offering += used + } else if (player.shopUpgrades.offeringPotion >= used || !spend) { + player.shopUpgrades.offeringPotion -= spend ? used : 0 + player.offerings = player.offerings.add(offeringPotionValue.times(used)) + player.shopPotionsConsumed.offering += used + } - if (!automatic) { - shopDescriptions('offeringPotion') - } - } else if (input === 'obtainiumPotion') { - if (player.currentChallenge.ascension === 14) { - return - } + if (!automatic) { + shopDescriptions('offeringPotion') + } + } else if (input === 'obtainiumPotion') { + if (player.currentChallenge.ascension === 14) { + return + } + + let obtainiumPotionValue = calculatePotionValue( + player.reincarnationcounter, + calculateObtainium(), + calculateBaseObtainium() + ) - const obtainiumPotionValue = calculatePotionValue( - player.reincarnationcounter, - calculateObtainiumToDecimal(), - calculateBaseObtainium() + if ( + player.singularityChallenges.taxmanLastStand.enabled + && player.singularityChallenges.taxmanLastStand.completions >= 2 + ) { + obtainiumPotionValue = Decimal.min( + obtainiumPotionValue, + player.obtainium.times(100).plus(1) ) + } - if (infiniteAutoBrew && automatic) { - player.researchPoints += obtainiumPotionValue * used - player.researchPoints = Math.min(1e300, player.researchPoints) - player.shopPotionsConsumed.obtainium += used - } else if (player.shopUpgrades.obtainiumPotion >= used || !spend) { - player.shopUpgrades.obtainiumPotion -= spend ? used : 0 - player.researchPoints += obtainiumPotionValue * used - player.researchPoints = Math.min(1e300, player.researchPoints) - player.shopPotionsConsumed.obtainium += used - } + if (infiniteAutoBrew && automatic) { + player.obtainium = player.obtainium.add(obtainiumPotionValue.times(used)) + player.shopPotionsConsumed.obtainium += used + } else if (player.shopUpgrades.obtainiumPotion >= used || !spend) { + player.shopUpgrades.obtainiumPotion -= spend ? used : 0 + player.obtainium = player.obtainium.add(obtainiumPotionValue.times(used)) + player.shopPotionsConsumed.obtainium += used + } - if (!automatic) { - shopDescriptions('obtainiumPotion') - } + if (!automatic) { + shopDescriptions('obtainiumPotion') } } } @@ -1946,11 +1994,11 @@ export const isShopUpgradeUnlocked = (upgrade: ShopUpgradeNames): boolean => { || player.highestSingularityCount > 0 ) case 'calculator4': - return Boolean(player.singularityUpgrades.wowPass.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass')) case 'calculator5': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'calculator6': - return Boolean(player.singularityUpgrades.wowPass3.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass3')) case 'calculator7': return Boolean( player.singularityChallenges.limitedAscensions.rewards.shopUpgrade @@ -1971,70 +2019,70 @@ export const isShopUpgradeUnlocked = (upgrade: ShopUpgradeNames): boolean => { || player.highestSingularityCount > 0 ) case 'chronometer3': - return Boolean(player.singularityUpgrades.wowPass.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass')) case 'seasonPassY': return ( player.challenge15Exponent >= G.challenge15Rewards.hepteractsUnlocked.requirement || player.highestSingularityCount > 0 ) case 'seasonPassZ': - return Boolean(player.singularityUpgrades.wowPass.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass')) case 'challengeTome2': - return Boolean(player.singularityUpgrades.wowPass.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass')) case 'instantChallenge2': - return Boolean(player.singularityUpgrades.wowPass.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass')) case 'cashGrab2': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'cubeToQuarkAll': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'chronometerZ': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'offeringEX2': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'obtainiumEX2': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'powderAuto': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'seasonPassLost': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'challenge15Auto': - return Boolean(player.singularityUpgrades.wowPass3.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass3')) case 'extraWarp': - return Boolean(player.singularityUpgrades.wowPass3.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass3')) case 'autoWarp': - return Boolean(player.singularityUpgrades.wowPass3.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass3')) case 'improveQuarkHept': return ( player.challenge15Exponent >= G.challenge15Rewards.hepteractsUnlocked.requirement || player.highestSingularityCount > 0 ) case 'improveQuarkHept2': - return Boolean(player.singularityUpgrades.wowPass.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass')) case 'improveQuarkHept3': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'improveQuarkHept4': - return Boolean(player.singularityUpgrades.wowPass3.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass3')) case 'shopImprovedDaily': return ( player.highestchallengecompletions[14] > 0 || player.highestSingularityCount > 0 ) case 'shopImprovedDaily2': - return Boolean(player.singularityUpgrades.wowPass.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass')) case 'shopImprovedDaily3': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'shopImprovedDaily4': - return Boolean(player.singularityUpgrades.wowPass3.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass3')) case 'offeringEX3': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'obtainiumEX3': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'improveQuarkHept5': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'chronometerInfinity': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'seasonPassInfinity': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'shopSingularityPenaltyDebuff': return Boolean( player.singularityChallenges.noSingularityUpgrades.rewards.shopUpgrade @@ -2048,27 +2096,27 @@ export const isShopUpgradeUnlocked = (upgrade: ShopUpgradeNames): boolean => { player.singularityChallenges.noOcteracts.rewards.shopUpgrade ) case 'shopAmbrosiaGeneration1': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'shopAmbrosiaGeneration2': - return Boolean(player.singularityUpgrades.wowPass3.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass3')) case 'shopAmbrosiaGeneration3': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'shopAmbrosiaGeneration4': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'shopAmbrosiaLuck1': - return Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass2')) case 'shopAmbrosiaLuck2': - return Boolean(player.singularityUpgrades.wowPass3.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass3')) case 'shopAmbrosiaLuck3': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'shopAmbrosiaLuck4': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'shopRedLuck1': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'shopRedLuck2': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'shopRedLuck3': - return Boolean(player.singularityUpgrades.wowPass4.getEffect().bonus) + return Boolean(getGQUpgradeEffect('wowPass4')) case 'shopCashGrabUltra': return Boolean(player.singularityChallenges.noSingularityUpgrades.rewards.shopUpgrade2) case 'shopAmbrosiaAccelerator': @@ -2087,5 +2135,7 @@ export const isShopUpgradeUnlocked = (upgrade: ShopUpgradeNames): boolean => { return Boolean(player.singularityChallenges.sadisticPrequel.rewards.shopUpgrade3) case 'shopInfiniteShopUpgrades': return Boolean(player.singularityChallenges.limitedAscensions.rewards.shopUpgrade0) + case 'shopHorseShoe': + return Boolean(player.singularityChallenges.taxmanLastStand.rewards.shopUpgrade) } } diff --git a/src/SingularityChallenges.ts b/src/SingularityChallenges.ts index 5047ec29d..e3b70f070 100644 --- a/src/SingularityChallenges.ts +++ b/src/SingularityChallenges.ts @@ -2,8 +2,8 @@ import i18next from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' import { calculateGoldenQuarks } from './Calculate' import { singularity } from './Reset' +import { runes } from './Runes' import { player } from './Synergism' -import type { Player } from './types/Synergism' import { Alert, Confirm } from './UpdateHTML' import { toOrdinal } from './Utility' import { Globals as G } from './Variables' @@ -12,18 +12,29 @@ export interface ISingularityChallengeData { baseReq: number maxCompletions: number unlockSingularity: number - HTMLTag: keyof Player['singularityChallenges'] + HTMLTag: SingularityChallengeDataKeys singularityRequirement: (baseReq: number, completions: number) => number effect: (n: number) => Record + achievementPointValue: (n: number) => number scalingrewardcount: number uniquerewardcount: number resetTime?: boolean completions?: number enabled?: boolean + alternateDescription?: () => string highestSingularityCompleted?: number - cacheUpdates?: (() => void)[] } +export type SingularityChallengeDataKeys = + | 'noSingularityUpgrades' + | 'oneChallengeCap' + | 'noOcteracts' + | 'limitedAscensions' + | 'noAmbrosiaUpgrades' + | 'limitedTime' + | 'sadisticPrequel' + | 'taxmanLastStand' + export class SingularityChallenge { public name public description @@ -37,9 +48,10 @@ export class SingularityChallenge { public resetTime public singularityRequirement public effect + public achievementPointValue + public alternateDescription public scalingrewardcount public uniquerewardcount - readonly cacheUpdates: (() => void)[] | undefined #key: string public constructor (data: ISingularityChallengeData, key: string) { @@ -59,12 +71,13 @@ export class SingularityChallenge { this.resetTime = data.resetTime ?? false this.singularityRequirement = data.singularityRequirement this.effect = data.effect + this.achievementPointValue = data.achievementPointValue + this.alternateDescription = data.alternateDescription ?? undefined this.scalingrewardcount = data.scalingrewardcount this.uniquerewardcount = data.uniquerewardcount this.updateIconHTML() this.updateChallengeCompletions() - this.cacheUpdates = data.cacheUpdates ?? undefined this.#key = key } @@ -88,7 +101,7 @@ export class SingularityChallenge { if (!this.enabled) { return this.enableChallenge() } else { - return this.exitChallenge(player.runelevels[6] > 0) + return this.exitChallenge(runes.antiquities.level > 0) } } @@ -146,7 +159,7 @@ export class SingularityChallenge { public async exitChallenge (success: boolean) { if (!success) { - const extra = player.runelevels[6] === 0 + const extra = runes.antiquities.level === 0 ? i18next.t('singularityChallenge.exitChallenge.incompleteWarning') : '' const confirmation = await Confirm( @@ -174,7 +187,6 @@ export class SingularityChallenge { this.updateChallengeCompletions() singularity(highestSingularityHold) player.singularityCounter = holdSingTimer - this.updateCaches() return Alert( i18next.t('singularityChallenge.exitChallenge.acceptSuccess', { tier: toOrdinal(this.completions), @@ -192,14 +204,6 @@ export class SingularityChallenge { } } - updateCaches (): void { - if (this.cacheUpdates !== undefined) { - for (const cache of this.cacheUpdates) { - cache() - } - } - } - /** * Given a Singularity Challenge, give a concise information regarding its data. * @returns A string that details the name, description, metadata. @@ -240,7 +244,9 @@ export class SingularityChallenge { this.completions ) } - ${this.description}` + ${ + this.alternateDescription !== undefined ? this.alternateDescription() : this.description + }` } // Numerates through total reward count for Scaling & Unique string for EXALTS. scaleString (): string { @@ -277,17 +283,25 @@ export class SingularityChallenge { return this.effect(this.completions) } + public get rewardAP () { + return this.achievementPointValue(this.completions) + } + + public get maxAP () { + return this.achievementPointValue(this.maxCompletions) + } + valueOf (): ISingularityChallengeData { return { baseReq: this.baseReq, effect: this.effect, HTMLTag: this.HTMLTag, maxCompletions: this.maxCompletions, + achievementPointValue: this.achievementPointValue, scalingrewardcount: this.scalingrewardcount, singularityRequirement: this.singularityRequirement, uniquerewardcount: this.uniquerewardcount, unlockSingularity: this.unlockSingularity, - cacheUpdates: this.cacheUpdates, completions: this.completions, enabled: this.enabled, highestSingularityCompleted: this.highestSingularityCompleted, @@ -301,7 +315,7 @@ export class SingularityChallenge { } export const singularityChallengeData: Record< - keyof Player['singularityUpgrades'], + SingularityChallengeDataKeys, ISingularityChallengeData > = { noSingularityUpgrades: { @@ -312,6 +326,9 @@ export const singularityChallengeData: Record< singularityRequirement: (baseReq: number, completions: number) => { return baseReq + 8 * completions }, + achievementPointValue: (n) => { + return 5 * n + 5 * Math.max(0, n - 15) + }, scalingrewardcount: 1, uniquerewardcount: 5, effect: (n: number) => { @@ -333,6 +350,9 @@ export const singularityChallengeData: Record< singularityRequirement: (baseReq: number, completions: number) => { return baseReq + 11 * completions }, + achievementPointValue: (n) => { + return 5 * n + 5 * Math.max(0, n - 12) + }, scalingrewardcount: 2, uniquerewardcount: 4, effect: (n: number) => { @@ -351,6 +371,9 @@ export const singularityChallengeData: Record< baseReq: 75, maxCompletions: 15, unlockSingularity: 100, + achievementPointValue: (n) => { + return 10 * n + 5 * Math.max(0, n - 7) + }, HTMLTag: 'noOcteracts', singularityRequirement: (baseReq: number, completions: number) => { if (completions < 10) { @@ -374,6 +397,9 @@ export const singularityChallengeData: Record< baseReq: 10, maxCompletions: 25, unlockSingularity: 50, + achievementPointValue: (n) => { + return 5 * n + 5 * Math.max(0, n - 10) + }, HTMLTag: 'limitedAscensions', singularityRequirement: (baseReq: number, completions: number) => { return baseReq + 10 * completions @@ -391,11 +417,18 @@ export const singularityChallengeData: Record< }, noAmbrosiaUpgrades: { baseReq: 150, - maxCompletions: 20, + maxCompletions: 25, unlockSingularity: 166, + achievementPointValue: (n) => { + return 10 * n + 5 * Math.max(0, n - 10) + }, HTMLTag: 'noAmbrosiaUpgrades', singularityRequirement: (baseReq: number, completions: number) => { - return baseReq + 6 * completions + if (completions < 20) { + return baseReq + 6 * completions + } else { + return baseReq + 6 * 19 + 3 * (completions - 19) + } }, scalingrewardcount: 4, uniquerewardcount: 7, @@ -417,6 +450,9 @@ export const singularityChallengeData: Record< baseReq: 203, maxCompletions: 25, unlockSingularity: 216, + achievementPointValue: (n) => { + return 10 * n + 5 * Math.max(0, n - 10) + 5 * Math.max(0, n - 20) + }, HTMLTag: 'limitedTime', singularityRequirement: (baseReq: number, completions: number) => { return baseReq + 3 * completions @@ -439,6 +475,9 @@ export const singularityChallengeData: Record< baseReq: 120, maxCompletions: 30, unlockSingularity: 256, + achievementPointValue: (n) => { + return 10 * n + 5 * Math.max(0, n - 10) + 5 * Math.max(0, n - 20) + 5 * Math.max(0, n - 25) + }, HTMLTag: 'sadisticPrequel', singularityRequirement: (baseReq: number, completions: number) => { return baseReq + 4 * completions @@ -455,5 +494,58 @@ export const singularityChallengeData: Record< shopUpgrade3: n >= 30 } } + }, + taxmanLastStand: { + baseReq: 231, + maxCompletions: 10, + unlockSingularity: 281, + achievementPointValue: (n) => { + return 50 * n + }, + HTMLTag: 'taxmanLastStand', + singularityRequirement: (baseReq: number, completions: number) => { + return baseReq + 3 * completions + }, + scalingrewardcount: 4, + uniquerewardcount: 3, + effect: (n: number) => { + return { + horseShoeUnlock: n > 0, + shopUpgrade: n >= 5, + talismanUnlock: n >= 10, + talismanFreeLevel: 25 * n, + talismanRuneEffect: 0.03 * n, + antiquityOOM: 1 / 50 * n / 10, + horseShoeOOM: 1 / 20 * n / 10 + } + }, + alternateDescription: () => { + const completions = player.singularityChallenges.taxmanLastStand.completions + const baseDesc = i18next.t('singularityChallenge.data.taxmanLastStand.description') + const salvText = i18next.t('singularityChallenge.data.taxmanLastStand.salvageMod') + const taxText = i18next.t('singularityChallenge.data.taxmanLastStand.taxMod') + const offText = i18next.t('singularityChallenge.data.taxmanLastStand.offeringMod') + const obtText = i18next.t('singularityChallenge.data.taxmanLastStand.obtainiumMod') + let stringText = `${baseDesc}
${salvText}
${taxText}
${offText}
${obtText}` + + if (completions >= 2) { + const capMod = i18next.t('singularityChallenge.data.taxmanLastStand.capMod') + stringText += `
${capMod}` + } + if (completions >= 5) { + const tributeMod = i18next.t('singularityChallenge.data.taxmanLastStand.tributeMod') + stringText += `
${tributeMod}` + } + if (completions >= 8) { + const omegaMod = i18next.t('singularityChallenge.data.taxmanLastStand.omegaMod') + stringText += `
${omegaMod}` + } + return stringText + } } } + +export const maxAPFromChallenges = Object.values(singularityChallengeData).reduce( + (acc, challenge) => acc + challenge.achievementPointValue(challenge.maxCompletions), + 0 +) diff --git a/src/Statistics.ts b/src/Statistics.ts index 18f5a2b39..c105e685f 100644 --- a/src/Statistics.ts +++ b/src/Statistics.ts @@ -1,5 +1,7 @@ import Decimal from 'break_infinity.js' import i18next from 'i18next' +import { getAchievementReward } from './Achievements' +import { getAmbrosiaUpgradeEffects } from './BlueberryUpgrades' import { DOMCacheGetOrSet } from './Cache/DOM' import { calculateAllCubeMultiplier, @@ -16,7 +18,7 @@ import { calculateAmbrosiaLuckShopUpgrade, calculateAmbrosiaLuckSingularityUpgrade, calculateAmbrosiaQuarkMult, - calculateAntSacrificeMultipliers, + calculateAntSacrificeMultiplier, calculateAscensionScore, calculateAscensionSpeedExponentSpread, calculateAscensionSpeedMult, @@ -30,7 +32,6 @@ import { calculateCubeMultFromPowder, calculateCubeMultiplier, calculateDilatedFiveLeafBonus, - calculateEffectiveIALevel, calculateEventBuff, calculateExalt6Penalty, calculateEXUltraCubeBonus, @@ -46,6 +47,8 @@ import { calculateHypercubeMultiplier, calculateLimitedAscensionsDebuff, calculateLuckConversion, + calculateNegativeSalvage, + calculateNegativeSalvageMultiplier, calculateNumberOfThresholds, calculateObtainium, calculateObtainiumDecimal, @@ -56,10 +59,14 @@ import { calculateOfferings, calculateOfferingsDecimal, calculatePlatonicMultiplier, + calculatePositiveSalvage, + calculatePositiveSalvageMultiplier, calculatePowderConversion, calculateQuarkMultFromPowder, calculateQuarkMultiplier, calculateRawAscensionSpeedMult, + calculateRawNegativeSalvage, + calculateRawPositiveSalvage, calculateRedAmbrosiaCubes, calculateRedAmbrosiaGenerationSpeed, calculateRedAmbrosiaLuck, @@ -74,15 +81,22 @@ import { calculateTotalOcteractObtainiumBonus, calculateTotalOcteractOfferingBonus, calculateTotalOcteractQuarkBonus, + calculateTotalSalvage, derpsmithCornucopiaBonus, isIARuneUnlocked, resetTimeThreshold, sumOfExaltCompletions } from './Calculate' -import { formatAsPercentIncrease } from './Campaign' import { CalcECC, type Challenge15Rewards, challenge15ScoreMultiplier } from './Challenges' +import { + calculateGlobalSpeedCubeBlessing, + calculateObtainiumCubeBlessing, + calculateOfferingCubeBlessing, + calculateRuneEffectivenessCubeBlessing, + calculateSalvageCubeBlessing +} from './Cubes' import { BuffType } from './Event' -import { hepteractEffective } from './Hepteracts' +import { getHepteractEffects, hepteracts } from './Hepteracts' import { addCodeBonuses, addCodeInterval, @@ -91,12 +105,36 @@ import { addCodeSingularityPerkBonus, addCodeTimeToNextUse } from './ImportExport' +import { getLevelMilestone, getLevelReward } from './Levels' +import { getOcteractUpgradeEffect, octeractUpgrades } from './Octeracts' +import { + calculateCubeMultiplierPlatonicBlessing, + calculateGlobalSpeedPlatonicBlessing, + calculateHypercubeMultiplierPlatonicBlessing, + calculatePlatonicMultiplierPlatonicBlessing, + calculateTesseractMultiplierPlatonicBlessing +} from './PlatonicCubes' import { PCoinUpgradeEffects } from './PseudoCoinUpgrades' import { getQuarkBonus } from './Quark' -import { getRedAmbrosiaUpgrade } from './RedAmbrosiaUpgrades' +import { getRedAmbrosiaUpgradeEffects } from './RedAmbrosiaUpgrades' +import { getRuneBlessingEffect } from './RuneBlessings' +import { + firstFiveEffectiveRuneLevelMult, + getRuneEffects, + runes, + SIEffectiveRuneLevelMult, + sumOfRuneLevels +} from './Runes' +import { getRuneSpiritEffect } from './RuneSpirits' import { shopData } from './Shop' -import { calculateSingularityDebuff, getFastForwardTotalMultiplier } from './singularity' -import { format, player } from './Synergism' +import { + calculateSingularityDebuff, + getFastForwardTotalMultiplier, + getGQUpgradeEffect, + goldenQuarkUpgrades +} from './singularity' +import { format, formatAsPercentIncrease, player } from './Synergism' +import { getTalismanEffects, sumOfTalismanRarities, talismans } from './Talismans' import type { GlobalVariables } from './types/Synergism' import { sumContents } from './Utility' import { Globals as G } from './Variables' @@ -113,17 +151,24 @@ export const allCubeStats: StatLine[] = [ { i18n: 'PseudoCoins', stat: () => PCoinUpgradeEffects.CUBE_BUFF, - color: 'gold' + color: 'gold', + displayCriterion: () => true }, { i18n: 'AscensionTime', - stat: () => - Math.pow(Math.min(1, player.ascensionCounter / resetTimeThreshold()), 2) - * (1 - + ((1 / 4) * player.achievements[204] - + (1 / 4) * player.achievements[211] - + (1 / 2) * player.achievements[218]) - * Math.max(0, player.ascensionCounter / resetTimeThreshold() - 1)) + stat: () => { + const ascensionCounter = player.ascensionCounter + const resetThreshold = resetTimeThreshold() + const scale = getAchievementReward('ascensionRewardScaling') + + if (scale) { + return Math.pow(Math.min(1, ascensionCounter / resetThreshold), 2) + * (1 + Math.max(0, ascensionCounter / resetThreshold - 1)) + } else { + return Math.pow(Math.min(1, ascensionCounter / resetThreshold), 2) + } + }, + displayCriterion: () => true }, { i18n: 'CampaignTutorial', @@ -135,27 +180,6 @@ export const allCubeStats: StatLine[] = [ stat: () => player.campaigns.cubeBonus, displayCriterion: () => player.challengecompletions[11] > 0 }, - { - i18n: 'SunMoon', - stat: () => - 1 - + (6 / 100) * player.achievements[250] - + (10 / 100) * player.achievements[251], - displayCriterion: () => player.challengecompletions[14] > 0 - }, - { - i18n: 'SpeedAchievement', - stat: () => - 1 - + player.achievements[240] - * Math.min( - 0.5, - Math.max( - 0.1, - (1 / 20) * Math.log10(calculateGlobalSpeedMult() + 0.01) - ) - ) - }, { i18n: 'Challenge15', stat: () => @@ -168,7 +192,7 @@ export const allCubeStats: StatLine[] = [ }, { i18n: 'InfiniteAscent', - stat: () => 1 + (1 / 100) * calculateEffectiveIALevel() + stat: () => getRuneEffects('infiniteAscent').cubeMult }, { i18n: 'Beta', @@ -198,7 +222,7 @@ export const allCubeStats: StatLine[] = [ { i18n: 'PassZ', stat: () => 1 + (player.shopUpgrades.seasonPassZ * player.singularityCount) / 100, - displayCriterion: () => Boolean(player.singularityUpgrades.wowPass2.getEffect().bonus) + displayCriterion: () => Boolean(getGQUpgradeEffect('wowPass2')) }, { i18n: 'PassINF', @@ -214,32 +238,32 @@ export const allCubeStats: StatLine[] = [ }, { i18n: 'StarterPack', - stat: () => 1 + 4 * (player.singularityUpgrades.starterPack.getEffect().bonus ? 1 : 0) + stat: () => 1 + 4 * (getGQUpgradeEffect('starterPack') ? 1 : 0) }, { i18n: 'SingCubes1', - stat: () => +player.singularityUpgrades.singCubes1.getEffect().bonus + stat: () => getGQUpgradeEffect('singCubes1') }, { i18n: 'SingCubes2', - stat: () => +player.singularityUpgrades.singCubes2.getEffect().bonus + stat: () => getGQUpgradeEffect('singCubes2') }, { i18n: 'SingCubes3', - stat: () => +player.singularityUpgrades.singCubes3.getEffect().bonus + stat: () => getGQUpgradeEffect('singCubes3') }, { i18n: 'SingCitadel', - stat: () => +player.singularityUpgrades.singCitadel.getEffect().bonus + stat: () => getGQUpgradeEffect('singCitadel') }, { i18n: 'SingCitadel2', - stat: () => +player.singularityUpgrades.singCitadel2.getEffect().bonus + stat: () => getGQUpgradeEffect('singCitadel2') }, { i18n: 'Delta', stat: () => - 1 + +player.singularityUpgrades.platonicDelta.getEffect().bonus + 1 + getGQUpgradeEffect('platonicDelta') * Math.min( 9, (player.shopUpgrades.shopSingularitySpeedup > 0) @@ -269,35 +293,35 @@ export const allCubeStats: StatLine[] = [ }, { i18n: 'ModuleTutorial', - stat: () => +player.blueberryUpgrades.ambrosiaTutorial.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaTutorial').cubes }, { i18n: 'ModuleCubes1', - stat: () => +player.blueberryUpgrades.ambrosiaCubes1.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaCubes1').cubes }, { i18n: 'ModuleLuckCube1', - stat: () => +player.blueberryUpgrades.ambrosiaLuckCube1.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaLuckCube1').cubes }, { i18n: 'ModuleQuarkCube1', - stat: () => +player.blueberryUpgrades.ambrosiaQuarkCube1.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaQuarkCube1').cubes }, { i18n: 'ModuleCubes2', - stat: () => +player.blueberryUpgrades.ambrosiaCubes2.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaCubes2').cubes }, { i18n: 'ModuleHyperflux', - stat: () => +player.blueberryUpgrades.ambrosiaHyperflux.bonus.hyperFlux + stat: () => getAmbrosiaUpgradeEffects('ambrosiaHyperflux').hyperFlux }, { i18n: 'ModuleCubes3', - stat: () => +player.blueberryUpgrades.ambrosiaCubes3.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaCubes3').cubes }, { i18n: 'RedAmbrosiaTutorial', - stat: () => getRedAmbrosiaUpgrade('tutorial').bonus.cubeMult + stat: () => getRedAmbrosiaUpgradeEffects('tutorial').cubeMult }, { i18n: 'RedAmbrosia', @@ -331,10 +355,23 @@ export const allWowCubeStats: StatLine[] = [ i18n: 'GlobalCube', stat: () => calculateAllCubeMultiplier() }, + { + i18n: 'SynergismLevel', + stat: () => getLevelReward('wowCubes'), + color: 'green' + }, + { + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('wowCubeGain') + }, { i18n: 'SeasonPass1', stat: () => 1 + (2.25 * player.shopUpgrades.seasonPass) / 100 }, + { + i18n: 'WowSquare', + stat: () => getTalismanEffects('wowSquare').oddDimBonus + }, { i18n: 'Researches', stat: () => @@ -367,52 +404,13 @@ export const allWowCubeStats: StatLine[] = [ * Decimal.log(player.ascendShards.add(1), 4) * Math.min(1, player.constantUpgrades[10]) }, - { - i18n: 'Achievement189', - stat: () => 1 + player.achievements[189] * Math.min(2, player.ascensionCount / 2.5e8) - }, - { - i18n: 'Achievement193', - stat: () => - 1 - + (player.achievements[193] * Decimal.log(player.ascendShards.add(1), 10)) - / 400 - }, - { - i18n: 'Achievement195', - stat: () => - 1 - + Math.min( - 250, - (player.achievements[195] - * Decimal.log(player.ascendShards.add(1), 10)) - / 400 - ) - }, - { - i18n: 'Achievement198-201', - stat: () => - 1 - + (4 / 100) - * (player.achievements[198] - + player.achievements[199] - + player.achievements[200]) - + (3 / 100) * player.achievements[201] - }, - { - i18n: 'Achievement254', - stat: () => - 1 - + Math.min(0.15, (0.6 / 100) * Math.log10(calculateAscensionScore().effectiveScore + 1)) - * player.achievements[254] - }, { i18n: 'SpiritPower', - stat: () => 1 + player.corruptions.used.totalCorruptionDifficultyMultiplier * G.effectiveRuneSpiritPower[2] + stat: () => getRuneSpiritEffect('duplication').wowCubes }, { i18n: 'PlatonicOpening', - stat: () => 1 + G.platonicBonusMultiplier[0] + stat: () => calculateCubeMultiplierPlatonicBlessing() }, { i18n: 'Platonic1x1', @@ -439,10 +437,23 @@ export const allTesseractStats: StatLine[] = [ i18n: 'GlobalCube', stat: () => calculateAllCubeMultiplier() }, + { + i18n: 'SynergismLevel', + stat: () => getLevelReward('wowTesseracts'), + color: 'green' + }, + { + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('wowTesseractGain') + }, { i18n: 'SeasonPass1', stat: () => 1 + (2.25 * player.shopUpgrades.seasonPass) / 100 }, + { + i18n: 'WowSquare', + stat: () => getTalismanEffects('wowSquare').evenDimBonus + }, { i18n: 'ConstantUpgrade10', stat: () => 1 + 0.01 * Decimal.log(player.ascendShards.add(1), 4) * Math.min(1, player.constantUpgrades[10]) @@ -455,39 +466,9 @@ export const allTesseractStats: StatLine[] = [ i18n: 'CubeUpgrade4x8', stat: () => 1 + (1 / 200) * player.cubeUpgrades[38] * player.corruptions.used.totalLevels }, - { - i18n: 'Achievement195', - stat: () => - 1 + Math.min( - 250, - (player.achievements[195] * Decimal.log(player.ascendShards.add(1), 10)) / 400 - ) - }, - { - i18n: 'Achievement202', - stat: () => 1 + player.achievements[202] * Math.min(2, player.ascensionCount / 5e8) - }, - { - i18n: 'Achievement205-208', - stat: () => - 1 + (4 / 100) * ( - player.achievements[205] - + player.achievements[206] - + player.achievements[207] - ) - + (3 / 100) * player.achievements[208] - }, - { - i18n: 'Achievement255', - stat: () => - 1 + Math.min( - 0.15, - (0.6 / 100) * Math.log10(calculateAscensionScore().effectiveScore + 1) - ) * player.achievements[255] - }, { i18n: 'PlatonicCube', - stat: () => G.platonicBonusMultiplier[1] + stat: () => calculateTesseractMultiplierPlatonicBlessing() }, { i18n: 'Platonic1x2', @@ -505,42 +486,25 @@ export const allHypercubeStats: StatLine[] = [ stat: () => calculateAllCubeMultiplier() }, { - i18n: 'SeasonPass2', - stat: () => 1 + (1.5 * player.shopUpgrades.seasonPass2) / 100 - }, - { - i18n: 'Achievement212-215', - stat: () => - 1 + (4 / 100) * ( - player.achievements[212] - + player.achievements[213] - + player.achievements[214] - ) - + (3 / 100) * player.achievements[215] - }, - { - i18n: 'Achievement216', - stat: () => 1 + player.achievements[216] * Math.min(2, player.ascensionCount / 1e9) + i18n: 'SynergismLevel', + stat: () => getLevelReward('wowHyperCubes'), + color: 'green' }, { - i18n: 'Achievement253', - stat: () => 1 + (1 / 10) * player.achievements[253] + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('wowHypercubeGain') }, { - i18n: 'Achievement256', - stat: () => - 1 + Math.min( - 0.15, - (0.6 / 100) * Math.log10(calculateAscensionScore().effectiveScore + 1) - ) * player.achievements[256] + i18n: 'SeasonPass2', + stat: () => 1 + (1.5 * player.shopUpgrades.seasonPass2) / 100 }, { - i18n: 'Achievement265', - stat: () => 1 + Math.min(2, player.ascensionCount / 2.5e10) * player.achievements[265] + i18n: 'WowSquare', + stat: () => getTalismanEffects('wowSquare').oddDimBonus }, { i18n: 'PlatonicCube', - stat: () => G.platonicBonusMultiplier[2] + stat: () => calculateHypercubeMultiplierPlatonicBlessing() }, { i18n: 'Platonic1x3', @@ -548,7 +512,7 @@ export const allHypercubeStats: StatLine[] = [ }, { i18n: 'HyperrealHepteract', - stat: () => 1 + (0.6 / 1000) * hepteractEffective('hyperrealism') + stat: () => getHepteractEffects('hyperrealism').hypercubeMultiplier } ] @@ -562,42 +526,25 @@ export const allPlatonicCubeStats: StatLine[] = [ stat: () => calculateAllCubeMultiplier() }, { - i18n: 'SeasonPass2', - stat: () => 1 + (1.5 * player.shopUpgrades.seasonPass2) / 100 - }, - { - i18n: 'Achievement196', - stat: () => - 1 + Math.min( - 20, - ((player.achievements[196] * 1) / 5000) * Decimal.log(player.ascendShards.add(1), 10) - ) + i18n: 'SynergismLevel', + stat: () => getLevelReward('wowPlatonicCubes'), + color: 'green' }, { - i18n: 'Achievement219-222', - stat: () => - 1 + (4 / 100) * ( - player.achievements[219] - + player.achievements[220] - + player.achievements[221] - ) - + (3 / 100) * player.achievements[222] + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('wowPlatonicGain') }, { - i18n: 'Achievement223', - stat: () => 1 + player.achievements[223] * Math.min(2, player.ascensionCount / 1.337e9) + i18n: 'SeasonPass2', + stat: () => 1 + (1.5 * player.shopUpgrades.seasonPass2) / 100 }, { - i18n: 'Achievement257', - stat: () => - 1 + Math.min( - 0.15, - (0.6 / 100) * Math.log10(calculateAscensionScore().effectiveScore + 1) - ) * player.achievements[257] + i18n: 'WowSquare', + stat: () => getTalismanEffects('wowSquare').evenDimBonus }, { i18n: 'PlatonicCube', - stat: () => G.platonicBonusMultiplier[3] + stat: () => calculatePlatonicMultiplierPlatonicBlessing() }, { i18n: 'Platonic1x4', @@ -615,32 +562,21 @@ export const allHepteractCubeStats: StatLine[] = [ stat: () => calculateAllCubeMultiplier() }, { - i18n: 'SeasonPass3', - stat: () => 1 + (1.5 * player.shopUpgrades.seasonPass3) / 100 - }, - { - i18n: 'Achievement258', - stat: () => - 1 + Math.min( - 0.15, - (0.6 / 100) * Math.log10(calculateAscensionScore().effectiveScore + 1) - ) * player.achievements[258] + i18n: 'SynergismLevel', + stat: () => getLevelReward('wowHepteractCubes'), + color: 'green' }, { - i18n: 'Achievement264', - stat: () => 1 + Math.min(0.4, player.ascensionCount / 2e13) * player.achievements[264] + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('wowHepteractGain') }, { - i18n: 'Achievement265', - stat: () => 1 + Math.min(0.2, player.ascensionCount / 8e14) * player.achievements[265] + i18n: 'SeasonPass3', + stat: () => 1 + (1.5 * player.shopUpgrades.seasonPass3) / 100 }, { - i18n: 'Achievement270', - stat: () => - Math.min( - 2, - 1 + (1 / 1000000) * Decimal.log(player.ascendShards.add(1), 10) * player.achievements[270] - ) + i18n: 'WowSquare', + stat: () => getTalismanEffects('wowSquare').oddDimBonus } ] @@ -662,6 +598,11 @@ export const allOcteractCubeStats: StatLine[] = [ stat: () => PCoinUpgradeEffects.CUBE_BUFF, color: 'gold' }, + { + i18n: 'SynergismLevel', + stat: () => getLevelReward('wowOcteracts'), + color: 'green' + }, { i18n: 'Campaign', stat: () => player.campaigns.octeractBonus @@ -682,61 +623,65 @@ export const allOcteractCubeStats: StatLine[] = [ i18n: 'SeasonPassLost', stat: () => 1 + player.shopUpgrades.seasonPassLost / 1000 }, + { + i18n: 'WowSquare', + stat: () => getTalismanEffects('wowSquare').evenDimBonus + }, { i18n: 'CookieUpgrade20', stat: () => 1 + (+(player.corruptions.used.totalLevels >= 14 * 8) * player.cubeUpgrades[70]) / 10000 }, { i18n: 'DivinePack', - stat: () => 1 + +(player.corruptions.used.totalLevels) * +player.singularityUpgrades.divinePack.getEffect().bonus + stat: () => getGQUpgradeEffect('divinePack') }, { i18n: 'SingCubes1', - stat: () => +player.singularityUpgrades.singCubes1.getEffect().bonus + stat: () => getGQUpgradeEffect('singCubes1') }, { i18n: 'SingCubes2', - stat: () => +player.singularityUpgrades.singCubes2.getEffect().bonus + stat: () => getGQUpgradeEffect('singCubes2') }, { i18n: 'SingCubes3', - stat: () => +player.singularityUpgrades.singCubes3.getEffect().bonus + stat: () => getGQUpgradeEffect('singCubes3') }, { i18n: 'SingOcteractGain', - stat: () => +player.singularityUpgrades.singOcteractGain.getEffect().bonus + stat: () => getGQUpgradeEffect('singOcteractGain') }, { i18n: 'SingOcteractGain2', - stat: () => +player.singularityUpgrades.singOcteractGain2.getEffect().bonus + stat: () => getGQUpgradeEffect('singOcteractGain2') }, { i18n: 'SingOcteractGain3', - stat: () => +player.singularityUpgrades.singOcteractGain3.getEffect().bonus + stat: () => getGQUpgradeEffect('singOcteractGain3') }, { i18n: 'SingOcteractGain4', - stat: () => +player.singularityUpgrades.singOcteractGain4.getEffect().bonus + stat: () => getGQUpgradeEffect('singOcteractGain4') }, { i18n: 'SingOcteractGain5', - stat: () => +player.singularityUpgrades.singOcteractGain5.getEffect().bonus + stat: () => getGQUpgradeEffect('singOcteractGain5') }, { i18n: 'PatreonBonus', - stat: () => 1 + (getQuarkBonus() / 100) * +player.singularityUpgrades.singOcteractPatreonBonus.getEffect().bonus + stat: () => 1 + (getQuarkBonus() / 100) * getGQUpgradeEffect('singOcteractPatreonBonus') }, { i18n: 'OcteractStarter', - stat: () => 1 + 0.2 * +player.octeractUpgrades.octeractStarter.getEffect().bonus + stat: () => getOcteractUpgradeEffect('octeractStarter') }, { i18n: 'OcteractGain', - stat: () => +player.octeractUpgrades.octeractGain.getEffect().bonus + stat: () => getOcteractUpgradeEffect('octeractGain') }, { i18n: 'OcteractGain2', - stat: () => +player.octeractUpgrades.octeractGain2.getEffect().bonus + stat: () => getOcteractUpgradeEffect('octeractGain2') }, { i18n: 'DerpsmithCornucopia', @@ -746,7 +691,7 @@ export const allOcteractCubeStats: StatLine[] = [ i18n: 'DigitalOcteractAccumulator', stat: () => Math.pow( - 1 + +player.octeractUpgrades.octeractAscensionsOcteractGain.getEffect().bonus, + 1 + getOcteractUpgradeEffect('octeractAscensionsOcteractGain'), 1 + Math.floor(Math.log10(1 + player.ascensionCount)) ) }, @@ -757,7 +702,7 @@ export const allOcteractCubeStats: StatLine[] = [ { i18n: 'PlatonicDelta', stat: () => - 1 + +player.singularityUpgrades.platonicDelta.getEffect().bonus + 1 + getGQUpgradeEffect('platonicDelta') * Math.min( 9, (player.shopUpgrades.shopSingularitySpeedup > 0) @@ -779,31 +724,31 @@ export const allOcteractCubeStats: StatLine[] = [ }, { i18n: 'ModuleTutorial', - stat: () => +player.blueberryUpgrades.ambrosiaTutorial.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaTutorial').cubes }, { i18n: 'ModuleCubes1', - stat: () => +player.blueberryUpgrades.ambrosiaCubes1.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaCubes1').cubes }, { i18n: 'ModuleLuckCube1', - stat: () => +player.blueberryUpgrades.ambrosiaLuckCube1.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaLuckCube1').cubes }, { i18n: 'ModuleQuarkCube1', - stat: () => +player.blueberryUpgrades.ambrosiaQuarkCube1.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaQuarkCube1').cubes }, { i18n: 'ModuleCubes2', - stat: () => +player.blueberryUpgrades.ambrosiaCubes2.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaCubes2').cubes }, { i18n: 'ModuleCubes3', - stat: () => +player.blueberryUpgrades.ambrosiaCubes3.bonus.cubes + stat: () => getAmbrosiaUpgradeEffects('ambrosiaCubes3').cubes }, { i18n: 'RedAmbrosiaTutorial', - stat: () => getRedAmbrosiaUpgrade('tutorial').bonus.cubeMult + stat: () => getRedAmbrosiaUpgradeEffects('tutorial').cubeMult }, { i18n: 'RedAmbrosia', @@ -820,10 +765,10 @@ export const allOcteractCubeStats: StatLine[] = [ { i18n: 'AscensionSpeed', stat: () => { - const ascensionSpeed = player.singularityUpgrades.oneMind.getEffect().bonus + const ascensionSpeed = getGQUpgradeEffect('oneMind') ? Math.pow(10, 1 / 2) * Math.pow( calculateAscensionSpeedMult() / 10, - +player.octeractUpgrades.octeractOneMindImprover.getEffect().bonus + getOcteractUpgradeEffect('octeractOneMindImprover') ) : Math.pow(calculateAscensionSpeedMult(), 1 / 2) return ascensionSpeed @@ -834,7 +779,7 @@ export const allOcteractCubeStats: StatLine[] = [ export const allBaseOfferingStats: StatLine[] = [ { i18n: 'Base', - stat: () => 6 // Absolute Base + stat: () => 1 // Absolute Base }, { i18n: 'PseudoCoins', @@ -853,14 +798,6 @@ export const allBaseOfferingStats: StatLine[] = [ i18n: 'Reincarnate', stat: () => player.reincarnationCount > 0 ? 5 : 0 // Reincarnated }, - { - i18n: 'Achievements', - stat: () => - Math.min(player.prestigecounter / 1800, 1) - * ((player.achievements[37] > 0 ? 15 : 0) - + (player.achievements[44] > 0 ? 15 : 0) - + (player.achievements[52] > 0 ? 25 : 0)) // Achievements 37, 44, 52 (Based on Prestige Timer) - }, { i18n: 'Challenge1', stat: () => (player.challengecompletions[2] > 0) ? 2 : 0 // Challenge 2x1 @@ -871,7 +808,7 @@ export const allBaseOfferingStats: StatLine[] = [ }, { i18n: 'ReincarnationUpgrade2', - stat: () => (player.upgrades[62] > 0) ? Math.min(50, (1 / 50) * sumContents(player.challengecompletions)) : 0 // Reincarnation Upgrade 2 + stat: () => (player.upgrades[62] > 0) ? Math.min(12, (1 / 50) * sumContents(player.challengecompletions)) : 0 // Reincarnation Upgrade 2 }, { i18n: 'Research1x24', @@ -887,11 +824,11 @@ export const allBaseOfferingStats: StatLine[] = [ }, { i18n: 'AmbrosiaBaseOffering1', - stat: () => +player.blueberryUpgrades.ambrosiaBaseOffering1.bonus.offering // Ambrosia Base Offering 1 + stat: () => getAmbrosiaUpgradeEffects('ambrosiaBaseOffering1').offering // Ambrosia Base Offering 1 }, { i18n: 'AmbrosiaBaseOffering2', - stat: () => +player.blueberryUpgrades.ambrosiaBaseOffering2.bonus.offering // Ambrosia Base Offering 2 + stat: () => getAmbrosiaUpgradeEffects('ambrosiaBaseOffering2').offering // Ambrosia Base Offering 2 }, { i18n: 'OfferingEX3', @@ -906,11 +843,19 @@ export const allOfferingStats = [ }, { i18n: 'PrestigeShards', - stat: () => 1 + Math.pow(Decimal.log(player.prestigeShards.add(1), 10), 1 / 2) / 5 // Prestige Shards + stat: () => 1 + Math.pow(Decimal.log(player.prestigeShards.add(1), 10), 1 / 2) / 5 + }, + { + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('offeringBonus') + }, + { + i18n: 'SynergismLevel', + stat: () => getLevelReward('offerings') // Synergism Level }, { i18n: 'SuperiorIntellect', - stat: () => 1 + (1 / 2000) * G.rune5level * G.effectiveLevelMult // Superior Intellect Rune + stat: () => getRuneEffects('superiorIntellect').offeringMult // Superior Intellect Rune }, { i18n: 'ReincarnationChallenge', @@ -919,25 +864,16 @@ export const allOfferingStats = [ + 1 / 25 * CalcECC('reincarnation', player.challengecompletions[8]) + 1 / 25 * CalcECC('reincarnation', player.challengecompletions[10]) // Reincarnation Challenges }, - { - i18n: 'AlchemyAchievement5', - stat: () => 1 + (10 * player.achievements[33]) / 100 // Alchemy Achievement 5 - }, - { - i18n: 'AlchemyAchievement6', - stat: () => 1 + (15 * player.achievements[34]) / 100 // Alchemy Achievement 6 - }, - { - i18n: 'AlchemyAchievement7', - stat: () => 1 + (25 * player.achievements[35]) / 100 // Alchemy Achievement 7 - }, { i18n: 'DiamondUpgrade4x3', stat: () => 1 + (20 * player.upgrades[38]) / 100 // Diamond Upgrade 4x3 }, { i18n: 'ParticleUpgrade3x5', - stat: () => 1 + player.upgrades[75] * 2 * Math.min(1, Math.pow(player.maxobtainium / 30000000, 0.5)) // Particle Upgrade 3x5 + stat: () => + 1 + + player.upgrades[75] * 2 + * Math.min(1, Math.pow(Decimal.min(player.maxObtainium, 1e10).toNumber() / 30000000, 0.5)) // Particle Upgrade 3x5 }, { i18n: 'AutoOfferingShop', @@ -961,7 +897,7 @@ export const allOfferingStats = [ }, { i18n: 'Brutus', - stat: () => G.cubeBonusMultiplier[3] // Brutus + stat: () => calculateOfferingCubeBlessing() // Cube Blessing }, { i18n: 'ConstantUpgrade3', @@ -970,8 +906,8 @@ export const allOfferingStats = [ { i18n: 'ResearchTalismans', stat: () => - 1 + 0.0003 * player.talismanLevels[3 - 1] * player.researches[149] - + 0.0004 * player.talismanLevels[3 - 1] * player.researches[179] // Research 6x24,8x4 + 1 + 0.0003 * talismans.midas.level * player.researches[149] + + 0.0004 * talismans.midas.level * player.researches[179] // Research 6x24,8x4 }, { i18n: 'TutorialBonus', @@ -986,16 +922,12 @@ export const allOfferingStats = [ stat: () => 1 + 0.12 * CalcECC('ascension', player.challengecompletions[12]) // Challenge 12 }, { - i18n: 'Research8x25', - stat: () => 1 + (0.01 / 100) * player.researches[200] // Research 8x25 - }, - { - i18n: 'AscensionAchievement', - stat: () => 1 + Math.min(1, player.ascensionCount / 1e6) * player.achievements[187] // Ascension Count Achievement + i18n: 'ThriftSpirit', + stat: () => getRuneSpiritEffect('thrift').offerings // Thrift }, { - i18n: 'SunMoonAchievements', - stat: () => 1 + 0.6 * player.achievements[250] + 1 * player.achievements[251] // Sun&Moon Achievements + i18n: 'Research8x25', + stat: () => 1 + (0.01 / 100) * player.researches[200] // Research 8x25 }, { i18n: 'CubeUpgrade5x6', @@ -1021,6 +953,10 @@ export const allOfferingStats = [ i18n: 'Challenge15', stat: () => G.challenge15Rewards.offering.value // C15 Reward }, + { + i18n: 'Antiquities', + stat: () => Math.pow(10, getRuneEffects('antiquities').offeringLog10) // Antiquities Rune + }, { i18n: 'SingularityDebuff', stat: () => 1 / calculateSingularityDebuff('Offering'), // Singularity Debuff @@ -1028,27 +964,27 @@ export const allOfferingStats = [ }, { i18n: 'StarterPack', - stat: () => 1 + 5 * (player.singularityUpgrades.starterPack.getEffect().bonus ? 1 : 0) // Starter Pack Upgrade + stat: () => 1 + 5 * getGQUpgradeEffect('starterPack') // Starter Pack Upgrade }, { i18n: 'OfferingCharge', - stat: () => +player.singularityUpgrades.singOfferings1.getEffect().bonus // Offering Charge GQ Upgrade + stat: () => getGQUpgradeEffect('singOfferings1') // Offering Charge GQ Upgrade }, { i18n: 'OfferingStorm', - stat: () => +player.singularityUpgrades.singOfferings2.getEffect().bonus // Offering Storm GQ Upgrade + stat: () => getGQUpgradeEffect('singOfferings2') // Offering Storm GQ Upgrade }, { i18n: 'OfferingTempest', - stat: () => +player.singularityUpgrades.singOfferings3.getEffect().bonus // Offering Tempest GQ Upgrade + stat: () => getGQUpgradeEffect('singOfferings3') // Offering Tempest GQ Upgrade }, { i18n: 'Citadel', - stat: () => +player.singularityUpgrades.singCitadel.getEffect().bonus // Citadel GQ Upgrade + stat: () => getGQUpgradeEffect('singCitadel') // Citadel GQ Upgrade }, { i18n: 'Citadel2', - stat: () => +player.singularityUpgrades.singCitadel2.getEffect().bonus // Citadel 2 GQ Upgrade + stat: () => getGQUpgradeEffect('singCitadel2') // Citadel 2 GQ Upgrade }, { i18n: 'CubeUpgradeCx4', @@ -1060,7 +996,7 @@ export const allOfferingStats = [ }, { i18n: 'OcteractElectrolosis', - stat: () => +player.octeractUpgrades.octeractOfferings1.getEffect().bonus // Offering Electrolosis OC Upgrade + stat: () => getOcteractUpgradeEffect('octeractOfferings1') // Offering Electrolosis OC Upgrade }, { i18n: 'OcteractBonus', @@ -1068,11 +1004,11 @@ export const allOfferingStats = [ }, { i18n: 'Ambrosia', - stat: () => 1 + 0.001 * +player.blueberryUpgrades.ambrosiaOffering1.bonus.offeringMult // Ambrosia!! + stat: () => 1 + 0.001 * getAmbrosiaUpgradeEffects('ambrosiaOffering1').offeringMult // Ambrosia!! }, { i18n: 'RedAmbrosiaTutorial', - stat: () => getRedAmbrosiaUpgrade('tutorial').bonus.offeringMult // Red Ambrosia Tutorial + stat: () => getRedAmbrosiaUpgradeEffects('tutorial').offeringMult // Red Ambrosia Tutorial }, { i18n: 'RedAmbrosia', @@ -1080,7 +1016,7 @@ export const allOfferingStats = [ }, { i18n: 'CubeUpgradeCx22', - stat: () => Math.pow(1.04, player.cubeUpgrades[72] * sumContents(player.talismanRarity)) // Cube upgrade 8x2 (Cx22) + stat: () => Math.pow(1.04, player.cubeUpgrades[72] * sumOfTalismanRarities()) // Cube upgrade 8x2 (Cx22) }, { i18n: 'CashGrab2', @@ -1106,6 +1042,17 @@ export const allOfferingStats = [ : 1, // Singularity Speedrun Penalty color: 'red' }, + { + i18n: 'TaxmanDebuff', + stat: () => { + if (!player.singularityChallenges.taxmanLastStand.enabled) { + return 1 + } + const obtainiumDigits = Math.floor(1 + Math.max(0, Decimal.log(player.obtainium, 10))) + return Math.pow(2.5, -Math.min(500, obtainiumDigits)) // Taxman Debuff + }, + color: 'red' + }, { i18n: 'Event', stat: () => 1 + calculateEventBuff(BuffType.Offering), // Event @@ -1113,22 +1060,134 @@ export const allOfferingStats = [ } ] -export const allQuarkStats: StatLine[] = [ +export const firstFiveRuneEffectivenessStats: StatLine[] = [ + { + i18n: 'Research1x4', + stat: () => 1 + player.researches[4] / 10 * CalcECC('ascension', player.challengecompletions[14]), + displayCriterion: () => { + const reincarnationCount = player.reincarnationCount + const singularity = player.highestSingularityCount + return reincarnationCount >= 1 || singularity >= 1 + } + }, + { + i18n: 'Research2x6', + stat: () => 1 + player.researches[21] / 100, + displayCriterion: () => { + const reincarnationCount = player.reincarnationCount + const singularity = player.highestSingularityCount + return reincarnationCount >= 1 || singularity >= 1 + } + }, + { + i18n: 'Research4x15', + stat: () => 1 + player.researches[90] / 100, + displayCriterion: () => { + const chal8 = player.highestchallengecompletions[8] + const singularity = player.highestSingularityCount + return chal8 >= 1 || singularity >= 1 + } + }, + { + i18n: 'Research6x6', + stat: () => 1 + player.researches[131] / 200, + displayCriterion: () => { + const ascCount = player.ascensionCount + const singularity = player.highestSingularityCount + return ascCount >= 1 || singularity >= 1 + } + }, + { + i18n: 'Research6x21', + stat: () => 1 + ((player.researches[146] / 200) * 4) / 5, + displayCriterion: () => { + const chal11 = player.highestchallengecompletions[11] + const singularity = player.highestSingularityCount + return chal11 >= 1 || singularity >= 1 + } + }, + { + i18n: 'Research7x11', + stat: () => 1 + ((player.researches[161] / 200) * 3) / 5, + displayCriterion: () => { + const chal12 = player.highestchallengecompletions[12] + const singularity = player.highestSingularityCount + return chal12 >= 1 || singularity >= 1 + } + }, { - i18n: 'AchievementPoints', - stat: () => 1 + player.achievementPoints / 50000 + i18n: 'Research8x1', + stat: () => 1 + ((player.researches[176] / 200) * 2) / 5, + displayCriterion: () => { + const chal13 = player.highestchallengecompletions[13] + const singularity = player.highestSingularityCount + return chal13 >= 1 || singularity >= 1 + } + }, + { + i18n: 'Research8x16', + stat: () => 1 + ((player.researches[191] / 200) * 1) / 5, + displayCriterion: () => { + const chal14 = player.highestchallengecompletions[14] + const singularity = player.highestSingularityCount + return chal14 >= 1 || singularity >= 1 + } + }, + { + i18n: 'ConstantUpgrade9', + stat: () => + 1 + 0.01 * Decimal.log(player.talismanShards.add(1), 4) + * Math.min(1, player.constantUpgrades[9]), + displayCriterion: () => { + const ascCount = player.ascensionCount + const singularity = player.highestSingularityCount + return ascCount >= 1 || singularity >= 1 + } + }, + { + i18n: 'Challenge15', + stat: () => G.challenge15Rewards.runeBonus.value, + displayCriterion: () => { + const chal14 = player.highestchallengecompletions[14] + const singularity = player.highestSingularityCount + return chal14 >= 1 || singularity >= 1 + } }, { - i18n: 'Achievement250', - stat: () => player.achievements[250] > 0 ? 1.05 : 1 + i18n: 'MidasTribute', + stat: () => calculateRuneEffectivenessCubeBlessing(), + displayCriterion: () => { + const ascCount = player.ascensionCount + const singularity = player.highestSingularityCount + return ascCount >= 1 || singularity >= 1 + } + } +] + +export const runeEffectivenessStatsSI: StatLine[] = [ + { + i18n: 'Research4x9', + stat: () => 1 + player.researches[84] / 200, + displayCriterion: () => { + const chal8 = player.highestchallengecompletions[8] + const singularity = player.highestSingularityCount + return chal8 >= 1 || singularity >= 1 + } + } +] + +export const allQuarkStats: StatLine[] = [ + { + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('quarkGain') }, { - i18n: 'Achievement251', - stat: () => player.achievements[251] > 0 ? 1.05 : 1 + i18n: 'SynergismLevel', + stat: () => getLevelReward('quarks') }, { - i18n: 'Achievement266', - stat: () => player.achievements[266] > 0 ? 1 + Math.min(0.1, player.ascensionCount / 1e16) : 1 + i18n: 'PlasticTalisman', + stat: () => getTalismanEffects('plastic').quarkBonus }, { i18n: 'PlatonicALPHA', @@ -1153,13 +1212,13 @@ export const allQuarkStats: StatLine[] = [ }, { i18n: 'InfiniteAscent', - stat: () => isIARuneUnlocked() ? 1.1 + (5 / 1300) * calculateEffectiveIALevel() : 1 + stat: () => isIARuneUnlocked() ? getRuneEffects('infiniteAscent').quarkMult : 1 }, { i18n: 'QuarkHepteract', stat: () => player.challenge15Exponent >= G.challenge15Rewards.hepteractsUnlocked.requirement - ? 1 + 5 / 10000 * hepteractEffective('quark') + ? getHepteractEffects('quark').quarkMultiplier : 1 }, { @@ -1182,36 +1241,43 @@ export const allQuarkStats: StatLine[] = [ i18n: 'SingularityMilestones', stat: () => calculateSingularityQuarkMilestoneMultiplier() }, + { + i18n: 'skrauQ', + stat: () => + (player.highestSingularityCount >= 200) + ? Math.max(1, Math.pow((player.singularityCount - 179) / 20, 2)) + : 1 + }, { i18n: 'OcteractQuarkBonus', stat: () => calculateTotalOcteractQuarkBonus() }, { i18n: 'OcteractStarter', - stat: () => +player.octeractUpgrades.octeractStarter.getEffect().bonus + stat: () => 1 + (octeractUpgrades.octeractStarter.level > 0 ? 0.15 : 0) }, { i18n: 'OcteractQuarkGain', - stat: () => +player.octeractUpgrades.octeractQuarkGain.getEffect().bonus + stat: () => getOcteractUpgradeEffect('octeractQuarkGain') }, { i18n: 'OcteractQuarkGain2', stat: () => 1 - + (1 / 10000) * Math.floor(player.octeractUpgrades.octeractQuarkGain.level / 111) - * player.octeractUpgrades.octeractQuarkGain2.level - * Math.floor(1 + Math.log10(Math.max(1, player.hepteractCrafts.quark.BAL))) + + (1 / 10000) * Math.floor(octeractUpgrades.octeractQuarkGain.level / 111) + * octeractUpgrades.octeractQuarkGain2.level + * Math.floor(1 + Math.log10(Math.max(1, hepteracts.quark.BAL))) }, { i18n: 'SingularityPacks', stat: () => - 1 + 0.02 * player.singularityUpgrades.intermediatePack.level - + 0.04 * player.singularityUpgrades.advancedPack.level + 0.06 * player.singularityUpgrades.expertPack.level - + 0.08 * player.singularityUpgrades.masterPack.level + 0.1 * player.singularityUpgrades.divinePack.level + 1 + 0.02 * getGQUpgradeEffect('intermediatePack') + + 0.04 * getGQUpgradeEffect('advancedPack') + 0.06 * getGQUpgradeEffect('expertPack') + + 0.08 * getGQUpgradeEffect('expertPack') + 0.1 * goldenQuarkUpgrades.divinePack.level }, { i18n: 'SingQuarkImprover1', - stat: () => +player.singularityUpgrades.singQuarkImprover1.getEffect().bonus + stat: () => getGQUpgradeEffect('singQuarkImprover1') }, { i18n: 'AmbrosiaQuarkMult', @@ -1219,31 +1285,31 @@ export const allQuarkStats: StatLine[] = [ }, { i18n: 'AmbrosiaTutorial', - stat: () => +player.blueberryUpgrades.ambrosiaTutorial.bonus.quarks + stat: () => getAmbrosiaUpgradeEffects('ambrosiaTutorial').quarks }, { i18n: 'AmbrosiaQuarks1', - stat: () => +player.blueberryUpgrades.ambrosiaQuarks1.bonus.quarks + stat: () => getAmbrosiaUpgradeEffects('ambrosiaQuarks1').quarks }, { i18n: 'AmbrosiaCubeQuark1', - stat: () => +player.blueberryUpgrades.ambrosiaCubeQuark1.bonus.quarks + stat: () => getAmbrosiaUpgradeEffects('ambrosiaCubeQuark1').quarks }, { i18n: 'AmbrosiaLuckQuark1', - stat: () => +player.blueberryUpgrades.ambrosiaLuckQuark1.bonus.quarks + stat: () => getAmbrosiaUpgradeEffects('ambrosiaLuckQuark1').quarks }, { i18n: 'AmbrosiaQuarks2', - stat: () => +player.blueberryUpgrades.ambrosiaQuarks2.bonus.quarks + stat: () => getAmbrosiaUpgradeEffects('ambrosiaQuarks2').quarks }, { i18n: 'AmbrosiaQuarks3', - stat: () => +player.blueberryUpgrades.ambrosiaQuarks3.bonus.quarks + stat: () => getAmbrosiaUpgradeEffects('ambrosiaQuarks3').quarks }, { i18n: 'Viscount', - stat: () => getRedAmbrosiaUpgrade('viscount').bonus.quarkBonus, + stat: () => getRedAmbrosiaUpgradeEffects('viscount').quarkBonus, color: 'red' }, { @@ -1286,21 +1352,17 @@ export const allBaseObtainiumStats: StatLine[] = [ stat: () => PCoinUpgradeEffects.BASE_OBTAINIUM_BUFF, // PseudoCoin Upgrade color: 'gold' }, - { - i18n: 'Achievement51', - stat: () => (player.achievements[51] > 0) ? 4 : 0 // Achievement 51 - }, { i18n: 'ShopPotionBonus', stat: () => calculateObtainiumPotionBaseObtainium().amount // Potion Permanent Bonus }, { i18n: 'Research3x13', - stat: () => player.researches[63] // Research 3x13 + stat: () => (player.reincarnationcounter >= 2) ? player.researches[63] : 0 // Research 3x13 }, { i18n: 'Research3x14', - stat: () => 2 * player.researches[64] // Research 3x14 + stat: () => (player.reincarnationcounter >= 5) ? 2 * player.researches[64] : 0 // Research 3x14 }, { i18n: 'FirstSingularity', @@ -1312,11 +1374,11 @@ export const allBaseObtainiumStats: StatLine[] = [ }, { i18n: 'AmbrosiaBaseObtainium1', - stat: () => +player.blueberryUpgrades.ambrosiaBaseObtainium1.bonus.obtainium // Ambrosia Base Obtainium 1 + stat: () => getAmbrosiaUpgradeEffects('ambrosiaBaseObtainium1').obtainium // Ambrosia Base Obtainium 1 }, { i18n: 'AmbrosiaBaseObtainium2', - stat: () => +player.blueberryUpgrades.ambrosiaBaseObtainium2.bonus.obtainium // Ambrosia Base Obtainium 2 + stat: () => getAmbrosiaUpgradeEffects('ambrosiaBaseObtainium2').obtainium // Ambrosia Base Obtainium 2 } ] @@ -1361,6 +1423,10 @@ export const allObtainiumIgnoreDRStats: StatLine[] = [ i18n: 'PlatonicOMEGA', stat: () => 1 + 5 * player.platonicUpgrades[15] // Platonic OMEGA }, + { + i18n: 'Antiquities', + stat: () => Math.pow(10, getRuneEffects('antiquities').obtainiumLog10) // Antiquities Rune + }, { i18n: 'CubeUpgradeCx5', stat: () => 1 + player.cubeUpgrades[55] / 100 // Cube Upgrade 6x5 (Cx5) @@ -1372,7 +1438,7 @@ export const allObtainiumIgnoreDRStats: StatLine[] = [ }, { i18n: 'RedAmbrosiaTutorial', - stat: () => getRedAmbrosiaUpgrade('tutorial').bonus.obtainiumMult // Red Ambrosia Tutorial + stat: () => getRedAmbrosiaUpgradeEffects('tutorial').obtainiumMult // Red Ambrosia Tutorial }, { i18n: 'RedAmbrosia', @@ -1380,7 +1446,7 @@ export const allObtainiumIgnoreDRStats: StatLine[] = [ }, { i18n: 'CubeUpgradeCx21', - stat: () => Math.pow(1.04, player.cubeUpgrades[71] * sumContents(player.talismanRarity)) // Cube Upgrade 8x1 + stat: () => Math.pow(1.04, player.cubeUpgrades[71] * sumOfTalismanRarities()) // Cube Upgrade 8x1 }, { i18n: 'ObtainiumEX3', @@ -1405,7 +1471,15 @@ export const allObtainiumIgnoreDRStats: StatLine[] = [ export const allObtainiumStats: StatLine[] = [ { i18n: 'TranscendShards', - stat: () => Math.pow(Decimal.log(player.transcendShards.add(1), 10) / 300, 2) // Transcend Shards + stat: () => Math.max(1, Math.pow(Decimal.log(player.transcendShards.add(1), 10) / 300, 2)) // Transcend Shards + }, + { + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('obtainiumBonus') // Achievement Bonus + }, + { + i18n: 'SynergismLevel', + stat: () => getLevelReward('obtainium') // Synergism Level }, { i18n: 'ReincarnationUpgrade9', @@ -1421,7 +1495,10 @@ export const allObtainiumStats: StatLine[] = [ }, { i18n: 'ReincarnationUpgrade14', - stat: () => (player.upgrades[74] > 0) ? 1 + 4 * Math.min(1, Math.pow(player.maxofferings / 100000, 0.5)) : 1 // Reincarnation Upgrade 14 + stat: () => + (player.upgrades[74] > 0) + ? 1 + 4 * Math.min(1, Math.pow(Decimal.min(player.maxOfferings, 1e10).toNumber() / 100000, 0.5)) + : 1 // Reincarnation Upgrade 14 }, { i18n: 'Research3x15', @@ -1449,48 +1526,15 @@ export const allObtainiumStats: StatLine[] = [ }, { i18n: 'Rune5', - stat: () => - 1 - + (G.rune5level / 200) * G.effectiveLevelMult - * (1 - + (player.researches[84] / 200) - * (1 + G.effectiveRuneSpiritPower[5] * player.corruptions.used.totalCorruptionDifficultyMultiplier)) // Rune 5 - }, - { - i18n: 'ChallengeAchievements', - stat: () => - 1 + 0.01 * player.achievements[84] + 0.03 * player.achievements[91] + 0.05 * player.achievements[98] - + 0.07 * player.achievements[105] + 0.09 * player.achievements[112] + 0.11 * player.achievements[119] - + 0.13 * player.achievements[126] + 0.15 * player.achievements[133] + 0.17 * player.achievements[140] - + 0.19 * player.achievements[147] // Challenge Achievements + stat: () => getRuneEffects('superiorIntellect').obtainiumMult }, { i18n: 'Ant10', stat: () => 1 + 2 * Math.pow((player.antUpgrades[10 - 1]! + G.bonusant10) / 50, 2 / 3) // Ant 10 }, - { - i18n: 'Achievement53', - stat: () => (player.achievements[53] > 0) ? 1 + G.runeSum / 800 : 1 // Achievement 53 - }, - { - i18n: 'Achievement128', - stat: () => (player.achievements[128] > 0) ? 1.5 : 1 // Achievement 128 - }, - { - i18n: 'Achievement129', - stat: () => (player.achievements[129] > 0) ? 1.25 : 1 // Achievement 129 - }, - { - i18n: 'Achievement188', - stat: () => (player.achievements[188] > 0) ? 1 + Math.min(2, player.ascensionCount / 5e6) : 1 // Achievement 188 - }, - { - i18n: 'Achievement250_251', - stat: () => 1 + 0.6 * player.achievements[250] + player.achievements[251] // Achievement 250, 251 - }, { i18n: 'CubeBonus', - stat: () => G.cubeBonusMultiplier[5] // Cube Bonus + stat: () => calculateObtainiumCubeBlessing() // Cube Blessing }, { i18n: 'ConstantUpgrade4', @@ -1510,11 +1554,11 @@ export const allObtainiumStats: StatLine[] = [ }, { i18n: 'SpiritPower', - stat: () => 1 + player.corruptions.used.totalCorruptionDifficultyMultiplier * G.effectiveRuneSpiritPower[4] // 4th Spirit + stat: () => getRuneSpiritEffect('superiorIntellect').obtainium // Spirit Power }, { i18n: 'Research6x19', - stat: () => 1 + ((0.03 * Math.log(player.uncommonFragments + 1)) / Math.log(4)) * player.researches[144] // Research 6x19 + stat: () => 1 + (0.03 * Decimal.log(player.uncommonFragments.add(1), 4)) * player.researches[144] // Research 6x19 }, { i18n: 'CubeUpgrade5x10', @@ -1522,27 +1566,27 @@ export const allObtainiumStats: StatLine[] = [ }, { i18n: 'StarterPack', - stat: () => 1 + 5 * (player.singularityUpgrades.starterPack.getEffect().bonus ? 1 : 0) // Starter Pack + stat: () => 1 + 5 * getGQUpgradeEffect('starterPack') // Starter Pack }, { i18n: 'SingObtainium1', - stat: () => +player.singularityUpgrades.singObtainium1.getEffect().bonus // Obtainium GQ Upgrade 1 + stat: () => getGQUpgradeEffect('singObtainium1') // Obtainium GQ Upgrade 1 }, { i18n: 'SingObtainium2', - stat: () => +player.singularityUpgrades.singObtainium2.getEffect().bonus // Obtainium GQ Upgrade 2 + stat: () => getGQUpgradeEffect('singObtainium2') // Obtainium GQ Upgrade 2 }, { i18n: 'SingObtainium3', - stat: () => +player.singularityUpgrades.singObtainium3.getEffect().bonus // Obtainium GQ Upgrade 3 + stat: () => getGQUpgradeEffect('singObtainium3') // Obtainium GQ Upgrade 3 }, { i18n: 'SingCitadel', - stat: () => +player.singularityUpgrades.singCitadel.getEffect().bonus // Singularity Citadel 1 + stat: () => getGQUpgradeEffect('singCitadel') // Singularity Citadel 1 }, { i18n: 'SingCitadel2', - stat: () => +player.singularityUpgrades.singCitadel2.getEffect().bonus // Singularity Citadel 2 + stat: () => getGQUpgradeEffect('singCitadel2') // Singularity Citadel 2 }, { i18n: 'ShopCashGrab2', @@ -1562,11 +1606,11 @@ export const allObtainiumStats: StatLine[] = [ }, { i18n: 'OcteractObtainium1', - stat: () => +player.octeractUpgrades.octeractObtainium1.getEffect().bonus // Octeract Obtainium 1 + stat: () => getOcteractUpgradeEffect('octeractObtainium1') // Octeract Obtainium 1 }, { i18n: 'AmbrosiaObtainium1', - stat: () => 1 + 0.001 * +player.blueberryUpgrades.ambrosiaObtainium1.bonus.obtainiumMult // Ambrosia Obtainium 1 + stat: () => 1 + 0.001 * getAmbrosiaUpgradeEffects('ambrosiaObtainium1').obtainiumMult // Ambrosia Obtainium 1 }, { i18n: 'EXUltraObtainium', @@ -1581,6 +1625,17 @@ export const allObtainiumStats: StatLine[] = [ i18n: 'SingularityDebuff', stat: () => 1 / calculateSingularityDebuff('Obtainium'), // Singularity Debuff color: 'red' + }, + { + i18n: 'TaxmanDebuff', + stat: () => { + if (!player.singularityChallenges.taxmanLastStand.enabled) { + return 1 + } + const offeringDigits = Math.floor(1 + Math.max(0, Decimal.log(player.offerings, 10))) + return Math.pow(2.5, -Math.min(500, offeringDigits)) // Taxman Debuff + }, + color: 'red' } ] @@ -1611,12 +1666,16 @@ export const offeringObtainiumTimeModifiers = (time: number, timeMultCheck: bool }, { i18n: 'HalfMind', - stat: () => (player.singularityUpgrades.halfMind.getEffect().bonus) ? calculateGlobalSpeedMult() / 10 : 1 + stat: () => getGQUpgradeEffect('halfMind') ? calculateGlobalSpeedMult() / 10 : 1 } ] } export const antSacrificeRewardStats: StatLine[] = [ + { + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('sacrificeMult') + }, { i18n: 'AntUpgrade11', stat: () => 1 + 2 * (1 - Math.pow(2, -(player.antUpgrades[11 - 1]! + G.bonusant11) / 125)) @@ -1629,17 +1688,9 @@ export const antSacrificeRewardStats: StatLine[] = [ i18n: 'Research104', stat: () => 1 + player.researches[104] / 20 }, - { - i18n: 'Achievement132', - stat: () => player.achievements[132] === 1 ? 1.25 : 1 - }, - { - i18n: 'Achievement137', - stat: () => player.achievements[137] === 1 ? 1.25 : 1 - }, { i18n: 'RuneBlessing', - stat: () => 1 + (20 / 3) * G.effectiveRuneBlessingPower[3] + stat: () => getRuneBlessingEffect('prism').antSacrificeMult // Rune Blessing }, { i18n: 'Challenge10', @@ -1669,10 +1720,6 @@ export const antSacrificeRewardStats: StatLine[] = [ i18n: 'AcceleratorBoostUpgrade', stat: () => 1 + (1 / 4) * player.upgrades[40] }, - { - i18n: 'CubeBlessingAres', - stat: () => G.cubeBonusMultiplier[7] - }, { i18n: 'Event', stat: () => 1 + calculateEventBuff(BuffType.AntSacrifice), @@ -1682,16 +1729,6 @@ export const antSacrificeRewardStats: StatLine[] = [ export const antSacrificeTimeStats = (time: number, timeMultCheck: boolean): StatLine[] => { return [ - { - i18n: 'NoAchievement177', - stat: () => - player.achievements[177] === 0 - ? Math.min( - 1000, - Math.max(1, player.antSacrificeTimer / resetTimeThreshold()) - ) - : 1 - }, { i18n: 'ThresholdPenalty', stat: () => Math.min(1, Math.pow(time / resetTimeThreshold(), 2)), @@ -1703,7 +1740,7 @@ export const antSacrificeTimeStats = (time: number, timeMultCheck: boolean): Sta }, { i18n: 'HalfMind', - stat: () => (player.singularityUpgrades.halfMind.getEffect().bonus) ? calculateGlobalSpeedMult() / 10 : 1 + stat: () => getGQUpgradeEffect('halfMind') ? calculateGlobalSpeedMult() / 10 : 1 } ] } @@ -1712,7 +1749,7 @@ export const antSacrificeTimeStats = (time: number, timeMultCheck: boolean): Sta export const allGlobalSpeedIgnoreDRStats: StatLine[] = [ { i18n: 'ChronosStatue', - stat: () => G.platonicBonusMultiplier[7] // Chronos statue + stat: () => calculateGlobalSpeedPlatonicBlessing() // Chronos statue }, { i18n: 'SingularityDebuff', @@ -1721,11 +1758,11 @@ export const allGlobalSpeedIgnoreDRStats: StatLine[] = [ }, { i18n: 'IntermediatePack', - stat: () => 1 + (player.singularityUpgrades.intermediatePack.getEffect().bonus ? 1 : 0) // Intermediate Pack + stat: () => 1 + getGQUpgradeEffect('intermediatePack') // Intermediate Pack }, { i18n: 'OcteractGlobalSpeed', - stat: () => 1 + +player.octeractUpgrades.octeractImprovedGlobalSpeed.getEffect().bonus * player.singularityCount // Oct Improved Global Speed + stat: () => 1 + getOcteractUpgradeEffect('octeractImprovedGlobalSpeed') * player.singularityCount // Oct Improved Global Speed }, { i18n: 'LimitedTimeChallenge', @@ -1743,9 +1780,13 @@ export const allGlobalSpeedIgnoreDRStats: StatLine[] = [ ] export const allGlobalSpeedStats: StatLine[] = [ + { + i18n: 'SpeedRune', + stat: () => getRuneEffects('speed').globalSpeed // Speed Rune + }, { i18n: 'ObtainiumLog', - stat: () => 1 + (1 / 300) * Math.log10(player.maxobtainium + 1) * player.upgrades[70] // Particle upgrade 2x5 + stat: () => 1 + (1 / 300) * Decimal.log10(player.maxObtainium.plus(1)) * player.upgrades[70] // Particle upgrade 2x5 }, { i18n: 'Research5x21', @@ -1773,15 +1814,16 @@ export const allGlobalSpeedStats: StatLine[] = [ }, { i18n: 'SpeedBlessing', - stat: () => 1 + 8 * G.effectiveRuneBlessingPower[1] // speed blessing + // stat: () => 1 + 8 * G.effectiveRuneBlessingPower[1] // speed blessing + stat: () => getRuneBlessingEffect('speed').globalSpeed // speed blessing }, { i18n: 'SpeedSpirit', - stat: () => 1 + player.corruptions.used.totalCorruptionDifficultyMultiplier * G.effectiveRuneSpiritPower[1] // speed SPIRIT + stat: () => getRuneSpiritEffect('speed').globalSpeed // speed spirit }, { i18n: 'ChronosCube', - stat: () => G.cubeBonusMultiplier[10] // Chronos cube blessing + stat: () => calculateGlobalSpeedCubeBlessing() // Chronos cube blessing }, { i18n: 'CubeUpgrade2x8', @@ -1793,7 +1835,7 @@ export const allGlobalSpeedStats: StatLine[] = [ }, { i18n: 'ChronosTalisman', - stat: () => 1 + 0.1 * (player.talismanRarity[2 - 1] - 1) // Chronos Talisman bonus + stat: () => getTalismanEffects('chronos').globalSpeed // Chronos Talisman bonus }, { i18n: 'Challenge15', @@ -1827,6 +1869,10 @@ export const allGlobalSpeedDRStats: StatLine[] = [ ] export const allAscensionSpeedStats: StatLine[] = [ + { + i18n: 'PolymathTalisman', + stat: () => getTalismanEffects('polymath').ascensionSpeedBonus // Polymath Talisman + }, { i18n: 'Chronometer', stat: () => 1 + (1.2 / 100) * player.shopUpgrades.chronometer // Chronometer @@ -1841,15 +1887,7 @@ export const allAscensionSpeedStats: StatLine[] = [ }, { i18n: 'ChronosHepteract', - stat: () => 1 + (0.6 / 1000) * hepteractEffective('chronos') // Chronos Hepteract - }, - { - i18n: 'Achievement262', - stat: () => 1 + Math.min(0.1, (1 / 100) * Math.log10(player.ascensionCount + 1)) * player.achievements[262] // Achievement 262 Bonus - }, - { - i18n: 'Achievement263', - stat: () => 1 + Math.min(0.1, (1 / 100) * Math.log10(player.ascensionCount + 1)) * player.achievements[263] // Achievement 263 Bonus + stat: () => getHepteractEffects('chronos').ascensionSpeed // Chronos Hepteract }, { i18n: 'PlatonicOMEGA', @@ -1865,7 +1903,7 @@ export const allAscensionSpeedStats: StatLine[] = [ }, { i18n: 'IntermediatePack', - stat: () => 1 + 0.5 * (player.singularityUpgrades.intermediatePack.getEffect().bonus ? 1 : 0) // Intermediate Pack, Sing Shop + stat: () => 1 + 0.5 * getGQUpgradeEffect('intermediatePack') // Intermediate Pack, Sing Shop }, { i18n: 'ChronometerZ', @@ -1873,11 +1911,11 @@ export const allAscensionSpeedStats: StatLine[] = [ }, { i18n: 'AbstractPhotokinetics', - stat: () => 1 + +player.octeractUpgrades.octeractImprovedAscensionSpeed.getEffect().bonus * player.singularityCount // Abstract Photokinetics, Oct Upg + stat: () => 1 + getOcteractUpgradeEffect('octeractImprovedAscensionSpeed') * player.singularityCount // Abstract Photokinetics, Oct Upg }, { i18n: 'AbstractExokinetics', - stat: () => 1 + +player.octeractUpgrades.octeractImprovedAscensionSpeed2.getEffect().bonus * player.singularityCount // Abstract Exokinetics, Oct Upg + stat: () => 1 + getOcteractUpgradeEffect('octeractImprovedAscensionSpeed2') * player.singularityCount // Abstract Exokinetics, Oct Upg }, { i18n: 'ChronometerINF', @@ -1952,7 +1990,16 @@ export const allAdditiveLuckMultStats: StatLine[] = [ }, { i18n: 'Cookie5', - stat: () => 0.001 * player.cubeUpgrades[77] // Cookie 5 (Cx27) + stat: () => 0.001 * player.cubeUpgrades[77], // Cookie 5 (Cx27) + acc: 3 + }, + { + i18n: 'BlueberryUpgrade', + stat: () => getAmbrosiaUpgradeEffects('ambrosiaLuck4').ambrosiaLuckPercentage // Blueberry Upgrade 4 + }, + { + i18n: 'HorseShoeTalisman', + stat: () => getTalismanEffects('horseShoe').luckPercentage // Horseshoe Talisman }, { i18n: 'Event', @@ -1971,6 +2018,11 @@ export const allAmbrosiaLuckStats: StatLine[] = [ stat: () => PCoinUpgradeEffects.AMBROSIA_LUCK_BUFF, // Platonic Coin Upgrade color: 'gold' }, + { + i18n: 'SynergismLevel', + stat: () => getLevelReward('ambrosiaLuck'), // Synergism Level + color: 'green' + }, { i18n: 'Campaign', stat: () => player.campaigns.ambrosiaLuckBonus // Campaign Bonus @@ -1993,23 +2045,23 @@ export const allAmbrosiaLuckStats: StatLine[] = [ }, { i18n: 'AmbrosiaLuck1', - stat: () => +player.blueberryUpgrades.ambrosiaLuck1.bonus.ambrosiaLuck // Ambrosia Luck from Luck Module I + stat: () => getAmbrosiaUpgradeEffects('ambrosiaLuck1').ambrosiaLuck // Ambrosia Luck from Luck Module I }, { i18n: 'AmbrosiaLuck2', - stat: () => +player.blueberryUpgrades.ambrosiaLuck2.bonus.ambrosiaLuck // Ambrosia Luck from Luck Module II + stat: () => getAmbrosiaUpgradeEffects('ambrosiaLuck2').ambrosiaLuck // Ambrosia Luck from Luck Module II }, { i18n: 'AmbrosiaLuck3', - stat: () => +player.blueberryUpgrades.ambrosiaLuck3.bonus.ambrosiaLuck // Ambrosia Luck from Luck Module III + stat: () => getAmbrosiaUpgradeEffects('ambrosiaLuck3').ambrosiaLuck // Ambrosia Luck from Luck Module III }, { i18n: 'AmbrosiaCubeLuck1', - stat: () => +player.blueberryUpgrades.ambrosiaCubeLuck1.bonus.ambrosiaLuck // Ambrosia Luck from Cube-Luck Synergy Module + stat: () => getAmbrosiaUpgradeEffects('ambrosiaCubeLuck1').ambrosiaLuck // Ambrosia Luck from Cube-Luck Synergy Module }, { i18n: 'AmbrosiaQuarkLuck1', - stat: () => +player.blueberryUpgrades.ambrosiaQuarkLuck1.bonus.ambrosiaLuck // Ambrosia Luck from Quark-Luck Synergy Module + stat: () => getAmbrosiaUpgradeEffects('ambrosiaQuarkLuck1').ambrosiaLuck // Ambrosia Luck from Quark-Luck Synergy Module }, { i18n: 'Singularity131', @@ -2030,15 +2082,15 @@ export const allAmbrosiaLuckStats: StatLine[] = [ }, { i18n: 'RedAmbrosiaUpgrade', - stat: () => getRedAmbrosiaUpgrade('regularLuck').bonus.ambrosiaLuck // Red Ambrosia Upgrade + stat: () => getRedAmbrosiaUpgradeEffects('regularLuck').ambrosiaLuck // Red Ambrosia Upgrade }, { i18n: 'RedAmbrosiaUpgrade2', - stat: () => getRedAmbrosiaUpgrade('regularLuck2').bonus.ambrosiaLuck // Red Ambrosia Upgrade 2 + stat: () => getRedAmbrosiaUpgradeEffects('regularLuck2').ambrosiaLuck // Red Ambrosia Upgrade 2 }, { i18n: 'Viscount', - stat: () => getRedAmbrosiaUpgrade('viscount').bonus.luckBonus, // Viscount Red Ambrosia Upgrade + stat: () => getRedAmbrosiaUpgradeEffects('viscount').luckBonus, // Viscount Red Ambrosia Upgrade color: 'red' }, { @@ -2052,6 +2104,10 @@ export const allAmbrosiaLuckStats: StatLine[] = [ { i18n: 'AmbrosiaUltra', stat: () => player.shopUpgrades.shopAmbrosiaUltra * sumOfExaltCompletions() // Ambrosia Ultra Shop Upgrade + }, + { + i18n: 'HorseShoeRune', + stat: () => getRuneEffects('horseShoe').ambrosiaLuck // Horseshoe Rune } ] @@ -2070,11 +2126,11 @@ export const allAmbrosiaBlueberryStats: StatLine[] = [ }, { i18n: 'SingBlueberries', - stat: () => +player.singularityUpgrades.blueberries.getEffect().bonus // Singularity Blueberry Upgrade + stat: () => getGQUpgradeEffect('blueberries') // Singularity Blueberry Upgrade }, { i18n: 'OcteractBlueberries', - stat: () => +player.octeractUpgrades.octeractBlueberries.getEffect().bonus // Octeract Blueberry Upgrade + stat: () => getOcteractUpgradeEffect('octeractBlueberries') // Octeract Blueberry Upgrade }, { i18n: 'ConglomerateBerries', @@ -2114,7 +2170,7 @@ export const allAmbrosiaGenerationSpeedStats: StatLine[] = [ }, { i18n: 'PatreonBonus', - stat: () => +player.blueberryUpgrades.ambrosiaPatreon.bonus.blueberryGeneration // Patreon Bonus + stat: () => getAmbrosiaUpgradeEffects('ambrosiaPatreon').blueberryGeneration // Patreon Bonus }, { i18n: 'OneChallengeCap', @@ -2126,11 +2182,11 @@ export const allAmbrosiaGenerationSpeedStats: StatLine[] = [ }, { i18n: 'RedAmbrosiaUpgrade', - stat: () => getRedAmbrosiaUpgrade('blueberryGenerationSpeed').bonus.blueberryGenerationSpeed // Red Ambrosia Upgrade + stat: () => getRedAmbrosiaUpgradeEffects('blueberryGenerationSpeed').blueberryGenerationSpeed // Red Ambrosia Upgrade }, { i18n: 'RedAmbrosiaUpgrade2', - stat: () => getRedAmbrosiaUpgrade('blueberryGenerationSpeed2').bonus.blueberryGenerationSpeed // Red Ambrosia Upgrade 2 + stat: () => getRedAmbrosiaUpgradeEffects('blueberryGenerationSpeed2').blueberryGenerationSpeed // Red Ambrosia Upgrade 2 }, { i18n: 'CookieUpgrade26', @@ -2159,6 +2215,10 @@ export const allPowderMultiplierStats: StatLine[] = [ i18n: 'Base', stat: () => 1 / 100 // Base value of 0.01 (1%) }, + { + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('overfluxConversionRate') // Achievement Bonus + }, { i18n: 'Challenge15', stat: () => G.challenge15Rewards.powder.value // Challenge 15 Reward @@ -2167,14 +2227,6 @@ export const allPowderMultiplierStats: StatLine[] = [ i18n: 'ShopPowderEX', stat: () => 1 + player.shopUpgrades.powderEX / 50 // powderEX shop upgrade (2% per level, max 20%) }, - { - i18n: 'Achievement256', - stat: () => 1 + player.achievements[256] / 20 // Achievement 256 (5%) - }, - { - i18n: 'Achievement257', - stat: () => 1 + player.achievements[257] / 20 // Achievement 257 (5%) - }, { i18n: 'PlatonicUpgrade4x1', stat: () => 1 + 0.01 * player.platonicUpgrades[16] // Platonic Upgrade 4x1 @@ -2208,7 +2260,7 @@ export const allGoldenQuarkMultiplierStats: StatLine[] = [ }, { i18n: 'GoldenQuarks1', - stat: () => +player.singularityUpgrades.goldenQuarks1.getEffect().bonus // Golden Quarks I + stat: () => getGQUpgradeEffect('goldenQuarks1') // Golden Quarks I }, { i18n: 'CookieUpgrade19', @@ -2266,27 +2318,23 @@ export const allGoldenQuarkPurchaseCostStats: StatLine[] = [ stat: () => 1 / (1 + getQuarkBonus() / 100), color: 'gold' }, - { - i18n: 'AchievementPoints', - stat: () => 1 - 0.1 * Math.min(1, player.achievementPoints / 10000) - }, { i18n: 'CubeUpgrade6x10', stat: () => 1 - (0.3 * player.cubeUpgrades[60]) / 10000 }, { i18n: 'GoldenQuarks2', - stat: () => +player.singularityUpgrades.goldenQuarks2.getEffect().bonus + stat: () => getGQUpgradeEffect('goldenQuarks2') }, { i18n: 'OcteractCostReduce', - stat: () => +player.octeractUpgrades.octeractGQCostReduce.getEffect().bonus + stat: () => getOcteractUpgradeEffect('octeractGQCostReduce') }, { i18n: 'GoldenRevolution2', stat: () => player.highestSingularityCount >= 100 - ? 1 - (0.5 * player.highestSingularityCount) / 250 + ? Math.max(0.5, 1 - (0.5 * player.highestSingularityCount) / 250) : 1 }, { @@ -2374,8 +2422,8 @@ export const allAddCodeTimerStats: StatLine[] = [ color: 'lime' }, { - i18n: 'InfiniteAscent', - stat: () => player.runelevels[6] > 0 ? 0.8 : 1, // Infinite Ascent rune reduction (20%) + i18n: 'Antiquities', + stat: () => getRuneEffects('antiquities').addCodeCooldownReduction, // Antiquities rune reduction (20%) color: 'lime' }, { @@ -2442,15 +2490,15 @@ export const allLuckConversionStats: StatLine[] = [ }, { i18n: 'RedAmbrosiaUpgrade1', - stat: () => getRedAmbrosiaUpgrade('conversionImprovement1').bonus.conversionImprovement // Conversion Improvement I + stat: () => getRedAmbrosiaUpgradeEffects('conversionImprovement1').conversionImprovement // Conversion Improvement I }, { i18n: 'RedAmbrosiaUpgrade2', - stat: () => getRedAmbrosiaUpgrade('conversionImprovement2').bonus.conversionImprovement // Conversion Improvement II + stat: () => getRedAmbrosiaUpgradeEffects('conversionImprovement2').conversionImprovement // Conversion Improvement II }, { i18n: 'RedAmbrosiaUpgrade3', - stat: () => getRedAmbrosiaUpgrade('conversionImprovement3').bonus.conversionImprovement // Conversion Improvement III + stat: () => getRedAmbrosiaUpgradeEffects('conversionImprovement3').conversionImprovement // Conversion Improvement III }, { i18n: 'ShopRedLuck1', @@ -2463,6 +2511,10 @@ export const allLuckConversionStats: StatLine[] = [ { i18n: 'ShopRedLuck3', stat: () => -0.01 * Math.floor(player.shopUpgrades.shopRedLuck3 / 20) // Shop Red Luck III + }, + { + i18n: 'HorseShoeRune', + stat: () => getRuneEffects('horseShoe').redLuckConversion // Horseshoe Rune } ] @@ -2476,13 +2528,18 @@ export const allRedAmbrosiaLuckStats: StatLine[] = [ stat: () => PCoinUpgradeEffects.RED_LUCK_BUFF, // PseudoCoin Upgrade color: 'gold' }, + { + i18n: 'SynergismLevel', + stat: () => getLevelReward('redAmbrosiaLuck'), // Synergism Level + color: 'green' + }, { i18n: 'LuckConversion', stat: () => Math.floor((calculateAmbrosiaLuck() - 100) / calculateLuckConversion()) // Luck Conversion }, { i18n: 'RedAmbrosia', - stat: () => getRedAmbrosiaUpgrade('redLuck').bonus.redAmbrosiaLuck // The Dice That Decide Your Fate + stat: () => getRedAmbrosiaUpgradeEffects('redLuck').redAmbrosiaLuck // The Dice That Decide Your Fate }, { i18n: 'Exalt5', @@ -2502,8 +2559,16 @@ export const allRedAmbrosiaLuckStats: StatLine[] = [ }, { i18n: 'Viscount', - stat: () => getRedAmbrosiaUpgrade('viscount').bonus.redLuckBonus, // Viscount Red Ambrosia Upgrade + stat: () => getRedAmbrosiaUpgradeEffects('viscount').redLuckBonus, // Viscount Red Ambrosia Upgrade color: 'red' + }, + { + i18n: 'HorseShoeRune', + stat: () => getRuneEffects('horseShoe').redLuck // Horseshoe Rune + }, + { + i18n: 'HorseShoeTalisman', + stat: () => getTalismanEffects('horseShoe').redLuck // Horseshoe Talisman } ] @@ -2526,7 +2591,7 @@ export const allRedAmbrosiaGenerationSpeedStats: StatLine[] = [ }, { i18n: 'RedAmbrosia', - stat: () => getRedAmbrosiaUpgrade('redGenerationSpeed').bonus.redAmbrosiaGenerationSpeed + stat: () => getRedAmbrosiaUpgradeEffects('redGenerationSpeed').redAmbrosiaGenerationSpeed }, { i18n: 'Exalt5', @@ -2556,7 +2621,7 @@ export const infinityShopUpgrades: StatLine[] = [ export const allShopTablets: StatLine[] = [ { i18n: 'Red', - stat: () => getRedAmbrosiaUpgrade('infiniteShopUpgrades').bonus.freeLevels, // Red Ambrosia Upgrade + stat: () => getRedAmbrosiaUpgradeEffects('infiniteShopUpgrades').freeLevels, // Red Ambrosia Upgrade acc: 0, color: 'red' }, @@ -2576,13 +2641,13 @@ export const allShopTablets: StatLine[] = [ }, { i18n: 'Yellow', - stat: () => +player.singularityUpgrades.singInfiniteShopUpgrades.getEffect().bonus, // Singularity Upgrade + stat: () => getGQUpgradeEffect('singInfiniteShopUpgrades'), // Singularity Upgrade acc: 0, color: 'yellow' }, { i18n: 'Green', - stat: () => +player.octeractUpgrades.octeractInfiniteShopUpgrades.getEffect().bonus, // Octeract Upgrade + stat: () => getOcteractUpgradeEffect('octeractInfiniteShopUpgrades'), // Octeract Upgrade acc: 0, color: 'green' }, @@ -2594,18 +2659,248 @@ export const allShopTablets: StatLine[] = [ }, { i18n: 'Indigo', - stat: () => +player.blueberryUpgrades.ambrosiaInfiniteShopUpgrades1.bonus.freeLevels, // Blueberry Upgrade + stat: () => getAmbrosiaUpgradeEffects('ambrosiaInfiniteShopUpgrades1').freeLevels, // Blueberry Upgrade acc: 0, color: 'orchid' }, { i18n: 'Violet', - stat: () => +player.blueberryUpgrades.ambrosiaInfiniteShopUpgrades2.bonus.freeLevels, // Blueberry Upgrade 2 + stat: () => getAmbrosiaUpgradeEffects('ambrosiaInfiniteShopUpgrades2').freeLevels, // Blueberry Upgrade 2 acc: 0, color: 'violet' } ] +/** + * Do NOT add anything here without adding it to @see {allTalismanRuneBonusStats} + */ +export const allTalismanRuneBonusStatsSum = () => { + return ( + 1 + + +getAchievementReward('talismanPower') + + (player.researches[106] / 1000) + + (player.researches[107] / 1000) + + (player.researches[116] / 1000) + + (player.researches[117] / 1000) + + (2 * player.researches[118] / 1000) + + (0.004 * Math.floor(player.researches[200] / 10000)) + + (0.006 * Math.floor(player.cubeUpgrades[50] / 10000)) + + (G.challenge15Rewards.talismanBonus.value - 1) + + getGQUpgradeEffect('singTalismanBonusRunes1') + + getGQUpgradeEffect('singTalismanBonusRunes2') + + getGQUpgradeEffect('singTalismanBonusRunes3') + + getGQUpgradeEffect('singTalismanBonusRunes4') + + getAmbrosiaUpgradeEffects('ambrosiaTalismanBonusRuneLevel').talismanBonusRuneLevel + + +player.singularityChallenges.taxmanLastStand.rewards.talismanRuneEffect + ) +} + +/** + * Do NOT add anything here without adding it to @see {allTalismanRuneBonusStatsSum} + */ +export const allTalismanRuneBonusStats: StatLine[] = [ + { + i18n: 'Base', + stat: () => 1, + displayCriterion: () => { + const chal9 = player.highestchallengecompletions[9] >= 1 + return chal9 + } + }, + { + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('talismanPower'), + displayCriterion: () => { + const chal9 = player.highestchallengecompletions[9] >= 1 + return chal9 + } + }, + { + i18n: 'Research106', + stat: () => player.researches[106] / 1000, + displayCriterion: () => { + const chal9 = player.highestchallengecompletions[9] >= 1 + return chal9 + } + }, + { + i18n: 'Research107', + stat: () => player.researches[107] / 1000, + displayCriterion: () => { + const chal9 = player.highestchallengecompletions[9] >= 1 + return chal9 + } + }, + { + i18n: 'Research116', + stat: () => player.researches[116] / 1000, + displayCriterion: () => { + const chal10 = player.highestchallengecompletions[10] >= 1 + return chal10 + } + }, + { + i18n: 'Research117', + stat: () => player.researches[117] / 1000, + displayCriterion: () => { + const chal10 = player.highestchallengecompletions[10] >= 1 + return chal10 + } + }, + { + i18n: 'Research118', + stat: () => 2 * player.researches[118] / 1000, + displayCriterion: () => { + const chal10 = player.highestchallengecompletions[10] >= 1 + return chal10 + } + }, + { + i18n: 'Research200', + stat: () => 0.004 * Math.floor(player.researches[200] / 10000), + displayCriterion: () => { + const chal14 = player.highestchallengecompletions[14] >= 1 + return chal14 + } + }, + { + i18n: 'CubeUpgrade50', + stat: () => 0.006 * Math.floor(player.cubeUpgrades[50] / 10000), + displayCriterion: () => { + const chal14 = player.highestchallengecompletions[14] >= 1 + return chal14 + } + }, + { + i18n: 'Challenge15', + stat: () => G.challenge15Rewards.talismanBonus.value - 1, + displayCriterion: () => { + const chal15 = G.challenge15Rewards.talismanBonus.value > 1 + return chal15 + } + }, + { + i18n: 'SingularityUpgrade1', + stat: () => getGQUpgradeEffect('singTalismanBonusRunes1'), // Singularity Upgrade 1 + displayCriterion: () => { + const singStuff = player.highestSingularityCount >= goldenQuarkUpgrades.singTalismanBonusRunes1.minimumSingularity + return singStuff + } + }, + { + i18n: 'SingularityUpgrade2', + stat: () => getGQUpgradeEffect('singTalismanBonusRunes2'), + displayCriterion: () => { + const singStuff = player.highestSingularityCount >= goldenQuarkUpgrades.singTalismanBonusRunes2.minimumSingularity + return singStuff + } + }, + { + i18n: 'SingularityUpgrade3', + stat: () => getGQUpgradeEffect('singTalismanBonusRunes3'), + displayCriterion: () => { + const singStuff = player.highestSingularityCount >= goldenQuarkUpgrades.singTalismanBonusRunes3.minimumSingularity + return singStuff + } + }, + { + i18n: 'SingularityUpgrade4', + stat: () => getGQUpgradeEffect('singTalismanBonusRunes4'), + displayCriterion: () => { + const singStuff = player.highestSingularityCount >= goldenQuarkUpgrades.singTalismanBonusRunes4.minimumSingularity + return singStuff + } + }, + { + i18n: 'BlueberryUpgrade', + stat: () => getAmbrosiaUpgradeEffects('ambrosiaTalismanBonusRuneLevel').talismanBonusRuneLevel + }, + { + i18n: 'TaxmanLastStand', + stat: () => +player.singularityChallenges.taxmanLastStand.rewards.talismanRuneEffect, + displayCriterion: () => { + const singStuff = player.highestSingularityCount >= 270 + return singStuff + } + } +] + +export const positiveSalvageStats: StatLine[] = [ + { + i18n: 'AchievementBonus', + stat: () => +getAchievementReward('salvage') + }, + { + i18n: 'SynergismLevel', + stat: () => getLevelReward('salvage') + }, + { + i18n: 'SynergismLevelMilestone', + stat: () => getLevelMilestone('salvageChallengeBuff') + }, + { + i18n: 'UpgradeBonus', + stat: () => 7 * player.upgrades[61] // Upgrade 61 + }, + { + i18n: 'RuneBonus', + stat: () => getRuneEffects('thrift').salvage // Thrift Rune + }, + { + i18n: 'ReincarnationChallenge', + stat: () => { + return 0.3 * CalcECC('reincarnation', player.challengecompletions[6]) + + 0.3 * CalcECC('reincarnation', player.challengecompletions[7]) + + 0.4 * CalcECC('reincarnation', player.challengecompletions[8]) + + 0.5 * CalcECC('reincarnation', player.challengecompletions[9]) + } + }, + { + i18n: 'CubeBlessing', + stat: () => calculateSalvageCubeBlessing() // Cube Blessing + }, + { + i18n: 'CubeUpgrade2', + stat: () => 3 * player.cubeUpgrades[2] // Cube Upgrade 2 + }, + { + i18n: 'AbyssHepteract', + stat: () => getHepteractEffects('abyss').salvage // Abyss Hepteract + }, + { + i18n: 'SingularityPerk', + stat: () => Math.min(50, 5 * player.highestSingularityCount) + }, + { + i18n: 'InfiniteAscentRune', + stat: () => getRuneEffects('infiniteAscent').salvage, // Infinite Ascent Rune + displayCriterion: () => { + return player.highestSingularityCount >= 30 + } + }, + { + i18n: 'RedAmbrosiaYinYang', + stat: () => getRedAmbrosiaUpgradeEffects('salvageYinYang').positiveSalvage // Red Ambrosia Upgrade: Yin Yang + } +] + +export const negativeSalvageStats: StatLine[] = [ + { + i18n: 'DroughtCorruption', + stat: () => player.corruptions.used.corruptionEffects('drought'), + color: 'red' + }, + { + i18n: 'SingularityDebuff', + stat: () => calculateSingularityDebuff('Salvage'), // Singularity Debuff + color: 'red' + }, + { + i18n: 'RedAmbrosiaYinYang', + stat: () => getRedAmbrosiaUpgradeEffects('salvageYinYang').negativeSalvage // Red Ambrosia Upgrade: Yin Yang + } +] + export const allMiscStats: StatLine[] = [ { i18n: 'PrestigeCount', @@ -2617,14 +2912,9 @@ export const allMiscStats: StatLine[] = [ stat: () => 1000 * player.fastestprestige, color: 'cyan' }, - { - i18n: 'MaxOfferings', - stat: () => player.maxofferings, - color: 'orange' - }, { i18n: 'RuneSum', - stat: () => G.runeSum, + stat: () => sumOfRuneLevels(), color: 'orange' }, { @@ -2647,21 +2937,6 @@ export const allMiscStats: StatLine[] = [ stat: () => 1000 * player.fastestreincarnate, color: 'green' }, - { - i18n: 'MaxObtainium', - stat: () => player.maxobtainium, - color: 'pink' - }, - { - i18n: 'MaxObtainiumPerSecond', - stat: () => player.maxobtainiumpersecond, - color: 'pink' - }, - { - i18n: 'ObtainiumPerSecond', - stat: () => player.obtainiumpersecond, - color: 'pink' - }, { i18n: 'AscensionCount', stat: () => player.ascensionCount, @@ -2697,11 +2972,14 @@ const associated = new Map([ ['kMisc', 'miscStats'], ['kBaseOffering', 'baseOfferingStats'], ['kOfferingMult', 'offeringMultiplierStats'], + ['kSalvage', 'salvageStats'], ['kBaseObtainium', 'baseObtainiumStats'], + ['kRuneEffectMult', 'runeEffectMultiplierStats'], ['kObtIgnoreDR', 'obtainiumIgnoreDRStats'], ['kObtMult', 'obtainiumMultiplierStats'], ['kGlobalCubeMult', 'globalCubeMultiplierStats'], ['kAntSacrificeMult', 'antSacrificeMultStats'], + ['kTalismanRuneBonusMult', 'talismanRuneBonusMultiplierStats'], ['kQuarkMult', 'globalQuarkMultiplierStats'], ['kGSpeedMultIgnoreDR', 'globalSpeedIgnoreDRStats'], ['kGSpeedMult', 'globalSpeedMultiplierStats'], @@ -2727,7 +3005,9 @@ const associated = new Map([ ]) export const displayStats = (btn: HTMLElement) => { - for (const e of Array.from(btn.parentElement!.children) as HTMLElement[]) { + const children = btn.parentElement ? Array.from(btn.parentElement.querySelectorAll('.statsNerds')) : [] + + for (const e of children as HTMLElement[]) { const statsEl = DOMCacheGetOrSet(associated.get(e.id)!) if (e.id !== btn.id) { e.style.backgroundColor = '' @@ -2756,6 +3036,13 @@ export const loadStatisticsUpdate = () => { case 'offeringMultiplierStats': loadStatisticsOfferingMultipliers() break + case 'runeEffectMultiplierStats': + loadStatisticsRuneEffectMultFirstFive() + loadStatisticsRuneEffectMultSI() + break + case 'salvageStats': + loadSalvageStats() + break case 'baseObtainiumStats': loadStatisticsObtainiumBase() break @@ -2777,6 +3064,9 @@ export const loadStatisticsUpdate = () => { case 'antSacrificeMultStats': loadStatisticsAntSacrificeMult() break + case 'talismanRuneBonusMultiplierStats': + loadTalismanRuneBonusMultiplierStats() + break case 'powderMultiplierStats': loadStatisticsPowderMultiplier() break @@ -2879,8 +3169,13 @@ export const loadStatistics = ( createdStatLines += 1 } + const statLine = DOMCacheGetOrSet(statHTMLName) const statNumber = DOMCacheGetOrSet(statNumHTMLName) + if (obj.displayCriterion) { + statLine.style.display = obj.displayCriterion() ? 'block' : 'none' + } + const accuracy = obj.acc ?? 2 const num = obj.stat() @@ -2890,7 +3185,10 @@ export const loadStatistics = ( const statTotalHTMLName = `${statLinePrefix}T` const statNumTotalHTMLName = `${statLinePrefix}NT` - if (numStatLines === 0 && hasSummative) { + // Query that an element with name statTotalHTMLName Exists + const totalElm = document.getElementById(statTotalHTMLName) + + if (totalElm === null && hasSummative) { const statTotal = document.createElement('p') statTotal.id = statTotalHTMLName statTotal.className = 'statPortion' @@ -2991,6 +3289,26 @@ export const loadStatisticsOfferingMultipliers = () => { ) } +export const loadStatisticsRuneEffectMultFirstFive = () => { + loadStatistics( + firstFiveRuneEffectivenessStats, + 'runeEffectFirstFive', + 'statREMFF', + 'RuneEffectivenessStatFirstFive', + firstFiveEffectiveRuneLevelMult + ) +} + +export const loadStatisticsRuneEffectMultSI = () => { + loadStatistics( + runeEffectivenessStatsSI, + 'runeEffectSI', + 'statREMSI', + 'RuneEffectivenessStatSI', + () => firstFiveEffectiveRuneLevelMult() * SIEffectiveRuneLevelMult() + ) +} + export const loadStatisticsObtainiumBase = () => { loadStatistics(allBaseObtainiumStats, 'baseObtainiumStats', 'statObtB', 'ObtainiumBaseStat', calculateBaseObtainium) } @@ -3023,7 +3341,7 @@ export const loadStatisticsAntSacrificeMult = () => { 'antSacrificeMultStats', 'statASM', 'AntSacrificeStat', - calculateAntSacrificeMultipliers + calculateAntSacrificeMultiplier ) } @@ -3218,6 +3536,67 @@ export const loadMiscellaneousStats = () => { DOMCacheGetOrSet('gameStageStatistic').innerHTML = i18next.t('statistics.gameStage', { stage: synergismStage(0) }) } +export const loadTalismanRuneBonusMultiplierStats = () => { + loadStatistics( + allTalismanRuneBonusStats, + 'talismanRuneBonusMultiplierStats', + 'statTRBM', + 'TalismanRuneBonusStat', + allTalismanRuneBonusStatsSum + ) +} + +export const loadSalvageStats = () => { + loadPositiveSalvageStats() + loadNegativeSalvageStats() + + loadStatistics( + [], + 'salvageStats', + 'statSalv', + 'SalvageStat', + calculateTotalSalvage + ) + + DOMCacheGetOrSet('salvageExtra').innerHTML = i18next.t('statistics.salvageStats.extraInfo') +} + +export const loadPositiveSalvageStats = () => { + loadStatistics( + positiveSalvageStats, + 'salvagePositive', + 'statSalvPos', + 'SalvageStatPositive', + calculateRawPositiveSalvage + ) + loadStatistics( + [{ i18n: 'PositiveMultiplier', stat: () => calculatePositiveSalvageMultiplier() }], + 'salvagePositive', + 'statSalvPos2', + 'SalvageStatPositive2', + calculatePositiveSalvage, + 'Total2' + ) +} + +export const loadNegativeSalvageStats = () => { + loadStatistics( + negativeSalvageStats, + 'salvageNegative', + 'statSalvNeg', + 'SalvageStatNegative', + calculateRawNegativeSalvage + ) + loadStatistics( + [{ i18n: 'NegativeMultiplier', stat: () => calculateNegativeSalvageMultiplier() }], + 'salvageNegative', + 'statSalvNeg2', + 'SalvageStatNegative2', + calculateNegativeSalvage, + 'Total2' + ) +} + export const c15RewardUpdate = () => { type Key = keyof GlobalVariables['challenge15Rewards'] const e = player.challenge15Exponent @@ -3232,22 +3611,6 @@ export const c15RewardUpdate = () => { } } - if (G.challenge15Rewards.challengeHepteractUnlocked.value > 0) { - void player.hepteractCrafts.challenge.unlock('the Hepteract of Challenge') - } - if (G.challenge15Rewards.abyssHepteractUnlocked.value > 0) { - void player.hepteractCrafts.abyss.unlock('the Hepteract of the Abyss') - } - if (G.challenge15Rewards.acceleratorHepteractUnlocked.value > 0) { - void player.hepteractCrafts.accelerator.unlock('the Hepteract of Way Too Many Accelerators') - } - if (G.challenge15Rewards.acceleratorBoostHepteractUnlocked.value > 0) { - void player.hepteractCrafts.acceleratorBoost.unlock('the Hepteract of Way Too Many Accelerator Boosts') - } - if (G.challenge15Rewards.multiplierHepteractUnlocked.value > 0) { - void player.hepteractCrafts.multiplier.unlock('the Hepteract of Way Too Many Multipliers') - } - updateDisplayC15Rewards() } @@ -3354,14 +3717,14 @@ export const gameStages = (): Stage[] => { stage: 5, tier: 4, name: 'ant-sacrifice', - unlocked: player.achievements[173] === 1, + unlocked: Boolean(getAchievementReward('antSacrificeUnlock')), reset: player.unlocks.reincarnate }, { stage: 6, tier: 4, name: 'sacrifice-ascension', - unlocked: player.achievements[183] === 1, + unlocked: player.ascensionCount > 0, reset: player.unlocks.reincarnate }, { @@ -3369,91 +3732,91 @@ export const gameStages = (): Stage[] => { tier: 5, name: 'ascension-challenge10', unlocked: player.ascensionCount > 1, - reset: player.achievements[183] === 1 + reset: player.ascensionCount > 0 }, { stage: 8, tier: 5, name: 'challenge10-challenge11', - unlocked: player.achievements[197] === 1, - reset: player.achievements[183] === 1 + unlocked: player.challengecompletions[11] > 0, + reset: player.ascensionCount > 0 }, { stage: 9, tier: 5, name: 'challenge11-challenge12', - unlocked: player.achievements[204] === 1, - reset: player.achievements[183] === 1 + unlocked: player.challengecompletions[12] > 0, + reset: player.ascensionCount > 0 }, { stage: 10, tier: 5, name: 'challenge12-challenge13', - unlocked: player.achievements[211] === 1, - reset: player.achievements[183] === 1 + unlocked: player.challengecompletions[13] > 0, + reset: player.ascensionCount > 0 }, { stage: 11, tier: 5, name: 'challenge13-challenge14', - unlocked: player.achievements[218] === 1, - reset: player.achievements[183] === 1 + unlocked: player.challengecompletions[14] > 0, + reset: player.ascensionCount > 0 }, { stage: 12, tier: 5, name: 'challenge14-w5x10max', unlocked: player.cubeUpgrades[50] >= 100000, - reset: player.achievements[183] === 1 + reset: player.ascensionCount > 0 }, { stage: 13, tier: 5, name: 'w5x10max-alpha', unlocked: player.platonicUpgrades[5] > 0, - reset: player.achievements[183] === 1 + reset: player.ascensionCount > 0 }, { stage: 14, tier: 5, name: 'alpha-p2x1x10', unlocked: player.platonicUpgrades[6] >= 10, - reset: player.achievements[183] === 1 + reset: player.ascensionCount > 0 }, { stage: 15, tier: 5, name: 'p2x1x10-p3x1', unlocked: player.platonicUpgrades[11] > 0, - reset: player.achievements[183] === 1 + reset: player.ascensionCount > 0 }, { stage: 16, tier: 5, name: 'p3x1-beta', unlocked: player.platonicUpgrades[10] > 0, - reset: player.achievements[183] === 1 + reset: player.ascensionCount > 0 }, { stage: 17, tier: 5, name: 'beta-1e15-expo', unlocked: player.challenge15Exponent >= G.challenge15Rewards.hepteractsUnlocked.requirement, - reset: player.achievements[183] === 1 + reset: player.ascensionCount > 0 }, { stage: 18, tier: 5, name: '1e15-expo-omega', unlocked: player.platonicUpgrades[15] > 0, - reset: player.achievements[183] === 1 + reset: player.ascensionCount > 0 }, { stage: 19, tier: 5, name: 'omega-singularity', - unlocked: player.singularityCount > 0 && player.runelevels[6] > 0, - reset: player.achievements[183] === 1 + unlocked: player.singularityCount > 0 && runes.antiquities.level > 0, + reset: player.ascensionCount > 0 }, { stage: 20, @@ -3487,7 +3850,7 @@ export const gameStages = (): Stage[] => { stage: 24, tier: 6, name: 'exalt6x25-pen', - unlocked: player.singularityUpgrades.ultimatePen.level > 0, + unlocked: goldenQuarkUpgrades.ultimatePen.level > 0, reset: player.highestSingularityCount > 0 }, { diff --git a/src/Summary.ts b/src/Summary.ts index 60d6d4487..5d950f880 100644 --- a/src/Summary.ts +++ b/src/Summary.ts @@ -2,22 +2,38 @@ import ClipboardJS from 'clipboard' import i18next from 'i18next' -import { totalachievementpoints } from './Achievements' +import { achievementPoints, maxAchievementPoints } from './Achievements' +import { type AmbrosiaUpgradeNames, ambrosiaUpgrades } from './BlueberryUpgrades' import { calculateAscensionSpeedMult, + calculateBlueberryInventory, calculateGlobalSpeedMult, calculateGoldenQuarks, - calculateMaxRunes, calculateOcteractMultiplier, calculateTotalOcteractCubeBonus, - calculateTotalOcteractQuarkBonus, - isIARuneUnlocked + calculateTotalOcteractQuarkBonus } from './Calculate' import { getMaxChallenges } from './Challenges' import { version } from './Config' +import { getFinalHepteractCap, hepteractKeys, hepteracts } from './Hepteracts' import { saveFilename } from './ImportExport' +import { + actualOcteractUpgradeTotalLevels, + computeOcteractFreeLevelSoftcap, + type OcteractDataKeys, + octeractUpgrades +} from './Octeracts' +import { redAmbrosiaUpgrades } from './RedAmbrosiaUpgrades' +import { type RuneKeys, runes } from './Runes' import { friendlyShopName, isShopUpgradeUnlocked, shopData, shopUpgradeTypes } from './Shop' -import { calculateEffectiveSingularities } from './singularity' +import { + actualGQUpgradeTotalLevels, + calculateEffectiveSingularities, + getGQUpgradeEffect, + goldenQuarkUpgrades, + type SingularityDataKeys +} from './singularity' +import type { SingularityChallengeDataKeys } from './SingularityChallenges' import { format, player } from './Synergism' import type { Player } from './types/Synergism' import { Alert } from './UpdateHTML' @@ -45,7 +61,7 @@ export const generateExportSummary = async (): Promise => { if (player.prestigeCount > 0 || player.highestSingularityCount > 0) { resources = `${resources}Diamonds: ${format(player.prestigePoints, 2, true)}\n` resources = `${resources}Crystals: ${format(player.prestigeShards, 2, true)}\n` - resources = `${resources}Offerings: ${format(player.runeshards, 0, true)}\n` + resources = `${resources}Offerings: ${format(player.offerings, 0, true)}\n` } if (player.transcendCount > 0 || player.highestSingularityCount > 0) { resources = `${resources}Mythos: ${format(player.transcendPoints, 2, true)}\n` @@ -54,7 +70,7 @@ export const generateExportSummary = async (): Promise => { if (player.reincarnationCount > 0 || player.highestSingularityCount > 0) { resources = `${resources}Particles: ${format(player.reincarnationPoints, 2, true)}\n` resources = `${resources}Atoms: ${format(player.reincarnationShards, 2, true)}\n` - resources = `${resources}Obtainium: ${format(player.researchPoints, 0, true)}\n` + resources = `${resources}Obtainium: ${format(player.obtainium, 0, true)}\n` } if (player.ascensionCount > 0 || player.highestSingularityCount > 0) { const cubeArray = [ @@ -123,14 +139,14 @@ export const generateExportSummary = async (): Promise => { format(Number(player.wowPlatonicCubes), 0, true) } -+- Total Plats Opened: ${platonicSum}\n` resources = `${resources}Wow! Hepteracts: ${format(player.wowAbyssals, 0, true)}\n` - if (player.singularityUpgrades.octeractUnlock.getEffect().bonus) { + if (getGQUpgradeEffect('octeractUnlock')) { resources = `${resources}Wow! Octeracts: ${format(player.wowOcteracts, 0, true)}\n` } } // Octeract Subportion! let octeract = '' - if (player.singularityUpgrades.octeractUnlock.getEffect().bonus) { + if (getGQUpgradeEffect('octeractUnlock')) { octeract = '===== OCTERACTS =====\n' octeract = `${octeract}Current Octeracts: ${format(player.wowOcteracts, 2, true)}\n` octeract = `${octeract}Current Per Second: ${format(calculateOcteractMultiplier(), 2, true)}\n` @@ -151,7 +167,7 @@ export const generateExportSummary = async (): Promise => { singularity = `${singularity}Effective Singularity [for penalties]: ${ format(calculateEffectiveSingularities(), 2, true) }\n` - singularity = `${singularity}Antiquity of Ant God Upgraded: ${(player.runelevels[6] > 0) ? '✔' : '✖'}\n` + singularity = `${singularity}Antiquity of Ant God Upgraded: ${(runes.antiquities.level > 0) ? '✔' : '✖'}\n` } // Ascension Subportion! @@ -180,30 +196,13 @@ export const generateExportSummary = async (): Promise => { || player.highestSingularityCount > 0 ) { ascension = `${ascension}----- HEPTERACTS -----\n` - ascension = `${ascension}Chronos Hepteract: ${format(player.hepteractCrafts.chronos.BAL, 0, true)}/${ - format(player.hepteractCrafts.chronos.CAP, 0, true) - }\n` - ascension = `${ascension}Hyperreal Hepteract: ${format(player.hepteractCrafts.hyperrealism.BAL, 0, true)}/${ - format(player.hepteractCrafts.hyperrealism.CAP, 0, true) - }\n` - ascension = `${ascension}Quark Hepteract: ${format(player.hepteractCrafts.quark.BAL, 0, true)}/${ - format(player.hepteractCrafts.quark.CAP, 0, true) - }\n` - ascension = `${ascension}Challenge Hepteract: ${format(player.hepteractCrafts.challenge.BAL, 0, true)}/${ - format(player.hepteractCrafts.challenge.CAP, 0, true) - }\n` - ascension = `${ascension}Abyss Hepteract: ${format(player.hepteractCrafts.abyss.BAL, 0, true)}/${ - format(player.hepteractCrafts.abyss.CAP, 0, true) - }\n` - ascension = `${ascension}Accelerators Hepteract: ${format(player.hepteractCrafts.accelerator.BAL, 0, true)}/${ - format(player.hepteractCrafts.accelerator.CAP, 0, true) - }\n` - ascension = `${ascension}Accelerator Boosts Hepteract: ${ - format(player.hepteractCrafts.acceleratorBoost.BAL, 0, true) - }/${format(player.hepteractCrafts.acceleratorBoost.CAP, 0, true)}\n` - ascension = `${ascension}Multipliers Hepteract: ${format(player.hepteractCrafts.multiplier.BAL, 0, true)}/${ - format(player.hepteractCrafts.multiplier.CAP, 0, true) - }\n` + + for (const key of hepteractKeys) { + const bal = hepteracts[key].BAL + const cap = getFinalHepteractCap(key) + ascension = `${ascension}${key.toUpperCase()} HEPTERACT: ${format(bal, 0, true)}/${format(cap, 0, true)}\n` + } + ascension = `${ascension}----- POWDER & ORBS -----\n` ascension = `${ascension}Orbs: ${format(player.overfluxOrbs, 0, true)}\n` ascension = `${ascension}Powder: ${format(player.overfluxPowder, 2, true)}\n` @@ -268,43 +267,19 @@ export const generateExportSummary = async (): Promise => { prestige = `${prestige}Fastest Prestige: ${formatS(player.fastestprestige)}\n` prestige = `${ prestige + i18next.t('achievements.totalPoints', { - x: format(player.achievementPoints), - y: format(totalachievementpoints), - z: (100 * player.achievementPoints / totalachievementpoints).toPrecision(4) + x: format(achievementPoints), + y: format(maxAchievementPoints), + z: (100 * achievementPoints / maxAchievementPoints).toPrecision(4) }) }\n` - prestige = `${prestige}Speed Rune: Level ${format(player.runelevels[0], 0, true)}/${ - format(calculateMaxRunes(1)) - } [Bonus: ${format(G.rune1level - player.runelevels[0], 0, true)}]\n` - if (player.achievements[38] > 0 || player.highestSingularityCount > 0) { - prestige = `${prestige}Duplication Rune: Level ${format(player.runelevels[1], 0, true)}/${ - format(calculateMaxRunes(2)) - } [Bonus: ${format(G.rune2level - player.runelevels[1], 0, true)}]\n` - } - if (player.achievements[44] > 0 || player.highestSingularityCount > 0) { - prestige = `${prestige}Prism Rune: Level ${format(player.runelevels[2], 0, true)}/${ - format(calculateMaxRunes(3)) - } [Bonus: ${format(G.rune3level - player.runelevels[2], 0, true)}]\n` - } - if (player.achievements[102] > 0 || player.highestSingularityCount > 0) { - prestige = `${prestige}Thrift Rune: Level ${format(player.runelevels[3], 0, true)}/${ - format(calculateMaxRunes(4)) - } [Bonus: ${format(G.rune4level - player.runelevels[3], 0, true)}]\n` - } - if (player.researches[82] > 0 || player.highestSingularityCount > 0) { - prestige = `${prestige}Superior Intellect: Level ${format(player.runelevels[4], 0, true)}/${ - format(calculateMaxRunes(5)) - } [Bonus: ${format(G.rune5level - player.runelevels[4], 0, true)}]\n` - } - if (isIARuneUnlocked() || player.highestSingularityCount > 0) { - prestige = `${prestige}Infinite Ascent: Level ${format(player.runelevels[5], 0, true)}/${ - format(calculateMaxRunes(6)) - }\n` - } - if (player.platonicUpgrades[20] > 0 || player.highestSingularityCount > 0) { - prestige = `${prestige}Antiquities: Level ${format(player.runelevels[6], 0, true)}/${ - format(calculateMaxRunes(7)) - }\n` + + for (const rune of Object.keys(runes)) { + const runeKey = rune as RuneKeys + if (runes[runeKey].isUnlocked()) { + prestige = `${prestige}${runes[runeKey].name()}: Level ${format(runes[runeKey].level, 0, true)} [+${ + format(runes[runeKey].freeLevels(), 0, true) + }]\n` + } } } @@ -362,15 +337,15 @@ export const generateExportSummary = async (): Promise => { if (player.highestSingularityCount > 0) { singularityUpgradeStats = '===== SINGULARITY UPGRADES =====\n - [★]: Upgrade is MAXED - \n - [∞]: Upgrade is infinite - \n - [✔]: Upgrade is unlocked - \n - [✖]: Upgrade is locked - \n' - const singUpgrade = Object.keys(player.singularityUpgrades) as (keyof Player['singularityUpgrades'])[] + const GQUpgrade = Object.keys(goldenQuarkUpgrades) as SingularityDataKeys[] let totalSingUpgradeCount = -1 // One upgrade cannot ever be leveled, by design, so subtract that from the actual count let totalSingInfiniteLevel = 0 let totalSingUpgradeUnlocked = 0 let totalSingUpgradeMax = 0 - let totalGoldenQuarksSpent = 0 - for (const key of singUpgrade) { + + for (const key of GQUpgrade) { let upgradeText = '' - const singUpg = player.singularityUpgrades[key] + const singUpg = goldenQuarkUpgrades[key] totalSingUpgradeCount += 1 if (singUpg.maxLevel === -1) { @@ -383,8 +358,6 @@ export const generateExportSummary = async (): Promise => { totalSingUpgradeUnlocked += 1 } - totalGoldenQuarksSpent += singUpg.goldenQuarksInvested - let unicodeSymbol = '[✖]' if (player.singularityCount >= singUpg.minimumSingularity) { if (singUpg.maxLevel === -1) { @@ -401,12 +374,12 @@ export const generateExportSummary = async (): Promise => { upgradeText = upgradeText + (singUpg.maxLevel === -1 ? ` Level ${singUpg.level}` : ` Level ${singUpg.level}/${singUpg.maxLevel}`) - upgradeText = upgradeText + (singUpg.freeLevels > 0 - ? ` [+${format(singUpg.computeFreeLevelSoftcap(), 2, true)}]` + upgradeText = upgradeText + (singUpg.freeLevel > 0 + ? ` [+${format(singUpg.freeLevel, 2, true)}]` : '') - upgradeText = upgradeText + (singUpg.freeLevels > 0 - ? ` =+= Effective Level: ${format(singUpg.actualTotalLevels(), 2, true)}` + upgradeText = upgradeText + (singUpg.freeLevel > 0 + ? ` =+= Effective Level: ${format(actualGQUpgradeTotalLevels(key), 2, true)}` : '') upgradeText = `${upgradeText}\n` @@ -418,25 +391,21 @@ export const generateExportSummary = async (): Promise => { singularityUpgradeStats = `${singularityUpgradeStats}Upgrades MAXED: ${totalSingUpgradeMax}/${ totalSingUpgradeCount - totalSingInfiniteLevel }\n` - singularityUpgradeStats = `${singularityUpgradeStats}Golden Quarks Spent on Upgrades: ${ - format(totalGoldenQuarksSpent, 0, true) - }\n` singularityUpgradeStats = singularityUpgradeStats + subCategoryDivisor } // Create Octeract Stuff let octeractUpgradeStats = '\n' - if (player.singularityUpgrades.octeractUnlock.getEffect().bonus) { + if (getGQUpgradeEffect('octeractUnlock')) { octeractUpgradeStats = '===== OCTERACT UPGRADES =====\n - [★]: Upgrade is MAXED - \n - [∞]: Upgrade is infinite - \n - [ ]: Upgrade INCOMPLETE - \n' - const octUpgrade = Object.keys(player.octeractUpgrades) as (keyof Player['octeractUpgrades'])[] + const octUpgrade = Object.keys(octeractUpgrades) as OcteractDataKeys[] let totalOctUpgradeCount = 0 let totalOctUpgradeMax = 0 - let totalOcteractsSpent = 0 for (const key of octUpgrade) { let upgradeText = '' - const octUpg = player.octeractUpgrades[key] + const octUpg = octeractUpgrades[key] if (octUpg.maxLevel !== -1) { totalOctUpgradeCount += 1 @@ -444,7 +413,6 @@ export const generateExportSummary = async (): Promise => { if (octUpg.level === octUpg.maxLevel) { totalOctUpgradeMax += 1 } - totalOcteractsSpent += octUpg.octeractsInvested let unicodeSymbol = '[ ]' if (octUpg.maxLevel === -1) { @@ -454,16 +422,16 @@ export const generateExportSummary = async (): Promise => { } upgradeText = upgradeText + unicodeSymbol - upgradeText = `${upgradeText + octUpg.name}:` + upgradeText = `${upgradeText} ${octUpg.name}:` upgradeText = upgradeText + (octUpg.maxLevel === -1 ? ` Level ${octUpg.level}` : ` Level ${octUpg.level}/${octUpg.maxLevel}`) - upgradeText = upgradeText + (octUpg.freeLevels > 0 - ? ` [+${format(octUpg.computeFreeLevelSoftcap(), 2, true)}]` + upgradeText = upgradeText + (octUpg.freeLevel > 0 + ? ` [+${format(computeOcteractFreeLevelSoftcap(key), 2, true)}]` : '') - upgradeText = upgradeText + (octUpg.freeLevels > 0 - ? ` =+= Effective Level: ${format(octUpg.actualTotalLevels(), 2, true)}` + upgradeText = upgradeText + (octUpg.freeLevel > 0 + ? ` =+= Effective Level: ${format(actualOcteractUpgradeTotalLevels(key), 2, true)}` : '') upgradeText = `${upgradeText}\n` @@ -471,14 +439,133 @@ export const generateExportSummary = async (): Promise => { } octeractUpgradeStats = octeractUpgradeStats + subCategoryDivisor octeractUpgradeStats = `${octeractUpgradeStats}Upgrades MAXED: ${totalOctUpgradeMax}/${totalOctUpgradeCount}\n` - octeractUpgradeStats = `${octeractUpgradeStats}Octeracts Spent on Upgrades: ${ - format(totalOcteractsSpent, 0, true) - }\n` octeractUpgradeStats = octeractUpgradeStats + subCategoryDivisor } + // Create EXALT Challenge Completion Stuff + let exaltChallengeStats = '\n' + if (player.highestSingularityCount >= 25) { + exaltChallengeStats = + '===== EXALT CHALLENGE COMPLETIONS =====\n - [✔]: Challenge Completed - \n - [✖]: Challenge NOT Completed - \n - [ ]: Challenge NOT Unlocked - \n' + + const exaltChallenges = Object.keys(player.singularityChallenges) as SingularityChallengeDataKeys[] + let totalExaltChallengeCompletions = 0 + let totalExaltChallengeMaxCompletions = 0 + + for (const key of exaltChallenges) { + let challengeText = '' + const exaltChallenge = player.singularityChallenges[key] + + if (exaltChallenge.unlockSingularity <= player.highestSingularityCount) { + if (exaltChallenge.completions === exaltChallenge.maxCompletions) { + challengeText = '[✔]' + } else { + challengeText = '[✖]' + } + totalExaltChallengeCompletions += exaltChallenge.completions + totalExaltChallengeMaxCompletions += exaltChallenge.maxCompletions + } else { + challengeText = '[ ]' + } + + challengeText = `${challengeText} ${exaltChallenge.name}:` + challengeText = `${challengeText} ${exaltChallenge.completions}/${exaltChallenge.maxCompletions}` + challengeText = `${challengeText}\n` + exaltChallengeStats = exaltChallengeStats + challengeText + } + + exaltChallengeStats = exaltChallengeStats + subCategoryDivisor + exaltChallengeStats = + `${exaltChallengeStats}Total Challenges Completed: ${totalExaltChallengeCompletions}/${totalExaltChallengeMaxCompletions}\n` + exaltChallengeStats = exaltChallengeStats + subCategoryDivisor + } + + // Create Octeract Stuff + let ambrosiaUpgradeStats = '\n' + if (player.visitedAmbrosiaSubtab) { + ambrosiaUpgradeStats = + '===== AMBROSIA UPGRADES =====\n - [★]: Upgrade is MAXED - \n - [𖥔]: Upgrade is ACTIVE - \n - [ ]: Upgrade INACTIVE - \n' + const ambUpgrade = Object.keys(ambrosiaUpgrades) as AmbrosiaUpgradeNames[] + + let spentBlueberries = 0 + + const currentAmbrosia = player.ambrosia + const lifetimeAmbrosia = player.lifetimeAmbrosia + + const blueberries = calculateBlueberryInventory() + + for (const key of ambUpgrade) { + let upgradeText = '' + const ambUpg = ambrosiaUpgrades[key] + + let unicodeSymbol = '[ ]' + if (ambUpg.level > 0) { + spentBlueberries += ambUpg.blueberryCost + unicodeSymbol = (ambUpg.level === ambUpg.maxLevel) ? '[★]' : '[𖥔]' + } + + upgradeText = upgradeText + unicodeSymbol + upgradeText = `${upgradeText} ${ambUpg.name}:` + upgradeText = `${upgradeText} Level ${ambUpg.level}/${ambUpg.maxLevel} [+${ + format(ambUpg.extraLevelCalc(), 0, true) + }]` + + upgradeText = upgradeText + (ambUpg.extraLevelCalc() > 0 + ? ` // Effective Level: ${format(ambUpg.extraLevelCalc(), 0, true)}` + : '') + + upgradeText = `${upgradeText}\n` + ambrosiaUpgradeStats = ambrosiaUpgradeStats + upgradeText + } + ambrosiaUpgradeStats = ambrosiaUpgradeStats + subCategoryDivisor + ambrosiaUpgradeStats = `${ambrosiaUpgradeStats} Current Ambrosia: ${format(currentAmbrosia, 0, true)}\n` + ambrosiaUpgradeStats = `${ambrosiaUpgradeStats} Lifetime Ambrosia: ${format(lifetimeAmbrosia, 0, true)}\n` + ambrosiaUpgradeStats = `${ambrosiaUpgradeStats} Total Blueberries: ${format(blueberries, 0, true)}\n` + ambrosiaUpgradeStats = `${ambrosiaUpgradeStats} Blueberries Spent: ${format(spentBlueberries, 0, true)}\n` + ambrosiaUpgradeStats = `${ambrosiaUpgradeStats} UNSPENT BLUEBERRIES: ${ + format(blueberries - spentBlueberries, 0, true) + }\n` + + ambrosiaUpgradeStats = ambrosiaUpgradeStats + subCategoryDivisor + } + + // Create Red Ambrosia Stuff + + let redAmbrosiaUpgradeStats = '\n' + if (player.visitedAmbrosiaSubtabRed) { + redAmbrosiaUpgradeStats = + '===== RED AMBROSIA UPGRADES =====\n - [★]: Upgrade is MAXED - \n - [ ]: Upgrade is NOT MAXED - \n' + const redAmbUpgrade = Object.keys(player.redAmbrosiaUpgrades) as (keyof Player['redAmbrosiaUpgrades'])[] + + const currentRedAmbrosia = player.redAmbrosia + const lifetimeRedAmbrosia = player.lifetimeRedAmbrosia + + for (const key of redAmbUpgrade) { + let upgradeText = '' + const redAmbUpg = redAmbrosiaUpgrades[key] + + const unicodeSymbol = (redAmbUpg.level === redAmbUpg.maxLevel) ? '[★]' : '[ ]' + + upgradeText = upgradeText + unicodeSymbol + upgradeText = `${upgradeText} ${redAmbUpg.name}:` + upgradeText = `${upgradeText} Level ${redAmbUpg.level}/${redAmbUpg.maxLevel}` + + upgradeText = `${upgradeText}\n` + redAmbrosiaUpgradeStats = redAmbrosiaUpgradeStats + upgradeText + } + redAmbrosiaUpgradeStats = redAmbrosiaUpgradeStats + subCategoryDivisor + redAmbrosiaUpgradeStats = `${redAmbrosiaUpgradeStats} Current Red Ambrosia: ${ + format(currentRedAmbrosia, 0, true) + }\n` + redAmbrosiaUpgradeStats = `${redAmbrosiaUpgradeStats} Lifetime Red Ambrosia: ${ + format(lifetimeRedAmbrosia, 0, true) + }\n` + + redAmbrosiaUpgradeStats = redAmbrosiaUpgradeStats + subCategoryDivisor + } + const returnString = - `${titleText}\n${time}\n${ver}\n${firstPlayed}${resources}${octeract}${singularity}${ascension}${reincarnation}${transcension}${prestige}${shopUpgradeStats}${singularityUpgradeStats}${octeractUpgradeStats}` + `${titleText}\n${time}\n${ver}\n${firstPlayed}${resources}${octeract}${singularity}${ascension}${reincarnation}${transcension}${prestige}${shopUpgradeStats}${singularityUpgradeStats}${octeractUpgradeStats}${exaltChallengeStats}${ambrosiaUpgradeStats}${redAmbrosiaUpgradeStats}` try { await navigator.clipboard.writeText(returnString) diff --git a/src/Synergism.ts b/src/Synergism.ts index 46fb13531..6c8c7f7dd 100644 --- a/src/Synergism.ts +++ b/src/Synergism.ts @@ -14,15 +14,26 @@ import { highestChallengeRewards, runChallengeSweep } from './Challenges' -import { btoa, cleanString, isDecimal, sortWithIndices, sumContents } from './Utility' +import { btoa, isMobileDevice, sumContents } from './Utility' import { blankGlobals, Globals as G } from './Variables' import { - achievementaward, - ascensionAchievementCheck, + achievementPoints, + awardAchievementGroup, + awardUngroupedAchievement, buildingAchievementCheck, - challengeachievementcheck, - resetachievementcheck + challengeAchievementCheck, + generateAchievementHTMLs, + getAchievementReward, + numAchievements, + type ProgressiveAchievements, + progressiveAchievements, + resetAchievementCheck, + updateAchievementPoints, + updateAllGroupedAchievementProgress, + updateAllProgressiveAchievementProgress, + updateAllUngroupedAchievementProgress, + updateProgressiveCache } from './Achievements' import { antSacrificePointsToMultiplier, autoBuyAnts, calculateCrumbToCoinExp } from './Ants' import { autoUpgrades } from './Automation' @@ -34,7 +45,6 @@ import { buyMax, buyMultiplier, buyParticleBuilding, - buyRuneBonusLevels, buyTesseractBuilding, calculateTessBuildingsInBudget, getCost, @@ -43,19 +53,16 @@ import { import { calculateAcceleratorMultiplier, calculateAnts, - calculateCubeBlessings, calculateGlobalSpeedMult, calculateGoldenQuarks, calculateObtainium, calculateOfferings, calculateOffline, - calculateRuneLevels, calculateSigmoidExponential, calculateTotalAcceleratorBoost, calculateTotalCoinOwned, dailyResetCheck, - exitOffline, - isShopTalismanUnlocked + exitOffline } from './Calculate' import { corruptionButtonsAdd, @@ -69,14 +76,25 @@ import { updateCorruptionLoadoutNames, updateUndefinedLoadouts } from './Corruptions' -import { updateCubeUpgradeBG } from './Cubes' +import { + calculateAcceleratorCubeBlessing, + calculateAntSpeedCubeBlessing, + calculateMultiplierCubeBlessing, + updateCubeUpgradeBG +} from './Cubes' import { generateEventHandlers } from './EventListeners' import { addTimers, automaticTools } from './Helper' import { resetHistoryRenderAllTables } from './History' -import { calculateHypercubeBlessings } from './Hypercubes' -import { calculatePlatonicBlessings } from './PlatonicCubes' -import { buyResearch, maxRoombaResearchIndex, updateResearchBG } from './Research' -import { autoResearchEnabled } from './Research' +import { + buyResearch, + refundOvercapResearches, + researchData, + researchOrderByCost, + roombaResearchEnabled, + updateResearchAuto, + updateResearchBG, + updateResearchRoomba +} from './Research' import { reset, resetrepeat, @@ -87,18 +105,30 @@ import { updateSingularityGlobalPerks, updateTesseractAutoBuyAmount } from './Reset' -import { redeemShards } from './Runes' +import { + generateRunesHTML, + getRuneEffects, + indexToRune, + type RuneKeys, + runes, + sacrificeOfferings, + sumOfRuneLevels, + updateAllRuneLevelsFromEXP +} from './Runes' import { c15RewardUpdate } from './Statistics' import { - buyTalismanEnhance, - buyTalismanLevels, - calculateMaxTalismanLevel, + buyTalismanLevelToRarityIncrease, + generateTalismansHTML, + noTalismanFragments, + type TalismanCraftItems, + type TalismanKeys, + talismans, toggleTalismanBuy, - updateTalismanAppearance, - updateTalismanInventory + updateTalismanInventory, + updateTalismanLevelAndSpentFromInvested, + updateTalismanRarities } from './Talismans' import { calculatetax } from './Tax' -import { calculateTesseractBlessings } from './Tesseracts' import { autoCubeUpgradesToggle, autoPlatonicUpgradesToggle, @@ -123,7 +153,6 @@ import { Notification, revealStuff, showCorruptionStatsLoadouts, - updateAchievementBG, updateChallengeDisplay, updateChallengeLevel } from './UpdateHTML' @@ -139,9 +168,11 @@ import { import i18next from 'i18next' import rfdc from 'rfdc' import { - BlueberryUpgrade, - blueberryUpgradeData, + type AmbrosiaUpgradeNames, + ambrosiaUpgrades, + blankAmbrosiaUpgradeObject, displayProperLoadoutCount, + setAmbrosiaUpgradeLevels, updateBlueberryLoadoutCount } from './BlueberryUpgrades' import { DOMCacheGetOrSet } from './Cache/DOM' @@ -149,36 +180,71 @@ import { campaignIconHTMLUpdates, CampaignManager, campaignTokenRewardHTMLUpdate, - createCampaignIconHTMLS + createCampaignIconHTMLS, + updateMaxTokens, + updateTokens } from './Campaign' import { dev, lastUpdated, prod, testing, version } from './Config' import { WowCubes, WowHypercubes, WowPlatonicCubes, WowTesseracts } from './CubeExperimental' import { eventCheck } from './Event' import { - AbyssHepteract, - AcceleratorBoostHepteract, - AcceleratorHepteract, - ChallengeHepteract, - ChronosHepteract, - HepteractCraft, - hepteractEffective, - HyperrealismHepteract, - MultiplierHepteract, - QuarkHepteract, + defaultHepteractValues, + getHepteractEffects, + type HepteractKeys, + hepteracts, + type HepteractValues, + setAutomaticHepteractTexts, toggleAutoBuyOrbs } from './Hepteracts' import { disableHotkeys } from './Hotkeys' import { init as i18nInit } from './i18n' +import { generateLevelMilestoneHTMLS, generateLevelRewardHTMLs, getLevelMilestone } from './Levels' import { handleLogin } from './Login' -import { octeractData, OcteractUpgrade } from './Octeracts' +import { fetchUnreadMessages } from './Messages' +import { + blankOcteractLevelObject, + getOcteractUpgradeEffect, + type OcteractDataKeys, + octeractUpgrades +} from './Octeracts' import { updatePlatonicUpgradeBG } from './Platonic' import { initializePCoinCache, PCoinUpgradeEffects } from './PseudoCoinUpgrades' import { getQuarkBonus, QuarkHandler } from './Quark' -import { initRedAmbrosiaUpgrades } from './RedAmbrosiaUpgrades' +import { + blankRedAmbrosiaUpgradeObject, + type RedAmbrosiaNames, + redAmbrosiaUpgrades, + setRedAmbrosiaUpgradeLevels +} from './RedAmbrosiaUpgrades' +import { + buyBlessingLevels, + generateBlessingsHTML, + getRuneBlessingEffect, + type RuneBlessingKeys, + runeBlessings, + updateAllBlessingLevelsFromEXP +} from './RuneBlessings' +import { + buySpiritLevels, + generateSpiritsHTML, + getRuneSpiritEffect, + type RuneSpiritKeys, + runeSpirits, + updateAllSpiritLevelsFromEXP +} from './RuneSpirits' import { playerJsonSchema } from './saves/PlayerJsonSchema' import { playerUpdateVarSchema } from './saves/PlayerUpdateVarSchema' -import { getFastForwardTotalMultiplier, singularityData, SingularityUpgrade } from './singularity' -import { SingularityChallenge, singularityChallengeData } from './SingularityChallenges' +import { + blankGQLevelObject, + getFastForwardTotalMultiplier, + goldenQuarkUpgrades, + type SingularityDataKeys +} from './singularity' +import { + SingularityChallenge, + singularityChallengeData, + type SingularityChallengeDataKeys +} from './SingularityChallenges' import { changeSubTab, changeTab, getActiveSubTab, Tabs } from './Tabs' import { settingAnnotation, toggleIconSet, toggleTheme } from './Themes' import { clearTimeout, clearTimers, setInterval, setTimeout } from './Timers' @@ -443,11 +509,11 @@ export const player: Player = { reincarnation: 0, ascension: 0 }, - researchPoints: 0, + + obtainium: new Decimal('0'), + maxObtainium: new Decimal('0'), + obtainiumtimer: 0, - obtainiumpersecond: 0, - maxobtainiumpersecond: 0, - maxobtainium: 0, // Ignore the first index. The other 25 are shaped in a 5x5 grid similar to the production appearance // dprint-ignore researches: [ @@ -474,9 +540,30 @@ export const player: Player = { rrow1: false, rrow2: false, rrow3: false, - rrow4: false + rrow4: false, + anthill: false, + blessings: false, + spirits: false, + talismans: false, + ascensions: false, + tesseracts: false, + hypercubes: false, + platonics: false, + hepteracts: false + }, + achievements: Array(numAchievements).fill(0) as number[], + progressiveAchievements: { + runeLevel: 0, + freeRuneLevel: 0, + singularityCount: 0, + ambrosiaCount: 0, + redAmbrosiaCount: 0, + exalts: 0, + talismanRarities: 0, + singularityUpgrades: 0, + octeractUpgrades: 0, + redAmbrosiaUpgrades: 0 }, - achievements: Array(281).fill(0) as number[], achievementPoints: 0, @@ -497,11 +584,36 @@ export const player: Player = { crystalUpgrades: [0, 0, 0, 0, 0, 0, 0, 0], crystalUpgradesCost: [7, 15, 20, 40, 100, 200, 500, 1000], - runelevels: [1, 1, 1, 1, 1, 0, 0], - runeexp: [0, 0, 0, 0, 0, 0, 0], - runeshards: 0, - maxofferings: 0, - offeringpersecond: 0, + runes: { + speed: new Decimal(0), + duplication: new Decimal(0), + prism: new Decimal(0), + thrift: new Decimal(0), + superiorIntellect: new Decimal(0), + infiniteAscent: new Decimal(0), + antiquities: new Decimal(0), + horseShoe: new Decimal(0), + finiteDescent: new Decimal(0) + }, + + runeBlessings: { + speed: new Decimal(0), + duplication: new Decimal(0), + prism: new Decimal(0), + thrift: new Decimal(0), + superiorIntellect: new Decimal(0) + }, + + runeSpirits: { + speed: new Decimal(0), + duplication: new Decimal(0), + prism: new Decimal(0), + thrift: new Decimal(0), + superiorIntellect: new Decimal(0) + }, + + offerings: new Decimal('0'), + maxOfferings: new Decimal('0'), prestigecounter: 0, transcendcounter: 0, @@ -629,7 +741,8 @@ export const player: Player = { shopRedLuck1: 0, shopRedLuck2: 0, shopRedLuck3: 0, - shopInfiniteShopUpgrades: 0 + shopInfiniteShopUpgrades: 0, + shopHorseShoe: 0 }, shopPotionsConsumed: { @@ -662,22 +775,27 @@ export const player: Player = { antSacrificeTimer: 0, antSacrificeTimerReal: 0, - talismanLevels: [0, 0, 0, 0, 0, 0, 0], - talismanRarity: [1, 1, 1, 1, 1, 1, 1], - talismanOne: [null, -1, 1, 1, 1, -1], - talismanTwo: [null, 1, 1, -1, -1, 1], - talismanThree: [null, 1, -1, 1, 1, -1], - talismanFour: [null, -1, -1, 1, 1, 1], - talismanFive: [null, 1, 1, -1, -1, 1], - talismanSix: [null, 1, 1, 1, -1, -1], - talismanSeven: [null, -1, 1, -1, 1, 1], - talismanShards: 0, - commonFragments: 0, - uncommonFragments: 0, - rareFragments: 0, - epicFragments: 0, - legendaryFragments: 0, - mythicalFragments: 0, + talismans: { + exemption: noTalismanFragments, + chronos: noTalismanFragments, + midas: noTalismanFragments, + metaphysics: noTalismanFragments, + polymath: noTalismanFragments, + mortuus: noTalismanFragments, + plastic: noTalismanFragments, + wowSquare: noTalismanFragments, + achievement: noTalismanFragments, + cookieGrandma: noTalismanFragments, + horseShoe: noTalismanFragments + }, + + talismanShards: new Decimal(0), + commonFragments: new Decimal(0), + uncommonFragments: new Decimal(0), + rareFragments: new Decimal(0), + epicFragments: new Decimal(0), + legendaryFragments: new Decimal(0), + mythicalFragments: new Decimal(0), buyTalismanShardPercent: 10, @@ -855,7 +973,18 @@ export const player: Player = { globalSpeed: 0 }, - hepteractCrafts: { + hepteracts: { + chronos: { ...defaultHepteractValues }, + hyperrealism: { ...defaultHepteractValues }, + quark: { ...defaultHepteractValues }, + challenge: { ...defaultHepteractValues }, + abyss: { ...defaultHepteractValues }, + accelerator: { ...defaultHepteractValues }, + acceleratorBoost: { ...defaultHepteractValues }, + multiplier: { ...defaultHepteractValues } + }, + + /*hepteractCrafts: { chronos: ChronosHepteract, hyperrealism: HyperrealismHepteract, quark: QuarkHepteract, @@ -864,7 +993,7 @@ export const player: Player = { accelerator: AcceleratorHepteract, acceleratorBoost: AcceleratorBoostHepteract, multiplier: MultiplierHepteract - }, + },*/ ascendShards: new Decimal('0'), autoAscend: false, @@ -945,8 +1074,6 @@ export const player: Player = { enter: 2 }, - runeBlessingLevels: [0, 0, 0, 0, 0, 0], - runeSpiritLevels: [0, 0, 0, 0, 0, 0], runeBlessingBuyAmount: 0, runeSpiritBuyAmount: 0, @@ -997,450 +1124,9 @@ export const player: Player = { iconSet: 1, notation: 'Default', - singularityUpgrades: { - goldenQuarks1: new SingularityUpgrade( - singularityData.goldenQuarks1, - 'goldenQuarks1' - ), - goldenQuarks2: new SingularityUpgrade( - singularityData.goldenQuarks2, - 'goldenQuarks2' - ), - goldenQuarks3: new SingularityUpgrade( - singularityData.goldenQuarks3, - 'goldenQuarks3' - ), - starterPack: new SingularityUpgrade( - singularityData.starterPack, - 'starterPack' - ), - wowPass: new SingularityUpgrade(singularityData.wowPass, 'wowPass'), - cookies: new SingularityUpgrade(singularityData.cookies, 'cookies'), - cookies2: new SingularityUpgrade(singularityData.cookies2, 'cookies2'), - cookies3: new SingularityUpgrade(singularityData.cookies3, 'cookies3'), - cookies4: new SingularityUpgrade(singularityData.cookies4, 'cookies4'), - cookies5: new SingularityUpgrade(singularityData.cookies5, 'cookies5'), - ascensions: new SingularityUpgrade( - singularityData.ascensions, - 'ascensions' - ), - corruptionFourteen: new SingularityUpgrade( - singularityData.corruptionFourteen, - 'corruptionFourteen' - ), - corruptionFifteen: new SingularityUpgrade( - singularityData.corruptionFifteen, - 'corruptionFifteen' - ), - singOfferings1: new SingularityUpgrade( - singularityData.singOfferings1, - 'singOfferings1' - ), - singOfferings2: new SingularityUpgrade( - singularityData.singOfferings2, - 'singOfferings2' - ), - singOfferings3: new SingularityUpgrade( - singularityData.singOfferings3, - 'singOfferings3' - ), - singObtainium1: new SingularityUpgrade( - singularityData.singObtainium1, - 'singObtainium1' - ), - singObtainium2: new SingularityUpgrade( - singularityData.singObtainium2, - 'singObtainium2' - ), - singObtainium3: new SingularityUpgrade( - singularityData.singObtainium3, - 'singObtainium3' - ), - singCubes1: new SingularityUpgrade( - singularityData.singCubes1, - 'singCubes1' - ), - singCubes2: new SingularityUpgrade( - singularityData.singCubes2, - 'singCubes2' - ), - singCubes3: new SingularityUpgrade( - singularityData.singCubes3, - 'singCubes3' - ), - singBonusTokens1: new SingularityUpgrade( - singularityData.singBonusTokens1, - 'singBonusTokens1' - ), - singBonusTokens2: new SingularityUpgrade( - singularityData.singBonusTokens2, - 'singBonusTokens2' - ), - singBonusTokens3: new SingularityUpgrade( - singularityData.singBonusTokens3, - 'singBonusTokens3' - ), - singBonusTokens4: new SingularityUpgrade( - singularityData.singBonusTokens4, - 'singBonusTokens4' - ), - singCitadel: new SingularityUpgrade( - singularityData.singCitadel, - 'singCitadel' - ), - singCitadel2: new SingularityUpgrade( - singularityData.singCitadel2, - 'singCitadel2' - ), - octeractUnlock: new SingularityUpgrade( - singularityData.octeractUnlock, - 'octeractUnlock' - ), - singOcteractPatreonBonus: new SingularityUpgrade( - singularityData.singOcteractPatreonBonus, - 'singOcteractPatreonBonus' - ), - intermediatePack: new SingularityUpgrade( - singularityData.intermediatePack, - 'intermediatePack' - ), - advancedPack: new SingularityUpgrade( - singularityData.advancedPack, - 'advancedPack' - ), - expertPack: new SingularityUpgrade( - singularityData.expertPack, - 'expertPack' - ), - masterPack: new SingularityUpgrade( - singularityData.masterPack, - 'masterPack' - ), - divinePack: new SingularityUpgrade( - singularityData.divinePack, - 'divinePack' - ), - wowPass2: new SingularityUpgrade(singularityData.wowPass2, 'wowPass2'), - potionBuff: new SingularityUpgrade( - singularityData.potionBuff, - 'potionBuff' - ), - potionBuff2: new SingularityUpgrade( - singularityData.potionBuff2, - 'potionBuff2' - ), - potionBuff3: new SingularityUpgrade( - singularityData.potionBuff3, - 'potionBuff3' - ), - singChallengeExtension: new SingularityUpgrade( - singularityData.singChallengeExtension, - 'singChallengeExtension' - ), - singChallengeExtension2: new SingularityUpgrade( - singularityData.singChallengeExtension2, - 'singChallengeExtension2' - ), - singChallengeExtension3: new SingularityUpgrade( - singularityData.singChallengeExtension3, - 'singChallengeExtension3' - ), - singQuarkImprover1: new SingularityUpgrade( - singularityData.singQuarkImprover1, - 'singQuarkImprover1' - ), - singQuarkHepteract: new SingularityUpgrade( - singularityData.singQuarkHepteract, - 'singQuarkHepteract' - ), - singQuarkHepteract2: new SingularityUpgrade( - singularityData.singQuarkHepteract2, - 'singQuarkHepteract2' - ), - singQuarkHepteract3: new SingularityUpgrade( - singularityData.singQuarkHepteract3, - 'singQuarkHepteract3' - ), - singOcteractGain: new SingularityUpgrade( - singularityData.singOcteractGain, - 'singOcteractGain' - ), - singOcteractGain2: new SingularityUpgrade( - singularityData.singOcteractGain2, - 'singOcteractGain2' - ), - singOcteractGain3: new SingularityUpgrade( - singularityData.singOcteractGain3, - 'singOcteractGain3' - ), - singOcteractGain4: new SingularityUpgrade( - singularityData.singOcteractGain4, - 'singOcteractGain4' - ), - singOcteractGain5: new SingularityUpgrade( - singularityData.singOcteractGain5, - 'singOcteractGain5' - ), - wowPass3: new SingularityUpgrade(singularityData.wowPass3, 'wowPass3'), - ultimatePen: new SingularityUpgrade( - singularityData.ultimatePen, - 'ultimatePen' - ), - platonicTau: new SingularityUpgrade( - singularityData.platonicTau, - 'platonicTau' - ), - platonicAlpha: new SingularityUpgrade( - singularityData.platonicAlpha, - 'platonicAlpha' - ), - platonicDelta: new SingularityUpgrade( - singularityData.platonicDelta, - 'platonicDelta' - ), - platonicPhi: new SingularityUpgrade( - singularityData.platonicPhi, - 'platonicPhi' - ), - singFastForward: new SingularityUpgrade( - singularityData.singFastForward, - 'singFastForward' - ), - singFastForward2: new SingularityUpgrade( - singularityData.singFastForward2, - 'singFastForward2' - ), - singAscensionSpeed: new SingularityUpgrade( - singularityData.singAscensionSpeed, - 'singAscensionSpeed' - ), - singAscensionSpeed2: new SingularityUpgrade( - singularityData.singAscensionSpeed2, - 'singAscensionSpeed2' - ), - halfMind: new SingularityUpgrade(singularityData.halfMind, 'halfMind'), - oneMind: new SingularityUpgrade(singularityData.oneMind, 'oneMind'), - wowPass4: new SingularityUpgrade(singularityData.wowPass4, 'wowPass4'), - offeringAutomatic: new SingularityUpgrade( - singularityData.offeringAutomatic, - 'offeringAutomatic' - ), - blueberries: new SingularityUpgrade( - singularityData.blueberries, - 'blueberries' - ), - singAmbrosiaLuck: new SingularityUpgrade( - singularityData.singAmbrosiaLuck, - 'singAmbrosiaLuck' - ), - singAmbrosiaLuck2: new SingularityUpgrade( - singularityData.singAmbrosiaLuck2, - 'singAmbrosiaLuck2' - ), - singAmbrosiaLuck3: new SingularityUpgrade( - singularityData.singAmbrosiaLuck3, - 'singAmbrosiaLuck3' - ), - singAmbrosiaLuck4: new SingularityUpgrade( - singularityData.singAmbrosiaLuck4, - 'singAmbrosiaLuck4' - ), - singAmbrosiaGeneration: new SingularityUpgrade( - singularityData.singAmbrosiaGeneration, - 'singAmbrosiaGeneration' - ), - singAmbrosiaGeneration2: new SingularityUpgrade( - singularityData.singAmbrosiaGeneration2, - 'singAmbrosiaGeneration2' - ), - singAmbrosiaGeneration3: new SingularityUpgrade( - singularityData.singAmbrosiaGeneration3, - 'singAmbrosiaGeneration3' - ), - singAmbrosiaGeneration4: new SingularityUpgrade( - singularityData.singAmbrosiaGeneration4, - 'singAmbrosiaGeneration4' - ), - singInfiniteShopUpgrades: new SingularityUpgrade( - singularityData.singInfiniteShopUpgrades, - 'singInfiniteShopUpgrades' - ) - }, - - octeractUpgrades: { - octeractStarter: new OcteractUpgrade( - octeractData.octeractStarter, - 'octeractStarter' - ), - octeractGain: new OcteractUpgrade( - octeractData.octeractGain, - 'octeractGain' - ), - octeractGain2: new OcteractUpgrade( - octeractData.octeractGain2, - 'octeractGain2' - ), - octeractQuarkGain: new OcteractUpgrade( - octeractData.octeractQuarkGain, - 'octeractQuarkGain' - ), - octeractQuarkGain2: new OcteractUpgrade( - octeractData.octeractQuarkGain2, - 'octeractQuarkGain2' - ), - octeractCorruption: new OcteractUpgrade( - octeractData.octeractCorruption, - 'octeractCorruption' - ), - octeractGQCostReduce: new OcteractUpgrade( - octeractData.octeractGQCostReduce, - 'octeractGQCostReduce' - ), - octeractExportQuarks: new OcteractUpgrade( - octeractData.octeractExportQuarks, - 'octeractExportQuarks' - ), - octeractImprovedDaily: new OcteractUpgrade( - octeractData.octeractImprovedDaily, - 'octeractImprovedDaily' - ), - octeractImprovedDaily2: new OcteractUpgrade( - octeractData.octeractImprovedDaily2, - 'octeractImprovedDaily2' - ), - octeractImprovedDaily3: new OcteractUpgrade( - octeractData.octeractImprovedDaily3, - 'octeractImprovedDaily3' - ), - octeractImprovedQuarkHept: new OcteractUpgrade( - octeractData.octeractImprovedQuarkHept, - 'octeractImprovedQuarkHept' - ), - octeractImprovedGlobalSpeed: new OcteractUpgrade( - octeractData.octeractImprovedGlobalSpeed, - 'octeractImprovedGlobalSpeed' - ), - octeractImprovedAscensionSpeed: new OcteractUpgrade( - octeractData.octeractImprovedAscensionSpeed, - 'octeractImprovedAscensionSpeed' - ), - octeractImprovedAscensionSpeed2: new OcteractUpgrade( - octeractData.octeractImprovedAscensionSpeed2, - 'octeractImprovedAscensionSpeed2' - ), - octeractImprovedFree: new OcteractUpgrade( - octeractData.octeractImprovedFree, - 'octeractImprovedFree' - ), - octeractImprovedFree2: new OcteractUpgrade( - octeractData.octeractImprovedFree2, - 'octeractImprovedFree2' - ), - octeractImprovedFree3: new OcteractUpgrade( - octeractData.octeractImprovedFree3, - 'octeractImprovedFree3' - ), - octeractImprovedFree4: new OcteractUpgrade( - octeractData.octeractImprovedFree4, - 'octeractImprovedFree4' - ), - octeractSingUpgradeCap: new OcteractUpgrade( - octeractData.octeractSingUpgradeCap, - 'octeractSingUpgradeCap' - ), - octeractOfferings1: new OcteractUpgrade( - octeractData.octeractOfferings1, - 'octeractOfferings1' - ), - octeractObtainium1: new OcteractUpgrade( - octeractData.octeractObtainium1, - 'octeractObtainium1' - ), - octeractAscensions: new OcteractUpgrade( - octeractData.octeractAscensions, - 'octeractAscensions' - ), - octeractAscensions2: new OcteractUpgrade( - octeractData.octeractAscensions2, - 'octeractAscensions2' - ), - octeractAscensionsOcteractGain: new OcteractUpgrade( - octeractData.octeractAscensionsOcteractGain, - 'octeractAscensionsOcteractGain' - ), - octeractFastForward: new OcteractUpgrade( - octeractData.octeractFastForward, - 'octeractFastForward' - ), - octeractAutoPotionSpeed: new OcteractUpgrade( - octeractData.octeractAutoPotionSpeed, - 'octeractAutoPotionSpeed' - ), - octeractAutoPotionEfficiency: new OcteractUpgrade( - octeractData.octeractAutoPotionEfficiency, - 'octeractAutoPotionEfficiency' - ), - octeractOneMindImprover: new OcteractUpgrade( - octeractData.octeractOneMindImprover, - 'octeractOneMindImprover' - ), - octeractAmbrosiaLuck: new OcteractUpgrade( - octeractData.octeractAmbrosiaLuck, - 'octeractAmbrosiaLuck' - ), - octeractAmbrosiaLuck2: new OcteractUpgrade( - octeractData.octeractAmbrosiaLuck2, - 'octeractAmbrosiaLuck2' - ), - octeractAmbrosiaLuck3: new OcteractUpgrade( - octeractData.octeractAmbrosiaLuck3, - 'octeractAmbrosiaLuck3' - ), - octeractAmbrosiaLuck4: new OcteractUpgrade( - octeractData.octeractAmbrosiaLuck4, - 'octeractAmbrosiaLuck4' - ), - octeractAmbrosiaGeneration: new OcteractUpgrade( - octeractData.octeractAmbrosiaGeneration, - 'octeractAmbrosiaGeneration' - ), - octeractAmbrosiaGeneration2: new OcteractUpgrade( - octeractData.octeractAmbrosiaGeneration2, - 'octeractAmbrosiaGeneration2' - ), - octeractAmbrosiaGeneration3: new OcteractUpgrade( - octeractData.octeractAmbrosiaGeneration3, - 'octeractAmbrosiaGeneration3' - ), - octeractAmbrosiaGeneration4: new OcteractUpgrade( - octeractData.octeractAmbrosiaGeneration4, - 'octeractAmbrosiaGeneration4' - ), - octeractBonusTokens1: new OcteractUpgrade( - octeractData.octeractBonusTokens1, - 'octeractBonusTokens1' - ), - octeractBonusTokens2: new OcteractUpgrade( - octeractData.octeractBonusTokens2, - 'octeractBonusTokens2' - ), - octeractBonusTokens3: new OcteractUpgrade( - octeractData.octeractBonusTokens3, - 'octeractBonusTokens3' - ), - octeractBonusTokens4: new OcteractUpgrade( - octeractData.octeractBonusTokens4, - 'octeractBonusTokens4' - ), - octeractBlueberries: new OcteractUpgrade( - octeractData.octeractBlueberries, - 'octeractBlueberries' - ), - octeractInfiniteShopUpgrades: new OcteractUpgrade( - octeractData.octeractInfiniteShopUpgrades, - 'octeractInfiniteShopUpgrades' - ) - }, + goldenQuarkUpgrades: blankGQLevelObject, + octUpgrades: blankOcteractLevelObject, + ambrosiaUpgrades: blankAmbrosiaUpgradeObject, dailyCodeUsed: false, hepteractAutoCraftPercentage: 50, @@ -1475,6 +1161,10 @@ export const player: Player = { sadisticPrequel: new SingularityChallenge( singularityChallengeData.sadisticPrequel, 'sadisticPrequel' + ), + taxmanLastStand: new SingularityChallenge( + singularityChallengeData.taxmanLastStand, + 'taxmanLastStand' ) }, @@ -1485,120 +1175,6 @@ export const player: Player = { visitedAmbrosiaSubtab: false, visitedAmbrosiaSubtabRed: false, spentBlueberries: 0, - blueberryUpgrades: { - ambrosiaTutorial: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaTutorial, - 'ambrosiaTutorial' - ), - ambrosiaQuarks1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaQuarks1, - 'ambrosiaQuarks1' - ), - ambrosiaCubes1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaCubes1, - 'ambrosiaQuarks1' - ), - ambrosiaLuck1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaLuck1, - 'ambrosiaLuck1' - ), - ambrosiaCubeQuark1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaCubeQuark1, - 'ambrosiaCubeQuark1' - ), - ambrosiaLuckQuark1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaLuckQuark1, - 'ambrosiaLuckQuark1' - ), - ambrosiaLuckCube1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaLuckCube1, - 'ambrosiaLuckCube1' - ), - ambrosiaQuarkCube1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaQuarkCube1, - 'ambrosiaQuarkCube1' - ), - ambrosiaCubeLuck1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaCubeLuck1, - 'ambrosiaCubeLuck1' - ), - ambrosiaQuarkLuck1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaQuarkLuck1, - 'ambrosiaQuarkLuck1' - ), - ambrosiaQuarks2: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaQuarks2, - 'ambrosiaQuarks2' - ), - ambrosiaCubes2: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaCubes2, - 'ambrosiaQuarks2' - ), - ambrosiaLuck2: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaLuck2, - 'ambrosiaLuck2' - ), - ambrosiaQuarks3: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaQuarks3, - 'ambrosiaQuarks3' - ), - ambrosiaCubes3: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaCubes3, - 'ambrosiaQuarks3' - ), - ambrosiaLuck3: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaLuck3, - 'ambrosiaLuck3' - ), - ambrosiaPatreon: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaPatreon, - 'ambrosiaPatreon' - ), - ambrosiaObtainium1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaObtainium1, - 'ambrosiaObtainium1' - ), - ambrosiaOffering1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaOffering1, - 'ambrosiaOffering1' - ), - ambrosiaHyperflux: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaHyperflux, - 'ambrosiaHyperflux' - ), - ambrosiaBaseObtainium1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaBaseObtainium1, - 'ambrosiaBaseObtainium1' - ), - ambrosiaBaseOffering1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaBaseOffering1, - 'ambrosiaBaseOffering1' - ), - ambrosiaBaseObtainium2: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaBaseObtainium2, - 'ambrosiaBaseObtainium2' - ), - ambrosiaBaseOffering2: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaBaseOffering2, - 'ambrosiaBaseOffering2' - ), - ambrosiaSingReduction1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaSingReduction1, - 'ambrosiaSingReduction1' - ), - ambrosiaInfiniteShopUpgrades1: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaInfiniteShopUpgrades1, - 'ambrosiaInfiniteShopUpgrades' - ), - ambrosiaInfiniteShopUpgrades2: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaInfiniteShopUpgrades2, - 'ambrosiaInfiniteShopUpgrades2' - ), - ambrosiaSingReduction2: new BlueberryUpgrade( - blueberryUpgradeData.ambrosiaSingReduction2, - 'ambrosiaSingReduction2' - ) - }, blueberryLoadouts: { 1: {}, @@ -1625,30 +1201,7 @@ export const player: Player = { redAmbrosiaTime: 0, // NOTE: This only keeps track of the total number of Red Ambrosia // Invested, because I realized that keeping classes on the player is generally a bad idea - redAmbrosiaUpgrades: { - 'tutorial': 0, - 'conversionImprovement1': 0, - 'conversionImprovement2': 0, - 'conversionImprovement3': 0, - 'freeTutorialLevels': 0, - 'freeLevelsRow2': 0, - 'freeLevelsRow3': 0, - 'freeLevelsRow4': 0, - 'freeLevelsRow5': 0, - 'blueberryGenerationSpeed': 0, - 'blueberryGenerationSpeed2': 0, - 'regularLuck': 0, - 'regularLuck2': 0, - 'redGenerationSpeed': 0, - 'redLuck': 0, - 'redAmbrosiaCube': 0, - 'redAmbrosiaObtainium': 0, - 'redAmbrosiaOffering': 0, - 'redAmbrosiaCubeImprover': 0, - 'viscount': 0, - 'infiniteShopUpgrades': 0, - 'redAmbrosiaAccelerator': 0 - }, + redAmbrosiaUpgrades: blankRedAmbrosiaUpgradeObject, singChallengeTimer: 0, @@ -1668,14 +1221,10 @@ export const deepClone = () => [WowTesseracts, (o: WowTesseracts) => new WowTesseracts(o.valueOf())], [WowHypercubes, (o: WowHypercubes) => new WowHypercubes(o.valueOf())], [WowPlatonicCubes, (o: WowPlatonicCubes) => new WowPlatonicCubes(o.valueOf())], - [HepteractCraft, (o: HepteractCraft) => new HepteractCraft(o.valueOf())], [CorruptionLoadout, (o: CorruptionLoadout) => new CorruptionLoadout(o.loadout)], [CorruptionSaves, (o: CorruptionSaves) => new CorruptionSaves(o.corrSaveData)], [CampaignManager, (o: CampaignManager) => new CampaignManager(o.campaignManagerData)], - [SingularityUpgrade, (o: SingularityUpgrade) => new SingularityUpgrade(o.valueOf(), o.key())], - [OcteractUpgrade, (o: OcteractUpgrade) => new OcteractUpgrade(o.valueOf(), o.key())], - [SingularityChallenge, (o: SingularityChallenge) => new SingularityChallenge(o.valueOf(), o.key())], - [BlueberryUpgrade, (o: BlueberryUpgrade) => new BlueberryUpgrade(o.valueOf(), o.key())] + [SingularityChallenge, (o: SingularityChallenge) => new SingularityChallenge(o.valueOf(), o.key())] ] }) @@ -1686,6 +1235,77 @@ export const saveSynergy = (button?: boolean) => { player.loaded1009 = true player.loaded1009hotfix1 = true + // save to player.goldenQuarkUpgrades, taking the level and freeLevel from corresponding goldenQuarkUpgrades from singularity.ts + player.goldenQuarkUpgrades = Object.fromEntries( + Object.keys(player.goldenQuarkUpgrades).map((key) => { + const k = key as SingularityDataKeys + const gqu = goldenQuarkUpgrades[k] + return [key, { level: gqu.level, freeLevel: gqu.freeLevel, goldenQuarksInvested: gqu.goldenQuarksInvested }] + }) + ) as Record + + player.octUpgrades = Object.fromEntries( + Object.keys(player.octUpgrades).map((key) => { + const k = key as OcteractDataKeys + const ou = octeractUpgrades[k] + return [key, { level: ou.level, freeLevel: ou.freeLevel, octeractsInvested: ou.octeractsInvested }] + }) + ) as Record + + player.ambrosiaUpgrades = Object.fromEntries( + Object.keys(player.ambrosiaUpgrades).map((key) => { + const k = key as AmbrosiaUpgradeNames + const au = ambrosiaUpgrades[k] + return [key, { ambrosiaInvested: au.ambrosiaInvested, blueberriesInvested: au.blueberriesInvested }] + }) + ) as Record + + player.redAmbrosiaUpgrades = Object.fromEntries( + Object.keys(player.redAmbrosiaUpgrades).map((key) => { + const k = key as RedAmbrosiaNames + return [key, redAmbrosiaUpgrades[k].redAmbrosiaInvested] + }) + ) as Record + + player.talismans = Object.fromEntries( + Object.keys(player.talismans).map((key) => { + const k = key as TalismanKeys + return [key, { ...talismans[k].fragmentsInvested }] + }) + ) as Record> + + player.runes = Object.fromEntries( + Object.keys(player.runes).map((key) => { + const k = key as RuneKeys + return [key, new Decimal(runes[k].runeEXP)] + }) + ) as Record + + player.runeBlessings = Object.fromEntries( + Object.keys(player.runeBlessings).map((key) => { + const k = key as RuneBlessingKeys + return [key, new Decimal(runeBlessings[k].runeEXP)] + }) + ) as Record + + player.runeSpirits = Object.fromEntries( + Object.keys(player.runeSpirits).map((key) => { + const k = key as RuneSpiritKeys + return [key, new Decimal(runeSpirits[k].runeEXP)] + }) + ) as Record + + player.hepteracts = Object.fromEntries( + Object.keys(player.hepteracts).map((key) => { + const k = key as HepteractKeys + return [key, { + BAL: hepteracts[k].BAL, + TIMES_CAP_EXTENDED: hepteracts[k].TIMES_CAP_EXTENDED, + AUTO: hepteracts[k].AUTO + }] + }) + ) as Record + const p = playerJsonSchema.parse(player) const save = btoa(JSON.stringify(p)) if (save !== null) { @@ -1778,7 +1398,7 @@ const loadSynergy = () => { // TODO(@KhafraDev): remove G.currentSingChallenge // fix current sing challenge blank if (player.insideSingularityChallenge) { - const challenges = Object.keys(player.singularityChallenges) + const challenges = Object.keys(player.singularityChallenges) as SingularityChallengeDataKeys[] for (let i = 0; i < challenges.length; i++) { if (player.singularityChallenges[challenges[i]].enabled) { G.currentSingChallenge = singularityChallengeData[challenges[i]].HTMLTag @@ -1787,10 +1407,6 @@ const loadSynergy = () => { } } - if (!('rngCode' in data)) { - player.rngCode = 0 - } - if (data.loaded1009 === undefined || !data.loaded1009) { player.loaded1009 = false } @@ -1807,396 +1423,36 @@ const loadSynergy = () => { player.loaded10101 = false } - if (typeof player.researches[76] === 'undefined') { - player.codes.set(13, false) - player.researches.push( - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ) - player.achievements.push( - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ) - player.maxofferings = player.runeshards - player.maxobtainium = player.researchPoints - player.researchPoints += 51200 * player.researches[50] - player.researches[50] = 0 - } - - player.maxofferings = player.maxofferings || 0 - player.maxobtainium = player.maxobtainium || 0 - player.runeshards = player.runeshards || 0 - player.researchPoints = player.researchPoints || 0 - - if ( - !data.loaded1009 - || data.loaded1009hotfix1 === null - || data.shopUpgrades?.offeringPotion === undefined - ) { - player.firstOwnedParticles = 0 - player.secondOwnedParticles = 0 - player.thirdOwnedParticles = 0 - player.fourthOwnedParticles = 0 - player.fifthOwnedParticles = 0 - player.firstCostParticles = new Decimal('1') - player.secondCostParticles = new Decimal('1e2') - player.thirdCostParticles = new Decimal('1e4') - player.fourthCostParticles = new Decimal('1e8') - player.fifthCostParticles = new Decimal('1e16') - player.autoSacrificeToggle = false - player.autoResearchToggle = false - player.autoResearchMode = 'manual' - player.autoResearch = 0 - player.autoSacrifice = 0 - player.sacrificeTimer = 0 - player.loaded1009 = true - player.codes.set(18, false) - } - if (!data.loaded1009hotfix1) { - player.loaded1009hotfix1 = true - player.codes.set(19, true) - player.firstOwnedParticles = 0 - player.secondOwnedParticles = 0 - player.thirdOwnedParticles = 0 - player.fourthOwnedParticles = 0 - player.fifthOwnedParticles = 0 - player.firstCostParticles = new Decimal('1') - player.secondCostParticles = new Decimal('1e2') - player.thirdCostParticles = new Decimal('1e4') - player.fourthCostParticles = new Decimal('1e8') - player.fifthCostParticles = new Decimal('1e16') - } - if ( - data.loaded10091 === undefined - || !data.loaded10091 - || player.researches[86] > 100 - || player.researches[87] > 100 - || player.researches[88] > 100 - || player.researches[89] > 100 - || player.researches[90] > 10 - ) { - player.loaded10091 = true - player.researchPoints += 7.5e8 * player.researches[82] - player.researchPoints += 2e8 * player.researches[83] - player.researchPoints += 4.5e9 * player.researches[84] - player.researchPoints += 2.5e7 * player.researches[86] - player.researchPoints += 7.5e7 * player.researches[87] - player.researchPoints += 3e8 * player.researches[88] - player.researchPoints += 1e9 * player.researches[89] - player.researchPoints += 2.5e7 * player.researches[90] - player.researchPoints += 1e8 * player.researches[91] - player.researchPoints += 2e9 * player.researches[92] - player.researchPoints += 9e9 * player.researches[93] - player.researchPoints += 7.25e10 * player.researches[94] - player.researches[86] = 0 - player.researches[87] = 0 - player.researches[88] = 0 - player.researches[89] = 0 - player.researches[90] = 0 - player.researches[91] = 0 - player.researches[92] = 0 - } - - // const shop = data.shopUpgrades as LegacyShopUpgrades & Player['shopUpgrades']; - if ( - data.achievements?.[169] === undefined - || typeof player.achievements[169] === 'undefined' - // (shop.antSpeed === undefined && shop.antSpeedLevel === undefined) || - // (shop.antSpeed === undefined && typeof shop.antSpeedLevel === 'undefined') || - || data.loaded1010 === undefined - || data.loaded1010 === false - ) { - player.loaded1010 = true - player.codes.set(21, false) - - player.firstOwnedAnts = 0 - player.firstGeneratedAnts = new Decimal('0') - player.firstCostAnts = new Decimal('1e700') - player.firstProduceAnts = 0.0001 - - player.secondOwnedAnts = 0 - player.secondGeneratedAnts = new Decimal('0') - player.secondCostAnts = new Decimal('3') - player.secondProduceAnts = 0.00005 - - player.thirdOwnedAnts = 0 - player.thirdGeneratedAnts = new Decimal('0') - player.thirdCostAnts = new Decimal('100') - player.thirdProduceAnts = 0.00002 - - player.fourthOwnedAnts = 0 - player.fourthGeneratedAnts = new Decimal('0') - player.fourthCostAnts = new Decimal('1e4') - player.fourthProduceAnts = 0.00001 - - player.fifthOwnedAnts = 0 - player.fifthGeneratedAnts = new Decimal('0') - player.fifthCostAnts = new Decimal('1e12') - player.fifthProduceAnts = 0.000005 - - player.sixthOwnedAnts = 0 - player.sixthGeneratedAnts = new Decimal('0') - player.sixthCostAnts = new Decimal('1e36') - player.sixthProduceAnts = 0.000002 - - player.seventhOwnedAnts = 0 - player.seventhGeneratedAnts = new Decimal('0') - player.seventhCostAnts = new Decimal('1e100') - player.seventhProduceAnts = 0.000001 - - player.eighthOwnedAnts = 0 - player.eighthGeneratedAnts = new Decimal('0') - player.eighthCostAnts = new Decimal('1e300') - player.eighthProduceAnts = 0.00000001 - - player.achievements.push(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - player.antPoints = new Decimal('1') - - player.upgrades[38] = 0 - player.upgrades[39] = 0 - player.upgrades[40] = 0 - - player.upgrades[76] = 0 - player.upgrades[77] = 0 - player.upgrades[78] = 0 - player.upgrades[79] = 0 - player.upgrades[80] = 0 - - // player.shopUpgrades.antSpeed = 0; - // player.shopUpgrades.shopTalisman = 0; - - player.antUpgrades = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - - player.unlocks.rrow4 = false - player.researchPoints += 3e7 * player.researches[50] - player.researchPoints += 2e9 * player.researches[96] - player.researchPoints += 5e9 * player.researches[97] - player.researchPoints += 3e10 * player.researches[98] - player.researches[50] = 0 - player.researches[96] = 0 - player.researches[97] = 0 - player.researches[98] = 0 - player.researches.push( - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ) - - player.talismanLevels = [0, 0, 0, 0, 0, 0, 0] - player.talismanRarity = [1, 1, 1, 1, 1, 1, 1] - - player.talismanShards = 0 - player.commonFragments = 0 - player.uncommonFragments = 0 - player.rareFragments = 0 - player.epicFragments = 0 - player.legendaryFragments = 0 - player.mythicalFragments = 0 - player.buyTalismanShardPercent = 10 - - player.talismanOne = [null, -1, 1, 1, 1, -1] - player.talismanTwo = [null, 1, 1, -1, -1, 1] - player.talismanThree = [null, 1, -1, 1, 1, -1] - player.talismanFour = [null, -1, -1, 1, 1, 1] - player.talismanFive = [null, 1, 1, -1, -1, 1] - player.talismanSix = [null, 1, 1, 1, -1, -1] - player.talismanSeven = [null, -1, 1, -1, 1, 1] - - player.antSacrificePoints = 0 - player.antSacrificeTimer = 0 - - player.obtainiumpersecond = 0 - player.maxobtainiumpersecond = 0 - } - - if (data.loaded10101 === undefined || data.loaded10101 === false) { - player.loaded10101 = true - - // dprint-ignore - const refundThese = [ - 0, 31, 32, 61, 62, 63, 64, 76, 77, 78, 79, 80, 81, 98, 104, 105, 106, - 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, - 121, 122, 123, 125, - ]; - // dprint-ignore - const refundReward = [ - 0, 2, 20, 5, 10, 80, 5e3, 1e7, 1e7, 2e7, 3e7, 4e7, 2e8, 3e10, 1e11, - 1e12, 2e11, 1e12, 2e10, 2e11, 1e12, 2e13, 5e13, 1e14, 2e14, 5e14, 1e15, - 2e15, 1e16, 1e15, 1e16, 1e14, 1e15, 1e15, 1e20, - ]; - for (let i = 1; i < refundThese.length; i++) { - player.researchPoints += player.researches[refundThese[i]] * refundReward[i] - player.researches[refundThese[i]] = 0 - } - player.autoAntSacrifice = false - player.antMax = false - } - if (player.firstOwnedAnts < 1 && player.firstCostAnts.gte('1e1200')) { player.firstCostAnts = new Decimal('1e700') player.firstOwnedAnts = 0 } - // checkVariablesOnLoad(data) - - if (player.ascensionCount === 0) { - if (player.prestigeCount > 0) { - player.ascensionCounter = 86400 * 90 - } - /*player.cubeUpgrades = [null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,];*/ - - if (player.singularityCount === 0) { - player.cubeUpgrades = [...blankSave.cubeUpgrades] - } - player.wowCubes = new WowCubes(0) - player.wowTesseracts = new WowTesseracts(0) - player.wowHypercubes = new WowHypercubes(0) - player.wowPlatonicCubes = new WowPlatonicCubes(0) - player.cubeBlessings = { - accelerator: 0, - multiplier: 0, - offering: 0, - runeExp: 0, - obtainium: 0, - antSpeed: 0, - antSacrifice: 0, - antELO: 0, - talismanBonus: 0, - globalSpeed: 0 - } - } - + // Does this need to be kept here? if (player.transcendCount < 0) { player.transcendCount = 0 } if (player.reincarnationCount < 0) { player.reincarnationCount = 0 } - if (player.runeshards < 0) { - player.runeshards = 0 - } - if (player.researchPoints < 0) { - player.researchPoints = 0 - } - - if (player.resettoggle1 === 0) { - player.resettoggle1 = 1 - player.resettoggle2 = 1 - player.resettoggle3 = 1 - player.resettoggle4 = 1 - } - if (player.tesseractAutoBuyerToggle === 0) { - player.tesseractAutoBuyerToggle = 1 - } - if (player.reincarnationCount < 0.5 && player.unlocks.rrow4) { - player.unlocks = { - coinone: false, - cointwo: false, - cointhree: false, - coinfour: false, - prestige: false, - generation: false, - transcend: false, - reincarnate: false, - rrow1: false, - rrow2: false, - rrow3: false, - rrow4: false - } + if (player.offerings.lte(0)) { + player.offerings = new Decimal(0) } - - if (!Number.isInteger(player.ascendBuilding1.cost)) { - player.ascendBuilding1.cost = 1 - player.ascendBuilding1.owned = 0 - player.ascendBuilding2.cost = 10 - player.ascendBuilding2.owned = 0 - player.ascendBuilding3.cost = 100 - player.ascendBuilding3.owned = 0 - player.ascendBuilding4.cost = 1000 - player.ascendBuilding4.owned = 0 - player.ascendBuilding5.cost = 10000 - player.ascendBuilding5.owned = 0 + if (player.obtainium.lte(0)) { + player.obtainium = new Decimal(0) } if (!player.dayCheck) { player.dayCheck = new Date() } + if (typeof player.dayCheck === 'string') { player.dayCheck = new Date(player.dayCheck) if (isNaN(player.dayCheck.getTime())) { player.dayCheck = new Date() } } + // Why was this made? // Measures for people who play the past let updatedLast = lastUpdated if (!isNaN(updatedLast.getTime())) { @@ -2218,41 +1474,9 @@ const loadSynergy = () => { player.dayCheck.getDate() ) - player.corruptions.used = new CorruptionLoadout(player.corruptions.used.loadout) - // This is needed to fix saves that had issues with not resetting corruption at the singularity - player.corruptions.used.setCorruptionLevelsWithChallengeRequirement(player.corruptions.used.loadout) - - for (let i = 1; i <= 5; i++) { - const ascendBuildingI = `ascendBuilding${i as OneToFive}` as const - player[ascendBuildingI].generated = new Decimal( - player[ascendBuildingI].generated - ) - } - - while (typeof player.achievements[252] === 'undefined') { - player.achievements.push(0) - } - while (typeof player.researches[200] === 'undefined') { - player.researches.push(0) - } - while (typeof player.upgrades[140] === 'undefined') { - player.upgrades.push(0) - } - - if ( - player.saveString === '' - || player.saveString === 'Synergism-v1011Test.txt' - ) { - player.saveString = player.singularityCount === 0 - ? 'Synergism-$VERSION$-$TIME$.txt' - : 'Synergism-$VERSION$-$TIME$-$SING$.txt' - } - ;(DOMCacheGetOrSet('saveStringInput') as HTMLInputElement).value = cleanString(player.saveString) - for (let j = 1; j < 126; j++) { upgradeupdate(j, true) } - for (let j = 1; j <= 200; j++) { updateResearchBG(j) } @@ -2264,6 +1488,7 @@ const loadSynergy = () => { updatePlatonicUpgradeBG(j) } + // WHY const q = [ 'coin', 'crystal', @@ -2272,56 +1497,9 @@ const loadSynergy = () => { 'offering', 'tesseract' ] as const - if ( - player.coinbuyamount !== 1 - && player.coinbuyamount !== 10 - && player.coinbuyamount !== 100 - && player.coinbuyamount !== 1000 - ) { - player.coinbuyamount = 1 - } - if ( - player.crystalbuyamount !== 1 - && player.crystalbuyamount !== 10 - && player.crystalbuyamount !== 100 - && player.crystalbuyamount !== 1000 - ) { - player.crystalbuyamount = 1 - } - if ( - player.mythosbuyamount !== 1 - && player.mythosbuyamount !== 10 - && player.mythosbuyamount !== 100 - && player.mythosbuyamount !== 1000 - ) { - player.mythosbuyamount = 1 - } - if ( - player.particlebuyamount !== 1 - && player.particlebuyamount !== 10 - && player.particlebuyamount !== 100 - && player.particlebuyamount !== 1000 - ) { - player.particlebuyamount = 1 - } - if ( - player.offeringbuyamount !== 1 - && player.offeringbuyamount !== 10 - && player.offeringbuyamount !== 100 - && player.offeringbuyamount !== 1000 - ) { - player.offeringbuyamount = 1 - } - if ( - player.tesseractbuyamount !== 1 - && player.tesseractbuyamount !== 10 - && player.tesseractbuyamount !== 100 - && player.tesseractbuyamount !== 1000 - ) { - player.tesseractbuyamount = 1 - } + for (let j = 0; j <= 5; j++) { - for (let k = 0; k < 4; k++) { + for (let k = 0; k < 6; k++) { let d = '' if (k === 0) { d = 'one' @@ -2335,6 +1513,12 @@ const loadSynergy = () => { if (k === 3) { d = 'thousand' } + if (k === 4) { + d = '10k' + } + if (k === 5) { + d = '100k' + } const e = `${q[j]}${d}` DOMCacheGetOrSet(e).style.backgroundColor = '' } @@ -2352,24 +1536,21 @@ const loadSynergy = () => { if (curBuyAmount === 1000) { c = 'thousand' } + if (curBuyAmount === 10000) { + c = '10k' + } + if (curBuyAmount === 100000) { + c = '100k' + } const b = `${q[j]}${c}` DOMCacheGetOrSet(b).style.backgroundColor = 'green' } - const testArray = [] - // Creates a copy of research costs array - for (let i = 0; i < G.researchBaseCosts.length; i++) { - testArray.push(G.researchBaseCosts[i]) - } - // Sorts the above array, and returns the index order of sorted array - G.researchOrderByCost = sortWithIndices(testArray) player.roombaResearchIndex = 0 // June 09, 2021: Updated toggleShops() and removed boilerplate - Platonic - toggleShops() getChallengeConditions() - updateChallengeDisplay() revealStuff() toggleauto() @@ -2401,32 +1582,13 @@ const loadSynergy = () => { // For blueberry upgrades! displayProperLoadoutCount() - DOMCacheGetOrSet('researchrunebonus').textContent = i18next.t( - 'runes.thanksResearches', - { - percent: format(100 * G.effectiveLevelMult - 100, 4, true) - } - ) - - DOMCacheGetOrSet('talismanlevelup').style.display = 'none' - DOMCacheGetOrSet('talismanrespec').style.display = 'none' + DOMCacheGetOrSet('talismanLevelUpCost').style.display = 'none' DOMCacheGetOrSet('antSacrificeSummary').style.display = 'none' // This must be initialized at the beginning of the calculation c15RewardUpdate() - calculatePlatonicBlessings() - calculateHypercubeBlessings() - calculateTesseractBlessings() - calculateCubeBlessings() - updateTalismanAppearance(0) - updateTalismanAppearance(1) - updateTalismanAppearance(2) - updateTalismanAppearance(3) - updateTalismanAppearance(4) - updateTalismanAppearance(5) - updateTalismanAppearance(6) for (const id in player.ascStatToggles) { toggleAscStatPerSecond(+id) // toggle each stat twice to make sure the displays are correct and match what they used to be toggleAscStatPerSecond(+id) @@ -2779,17 +1941,6 @@ const loadSynergy = () => { ) DOMCacheGetOrSet('toggleautofortify').style.border = '2px solid red' } - if (player.autoEnhanceToggle) { - DOMCacheGetOrSet('toggleautoenhance').textContent = i18next.t( - 'runes.autoEnhanceOn' - ) - DOMCacheGetOrSet('toggleautoenhance').style.border = '2px solid green' - } else { - DOMCacheGetOrSet('toggleautoenhance').textContent = i18next.t( - 'runes.autoEnhanceOff' - ) - DOMCacheGetOrSet('toggleautoenhance').style.border = '2px solid red' - } player.saveOfferingToggle = false // Lint doesnt like it being inside if DOMCacheGetOrSet('saveOffToggle').textContent = i18next.t( 'toggles.saveOfferingsOff' @@ -2871,13 +2022,13 @@ const loadSynergy = () => { DOMCacheGetOrSet('historyTogglePerSecondButton').style.borderColor = player.historyShowPerSecond ? 'green' : 'red' - // If auto research is enabled and runing; Make sure there is something to try to research if possible + // If roomba auto research is enabled and runing; Make sure there is something to try to research if possible if ( player.autoResearchToggle - && autoResearchEnabled() + && roombaResearchEnabled() && player.autoResearchMode === 'cheapest' ) { - player.autoResearch = G.researchOrderByCost[player.roombaResearchIndex] + player.autoResearch = researchOrderByCost[player.roombaResearchIndex] } player.autoResearch = Math.min(200, player.autoResearch) @@ -2889,8 +2040,8 @@ const loadSynergy = () => { ) } - if (player.autoSacrificeToggle && player.autoSacrifice > 0.5) { - DOMCacheGetOrSet(`rune${player.autoSacrifice}`).style.backgroundColor = 'orange' + if (player.autoSacrificeToggle && player.autoSacrifice > 0) { + DOMCacheGetOrSet(`${indexToRune[player.autoSacrifice]}Rune`).style.backgroundColor = 'orange' } if (player.autoWarpCheck) { @@ -2924,7 +2075,6 @@ const loadSynergy = () => { updateTalismanInventory() calculateObtainium() calculateAnts() - calculateRuneLevels() resetHistoryRenderAllTables() updateSingularityAchievements() updateSingularityGlobalPerks() @@ -2936,7 +2086,7 @@ const loadSynergy = () => { } } - updateAchievementBG() + // updateAchievementBG() if (player.currentChallenge.reincarnation) { resetrepeat('reincarnationChallenge') } else if (player.currentChallenge.transcension) { @@ -3091,7 +2241,7 @@ const padEvery = (str: string, places = 3) => { } } // re-add decimal places - if (typeof strParts[1] !== 'undefined') { + if (strParts[1] !== undefined) { newStr += dec + strParts[1] } // see https://www.npmjs.com/package/flatstr @@ -3111,7 +2261,6 @@ export const format = ( input: | Decimal | number - | { [Symbol.toPrimitive]: unknown } | null | undefined, accuracy = 0, @@ -3123,40 +2272,51 @@ export const format = ( return '0 [null]' } - if (typeof input === 'object' && Symbol.toPrimitive in input) { - input = Number(input) + // NaN check + // biome-ignore lint/suspicious/noSelfCompare: NaN !== NaN + if (input !== input) { + return '0 [NaN]' } + const inputType = typeof input + if ( - // invalid parameter - (!(input instanceof Decimal) && typeof input !== 'number') - || isNaN(input as number) - ) { - return isNaN(input as number) ? '0 [NaN]' : '0 [und.]' - } else if ( // this case handles numbers less than 1e-6 and greater than 0 - typeof input === 'number' + inputType === 'number' && player.notation === 'Default' - && input < (!fractional ? 1e-3 : 1e-15) // arbitrary number, don't change 1e-3 - && input > 0 // don't handle negative numbers, probably could be removed + && (input as number) < (!fractional ? 1e-3 : 1e-15) // arbitrary number, don't change 1e-3 + && (input as number) > 0 // don't handle negative numbers, probably could be removed ) { return input.toExponential(accuracy) + } else if ( + inputType === 'number' + && player.notation === 'Default' + && -(input as number) < (!fractional ? 1e-3 : 1e-15) // arbitrary number, don't change 1e-3 + && -(input as number) > 0 + ) { + return `-${(-input).toExponential(accuracy)}` } let power!: number let mantissa!: number - if (isDecimal(input)) { - // Gets power and mantissa if input is of type decimal - power = input.e - mantissa = input.mantissa - } else if (typeof input === 'number') { + if (inputType === 'number') { + if ((input as number) < 0) { + return `-${format(-input, accuracy, long, truncate, fractional)}` + } if (input === 0) { return '0' } // Gets power and mantissa if input is of type number and isn't 0 - power = Math.floor(Math.log10(Math.abs(input))) - mantissa = input / Math.pow(10, power) + power = Math.floor(Math.log10(Math.abs(input as number))) + mantissa = (input as number) / Math.pow(10, power) + } else if (input instanceof Decimal) { + if (input.lessThan(0)) { + return `-${format(input.negated(), accuracy, long, truncate, fractional)}` + } + // Gets power and mantissa if input is of type decimal + power = input.e + mantissa = input.mantissa } // This prevents numbers from jittering between two different powers by rounding errors @@ -3178,8 +2338,7 @@ export const format = ( const powerOver = power % 3 < 0 ? 3 + (power % 3) : power % 3 power = power - powerOver mantissa = mantissa * Math.pow(10, powerOver) - } - if ( + } else if ( player.notation === 'Pure Scientific' || player.notation === 'Pure Engineering' ) { @@ -3213,7 +2372,7 @@ export const format = ( return `${mantissaLook}` } // If the power is negative, then we will want to address that separately. - if (power < 0 && !isDecimal(input) && fractional) { + if (power < 0 && inputType === 'number' && fractional) { if (power <= -15) { return `${format(mantissa, accuracy, long)} / ${ Math.pow( @@ -3320,9 +2479,9 @@ export const format = ( // If it doesn't fit a notation then default to mantissa e power return `e${power.toExponential(2)}` - } else { - return '0 [und.]' } + + return '0 [und.]' } export const formatTimeShort = ( @@ -3348,6 +2507,14 @@ export const formatTimeShort = ( ) } +export const formatAsPercentIncrease = (n: number, accuracy = 2) => { + return `${format((n - 1) * 100, accuracy, true)}%` +} + +export const formatDecimalAsPercentIncrease = (n: Decimal, accuracy = 2) => { + return `${format(n.minus(1).times(100), accuracy, true)}%` +} + export const updateAllTick = (): void => { let a = 0 @@ -3372,26 +2539,6 @@ export const updateAllTick = (): void => { if (player.upgrades[25] !== 0) { a += 1 } - if (player.upgrades[27] !== 0) { - a += Math.min(250, Math.floor(Decimal.log(player.coins.add(1), 1e3))) - + Math.min( - 1750, - Math.max(0, Math.floor(Decimal.log(player.coins.add(1), 1e15)) - 50) - ) - } - if (player.upgrades[29] !== 0) { - a += Math.floor( - Math.min( - 2000, - (player.firstOwnedCoin - + player.secondOwnedCoin - + player.thirdOwnedCoin - + player.fourthOwnedCoin - + player.fifthOwnedCoin) - / 80 - ) - ) - } if (player.upgrades[32] !== 0) { a += Math.min( 500, @@ -3404,42 +2551,19 @@ export const updateAllTick = (): void => { Math.floor(Decimal.log(player.transcendShards.add(1), 10)) ) } - if (player.achievements[5] !== 0) { - a += Math.floor(player.firstOwnedCoin / 500) - } - if (player.achievements[12] !== 0) { - a += Math.floor(player.secondOwnedCoin / 500) - } - if (player.achievements[19] !== 0) { - a += Math.floor(player.thirdOwnedCoin / 500) - } - if (player.achievements[26] !== 0) { - a += Math.floor(player.fourthOwnedCoin / 500) - } - if (player.achievements[33] !== 0) { - a += Math.floor(player.fifthOwnedCoin / 500) - } - if (player.achievements[60] !== 0) { - a += 2 - } - if (player.achievements[61] !== 0) { - a += 2 - } - if (player.achievements[62] !== 0) { - a += 2 - } + a += +getAchievementReward('accelerators') a += 5 * CalcECC('transcend', player.challengecompletions[2]) G.freeUpgradeAccelerator = a a += G.totalAcceleratorBoost - * (4 + * (5 + 2 * player.researches[18] + 2 * player.researches[19] + 3 * player.researches[20] - + G.cubeBonusMultiplier[1]) + + calculateAcceleratorCubeBlessing()) + if (player.unlocks.prestige) { - a += Math.floor(Math.pow((G.rune1level * G.effectiveLevelMult) / 4, 1.25)) - a *= 1 + ((G.rune1level * 1) / 400) * G.effectiveLevelMult + a *= getRuneEffects('speed').multiplicativeAccelerators } calculateAcceleratorMultiplier() @@ -3452,9 +2576,9 @@ export const updateAllTick = (): void => { * G.viscosityPower[player.corruptions.used.viscosity] ) ) - a += 2000 * hepteractEffective('accelerator') + a += getHepteractEffects('accelerator').accelerators a *= G.challenge15Rewards.accelerator.value - a *= 1 + (3 / 10000) * hepteractEffective('accelerator') + a *= getHepteractEffects('accelerator').acceleratorMultiplier a = Math.floor(Math.min(1e100, a)) if (player.corruptions.used.viscosity >= 15) { @@ -3473,22 +2597,18 @@ export const updateAllTick = (): void => { G.tuSevenMulti = 1.05 } + const achievementBonus = +getAchievementReward('acceleratorPower') + G.acceleratorPower = Math.pow( 1.1 + + getRuneEffects('speed').acceleratorPower + + 1 / 400 * CalcECC('transcend', player.challengecompletions[2]) + + achievementBonus + G.tuSevenMulti * (G.totalAcceleratorBoost / 100) * (1 + CalcECC('transcend', player.challengecompletions[2]) / 20), 1 + 0.04 * CalcECC('reincarnation', player.challengecompletions[7]) ) - G.acceleratorPower += ((1 / 200) - * Math.floor(CalcECC('transcend', player.challengecompletions[2]) / 2) - * 100) - / 100 - for (let i = 1; i <= 5; i++) { - if (player.achievements[7 * i - 4] > 0) { - G.acceleratorPower += 0.0005 * i - } - } // No MA and Sadistic will always overwrite Transcend challenges starting in v2.0.0 if ( @@ -3569,23 +2689,6 @@ export const updateAllMultiplier = (): void => { if (player.upgrades[25] > 0) { a += 1 } - if (player.upgrades[28] > 0) { - a += Math.min( - 1000, - Math.floor( - (player.firstOwnedCoin - + player.secondOwnedCoin - + player.thirdOwnedCoin - + player.fourthOwnedCoin - + player.fifthOwnedCoin) - / 160 - ) - ) - } - if (player.upgrades[30] > 0) { - a += Math.min(75, Math.floor(Decimal.log(player.coins.add(1), 1e10))) - + Math.min(925, Math.floor(Decimal.log(player.coins.add(1), 1e30))) - } if (player.upgrades[33] > 0) { a += G.totalAcceleratorBoost } @@ -3601,56 +2704,13 @@ export const updateAllMultiplier = (): void => { if (player.challengecompletions[1] > 0) { a += 1 } - if (player.achievements[6] > 0.5) { - a += Math.floor(player.firstOwnedCoin / 1000) - } - if (player.achievements[13] > 0.5) { - a += Math.floor(player.secondOwnedCoin / 1000) - } - if (player.achievements[20] > 0.5) { - a += Math.floor(player.thirdOwnedCoin / 1000) - } - if (player.achievements[27] > 0.5) { - a += Math.floor(player.fourthOwnedCoin / 1000) - } - if (player.achievements[34] > 0.5) { - a += Math.floor(player.fifthOwnedCoin / 1000) - } - if (player.achievements[57] > 0.5) { - a += 1 - } - if (player.achievements[58] > 0.5) { - a += 1 - } - if (player.achievements[59] > 0.5) { - a += 1 - } + + a += +getAchievementReward('multipliers') a += 20 * player.researches[94] - * Math.floor( - (G.rune1level - + G.rune2level - + G.rune3level - + G.rune4level - + G.rune5level) - / 8 - ) + * Math.floor(sumOfRuneLevels() / 8) G.freeUpgradeMultiplier = Math.min(1e100, a) - - if (player.achievements[38] > 0.5) { - a += (Math.floor( - (Math.floor((G.rune2level / 10) * G.effectiveLevelMult) - * Math.floor(1 + (G.rune2level / 10) * G.effectiveLevelMult)) - / 2 - ) - * 100) - / 100 - } - - a *= 1 + player.achievements[57] / 100 - a *= 1 + player.achievements[58] / 100 - a *= 1 + player.achievements[59] / 100 a *= Math.pow( 1.01, player.upgrades[21] @@ -3670,7 +2730,7 @@ export const updateAllMultiplier = (): void => { + (1 / 40) * player.researches[13] + (3 / 200) * player.researches[14] + (1 / 200) * player.researches[15] - a *= 1 + (G.rune2level / 400) * G.effectiveLevelMult + a *= getRuneEffects('duplication').multiplicativeMultipliers a *= 1 + (1 / 20) * player.researches[87] a *= 1 + (1 / 100) * player.researches[128] a *= 1 + (0.8 / 100) * player.researches[143] @@ -3683,7 +2743,7 @@ export const updateAllMultiplier = (): void => { 40, (((player.antUpgrades[4]! + G.bonusant5) / 1000) * 40) / 39 ) - a *= G.cubeBonusMultiplier[2] + a *= calculateMultiplierCubeBlessing() if ( (player.currentChallenge.transcension !== 0 || player.currentChallenge.reincarnation !== 0) @@ -3699,9 +2759,9 @@ export const updateAllMultiplier = (): void => { * G.viscosityPower[player.corruptions.used.viscosity] ) ) - a += 1000 * hepteractEffective('multiplier') + a += getHepteractEffects('multiplier').multiplier a *= G.challenge15Rewards.multiplier.value - a *= 1 + (3 / 10000) * hepteractEffective('multiplier') + a *= getHepteractEffects('multiplier').multiplierMultiplier a = Math.floor(Math.min(1e100, a)) if (player.corruptions.used.viscosity >= 15) { @@ -3717,19 +2777,16 @@ export const updateAllMultiplier = (): void => { G.challengeOneLog = 3 let b = 0 - let c = 0 + const c = 0 b += Decimal.log(player.transcendShards.add(1), 3) + b += getRuneEffects('duplication').multiplierBoosts + b += 2 * CalcECC('transcend', player.challengecompletions[1]) b *= 1 + (11 * player.researches[33]) / 100 b *= 1 + (11 * player.researches[34]) / 100 b *= 1 + (11 * player.researches[35]) / 100 b *= 1 + player.researches[89] / 5 - b *= 1 + 10 * G.effectiveRuneBlessingPower[2] + b *= getRuneBlessingEffect('duplication').multiplierBoosts - c += Math.floor( - 0.1 * b * CalcECC('transcend', player.challengecompletions[1]) - ) - c += CalcECC('transcend', player.challengecompletions[1]) * 10 - G.freeMultiplierBoost = c G.totalMultiplierBoost = Math.pow( Math.floor(b) + c, 1 + CalcECC('reincarnation', player.challengecompletions[7]) * 0.04 @@ -3740,7 +2797,7 @@ export const updateAllMultiplier = (): void => { c7 = 1.25 } - G.multiplierPower = 2 + 0.005 * G.totalMultiplierBoost * c7 + G.multiplierPower = 2 + 0.02 * G.totalMultiplierBoost * c7 // No MA and Sadistic will always override Transcend Challenges starting in v2.0.0 if ( @@ -3772,10 +2829,8 @@ export const multipliers = (): void => { let crystalExponent = 1 / 3 crystalExponent += Math.min( 10 - + (0.05 * player.researches[129] * Math.log(player.commonFragments + 1)) - / Math.log(4) - + ((20 * player.corruptions.used.totalCorruptionDifficultyMultiplier) - * G.effectiveRuneSpiritPower[3]), + + (0.05 * player.researches[129] * Decimal.log(player.commonFragments.add(1), 4)) + + getRuneSpiritEffect('prism').crystalCaps, 0.05 * player.crystalUpgrades[3] ) crystalExponent += 0.04 * CalcECC('transcend', player.challengecompletions[3]) @@ -3850,7 +2905,7 @@ export const multipliers = (): void => { } if (player.upgrades[41] > 0.5) { s = s.times( - Decimal.min(1e30, Decimal.pow(player.transcendPoints.add(1), 1 / 2)) + Decimal.min(1e30, Decimal.pow(player.transcendPoints.add(4), 1 / 2)) ) } if (player.upgrades[43] > 0.5) { @@ -3931,7 +2986,7 @@ export const multipliers = (): void => { Decimal.pow( player.firstGeneratedMythos.add(player.firstOwnedMythos).add(1), 4 / 3 - ).times(1e10) + ).times(1e22) ) ) } @@ -3977,21 +3032,12 @@ export const multipliers = (): void => { } G.globalCrystalMultiplier = new Decimal(1) - if (player.achievements[36] > 0.5) { - G.globalCrystalMultiplier = G.globalCrystalMultiplier.times(2) - } - if (player.achievements[37] > 0.5 && player.prestigePoints.gte(10)) { - G.globalCrystalMultiplier = G.globalCrystalMultiplier.times( - Decimal.log(player.prestigePoints.add(1), 10) - ) - } - if (player.achievements[44] > 0.5) { - G.globalCrystalMultiplier = G.globalCrystalMultiplier.times( - Decimal.pow((G.rune3level / 2) * G.effectiveLevelMult, 2) - .times(Decimal.pow(2, (G.rune3level * G.effectiveLevelMult) / 2 - 8)) - .add(1) - ) - } + G.globalCrystalMultiplier = G.globalCrystalMultiplier.times( + +getAchievementReward('crystalMultiplier') + ) + G.globalCrystalMultiplier = G.globalCrystalMultiplier.times( + Decimal.pow(10, getRuneEffects('prism').productionLog10) + ) if (player.upgrades[36] > 0.5) { G.globalCrystalMultiplier = G.globalCrystalMultiplier.times( Decimal.min('1e5000', Decimal.pow(player.prestigePoints, 1 / 500)) @@ -4011,7 +3057,7 @@ export const multipliers = (): void => { G.globalCrystalMultiplier = G.globalCrystalMultiplier.times( Decimal.min( Decimal.pow(10, 50 + 2 * player.crystalUpgrades[0]), - Decimal.pow(1.05, player.achievementPoints * player.crystalUpgrades[0]) + Decimal.pow(1.01, achievementPoints * player.crystalUpgrades[0]) ) ) G.globalCrystalMultiplier = G.globalCrystalMultiplier.times( @@ -4030,9 +3076,8 @@ export const multipliers = (): void => { 0.12 + 0.88 * player.upgrades[122] + (0.001 - * player.researches[129] - * Math.log(player.commonFragments + 1)) - / Math.log(4), + * player.researches[129] + * Decimal.log(player.commonFragments.add(1), 4)), 0.001 * player.crystalUpgrades[2] ), player.firstOwnedDiamonds @@ -4089,8 +3134,8 @@ export const multipliers = (): void => { } if (player.upgrades[47] > 0.5) { G.globalMythosMultiplier = G.globalMythosMultiplier - .times(Decimal.pow(1.05, player.achievementPoints)) - .times(player.achievementPoints + 1) + .times(Decimal.pow(1.01, achievementPoints)) + .times(achievementPoints / 5 + 1) } if (player.upgrades[51] > 0.5) { G.globalMythosMultiplier = G.globalMythosMultiplier.times( @@ -4152,7 +3197,7 @@ export const multipliers = (): void => { G.globalConstantMult = G.globalConstantMult.times( Decimal.pow( 1.05 - + 0.01 * player.achievements[270] + + +getAchievementReward('constUpgrade1Buff') + 0.001 * player.platonicUpgrades[18], player.constantUpgrades[1] ) @@ -4163,7 +3208,7 @@ export const multipliers = (): void => { + 0.001 * Math.min( 100 - + 10 * player.achievements[270] + + 1000 * +getAchievementReward('constUpgrade2Buff') + 10 * player.shopUpgrades.constantEX + 1000 * (G.challenge15Rewards.exponent.value - 1) + 3 * player.platonicUpgrades[18], @@ -4416,7 +3461,7 @@ export const resourceGain = (dt: number): void => { ) if (player.ascensionCount > 0) { - ascensionAchievementCheck(2) + awardAchievementGroup('constant') } if ( @@ -4436,7 +3481,7 @@ export const resourceGain = (dt: number): void => { ) ) { player.challengecompletions[1] += 1 - challengeachievementcheck(1, true) + challengeAchievementCheck(1) updateChallengeLevel(1) } if ( @@ -4456,7 +3501,7 @@ export const resourceGain = (dt: number): void => { ) ) { player.challengecompletions[2] += 1 - challengeachievementcheck(2, true) + challengeAchievementCheck(2) updateChallengeLevel(2) } if ( @@ -4476,7 +3521,7 @@ export const resourceGain = (dt: number): void => { ) ) { player.challengecompletions[3] += 1 - challengeachievementcheck(3, true) + challengeAchievementCheck(3) updateChallengeLevel(3) } if ( @@ -4496,7 +3541,7 @@ export const resourceGain = (dt: number): void => { ) ) { player.challengecompletions[4] += 1 - challengeachievementcheck(4, true) + challengeAchievementCheck(4) updateChallengeLevel(4) } if ( @@ -4516,7 +3561,7 @@ export const resourceGain = (dt: number): void => { ) ) { player.challengecompletions[5] += 1 - challengeachievementcheck(5, true) + challengeAchievementCheck(5) updateChallengeLevel(5) } @@ -4571,7 +3616,7 @@ export const resourceGain = (dt: number): void => { ) as number) ) { void resetCheck('ascensionChallenge', false) - challengeachievementcheck(ascendchal, true) + challengeAchievementCheck(ascendchal) } } if (ascendchal === 15) { @@ -4585,30 +3630,14 @@ export const resourceGain = (dt: number): void => { ) ) { void resetCheck('ascensionChallenge', false) + challengeAchievementCheck(15) } } } export const updateAntMultipliers = (): void => { - // Update 2.5.0: Updated to have a base of 10 instead of 1x - G.globalAntMult = new Decimal(10) - // Update 2.9.0: Updated to give a 5x multiplier no matter what - G.globalAntMult = G.globalAntMult.times(5) - G.globalAntMult = G.globalAntMult.times( - 1 - + (1 / 2500) - * Math.pow( - G.rune5level - * G.effectiveLevelMult - * (1 - + (player.researches[84] / 200) - * (1 - + (1 - * G.effectiveRuneSpiritPower[5] - * player.corruptions.used.totalCorruptionDifficultyMultiplier))), - 2 - ) - ) + G.globalAntMult = new Decimal(1) + G.globalAntMult = G.globalAntMult.times(getRuneEffects('superiorIntellect').antSpeed) if (player.upgrades[76] === 1) { G.globalAntMult = G.globalAntMult.times(5) } @@ -4632,7 +3661,7 @@ export const updateAntMultipliers = (): void => { 1 + player.upgrades[78] * 0.005 - * Math.pow(Math.log10(player.maxofferings + 1), 2) + * Math.pow(Decimal.log10(player.maxOfferings.add(1)), 2) ) G.globalAntMult = G.globalAntMult.times( Decimal.pow( @@ -4645,31 +3674,18 @@ export const updateAntMultipliers = (): void => { ) G.globalAntMult = G.globalAntMult.times( Decimal.pow( - Math.max(1, player.researchPoints), - G.effectiveRuneBlessingPower[5] + Decimal.max(1, player.obtainium), + getRuneBlessingEffect('superiorIntellect').obtToAntExponent ) ) + G.globalAntMult = G.globalAntMult.times( - Decimal.pow(1 + G.runeSum / 100, G.talisman6Power) + Decimal.pow(1.1, CalcECC('reincarnation', player.challengecompletions[9])) ) + G.globalAntMult = G.globalAntMult.times(calculateAntSpeedCubeBlessing()) G.globalAntMult = G.globalAntMult.times( - Decimal.pow(1.1, CalcECC('reincarnation', player.challengecompletions[9])) + +getAchievementReward('antSpeed') ) - G.globalAntMult = G.globalAntMult.times(G.cubeBonusMultiplier[6]) - if (player.achievements[169] === 1) { - G.globalAntMult = G.globalAntMult.times( - Decimal.log(player.antPoints.add(10), 10) - ) - } - if (player.achievements[171] === 1) { - G.globalAntMult = G.globalAntMult.times(1.16666) - } - if (player.achievements[172] === 1) { - G.globalAntMult = G.globalAntMult.times( - 1 - + 2 * (1 - Math.pow(2, -Math.min(1, player.reincarnationcounter / 7200))) - ) - } if (player.upgrades[39] === 1) { G.globalAntMult = G.globalAntMult.times(1.6) } @@ -4740,7 +3756,8 @@ export const updateAntMultipliers = (): void => { ) { G.globalAntMult = Decimal.pow(G.globalAntMult, 1.25) } - if (player.achievements[274] > 0) { + + if (player.highestSingularityCount >= 1) { G.globalAntMult = G.globalAntMult.times(4.44) } @@ -4754,7 +3771,7 @@ export const updateAntMultipliers = (): void => { G.globalAntMult = Decimal.pow(G.globalAntMult, 0.02) } - if (player.octeractUpgrades.octeractStarter.getEffect().bonus) { + if (getOcteractUpgradeEffect('octeractStarter')) { G.globalAntMult = G.globalAntMult.times(100000) } @@ -4885,9 +3902,9 @@ export const resetCurrency = (): void => { if (player.currentChallenge.reincarnation !== 0) { G.reincarnationPointGain = Decimal.pow(G.reincarnationPointGain, 0.01) } - if (player.achievements[50] === 1) { - G.reincarnationPointGain = G.reincarnationPointGain.times(2) - } + G.reincarnationPointGain = G.reincarnationPointGain.times( + +getAchievementReward('particleGain') + ) if (player.upgrades[65] > 0.5) { G.reincarnationPointGain = G.reincarnationPointGain.times(5) } @@ -4906,7 +3923,7 @@ export const resetCheck = async ( if (manual) { void resetConfirmation('prestige') } else { - resetachievementcheck(1) + resetAchievementCheck('prestige') reset('prestige') } } @@ -4921,7 +3938,7 @@ export const resetCheck = async ( void resetConfirmation('transcend') } if (!manual) { - resetachievementcheck(2) + resetAchievementCheck('transcension') reset('transcension') } } @@ -4968,9 +3985,8 @@ export const resetCheck = async ( player.highestchallengecompletions[q] += 1 highestChallengeRewards(q, player.highestchallengecompletions[q]) } - calculateCubeBlessings() } - challengeachievementcheck(q) + challengeAchievementCheck(q) if ( !player.retrychallenges || manual @@ -4997,7 +4013,7 @@ export const resetCheck = async ( void resetConfirmation('reincarnate') } if (!manual) { - resetachievementcheck(3) + resetAchievementCheck('reincarnation') reset('reincarnation') } } @@ -5051,11 +4067,18 @@ export const resetCheck = async ( player.highestchallengecompletions[q] += 1 highestChallengeRewards(q, player.highestchallengecompletions[q]) } - calculateHypercubeBlessings() - calculateTesseractBlessings() - calculateCubeBlessings() } - challengeachievementcheck(q) + challengeAchievementCheck(q) + if (player.highestchallengecompletions[8] > 0) { + player.unlocks.anthill = true + } + if (player.highestchallengecompletions[9] > 0) { + player.unlocks.talismans = true + player.unlocks.blessings = true + } + if (player.highestchallengecompletions[10] > 0) { + player.unlocks.ascensions = true + } if ( !player.retrychallenges || manual @@ -5070,7 +4093,6 @@ export const resetCheck = async ( } } updateChallengeDisplay() - calculateRuneLevels() calculateAnts() } if (player.shopUpgrades.instantChallenge === 0 || leaving) { @@ -5081,7 +4103,7 @@ export const resetCheck = async ( if (i === 'ascension') { if ( - player.achievements[141] > 0 + player.unlocks.ascensions && (!player.toggles[31] || player.challengecompletions[10] > 0) ) { if (manual) { @@ -5117,7 +4139,19 @@ export const resetCheck = async ( updateChallengeLevel(a) challengeDisplay(a, false) } - challengeachievementcheck(a, true) + challengeAchievementCheck(a) + if (player.highestchallengecompletions[11] > 0) { + player.unlocks.tesseracts = true + } + if (player.highestchallengecompletions[12] > 0) { + player.unlocks.spirits = true + } + if (player.highestchallengecompletions[13] > 0) { + player.unlocks.hypercubes = true + } + if (player.highestchallengecompletions[14] > 0) { + player.unlocks.platonics = true + } } if (a === 15) { const c15SM = challenge15ScoreMultiplier() @@ -5141,6 +4175,9 @@ export const resetCheck = async ( c15RewardUpdate() } } + if (player.challenge15Exponent >= 1e15) { + player.unlocks.hepteracts = true + } } if ( @@ -5175,7 +4212,7 @@ export const resetCheck = async ( } if (i === 'singularity') { - if (player.runelevels[6] === 0) { + if (runes.antiquities.level === 0) { return Alert(i18next.t('main.noAntiquity')) } @@ -5244,11 +4281,11 @@ export const resetConfirmation = async (i: string): Promise => { if (player.toggles[28]) { const r = await Confirm(i18next.t('main.prestigePrompt')) if (r) { - resetachievementcheck(1) + resetAchievementCheck('prestige') reset('prestige') } } else { - resetachievementcheck(1) + resetAchievementCheck('prestige') reset('prestige') } } @@ -5256,11 +4293,11 @@ export const resetConfirmation = async (i: string): Promise => { if (player.toggles[29]) { const z = await Confirm(i18next.t('main.transcendPrompt')) if (z) { - resetachievementcheck(2) + resetAchievementCheck('transcension') reset('transcension') } } else { - resetachievementcheck(2) + resetAchievementCheck('transcension') reset('transcension') } } @@ -5269,11 +4306,11 @@ export const resetConfirmation = async (i: string): Promise => { if (player.toggles[30]) { const z = await Confirm(i18next.t('main.reincarnatePrompt')) if (z) { - resetachievementcheck(3) + resetAchievementCheck('reincarnation') reset('reincarnation') } } else { - resetachievementcheck(3) + resetAchievementCheck('reincarnation') reset('reincarnation') } } @@ -5286,33 +4323,15 @@ export const resetConfirmation = async (i: string): Promise => { } } -export const updateEffectiveLevelMult = (): void => { - G.effectiveLevelMult = 1 - G.effectiveLevelMult *= 1 - + (player.researches[4] / 10) - * (1 + (1 / 2) * CalcECC('ascension', player.challengecompletions[14])) // Research 1x4 - G.effectiveLevelMult *= 1 + player.researches[21] / 100 // Research 2x6 - G.effectiveLevelMult *= 1 + player.researches[90] / 100 // Research 4x15 - G.effectiveLevelMult *= 1 + player.researches[131] / 200 // Research 6x6 - G.effectiveLevelMult *= 1 + ((player.researches[161] / 200) * 3) / 5 // Research 7x11 - G.effectiveLevelMult *= 1 + ((player.researches[176] / 200) * 2) / 5 // Research 8x1 - G.effectiveLevelMult *= 1 + ((player.researches[191] / 200) * 1) / 5 // Research 8x16 - G.effectiveLevelMult *= 1 + ((player.researches[146] / 200) * 4) / 5 // Research 6x21 - G.effectiveLevelMult *= 1 - + ((0.01 * Math.log(player.talismanShards + 1)) / Math.log(4)) - * Math.min(1, player.constantUpgrades[9]) - G.effectiveLevelMult *= G.challenge15Rewards.runeBonus.value -} - export const updateAll = (): void => { G.uFourteenMulti = new Decimal(1) G.uFifteenMulti = new Decimal(1) if (player.upgrades[14] > 0.5) { - G.uFourteenMulti = Decimal.pow(1.15, G.freeAccelerator) + G.uFourteenMulti = Decimal.pow(1.15, G.freeAccelerator).times(1e5) } if (player.upgrades[15] > 0.5) { - G.uFifteenMulti = Decimal.pow(1.15, G.freeAccelerator) + G.uFifteenMulti = Decimal.pow(1.15, G.freeAccelerator).times(1e5) } if (!player.unlocks.coinone && player.coins.gte(500)) { @@ -5331,34 +4350,10 @@ export const updateAll = (): void => { player.unlocks.coinfour = true revealStuff() } - if (player.achievements[169] === 0 && player.antPoints.gte(3)) { - achievementaward(169) - } - if (player.achievements[170] === 0 && player.antPoints.gte(1e5)) { - achievementaward(170) - } - if (player.achievements[171] === 0 && player.antPoints.gte(666666666)) { - achievementaward(171) - } - if (player.achievements[172] === 0 && player.antPoints.gte(1e20)) { - achievementaward(172) - } - if (player.achievements[173] === 0 && player.antPoints.gte(1e40)) { - achievementaward(173) - } - if (player.achievements[174] === 0 && player.antPoints.gte('1e500')) { - achievementaward(174) - } - if (player.achievements[175] === 0 && player.antPoints.gte('1e2500')) { - achievementaward(175) - } - if (player.researches[200] >= 1e5 && player.achievements[250] < 1) { - achievementaward(250) - } - if (player.cubeUpgrades[50] >= 1e5 && player.achievements[251] < 1) { - achievementaward(251) - } + awardAchievementGroup('antCrumbs') + awardUngroupedAchievement('thousandSuns') + awardUngroupedAchievement('thousandMoons') // Autobuy "Upgrades" Tab autoUpgrades() @@ -5426,56 +4421,54 @@ export const updateAll = (): void => { if ( player.toggles[10] - && player.achievements[78] === 1 + && getLevelMilestone('tier1CrystalAutobuy') === 1 && player.prestigePoints.gte(player.firstCostDiamonds) ) { buyMax(1, 'Diamonds') } if ( player.toggles[11] - && player.achievements[85] === 1 + && getLevelMilestone('tier2CrystalAutobuy') === 1 && player.prestigePoints.gte(player.secondCostDiamonds) ) { buyMax(2, 'Diamonds') } if ( player.toggles[12] - && player.achievements[92] === 1 + && getLevelMilestone('tier3CrystalAutobuy') === 1 && player.prestigePoints.gte(player.thirdCostDiamonds) ) { buyMax(3, 'Diamonds') } if ( player.toggles[13] - && player.achievements[99] === 1 + && getLevelMilestone('tier4CrystalAutobuy') === 1 && player.prestigePoints.gte(player.fourthCostDiamonds) ) { buyMax(4, 'Diamonds') } if ( player.toggles[14] - && player.achievements[106] === 1 + && getLevelMilestone('tier5CrystalAutobuy') === 1 && player.prestigePoints.gte(player.fifthCostDiamonds) ) { buyMax(5, 'Diamonds') } - updateEffectiveLevelMult() // update before prism rune, fixes c15 bug - let c = 0 - c += (Math.floor((G.rune3level / 16) * G.effectiveLevelMult) * 100) / 100 if ( player.upgrades[73] > 0.5 && player.currentChallenge.reincarnation !== 0 ) { c += 10 } + const logDiscount = getRuneEffects('prism').costDivisorLog10 if ( - player.achievements[79] > 0.5 + getLevelMilestone('tier1CrystalAutobuy') === 1 && player.prestigeShards.gte( Decimal.pow( 10, - G.crystalUpgradesCost[0] + G.crystalUpgradesCost[0] - logDiscount + G.crystalUpgradeCostIncrement[0] * Math.floor(Math.pow(player.crystalUpgrades[0] - 0.5 - c, 2) / 2) ) @@ -5484,11 +4477,11 @@ export const updateAll = (): void => { buyCrystalUpgrades(1, true) } if ( - player.achievements[86] > 0.5 + getLevelMilestone('tier2CrystalAutobuy') === 1 && player.prestigeShards.gte( Decimal.pow( 10, - G.crystalUpgradesCost[1] + G.crystalUpgradesCost[1] - logDiscount + G.crystalUpgradeCostIncrement[1] * Math.floor(Math.pow(player.crystalUpgrades[1] - 0.5 - c, 2) / 2) ) @@ -5497,11 +4490,11 @@ export const updateAll = (): void => { buyCrystalUpgrades(2, true) } if ( - player.achievements[93] > 0.5 + getLevelMilestone('tier3CrystalAutobuy') === 1 && player.prestigeShards.gte( Decimal.pow( 10, - G.crystalUpgradesCost[2] + G.crystalUpgradesCost[2] - logDiscount + G.crystalUpgradeCostIncrement[2] * Math.floor(Math.pow(player.crystalUpgrades[2] - 0.5 - c, 2) / 2) ) @@ -5510,11 +4503,11 @@ export const updateAll = (): void => { buyCrystalUpgrades(3, true) } if ( - player.achievements[100] > 0.5 + getLevelMilestone('tier4CrystalAutobuy') === 1 && player.prestigeShards.gte( Decimal.pow( 10, - G.crystalUpgradesCost[3] + G.crystalUpgradesCost[3] - logDiscount + G.crystalUpgradeCostIncrement[3] * Math.floor(Math.pow(player.crystalUpgrades[3] - 0.5 - c, 2) / 2) ) @@ -5523,11 +4516,11 @@ export const updateAll = (): void => { buyCrystalUpgrades(4, true) } if ( - player.achievements[107] > 0.5 + getLevelMilestone('tier5CrystalAutobuy') === 1 && player.prestigeShards.gte( Decimal.pow( 10, - G.crystalUpgradesCost[4] + G.crystalUpgradesCost[4] - logDiscount + G.crystalUpgradeCostIncrement[4] * Math.floor(Math.pow(player.crystalUpgrades[4] - 0.5 - c, 2) / 2) ) @@ -5650,46 +4643,10 @@ export const updateAll = (): void => { } // Talismans - if (player.researches[130] > 0 || player.researches[135] > 0) { - const talismansUnlocked = [ - player.achievements[119] > 0, - player.achievements[126] > 0, - player.achievements[133] > 0, - player.achievements[140] > 0, - player.achievements[147] > 0, - player.antUpgrades[11]! > 0 || player.ascensionCount > 0, - isShopTalismanUnlocked() - ] - let upgradedTalisman = false - - // First, we need to enhance all of the talismans. Then, we can fortify all of the talismans. - // If we were to do this in one loop, the players resources would be drained on individual expensive levels - // of early talismans before buying important enhances for the later ones. This results in drastically - // reduced overall gains when talisman resources are scarce. - if (player.autoEnhanceToggle && player.researches[135] > 0) { - for (let i = 0; i < talismansUnlocked.length; ++i) { - if (talismansUnlocked[i] && player.talismanRarity[i] < 6) { - upgradedTalisman = buyTalismanEnhance(i, true) || upgradedTalisman - } - } - } - if (player.autoFortifyToggle && player.researches[130] > 0) { - for (let i = 0; i < talismansUnlocked.length; ++i) { - const maxTalismanLevel = calculateMaxTalismanLevel(i) - if ( - talismansUnlocked[i] - && player.talismanLevels[i] < maxTalismanLevel - ) { - upgradedTalisman = buyTalismanLevels(i, true) || upgradedTalisman - } - } - } - - // Recalculate talisman-related upgrades and display on success - if (upgradedTalisman) { - updateTalismanInventory() - calculateRuneLevels() + if ((player.researches[130] > 0 || player.researches[135] > 0) && player.autoFortifyToggle) { + for (const key of Object.keys(talismans) as TalismanKeys[]) { + buyTalismanLevelToRarityIncrease(key, true) } } @@ -5730,14 +4687,7 @@ export const updateAll = (): void => { ) } let p = 1 - p += (1 / 100) - * (player.achievements[71] - + player.achievements[72] - + player.achievements[73] - + player.achievements[74] - + player.achievements[75] - + player.achievements[76] - + player.achievements[77]) + p += +getAchievementReward('conversionExponent') let a = 0 if (player.upgrades[106] > 0.5) { @@ -5818,31 +4768,13 @@ export const updateAll = (): void => { ) } - if (player.runeshards > player.maxofferings) { - player.maxofferings = player.runeshards + if (player.offerings.greaterThan(player.maxOfferings)) { + player.maxOfferings = new Decimal(player.offerings) } - if (player.researchPoints > player.maxobtainium) { - player.maxobtainium = player.researchPoints + if (player.obtainium.greaterThan(player.maxObtainium)) { + player.maxObtainium = new Decimal(player.obtainium) } - if (isNaN(player.runeshards)) { - player.runeshards = 0 - } - if (player.runeshards > 1e300) { - player.runeshards = 1e300 - } - if (isNaN(player.researchPoints)) { - player.researchPoints = 0 - } - if (player.researchPoints > 1e300) { - player.researchPoints = 1e300 - } - - G.optimalOfferingTimer = 600 - + 30 * player.researches[85] - + 0.4 * G.rune5level - + 120 * player.shopUpgrades.offeringEX - G.optimalObtainiumTimer = 3600 + 120 * player.shopUpgrades.obtainiumEX autoBuyAnts() if ( @@ -5981,6 +4913,14 @@ export const constantIntervals = (): void => { setInterval(slowUpdates, 200) setInterval(fastUpdates, 50) setInterval(campaignIconHTMLUpdates, 15000) + setInterval(updateAllRuneLevelsFromEXP, 25) + setInterval(updateTalismanRarities, 250) + setInterval(() => awardAchievementGroup('runeFreeLevel'), 25) + setInterval(() => { + for (const key of Object.keys(progressiveAchievements) as ProgressiveAchievements[]) { + updateProgressiveCache(key) + } + }, 25) if (!G.timeWarp) { exitOffline() @@ -6053,7 +4993,7 @@ const tack = (dt: number) => { } // Triggers automatic ant sacrifice (adds milliseonds to payload timers) - if (player.achievements[173] === 1) { + if (getAchievementReward('antSacrificeUnlock')) { automaticTools('antSacrifice', dt) } @@ -6065,25 +5005,37 @@ const tack = (dt: number) => { calculateObtainium() } - // Automatically tries and buys researches lol + // Regular Research Automation (only once) + if (player.autoResearchToggle && player.autoResearch > 0 && player.autoResearchMode === 'manual') { + const auto = true + const hover = false + buyResearch(player.autoResearch, auto, hover) + updateResearchAuto(player.autoResearch) + } + + // Roomba! (Cube Upgrade 1x9) if ( player.autoResearchToggle && player.autoResearch > 0 - && player.autoResearch <= maxRoombaResearchIndex(player) - && (autoResearchEnabled() || player.autoResearchMode === 'manual') + && roombaResearchEnabled() + && player.autoResearchMode === 'cheapest' ) { - // buyResearch() probably shouldn't even be called if player.autoResearch exceeds the highest unlocked research let counter = 0 - const maxCount = 1 + player.challengecompletions[14] + const maxCount = 1 + Math.floor(CalcECC('ascension', player.challengecompletions[14])) while (counter < maxCount) { - if (player.autoResearch > 0) { - const linGrowth = player.autoResearch === 200 ? 0.01 : 0 - if (!buyResearch(player.autoResearch, true, linGrowth)) { - break - } + const currIndex = player.autoResearch + if (currIndex > 0) { + const auto = true + const hover = false + buyResearch(currIndex, auto, hover) } else { break } + updateResearchRoomba() + // If not max level, you could not afford the research, so do not run more times + if (player.researches[currIndex] < researchData[currIndex].maxLevel) { + break + } counter++ } } @@ -6094,11 +5046,6 @@ const tack = (dt: number) => { automaticTools('addOfferings', dt / 2) } - // Adds an offering every 1/(cube upgrade 1x2) seconds. It shares a timer with the one above. - if (player.cubeUpgrades[2] > 0) { - automaticTools('addOfferings', dt * player.cubeUpgrades[2]) - } - runChallengeSweep(dt) // Check for automatic resets @@ -6106,13 +5053,13 @@ const tack = (dt: number) => { if (player.resettoggle1 === 1 || player.resettoggle1 === 0) { if ( player.toggles[15] - && player.achievements[43] === 1 + && getLevelMilestone('autoPrestige') === 1 && G.prestigePointGain.gte( player.prestigePoints.times(Decimal.pow(10, player.prestigeamount)) ) && player.coinsThisPrestige.gte(1e16) ) { - resetachievementcheck(1) + resetAchievementCheck('prestige') reset('prestige', true) } } @@ -6121,11 +5068,11 @@ const tack = (dt: number) => { const time = Math.max(0.01, player.prestigeamount) if ( player.toggles[15] - && player.achievements[43] === 1 + && getLevelMilestone('autoPrestige') === 1 && G.autoResetTimers.prestige >= time && player.coinsThisPrestige.gte(1e16) ) { - resetachievementcheck(1) + resetAchievementCheck('transcension') reset('prestige', true) } } @@ -6140,7 +5087,7 @@ const tack = (dt: number) => { && player.coinsThisTranscension.gte(1e100) && player.currentChallenge.transcension === 0 ) { - resetachievementcheck(2) + resetAchievementCheck('transcension') reset('transcension', true) } } @@ -6154,7 +5101,7 @@ const tack = (dt: number) => { && player.coinsThisTranscension.gte(1e100) && player.currentChallenge.transcension === 0 ) { - resetachievementcheck(2) + resetAchievementCheck('transcension') reset('transcension', true) } } @@ -6171,7 +5118,7 @@ const tack = (dt: number) => { && player.currentChallenge.transcension === 0 && player.currentChallenge.reincarnation === 0 ) { - resetachievementcheck(3) + resetAchievementCheck('reincarnation') reset('reincarnation', true) } } @@ -6188,7 +5135,7 @@ const tack = (dt: number) => { && player.currentChallenge.transcension === 0 && player.currentChallenge.reincarnation === 0 ) { - resetachievementcheck(3) + resetAchievementCheck('reincarnation') reset('reincarnation', true) } } @@ -6261,11 +5208,11 @@ export const synergismHotkeys = (event: KeyboardEvent, key: string): void => { } if (G.currentTab === Tabs.Runes) { if (getActiveSubTab() === 0) { - redeemShards(num) + sacrificeOfferings(indexToRune[num], player.offerings) } else if (getActiveSubTab() === 2) { - buyRuneBonusLevels('Blessings', num) + buyBlessingLevels(indexToRune[num] as RuneBlessingKeys, player.offerings) } else if (getActiveSubTab() === 3) { - buyRuneBonusLevels('Spirits', num) + buySpiritLevels(indexToRune[num] as RuneSpiritKeys, player.offerings) } } if (G.currentTab === Tabs.Challenges) { @@ -6286,6 +5233,11 @@ export const synergismHotkeys = (event: KeyboardEvent, key: string): void => { toggleChallenges(6) challengeDisplay(6) } + if (G.currentTab === Tabs.Runes) { + if (getActiveSubTab() === 0) { + sacrificeOfferings('infiniteAscent', player.offerings) + } + } break case '7': if (G.currentTab === Tabs.Buildings && G.buildingSubTab === 'diamond') { @@ -6295,6 +5247,11 @@ export const synergismHotkeys = (event: KeyboardEvent, key: string): void => { toggleChallenges(7) challengeDisplay(7) } + if (G.currentTab === Tabs.Runes) { + if (getActiveSubTab() === 0) { + sacrificeOfferings('antiquities', player.offerings) + } + } break case '8': if (G.currentTab === Tabs.Buildings && G.buildingSubTab === 'diamond') { @@ -6372,7 +5329,92 @@ export const reloadShit = (reset = false) => { loadSynergy() } - initRedAmbrosiaUpgrades(player.redAmbrosiaUpgrades) + // Recover Sing Upgrade (now GQ upgrade) level from Player Obj + if (player.goldenQuarkUpgrades !== undefined) { + for (const [key, value] of Object.entries(player.goldenQuarkUpgrades)) { + const k = key as SingularityDataKeys + goldenQuarkUpgrades[k].level = value.level + goldenQuarkUpgrades[k].freeLevel = value.freeLevel + goldenQuarkUpgrades[k].goldenQuarksInvested = value.goldenQuarksInvested + } + } + + // Recover Oct Upgrades level from Player Obj + if (player.octUpgrades !== undefined) { + for (const [key, value] of Object.entries(player.octUpgrades)) { + const k = key as OcteractDataKeys + + octeractUpgrades[k].level = value.level + octeractUpgrades[k].freeLevel = value.freeLevel + octeractUpgrades[k].octeractsInvested = value.octeractsInvested + } + } + + if (player.ambrosiaUpgrades !== undefined) { + for (const [key, value] of Object.entries(player.ambrosiaUpgrades)) { + const k = key as AmbrosiaUpgradeNames + ambrosiaUpgrades[k].ambrosiaInvested = value.ambrosiaInvested ?? 0 + ambrosiaUpgrades[k].blueberriesInvested = value.blueberriesInvested ?? 0 + } + } + + if (player.redAmbrosiaUpgrades !== undefined) { + for (const [key, value] of Object.entries(player.redAmbrosiaUpgrades)) { + const k = key as RedAmbrosiaNames + redAmbrosiaUpgrades[k].redAmbrosiaInvested = value + } + } + + if (player.talismans !== undefined) { + for (const key of Object.keys(player.talismans) as TalismanKeys[]) { + updateTalismanLevelAndSpentFromInvested(key) + } + } + + if (player.runes !== undefined) { + for (const key of Object.keys(player.runes) as RuneKeys[]) { + const runeEXP = player.runes[key] + runes[key].runeEXP = new Decimal(runeEXP) + } + updateAllRuneLevelsFromEXP() + } + + if (player.runeBlessings !== undefined) { + for (const key of Object.keys(player.runeBlessings) as RuneBlessingKeys[]) { + const blessingEXP = player.runeBlessings[key] + runeBlessings[key].runeEXP = new Decimal(blessingEXP) + } + updateAllBlessingLevelsFromEXP() + } + + if (player.runeSpirits !== undefined) { + for (const key of Object.keys(player.runeSpirits) as RuneSpiritKeys[]) { + const spiritEXP = player.runeSpirits[key] + runeSpirits[key].runeEXP = new Decimal(spiritEXP) + } + updateAllSpiritLevelsFromEXP() + } + + if (player.hepteracts !== undefined) { + for (const key of Object.keys(player.hepteracts) as HepteractKeys[]) { + hepteracts[key].BAL = player.hepteracts[key].BAL ?? hepteracts[key].BAL + hepteracts[key].AUTO = player.hepteracts[key].AUTO ?? hepteracts[key].AUTO + hepteracts[key].TIMES_CAP_EXTENDED = player.hepteracts[key].TIMES_CAP_EXTENDED + ?? hepteracts[key].TIMES_CAP_EXTENDED + } + } + + // Probably want to remove Corruptions from Player Object... + player.corruptions.used = new CorruptionLoadout(player.corruptions.used.loadout) + // This is needed to fix saves that had issues with not resetting corruption at the singularity + player.corruptions.used.setCorruptionLevelsWithChallengeRequirement(player.corruptions.used.loadout) + + updateTokens() + updateMaxTokens() + updateAchievementPoints() + setAmbrosiaUpgradeLevels() + setRedAmbrosiaUpgradeLevels() + refundOvercapResearches() if (!reset) { calculateOffline() @@ -6387,6 +5429,8 @@ export const reloadShit = (reset = false) => { } toggleTheme(true) + toggleShops() + setAutomaticHepteractTexts() settingAnnotation() toggleIconSet() toggleauto() @@ -6424,6 +5468,10 @@ export const reloadShit = (reset = false) => { showExitOffline() campaignIconHTMLUpdates() campaignTokenRewardHTMLUpdate() + updateAllUngroupedAchievementProgress() + updateAllGroupedAchievementProgress() + updateAllProgressiveAchievementProgress() + updateChallengeDisplay() clearTimeout(preloadDeleteGame) if (localStorage.getItem('pleaseStar') === null) { @@ -6451,7 +5499,7 @@ export const reloadShit = (reset = false) => { } window.addEventListener('load', async () => { - if (dev) { + if (dev || testing) { const { worker } = await import('./mock/browser') await worker.start({ serviceWorker: { @@ -6460,6 +5508,8 @@ window.addEventListener('load', async () => { }) } + isMobileDevice() + await i18nInit() handleLogin().catch(console.error) @@ -6496,11 +5546,21 @@ window.addEventListener('load', async () => { } document.title = `Synergism v${version}` + generateRunesHTML() + generateTalismansHTML() + generateBlessingsHTML() + generateSpiritsHTML() generateEventHandlers() corruptionButtonsAdd() corruptionLoadoutTableCreate() createCampaignIconHTMLS() - initRedAmbrosiaUpgrades(player.redAmbrosiaUpgrades) + generateAchievementHTMLs() + generateLevelRewardHTMLs() + generateLevelMilestoneHTMLS() + + // Initialize messages on game load + fetchUnreadMessages().catch(console.error) + reloadShit() }, { once: true }) diff --git a/src/Tabs.ts b/src/Tabs.ts index 9200f23ac..a00753e08 100644 --- a/src/Tabs.ts +++ b/src/Tabs.ts @@ -1,11 +1,15 @@ +import { awardUngroupedAchievement } from './Achievements' import { DOMCacheGetOrSet, DOMCacheHas } from './Cache/DOM' import { prod } from './Config' import { pressedKeys } from './Hotkeys' import { isLoggedIn } from './Login' +import { hasUnreadMessages } from './Messages' import { initializeCart } from './purchases/CartTab' +import { getGQUpgradeEffect } from './singularity' import { player } from './Synergism' import { setActiveSettingScreen, + toggleAchievementScreen, toggleBuildingScreen, toggleChallengesScreen, toggleCorruptionLoadoutsStats, @@ -82,7 +86,14 @@ const subtabInfo: Record = { buttonID: 'switchSettingSubTab7' }, { subTabID: 'hotkeys', unlocked: true, buttonID: 'switchSettingSubTab8' }, - { subTabID: 'accountSubTab', unlocked: true, buttonID: 'switchSettingSubTab9' } + { subTabID: 'accountSubTab', unlocked: true, buttonID: 'switchSettingSubTab9' }, + { + subTabID: 'messagesSubTab', + get unlocked () { + return hasUnreadMessages() + }, + buttonID: 'switchSettingSubTab10' + } ] }, [Tabs.Shop]: { @@ -118,7 +129,7 @@ const subtabInfo: Record = { { subTabID: 'tesseract', get unlocked () { - return player.achievements[183] > 0 + return player.ascensionCount > 0 }, buttonID: 'switchToTesseractBuilding' } @@ -129,8 +140,24 @@ const subtabInfo: Record = { subtabIndex: 0 }, [Tabs.Achievements]: { - subTabList: [], - subtabIndex: 0 + tabSwitcher: () => toggleAchievementScreen, + subtabIndex: 0, + subTabList: [ + { + subTabID: '1', + get unlocked () { + return true + }, + buttonID: 'toggleAchievementSubTab1' + }, + { + subTabID: '2', + get unlocked () { + return true + }, + buttonID: 'toggleAchievementSubTab2' + } + ] }, [Tabs.Runes]: { tabSwitcher: () => toggleRuneScreen, @@ -146,21 +173,21 @@ const subtabInfo: Record = { { subTabID: '2', get unlocked () { - return player.achievements[134] > 0 + return player.unlocks.talismans }, buttonID: 'toggleRuneSubTab2' }, { subTabID: '3', get unlocked () { - return player.achievements[134] > 0 + return player.unlocks.blessings }, buttonID: 'toggleRuneSubTab3' }, { subTabID: '4', get unlocked () { - return player.achievements[204] > 0 + return player.unlocks.spirits }, buttonID: 'toggleRuneSubTab4' } @@ -195,49 +222,49 @@ const subtabInfo: Record = { { subTabID: '1', get unlocked () { - return player.achievements[141] > 0 + return player.unlocks.ascensions }, buttonID: 'switchCubeSubTab1' }, { subTabID: '2', get unlocked () { - return player.achievements[197] > 0 + return player.unlocks.tesseracts }, buttonID: 'switchCubeSubTab2' }, { subTabID: '3', get unlocked () { - return player.achievements[211] > 0 + return player.unlocks.hypercubes }, buttonID: 'switchCubeSubTab3' }, { subTabID: '4', get unlocked () { - return player.achievements[218] > 0 + return player.unlocks.platonics }, buttonID: 'switchCubeSubTab4' }, { subTabID: '5', get unlocked () { - return player.achievements[141] > 0 + return player.unlocks.ascensions }, buttonID: 'switchCubeSubTab5' }, { subTabID: '6', get unlocked () { - return player.achievements[218] > 0 + return player.unlocks.platonics }, buttonID: 'switchCubeSubTab6' }, { subTabID: '7', get unlocked () { - return player.challenge15Exponent >= G.challenge15Rewards.hepteractsUnlocked.requirement + return player.unlocks.hepteracts }, buttonID: 'switchCubeSubTab7' } @@ -253,16 +280,12 @@ const subtabInfo: Record = { subTabList: [ { subTabID: 'true', - get unlocked () { - return player.achievements[141] > 0 - }, + unlocked: true, buttonID: 'corrStatsBtn' }, { subTabID: 'false', - get unlocked () { - return player.achievements[141] > 0 - }, + unlocked: true, buttonID: 'corrLoadoutsBtn' } ] @@ -288,7 +311,7 @@ const subtabInfo: Record = { { subTabID: '3', get unlocked () { - return Boolean(player.singularityUpgrades.octeractUnlock.getEffect().bonus) + return Boolean(getGQUpgradeEffect('octeractUnlock')) }, buttonID: 'toggleSingularitySubTab3' }, @@ -582,8 +605,8 @@ tabRow.appendButton( .setType(Tabs.Upgrades) .makeDraggable() .makeRemoveable(), - new $Tab({ id: 'achievementstab', i18n: 'tabs.main.achievements', class: 'coinunlock4' }) - .setUnlockedState(() => player.unlocks.coinfour) + new $Tab({ /*class: 'prestigeunlock',*/ id: 'achievementstab', i18n: 'tabs.main.achievements' }) + // .setUnlockedState(() => player.unlocks.prestige) .setType(Tabs.Achievements) .makeDraggable() .makeRemoveable(), @@ -603,12 +626,12 @@ tabRow.appendButton( .makeDraggable() .makeRemoveable(), new $Tab({ class: 'chal8', id: 'anttab', i18n: 'tabs.main.antHill' }) - .setUnlockedState(() => player.achievements[127] > 0) + .setUnlockedState(() => player.unlocks.anthill) .setType(Tabs.AntHill) .makeDraggable() .makeRemoveable(), new $Tab({ class: 'chal10', id: 'cubetab', i18n: 'tabs.main.wowCubes' }) - .setUnlockedState(() => player.achievements[141] > 0) + .setUnlockedState(() => player.unlocks.ascensions) .setType(Tabs.WowCubes) .makeDraggable() .makeRemoveable(), @@ -685,6 +708,10 @@ export const changeTab = (tabs: Tabs, step?: number) => { G.currentTab = tabRow.getCurrentTab().getType() subtabInfo[tabRow.getCurrentTab().getType()].subtabIndex + if (G.currentTab === Tabs.Achievements) { + awardUngroupedAchievement('participationTrophy') + } + revealStuff() hideStuff() ;(document.activeElement as HTMLElement | null)?.blur() diff --git a/src/Talismans.ts b/src/Talismans.ts index 5b8ce5a6a..94cba152a 100644 --- a/src/Talismans.ts +++ b/src/Talismans.ts @@ -1,13 +1,34 @@ +import Decimal from 'break_infinity.js' import i18next from 'i18next' -import { achievementaward } from './Achievements' +import { achievementPoints, awardUngroupedAchievement, getAchievementReward } from './Achievements' import { DOMCacheGetOrSet } from './Cache/DOM' -import { calculateRuneLevels } from './Calculate' +import { isShopTalismanUnlocked } from './Calculate' import { CalcECC } from './Challenges' +import { getLevelMilestone } from './Levels' +import { getOcteractUpgradeEffect } from './Octeracts' import { PCoinUpgradeEffects } from './PseudoCoinUpgrades' -import { format, player } from './Synergism' +import { resetTiers, type RuneKeys, runes } from './Runes' +import { allTalismanRuneBonusStatsSum } from './Statistics' +import { format, formatAsPercentIncrease, player } from './Synergism' +import { Tabs } from './Tabs' +import { assert } from './Utility' import { Globals as G } from './Variables' -const talismanResourceCosts = { +interface TalismanFragmentCost { + obtainium: number + offerings: number +} + +export type TalismanCraftItems = + | 'shard' + | 'commonFragment' + | 'uncommonFragment' + | 'rareFragment' + | 'epicFragment' + | 'legendaryFragment' + | 'mythicalFragment' + +const talismanResourceCosts: Record = { shard: { obtainium: 1e13, offerings: 1e2 @@ -38,558 +59,1404 @@ const talismanResourceCosts = { } } -const num = ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven'] as const +export type TalismanRuneBonus = Record -export const calculateMaxTalismanLevel = (i: number) => { - let maxLevel = 30 * player.talismanRarity[i] - maxLevel += 6 * CalcECC('ascension', player.challengecompletions[13]) - maxLevel += Math.floor(player.researches[200] / 400) +type TalismanTypeMap = { + exemption: { taxReduction: number; duplicationOOMBonus: number } + chronos: { globalSpeed: number; speedOOMBonus: number } + midas: { blessingBonus: number; thriftOOMBonus: number } + metaphysics: { talismanEffect: number; extraTalismanEffect: number } + polymath: { ascensionSpeedBonus: number; SIOOMBonus: number } + mortuus: { antBonus: number; prismOOMBonus: number } + plastic: { quarkBonus: number } + wowSquare: { evenDimBonus: number; oddDimBonus: number } + achievement: { positiveSalvageMult: number; negativeSalvageMult: number } + cookieGrandma: { freeCorruptionLevel: number; cookieSix: boolean } + horseShoe: { luckPercentage: number; redLuck: number } +} - if (i === 6) { - maxLevel += PCoinUpgradeEffects.INSTANT_UNLOCK_1 ? 10 : 0 - } +export type TalismanKeys = keyof TalismanTypeMap - if (player.cubeUpgrades[67] > 0 && i === 3) { - maxLevel += 1337 - } +export const noTalismanFragments: Record = { + shard: new Decimal(0), + commonFragment: new Decimal(0), + uncommonFragment: new Decimal(0), + rareFragment: new Decimal(0), + epicFragment: new Decimal(0), + legendaryFragment: new Decimal(0), + mythicalFragment: new Decimal(0) +} - return maxLevel +const rarityValues: Record = { + 0: 0, + 1: 1, + 2: 1.2, + 3: 1.5, + 4: 1.8, + 5: 2.1, + 6: 2.5, + 7: 3, + 8: 3.25, + 9: 3.5, + 10: 4 } -const getTalismanResourceInfo = ( - type: keyof typeof talismanResourceCosts, - percentage = player.buyTalismanShardPercent -) => { - const obtainiumCost = talismanResourceCosts[type].obtainium - const offeringCost = talismanResourceCosts[type].offerings +interface TalismanData { + level: number + rarity: number + baseMult: Decimal + maxLevel: number + costs: (this: void, baseMult: Decimal, level: number) => Record + levelCapIncrease: () => number + effects(n: number): TalismanTypeMap[K] + inscriptionDesc(n: number): string + signatureDesc(n: number): string + isUnlocked: () => boolean + minimalResetTier: keyof typeof resetTiers + talismanBaseCoefficient: TalismanRuneBonus + name: () => string + description: () => string + + // Field that is stored in the player + fragmentsInvested: Record +} + +const regularCostProgression = (baseMult: Decimal, level: number): Record => { + let priceMult = baseMult + if (level >= 120) { + priceMult = priceMult.times((level - 90) / 30) + } + if (level >= 150) { + priceMult = priceMult.times((level - 120) / 30) + } + if (level >= 180) { + priceMult = priceMult.times((level - 170) / 10) + } + + const shardCost = Decimal.pow(level, 3).times(1 / 8).plus(1).floor().times(priceMult) + const commonCost = level >= 30 + ? Decimal.pow(level - 30, 3).times(1 / 32).plus(1).floor().times(priceMult) + : new Decimal(0) + const uncommonCost = level >= 60 + ? Decimal.pow(level - 60, 3).times(1 / 384).plus(1).floor().times(priceMult) + : new Decimal(0) + const rareCost = level >= 90 + ? Decimal.pow(level - 90, 3).times(1 / 500).plus(1).floor().times(priceMult) + : new Decimal(0) + const epicCost = level >= 120 + ? Decimal.pow(level - 120, 3).times(1 / 375).plus(1).floor().times(priceMult) + : new Decimal(0) + const legendaryCost = level >= 150 + ? Decimal.pow(level - 150, 3).times(1 / 192).plus(1).floor().times(priceMult) + : new Decimal(0) + const mythicalCost = level >= 150 + ? Decimal.pow(level - 150, 3).times(1 / 1280).plus(1).floor().times(priceMult) + : new Decimal(0) - const maxBuyObtainium = Math.max(1, Math.floor(player.researchPoints / obtainiumCost)) - const maxBuyOffering = Math.max(1, Math.floor(player.runeshards / offeringCost)) - const amountToBuy = Math.max(1, Math.floor(percentage / 100 * Math.min(maxBuyObtainium, maxBuyOffering))) - const canBuy = obtainiumCost <= player.researchPoints && offeringCost <= player.runeshards return { - canBuy, // Boolean, if false will not buy any fragments - buyAmount: amountToBuy, // Integer, will buy as specified above. - obtainiumCost: obtainiumCost * amountToBuy, // Integer, cost in obtainium to buy (buyAmount) resource - offeringCost: offeringCost * amountToBuy // Integer, cost in offerings to buy (buyAmount) resource + 'shard': Decimal.max(0, shardCost), + 'commonFragment': Decimal.max(0, commonCost), + 'uncommonFragment': Decimal.max(0, uncommonCost), + 'rareFragment': Decimal.max(0, rareCost), + 'epicFragment': Decimal.max(0, epicCost), + 'legendaryFragment': Decimal.max(0, legendaryCost), + 'mythicalFragment': Decimal.max(0, mythicalCost) } } -export const updateTalismanCostDisplay = ( - type: keyof typeof talismanResourceCosts | null, - percentage = player.buyTalismanShardPercent -) => { - const el = DOMCacheGetOrSet('talismanFragmentCost') - if (type) { - const talismanCostInfo = getTalismanResourceInfo(type, percentage) - const talismanShardName = i18next.t(`runes.talismans.shards.${type}`) +const exponentialCostProgression = ( + baseMult: Decimal, + level: number, + ratio: number +): Record => { + const baseMultDecimal = new Decimal(baseMult) - el.textContent = i18next.t('runes.talismans.costToBuy', { - name: talismanShardName, - buyAmount: format(talismanCostInfo.buyAmount), - obtainium: format(talismanCostInfo.obtainiumCost), - offerings: format(talismanCostInfo.offeringCost) - }) - } else { - // Buy All - el.textContent = i18next.t('runes.talismans.clickBuyEveryType') + return { + shard: Decimal.pow(ratio, level).times(baseMultDecimal).times(100).floor(), + commonFragment: level >= 30 + ? Decimal.pow(ratio, level - 30).times(baseMultDecimal).times(50).floor() + : new Decimal(0), + uncommonFragment: level >= 60 + ? Decimal.pow(ratio, level - 60).times(baseMultDecimal).times(25).floor() + : new Decimal(0), + rareFragment: level >= 90 + ? Decimal.pow(ratio, level - 90).times(baseMultDecimal).times(20).floor() + : new Decimal(0), + epicFragment: level >= 120 + ? Decimal.pow(ratio, level - 120).times(baseMultDecimal).times(15).floor() + : new Decimal(0), + legendaryFragment: level >= 150 + ? Decimal.pow(ratio, level - 150).times(baseMultDecimal).times(10).floor() + : new Decimal(0), + mythicalFragment: level >= 150 + ? Decimal.pow(ratio, level - 150).times(baseMultDecimal).times(5).floor() + : new Decimal(0) } } -export const toggleTalismanBuy = (i = player.buyTalismanShardPercent) => { - DOMCacheGetOrSet('talismanTen').style.backgroundColor = '' - DOMCacheGetOrSet('talismanTwentyFive').style.backgroundColor = '' - DOMCacheGetOrSet('talismanFifty').style.backgroundColor = '' - DOMCacheGetOrSet('talismanHundred').style.backgroundColor = '' - player.buyTalismanShardPercent = i - let x = 'Ten' - if (i === 25) { - x = 'TwentyFive' +export const universalTalismanMaxLevelIncreasers = () => { + return ( + 6 * CalcECC('ascension', player.challengecompletions[13]) + + Math.floor(player.researches[200] / 400) + + +player.singularityChallenges.taxmanLastStand.rewards.talismanFreeLevel + + getOcteractUpgradeEffect('octeractTalismanLevelCap1') + + getOcteractUpgradeEffect('octeractTalismanLevelCap2') + + getOcteractUpgradeEffect('octeractTalismanLevelCap3') + + getOcteractUpgradeEffect('octeractTalismanLevelCap4') + ) +} + +export const metaphysicsTalismanMaxLevelIncreasers = () => { + return player.cubeUpgrades[67] > 0 ? 1337 : 0 +} + +export const plasticTalismanMaxLevelIncreasers = () => { + return PCoinUpgradeEffects.INSTANT_UNLOCK_1 ? 10 : 0 +} + +export const talismans: { [K in TalismanKeys]: TalismanData } = { + exemption: { + level: 0, + rarity: 0, + fragmentsInvested: { ...noTalismanFragments }, + baseMult: new Decimal(1), + maxLevel: 180, + costs: regularCostProgression, + levelCapIncrease: () => universalTalismanMaxLevelIncreasers(), + effects: (n) => { + const inscriptValues = [0, -0.2, -0.3, -0.4, -0.45, -0.5, -0.55, -0.6, -0.61, -0.62, -0.65] + const duplicationBonus = (n >= 6) ? 12 : 0 + return { + taxReduction: inscriptValues[n] ?? 0, + duplicationOOMBonus: duplicationBonus + } + }, + inscriptionDesc: (n) => { + const inscriptValues = [0, -0.2, -0.3, -0.4, -0.45, -0.5, -0.55, -0.6, -0.61, -0.62, -0.65] + return i18next.t('runes.talismans.exemption.inscription', { + val: format(1 + (inscriptValues[n] ?? 1), 2, true) + }) + }, + signatureDesc: (n) => { + const duplicationBonus = (n >= 6) ? 12 : 0 + return i18next.t('runes.talismans.exemption.signature', { + val: format(duplicationBonus, 0, true) + }) + }, + talismanBaseCoefficient: { + speed: 0, + duplication: 1.5, + prism: 0.75, + thrift: 0.75, + superiorIntellect: 0, + infiniteAscent: 0, + antiquities: 0, + horseShoe: 0, + finiteDescent: 0 + }, + minimalResetTier: 'ascension', + isUnlocked: () => { + return player.unlocks.talismans + }, + name: () => i18next.t('runes.talismans.exemption.name'), + description: () => i18next.t('runes.talismans.exemption.description') + }, + chronos: { + level: 0, + rarity: 0, + fragmentsInvested: { ...noTalismanFragments }, + baseMult: new Decimal(10), + maxLevel: 180, + costs: regularCostProgression, + levelCapIncrease: () => universalTalismanMaxLevelIncreasers(), + effects: (n) => { + const inscriptValues = [1, 1.04, 1.08, 1.12, 1.16, 1.20, 1.25, 1.30, 1.325, 1.35, 1.4] + const speedBonus = (n >= 6) ? 12 : 0 + return { + globalSpeed: inscriptValues[n] ?? 1, + speedOOMBonus: speedBonus + } + }, + inscriptionDesc: (n) => { + const inscriptValues = [1, 1.04, 1.08, 1.12, 1.16, 1.20, 1.25, 1.30, 1.325, 1.35, 1.4] + return i18next.t('runes.talismans.chronos.inscription', { + val: formatAsPercentIncrease(inscriptValues[n] ?? 1, 0) + }) + }, + signatureDesc: (n) => { + const speedBonus = (n >= 6) ? 12 : 0 + return i18next.t('runes.talismans.chronos.signature', { + val: format(speedBonus, 0, true) + }) + }, + talismanBaseCoefficient: { + speed: 1.5, + duplication: 0, + prism: 0, + thrift: 0.75, + superiorIntellect: 0.75, + infiniteAscent: 0, + antiquities: 0, + horseShoe: 0, + finiteDescent: 0 + }, + minimalResetTier: 'ascension', + isUnlocked: () => { + return Boolean(getAchievementReward('chronosTalisman')) + }, + name: () => i18next.t('runes.talismans.chronos.name'), + description: () => i18next.t('runes.talismans.chronos.description') + }, + midas: { + level: 0, + rarity: 0, + fragmentsInvested: { ...noTalismanFragments }, + baseMult: new Decimal(1e4), + maxLevel: 180, + costs: regularCostProgression, + levelCapIncrease: () => universalTalismanMaxLevelIncreasers(), + effects: (n) => { + const inscriptValues = [1, 1.04, 1.08, 1.12, 1.16, 1.20, 1.25, 1.30, 1.325, 1.35, 1.40] + const thriftBonus = (n >= 6) ? 12 : 0 + return { + blessingBonus: inscriptValues[n] ?? 1, + thriftOOMBonus: thriftBonus + } + }, + inscriptionDesc: (n) => { + const inscriptValues = [1, 1.04, 1.08, 1.12, 1.16, 1.20, 1.25, 1.30, 1.325, 1.35, 1.40] + return i18next.t('runes.talismans.midas.inscription', { + val: formatAsPercentIncrease(inscriptValues[n] ?? 1, 0) + }) + }, + signatureDesc: (n) => { + const thriftBonus = (n >= 6) ? 12 : 0 + return i18next.t('runes.talismans.midas.signature', { + val: format(thriftBonus, 0, true) + }) + }, + talismanBaseCoefficient: { + speed: 0, + duplication: 0.75, + prism: 0.75, + thrift: 1.5, + superiorIntellect: 0, + infiniteAscent: 0, + antiquities: 0, + horseShoe: 0, + finiteDescent: 0 + }, + minimalResetTier: 'ascension', + isUnlocked: () => { + return Boolean(getAchievementReward('midasTalisman')) + }, + name: () => i18next.t('runes.talismans.midas.name'), + description: () => i18next.t('runes.talismans.midas.description') + }, + metaphysics: { + level: 0, + rarity: 0, + fragmentsInvested: { ...noTalismanFragments }, + baseMult: new Decimal(1e8), + maxLevel: 180, + costs: regularCostProgression, + levelCapIncrease: () => { + return universalTalismanMaxLevelIncreasers() + metaphysicsTalismanMaxLevelIncreasers() + }, + effects: (n) => { + const inscriptValues = [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2] + const signatureValue = (n >= 6) ? 1.07 : 1 + return { + talismanEffect: inscriptValues[n] ?? 1, + extraTalismanEffect: signatureValue + } + }, + inscriptionDesc: (n) => { + const inscriptValues = [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2] + return i18next.t('runes.talismans.metaphysics.inscription', { + val: formatAsPercentIncrease(inscriptValues[n] ?? 1, 0) + }) + }, + signatureDesc: (n) => { + const signatureValue = (n >= 6) ? 1.07 : 1 + return i18next.t('runes.talismans.metaphysics.signature', { + val: formatAsPercentIncrease(signatureValue, 2) + }) + }, + talismanBaseCoefficient: { + speed: 0.6, + duplication: 0.6, + prism: 0.6, + thrift: 0.6, + superiorIntellect: 0.6, + infiniteAscent: 0, + antiquities: 0, + horseShoe: 0, + finiteDescent: 0 + }, + minimalResetTier: 'ascension', + isUnlocked: () => { + return Boolean(getAchievementReward('metaphysicsTalisman')) + }, + name: () => i18next.t('runes.talismans.metaphysics.name'), + description: () => i18next.t('runes.talismans.metaphysics.description') + }, + polymath: { + level: 0, + rarity: 0, + fragmentsInvested: { ...noTalismanFragments }, + baseMult: new Decimal(1e16), + maxLevel: 180, + costs: regularCostProgression, + levelCapIncrease: () => universalTalismanMaxLevelIncreasers(), + effects: (n) => { + const inscriptValues = [1, 1.04, 1.08, 1.12, 1.16, 1.20, 1.25, 1.30, 1.325, 1.35, 1.40] + const SIOOMBonus = (n >= 6) ? 12 : 0 + return { + ascensionSpeedBonus: inscriptValues[n] ?? 1, + SIOOMBonus: SIOOMBonus + } + }, + inscriptionDesc: (n) => { + const inscriptValues = [1, 1.04, 1.08, 1.12, 1.16, 1.20, 1.25, 1.30, 1.325, 1.35, 1.40] + return i18next.t('runes.talismans.polymath.inscription', { + val: formatAsPercentIncrease(inscriptValues[n] ?? 1, 0) + }) + }, + signatureDesc: (n) => { + const SIOOMBonus = (n >= 6) ? 12 : 0 + return i18next.t('runes.talismans.polymath.signature', { + val: format(SIOOMBonus, 0, true) + }) + }, + talismanBaseCoefficient: { + speed: 0.75, + duplication: 0.75, + prism: 0, + thrift: 0, + superiorIntellect: 1.5, + infiniteAscent: 0, + antiquities: 0, + horseShoe: 0, + finiteDescent: 0 + }, + minimalResetTier: 'ascension', + isUnlocked: () => { + return Boolean(getAchievementReward('polymathTalisman')) + }, + name: () => i18next.t('runes.talismans.polymath.name'), + description: () => i18next.t('runes.talismans.polymath.description') + }, + mortuus: { + level: 0, + rarity: 0, + fragmentsInvested: { ...noTalismanFragments }, + baseMult: new Decimal(100), + maxLevel: 180, + costs: regularCostProgression, + levelCapIncrease: () => universalTalismanMaxLevelIncreasers(), + effects: (n) => { + const inscriptValues = [1, 1.02, 1.04, 1.06, 1.07, 1.08, 1.09, 1.10, 1.11, 1.125, 1.15] + const prismOOMBonus = (n >= 6) ? 12 : 0 + return { + antBonus: inscriptValues[n] ?? 1, + prismOOMBonus: prismOOMBonus + } + }, + inscriptionDesc: (n) => { + const inscriptValues = [1, 1.02, 1.04, 1.06, 1.07, 1.08, 1.09, 1.10, 1.11, 1.125, 1.15] + return i18next.t('runes.talismans.mortuus.inscription', { + val: formatAsPercentIncrease(inscriptValues[n] ?? 1, 0) + }) + }, + signatureDesc: (n) => { + const prismOOMBonus = (n >= 6) ? 12 : 0 + return i18next.t('runes.talismans.mortuus.signature', { + val: format(prismOOMBonus, 0, true) + }) + }, + talismanBaseCoefficient: { + speed: 0.6, + duplication: 0.6, + prism: 0.6, + thrift: 0.6, + superiorIntellect: 0.6, + infiniteAscent: 0, + antiquities: 0, + horseShoe: 0, + finiteDescent: 0 + }, + minimalResetTier: 'ascension', + isUnlocked: () => { + return player.antUpgrades[11]! > 0 || player.ascensionCount > 0 + }, + name: () => i18next.t('runes.talismans.mortuus.name'), + description: () => i18next.t('runes.talismans.mortuus.description') + }, + plastic: { + level: 0, + rarity: 0, + fragmentsInvested: { ...noTalismanFragments }, + baseMult: new Decimal(1e5), + maxLevel: 180, + costs: regularCostProgression, + levelCapIncrease: () => { + return universalTalismanMaxLevelIncreasers() + plasticTalismanMaxLevelIncreasers() + }, + effects: (n) => { + const inscriptValues = [1, 1.005, 1.01, 1.015, 1.02, 1.025, 1.03, 1.04, 1.045, 1.05, 1.0666] + return { + quarkBonus: inscriptValues[n] ?? 1 + } + }, + inscriptionDesc: (n) => { + const inscriptValues = [1, 1.005, 1.01, 1.015, 1.02, 1.025, 1.03, 1.04, 1.045, 1.05, 1.0666] + return i18next.t('runes.talismans.plastic.inscription', { + val: formatAsPercentIncrease(inscriptValues[n] ?? 1, 2) + }) + }, + signatureDesc: () => i18next.t('runes.talismans.plastic.signature'), + talismanBaseCoefficient: { + speed: 0.75, + duplication: 0, + prism: 1.5, + thrift: 0, + superiorIntellect: 0.75, + infiniteAscent: 0.005, + antiquities: 0, + horseShoe: 0, + finiteDescent: 0 + }, + minimalResetTier: 'ascension', + isUnlocked: () => { + return isShopTalismanUnlocked() + }, + name: () => i18next.t('runes.talismans.plastic.name'), + description: () => i18next.t('runes.talismans.plastic.description') + }, + wowSquare: { + level: 0, + rarity: 0, + fragmentsInvested: { ...noTalismanFragments }, + maxLevel: 210, + baseMult: new Decimal(1e5), + costs: (baseMult: Decimal, level: number) => exponentialCostProgression(baseMult, level, 2), + levelCapIncrease: () => universalTalismanMaxLevelIncreasers(), + effects: (n) => { + const inscriptValues = [1, 1.025, 1.05, 1.075, 1.1, 1.125, 1.15, 1.2, 1.225, 1.25, 1.30] + return { + evenDimBonus: inscriptValues[n] ?? 1, + oddDimBonus: n >= 6 ? 1.20 : 1 + } + }, + inscriptionDesc: (n) => { + const inscriptValues = [1, 1.025, 1.05, 1.075, 1.1, 1.125, 1.15, 1.2, 1.225, 1.25, 1.30] + return i18next.t('runes.talismans.wowSquare.inscription', { + val: formatAsPercentIncrease(inscriptValues[n] ?? 1, 0) + }) + }, + signatureDesc: () => i18next.t('runes.talismans.wowSquare.signature'), + talismanBaseCoefficient: { + speed: 0, + duplication: 1, + prism: 1, + thrift: 0, + superiorIntellect: 1, + infiniteAscent: 0, + antiquities: 0, + horseShoe: 0, + finiteDescent: 0 + }, + minimalResetTier: 'ascension', + isUnlocked: () => { + return player.ascensionCount >= 100 + }, + name: () => i18next.t('runes.talismans.wowSquare.name'), + description: () => i18next.t('runes.talismans.wowSquare.description') + }, + achievement: { + level: 0, + rarity: 0, + fragmentsInvested: { ...noTalismanFragments }, + baseMult: new Decimal(1e30), + maxLevel: 40, + costs: (baseMult: Decimal, level: number) => exponentialCostProgression(baseMult, level, 10), + levelCapIncrease: () => getLevelMilestone('achievementTalismanEnhancement'), + effects: (n) => { + const inscriptValues = [0, 0.001, 0.002, 0.003, 0.004, 0.006, 0.008, .01, .015, .02, .03] + const signatureValue = (n >= 6) ? -0.02 : 0 + return { + positiveSalvageMult: inscriptValues[n] ?? 1, + negativeSalvageMult: signatureValue + } + }, + inscriptionDesc: (n) => { + const inscriptValues = [1, 1, 1, 1, 1, 1, 1, 1.01, 1.015, 1.02, 1.03] + return i18next.t('runes.talismans.achievement.inscription', { + val: formatAsPercentIncrease(inscriptValues[n] ?? 1, 1) + }) + }, + signatureDesc: (n) => { + const negativeSalvageMult = (n >= 6) ? 0.98 : 1 + return i18next.t('runes.talismans.achievement.signature', { + val: formatAsPercentIncrease(negativeSalvageMult, 0) + }) + }, + talismanBaseCoefficient: { + speed: 1.4, + duplication: 1.4, + prism: 1.4, + thrift: 1.4, + superiorIntellect: 1.4, + infiniteAscent: 0.01, + antiquities: 0, + horseShoe: 0, + finiteDescent: 0 + }, + minimalResetTier: 'singularity', + isUnlocked: () => { + return getLevelMilestone('achievementTalismanUnlock') === 1 + }, + name: () => i18next.t('runes.talismans.achievement.name'), + description: () => + i18next.t('runes.talismans.achievement.description', { + num: achievementPoints + }) + }, + cookieGrandma: { + level: 0, + rarity: 0, + fragmentsInvested: { ...noTalismanFragments }, + baseMult: new Decimal('1e1000'), + maxLevel: 6, + costs: (baseMult: Decimal, level: number) => exponentialCostProgression(baseMult, level, 1e8), + levelCapIncrease: () => 54, + effects: (n) => { + const inscriptValues = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.10] + const cookiesSix = n >= 6 + return { + freeCorruptionLevel: inscriptValues[n] ?? 0, + cookieSix: cookiesSix + } + }, + inscriptionDesc: (n) => { + const inscriptValues = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1] + return i18next.t('runes.talismans.cookieGrandma.inscription', { + val: format(inscriptValues[n] ?? 0, 3) + }) + }, + signatureDesc: () => i18next.t('runes.talismans.cookieGrandma.signature'), + talismanBaseCoefficient: { + speed: 1, + duplication: 1, + prism: 1, + thrift: 1, + superiorIntellect: 1, + infiniteAscent: 0.01, + antiquities: 0, + horseShoe: 0, + finiteDescent: 0 + }, + minimalResetTier: 'never', + isUnlocked: () => { + return player.cubeUpgrades[80] > 0 + }, + name: () => i18next.t('runes.talismans.cookieGrandma.name'), + description: () => i18next.t('runes.talismans.cookieGrandma.description') + }, + horseShoe: { + level: 0, + rarity: 0, + fragmentsInvested: { ...noTalismanFragments }, + baseMult: new Decimal('1e1200'), + maxLevel: 12, + costs: (baseMult: Decimal, level: number) => exponentialCostProgression(baseMult, level, 1e5), + levelCapIncrease: () => 88, + effects: (n) => { + const inscriptValues = [0, 0.001, 0.002, 0.003, 0.004, 0.005, 0.007, 0.01, 0.012, 0.015, 0.02] + const signatureValue = (n >= 6) ? 40 : 0 + return { + luckPercentage: inscriptValues[n] ?? 0, + redLuck: signatureValue + } + }, + inscriptionDesc: (n) => { + const inscriptValues = [0, 0.001, 0.002, 0.003, 0.004, 0.005, 0.007, 0.01, 0.012, 0.015, 0.02] + return i18next.t('runes.talismans.horseShoe.inscription', { + val: format(100 * (inscriptValues[n] ?? 0), 2) + }) + }, + signatureDesc: (n) => { + const signatureValue = (n >= 6) ? 40 : 0 + return i18next.t('runes.talismans.horseShoe.signature', { + val: format(signatureValue, 0, true) + }) + }, + talismanBaseCoefficient: { + speed: 1.2, + duplication: 1.2, + prism: 1.2, + thrift: 1.2, + superiorIntellect: 1.2, + infiniteAscent: 0, + antiquities: 0, + horseShoe: 0.01, + finiteDescent: 0 + }, + minimalResetTier: 'never', + isUnlocked: () => { + return Boolean(player.singularityChallenges.taxmanLastStand.rewards.talismanUnlock) + }, + name: () => i18next.t('runes.talismans.horseShoe.name'), + description: () => i18next.t('runes.talismans.horseShoe.description') } - if (i === 50) { - x = 'Fifty' +} + +export const maxTalismansRarityAP = 50 * Object.keys(talismans).length + +export const getTalismanCostTNL = (t: TalismanKeys) => { + return talismans[t].costs(talismans[t].baseMult, talismans[t].level) +} + +export const getTalismanLevelCap = (t: TalismanKeys) => { + return talismans[t].maxLevel + talismans[t].levelCapIncrease() +} + +export const setTalismanRarity = (t: TalismanKeys) => { + if (!talismans[t].isUnlocked()) { + talismans[t].rarity = 0 + return } - if (i === 100) { - x = 'Hundred' + + // Since the actual level cap depends on + // level cap increasers, this can be greater than 1 + const levelRatio = talismans[t].level / talismans[t].maxLevel + + let extraRarity = 0 + if (levelRatio >= 1) { + if (levelRatio >= 2) { + extraRarity += 1 + } + if (levelRatio >= 4) { + extraRarity += 1 + } + if (levelRatio >= 8) { + extraRarity += 1 + } } - DOMCacheGetOrSet(`talisman${x}`).style.backgroundColor = 'green' + talismans[t].rarity = 1 + Math.min(6, Math.floor(6 * levelRatio)) + extraRarity } -export const updateTalismanInventory = () => { - DOMCacheGetOrSet('talismanShardInventory').textContent = format(player.talismanShards) - DOMCacheGetOrSet('commonFragmentInventory').textContent = format(player.commonFragments) - DOMCacheGetOrSet('uncommonFragmentInventory').textContent = format(player.uncommonFragments) - DOMCacheGetOrSet('rareFragmentInventory').textContent = format(player.rareFragments) - DOMCacheGetOrSet('epicFragmentInventory').textContent = format(player.epicFragments) - DOMCacheGetOrSet('legendaryFragmentInventory').textContent = format(player.legendaryFragments) - DOMCacheGetOrSet('mythicalFragmentInventory').textContent = format(player.mythicalFragments) +export const levelsUntilRarityIncrease = (t: TalismanKeys) => { + const level = talismans[t].level + const maxLevel = talismans[t].maxLevel + if (level >= maxLevel) { + // This ignores rarity above 7... + // And just tries to level to cap + return getTalismanLevelCap(t) - level + } else { + const currentRarity = talismans[t].rarity + const levelReq = Math.ceil(maxLevel * currentRarity / 6) + return levelReq - level + } } -export const buyAllTalismanResources = () => { - const talismanItemNames = [ - 'shard', - 'commonFragment', - 'uncommonFragment', - 'rareFragment', - 'epicFragment', - 'legendaryFragment', - 'mythicalFragment' - ] as const - for (let index = talismanItemNames.length - 1; index >= 0; index--) { - buyTalismanResources(talismanItemNames[index]) +export const affordableNextLevel = ( + t: TalismanKeys, + budget: Record, + level: number +): boolean => { + const costs = talismans[t].costs(talismans[t].baseMult, level) + + for (const item in costs) { + if (costs[item as TalismanCraftItems].gt(budget[item as TalismanCraftItems])) { + return false + } } + + return true } -export const buyTalismanResources = ( - type: keyof typeof talismanResourceCosts, - percentage = player.buyTalismanShardPercent -) => { - const talismanResourcesData = getTalismanResourceInfo(type, percentage) +export const updateTalismanLevelAndSpentFromInvested = (t: TalismanKeys): void => { + let level = 0 - if (talismanResourcesData.canBuy) { - if (type === 'shard') { - player.talismanShards += talismanResourcesData.buyAmount - } else { - player[`${type}s` as const] += talismanResourcesData.buyAmount + const budget = { ...player.talismans[t] } + talismans[t].fragmentsInvested = { ...player.talismans[t] } + const trueLevelCap = getTalismanLevelCap(t) + + let nextCost = talismans[t].costs(talismans[t].baseMult, level) + + let canAffordNextLevel = affordableNextLevel(t, budget, level) + + while (canAffordNextLevel) { + for (const item in nextCost) { + budget[item as TalismanCraftItems] = budget[item as TalismanCraftItems].sub(nextCost[item as TalismanCraftItems]) } - if (type === 'mythicalFragment' && player.mythicalFragments >= 1e25 && player.achievements[239] < 1) { - achievementaward(239) + level += 1 + nextCost = talismans[t].costs(talismans[t].baseMult, level) + + if (level >= trueLevelCap) { + break } - player.researchPoints -= talismanResourcesData.obtainiumCost - player.runeshards -= talismanResourcesData.offeringCost + canAffordNextLevel = affordableNextLevel(t, budget, level) + } - // When dealing with high values, calculations can be very slightly off due to floating point precision - // and result in buying slightly (usually 1) more than the player can actually afford. - // This results in negative obtainium or offerings with further calcs somehow resulting in NaN/undefined. - // Instead of trying to work around floating point limits, just make sure nothing breaks as a result. - // The calculation being done overall is similar to the following calculation: - // 2.9992198253874083e47 - (Math.floor(2.9992198253874083e47 / 1e20) * 1e20) - // which, for most values, returns 0, but values like this example will return a negative number instead. - if (player.researchPoints < 0) { - player.researchPoints = 0 + talismans[t].level = level + setTalismanRarity(t) +} + +export const updateTalismanRarities = (): void => { + for (const t of Object.keys(talismans) as TalismanKeys[]) { + if (talismans[t].isUnlocked()) { + setTalismanRarity(t) } - if (player.runeshards < 0) { - player.runeshards = 0 + } +} + +export const getPlayerTalismanBudget = (): Record => { + return { + shard: player.talismanShards, + commonFragment: player.commonFragments, + uncommonFragment: player.uncommonFragments, + rareFragment: player.rareFragments, + epicFragment: player.epicFragments, + legendaryFragment: player.legendaryFragments, + mythicalFragment: player.mythicalFragments + } +} + +export const buyTalismanLevel = (t: TalismanKeys, fromMultibuy = false): void => { + if (!talismans[t].isUnlocked()) { + return + } + + if (talismans[t].level >= getTalismanLevelCap(t)) { + return + } + + const costs = talismans[t].costs(talismans[t].baseMult, talismans[t].level) + const budget = getPlayerTalismanBudget() + const canAffordNextLevel = affordableNextLevel(t, budget, talismans[t].level) + + if (canAffordNextLevel) { + player.talismanShards = player.talismanShards.sub(costs.shard) + player.commonFragments = player.commonFragments.sub(costs.commonFragment) + player.uncommonFragments = player.uncommonFragments.sub(costs.uncommonFragment) + player.rareFragments = player.rareFragments.sub(costs.rareFragment) + player.epicFragments = player.epicFragments.sub(costs.epicFragment) + player.legendaryFragments = player.legendaryFragments.sub(costs.legendaryFragment) + player.mythicalFragments = player.mythicalFragments.sub(costs.mythicalFragment) + + for (const item in costs) { + talismans[t].fragmentsInvested[item as TalismanCraftItems] = talismans[t] + .fragmentsInvested[item as TalismanCraftItems].add(costs[item as TalismanCraftItems]) + } + + talismans[t].level += 1 + } + + if (!fromMultibuy) { + updateTalismanCostHTML(t) + updateTalismanInventory() + setTalismanRarity(t) + } +} + +export const buyTalismanLevelToRarityIncrease = (t: TalismanKeys, auto = false): void => { + const levelsToBuy = levelsUntilRarityIncrease(t) + if (levelsToBuy > 0) { + for (let i = 0; i < levelsToBuy; i++) { + const budget = getPlayerTalismanBudget() + if (!affordableNextLevel(t, budget, talismans[t].level)) { + break + } + buyTalismanLevel(t, true) } } - updateTalismanCostDisplay(type, percentage) + + if (!auto) { + updateTalismanCostHTML(t) + } updateTalismanInventory() + setTalismanRarity(t) } -export const showTalismanEffect = (i: number) => { - DOMCacheGetOrSet('talismanlevelup').style.display = 'none' - DOMCacheGetOrSet('talismanEffect').style.display = 'block' - DOMCacheGetOrSet('talismanrespec').style.display = 'none' - const a = DOMCacheGetOrSet('talismanSummary') - const b = DOMCacheGetOrSet('talismanBonus') - const c = DOMCacheGetOrSet('talismanRune1Effect') - const d = DOMCacheGetOrSet('talismanRune2Effect') - const e = DOMCacheGetOrSet('talismanRune3Effect') - const f = DOMCacheGetOrSet('talismanRune4Effect') - const g = DOMCacheGetOrSet('talismanRune5Effect') - const h = DOMCacheGetOrSet('talismanMythicEffect') - - let talismanKey = '' - let effectValue = '' - - switch (i) { - case 0: - talismanKey = 'exemption' - effectValue = format(10 * (player.talismanRarity[0] - 1)) - break - case 1: - talismanKey = 'chronos' - effectValue = format(10 * (player.talismanRarity[1] - 1)) - break - case 2: - talismanKey = 'midas' - effectValue = format(10 * (player.talismanRarity[2] - 1)) - break - case 3: - talismanKey = 'metaphysics' - effectValue = format(0.02 * (player.talismanRarity[3] - 1), 2) - break - case 4: - talismanKey = 'polymath' - effectValue = format(1 * (player.talismanRarity[4] - 1)) - break - case 5: - talismanKey = 'mortuus' - effectValue = format(2 * (player.talismanRarity[5] - 1)) - break - case 6: - talismanKey = 'plastic' +export const buyTalismanLevelToMax = (t: TalismanKeys): void => { + const trueLevelCap = getTalismanLevelCap(t) + const levelsToBuy = trueLevelCap - talismans[t].level + if (levelsToBuy > 0) { + for (let i = 0; i < levelsToBuy; i++) { + const budget = getPlayerTalismanBudget() + if (!affordableNextLevel(t, budget, talismans[t].level)) { + break + } + buyTalismanLevel(t, true) + } + } - break + updateTalismanCostHTML(t) + updateTalismanInventory() + setTalismanRarity(t) +} + +export const getRuneBonusFromIndividualTalisman = (t: TalismanKeys, rune: RuneKeys): number => { + const talisman = talismans[t] + if (!talisman.isUnlocked()) { + return 0 } - const runeEffectName = `talisman${i + 1}Effect` as - | 'talisman1Effect' - | 'talisman2Effect' - | 'talisman3Effect' - | 'talisman4Effect' - | 'talisman5Effect' - | 'talisman6Effect' - | 'talisman7Effect' + let metaPhysicsMult = 1 + if (t === 'metaphysics') { + metaPhysicsMult *= (talisman.effects(talisman.rarity) as TalismanTypeMap['metaphysics']).talismanEffect + metaPhysicsMult *= (talisman.effects(talisman.rarity) as TalismanTypeMap['metaphysics']).extraTalismanEffect + } - a.textContent = i18next.t(`runes.talismans.summaries.${talismanKey}`) - b.textContent = i18next.t(`runes.talismans.effects.${talismanKey}`, { x: effectValue }) - c.textContent = i18next.t('runes.talismans.bonusRuneLevels.speed', { x: format(G[runeEffectName][1], 2, true) }) - d.textContent = i18next.t('runes.talismans.bonusRuneLevels.duplication', { x: format(G[runeEffectName][2], 2, true) }) - e.textContent = i18next.t('runes.talismans.bonusRuneLevels.prism', { x: format(G[runeEffectName][3], 2, true) }) - f.textContent = i18next.t('runes.talismans.bonusRuneLevels.thrift', { x: format(G[runeEffectName][4], 2, true) }) - g.textContent = i18next.t('runes.talismans.bonusRuneLevels.SI', { x: format(G[runeEffectName][5], 2, true) }) - h.textContent = i18next.t(`runes.talismans.mythicEffects.${talismanKey}`) + return talisman.talismanBaseCoefficient[rune] * metaPhysicsMult * talisman.level * rarityValues[talisman.rarity] +} - if (player.talismanRarity[i] !== 6) { - h.textContent = i18next.t('runes.talismans.maxEnhance') +export const getRuneBonusFromAllTalismans = (rune: RuneKeys): number => { + const specialMultiplier = allTalismanRuneBonusStatsSum() + let totalBonus = 0 + for (const t of Object.keys(talismans) as TalismanKeys[]) { + totalBonus += getRuneBonusFromIndividualTalisman(t, rune) } + + return totalBonus * specialMultiplier } -export const showTalismanPrices = (i: number) => { - DOMCacheGetOrSet('talismanEffect').style.display = 'none' - DOMCacheGetOrSet('talismanlevelup').style.display = 'block' - DOMCacheGetOrSet('talismanrespec').style.display = 'none' - const a = DOMCacheGetOrSet('talismanShardCost') - const b = DOMCacheGetOrSet('talismanCommonFragmentCost') - const c = DOMCacheGetOrSet('talismanUncommonFragmentCost') - const d = DOMCacheGetOrSet('talismanRareFragmentCost') - const e = DOMCacheGetOrSet('talismanEpicFragmentCost') - const f = DOMCacheGetOrSet('talismanLegendaryFragmentCost') - const g = DOMCacheGetOrSet('talismanMythicalFragmentCost') +export const getTalismanEffects = ( + t: T +): TalismanTypeMap[T] => { + return talismans[t].effects(talismans[t].rarity) +} - DOMCacheGetOrSet('talismanLevelUpSummary').textContent = i18next.t('runes.resourcesToLevelup') - DOMCacheGetOrSet('talismanLevelUpSummary').style.color = 'silver' +export const talismanRarityInfo = (t: TalismanKeys): void => { + DOMCacheGetOrSet('rarityInfoTexts').style.display = 'block' - let m = G.talismanLevelCostMultiplier[i] - if (player.talismanLevels[i] >= 120) { - m *= (player.talismanLevels[i] - 90) / 30 + const title = `${ + i18next.t('runes.talismans.rarityInfo.title', { + talismanName: String(i18next.t(`runes.talismans.${t}.name`)) + }) } - if (player.talismanLevels[i] >= 150) { - m *= (player.talismanLevels[i] - 120) / 30 + ` + + const levelCap = talismans[t].maxLevel + const rarity = talismans[t].rarity + + const common = `${ + i18next.t('runes.talismans.rarityInfo.common') + } + ${rarity === 1 ? '▶ ' : ''} + ${i18next.t('runes.talismans.rarityInfo.default')} + + ` + + const uncommon = `${ + i18next.t('runes.talismans.rarityInfo.uncommon') + } + ${rarity === 2 ? '▶ ' : ''} + ${ + i18next.t('runes.talismans.rarityInfo.levelReq', { + level: format(Math.ceil(levelCap / 6), 0, true) + }) } - if (player.talismanLevels[i] >= 180) { - m *= (player.talismanLevels[i] - 170) / 10 + + ` + + const rare = `${ + i18next.t('runes.talismans.rarityInfo.rare') + } + ${rarity === 3 ? '▶ ' : ''} + ${ + i18next.t('runes.talismans.rarityInfo.levelReq', { + level: format(Math.ceil(levelCap / 3), 0, true) + }) } - a.textContent = format(m * Math.max(0, Math.floor(1 + 1 / 8 * Math.pow(player.talismanLevels[i], 3)))) - b.textContent = format(m * Math.max(0, Math.floor(1 + 1 / 32 * Math.pow(player.talismanLevels[i] - 30, 3)))) - c.textContent = format(m * Math.max(0, Math.floor(1 + 1 / 384 * Math.pow(player.talismanLevels[i] - 60, 3)))) - d.textContent = format(m * Math.max(0, Math.floor(1 + 1 / 500 * Math.pow(player.talismanLevels[i] - 90, 3)))) - e.textContent = format(m * Math.max(0, Math.floor(1 + 1 / 375 * Math.pow(player.talismanLevels[i] - 120, 3)))) - f.textContent = format(m * Math.max(0, Math.floor(1 + 1 / 192 * Math.pow(player.talismanLevels[i] - 150, 3)))) - g.textContent = format(m * Math.max(0, Math.floor(1 + 1 / 1280 * Math.pow(player.talismanLevels[i] - 150, 3)))) -} + + ` -export const showEnhanceTalismanPrices = (i: number) => { - DOMCacheGetOrSet('talismanEffect').style.display = 'none' - DOMCacheGetOrSet('talismanlevelup').style.display = 'block' - DOMCacheGetOrSet('talismanrespec').style.display = 'none' - const a = DOMCacheGetOrSet('talismanShardCost') - const b = DOMCacheGetOrSet('talismanCommonFragmentCost') - const c = DOMCacheGetOrSet('talismanUncommonFragmentCost') - const d = DOMCacheGetOrSet('talismanRareFragmentCost') - const e = DOMCacheGetOrSet('talismanEpicFragmentCost') - const f = DOMCacheGetOrSet('talismanLegendaryFragmentCost') - const g = DOMCacheGetOrSet('talismanMythicalFragmentCost') + const epic = `${i18next.t('runes.talismans.rarityInfo.epic')} + ${rarity === 4 ? '▶ ' : ''} + ${ + i18next.t('runes.talismans.rarityInfo.levelReq', { + level: format(Math.ceil(levelCap / 2), 0, true) + }) + } + + ` - DOMCacheGetOrSet('talismanLevelUpSummary').textContent = i18next.t('runes.resourcesToEnhance') - DOMCacheGetOrSet('talismanLevelUpSummary').style.color = 'gold' - - const array = [ - G.commonTalismanEnhanceCost, - G.uncommonTalismanEnchanceCost, - G.rareTalismanEnchanceCost, - G.epicTalismanEnhanceCost, - G.legendaryTalismanEnchanceCost, - G.mythicalTalismanEnchanceCost - ] - const index = player.talismanRarity[i] - const costArray = array[index - 1] - const m = G.talismanLevelCostMultiplier[i] - a.textContent = format(m * costArray[1]) - b.textContent = format(m * costArray[2]) - c.textContent = format(m * costArray[3]) - d.textContent = format(m * costArray[4]) - e.textContent = format(m * costArray[5]) - f.textContent = format(m * costArray[6]) - g.textContent = format(m * costArray[7]) -} - -export const showRespecInformation = (i: number) => { - G.talismanRespec = i - DOMCacheGetOrSet('talismanEffect').style.display = 'none' - DOMCacheGetOrSet('talismanlevelup').style.display = 'none' - DOMCacheGetOrSet('talismanrespec').style.display = 'block' - - const runeName = ['speed', 'duplication', 'prism', 'thrift', 'SI'] - const runeModifier = ['positive', 'positive', 'positive', 'positive', 'positive'] - if (i <= 6) { - for (let k = 1; k <= 5; k++) { - G.mirrorTalismanStats[k] = player[`talisman${num[i]}` as const][k] - } - DOMCacheGetOrSet('confirmTalismanRespec').textContent = i18next.t('runes.talismans.respecConfirm') + const legendary = `${ + i18next.t('runes.talismans.rarityInfo.legendary') + } + ${rarity === 5 ? '▶ ' : ''} + ${ + i18next.t('runes.talismans.rarityInfo.levelReq', { + level: format(Math.ceil(levelCap * 2 / 3), 0, true) + }) } - if (i === 7) { - for (let k = 1; k <= 5; k++) { - G.mirrorTalismanStats[k] = 1 - } - DOMCacheGetOrSet('confirmTalismanRespec').textContent = i18next.t('runes.talismans.respecConfirmAll') - } - for (let j = 1; j <= 5; j++) { - const el = DOMCacheGetOrSet(`talismanRespecButton${j}`) - if (G.mirrorTalismanStats[j] === 1) { - el.style.border = '2px solid limegreen' - runeModifier[j - 1] = 'positive' - } else if (G.mirrorTalismanStats[j] === -1) { - el.style.border = '2px solid crimson' - runeModifier[j - 1] = 'negative' - } - el.textContent = i18next.t(`runes.talismans.modifiers.${runeModifier[j - 1]}`, { - name: i18next.t(`runes.names.${runeName[j - 1]}`) + + ` + + const mythic = `${ + i18next.t('runes.talismans.rarityInfo.mythic') + } + ${rarity === 6 ? '▶ ' : ''} + ${ + i18next.t('runes.talismans.rarityInfo.levelReq', { + level: format(Math.ceil(levelCap * 5 / 6), 0, true) }) } + + ` - DOMCacheGetOrSet('confirmTalismanRespec').style.display = 'none' -} + const extraordinary = `${ + i18next.t('runes.talismans.rarityInfo.extraordinary') + } + ${rarity === 7 ? '▶ ' : ''} + ${ + i18next.t('runes.talismans.rarityInfo.levelReq', { + level: format(levelCap, 0, true) + }) + } + + ` -export const changeTalismanModifier = (i: number) => { - const runeName = [null, 'speed', 'duplication', 'prism', 'thrift', 'SI'] - const el = DOMCacheGetOrSet(`talismanRespecButton${i}`) - if (G.mirrorTalismanStats[i] === 1) { - G.mirrorTalismanStats[i] = -1 - el.textContent = i18next.t('runes.talismans.modifiers.negative', { name: i18next.t(`runes.names.${runeName[i]}`) }) - el.style.border = '2px solid crimson' - } else { - G.mirrorTalismanStats[i] = 1 - el.textContent = i18next.t('runes.talismans.modifiers.positive', { name: i18next.t(`runes.names.${runeName[i]}`) }) - el.style.border = '2px solid limegreen' + const godlike = `${ + i18next.t('runes.talismans.rarityInfo.godlike') + } + ${rarity === 8 ? '▶ ' : ''} + ${ + i18next.t('runes.talismans.rarityInfo.levelReq', { + level: format(2 * levelCap, 0, true) + }) } + + ` - const checkSum = G.mirrorTalismanStats.reduce((a, b) => a! + b!, 0) + const perfect = `${ + i18next.t('runes.talismans.rarityInfo.perfect') + } + ${rarity === 9 ? '▶ ' : ''} + ${ + i18next.t('runes.talismans.rarityInfo.levelReq', { + level: format(4 * levelCap, 0, true) + }) + } + + ` - if (checkSum === 1) { - DOMCacheGetOrSet('confirmTalismanRespec').style.display = 'block' - } else { - DOMCacheGetOrSet('confirmTalismanRespec').style.display = 'none' + const immaculate = `${ + i18next.t('runes.talismans.rarityInfo.immaculate') + } + ${rarity === 10 ? '▶ ' : ''} + ${ + i18next.t('runes.talismans.rarityInfo.levelReq', { + level: format(8 * levelCap, 0, true) + }) } + + ` + + DOMCacheGetOrSet('rarityInfoMultiline').innerHTML = `${title}
${common}
${uncommon} +
${rare}
${epic}
${legendary} +
${mythic}
${extraordinary}
${godlike} +
${perfect}
${immaculate}` } -export const respecTalismanConfirm = (i: number) => { - if (player.runeshards >= 100000 && i < 7) { - for (let j = 1; j <= 5; j++) { - player[`talisman${num[i]}` as const][j] = G.mirrorTalismanStats[j] - } - player.runeshards -= 100000 - DOMCacheGetOrSet('confirmTalismanRespec').style.display = 'none' - DOMCacheGetOrSet('talismanrespec').style.display = 'none' - DOMCacheGetOrSet('talismanEffect').style.display = 'block' - showTalismanEffect(i) - } else if (player.runeshards >= 400000 && i === 7) { - player.runeshards -= 400000 - for (let j = 0; j < 7; j++) { - for (let k = 1; k <= 5; k++) { - player[`talisman${num[j]}` as const][k] = G.mirrorTalismanStats[k] - } +export const talismanToStringHTML = (t: TalismanKeys): void => { + assert(G.currentTab === Tabs.Runes, 'Talisman updateRewardHTML called outside of Runes tab') + const talisman = talismans[t] + DOMCacheGetOrSet('talismanLevelUpCost').style.display = 'none' + DOMCacheGetOrSet('talismanEffect').style.display = 'block' + + DOMCacheGetOrSet('talismanTitle').innerHTML = `${talisman.name()} - ${ + i18next.t(`runes.talismans.rarity.${talisman.rarity}`) + }` + DOMCacheGetOrSet('talismanDescription').innerHTML = talisman.description() + + const inscriptionHTML = DOMCacheGetOrSet('talismanInscriptionBonus') + const signatureHTML = DOMCacheGetOrSet('talismanSignatureBonus') + + const noResetHTML = DOMCacheGetOrSet('talismanNoResetText') + + inscriptionHTML.innerHTML = talisman.inscriptionDesc(talisman.rarity) + signatureHTML.style.display = talisman.rarity >= 6 ? 'block' : 'none' + signatureHTML.innerHTML = talisman.signatureDesc(talisman.rarity) + + const runeLevelMult = allTalismanRuneBonusStatsSum() + for (const rune of Object.keys(talisman.talismanBaseCoefficient) as RuneKeys[]) { + const levels = getRuneBonusFromIndividualTalisman(t, rune) * runeLevelMult + const capitalizedRune = rune.charAt(0).toUpperCase() + rune.slice(1) + if (levels > 0 && runes[rune].isUnlocked()) { + DOMCacheGetOrSet(`talisman${capitalizedRune}Effect`).style.display = 'block' + DOMCacheGetOrSet(`talisman${capitalizedRune}Effect`).innerHTML = i18next.t( + `runes.talismans.bonusRuneLevels.${rune}`, + { + x: format(levels, 3, true) + } + ) + } else { + DOMCacheGetOrSet(`talisman${capitalizedRune}Effect`).style.display = 'none' } - DOMCacheGetOrSet('confirmTalismanRespec').style.display = 'none' } - calculateRuneLevels() + if (talisman.minimalResetTier === 'never') { + noResetHTML.style.display = 'block' + noResetHTML.innerHTML = i18next.t('runes.talismans.doesNotReset') + } else { + noResetHTML.style.display = 'none' + } } -export const respecTalismanCancel = (i: number) => { - DOMCacheGetOrSet('talismanrespec').style.display = 'none' - if (i < 7) { - DOMCacheGetOrSet('talismanEffect').style.display = 'block' - showTalismanEffect(i) - } +export const updateTalismanCostHTML = (t: TalismanKeys) => { + assert(G.currentTab === Tabs.Runes, 'Talisman updateCostHTML called outside of Runes tab') + DOMCacheGetOrSet('talismanEffect').style.display = 'none' + DOMCacheGetOrSet('talismanLevelUpCost').style.display = 'block' + const a = DOMCacheGetOrSet('talismanShardCost') + const b = DOMCacheGetOrSet('talismanCommonFragmentCost') + const c = DOMCacheGetOrSet('talismanUncommonFragmentCost') + const d = DOMCacheGetOrSet('talismanRareFragmentCost') + const e = DOMCacheGetOrSet('talismanEpicFragmentCost') + const f = DOMCacheGetOrSet('talismanLegendaryFragmentCost') + const g = DOMCacheGetOrSet('talismanMythicalFragmentCost') + + DOMCacheGetOrSet('talismanLevelUpSummary').textContent = i18next.t('runes.resourcesToLevelup') + DOMCacheGetOrSet('talismanLevelUpSummary').style.color = 'silver' + + const nextCost = getTalismanCostTNL(t) + a.textContent = format(nextCost.shard, 0, false) + b.textContent = format(nextCost.commonFragment, 0, false) + c.textContent = format(nextCost.uncommonFragment, 0, false) + d.textContent = format(nextCost.rareFragment, 0, false) + e.textContent = format(nextCost.epicFragment, 0, false) + f.textContent = format(nextCost.legendaryFragment, 0, false) + g.textContent = format(nextCost.mythicalFragment, 0, false) } -export const updateTalismanAppearance = (i: number) => { - const el = DOMCacheGetOrSet(`talisman${i + 1}`) - const la = DOMCacheGetOrSet(`talisman${i + 1}level`) +export const updateTalismanDisplay = (t: TalismanKeys) => { + assert(G.currentTab === Tabs.Runes, 'Talisman updateTalismanDisplay called outside of Runes tab') + const talisman = talismans[t] + const el = DOMCacheGetOrSet(`${t}TalismanIconWrapper`) + const la = DOMCacheGetOrSet(`${t}TalismanLevel`) + const ti = DOMCacheGetOrSet(`${t}Talisman`) + + el.classList.remove('rainbowBorder') + el.classList.add('talismanIcon') + la.classList.remove('rainbowText') - const rarity = player.talismanRarity[i] + la.textContent = `${format(talisman.level)}/${format(getTalismanLevelCap(t))}` + const rarity = talisman.rarity if (rarity === 1) { - el.style.border = '4px solid white' + ti.style.border = '3px solid white' la.style.color = 'white' } if (rarity === 2) { - el.style.border = '4px solid limegreen' + ti.style.border = '3px solid limegreen' la.style.color = 'limegreen' } if (rarity === 3) { - el.style.border = '4px solid lightblue' + ti.style.border = '3px solid lightblue' la.style.color = 'lightblue' } if (rarity === 4) { - el.style.border = '4px solid plum' + ti.style.border = '3px solid plum' la.style.color = 'plum' } if (rarity === 5) { - el.style.border = '4px solid orange' - la.style.color = 'orange' + ti.style.border = '3px solid orange' + la.style.color = 'darkorange' } if (rarity === 6) { - el.style.border = '4px solid crimson' + ti.style.border = '3px solid crimson' la.style.color = 'var(--crimson-text-color)' } -} - -// Attempt to buy a fixed number of levels (number varies based on -// ascension). Returns true if any levels were bought, false otherwise. -export const buyTalismanLevels = (i: number, auto = false): boolean => { - let max = 1 - if (player.ascensionCount > 0) { - max = 30 + if (rarity === 7) { + ti.style.border = '3px solid cyan' + la.style.color = 'cyan' } - if (player.highestSingularityCount > 0) { - max = 180 + if (rarity === 8) { + ti.style.border = '3px solid red' + la.style.color = 'red' } - let hasPurchased = false - for (let j = 1; j <= max; j++) { - let checkSum = 0 - let priceMult = G.talismanLevelCostMultiplier[i] - if (player.talismanLevels[i] >= 120) { - priceMult *= (player.talismanLevels[i] - 90) / 30 - } - if (player.talismanLevels[i] >= 150) { - priceMult *= (player.talismanLevels[i] - 120) / 30 - } - if (player.talismanLevels[i] >= 180) { - priceMult *= (player.talismanLevels[i] - 170) / 10 - } + if (rarity === 9) { + ti.style.border = '3px solid gold' + la.style.color = 'gold' + } + if (rarity === 10) { + ti.style.border = '' + el.classList.remove('talismanIcon') + el.classList.add('rainbowBorder') + la.style.color = '' + la.classList.add('talismanLevel') + la.classList.add('rainbowText') + } +} - if (player.talismanLevels[i] < calculateMaxTalismanLevel(i)) { - if ( - player.talismanShards >= priceMult * Math.max(0, Math.floor(1 + 1 / 8 * Math.pow(player.talismanLevels[i], 3))) - ) { - checkSum++ - } - if ( - player.commonFragments - >= priceMult * Math.max(0, Math.floor(1 + 1 / 32 * Math.pow(player.talismanLevels[i] - 30, 3))) - ) { - checkSum++ - } - if ( - player.uncommonFragments - >= priceMult * Math.max(0, Math.floor(1 + 1 / 384 * Math.pow(player.talismanLevels[i] - 60, 3))) - ) { - checkSum++ - } - if ( - player.rareFragments - >= priceMult * Math.max(0, Math.floor(1 + 1 / 500 * Math.pow(player.talismanLevels[i] - 90, 3))) - ) { - checkSum++ - } - if ( - player.epicFragments - >= priceMult * Math.max(0, Math.floor(1 + 1 / 375 * Math.pow(player.talismanLevels[i] - 120, 3))) - ) { - checkSum++ - } - if ( - player.legendaryFragments - >= priceMult * Math.max(0, Math.floor(1 + 1 / 192 * Math.pow(player.talismanLevels[i] - 150, 3))) - ) { - checkSum++ - } - if ( - player.mythicalFragments - >= priceMult * Math.max(0, Math.floor(1 + 1 / 1280 * Math.pow(player.talismanLevels[i] - 150, 3))) - ) { - checkSum++ - } +export const resetSingleTalisman = (t: TalismanKeys) => { + talismans[t].level = 0 + talismans[t].fragmentsInvested = { ...noTalismanFragments } + player.talismans[t] = { ...noTalismanFragments } + setTalismanRarity(t) +} + +export const resetTalismanData = (tier: keyof typeof resetTiers) => { + for (const t of Object.keys(talismans) as TalismanKeys[]) { + if (resetTiers[tier] >= resetTiers[talismans[t].minimalResetTier]) { + resetSingleTalisman(t) } - if (checkSum === 7) { - player.talismanShards -= priceMult * Math.max(0, Math.floor(1 + 1 / 8 * Math.pow(player.talismanLevels[i], 3))) - player.commonFragments -= priceMult - * Math.max(0, Math.floor(1 + 1 / 32 * Math.pow(player.talismanLevels[i] - 30, 3))) - player.uncommonFragments -= priceMult - * Math.max(0, Math.floor(1 + 1 / 384 * Math.pow(player.talismanLevels[i] - 60, 3))) - player.rareFragments -= priceMult - * Math.max(0, Math.floor(1 + 1 / 500 * Math.pow(player.talismanLevels[i] - 90, 3))) - player.epicFragments -= priceMult - * Math.max(0, Math.floor(1 + 1 / 375 * Math.pow(player.talismanLevels[i] - 120, 3))) - player.legendaryFragments -= priceMult - * Math.max(0, Math.floor(1 + 1 / 192 * Math.pow(player.talismanLevels[i] - 150, 3))) - player.mythicalFragments -= priceMult - * Math.max(0, Math.floor(1 + 1 / 1280 * Math.pow(player.talismanLevels[i] - 150, 3))) - player.talismanLevels[i] += 1 - hasPurchased = true - } else { - break + // Hard reset + if (tier === 'never') { + talismans[t].rarity = 0 } } - if (!auto && hasPurchased) { - showTalismanPrices(i) - // When adding game state recalculations, update the talisman autobuyer in tack() as well - updateTalismanInventory() - calculateRuneLevels() - } - - return hasPurchased -} - -export const buyTalismanEnhance = (i: number, auto = false): boolean => { - let checkSum = 0 - if (player.talismanRarity[i] < 6) { - const priceMult = G.talismanLevelCostMultiplier[i] - const array = [ - G.commonTalismanEnhanceCost, - G.uncommonTalismanEnchanceCost, - G.rareTalismanEnchanceCost, - G.epicTalismanEnhanceCost, - G.legendaryTalismanEnchanceCost, - G.mythicalTalismanEnchanceCost - ] - const index = player.talismanRarity[i] - 1 - const costArray = array[index] - if (player.commonFragments >= priceMult * costArray[2]) { - checkSum++ - } - if (player.uncommonFragments >= priceMult * costArray[3]) { - checkSum++ - } - if (player.rareFragments >= priceMult * costArray[4]) { - checkSum++ + player.talismanShards = new Decimal(0) + player.commonFragments = new Decimal(0) + player.uncommonFragments = new Decimal(0) + player.rareFragments = new Decimal(0) + player.epicFragments = new Decimal(0) + player.legendaryFragments = new Decimal(0) + player.mythicalFragments = new Decimal(0) +} + +export const sumOfTalismanRarities = (): number => { + let sum = 0 + for (const t of Object.keys(talismans) as TalismanKeys[]) { + sum += talismans[t].rarity + } + return sum +} + +/** + * Updates legacy talisman data (player.talismanLevels[i]) to the + * new talismans object. Takes level and creates talisman.level and + * talismans.fragmentsInvested. Should only be used in PlayerUpdateVarSchema.ts + */ +export const updateResourcePredefinedLevel = (level: number, t: TalismanKeys): void => { + talismans[t].level = Math.min(level, getTalismanLevelCap(t)) + talismans[t].fragmentsInvested = { ...noTalismanFragments } + setTalismanRarity(t) + + for (let n = 0; n < talismans[t].level; n++) { + const nextCost = talismans[t].costs(talismans[t].baseMult, n) + for (const item in nextCost) { + talismans[t].fragmentsInvested[item as TalismanCraftItems] = talismans[t] + .fragmentsInvested[item as TalismanCraftItems].add(nextCost[item as TalismanCraftItems]) } - if (player.epicFragments >= priceMult * costArray[5]) { - checkSum++ + } +} + +export const updateAllTalismanHTML = () => { + for (const t of Object.keys(talismans) as TalismanKeys[]) { + updateTalismanDisplay(t) + } +} + +export const generateTalismansHTML = () => { + const alreadyGenerated = document.getElementsByClassName('talismanContainer').length > 0 + + if (alreadyGenerated) { + return + } else { + const talismansContainer = DOMCacheGetOrSet('talismansContainerDiv') + + for (const key of Object.keys(talismans) as TalismanKeys[]) { + const talismansDiv = document.createElement('div') + talismansDiv.className = 'talismanContainer' + talismansDiv.id = `${key}TalismanContainer` + + const talismansName = document.createElement('span') + talismansName.className = 'talismanName' + talismansName.setAttribute('i18n', `runes.talismans.names.${key}`) + + talismansDiv.appendChild(talismansName) + + const talismanIconDivWrapper = document.createElement('div') + talismanIconDivWrapper.id = `${key}TalismanIconWrapper` + talismanIconDivWrapper.className = 'talismanIcon' + + const talismansIcon = document.createElement('img') + talismansIcon.id = `${key}Talisman` + talismansIcon.alt = `${key} Talisman` + talismansIcon.src = `Pictures/Talismans/${key.charAt(0).toUpperCase() + key.slice(1)}.png` + talismansIcon.loading = 'lazy' + + talismanIconDivWrapper.appendChild(talismansIcon) + + talismansDiv.appendChild(talismanIconDivWrapper) + + const talismansLevel = document.createElement('span') + talismansLevel.className = 'talismanLevel' + talismansLevel.id = `${key}TalismanLevel` + talismansLevel.textContent = 'Level 0/30' + + talismansDiv.appendChild(talismansLevel) + + const talismansLevelUpButton = document.createElement('button') + talismansLevelUpButton.className = 'talismanBtn' + talismansLevelUpButton.id = `level${key}Once` + talismansLevelUpButton.style.color = 'silver' + talismansLevelUpButton.style.border = '2px solid white' + talismansLevelUpButton.setAttribute('i18n', 'runes.talismans.fortify') + talismansLevelUpButton.textContent = i18next.t('runes.talismans.fortify') + + talismansDiv.appendChild(talismansLevelUpButton) + + const talismansLevelUpButton2 = document.createElement('button') + talismansLevelUpButton2.className = 'talismanBtn' + talismansLevelUpButton2.id = `level${key}ToRarityIncrease` + talismansLevelUpButton2.style.color = 'gold' + talismansLevelUpButton2.style.border = '2px solid orangered' + talismansLevelUpButton2.setAttribute('i18n', 'runes.talismans.enhance') + talismansLevelUpButton2.textContent = i18next.t('runes.talismans.enhance') + + talismansDiv.appendChild(talismansLevelUpButton2) + + const talismansLevelUpButton3 = document.createElement('button') + talismansLevelUpButton3.className = 'talismanBtn' + talismansLevelUpButton3.id = `level${key}ToMax` + talismansLevelUpButton3.style.color = 'plum' + talismansLevelUpButton3.style.border = '2px solid white' + talismansLevelUpButton3.setAttribute('i18n', 'runes.talismans.respec') + talismansLevelUpButton3.textContent = i18next.t('runes.talismans.respec') + + talismansDiv.appendChild(talismansLevelUpButton3) + + talismansContainer.appendChild(talismansDiv) } - if (player.legendaryFragments >= priceMult * costArray[6]) { - checkSum++ + } +} + +const getTalismanResourceInfo = ( + type: keyof typeof talismanResourceCosts, + obtainiumBudget: Decimal, + offeringBudget: Decimal +) => { + const obtainiumCost = talismanResourceCosts[type].obtainium + const offeringCost = talismanResourceCosts[type].offerings + + const maxBuyObtainium = Decimal.max( + 1, + Decimal.floor(obtainiumBudget.div(obtainiumCost)) + ) + const maxBuyOffering = Decimal.max( + 1, + Decimal.floor(offeringBudget.div(offeringCost)) + ) + + const amountToBuy = Decimal.max( + 1, + Decimal.floor(Decimal.min(maxBuyObtainium, maxBuyOffering)) + ) + const canBuy = player.obtainium.gte(obtainiumCost) && player.offerings.gte(offeringCost) + return { + canBuy, // Boolean, if false will not buy any fragments + buyAmount: amountToBuy, // Integer, will buy as specified above. + obtainiumCost: amountToBuy.times(obtainiumCost), // Integer, cost in obtainium to buy (buyAmount) resource + offeringCost: amountToBuy.times(offeringCost) // Integer, cost in offerings to buy (buyAmount) resource + } +} + +export const updateTalismanCostDisplay = ( + type: keyof typeof talismanResourceCosts | null, + obtainiumBudget: Decimal, + offeringBudget: Decimal +) => { + const el = DOMCacheGetOrSet('talismanFragmentCost') + if (type) { + const talismanCostInfo = getTalismanResourceInfo(type, obtainiumBudget, offeringBudget) + const talismanShardName = i18next.t(`runes.talismans.shards.${type}`) + + el.textContent = i18next.t('runes.talismans.costToBuy', { + name: talismanShardName, + buyAmount: format(talismanCostInfo.buyAmount), + obtainium: format(talismanCostInfo.obtainiumCost), + offerings: format(talismanCostInfo.offeringCost) + }) + } else { + // Buy All + el.textContent = i18next.t('runes.talismans.clickBuyEveryType') + } +} + +export const toggleTalismanBuy = (i = player.buyTalismanShardPercent) => { + DOMCacheGetOrSet('talismanTen').style.backgroundColor = '' + DOMCacheGetOrSet('talismanTwentyFive').style.backgroundColor = '' + DOMCacheGetOrSet('talismanFifty').style.backgroundColor = '' + DOMCacheGetOrSet('talismanHundred').style.backgroundColor = '' + player.buyTalismanShardPercent = i + let x = 'Ten' + if (i === 25) { + x = 'TwentyFive' + } + if (i === 50) { + x = 'Fifty' + } + if (i === 100) { + x = 'Hundred' + } + + DOMCacheGetOrSet(`talisman${x}`).style.backgroundColor = 'green' +} + +export const updateTalismanInventory = () => { + DOMCacheGetOrSet('talismanShardInventory').textContent = format(player.talismanShards) + DOMCacheGetOrSet('commonFragmentInventory').textContent = format(player.commonFragments) + DOMCacheGetOrSet('uncommonFragmentInventory').textContent = format(player.uncommonFragments) + DOMCacheGetOrSet('rareFragmentInventory').textContent = format(player.rareFragments) + DOMCacheGetOrSet('epicFragmentInventory').textContent = format(player.epicFragments) + DOMCacheGetOrSet('legendaryFragmentInventory').textContent = format(player.legendaryFragments) + DOMCacheGetOrSet('mythicalFragmentInventory').textContent = format(player.mythicalFragments) +} + +export const buyAllTalismanResources = () => { + const talismanItemNames = Object.keys(talismanResourceCosts) as TalismanCraftItems[] + const numElms = talismanItemNames.length + // Get the budget for each of the resources in the talismanResourceCosts object + const obtainiumBudget = player.obtainium.times(player.buyTalismanShardPercent / 100).div(numElms) + const offeringBudget = player.offerings.times(player.buyTalismanShardPercent / 100).div(numElms) + for (let index = talismanItemNames.length - 1; index >= 0; index--) { + buyTalismanResources(talismanItemNames[index], obtainiumBudget, offeringBudget) + } +} + +export const buyTalismanResources = ( + type: keyof typeof talismanResourceCosts, + obtainiumBudget: Decimal, + offeringBudget: Decimal +) => { + const talismanResourcesData = getTalismanResourceInfo(type, obtainiumBudget, offeringBudget) + + if (talismanResourcesData.canBuy) { + if (type === 'shard') { + player.talismanShards = player.talismanShards.add(talismanResourcesData.buyAmount) + } else { + player[`${type}s` as const] = player[`${type}s` as const].add(talismanResourcesData.buyAmount) } - if (player.mythicalFragments >= priceMult * costArray[7]) { - checkSum++ + if (type === 'mythicalFragment' && player.mythicalFragments.gte(1e25)) { + awardUngroupedAchievement('seeingRed') } - if (checkSum === 6) { - player.commonFragments -= priceMult * costArray[2] - player.uncommonFragments -= priceMult * costArray[3] - player.rareFragments -= priceMult * costArray[4] - player.epicFragments -= priceMult * costArray[5] - player.legendaryFragments -= priceMult * costArray[6] - player.mythicalFragments -= priceMult * costArray[7] - player.talismanRarity[i] += 1 - - // Appearance always needs updating if bought - updateTalismanAppearance(i) - if (!auto) { - showEnhanceTalismanPrices(i) - // When adding game state recalculations, update the talisman autobuyer in tack() as well - updateTalismanInventory() - calculateRuneLevels() - } + player.obtainium = player.obtainium.sub(talismanResourcesData.obtainiumCost) + player.offerings = player.offerings.sub(talismanResourcesData.offeringCost) - return true + // When dealing with high values, calculations can be very slightly off due to floating point precision + // and result in buying slightly (usually 1) more than the player can actually afford. + // This results in negative obtainium or offerings with further calcs somehow resulting in NaN/undefined. + // Instead of trying to work around floating point limits, just make sure nothing breaks as a result. + // The calculation being done overall is similar to the following calculation: + // 2.9992198253874083e47 - (Math.floor(2.9992198253874083e47 / 1e20) * 1e20) + // which, for most values, returns 0, but values like this example will return a negative number instead. + if (player.obtainium.lt(0)) { + player.obtainium = new Decimal(0) + } + if (player.offerings.lt(0)) { + player.offerings = new Decimal(0) } } - return false + updateTalismanCostDisplay(type, obtainiumBudget, offeringBudget) + updateTalismanInventory() } diff --git a/src/Tax.ts b/src/Tax.ts index cc84935d2..3f7a4304c 100644 --- a/src/Tax.ts +++ b/src/Tax.ts @@ -3,8 +3,11 @@ import { sumContents } from './Utility' import { Globals as G } from './Variables' import Decimal from 'break_infinity.js' -import { achievementaward } from './Achievements' +import { awardUngroupedAchievement, getAchievementReward } from './Achievements' import { CalcECC } from './Challenges' +import { calculateTaxPlatonicBlessing } from './PlatonicCubes' +import { getRuneEffects } from './Runes' +import { getTalismanEffects } from './Talismans' export const calculatetax = () => { let exp = 1 @@ -79,30 +82,19 @@ export const calculatetax = () => { exponent *= exp exponent *= 1 - 1 / 20 * player.researches[51] - 1 / 40 * player.researches[52] - 1 / 80 * player.researches[53] - 1 / 160 * player.researches[54] - 1 / 320 * player.researches[55] - exponent *= 1 - - 0.05 / 1800 * (player.achievements[45] + player.achievements[46] + 2 * player.achievements[47]) - * Math.min(player.prestigecounter, 1800) + exponent *= +getAchievementReward('taxReduction') exponent *= Math.pow(0.965, CalcECC('reincarnation', player.challengecompletions[6])) - exponent *= 0.001 + .999 * (Math.pow(6, -(G.rune2level * G.effectiveLevelMult) / 1000)) - exponent *= 0.01 + .99 * (Math.pow(4, Math.min(0, (400 - G.rune4level) / 1100))) - exponent *= 1 - 0.04 * player.achievements[82] - 0.04 * player.achievements[89] - 0.04 * player.achievements[96] - - 0.04 * player.achievements[103] - 0.04 * player.achievements[110] - 0.0566 * player.achievements[117] - - 0.0566 * player.achievements[124] - 0.0566 * player.achievements[131] - exponent *= Math.pow( - 0.9925, - player.achievements[118] - * (player.challengecompletions[6] + player.challengecompletions[7] + player.challengecompletions[8] - + player.challengecompletions[9] + player.challengecompletions[10]) - ) + exponent *= getRuneEffects('duplication').taxReduction + exponent *= getRuneEffects('thrift').taxReduction exponent *= 0.005 + 0.995 * Math.pow(0.99, player.antUpgrades[2]! + G.bonusant3) exponent *= 1 / Math.pow( 1 + Decimal.log(player.ascendShards.add(1), 10), - 1 + .2 / 60 * player.challengecompletions[10] * player.upgrades[125] + 0.1 * player.platonicUpgrades[5] - + 0.2 * player.platonicUpgrades[10] + (G.platonicBonusMultiplier[5] - 1) + 1 + 1 / 300 * player.challengecompletions[10] * player.upgrades[125] + 0.1 * player.platonicUpgrades[5] + + 0.2 * player.platonicUpgrades[10] + calculateTaxPlatonicBlessing() ) - exponent *= 1 - 0.10 * (player.talismanRarity[1 - 1] - 1) - exponent *= Math.pow(0.98, 3 / 5 * Math.log(1 + player.rareFragments) / Math.log(10) * player.researches[159]) + exponent *= 1 + getTalismanEffects('exemption').taxReduction + exponent *= Math.pow(0.98, 3 / 5 * Decimal.log(player.rareFragments.add(1), 10) * player.researches[159]) exponent *= Math.pow(0.966, CalcECC('ascension', player.challengecompletions[13])) exponent *= 1 - 0.666 * player.researches[200] / 100000 exponent *= 1 - 0.666 * player.cubeUpgrades[50] / 100000 @@ -111,6 +103,20 @@ export const calculatetax = () => { if (player.upgrades[121] > 0) { exponent *= 0.5 } + + if (player.highestSingularityCount >= 281) { + exponent *= 0.5 + } + + if (player.singularityChallenges.taxmanLastStand.enabled) { + if (player.unlocks.ascensions) { + exponent *= 4 + } + if (player.highestchallengecompletions[14] > 0) { + exponent *= 5 + } + } + // Cap the calculation overflow bug || httpsnet if (exponent < 1e-300) { exponent = 1e-300 @@ -118,12 +124,12 @@ export const calculatetax = () => { G.maxexponent = Math.floor(275 / (Decimal.log(1.01, 10) * exponent)) - 1 const a2 = Math.min(G.maxexponent, Math.floor(Decimal.log(G.produceTotal.add(1), 10))) - if (player.currentChallenge.ascension === 13 && G.maxexponent <= 99999 && player.achievements[249] < 1) { + if (player.currentChallenge.ascension === 13 && G.maxexponent <= 99999) { // i don't think it makes sense to give the achievement as soon as the challenge is opened // as soon as the challenge is opened you don't have enough tax reducers to have max exponent above 100000 // so for the achievement description to make sense i think it should require at least 1 challenge completion || Dorijanko if (c13effcompletions >= 1) { - achievementaward(249) + awardUngroupedAchievement('overtaxed') } } diff --git a/src/Tesseracts.ts b/src/Tesseracts.ts index 9cdf75fd0..71265b8de 100644 --- a/src/Tesseracts.ts +++ b/src/Tesseracts.ts @@ -1,30 +1,117 @@ +import Decimal from 'break_infinity.js' +import { + calculateAcceleratorHypercubeBlessing, + calculateAntELOHypercubeBlessing, + calculateAntSacrificeHypercubeBlessing, + calculateAntSpeedHypercubeBlessing, + calculateGlobalSpeedHypercubeBlessing, + calculateMultiplierHypercubeBlessing, + calculateObtainiumHypercubeBlessing, + calculateOfferingHypercubeBlessing, + calculateRuneEffectivenessHypercubeBlessing, + calculateSalvageHypercubeBlessing +} from './Hypercubes' import { player } from './Synergism' -import { Globals as G } from './Variables' - -export const calculateTesseractBlessings = () => { - // The visual updates are handled in visualUpdateCubes() - const tesseractArray = [ - player.tesseractBlessings.accelerator, - player.tesseractBlessings.multiplier, - player.tesseractBlessings.offering, - player.tesseractBlessings.runeExp, - player.tesseractBlessings.obtainium, - player.tesseractBlessings.antSpeed, - player.tesseractBlessings.antSacrifice, - player.tesseractBlessings.antELO, - player.tesseractBlessings.talismanBonus, - player.tesseractBlessings.globalSpeed - ] - - for (let i = 0; i < 10; i++) { - let power = 1 - let mult = 1 - if (tesseractArray[i] >= 1000 && i !== 5) { - power = G.giftDRPower[i] - mult *= Math.pow(1000, 1 - G.giftDRPower[i]) - } - - G.tesseractBonusMultiplier[i + 1] = 1 - + mult * G.giftbase[i] * Math.pow(tesseractArray[i], power) * G.hypercubeBonusMultiplier[i + 1]! + +export const calculateAcceleratorTesseractBlessing = () => { + const DR = 1 / 6 + const effectPerBlessing = calculateAcceleratorHypercubeBlessing() / 1000 + const limit = 1000 + + if (player.tesseractBlessings.accelerator < limit) { + return 1 + effectPerBlessing * player.tesseractBlessings.accelerator + } else { + const limitMult = Math.pow(limit, 1 - DR) + return effectPerBlessing * limitMult * Math.pow(player.tesseractBlessings.accelerator, DR) + } +} + +export const calculateMultiplierTesseractBlessing = () => { + const DR = 1 / 6 + const effectPerBlessing = calculateMultiplierHypercubeBlessing() / 1000 + const limit = 1000 + if (player.tesseractBlessings.multiplier < limit) { + return 1 + effectPerBlessing * player.tesseractBlessings.multiplier + } else { + const limitMult = Math.pow(limit, 1 - DR) + return effectPerBlessing * limitMult * Math.pow(player.tesseractBlessings.multiplier, DR) + } +} + +export const calculateOfferingTesseractBlessing = () => { + const DR = 1 / 3 + const effectPerBlessing = calculateOfferingHypercubeBlessing() / 1000 + const limit = 1000 + if (player.tesseractBlessings.offering < limit) { + return 1 + effectPerBlessing * player.tesseractBlessings.offering + } else { + const limitMult = Math.pow(limit, 1 - DR) + return effectPerBlessing * limitMult * Math.pow(player.tesseractBlessings.offering, DR) + } +} + +export const calculateSalvageTesseractBlessing = () => { + const factor = Math.pow(Math.log10(player.tesseractBlessings.runeExp + 1), 1.25) + const cap = 1 / 2 * calculateSalvageHypercubeBlessing() + return 1 + cap * factor / (20 + factor) +} + +export const calculateObtainiumTesseractBlessing = () => { + const DR = 1 / 3 + const effectPerBlessing = calculateObtainiumHypercubeBlessing() / 1000 + const limit = 1000 + if (player.tesseractBlessings.obtainium < limit) { + return 1 + effectPerBlessing * player.tesseractBlessings.obtainium + } else { + const limitMult = Math.pow(limit, 1 - DR) + return effectPerBlessing * limitMult * Math.pow(player.tesseractBlessings.obtainium, DR) + } +} + +export const calculateAntSpeedTesseractBlessing = () => { + const effectPerBlessing = 1 / 1000 + return new Decimal(1 + effectPerBlessing * player.tesseractBlessings.antSpeed).times( + calculateAntSpeedHypercubeBlessing() + ) +} + +export const calculateAntSacrificeTesseractBlessing = () => { + const DR = 1 / 6 + const effectPerBlessing = calculateAntSacrificeHypercubeBlessing() / 1000 + const limit = 1000 + if (player.tesseractBlessings.antSacrifice < limit) { + return 1 + effectPerBlessing * player.tesseractBlessings.antSacrifice + } else { + const limitMult = Math.pow(limit, 1 - DR) + return effectPerBlessing * limitMult * Math.pow(player.tesseractBlessings.antSacrifice, DR) + } +} + +export const calculateAntELOTesseractBlessing = () => { + const hypercubeMult = calculateAntELOHypercubeBlessing() + return 1 + Math.log10(player.tesseractBlessings.antELO + 1) * hypercubeMult / 100 +} + +export const calculateRuneEffectivenessTesseractBlessing = () => { + const DR = 1 / 32 + const effectPerBlessing = calculateRuneEffectivenessHypercubeBlessing() / 1000 + const limit = 1000 + if (player.tesseractBlessings.talismanBonus < limit) { + return 1 + effectPerBlessing * player.tesseractBlessings.talismanBonus + } else { + const limitMult = Math.pow(limit, 1 - DR) + return effectPerBlessing * limitMult * Math.pow(player.tesseractBlessings.talismanBonus, DR) + } +} + +export const calculateGlobalSpeedTesseractBlessing = () => { + const DR = 1 / 32 + const effectPerBlessing = calculateGlobalSpeedHypercubeBlessing() / 1000 + const limit = 1000 + if (player.tesseractBlessings.globalSpeed < limit) { + return 1 + effectPerBlessing * player.tesseractBlessings.globalSpeed + } else { + const limitMult = Math.pow(limit, 1 - DR) + return effectPerBlessing * limitMult * Math.pow(player.tesseractBlessings.globalSpeed, DR) } } diff --git a/src/Themes.ts b/src/Themes.ts index d351c5432..55a6495c2 100644 --- a/src/Themes.ts +++ b/src/Themes.ts @@ -74,7 +74,6 @@ export const toggleTheme = (initial = false, themeNumber = 1, change = false) => DOMCacheGetOrSet('importFileButton').style.backgroundColor = '' DOMCacheGetOrSet('switchTheme2').style.borderColor = 'darkslategray' - DOMCacheGetOrSet('bonussummation').style.color = 'orangered' // CSS colors, instead of having '', will write out full color, in case someone will move CSS color into HTML DOMCacheGetOrSet('corruptionDescription').style.color = 'darkviolet' DOMCacheGetOrSet('versionnumber').style.color = 'fuchsia' DOMCacheGetOrSet('singularitytab').style.color = 'red' @@ -84,7 +83,6 @@ export const toggleTheme = (initial = false, themeNumber = 1, change = false) => DOMCacheGetOrSet('buildinghotkeys').style.color = 'lightgray' DOMCacheGetOrSet('buildinghotkeys2').style.color = 'lightgray' DOMCacheGetOrSet('antspecies').style.color = 'royalblue' // HTML colors - DOMCacheGetOrSet('achievementcolorcode2').style.color = 'purple' DOMCacheGetOrSet('corruptionTesseracts').style.color = 'darkviolet' DOMCacheGetOrSet('antwelcome').style.color = 'lightslategrey' DOMCacheGetOrSet('confirmationToggleTitle').style.color = 'pink' @@ -96,7 +94,7 @@ export const toggleTheme = (initial = false, themeNumber = 1, change = false) => DOMCacheGetOrSet('cube6Bonus').style.color = 'brown' DOMCacheGetOrSet('tesseract6Bonus').style.color = 'brown' DOMCacheGetOrSet('hypercube6Bonus').style.color = 'brown' - DOMCacheGetOrSet('runeshowpower5').style.color = 'tomato' + DOMCacheGetOrSet('superiorIntellectRunePower').style.color = 'tomato' DOMCacheGetOrSet('hypercubeWelcome').style.color = '#ff004c' // Hypercube colors DOMCacheGetOrSet('hypercubeQuantity').style.color = '#ff004c' DOMCacheGetOrSet('hypercubeBlessingsTotal').style.color = '#ff004c' @@ -200,7 +198,6 @@ export const toggleTheme = (initial = false, themeNumber = 1, change = false) => body.style.setProperty('--lightseagreen-text-color', 'limegreen') body.style.setProperty('--orangered-text-color', '#f74') body.style.setProperty('--gray-text-color', '#a5a5a5') - DOMCacheGetOrSet('achievementcolorcode2').style.color = '#dc7dff' DOMCacheGetOrSet('corruptionDescription').style.color = '#d272ff' DOMCacheGetOrSet('corruptionTesseracts').style.color = '#d272ff' DOMCacheGetOrSet('antwelcome').style.color = '#b1b1b1' @@ -216,7 +213,7 @@ export const toggleTheme = (initial = false, themeNumber = 1, change = false) => DOMCacheGetOrSet('hypercubeWelcome').style.color = '#f58' DOMCacheGetOrSet('hypercubeQuantity').style.color = '#f58' DOMCacheGetOrSet('hypercubeBlessingsTotal').style.color = '#f58' - DOMCacheGetOrSet('runeshowpower5').style.color = '#ff7158' + DOMCacheGetOrSet('superiorIntellectRunePower').style.color = '#ff7158' themeButton.textContent = i18next.t('settings.themes.light') } else if (themeNumber === 5) { // 'Dracula Mode' @@ -267,7 +264,6 @@ export const toggleTheme = (initial = false, themeNumber = 1, change = false) => DOMCacheGetOrSet('corruptionStatsLoadouts').style.borderColor = '#04d481' DOMCacheGetOrSet('heptGrid').style.backgroundColor = '#11111b' DOMCacheGetOrSet('heptGrid').style.borderColor = '#9b7306' - DOMCacheGetOrSet('achievementcolorcode2').style.color = '#ef00e4' // Text colors DOMCacheGetOrSet('corruptionDescription').style.color = '#c205ff' DOMCacheGetOrSet('corruptionTesseracts').style.color = '#c205ff' DOMCacheGetOrSet('antwelcome').style.color = 'darkgrey' @@ -278,7 +274,6 @@ export const toggleTheme = (initial = false, themeNumber = 1, change = false) => DOMCacheGetOrSet('hepteractWelcome').style.color = '#ac47ff' DOMCacheGetOrSet('confirmationdisclaimer').style.color = '#bb68ff' DOMCacheGetOrSet('antspecies').style.color = '#184ff3' - DOMCacheGetOrSet('bonussummation').style.color = '#eb0000' DOMCacheGetOrSet('buildinghotkeys').style.color = '#838383' DOMCacheGetOrSet('buildinghotkeys2').style.color = '#838383' diff --git a/src/Toggles.ts b/src/Toggles.ts index f82e06f8d..d491c24f6 100644 --- a/src/Toggles.ts +++ b/src/Toggles.ts @@ -1,15 +1,16 @@ import i18next from 'i18next' -import { achievementaward } from './Achievements' +import { awardUngroupedAchievement } from './Achievements' import { DOMCacheGetOrSet } from './Cache/DOM' -import { calculateRuneLevels } from './Calculate' import { getChallengeConditions } from './Challenges' import { corruptionDisplay, corruptionLoadoutTableUpdate, type Corruptions } from './Corruptions' import { renderCaptcha } from './Login' -import { autoResearchEnabled } from './Research' +import { initializeMessages } from './Messages' +import { researchOrderByCost, roombaResearchEnabled } from './Research' import { reset, resetrepeat } from './Reset' +import { indexToRune } from './Runes' import { format, player, resetCheck } from './Synergism' import { getActiveSubTab, subTabsInMainTab, Tabs } from './Tabs' -import type { BuildingSubtab, Player } from './types/Synergism' +import type { BuildingSubtab, BuyAmount, Player } from './types/Synergism' import { Alert, Prompt, showCorruptionStatsLoadouts, updateChallengeDisplay } from './UpdateHTML' import { visualUpdateAmbrosia, visualUpdateCubes, visualUpdateOcteracts } from './UpdateVisuals' import { Globals as G } from './Variables' @@ -71,7 +72,10 @@ export const toggleChallenges = (i: number, auto = false) => { } } if ( - (i >= 11 && i <= 15) && (i === 11 ? player.achievements[141] === 1 : player.highestchallengecompletions[i - 1] > 0) + (i >= 11 && i <= 15) + && (i === 11 + ? player.unlocks.ascensions + : player.highestchallengecompletions[i - 1] > 0) && ((!auto && !player.toggles[31]) || player.challengecompletions[10] > 0 || (player.currentChallenge.transcension === 0 && player.currentChallenge.reincarnation === 0 && player.currentChallenge.ascension === 0)) @@ -91,15 +95,15 @@ export const toggleChallenges = (i: number, auto = false) => { if ( player.currentChallenge.transcension !== 0 && player.currentChallenge.reincarnation !== 0 - && player.currentChallenge.ascension !== 0 && player.achievements[238] < 1 + && player.currentChallenge.ascension !== 0 ) { - achievementaward(238) + awardUngroupedAchievement('metaChallenged') } } type ToggleBuy = 'coin' | 'crystal' | 'mythos' | 'particle' | 'offering' | 'tesseract' -export const toggleBuyAmount = (quantity: 1 | 10 | 100 | 1000 | 10000 | 100000, type: ToggleBuy) => { +export const toggleBuyAmount = (quantity: BuyAmount, type: ToggleBuy) => { player[`${type}buyamount` as const] = quantity const a = ['one', 'ten', 'hundred', 'thousand', '10k', '100k'][quantity.toString().length - 1] @@ -274,14 +278,14 @@ export const toggleAutoResearch = () => { el.textContent = i18next.t('researches.automaticOn') } - if (player.autoResearchToggle && autoResearchEnabled() && player.autoResearchMode === 'cheapest') { - player.autoResearch = G.researchOrderByCost[player.roombaResearchIndex] + if (player.autoResearchToggle && roombaResearchEnabled() && player.autoResearchMode === 'cheapest') { + player.autoResearch = researchOrderByCost[player.roombaResearchIndex] } } export const toggleAutoResearchMode = () => { const el = DOMCacheGetOrSet('toggleautoresearchmode') - if (player.autoResearchMode === 'cheapest' || !autoResearchEnabled()) { + if (player.autoResearchMode === 'cheapest' || !roombaResearchEnabled()) { player.autoResearchMode = 'manual' el.textContent = i18next.t('researches.autoModeManual') } else { @@ -290,14 +294,15 @@ export const toggleAutoResearchMode = () => { } DOMCacheGetOrSet(`res${player.autoResearch || 1}`).classList.remove('researchRoomba') - if (player.autoResearchToggle && autoResearchEnabled() && player.autoResearchMode === 'cheapest') { - player.autoResearch = G.researchOrderByCost[player.roombaResearchIndex] + if (player.autoResearchToggle && roombaResearchEnabled() && player.autoResearchMode === 'cheapest') { + player.autoResearch = researchOrderByCost[player.roombaResearchIndex] } } export const toggleAutoSacrifice = (index: number) => { const el = DOMCacheGetOrSet('toggleautosacrifice') - if (index === 0) { + const numIndex = Number(index) + if (numIndex === 0) { if (player.autoSacrificeToggle) { player.autoSacrificeToggle = false el.textContent = i18next.t('runes.blessings.autoRuneOff') @@ -312,16 +317,15 @@ export const toggleAutoSacrifice = (index: number) => { DOMCacheGetOrSet('saveOffToggle').style.color = 'white' } } else if (player.autoSacrificeToggle && player.shopUpgrades.offeringAuto > 0.5) { - if (player.autoSacrifice === index) { + if (player.autoSacrifice === numIndex) { player.autoSacrifice = 0 } else { - player.autoSacrifice = index + player.autoSacrifice = numIndex } } for (let i = 1; i <= 5; i++) { - DOMCacheGetOrSet(`rune${i}`).style.backgroundColor = player.autoSacrifice === i ? 'orange' : '' + DOMCacheGetOrSet(`${indexToRune[i]}Rune`).style.backgroundColor = player.autoSacrifice === i ? 'orange' : '' } - calculateRuneLevels() } export const toggleAutoBuyFragment = () => { @@ -376,6 +380,22 @@ export const toggleBuildingScreen = (input: string) => { // player.subtabNumber = screen[G.buildingSubTab].subtabNumber } +export const toggleAchievementScreen = (indexStr: string) => { + const index = Number(indexStr) + + for (let i = 1; i <= 2; i++) { + const a = DOMCacheGetOrSet(`toggleAchievementSubTab${i}`) + const b = DOMCacheGetOrSet(`achievementContainer${i}`) + if (i === index) { + a.style.border = '2px solid gold' + b.style.display = 'flex' + } else { + a.style.border = '2px solid silver' + b.style.display = 'none' + } + } +} + export const toggleRuneScreen = (indexStr: string) => { const index = Number(indexStr) @@ -390,6 +410,12 @@ export const toggleRuneScreen = (indexStr: string) => { b.style.display = 'none' } } + + if (index === 2) { + DOMCacheGetOrSet('offeringDetails').style.display = 'none' + } else { + DOMCacheGetOrSet('offeringDetails').style.display = 'flex' + } // player.subtabNumber = index - 1 } @@ -570,6 +596,8 @@ export const setActiveSettingScreen = async (subtab: string) => { } } else if (subtab === 'accountSubTab') { renderCaptcha() + } else if (subtab === 'messagesSubTab') { + initializeMessages() } } diff --git a/src/UpdateHTML.ts b/src/UpdateHTML.ts index ac483dd95..8db0ab90f 100644 --- a/src/UpdateHTML.ts +++ b/src/UpdateHTML.ts @@ -1,22 +1,33 @@ import Decimal from 'break_infinity.js' import i18next from 'i18next' -import { achievementaward, totalachievementpoints } from './Achievements' -import { DOMCacheGetOrSet } from './Cache/DOM' import { - CalcCorruptionStuff, - calculateAscensionSpeedMult, - calculateGlobalSpeedMult, - isIARuneUnlocked, - isShopTalismanUnlocked -} from './Calculate' + type AchievementGroups, + achievementLevel, + achievementPoints, + getAchievementReward, + groupedAchievementData, + type ProgressiveAchievements, + progressiveAchievements, + toNextAchievementLevelEXP, + ungroupedAchievementData, + type UngroupedAchievementNames, + updateAllGroupedAchievementProgress, + updateAllProgressiveAchievementProgress, + updateAllUngroupedAchievementProgress +} from './Achievements' +import { DOMCacheGetOrSet } from './Cache/DOM' +import { CalcCorruptionStuff, calculateAscensionSpeedMult, calculateGlobalSpeedMult } from './Calculate' import { getMaxChallenges } from './Challenges' import { revealCorruptions } from './Corruptions' +import { getLevelMilestone } from './Levels' +import { hasUnreadMessages } from './Messages' import { initializeCart } from './purchases/CartTab' -import { autoResearchEnabled } from './Research' -import { displayRuneInformation } from './Runes' -import { updateSingularityPenalties, updateSingularityPerks } from './singularity' +import { isResearchUnlocked, roombaResearchEnabled } from './Research' +import { getRuneEffects, type RuneKeys, runes, updateRuneHTML } from './Runes' +import { getGQUpgradeEffect, updateSingularityPenalties, updateSingularityPerks } from './singularity' import { format, formatTimeShort, /*formatTimeShort*/ player } from './Synergism' import { getActiveSubTab, Tabs } from './Tabs' +import { type TalismanKeys, talismans } from './Talismans' import type { OneToFive, ZeroToFour, ZeroToSeven } from './types/Synergism' import { visualUpdateAchievements, @@ -35,7 +46,7 @@ import { visualUpdateSingularity, visualUpdateUpgrades } from './UpdateVisuals' -import { createDeferredPromise } from './Utility' +import { createDeferredPromise, updateClassList } from './Utility' import { Globals as G } from './Variables' export const revealStuff = () => { @@ -95,23 +106,22 @@ export const revealStuff = () => { document.documentElement.dataset.chal6 = player.achievements[113] === 1 ? 'true' : 'false' document.documentElement.dataset.chal7 = player.achievements[120] === 1 ? 'true' : 'false' - document.documentElement.dataset.chal7x10 = player.achievements[124] === 1 ? 'true' : 'false' const example17 = document.getElementsByClassName('chal8') as HTMLCollectionOf for (let i = 0; i < example17.length; i++) { const parent = example17[i].parentElement! if (parent.classList.contains('offlineStats')) { - example17[i].style.display = player.achievements[127] === 1 ? 'flex' : 'none' - example17[i].setAttribute('aria-disabled', `${player.achievements[127] !== 1}`) + example17[i].style.display = player.unlocks.anthill ? 'flex' : 'none' + example17[i].setAttribute('aria-disabled', `${!player.unlocks.anthill}`) } else { - example17[i].style.display = player.achievements[127] === 1 ? 'block' : 'none' - example17[i].setAttribute('aria-disabled', `${player.achievements[127] !== 1}`) + example17[i].style.display = player.unlocks.anthill ? 'block' : 'none' + example17[i].setAttribute('aria-disabled', `${!player.unlocks.anthill}`) } } - document.documentElement.dataset.chal9 = player.achievements[134] === 1 ? 'true' : 'false' + document.documentElement.dataset.chal9 = player.unlocks.talismans ? 'true' : 'false' document.documentElement.dataset.chal9x1 = player.highestchallengecompletions[9] > 0 ? 'true' : 'false' - document.documentElement.dataset.chal10 = player.achievements[141] === 1 ? 'true' : 'false' + document.documentElement.dataset.chal10 = player.unlocks.ascensions ? 'true' : 'false' const example21 = document.getElementsByClassName('ascendunlock') as HTMLCollectionOf for (let i = 0; i < example21.length; i++) { @@ -125,6 +135,15 @@ export const revealStuff = () => { } } + for (let i = 1; i <= player.researches.length - 1; i++) { + const resKey = `res${i}` + if (isResearchUnlocked(i)) { + updateClassList(resKey, ['researchUnlocked'], ['researchLocked']) + } else { + updateClassList(resKey, ['researchLocked'], ['researchUnlocked']) + } + } + document.documentElement.dataset.chal11 = player.highestchallengecompletions[11] > 0 ? 'true' : 'false' document.documentElement.dataset.chal12 = player.highestchallengecompletions[12] > 0 ? 'true' : 'false' document.documentElement.dataset.chal13 = player.highestchallengecompletions[13] > 0 ? 'true' : 'false' @@ -138,7 +157,7 @@ export const revealStuff = () => { document.documentElement.dataset.cubeUpgrade10 = player.cubeUpgrades[10] > 0 ? 'true' : 'false' document.documentElement.dataset.cubeUpgrade19 = player.cubeUpgrades[19] > 0 ? 'true' : 'false' - document.documentElement.dataset.sacrificeAnts = player.achievements[173] === 1 ? 'true' : 'false' + document.documentElement.dataset.sacrificeAnts = getAchievementReward('antSacrificeUnlock') ? 'true' : 'false' document.documentElement.dataset.hepteracts = // Ability to use and gain hepteracts player.challenge15Exponent >= G.challenge15Rewards.hepteractsUnlocked.requirement ? 'true' : 'false' @@ -149,16 +168,15 @@ export const revealStuff = () => { visualUpdateShop() const hepts = DOMCacheGetOrSet('corruptionHepteracts') - hepts.style.display = (player.achievements[255] > 0) ? 'block' : 'none' + hepts.style.display = 'block' - document.documentElement.dataset.cookies1 = player.singularityUpgrades.cookies.getEffect().bonus ? 'true' : 'false' - document.documentElement.dataset.cookies2 = player.singularityUpgrades.cookies2.getEffect().bonus ? 'true' : 'false' - document.documentElement.dataset.cookies3 = player.singularityUpgrades.cookies3.getEffect().bonus ? 'true' : 'false' - document.documentElement.dataset.cookies4 = player.singularityUpgrades.cookies4.getEffect().bonus ? 'true' : 'false' - document.documentElement.dataset.cookies5 = player.singularityUpgrades.cookies5.getEffect().bonus ? 'true' : 'false' + document.documentElement.dataset.cookies1 = getGQUpgradeEffect('cookies') ? 'true' : 'false' + document.documentElement.dataset.cookies2 = getGQUpgradeEffect('cookies2') ? 'true' : 'false' + document.documentElement.dataset.cookies3 = getGQUpgradeEffect('cookies3') ? 'true' : 'false' + document.documentElement.dataset.cookies4 = getGQUpgradeEffect('cookies4') ? 'true' : 'false' + document.documentElement.dataset.cookies5 = getGQUpgradeEffect('cookies5') ? 'true' : 'false' - document.documentElement.dataset.goldenQuark3Upg = - (player.singularityUpgrades.goldenQuarks3.getEffect().bonus as number) > 0 ? 'true' : 'false' + document.documentElement.dataset.goldenQuark3Upg = getGQUpgradeEffect('goldenQuarks3') > 0 ? 'true' : 'false' if (player.upgrades[89] === 1) { DOMCacheGetOrSet('transcendautotoggle').style.display = 'block' @@ -170,15 +188,54 @@ export const revealStuff = () => { DOMCacheGetOrSet('autotranscend').style.display = 'none' } - if (player.achievements[38] === 1) { // Prestige Diamond Achievement 3 - DOMCacheGetOrSet('rune2area').style.display = 'flex' - DOMCacheGetOrSet('runeshowpower2').style.display = 'block' - } else { - DOMCacheGetOrSet('rune2area').style.display = 'none' - DOMCacheGetOrSet('runeshowpower2').style.display = 'none' + for (const groupedAch of Object.keys(groupedAchievementData) as (Exclude)[]) { + const capitalizedName = groupedAch.charAt(0).toUpperCase() + groupedAch.slice(1) + + DOMCacheGetOrSet(`achievementGroup${capitalizedName}`).style.display = + (groupedAchievementData[groupedAch].displayCondition() || player.highestSingularityCount > 0) + ? 'block' + : 'none' + } + + for (const ungroupedAch of Object.keys(ungroupedAchievementData) as UngroupedAchievementNames[]) { + const capitalizedName = ungroupedAch.charAt(0).toUpperCase() + ungroupedAch.slice(1) + + DOMCacheGetOrSet(`ungroupedAchievement${capitalizedName}`).style.display = + (ungroupedAchievementData[ungroupedAch].displayCondition() || player.highestSingularityCount > 0) + ? 'block' + : 'none' + } + + for (const progAch of Object.keys(progressiveAchievements) as ProgressiveAchievements[]) { + const capitalizedName = progAch.charAt(0).toUpperCase() + progAch.slice(1) + + DOMCacheGetOrSet(`progressiveAchievement${capitalizedName}`).style.display = + (progressiveAchievements[progAch].displayCondition() || player.highestSingularityCount > 0) + ? 'block' + : 'none' + } + + for (const rune of Object.keys(player.runes) as RuneKeys[]) { + if (runes[rune].isUnlocked()) { + DOMCacheGetOrSet(`${rune}RuneContainer`).style.display = 'flex' + DOMCacheGetOrSet(`${rune}RuneLockedContainer`).style.display = 'none' + DOMCacheGetOrSet(`${rune}RunePower`).style.display = 'block' + } else { + DOMCacheGetOrSet(`${rune}RuneContainer`).style.display = 'none' + DOMCacheGetOrSet(`${rune}RuneLockedContainer`).style.display = 'flex' + DOMCacheGetOrSet(`${rune}RunePower`).style.display = 'none' + } } - if (player.achievements[43] === 1) { // Transcend Mythos Achievement 1 + for (const t of Object.keys(talismans) as TalismanKeys[]) { + if (talismans[t].isUnlocked()) { + DOMCacheGetOrSet(`${t}TalismanContainer`).style.display = 'flex' + } else { + DOMCacheGetOrSet(`${t}TalismanContainer`).style.display = 'none' + } + } + + if (getLevelMilestone('autoPrestige') === 1) { // Transcend Mythos Achievement 1 DOMCacheGetOrSet('prestigeautotoggle').style.display = 'block' DOMCacheGetOrSet('prestigeamount').style.display = 'block' DOMCacheGetOrSet('autoprestige').style.display = 'block' @@ -188,35 +245,7 @@ export const revealStuff = () => { DOMCacheGetOrSet('autoprestige').style.display = 'none' } - if (player.achievements[44] === 1) { // Transcend Mythos Achievement 2 - DOMCacheGetOrSet('rune3area').style.display = 'flex' - DOMCacheGetOrSet('runeshowpower3').style.display = 'block' - } else { - DOMCacheGetOrSet('rune3area').style.display = 'none' - DOMCacheGetOrSet('runeshowpower3').style.display = 'none' - } - - if (player.achievements[102] === 1) { // Cost+ Challenge Achievement 4 - DOMCacheGetOrSet('rune4area').style.display = 'flex' - DOMCacheGetOrSet('runeshowpower4').style.display = 'block' - } else { - DOMCacheGetOrSet('rune4area').style.display = 'none' - DOMCacheGetOrSet('runeshowpower4').style.display = 'none' - } - - player.achievements[119] === 1 // Tax+ Challenge Achievement 7 - ? DOMCacheGetOrSet('talisman1area').style.display = 'flex' - : DOMCacheGetOrSet('talisman1area').style.display = 'none' - - player.achievements[126] === 1 // No MA Challenge Achievement 7 - ? DOMCacheGetOrSet('talisman2area').style.display = 'flex' - : DOMCacheGetOrSet('talisman2area').style.display = 'none' - - player.achievements[133] === 1 // Cost++ Challenge Achievement 7 - ? DOMCacheGetOrSet('talisman3area').style.display = 'flex' - : DOMCacheGetOrSet('talisman3area').style.display = 'none' - - if (player.achievements[134] === 1) { // No Runes Challenge Achievement 1 + if (player.unlocks.talismans) { // No Runes Challenge Achievement 1 DOMCacheGetOrSet('toggleRuneSubTab2').style.display = 'block' DOMCacheGetOrSet('toggleRuneSubTab3').style.display = 'block' } else { @@ -224,15 +253,7 @@ export const revealStuff = () => { DOMCacheGetOrSet('toggleRuneSubTab3').style.display = 'none' } - player.achievements[140] === 1 // No Runes Challenge Achievement 7 - ? DOMCacheGetOrSet('talisman4area').style.display = 'flex' - : DOMCacheGetOrSet('talisman4area').style.display = 'none' - - player.achievements[147] === 1 // Sadistic Challenge Achievement 7 - ? DOMCacheGetOrSet('talisman5area').style.display = 'flex' - : DOMCacheGetOrSet('talisman5area').style.display = 'none' - - player.achievements[173] === 1 // Galactic Crumb Achievement 5 + getAchievementReward('antSacrificeUnlock') // Galactic Crumb Achievement 5 ? DOMCacheGetOrSet('sacrificeAnts').style.display = 'block' : DOMCacheGetOrSet('sacrificeAnts').style.display = 'none' @@ -248,14 +269,6 @@ export const revealStuff = () => { ? DOMCacheGetOrSet('reincarnateautomation').style.display = 'block' : DOMCacheGetOrSet('reincarnateautomation').style.display = 'none' - if (player.researches[82] > 0) { // 2x17 Research [SI Rune Unlock] - DOMCacheGetOrSet('rune5area').style.display = 'flex' - DOMCacheGetOrSet('runeshowpower5').style.display = 'block' - } else { - DOMCacheGetOrSet('rune5area').style.display = 'none' - DOMCacheGetOrSet('runeshowpower5').style.display = 'none' - } - if (player.researches[124] > 0) { // 5x24 Research [AutoSac] DOMCacheGetOrSet('antSacrificeButtons').style.display = 'flex' DOMCacheGetOrSet('autoAntSacrifice').style.display = 'block' @@ -272,10 +285,6 @@ export const revealStuff = () => { ? DOMCacheGetOrSet('toggleautofortify').style.display = 'block' : DOMCacheGetOrSet('toggleautofortify').style.display = 'none' - player.researches[135] > 0 // 6x10 Research [Talisman Auto Sac] - ? DOMCacheGetOrSet('toggleautoenhance').style.display = 'block' - : DOMCacheGetOrSet('toggleautoenhance').style.display = 'none' - for (let z = 1; z <= 5; z++) { ;(player.researches[190] > 0) // 8x15 Research [Auto Tesseracts] ? DOMCacheGetOrSet(`tesseractAutoToggle${z}`).style.display = 'block' @@ -293,9 +302,6 @@ export const revealStuff = () => { player.researches[190] > 0 // 8x15 Research [Auto Tesseracts] ? DOMCacheGetOrSet('autotessbuyeramount').style.display = 'block' : DOMCacheGetOrSet('autotessbuyeramount').style.display = 'none' - ;(player.antUpgrades[11]! > 0 || player.ascensionCount > 0) // Ant Talisman Unlock, Mortuus - ? DOMCacheGetOrSet('talisman6area').style.display = 'flex' - : DOMCacheGetOrSet('talisman6area').style.display = 'none' player.shopUpgrades.offeringAuto > 0 // Auto Offering Shop Purchase ? DOMCacheGetOrSet('toggleautosacrifice').style.display = 'block' @@ -310,34 +316,14 @@ export const revealStuff = () => { : DOMCacheGetOrSet('toggleautoresearch').style.display = 'none' DOMCacheGetOrSet('toggleautoresearchmode').style.display = - player.shopUpgrades.obtainiumAuto > 0 && autoResearchEnabled() // Auto Research Shop Purchase Mode + player.shopUpgrades.obtainiumAuto > 0 && roombaResearchEnabled() // Auto Research Shop Purchase Mode ? 'block' : 'none' - isShopTalismanUnlocked() // Plastic Talisman Shop Purchase - ? DOMCacheGetOrSet('talisman7area').style.display = 'flex' - : DOMCacheGetOrSet('talisman7area').style.display = 'none' - player.cubeUpgrades[8] > 0 ? DOMCacheGetOrSet('reincarnateAutoUpgrade').style.display = 'block' : DOMCacheGetOrSet('reincarnateAutoUpgrade').style.display = 'none' - if (isIARuneUnlocked()) { - DOMCacheGetOrSet('rune6area').style.display = 'flex' - DOMCacheGetOrSet('runeshowpower6').style.display = 'block' - } else { - DOMCacheGetOrSet('rune6area').style.display = 'none' - DOMCacheGetOrSet('runeshowpower6').style.display = 'none' - } - - if (player.platonicUpgrades[20] > 0) { - DOMCacheGetOrSet('rune7area').style.display = 'flex' - DOMCacheGetOrSet('runeshowpower7').style.display = 'block' - } else { - DOMCacheGetOrSet('rune7area').style.display = 'none' - DOMCacheGetOrSet('runeshowpower7').style.display = 'none' - } - player.highestSingularityCount > 0 // Save Offerings ? DOMCacheGetOrSet('saveOffToggle').style.display = 'block' : DOMCacheGetOrSet('saveOffToggle').style.display = 'none' @@ -389,11 +375,11 @@ export const revealStuff = () => { for (const item of Array.from(octeractUnlocks)) { // Stuff that you need octeracts to access const parent = item.parentElement! if (parent.classList.contains('offlineStats')) { - item.style.display = player.singularityUpgrades.octeractUnlock.getEffect().bonus ? 'flex' : 'none' - item.setAttribute('aria-disabled', `${!player.singularityUpgrades.octeractUnlock.getEffect().bonus}`) + item.style.display = getGQUpgradeEffect('octeractUnlock') ? 'flex' : 'none' + item.setAttribute('aria-disabled', `${!getGQUpgradeEffect('octeractUnlock')}`) } else { - item.style.display = player.singularityUpgrades.octeractUnlock.getEffect().bonus ? 'block' : 'none' - item.setAttribute('aria-disabled', `${!player.singularityUpgrades.octeractUnlock.getEffect().bonus}`) + item.style.display = getGQUpgradeEffect('octeractUnlock') ? 'block' : 'none' + item.setAttribute('aria-disabled', `${!getGQUpgradeEffect('octeractUnlock')}`) } } @@ -434,17 +420,17 @@ export const revealStuff = () => { ? 'flex' : 'none' - player.runelevels[6] > 0 || player.highestSingularityCount > 0 + runes.antiquities.level > 0 || player.highestSingularityCount > 0 ? (DOMCacheGetOrSet('singularitybtn').style.display = 'block') : (DOMCacheGetOrSet('singularitybtn').style.display = 'none') DOMCacheGetOrSet('ascSingChallengeTimeTakenStats').style.display = player.insideSingularityChallenge ? '' : 'none' DOMCacheGetOrSet('ascensionStats').style.visibility = - (player.achievements[197] > 0 || player.highestSingularityCount > 0) ? 'visible' : 'hidden' + (Boolean(getAchievementReward('statTracker')) || player.highestSingularityCount > 0) ? 'visible' : 'hidden' DOMCacheGetOrSet('ascHyperStats').style.display = player.challengecompletions[13] > 0 ? '' : 'none' DOMCacheGetOrSet('ascPlatonicStats').style.display = player.challengecompletions[14] > 0 ? '' : 'none' - DOMCacheGetOrSet('ascHepteractStats').style.display = player.achievements[255] > 0 ? '' : 'none' + DOMCacheGetOrSet('ascHepteractStats').style.display = G.challenge15Rewards.hepteractsUnlocked.value >= 1 ? '' : 'none' // I'll clean this up later. Note to 2019 Platonic: Fuck you // note to 2019 and 2020 Platonic, you're welcome @@ -461,12 +447,12 @@ export const revealStuff = () => { toggle6: player.upgrades[86] === 1, // Autobuyer - Coin Buildings - Accelerator toggle7: player.upgrades[87] === 1, // Autobuyer - Coin Buildings - Multiplier toggle8: player.upgrades[88] === 1, // Autobuyer - Coin Buildings - Accelerator Boost - toggle10: player.achievements[78] === 1, // Autobuyer - Diamond Buildings - Tier 1 (Refineries) - toggle11: player.achievements[85] === 1, // Autobuyer - Diamond Buildings - Tier 2 (Coal Plants) - toggle12: player.achievements[92] === 1, // Autobuyer - Diamond Buildings - Tier 3 (Coal Rigs) - toggle13: player.achievements[99] === 1, // Autobuyer - Diamond Buildings - Tier 4 (Pickaxes) - toggle14: player.achievements[106] === 1, // Autobuyer - Diamond Buildings - Tier 5 (Pandora's Boxes) - toggle15: player.achievements[43] === 1, // Feature - Diamond Buildings - Auto Prestige + toggle10: getLevelMilestone('tier1CrystalAutobuy') === 1, // Autobuyer - Diamond Buildings - Tier 1 (Refineries) + toggle11: getLevelMilestone('tier2CrystalAutobuy') === 1, // Autobuyer - Diamond Buildings - Tier 2 (Coal Plants) + toggle12: getLevelMilestone('tier3CrystalAutobuy') === 1, // Autobuyer - Diamond Buildings - Tier 3 (Coal Rigs) + toggle13: getLevelMilestone('tier4CrystalAutobuy') === 1, // Autobuyer - Diamond Buildings - Tier 4 (Pickaxes) + toggle14: getLevelMilestone('tier5CrystalAutobuy') === 1, // Autobuyer - Diamond Buildings - Tier 5 (Pandora's Boxes) + toggle15: getLevelMilestone('autoPrestige') === 1, // Feature - Diamond Buildings - Auto Prestige toggle16: player.upgrades[94] === 1, // Autobuyer - Mythos Buildings - Tier 1 (Augments) toggle17: player.upgrades[95] === 1, // Autobuyer - Mythos Buildings - Tier 2 (Enchantments) toggle18: player.upgrades[96] === 1, // Autobuyer - Mythos Buildings - Tier 3 (Wizards) @@ -488,7 +474,7 @@ export const revealStuff = () => { toggle29: player.transcendCount > 0.5 || player.reincarnationCount > 0.5, // Settings - Confirmations - Transcension toggle30: player.reincarnationCount > 0.5, // Settings - Confirmations - Reincarnation toggle31: player.ascensionCount > 0, // Settings - Confirmations - Ascension and Asc. Challenge - toggle32: player.achievements[173] > 0, // Settings - Confirmations - Ant Sacrifice + toggle32: Boolean(getAchievementReward('antSacrificeUnlock')), // Settings - Confirmations - Ant Sacrifice toggle33: player.highestSingularityCount > 0 && player.ascensionCount > 0, // Settings - Confirmations - Singularity toggle34: player.unlocks.coinfour, // Achievements - Notifications toggle35: player.challenge15Exponent >= G.challenge15Rewards.hepteractsUnlocked.requirement @@ -513,6 +499,9 @@ export const revealStuff = () => { el.style.display = automationUnlocks[key] ? 'block' : 'none' }) + // Messages subtab visibility - only show when there are unread messages + DOMCacheGetOrSet('switchSettingSubTab10').style.display = hasUnreadMessages() ? 'block' : 'none' + revealCorruptions() } @@ -572,28 +561,28 @@ export const hideStuff = () => { DOMCacheGetOrSet('statistics').style.display = 'block' DOMCacheGetOrSet('achievementstab').style.backgroundColor = 'white' DOMCacheGetOrSet('achievementstab').style.color = 'black' - DOMCacheGetOrSet('achievementprogress').textContent = i18next.t('achievements.totalPoints', { - x: format(player.achievementPoints), - y: format(totalachievementpoints), - z: (100 * player.achievementPoints / totalachievementpoints).toPrecision(4) + DOMCacheGetOrSet('achievementprogress').textContent = i18next.t('achievements.achievementPoints', { + x: format(achievementPoints) }) - DOMCacheGetOrSet('achievementQuarkBonus').innerHTML = i18next.t('achievements.quarkBonus', { - multiplier: format(1 + player.achievementPoints / 50000, 3, true) + DOMCacheGetOrSet('achievementQuarkBonus').innerHTML = i18next.t('achievements.achievementLevel', { + level: format(achievementLevel) }) + DOMCacheGetOrSet('achievementTNLText').innerHTML = i18next.t('achievements.achievementToNextLevel', { + level: format(achievementLevel + 1), + AP: format(toNextAchievementLevelEXP(), 0, true) + }) + updateAllGroupedAchievementProgress() + updateAllUngroupedAchievementProgress() + updateAllProgressiveAchievementProgress() } else if (G.currentTab === Tabs.Runes) { DOMCacheGetOrSet('runes').style.display = 'block' DOMCacheGetOrSet('runestab').style.backgroundColor = 'blue' - DOMCacheGetOrSet('runeshowlevelup').textContent = i18next.t('runes.hover') - DOMCacheGetOrSet('researchrunebonus').textContent = i18next.t('runes.thanksResearches', { - percent: format(100 * G.effectiveLevelMult - 100, 4, true) - }) - displayRuneInformation(1, false) - displayRuneInformation(2, false) - displayRuneInformation(3, false) - displayRuneInformation(4, false) - displayRuneInformation(5, false) - displayRuneInformation(6, false) - displayRuneInformation(7, false) + DOMCacheGetOrSet('focusedRuneLevelInfo').textContent = i18next.t('runes.hover') + + for (const rune of Object.keys(player.runes)) { + const runeKey = rune as RuneKeys + updateRuneHTML(runeKey) + } } if (G.currentTab === Tabs.Challenges) { DOMCacheGetOrSet('challenges').style.display = 'block' @@ -666,13 +655,13 @@ export const htmlInserts = () => { // ALWAYS Update these, for they are the most important resources const playerRequirements = [ 'coins', - 'runeshards', + 'offerings', 'prestigePoints', 'transcendPoints', 'transcendShards', 'reincarnationPoints', 'worlds', - 'researchPoints' + 'obtainium' ] as const const domRequirements = [ 'coinDisplay', @@ -685,7 +674,8 @@ export const htmlInserts = () => { 'obtainiumDisplay' ] as const for (let i = 0; i < playerRequirements.length; i++) { - const text = format(player[`${playerRequirements[i]}` as const]) + const value = player[`${playerRequirements[i]}` as const] + const text = format(value instanceof Decimal ? value : value.valueOf()) const dom = DOMCacheGetOrSet(`${domRequirements[i]}` as const) if (dom.textContent !== text) { dom.textContent = text @@ -699,7 +689,7 @@ export const htmlInserts = () => { // TODO(not @KhafraDev): cache the elements and stop getting them every time? export const buttoncolorchange = () => { - DOMCacheGetOrSet('prestigebtn').style.backgroundColor = player.toggles[15] && player.achievements[43] === 1 + DOMCacheGetOrSet('prestigebtn').style.backgroundColor = player.toggles[15] && getLevelMilestone('autoPrestige') === 1 ? 'green' : '' @@ -726,7 +716,7 @@ export const buttoncolorchange = () => { DOMCacheGetOrSet('ascendbtn').style.backgroundColor = player.autoAscend && player.challengecompletions[11] > 0 && player.cubeUpgrades[10] > 0 ? 'green' : '' - DOMCacheGetOrSet('singularitybtn').style.filter = player.runelevels[6] > 0 + DOMCacheGetOrSet('singularitybtn').style.filter = runes.antiquities.level > 0 ? '' : 'contrast(1.25) sepia(1) grayscale(0.25)' @@ -800,76 +790,80 @@ export const buttoncolorchange = () => { const h = DOMCacheGetOrSet('buycrystalupgrade3') const i = DOMCacheGetOrSet('buycrystalupgrade4') const j = DOMCacheGetOrSet('buycrystalupgrade5') - ;((!player.toggles[10] || player.achievements[78] === 0) && player.prestigePoints.gte(player.firstCostDiamonds)) + ;((!player.toggles[10] || getLevelMilestone('tier1CrystalAutobuy') === 0) + && player.prestigePoints.gte(player.firstCostDiamonds)) ? a.classList.add('buildingPurchaseBtnAvailable') : a.classList.remove('buildingPurchaseBtnAvailable') - ;((!player.toggles[11] || player.achievements[85] === 0) && player.prestigePoints.gte(player.secondCostDiamonds)) + ;((!player.toggles[11] || getLevelMilestone('tier2CrystalAutobuy') === 0) + && player.prestigePoints.gte(player.secondCostDiamonds)) ? b.classList.add('buildingPurchaseBtnAvailable') : b.classList.remove('buildingPurchaseBtnAvailable') - ;((!player.toggles[12] || player.achievements[92] === 0) && player.prestigePoints.gte(player.thirdCostDiamonds)) + ;((!player.toggles[12] || getLevelMilestone('tier3CrystalAutobuy') === 0) + && player.prestigePoints.gte(player.thirdCostDiamonds)) ? c.classList.add('buildingPurchaseBtnAvailable') : c.classList.remove('buildingPurchaseBtnAvailable') - ;((!player.toggles[13] || player.achievements[99] === 0) && player.prestigePoints.gte(player.fourthCostDiamonds)) + ;((!player.toggles[13] || getLevelMilestone('tier4CrystalAutobuy') === 0) + && player.prestigePoints.gte(player.fourthCostDiamonds)) ? d.classList.add('buildingPurchaseBtnAvailable') : d.classList.remove('buildingPurchaseBtnAvailable') - ;((!player.toggles[14] || player.achievements[106] === 0) && player.prestigePoints.gte(player.fifthCostDiamonds)) + ;((!player.toggles[14] || getLevelMilestone('tier5CrystalAutobuy') === 0) + && player.prestigePoints.gte(player.fifthCostDiamonds)) ? e.classList.add('buildingPurchaseBtnAvailable') : e.classList.remove('buildingPurchaseBtnAvailable') let k = 0 - k += Math.floor(G.rune3level / 16 * G.effectiveLevelMult) * 100 / 100 if (player.upgrades[73] === 1 && player.currentChallenge.reincarnation !== 0) { k += 10 } - player.achievements[79] < 1 + getLevelMilestone('tier1CrystalAutobuy') === 0 ? (player.prestigeShards.gte( Decimal.pow( 10, - G.crystalUpgradesCost[0] + G.crystalUpgradesCost[0] - getRuneEffects('prism').costDivisorLog10 + G.crystalUpgradeCostIncrement[0] * Math.floor(Math.pow(player.crystalUpgrades[0] + 0.5 - k, 2) / 2) ) ) ? f.style.backgroundColor = 'purple' : f.style.backgroundColor = '') : f.style.backgroundColor = 'green' - player.achievements[86] < 1 + getLevelMilestone('tier2CrystalAutobuy') === 0 ? (player.prestigeShards.gte( Decimal.pow( 10, - G.crystalUpgradesCost[1] + G.crystalUpgradesCost[1] - getRuneEffects('prism').costDivisorLog10 + G.crystalUpgradeCostIncrement[1] * Math.floor(Math.pow(player.crystalUpgrades[1] + 0.5 - k, 2) / 2) ) ) ? g.style.backgroundColor = 'purple' : g.style.backgroundColor = '') : g.style.backgroundColor = 'green' - player.achievements[93] < 1 + getLevelMilestone('tier3CrystalAutobuy') === 0 ? (player.prestigeShards.gte( Decimal.pow( 10, - G.crystalUpgradesCost[2] + G.crystalUpgradesCost[2] - getRuneEffects('prism').costDivisorLog10 + G.crystalUpgradeCostIncrement[2] * Math.floor(Math.pow(player.crystalUpgrades[2] + 0.5 - k, 2) / 2) ) ) ? h.style.backgroundColor = 'purple' : h.style.backgroundColor = '') : h.style.backgroundColor = 'green' - player.achievements[100] < 1 + getLevelMilestone('tier4CrystalAutobuy') === 0 ? (player.prestigeShards.gte( Decimal.pow( 10, - G.crystalUpgradesCost[3] + G.crystalUpgradesCost[3] - getRuneEffects('prism').costDivisorLog10 + G.crystalUpgradeCostIncrement[3] * Math.floor(Math.pow(player.crystalUpgrades[3] + 0.5 - k, 2) / 2) ) ) ? i.style.backgroundColor = 'purple' : i.style.backgroundColor = '') : i.style.backgroundColor = 'green' - player.achievements[107] < 1 + getLevelMilestone('tier5CrystalAutobuy') === 0 ? (player.prestigeShards.gte( Decimal.pow( 10, - G.crystalUpgradesCost[4] + G.crystalUpgradesCost[4] - getRuneEffects('prism').costDivisorLog10 + G.crystalUpgradeCostIncrement[4] * Math.floor(Math.pow(player.crystalUpgrades[4] + 0.5 - k, 2) / 2) ) ) @@ -880,10 +874,12 @@ export const buttoncolorchange = () => { if (G.currentTab === Tabs.Runes) { if (getActiveSubTab() === 0) { - for (let i = 1; i <= 7; i++) { - player.runeshards > 0.5 - ? DOMCacheGetOrSet(`activaterune${i}`).classList.add('runeButtonAvailable') - : DOMCacheGetOrSet(`activaterune${i}`).classList.remove('runeButtonAvailable') + for (const rune of Object.keys(player.runes)) { + if (player.offerings.gt(0)) { + DOMCacheGetOrSet(`${rune}RuneSacrifice`).classList.add('runeButtonAvailable') + } else { + DOMCacheGetOrSet(`${rune}RuneSacrifice`).classList.remove('runeButtonAvailable') + } } } if (getActiveSubTab() === 1) { @@ -896,8 +892,8 @@ export const buttoncolorchange = () => { const g = DOMCacheGetOrSet('buyTalismanItem7') const arr = [a, b, c, d, e, f, g] for (let i = 0; i < arr.length; i++) { - ;(player.researchPoints > G.talismanResourceObtainiumCosts[i] - && player.runeshards > G.talismanResourceOfferingCosts[i]) + ;(player.obtainium.gte(G.talismanResourceObtainiumCosts[i]) + && player.offerings.gte(G.talismanResourceOfferingCosts[i])) ? arr[i].classList.add('talisminBtnAvailable') : arr[i].classList.remove('talisminBtnAvailable') } @@ -969,7 +965,7 @@ export const buttoncolorchange = () => { player.antPoints.gte( Decimal.pow( G.antUpgradeCostIncreases[i - 1], - player.antUpgrades[i - 1]! * player.corruptions.used.corruptionEffects('extinction') + player.antUpgrades[i - 1]! ).times(G.antUpgradeBaseCost[i - 1]) ) ? DOMCacheGetOrSet(`antUpgrade${i}`).classList.add('antUpgradeBtnAvailable') @@ -1015,7 +1011,7 @@ export const updateChallengeLevel = (k: number) => { } } -export const updateAchievementBG = () => { +/*export const updateAchievementBG = () => { // When loading/importing, the game needs to correctly update achievement backgrounds. for (let i = 1; i <= 280; i++) { // Initiates by setting all to default DOMCacheGetOrSet(`ach${i}`).classList.remove('green-background') @@ -1028,12 +1024,12 @@ export const updateAchievementBG = () => { for (let i = 0; i < fixDisplay2.length; i++) { fixDisplay2[i].style.backgroundColor = 'maroon' // Sets the appropriate achs to maroon (red) } - for (let i = 1; i < player.achievements.length; i++) { + for (let i = 1; i < 281; i++) { if (player.achievements[i] > 0.5) { achievementaward(i) // This sets all completed ach to green } } -} +} */ export const showCorruptionStatsLoadouts = () => { const statsButton = DOMCacheGetOrSet('corrStatsBtn') @@ -1059,7 +1055,7 @@ const updateAscensionStats = () => { t = 1 } const [cubes, tess, hyper, platonic, hepteract] = CalcCorruptionStuff().slice(4) - const addedAsterisk = player.singularityUpgrades.oneMind.getEffect().bonus + const addedAsterisk = getGQUpgradeEffect('oneMind') const fillers: Record = { ascLen: formatTimeShort(player.ascStatToggles[6] ? player.ascensionCounter : player.ascensionCounterReal, 0), ascCubes: format(cubes * (player.ascStatToggles[1] ? 1 : 1 / t), 2), @@ -1342,6 +1338,71 @@ export const Notification = (text: string, time = 30000): Promise => { return p.promise } +export type OptionalHTMLStyle = Partial + +let modalUsed = false + +export const Modal = ( + HTML: string, + currX: number, + currY: number, + styleMods: OptionalHTMLStyle = {}, + forceUpdate = false +) => { + const modal = DOMCacheGetOrSet('modal') + const modalContent = DOMCacheGetOrSet('modalContent') + + if (forceUpdate || !modalUsed) { + modalContent.innerHTML = HTML + modalUsed = true + } + + Object.assign(modal.style, styleMods) + + // Measure the dimensions of modal content and viewport + modal.style.visibility = 'hidden' + modal.style.display = 'block' + requestAnimationFrame(() => { + const modalRect = modalContent.getBoundingClientRect() + const viewportWidth = window.innerWidth + const viewportHeight = window.innerHeight + + // Base positioning + let modalX = currX + 20 + let modalY = currY + 20 + + // Check right edge boundary + if (modalX + modalRect.width > viewportWidth) { + modalX = currX - modalRect.width - -20 + } + + // Check bottom edge boundary + if (modalY + modalRect.height > viewportHeight) { + modalY = currY - modalRect.height - 20 + } + + modalX = Math.max(0, modalX) + modalY = Math.max(0, modalY) + + modal.style.left = `${modalX}px` + modal.style.top = `${modalY}px` + + modal.style.visibility = 'visible' + }) +} + +export const CloseModal = () => { + const modal = DOMCacheGetOrSet('modal') + const modalContent = DOMCacheGetOrSet('modalContent') + + // Clear the content + modalContent.innerHTML = '' + + // Hide the modal + modal.style.display = 'none' + modalUsed = false +} + export const openChangelog = () => { const wrapper = document.getElementById('changelogWrapper')! const wrapperBlur = document.getElementById('changelogBlur')! diff --git a/src/UpdateVisuals.ts b/src/UpdateVisuals.ts index 5f3ae9bb3..0dd2cce71 100644 --- a/src/UpdateVisuals.ts +++ b/src/UpdateVisuals.ts @@ -1,5 +1,6 @@ import Decimal from 'break_infinity.js' import i18next from 'i18next' +import { achievementLevel, achievementPoints, getAchievementReward, toNextAchievementLevelEXP } from './Achievements' import { showSacrifice } from './Ants' import { DOMCacheGetOrSet } from './Cache/DOM' import { @@ -14,10 +15,8 @@ import { calculateBlueberryInventory, calculateCookieUpgrade29Luck, calculateCubeQuarkMultiplier, - calculateMaxRunes, calculateNumberOfThresholds, calculateOcteractMultiplier, - calculateRecycleMultiplier, calculateRedAmbrosiaCubes, calculateRedAmbrosiaGenerationSpeed, calculateRedAmbrosiaLuck, @@ -26,33 +25,87 @@ import { calculateRequiredBlueberryTime, calculateRequiredRedAmbrosiaTime, calculateResearchAutomaticObtainium, - calculateRuneExpToLevel, + calculateSalvageRuneEXPMultiplier, calculateSigmoidExponential, - calculateSummationLinear, calculateSummationNonLinear, calculateToNextThreshold, calculateTotalOcteractCubeBonus, calculateTotalOcteractObtainiumBonus, calculateTotalOcteractOfferingBonus, - calculateTotalOcteractQuarkBonus + calculateTotalOcteractQuarkBonus, + calculateTotalSalvage } from './Calculate' -import { formatAsPercentIncrease } from './Campaign' import { CalcECC } from './Challenges' import { version } from './Config' -import type { IMultiBuy } from './Cubes' +import { + calculateAcceleratorCubeBlessing, + calculateAntELOCubeBlessing, + calculateAntSacrificeCubeBlessing, + calculateAntSpeedCubeBlessing, + calculateGlobalSpeedCubeBlessing, + calculateMultiplierCubeBlessing, + calculateObtainiumCubeBlessing, + calculateOfferingCubeBlessing, + calculateRuneEffectivenessCubeBlessing, + calculateSalvageCubeBlessing, + type IMultiBuy +} from './Cubes' import { BuffType, consumableEventBuff, eventBuffType, getEvent, getEventBuff } from './Event' -import type { hepteractTypes } from './Hepteracts' -import { hepteractTypeList } from './Hepteracts' +import { getFinalHepteractCap, type HepteractKeys, hepteractKeys, hepteracts } from './Hepteracts' +import { + calculateAcceleratorHypercubeBlessing, + calculateAntELOHypercubeBlessing, + calculateAntSacrificeHypercubeBlessing, + calculateAntSpeedHypercubeBlessing, + calculateGlobalSpeedHypercubeBlessing, + calculateMultiplierHypercubeBlessing, + calculateObtainiumHypercubeBlessing, + calculateOfferingHypercubeBlessing, + calculateRuneEffectivenessHypercubeBlessing, + calculateSalvageHypercubeBlessing +} from './Hypercubes' import { allDurableConsumables, type PseudoCoinConsumableNames } from './Login' -import { PCoinUpgradeEffects } from './PseudoCoinUpgrades' +import { getOcteractUpgradeCostTNL, type OcteractDataKeys, octeractUpgrades } from './Octeracts' +import { + calculateAscensionScorePlatonicBlessing, + calculateCubeMultiplierPlatonicBlessing, + calculateGlobalSpeedPlatonicBlessing, + calculateHypercubeBlessingMultiplierPlatonicBlessing, + calculateHypercubeMultiplierPlatonicBlessing, + calculatePlatonicMultiplierPlatonicBlessing, + calculateTaxPlatonicBlessing, + calculateTesseractMultiplierPlatonicBlessing +} from './PlatonicCubes' import { getQuarkBonus, quarkHandler } from './Quark' -import { displayRuneInformation } from './Runes' +import { runeBlessingKeys, updateRuneBlessingHTML } from './RuneBlessings' +import { type RuneKeys, updateRuneHTML } from './Runes' +import { runeSpiritKeys, updateRuneSpiritHTML } from './RuneSpirits' import { getShopCosts, isShopUpgradeUnlocked, shopData, shopUpgradeTypes } from './Shop' -import { getGoldenQuarkCost } from './singularity' +import { + computeGQUpgradeFreeLevelSoftcap, + computeGQUpgradeMaxLevel, + getGoldenQuarkCost, + getGQUpgradeCostTNL, + getGQUpgradeEffect, + goldenQuarkUpgrades, + type SingularityDataKeys +} from './singularity' import { loadStatisticsUpdate } from './Statistics' -import { format, formatTimeShort, player } from './Synergism' +import { format, formatAsPercentIncrease, formatDecimalAsPercentIncrease, formatTimeShort, player } from './Synergism' import { getActiveSubTab, Tabs } from './Tabs' -import { calculateMaxTalismanLevel } from './Talismans' +import { getTalismanLevelCap, type TalismanKeys, talismans, updateAllTalismanHTML } from './Talismans' +import { + calculateAcceleratorTesseractBlessing, + calculateAntELOTesseractBlessing, + calculateAntSacrificeTesseractBlessing, + calculateAntSpeedTesseractBlessing, + calculateGlobalSpeedTesseractBlessing, + calculateMultiplierTesseractBlessing, + calculateObtainiumTesseractBlessing, + calculateOfferingTesseractBlessing, + calculateRuneEffectivenessTesseractBlessing, + calculateSalvageTesseractBlessing +} from './Tesseracts' import type { Player, ZeroToFour } from './types/Synergism' import { sumContents, timeReminingHours } from './Utility' import { Globals as G } from './Variables' @@ -86,6 +139,31 @@ export const visualUpdateBuildings = () => { totalProductionDivisor = new Decimal(1) } + DOMCacheGetOrSet('coinInformation').innerHTML = i18next.t('buildings.coinInformation', { + coins: format(player.coins, 2, false), + coinsPerSecond: format( + Decimal.min( + G.producePerSecond.dividedBy(G.taxdivisor), + Decimal.pow(10, G.maxexponent - Decimal.log(G.taxdivisorcheck, 10)) + ), + 0, + false + ), + totalGenerated: format(player.coinsTotal, 0, true) + }) + + let vanityIndex = 0 + const decimalCoin = Decimal.log10(player.coinsTotal) + for (let i = 0; i < G.coinVanityThresholds.length; i++) { + if (decimalCoin < G.coinVanityThresholds[i]) { + break + } else { + vanityIndex += 1 + } + } + + DOMCacheGetOrSet('coinVanity').innerHTML = `${i18next.t(`buildings.coinFlavorTexts.${vanityIndex}`)}` + for (let i = 1; i <= 5; i++) { const place = G[upper[i - 1]] const ith = G.ordinals[(i - 1) as ZeroToFour] @@ -113,8 +191,8 @@ export const visualUpdateBuildings = () => { DOMCacheGetOrSet(`buildtext${2 * i}`).textContent = i18next.t( 'buildings.coinsPerSecond', { - coins: format(place.dividedBy(G.taxdivisor).times(40), 2), - percent: format(percentage, 3) + coins: format(place.dividedBy(G.taxdivisor).times(40), 0), + percent: format(percentage, 2) } ) } @@ -163,10 +241,17 @@ export const visualUpdateBuildings = () => { 'buildings.acceleratorBoost', { amount: format( - G.tuSevenMulti - * (1 + player.researches[16] / 50) - * (1 + CalcECC('transcend', player.challengecompletions[2]) / 100), + 100 * (0.01 * G.tuSevenMulti * (1 + CalcECC('transcend', player.challengecompletions[2]) / 20)), 2 + ), + accelsPerBoost: format( + 5 + + 2 * player.researches[18] + + 2 * player.researches[19] + + 3 * player.researches[20] + + (calculateAcceleratorCubeBlessing()), + 0, + true ) } ) @@ -504,7 +589,7 @@ export const visualUpdateBuildings = () => { DOMCacheGetOrSet('tesseractInfo').textContent = i18next.t( 'buildings.tesseractsYouHave', { - tesseracts: format(player.wowTesseracts) + tesseracts: format(player.wowTesseracts.valueOf()) } ) @@ -521,7 +606,7 @@ export const visualUpdateBuildings = () => { * player.upgrades[125] + 0.1 * player.platonicUpgrades[5] + 0.2 * player.platonicUpgrades[10] - + (G.platonicBonusMultiplier[5] - 1) + + calculateTaxPlatonicBlessing() ), 4, true @@ -549,237 +634,90 @@ export const visualUpdateBuildings = () => { export const visualUpdateUpgrades = () => {} -export const visualUpdateAchievements = () => {} - -export const visualUpdateRunes = () => { - if (G.currentTab !== Tabs.Runes) { +export const visualUpdateAchievements = () => { + if (G.currentTab !== Tabs.Achievements) { return } - if (getActiveSubTab() === 0) { - // Placeholder and place work similarly to buildings, except for the specific Talismans. - const talismans = [ - 'rune1Talisman', - 'rune2Talisman', - 'rune3Talisman', - 'rune4Talisman', - 'rune5Talisman' - ] as const - DOMCacheGetOrSet('offeringCount').textContent = i18next.t( - 'runes.offeringsYouHave', - { - offerings: format(player.runeshards, 0, true) - } - ) + const tnl = toNextAchievementLevelEXP() - for (let i = 1; i <= 7; i++) { - // First one updates level, second one updates TNL, third updates orange bonus levels - let place = G[talismans[i - 1]] - if (i > 5) { - place = 0 - } - const runeLevel = player.runelevels[i - 1] - const maxLevel = calculateMaxRunes(i) - DOMCacheGetOrSet(`rune${i}level`).childNodes[0].textContent = i18next.t( - 'cubes.cubeMetadata.level', - { - value1: format(runeLevel), - value2: format(maxLevel) - } - ) + DOMCacheGetOrSet('achievementprogress').textContent = i18next.t('achievements.achievementPoints', { + x: format(achievementPoints, 0, true) + }) + DOMCacheGetOrSet('achievementQuarkBonus').innerHTML = i18next.t('achievements.achievementLevel', { + level: format(achievementLevel) + }) + DOMCacheGetOrSet('achievementTNLText').innerHTML = i18next.t('achievements.achievementToNextLevel', { + level: format(achievementLevel + 1), + AP: format(tnl, 0, true) + }) - if (runeLevel < maxLevel) { - DOMCacheGetOrSet(`rune${i}exp`).textContent = i18next.t('runes.TNL', { - EXP: format( - calculateRuneExpToLevel(i - 1) - player.runeexp[i - 1], - 2 - ) - }) - } else { - DOMCacheGetOrSet(`rune${i}exp`).textContent = i18next.t('runes.maxLevel') - } - if (i <= 5) { - DOMCacheGetOrSet(`bonusrune${i}`).textContent = i18next.t( - 'runes.bonusAmount', - { - x: format( - 7 * player.constantUpgrades[7] - + Math.min(1e7, player.antUpgrades[8]! + G.bonusant9) - + place - ) - } - ) - } else if (i === 6) { - DOMCacheGetOrSet(`bonusrune${i}`).textContent = i18next.t('runes.bonusAmount', { - x: player.cubeUpgrades[73] + (PCoinUpgradeEffects.INSTANT_UNLOCK_2 ? 6 : 0) + player.campaigns.bonusRune6 - }) - } else { - DOMCacheGetOrSet(`bonusrune${i}`).textContent = i18next.t('runes.bonusNope') - } - displayRuneInformation(i, false) + if (achievementPoints < 2500) { + DOMCacheGetOrSet('achievementProgressFill').style.width = `${Math.floor(100 * (50 - tnl) / 50)}%` + } else { + DOMCacheGetOrSet('achievementProgressFill').style.width = `${Math.floor(100 * (100 - tnl) / 100)}%` + } +} + +const updateOfferingAndSalvageText = () => { + DOMCacheGetOrSet('offeringCount').textContent = i18next.t( + 'runes.offeringsYouHave', + { + offerings: format(player.offerings, 0, true) } + ) - const calculateRecycle = calculateRecycleMultiplier() - const allRuneExpAdditiveMultiplier = sumContents([ - // Base amount multiplied per offering - 1 * calculateRecycle, - // +1 if C1 completion - Math.min(1, player.highestchallengecompletions[1]), - // +0.10 per C1 completion - (0.4 / 10) * player.highestchallengecompletions[1], - // Research 5x2 - 0.6 * player.researches[22], - // Research 5x3 - 0.3 * player.researches[23], - // Particle Upgrade 1x1 - 2 * player.upgrades[61] - ]) - - DOMCacheGetOrSet('offeringExperienceValue').textContent = i18next.t( - 'runes.gainExp', - { - amount: format(allRuneExpAdditiveMultiplier, 2, true) - } - ) + const calculateSalvage = calculateTotalSalvage() + const calculateRecycle = calculateSalvageRuneEXPMultiplier() + if (calculateSalvage >= 0) { DOMCacheGetOrSet('offeringRecycleInfo').textContent = i18next.t( 'runes.recycleChance', { - percent: format((1 - 1 / calculateRecycle) * 100, 2, true), + amount: format(calculateSalvage, 1, true), mult: format(calculateRecycle, 2, true) } ) + } else { + DOMCacheGetOrSet('offeringRecycleInfo').textContent = i18next.t( + 'runes.recycleChanceDividedBy', + { + amount: format(calculateSalvage, 1, true), + div: format(Decimal.pow(calculateRecycle, -1), 2, true) + } + ) + } +} + +export const visualUpdateRunes = () => { + if (G.currentTab !== Tabs.Runes) { + return + } + if (getActiveSubTab() === 0) { + updateOfferingAndSalvageText() + for (const key of Object.keys(player.runes)) { + const runeKey = key as RuneKeys + updateRuneHTML(runeKey) + } } if (getActiveSubTab() === 1) { - for (let i = 0; i < 7; i++) { - const maxTalismanLevel = calculateMaxTalismanLevel(i) - // TODO(@KhafraDev): i18n - DOMCacheGetOrSet(`talisman${i + 1}level`).textContent = `${player.ascensionCount > 0 ? '' : 'Level '} ${ - format(player.talismanLevels[i]) - }/${format(maxTalismanLevel)}` + for (const t of Object.keys(talismans) as TalismanKeys[]) { + DOMCacheGetOrSet(`${t}TalismanLevel`).textContent = i18next.t('runes.talismans.level', { + x: format(talismans[t].level, 0, true), + y: format(getTalismanLevelCap(t), 0, true) + }) } + updateAllTalismanHTML() } else if (getActiveSubTab() === 2) { - const blessingMultiplierArray = [0, 8, 10, 6.66, 2, 1] - let t = 0 - for (let i = 1; i <= 5; i++) { - DOMCacheGetOrSet(`runeBlessingLevel${i}Value`).innerHTML = i18next.t( - 'runes.blessings.blessingLevel', - { - amount: format(player.runeBlessingLevels[i]) - } - ) - - DOMCacheGetOrSet(`runeBlessingPower${i}Value1`).innerHTML = i18next.t( - 'runes.blessings.blessingPower', - { - reward: i18next.t(`runes.blessings.rewards.${i - 1}`), - value: format(G.runeBlessings[i]), - speed: format( - 1 - - t - + blessingMultiplierArray[i] * G.effectiveRuneBlessingPower[i], - 4, - true - ) - } - ) - - const levelsPurchasable = calculateSummationLinear( - player.runeBlessingLevels[i], - G.blessingBaseCost, - player.runeshards, - player.runeBlessingBuyAmount - )[0] - player.runeBlessingLevels[i] - levelsPurchasable > 0 - ? DOMCacheGetOrSet(`runeBlessingPurchase${i}`).classList.add( - 'runeButtonsAvailable' - ) - : DOMCacheGetOrSet(`runeBlessingPurchase${i}`).classList.remove( - 'runeButtonsAvailable' - ) - - DOMCacheGetOrSet(`runeBlessingPurchase${i}`).innerHTML = i18next.t( - 'runes.blessings.increaseLevel', - { - amount: format(Math.max(1, levelsPurchasable)), - offerings: format( - Math.max( - G.blessingBaseCost * (1 + player.runeBlessingLevels[i]), - calculateSummationLinear( - player.runeBlessingLevels[i], - G.blessingBaseCost, - player.runeshards, - player.runeBlessingBuyAmount - )[1] - ) - ) - } - ) - - if (i === 5) { - t = 1 - } + updateOfferingAndSalvageText() + for (const bless of runeBlessingKeys) { + updateRuneBlessingHTML(bless) } } else if (getActiveSubTab() === 3) { - const spiritMultiplierArray = [0, 1, 1, 20, 1, 100] - const subtract = [0, 0, 0, 1, 0, 0] - for (let i = 1; i <= 5; i++) { - spiritMultiplierArray[i] *= player.corruptions.used.totalCorruptionDifficultyMultiplier - - DOMCacheGetOrSet(`runeSpiritLevel${i}Value`).innerHTML = i18next.t( - 'runes.spirits.spiritLevel', - { - amount: format(player.runeSpiritLevels[i]) - } - ) - - DOMCacheGetOrSet(`runeSpiritPower${i}Value1`).innerHTML = i18next.t( - 'runes.spirits.spiritPower', - { - reward: i18next.t(`runes.spirits.rewards.${i - 1}`), - value: format(G.runeSpirits[i]), - speed: format( - 1 - - subtract[i] - + spiritMultiplierArray[i] * G.effectiveRuneSpiritPower[i], - 4, - true - ) - } - ) - - const levelsPurchasable = calculateSummationLinear( - player.runeSpiritLevels[i], - G.spiritBaseCost, - player.runeshards, - player.runeSpiritBuyAmount - )[0] - player.runeSpiritLevels[i] - levelsPurchasable > 0 - ? DOMCacheGetOrSet(`runeSpiritPurchase${i}`).classList.add( - 'runeButtonsAvailable' - ) - : DOMCacheGetOrSet(`runeSpiritPurchase${i}`).classList.remove( - 'runeButtonsAvailable' - ) - - DOMCacheGetOrSet(`runeSpiritPurchase${i}`).innerHTML = i18next.t( - 'runes.blessings.increaseLevel', - { - amount: format(Math.max(1, levelsPurchasable)), - offerings: format( - Math.max( - G.spiritBaseCost * (1 + player.runeSpiritLevels[i]), - calculateSummationLinear( - player.runeSpiritLevels[i], - G.spiritBaseCost, - player.runeshards, - player.runeSpiritBuyAmount - )[1] - ) - ) - } - ) + updateOfferingAndSalvageText() + for (const spirit of runeSpiritKeys) { + updateRuneSpiritHTML(spirit) } } } @@ -855,7 +793,7 @@ export const visualUpdateAnts = () => { } ) - if (player.achievements[173] === 1) { + if (getAchievementReward('antSacrificeUnlock')) { DOMCacheGetOrSet('antSacrificeTimer').textContent = formatTimeShort( player.antSacrificeTimer ) @@ -941,7 +879,6 @@ export const visualUpdateCubes = () => { } // TODO: this code is fucking terrible holy shit. Also pretty sure there's a bug. - let accuracy: [null | number, ...number[]] switch (getActiveSubTab()) { case 0: { if (player.autoOpenCubes) { @@ -955,48 +892,102 @@ export const visualUpdateCubes = () => { DOMCacheGetOrSet('cubeQuantity').innerHTML = i18next.t( 'wowCubes.cubes.inventory', { - amount: format(player.wowCubes, 0, true) - } - ) - const cubeArray = [ - null, - player.cubeBlessings.accelerator, - player.cubeBlessings.multiplier, - player.cubeBlessings.offering, - player.cubeBlessings.runeExp, - player.cubeBlessings.obtainium, - player.cubeBlessings.antSpeed, - player.cubeBlessings.antSacrifice, - player.cubeBlessings.antELO, - player.cubeBlessings.talismanBonus, - player.cubeBlessings.globalSpeed - ] - - accuracy = [null, 2, 2, 2, 2, 2, 2, 2, 1, 4, 3] - for (let i = 1; i <= 10; i++) { - let augmentAccuracy = 0 - if (cubeArray[i]! >= 1000 && i !== 6) { - augmentAccuracy += 2 - } - const aestheticMultiplier = i === 1 || i === 8 || i === 9 ? 1 : 100 - DOMCacheGetOrSet(`cube${i}Bonus`).innerHTML = i18next.t( - `wowCubes.cubes.items.${i}`, - { - amount: format(cubeArray[i], 0, true), - bonus: format( - aestheticMultiplier * (G.cubeBonusMultiplier[i]! - 1), - accuracy[i]! + augmentAccuracy, - true - ) - } - ) - } + amount: format(player.wowCubes.valueOf(), 0, true) + } + ) + + DOMCacheGetOrSet('cubeAcceleratorBonus').innerHTML = i18next.t( + 'wowCubes.cubes.items.1', + { + amount: format(player.cubeBlessings.accelerator, 0, true), + bonus: format(calculateAcceleratorCubeBlessing(), 3, true) + } + ) + + DOMCacheGetOrSet('cubeMultiplierBonus').innerHTML = i18next.t( + 'wowCubes.cubes.items.2', + { + amount: format(player.cubeBlessings.multiplier, 0, true), + bonus: formatAsPercentIncrease(calculateMultiplierCubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('cubeOfferingBonus').innerHTML = i18next.t( + 'wowCubes.cubes.items.3', + { + amount: format(player.cubeBlessings.offering, 0, true), + bonus: formatAsPercentIncrease(calculateOfferingCubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('cubeSalvageBonus').innerHTML = i18next.t( + 'wowCubes.cubes.items.4', + { + amount: format(player.cubeBlessings.runeExp, 0, true), + bonus: format(calculateSalvageCubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('cubeObtainiumBonus').innerHTML = i18next.t( + 'wowCubes.cubes.items.5', + { + amount: format(player.cubeBlessings.obtainium, 0, true), + bonus: formatAsPercentIncrease(calculateObtainiumCubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('cubeAntSpeedBonus').innerHTML = i18next.t( + 'wowCubes.cubes.items.6', + { + amount: format(player.cubeBlessings.antSpeed, 0, true), + bonus: formatDecimalAsPercentIncrease(calculateAntSpeedCubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('cubeAntSacrificeBonus').innerHTML = i18next.t( + 'wowCubes.cubes.items.7', + { + amount: format(player.cubeBlessings.antSacrifice, 0, true), + bonus: formatDecimalAsPercentIncrease(calculateAntSacrificeCubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('cubeAntELOBonus').innerHTML = i18next.t( + 'wowCubes.cubes.items.8', + { + amount: format(player.cubeBlessings.antELO, 0, true), + bonus: formatAsPercentIncrease(calculateAntELOCubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('cubeRuneEffectBonus').innerHTML = i18next.t( + 'wowCubes.cubes.items.9', + { + amount: format(player.cubeBlessings.talismanBonus, 0, true), + bonus: formatAsPercentIncrease(calculateRuneEffectivenessCubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('cubeGlobalSpeedBonus').innerHTML = i18next.t( + 'wowCubes.cubes.items.10', + { + amount: format(player.cubeBlessings.globalSpeed, 0, true), + bonus: formatAsPercentIncrease(calculateGlobalSpeedCubeBlessing(), 2) + } + ) + DOMCacheGetOrSet('cubeBlessingsTotal').innerHTML = i18next.t( 'wowCubes.cubes.total', { - amount: format(sumContents(cubeArray.slice(1) as number[]), 0, true) + amount: format(sumContents(Object.values(player.cubeBlessings)), 0, true) } ) + + const sumOfTributes = sumContents(Object.values(player.cubeBlessings)) + + DOMCacheGetOrSet('cubeFull').innerHTML = sumOfTributes >= 1e300 + ? i18next.t('wowCubes.cubes.full') + : '' break } case 1: { @@ -1011,45 +1002,96 @@ export const visualUpdateCubes = () => { DOMCacheGetOrSet('tesseractQuantity').innerHTML = i18next.t( 'wowCubes.tesseracts.inventory', { - amount: format(player.wowTesseracts, 0, true) - } - ) - const tesseractArray = [ - null, - player.tesseractBlessings.accelerator, - player.tesseractBlessings.multiplier, - player.tesseractBlessings.offering, - player.tesseractBlessings.runeExp, - player.tesseractBlessings.obtainium, - player.tesseractBlessings.antSpeed, - player.tesseractBlessings.antSacrifice, - player.tesseractBlessings.antELO, - player.tesseractBlessings.talismanBonus, - player.tesseractBlessings.globalSpeed - ] - accuracy = [null, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] - for (let i = 1; i <= 10; i++) { - let augmentAccuracy = 0 - if (tesseractArray[i]! >= 1000 && i !== 6) { - augmentAccuracy += 2 - } - DOMCacheGetOrSet(`tesseract${i}Bonus`).innerHTML = i18next.t( - `wowCubes.tesseracts.items.${i}`, - { - amount: format(tesseractArray[i], 0, true), - bonus: format( - 100 * (G.tesseractBonusMultiplier[i]! - 1), - accuracy[i]! + augmentAccuracy, - true - ) - } - ) - } + amount: format(player.wowTesseracts.valueOf(), 0, true) + } + ) + + DOMCacheGetOrSet('tesseractAcceleratorBonus').innerHTML = i18next.t( + 'wowCubes.tesseracts.items.1', + { + amount: format(player.tesseractBlessings.accelerator, 0, true), + bonus: formatAsPercentIncrease(calculateAcceleratorTesseractBlessing(), 2) + } + ) + + DOMCacheGetOrSet('tesseractMultiplierBonus').innerHTML = i18next.t( + 'wowCubes.tesseracts.items.2', + { + amount: format(player.tesseractBlessings.multiplier, 0, true), + bonus: formatAsPercentIncrease(calculateMultiplierTesseractBlessing(), 2) + } + ) + + DOMCacheGetOrSet('tesseractOfferingBonus').innerHTML = i18next.t( + 'wowCubes.tesseracts.items.3', + { + amount: format(player.tesseractBlessings.offering, 0, true), + bonus: formatAsPercentIncrease(calculateOfferingTesseractBlessing(), 2) + } + ) + + DOMCacheGetOrSet('tesseractSalvageBonus').innerHTML = i18next.t( + 'wowCubes.tesseracts.items.4', + { + amount: format(player.tesseractBlessings.runeExp, 0, true), + bonus: formatAsPercentIncrease(calculateSalvageTesseractBlessing(), 2), + cap: formatAsPercentIncrease(1 + 0.5 * calculateSalvageHypercubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('tesseractObtainiumBonus').innerHTML = i18next.t( + 'wowCubes.tesseracts.items.5', + { + amount: format(player.tesseractBlessings.obtainium, 0, true), + bonus: formatAsPercentIncrease(calculateObtainiumTesseractBlessing(), 2) + } + ) + + DOMCacheGetOrSet('tesseractAntSpeedBonus').innerHTML = i18next.t( + 'wowCubes.tesseracts.items.6', + { + amount: format(player.tesseractBlessings.antSpeed, 0, true), + bonus: formatDecimalAsPercentIncrease(calculateAntSpeedTesseractBlessing(), 2) + } + ) + + DOMCacheGetOrSet('tesseractAntSacrificeBonus').innerHTML = i18next.t( + 'wowCubes.tesseracts.items.7', + { + amount: format(player.tesseractBlessings.antSacrifice, 0, true), + bonus: formatAsPercentIncrease(calculateAntSacrificeTesseractBlessing(), 2) + } + ) + + DOMCacheGetOrSet('tesseractAntELOBonus').innerHTML = i18next.t( + 'wowCubes.tesseracts.items.8', + { + amount: format(player.tesseractBlessings.antELO, 0, true), + bonus: formatAsPercentIncrease(calculateAntELOTesseractBlessing(), 2) + } + ) + + DOMCacheGetOrSet('tesseractRuneEffectBonus').innerHTML = i18next.t( + 'wowCubes.tesseracts.items.9', + { + amount: format(player.tesseractBlessings.talismanBonus, 0, true), + bonus: formatAsPercentIncrease(calculateRuneEffectivenessTesseractBlessing(), 2) + } + ) + + DOMCacheGetOrSet('tesseractGlobalSpeedBonus').innerHTML = i18next.t( + 'wowCubes.tesseracts.items.10', + { + amount: format(player.tesseractBlessings.globalSpeed, 0, true), + bonus: formatAsPercentIncrease(calculateGlobalSpeedTesseractBlessing(), 2) + } + ) + DOMCacheGetOrSet('tesseractBlessingsTotal').innerHTML = i18next.t( 'wowCubes.tesseracts.total', { amount: format( - sumContents(tesseractArray.slice(1) as number[]), + sumContents(Object.values(player.tesseractBlessings)), 0, true ) @@ -1069,45 +1111,95 @@ export const visualUpdateCubes = () => { DOMCacheGetOrSet('hypercubeQuantity').innerHTML = i18next.t( 'wowCubes.hypercubes.inventory', { - amount: format(player.wowHypercubes, 0, true) - } - ) - const hypercubeArray = [ - null, - player.hypercubeBlessings.accelerator, - player.hypercubeBlessings.multiplier, - player.hypercubeBlessings.offering, - player.hypercubeBlessings.runeExp, - player.hypercubeBlessings.obtainium, - player.hypercubeBlessings.antSpeed, - player.hypercubeBlessings.antSacrifice, - player.hypercubeBlessings.antELO, - player.hypercubeBlessings.talismanBonus, - player.hypercubeBlessings.globalSpeed - ] - accuracy = [null, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] - for (let i = 1; i <= 10; i++) { - let augmentAccuracy = 0 - if (hypercubeArray[i]! >= 1000) { - augmentAccuracy += 2 - } - DOMCacheGetOrSet(`hypercube${i}Bonus`).innerHTML = i18next.t( - `wowCubes.hypercubes.items.${i}`, - { - amount: format(hypercubeArray[i], 0, true), - bonus: format( - 100 * (G.hypercubeBonusMultiplier[i]! - 1), - accuracy[i]! + augmentAccuracy, - true - ) - } - ) - } + amount: format(player.wowHypercubes.valueOf(), 0, true) + } + ) + + DOMCacheGetOrSet('hypercubeAcceleratorBonus').innerHTML = i18next.t( + 'wowCubes.hypercubes.items.1', + { + amount: format(player.hypercubeBlessings.accelerator, 0, true), + bonus: formatAsPercentIncrease(calculateAcceleratorHypercubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('hypercubeMultiplierBonus').innerHTML = i18next.t( + 'wowCubes.hypercubes.items.2', + { + amount: format(player.hypercubeBlessings.multiplier, 0, true), + bonus: formatAsPercentIncrease(calculateMultiplierHypercubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('hypercubeOfferingBonus').innerHTML = i18next.t( + 'wowCubes.hypercubes.items.3', + { + amount: format(player.hypercubeBlessings.offering, 0, true), + bonus: formatAsPercentIncrease(calculateOfferingHypercubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('hypercubeSalvageBonus').innerHTML = i18next.t( + 'wowCubes.hypercubes.items.4', + { + amount: format(player.hypercubeBlessings.runeExp, 0, true), + bonus: formatAsPercentIncrease(calculateSalvageHypercubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('hypercubeObtainiumBonus').innerHTML = i18next.t( + 'wowCubes.hypercubes.items.5', + { + amount: format(player.hypercubeBlessings.obtainium, 0, true), + bonus: formatAsPercentIncrease(calculateObtainiumHypercubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('hypercubeAntSpeedBonus').innerHTML = i18next.t( + 'wowCubes.hypercubes.items.6', + { + amount: format(player.hypercubeBlessings.antSpeed, 0, true), + bonus: formatAsPercentIncrease(calculateAntSpeedHypercubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('hypercubeAntSacrificeBonus').innerHTML = i18next.t( + 'wowCubes.hypercubes.items.7', + { + amount: format(player.hypercubeBlessings.antSacrifice, 0, true), + bonus: formatAsPercentIncrease(calculateAntSacrificeHypercubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('hypercubeAntELOBonus').innerHTML = i18next.t( + 'wowCubes.hypercubes.items.8', + { + amount: format(player.hypercubeBlessings.antELO, 0, true), + bonus: formatAsPercentIncrease(calculateAntELOHypercubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('hypercubeRuneEffectBonus').innerHTML = i18next.t( + 'wowCubes.hypercubes.items.9', + { + amount: format(player.hypercubeBlessings.talismanBonus, 0, true), + bonus: formatAsPercentIncrease(calculateRuneEffectivenessHypercubeBlessing(), 2) + } + ) + + DOMCacheGetOrSet('hypercubeGlobalSpeedBonus').innerHTML = i18next.t( + 'wowCubes.hypercubes.items.10', + { + amount: format(player.hypercubeBlessings.globalSpeed, 0, true), + bonus: formatAsPercentIncrease(calculateGlobalSpeedHypercubeBlessing(), 2) + } + ) + DOMCacheGetOrSet('hypercubeBlessingsTotal').innerHTML = i18next.t( 'wowCubes.hypercubes.total', { amount: format( - sumContents(hypercubeArray.slice(1) as number[]), + sumContents(Object.values(player.hypercubeBlessings)), 0, true ) @@ -1127,42 +1219,78 @@ export const visualUpdateCubes = () => { DOMCacheGetOrSet('platonicQuantity').innerHTML = i18next.t( 'wowCubes.platonics.inventory', { - amount: format(player.wowPlatonicCubes, 0, true) - } - ) - const platonicArray = [ - player.platonicBlessings.cubes, - player.platonicBlessings.tesseracts, - player.platonicBlessings.hypercubes, - player.platonicBlessings.platonics, - player.platonicBlessings.hypercubeBonus, - player.platonicBlessings.taxes, - player.platonicBlessings.scoreBonus, - player.platonicBlessings.globalSpeed - ] - const DRThreshold = [4e6, 4e6, 4e6, 8e4, 1e4, 1e4, 1e4, 1e4] - accuracy = [5, 5, 5, 5, 2, 3, 3, 2] - for (let i = 0; i < platonicArray.length; i++) { - let augmentAccuracy = 0 - if (platonicArray[i] >= DRThreshold[i]) { - augmentAccuracy += 1 - } - DOMCacheGetOrSet(`platonicCube${i + 1}Bonus`).innerHTML = i18next.t( - `wowCubes.platonics.items.${i + 1}`, - { - amount: format(platonicArray[i], 0, true), - bonus: format( - 100 * (G.platonicBonusMultiplier[i] - 1), - accuracy[i]! + augmentAccuracy, - true - ) - } - ) - } + amount: format(player.wowPlatonicCubes.valueOf(), 0, true) + } + ) + + DOMCacheGetOrSet('platonicCubeMultiplierBonus').innerHTML = i18next.t( + 'wowCubes.platonics.items.1', + { + amount: format(player.platonicBlessings.cubes, 0, true), + bonus: formatAsPercentIncrease(calculateCubeMultiplierPlatonicBlessing(), 2) + } + ) + + DOMCacheGetOrSet('platonicTesseractMultiplierBonus').innerHTML = i18next.t( + 'wowCubes.platonics.items.2', + { + amount: format(player.platonicBlessings.tesseracts, 0, true), + bonus: formatAsPercentIncrease(calculateTesseractMultiplierPlatonicBlessing(), 2) + } + ) + + DOMCacheGetOrSet('platonicHypercubeMultiplierBonus').innerHTML = i18next.t( + 'wowCubes.platonics.items.3', + { + amount: format(player.platonicBlessings.hypercubes, 0, true), + bonus: formatAsPercentIncrease(calculateHypercubeMultiplierPlatonicBlessing(), 2) + } + ) + + DOMCacheGetOrSet('platonicPlatonicMultiplierBonus').innerHTML = i18next.t( + 'wowCubes.platonics.items.4', + { + amount: format(player.platonicBlessings.platonics, 0, true), + bonus: formatAsPercentIncrease(calculatePlatonicMultiplierPlatonicBlessing(), 2) + } + ) + + DOMCacheGetOrSet('platonicHypercubeBlessingBonus').innerHTML = i18next.t( + 'wowCubes.platonics.items.5', + { + amount: format(player.platonicBlessings.hypercubeBonus, 0, true), + bonus: formatAsPercentIncrease(calculateHypercubeBlessingMultiplierPlatonicBlessing(), 2) + } + ) + + DOMCacheGetOrSet('platonicTaxBonus').innerHTML = i18next.t( + 'wowCubes.platonics.items.6', + { + amount: format(player.platonicBlessings.taxes, 0, true), + bonus: format(calculateTaxPlatonicBlessing(), 3, true) + } + ) + + DOMCacheGetOrSet('platonicAscensionScoreBonus').innerHTML = i18next.t( + 'wowCubes.platonics.items.7', + { + amount: format(player.platonicBlessings.scoreBonus, 0, true), + bonus: formatAsPercentIncrease(calculateAscensionScorePlatonicBlessing(), 2) + } + ) + + DOMCacheGetOrSet('platonicGlobalSpeedBonus').innerHTML = i18next.t( + 'wowCubes.platonics.items.8', + { + amount: format(player.platonicBlessings.globalSpeed, 0, true), + bonus: formatAsPercentIncrease(calculateGlobalSpeedPlatonicBlessing(), 2) + } + ) + DOMCacheGetOrSet('platonicBlessingsTotal').innerHTML = i18next.t( 'wowCubes.platonics.total', { - amount: format(sumContents(platonicArray), 0, true) + amount: format(sumContents(Object.values(player.platonicBlessings)), 0, true) } ) break @@ -1170,7 +1298,7 @@ export const visualUpdateCubes = () => { case 4: DOMCacheGetOrSet('cubeAmount2').textContent = `You have ${ format( - player.wowCubes, + player.wowCubes.valueOf(), 0, true ) @@ -1187,9 +1315,9 @@ export const visualUpdateCubes = () => { ) // Update the grid - hepteractTypeList.forEach((type) => { - UpdateHeptGridValues(type) - }) + for (const key of hepteractKeys) { + UpdateHeptGridValues(key) + } // orbs DOMCacheGetOrSet('heptGridOrbBalance').textContent = format( @@ -1216,20 +1344,20 @@ export const visualUpdateCubes = () => { } } -const UpdateHeptGridValues = (type: hepteractTypes) => { - const text = `${type}ProgressBarText` - const bar = `${type}ProgressBar` +const UpdateHeptGridValues = (hept: HepteractKeys) => { + const text = `${hept}ProgressBarText` + const bar = `${hept}ProgressBar` const textEl = DOMCacheGetOrSet(text) const barEl = DOMCacheGetOrSet(bar) - const unlocked = player.hepteractCrafts[type].UNLOCKED + const unlocked = hepteracts[hept].UNLOCKED() if (!unlocked) { textEl.textContent = 'LOCKED' barEl.style.width = '100%' barEl.style.backgroundColor = 'var(--hepteract-bar-red)' } else { - const balance = player.hepteractCrafts[type].BAL - const cap = player.hepteractCrafts[type].computeActualCap() + const balance = hepteracts[hept].BAL + const cap = getFinalHepteractCap(hept) const barWidth = Math.round((balance / cap) * 100) let barColor = '' @@ -1403,13 +1531,13 @@ export const visualUpdateSettings = () => { 3600 / Math.max( 1, - +player.singularityUpgrades.goldenQuarks3.getEffect().bonus + getGQUpgradeEffect('goldenQuarks3') ) - (player.goldenQuarksTimer % (3600.00001 / Math.max( 1, - +player.singularityUpgrades.goldenQuarks3.getEffect().bonus + getGQUpgradeEffect('goldenQuarks3') ))) ), y: format(goldenQuarkMultiplier, 2, true) @@ -1422,7 +1550,7 @@ export const visualUpdateSettings = () => { x: format( Math.floor( (player.goldenQuarksTimer - * +player.singularityUpgrades.goldenQuarks3.getEffect().bonus) + * getGQUpgradeEffect('goldenQuarks3')) / 3600 ) * goldenQuarkMultiplier, 2 @@ -1430,7 +1558,7 @@ export const visualUpdateSettings = () => { y: format( Math.floor( 168 - * +player.singularityUpgrades.goldenQuarks3.getEffect().bonus + * getGQUpgradeEffect('goldenQuarks3') * goldenQuarkMultiplier ) ) @@ -1453,32 +1581,30 @@ export const visualUpdateSingularity = () => { } ) - const keys = Object.keys( - player.singularityUpgrades - ) as (keyof Player['singularityUpgrades'])[] + const keys = Object.keys(goldenQuarkUpgrades) as SingularityDataKeys[] const val = G.shopEnhanceVision for (const key of keys) { if (key === 'offeringAutomatic') { continue } - const singItem = player.singularityUpgrades[key] - const el = DOMCacheGetOrSet(`${String(key)}`) + const singItem = goldenQuarkUpgrades[key] + const el = DOMCacheGetOrSet(key) if ( singItem.maxLevel !== -1 - && singItem.level >= singItem.computeMaxLevel() + && singItem.level >= computeGQUpgradeMaxLevel(key) ) { el.style.filter = val ? 'brightness(.9)' : 'none' } else if ( - singItem.getCostTNL() > player.goldenQuarks + getGQUpgradeCostTNL(key) > player.goldenQuarks || player.singularityCount < singItem.minimumSingularity ) { el.style.filter = val ? 'grayscale(.9) brightness(.8)' : 'none' } else if ( singItem.maxLevel === -1 - || singItem.level < singItem.computeMaxLevel() + || singItem.level < computeGQUpgradeMaxLevel(key) ) { - if (singItem.freeLevels > singItem.level) { + if (computeGQUpgradeFreeLevelSoftcap(key) > singItem.level) { el.style.filter = val ? 'blur(1px) invert(.9) saturate(200)' : 'none' } else { el.style.filter = val ? 'invert(.9) brightness(1.1)' : 'none' @@ -1486,20 +1612,18 @@ export const visualUpdateSingularity = () => { } } } else if (getActiveSubTab() === 2) { - const keys = Object.keys( - player.octeractUpgrades - ) as (keyof Player['octeractUpgrades'])[] + const keys = Object.keys(octeractUpgrades) as OcteractDataKeys[] const val = G.shopEnhanceVision for (const key of keys) { - const octItem = player.octeractUpgrades[key] + const octItem = octeractUpgrades[key] const el = DOMCacheGetOrSet(`${String(key)}`) if (octItem.maxLevel !== -1 && octItem.level >= octItem.maxLevel) { el.style.filter = val ? 'brightness(.9)' : 'none' - } else if (octItem.getCostTNL() > player.wowOcteracts) { + } else if (getOcteractUpgradeCostTNL(key) > player.wowOcteracts) { el.style.filter = val ? 'grayscale(.9) brightness(.8)' : 'none' } else if (octItem.maxLevel === -1 || octItem.level < octItem.maxLevel) { - if (octItem.freeLevels > octItem.level) { + if (octItem.freeLevel > octItem.level) { el.style.filter = val ? 'blur(2px) invert(.9) saturate(200)' : 'none' } else { el.style.filter = val ? 'invert(.9) brightness(1.1)' : 'none' @@ -1752,7 +1876,7 @@ export const visualUpdateShop = () => { } DOMCacheGetOrSet('quarkamount').textContent = i18next.t( 'shop.youHaveQuarks', - { x: format(player.worlds, 0, true) } + { x: format(player.worlds.valueOf(), 0, true) } ) DOMCacheGetOrSet('offeringpotionowned').textContent = format( player.shopUpgrades.offeringPotion, diff --git a/src/Upgrades.ts b/src/Upgrades.ts index f8b91979e..90abf68af 100644 --- a/src/Upgrades.ts +++ b/src/Upgrades.ts @@ -1,9 +1,12 @@ import Decimal from 'break_infinity.js' import i18next from 'i18next' +import { achievementPoints, getAchievementReward } from './Achievements' import { buyAutobuyers, buyGenerator } from './Automation' import { buyUpgrades } from './Buy' import { DOMCacheGetOrSet } from './Cache/DOM' -import { calculateAnts, calculateRuneLevels } from './Calculate' +import { calculateAnts } from './Calculate' +import { getRuneEffects } from './Runes' +import { getRuneSpiritEffect } from './RuneSpirits' import { format, player } from './Synergism' import { revealStuff } from './UpdateHTML' import { sumContents } from './Utility' @@ -13,25 +16,30 @@ const crystalupgdesc: Record Record> = { 3: () => ({ max: format( 100 * (0.12 + 0.88 * player.upgrades[122] + 0.001 * player.researches[129] - * Math.log(player.commonFragments + 1) / Math.log(4)), + * Decimal.log(player.commonFragments.add(1), 4)), 2, true ) }), 4: () => ({ max: format( - 10 + 0.05 * player.researches[129] * Math.log(player.commonFragments + 1) - / Math.log(4) - + 20 * player.corruptions.used.totalCorruptionDifficultyMultiplier * G.effectiveRuneSpiritPower[3] + 10 + 0.05 * player.researches[129] * Decimal.log(player.commonFragments.add(1), 4) + + getRuneSpiritEffect('prism').crystalCaps ) }) } const constantUpgDesc: Record Record> = { - 1: () => ({ level: format(5 + player.achievements[270] + 0.1 * player.platonicUpgrades[18], 1, true) }), + 1: () => ({ + level: format( + 5 + 100 * +getAchievementReward('constUpgrade1Buff') + 0.1 * player.platonicUpgrades[18], + 1, + true + ) + }), 2: () => ({ max: format( - 10 + player.achievements[270] + player.shopUpgrades.constantEX + 100 + 10 + 100 * +getAchievementReward('constUpgrade2Buff') + player.shopUpgrades.constantEX + 100 * (G.challenge15Rewards.exponent.value - 1) + 0.3 * player.platonicUpgrades[18], 2, @@ -48,8 +56,8 @@ const upgradetexts = [ () => format((G.totalCoinOwned + 1) * Math.min(1e30, Math.pow(1.008, G.totalCoinOwned)), 2), () => format((G.totalCoinOwned + 1) * Math.min(1e30, Math.pow(1.008, G.totalCoinOwned)), 2), () => Math.min(4, 1 + Math.floor(Decimal.log(player.fifthOwnedCoin + 1, 10))), - () => Math.floor(player.multiplierBought / 7), - () => Math.floor(player.acceleratorBought / 10), + () => format(Math.floor(player.multiplierBought / 7), 0, true), + () => format(Math.floor(player.acceleratorBought / 10), 0, true), () => format(Decimal.pow(2, Math.min(50, player.secondOwnedCoin / 15)), 2), () => format(Decimal.pow(1.02, G.freeAccelerator), 2), () => format(Decimal.min(1e4, Decimal.pow(1.01, player.prestigeCount)), 2), @@ -57,14 +65,19 @@ const upgradetexts = [ format( Decimal.min( 1e50, - Decimal.pow(player.firstGeneratedMythos.add(player.firstOwnedMythos).add(1), 4 / 3).times(1e10) + Decimal.pow(player.firstGeneratedMythos.add(player.firstOwnedMythos).add(1), 4 / 3).times(1e22) ), 2 ), - () => format(Decimal.pow(1.15, G.freeAccelerator), 2), - () => format(Decimal.pow(1.15, G.freeAccelerator), 2), + () => format(Decimal.pow(1.15, G.freeAccelerator).times(1e5), 2), + () => format(Decimal.pow(1.15, G.freeAccelerator).times(1e5), 2), () => format(Decimal.pow(G.acceleratorEffect, 1 / 3), 2), - () => null, + () => + format( + Decimal.min(1e125, player.transcendShards.add(1)), + 0, + true + ), () => format(Decimal.min(1e125, player.transcendShards.add(1))), () => format(Decimal.min(1e200, player.transcendPoints.times(1e30).add(1))), () => format(Decimal.pow((G.totalCoinOwned + 1) * Math.min(1e30, Math.pow(1.008, G.totalCoinOwned)), 10), 2), @@ -91,16 +104,16 @@ const upgradetexts = [ () => null, () => format( - Math.min(250, Math.floor(Decimal.log(player.coins.add(1), 1e3))) - + Math.max(0, Math.min(1750, Math.floor(Decimal.log(player.coins.add(1), 1e15)) - 50)) + Math.min(50, Math.floor(Decimal.log(player.coins.add(1), 1e10))) + + Math.max(0, Math.min(50, Math.floor(Decimal.log(player.coins.add(1), 1e50)) - 10)) ), () => format( Math.min( - 1000, + 100, Math.floor( (player.firstOwnedCoin + player.secondOwnedCoin + player.thirdOwnedCoin + player.fourthOwnedCoin - + player.fifthOwnedCoin) / 160 + + player.fifthOwnedCoin) / 400 ) ) ), @@ -108,16 +121,16 @@ const upgradetexts = [ format( Math.floor( Math.min( - 2000, + 100, (player.firstOwnedCoin + player.secondOwnedCoin + player.thirdOwnedCoin + player.fourthOwnedCoin - + player.fifthOwnedCoin) / 80 + + player.fifthOwnedCoin) / 400 ) ) ), () => format( - Math.min(75, Math.floor(Decimal.log(player.coins.add(1), 1e10))) - + Math.min(925, Math.floor(Decimal.log(player.coins.add(1), 1e30))) + Math.min(50, Math.floor(Decimal.log(player.coins.add(1), 1e30))) + + Math.min(50, Math.floor(Decimal.log(player.coins.add(1), 1e300))) ), () => format(Math.floor(G.totalCoinOwned / 2000)), () => format(Math.min(500, Math.floor(Decimal.log(player.prestigePoints.add(1), 1e25)))), @@ -129,13 +142,13 @@ const upgradetexts = [ () => null, () => null, () => null, - () => format(Decimal.min(1e30, Decimal.pow(player.transcendPoints.add(1), 1 / 2))), + () => format(Decimal.min(1e30, Decimal.pow(player.transcendPoints.add(4), 1 / 2))), () => format(Decimal.min(1e50, Decimal.pow(player.prestigePoints.add(1), 1 / 50).dividedBy(2.5).add(1)), 2), () => format(Decimal.min(1e30, Decimal.pow(1.01, player.transcendCount)), 2), () => format(Decimal.min(1e6, Decimal.pow(1.01, player.transcendCount)), 2), () => format(Math.min(2500, Math.floor(Decimal.log(player.transcendShards.add(1), 10)))), () => null, - () => format(Math.pow(1.05, player.achievementPoints) * (player.achievementPoints + 1), 2), + () => format(Math.pow(1.01, achievementPoints) * (achievementPoints / 5 + 1), 2), () => format(Math.pow(Math.min(1e25, G.totalMultiplier * G.totalAccelerator) / 1000 + 1, 8)), () => format(Math.min(50, Math.floor(Decimal.log(player.transcendPoints.add(1), 1e10)))), () => null, @@ -173,7 +186,7 @@ const upgradetexts = [ y: format(Math.min(3, new Decimal(b).toNumber()), 2) } }, - () => format(1 / 3 * Math.log(player.maxobtainium + 1) / Math.log(10), 2, true), + () => format(1 / 3 * Decimal.log10(player.maxObtainium.plus(1)), 2, true), () => null, () => Math.min( @@ -182,8 +195,8 @@ const upgradetexts = [ + 2 * player.challengecompletions[9] + 2 * player.challengecompletions[10] ), () => null, - () => format(1 + 4 * Math.min(1, Math.pow(player.maxofferings / 100000, 0.5)), 2), - () => format(1 + 2 * Math.min(1, Math.pow(player.maxobtainium / 30000000, 0.5)), 2), + () => format(1 + 4 * Math.min(1, Math.pow(Decimal.min(player.maxOfferings, 1e10).toNumber() / 100000, 0.5)), 2), + () => format(1 + 2 * Math.min(1, Math.pow(Decimal.min(player.maxObtainium, 1e10).toNumber() / 30000000, 0.5)), 2), () => null, () => format( @@ -194,7 +207,7 @@ const upgradetexts = [ ), 3 ), - () => format(1 + 0.005 * Math.pow(Math.log(player.maxofferings + 1) / Math.log(10), 2), 2, true), + () => format(1 + 0.005 * Math.pow(Decimal.log10(player.maxOfferings.plus(1)), 2), 2, true), () => null, () => null, ...Array.from({ length: 39 }, () => () => null), @@ -348,7 +361,7 @@ const crystalupgeffect: Record Record> = { x: format( Decimal.min( Decimal.pow(10, 50 + 2 * player.crystalUpgrades[0]), - Decimal.pow(1.05, player.achievementPoints * player.crystalUpgrades[0]) + Decimal.pow(1.01, achievementPoints * player.crystalUpgrades[0]) ), 2, true @@ -370,7 +383,7 @@ const crystalupgeffect: Record Record> = { 1 + Math.min( 0.12 + 0.88 * player.upgrades[122] - + 0.001 * player.researches[129] * Math.log(player.commonFragments + 1) / Math.log(4), + + 0.001 * player.researches[129] * Decimal.log(player.commonFragments.add(1), 4), 0.001 * player.crystalUpgrades[2] ), player.firstOwnedDiamonds + player.secondOwnedDiamonds + player.thirdOwnedDiamonds + player.fourthOwnedDiamonds @@ -383,8 +396,8 @@ const crystalupgeffect: Record Record> = { 4: () => ({ x: format( Math.min( - 10 + 0.05 * player.researches[129] * Math.log(player.commonFragments + 1) / Math.log(4) - + 20 * player.corruptions.used.totalCorruptionDifficultyMultiplier * G.effectiveRuneSpiritPower[3], + 10 + 0.05 * player.researches[129] * Decimal.log(player.commonFragments.add(1), 4) + + getRuneSpiritEffect('prism').crystalCaps, 0.05 * player.crystalUpgrades[3] ), 2, @@ -412,12 +425,11 @@ const returnCrystalUpgEffect = (i: number) => export const crystalupgradedescriptions = (i: number) => { const p = player.crystalUpgrades[i - 1] - const c = (player.upgrades[73] > 0.5 && player.currentChallenge.reincarnation !== 0 ? 10 : 0) - + (Math.floor(G.rune3level * G.effectiveLevelMult / 16) * 100 / 100) + const c = player.upgrades[73] > 0.5 && player.currentChallenge.reincarnation !== 0 ? 10 : 0 const q = Decimal.pow( 10, - G.crystalUpgradesCost[i - 1] + G.crystalUpgradesCost[i - 1] - getRuneEffects('prism').costDivisorLog10 + G.crystalUpgradeCostIncrement[i - 1] * Math.floor(Math.pow(player.crystalUpgrades[i - 1] + 0.5 - c, 2) / 2) ) DOMCacheGetOrSet('crystalupgradedescription').textContent = returnCrystalUpgDesc(i) @@ -469,7 +481,7 @@ const constUpgEffect: Record Record> = { 1: () => ({ x: format( Decimal.pow( - 1.05 + 0.01 * player.achievements[270] + 0.001 * player.platonicUpgrades[18], + 1.05 + +getAchievementReward('constUpgrade1Buff') + 0.001 * player.platonicUpgrades[18], player.constantUpgrades[1] ), 2, @@ -482,7 +494,7 @@ const constUpgEffect: Record Record> = { 1 + 0.001 * Math.min( - 100 + 10 * player.achievements[270] + 10 * player.shopUpgrades.constantEX + 100 + 1000 * +getAchievementReward('constUpgrade2Buff') + 10 * player.shopUpgrades.constantEX + 3 * player.platonicUpgrades[18] + 1000 * (G.challenge15Rewards.exponent.value - 1), player.constantUpgrades[2] ), @@ -505,15 +517,14 @@ const constUpgEffect: Record Record> = { x: format(2 * player.constantUpgrades[6]) }), 7: () => ({ - x: format(7 * player.constantUpgrades[7]), - y: format(3 * player.constantUpgrades[7]) + x: format(7 * Math.min(1000, player.constantUpgrades[7])) }), 8: () => ({ x: format(1 + 1 / 10 * player.constantUpgrades[8], 2, true) }), 9: () => ({ x: format( - 1 + 0.01 * Math.log(player.talismanShards + 1) / Math.log(4) * Math.min(1, player.constantUpgrades[9]), + 1 + 0.01 * Decimal.log(player.talismanShards.add(1), 4) * Math.min(1, player.constantUpgrades[9]), 4, true ) @@ -589,5 +600,4 @@ export const buyConstantUpgrades = (i: number, fast = false) => { } } calculateAnts() - calculateRuneLevels() } diff --git a/src/Utility.ts b/src/Utility.ts index b56803e40..0e00edf06 100644 --- a/src/Utility.ts +++ b/src/Utility.ts @@ -1,4 +1,4 @@ -import Decimal from 'break_infinity.js' +import Decimal, { type DecimalSource } from 'break_infinity.js' import { DOMCacheGetOrSet } from './Cache/DOM' import { format } from './Synergism' @@ -32,20 +32,58 @@ export const smallestInc = (x = 0): number => { * @returns {number} */ export const sumContents = (array: number[]): number => { - array = Array.isArray(array) - ? array - : Object.values(array) + assert(Array.isArray(array)) + + switch (array.length) { + case 0: + return 0 + case 1: + return array[0] + case 2: + return array[0] + array[1] + case 3: + return array[0] + array[1] + array[2] + case 4: + return array[0] + array[1] + array[2] + array[3] + case 5: + return array[0] + array[1] + array[2] + array[3] + array[4] + } + + let total = 0 + for (let i = 0; i < array.length; i++) { + total += array[i] + } - return array.reduce((a, b) => a + b, 0) + return total } /** * Returns the product of all contents in an array * @param array {number[]} - * @returns {number} */ -// TODO: Add a productContents for Decimal, but callable using productContents... -export const productContents = (array: number[]): number => array.reduce((a, b) => a * b) +export const productContents = (array: number[]) => { + switch (array.length) { + case 0: + return 0 + case 1: + return array[0] + case 2: + return array[0] * array[1] + case 3: + return array[0] * array[1] * array[2] + case 4: + return array[0] * array[1] * array[2] * array[3] + case 5: + return array[0] * array[1] * array[2] * array[3] * array[4] + } + + let total = 1 + for (let i = 0; i < array.length; i++) { + total *= array[i] + } + + return total +} export const sortWithIndices = (toSort: number[]) => { return Array @@ -53,6 +91,14 @@ export const sortWithIndices = (toSort: number[]) => { .sort((a, b) => toSort[a] < toSort[b] ? -1 : +(toSort[b] < toSort[a])) } +export const sortDecimalWithIndices = (toSort: DecimalSource[]) => { + return Array + .from([...toSort.keys()]) + .sort((a, b) => + new Decimal(toSort[a]).lt(new Decimal(toSort[b])) ? -1 : +(new Decimal(toSort[b]).lt(new Decimal(toSort[a]))) + ) +} + /** * Identical to @see {DOMCacheGetOrSet} but casts the type. * @param id {string} @@ -175,7 +221,7 @@ export const cleanString = (s: string): string => { export function assert (condition: unknown, message?: string): asserts condition { if (!condition) { - throw new TypeError('assertion failed', { cause: new TypeError(message) }) + throw new TypeError('assertion failed', message ? { cause: new TypeError(message) } : undefined) } } @@ -254,3 +300,10 @@ export const findInsertionIndex = (target: number, array: number[]): number => { return low + 1 } + +export let isMobile = true + +export function isMobileDevice () { + isMobile = window.matchMedia('(pointer: coarse)').matches + || /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) +} diff --git a/src/Variables.ts b/src/Variables.ts index db8e40e92..290e12e00 100644 --- a/src/Variables.ts +++ b/src/Variables.ts @@ -18,11 +18,11 @@ export const Globals: GlobalVariables = { // this shows the logarithm of costs. ex: upgrade one will cost 1e+6 coins, upgrade 2 1e+7, etc. // dprint-ignore upgradeCosts: [ - 0, 6, 7, 8, 10, 12, 20, 25, 30, 35, 45, 55, 75, 110, 150, 200, 250, 500, 750, 1000, 1500, - 2, 3, 4, 5, 6, 7, 10, 13, 20, 30, 150, 400, 800, 1600, 3200, 10000, 20000, 50000, 100000, 200000, + 0, 6, 7, 8, 10, 12, 20, 35, 50, 75, 100, 55, 75, 125, 150, 200, 250, 500, 750, 1000, 1500, + 5, 15, 25, 40, 60, 45, 75, 100, 125, 150, 150, 400, 800, 1600, 3200, 10000, 20000, 50000, 100000, 200000, 1, 2, 3, 5, 6, 7, 42, 65, 87, 150, 300, 500, 1000, 1500, 2000, 3000, 6000, 12000, 25000, 75000, 0, 1, 2, 2, 3, 5, 6, 10, 15, 22, 30, 37, 45, 52, 60, 1900, 2500, 3000, 7482, 21397, - 3, 6, 9, 12, 15, 20, 30, 6, 8, 8, 10, 13, 60, 1, 2, 4, 8, 16, 25, 40, + 3, 6, 9, 12, 15, 60, 200, 6, 8, 8, 10, 13, 60, 1, 2, 4, 8, 16, 25, 40, 12, 16, 20, 30, 50, 500, 1250, 5000, 25000, 125000, 1500, 7500, 30000, 150000, 1000000, 250, 1000, 5000, 25000, 125000, 1e3, 1e6, 1e9, 1e12, 1e15 ], @@ -30,94 +30,6 @@ export const Globals: GlobalVariables = { // Mega list of Variables to be used elsewhere crystalUpgradesCost: [6, 15, 20, 40, 100, 200, 500, 1000], crystalUpgradeCostIncrement: [8, 15, 20, 40, 100, 200, 500, 1000], - // dprint-ignore - researchBaseCosts: [ - 1e200, - 1, 1, 1, 1, 1, - 1, 1e2, 1e4, 1e6, 1e8, - 2, 2e2, 2e4, 2e6, 2e8, - 4e4, 4e8, 10, 1e5, 1e9, - 100, 100, 1e4, 2e3, 2e5, - 40, 200, 50, 5000, 20000000, - 777, 7777, 50000, 500000, 5000000, - 2e3, 2e6, 2e9, 1e5, 1e9, - 1, 1, 5, 25, 125, - 2, 5, 320, 1280, 2.5e9, - 10, 2e3, 4e5, 8e7, 2e9, - 5, 400, 1e4, 3e6, 9e8, - 100, 2500, 100, 2000, 2e5, - 1, 20, 3e3, 4e5, 5e7, - 10, 40, 160, 1000, 10000, - 4e9, 7e9, 1e10, 1.2e10, 1.5e10, - 1e12, 1e13, 3e12, 2e13, 2e13, - 2e14, 6e14, 2e15, 6e15, 2e16, - 1e16, 2e16, 2e17, 4e17, 1e18, - 1e13, 1e14, 1e15, 7.777e18, 7.777e20, - 1e16, 3e16, 1e17, 3e17, 1e20, - 1e18, 3e18, 1e19, 3e19, 1e20, - 1e20, 2e20, 4e20, 8e20, 1e21, - 2e21, 4e21, 8e21, 2e22, 4e22, - 3.2e21, 2e23, 4e23, 1e21, 7.777e32, - 5e8, 5e12, 5e16, 5e20, 5e24, /*ascension tier */ - 1e25, 2e25, 4e25, 8e25, 1e26, - 4e26, 8e26, 1e27, 2e27, 1e28, - 5e9, 5e15, 5e21, 5e27, 1e28, /*challenge 11 tier */ - 1e29, 2e29, 4e29, 8e29, 1e27, - 2e30, 4e30, 8e30, 1e31, 2e31, - 5e31, 1e32, 2e32, 4e32, 8e32, /*challenge 12 tier */ - 1e33, 2e33, 4e33, 8e33, 1e34, - 3e34, 1e35, 3e35, 6e35, 1e36, - 3e36, 1e37, 3e37, 1e38, 3e38, /*challenge 13 tier */ - 1e39, 3e39, 1e40, 3e40, 1e50, - 3e41, 1e42, 3e42, 6e42, 1e43, - 3e43, 1e44, 3e44, 1e45, 3e45, /*challenge 14 tier */ - 2e46, 6e46, 2e47, 6e47, 1e64, - 6e48, 2e49, 1e50, 1e51, 4e56 - ], - - // dprint-ignore - researchMaxLevels: [ - 0, 1, 1, 1, 1, 1, - 10, 10, 10, 10, 10, - 10, 10, 10, 10, 10, - 10, 10, 1, 1, 1, - 25, 25, 25, 20, 20, - 10, 10, 10, 10, 10, - 12, 12, 10, 10, 10, - 10, 10, 10, 1, 1, - 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - 10, 10, 10, 10, 10, - 20, 20, 20, 20, 20, - 1, 5, 4, 5, 5, - 10, 10, 10, 10, 10, - 1, 1, 1, 1, 1, - 10, 50, 50, 50, 50, - 10, 1, 20, 20, 20, - 20, 20, 20, 20, 10, - 20, 20, 20, 20, 1, - 20, 5, 5, 3, 2, - 10, 10, 10, 10, 1, - 10, 10, 20, 25, 25, - 50, 50, 50, 50, 100, - 10, 10, 10, 100, 100, - 25, 25, 25, 1, 5, - 10, 10, 10, 10, 1, - 10, 10, 10, 1, 1, - 25, 25, 25, 15, 1, - 10, 10, 10, 10, 1, - 10, 1, 6, 10, 1, - 25, 25, 1, 15, 1, - 10, 10, 10, 1, 1, - 10, 10, 10, 10, 1, - 25, 25, 25, 15, 1, - 10, 10, 10, 1, 1, - 10, 3, 6, 10, 5, - 25, 25, 1, 15, 1, - 20, 20, 20, 1, 1, - 20, 1, 50, 50, 10, - 25, 25, 25, 15, 100000 - ], ticker: 0, @@ -137,7 +49,6 @@ export const Globals: GlobalVariables = { multiplierPower: 2, multiplierEffect: new Decimal(1), challengeOneLog: 3, - freeMultiplierBoost: 0, totalMultiplierBoost: 0, globalCoinMultiplier: new Decimal(1), @@ -206,9 +117,6 @@ export const Globals: GlobalVariables = { tuSevenMulti: 1, currentTab: Tabs.Buildings, - researchfiller1: 'Hover over the grid to get details about researches!', - researchfiller2: 'Level: ', - // dprint-ignore ordinals: ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth', 'fourteenth', 'fifteenth', 'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth', 'twentieth'] as const, // dprint-ignore @@ -234,12 +142,6 @@ export const Globals: GlobalVariables = { maxexponent: 10000, - effectiveLevelMult: 1, - optimalOfferingTimer: 600, - optimalObtainiumTimer: 3600, - - runeSum: 0, - globalAntMult: new Decimal('1'), antMultiplier: new Decimal('1'), @@ -270,28 +172,6 @@ export const Globals: GlobalVariables = { bonusant11: 0, bonusant12: 0, - rune1level: 1, - rune2level: 1, - rune3level: 1, - rune4level: 1, - rune5level: 1, - rune1Talisman: 0, - rune2Talisman: 0, - rune3Talisman: 0, - rune4Talisman: 0, - rune5Talisman: 0, - - talisman1Effect: [null, 0, 0, 0, 0, 0], - talisman2Effect: [null, 0, 0, 0, 0, 0], - talisman3Effect: [null, 0, 0, 0, 0, 0], - talisman4Effect: [null, 0, 0, 0, 0, 0], - talisman5Effect: [null, 0, 0, 0, 0, 0], - talisman6Effect: [null, 0, 0, 0, 0, 0], - talisman7Effect: [null, 0, 0, 0, 0, 0], - - talisman6Power: 0, - talisman7Quarks: 0, - settingscreen: 'settings', talismanResourceObtainiumCosts: [1e13, 1e14, 1e16, 1e18, 1e20, 1e22, 1e24], @@ -314,57 +194,17 @@ export const Globals: GlobalVariables = { obtainiumGain: 0, mirrorTalismanStats: [null, 1, 1, 1, 1, 1], - antELO: 0, - effectiveELO: 0, timeWarp: false, - blessingMultiplier: 1, - spiritMultiplier: 1, - runeBlessings: [0, 0, 0, 0, 0, 0], - runeSpirits: [0, 0, 0, 0, 0, 0], - - effectiveRuneBlessingPower: [0, 0, 0, 0, 0, 0], - effectiveRuneSpiritPower: [0, 0, 0, 0, 0, 0], - - blessingBaseCost: 1e6, - spiritBaseCost: 1e20, - triggerChallenge: 0, prevReductionValue: -1, buildingSubTab: 'coin', // 1,000 of each before Diminishing Returns - blessingbase: [null, 1 / 500, 1 / 5000, 1 / 2000, 1 / 750, 1 / 200, 1 / 10000, 1 / 5000, 1 / 10, 1 / 10000, 1 / 1000], - blessingDRPower: [null, 1 / 3, 1 / 3, 2 / 3, 1 / 2, 2 / 3, 2, 1 / 3, 1 / 3, 1 / 16, 1 / 16], - giftbase: [1 / 1000, 1 / 1000, 1 / 1000, 1 / 1000, 1 / 1000, 1 / 1000, 1 / 1000, 1 / 1000, 1 / 1000, 1 / 1000], - giftDRPower: [1 / 6, 1 / 6, 1 / 3, 1 / 4, 1 / 3, 1, 1 / 6, 1 / 6, 1 / 32, 1 / 32], - benedictionbase: [ - null, - 1 / 1000, - 1 / 1000, - 1 / 1000, - 1 / 1000, - 1 / 1000, - 1 / 1000, - 1 / 1000, - 1 / 1000, - 1 / 1000, - 1 / 1000 - ], - benedictionDRPower: [null, 1 / 12, 1 / 12, 1 / 6, 1 / 8, 1 / 6, 1 / 2, 1 / 12, 1 / 12, 1 / 64, 1 / 64], - // 10 Million of each before Diminishing returns on first 3, 200k for second, and 10k for the last few - platonicCubeBase: [2 / 4e6, 1.5 / 4e6, 1 / 4e6, 1 / 8e4, 1 / 1e4, 1 / 1e5, 1 / 1e4, 1 / 1e4], - platonicDRPower: [1 / 5, 1 / 5, 1 / 5, 1 / 5, 1 / 16, 1 / 16, 1 / 4, 1 / 8], - - cubeBonusMultiplier: [null, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - tesseractBonusMultiplier: [null, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - hypercubeBonusMultiplier: [null, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - platonicBonusMultiplier: [1, 1, 1, 1, 1, 1, 1, 1], autoOfferingCounter: 0, - researchOrderByCost: [], viscosityPower: [1, 0.87, 0.80, 0.75, 0.70, 0.6, 0.54, 0.45, 0.39, 0.33, 0.3, 0.2, 0.1, 0.05, 0, 0, 0], dilationMultiplier: [ @@ -408,7 +248,47 @@ export const Globals: GlobalVariables = { 0 ], extinctionMultiplier: [1, 0.92, 0.86, 0.8, 0.74, 0.65, 0.55, 0.5, 0.45, 0.4, 0.35, 0.3, 0.1, 0, 0, 0, 0], - droughtMultiplier: [1, 5, 25, 200, 1e4, 1e7, 1e11, 1e16, 1e22, 1e30, 1e40, 1e55, 1e80, 1e120, 1e177, 1e200, 1e250], + + droughtSalvage: [ + 0, + -25, + -50, + -75, + -100, + -200, + -300, + -400, + -600, + -800, + -1_000, + -1_250, + -2_000, + -4_000, + -8_000, + -12_000, + -16_000 + ], + /* + droughtMultiplier: [ + 1, + 1 / 5, + 1 / 25, + 1 / 200, + 1 / 1e4, + 1 / 1e7, + 1 / 1e11, + 1 / 1e16, + 1 / 1e22, + 1 / 1e30, + 1 / 1e40, + 1 / 1e55, + 1 / 1e80, + 1 / 1e120, + 1 / 1e177, + 1 / 1e200, + 1 / 1e250 + ],*/ + recessionPower: [ 1, 0.9, @@ -467,8 +347,9 @@ export const Globals: GlobalVariables = { reincarnationChallengeReduction: (e: number) => Math.pow(0.98, Math.log(e / 2.5e4) / Math.log(2)), antSpeed: (e: number) => Math.pow(1 + Math.log(e / 2e5) / Math.log(2), 4), bonusAntLevel: (e: number) => 1 + ((1 / 20) * Math.log(e / 1.5e5)) / Math.log(2), + achievementUnlock: (e: number) => e >= 666666 ? 1 : 0, cube3: (e: number) => 1 + ((1 / 150) * Math.log(e / 2.5e5)) / Math.log(2), - talismanBonus: (e: number) => 1 + ((1 / 20) * Math.log(e / 7.5e5)) / Math.log(2), + talismanBonus: (e: number) => (e >= 7.5e5) ? 1 + 0.02 + ((1 / 1000) * Math.log(e / 7.5e5)) / Math.log(2) : 1, globalSpeed: (e: number) => 1 + ((1 / 20) * Math.log(e / 2.5e6)) / Math.log(2), blessingBonus: (e: number) => 1 + (1 / 5) * Math.pow(e / 3e7, 1 / 4), constantBonus: (e: number) => 1 + (1 / 5) * Math.pow(e / 1e8, 2 / 3), @@ -568,6 +449,11 @@ export const Globals: GlobalVariables = { baseValue: 1, requirement: 500000 }, + achievementUnlock: { + value: 0, + baseValue: 0, + requirement: 666666 + }, cube3: { value: 1, baseValue: 1, @@ -662,7 +548,7 @@ export const Globals: GlobalVariables = { value: 0, baseValue: 0, requirement: 3.33e16, - HTMLColor: 'purple' + HTMLColor: 'pink' }, freeOrbs: { value: 0, @@ -700,7 +586,36 @@ export const Globals: GlobalVariables = { redAmbrosiaTimer: 0, TIME_PER_AMBROSIA: 30, TIME_PER_RED_AMBROSIA: 100000, - currentSingChallenge: undefined + currentSingChallenge: undefined, + + coinVanityThresholds: [ + 0, + 3, + 6, + 16, + 100, + 500, + 2500, + 1e4, + 1e5, + 1e6, + 1e7, + 1e8, + 1e9, + 1e12, + 1e15, + 1e20, + 1e24, + 1e28, + 1e32, + 1e40, + 1e50, + 1e60, + 1e70, + 1e80, + 1e90, + 1e100 + ] } export const blankGlobals = { ...Globals } diff --git a/src/i18n.ts b/src/i18n.ts index a87b57265..4030c26aa 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -2,6 +2,7 @@ import i18next, { type Resource } from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' import { prod } from './Config' import ColorTextPlugin from './Plugins/ColorText' +import StatSymbolsPlugin from './Plugins/StatSymbols' import { Confirm } from './UpdateHTML' // For 'flag': https://emojipedia.org/emoji-flag-sequence/ @@ -40,12 +41,12 @@ export const init = async (): Promise => { resources.en = { translation: file } } - await i18next.use(ColorTextPlugin).init({ + await i18next.use(StatSymbolsPlugin).use(ColorTextPlugin).init({ lng: language, fallbackLng: 'en', debug: !prod, resources, - postProcess: ['ColorText'], + postProcess: ['StatSymbols', 'ColorText'], // crowdin returns an empty string when a translation for // a language isn't present returnEmptyString: false, diff --git a/src/mock/browser.ts b/src/mock/browser.ts index a6e79fa2f..cd90cb8a8 100644 --- a/src/mock/browser.ts +++ b/src/mock/browser.ts @@ -1,8 +1,15 @@ import { http, HttpResponse } from 'msw' import { setupWorker } from 'msw/browser' +import { cloudSaveHandlers } from './handlers/CloudSaveHandlers' +import { messageHandlers } from './handlers/MessageHandlers' import { consumeHandlers } from './websocket' const GETHandlers = [ + http.get('https://synergism.cc/api/v1/quark-bonus', () => { + return HttpResponse.json({ + bonus: 105.3 + }) + }), http.get('https://synergism.cc/stripe/coins', () => { return HttpResponse.json({ coins: 49001 @@ -1152,8 +1159,6 @@ const PUTHandlers = [ const { id } = params // TODO: Mock buying beyond level 1 - // I was able to confirm that the new upgrades all worked on the frontend - // April 29, 2025 return HttpResponse.json({ upgradeId: Number(id), level: 1 @@ -1208,5 +1213,7 @@ export const worker = setupWorker( }), ...GETHandlers, ...PUTHandlers, - ...consumeHandlers + ...consumeHandlers, + ...cloudSaveHandlers, + ...messageHandlers ) diff --git a/src/mock/handlers/CloudSaveHandlers.ts b/src/mock/handlers/CloudSaveHandlers.ts new file mode 100644 index 000000000..db728e1c3 --- /dev/null +++ b/src/mock/handlers/CloudSaveHandlers.ts @@ -0,0 +1,81 @@ +import { delay, http, type HttpHandler, HttpResponse } from 'msw' + +interface Save { + id: number + name: string + uploadedAt: string + save: string +} + +function isAscii (buffer: ArrayBuffer) { + const uint8Array = new Uint8Array(buffer) + for (let i = 0; i < uint8Array.length; i++) { + if (uint8Array[i] > 127) { + return false + } + } + + return true +} + +const saves: Save[] = [] + +export const cloudSaveHandlers: HttpHandler[] = [ + http.get('/saves/retrieve/metadata', async () => { + await delay(500) + + return HttpResponse.json(saves.map((s) => { + const { save, ...rest } = s + return rest + })) + }), + http.get('/saves/retrieve/all', async () => { + await delay(1000) + return HttpResponse.json(saves) + }), + http.post('/saves/upload', async ({ request }) => { + await delay(1000) + + const fd = await request.formData() + const file = fd.get('file') + const name = fd.get('name') + + if (!(file instanceof File) || typeof name !== 'string') { + return new HttpResponse(null, { status: 400 }) + } + + const text = await file.arrayBuffer() + + if (!isAscii(text)) { + return new Response(null, { status: 400 }) + } + + const base64 = await file.text() + const decoded = atob(base64) + const stream = new Blob([decoded]).stream().pipeThrough(new CompressionStream('gzip')) + + const compressedData = await new Response(stream).bytes() + const encoded = btoa(String.fromCharCode(...compressedData)) + + saves.push({ id: saves.length, name, uploadedAt: new Date().toString(), save: encoded }) + + return new Response('Ok!', { status: 200 }) + }), + http.get('/saves/transfer', async () => { + await delay(1000) + + return new Response('Ok!', { status: 200 }) + }), + http.delete('/saves/delete', async ({ request }) => { + await delay(1000) + + const { name } = await request.json() as { name: string } + const save = saves.find((save) => save.name === name) + + if (save) { + saves.splice(saves.indexOf(save), 1) + } + + return new Response(null, { status: 204 }) + }) +] diff --git a/src/mock/handlers/MessageHandlers.ts b/src/mock/handlers/MessageHandlers.ts new file mode 100644 index 000000000..0c005fb27 --- /dev/null +++ b/src/mock/handlers/MessageHandlers.ts @@ -0,0 +1,119 @@ +import { delay, http, type HttpHandler, HttpResponse } from 'msw' +import type { Message } from '../../Messages' + +// Mock messages data - includes different types and priority levels for testing +const mockMessages: Message[] = [ + { + id: 1, + title: '🎉 Welcome to Synergism!', + content: + 'Thanks for playing the greatest idle game of all time! Check out the new features in this update including improved performance and bug fixes.', + type: 'success', + priority: 100, + is_active: true, + created_at: new Date(Date.now() - 86400000).toISOString(), // 1 day ago + updated_at: new Date(Date.now() - 86400000).toISOString(), + expires_at: new Date(Date.now() + 86400000 * 7).toISOString() // 7 days from now + }, + { + id: 2, + title: '⚠️ Server Maintenance Notice', + content: + 'We will be performing server maintenance on Sunday at 3:00 AM UTC. The game will remain playable but cloud saves may be temporarily unavailable.\n\nMaintenance is expected to last 2 hours.', + type: 'warning', + priority: 80, + is_active: true, + created_at: new Date(Date.now() - 43200000).toISOString(), // 12 hours ago + updated_at: new Date(Date.now() - 43200000).toISOString(), + expires_at: new Date(Date.now() + 86400000 * 3).toISOString() // 3 days from now + }, + { + id: 3, + title: 'ℹ️ New Community Discord Features', + content: + 'Join our Discord server to access:\n• Exclusive roles and channels\n• Beta testing opportunities\n• Direct feedback to developers\n• Community events and giveaways\n\nLink: https://discord.gg/synergism', + type: 'info', + priority: 50, + is_active: true, + created_at: new Date(Date.now() - 21600000).toISOString(), // 6 hours ago + updated_at: new Date(Date.now() - 21600000).toISOString() + }, + { + id: 4, + title: '🔧 Bug Fix Update Available', + content: + 'A small bug fix update is now live:\n• Fixed calculation errors in corruption loadouts\n• Improved memory usage\n• Fixed visual glitches in the research tab\n\nRefresh your page to get the latest version!', + type: 'info', + priority: 70, + is_active: true, + created_at: new Date(Date.now() - 7200000).toISOString(), // 2 hours ago + updated_at: new Date(Date.now() - 7200000).toISOString(), + expires_at: new Date(Date.now() + 86400000 * 5).toISOString() // 5 days from now + }, + { + id: 5, + title: '❌ Critical Error Fixed', + content: + 'A critical bug affecting save files has been resolved. If you experienced data loss, please contact support with your save backup.', + type: 'error', + priority: 95, + is_active: true, + created_at: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago + updated_at: new Date(Date.now() - 3600000).toISOString(), + expires_at: new Date(Date.now() + 86400000 * 2).toISOString() // 2 days from now + }, + { + id: 6, + title: 'ℹ️ Holiday Event Coming Soon', + content: 'Get ready for our upcoming holiday event! Special rewards and challenges await. Event starts next week.', + type: 'info', + priority: 40, + is_active: true, + created_at: new Date(Date.now() - 1800000).toISOString(), // 30 minutes ago + updated_at: new Date(Date.now() - 1800000).toISOString(), + expires_at: new Date(Date.now() + 86400000 * 14).toISOString() // 14 days from now + } +] + +// Track which messages have been marked as read (in a real app this would be per-user) +const readMessageIds = new Set() + +export const messageHandlers: HttpHandler[] = [ + // GET /messages/unread - Fetch unread messages + http.get('https://synergism.cc/messages/unread', async () => { + await delay(500) // Simulate network delay + + // Filter out read messages and inactive messages + const unreadMessages = mockMessages.filter( + (message) => !readMessageIds.has(message.id) && message.is_active + ) + + return HttpResponse.json({ + success: true, + data: unreadMessages + }) + }), + + // POST /messages/:id/mark-read - Mark a message as read + http.post('https://synergism.cc/messages/:id/mark-read', async ({ params }) => { + await delay(300) // Simulate network delay + + const messageId = Number.parseInt(params.id as string) + + // Check if message exists + const messageExists = mockMessages.some((msg) => msg.id === messageId) + if (!messageExists) { + return HttpResponse.json({ + success: false, + error: 'Message not found' + }, { status: 404 }) + } + + // Mark message as read + readMessageIds.add(messageId) + + return HttpResponse.json({ + success: true + }) + }) +] diff --git a/src/mock/util/messages.ts b/src/mock/util/messages.ts index a6c2383a1..f2313eb6a 100644 --- a/src/mock/util/messages.ts +++ b/src/mock/util/messages.ts @@ -19,11 +19,12 @@ export const messages = { }) }, - consumed (consumable: string, at: number) { + consumed (consumable: string, displayName: string, startedAt: number) { return JSON.stringify({ type: 'consumed', consumable, - startedAt: at + displayName, + startedAt }) }, diff --git a/src/mock/websocket.ts b/src/mock/websocket.ts index 79465371e..40b4dd3c4 100644 --- a/src/mock/websocket.ts +++ b/src/mock/websocket.ts @@ -40,7 +40,7 @@ export const consumeHandlers = [ sleep(2500).then(() => client.send(messages.timeSkip(data.consumable, length))) } else { // Happy Hour Bell sleep(1000).then(() => { - consumable.broadcast(messages.consumed(data.consumable, Date.now() + (1000 * 60 * 60))) + consumable.broadcast(messages.consumed(data.consumable, 'Happy Hour Bell', Date.now() + (1000 * 60 * 60))) client.send(messages.thanks()) }) } diff --git a/src/saves/PlayerJsonSchema.ts b/src/saves/PlayerJsonSchema.ts index 47667d47c..1edd7fe8c 100644 --- a/src/saves/PlayerJsonSchema.ts +++ b/src/saves/PlayerJsonSchema.ts @@ -45,36 +45,6 @@ export const playerJsonSchema = playerSchema.extend({ } }), - singularityUpgrades: z.any().transform((upgrades: Player['singularityUpgrades']) => - Object.fromEntries( - Object.entries(upgrades).map(([key, value]) => { - return [ - key, - { - level: value.level, - goldenQuarksInvested: value.goldenQuarksInvested, - toggleBuy: value.toggleBuy, - freeLevels: value.freeLevels - } - ] - }) - ) - ), - octeractUpgrades: z.any().transform((upgrades: Player['octeractUpgrades']) => - Object.fromEntries( - Object.entries(upgrades).map(([key, value]) => { - return [ - key, - { - level: value.level, - octeractsInvested: value.octeractsInvested, - toggleBuy: value.toggleBuy, - freeLevels: value.freeLevels - } - ] - }) - ) - ), singularityChallenges: z.any().transform((challenges: Player['singularityChallenges']) => Object.fromEntries( Object.entries(challenges).map(([key, value]) => { @@ -89,22 +59,6 @@ export const playerJsonSchema = playerSchema.extend({ }) ) ), - blueberryUpgrades: z.any().transform((upgrades: Player['blueberryUpgrades']) => - Object.fromEntries( - Object.entries(upgrades).map(([key, value]) => { - return [ - key, - { - level: value.level, - ambrosiaInvested: value.ambrosiaInvested, - blueberriesInvested: value.blueberriesInvested, - toggleBuy: value.toggleBuy, - freeLevels: value.freeLevels - } - ] - }) - ) - ), dayCheck: z.any().transform((dayCheck: Player['dayCheck']) => dayCheck?.toISOString() ?? null) }) diff --git a/src/saves/PlayerSchema.ts b/src/saves/PlayerSchema.ts index d36cd700d..2843aad8d 100644 --- a/src/saves/PlayerSchema.ts +++ b/src/saves/PlayerSchema.ts @@ -1,17 +1,19 @@ import Decimal, { type DecimalSource } from 'break_infinity.js' import { z, type ZodNumber, type ZodType } from 'zod' -import { BlueberryUpgrade, blueberryUpgradeData } from '../BlueberryUpgrades' import { CampaignManager, type ICampaignManagerData } from '../Campaign' import { CorruptionLoadout, CorruptionSaves } from '../Corruptions' import { WowCubes, WowHypercubes, WowPlatonicCubes, WowTesseracts } from '../CubeExperimental' -import { HepteractCraft } from '../Hepteracts' -import { octeractData, OcteractUpgrade } from '../Octeracts' +import { type HepteractKeys, hepteracts } from '../Hepteracts' import { QuarkHandler } from '../Quark' -import { singularityData, SingularityUpgrade } from '../singularity' -import { SingularityChallenge, singularityChallengeData } from '../SingularityChallenges' +import { + SingularityChallenge, + singularityChallengeData, + type SingularityChallengeDataKeys +} from '../SingularityChallenges' import { blankSave, deepClone } from '../Synergism' +import { noTalismanFragments } from '../Talismans' import type { Player } from '../types/Synergism' -import { padArray } from '../Utility' +import { padArray, sumContents } from '../Utility' const decimalSchema = z.custom((value) => { try { @@ -24,13 +26,21 @@ const decimalSchema = z.custom((value) => { const arrayStartingWithNull = (s: ZodType) => z.tuple([z.null()]).rest(s) -const arrayExtend = (array: Value, k: K) => { - if (array.length < blankSave[k].length) { - array.push(...blankSave[k].slice(array.length)) +const arrayExtend = < + K extends keyof Player, + Value extends Player[K] extends Array ? V[] : never +>(array: Value, k: K) => { + const b = blankSave[k] as Value + if (array.length < b.length) { + array.push(...b.slice(array.length)) } return array } +const buyAmount = z.number().refine((arg) => + arg === 1 || arg === 10 || arg === 100 || arg === 1000 || arg === 10_000 || arg === 100_000 +).default(1) + const ascendBuildingSchema = z.object({ cost: z.number(), owned: z.number(), @@ -39,9 +49,8 @@ const ascendBuildingSchema = z.object({ }) const singularityUpgradeSchema = (...keys: string[]) => { - return z.object>({ + return z.object>({ level: z.number(), - toggleBuy: z.number(), freeLevels: z.number(), ...keys.reduce((accum, value) => { accum[value] = z.number() @@ -52,7 +61,7 @@ const singularityUpgradeSchema = (...keys: string[]) => { const toggleSchema = z.record(z.string(), z.boolean()).transform((record) => { return Object.fromEntries( - Object.entries(record).filter(([key, _value]) => /^\d+$/.test(key)) + Object.entries(record).filter(([key]) => /^\d+$/.test(key)) ) }).transform((record) => { const entries = Object.entries(blankSave.toggles) @@ -69,19 +78,26 @@ const toggleSchema = z.record(z.string(), z.boolean()).transform((record) => { const decimalStringSchema = z.string().regex(/^|-?\d+(\.\d{1,2})?$/) const integerStringSchema = z.string().regex(/^\d+$/) -const hepteractCraftSchema = (k: keyof Player['hepteractCrafts']) => +// TODO: FUCK THIS SHIT. +const hepteractCraftSchema = (k: HepteractKeys) => z.object({ - AUTO: z.boolean().default(() => blankSave.hepteractCrafts[k].AUTO), - BAL: z.number().default(() => blankSave.hepteractCrafts[k].BAL), - BASE_CAP: z.number(), - CAP: z.number().default(() => blankSave.hepteractCrafts[k].CAP), - DISCOUNT: z.number().default(() => blankSave.hepteractCrafts[k].DISCOUNT), + AUTO: z.boolean().default(() => blankSave.hepteracts[k].AUTO), + BAL: z.number().default(() => blankSave.hepteracts[k].BAL), + BASE_CAP: z.number().default(() => hepteracts[k].BASE_CAP), + CAP: z.number().default(() => 1000), + DISCOUNT: z.number().default(() => 0), HEPTERACT_CONVERSION: z.number(), - HTML_STRING: z.string().default(() => blankSave.hepteractCrafts[k].HTML_STRING), + HTML_STRING: z.string().default(() => k), OTHER_CONVERSIONS: z.record(z.string(), z.number()), - UNLOCKED: z.boolean().default(() => blankSave.hepteractCrafts[k].UNLOCKED) + UNLOCKED: z.boolean().default(() => false) }) +const newHepteractCraftSchema = z.object({ + BAL: z.number(), + TIMES_CAP_EXTENDED: z.number(), + AUTO: z.boolean() +}) + const optionalCorruptionSchema = z.object({ viscosity: z.number().optional().default(0), drought: z.number().optional().default(0), @@ -93,6 +109,33 @@ const optionalCorruptionSchema = z.object({ hyperchallenge: z.number().optional().default(0) }) +const talismanFragmentSchema = z.object({ + shard: decimalSchema.default(() => new Decimal(0)), + commonFragment: decimalSchema.default(() => new Decimal(0)), + uncommonFragment: decimalSchema.default(() => new Decimal(0)), + rareFragment: decimalSchema.default(() => new Decimal(0)), + epicFragment: decimalSchema.default(() => new Decimal(0)), + legendaryFragment: decimalSchema.default(() => new Decimal(0)), + mythicalFragment: decimalSchema.default(() => new Decimal(0)) +}) + +const goldenQuarkUpgradeSchema = z.object({ + level: z.number().default(0), + freeLevel: z.number().default(0), + goldenQuarksInvested: z.number().default(0) +}) + +const octeractUpgradeSchema = z.object({ + level: z.number().default(0), + freeLevel: z.number().default(0), + octeractsInvested: z.number().default(0) +}) + +const ambrosiaUpgradeSchema = z.object({ + ambrosiaInvested: z.number().default(0), + blueberriesInvested: z.number().default(0) +}) + export const playerCorruptionSchema = z.object({ used: optionalCorruptionSchema.transform((value) => { return new CorruptionLoadout(value) @@ -337,16 +380,38 @@ export const playerSchema = z.object({ ascension: z.number() }).default(() => ({ ...blankSave.currentChallenge })) ]), - researchPoints: z.number(), + + obtainium: decimalSchema.default(() => blankSave.obtainium), + maxObtainium: decimalSchema.default(() => blankSave.maxObtainium), + + researchPoints: z.number().optional(), obtainiumtimer: z.number(), - obtainiumpersecond: z.number().default(() => blankSave.obtainiumpersecond), - maxobtainiumpersecond: z.number().default(() => blankSave.maxobtainiumpersecond), - maxobtainium: z.number().default(() => blankSave.maxobtainium), + obtainiumpersecond: z.number().optional(), + maxobtainiumpersecond: z.number().optional(), + maxobtainium: z.number().optional(), researches: z.number().array().transform((array) => arrayExtend(array, 'researches')), - unlocks: z.record(z.string(), z.boolean()), + unlocks: z.record(z.string(), z.boolean()).transform((object) => { + return Object.fromEntries( + Object.keys(blankSave.unlocks).map((key) => { + const value = object[key] ?? blankSave.unlocks[key as keyof typeof blankSave['unlocks']] + return value === null ? [key, false] : [key, Boolean(value)] + }) + ) + }).default(() => ({ ...blankSave.unlocks })), achievements: z.number().array().transform((array) => arrayExtend(array, 'achievements')), + progressiveAchievements: z.record(z.string(), z.number()).transform( + (object) => { + return Object.fromEntries( + Object.keys(blankSave.progressiveAchievements).map((key) => { + const value = object[key] + ?? blankSave.progressiveAchievements[key as keyof typeof blankSave['progressiveAchievements']] + return value === null ? [key, 0] : [key, Number(value)] + }) + ) + } + ).default(() => ({ ...blankSave.progressiveAchievements })), achievementPoints: z.number(), @@ -367,13 +432,49 @@ export const playerSchema = z.object({ crystalUpgrades: z.number().array(), crystalUpgradesCost: z.number().array().default(() => [...blankSave.crystalUpgradesCost]), - runelevels: z.number().array().transform((array) => arrayExtend(array, 'runelevels')), - runeexp: z.union([z.number(), z.null().transform(() => 0)]).array().transform((value) => - arrayExtend(value, 'runeexp') - ), - runeshards: z.number(), - maxofferings: z.number().default(() => blankSave.maxofferings), - offeringpersecond: z.number().default(() => blankSave.offeringpersecond), + runes: z.record(z.string(), decimalSchema) + .transform((object) => { + return Object.fromEntries( + Object.keys(blankSave.runes).map((key) => { + const value = object[key] ?? blankSave.runes[key as keyof typeof blankSave['runes']] + return value === null ? [key, new Decimal('0')] : [key, new Decimal(value)] + }) + ) + }) + .default(() => ({ ...blankSave.runes })), + + runeBlessings: z.record(z.string(), decimalSchema) + .transform((object) => { + return Object.fromEntries( + Object.keys(blankSave.runeBlessings).map((key) => { + const value = object[key] ?? blankSave.runeBlessings[key as keyof typeof blankSave['runeBlessings']] + return value === null ? [key, new Decimal('0')] : [key, new Decimal(value)] + }) + ) + }) + .default(() => ({ ...blankSave.runeBlessings })), + + runeSpirits: z.record(z.string(), decimalSchema) + .transform((object) => { + return Object.fromEntries( + Object.keys(blankSave.runeSpirits).map((key) => { + const value = object[key] ?? blankSave.runeSpirits[key as keyof typeof blankSave['runeSpirits']] + return value === null ? [key, new Decimal('0')] : [key, new Decimal(value)] + }) + ) + }) + .default(() => ({ ...blankSave.runeSpirits })), + + runelevels: z.number().array().optional(), + runeexp: z.union([z.number(), z.null().transform(() => 0)]).array().optional(), + + offerings: decimalSchema.default(() => blankSave.offerings), + maxOfferings: decimalSchema.default(() => blankSave.maxOfferings), + + runeshards: z.number().optional(), + maxofferings: z.number().optional(), + + offeringpersecond: z.number().optional(), prestigecounter: z.number(), transcendcounter: z.number(), @@ -398,12 +499,12 @@ export const playerSchema = z.object({ tesseractAutoBuyerToggle: z.number().default(() => blankSave.tesseractAutoBuyerToggle), tesseractAutoBuyerAmount: z.number().default(() => blankSave.tesseractAutoBuyerAmount), - coinbuyamount: z.number(), - crystalbuyamount: z.number(), - mythosbuyamount: z.number(), - particlebuyamount: z.number(), - offeringbuyamount: z.number(), - tesseractbuyamount: z.number().default(() => blankSave.tesseractbuyamount), + coinbuyamount: buyAmount, + crystalbuyamount: buyAmount, + mythosbuyamount: buyAmount, + particlebuyamount: buyAmount, + offeringbuyamount: buyAmount, + tesseractbuyamount: buyAmount, shoptoggles: z.record(z.string(), z.boolean()), tabnumber: z.any().optional(), @@ -463,30 +564,39 @@ export const playerSchema = z.object({ antSacrificeTimer: z.number().default(() => blankSave.antSacrificeTimer), antSacrificeTimerReal: z.number().default(() => blankSave.antSacrificeTimerReal), + talismans: z.record(z.string(), talismanFragmentSchema) + .transform((object) => { + return Object.fromEntries( + Object.keys(blankSave.talismans).map((key) => { + const value = object[key] ?? noTalismanFragments + return value === null ? [key, noTalismanFragments] : [key, value] + }) + ) + }) + .default(() => ({ ...blankSave.talismans })), + talismanLevels: z.union([ z.number().array(), arrayStartingWithNull(z.number()).transform((array) => array.slice(1)) - ]) - .default(() => [...blankSave.talismanLevels]), + ]).optional(), talismanRarity: z.union([ z.number().array(), arrayStartingWithNull(z.number()).transform((array) => array.slice(1)) - ]) - .default(() => [...blankSave.talismanRarity]), - talismanOne: arrayStartingWithNull(z.number()).default(() => blankSave.talismanOne), - talismanTwo: arrayStartingWithNull(z.number()).default(() => blankSave.talismanTwo), - talismanThree: arrayStartingWithNull(z.number()).default(() => blankSave.talismanThree), - talismanFour: arrayStartingWithNull(z.number()).default(() => blankSave.talismanFour), - talismanFive: arrayStartingWithNull(z.number()).default(() => blankSave.talismanFive), - talismanSix: arrayStartingWithNull(z.number()).default(() => blankSave.talismanSix), - talismanSeven: arrayStartingWithNull(z.number()).default(() => blankSave.talismanSeven), - talismanShards: z.number().default(() => blankSave.talismanShards), - commonFragments: z.number().default(() => blankSave.commonFragments), - uncommonFragments: z.number().default(() => blankSave.uncommonFragments), - rareFragments: z.number().default(() => blankSave.rareFragments), - epicFragments: z.number().default(() => blankSave.epicFragments), - legendaryFragments: z.number().default(() => blankSave.legendaryFragments), - mythicalFragments: z.number().default(() => blankSave.mythicalFragments), + ]).optional(), + talismanOne: arrayStartingWithNull(z.number()).optional(), + talismanTwo: arrayStartingWithNull(z.number()).optional(), + talismanThree: arrayStartingWithNull(z.number()).optional(), + talismanFour: arrayStartingWithNull(z.number()).optional(), + talismanFive: arrayStartingWithNull(z.number()).optional(), + talismanSix: arrayStartingWithNull(z.number()).optional(), + talismanSeven: arrayStartingWithNull(z.number()).optional(), + talismanShards: decimalSchema.default(() => blankSave.talismanShards), + commonFragments: decimalSchema.default(() => blankSave.commonFragments), + uncommonFragments: decimalSchema.default(() => blankSave.uncommonFragments), + rareFragments: decimalSchema.default(() => blankSave.rareFragments), + epicFragments: decimalSchema.default(() => blankSave.epicFragments), + legendaryFragments: decimalSchema.default(() => blankSave.legendaryFragments), + mythicalFragments: decimalSchema.default(() => blankSave.mythicalFragments), buyTalismanShardPercent: z.number().default(() => blankSave.buyTalismanShardPercent), @@ -522,33 +632,52 @@ export const playerSchema = z.object({ wowAbyssals: z.number().default(() => blankSave.wowAbyssals), wowOcteracts: z.number().default(() => blankSave.wowOcteracts), totalWowOcteracts: z.number().default(() => blankSave.totalWowOcteracts), - cubeBlessings: z.record(z.string(), z.number()).default(() => ({ ...blankSave.cubeBlessings })), + cubeBlessings: z.record(z.string(), z.number()).transform((obj) => { + const sum = sumContents(Object.values(obj)) + if (!isFinite(sum) || sum > 1e300) { + const obj: typeof blankSave.cubeBlessings = { + accelerator: 2e299, + multiplier: 2e299, + offering: 1e299, + runeExp: 1e299, + obtainium: 1e299, + antSpeed: 1e299, + antSacrifice: 5e298, + antELO: 5e298, + talismanBonus: 5e298, + globalSpeed: 5e298 + } + return obj + } + return obj + }).default(() => ({ ...blankSave.cubeBlessings })), tesseractBlessings: z.record(z.string(), z.number()).default(() => ({ ...blankSave.tesseractBlessings })), hypercubeBlessings: z.record(z.string(), z.number()).default(() => ({ ...blankSave.hypercubeBlessings })), platonicBlessings: z.record(z.string(), z.number()).default(() => ({ ...blankSave.platonicBlessings })), + hepteracts: z.object({ + chronos: newHepteractCraftSchema.default(() => blankSave.hepteracts.chronos), + hyperrealism: newHepteractCraftSchema.default(() => blankSave.hepteracts.hyperrealism), + quark: newHepteractCraftSchema.default(() => blankSave.hepteracts.quark), + challenge: newHepteractCraftSchema.default(() => blankSave.hepteracts.challenge), + abyss: newHepteractCraftSchema.default(() => blankSave.hepteracts.abyss), + accelerator: newHepteractCraftSchema.default(() => blankSave.hepteracts.accelerator), + acceleratorBoost: newHepteractCraftSchema.default(() => blankSave.hepteracts.acceleratorBoost), + multiplier: newHepteractCraftSchema.default(() => blankSave.hepteracts.multiplier) + }).default(() => { + return { ...blankSave.hepteracts } + }), + hepteractCrafts: z.object({ - chronos: hepteractCraftSchema('chronos'), - hyperrealism: hepteractCraftSchema('hyperrealism'), - quark: hepteractCraftSchema('quark'), - challenge: hepteractCraftSchema('challenge'), - abyss: hepteractCraftSchema('abyss'), - accelerator: hepteractCraftSchema('accelerator'), - acceleratorBoost: hepteractCraftSchema('acceleratorBoost'), - multiplier: hepteractCraftSchema('multiplier') - }).transform((crafts) => { - return Object.fromEntries( - Object.entries(blankSave.hepteractCrafts).map(([key, value]) => { - return [ - key, - new HepteractCraft({ - ...value, - ...crafts[key as keyof typeof crafts] - }) - ] - }) - ) - }).default(() => blankSave.hepteractCrafts), + chronos: hepteractCraftSchema('chronos').optional(), + hyperrealism: hepteractCraftSchema('hyperrealism').optional(), + quark: hepteractCraftSchema('quark').optional(), + challenge: hepteractCraftSchema('challenge').optional(), + abyss: hepteractCraftSchema('abyss').optional(), + accelerator: hepteractCraftSchema('accelerator').optional(), + acceleratorBoost: hepteractCraftSchema('acceleratorBoost').optional(), + multiplier: hepteractCraftSchema('multiplier').optional() + }).optional(), ascendShards: decimalSchema.default(() => deepClone()(blankSave.ascendShards)), autoAscend: z.boolean().default(() => blankSave.autoAscend), @@ -595,8 +724,8 @@ export const playerSchema = z.object({ autoChallengeStartExponent: z.number().default(() => blankSave.autoChallengeStartExponent), autoChallengeTimer: z.record(z.string(), z.number()).default(() => ({ ...blankSave.autoChallengeTimer })), - runeBlessingLevels: z.number().array().default(() => [...blankSave.runeBlessingLevels]), - runeSpiritLevels: z.number().array().default(() => [...blankSave.runeSpiritLevels]), + runeBlessingLevels: z.number().array().optional(), + runeSpiritLevels: z.number().array().optional(), runeBlessingBuyAmount: z.number().default(() => blankSave.runeBlessingBuyAmount), runeSpiritBuyAmount: z.number().default(() => blankSave.runeSpiritBuyAmount), @@ -653,62 +782,40 @@ export const playerSchema = z.object({ iconSet: z.number().default(() => blankSave.iconSet), notation: z.string().default(() => blankSave.notation), - // TODO: why is this on player? - singularityUpgrades: z.record(z.string(), singularityUpgradeSchema('goldenQuarksInvested')) - .transform((upgrades) => - Object.fromEntries( - Object.keys(singularityData).filter((k) => k in upgrades || k in blankSave.singularityUpgrades).map((k) => { - const { level, goldenQuarksInvested, toggleBuy, freeLevels } = upgrades[k] - ?? blankSave.singularityUpgrades[k as keyof typeof blankSave['singularityUpgrades']] + goldenQuarkUpgrades: z.record(z.string(), goldenQuarkUpgradeSchema).transform((object) => { + return Object.fromEntries( + Object.keys(blankSave.goldenQuarkUpgrades).map((key) => { + const value = object[key] ?? { level: 0, freeLevel: 0, goldenQuarksInvested: 0 } + return value === null ? [key, { level: 0, freeLevel: 0, goldenQuarksInvested: 0 }] : [key, value] + }) + ) + }) + .default(() => ({ ...blankSave.goldenQuarkUpgrades })), - return [ - k, - new SingularityUpgrade({ - maxLevel: singularityData[k].maxLevel, - costPerLevel: singularityData[k].costPerLevel, - - level: level as number, - goldenQuarksInvested, - toggleBuy: toggleBuy as number, - freeLevels: freeLevels as number, - minimumSingularity: singularityData[k].minimumSingularity, - effect: singularityData[k].effect, - canExceedCap: singularityData[k].canExceedCap, - specialCostForm: singularityData[k].specialCostForm, - qualityOfLife: singularityData[k].qualityOfLife, - cacheUpdates: singularityData[k].cacheUpdates - }, k) - ] - }) - ) + octUpgrades: z.record(z.string(), octeractUpgradeSchema).transform((object) => { + // We use the same goldenQuarkUpgradeSchema for multiple things. maybe it should be called + // something different. Oh well... this can be changed later. -Plat + return Object.fromEntries( + Object.keys(blankSave.octUpgrades).map((key) => { + const value = object[key] ?? { level: 0, freeLevel: 0, octeractsInvested: 0 } + return value === null ? [key, { level: 0, freeLevel: 0, octeractsInvested: 0 }] : [key, value] + }) ) - .default(() => JSON.parse(JSON.stringify(blankSave.singularityUpgrades))), - octeractUpgrades: z.record(z.string(), singularityUpgradeSchema('octeractsInvested')) - .transform((upgrades) => - Object.fromEntries( - Object.keys(octeractData).map((k) => { - const { level, octeractsInvested, toggleBuy, freeLevels } = upgrades[k] - ?? blankSave.octeractUpgrades[k as keyof typeof blankSave['octeractUpgrades']] + }) + .default(() => ({ ...blankSave.octUpgrades })), - return [ - k, - new OcteractUpgrade({ - maxLevel: octeractData[k].maxLevel, - costPerLevel: octeractData[k].costPerLevel, - level: level as number, - octeractsInvested, - toggleBuy: toggleBuy as number, - effect: octeractData[k].effect, - costFormula: octeractData[k].costFormula, - freeLevels: freeLevels as number, - qualityOfLife: octeractData[k].qualityOfLife, - cacheUpdates: octeractData[k].cacheUpdates - }, k) - ] - }) - ) + ambrosiaUpgrades: z.record(z.string(), ambrosiaUpgradeSchema).transform((object) => { + return Object.fromEntries( + Object.keys(blankSave.ambrosiaUpgrades).map((key) => { + const value = object[key] ?? { ambrosiaInvested: 0, blueberriesInvested: 0 } + return value === null ? [key, { ambrosiaInvested: 0, blueberriesInvested: 0 }] : [key, value] + }) ) - .default(() => JSON.parse(JSON.stringify(blankSave.octeractUpgrades))), + }) + .default(() => ({ ...blankSave.ambrosiaUpgrades })), + + singularityUpgrades: z.record(z.string(), singularityUpgradeSchema('goldenQuarksInvested')).optional(), + octeractUpgrades: z.record(z.string(), singularityUpgradeSchema('octeractsInvested')).optional(), dailyCodeUsed: z.boolean().default(() => blankSave.dailyCodeUsed), hepteractAutoCraftPercentage: z.number().default(() => blankSave.hepteractAutoCraftPercentage), @@ -725,7 +832,8 @@ export const playerSchema = z.object({ ) .transform((upgrades) => Object.fromEntries( - Object.keys(blankSave.singularityChallenges).map((k) => { + Object.keys(blankSave.singularityChallenges).map((key) => { + const k = key as SingularityChallengeDataKeys const { completions, highestSingularityCompleted, enabled } = upgrades[k] ?? blankSave.singularityChallenges[k] @@ -735,6 +843,7 @@ export const playerSchema = z.object({ baseReq: singularityChallengeData[k].baseReq, completions, maxCompletions: singularityChallengeData[k].maxCompletions, + achievementPointValue: singularityChallengeData[k].achievementPointValue, unlockSingularity: singularityChallengeData[k].unlockSingularity, HTMLTag: singularityChallengeData[k].HTMLTag, highestSingularityCompleted, @@ -744,7 +853,7 @@ export const playerSchema = z.object({ scalingrewardcount: singularityChallengeData[k].scalingrewardcount, uniquerewardcount: singularityChallengeData[k].uniquerewardcount, effect: singularityChallengeData[k].effect, - cacheUpdates: singularityChallengeData[k].cacheUpdates + alternateDescription: singularityChallengeData[k].alternateDescription }, k) ] }) @@ -761,35 +870,7 @@ export const playerSchema = z.object({ spentBlueberries: z.number().default(() => blankSave.spentBlueberries), // TODO: is this right? blueberryUpgrades: z.record(z.string(), singularityUpgradeSchema('blueberriesInvested', 'ambrosiaInvested')) - .transform((upgrades) => - Object.fromEntries( - Object.keys(blankSave.blueberryUpgrades).map((k) => { - const { level, ambrosiaInvested, blueberriesInvested, toggleBuy, freeLevels } = upgrades[k] - ?? blankSave.blueberryUpgrades[k] - - return [ - k, - new BlueberryUpgrade({ - maxLevel: blueberryUpgradeData[k].maxLevel, - costPerLevel: blueberryUpgradeData[k].costPerLevel, - level: level as number, - ambrosiaInvested, - blueberriesInvested, - toggleBuy: toggleBuy as number, - blueberryCost: blueberryUpgradeData[k].blueberryCost, - rewards: blueberryUpgradeData[k].rewards, - costFormula: blueberryUpgradeData[k].costFormula, - extraLevelCalc: blueberryUpgradeData[k].extraLevelCalc, - freeLevels: freeLevels as number, - prerequisites: blueberryUpgradeData[k].prerequisites, - cacheUpdates: blueberryUpgradeData[k].cacheUpdates, - ignoreEXALT: blueberryUpgradeData[k].ignoreEXALT - }, k) - ] - }) - ) - ) - .default(() => JSON.parse(JSON.stringify(blankSave.blueberryUpgrades))), + .optional(), // TODO: what type? blueberryLoadouts: z.record(integerStringSchema, z.any()).default(() => blankSave.blueberryLoadouts), diff --git a/src/saves/PlayerUpdateVarSchema.ts b/src/saves/PlayerUpdateVarSchema.ts index bf8966e2c..89f3db663 100644 --- a/src/saves/PlayerUpdateVarSchema.ts +++ b/src/saves/PlayerUpdateVarSchema.ts @@ -1,4 +1,10 @@ +import Decimal from 'break_infinity.js' +import { type AmbrosiaUpgradeNames, ambrosiaUpgrades } from '../BlueberryUpgrades' import { CorruptionLoadout, type Corruptions, CorruptionSaves } from '../Corruptions' +import { type HepteractKeys, hepteracts } from '../Hepteracts' +import { type OcteractDataKeys, octeractUpgrades } from '../Octeracts' +import { goldenQuarkUpgrades, type SingularityDataKeys } from '../singularity' +import { updateResourcePredefinedLevel } from '../Talismans' import { convertArrayToCorruption } from './PlayerJsonSchema' import { playerSchema } from './PlayerSchema' @@ -41,6 +47,160 @@ export const playerUpdateVarSchema = playerSchema.transform((player) => { player.lifetimeRedAmbrosia += Math.floor(ultimatePixels * 0.2 + redBarFilled) } + if (player.talismanLevels !== undefined) { + updateResourcePredefinedLevel(player.talismanLevels[0], 'exemption') + updateResourcePredefinedLevel(player.talismanLevels[1], 'chronos') + updateResourcePredefinedLevel(player.talismanLevels[2], 'midas') + updateResourcePredefinedLevel(player.talismanLevels[3], 'metaphysics') + updateResourcePredefinedLevel(player.talismanLevels[4], 'polymath') + updateResourcePredefinedLevel(player.talismanLevels[5], 'mortuus') + updateResourcePredefinedLevel(player.talismanLevels[6], 'plastic') + } + + if (player.runeexp !== undefined) { + player.runes.speed = new Decimal(player.runeexp[0] ?? 0) + player.runes.duplication = new Decimal(player.runeexp[1] ?? 0) + player.runes.prism = new Decimal(player.runeexp[2] ?? 0) + player.runes.thrift = new Decimal(player.runeexp[3] ?? 0) + player.runes.superiorIntellect = new Decimal(player.runeexp[4] ?? 0) + player.runes.infiniteAscent = new Decimal(player.runeexp[5] ?? 0) + player.runes.antiquities = new Decimal(player.runeexp[6] ?? 0) + + // Retroactively reward players with unlocks, because I don't have a better place here + player.unlocks.anthill = player.achievements[127] === 1 || (player.highestchallengecompletions[8] ?? 0) > 0 + player.unlocks.talismans = player.achievements[134] === 1 || (player.highestchallengecompletions[9] ?? 0) > 0 + player.unlocks.blessings = player.achievements[134] === 1 || (player.highestchallengecompletions[9] ?? 0) > 0 + player.unlocks.ascensions = player.achievements[141] === 1 || (player.highestchallengecompletions[10] ?? 0) > 0 + player.unlocks.tesseracts = player.achievements[197] === 1 || (player.highestchallengecompletions[11] ?? 0) > 0 + player.unlocks.spirits = player.achievements[204] === 1 || (player.highestchallengecompletions[12] ?? 0) > 0 + player.unlocks.hypercubes = player.achievements[211] === 1 || (player.highestchallengecompletions[13] ?? 0) > 0 + player.unlocks.platonics = player.achievements[218] === 1 || (player.highestchallengecompletions[14] ?? 0) > 0 + player.unlocks.hepteracts = player.challenge15Exponent >= 1e15 + } + + if (player.runeshards !== undefined) { + player.offerings = new Decimal(player.runeshards) + } + + if (player.maxofferings !== undefined) { + player.maxOfferings = new Decimal(player.maxofferings) + } + + if (player.researchPoints !== undefined) { + player.obtainium = new Decimal(player.researchPoints) + } + + if (player.maxobtainium !== undefined) { + player.maxObtainium = new Decimal(player.maxobtainium) + } + + if (player.runeBlessingLevels !== undefined) { + player.runeBlessings.speed = new Decimal(Math.pow(Math.min(1e140, player.runeBlessingLevels[1] ?? 0), 2) * 1e8 / 2) + player.runeBlessings.duplication = new Decimal( + Math.pow(Math.min(1e140, player.runeBlessingLevels[2] ?? 0), 2) * 1e8 / 2 + ) + player.runeBlessings.prism = new Decimal(Math.pow(Math.min(1e140, player.runeBlessingLevels[3] ?? 0), 2) * 1e8 / 2) + player.runeBlessings.thrift = new Decimal(Math.pow(Math.min(1e140, player.runeBlessingLevels[4] ?? 0), 2) * 1e8 / 2) + player.runeBlessings.superiorIntellect = new Decimal( + Math.pow(Math.min(1e140, player.runeBlessingLevels[5] ?? 0), 2) * 1e8 / 2 + ) + } + + if (player.runeSpiritLevels !== undefined) { + player.runeSpirits.speed = new Decimal(Math.pow(Math.min(1e140, player.runeSpiritLevels[1] ?? 0), 2) * 1e20 / 2) + player.runeSpirits.duplication = new Decimal( + Math.pow(Math.min(1e140, player.runeSpiritLevels[2] ?? 0), 2) * 1e20 / 2 + ) + player.runeSpirits.prism = new Decimal(Math.pow(Math.min(1e140, player.runeSpiritLevels[3] ?? 0), 2) * 1e20 / 2) + player.runeSpirits.thrift = new Decimal(Math.pow(Math.min(1e140, player.runeSpiritLevels[4] ?? 0), 2) * 1e20 / 2) + player.runeSpirits.superiorIntellect = new Decimal( + Math.pow(Math.min(1e140, player.runeSpiritLevels[5] ?? 0), 2) * 1e20 / 2 + ) + } + + if (player.hepteractCrafts !== undefined) { + for (const [key, value] of Object.entries(player.hepteractCrafts)) { + const k = key as HepteractKeys + if (value !== undefined) { + const BAL = value.BAL ?? 0 + const TIMES_CAP_EXTENDED = Math.round(Math.log2(value.CAP / value.BASE_CAP)) ?? 0 + const AUTO = value.AUTO ?? false + + player.hepteracts[k] = { BAL, TIMES_CAP_EXTENDED, AUTO } + hepteracts[k].BAL = BAL + hepteracts[k].TIMES_CAP_EXTENDED = TIMES_CAP_EXTENDED + hepteracts[k].AUTO = AUTO + } + } + } + + if (player.singularityUpgrades !== undefined) { + for (const key of Object.keys(player.singularityUpgrades)) { + // This is shit - the old SingularityUpgrades object had this upgrade that didn't do anything + if (key === 'WIP') { + continue + } + + const k = key as SingularityDataKeys + + const level = player.singularityUpgrades[k].level ?? 0 + const freeLevel = player.singularityUpgrades[k].freeLevels ?? 0 + const goldenQuarksInvested = player.singularityUpgrades[k].goldenQuarksInvested ?? 0 + + player.goldenQuarkUpgrades[k] = { + level, + freeLevel, + goldenQuarksInvested + } + goldenQuarkUpgrades[k].level = level + goldenQuarkUpgrades[k].freeLevel = freeLevel + goldenQuarkUpgrades[k].goldenQuarksInvested = goldenQuarksInvested + } + } + + if (player.octeractUpgrades !== undefined) { + for (const key of Object.keys(player.octeractUpgrades)) { + const k = key as OcteractDataKeys + + const level = player.octeractUpgrades[k].level ?? 0 + const freeLevel = player.octeractUpgrades[k].freeLevels ?? 0 + const octeractsInvested = player.octeractUpgrades[k].octeractsInvested ?? 0 + + player.octUpgrades[k] = { + level, + freeLevel, + octeractsInvested + } + octeractUpgrades[k].level = level + octeractUpgrades[k].freeLevel = level + octeractUpgrades[k].octeractsInvested = octeractsInvested + } + } + + if (player.blueberryUpgrades !== undefined) { + for (const key of Object.keys(player.blueberryUpgrades)) { + const k = key as AmbrosiaUpgradeNames + + const ambrosiaInvested = player.blueberryUpgrades[k].ambrosiaInvested ?? 0 + const blueberriesInvested = player.blueberryUpgrades[k].blueberriesInvested ?? 0 + + player.ambrosiaUpgrades[k] = { + ambrosiaInvested, + blueberriesInvested + } + ambrosiaUpgrades[k].ambrosiaInvested = ambrosiaInvested + ambrosiaUpgrades[k].blueberriesInvested = blueberriesInvested + } + } + + Reflect.deleteProperty(player, 'runeshards') + Reflect.deleteProperty(player, 'maxofferings') + Reflect.deleteProperty(player, 'researchPoints') + Reflect.deleteProperty(player, 'maxobtainium') + Reflect.deleteProperty(player, 'obtainiumpersecond') + Reflect.deleteProperty(player, 'maxobtainiumpersecond') + Reflect.deleteProperty(player, 'runeexp') + Reflect.deleteProperty(player, 'runelevels') Reflect.deleteProperty(player, 'usedCorruptions') Reflect.deleteProperty(player, 'prototypeCorruptions') Reflect.deleteProperty(player, 'corruptionShowStats') @@ -48,6 +208,22 @@ export const playerUpdateVarSchema = playerSchema.transform((player) => { Reflect.deleteProperty(player, 'corruptionLoadoutNames') Reflect.deleteProperty(player, 'ultimatePixels') Reflect.deleteProperty(player, 'cubeUpgradeRedBarFilled') + Reflect.deleteProperty(player, 'talismanLevels') + Reflect.deleteProperty(player, 'talismanRarity') + Reflect.deleteProperty(player, 'talismanOne') + Reflect.deleteProperty(player, 'talismanTwo') + Reflect.deleteProperty(player, 'talismanThree') + Reflect.deleteProperty(player, 'talismanFour') + Reflect.deleteProperty(player, 'talismanFive') + Reflect.deleteProperty(player, 'talismanSix') + Reflect.deleteProperty(player, 'talismanSeven') + Reflect.deleteProperty(player, 'offeringpersecond') + Reflect.deleteProperty(player, 'runeBlessingLevels') + Reflect.deleteProperty(player, 'runeSpiritLevels') + Reflect.deleteProperty(player, 'hepteractCrafts') + Reflect.deleteProperty(player, 'singularityUpgrades') + Reflect.deleteProperty(player, 'octeractUpgrades') + Reflect.deleteProperty(player, 'blueberryUpgrades') return player }) diff --git a/src/singularity.ts b/src/singularity.ts index 4e1f5a6cd..55fd58364 100644 --- a/src/singularity.ts +++ b/src/singularity.ts @@ -1,12 +1,94 @@ import i18next from 'i18next' +import { getAmbrosiaUpgradeEffects } from './BlueberryUpgrades' import { DOMCacheGetOrSet } from './Cache/DOM' -import { campaignTokenRewardHTMLUpdate } from './Campaign' -import type { IUpgradeData } from './DynamicUpgrade' -import { DynamicUpgrade } from './DynamicUpgrade' -import { format, player } from './Synergism' -import type { Player } from './types/Synergism' +import { calculateGoldenQuarkCost } from './Calculate' +import { updateMaxTokens, updateTokens } from './Campaign' +import { getOcteractUpgradeEffect } from './Octeracts' +import { getRuneEffectiveLevel, runes } from './Runes' +import { format, formatAsPercentIncrease, player } from './Synergism' import { Alert, Prompt, revealStuff } from './UpdateHTML' -import { sumContents, toOrdinal } from './Utility' +import { isMobile, toOrdinal } from './Utility' + +export type SingularityDataKeys = + | 'goldenQuarks1' + | 'goldenQuarks2' + | 'goldenQuarks3' + | 'starterPack' + | 'wowPass' + | 'cookies' + | 'cookies2' + | 'cookies3' + | 'cookies4' + | 'cookies5' + | 'ascensions' + | 'corruptionFourteen' + | 'corruptionFifteen' + | 'singOfferings1' + | 'singOfferings2' + | 'singOfferings3' + | 'singObtainium1' + | 'singObtainium2' + | 'singObtainium3' + | 'singCubes1' + | 'singCubes2' + | 'singCubes3' + | 'singCitadel' + | 'singCitadel2' + | 'octeractUnlock' + | 'singOcteractPatreonBonus' + | 'offeringAutomatic' + | 'intermediatePack' + | 'advancedPack' + | 'expertPack' + | 'masterPack' + | 'divinePack' + | 'wowPass2' + | 'wowPass3' + | 'potionBuff' + | 'potionBuff2' + | 'potionBuff3' + | 'singChallengeExtension' + | 'singChallengeExtension2' + | 'singChallengeExtension3' + | 'singQuarkImprover1' + | 'singQuarkHepteract' + | 'singQuarkHepteract2' + | 'singQuarkHepteract3' + | 'singOcteractGain' + | 'singOcteractGain2' + | 'singOcteractGain3' + | 'singOcteractGain4' + | 'singOcteractGain5' + | 'platonicTau' + | 'platonicAlpha' + | 'platonicDelta' + | 'platonicPhi' + | 'singFastForward' + | 'singFastForward2' + | 'singAscensionSpeed' + | 'singAscensionSpeed2' + | 'ultimatePen' + | 'halfMind' + | 'oneMind' + | 'wowPass4' + | 'blueberries' + | 'singAmbrosiaLuck' + | 'singAmbrosiaLuck2' + | 'singAmbrosiaLuck3' + | 'singAmbrosiaLuck4' + | 'singAmbrosiaGeneration' + | 'singAmbrosiaGeneration2' + | 'singAmbrosiaGeneration3' + | 'singAmbrosiaGeneration4' + | 'singBonusTokens1' + | 'singBonusTokens2' + | 'singBonusTokens3' + | 'singBonusTokens4' + | 'singInfiniteShopUpgrades' + | 'singTalismanBonusRunes1' + | 'singTalismanBonusRunes2' + | 'singTalismanBonusRunes3' + | 'singTalismanBonusRunes4' export const updateSingularityPenalties = (): void => { const singularityCount = player.singularityCount @@ -57,6 +139,15 @@ export const updateSingularityPenalties = (): void => { true ) }) + } + ${ + i18next.t('singularity.penalties.salvage', { + amount: format( + -calculateSingularityDebuff('Salvage', singularityCount), + 0, + true + ) + }) } ${ i18next.t('singularity.penalties.obtainiumGain', { @@ -108,7 +199,7 @@ export const updateSingularityPenalties = (): void => { }) } ${ - player.runelevels[6] > 0 + runes.antiquities.level > 0 ? i18next.t('singularity.penalties.antiquitiesBought') : i18next.t('singularity.penalties.antiquitiesNotBought') }` @@ -122,1564 +213,2045 @@ function getSingularityOridnalText (singularityCount: number): string { }) } -// Need a better way of handling the ones without a special formulae than 'Default' variant type SingularitySpecialCostFormulae = | 'Default' | 'Quadratic' | 'Cubic' | 'Exponential2' -export interface ISingularityData extends Omit { - goldenQuarksInvested?: number - minimumSingularity?: number - canExceedCap?: boolean - specialCostForm?: SingularitySpecialCostFormulae - qualityOfLife?: boolean - cacheUpdates?: (() => void)[] // TODO: Improve this type signature -Plat -} - -/** - * Singularity Upgrades are bought in the Shop of the singularity tab, and all have their own - * name, description, level and maxlevel, plus a feature to toggle buy on each. - */ -export class SingularityUpgrade extends DynamicUpgrade { - // Field Initialization - public goldenQuarksInvested = 0 - public minimumSingularity: number - public canExceedCap: boolean - public specialCostForm: SingularitySpecialCostFormulae - public qualityOfLife: boolean - readonly cacheUpdates: (() => void)[] | undefined - #key: string - - public constructor (data: ISingularityData, key: string) { - const name = i18next.t(`singularity.data.${key}.name`) - const description = i18next.t(`singularity.data.${key}.description`) - - super({ ...data, name, description }) - this.goldenQuarksInvested = data.goldenQuarksInvested ?? 0 - this.minimumSingularity = data.minimumSingularity ?? 0 - this.canExceedCap = data.canExceedCap ?? false - this.specialCostForm = data.specialCostForm ?? 'Default' - this.qualityOfLife = data.qualityOfLife ?? false - this.cacheUpdates = data.cacheUpdates ?? undefined - this.#key = key - } - - /** - * Given an upgrade, give a concise information regarding its data. - * @returns A string that details the name, description, level statistic, and next level cost. - */ - toString (): string { - const costNextLevel = this.getCostTNL() - const maxLevel = this.maxLevel === -1 ? '' : `/${format(this.computeMaxLevel(), 0, true)}` - const color = this.computeMaxLevel() === this.level ? 'plum' : 'white' - const minReqColor = player.highestSingularityCount < this.minimumSingularity - ? 'var(--crimson-text-color)' - : 'var(--green-text-color)' - const minimumSingularity = this.minimumSingularity > 0 - ? `${i18next.t('general.minimum')} Singularity: ${this.minimumSingularity}` - : i18next.t('singularity.toString.noMinimum') - - let freeLevelInfo = this.freeLevels > 0 - ? ` [+${ - format( - this.freeLevels, - 2, - true - ) - }]` - : '' - - if (this.freeLevels > this.level) { - freeLevelInfo = `${freeLevelInfo} ${ - i18next.t( - 'general.softCapped' - ) - }` - } - - const costNextLevelStr = i18next.t('singularity.toString.costNextLevel', { - amount: format( - costNextLevel, - 0, - true - ) - }) - - const spentQuarksStr = i18next.t('singularity.toString.spentQuarks', { - amount: format( - this.goldenQuarksInvested, - 0, - true - ) - }) - - return `${this.name} - ${this.description} - ${minimumSingularity} - ${ - i18next.t( - 'general.level' - ) - } ${format(this.level, 0, true)}${maxLevel}${freeLevelInfo} - ${this.getEffect().desc} - ${costNextLevelStr} - ${spentQuarksStr}` - } - - public updateUpgradeHTML (): void { - DOMCacheGetOrSet('testingMultiline').innerHTML = this.toString() - } - - /** - * Retrieves the cost for upgrading the singularity upgrade once. Return 0 if maxed. - * @returns A number representing how many Golden Quarks a player must have to upgrade once. - */ - getCostTNL (): number { - let costMultiplier = 1 - if (this.computeMaxLevel() === this.level) { - return 0 - } - - // Overcap - if (this.computeMaxLevel() > this.maxLevel && this.level >= this.maxLevel) { - costMultiplier *= Math.pow(4, this.level - this.maxLevel + 1) - } - - if (this.specialCostForm === 'Exponential2') { - return ( - this.costPerLevel * Math.sqrt(costMultiplier) * Math.pow(2, this.level) - ) - } - - if (this.specialCostForm === 'Cubic') { - return ( - this.costPerLevel - * costMultiplier - * (Math.pow(this.level + 1, 3) - Math.pow(this.level, 3)) - ) - } - - if (this.specialCostForm === 'Quadratic') { - return ( - this.costPerLevel - * costMultiplier - * (Math.pow(this.level + 1, 2) - Math.pow(this.level, 2)) - ) - } - - costMultiplier *= this.maxLevel === -1 && this.level >= 100 ? this.level / 50 : 1 - costMultiplier *= this.maxLevel === -1 && this.level >= 400 ? this.level / 100 : 1 - - return this.computeMaxLevel() === this.level - ? 0 - : Math.ceil(this.costPerLevel * (1 + this.level) * costMultiplier) - } - - /** - * Buy levels up until togglebuy or maxed. - * @returns An alert indicating cannot afford, already maxed or purchased with how many - * levels purchased - */ - public async buyLevel (event: MouseEvent): Promise { - let purchased = 0 - let maxPurchasable = 1 - let GQBudget = player.goldenQuarks - - if (event.shiftKey) { - maxPurchasable = 100000 - const buy = Number( - await Prompt( - i18next.t('singularity.goldenQuarks.spendPrompt', { - gq: format(player.goldenQuarks, 0, true) - }) - ) - ) - - if (isNaN(buy) || !isFinite(buy) || !Number.isInteger(buy)) { - // nan + Infinity checks - return Alert(i18next.t('general.validation.finite')) - } - - if (buy === -1) { - GQBudget = player.goldenQuarks - } else if (buy <= 0) { - return Alert(i18next.t('general.validation.zeroOrLess')) - } else { - GQBudget = buy - } - GQBudget = Math.min(player.goldenQuarks, GQBudget) - } - - if (this.maxLevel > 0) { - maxPurchasable = Math.min( - maxPurchasable, - this.computeMaxLevel() - this.level - ) - } - - if (maxPurchasable === 0) { - return Alert(i18next.t('singularity.goldenQuarks.hasUpgrade')) - } - - if (player.highestSingularityCount < this.minimumSingularity) { - return Alert(i18next.t('singularity.goldenQuarks.notHighEnoughLevel')) - } - while (maxPurchasable > 0) { - const cost = this.getCostTNL() - if (player.goldenQuarks < cost || GQBudget < cost) { - break - } else { - player.goldenQuarks -= cost - GQBudget -= cost - this.goldenQuarksInvested += cost - this.level += 1 - purchased += 1 - maxPurchasable -= 1 - } - if (this.name === player.singularityUpgrades.oneMind.name) { - player.ascensionCounter = 0 - player.ascensionCounterReal = 0 - player.ascensionCounterRealReal = 0 - void Alert(i18next.t('singularity.goldenQuarks.ascensionReset')) - } - - if (this.name === player.singularityUpgrades.singCitadel2.name) { - player.singularityUpgrades.singCitadel.freeLevels = player.singularityUpgrades.singCitadel2.level - } - } - - if (purchased === 0) { - return Alert(i18next.t('general.validation.moreThanPlayerHas')) - } - if (purchased > 1) { - void Alert( - i18next.t('singularity.goldenQuarks.multiBuyPurchased', { - levels: format(purchased) - }) - ) - } - - this.updateUpgradeHTML() - this.updateCaches() - updateSingularityPenalties() - updateSingularityPerks() - revealStuff() - } - - public computeFreeLevelSoftcap (): number { - let freeLevelMult = (player.shopUpgrades.shopSingularityPotency > 0) ? 3.66 : 1 - freeLevelMult += 0.3 / 100 * player.cubeUpgrades[75] - const baseRealFreeLevels = freeLevelMult * this.freeLevels - return ( - Math.min(this.level, baseRealFreeLevels) - + Math.sqrt(Math.max(0, baseRealFreeLevels - this.level)) - ) - } - - public computeMaxLevel (): number { - if (!this.canExceedCap) { - return this.maxLevel - } else { - let cap = this.maxLevel - const overclockPerks = [50, 60, 75, 100, 125, 150, 175, 200, 225, 250] - for (const perk of overclockPerks) { - if (player.highestSingularityCount >= perk) { - cap += 1 - } else { - break - } - } - cap += +player.octeractUpgrades.octeractSingUpgradeCap.getEffect().bonus - return cap - } - } - - public actualTotalLevels (): number { - if ( - (player.singularityChallenges.noSingularityUpgrades.enabled - || player.singularityChallenges.sadisticPrequel.enabled) - && !this.qualityOfLife - ) { - return 0 - } - - if ( - (player.singularityChallenges.limitedAscensions.enabled || player.singularityChallenges.limitedTime.enabled - || player.singularityChallenges.sadisticPrequel.enabled) - && this.name === player.singularityUpgrades.platonicDelta.name - ) { - return 0 - } - - const actualFreeLevels = this.computeFreeLevelSoftcap() - const linearLevels = this.level + actualFreeLevels - let polynomialLevels = 0 - if (player.octeractUpgrades.octeractImprovedFree.getEffect().bonus) { - let exponent = 0.6 - exponent += +player.octeractUpgrades.octeractImprovedFree2.getEffect().bonus - exponent += +player.octeractUpgrades.octeractImprovedFree3.getEffect().bonus - exponent += +player.octeractUpgrades.octeractImprovedFree4.getEffect().bonus - polynomialLevels = Math.pow(this.level * actualFreeLevels, exponent) - } - - return Math.max(linearLevels, polynomialLevels) - } - - public getEffect (): { bonus: number | boolean; desc: string } { - return this.effect(this.actualTotalLevels()) - } - - updateCaches (): void { - if (this.cacheUpdates !== undefined) { - for (const cache of this.cacheUpdates) { - cache() - } - } - } - - public refund (): void { - player.goldenQuarks += this.goldenQuarksInvested - this.level = 0 - this.goldenQuarksInvested = 0 - } - - valueOf (): ISingularityData { - return { - costPerLevel: this.costPerLevel, - maxLevel: this.maxLevel, - cacheUpdates: this.cacheUpdates, - canExceedCap: this.canExceedCap, - effect: this.effect, - freeLevels: this.freeLevels, - goldenQuarksInvested: this.goldenQuarksInvested, - level: this.level, - minimumSingularity: this.minimumSingularity, - qualityOfLife: this.qualityOfLife, - specialCostForm: this.specialCostForm, - toggleBuy: this.toggleBuy - } - } - - key () { - return this.#key - } +export interface GoldenQuarkUpgrade { + level: number + freeLevel: number + goldenQuarksInvested: number + maxLevel: number + canExceedCap: boolean + qualityOfLife: boolean + costPerLevel: number + minimumSingularity: number + specialCostForm: SingularitySpecialCostFormulae + effect(n: number): number + effectDescription(n: number): string + name(): string + description(): string } -export const singularityData: Record< - keyof Player['singularityUpgrades'], - ISingularityData -> = { +export const goldenQuarkUpgrades: Record = { goldenQuarks1: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 15, - costPerLevel: 12, canExceedCap: true, + qualityOfLife: true, + costPerLevel: 12, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.1 * n, - get desc () { - return i18next.t('singularity.data.goldenQuarks1.effect', { - n: format(10 * n, 0, true) - }) - } - } + return 1 + 0.1 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.goldenQuarks1.effect', { + n: formatAsPercentIncrease(this.effect(n), 2) + }) + }, + name: () => { + return i18next.t('singularity.data.goldenQuarks1.name') }, - qualityOfLife: true + description: () => { + return i18next.t('singularity.data.goldenQuarks1.description') + } }, goldenQuarks2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 75, - costPerLevel: 60, canExceedCap: true, + qualityOfLife: true, + costPerLevel: 60, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 250 ? 1 / Math.log2(n / 62.5) : 1 - Math.min(0.5, n / 500), - get desc () { - return i18next.t('singularity.data.goldenQuarks2.effect', { - n: n > 250 - ? format(100 - 100 / Math.log2(n / 62.5), 2, true) - : format(Math.min(50, n / 5), 2, true) - }) - } - } + return n > 250 ? 1 / Math.log2(n / 62.5) : 1 - Math.min(0.5, n / 500) + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + // Convert the effect to percentage format like the original + const percentageValue = n > 250 + ? 100 - 100 * effectValue // Since effectValue = 1/Math.log2(n/62.5), this gives us the reduction % + : (1 - effectValue) * 100 // Since effectValue = 1 - reduction, this gives us the reduction % + + return i18next.t('singularity.data.goldenQuarks2.effect', { + n: format(percentageValue, 2, true) + }) + }, + name: () => { + return i18next.t('singularity.data.goldenQuarks2.name') }, - qualityOfLife: true + description: () => { + return i18next.t('singularity.data.goldenQuarks2.description') + } }, goldenQuarks3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1000, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1000, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: (n * (n + 1)) / 2, - get desc () { - return i18next.t('singularity.data.goldenQuarks3.effect', { - n: format((n * (n + 1)) / 2) - }) - } - } + return (n * (n + 1)) / 2 + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.goldenQuarks3.effect', { + n: format(this.effect(n)) + }) + }, + name: () => { + return i18next.t('singularity.data.goldenQuarks3.name') + }, + description: () => { + return i18next.t('singularity.data.goldenQuarks3.description') } }, starterPack: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 10, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.starterPack.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t( + `singularity.data.starterPack.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => { + return i18next.t('singularity.data.starterPack.name') + }, + description: () => i18next.t('singularity.data.starterPack.description') }, wowPass: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 350, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.wowPass.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.wowPass.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.wowPass.name'), + description: () => i18next.t('singularity.data.wowPass.description') }, cookies: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 100, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.cookies.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.cookies.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.cookies.name'), + description: () => i18next.t('singularity.data.cookies.description') }, cookies2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 500, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.cookies2.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.cookies2.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.cookies2.name'), + description: () => i18next.t('singularity.data.cookies2.description') }, cookies3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 24999, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.cookies3.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.cookies3.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.cookies3.name'), + description: () => i18next.t('singularity.data.cookies3.description') }, cookies4: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 499999, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.cookies4.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.cookies4.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.cookies4.name'), + description: () => i18next.t('singularity.data.cookies4.description') }, cookies5: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 1.66e15, minimumSingularity: 209, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.cookies5.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.cookies5.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.cookies5.name'), + description: () => i18next.t('singularity.data.cookies5.description') }, ascensions: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: -1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 5, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: (1 + (2 * n) / 100) * (1 + Math.floor(n / 10) / 100), - get desc () { - return i18next.t('singularity.data.ascensions.effect', { - n: format( - (100 + 2 * n) * (1 + Math.floor(n / 10) / 100) - 100, - 1, - true - ) - }) - } - } - } + return (1 + (2 * n) / 100) * (1 + Math.floor(n / 10) / 100) + }, + effectDescription: function(n: number) { + const effectValue = this.effect(n) + return i18next.t('singularity.data.ascensions.effect', { + n: formatAsPercentIncrease(effectValue, 1) + }) + }, + name: () => i18next.t('singularity.data.ascensions.name'), + description: () => i18next.t('singularity.data.ascensions.description') }, corruptionFourteen: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1000, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.corruptionFourteen.effect${n > 0 ? 'Have' : 'HaveNot'}`, - { - m: n > 0 ? ':)' : ':(' - } - ) + return n + }, + effectDescription: (n: number) => + i18next.t( + `singularity.data.corruptionFourteen.effect${n > 0 ? 'Have' : 'HaveNot'}`, + { + m: n > 0 ? ':)' : ':(' } - } - } + ), + name: () => i18next.t('singularity.data.corruptionFourteen.name'), + description: () => i18next.t('singularity.data.corruptionFourteen.description') }, corruptionFifteen: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 40000, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.corruptionFifteen.effect${n > 0 ? 'Have' : 'HaveNot'}`, - { - m: n > 0 ? ':)' : ':(' - } - ) + return n + }, + effectDescription: (n: number) => + i18next.t( + `singularity.data.corruptionFifteen.effect${n > 0 ? 'Have' : 'HaveNot'}`, + { + m: n > 0 ? ':)' : ':(' } - } - } + ), + name: () => i18next.t('singularity.data.corruptionFifteen.name'), + description: () => i18next.t('singularity.data.corruptionFifteen.description') }, singOfferings1: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: -1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.02 * n, - get desc () { - return i18next.t('singularity.data.singOfferings1.effect', { - n: format(2 * n, 0, true) - }) - } - } - } + return 1 + 0.02 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singOfferings1.effect', { + n: formatAsPercentIncrease(this.effect(n), 2) + }) + }, + name: () => i18next.t('singularity.data.singOfferings1.name'), + description: () => i18next.t('singularity.data.singOfferings1.description') }, singOfferings2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 25, - costPerLevel: 25, canExceedCap: true, + qualityOfLife: false, + costPerLevel: 25, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.08 * n, - get desc () { - return i18next.t('singularity.data.singOfferings2.effect', { - n: format(8 * n, 0, true) - }) - } - } - } + return 1 + 0.08 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singOfferings2.effect', { + n: formatAsPercentIncrease(this.effect(n), 0) + }) + }, + name: () => i18next.t('singularity.data.singOfferings2.name'), + description: () => i18next.t('singularity.data.singOfferings2.description') }, singOfferings3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 40, - costPerLevel: 500, canExceedCap: true, + qualityOfLife: false, + costPerLevel: 500, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.04 * n, - get desc () { - return i18next.t('singularity.data.singOfferings3.effect', { - n: format(4 * n, 0, true) - }) - } - } - } + return 1 + 0.04 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singOfferings3.effect', { + n: formatAsPercentIncrease(this.effect(n), 0) + }) + }, + name: () => i18next.t('singularity.data.singOfferings3.name'), + description: () => i18next.t('singularity.data.singOfferings3.description') }, singObtainium1: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: -1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.02 * n, - get desc () { - return i18next.t('singularity.data.singObtainium1.effect', { - n: format(2 * n, 0, true) - }) - } - } - } + return 1 + 0.02 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singObtainium1.effect', { + n: formatAsPercentIncrease(this.effect(n), 2) + }) + }, + name: () => i18next.t('singularity.data.singObtainium1.name'), + description: () => i18next.t('singularity.data.singObtainium1.description') }, singObtainium2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 25, - costPerLevel: 25, canExceedCap: true, + qualityOfLife: false, + costPerLevel: 25, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.08 * n, - get desc () { - return i18next.t('singularity.data.singObtainium2.effect', { - n: format(8 * n, 0, true) - }) - } - } - } + return 1 + 0.08 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singObtainium2.effect', { + n: formatAsPercentIncrease(this.effect(n), 0) + }) + }, + name: () => i18next.t('singularity.data.singObtainium2.name'), + description: () => i18next.t('singularity.data.singObtainium2.description') }, singObtainium3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 40, - costPerLevel: 500, canExceedCap: true, + qualityOfLife: false, + costPerLevel: 500, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.04 * n, - get desc () { - return i18next.t('singularity.data.singObtainium3.effect', { - n: format(4 * n, 0, true) - }) - } - } - } + return 1 + 0.04 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singObtainium3.effect', { + n: formatAsPercentIncrease(this.effect(n), 0) + }) + }, + name: () => i18next.t('singularity.data.singObtainium3.name'), + description: () => i18next.t('singularity.data.singObtainium3.description') }, singCubes1: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: -1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.006 * n, - get desc () { - return i18next.t('singularity.data.singCubes1.effect', { - n: format(0.6 * n, 1, true) - }) - } - } - } + return 1 + 0.006 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singCubes1.effect', { + n: formatAsPercentIncrease(this.effect(n), 3) + }) + }, + name: () => i18next.t('singularity.data.singCubes1.name'), + description: () => i18next.t('singularity.data.singCubes1.description') }, singCubes2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 25, - costPerLevel: 25, canExceedCap: true, + qualityOfLife: false, + costPerLevel: 25, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.08 * n, - get desc () { - return i18next.t('singularity.data.singCubes2.effect', { - n: format(8 * n, 0, true) - }) - } - } - } + return 1 + 0.08 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singCubes2.effect', { + n: formatAsPercentIncrease(this.effect(n), 0) + }) + }, + name: () => i18next.t('singularity.data.singCubes2.name'), + description: () => i18next.t('singularity.data.singCubes2.description') }, singCubes3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 40, - costPerLevel: 500, canExceedCap: true, + qualityOfLife: false, + costPerLevel: 500, + minimumSingularity: 0, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.04 * n, - get desc () { - return i18next.t('singularity.data.singCubes3.effect', { - n: format(4 * n, 0, true) - }) - } - } - } + return 1 + 0.04 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singCubes3.effect', { + n: formatAsPercentIncrease(this.effect(n), 0) + }) + }, + name: () => i18next.t('singularity.data.singCubes3.name'), + description: () => i18next.t('singularity.data.singCubes3.description') }, singCitadel: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: -1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 500000, minimumSingularity: 100, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: (1 + 0.02 * n) * (1 + Math.floor(n / 10) / 100), - get desc () { - return i18next.t('singularity.data.singCubes2.effect', { - n: format( - 100 * ((1 + 0.02 * n) * (1 + Math.floor(n / 10) / 100) - 1), - 2, - true - ) - }) - } - } - } + return (1 + 0.02 * n) * (1 + Math.floor(n / 10) / 100) + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singCitadel.effect', { + n: formatAsPercentIncrease(this.effect(n), 2) + }) + }, + name: () => i18next.t('singularity.data.singCitadel.name'), + description: () => i18next.t('singularity.data.singCitadel.description') }, singCitadel2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 100, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1e14, minimumSingularity: 204, specialCostForm: 'Quadratic', effect: (n: number) => { - return { - bonus: (1 + 0.02 * n) * (1 + Math.floor(n / 10) / 100), - get desc () { - return i18next.t('singularity.data.singCubes3.effect', { - n: format( - 100 * ((1 + 0.02 * n) * (1 + Math.floor(n / 10) / 100) - 1), - 2, - true - ) - }) - } - } - } + return (1 + 0.02 * n) * (1 + Math.floor(n / 10) / 100) + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singCitadel2.effect', { + n: formatAsPercentIncrease(this.effect(n), 2) + }) + }, + name: () => i18next.t('singularity.data.singCitadel2.name'), + description: () => i18next.t('singularity.data.singCitadel2.description') }, octeractUnlock: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 8888, minimumSingularity: 8, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.octeractUnlock.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.octeractUnlock.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.octeractUnlock.name'), + description: () => i18next.t('singularity.data.octeractUnlock.description') }, singOcteractPatreonBonus: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 9999, minimumSingularity: 12, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t('singularity.data.singOcteractPatreonBonus.effect', { - n - }) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t('singularity.data.singOcteractPatreonBonus.effect', { + n + }), + name: () => i18next.t('singularity.data.singOcteractPatreonBonus.name'), + description: () => i18next.t('singularity.data.singOcteractPatreonBonus.description') }, offeringAutomatic: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: -1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1e14, minimumSingularity: 222, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('singularity.data.offeringAutomatic.effect', { n }) - } - } - } + return n + }, + effectDescription: (n: number) => i18next.t('singularity.data.offeringAutomatic.effect', { n }), + name: () => i18next.t('singularity.data.offeringAutomatic.name'), + description: () => i18next.t('singularity.data.offeringAutomatic.description') }, intermediatePack: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1, minimumSingularity: 4, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.intermediatePack.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t( + `singularity.data.intermediatePack.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.intermediatePack.name'), + description: () => i18next.t('singularity.data.intermediatePack.description') }, advancedPack: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 200, minimumSingularity: 9, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.advancedPack.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t( + `singularity.data.advancedPack.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.advancedPack.name'), + description: () => i18next.t('singularity.data.advancedPack.description') }, expertPack: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 800, minimumSingularity: 16, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.expertPack.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t( + `singularity.data.expertPack.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.expertPack.name'), + description: () => i18next.t('singularity.data.expertPack.description') }, masterPack: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 3200, minimumSingularity: 25, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.masterPack.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t( + `singularity.data.masterPack.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.masterPack.name'), + description: () => i18next.t('singularity.data.masterPack.description') }, divinePack: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 12800, minimumSingularity: 36, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.divinePack.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } + if (n === 0) { + return 1 } - } + const corruptions = player.corruptions.used + const octMult = Object.values(corruptions.loadout).reduce( + (acc, curr) => acc * (curr === 16 ? 1.4 : (curr === 15 ? 1.3 : (curr === 14 ? 1.25 : 1))), + 1 + ) + return octMult + }, + effectDescription: function(n: number) { + return i18next.t( + `singularity.data.divinePack.effect${n > 0 ? 'Have' : 'HaveNot'}`, + { + n: formatAsPercentIncrease(this.effect(n), 0) + } + ) + }, + name: () => i18next.t('singularity.data.divinePack.name'), + description: () => i18next.t('singularity.data.divinePack.description') }, wowPass2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 12500, minimumSingularity: 9, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.wowPass2.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.wowPass2.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.wowPass2.name'), + description: () => i18next.t('singularity.data.wowPass2.description') }, wowPass3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 3e7 - 1, minimumSingularity: 83, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.wowPass3.effect${n > 0 ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.wowPass3.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.wowPass3.name'), + description: () => i18next.t('singularity.data.wowPass3.description') }, potionBuff: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 10, + canExceedCap: true, + qualityOfLife: false, costPerLevel: 999, minimumSingularity: 4, - canExceedCap: true, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: Math.max(1, 10 * Math.pow(n, 2)), - get desc () { - return i18next.t('singularity.data.potionBuff.effect', { - n: format(Math.max(1, 10 * Math.pow(n, 2)), 0, true) - }) - } - } - } + return Math.max(1, 10 * Math.pow(n, 2)) + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.potionBuff.effect', { + n: format(this.effect(n), 0, true) + }) + }, + name: () => i18next.t('singularity.data.potionBuff.name'), + description: () => i18next.t('singularity.data.potionBuff.description') }, potionBuff2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 10, + canExceedCap: true, + qualityOfLife: false, costPerLevel: 1e8, minimumSingularity: 119, - canExceedCap: true, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: Math.max(1, 2 * n), - get desc () { - return i18next.t('singularity.data.potionBuff2.effect', { - n: format(Math.max(1, 2 * n), 0, true) - }) - } - } - } + return Math.max(1, 2 * n) + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.potionBuff2.effect', { + n: format(this.effect(n), 0, true) + }) + }, + name: () => i18next.t('singularity.data.potionBuff2.name'), + description: () => i18next.t('singularity.data.potionBuff2.description') }, potionBuff3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 10, + canExceedCap: true, + qualityOfLife: false, costPerLevel: 1e12, minimumSingularity: 191, - canExceedCap: true, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: Math.max(1, 1 + 0.5 * n), - get desc () { - return i18next.t('singularity.data.potionBuff3.effect', { - n: format(Math.max(1, 1 + 0.5 * n), 2, true) - }) - } - } - } + return Math.max(1, 1 + 0.5 * n) + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.potionBuff3.effect', { + n: format(this.effect(n), 2, true) + }) + }, + name: () => i18next.t('singularity.data.potionBuff3.name'), + description: () => i18next.t('singularity.data.potionBuff3.description') }, singChallengeExtension: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 4, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 999, minimumSingularity: 11, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('singularity.data.singChallengeExtension.effect', { - n: 2 * n, - m: n - }) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t('singularity.data.singChallengeExtension.effect', { + n: 2 * n, + m: n + }), + name: () => i18next.t('singularity.data.singChallengeExtension.name'), + description: () => i18next.t('singularity.data.singChallengeExtension.description') }, singChallengeExtension2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 3, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 29999, minimumSingularity: 26, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('singularity.data.singChallengeExtension2.effect', { - n: 2 * n, - m: n - }) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t('singularity.data.singChallengeExtension2.effect', { + n: 2 * n, + m: n + }), + name: () => i18next.t('singularity.data.singChallengeExtension2.name'), + description: () => i18next.t('singularity.data.singChallengeExtension2.description') }, singChallengeExtension3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 3, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 749999, minimumSingularity: 51, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('singularity.data.singChallengeExtension3.effect', { - n: 2 * n, - m: n - }) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t('singularity.data.singChallengeExtension3.effect', { + n: 2 * n, + m: n + }), + name: () => i18next.t('singularity.data.singChallengeExtension3.name'), + description: () => i18next.t('singularity.data.singChallengeExtension3.description') }, singQuarkImprover1: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 30, + canExceedCap: true, + qualityOfLife: true, costPerLevel: 1, minimumSingularity: 173, - canExceedCap: true, specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: 1 + n / 200, - get desc () { - return i18next.t('singularity.data.singQuarkImprover1.effect', { - n: format(n / 2, 2, true) - }) - } - } + return 1 + n / 200 + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singQuarkImprover1.effect', { + n: formatAsPercentIncrease(this.effect(n), 2) + }) }, - qualityOfLife: true + name: () => i18next.t('singularity.data.singQuarkImprover1.name'), + description: () => i18next.t('singularity.data.singQuarkImprover1.description') }, singQuarkHepteract: { - maxLevel: 1, + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, + maxLevel: 10, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 14999, minimumSingularity: 5, + specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: n / 100, - get desc () { - return i18next.t('singularity.data.singQuarkHepteract.effect', { - n: format(2 * n, 2, true) - }) - } - } + return n / 50 }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t('singularity.data.singQuarkHepteract.effect', { + n: format(n / 50, 2, true) + }), + name: () => i18next.t('singularity.data.singQuarkHepteract.name'), + description: () => i18next.t('singularity.data.singQuarkHepteract.description') }, singQuarkHepteract2: { - maxLevel: 1, + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, + maxLevel: 10, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 449999, minimumSingularity: 30, + specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: n / 100, - get desc () { - return i18next.t('singularity.data.singQuarkHepteract2.effect', { - n: format(2 * n, 2, true) - }) - } - } + return n / 50 }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t('singularity.data.singQuarkHepteract2.effect', { + n: format(n / 50, 2, true) + }), + name: () => i18next.t('singularity.data.singQuarkHepteract2.name'), + description: () => i18next.t('singularity.data.singQuarkHepteract2.description') }, singQuarkHepteract3: { - maxLevel: 1, + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, + maxLevel: 10, + canExceedCap: true, + qualityOfLife: true, costPerLevel: 13370000, minimumSingularity: 61, + specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: n / 100, - get desc () { - return i18next.t('singularity.data.singQuarkHepteract3.effect', { - n: format(2 * n, 2, true) - }) - } - } + return n / 100 }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t('singularity.data.singQuarkHepteract3.effect', { + n: format(n / 100, 2, true) + }), + name: () => i18next.t('singularity.data.singQuarkHepteract3.name'), + description: () => i18next.t('singularity.data.singQuarkHepteract3.description') }, singOcteractGain: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: -1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 20000, minimumSingularity: 36, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.0125 * n, - get desc () { - return i18next.t('singularity.data.singOcteractGain.effect', { - n: format(1.25 * n, 2, true) - }) - } - } - } + return 1 + 0.0125 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singOcteractGain.effect', { + n: formatAsPercentIncrease(this.effect(n), 2) + }) + }, + name: () => i18next.t('singularity.data.singOcteractGain.name'), + description: () => i18next.t('singularity.data.singOcteractGain.description') }, singOcteractGain2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 25, + canExceedCap: true, + qualityOfLife: false, costPerLevel: 40000, minimumSingularity: 36, - canExceedCap: true, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.05 * n, - get desc () { - return i18next.t('singularity.data.singOcteractGain2.effect', { - n: format(5 * n, 0, true) - }) - } - } - } + return 1 + 0.05 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singOcteractGain2.effect', { + n: formatAsPercentIncrease(this.effect(n), 0) + }) + }, + name: () => i18next.t('singularity.data.singOcteractGain2.name'), + description: () => i18next.t('singularity.data.singOcteractGain2.description') }, singOcteractGain3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 50, + canExceedCap: true, + qualityOfLife: false, costPerLevel: 250000, minimumSingularity: 55, - canExceedCap: true, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.025 * n, - get desc () { - return i18next.t('singularity.data.singOcteractGain3.effect', { - n: format(2.5 * n, 0, true) - }) - } - } - } + return 1 + 0.025 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singOcteractGain3.effect', { + n: formatAsPercentIncrease(this.effect(n), 1) + }) + }, + name: () => i18next.t('singularity.data.singOcteractGain3.name'), + description: () => i18next.t('singularity.data.singOcteractGain3.description') }, singOcteractGain4: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 100, + canExceedCap: true, + qualityOfLife: false, costPerLevel: 750000, minimumSingularity: 77, - canExceedCap: true, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.02 * n, - get desc () { - return i18next.t('singularity.data.singOcteractGain4.effect', { - n: format(2 * n, 0, true) - }) - } - } - } + return 1 + 0.02 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singOcteractGain4.effect', { + n: formatAsPercentIncrease(this.effect(n), 0) + }) + }, + name: () => i18next.t('singularity.data.singOcteractGain4.name'), + description: () => i18next.t('singularity.data.singOcteractGain4.description') }, singOcteractGain5: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 200, + canExceedCap: true, + qualityOfLife: false, costPerLevel: 7777777, minimumSingularity: 100, - canExceedCap: true, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + 0.01 * n, - get desc () { - return i18next.t('singularity.data.singOcteractGain5.effect', { - n: format(n, 0, true) - }) - } - } - } + return 1 + 0.01 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singOcteractGain5.effect', { + n: formatAsPercentIncrease(this.effect(n), 0) + }) + }, + name: () => i18next.t('singularity.data.singOcteractGain5.name'), + description: () => i18next.t('singularity.data.singOcteractGain5.description') }, platonicTau: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 100000, minimumSingularity: 29, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.platonicTau.effect${n ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.platonicTau.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.platonicTau.name'), + description: () => i18next.t('singularity.data.platonicTau.description') }, platonicAlpha: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 2e7, minimumSingularity: 70, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.platonicAlpha.effect${n ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.platonicAlpha.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.platonicAlpha.name'), + description: () => i18next.t('singularity.data.platonicAlpha.description') }, platonicDelta: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 5e9, minimumSingularity: 110, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.platonicDelta.effect${n ? 'Have' : 'HaveNot'}` - ) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t( + `singularity.data.platonicDelta.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.platonicDelta.name'), + description: () => i18next.t('singularity.data.platonicDelta.description') }, platonicPhi: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 2e11, minimumSingularity: 149, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.platonicPhi.effect${n ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.platonicPhi.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.platonicPhi.name'), + description: () => i18next.t('singularity.data.platonicPhi.description') }, singFastForward: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 7e6 - 1, minimumSingularity: 50, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.singFastForward.effect${n ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.singFastForward.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.singFastForward.name'), + description: () => i18next.t('singularity.data.singFastForward.description') }, singFastForward2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 1e11 - 1, minimumSingularity: 147, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.singFastForward2.effect${n ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.singFastForward2.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.singFastForward2.name'), + description: () => i18next.t('singularity.data.singFastForward2.description') }, singAscensionSpeed: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1e10, minimumSingularity: 128, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t('singularity.data.singAscensionSpeed.effect', { - n: format(1 + 0.03 * n, 2, true), - m: format(1 - 0.03 * n, 2, true) - }) - } - } - } + return 0.03 * n + }, + effectDescription: (n: number) => + i18next.t('singularity.data.singAscensionSpeed.effect', { + n: format(1 + 0.03 * n, 2, true), + m: format(1 - 0.03 * n, 2, true) + }), + name: () => i18next.t('singularity.data.singAscensionSpeed.name'), + description: () => i18next.t('singularity.data.singAscensionSpeed.description') }, singAscensionSpeed2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 30, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1e12, - specialCostForm: 'Exponential2', minimumSingularity: 147, + specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: 0.001 * n, - get desc () { - return i18next.t('singularity.data.singAscensionSpeed2.effect', { - n: format(0.001 * n, 3, true) - }) - } - } - } + return 0.001 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singAscensionSpeed2.effect', { + n: format(this.effect(n), 3, true) + }) + }, + name: () => i18next.t('singularity.data.singAscensionSpeed2.name'), + description: () => i18next.t('singularity.data.singAscensionSpeed2.description') }, ultimatePen: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, - costPerLevel: 2.22e22, + canExceedCap: false, + qualityOfLife: false, + costPerLevel: 2.22e26, minimumSingularity: 300, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t('singularity.data.ultimatePen.effect', { - n: n ? '' : 'NOT', - m: n > 0 - ? ' However, the pen just ran out of ink. How will you get more?' - : '' - }) - } - } - } + return n + }, + effectDescription: (n: number) => + i18next.t('singularity.data.ultimatePen.effect', { + n: n > 0 ? '' : 'NOT', + m: n > 0 + ? ' However, the pen just ran out of ink. How will you get more?' + : '' + }), + name: () => i18next.t('singularity.data.ultimatePen.name'), + description: () => i18next.t('singularity.data.ultimatePen.description') }, halfMind: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 1.66e12, minimumSingularity: 150, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.halfMind.effect${n ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.halfMind.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.halfMind.name'), + description: () => i18next.t('singularity.data.halfMind.description') }, oneMind: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 1.66e13, minimumSingularity: 162, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.oneMind.effect${n ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.oneMind.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.oneMind.name'), + description: () => i18next.t('singularity.data.oneMind.description') }, wowPass4: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 66666666666, minimumSingularity: 147, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n > 0, - get desc () { - return i18next.t( - `singularity.data.wowPass4.effect${n ? 'Have' : 'HaveNot'}` - ) - } - } + return n }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t( + `singularity.data.wowPass4.effect${n > 0 ? 'Have' : 'HaveNot'}` + ), + name: () => i18next.t('singularity.data.wowPass4.name'), + description: () => i18next.t('singularity.data.wowPass4.description') }, blueberries: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 10, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 1e16, minimumSingularity: 215, + specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('singularity.data.blueberries.effect', { n }) - } - } + return n }, - specialCostForm: 'Exponential2', - qualityOfLife: true + effectDescription: (n: number) => i18next.t('singularity.data.blueberries.effect', { n }), + name: () => i18next.t('singularity.data.blueberries.name'), + description: () => i18next.t('singularity.data.blueberries.description') }, singAmbrosiaLuck: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: -1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 1e9, minimumSingularity: 187, + specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: 4 * n, - get desc () { - return i18next.t('singularity.data.singAmbrosiaLuck.effect', { - n: format(4 * n) - }) - } - } + return 4 * n }, - specialCostForm: 'Exponential2', - qualityOfLife: true + effectDescription: function(n: number) { + return i18next.t('singularity.data.singAmbrosiaLuck.effect', { + n: format(this.effect(n)) + }) + }, + name: () => i18next.t('singularity.data.singAmbrosiaLuck.name'), + description: () => i18next.t('singularity.data.singAmbrosiaLuck.description') }, singAmbrosiaLuck2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 30, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 4e5, minimumSingularity: 50, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 2 * n, - get desc () { - return i18next.t('singularity.data.singAmbrosiaLuck2.effect', { - n: format(2 * n) - }) - } - } + return 2 * n }, - qualityOfLife: true + effectDescription: function(n: number) { + return i18next.t('singularity.data.singAmbrosiaLuck2.effect', { + n: format(this.effect(n)) + }) + }, + name: () => i18next.t('singularity.data.singAmbrosiaLuck2.name'), + description: () => i18next.t('singularity.data.singAmbrosiaLuck2.description') }, singAmbrosiaLuck3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 30, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 2e8, minimumSingularity: 119, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 3 * n, - get desc () { - return i18next.t('singularity.data.singAmbrosiaLuck3.effect', { - n: format(3 * n) - }) - } - } + return 3 * n + }, + effectDescription: function(n: number) { + return i18next.t('singularity.data.singAmbrosiaLuck3.effect', { + n: format(this.effect(n)) + }) }, - qualityOfLife: true + name: () => i18next.t('singularity.data.singAmbrosiaLuck3.name'), + description: () => i18next.t('singularity.data.singAmbrosiaLuck3.description') }, singAmbrosiaLuck4: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 50, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 1e19, minimumSingularity: 256, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 5 * n, - get desc () { - return i18next.t('singularity.data.singAmbrosiaLuck4.effect', { - n: format(5 * n) - }) - } - } + return 5 * n }, - qualityOfLife: true + effectDescription: function(n: number) { + return i18next.t('singularity.data.singAmbrosiaLuck4.effect', { + n: format(this.effect(n)) + }) + }, + name: () => i18next.t('singularity.data.singAmbrosiaLuck4.name'), + description: () => i18next.t('singularity.data.singAmbrosiaLuck4.description') }, singAmbrosiaGeneration: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: -1, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 1e9, minimumSingularity: 187, + specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: 1 + n / 100, - get desc () { - return i18next.t('singularity.data.singAmbrosiaGeneration.effect', { - n: format(n) - }) - } - } + return 1 + n / 100 }, - specialCostForm: 'Exponential2', - qualityOfLife: true + effectDescription: (n: number) => + i18next.t('singularity.data.singAmbrosiaGeneration.effect', { + n: format(n) + }), + name: () => i18next.t('singularity.data.singAmbrosiaGeneration.name'), + description: () => i18next.t('singularity.data.singAmbrosiaGeneration.description') }, singAmbrosiaGeneration2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 20, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 8e5, minimumSingularity: 50, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + n / 100, - get desc () { - return i18next.t('singularity.data.singAmbrosiaGeneration2.effect', { - n: format(n) - }) - } - } + return 1 + n / 100 }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t('singularity.data.singAmbrosiaGeneration2.effect', { + n: format(n) + }), + name: () => i18next.t('singularity.data.singAmbrosiaGeneration2.name'), + description: () => i18next.t('singularity.data.singAmbrosiaGeneration2.description') }, singAmbrosiaGeneration3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 35, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 3e8, minimumSingularity: 119, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + n / 100, - get desc () { - return i18next.t('singularity.data.singAmbrosiaGeneration3.effect', { - n: format(n) - }) - } - } + return 1 + n / 100 }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t('singularity.data.singAmbrosiaGeneration3.effect', { + n: format(n) + }), + name: () => i18next.t('singularity.data.singAmbrosiaGeneration3.name'), + description: () => i18next.t('singularity.data.singAmbrosiaGeneration3.description') }, singAmbrosiaGeneration4: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 50, + canExceedCap: false, + qualityOfLife: true, costPerLevel: 1e19, minimumSingularity: 256, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: 1 + (2 * n) / 100, - get desc () { - return i18next.t('singularity.data.singAmbrosiaGeneration4.effect', { - n: format(2 * n) - }) - } - } + return 1 + (2 * n) / 100 }, - qualityOfLife: true + effectDescription: (n: number) => + i18next.t('singularity.data.singAmbrosiaGeneration4.effect', { + n: format(2 * n) + }), + name: () => i18next.t('singularity.data.singAmbrosiaGeneration4.name'), + description: () => i18next.t('singularity.data.singAmbrosiaGeneration4.description') }, singBonusTokens1: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 5, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 25, minimumSingularity: 1, specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('singularity.data.singBonusTokens1.effect', { - n: format(n) - }) - } - } + return n }, - cacheUpdates: [ - () => { - player.campaigns.updateCurrentTokens() - campaignTokenRewardHTMLUpdate() - } - ] + effectDescription: (n: number) => + i18next.t('singularity.data.singBonusTokens1.effect', { + n: format(n) + }), + name: () => i18next.t('singularity.data.singBonusTokens1.name'), + description: () => i18next.t('singularity.data.singBonusTokens1.description') }, singBonusTokens2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 5, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 10000, minimumSingularity: 25, specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: 1 + n / 100, - get desc () { - return i18next.t('singularity.data.singBonusTokens2.effect', { - n: format(n) - }) - } - } + return 1 + n / 100 }, - cacheUpdates: [ - () => { - player.campaigns.updateCurrentTokens() - campaignTokenRewardHTMLUpdate() - } - ] + effectDescription: (n: number) => + i18next.t('singularity.data.singBonusTokens2.effect', { + n: format(n) + }), + name: () => i18next.t('singularity.data.singBonusTokens2.name'), + description: () => i18next.t('singularity.data.singBonusTokens2.description') }, singBonusTokens3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 5, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1e8, minimumSingularity: 100, specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: 2 * n, - get desc () { - return i18next.t('singularity.data.singBonusTokens3.effect', { - n: format(2 * n) - }) - } - } + return 2 * n }, - cacheUpdates: [ - () => { - player.campaigns.updateCurrentTokens() - campaignTokenRewardHTMLUpdate() - } - ] + effectDescription: function(n: number) { + return i18next.t('singularity.data.singBonusTokens3.effect', { + n: format(this.effect(n)) + }) + }, + name: () => i18next.t('singularity.data.singBonusTokens3.name'), + description: () => i18next.t('singularity.data.singBonusTokens3.description') }, singBonusTokens4: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 30, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1e13, minimumSingularity: 166, specialCostForm: 'Exponential2', effect: (n: number) => { - return { - bonus: 5 * n, - get desc () { - return i18next.t('singularity.data.singBonusTokens4.effect', { - n: format(5 * n) - }) - } - } + return 5 * n }, - cacheUpdates: [ - () => { - player.campaigns.updateCurrentTokens() - campaignTokenRewardHTMLUpdate() - } - ] + effectDescription: function(n: number) { + return i18next.t('singularity.data.singBonusTokens4.effect', { + n: format(this.effect(n)) + }) + }, + name: () => i18next.t('singularity.data.singBonusTokens4.name'), + description: () => i18next.t('singularity.data.singBonusTokens4.description') }, singInfiniteShopUpgrades: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, maxLevel: 80, + canExceedCap: false, + qualityOfLife: false, costPerLevel: 1e18, minimumSingularity: 233, + specialCostForm: 'Default', effect: (n: number) => { - return { - bonus: n, - get desc () { - return i18next.t('singularity.data.singInfiniteShopUpgrades.effect', { - n: format(n) - }) - } + return n + }, + effectDescription: (n: number) => + i18next.t('singularity.data.singInfiniteShopUpgrades.effect', { + n: format(n) + }), + name: () => i18next.t('singularity.data.singInfiniteShopUpgrades.name'), + description: () => i18next.t('singularity.data.singInfiniteShopUpgrades.description') + }, + singTalismanBonusRunes1: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, + maxLevel: 5, + canExceedCap: false, + qualityOfLife: false, + costPerLevel: 25, + minimumSingularity: 1, + specialCostForm: 'Default', + effect: (n: number) => { + return n / 100 + }, + effectDescription: (n: number) => + i18next.t('singularity.data.singTalismanBonusRunes1.effect', { + n: format(n, 0, true) + }), + name: () => i18next.t('singularity.data.singTalismanBonusRunes1.name'), + description: () => i18next.t('singularity.data.singTalismanBonusRunes1.description') + }, + singTalismanBonusRunes2: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, + maxLevel: 5, + canExceedCap: false, + qualityOfLife: false, + costPerLevel: 10000, + minimumSingularity: 27, + specialCostForm: 'Default', + effect: (n: number) => { + return n / 100 + }, + effectDescription: (n: number) => + i18next.t('singularity.data.singTalismanBonusRunes2.effect', { + n: format(n, 0, true) + }), + name: () => i18next.t('singularity.data.singTalismanBonusRunes2.name'), + description: () => i18next.t('singularity.data.singTalismanBonusRunes2.description') + }, + singTalismanBonusRunes3: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, + maxLevel: 5, + canExceedCap: false, + qualityOfLife: false, + costPerLevel: 1e8, + minimumSingularity: 99, + specialCostForm: 'Default', + effect: (n: number) => { + return n / 100 + }, + effectDescription: (n: number) => + i18next.t('singularity.data.singTalismanBonusRunes3.effect', { + n: format(n, 0, true) + }), + name: () => i18next.t('singularity.data.singTalismanBonusRunes3.name'), + description: () => i18next.t('singularity.data.singTalismanBonusRunes3.description') + }, + singTalismanBonusRunes4: { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0, + maxLevel: 10, + canExceedCap: false, + qualityOfLife: false, + costPerLevel: 3e15, + minimumSingularity: 211, + specialCostForm: 'Default', + effect: (n: number) => { + return n / 100 + }, + effectDescription: (n: number) => + i18next.t('singularity.data.singTalismanBonusRunes4.effect', { + n: format(n, 0, true) + }), + name: () => i18next.t('singularity.data.singTalismanBonusRunes4.name'), + description: () => i18next.t('singularity.data.singTalismanBonusRunes4.description') + } +} + +export const blankGQLevelObject: Record< + SingularityDataKeys, + { level: number; freeLevel: number; goldenQuarksInvested: number } +> = Object.fromEntries( + Object.keys(goldenQuarkUpgrades).map((key) => [ + key as SingularityDataKeys, + { + level: 0, + freeLevel: 0, + goldenQuarksInvested: 0 + } + ]) +) as Record + +export const maxGoldenQuarkUpgradeAP = Object.values(goldenQuarkUpgrades).reduce((acc, upgrade) => { + if (upgrade.maxLevel === -1) { + return acc + } + return acc + 5 +}, 0) + +/** + * Get the upgrade's HTML representation with all relevant information + */ +export function upgradeGQToString (upgradeKey: SingularityDataKeys): string { + const upgrade = goldenQuarkUpgrades[upgradeKey] + const name = upgrade.name() + const description = upgrade.description() + const costNextLevel = getGQUpgradeCostTNL(upgradeKey) + const maxLevel = upgrade.maxLevel === -1 ? '' : `/${format(computeGQUpgradeMaxLevel(upgradeKey), 0, true)}` + const effectDesc = getGQUpgradeDescription(upgradeKey) + const freeLevelMult = computeFreeLevelMultiplier() + const freeLevelsWithMult = upgrade.freeLevel * freeLevelMult + const totalEffectiveLevels = actualGQUpgradeTotalLevels(upgradeKey) + const color = computeGQUpgradeMaxLevel(upgradeKey) === upgrade.level ? 'plum' : 'white' + + // Upgrade Name Text + const nameHTML = `${name}` + + // Upgrade Description Text + const descriptionHTML = `${description}` + + // 'Minimum Singularity' Text + const minReqColor = player.highestSingularityCount < upgrade.minimumSingularity + ? 'var(--crimson-text-color)' + : 'var(--green-text-color)' + const minimumSingularity = upgrade.minimumSingularity > 0 + ? i18next.t('singularity.toString.minimum', { + minSingularity: upgrade.minimumSingularity + }) + : i18next.t('singularity.toString.noMinimum') + + const minSingularityHTML = `${minimumSingularity}` + + // Level Text + const freeMultText = freeLevelMult > 1 + ? ` (x${format(freeLevelMult, 2, true)})` + : '' + + let freeLevelText = freeLevelsWithMult > 0 + ? ` [+${format(upgrade.freeLevel, 2, true)}${freeMultText}]` + : '' + + if (freeLevelsWithMult > upgrade.level) { + freeLevelText = `${freeLevelText} ${ + i18next.t( + 'general.softCapped' + ) + }` + } + + const effectiveLevelText = totalEffectiveLevels !== upgrade.level + ? `
${ + i18next.t('general.effectiveLevel', { + level: format(totalEffectiveLevels, 2, true) + }) + }` + : '' + + const levelText = `${i18next.t('general.level')} ${ + format(upgrade.level, 0, true) + }${maxLevel}${freeLevelText}` + + // Upgrade Effect Text + const upgradeEffectHTML = `${effectDesc}` + + // TNL Cost Text + const costHTML = computeGQUpgradeMaxLevel(upgradeKey) === upgrade.level + ? '' + : i18next.t('singularity.toString.costNextLevel', { amount: format(costNextLevel, 0, true) }) + + const investedGQHTML = upgrade.goldenQuarksInvested > 0 + ? `
${ + i18next.t('singularity.toString.spentGQ', { spent: format(upgrade.goldenQuarksInvested, 0, true) }) + }` + : '' + + // QoL Text + const qualityOfLifeText = upgrade.qualityOfLife + ? `
${i18next.t('general.alwaysEnabled')}` + : '' + + return `${nameHTML}
${levelText}${effectiveLevelText}
${descriptionHTML}
${minSingularityHTML}
${upgradeEffectHTML}
${costHTML}${investedGQHTML}${qualityOfLifeText}` +} + +export function updateMobileGQHTML (k: SingularityDataKeys) { + const elm = DOMCacheGetOrSet('goldenQuarkMultiline') + elm.innerHTML = upgradeGQToString(k) + + // MOBILE ONLY - Add a button for buying upgrades + if (isMobile) { + const buttonDiv = document.createElement('div') + + const buyOne = document.createElement('button') + const buyMax = document.createElement('button') + + buyOne.classList.add('modalBtnBuy') + buyOne.textContent = i18next.t('general.buyOne') + buyOne.addEventListener('click', (event: MouseEvent) => { + buyGQUpgradeLevel(k, event, false) + updateMobileGQHTML(k) + }) + + buyMax.classList.add('modalBtnBuy') + buyMax.textContent = i18next.t('general.buyMax') + buyMax.addEventListener('click', (event: MouseEvent) => { + buyGQUpgradeLevel(k, event, true) + updateMobileGQHTML(k) + }) + + buttonDiv.appendChild(buyOne) + buttonDiv.appendChild(buyMax) + elm.appendChild(buttonDiv) + } +} + +/** + * Get the cost for upgrading once. Returns 0 if maxed. + */ +export function getGQUpgradeCostTNL (upgradeKey: SingularityDataKeys): number { + const upgrade = goldenQuarkUpgrades[upgradeKey] + let costMultiplier = 1 + + if (computeGQUpgradeMaxLevel(upgradeKey) === upgrade.level) { + return 0 + } + + // Overcap + if (computeGQUpgradeMaxLevel(upgradeKey) > upgrade.maxLevel && upgrade.level >= upgrade.maxLevel) { + costMultiplier *= Math.pow(4, upgrade.level - upgrade.maxLevel + 1) + } + + if (upgrade.specialCostForm === 'Exponential2') { + return ( + upgrade.costPerLevel * Math.sqrt(costMultiplier) * Math.pow(2, upgrade.level) + ) + } + + if (upgrade.specialCostForm === 'Cubic') { + return ( + upgrade.costPerLevel + * costMultiplier + * (Math.pow(upgrade.level + 1, 3) - Math.pow(upgrade.level, 3)) + ) + } + + if (upgrade.specialCostForm === 'Quadratic') { + return ( + upgrade.costPerLevel + * costMultiplier + * (Math.pow(upgrade.level + 1, 2) - Math.pow(upgrade.level, 2)) + ) + } + + costMultiplier *= upgrade.maxLevel === -1 && upgrade.level >= 100 ? upgrade.level / 50 : 1 + costMultiplier *= upgrade.maxLevel === -1 && upgrade.level >= 400 ? upgrade.level / 100 : 1 + + return computeGQUpgradeMaxLevel(upgradeKey) === upgrade.level + ? 0 + : Math.ceil(upgrade.costPerLevel * (1 + upgrade.level) * costMultiplier) +} + +/** + * Buy levels for an upgrade + */ +export async function buyGQUpgradeLevel ( + upgradeKey: SingularityDataKeys, + event: MouseEvent, + buyMax = false +): Promise { + const upgrade = goldenQuarkUpgrades[upgradeKey] + let purchased = 0 + let maxPurchasable = 1 + let GQBudget = player.goldenQuarks + + if (event.shiftKey || buyMax) { + maxPurchasable = 100000000 + const buy = Number( + await Prompt( + i18next.t('singularity.goldenQuarks.spendPrompt', { + gq: format(player.goldenQuarks, 0, true) + }) + ) + ) + + if (isNaN(buy) || !isFinite(buy) || !Number.isInteger(buy)) { + return Alert(i18next.t('general.validation.finite')) + } + + if (buy === -1) { + GQBudget = player.goldenQuarks + } else if (buy <= 0) { + return Alert(i18next.t('general.validation.zeroOrLess')) + } else { + GQBudget = buy + } + GQBudget = Math.min(player.goldenQuarks, GQBudget) + } + + if (upgrade.maxLevel > 0) { + maxPurchasable = Math.min( + maxPurchasable, + computeGQUpgradeMaxLevel(upgradeKey) - upgrade.level + ) + } + + if (maxPurchasable === 0) { + return Alert(i18next.t('singularity.goldenQuarks.hasUpgrade')) + } + + if (player.highestSingularityCount < upgrade.minimumSingularity) { + return Alert(i18next.t('singularity.goldenQuarks.notHighEnoughLevel')) + } + + while (maxPurchasable > 0) { + const cost = getGQUpgradeCostTNL(upgradeKey) + if (player.goldenQuarks < cost || GQBudget < cost) { + break + } else { + player.goldenQuarks -= cost + upgrade.goldenQuarksInvested += cost + GQBudget -= cost + upgrade.level += 1 + purchased += 1 + maxPurchasable -= 1 + } + + // Special upgrade effects + if (upgradeKey === 'oneMind') { + player.ascensionCounter = 0 + player.ascensionCounterReal = 0 + player.ascensionCounterRealReal = 0 + void Alert(i18next.t('singularity.goldenQuarks.ascensionReset')) + } + + if (upgradeKey === 'singCitadel2') { + goldenQuarkUpgrades.singCitadel.freeLevel = upgrade.level + } + } + + if (purchased === 0) { + return Alert(i18next.t('general.validation.moreThanPlayerHas')) + } + if (purchased > 1) { + void Alert( + i18next.t('singularity.goldenQuarks.multiBuyPurchased', { + levels: format(purchased) + }) + ) + } + + updateSingularityPenalties() + updateSingularityPerks() + updateTokens() + updateMaxTokens() + revealStuff() +} + +export function computeFreeLevelMultiplier (): number { + return (player.shopUpgrades.shopSingularityPotency > 0 ? 3.66 : 1) + 0.3 / 100 * player.cubeUpgrades[75] +} + +export function computeGQUpgradeFreeLevelSoftcap (upgradeKey: SingularityDataKeys): number { + const upgrade = goldenQuarkUpgrades[upgradeKey] + const freeLevelMult = computeFreeLevelMultiplier() + const baseRealFreeLevels = freeLevelMult * upgrade.freeLevel + return ( + Math.min(upgrade.level, baseRealFreeLevels) + + Math.sqrt(Math.max(0, baseRealFreeLevels - upgrade.level)) + ) +} + +export function computeGQUpgradeMaxLevel (upgradeKey: SingularityDataKeys): number { + const upgrade = goldenQuarkUpgrades[upgradeKey] + if (!upgrade.canExceedCap) { + return upgrade.maxLevel + } else { + let cap = upgrade.maxLevel + const overclockPerks = [50, 60, 75, 100, 125, 150, 175, 200, 225, 250] + for (const perk of overclockPerks) { + if (player.highestSingularityCount >= perk) { + cap += 1 + } else { + break } } + cap += getOcteractUpgradeEffect('octeractSingUpgradeCap') + return cap + } +} + +export function actualGQUpgradeTotalLevels (upgradeKey: SingularityDataKeys): number { + const upgrade = goldenQuarkUpgrades[upgradeKey] + + if ( + (player.singularityChallenges.noSingularityUpgrades.enabled + || player.singularityChallenges.sadisticPrequel.enabled) + && !upgrade.qualityOfLife + ) { + return 0 + } + + if ( + (player.singularityChallenges.limitedAscensions.enabled || player.singularityChallenges.limitedTime.enabled + || player.singularityChallenges.sadisticPrequel.enabled) + && upgradeKey === 'platonicDelta' + ) { + return 0 + } + + const actualFreeLevels = computeGQUpgradeFreeLevelSoftcap(upgradeKey) + const linearLevels = upgrade.level + actualFreeLevels + let polynomialLevels = 0 + + if (getOcteractUpgradeEffect('octeractImprovedFree')) { + let exponent = 0.6 + exponent += getOcteractUpgradeEffect('octeractImprovedFree2') + exponent += getOcteractUpgradeEffect('octeractImprovedFree3') + exponent += getOcteractUpgradeEffect('octeractImprovedFree4') + polynomialLevels = Math.pow(upgrade.level * actualFreeLevels, exponent) } + + return Math.max(linearLevels, polynomialLevels) +} + +/** + * Gets effect of a Golden Quark upgrade, using actualTotalLevels + */ +export function getGQUpgradeEffect (upgradeKey: SingularityDataKeys): number { + const upgrade = goldenQuarkUpgrades[upgradeKey] + const totalLevels = actualGQUpgradeTotalLevels(upgradeKey) + return upgrade.effect(totalLevels) +} + +export function getGQUpgradeDescription (upgradeKey: SingularityDataKeys): string { + const upgrade = goldenQuarkUpgrades[upgradeKey] + const totalLevels = actualGQUpgradeTotalLevels(upgradeKey) + return upgrade.effectDescription(totalLevels) +} + +export function resetGQUpgrade (upgradeKey: SingularityDataKeys): void { + const upgrade = goldenQuarkUpgrades[upgradeKey] + upgrade.level = 0 } /** @@ -1799,6 +2371,19 @@ export const singularityPerks: SingularityPerk[] = [ }, ID: 'researchDummies' }, + { + name: () => { + return i18next.t('singularity.perks.recycledContent.name') + }, + levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + description: (n: number, _levels: number[]) => { + const salvageBonus = Math.min(50, 5 * n) + return i18next.t('singularity.perks.recycledContent.default', { + amount: salvageBonus + }) + }, + ID: 'recycledContent' + }, { name: () => { return i18next.t('singularity.perks.antGodsCornucopia.name') @@ -2129,6 +2714,24 @@ export const singularityPerks: SingularityPerk[] = [ }, ID: 'coolQOLCubes' }, + { + name: () => { + return i18next.t('singularity.perks.infiniteRecycling.name') + }, + levels: [30, 40, 61, 81, 111, 131, 161, 191, 236, 260], + description: (n: number, levels: number[]) => { + for (let i = levels.length - 1; i >= 0; i--) { + if (n >= levels[i]) { + const salvage = 0.025 * (i + 1) * (runes.infiniteAscent.level + runes.infiniteAscent.freeLevels()) + return i18next.t('singularity.perks.infiniteRecycling.default', { + salvage: format(salvage, 3, true) + }) + } + } + return i18next.t('singularity.perks.evenMoreQuarks.bug') + }, + ID: 'infiniteRecycling' + }, { name: () => { return i18next.t('singularity.perks.irishAnt.name') @@ -2225,6 +2828,24 @@ export const singularityPerks: SingularityPerk[] = [ }, ID: 'lastClearTokens' }, + { + name: () => { + return i18next.t('singularity.perks.recyclistsDesktop.name') + }, + levels: [75, 85, 105, 125, 155, 185, 215, 245, 260, 275], + description: (n: number, levels: number[]) => { + for (let i = levels.length - 1; i >= 0; i--) { + if (n >= levels[i]) { + return i18next.t('singularity.perks.recyclistsDesktop.default', { + i: i + 1 + }) + } + } + + return i18next.t('singularity.perks.evenMoreQuarks.bug') + }, + ID: 'recyclistsDesktop' + }, { name: () => { return i18next.t('singularity.perks.goldenRevolution.name') @@ -2398,6 +3019,24 @@ export const singularityPerks: SingularityPerk[] = [ }, ID: 'skrauQ' }, + { + name: () => { + return i18next.t('singularity.perks.demeterHarvest.name') + }, + levels: [230, 245, 260, 275, 290], + description: (n: number, levels: number[]) => { + for (let i = levels.length - 1; i >= 0; i--) { + if (n >= levels[i]) { + return i18next.t('singularity.perks.demeterHarvest.default', { + i: i + 1 + }) + } + } + + return i18next.t('singularity.perks.evenMoreQuarks.bug') + }, + ID: 'demeterHarvest' + }, { name: () => { return i18next.t('singularity.perks.permanentBenefaction.name') @@ -2427,6 +3066,18 @@ export const singularityPerks: SingularityPerk[] = [ } }, ID: 'infiniteShopUpgrades' + }, + { + name: () => { + return i18next.t('singularity.perks.taxReduction.name') + }, + levels: [281], + description: () => { + return i18next.t('singularity.perks.taxReduction.default', { + amt: format(50, 0) + }) + }, + ID: 'taxReduction' } ] @@ -2552,9 +3203,9 @@ const handlePerks = (singularityCount: number) => { // Indicates the number of extra Singularity count gained on Singularity reset export const getFastForwardTotalMultiplier = (): number => { let fastForward = 0 - fastForward += +player.singularityUpgrades.singFastForward.getEffect().bonus - fastForward += +player.singularityUpgrades.singFastForward2.getEffect().bonus - fastForward += +player.octeractUpgrades.octeractFastForward.getEffect().bonus + fastForward += getGQUpgradeEffect('singFastForward') + fastForward += getGQUpgradeEffect('singFastForward2') + fastForward += getOcteractUpgradeEffect('octeractFastForward') // Stop at sing 200 even if you include fast forward fastForward = Math.max( @@ -2588,35 +3239,12 @@ export const getGoldenQuarkCost = (): { cost: number costReduction: number } => { + const cost = calculateGoldenQuarkCost() const baseCost = 10000 - let costReduction = 10000 // We will construct our cost reduction by subtracting 10000 - this value. - - costReduction *= 1 - 0.1 * Math.min(1, player.achievementPoints / 10000) - costReduction *= 1 - (0.3 * player.cubeUpgrades[60]) / 10000 - costReduction *= +player.singularityUpgrades.goldenQuarks2.getEffect().bonus - costReduction *= +player.octeractUpgrades.octeractGQCostReduce.getEffect().bonus - costReduction *= player.highestSingularityCount >= 100 - ? 1 - (0.5 * player.highestSingularityCount) / 250 - : 1 - - let perkDivisor = 1 - if (player.highestSingularityCount >= 200) { - perkDivisor = 3 - } - if (player.highestSingularityCount >= 208) { - perkDivisor = 5 - } - if (player.highestSingularityCount >= 221) { - perkDivisor = 8 - } - costReduction /= perkDivisor - - costReduction = 10000 - costReduction - return { - cost: baseCost - costReduction, - costReduction + cost: cost, + costReduction: Math.max(0, baseCost - cost) } } @@ -2680,6 +3308,7 @@ export async function buyGoldenQuarks (): Promise { export type SingularityDebuffs = | 'Offering' | 'Obtainium' + | 'Salvage' | 'Global Speed' | 'Researches' | 'Ascension Speed' @@ -2689,14 +3318,12 @@ export type SingularityDebuffs = | 'Hepteract Costs' export const calculateSingularityReductions = () => { - const arr = [ - player.shopUpgrades.shopSingularityPenaltyDebuff, - (player.insideSingularityChallenge) - ? +player.blueberryUpgrades.ambrosiaSingReduction2.bonus.singularityReduction - : +player.blueberryUpgrades.ambrosiaSingReduction1.bonus.singularityReduction - ] - - return sumContents(arr) + return ( + player.shopUpgrades.shopSingularityPenaltyDebuff + + (player.insideSingularityChallenge + ? getAmbrosiaUpgradeEffects('ambrosiaSingReduction2').singularityReduction + : getAmbrosiaUpgradeEffects('ambrosiaSingReduction1').singularityReduction) + ) } export const calculateEffectiveSingularities = ( @@ -2766,6 +3393,14 @@ export const calculateEffectiveSingularities = ( effectiveSingularities *= Math.pow(3, singularityCount - 269) } + if ( + player.singularityChallenges.taxmanLastStand.enabled + && player.singularityChallenges.taxmanLastStand.completions >= 8 + && player.platonicUpgrades[15] === 0 + ) { + effectiveSingularities = Math.pow(effectiveSingularities, 5 / 3) + } + return effectiveSingularities } @@ -2786,11 +3421,8 @@ export const calculateSingularityDebuff = ( debuff: SingularityDebuffs, singularityCount: number = player.singularityCount ) => { - if (singularityCount === 0) { - return 1 - } - if (player.runelevels[6] > 0) { - return 1 + if (singularityCount === 0 || runes.antiquities.level > 0) { + return (debuff === 'Salvage') ? 0 : 1 } const constitutiveSingularityCount = singularityCount - calculateSingularityReductions() @@ -2802,39 +3434,52 @@ export const calculateSingularityDebuff = ( constitutiveSingularityCount ) + let baseDebuffMultiplier = 1 + baseDebuffMultiplier *= 1 + - Math.min(300, player.shopUpgrades.shopHorseShoe * getRuneEffectiveLevel('horseShoe')) / 1000 + if (debuff === 'Offering') { - return constitutiveSingularityCount < 150 - ? Math.sqrt(effectiveSingularities) + 1 - : Math.pow(effectiveSingularities, 2 / 3) / 400 + const extraMult = 10 * Math.pow(1.02, constitutiveSingularityCount) + return extraMult * baseDebuffMultiplier * (constitutiveSingularityCount < 150 + ? 3 * (Math.sqrt(effectiveSingularities) + 1) + : Math.pow(effectiveSingularities, 2 / 3) / 400) + } else if (debuff === 'Salvage') { + return -(5 * constitutiveSingularityCount + + 5 * Math.max(0, constitutiveSingularityCount - 100) + + 5 * Math.max(0, constitutiveSingularityCount - 200) + + 5 * Math.max(0, constitutiveSingularityCount - 250) + + 5 * Math.max(0, constitutiveSingularityCount - 270) + + 5 * Math.max(0, constitutiveSingularityCount - 280)) } else if (debuff === 'Global Speed') { - return 1 + Math.sqrt(effectiveSingularities) / 4 + return baseDebuffMultiplier * (1 + Math.sqrt(effectiveSingularities) / 4) } else if (debuff === 'Obtainium') { - return constitutiveSingularityCount < 150 - ? Math.sqrt(effectiveSingularities) + 1 - : Math.pow(effectiveSingularities, 2 / 3) / 400 + const extraMult = 10 * Math.pow(1.02, constitutiveSingularityCount) + return extraMult * baseDebuffMultiplier * (constitutiveSingularityCount < 150 + ? 3 * (Math.sqrt(effectiveSingularities) + 1) + : Math.pow(effectiveSingularities, 2 / 3) / 400) } else if (debuff === 'Researches') { - return 1 + Math.sqrt(effectiveSingularities) / 2 + return baseDebuffMultiplier * (1 + Math.sqrt(effectiveSingularities) / 2) } else if (debuff === 'Ascension Speed') { - return constitutiveSingularityCount < 150 + return baseDebuffMultiplier * (constitutiveSingularityCount < 150 ? 1 + Math.sqrt(effectiveSingularities) / 5 - : 1 + Math.pow(effectiveSingularities, 0.75) / 10000 + : 1 + Math.pow(effectiveSingularities, 0.75) / 10000) } else if (debuff === 'Cubes') { const extraMult = constitutiveSingularityCount > 100 - ? Math.pow(1.02, constitutiveSingularityCount - 100) - : 1 - return constitutiveSingularityCount < 150 - ? 1 + (Math.sqrt(effectiveSingularities) * extraMult) / 4 - : 1 + (Math.pow(effectiveSingularities, 0.75) * extraMult) / 1000 + ? (10 + constitutiveSingularityCount / 10) * Math.pow(1.02, constitutiveSingularityCount - 100) + : 10 + return baseDebuffMultiplier * (constitutiveSingularityCount < 150 + ? 3 * (1 + (Math.sqrt(effectiveSingularities) * extraMult) / 4) + : 1 + (Math.pow(effectiveSingularities, 0.75) * extraMult) / 1000) } else if (debuff === 'Platonic Costs') { - return constitutiveSingularityCount > 36 + return baseDebuffMultiplier * (constitutiveSingularityCount > 36 ? 1 + Math.pow(effectiveSingularities, 3 / 10) / 12 - : 1 + : 1) } else if (debuff === 'Hepteract Costs') { - return constitutiveSingularityCount > 50 + return baseDebuffMultiplier * (constitutiveSingularityCount > 50 ? 1 + Math.pow(effectiveSingularities, 11 / 50) / 25 - : 1 + : 1) } else { // Cube upgrades - return Math.cbrt(effectiveSingularities + 1) + return baseDebuffMultiplier * Math.cbrt(effectiveSingularities + 1) } } diff --git a/src/types/Synergism.d.ts b/src/types/Synergism.d.ts index 8f795211f..53c9a9959 100644 --- a/src/types/Synergism.d.ts +++ b/src/types/Synergism.d.ts @@ -1,21 +1,36 @@ import type Decimal from 'break_infinity.js' -import type { BlueberryUpgrade } from '../BlueberryUpgrades' +import type { ProgressiveAchievements } from '../Achievements' +import type { + AmbrosiaUpgradeNames, + BlueberryLoadoutMode, + BlueberryOpt, + BlueberryUpgrade, + BlueberryUpgradeNames +} from '../BlueberryUpgrades' import type { CampaignManager } from '../Campaign' import type { Challenge15RewardObject, Challenge15Rewards } from '../Challenges' import type { CorruptionLoadout, Corruptions, CorruptionSaves } from '../Corruptions' import type { WowCubes, WowHypercubes, WowPlatonicCubes, WowTesseracts } from '../CubeExperimental' -import type { HepteractCraft } from '../Hepteracts' +import type { HepteractCraft, HepteractKeys, HepteractNames, HepteractValues } from '../Hepteracts' import type { Category, ResetHistoryEntryUnion } from '../History' -import type { OcteractUpgrade } from '../Octeracts' +import type { OcteractDataKeys, OcteractUpgrade } from '../Octeracts' import type { IPlatBaseCost } from '../Platonic' import type { QuarkHandler } from '../Quark' -import type { RedAmbrosiaKeys } from '../RedAmbrosiaUpgrades' -import type { SingularityUpgrade } from '../singularity' -import type { SingularityChallenge, singularityChallengeData } from '../SingularityChallenges' +import type { RedAmbrosiaNames } from '../RedAmbrosiaUpgrades' +import type { RuneBlessingKeys, RuneKeys, RuneSpiritKeys } from '../Runes' +import type { SingularityDataKeys, SingularityUpgrade } from '../singularity' +import type { + SingularityChallenge, + singularityChallengeData, + SingularityChallengeDataKeys +} from '../SingularityChallenges' import type { Tabs } from '../Tabs' +import type { TalismanCraftItems, TalismanKeys } from '../Talismans' type ArrayStartingWithNull = [null, ...T[]] +export type BuyAmount = 1 | 10 | 100 | 1000 | 10_000 | 100_000 + export interface Player { firstPlayed: string worlds: QuarkHandler @@ -232,11 +247,11 @@ export interface Player { reincarnation: number ascension: number } - researchPoints: number + + obtainium: Decimal + maxObtainium: Decimal obtainiumtimer: number - obtainiumpersecond: number - maxobtainiumpersecond: number - maxobtainium: number + // Ignore the first index. The other 25 are shaped in a 5x5 grid similar to the production appearance researches: number[] @@ -253,8 +268,18 @@ export interface Player { rrow2: boolean rrow3: boolean rrow4: boolean + anthill: boolean + blessings: boolean + spirits: boolean + talismans: boolean + ascensions: boolean + tesseracts: boolean + hypercubes: boolean + platonics: boolean + hepteracts: boolean } achievements: number[] + progressiveAchievements: Record achievementPoints: number @@ -275,11 +300,12 @@ export interface Player { crystalUpgrades: number[] crystalUpgradesCost: number[] - runelevels: number[] - runeexp: number[] - runeshards: number - maxofferings: number - offeringpersecond: number + runes: Record + runeBlessings: Record + runeSpirits: Record + + offerings: Decimal + maxOfferings: Decimal prestigecounter: number transcendcounter: number @@ -302,12 +328,12 @@ export interface Player { tesseractAutoBuyerToggle: number tesseractAutoBuyerAmount: number - coinbuyamount: number - crystalbuyamount: number - mythosbuyamount: number - particlebuyamount: number - offeringbuyamount: number - tesseractbuyamount: number + coinbuyamount: BuyAmount + crystalbuyamount: BuyAmount + mythosbuyamount: BuyAmount + particlebuyamount: BuyAmount + offeringbuyamount: BuyAmount + tesseractbuyamount: BuyAmount shoptoggles: { coin: boolean @@ -408,6 +434,7 @@ export interface Player { shopRedLuck2: number shopRedLuck3: number shopInfiniteShopUpgrades: number + shopHorseShoe: number } shopPotionsConsumed: { @@ -440,22 +467,15 @@ export interface Player { antSacrificeTimer: number antSacrificeTimerReal: number - talismanLevels: number[] - talismanRarity: number[] - talismanOne: ArrayStartingWithNull - talismanTwo: ArrayStartingWithNull - talismanThree: ArrayStartingWithNull - talismanFour: ArrayStartingWithNull - talismanFive: ArrayStartingWithNull - talismanSix: ArrayStartingWithNull - talismanSeven: ArrayStartingWithNull - talismanShards: number - commonFragments: number - uncommonFragments: number - rareFragments: number - epicFragments: number - legendaryFragments: number - mythicalFragments: number + talismans: Record> + + talismanShards: Decimal + commonFragments: Decimal + uncommonFragments: Decimal + rareFragments: Decimal + epicFragments: Decimal + legendaryFragments: Decimal + mythicalFragments: Decimal buyTalismanShardPercent: number @@ -499,7 +519,7 @@ export interface Player { antSacrifice: number antELO: number talismanBonus: number - globalSpeed: 0 + globalSpeed: number } tesseractBlessings: { accelerator: number @@ -561,8 +581,6 @@ export interface Player { autoChallengeStartExponent: number autoChallengeTimer: Record - runeBlessingLevels: number[] - runeSpiritLevels: number[] runeBlessingBuyAmount: number runeSpiritBuyAmount: number @@ -600,7 +618,9 @@ export interface Player { time: number } - hepteractCrafts: { + hepteracts: Record + + /*hepteractCrafts: { chronos: HepteractCraft hyperrealism: HepteractCraft quark: HepteractCraft @@ -609,7 +629,7 @@ export interface Player { accelerator: HepteractCraft acceleratorBoost: HepteractCraft multiplier: HepteractCraft - } + }*/ overfluxOrbs: number overfluxOrbsAutoBuy: boolean overfluxPowder: number @@ -627,15 +647,30 @@ export interface Player { iconSet: number notation: string - singularityUpgrades: Record - octeractUpgrades: Record + goldenQuarkUpgrades: Record + + octUpgrades: Record + + ambrosiaUpgrades: Record + dailyCodeUsed: boolean hepteractAutoCraftPercentage: number octeractTimer: number insideSingularityChallenge: boolean singularityChallenges: Record< - keyof typeof singularityChallengeData, + SingularityChallengeDataKeys, SingularityChallenge > @@ -647,17 +682,14 @@ export interface Player { visitedAmbrosiaSubtab: boolean visitedAmbrosiaSubtabRed: boolean spentBlueberries: number - blueberryUpgrades: Record< - keyof typeof blueberryUpgradeData, - BlueberryUpgrade - > + blueberryLoadouts: Record blueberryLoadoutMode: BlueberryLoadoutMode redAmbrosia: number lifetimeRedAmbrosia: number redAmbrosiaTime: number - redAmbrosiaUpgrades: Record + redAmbrosiaUpgrades: Record singChallengeTimer: number @@ -678,9 +710,6 @@ export interface GlobalVariables { // Mega list of Variables to be used elsewhere crystalUpgradesCost: number[] crystalUpgradeCostIncrement: number[] - researchBaseCosts: number[] - - researchMaxLevels: number[] ticker: number @@ -700,7 +729,6 @@ export interface GlobalVariables { multiplierPower: number multiplierEffect: Decimal challengeOneLog: number - freeMultiplierBoost: number totalMultiplierBoost: number globalCoinMultiplier: Decimal @@ -769,9 +797,6 @@ export interface GlobalVariables { tuSevenMulti: number currentTab: Tabs - researchfiller1: string - researchfiller2: string - ordinals: readonly [ 'first', 'second', @@ -805,12 +830,6 @@ export interface GlobalVariables { maxexponent: number - effectiveLevelMult: number - optimalOfferingTimer: number - optimalObtainiumTimer: number - - runeSum: number - globalAntMult: Decimal antMultiplier: Decimal @@ -841,28 +860,6 @@ export interface GlobalVariables { bonusant11: number bonusant12: number - rune1level: number - rune2level: number - rune3level: number - rune4level: number - rune5level: number - rune1Talisman: number - rune2Talisman: number - rune3Talisman: number - rune4Talisman: number - rune5Talisman: number - - talisman1Effect: ArrayStartingWithNull - talisman2Effect: ArrayStartingWithNull - talisman3Effect: ArrayStartingWithNull - talisman4Effect: ArrayStartingWithNull - talisman5Effect: ArrayStartingWithNull - talisman6Effect: ArrayStartingWithNull - talisman7Effect: ArrayStartingWithNull - - talisman6Power: number - talisman7Quarks: number - settingscreen: string talismanResourceObtainiumCosts: number[] @@ -885,54 +882,24 @@ export interface GlobalVariables { obtainiumGain: number mirrorTalismanStats: ArrayStartingWithNull - antELO: number - effectiveELO: number timeWarp: boolean - blessingMultiplier: number - spiritMultiplier: number - runeBlessings: number[] - runeSpirits: number[] - - effectiveRuneBlessingPower: number[] - effectiveRuneSpiritPower: number[] - - blessingBaseCost: number - spiritBaseCost: number - triggerChallenge: number prevReductionValue: number buildingSubTab: BuildingSubtab - // number000 of each before Diminishing Returns - blessingbase: ArrayStartingWithNull - blessingDRPower: ArrayStartingWithNull - giftbase: number[] - giftDRPower: number[] - benedictionbase: ArrayStartingWithNull - benedictionDRPower: ArrayStartingWithNull - // 10 Million of each before Diminishing returns on first number 200k for second, and 10k for the last few - platonicCubeBase: number[] - platonicDRPower: number[] - - cubeBonusMultiplier: ArrayStartingWithNull - tesseractBonusMultiplier: ArrayStartingWithNull - hypercubeBonusMultiplier: ArrayStartingWithNull - platonicBonusMultiplier: number[] autoOfferingCounter: number - researchOrderByCost: number[] - viscosityPower: number[] dilationMultiplier: number[] hyperchallengeMultiplier: number[] illiteracyPower: number[] deflationMultiplier: number[] extinctionMultiplier: number[] - droughtMultiplier: number[] + droughtSalvage: number[] recessionPower: number[] corruptionPointMultipliers: number[] @@ -981,7 +948,9 @@ export interface GlobalVariables { TIME_PER_AMBROSIA: number TIME_PER_RED_AMBROSIA: number - currentSingChallenge: keyof Player['singularityChallenges'] | undefined + currentSingChallenge: SingularityChallengeDataKeys | undefined + + coinVanityThresholds: number[] } export interface SynergismEvents { diff --git a/translations/da.json b/translations/da.json index cfdcdb0f7..55525419c 100644 --- a/translations/da.json +++ b/translations/da.json @@ -2772,7 +2772,7 @@ "duplication": "Bonus Duplication Rune Levels: {{x}}", "prism": "Bonus Prism Rune Levels: {{x}}", "thrift": "Bonus Thrift Rune Levels: {{x}}", - "SI": "Bonus SI Rune Levels: {{x}}" + "superiorIntellect": "Bonus SI Rune Levels: {{x}}" }, "mythicEffects": { "exemption": "Mythic Effect: +400 Duplication Rune Levels!", @@ -2793,11 +2793,11 @@ "blessingLevel": "<>: {{amount}}", "blessingPower": "Blessing Power: <> =-=-= Effect: <>x {{reward}}!", "rewards": { - "0": "Global Speed", - "1": "Multiplier Boosts", - "2": "Ant Sacrifice Rewards", - "3": "Delay for A.Boost Cost", - "4": "Obtainium --> Ant Mult." + "speed": "Global Speed", + "duplication": "Multiplier Boosts", + "prism": "Ant Sacrifice Rewards", + "thrift": "Delay for A.Boost Cost", + "superiorIntellect": "Obtainium --> Ant Mult." }, "increaseLevel": "Increase Level By <> [Cost: <> Offerings]" }, @@ -2805,11 +2805,11 @@ "spiritLevel": "Spirit level: <>", "spiritPower": "Spirit Power: <> =-=-= Effect: <>{{reward}}!", "rewards": { - "0": "x Global Speed", - "1": "x Wow! Cubes by Ascension", - "2": "x Additional levels to Crystal caps", - "3": "x Obtainium", - "4": "% stronger Research 4x9 effect" + "speed": "x Global Speed", + "duplication": "x Wow! Cubes by Ascension", + "prism": "x Additional levels to Crystal caps", + "thrift": "x Obtainium", + "superiorIntellect": "% stronger Research 4x9 effect" }, "buyUpTo": "Buy up to <> Levels per purchase, if affordable. [Input to toggle]" }, diff --git a/translations/de.json b/translations/de.json index 2263b4c30..7c8ca4a1f 100644 --- a/translations/de.json +++ b/translations/de.json @@ -1997,8 +1997,7 @@ }, "toString": { "noMinimum": "Keine minimale Menge an Singularitäten zum Kaufen erforderlich", - "costNextLevel": "Cost for next level: {{amount}} Golden Quarks.", - "spentQuarks": "Spent Quarks: {{amount}}" + "costNextLevel": "Cost for next level: {{amount}} Golden Quarks." }, "data": { "goldenQuarks1": { @@ -3651,7 +3650,7 @@ "duplication": "Bonus Duplication Rune Levels: {{x}}", "prism": "Bonus Prism Rune Levels: {{x}}", "thrift": "Bonus Thrift Rune Levels: {{x}}", - "SI": "Bonus SI Rune Levels: {{x}}" + "superiorIntellect": "Bonus SI Rune Levels: {{x}}" }, "mythicEffects": { "exemption": "Mythic Effect: +400 Duplication Rune Levels!", @@ -3672,11 +3671,11 @@ "blessingLevel": "<>: {{amount}}", "blessingPower": "Blessing Power: <> =-=-= Effect: <>x {{reward}}!", "rewards": { - "0": "Globale-Geschwindigkeit", - "1": "Multiplier Boosts", - "2": "Ant Sacrifice Rewards", - "3": "Delay for A.Boost Cost", - "4": "Obtainium --> Ant Mult." + "speed": "Globale-Geschwindigkeit", + "duplication": "Multiplier Boosts", + "prism": "Ant Sacrifice Rewards", + "thrift": "Delay for A.Boost Cost", + "superiorIntellect": "Obtainium --> Ant Mult." }, "increaseLevel": "Increase Level By <> [Cost: <> Offerings]" }, @@ -3684,11 +3683,11 @@ "spiritLevel": "Spirit level: <>", "spiritPower": "Spirit Power: <> =-=-= Effect: <>{{reward}}!", "rewards": { - "0": "x Globale-Geschwindigkeit", - "1": "x Wow! Cubes by Ascension", - "2": "x Additional levels to Crystal caps", - "3": "x Obtainium", - "4": "% stronger Research 4x9 effect" + "speed": "x Globale-Geschwindigkeit", + "duplication": "x Wow! Cubes by Ascension", + "prism": "x Additional levels to Crystal caps", + "thrift": "x Obtainium", + "superiorIntellect": "% stronger Research 4x9 effect" }, "buyUpTo": "Buy up to <> Levels per purchase, if affordable. [Input to toggle]" }, @@ -4481,7 +4480,6 @@ "BaseTimer": "Base Timer (ms):", "Calculator4": "PL-AT δ Discount:", "SingularityCount": "Singularity Milestone Bonus:", - "InfiniteAscent": "Infinite Ascent Bonus:", "SingularityPerkBonus": "Singularity Perk Bonus:", "Total": "Final Add Code Interval (ms):" }, diff --git a/translations/en.json b/translations/en.json index 85c30ecd2..ab93b99d6 100644 --- a/translations/en.json +++ b/translations/en.json @@ -29,32 +29,33 @@ "notEnoughBlueberries": "You do not have enough Blueberries to activate this module.", "timeThresholds": "Your Ambrosia Bar Point requirements have been doubled <> times due to lifetime Ambrosia. Next doubling in <> lifetime Ambrosia.", "cubeUpgradeThresholds": "Your Ambrosia Bar Point requirements have been doubled <> times due to lifetime Ambrosia. Next doubling in <> lifetime Ambrosia. <>!", - "cubeUpgradeRedBarFills": "Your Red Bar has filled <> times while Cookie Upgrade 29 is purchased, giving <>!", + "cubeUpgradeRedBarFills": "Your Red Bar has filled <> times while Cookie Upgrade 29 is purchased, giving <>!", "ambrosiaBuyPrompt": "How many Ambrosia would you like to spend? You have {{amount}} Ambrosia. Type -1 to use max!", "refund": "You have refunded all spent Blueberries and Ambrosia.", "refundWord": "Refund", "ignoreEXALT": "[<>] This upgrade is active even if an EXALT says otherwise!", - "notUnlocked": "Clear No Singularity Upgrades, Tier 1!", + "notUnlocked": "Clear No Golden Quark Upgrades, Tier 1!", + "ambrosiaCost": "Cost for next level: <> Ambrosia", "data": { "ambrosiaTutorial": { "name": "Ambrosia Tutorial Module", - "description": "Blueberries generate Ambrosia over time. Spend them in the Ambrosia tree! +5% Cubes/lvl, +1% Quarks/lvl", - "effect": "This tutorial module increases cube gain by {{cubeAmount}}% and quarks by {{quarkAmount}}%" + "description": "Blueberries generate Ambrosia over time. Spend them in the Ambrosia tree! +5% Cubes per level, +1% Quarks per level", + "effect": "This tutorial module increases Cube gain by {{cubeAmount}}% and Quarks by {{quarkAmount}}%" }, "ambrosiaQuarks1": { "name": "Ambrosia Quark Module I", - "description": "Congrats on completing the tutorial module. You need to have it maxed to buy levels of this. +1% Quarks/lvl.", - "effect": "This quark module increases Quark gain by {{amount}}%" + "description": "Congrats on completing the Tutorial module. You need to have it maxed to buy levels of this. +1% Quarks per level.", + "effect": "This Quark module increases Quark gain by {{amount}}%" }, "ambrosiaCubes1": { "name": "Ambrosia Cube Module I", - "description": "Congrats on completing the tutorial module. You need to have it maxxed to buy levels of this. +5% Cubes/lvl. Every 5 levels gives 1.1x Cubes.", - "effect": "This cube module increases cube gain by {{amount}}%" + "description": "Congrats on completing the Tutorial module. You need to have it maxxed to buy levels of this. +5% Cubes per level. Every 5 levels give 1.1x Cubes.", + "effect": "This Cube module increases Cube gain by {{amount}}%" }, "ambrosiaLuck1": { "name": "Ambrosia Luck Module I", - "description": "Congrats on completing the tutorial module. You need to have it maxxed to buy levels of this. ☘ +2 Ambrosia Luck/lvl. Every 10 levels gives ☘ +12 more!", - "effect": "This luck module increases ☘ Ambrosia Luck by {{amount}}" + "description": "Congrats on completing the Tutorial module. You need to have it maxxed to buy levels of this. +2 Ambrosia Luck per lvl. Every 10 levels give +12 more!", + "effect": "This luck module increases Ambrosia Luck by {{amount}}" }, "ambrosiaCubeQuark1": { "name": "Ambrosia Cube-Quark Hybrid Module I", @@ -63,28 +64,28 @@ }, "ambrosiaLuckQuark1": { "name": "Ambrosia Luck-Quark Hybrid Module I", - "description": "A first generation hybrid module. Gain +0.01% more Quarks per level per 1 ☘ Ambrosia Luck! Diminishing returns after 1,000 Luck.", + "description": "A first generation hybrid module. Gain +0.01% more Quarks per level per 1 Ambrosia Luck! Diminishing returns after 1,000 Luck.", "effect": "This first generation hybrid module increases Quark gain by {{amount}}%" }, "ambrosiaQuarkCube1": { "name": "Ambrosia Quark-Cube Hybrid Module I", - "description": "A first generation hybrid module. Gain +0.1% more cubes per level per digit in your Quark balance, squared!", - "effect": "This first generation hybrid module increases cube gain by {{amount}}%" + "description": "A first generation hybrid module. Gain +0.1% more Cubes per level per digit in your Quark balance, squared!", + "effect": "This first generation hybrid module increases Cube gain by {{amount}}%" }, "ambrosiaLuckCube1": { "name": "Ambrosia Luck-Cube Hybrid Module I", - "description": "A first generation hybrid module. Gain +0.05% more cubes per level per 1 ☘ Ambrosia Luck!", - "effect": "This first generation hybrid module increases cube gain by {{amount}}%" + "description": "A first generation hybrid module. Gain +0.05% more Cubes per level per 1 Ambrosia Luck!", + "effect": "This first generation hybrid module increases Cube gain by {{amount}}%" }, "ambrosiaCubeLuck1": { "name": "Ambrosia Cube-Luck Hybrid Module I", - "description": "A first generation hybrid module. Gain ☘ +0.02 Ambrosia Luck per level per digit in the values of all cube types.", - "effect": "This first generation hybrid module increases ☘ Ambrosia Luck by {{amount}}" + "description": "A first generation hybrid module. Gain +0.02 Ambrosia Luck per level per digit in the values of all cube types.", + "effect": "This first generation hybrid module increases Ambrosia Luck by {{amount}}" }, "ambrosiaQuarkLuck1": { "name": "Ambrosia Quark-Luck Hybrid Module I", - "description": "A first generation hybrid module. Gain ☘ +0.02 Ambrosia Luck per level per digit in your Quark balance, squared.", - "effect": "This first generation hybrid module increases ☘ Ambrosia Luck by {{amount}}" + "description": "A first generation hybrid module. Gain +0.02 Ambrosia Luck per level per digit in your Quark balance, squared.", + "effect": "This first generation hybrid module increases Ambrosia Luck by {{amount}}" }, "ambrosiaQuarks2": { "name": "Ambrosia Quark Module II", @@ -93,13 +94,13 @@ }, "ambrosiaCubes2": { "name": "Ambrosia Cube Module II", - "description": "Self-Synergies! Gain +10% cubes / lvl , +1% for every 10 levels in Ambrosia Cube Module I. Every 5 levels gives 1.15x cubes.", - "effect": "This second generation Cube module increases cube gain by {{amount}}%" + "description": "Self-Synergies! Gain +10% cubes per level, +1% for every 10 levels in Ambrosia Cube Module I. Every 5 levels give 1.15x Cubes.", + "effect": "This second generation Cube module increases Cube gain by {{amount}}%" }, "ambrosiaLuck2": { "name": "Ambrosia Luck Module II", - "description": "Self-Synergies! Gain ☘ +3 Ambrosia Luck/lvl, +0.3 for every 10 levels in Ambrosia Luck Module I. Every 10 levels gives ☘ +40 more!", - "effect": "This second generation Luck module increases ☘ Ambrosia Luck by {{amount}}" + "description": "Self-Synergies! Gain +3 Ambrosia Luck per level, +0.3 for every 10 levels in Ambrosia Luck Module I. Every 10 levels give +40 more!", + "effect": "This second generation Luck module increases Ambrosia Luck by {{amount}}" }, "ambrosiaQuarks3": { "name": "Ambrosia Quark Module III", @@ -108,32 +109,37 @@ }, "ambrosiaCubes3": { "name": "Ambrosia Cube Module III", - "description": "A darkness emboldens this module. +20% cubes / lvl, and the effect is 3% stronger per level of Ambrosia Cube Module II. Every 5 levels gives 1.2x cubes.", - "effect": "This third generation Cube module increases cube gain by {{amount}}%" + "description": "A darkness emboldens this module. +20% Cubes per level, and the effect is 3% stronger per level of Ambrosia Cube Module II. Every 5 levels give 1.2x Cubes.", + "effect": "This third generation Cube module increases Cube gain by {{amount}}%" }, "ambrosiaLuck3": { "name": "Ambrosia Luck Module III", - "description": "A darkness emboldens this module. +1 ☘ Ambrosia Luck/lvl, and the effect is multiplied by the total number of Blueberries you own!", - "effect": "This third generation Luck module increases ☘ Ambrosia Luck by {{amount}}" + "description": "A darkness emboldens this module. +1 Ambrosia Luck per level, and the effect is multiplied by the total number of Blueberries you own!", + "effect": "This third generation Luck module increases Ambrosia Luck by {{amount}}" + }, + "ambrosiaLuck4": { + "name": "The Eight Leaf Clover", + "description": "My magical lucky charms! +0.01% Ambrosia Luck per level, multiplied by the number of total digits in your Lifetime Ambrosia and Red Ambrosia!", + "effect": "This fourth generation Luck module increases Ambrosia Luck by {{amount}}" }, "ambrosiaPatreon": { "name": "Shameless, Ambrosial Patreon Reminder", - "description": "In every realm, Platonic sells out. +1% Ambrosia Bar Point speed for every +1% Quarks by the Patreon bonus!", + "description": "In every realm, Platonic sells out. The Patreon bonus will also impact your Ambrosia Bar Point generation.", "effect": "Generous Patreon supporters grant you {{amount}}% more Ambrosia Bar Points. If you are not already, consider buying PseudoCoins to support development!" }, "ambrosiaObtainium1": { "name": "RNG-based Obtainium Booster", - "description": "Gain +0.1% Obtainium per ☘ Ambrosia Luck!", + "description": "Gain +0.1% Obtainium per Ambrosia Luck!", "effect": "This module increases Obtainium gain by {{amount}}%!" }, "ambrosiaOffering1": { - "name": "RNG-based Offering Booster", - "description": "Gain +0.1% Offerings per ☘ Ambrosia Luck!", - "effect": "This module increases Offering gain by {{amount}}%!" + "name": "RNG-based Offerings Booster", + "description": "Gain +0.1% Offerings per Ambrosia Luck!", + "effect": "This module increases Offerings gain by {{amount}}%!" }, "ambrosiaHyperflux": { "name": "Hyperfluxed Wow! Cube Industrial Production", - "description": "Gain +1% cubes per level, per level of Platonic Upgrade 4x4 purchased (multiplicative!!!)", + "description": "Gain +1% (3-7D) Cubes per level, per level of Platonic Upgrade 4x4 purchased (multiplicative!!!)", "effect": "This module increases Cubes gain by {{amount}}%!" }, "ambrosiaBaseObtainium1": { @@ -142,9 +148,9 @@ "effect": "This module increases Base Obtainium by {{amount}}!" }, "ambrosiaBaseOffering1": { - "name": "Basic Offering Booster", - "description": "+1 Base Offering per level! Exceed the possibilities of your strength.", - "effect": "This module increases Base Offering by {{amount}}!" + "name": "Basic Offerings Booster", + "description": "+1 Base Offerings per level! Exceed the possibilities of your strength.", + "effect": "This module increases Base Offerings by {{amount}}!" }, "ambrosiaBaseObtainium2": { "name": "Based Obtainium Booster", @@ -152,9 +158,9 @@ "effect": "This module increases Base Obtainium by {{amount}}!" }, "ambrosiaBaseOffering2": { - "name": "Based Offering Booster", - "description": "+1 Base Offering per level! Channel your... I got nothing.", - "effect": "This module increases Base Offering by {{amount}}!" + "name": "Based Offerings Booster", + "description": "+1 Base Offerings per level! Channel your... I got nothing.", + "effect": "This module increases Base Offerings by {{amount}}!" }, "ambrosiaSingReduction1": { "name": "Singularity Stabilizer Module", @@ -163,18 +169,28 @@ }, "ambrosiaInfiniteShopUpgrades1": { "name": "Indigo Infinity Shop Voucher", - "description": "A Shop Voucher, printed on an indigo tablet. Each grants +1 effective level the 'infinity' shop upgrades.", - "effect": "'Infinity' shop upgrades level +{{amount}}" + "description": "A Shop Voucher, printed on an indigo tablet. Each grants +1 effective level of the 'Infinity CO.' Quark Shop upgrades.", + "effect": "You own {{amount}} Indigo Infinity Shop Vouchers!" }, "ambrosiaInfiniteShopUpgrades2": { "name": "Violet Infinity Shop Voucher", - "description": "A Shop Voucher, printed on a violet tablet. Each grants +1 effective level the 'infinity' shop upgrades.", - "effect": "'Infinity' shop upgrades level +{{amount}}" + "description": "A Shop Voucher, printed on a violet tablet. Each grants +1 effective level of the 'Infinity CO.' Quark Shop upgrades.", + "effect": "You own {{amount}} Violet Infinity Shop Vouchers!" }, "ambrosiaSingReduction2": { "name": "Exo-Chromatic Kinetic Post-Modulated Singularity Stabilizer Module", "description": "Retrofitted with... those kinds of Stabilizers, your EXALTED Singularities are a bit smoother. Singularity Penalties are delayed by 1 singularity, if you are ACTUALLY! in an EXALT.", "effect": "Singularity Penalties are as if you were {{amount}} Singularities lower." + }, + "ambrosiaTalismanBonusRuneLevel": { + "name": "Talisman Ambrosia Embedding Module", + "description": "With the power of a lot of Ambrosia, grant +0.5% Talisman Power per level! Additive with other bonuses.", + "effect": "Talisman Power is increased by {{amount}}%" + }, + "ambrosiaRuneOOMBonus": { + "name": "Rune Coefficient Module", + "description": "For each of the first five runes, add 1 to their Rune Coefficient per level! For Infinite Ascent, add +0.001 to the Rune Coefficient per level.", + "effect": "Runes 1-5 have +{{amount}} Rune Coefficient; IA Rune has +{{amount2}} Rune Coefficient" } }, "loadouts": { @@ -197,10 +213,12 @@ "redAmbrosia": { "redAmbrosia": "Red Ambrosia", "amount": "Ambrosia: <> Current | <> Lifetime", - "purchaseWarning": "<> Unlike Blueberry Modules, you <> refund this upgrade. Please spend wisely!", - "perGen": "Currently gaining <> Red Ambrosia with a <> chance of another. [<>]", + "purchaseWarning": "<> Unlike Blueberry Modules, you <> refund this upgrade.", + "perGen": "Currently gaining <> Red Ambrosia with a <> chance of another. [<>]", "redAmbrosiaBuyPrompt": "How many Red Ambrosia would you like to spend? You have {{amount}} Red Ambrosia. Type -1 to use max!", "notUnlocked": "Clear No Ambrosia Upgrades, Tier 1!", + "redAmbrosiaCost": "Cost for next level: <> Red Ambrosia", + "redAmbrosiaSpent": "Total Red Ambrosia spent: <>", "data": { "tutorial": { "name": "A beginners guide to Red Ambrosia", @@ -209,18 +227,18 @@ }, "conversionImprovement1": { "name": "Ambrosia Luck Converter Improvement", - "description": "Every 20 Ambrosia luck converts to 1 Red Ambrosia Luck? Nah. Let's lower that ratio by 1 per level.", - "effect": "Reduces the number of Ambrosia Luck needed by <>!" + "description": "Every 20 Ambrosia luck converts to 1 Red Luck? Nah. Let's lower that ratio by 1 per level.", + "effect": "Reduces the number of Ambrosia Luck needed by <>!" }, "conversionImprovement2": { "name": "Ambrosia Luck Converter Improvement II: The Red Electric Boogaloo", - "description": "Every 20 Ambrosia luck converts to 1 Red Ambrosia Luck? Nah. Let's lower that ratio by 1 per level.", - "effect": "Reduces the number of Ambrosia Luck needed by <>!" + "description": "Every 20 Ambrosia luck converts to 1 Red Luck? Nah. Let's lower that ratio by 1 per level.", + "effect": "Reduces the number of Ambrosia Luck needed by <>!" }, "conversionImprovement3": { "name": "Ambrosia Luck Converter Improvement III and a half", - "description": "Every 20 Ambrosia luck converts to 1 Red Ambrosia Luck? Nah. Let's lower that ratio by 1 per level.", - "effect": "Reduces the number of Ambrosia Luck needed by <>!" + "description": "Every 20 Ambrosia luck converts to 1 Red Luck? Nah. Let's lower that ratio by 1 per level.", + "effect": "Reduces the number of Ambrosia Luck needed by <>!" }, "freeTutorialLevels": { "name": "More Blueberry Tutorial Module Levels!", @@ -254,18 +272,18 @@ }, "regularLuck": { "name": "Ossified Blueberry Tactics", - "description": "Most strange. Adding this gold-colored wax on your blueberries makes you slightly more likely to get Ambrosia! +2 Ambrosia Luck / level.", + "description": "Most strange. Adding this gold-colored wax on your blueberries makes you slightly more likely to get Ambrosia! +2 Ambrosia Luck per level.", "effect": "This upgrade increases Ambrosia Luck by {{amount}}!" }, "redGenerationSpeed": { "name": "Millenium-Aged Red Ambrosia", - "description": "Surely, with how long this Ambrosia is left to sit, it knows something about time. +0.3% more Red Ambrosia Bar Points per level.", - "effect": "This upgrade increases Red Ambrosia Bar Point gain by {{amount}}!" + "description": "Surely, with how long this Ambrosia is left to sit, it knows something about time. +0.3% more Red Bar Points per level.", + "effect": "This upgrade increases Red Bar Point gain by {{amount}}!" }, "redLuck": { "name": "The Dice that Decide Your Fate", - "description": "A life on every face, until you're finally left with an eight. +1 Red Ambrosia Luck per level!", - "effect": "This upgrade increases Red Ambrosia Luck by {{amount}}!" + "description": "A life on every face, until you're finally left with an eight. +1 Red Luck per level!", + "effect": "This upgrade increases Red Luck by {{amount}}!" }, "redAmbrosiaCube": { "name": "Cubes \"made\" of Red Ambrosia", @@ -294,8 +312,8 @@ }, "infiniteShopUpgrades": { "name": "Red Infinity Shop Voucher", - "description": "A Shop Voucher, printed on a red tablet. Each grants +1 effective level the 'infinity' shop upgrades.", - "effect": "'Infinity' shop upgrades level +{{amount}}" + "description": "A Shop Voucher, printed on a red tablet. Each grants +1 effective level of the 'Infinity CO.' Quark Shop upgrades.", + "effect": "You own {{amount}} Red Infinity Shop Vouchers!" }, "redAmbrosiaAccelerator": { "name": "Red-Blue Ultrafusion", @@ -311,314 +329,1214 @@ "name": "The Sands of Time II", "description": "This gives +0.1% Ambrosia Bar Point gain per level, but you can purchase a ton of levels of this!", "effect": "This upgrade increases Ambrosia Bar Point gain by {{amount}}!" + }, + "salvageYinYang": { + "name": "Yin and Yang: Salvage Edition", + "description": "This upgrade, oddly enough, increases both your Positive and Negative Salvage by 10 per level! <>", + "effect": "Your Positive and Negative Salvage are both increased by {{amount}}." } } }, "achievements": { "descriptions": { - "1": "[[1]] A Loyal Employee: Hire your first Worker.", - "2": "[[2]] Small Business: Hire 10 Workers.", - "3": "[[3]] Now we're synergizing!: Hire 100 Workers.", - "4": "[[4]] Gaining Redundancies: Hire 1,000 Workers.", - "5": "[[5]] A cog in the machine: Hire 5,000 Workers.", - "6": "[[6]] A nail in the machine: Hire 10,000 Workers.", - "7": "[[7]] Are we even in the machine anymore?: Hire 20,000 Workers.", - "8": "[[8]] STONKS!!!: Purchase 1 Investment.", - "9": "[[9]] Planning ahead: Purchase 10 Investments.", - "10": "[[10]] Inside Trading: Purchase 100 Investments.", - "11": "[[11]] Outside Trading?: Purchase 1,000 Investments.", - "12": "[[12]] Market Takeover: Purchase 5,000 Investments.", - "13": "[[13]] Trickle-Down Economics: Purchase 10,000 Investments.", - "14": "[[14]] Eliminated Regulation: Purchase 20,000 Investments.", - "15": "[[15]] Stationery!: Build 1 Printer.", - "16": "[[16]] Printing Press: Build 10 Printers.", - "17": "[[17]] It prints free money!: Build 100 Printers.", - "18": "[[18]] Solving Our Debts: Build 1,000 Printers.", - "19": "[[19]] Monopolizing the market: Build 5,000 Printers.", - "20": "[[20]] We're running out of Ink!: Build 10,000 Printers.", - "21": "[[21]] 3D-printing the universe: Build 20,000 Printers.", - "22": "[[22]] A national treasure: Establish 1 Coin Mint.", - "23": "[[23]] Now with competition!: Establish 10 Coin Mints.", - "24": "[[24]] Counterfeiting with Style!: Establish 100 Coin Mints.", - "25": "[[25]] Why do we need all these?: Establish 1000 Coin Mints.", - "26": "[[26]] No really, why??: Establish 5,000 Coin Mints.", - "27": "[[27]] Is no one to stop us???: Establish 10,000 Coin Mints.", - "28": "[[28]] Oh well, time to mint: Establish 20,000 Coin Mints.", - "29": "[[29]] Newton's Apprentice: Create 1 Alchemy.", - "30": "[[30]] Lab Work: Create 10 Alchemies.", - "31": "[[31]] Satanic Becomings: Create 66 Alchemies.", - "32": "[[32]] Satan Incarnate: Create 666 Alchemies.", - "33": "[[33]] Is this more demonic?: Create 6,666 Alchemies.", - "34": "[[34]] Golden Paradise: Create 17,777 Alchemies.", - "35": "[[35]] Unlocking secrets to the world: Create 42,777 Alchemies.", - "36": "[[36]] Leveling up: Prestige for at least 1 Diamond.", - "37": "[[37]] High-Tiered: Prestige for at least 1e6 Diamonds.", - "38": "[[38]] Highly Regarded: Prestige for at least 1e100 Diamonds.", - "39": "[[39]] Prestigious: Prestige for at least 1e1,000 Diamonds.", - "40": "[[40]] Legendary: Prestige for at least 1e10,000 Diamonds.", - "41": "[[41]] Divine: Prestige for at least 1e77,777 Diamonds.", - "42": "[[42]] Perfectly Respected: Prestige for at least 1e250,000 Diamonds.", - "43": "[[43]] A Simple Detour: Transcend for at least 1 Mythos.", - "44": "[[44]] Tunnel Vision: Transcend for at least 1e6 Mythos.", - "45": "[[45]] Risen from the Ashes: Transcend for at least 1e50 Mythos.", - "46": "[[46]] Paradigm Shift: Transcend for at least 1e308 Mythos.", - "47": "[[47]] Preparation: Transcend for at least 1e1,500 Mythos.", - "48": "[[48]] Revising the Plan: Transcend for at least 1e25,000 Mythos.", - "49": "[[49]] Leaving the Universe: Transcend for at least 1e100,000 Mythos.", - "50": "[[50]] Going Quantum: Reincarnate for at least 1 Particle.", - "51": "[[51]] Tunneling Vision: Reincarnate for at least 100,000 Particles.", - "52": "[[52]] Simulating the World: Reincarnate for at least 1e30 Particles.", - "53": "[[53]] Multidimensional Creation: Reincarnate for at least 1e200 Particles.", - "54": "[[54]] Lepton Dance: Reincarnate for at least 1e1,500 Particles.", - "55": "[[55]] Do we have enough yet?: Reincarnate for at least 1e5,000 Particles.", - "56": "[[56]] I Feel Luck in My Cells: Reincarnate for at least 1e7,777 Particles.", - "57": "[[57]] One Way Only: Prestige without buying Multipliers.", - "58": "[[58]] Authentic Shifting: Transcend without having bought a Multiplier.", - "59": "[[59]] The Singularity: Reincarnate without having bought a Multiplier.", - "60": "[[60]] Gotta go SLOW!: Prestige without buying Accelerators or Accelerator Boosts.", - "61": "[[61]] I'm really going slow: Transcend without having bought Accelerators or Boosts.", - "62": "[[62]] Are we there yet?: Reincarnate without having bought Accelerators or Boosts.", - "63": "[[63]] A careful search for Diamonds: Get 1e120,000 Coins in [Reduced Diamonds] without buying Accelerators or Boosts.", - "64": "[[64]] Very Based: Prestige without purchasing Coin Upgrades.", - "65": "[[65]] Miser: Transcend without purchasing Coin Upgrades.", - "66": "[[66]] True Miser: Transcend without purchasing Coin or Diamond Upgrades.", - "67": "[[67]] Coinless Pursuit: Reincarnate without purchasing Coin Upgrades.", - "68": "[[68]] Diamonds don't matter to me!: Reincarnate without purchasing Coin or Diamond Upgrades.", - "69": "[[69]] Leave nothing behind: Reincarnate without purchasing Coin, Diamond or Mythos Upgrades.", - "70": "[[70]] Leave NOTHING behind.: Reincarnate without purchasing Coin, Diamond, Mythos, or Generator Upgrades.", - "71": "[[71]] Out of Order: Buy Generator Upgrade Row 1, #2 first in a Transcension (IV -> III)", - "72": "[[72]] More Out of Order: Buy Generator Upgrade Row 1, #3 first in a Transcension (III -> II)", - "73": "[[73]] Four's a Company: Buy Generator Upgrade Row 1, #4 first in a Transcension (II -> I)", - "74": "[[74]] Five's a Croud: Buy Generator Upgrade Row 1, #5 first in a Transcension (I -> V)", - "75": "[[75]] Vaseline without the Machine: Exit [No Multiplier] with at least 1e1000 coins and without any of the row 1 generator upgrades.", - "76": "[[76]] Rage against the Machine: Exit [No Accelerator] with at least 1e1000 coins and without any of the row 1 generator upgrades.", - "77": "[[77]] Amish Paradise: Exit [No Shards] with at least 1e99,999 coins and without any of the row 1 generator upgrades.", - "78": "[[78]] Single-Cell: Complete [No Multiplier] once.", - "79": "[[79]] Solidarity: Complete [No Multiplier] three times.", - "80": "[[80]] Duplication-Free!: Complete [No Multiplier] five times.", - "81": "[[81]] Multitasking Challenged: Complete [No Multiplier] ten times.", - "82": "[[82]] No Deaths: Complete [No Multiplier] twenty times.", - "83": "[[83]] Population One: Complete [No Multiplier] fifty times.", - "84": "[[84]] Insert Another Token: Complete [No Multiplier] seventy-five times.", - "85": "[[85]] Slow Start: Complete [No Accelerator] once", - "86": "[[86]] Respawn Rate -12%: Complete [No Accelerator] three times.", - "87": "[[87]] Putting the Breaks On: Complete [No Accelerator] five times.", - "88": "[[88]] Racing a Sloth...: Complete [No Accelerator] ten times.", - "89": "[[89]] ... and Losing.: Complete [No Accelerator] twenty times.", - "90": "[[90]] Planck Distance Traveled: Complete [No Accelerator] fifty times.", - "91": "[[91]] Inverse-Ackermann Growth: Complete [No Accelerator] seventy-five times.", - "92": "[[92]] Intact: Complete [No Shards] once.", - "93": "[[93]] Augments are Stupid!: Complete [No Shards] three times.", - "94": "[[94]] Grandmasters are Brilliant!: Complete [No Shards] five times.", - "95": "[[95]] Gotta get those Grandmasters Stronger: Complete [No Shards] ten times.", - "96": "[[96]] Summoning Enhancements: Complete [No Shards] twenty times.", - "97": "[[97]] Magic 99/99: Complete [No Shards] fifty times.", - "98": "[[98]] Perfect Foresight: Complete [No Shards] seventy-five times.", - "99": "[[99]] Inflation: Complete [Cost+] once.", - "100": "[[100]] Hyperinflation: Complete [Cost+] three times.", - "101": "[[101]] Market Bubble: Complete [Cost+] five times.", - "102": "[[102]] Bull Market: Complete [Cost+] ten times.", - "103": "[[103]] Wealth Inequality: Complete [Cost+] twenty times.", - "104": "[[104]] Severe Overpay: Complete [Cost+] fifty times.", - "105": "[[105]] Societal Collapse: Complete [Cost+] seventy-five times.", - "106": "[[106]] Excavation: Complete [Reduced Diamonds] once.", - "107": "[[107]] Digging Deep: Complete [Reduced Diamonds] three times.", - "108": "[[108]] Frack As Needed: Complete [Reduced Diamonds] five times.", - "109": "[[109]] Unobtainium Pickaxe: Complete [Reduced Diamonds] ten times.", - "110": "[[110]] Fortune III: Complete [Reduced Diamonds] twenty times.", - "111": "[[111]] Every kiss...: Complete [Reduced Diamonds] fifty times.", - "112": "[[112]] ...begins with K.: Complete [Reduced Diamonds] seventy-five times.", - "113": "[[113]] Tax evasion!: Complete {[Tax+]} once.", - "114": "[[114]] Keeping up with the Joneses: Complete {[Tax+]} twice.", - "115": "[[115]] Offshore deposits: Complete {[Tax+]} three times.", - "116": "[[116]] Bribing officials: Complete {[Tax+]} five times.", - "117": "[[117]] Becoming President: Complete {[Tax+]} ten times.", - "118": "[[118]] Charitable Donation: Complete {[Tax+]} fifteen times.", - "119": "[[119]] IRS Audit: Complete {[Tax+]} twenty-five times.", - "120": "[[120]] Is there anybody in there?: Complete {[No Accelerator/Multiplier]} once.", - "121": "[[121]] Human being: Complete {[No Accelerator/Multiplier]} twice.", - "122": "[[122]] Interdimensional: Complete {[No Accelerator/Multiplier]} three times.", - "123": "[[123]] A slow nickel: Complete {[No Accelerator/Multiplier]} five times.", - "124": "[[124]] Multipliers don't even work 0/5: Complete {[No Accelerator/Multiplier]} ten times.", - "125": "[[125]] Accelerators don't even work -5/5: Complete {[No Accelerator/Multiplier]} fifteen times.", - "126": "[[126]] ACCELERATOR BOOSTS DON'T EVEN WORK -100/5: Complete {[No Accelerator/Multiplier]} twenty-five times.", - "127": "[[127]] I hate this challenge: Complete Cost++ Once.", - "128": "[[128]] A costly mistake: Complete Cost++ Twice.", - "129": "[[129]] Impetus: Complete Cost++ Three Times.", - "130": "[[130]] Are you broke yet? Complete Cost++ Five Times.", - "131": "[[131]] The world of Finance: Complete Cost++ Ten Times.", - "132": "[[132]] Marginal Gains: Complete Cost++ Twenty Times.", - "133": "[[133]] I buy these: Complete Cost++ Twenty-Five Times.", - "134": "[[134]] Agnostic: Complete No Runes Once.", - "135": "[[135]] Ant-i Runes: Complete No Runes Twice.", - "136": "[[136]] Isn't it getting tiresome?: Complete No Runes Three Times.", - "137": "[[137]] Machine does not accept offerings: Complete No Runes Five Times.", - "138": "[[138]] Runes Suck 1/5: Complete No Runes Ten Times.", - "139": "[[139]] I didn't even notice Prism was gone: Complete No Runes Twenty Times.", - "140": "[[140]] Atheist: Complete No Runes Twenty-Five Times.", - "141": "[[141]] Sadism: Complete {[Sadistic I]} Once.", - "142": "[[142]] Masochism: Complete {[Sadistic I]} Twice.", - "143": "[[143]] Insanity: Complete {[Sadistic I]} Three Times.", - "144": "[[144]] How? Complete {[Sadistic I]} Five Times.", - "145": "[[145]] Why? Complete {[Sadistic I]} Ten Times.", - "146": "[[146]] Descend: Complete {[Sadistic I]} Twenty Times.", - "147": "[[147]] End of the Universe: Complete {[Sadistic I]} Twenty-Five Times.", - "148": "[[148]] Gas gas gas: Purchase 5 Accelerators.", - "149": "[[149]] 0 to 25: Purchase 25 Accelerators.", - "150": "[[150]] 0 to 100: Purchase 100 Accelerators", - "151": "[[151]] Highway to Hell: Purchase 666 Accelerators.", - "152": "[[152]] Perhaps you should brake: Purchase 2,000 Accelerators.", - "153": "[[153]] Exit the vehicle now!: Purchase 12,500 Accelerators.", - "154": "[[154]] Faster than light: Purchase 100,000 Accelerators.", - "155": "[[155]] I've been duped!: Purchase 2 Multipliers.", - "156": "[[156]] Funhouse Mirrors: Purchase 20 Multipliers.", - "157": "[[157]] Friend of binary: Purchase 100 Multipliers.", - "158": "[[158]] Feeling the cost growth yet?: Purchase 500 Multipliers.", - "159": "[[159]] Perhaps you'll feel the cost now: Purchase 2,000 Multipliers.", - "160": "[[160]] Exponential Synergy: Purchase 12,500 Multipliers.", - "161": "[[161]] Cloned: Purchase 100,000 Multipliers.", - "162": "[[162]] Jerk > 0: Purchase 2 Accelerator Boosts.", - "163": "[[163]] Can't the speedometer move any faster?: Purchase 10 Accelerator Boosts.", - "164": "[[164]] 50 G rotations: Purchase 50 Accelerator Boosts.", - "165": "[[165]] Dematerialize: Purchase 200 Accelerator Boosts.", - "166": "[[166]] Breaking the laws of Physics: Purchase 1,000 Accelerator Boosts.", - "167": "[[167]] Decayed Realism: Purchase 5,000 Accelerator Boosts.", - "168": "[[168]] Kinda fast: Purchase 15,000 Accelerator Boosts.", - "169": "[[169]] The Galactic Feast: Obtain 3 Galactic Crumbs.", - "170": "[[170]] Only the finest: Obtain 100,000 Galactic Crumbs.", - "171": "[[171]] Six-Course Meal: Obtain 666,666,666 Galactic Crumbs.", - "172": "[[172]] Accumulation of Food: Obtain 1e20 Galactic Crumbs.", - "173": "[[173]] Cookie Clicking: Obtain 1e40 Galactic Crumbs.", - "174": "[[174]] Unlimited Bread Sticks!: Obtain 1e500 Galactic Crumbs.", - "175": "[[175]] Restaurant at the end of the Universe: Obtain 1e2500 Galactic Crumbs.", - "176": "[[176]] Ant-icipation!: Amass a 2x Ant Multiplier through sacrifice and own a Tier 2 Ant.", - "177": "[[177]] Ant-ecedent: Amass a 6x Ant Multiplier through sacrifice and own a Tier 3 Ant.", - "178": "[[178]] Ants are friends, not food!: Amass a 20x Ant Multiplier through sacrifice and own a Tier 4 Ant.", - "179": "[[179]] Ant Devil?: Amass a 100x Ant Multiplier through sacrifice and own a Tier 5 Ant.", - "180": "[[180]] The world's best chef: Amass a 500x Ant Multiplier through sacrifice and own a Tier 6 Ant.", - "181": "[[181]] 6 Michelin Stars: Amass a 6,666x Ant Multiplier through sacrifice and own a Tier 7 Ant.", - "182": "[[182]] Keys to the Restaurant at the end of the Universe: Amass a 77,777x Ant Multiplier through sacrifice and own a Tier 8 Ant.", - "183": "[[183]] Up: Ascend Once.", - "184": "[[184]] Double-Up: Ascend Twice.", - "185": "[[185]] Give me Ten!: Ascend Ten Times.", - "186": "[[186]] Give me a Hundred: Ascend 100 Times.", - "187": "[[187]] Give me a Thousand: Ascend 1,000 Times.", - "188": "[[188]] Give me some arbitrary number I: Ascend 14,142 Times.", - "189": "[[189]] Give me some arbitrary number II: Ascend 141,421 Times.", - "190": "[[190]] Now that's what I call getting some Pi!: Attain a constant of 3.14.", - "191": "[[191]] One in a million: Attain a constant of 1,000,000 [1e6].", - "192": "[[192]] A number: Attain a constant of 4.32e10.", - "193": "[[193]] The coolest of numbers: Attain a constant of 6.9e21.", - "194": "[[194]] Planck^(-1): Attain a constant of 1.509e33.", - "195": "[[195]] Epsilon > a lot: Attain a constant of 1e66.", - "196": "[[196]] NUM_MAX: Attain a constant of 1.8e308.", - "197": "[[197]] Casualties: Clear 'Reduced Ants' challenge once.", - "198": "[[198]] Fatalities: Clear 'Reduced Ants' challenge twice.", - "199": "[[199]] Destruction: Clear 'Reduced Ants' challenge three times.", - "200": "[[200]] War, what is it good for?: Clear 'Reduced Ants' challenge five times.", - "201": "[[201]] Absolutely everything.: Clear 'Reduced Ants' challenge ten times.", - "202": "[[202]] Perfect Storm: Clear 'Reduced Ants' challenge twenty times.", - "203": "[[203]] Immaculate Storm: Clear 'Reduced Ants' challenge thirty times.", - "204": "[[204]] I didn't need those stupid reincarnations anyway!: Clear 'No Reincarnation' challenge once.", - "205": "[[205]] [x1,x2,0,x3]: Clear 'No Reincarnation' challenge twice.", - "206": "[[206]] Nonmetaphysical: Clear 'No Reincarnation' challenge three times.", - "207": "[[207]] Living alone: Clear 'No Reincarnation' challenge five times.", - "208": "[[208]] DM me on discord if you read these names: Clear 'No Reincarnation' challenge ten times.", - "209": "[[209]] Yeah: Clear 'No Reincarnation' challenge twenty times.", - "210": "[[210]] Science! Clear 'No Reincarnation' challenge thirty times.", - "211": "[[211]] The IRS strikes back: Clear 'Tax+++' challenge once.", - "212": "[[212]] Fiscal Policy: Clear 'Tax+++' challenge twice.", - "213": "[[213]] Economic Boom: Clear 'Tax+++' challenge three times.", - "214": "[[214]] Ant-onomics: Clear 'Tax+++' challenge five times.", - "215": "[[215]] 'Wow Platonic Tax sucks 1/5': Clear 'Tax+++' challenge ten times.", - "216": "[[216]] Haha this is hard for some reason: Clear 'Tax+++' challenge twenty times.", - "217": "[[217]] Taxes are hard: Clear 'Tax+++' challenge thirty times.", - "218": "[[218]] Shiny Blue Rock: Clear 'No Research' once.", - "219": "[[219]] It's like Avatar: Clear 'No Research' twice.", - "220": "[[220]] It's like Unobtainium: Clear 'No Research' three times.", - "221": "[[221]] It's like a thing: Clear 'No Research' five times.", - "222": "[[222]] It's like: Clear 'No Research' ten times.", - "223": "[[223]] It's: Clear 'No Research' twenty times.", - "224": "[[224]] It: Clear 'No Research' thirty times.", - "225": "[[225]] Pretty Corrupt: Clear an Ascension with above 100,000 score.", - "226": "[[226]] Bought out: Clear an Ascension with above 1 million score.", - "227": "[[227]] Utterly Corrupt: Clear an Ascension with above 10 million score.", - "228": "[[228]] Antitrust: Clear an Ascension with above 100 million score.", - "229": "[[229]] Ant-i-trust: Clear an Ascension with above 1 billion score.", - "230": "[[230]] This is pretty unfair: Clear an Ascension with above 5 billion score.", - "231": "[[231]] Antichrist: Clear an Ascension with above 25 billion score.", - "232": "[[232]] Highly Blessed: Level your Speed Rune Blessing to 100,000.", - "233": "[[233]] Divine Blessing: Level your Speed Rune Blessing to 100,000,000.", - "234": "[[234]] Blessing III: Level your Speed Rune Blessing to 100 billion.", - "235": "[[235]] Spirit I: Level your Speed Spirit to 1 Million.", - "236": "[[236]] Spirit II: Level your Speed Spirit to 1 Billion.", - "237": "[[237]] Spirit III: Level your Speed Spirit to 1 Trillion.", - "238": "[[238]] Three-folded: [Hint: you may want to look into the inception]", - "239": "[[239]] Seeing red: [Hint: you may need a lot of red items]", - "240": "[[240]] ASCENDED: [Hint: you may need a LOT of Ascensions OR an particularly amazing Ascension]", - "241": "[[241]] Aesop: [Hint: you gotta be pretty dang slow]", - "242": "[[242]] Aesop's Revenge: [Hint: you gotta be pretty dang fast]", - "243": "[[243]] Unsmith: [Hint: unsmith emoji :unsmith: can be a pretty good input]", - "244": "[[244]] Smith: [Hint: :antismith: looks promising as well]", - "245": "[[245]] BLESSED: [Hint: Your Speed Blessing best be unreasonably high!]", - "246": "[[246]] Why? [Hint: Sometimes even 1 in over a Trillion counts!]", - "247": "[[247]] Challenging! [Hint: Challenge 11 is calling your name, but with even less Ants]", - "248": "[[248]] Seeing Red but not Blue: [Hint: Can you get red stuff without getting blue stuff?]", - "249": "[[249]] Overtaxed: [Hint: It might pay not to read!]", - "250": "[[250]] The Thousand Suns: [Hint: You need to fully research into becoming GOD]", - "251": "[[251]] The Thousand Moons: [Hint: You may need to cube yourself up]", - "252": "[[252]] Ultimate: Complete 'SADISTIC II' Challenge.", - "253": "[[253]] Platonicism: Clear an Ascension with 1e12 score.", - "254": "[[254]] That's a handful!: Clear an Ascension with 1e14 score.", - "255": "[[255]] The game where everything is made up: Clear an Ascension with 1e17 score.", - "256": "[[256]] ... and the points don't matter: Clear an Ascension with 2e18 score.", - "257": "[[257]] Arguably moral: Clear an Ascension with 4e19 score.", - "258": "[[258]] Khafra's Personal Best: Clear an Ascension with 1e21 score.", - "259": "[[259]] 100 million million million!: Clear an Ascension with 1e23 score.", - "260": "[[260]] Highly Dimensional Being: Ascend a total of 10 million times.", - "261": "[[261]] Ant God's upheaval: Ascend a total of 100 million times.", - "262": "[[262]] Did you forget about Ant God?: Ascend a total of 2 billion times.", - "263": "[[263]] Ant God is unemployed thanks to you: Ascend a total of 40 billion times.", - "264": "[[264]] I hope you're happy with yourself: Ascend a total of 800 billion times.", - "265": "[[265]] Oh well: Ascend a total of 16 trillion times.", - "266": "[[266]] Keep up the gradual numerical increase: Ascend a total of 100 trillion times.", - "267": "[[267]] Eigenvalued: Achieve a constant of 1e1,000.", - "268": "[[268]] Achieve Mathematics: Achieve a constant of 1e5,000.", - "269": "[[269]] Ramsay (5,5): Achieve a constant of 1e15,000.", - "270": "[[270]] What comes after this?: Achieve a constant of 1e50,000.", - "271": "[[271]] LARGE BOY: Achieve a constant of 1e100,000.", - "272": "[[272]] LARGER BOY: Achieve a constant of 1e300,000.", - "273": "[[273]] LARGEST BOY: Achieve a constant of 1e1,000,000.", - "274": "[[274]] Power Creep: Singularity 1 time.", - "275": "[[275]] Have you enough cubes?: Singularity 2 times.", - "276": "[[276]] Singularity: Singularity 3 times.", - "277": "[[277]] SingularRity: Singularity 4 times.", - "278": "[[278]] SiINguLaRrRity: Singularity 5 times.", - "279": "[[279]] SiIINGuLArRrIiTyY: Singularity 7 times.", - "280": "[[280]] Inception: Singularity 10 times." + "0": "Achievement Hunter: Open the Achievements Screen.", + "1": "A Loyal Employee: Hire your first Worker.", + "2": "Small Business: Hire 10 Workers.", + "3": "Now we're synergizing!: Hire 100 Workers.", + "4": "Gaining Redundancies: Hire 1,000 Workers.", + "5": "A cog in the machine: Hire 5,000 Workers.", + "6": "A nail in the machine: Hire 10,000 Workers.", + "7": "Are we even in the machine anymore?: Hire 20,000 Workers.", + "8": "STONKS!!!: Purchase 1 Investment.", + "9": "Planning ahead: Purchase 10 Investments.", + "10": "Inside Trading: Purchase 100 Investments.", + "11": "Outside Trading?: Purchase 500 Investments.", + "12": "Market Takeover: Purchase 5,000 Investments.", + "13": "Trickle-Down Economics: Purchase 10,000 Investments.", + "14": "Eliminated Regulation: Purchase 20,000 Investments.", + "15": "Stationery!: Build 1 Printer.", + "16": "Printing Press: Build 10 Printers.", + "17": "It prints free money!: Build 100 Printers.", + "18": "Solving Our Debts: Build 333 Printers.", + "19": "Monopolizing the market: Build 5,000 Printers.", + "20": "We're running out of Ink!: Build 10,000 Printers.", + "21": "3D-printing the universe: Build 20,000 Printers.", + "22": "A national treasure: Establish 1 Coin Mint.", + "23": "Now with competition!: Establish 10 Coin Mints.", + "24": "Counterfeiting with Style!: Establish 100 Coin Mints.", + "25": "Why do we need all these?: Establish 250 Coin Mints.", + "26": "No really, why??: Establish 5,000 Coin Mints.", + "27": "Is no one to stop us???: Establish 10,000 Coin Mints.", + "28": "Oh well, time to mint: Establish 20,000 Coin Mints.", + "29": "Newton's Apprentice: Create 1 Alchemy.", + "30": "Lab Work: Create 10 Alchemies.", + "31": "Satanic Becomings: Create 66 Alchemies.", + "32": "Beelzebub Grinned: Create 200 Alchemies.", + "33": "Is this more demonic?: Create 6,666 Alchemies.", + "34": "Golden Paradise: Create 17,777 Alchemies.", + "35": "Unlocking secrets to the world: Create 42,777 Alchemies.", + "36": "Leveling up: Prestige for at least 1 Diamond.", + "37": "High-Tiered: Prestige for at least 1e6 Diamonds.", + "38": "Highly Regarded: Prestige for at least 1e100 Diamonds.", + "39": "Prestigious: Prestige for at least 1e1,000 Diamonds.", + "40": "Legendary: Prestige for at least 1e10,000 Diamonds.", + "41": "Divine: Prestige for at least 1e77,777 Diamonds.", + "42": "Perfectly Respected: Prestige for at least 1e250,000 Diamonds.", + "43": "A Simple Detour: Transcend for at least 1 Mythos.", + "44": "Tunnel Vision: Transcend for at least 1e6 Mythos.", + "45": "Risen from the Ashes: Transcend for at least 1e50 Mythos.", + "46": "Paradigm Shift: Transcend for at least 1e308 Mythos.", + "47": "Preparation: Transcend for at least 1e1,500 Mythos.", + "48": "Revising the Plan: Transcend for at least 1e25,000 Mythos.", + "49": "Leaving the Universe: Transcend for at least 1e100,000 Mythos.", + "50": "Going Quantum: Reincarnate for at least 1 Particle.", + "51": "Tunneling Vision: Reincarnate for at least 100,000 Particles.", + "52": "Simulating the World: Reincarnate for at least 1e30 Particles.", + "53": "Multidimensional Creation: Reincarnate for at least 1e200 Particles.", + "54": "Lepton Dance: Reincarnate for at least 1e1,500 Particles.", + "55": "Do we have enough yet?: Reincarnate for at least 1e5,000 Particles.", + "56": "I Feel Luck in My Cells: Reincarnate for at least 1e7,777 Particles.", + "57": "One Way Only: Prestige without buying Multipliers.", + "58": "Authentic Shifting: Transcend without having bought a Multiplier.", + "59": "The [REDACTED]: Reincarnate without having bought a Multiplier.", + "60": "Gotta go SLOW!: Prestige without buying Accelerators.", + "61": "I'm really going slow: Transcend without having bought Accelerators or Boosts.", + "62": "Are we there yet?: Reincarnate without having bought Accelerators or Boosts.", + "63": "A careful search for Diamonds: Get 1e120,000 Coins in [Reduced Diamonds] without buying Accelerators or Boosts.", + "64": "Very Based: Prestige without purchasing Coin Upgrades.", + "65": "Miser: Transcend without purchasing Coin Upgrades.", + "66": "True Miser: Transcend without purchasing Coin or Diamond Upgrades.", + "67": "Coinless Pursuit: Reincarnate without purchasing Coin Upgrades.", + "68": "Diamonds don't matter to me!: Reincarnate without purchasing Coin or Diamond Upgrades.", + "69": "Leave nothing behind: Reincarnate without purchasing Coin, Diamond or Mythos Upgrades.", + "70": "Leave NOTHING behind.: Reincarnate without purchasing Coin, Diamond, Mythos, or Generator Upgrades.", + "71": "Out of Order: Buy Generator Upgrade Row 1, #2 first in a Transcension (IV -> III)", + "72": "More Out of Order: Buy Generator Upgrade Row 1, #3 first in a Transcension (III -> II)", + "73": "Four's a Company: Buy Generator Upgrade Row 1, #4 first in a Transcension (II -> I)", + "74": "Five's a Croud: Buy Generator Upgrade Row 1, #5 first in a Transcension (I -> V)", + "75": "Vaseline without the Machine: Exit [No Multiplier] with at least 1e1000 coins and without any of the row 1 generator upgrades.", + "76": "Rage against the Machine: Exit [No Accelerator] with at least 1e1000 coins and without any of the row 1 generator upgrades.", + "77": "Amish Paradise: Exit [No Shards] with at least 1e99,999 coins and without any of the row 1 generator upgrades.", + "78": "Single-Cell: Complete [No Multiplier] once.", + "79": "Solidarity: Complete [No Multiplier] three times.", + "80": "Duplication-Free!: Complete [No Multiplier] five times.", + "81": "Multitasking Challenged: Complete [No Multiplier] ten times.", + "82": "No Deaths: Complete [No Multiplier] twenty times.", + "83": "Population One: Complete [No Multiplier] fifty times.", + "84": "Insert Another Token: Complete [No Multiplier] seventy-five times.", + "85": "Slow Start: Complete [No Accelerator] once", + "86": "Respawn Rate -12%: Complete [No Accelerator] three times.", + "87": "Putting the Breaks On: Complete [No Accelerator] five times.", + "88": "Racing a Sloth...: Complete [No Accelerator] ten times.", + "89": "... and Losing.: Complete [No Accelerator] twenty times.", + "90": "Planck Distance Traveled: Complete [No Accelerator] fifty times.", + "91": "Inverse-Ackermann Growth: Complete [No Accelerator] seventy-five times.", + "92": "Intact: Complete [No Shards] once.", + "93": "Augments are Stupid!: Complete [No Shards] three times.", + "94": "Grandmasters are Brilliant!: Complete [No Shards] five times.", + "95": "Gotta get those Grandmasters Stronger: Complete [No Shards] ten times.", + "96": "Summoning Enhancements: Complete [No Shards] twenty times.", + "97": "Magic 99/99: Complete [No Shards] fifty times.", + "98": "Perfect Foresight: Complete [No Shards] seventy-five times.", + "99": "Inflation: Complete [Cost+] once.", + "100": "Hyperinflation: Complete [Cost+] three times.", + "101": "Market Bubble: Complete [Cost+] five times.", + "102": "Bull Market: Complete [Cost+] ten times.", + "103": "Wealth Inequality: Complete [Cost+] twenty times.", + "104": "Severe Overpay: Complete [Cost+] fifty times.", + "105": "Societal Collapse: Complete [Cost+] seventy-five times.", + "106": "Excavation: Complete [Reduced Diamonds] once.", + "107": "Digging Deep: Complete [Reduced Diamonds] three times.", + "108": "Frack As Needed: Complete [Reduced Diamonds] five times.", + "109": "Unobtainium Pickaxe: Complete [Reduced Diamonds] ten times.", + "110": "Fortune III: Complete [Reduced Diamonds] twenty times.", + "111": "Every kiss...: Complete [Reduced Diamonds] fifty times.", + "112": "...begins with K.: Complete [Reduced Diamonds] seventy-five times.", + "113": "Tax evasion!: Complete {[Tax+]} once.", + "114": "Keeping up with the Joneses: Complete {[Tax+]} twice.", + "115": "Offshore deposits: Complete {[Tax+]} three times.", + "116": "Bribing officials: Complete {[Tax+]} five times.", + "117": "Becoming President: Complete {[Tax+]} ten times.", + "118": "Charitable Donation: Complete {[Tax+]} fifteen times.", + "119": "IRS Audit: Complete {[Tax+]} twenty-five times.", + "120": "Is there anybody in there?: Complete {[No Accelerator/Multiplier]} once.", + "121": "Human being: Complete {[No Accelerator/Multiplier]} twice.", + "122": "Interdimensional: Complete {[No Accelerator/Multiplier]} three times.", + "123": "A slow nickel: Complete {[No Accelerator/Multiplier]} five times.", + "124": "Multipliers don't even work 0/5: Complete {[No Accelerator/Multiplier]} ten times.", + "125": "Accelerators don't even work -5/5: Complete {[No Accelerator/Multiplier]} fifteen times.", + "126": "ACCELERATOR BOOSTS DON'T EVEN WORK -100/5: Complete {[No Accelerator/Multiplier]} twenty-five times.", + "127": "I hate this challenge: Complete Cost++ Once.", + "128": "A costly mistake: Complete Cost++ Twice.", + "129": "Impetus: Complete Cost++ Three Times.", + "130": "Are you broke yet? Complete Cost++ Five Times.", + "131": "The world of Finance: Complete Cost++ Ten Times.", + "132": "Marginal Gains: Complete Cost++ Twenty Times.", + "133": "I buy these: Complete Cost++ Twenty-Five Times.", + "134": "Agnostic: Complete No Runes Once.", + "135": "Ant-i Runes: Complete No Runes Twice.", + "136": "Isn't it getting tiresome?: Complete No Runes Three Times.", + "137": "Machine does not accept offerings: Complete No Runes Five Times.", + "138": "Runes Suck 1/5: Complete No Runes Ten Times.", + "139": "I didn't even notice Prism was gone: Complete No Runes Twenty Times.", + "140": "Atheist: Complete No Runes Twenty-Five Times.", + "141": "Sadism: Complete {[Sadistic I]} Once.", + "142": "Masochism: Complete {[Sadistic I]} Twice.", + "143": "Insanity: Complete {[Sadistic I]} Three Times.", + "144": "How?: Complete {[Sadistic I]} Five Times.", + "145": "Why?: Complete {[Sadistic I]} Ten Times.", + "146": "Descend: Complete {[Sadistic I]} Twenty Times.", + "147": "End of the Universe: Complete {[Sadistic I]} Twenty-Five Times.", + "148": "Gas gas gas: Purchase 5 Accelerators.", + "149": "0 to 25: Purchase 25 Accelerators.", + "150": "0 to 100: Purchase 100 Accelerators", + "151": "Highway to Hell: Purchase 666 Accelerators.", + "152": "Perhaps you should brake: Purchase 2,000 Accelerators.", + "153": "Exit the vehicle now!: Purchase 12,500 Accelerators.", + "154": "Faster than light: Purchase 100,000 Accelerators.", + "155": "I've been duped!: Purchase 2 Multipliers.", + "156": "Funhouse Mirrors: Purchase 20 Multipliers.", + "157": "Friend of binary: Purchase 100 Multipliers.", + "158": "Feeling the cost growth yet?: Purchase 500 Multipliers.", + "159": "Perhaps you'll feel the cost now: Purchase 2,000 Multipliers.", + "160": "Exponential Synergy: Purchase 12,500 Multipliers.", + "161": "Cloned: Purchase 100,000 Multipliers.", + "162": "Jerk > 0: Purchase 2 Accelerator Boosts.", + "163": "Can't the speedometer move any faster?: Purchase 10 Accelerator Boosts.", + "164": "50 G rotations: Purchase 50 Accelerator Boosts.", + "165": "Dematerialize: Purchase 200 Accelerator Boosts.", + "166": "Breaking the laws of Physics: Purchase 1,000 Accelerator Boosts.", + "167": "Decayed Realism: Purchase 5,000 Accelerator Boosts.", + "168": "Kinda fast: Purchase 15,000 Accelerator Boosts.", + "169": "The Galactic Feast: Obtain 3 Galactic Crumbs.", + "170": "Only the finest: Obtain 100,000 Galactic Crumbs.", + "171": "Six-Course Meal: Obtain 666,666,666 Galactic Crumbs.", + "172": "Accumulation of Food: Obtain 1e20 Galactic Crumbs.", + "173": "Cookie Clicking: Obtain 1e40 Galactic Crumbs.", + "174": "Unlimited Bread Sticks!: Obtain 1e500 Galactic Crumbs.", + "175": "Restaurant at the end of the Universe: Obtain 1e2500 Galactic Crumbs.", + "176": "Ant-icipation!: Amass a 2x Ant Speed Multiplier through sacrifice and own a Tier 2 Ant.", + "177": "Ant-ecedent: Amass a 6x Ant Speed Multiplier through sacrifice and own a Tier 3 Ant.", + "178": "Ants are friends, not food!: Amass a 20x Ant Speed Multiplier through sacrifice and own a Tier 4 Ant.", + "179": "Ant Devil?: Amass a 100x Ant Speed Multiplier through sacrifice and own a Tier 5 Ant.", + "180": "The world's best chef: Amass a 500x Ant Speed Multiplier through sacrifice and own a Tier 6 Ant.", + "181": "6 Michelin Stars: Amass a 6,666x Ant Speed Multiplier through sacrifice and own a Tier 7 Ant.", + "182": "Keys to the Restaurant at the end of the Universe: Amass a 77,777x Ant Speed Multiplier through sacrifice and own a Tier 8 Ant.", + "183": "Up: Ascend Once.", + "184": "Double-Up: Ascend Twice.", + "185": "Give me Ten!: Ascend Ten Times.", + "186": "Give me a Hundred: Ascend 100 Times.", + "187": "Give me a Thousand: Ascend 1,000 Times.", + "188": "Give me some arbitrary number I: Ascend 14,142 Times.", + "189": "Give me some arbitrary number II: Ascend 141,421 Times.", + "190": "Now that's what I call getting some Pi!: Attain a constant of 3.14.", + "191": "One in a million: Attain a constant of 1,000,000 [1e6].", + "192": "A number: Attain a constant of 4.32e10.", + "193": "The coolest of numbers: Attain a constant of 6.9e21.", + "194": "Planck^(-1): Attain a constant of 1.509e33.", + "195": "Epsilon > a lot: Attain a constant of 1e66.", + "196": "NUM_MAX: Attain a constant of 1.8e308.", + "197": "Casualties: Clear 'Reduced Ants' challenge once.", + "198": "Fatalities: Clear 'Reduced Ants' challenge twice.", + "199": "Destruction: Clear 'Reduced Ants' challenge three times.", + "200": "War, what is it good for?: Clear 'Reduced Ants' challenge five times.", + "201": "Absolutely everything.: Clear 'Reduced Ants' challenge ten times.", + "202": "Perfect Storm: Clear 'Reduced Ants' challenge twenty times.", + "203": "Immaculate Storm: Clear 'Reduced Ants' challenge thirty times.", + "204": "I didn't need those stupid reincarnations anyway!: Clear 'No Reincarnation' challenge once.", + "205": "[x1,x2,0,x3]: Clear 'No Reincarnation' challenge twice.", + "206": "Nonmetaphysical: Clear 'No Reincarnation' challenge three times.", + "207": "Living alone: Clear 'No Reincarnation' challenge five times.", + "208": "Reinventing the 'wheel': Clear 'No Reincarnation' challenge ten times.", + "209": "Yeah: Clear 'No Reincarnation' challenge twenty times.", + "210": "Science! Clear 'No Reincarnation' challenge thirty times.", + "211": "The IRS strikes back: Clear 'Tax+++' challenge once.", + "212": "Fiscal Policy: Clear 'Tax+++' challenge twice.", + "213": "Economic Boom: Clear 'Tax+++' challenge three times.", + "214": "Ant-onomics: Clear 'Tax+++' challenge five times.", + "215": "'Wow Platonic Tax sucks 1/5': Clear 'Tax+++' challenge ten times.", + "216": "Haha this is hard for some reason: Clear 'Tax+++' challenge twenty times.", + "217": "Taxes are hard: Clear 'Tax+++' challenge thirty times.", + "218": "Shiny Blue Rock: Clear 'No Research' once.", + "219": "It's like Avatar: Clear 'No Research' twice.", + "220": "It's like Unobtainium: Clear 'No Research' three times.", + "221": "It's like a thing: Clear 'No Research' five times.", + "222": "It's like: Clear 'No Research' ten times.", + "223": "It's: Clear 'No Research' twenty times.", + "224": "It: Clear 'No Research' thirty times.", + "225": "Pretty Corrupt: Clear an Ascension with above 100,000 score.", + "226": "Bought out: Clear an Ascension with above 1 million score.", + "227": "Utterly Corrupt: Clear an Ascension with above 10 million score.", + "228": "Antitrust: Clear an Ascension with above 100 million score.", + "229": "Ant-i-trust: Clear an Ascension with above 1 billion score.", + "230": "This is pretty unfair: Clear an Ascension with above 5 billion score.", + "231": "Antichrist: Clear an Ascension with above 25 billion score.", + "232": "Highly Blessed: Level your Speed Rune Blessing to 20.", + "233": "Divine Blessing: Level your Speed Rune Blessing to 40.", + "234": "Blessing III: Level your Speed Rune Blessing to 80.", + "235": "Spirit I: Level your Speed Spirit to 20.", + "236": "Spirit II: Level your Speed Spirit to 40.", + "237": "Spirit III: Level your Speed Spirit to 80.", + "238": "Three-folded: [Hint: you may want to look into the inception]", + "239": "Seeing red: [Hint: you may need a lot of red items]", + "240": "ASCENDED: [Hint: you may need a LOT of Ascensions OR an particularly amazing Ascension]", + "241": "Aesop: [Hint: you gotta be pretty dang slow]", + "242": "Aesop's Revenge: [Hint: you gotta be pretty dang fast]", + "243": "Unsmith: [Hint: unsmith emoji :unsmith: can be a pretty good input]", + "244": "Smith: [Hint: :antismith: looks promising as well]", + "245": "BLESSED: [Hint: Your Speed Blessing best be unreasonably high!]", + "246": "Why?: [Hint: Sometimes even 1 in over a Trillion counts!]", + "247": "Challenging!: [Hint: Challenge 11 is calling your name, but with even less Ants]", + "248": "Seeing Red but not Blue: [Hint: Can you get red stuff without getting blue stuff?]", + "249": "Overtaxed: [Hint: It might pay not to read!]", + "250": "The Thousand Suns: [Hint: You need to fully research into becoming GOD]", + "251": "The Thousand Moons: [Hint: You may need to cube yourself up]", + "252": "Ultimate: [Hint: a good enough score from the second Sadistic challenge shall do]", + "253": "Platonicism: Clear an Ascension with 1e12 score.", + "254": "That's a handful!: Clear an Ascension with 1e14 score.", + "255": "The game where everything is made up: Clear an Ascension with 1e17 score.", + "256": "... and the points don't matter: Clear an Ascension with 2e18 score.", + "257": "Arguably moral: Clear an Ascension with 4e19 score.", + "258": "Khafra's Personal Best: Clear an Ascension with 1e21 score.", + "259": "100 million million million!: Clear an Ascension with 1e23 score.", + "260": "Highly Dimensional Being: Ascend a total of 10 million times.", + "261": "Ant God's upheaval: Ascend a total of 100 million times.", + "262": "Did you forget about Ant God?: Ascend a total of 2 billion times.", + "263": "Ant God is unemployed thanks to you: Ascend a total of 40 billion times.", + "264": "I hope you're happy with yourself: Ascend a total of 800 billion times.", + "265": "Oh well: Ascend a total of 16 trillion times.", + "266": "Keep up the gradual numerical increase: Ascend a total of 100 trillion times.", + "267": "Eigenvalued: Achieve a constant of 1e1,000.", + "268": "Achieve Mathematics: Achieve a constant of 1e5,000.", + "269": "Ramsay (5,5): Achieve a constant of 1e15,000.", + "270": "What comes after this?: Achieve a constant of 1e50,000.", + "271": "LARGE BOY: Achieve a constant of 1e100,000.", + "272": "LARGER BOY: Achieve a constant of 1e300,000.", + "273": "LARGEST BOY: Achieve a constant of 1e1,000,000.", + "274": "Power Creep: Singularity 1 time.", + "275": "Have you enough cubes?: Singularity 2 times.", + "276": "Singularity: Singularity 3 times.", + "277": "SingularRity: Singularity 4 times.", + "278": "SiINguLaRrRity: Singularity 5 times.", + "279": "SiIINGuLArRrIiTyY: Singularity 7 times.", + "280": "Inception: Singularity 10 times.", + "281": "The 100,000 Worker March: Hire 100,000 Workers.", + "282": "An Amazonian-sized workforce: Hire 1,000,000 Workers.", + "283": "A feudal economy: Hire 100,000,000 Workers.", + "284": "Too big to fail: Purchase 1,000,000 Investments.", + "285": "Why play the market: Purchase 100,000,000 Investments.", + "286": "... when you ARE the market: Purchase 1 Billion Investments.", + "287": "Office Space: Purchase 10,000,000 Printers.", + "288": "I'm going to need that by Saturday: Purchase 100,000,000 Printers.", + "289": "If you could do that, it'd be greaaaat: Purchase 5 Billion Printers", + "290": "Mint Condition: Purchase 100,000,000 Coin Mints.", + "291": "Chocolate Mints, anyone?: Purchase 1 Billion Coin Mints.", + "292": "A good clean feeling, no matter what: Purchase 20 Billion Coin Mints", + "293": "Alpha Regeneration: Create 1 Billion Coin Mints.", + "294": "Citrinitas Synthesis: Create 20 Billion Coin Mints.", + "295": "The Philosopher's Stone: Create 1 Trillion Coin Mints.", + "296": "Prestige...?: Prestige for at least 1e10,000,000 Diamonds.", + "297": "Shine bright like a ...: Prestige for at least 1e10,000,000,000 [1e1e10] Diamonds.", + "298": "Diamonds are forever: Prestige for at least 1e10,000,000,000,000 [1e1e13] Diamonds.", + "299": "Transcend...?: Transcend for at least 1e2,500,000 Mythos.", + "300": "Mythos is not really a good name: Transcend for at least 1e2,500,000,000 [1e2.5e9] Mythos.", + "301": "Too Transcendent: Transcend for at least 1e2,500,000,000,000 [1e2.5e12] Mythos.", + "302": "Reincarnate...?: Reincarnate for at least 1e100,000 Particles.", + "303": "One Hundred Million Dollars.: Reincarnate for at least 1e100,000,000 [1e1e8] Particles.", + "304": "12 figure log-salary: Reincarnate for at least 1e100,000,000,000 [1e1e11] Particles.", + "305": "Mononucleic: Clear [No Multiplier] 1,000 times.", + "306": "Solo Leveled: Clear [No Multiplier] 9,000 times.", + "307": "Power of One: Clear [No Multiplier] 9,001 times.", + "308": "TREE^-1(n): Clear [No Accelerators] 1,000 times.", + "309": "TREE^-1(TREE^-1(n)): Clear [No Accelerators] 9,000 times.", + "310": "O(1): Clear [No Accelerators] 9,001 times.", + "311": "I saw this in a movie once: Clear [No Shards] 1,000 times.", + "312": "Achievement 312: Clear [No Shards] 9,000 times.", + "313": "Achievement 313: Clear [No Shards] 9,001 times.", + "314": "The consequences of economy: Clear [Cost+] 1,000 times.", + "315": "Have been a disaster: Clear [Cost+] 9,000 times.", + "316": "For the ant race: Clear [Cost+] 9,001 times.", + "317": "491 karats: Clear [Reduced Diamonds] 1,000 times.", + "318": "Lab-shrunk diamonds: Clear [Reduced Diamonds] 9,000 times.", + "319": "Diamond is not breakable: Clear [Reduced Diamonds] 9,001 times.", + "320": "Tariffs: Clear {[Tax+]} 40 times.", + "321": "Council Taxes: Clear {[Tax+]} 80 times.", + "322": "Death and Taxes: Clear {[Tax+]} 120 times.", + "323": "This challenge honestly isn't that bad: Clear {[No Accelerator/Multiplier]} 40 times.", + "324": "You can't even rate this game anymore: Clear {[No Accelerator/Multiplier]} 80 times.", + "325": "Oh well... -140/5: Clear {[No Accelerator/Multiplier]} 125 times.", + "326": "I'm in Jeopardy!: Clear {[Cost++]} 40 times.", + "327": "Pretending to afford things: Clear {[Cost++]} 80 times.", + "328": "Do you accept traveler's checks?: Clear {[Cost++]} 130 times.", + "329": "Hieroglyphics: Clear {[No Runes]} 40 times.", + "330": "Hieroglyphics II: Clear {[No Runes]} 80 times.", + "331": "The Rosetta Stone: Clear {[No Runes]} 135 times.", + "332": "The ender of the universe: Clear {[Sadistic I]} 40 times.", + "333": "The endest of the universe: Clear {[Sadistic I]} 80 times.", + "334": "The more endest of the universe: Clear {[Sadistic I]} 140 times.", + "335": "I would go fast: Purchase 1,000,000 Accelerators", + "336": "The journey of 10,000,000 miles: Purchase 10,000,000 Accelerators", + "337": "Starts with a single mile: Purchase 100,000,000 Accelerators", + "338": "I think I'm a clone now: Purchase 3,000,000 Multipliers", + "339": "Variety platters: Purchase 30,000,000 Multipliers", + "340": "Every taste and talent: Purchase 300,000,000 Multipliers", + "341": "Booster Seats: Purchase 100,000 Accelerator Boosts", + "342": "A brief history of time: Purchase 1,000,000 Accelerator Boosts", + "343": "Vivace: Purchase 10,000,000 Accelerator Boosts", + "344": "A crumby achievement: Obtain 1e25,000 Galactic Crumbs.", + "345": "Get it? Crumbs?: Obtain 1e125,000 Galactic Crumbs.", + "346": "You clearly do: Obtain 1e1,000,000 Galactic Crumbs.", + "347": "Gordon Ramses: Amass a 1e50x Ant Speed Multiplier through Ant Sacrifice.", + "348": "Wolfgang Pluck: Amass a 1e200x And Speed Multiplier through Ant Sacrifice.", + "349": "Marco Pierre Blanco: Amass a 1e1,000x Ant Speed Multiplier through Ant Sacrifice.", + "350": "Ascensions are suggestions: Ascend 1e16 times.", + "351": "Nothing promised, no regrets: Ascend 1e20 times.", + "352": "Immaculate Ascensions: Ascend 1e25 times.", + "353": "That's almost 100: Ascend 1e35 times.", + "354": "Here's another 95 AP: Ascend 1e50 times.", + "355": "Digital Ascension Enjoyer: Ascend 1e75 times.", + "356": "Let's name some more numbers: Achieve a constant of 1e2,000,000", + "357": "Threeve: Achieve a constant of 1e5,000,000", + "358": "Eleventy Billion: Achieve a constant of 1e10,000,000", + "359": "A number so unremarkable it becomes remarkable: Achieve a constant of 1e25,000,000", + "360": "I love large numbers: Achieve a constant of 1e50,000,000", + "361": "There are 10 Million Million Million...: Achieve a constant of 1e100,000,000", + "362": "Ant-i Peace: Clear 'Reduced Ants' challenge 40 times.", + "363": "The great Ant War of '25: Clear 'Reduced Ants' challenge 50 times.", + "364": "Ambivalent to Life: Clear 'Reduced Ants' challenge 60 times.", + "365": "The 65 trials of an ant: Clear 'Reduced Ants' challenge 65 times.", + "366": "Ascended ant hatred: Clear 'Reduced Ants' challenge 70 times.", + "367": "Snap back to reality: Clear 'No Reincarnation' challenge 40 times.", + "368": "Oh there goes gravity: Clear 'No Reincarnation' challenge 50 times.", + "369": "Around the world in a tea daze: Clear 'No Reincarnation' challenge 60 times.", + "370": "So normal I became abnormal: Clear 'No Reincarnation' challenge 65 times.", + "371": "I don't need no stinkin' Reincarnation!: Clear 'No Reincarnation' challenge 70 times.", + "372": "Where is Tax++?: Clear 'Tax+++' challenge 40 times.", + "373": "Early Returns: Clear 'Tax+++' challenge 50 times.", + "374": "Bank error in your favor: Clear 'Tax+++' challenge 60 times.", + "375": "Offshoring your Offshore Account: Clear 'Tax+++' challenge 70 times.", + "376": "Immaculate Accounting: Clear 'Tax+++' challenge 72 times.", + "377": "I: Clear 'No Research' challenge 40 times.", + "378": "I don't: Clear 'No Research' challenge 50 times.", + "379": "I don't like: Clear 'No Research' challenge 60 times.", + "380": "I don't like to: Clear 'No Research' challenge 70 times.", + "381": "I don't like to study: Clear 'No Research' challenge 72 times.", + "382": "Quick to be square: Level your Speed Blessing to 200.", + "383": "400 Newtons: Level your Speed Blessing to 400.", + "384": "800 Newtons: Level your Speed Blessing to 800.", + "385": "Going at the speed of now: Level your Speed Blessing to 1,000.", + "386": "Short-term long-term: Level your Speed Blessing to 1,200.", + "387": "Chasing shadows: Level your Speed Blessing to 1,500.", + "388": "The immaculate sacrifice: Level your Speed Blessing to 2,000.", + "389": "Spirit IV: Level your Speed Spirit to 160.", + "390": "Spirit V: Level your Speed Spirit to 320.", + "391": "Spirit VI: Level your Speed Spirit to 640.", + "392": "Spirit VII: Level your Speed Spirit to 960.", + "393": "Spirit VIII: Level your Speed Spirit to 1,280.", + "394": "Spirit IX: Level your Speed Spirit to 1,600.", + "395": "Spirit X: Level your Speed Spirit to 2,000.", + "396": "A minor sacrifice: Level your Speed Rune to 100.", + "397": "A snack for the gods: Level your Speed Rune to 250.", + "398": "For your consideration: Level your Speed Rune to 500.", + "399": "Hints of shine in these rocks: Level your Speed Rune to 1,000.", + "400": "Like throwing offerings into a fire: Level your Speed Rune to 2,000.", + "401": "Atlas Shirked: Level your Speed Rune to 5,000.", + "402": "Platonic Sacrifice: Level your Speed Rune to 10,000.", + "403": "Boots of power: Level your Speed Rune to 20,000.", + "404": "An amount of Offerings you cannot fathom: Level your Speed Rune to 50,000.", + "405": "Salvaged to oblivion: Level your Speed Rune to 100,000.", + "406": "Level 98 Runecrafting: Level your Speed Rune to 200,000.", + "407": "Level 99 Runecrafting: Level your Speed Rune to 300,000.", + "408": "Halfway to cap: Level your Speed Rune to 500,000", + "409": "Just kidding, there is no cap: Level your Speed Rune to 750,000.", + "410": "I want to be a millionaire: Level your Speed Rune to 1,000,000.", + "411": "Simulated Tithe: Obtain 10 free levels of the Speed Rune.", + "412": "Simulated Tithe II: Obtain 40 free levels of the Speed Rune.", + "413": "Simulated Tithe III: Obtain 125 free levels of the Speed Rune.", + "414": "Simulated Tithe IV: Obtain 250 free levels of the Speed Rune.", + "415": "Simulated Tithe V: Obtain 500 free levels of the Speed Rune.", + "416": "Simulated Tithe VI: Obtain 1,000 free levels of the Speed Rune.", + "417": "Simulated Tithe VII: Obtain 2,000 free levels of the Speed Rune.", + "418": "Simulated Tithe VIII: Obtain 4,000 free levels of the Speed Rune.", + "419": "Simulated Tithe IX: Obtain 7,500 free levels of the Speed Rune.", + "420": "Simulated Tithe X: Obtain 12,500 free levels of the Speed Rune.", + "421": "Simulated Tithe XI: Obtain 25,000 free levels of the Speed Rune.", + "422": "Simulated Tithe XII: Obtain 37,500 free levels of the Speed Rune.", + "423": "Simulated Tithe XIII: Obtain 50,000 free levels of the Speed Rune.", + "424": "Simulated Tithe XIV: Obtain 75,000 free levels of the Speed Rune.", + "425": "God needs better names: Obtain 100,000 free levels of the Speed Rune.", + "426": "Proof of Work: Gain 10 Campaign Tokens from Campaigns.", + "427": "Insert tokens: Gain 20 Campaign Tokens from Campaigns.", + "428": "Novice Corruptor: Gain 40 Campaign Tokens from Campaigns.", + "429": "A Roll of Campaign Tokens: Gain 80 Campaign Tokens from Campaigns.", + "430": "Relative Campaign Expert: Gain 160 Campaign Tokens from Campaigns.", + "431": "Five Stacks: Gain 320 Campaign Tokens from Campaigns.", + "432": "Thousandaire: Gain 1,000 Campaign Tokens from Campaigns.", + "433": "Question 6 of a possible 15: Gain 2,000 Campaign Tokens from Campaigns.", + "434": "Platonic Campaigns: Gain 4,000 Campaign Tokens from Campaigns.", + "435": "Campaign Master: Gain 9,000 Campaign Tokens from Campaigns.", + "436": "Baby's first Prestige: Prestige for the first time.", + "437": "Ten Reputation: Prestige 10 times.", + "438": "Duplicating yourself: Prestige 100 times.", + "439": "Number? Go UP!: Prestige 1,000 times.", + "440": "Pretend to be a god: Prestige 10,000 times.", + "441": "The now-idolized: Prestige 100,000 times.", + "442": "One. Million. Prestige.: Prestige 1,000,000 times.", + "443": "Powers of Ten: Prestige 10,000,000 times.", + "444": "Now that's what I call Prestige!: Prestige 100,000,000 times.", + "445": "Billionaire I: Prestige 1,000,000,000 times.", + "446": "Powers of a Hundred: Prestige 100,000,000,000 times.", + "447": "Numbers as large as they are wide: Prestige 10,000,000,000,000 times.", + "448": "That's a quadrillion to you: Prestige 1e15 times.", + "449": "Prestige ad nauseum: Prestige 1e17 times.", + "450": "Prestige so Presitgious, it becomes ordinary: Prestige 1e20 times.", + "451": "Transcendental: Transcend for the first time.", + "452": "Ten degrees of separation: Transcend 10 times.", + "453": "Polychroma: Transcend 100 times.", + "454": "Absent further instructions: Transcend 1,000 times.", + "455": "The visage of godliness: Transcend 10,000 times.", + "456": "Post-human: Transcend 100,000 times.", + "457": "The Million Transcension Man: Transcend 1,000,000 times.", + "458": "Why not another?: Transcend 10,000,000 times.", + "459": "The average AI Salary in 2025: Transcend 100,000,000 times.", + "460": "Billionaire II: Transcend 1,000,000,000 times.", + "461": "Powers of thirty: Transcend 30,000,000,000 times.", + "462": "Wheel turning round and round: Transcend 900,000,000,000 times.", + "463": "The pursuit of numbers: Transcend 2.7e13 times.", + "464": "42 Achievement Points: Transcend 8.1e14 times.", + "465": "How to Transcend your Dragon: Transcend 1e17 times.", + "466": "Particle Man: Reincarnate for the first time.", + "467": "Some giants, possibly: Reincarnate 10 times.", + "468": "Atomic Discounts: Reincarnate 100 times.", + "469": "The nuclear option: Reincarnate 1,000 times.", + "470": "Reinvent yourself: Reincarnate 10,000 times.", + "471": "Your mind is software: Reincarnate 100,000 times.", + "472": "Bioethical: Reincarnate 1,000,000 times.", + "473": "Can you count to 10 million: Reincarnate 10,000,000 times.", + "474": "Reincarnated Grandmas... wait: Reincarnate 100,000,000 times.", + "475": "Billionaire III: Reincarnate 1,000,000,000 times.", + "476": "Out of bodies: Reincarnate 8,000,000,000 times", + "477": "Become the unsmith: Reincarnate 100,000,000,000 times.", + "478": "One trillion reincarnations later: Reincarnate 1,000,000,000,000 times.", + "479": "Log-triskaidekaphobia: Reincarnate 13,131,313,131,313 times.", + "480": "M2: Reincarnate 2e14 times." + }, + "achievementRewards": { + "3": { + "acceleratorPower": "+0.1% Accelerator Power" + }, + "4": { + "workerAutobuyer": "Start with the Worker Autobuyer unlocked!" + }, + "5": { + "accelerators": "+1 Free Accelerator per 500 Workers owned" + }, + "6": { + "multipliers": "+1 Free Multiplier per 1,000 Workers owned" + }, + "7": { + "accelBoosts": "+1 Free Accelerator Boost per 2,000 Workers owned" + }, + "10": { + "acceleratorPower": "+0.15% Accelerator Power" + }, + "11": { + "investmentAutobuyer": "Start with the Investment Autobuyer unlocked!" + }, + "12": { + "accelerators": "+1 Free Accelerator per 500 Investments owned" + }, + "13": { + "multipliers": "+1 Free Multiplier per 1,000 Investments owned" + }, + "14": { + "accelBoosts": "+1 Free Accelerator Boost per 2,000 Investments owned" + }, + "17": { + "acceleratorPower": "+0.2% Accelerator Power" + }, + "18": { + "printerAutobuyer": "Start with the Printer Autobuyer unlocked!" + }, + "19": { + "accelerators": "+1 Free Accelerator per 500 Printers owned" + }, + "20": { + "multipliers": "+1 Free Multiplier per 1,000 Printers owned" + }, + "21": { + "accelBoosts": "+1 Free Accelerator Boost per 2,000 Printers owned" + }, + "24": { + "acceleratorPower": "+0.2% Accelerator Power" + }, + "25": { + "mintAutobuyer": "Start with the Mint Autobuyer unlocked!" + }, + "26": { + "accelerators": "+1 Free Accelerator per 500 Coin Mints owned" + }, + "27": { + "multipliers": "+1 Free Multiplier per 1,000 Coin Mints owned" + }, + "28": { + "accelBoosts": "+1 Free Accelerator Boost per 2,000 Coin Mints owned" + }, + "31": { + "acceleratorPower": "+0.3% Accelerator Power" + }, + "32": { + "alchemyAutobuyer": "Start with the Alchemy Autobuyer unlocked!" + }, + "33": { + "accelerators": "+1 Free Accelerator per 500 Alchemies owned" + }, + "34": { + "multipliers": "+1 Free Multiplier per 1,000 Alchemies owned" + }, + "35": { + "accelBoosts": "+1 Free Accelerator Boost per 2,000 Alchemies owned" + }, + "36": { + "offeringPrestigeTimer": "Prestige gives more Offerings based on time spent! (+100% per 10 seconds)" + }, + "37": { + "crystalMultiplier": "Crystal production multiplier based on log(Diamonds)" + }, + "38": { + "duplicationRuneUnlock": "Permanently unlock the Duplication Rune!" + }, + "43": { + "autoPrestigeFeature": "Unlock the Auto Prestige Feature!" + }, + "44": { + "prismRuneUnlock": "Permanently unlock the Prism Rune!" + }, + "45": { + "taxReduction": "Tax scaling is reduced by 5%" + }, + "46": { + "taxReduction": "Tax scaling is reduced by 5%" + }, + "47": { + "taxReduction": "Tax scaling is reduced by 10%" + }, + "50": { + "particleGain": "Permanently double your Particle Gain!" + }, + "53": { + "multiplicativeObtainium": "Obtainium gain is increased by +0.125% for each Rune level" + }, + "57": { + "multipliers": "+1 free Multiplier" + }, + "58": { + "multipliers": "Another +1 free Multiplier!" + }, + "59": { + "multipliers": "ANOTHER +1 free Multiplier!" + }, + "60": { + "accelerators": "+2 free Accelerators" + }, + "61": { + "accelerators": "Another +2 free Accelerators" + }, + "62": { + "accelerators": "ANOTHER +2 free Accelerators" + }, + "71": { + "conversionExponent": "+1% Conversion Exponent on all generator upgrades!" + }, + "72": { + "conversionExponent": "+1% Conversion Exponent on all generator upgrades!" + }, + "73": { + "conversionExponent": "+1% Conversion Exponent on all generator upgrades!" + }, + "74": { + "conversionExponent": "+1% Conversion Exponent on all generator upgrades!" + }, + "75": { + "conversionExponent": "+1% Conversion Exponent on all generator upgrades!" + }, + "76": { + "conversionExponent": "+1% Conversion Exponent on all generator upgrades!" + }, + "77": { + "conversionExponent": "+1% Conversion Exponent on all generator upgrades!" + }, + "78": { + "refineryAutobuy": "Unlock the Refinery Autobuyer!" + }, + "79": { + "crystalUpgrade1Autobuy": "Unlock the Autobuyer for the first Crystal Upgrade!" + }, + "80": { + "salvage": "Salvage +3", + "multAutobuyer": "Start with the Multiplier Autobuyer unlocked!" + }, + "82": { + "taxReduction": "Tax scaling reduced by 4%" + }, + "84": { + "multiplicativeObtainium": "Obtainium x1.05" + }, + "85": { + "coalPlantAutobuy": "Unlock the Coal Plant Autobuyer!" + }, + "86": { + "crystalUpgrade2Autobuy": "Unlock the Autobuyer for the second Crystal Upgrade!" + }, + "87": { + "salvage": "+3 Salvage", + "accelAutobuyer": "Start with the Accelerator Autobuyer unlocked!" + }, + "89": { + "taxReduction": "Tax scaling reduced by 4%" + }, + "91": { + "multiplicativeObtainium": "Obtainium gain x1.05" + }, + "92": { + "coalRigAutobuy": "Unlock the Coal Rig Autobuyer!" + }, + "93": { + "crystalUpgrade3Autobuy": "Unlock the Autobuyer for the third Crystal Upgrade!" + }, + "94": { + "salvage": "+4 Salvage" + }, + "96": { + "taxReduction": "Tax scaling reduced by 4%" + }, + "98": { + "multiplicativeObtainium": "Obtainium gain x1.05" + }, + "99": { + "pickaxeAutobuy": "Unlock the Pickaxe Autobuyer!" + }, + "100": { + "crystalUpgrade4Autobuy": "Unlock the Autobuyer for the fourth Crystal Upgrade!" + }, + "101": { + "salvage": "+5 Salvage" + }, + "102": { + "thriftRuneUnlock": "Permanently unlock the Thrift Rune!" + }, + "103": { + "taxReduction": "Tax scaling reduced by 4%" + }, + "105": { + "multiplicativeObtainium": "Obtainium gain x1.05" + }, + "106": { + "pandorasBoxAutobuy": "Unlock the Pandora's Box Autobuyer!" + }, + "107": { + "crystalUpgrade5Autobuy": "Unlock the Autobuyer for the fifth Crystal Upgrade!" + }, + "108": { + "salvage": "+5 Salvage" + }, + "110": { + "taxReduction": "Tax scaling reduced by 4%" + }, + "112": { + "multiplicativeObtainium": "Obtainium gain x1.05" + }, + "115": { + "salvage": "+5 Salvage" + }, + "117": { + "taxReduction": "Tax scaling reduced by 5%" + }, + "118": { + "taxReduction": "Tax scaling reduced by 0.5% per Reincarnation Challenge clear (multiplicative)" + }, + "119": { + "exemptionTalisman": "Unlock the Exemption Talisman!" + }, + "120": { + "diamondUpgrade18": "Unlock the 18th Diamond Upgrade!" + }, + "122": { + "salvage": "+5 Salvage" + }, + "124": { + "taxReduction": "Tax scaling reduced by 5%" + }, + "125": { + "multiplicativeObtainium": "Obtainium gain x1.05" + }, + "126": { + "chronosTalisman": "Unlock the Chronos Talisman!" + }, + "127": { + "diamondUpgrade19": "Unlock the 19th Diamond Upgrade!", + "antHillUnlock": "Unlock the Anthill!" + }, + "129": { + "salvage": "+6 Salvage" + }, + "131": { + "taxReduction": "Tax scaling reduced by 5%" + }, + "132": { + "multiplicativeObtainium": "Obtainium gain x1.05" + }, + "133": { + "midasTalisman": "Unlock the Midas Talisman!" + }, + "134": { + "diamondUpgrade20": "Unlock the 20th Diamond Upgrade!", + "blessingUnlock": "Unlock Rune Blessings!", + "talismanUnlock": "Also, unlock Talismans!" + }, + "135": { + "talismanPower": "+2% Talisman Power" + }, + "136": { + "talismanPower": "+2% Talisman Power", + "salvage": "+7 Salvage" + }, + "137": { + "sacrificeMult": "Sacrifice multiplier x1.25" + }, + "139": { + "multiplicativeObtainium": "Obtainium gain x1.05" + }, + "140": { + "metaphysicsTalisman": "Unlock the Metaphysics Talisman!" + }, + "141": { + "ascensionUnlock": "Unlock the Ascension reset tier! Requires at least 1 {[Sadistic I]} clear." + }, + "143": { + "salvage": "+7 Salvage" + }, + "144": { + "talismanPower": "+2.5% Talisman Power" + }, + "145": { + "talismanPower": "+2.5% Talisman Power" + }, + "146": { + "multiplicativeObtainium": "Obtainium gain x1.05" + }, + "147": { + "polymathTalisman": "Unlock the Polymath Talisman!" + }, + "149": { + "acceleratorPower": "+1% Accelerator Power" + }, + "151": { + "accelerators": "Gain +5 free Accelerators" + }, + "152": { + "accelerators": "Gain +12 free Accelerators" + }, + "153": { + "accelerators": "Gain +25 free Accelerators" + }, + "154": { + "accelerators": "Gain +50 free Accelerators. Wow!" + }, + "156": { + "multipliers": "Gain +1 free Multiplier" + }, + "158": { + "multipliers": "Gain +1 free Multiplier" + }, + "159": { + "multipliers": "Gain +3 free Multipliers" + }, + "160": { + "multipliers": "Gain +6 free Multipliers" + }, + "161": { + "multipliers": "Gain +10 free Multipliers. Wow!" + }, + "169": { + "antSpeed": "Ant Speed is multiplied by logarithm of Galactic Crumbs" + }, + "171": { + "antSpeed": "Ant Speed x1.2" + }, + "172": { + "antSpeed": "Ant Speed x1.25" + }, + "173": { + "antSpeed": "Ant Speed x1.4", + "antSacrificeUnlock": "Unlock Ant Sacrifice!", + "antAutobuyers": "Unlock an Ant Autobuyer!" + }, + "174": { + "antSpeed": "Ant Speed is multiplied by logarithm of Ant Sacrifice Points" + }, + "176": { + "antAutobuyers": "Unlock the Breeder Ant Autobuyer!", + "antUpgradeAutobuyers": "Unlock the Inceptus and Fortunae Ant Autobuyers!" + }, + "177": { + "antAutobuyers": "Unlock the Meta-Breeder Ant Autobuyer!", + "antUpgradeAutobuyers": "Unlock the Tributum Ant Autobuyer!" + }, + "178": { + "antAutobuyers": "Unlock the Mega-Breeder Ant Autobuyer!", + "antUpgradeAutobuyers": "Unlock the Celeritas and Multa Ant Autobuyers!" + }, + "179": { + "antAutobuyers": "Unlock the Queen Ant Autobuyer!", + "antUpgradeAutobuyers": "Unlock the Sacrificium Ant Autobuyer!" + }, + "180": { + "antAutobuyers": "Unlock the Lord Royal Ant Autobuyer!", + "antUpgradeAutobuyers": "Unlock the Hic and Experientia Ant Autobuyers!" + }, + "181": { + "antAutobuyers": "Unlock the ALMIGHTY ANT Autobuyer!", + "antUpgradeAutobuyers": "Unlock the Praemoenio Ant Autobuyer!" + }, + "182": { + "antAutobuyers": "Unlock the DISCIPLE OF ANT GOD Autobuyer!", + "antUpgradeAutobuyers": "Unlock the Scientia and Phylacterium Ant Autobuyers!" + }, + "186": { + "wowSquareTalisman": "Unlock the Wow Square Talisman!" + }, + "187": { + "ascensionCountMultiplier": "Gain more Ascension Count based on Ascension Score [+100% per digit of Ascension Score, minus 1]", + "multiplicativeOffering": "Offerings multiplier based on Ascension Count" + }, + "188": { + "ascensionCountAdditive": "If your Ascension is at least 10 seconds long, add +100 Base Ascension Count!", + "multiplicativeObtainium": "Gain more Obtainium based on Ascension Count [up to x2 at 5 million Ascension Count]" + }, + "189": { + "ascensionCountAdditive": "Ascension Count multiplier based on excess time", + "wowCubeGain": "Cube gain multiplier based on Ascension Count" + }, + "193": { + "wowCubeGain": "Gain more Cubes based on your Constant [+0.25% per digit of Constant]" + }, + "195": { + "wowCubeGain": "Gain more Cubes based on your Constant [up to x250 at 1e100,000 Constant]", + "wowTesseractGain": "Gain more Tesseracts based on your Constant [up to x250 at 1e100,000 Constant]" + }, + "196": { + "wowPlatonicGain": "Gain more Platonic Cubes based on your Constant [up to x20 at 1e100,000 Constant]" + }, + "197": { + "statTracker": "Unlock a Stat Tracker at the top of your screen!", + "tesseractUnlock": "Unlock gaining Tesseracts from Ascending!" + }, + "198": { + "wowCubeGain": "Cube gain x1.02" + }, + "199": { + "wowCubeGain": "Cube gain x1.02" + }, + "200": { + "wowCubeGain": "Cube gain x1.02" + }, + "201": { + "wowCubeGain": "Cube gain x1.02" + }, + "202": { + "ascensionCountAdditive": "Add +2 Base Ascension Count per second of Ascension time" + }, + "203": { + "talismanPower": "+1% Talisman Power" + }, + "204": { + "ascensionRewardScaling": "Ascensions give more cubes of all types based on your Ascension time [+10% per second]", + "spiritUnlock": "Unlock Spirits, in the Runes tab!" + }, + "205": { + "wowTesseractGain": "Tesseract gain x1.02" + }, + "206": { + "wowTesseractGain": "Tesseract gain x1.02" + }, + "207": { + "wowTesseractGain": "Tesseract gain x1.02" + }, + "208": { + "wowTesseractGain": "Tesseract gain x1.02" + }, + "209": { + "ascensionCountAdditive": "Add +2 Base Ascension Count per second of Ascension time" + }, + "210": { + "talismanPower": "+1% Talisman Power" + }, + "211": { + "wowHypercubeGain": "Hypercube gain x1.05", + "hypercubeUnlock": "Unlock gaining Hypercubes from Ascending!" + }, + "212": { + "wowHypercubeGain": "Hypercube gain x1.02" + }, + "213": { + "wowHypercubeGain": "Hypercube gain x1.02" + }, + "214": { + "wowHypercubeGain": "Hypercube gain x1.02" + }, + "215": { + "wowHypercubeGain": "Hypercube gain x1.02" + }, + "216": { + "ascensionCountAdditive": "Add +2 Base Ascension Count per second of Ascension time" + }, + "217": { + "talismanPower": "+1% Talisman Power" + }, + "218": { + "wowPlatonicGain": "Platonic Cubes x1.05", + "platonicUnlock": "Unlock gaining Platonic Cubes from Ascending!" + }, + "219": { + "wowPlatonicGain": "Platonic Cube gain x1.02" + }, + "220": { + "wowPlatonicGain": "Platonic Cubes gain x1.02" + }, + "221": { + "wowPlatonicGain": "Platonic Cubes gain x1.02" + }, + "222": { + "wowPlatonicGain": "Platonic Cubes gain x1.02" + }, + "223": { + "ascensionCountAdditive": "Add +2 Base Ascension Count per second of Ascension time", + "wowPlatonicGain": "Gain more Platonic Cubes based on Ascension Count [up to x3 at 2.674e9 Ascension Count]" + }, + "233": { + "salvage": "+2 Salvage" + }, + "235": { + "salvage": "+2 Salvage" + }, + "237": { + "salvage": "+3 Salvage" + }, + "240": { + "allCubeGain": "All Cube Gain x1.2" + }, + "245": { + "salvage": "+3 Salvage" + }, + "250": { + "allCubeGain": "All Cube Gain x1.05", + "multiplicativeObtainium": "Obtainium x1.1", + "multiplicativeOffering": "Offerings x1.5", + "quarkGain": "Quark Gain x1.05" + }, + "251": { + "allCubeGain": "All Cube Gain x1.05", + "multiplicativeObtainium": "Obtainium x1.5", + "multiplicativeOffering": "Offerings x1.1", + "quarkGain": "Quark Gain x1.05" + }, + "253": { + "wowHypercubeGain": "Hypercube gain x1.1" + }, + "254": { + "wowCubeGain": "Cube gain x1.1" + }, + "255": { + "wowTesseractGain": "Tesseract gain x1.1" + }, + "256": { + "wowPlatonicGain": "Platonic Cubes x1.1", + "overfluxConversionRate": "Overflux Powder conversion x1.05" + }, + "257": { + "allCubeGain": "All Cube Gain x1.1", + "overfluxConversionRate": "Overflux Powder conversion x1.05" + }, + "258": { + "wowHepteractGain": "Hepteract gain x1.1" + }, + "259": { + "ascensionScore": "Ascension Score multiplier based on Abyss Hepteract expansions [x1.01 per expansion]" + }, + "260": { + "ascensionCountMultiplier": "Ascension Count x1.1" + }, + "261": { + "ascensionCountMultiplier": "Ascension Count x1.1" + }, + "262": { + "allCubeGain": "All Cube Gain x1.1" + }, + "263": { + "allCubeGain": "All Cube Gain x1.1" + }, + "264": { + "allCubeGain": "All Cube Gain multiplier based on Ascension Count [up to x1.2 at 8e12 Ascension Count]" + }, + "265": { + "allCubeGain": "All Cube Gain multiplier based on Ascension Count [up to x1.2 at 1.6e14 Ascension Count]" + }, + "266": { + "quarkGain": "Quark gain multiplier based on Ascension Count [up to x1.1 at 1e15 Ascension Count]" + }, + "267": { + "ascensionScore": "Ascension Score multiplier based on constants [up to x2 at 1e100,000 Constant]" + }, + "270": { + "wowHepteractGain": "Hepteract gain multiplier based on constants [up to x2 at 1e1,000,000 Constant]", + "constUpgrade1Buff": "Constant Upgrade 1 boosted to 1.06", + "constUpgrade2Buff": "Constant Upgrade 2 boosted to 1.11" + }, + "271": { + "platonicToHypercubes": "Platonic Cubes grant Hypercubes when opened [up to +1 at 1e1,000,000 Constant]" + }, + "382": { + "salvage": "+3 Salvage" + }, + "384": { + "salvage": "+4 Salvage" + }, + "386": { + "salvage": "+5 Salvage" + }, + "388": { + "salvage": "+6 Salvage" + }, + "390": { + "salvage": "+4 Salvage" + }, + "392": { + "salvage": "+5 Salvage" + }, + "394": { + "salvage": "+6 Salvage" + }, + "396": { + "salvage": "+1 Salvage" + }, + "398": { + "salvage": "+2 Salvage" + }, + "400": { + "salvage": "+3 Salvage" + }, + "402": { + "salvage": "+4 Salvage" + }, + "404": { + "salvage": "+5 Salvage" + }, + "406": { + "salvage": "+6 Salvage" + }, + "407": { + "salvage": "+2 Salvage" + }, + "408": { + "salvage": "+7 Salvage" + }, + "409": { + "salvage": "+2 Salvage" + }, + "410": { + "salvage": "+8 Salvage" + }, + "411": { + "salvage": "+1 Salvage" + }, + "413": { + "salvage": "+2 Salvage" + }, + "415": { + "salvage": "+2 Salvage" + }, + "417": { + "salvage": "+3 Salvage" + }, + "419": { + "salvage": "+4 Salvage" + }, + "421": { + "salvage": "+5 Salvage" + }, + "423": { + "salvage": "+6 Salvage" + }, + "425": { + "salvage": "+7 Salvage" + }, + "437": { + "prestigeCountMultiplier": "Gain +100% more Prestige Count per digit in your Prestige Count (additive)!" + }, + "438": { + "duplicationRuneUnlock": "Permanently unlock the Duplication Rune!" + }, + "439": { + "offeringBonus": "Gain +2% more Offerings per digit in your Prestige Count (additive)!" + }, + "442": { + "transcendToPrestige": "For each Transcension Count you gain, you get 1 Prestige Count (subject to Prestige Count multipliers)" + }, + "444": { + "transcensionCountMultiplier": "Gain up to 4x the Transcension Count based on your Prestige Timer (maxes at 10s)" + }, + "452": { + "transcensionCountMultiplier": "Gain +100% more Transcension Count per digit in your Transcension Count (additive)!" + }, + "453": { + "salvage": "Gain +2 Salvage per digit in your Transcension Count!" + }, + "454": { + "prismRuneUnlock": "Permanently unlock the Prism Rune!" + }, + "457": { + "reincarnationToTranscend": "For each Reincarnation Count you gain, you get 1 Transcension Count (subject to Transcension Count multipliers)" + }, + "459": { + "reincarnationCountMultiplier": "Gain up to 4x the Reincarnation Count based on your Prestige Timer (maxes at 1,000s)" + }, + "467": { + "reincarnationCountMultiplier": "Gain +100% more Reincarnation Count per digit in your Reincarnation Count (additive)!" + }, + "468": { + "obtainiumBonus": "Gain +2% more Obtainium per digit in your Reincarnation Count (additive)!" + }, + "470": { + "thriftRuneUnlock": "Permanently unlock the Thrift Rune!" + }, + "474": { + "prestigeCountMultiplier": "Gain up to 4x the Prestige Count based on your Prestige Timer (maxes at 1,000,000s)", + "ascensionCountMultiplier": "Gain up to 1.25x the Ascension Count based on your Ascension Timer (maxes at 1,000,000s)" + } }, "rewards": { - "3": "Gain +.05% to Accelerator Power.", + "3": "Gain +.10% to Accelerator Power.", "4": "Start Transcensions/Challenges with Worker Autobuyer unlocked.", "5": "Gain +1 Accelerator per 500 Workers owned.", "6": "Gain +1 Multiplier per 1,000 Workers owned.", "7": "Gain +1 Accelerator Boost per 2,000 workers owned.", - "10": "Gain +.10% to Accelerator Power.", + "10": "Gain +.15% to Accelerator Power.", "11": "Start Transcensions/Challenges with Investment Autobuyer unlocked.", "12": "Gain +1 Accelerator per 500 Investments owned.", "13": "Gain +1 Multiplier per 1,000 Investments owned.", "14": "Gain +1 Accelerator Boost per 2,000 Investments owned.", - "17": "Gain +.15% to Accelerator Power.", + "17": "Gain +.20% to Accelerator Power.", "18": "Start Transcensions/Challenges with Printer Autobuyer unlocked.", "19": "Gain +1 Accelerator per 500 Printers owned.", "20": "Gain +1 Multiplier per 1,000 Printers owned.", "21": "Gain +1 Accelerator Boost per 2,000 Printers owned.", - "24": "Gain +.20% to Accelerator Power.", + "24": "Gain +.25% to Accelerator Power.", "25": "Start Transcensions/Challenges with Coin Mint Autobuyer unlocked.", "26": "Gain +1 Accelerator per 500 Mints owned.", "27": "Gain +1 Multiplier per 1,000 Mints owned.", "28": "Gain +1 Accelerator Boost per 2,000 Mints owned.", - "31": "Gain +.25% to Accelerator Power.", + "31": "Gain +.30% to Accelerator Power.", "32": "Start Transcensions/Challenges with Alchemy Autobuyer unlocked.", "33": "Gain 10% more Offerings from resets || +1 Accelerator per 500 Alchemies!", "34": "Gain 15% more Offerings from resets (stacks multiplicatively!) || +1 Multiplier per 1,000 Alchemies!", @@ -706,6 +1624,7 @@ "180": "Unlock Tier 6 Ant autobuy, and autobuy Hic and Experientia Ants! Add +1% Base Ant ELO.", "181": "Unlock Tier 7 Ant autobuy, and autobuy Praemoenio Ants! Add +2% Base Ant ELO.", "182": "Unlock Tier 8 Ant autobuy, and autobuy Scientia and Phylacterium Ants! Add +3% Base Ant ELO.", + "186": "Unlock the powers of a 'Wow Square' Talisman. Sounds cool!", "187": "Gain an Ascension Count multiplier based on your score: x{{x}}. Also: Offerings +{{y}}% [Max: 100% at 1M Ascensions]", "188": "Gain +100 Ascension count for all Ascensions longer than 10 seconds. Also: Obtainium +{{x}}% [Max: 100% at 5M Ascensions]", "189": "Gain 20% of Excess time after 10 seconds each Ascension as a linear multiplier to Ascension count. Also: Cubes +{{x}}% [Max: 200% at 500M Ascensions]", @@ -757,17 +1676,330 @@ "270": "Hepteract Gain is boosted by {{x}}% [Max: 100% at 1e1,000,000 const], Constant Upgrade 1 boosted to 1.06 (from 1.05), Constant Upgrade 2 boosted to 1.11 (from 1.10).", "271": "When you open a Platonic Cube, gain {{x}} Hypercubes, rounded down [Max: 1 at 1e1,000,000 Const]" }, + "groupNames": { + "firstOwnedCoin": "Workers", + "secondOwnedCoin": "Investments", + "thirdOwnedCoin": "Printers", + "fourthOwnedCoin": "Coin Mints", + "fifthOwnedCoin": "Alchemies", + "prestigePointGain": "Prestige Gains", + "transcendPointGain": "Transcension Gains", + "reincarnationPointGain": "Reincarnation Gains", + "challenge1": "No Multipliers Challenge", + "challenge2": "No Accelerators Challenge", + "challenge3": "No Shards Challenge", + "challenge4": "Cost+ Challenge", + "challenge5": "Reduced Diamonds Challenge", + "challenge6": "Higher Tax Challenge", + "challenge7": "No Multipliers/Accelerators Challenge", + "challenge8": "Cost++ Challenge", + "challenge9": "No Runes Challenge", + "challenge10": "Sadistic Challenge I", + "challenge11": "Reduced Ants Challenge", + "challenge12": "No Reincarnation Challenge", + "challenge13": "Tax+++ Challenge", + "challenge14": "No Research Challenge", + "accelerators": "Accelerators", + "acceleratorBoosts": "Accelerator Boosts", + "multipliers": "Multipliers", + "antCrumbs": "Galactic Crumbs", + "sacMult": "Ant Sacrifice", + "ascensionCount": "Ascensions", + "constant": "Constants", + "ascensionScore": "Ascension Score", + "speedBlessing": "Speed Blessing", + "speedSpirit": "Speed Spirit", + "singularityCount": "Singularities", + "runeLevel": "Rune Levels", + "runeFreeLevel": "Free Rune Levels", + "campaignTokens": "Campaign Tokens", + "prestigeCount": "Times Prestiged", + "transcensionCount": "Times Transcended", + "reincarnationCount": "Times Reincarnated", + "ungrouped": "Miscellaneous" + }, + "rewardTypes": { + "title": "Achievement-specific bonuses grant you the following!", + "unlocked": "✔", + "locked": "❌", + "acceleratorPower": "• Accelerator Power +{{val}}", + "accelerators": "• Free Accelerators +{{val}}", + "multipliers": "• Free Multipliers +{{val}}", + "accelBoosts": "• Free Accelerator Boosts +{{val}}", + "workerAutobuyer": "• Worker Autobuyer {{unlock}}", + "investmentAutobuyer": "• Investment Autobuyer {{unlock}}", + "printerAutobuyer": "• Printer Autobuyer {{unlock}}", + "mintAutobuyer": "• Coin Mint Autobuyer {{unlock}}", + "alchemyAutobuyer": "• Alchemy Autobuyer {{unlock}}", + "offeringPrestigeTimer": "• Offerings Prestige Timer {{unlock}}", + "crystalMultiplier": "• Crystal Production x{{val}}", + "duplicationRuneUnlock": "• Duplication Rune {{unlock}}", + "quarkGain": "• Quark Gain x{{val}}", + "autoPrestigeFeature": "• Auto-Prestige Feature {{unlock}}", + "prismRuneUnlock": "• Prism Rune {{unlock}}", + "taxReduction": "• Taxes {{val}}", + "particleGain": "• Particle Gain x{{val}}", + "multiplicativeObtainium": "• Obtainium x{{val}}", + "multiplicativeOffering": "• Offerings x{{val}}", + "refineryAutobuy": "• Refinery Autobuy {{unlock}}", + "coalPlantAutobuy": "• Coal Plant Autobuy {{unlock}}", + "coalRigAutobuy": "• Coal Rig Autobuy {{unlock}}", + "pickaxeAutobuy": "• Diamond Pickaxe Autobuy {{unlock}}", + "pandorasBoxAutobuy": "• Pandora's Box Autobuy {{unlock}}", + "crystalUpgrade1Autobuy": "• Crystal Upgrade 1 Autobuy {{unlock}}", + "crystalUpgrade2Autobuy": "• Crystal Upgrade 2 Autobuy {{unlock}}", + "crystalUpgrade3Autobuy": "• Crystal Upgrade 3 Autobuy {{unlock}}", + "crystalUpgrade4Autobuy": "• Crystal Upgrade 4 Autobuy {{unlock}}", + "crystalUpgrade5Autobuy": "• Crystal Upgrade 5 Autobuy {{unlock}}", + "salvage": "• Salvage +{{val}}", + "exemptionTalisman": "• Exemption Talisman {{unlock}}", + "chronosTalisman": "• Chronos Talisman {{unlock}}", + "midasTalisman": "• Midas Talisman {{unlock}}", + "metaphysicsTalisman": "• Metaphysics Talisman {{unlock}}", + "polymathTalisman": "• Polymath Talisman {{unlock}}", + "wowSquareTalisman": "• Wow Square Talisman {{unlock}}", + "conversionExponent": "• Conversion Exponent +{{val}}", + "talismanPower": "• Talisman Power {{val}}", + "sacrificeMult": "• Sacrifice Multiplier x{{val}}", + "ascensionUnlock": "• Ascension {{unlock}}", + "antSpeed": "• Ant Speed x{{val}}", + "antSacrificeUnlock": "• Ant Sacrifice {{unlock}}", + "antAutobuyers": "• Ant Autobuyers +{{val}}", + "antUpgradeAutobuyers": "• Ant Upgrade Autobuyers +{{val}}", + "antELOAdditive": "• Ant ELO +{{val}}", + "antELOMultiplicative": "• Ant ELO x{{val}}", + "ascensionCountMultiplier": "• Ascension Count x{{val}}", + "ascensionCountAdditive": "• Ascension Count +{{val}}", + "allCubeGain": "• All Cube Gain x{{val}}", + "wowCubeGain": "• Wow! Cube Gain x{{val}}", + "wowTesseractGain": "• Wow! Tesseract Gain x{{val}}", + "wowHypercubeGain": "• Wow! Hypercube Gain x{{val}}", + "wowPlatonicGain": "• Wow! Platonic Cube Gain x{{val}}", + "wowHepteractGain": "• Wow! Hepteract Gain x{{val}}", + "ascensionScore": "• Ascension Score x{{val}}", + "ascensionRewardScaling": "• Ascension Reward Scaling {{unlock}}", + "constUpgrade1Buff": "• Constant Upgrade 1 Buff +{{val}}", + "constUpgrade2Buff": "• Constant Upgrade 2 Buff +{{val}}", + "platonicToHypercubes": "• Platonic to Hypercubes +{{val}}", + "statTracker": "• Stat Tracker {{unlock}}", + "antHillUnlock": "• Anthill Tab {{unlock}}", + "diamondUpgrade18": "• Diamond Upgrade 4x3 {{unlock}}", + "diamondUpgrade19": "• Diamond Upgrade 4x4 {{unlock}}", + "diamondUpgrade20": "• Diamond Upgrade 4x5 {{unlock}}", + "overfluxConversionRate": "• Overflux Powder Conversion Rate x{{val}}" + }, + "levelRewards": { + "minLevel": "Minimum Level Required: {{level}}", + "noLevelReq": "Unlocked by Default!", + "salvage": { + "name": "Salvage", + "description": "Gain +1 Salvage per Synergism level! This increases by 1 per 100 levels (e.g. gain +2 at level 101).", + "effect": "Current Effect: +{{salvage}} Salvage" + }, + "quarks": { + "name": "Quark Multiplier", + "description": "Every 20 Synergism levels, multiply your Quark gain by 1.01(!)", + "effect": "Current Effect: +{{mult}} Quark Gain" + }, + "offerings": { + "name": "Offerings Multiplier", + "description": "Every level, gain 1.01x Offerings. After level 100, multiply it by another 1.02 per level!", + "effect": "Current Effect: +{{mult}} Offerings Gain" + }, + "obtainium": { + "name": "Obtainium Multiplier", + "description": "For each level past 14, multiply your Obtainium gain by 1.01. After level 100, multiply it by another 1.02 per level!", + "effect": "Current Effect: +{{mult}} Obtainium Gain" + }, + "wowCubes": { + "name": "Wow! Cube Production", + "description": "For each level past 39, gain +5% more Wow! Cubes. Every 10 levels, multiply the bonus by 1.07.", + "effect": "Current Effect: +{{mult}} Wow! Cube Gain" + }, + "wowTesseracts": { + "name": "Wow! Tesseract Production", + "description": "For each level past 59, gain +5% more Wow! Tesseracts. Every 10 levels, multiply the bonus by 1.07.", + "effect": "Current Effect: +{{mult}} Wow! Tesseract Gain" + }, + "wowHyperCubes": { + "name": "Wow! Hypercube Production", + "description": "For each level past 79, gain +5% more Wow! Hypercubes. Every 10 levels, multiply the bonus by 1.07.", + "effect": "Current Effect: +{{mult}} Wow! Hypercube Gain" + }, + "wowPlatonicCubes": { + "name": "Wow! Platonic Cube Production", + "description": "For each level past 99, gain +5% more Wow! Platonic Cubes. Every 10 levels, multiply the bonus by 1.07.", + "effect": "Current Effect: +{{mult}} Wow! Platonic Cube Gain" + }, + "wowHepteractCubes": { + "name": "Wow! Hepteract Production", + "description": "For each level past 124, gain +5% more Wow! Hepteracts. Every 10 levels, multiply the bonus by 1.07.", + "effect": "Current Effect: +{{mult}} Wow! Hepteract Gain" + }, + "wowOcteracts": { + "name": "Wow! Octeracts!... Wait, what?", + "description": "For each level past 187, gain +5% more Wow! Octeracts. What's more, multiply this by 1.02 per level as well!", + "effect": "Current Effect: +{{mult}} Wow! Octeract Gain" + }, + "ambrosiaLuck": { + "name": "Ambrosia luck", + "description": "For each level past 199, gain +4 Ambrosia Luck! I like stats with symbols next to them.", + "effect": "Current Effect: +{{luck}} Ambrosia Luck" + }, + "redAmbrosiaLuck": { + "name": "Red luck", + "description": "For each level past 249, gain +1 Red Luck! I like stats with symbols next to them.", + "effect": "Current Effect: +{{luck}} Red Luck" + } + }, + "levelMilestones": { + "offeringTimerScaling": { + "name": "Offerings - Prestige Timer Scaling", + "description": "To encourage longer Prestiges, Gain +10% Offerings on Prestige per second, past 10 seconds in your current Prestige.", + "effect": "Current Effect: Gain {{mult}} more Offerings from Prestige timer" + }, + "duplicationRune": { + "name": "Duplication Rune XL!", + "description": "Power up your Duplication Rune! Each level 40+ grants +0.4 to Duplication Rune's Rune Coefficient.", + "effect": "Current Effect: Duplication Rune - Rune Coefficient +{{duplicationRune}}" + }, + "speedRune": { + "name": "Speed Rune XL", + "description": "Power up your Speed Rune! Each level 20+ grants +0.5 to Speed Rune's Rune Coefficient.", + "effect": "Current Effect: Speed Rune - Rune Coefficient +{{speedRune}}" + }, + "prismRune": { + "name": "Prism Rune XL", + "description": "Power up your Prism Rune! Each level 60+ grants +0.3 to Prism Rune's Rune Coefficient.", + "effect": "Current Effect: Prism Rune - Rune Coefficient +{{prismRune}}" + }, + "thriftRune": { + "name": "Thrift Rune XL", + "description": "Power up your Thrift Rune! Each level 80+ grants +0.2 to Thrift Rune's Rune Coefficient.", + "effect": "Current Effect: Thrift Rune - Rune Coefficient +{{thriftRune}}" + }, + "SIRune": { + "name": "SI Rune XL", + "description": "Power up your SI Rune! Each level 100+ grants +0.1 to SI Rune's Rune Coefficient.", + "effect": "Current Effect: SI Rune - Rune Coefficient +{{siRune}}" + }, + "autoPrestige": { + "name": "Auto Prestiging!", + "description": "Why prestige by hand, when you can make your computer do it for you?", + "effect": "Current Effect: Auto-Prestige Feature Unlocked!" + }, + "tier1CrystalAutobuy": { + "name": "Tier 1 Crystal Autobuy", + "description": "You can now automatically buy Diamond Refineries! Also automagically buys the first Crystal upgrade if you can afford it.", + "effect": "Current Effect: See Above!" + }, + "tier2CrystalAutobuy": { + "name": "Tier 2 Crystal Autobuy", + "description": "You can now automatically buy Coal Plants! Also automagically buys the second Crystal upgrade if you can afford it.", + "effect": "Current Effect: See Above!" + }, + "tier3CrystalAutobuy": { + "name": "Tier 3 Crystal Autobuy", + "description": "You can now automatically buy Coal Rigs! Also automagically buys the third Crystal upgrade if you can afford it.", + "effect": "Current Effect: See Above!" + }, + "tier4CrystalAutobuy": { + "name": "Tier 4 Crystal Autobuy", + "description": "You can now automatically buy Diamond Pickaxes! Also automagically buys the fourth Crystal upgrade if you can afford it.", + "effect": "Current Effect: See Above!" + }, + "tier5CrystalAutobuy": { + "name": "Tier 5 Crystal Autobuy", + "description": "You can now automatically buy Pandora's Boxes! Also automagically buys the fifth Crystal upgrade if you can afford it.", + "effect": "Current Effect: See Above!" + }, + "achievementTalismanUnlock": { + "name": "Achievement Talisman!", + "description": "Wear your Achievements with pride, and unlock the new Achievement Talisman!", + "effect": "Current Effect: See Above!" + }, + "achievementTalismanEnhancement": { + "name": "Achievement Talisman Enhancement", + "description": "Your Achievement Talisman may be leveled up one additional time for each Synergism Level!", + "effect": "Current Effect: Achievement Talisman may be leveled up {{level}} additional times." + }, + "salvageChallengeBuff": { + "name": "Salvage Challenge Buff", + "description": "Gain +25 Salvage.
If you are in any Challenge, this value is doubled.
If you are in Challenge 15, this value is doubled.
If you are in EXALT, this value is tripled!
Effects may stack.", + "effect": "Current Effect: +{{salvage}} Salvage" + } + }, + "progressiveAchievements": { + "runeLevel": { + "name": "Progressive Achievement - Purchased Rune Levels", + "description": "Gain AP based on the sum of all rune levels purchased! Best Total: {{x}}", + "apSource": "Gain +1 AP per 1,000 Rune Levels purchased, up to 200,000 Levels.
Gain +1 AP per 2,500 Rune Levels purchased, up to 1,000,000 Levels.
Gain +1 AP per 12,500 Rune Levels purchased, up to 5,000,000 Levels." + }, + "freeRuneLevel": { + "name": "Progressive Achievement - Free Rune Levels", + "description": "Gain AP based on the sum of all your free rune levels! Best Total: {{x}}", + "apSource": "Gain +1 AP per 250 Free Rune Levels owned, up to 25,000 Levels.
Gain +1 AP per 750 Free Rune Levels owned, up to 150,000 Levels.
Gain +1 AP per 2,500 Free Rune Levels owned, up to 500,000 Levels." + }, + "singularityCount": { + "name": "Progressive Achievement - Singularity Count", + "description": "Gain AP based on the highest Singularity you have reached! Highest Reached: {{x}}", + "apSource": "Gain +9 AP for Singularities 1-100
Gain +12 AP for Singularities 101-200
Gain +15 AP for Singularities 201-300" + }, + "ambrosiaCount": { + "name": "Progressive Achievement - Lifetime Ambrosia", + "description": "Gain AP based on your lifetime Ambrosia! Lifetime Ambrosia: {{x}}", + "apSource": "Gain +1 AP per 100 Ambrosia, up to 20,000.
Gain +1 AP per 10,000 Ambrosia, up to 2,000,000.
Gain up to 400 MORE AP, by reaching 100,000,000 total Lifetime Ambrosia!" + }, + "redAmbrosiaCount": { + "name": "Progressive Achievement - Lifetime Red Ambrosia", + "description": "Gain AP based on your lifetime Red Ambrosia! Lifetime Red Ambrosia: {{x}}", + "apSource": "Gain +1 AP per 25 Red Ambrosia, up to 5,000.
Gain +1 AP per 2,500 Red Ambrosia, up to 500,000.
Gain up to 400 MORE AP, by reaching 5,000,000 total Lifetime Red Ambrosia!" + }, + "exalts": { + "name": "Progressive Achievement - EXALT Completions", + "description": "Gain AP for each EXALT you have completed!", + "apSource": "No Golden Quark Upgrades: {{num1}}/{{cap1}} AP
One Challenge Caps: {{num2}}/{{cap2}} AP
Twenty Ascensions: {{num3}}/{{cap3}} AP
No Octeract Effects: {{num4}}/{{cap4}} AP
No Ambrosia Upgrades: {{num5}}/{{cap5}} AP
Grand Singularity Speedrun: {{num6}}/{{cap6}} AP
Sadistic III: {{num7}}/{{cap7}} AP
Taxman's Last Stand: {{num8}}/{{cap8}} AP" + }, + "singularityUpgrades": { + "name": "Progressive Achievement - Golden Quark Upgrades", + "description": "Gain AP for each Golden Quark upgrade you've maxxed!", + "apSource": "Capped upgrades: +5 AP If the upgraded level (excluding free upgrades) is at least the base maximum level." + }, + "octeractUpgrades": { + "name": "Progressive Achievement - Octeract Upgrades", + "description": "Gain AP for each Octeract upgrade you've maxxed!", + "apSource": "Capped upgrades: +8 AP if the upgraded level (excluding free upgrades) is the maximum level." + }, + "redAmbrosiaUpgrades": { + "name": "Progressive Achievement - Red Ambrosia Upgrades", + "description": "Gain AP for each Red Ambrosia upgrade you've maxxed!", + "apSource": "For each upgrade leveled to the max: +10 AP" + }, + "talismanRarities": { + "name": "Talisman Rarity", + "description": "Your talismans grant AP based on their rarity! Best sum of rarities: {{x}}", + "apSource": "Gain +5 AP for each rarity. Unlocked Talismans have a rarity of at least 1." + } + }, "alerts": { "36": "Congratulations on your first Prestige. The first of many. You obtain Offerings. You can use them in the new Runes tab! [Unlocked Runes, Achievements, Diamond Buildings and some Upgrades!]", "38": "Hmm, it seems you are getting richer, being able to get 1 Googol diamonds in a single Prestige. How about we give you another Rune? [Unlocked Duplication Rune in Runes tab!]", "255": "Wow! You gained 1e17 (100 Quadrillion) score in a single Ascension. For that, you can now generate Hepteracts if you get above 1.66e17 (166.6 Quadrillion) score in an Ascension. Good luck!" }, "hover": "Hover over an achievement to view information.", + "tieredExtraRewards": "This Tiered Achievement has extra rewards!", + "ungroupedExtraRewards": "This Achievement has an additional reward!", "totalPoints": "Achievement Points: {{x}}/{{y}} [{{z}}%]", + "achievementPoints": "You have {{x}} Achievement Points (AP)!", "rewardGainMessage": "Reward: {{x}} AP. {{y}} Quarks! {{z}}", "completed": " COMPLETED!", "notification": "You unlocked an achievement: {{m}}", - "quarkBonus": "Gain .01 Quark Multiplier per 500 AP! Quark Multiplier: <>" + "quarkBonus": "Gain .01 Quark Multiplier per 500 AP! Quark Multiplier: <>", + "achievementLevel": "Based on your Achievement Points, you are at Synergism Level {{level}}", + "achievementToNextLevel": "Level {{level}} in {{AP}} AP", + "levelUpNotification": "Level Up! Synergism Level {{old}} → {{new}}", + "complete": "    Tier {{tier}} (COMPLETE!)", + "tier": "    Tier {{tier}}" }, "reset": { "details": { @@ -801,7 +2033,7 @@ "6": "Tries to please Ant God... but fails [Additional Offerings!]", "7": "Helps you build a few things here and there [+3% Building Cost Delay / level, Cap 9,999,999%]", "8": "Knows how to salt and pepper food [Up to 1,000x Rune EXP!]", - "9": "Can make your message to Ant God a little more clear [+1 all Rune Levels / level, Cap 10 Million]", + "9": "Can make your message to Ant God a little more clear [+1 all Rune Levels / level, Cap 3,000]", "10": "Has big brain energy [Additional Obtainium!]", "11": "A valuable offering to the Ant God [Gain up to 3x Sacrifice Rewards!]", "12": "Betray Ant God increasing the fragility of your dimension [Unlocks ant talisman, Up to 2x faster timers on most things]" @@ -851,12 +2083,19 @@ "modeRealTime": "Mode: Real-time", "modeInGameTime": "Mode: In-game time", "sacrificeWhenTimer": "Sacrifice when the timer is at least {{x}} seconds ({{y}}), Currently: {{z}}", - "yourAntELO": "<> <> [{{y}} effective]", - "upgradeMultiplier": "Upgrade Multiplier: <>", - "timeMultiplier": "Time Multiplier: <>", - "resetAnthill": "Reset Anthill features for:", - "antSacMultiplier": "Ant Sacrifice Multiplier: {{y}}x --> <>", - "autoReset": "This resets your Crumbs, Ants and Ant Upgrades in exchange for some multiplier and resources. Continue?", + "yourAntELO": "<> <>", + "upgradeMultiplier": "Upgrade Reward Multiplier: <>", + "timeMultiplier": "Time Reward Multiplier: <>", + "immortalELO": "<> <>", + "immortalELOAntSpeed": "↳ Ant Speed x{{x}}", + "resetAnthill": "Ant Sacrifice", + "resetAlert": "<> Performs a <>", + "immortalELOGain": "• Gain <> <>", + "antSacMultiplier": "↳ Ant Speed x{{x}}", + "offeringMultiplier": "• Gain <>", + "obtainiumMultiplier": "• Gain <>", + "talismanFragments": "• Gain Some Talisman Fragments!", + "autoReset": "This resets your Crumbs, Ants and Ant Upgrades, and performs a Reincarnation in exchange for some Ant Speed and resources. Continue?", "elo": "+{{x}} [>{{y}} ELO]", "antSacrifice": "Ant Sacrifice" }, @@ -867,15 +2106,15 @@ "restrictions": "Transcend and reach the goal except Multipliers do nothing but act like Accelerators, which are nerfed by 50%!", "goal": "Goal: Gain {{value}} Coins in challenge.", "per": { - "1": "+10 base Multiplier Boosts! [+0.05 to power!] Current: ", - "2": "+10% total Multiplier Boosts! Current: ", + "1": "+2 base Multiplier Boosts! [+0.02 to power!] Current: ", + "2": "Duplication Rune level scaling coefficient is increased by 0.75! Current: ", "3": "+0.04 base Rune exp per Offering! Current: " }, "first": "+1 free Multiplier! +1 Base EXP per offering used!", "start": "Start [No Multipliers]", "current": { "1": "+{{value}} Boosts", - "2": "+{{value}}% more Boosts", + "2": "+{{value}} Duplication Rune level scaling coefficient", "3": "+{{value}} Rune EXP [Highest Completion]" } }, @@ -941,14 +2180,14 @@ "per": { "1": "+0.01 Coin --> Diamond conversion exponent on Prestige! Current: ", "2": "Multiply Crystal production by 10! Current: ", - "3": " " + "3": "+5% Prestige Count when Prestiging! Current: " }, "first": "None", "start": "Start [Reduced Diamonds]", "current": { "1": "Exponent = ^{{value}}", "2": "Crystal production x{{value}}", - "3": " " + "3": "Prestige Count +{{value}}%" } }, "6": { @@ -958,15 +2197,15 @@ "goal": "Goal: Gain {{value}} Mythos Shards in challenge.", "per": { "1": "-3.5% Taxes [Multiplicative]! Current: ", - "2": "Thrift Rune Exp +10%! Current: ", - "3": "Prestige Offerings +2%! Current: " + "2": "+0.3 Salvage! Current: ", + "3": "Offerings +2%! Current: " }, "first": "-7.5% Taxes!", "start": "Start ", "current": { "1": "Tax multiplier x{{value}}", - "2": "EXP +{{value}}%", - "3": "+{{value}}% Prestige-based Offerings" + "2": "Salvage +{{value}}", + "3": "+{{value}}% Offerings" } }, "7": { @@ -976,15 +2215,15 @@ "goal": "Goal: Gain {{value}} Mythos Shards in challenge.", "per": { "1": "Accelerator/Multiplier boost power exponent +0.04! Current: ", - "2": "Speed Rune Exp +10%! Current: ", - "3": "Duplication Rune Exp +10%! Current: " + "2": "+0.3 Salvage! Current: ", + "3": "+15% more Transcension Count when Transcending! Current: " }, "first": "Multiplier Boost power +25%! The first Discord-Booster Global Diamond Upgrade.", "start": "Start ", "current": { "1": "Exponent = ^{{value}}", - "2": "EXP +{{value}}%", - "3": "EXP +{{value}}%" + "2": "Salvage +{{value}}", + "3": "Transcension Count +{{value}}%" } }, "8": { @@ -994,15 +2233,15 @@ "goal": "Goal: Gain {{value}} Mythos Shards in challenge.", "per": { "1": "Base Building Power +0.25! Current: ", - "2": "Prism Rune Exp +20%! Current: ", - "3": "Transcend Offerings +4%! Current: " + "2": "+0.4 Salvage! Current: ", + "3": "Offerings +4% (additive with other Challenge bonuses) ! Current: " }, "first": "Unlock the Anthill feature! Includes 20 new Researches. A Global Diamond Upgrade.", "start": "Start ", "current": { "1": "+{{value}}", - "2": "+{{value}}% EXP", - "3": "+{{value}}% Transcend-based offerings" + "2": "Salvage +{{value}}", + "3": "+{{value}}% Offerings" } }, "9": { @@ -1013,14 +2252,14 @@ "per": { "1": "+1 free Ant level! Current: ", "2": "+10% Ant speed [Multiplicative!] Current: ", - "3": "SI Rune Exp +20%! Current: " + "3": "+0.5 Salvage! Current: " }, - "first": "Unlock the Talismans feature! [In Runes tab]. A Global Diamond Upgrade.", + "first": "Unlock Talismans and Blessings, in the rune Tab. A Global Diamond Upgrade.", "start": "Start ", "current": { "1": "+{{value}} free levels", "2": "x{{value}} Ant Speed", - "3": "+{{value}}% EXP" + "3": "+{{value}} Salvage" } }, "10": { @@ -1031,32 +2270,32 @@ "per": { "1": "+100 base ELO for sacrificing ants! Current: ", "2": "+2% Ant Sacrifice Reward! Current: ", - "3": "Reincarnation Offerings +10%! Current: " + "3": "Offerings +10% (additive with other Challenge bonuses)! Current: " }, "first": "Unlock the Ascension Reset Tier!", "start": "Start ", "current": { "1": "+{{value}} Ant ELO", "2": "+{{value}}% Ant Sacrifice reward", - "3": "+{{value}}% Reincarnate-based offerings" + "3": "+{{value}}% Offerings" } }, "11": { "name": "Reduced Ants Challenge || {{- value}} Completions", "flavor": "The great Ant War of '21 wiped off all of the skilled ants.", - "restrictions": "Ascend and reach the goal but only get free ant upgrades and from Challenge8/9 completions. FOR ASCENSION CHALLENGES YOU MUST CLEAR CHALLENGE 10 TO ATTEMPT THEM.", + "restrictions": "Ascend and reach the goal but ant upgrades cannot be purchased. To compensate, Challenge 8 and 9 completions give free ant upgrades. FOR ASCENSION CHALLENGES YOU MUST CLEAR CHALLENGE 10 TO ATTEMPT THEM.", "goal": "Goal: Complete Challenge 10 [Sadistic Challenge I] {{value}} times.", "per": { "1": "+12 free Ant Levels! Current: ", "2": "Ant Speed x(1e5)^completions! Current: ", - "3": "+80 to Rune Caps! Current: " + "3": "+1 to the Rune Level Coefficient of the first five runes! Current: " }, - "first": "Unlock 15 Researches, and unlock the ability to open Tesseracts! Also unlocks the Campaign mode. I wonder what that does?", + "first": "Unlock 15 Researches, and unlock the ability to open Tesseracts! You also get to 'Corrupt' your game... and explore this in the Campaigns feature.", "start": "Start <[(Reduced Ants)]>", "current": { "1": "+{{value}} free ant levels", "2": "Ant Speed x{{value}}", - "3": "+{{value}} to Rune Caps" + "3": "+{{value}}" } }, "12": { @@ -1067,14 +2306,14 @@ "per": { "1": "+50% Obtainium! Current: ", "2": "+12% Offerings! Current: ", - "3": "+1 Cube Tribute per opening! Current: " + "3": "+20% more Reincarnation Count on Reincarnation! Current: " }, - "first": "Unlock 15 Researches, and unlock the mystical Spirit Power! Find these in the Runes tab. Also adds additional Campaigns to beat!", + "first": "Unlock 15 Researches, and unlock the mystical Spirit Power! Find these in the Runes tab. Also adds additional Campaigns and Corruptions!", "start": "Start <[(No Reincarnation)]>", "current": { "1": "+{{value}}% Obtainium", "2": "+{{value}}% Offerings", - "3": "+{{value}} additional Cube Tributes" + "3": "+{{value}}% Reincarnation Count" } }, "13": { @@ -1087,7 +2326,7 @@ "2": "+6 maximum to Talisman Level Cap! Current: ", "3": "+3% Spirit Power effectiveness! Current: " }, - "first": "Unlock 15 Researches, and unlock the power of the Hypercube! Unlock even more Campaigns!", + "first": "Unlock 15 Researches, and unlock the power of the Hypercube! Unlock even more Campaigns and Corruptions!", "start": "Start <[(Tax+++)]>", "current": { "1": "+{{value}}% Corruption Tax", @@ -1103,14 +2342,14 @@ "per": { "1": "+50% stronger effect on researches 1x1 through 1x5. Current: ", "2": "+1 research purchased per roomba tick! Current: ", - "3": "+200 to Rune Caps! Current: " + "3": "+1.5 to the Rune Level Coefficient of the first five runes! Current: " }, - "first": "Unlock 15 Researches, and a way to coalesce your power into the Singularity. Unlock Campaigns, and the ability to create your own Corruption builds!", + "first": "Unlock 15 Researches, and discover a new type of ideal cube - a Platonic one perhaps? Plus more Campaigns and Corruptions of course.", "start": "Start <[(No Research)]>", "current": { "1": "+{{value}}% Power", "2": "+{{value}} per Tick", - "3": "+{{value}} to Rune Caps" + "3": "+{{value}}" } }, "15": { @@ -1161,50 +2400,50 @@ "cubes": { "upgradeNames": { "1": "Wow! I want more Cubes.", - "2": "Wow! I want passive Offering gain too.", + "2": "Wow! I want me some Salvage.", "3": "Wow! I want better passive Obtainium", - "4": "Wow! I want to keep mythos building autobuyers.", - "5": "Wow! I want to keep mythos upgrade autobuyer.", + "4": "Wow! I want to keep Mythos building autobuyers.", + "5": "Wow! I want to keep Mythos upgrade autobuyer.", "6": "Wow! I want to keep auto Mythos gain.", - "7": "Wow! I want the particle building automators.", - "8": "Wow! I want to automate Particle Upgrades.", - "9": "Wow! I want to automate researches better dangit.", + "7": "Wow! I want the Particle building automators.", + "8": "Wow! I want to automate Particle upgrades.", + "9": "Wow! I want to automate Researches better please.", "10": "Wow! This is pretty good but expensive.", - "11": "Wow! I want more Cubes 2.", - "12": "Wow! I want building power to be useful 1.", - "13": "Wow! I want opened Cubes to give more tributes 1.", - "14": "Wow! I want Iris Tribute bonuses to scale better 1.", - "15": "Wow! I want Ares Tribute bonuses to scale better 1.", - "16": "Wow! I want more rune levels 1.", + "11": "Wow! I want more Cubes 2, the sequel.", + "12": "Wow! I want building power to be useful 1, the current.", + "13": "Wow! I want opened Cubes to give more Tributes 1.", + "14": "Wow! I want Iris Tribute bonuses to scale better.", + "15": "Wow! I want Ares Tribute bonuses to scale better.", + "16": "Wow! I want more Rune levels 1.", "17": "Wow! I want just a little bit more Crystal power.", "18": "Wow! I want to accelerate time!", - "19": "Wow! I want to unlock a couple more coin upgrades.", - "20": "Wow! I want to improve automatic rune tools.", - "21": "Wow! I want to hack in more score 1.", + "19": "Wow! I want to unlock a couple more Coin upgrades.", + "20": "Wow! I want to improve automatic Rune tools.", + "21": "Wow! I want to hack in more Ascension Score 1.", "22": "Wow! I wish my Artemis was a little better 1", - "23": "Wow! I want opened Cubes to give more tributes 2.", - "24": "Wow! I want Plutus Tribute bonuses to scale better 1", - "25": "Wow! I want Moloch Tribute bonuses to scale better 1", - "26": "Wow! I want to start Ascensions with rune levels.", + "23": "Wow! I want opened Cubes to give more Tributes 2.", + "24": "Wow! I want Plutus Tribute bonuses to scale better.", + "25": "Wow! I want Moloch Tribute bonuses to scale better.", + "26": "Wow! I want to start Ascensions with Rune levels.", "27": "Wow! I want to start Ascensions with one of each Reincarnation building.", - "28": "Wow! I want to finally render Reincarnating obsolete.", + "28": "Wow! I want to render Reincarnating even MORE obsolete.", "29": "Wow! I want to increase maximum Reincarnation Challenge completions.", "30": "Wow! I want to arbitrarily increase my Cube and Tesseract gain.", "31": "Wow! I want to hack in more score 2.", - "32": "Wow! I want runes to be easier to level up over time.", + "32": "Wow! I want Runes to be easier to level up over time.", "33": "Wow! I want opened Cubes to give more tributes 3.", - "34": "Wow! I want Chronos Tribute bonuses to scale better 1", - "35": "Wow! I want Aphrodite Tribute bonuses to scale better 1", + "34": "Wow! I want Chronos Tribute bonuses to scale better.", + "35": "Wow! I want Aphrodite Tribute bonuses to scale better.", "36": "Wow! I want building power to be useful 2.", - "37": "Wow! I want more rune levels 2.", + "37": "Wow! I want more Rune levels 2.", "38": "Wow! I want more Tesseracts while corrupted!", "39": "Wow! I want more score from Challenge 10 completions.", - "40": "Wow! I want Athena Tribute bonuses to scale better 1.", + "40": "Wow! I want Athena Tribute bonuses to scale better.", "41": "Wow! I want to hack in more score 3.", "42": "Wow! I want some Immaculate Obtainium.", "43": "Wow! I want even more Immaculate Obtainium!", - "44": "Wow! I want Midas Tribute bonus to scale better 1.", - "45": "Wow! I want Hermes Tribute bonus to scale better 1.", + "44": "Wow! I want Midas Tribute bonus to scale better.", + "45": "Wow! I want Hermes Tribute bonus to scale better.", "46": "Wow! I want even MORE Offerings!", "47": "Wow! I want even MORE Obtainium!", "48": "Wow! I want to start ascension with an Ant.", @@ -1243,83 +2482,83 @@ }, "upgradeDescriptions": { "1": "[1x1] You got it! +16.666% 3D Cubes from Ascending per level.", - "2": "[1x2] Plutus grants you +1 Offering per second, no matter what, per level. Also a +0.5% Recycling chance!", + "2": "[1x2] Plutus grants you +3 Salage per level!", "3": "[1x3] Athena grants you +10% more Obtainium, and +80% Auto Obtainium per level.", "4": "[1x4] You keep those 5 useful automation upgrades in the upgrades tab!", - "5": "[1x5] You keep the mythos upgrade automation upgrade in the upgrades tab!", - "6": "[1x6] You keep the automatic mythos gain upgrade in the upgrades tab!", - "7": "[1x7] Automatically buy each Particle Building whenever possible.", - "8": "[1x8] Automatically buy Particle Upgrades.", - "9": "[1x9] The research automator in shop now automatically buys cheapest when enabled. It's like a roomba kinda!", + "5": "[1x5] You keep the Mythos upgrade automation upgrade in the upgrades tab!", + "6": "[1x6] You keep the automatic \"Mythos gain\" upgrade in the upgrades tab!", + "7": "[1x7] Automatically buy each Particle building whenever possible.", + "8": "[1x8] Automatically buy Particle upgrades.", + "9": "[1x9] The Research automator in shop now automatically buys cheapest when enabled. It's like a roomba kinda!", "10": "[1x10] Unlock some tools to automate Ascensions or whatever. Kinda expensive but cool.", "11": "[2x1] You got it again! +9.09% 3D Cubes from Ascending per level.", - "12": "[2x2] Raise building power to the power of (1 + level * 0.09).", - "13": "[2x3] For each 20 Cubes opened at once, you get 1 additional tribute at random.", - "14": "[2x4] Iris shines her light on you. The effect power is now increased by +0.01 (+0.005 if >1000 tributes) per level.", - "15": "[2x5] Ares teaches you the Art of War. The effect power is now increased by +0.01 (+0.0033 if >1000 tributes) per level.", - "16": "[2x6] You got it buster! +20 ALL max rune levels per level.", - "17": "[2x7] Yep. +5 Exponent per level to crystals.", - "18": "[2x8] Quantum tunnelling ftw. +20% global game speed.", - "19": "[2x9] Unlocks new coin upgrades ranging from start of Ascend to post c10 and beyond.", - "20": "[2x10] The rune automator in shop now spends all Offerings automatically, 'splitting' them into each of the 5 runes equally.", - "21": "[3x1] Perhaps score will benefit you more? Gain +5% more score on Ascensions per level.", - "22": "[3x2] The exponent of the bonus of Artemis is increased by 0.05 per level.", - "23": "[3x3] For each 20 Cubes opened at once, you get 1 additional tribute at random.", - "24": "[3x4] Plutus teaches you the Art of the Deal. The effect power is now increased by +0.01 (+0.0033 if >1000 tributes) per level.", - "25": "[3x5] Moloch lends you a hand in communicating with Ant God. The effect power is now increased by +0.01 (+0.0033 if >1000 tributes) per level.", - "26": "[3x6] Start Ascensions with 3 additional rune levels [Does not decrease EXP requirement] per level.", + "12": "[2x2] Raise Building Power to the power of (1 + level * 0.09).", + "13": "[2x3] For each 20 Cubes opened at once, you get 1 additional Tribute at random.", + "14": "[2x4] Iris shines her light on you. Each level increases the amount of Salvage she gives by 1%!", + "15": "[2x5] Ares teaches you the Art of War. The effect of Ares Tributes is raised to the power of (1 + level / 100).", + "16": "[2x6] You got it buster! For Speed and Prism Runes, add +1 to their Rune Coefficients per level!", + "17": "[2x7] Yep. +5 exponent per level to crystals.", + "18": "[2x8] Quantum tunnelling ftw. +20% Global Speed.", + "19": "[2x9] Unlocks new Coin upgrades ranging from start of Ascend to after Challenge 10 and beyond.", + "20": "[2x10] The Rune automator in shop now spends all Offerings automatically, splitting them into unlocked runes evenly. It also buys levels 20 times faster. Have fun!", + "21": "[3x1] Perhaps Score will benefit you more? Gain +5% more Ascension Score per level.", + "22": "[3x2] Te effect of Artemis Tributes is raised to the power of (1 + level / 100).", + "23": "[3x3] For each 20 Cubes opened at once, you get 1 additional Tribute at random.", + "24": "[3x4] Plutus teaches you the Art of the Deal. The effect of his Tributes is raised to the power of (1 + level / 100).", + "25": "[3x5] Moloch lends you a hand in communicating with Ant God. The effect of his Tributes is raised to the power of (1 + level / 100).", + "26": "[3x6] Start Ascensions with enough EXP for 3 Rune Levels per level (may actually be more or less depending on Rune Coefficient). Only affects the first five Runes.", "27": "[3x7] Upon an Ascension, you will start with 1 of each Reincarnation building to speed up Ascensions.", "28": "[3x8] Well, I think you got it? Gain +1% of particles on Reincarnation per second.", "29": "[3x9] Add +4 to Reincarnation Challenge cap per level. Completions after 25 scale faster in requirement!", "30": "[3x10] You now get +40% Cubes and Tesseracts forever!", - "31": "[4x1] You again? +5% more score on Ascensions per level.", + "31": "[4x1] You again? +5% more Ascension Score per level.", "32": "[4x2] Gain +0.1% Rune EXP per second you have spent in an Ascension. This has no cap!", - "33": "[4x3] For each 20 Cubes opened at once, you get yet another additional tribute at random.", - "34": "[4x4] Chronos overclocks the universe for your personal benefit. (Rewards the same as others)", - "35": "[4x5] Aphrodite increases the fertility of your coins. (Rewards the same as others)", - "36": "[4x6] Raise building power to (1 + 0.05 * Level) once more.", - "37": "[4x7] Adds +20 to ALL rune caps again per level.", - "38": "[4x8] Gain +0.5% more Tesseracts on Ascension for each additional level in a corruption you enable.", - "39": "[4x9] Instead of the multiplier being 1.03^(C10 completions), it is now 1.035^(C10 completions)!", - "40": "[4x10] Athena is very smart (Rewards the same as others).", - "41": "[5x1] Yeah yeah yeah, +5% score on Ascension per level. Isn't it enough?", - "42": "[5x2] You now gain +4% Obtainium per level, which is not dependent on corruptions!", - "43": "[5x3] Gain another +3% corruption-independent Obtainium per level.", - "44": "[5x4] Blah blah blah Midas works harder (same rewards as before)", - "45": "[5x5] Blah blah blah Hermes works harder (same rewards as before)", + "33": "[4x3] For each 20 Cubes opened at once, you get yet another additional Tribute at random.", + "34": "[4x4] Chronos overclocks the universe for your personal benefit. The effect of Chronos Tributes is raised to the power of (1 + level / 100).", + "35": "[4x5] Aphrodite increases the fertility of your coins. The effect of Aphrodite Tributes is raised to the power of (1 + level / 100).", + "36": "[4x6] Raise Building Power to (1 + 0.05 * Level) once more.", + "37": "[4x7] For Thrift and Superior Intellect runes, add +1 to the Rune Level Coefficient per level!", + "38": "[4x8] Gain +0.5% more Tesseracts on Ascension for each additional level in a Corruption you enable.", + "39": "[4x9] Instead of the Ascension Score multiplier being 1.03^(C10 completions), it is now 1.035^(C10 completions)!", + "40": "[4x10] Athena is very smart. The effect of Athena Tributes is raised to the power of (1 + level / 100).", + "41": "[5x1] Yeah yeah yeah, +5% Ascension Score per level. Isn't it enough?", + "42": "[5x2] You now gain +4% Immaculate Obtainium per level, which is not dependent on corruptions!", + "43": "[5x3] Gain another +3% Immaculate Obtainium per level.", + "44": "[5x4] Blah blah blah Midas works harder. The effect of Midas Tributes is raised to the power of (1 + level / 100).", + "45": "[5x5] Blah blah blah Hermes works harder. The effect of Hermes Tributes is raised to the power of (1 + level / 100).", "46": "[5x6] Gain +5% more Offerings per level!", "47": "[5x7] Gain +10% more Obtainium per level!", - "48": "[5x8] When you ascend, start with 1 worker Ant (this is a lot better than it sounds!)", + "48": "[5x8] When you ascend, start with 1 Worker Ant (this is a lot better than it sounds!)", "49": "[5x9] When you ascend, gain 1 of each Challenge 6-8 completion.", - "50": "[5x10] What doesn't this boost? +0.01% Accelerators, Multipliers, Accelerator Boosts, +0.02% Obtainium, +0.02% Offerings, +0.04 Max Rune Levels, +1 Effective ELO, +0.0004 Talisman bonuses per level, 0.00066% Tax reduction per level.", + "50": "[5x10] What doesn't this boost? +0.01% Accelerators, Multipliers, Accelerator Boosts, +0.02% Obtainium, +0.02% Offerings, +1 Effective ELO, 0.00066% Tax reduction per level. Every 10,000 levels grant +0.6% Talisman Power!", "51": "[Cx1] Wow! Bakery is open!!! Immediately unlock all automations in the cube tab, and researches as well.", - "52": "[Cx2] These sugar cookies sure boost your blood sugar. Gain +1% global speed per level.", + "52": "[Cx2] These sugar cookies sure boost your blood sugar. Gain +1% Global Speed per level.", "53": "[Cx3] What a hearty snack. Gain +0.1% Quarks per level.", - "54": "[Cx4] Pretty dry, but they suffice. Increase Offering gain by 1% per level.", - "55": "[Cx5] An inventive take on the original chip cookie. Increase Obtainium gain by 1% per level.", + "54": "[Cx4] Pretty dry, but they suffice. Gain +1% Offerings per level.", + "55": "[Cx5] An inventive take on the original chip cookie. Gain +1% Obtainium per level.", "56": "[Cx6] These are a little more exotic. Gain +1 more raw score from Challenge 1 completions per level.", "57": "[Cx7] Yum yum! Now we're talking... or maybe not. Increase the cap of Cube Upgrades 1x1, 2x1, 3x1, 4x1, 5x1 by 1.", "58": "[Cx8] A bit festive! If there is an event, All Cube gain is multiplied by 1.25.", - "59": "[Cx9] Quite sour for a cookie. But it increases your Ascension speed by 0.25% per level, so who is to complain?", + "59": "[Cx9] Quite sour for a cookie. But it increases your Ascension Speed by +0.25% per level, so who is to complain?", "60": "[Cx10] Wow! Bakery had extra ginger from their christmas sale. Reduce the cost of buying Golden Quarks by 0.003% per level.", - "61": "[Cx11] Edible but prone to mistakes. Adds 125 whole milliseconds to the tolerance of code 'time', and increases reward by +2% per level.", - "62": "[Cx12] Platonic loves toffee. Octuple Obtainium and Offering gain in Challenge 15.", - "63": "[Cx13] Brownie Cookies, the best of both worlds. Increase Regular Cube Gain by 1% based on owned Hepteracts (+3% per OOM).", + "61": "[Cx11] Edible but prone to mistakes. Adds 125 whole milliseconds to the tolerance of code 'time', and increases its reward by +2% per level.", + "62": "[Cx12] Platonic loves toffee. 8x your Obtainium and Offering gain in Challenge 15.", + "63": "[Cx13] Brownie Cookies, the best of both worlds. Increase 3D Cube Gain by 1% based on owned Hepteracts (+3% per OOM).", "64": "[Cx14] Some say the Ant God itself penned these fortunes. When you gain a statue from Platonic Cubes, you gain two instead.", - "65": "[Cx15] That's amore, but is quite a crumbful! Increase Ant efficiency by 0.4%. (Roughly every 200 Ants purchased doubles crumb production!)", + "65": "[Cx15] That's amore, but is quite a crumbful! Multiply Ant Speed by 1.004 per purchased Ant.", "66": "[Cx16] You just wish you could have one more cookie baked by her. Gain 2x all Cubes until you purchase OMEGA.", "67": "[Cx17] What the hell are in these??? Anyway, Metaphysics Talisman level cap is increased by 1,337.", "68": "[Cx18] What the heck! These aren't even cookies. +0.01% Quarks per level purchased of this upgrade. +5% more at level 1,000!", "69": "[Cx19] Cookies that you'll never remember again. +12% Golden Quarks this Singularity.", - "70": "[Cx20] The pinnacle of baking. Nothing you'll eat will taste better than this. Gain +0.01% more Octeracts on Ascension if the average level of all corruptions is at a minimum of 14.", - "71": "[Cx21] For each talisman rarity upgrade, multiply Obtainium gain by 1.04", - "72": "[Cx22] For each talisman rarity upgrade, multiply Offering gain by 1.04", + "70": "[Cx20] The pinnacle of baking. Nothing you'll eat will taste better than this. Gain +0.01% more Octeracts per level on Ascension if the average level of your Corruptions is at least 14.", + "71": "[Cx21] For each Talisman Rarity, multiply Obtainium gain by 1.04.", + "72": "[Cx22] For each Talisman Rarity, multiply Offering gain by 1.04.", "73": "[Cx23] Each level adds 1 bonus level to the Infinite Ascent rune!", "74": "[Cx24] Add +0.30 to all Corruption Multiplier values.", - "75": "[Cx25] Each level adds +0.3% effectiveness to free Singularity-tier Upgrades per level! (additive with all other boosts)", - "76": "[Cx26] Each level adds <> for each time threshold you have reached!", - "77": "[Cx27] Each level adds <>!", - "78": "[Cx28] Each level adds +0.3% effectiveness to free Octeract-tier Upgrades per level!", + "75": "[Cx25] Each level adds +0.3% effectiveness to free Golden Quark upgrades per level! (additive with all other boosts)", + "76": "[Cx26] Each level gives <> for each time threshold you have reached!", + "77": "[Cx27] Each level adds <>!", + "78": "[Cx28] Each level adds +0.3% effectiveness to free Octeract Upgrades per level!", "79": "[Cx29] Gain Ambrosia Luck when this upgrade is purchased, based on your Lifetime Red Ambrosia! [Formula is 10 * (Log_10(x))^2]", "80": "[Cx30] You intercept a singular message: <>..." }, @@ -1350,7 +2589,7 @@ "1": "[1x1] Increase the number of free Accelerators gained by 20% from all sources.", "2": "[1x2] Increase the number of free Multipliers gained by 20% from all sources.", "3": "[1x3] Increase the number of free Accelerator Boosts gained by 20% from all sources.", - "4": "[1x4] Increase most rune effects by 10%. (Excludes any recycle chance bonus)", + "4": "[1x4] +10% Rune Power to the first five Runes! (Yes, there are more than five runes)", "5": "[1x5] Multiply the production of all Crystal producers by 1e4.", "6": "[1x6] Gain +5% free Accelerators per level.", "7": "[1x7] Gain +4% free Accelerators per level.", @@ -1367,11 +2606,11 @@ "18": "[1x18] Gain +2 free Accelerator per Accelerator Boost.", "19": "[1x19] Gain +2 free Accelerator per Accelerator Boost.", "20": "[1x20] Gain +3 free Accelerator per Accelerator Boost!", - "21": "[1x21] Most rune effects are increased by 1% per level. (Excludes any recycle chance bonus)", + "21": "[1x21] +1% Rune Power to the first five Runes per level.", "22": "[1x22] Each Offering used increases Rune EXP by 0.6 per level.", "23": "[1x23] Each Offering used increases Rune EXP by another 0.3 per level!", - "24": "[1x24] Prestige and Transcensions base Offering is increased by 0.2 per level.", - "25": "[1x25] Reincarnations base Offering is increased by 0.6 per level.", + "24": "[1x24] Increase your Base Offerings by 0.4 per level.", + "25": "[1x25] Increase your Base Offerings by 0.6 per level.", "26": "[2x1] Multiply all Crystal producer production by 150% per level (Multiplicative).", "27": "[2x2] Multiply all Crystal producer production by 150% per level (Multiplicative).", "28": "[2x3] Coin Exponent is increased by 0.08 per level.", @@ -1409,9 +2648,9 @@ "60": "[3x10] Building Cost Scale is delayed by 0.5% per level.", "61": "[3x11] Gain +50% of your best Obtainium per second AUTOMATICALLY!", "62": "[3x12] Gain an additional +10% of your best Obtainium per second automatically.", - "63": "[3x13] If your Reincarnation lasts at least 2 seconds you gain +1 Obtainium per level.", - "64": "[3x14] If your Reincarnation lasts at least 5 seconds you gain +2 Obtainium per level.", - "65": "[3x15] Increase the rate of gaining Obtainium through Reincarnations by 20% per level.", + "63": "[3x13] If your Reincarnation lasts at least 2 seconds you gain +1 Base Obtainium per level.", + "64": "[3x14] If your Reincarnation lasts at least 5 seconds you gain +2 Base Obtainium per level.", + "65": "[3x15] Gain +20% more Obtainium per level. Quite simple!", "66": "[3x16] Increase the maximum number of [No Multipliers] completions by 5 per level.", "67": "[3x17] Increase the maximum number of [No Accelerators] completions by 5 per level.", "68": "[3x18] Increase the maximum number of [No Shards] completions by 5 per level.", @@ -1422,131 +2661,131 @@ "73": "[3x23] Automatically gain completions for Challenge 3 while running a Reincarnation Challenge", "74": "[3x24] Automatically gain completions for Challenge 4 while running a Reincarnation Challenge", "75": "[3x25] Automatically gain completions for Challenge 5 while running a Reincarnation Challenge", - "76": "[4x1] Welcome to the land of expensive researches. Here's +10% Obtainium per level to help you out!", - "77": "[4x2] Increase the level cap of Thrift rune by 10 per level, and +2% EXP for that rune in particular.", - "78": "[4x3] Increase the level cap of Speed rune by 10 per level, and +2% EXP for that rune in particular.", - "79": "[4x4] Increase the level cap of Prism rune by 10 per level, and +2% EXP for that rune in particular.", - "80": "[4x5] Increase the level cap of Duplication rune by 10 per level, and +2% EXP for that rune in particular.", - "81": "[4x6] You thought the previous researches are expensive? You're going to need this! [+10% Obtainium/level]", - "82": "[4x7] Permanently UNLOCK the Rune of Superior Intellect! [+%Ob / +Ant Speed / +%Off.]", - "83": "[4x8] Taking forever to level up that SI Rune? Here's +5% SI Rune EXP per level.", - "84": "[4x9] Does the new rune kinda suck? Power it up! +0.5% level effectiveness for SI rune per level! Well, except the Offering bonus. Because it's a rune, after all.", + "76": "[4x1] Here's +10% Obtainium per level to help you afford these next Researches!", + "77": "[4x2] Here's +1 Rune Coefficient to Speed Rune, per level!", + "78": "[4x3] Here's +1 Rune Coefficient to Duplication Rune, per level!", + "79": "[4x4] Here's +1 Rune Coefficient to Prism Rune, per level!", + "80": "[4x5] Here's +1 Rune Coefficient to Thrift Rune, per level!", + "81": "[4x6] You thought the previous researches are expensive? You're going to need this! [+10% Obtainium per level]", + "82": "[4x7] Permanently UNLOCK the Rune of Superior Intellect! [Bonuses: Offerings, Obtainium and Ant Speed]", + "83": "[4x8] Taking forever to level up that new Rune? Here's +5% Superior Intellect Rune EXP per level.", + "84": "[4x9] Does the Superior Intellect Rune kinda suck? Power it up! +0.5% Rune Power only for SI Rune per level!", "85": "[4x10] Gain +0.01% more Offerings per level per Challenge completion!", - "86": "[4x11] Yeah, going back to basics. +5% Accelerators/Level.", - "87": "[4x12] 0/5 Multipliers SUCK: +5% Multipliers/Level.", - "88": "[4x13] -1/5 A.Boosts SUCK: +5% Accelerator Boosts/Level.", - "89": "[4x14] -5/5 MULTIPLIERS STILL SUCK: +20% Multiplier Boosts/Level", - "90": "[4x15] Runes don't suck at all, but why not make them even BETTER? +1% Rune Effectiveness/level!", - "91": "[4x16] A simple +5% Rune EXP for all runes!", - "92": "[4x17] Another simple +5% Rune EXP for all runes!", - "93": "[4x18] +1 Accelerator Boost per 20 Summative Rune Levels, per level.", - "94": "[4x19] +20 Multiplier per 8 Summative Rune Levels, per level.", - "95": "[4x20] Gain +4 base Offerings from Reincarnations by purchasing this. Math Nerds will love this!", - "96": "[4x21] Ants slow? Add +0.0002 to Ant efficiency increase per Ant purchased per level.", - "97": "[4x22] Add +4 level to the first six upgradable Ants per level!", - "98": "[4x23] Add +4 level to the next five upgradable Ants per level!", + "86": "[4x11] Yeah, going back to basics. +5% Accelerators per level.", + "87": "[4x12] 0/5 Multipliers SUCK: +5% Multipliers per level.", + "88": "[4x13] -1/5 A.Boosts SUCK: +5% Accelerator Boosts per level.", + "89": "[4x14] -5/5 MULTIPLIERS STILL SUCK: +20% Multiplier Boosts per level", + "90": "[4x15] Runes don't suck at all, but why not make them even BETTER? +1% Rune Power per level!", + "91": "[4x16] A simple +5% Rune EXP for all Runes!", + "92": "[4x17] Another simple +5% Rune EXP for all Runes!", + "93": "[4x18] +1 Accelerator Boost per 20 Rune Levels, per level.", + "94": "[4x19] +20 Multiplier per 8 Rune Levels, per level.", + "95": "[4x20] Gain +4 base Offerings from by purchasing this. Easy Shmeezy.", + "96": "[4x21] Ants slow? Multiply Ant Speed by 1.0002 per Ant purchased.", + "97": "[4x22] Add +4 free levels to the first six upgradable Ants per level!", + "98": "[4x23] Add +4 free levels to the next five upgradable Ants per level!", "99": "[4x24] Is the Quark Shop too hot to resist? Get +1 Quark per hour from Exporting for each level!", - "100": "[4x25] Alright, Platonic is off his rocker. I don't expect you to get this but this will give +1 MORE Quark per hour from Exporting for each level!", - "101": "[5x1] Alright, you're past the big wall. How about adding +.001 to Inceptus Ant efficiency per level?", - "102": "[5x2] Gain +1 bonus level to ALL Ants per level! A rainbow attack!", - "103": "[5x3] Pray to Ant God for +5% sacrifice rewards per level!", - "104": "[5x4] You're beginning to feel like an Ant God (Ant God): +5% sacrifice reward per level!", - "105": "[5x5] Buy this and be able to run the first five Challenges 9,001 times! (Note that requirements scale a LOT faster after 75, and again after 1,000)", - "106": "[5x6] Engrave your talismans with Obtainium to get +0.03 Rune Levels per talisman level per level.", - "107": "[5x7] Refine your talismans with the powder of Obtainium to get +0.03 Rune Levels per talisman level per level again.", + "100": "[4x25] Alright, Platonic is off his rocker. +1 MORE Quark per hour from Exporting for each level!", + "101": "[5x1] Let's add +.001 to Inceptus Ant efficiency per level?", + "102": "[5x2] Gain +1 free level to ALL Ants per level! A rainbow attack!", + "103": "[5x3] Pray to Ant God for +5% Ant Sacrifice rewards per level!", + "104": "[5x4] You're beginning to feel like an Ant God (Ant God): +5% Ant Sacrifice reward per level!", + "105": "[5x5] Buy this and be able to run the first five Challenges up to 9,001 times! (Note that requirements scale a LOT faster after 75, and again after 1,000)", + "106": "[5x6] Engrave your talismans with Obtainium to get +0.1% Talisman Power per level (increases the amount of free levels you get for Runes)!", + "107": "[5x7] Refine your talismans with the powder of Obtainium to get +0.1% Talisman Power per level, again!", "108": "[5x8] A simple trick makes your base Ant ELO increase by 25 per level!", "109": "[5x9] A more convoluted trick makes your base Ant ELO increase by 25 per level again!", - "110": "[5x10] Gain +1% more ELO from Ant sources per level because why not?", - "111": "[5x11] Gotta go fast [+10 max Speed Rune Level per level, +1% EXP to that rune]", - "112": "[5x12] Double Trouble [+10 max Duplication Rune level per level, +1% EXP to that rune]", - "113": "[5x13] Newton's Delight [+10 max Prism Rune Level per level, +1% EXP to that rune]", - "114": "[5x14] Five-Finger discounts [+10 max Thrift Rune Level per level, +1% EXP to that rune]", - "115": "[5x15] Scientific Breakthrough [+10 max SI Rune Level per level +1% EXP to that rune]", - "116": "[5x16] Talismans have +0.015 Rune levels per talisman level per level. Levelception!", - "117": "[5x17] Talismans have another +0.015 Rune levels per talisman level per level!", - "118": "[5x18] For 'neutral' talisman effects, increase by +0.06 per level!", + "110": "[5x10] Gain +1% more Ant ELO from Ant sources per level because why not?", + "111": "[5x11] Here's another +1 Rune Coefficient to Speed Rune, per level!", + "112": "[5x12] Here's another +1 Rune Coefficient to Duplication Rune, per level!", + "113": "[5x13] Here's another +1 Rune Coefficient to Prism Rune, per level!", + "114": "[5x14] Here's another +1 Rune Coefficient to Thrift Rune, per level!", + "115": "[5x15] Here's +1 Rune Coefficient to Superior Intellect Rune, per level! I realize you didn't have an earlier Research for this, so this has twice as many levels.", + "116": "[5x16] Gain yet another +0.1% Talisman Power per level! (Unlike Rune Power, Talisman Power is additive across all sources. Don't ask why.)", + "117": "[5x17] Incredibly, gain another +0.1% Talisman Power per level!", + "118": "[5x18] Try this for size: gain +0.2% Talisman Power per level! It's like twice as good as the other upgrades.", "119": "[5x19] Gain +0.25% Wow! Cubes per level upon Ascension.", "120": "[5x20] Gain another +0.25% Wow! Cubes per level upon Ascension.", - "121": "[5x21] Bend time to your will, making all ticks 2% faster each level.", - "122": "[5x22] Adds +2% Ant sacrifice reward per level.", - "123": "[5x23] Adds +40 base Ant ELO per level.", - "124": "[5x24] Unlock the automator for Ant Sacrifice! [Good luck buying this.]", + "121": "[5x21] Bend (global) time to your will, gaining +2% Global Speed per level.", + "122": "[5x22] +2% Ant Sacrifice rewards per level.", + "123": "[5x23] +40 Ant ELO per level.", + "124": "[5x24] Unlock the automator for Ant Sacrifice!", "125": "[5x25] Good luck, buddy. [+1 Export Quark/hour per level]", - "126": "[6x1] 6 rows? That can't be... You've angered Ant God (+1% Accelerators / level)", - "127": "[6x2] Ant God gets angrier (+1% Accelerator Boosts / level)", - "128": "[6x3] Ant God cannot believe your bravery (+1% Multipliers / level)", + "126": "[6x1] 6 rows? That can't be... (+1% Accelerators / level)", + "127": "[6x2] Another +1% Accelerator Boosts / level!", + "128": "[6x3] The trifecta! +1% Multipliers / level.", "129": "[6x4] Add +1 extra level to Crystal upgrade caps multiplied by Level * Log4(Common Fragments + 1)", - "130": "[6x5] Unlock automation for Fortifying talismans! Activates every 2 real life seconds.", - "131": "[6x6] Turn some Ant Disciples against Ant God, giving +0.5% Rune Effectiveness per level.", - "132": "[6x7] Recruit a couple other Ants towards your side as well, giving +2 free Ant levels per level.", - "133": "[6x8] Using some coalesced Obtainium, you can make Ant Sacrifice 3% better per level.", - "134": "[6x9 lol] The funny number. Gain a +6.9% bonus to Blessing level.", - "135": "[6x10] Unlock automation for Enhancing talismans! Activates every 2 real life seconds.", - "136": "[6x11] It may be time to look back. Makes all ticks 1.5% faster each level.", + "130": "[6x5] Unlock automation for leveling Talismans! Activates every 2 real life seconds.", + "131": "[6x6] Turn some Ant Disciples against Ant God, giving +0.5% Rune Power per level. False prophets, or false profits?", + "132": "[6x7] Recruit a couple other Ants towards your side as well, giving +2 free Ant upgrade levels per level.", + "133": "[6x8] Using some coalesced Obtainium, you can make Ant Sacrifice +3% more bountiful per level.", + "134": "[6x9 lol] The funny number. Gain a +6.9% bonus to Blessing Power.", + "135": "[6x10] Makes it so that the Talisman autobuyer automatically buys to Rarity increase!", + "136": "[6x11] It may be time to look back. Increase your Global Speed by +1.5% per level.", "137": "[6x12] Paying off Wow! Industries, they'll sponsor +1% Cubes per level towards your Ascension bank.", - "138": "[6x13] When you open Wow! Cubes you will get +0.1% tributes per level!", + "138": "[6x13] When you open Wow! Cubes you will get +0.1% Tributes per level!", "139": "[6x14] Make all Tesseract buildings produce 2% faster per level.", - "140": "[6x15] The first of a Tetralogy, this tome reduces the base requirements of Challenge 10 by dividing it by 1e100M! A must-read!", - "141": "[6x16] The Ant God has infiltrated your mind. Run away from your conscience! (+0.8% Accelerators / level)", - "142": "[6x17] Run... RUN FASTER from your nightmares! (+0.8% Accelerator Boosts / level)", - "143": "[6x18] Your resilience somehow gives you +0.8% Multipliers / level!", + "140": "[6x15] The first of a tetralogy, this tome reduces the base requirements of Challenge 10 by dividing it by 1e100M! A must-read!", + "141": "[6x16] The Ant God has infiltrated your mind. Run away from your conscience! +0.8% Accelerators per level", + "142": "[6x17] Run... RUN FASTER from your nightmares! +0.8% Accelerator Boosts per level", + "143": "[6x18] Your resilience somehow gives you +0.8% Multipliers per level!", "144": "[6x19] Your Obtainium gain is increased by 3 * Log4(Uncommon Fragments + 1) * level%! Why is this? I don't know.", "145": "[6x20] Your knowledge from the ant war will help you automatically gain Mortuus Est Ant levels.", - "146": "[6x21] Feed your Disciples pure Obtainium to make your runes +0.4% more effective per level.", + "146": "[6x21] Feed your Disciples pure Obtainium to make them produce +0.4% Rune Power (to the first five runes) per level.", "147": "[6x22] Feed your Ants their own crumbs to make them Log(Crumbs + 10)x faster!", "148": "[6x23] Increase your base Ant ELO by 2.5% per level!", "149": "[6x24] You will gain +0.03% more Offerings per level per level in the Midas Talisman!", - "150": "[6x25] Auto Challenge. Enough said. (Lets you automatically run and complete Challenges!)", - "151": "[7x1] A new row, old upgrade. Makes all ticks 1.2% faster each level.", + "150": "[6x25] Auto Challenge. Enough said. Lets you automatically run and complete Challenges!", + "151": "[7x1] A new row, old upgrade. Increase your Global Speed by +1.2% per level.", "152": "[7x2] Wow! Industries sponsors another +0.9% Cubes per level towards your Ascension bank!", - "153": "[7x3] Hey, I totally didn't steal this idea. You gain 12 tributes of Wow! Cube tier for every Tesseract opened.", + "153": "[7x3] Hey, I totally didn't steal this idea. You gain 12 Tributes of Wow! Cube tier for every Tesseract opened.", "154": "[7x4] Make all Tesseract buildings produce 3% faster per level. Hey, isn't that more than the last research tier?", "155": "[7x5] Tome 2 of 4: How to win over the Ant universe. Another e100M Divider to Challenge 10 Base Requirement on purchase.", - "156": "[7x6] What, again? Alright. +0.6% Accelerators / level.", - "157": "[7x7] Gas, gas, gas. +0.6% Accelerator Boosts / level.", - "158": "[7x8] Dupe DUPE DUPE. +0.6% Multipliers / level.", - "159": "[7x9] Somehow, I can't explain why, you reduce your taxes by 2% multiplicative, based on 3/5 * log10(Rare Fragments)!", - "160": "[7x10] Want a permanent Blessing boost? I know you do. A permanent +25% effect to all Blessings.", - "161": "[7x11] SIGMA KAPPA: +0.3% Rune Effectiveness each level!", + "156": "[7x6] What, again? Alright. +0.6% Accelerators per level.", + "157": "[7x7] Gas, gas, gas. +0.6% Accelerator Boosts per level.", + "158": "[7x8] Dupe DUPE DUPE. +0.6% Multipliers per level.", + "159": "[7x9] Somehow, I can't explain why, you reduce your Taxes by 2% multiplicative, based on 3/5 * log10(Rare Fragments)!", + "160": "[7x10] Want a permanent Blessing boost? I know you do. A permanent +25% Blessing Power increase.", + "161": "[7x11] SIGMA KAPPA: +0.3% Rune Power each level!", "162": "[7x12] More exponentiation! +0.0001% more Inceptus power per level!", "163": "[7x13] Ant God's wanting blood: +2% Ant Sacrifice rewards / level", - "164": "[7x14] Spirit power still sucks, so add +8% power per level!", - "165": "[7x15] Gain 2x the Spirit buffs in Ascension Challenges!", - "166": "[7x16] < T I M E >: +0.9% faster ticks / level ", - "167": "[7x17] Because of sponsorships, Wow! Industries is raising Cubes gained in Ascension by 0.8% per level.", - "168": "[7x18] Gain +0.08% tributes from Cubes per level. You know, you should expect it at this point.", + "164": "[7x14] Spirits suck, so add +8% Spirit Power per level!", + "165": "[7x15] Gain 2x Spirit Power in Ascension Challenges!", + "166": "[7x16] < T I M E >: +0.9% Global Speed per level.", + "167": "[7x17] Because of sponsorships, Wow! Industries is increasing Cubes gained in Ascension by 0.8% per level.", + "168": "[7x18] Gain +0.08% Tributes from Cubes per level. You know, you should expect it at this point.", "169": "[7x19] +4% faster Tesseract Buildings / level. It's GROWING.", "170": "[7x20] Tome 3 of 4: How to totally ROCK Challenge 10. e100m divisor!", - "171": "[7x21] You should know how this goes. +0.4% Accelerators / level", - "172": "[7x22] Accelerator Boosts += 0.004 * Accelerator Boosts", + "171": "[7x21] You should know how this goes. +0.4% Accelerators per level", + "172": "[7x22] Accelerator Boosts += 0.004 * Accelerator Boosts * player.researches[172]", "173": "[7x23] A lot of a small +0.4% Multipliers per level", - "174": "[7x24] Epic Fragments boost Blessing power by 10% * Log10(Epic Shards + 1)", - "175": "[7x25] Automatically buy Constant Upgrades, if they are affordable! They also no longer subtract from your constant.", - "176": "[8x1] Row 8 baby! +0.2% Rune Effectiveness / level.", - "177": "[8x2] +Log10(Crumbs)% to Ant production per level. Pretty cool buff ain't it?", + "174": "[7x24] Epic Fragments boost Blessing Power by +10% * Log10(Epic Shards + 1)", + "175": "[7x25] Automatically buy Constant Upgrades, if they are affordable! They also no longer subtract from your Constant.", + "176": "[8x1] Row 8 baby! +0.2% Rune Power per level.", + "177": "[8x2] +Log10(Crumbs)% to Ant Speed per level. Pretty cool buff ain't it?", "178": "[8x3] +666 Base ELO per level! Spooky number of the devil.", - "179": "[8x4] +0.04% more Offerings per level per midas level!", + "179": "[8x4] +0.04% more Offerings per level per Midas Talisman level!", "180": "[8x5] +1 Export Quark per hour per level, yet again.", - "181": "[8x6] +0.6% faster ticks / level because why not? You're already the speed of light.", - "182": "[8x7] +0.7% Cubes in Ascension bank / level, from dividends in Wow! Stock.", + "181": "[8x6] +0.6% Global Speed per level because why not? You're already the speed of light.", + "182": "[8x7] +0.7% Cubes in Ascension bank per level, from dividends in Wow! Stock.", "183": "[8x8] When you open a Hypercube, you also open 100 Tesseracts! (This works with 7x3, if you were curious.)", - "184": "[8x9] +5% faster Tesseract Buildings / level. ASCENDED.", - "185": "[8x10] Tome 4 of 4: You need to prepare for your Ascent. e100m divisor!", - "186": "[8x11] Something something +0.2% Accelerators pretty cool!", - "187": "[8x12] Something somewhere, +0.2% Accelerator Boosts!", - "188": "[8x13] You are DUPLICATED. +0.2% Multipliers/level", - "189": "[8x14] Legendary Fragments increase Spirit powers by +15% multiplied by Log10(Legendary Fragments + 1)", + "184": "[8x9] +5% faster Tesseract Buildings per level. ASCENDED.", + "185": "[8x10] Tome 4 of 4: How to Train your Ascendant. e100m divisor!", + "186": "[8x11] Something something +0.2% Accelerators per level... pretty cool!", + "187": "[8x12] Something somewhere, +0.2% Accelerator Boosts per level.", + "188": "[8x13] You are a DUPLICATE. +0.2% Multipliers per level", + "189": "[8x14] Legendary Fragments increase Spirit Power by +15% * Log10(Legendary Fragments + 1)", "190": "[8x15] Unlock Automations for all 5 of the Tesseract buildings.", - "191": "[8x16] +0.1% Rune Effectiveness / level. Does this even do anything at this point?", - "192": "[8x17] Each purchased level of Mortuus Est also increases Ascension Cube reward by +0.03%", - "193": "[8x18] +1% Ant Sacrifice Reward per level. Singularity HYPE.", - "194": "[8x19] Increases both Spirit AND Blessing power by 2% per level.", + "191": "[8x16] +0.1% Rune Power per level. Woah!", + "192": "[8x17] Each purchased level of Mortuus Est (the Ant Upgrade) also increases Ascension Cube reward by +0.03%", + "193": "[8x18] +1% Ant Sacrifice reward per level.", + "194": "[8x19] Increases both Spirit Power AND Blessing Power by +2% per level.", "195": "[8x20] Gain +1 export Quark per level, and increases the max timer to redeem Quarks by 5 hours each!", - "196": "[8x21] +0.3% faster ticks / level, because you just can't wait to become the Singularity.", - "197": "[8x22] +0.6% Cubes in Ascension Bank / level. No one knows how. Bank error perhaps.", - "198": "[8x23] +0.06% tributes from Cubes / level!. Wow! Cubes really has a lot of manufacturing errors in your favor.", - "199": "[8x24] +10% faster Tesseract Buildings / level. THE ARISEN. WITH THE PRAISE OF THE SINGULARITY.", - "200": "[8x25] Gain the power of a thousand suns! +0.01% Accelerators, A. Boosts, Multipliers, Offerings, and +0.004% Cubes, +0.04 Max Rune level, +(level/400) max Talisman Level, +(level/200) free Ants, 0.000666% Tax reduction per level." + "196": "[8x21] +0.3% Global Speed per level. Faster than the speed of light?", + "197": "[8x22] +0.6% Cubes in Ascension Bank per level. No one knows how. Bank error perhaps.", + "198": "[8x23] +0.06% Tributes from Cubes per level! Wow! Cubes really has a lot of manufacturing errors in your favor.", + "199": "[8x24] +10% faster Tesseract Buildings / level. THE ARISEN. WITH THE PRAISE OF THE [[REDACTED]].", + "200": "[8x25] Gain the power of a thousand suns! +0.01% Accelerators, A. Boosts, Multipliers, Offerings per level. +0.004% Cubes per level, +(level/200) free Ant upgrade levels, 0.000666% Tax reduction per level. Every 10,000 levels grant +0.4% more Talisman Power!" }, "thanksToResearches": "Thanks to researches you automatically gain {{x}} Obtainium per real life second.", "level": "Level {{x}}/{{y}}", @@ -1589,7 +2828,7 @@ "calculator": "The PL-AT can do addition in the blink of an eye. Not much else though. +14% Quarks from using code 'add' per level, the first level provides the answer and the final level does it automatically!", "calculator2": "The PL-AT X has improved memory capacity, allowing you to store 2 additional uses to code 'add' per level. Final level makes 'add' give 25% more Quarks!", "calculator3": "The PL-AT Ω is infused with some Unobtainium, which is epic! But furthermore, it reduces the variance of Quarks by code 'add' by 10% per level, which makes you more likely to get the maximum multiplier. It also has the ability to give +60 seconds to Ascension Timer per level using that code.", - "calculator4": "The PL-AT δ runs at 4,096Hz, which is a huge improvement over previous models. Add attempts refill 4% faster per level! Final level adds 32 additional capacity!", + "calculator4": "The PL-AT δ runs at 4,096Hz, which is a huge improvement over previous models. Reduce the interval between 'Add' code uses by 4% per level! Final level adds 32 additional capacity!", "calculator5": "The PL-AT Γ model somehow performs more 'powerful' computations, whatever that means. +6 seconds of GQ Export timer per level. +1 capacity every 10 levels, with 6 more at final level!", "calculator6": "The PL-AT _ model was made by Derpsmith, before he was banished from the industry forever. Gain 1 second of Octeract per usage per level. Final level grants 24 additional capacity!", "calculator7": "The PL-AT ΩΩ model was made by Derpsmith Ω, before he was banished from the industry forever. Gain 1 real-life second of Ambrosia Bar Points per usage per level. Final level grants 48 additional capacity!", @@ -1610,10 +2849,10 @@ "challenge15Auto": "Your grandparents had to bend dimensions to gain Challenge 15 score, but not you! Updates Challenge 15 Exponent every tick while in challenge 15!", "extraWarp": "\"Hey dude, get in this portal I built up last night in my shed!\" said the Quack Merchant", "autoWarp": "With the power of Quacks Warp machine will now be able to go into overdrive", - "improveQuarkHept": "Did you know that after 1,000 Quark Hepteracts, their effect is raised to ^0.5? The Seal disapproves. Gain +2% to the diminishing return exponent.", - "improveQuarkHept2": "After 1,024,000 Quark Hepts, their effect is raised to ^0.25!!! Nonsense. Gain +2% to all Quark Hept DRs.", - "improveQuarkHept3": "After ~100 million Quark Hepts, their effect is raised to ^0.16! Absolute rubbish. Gain +2% to all Quark Hept DRs, yet again.", - "improveQuarkHept4": "And when they've given you their all, some stagger and fall after all it's not easy...", + "improveQuarkHept": "After 1,000 Quark Hepts, you gain +20% more Quarks every doubling! This, however, raises the entire reward by +0.01 power per level!", + "improveQuarkHept2": "Want even stronger Quark Hepts? Gain +0.01 power per level to the Quark Hept Exponent!", + "improveQuarkHept3": "You are probably asking why I would sell this to you instead of using it for myself. Well... here's +0.01 power per level!", + "improveQuarkHept4": "+0.01 power per level... You know that 10^0.01 is about 1.023, right?", "shopImprovedDaily": "Hey you. Yeah, you! Quarks make seal merchant happy. Get +5% more of them from code 'daily' per level.", "shopImprovedDaily2": "Gain 1 additional free Singularity Upgrade and 20% more Golden Quarks per use of 'daily' per level!", "shopImprovedDaily3": "Gain 1 additional free Singularity Upgrade and 15% more Golden Quarks per use of 'daily' per level!", @@ -1624,29 +2863,30 @@ "seasonPassInfinity": "[Supplied by - Infinity CO.] Gain 1.2% more cubes per level, multiplicative! (Multiplier is 1.012^level). This effect is raised to the power of 1.25 for Octeract calculations!", "shopSingularityPenaltyDebuff": "Derpsmith was so proud of your performance in the first EXALT that he wants to make your singularity debuffs weaker. At a cost. A big cost.", "obtainiumEX2": "Gain +1% Obtainium per level per Singularity!!!", - "improveQuarkHept5": "[NOT Supplied by - Infinity CO.] This is 1/50 as effective as a normal improver. Why? Because of balancing...", + "improveQuarkHept5": "[NOT Supplied by - Infinity CO.] Adds +0.01 Quark Hept Exponent per level. Platonic claims this is not from Infinity CO., but that might just be a cover.", "shopAmbrosiaGeneration1": "Buying this charm grants +1% more Ambrosia Bar Points per level, for more Ambrosia! Don't know what that means? You soon will.", "shopAmbrosiaGeneration2": "Newly fortified Obtainium Dust makes your charm more powerful, giving +1% more Ambrosia Bar Points per level, stacking multiplicatively.", "shopAmbrosiaGeneration3": "It turns out adding more Obtainium Dust makes your charm more powerful, go figure. +1% more Ambrosia Bar Points per level, stacking multiplicatively.", "shopAmbrosiaGeneration4": "What if you also added Quark Dust??? You will find out. +0.1% more Ambrosia Bar Points per level, stacking multiplicatively.", - "shopAmbrosiaLuck1": "Buying this charm grants +2 ☘ Ambrosia luck per level, for more Ambrosia! Don't know what that means? You soon will.", - "shopAmbrosiaLuck2": "You get a slightly larger four leaf clover, giving +2 ☘ Ambrosia luck per level, stacking additively.", - "shopAmbrosiaLuck3": "You get a slightly greener four leaf clover, giving +2 ☘ Ambrosia luck per level, stacking additively.", - "shopAmbrosiaLuck4": "You get a slightly 'better' four leaf clover, what ever that means, giving +0.6 ☘ Ambrosia luck per level, stacking additively.", - "shopRedLuck1": "A small, lesser dice from the tactician of gambling himself. +0.05 ⚅ Red Ambrosia Luck per level. Every 20 levels, reduce the conversion ratio of Ambrosia Luck to Red Luck by 0.01.", - "shopRedLuck2": "This one seems to have a heavy side. I wonder what that is about. +0.075 ⚅ Red Ambrosia Luck per level. Every 20 levels, reduce the conversion ratio of Ambrosia Luck to Red Luck by 0.01.", - "shopRedLuck3": "When you pick up this dice, it phases out of existence, then returns, the six face-up. +0.1 ⚅ Red Ambrosia Luck per level. Every 20 levels, reduce the conversion ratio of Ambrosia Luck to Red Luck by 0.01.", - "shopAmbrosiaLuckMultiplier4": "Gain ☘ +1% Ambrosia Luck per level! Simple as that.", - "shopOcteractAmbrosiaLuck": "Gain ☘ +1 Ambrosia Luck per digit in your Wow! Octeract inventory per level. Simple as that.", - "shopCashGrabUltra": "For a nominal price, gain up to +20% Blueberry Speed, +80% Cubes and +12% Quarks per level based on Ambrosia, scaling nonlinearly until 10,000,000 lifetime earned.", - "shopAmbrosiaAccelerator": "Each level of this 'accelerator' provides <> real-life seconds worth of Ambrosia Bar Points for every <> Ambrosia gained, from any source!", + "shopAmbrosiaLuck1": "Buying this charm grants +2 Ambrosia luck per level, for more Ambrosia! Don't know what that means? You soon will.", + "shopAmbrosiaLuck2": "You get a slightly larger four leaf clover, giving +2 Ambrosia luck per level, stacking additively.", + "shopAmbrosiaLuck3": "You get a slightly greener four leaf clover, giving +2 Ambrosia luck per level, stacking additively.", + "shopAmbrosiaLuck4": "You get a slightly 'better' four leaf clover, what ever that means, giving +0.6 Ambrosia luck per level, stacking additively.", + "shopRedLuck1": "A small, lesser dice from the tactician of gambling himself. +0.05 Red Luck per level. Every 20 levels, reduce the conversion ratio of Ambrosia Luck to Red Luck by 0.01.", + "shopRedLuck2": "This one seems to have a heavy side. I wonder what that is about. +0.075 Red Luck per level. Every 20 levels, reduce the conversion ratio of Ambrosia Luck to Red Luck by 0.01.", + "shopRedLuck3": "When you pick up this dice, it phases out of existence, then returns, the six face-up. +0.1 Red Luck per level. Every 20 levels, reduce the conversion ratio of Ambrosia Luck to Red Luck by 0.01.", + "shopAmbrosiaLuckMultiplier4": "Gain +1% Ambrosia Luck per level! Simple as that.", + "shopOcteractAmbrosiaLuck": "Gain +1 Ambrosia Luck per digit in your Wow! Octeract inventory per level. Simple as that.", + "shopCashGrabUltra": "For a nominal price, gain up to +15% Ambrosia Bar Points, +120% Cubes and +8% Quarks per level based on Ambrosia, scaling nonlinearly until 10,000,000 lifetime earned.", + "shopAmbrosiaAccelerator": "For each level of this Accelerator, reduce Ambrosia Bar Point requirements for Ambrosia generation by 0.4% per completion of EXALT 5 - No Ambrosia Upgrades.", "shopEXUltra": "With this EX Upgrade, every 1,000 owned Ambrosia adds +0.1% <>, <> and <>! This effect caps at 125,000 * (level of shop upgrade)!", "shopChronometerS": "With this upgrade, you gain +1% Ascension Speed and Global Speed per Singularity number above 200 your Singularity takes place!", - "shopAmbrosiaUltra": "For this absurdly expensive EX Upgrade, every EXALT completion grants +1 permanent ☘ Ambrosia Luck, per level.", + "shopAmbrosiaUltra": "For this absurdly expensive EX Upgrade, every EXALT completion grants +1 permanent Ambrosia Luck, per level.", "shopSingularitySpeedup": "You know all those time-gated Singularity and Octeract Upgrades? Now they accumulate 50 times faster! Thanks to this weird clock that somehow alters even <>...", "shopSingularityPotency": "You know all those free Singularity Upgrades? They are now 266% more effective (so, each is worth the equivalent to 3.66 levels, before softcaps). Why? This kinda weird <>! Don't question it.", - "shopSadisticRune": "What a weird rune; The shopkeeper (who you know no longer can see) says telepathically that it is an Infinite Ascent rune drenched in some <>. There were multiple infinite ascents?", - "shopInfiniteShopUpgrades": "A Shop Voucher, printed on a blue tablet. Each grants +0.005 effective levels for the 'Infinity CO.' Quark shop upgrades, multiplied by the sum of your EXALT completions, all rounded down." + "shopSadisticRune": "What a weird rune; The shopkeeper (who you know no longer can see) says telepathically that it is an Infinite Ascent rune drenched in some <>. Were there other Ascents? Or maybe a <>", + "shopInfiniteShopUpgrades": "A Shop Voucher, printed on a blue tablet. Each grants +0.005 effective levels of the 'Infinity CO.' Quark shop upgrades, multiplied by the sum of your EXALT completions, all rounded down.", + "shopHorseShoe": "Horse Shoes make good protection! Add 3 free levels of Horse Shoe rune, and each level of Horse Shoe rune decreases effects of Singularity Debuffs (except Salvage) by 0.1% per level additive (Max -30%)!" }, "maxed": "Maxed!", "upgradeFor": "Upgrade for {{x}} Quarks", @@ -1697,7 +2937,7 @@ "calculator": "Code 'add' provides <> more Quarks. AutoAnswer: <>, AutoFill: <>", "calculator2": "Code 'add' has <> more capacity. 'add' generates <> more Quarks.", "calculator3": "Code 'add' rewards are <> less random. 'add' generates <> seconds to Ascension Timer.", - "calculator4": "Code 'add' refills <> faster. Capacity is increased by <>.", + "calculator4": "Code 'add' has a <> shorter interval between uses. Capacity is increased by <>.", "calculator5": "Code 'add' generates <> seconds of GQ export timer. Capacity is increased by <>.", "calculator6": "Code 'add' generates <> seconds of Octeracts. Capacity is increased by <>.", "calculator7": "Code 'add' generates <> seconds of Ambrosia Bar Points. Capacity is increased by <>.", @@ -1719,11 +2959,11 @@ "challenge15Auto": "Even in a premium shop it's kinda obvious, right?", "extraWarp": "You can warp <> extra times.", "autoWarp": "If you buy this upgrade, you can toggle <> in the Hepteract Forge tab. Only if you buy this, though.", - "improveQuarkHept": "The Diminishing Returns Exponent on Quark Hepteract is increased by <>.", - "improveQuarkHept2": "The Diminishing Returns Exponent on Quark Hepteract is increased by <>.", - "improveQuarkHept3": "The Diminishing Returns Exponent on Quark Hepteract is increased by <>.", - "improveQuarkHept4": "The Diminishing Returns Exponent on Quark Hepteract is increased by <>.", - "improveQuarkHept5": "The Diminishing Returns Exponent on Quark Hepteract is increased by <>.", + "improveQuarkHept": "The Exponent on Quark Hepteract is increased by <>.", + "improveQuarkHept2": "The Exponent on Quark Hepteract is increased by <>.", + "improveQuarkHept3": "The Exponent on Quark Hepteract is increased by <>.", + "improveQuarkHept4": "The Exponent on Quark Hepteract is increased by <>.", + "improveQuarkHept5": "The Exponent on Quark Hepteract is increased by <>.", "shopImprovedDaily": "Code 'daily' gives <> more quarks.", "shopImprovedDaily2": "Code 'daily' grants <> free Singularity Upgrades and <> more Golden Quarks.", "shopImprovedDaily3": "Code 'daily' grants <> free Singularity Upgrades and <> more Golden Quarks.", @@ -1733,28 +2973,29 @@ "chronometerInfinity": "Ascension timer increases <> faster. Exponent spread is increased by <>.", "seasonPassInfinity": "Ascensions give <> more cubes (of <> types) on Ascension. Octeract production is instead increased by <>.", "shopSingularityPenaltyDebuff": "At Singularity <>, your debuffs are as if you were in <>.", - "shopAmbrosiaLuckMultiplier4": "Gain <> Ambrosia Luck!", - "shopOcteractAmbrosiaLuck": "Octeracts grant <> Ambrosia Luck!", - "shopAmbrosiaGeneration1": "Ambrosia Bar Points are generated <> faster.", - "shopAmbrosiaGeneration2": "Ambrosia Bar Points are generated <> faster.", - "shopAmbrosiaGeneration3": "Ambrosia Bar Points are generated <> faster.", - "shopAmbrosiaGeneration4": "Ambrosia Bar Points are generated <> faster.", - "shopAmbrosiaLuck1": "Ambrosia Luck <>", - "shopAmbrosiaLuck2": "Ambrosia Luck <>", - "shopAmbrosiaLuck3": "Ambrosia Luck <>", - "shopAmbrosiaLuck4": "Ambrosia Luck <>", - "shopRedLuck1": "Red Ambrosia Luck <>, Conversion Ratio <>", - "shopRedLuck2": "Red Ambrosia Luck <>, Conversion Ratio <>", - "shopRedLuck3": "Red Ambrosia Luck <>, Conversion Ratio <>", - "shopCashGrabUltra": "Ambrosia Bar Point gain is <> faster. Also, Cubes <>! Finally, Quarks <>!", - "shopAmbrosiaAccelerator": "For every <> gained, generate <> real-life seconds of Ambrosia Bar Points (<> ABP)!", + "shopAmbrosiaLuckMultiplier4": "Gain <> Ambrosia Luck!", + "shopOcteractAmbrosiaLuck": "Octeracts grant <> Ambrosia Luck!", + "shopAmbrosiaGeneration1": "Ambrosia Bar Points are generated <> faster.", + "shopAmbrosiaGeneration2": "Ambrosia Bar Points are generated <> faster.", + "shopAmbrosiaGeneration3": "Ambrosia Bar Points are generated <> faster.", + "shopAmbrosiaGeneration4": "Ambrosia Bar Points are generated <> faster.", + "shopAmbrosiaLuck1": "<>", + "shopAmbrosiaLuck2": "<>", + "shopAmbrosiaLuck3": "<>", + "shopAmbrosiaLuck4": "<>", + "shopRedLuck1": "<>, <>", + "shopRedLuck2": "<>, <>", + "shopRedLuck3": "<>, <>", + "shopCashGrabUltra": "<>. Also, <>! Finally, <>!", + "shopAmbrosiaAccelerator": "Ambrosia Bar Points required for Ambrosia generation are reduced by <> per completion of EXALT 5 - No Ambrosia Upgrades. Total: <>", "shopEXUltra": "Ambrosia increases <>, <> and <> by <>!", "shopChronometerS": "Ascension Speed and Global Speed are both increased by <>!", - "shopAmbrosiaUltra": "Gain <> Ambrosia Luck from your EXALT completions!", + "shopAmbrosiaUltra": "Gain <> from your EXALT completions!", "shopSingularitySpeedup": "Singularity Perks and Upgrades accumulate <> times faster!", "shopSingularityPotency": "Free Singularity Upgrades are as if you had <> of them.", "shopSadisticRune": "I'm not even sure what this rune can do. Have you purchased it? I can't see you, you know.", - "shopInfiniteShopUpgrades": "Effective levels for the 'Infinity CO.' Quark shop upgrades are increased by <>!" + "shopInfiniteShopUpgrades": "Effective levels for the 'Infinity CO.' Quark Shop upgrades are increased by <>!", + "shopHorseShoe": "Free Horse Shoe levels <>, Singularity Penalty effects <>" } }, "singularity": { @@ -1917,11 +3158,11 @@ }, "irishAnt": { "name": "Irish Ants", - "default": "Ants blessed with the luck of the Irish grant <> Ambrosia Luck!" + "default": "Ants blessed with the luck of the Irish grant <>!" }, "irishAnt2": { "name": "Irish Ants II: Electric Boogaloo", - "default": "Ants blessed with more fortunate luck of the Irish increase base ☘ Ambrosia Luck by <>! +1% per perk level." + "default": "Ants blessed with more fortunate luck of the Irish increase base <> by <>! <> per perk level." }, "overclocked": { "name": "Overclocked", @@ -1978,12 +3219,12 @@ }, "skrauQ": { "name": "skrauQ", - "default": "Multiply all Quark Gain by ((Singularity - 179)/20)^2. Currently: {{amt}}... Yes, it's that good." + "default": "Multiply all Quark Gain by ((Singularity - 179)/20)^2. Currently: <>... Yes, it's <>.
<> This Perk takes into account your current Singularity count, and not your highest!" }, "primalPower": { "name": "Primal Power!", - "hasLevel1": "Gain awesome power by reaching Prime Numbered Singularities! Currently gaining: + <> & <> Ambrosia Luck!", - "default": "Gain awesome power by reaching Prime Numbered Singularities! Currently gaining: <> Ambrosia Luck!" + "hasLevel1": "Gain awesome power by reaching Prime Numbered Singularities! Currently gaining: <> & <>!", + "default": "Gain awesome power by reaching Prime Numbered Singularities! Currently gaining: <>!" }, "permanentBenefaction": { "name": "Permanent Benefaction", @@ -1991,20 +3232,41 @@ }, "infiniteShopUpgrades": { "name": "Orange Infinity Shop Voucher", - "default": "Gain +0.5 to 'Infinity CO.' Quark Shop upgrades per highest Singularity, minus 200, rounded down!. Currently: {{amt}}", - "level2": "Gain +0.8 to 'Infinity CO.' Quark Shop upgrades per highest Singularity, minus 200, rounded down! Currently: {{amt}}" + "default": "Gain +0.5 effective levels of the 'Infinity CO.' Quark Shop upgrades per highest Singularity, minus 200, rounded down! Currently: {{amt}}", + "level2": "Gain +0.8 effective levels of the 'Infinity CO.' Quark Shop upgrades per highest Singularity, minus 200, rounded down! Currently: {{amt}}" + }, + "taxReduction": { + "name": "Accountants Clicking at Your Desktop", + "default": "Reduce your taxes by 50%!" + }, + "recycledContent": { + "name": "... Recycled Content?", + "default": "Increase your <> by 5 per tier! Currently: +{{amount}}" + }, + "infiniteRecycling": { + "name": "Infinite Recycling", + "default": "Your Infinite Ascent Rune gains a third perk: +0.025 <> per level, per tier! Currently: +{{salvage}}" + }, + "recyclistsDesktop": { + "name": "Recyclists Clicking at Your Desktop", + "default": "For all sources of <>, they reduce it by {{i}}% less!" + }, + "demeterHarvest": { + "name": "Demeter's Harvest", + "default": "For all sources of <>, they increase it by {{i}}% more!" } }, "toString": { + "minimum": "Minimum Singularity: {{minSingularity}}", "noMinimum": "No minimal Singularity to purchase required", - "costNextLevel": "Cost for next level: {{amount}} Golden Quarks.", - "spentQuarks": "Spent Quarks: {{amount}}" + "costNextLevel": "Cost for next level: {{amount}} Golden Quarks", + "spentGQ": "Golden Quarks Spent: {{spent}}" }, "data": { "goldenQuarks1": { "name": "Golden Quarks I", "description": "In the future, you will gain 10% more Golden Quarks on Singularities per level!", - "effect": "Permanently gain {{n}}% more Golden Quarks on Singularities." + "effect": "Permanently gain {{n}} more Golden Quarks on Singularities." }, "goldenQuarks2": { "name": "Golden Quarks II", @@ -2018,13 +3280,13 @@ }, "starterPack": { "name": "Starter Pack", - "description": "Buy this! Buy This! Cube gain is permanently multiplied by 5, and gain 6x the Obtainium and Offerings from all sources, post-corruption.", - "effectHave": "You have unlocked a 5x multiplier to Cubes and 6x multiplier to Obtainium and Offerings.", - "effectHaveNot": "You have not unlocked a 5x multiplier to Cubes and 6x multiplier to Obtainium and Offerings." + "description": "Buy this! Buy This! Cube gain is permanently multiplied by 5, and gain 6x the Immaculate Obtainium and Offerings from all sources.", + "effectHave": "You have unlocked a 5x multiplier to Cubes and 6x multiplier to Immaculate Obtainium and Offerings.", + "effectHaveNot": "You have not unlocked a 5x multiplier to Cubes and 6x multiplier to Immaculate Obtainium and Offerings." }, "wowPass": { "name": "Shop Bonanza", - "description": "This upgrade will convince the seal merchant to sell you more cool stuff, which even persist on Singularity!", + "description": "This upgrade will convince the Seal Merchant to sell you more cool stuff, which even persist on Singularity!", "effectHave": "You have unlocked the Shop Bonanza.", "effectHaveNot": "You have not unlocked the Shop Bonanza." }, @@ -2042,7 +3304,7 @@ }, "cookies3": { "name": "Cookie Recipes III", - "description": "Your Bakers threaten to quit without a higher pay. If you do pay them, they will bake even more fancy cookies.", + "description": "Your bakers threaten to quit without a higher pay. If you do pay them, they will bake even more fancy cookies.", "effectHave": "You have appeased the union of Bakers.", "effectHaveNot": "You have not appeased the union of Bakers." }, @@ -2061,74 +3323,74 @@ "ascensions": { "name": "Improved Ascension Gain", "description": "Buying this, you will gain +2% Ascension Count forever, per level! Every 10 levels grants an additional, multiplicative +1% Ascension Count.", - "effect": "Ascension Count increases {{n}}% faster." + "effect": "Ascension Count increases {{n}} faster." }, "corruptionFourteen": { "name": "Level Fourteen Corruptions", - "description": "Buy this to unlock level fourteen corruptions. :)", - "effectHave": "You have gained the ability to use level 14 corruptions. {{m}}", - "effectHaveNot": "You have not gained the ability to use level 14 corruptions. {{m}}" + "description": "Buy this to unlock level fourteen Corruptions. :)", + "effectHave": "You have gained the ability to use level 14 Corruptions. {{m}}", + "effectHaveNot": "You have not gained the ability to use level 14 Corruptions. {{m}}" }, "corruptionFifteen": { "name": "Level Fifteen Corruptions", - "description": "This doesn't *really* raise the corruption limit. Rather, it adds one FREE level to corruption multipliers, no matter what (can exceed cap). :)", - "effectHave": "You have gained a free corruption level. {{m}}", - "effectHaveNot": "You have not gained a free corruption level. {{m}}" + "description": "This doesn't *really* raise the Corruption limit. Rather, it adds one FREE level to Corruption multipliers, no matter what (can exceed cap). :)", + "effectHave": "You have gained a free Corruption level. {{m}}", + "effectHaveNot": "You have not gained a free Corruption level. {{m}}" }, "singOfferings1": { "name": "Offering Charge", "description": "Upgrade this to get +2% Offerings per level, forever!", - "effect": "Permanently gain {{n}}% more Offerings." + "effect": "Permanently gain {{n}} more Offerings." }, "singOfferings2": { "name": "Offering Storm", "description": "Apparently, you can use this bar to attract more Offerings. +8% per level, to be precise.", - "effect": "Permanently gain {{n}}% more Offerings." + "effect": "Permanently gain {{n}} more Offerings." }, "singOfferings3": { "name": "Offering Tempest", "description": "This bar is so prestine, it'll make anyone submit their Offerings. +4% per level, to be precise.", - "effect": "Permanently gain {{n}}% more Offerings." + "effect": "Permanently gain {{n}} more Offerings." }, "singObtainium1": { "name": "Obtainium Wave", "description": "Upgrade this to get +2% Obtainium per level, forever!", - "effect": "Permanently gain {{n}}% more Obtainium." + "effect": "Permanently gain {{n}} more Obtainium." }, "singObtainium2": { "name": "Obtainium Flood", "description": "Holy crap, water bending! +8% gained Obtainium per level.", - "effect": "Permanently gain {{n}}% more Obtainium." + "effect": "Permanently gain {{n}} more Obtainium." }, "singObtainium3": { "name": "Obtainium Tsunami", "description": "A rising tide lifts all boats. +4% gained Obtainium per level.", - "effect": "Permanently gain {{n}}% more Obtainium." + "effect": "Permanently gain {{n}} more Obtainium." }, "singCubes1": { "name": "Cube Flame", "description": "Upgrade this to get +0.6% Cubes per level, forever!", - "effect": "Permanently gain {{n}}% more Cubes." + "effect": "Permanently gain {{n}} more Cubes." }, "singCubes2": { "name": "Cube Blaze", "description": "Burn some more Golden Quarks! +8% gained Cubes per level.", - "effect": "Permanently gain {{n}}% more Cubes." + "effect": "Permanently gain {{n}} more Cubes." }, "singCubes3": { "name": "Cube Inferno", "description": "Even Dante is impressed. +4% gained Cubes per level.", - "effect": "Permanently gain {{n}}% more Cubes." + "effect": "Permanently gain {{n}} more Cubes." }, "singCitadel": { "name": "Citadel of Singularity", "description": "What a unique structual phenomenon... but it gives +2% Obtainium, Offerings, and 3-7D cubes per level! +1% Additional for every 10 levels!", - "effect": "Obtainium, Offerings, and 3-7D Cubes +{{n}}%, forever!" + "effect": "Obtainium, Offerings, and 3-7D Cubes +{{n}}, forever!" }, "singCitadel2": { "name": "Citadel of 'Singularity': The Real Edition", "description": "This actual Citadel gives +2% Obtainium, Offerings, and 3-7D cubes per level! +1% Additional for every 10 levels! Also sets the free level of the fake citadel to whatever level this is.", - "effect": "Obtainium, Offerings, and 3-7D Cubes +{{n}}%, forever!" + "effect": "Obtainium, Offerings, and 3-7D Cubes +{{n}}, forever!" }, "octeractUnlock": { "name": "Octeracts", @@ -2139,7 +3401,7 @@ "singOcteractPatreonBonus": { "name": "Platonic $ells out!!!", "description": "You know that Patreon bonus? Yeah, that's cool and all, but what if it also boosted Octeract production by the same amount?", - "effect": "Octeract production is {{n}}% faster for every $10 per month on the Patreon! Same as the Quark bonus which already exists." + "effect": "Octeract production is {{n}} faster for every $10 per month on the Patreon! Same as the Quark bonus which already exists." }, "offeringAutomatic": { "name": "Blueberry Shards!", @@ -2148,32 +3410,32 @@ }, "intermediatePack": { "name": "Intermediate Pack", - "description": "Double Global Speed, Multiply Ascension speed by 1.5, and gain +2% Quarks forever. Yum... 2% Quark Milk.", + "description": "Double Global Speed, Multiply Ascension Speed by 1.5, and gain +2% Quarks forever. Yum... 2% Quark Milk.", "effectHave": "You have upgraded your package to intermediate.", "effectHaveNot": "You have not upgraded your package to intermediate." }, "advancedPack": { "name": "Advanced Pack", - "description": "Now we're cooking with kerosene! Gain +4% Quarks stack with intermediate, +0.33 to all corruption score multipliers, regardless of level!", + "description": "Now we're cooking with kerosene! Gain +4% Quarks, additive with intermediate pack, +0.33 to all Corruption score multipliers, regardless of level!", "effectHave": "You have bought our advanced package.", "effectHaveNot": "You have not bought our advanced package." }, "expertPack": { "name": "Expert Pack", - "description": "That's a handful! Gain +6% Quarks stack with advanced, 1.5x Ascension Score, Code 'add' gives 1.2x Ascension Timer.", + "description": "That's a handful! Gain +6% Quarks, additive with other packs, 1.5x Ascension Score, Code 'add' gives 1.2x Ascension Timer.", "effectHave": "You have switched to the expert provider.", "effectHaveNot": "You have not switched to the expert provider." }, "masterPack": { "name": "Master Pack", - "description": "A tad insane. Gain +8% Quarks stack with expert, for every level 14 corruption, Ascension score is multiplied by 1.1.", + "description": "A tad insane. Gain +8% Quarks, additive with other packs, for every level 14 Corruption, Ascension Score is multiplied by 1.1.", "effectHave": "You have mastered your inner chakras.", "effectHaveNot": "You have not mastered your inner chakras." }, "divinePack": { "name": "Divine Pack", - "description": "OHHHHH. Gain +10% Quarks stack with master, and multiply Octeract gain by 7.77 if corruptions are all set to 14.", - "effectHave": "You have found the reason for existence.", + "description": "OHHHHH. Gain +10% Quarks, additive with other packs, and multiply Octeract gain by 1.25 for each Corruption at level 14 or higher (1.3 for >=15 and 1.4 for >=16)!", + "effectHave": "You have found the reason for existence. Current Octeract Bonus: +{{n}}", "effectHaveNot": "You have not found the reason for existence." }, "wowPass2": { @@ -2200,7 +3462,7 @@ }, "potionBuff3": { "name": "Potion Decanter of Maddening Instability", - "description": "SHE'S GONNA BLOW!!!! Said Midas, the Golden Quark Salesman. Oh yeah, did we mention he's in the game?", + "description": "SHE'S GONNA BLOW!!!! Said Midas, the Golden Quark Salesman.", "effect": "Potions currently give {{n}}x items!" }, "singChallengeExtension": { @@ -2225,43 +3487,43 @@ }, "singQuarkHepteract": { "name": "I wish my Quark Hepteract was marginally better.", - "description": "Wrong game, oops. Anyway, would you like a very slightly better DR exponent on Quark Hepteract?", - "effect": "The DR exponent is now {{n}}% larger!" + "description": "Wrong game, oops. Anyway, would you like more exponent on your Quark Hepteract. +0.02 per level, to be precise.", + "effect": "Quark Hepteract Exponent +{{n}}" }, "singQuarkHepteract2": { "name": "I wish my Quark Hepteract was marginally better II.", "description": "Still not the right game. Same as the previous upgrade.", - "effect": "The DR exponent is now {{n}}% larger!" + "effect": "Quark Hepteract Exponent +{{n}}" }, "singQuarkHepteract3": { "name": "I wish my Quark Hepteract was marginally better III.", - "description": "I AM NOT THE GODMOTHER YOU ARE LOOKING FOR, DYLAN!", - "effect": "The DR exponent is now {{n}}% larger!" + "description": "I AM NOT THE GODMOTHER YOU ARE LOOKING FOR! This wish is weaker... but it can be overclocked.", + "effect": "Quark Hepteract Exponent +{{n}}" }, "singOcteractGain": { "name": "Octeract Absinthe", "description": "You would have never known this tonic can boost your Octeracts! [+1.25% per level, in fact!]", - "effect": "Octeract Gain +{{n}}%" + "effect": "Octeract Gain +{{n}}" }, "singOcteractGain2": { "name": "Pieces of Eight", "description": "There is indeed eight of them, but each only gives +0.625% bonus, so each level gives +5% Octeract per level.", - "effect": "Octeract Gain +{{n}}%" + "effect": "Octeract Gain +{{n}}" }, "singOcteractGain3": { "name": "The Obelisk Shaped like an Octagon.", "description": "Platonic had to reach pretty far here. +2.5% Octeracts yeah!", - "effect": "Octeract Gain +{{n}}%" + "effect": "Octeract Gain +{{n}}" }, "singOcteractGain4": { "name": "Octahedral Synthesis", "description": "How does this even work!?? +2% Octeracts, you bet!", - "effect": "Octeract Gain +{{n}}%" + "effect": "Octeract Gain +{{n}}" }, "singOcteractGain5": { "name": "The Eighth Wonder of the World", - "description": "is the wonder of the world we live in. [+1% Octeracts. Platonic, this is so stingy! but, he does not care one bit.]", - "effect": "Octeract Gain +{{n}}%" + "description": "is the wonder of the world we live in. +1% Octeracts per level.", + "effect": "Octeract Gain +{{n}}" }, "platonicTau": { "name": "Platonic TAU", @@ -2277,30 +3539,30 @@ }, "platonicDelta": { "name": "Platonic DELTA", - "description": "Time follows you towards the future, after getting this bad boy. Gain +100% more cubes per day in your current singularity, up to +900% at day 9.", + "description": "Time follows you towards the future, after getting this bad boy. Gain +100% more Cubes per day in your current singularity, up to +900% at day 9.", "effectHave": "This upgrade has been purchased", "effectHaveNot": "This upgrade has not been purchased" }, "platonicPhi": { "name": "Platonic PHI", - "description": "Time follows you toward the past as well. Gain 5 additional free Singularity Upgrades per day in your singularity from code daily, up to +50 after 10 days.", + "description": "Time follows you toward the past as well. Gain 5 additional free Singularity Upgrades per day in your singularity from code 'daily', up to +50 after 10 days.", "effectHave": "This upgrade has been purchased", "effectHaveNot": "This upgrade has not been purchased" }, "singFastForward": { "name": "Etherflux Singularities", - "description": "Golden Quark gained by Singularity is increased by 100% (additive), and going singular at your all time highest count gives +1 singularity count!", + "description": "Golden Quark gained by Singularity is increased by 100% (additive), and going singular at your all time highest count gives +1 Singularity count!", "effectHave": "You've transformed the Etherflux!", "effectHaveNot": "You haven't transformed the Etherflux!" }, "singFastForward2": { "name": "Aetherflux Singularities", - "description": "Golden Quark gained by Singularity is increased by 100% (additive) and going singular at your all time highest count gives +1 singularity count! It's like Etherflux but with an A.", + "description": "Golden Quark gained by Singularity is increased by 100% (additive) and going singular at your all time highest count gives +1 Singularity count! It's like Etherflux but with an A.", "effectHave": "You've transformed the Aetherflux!", "effectHaveNot": "You haven't transformed the Aetherflux!" }, "singAscensionSpeed": { - "name": "A hecking good ascension speedup!", + "name": "A hecking good Ascension speedup!", "description": "Ascension Speed is raised to the power of 1.03, raised to 0.97 if less than 1x.", "effect": "Ascension Speed ^{{n}}, ^{{m}} if < 1x" }, @@ -2339,43 +3601,43 @@ }, "singAmbrosiaLuck2": { "name": "Gilded Berries of Good Fortune", - "description": "Literally put some gold onto 'berries' and become lucky. ☘ +2 Ambrosia Luck per level!", - "effect": "☘ Ambrosia Luck +{{n}}(!)" + "description": "Literally put some gold onto 'berries' and become lucky. +2 Ambrosia Luck per level!", + "effect": "+{{n}} Ambrosia Luck!" }, "singAmbrosiaLuck3": { "name": "Dedicated Gold Smelting", - "description": "More gold is dipped onto each berry. ☘ +3 Ambrosia Luck per level!", - "effect": "☘ Ambrosia Luck +{{n}}(!)" + "description": "More gold is dipped onto each berry. +3 Ambrosia Luck per level!", + "effect": "+{{n}} Ambrosia Luck!" }, "singAmbrosiaLuck": { "name": "Absolute Hot", - "description": "Your gold smelters get arbitrarily close to 'Absolute Hot'. ☘ +4 Ambrosia Luck per level, infinitely levelable!", - "effect": "☘ Ambrosia Luck +{{n}}(!)" + "description": "Your gold smelters get arbitrarily close to 'Absolute Hot'. +4 Ambrosia Luck per level, infinitely levelable!", + "effect": "+{{n}} Ambrosia Luck!" }, "singAmbrosiaLuck4": { "name": "The Alchemists from the Beginning of the Game Work for your Golden Berry Needs", - "description": "Yeah, these upgrade titles can be arbitrarily long, too. ☘ +5 Ambrosia Luck per level!", - "effect": "☘ Ambrosia Luck +{{n}}(!)" + "description": "Yeah, these upgrade titles can be arbitrarily long, too. +5 Ambrosia Luck per level!", + "effect": "+{{n}} Ambrosia Luck!" }, "singAmbrosiaGeneration2": { "name": "Hourglass of Importance", - "description": "A somewhat important hourglass which somehow makes your blueberries generate seconds 1% faster per level..", - "effect": "Blueberry Seconds Generation +{{n}}%(!)" + "description": "A somewhat important hourglass which somehow grants +1% faster Ambrosia Bar Point creation per level...", + "effect": "Ambrosia Bar Point creation is +{{n}}% faster!" }, "singAmbrosiaGeneration3": { "name": "Hourglass of Significance", - "description": "An hourglass which is still important, perhaps even more so. +1% Blueberry Second Generation per level.", - "effect": "Blueberry Seconds Generation +{{n}}%(!)" + "description": "An hourglass which is still important, perhaps even more so. +1% faster Ambrosia Bar Point creation per level.", + "effect": "Ambrosia Bar Point creation is +{{n}}% faster!" }, "singAmbrosiaGeneration": { "name": "Hourglass of Limitlessness", - "description": "Remember- this hourglass is really important. But it gives +1% Blueberry Second Generation per level and is infinitely levelable!", - "effect": "Blueberry Seconds Generation +{{n}}%(!)" + "description": "Remember- this hourglass is really important. But it gives +1% faster Ambrosia Bar Point creation per level and is infinitely levelable!", + "effect": "Ambrosia Bar Point creation is +{{n}}% faster!" }, "singAmbrosiaGeneration4": { "name": "Hourglass of Fantasy", - "description": "An hourglass which is unimportant and 'less timely'- whatever that means. +2% Blueberry Second Generation per level.", - "effect": "Blueberry Seconds Generation +{{n}}%(!)" + "description": "An hourglass which is unimportant and 'less timely'- whatever that means. +2% faster Ambrosia Bar Point creation per level.", + "effect": "Ambrosia Bar Point creation is +{{n}}% faster!" }, "singBonusTokens1": { "name": "Spare Change on the Sofa Cushion", @@ -2399,14 +3661,35 @@ }, "singInfiniteShopUpgrades": { "name": "Yellow Infinity Shop Voucher", - "description": "A Shop Voucher, printed on a yellow tablet. Each grants +1 effective level the 'Infinity CO.' Quark Shop upgrades.", - "effect": "You have +{{n}} free levels of the 'Infinity CO.' Quark Shop upgrades." + "description": "A Shop Voucher, printed on a yellow tablet. Each grants +1 effective level of the 'Infinity CO.' Quark Shop upgrades.", + "effect": "You own {{n}} Yellow Infinity Shop Vouchers!" + }, + "singTalismanBonusRunes1": { + "name": "Talisman Buff!", + "description": "Gain +1% Talisman Power per level, additive to other bonuses.", + "effect": "{{n}}% more bonus Rune Levels from Talismans!" + }, + "singTalismanBonusRunes2": { + "name": "Talisman Buff! The second", + "description": "Gain +1% more Talisman Power per level, additive to other bonuses.", + "effect": "{{n}}% more bonus Rune Levels from Talismans!" + }, + "singTalismanBonusRunes3": { + "name": "Talisman Buff! The third", + "description": "Gain +1% more Talisman Power per level again, additive to other bonuses.", + "effect": "{{n}}% more bonus Rune Levels from Talismans!" + }, + "singTalismanBonusRunes4": { + "name": "Talisman Buff! The fourth", + "description": "Gain +1% more Talisman Power per level again, AGAIN, additive to other bonuses. Instead of adding five upgrades, Platonic gave this one twice as many levels. Game design!", + "effect": "{{n}}% more bonus Rune Levels from Talismans!" } }, "goldenQuarkAmount": "You have {{goldenQuarks}} Golden Quarks!", "penalties": { "globalSpeed": "<> is divided by <>.", "ascensionSpeed": "<> is divided by <>.", + "salvage": "<> is reduced by <>", "offeringGain": "<> is divided by <>.", "obtainiumGain": "<> is divided by <>.", "cubeGain": "<> is divided by <>.", @@ -2443,8 +3726,12 @@ "maxed": "(Maxed)", "affordable": "(Affordable)", "enabled": "(Enabled)", + "effectiveLevel": "Effective Level: {{level}}", + "alwaysEnabled": "[<>] This upgrade is always active, even if something else says otherwise!", "infinity": "Infinity", "sacrificeCapital": "SACRIFICE", + "blessCapital": "BLESS", + "spiritCapital": "REJUVENATE", "multiBuyInstructions": "Buy multiple levels at once by holding <> while clicking!", "autoOnColon": "Auto: ON", "autoOffColon": "Auto: OFF", @@ -2452,6 +3739,8 @@ "buyMaxOff": "Buy Max: OFF", "on": "ON", "off": "OFF", + "buyOne": "Buy x1", + "buyMax": "Buy MAX", "updateAlerts": { "june282021": "June 28, 2021: V2.5.3. You have been refunded quarks from calculators if you purchased them. They are no longer refundable so be wary!", "july22021": "July 2, 2021: V2.5.5. You have been refunded quarks from Powder EX upgrade, if you purchased levels. Your T1 ants were also reset and base cost set to 1e700 particles. Powder EX is no longer refundable, though, so be careful!", @@ -2514,10 +3803,10 @@ "24": "Gain 1 Multiplier and 2 Accelerators plus 1% more free Multipliers/Accelerators.", "25": "Gain 1 Multiplier and 1 Accelerators plus 1% more free Multipliers/Accelerators.", "26": "Gain a free Accelerator Boost.", - "27": "Gain free Accelerators based on unspent Coins.", - "28": "Gain a free Multiplier per 160 Coin producers bought.", - "29": "Gain a free Accelerator per 80 Coin producers bought.", - "30": "Gain free Multipliers based on unspent Coins.", + "27": "Gain free levels of Speed Rune based on unspent Coins. (Max: +100)", + "28": "Gain a free level of Duplication Rune per 400 Coin producers bought. (Max: +100)", + "29": "Gain a free level of Speed Rune per 400 Coin producers bought. (Max: +100)", + "30": "Gain free levels of Duplication Rune based on unspent Coins. (Max: +100)", "31": "Gain 1 free Accelerator Boost per 2,000 Coin producers bought.", "32": "Gain free Accelerators based on Unspent Diamonds.", "33": "Gain 1 free Multiplier for each Accelerator Boost owned.", @@ -2548,12 +3837,12 @@ "58": "Printer production is multiplied by 1e+15000.", "59": "Coin Mint production is multiplied by 1e+25000.", "60": "Alchemies production is multiplied by 1e+35000.", - "61": "Welcome to Reincarnation! +5% Offering Recycle, +2 EXP/Offering!", - "62": "Completing Challenges, automatically or manually, increase Offerings gained in Reincarnation. Bonus subject to time multiplier!", + "61": "Welcome to Reincarnation! +7 Salvage to get you started.", + "62": "Gain +0.02 Base Offerings per Challenge Completion! (Max: +12)", "63": "Crystal Production is multiplied based on Particles to the sixth power [Caps at 1e6000x].", "64": "Mythos Shard Production is multiplied by your Particles squared.", "65": "Multiply the gain of Particles from Reincarnation by 5x!", - "66": "When you use an Offering, every unlocked rune will get 1 free experience.", + "66": "Add +2 to the Rune Coefficient of the first five Runes. Reduce how many offerings you need to level up your Runes!", "67": "Atom gain is increased by 3% per Particle producer purchased!", "68": "Gain a free Multiplier for every 1e1000x increase in tax.", "69": "Gain more Obtainium based on your particle gain. [Works with automation at a reduced rate!]", @@ -2561,12 +3850,12 @@ "71": "Runes will gain (Rune Level/25) additional EXP per Offering used.", "72": "Obtainium gain from Reincarnations is multiplied (1 + 2C) where C is #Reincarnation Challenges completed, up to 50x!", "73": "Gain +100% free Accelerator Boosts and +10 free Crystal Upgrade levels, but only in Reincarnation Challenges.", - "74": "Obtainium gain is increased based on highest ever unspent Offerings. [Max: 100,000 Unspent]", + "74": "Obtainium gain is increased based on highest ever unspent Offerings. [Max: 100,000 Offerings]", "75": "Offering gain is increased based on highest ever unspent Obtainium [Max: 30,000,000 Obtainium]", - "76": "Ant generation kinda slow? I agree! Make all Ant tiers 5x faster!", - "77": "This is Synergism, right? Let's make each purchased Ant make all Ants 0.4% faster.", - "78": "Gain an Ant speed multiplier equivalent to (1 + 0.005 * (log10(MAX Offerings + 1))^2).", - "79": "The Ant God will accept an arbitrary number of Particles in order to give you 10% more from sacrifices.", + "76": "Ant generation kinda slow? I agree! Multiply Ant Speed by 5.", + "77": "This is Synergism, right? Let's make each purchased Ant multiply Ant Speed by 1.004.", + "78": "Gain an Ant speed multiplier! x(1 + 0.005 * (log10(MAX Offerings + 1))^2) Ant Speed, to be precise.", + "79": "The Ant God will accept an arbitrary number of Particles in order to give you 10% more rewards from Ant Sacrifice.", "80": "The Ant God will accept a larger arbitrary number of Particles to give you more Ant ELO.", "81": "Automatically buy Workers if affordable.", "82": "Automatically buy Investments if affordable.", @@ -2622,7 +3911,7 @@ "5": "Crystal production x{{x}}" }, "crystalUpgrades": { - "1": "Gain a 5% multiplicative boost to Crystals per AP per level.", + "1": "Gain a 1% multiplicative boost to Crystals per AP per level.", "2": "Gain a boost to Crystals based on held coins per level.", "3": "Each purchased Crystal producer increases generation of Crystal producers by .1% per level. [MAX: {{max}}%]", "4": "Improve the multiplier to coin production by .05 exponent per level. [MAX: +{{max}}]", @@ -2638,7 +3927,7 @@ "4": "Obtainium gain x{{x}}", "5": "Ant Speed x{{x}}", "6": "+{{x}} free Ant Levels", - "7": "+{{x}} free Rune Levels, +{{y}} to Rune Cap", + "7": "+{{x}} free Rune Levels", "8": "Rune EXP x{{x}}", "9": "Runes effectiveness x{{x}}", "10": "Cubes/Tesseracts on Ascension x{{x}}" @@ -2650,9 +3939,9 @@ "4": "Increase Obtainium gain +4% per level.", "5": "Multiply Ant speed by (1 + log10(Constant + 1)/10)^level", "6": "Add +2 free Ant Levels per level.", - "7": "Provides 7 free rune levels and increases the rune cap by 3 per level.", + "7": "Provides 7 free rune levels per level, up to +7,000.", "8": "Increase the rune EXP given by Offerings by 10% per level [Additive]", - "9": "When bought, rune effectiveness is increased by Log4(Talisman Shards +1) %", + "9": "When bought, Rune Power is increased by Log4(Talisman Shards +1) %", "10": "When bought, gain Log4(Constant + 1)% more Wow! Cubes and Tesseracts on Ascension." }, "effects": { @@ -2673,7 +3962,7 @@ "15": "Effect: Mint Generation x{{x}}", "16": "Effect: Gain {{x}}x more Diamonds on Prestige", "17": "Effect: Mint Production x1e100 (Duh)", - "18": "Effect: Printer Production x", + "18": "Effect: Printer Production x{{x}}", "19": "Effect: Investment Production x{{x}}", "20": "Effect: All coin production is further multiplied by {{x}} [Stacks with upgrade 1]!", "21": "Effect: {{x}} Multipliers, +{{y}} Accelerators.", @@ -2682,10 +3971,10 @@ "24": "Effect: {{x}} Multipliers, +{{y}} Accelerators.", "25": "Effect: {{x}} Multipliers, +{{y}} Accelerators.", "26": "Effect: +1 Accelerator Boost.", - "27": "Effect: +{{x}} Accelerators.", - "28": "Effect: +{{x}} Multipliers.", - "29": "Effect: +{{x}} Accelerators.", - "30": "Effect: +{{x}} Multipliers.", + "27": "Effect: +{{x}} free Speed Rune levels!", + "28": "Effect: +{{x}} free Duplication Rune levels!", + "29": "Effect: +{{x}} free Speed Rune levels!", + "30": "Effect: +{{x}} free Duplication Rune levels!", "31": "Effect: +{{x}} Accelerator Boosts", "32": "Effect: +{{x}} Accelerators", "33": "Effect: +{{x}} Multipliers", @@ -2716,8 +4005,8 @@ "58": "Effect: Look above!", "59": "Effect: Look above!", "60": "Effect: Look above!", - "61": "Effect: +5% Offering Recycle/+2EXP per Offerings. Duh!", - "62": "Effect: Base Offering amount for Reincarnations +{{x}}. Challenge yourself!", + "61": "Effect: +7 Salvage. Wow!", + "62": "Effect: +{{x}} Base Offerings. Challenge yourself!", "63": "Effect: All Crystal production x{{x}}", "64": "Effect: All Mythos Shard production x{{x}}", "65": "Effect: 5x Particle gain from Reincarnations. Duh!", @@ -2725,17 +4014,17 @@ "67": "Effect: The first particle-tier producer is {{x}}x as productive.", "68": "Effect: Your compliance with tax laws provides you with {{x}} free Multipliers, for some reason.", "69": "Effect: Cosmic Magnetics will allow you to gain {{x}}x as much Obtainium reincarnating x{{y}} automatic gain.", - "70": "Effect: Contracted time makes your game timers run {{x}}% more quickly.", + "70": "Effect: Global Speed +{{x}}%!", "71": "Effect: Writing's on the wall. Look above!", "72": "Effect: Obtainium multiplier: x{{x}}", "73": "Effect: Same as Transcend upgrade 10, except you MUST be in a Reincarnation Challenge in particular.", "74": "Effect: Obtainium multiplier: x{{x}}", "75": "Effect: Offering Multiplier: x{{x}}", - "76": "Effect: Epic 5x Ants!", - "77": "Effect: Ant Speed Multiplier: x{{x}}", - "78": "Effect: Ant Speed Multiplier: x{{x}}", - "79": "Effect: You will gain +10% rewards =)", - "80": "Effect: Ant ELO +75 if this upgrade is purchased.", + "76": "Effect: Epic 5x Ant Speed!", + "77": "Effect: Ant Speed x{{x}}", + "78": "Effect: Ant Speed x{{x}}", + "79": "Effect: Ant Sacrifice reward will be increased by 10% =)", + "80": "Effect: +75 Ant ELO if this upgrade is purchased.", "81": "Effect: All you need is right above this message.", "120": "Effect: All you need to know is right above this message!", "121": "Effect: -50% Taxes duh!", @@ -2781,6 +4070,10 @@ "particle": "Particle Buildings", "tesseract": "Tesseract Buildings" }, + "achievements": { + "achievements": "Achievements", + "rewards": "Rewards" + }, "runes": { "runes": "Runes", "talismans": "Talismans", @@ -2814,6 +4107,7 @@ "singularityHistory": "Singularity History", "hotkeys": "Hotkeys", "account": "Account", + "messages": "Messages", "confirmation": { "prestige": "Prestige", "transcend": "Transcend", @@ -3067,7 +4361,7 @@ "illiteracy": "Obtainium gain ^{{ effect }}", "deflation": "Diamond gain ^{{ effect }}", "extinction": "Ant Production ^{{ effect }}", - "drought": "Offering EXP divided by {{ effect }}", + "drought": "Salvage {{ effect }}", "recession": "Coin Gain ^{{ effect }}" }, "corruptionStats": { @@ -3084,19 +4378,19 @@ "count": "You have {{ count }} / {{ maxCount }} tokens!", "rewardTexts": { "tutorial": "For clearing your first Campaign, get {{cube}} more Cubes, <> more Obtainium, and <> more Offerings!", - "cube": "Increase Cube gain by {{ reward }} (Maximum +150%).", - "obtainium": "Increase Obtainium gain by <> (Maximum +150%).", - "offering": "Increase Offering gain by <> (Maximum +150%).", - "ascensionScore": "Increase Ascension Score by <> (Maximum +150%).", + "cube": "Increase Cube gain by {{ reward }}", + "obtainium": "Increase Obtainium gain by <>", + "offering": "Increase Offering gain by <>", + "ascensionScore": "Increase Ascension Score by <>", "timeThreshold": "For Reset Rewards that have a penalty if you reset under a certain time, reduce that time by <> seconds (Base: 10s).", - "quark": "Increase Quark gain by <> (Maximum +40%).", - "tax": "Taxes <> (Minimum -25%).", - "c15": "Increase Challenge 15 bonus by <> (Maximum +100%)", - "rune6": "Increase Rune 6 Bonus Levels by <> (Maximum +3).", - "goldenQuark": "Increase Golden Quark gain by <> (Maximum +25%).", - "octeract": "Increase Octeract gain by <> (Maximum +50%).", - "ambrosiaLuck": "Increase ☘ Ambrosia Luck by <> (Maximum ☘ +100).", - "blueberrySpeed": "Increase Ambrosia Bar Points gain by <> (Maximum +10%)." + "quark": "Increase Quark gain by <>", + "tax": "Taxes <>", + "c15": "Increase Challenge 15 bonus by <>", + "rune6": "Increase Rune 6 Bonus Levels by <>", + "goldenQuark": "Increase Golden Quark gain by <>", + "octeract": "Increase Octeract gain by <>", + "ambrosiaLuck": "Increase Ambrosia Luck by <>", + "blueberrySpeed": "Increase Ambrosia Bar Point creation by <>" } }, "currentCampaignPreTitle": "Current Campaign: ", @@ -3126,7 +4420,7 @@ "illiteracy": "Maybe Albert wouldn't have theorized Dilation after all.", "deflation": "Diamond Mine destroyed... no more monopolies!", "extinction": "It killed the dinosaurs too, ya Dingus.", - "drought": "More like California, am I right?", + "drought": "Your farmland is unsalvagable!", "recession": "Yet another 2025 catastrophe." }, "currentLevel": { @@ -3136,7 +4430,7 @@ "illiteracy": "On this Ascension, this corruption is level {{ level }}. Effect: Obtainium gain ^{{ effect }}", "deflation": "On this Ascension, this corruption is level {{ level }}. Effect: Diamond gain ^{{ effect }}", "extinction": "On this Ascension, this corruption is level {{ level }}. Effect: Ant Production ^{{ effect }}", - "drought": "On this Ascension, this corruption is level {{ level }}. Effect: Offering EXP divided by {{ effect }}", + "drought": "On this Ascension, this corruption is level {{ level }}. Effect: Salvage {{ effect }}", "recession": "On this Ascension, this corruption is level {{ level }}. Effect: Coin Gain ^{{ effect }}" }, "prototypeLevel": { @@ -3146,7 +4440,7 @@ "illiteracy": "On next Ascension, this corruption will be level {{ level }}. Effect: Obtainium gain ^{{ effect }}", "deflation": "On next Ascension, this corruption will be level {{ level }}. Effect: Diamond gain ^{{ effect }}", "extinction": "On next Ascension, this corruption will be level {{ level }}. Effect: Ant Production ^{{ effect }}", - "drought": "On next Ascension, this corruption will be level {{ level }}. Effect: Offering EXP divided by {{ effect }}", + "drought": "On next Ascension, this corruption will be level {{ level }}. Effect: Salvage {{ effect }}", "recession": "On next Ascension, this corruption will be level {{ level }}. Effect: Coin Gain ^{{ effect }}" }, "scoreMultiplier": "Current Score Multiplier: {{ curr }} / Next Ascension Score Multiplier: {{ next }}", @@ -3223,21 +4517,21 @@ "loadoutApplied": "Corruption Loadout from previous run has been applied. This will take effect on the next Ascension." }, "footnotes": { - "buildings": "Press [1], [2], [3], [4], or [5] to buy the corresponding tier starting from the top. Press [A] to buy Accelerator, [M] to buy Multiplier, or [B] to buy Accelerator Boost.", - "buildings2": "Press [Left arrow] or [Right arrow] to switch tabs. Enjoy!", - "crystals": "Press [1], [2], [3], [4], or [5] to buy the respective tiered producer. Press [6], [7], [8], [9] or [0] to buy the corresponding Crystal upgrade starting on the left.", - "mythos": "Press [1], [2], [3], [4], or [5] to buy the respective tiered producer.", - "particle": "Press [1], [2], [3], [4], or [5] to buy the respective tiered producer.", - "upgrade": "Press [1], [2], [3], [4], [5] or [6] on your keyboard to use resources for each upgrades.", + "buildings": "Press [1], [2], [3], [4], or [5] on your keyboard to buy the corresponding tier starting from the top. Press [A] to buy Accelerator, [M] to buy Multiplier, or [B] to buy Accelerator Boost.", + "buildings2": "Press [Left arrow] or [Right arrow] on your keyboard to switch tabs. Enjoy!", + "crystals": "Press [1], [2], [3], [4], or [5] on your keyboard to buy the respective tiered producer. Press [6], [7], [8], [9] or [0] to buy the corresponding Crystal upgrade starting on the left.", + "mythos": "Press [1], [2], [3], [4], or [5] on your keyboard to buy the respective tiered producer.", + "particle": "Press [1], [2], [3], [4], or [5] on your keyboard to buy the respective tiered producer.", + "upgrade": "Press [1], [2], [3], [4], [5], or [6] on your keyboard to use resources for each upgrades.", "achievements": { "green": "Green background: Achieved.", "purple": "Purple background: Unachieved, provides bonus multiplier.", "red": "Red background: Unachieved, provides content/feature unlock.", "none": "No background: Unachieved, nothing special." }, - "runes": "Press [1], [2], [3], [4] or [5] on your keyboard to use offerings for each rune, starting with Speed Rune on the left; must have rune unlocked to use hotkey!", + "runes": "Press [1], [2], [3], [4], [5], [6], or [7] on your keyboard to use offerings for each rune, starting with Speed Rune on the left; must have rune unlocked to use hotkey!", "challenges": { - "hotkey": "Press [1], [2]... up to [0] to enter challenges 1 to 10. Press [E] to exit your Transcension or Reincarnation challenge if stuck!", + "hotkey": "Press [1], [2]... up to [0] on your keyboard to enter challenges 1 to 10. Press [E] to exit your Transcension or Reincarnation challenge if stuck!", "finishTranscension": "If you are unable to finish a Transcension challenge, press the hotkey listed above OR click on the purple Challenge \"C\" button on the reset buttons on top.", "leaveReincarnation": "To leave a reincarnation challenge, press the grey Challenge \"C\" instead!" }, @@ -3245,10 +4539,39 @@ "shop": "Hover over each portion of the shop to see what each upgrade does!" }, "buildings": { + "coinInformation": "You hold <> [<>] | Total Ever: <>", + "coinFlavorTexts": { + "1": "You have nothing but 100 coins. You can hire a worker, conveniently for exactly 100 coins!", + "2": "Your workers sure are working hard. Let's buy some investments with our coins!", + "3": "A million coins! Something tells you that being a millionaire here isn't all that impressive.", + "4": "You're beyond rich, you're loaded! However, it does not feel... <> enough.", + "5": "Even with your prestige, it does not do your 1 googol coins justice. You must <> your idea of wealth.", + "6": "1e500 coins! Weren't you dirt poor just a couple hours ago?", + "7": "1e2500 coins! Even the amount of digits in your wealth is becoming noteworthy to you or I. Might I need to <> to regain consciousness?", + "8": "I'm starting to believe that no amount of coins are enough. <> only strengthens your penchant for wealth accumulation.", + "9": "Your wealth now has 100,000 digits. Have you even considered how long it would take to write it out? You should ask <>.", + "10": "Remember when you had just a million coins? Pepperidge farm remembers. <> remembers...", + "11": "The three digits at the front of your total coins are called a <>. Do they even matter in your case?", + "12": "Your wealth is equal to the number of atoms in the universe, raised to the power of <>...", + "13": "You see the ants from down here. However, do you see the ants from <>?", + "14": "If you had a dollar for every digit in your wealth, you would be the richest person on this thing one would call <>...", + "15": "10^1,000,000,000,000,000 Coins! If the exponent were multiplied by 9, in some alternative <> you would overflow the universe with coins.", + "16": "An exponent so large, many wouldn't know it by name.", + "17": "Have you ever questioned why the <> divides your coins? The ultimate creator of this reality, which you believe to be a <> (ideal) being, instructed as much.", + "18": "Octillions of digits in your wealth! What if there were other <> objects in this reality? That would be silly.", + "19": "I still remember ye olde days, when workers were <>, do you?", + "20": "I think you are long past the point where the <> is the only thing holding you back. Right?", + "21": "Sometimes you wonder if the Ant God is better off just feeding off your Coins. You surely have enough.", + "22": "Ant God, Derpsmith, the Gold Guy, <>... who are all of these entities anyway?", + "23": "10 to the <><><><<>", + "24": "They should write stories about your wealth. It only suffices that an <> will do.", + "25": "No one will know my name, but the bards' songs will remain.", + "26": "That's a lot of coins lol" + }, "taxWarning": "Your tax also caps your Coin gain at {{gain}}/s.", "acceleratorPower": "Acceleration Power: {{power}}% || Acceleration Multiplier: {{mult}}x", "multiplierPower": "Multiplier Power: {{power}}x || Multiplier: {{mult}}x", - "acceleratorBoost": "Reset Diamonds and Prestige Upgrades, but add {{amount}}% Acceleration Power and 5 free Accelerators.", + "acceleratorBoost": "Reset Diamonds and Diamond Upgrades, but add {{amount}}% Acceleration Power and {{accelsPerBoost}} free Accelerators.", "coinsPerSecond": "Coins/Sec: {{coins}} [{{percent}}%]", "costCoins": "Cost: {{coins}} coins.", "costDiamonds": "Cost: {{diamonds}} Diamonds.", @@ -3313,7 +4636,7 @@ "currentEffect": "Current Effect: <>" }, "mythosYouHave": "You have {{shards}} Mythos Shards, providing {{mult}} Multiplier Power boosts.", - "eachMultiplierBoost": "Each Multiplier Boost increases the base effect of Multipliers by 0.005!", + "eachMultiplierBoost": "Each Multiplier Boost increases the base effect of Multipliers by 0.02.", "costMythos": "Cost: {{mythos}} Mythos", "atomsYouHave": "You have {{atoms}} Atoms, providing {{power}} Building Power. Multiplier to Coin Production: {{mult}}", "costParticles": "Cost: {{particles}} Particles", @@ -3337,9 +4660,9 @@ "multiBuy": "Purchased {{n}} levels, thanks to Multi Buy!" }, "toString": { - "costNextLevel": "Cost for next level: {{amount}} {{resource}}.", + "costNextLevel": "Cost for next level: {{amount}} Octeracts.", "becomeAffordable": "(Affordable in {{n}})", - "spentOcteracts": "Spent Octeracts: {{amount}}" + "spentOcteracts": "Spent Octeracts: {{spent}}" }, "data": { "octeractStarter": { @@ -3349,27 +4672,27 @@ }, "octeractGain": { "name": "Octeract Cogenesis", - "description": "Have you despised how slow these damn things are? Gain 1% more of them per level! Simple.", - "effect": "Octeract Gain is increased by {{n}}%." + "description": "Are you despising how slow these damn things are? Gain 1% more of them per level! Simple.", + "effect": "Octeract Gain is increased by {{n}}." }, "octeractGain2": { "name": "Octeract Trigenesis", "description": "It turns out that you have six additional dimensions to modify your Cogenesis. +1% more Octs per level!", - "effect": "Octeract Gain is increased by {{n}}%." + "effect": "Octeract Gain is increased by {{n}}." }, "octeractQuarkGain": { "name": "Quark Octeract", "description": "An altered forme of the hepteract, this gives a 1.1% Quark Bonus per level without Diminishing Return.", - "effect": "Quark gain is increased by {{n}}%." + "effect": "Quark gain is increased by {{n}}." }, "octeractQuarkGain2": { "name": "Octo-Hepteract Primality Synergism", - "description": "For every 111 levels of Quark Octeract, you gain 0.01% more quarks per digit in your Quark Hepteract count per level!", + "description": "For every 111 levels of Quark Octeract, you gain 0.01% more Quarks per digit in your Quark Hepteract count per level!", "effect": "Octo-Hepteract Primality Synergism is {{n}} active." }, "octeractCorruption": { "name": "EXTRA CHONKY Corruptions", - "description": "Adds one level to the cap on corruptions. Derpsmith approves.", + "description": "Adds one level to the cap on Corruptions. Derpsmith approves.", "effect": "Corruption level cap is increased by {{n}}" }, "octeractGQCostReduce": { @@ -3379,31 +4702,31 @@ }, "octeractExportQuarks": { "name": "Improved Download Speeds", - "description": "Thanks to ethernet technology, export quarks are increased by 40% per level! Only normal ones.", - "effect": "Export quarks +{{n}}%" + "description": "Thanks to ethernet technology, Export Quarks are increased by 40% per level! Only normal ones.", + "effect": "Export Quarks +{{n}}%" }, "octeractImprovedDaily": { "name": "CHONKER Daily Code", - "description": "Derpsmith hacks into the source code, and adds +1 free Singularity upgrade per day from Daily.", - "effect": "Code 'daily' gives +{{n}} free Singularity upgrades per use." + "description": "Derpsmith hacks into the source code, and adds +1 free Golden Quark upgrade per day from Daily.", + "effect": "Code 'daily' gives +{{n}} free Golden Quark upgrades per use." }, "octeractImprovedDaily2": { "name": "CHONKERER Daily Code", - "description": "Derpsmith implemented hyperspeed multiplication. +1% more free Singularity upgrades per day from Daily!", - "effect": "Code 'daily' gives +{{n}}% more free Singularity upgrades per use." + "description": "Derpsmith implemented hyperspeed multiplication. +1% more free Golden Quark upgrades per day from Daily!", + "effect": "Code 'daily' gives +{{n}}% more free Golden Quark upgrades per use." }, "octeractImprovedDaily3": { "name": "CHONKEREREST Daily Code", - "description": "It will never satisfy Derpsmith. +1 +0.5% more free Singularity upgrades per day from Daily!", - "effect": "Code 'daily' gives +{{n}} more free Singularity upgrades per use." + "description": "It will never satisfy Derpsmith. +1 +0.5% more free Golden Quark upgrades per day from Daily!", + "effect": "Code 'daily' gives +{{n}} more free Golden Quark upgrades per use." }, "octeractImprovedQuarkHept": { "name": "I wish for even better Quark Hepteracts.", - "description": "The godmother is absent, but Derpsmith is here! +2% DR exponent per level. Stacks additively with all the others!", - "effect": "Quark Hepteract DR +{{n}}." + "description": "The godmother is absent, but Derpsmith is here! +0.02 exponent to your Quark Hepteract per level.", + "effect": "Quark Hepteract Exponent +{{n}}" }, "octeractImprovedGlobalSpeed": { - "name": "The forbidden clock of time", + "name": "The Forbidden Clock of Time", "description": "Hypothesized to be locked in a hyperbolic time chamber. +1% Global Speed per level per singularity!", "effect": "Global Speed per singularity +{{n}}%" }, @@ -3419,22 +4742,22 @@ }, "octeractImprovedFree": { "name": "Wow! I want free upgrades to be better.", - "description": "Upgrade level of Golden Quark and Octerct upgrades becomes (paid level * free levels)^0.6 instead of being added.", - "effect": "Singularity Upgrade free levels are {{n}} being powered!" + "description": "Makes the effective level of Golden Quark upgrades equal to <> instead of <>. But, if the second formula is greater then you still get that value!", + "effect": "Golden Quark upgrade free levels are {{n}} being powered!" }, "octeractImprovedFree2": { "name": "Wow! Free upgrades still suck.", - "description": "Who said beggars can't be choosers? Extends the exponent of the first upgrade to 0.65.", + "description": "Who said beggars can't be choosers? Increase the exponent of the first upgrade in this set by 0.05.", "effect": "Exponent of previous upgrade +{{n}}." }, "octeractImprovedFree3": { "name": "Wow! Make free upgrades good already, Platonic!", - "description": "Extends the exponent of the free upgrades to 0.70.", + "description": "Increase the exponent of the first upgrade in this set by 0.05", "effect": "Exponent of the first upgrade +{{n}}" }, "octeractImprovedFree4": { "name": "Coupon of Ultimate Penniless Derpsmiths", - "description": "Each level adds 0.001 to the exponent of free upgrades, with the first level adding another 0.01!", + "description": "Each level increases the exponent of the first upgrade in this set by 0.001, with the first level adding another 0.01.", "effect": "Exponent of the first upgrade +{{n}}" }, "octeractBonusTokens1": { @@ -3459,18 +4782,18 @@ }, "octeractSingUpgradeCap": { "name": "Overwriting Pointers", - "description": "Derpsmith encountered a SegFault after reassigning null... +1 to level cap on certain Singularity Upgrades per level!", - "effect": "Some Singularity Upgrades have +{{n}} max level!" + "description": "Derpsmith encountered a SegFault after reassigning null... +1 to level cap on certain Golden Quark Upgrades per level!", + "effect": "Some Golden Quark Upgrades have +{{n}} max level!" }, "octeractOfferings1": { "name": "Offering Electrolosis", - "description": "Gain 1% more offerings per level.", - "effect": "Offering gain +{{n}}%" + "description": "Gain +1% more Offerings per level.", + "effect": "Offering gain +{{n}}" }, "octeractObtainium1": { "name": "Obtainium Deluge", - "description": "Gain 1% more obtainium per level.", - "effect": "Obtainium gain +{{n}}%" + "description": "Gain +1% more Obtainium per level.", + "effect": "Obtainium gain +{{n}}" }, "octeractAscensions": { "name": "Voided Warranty", @@ -3485,7 +4808,7 @@ "octeractAscensionsOcteractGain": { "name": "Digital Octeract Accumulator", "description": "Octeract gain is 1% faster for every digit in your Ascension count!", - "effect": "Octeract Gain per OOM Ascension count +{{n}}%" + "effect": "Octeract Gain per order of magnitude Ascension count increase +{{n}}%" }, "octeractFastForward": { "name": "Derpsmith's Singularity Discombobulator", @@ -3509,43 +4832,43 @@ }, "octeractAmbrosiaLuck2": { "name": "Florentine Cranberries", - "description": "Totally not a reskin of the blueberry! Adds ☘ +2 Ambrosia Luck, which can increase Ambrosia drop amounts when Blueberries generate Ambrosia.", - "effect": "☘ Ambrosia Luck +{{n}}(!)" + "description": "Totally not a reskin of the blueberry! Adds +2 Ambrosia Luck, which can increase Ambrosia drop amounts when Blueberries generate Ambrosia.", + "effect": "+{{n}} Ambrosia Luck!" }, "octeractAmbrosiaLuck3": { "name": "Berries of a Four Leaf Clover", - "description": "Totally not a reskin of the blueberry! Adds ☘ +3 Ambrosia Luck per level.", - "effect": "☘ Ambrosia Luck +{{n}}(!)" + "description": "Totally not a reskin of the blueberry! Adds +3 Ambrosia Luck per level.", + "effect": "+{{n}} Ambrosia Luck!" }, "octeractAmbrosiaLuck": { "name": "Berries of an Eight Leaf Clover", - "description": "Totally not a reskin of the blueberry! Adds ☘ +4 Ambrosial Luck per level, and it is infinitely levelable!", - "effect": "☘ Ambrosia Luck +{{n}}(!)" + "description": "Totally not a reskin of the blueberry! Adds +4 Ambrosial Luck per level, and it is infinitely levelable!", + "effect": "+{{n}} Ambrosia Luck!" }, "octeractAmbrosiaLuck4": { "name": "Berries of a Ten Leaf Clover", - "description": "Totally not a reskin of the blueberry! Adds ☘ +5 Ambrosial Luck. And it's mega expensive somehow.", - "effect": "☘ Ambrosia Luck +{{n}}(!)" + "description": "Totally not a reskin of the blueberry! Adds +5 Ambrosial Luck. And it's mega expensive somehow.", + "effect": "+{{n}} Ambrosia Luck!" }, "octeractAmbrosiaGeneration2": { "name": "Hourglass of Minutiae", - "description": "A somewhat primative hourglass which somehow makes your blueberries generate seconds 1% faster per level..", - "effect": "Blueberry Seconds Generation +{{n}}%(!)" + "description": "A somewhat primative hourglass which somehow grants +1% faster Ambrosia Bar Point creation per level...", + "effect": "Ambrosia Bar Point creation is +{{n}}% faster!" }, "octeractAmbrosiaGeneration3": { "name": "Hourglass of Myopathy", - "description": "An hourglass which is still primative, though somehow less so. +1% Blueberry Second Generation per level.", - "effect": "Blueberry Seconds Generation +{{n}}%(!)" + "description": "An hourglass which is still primative, though somehow less so. +1% faster Ambrosia Bar Point creation per level", + "effect": "Ambrosia Bar Point creation is +{{n}}% faster!" }, "octeractAmbrosiaGeneration": { "name": "Hourglass of Infinitude", - "description": "Don't get it twisted- this hourglass is really primative. But it gives +1% Blueberry Second Generation per level and is infinitely levelable!", - "effect": "Blueberry Seconds Generation +{{n}}%(!)" + "description": "Don't get it twisted, this hourglass is really primative. But it gives +1% faster Ambrosia Bar Point creation per level and is infinitely levelable!", + "effect": "Ambrosia Bar Point creation is +{{n}}% faster!" }, "octeractAmbrosiaGeneration4": { "name": "Hourglass of Reality", - "description": "An hourglass which is nonprimative and 'more timely'- whatever that means. +2% Blueberry Second Generation per level.", - "effect": "Blueberry Seconds Generation +{{n}}%(!)" + "description": "An hourglass which is nonprimative and 'more timely'- whatever that means. +2% faster Ambrosia Bar Point creation per level.", + "effect": "Ambrosia Bar Point creation is +{{n}}% faster!" }, "octeractBlueberries": { "name": "Berries that are Blue!", @@ -3554,8 +4877,28 @@ }, "octeractInfiniteShopUpgrades": { "name": "Green Infinity Shop Voucher", - "description": "A Shop Voucher, printed on a green tablet. Each grants +1 effective level for the 'Infinity CO.' Quark Shop upgrades.", - "effect": "'Infinity CO.' shop upgrades level +{{n}}" + "description": "A Shop Voucher, printed on a green tablet. Each grants +1 effective level of the 'Infinity CO.' Quark Shop upgrades.", + "effect": "You own {{n}} Green Infinity Shop Vouchers!" + }, + "octeractTalismanLevelCap1": { + "name": "Talisman Level Cap Increaser Talisman", + "description": "You can't wear this Talisman, but it will give you an extra level to (most) Talisman level caps per level!", + "effect": "Talisman level cap is increased by {{n}}" + }, + "octeractTalismanLevelCap2": { + "name": "Talisman Level Cap Increaser Talisman - UNCOMMON Rarity", + "description": "By increasing the rarity of this unwearable Talisman, you gain an extra level to (most) Talisman level caps per level!", + "effect": "Talisman level cap is increased by {{n}}" + }, + "octeractTalismanLevelCap3": { + "name": "Talisman Level Cap Increaser Talisman - EPIC Rarity", + "description": "Gain another extra level to (most) Talisman level caps per level!", + "effect": "Talisman level cap is increased by {{n}}" + }, + "octeractTalismanLevelCap4": { + "name": "Talisman Level Cap Increaser Talisman - MYTHIC Rarity", + "description": "I think you can guess what the <> Signature of this Talisman would be...", + "effect": "Talisman level cap is increased by {{n}}" } }, "amount": "You have {{octeracts}} Octeracts!", @@ -3568,11 +4911,107 @@ "generatedObtainiumBonus": "Obtainium <>" }, "runes": { + "runeName": "<<{{color}}|{{name}}>> Rune", "offeringsYouHave": "You have {{offerings}} Offerings.", - "gainExp": "Gain {{amount}}x EXP per offering sacrificed.", - "recycleChance": "You have {{percent}}% chance of recycling your offerings. This multiplies EXP gain by {{mult}}!", + "gainExp": "Gain {{amount}}x EXP per Offering sacrificed.", + "recycleChance": "You have {{amount}} Salvage. The amount of Offerings needed to level up Runes is divided by {{mult}}!", + "recycleChanceDividedBy": "You have {{amount}} Salvage. The amount of Offerings needed to level up Runes is multiplied by {{div}}.", "hover": "Hey, hover over a rune icon to get details on what each one does and what benefits they're giving you!", - "thanksResearches": "Thanks to researches, your effective levels are increased by {{percent}}%", + "thanksResearches": "🜇 The effect of the first five Runes is increased by {{percent}}%", + "offeringInvested": "<{{amount}}>", + "runeEffectiveLevel": "Effective Rune Power: <>", + "runeEffectiveLevelCalc": "[(<> + <>) * <>]", + "speed": { + "name": "Speed", + "description": "An energy emanates from this stone. You feel compelled to inscribe power into it with your Offerings.", + "lockedDescription": "Erm. This should be unlocked. If you see this, please report it as a bug.", + "acceleratorPower": "<> (+0.02% per Power)", + "freeAccelerators": "<> (+0.25% per Power)", + "globalSpeed": "<> (Up to +100%)", + "values": "+0.02% Accelerator Power, +0.25% Free Accelerators per level. Global Speed (affecting resource production, and most reset timers) is multiplied by up to 2!", + "effect": "Speed Rune Bonus: +{{val}}% Accelerator Power, +{{val2}} Free Accelerators, +{{val3}} Global Speed." + }, + "duplication": { + "name": "Duplication", + "description": "\"Its ability lies in the world inside of the mirror...\" -some bizarre inscription", + "lockedDescription": "This rune is locked! To unlock it, Prestige 100 times. You even get an Achievement for it!", + "multiplierPower": "<> (+0.2 per Power)", + "freeMultipliers": "<> (+0.25% per Power)", + "taxReduction": "<> (Up to -99.9%)", + "values": "+0.2 Multiplier Power Boost (each gives +0.02 to Multiplier Power), +0.25% Free Multipliers per level. Tax growth is delayed more for each level (cap: 99.9%)!", + "effect": "Duplication Rune Bonus: +{{val}} Multiplier Power Boosts, +{{val2}} Free Multipliers, -{{val3}}% Tax Growth." + }, + "prism": { + "name": "Prism", + "description": "Has all colors of the visible light spectrum. As well as a few you cannot yet fathom.", + "lockedDescription": "This rune is locked! To unlock it, Transcend 1,000 times. You even get an Achievement for it!", + "crystalProduction": "<> (1 + (Power / 2)^2 * 2^(Power / 2) / 256)x", + "costDivisor": "<> (Divide by 10 every 10 Power)", + "values": "~(1 + (Level/2)^2 * 2^(Level/2) / 256)x Crystal Production. Every 10 Rune Power, divide the cost of all Crystal Upgrades by 10.", + "effect": "Prism Rune Bonus: All Crystal Producer production multiplied by {{val}}x, The costs of all Crystal Upgrades is divided by {{val2}}." + }, + "thrift": { + "name": "Thrift", + "description": "It seems with this Rune in tow, you keep making great deals on your purchases, and your Offerings are just a little more reusable.", + "lockedDescription": "This rune is locked! To unlock it, Reincarnate 10,000 times. You even get an Achievement for it!", + "costDelay": "<> (+0.125% per Power, max +1.00e15% at 8e15 Power)", + "salvage": "<> (+5.756 * log_10(1 + Power / 10))", + "taxReduction": "<> (Up to -99%)", + "values": "+0.125% building cost growth delay per level, more Salvage per level (log scaling), Tax Growth is delayed more for each level (cap: 99%)!", + "effect": "Thrift Rune Bonus: Delay all producer cost increases by {{val}}%, Salvage: +{{val2}}. -{{val3}}% Tax Growth" + }, + "superiorIntellect": { + "name": "Superior Intellect", + "description": "Over a hundred times the number of brain cells the average human has, put into one rock. It's probably sentient.", + "lockedDescription": "This rune is locked! Unlocking it might require some novel research.", + "offeringMult": "<> (+0.05% per Power)", + "obtainiumMult": "<> (+0.50% per Power)", + "antSpeed": "<> (1 + Power^2 / 2500)x", + "values": "(1 + level/2000)x Offerings, (1 + level/200)x Obtainium, (1 + Level^2/2500)x Ant Hatch Speed", + "effect": "S. Intellect Rune Bonus: Offerings x{{val}}, Obtainium x{{val2}}, Ant Speed x{{val3}}" + }, + "infiniteAscent": { + "name": "Infinite Ascent", + "description": "A staircase you can only climb facing backwards, how weird is that?", + "lockedDescription": "This rune is locked! You may need some Quarks to unlock this one. Or, head to the <> tab to unlock it instantly!", + "quarkBonus": "<> (+0.2% per Power)", + "cubeBonus": "<> (+1% per Power)", + "salvage": "BONUS! <> (+{{val2}} per Power. see Infinite Recycling)", + "values": "+0.2% Quarks, +1% all Cube types per level! Start with +10% Quarks.", + "effect": "IA Rune Bonus: Quark Gain +{{val}}, Ascensions give +{{val2}} more Cubes.", + "bonusEffect": "PLUS, Salvage +{{val3}} (Infinite Recycling bonus)" + }, + "antiquities": { + "name": "Antiquities of Ant God", + "description": "The piece of eight, long thought lost, no longer. It went there and back again.", + "lockedDescription": "This rune is locked! To unlock it, you might need to buy something that should be left unbought.", + "singularityUnlock": "Something singular that's unlocked at Level 1: {{symbol}}", + "offeringBonus": "<> (x10 per Power)", + "obtainiumBonus": "<> (x10 per Power)", + "addCode": "<> (-20% at 1 Power, up to -50%)", + "values": "Level 1 unlocks something special! Each level gives 10x Offerings, 10x <> Obtainium and makes code 'Add' regenerate faster.", + "effect": "Offerings x{{val}}, <> Obtainium x{{val2}}, 'Add' code regeneration interval is {{val3}}% as long." + }, + "horseShoe": { + "name": "The Horse's Shoe", + "description": "I never realized the world was so easily won, just by realizing you got someone to say you won't be lonely anymore.", + "lockedDescription": "This rune is locked! To unlock it, you might need to tussle with the <> himself.", + "ambrosiaLuck": "<> (+1 per Power)", + "redLuck": "<> (+0.2 per Power)", + "luckConversion": "<> (Up to -0.5)", + "values": "Each level gives you +1 Ambrosia Luck, +0.2 Red Luck. You also reduce the Luck Conversion Ratio between the two by up to 0.5, depending on level (-0.5 * level / (level + 50))", + "effect": "Horseshoe Rune Bonus: +{{val}} Ambrosia Luck, +{{val2}} Red Luck, {{val3}} Luck Conversion Ratio." + }, + "finiteDescent": { + "name": "Finite Descent", + "description": "That shopkeep told me it was an Ascent. That liar!", + "lockedDescription": "This rune is locked! Honestly, I have no idea how you unlock this.", + "corruptionLevels": "<> (up to +0.15)", + "ascensionScore": "<> (up to +100%)", + "infiniteAscentLevels": "<> (+1 every 2 Power)", + "values": "Gain up to 2x more Ascension Score for your current Ascension, increase your free Corruption levels up to 0.15, and tack on +1 free Infinite Ascent level every two levels. Shweet!", + "effect": "This Ascension, gain +{{val}} Ascension Score, {{val2}} free Corruption levels, and +{{val3}} Infinite Ascent levels." + }, "levelup": { "1": "+(Level/4)^1.25 Accelerator, +0.25% Accelerators per level. +1 Accelerator Boost every 20 levels!", "2": "+(Level/10) Multipliers every 10th level, +0.25% Multipliers per level. Tax growth is delayed more for each level!", @@ -3592,9 +5031,9 @@ "7": "You cannot grasp the true form of Ant God's treasure." }, "talismans": { - "fortify": "FORTIFY", - "enhance": "ENHANCE", - "respec": "RESPEC", + "fortify": "x1", + "enhance": "To Rarity", + "respec": "MAX", "respecAll": "RESPEC ALL", "respecConfirm": "Confirm [-100,000 Offerings]", "respecConfirmAll": "Confirm All [-400,000 Offerings]", @@ -3606,10 +5045,90 @@ "autoBuyOff": "Auto Buy: OFF", "clickBuyEveryType": "Click to buy every type of Talisman Shards and Fragments, if affordable", "costToBuy": "Cost to buy {{name}} ({{buyAmount}}): {{obtainium}} Obtainium and {{offerings}} Offerings.", + "doesNotReset": "[<>] This talisman NEVER resets! Not even on Singularity.", + "rarity": { + "1": "<>", + "2": "<>", + "3": "<>", + "4": "<>", + "5": "<>", + "6": "<>", + "7": "<>", + "8": "<>", + "9": "<>", + "10": "IMMACULATE!" + }, "modifiers": { "positive": "{{name}}: Positive", "negative": "{{name}}: Negative" }, + "level": "{{x}}/{{y}}", + "exemption": { + "name": "The Exemption Talisman", + "description": "Fake Science Monthly says wearing this trinket lowers your taxes!", + "inscription": "<> Inscription: Your taxes are multiplied by <>.", + "signature": "<> Signature: Add <> to the Rune Level Coefficient of the Duplication Rune." + }, + "chronos": { + "name": "Talisman of Chronology", + "description": "Time flows just a little faster when worn, you read on the tag.", + "inscription": "<> Inscription: Global Speed is increased by <>.", + "signature": "<> Signature: Add <> to the Rune Level Coefficient of the Speed Rune." + }, + "midas": { + "name": "Midas Necklace", + "description": "If Midas turned all he touched to gold...", + "inscription": "<> Inscription: Rune Blessing bonuses are increased by <>.", + "signature": "<> Signature: Add <> to the Rune Level Coefficient of Thrift Rune." + }, + "metaphysics": { + "name": "Metaphysical Gem", + "description": "Do you wear this, or does this wear you?", + "inscription": "<> Inscription: This Talismans' Bonus Rune Levels are increased by <>.", + "signature": "<> Signature: This Talismans' Bonus Rune Levels are further multiplied by <>." + }, + "polymath": { + "name": "Trinket of the Polymath", + "description": "Do you feel like an exiled trickster?", + "inscription": "<> Inscription: Ascension Speed is increased by <>.", + "signature": "<> Signature: Add <> to the Rune level Coefficient of Superior Intellect Rune." + }, + "mortuus": { + "name": "Preserved Ant Talisman", + "description": "It's said that great sacrifice was made for this Talisman to exist.", + "inscription": "<> Inscription: Ant ELO is increased by <>.", + "signature": "<> Signature: Add <> to the Rune Level Coefficient of Prism Rune." + }, + "plastic": { + "name": "A hunk of Plastic", + "description": "Probably purchased on a buy now, pay later app.", + "inscription": "<> Inscription: Quark Gain is increased by <>.", + "signature": "<> Signature: None. You cannot seem to sign your name on this hunk of plastic." + }, + "wowSquare": { + "name": "The Wow! Square", + "description": "For the average :unsmith:, this is a cube.", + "inscription": "<> Inscription: Even-Dimensional Cube Gain is increased by <>", + "signature": "<> Signature: Odd-Dimensional Cube Gain is increased by <>" + }, + "achievement": { + "name": "Medal of Achievement", + "description": "Has serial number {{num}} on it.", + "inscription": "<> Inscription: Sources of Positive Salvage give <> more!", + "signature": "<> Signature: Sources of Negative Salvage effect <>." + }, + "cookieGrandma": { + "name": "Freshly-Baked Cookie", + "description": "Kinda burns your neck. Thanks Grandma!", + "inscription": "<> Inscription: Gain <> free Corruption Levels!", + "signature": "<> Signature: Turns out, you cannot write on cookies." + }, + "horseShoe": { + "name": "The Horse's Boot", + "description": "It's an actual horse shoe!", + "inscription": "<> Inscription: Gain <> Ambrosia Luck.", + "signature": "<> Signature: Gain <> Red Luck." + }, "shards": { "shard": "Talisman Shard", "commonFragment": "Common Fragment", @@ -3628,6 +5147,23 @@ "mortuus": "Mortuus Est", "plastic": "Plastic" }, + "rarityInfo": { + "title": "⋆ Rarity Requirements ⋆
{{talismanName}}", + "infoText1": "Higher Talisman Rarities improve the power of its <> Inscription and give more Free Rune Levels!", + "infoText2": "When your Talisman is Mythic or better, it gains a <> Signature!", + "common": "Common", + "uncommon": "Uncommon", + "rare": "Rare", + "epic": "Epic", + "legendary": "Legendary", + "mythic": "Mythic", + "extraordinary": "Extraordinary", + "godlike": "Godlike", + "perfect": "Perfect", + "immaculate": "Immaculate", + "default": "Default!", + "levelReq": "≥ Level {{level}}" + }, "summaries": { "exemption": "=-=-=-= Exemption Talisman Effects =-=-=-=", "chronos": "=-=-=-= Chronos Talisman Effects =-=-=-=", @@ -3647,11 +5183,14 @@ "plastic": "Gain 1x normal production (Joke)!" }, "bonusRuneLevels": { - "speed": "Bonus Speed Rune Levels: {{x}}", - "duplication": "Bonus Duplication Rune Levels: {{x}}", - "prism": "Bonus Prism Rune Levels: {{x}}", - "thrift": "Bonus Thrift Rune Levels: {{x}}", - "SI": "Bonus SI Rune Levels: {{x}}" + "speed": "Bonus Speed Rune Levels: <>", + "duplication": "Bonus Duplication Rune Levels: <>", + "prism": "Bonus Prism Rune Levels: <>", + "thrift": "Bonus Thrift Rune Levels: <>", + "superiorIntellect": "Bonus Superior Intellect Rune Levels: <>", + "infiniteAscent": "Bonus Infinite Ascent Rune Levels: <>", + "antiquities": "Bonus Antiquities of Ant God Rune Levels: <>", + "horseShoe": "Bonus Horse Shoe levels: <>" }, "mythicEffects": { "exemption": "Mythic Effect: +400 Duplication Rune Levels!", @@ -3665,34 +5204,97 @@ "maxEnhance": "Get Max Enhance for a Mythical bonus effect!" }, "blessings": { + "blessingName": "<<{{color}}|{{name}}>> Rune Blessing", "buyUpTo": "Buy up to <> Levels per purchase, if affordable. [Input to toggle]", "autoRuneOn": "Auto Rune: ON", "autoRuneOff": "Auto Rune: OFF", "allIncreaseLevel": "ALL Increase Level", - "blessingLevel": "<>: {{amount}}", - "blessingPower": "Blessing Power: <> =-=-= Effect: <>x {{reward}}!", + "blessingLevel": "Blessing level <>", + "blessingPower": "Blessing Power: <> =-=-= Effect: {{desc}}", "rewards": { - "0": "Global Speed", - "1": "Multiplier Boosts", - "2": "Ant Sacrifice Rewards", - "3": "Delay for A.Boost Cost", - "4": "Obtainium --> Ant Mult." + "speed": "{{effect}}x Global Speed", + "duplication": "{{effect}}x Multiplier Boosts", + "prism": "{{effect}}x Ant Sacrifice Rewards", + "thrift": "{{effect}}x Accelerator Boost Cost Delays", + "superiorIntellect": "Ant Speed multiplied by Obt^{{effect}} || Ant Speed x{{effect2}}" + }, + "effectiveBlessingPower": "Effective Blessing Power: <>", + "baseBlessingPower": "{{level}} <>", + "runePowerMult": "* {{level}} <>", + "otherPowerMult": "* {{mult}} (Other Bonuses)", + "runeCoefficientText": "Rune Coefficient: {{val}}", + "blessingEXP": "Blessing EXP: {{exp}} <>", + "speed": { + "description": "A few times, it's been around this track.", + "globalSpeed": "<> (+1% per 10,000 Blessing Power)" + }, + "duplication": { + "description": "I control the inside of the mirror... that's my ability.", + "multiplierBoosts": "<> (+1% per 10,000 Blessing Power)" + }, + "prism": { + "description": "If I get with the ultraviolet dream...", + "antSacrifice": "<> (+1% per 10,000 Blessing Power)" + }, + "thrift": { + "description": "\"Who wishes to fight must first count the cost.\" - Sun Tzu, The Art of War", + "acceleratorBoostDelay": "<> (+0.01% per 10,000 Blessing Power)" + }, + "superiorIntellect": { + "description": "-- ANT LEGION -- ANT LEGION -- ANT LEGION --", + "obtExponent": "<> (Exponent = ln(1 + Blessing Power / 1e6))", + "antSpeed": "↳<>" }, "increaseLevel": "Increase Level By <> [Cost: <> Offerings]" }, "spirits": { - "spiritLevel": "Spirit level: <>", - "spiritPower": "Spirit Power: <> =-=-= Effect: <>{{reward}}!", + "spiritName": "<<{{color}}|{{name}}>> Rune Spirit", + "spiritLevel": "Spirit level <>", + "spiritPower": "Spirit Power: <> =-=-= Effect: {{desc}}", + "effectiveSpiritPower": "Effective Spirit Power: <>", + "baseSpiritPower": "{{level}} <>", + "runePowerMult": "* {{level}} <>", + "blessingPowerMult": "* {{level}} <>", + "otherPowerMult": "* {{mult}} (Other Bonuses)", + "runeCoefficientText": "Rune Coefficient: {{val}}", + "spiritEXP": "Spirit EXP: {{exp}} <>", "rewards": { - "0": "x Global Speed", - "1": "x Wow! Cubes by Ascension", - "2": "x Additional levels to Crystal caps", - "3": "x Obtainium", - "4": "% stronger Research 4x9 effect" + "speed": "{{effect}}x Global Speed", + "duplication": "{{effect}}x Wow! Cubes by Ascension", + "prism": "{{effect}}x Additional levels to Crystal caps", + "thrift": "{{effect}}x Offerings", + "superiorIntellect": "{{effect}}x Obtainium" + }, + "speed": { + "description": "Roger Bannister et al. wore this shoe.", + "globalSpeed": "<> (+1% per 10,000,000 Spirit Power)" + }, + "duplication": { + "description": "A male contained within a reflective surface, quite illusive.", + "wowCubes": "<> (+1% per 10,000,000 Spirit Power)" + }, + "prism": { + "description": "Hide from the red light beam.", + "crystalCaps": "<> (+1% per 10,000,000 Spirit Power)" + }, + "thrift": { + "description": "Conjure the ghost of Synergism's Past!", + "offerings": "<> (+1% per 10,000,000 Spirit Power)" + }, + "superiorIntellect": { + "description": "I too have a theoretical degree in Physics.", + "obtainium": "<> (+1% per 10,000,000 Spirit Power)" }, - "buyUpTo": "Buy up to <> Levels per purchase, if affordable. [Input to toggle]" + "buyUpTo": "Buy up to <> Levels per purchase, if affordable. [Input to toggle]", + "increaseLevel": "Increase Level By <> [Cost: <> Offerings]" }, + "lockedRune": "LOCKED", + "lockedRuneModal": "??? - LOCKED", "perOfferingText": "+{{exp}} EXP per offering, {{x}} [{{y}}] Offerings to level up once.", + "runeCoefficientText": "Rune Coefficient: {{total}} ({{base}} + {{bonus}})", + "perLevelIncrease": "Each additional level costs <> as many Offerings.", + "runeEXP": "Rune EXP: {{exp}} <>", + "EXPNeeded": "Level +{{level}} at {{exp}} EXP <>", "runeEffectSummaryTitle": "=====RUNE EFFECT SUMMARY=====", "bonusAmount": "[Bonus: {{x}}]", "bonusNope": "[Bonus: Nope!]", @@ -3711,8 +5313,11 @@ "IA": "Infinite Ascent", "AOAG": "Antiquities of Ant God" }, + "level": "Level {{x}}", + "freeLevels": "[<>]", + "offeringText": "+{{exp}} EXP per Offering - {{offeringReq}} Offerings for +{{levels}}.", "maxLevel": "Maxed Level!", - "TNL": "+1 in {{EXP}} EXP" + "TNL": "+{{levelAmount}} in <>" }, "singularityChallenge": { "enterChallenge": { @@ -3736,13 +5341,13 @@ }, "data": { "noSingularityUpgrades": { - "name": "No Singularity Upgrades", - "description": "Simply put, you have to beat the target singularity without (most) Singularity Upgrades.
Octeracts, Perks and Quality of Life Singularity Upgrades are preserved.", + "name": "No Golden Quark Upgrades", + "description": "Simply put, you have to beat the target Singularity without (most) Golden Quark Upgrades.
Octeracts, Perks and Quality of Life Singularity Upgrades are preserved.", "ScalingReward1": "+50% increase to <> gain", "UniqueReward1": "1st - +12% <>!", "UniqueReward2": "1st - A free <>!", "UniqueReward3": "20th - A new <> upgrade!", - "UniqueReward4": "30th - +5% <> for free!", + "UniqueReward4": "30th - +5% <> for free!", "UniqueReward5": "30th - A new <> upgrade for NOT FREE!" }, "oneChallengeCap": { @@ -3774,9 +5379,9 @@ "noAmbrosiaUpgrades": { "name": "No Ambrosia Upgrades", "description": "Gold Guy has lost his muster (and yes, that's his name!) and needs your help.
Clear the Singularity without any effects from Ambrosia or Ambrosia-Tier Upgrades!", - "ScalingReward1": "+15 & +0.5% <>", + "ScalingReward1": "+15 & +0.5% <>", "ScalingReward2": "+2% <> gain", - "ScalingReward3": "+4 <>", + "ScalingReward3": "+4 <>", "ScalingReward4": "+1% <> gain", "UniqueReward1": "1st - A free <>!", "UniqueReward2": "1st - +1 <> every time <> is gained!", @@ -3806,6 +5411,24 @@ "UniqueReward2": "10th - A first-tier <>", "UniqueReward3": "20th - A second-tier <>", "UniqueReward4": "30th - A particularly Sadistic <>!" + }, + "taxmanLastStand": { + "name": "The Taxman's Last Stand", + "description": "The Tax Man, wanting the Ultimate Pen, has some devious new tricks up his sleeve. Beat the target Singularity, except:", + "salvageMod": "<>", + "offeringMod": "<>", + "obtainiumMod": "<>", + "taxMod": "¢ Gain <> the taxes when you've beaten Challenge 10, and <> more taxes when you've beaten Challenge 14", + "capMod": "<>", + "tributeMod": "<>", + "omegaMod": "<>", + "ScalingReward1": "+25 <> for any Talisman that resets on Singularity", + "ScalingReward2": "+3% <>", + "ScalingReward3": "+0.002 <> for Antiquities!", + "ScalingReward4": "+0.005 <> for... <>...", + "UniqueReward1": "1st - Unlock <>", + "UniqueReward2": "5th - Unlock <>", + "UniqueReward3": "10th - Unlock <>" } }, "intro": "Do you have what it takes to earn EXALT from Ant God?", @@ -3861,6 +5484,14 @@ "activeUntil": "ACTIVE UNTIL: {{x}}", "starts": "STARTS {{x}}" }, + "cloud": { + "upload": "Upload Save", + "uploadSuccess": "✅ Uploaded Your Save", + "uploadFailed": "❌ Failed to Upload", + "transfer": "Transfer Save from Old System", + "transferSuccess": "✅ Transferred Your Save", + "transferFailed": "❌ Failed to Transfer" + }, "quarkBonusSimple": "Generous patrons give you a bonus of {{globalBonus}}% more Quarks!", "quarkBonusExtended": "Generous patrons give you a bonus of {{globalBonus}}% more Quarks! You also receive an extra {{personalBonus}}% bonus for being a Patreon member and/or boosting the Discord server! Multiplicative with global bonus!" }, @@ -3869,13 +5500,16 @@ "misc": "Miscellaneous Stats", "baseOffering": "Base Offerings", "offeringMult": "Offering Multipliers", + "salvage": "Salvage", "baseObtainium": "Base Obtainium", + "runeEffectMult": "Rune Power", "obtIgnoreDR": "Immaculate Obtainium", "obtMult": "Obtainium Multipliers", "quarkMult": "Global Quark Multipliers", "gSpeedMultIgnoreDR": "Immaculate Global Speed", "gSpeedMult": "Global Speed Multipliers", "antSacrificeMult": "Ant Sacrifice Multipliers", + "talismanRuneBonusMult": "Talisman Power", "ascMult": "Asc. Speed Multipliers", "globalCubeMult": "Global Cube Multipliers", "cubeMult": "Cube Multipliers", @@ -3893,8 +5527,8 @@ "ambrosiaBlueberries": "Blueberry Inventory", "ambrosiaGenMult": "Ambrosia Bar Points", "luckConversion": "Luck Conversion", - "redAmbrosiaLuck": "Red Ambrosia Luck", - "redAmbrosiaGenMult": "Red Ambrosia Bar Points", + "redAmbrosiaLuck": "Red Luck", + "redAmbrosiaGenMult": "Red Bar Points", "shopVouchers": "Shop Vouchers", "generate": "Generate Savefile Stats Summary" }, @@ -3903,7 +5537,7 @@ "PseudoCoins": "PseudoCoin Upgrade:", "AscensionTime": "Ascension Time Multiplier:", "CampaignTutorial": "Campaign - Tutorial Bonus:", - "Campaign": "Campaign - Cube Bonus", + "Campaign": "Campaign - Cube Bonus:", "SunMoon": "Sun & Moon Achievement Multiplier:", "SpeedAchievement": "Speed Achievement:", "Challenge15": "Challenge 15 - All Cube Bonus I-V:", @@ -3945,9 +5579,12 @@ }, "cubeMultiplierStats": { "title": "Wow! Cube Multiplier", + "AchievementBonus": "Achievement Wow! Cubes Bonus:", + "SynergismLevel": "Synergism Level Reward:", "AscensionScore": "Ascension Score Multiplier:", "GlobalCube": "Global Cube Multiplier:", "SeasonPass1": "Season Pass 1:", + "WowSquare": "Wow Square - Signature Bonus:", "Researches": "Researches (Except 8x25):", "Research8x25": "Research 8x25:", "CubeUpgrades": "Cube Upgrades:", @@ -3965,9 +5602,12 @@ }, "tesseractMultiplierStats": { "title": "Wow! Tesseract Multiplier", + "AchievementBonus": "Achievement Wow! Tesseracts Bonus:", + "SynergismLevel": "Synergism Level Reward:", "AscensionScore": "Ascension Score Multiplier:", "GlobalCube": "Global Cube Multiplier:", "SeasonPass1": "Season Pass 1:", + "WowSquare": "Wow Square - Inscription Bonus:", "ConstantUpgrade10": "Constant Upgrade 10:", "CubeUpgrade3x10": "Cube Upgrade 3x10:", "CubeUpgrade4x8": "Cube Upgrade 4x8:", @@ -3981,9 +5621,12 @@ }, "hypercubeMultiplierStats": { "title": "Wow! Hypercube Multiplier", + "AchievementBonus": "Achievement Wow! Hypercubes Bonus:", + "SynergismLevel": "Synergism Level Reward:", "AscensionScore": "Ascension Score Multiplier:", "GlobalCube": "Global Cube Multiplier:", "SeasonPass2": "Season Pass 2:", + "WowSquare": "Wow Square - Signature Bonus:", "Achievement212-215": "Achievement 212-215 Bonus:", "Achievement216": "Achievement 216 Bonus:", "Achievement253": "Achievement 253 Bonus:", @@ -3996,9 +5639,12 @@ }, "platonicMultiplierStats": { "title": "Wow! Platonic Cube Multiplier", + "AchievementBonus": "Achievement Platonic Cubes Bonus:", + "SynergismLevel": "Synergism Level Reward:", "AscensionScore": "Ascension Score Multiplier:", "GlobalCube": "Global Cube Multiplier:", "SeasonPass2": "Season Pass 2:", + "WowSquare": "Wow Square - Inscription Bonus:", "Achievement196": "Achievement 196 Bonus:", "Achievement219-222": "Achievement 219-222 Bonus:", "Achievement223": "Achievement 223 Bonus:", @@ -4009,9 +5655,12 @@ }, "hepteractMultiplierStats": { "title": "Wow! Hepteract Cube Multiplier", + "AchievementBonus": "Achievement Hepteracts Bonus:", + "SynergismLevel": "Synergism Level Reward:", "AscensionScore": "Ascension Score Multiplier:", "GlobalCube": "Global Cube Multiplier:", "SeasonPass3": "Season Pass 3:", + "WowSquare": "Wow Square - Signature Bonus:", "Achievement258": "Achievement 258 Bonus:", "Achievement264": "Achievement 264 Bonus:", "Achievement265": "Achievement 265 Bonus:", @@ -4020,6 +5669,7 @@ }, "octeractMultiplierStats": { "title": "Wow! Octeract Cube Multiplier", + "SynergismLevel": "Synergism Level Reward:", "BasePerSecond": "Base Per Second:", "AscensionScore": "Ascension Score Multiplier:", "PseudoCoins": "PseudoCoin Upgrade:", @@ -4028,6 +5678,7 @@ "SeasonPassY": "Season Pass Y:", "SeasonPassZ": "Season Pass Z:", "SeasonPassLost": "Season Pass Lost:", + "WowSquare": "Wow Square - Inscription Bonus:", "CookieUpgrade20": "Cookie Upgrade 20:", "DivinePack": "Divine Pack:", "SingCubes1": "Cube Flame:", @@ -4046,7 +5697,7 @@ "DigitalOcteractAccumulator": "Digital Octeract Accumulator:", "Event": "Event Buff:", "PlatonicDelta": "Platonic DELTA:", - "NoSingUpgrades": "No Singularity Upgrades Challenge:", + "NoSingUpgrades": "No Golden Quark Upgrades Challenge:", "PassINF": "Wow Pass ∞:", "Ambrosia": "Unspent Ambrosia Bonus:", "ModuleTutorial": "Module- Tutorial:", @@ -4084,6 +5735,8 @@ "offeringMultiplierStats": { "title": "Offering Multiplier", "Base": "Base Offerings:", + "AchievementBonus": "Achievement Offering Multiplier:", + "SynergismLevel": "Synergism Level Reward:", "PrestigeShards": "Prestige Shards Multiplier:", "SuperiorIntellect": "Superior Intellect Rune Bonus:", "ReincarnationChallenge": "Reincarnation Challenges 6, 8, 10:", @@ -4105,6 +5758,7 @@ "TutorialBonus": "Campaign - Tutorial Bonus:", "CampaignBonus": "Campaign - Offering Bonus:", "Challenge12": "Challenge 12:", + "ThriftSpirit": "Thrift Spirit Bonus:", "Research8x25": "Research 8x25:", "AscensionAchievement": "Ascension Count Achievement:", "SunMoonAchievements": "Sun & Moon Achievements:", @@ -4114,6 +5768,7 @@ "PlatonicBETA": "Platonic BETA:", "PlatonicOMEGA": "Platonic OMEGA:", "Challenge15": "Challenge 15:", + "Antiquities": "Antiquities of the Ant God:", "SingularityDebuff": "Singularity Debuff:", "StarterPack": "Singularity Upgrade - Starter Pack:", "OfferingCharge": "Singularity Upgrade - Offering Charge:", @@ -4135,13 +5790,37 @@ "RedAmbrosia": "Offerings \"\"\"made\"\"\" of Red Ambrosia", "Event": "Event Bonus:", "Exalt6Penalty": "The Grand Singularity Speedrun - Too Slow:", + "TaxmanDebuff": "Taxman's Last Stand - ☢ Radioactive Debuff:", "Total": "Offering Multiplier:", "HalfMind": "HALF MIND Modifier:", "Total2": "Final Offerings Gained:" }, + "runeEffectFirstFive": { + "title": "First Five Runes - Rune Power Multiplier", + "Research1x4": "Research 1x4:", + "Research2x6": "Research 2x6:", + "Research4x15": "Research 4x15:", + "Research6x6": "Research 6x6:", + "Research6x21": "Research 6x21:", + "Research7x11": "Research 7x11:", + "Research8x1": "Research 8x1:", + "Research8x16": "Research 8x16:", + "ConstantUpgrade9": "Constant Upgrade 9:", + "Challenge15": "Challenge 15:", + "MidasTribute": "Midas Tribute:", + "Total": "Total Multiplier for first five Runes" + }, + "runeEffectSI": { + "title": "Superior Intellect - Specific Rune Power Multiplier", + "Research4x9": "Research 4x9:", + "Total": "Total SI Rune Power Multiplier" + }, "globalQuarkMultiplierStats": { "title": "Quark Multiplier", "AchievementPoints": "Achievement Points Bonus:", + "AchievementBonus": "Achievement Quarks Multiplier:", + "SynergismLevel": "Synergism Level Reward:", + "PlasticTalisman": "Plastic Talisman - Inscription Bonus:", "Achievement250": "Achievement 250 Bonus:", "Achievement251": "Achievement 251 Bonus:", "Achievement266": "Achievement 266 Bonus:", @@ -4156,7 +5835,8 @@ "SingularityCount": "Singularity Count Bonus:", "CookieUpgrade3": "Cookie Upgrade 3 Bonus:", "CookieUpgrade18": "Cookie Upgrade 18 Bonus:", - "SingularityMilestones": "Singularity Milestones Bonus:", + "SingularityMilestones": "Singularity Perk - Even More Quarks:", + "skrauQ": "Singularity Perk - skrauQ:", "OcteractQuarkBonus": "Octeract Passive Bonus:", "OcteractStarter": "Octeract Starter Pack Bonus:", "OcteractQuarkGain": "Octeract Quark Gain Upgrade:", @@ -4205,6 +5885,7 @@ "PlatonicUpgrade9": "Platonic Upgrade 9:", "PlatonicBETA": "Platonic BETA:", "PlatonicOMEGA": "Platonic OMEGA:", + "Antiquities": "Antiquities of the Ant God:", "CubeUpgradeCx5": "Cube Upgrade 6x5 (Cx5):", "CubeUpgradeCx12": "Cube Upgrade 7x2 (Cx12):", "CubeUpgradeCx21": "Cube Upgrade 8x1 (Cx21):", @@ -4218,6 +5899,8 @@ "obtainiumMultiplierStats": { "title": "Obtainium Multiplier", "Base": "Base Obtainium:", + "AchievementBonus": "Achievement Obtainium Multiplier:", + "SynergismLevel": "Synergism Level Reward:", "ThresholdPenalty": "Reincarnation Time Penalty:", "TimeMultiplier": "Reincarnation Time Multiplier:", "TranscendShards": "Transcend Shards Bonus:", @@ -4243,7 +5926,7 @@ "CubeUpgrade1x3": "Cube Upgrade 1x3:", "CubeUpgrade4x7": "Cube Upgrade 4x7:", "Challenge12": "Challenge 12:", - "SpiritPower": "Fourth Spirit Bonus:", + "SpiritPower": "Superior Intellect Spirit:", "Research6x19": "Research 6x19:", "PlatonicALPHA": "Platonic ALPHA:", "PlatonicUpgrade9": "Platonic Upgrade 9:", @@ -4269,6 +5952,7 @@ "Challenge14": "Challenge 14 - No Obtainium:", "SingularityDebuff": "Singularity Debuff:", "Exalt6Penalty": "The Grand Singularity Speedrun - Too Slow:", + "TaxmanDebuff": "Taxman's Last Stand - ☼ A Billion Suns Debuff:", "Event": "Event Bonus:", "Total": "Obtainium Multiplier:", "ObtainiumDR": "Illiteracy Power:", @@ -4278,6 +5962,7 @@ }, "antSacrificeMultStats": { "title": "Ant Sacrifice Reward Multiplier", + "AchievementBonus": "Achievement Ant Sacrifice Multiplier:", "AntUpgrade11": "Ant Upgrade 11:", "Research103": "Research 5x3:", "Research104": "Research 5x4:", @@ -4308,6 +5993,7 @@ }, "globalSpeedMultiplierStats": { "title": "Global Speed Multiplier", + "SpeedRune": "Speed Rune Bonus:", "ObtainiumLog": "Obtainium Log Bonus:", "Research5x21": "Research 5x21:", "Research6x11": "Research 6x11:", @@ -4332,6 +6018,8 @@ }, "ascensionSpeedMultiplierStats": { "title": "Ascension Speed Multiplier", + "SpeedSpirit": "Speed Spirit Bonus:", + "PolymathTalisman": "Polymath Talisman - Inscription Bonus:", "Chronometer": "Chronometer Shop Upgrade:", "Chronometer2": "Chronometer 2 Shop Upgrade:", "Chronometer3": "Chronometer 3 Shop Upgrade:", @@ -4361,17 +6049,20 @@ "ambrosiaAdditiveLuckMultStats": { "title": "Ambrosia Additive Luck Multiplier", "Base": "Base Multiplier:", - "NoSingularityUpgrades": "No Singularity Upgrades Challenge:", + "NoSingularityUpgrades": "No Golden Quark Upgrades Challenge:", "DilatedFiveLeaf": "Dilated Five Leaf Clover:", "ShopUpgrade": "EXALT Ambrosia Luck Multiplier:", "NoAmbrosiaUpgrades": "No Ambrosia Upgrades Challenge:", "Cookie5": "Cookie Upgrade 5 (Cx27):", + "BlueberryUpgrade": "Blueberry Module - Ambrosia Luck IV:", + "HorseShoeTalisman": "Horse Shoe Talisman Inscription Bonus:", "Event": "Event Bonus:", "Total": "Total Additive Luck Multiplier:" }, "ambrosiaLuckStats": { "title": "Ambrosia Luck", "Base": "Base Value:", + "SynergismLevel": "Synergism Level Reward:", "PseudoCoins": "PseudoCoin Upgrade:", "Campaign": "Campaign Bonus:", "SingularityMilestones": "Singularity Milestones:", @@ -4394,12 +6085,13 @@ "AmbrosiaUltra": "Ambrosia Ultra Shop Upgrade:", "Viscount": "Benefits of being a Viscount:", "AdditiveLuckMult": "Additive Luck Multiplier:", + "HorseShoeRune": "Horse Shoe Rune Bonus:", "Total": "Ambrosia Luck:", "Total2": "Total Ambrosia Luck" }, "ambrosiaBlueberryStats": { "title": "Ambrosia Blueberry Inventory", - "E1x1Clear": "No Singularity Upgrades Challenge:", + "E1x1Clear": "No Golden Quark Upgrades Challenge:", "SingBlueberries": "Singularity Blueberry Upgrade:", "ConglomerateBerries": "Congealed Blueberries (Singularity Milestones):", "NoAmbrosiaUpgrades": "No Ambrosia Upgrades Challenge Reward:", @@ -4429,6 +6121,7 @@ "powderMultiplierStats": { "title": "Orb to Powder Conversion", "Base": "Base Conversion Rate:", + "AchievementBonus": "Achievement Power Conversion Bonus:", "Challenge15": "Challenge 15 Reward:", "ShopPowderEX": "Powder EX Shop Upgrade:", "Achievement256": "Achievement 256 Bonus:", @@ -4445,8 +6138,8 @@ "Challenge15": "Challenge 15 Exponent:", "GoldenQuarks1": "Golden Quarks I Upgrade:", "CookieUpgrade19": "Cookie Upgrade 19:", - "NoSingularityUpgrades": "No Singularity Upgrades Challenge:", - "GoldenRevolution2": "Golden Revolution II:", + "NoSingularityUpgrades": "No Golden Quark Upgrades Challenge:", + "GoldenRevolution2": "Golden Revolution I:", "FastForwards": "Singularity Fast Forwards:", "ImmaculateAlchemy": "Immaculate Alchemy:", "Event": "Event Bonus:", @@ -4481,7 +6174,7 @@ "BaseTimer": "Base Timer (ms):", "Calculator4": "PL-AT δ Discount:", "SingularityCount": "Singularity Milestone Bonus:", - "InfiniteAscent": "Infinite Ascent Bonus:", + "Antiquities": "Antiquities of Ant God Bonus:", "SingularityPerkBonus": "Singularity Perk Bonus:", "Total": "Final Add Code Interval (ms):" }, @@ -4527,11 +6220,13 @@ "ShopRedLuck1": "Low Class Dice of Asmodeus:", "ShopRedLuck2": "Dice of Asmodeus:", "ShopRedLuck3": "High Class Dice of Asmodeus:", - "Total": "Ambrosia Luck per 1 Red Ambrosia Luck:" + "HorseShoeRune": "Horse Shoe Rune Reduction:", + "Total": "Ambrosia Luck per 1 Red Luck:" }, "redAmbrosiaLuckStats": { - "title": "Red Ambrosia Luck", + "title": "Red Luck", "Base": "Base Value:", + "SynergismLevel": "Synergism Level Reward:", "PseudoCoins": "PseudoCoin Upgrade:", "LuckConversion": "Luck from Ambrosia Luck:", "RedAmbrosia": "The Dice That Decide Your Fate", @@ -4540,16 +6235,18 @@ "ShopRedLuck2": "Dice of Asmodeus:", "ShopRedLuck3": "High Class Dice of Asmodeus:", "Viscount": "Benefits of being a Viscount:", - "Total": "Total Red Ambrosia Luck:" + "HorseShoeRune": "Horse Shoe Rune Bonus:", + "HorseShoeTalisman": "Horse Shoe Talisman Bonus:", + "Total": "Total Red Luck:" }, "redAmbrosiaGenerationStats": { - "title": "Red Ambrosia Bar Points", + "title": "Red Bar Points", "PseudoCoins": "PseudoCoin Upgrade:", "Base": "Visited Ambrosia Subtab after clearing EXALT 5x1:", "BlueberrySpeed": "Speed from Ambrosia (0.5 DR after 1,000):", "RedAmbrosia": "Millenium-Aged Red Ambrosia:", "Exalt5": "EXALT Reward - No Ambrosia Upgrades:", - "Total": "Total Red Ambrosia Bar Points/s:" + "Total": "Total Red Bar Points/s:" }, "shopVoucherStats": { "title": "Free 'Infinity CO' Quark Shop Levels", @@ -4566,6 +6263,58 @@ "Violet": "Violet Vouchers [Ambrosia 2]:", "Total": "Total Free Levels:" }, + "talismanRuneBonusMultiplierStats": { + "title": "Talisman Power (Additive Multipliers)", + "Base": "Base Multiplier", + "AchievementBonus": "Achievement Talisman Effect Bonus:", + "Research106": "Research 5x6:", + "Research107": "Research 5x7:", + "Research116": "Research 5x16:", + "Research117": "Research 5x17:", + "Research118": "Research 5x18:", + "Research200": "Research 8x25:", + "CubeUpgrade50": "Cube Upgrade 5x10:", + "TaxmanLastStand": "Taxman's Last Stand Reward:", + "Challenge15": "Challenge 15:", + "SingularityUpgrade1": "Singularity Upgrade - Talisman Rune Bonus:", + "SingularityUpgrade2": "Singularity Upgrade - Talisman Rune Bonus II:", + "SingularityUpgrade3": "Singularity Upgrade - Talisman Rune Bonus III:", + "SingularityUpgrade4": "Singularity Upgrade - Talisman Rune Bonus IV:", + "BlueberryUpgrade": "Blueberry Module - Talisman Ambrosia Embedding:", + "Total": "Total Talisman Power Multiplier:" + }, + "salvageStats": { + "title": "Offering Salvage", + "Total": "Total Salvage:", + "extraInfo": "Your Salvage multiplies your Rune, Blessing and Spirit EXP gain by
<>
<>" + }, + "salvagePositive": { + "title": "Positive Salvage", + "AchievementBonus": "Salvage from Achievements:", + "SynergismLevel": "Synergism Level Reward:", + "SynergismLevelMilestone": "Level Milestone - Salvage Challenge Buff:", + "ReincarnationChallenge": "Reincarnation Challenge Rewards:", + "UpgradeBonus": "Reincarnation Upgrade 1:", + "CubeBlessing": "Iris Cube Blessing:", + "RuneBonus": "Thrift Rune:", + "CubeUpgrade2": "Cube Upgrade 2:", + "AbyssHepteract": "Abyss Hepteract Bonus:", + "SingularityPerk": "'...Recycled Content?' Perk:", + "InfiniteAscentRune": "Infinite Ascent - Hidden Bonus:", + "RedAmbrosiaYinYang": "Red Ambrosia: Yin:", + "Total": "Positive Salvage:", + "PositiveMultiplier": "'Demeter's Harvest' Perk Multiplier:", + "Total2": "Total Positive Salvage:" + }, + "salvageNegative": { + "title": "Negative Salvage", + "DroughtCorruption": "'Less Fruitful Harvests' Corruption", + "SingularityDebuff": "Singularity Debuff:", + "RedAmbrosiaYinYang": "Red Ambrosia: Yang:", + "Total": "Negative Salvage:", + "NegativeMultiplier": "'Recyclists clicking at your Desktop' Perk Multiplier", + "Total2": "Total Negative Salvage:" + }, "gameStage": "Current Game Stage: {{stage}}" }, "wowCubes": { @@ -4590,7 +6339,7 @@ "hypercubeQuarksToday": "Quarks gained from Hypercubes: <>", "hypercubeQuarksOpenRequirement": "Open {{amount}} Hypercubes for next quark.", "hypercubeQuarksOpenToday": "Hypercubes opened today: <>", - "platonicCubeQuarksToday": "Platonic Cubes gained from Cubes: <>", + "platonicCubeQuarksToday": "Quarks gained from Platonic Cubes: <>", "platonicCubeQuarksOpenRequirement": "Open {{amount}} Platonic Cubes for next quark.", "platonicCubeQuarksOpenToday": "Platonic Cubes opened today: <>" }, @@ -4601,16 +6350,17 @@ "total": "You have a total of <> Cube tributes!", "items": { "1": "Hermes' Tribute x{{amount}}: +{{bonus}} Accelerators per Accelerator Boost", - "2": "Aphrodite's Tribute x{{amount}}: +{{bonus}}% Multipliers", - "3": "Plutus' Tribute x{{amount}}: +{{bonus}}% Offering Gain", - "4": "Iris' Tribute x{{amount}}: +{{bonus}}% Rune EXP", - "5": "Athena's Tribute x{{amount}}: +{{bonus}}% Obtainium Gain", - "6": "Artemis' Tribute x{{amount}}: +{{bonus}}% Ant Speed", - "7": "Ares' Tribute x{{amount}}: +{{bonus}}% Sacrifice Reward", - "8": "Moloch's Tribute x{{amount}}: +{{bonus}} Effective ELO", - "9": "Midas' Tribute x{{amount}}: +{{bonus}} Bonus Rune Levels on Talismans", - "10": "Chronos' Tribute x{{amount}}: +{{bonus}}% Global Time Acceleration" - } + "2": "Aphrodite's Tribute x{{amount}}: +{{bonus}} Multipliers", + "3": "Plutus' Tribute x{{amount}}: +{{bonus}} Offering Gain", + "4": "Iris' Tribute x{{amount}}: +{{bonus}} Salvage", + "5": "Athena's Tribute x{{amount}}: +{{bonus}} Obtainium Gain", + "6": "Artemis' Tribute x{{amount}}: +{{bonus}} Ant Speed", + "7": "Ares' Tribute x{{amount}}: +{{bonus}} Sacrifice Reward", + "8": "Moloch's Tribute x{{amount}}: +{{bonus}} Effective ELO (additive)", + "9": "Midas' Tribute x{{amount}}: +{{bonus}} Effective Rune Levels", + "10": "Chronos' Tribute x{{amount}}: +{{bonus}} Global Time Acceleration" + }, + "full": "▼ Your tributes are overflowing!
<>You no longer gain Cube tributes.
<> Cubes spent still count towards Quark goals.
<> It's lonely, this far in the endgame." }, "tesseracts": { "intro": "You obtain guidance from beings in another dimension!", @@ -4618,16 +6368,16 @@ "inventory": "You possess <> Wow! Tesseracts. Get them from ascending with at least 100,000 score!", "total": "You have a total of <> Tesseract gifts!", "items": { - "1": "Hermes' Gift x{{amount}}: +{{bonus}}% Hermes Tribute Power", - "2": "Aphrodite's Gift x{{amount}}: +{{bonus}}% Aphrodite Tribute Power", - "3": "Plutus' Gift x{{amount}}: +{{bonus}}% Plutus Tribute Power", - "4": "Iris' Gift x{{amount}}: +{{bonus}}% Iris Tribute Power", - "5": "Athena's Gift x{{amount}}: +{{bonus}}% Athena Tribute Power", - "6": "Artemis' Gift x{{amount}}: +{{bonus}}% Artemis Tribute Power", - "7": "Ares' Gift x{{amount}}: +{{bonus}}% Ares Tribute Power", - "8": "Moloch's Gift x{{amount}}: +{{bonus}}% Moloch Tribute Power", - "9": "Midas' Gift x{{amount}}: +{{bonus}}% Midas Tribute Power", - "10": "Chronos' Gift x{{amount}}: +{{bonus}}% Chronos Tribute Power" + "1": "Hermes' Gift x{{amount}}: +{{bonus}} Hermes Accelerators", + "2": "Aphrodite's Gift x{{amount}}: +{{bonus}} Aphrodite Multipliers", + "3": "Plutus' Gift x{{amount}}: +{{bonus}} Plutus Offerings", + "4": "Iris' Gift x{{amount}}: +{{bonus}} Iris Salvage <>", + "5": "Athena's Gift x{{amount}}: +{{bonus}} Athena Obtainium", + "6": "Artemis' Gift x{{amount}}: +{{bonus}} Artemis Ant Speed", + "7": "Ares' Gift x{{amount}}: +{{bonus}} Ares Ant Sacrifice Reward", + "8": "Moloch's Gift x{{amount}}: +{{bonus}} Moloch ELO Bonus", + "9": "Midas' Gift x{{amount}}: +{{bonus}} Midas Effective Rune Levels", + "10": "Chronos' Gift x{{amount}}: +{{bonus}} Chronos Tribute Power" } }, "hypercubes": { @@ -4636,16 +6386,16 @@ "inventory": "You possess <> Wow! Hypercubes. Get them from ascending with at least 1e9 score!", "total": "You have a total of <> Hypercube benedictions!", "items": { - "1": "Hermes' Benediction x{{amount}}: +{{bonus}}% Hermes Gift Power", - "2": "Aphrodite's Benediction x{{amount}}: +{{bonus}}% Aphrodite Gift Power", - "3": "Plutus' Benediction x{{amount}}: +{{bonus}}% Plutus Gift Power", - "4": "Iris' Benediction x{{amount}}: +{{bonus}}% Iris Gift Power", - "5": "Athena's Benediction x{{amount}}: +{{bonus}}% Athena Gift Power", - "6": "Artemis' Benediction x{{amount}}: +{{bonus}}% Artemis Gift Power", - "7": "Ares' Benediction x{{amount}}: +{{bonus}}% Ares Gift Power", - "8": "Moloch's Benediction x{{amount}}: +{{bonus}}% Moloch Gift Power", - "9": "Midas' Benediction x{{amount}}: +{{bonus}}% Midas Gift Power", - "10": "Chronos' Benediction x{{amount}}: +{{bonus}}% Chronos Gift Power" + "1": "Hermes' Benediction x{{amount}}: +{{bonus}} Hermes Gift → Hermes Accelerators", + "2": "Aphrodite's Benediction x{{amount}}: +{{bonus}} Aphrodite Gift → Aphrodite Multipliers", + "3": "Plutus' Benediction x{{amount}}: +{{bonus}} Plutus Gift → Plutus Offerings", + "4": "Iris' Benediction x{{amount}}: +{{bonus}} Iris Gift Cap <>", + "5": "Athena's Benediction x{{amount}}: +{{bonus}} Athena Gift → Athena Ant Speed", + "6": "Artemis' Benediction x{{amount}}: +{{bonus}} Artemis Gift → Artemis Ant Sacrifice Reward", + "7": "Ares' Benediction x{{amount}}: +{{bonus}} Ares Gift → Ares ELO Bonus", + "8": "Moloch's Benediction x{{amount}}: +{{bonus}} Moloch Gift → Moloch ELO Bonus", + "9": "Midas' Benediction x{{amount}}: +{{bonus}} Midas Gift → Midas Effective Rune Levels", + "10": "Chronos' Benediction x{{amount}}: +{{bonus}} Chronos Gift → Chronos Tribute Power" } }, "platonics": { @@ -4654,15 +6404,16 @@ "inventory": "You possess <> Wow! Platonic Cubes. Get them from ascending with at least 2.666e12 score!", "total": "You have a total of <> Platonic Cubes opened!", "items": { - "1": "Cube Shard: x{{amount}}: +{{bonus}}% Bonus Wow! Cubes", - "2": "Tesseract Shard: x{{amount}}: +{{bonus}}% Bonus Wow! Tesseracts", - "3": "Hypercube Shard: x{{amount}}: +{{bonus}}% Bonus Wow! Hypercubes", - "4": "Platonic Remnant: x{{amount}}: +{{bonus}}% Bonus Platonic! Cubes", - "5": "Hypercube Statue: x{{amount}}: +{{bonus}}% ALL Hypercube bonuses", - "6": "Keynesian Statue: x{{amount}}: +{{bonus}}% Bonus Constant exponent on taxes", - "7": "High-Scoring Statue: x{{amount}}: +{{bonus}}% More Score on corruptions", - "8": "Chronos Statue: x{{amount}}: +{{bonus}}% <> Global Speed (after speed diminishing returns)" - } + "1": "Cube Shard: x{{amount}}: +{{bonus}} Bonus Wow! Cubes", + "2": "Tesseract Shard: x{{amount}}: +{{bonus}} Bonus Wow! Tesseracts", + "3": "Hypercube Shard: x{{amount}}: +{{bonus}} Bonus Wow! Hypercubes", + "4": "Platonic Remnant: x{{amount}}: +{{bonus}} Bonus Platonic! Cubes", + "5": "Hypercube Statue: x{{amount}}: +{{bonus}} to most<> Hypercube Benediction bonuses", + "6": "Keynesian Statue: x{{amount}}: +{{bonus}} Bonus Constant exponent on taxes <>", + "7": "High-Scoring Statue: x{{amount}}: +{{bonus}} More Score on corruptions", + "8": "Chronos Statue: x{{amount}}: +{{bonus}} <> Global Speed (ignores diminishing returns)" + }, + "asterisk": "* The Hypercube Statue ignores Iris and Moloch Benedictions, because they do not worship its beauty." }, "platonicUpgrades": { "c15Rewards": { @@ -4679,14 +6430,15 @@ "accelerator": "• Accelerators: +{{amount}} [Immaculate!]", "multiplier": "• Multipliers: +{{amount}} [Immaculate!]", "runeExp": "• Rune EXP: +{{amount}}", - "runeBonus": "• Rune Effectiveness: +{{amount}}", + "runeBonus": "• Rune Power: +{{amount}}", "cube2": "• All Cube Types II: +{{amount}}", "transcendChallengeReduction": "• Challenge 1-5 Requirement Scaling: {{amount}}", "reincarnationChallengeReduction": "• Challenge 6-10 Requirement Scaling: {{amount}}", "antSpeed": "• Ant Speed: +{{amount}} [Immaculate!]", "bonusAntLevel": "• Bonus Ant Levels: +{{amount}}", + "achievementUnlock": "• Achievement Unlocked! [[252]]", "cube3": "• All Cube Types III: +{{amount}}", - "talismanBonus": "• Talisman Effectiveness: +{{amount}}", + "talismanBonus": "• Talisman Bonus Free Levels: +{{amount}}", "globalSpeed": "• Global Speed: +{{amount}}", "blessingBonus": "• Blessing Effectiveness: +{{amount}}", "constantBonus": "• Ascend Building Production: +{{amount}}", @@ -4736,21 +6488,23 @@ "10": "C10 Exponent: 1.0375 --> 1.04, Constant tax exponent +0.20, 10x faster Constant production, 1.10x Quarks, +10 Reincarnation Challenge Cap, +5 Ascension Challenge Cap, 3.5x Immaculate Obtainium and Offerings, 2x All Cubes, ^1.25 ant exponent in C15, +1 Corruption Cap Level again!", "11": "If Crystal Mine Collapse is set to level 11 or higher, Reincarnations will also give Diamonds based on Particle gain!", "12": "Gain an incorruptible x(1 + level/100) Ant multiplier per Challenge completion!", - "13": "The effect of Less Fruitful Harvests is raised to ^0.5.", + "13": "'Less Fruitful Harvests' reduces your <> by 50% less!", "14": "Multiply Great Depression II's coin exponent by 1.55, but only in Challenge 15.", - "15": "You begin to find the start of the abyss. Coin Exponent +0.10 in Challenge 15, Challenge 15 Score +25%, Ascension Speed +0.2% per Corruption Level (Max: 20%), +1% all Cube types per C9 Completion (Multiplicative), 1.15x Quarks, 1e250x Tesseract Building Multiplier, 2x Ascension Count, +30 Reincarnation Challenge Cap, +20 Ascension Challenge Cap, 6x Immaculate Obtainium and Offerings! Talk about a deep dive.", + "15": "You begin to find the start of the abyss. Coin Exponent +0.10 in Challenge 15, Challenge 15 Score +25%, Ascension Speed +0.2% per Corruption Level (Max: 20%), +1% to 3D-7D (Cube-Hepteracts) Cubes per C9 Completion (Multiplicative), 1.15x Quarks, 1e250x Tesseract Building Multiplier, 2x Ascension Count, +30 Reincarnation Challenge Cap, +20 Ascension Challenge Cap, 6x Immaculate Obtainium and Offerings! Talk about a deep dive.", "16": "Increase powder conversion rate by 1% per level, gain +2% Ascension count per level and gain up to 2% more Ascension count per level based on powder, up to 100,000. This will also multiply Tesseract Building production by (Powder + 1)^(10 * level), uncapped.", "17": "If this is bought, Viscous's score multiplier is raised to 3+(level*0.04) if its level is set to 10 or higher.", "18": "Raise the base percentage of Constant Upgrade 1 by 0.1% and increase the base percentage cap of Constant Upgrade 2 by 0.3% per level!", "19": "The Chronos Hepteract's diminishing return exponent is increased by level/750 (roughly 0.00133). (Base: 1/6)", "20": "You know, maybe some things should be left unbought." - } + }, + "20Bought": "While I strongly recommended you not to buy this, you did it anyway. For that, you have unlocked the Antiquities of Ant God Rune." }, "hepteractForge": { "howDidIGetHere": "How did I get here?", "noTimeToWaste": "Oh well, no time to waste. This is your lab. Make cool stuff with your Hepteracts!", "noPassiveBonus": "Hepteracts do not provide passive bonuses. They must be made useful through crafting.", "youPossessHepteracts": "You possess <> Hepteracts! You know where to get these, right?", + "multiplierText": "<> Hepteract capacities are currently multiplied by <>. Expansions cost what they would if this multiplier were 1.", "craft": "Craft", "max": "Max", "expand": "Expand", @@ -4768,42 +6522,42 @@ "descriptions": { "chronos": { "effect": "This hepteract bends time, in your favor. +0.06% Ascension Speed per Chronos Hepteract.", - "currentEffect": "Current Effect: Ascension Speed +{{x}}%", + "currentEffect": "Current Effect: Ascension Speed +{{x}}", "oneCost": "One of these will cost you {{x}} Hepteracts and {{y}} Obtainium!" }, "hyperrealism": { "effect": "This bad boy can make hypercube gain skyrocket. +0.06% Hypercubes per Hyperreal Hepteract.", - "currentEffect": "Current Effect: Hypercubes +{{x}}%", + "currentEffect": "Current Effect: Hypercubes +{{x}}", "oneCost": "One of these will cost you {{x}} Hepteracts and {{y}} Offerings." }, "quark": { "effect": "One pound, two pound fish, fishy grant +0.05% Quarks per Quark Hepteract fish fish.", - "currentEffect": "Current Effect: Quarks +{{x}}%", + "currentEffect": "Current Effect: Quarks +{{x}}", "oneCost": "One of these will cost you {{x}} Hepteracts and {{y}} Quarks." }, "challenge": { "effect": "That's preposterous. How are you going to gain +0.05% C15 Exponent per Challenge Hepteract? How!?", - "currentEffect": "Current Effect: C15 Exponent +{{x}}%", + "currentEffect": "Current Effect: C15 Exponent +{{x}}", "oneCost": "One of these will cost you {{x}} Hepteracts, {{y}} Platonic Cubes and {{z}} Cubes." }, "abyss": { "effect": "It seems like this holds the power to be at the End of Time. Do you remember why you need this?", - "currentEffect": "<[You will submit to the Omega Entity of Time]>", + "currentEffect": "Your rememberance of Time Immemorial grants you <>", "oneCost": "One of these will cost you {{x}} Hepteracts and {{y}} Wow! Cubes (lol)" }, "accelerator": { "effect": "Haha, stupid Corruptions. +2,000 +0.03% Immaculate Accelerators per 'Way too many accelerators' Hepteract!", - "currentEffect": "Current Effect: Immaculate Accelerators +{{x}} + {{y}}%", + "currentEffect": "Current Effect: Immaculate Accelerators +{{x}} + {{y}}", "oneCost": "One of these will cost you {{x}} Hepteracts and {{y}} Wow! Tesseracts" }, "acceleratorBoost": { "effect": "Haha, stupid Corruptions. +0.1% Accelerator Boosts per 'Way too many accelerator boosts' Hepteract!", - "currentEffect": "Current Effect: Accelerator Boosts +{{x}}%", + "currentEffect": "Current Effect: Accelerator Boosts +{{x}}", "oneCost": "One of these will cost you {{x}} Hepteracts and {{y}} Hypercubes" }, "multiplier": { "effect": "Haha, stupid Corruptions. +1,000 +0.03% Immaculate Multipliers per 'Way too many multipliers' Hepteract!", - "currentEffect": "Current Effect: Immaculate Multipliers +{{x}} +{{y}}%", + "currentEffect": "Current Effect: Immaculate Multipliers +{{x}} +{{y}}", "oneCost": "One of these will cost you {{x}} Hepteracts and {{y}} Obtainium" } } @@ -4955,7 +6709,7 @@ "transferredFromLZ": "Transferred save to new format successfully!", "allCorruptionsZero": "All Corruptions have been set to Zero. This will take effect on the next Ascension.", "corruptionLoadoutApplied": "Corruption Loadout {{x}} \"{{y}}\" has been applied. This will take effect on the next Ascension.", - "ascendPrompt": "Ascending will reset all buildings, rune levels [NOT CAP!], talismans, most researches, and the anthill feature for Cubes of Power. Continue?", + "ascendPrompt": "Ascending will reset all buildings, rune EXP, talismans, most researches, and the anthill feature for Cubes of Power. Continue?", "reincarnatePrompt": "Reincarnating will reset EVERYTHING but in return you will get extraordinarily powerful Particles, and unlock some very strong upgrades and some new features. would you like to Reincarnate? [Disable this message in settings.]", "transcendPrompt": "Transcends will reset coin and prestige upgrades, coin producers, crystal producers AND diamonds. The first Transcension unlocks new features. Would you like to Transcend? [Toggle this message in settings.]", "prestigePrompt": "Prestige will reset coin upgrades, coin producers AND crystals. The first Prestige unlocks new features. Would you like to Prestige? [Toggle this message in settings.]", @@ -4992,7 +6746,15 @@ } }, "account": { - "logout": "The page will now reload. You are logged out! Goodbye!!" + "logout": "The page will now reload. You are logged out! Goodbye!!", + "download": "Download", + "loadSave": "Load Save", + "delete": "Delete", + "noSaves": "No saves available", + "noSaveFound": "No save found with that ID. Try reloading the page.", + "downloadComplete": "Download complete", + "deletedSave": "\"{{name}}\" was deleted", + "notDeleted": "Your save wasn't deleted. Check the console for more information." }, "pseudoCoins": { "coinCount": "You have <>!", @@ -5035,8 +6797,8 @@ "ADD_CODE_CAP_BUFF": "Code 'add' Capacity is multiplied by <>", "BASE_OFFERING_BUFF": "Base Offering Gain is increased by <>", "BASE_OBTAINIUM_BUFF": "Base Obtainium Gain is increased by <>", - "RED_LUCK_BUFF": "Red Ambrosia Luck is increased by <>", - "RED_GENERATION_BUFF": "Red Ambrosia Bar Point gain is multiplied by <>" + "RED_LUCK_BUFF": "Red Luck is increased by <>", + "RED_GENERATION_BUFF": "Red Bar Point gain is multiplied by <>" }, "consumables": { "tipReceived": "{{offlineTime}} people tipped you in the last minute!", @@ -5058,7 +6820,7 @@ }, "AMBROSIA": { "title": "Ambrosia Time Skips", - "description": "Fast-forward real-life time on your Ambrosia and Red Ambrosia Bar Points!" + "description": "Fast-forward real-life time on your Ambrosia and Red Bar Points!" }, "purchaseBtn": "+{{time}} hours [<>]", "warning": "<> Takes a few seconds to apply after purchase! <>" diff --git a/translations/es.json b/translations/es.json index ae151c256..cac138d5f 100644 --- a/translations/es.json +++ b/translations/es.json @@ -1997,8 +1997,7 @@ }, "toString": { "noMinimum": "No requiere Singularidad mínima para comprar", - "costNextLevel": "Cost for next level: {{amount}} Golden Quarks.", - "spentQuarks": "Spent Quarks: {{amount}}" + "costNextLevel": "Cost for next level: {{amount}} Golden Quarks." }, "data": { "goldenQuarks1": { @@ -3651,7 +3650,7 @@ "duplication": "Bonus Duplication Rune Levels: {{x}}", "prism": "Bonus Prism Rune Levels: {{x}}", "thrift": "Bonus Thrift Rune Levels: {{x}}", - "SI": "Bonus SI Rune Levels: {{x}}" + "superiorIntellect": "Bonus SI Rune Levels: {{x}}" }, "mythicEffects": { "exemption": "Mythic Effect: +400 Duplication Rune Levels!", @@ -3672,11 +3671,11 @@ "blessingLevel": "<>: {{amount}}", "blessingPower": "Poder de Bendición: <> =-= Efecto: ¡<>x {{reward}}!", "rewards": { - "0": "Velocidad Global", - "1": "Potenciadores de Multiplicador", - "2": "Recompensas de Sacrificio de Hormigas", - "3": "Retraso del Costo de P. de Acc", - "4": "Obtainium --> Mult. Hormigas" + "speed": "Velocidad Global", + "duplication": "Potenciadores de Multiplicador", + "prism": "Recompensas de Sacrificio de Hormigas", + "thrift": "Retraso del Costo de P. de Acc", + "superiorIntellect": "Obtainium --> Mult. Hormigas" }, "increaseLevel": "Aumenta el nivel en <> [Coste: <> Ofrendas]" }, @@ -3684,11 +3683,11 @@ "spiritLevel": "Nivel de Espíritu: <>", "spiritPower": "Poder de Espíritus: <> =-= Efecto: ¡<>x {{reward}}!", "rewards": { - "0": "x Velocidad Global", - "1": "x Cubos ¡Wow! al Ascender", - "2": "x Niveles adicionales al límite de Cristales", - "3": "x Obtainium", - "4": "Efecto de Investigación 4x9 % más fuerte" + "speed": "x Velocidad Global", + "duplication": "x Cubos ¡Wow! al Ascender", + "prism": "x Niveles adicionales al límite de Cristales", + "thrift": "x Obtainium", + "superiorIntellect": "Efecto de Investigación 4x9 % más fuerte" }, "buyUpTo": "Compra hasta <> Niveles por compra, si es asequible. [Entrada para alternar]" }, @@ -4481,7 +4480,6 @@ "BaseTimer": "Base Timer (ms):", "Calculator4": "PL-AT δ Discount:", "SingularityCount": "Singularity Milestone Bonus:", - "InfiniteAscent": "Infinite Ascent Bonus:", "SingularityPerkBonus": "Singularity Perk Bonus:", "Total": "Final Add Code Interval (ms):" }, diff --git a/translations/fr.json b/translations/fr.json index cf6be5433..50e07949f 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -1997,8 +1997,7 @@ }, "toString": { "noMinimum": "No minimal Singularity to purchase required", - "costNextLevel": "Cost for next level: {{amount}} Golden Quarks.", - "spentQuarks": "Spent Quarks: {{amount}}" + "costNextLevel": "Cost for next level: {{amount}} Golden Quarks." }, "data": { "goldenQuarks1": { @@ -3651,7 +3650,7 @@ "duplication": "Niveaux gratuits pour la Rune de Duplication : {{x}}", "prism": "Niveaux gratuits pour la Rune Prismatique : {{x}}", "thrift": "Niveaux gratuits pour la Rune de l'Épargne : {{x}}", - "SI": "Niveaux gratuits pour la Rune d'IS : {{x}}" + "superiorIntellect": "Niveaux gratuits pour la Rune d'IS : {{x}}" }, "mythicEffects": { "exemption": "Effet mythique : +400 niveaux gratuits pour la Rune de Duplication !", @@ -3672,11 +3671,11 @@ "blessingLevel": "<> : {{amount}}", "blessingPower": "Puissance de Bénédiction : <> =-=-= Effet : {{reward}} x<> !", "rewards": { - "0": "vitesse globale", - "1": "Renfort de Multiplicateurs", - "2": "récompenses de sacrifice", - "3": "délai du coût des Renforts d'Accélérateurs", - "4": "multiplicateur Obtainium → Fourmis" + "speed": "vitesse globale", + "duplication": "Renfort de Multiplicateurs", + "prism": "récompenses de sacrifice", + "thrift": "délai du coût des Renforts d'Accélérateurs", + "superiorIntellect": "multiplicateur Obtainium → Fourmis" }, "increaseLevel": "Augmenter de <> niveaux [Coût : <> Offrandes]" }, @@ -3684,11 +3683,11 @@ "spiritLevel": "Niveau d'Esprit : <>", "spiritPower": "Puissance d'Esprit : <> =-=-= Effet : {{reward}} x<> !", "rewards": { - "0": "vitesse globale", - "1": "Wow! Cubes par Ascension", - "2": "limite de niveau des améliorations de Cristaux", - "3": "Obtainium", - "4": "effet de la Recherche 4x9" + "speed": "vitesse globale", + "duplication": "Wow! Cubes par Ascension", + "prism": "limite de niveau des améliorations de Cristaux", + "thrift": "Obtainium", + "superiorIntellect": "effet de la Recherche 4x9" }, "buyUpTo": "Achetez jusqu'à <> niveaux par achat, si vous en avez les moyens. [Changez la valeur ci-dessus]" }, @@ -4481,7 +4480,6 @@ "BaseTimer": "Base Timer (ms):", "Calculator4": "Réduction de la PL-AT δ :", "SingularityCount": "Singularity Milestone Bonus:", - "InfiniteAscent": "Infinite Ascent Bonus:", "SingularityPerkBonus": "Singularity Perk Bonus:", "Total": "Final Add Code Interval (ms):" }, diff --git a/translations/ja.json b/translations/ja.json index 85f47bbe6..5cb3ee823 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -1997,8 +1997,7 @@ }, "toString": { "noMinimum": "No minimal Singularity to purchase required", - "costNextLevel": "Cost for next level: {{amount}} Golden Quarks.", - "spentQuarks": "Spent Quarks: {{amount}}" + "costNextLevel": "Cost for next level: {{amount}} Golden Quarks." }, "data": { "goldenQuarks1": { @@ -3651,7 +3650,7 @@ "duplication": "Bonus Duplication Rune Levels: {{x}}", "prism": "Bonus Prism Rune Levels: {{x}}", "thrift": "Bonus Thrift Rune Levels: {{x}}", - "SI": "Bonus SI Rune Levels: {{x}}" + "superiorIntellect": "Bonus SI Rune Levels: {{x}}" }, "mythicEffects": { "exemption": "Mythic Effect: +400 Duplication Rune Levels!", @@ -3671,25 +3670,11 @@ "allIncreaseLevel": "ALL Increase Level", "blessingLevel": "<>: {{amount}}", "blessingPower": "Blessing Power: <> =-=-= Effect: <>x {{reward}}!", - "rewards": { - "0": "Global Speed", - "1": "Multiplier Boosts", - "2": "Ant Sacrifice Rewards", - "3": "Delay for A.Boost Cost", - "4": "Obtainium --> Ant Mult." - }, "increaseLevel": "Increase Level By <> [Cost: <> Offerings]" }, "spirits": { "spiritLevel": "Spirit level: <>", "spiritPower": "Spirit Power: <> =-=-= Effect: <>{{reward}}!", - "rewards": { - "0": "x Global Speed", - "1": "x Wow! Cubes by Ascension", - "2": "x Additional levels to Crystal caps", - "3": "x Obtainium", - "4": "% stronger Research 4x9 effect" - }, "buyUpTo": "Buy up to <> Levels per purchase, if affordable. [Input to toggle]" }, "perOfferingText": "+{{exp}} EXP per offering, {{x}} [{{y}}] Offerings to level up once.", @@ -4481,7 +4466,6 @@ "BaseTimer": "Base Timer (ms):", "Calculator4": "PL-AT δ Discount:", "SingularityCount": "Singularity Milestone Bonus:", - "InfiniteAscent": "Infinite Ascent Bonus:", "SingularityPerkBonus": "Singularity Perk Bonus:", "Total": "Final Add Code Interval (ms):" }, diff --git a/translations/kaa.json b/translations/kaa.json index 631667f25..0315ee866 100644 --- a/translations/kaa.json +++ b/translations/kaa.json @@ -2772,7 +2772,7 @@ "duplication": "Bonus Duplication Rune Levels: {{x}}", "prism": "Bonus Prism Rune Levels: {{x}}", "thrift": "Bonus Thrift Rune Levels: {{x}}", - "SI": "Bonus SI Rune Levels: {{x}}" + "superiorIntellect": "Bonus SI Rune Levels: {{x}}" }, "mythicEffects": { "exemption": "Mythic Effect: +400 Duplication Rune Levels!", @@ -2792,25 +2792,11 @@ "allIncreaseLevel": "ALL Increase Level", "blessingLevel": "<>: {{amount}}", "blessingPower": "Blessing Power: <> =-=-= Effect: <>x {{reward}}!", - "rewards": { - "0": "Global Speed", - "1": "Multiplier Boosts", - "2": "Ant Sacrifice Rewards", - "3": "Delay for A.Boost Cost", - "4": "Obtainium --> Ant Mult." - }, "increaseLevel": "Increase Level By <> [Cost: <> Offerings]" }, "spirits": { "spiritLevel": "Spirit level: <>", "spiritPower": "Spirit Power: <> =-=-= Effect: <>{{reward}}!", - "rewards": { - "0": "x Global Speed", - "1": "x Wow! Cubes by Ascension", - "2": "x Additional levels to Crystal caps", - "3": "x Obtainium", - "4": "% stronger Research 4x9 effect" - }, "buyUpTo": "Buy up to <> Levels per purchase, if affordable. [Input to toggle]" }, "perOfferingText": "+{{exp}} EXP per offering, {{x}} [{{y}}] Offerings to level up once.", diff --git a/translations/nl.json b/translations/nl.json index 8b0fc4c06..bc615caf8 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -3054,7 +3054,7 @@ "duplication": "Bonus Duplication Rune Levels: {{x}}", "prism": "Bonus Prism Rune Levels: {{x}}", "thrift": "Bonus Thrift Rune Levels: {{x}}", - "SI": "Bonus SI Rune Levels: {{x}}" + "superiorIntellect": "Bonus SI Rune Levels: {{x}}" }, "mythicEffects": { "exemption": "Mythic Effect: +400 Duplication Rune Levels!", @@ -3074,25 +3074,11 @@ "allIncreaseLevel": "ALL Increase Level", "blessingLevel": "<>: {{amount}}", "blessingPower": "Blessing Power: <> =-=-= Effect: <>x {{reward}}!", - "rewards": { - "0": "Global Speed", - "1": "Multiplier Boosts", - "2": "Ant Sacrifice Rewards", - "3": "Delay for A.Boost Cost", - "4": "Obtainium --> Ant Mult." - }, "increaseLevel": "Increase Level By <> [Cost: <> Offerings]" }, "spirits": { "spiritLevel": "Spirit level: <>", "spiritPower": "Spirit Power: <> =-=-= Effect: <>{{reward}}!", - "rewards": { - "0": "x Global Speed", - "1": "x Wow! Cubes by Ascension", - "2": "x Additional levels to Crystal caps", - "3": "x Obtainium", - "4": "% stronger Research 4x9 effect" - }, "buyUpTo": "Buy up to <> Levels per purchase, if affordable. [Input to toggle]" }, "perOfferingText": "+{{exp}} EXP per offering, {{x}} [{{y}}] Offerings to level up once.", diff --git a/translations/pl.json b/translations/pl.json index 4e52cde37..9d95600d1 100644 --- a/translations/pl.json +++ b/translations/pl.json @@ -1997,8 +1997,7 @@ }, "toString": { "noMinimum": "No minimal Singularity to purchase required", - "costNextLevel": "Cost for next level: {{amount}} Golden Quarks.", - "spentQuarks": "Spent Quarks: {{amount}}" + "costNextLevel": "Cost for next level: {{amount}} Golden Quarks." }, "data": { "goldenQuarks1": { @@ -3651,7 +3650,7 @@ "duplication": "Bonus Duplication Rune Levels: {{x}}", "prism": "Bonus Prism Rune Levels: {{x}}", "thrift": "Bonus Thrift Rune Levels: {{x}}", - "SI": "Bonus SI Rune Levels: {{x}}" + "superiorIntellect": "Bonus SI Rune Levels: {{x}}" }, "mythicEffects": { "exemption": "Mythic Effect: +400 Duplication Rune Levels!", @@ -3672,11 +3671,11 @@ "blessingLevel": "<>: {{amount}}", "blessingPower": "Blessing Power: <> =-=-= Effect: <>x {{reward}}!", "rewards": { - "0": "Globalna prędkość", - "1": "Multiplier Boosts", - "2": "Ant Sacrifice Rewards", - "3": "Delay for A.Boost Cost", - "4": "Obtainium --> Ant Mult." + "speed": "Globalna prędkość", + "duplication": "Multiplier Boosts", + "prism": "Ant Sacrifice Rewards", + "thrift": "Delay for A.Boost Cost", + "superiorIntellect": "Obtainium --> Ant Mult." }, "increaseLevel": "Increase Level By <> [Cost: <> Offerings]" }, @@ -3684,11 +3683,11 @@ "spiritLevel": "Spirit level: <>", "spiritPower": "Spirit Power: <> =-=-= Effect: <>{{reward}}!", "rewards": { - "0": "x Globalna prędkość", - "1": "x Wow! Cubes by Ascension", - "2": "x Additional levels to Crystal caps", - "3": "x Obtainium", - "4": "% stronger Research 4x9 effect" + "speed": "x Globalna prędkość", + "duplication": "x Wow! Cubes by Ascension", + "prism": "x Additional levels to Crystal caps", + "thrift": "x Obtainium", + "superiorIntellect": "% stronger Research 4x9 effect" }, "buyUpTo": "Buy up to <> Levels per purchase, if affordable. [Input to toggle]" }, @@ -4481,7 +4480,6 @@ "BaseTimer": "Base Timer (ms):", "Calculator4": "PL-AT δ Discount:", "SingularityCount": "Singularity Milestone Bonus:", - "InfiniteAscent": "Infinite Ascent Bonus:", "SingularityPerkBonus": "Singularity Perk Bonus:", "Total": "Final Add Code Interval (ms):" }, diff --git a/translations/pt.json b/translations/pt.json index ff9b16fb5..0f40c275f 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -2772,7 +2772,7 @@ "duplication": "Bonus Duplication Rune Levels: {{x}}", "prism": "Bonus Prism Rune Levels: {{x}}", "thrift": "Bonus Thrift Rune Levels: {{x}}", - "SI": "Bonus SI Rune Levels: {{x}}" + "superiorIntellect": "Bonus SI Rune Levels: {{x}}" }, "mythicEffects": { "exemption": "Mythic Effect: +400 Duplication Rune Levels!", @@ -2792,25 +2792,11 @@ "allIncreaseLevel": "ALL Increase Level", "blessingLevel": "<>: {{amount}}", "blessingPower": "Blessing Power: <> =-=-= Effect: <>x {{reward}}!", - "rewards": { - "0": "Global Speed", - "1": "Multiplier Boosts", - "2": "Ant Sacrifice Rewards", - "3": "Delay for A.Boost Cost", - "4": "Obtainium --> Ant Mult." - }, "increaseLevel": "Increase Level By <> [Cost: <> Offerings]" }, "spirits": { "spiritLevel": "Spirit level: <>", "spiritPower": "Spirit Power: <> =-=-= Effect: <>{{reward}}!", - "rewards": { - "0": "x Global Speed", - "1": "x Wow! Cubes by Ascension", - "2": "x Additional levels to Crystal caps", - "3": "x Obtainium", - "4": "% stronger Research 4x9 effect" - }, "buyUpTo": "Buy up to <> Levels per purchase, if affordable. [Input to toggle]" }, "perOfferingText": "+{{exp}} EXP per offering, {{x}} [{{y}}] Offerings to level up once.", diff --git a/translations/pt_BR.json b/translations/pt_BR.json index 72a5f08b9..360150905 100644 --- a/translations/pt_BR.json +++ b/translations/pt_BR.json @@ -2768,7 +2768,7 @@ "duplication": "Bonus Duplication Rune Levels: {{x}}", "prism": "Bonus Prism Rune Levels: {{x}}", "thrift": "Bonus Thrift Rune Levels: {{x}}", - "SI": "Bonus SI Rune Levels: {{x}}" + "superiorIntellect": "Bonus SI Rune Levels: {{x}}" }, "mythicEffects": { "exemption": "Mythic Effect: +400 Duplication Rune Levels!", @@ -2788,25 +2788,11 @@ "allIncreaseLevel": "", "blessingLevel": "", "blessingPower": "", - "rewards": { - "0": "", - "1": "", - "2": "", - "3": "", - "4": "" - }, "increaseLevel": "" }, "spirits": { "spiritLevel": "", "spiritPower": "", - "rewards": { - "0": "", - "1": "", - "2": "", - "3": "", - "4": "" - }, "buyUpTo": "" }, "perOfferingText": "", diff --git a/translations/ru.json b/translations/ru.json index 36b5b19fd..7deaf67d4 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -1997,8 +1997,7 @@ }, "toString": { "noMinimum": "Никакой минимальной Сингулярности для покупки не требуется", - "costNextLevel": "Стоимость следующего уровня: {{amount}} Золотых Кварков.", - "spentQuarks": "Кварков Потрачено: {{amount}}" + "costNextLevel": "Стоимость следующего уровня: {{amount}} Золотых Кварков." }, "data": { "goldenQuarks1": { @@ -3651,7 +3650,7 @@ "duplication": "Бонусные Уровни Руны Дубликации: {{x}}", "prism": "Бонусные Уровни Руны Призмы: {{x}}", "thrift": "Бонусные Уровни Руны Экономии: {{x}}", - "SI": "Бонусные Уровни Руны ВР: {{x}}" + "superiorIntellect": "Бонусные Уровни Руны ВР: {{x}}" }, "mythicEffects": { "exemption": "Мифический Эффект: +400 Уровней Руны Дубликации!", @@ -3672,11 +3671,11 @@ "blessingLevel": "<>: {{amount}}", "blessingPower": "Сила Благословения: <> =-=-= Эффект: <>x {{reward}}!", "rewards": { - "0": "Глобальная Скорость", - "1": "Усилители Множителя", - "2": "Награда за Жертвоприношения", - "3": "Задержка Стоимости Усилений Ускорителей", - "4": "Обтаниум --> Множ. Муравьёв" + "speed": "Глобальная Скорость", + "duplication": "Усилители Множителя", + "prism": "Награда за Жертвоприношения", + "thrift": "Задержка Стоимости Усилений Ускорителей", + "superiorIntellect": "Обтаниум --> Множ. Муравьёв" }, "increaseLevel": "Увеличить уровень на <> [Стоимость: <> Подношений]" }, @@ -3684,11 +3683,11 @@ "spiritLevel": "Уровень Духа: <>", "spiritPower": "Сила Духа: <> =-=-= Эффект: <>x {{reward}}!", "rewards": { - "0": "х Глобальная Скорость", - "1": "x Вау! Кубов при Вознесении", - "2": "x Дополнительных макс уровней улучшений Кристаллов", - "3": "x Обтаниум", - "4": "% сильнее эффект Исследования 4x9" + "speed": "х Глобальная Скорость", + "duplication": "x Вау! Кубов при Вознесении", + "prism": "x Дополнительных макс уровней улучшений Кристаллов", + "thrift": "x Обтаниум", + "superiorIntellect": "% сильнее эффект Исследования 4x9" }, "buyUpTo": "Купить до <> Уровней за покупку, если возможно. [Введите для переключения]" }, @@ -4481,7 +4480,6 @@ "BaseTimer": "Базовый Таймер (мс):", "Calculator4": "Скидка PL-AT δ:", "SingularityCount": "Бонус Вех Сингулярности:", - "InfiniteAscent": "Бонус Бесконечного Восхождения:", "SingularityPerkBonus": "Бонус Привилегии Сингулярности:", "Total": "Окончательный Интервал Кода Add (мс):" }, diff --git a/translations/zh.json b/translations/zh.json index 508538552..717d5eb2c 100644 --- a/translations/zh.json +++ b/translations/zh.json @@ -1997,8 +1997,7 @@ }, "toString": { "noMinimum": "无进入奇点次数要求", - "costNextLevel": "下一级的花费:{{amount}}金夸克。", - "spentQuarks": "已花费金夸克数量:{{amount}}" + "costNextLevel": "下一级的花费:{{amount}}金夸克。" }, "data": { "goldenQuarks1": { @@ -3651,7 +3650,7 @@ "duplication": "重叠符文额外等级:{{x}}", "prism": "棱柱符文额外等级:{{x}}", "thrift": "节俭符文额外等级:{{x}}", - "SI": "卓越智慧额外等级:{{x}}" + "superiorIntellect": "卓越智慧额外等级:{{x}}" }, "mythicEffects": { "exemption": "神秘效果:重叠符文等级增加400!", @@ -3672,11 +3671,11 @@ "blessingLevel": "<>:{{amount}}", "blessingPower": "祝福能量:<> =-=-= 效果:<>倍{{reward}}!", "rewards": { - "0": "全局速度", - "1": "加倍器加成", - "2": "蚂蚁献祭奖励", - "3": "加速器加成成本增长减缓", - "4": "难得素对蚂蚁倍率的指数" + "speed": "全局速度", + "duplication": "加倍器加成", + "prism": "蚂蚁献祭奖励", + "thrift": "加速器加成成本增长减缓", + "superiorIntellect": "难得素对蚂蚁倍率的指数" }, "increaseLevel": "提升<>级[花费:<>祭品]" }, @@ -3684,11 +3683,11 @@ "spiritLevel": "魂灵等级:<>", "spiritPower": "魂灵能量:<> =-=-= 效果:<>{{reward}}!", "rewards": { - "0": "倍全局速度", - "1": "倍飞升时惊奇方盒获取数量", - "2": "倍额外的水晶升级等级上限", - "3": "倍难得素获取数量", - "4": "%额外的研究4x9效果" + "speed": "倍全局速度", + "duplication": "倍飞升时惊奇方盒获取数量", + "prism": "倍额外的水晶升级等级上限", + "thrift": "倍难得素获取数量", + "superiorIntellect": "%额外的研究4x9效果" }, "buyUpTo": "最多一次购买<>级。[输入数字来控制]" }, @@ -4481,7 +4480,6 @@ "BaseTimer": "基础时间(单位为毫秒):", "Calculator4": "PL-AT δ加快恢复:", "SingularityCount": "奇点次数里程碑加成:", - "InfiniteAscent": "无限晋升符文加成:", "SingularityPerkBonus": "奇点特权加成:", "Total": "“增加”代码最终恢复时间(单位为毫秒):" },