• Register
Post feature Report RSS Adding a Persistence System to Crysis Wars

The following article describes how we managed to store all kinds of data, such as entity positions or the player's inventory, when travelling between different levels in Crysis Wars. This way changes to a level can be restored when visiting it again.

Posted by on

Enabling free traveling between levels, in contrast to completing them one-by-one, poses the challenge of storing changes in the level you are leaving and restoring them when returning to it.


Although Crysis Wars doesn't have this feature built-in, the SDK allows the possibility to implement a persistence system doing exactly this.

1.) Technical Implementation

(Ab)Using the Savegame system of Crysis Wars

When thinking of storing data in a game the first thing that may come to mind is the Savegame system. This naive approach did the trick for us:

  • When leaving a level, a savegame storing its local data is created
  • After returning to it, the saved data is loaded again

Sounds too simple to be true? Correct.

Problems arise as soon as you try to save regularly: After loading, the local data of the levels would stay just the same. We solved this issue by storing the current local data and local data of saved games separately. For it a folder named the same as the regular savegame file is created and the local files are copied into it when saving, while they are moved out of it to the folder containing the current data when loading.

Serialize it!

When taking a closer look at the C++ code of the Crysis (Wars) SDK you may notice that many classes have functions named Serialize(...) or FullSerialize(...). It is these that take care of what a class should store when saving, and read from a savegame file when loading.

They all require a TSerialize object as a parameter that has to be created from an instance of a class implementing the ILoadGame or ISaveGame interface, depending on what you want to do.

In order to write such Serialize functions for custom classes it pays off to check out the ISerialize interface. I won't elaborate further on that since this article is NOT a tutorial.

You may find some ideas in the Code Snippets section though. :)

Triggering everything with the LevelSystem

Now you’ve got an impression of how data can be saved and loaded; but these processes need to be triggered at the right time:

  • Local data should be saved just before leaving a level ...
  • ... and loaded again just after returning to it, if possible before the player even can move

Our solution was to add an alternative travelling console command, that saves the local data and invokes the "map" command with the target level as parameter (this method works in non-devmode as well, since the console commands are called by code rather than by console).

For restoring the local data at the right time, we simply used the ILevelSystemListener interface and implemented the OnLoadingComplete function accordingly. The loading could also be done with a custom FlowNode - you'd have to include that node in each map you want to remember changes after travelling though.

2.) Code Snippets

Most of the following things weren't documented anywhere (or at least we didn't find any tutorials or similar) so you may find them useful.

Handling Savegame files

With the following code you can create your TSerialize object needed by Serialize functions when saving:

cpp code:
// Get a pointer to the currently used profile
IPlayerProfileManager* pProfileManager =
m_pGame->GetIGameFramework()->GetIPlayerProfileManager();
IPlayerProfile* pProfile =  
pProfileManager->GetCurrentProfile(m_pProfileManager->GetCurrentUser());

// Create the savegame file "myfile.temp"
ISaveGame* pSG = pProfile->CreateSaveGame();
pSG->Init(PathUtil::Make("", "myfile", ".temp"));

// You need to create a section to store data in
TSerialize pSer = pSG->AddSection("my_data");

// Use pSer to store data or create more sections...

pSG->Complete(true);

... and for loading it can be done as shown below.

cpp code:
// Get a pointer to the currently used profile
IPlayerProfileManager* pProfileManager =
m_pGame->GetIGameFramework()->GetIPlayerProfileManager();
IPlayerProfile* pProfile =  
pProfileManager->GetCurrentProfile(m_pProfileManager->GetCurrentUser());

// Create the loadgame by opening "my_file.temp"
ILoadGame* pLG = pProfile->CreateLoadGame();
pLG->Init(PathUtil::Make("", "my_file", ".temp"))

// The following lines are IMPORTANT!
// Doing this in the wrong way leads to memory errors
std::auto_ptr_ref<TSerialize> pSer(pLG->GetSection("my_data"));

// Now you can load data from the savefile
DUMMY_OBJECT->Serialize(*pSer._Ref);

//...

pLG->Complete();

Storing and loading stuff

Now we have some kind of TSerialize object but how can we handle it? Let's save the player's inventory for example:

cpp code:
// Initialize pSG as described above

