Report article RSS Feed 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 s87 on May 29th, 2010
Article

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 May 29 2010, 11:51am says:

All greek to me but yay!

+6 votes     reply to comment
Lenox47
Lenox47 May 29 2010, 1:29pm says:

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

+2 votes     reply to comment
hendrikp
hendrikp May 29 2010, 5:39pm replied:

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

+1 vote     reply to comment
vfn4i83
vfn4i83 May 29 2010, 4:58pm says:

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

+2 votes     reply to comment
methy
methy May 29 2010, 10:31pm says:

just so impressive...

+2 votes     reply to comment
formerlyknownasMrCP
formerlyknownasMrCP May 30 2010, 1:31am says:

This is really cool

+1 vote     reply to comment
Haephestos
Haephestos May 30 2010, 5:40am says:

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

+1 vote     reply to comment
xXMaNiAcXx
xXMaNiAcXx May 30 2010, 11:46am says:

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!

+1 vote     reply to comment
Alex626
Alex626 Jun 6 2010, 7:41am says:

Not bad :)

+1 vote     reply to comment
Alex626
Alex626 Sep 13 2010, 12:50pm says:

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 ?

+1 vote     reply to comment
JBJHJM
JBJHJM Apr 13 2012, 1:19pm says:

Great work dude! Bookmarked for later use! :D

+1 vote     reply to comment
Post a Comment
click to sign in

You are not logged in, your comment will be anonymous unless you join the community today (totally free - or sign in with your social account on the right) which we encourage all contributors to do.

2000 characters limit; HTML formatting and smileys are not supported - text only

Feature
Browse
Features
Report Abuse
Report article
Related Mods
open Outcast (Crysis Wars)
open Outcast Crysis Wars - Single Player Adventure
Related Games
Crysis Wars
Crysis Wars Multiplayer First Person Shooter
Related Groups
CryENGINE 2 Developers
CryENGINE 2 Developers Fans & Clans group with 216 members
Eternal Outcasts
Eternal Outcasts Developer & Publisher with 20 members
Single Player Modders
Single Player Modders Fans & Clans group with 93 members