Index: ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json (revision 18202)
+++ ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json (revision 18203)
@@ -1,206 +1,207 @@
{
"Content": [
{
"Title": "Programming managers",
"List": [
{"nick": "Acumen", "name": "Stuart Walpole"},
{"nick": "Dak Lozar", "name": "Dave Loeser"},
{"nick": "h20", "name": "Daniel Wilhelm"},
{"nick": "Janwas", "name": "Jan Wassenberg"},
{"nick": "Raj", "name": "Raj Sharma"}
]
},
{
"Subtitle": "Special thanks to",
"List": [{"nick": "Ykkrosh", "name": "Philip Taylor"}]
},
{
"List": [
{"nick": "01d55"},
{"nick": "Acumen", "name": "Stuart Walpole"},
{"name": "Adrian Fatol"},
{"nick": "AI-Amsterdam"},
{"nick": "Alan", "name": "Alan Kemp"},
{"nick": "aBothe", "name": "Alexander Bothe"},
{"nick": "alpha123", "name": "Peter P. Cannici"},
{"nick": "Aurium", "name": "Aurélio Heckert"},
{"nick": "badmadblacksad", "name": "Martin F"},
{"name": "Mikołaj \"Bajter\" Korcz"},
{"nick": "bb", "name": "Bouke Jansen"},
{"nick": "Ben", "name": "Ben Vinegar"},
{"nick": "Bird"},
{"nick": "Blue", "name": "Richard Welsh"},
{"nick": "bmwiedemann"},
{"nick": "boeseRaupe", "name": "Michael Kluge"},
{"nick": "bog_dan_ro", "name": "BogDan Vatra"},
{"nick": "Bonk", "name": "Christopher Ebbert"},
{"nick": "Caius", "name": "Lars Kemmann"},
{"nick": "Calefaction", "name": "Matt Holmes"},
{"nick": "Calvinh", "name": "Carl-Johan Höiby"},
{"name": "Cédric Houbart"},
{"nick": "Chakakhan", "name": "Kenny Long"},
{"nick": "Clockwork-Muse", "name": "Stephen A. Imhoff"},
{"nick": "Cracker78", "name": "Chad Heim"},
{"nick": "Crynux", "name": "Stephen J. Fewer"},
{"nick": "cwprogger"},
{"nick": "Dak Lozar", "name": "Dave Loeser"},
{"nick": "dalerank", "name": "Sergey Kushnirenko"},
{"nick": "dan", "name": "Dan Strandberg"},
{"name": "Daniel Trevitz"},
{"nick": "DanCar", "name": "Daniel Cardenas"},
{"nick": "Dave", "name": "David Protasowski"},
{"nick": "dax", "name": "Dacian Fiordean"},
{"nick": "deebee", "name": "Deepak Anthony"},
{"nick": "Deiz"},
{"nick": "Dietger", "name": "Dietger van Antwerpen"},
{"nick": "dpiquet", "name": "Damien Piquet"},
{"nick": "dumbo"},
{"nick": "dvangennip", "name": "Doménique"},
{"nick": "Echelon9", "name": "Rhys Kidd"},
+ {"nick": "echotangoecho"},
{"nick": "eihrul", "name": "Lee Salzman"},
{"nick": "elexis", "name": "Alexander Heinsius"},
{"nick": "EmjeR", "name": "Matthijs de Rijk"},
{"nick": "EMontana"},
{"nick": "ericb"},
{"nick": "evanssthomas", "name": "Evans Thomas"},
{"nick": "Evulant", "name": "Alexander S."},
{"nick": "fabio", "name": "Fabio Pedretti"},
{"nick": "falsevision", "name": "Mahdi Khodadadifard"},
{"nick": "fatherbushido", "name": "Nicolas Tisserand"},
{"nick": "fcxSanya", "name": "Alexander Olkhovskiy"},
{"nick": "FeXoR", "name": "Florian Finke"},
{"nick": "Fire Giant", "name": "Malte Schwarzkopf"},
{"nick": "fpre", "name": "Frederick Stallmeyer"},
{"nick": "freenity", "name": "Anton Galitch"},
{"nick": "gbish (aka Iny)", "name": "Grant Bishop"},
{"nick": "Gee", "name": "Gustav Larsson"},
{"nick": "gerbilOFdoom"},
{"nick": "godlikeldh"},
{"nick": "greybeard", "name": "Joe Cocovich"},
{"nick": "grillaz"},
{"nick": "gudo"},
{"nick": "Guuts", "name": "Matthew Guttag"},
{"name": "Samuel Guarnieri"},
{"nick": "Haommin"},
{"nick": "h20", "name": "Daniel Wilhelm"},
{"nick": "historic_bruno", "name": "Ben Brian"},
{"nick": "idanwin"},
{"nick": "Imarok", "name": "J. S."},
{"nick": "infyquest", "name": "Vijay Kiran Kamuju"},
{"nick": "IronNerd", "name": "Matthew McMullan"},
{"nick": "Itms", "name": "Nicolas Auvray"},
{"nick": "Jaison", "name": "Marco tom Suden"},
{"nick": "jammus", "name": "James Scott"},
{"nick": "Jgwman"},
{"nick": "JonBaer", "name": "Jon Baer"},
{"nick": "Josh", "name": "Joshua J. Bakita"},
{"nick": "jP_wanN", "name": "Jonas Platte"},
{"nick": "Jubalbarca", "name": "James Baillie"},
{"nick": "JubJub", "name": "Sebastian Vetter"},
{"nick": "kabzerek", "name": "Grzegorz Kabza"},
{"nick": "Kai", "name": "Kai Chen"},
{"name": "Kareem Ergawy"},
{"nick": "kevmo", "name": "Kevin Caffrey"},
{"nick": "kezz", "name": "Graeme Kerry"},
{"nick": "kingadami", "name": "Adam Winsor"},
{"nick": "kingbasil", "name": "Giannis Fafalios"},
{"nick": "lafferjm", "name": "Justin Lafferty"},
{"nick": "leper", "name": "Georg Kilzer"},
{"nick": "LittleDev"},
{"nick": "livingaftermidnight", "name": "Will Dull"},
{"nick": "Louhike"},
{"nick": "lsdh"},
{"nick": "madmax", "name": "Abhijit Nandy"},
{"nick": "m0l0t0ph", "name": "Christoph Gielisch"},
{"nick": "markcho"},
{"nick": "MarkT", "name": "Mark Thompson"},
{"nick": "Markus"},
{"nick": "Matei", "name": "Matei Zaharia"},
{"nick": "MattDoerksen", "name": "Matt Doerksen"},
{"nick": "mattlott", "name": "Matt Lott"},
{"nick": "maveric", "name": "Anton Protko"},
{"nick": "Micnasty", "name": "Travis Gorkin"},
{"nick": "mimo"},
{"nick": "mk12", "name": "Mitchell Kember"},
{"nick": "Molotov", "name": "Dario Alvarez"},
{"nick": "mpmoreti", "name": "Marcos Paulo Moreti"},
{"nick": "mreiland", "name": "Michael Reiland"},
{"nick": "myconid"},
{"nick": "nd3c3nt", "name": "Gavin Fowler"},
{"nick": "niektb", "name": "Niek ten Brinke"},
{"nick": "njm"},
{"nick": "NoMonkey", "name": "John Mena"},
{"nick": "notpete", "name": "Rich Cross"},
{"nick": "Ols", "name": "Oliver Whiteman"},
{"nick": "olsner", "name": "Simon Brenner"},
{"nick": "otero"},
{"name": "Nick Owens"},
{"nick": "Palaxin", "name": "David A. Freitag"},
{"name": "Paul Withers"},
{"nick": "pcpa", "name": "Paulo Andrade"},
{"nick": "Pendingchaos"},
{"nick": "PeteVasi", "name": "Pete Vasiliauskas"},
{"nick": "Polakrity"},
{"nick": "Poya", "name": "Poya Manouchehri"},
{"name": "Quentin Pradet"},
{"nick": "prefect", "name": "Nicolai Hähnle"},
{"nick": "pstumpf", "name": "Pascal Stumpf"},
{"name": "André Puel"},
{"nick": "Prodigal Son"},
{"nick": "pyrolink", "name": "Andrew Decker"},
{"nick": "quantumstate", "name": "Jonathan Waller"},
{"nick": "QuickShot", "name": "Walter Krawec"},
{"nick": "quonter"},
{"nick": "qwertz"},
{"nick": "Radagast"},
{"nick": "Raj", "name": "Raj Sharma"},
{"nick": "RedFox", "name": "Jorma Rebane"},
{"nick": "RefinedCode"},
{"nick": "Riemer"},
{"name": "Rolf Sievers"},
{"nick": "s0600204", "name": "Matthew Norwood"},
{"nick": "SafaAlfulaij"},
{"nick": "sanderd17", "name": "Sander Deryckere"},
{"nick": "sathyam", "name": "Sathyam Vellal"},
{"nick": "sbte", "name": "Sven Baars"},
{"nick": "scroogie", "name": "André Gemünd"},
{"nick": "scythetwirler", "name": "Casey X."},
{"nick": "serveurix"},
{"nick": "Shane", "name": "Shane Grant"},
{"nick": "Silk", "name": "Josh Godsiff"},
{"nick": "silure"},
{"nick": "Simikolon", "name": "Yannick & Simon"},
{"nick": "Spahbod", "name": "Omid Davoodi"},
{"nick": "stanislas69", "name": "Stanislas Dolcini"},
{"nick": "Stefan"},
{"nick": "stilz", "name": "Sławomir Zborowski"},
{"nick": "stwf", "name": "Steven Fuchs"},
{"nick": "svott", "name": "Sven Ott"},
{"nick": "t4nk004"},
{"nick": "tbm", "name": "Martin Michlmayr"},
{"nick": "tau"},
{"nick": "Teiresias"},
{"nick": "texane"},
{"nick": "thamlett", "name": "Timothy Hamlett"},
{"nick": "thedrunkyak", "name": "Dan Fuhr"},
{"nick": "TrinityDeath", "name": "Jethro Lu"},
{"nick": "triumvir", "name": "Corin Schedler"},
{"nick": "trompetin17", "name": "Juan Guillermo"},
{"nick": "vladislavbelov", "name": "Vladislav Belov"},
{"nick": "vts", "name": "Jeroen DR"},
{"nick": "WhiteTreePaladin", "name": "Brian Ashley"},
{"nick": "wraitii", "name": "Lancelot de Ferrière le Vayer"},
{"nick": "Xentelian", "name": "Mark Strawson"},
{"nick": "Xienen", "name": "Dayle Flowers"},
{"nick": "xtizer", "name": "Matt Green"},
{"nick": "yashi", "name": "Yasushi Shoji"},
{"nick": "Ykkrosh", "name": "Philip Taylor"},
{"nick": "Yves"},
{"nick": "Zeusthor", "name": "Jeffrey Tavares"},
{"nick": "zoot"},
{"nick": "zsol", "name": "Zsolt Dollenstein"},
{"nick": "Zyi", "name": "Charles De Meulenaer"}
]
}
]
}
Index: ps/trunk/binaries/data/mods/public/gui/loading/loading.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/loading/loading.js (revision 18202)
+++ ps/trunk/binaries/data/mods/public/gui/loading/loading.js (revision 18203)
@@ -1,112 +1,108 @@
let g_Data;
const g_EndPieceWidth = 16;
function init(data)
{
g_Data = data;
// Set to "hourglass" cursor.
Engine.SetCursor("cursor-wait");
// Get tip image and corresponding tip text
let tipTextLoadingArray = Engine.BuildDirEntList("gui/text/tips/", "*.txt", false);
if (tipTextLoadingArray.length > 0)
{
// Set tip text
let tipTextFilePath = tipTextLoadingArray[getRandom(0, tipTextLoadingArray.length-1)];
let tipText = Engine.TranslateLines(Engine.ReadFile(tipTextFilePath));
if (tipText)
{
let index = tipText.indexOf("\n");
let tipTextTitle = tipText.substring(0, index);
let tipTextMessage = tipText.substring(index);
Engine.GetGUIObjectByName("tipTitle").caption = tipTextTitle? tipTextTitle : "";
Engine.GetGUIObjectByName("tipText").caption = tipTextMessage? tipTextMessage : "";
}
// Set tip image
let fileName = tipTextFilePath.substring(tipTextFilePath.lastIndexOf("/")+1).replace(".txt", ".png");
let tipImageFilePath = "loading/tips/" + fileName;
let sprite = "stretched:" + tipImageFilePath;
Engine.GetGUIObjectByName("tipImage").sprite = sprite? sprite : "";
}
else
error("Failed to find any matching tips for the loading screen.");
// janwas: main loop now sets progress / description, but that won't
// happen until the first timeslice completes, so set initial values.
let loadingMapName = Engine.GetGUIObjectByName("loadingMapName");
if (data)
{
let mapName = translate(data.attribs.settings.Name);
switch (data.attribs.mapType)
{
case "skirmish":
case "scenario":
loadingMapName.caption = sprintf(translate("Loading “%(map)s”"), { "map": mapName });
break;
case "random":
loadingMapName.caption = sprintf(translate("Generating “%(map)s”"), { "map": mapName });
break;
default:
error("Unknown map type: " + data.attribs.mapType);
}
}
Engine.GetGUIObjectByName("progressText").caption = "";
Engine.GetGUIObjectByName("progressbar").caption = 0;
// Pick a random quote of the day (each line is a separate tip).
let quoteArray = Engine.ReadFileLines("gui/text/quotes.txt");
Engine.GetGUIObjectByName("quoteText").caption = translate(quoteArray[getRandom(0, quoteArray.length-1)]);
}
function displayProgress()
{
// Make the progessbar finish a little early so that the user can actually see it finish
if (g_Progress >= 100)
return;
// Show 100 when it is really 99
let progress = g_Progress + 1;
Engine.GetGUIObjectByName("progressbar").caption = progress; // display current progress
Engine.GetGUIObjectByName("progressText").caption = progress + "%";
// Displays detailed loading info rather than a percent
// Engine.GetGUIObjectByName("progressText").caption = g_LoadDescription; // display current progess details
// Keep curved right edge of progress bar in sync with the rest of the progress bar
let middle = Engine.GetGUIObjectByName("progressbar");
let rightSide = Engine.GetGUIObjectByName("progressbar_right");
let middleLength = (middle.size.right - middle.size.left) - (g_EndPieceWidth / 2);
let increment = Math.round(progress * middleLength / 100);
let size = rightSide.size;
size.left = increment;
size.right = increment + g_EndPieceWidth;
rightSide.size = size;
}
/**
* This is a reserved function name that is executed by the engine when it is ready
* to start the game (i.e. loading progress has reached 100%).
*/
function reallyStartGame()
{
// Switch GUI from loading screen to game session.
Engine.SwitchGuiPage("page_session.xml", g_Data);
// Restore default cursor.
Engine.SetCursor("arrow-default");
-
- // Notify the other clients that we have finished the loading screen
- if (g_Data.isNetworked && g_Data.isRejoining)
- Engine.SendNetworkRejoined();
}
Index: ps/trunk/source/gui/scripting/ScriptFunctions.cpp
===================================================================
--- ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 18202)
+++ ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 18203)
@@ -1,1125 +1,1117 @@
/* Copyright (C) 2016 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 "scriptinterface/ScriptInterface.h"
#include "graphics/Camera.h"
#include "graphics/FontMetrics.h"
#include "graphics/GameView.h"
#include "graphics/MapReader.h"
#include "graphics/scripting/JSInterface_GameView.h"
#include "gui/GUI.h"
#include "gui/GUIManager.h"
#include "gui/IGUIObject.h"
#include "gui/scripting/JSInterface_GUITypes.h"
#include "i18n/L10n.h"
#include "i18n/scripting/JSInterface_L10n.h"
#include "lib/svn_revision.h"
#include "lib/sysdep/sysdep.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lobby/scripting/JSInterface_Lobby.h"
#include "maths/FixedVector3D.h"
#include "network/NetClient.h"
#include "network/NetServer.h"
#include "network/NetTurnManager.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/Errors.h"
#include "ps/GUID.h"
#include "ps/Game.h"
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/Config.h"
#include "ps/Globals.h" // g_frequencyFilter
#include "ps/Hotkey.h"
#include "ps/ProfileViewer.h"
#include "ps/Pyrogenesis.h"
#include "ps/Replay.h"
#include "ps/SavedGame.h"
#include "ps/UserReport.h"
#include "ps/World.h"
#include "ps/scripting/JSInterface_ConfigDB.h"
#include "ps/scripting/JSInterface_Console.h"
#include "ps/scripting/JSInterface_Mod.h"
#include "ps/scripting/JSInterface_VFS.h"
#include "ps/scripting/JSInterface_VisualReplay.h"
#include "renderer/scripting/JSInterface_Renderer.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpAIManager.h"
#include "simulation2/components/ICmpCommandQueue.h"
#include "simulation2/components/ICmpGuiInterface.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/helpers/Selection.h"
#include "soundmanager/SoundManager.h"
#include "soundmanager/scripting/JSInterface_Sound.h"
#include "tools/atlas/GameInterface/GameLoop.h"
/*
* This file defines a set of functions that are available to GUI scripts, to allow
* interaction with the rest of the engine.
* Functions are exposed to scripts within the global object 'Engine', so
* scripts should call "Engine.FunctionName(...)" etc.
*/
extern void restart_mainloop_in_atlas(); // from main.cpp
extern void EndGame();
extern void kill_mainloop();
namespace {
// Note that the initData argument may only contain clonable data.
// Functions aren't supported for example!
// TODO: Use LOGERROR to print a friendly error message when the requirements aren't met instead of failing with debug_warn when cloning.
void PushGuiPage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue initData)
{
g_GUI->PushPage(name, pCxPrivate->pScriptInterface->WriteStructuredClone(initData));
}
void SwitchGuiPage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue initData)
{
g_GUI->SwitchPage(name, pCxPrivate->pScriptInterface, initData);
}
void PopGuiPage(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_GUI->PopPage();
}
// Note that the args argument may only contain clonable data.
// Functions aren't supported for example!
// TODO: Use LOGERROR to print a friendly error message when the requirements aren't met instead of failing with debug_warn when cloning.
void PopGuiPageCB(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue args)
{
g_GUI->PopPageCB(pCxPrivate->pScriptInterface->WriteStructuredClone(args));
}
JS::Value GuiInterfaceCall(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue data)
{
if (!g_Game)
return JS::UndefinedValue();
CSimulation2* sim = g_Game->GetSimulation2();
ENSURE(sim);
CmpPtr cmpGuiInterface(*sim, SYSTEM_ENTITY);
if (!cmpGuiInterface)
return JS::UndefinedValue();
int player = g_Game->GetPlayerID();
JSContext* cxSim = sim->GetScriptInterface().GetContext();
JSAutoRequest rqSim(cxSim);
JS::RootedValue arg(cxSim, sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), data));
JS::RootedValue ret(cxSim);
cmpGuiInterface->ScriptCall(player, name, arg, &ret);
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(sim->GetScriptInterface(), ret);
}
void PostNetworkCommand(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue cmd)
{
if (!g_Game)
return;
CSimulation2* sim = g_Game->GetSimulation2();
ENSURE(sim);
CmpPtr cmpCommandQueue(*sim, SYSTEM_ENTITY);
if (!cmpCommandQueue)
return;
JSContext* cxSim = sim->GetScriptInterface().GetContext();
JSAutoRequest rqSim(cxSim);
JS::RootedValue cmd2(cxSim, sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), cmd));
cmpCommandQueue->PostNetworkCommand(cmd2);
}
entity_id_t PickEntityAtPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y)
{
return EntitySelection::PickEntityAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID(), false);
}
std::vector PickPlayerEntitiesInRect(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x0, int y0, int x1, int y1, int player)
{
return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player, false);
}
std::vector PickPlayerEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate, int player)
{
return PickPlayerEntitiesInRect(pCxPrivate, 0, 0, g_xres, g_yres, player);
}
std::vector PickNonGaiaEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate)
{
std::vector entities;
CmpPtr cmpPlayerManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (!cmpPlayerManager)
return entities;
i32 numPlayers = cmpPlayerManager->GetNumPlayers();
for (i32 player = 1; player < numPlayers; ++player)
{
std::vector ents = PickPlayerEntitiesOnScreen(pCxPrivate, player);
entities.insert(entities.end(), ents.begin(), ents.end());
}
return entities;
}
std::vector PickSimilarPlayerEntities(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& templateName, bool includeOffScreen, bool matchRank, bool allowFoundations)
{
return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false, allowFoundations);
}
CFixedVector3D GetTerrainAtScreenPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y)
{
CVector3D pos = g_Game->GetView()->GetCamera()->GetWorldCoordinates(x, y, true);
return CFixedVector3D(fixed::FromFloat(pos.X), fixed::FromFloat(pos.Y), fixed::FromFloat(pos.Z));
}
std::wstring SetCursor(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& name)
{
std::wstring old = g_CursorName;
g_CursorName = name;
return old;
}
bool IsVisualReplay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (!g_Game)
return false;
return g_Game->IsVisualReplay();
}
std::wstring GetCurrentReplayDirectory(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (!g_Game)
return std::wstring();
if (g_Game->IsVisualReplay())
return OsPath(g_Game->GetReplayPath()).Parent().Filename().string();
return g_Game->GetReplayLogger().GetDirectory().Filename().string();
}
int GetPlayerID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game)
return g_Game->GetPlayerID();
return -1;
}
void SetPlayerID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int id)
{
if (g_Game)
g_Game->SetPlayerID(id);
}
void SetViewedPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int id)
{
if (g_Game)
g_Game->SetViewedPlayerID(id);
}
JS::Value GetEngineInfo(ScriptInterface::CxPrivate* pCxPrivate)
{
return SavedGames::GetEngineInfo(*(pCxPrivate->pScriptInterface));
}
void StartNetworkGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
ENSURE(g_NetServer);
g_NetServer->StartGame();
}
void StartGame(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue attribs, int playerID)
{
ENSURE(!g_NetServer);
ENSURE(!g_NetClient);
ENSURE(!g_Game);
g_Game = new CGame();
// Convert from GUI script context to sim script context
CSimulation2* sim = g_Game->GetSimulation2();
JSContext* cxSim = sim->GetScriptInterface().GetContext();
JSAutoRequest rqSim(cxSim);
JS::RootedValue gameAttribs(cxSim,
sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), attribs));
g_Game->SetPlayerID(playerID);
g_Game->StartGame(&gameAttribs, "");
}
JS::Value StartSavedGame(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name)
{
// We need to be careful with different compartments and contexts.
// The GUI calls this function from the GUI context and expects the return value in the same context.
// The game we start from here creates another context and expects data in this context.
JSContext* cxGui = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cxGui);
ENSURE(!g_NetServer);
ENSURE(!g_NetClient);
ENSURE(!g_Game);
// Load the saved game data from disk
JS::RootedValue guiContextMetadata(cxGui);
std::string savedState;
Status err = SavedGames::Load(name, *(pCxPrivate->pScriptInterface), &guiContextMetadata, savedState);
if (err < 0)
return JS::UndefinedValue();
g_Game = new CGame();
{
CSimulation2* sim = g_Game->GetSimulation2();
JSContext* cxGame = sim->GetScriptInterface().GetContext();
JSAutoRequest rq(cxGame);
JS::RootedValue gameContextMetadata(cxGame,
sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), guiContextMetadata));
JS::RootedValue gameInitAttributes(cxGame);
sim->GetScriptInterface().GetProperty(gameContextMetadata, "initAttributes", &gameInitAttributes);
int playerID;
sim->GetScriptInterface().GetProperty(gameContextMetadata, "player", playerID);
// Start the game
g_Game->SetPlayerID(playerID);
g_Game->StartGame(&gameInitAttributes, savedState);
}
return guiContextMetadata;
}
void SaveGame(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filename, const std::wstring& description, JS::HandleValue GUIMetadata)
{
shared_ptr GUIMetadataClone = pCxPrivate->pScriptInterface->WriteStructuredClone(GUIMetadata);
if (SavedGames::Save(filename, description, *g_Game->GetSimulation2(), GUIMetadataClone, g_Game->GetPlayerID()) < 0)
LOGERROR("Failed to save game");
}
void SaveGamePrefix(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& prefix, const std::wstring& description, JS::HandleValue GUIMetadata)
{
shared_ptr GUIMetadataClone = pCxPrivate->pScriptInterface->WriteStructuredClone(GUIMetadata);
if (SavedGames::SavePrefix(prefix, description, *g_Game->GetSimulation2(), GUIMetadataClone, g_Game->GetPlayerID()) < 0)
LOGERROR("Failed to save game");
}
void SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue attribs1)
{
ENSURE(g_NetServer);
//TODO: This is a workaround because we need to pass a MutableHandle to a JSAPI functions somewhere
// (with no obvious reason).
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue attribs(cx, attribs1);
g_NetServer->UpdateGameAttributes(&attribs, *(pCxPrivate->pScriptInterface));
}
void StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& playerName)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
g_NetServer = new CNetServer();
if (!g_NetServer->SetupConnection())
{
pCxPrivate->pScriptInterface->ReportError("Failed to start server");
SAFE_DELETE(g_NetServer);
return;
}
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game, true);
g_NetClient->SetUserName(playerName);
if (!g_NetClient->SetupConnection("127.0.0.1"))
{
pCxPrivate->pScriptInterface->ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
}
void StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& playerName, const std::string& serverAddress)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game, false);
g_NetClient->SetUserName(playerName);
if (!g_NetClient->SetupConnection(serverAddress))
{
pCxPrivate->pScriptInterface->ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
}
void DisconnectNetworkGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
// TODO: we ought to do async reliable disconnections
SAFE_DELETE(g_NetServer);
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
std::string GetPlayerGUID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (!g_NetClient)
return std::string();
return g_NetClient->GetGUID();
}
bool KickPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const CStrW& playerName, bool ban)
{
if (!g_NetServer)
return false;
return g_NetServer->KickPlayer(playerName, ban);
}
JS::Value PollNetworkClient(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_NetClient)
return JS::UndefinedValue();
// Convert from net client context to GUI script context
JSContext* cxNet = g_NetClient->GetScriptInterface().GetContext();
JSAutoRequest rqNet(cxNet);
JS::RootedValue pollNet(cxNet);
g_NetClient->GuiPoll(&pollNet);
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), pollNet);
}
void AssignNetworkPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int playerID, const std::string& guid)
{
ENSURE(g_NetServer);
g_NetServer->AssignPlayer(playerID, guid);
}
void SetNetworkPlayerStatus(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& guid, int ready)
{
ENSURE(g_NetServer);
g_NetServer->SetPlayerReady(guid, ready);
}
void ClearAllPlayerReady (ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
ENSURE(g_NetServer);
g_NetServer->ClearAllPlayerReady();
}
void SendNetworkChat(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& message)
{
ENSURE(g_NetClient);
g_NetClient->SendChatMessage(message);
}
void SendNetworkReady(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int message)
{
ENSURE(g_NetClient);
g_NetClient->SendReadyMessage(message);
}
-void SendNetworkRejoined(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
-{
- ENSURE(g_NetClient);
-
- g_NetClient->SendRejoinedMessage();
-}
-
JS::Value GetAIs(ScriptInterface::CxPrivate* pCxPrivate)
{
return ICmpAIManager::GetAIs(*(pCxPrivate->pScriptInterface));
}
JS::Value GetSavedGames(ScriptInterface::CxPrivate* pCxPrivate)
{
return SavedGames::GetSavedGames(*(pCxPrivate->pScriptInterface));
}
bool DeleteSavedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& name)
{
return SavedGames::DeleteSavedGame(name);
}
void OpenURL(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& url)
{
sys_open_url(url);
}
std::wstring GetMatchID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return ps_generate_guid().FromUTF8();
}
void RestartInAtlas(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
restart_mainloop_in_atlas();
}
bool AtlasIsAvailable(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return ATLAS_IsAvailable();
}
bool IsAtlasRunning(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return (g_AtlasGameLoop && g_AtlasGameLoop->running);
}
JS::Value LoadMapSettings(ScriptInterface::CxPrivate* pCxPrivate, const VfsPath& pathname)
{
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
CMapSummaryReader reader;
if (reader.LoadMap(pathname) != PSRETURN_OK)
return JS::UndefinedValue();
JS::RootedValue settings(cx);
reader.GetMapSettings(*(pCxPrivate->pScriptInterface), &settings);
return settings;
}
JS::Value GetMapSettings(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_Game)
return JS::UndefinedValue();
JSContext* cx = g_Game->GetSimulation2()->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
JS::RootedValue mapSettings(cx);
g_Game->GetSimulation2()->GetMapSettings(&mapSettings);
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(
g_Game->GetSimulation2()->GetScriptInterface(),
mapSettings);
}
/**
* Get the current X coordinate of the camera.
*/
float CameraGetX(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game && g_Game->GetView())
return g_Game->GetView()->GetCameraX();
return -1;
}
/**
* Get the current Z coordinate of the camera.
*/
float CameraGetZ(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game && g_Game->GetView())
return g_Game->GetView()->GetCameraZ();
return -1;
}
/**
* Start / stop camera following mode
* @param entityid unit id to follow. If zero, stop following mode
*/
void CameraFollow(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_id_t entityid)
{
if (g_Game && g_Game->GetView())
g_Game->GetView()->CameraFollow(entityid, false);
}
/**
* Start / stop first-person camera following mode
* @param entityid unit id to follow. If zero, stop following mode
*/
void CameraFollowFPS(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_id_t entityid)
{
if (g_Game && g_Game->GetView())
g_Game->GetView()->CameraFollow(entityid, true);
}
/**
* Set the data (position, orientation and zoom) of the camera
*/
void SetCameraData(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_pos_t x, entity_pos_t y, entity_pos_t z, entity_pos_t rotx, entity_pos_t roty, entity_pos_t zoom)
{
// called from JS; must not fail
if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain()))
return;
CVector3D Pos = CVector3D(x.ToFloat(), y.ToFloat(), z.ToFloat());
float RotX = rotx.ToFloat();
float RotY = roty.ToFloat();
float Zoom = zoom.ToFloat();
g_Game->GetView()->SetCamera(Pos, RotX, RotY, Zoom);
}
/// Move camera to a 2D location
void CameraMoveTo(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_pos_t x, entity_pos_t z)
{
// called from JS; must not fail
if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain()))
return;
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CVector3D target;
target.X = x.ToFloat();
target.Z = z.ToFloat();
target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
g_Game->GetView()->MoveCameraTarget(target);
}
entity_id_t GetFollowedEntity(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game && g_Game->GetView())
return g_Game->GetView()->GetFollowedEntity();
return INVALID_ENTITY;
}
bool HotkeyIsPressed_(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& hotkeyName)
{
return HotkeyIsPressed(hotkeyName);
}
void DisplayErrorDialog(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& msg)
{
debug_DisplayError(msg.c_str(), DE_NO_DEBUG_INFO, NULL, NULL, NULL, 0, NULL, NULL);
}
JS::Value GetProfilerState(ScriptInterface::CxPrivate* pCxPrivate)
{
return g_ProfileViewer.SaveToJS(*(pCxPrivate->pScriptInterface));
}
bool IsUserReportEnabled(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return g_UserReporter.IsReportingEnabled();
}
void SetUserReportEnabled(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
{
g_UserReporter.SetReportingEnabled(enabled);
}
std::string GetUserReportStatus(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return g_UserReporter.GetStatus();
}
void SubmitUserReport(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& type, int version, const std::wstring& data)
{
g_UserReporter.SubmitReport(type.c_str(), version, utf8_from_wstring(data));
}
void SetSimRate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float rate)
{
g_Game->SetSimRate(rate);
}
float GetSimRate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return g_Game->GetSimRate();
}
void SetTurnLength(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int length)
{
if (g_NetServer)
g_NetServer->SetTurnLength(length);
else
LOGERROR("Only network host can change turn length");
}
// Focus the game camera on a given position.
void SetCameraTarget(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float x, float y, float z)
{
g_Game->GetView()->ResetCameraTarget(CVector3D(x, y, z));
}
// Deliberately cause the game to crash.
// Currently implemented via access violation (read of address 0).
// Useful for testing the crashlog/stack trace code.
int Crash(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
debug_printf("Crashing at user's request.\n");
return *(volatile int*)0;
}
void DebugWarn(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
debug_warn(L"Warning at user's request.");
}
// Force a JS garbage collection cycle to take place immediately.
// Writes an indication of how long this took to the console.
void ForceGC(ScriptInterface::CxPrivate* pCxPrivate)
{
double time = timer_Time();
JS_GC(pCxPrivate->pScriptInterface->GetJSRuntime());
time = timer_Time() - time;
g_Console->InsertMessage(fmt::sprintf("Garbage collection completed in: %f", time));
}
void DumpSimState(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
OsPath path = psLogDir()/"sim_dump.txt";
std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
g_Game->GetSimulation2()->DumpDebugState(file);
}
void DumpTerrainMipmap(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
VfsPath filename(L"screenshots/terrainmipmap.png");
g_Game->GetWorld()->GetTerrain()->GetHeightMipmap().DumpToDisk(filename);
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER("Terrain mipmap written to '%s'", realPath.string8());
}
void EnableTimeWarpRecording(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), unsigned int numTurns)
{
g_Game->GetTurnManager()->EnableTimeWarpRecording(numTurns);
}
void RewindTimeWarp(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_Game->GetTurnManager()->RewindTimeWarp();
}
void QuickSave(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_Game->GetTurnManager()->QuickSave();
}
void QuickLoad(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_Game->GetTurnManager()->QuickLoad();
}
void SetBoundingBoxDebugOverlay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
{
ICmpSelectable::ms_EnableDebugOverlays = enabled;
}
void Script_EndGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
EndGame();
}
CStrW GetSystemUsername(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return sys_get_user_name();
}
// Cause the game to exit gracefully.
// params:
// returns:
// notes:
// - Exit happens after the current main loop iteration ends
// (since this only sets a flag telling it to end)
void ExitProgram(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
kill_mainloop();
}
// Is the game paused?
bool IsPaused(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_Game)
{
JS_ReportError(pCxPrivate->pScriptInterface->GetContext(), "Game is not started");
return false;
}
return g_Game->m_Paused;
}
// Pause/unpause the game
void SetPaused(ScriptInterface::CxPrivate* pCxPrivate, bool pause)
{
if (!g_Game)
{
JS_ReportError(pCxPrivate->pScriptInterface->GetContext(), "Game is not started");
return;
}
g_Game->m_Paused = pause;
#if CONFIG2_AUDIO
if (g_SoundManager)
g_SoundManager->Pause(pause);
#endif
}
// Return the global frames-per-second value.
// params:
// returns: FPS [int]
// notes:
// - This value is recalculated once a frame. We take special care to
// filter it, so it is both accurate and free of jitter.
int GetFps(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
int freq = 0;
if (g_frequencyFilter)
freq = g_frequencyFilter->StableFrequency();
return freq;
}
JS::Value GetGUIObjectByName(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const CStr& name)
{
IGUIObject* guiObj = g_GUI->FindObjectByName(name);
if (guiObj)
return JS::ObjectValue(*guiObj->GetJSObject());
else
return JS::UndefinedValue();
}
// Return the date/time at which the current executable was compiled.
// params: mode OR an integer specifying
// what to display: -1 for "date time (svn revision)", 0 for date, 1 for time, 2 for svn revision
// returns: string with the requested timestamp info
// notes:
// - Displayed on main menu screen; tells non-programmers which auto-build
// they are running. Could also be determined via .EXE file properties,
// but that's a bit more trouble.
// - To be exact, the date/time returned is when scriptglue.cpp was
// last compiled, but the auto-build does full rebuilds.
// - svn revision is generated by calling svnversion and cached in
// lib/svn_revision.cpp. it is useful to know when attempting to
// reproduce bugs (the main EXE and PDB should be temporarily reverted to
// that revision so that they match user-submitted crashdumps).
std::wstring GetBuildTimestamp(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int mode)
{
char buf[200];
if (mode == -1) // Date, time and revision.
{
UDate dateTime = g_L10n.ParseDateTime(__DATE__ " " __TIME__, "MMM d yyyy HH:mm:ss", Locale::getUS());
std::string dateTimeString = g_L10n.LocalizeDateTime(dateTime, L10n::DateTime, SimpleDateFormat::DATE_TIME);
char svnRevision[32];
sprintf_s(svnRevision, ARRAY_SIZE(svnRevision), "%ls", svn_revision);
if (strcmp(svnRevision, "custom build") == 0)
{
// Translation: First item is a date and time, item between parenthesis is the Subversion revision number of the current build.
sprintf_s(buf, ARRAY_SIZE(buf), g_L10n.Translate("%s (custom build)").c_str(), dateTimeString.c_str());
}
else
{
// Translation: First item is a date and time, item between parenthesis is the Subversion revision number of the current build.
sprintf_s(buf, ARRAY_SIZE(buf), g_L10n.Translate("%s (%ls)").c_str(), dateTimeString.c_str(), svn_revision);
}
}
else if (mode == 0) // Date.
{
UDate dateTime = g_L10n.ParseDateTime(__DATE__, "MMM d yyyy", Locale::getUS());
std::string dateTimeString = g_L10n.LocalizeDateTime(dateTime, L10n::Date, SimpleDateFormat::MEDIUM);
sprintf_s(buf, ARRAY_SIZE(buf), "%s", dateTimeString.c_str());
}
else if (mode == 1) // Time.
{
UDate dateTime = g_L10n.ParseDateTime(__TIME__, "HH:mm:ss", Locale::getUS());
std::string dateTimeString = g_L10n.LocalizeDateTime(dateTime, L10n::Time, SimpleDateFormat::MEDIUM);
sprintf_s(buf, ARRAY_SIZE(buf), "%s", dateTimeString.c_str());
}
else if (mode == 2) // Revision.
{
char svnRevision[32];
sprintf_s(svnRevision, ARRAY_SIZE(svnRevision), "%ls", svn_revision);
if (strcmp(svnRevision, "custom build") == 0)
{
sprintf_s(buf, ARRAY_SIZE(buf), "%s", g_L10n.Translate("custom build").c_str());
}
else
{
sprintf_s(buf, ARRAY_SIZE(buf), "%ls", svn_revision);
}
}
return wstring_from_utf8(buf);
}
JS::Value ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath)
{
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue out(cx);
pCxPrivate->pScriptInterface->ReadJSONFile(filePath, &out);
return out;
}
void WriteJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath, JS::HandleValue val1)
{
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
// TODO: This is a workaround because we need to pass a MutableHandle to StringifyJSON.
JS::RootedValue val(cx, val1);
std::string str(pCxPrivate->pScriptInterface->StringifyJSON(&val, false));
VfsPath path(filePath);
WriteBuffer buf;
buf.Append(str.c_str(), str.length());
g_VFS->CreateFile(path, buf.Data(), buf.Size());
}
bool TemplateExists(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& templateName)
{
return g_GUI->TemplateExists(templateName);
}
CParamNode GetTemplate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& templateName)
{
return g_GUI->GetTemplate(templateName);
}
int GetTextWidth(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const CStr& fontName, const CStrW& text)
{
int width = 0;
int height = 0;
CStrIntern _fontName(fontName);
CFontMetrics fontMetrics(_fontName);
fontMetrics.CalculateStringSize(text.c_str(), width, height);
return width;
}
//-----------------------------------------------------------------------------
// Timer
//-----------------------------------------------------------------------------
// Script profiling functions: Begin timing a piece of code with StartJsTimer(num)
// and stop timing with StopJsTimer(num). The results will be printed to stdout
// when the game exits.
static const size_t MAX_JS_TIMERS = 20;
static TimerUnit js_start_times[MAX_JS_TIMERS];
static TimerUnit js_timer_overhead;
static TimerClient js_timer_clients[MAX_JS_TIMERS];
static wchar_t js_timer_descriptions_buf[MAX_JS_TIMERS * 12]; // depends on MAX_JS_TIMERS and format string below
static void InitJsTimers(ScriptInterface& scriptInterface)
{
wchar_t* pos = js_timer_descriptions_buf;
for(size_t i = 0; i < MAX_JS_TIMERS; i++)
{
const wchar_t* description = pos;
pos += swprintf_s(pos, 12, L"js_timer %d", (int)i)+1;
timer_AddClient(&js_timer_clients[i], description);
}
// call several times to get a good approximation of 'hot' performance.
// note: don't use a separate timer slot to warm up and then judge
// overhead from another: that causes worse results (probably some
// caching effects inside JS, but I don't entirely understand why).
std::wstring calibration_script =
L"Engine.StartXTimer(0);\n" \
L"Engine.StopXTimer (0);\n" \
L"\n";
scriptInterface.LoadGlobalScript("timer_calibration_script", calibration_script);
// slight hack: call LoadGlobalScript twice because we can't average several
// TimerUnit values because there's no operator/. this way is better anyway
// because it hopefully avoids the one-time JS init overhead.
js_timer_clients[0].sum.SetToZero();
scriptInterface.LoadGlobalScript("timer_calibration_script", calibration_script);
js_timer_clients[0].sum.SetToZero();
js_timer_clients[0].num_calls = 0;
}
void StartJsTimer(ScriptInterface::CxPrivate* pCxPrivate, unsigned int slot)
{
ONCE(InitJsTimers(*(pCxPrivate->pScriptInterface)));
if (slot >= MAX_JS_TIMERS)
{
LOGERROR("Exceeded the maximum number of timer slots for scripts!");
return;
}
js_start_times[slot].SetFromTimer();
}
void StopJsTimer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), unsigned int slot)
{
if (slot >= MAX_JS_TIMERS)
{
LOGERROR("Exceeded the maximum number of timer slots for scripts!");
return;
}
TimerUnit now;
now.SetFromTimer();
now.Subtract(js_timer_overhead);
BillingPolicy_Default()(&js_timer_clients[slot], js_start_times[slot], now);
js_start_times[slot].SetToZero();
}
} // namespace
void GuiScriptingInit(ScriptInterface& scriptInterface)
{
JSI_IGUIObject::init(scriptInterface);
JSI_GUITypes::init(scriptInterface);
JSI_GameView::RegisterScriptFunctions(scriptInterface);
JSI_Renderer::RegisterScriptFunctions(scriptInterface);
JSI_Console::RegisterScriptFunctions(scriptInterface);
JSI_ConfigDB::RegisterScriptFunctions(scriptInterface);
JSI_Mod::RegisterScriptFunctions(scriptInterface);
JSI_Sound::RegisterScriptFunctions(scriptInterface);
JSI_L10n::RegisterScriptFunctions(scriptInterface);
JSI_Lobby::RegisterScriptFunctions(scriptInterface);
JSI_VisualReplay::RegisterScriptFunctions(scriptInterface);
// VFS (external)
scriptInterface.RegisterFunction("BuildDirEntList");
scriptInterface.RegisterFunction("FileExists");
scriptInterface.RegisterFunction("GetFileMTime");
scriptInterface.RegisterFunction("GetFileSize");
scriptInterface.RegisterFunction("ReadFile");
scriptInterface.RegisterFunction("ReadFileLines");
// GUI manager functions:
scriptInterface.RegisterFunction("PushGuiPage");
scriptInterface.RegisterFunction("SwitchGuiPage");
scriptInterface.RegisterFunction("PopGuiPage");
scriptInterface.RegisterFunction("PopGuiPageCB");
scriptInterface.RegisterFunction("GetGUIObjectByName");
// Simulation<->GUI interface functions:
scriptInterface.RegisterFunction("GuiInterfaceCall");
scriptInterface.RegisterFunction("PostNetworkCommand");
// Entity picking
scriptInterface.RegisterFunction("PickEntityAtPoint");
scriptInterface.RegisterFunction, int, int, int, int, int, &PickPlayerEntitiesInRect>("PickPlayerEntitiesInRect");
scriptInterface.RegisterFunction, int, &PickPlayerEntitiesOnScreen>("PickPlayerEntitiesOnScreen");
scriptInterface.RegisterFunction, &PickNonGaiaEntitiesOnScreen>("PickNonGaiaEntitiesOnScreen");
scriptInterface.RegisterFunction, std::string, bool, bool, bool, &PickSimilarPlayerEntities>("PickSimilarPlayerEntities");
scriptInterface.RegisterFunction("GetTerrainAtScreenPoint");
// Network / game setup functions
scriptInterface.RegisterFunction("StartNetworkGame");
scriptInterface.RegisterFunction("StartGame");
scriptInterface.RegisterFunction("EndGame");
scriptInterface.RegisterFunction("StartNetworkHost");
scriptInterface.RegisterFunction("StartNetworkJoin");
scriptInterface.RegisterFunction("DisconnectNetworkGame");
scriptInterface.RegisterFunction("GetPlayerGUID");
scriptInterface.RegisterFunction("KickPlayer");
scriptInterface.RegisterFunction("PollNetworkClient");
scriptInterface.RegisterFunction("SetNetworkGameAttributes");
scriptInterface.RegisterFunction("AssignNetworkPlayer");
scriptInterface.RegisterFunction("SetNetworkPlayerStatus");
scriptInterface.RegisterFunction("ClearAllPlayerReady");
scriptInterface.RegisterFunction("SendNetworkChat");
scriptInterface.RegisterFunction("SendNetworkReady");
- scriptInterface.RegisterFunction("SendNetworkRejoined");
scriptInterface.RegisterFunction("GetAIs");
scriptInterface.RegisterFunction("GetEngineInfo");
// Saved games
scriptInterface.RegisterFunction("StartSavedGame");
scriptInterface.RegisterFunction("GetSavedGames");
scriptInterface.RegisterFunction("DeleteSavedGame");
scriptInterface.RegisterFunction("SaveGame");
scriptInterface.RegisterFunction("SaveGamePrefix");
scriptInterface.RegisterFunction("QuickSave");
scriptInterface.RegisterFunction("QuickLoad");
// Misc functions
scriptInterface.RegisterFunction("SetCursor");
scriptInterface.RegisterFunction("IsVisualReplay");
scriptInterface.RegisterFunction("GetCurrentReplayDirectory");
scriptInterface.RegisterFunction("GetPlayerID");
scriptInterface.RegisterFunction("SetPlayerID");
scriptInterface.RegisterFunction("SetViewedPlayer");
scriptInterface.RegisterFunction("OpenURL");
scriptInterface.RegisterFunction("GetMatchID");
scriptInterface.RegisterFunction("RestartInAtlas");
scriptInterface.RegisterFunction("AtlasIsAvailable");
scriptInterface.RegisterFunction("IsAtlasRunning");
scriptInterface.RegisterFunction("LoadMapSettings");
scriptInterface.RegisterFunction("GetMapSettings");
scriptInterface.RegisterFunction("CameraGetX");
scriptInterface.RegisterFunction("CameraGetZ");
scriptInterface.RegisterFunction("CameraFollow");
scriptInterface.RegisterFunction("CameraFollowFPS");
scriptInterface.RegisterFunction("SetCameraData");
scriptInterface.RegisterFunction("CameraMoveTo");
scriptInterface.RegisterFunction("GetFollowedEntity");
scriptInterface.RegisterFunction("HotkeyIsPressed");
scriptInterface.RegisterFunction("DisplayErrorDialog");
scriptInterface.RegisterFunction("GetProfilerState");
scriptInterface.RegisterFunction("Exit");
scriptInterface.RegisterFunction("IsPaused");
scriptInterface.RegisterFunction("SetPaused");
scriptInterface.RegisterFunction("GetFPS");
scriptInterface.RegisterFunction("GetBuildTimestamp");
scriptInterface.RegisterFunction("ReadJSONFile");
scriptInterface.RegisterFunction("WriteJSONFile");
scriptInterface.RegisterFunction("TemplateExists");
scriptInterface.RegisterFunction("GetTemplate");
scriptInterface.RegisterFunction("GetTextWidth");
// User report functions
scriptInterface.RegisterFunction("IsUserReportEnabled");
scriptInterface.RegisterFunction("SetUserReportEnabled");
scriptInterface.RegisterFunction("GetUserReportStatus");
scriptInterface.RegisterFunction("SubmitUserReport");
// Development/debugging functions
scriptInterface.RegisterFunction("StartXTimer");
scriptInterface.RegisterFunction("StopXTimer");
scriptInterface.RegisterFunction("SetSimRate");
scriptInterface.RegisterFunction("GetSimRate");
scriptInterface.RegisterFunction("SetTurnLength");
scriptInterface.RegisterFunction("SetCameraTarget");
scriptInterface.RegisterFunction("Crash");
scriptInterface.RegisterFunction("DebugWarn");
scriptInterface.RegisterFunction("ForceGC");
scriptInterface.RegisterFunction("DumpSimState");
scriptInterface.RegisterFunction("DumpTerrainMipmap");
scriptInterface.RegisterFunction("EnableTimeWarpRecording");
scriptInterface.RegisterFunction("RewindTimeWarp");
scriptInterface.RegisterFunction("SetBoundingBoxDebugOverlay");
scriptInterface.RegisterFunction("GetSystemUsername");
}
Index: ps/trunk/source/network/NetClient.cpp
===================================================================
--- ps/trunk/source/network/NetClient.cpp (revision 18202)
+++ ps/trunk/source/network/NetClient.cpp (revision 18203)
@@ -1,776 +1,780 @@
/* Copyright (C) 2016 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 "NetClient.h"
#include "NetMessage.h"
#include "NetSession.h"
#include "NetTurnManager.h"
#include "lib/byte_order.h"
#include "lib/sysdep/sysdep.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/Compress.h"
#include "ps/CStr.h"
#include "ps/Game.h"
#include "ps/GUID.h"
#include "ps/Loader.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
CNetClient *g_NetClient = NULL;
/**
* Async task for receiving the initial game state when rejoining an
* in-progress network game.
*/
class CNetFileReceiveTask_ClientRejoin : public CNetFileReceiveTask
{
NONCOPYABLE(CNetFileReceiveTask_ClientRejoin);
public:
CNetFileReceiveTask_ClientRejoin(CNetClient& client)
: m_Client(client)
{
}
virtual void OnComplete()
{
// We've received the game state from the server
// Save it so we can use it after the map has finished loading
m_Client.m_JoinSyncBuffer = m_Buffer;
// Pretend the server told us to start the game
CGameStartMessage start;
m_Client.HandleMessage(&start);
}
private:
CNetClient& m_Client;
};
CNetClient::CNetClient(CGame* game, bool isLocalClient) :
m_Session(NULL),
m_UserName(L"anonymous"),
m_GUID(ps_generate_guid()), m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game),
m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetContext()),
m_IsLocalClient(isLocalClient),
- m_LastConnectionCheck(0)
+ m_LastConnectionCheck(0),
+ m_Rejoin(false)
{
m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it
void* context = this;
JS_AddExtraGCRootsTracer(GetScriptInterface().GetJSRuntime(), CNetClient::Trace, this);
// Set up transitions for session
AddTransition(NCS_UNCONNECTED, (uint)NMT_CONNECT_COMPLETE, NCS_CONNECT, (void*)&OnConnect, context);
AddTransition(NCS_CONNECT, (uint)NMT_SERVER_HANDSHAKE, NCS_HANDSHAKE, (void*)&OnHandshake, context);
AddTransition(NCS_HANDSHAKE, (uint)NMT_SERVER_HANDSHAKE_RESPONSE, NCS_AUTHENTICATE, (void*)&OnHandshakeResponse, context);
AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE_RESULT, NCS_INITIAL_GAMESETUP, (void*)&OnAuthenticate, context);
AddTransition(NCS_INITIAL_GAMESETUP, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context);
AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context);
AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context);
AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_PREGAME, (void*)&OnClientTimeout, context);
AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_PREGAME, (void*)&OnClientPerformance, context);
AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
AddTransition(NCS_PREGAME, (uint)NMT_JOIN_SYNC_START, NCS_JOIN_SYNCING, (void*)&OnJoinSyncStart, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CHAT, NCS_JOIN_SYNCING, (void*)&OnChat, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_SETUP, NCS_JOIN_SYNCING, (void*)&OnGameSetup, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_JOIN_SYNCING, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_TIMEOUT, NCS_JOIN_SYNCING, (void*)&OnClientTimeout, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_PERFORMANCE, NCS_JOIN_SYNCING, (void*)&OnClientPerformance, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_START, NCS_JOIN_SYNCING, (void*)&OnGameStart, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_SIMULATION_COMMAND, NCS_JOIN_SYNCING, (void*)&OnInGame, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_END_COMMAND_BATCH, NCS_JOIN_SYNCING, (void*)&OnJoinSyncEndCommandBatch, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
AddTransition(NCS_LOADING, (uint)NMT_CHAT, NCS_LOADING, (void*)&OnChat, context);
AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_LOADING, (uint)NMT_CLIENT_TIMEOUT, NCS_LOADING, (void*)&OnClientTimeout, context);
AddTransition(NCS_LOADING, (uint)NMT_CLIENT_PERFORMANCE, NCS_LOADING, (void*)&OnClientPerformance, context);
AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
AddTransition(NCS_INGAME, (uint)NMT_REJOINED, NCS_INGAME, (void*)&OnRejoined, context);
AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context);
AddTransition(NCS_INGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_INGAME, (void*)&OnClientTimeout, context);
AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_INGAME, (void*)&OnClientPerformance, context);
AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context);
AddTransition(NCS_INGAME, (uint)NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, context);
// Set first state
SetFirstState(NCS_UNCONNECTED);
}
CNetClient::~CNetClient()
{
DestroyConnection();
JS_RemoveExtraGCRootsTracer(GetScriptInterface().GetJSRuntime(), CNetClient::Trace, this);
}
void CNetClient::TraceMember(JSTracer *trc)
{
for (JS::Heap& guiMessage : m_GuiMessageQueue)
JS_CallHeapValueTracer(trc, &guiMessage, "m_GuiMessageQueue");
}
void CNetClient::SetUserName(const CStrW& username)
{
ENSURE(!m_Session); // must be called before we start the connection
m_UserName = username;
}
bool CNetClient::SetupConnection(const CStr& server)
{
CNetClientSession* session = new CNetClientSession(*this);
bool ok = session->Connect(PS_DEFAULT_PORT, server, m_IsLocalClient);
SetAndOwnSession(session);
return ok;
}
void CNetClient::SetAndOwnSession(CNetClientSession* session)
{
delete m_Session;
m_Session = session;
}
void CNetClient::DestroyConnection()
{
// Send network messages from the current frame before connection is destroyed.
if (m_ClientTurnManager)
{
m_ClientTurnManager->OnDestroyConnection(); // End sending of commands for scheduled turn.
Flush(); // Make sure the messages are sent.
}
SAFE_DELETE(m_Session);
}
void CNetClient::Poll()
{
if (!m_Session)
return;
CheckServerConnection();
m_Session->Poll();
}
void CNetClient::CheckServerConnection()
{
// Trigger local warnings if the connection to the server is bad.
// At most once per second.
std::time_t now = std::time(nullptr);
if (now <= m_LastConnectionCheck)
return;
m_LastConnectionCheck = now;
JSContext* cx = GetScriptInterface().GetContext();
// Report if we are losing the connection to the server
u32 lastReceived = m_Session->GetLastReceivedTime();
if (lastReceived > NETWORK_WARNING_TIMEOUT)
{
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-timeout' })", &msg);
GetScriptInterface().SetProperty(msg, "lastReceivedTime", lastReceived);
PushGuiMessage(msg);
return;
}
// Report if we have a bad ping to the server
u32 meanRTT = m_Session->GetMeanRTT();
if (meanRTT > DEFAULT_TURN_LENGTH_MP)
{
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-latency' })", &msg);
GetScriptInterface().SetProperty(msg, "meanRTT", meanRTT);
PushGuiMessage(msg);
}
}
void CNetClient::Flush()
{
if (m_Session)
m_Session->Flush();
}
void CNetClient::GuiPoll(JS::MutableHandleValue ret)
{
if (m_GuiMessageQueue.empty())
{
ret.setUndefined();
return;
}
ret.set(m_GuiMessageQueue.front());
m_GuiMessageQueue.pop_front();
}
void CNetClient::PushGuiMessage(const JS::HandleValue message)
{
ENSURE(!message.isUndefined());
m_GuiMessageQueue.push_back(JS::Heap(message));
}
std::string CNetClient::TestReadGuiMessages()
{
JSContext* cx = GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
std::string r;
JS::RootedValue msg(cx);
while (true)
{
GuiPoll(&msg);
if (msg.isUndefined())
break;
r += GetScriptInterface().ToString(&msg) + "\n";
}
return r;
}
ScriptInterface& CNetClient::GetScriptInterface()
{
return m_Game->GetSimulation2()->GetScriptInterface();
}
void CNetClient::PostPlayerAssignmentsToScript()
{
JSContext* cx = GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({'type':'players', 'hosts':{}})", &msg);
JS::RootedValue hosts(cx);
GetScriptInterface().GetProperty(msg, "hosts", &hosts);
for (const std::pair& p : m_PlayerAssignments)
{
JS::RootedValue host(cx);
GetScriptInterface().Eval("({})", &host);
GetScriptInterface().SetProperty(host, "name", std::wstring(p.second.m_Name), false);
GetScriptInterface().SetProperty(host, "player", p.second.m_PlayerID, false);
GetScriptInterface().SetProperty(host, "status", p.second.m_Status, false);
GetScriptInterface().SetProperty(hosts, p.first.c_str(), host, false);
}
PushGuiMessage(msg);
}
bool CNetClient::SendMessage(const CNetMessage* message)
{
if (!m_Session)
return false;
return m_Session->SendMessage(message);
}
void CNetClient::HandleConnect()
{
Update((uint)NMT_CONNECT_COMPLETE, NULL);
}
void CNetClient::HandleDisconnect(u32 reason)
{
JSContext* cx = GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({'type':'netstatus','status':'disconnected'})", &msg);
GetScriptInterface().SetProperty(msg, "reason", (int)reason, false);
PushGuiMessage(msg);
SAFE_DELETE(m_Session);
// Update the state immediately to UNCONNECTED (don't bother with FSM transitions since
// we'd need one for every single state, and we don't need to use per-state actions)
SetCurrState(NCS_UNCONNECTED);
}
void CNetClient::SendChatMessage(const std::wstring& text)
{
CChatMessage chat;
chat.m_Message = text;
SendMessage(&chat);
}
void CNetClient::SendReadyMessage(const int status)
{
CReadyMessage readyStatus;
readyStatus.m_Status = status;
SendMessage(&readyStatus);
}
void CNetClient::SendRejoinedMessage()
{
CRejoinedMessage rejoinedMessage;
SendMessage(&rejoinedMessage);
}
bool CNetClient::HandleMessage(CNetMessage* message)
{
// Handle non-FSM messages first
Status status = m_Session->GetFileTransferer().HandleMessageReceive(message);
if (status == INFO::OK)
return true;
if (status != INFO::SKIPPED)
return false;
if (message->GetType() == NMT_FILE_TRANSFER_REQUEST)
{
CFileTransferRequestMessage* reqMessage = (CFileTransferRequestMessage*)message;
// TODO: we should support different transfer request types, instead of assuming
// it's always requesting the simulation state
std::stringstream stream;
LOGMESSAGERENDER("Serializing game at turn %u for rejoining player", m_ClientTurnManager->GetCurrentTurn());
u32 turn = to_le32(m_ClientTurnManager->GetCurrentTurn());
stream.write((char*)&turn, sizeof(turn));
bool ok = m_Game->GetSimulation2()->SerializeState(stream);
ENSURE(ok);
// Compress the content with zlib to save bandwidth
// (TODO: if this is still too large, compressing with e.g. LZMA works much better)
std::string compressed;
CompressZLib(stream.str(), compressed, true);
m_Session->GetFileTransferer().StartResponse(reqMessage->m_RequestID, compressed);
return true;
}
// Update FSM
bool ok = Update(message->GetType(), message);
if (!ok)
LOGERROR("Net client: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)GetCurrState());
return ok;
}
void CNetClient::LoadFinished()
{
JSContext* cx = GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
if (!m_JoinSyncBuffer.empty())
{
// We're rejoining a game, and just finished loading the initial map,
// so deserialize the saved game state now
std::string state;
DecompressZLib(m_JoinSyncBuffer, state, true);
std::stringstream stream(state);
u32 turn;
stream.read((char*)&turn, sizeof(turn));
turn = to_le32(turn);
LOGMESSAGE("Rejoining client deserializing state at turn %u\n", turn);
bool ok = m_Game->GetSimulation2()->DeserializeState(stream);
ENSURE(ok);
m_ClientTurnManager->ResetState(turn, turn);
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({'type':'netstatus','status':'join_syncing'})", &msg);
PushGuiMessage(msg);
}
else
{
// Connecting at the start of a game, so we'll wait for other players to finish loading
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({'type':'netstatus','status':'waiting_for_players'})", &msg);
PushGuiMessage(msg);
}
CLoadedGameMessage loaded;
loaded.m_CurrentTurn = m_ClientTurnManager->GetCurrentTurn();
SendMessage(&loaded);
}
bool CNetClient::OnConnect(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_CONNECT_COMPLETE);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'netstatus','status':'connected'})", &msg);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnHandshake(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE);
CNetClient* client = (CNetClient*)context;
CCliHandshakeMessage handshake;
handshake.m_MagicResponse = PS_PROTOCOL_MAGIC_RESPONSE;
handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
client->SendMessage(&handshake);
return true;
}
bool CNetClient::OnHandshakeResponse(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE_RESPONSE);
CNetClient* client = (CNetClient*)context;
CAuthenticateMessage authenticate;
authenticate.m_GUID = client->m_GUID;
authenticate.m_Name = client->m_UserName;
authenticate.m_Password = L""; // TODO
authenticate.m_IsLocalClient = client->m_IsLocalClient;
client->SendMessage(&authenticate);
return true;
}
bool CNetClient::OnAuthenticate(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE_RESULT);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
CAuthenticateResultMessage* message = (CAuthenticateResultMessage*)event->GetParamRef();
LOGMESSAGE("Net: Authentication result: host=%u, %s", message->m_HostID, utf8_from_wstring(message->m_Message));
- bool isRejoining = (message->m_Code == ARC_OK_REJOINING);
-
client->m_HostID = message->m_HostID;
+ client->m_Rejoin = message->m_Code == ARC_OK_REJOINING;
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'netstatus','status':'authenticated'})", &msg);
- client->GetScriptInterface().SetProperty(msg, "rejoining", isRejoining);
+ client->GetScriptInterface().SetProperty(msg, "rejoining", client->m_Rejoin);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnChat(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_CHAT);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
CChatMessage* message = (CChatMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'chat'})", &msg);
client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
client->GetScriptInterface().SetProperty(msg, "text", std::wstring(message->m_Message), false);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnReady(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_READY);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
CReadyMessage* message = (CReadyMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'ready'})", &msg);
client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
client->GetScriptInterface().SetProperty(msg, "status", int (message->m_Status), false);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnGameSetup(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_GAME_SETUP);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
CGameSetupMessage* message = (CGameSetupMessage*)event->GetParamRef();
client->m_GameAttributes = message->m_Data;
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'gamesetup'})", &msg);
client->GetScriptInterface().SetProperty(msg, "data", message->m_Data, false);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnPlayerAssignment(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_PLAYER_ASSIGNMENT);
CNetClient* client = (CNetClient*)context;
CPlayerAssignmentMessage* message = (CPlayerAssignmentMessage*)event->GetParamRef();
// Unpack the message
PlayerAssignmentMap newPlayerAssignments;
for (size_t i = 0; i < message->m_Hosts.size(); ++i)
{
PlayerAssignment assignment;
assignment.m_Enabled = true;
assignment.m_Name = message->m_Hosts[i].m_Name;
assignment.m_PlayerID = message->m_Hosts[i].m_PlayerID;
assignment.m_Status = message->m_Hosts[i].m_Status;
newPlayerAssignments[message->m_Hosts[i].m_GUID] = assignment;
}
client->m_PlayerAssignments.swap(newPlayerAssignments);
client->PostPlayerAssignmentsToScript();
return true;
}
bool CNetClient::OnGameStart(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_GAME_START);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
// Find the player assigned to our GUID
int player = -1;
if (client->m_PlayerAssignments.find(client->m_GUID) != client->m_PlayerAssignments.end())
player = client->m_PlayerAssignments[client->m_GUID].m_PlayerID;
client->m_ClientTurnManager = new CNetClientTurnManager(
*client->m_Game->GetSimulation2(), *client, client->m_HostID, client->m_Game->GetReplayLogger());
client->m_Game->SetPlayerID(player);
client->m_Game->StartGame(&client->m_GameAttributes, "");
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'start'})", &msg);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnJoinSyncStart(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_JOIN_SYNC_START);
CNetClient* client = (CNetClient*)context;
// The server wants us to start downloading the game state from it, so do so
client->m_Session->GetFileTransferer().StartTask(
shared_ptr(new CNetFileReceiveTask_ClientRejoin(*client))
);
return true;
}
bool CNetClient::OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_END_COMMAND_BATCH);
CNetClient* client = (CNetClient*)context;
CEndCommandBatchMessage* endMessage = (CEndCommandBatchMessage*)event->GetParamRef();
client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
// Execute all the received commands for the latest turn
client->m_ClientTurnManager->UpdateFastForward();
return true;
}
bool CNetClient::OnRejoined(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_REJOINED);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
CRejoinedMessage* message = (CRejoinedMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'rejoined'})", &msg);
client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnKicked(void *context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_KICKED);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
CKickedMessage* message = (CKickedMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({})", &msg);
client->GetScriptInterface().SetProperty(msg, "username", message->m_Name);
client->GetScriptInterface().SetProperty(msg, "type", message->m_Ban ? std::string("banned") : std::string("kicked"));
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnClientTimeout(void *context, CFsmEvent* event)
{
// Report the timeout of some other client
ENSURE(event->GetType() == (uint)NMT_CLIENT_TIMEOUT);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
if (client->GetCurrState() == NCS_LOADING)
return true;
CClientTimeoutMessage* message = (CClientTimeoutMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-timeout' })", &msg);
client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID));
client->GetScriptInterface().SetProperty(msg, "lastReceivedTime", message->m_LastReceivedTime);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnClientPerformance(void *context, CFsmEvent* event)
{
// Performance statistics for one or multiple clients
ENSURE(event->GetType() == (uint)NMT_CLIENT_PERFORMANCE);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
if (client->GetCurrState() == NCS_LOADING)
return true;
CClientPerformanceMessage* message = (CClientPerformanceMessage*)event->GetParamRef();
// Display warnings for other clients with bad ping
for (size_t i = 0; i < message->m_Clients.size(); ++i)
{
if (message->m_Clients[i].m_MeanRTT < DEFAULT_TURN_LENGTH_MP || message->m_Clients[i].m_GUID == client->m_GUID)
continue;
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-latency' })", &msg);
client->GetScriptInterface().SetProperty(msg, "guid", message->m_Clients[i].m_GUID);
client->GetScriptInterface().SetProperty(msg, "meanRTT", message->m_Clients[i].m_MeanRTT);
client->PushGuiMessage(msg);
}
return true;
}
bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
// All players have loaded the game - start running the turn manager
// so that the game begins
client->m_Game->SetTurnManager(client->m_ClientTurnManager);
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'netstatus','status':'active'})", &msg);
client->PushGuiMessage(msg);
+ // If we have rejoined an in progress game, send the rejoined message to the server.
+ if (client->m_Rejoin)
+ client->SendRejoinedMessage();
+
return true;
}
bool CNetClient::OnInGame(void *context, CFsmEvent* event)
{
// TODO: should split each of these cases into a separate method
CNetClient* client = (CNetClient*)context;
CNetMessage* message = (CNetMessage*)event->GetParamRef();
if (message)
{
if (message->GetType() == NMT_SIMULATION_COMMAND)
{
CSimulationMessage* simMessage = static_cast (message);
client->m_ClientTurnManager->OnSimulationMessage(simMessage);
}
else if (message->GetType() == NMT_SYNC_ERROR)
{
CSyncErrorMessage* syncMessage = static_cast (message);
client->m_ClientTurnManager->OnSyncError(syncMessage->m_Turn, syncMessage->m_HashExpected, syncMessage->m_PlayerNames);
}
else if (message->GetType() == NMT_END_COMMAND_BATCH)
{
CEndCommandBatchMessage* endMessage = static_cast (message);
client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
}
}
return true;
}
Index: ps/trunk/source/network/NetClient.h
===================================================================
--- ps/trunk/source/network/NetClient.h (revision 18202)
+++ ps/trunk/source/network/NetClient.h (revision 18203)
@@ -1,267 +1,270 @@
/* Copyright (C) 2016 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 NETCLIENT_H
#define NETCLIENT_H
#include "network/fsm.h"
#include "network/NetFileTransfer.h"
#include "network/NetHost.h"
#include "scriptinterface/ScriptVal.h"
#include "ps/CStr.h"
#include
class CGame;
class CNetClientSession;
class CNetClientTurnManager;
class CNetServer;
class ScriptInterface;
// NetClient session FSM states
enum
{
NCS_UNCONNECTED,
NCS_CONNECT,
NCS_HANDSHAKE,
NCS_AUTHENTICATE,
NCS_INITIAL_GAMESETUP,
NCS_PREGAME,
NCS_LOADING,
NCS_JOIN_SYNCING,
NCS_INGAME
};
/**
* Network client.
* This code is run by every player (including the host, if they are not
* a dedicated server).
* It provides an interface between the GUI, the network (via CNetClientSession),
* and the game (via CGame and CNetClientTurnManager).
*/
class CNetClient : public CFsm
{
NONCOPYABLE(CNetClient);
friend class CNetFileReceiveTask_ClientRejoin;
public:
/**
* Construct a client associated with the given game object.
* The game must exist for the lifetime of this object.
*/
CNetClient(CGame* game, bool isLocalClient);
virtual ~CNetClient();
/**
* We assume that adding a tracing function that's only called
* during GC is better for performance than using a
* PersistentRooted where each value needs to be added to
* the root set.
*/
static void Trace(JSTracer *trc, void *data)
{
reinterpret_cast(data)->TraceMember(trc);
}
void TraceMember(JSTracer *trc);
/**
* Set the user's name that will be displayed to all players.
* This must not be called after the connection setup.
*/
void SetUserName(const CStrW& username);
/**
* Returns the GUID of the local client.
* Used for distinguishing observers.
*/
CStr GetGUID() const { return m_GUID; }
/**
* Set up a connection to the remote networked server.
* @param server IP address or host name to connect to
* @return true on success, false on connection failure
*/
bool SetupConnection(const CStr& server);
/**
* Destroy the connection to the server.
* This client probably cannot be used again.
*/
void DestroyConnection();
/**
* Poll the connection for messages from the server and process them, and send
* any queued messages.
* This must be called frequently (i.e. once per frame).
*/
void Poll();
/**
* Locally triggers a GUI message if the connection to the server is being lost or has bad latency.
*/
void CheckServerConnection();
/**
* Flush any queued outgoing network messages.
* This should be called soon after sending a group of messages that may be batched together.
*/
void Flush();
/**
* Retrieves the next queued GUI message, and removes it from the queue.
* The returned value is in the GetScriptInterface() JS context.
*
* This is the only mechanism for the networking code to send messages to
* the GUI - it is pull-based (instead of push) so the engine code does not
* need to know anything about the code structure of the GUI scripts.
*
* The structure of the messages is { "type": "...", ... }
.
* The exact types and associated data are not specified anywhere - the
* implementation and GUI scripts must make the same assumptions.
*
* @return next message, or the value 'undefined' if the queue is empty
*/
void GuiPoll(JS::MutableHandleValue);
/**
* Add a message to the queue, to be read by GuiPoll.
* The script value must be in the GetScriptInterface() JS context.
*/
void PushGuiMessage(const JS::HandleValue message);
/**
* Return a concatenation of all messages in the GUI queue,
* for test cases to easily verify the queue contents.
*/
std::string TestReadGuiMessages();
/**
* Get the script interface associated with this network client,
* which is equivalent to the one used by the CGame in the constructor.
*/
ScriptInterface& GetScriptInterface();
/**
* Send a message to the server.
* @param message message to send
* @return true on success
*/
bool SendMessage(const CNetMessage* message);
/**
* Call when the network connection has been successfully initiated.
*/
void HandleConnect();
/**
* Call when the network connection has been lost.
*/
void HandleDisconnect(u32 reason);
/**
* Call when a message has been received from the network.
*/
bool HandleMessage(CNetMessage* message);
/**
* Call when the game has started and all data files have been loaded,
* to signal to the server that we are ready to begin the game.
*/
void LoadFinished();
void SendChatMessage(const std::wstring& text);
void SendReadyMessage(const int status);
/**
* Call when the client has rejoined a running match and finished
* the loading screen.
*/
void SendRejoinedMessage();
private:
// Net message / FSM transition handlers
static bool OnConnect(void* context, CFsmEvent* event);
static bool OnHandshake(void* context, CFsmEvent* event);
static bool OnHandshakeResponse(void* context, CFsmEvent* event);
static bool OnAuthenticate(void* context, CFsmEvent* event);
static bool OnChat(void* context, CFsmEvent* event);
static bool OnReady(void* context, CFsmEvent* event);
static bool OnGameSetup(void* context, CFsmEvent* event);
static bool OnPlayerAssignment(void* context, CFsmEvent* event);
static bool OnInGame(void* context, CFsmEvent* event);
static bool OnGameStart(void* context, CFsmEvent* event);
static bool OnJoinSyncStart(void* context, CFsmEvent* event);
static bool OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event);
static bool OnRejoined(void* context, CFsmEvent* event);
static bool OnKicked(void* context, CFsmEvent* event);
static bool OnClientTimeout(void* context, CFsmEvent* event);
static bool OnClientPerformance(void* context, CFsmEvent* event);
static bool OnLoadedGame(void* context, CFsmEvent* event);
/**
* Take ownership of a session object, and use it for all network communication.
*/
void SetAndOwnSession(CNetClientSession* session);
/**
* Push a message onto the GUI queue listing the current player assignments.
*/
void PostPlayerAssignmentsToScript();
CGame *m_Game;
CStrW m_UserName;
/// Current network session (or NULL if not connected)
CNetClientSession* m_Session;
/// Turn manager associated with the current game (or NULL if we haven't started the game yet)
CNetClientTurnManager* m_ClientTurnManager;
/// Unique-per-game identifier of this client, used to identify the sender of simulation commands
u32 m_HostID;
+ /// True if the player is currently rejoining or has already rejoined the game.
+ bool m_Rejoin;
+
/// Whether to prevent the client of the host from timing out
bool m_IsLocalClient;
/// Latest copy of game setup attributes heard from the server
JS::PersistentRootedValue m_GameAttributes;
/// Latest copy of player assignments heard from the server
PlayerAssignmentMap m_PlayerAssignments;
/// Globally unique identifier to distinguish users beyond the lifetime of a single network session
CStr m_GUID;
/// Queue of messages for GuiPoll
std::deque> m_GuiMessageQueue;
/// Serialized game state received when joining an in-progress game
std::string m_JoinSyncBuffer;
/// Time when the server was last checked for timeouts and bad latency
std::time_t m_LastConnectionCheck;
};
/// Global network client for the standard game
extern CNetClient *g_NetClient;
#endif // NETCLIENT_H