Skip to content

Commit

Permalink
libdeng2|SavedSession: Use absolute paths to SavedSessions in the "re…
Browse files Browse the repository at this point in the history
…pository" index

Although the user's savegames will always exist in /home/savegames
we should require that all game saving is done here. For example,
Hexen uses a "base" savegame internally for storing the states of
visited maps in a hub for the current game session. This built-in
savegame should not be stored alongside the user's savegames.
  • Loading branch information
danij-deng committed Mar 25, 2014
1 parent 8909f43 commit 8d40599
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 168 deletions.
4 changes: 2 additions & 2 deletions doomsday/client/src/resource/resourcesystem.cpp
Expand Up @@ -1947,8 +1947,8 @@ DENG2_PIMPL(ResourceSystem)
try
{
// Update the /home/savegames/<gameId> folder.
Folder &saveFolder = saveRepo.folder().locate<Folder>(gameId);
saveFolder.populate(Folder::PopulateOnlyThisFolder);
Folder &saveFolder = App::rootFolder().locate<Folder>(String("home/savegames") / gameId);
saveFolder.populate();
saveRepo.add(saveFolder.locate<game::SavedSession>(outputName));
return;
}
Expand Down
7 changes: 4 additions & 3 deletions doomsday/client/src/ui/widgets/savegameselectionwidget.cpp
Expand Up @@ -49,7 +49,7 @@ DENG_GUI_PIMPL(SavegameSelectionWidget)
public:
SavegameListItem(SavedSession const &session)
{
setData(session.repoPath());
setData(session.path().toLower());
_session = &session;
}

