Merge branch 'master' into amoled-theme

This commit is contained in:
SpectralFlame 2023-02-11 09:29:16 -06:00
commit de2f9c3b68
208 changed files with 3657 additions and 836 deletions

View File

@ -142,12 +142,6 @@ export const Achievements = {
return;
}
if (Achievements.preReality.every(a => a.isUnlocked)) return;
if (Perk.achievementGroup5.isBought) {
for (const achievement of Achievements.preReality) {
achievement.unlock(true);
}
return;
}
player.reality.achTimer += diff;
if (player.reality.achTimer < this.period) return;

View File

@ -72,6 +72,8 @@ import SwitchAutomatorEditorModal from "@/components/modals/SwitchAutomatorEdito
import UiChoiceModal from "@/components/modals/UiChoiceModal";
import UndoGlyphModal from "@/components/modals/UndoGlyphModal";
import S12GamesModal from "@/components/modals/secret-themes/S12GamesModal";
let nextModalID = 0;
export class Modal {
constructor(component, priority = 0, closeEvent) {
@ -255,6 +257,8 @@ Modal.sacrifice = new Modal(SacrificeModal, 1, GAME_EVENT.DIMBOOST_AFTER);
Modal.breakInfinity = new Modal(BreakInfinityModal, 1, GAME_EVENT.ETERNITY_RESET_AFTER);
Modal.respecIAP = new Modal(RespecIAPModal);
Modal.s12Games = new Modal(S12GamesModal);
function getSaveInfo(save) {
const resources = {
realTimePlayed: 0,
@ -287,13 +291,15 @@ function getSaveInfo(save) {
resources.eternityPoints.copyFrom(new Decimal(save.eternityPoints));
resources.realityMachines.copyFrom(new Decimal(save.reality?.realityMachines));
resources.imaginaryMachines = save.reality?.iMCap ?? 0;
resources.dilatedTime.copyFrom(new Decimal(save.dilation.dilatedTime));
// Use max DT instead of current DT because spending it can cause it to drop and trigger the conflict modal
// unnecessarily. We only use current DT as a fallback (eg. loading a save from pre-reality versions)
resources.dilatedTime.copyFrom(new Decimal(save.records?.thisReality.maxDT ?? (save.dilation?.dilatedTime ?? 0)));
resources.bestLevel = save.records?.bestReality.glyphLevel ?? 0;
resources.pelleAM.copyFrom(new Decimal(save.celestials?.pelle.records.totalAntimatter));
resources.remnants = save.celestials?.pelle.remnants ?? 0;
resources.realityShards.copyFrom(new Decimal(save.celestials?.pelle.realityShards));
resources.pelleLore = save.celestials?.pelle.quoteBits ?? 0;
resources.saveName = save.options.saveFileName ?? "";
resources.saveName = save.options?.saveFileName ?? "";
resources.compositeProgress = ProgressChecker.getCompositeProgress(save);
return resources;

View File

@ -4,7 +4,7 @@ export const Theme = function Theme(name, config) {
this.name = name;
this.isDark = function() {
return this.isDefault()
return (this.isDefault() || name === "S12")
? player.options.newUI
: config.isDark;
};
@ -88,6 +88,7 @@ Theme.secretThemeIndex = function(name) {
"dba8336cd3224649d07952b00045a6ec3c8df277aa8a0a0e3e7c2aaa77f1fbb9",
"73de8a7f9efa1cbffc80a8effc9891a799127cd204b3a8b023bea8f513ed4753",
"f3a71114261b4af6517a53f89bf0c6b56bb81b6f0e931d0e0d71249eb196628c",
"1248689171faaa0abb68279199a8d2eb232dba10d2dacb79a705f680b6862c0e",
];
const sha = sha512_256(name.toUpperCase());
return secretThemes.indexOf(sha);
@ -114,6 +115,10 @@ Theme.tryUnlock = function(name) {
SecretAchievement(25).unlock();
if (!isAlreadyUnlocked) {
GameUI.notify.success(`You have unlocked the ${name.capitalize()} theme!`, 5000);
if (Theme.current().isAnimated) {
setTimeout(Modal.message.show(`This secret theme has animations. If they are giving you performance issues,
you can turn them off in the Options/Visual tab to reduce lag.`), 100);
}
}
return true;
};
@ -150,7 +155,8 @@ export const Themes = {
Theme.create("S8", { metro: true, secret: true, }),
Theme.create("S9", { secret: true, }),
Theme.create("S10", { dark: true, metro: true, animated: true, secret: true, }),
Theme.create("S11", { dark: true, secret: true, }),
Theme.create("S11", { dark: true, animated: true, secret: true, }),
Theme.create("S12", { secret: true, }),
/* eslint-enable no-multi-spaces */
],

View File

@ -61,6 +61,7 @@ Autobuyer.eternity = new class EternityAutobuyerState extends AutobuyerState {
return (considerMilestoneReached || EternityMilestone.autoEternities.isReached) &&
!Player.isInAnyChallenge && !player.dilation.active &&
player.auto.autobuyersOn && this.data.isActive &&
this.mode === AUTO_ETERNITY_MODE.AMOUNT &&
this.amount.equals(0);
}

View File

@ -40,6 +40,12 @@ Autobuyer.epMult = new class EPMultAutobuyerState extends AutobuyerState {
}
tick() {
// While the active check is normally automatically handled with the global autobuyer ticking method, we also
// call this from the TD autobuyers in order to make sure this is executed before TDs are purchased. Simply
// reordering the autobuyer call order is undesirable because much of the codebase relies on autobuyers being
// grouped as they are, and many other autobuyers in the 5xEP group must execute *after* dimensions
if (!this.isActive) return;
applyEU2();
EternityUpgrade.epMult.buyMax();
}
}();

View File

@ -41,12 +41,30 @@ Autobuyer.reality = new class RealityAutobuyerState extends AutobuyerState {
this.data.glyph = value;
}
get time() {
return this.data.time;
}
set time(value) {
this.data.time = value;
}
get shard() {
return this.data.shard;
}
set shard(value) {
this.data.shard = value;
}
toggleMode() {
this.mode = [
AUTO_REALITY_MODE.RM,
AUTO_REALITY_MODE.GLYPH,
AUTO_REALITY_MODE.EITHER,
AUTO_REALITY_MODE.BOTH
AUTO_REALITY_MODE.BOTH,
AUTO_REALITY_MODE.TIME,
AUTO_REALITY_MODE.RELIC_SHARD
]
.nextSibling(this.mode);
}
@ -74,6 +92,12 @@ Autobuyer.reality = new class RealityAutobuyerState extends AutobuyerState {
case AUTO_REALITY_MODE.BOTH:
proc = rmProc && glyphProc;
break;
case AUTO_REALITY_MODE.TIME:
proc = player.records.thisReality.realTime / 1000 > this.time;
break;
case AUTO_REALITY_MODE.RELIC_SHARD:
proc = Effarig.shardsGained > this.shard;
break;
}
if (proc) autoReality();
}

View File

@ -34,6 +34,8 @@ class TimeDimensionAutobuyerState extends IntervaledAutobuyerState {
}
tick() {
applyEU2();
Autobuyer.epMult.tick();
const tier = this.tier;
if (!TimeDimension(tier).isAvailableForPurchase) return;
super.tick();

View File

@ -856,9 +856,12 @@ export const AutomatorBackend = {
},
start(scriptID = this.state.topLevelScript, initialMode = AUTOMATOR_MODE.RUN, compile = true) {
// Automator execution behaves oddly across new games, so we explicitly stop it from running if not unlocked
if (!Player.automatorUnlocked) return;
this.hasJustCompleted = false;
this.state.topLevelScript = scriptID;
const scriptObject = this.findScript(scriptID);
if (!scriptObject) return;
if (compile) scriptObject.compile();
if (scriptObject.commands) {
this.reset(scriptObject.commands);

View File

@ -57,15 +57,15 @@ export const AutomatorCommands = ((() => {
let addedECs, gainedEP;
switch (layer) {
case "INFINITY":
return `${format(player.records.lastTenInfinities[0][1], 2)} IP`;
return `${format(player.records.recentInfinities[0][1], 2)} IP`;
case "ETERNITY":
addedECs = AutomatorData.lastECCompletionCount;
gainedEP = `${format(player.records.lastTenEternities[0][1], 2)} EP`;
gainedEP = `${format(player.records.recentEternities[0][1], 2)} EP`;
return addedECs === 0
? `${gainedEP}`
: `${gainedEP}, ${addedECs} completions`;
case "REALITY":
return `${format(player.records.lastTenRealities[0][1], 2)} RM`;
return `${format(player.records.recentRealities[0][1], 2)} RM`;
default:
throw Error(`Unrecognized prestige ${layer} in Automator event log`);
}

View File

@ -3,6 +3,7 @@ import { GameDatabase } from "./secret-formula/game-database";
class AwayProgress {
constructor(config) {
this.name = config.name;
this.forcedName = config.forcedName;
this.isUnlocked = config.isUnlocked;
this.awayOption = config.awayOption ?? this.name;
this.showOption = config.showOption ?? true;
@ -32,6 +33,7 @@ class AwayProgress {
}
get formatName() {
if (this.forcedName) return this.forcedName;
// Format the camelCase name to Title Case, with spaces added before the capital letters
return this.name
.replace(/[A-Z]/gu, match => ` ${match}`)

View File

@ -27,7 +27,9 @@ function handleChallengeCompletion() {
export function manualBigCrunchResetRequest() {
if (!Player.canCrunch) return;
if (GameEnd.creditsEverClosed) return;
if (player.options.confirmations.bigCrunch) {
// We show the modal under two conditions - on the first ever infinity (to explain the mechanic) and
// post-break (to show total IP and infinities gained)
if (player.options.confirmations.bigCrunch && (!PlayerProgress.infinityUnlocked() || player.break)) {
Modal.bigCrunch.show();
} else {
bigCrunchResetRequest();
@ -177,7 +179,7 @@ export function preProductionGenerateIP(diff) {
genCount = Math.floor(player.partInfinityPoint);
player.partInfinityPoint -= genCount;
}
let gainedPerGen = InfinityUpgrade.ipGen.effectValue;
let gainedPerGen = player.records.bestInfinity.time >= 999999999999 ? DC.D0 : InfinityUpgrade.ipGen.effectValue;
if (Laitela.isRunning) gainedPerGen = dilatedValueOf(gainedPerGen);
const gainedThisTick = new Decimal(genCount).times(gainedPerGen);
Currency.infinityPoints.add(gainedThisTick);

View File

@ -197,16 +197,7 @@ class BlackHoleState {
if (Enslaved.autoReleaseTick < 3) return `<i class="fas fa-compress-arrows-alt u-fa-padding"></i> Pulsing`;
return `<i class="fas fa-expand-arrows-alt u-fa-padding"></i> Pulsing`;
}
if (Enslaved.isStoringGameTime) {
if (Ra.unlocks.adjustableStoredTime.canBeApplied) {
const storedTimeWeight = player.celestials.enslaved.storedFraction;
if (storedTimeWeight !== 0) {
return `<i class="fas fa-compress-arrows-alt"></i> Charging (${formatPercents(storedTimeWeight, 1)})`;
}
} else {
return `<i class="fas fa-compress-arrows-alt"></i> Charging`;
}
}
if (Enslaved.isStoringGameTime) return `<i class="fas fa-compress-arrows-alt"></i> Charging`;
if (BlackHoles.areNegative) return `<i class="fas fa-caret-left"></i> Inverted`;
if (BlackHoles.arePaused) return `<i class="fas fa-pause"></i> Paused`;
if (this.isPermanent) return `<i class="fas fa-infinity"></i> Permanent`;

View File

@ -48,14 +48,14 @@ export const GameCache = {
worstChallengeTime: new Lazy(() => player.challenge.normal.bestTimes.max()),
bestRunIPPM: new Lazy(() =>
player.records.lastTenInfinities
.map(run => ratePerMinute(run[1], run[0]))
player.records.recentInfinities
.map(run => ratePerMinute(run[2], run[0]))
.reduce(Decimal.maxReducer)
),
averageRealTimePerEternity: new Lazy(() => player.records.lastTenEternities
.map(run => run[3])
.reduce(Number.sumReducer) / (1000 * player.records.lastTenEternities.length)),
averageRealTimePerEternity: new Lazy(() => player.records.recentEternities
.map(run => run[1])
.reduce(Number.sumReducer) / (1000 * player.records.recentEternities.length)),
tickSpeedMultDecrease: new Lazy(() => 10 - Effects.sum(
BreakInfinityUpgrade.tickspeedCostMult,
@ -76,8 +76,7 @@ export const GameCache = {
Perk.achievementGroup1,
Perk.achievementGroup2,
Perk.achievementGroup3,
Perk.achievementGroup4,
Perk.achievementGroup5
Perk.achievementGroup4
)).totalMilliseconds),
buyablePerks: new Lazy(() => Perks.all.filter(p => p.canBeBought)),

View File

@ -82,7 +82,8 @@ class VRunUnlockState extends GameMechanicState {
Decimal.gte(playerData.runRecords[this.id], this.conditionValue)) {
if (!V.isFlipped && this.config.isHard) continue;
this.completions++;
GameUI.notify.success(`You have unlocked V-Achievement '${this.config.name}' tier ${this.completions}`);
GameUI.notify.success(`You have unlocked V-Achievement
'${this.config.name}' tier ${formatInt(this.completions)}`);
V.updateTotalRunUnlocks();

View File

@ -51,10 +51,10 @@ export const Effarig = {
}
},
get glyphEffectAmount() {
const genEffectBitmask = Glyphs.activeList
const genEffectBitmask = Glyphs.activeWithoutCompanion
.filter(g => generatedTypes.includes(g.type))
.reduce((prev, curr) => prev | curr.effects, 0);
const nongenEffectBitmask = Glyphs.activeList
const nongenEffectBitmask = Glyphs.activeWithoutCompanion
.filter(g => !generatedTypes.includes(g.type))
.reduce((prev, curr) => prev | curr.effects, 0);
return countValuesFromBitmask(genEffectBitmask) + countValuesFromBitmask(nongenEffectBitmask);

View File

@ -44,9 +44,6 @@ export const Enslaved = {
if (!this.canModifyGameTimeStorage) return;
player.celestials.enslaved.isStoring = !player.celestials.enslaved.isStoring;
player.celestials.enslaved.isStoringReal = false;
if (!Ra.unlocks.adjustableStoredTime.canBeApplied) {
player.celestials.enslaved.storedFraction = 1;
}
},
toggleStoreReal() {
if (!this.canModifyRealTimeStorage && !this.isStoredRealTimeCapped) return;
@ -69,8 +66,9 @@ export const Enslaved = {
},
// We assume that the situations where you can't modify time storage settings (of either type) are exactly the cases
// where they have also been explicitly disabled via other game mechanics. This also reduces UI boilerplate code.
// Note that we force time storage when auto-releasing, as not doing so caused a lot of poor usability issues
get isStoringGameTime() {
return this.canModifyGameTimeStorage && player.celestials.enslaved.isStoring;
return this.canModifyGameTimeStorage && (this.isAutoReleasing || player.celestials.enslaved.isStoring);
},
get isStoringRealTime() {
return this.canModifyRealTimeStorage && player.celestials.enslaved.isStoringReal;
@ -171,6 +169,9 @@ export const Enslaved = {
get isCompleted() {
return player.celestials.enslaved.completed;
},
get canTickHintTimer() {
return !EnslavedProgress.hintsUnlocked.hasProgress && Enslaved.has(ENSLAVED_UNLOCKS.RUN) && !Enslaved.isCompleted;
},
get isUnlocked() {
return EffarigUnlock.eternity.isUnlocked;
},

View File

@ -58,6 +58,61 @@ export const Pelle = {
// Suppress the randomness for this form
possessiveName: "Pelle's",
// This is called upon initial Dooming and after every Armageddon when using the modal
initializeRun() {
if (this.isDoomed) {
Pelle.armageddon(true);
return;
}
Glyphs.harshAutoClean();
if (!Glyphs.unequipAll()) {
Modal.message.show(`Dooming your Reality will unequip your Glyphs. Some of your
Glyphs could not be unequipped due to lack of inventory space.`, 1);
return;
}
Glyphs.harshAutoClean();
for (const type of BASIC_GLYPH_TYPES) Glyphs.addToInventory(GlyphGenerator.doomedGlyph(type));
Glyphs.refreshActive();
player.options.confirmations.glyphReplace = true;
player.reality.automator.state.repeat = false;
player.reality.automator.state.forceRestart = false;
if (BlackHoles.arePaused) BlackHoles.togglePause();
player.celestials.pelle.doomed = true;
Pelle.armageddon(false);
respecTimeStudies(true);
Currency.infinityPoints.reset();
player.IPMultPurchases = 0;
Autobuyer.bigCrunch.mode = AUTO_CRUNCH_MODE.AMOUNT;
disChargeAll();
clearCelestialRuns();
// Force-enable the group toggle for AD autobuyers to be active; whether or not they can actually tick
// is still handled through if the autobuyers are unlocked at all. This fixes an odd edge case where the player
// enters cel7 with AD autobuyers disabled - AD autobuyers need to be reupgraded, but the UI component
// for the group toggle is hidden until they're all re-upgraded to the max again.
player.auto.antimatterDims.isActive = true;
player.buyUntil10 = true;
player.records.realTimeDoomed = 0;
for (const res of AlchemyResources.all) res.amount = 0;
AutomatorBackend.stop();
// Force-unhide all tabs except for the shop tab, for which we retain the hide state instead
const shopTab = ~1 & (1 << GameDatabase.tabs.find(t => t.key === "shop").id);
player.options.hiddenTabBits &= shopTab;
// Force unhide MOST subtabs, although some of the tabs get ignored since they don't contain any
// meaningful interactable gameplay elements in Doomed
const tabsToIgnore = ["statistics", "achievements", "reality", "celestials"];
const ignoredIDs = GameDatabase.tabs.filter(t => tabsToIgnore.includes(t.key)).map(t => t.id);
for (let tabIndex = 0; tabIndex < GameDatabase.tabs.length; tabIndex++) {
player.options.hiddenSubtabBits[tabIndex] &= ignoredIDs.includes(tabIndex) ? -1 : 0;
}
Pelle.quotes.initial.show();
GameStorage.save(true);
},
get displayName() {
return Date.now() % 4000 > 500 ? "Pelle" : wordShift.randomCrossWords("Pelle");
},
@ -120,8 +175,8 @@ export const Pelle = {
},
get disabledAchievements() {
return [164, 156, 143, 142, 141, 137, 134, 133, 132, 125, 118, 117, 111, 104, 103, 93, 92, 91, 87, 85, 78, 76,
74, 65, 55, 54, 37];
return [164, 156, 143, 142, 141, 137, 134, 133, 132, 125, 118, 117, 113, 111, 104, 103, 93, 92, 91, 87, 85, 78,
76, 74, 65, 55, 54, 37];
},
get uselessInfinityUpgrades() {
@ -193,7 +248,7 @@ export const Pelle = {
case undefined:
return "No Glyph equipped!";
default:
return "";
return "You cannot equip this Glyph while Doomed!";
}
},

View File

@ -67,7 +67,7 @@ class AlchemyResourceState extends GameMechanicState {
}
get lockText() {
return `${this.unlockedWith.name} Level ${this.unlockedAt}`;
return `${this.unlockedWith.name} Level ${formatInt(this.unlockedAt)}`;
}
get isCustomEffect() {

View File

@ -280,7 +280,9 @@ export const Ra = {
// Returns a string containing a time estimate for gaining a specific amount of exp (UI only)
timeToGoalString(pet, expToGain) {
// Quadratic formula for growth (uses constant growth for a = 0)
const a = Ra.productionPerMemoryChunk * pet.memoryUpgradeCurrentMult * pet.memoryChunksPerSecond / 2;
const a = Enslaved.isStoringRealTime
? 0
: Ra.productionPerMemoryChunk * pet.memoryUpgradeCurrentMult * pet.memoryChunksPerSecond / 2;
const b = Ra.productionPerMemoryChunk * pet.memoryUpgradeCurrentMult * pet.memoryChunks;
const c = -expToGain;
const estimate = a === 0
@ -374,7 +376,7 @@ export const Ra = {
},
get momentumValue() {
const hoursFromUnlock = TimeSpan.fromMilliseconds(player.celestials.ra.momentumTime).totalHours;
return Math.clampMax(1 + 0.002 * hoursFromUnlock, AlchemyResource.momentum.effectValue);
return Math.clampMax(1 + 0.005 * hoursFromUnlock, AlchemyResource.momentum.effectValue);
},
quotes: Quotes.ra,
symbol: "<i class='fas fa-sun'></i>"

View File

@ -215,7 +215,9 @@ window.AUTO_REALITY_MODE = {
RM: 0,
GLYPH: 1,
EITHER: 2,
BOTH: 3
BOTH: 3,
TIME: 4,
RELIC_SHARD: 5,
};
// Free tickspeed multiplier with TS171. Shared here because formatting glyph effects depends on it
@ -343,9 +345,10 @@ window.GLYPH_SIDEBAR_MODE = {
window.AUTO_SORT_MODE = {
NONE: 0,
POWER: 1,
EFFECT: 2,
SCORE: 3
LEVEL: 1,
POWER: 2,
EFFECT: 3,
SCORE: 4
};
window.AUTO_GLYPH_SCORE = {

View File

@ -8,10 +8,12 @@ export function animateAndDilate() {
setTimeout(startDilatedEternity, 1000);
}
export function animateAndUndilate() {
// eslint-disable-next-line no-empty-function
export function animateAndUndilate(callback) {
FullScreenAnimationHandler.display("a-undilate", 2);
setTimeout(() => {
eternity(false, false, { switchingDilation: true });
if (callback) callback();
}, 1000);
}
@ -174,8 +176,8 @@ export function getTP(antimatter, requireEternity) {
return getBaseTP(antimatter, requireEternity).times(tachyonGainMultiplier());
}
// Returns the amount of TP gained, subtracting out current TP; used only for displaying gained TP
// and for "exit dilation" button (saying whether you need more antimatter)
// Returns the amount of TP gained, subtracting out current TP; used for displaying gained TP, text on the
// "exit dilation" button (saying whether you need more antimatter), and in last 10 eternities
export function getTachyonGain(requireEternity) {
return getTP(Currency.antimatter.value, requireEternity).minus(Currency.tachyonParticles.value).clampMin(0);
}

View File

@ -270,7 +270,7 @@ function buyUntilTen(tier) {
}
export function maxAll() {
if (Laitela.continuumActive || Currency.antimatter.gt(Player.infinityLimit)) return;
if (Laitela.continuumActive) return;
player.requirementChecks.infinity.maxAll = true;
@ -297,7 +297,8 @@ export function buyMaxDimension(tier, bulk = Infinity) {
}
// Buy any remaining until 10 before attempting to bulk-buy
if (Currency.antimatter.purchase(cost)) {
if (dimension.currencyAmount.gte(cost)) {
dimension.currencyAmount = dimension.currencyAmount.minus(cost);
buyUntilTen(tier);
bulkLeft--;
}
@ -307,7 +308,9 @@ export function buyMaxDimension(tier, bulk = Infinity) {
// Buy in a while loop in order to properly trigger abnormal price increases
if (NormalChallenge(9).isRunning || InfinityChallenge(5).isRunning) {
while (dimension.isAffordableUntil10 && dimension.cost.lt(goal) && bulkLeft > 0) {
Currency.antimatter.subtract(dimension.costUntil10);
// We can use dimension.currencyAmount or Currency.antimatter here, they're the same,
// but it seems safest to use dimension.currencyAmount for consistency.
dimension.currencyAmount = dimension.currencyAmount.minus(dimension.costUntil10);
buyUntilTen(tier);
bulkLeft--;
}
@ -328,11 +331,6 @@ export function buyMaxDimension(tier, bulk = Infinity) {
dimension.currencyAmount = dimension.currencyAmount.minus(Decimal.pow10(maxBought.logPrice));
}
export function canAfford(cost) {
return (player.break || cost.lt(Decimal.NUMBER_MAX_VALUE)) && Currency.antimatter.gte(cost);
}
class AntimatterDimensionState extends DimensionState {
constructor(tier) {
super(() => player.dimensions.antimatter, tier);
@ -477,7 +475,10 @@ class AntimatterDimensionState extends DimensionState {
// Nameless limits dim 8 purchases to 1 only
// Continuum should be no different
if (this.tier === 8 && Enslaved.isRunning) return 1;
return this.costScale.getContinuumValue(Currency.antimatter.value, 10) * Laitela.matterExtraPurchaseFactor;
// It's safe to use dimension.currencyAmount because this is
// a dimension-only method (so don't just copy it over to tickspeed).
// We need to use dimension.currencyAmount here because of different costs in NC6.
return this.costScale.getContinuumValue(this.currencyAmount, 10) * Laitela.matterExtraPurchaseFactor;
}
/**
@ -524,7 +525,6 @@ class AntimatterDimensionState extends DimensionState {
}
get isAvailableForPurchase() {
if (Currency.antimatter.gt(Player.infinityLimit)) return false;
if (!EternityMilestone.unlockAllND.isReached && this.tier > DimBoost.totalBoosts + 4) return false;
const hasPrevTier = this.tier === 1 || AntimatterDimension(this.tier - 1).totalAmount.gt(0);
if (!EternityMilestone.unlockAllND.isReached && !hasPrevTier) return false;

View File

@ -77,7 +77,7 @@ export function eternity(force, auto, specialConditions = {}) {
if (!force) {
if (!Player.canEternity) return false;
EventHub.dispatch(GAME_EVENT.ETERNITY_RESET_BEFORE);
if (!player.dilation.active) giveEternityRewards(auto);
giveEternityRewards(auto);
player.requirementChecks.reality.noEternities = false;
}
@ -125,7 +125,7 @@ export function eternity(force, auto, specialConditions = {}) {
resetTickspeed();
playerInfinityUpgradesOnReset();
AchievementTimers.marathon2.reset();
applyRealityUpgradesAfterEternity();
applyEU1();
player.records.thisInfinity.maxAM = DC.D0;
player.records.thisEternity.maxAM = DC.D0;
Currency.antimatter.reset();
@ -137,22 +137,28 @@ export function eternity(force, auto, specialConditions = {}) {
return true;
}
export function animateAndEternity() {
if (!Player.canEternity) return;
// eslint-disable-next-line no-empty-function
export function animateAndEternity(callback) {
if (!Player.canEternity) return false;
const hasAnimation = !FullScreenAnimationHandler.isDisplaying &&
((player.dilation.active && player.options.animations.dilation) ||
(!player.dilation.active && player.options.animations.eternity));
if (hasAnimation) {
if (player.dilation.active) {
animateAndUndilate();
animateAndUndilate(callback);
} else {
eternityAnimation();
setTimeout(eternity, 2250);
setTimeout(() => {
eternity();
if (callback) callback();
}, 2250);
}
} else {
eternity();
if (callback) callback();
}
return hasAnimation;
}
export function initializeChallengeCompletions(isReality) {
@ -189,12 +195,23 @@ export function initializeResourcesAfterEternity() {
Player.resetRequirements("eternity");
}
function applyRealityUpgradesAfterEternity() {
export function applyEU1() {
if (player.eternityUpgrades.size < 3 && Perk.autounlockEU1.canBeApplied) {
for (const id of [1, 2, 3]) player.eternityUpgrades.add(id);
}
}
// We want this to be checked before any EP-related autobuyers trigger, but we need to call this from the autobuyer
// code since those run asynchronously from gameLoop
export function applyEU2() {
if (player.eternityUpgrades.size < 6 && Perk.autounlockEU2.canBeApplied) {
const secondRow = EternityUpgrade.all.filter(u => u.id > 3);
for (const upgrade of secondRow) {
if (player.eternityPoints.gte(upgrade.cost / 1e10)) player.eternityUpgrades.add(upgrade.id);
}
}
}
function askEternityConfirmation() {
if (player.dilation.active && player.options.confirmations.dilation) {
Modal.exitDilation.show();

View File

@ -139,7 +139,7 @@ export class EternityChallengeState extends GameMechanicState {
}
get isGoalReached() {
return Currency.infinityPoints.gte(this.currentGoal);
return player.records.thisEternity.maxIP.gte(this.currentGoal);
}
get canBeCompleted() {
@ -188,10 +188,11 @@ export class EternityChallengeState extends GameMechanicState {
// If dilation is active, the { enteringEC: true } parameter will cause
// dilation to not be disabled. We still don't force-eternity, though;
// this causes TP to still be gained.
const enteringGamespeed = getGameSpeedupFactor();
if (Player.canEternity) eternity(false, auto, { enteringEC: true });
player.challenge.eternity.current = this.id;
if (this.id === 12) {
if (player.requirementChecks.reality.slowestBH < 1) {
if (enteringGamespeed < 0.001) {
SecretAchievement(42).unlock();
}
player.requirementChecks.reality.slowestBH = 1;
@ -310,7 +311,10 @@ export const EternityChallenges = {
autoComplete: {
tick() {
if (!player.reality.autoEC || Pelle.isDisabled("autoec")) return;
if (!player.reality.autoEC || Pelle.isDisabled("autoec")) {
player.reality.lastAutoEC = Math.clampMax(player.reality.lastAutoEC, this.interval);
return;
}
if (Ra.unlocks.instantECAndRealityUpgradeAutobuyers.canBeApplied) {
let next = this.nextChallenge;
while (next !== undefined) {

View File

@ -136,7 +136,7 @@ export function manualRequestGalaxyReset(bulk) {
if (!Galaxy.canBeBought || !Galaxy.requirement.isSatisfied) return;
if (GameEnd.creditsEverClosed) return;
if (player.options.confirmations.antimatterGalaxy) {
Modal.antimatterGalaxy.show({ bulk });
Modal.antimatterGalaxy.show({ bulk: bulk && EternityMilestone.autobuyMaxGalaxies.isReached });
return;
}
requestGalaxyReset(bulk);

View File

@ -40,6 +40,9 @@ export const Glyphs = {
get activeList() {
return player.reality.glyphs.active;
},
get activeWithoutCompanion() {
return this.activeList.filter(g => g.type !== "companion");
},
get allGlyphs() {
return this.inventoryList.concat(this.activeList);
},
@ -292,7 +295,7 @@ export const Glyphs = {
this.addToInventory(glyph, freeIndex, true);
}
this.updateRealityGlyphEffects();
this.updateMaxGlyphCount();
this.updateMaxGlyphCount(true);
EventHub.dispatch(GAME_EVENT.GLYPHS_EQUIPPED_CHANGED);
EventHub.dispatch(GAME_EVENT.GLYPHS_CHANGED);
return !player.reality.glyphs.active.length;
@ -305,7 +308,7 @@ export const Glyphs = {
this.active[activeIndex] = null;
this.addToInventory(glyph, requestedInventoryIndex, true);
this.updateRealityGlyphEffects();
this.updateMaxGlyphCount();
this.updateMaxGlyphCount(true);
EventHub.dispatch(GAME_EVENT.GLYPHS_EQUIPPED_CHANGED);
EventHub.dispatch(GAME_EVENT.GLYPHS_CHANGED);
},
@ -445,11 +448,14 @@ export const Glyphs = {
}
if (player.reality.autoCollapse) this.collapseEmptySlots();
},
sortByLevel() {
this.sort((a, b) => b.level - a.level);
},
sortByPower() {
this.sort((a, b) => -a.level * a.strength + b.level * b.strength);
this.sort((a, b) => b.level * b.strength - a.level * a.strength);
},
sortByScore() {
this.sort((a, b) => -AutoGlyphProcessor.filterValue(a) + AutoGlyphProcessor.filterValue(b));
this.sort((a, b) => AutoGlyphProcessor.filterValue(b) - AutoGlyphProcessor.filterValue(a));
},
sortByEffect() {
function reverseBitstring(eff) {
@ -457,7 +463,7 @@ export const Glyphs = {
}
// The bitwise reversal is so that the effects with the LOWER id are valued higher in the sorting.
// This primarily meant for effarig glyph effect sorting, which makes it prioritize timespeed pow highest.
this.sort((a, b) => -reverseBitstring(a.effects) + reverseBitstring(b.effects));
this.sort((a, b) => reverseBitstring(b.effects) - reverseBitstring(a.effects));
},
// If there are enough glyphs that are better than the specified glyph, in every way, then
// the glyph is objectively a useless piece of garbage.
@ -540,6 +546,9 @@ export const Glyphs = {
switch (player.reality.autoSort) {
case AUTO_SORT_MODE.NONE:
break;
case AUTO_SORT_MODE.LEVEL:
this.sortByLevel();
break;
case AUTO_SORT_MODE.POWER:
this.sortByPower();
break;
@ -637,7 +646,7 @@ export const Glyphs = {
// Normal glyph count minus 3 for each cursed glyph, uses 4 instead of 3 in the calculation because cursed glyphs
// still contribute to the length of the active list. Note that it only ever decreases if startingReality is true.
updateMaxGlyphCount(startingReality = false) {
const activeGlyphList = this.activeList;
const activeGlyphList = this.activeWithoutCompanion;
const currCount = activeGlyphList.length - 4 * activeGlyphList.filter(x => x && x.type === "cursed").length;
if (startingReality) player.requirementChecks.reality.maxGlyphs = currCount;
player.requirementChecks.reality.maxGlyphs = Math.max(player.requirementChecks.reality.maxGlyphs, currCount);
@ -664,7 +673,7 @@ export const Glyphs = {
this.active[targetSlot] = glyph;
glyph.idx = targetSlot;
this.updateRealityGlyphEffects();
this.updateMaxGlyphCount();
this.updateMaxGlyphCount(true);
EventHub.dispatch(GAME_EVENT.GLYPHS_EQUIPPED_CHANGED);
EventHub.dispatch(GAME_EVENT.GLYPHS_CHANGED);
this.validate();

View File

@ -321,9 +321,11 @@ GameKeyboard.bind(
// Toggle autobuyers
function toggleAutobuyer(buyer) {
// Autobuyer.tickspeed.isUnlocked is false without NC9, but we still want the simpler one to be togglable via hotkey
const isSimpleTickspeed = buyer === Autobuyer.tickspeed && buyer.isBought;
if (buyer.disabledByContinuum) {
GameUI.notify.info("Continuum is enabled, you cannot alter this autobuyer");
} else if (buyer.isUnlocked) {
} else if (buyer.isUnlocked || isSimpleTickspeed) {
buyer.toggle();
GameUI.notify.info(`${buyer.name} Autobuyer toggled ${(buyer.isActive) ? "on" : "off"}`);
}

View File

@ -59,7 +59,7 @@ export const GameIntervals = (function() {
),
checkCloudSave: interval(() => {
if (player.options.cloudEnabled && Cloud.loggedIn) Cloud.saveCheck();
}, 300000),
}, 600 * 1000),
randomSecretAchievement: interval(() => {
if (Math.random() < 0.00001) SecretAchievement(18).unlock();
}, 1000),

View File

@ -514,6 +514,16 @@ window.logFactorial = (function() {
};
}());
window.exp1m = function(x) {
if (x.abs().gte(0.001)) {
return x.exp().minus(1);
}
// This sum contains all the terms that are relevant for |x| < 0.001. We could do some sort of loop
// (add terms as long as they matter) but that probably has a greater fixed overhead, and we don't
// call this enough for efficiency to be very worrying anyway.
return x.plus(x.pow(2).div(2)).plus(x.pow(3).div(6)).plus(x.pow(4).div(24)).plus(x.pow(5).div(120));
};
/** 32 bit XORSHIFT generator */
window.xorshift32Update = function xorshift32Update(state) {
/* eslint-disable no-param-reassign */

View File

@ -18,17 +18,22 @@ export const NG = {
const automatorConstants = JSON.stringify(player.reality.automator.constants);
const automatorScripts = JSON.stringify(player.reality.automator.scripts);
const fullCompletions = player.records.fullGameCompletions;
const fullTimePlayed = player.records.previousRunRealTime + player.records.realTimePlayed;
GlyphAppearanceHandler.unlockSet();
const glyphCosmetics = JSON.stringify(player.reality.glyphs.cosmetics);
Modal.hideAll();
Quote.clearAll();
GameStorage.hardReset();
player.options = JSON.parse(backUpOptions);
// We need to force this one to be true because otherwise the player will be unable to select their glyphs
// until they can auto-reality
player.options.confirmations.glyphSelection = true;
player.secretUnlocks = secretUnlocks;
player.secretAchievementBits = JSON.parse(secretAchievements);
player.reality.automator.constants = JSON.parse(automatorConstants);
player.reality.automator.scripts = JSON.parse(automatorScripts);
player.records.fullGameCompletions = fullCompletions + 1;
player.records.previousRunRealTime = fullTimePlayed;
ui.view.newUI = player.options.newUI;
ui.view.news = player.options.news.enabled;
player.reality.glyphs.cosmetics = JSON.parse(glyphCosmetics);

View File

@ -23,6 +23,7 @@ export const NewsHandler = {
// we pad the array with zeroes until we can fit the new ID in before actually adding it.
while (this.BITS_PER_MASK * player.news.seen[type].length <= number) player.news.seen[type].push(0);
player.news.seen[type][Math.floor(number / this.BITS_PER_MASK)] |= 1 << (number % this.BITS_PER_MASK);
player.news.totalSeen++;
},
hasSeenNews(id) {

View File

@ -47,6 +47,13 @@ class PerkState extends SetPurchasableMechanicState {
onPurchased() {
if (this.config.bumpCurrency !== undefined) this.config.bumpCurrency();
if (this.label === "EU1" && Currency.eternities.gt(0)) applyEU1();
if (this.label === "ACHNR") {
if (Achievements.preReality.some(a => !a.isUnlocked)) player.reality.gainedAutoAchievements = true;
for (const achievement of Achievements.preReality) {
achievement.unlock(true);
}
}
GameCache.achievementPeriod.invalidate();
GameCache.buyablePerks.invalidate();
EventHub.dispatch(GAME_EVENT.PERK_BOUGHT);

View File

@ -62,6 +62,8 @@ window.player = {
mode: 0,
rm: DC.D1,
glyph: 0,
time: 0,
shard: 0,
isActive: false
},
eternity: {
@ -271,19 +273,21 @@ window.player = {
realTimePlayed: 0,
realTimeDoomed: 0,
fullGameCompletions: 0,
previousRunRealTime: 0,
totalAntimatter: DC.E1,
lastTenInfinities: Array.range(0, 10).map(() =>
[Number.MAX_VALUE, DC.D1, DC.D1, Number.MAX_VALUE]),
lastTenEternities: Array.range(0, 10).map(() =>
[Number.MAX_VALUE, DC.D1, DC.D1, Number.MAX_VALUE]),
lastTenRealities: Array.range(0, 10).map(() =>
[Number.MAX_VALUE, DC.D1, 1, Number.MAX_VALUE, 0]),
recentInfinities: Array.range(0, 10).map(() =>
[Number.MAX_VALUE, Number.MAX_VALUE, DC.D1, DC.D1, ""]),
recentEternities: Array.range(0, 10).map(() =>
[Number.MAX_VALUE, Number.MAX_VALUE, DC.D1, DC.D1, "", DC.D0]),
recentRealities: Array.range(0, 10).map(() =>
[Number.MAX_VALUE, Number.MAX_VALUE, DC.D1, 1, "", 0, 0]),
thisInfinity: {
time: 0,
realTime: 0,
lastBuyTime: 0,
maxAM: DC.D0,
bestIPmin: DC.D0,
bestIPminVal: DC.D0,
},
bestInfinity: {
time: Number.MAX_VALUE,
@ -298,6 +302,7 @@ window.player = {
maxIP: DC.D0,
bestIPMsWithoutMaxAll: DC.D0,
bestEPmin: DC.D0,
bestEPminVal: DC.D0,
bestInfinitiesPerMs: DC.D0,
},
bestEternity: {
@ -314,6 +319,8 @@ window.player = {
bestEternitiesPerMs: DC.D0,
maxReplicanti: DC.D0,
maxDT: DC.D0,
bestRSmin: 0,
bestRSminVal: 0,
},
bestReality: {
time: Number.MAX_VALUE,
@ -521,7 +528,7 @@ window.player = {
},
constants: {},
execTimer: 0,
type: AUTOMATOR_TYPE.BLOCK,
type: AUTOMATOR_TYPE.TEXT,
forceUnlock: false,
currentInfoPane: AutomatorPanels.INTRO_PAGE,
},
@ -584,7 +591,6 @@ window.player = {
storedReal: 0,
autoStoreReal: false,
isAutoReleasing: false,
storedFraction: 1,
quoteBits: 0,
unlocks: [],
run: false,
@ -779,6 +785,7 @@ window.player = {
retryCelestial: false,
showAllChallenges: false,
cloudEnabled: true,
hideGoogleName: false,
showCloudModal: true,
forceCloudOverwrite: false,
syncSaveIntervals: true,
@ -791,8 +798,8 @@ window.player = {
offlineProgress: true,
automaticTabSwitching: true,
respecIntoProtected: false,
offlineTicks: 1000,
showLastTenResourceGain: true,
offlineTicks: 1e5,
showRecentRate: true,
autosaveInterval: 30000,
showTimeSinceSave: true,
saveFileName: "",
@ -888,7 +895,8 @@ window.player = {
maxEntries: 200,
clearOnReality: true,
clearOnRestart: true,
}
},
invertTTgenDisplay: false,
},
IAP: {
enabled: false,

View File

@ -340,6 +340,30 @@ export function beginProcessReality(realityProps) {
// Do this before processing glyphs so that we don't try to reality again while async is running.
finishProcessReality(realityProps);
// If we have less than a certain amount of simulated realities, then we just shortcut the heavier async and
// sampling code in order to just directly give all the glyphs. The later code is a fixed amount of overhead
// which is large enough that quick realities can cause it to softlock the game due to lag on slower devices
// Note: This is mostly a copy-paste of a code block in processManualReality() with slight modifications
if (glyphsToProcess < 100) {
for (let glyphNum = 0; glyphNum < glyphsToProcess; glyphNum++) {
if (EffarigUnlock.glyphFilter.isUnlocked) {
const glyphChoices = GlyphSelection.glyphList(GlyphSelection.choiceCount,
realityProps.gainedGlyphLevel, { rng });
const newGlyph = AutoGlyphProcessor.pick(glyphChoices);
if (!AutoGlyphProcessor.wouldKeep(newGlyph) || GameCache.glyphInventorySpace.value === 0) {
AutoGlyphProcessor.getRidOfGlyph(newGlyph);
} else {
Glyphs.addToInventory(newGlyph);
}
} else {
GlyphSelection.select(Math.floor(Math.random() * GlyphSelection.choiceCount), false);
}
}
rng.finalize();
Glyphs.processSortingAfterReality();
return;
}
// We need these variables in this scope in order to modify the behavior of the Async loop while it's running
const progress = {};
let fastToggle = false;
@ -409,7 +433,7 @@ export function beginProcessReality(realityProps) {
if (VUnlocks.autoAutoClean.canBeApplied && player.reality.autoAutoClean) Glyphs.autoClean();
}
};
const glyphsToSample = 10000;
const glyphsToSample = Math.min(glyphsToProcess, 10000);
Async.run(glyphFunction,
glyphsToProcess,
{
@ -669,6 +693,10 @@ export function finishProcessReality(realityProps) {
if (TeresaUnlocks.startEU.canBeApplied) {
for (const id of [1, 2, 3, 4, 5, 6]) player.eternityUpgrades.add(id);
} else if (RealityUpgrade(14).isBought) {
// Eternal flow will always give eternities after the first tick,
// better to try apply EU1 immediately once at the start rather than on every tick
applyEU1();
}
if (!isReset) Ra.applyAlchemyReactions(realityRealTime);
@ -729,6 +757,8 @@ export function applyRUPG10() {
Currency.eternities.bumpTo(100);
Replicanti.amount = Replicanti.amount.clampMin(1);
Replicanti.unlock(true);
applyEU1();
}
export function clearCelestialRuns() {

View File

@ -7,6 +7,7 @@ export const ReplicantiGrowth = {
return Math.log10(Number.MAX_VALUE);
},
get scaleFactor() {
if (PelleStrikes.eternity.hasStrike && Replicanti.amount.gte(DC.E2000)) return 10;
if (Pelle.isDoomed) return 2;
return AlchemyResource.cardinality.effectValue;
}
@ -89,12 +90,13 @@ export function getReplicantiInterval(overCapOverride, intervalIn) {
}
if (overCap) {
const increases = (amount.log10() - replicantiCap().log10()) / ReplicantiGrowth.scaleLog10;
interval = interval.times(Decimal.pow(ReplicantiGrowth.scaleFactor, increases));
if (PelleStrikes.eternity.hasStrike && amount.e > 2000) {
const pelleIncreases = (amount.log10() - 2000) / ReplicantiGrowth.scaleLog10;
interval = interval.times(Decimal.pow(5, pelleIncreases));
let increases = (amount.log10() - replicantiCap().log10()) / ReplicantiGrowth.scaleLog10;
if (PelleStrikes.eternity.hasStrike && amount.gte(DC.E2000)) {
// The above code assumes in this case there's 10x scaling for every 1e308 increase;
// in fact, before e2000 it's only 2x.
increases -= Math.log10(5) * (2000 - replicantiCap().log10()) / ReplicantiGrowth.scaleLog10;
}
interval = interval.times(Decimal.pow(ReplicantiGrowth.scaleFactor, increases));
}
interval = interval.div(PelleRifts.decay.effectValue);
@ -180,6 +182,10 @@ export function replicantiLoop(diff) {
if (isUncapped && Replicanti.amount.gte(replicantiCap()) && remainingGain.gt(0)) {
// Recalculate the interval (it may have increased due to additional replicanti, or,
// far less importantly, decreased due to Reality Upgrade 6 and additional RG).
// Don't worry here about the lack of e2000 scaling in Pelle on the first tick
// (with replicanti still under e2000) causing a huge replicanti jump;
// there's code later to stop replicanti from increasing by more than e308
// in a single tick in Pelle.
const intervalRatio = getReplicantiInterval(true).div(interval);
remainingGain = remainingGain.div(intervalRatio);
Replicanti.amount =

View File

@ -377,10 +377,11 @@ GameDatabase.achievements.normal = [
name: "Bulked Up",
get description() {
return `Get all of your Antimatter Dimension Autobuyer bulk amounts to
${formatInt(Autobuyer.antimatterDimension.bulkCap)} or higher.`;
${formatInt(Autobuyer.antimatterDimension.bulkCap)}.`;
},
checkRequirement: () => Autobuyer.antimatterDimension.zeroIndexed.every(x => x.hasMaxedBulk),
checkEvent: [GAME_EVENT.REALITY_RESET_AFTER, GAME_EVENT.REALITY_UPGRADE_TEN_BOUGHT],
checkEvent: [GAME_EVENT.REALITY_RESET_AFTER, GAME_EVENT.REALITY_UPGRADE_TEN_BOUGHT,
GAME_EVENT.SAVE_CONVERTED_FROM_PREVIOUS_VERSION],
reward: "Dimension Autobuyer bulks are unlimited."
},
{
@ -712,7 +713,7 @@ GameDatabase.achievements.normal = [
checkRequirement: () => Currency.infinityPoints.exponent >= 1000,
checkEvent: GAME_EVENT.GAME_TICK_AFTER,
get reward() {
return `Make the Infinity Point formula better. log(x/${formatInt(308)}) ➜ log(x/${formatFloat(307.8, 1)})`;
return `Make the Infinity Point formula better. log(x)/${formatInt(308)} ➜ log(x)/${formatFloat(307.8, 1)}`;
},
effect: 307.8
},
@ -764,8 +765,8 @@ GameDatabase.achievements.normal = [
${format(Decimal.NUMBER_MAX_VALUE, 1, 0)} times higher Infinity Points than the previous one.`;
},
checkRequirement: () => {
if (player.records.lastTenInfinities.some(i => i[0] === Number.MAX_VALUE)) return false;
const infinities = player.records.lastTenInfinities.map(run => run[1]);
if (player.records.recentInfinities.some(i => i[0] === Number.MAX_VALUE)) return false;
const infinities = player.records.recentInfinities.map(run => run[2]);
for (let i = 0; i < infinities.length - 1; i++) {
if (infinities[i].lt(infinities[i + 1].times(Decimal.NUMBER_MAX_VALUE))) return false;
}
@ -932,8 +933,8 @@ GameDatabase.achievements.normal = [
id: 132,
name: "Unique snowflakes",
get description() {
return `Have ${formatInt(569)} Antimatter Galaxies without getting any
Replicanti Galaxies in your current Eternity.`;
return `Have ${formatInt(569)} Antimatter Galaxies without gaining any
Replicanti Galaxies in your current Eternity.`;
},
checkRequirement: () => player.galaxies >= 569 && player.requirementChecks.eternity.noRG,
checkEvent: GAME_EVENT.GALAXY_RESET_AFTER,
@ -1040,8 +1041,8 @@ GameDatabase.achievements.normal = [
${format(Decimal.NUMBER_MAX_VALUE, 1, 0)} times higher Eternity Points than the previous one.`;
},
checkRequirement: () => {
if (player.records.lastTenEternities.some(i => i[0] === Number.MAX_VALUE)) return false;
const eternities = player.records.lastTenEternities.map(run => run[1]);
if (player.records.recentEternities.some(i => i[0] === Number.MAX_VALUE)) return false;
const eternities = player.records.recentEternities.map(run => run[2]);
for (let i = 0; i < eternities.length - 1; i++) {
if (eternities[i].lt(eternities[i + 1].times(Decimal.NUMBER_MAX_VALUE))) return false;
}
@ -1089,7 +1090,7 @@ GameDatabase.achievements.normal = [
.every(type => Glyphs.activeList.some(g => g.type === type)),
checkEvent: GAME_EVENT.REALITY_RESET_BEFORE,
reward: "Gained Glyph level is increased by number of distinct Glyph types equipped.",
effect: () => (new Set(Glyphs.activeList.map(g => g.type))).size,
effect: () => (new Set(Glyphs.activeWithoutCompanion.map(g => g.type))).size,
formatEffect: value => `+${formatInt(value)}`
},
{
@ -1141,7 +1142,7 @@ GameDatabase.achievements.normal = [
description: "Reality without buying Time Theorems.",
checkRequirement: () => player.requirementChecks.reality.noPurchasedTT,
checkEvent: GAME_EVENT.REALITY_RESET_BEFORE,
get reward() { return `Gain ${formatX(2.5, 0, 1)} Time Theorems, and a free coupon to McDonalds™.`; },
get reward() { return `Gain ${formatX(2.5, 0, 1)} generated Time Theorems, and a free coupon to McDonalds™.`; },
effect: 2.5
},
{
@ -1252,7 +1253,7 @@ GameDatabase.achievements.normal = [
any Charged Infinity Upgrades, having any equipped Glyphs, or buying any Triad Studies.`;
},
checkRequirement: () => MachineHandler.gainedRealityMachines.gte(Decimal.NUMBER_MAX_VALUE) &&
player.celestials.ra.charged.size === 0 && Glyphs.activeList.length === 0 &&
player.celestials.ra.charged.size === 0 && Glyphs.activeWithoutCompanion.length === 0 &&
player.requirementChecks.reality.noTriads,
checkEvent: GAME_EVENT.REALITY_RESET_BEFORE,
},

View File

@ -115,7 +115,7 @@ GameDatabase.achievements.secret = [
checkRequirement: () =>
Time.bestInfinity.totalMilliseconds <= 1 ||
Time.bestEternity.totalMilliseconds <= 1,
checkEvent: [GAME_EVENT.BIG_CRUNCH_BEFORE, GAME_EVENT.ETERNITY_RESET_BEFORE]
checkEvent: [GAME_EVENT.BIG_CRUNCH_AFTER, GAME_EVENT.ETERNITY_RESET_AFTER]
},
{
id: 33,

View File

@ -97,6 +97,7 @@ GameDatabase.awayProgressTypes = [
showOption: false,
}, {
name: "enslavedMemories",
forcedName: "Nameless Memories",
awayOption: "celestialMemories",
reference: ["celestials", "ra", "pets", "enslaved", "memories"],
isUnlocked: () => Ra.pets.enslaved.isUnlocked && !Ra.pets.enslaved.isCapped,

View File

@ -104,7 +104,7 @@ GameDatabase.catchupResources = [
id: 13,
openH2pEntry: "Eternity",
requiredStage: PROGRESS_STAGE.EARLY_ETERNITY,
description: `Infinity Points are the primary resource after completing your first Eternity, and scale based on your
description: `Eternity Points are the primary resource after completing your first Eternity, and scale based on your
Infinity Points at the time you complete the Eternity.`
},
{

View File

@ -235,7 +235,7 @@ GameDatabase.celestials.alchemy.resources = {
unlockedAt: 15,
description: "provides a power to all Dimensions that permanently grows over time",
formatEffect: value => `All Dimensions ${formatPow(Ra.momentumValue, 4, 4)}, increasing by
${format(0.002 * Achievement(175).effectOrDefault(1), 3, 3)}
${format(0.005 * Achievement(175).effectOrDefault(1), 3, 3)}
per real-time hour after the resource is unlocked, up to a maximum of ${formatPow(value, 4, 4)}`,
reagents: [
{

View File

@ -7,7 +7,7 @@ GameDatabase.celestials.enslaved = {
id: 0,
hint: "The Nameless Ones want to help, but the help takes a while.",
condition: () => `Spent more than ${formatInt(5)} real-time hours inside the Reality without completing it;
time outside the Reality counts for ${formatPercents(0.04)} as much. The timer starts once Nameless's
time outside the Reality counts for ${formatPercents(0.4)} as much. The timer starts once the
Reality is unlocked, but accumulates continuously.`,
},
ec1: {

View File

@ -140,7 +140,7 @@ function pelleRiftFill(name, index, textAngle, fillType) {
visibleCheck = () => riftFillStage(name) === FILL_STATE.FILL;
progressFn = () => Math.clamp(0.1 + PelleRifts[name.toLowerCase()].realPercentage / 0.9, 1e-6, 1);
legendFn = () => false;
percentFn = x => (x - 0.1) / 0.9;
percentFn = () => PelleRifts[name.toLowerCase()].realPercentage;
incompleteClass = "c-celestial-nav__test-incomplete";
nodeFill = "crimson";
connectorFill = "crimson";
@ -153,7 +153,7 @@ function pelleRiftFill(name, index, textAngle, fillType) {
visibleCheck = () => riftFillStage(name) >= FILL_STATE.DRAIN;
progressFn = () => Math.clamp(Math.sqrt(PelleRifts[name.toLowerCase()].reducedTo), 1e-6, 1);
legendFn = () => riftFillStage(name) === FILL_STATE.DRAIN && PelleRifts[name.toLowerCase()].reducedTo < 1;
percentFn = x => x;
percentFn = () => PelleRifts[name.toLowerCase()].reducedTo;
incompleteClass = "c-celestial-nav__drained-rift";
nodeFill = "crimson";
connectorFill = "#550919";
@ -161,7 +161,7 @@ function pelleRiftFill(name, index, textAngle, fillType) {
case FILL_STATE.OVERFILL:
visibleCheck = () => riftFillStage(name) === FILL_STATE.OVERFILL;
progressFn = () => Math.clamp(PelleRifts[name.toLowerCase()].percentage - 1, 1e-6, 1);
percentFn = x => x + 1;
percentFn = () => PelleRifts[name.toLowerCase()].percentage;
legendFn = () => true;
incompleteClass = undefined;
nodeFill = "#ff7700";
@ -182,8 +182,8 @@ function pelleRiftFill(name, index, textAngle, fillType) {
},
forceLegend: () => legendFn(),
legend: {
text: complete => [
`${formatPercents(percentFn(complete), 1)} ${wordShift.wordCycle(PelleRifts[name.toLowerCase()].name)}`
text: () => [
`${formatPercents(percentFn(), 1)} ${wordShift.wordCycle(PelleRifts[name.toLowerCase()].name)}`
],
angle: textAngle,
diagonal: 30,
@ -586,7 +586,7 @@ GameDatabase.celestials.navigation = {
},
legend: {
text: complete => {
if (complete >= 1) return "Broken the chain with Glyph level";
if (complete >= 1) return "Glyph level chain has been broken";
const goal = 5000;
return [
"Break a chain",
@ -631,7 +631,7 @@ GameDatabase.celestials.navigation = {
},
legend: {
text: complete => {
if (complete >= 1) return "Broken the chain with Glyph rarity";
if (complete >= 1) return "Glyph rarity chain has been broken";
const goal = 100;
return [
"Break a chain",
@ -1527,8 +1527,8 @@ GameDatabase.celestials.navigation = {
if (upgrade.isAvailableForPurchase) return [
dmdText,
`Imaginary Machines
${format(Math.min(upgrade.currency.value, upgrade.cost), upgrade.canBeBought ? 0 : 2)}
/ ${format(upgrade.cost)}`
${format(Math.min(upgrade.currency.value, upgrade.cost), upgrade.canBeBought ? 1 : 2)}
/ ${format(upgrade.cost, 1)}`
];
if (player.celestials.laitela.fastestCompletion > 30 && Laitela.difficultyTier < 0) return [
@ -1814,7 +1814,7 @@ GameDatabase.celestials.navigation = {
if (Pelle.isUnlocked) return 1;
const imCost = Math.clampMax(emphasizeEnd(Math.log10(Currency.imaginaryMachines.value) / Math.log10(1.6e15)), 1);
let laitelaProgress = Laitela.isRunning ? Math.min(Currency.eternityPoints.value.log10() / 4000, 0.99) : 0;
if (Laitela.difficultyTier !== 8 || Glyphs.activeList.length > 1) laitelaProgress = 0;
if (Laitela.difficultyTier !== 8 || Glyphs.activeWithoutCompanion.length > 1) laitelaProgress = 0;
else if (ImaginaryUpgrade(25).isAvailableForPurchase) laitelaProgress = 1;
return (imCost + laitelaProgress) / 2;
},
@ -1835,7 +1835,7 @@ GameDatabase.celestials.navigation = {
];
}
let laitelaString = `${format(Currency.eternityPoints.value)} / ${format("1e4000")} EP`;
if (!Laitela.isRunning || Laitela.difficultyTier !== 8 || Glyphs.activeList.length > 1) {
if (!Laitela.isRunning || Laitela.difficultyTier !== 8 || Glyphs.activeWithoutCompanion.length > 1) {
laitelaString = "Lai'tela's Reality is still intact";
} else if (ImaginaryUpgrade(25).isAvailableForPurchase) {
laitelaString = "Lai'tela's Reality has been destroyed";

View File

@ -184,10 +184,10 @@ GameDatabase.celestials.ra = {
level: 5,
displayIcon: `<span class="fas fa-stopwatch"></span>`
},
adjustableStoredTime: {
autoPulseTime: {
id: 17,
reward: () => `Black Hole charging can be done at an adjustable rate and automatically
pulsed every ${formatInt(5)} ticks. You can change these in the Black Hole and The Nameless Ones' tabs`,
reward: () => `Black Hole charging now only uses ${formatPercents(0.99)} of your game speed and you can
automatically discharge ${formatPercents(0.01)} of your stored game time every ${formatInt(5)} ticks.`,
pet: "enslaved",
level: 10,
displayIcon: `<span class="fas fa-expand-arrows-alt"></span>`,

View File

@ -174,7 +174,7 @@ GameDatabase.celestials.pelle.rifts = {
{
resource: "recursion",
requirement: 1,
description: "Unlock the Galaxy Generator",
description: "Permanently unlock the Galaxy Generator",
},
],
galaxyGeneratorText: "Creating more Galaxies is unsustainable, you must focus the $value to allow more"

View File

@ -282,7 +282,7 @@ GameDatabase.celestials.singularityMilestones = {
upgradeDirection: LAITELA_UPGRADE_DIRECTION.BOOSTS_MAIN,
},
tesseractMultFromSingularities: {
start: 8e45,
start: 2.5e45,
repeat: 0,
limit: 1,
description: "Singularities increase effective Tesseract count",

View File

@ -37,7 +37,7 @@ GameDatabase.celestials.pelle.strikes = {
id: 5,
requirementDescription: "Dilate Time",
penaltyDescription: "Time Dilation is permanently active",
rewardDescription: () => `Keep access to Time Dilation upgrades across Armageddon and unlock
rewardDescription: () => `Keep the Time Dilation study across Armageddon, boost Remnant gain, and unlock
${wordShift.wordCycle(PelleRifts.paradox.name)}`,
rift: () => PelleRifts.paradox
}

View File

@ -71,7 +71,7 @@ GameDatabase.celestials.v = {
// This achievement has internally negated values since the check is always greater than
values: [-5, -4, -3, -2, -1, 0],
condition: () => V.isRunning && TimeStudy.reality.isBought,
currentValue: () => -Glyphs.activeList.length,
currentValue: () => -Glyphs.activeWithoutCompanion.length,
formatRecord: x => (x >= -5 ? formatInt(-x) : "Not reached"),
shardReduction: () => 0,
maxShardReduction: () => 0,
@ -230,7 +230,7 @@ GameDatabase.celestials.v = {
},
autoAutoClean: {
id: 4,
reward: "Unlock the ability to Automatically Purge on Reality.",
reward: "Unlock the ability to Automatically Purge Glyphs on Reality.",
description: () => `Have ${formatInt(16)} V-Achievements`,
requirement: () => V.spaceTheorems >= 16
},

View File

@ -168,7 +168,7 @@ GameDatabase.challenges.eternity = [
id: 11,
description: () => `all Dimension multipliers and powers are disabled except for the multipliers from
Infinity Power and Dimension Boosts (to Antimatter Dimensions). ${specialInfinityGlyphDisabledEffectText()}`,
goal: DC.E500,
goal: DC.E450,
pelleGoal: DC.E11200,
goalIncrease: DC.E200,
pelleGoalIncrease: DC.E1400,

View File

@ -38,8 +38,7 @@ GameDatabase.challenges.infinity = [
effect: () => Decimal.pow(1.05 + (player.galaxies * 0.005), player.totalTickBought),
formatEffect: value => formatX(value, 2, 2),
reward: {
description: `Antimatter Dimension multiplier based on Antimatter Galaxies and Tickspeed purchases
(same as challenge multiplier)`,
description: `Antimatter Dimension multiplier based on Antimatter Galaxies and Tickspeed purchases`,
effect: () => (Laitela.continuumActive
? Decimal.pow(1.05 + (player.galaxies * 0.005), Tickspeed.continuumValue)
: Decimal.pow(1.05 + (player.galaxies * 0.005), player.totalTickBought)),
@ -64,8 +63,8 @@ GameDatabase.challenges.infinity = [
{
id: 5,
description:
`buying Antimatter Dimensions 1-4 causes all smaller Antimatter Dimension costs to increase.
Buying Antimatter Dimensions 5-8 causes all larger Antimatter Dimension costs to increase.`,
`buying Antimatter Dimensions 1-4 causes all cheaper AD costs to increase.
Buying Antimatter Dimensions 5-8 causes all more expensive AD costs to increase.`,
goal: DC.E16500,
isQuickResettable: true,
reward: {
@ -105,7 +104,7 @@ GameDatabase.challenges.infinity = [
TimeStudy(81)
);
return `you cannot buy Antimatter Galaxies. Base Dimension Boost multiplier is increased to a maximum
of ${formatX(10)}. (Current base multiplier: ${formatX(mult)})`;
of ${formatX(10)}. (Current base multiplier: ${formatX(mult, 2, 1)})`;
},
goal: DC.E10000,
isQuickResettable: false,

View File

@ -128,8 +128,8 @@ GameDatabase.challenges.normal = [
legacyId: 7,
isQuickResettable: false,
description: () => `each Antimatter Dimension produces the Dimension ${formatInt(2)} tiers below it
instead of ${formatInt(1)}. The 1st Dimension still produces antimatter, and the 2nd, 4th, and 6th
Dimensions are made stronger to compensate.`,
instead of ${formatInt(1)}. Both 1st and 2nd Dimensions produce antimatter.
The 2nd, 4th, and 6th Dimensions are made stronger to compensate.`,
name: "Automated Big Crunches",
reward: "Big Crunches Autobuyer",
lockedAt: DC.D16,

View File

@ -28,7 +28,7 @@ GameDatabase.changelog = [
<li>Added a How to Play modal with much more detail compared to the old How to Play.</li>
<li>Added 5 new rows of achievements.</li>
<li>Added a Multiplier Breakdown subtab.</li>
<li>Added more Nicholas Cage.</li>
<li>Added more Nicolas Cage.</li>
<li>Cloud saving is now available to everyone. This needs your Google account.</li>
<li>Shop tab is now available to everyone.</li>
<li>Redesigned overall UI styling.</li>
@ -136,6 +136,7 @@ Balance Changes:
<li>The 20-eternities milestone was moved to 8-eternities.</li>
<li>Increased cost scaling for Time Dimensions after 1e6000.</li>
<li>TS 83 has been hard capped.</li>
<li>EC10 reward for less than 5 completions has been nerfed (reward at 5 completions is the same).</li>
<li>Lowered the Dilation unlock requirement from 13000 to 12900 total TT.</li>
<li>TP gain amount in Dilation is now calculated based on the highest AM reached.</li>
<li>Purchasing the study to unlock Dilation now requires a 23rd row study purchase.</li>
@ -194,6 +195,7 @@ Removed features:
Bugfixes:
<ul>
<li>ID and replicanti autobuyer buttons are now hidden in EC8.</li>
<li>Fixed next Sacrifice multiplier not properly displaying NC8's effect.</li>
<li>Fixed a bug where IC5's cost increment was applied 2 times.</li>
<li>Fixed a bug where inverted themes were broken.</li>
<li>Fixed a bug where resetting the game unlocks a secret achievement.</li>

View File

@ -229,6 +229,9 @@ GameDatabase.credits = {
}, {
name: "Anthios",
roles: 13
}, {
name: "Aubrey",
roles: 13
}, {
name: "Auti",
name2: "Lucia Tolle",
@ -321,9 +324,6 @@ GameDatabase.credits = {
}, {
name: "Pavlxiiv",
roles: 13
}, {
name: "Porygon-Z",
roles: 13
}, {
name: "PotatoTIAB",
roles: 13

View File

@ -27,17 +27,19 @@ GameDatabase.eternity.milestones = {
eternities: 6,
reward: () => {
const EPmin = getOfflineEPGain(TimeSpan.fromMinutes(1).totalMilliseconds);
const em200 = getEternitiedMilestoneReward(TimeSpan.fromHours(1).totalMilliseconds, true).gt(0);
const em1000 = getInfinitiedMilestoneReward(TimeSpan.fromHours(1).totalMilliseconds, true).gt(0);
const em200 = getEternitiedMilestoneReward(TimeSpan.fromHours(1).totalMilliseconds,
EternityMilestone.autoEternities.isReached).gt(0);
const em1000 = getInfinitiedMilestoneReward(TimeSpan.fromHours(1).totalMilliseconds,
EternityMilestone.autoInfinities.isReached).gt(0);
if (!player.options.offlineProgress) return `This milestone would give offline EP generation, but offline progress
is currently disabled.`;
is currently disabled`;
const effectText = (em200 || em1000) ? "Inactive" : `Currently ${format(EPmin, 2, 2)} EP/min`;
return `While offline, gain ${formatPercents(0.25)} of your best Eternity Points per minute from previous
Eternities. (${effectText})`;
Eternities (${effectText})`;
},
activeCondition: () => (player.options.offlineProgress
? `Active as long as neither of the other offline milestones
(${formatInt(200)} or ${formatInt(1000)}) are also active.`
(${formatInt(200)} or ${formatInt(1000)}) are also active`
: ""),
},
autoIC: {
@ -130,34 +132,37 @@ GameDatabase.eternity.milestones = {
},
autobuyerEternity: {
eternities: 100,
reward: "Unlock autobuyer for Eternities."
reward: "Unlock autobuyer for Eternities"
},
autoEternities: {
eternities: 200,
reward: () => {
if (!player.options.offlineProgress) return `This milestone would generate eternities offline, but offline
progress is currently disabled.`;
const eternities = getEternitiedMilestoneReward(TimeSpan.fromHours(1).totalMilliseconds, true);
progress is currently disabled`;
const eternities = getEternitiedMilestoneReward(TimeSpan.fromHours(1).totalMilliseconds,
player.eternities.gte(200));
// As far as I can tell, using templates here as Codefactor wants would lead to nested templates,
// which seems messy to say the least.
const realTime = PlayerProgress.seenAlteredSpeed() ? " real-time" : "";
// eslint-disable-next-line prefer-template
return `While offline, gain Eternities at ${formatPercents(0.5)} the rate of your fastest Eternity. ` +
return `While offline, gain Eternities at ${formatPercents(0.5)} the rate of your fastest${realTime} Eternity ` +
(eternities.gt(0) ? `(Currently ${format(eternities, 2, 2)}/hour)` : "(Inactive)");
},
activeCondition: () => (player.options.offlineProgress
? `Must be outside of all Challenges and Dilation,
and the Eternity Autobuyer must be turned on and set to zero EP.`
? `Must be outside of all Challenges and Dilation, and the Eternity Autobuyer must be set to Eternity at zero EP.
This milestone's effect is capped at ${formatInt(33)}ms.`
: ""),
},
autoInfinities: {
eternities: 1000,
reward: () => {
if (!player.options.offlineProgress) return `This milestone would generate infinities offline, but offline
progress is currently disabled.`;
const infinities = getInfinitiedMilestoneReward(TimeSpan.fromHours(1).totalMilliseconds, true);
progress is currently disabled`;
const infinities = getInfinitiedMilestoneReward(TimeSpan.fromHours(1).totalMilliseconds,
player.eternities.gte(1000));
// eslint-disable-next-line prefer-template
return `While offline, gain Infinities equal to ${formatPercents(0.5)}
your best Infinities/hour this Eternity. ` +
your best Infinities/hour this Eternity ` +
(infinities.gt(0) ? `(Currently ${format(infinities, 2, 2)}/hour)` : "(Inactive)");
},
activeCondition: () => (player.options.offlineProgress

View File

@ -32,7 +32,9 @@ GameDatabase.eternity.upgrades = {
id: 3,
cost: 5e4,
description: "Infinity Dimensions multiplier based on sum of Infinity Challenge times",
effect: () => DC.D2.pow(30 / Time.infinityChallengeSum.totalSeconds),
// The cap limits this at a lower value, but we also need an explicit cap here because very old versions have
// allowed EC12 to make all the challenge records sum to zero (causing a division by zero here)
effect: () => DC.D2.pow(30 / Math.clampMin(Time.infinityChallengeSum.totalSeconds, 0.1)),
cap: DC.D2P30D0_61,
formatEffect: value => formatX(value, 2, 1)
},
@ -57,7 +59,7 @@ GameDatabase.eternity.upgrades = {
? "Time Dimensions are multiplied by days played in this Armageddon"
: "Time Dimensions are multiplied by days played"
),
effect: () => (Pelle.isDoomed ? 1 + Time.thisReality.totalDays : Time.totalTimePlayed.totalDays),
effect: () => (Pelle.isDoomed ? 1 + Time.thisReality.totalDays : Math.max(Time.totalTimePlayed.totalDays, 1)),
formatEffect: value => formatX(value, 2, 1)
}
};

View File

@ -10,7 +10,7 @@ GameDatabase.eternity.timeStudies.ec = [
secondary: {
resource: "Eternities",
current: () => Currency.eternities.value,
required: completions => new Decimal(20000 + completions * 20000),
required: completions => new Decimal(20000 + Math.min(completions, Enslaved.isRunning ? 999 : 4) * 20000),
formatValue: formatInt
}
},
@ -22,7 +22,7 @@ GameDatabase.eternity.timeStudies.ec = [
secondary: {
resource: "Tickspeed upgrades from Time Dimensions",
current: () => player.totalTickGained,
required: completions => 1300 + completions * 150,
required: completions => 1300 + Math.min(completions, 4) * 150,
formatValue: formatInt
}
},
@ -34,7 +34,7 @@ GameDatabase.eternity.timeStudies.ec = [
secondary: {
resource: "8th Antimatter Dimensions",
current: () => AntimatterDimension(8).totalAmount,
required: completions => new Decimal(17300 + completions * 1250),
required: completions => new Decimal(17300 + Math.min(completions, 4) * 1250),
formatValue: value => formatInt(Math.floor(value.toNumber()))
}
},
@ -46,7 +46,7 @@ GameDatabase.eternity.timeStudies.ec = [
secondary: {
resource: "Infinities",
current: () => Currency.infinitiesTotal.value,
required: completions => new Decimal(1e8 + completions * 5e7),
required: completions => new Decimal(1e8 + Math.min(completions, 4) * 2.5e7),
formatValue: value => formatInt(Math.floor(value.toNumber()))
}
},
@ -58,7 +58,7 @@ GameDatabase.eternity.timeStudies.ec = [
secondary: {
resource: "Antimatter Galaxies",
current: () => player.galaxies,
required: completions => 160 + completions * 14,
required: completions => 160 + Math.min(completions, 4) * 14,
formatValue: formatInt
}
},
@ -70,7 +70,7 @@ GameDatabase.eternity.timeStudies.ec = [
secondary: {
resource: "Replicanti Galaxies",
current: () => player.replicanti.galaxies,
required: completions => 40 + completions * 5,
required: completions => 40 + Math.min(completions, 4) * 5,
formatValue: formatInt
}
},
@ -82,7 +82,7 @@ GameDatabase.eternity.timeStudies.ec = [
secondary: {
resource: "antimatter",
current: () => Currency.antimatter.value,
required: completions => DC.E300000.pow(completions).times(DC.E500000),
required: completions => DC.E300000.pow(Math.min(completions, 4)).times(DC.E500000),
formatValue: value => format(value)
}
},
@ -94,7 +94,7 @@ GameDatabase.eternity.timeStudies.ec = [
secondary: {
resource: "Infinity Points",
current: () => Currency.infinityPoints.value,
required: completions => DC.E1000.pow(completions).times(DC.E4000),
required: completions => DC.E1000.pow(Math.min(completions, 4)).times(DC.E4000),
formatValue: value => format(value)
}
},
@ -106,7 +106,7 @@ GameDatabase.eternity.timeStudies.ec = [
secondary: {
resource: "Infinity Power",
current: () => Currency.infinityPower.value,
required: completions => DC.E2000.pow(completions).times(DC.E17500),
required: completions => DC.E2000.pow(Math.min(completions, 4)).times(DC.E17500),
formatValue: value => format(value)
}
},
@ -118,7 +118,7 @@ GameDatabase.eternity.timeStudies.ec = [
secondary: {
resource: "Eternity Points",
current: () => Currency.eternityPoints.value,
required: completions => DC.E20.pow(completions).times(DC.E100),
required: completions => DC.E20.pow(Math.min(completions, 4)).times(DC.E100),
formatValue: value => format(value)
}
},

View File

@ -55,8 +55,16 @@ GameDatabase.eternity.timeStudies.normal = [
requirement: [11],
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
description: () => `Improve Replicanti multiplier formula to
(log2(x)${formatPow(2)})+x${formatPow(0.032, 3, 3)}`,
effect: () => Replicanti.amount.pow(0.032)
(log2(x)${formatPow(2)})+x${formatPow(0.032, 3, 3)}`,
effect: () => Replicanti.amount.pow(0.032),
// This is a special case because the study itself is *added* to the existing formula, but it makes more sense
// to display a multiplicative increase just like every other study. We need to do the calculation in here in order
// to properly show only the effect of this study and nothing else
formatEffect: value => {
const oldVal = Decimal.pow(Decimal.log2(Replicanti.amount.clampMin(1)), 2);
const newVal = oldVal.plus(value);
return formatX(newVal.div(oldVal).clampMin(1), 2, 2);
}
},
{
id: 22,
@ -81,7 +89,7 @@ GameDatabase.eternity.timeStudies.normal = [
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
description: `You gain more Infinities based on Dimension Boosts`,
effect: () => Math.max(DimBoost.totalBoosts, 1),
formatEffect: value => formatX(value)
formatEffect: value => formatX(value, 2)
},
{
id: 33,
@ -254,8 +262,8 @@ GameDatabase.eternity.timeStudies.normal = [
requirement: [101, 102, 103],
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
description: () => (Achievement(103).canBeApplied
? `Make the Infinity Point formula better log(x/${formatFloat(307.8, 1)}) ➜ log(x/${formatInt(285)})`
: `Make the Infinity Point formula better log(x/${formatInt(308)}) ➜ log(x/${formatInt(285)})`),
? `Make the Infinity Point formula better log(x)/${formatFloat(307.8, 1)} ➜ log(x)/${formatInt(285)}`
: `Make the Infinity Point formula better log(x)/${formatInt(308)} ➜ log(x)/${formatInt(285)}`),
effect: 285
},
{

View File

@ -188,11 +188,43 @@ ${PlayerProgress.realityUnlocked()
isUnlocked: () => true,
tags: ["effect", "stack", "combine", "add", "reduce", "multiply", "divide", "power", "dilation", "glyph"],
tab: "options/gameplay"
}, {
name: "Common Abbreviations",
info: () => `
Many resources within the game may appear in an abbreviated format as text in order to save space. This How to
Play entry will update itself with additional entries for new resources as you encounter them for the first time
<br>
- <b>AM</b>: Antimatter<br>
- <b>AD</b>: Antimatter Dimension<br>
- <b>AG</b>: Antimatter Galaxy<br>
${PlayerProgress.infinityUnlocked() ? "- <b>IP</b>: Infinity Point<br>" : ""}
${PlayerProgress.infinityUnlocked() ? "- <b>NC</b>: Normal Challenge<br>" : ""}
${PlayerProgress.infinityUnlocked() ? "- <b>IC</b>: Infinity Challenge<br>" : ""}
${InfinityDimension(1).isUnlocked || PlayerProgress.eternityUnlocked() ? "- <b>ID</b>: Infinity Dimension<br>" : ""}
${PlayerProgress.replicantiUnlocked() ? "- <b>RG</b>: Replicanti Galaxy<br>" : ""}
${PlayerProgress.eternityUnlocked() ? "- <b>EP</b>: Eternity Point<br>" : ""}
${PlayerProgress.eternityUnlocked() ? "- <b>TT</b>: Time Theorem<br>" : ""}
${PlayerProgress.eternityUnlocked() ? "- <b>TD</b>: Time Dimension<br>" : ""}
${PlayerProgress.eternityUnlocked() ? "- <b>EC</b>: Eternity Challenge<br>" : ""}
${PlayerProgress.dilationUnlocked() ? "- <b>TP</b>: Tachyon Particle<br>" : ""}
${PlayerProgress.dilationUnlocked() ? "- <b>DT</b>: Dilated Time<br>" : ""}
${PlayerProgress.dilationUnlocked() ? "- <b>TG</b>: Tachyon Galaxy<br>" : ""}
${PlayerProgress.realityUnlocked() ? "- <b>RM</b>: Reality Machine<br>" : ""}
${PlayerProgress.realityUnlocked() ? "- <b>AP</b>: Automator Point<br>" : ""}
${PlayerProgress.realityUnlocked() ? "- <b>BH</b>: Black Hole<br>" : ""}
${MachineHandler.isIMUnlocked ? "- <b>iM</b>: Imaginary Machine<br>" : ""}
${Laitela.isUnlocked ? "- <b>DM</b>: Dark Matter<br>" : ""}
${Laitela.isUnlocked ? "- <b>DE</b>: Dark Energy<br>" : ""}
`,
isUnlocked: () => true,
tags: ["abbreviation", "shorten", "am", "ad", "ag", "ip", "nc", "ic", "id", "rg", "ep", "tt", "td", "ec", "tp",
"dt", "tg", "rm", "ap", "bh", "im", "dm", "de"],
tab: ""
}, {
name: "Antimatter Dimensions",
info: () => `
Antimatter is a resource that is throughout the entire game for purchasing various things as you progress. You start
with ${formatInt(10)} antimatter when you first open the game. And you can
Antimatter is a resource that is used throughout the entire game for purchasing various things as you progress. You
start with ${formatInt(10)} antimatter when you first open the game, and you can
spend it to buy the 1st Antimatter Dimension to start the game.
<br>
<br>
@ -305,7 +337,7 @@ available, but will increase the effect of your Tickspeed Upgrades by +${format(
Galaxies. As you get more Galaxies, the multiplier will continue becoming stronger and stronger.
<br>
<br>
Though it will have very little impact for the first few purchases,
Though it will have very little impact for the first few Tickspeed purchases,
the increase is multiplicative and won't take long to be visible.
<br>
<br>
@ -328,7 +360,7 @@ increases by another ${formatPercents(0.002, 1)} per Galaxy, on top of Distant s
}, {
name: "Dimensional Sacrifice",
info: () => `
<b>You unlock Dimensional Sacrifice after your first Dimension Boost.</b>
<b>You unlock Dimensional Sacrifice after your fifth Dimension Boost.</b>
<br>
<br>
Sacrificing will immediately reset the owned quantity of all non-Eighth Dimensions to zero, without reducing the
@ -476,7 +508,7 @@ It does not change individual autobuyer settings. Think of it like a master swit
Additionally, holding <b>Alt</b> when pressing a hotkey associated with an upgrade, dimension, or prestige will
toggle the associated autobuyer.
`,
isUnlocked: () => PlayerProgress.infinityUnlocked(),
isUnlocked: () => true,
tags: ["infinity", "automation", "challenges", "rewards", "interval", "earlygame"],
tab: "automation/autobuyers"
}, {
@ -732,7 +764,7 @@ to buy your preferred path and continue on instead of stopping completely at the
for the Dimension split in this dialog if you have purchased the relevant Time Study.
<br>
<br>
<b>Respecs:</b> A respec allows you to reset the upgrades you have in the tree to retreive all of the Time Theorems
<b>Respecs:</b> A respec allows you to reset the upgrades you have in the tree to retrieve all of the Time Theorems
spent on them. It can be done for free, but only triggers on finishing an Eternity; you can't respec Time Studies in
the middle of an Eternity.
<br>
@ -769,9 +801,10 @@ goal to the next completion also increases. Additionally, the secondary requirem
also increase. The Time Theorem cost does not increase.
<br>
<br>
After you have unlocked an Eternity Challenge, you can unlock it without meeting its secondary requirements until you
unlock another Eternity Challenge or beat that Eternity Challenge, allowing you to unlock an Eternity Challenge
with one set of studies, and then respec into a different set of studies to beat the challenge.
Completing an Eternity Challenge's secondary requirements will remove them from the study requirement until you complete
that particular Eternity Challenge, meaning you only need to complete the secondary requirement <i>once</i>.
As a result, you can unlock an Eternity Challenge with one set of studies, and then respec into a different set of
studies to beat the challenge.
`,
isUnlocked: () => PlayerProgress.eternityUnlocked(),
tags: ["ec", "study", "time", "rewards", "completions", "midgame"],
@ -1109,6 +1142,10 @@ Unlocking or defeating a Celestial has different conditions depending on the Cel
<br>
All Celestials have their own Celestial Reality, but how the Reality is relevant to each Celestial and the rest of
the game will depend on the Celestial.
<br>
<br>
Celestials are timeless entities. Unless otherwise stated, any new mechanics introduced by Celestials are not affected
by game speed multipliers and instead refer specifically to real time instead of game time.
`,
isUnlocked: () => Teresa.isUnlocked,
tags: ["reality", "challenges", "endgame", "lategame"],
@ -1233,6 +1270,12 @@ being selected; this can be useful for effect testing and other more limited sit
will let you forbid entire types like Specified Effect Mode as well
<br>
<br>
The Glyph Filter mode is a global setting which applies to all Glyph types at once; for example, you cannot filter
power Glyphs with "Rarity Threshold" and time Glyphs with "Specified Effect". Selecting one mode will require
you to configure every Glyph type within its settings for proper filtering. Each filter mode has its own settings
which will be kept if you switch to another mode.
<br>
<br>
Glyph Presets are purchasable for ${format(GameDatabase.celestials.effarig.unlocks.setSaves.cost)} Relic
Shards. This unlocks ${formatInt(5)} slots that allow you to save your currently equipped Glyphs into sets.
You can't overwrite a set, you must delete it first. When you load a set, each Glyph in it is found and equipped.
@ -1463,7 +1506,8 @@ with a higher refinement value.
Alchemy Resources can be combined together in certain combinations in order to create new compound resources, which
are unlocked at certain Effarig levels. Resources are combined once per Reality, unaffected by real time
amplification. Reactions have a higher yield and thus happen faster when your reagent amounts are higher. The cap for
compound resources is equal to the lowest cap amongst all of its reagents.
compound resources is equal to the lowest cap amongst all of its reagents. In order for a reaction to occur, the
current amount of all reagents must be greater than the current amount of the produced resource.
<br>
<br>
To activate or deactivate a reaction, click the circle corresponding to the reaction's product. When the reaction can

View File

@ -85,7 +85,10 @@ GameDatabase.infinity.breakUpgrades = {
Ra.unlocks.continuousTTBoost.effects.infinity
);
infinities = infinities.times(getAdjustedGlyphEffect("infinityinfmult"));
return `${quantify("Infinity", infinities)} every ${Time.bestInfinity.times(5).toStringShort()}`;
const timeStr = Time.bestInfinity.totalMilliseconds <= 50
? `${TimeSpan.fromMilliseconds(100).toStringShort()} (capped)`
: `${Time.bestInfinity.times(2).toStringShort()}`;
return `${quantify("Infinity", infinities)} every ${timeStr}`;
}
},
autobuyMaxDimboosts: {
@ -135,8 +138,7 @@ GameDatabase.infinity.breakUpgrades = {
if (!BreakInfinityUpgrade.ipGen.isCapped) {
generation += `${formatInt(5 * (1 + player.infinityRebuyables[2]))}%`;
}
const offlineString = player.options.offlineProgress ? ", works offline" : "";
return `${generation} of your best IP/min from your last 10 Infinities${offlineString}`;
return `${generation} of your best IP/min from your last 10 Infinities`;
},
isDisabled: effect => effect.eq(0),
formatEffect: value => `${format(value, 2, 1)} IP/min`,

View File

@ -165,11 +165,8 @@ GameDatabase.infinity.upgrades = {
formatEffect: value => {
if (Teresa.isRunning || V.isRunning) return "Disabled in this reality";
if (Pelle.isDoomed) return "Disabled";
const income = format(value, 2, 0);
const period = player.records.bestInfinity.time >= 999999999999
? "∞"
: Time.bestInfinity.times(10).toStringShort();
return `${income} every ${period}`;
if (player.records.bestInfinity.time >= 999999999999) return "Too slow to generate";
return `${format(value, 2)} every ${Time.bestInfinity.times(10).toStringShort()}`;
},
charged: {
description: () =>

View File

@ -83,7 +83,8 @@ GameDatabase.multiplierTabValues.gamespeed = {
},
chargingBH: {
name: "Black Hole Charging",
multValue: () => 1 - player.celestials.enslaved.storedFraction,
// The 0 in multValue is irrelevant; if this upgrade isn't available, the subtab is hidden by 1x total effect
multValue: () => (Ra.unlocks.autoPulseTime.canBeApplied ? 0.01 : 0),
isActive: () => Enslaved.isStoringGameTime,
icon: MultiplierTabIcons.BLACK_HOLE,
},

View File

@ -23,10 +23,9 @@ GameDatabase.multiplierTabValues.ID = {
.filter(id => id.isProducing)
.map(id => id.multiplier)
.reduce((x, y) => x.times(y), DC.D1)),
isActive: dim => !EternityChallenge(11).isRunning &&
(dim
? InfinityDimension(dim).isProducing
: (PlayerProgress.eternityUnlocked() || InfinityDimension(1).isProducing)),
isActive: dim => (dim
? InfinityDimension(dim).isProducing
: (PlayerProgress.eternityUnlocked() || InfinityDimension(1).isProducing)),
dilationEffect: () => {
const baseEff = player.dilation.active
? 0.75 * Effects.product(DilationUpgrade.dilationPenalty)

View File

@ -23,8 +23,9 @@ GameDatabase.multiplierTabValues.TD = {
.filter(td => td.isProducing)
.map(td => td.multiplier)
.reduce((x, y) => x.times(y), DC.D1)),
isActive: dim => !EternityChallenge(11).isRunning &&
(dim ? TimeDimension(dim).isProducing : (PlayerProgress.realityUnlocked() || TimeDimension(1).isProducing)),
isActive: dim => (dim
? TimeDimension(dim).isProducing
: (PlayerProgress.realityUnlocked() || TimeDimension(1).isProducing)),
dilationEffect: () => {
const baseEff = player.dilation.active
? 0.75 * Effects.product(DilationUpgrade.dilationPenalty)

View File

@ -223,7 +223,9 @@ GameDatabase.news = [
},
{
id: "a49",
text: "Can we get 1e169 likes on this video??? Smash that like button!!"
get text() {
return `Can we get ${format(1e169)} likes on this video??? Smash that like button!!`;
}
},
{
id: "a50",
@ -317,7 +319,7 @@ GameDatabase.news = [
},
{
id: "a70",
text: "If you can't read this you disabled the news."
text: "If you can't read this, you disabled the news."
},
{
id: "a71",
@ -424,7 +426,7 @@ GameDatabase.news = [
link: "https://trimps.github.io/"
},
{
name: "Mine Defense",
name: "Mine Defense (the game's ui is broken on https so make sure you're on http!)",
link: "http://scholtek.com/minedefense"
},
{
@ -450,6 +452,10 @@ GameDatabase.news = [
{
name: "The First Alkahistorian stages 1, 2, and 3",
link: "https://nagshell.github.io/elemental-inception-incremental/"
},
{
name: "Melvor Idle",
link: "https://melvoridle.com/"
}
];
const game = games.randomElement();
@ -507,7 +513,8 @@ GameDatabase.news = [
id: "a103",
text:
`Antimatter... antimatter never changes... until you get to quantum physics of antimatter,
but we don't have enough tachyon particles for that.`
but we don't have enough tachyon particles for that.`,
get unlocked() { return PlayerProgress.realityUnlocked() || PlayerProgress.dilationUnlocked(); }
},
{
id: "a104",
@ -597,7 +604,8 @@ GameDatabase.news = [
Sacrifice' is moving away. You have to give up a lot of the things you had that made you happy, but there is
new opportunity in where you move to. And that new opportunity gives you more happiness than you ever had.
'Tickspeed' is how easy it is to make you happy, and 'Time Dimensions' make it even easier to be happy.
Antimatter Dimensions is a metaphor for a depressed man's successful battle against his illness.`
Antimatter Dimensions is a metaphor for a depressed man's successful battle against his illness.`,
get unlocked() { return PlayerProgress.eternityUnlocked(); }
},
{
id: "a114",
@ -715,7 +723,8 @@ GameDatabase.news = [
},
{
id: "a134",
text: "Because of this game I can now use the word \"infinity\" as a verb."
text: "Because of this game I can now use the word \"infinity\" as a verb.",
get unlocked() { return PlayerProgress.infinityUnlocked(); }
},
{
id: "a135",
@ -841,7 +850,8 @@ GameDatabase.news = [
id: "a159",
text:
`What does it mean when you "bank" Infinities? Is there a bank somewhere that you just deposit these
infinities? Does having a lot of banked Infinities improve your credit score? Do you get a credit card?`
infinities? Does having a lot of banked Infinities improve your credit score? Do you get a credit card?`,
get unlocked() { return PlayerProgress.eternityUnlocked(); }
},
{
id: "a160",
@ -1277,7 +1287,8 @@ GameDatabase.news = [
},
{
id: "a223",
text: "If you find your infinity lasting longer than 5 hours please contact a medical professional."
text: "If you find your infinity lasting longer than 5 hours please contact a medical professional.",
get unlocked() { return PlayerProgress.infinityUnlocked(); }
},
{
id: "a224",
@ -1432,6 +1443,9 @@ GameDatabase.news = [
${BLOB} are just blobbling and bouncing around, occasionally merging and dividing. Only ${BLOB} know where
they are from or where they are going to go. Still, ${BLOB} are there, always with me.
You love ${BLOB}, so ${BLOB} loves you too.`,
S12:
`it makes you feel warm and comfortable, as if you were right at home. However, it is highly recommended
to update your theme to the newest theme for the best user experience.`,
};
const reason = reasons[Theme.current().name.replace(/\s/gu, "")];
return `Ah, a fellow ${theme} theme user. I see that you have impeccable taste.
@ -1507,6 +1521,7 @@ GameDatabase.news = [
get text() {
return `<span style='animation: a-text-stretch ${newsAnimSpd(35)}s 1 forwards'>This message is dilated.</span>`;
},
get unlocked() { return PlayerProgress.realityUnlocked() || PlayerProgress.dilationUnlocked(); }
},
{
id: "a253",
@ -1570,7 +1585,8 @@ GameDatabase.news = [
id: "a260",
text:
`It seems that the Replicanti have a very divide-and-conquer method of doing things.
Well, everything at this rate.`
Well, everything at this rate.`,
get unlocked() { return PlayerProgress.eternityUnlocked() || PlayerProgress.replicantiUnlocked(); }
},
{
id: "a261",
@ -2279,8 +2295,8 @@ GameDatabase.news = [
}
};
}()),
// Blob from the blob font
{
// Blob from the blob font
id: "a354",
text:
`<span style='color: #FBC21B; text-shadow: 0px 1px 0px black, 1px 0px 0px black, 1px 1px 0px black,
@ -2316,13 +2332,13 @@ GameDatabase.news = [
id: "a360",
text: `Press "Choose save" to explore the other 2 parallel universes.`
},
// Discord contest winner #1
{
// Discord contest winner #1
id: "a361",
text: "We're having a sale of top quality waterproof towels! Be sure to get some on your way out!"
},
// Discord contest winner #2
{
// Discord contest winner #2
id: "a362",
text:
`Hevipelle Incorporated is proud to present a new brand of cereal: The Big Crunch! This nutritious breakfast
@ -2330,7 +2346,8 @@ GameDatabase.news = [
Replicanti, and Eternity-flavored Marshmallows. Now you can experience Antimatter Dimensions inside of your
stomach! Warning: Side effects may include spontaneous combustion, nausea, vomiting, diarrhea,
dematerialization, vaporization, heart failure, the end of the world, or death. If you are not made out of
antimatter, consult an educated professional on Antimatter Consumption before eating 'The Big Crunch'.`
antimatter, consult an educated professional on Antimatter Consumption before eating 'The Big Crunch'.`,
get unlocked() { return PlayerProgress.eternityUnlocked(); }
},
{
id: "a363",
@ -2368,7 +2385,8 @@ GameDatabase.news = [
},
{
id: "a365",
text: "I don't like Replicanti. They're coarse and rough and irritating and they replicate everywhere."
text: "I don't like Replicanti. They're coarse and rough and irritating and they replicate everywhere.",
get unlocked() { return PlayerProgress.eternityUnlocked() || PlayerProgress.replicantiUnlocked(); }
},
{
id: "a366",
@ -2437,6 +2455,148 @@ GameDatabase.news = [
at the WAIC (Witches Annual Infrastructure Committee) as part of stage 56. Truly, tragic stuff - 3 award
nominations and 2 wins during that process due to EBIF (Efficient Bureaucracy In (the) Field).`
},
{
id: "a370",
text:
"Man tries installing cookies to store computer data, accidentally cleans them due to being too delicious."
},
{
id: "a371",
text:
`Pop quiz: there are 3 doors, you pick a door at random, and get to keep what's behind the door. The doors
have 2 golden goats, 2 silver goats, and a gold and a silver goat. After you pick a door, the door with the
lowest $ worth of goats will be opened and shown to you. After this, you are given the choice to swap.
What is the probability that you will swap doors?`
},
{
id: "a372",
text:
`If you're ever lost in a forest, look at the trees around you. It's said that moss grows north, so by the
time you've finished looking at a tree, a roaming guitarist will run up to you and ask if you want to hear
wonderwall`
},
{
id: "a373",
text:
`As a symbol of friendship between the Matter and Antimatter Periodic Tables, they have done an exchange of
elements. The element of Mony is now part of the Antimatter Periodic Table, while Antimony has been added
to the regular Periodic Table.`
},
{
id: "a374",
text: "This newsticker was specifically designed for promotional purposes only."
},
{
id: "a375",
text:
`As you probably know, it is traditional to give gifts made of certain materials to celebrate anniversaries
The classic ones are silver at 25 and gold at 50. Here are some little known anniversary gifts:
Pineapple - 37 years Hellstone - 66 years Lizardite- 82 years Nitrowhisperin- 86 years Taconite - 95 years
Hatchettite - 100 years Electrum - 110 Yakitoda - 111 years years Fordite - 119 years Bloodstone - 120 years
Celestite - 125 years Jet - 140 years Petroleum - 145 years Steel - 150 years Cummingtonite - 198 years
Concrete - 200 years Laserblue- 210 years Painite - 250 years Parisite - 255 years Parasite - 260 years
Carbon Nanotubes - 300 years Mercury - 310 years Martian Soil - 340 years Neptunium - 370 years
Uranium - 380 years Plutonium - 390 years Xium - 400 years Blaze rods - 420 years Asbestos - 430 years
Gabite - 444 years Crimtane - 666 years Lagga - 777 years`
},
{
id: "a376",
text:
`Big tech companies have collaborated to create a new neural network that's trained in the generation of rap
lyrics, called RAP-3. First lyrical generations include "Call me prometheus 'cuz I bring the fire" and
"Call me Sonic the way I'm gettin' these rings". Critics say it still has a way to go before it replaces
traditional music.`
},
{
id: "a377",
text:
`With the new android OS, android 20, being predicted in the near future, the new system for internal codenames
has been revealed. The first codename, as it currently stands, is antimatter. This conveniently works well
with the predicted generation of phones that will use Android 20 - these phones will be the most explosive
ever due to their annihilation-based power source. Sources tell us that a billion dollar research unit is
working on a name for android 21, by tradition to start with B, that doesn't sound too bad when you think
about it. `
},
{
id: "a378",
text: "If every antimatter were an apple, you would have enough to keep all the doctors away for 3000 years"
},
{
id: "a379",
get text() {
return `THE ${format(Number.MAX_VALUE, 2)} PIECE! THE ${format(Number.MAX_VALUE, 2)} PIECE IS REAL!`;
}
},
{
id: "a380",
text:
`The FitnessGram Pacer Test is a multistage aerobic capacity test that progressively gets more difficult
as it continues. The 20 meter pacer test will begin in 30 seconds. Line up at the start. The running speed
starts slowly, but gets faster each minute after you hear this signal. [beep] A single lap should be
completed each time you hear this sound. [ding] Remember to run in a straight line, and run as long as
possible. The second time you fail to complete a lap before the sound, your test is over. The test will
begin on the word start. On your mark, get ready, start.`
},
{
id: "a381",
text: "Why do they call it second dimension when you of in the first dimension of out second eat the dimension?"
},
{
id: "a382",
text:
"Any AD player born after 1993 can't joke... All they know is 5 hours, paperclips, 1.79e308 & Ninth Dimension."
},
{
id: "a383",
text:
"The only thing better than an anti-joke is two. Like the number. Not two anti-jokes. I just like the number two."
},
{
id: "a384",
text: "Click here to make nothing happen."
},
{
id: "a385",
text:
`I wonder... Why did Apple skip iPhone 9 and Microsoft skip Windows 9...
Was it because they were bribed by a game developer?`
},
{
id: "a386",
text: "9 out of 10 doctors recommended against trying to touch antimatter. We haven't heard back from the 10th one."
},
{
id: "a387",
text:
`In spring, Man built a pillar. In summer, another. Throughout autumn they held. But in winter, one experienced
an unexpected (See definition in: Abstract Multidimensional Retrocausal Physics) ZW-Class "Ascension" event,
and is hypothesised to have fallen into a dimensional loophole, where it, by definition, has to take up more
dimensions than itself. Current efforts at retrieving the pillar and returning it to baseline reality have been
unsuccessful (See test log 2453-3e9a-50d1-84fc for more details)`
},
{
id: "a388",
text:
`In light of recent events, we'd like to issue an official statement. Antimatter Dimensions™ is in no way
affiliated with Jimmy's Causality Violating Brainworms. We do not endorse, no were we involved in their creation
of the product which was involved in several catastrophic dimension-destabilising and reality-toppling incidents.
We almost certainly did not sign a contract at 5:30:26 UTC on 08/12/1994 after discussing how we could benefit
from destabilising and warping dimensions. There was no industrial zone constructed in the 5th Orion Arm of the
' galaxy, and even if they were we did not install localised anomalies following the Scranton Reality Anti-anchor
mechanism. Additionally, no time loop is occurring at Acroamatic Abatement Facility AAF-D in site 43. We apologise
if things seemed this way, and we will be more thorough in cracking down misinformation in the future.`
},
{
id: "a389",
text: "If only we could condense the antimatter in the universe into cookies..."
},
{
id: "a390",
text:
`Can you believe it guys? Update, just 5 hours away. Update is in a 5 hours. Wahoo. I'm so happy about this
information. Update just 5 hours away. Oh wow. Can you believe it? Update just in a 5 hours. It got here so
fast. Update, just 5 hours.`
},
{
id: "l1",
text: "You just made your 1,000,000,000,000,000th antimatter. This one tastes like chicken.",
@ -3067,6 +3227,62 @@ GameDatabase.news = [
// 3 years of time to write
get unlocked() { return Currency.antimatter.value.gte("1e777600"); }
},
{
id: "l83",
text:
`AD Patch Notes: Cleaned up the celestial problem Made Antimatter care about annihilation more Added mouths
Removed mouths Stopped unwanted interlopers from corporate takeovers of shops Fixed problem with newstickers
hanging in the air Dead replicanti remain in their galaxies Redefined interlopers to not include [REDACTED]
Tachyon Particles get stuck in the top left corner of the screen, obliterate time Added Coriolis effect to
Galaxy Spin Direction`,
get unlocked() { return Teresa.isUnlocked; }
},
{
id: "l84",
get text() {
return `For the record, you currently have ${player.news.specialTickerData.paperclips}
Useless Paperclips. You may want to spend them on something.`;
},
get unlocked() { return player.news.specialTickerData.paperclips > 0; }
},
{
id: "l85",
text:
`On opposite day, the new update is just -5 hours away. You begin increasing your Matter. Once you acquire a
huge abundance of Matter, you must become Infinitesimal. After increasing your wealth in Infinitesimal Points,
you can eventually Jiffy, the shortest unit of time. After enough time, your Jiffies will accumulate, and you
will Contract Time. Contracting Time will grant you enough of a boost to eventually Fantasy, the final layer
of maintenance. However, you find out that it was all a dream. Your Antimatter is safe and well, and the new
update is still just 5 hours away.`,
get unlocked() { return PlayerProgress.realityUnlocked(); }
},
{
id: "l86",
text:
`Hello, player. I'd like to play a game. In front of you is a pile of replicanti. They are currently frozen in
time, and cannot replicate. To your right is a computer playing Antimatter Dimensions on an empty save. You
must reach infinity. However, once you buy a 1st dimension, the replicanti will start replicating. As you know,
they replicate fast, and if they fill up the room you will suffocate. If you reach infinity before that, they
will be frozen again. The clock is ticking. Start now.`,
get unlocked() { return PlayerProgress.replicantiUnlocked(); }
},
{
id: "l87",
text:
`"To see a World in a Grain of Sand. And a Heaven in a Wild Flower. Hold Infinity in the palm of your hand.
And Eternity in an hour. And Reality in about 5 hours" ~Anti-William Blake `,
get unlocked() { return PlayerProgress.realityUnlocked(); }
},
{
id: "l88",
text:
`Our deepest apologies for the new glyph mechanic. The intent is to provide players with a sense of pride and
accomplishment for unlocking rare glyphs. We selected initial values based upon data from the final wave of
testing and other adjustments made to milestone rewards before launch. Among other things, we're looking at
average per-player credit earn rates on a daily basis, and we'll be making constant adjustments to ensure that
players have challenges that are compelling, rewarding, and of course attainable via gameplay.`,
get unlocked() { return PlayerProgress.realityUnlocked(); }
},
{
id: "r1",
text: "This news message is 100x rarer than all the others.",

View File

@ -59,7 +59,7 @@ GameDatabase.progressStages = [
name: "Eternity",
hasReached: save => new Decimal(save.eternities).gt(0),
suggestedResource: "Eternity Points and Eternity count",
subProgressValue: save => Math.sqrt(new Decimal(save.eternityPoints).pLog10() / 18),
subProgressValue: save => new Decimal(save.eternities).clampMax(1e5).toNumber() / 1e5,
},
{
id: PROGRESS_STAGE.ETERNITY_CHALLENGES,

View File

@ -139,7 +139,7 @@ GameDatabase.reality.glyphCosmeticSets = {
celestial: {
id: "celestial",
name: "Celestial Icons",
symbol: ["⌬", "ᛝ", "♅"],
symbol: ["\uF0C1", "⌬", "ᛝ", "♅"],
color: ["B#00BCD4"],
},
alchemy: {

View File

@ -222,20 +222,20 @@ GameDatabase.reality.glyphEffects = {
isGenerated: true,
glyphTypes: ["replication"],
singleDesc: () => (GlyphAlteration.isAdded("replication")
? "Multiply Dilated Time \n[and Replicanti speed] by \nlog₁₀(replicanti)×{value}"
: "Multiply Dilated Time gain by \nlog₁₀(replicanti)×{value}"),
? `Multiply Dilated Time \n[and Replicanti speed] by \n+{value} per ${format(DC.E10000)} replicanti`
: `Multiply Dilated Time gain by \n+{value} per ${format(DC.E10000)} replicanti`),
totalDesc: () => (GlyphAlteration.isAdded("replication")
? "Dilated Time gain and Replication speed ×(log₁₀(replicanti)×{value})"
: "Dilated Time gain ×(log₁₀(replicanti)×{value})"),
? `Multiply Dilated Time and Replication speed by +{value} per ${format(DC.E10000)} replicanti`
: `Multiply Dilated Time gain by +{value} per ${format(DC.E10000)} replicanti`),
genericDesc: () => (GlyphAlteration.isAdded("replication")
? "Dilated Time+Replicanti mult (log₁₀(replicanti))"
: "Dilated Time gain multiplier (log₁₀(replicanti))"),
? "Dilated Time+Replicanti mult from replicanti"
: "Dilated Time gain multiplier from replicanti"),
shortDesc: () => (GlyphAlteration.isAdded("replication")
? "DT and repl. ×log₁₀(repl.)×{value}"
: "DT ×log₁₀(repl.)×{value}"),
? `×DT and repl. by +{value} per ${format(DC.E10000)} replicanti`
: `×DT by +{value} per ${format(DC.E10000)} replicanti`),
effect: (level, strength) => 0.0003 * Math.pow(level, 0.3) * Math.pow(strength, 0.65),
formatEffect: x => format(x, 5, 5),
formatSingleEffect: x => format(x, 5, 5),
formatEffect: x => format(10000 * x, 2, 2),
formatSingleEffect: x => format(10000 * x, 2, 2),
// It's bad to stack this one additively (N glyphs acts as a DT mult of N) or multiplicatively (the raw number is
// less than 1), so instead we do a multiplicative stacking relative to the "base" effect of a level 1, 0% glyph.
// We also introduce a 3x mult per glyph after the first, so that stacking level 1, 0% glyphs still has an effect.

View File

@ -146,6 +146,7 @@ GameDatabase.reality.imaginaryUpgrades = [
name: "Recollection of Intrusion",
id: 14,
cost: 3.5e8,
formatCost: x => format(x, 1),
requirement: () => `Reach a tickspeed of ${format("1e75000000000")} / sec within Eternity Challenge 5`,
hasFailed: () => false,
checkRequirement: () => EternityChallenge(5).isRunning && Tickspeed.perSecond.exponent >= 7.5e10,
@ -294,9 +295,10 @@ GameDatabase.reality.imaginaryUpgrades = [
formatCost: x => format(x, 1),
requirement: () => `Reach Reality in Lai'tela's Reality with all Dimensions disabled and
at least ${formatInt(4)} empty Glyph slots`,
hasFailed: () => !Laitela.isRunning || Laitela.maxAllowedDimension !== 0 || Glyphs.activeList.length > 1,
hasFailed: () => !Laitela.isRunning || Laitela.maxAllowedDimension !== 0 ||
Glyphs.activeWithoutCompanion.length > 1,
checkRequirement: () => Laitela.isRunning && Laitela.maxAllowedDimension === 0 &&
Glyphs.activeList.length <= 1 && TimeStudy.reality.isBought,
Glyphs.activeWithoutCompanion.length <= 1 && TimeStudy.reality.isBought,
checkEvent: GAME_EVENT.GAME_TICK_AFTER,
description: "Unlock Pelle, Celestial of Antimatter",
},

View File

@ -128,8 +128,7 @@ GameDatabase.reality.perks = {
id: 40,
label: "EU1",
family: PERK_FAMILY.ETERNITY,
description: `After the first Eternity of a Reality,
automatically unlock the first row of Eternity Upgrades for free.`,
description: `Automatically unlock the first row of Eternity Upgrades for free once you have Eternities.`,
defaultPosition: new Vector(50, 150)
},
autounlockEU2: {
@ -144,14 +143,14 @@ GameDatabase.reality.perks = {
},
autounlockDilation1: {
id: 42,
label: "UD1",
label: "DU1",
family: PERK_FAMILY.DILATION,
description: "After unlocking Dilation, automatically unlock the second row of Dilation Upgrades for free.",
defaultPosition: new Vector(165, 565)
},
autounlockDilation2: {
id: 43,
label: "UD2",
label: "DU2",
family: PERK_FAMILY.DILATION,
description: "After unlocking Dilation, automatically unlock the third row of Dilation Upgrades for free.",
defaultPosition: new Vector(310, 605)
@ -257,7 +256,8 @@ GameDatabase.reality.perks = {
label: "PEC2",
family: PERK_FAMILY.AUTOMATION,
get description() {
return `Auto-complete one Eternity Challenge every ${formatInt(40)} minutes (real-time).`;
return `Auto-complete one Eternity Challenge every ${formatInt(40)} minutes (real-time).
(${formatInt(20)} minute decrease)`;
},
effect: 40,
defaultPosition: new Vector(425, 235)
@ -267,7 +267,8 @@ GameDatabase.reality.perks = {
label: "PEC3",
family: PERK_FAMILY.AUTOMATION,
get description() {
return `Auto-complete one Eternity Challenge every ${formatInt(20)} minutes (real-time).`;
return `Auto-complete one Eternity Challenge every ${formatInt(20)} minutes (real-time).
(${formatInt(20)} minute decrease)`;
},
effect: 20,
automatorPoints: 10,
@ -295,7 +296,7 @@ GameDatabase.reality.perks = {
id: 72,
label: "ECR",
family: PERK_FAMILY.ETERNITY,
description: "Remove nonTime Theorem requirements for unlocking Eternity Challenges.",
description: "Remove non-Time Theorem requirements for unlocking Eternity Challenges.",
automatorPoints: 10,
shortDescription: () => "Remove EC secondary requirements",
defaultPosition: new Vector(605, -160)
@ -316,7 +317,7 @@ GameDatabase.reality.perks = {
label: "TP1",
family: PERK_FAMILY.DILATION,
get description() {
return `When buying the "You gain ${formatInt(3)} times more Tachyon Particles" Dilation Upgrade,
return `When buying the 3rd rebuyable Dilation Upgrade,
multiply your current Tachyon Particle amount by ${formatFloat(1.5, 1)}.`;
},
effect: 1.5,
@ -327,7 +328,7 @@ GameDatabase.reality.perks = {
label: "TP2",
family: PERK_FAMILY.DILATION,
get description() {
return `When buying the "You gain ${formatInt(3)} times more Tachyon Particles" Dilation Upgrade,
return `When buying the 3rd rebuyable Dilation Upgrade,
multiply your current Tachyon Particle amount by ${formatInt(2)}.`;
},
effect: 2,
@ -338,7 +339,7 @@ GameDatabase.reality.perks = {
label: "TP3",
family: PERK_FAMILY.DILATION,
get description() {
return `When buying the "You gain ${formatInt(3)} times more Tachyon Particles" Dilation Upgrade,
return `When buying the 3rd rebuyable Dilation Upgrade,
multiply your current Tachyon Particle amount by ${formatFloat(2.5, 1)}.`;
},
effect: 2.5,
@ -349,7 +350,7 @@ GameDatabase.reality.perks = {
label: "TP4",
family: PERK_FAMILY.DILATION,
get description() {
return `When buying the "You gain ${formatInt(3)} times more Tachyon Particles" Dilation Upgrade,
return `When buying the 3rd rebuyable Dilation Upgrade,
multiply your current Tachyon Particle amount by ${formatInt(3)}.`;
},
effect: 3,
@ -481,8 +482,10 @@ GameDatabase.reality.perks = {
id: 205,
label: "ACHNR",
family: PERK_FAMILY.ACHIEVEMENT,
description: "Reality no longer resets your Achievements.",
effect: 2,
get description() {
return `Immediately unlock the first ${formatInt(13)} rows of Achievements
and Reality no longer resets them.`;
},
automatorPoints: 10,
shortDescription: () => "Keep Achievements on Reality",
defaultPosition: new Vector(-195, -630)

View File

@ -71,7 +71,7 @@ GameDatabase.reality.upgrades = [
name: "Cosmically Duplicate",
id: 6,
cost: 15,
requirement: "Complete your first Eternity without using Replicanti Galaxies",
requirement: "Complete your first manual Eternity without using Replicanti Galaxies",
// Note that while noRG resets on eternity, the reality-level check will be false after the first eternity.
// The noRG variable is eternity-level as it's also used for an achievement check
hasFailed: () => !(player.requirementChecks.eternity.noRG && player.requirementChecks.reality.noEternities),
@ -97,7 +97,7 @@ GameDatabase.reality.upgrades = [
name: "Paradoxically Attain",
id: 8,
cost: 15,
requirement: "Get to Eternity without any automatic Achievements",
requirement: "Manually Eternity without any automatic Achievements",
hasFailed: () => player.reality.gainedAutoAchievements,
checkRequirement: () => !player.reality.gainedAutoAchievements,
checkEvent: GAME_EVENT.ETERNITY_RESET_BEFORE,
@ -110,15 +110,15 @@ GameDatabase.reality.upgrades = [
id: 9,
cost: 15,
requirement: () => `Eternity for ${format("1e4000")} Eternity Points using
only a single level ${formatInt(3)}+ Glyph.`,
only a single Glyph which must be level ${formatInt(3)}+.`,
hasFailed: () => {
const invalidEquippedGlyphs = Glyphs.activeList.length > 1 ||
(Glyphs.activeList.length === 1 && Glyphs.activeList[0].level < 3);
const invalidEquippedGlyphs = Glyphs.activeWithoutCompanion.length > 1 ||
(Glyphs.activeWithoutCompanion.length === 1 && Glyphs.activeWithoutCompanion[0].level < 3);
const hasValidGlyphInInventory = Glyphs.inventory.countWhere(g => g && g.level >= 3) > 0;
return invalidEquippedGlyphs || (Glyphs.activeList.length === 0 && !hasValidGlyphInInventory);
return invalidEquippedGlyphs || (Glyphs.activeWithoutCompanion.length === 0 && !hasValidGlyphInInventory);
},
checkRequirement: () => Currency.eternityPoints.exponent >= 4000 &&
Glyphs.activeList.length === 1 && Glyphs.activeList[0].level >= 3,
Glyphs.activeWithoutCompanion.length === 1 && Glyphs.activeWithoutCompanion[0].level >= 3,
checkEvent: GAME_EVENT.ETERNITY_RESET_AFTER,
description: "Gain another Glyph slot",
effect: () => 1
@ -127,7 +127,7 @@ GameDatabase.reality.upgrades = [
name: "Existentially Prolong",
id: 10,
cost: 15,
requirement: () => `Complete your first Eternity with at least ${formatPostBreak(DC.E400)} Infinity Points`,
requirement: () => `Complete your first manual Eternity with at least ${formatPostBreak(DC.E400)} Infinity Points`,
hasFailed: () => !player.requirementChecks.reality.noEternities,
checkRequirement: () => Currency.infinityPoints.exponent >= 400 &&
player.requirementChecks.reality.noEternities,
@ -168,13 +168,12 @@ GameDatabase.reality.upgrades = [
name: "The Telemechanical Process",
id: 13,
cost: 50,
requirement: () => `Eternity for ${format(DC.E4000)} Eternity Points without Time Dimensions 5-8`,
requirement: () => `Eternity for ${format(DC.E4000)} Eternity Points without Time Dim. 5-8`,
hasFailed: () => !Array.range(5, 4).every(i => TimeDimension(i).amount.equals(0)),
checkRequirement: () => Currency.eternityPoints.exponent >= 4000 &&
Array.range(5, 4).every(i => TimeDimension(i).amount.equals(0)),
checkEvent: GAME_EVENT.ETERNITY_RESET_AFTER,
description: () => `Unlock Time Dimension, ${formatX(5)} Eternity Point multiplier,
and improved Eternity autobuyers`,
description: () => `Improve Eternity Autobuyer and unlock autobuyers for Time Dimensions and ${formatX(5)} EP`,
automatorPoints: 10,
shortDescription: () => `TD and ${formatX(5)} EP Autobuyers, improved Eternity Autobuyer`,
},
@ -209,14 +208,14 @@ GameDatabase.reality.upgrades = [
id: 16,
cost: 1500,
requirement: () => `Reality with ${formatInt(4)} Glyphs equipped of uncommon or better rarity
(${formatInt(Glyphs.activeList.countWhere(g => g && g.strength >= 1.5))} equipped)`,
(${formatInt(Glyphs.activeWithoutCompanion.countWhere(g => g && g.strength >= 1.5))} equipped)`,
hasFailed: () => {
const availableGlyphs = Glyphs.inventory.countWhere(g => g && g.strength >= 1.5);
const equipped = Glyphs.activeList.countWhere(g => g.strength >= 1.5);
const equipped = Glyphs.activeWithoutCompanion.countWhere(g => g.strength >= 1.5);
const availableSlots = Glyphs.activeSlotCount - Glyphs.activeList.length;
return equipped + Math.min(availableGlyphs, availableSlots) < 4;
},
checkRequirement: () => Glyphs.activeList.countWhere(g => g.strength >= 1.5) === 4,
checkRequirement: () => Glyphs.activeWithoutCompanion.countWhere(g => g.strength >= 1.5) === 4,
checkEvent: GAME_EVENT.REALITY_RESET_BEFORE,
description: "Improve the Glyph rarity formula",
effect: 1.3,
@ -227,14 +226,15 @@ GameDatabase.reality.upgrades = [
id: 17,
cost: 1500,
requirement: () => `Reality with ${formatInt(4)} Glyphs equipped, each having at least ${formatInt(2)} effects
(${formatInt(Glyphs.activeList.countWhere(g => g && countValuesFromBitmask(g.effects) >= 2))} equipped)`,
(${formatInt(Glyphs.activeWithoutCompanion.countWhere(g => g && countValuesFromBitmask(g.effects) >= 2))}
equipped)`,
hasFailed: () => {
const availableGlyphs = Glyphs.inventory.countWhere(g => g && countValuesFromBitmask(g.effects) >= 2);
const equipped = Glyphs.activeList.countWhere(g => countValuesFromBitmask(g.effects) >= 2);
const equipped = Glyphs.activeWithoutCompanion.countWhere(g => countValuesFromBitmask(g.effects) >= 2);
const availableSlots = Glyphs.activeSlotCount - Glyphs.activeList.length;
return equipped + Math.min(availableGlyphs, availableSlots) < 4;
},
checkRequirement: () => Glyphs.activeList.countWhere(g => countValuesFromBitmask(g.effects) >= 2) === 4,
checkRequirement: () => Glyphs.activeWithoutCompanion.countWhere(g => countValuesFromBitmask(g.effects) >= 2) === 4,
checkEvent: GAME_EVENT.REALITY_RESET_BEFORE,
description: () => `${formatPercents(0.5)} chance to get an additional effect on Glyphs`,
effect: 0.5,
@ -245,14 +245,14 @@ GameDatabase.reality.upgrades = [
id: 18,
cost: 1500,
requirement: () => `Reality with ${formatInt(4)} Glyphs equipped, each at level ${formatInt(10)} or higher
(${formatInt(Glyphs.activeList.countWhere(g => g && g.level >= 10))} equipped)`,
(${formatInt(Glyphs.activeWithoutCompanion.countWhere(g => g && g.level >= 10))} equipped)`,
hasFailed: () => {
const availableGlyphs = Glyphs.inventory.countWhere(g => g && g.level >= 10);
const equipped = Glyphs.activeList.countWhere(g => g.level >= 10);
const equipped = Glyphs.activeWithoutCompanion.countWhere(g => g.level >= 10);
const availableSlots = Glyphs.activeSlotCount - Glyphs.activeList.length;
return equipped + Math.min(availableGlyphs, availableSlots) < 4;
},
checkRequirement: () => Glyphs.activeList.countWhere(g => g.level >= 10) === 4,
checkRequirement: () => Glyphs.activeWithoutCompanion.countWhere(g => g.level >= 10) === 4,
checkEvent: GAME_EVENT.REALITY_RESET_BEFORE,
description: "Eternity count boosts Glyph level",
effect: () => Math.max(Math.sqrt(Currency.eternities.value.plus(1).log10()) * 0.45, 1),
@ -263,9 +263,9 @@ GameDatabase.reality.upgrades = [
id: 19,
cost: 1500,
requirement: () => `Have a total of ${formatInt(30)} or more Glyphs at once
(You have ${formatInt(Glyphs.allGlyphs.countWhere(g => g))})`,
hasFailed: () => Glyphs.allGlyphs.countWhere(g => g) < 30,
checkRequirement: () => Glyphs.allGlyphs.countWhere(g => g) >= 30,
(You have ${formatInt(Glyphs.allGlyphs.countWhere(g => g.type !== "companion"))})`,
hasFailed: () => Glyphs.allGlyphs.countWhere(g => g.type !== "companion") < 30,
checkRequirement: () => Glyphs.allGlyphs.countWhere(g => g.type !== "companion") >= 30,
checkEvent: GAME_EVENT.REALITY_RESET_BEFORE,
description: "You can sacrifice Glyphs for permanent bonuses (Shift + click)",
formatCost: value => format(value, 1, 0)
@ -311,11 +311,12 @@ GameDatabase.reality.upgrades = [
name: "Replicative Rapidity",
id: 23,
cost: 100000,
requirement: () => `Reality in under ${formatInt(15)} minutes (Best: ${Time.bestReality.toStringShort()})`,
requirement: () => `Reality in under ${formatInt(15)} minutes of game time
(Fastest: ${Time.bestReality.toStringShort()})`,
hasFailed: () => Time.thisReality.totalMinutes >= 15,
checkRequirement: () => Time.thisReality.totalMinutes < 15,
checkEvent: GAME_EVENT.REALITY_RESET_BEFORE,
description: "Replicanti speed is boosted based on your fastest Reality",
description: "Replicanti speed is boosted based on your fastest game-time Reality",
effect: () => 15 / Math.clamp(Time.bestReality.totalMinutes, 1 / 12, 15),
cap: 180,
formatEffect: value => formatX(value, 2, 2)
@ -324,9 +325,10 @@ GameDatabase.reality.upgrades = [
name: "Synthetic Symbolism",
id: 24,
cost: 100000,
requirement: () => `Reality for ${formatInt(5000)} Reality Machines without Glyphs`,
hasFailed: () => Glyphs.activeList.length > 0,
checkRequirement: () => MachineHandler.gainedRealityMachines.gte(5000) && Glyphs.activeList.length === 0,
requirement: () => `Reality for ${formatInt(5000)} Reality Machines without equipped Glyphs`,
hasFailed: () => Glyphs.activeWithoutCompanion.length > 0,
checkRequirement: () => MachineHandler.gainedRealityMachines.gte(5000) &&
Glyphs.activeWithoutCompanion.length === 0,
checkEvent: GAME_EVENT.REALITY_RESET_BEFORE,
description: "Gain another Glyph slot",
effect: () => 1

View File

@ -6,7 +6,8 @@ GameDatabase.shopPurchases = {
key: "dimPurchases",
cost: 30,
description: "Double all your Antimatter Dimension multipliers. Forever.",
multiplier: purchases => Math.pow(2, purchases)
multiplier: purchases => Math.pow(2, purchases),
formatEffect: x => `×${Notation.scientific.formatDecimal(new Decimal(x))}`,
},
allDimPurchases: {
key: "allDimPurchases",
@ -18,12 +19,14 @@ GameDatabase.shopPurchases = {
return `Double ALL Dimension multipliers (${makeEnumeration(dims)}; multiplicative until 32x). Forever.`;
},
multiplier: purchases => (purchases > 4 ? 32 + (purchases - 5) * 2 : Math.pow(2, purchases)),
formatEffect: x => `×${x.toFixed(0)}`,
},
IPPurchases: {
key: "IPPurchases",
cost: 40,
description: "Double your Infinity Point gain from all sources. (additive)",
multiplier: purchases => (purchases === 0 ? 1 : 2 * purchases),
formatEffect: x => `×${x.toFixed(0)}`,
isUnlocked: () => PlayerProgress.infinityUnlocked(),
lockText: "Infinity",
},
@ -32,7 +35,7 @@ GameDatabase.shopPurchases = {
cost: 60,
description: "Increase your Replicanti gain by 50%. (additive)",
multiplier: purchases => (purchases === 0 ? 1 : 1 + 0.5 * purchases),
formatEffect: x => formatX(x, 2, 1),
formatEffect: x => `×${x.toFixed(1)}`,
isUnlocked: () => Replicanti.areUnlocked || PlayerProgress.eternityUnlocked(),
lockText: "Replicanti",
},
@ -41,6 +44,7 @@ GameDatabase.shopPurchases = {
cost: 50,
description: "Triple your Eternity Point gain from all sources. (additive)",
multiplier: purchases => (purchases === 0 ? 1 : 3 * purchases),
formatEffect: x => `×${x.toFixed(0)}`,
isUnlocked: () => PlayerProgress.eternityUnlocked(),
lockText: "Eternity",
},
@ -49,7 +53,7 @@ GameDatabase.shopPurchases = {
cost: 40,
description: "Increase your Dilated Time gain by 50%. (additive)",
multiplier: purchases => (purchases === 0 ? 1 : 1 + 0.5 * purchases),
formatEffect: x => formatX(x, 2, 1),
formatEffect: x => `×${x.toFixed(1)}`,
isUnlocked: () => PlayerProgress.dilationUnlocked() || PlayerProgress.realityUnlocked(),
lockText: "Dilation",
},
@ -58,7 +62,7 @@ GameDatabase.shopPurchases = {
cost: 60,
description: "Increase your Reality Machine gain by 100%. (additive)",
multiplier: purchases => purchases + 1,
formatEffect: x => formatX(x, 2),
formatEffect: x => `×${x.toFixed(0)}`,
isUnlocked: () => PlayerProgress.realityUnlocked(),
lockText: "Reality",
},

View File

@ -13,6 +13,10 @@ GameDatabase.tabNotifications = {
{
parent: "challenges",
tab: "normal"
},
{
parent: "statistics",
tab: "multipliers"
}
],
condition: () => !PlayerProgress.realityUnlocked() &&

View File

@ -124,11 +124,14 @@ class ShopPurchaseState extends RebuyableMechanicState {
return typeof cost === "function" ? cost() : cost;
}
// ShopPurchaseData for any particular key is undefined in between page load and STD load,
// so we need to guard against that causing NaNs to propagate through the save
get purchases() {
return ShopPurchaseData[this.config.key];
return ShopPurchaseData[this.config.key] ?? 0;
}
set purchases(value) {
if (!Number.isFinite(value)) return;
ShopPurchaseData[this.config.key] = value;
}

View File

@ -40,8 +40,11 @@ export const Speedrun = {
if (typeof player.options.animations[key] === "boolean") player.options.animations[key] = false;
}
// "Fake News" Achievement, given for free to partially mitigate promoting weird strategies at the beginning of runs
// A few achievements are given for free to mitigate weird strategies at the beginning of runs or unavoidable
// timewalls for particularly fast/optimized runs
Achievement(22).unlock();
Achievement(35).unlock();
Achievement(76).unlock();
// Some time elapses after the reset and before the UI is actually ready, which ends up getting "counted" as offline
player.speedrun.offlineTimeUsed = 0;

View File

@ -43,12 +43,14 @@ export const Cloud = {
try {
await signInWithPopup(this.auth, this.provider);
ShopPurchaseData.syncSTD();
GameUI.notify.success(`Logged in as ${this.user.displayName}`);
if (player.options.hideGoogleName) GameUI.notify.success(`Successfully logged in to Google Account`);
else GameUI.notify.success(`Successfully logged in as ${this.user.displayName}`);
} catch (e) {
GameUI.notify.error("Google Account login failed");
}
},
// NOTE: This function is largely untested due to not being used at any place within web reality code
async loadMobile() {
if (!this.user) return;
const snapshot = await get(ref(this.db, `users/${this.user.id}/player`));
@ -80,23 +82,16 @@ export const Cloud = {
},
async saveCheck(forceModal = false) {
const save = await this.load();
if (save === null) {
const saveId = GameStorage.currentSlot;
const cloudSave = await this.load();
if (cloudSave === null) {
this.save();
} else {
const root = GameSaveSerializer.deserialize(save);
const saveId = GameStorage.currentSlot;
const cloudSave = root.saves[saveId];
const thisCloudHash = sha512_256(GameSaveSerializer.serialize(cloudSave));
if (!this.lastCloudHash) this.lastCloudHash = thisCloudHash;
const localSave = GameStorage.saves[saveId];
const saveComparison = this.compareSaves(cloudSave, localSave, thisCloudHash);
// eslint-disable-next-line no-loop-func
const overwriteAndSendCloudSave = () => {
root.saves[saveId] = GameStorage.saves[saveId];
this.save(saveId);
};
const overwriteAndSendCloudSave = () => this.save();
// If the comparison fails, we assume the cloud data is corrupted and show the relevant modal
if (!saveComparison) {
@ -119,37 +114,38 @@ export const Cloud = {
}
},
save(slot) {
save() {
if (!this.user) return;
if (GlyphSelection.active || ui.$viewModel.modal.progressBar !== undefined) return;
if (player.options.syncSaveIntervals) GameStorage.save();
const root = {
current: GameStorage.currentSlot,
saves: GameStorage.saves,
};
const serializedSave = GameSaveSerializer.serialize(GameStorage.saves[GameStorage.currentSlot]);
this.lastCloudHash = sha512_256(GameSaveSerializer.serialize(root.saves[slot]));
this.lastCloudHash = sha512_256(serializedSave);
GameStorage.lastCloudSave = Date.now();
GameIntervals.checkCloudSave.restart();
set(ref(this.db, `users/${this.user.id}/web`), GameSaveSerializer.serialize(root));
GameUI.notify.info(`Game saved (slot ${slot + 1}) to cloud with user ${this.user.displayName}`);
const slot = GameStorage.currentSlot;
this.writeToCloudDB(slot, serializedSave);
if (player.options.hideGoogleName) GameUI.notify.info(`Game saved (slot ${slot + 1}) to cloud`);
else GameUI.notify.info(`Game saved (slot ${slot + 1}) to cloud as user ${this.user.displayName}`);
},
async loadCheck() {
const save = await this.load();
if (save === null) {
GameUI.notify.info(`No cloud save for user ${this.user.displayName}`);
if (player.options.hideGoogleName) GameUI.notify.info(`No cloud save for current Google Account`);
else GameUI.notify.info(`No cloud save for user ${this.user.displayName}`);
} else {
const root = GameSaveSerializer.deserialize(save);
const cloudSave = save;
const saveId = GameStorage.currentSlot;
const cloudSave = root.saves[saveId];
const localSave = GameStorage.saves[saveId];
const saveComparison = this.compareSaves(cloudSave, localSave);
// eslint-disable-next-line no-loop-func
const overwriteLocalSave = () => {
GameStorage.overwriteSlot(saveId, cloudSave);
GameUI.notify.info(`Cloud save (slot ${saveId + 1}) loaded for user ${this.user.displayName}`);
if (player.options.hideGoogleName) GameUI.notify.info(`Cloud save (slot ${saveId + 1}) loaded`);
else GameUI.notify.info(`Cloud save (slot ${saveId + 1}) loaded for user ${this.user.displayName}`);
};
// If the comparison fails, we assume the cloud data is corrupted and show the relevant modal
@ -173,12 +169,42 @@ export const Cloud = {
},
async load() {
const snapshot = await get(ref(this.db, `users/${this.user.id}/web`));
if (snapshot.exists) return snapshot.val();
let singleSlot = await this.readFromCloudDB(GameStorage.currentSlot);
if (singleSlot.exists()) return GameSaveSerializer.deserialize(singleSlot.val());
// If it doesn't exist, we assume that the cloud save hasn't been migrated yet and apply the migration before
// trying again. If it still doesn't exist, the cloud save was actually empty and there was nothing to migrate
await this.separateSaveSlots(combinedSlots.val());
singleSlot = await this.readFromCloudDB(GameStorage.currentSlot);
if (singleSlot.exists()) return GameSaveSerializer.deserialize(singleSlot.val());
return null;
},
// The initial implementation of cloud saving combined all save files in the same DB entry, but we have since changed
// it so that they're all saved in separate slots. The database itself retains the single-entry data until the first
// player load attempt after this change, at which point this is called client-side to do a one-time format migration
// Before the migration, saves were stored in ".../web" and afterward they have been moved to ".../web/1" and similar
async separateSaveSlots(oldData) {
const allData = GameSaveSerializer.deserialize(oldData);
if (!allData) return;
for (const slot of Object.keys(allData.saves)) {
const newData = GameSaveSerializer.serialize(allData.saves[slot]);
await this.writeToCloudDB(Number(slot), newData);
}
},
readFromCloudDB(slot) {
const slotStr = slot === null ? "" : `/${slot}`;
return get(ref(this.db, `users/${this.user.id}/web${slotStr}`));
},
writeToCloudDB(slot, data) {
const slotStr = slot === null ? "" : `/${slot}`;
return set(ref(this.db, `users/${this.user.id}/web${slotStr}`), data);
},
logout() {
signOut(this.auth);
ShopPurchaseData.clearLocalSTD();

View File

@ -157,6 +157,72 @@ GameStorage.migrations = {
GameStorage.migrations.moveTS33(player);
GameStorage.migrations.addBestPrestigeCurrency(player);
GameStorage.migrations.migrateTheme(player);
},
14: player => {
GameStorage.migrations.reworkBHPulsing(player);
// Added glyph auto-sort by level; in order to keep the button state cycling consistent with the sort buttons' UI
// order, AUTO_SORT_MODE had to be changed to insert LEVEL mode at the top and shift the others down. This
// makes sure that older saves maintain the same settings after this shift
if (player.reality.autoSort !== 0) player.reality.autoSort++;
},
15: player => {
// Added additional resource tracking in last 10 prestige records and adjusted data format to be more consistent
// by reordering to be [game time, real time, prestige currency, prestige count, challenge, ...(other resources)]
// Also fixes a migration bug where values could be undefined or null by assigning defaults when necessary
for (let i = 0; i < 10; i++) {
if (player.records.lastTenInfinities) {
const infRec = player.records.lastTenInfinities[i];
player.records.recentInfinities[i] = [
infRec[0] ?? Number.MAX_VALUE,
Number(infRec[3] ?? Number.MAX_VALUE),
new Decimal(infRec[1] ?? 1),
new Decimal(infRec[2] ?? 1),
""
];
}
if (player.records.lastTenEternities) {
const eterRec = player.records.lastTenEternities[i];
player.records.recentEternities[i] = [
eterRec[0] ?? Number.MAX_VALUE,
Number(eterRec[3] ?? Number.MAX_VALUE),
new Decimal(eterRec[1] ?? 1),
new Decimal(eterRec[2] ?? 1),
"",
new Decimal(0)
];
}
if (player.records.lastTenRealities) {
const realRec = player.records.lastTenRealities[i];
player.records.recentRealities[i] = [
realRec[0] ?? Number.MAX_VALUE,
Number(realRec[3] ?? Number.MAX_VALUE),
new Decimal(realRec[1] ?? 1),
realRec[2] ?? 1,
"",
0,
0
];
}
}
delete player.records.lastTenInfinities;
delete player.records.lastTenEternities;
delete player.records.lastTenRealities;
delete player.options.showLastTenResourceGain;
// Fixes a desync which occasionally causes unique > total seen due to total not being updated properly
if (player.news.seen) {
let unique = 0;
for (const bitmaskArray of Object.values(player.news.seen)) {
for (const bitmask of bitmaskArray) {
unique += countValuesFromBitmask(bitmask);
}
}
player.news.totalSeen = Math.max(player.news.totalSeen, unique);
}
}
},
@ -941,6 +1007,11 @@ GameStorage.migrations = {
delete player.options.secretThemeKey;
},
// This change removed the ability to adjust stored time rate after Ra-Nameless 10, instead forcing it to be 99%
reworkBHPulsing(player) {
delete player.celestials.enslaved.storedFraction;
},
prePatch(saveData) {
// Initialize all possibly undefined properties that were not present in
// previous versions and which could be overwritten by deepmerge

View File

@ -17,6 +17,10 @@ export const GameStorage = {
offlineEnabled: undefined,
offlineTicks: undefined,
maxOfflineTicks(simulatedMs, defaultTicks = this.offlineTicks) {
return Math.clampMax(defaultTicks, Math.floor(simulatedMs / 50));
},
get localStorageKey() {
return DEV ? "dimensionTestSave" : "dimensionSave";
},
@ -226,7 +230,7 @@ export const GameStorage = {
// Needed to check some notification about reality unlock study.
EventHub.dispatch(GAME_EVENT.SAVE_CONVERTED_FROM_PREVIOUS_VERSION);
}
if (DEV || player.options.testVersion !== undefined) {
if (DEV && player.options.testVersion !== undefined) {
this.devMigrations.patch(player);
}
}
@ -249,6 +253,7 @@ export const GameStorage = {
Enslaved.boostReality = false;
GameEnd.additionalEnd = 0;
Theme.set(Theme.currentName());
Glyphs.unseen = [];
Notations.find(player.options.notation).setAsCurrent(true);
ADNotations.Settings.exponentCommas.show = player.options.commas;

View File

@ -87,7 +87,7 @@ export function buyMaxTickSpeed() {
let boughtTickspeed = false;
Tutorial.turnOffEffect(TUTORIAL_STATE.TICKSPEED);
if (NormalChallenge(9).isRunning || InfinityChallenge(5).isRunning) {
if (NormalChallenge(9).isRunning) {
const goal = Player.infinityGoal;
let cost = Tickspeed.cost;
while (Currency.antimatter.gt(cost) && cost.lt(goal)) {
@ -127,7 +127,6 @@ export const Tickspeed = {
get isAvailableForPurchase() {
return this.isUnlocked &&
Currency.antimatter.lt(Player.infinityLimit) &&
!EternityChallenge(9).isRunning &&
!Laitela.continuumActive &&
(player.break || this.cost.lt(Decimal.NUMBER_MAX_VALUE));

View File

@ -71,6 +71,16 @@ function findLastOpenSubtab(tabId, subtabs) {
return subtabs.find(s => s.id === player.options.lastOpenSubtab[tabId]) ?? subtabs[0];
}
function cycleThroughSubtabs(subtabs, currentSubtab) {
const availableTabs = subtabs.filter(tab => tab.isAvailable);
const currentIndex = availableTabs.indexOf(currentSubtab);
const direction = ui.view.shiftDown ? -1 : 1;
let newIndex = currentIndex + direction;
newIndex = newIndex < 0 ? availableTabs.length - 1 : newIndex;
newIndex = newIndex > availableTabs.length - 1 ? 0 : newIndex;
return availableTabs[newIndex];
}
class TabState {
constructor(config) {
this.config = config;
@ -129,17 +139,19 @@ class TabState {
show(manual, subtab = undefined) {
if (!manual && !player.options.automaticTabSwitching || Quote.isOpen) return;
ui.view.tab = this.key;
if (subtab === undefined) {
this._currentSubtab = findLastOpenSubtab(this.id, this.subtabs);
} else {
if (subtab !== undefined) {
if (!Enslaved.isRunning) subtab.unhideTab();
this._currentSubtab = subtab;
} else if (ui.view.tab === this.key && ui.view.initialized) {
this._currentSubtab = cycleThroughSubtabs(this.subtabs, this._currentSubtab);
} else {
this._currentSubtab = findLastOpenSubtab(this.id, this.subtabs);
}
if (!this._currentSubtab.isUnlocked) this.resetToUnlocked();
if (!this._currentSubtab.isAvailable) this.resetToAvailable();
ui.view.tab = this.key;
ui.view.subtab = this._currentSubtab.key;
const tabNotificationKey = this.key + this._currentSubtab.key;
if (player.tabNotifications.has(tabNotificationKey)) player.tabNotifications.delete(tabNotificationKey);

View File

@ -2,11 +2,11 @@ import TWEEN from "tween.js";
import { DC } from "./core/constants";
import { deepmergeAll } from "@/utility/deepmerge";
import { DEV } from "./core/devtools";
import { SpeedrunMilestones } from "./core/speedrun";
import { supportedBrowsers } from "./supported-browsers";
import Payments from "./core/payments";
import { DEV } from "./core/devtools";
if (GlobalErrorHandler.handled) {
throw new Error("Initialization failed");
@ -75,6 +75,8 @@ export function breakInfinity() {
for (const autobuyer of Autobuyers.all) {
if (autobuyer.data.interval !== undefined) autobuyer.maxIntervalForFree();
}
// There's a potential migration edge case involving already-maxed autobuyers; this should give the achievement
Achievement(61).tryUnlock();
player.break = !player.break;
TabNotification.ICUnlock.tryTrigger();
EventHub.dispatch(player.break ? GAME_EVENT.BREAK_INFINITY : GAME_EVENT.FIX_INFINITY);
@ -177,42 +179,20 @@ export function ratePerMinute(amount, time) {
return Decimal.divide(amount, time / (60 * 1000));
}
export function averageRun(allRuns, name) {
// Filter out all runs which have the default infinite value for time, but if we're left with no valid runs then we
// take just one entry so that the averages also have the same value and we don't get division by zero.
let runs = allRuns.filter(run => run[0] !== Number.MAX_VALUE);
if (runs.length === 0) runs = [allRuns[0]];
const totalTime = runs.map(run => run[0]).sum();
const totalAmount = runs
.map(run => run[1])
.reduce(Decimal.sumReducer);
const totalPrestigeGain = runs
.map(run => run[2])
.reduce(name === "Reality" ? Number.sumReducer : Decimal.sumReducer);
const realTime = runs.map(run => run[3]).sum();
const average = [
totalTime / runs.length,
totalAmount.dividedBy(runs.length),
(name === "Reality") ? totalPrestigeGain / runs.length : totalPrestigeGain.dividedBy(runs.length),
realTime / runs.length
];
if (name === "Reality") {
average.push(runs.map(x => x[4]).sum() / runs.length);
}
return average;
}
// eslint-disable-next-line max-params
export function addInfinityTime(time, realTime, ip, infinities) {
player.records.lastTenInfinities.pop();
player.records.lastTenInfinities.unshift([time, ip, infinities, realTime]);
let challenge = "";
if (player.challenge.normal.current) challenge = `Normal Challenge ${player.challenge.normal.current}`;
if (player.challenge.infinity.current) challenge = `Infinity Challenge ${player.challenge.infinity.current}`;
player.records.recentInfinities.pop();
player.records.recentInfinities.unshift([time, realTime, ip, infinities, challenge]);
GameCache.bestRunIPPM.invalidate();
}
export function resetInfinityRuns() {
player.records.lastTenInfinities = Array.from(
player.records.recentInfinities = Array.from(
{ length: 10 },
() => [Number.MAX_VALUE, DC.D1, DC.D1, Number.MAX_VALUE]
() => [Number.MAX_VALUE, Number.MAX_VALUE, DC.D1, DC.D1, ""]
);
GameCache.bestRunIPPM.invalidate();
}
@ -227,15 +207,24 @@ export function getInfinitiedMilestoneReward(ms, considerMilestoneReached) {
// eslint-disable-next-line max-params
export function addEternityTime(time, realTime, ep, eternities) {
player.records.lastTenEternities.pop();
player.records.lastTenEternities.unshift([time, ep, eternities, realTime]);
let challenge = "";
if (player.challenge.eternity.current) {
const currEC = player.challenge.eternity.current;
const ec = EternityChallenge(currEC);
const challText = player.dilation.active ? "Dilated EC" : "Eternity Challenge";
challenge = `${challText} ${currEC} (${formatInt(ec.completions)}/${formatInt(ec.maxCompletions)})`;
} else if (player.dilation.active) challenge = "Time Dilation";
// If we call this function outside of dilation, it uses the existing AM and produces an erroneous number
const gainedTP = player.dilation.active ? getTachyonGain() : DC.D0;
player.records.recentEternities.pop();
player.records.recentEternities.unshift([time, realTime, ep, eternities, challenge, gainedTP]);
GameCache.averageRealTimePerEternity.invalidate();
}
export function resetEternityRuns() {
player.records.lastTenEternities = Array.from(
player.records.recentEternities = Array.from(
{ length: 10 },
() => [Number.MAX_VALUE, DC.D1, DC.D1, Number.MAX_VALUE]
() => [Number.MAX_VALUE, Number.MAX_VALUE, DC.D1, DC.D1, "", DC.D0]
);
GameCache.averageRealTimePerEternity.invalidate();
}
@ -260,8 +249,14 @@ export function getOfflineEPGain(ms) {
// eslint-disable-next-line max-params
export function addRealityTime(time, realTime, rm, level, realities) {
player.records.lastTenRealities.pop();
player.records.lastTenRealities.unshift([time, rm, realities, realTime, level]);
let reality = "";
const celestials = [Teresa, Effarig, Enslaved, V, Ra, Laitela];
for (const cel of celestials) {
if (cel.isRunning) reality = cel.displayName;
}
const shards = Effarig.shardsGained;
player.records.recentRealities.pop();
player.records.recentRealities.unshift([time, realTime, rm, realities, reality, level, shards]);
}
export function gainedInfinities() {
@ -346,9 +341,8 @@ export function getGameSpeedupFactor(effectsToConsider, blackHolesActiveOverride
factor = Math.pow(factor, getAdjustedGlyphEffect("effarigblackhole"));
}
// Time storage is linearly scaled because exponential scaling is pretty useless in practice
if (Enslaved.isStoringGameTime && effects.includes(GAME_SPEED_EFFECT.TIME_STORAGE)) {
const storedTimeWeight = player.celestials.enslaved.storedFraction;
const storedTimeWeight = Ra.unlocks.autoPulseTime.canBeApplied ? 0.99 : 1;
factor = factor * (1 - storedTimeWeight) + storedTimeWeight;
}
@ -534,7 +528,7 @@ export function gameLoop(passDiff, options = {}) {
Currency.realities.add(uncountabilityGain);
Currency.perkPoints.add(uncountabilityGain);
if (Perk.autocompleteEC1.canBeApplied && player.reality.autoEC) player.reality.lastAutoEC += realDiff;
if (Perk.autocompleteEC1.canBeApplied) player.reality.lastAutoEC += realDiff;
EternityChallenge(12).tryFail();
Achievements._power.invalidate();
@ -546,21 +540,13 @@ export function gameLoop(passDiff, options = {}) {
const gain = Math.clampMin(FreeTickspeed.fromShards(Currency.timeShards.value).newAmount - player.totalTickGained, 0);
player.totalTickGained += gain;
const currentIPmin = gainedInfinityPoints().dividedBy(Math.clampMin(0.0005, Time.thisInfinityRealTime.totalMinutes));
if (currentIPmin.gt(player.records.thisInfinity.bestIPmin) && Player.canCrunch)
player.records.thisInfinity.bestIPmin = currentIPmin;
updatePrestigeRates();
tryCompleteInfinityChallenges();
EternityChallenges.autoComplete.tick();
replicantiLoop(diff);
const currentEPmin = gainedEternityPoints().dividedBy(Math.clampMin(0.0005, Time.thisEternityRealTime.totalMinutes));
if (currentEPmin.gt(player.records.thisEternity.bestEPmin) && Player.canEternity)
player.records.thisEternity.bestEPmin = currentEPmin;
if (PlayerProgress.dilationUnlocked()) {
Currency.dilatedTime.add(getDilationGainPerSecond().times(diff / 1000));
}
@ -591,8 +577,8 @@ export function gameLoop(passDiff, options = {}) {
const teresa25 = !isInCelestialReality() && Ra.unlocks.unlockDilationStartingTP.canBeApplied;
if ((teresa1 || teresa25) && !Pelle.isDoomed) rewardTP();
if (!EnslavedProgress.hintsUnlocked.hasProgress && Enslaved.has(ENSLAVED_UNLOCKS.RUN) && !Enslaved.isCompleted) {
player.celestials.enslaved.hintUnlockProgress += Enslaved.isRunning ? realDiff : realDiff / 25;
if (Enslaved.canTickHintTimer) {
player.celestials.enslaved.hintUnlockProgress += Enslaved.isRunning ? realDiff : (realDiff * 0.4);
if (player.celestials.enslaved.hintUnlockProgress >= TimeSpan.fromHours(5).totalMilliseconds) {
EnslavedProgress.hintsUnlocked.giveProgress();
Enslaved.quotes.hintUnlock.show();
@ -630,6 +616,26 @@ export function gameLoop(passDiff, options = {}) {
PerformanceStats.end("Game Update");
}
function updatePrestigeRates() {
const currentIPmin = gainedInfinityPoints().dividedBy(Math.clampMin(0.0005, Time.thisInfinityRealTime.totalMinutes));
if (currentIPmin.gt(player.records.thisInfinity.bestIPmin) && Player.canCrunch) {
player.records.thisInfinity.bestIPmin = currentIPmin;
player.records.thisInfinity.bestIPminVal = gainedInfinityPoints();
}
const currentEPmin = gainedEternityPoints().dividedBy(Math.clampMin(0.0005, Time.thisEternityRealTime.totalMinutes));
if (currentEPmin.gt(player.records.thisEternity.bestEPmin) && Player.canEternity) {
player.records.thisEternity.bestEPmin = currentEPmin;
player.records.thisEternity.bestEPminVal = gainedEternityPoints();
}
const currentRSmin = Effarig.shardsGained / Math.clampMin(0.0005, Time.thisRealityRealTime.totalMinutes);
if (currentRSmin > player.records.thisReality.bestRSmin && isRealityAvailable()) {
player.records.thisReality.bestRSmin = currentRSmin;
player.records.thisReality.bestRSminVal = Effarig.shardsGained;
}
}
function passivePrestigeGen() {
let eternitiedGain = 0;
if (RealityUpgrade(14).isBought) {
@ -650,7 +656,7 @@ function passivePrestigeGen() {
let infGen = DC.D0;
if (BreakInfinityUpgrade.infinitiedGen.isBought) {
// Multipliers are done this way to explicitly exclude ach87 and TS32
infGen = infGen.plus(0.2 * Time.deltaTimeMs / Math.clampMin(33, player.records.bestInfinity.time));
infGen = infGen.plus(0.5 * Time.deltaTimeMs / Math.clampMin(50, player.records.bestInfinity.time));
infGen = infGen.timesEffectsOf(
RealityUpgrade(5),
RealityUpgrade(7),
@ -683,12 +689,7 @@ function applyAutoUnlockPerks() {
}
if (Perk.autounlockDilation3.canBeApplied) buyDilationUpgrade(DilationUpgrade.ttGenerator.id);
if (Perk.autounlockReality.canBeApplied) TimeStudy.reality.purchase(true);
if (player.eternityUpgrades.size < 6 && Perk.autounlockEU2.canBeApplied) {
const secondRow = EternityUpgrade.all.filter(u => u.id > 3);
for (const upgrade of secondRow) {
if (player.eternityPoints.gte(upgrade.cost / 1e10)) player.eternityUpgrades.add(upgrade.id);
}
}
applyEU2();
}
function laitelaRealityTick(realDiff) {
@ -872,7 +873,7 @@ export function simulateTime(seconds, real, fast) {
GameUI.notify.showBlackHoles = false;
// Limit the tick count (this also applies if the black hole is unlocked)
const maxTicks = GameStorage.offlineTicks ?? player.options.offlineTicks;
const maxTicks = GameStorage.maxOfflineTicks(1000 * seconds, GameStorage.offlineTicks ?? player.options.offlineTicks);
if (ticks > maxTicks && !real && !fast) {
ticks = maxTicks;
} else if (ticks > 50 && !real && fast) {

Binary file not shown.

BIN
public/images/s12-bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 465.39 513.15"><defs><style>.cls-1{fill:#bd1818;}.cls-2{fill:#e52320;}.cls-3{fill:#dc9f12;}.cls-4{fill:#f4df61;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="20.21 115.95 32.02 124.19 32.02 135.2 20.21 139.25 20.21 115.95"/><polygon class="cls-2" points="349.47 473.5 319.37 451.96 319.37 120.21 349.47 106.41 349.47 473.5"/><polygon class="cls-1" points="64.88 101.04 76.69 109.47 76.69 119.87 64.88 123.92 64.88 101.04"/><polygon class="cls-2" points="349.47 84.33 319.37 105.56 319.37 120.21 349.47 120.05 349.47 84.33"/><polygon class="cls-2" points="445.18 115.95 433.38 123.92 433.38 135.2 445.18 139.25 445.18 115.95"/><polygon class="cls-2" points="400.51 101.04 388.7 109.19 388.7 119.87 400.51 123.92 400.51 101.04"/><polygon class="cls-2" points="349.47 148.97 319.44 148.97 232.73 209.23 232.73 233.27 349.47 148.97"/><polygon class="cls-1" points="146.02 148.97 145.87 148.98 145.87 120.21 145.87 106.17 115.91 84.33 115.91 106.41 115.91 120.05 115.91 473.5 145.87 452.03 145.87 171.27 232.73 233.27 232.73 209.23 146.02 148.97"/><polygon class="cls-2" points="232.73 233.27 115.92 149.89 115.92 120.05 115.92 120.05 115.92 84.33 85.82 93.64 85.82 116.74 64.88 123.92 64.88 101.04 41.37 108.9 41.37 131.99 20.21 139.25 20.21 115.95 0 122.66 0 146.19 0 146.19 0 205.18 26.54 224.19 26.54 458.69 115.92 473.5 115.92 310.95 232.73 399.41 232.73 233.27"/><polygon class="cls-1" points="445.18 115.95 445.18 139.25 424.02 131.99 424.02 108.89 400.51 101.04 400.51 123.92 379.57 116.74 379.57 93.64 349.47 84.33 349.47 106.41 349.47 120.05 349.47 148.97 232.73 233.27 232.73 399.41 349.47 309.54 349.47 473.5 438.17 462.82 438.17 224.19 465.39 205.16 465.39 148.97 465.39 146.19 465.39 122.66 445.18 115.95"/><path class="cls-3" d="M232.7,416.27h0v96.89l.8-1.1,31.12-44.37c-4.5-8.09-6.84-40.89-8.06-70.72Z"/><path class="cls-3" d="M241.18,23.83l5.26,60,40.82,0,14.06,14.64-16.44,15.93-10.69-11.06h-8.71c-4.14,4.34-7.3,9.8-7.53,16.46v54.11L232.7,193.84h0V0a12.73,12.73,0,0,1,11.65,12.66Z"/><path class="cls-4" d="M232.68,416.27h0v96.89l-.81-1.1-31.11-44.37c4.49-8.09,6.84-40.89,8-70.72Z"/><path class="cls-4" d="M224,23.83l-5.07,60-40.82,0L164.07,98.48l16.44,15.93,10.69-11.06h8.71c4.14,4.34,7.3,9.8,7.53,16.46v54.11l25.25,19.92h0V0a12.73,12.73,0,0,0-11.64,12.66Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Some files were not shown because too many files have changed in this diff Show More