Merge branch 'master' into Steam-Webpack

This commit is contained in:
ZackRhodes 2022-10-12 16:20:45 -04:00
commit c77f6d85e3
44 changed files with 660 additions and 146 deletions

View File

@ -1,3 +1,5 @@
import { ProgressChecker } from "../storage/progress-checker";
import CloudLoadConflictModal from "@/components/modals/cloud/CloudLoadConflictModal";
import CloudManualLoginModal from "@/components/modals/cloud/CloudManualLoginModal";
import CloudSaveConflictModal from "@/components/modals/cloud/CloudSaveConflictModal";
@ -40,6 +42,7 @@ import PurgeGlyphModal from "@/components/modals/glyph-management/PurgeGlyphModa
import RefineGlyphModal from "@/components/modals/glyph-management/RefineGlyphModal";
import SacrificeGlyphModal from "@/components/modals/glyph-management/SacrificeGlyphModal";
import AutobuyerEditModal from "@/components/modals/AutobuyerEditModal";
import AutomatorScriptTemplate from "@/components/modals/AutomatorScriptTemplate";
import AwayProgressModal from "@/components/modals/AwayProgressModal";
import BreakInfinityModal from "@/components/modals/BreakInfinityModal";
@ -240,6 +243,7 @@ Modal.importScriptData = new Modal(ImportAutomatorDataModal);
Modal.automatorScriptDelete = new Modal(DeleteAutomatorScriptModal);
Modal.automatorScriptTemplate = new Modal(AutomatorScriptTemplate);
Modal.switchAutomatorEditorMode = new Modal(SwitchAutomatorEditorModal);
Modal.autobuyerEditModal = new Modal(AutobuyerEditModal);
Modal.shop = new Modal(StdStoreModal);
Modal.studyString = new Modal(StudyStringModal);
Modal.singularityMilestones = new Modal(SingularityMilestonesModal);
@ -263,6 +267,7 @@ function getSaveInfo(save) {
bestLevel: 0,
totalSTD: 0,
saveName: "",
compositeProgress: 0,
};
// This code ends up getting run on raw save data before any migrations are applied, so we need to default to props
// which only exist on the pre-reality version when applicable. Note that new Decimal(undefined) gives zero.
@ -279,6 +284,7 @@ function getSaveInfo(save) {
resources.bestLevel = save.records?.bestReality.glyphLevel ?? 0;
resources.totalSTD = save?.IAP?.totalSTD ?? 0;
resources.saveName = save.options.saveFileName ?? "";
resources.compositeProgress = ProgressChecker.getCompositeProgress(save);
return resources;
}

View File

@ -127,6 +127,7 @@ class AntimatterDimensionAutobuyerState extends UpgradeableAutobuyerState {
this.data.isUnlocked = false;
this.data.isBought = false;
this.data.bulk = 1;
TabNotification.newAutobuyer.clearTrigger();
}
static get entryCount() { return 8; }

View File

@ -64,6 +64,14 @@ export const Autobuyers = (function() {
return Autobuyers.all.filter(a => a.isUnlocked || a.isBought);
},
get hasAutobuyersForEditModal() {
return [Autobuyer.dimboost,
Autobuyer.galaxy,
Autobuyer.bigCrunch,
Autobuyer.eternity,
Autobuyer.reality].some(autobuyer => autobuyer.isUnlocked);
},
toggle() {
player.auto.autobuyersOn = !player.auto.autobuyersOn;
},

View File

@ -95,5 +95,6 @@ Autobuyer.tickspeed = new class TickspeedAutobuyerState extends UpgradeableAutob
this.data.mode = AUTOBUYER_MODE.BUY_SINGLE;
this.data.isUnlocked = false;
this.data.isBought = false;
TabNotification.newAutobuyer.clearTrigger();
}
}();

View File

@ -85,7 +85,7 @@ export const GameCache = {
// Cached because it needs to be checked upon any change to antimatter, but that's a hot path and we want to keep
// unnecessary repetitive calculations and accessing to a minimum
cheapestAntimatterAutobuyer: new Lazy(() => Autobuyer.antimatterDimension.zeroIndexed.concat(Autobuyer.tickspeed)
.filter(ab => !ab.isBought)
.filter(ab => !(ab.isBought || ab.isUnlocked))
.map(ab => ab.antimatterCost.toNumber())
.min()
),

View File

@ -202,6 +202,9 @@ Currency.antimatter = new class extends DecimalCurrency {
set value(value) {
if (InfinityChallenges.nextIC) InfinityChallenges.notifyICUnlock(value);
if (GameCache.cheapestAntimatterAutobuyer.value && value.gte(GameCache.cheapestAntimatterAutobuyer.value)) {
// Clicking into the automation tab clears the trigger and prevents it from retriggering as long as the player
// stays on the tab; leaving the tab with an available autobuyer will immediately force it to trigger again
TabNotification.newAutobuyer.clearTrigger();
TabNotification.newAutobuyer.tryTrigger();
}
player.antimatter = value;

View File

@ -578,7 +578,8 @@ dev.testGlyphs = function(config) {
const glyphData = glyphSets[index].map(glyphToShortString).sum();
console.log(`${done} ${glyphData} rm=${rm} gl=${gl} ep=${ep} ip=${ip} am=${am} ` +
`dimboosts=${dimboosts} galaxies=${galaxies}`);
GameStorage.import(save, Date.now());
GameStorage.offlineEnabled = false;
GameStorage.import(save);
if (index < glyphSets.length - 1) {
setTimeout(runTrial, 100, index + 1);
}
@ -605,6 +606,7 @@ dev.forceCloudSave = async function() {
const save = await Cloud.load();
const root = GameSaveSerializer.deserialize(save);
const saveId = GameStorage.currentSlot;
if (!root.saves) root.saves = [];
root.saves[saveId] = GameStorage.saves[saveId];
Cloud.save(saveId);
};

View File

@ -151,7 +151,7 @@ function tachyonGainMultiplier() {
}
export function rewardTP() {
Currency.tachyonParticles.bumpTo(getTP(Currency.antimatter.value));
Currency.tachyonParticles.bumpTo(getTP(Currency.antimatter.value, true));
player.dilation.lastEP = Currency.eternityPoints.value;
}
@ -159,8 +159,8 @@ export function rewardTP() {
// applying the reward only once upon unlock promotes min-maxing the upgrade by unlocking dilation with
// TP multipliers as large as possible. Applying the reward to a base TP value and letting the multipliers
// act dynamically on this fixed base value elsewhere solves that issue
export function getBaseTP(antimatter) {
if (!Player.canEternity) return DC.D0;
export function getBaseTP(antimatter, requireEternity) {
if (!Player.canEternity && requireEternity) return DC.D0;
const am = (isInCelestialReality() || Pelle.isDoomed)
? antimatter
: Ra.unlocks.unlockDilationStartingTP.effectOrDefault(antimatter);
@ -170,13 +170,14 @@ export function getBaseTP(antimatter) {
}
// Returns the TP that would be gained this run
export function getTP(antimatter) {
return getBaseTP(antimatter).times(tachyonGainMultiplier());
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
export function getTachyonGain() {
return getTP(Currency.antimatter.value).minus(Currency.tachyonParticles.value).clampMin(0);
// and for "exit dilation" button (saying whether you need more antimatter)
export function getTachyonGain(requireEternity) {
return getTP(Currency.antimatter.value, requireEternity).minus(Currency.tachyonParticles.value).clampMin(0);
}
// Returns the minimum antimatter needed in order to gain more TP; used only for display purposes

View File

@ -74,16 +74,19 @@ export function eternity(force, auto, specialConditions = {}) {
// Annoyingly, we need to check for studies right here; giveEternityRewards removes studies if we're in an EC,
// so doing the check later doesn't give us the initial state of having studies or not.
const noStudies = player.timestudy.studies.length === 0;
if (force) {
player.challenge.eternity.current = 0;
} else {
if (!force) {
if (!Player.canEternity) return false;
EventHub.dispatch(GAME_EVENT.ETERNITY_RESET_BEFORE);
if (!player.dilation.active) giveEternityRewards(auto);
player.requirementChecks.reality.noEternities = false;
}
if (player.dilation.active && (!force || Currency.infinityPoints.gte(Number.MAX_VALUE))) rewardTP();
if (player.dilation.active) rewardTP();
// This needs to be after the dilation check for the "can gain TP" check in rewardTP to be correct.
if (force) {
player.challenge.eternity.current = 0;
}
initializeChallengeCompletions();
initializeResourcesAfterEternity();

View File

@ -256,7 +256,14 @@ export const shortcuts = [
type: "bind",
function: () => SecretAchievement(41).unlock(),
visible: false
}
},
{
name: "Adjust Autobuyers",
keys: ["mod", "alt", "a"],
type: "bind",
function: () => keyboardEditAutobuyers(),
visible: () => Autobuyers.hasAutobuyersForEditModal
},
];
for (const hotkey of shortcuts) {
@ -411,6 +418,16 @@ function keyboardH2PToggle() {
Modal.h2p.show();
}
function keyboardEditAutobuyers() {
if (Modal.autobuyerEditModal.isOpen) {
EventHub.dispatch(GAME_EVENT.CLOSE_MODAL);
return;
}
if (Modal.isOpen) return;
if (!Autobuyers.hasAutobuyersForEditModal) return;
Modal.autobuyerEditModal.show();
}
function keyboardVisibleTabsToggle() {
if (Modal.hiddenTabs.isOpen) {
EventHub.dispatch(GAME_EVENT.CLOSE_MODAL);
@ -458,7 +475,12 @@ let konamiStep = 0;
function testKonami(character) {
if (SecretAchievement(17).isUnlocked) return;
// This conditional is structured weirdly in order to make sure more than 2 consecutive "up" inputs doesn't
// reset the sequence state unnecessarily, and that interrupting the sequence later on with the starting
// input will correctly set the state to one step in
if (konamiCode[konamiStep] === character) konamiStep++;
else if (konamiStep === 2 && character === "up") konamiStep = 2;
else if (character === konamiCode[0]) konamiStep = 1;
else konamiStep = 0;
if (konamiCode.length <= konamiStep) {
SecretAchievement(17).unlock();

View File

@ -55,7 +55,7 @@ export const GameIntervals = (function() {
},
gameLoop: interval(() => gameLoop(), () => player.options.updateRate),
save: interval(() => GameStorage.save(), () =>
(player.options.autosaveInterval - Math.clampMin(0, Date.now() - GameStorage.lastSaveTime))
player.options.autosaveInterval - Math.clampMin(0, Date.now() - GameStorage.lastSaveTime)
),
checkCloudSave: interval(() => {
if (player.options.cloudEnabled && Cloud.loggedIn) Cloud.saveCheck();

View File

@ -109,11 +109,11 @@ ShopPurchase.respecRequest = function() {
};
kong.purchaseTimeSkip = function() {
simulateTime(3600 * 6);
simulateTime(3600 * 6, true);
};
kong.purchaseLongerTimeSkip = function() {
simulateTime(3600 * 24);
simulateTime(3600 * 24, true);
};
kong.updatePurchases = function() {

View File

@ -49,6 +49,10 @@ class NormalChallengeState extends GameMechanicState {
return player.challenge.normal.current === this.id || (isPartOfIC1 && InfinityChallenge(1).isRunning);
}
get isOnlyActiveChallenge() {
return player.challenge.normal.current === this.id;
}
get isUnlocked() {
if (PlayerProgress.eternityUnlocked()) return true;
if (this.id === 0) return true;
@ -75,7 +79,7 @@ class NormalChallengeState extends GameMechanicState {
}
start() {
if (this.id === 1 || this.isRunning) return;
if (this.id === 1 || this.isOnlyActiveChallenge) return;
if (!Tab.challenges.isUnlocked) return;
player.challenge.normal.current = this.id;
player.challenge.infinity.current = 0;

View File

@ -767,6 +767,7 @@ window.player = {
retryCelestial: false,
showAllChallenges: false,
cloudEnabled: true,
syncSaveIntervals: true,
hotkeys: true,
theme: "Normal",
commas: true,

View File

@ -134,7 +134,7 @@ GameDatabase.achievements.normal = [
id: 32,
name: "The Gods are pleased",
get description() { return `Get over ${formatX(600)} from Dimensional Sacrifice outside of Challenge 8.`; },
checkRequirement: () => !NormalChallenge(8).isRunning && Sacrifice.totalBoost.gte(600),
checkRequirement: () => !NormalChallenge(8).isOnlyActiveChallenge && Sacrifice.totalBoost.gte(600),
checkEvent: GAME_EVENT.SACRIFICE_RESET_AFTER,
get reward() {
return `Dimensional Sacrifice is stronger.
@ -337,7 +337,7 @@ GameDatabase.achievements.normal = [
get description() {
return `Complete the 2nd Antimatter Dimension Autobuyer Challenge in ${formatInt(3)} minutes or less.`;
},
checkRequirement: () => NormalChallenge(2).isRunning && Time.thisInfinityRealTime.totalMinutes <= 3,
checkRequirement: () => NormalChallenge(2).isOnlyActiveChallenge && Time.thisInfinityRealTime.totalMinutes <= 3,
checkEvent: GAME_EVENT.BIG_CRUNCH_BEFORE,
get reward() {
return `All Antimatter Dimensions are stronger in the first ${formatInt(3)} minutes of Infinities.`;
@ -352,7 +352,7 @@ GameDatabase.achievements.normal = [
get description() {
return `Complete the 8th Antimatter Dimension Autobuyer Challenge in ${formatInt(3)} minutes or less.`;
},
checkRequirement: () => NormalChallenge(8).isRunning && Time.thisInfinityRealTime.totalMinutes <= 3,
checkRequirement: () => NormalChallenge(8).isOnlyActiveChallenge && Time.thisInfinityRealTime.totalMinutes <= 3,
checkEvent: GAME_EVENT.BIG_CRUNCH_BEFORE,
get reward() {
return `Dimensional Sacrifice is stronger.
@ -365,7 +365,7 @@ GameDatabase.achievements.normal = [
id: 58,
name: "This is fine.",
get description() { return `Complete the Tickspeed Autobuyer Challenge in ${formatInt(3)} minutes or less.`; },
checkRequirement: () => NormalChallenge(9).isRunning && Time.thisInfinityRealTime.totalMinutes <= 3,
checkRequirement: () => NormalChallenge(9).isOnlyActiveChallenge && Time.thisInfinityRealTime.totalMinutes <= 3,
checkEvent: GAME_EVENT.BIG_CRUNCH_BEFORE,
get reward() {
return `Increase the multiplier for buying ${formatInt(10)} Antimatter Dimensions by +${formatPercents(0.01)}.`;
@ -442,7 +442,7 @@ GameDatabase.achievements.normal = [
get description() {
return `Complete the 3rd Antimatter Dimension Autobuyer Challenge in ${formatInt(10)} seconds or less.`;
},
checkRequirement: () => NormalChallenge(3).isRunning && Time.thisInfinityRealTime.totalSeconds <= 10,
checkRequirement: () => NormalChallenge(3).isOnlyActiveChallenge && Time.thisInfinityRealTime.totalSeconds <= 10,
checkEvent: GAME_EVENT.BIG_CRUNCH_BEFORE,
get reward() { return `1st Antimatter Dimensions are ${formatPercents(0.5)} stronger.`; },
effect: 1.5
@ -454,7 +454,7 @@ GameDatabase.achievements.normal = [
`Get to Infinity with only a single 1st Antimatter Dimension without Dimension Boosts
or Antimatter Galaxies, while in the 2nd Antimatter Dimension Autobuyer Challenge.`,
checkRequirement: () =>
NormalChallenge(2).isRunning &&
NormalChallenge(2).isOnlyActiveChallenge &&
AntimatterDimension(1).amount.eq(1) &&
DimBoost.purchasedBoosts === 0 &&
player.galaxies === 0,

View File

@ -18,8 +18,8 @@ function rebuyable(config) {
(value => {
const afterECText = config.afterEC ? config.afterEC() : "";
return value === config.maxUpgrades
? `Default: ${formatX(10)} | Currently: ${formatX(10 - value)} ${afterECText}`
: `Default: ${formatX(10)} | Currently: ${formatX(10 - value)} Next: ${formatX(10 - value - 1)}`;
? `Currently: ${formatX(10 - value)} ${afterECText}`
: `Currently: ${formatX(10 - value)} | Next: ${formatX(10 - value - 1)}`;
}),
formatCost: value => format(value, 2, 0),
noLabel,

View File

@ -33,6 +33,14 @@ export const Cloud = {
shouldOverwriteCloudSave: true,
lastCloudHash: null,
resetTempState() {
this.hasSeenSavingConflict = false;
this.shouldOverwriteCloudSave = true;
this.lastCloudHash = null;
GameStorage.lastCloudSave = Date.now();
GameIntervals.checkCloudSave.restart();
},
get loggedIn() {
return this.user !== null;
},
@ -78,12 +86,11 @@ export const Cloud = {
older: ProgressChecker.compareSaveTimes(cloud, local),
diffSTD: (cloud?.IAP?.totalSTD ?? 0) - (local?.IAP?.totalSTD ?? 0),
differentName: cloud?.options.saveFileName !== local?.options.saveFileName,
hashMismatch: hash && this.lastCloudHash !== hash,
hashMismatch: this.lastCloudHash && this.lastCloudHash !== hash,
};
},
async saveCheck() {
GameIntervals.checkCloudSave.restart();
const save = await this.load();
if (save === null) {
this.save();
@ -91,8 +98,9 @@ export const Cloud = {
const root = GameSaveSerializer.deserialize(save);
const saveId = GameStorage.currentSlot;
const cloudSave = root.saves[saveId];
const thisCloudHash = sha512_256(GameSaveSerializer.serialize(cloudSave));
const localSave = GameStorage.saves[saveId];
const saveComparison = this.compareSaves(cloudSave, localSave, sha512_256(save));
const saveComparison = this.compareSaves(cloudSave, localSave, thisCloudHash);
// eslint-disable-next-line no-loop-func
const overwriteAndSendCloudSave = () => {
@ -103,11 +111,9 @@ export const Cloud = {
// Bring up the modal if cloud saving will overwrite a cloud save which is older or possibly farther
const hasBoth = cloudSave && localSave;
// NOTE THIS CHECK IS INTENTIONALLY DIFFERENT FROM THE LOAD CHECK
// Hash mismatch check should be separate from the others because otherwise it only ever shows up on the first
// mismatch; there are situations (eg. two devices actively saving) which can cause this to happen repeatedly.
const hasConflict = hasBoth && (saveComparison.older === -1 || saveComparison.farther !== 1 ||
saveComparison.diffSTD > 0 || saveComparison.differentName);
if ((hasConflict && !this.hasSeenSavingConflict) || saveComparison.hashMismatch) {
saveComparison.diffSTD > 0 || saveComparison.differentName || saveComparison.hashMismatch);
if (hasConflict && !this.hasSeenSavingConflict) {
Modal.addCloudConflict(saveId, saveComparison, cloudSave, localSave, overwriteAndSendCloudSave);
Modal.cloudSaveConflict.show();
} else if (!hasConflict || (this.hasSeenSavingConflict && this.shouldOverwriteCloudSave)) {
@ -119,14 +125,16 @@ export const Cloud = {
save(slot) {
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 toSave = GameSaveSerializer.serialize(root);
this.lastCloudHash = sha512_256(toSave);
set(ref(this.db, `users/${this.user.id}/web`), toSave);
this.lastCloudHash = sha512_256(GameSaveSerializer.serialize(root.saves[slot]));
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}`);*/
},

View File

@ -11,6 +11,9 @@ export const GameStorage = {
},
saved: 0,
lastSaveTime: Date.now(),
lastCloudSave: Date.now(),
offlineEnabled: undefined,
offlineTicks: undefined,
get localStorageKey() {
return isDevEnvironment() ? "dimensionTestSave" : "dimensionSave";
@ -54,11 +57,12 @@ export const GameStorage = {
this.save(true);
this.loadPlayerObject(this.saves[slot] ?? Player.defaultStart);
Tabs.all.find(t => t.id === player.options.lastOpenTab).show(true);
Cloud.resetTempState();
GameUI.notify.info("Game loaded");
SteamFunctions.BackfillAchievements()
},
import(saveData, overrideLastUpdate = undefined) {
import(saveData) {
if (tryImportSecret(saveData) || Theme.tryUnlock(saveData)) {
return;
}
@ -70,9 +74,10 @@ export const GameStorage = {
Modal.hideAll();
Quote.clearAll();
AutomatorBackend.clearEditor();
this.loadPlayerObject(player, overrideLastUpdate);
this.loadPlayerObject(player);
if (player.speedrun?.isActive) Speedrun.setSegmented(true);
this.save(true);
Cloud.resetTempState();
// This is to fix a very specific exploit: When the game is ending, some tabs get hidden
// The options tab is the first one of those, which makes the player redirect to the Pelle tab
@ -195,9 +200,10 @@ export const GameStorage = {
player.IAP = IAP;
this.save(true);
Tab.dimensions.antimatter.show();
Cloud.resetTempState();
},
loadPlayerObject(playerObject, overrideLastUpdate = undefined) {
loadPlayerObject(playerObject) {
this.saved = 0;
const checkString = this.checkPlayerObject(playerObject);
@ -246,11 +252,10 @@ export const GameStorage = {
AutomatorBackend.initializeFromSave();
Lazy.invalidateAll();
if (overrideLastUpdate) {
player.lastUpdate = overrideLastUpdate;
}
const rawDiff = Date.now() - player.lastUpdate;
if (player.options.offlineProgress && !Speedrun.isPausedAtStart()) {
// We set offlineEnabled externally on importing; otherwise this is just a local load
const simulateOffline = this.offlineEnabled ?? player.options.offlineProgress;
if (simulateOffline && !Speedrun.isPausedAtStart()) {
let diff = rawDiff;
player.speedrun.offlineTimeUsed += diff;
if (diff > 5 * 60 * 1000 && player.celestials.enslaved.autoStoreReal) {

View File

@ -41,7 +41,7 @@ export class DilationTimeStudyState extends TimeStudyState {
}
if (!Pelle.isDoomed) Currency.tachyonParticles.bumpTo(Perk.startTP.effectOrDefault(0));
if (Ra.unlocks.unlockDilationStartingTP.canBeApplied && !isInCelestialReality() && !Pelle.isDoomed) {
Currency.tachyonParticles.bumpTo(getTP(Ra.unlocks.unlockDilationStartingTP.effectOrDefault(0)));
Currency.tachyonParticles.bumpTo(getTP(Ra.unlocks.unlockDilationStartingTP.effectOrDefault(0), false));
}
TabNotification.dilationAfterUnlock.tryTrigger();
}

View File

@ -14,8 +14,11 @@ class TabNotificationState {
tryTrigger() {
if (!this.config.condition() || this.triggered) return;
const currentTabKey = `${Tabs.current.key}${Tabs.current._currentSubtab.key}`;
this.config.tabsToHighLight.map(t => t.parent + t.tab)
.forEach(tab => player.tabNotifications.add(tab));
.forEach(tab => {
if (tab !== currentTabKey) player.tabNotifications.add(tab);
});
player.triggeredTabNotificationBits |= 1 << this.config.id;
// Force all tabs and subtabs of this notification to be unhidden

View File

@ -842,18 +842,19 @@ function afterSimulation(seconds, playerBefore) {
}
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
// The game is simulated at a base 50ms update rate, with a maximum tick count based on the values of real and fast
// - Calling with real === true will always simulate at full accuracy with no tick count reduction
// - Calling with fast === true will only simulate it with a max of 50 ticks
// - Otherwise, tick count will be limited to the offline tick count (which may be set externally during save import)
// Tick count is never *increased*, and only ever decreased if needed.
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) {
const maxTicks = GameStorage.offlineTicks ?? player.options.offlineTicks;
if (ticks > maxTicks && !real && !fast) {
ticks = maxTicks;
} else if (ticks > 50 && !real && fast) {
ticks = 50;
}

View File

@ -624,6 +624,34 @@ body.t-s9 {
border-color: var(--color-accent);
}
.c-autobuyer-box-row__modal {
background-color: #111014;
border-color: #383232;
}
.t-metro .c-autobuyer-box-row__modal {
background-color: var(--color-base);
border-color: var(--color-text);
}
.t-inverted .c-autobuyer-box-row__modal,
.t-inverted-metro .c-autobuyer-box-row__modal {
background-color: var(--color-base);
border-color: var(--color-text);
}
.t-s7 .c-autobuyer-box-row__modal {
background-color: var(--color-base);
}
.o-autobuyer-toggle-checkbox__label-modal {
border-color: #383232;
}
.t-inverted .o-autobuyer-toggle-checkbox__label-modal {
border-color: var(--color-text);
}
.o-autobuyer-input,
.c-autobuyer-box__mode-select {
color: var(--color-accent);
@ -653,6 +681,11 @@ body.t-s9 {
border: 0.1rem solid black;
}
.t-s4 .o-autobuyer-input,
.t-s7 .o-autobuyer-input {
background-color: white;
}
.t-s6 .o-autobuyer-input,
.t-s6 .c-autobuyer-box__mode-select,
.t-s10 .o-autobuyer-input,

View File

@ -83,6 +83,21 @@
box-shadow: none;
}
.t-s11 .o-autobuyer-input,
.t-s11 .c-autobuyer-box__mode-select {
color: var(--color-accent);
background: var(--color-base);
border: 0.1rem solid var(--color-accent);
}
.t-s11 .o-autobuyer-btn {
border: 0.1rem solid var(--color-accent);
}
.t-s11 .c-challenge-box--normal {
border-color: var(--color-accent);
}
.o-tab-btn--secondary {
width: 18.5rem;
height: 2.5rem;

View File

@ -61,6 +61,7 @@ html {
--color-bad: #b84b5f;
--color-gh-purple: #8957e5;
--color-notification: red;
--color-overlay: rgba(30, 30, 50, 90%);
--color-antimatter: #2196f3;
--color-infinity: #b67f33;
@ -120,6 +121,7 @@ html {
--color-disabled: #37474f;
--color-accent: #1256a3;
--color-notification: yellow;
--color-overlay: rgba(60, 60, 100, 50%);
--color-infinity: #ff9800;
--color-reality-dark: #0ba00e;
@ -141,6 +143,7 @@ html {
--color-good-dark: #2e7d32;
--color-bad: #e53935;
--color-notification: yellow;
--color-overlay: rgba(60, 60, 100, 50%);
--color-infinity: #ff9800;
--color-eternity: #673ab7;
@ -194,6 +197,7 @@ html {
--color-good: #2f9e35;
--color-good-dark: #08510b;
--color-notification: yellow;
--color-overlay: rgba(60, 60, 100, 50%);
--color-prestige--accent: black;
@ -209,6 +213,7 @@ html {
--color-base: black;
--color-accent: #fbc21b;
--color-notification: yellow;
--color-overlay: rgba(60, 60, 100, 50%);
--color-prestige--accent: black;
@ -4146,27 +4151,52 @@ br {
cursor: pointer;
}
.o-autobuyer-toggle-checkbox__label-modal {
border-color: var(--color-text);
}
.s-base--metro .o-autobuyer-toggle-checkbox__label {
border-color: black;
}
.s-base--metro .o-autobuyer-toggle-checkbox__label-modal {
border-color: var(--color-text);
}
.t-dark .o-autobuyer-toggle-checkbox__label {
border-color: var(--color-base);
}
.t-dark .o-autobuyer-toggle-checkbox__label-modal {
border-color: var(--color-good-dark);
}
.t-dark-metro .o-autobuyer-toggle-checkbox__label {
border-color: var(--color-base);
}
.t-dark-metro .o-autobuyer-toggle-checkbox__label-modal {
border-color: var(--color-good-dark);
}
.t-s1 .o-autobuyer-toggle-checkbox__label {
border-color: black;
}
.t-s1 .o-autobuyer-toggle-checkbox__label-modal {
border-color: var(--color-text);
}
.t-s6 .o-autobuyer-toggle-checkbox__label,
.t-s10 .o-autobuyer-toggle-checkbox__label {
border-color: #cccccc;
}
.t-s6 .o-autobuyer-toggle-checkbox__label,
.t-s10 .o-autobuyer-toggle-checkbox__label {
border-color: var(--color-text);
}
.o-autobuyer-toggle-checkbox__label:hover {
transform: scale(1.1) translate(-0.1rem, 0.1rem);
}
@ -4372,6 +4402,14 @@ br {
padding: 1rem 2rem;
}
/* For some reason, we need to specifically define the font size here or else it balloons up to a size
bigger than that of the autobuyer tab. We also explicitly set the border color in order for it to appear
properly on certain themes. */
.c-autobuyer-box-row__modal {
font-size: 1.1rem;
border-color: var(--color-text);
}
.s-base--metro .c-autobuyer-box-row {
border-color: black;
}
@ -4380,15 +4418,41 @@ br {
border-color: var(--color-base);
}
.t-dark .c-autobuyer-box-row__modal {
background-color: rgb(55, 71, 79);
border-color: var(--color-good-dark);
}
.t-dark-metro .c-autobuyer-box-row {
border-color: var(--color-base);
}
.t-dark-metro .c-autobuyer-box-row__modal {
background-color: rgb(55, 71, 79);
border-color: var(--color-good-dark);
}
.t-s1 .c-autobuyer-box-row {
background: var(--color-base);
border-color: black;
}
.t-s2 .c-autobuyer-box-row__modal {
background-color: rgb(230, 230, 255);
}
.t-s3 .c-autobuyer-box-row__modal {
background-color: var(--color-base);
}
.t-s4 .c-autobuyer-box-row__modal {
background-color: red;
}
.t-s5 .c-autobuyer-box-row__modal {
background-color: var(--color-base);
}
.t-s6 .c-autobuyer-box-row,
.t-s10 .c-autobuyer-box-row {
background: black;
@ -5258,7 +5322,7 @@ br {
@keyframes a-modal-overlay-fadein {
from { background-color: rgba(0, 0, 0, 0%); }
to { background-color: rgba(60, 60, 100, 50%); }
to { background-color: var(--color-overlay); }
}
.l-modal-overlay {
@ -5271,7 +5335,7 @@ br {
left: 0;
z-index: 6;
animation-name: a-modal-overlay-fadein;
animation-duration: 2s;
animation-duration: 1s;
animation-fill-mode: forwards;
pointer-events: auto;
}

View File

@ -4,19 +4,30 @@ export default {
data() {
return {
currentTime: 0,
lastSave: 0,
cloudSaveEnabled: false,
lastLocalSave: 0,
lastCloudSave: 0,
showTimeSinceSave: false
};
},
computed: {
time() {
return timeDisplayShort(this.currentTime - this.lastSave);
}
localString() {
const desc = this.cloudSaveEnabled
? "Time since last local save:"
: "Time since last save:";
return `${desc} ${timeDisplayShort(this.currentTime - this.lastLocalSave)}`;
},
cloudString() {
if (!this.cloudSaveEnabled) return null;
return `Time since last cloud save: ${timeDisplayShort(this.currentTime - this.lastCloudSave)}`;
},
},
methods: {
update() {
this.lastSave = GameStorage.lastSaveTime;
this.currentTime = Date.now();
this.cloudSaveEnabled = player.options.cloudEnabled && Cloud.loggedIn;
this.lastLocalSave = GameStorage.lastSaveTime;
this.lastCloudSave = GameStorage.lastCloudSave;
this.showTimeSinceSave = player.options.showTimeSinceSave;
},
save() {
@ -32,7 +43,9 @@ export default {
class="o-save-timer"
@click="save"
>
Time since last save: {{ time }}
{{ localString }}
<br>
{{ cloudString }}
</div>
</template>
@ -42,6 +55,7 @@ export default {
position: absolute;
bottom: 0;
left: 0;
text-align: left;
z-index: 5;
color: var(--color-text);
background-color: var(--color-base);

View File

@ -0,0 +1,86 @@
<script>
import BigCrunchAutobuyerBox from "@/components/tabs/autobuyers/BigCrunchAutobuyerBox";
import DimensionBoostAutobuyerBox from "@/components/tabs/autobuyers/DimensionBoostAutobuyerBox";
import EternityAutobuyerBox from "@/components/tabs/autobuyers/EternityAutobuyerBox";
import GalaxyAutobuyerBox from "@/components/tabs/autobuyers/GalaxyAutobuyerBox";
import ModalWrapper from "@/components/modals/ModalWrapper";
import RealityAutobuyerBox from "@/components/tabs/autobuyers/RealityAutobuyerBox";
export default {
name: "AutobuyerEditModal",
components: {
BigCrunchAutobuyerBox,
DimensionBoostAutobuyerBox,
EternityAutobuyerBox,
GalaxyAutobuyerBox,
ModalWrapper,
RealityAutobuyerBox,
},
computed: {
header() {
return `Edit Autobuyers`;
},
message() {
// We have to have this edge-case due to a weird happening where you could open this modal
// during the Reality animation, which would then show an empty modal.
return Autobuyers.hasAutobuyersForEditModal
? `Using this modal, you can edit various values inside your autobuyers.`
: `You currently have no autobuyers which could be shown here.`;
},
},
};
</script>
<template>
<ModalWrapper>
<template #header>
{{ header }}
</template>
<div class="c-modal-message__text-fit">
<span>
{{ message }}
</span>
</div>
<!--
We only include these autobuyers as these are (probably) the ones that users will want to change
most often.
-->
<RealityAutobuyerBox
class="c-reality-pos"
is-modal
/>
<EternityAutobuyerBox
class="c-eternity-pos"
is-modal
/>
<BigCrunchAutobuyerBox
class="c-infinity-pos"
is-modal
/>
<GalaxyAutobuyerBox is-modal />
<DimensionBoostAutobuyerBox is-modal />
</ModalWrapper>
</template>
<style scoped>
/* From AutobuyersTab.vue */
/* This is necessary for the ExpandingControlBox within these components to render in the right stacking order
when they're open. It looks slightly hacky but actually can't be done any other way; each AutobuyerBox creates
its own stacking context, which means that all z-indices specified within are essentially scoped and the
AutobuyerBox components will always render in page order regardless of internal z-indices without these. */
.c-reality-pos {
z-index: 3;
}
.c-eternity-pos {
z-index: 2;
}
.c-infinity-pos {
z-index: 1;
}
.c-modal-message__text-fit {
width: auto;
}
</style>

View File

@ -2,6 +2,12 @@
import ModalWrapperChoice from "@/components/modals/ModalWrapperChoice";
import PrimaryButton from "@/components/PrimaryButton";
const OFFLINE_PROGRESS_TYPE = {
IMPORTED: 0,
LOCAL: 1,
IGNORED: 2,
};
export default {
name: "ImportSaveModal",
components: {
@ -12,6 +18,7 @@ export default {
return {
input: "",
importCounter: 0,
offlineImport: OFFLINE_PROGRESS_TYPE.IMPORTED,
};
},
computed: {
@ -56,12 +63,66 @@ export default {
},
clicksLeft() {
return 5 - this.importCounter;
},
timeSinceSave() {
return TimeSpan.fromMilliseconds(Date.now() - this.player.lastUpdate).toString();
},
offlineType() {
// We update here in the computed method instead of elsewhere because otherwise it initializes the text
// to a wrong or undefined setting
this.updateOfflineSettings();
switch (this.offlineImport) {
case OFFLINE_PROGRESS_TYPE.IMPORTED:
return "Using imported save settings";
case OFFLINE_PROGRESS_TYPE.LOCAL:
return "Using existing save settings";
case OFFLINE_PROGRESS_TYPE.IGNORED:
return "Will not simulate offline time";
default:
throw new Error("Unrecognized offline progress setting for importing");
}
},
offlineDetails() {
if (this.offlineImport === OFFLINE_PROGRESS_TYPE.IGNORED) {
return `Save will be imported without offline progress.`;
}
const ticks = GameStorage.offlineTicks;
return GameStorage.offlineEnabled
? `After importing, will simulate ${formatInt(ticks)} ticks of duration
${TimeSpan.fromMilliseconds((Date.now() - this.player.lastUpdate) / ticks).toStringShort()} each.`
: "This setting will not apply any offline progress after importing.";
}
},
mounted() {
this.$refs.input.select();
},
destroyed() {
// Explicitly setting this to undefined after closing forces the game to fall-back to the stored settings within
// the player object if this modal is closed - ie. it makes sure actions in the modal don't persist
GameStorage.offlineEnabled = undefined;
GameStorage.offlineTicks = undefined;
},
methods: {
changeOfflineSetting() {
this.offlineImport = (this.offlineImport + 1) % 3;
},
updateOfflineSettings() {
switch (this.offlineImport) {
case OFFLINE_PROGRESS_TYPE.IMPORTED:
// These are default values from a new save, used if importing from pre-reality where these props don't exist
GameStorage.offlineEnabled = this.player.options.offlineProgress ?? true;
GameStorage.offlineTicks = this.player.options.offlineTicks ?? 1000;
break;
case OFFLINE_PROGRESS_TYPE.LOCAL:
GameStorage.offlineEnabled = player.options.offlineProgress;
GameStorage.offlineTicks = player.options.offlineTicks;
break;
case OFFLINE_PROGRESS_TYPE.IGNORED:
GameStorage.offlineEnabled = false;
break;
}
},
importSave() {
this.importCounter++;
if (this.hasLessSTDs && this.clicksLeft > 0) return;
@ -108,7 +169,18 @@ export default {
Realities: {{ formatPostBreak(player.realities, 2) }}
</div>
<div class="c-modal-import__warning">
(your current save file will be overwritten!)
(Your current save file will be overwritten!)
</div>
<br>
<div>
This save was last opened {{ timeSinceSave }} ago.
<div
class="o-primary-btn"
@click="changeOfflineSetting"
>
Offline Progress: {{ offlineType }}
</div>
<span v-html="offlineDetails" />
</div>
<div
v-if="hasLessSTDs"

View File

@ -48,6 +48,11 @@ export default {
icon="fa-brands fa-google-play"
link="https://play.google.com/store/apps/details?id=kajfosz.antimatterdimensions"
/>
<InformationModalButton
name="Antimatter Dimensions on Steam"
icon="fa-brands fa-steam"
link="https://store.steampowered.com/app/1399720/Antimatter_Dimensions/"
/>
<InformationModalButton
name="Credits"
icon="fa-solid fa-users"

View File

@ -20,47 +20,50 @@ export default {
};
},
computed: {
name() {
return this.unlockedQuotes[0].quote._celestial;
},
focusedQuote() {
return this.unlockedQuotes[this.focusedQuoteId];
},
currentQuoteLine() {
return this.focusedQuote.currentLine;
},
commonButtonClass() {
const lightBG = this.name === "laitela" && !Theme.current().isDark();
return {
"fas c-modal-celestial-quote-history__arrow": true,
"o-dark-button": lightBG,
"o-light-button": !lightBG,
};
},
upClass() {
return {
"c-modal-celestial-quote-history__arrow": true,
"c-modal-celestial-quote-history__arrow-up": true,
...this.commonButtonClass,
"c-modal-celestial-quote-history__arrow-up fa-chevron-circle-up": true,
"c-modal-celestial-quote-history__arrow--disabled": this.focusedQuoteId <= 0,
"fas": true,
"fa-chevron-circle-up": true,
};
},
downClass() {
return {
"c-modal-celestial-quote-history__arrow": true,
"c-modal-celestial-quote-history__arrow-down": true,
...this.commonButtonClass,
"c-modal-celestial-quote-history__arrow-down fa-chevron-circle-down": true,
"c-modal-celestial-quote-history__arrow--disabled": this.focusedQuoteId >= this.unlockedQuotes.length - 1,
"fas": true,
"fa-chevron-circle-down": true,
};
},
leftClass() {
return {
"c-modal-celestial-quote-history__arrow": true,
"c-modal-celestial-quote-history__arrow-left": true,
...this.commonButtonClass,
"c-modal-celestial-quote-history__arrow-left fa-chevron-circle-left": true,
"c-modal-celestial-quote-history__arrow--disabled": this.currentQuoteLine <= 0,
"fas": true,
"fa-chevron-circle-left": true,
};
},
rightClass() {
return {
"c-modal-celestial-quote-history__arrow": true,
"c-modal-celestial-quote-history__arrow-right": true,
...this.commonButtonClass,
"c-modal-celestial-quote-history__arrow-right fa-chevron-circle-right": true,
"c-modal-celestial-quote-history__arrow--disabled":
this.currentQuoteLine >= this.focusedQuote.quote.totalLines - 1,
"fas": true,
"fa-chevron-circle-right": true,
};
},
},
@ -140,7 +143,7 @@ function easeOut(x) {
<template>
<div class="l-modal-overlay c-modal-overlay">
<i
class="c-modal-celestial-quote-history__close fas fa-circle-xmark"
class="c-modal-celestial-quote-history__close fas fa-circle-xmark o-light-button"
@click="close"
/>
<div
@ -210,10 +213,17 @@ function easeOut(x) {
position: absolute;
z-index: 1;
font-size: 2.5rem;
color: var(--color-text);
cursor: pointer;
}
.o-light-button {
color: white;
}
.o-dark-button {
color: black;
}
.c-modal-celestial-quote-history__arrow--disabled {
opacity: 0.4;
cursor: default;

View File

@ -31,6 +31,29 @@ export default {
},
clicksLeft() {
return 5 - this.overwriteCounter;
},
suggestionText() {
const goodStyle = `style="color: var(--color-good)"`;
const badStyle = `style="color: var(--color-bad)"`;
const suggestions = ["Loading this Cloud save "];
const cloudProg = this.conflict.cloud.compositeProgress, localProg = this.conflict.local.compositeProgress;
const warnOverwrite = this.farther && Math.abs(cloudProg - localProg) > 0.15;
suggestions.push(warnOverwrite
? `<b ${badStyle}>would cause your local save to lose significant progress</b>`
: `<b ${goodStyle}>is probably safe</b>`);
suggestions.push(this.hasLessSTDs
? ` ${warnOverwrite ? "and" : "but"} <b ${badStyle}>will cause you to lose STDs on your local save</b>.`
: "."
);
if (this.hasDifferentName) {
suggestions.push(`<br>${warnOverwrite ? "Additionally" : "However"}, the Cloud save
<b ${badStyle}>may be a save from a different device</b>.`);
}
if (warnOverwrite || this.hasDifferentName) {
suggestions.push(`<br><b ${badStyle}>Are you sure you wish to overwrite your local save?</b>`);
}
return suggestions.join("");
}
},
methods: {
@ -54,21 +77,19 @@ export default {
<template #header>
Load Game from Cloud
</template>
<b>
<span v-if="hasDifferentName">
Your Local and Cloud Saves have different names.
</span>
<span v-else-if="older">
Your Local Save appears to be older than your Cloud Save.
</span>
<span v-else-if="farther">
Your Local Save appears to be farther than your Cloud Save.
</span>
<span v-else>
Your Local Save and Cloud Save appear to have similar amounts of progress.
</span>
Please select the save you want to load.
</b>
<span v-if="hasDifferentName">
Your Local and Cloud Saves have <b>different names</b>.
</span>
<span v-else-if="older">
Loading from the Cloud would <b>load a save with less playtime</b>.
</span>
<span v-else-if="farther">
Loading from the Cloud would <b>cause you to lose progress</b>.
</span>
<span v-else>
Your Local Save and Cloud Save <b>appear to have similar amounts of progress</b>.
</span>
Please select the save you want to load.
<br>
<SaveInfoEntry
:save-data="conflict.local"
@ -84,6 +105,7 @@ export default {
:show-name="hasDifferentName"
save-type="Cloud Save"
/>
<span v-html="suggestionText" />
<div
v-if="hasLessSTDs"
class="c-modal-IAP__warning"

View File

@ -34,6 +34,29 @@ export default {
},
clicksLeft() {
return 5 - this.overwriteCounter;
},
suggestionText() {
const goodStyle = `style="color: var(--color-good)"`;
const badStyle = `style="color: var(--color-bad)"`;
const suggestions = ["Saving to the Cloud "];
const cloudProg = this.conflict.cloud.compositeProgress, localProg = this.conflict.local.compositeProgress;
const warnOverwrite = this.farther && Math.abs(cloudProg - localProg) > 0.15;
suggestions.push(warnOverwrite
? `<b ${badStyle}>would overwrite a save with significantly more progress</b>`
: `<b ${goodStyle}>is probably safe</b>`);
suggestions.push(this.hasLessSTDs
? ` ${warnOverwrite ? "and" : "but"} <b ${badStyle}>will cause your Cloud save to lose STDs</b>.`
: "."
);
if (this.hasDifferentName || this.wrongHash) {
suggestions.push(` ${warnOverwrite ? "Additionally" : "However"}, you may be overwriting a
<b ${badStyle}>save from a different device</b>.`);
}
if (warnOverwrite || this.hasDifferentName || this.wrongHash) {
suggestions.push(`<br><b ${badStyle}>Are you sure you wish to overwrite the Cloud save?</b>`);
}
return suggestions.join("");
}
},
methods: {
@ -43,6 +66,7 @@ export default {
if (accepted) {
this.conflict.onAccept?.();
}
EventHub.dispatch(GAME_EVENT.CLOSE_MODAL);
},
cancel() {
this.overwriteCounter++;
@ -67,23 +91,21 @@ export default {
<template #header>
Save Game to Cloud
</template>
<b>
<span v-if="wrongHash">
Your Cloud Save has been changed by someone else since you last saved to the Cloud this session.
</span>
<span v-else-if="hasDifferentName">
Your Local and Cloud Saves have different names.
</span>
<span v-else-if="older">
Your Cloud Save appears to be older than your Local Save.
</span>
<span v-else-if="farther">
Your Cloud Save appears to be farther than your Local Save.
</span>
<span v-else>
Your Local Save and Cloud Save appear to have similar amounts of progress.
</span>
</b>
<span v-if="wrongHash">
Your Cloud Save has been <b>changed by a different device</b> since you last saved to the Cloud this session.
</span>
<span v-else-if="hasDifferentName">
Your Local and Cloud Saves have <b>different names</b>.
</span>
<span v-else-if="older">
Saving to the Cloud would <b>overwrite an older save</b>.
</span>
<span v-else-if="farther">
Saving to the Cloud would <b>overwrite a save with more progress</b>.
</span>
<span v-else>
Your Local Save and Cloud Save <b>appear to have similar amounts of progress</b>.
</span>
<br>
<SaveInfoEntry
:save-data="conflict.local"
@ -99,8 +121,7 @@ export default {
:show-name="hasDifferentName"
save-type="Cloud Save"
/>
Would you like to overwrite the Cloud Save? Your choice here will apply for every
time the game automatically attempts to Cloud Save, until the page is reloaded.
<span v-html="suggestionText" />
<div
v-if="hasLessSTDs"
class="c-modal-IAP__warning"
@ -108,6 +129,9 @@ export default {
LOCAL SAVE HAS LESS STDs BOUGHT, YOU WILL LOSE THEM IF YOU OVERWRITE.
<br>CLICK THE BUTTON 5 TIMES TO CONFIRM.
</div>
<br>
Choosing to overwrite will force a save to the Cloud every time, while choosing to not
overwrite will effectively disable Cloud saving. This lasts until you reload the page.
<template #cancel-text>
Overwrite Cloud Save <span v-if="hasLessSTDs">({{ clicksLeft }})</span>
</template>
@ -115,4 +139,4 @@ export default {
Do not overwrite
</template>
</ModalWrapperChoice>
</template>
</template>

View File

@ -22,7 +22,7 @@ export default {
},
methods: {
update() {
this.tachyonGain.copyFrom(getTachyonGain());
this.tachyonGain.copyFrom(getTachyonGain(true));
this.isDoomed = Pelle.isDoomed;
},
handleYesClick() {

View File

@ -21,6 +21,11 @@ export default {
type: Boolean,
required: false,
default: false
},
isModal: {
type: Boolean,
required: false,
default: false
}
},
data() {
@ -44,6 +49,12 @@ export default {
"o-primary-btn--disabled": !this.isUnlockable
};
},
autobuyerBoxRowClass() {
return {
"c-autobuyer-box-row": true,
"c-autobuyer-box-row__modal": this.isModal
};
},
autobuyerToggleClass() {
if (!this.globalToggle) {
return this.isActive ? "fas fa-pause" : "fas fa-times";
@ -54,6 +65,7 @@ export default {
if (!this.globalToggle) {
return {
"o-autobuyer-toggle-checkbox__label": true,
"o-autobuyer-toggle-checkbox__label-modal": this.isModal,
"o-autobuyer-toggle-checkbox__label--active-paused": this.isActive,
"o-autobuyer-toggle-checkbox__label--deactive-paused": !this.isActive,
"o-autobuyer-toggle-checkbox__label--disabled": !this.globalToggle
@ -61,6 +73,7 @@ export default {
}
return {
"o-autobuyer-toggle-checkbox__label": true,
"o-autobuyer-toggle-checkbox__label-modal": this.isModal,
"o-autobuyer-toggle-checkbox__label--active": this.isActive,
"o-autobuyer-toggle-checkbox__label--disabled": !this.globalToggle
};
@ -104,7 +117,7 @@ export default {
<template>
<div
v-if="isUnlocked || isBought"
class="c-autobuyer-box-row"
:class="autobuyerBoxRowClass"
>
<div class="l-autobuyer-box__header">
{{ name }}

View File

@ -14,6 +14,13 @@ export default {
ExpandingControlBox,
AutobuyerDropdownEntry
},
props: {
isModal: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
isDoomed: false,
@ -84,6 +91,7 @@ export default {
<AutobuyerBox
:autobuyer="autobuyer"
:show-interval="!postBreak"
:is-modal="isModal"
name="Automatic Big Crunch"
>
<template

View File

@ -10,6 +10,13 @@ export default {
AutobuyerIntervalButton,
AutobuyerInput
},
props: {
isModal: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
hasMaxedInterval: false,
@ -44,6 +51,7 @@ export default {
<template>
<AutobuyerBox
:autobuyer="autobuyer"
:is-modal="isModal"
:show-interval="!isBuyMaxUnlocked"
name="Automatic Dimension Boosts"
>

View File

@ -12,6 +12,13 @@ export default {
ExpandingControlBox,
AutobuyerDropdownEntry
},
props: {
isModal: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
isDoomed: false,
@ -77,6 +84,7 @@ export default {
<template>
<AutobuyerBox
:autobuyer="autobuyer"
:is-modal="isModal"
name="Automatic Eternity"
>
<template #intervalSlot>

View File

@ -10,6 +10,13 @@ export default {
AutobuyerIntervalButton,
AutobuyerInput
},
props: {
isModal: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
hasMaxedInterval: false,
@ -42,6 +49,7 @@ export default {
<template>
<AutobuyerBox
:autobuyer="autobuyer"
:is-modal="isModal"
name="Automatic Antimatter Galaxies"
:show-interval="!isBuyMaxUnlocked"
>

View File

@ -12,6 +12,13 @@ export default {
ExpandingControlBox,
AutobuyerDropdownEntry
},
props: {
isModal: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
mode: AUTO_REALITY_MODE.RM,
@ -50,6 +57,7 @@ export default {
<template>
<AutobuyerBox
:autobuyer="autobuyer"
:is-modal="isModal"
name="Automatic Reality"
>
<template #intervalSlot>

View File

@ -45,7 +45,8 @@ export default {
update() {
this.isDisabled = this.challenge.isDisabled;
this.isUnlocked = this.challenge.isUnlocked;
this.isRunning = this.challenge.isRunning;
// This stops normal challenges from appearing like they're running during IC1
this.isRunning = this.challenge.isOnlyActiveChallenge;
this.lockedAt = this.challenge.config.lockedAt;
this.isBroken = Enslaved.isRunning && Enslaved.BROKEN_CHALLENGES.includes(this.challengeId);
this.isCompleted = this.challenge.isCompleted && !this.isBroken;

View File

@ -6,29 +6,43 @@ export default {
components: {
SliderComponent
},
props: {
min: {
type: Number,
required: true,
},
max: {
type: Number,
required: true,
},
interval: {
type: Number,
required: true,
}
},
data() {
return {
autosaveInterval: 10
sliderInterval: 10
};
},
computed: {
sliderProps() {
return {
min: 10,
max: 60,
interval: 1,
min: this.min,
max: this.max,
interval: this.interval,
width: "100%",
tooltip: false
};
}
},
},
methods: {
update() {
this.autosaveInterval = player.options.autosaveInterval / 1000;
this.sliderInterval = player.options.autosaveInterval / 1000;
},
adjustSliderValue(value) {
this.autosaveInterval = value;
player.options.autosaveInterval = this.autosaveInterval * 1000;
this.sliderInterval = value;
player.options.autosaveInterval = this.sliderInterval * 1000;
GameOptions.refreshAutosaveInterval();
}
}
@ -37,11 +51,11 @@ export default {
<template>
<div class="o-primary-btn o-primary-btn--option o-primary-btn--slider l-options-grid__button">
<b>Autosave interval: {{ formatInt(autosaveInterval) }}s</b>
<b>Autosave interval: {{ formatInt(sliderInterval) }}s</b>
<SliderComponent
class="o-primary-btn--slider__slider"
v-bind="sliderProps"
:value="autosaveInterval"
:value="sliderInterval"
@input="adjustSliderValue($event)"
/>
</div>

View File

@ -17,17 +17,22 @@ export default {
data() {
return {
cloudEnabled: false,
syncSaveIntervals: false,
showTimeSinceSave: false,
loggedIn: false,
userName: "",
canSpeedrun: false,
creditsClosed: false
creditsClosed: false,
isCloudSaving: false,
};
},
watch: {
cloudEnabled(newValue) {
player.options.cloudEnabled = newValue;
},
syncSaveIntervals(newValue) {
player.options.syncSaveIntervals = newValue;
},
showTimeSinceSave(newValue) {
player.options.showTimeSinceSave = newValue;
}
@ -36,12 +41,14 @@ export default {
update() {
const options = player.options;
this.cloudEnabled = options.cloudEnabled;
this.syncSaveIntervals = options.syncSaveIntervals;
this.showTimeSinceSave = options.showTimeSinceSave;
this.loggedIn = Cloud.loggedIn;
this.canSpeedrun = player.speedrun.isUnlocked;
this.creditsClosed = GameEnd.creditsEverClosed;
if (!this.loggedIn) return;
this.userName = Cloud.user.displayName;
this.isCloudSaving = Cloud.shouldOverwriteCloudSave;
},
importAsFile(event) {
// This happens if the file dialog is canceled instead of a file being selected
@ -52,6 +59,15 @@ export default {
const contents = reader.result;
const toImport = GameSaveSerializer.deserialize(contents);
const showWarning = (toImport?.IAP?.totalSTD ?? 0) < player.IAP.totalSTD;
// File importing behavior should use the behavior on the existing and to-be-overwritten save instead of the
// settings in the to-be-imported save. This is largely because the former is more easily edited by the player,
// and in contrast with the import-as-string case which allows the player to choose.
// Note: Do not move this into GameStorage.import, as this would cause the offline progress choice in the text
// import modal (the only other place GameStorage.import is called) to always be overridden
GameStorage.offlineEnabled = player.options.offlineProgress;
GameStorage.offlineTicks = player.options.offlineTicks;
if (showWarning) {
Modal.addImportConflict(toImport, GameStorage.saves[GameStorage.currentSlot]);
Modal.importWarning.show({
@ -110,7 +126,11 @@ export default {
>
Choose save
</OptionsButton>
<AutosaveIntervalSlider />
<AutosaveIntervalSlider
:min="10"
:max="60"
:interval="1"
/>
</div>
<div class="l-options-grid__row">
<OptionsButton
@ -155,31 +175,28 @@ export default {
<span v-if="loggedIn">Logged in as {{ userName }}</span>
<span v-else>Not logged in</span>
</h2>
<div v-if="loggedIn">
<span v-if="isCloudSaving">Cloud Saving will occur automatically every 5 minutes.</span>
<span v-else>Cloud Saving has been disabled until you refresh the page or switch saves.</span>
</div>
<div class="l-options-grid">
<div
v-if="loggedIn"
class="l-options-grid__row"
>
<OptionsButton
v-if="loggedIn"
onclick="GameOptions.cloudSave()"
:class="{ 'o-pelle-disabled-pointer': creditsClosed }"
>
Cloud save
</OptionsButton>
<OptionsButton
v-if="loggedIn"
onclick="GameOptions.cloudLoad()"
:class="{ 'o-pelle-disabled-pointer': creditsClosed }"
>
Cloud load
</OptionsButton>
<PrimaryToggleButton
v-model="cloudEnabled"
class="o-primary-btn--option l-options-grid__button"
:class="{ 'o-pelle-disabled-pointer': creditsClosed }"
label="Automatic cloud saving/loading:"
/>
</div>
<div class="l-options-grid__row">
<OptionsButton
v-if="loggedIn"
onclick="GameOptions.logout()"
@ -194,6 +211,23 @@ export default {
Login with Google to enable Cloud Saving
</OptionsButton>
</div>
<div
v-if="loggedIn"
class="l-options-grid__row"
>
<PrimaryToggleButton
v-model="cloudEnabled"
class="o-primary-btn--option l-options-grid__button"
:class="{ 'o-pelle-disabled-pointer': creditsClosed }"
label="Automatic cloud saving/loading:"
/>
<PrimaryToggleButton
v-model="syncSaveIntervals"
class="o-primary-btn--option l-options-grid__button"
:class="{ 'o-pelle-disabled-pointer': creditsClosed }"
label="Force local save before cloud saving:"
/>
</div>
</div>
</div>
</template>

View File

@ -25,7 +25,6 @@ export default {
theme: "",
notation: "",
commas: false,
autosaveInterval: 3000,
headerTextColored: true,
};
},

View File

@ -29,9 +29,10 @@ export default {
this.showRequirement = Pelle.isDoomed && !Pelle.canDilateInPelle;
if (!this.isRunning) return;
this.canEternity = Player.canEternity;
this.hasGain = getTachyonGain().gt(0);
// This lets this.hasGain be true even before eternity.
this.hasGain = getTachyonGain(false).gt(0);
if (this.canEternity && this.hasGain) {
this.tachyonGain.copyFrom(getTachyonGain());
this.tachyonGain.copyFrom(getTachyonGain(true));
} else if (this.hasGain) {
this.eternityGoal.copyFrom(Player.eternityGoal);
} else {

View File

@ -142,15 +142,13 @@ export default {
this.gainedEP.copyFrom(gainedEP);
const hasNewContent = !PlayerProgress.realityUnlocked() &&
Currency.eternityPoints.exponent >= 4000 &&
Currency.timeTheorems.gte(5e9) &&
player.records.thisReality.maxReplicanti.exponent > 20000;
!TimeStudy.reality.isBought;
if (player.dilation.active) {
this.type = hasNewContent
? EP_BUTTON_DISPLAY_TYPE.DILATION_EXPLORE_NEW_CONTENT
: EP_BUTTON_DISPLAY_TYPE.DILATION;
this.currentTachyons.copyFrom(Currency.tachyonParticles);
this.gainedTachyons.copyFrom(getTachyonGain());
this.gainedTachyons.copyFrom(getTachyonGain(true));
return;
}