Expand Down Expand Up @@ -185,8 +185,8 @@ DENG_GUI_PIMPL(SavegameSelectionWidget)
// Remove obsolete entries.
for(ui::Data::Pos idx = 0; idx < self.items().size(); ++idx)
{
String const repoPath = self.items().at(idx).data().toString();
if(!repository.find(repoPath))
String const savePath = self.items().at(idx).data().toString();
if(!repository.find(savePath))
{
self.items().remove(idx--);
changed = true;
Expand Down Expand Up @@ -248,6 +248,7 @@ void SavegameSelectionWidget::update()
{
if(d->needUpdateFromRepository)
{
d->needUpdateFromRepository = false;
d->updateItemsFromRepository();
}
MenuWidget::update();
Expand Down
6 changes: 2 additions & 4 deletions doomsday/libdeng2/include/de/game/savedsessionrepository.h
Expand Up @@ -45,8 +45,6 @@ class DENG2_PUBLIC SavedSessionRepository
public:
SavedSessionRepository();

Folder &folder() const;

/**
* Clear the SavedSession index.
*/
Expand All @@ -63,12 +61,12 @@ class DENG2_PUBLIC SavedSessionRepository
/**
* Remove a saved session from the index (if present).
*
* @param path Relative path of the associated .save package.
* @param path Absolute path of the associated .save package.
*/
void remove(String path);

/**
* Lookup a SavedSession in the index.
* Lookup a SavedSession in the index by absolute path.
*/
SavedSession *find(String path) const;

Expand Down
2 changes: 2 additions & 0 deletions doomsday/libdeng2/src/filesys/folder.cpp
Expand Up @@ -246,6 +246,8 @@ bool Folder::has(String const &name) const
{
DENG2_GUARD(this);

if(name.isEmpty()) return false;

// Check if we were given a path rather than just a name.
String path = name.fileNamePath();
if(!path.empty())
Expand Down
13 changes: 5 additions & 8 deletions doomsday/libdeng2/src/game/savedsessionrepository.cpp
Expand Up @@ -45,11 +45,6 @@ DENG2_AUDIENCE_METHOD(SavedSessionRepository, AvailabilityUpdate)
SavedSessionRepository::SavedSessionRepository() : d(new Instance(this))
{}

Folder &SavedSessionRepository::folder() const
{
return App::homeFolder().locate<Folder>("savegames");
}

void SavedSessionRepository::clear()
{
// Disable updates for now, we'll do that when we're done.
Expand All @@ -66,14 +61,16 @@ void SavedSessionRepository::clear()

void SavedSessionRepository::add(SavedSession &session)
{
d->sessions[session.repoPath().toLower()] = &session;
d->sessions[session.path().toLower()] = &session;
d->notifyAvailabilityUpdate();
}

void SavedSessionRepository::remove(String path)
{
d->sessions.remove(path);
d->notifyAvailabilityUpdate();
if(d->sessions.remove(path.toLower()))
{
d->notifyAvailabilityUpdate();
}
}

SavedSession *SavedSessionRepository::find(String path) const
Expand Down
10 changes: 7 additions & 3 deletions doomsday/plugins/common/include/gamesessionwriter.h
Expand Up @@ -31,16 +31,20 @@
*/
class GameSessionWriter
{
public:
typedef de::game::SavedSession SavedSession;
typedef de::game::SessionMetadata SessionMetadata;

public:
/**
* @param sessionName Name of the saved session in the repository.
* @param savePath Path of the saved session being written to.
*/
GameSessionWriter(de::String sessionName);
GameSessionWriter(de::String const &savePath);

/**
* @param metadata Session metadata to be written. A copy is made.
*/
void write(de::game::SessionMetadata const &metadata);
void write(SessionMetadata const &metadata);

private:
DENG2_PRIVATE(d)
Expand Down
26 changes: 9 additions & 17 deletions doomsday/plugins/common/include/saveslots.h
Expand Up @@ -54,8 +54,7 @@ class SaveSlots
};

public:
Slot(de::String id, bool userWritable, de::String repositoryPath,
int menuWidgetId = 0);
Slot(de::String id, bool userWritable, de::String savePath, int menuWidgetId = 0);

/**
* Returns the logical status of the saved session associated with the logical save slot.
Expand Down Expand Up @@ -91,10 +90,6 @@ class SaveSlots
*/
de::game::SavedSession &savedSession() const;

inline de::game::SavedSession *savedSessionPtr() const {
return hasSavedSession()? &savedSession() : 0;
}

/**
* Change the saved session linked with the logical save slot. It is not usually
* necessary to call this.
Expand All @@ -106,30 +101,27 @@ class SaveSlots
/**
* Copies the saved session from the @a source slot.
*/
void copySavedSessionFile(Slot const &source);
void copySavedSession(Slot const &source);

/**
* Returns the unique identifier/name for the logical save slot.
*/
de::String const &id() const;

/**
* Returns the relative path and identifier of the saved session, in the repository,
* bound to the logical save slot.
* Returns the absolute path of the saved session, bound to the logical save slot.
*/
de::String const &repositoryPath() const;
de::String const &savePath() const;

/**
* Change the relative path and identifier of the saved session, in the repository,
* bound to the logical save slot.
* Change the absolute path of the saved session, bound to the logical save slot.
*
* @param newPath New relative path for the saved session to bind to.
* @param newPath New absolute path of the saved session to bind to.
*/
void bindRepositoryPath(de::String newPath);
void bindSavePath(de::String newPath);

/**
* Deletes the file package for saved session (from the repository) which is bound
* to the logical save slot.
* Deletes the saved session linked to the logical save slot (if any).
*/
void clear();

Expand All @@ -149,7 +141,7 @@ class SaveSlots
* @param menuWidgetId Unique identifier of the game menu widget to associate this slot with.
* Use @c 0 for none.
*/
void add(de::String id, bool userWritable, de::String repositoryPath, int menuWidgetId = 0);
void add(de::String id, bool userWritable, de::String savePath, int menuWidgetId = 0);

/**
* Returns the total number of logical save slots.
Expand Down
79 changes: 40 additions & 39 deletions doomsday/plugins/common/src/g_game.cpp
Expand Up @@ -59,16 +59,18 @@
#include "saveslots.h"
#include "x_hair.h"

#include <de/ArrayValue>
#include <de/game/SavedSession>
#include <de/game/SavedSessionRepository>
#include <de/NativePath>
#include <de/NumberValue>
#include <cctype>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <de/App>
#include <de/ArrayValue>
#include <de/FixedByteArray>
#include <de/game/SavedSession>
#include <de/game/SavedSessionRepository>
#include <de/NativePath>
#include <de/NumberValue>

#define BODYQUEUESIZE (32)

Expand Down Expand Up @@ -477,6 +479,12 @@ gameaction_t G_GameAction()
return gameAction;
}

/// @return Relative path to a saved session in /home/savegames
static inline de::String composeSavedSessionPathForSlot(int slot)
{
return de::String("/home/savegames") / G_IdentityKey() / SAVEGAMENAME + de::String::number(slot) + ".save";
}

static void initSaveSlots()
{
delete sslots;
Expand All @@ -491,13 +499,12 @@ static void initSaveSlots()
};
for(int i = 0; i < NUMSAVESLOTS; ++i)
{
sslots->add(de::String::number(i), true,
de::String("%1/%2%3").arg(G_IdentityKey()).arg(SAVEGAMENAME).arg(i),
sslots->add(de::String::number(i), true, composeSavedSessionPathForSlot(i),
gameMenuSaveSlotWidgetIds[i]);
}
sslots->add("auto", false, de::String(SAVEGAMENAME "%1").arg(AUTO_SLOT));
sslots->add("auto", false, composeSavedSessionPathForSlot(AUTO_SLOT));
#if __JHEXEN__
sslots->add("base", false, de::String(SAVEGAMENAME "%1").arg(BASE_SLOT));
sslots->add("base", false, composeSavedSessionPathForSlot(BASE_SLOT));
#endif
}

Expand Down Expand Up @@ -974,16 +981,13 @@ de::game::SavedSessionRepository &G_SavedSessionRepository()
{
return *static_cast<de::game::SavedSessionRepository *>(DD_SavedSessionRepository());
}
de::Folder &G_SaveFolder()
{
return G_SavedSessionRepository().folder().locate<de::Folder>(G_IdentityKey());
}

static de::game::SavedSession *savedSessionByUserDescription(de::String description)
{
if(!description.isEmpty())
{
DENG2_FOR_EACH_CONST(de::Folder::Contents, i, G_SaveFolder().contents())
de::Folder &saveFolder = DENG2_APP->rootFolder().locate<de::Folder>("home/savegames");
DENG2_FOR_EACH_CONST(de::Folder::Contents, i, saveFolder.contents())
{
if(de::game::SavedSession *session = i->second->maybeAs<de::game::SavedSession>())
{
Expand All @@ -1005,8 +1009,9 @@ de::String G_SaveSlotIdFromUserInput(de::String str)
return sslot->id();
}

// Perhaps a saved session package file name?
if(SaveSlot *sslot = G_SaveSlots().slot(G_SaveFolder().tryLocate<de::game::SavedSession>(str)))
// Perhaps a saved session file name?
de::String savePath = de::String("home/savegames") / G_IdentityKey() / str + ".save";
if(SaveSlot *sslot = G_SaveSlots().slot(DENG2_APP->rootFolder().tryLocate<de::game::SavedSession const>(savePath)))
{
return sslot->id();
}
Expand Down Expand Up @@ -1408,7 +1413,7 @@ int G_DoLoadMap(loadmap_params_t *p)

try
{
de::game::SavedSession &session = G_SaveSlots()["base"].savedSession();
de::game::SavedSession const &session = G_SaveSlots()["base"].savedSession();
SV_MapStateReader(session, mapUriStr)->read(mapUriStr);
}
catch(de::Error const &er)
Expand Down Expand Up @@ -2976,8 +2981,6 @@ static int saveGameSessionWorker(void *context)
<< parm.slotId << er.asText();
}

delete metadata;

BusyMode_WorkerEnd();
return didSave;
}
Expand Down Expand Up @@ -3229,23 +3232,22 @@ void G_DoLoadSession(de::String slotId)
de::String const logicalSlot = slotId;
#endif

#if __JHEXEN__
// Copy all needed save files to the base slot.
if(slotId.compareWithoutCase("base"))
// Attempt to load the saved game state.
try
{
G_SaveSlots()["base"].copySavedSessionFile(G_SaveSlots()[slotId]);
}
#if __JHEXEN__
if(slotId.compareWithoutCase("base"))
{
G_SaveSlots()["base"].copySavedSession(G_SaveSlots()[slotId]);
}
#endif

// Attempt to recognize and load the saved game state.
try
{
de::game::SavedSession const &session = G_SaveSlots()[logicalSlot].savedSession();
LOG_VERBOSE("Attempting to load saved game from \"%s\"") << session.path();

#if __JHEXEN__
// Deserialize the world ACS data.
if(de::File *file = session.tryLocateStateFile("ACScript"))
if(de::File const *file = session.tryLocateStateFile("ACScript"))
{
Game_ACScriptInterpreter().readWorldScriptData(de::Reader(*file));
}
Expand Down Expand Up @@ -3305,8 +3307,7 @@ void G_DoLoadSession(de::String slotId)
}
catch(de::Error const &er)
{
LOG_RES_WARNING("Error loading save slot #%s:\n")
<< slotId << er.asText();
LOG_RES_WARNING("Error loading save slot #%s:\n") << slotId << er.asText();
}

// Failure... Return to the title loop.
Expand Down Expand Up @@ -4407,18 +4408,18 @@ D_CMD(InspectSavedSession)
de::String slotId = G_SaveSlotIdFromUserInput(argv[1]);
try
{
SaveSlot &sslot = G_SaveSlots()[slotId];
if(sslot.hasSavedSession())
{
App_Log(DE2_LOG_MESSAGE, "%s", sslot.savedSession().description().toLatin1().constData());
return true;
}

App_Log(DE2_LOG_ERROR, "Save slot '%s' is not in use", slotId.toLatin1().constData());
de::game::SavedSession const &session = G_SaveSlots()[slotId].savedSession();
LOG_SCR_MSG("%s") << session.metadata().asStyledText();
LOG_SCR_MSG(_E(D) "Resource: " _E(.)_E(i) "\"%s\"") << session.path();
return true;
}
catch(SaveSlot::MissingSessionError const &)
{
LOG_WARNING("Save slot '%s' is not in use") << slotId;
}
catch(SaveSlots::MissingSlotError const &)
{
App_Log(DE2_SCR_WARNING, "Failed to determine save slot from \"%s\"", argv[1]);
LOG_WARNING("Failed to determine save slot from \"%s\"") << argv[1];
}

// No action means the command failed.
Expand Down

0 comments on commit 8d40599

Please sign in to comment.