Skip to content

Commit

Permalink
SaveGame|UI: Custom profiles have their own save folders
Browse files Browse the repository at this point in the history
Each custom profile is assigned a unique empty save folder when the profile is created.

SaveGames can be queried for the current game profile’s save path.

IssueID #2177
  • Loading branch information
skyjake committed Nov 8, 2018
1 parent 4615437 commit 5143d82
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 42 deletions.
2 changes: 1 addition & 1 deletion doomsday/apps/client/src/dd_main.cpp
Expand Up @@ -1360,7 +1360,7 @@ static dint DD_StartupWorker(void * /*context*/)
#endif
);
DoomsdayApp::bundles().waitForEverythingIdentified();*/
FS::get().waitForIdle();
FS::waitForIdle();

/*String foundPath = App_FileSystem().findPath(de::Uri("doomsday.pk3", RC_PACKAGE),
RLF_DEFAULT, App_ResourceClass(RC_PACKAGE));
Expand Down
21 changes: 6 additions & 15 deletions doomsday/apps/client/src/ui/dialogs/createprofiledialog.cpp
Expand Up @@ -20,7 +20,6 @@
#include "ui/widgets/packagesbuttonwidget.h"
#include "ui/widgets/packageswidget.h"
#include "ui/dialogs/datafilesettingsdialog.h"
//#include "ui/widgets/nativepathwidget.h"

#include <doomsday/DoomsdayApp>
#include <doomsday/Games>
Expand All @@ -37,14 +36,11 @@
using namespace de;

DENG_GUI_PIMPL(CreateProfileDialog)
//, DENG2_OBSERVES(NativePathWidget, UserChange)
{
ChoiceWidget *gameChoice;
PackagesButtonWidget *packages;
ChoiceWidget *autoStartMap;
ChoiceWidget *autoStartSkill;
//NativePathWidget *customDataFile;
//String customDataFile;
AuxButtonWidget *customDataFileName;
ui::ListData customDataFileActions;
SafeWidgetPtr<PackagesWidget> customPicker;
Expand Down Expand Up @@ -157,17 +153,6 @@ DENG_GUI_PIMPL(CreateProfileDialog)
autoStartMap->setSelected(pos != ui::Data::InvalidPos ? pos : 0);
}

// void pathChangedByUser(NativePathWidget &) override
// {
// setCustomDataFile(customDataFile->path());
// }

// void setCustomDataFile(const NativePath &path)
// {
// tempProfile->setUseGameRequirements(path.isEmpty());
// tempProfile->setCustomDataFile(path.toString());
// }

void updateDataFile()
{
if (tempProfile->customDataFile().isEmpty())
Expand Down Expand Up @@ -341,6 +326,10 @@ GameProfile *CreateProfileDialog::makeProfile() const
auto *prof = new GameProfile(profileName());
prof->setUserCreated(true);
applyTo(*prof);
if (!prof->saveLocationId())
{
prof->createSaveLocation();
}
return prof;
}

Expand All @@ -353,6 +342,7 @@ void CreateProfileDialog::fetchFrom(GameProfile const &profile)
d->updateDataFile();
d->autoStartMap->setSelected(d->autoStartMap->items().findData(profile.autoStartMap()));
d->autoStartSkill->setSelected(d->autoStartSkill->items().findData(profile.autoStartSkill()));
d->tempProfile->setSaveLocationId(profile.saveLocationId());
}

void CreateProfileDialog::applyTo(GameProfile &profile) const
Expand All @@ -367,6 +357,7 @@ void CreateProfileDialog::applyTo(GameProfile &profile) const
profile.setPackages(d->packages->packages());
profile.setAutoStartMap(d->autoStartMap->selectedItem().data().toString());
profile.setAutoStartSkill(d->autoStartSkill->selectedItem().data().toInt());
profile.setSaveLocationId(profile.saveLocationId());
}

String CreateProfileDialog::profileName() const
Expand Down
86 changes: 71 additions & 15 deletions doomsday/apps/client/src/ui/home/gamecolumnwidget.cpp
Expand Up @@ -30,6 +30,7 @@
#include <de/ChildWidgetOrganizer>
#include <de/Config>
#include <de/DirectoryListDialog>
#include <de/FileSystem>
#include <de/GridPopupWidget>
#include <de/Loop>
#include <de/MenuWidget>
Expand All @@ -39,6 +40,8 @@
#include <de/VariableChoiceWidget>
#include <de/VariableToggleWidget>

#include <QDesktopServices>

using namespace de;

const String GameColumnWidget::SORT_GAME_ID("game");
Expand Down Expand Up @@ -147,7 +150,7 @@ DENG_GUI_PIMPL(GameColumnWidget)
if (dlg->exec(root()))
{
// Adding the profile has the side effect that a widget is
// created for it.
// created for it in the menu.
auto *added = dlg->makeProfile();
DoomsdayApp::gameProfiles().add(added);
}
Expand Down Expand Up @@ -380,7 +383,7 @@ DENG_GUI_PIMPL(GameColumnWidget)
{
cmp = -1;
}
else
else if (prof2.lastPlayedAt().isValid())
{
cmp = +1;
}
Expand Down Expand Up @@ -509,9 +512,10 @@ DENG_GUI_PIMPL(GameColumnWidget)
button->clearPackages();
}))
<< new ui::ActionItem(
tr("Duplicate"), new CallbackAction([this, profileItem]() {
tr("Duplicate"), new CallbackAction([profileItem]() {
GameProfile *dup = new GameProfile(*profileItem->profile);
dup->setUserCreated(true);
dup->createSaveLocation();

// Generate a unique name.
for (int attempt = 1;; ++attempt)
Expand All @@ -530,24 +534,76 @@ DENG_GUI_PIMPL(GameColumnWidget)
}
}));

if (const auto *loc = FS::tryLocate<const Folder>(profileItem->profile->savePath()))
{
popup->items() << new ui::Item(ui::Item::Separator)
<< new ui::ActionItem(
"Show Save Folder", new CallbackAction([loc]() {
QDesktopServices::openUrl(
QUrl::fromLocalFile(loc->correspondingNativePath()));
}));
}

if (isUserProfile)
{
auto *deleteSub = new ui::SubmenuItem(style().images().image("close.ring"),
tr("Delete"), ui::Left);
deleteSub->items()
<< new ui::Item(ui::Item::Separator, tr("Are you sure?"))
<< new ui::ActionItem(tr("Delete Profile"),
new CallbackAction([this, button, profileItem, popup] ()
{
popup->detachAnchor();
// Animate the widget to fade it away.
TimeSpan const SPAN = 0.2;
button->setOpacity(0, SPAN);
Loop::get().timer(SPAN, [profileItem] ()
{
delete profileItem->profile;
});
}))
<< new ui::ActionItem(
tr("Delete Profile"),
new CallbackAction([this, button, profileItem, popup]() {
if (profileItem->profile->saveLocationId())
{
const Folder *saveFolder =
FS::tryLocate<const Folder>(profileItem->profile->savePath());

if (saveFolder && !profileItem->profile->isSaveLocationEmpty())
{
// What to do with the savegames?
auto *question = new MessageDialog;
question->setDeleteAfterDismissed(true);
question->title().setText("Delete Saved Games?");
question->title().setStyleImage("alert");
question->message().setText(
"The profile " _E(b) + profileItem->profile->name() +
_E(.) " that is being deleted has saved games. "
"Do you wish to delete the save files as well?");
const NativePath savePath =
saveFolder->correspondingNativePath();
question->buttons()
<< new DialogButtonItem(DialogWidget::Accept,
"Delete All")
<< new DialogButtonItem(DialogWidget::Reject |
DialogWidget::Default,
"Cancel")
<< new DialogButtonItem(
DialogWidget::Action,
"Show Folder",
new CallbackAction([savePath]() {
QDesktopServices::openUrl(
QUrl::fromLocalFile(savePath));
}));
if (!question->exec(root()))
{
// Cancelled.
return;
}
}
if (saveFolder)
{
profileItem->profile->destroySaveLocation();
}
}

// Animate the widget to fade it away.
const TimeSpan SPAN = 0.2;
button->setOpacity(0, SPAN);
popup->detachAnchor();
popup->close();
Loop::get().timer(SPAN,
[profileItem]() { delete profileItem->profile; });
}))
<< new ui::ActionItem(tr("Cancel"), new Action);

popup->items()
Expand Down
6 changes: 6 additions & 0 deletions doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp
Expand Up @@ -78,6 +78,12 @@ DENG_GUI_PIMPL(GamePanelButtonWidget)

if (!gameProfile.isPlayable()) return false;

// The file must be in the right save folder.
if (item.savePath().fileNamePath().compareWithoutCase(gameProfile.savePath()))
{
return false;
}

StringList const savePacks = item.loadedPackages();

// Fallback for older saves without package metadata.
Expand Down
7 changes: 7 additions & 0 deletions doomsday/apps/libdoomsday/include/doomsday/gameprofiles.h
Expand Up @@ -57,6 +57,7 @@ class LIBDOOMSDAY_PUBLIC GameProfiles : public de::Profiles
void setAutoStartMap(de::String const &map);
void setAutoStartSkill(int level);
void setLastPlayedAt(const de::Time &at = de::Time());
void setSaveLocationId(de::duint32 saveLocationId);

bool appendPackage(de::String const &id);

Expand All @@ -69,8 +70,14 @@ class LIBDOOMSDAY_PUBLIC GameProfiles : public de::Profiles
de::String autoStartMap() const;
int autoStartSkill() const;
de::Time lastPlayedAt() const;
de::duint32 saveLocationId() const;
de::String savePath() const;

void createSaveLocation();
void destroySaveLocation();
void checkSaveLocation() const;
bool isSaveLocationEmpty() const;

/**
* Returns a list of the game's packages in addition to the profile's
* configured packages.
Expand Down
15 changes: 8 additions & 7 deletions doomsday/apps/libdoomsday/src/doomsdayapp.cpp
Expand Up @@ -549,13 +549,13 @@ void DoomsdayApp::initialize()

void DoomsdayApp::initWadFolders()
{
FS::get().waitForIdle();
FS::waitForIdle();
d->initWadFolders();
}

void DoomsdayApp::initPackageFolders()
{
FS::get().waitForIdle();
FS::waitForIdle();
d->initPackageFolders();
}

Expand Down Expand Up @@ -864,9 +864,9 @@ void DoomsdayApp::setGame(Game const &game)
app().d->currentGame = const_cast<Game *>(&game);
}

void DoomsdayApp::makeGameCurrent(GameProfile const &profile)
void DoomsdayApp::makeGameCurrent(const GameProfile &profile)
{
auto const &newGame = profile.game();
const auto &newGame = profile.game();

if (!newGame.isNull())
{
Expand All @@ -889,7 +889,8 @@ void DoomsdayApp::makeGameCurrent(GameProfile const &profile)
// This is now the current game.
setGame(newGame);
d->currentProfile = &profile;
//AbstractSession::profile().gameId = newGame.id();

profile.checkSaveLocation(); // in case it's gone missing

if (!newGame.isNull())
{
Expand Down Expand Up @@ -921,9 +922,9 @@ bool DoomsdayApp::changeGame(GameProfile const &profile,
std::function<int (void *)> gameActivationFunc,
Behaviors behaviors)
{
auto const &newGame = profile.game();
const auto &newGame = profile.game();

bool const arePackagesDifferent =
const bool arePackagesDifferent =
!GameProfiles::arePackageListsCompatible(DoomsdayApp::app().loadedPackagesAffectingGameplay(),
profile.packagesAffectingGameplay());

Expand Down
Expand Up @@ -107,7 +107,7 @@ DENG2_PIMPL(IdgamesPackageInfoFile)
}
return LoopContinue;
});
FS::get().waitForIdle();
FS::waitForIdle();

StringList components;
foreach (String path, dataFiles)
Expand Down

0 comments on commit 5143d82

Please sign in to comment.