mirror of
https://github.com/IvarK/AntimatterDimensionsSourceCode.git
synced 2024-11-10 14:12:02 +00:00
f7ec26fc0a
credits
1115 lines
43 KiB
JavaScript
1115 lines
43 KiB
JavaScript
import { playFabLogin } from "./core/playfab.js";
|
|
import { DC } from "./core/constants.js";
|
|
import { SpeedrunMilestones } from "./core/speedrun.js";
|
|
import TWEEN from "tween.js";
|
|
import { deepmergeAll } from "@/utility/deepmerge";
|
|
import { supportedBrowsers } from "./supported-browsers";
|
|
|
|
if (GlobalErrorHandler.handled) {
|
|
throw new Error("Initialization failed");
|
|
}
|
|
GlobalErrorHandler.cleanStart = true;
|
|
|
|
export function playerInfinityUpgradesOnReset() {
|
|
|
|
const infinityUpgrades = new Set(
|
|
["timeMult", "dimMult", "timeMult2",
|
|
"skipReset1", "skipReset2", "unspentBonus",
|
|
"27Mult", "18Mult", "36Mult", "resetMult",
|
|
"skipReset3", "passiveGen", "45Mult",
|
|
"resetBoost", "galaxyBoost", "skipResetGalaxy",
|
|
"ipOffline"]
|
|
);
|
|
|
|
const breakInfinityUpgrades = new Set(
|
|
["timeMult", "dimMult", "timeMult2",
|
|
"skipReset1", "skipReset2", "unspentBonus",
|
|
"27Mult", "18Mult", "36Mult", "resetMult",
|
|
"skipReset3", "passiveGen", "45Mult",
|
|
"resetBoost", "galaxyBoost", "skipResetGalaxy",
|
|
"totalMult", "currentMult", "postGalaxy",
|
|
"challengeMult", "achievementMult", "infinitiedMult",
|
|
"infinitiedGeneration", "autoBuyerUpgrade", "autobuyMaxDimboosts",
|
|
"ipOffline"]
|
|
);
|
|
|
|
if (PelleUpgrade.keepBreakInfinityUpgrades.canBeApplied) {
|
|
player.infinityUpgrades = new Set([...player.infinityUpgrades].filter(u => breakInfinityUpgrades.has(u)));
|
|
return;
|
|
}
|
|
|
|
if (PelleUpgrade.keepInfinityUpgrades.canBeApplied) {
|
|
player.infinityUpgrades = new Set([...player.infinityUpgrades].filter(u => infinityUpgrades.has(u)));
|
|
player.infinityRebuyables = [0, 0, 0];
|
|
GameCache.tickSpeedMultDecrease.invalidate();
|
|
GameCache.dimensionMultDecrease.invalidate();
|
|
return;
|
|
}
|
|
|
|
if (RealityUpgrade(10).isBought || EternityMilestone.keepBreakUpgrades.isReached) {
|
|
player.infinityUpgrades = breakInfinityUpgrades;
|
|
player.infinityRebuyables = [8, 7, 10];
|
|
} else if (EternityMilestone.keepInfinityUpgrades.isReached) {
|
|
player.infinityUpgrades = infinityUpgrades;
|
|
player.infinityRebuyables = [0, 0, 0];
|
|
} else {
|
|
player.infinityUpgrades.clear();
|
|
player.infinityRebuyables = [0, 0, 0];
|
|
}
|
|
|
|
if (Pelle.isDoomed) {
|
|
player.infinityUpgrades.clear();
|
|
player.infinityRebuyables = [0, 0, 0];
|
|
}
|
|
|
|
GameCache.tickSpeedMultDecrease.invalidate();
|
|
GameCache.dimensionMultDecrease.invalidate();
|
|
}
|
|
|
|
export function breakInfinity() {
|
|
if (!Autobuyer.bigCrunch.hasMaxedInterval) return;
|
|
if (InfinityChallenge.isRunning) return;
|
|
for (const autobuyer of Autobuyers.all) {
|
|
if (autobuyer.data.interval !== undefined) autobuyer.maxIntervalForFree();
|
|
}
|
|
player.break = !player.break;
|
|
TabNotification.ICUnlock.tryTrigger();
|
|
EventHub.dispatch(player.break ? GAME_EVENT.BREAK_INFINITY : GAME_EVENT.FIX_INFINITY);
|
|
GameUI.update();
|
|
}
|
|
|
|
export function gainedInfinityPoints() {
|
|
const div = Effects.min(
|
|
308,
|
|
Achievement(103),
|
|
TimeStudy(111)
|
|
);
|
|
const mult = NG.multiplier;
|
|
const pow = NG.power;
|
|
if (Pelle.isDisabled("IPMults")) {
|
|
return Decimal.pow10(player.records.thisInfinity.maxAM.log10() / div - 0.75)
|
|
.timesEffectsOf(PelleRifts.famine)
|
|
.times(Pelle.specialGlyphEffect.infinity)
|
|
.times(mult)
|
|
.pow(pow)
|
|
.floor();
|
|
}
|
|
let ip = player.break
|
|
? Decimal.pow10(player.records.thisInfinity.maxAM.log10() / div - 0.75)
|
|
: new Decimal(308 / div);
|
|
if (Effarig.isRunning && Effarig.currentStage === EFFARIG_STAGES.ETERNITY) {
|
|
ip = ip.min(DC.E200);
|
|
}
|
|
ip = ip.times(mult);
|
|
ip = ip.times(GameCache.totalIPMult.value);
|
|
if (Teresa.isRunning) {
|
|
ip = ip.pow(0.55);
|
|
} else if (V.isRunning) {
|
|
ip = ip.pow(0.5);
|
|
} else if (Laitela.isRunning) {
|
|
ip = dilatedValueOf(ip);
|
|
}
|
|
if (GlyphAlteration.isAdded("infinity")) {
|
|
ip = ip.pow(getSecondaryGlyphEffect("infinityIP"));
|
|
}
|
|
|
|
ip = ip.pow(pow);
|
|
return ip.floor();
|
|
}
|
|
|
|
function totalEPMult() {
|
|
const totalMult = new Decimal(NG.multiplier);
|
|
return Pelle.isDisabled("EPMults")
|
|
? totalMult.times(Pelle.specialGlyphEffect.time.timesEffectOf(PelleRifts.famine.milestones[2]))
|
|
: totalMult.times(getAdjustedGlyphEffect("cursedEP"))
|
|
.times(ShopPurchase.EPPurchases.currentMult)
|
|
.timesEffectsOf(
|
|
EternityUpgrade.epMult,
|
|
TimeStudy(61),
|
|
TimeStudy(122),
|
|
TimeStudy(121),
|
|
TimeStudy(123),
|
|
RealityUpgrade(12),
|
|
GlyphEffect.epMult
|
|
);
|
|
}
|
|
|
|
export function gainedEternityPoints() {
|
|
const pow = NG.power;
|
|
let ep = DC.D5.pow(player.records.thisEternity.maxIP.plus(
|
|
gainedInfinityPoints()).log10() / (308 - PelleRifts.war.effectValue.toNumber()) - 0.7).times(totalEPMult());
|
|
|
|
if (Teresa.isRunning) {
|
|
ep = ep.pow(0.55);
|
|
} else if (V.isRunning) {
|
|
ep = ep.pow(0.5);
|
|
} else if (Laitela.isRunning) {
|
|
ep = dilatedValueOf(ep);
|
|
}
|
|
if (GlyphAlteration.isAdded("time")) {
|
|
ep = ep.pow(getSecondaryGlyphEffect("timeEP"));
|
|
}
|
|
|
|
return ep.pow(pow).floor();
|
|
}
|
|
|
|
export function requiredIPForEP(epAmount) {
|
|
return Decimal.pow10(308 * (Decimal.log(Decimal.divide(Math.pow(epAmount, 1 / NG.power), totalEPMult()), 5) + 0.7))
|
|
.clampMin(Number.MAX_VALUE);
|
|
}
|
|
|
|
export function gainedGlyphLevel() {
|
|
const glyphState = getGlyphLevelInputs();
|
|
let rawLevel = Math.floor(glyphState.rawLevel);
|
|
if (!isFinite(rawLevel)) rawLevel = 0;
|
|
let actualLevel = Math.floor(glyphState.actualLevel);
|
|
if (!isFinite(actualLevel)) actualLevel = 0;
|
|
return {
|
|
rawLevel,
|
|
actualLevel
|
|
};
|
|
}
|
|
|
|
export function resetChallengeStuff() {
|
|
player.chall2Pow = 1;
|
|
player.chall3Pow = DC.D0_01;
|
|
Currency.matter.reset();
|
|
player.chall8TotalSacrifice = DC.D1;
|
|
player.postC4Tier = 1;
|
|
}
|
|
|
|
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]);
|
|
GameCache.bestRunIPPM.invalidate();
|
|
}
|
|
|
|
export function resetInfinityRuns() {
|
|
player.records.lastTenInfinities = Array.from(
|
|
{ length: 10 },
|
|
() => [Number.MAX_VALUE, DC.D1, DC.D1, Number.MAX_VALUE]
|
|
);
|
|
GameCache.bestRunIPPM.invalidate();
|
|
}
|
|
|
|
// Player gains 50% of infinities they would get based on their best infinities/hour crunch if they have the
|
|
// milestone and turned on infinity autobuyer with 1 minute or less per crunch
|
|
export function getInfinitiedMilestoneReward(ms, considerMilestoneReached) {
|
|
return Autobuyer.bigCrunch.autoInfinitiesAvailable(considerMilestoneReached)
|
|
? Decimal.floor(player.records.thisEternity.bestInfinitiesPerMs.times(ms).dividedBy(2))
|
|
: DC.D0;
|
|
}
|
|
|
|
// 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]);
|
|
GameCache.averageRealTimePerEternity.invalidate();
|
|
}
|
|
|
|
export function resetEternityRuns() {
|
|
player.records.lastTenEternities = Array.from(
|
|
{ length: 10 },
|
|
() => [Number.MAX_VALUE, DC.D1, DC.D1, Number.MAX_VALUE]
|
|
);
|
|
GameCache.averageRealTimePerEternity.invalidate();
|
|
}
|
|
|
|
// Player gains 50% of the eternities they would get if they continuously repeated their fastest eternity, if they
|
|
// have the auto-eternity milestone and turned on eternity autobuyer with 0 EP
|
|
export function getEternitiedMilestoneReward(ms, considerMilestoneReached) {
|
|
return Autobuyer.eternity.autoEternitiesAvailable(considerMilestoneReached)
|
|
? Decimal.floor(player.records.thisReality.bestEternitiesPerMs.times(ms).dividedBy(2))
|
|
: DC.D0;
|
|
}
|
|
|
|
function isOfflineEPGainEnabled() {
|
|
return player.options.offlineProgress && !Autobuyer.bigCrunch.autoInfinitiesAvailable() &&
|
|
!Autobuyer.eternity.autoEternitiesAvailable();
|
|
}
|
|
|
|
export function getOfflineEPGain(ms) {
|
|
if (!EternityMilestone.autoEP.isReached || !isOfflineEPGainEnabled()) return DC.D0;
|
|
return player.records.bestEternity.bestEPminReality.times(TimeSpan.fromMilliseconds(ms).totalMinutes / 4);
|
|
}
|
|
|
|
// 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]);
|
|
}
|
|
|
|
export function gainedInfinities() {
|
|
if (EternityChallenge(4).isRunning || Pelle.isDisabled("InfinitiedMults")) {
|
|
return DC.D1;
|
|
}
|
|
let infGain = Effects.max(
|
|
1,
|
|
Achievement(87)
|
|
).toDecimal();
|
|
|
|
infGain = infGain.timesEffectsOf(
|
|
TimeStudy(32),
|
|
RealityUpgrade(5),
|
|
RealityUpgrade(7),
|
|
Achievement(164),
|
|
Ra.unlocks.continuousTTBoost.effects.infinity
|
|
);
|
|
infGain = infGain.times(getAdjustedGlyphEffect("infinityinfmult"));
|
|
infGain = infGain.powEffectOf(SingularityMilestone.infinitiedPow);
|
|
return infGain;
|
|
}
|
|
|
|
export function updateRefresh() {
|
|
GameStorage.save(true);
|
|
location.reload(true);
|
|
}
|
|
|
|
export const GAME_SPEED_EFFECT = {
|
|
FIXED_SPEED: 1,
|
|
TIME_GLYPH: 2,
|
|
BLACK_HOLE: 3,
|
|
TIME_STORAGE: 4,
|
|
SINGULARITY_MILESTONE: 5,
|
|
NERFS: 6
|
|
};
|
|
|
|
/**
|
|
* @param {number[]?} effectsToConsider A list of various game speed changing effects to apply when calculating
|
|
* the game speed. If left undefined, all effects will be applied.
|
|
* @param {number?} blackHolesActiveOverride A numerical value which forces all black holes up to its specified index
|
|
* to be active for the purposes of game speed calculation. This is only used during offline black hole stuff.
|
|
*/
|
|
export function getGameSpeedupFactor(effectsToConsider, blackHolesActiveOverride) {
|
|
let effects;
|
|
if (effectsToConsider === undefined) {
|
|
effects = [GAME_SPEED_EFFECT.FIXED_SPEED, GAME_SPEED_EFFECT.TIME_GLYPH, GAME_SPEED_EFFECT.BLACK_HOLE,
|
|
GAME_SPEED_EFFECT.TIME_STORAGE, GAME_SPEED_EFFECT.SINGULARITY_MILESTONE, GAME_SPEED_EFFECT.NERFS];
|
|
} else {
|
|
effects = effectsToConsider;
|
|
}
|
|
|
|
if (effects.includes(GAME_SPEED_EFFECT.FIXED_SPEED)) {
|
|
if (EternityChallenge(12).isRunning) {
|
|
return 1 / 1000;
|
|
}
|
|
}
|
|
|
|
let factor = 1;
|
|
if (effects.includes(GAME_SPEED_EFFECT.BLACK_HOLE)) {
|
|
if (BlackHoles.arePaused) {
|
|
factor *= player.blackHoleNegative;
|
|
} else {
|
|
for (const blackHole of BlackHoles.list) {
|
|
if (!blackHole.isUnlocked) break;
|
|
const isActive = blackHolesActiveOverride === undefined
|
|
? blackHole.isActive
|
|
: blackHole.id <= blackHolesActiveOverride;
|
|
if (!isActive) break;
|
|
factor *= Math.pow(blackHole.power, BlackHoles.unpauseAccelerationFactor);
|
|
factor *= VUnlocks.achievementBH.effectOrDefault(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (effects.includes(GAME_SPEED_EFFECT.SINGULARITY_MILESTONE)) {
|
|
factor *= SingularityMilestone.gamespeedFromSingularities.canBeApplied
|
|
? SingularityMilestone.gamespeedFromSingularities.effectValue
|
|
: 1;
|
|
}
|
|
|
|
if (effects.includes(GAME_SPEED_EFFECT.TIME_GLYPH)) {
|
|
factor *= getAdjustedGlyphEffect("timespeed");
|
|
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;
|
|
factor = factor * (1 - storedTimeWeight) + storedTimeWeight;
|
|
}
|
|
|
|
// These effects should always be active, but need to be disabled during offline black hole simulations because
|
|
// otherwise it gets applied twice
|
|
if (effects.includes(GAME_SPEED_EFFECT.NERFS)) {
|
|
if (Effarig.isRunning) {
|
|
factor = Effarig.multiplier(factor).toNumber();
|
|
} else if (Laitela.isRunning) {
|
|
const nerfModifier = Math.clampMax(Time.thisRealityRealTime.totalMinutes / 10, 1);
|
|
factor = Math.pow(factor, nerfModifier);
|
|
}
|
|
}
|
|
|
|
|
|
factor *= PelleUpgrade.timeSpeedMult.effectValue.toNumber();
|
|
|
|
// 1e-300 is now possible with max inverted BH, going below it would be possible with
|
|
// an effarig glyph.
|
|
factor = Math.clamp(factor, 1e-300, 1e300);
|
|
|
|
// Dev speedup should always be active
|
|
if (tempSpeedupToggle) {
|
|
factor *= tempSpeedupFactor;
|
|
}
|
|
return factor;
|
|
}
|
|
|
|
export function getGameSpeedupForDisplay() {
|
|
const speedFactor = getGameSpeedupFactor();
|
|
if (
|
|
Enslaved.isAutoReleasing &&
|
|
Enslaved.canRelease(true) &&
|
|
!BlackHoles.areNegative &&
|
|
!Pelle.isDisabled("blackhole")
|
|
) {
|
|
return Math.max(Enslaved.autoReleaseSpeed, speedFactor);
|
|
}
|
|
return speedFactor;
|
|
}
|
|
|
|
// "diff" is in ms. It is only unspecified when it's being called normally and not due to simulating time, in which
|
|
// case it uses the gap between now and the last time the function was called. This is on average equal to the update
|
|
// rate.
|
|
// TODO: Clean this up, remove the disable line
|
|
// eslint-disable-next-line complexity
|
|
export function gameLoop(passDiff, options = {}) {
|
|
let diff = passDiff;
|
|
PerformanceStats.start("Frame Time");
|
|
PerformanceStats.start("Game Update");
|
|
EventHub.dispatch(GAME_EVENT.GAME_TICK_BEFORE);
|
|
const thisUpdate = Date.now();
|
|
const realDiff = diff === undefined
|
|
? Math.clamp(thisUpdate - player.lastUpdate, 1, 21600000)
|
|
: diff;
|
|
|
|
// We want to allow for a speedrunner to be able to adjust their visual settings before actually starting the run,
|
|
// which means that we need to effectively halt the game loop until the official start
|
|
if (Speedrun.isPausedAtStart()) {
|
|
GameUI.update();
|
|
return;
|
|
}
|
|
|
|
// Ra memory generation bypasses stored real time, but memory chunk generation is disabled when storing real time.
|
|
// This is in order to prevent players from using time inside of Ra's reality for amplification as well
|
|
Ra.memoryTick(realDiff, !Enslaved.isStoringRealTime);
|
|
if (AlchemyResource.momentum.isUnlocked) {
|
|
player.celestials.ra.momentumTime += realDiff * Achievement(175).effectOrDefault(1);
|
|
}
|
|
|
|
// Lai'tela mechanics should bypass stored real time entirely
|
|
DarkMatterDimensions.tick(realDiff);
|
|
|
|
// When storing real time, skip everything else having to do with production once stats are updated
|
|
if (Enslaved.isStoringRealTime) {
|
|
player.records.realTimePlayed += realDiff;
|
|
player.records.thisInfinity.realTime += realDiff;
|
|
player.records.thisEternity.realTime += realDiff;
|
|
player.records.thisReality.realTime += realDiff;
|
|
Enslaved.storeRealTime();
|
|
GameUI.update();
|
|
return;
|
|
}
|
|
|
|
// Ra-Enslaved auto-release stored time (once every 5 ticks)
|
|
if (Enslaved.isAutoReleasing) {
|
|
Enslaved.autoReleaseTick++;
|
|
}
|
|
if (Enslaved.autoReleaseTick >= 5) {
|
|
Enslaved.autoReleaseTick = 0;
|
|
Enslaved.useStoredTime(true);
|
|
Enslaved.isReleaseTick = true;
|
|
} else if (!Enslaved.isReleaseTick) {
|
|
Enslaved.nextTickDiff = realDiff;
|
|
}
|
|
if (diff === undefined) {
|
|
diff = Enslaved.nextTickDiff;
|
|
}
|
|
|
|
Autobuyers.tick();
|
|
Tutorial.tutorialLoop();
|
|
|
|
if (Achievement(165).isUnlocked && player.celestials.effarig.autoAdjustGlyphWeights) {
|
|
autoAdjustGlyphWeights();
|
|
}
|
|
|
|
// We do these after autobuyers, since it's possible something there might
|
|
// change a multiplier.
|
|
GameCache.antimatterDimensionCommonMultiplier.invalidate();
|
|
GameCache.antimatterDimensionFinalMultipliers.invalidate();
|
|
GameCache.infinityDimensionCommonMultiplier.invalidate();
|
|
GameCache.timeDimensionCommonMultiplier.invalidate();
|
|
GameCache.totalIPMult.invalidate();
|
|
|
|
const blackHoleDiff = realDiff;
|
|
const fixedSpeedActive = EternityChallenge(12).isRunning;
|
|
if (!Enslaved.isReleaseTick && !fixedSpeedActive) {
|
|
let speedFactor;
|
|
if (options.blackHoleSpeedup === undefined) {
|
|
speedFactor = getGameSpeedupFactor();
|
|
} else {
|
|
// This is only called from simulateTime() and is calculated externally in order to avoid weirdness when game
|
|
// speed is directly nerfed
|
|
speedFactor = options.blackHoleSpeedup;
|
|
}
|
|
|
|
if (Enslaved.isStoringGameTime && !fixedSpeedActive) {
|
|
// These variables are the actual game speed used and the game speed unaffected by time storage, respectively
|
|
const reducedTimeFactor = getGameSpeedupFactor();
|
|
const totalTimeFactor = getGameSpeedupFactor([GAME_SPEED_EFFECT.FIXED_SPEED, GAME_SPEED_EFFECT.TIME_GLYPH,
|
|
GAME_SPEED_EFFECT.BLACK_HOLE, GAME_SPEED_EFFECT.SINGULARITY_MILESTONE]);
|
|
const amplification = Ra.unlocks.improvedStoredTime.effects.gameTimeAmplification.effectOrDefault(1);
|
|
const beforeStore = player.celestials.enslaved.stored;
|
|
player.celestials.enslaved.stored = Math.clampMax(player.celestials.enslaved.stored +
|
|
diff * (totalTimeFactor - reducedTimeFactor) * amplification, Enslaved.timeCap);
|
|
Enslaved.currentBlackHoleStoreAmountPerMs = (player.celestials.enslaved.stored - beforeStore) / diff;
|
|
speedFactor = reducedTimeFactor;
|
|
}
|
|
diff *= speedFactor;
|
|
} else if (fixedSpeedActive) {
|
|
diff *= getGameSpeedupFactor();
|
|
Enslaved.currentBlackHoleStoreAmountPerMs = 0;
|
|
}
|
|
player.celestials.ra.peakGamespeed = Math.max(player.celestials.ra.peakGamespeed, getGameSpeedupFactor());
|
|
Enslaved.isReleaseTick = false;
|
|
|
|
// These need to all be done consecutively in order to minimize the chance of a reset occurring between real time
|
|
// updating and game time updating. This is only particularly noticeable when game speed is 1 and the player
|
|
// expects to see identical numbers.
|
|
player.records.realTimeDoomed += realDiff;
|
|
player.records.realTimePlayed += realDiff;
|
|
player.records.totalTimePlayed += diff;
|
|
player.records.thisInfinity.realTime += realDiff;
|
|
player.records.thisInfinity.time += diff;
|
|
player.records.thisEternity.realTime += realDiff;
|
|
if (Enslaved.isRunning && Enslaved.feltEternity && !EternityChallenge(12).isRunning) {
|
|
player.records.thisEternity.time += diff * (1 + Currency.eternities.value.clampMax(1e66).toNumber());
|
|
} else {
|
|
player.records.thisEternity.time += diff;
|
|
}
|
|
player.records.thisReality.realTime += realDiff;
|
|
player.records.thisReality.time += diff;
|
|
|
|
DeltaTimeState.update(realDiff, diff);
|
|
|
|
updateNormalAndInfinityChallenges(diff);
|
|
|
|
// IP generation is broken into a couple of places in gameLoop; changing that might change the
|
|
// behavior of eternity farming.
|
|
preProductionGenerateIP(diff);
|
|
|
|
if (!Pelle.isDoomed) {
|
|
passivePrestigeGen();
|
|
}
|
|
|
|
|
|
applyAutoprestige(realDiff);
|
|
updateImaginaryMachines(realDiff);
|
|
|
|
const uncountabilityGain = AlchemyResource.uncountability.effectValue * Time.unscaledDeltaTime.totalSeconds;
|
|
Currency.realities.add(uncountabilityGain);
|
|
Currency.perkPoints.add(uncountabilityGain);
|
|
|
|
if (Perk.autocompleteEC1.isBought && player.reality.autoEC) player.reality.lastAutoEC += realDiff;
|
|
|
|
EternityChallenge(12).tryFail();
|
|
Achievements._power.invalidate();
|
|
|
|
TimeDimensions.tick(diff);
|
|
InfinityDimensions.tick(diff);
|
|
AntimatterDimensions.tick(diff);
|
|
|
|
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;
|
|
|
|
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));
|
|
}
|
|
|
|
updateTachyonGalaxies();
|
|
Currency.timeTheorems.add(getTTPerSecond().times(diff / 1000));
|
|
InfinityDimensions.tryAutoUnlock();
|
|
|
|
BlackHoles.updatePhases(blackHoleDiff);
|
|
|
|
// Unlocks dilation at a certain total TT count for free, but we add the cost first in order to make
|
|
// sure that TT count doesn't go negative and that we can actually buy it. This technically bumps the max theorem
|
|
// amount up as well, but at this point of the game 5k TT is insignificant to basically all other sources of TT.
|
|
if (Ra.unlocks.autoUnlockDilation.canBeApplied &&
|
|
Currency.timeTheorems.max.gte(TimeStudy.dilation.totalTimeTheoremRequirement) &&
|
|
!isInCelestialReality() &&
|
|
!Pelle.isDoomed) {
|
|
Currency.timeTheorems.add(TimeStudy.dilation.cost);
|
|
TimeStudy.dilation.purchase(true);
|
|
}
|
|
|
|
applyAutoUnlockPerks();
|
|
if (GlyphSelection.active) GlyphSelection.update(gainedGlyphLevel());
|
|
|
|
if (player.dilation.active && Ra.unlocks.autoTP.canBeApplied && !Pelle.isDoomed) rewardTP();
|
|
|
|
if (!EnslavedProgress.hintsUnlocked.hasProgress && Enslaved.has(ENSLAVED_UNLOCKS.RUN) && !Enslaved.isCompleted) {
|
|
player.celestials.enslaved.hintUnlockProgress += Enslaved.isRunning ? realDiff : realDiff / 25;
|
|
if (player.celestials.enslaved.hintUnlockProgress >= TimeSpan.fromHours(5).totalMilliseconds) {
|
|
EnslavedProgress.hintsUnlocked.giveProgress();
|
|
Enslaved.quotes.show(Enslaved.quotes.HINT_UNLOCK);
|
|
}
|
|
}
|
|
|
|
laitelaRealityTick(realDiff);
|
|
Achievements.autoAchieveUpdate(diff);
|
|
V.checkForUnlocks();
|
|
AutomatorBackend.update(realDiff);
|
|
Pelle.gameLoop(realDiff);
|
|
GalaxyGenerator.loop(realDiff);
|
|
GameEnd.gameLoop(realDiff);
|
|
|
|
if (Tabs.current.isPermanentlyHidden) {
|
|
const tab = Tabs.all.reverse().find(t => !t.isPermanentlyHidden && t.id !== 10);
|
|
if (tab) tab.show(true);
|
|
else [...Tab.dimensions.subtabs].reverse().find(t => !t.isPermanentlyHidden).show(true);
|
|
}
|
|
|
|
if (Tabs.current.subtabs.find(t => t.isOpen).isPermanentlyHidden) {
|
|
[...Tab.dimensions.subtabs].reverse().find(t => !t.isPermanentlyHidden).show(true);
|
|
}
|
|
|
|
EventHub.dispatch(GAME_EVENT.GAME_TICK_AFTER);
|
|
GameUI.update();
|
|
player.lastUpdate = thisUpdate;
|
|
PerformanceStats.end("Game Update");
|
|
}
|
|
|
|
function passivePrestigeGen() {
|
|
let eternitiedGain = 0;
|
|
if (RealityUpgrade(14).isBought) {
|
|
eternitiedGain = Effects.product(
|
|
RealityUpgrade(3),
|
|
RealityUpgrade(14)
|
|
);
|
|
eternitiedGain = Decimal.times(eternitiedGain, getAdjustedGlyphEffect("timeetermult"));
|
|
eternitiedGain = new Decimal(Time.deltaTime).times(
|
|
Decimal.pow(eternitiedGain, AlchemyResource.eternity.effectValue));
|
|
player.reality.partEternitied = player.reality.partEternitied.plus(eternitiedGain);
|
|
Currency.eternities.add(player.reality.partEternitied.floor());
|
|
player.reality.partEternitied = player.reality.partEternitied.sub(player.reality.partEternitied.floor());
|
|
}
|
|
|
|
if (!EternityChallenge(4).isRunning) {
|
|
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.timesEffectsOf(
|
|
RealityUpgrade(5),
|
|
RealityUpgrade(7),
|
|
Ra.unlocks.continuousTTBoost.effects.infinity
|
|
);
|
|
infGen = infGen.times(getAdjustedGlyphEffect("infinityinfmult"));
|
|
}
|
|
if (RealityUpgrade(11).isBought) {
|
|
infGen = infGen.plus(RealityUpgrade(11).effectValue.times(Time.deltaTime));
|
|
}
|
|
if (EffarigUnlock.eternity.isUnlocked) {
|
|
// We consider half of the eternities we gained above this tick
|
|
// to have been gained before the infinities, and thus not to
|
|
// count here. This gives us the desirable behavior that
|
|
// infinities and eternities gained overall will be the same
|
|
// for two ticks as for one tick of twice the length.
|
|
infGen = infGen.plus(gainedInfinities().times(
|
|
Currency.eternities.value.minus(eternitiedGain.div(2).floor())).times(Time.deltaTime));
|
|
}
|
|
infGen = infGen.plus(player.partInfinitied);
|
|
Currency.infinities.add(infGen.floor());
|
|
player.partInfinitied = infGen.minus(infGen.floor()).toNumber();
|
|
}
|
|
}
|
|
|
|
// Applies all perks which automatically unlock things when passing certain thresholds, needs to be checked every tick
|
|
function applyAutoUnlockPerks() {
|
|
if (Pelle.isDoomed) return;
|
|
if (!TimeDimension(8).isUnlocked && Perk.autounlockTD.isBought) {
|
|
for (let dim = 5; dim <= 8; ++dim) TimeStudy.timeDimension(dim).purchase();
|
|
}
|
|
if (Perk.autounlockDilation3.isBought) buyDilationUpgrade(DilationUpgrade.ttGenerator.id);
|
|
if (Perk.autounlockReality.isBought) TimeStudy.reality.purchase(true);
|
|
if (player.eternityUpgrades.size < 6 && Perk.autounlockEU2.isBought) {
|
|
const secondRow = Object.values(EternityUpgrade).filter(u => u.id > 3);
|
|
for (const upgrade of secondRow) {
|
|
if (player.eternityPoints.gte(upgrade.cost / 1e10)) player.eternityUpgrades.add(upgrade.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
function laitelaRealityTick(realDiff) {
|
|
const laitelaInfo = player.celestials.laitela;
|
|
if (!Laitela.isRunning) return;
|
|
if (laitelaInfo.entropy >= 0) {
|
|
laitelaInfo.entropy += (realDiff / 1000) * Laitela.entropyGainPerSecond;
|
|
}
|
|
|
|
// Setting entropy to -1 on completion prevents the modal from showing up repeatedly
|
|
if (laitelaInfo.entropy >= 1) {
|
|
let completionText = `Lai'tela's Reality has been destabilized after ${Time.thisRealityRealTime.toStringShort()}.`;
|
|
laitelaInfo.entropy = -1;
|
|
const oldInfo = {
|
|
fastestCompletion: laitelaInfo.fastestCompletion,
|
|
difficultyTier: laitelaInfo.difficultyTier,
|
|
realityReward: Laitela.realityReward
|
|
};
|
|
laitelaInfo.thisCompletion = Time.thisRealityRealTime.totalSeconds;
|
|
laitelaInfo.fastestCompletion = Math.min(laitelaInfo.thisCompletion, laitelaInfo.fastestCompletion);
|
|
clearCelestialRuns();
|
|
if (Time.thisRealityRealTime.totalSeconds < 30) {
|
|
laitelaInfo.difficultyTier++;
|
|
laitelaInfo.fastestCompletion = 300;
|
|
completionText += laitelaBeatText(Laitela.maxAllowedDimension + 1);
|
|
for (const quote of Object.values(Laitela.quotes)) {
|
|
if (laitelaInfo.difficultyTier >= quote.destabilize) {
|
|
Laitela.quotes.show(quote);
|
|
}
|
|
}
|
|
}
|
|
if (Laitela.realityReward > oldInfo.realityReward) {
|
|
completionText += `<br><br>Dark Matter Multiplier: ${formatX(oldInfo.realityReward, 2, 2)}
|
|
➜ ${formatX(Laitela.realityReward, 2, 2)}
|
|
<br>Best Completion Time: ${TimeSpan.fromSeconds(oldInfo.fastestCompletion).toStringShort()}
|
|
(${formatInt(8 - oldInfo.difficultyTier)}) ➜
|
|
${TimeSpan.fromSeconds(laitelaInfo.fastestCompletion).toStringShort()}
|
|
(${formatInt(8 - laitelaInfo.difficultyTier)})`;
|
|
player.records.bestReality.laitelaSet = Glyphs.copyForRecords(Glyphs.active.filter(g => g !== null));
|
|
} else {
|
|
completionText += ` You need to destabilize in faster than
|
|
${TimeSpan.fromSeconds(laitelaInfo.fastestCompletion).toStringShort()} to improve your multiplier.`;
|
|
}
|
|
if (Laitela.isFullyDestabilized) SpeedrunMilestones(24).tryComplete();
|
|
Modal.message.show(completionText);
|
|
}
|
|
}
|
|
|
|
function laitelaBeatText(disabledDim) {
|
|
switch (disabledDim) {
|
|
case 1: return `<br><br>Lai'tela's Reality will now completely disable production from all Dimensions.
|
|
The Reality can still be entered, but further destabilization is no longer possible.
|
|
For completely destabilizing the Reality, you also get an additional ${formatX(8)} to Dark Energy gain.`;
|
|
case 2:
|
|
case 3: return `<br><br>Lai'tela's Reality will now disable production from all
|
|
${disabledDim}${disabledDim === 2 ? "nd" : "rd"} Dimensions during
|
|
future runs, but the reward will be ${formatInt(100)} times stronger than before.`;
|
|
case 8: return `<br><br>Lai'tela's Reality will now disable production from all 8th Dimensions during
|
|
future runs, but the reward will be ${formatInt(100)} times stronger than before. This boost can be
|
|
repeated for each remaining Dimension by reaching destabilization within ${formatInt(30)} seconds again.`;
|
|
default: return `<br><br>Lai'tela's Reality will now disable production from all
|
|
${disabledDim}th Dimensions during future runs, but the reward will be
|
|
${formatInt(100)} times stronger than before.`;
|
|
}
|
|
}
|
|
|
|
// This gives IP/EP/RM from the respective upgrades that reward the prestige currencies continuously
|
|
function applyAutoprestige(diff) {
|
|
Currency.infinityPoints.add(TimeStudy(181).effectOrDefault(0));
|
|
|
|
if (TeresaUnlocks.epGen.canBeApplied) {
|
|
Currency.eternityPoints.add(player.records.thisEternity.bestEPmin.times(DC.D0_01)
|
|
.times(getGameSpeedupFactor() * diff / 1000).timesEffectOf(Ra.unlocks.continuousTTBoost.effects.autoPrestige));
|
|
}
|
|
|
|
if (InfinityUpgrade.ipGen.isCharged) {
|
|
const addedRM = MachineHandler.gainedRealityMachines
|
|
.timesEffectsOf(InfinityUpgrade.ipGen.chargedEffect)
|
|
.times(diff / 1000);
|
|
Currency.realityMachines.add(addedRM);
|
|
}
|
|
|
|
if (PelleRifts.chaos.milestones[2].canBeApplied) {
|
|
Currency.eternityPoints.add(gainedEternityPoints().times(DC.D0_1).times(diff / 1000));
|
|
}
|
|
}
|
|
|
|
function updateImaginaryMachines(diff) {
|
|
MachineHandler.updateIMCap();
|
|
Currency.imaginaryMachines.add(MachineHandler.gainedImaginaryMachines(diff));
|
|
}
|
|
|
|
function updateTachyonGalaxies() {
|
|
const tachyonGalaxyMult = Effects.max(1, DilationUpgrade.doubleGalaxies);
|
|
const tachyonGalaxyThreshold = 1000;
|
|
const thresholdMult = getTachyonGalaxyMult();
|
|
player.dilation.baseTachyonGalaxies = Math.max(player.dilation.baseTachyonGalaxies,
|
|
1 + Math.floor(Decimal.log(Currency.dilatedTime.value.dividedBy(1000), thresholdMult)));
|
|
player.dilation.nextThreshold = DC.E3.times(new Decimal(thresholdMult)
|
|
.pow(player.dilation.baseTachyonGalaxies));
|
|
player.dilation.totalTachyonGalaxies =
|
|
Math.min(player.dilation.baseTachyonGalaxies * tachyonGalaxyMult, tachyonGalaxyThreshold) +
|
|
Math.max(player.dilation.baseTachyonGalaxies * tachyonGalaxyMult - tachyonGalaxyThreshold, 0) / tachyonGalaxyMult;
|
|
|
|
player.dilation.totalTachyonGalaxies *= DilationUpgrade.galaxyMultiplier.effectValue;
|
|
}
|
|
|
|
export function getTTPerSecond() {
|
|
// All TT multipliers (note that this is equal to 1 pre-Ra)
|
|
let ttMult = Effects.product(
|
|
Ra.unlocks.continuousTTBoost.effects.ttGen,
|
|
Ra.unlocks.achievementTTMult,
|
|
Achievement(137),
|
|
);
|
|
if (GlyphAlteration.isAdded("dilation")) ttMult *= getSecondaryGlyphEffect("dilationTTgen");
|
|
|
|
// Glyph TT generation
|
|
const glyphTT = Teresa.isRunning || Enslaved.isRunning || Pelle.isDoomed
|
|
? 0
|
|
: getAdjustedGlyphEffect("dilationTTgen") * ttMult;
|
|
|
|
// Dilation TT generation
|
|
const dilationTT = DilationUpgrade.ttGenerator.isBought
|
|
? DilationUpgrade.ttGenerator.effectValue.times(Pelle.isDoomed ? 1 : ttMult)
|
|
: DC.D0;
|
|
|
|
// Lai'tela TT power
|
|
let finalTT = dilationTT.add(glyphTT);
|
|
if (SingularityMilestone.theoremPowerFromSingularities.isUnlocked && finalTT.gt(1) && !Pelle.isDoomed) {
|
|
finalTT = finalTT.pow(SingularityMilestone.theoremPowerFromSingularities.effectValue);
|
|
}
|
|
|
|
return finalTT;
|
|
}
|
|
|
|
function recursiveTimeOut(fn, iterations, endFn) {
|
|
fn(iterations);
|
|
if (iterations === 0) endFn();
|
|
else setTimeout(() => recursiveTimeOut(fn, iterations - 1, endFn), 0);
|
|
}
|
|
|
|
function afterSimulation(seconds, playerBefore) {
|
|
if (seconds > 600) {
|
|
const playerAfter = deepmergeAll([{}, player]);
|
|
Modal.awayProgress.show({ playerBefore, playerAfter, seconds });
|
|
}
|
|
|
|
GameUI.notify.showBlackHoles = true;
|
|
}
|
|
|
|
const OFFLINE_BH_PAUSE_STATE = {
|
|
ACTIVE: 0,
|
|
INACTIVE: 1,
|
|
PAUSED: 2,
|
|
};
|
|
|
|
export function simulateTime(seconds, real, fast) {
|
|
// The game is simulated at a base 50ms update rate, with a max of
|
|
// player.options.offlineTicks ticks. additional ticks are converted
|
|
// into a higher diff per tick
|
|
// warning: do not call this function with real unless you know what you're doing
|
|
// calling it with fast will only simulate it with a max of 50 ticks
|
|
let ticks = Math.floor(seconds * 20);
|
|
GameUI.notify.showBlackHoles = false;
|
|
|
|
// Limit the tick count (this also applies if the black hole is unlocked)
|
|
if (ticks > player.options.offlineTicks && !real && !fast) {
|
|
ticks = player.options.offlineTicks;
|
|
} else if (ticks > 50 && fast) {
|
|
ticks = 50;
|
|
}
|
|
|
|
const playerStart = deepmergeAll([{}, player]);
|
|
|
|
let totalGameTime;
|
|
|
|
if (BlackHoles.areUnlocked && !BlackHoles.arePaused) {
|
|
totalGameTime = BlackHoles.calculateGameTimeFromRealTime(seconds, BlackHoles.calculateSpeedups());
|
|
} else {
|
|
totalGameTime = getGameSpeedupFactor() * seconds;
|
|
}
|
|
|
|
const infinitiedMilestone = getInfinitiedMilestoneReward(totalGameTime * 1000);
|
|
const eternitiedMilestone = getEternitiedMilestoneReward(totalGameTime * 1000);
|
|
|
|
if (eternitiedMilestone.gt(0)) {
|
|
Currency.eternities.add(eternitiedMilestone);
|
|
} else if (infinitiedMilestone.gt(0)) {
|
|
Currency.infinities.add(infinitiedMilestone);
|
|
} else {
|
|
Currency.eternityPoints.add(getOfflineEPGain(totalGameTime * 1000));
|
|
}
|
|
|
|
if (InfinityUpgrade.ipOffline.isBought && player.options.offlineProgress) {
|
|
Currency.infinityPoints.add(player.records.thisEternity.bestIPMsWithoutMaxAll.times(seconds * 1000 / 2));
|
|
}
|
|
|
|
let remainingRealSeconds = seconds;
|
|
// During async code the number of ticks remaining can go down suddenly
|
|
// from "Speed up" which means tick length needs to go up, and thus
|
|
// you can't just divide total time by total ticks to get tick length.
|
|
// For example, suppose you had 6000 offline ticks, and called "Speed up"
|
|
// 1000 ticks in, meaning that after "Speed up" there'd only be 1000 ticks more
|
|
// (so 1000 + 1000 = 2000 ticks total). Dividing total time by total ticks would
|
|
// use 1/6th of the total time before "Speed up" (1000 of 6000 ticks), and 1/2 after
|
|
// (1000 of 2000 ticks). Short of some sort of magic user prediction to figure out
|
|
// whether the user *will* press "Speed up" at some point, dividing remaining time
|
|
// by remaining ticks seems like the best thing to do.
|
|
let loopFn = i => {
|
|
const diff = remainingRealSeconds / i;
|
|
gameLoop(1000 * diff);
|
|
remainingRealSeconds -= diff;
|
|
};
|
|
|
|
// Simulation code which accounts for BH cycles (segments where a BH is active doesn't use diff since it splits
|
|
// up intervals based on real time instead in an effort to keep ticks all roughly equal in game time). With black
|
|
// hole auto-pausing, the simulation now becomes a three-step process:
|
|
// 1. Simulate until the BH dectivates (this only occurs if it's active when the simulation starts)
|
|
// 2. At this point, the BH we're tracking is inactive and timeToNextStateChange will return the proper value until
|
|
// we should pause it, so we run until we either hit that or run out of time (this often takes very few ticks)
|
|
// 3. The BH is now paused and the simpler code works to finish the rest of the ticks
|
|
let offlineBHState = OFFLINE_BH_PAUSE_STATE.ACTIVE;
|
|
const trackedBH = player.blackHoleAutoPauseMode;
|
|
if (BlackHoles.areUnlocked && !BlackHoles.arePaused) {
|
|
if (trackedBH === 0) {
|
|
// Auto-pause is off, don't bother doing anything fancy
|
|
loopFn = i => {
|
|
const [realTickTime, blackHoleSpeedup] = BlackHoles.calculateOfflineTick(remainingRealSeconds,
|
|
i, 0.0001);
|
|
remainingRealSeconds -= realTickTime;
|
|
gameLoop(1000 * realTickTime, { blackHoleSpeedup });
|
|
};
|
|
} else {
|
|
if (!BlackHole(trackedBH).isActive) offlineBHState++;
|
|
loopFn = i => {
|
|
let realTickTime, blackHoleSpeedup, limit, diff;
|
|
switch (offlineBHState) {
|
|
case OFFLINE_BH_PAUSE_STATE.ACTIVE:
|
|
// If we have to reduce tick length to not overshoot the transition, we also advance the simulation state
|
|
// We skip past the BH going inactive by 1 ms in order to ensure that the next simulation step actually has
|
|
// an inactive BH in order for the logic to work out
|
|
[realTickTime, blackHoleSpeedup] = BlackHoles.calculateOfflineTick(remainingRealSeconds,
|
|
i, 0.0001);
|
|
limit = BlackHole(trackedBH).timeToNextStateChange + 0.001;
|
|
if (realTickTime > limit) {
|
|
remainingRealSeconds -= limit;
|
|
gameLoop(1000 * limit, { blackHoleSpeedup });
|
|
offlineBHState++;
|
|
} else {
|
|
remainingRealSeconds -= realTickTime;
|
|
gameLoop(1000 * realTickTime, { blackHoleSpeedup });
|
|
}
|
|
break;
|
|
case OFFLINE_BH_PAUSE_STATE.INACTIVE:
|
|
// Same as above, but this time the extra 1 ms serves the purpose of putting the game past the auto-pause
|
|
// threshold. Otherwise, it'll immediately auto-pause once more when online
|
|
[realTickTime, blackHoleSpeedup] = BlackHoles.calculateOfflineTick(remainingRealSeconds,
|
|
i, 0.0001);
|
|
limit = BlackHole(trackedBH).timeToNextStateChange - BlackHoles.ACCELERATION_TIME + 0.001;
|
|
if (realTickTime > limit) {
|
|
remainingRealSeconds -= limit;
|
|
gameLoop(1000 * limit, { blackHoleSpeedup });
|
|
offlineBHState++;
|
|
} else {
|
|
remainingRealSeconds -= realTickTime;
|
|
gameLoop(1000 * realTickTime, { blackHoleSpeedup });
|
|
}
|
|
break;
|
|
case OFFLINE_BH_PAUSE_STATE.PAUSED:
|
|
// At this point the BH is paused and we just use the same code as no BH at all. This isn't necessarily
|
|
// executed in all situations; for example short offline periods may not reach this code
|
|
diff = remainingRealSeconds / i;
|
|
gameLoop(1000 * diff);
|
|
remainingRealSeconds -= diff;
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
const oldLoopFn = loopFn;
|
|
loopFn = i => {
|
|
Pelle.addAdditionalEnd = false;
|
|
oldLoopFn(i);
|
|
Pelle.addAdditionalEnd = true;
|
|
};
|
|
|
|
// We don't show the offline modal here or bother with async if doing a fast simulation
|
|
if (fast) {
|
|
// Fast simulations happen when simulating between 10 and 50 seconds of offline time.
|
|
// One easy way to get this is to autosave every 30 or 60 seconds, wait until the save timer
|
|
// in the bottom-left hits 15 seconds, and refresh (without saving directly beforehand).
|
|
GameIntervals.stop();
|
|
// Fast simulations are always 50 ticks. They're done in this weird countdown way because
|
|
// we want to be able to call the same function that we call when using async code (to avoid
|
|
// duplicating functions), and that function expects a parameter saying how many ticks are remaining.
|
|
for (let remaining = 50; remaining > 0; remaining--) {
|
|
loopFn(remaining);
|
|
}
|
|
GameStorage.postLoadStuff();
|
|
afterSimulation(seconds, playerStart);
|
|
} else {
|
|
const progress = {};
|
|
ui.view.modal.progressBar = {};
|
|
Async.run(loopFn,
|
|
ticks,
|
|
{
|
|
batchSize: 1,
|
|
maxTime: 60,
|
|
sleepTime: 1,
|
|
asyncEntry: doneSoFar => {
|
|
GameIntervals.stop();
|
|
ui.$viewModel.modal.progressBar = {
|
|
label: "Offline Progress Simulation",
|
|
info: () => `The game is being run at a lower accuracy in order to quickly calculate the resources you
|
|
gained while you were away. See the How To Play entry on "Offline Progress" for technical details. If
|
|
you are impatient and want to get back to the game sooner, you can click the "Speed up" button to
|
|
simulate the rest of the time with half as many ticks (down to a minimum of ${formatInt(500)} ticks
|
|
remaining). The "SKIP" button will instead use all the remaining offline time in ${formatInt(10)}
|
|
ticks.`,
|
|
progressName: "Ticks",
|
|
current: doneSoFar,
|
|
max: ticks,
|
|
startTime: Date.now(),
|
|
buttons: [{
|
|
text: "Speed up",
|
|
condition: (current, max) => max - current > 500,
|
|
click: () => {
|
|
const newRemaining = Math.clampMin(Math.floor(progress.remaining / 2), 500);
|
|
// We subtract the number of ticks we skipped, which is progress.remaining - newRemaining.
|
|
// This, and the below similar code in "SKIP", are needed or the progress bar to be accurate
|
|
// (both with respect to the number of ticks it shows and with respect to how full it is).
|
|
progress.maxIter -= progress.remaining - newRemaining;
|
|
progress.remaining = newRemaining;
|
|
// We update the progress bar max data (remaining will update automatically).
|
|
ui.$viewModel.modal.progressBar.max = progress.maxIter;
|
|
}
|
|
},
|
|
{
|
|
text: "SKIP",
|
|
condition: (current, max) => max - current > 10,
|
|
click: () => {
|
|
// We jump to 10 from the end (condition guarantees there are at least 10 left).
|
|
// We subtract the number of ticks we skipped, which is progress.remaining - 10.
|
|
progress.maxIter -= progress.remaining - 10;
|
|
progress.remaining = 10;
|
|
}
|
|
}]
|
|
};
|
|
},
|
|
asyncProgress: doneSoFar => {
|
|
ui.$viewModel.modal.progressBar.current = doneSoFar;
|
|
},
|
|
asyncExit: () => {
|
|
ui.$viewModel.modal.progressBar = undefined;
|
|
// .postLoadStuff will restart GameIntervals
|
|
GameStorage.postLoadStuff();
|
|
},
|
|
then: () => {
|
|
afterSimulation(seconds, playerStart);
|
|
},
|
|
progress
|
|
});
|
|
}
|
|
}
|
|
|
|
window.onload = function() {
|
|
const supportedBrowser = browserCheck();
|
|
GameUI.initialized = supportedBrowser;
|
|
ui.view.initialized = supportedBrowser;
|
|
setTimeout(() => {
|
|
if (kong.enabled) {
|
|
playFabLogin();
|
|
}
|
|
document.getElementById("loading").style.display = "none";
|
|
document.body.style.overflowY = "auto";
|
|
}, 500);
|
|
if (!supportedBrowser) {
|
|
GameIntervals.stop();
|
|
document.getElementById("loading").style.display = "none";
|
|
document.getElementById("browser-warning").style.display = "flex";
|
|
}
|
|
};
|
|
|
|
window.onfocus = function() {
|
|
setShiftKey(false);
|
|
};
|
|
|
|
window.onblur = function() {
|
|
GameKeyboard.stopSpins();
|
|
};
|
|
|
|
export function setShiftKey(isDown) {
|
|
ui.view.shiftDown = isDown;
|
|
}
|
|
|
|
export function setHoldingR(x) {
|
|
Replicanti.galaxies.isPlayerHoldingR = x;
|
|
}
|
|
|
|
export function browserCheck() {
|
|
return supportedBrowsers.test(navigator.userAgent);
|
|
}
|
|
|
|
export function init() {
|
|
// eslint-disable-next-line no-console
|
|
console.log("🌌 Antimatter Dimensions: Reality Update 🌌");
|
|
GameStorage.load();
|
|
Tabs.all.find(t => t.config.id === player.options.lastOpenTab).show(true);
|
|
kong.init();
|
|
}
|
|
|
|
window.tweenTime = 0;
|
|
let lastFrame;
|
|
function animateTweens(time) {
|
|
requestAnimationFrame(animateTweens);
|
|
if (time === undefined || lastFrame === undefined) {
|
|
lastFrame = time;
|
|
return;
|
|
}
|
|
let delta = time - lastFrame;
|
|
lastFrame = time;
|
|
if (player.dilation.active) {
|
|
delta /= 10;
|
|
}
|
|
tweenTime += delta;
|
|
TWEEN.update(tweenTime);
|
|
}
|
|
|
|
animateTweens();
|