// pPlayer should be an IEntity* pointing to the player ;)
if (pPlayer != NULL)
{
     // Create a new section in the savefile
     TSerialize ser = pSG->AddSection("player_inventory");
     pPlayer->GetInventory()->SerializeInventoryForLevelChange(ser);
}

// Don't forget to call pSG->Complete(true) afterwards!

And load it again:

cpp code:
// Initialize pLG as described above and check, whether the needed section exists
if (pLG->HaveSection("player_inventory"))
{
     // get the TSerialize object to load from
     std::auto_ptr_ref<TSerialize> pSer(pLG->GetSection("player_inventory"));
     // pPlayer should point to the Player entity again ;)         
     pPlayer->GetInventory()->SerializeInventoryForLevelChange(*pSer._Ref);
}

// Don't forget pLG->Complete() when you're done with loading

Registering a travel command

Creating a custom console command is quite easy. First of all you need to declare the C++ function that will be called by the console command in Game.h:

cpp code:
// add the following function as "protected" to the CGame class:
static void CmdNEWTravel(IConsoleCmdArgs *pArgs);
 

Then you will need to implement the function and register the new console command in GameCVars.cpp:

cpp code:
// In CGame::RegisterConsoleCommands(), add:
m_pConsole->AddCommand("new_travel", CmdNEWTravel, 0, "Travel to another level (same as the map command just with persistence support)");

// Don't forget to unregister the command!
// In CGame::UnregisterConsoleCommands(), add:
m_pConsole->RemoveCommand("new_travel");

// And finally add the command implementation somewhere in that source file
void CGame::CmdNEWTravel(IConsoleCmdArgs *pArgs)
{
    string target = pArgs->GetArg(1);

    // Place your saving routines HERE

    // Trigger the actual travelling using the "map" console command
    gEnv->pConsole->ExecuteString(string("map ") + target + " nonblocking");
}

Utilizing the LevelSystemListener interface

If you want to listen to events of the level system, your class (could be a FlowNode for example) needs to implement the ILevelSystemListener interface:

cpp code:
// Your Persistence class will need to implement the ILevelSystemListener interface, of course
class CPersistenceSystem : public ILevelSystemListener
{
    //...
};

// In the actual implementation the loading code goes into the OnLoadingComplete function

void CPersistenceSystem::OnLoadingComplete (ILevel *pLevel)
{
    // Load your data here
}

// Also don't forget to implement the remaining functions of the interface as dummies at least


oOC HomepageoOC @ TwitteroOC @ FacebookoOC @ Gametrailers.comoOC @ CrymodoOC @ YouTube
Post comment Comments
hogan_skoll
hogan_skoll - - 359 comments

All greek to me but yay!

Reply Good karma Bad karma+6 votes
Lenox47
Lenox47 - - 676 comments

Damn, that's exactly the system we need for our project. We were not 100% sure that was possible. Your mod is really exceptional.

Reply Good karma Bad karma+2 votes
hendrikp
hendrikp - - 6 comments

well your mod looks/sounds pretty promising too i have to say ;)

Reply Good karma Bad karma+1 vote
vfn4i83
vfn4i83 - - 692 comments

Great tech news, it might help a few other Crysis mod devs

Reply Good karma Bad karma+2 votes
methy
methy - - 1,221 comments

just so impressive...

Reply Good karma Bad karma+2 votes
formerlyknownasMrCP
formerlyknownasMrCP - - 892 comments

This is really cool

Reply Good karma Bad karma+1 vote
Haephestos
Haephestos - - 2 comments

That is a brilliant system. Could be very useful. :D

Reply Good karma Bad karma+1 vote
xXMaNiAcXx
xXMaNiAcXx - - 4,807 comments

Awesome! Nice engine that you chose for creating and developing such a nice game as this will be, and now, saving system? Just awesome, comepletely good work!

Reply Good karma Bad karma+1 vote
Alex626
Alex626 - - 639 comments

Its nice, but how will I save the dynamical entities position\rotation ? I assume I will need to store each entity, but first find them using iterator ?

Reply Good karma Bad karma+1 vote
JBJHJM
JBJHJM - - 41 comments

Great work dude! Bookmarked for later use! :D

Reply Good karma Bad karma+1 vote
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: