mirror of
https://github.com/IvarK/AntimatterDimensionsSourceCode.git
synced 2024-09-20 11:01:45 +00:00
Add backup save modal and loading functionality
This commit is contained in:
parent
b076c38504
commit
3a50050f79
105
src/components/modals/options/BackupEntry.vue
Normal file
105
src/components/modals/options/BackupEntry.vue
Normal file
|
@ -0,0 +1,105 @@
|
|||
<script>
|
||||
import PrimaryButton from "@/components/PrimaryButton";
|
||||
|
||||
import { BACKUP_SLOT_TYPE } from "@/core/storage";
|
||||
|
||||
export default {
|
||||
name: "BackupEntry",
|
||||
components: {
|
||||
PrimaryButton
|
||||
},
|
||||
props: {
|
||||
slotData: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currTime: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
save() {
|
||||
return GameStorage.loadFromBackup(this.slotData.id);
|
||||
},
|
||||
progressStr() {
|
||||
if (!this.save) return "(Empty)";
|
||||
const rm = new Decimal(this.save.reality.realityMachines);
|
||||
if (rm.gt(0)) return `Reality Machines: ${format(new Decimal(rm), 2)}`;
|
||||
const ep = new Decimal(this.save.eternityPoints);
|
||||
if (ep.gt(0)) return `Eternity Points: ${format(new Decimal(ep), 2)}`;
|
||||
const ip = new Decimal(this.save.infinityPoints);
|
||||
if (ip.gt(0)) return `Infinity Points: ${format(new Decimal(ip), 2)}`;
|
||||
return `Antimatter: ${formatPostBreak(new Decimal(this.save.antimatter), 2, 1)}`;
|
||||
},
|
||||
slotType() {
|
||||
const formattedTime = this.slotData.intervalStr?.();
|
||||
switch (this.slotData.type) {
|
||||
case BACKUP_SLOT_TYPE.ONLINE:
|
||||
return `Saves every ${formattedTime} online`;
|
||||
case BACKUP_SLOT_TYPE.OFFLINE:
|
||||
return `Saves after ${formattedTime} offline`;
|
||||
case BACKUP_SLOT_TYPE.RESERVE:
|
||||
return "Pre-loading save";
|
||||
default:
|
||||
throw new Error("Unrecognized backup save type");
|
||||
}
|
||||
},
|
||||
lastSaved() {
|
||||
const lastSave = GameStorage.backupTimeData[this.slotData.id].last;
|
||||
return lastSave
|
||||
? `Last saved: ${TimeSpan.fromMilliseconds(this.currTime - lastSave)} ago`
|
||||
: "Slot not currently in use";
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update() {
|
||||
this.currTime = Date.now();
|
||||
},
|
||||
load() {
|
||||
if (!this.save) return;
|
||||
// This seems to be the only way to properly hide the modal after the save is properly loaded,
|
||||
// since the offline progress modal appears nearly immediately after clicking the button
|
||||
Modal.hide();
|
||||
if (this.slotData.type !== BACKUP_SLOT_TYPE.RESERVE) GameStorage.saveToReserveSlot();
|
||||
GameStorage.loadPlayerObject(this.save);
|
||||
GameUI.notify.info(`Game loaded from backup slot #${this.slotData.id}`);
|
||||
GameStorage.processLocalBackups();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="c-bordered-entry">
|
||||
<h3>Slot #{{ slotData.id }}:</h3>
|
||||
<span>{{ progressStr }}</span>
|
||||
<span>{{ slotType }}</span>
|
||||
<span class="c-fixed-height">{{ lastSaved }}</span>
|
||||
<PrimaryButton
|
||||
class="o-primary-btn--width-medium"
|
||||
:class="{ 'o-primary-btn--disabled' : !save }"
|
||||
@click="load()"
|
||||
>
|
||||
Load
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.c-bordered-entry {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 1.1rem;
|
||||
border: var(--var-border-width, 0.2rem) solid;
|
||||
border-radius: var(--var-border-radius, 0.4rem);
|
||||
padding: 0.5rem 0.3rem;
|
||||
margin: 0.3rem;
|
||||
}
|
||||
|
||||
.c-fixed-height {
|
||||
height: 4rem;
|
||||
}
|
||||
</style>
|
65
src/components/modals/options/BackupWindowModal.vue
Normal file
65
src/components/modals/options/BackupWindowModal.vue
Normal file
|
@ -0,0 +1,65 @@
|
|||
<script>
|
||||
import BackupEntry from "@/components/modals/options/BackupEntry";
|
||||
import ModalWrapper from "@/components/modals/ModalWrapper";
|
||||
|
||||
import { AutoBackupSlots } from "@/core/storage";
|
||||
|
||||
export default {
|
||||
name: "BackupWindowModal",
|
||||
components: {
|
||||
ModalWrapper,
|
||||
BackupEntry
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Used to force a key-swap whenever a save happens, to make unused slots immediately update
|
||||
nextSave: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
backupSlots: () => AutoBackupSlots,
|
||||
},
|
||||
methods: {
|
||||
update() {
|
||||
this.nextSave = GameStorage.nextBackup;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalWrapper>
|
||||
<template #header>
|
||||
Automatic Backup Saves
|
||||
</template>
|
||||
<div class="c-info">
|
||||
The game makes automatic backups based on time you have spent online or offline.
|
||||
Additionally, your current save is saved into the last slot any time a backup from here is loaded.
|
||||
<div class="c-entry-container">
|
||||
<BackupEntry
|
||||
v-for="slot in backupSlots"
|
||||
:key="nextSave + slot.id"
|
||||
class="l-backup-entry"
|
||||
:slot-data="slot"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.c-info {
|
||||
width: 60rem;
|
||||
}
|
||||
|
||||
.c-entry-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.l-backup-entry {
|
||||
width: calc(50% - 0.6rem);
|
||||
height: calc(25% - 0.6rem);
|
||||
}
|
||||
</style>
|
|
@ -189,7 +189,15 @@ export default {
|
|||
/>
|
||||
</div>
|
||||
<div class="l-options-grid__row">
|
||||
<OptionsButton
|
||||
:class="{ 'o-pelle-disabled-pointer': creditsClosed }"
|
||||
onclick="Modal.backupWindows.show()"
|
||||
>
|
||||
Open Automatic Save Backup Menu
|
||||
</OptionsButton>
|
||||
<SaveFileName />
|
||||
</div>
|
||||
<div class="l-options-grid__row">
|
||||
<OptionsButton
|
||||
v-if="canSpeedrun"
|
||||
class="o-primary-btn--option_font-x-large"
|
||||
|
|
|
@ -11,7 +11,6 @@ import NormalChallengeStartModal from "@/components/modals/challenges/NormalChal
|
|||
import AntimatterGalaxyModal from "@/components/modals/prestige/AntimatterGalaxyModal";
|
||||
import ArmageddonModal from "@/components/modals/prestige/ArmageddonModal";
|
||||
import BigCrunchModal from "@/components/modals/prestige/BigCrunchModal";
|
||||
import ChangeNameModal from "@/components/modals/ChangeNameModal";
|
||||
import DimensionBoostModal from "@/components/modals/prestige/DimensionBoostModal";
|
||||
import EnterCelestialsModal from "@/components/modals/prestige/EnterCelestialsModal";
|
||||
import EnterDilationModal from "@/components/modals/prestige/EnterDilationModal";
|
||||
|
@ -19,14 +18,13 @@ import EternityModal from "@/components/modals/prestige/EternityModal";
|
|||
import ExitChallengeModal from "@/components/modals/prestige/ExitChallengeModal";
|
||||
import ExitDilationModal from "@/components/modals/prestige/ExitDilationModal";
|
||||
import HardResetModal from "@/components/modals/prestige/HardResetModal";
|
||||
import ModifySeedModal from "@/components/modals/ModifySeedModal";
|
||||
import RealityModal from "@/components/modals/prestige/RealityModal";
|
||||
import ReplicantiGalaxyModal from "@/components/modals/prestige/ReplicantiGalaxyModal";
|
||||
import ResetRealityModal from "@/components/modals/prestige/ResetRealityModal";
|
||||
import SpeedrunModeModal from "@/components/modals/SpeedrunModeModal";
|
||||
|
||||
import AnimationOptionsModal from "@/components/modals/options/AnimationOptionsModal";
|
||||
import AwayProgressOptionsModal from "@/components/modals/options/AwayProgressOptionsModal";
|
||||
import BackupWindowModal from "@/components/modals/options/BackupWindowModal";
|
||||
import ConfirmationOptionsModal from "@/components/modals/options/ConfirmationOptionsModal";
|
||||
import CosmeticSetChoiceModal from "@/components/modals/options/glyph-appearance/CosmeticSetChoiceModal";
|
||||
import GlyphDisplayOptionsModal from "@/components/modals/options/glyph-appearance/GlyphDisplayOptionsModal";
|
||||
|
@ -51,6 +49,7 @@ import AwayProgressModal from "@/components/modals/AwayProgressModal";
|
|||
import BreakInfinityModal from "@/components/modals/BreakInfinityModal";
|
||||
import CatchupModal from "@/components/modals/catchup/CatchupModal";
|
||||
import ChangelogModal from "@/components/modals/ChangelogModal";
|
||||
import ChangeNameModal from "@/components/modals/ChangeNameModal";
|
||||
import ClearConstantsModal from "@/components/modals/ClearConstantsModal";
|
||||
import CreditsModal from "@/components/modals/CreditsModal";
|
||||
import DeleteAutomatorScriptModal from "@/components/modals/DeleteAutomatorScriptModal";
|
||||
|
@ -64,12 +63,14 @@ import ImportSaveModal from "@/components/modals/ImportSaveModal";
|
|||
import ImportTimeStudyConstants from "@/components/modals/ImportTimeStudyConstants";
|
||||
import InformationModal from "@/components/modals/InformationModal";
|
||||
import LoadGameModal from "@/components/modals/LoadGameModal";
|
||||
import ModifySeedModal from "@/components/modals/ModifySeedModal";
|
||||
import PelleEffectsModal from "@/components/modals/PelleEffectsModal";
|
||||
import RealityGlyphCreationModal from "@/components/modals/RealityGlyphCreationModal";
|
||||
import ReplaceGlyphModal from "@/components/modals/ReplaceGlyphModal";
|
||||
import RespecIAPModal from "@/components/modals/RespecIAPModal";
|
||||
import SacrificeModal from "@/components/modals/SacrificeModal";
|
||||
import SingularityMilestonesModal from "@/components/modals/SingularityMilestonesModal";
|
||||
import SpeedrunModeModal from "@/components/modals/SpeedrunModeModal";
|
||||
import StdStoreModal from "@/components/modals/StdStoreModal";
|
||||
import StudyStringModal from "@/components/modals/StudyStringModal";
|
||||
import SwitchAutomatorEditorModal from "@/components/modals/SwitchAutomatorEditorModal";
|
||||
|
@ -211,6 +212,7 @@ Modal.reality = new Modal(RealityModal, 1, GAME_EVENT.REALITY_RESET_AFTER);
|
|||
Modal.resetReality = new Modal(ResetRealityModal, 1, GAME_EVENT.REALITY_RESET_AFTER);
|
||||
Modal.celestials = new Modal(EnterCelestialsModal, 1);
|
||||
Modal.hardReset = new Modal(HardResetModal, 1);
|
||||
Modal.backupWindows = new Modal(BackupWindowModal, 1);
|
||||
Modal.enterSpeedrun = new Modal(SpeedrunModeModal);
|
||||
Modal.modifySeed = new Modal(ModifySeedModal);
|
||||
Modal.changeName = new Modal(ChangeNameModal);
|
||||
|
|
|
@ -6,47 +6,55 @@ import { migrations } from "./migrations";
|
|||
|
||||
import { deepmergeAll } from "@/utility/deepmerge";
|
||||
|
||||
const BACKUP_SLOT_TYPE = {
|
||||
export const BACKUP_SLOT_TYPE = {
|
||||
ONLINE: 0,
|
||||
OFFLINE: 1,
|
||||
RESERVE: 2,
|
||||
};
|
||||
|
||||
// Note: interval is in seconds, and only the first RESERVE slot is ever used
|
||||
// Note: interval is in seconds, and only the first RESERVE slot is ever used. Having intervalStr as a redundant
|
||||
// prop is necessary because using our TimeSpan formatting functions produces undesirable strings like "1.00 minutes"
|
||||
export const AutoBackupSlots = [
|
||||
{
|
||||
id: 1,
|
||||
type: BACKUP_SLOT_TYPE.ONLINE,
|
||||
intervalStr: () => `${formatInt(1)} minute`,
|
||||
interval: 60,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: BACKUP_SLOT_TYPE.ONLINE,
|
||||
intervalStr: () => `${formatInt(5)} minutes`,
|
||||
interval: 5 * 60,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: BACKUP_SLOT_TYPE.ONLINE,
|
||||
intervalStr: () => `${formatInt(20)} minutes`,
|
||||
interval: 20 * 60,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: BACKUP_SLOT_TYPE.ONLINE,
|
||||
intervalStr: () => `${formatInt(1)} hour`,
|
||||
interval: 3600,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: BACKUP_SLOT_TYPE.OFFLINE,
|
||||
intervalStr: () => `${formatInt(10)} minutes`,
|
||||
interval: 10 * 60,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
type: BACKUP_SLOT_TYPE.OFFLINE,
|
||||
intervalStr: () => `${formatInt(1)} hour`,
|
||||
interval: 3600,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
type: BACKUP_SLOT_TYPE.OFFLINE,
|
||||
intervalStr: () => `${formatInt(5)} hours`,
|
||||
interval: 5 * 3600,
|
||||
},
|
||||
{
|
||||
|
@ -115,6 +123,7 @@ export const GameStorage = {
|
|||
};
|
||||
this.currentSlot = 0;
|
||||
this.loadPlayerObject(root);
|
||||
this.processLocalBackups();
|
||||
this.save(true);
|
||||
return;
|
||||
}
|
||||
|
@ -130,6 +139,7 @@ export const GameStorage = {
|
|||
// Save current slot to make sure no changes are lost
|
||||
this.save(true);
|
||||
this.loadPlayerObject(this.saves[slot] ?? Player.defaultStart);
|
||||
this.processLocalBackups();
|
||||
Tabs.all.find(t => t.id === player.options.lastOpenTab).show(false);
|
||||
Modal.hideAll();
|
||||
Cloud.resetTempState();
|
||||
|
@ -250,6 +260,12 @@ export const GameStorage = {
|
|||
localStorage.setItem(this.backupTimeKey(this.currentSlot), GameSaveSerializer.serialize(this.backupTimeData));
|
||||
},
|
||||
|
||||
// Does not actually load, but returns an object which is meant to be passed on to loadPlayerObject()
|
||||
loadFromBackup(backupSlot) {
|
||||
const data = localStorage.getItem(this.backupDataKey(this.currentSlot, backupSlot));
|
||||
return GameSaveSerializer.deserialize(data);
|
||||
},
|
||||
|
||||
// This is called after the player object is loaded
|
||||
processLocalBackups() {
|
||||
// Set the next backup timer to whatever the next multiple of the shortest online interval is
|
||||
|
@ -258,11 +274,12 @@ export const GameStorage = {
|
|||
|
||||
// Check for the amount of time spent offline and perform immediate backups for any slots
|
||||
// which have had more than their timers elapse since the last time the game was open and saved
|
||||
const offlineTimeMs = Date.now() - this.lastUpdateOnLoad;
|
||||
const currentTime = Date.now();
|
||||
const offlineTimeMs = currentTime - this.lastUpdateOnLoad;
|
||||
for (const backupInfo of AutoBackupSlots.filter(slot => slot.type === BACKUP_SLOT_TYPE.OFFLINE)) {
|
||||
if (offlineTimeMs < 1000 * backupInfo.interval) continue;
|
||||
const id = backupInfo.id;
|
||||
this.saveToBackup(id);
|
||||
this.saveToBackup(id, currentTime);
|
||||
}
|
||||
|
||||
// Load in all the data from previous backup times
|
||||
|
@ -291,6 +308,12 @@ export const GameStorage = {
|
|||
GameIntervals.backup.restart();
|
||||
},
|
||||
|
||||
// Saves the current game state to the first reserve slot it finds
|
||||
saveToReserveSlot() {
|
||||
const targetSlot = AutoBackupSlots.find(slot => slot.type === 2).id;
|
||||
this.saveToBackup(targetSlot, Date.now());
|
||||
},
|
||||
|
||||
export() {
|
||||
copyToClipboard(this.exportModifiedSave());
|
||||
GameUI.notify.info("Exported current savefile to your clipboard");
|
||||
|
|
Loading…
Reference in New Issue
Block a user