# Allow saving and loading single-player games.
Support writing zip files based on in-memory data. Fix menu sliding animations to be framerate independent. This was SVN commit r10454.
This commit is contained in:
parent
21c6141cdc
commit
7064565ff6
@ -97,7 +97,7 @@
|
||||
</object>
|
||||
</object>
|
||||
|
||||
<object type="button" style="StoneButton" size="50%+16 100%-60 50%+144 100%-32">
|
||||
<object type="button" style="StoneButton" size="50%+16 100%-60 50%+144 100%-32">
|
||||
Cancel
|
||||
<action on="Press"><![CDATA[cancelSetup();]]></action>
|
||||
</object>
|
||||
|
11
binaries/data/mods/public/gui/page_loadgame.xml
Normal file
11
binaries/data/mods/public/gui/page_loadgame.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<page>
|
||||
<include>common/setup.xml</include>
|
||||
<include>common/styles.xml</include>
|
||||
<include>common/sprite1.xml</include>
|
||||
|
||||
<include>common/common_sprites.xml</include>
|
||||
<include>common/common_styles.xml</include>
|
||||
|
||||
<include>savedgames/load.xml</include>
|
||||
</page>
|
@ -83,13 +83,18 @@ function formatUserReportStatus(status)
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
var lastTickTime = new Date;
|
||||
function onTick()
|
||||
{
|
||||
var now = new Date;
|
||||
var tickLength = new Date - lastTickTime;
|
||||
lastTickTime = now;
|
||||
|
||||
// Animate backgrounds
|
||||
scrollBackgrounds();
|
||||
|
||||
// Animate submenu
|
||||
updateMenuPosition();
|
||||
updateMenuPosition(tickLength);
|
||||
|
||||
// Update music state
|
||||
global.music.updateTimer();
|
||||
@ -124,24 +129,23 @@ function onTick()
|
||||
//}
|
||||
|
||||
// Slide menu
|
||||
function updateMenuPosition()
|
||||
function updateMenuPosition(dt)
|
||||
{
|
||||
var submenu = getGUIObjectByName("submenu");
|
||||
|
||||
if (submenu.hidden == false)
|
||||
{
|
||||
// The offset is the increment or number of units/pixels to move
|
||||
// the menu. An offset of one is always accurate, but it is too
|
||||
// slow. The offset must divide into the travel distance evenly
|
||||
// in order for the menu to end up at the right spot. The travel
|
||||
// distance is the max-initial. The travel distance in this
|
||||
// example is 300-60 = 240. We choose an offset of 5 because it
|
||||
// divides into 240 evenly and provided the speed we wanted.
|
||||
const OFFSET = 10;
|
||||
// Number of pixels per millisecond to move
|
||||
const SPEED = 1.2;
|
||||
|
||||
if (submenu.size.left < getGUIObjectByName("mainMenu").size.right)
|
||||
var maxOffset = getGUIObjectByName("mainMenu").size.right - submenu.size.left;
|
||||
if (maxOffset > 0)
|
||||
{
|
||||
submenu.size = (submenu.size.left + OFFSET) + " " + submenu.size.top + " " + (submenu.size.right + OFFSET) + " " + submenu.size.bottom;
|
||||
var offset = Math.min(SPEED * dt, maxOffset);
|
||||
var size = submenu.size;
|
||||
size.left += offset;
|
||||
size.right += offset;
|
||||
submenu.size = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +189,21 @@ Status: $status.
|
||||
]]>
|
||||
</action>
|
||||
</object>
|
||||
|
||||
<object name="subMenuLoadButton"
|
||||
type="button"
|
||||
style="StoneButtonFancy"
|
||||
size="0 64 100% 92"
|
||||
tooltip_style="pgToolTip"
|
||||
tooltip="Click here to load a saved game."
|
||||
>
|
||||
Load Game
|
||||
<action on="Press">
|
||||
closeMenu();
|
||||
Engine.PushGuiPage("page_loadgame.xml", { type: "offline" });
|
||||
</action>
|
||||
</object>
|
||||
|
||||
</object>
|
||||
|
||||
<!-- submenuMultiplayer -->
|
||||
@ -344,7 +359,7 @@ Status: $status.
|
||||
Single Player
|
||||
<action on="Press">
|
||||
closeMenu();
|
||||
openMenu("submenuSinglePlayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 2);
|
||||
openMenu("submenuSinglePlayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);
|
||||
</action>
|
||||
</object>
|
||||
|
||||
|
47
binaries/data/mods/public/gui/savedgames/load.js
Normal file
47
binaries/data/mods/public/gui/savedgames/load.js
Normal file
@ -0,0 +1,47 @@
|
||||
function sortDecreasingDate(a, b)
|
||||
{
|
||||
return b.metadata.time - a.metadata.time;
|
||||
}
|
||||
|
||||
function twoDigits(n)
|
||||
{
|
||||
return n < 10 ? "0" + n : n;
|
||||
}
|
||||
|
||||
function generateLabel(metadata)
|
||||
{
|
||||
var t = new Date(metadata.time*1000);
|
||||
// TODO: timezones
|
||||
var date = t.getUTCFullYear()+"-"+twoDigits(1+t.getUTCMonth())+"-"+twoDigits(t.getUTCDate());
|
||||
var time = twoDigits(t.getUTCHours())+":"+twoDigits(t.getUTCMinutes())+":"+twoDigits(t.getUTCSeconds());
|
||||
return "["+date+" "+time+"] "+metadata.initAttributes.map;
|
||||
}
|
||||
|
||||
function init()
|
||||
{
|
||||
var savedGames = Engine.GetSavedGames();
|
||||
|
||||
savedGames.sort(sortDecreasingDate);
|
||||
|
||||
var gameListIDs = [ game.id for each (game in savedGames) ];
|
||||
var gameListLabels = [ generateLabel(game.metadata) for each (game in savedGames) ];
|
||||
|
||||
var gameSelection = getGUIObjectByName("gameSelection");
|
||||
gameSelection.list = gameListLabels;
|
||||
gameSelection.list_data = gameListIDs;
|
||||
gameSelection.selected = 0;
|
||||
}
|
||||
|
||||
function loadGame()
|
||||
{
|
||||
var gameSelection = getGUIObjectByName("gameSelection");
|
||||
var gameID = gameSelection.list_data[gameSelection.selected];
|
||||
|
||||
var metadata = Engine.StartSavedGame(gameID);
|
||||
|
||||
Engine.SwitchGuiPage("page_loading.xml", {
|
||||
"attribs": metadata.initAttributes,
|
||||
"isNetworked" : false,
|
||||
"playerAssignments": metadata.gui.playerAssignments
|
||||
});
|
||||
}
|
34
binaries/data/mods/public/gui/savedgames/load.xml
Normal file
34
binaries/data/mods/public/gui/savedgames/load.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<objects>
|
||||
|
||||
<script file="gui/savedgames/load.js"/>
|
||||
|
||||
<!-- Add a translucent black background to fade out the menu page -->
|
||||
<object type="image" z="0" sprite="BackgroundTranslucent"/>
|
||||
|
||||
<object type="image" style="StoneDialog" size="50%-190 50%-200 50%+190 50%+200">
|
||||
|
||||
<object type="text" style="TitleText" size="50%-128 0%-16 50%+128 16">
|
||||
Load Game
|
||||
</object>
|
||||
|
||||
<object name="gameSelection"
|
||||
style="StoneList"
|
||||
type="list"
|
||||
size="24 24 100%-24 100%-100">
|
||||
</object>
|
||||
|
||||
<object type="button" size="50%-144 100%-60 50%-16 100%-32" style="StoneButton">
|
||||
Load Game
|
||||
<action on="Press">loadGame();</action>
|
||||
</object>
|
||||
|
||||
<object type="button" style="StoneButton" size="50%+16 100%-60 50%+144 100%-32">
|
||||
Cancel
|
||||
<action on="Press">Engine.PopGuiPage();</action>
|
||||
</object>
|
||||
|
||||
</object>
|
||||
|
||||
</objects>
|
@ -9,7 +9,7 @@ const RESUME = "Resume";
|
||||
const MARGIN = 4;
|
||||
|
||||
// Includes the main menu button
|
||||
const NUM_BUTTONS = 5;
|
||||
const NUM_BUTTONS = 6;
|
||||
|
||||
// Regular menu buttons
|
||||
const BUTTON_HEIGHT = 32;
|
||||
@ -26,14 +26,8 @@ const MENU_TOP = MENU_BOTTOM - END_MENU_POSITION;
|
||||
// Menu starting position: overall
|
||||
const INITIAL_MENU_POSITION = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM;
|
||||
|
||||
// The offset is the increment or number of units/pixels to move
|
||||
// the menu. An offset of one is always accurate, but it is too
|
||||
// slow. The offset must divide into the travel distance evenly
|
||||
// in order for the menu to end up at the right spot. The travel
|
||||
// distance is the max-initial. The travel distance in this
|
||||
// example is 164-0 = 164. We choose an offset of 10.25 because it
|
||||
// divides into 164 evenly and provided the speed we wanted.
|
||||
const OFFSET = 20.5;
|
||||
// Number of pixels per millisecond to move
|
||||
const MENU_SPEED = 1.2;
|
||||
|
||||
var isMenuOpen = false;
|
||||
var menu;
|
||||
@ -51,20 +45,30 @@ function initMenuPosition()
|
||||
// =============================================================================
|
||||
//
|
||||
// Slide menu
|
||||
function updateMenuPosition()
|
||||
function updateMenuPosition(dt)
|
||||
{
|
||||
if (isMenuOpen)
|
||||
{
|
||||
if (menu.size.bottom < END_MENU_POSITION)
|
||||
var maxOffset = END_MENU_POSITION - menu.size.bottom;
|
||||
if (maxOffset > 0)
|
||||
{
|
||||
menu.size = "100%-164 " + (menu.size.top + OFFSET) + " 100% " + (menu.size.bottom + OFFSET);
|
||||
var offset = Math.min(MENU_SPEED * dt, maxOffset);
|
||||
var size = menu.size;
|
||||
size.top += offset;
|
||||
size.bottom += offset;
|
||||
menu.size = size;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (menu.size.top > MENU_TOP)
|
||||
var maxOffset = menu.size.top - MENU_TOP;
|
||||
if (maxOffset > 0)
|
||||
{
|
||||
menu.size = "100%-164 " + (menu.size.top - OFFSET) + " 100% " + (menu.size.bottom - OFFSET);
|
||||
var offset = Math.min(MENU_SPEED * dt, maxOffset);
|
||||
var size = menu.size;
|
||||
size.top -= offset;
|
||||
size.bottom -= offset;
|
||||
menu.size = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -159,9 +159,22 @@ function getHotloadData()
|
||||
return { selection: g_Selection.selected };
|
||||
}
|
||||
|
||||
// Return some data that will be stored in saved game files
|
||||
function getSavedGameData()
|
||||
{
|
||||
var data = {};
|
||||
data.playerAssignments = g_PlayerAssignments;
|
||||
// TODO: control groups, etc
|
||||
return data;
|
||||
}
|
||||
|
||||
var lastTickTime = new Date;
|
||||
function onTick()
|
||||
{
|
||||
var now = new Date;
|
||||
var tickLength = new Date - lastTickTime;
|
||||
lastTickTime = now;
|
||||
|
||||
checkPlayerState();
|
||||
|
||||
while (true)
|
||||
@ -187,7 +200,7 @@ function onTick()
|
||||
updateTimers();
|
||||
|
||||
// Animate menu
|
||||
updateMenuPosition();
|
||||
updateMenuPosition(tickLength);
|
||||
|
||||
// Update music state
|
||||
global.music.updateTimer();
|
||||
|
@ -371,7 +371,6 @@
|
||||
<object name="menu"
|
||||
style="StonePanelThinBorder"
|
||||
type="image"
|
||||
size="100%-164 -160 100% 36"
|
||||
hidden="false"
|
||||
>
|
||||
<object size="4 36 100%-4 50%+20">
|
||||
@ -419,6 +418,21 @@
|
||||
<object name="pauseButtonText" type="text" style="CenteredButtonText" ghost="true">Pause</object>
|
||||
<action on="Press">togglePause();</action>
|
||||
</object>
|
||||
|
||||
<!-- Save game button -->
|
||||
<object type="button"
|
||||
name="saveGameButton"
|
||||
style="StoneButtonFancy"
|
||||
size="0 128 100% 154"
|
||||
tooltip_style="sessionToolTip"
|
||||
>
|
||||
Save Game
|
||||
<action on="Press">
|
||||
Engine.SaveGame();
|
||||
closeMenu();
|
||||
</action>
|
||||
</object>
|
||||
|
||||
</object>
|
||||
</object>
|
||||
|
||||
|
@ -178,6 +178,12 @@ Status CGUIManager::ReloadChangedFiles(const VfsPath& path)
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
CScriptVal CGUIManager::GetSavedGameData()
|
||||
{
|
||||
CScriptVal data;
|
||||
m_ScriptInterface.CallFunction(OBJECT_TO_JSVAL(top()->GetScriptObject()), "getSavedGameData", data);
|
||||
return data;
|
||||
}
|
||||
|
||||
InReaction CGUIManager::HandleEvent(const SDL_Event_* ev)
|
||||
{
|
||||
|
@ -134,6 +134,11 @@ public:
|
||||
*/
|
||||
void UpdateResolution();
|
||||
|
||||
/**
|
||||
* Calls the current page's script function getSavedGameData() and returns the result.
|
||||
*/
|
||||
CScriptVal GetSavedGameData();
|
||||
|
||||
private:
|
||||
struct SGUIPage
|
||||
{
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include "ps/Overlay.h"
|
||||
#include "ps/ProfileViewer.h"
|
||||
#include "ps/Pyrogenesis.h"
|
||||
#include "ps/SavedGame.h"
|
||||
#include "ps/UserReport.h"
|
||||
#include "ps/GameSetup/Atlas.h"
|
||||
#include "ps/GameSetup/Config.h"
|
||||
@ -189,7 +190,52 @@ void StartGame(void* cbdata, CScriptVal attribs, int playerID)
|
||||
sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), attribs.get()));
|
||||
|
||||
g_Game->SetPlayerID(playerID);
|
||||
g_Game->StartGame(gameAttribs);
|
||||
g_Game->StartGame(gameAttribs, "");
|
||||
}
|
||||
|
||||
CScriptVal StartSavedGame(void* cbdata, std::wstring name)
|
||||
{
|
||||
CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
|
||||
|
||||
ENSURE(!g_NetServer);
|
||||
ENSURE(!g_NetClient);
|
||||
|
||||
ENSURE(!g_Game);
|
||||
|
||||
// Load the saved game data from disk
|
||||
CScriptValRooted metadata;
|
||||
std::string savedState;
|
||||
Status err = SavedGames::Load(name, guiManager->GetScriptInterface(), metadata, savedState);
|
||||
WARN_IF_ERR(err);
|
||||
if (err < 0)
|
||||
return CScriptVal();
|
||||
|
||||
g_Game = new CGame();
|
||||
|
||||
// Convert from GUI script context to sim script context
|
||||
CSimulation2* sim = g_Game->GetSimulation2();
|
||||
CScriptValRooted gameMetadata (sim->GetScriptInterface().GetContext(),
|
||||
sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), metadata.get()));
|
||||
|
||||
CScriptValRooted gameInitAttributes;
|
||||
sim->GetScriptInterface().GetProperty(gameMetadata.get(), "initAttributes", gameInitAttributes);
|
||||
|
||||
int playerID;
|
||||
sim->GetScriptInterface().GetProperty(gameMetadata.get(), "player", playerID);
|
||||
|
||||
// Start the game
|
||||
g_Game->SetPlayerID(playerID);
|
||||
g_Game->StartGame(gameInitAttributes, savedState);
|
||||
|
||||
return metadata.get();
|
||||
}
|
||||
|
||||
void SaveGame(void* cbdata)
|
||||
{
|
||||
CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
|
||||
|
||||
if (SavedGames::Save(L"quicksave", *g_Game->GetSimulation2(), guiManager, g_Game->GetPlayerID()) < 0)
|
||||
LOGERROR(L"Failed to save game");
|
||||
}
|
||||
|
||||
void SetNetworkGameAttributes(void* cbdata, CScriptVal attribs)
|
||||
@ -291,6 +337,13 @@ std::vector<CScriptValRooted> GetAIs(void* cbdata)
|
||||
return ICmpAIManager::GetAIs(guiManager->GetScriptInterface());
|
||||
}
|
||||
|
||||
std::vector<CScriptValRooted> GetSavedGames(void* cbdata)
|
||||
{
|
||||
CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
|
||||
|
||||
return SavedGames::GetSavedGames(guiManager->GetScriptInterface());
|
||||
}
|
||||
|
||||
void OpenURL(void* UNUSED(cbdata), std::string url)
|
||||
{
|
||||
sys_open_url(url);
|
||||
@ -498,6 +551,13 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
|
||||
scriptInterface.RegisterFunction<void, std::wstring, &SendNetworkChat>("SendNetworkChat");
|
||||
scriptInterface.RegisterFunction<std::vector<CScriptValRooted>, &GetAIs>("GetAIs");
|
||||
|
||||
// Saved games
|
||||
scriptInterface.RegisterFunction<CScriptVal, std::wstring, &StartSavedGame>("StartSavedGame");
|
||||
scriptInterface.RegisterFunction<std::vector<CScriptValRooted>, &GetSavedGames>("GetSavedGames");
|
||||
scriptInterface.RegisterFunction<void, &SaveGame>("SaveGame");
|
||||
scriptInterface.RegisterFunction<void, &QuickSave>("QuickSave");
|
||||
scriptInterface.RegisterFunction<void, &QuickLoad>("QuickLoad");
|
||||
|
||||
// Misc functions
|
||||
scriptInterface.RegisterFunction<std::wstring, std::wstring, &SetCursor>("SetCursor");
|
||||
scriptInterface.RegisterFunction<int, &GetPlayerID>("GetPlayerID");
|
||||
@ -530,6 +590,4 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
|
||||
scriptInterface.RegisterFunction<void, &DumpSimState>("DumpSimState");
|
||||
scriptInterface.RegisterFunction<void, unsigned int, &EnableTimeWarpRecording>("EnableTimeWarpRecording");
|
||||
scriptInterface.RegisterFunction<void, &RewindTimeWarp>("RewindTimeWarp");
|
||||
scriptInterface.RegisterFunction<void, &QuickSave>("QuickSave");
|
||||
scriptInterface.RegisterFunction<void, &QuickLoad>("QuickLoad");
|
||||
}
|
||||
|
@ -92,6 +92,16 @@ struct IArchiveWriter
|
||||
* @param pathnameInArchive the name to store in the archive
|
||||
**/
|
||||
virtual Status AddFile(const OsPath& pathname, const Path& pathameInArchive) = 0;
|
||||
|
||||
/**
|
||||
* add a file to the archive, when it is already in memory and not on disk.
|
||||
*
|
||||
* @param data the uncompressed file contents to add
|
||||
* @param size the length of data
|
||||
* @param mtime the last-modified-time to be stored in the archive
|
||||
* @param pathnameInArchive the name to store in the archive
|
||||
**/
|
||||
virtual Status AddMemory(const u8* data, size_t size, time_t mtime, const OsPath& pathnameInArchive) = 0;
|
||||
};
|
||||
|
||||
typedef shared_ptr<IArchiveWriter> PIArchiveWriter;
|
||||
|
@ -607,6 +607,24 @@ public:
|
||||
{
|
||||
FileInfo fileInfo;
|
||||
RETURN_STATUS_IF_ERR(GetFileInfo(pathname, &fileInfo));
|
||||
|
||||
PFile file(new File);
|
||||
RETURN_STATUS_IF_ERR(file->Open(pathname, O_RDONLY));
|
||||
|
||||
return AddFileOrMemory(fileInfo, pathnameInArchive, file, NULL);
|
||||
}
|
||||
|
||||
Status AddMemory(const u8* data, size_t size, time_t mtime, const OsPath& pathnameInArchive)
|
||||
{
|
||||
FileInfo fileInfo(pathnameInArchive, size, mtime);
|
||||
|
||||
return AddFileOrMemory(fileInfo, pathnameInArchive, PFile(), data);
|
||||
}
|
||||
|
||||
Status AddFileOrMemory(const FileInfo& fileInfo, const OsPath& pathnameInArchive, const PFile& file, const u8* data)
|
||||
{
|
||||
ENSURE((file && !data) || (data && !file));
|
||||
|
||||
const off_t usize = fileInfo.Size();
|
||||
// skip 0-length files.
|
||||
// rationale: zip.cpp needs to determine whether a CDFH entry is
|
||||
@ -619,9 +637,6 @@ public:
|
||||
if(!usize)
|
||||
return INFO::SKIPPED;
|
||||
|
||||
PFile file(new File);
|
||||
RETURN_STATUS_IF_ERR(file->Open(pathname, O_RDONLY));
|
||||
|
||||
const size_t pathnameLength = pathnameInArchive.string().length();
|
||||
|
||||
// choose method and the corresponding codec
|
||||
@ -648,9 +663,16 @@ public:
|
||||
u8* cdata = (u8*)buf.get() + sizeof(LFH) + pathnameLength;
|
||||
Stream stream(codec);
|
||||
stream.SetOutputBuffer(cdata, csizeMax);
|
||||
io::Operation op(*file.get(), 0, usize);
|
||||
StreamFeeder streamFeeder(stream);
|
||||
RETURN_STATUS_IF_ERR(io::Run(op, io::Parameters(), streamFeeder));
|
||||
if(file)
|
||||
{
|
||||
io::Operation op(*file.get(), 0, usize);
|
||||
RETURN_STATUS_IF_ERR(io::Run(op, io::Parameters(), streamFeeder));
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_STATUS_IF_ERR(streamFeeder(data, usize));
|
||||
}
|
||||
RETURN_STATUS_IF_ERR(stream.Finish());
|
||||
csize = stream.OutSize();
|
||||
checksum = stream.Checksum();
|
||||
|
@ -38,7 +38,7 @@ WriteBuffer::WriteBuffer()
|
||||
}
|
||||
|
||||
|
||||
void WriteBuffer::Append(const void* data, size_t size)
|
||||
void WriteBuffer::EnsureSufficientCapacity(size_t size)
|
||||
{
|
||||
if(m_size + size > m_capacity)
|
||||
{
|
||||
@ -48,12 +48,25 @@ void WriteBuffer::Append(const void* data, size_t size)
|
||||
memcpy(newData.get(), m_data.get(), m_size);
|
||||
m_data = newData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WriteBuffer::Append(const void* data, size_t size)
|
||||
{
|
||||
EnsureSufficientCapacity(size);
|
||||
memcpy(m_data.get() + m_size, data, size);
|
||||
m_size += size;
|
||||
}
|
||||
|
||||
|
||||
void WriteBuffer::Reserve(size_t size)
|
||||
{
|
||||
EnsureSufficientCapacity(size);
|
||||
memset(m_data.get() + m_size, 0, size);
|
||||
m_size += size;
|
||||
}
|
||||
|
||||
|
||||
void WriteBuffer::Overwrite(const void* data, size_t size, size_t offset)
|
||||
{
|
||||
ENSURE(offset+size < m_size);
|
||||
|
@ -31,6 +31,7 @@ public:
|
||||
WriteBuffer();
|
||||
|
||||
void Append(const void* data, size_t size);
|
||||
void Reserve(size_t size);
|
||||
void Overwrite(const void* data, size_t size, size_t offset);
|
||||
|
||||
shared_ptr<u8> Data() const
|
||||
@ -44,6 +45,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void EnsureSufficientCapacity(size_t size);
|
||||
|
||||
size_t m_capacity; // must come first (init order)
|
||||
|
||||
shared_ptr<u8> m_data;
|
||||
|
@ -205,6 +205,15 @@ public:
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname)
|
||||
{
|
||||
ScopedLock s;
|
||||
VfsDirectory* directory;
|
||||
WARN_RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, NULL));
|
||||
realPathname = directory->AssociatedDirectory()->Path();
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
virtual Status GetVirtualPath(const OsPath& realPathname, VfsPath& pathname)
|
||||
{
|
||||
ScopedLock s;
|
||||
|
@ -155,6 +155,13 @@ struct IVFS
|
||||
**/
|
||||
virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname) = 0;
|
||||
|
||||
/**
|
||||
* retrieve the real (POSIX) pathname underlying a VFS directory.
|
||||
*
|
||||
* this is useful for passing paths to external libraries.
|
||||
**/
|
||||
virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname) = 0;
|
||||
|
||||
/**
|
||||
* retrieve the VFS pathname that corresponds to a real file.
|
||||
*
|
||||
|
@ -471,7 +471,7 @@ bool CNetClient::OnGameStart(void* context, CFsmEvent* event)
|
||||
*client->m_Game->GetSimulation2(), *client, client->m_HostID, client->m_Game->GetReplayLogger());
|
||||
|
||||
client->m_Game->SetPlayerID(player);
|
||||
client->m_Game->StartGame(client->m_GameAttributes);
|
||||
client->m_Game->StartGame(client->m_GameAttributes, "");
|
||||
|
||||
CScriptValRooted msg;
|
||||
client->GetScriptInterface().Eval("({'type':'start'})", msg);
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "ps/Profile.h"
|
||||
#include "ps/Pyrogenesis.h"
|
||||
#include "ps/Replay.h"
|
||||
#include "ps/SavedGame.h"
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
#include "simulation2/Simulation2.h"
|
||||
|
||||
|
@ -112,8 +112,10 @@ void CGame::SetTurnManager(CNetTurnManager* turnManager)
|
||||
* Makes calls to initialize the game view, world, and simulation objects.
|
||||
* Calls are made to facilitate progress reporting of the initialization.
|
||||
**/
|
||||
void CGame::RegisterInit(const CScriptValRooted& attribs)
|
||||
void CGame::RegisterInit(const CScriptValRooted& attribs, const std::string& savedState)
|
||||
{
|
||||
m_InitialSavedState = savedState;
|
||||
|
||||
m_Simulation2->SetInitAttributes(attribs);
|
||||
|
||||
std::string mapType;
|
||||
@ -151,9 +153,27 @@ void CGame::RegisterInit(const CScriptValRooted& attribs)
|
||||
m_World->RegisterInitRMS(scriptFile, settings, m_PlayerID);
|
||||
}
|
||||
|
||||
if (!m_InitialSavedState.empty())
|
||||
RegMemFun(this, &CGame::LoadInitialState, L"Loading game", 1000);
|
||||
|
||||
LDR_EndRegistering();
|
||||
}
|
||||
|
||||
int CGame::LoadInitialState()
|
||||
{
|
||||
ENSURE(!m_InitialSavedState.empty());
|
||||
|
||||
std::string state;
|
||||
m_InitialSavedState.swap(state); // deletes the original to save a bit of memory
|
||||
|
||||
std::stringstream stream(state);
|
||||
|
||||
bool ok = m_Simulation2->DeserializeState(stream);
|
||||
ENSURE(ok);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Game initialization has been completed. Set game started flag and start the session.
|
||||
*
|
||||
@ -209,11 +229,11 @@ void CGame::SetPlayerID(int playerID)
|
||||
m_TurnManager->SetPlayerID(m_PlayerID);
|
||||
}
|
||||
|
||||
void CGame::StartGame(const CScriptValRooted& attribs)
|
||||
void CGame::StartGame(const CScriptValRooted& attribs, const std::string& savedState)
|
||||
{
|
||||
m_ReplayLogger->StartGame(attribs);
|
||||
|
||||
RegisterInit(attribs);
|
||||
RegisterInit(attribs, savedState);
|
||||
}
|
||||
|
||||
// TODO: doInterpolate is optional because Atlas interpolates explicitly,
|
||||
|
@ -73,7 +73,7 @@ public:
|
||||
**/
|
||||
bool m_Paused;
|
||||
|
||||
void StartGame(const CScriptValRooted& attribs);
|
||||
void StartGame(const CScriptValRooted& attribs, const std::string& savedState);
|
||||
PSRETURN ReallyStartGame();
|
||||
|
||||
/*
|
||||
@ -154,10 +154,13 @@ public:
|
||||
{ return *m_ReplayLogger; }
|
||||
|
||||
private:
|
||||
void RegisterInit(const CScriptValRooted& attribs);
|
||||
void RegisterInit(const CScriptValRooted& attribs, const std::string& savedState);
|
||||
IReplayLogger* m_ReplayLogger;
|
||||
|
||||
std::vector<CColor> m_PlayerColours;
|
||||
|
||||
int LoadInitialState();
|
||||
std::string m_InitialSavedState; // valid between RegisterInit and LoadInitialState
|
||||
};
|
||||
|
||||
extern CGame *g_Game;
|
||||
|
@ -430,6 +430,7 @@ static void InitVfs(const CmdLineArgs& args)
|
||||
g_VFS = CreateVfs(cacheSize);
|
||||
|
||||
g_VFS->Mount(L"screenshots/", paths.Data()/"screenshots"/"");
|
||||
g_VFS->Mount(L"saves/", paths.Data()/"saves"/"");
|
||||
const OsPath readonlyConfig = paths.RData()/"config"/"";
|
||||
g_VFS->Mount(L"config/", readonlyConfig);
|
||||
if(readonlyConfig != paths.Config())
|
||||
@ -1145,7 +1146,7 @@ bool Autostart(const CmdLineArgs& args)
|
||||
else
|
||||
{
|
||||
g_Game->SetPlayerID(1);
|
||||
g_Game->StartGame(attrs);
|
||||
g_Game->StartGame(attrs, "");
|
||||
|
||||
LDR_NonprogressiveLoad();
|
||||
|
||||
|
@ -155,7 +155,7 @@ void CReplayPlayer::Replay()
|
||||
std::getline(*m_Stream, line);
|
||||
CScriptValRooted attribs = game.GetSimulation2()->GetScriptInterface().ParseJSON(line);
|
||||
|
||||
game.StartGame(attribs);
|
||||
game.StartGame(attribs, "");
|
||||
|
||||
// TODO: Non progressive load can fail - need a decent way to handle this
|
||||
LDR_NonprogressiveLoad();
|
||||
|
216
source/ps/SavedGame.cpp
Normal file
216
source/ps/SavedGame.cpp
Normal file
@ -0,0 +1,216 @@
|
||||
/* Copyright (C) 2011 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "SavedGame.h"
|
||||
|
||||
#include "gui/GUIManager.h"
|
||||
#include "lib/allocators/shared_ptr.h"
|
||||
#include "lib/file/archive/archive_zip.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/Filesystem.h"
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
#include "simulation2/Simulation2.h"
|
||||
|
||||
static const int SAVED_GAME_VERSION_MAJOR = 1; // increment on incompatible changes to the format
|
||||
static const int SAVED_GAME_VERSION_MINOR = 0; // increment on compatible changes to the format
|
||||
// TODO: we ought to check version numbers when loading files
|
||||
|
||||
Status SavedGames::Save(const std::wstring& prefix, CSimulation2& simulation, CGUIManager* gui, int playerID)
|
||||
{
|
||||
// Determine the filename to save under
|
||||
const VfsPath basenameFormat(L"saves/" + prefix + L"-%04d");
|
||||
const VfsPath filenameFormat = basenameFormat.ChangeExtension(L".0adsave");
|
||||
VfsPath filename;
|
||||
|
||||
// Don't make this a static global like NextNumberedFilename expects, because
|
||||
// that wouldn't work when 'prefix' changes, and because it's not thread-safe
|
||||
size_t nextSaveNumber = 0;
|
||||
vfs::NextNumberedFilename(g_VFS, filenameFormat, nextSaveNumber, filename);
|
||||
|
||||
// ArchiveWriter_Zip can only write to OsPaths, not VfsPaths,
|
||||
// but we'd like to handle saved games via VFS.
|
||||
// To avoid potential confusion from writing with non-VFS then
|
||||
// reading the same file with VFS, we'll just write to a temporary
|
||||
// non-VFS path and then load and save again via VFS,
|
||||
// which is kind of a hack.
|
||||
|
||||
OsPath tempSaveFileRealPath;
|
||||
WARN_RETURN_STATUS_IF_ERR(g_VFS->GetDirectoryRealPath("cache/", tempSaveFileRealPath));
|
||||
tempSaveFileRealPath = tempSaveFileRealPath / "temp.0adsave";
|
||||
|
||||
time_t now = time(NULL);
|
||||
|
||||
// Construct the serialized state to be saved
|
||||
|
||||
std::stringstream simStateStream;
|
||||
if (!simulation.SerializeState(simStateStream))
|
||||
WARN_RETURN(ERR::FAIL);
|
||||
|
||||
CScriptValRooted metadata;
|
||||
simulation.GetScriptInterface().Eval("({})", metadata);
|
||||
simulation.GetScriptInterface().SetProperty(metadata.get(), "version_major", SAVED_GAME_VERSION_MAJOR);
|
||||
simulation.GetScriptInterface().SetProperty(metadata.get(), "version_minor", SAVED_GAME_VERSION_MINOR);
|
||||
simulation.GetScriptInterface().SetProperty(metadata.get(), "time", (double)now);
|
||||
simulation.GetScriptInterface().SetProperty(metadata.get(), "player", playerID);
|
||||
simulation.GetScriptInterface().SetProperty(metadata.get(), "initAttributes", simulation.GetInitAttributes());
|
||||
if (gui)
|
||||
simulation.GetScriptInterface().SetProperty(metadata.get(), "gui", gui->GetSavedGameData());
|
||||
|
||||
std::string metadataString = simulation.GetScriptInterface().StringifyJSON(metadata.get(), true);
|
||||
|
||||
// Write the saved game as zip file containing the various components
|
||||
PIArchiveWriter archiveWriter;
|
||||
try
|
||||
{
|
||||
archiveWriter = CreateArchiveWriter_Zip(tempSaveFileRealPath, false);
|
||||
}
|
||||
catch (Status err)
|
||||
{
|
||||
WARN_RETURN(err);
|
||||
}
|
||||
|
||||
WARN_RETURN_STATUS_IF_ERR(archiveWriter->AddMemory((const u8*)metadataString.c_str(), metadataString.length(), now, "metadata.json"));
|
||||
WARN_RETURN_STATUS_IF_ERR(archiveWriter->AddMemory((const u8*)simStateStream.str().c_str(), simStateStream.str().length(), now, "simulation.dat"));
|
||||
archiveWriter.reset(); // close the file
|
||||
|
||||
WriteBuffer buffer;
|
||||
FileInfo tempSaveFile;
|
||||
WARN_RETURN_STATUS_IF_ERR(GetFileInfo(tempSaveFileRealPath, &tempSaveFile));
|
||||
buffer.Reserve(tempSaveFile.Size());
|
||||
WARN_RETURN_STATUS_IF_ERR(io::Load(tempSaveFileRealPath, buffer.Data().get(), buffer.Size()));
|
||||
WARN_RETURN_STATUS_IF_ERR(g_VFS->CreateFile(filename, buffer.Data(), buffer.Size()));
|
||||
|
||||
OsPath realPath;
|
||||
WARN_RETURN_STATUS_IF_ERR(g_VFS->GetRealPath(filename, realPath));
|
||||
LOGMESSAGERENDER(L"Saved game to %ls\n", realPath.string().c_str());
|
||||
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
class CGameLoader
|
||||
{
|
||||
NONCOPYABLE(CGameLoader);
|
||||
public:
|
||||
CGameLoader(ScriptInterface& scriptInterface, CScriptValRooted* metadata, std::string* savedState) :
|
||||
m_ScriptInterface(scriptInterface), m_Metadata(metadata), m_SavedState(savedState)
|
||||
{
|
||||
}
|
||||
|
||||
static void ReadEntryCallback(const VfsPath& pathname, const FileInfo& fileInfo, PIArchiveFile archiveFile, uintptr_t cbData)
|
||||
{
|
||||
((CGameLoader*)cbData)->ReadEntry(pathname, fileInfo, archiveFile);
|
||||
}
|
||||
|
||||
void ReadEntry(const VfsPath& pathname, const FileInfo& fileInfo, PIArchiveFile archiveFile)
|
||||
{
|
||||
if (pathname == L"metadata.json" && m_Metadata)
|
||||
{
|
||||
std::string buffer;
|
||||
buffer.resize(fileInfo.Size());
|
||||
WARN_IF_ERR(archiveFile->Load("", DummySharedPtr((u8*)buffer.data()), buffer.size()));
|
||||
*m_Metadata = m_ScriptInterface.ParseJSON(buffer);
|
||||
}
|
||||
else if (pathname == L"simulation.dat" && m_SavedState)
|
||||
{
|
||||
m_SavedState->resize(fileInfo.Size());
|
||||
WARN_IF_ERR(archiveFile->Load("", DummySharedPtr((u8*)m_SavedState->data()), m_SavedState->size()));
|
||||
}
|
||||
}
|
||||
|
||||
ScriptInterface& m_ScriptInterface;
|
||||
CScriptValRooted* m_Metadata;
|
||||
std::string* m_SavedState;
|
||||
};
|
||||
|
||||
Status SavedGames::Load(const std::wstring& name, ScriptInterface& scriptInterface, CScriptValRooted& metadata, std::string& savedState)
|
||||
{
|
||||
// Determine the filename to load
|
||||
const VfsPath basename(L"saves/" + name);
|
||||
const VfsPath filename = basename.ChangeExtension(L".0adsave");
|
||||
|
||||
OsPath realPath;
|
||||
WARN_RETURN_STATUS_IF_ERR(g_VFS->GetRealPath(filename, realPath));
|
||||
|
||||
PIArchiveReader archiveReader;
|
||||
try
|
||||
{
|
||||
archiveReader = CreateArchiveReader_Zip(realPath);
|
||||
}
|
||||
catch (Status err)
|
||||
{
|
||||
WARN_RETURN(err);
|
||||
}
|
||||
|
||||
CGameLoader loader(scriptInterface, &metadata, &savedState);
|
||||
WARN_RETURN_STATUS_IF_ERR(archiveReader->ReadEntries(CGameLoader::ReadEntryCallback, (uintptr_t)&loader));
|
||||
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
std::vector<CScriptValRooted> SavedGames::GetSavedGames(ScriptInterface& scriptInterface)
|
||||
{
|
||||
TIMER(L"GetSavedGames");
|
||||
|
||||
std::vector<CScriptValRooted> games;
|
||||
|
||||
Status err;
|
||||
|
||||
VfsPaths pathnames;
|
||||
err = vfs::GetPathnames(g_VFS, "saves/", L"*.0adsave", pathnames);
|
||||
WARN_IF_ERR(err);
|
||||
|
||||
for (size_t i = 0; i < pathnames.size(); ++i)
|
||||
{
|
||||
OsPath realPath;
|
||||
err = g_VFS->GetRealPath(pathnames[i], realPath);
|
||||
if (err < 0)
|
||||
{
|
||||
DEBUG_WARN_ERR(err);
|
||||
continue; // skip this file
|
||||
}
|
||||
|
||||
PIArchiveReader archiveReader;
|
||||
try
|
||||
{
|
||||
archiveReader = CreateArchiveReader_Zip(realPath);
|
||||
}
|
||||
catch (Status err)
|
||||
{
|
||||
DEBUG_WARN_ERR(err);
|
||||
continue; // skip this file
|
||||
}
|
||||
|
||||
CScriptValRooted metadata;
|
||||
CGameLoader loader(scriptInterface, &metadata, NULL);
|
||||
err = archiveReader->ReadEntries(CGameLoader::ReadEntryCallback, (uintptr_t)&loader);
|
||||
if (err < 0)
|
||||
{
|
||||
DEBUG_WARN_ERR(err);
|
||||
continue; // skip this file
|
||||
}
|
||||
|
||||
CScriptValRooted game;
|
||||
scriptInterface.Eval("({})", game);
|
||||
scriptInterface.SetProperty(game.get(), "id", pathnames[i].Basename());
|
||||
scriptInterface.SetProperty(game.get(), "metadata", metadata);
|
||||
games.push_back(game);
|
||||
}
|
||||
|
||||
return games;
|
||||
}
|
37
source/ps/SavedGame.h
Normal file
37
source/ps/SavedGame.h
Normal file
@ -0,0 +1,37 @@
|
||||
/* Copyright (C) 2011 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_SAVEDGAME
|
||||
#define INCLUDED_SAVEDGAME
|
||||
|
||||
class CSimulation2;
|
||||
class ScriptInterface;
|
||||
class CScriptValRooted;
|
||||
class CGUIManager;
|
||||
|
||||
namespace SavedGames
|
||||
{
|
||||
|
||||
Status Save(const std::wstring& prefix, CSimulation2& simulation, CGUIManager* gui, int playerIDo);
|
||||
|
||||
Status Load(const std::wstring& name, ScriptInterface& scriptInterface, CScriptValRooted& metadata, std::string& savedState);
|
||||
|
||||
std::vector<CScriptValRooted> GetSavedGames(ScriptInterface& scriptInterface);
|
||||
|
||||
}
|
||||
|
||||
#endif // INCLUDED_SAVEDGAME
|
@ -58,7 +58,7 @@ namespace
|
||||
|
||||
void StartGame(const CScriptValRooted& attrs)
|
||||
{
|
||||
g_Game->StartGame(attrs);
|
||||
g_Game->StartGame(attrs, "");
|
||||
|
||||
// TODO: Non progressive load can fail - need a decent way to handle this
|
||||
LDR_NonprogressiveLoad();
|
||||
|
Loading…
Reference in New Issue
Block a user