Index: ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js (revision 17355)
+++ ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js (revision 17356)
@@ -1,341 +1,342 @@
const g_EngineInfo = Engine.GetEngineInfo();
const g_CivData = loadCivData();
const g_mapSizes = initMapSizes();
/**
* All replays found in the directory.
*/
var g_Replays = [];
/**
* List of replays after applying the display filter.
*/
var g_ReplaysFiltered = [];
/**
* Array of unique usernames of all replays. Used for autocompleting usernames.
*/
var g_Playernames = [];
/**
* Sorted list of unique maptitles. Used by mapfilter.
*/
var g_MapNames = [];
/**
* Directory name of the currently selected replay. Used to restore the selection after changing filters.
*/
var g_selectedReplayDirectory = "";
/**
* Initializes globals, loads replays and displays the list.
*/
function init()
{
if (!g_Settings)
{
Engine.SwitchGuiPage("page_pregame.xml");
return;
}
// By default, sort replays by date in descending order
Engine.GetGUIObjectByName("replaySelection").selected_column_order = -1;
loadReplays();
displayReplayList();
}
/**
* Store the list of replays loaded in C++ in g_Replays.
* Check timestamp and compatibility and extract g_Playernames, g_MapNames
*/
function loadReplays()
{
g_Replays = Engine.GetReplays();
g_Playernames = [];
for (let replay of g_Replays)
{
// Use time saved in file, otherwise file mod date
- replay.timestamp = replay.attribs.timestamp ? +replay.attribs.timestamp : +replay.filemod_timestamp;
+ replay.timestamp = replay.attribs.timestamp ? +replay.attribs.timestamp : +replay.filemod_timestamp-replay.duration;
// Check replay for compability
replay.isCompatible = isReplayCompatible(replay);
sanitizeGameAttributes(replay.attribs);
// Extract map names
if (g_MapNames.indexOf(replay.attribs.settings.Name) == -1 && replay.attribs.settings.Name != "")
g_MapNames.push(replay.attribs.settings.Name);
// Extract playernames
for (let playerData of replay.attribs.settings.PlayerData)
{
if (!playerData || playerData.AI)
continue;
// Remove rating from nick
let playername = playerData.Name;
let ratingStart = playername.indexOf(" (");
if (ratingStart != -1)
playername = playername.substr(0, ratingStart);
if (g_Playernames.indexOf(playername) == -1)
g_Playernames.push(playername);
}
}
g_MapNames.sort();
// Reload filters (since they depend on g_Replays and its derivatives)
initFilters();
}
/**
* We may encounter malformed replays.
*/
function sanitizeGameAttributes(attribs)
{
if (!attribs.settings)
attribs.settings = {};
if (!attribs.settings.Size)
attribs.settings.Size = -1;
if (!attribs.settings.Name)
attribs.settings.Name = "";
if (!attribs.settings.PlayerData)
attribs.settings.PlayerData = [];
if (!attribs.settings.PopulationCap)
attribs.settings.PopulationCap = 300;
if (!attribs.settings.mapType)
attribs.settings.mapType = "skirmish";
if (!attribs.settings.GameType)
attribs.settings.GameType = "conquest";
// Remove gaia
if (attribs.settings.PlayerData.length && attribs.settings.PlayerData[0] == null)
attribs.settings.PlayerData.shift();
attribs.settings.PlayerData.forEach((pData, index) => {
if (!pData.Name)
pData.Name = "";
});
}
/**
* Filter g_Replays, fill the GUI list with that data and show the description of the current replay.
*/
function displayReplayList()
{
// Remember previously selected replay
var replaySelection = Engine.GetGUIObjectByName("replaySelection");
if (replaySelection.selected != -1)
g_selectedReplayDirectory = g_ReplaysFiltered[replaySelection.selected].directory;
filterReplays();
// Create GUI list data
var list = g_ReplaysFiltered.map(replay => {
let works = replay.isCompatible;
return {
"directories": replay.directory,
"months": greyout(getReplayDateTime(replay), works),
"popCaps": greyout(translatePopulationCapacity(replay.attribs.settings.PopulationCap), works),
"mapNames": greyout(getReplayMapName(replay), works),
"mapSizes": greyout(translateMapSize(replay.attribs.settings.Size), works),
"durations": greyout(getReplayDuration(replay), works),
"playerNames": greyout(getReplayPlayernames(replay), works)
};
});
// Extract arrays
if (list.length)
list = prepareForDropdown(list);
// Push to GUI
replaySelection.selected = -1;
replaySelection.list_name = list.months || [];
replaySelection.list_players = list.playerNames || [];
replaySelection.list_mapName = list.mapNames || [];
replaySelection.list_mapSize = list.mapSizes || [];
replaySelection.list_popCapacity = list.popCaps || [];
replaySelection.list_duration = list.durations || [];
// Change these last, otherwise crash
replaySelection.list = list.directories || [];
replaySelection.list_data = list.directories || [];
// Restore selection
replaySelection.selected = replaySelection.list.findIndex(directory => directory == g_selectedReplayDirectory);
displayReplayDetails();
}
/**
* Shows preview image, description and player text in the right panel.
*/
function displayReplayDetails()
{
var selected = Engine.GetGUIObjectByName("replaySelection").selected;
var replaySelected = selected > -1;
Engine.GetGUIObjectByName("replayInfo").hidden = !replaySelected;
Engine.GetGUIObjectByName("replayInfoEmpty").hidden = replaySelected;
Engine.GetGUIObjectByName("startReplayButton").enabled = replaySelected;
Engine.GetGUIObjectByName("deleteReplayButton").enabled = replaySelected;
- Engine.GetGUIObjectByName("summaryButton").enabled = replaySelected;
+ Engine.GetGUIObjectByName("summaryButton").hidden = true;
if (!replaySelected)
return;
var replay = g_ReplaysFiltered[selected];
var mapData = getMapDescriptionAndPreview(replay.attribs.settings.mapType, replay.attribs.map);
// Update GUI
Engine.GetGUIObjectByName("sgMapName").caption = translate(replay.attribs.settings.Name);
Engine.GetGUIObjectByName("sgMapSize").caption = translateMapSize(replay.attribs.settings.Size);
Engine.GetGUIObjectByName("sgMapType").caption = translateMapType(replay.attribs.settings.mapType);
Engine.GetGUIObjectByName("sgVictory").caption = translateVictoryCondition(replay.attribs.settings.GameType);
Engine.GetGUIObjectByName("sgNbPlayers").caption = replay.attribs.settings.PlayerData.length;
Engine.GetGUIObjectByName("sgPlayersNames").caption = getReplayTeamText(replay);
Engine.GetGUIObjectByName("sgMapDescription").caption = mapData.description;
Engine.GetGUIObjectByName("sgMapPreview").sprite = "cropped:(0.7812,0.5859)session/icons/mappreview/" + mapData.preview;
+ Engine.GetGUIObjectByName("summaryButton").hidden = !Engine.HasReplayMetadata(replay.directory);
}
/**
* Adds grey font if replay is not compatible.
*/
function greyout(text, isCompatible)
{
return isCompatible ? text : '[color="96 96 96"]' + text + '[/color]';
}
/**
* Returns a human-readable version of the replay date.
*/
function getReplayDateTime(replay)
{
return Engine.FormatMillisecondsIntoDateString(replay.timestamp * 1000, translate("yyyy-MM-dd HH:mm"))
}
/**
* Returns a human-readable list of the playernames of that replay.
*
* @returns {string}
*/
function getReplayPlayernames(replay)
{
// TODO: colorize playernames like in the lobby.
return replay.attribs.settings.PlayerData.map(pData => pData.Name).join(", ");
}
/**
* Returns the name of the map of the given replay.
*
* @returns {string}
*/
function getReplayMapName(replay)
{
return translate(replay.attribs.settings.Name);
}
/**
* Returns the month of the given replay in the format "yyyy-MM".
*
* @returns {string}
*/
function getReplayMonth(replay)
{
return Engine.FormatMillisecondsIntoDateString(replay.timestamp * 1000, translate("yyyy-MM"));
}
/**
* Returns a human-readable version of the time when the replay started.
*
* @returns {string}
*/
function getReplayDuration(replay)
{
return timeToString(replay.duration * 1000);
}
/**
* True if we can start the given replay with the currently loaded mods.
*/
function isReplayCompatible(replay)
{
return replayHasSameEngineVersion(replay) && hasSameMods(replay.attribs, g_EngineInfo);
}
/**
* True if we can start the given replay with the currently loaded mods.
*/
function replayHasSameEngineVersion(replay)
{
return replay.attribs.engine_version && replay.attribs.engine_version == g_EngineInfo.engine_version;
}
/**
* Returns a description of the player assignments.
* Including civs, teams, AI settings and player colors.
*
* If the spoiler-checkbox is checked, it also shows defeated players.
*
* @returns {string}
*/
function getReplayTeamText(replay)
{
// Load replay metadata
const metadata = Engine.GetReplayMetadata(replay.directory);
const spoiler = Engine.GetGUIObjectByName("showSpoiler").checked;
var playerDescriptions = {};
var playerIdx = 0;
for (let playerData of replay.attribs.settings.PlayerData)
{
// Get player info
++playerIdx;
let teamIdx = playerData.Team;
let playerColor = playerData.Color ? playerData.Color : g_Settings.PlayerDefaults[playerIdx].Color;
let showDefeated = spoiler && metadata && metadata.playerStates && metadata.playerStates[playerIdx].state == "defeated";
let isAI = playerData.AI;
// Create human-readable player description
let playerDetails = {
"playerName": '[color="' + rgbToGuiColor(playerColor) + '"]' + escapeText(playerData.Name) + "[/color]",
"civ": translate(g_CivData[playerData.Civ].Name),
"AIname": isAI ? translateAIName(playerData.AI) : "",
"AIdifficulty": isAI ? translateAIDifficulty(playerData.AIDiff) : ""
};
if (!isAI && !showDefeated)
playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s)"), playerDetails);
else if (!isAI && showDefeated)
playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s, defeated)"), playerDetails);
else if (isAI && !showDefeated)
playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s)"), playerDetails);
else
playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s, defeated)"), playerDetails);
// Sort player descriptions by team
if (!playerDescriptions[teamIdx])
playerDescriptions[teamIdx] = [];
playerDescriptions[teamIdx].push(playerDetails);
}
var teams = Object.keys(playerDescriptions);
// If there are no teams, merge all playersDescriptions
if (teams.length == 1)
return playerDescriptions[teams[0]].join("\n") + "\n";
// If there are teams, merge "Team N:" + playerDescriptions
return teams.map(team => {
let teamCaption = (team == -1) ? translate("No Team") : sprintf(translate("Team %(team)s"), { "team": +team + 1 });
return '[font="sans-bold-14"]' + teamCaption + "[/font]:\n" + playerDescriptions[team].join("\n");
}).join("\n");
}
Index: ps/trunk/source/ps/VisualReplay.cpp
===================================================================
--- ps/trunk/source/ps/VisualReplay.cpp (revision 17355)
+++ ps/trunk/source/ps/VisualReplay.cpp (revision 17356)
@@ -1,321 +1,325 @@
/* Copyright (C) 2015 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 .
*/
#include "precompiled.h"
#include "VisualReplay.h"
#include "graphics/GameView.h"
#include "gui/GUIManager.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/utf8.h"
#include "network/NetClient.h"
#include "network/NetServer.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/Pyrogenesis.h"
#include "ps/Replay.h"
#include "scriptinterface/ScriptInterface.h"
/**
* Filter too short replays (value in seconds).
*/
const u8 minimumReplayDuration = 3;
/**
* Allows quick debugging of potential platform-dependent file-reading bugs.
*/
const bool debugParser = false;
OsPath VisualReplay::GetDirectoryName()
{
return OsPath(psLogDir() / L"sim_log");
}
-void VisualReplay::StartVisualReplay(CStrW directory)
+void VisualReplay::StartVisualReplay(const CStrW& directory)
{
ENSURE(!g_NetServer);
ENSURE(!g_NetClient);
ENSURE(!g_Game);
const OsPath replayFile = VisualReplay::GetDirectoryName() / directory / L"commands.txt";
if (!FileExists(replayFile))
return;
g_Game = new CGame(false, false);
g_Game->StartVisualReplay(replayFile.string8());
}
JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface)
{
TIMER(L"GetReplays");
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
u32 i = 0;
DirectoryNames directories;
JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) == INFO::OK)
for (OsPath& directory : directories)
{
JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory));
if (!replayData.isNull())
JS_SetElement(cx, replays, i++, replayData);
}
return JS::ObjectValue(*replays);
}
/**
* Move the cursor backwards until a newline was read or the beginning of the file was found.
* Either way the cursor points to the beginning of a newline.
*
* @return The current cursor position or -1 on error.
*/
inline int goBackToLineBeginning(std::istream* replayStream, const CStr& fileName, const u64& fileSize)
{
int currentPos;
char character;
for (int characters = 0; characters < 10000; ++characters)
{
currentPos = (int) replayStream->tellg();
// Stop when reached the beginning of the file
if (currentPos == 0)
return currentPos;
if (!replayStream->good())
{
LOGERROR("Unknown error when returning to the last line (%i of %lu) of %s", currentPos, fileSize, fileName.c_str());
return -1;
}
// Stop when reached newline
replayStream->get(character);
if (character == '\n')
return currentPos;
// Otherwise go back one character.
// Notice: -1 will set the cursor back to the most recently read character.
replayStream->seekg(-2, std::ios_base::cur);
}
LOGERROR("Infinite loop when going back to a line beginning in %s", fileName.c_str());
return -1;
}
/**
* Compute game duration. Assume constant turn length.
* Find the last line that starts with "turn" by reading the file backwards.
*
* @return seconds or -1 on error
*/
inline int getReplayDuration(std::istream *replayStream, const CStr& fileName, const u64& fileSize)
{
CStr type;
// Move one character before the file-end
replayStream->seekg(-2, std::ios_base::end);
// Infinite loop protection, should never occur.
// There should be about 5 lines to read until a turn is found.
for (int linesRead = 1; linesRead < 1000; ++linesRead)
{
int currentPosition = goBackToLineBeginning(replayStream, fileName, fileSize);
// Read error or reached file beginning. No turns exist.
if (currentPosition < 1)
return -1;
if (debugParser)
+ // TODO: throws a compiler warning on some systems
debug_printf("At position %i of %lu after %i lines reads.\n", currentPosition, fileSize, linesRead);
if (!replayStream->good())
{
LOGERROR("Read error when determining replay duration at %i of %llu in %s", currentPosition - 2, fileSize, fileName.c_str());
return -1;
}
// Found last turn, compute duration.
if ((u64) currentPosition + 4 < fileSize && (*replayStream >> type).good() && type == "turn")
{
u32 turn = 0, turnLength = 0;
*replayStream >> turn >> turnLength;
return (turn+1) * turnLength / 1000; // add +1 as turn numbers starts with 0
}
// Otherwise move cursor back to the character before the last newline
replayStream->seekg(currentPosition - 2, std::ios_base::beg);
}
LOGERROR("Infinite loop when determining replay duration for %s", fileName.c_str());
return -1;
}
JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory)
{
// The directory argument must not be constant, otherwise concatenating will fail
const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt";
if (debugParser)
debug_printf("Opening %s\n", replayFile.string8().c_str());
if (!FileExists(replayFile))
return JSVAL_NULL;
// Get file size and modification date
CFileInfo fileInfo;
GetFileInfo(replayFile, &fileInfo);
const u64 fileTime = (u64)fileInfo.MTime() & ~1; // skip lowest bit, since zip and FAT don't preserve it (according to CCacheLoader::LooseCachePath)
const u64 fileSize = (u64)fileInfo.Size();
if (fileSize == 0)
return JSVAL_NULL;
// Open file
const CStr fileName = replayFile.string8();
std::ifstream* replayStream = new std::ifstream(fileName.c_str());
// File must begin with "start"
CStr type;
if (!(*replayStream >> type).good() || type != "start")
{
LOGERROR("Couldn't open %s. Non-latin characters are not supported yet.", fileName.c_str());
SAFE_DELETE(replayStream);
return JSVAL_NULL;
}
// Parse header / first line
CStr header;
std::getline(*replayStream, header);
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
JS::RootedValue attribs(cx);
if (!scriptInterface.ParseJSON(header, &attribs))
{
LOGERROR("Couldn't parse replay header of %s", fileName.c_str());
SAFE_DELETE(replayStream);
return JSVAL_NULL;
}
// Ensure "turn" after header
if (!(*replayStream >> type).good() || type != "turn")
{
SAFE_DELETE(replayStream);
return JSVAL_NULL; // there are no turns at all
}
// Don't process files of rejoined clients
u32 turn = 1;
*replayStream >> turn;
if (turn != 0)
{
SAFE_DELETE(replayStream);
return JSVAL_NULL;
}
int duration = getReplayDuration(replayStream, fileName, fileSize);
SAFE_DELETE(replayStream);
// Ensure minimum duration
if (duration < minimumReplayDuration)
return JSVAL_NULL;
// Return the actual data
JS::RootedValue replayData(cx);
scriptInterface.Eval("({})", &replayData);
scriptInterface.SetProperty(replayData, "file", replayFile);
scriptInterface.SetProperty(replayData, "directory", directory);
scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime));
scriptInterface.SetProperty(replayData, "attribs", attribs);
scriptInterface.SetProperty(replayData, "duration", duration);
return replayData;
}
bool VisualReplay::DeleteReplay(const CStrW& replayDirectory)
{
if (replayDirectory.empty())
return false;
const OsPath directory = GetDirectoryName() / replayDirectory;
return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK;
}
JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
{
// Create empty JS object
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue attribs(cx);
pCxPrivate->pScriptInterface->Eval("({})", &attribs);
// Return empty object if file doesn't exist
const OsPath replayFile = GetDirectoryName() / directoryName / L"commands.txt";
if (!FileExists(replayFile))
return attribs;
// Open file
std::istream* replayStream = new std::ifstream(replayFile.string8().c_str());
CStr type, line;
ENSURE((*replayStream >> type).good() && type == "start");
// Read and return first line
std::getline(*replayStream, line);
pCxPrivate->pScriptInterface->ParseJSON(line, &attribs);
SAFE_DELETE(replayStream);;
return attribs;
}
// TODO: enhancement: how to save the data if the process is killed? (case SDL_QUIT in main.cpp)
void VisualReplay::SaveReplayMetadata(const CStrW& data)
{
// TODO: enhancement: use JS::HandleValue similar to SaveGame
if (!g_Game)
return;
// Get the directory of the currently active replay
const OsPath fileName = g_Game->GetReplayLogger().GetDirectory() / L"metadata.json";
CreateDirectories(fileName.Parent(), 0700);
- std::ofstream stream (OsString(fileName).c_str(), std::ofstream::out | std::ofstream::trunc);
+ std::ofstream stream (fileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
stream << utf8_from_wstring(data);
stream.close();
}
+bool VisualReplay::HasReplayMetadata(const CStrW& directoryName)
+{
+ return FileExists(GetDirectoryName() / directoryName / L"metadata.json");
+}
+
JS::Value VisualReplay::GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
{
- const OsPath filePath = GetDirectoryName() / directoryName / L"metadata.json";
+ if (!HasReplayMetadata(directoryName))
+ return JSVAL_NULL;
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue metadata(cx);
- if (!FileExists(filePath))
- return JSVAL_NULL;
-
- std::ifstream* stream = new std::ifstream(OsString(filePath).c_str());
+ std::ifstream* stream = new std::ifstream(OsPath(GetDirectoryName() / directoryName / L"metadata.json").string8());
ENSURE(stream->good());
CStr line;
std::getline(*stream, line);
stream->close();
SAFE_DELETE(stream);
pCxPrivate->pScriptInterface->ParseJSON(line, &metadata);
return metadata;
}
Index: ps/trunk/source/ps/VisualReplay.h
===================================================================
--- ps/trunk/source/ps/VisualReplay.h (revision 17355)
+++ ps/trunk/source/ps/VisualReplay.h (revision 17356)
@@ -1,83 +1,88 @@
/* Copyright (C) 2015 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 .
*/
#ifndef INCLUDED_REPlAY
#define INCLUDED_REPlAY
#include "scriptinterface/ScriptInterface.h"
class CSimulation2;
class CGUIManager;
/**
* Contains functions for visually replaying past games.
*/
namespace VisualReplay
{
/**
* Returns the path to the sim-log directory (that contains the directories with the replay files.
*
* @param scriptInterface the ScriptInterface in which to create the return data.
* @return OsPath the absolute file path
*/
OsPath GetDirectoryName();
/**
* Replays the commands.txt file in the given subdirectory visually.
*/
-void StartVisualReplay(CStrW directory);
+void StartVisualReplay(const CStrW& directory);
/**
* Get a list of replays to display in the GUI.
*
* @param scriptInterface the ScriptInterface in which to create the return data.
* @return array of objects containing replay data
*/
JS::Value GetReplays(ScriptInterface& scriptInterface);
/**
* Parses a commands.txt file and extracts metadata.
* Works similarly to CGame::LoadReplayData().
*/
JS::Value LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory);
/**
* Permanently deletes the visual replay (including the parent directory)
*
* @param replayFile path to commands.txt, whose parent directory will be deleted
* @return true if deletion was successful, false on error
*/
bool DeleteReplay(const CStrW& replayFile);
/**
* Returns the parsed header of the replay file (commands.txt).
*/
JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
/**
+ * Returns whether or not the metadata / summary screen data has been saved properly when the game ended.
+ */
+bool HasReplayMetadata(const CStrW& directoryName);
+
+/**
* Returns the metadata of a replay.
*/
JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
/**
* Saves the metadata from the session to metadata.json
*/
void SaveReplayMetadata(const CStrW& data);
}
#endif
Index: ps/trunk/source/ps/scripting/JSInterface_VisualReplay.cpp
===================================================================
--- ps/trunk/source/ps/scripting/JSInterface_VisualReplay.cpp (revision 17355)
+++ ps/trunk/source/ps/scripting/JSInterface_VisualReplay.cpp (revision 17356)
@@ -1,61 +1,67 @@
/* Copyright (C) 2015 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 .
*/
#include "precompiled.h"
#include "ps/VisualReplay.h"
#include "ps/scripting/JSInterface_VisualReplay.h"
void JSI_VisualReplay::StartVisualReplay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW directory)
{
VisualReplay::StartVisualReplay(directory);
}
bool JSI_VisualReplay::DeleteReplay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW replayFile)
{
return VisualReplay::DeleteReplay(replayFile);
}
JS::Value JSI_VisualReplay::GetReplays(ScriptInterface::CxPrivate* pCxPrivate)
{
return VisualReplay::GetReplays(*(pCxPrivate->pScriptInterface));
}
JS::Value JSI_VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, CStrW directoryName)
{
return VisualReplay::GetReplayAttributes(pCxPrivate, directoryName);
}
+bool JSI_VisualReplay::HasReplayMetadata(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW directoryName)
+{
+ return VisualReplay::HasReplayMetadata(directoryName);
+}
+
JS::Value JSI_VisualReplay::GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, CStrW directoryName)
{
return VisualReplay::GetReplayMetadata(pCxPrivate, directoryName);
}
void JSI_VisualReplay::SaveReplayMetadata(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW data)
{
VisualReplay::SaveReplayMetadata(data);
}
void JSI_VisualReplay::RegisterScriptFunctions(ScriptInterface& scriptInterface)
{
scriptInterface.RegisterFunction("GetReplays");
scriptInterface.RegisterFunction("DeleteReplay");
scriptInterface.RegisterFunction("StartVisualReplay");
scriptInterface.RegisterFunction("GetReplayAttributes");
scriptInterface.RegisterFunction("GetReplayMetadata");
+ scriptInterface.RegisterFunction("HasReplayMetadata");
scriptInterface.RegisterFunction("SaveReplayMetadata");
}
Index: ps/trunk/source/ps/scripting/JSInterface_VisualReplay.h
===================================================================
--- ps/trunk/source/ps/scripting/JSInterface_VisualReplay.h (revision 17355)
+++ ps/trunk/source/ps/scripting/JSInterface_VisualReplay.h (revision 17356)
@@ -1,35 +1,36 @@
/* Copyright (C) 2015 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 .
*/
#ifndef INCLUDED_JSI_VISUALREPLAY
#define INCLUDED_JSI_VISUALREPLAY
#include "ps/VisualReplay.h"
#include "scriptinterface/ScriptInterface.h"
namespace JSI_VisualReplay
{
- void StartVisualReplay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW directory);
- bool DeleteReplay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW replayFile);
+ void StartVisualReplay(ScriptInterface::CxPrivate* pCxPrivate, CStrW directory);
+ bool DeleteReplay(ScriptInterface::CxPrivate* pCxPrivate, CStrW replayFile);
JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate);
JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, CStrW directoryName);
+ bool HasReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, CStrW directoryName);
JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, CStrW directoryName);
- void SaveReplayMetadata(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW data);
+ void SaveReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, CStrW data);
void RegisterScriptFunctions(ScriptInterface& scriptInterface);
}
#endif