Index: ps/trunk/source/graphics/MapGenerator.cpp
===================================================================
--- ps/trunk/source/graphics/MapGenerator.cpp (revision 15567)
+++ ps/trunk/source/graphics/MapGenerator.cpp (revision 15568)
@@ -1,316 +1,311 @@
/* Copyright (C) 2014 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 "MapGenerator.h"
#include "lib/timer.h"
#include "ps/CLogger.h"
// TODO: what's a good default? perhaps based on map size
#define RMS_RUNTIME_SIZE 96 * 1024 * 1024
CMapGeneratorWorker::CMapGeneratorWorker()
{
// If something happens before we initialize, that's a failure
m_Progress = -1;
}
CMapGeneratorWorker::~CMapGeneratorWorker()
{
// Wait for thread to end
pthread_join(m_WorkerThread, NULL);
}
void CMapGeneratorWorker::Initialize(const VfsPath& scriptFile, const std::string& settings)
{
CScopeLock lock(m_WorkerMutex);
// Set progress to positive value
m_Progress = 1;
m_ScriptPath = scriptFile;
m_Settings = settings;
// Launch the worker thread
int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this);
ENSURE(ret == 0);
}
void* CMapGeneratorWorker::RunThread(void *data)
{
debug_SetThreadName("MapGenerator");
g_Profiler2.RegisterCurrentThread("MapGenerator");
CMapGeneratorWorker* self = static_cast(data);
self->m_ScriptInterface = new ScriptInterface("RMS", "MapGenerator", ScriptInterface::CreateRuntime(RMS_RUNTIME_SIZE));
// Run map generation scripts
if ((!self->Run()) || (self->m_Progress > 0))
{
// Don't leave progress in an unknown state, if generator failed, set it to -1
CScopeLock lock(self->m_WorkerMutex);
self->m_Progress = -1;
}
// At this point the random map scripts are done running, so the thread has no further purpose
// and can die. The data will be stored in m_MapData already if successful, or m_Progress
// will contain an error value on failure.
return NULL;
}
bool CMapGeneratorWorker::Run()
{
+ JSContext* cx = m_ScriptInterface->GetContext();
+ JSAutoRequest rq(cx);
+
m_ScriptInterface->SetCallbackData(static_cast (this));
// Replace RNG with a seeded deterministic function
m_ScriptInterface->ReplaceNondeterministicRNG(m_MapGenRNG);
m_ScriptInterface->LoadGlobalScripts();
// Functions for RMS
m_ScriptInterface->RegisterFunction("LoadLibrary");
m_ScriptInterface->RegisterFunction("ExportMap");
m_ScriptInterface->RegisterFunction("SetProgress");
m_ScriptInterface->RegisterFunction("MaybeGC");
m_ScriptInterface->RegisterFunction, CMapGeneratorWorker::GetCivData>("GetCivData");
m_ScriptInterface->RegisterFunction("GetTemplate");
m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindTemplates>("FindTemplates");
m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindActorTemplates>("FindActorTemplates");
- // TODO: This code is a bit ugly because we have to ensure that CScriptValRooted gets destroyed before the ScriptInterface.
- // In the future we should work more with the standard JSAPI types for rooting on the stack, which should avoid such problems.
- bool ret = true;
+ // Parse settings
+ JS::RootedValue settingsVal(cx, m_ScriptInterface->ParseJSON(m_Settings).get());
+ if (settingsVal.isUndefined())
{
- // Parse settings
- CScriptValRooted settingsVal = m_ScriptInterface->ParseJSON(m_Settings);
- if (settingsVal.undefined())
- {
- LOGERROR(L"CMapGeneratorWorker::Run: Failed to parse settings");
- ret = false;
- }
- else
- {
- // Init RNG seed
- u32 seed;
- if (!m_ScriptInterface->GetProperty(settingsVal.get(), "Seed", seed))
- { // No seed specified
- LOGWARNING(L"CMapGeneratorWorker::Run: No seed value specified - using 0");
- seed = 0;
- }
+ LOGERROR(L"CMapGeneratorWorker::Run: Failed to parse settings");
+ return false;
+ }
+
+ // Init RNG seed
+ u32 seed;
+ if (!m_ScriptInterface->GetProperty(settingsVal, "Seed", seed))
+ { // No seed specified
+ LOGWARNING(L"CMapGeneratorWorker::Run: No seed value specified - using 0");
+ seed = 0;
+ }
- m_MapGenRNG.seed(seed);
+ m_MapGenRNG.seed(seed);
- // Copy settings to global variable
- if (!m_ScriptInterface->SetProperty(m_ScriptInterface->GetGlobalObject(), "g_MapSettings", settingsVal))
- {
- LOGERROR(L"CMapGeneratorWorker::Run: Failed to define g_MapSettings");
- ret = false;
- }
- else
- {
- // Load RMS
- LOGMESSAGE(L"Loading RMS '%ls'", m_ScriptPath.string().c_str());
- if (!m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath))
- {
- LOGERROR(L"CMapGeneratorWorker::Run: Failed to load RMS '%ls'", m_ScriptPath.string().c_str());
- ret = false;
- }
- }
- }
+ // Copy settings to global variable
+ JS::RootedValue global(cx, m_ScriptInterface->GetGlobalObject());
+ if (!m_ScriptInterface->SetProperty(global, "g_MapSettings", settingsVal))
+ {
+ LOGERROR(L"CMapGeneratorWorker::Run: Failed to define g_MapSettings");
+ return false;
+ }
+
+ // Load RMS
+ LOGMESSAGE(L"Loading RMS '%ls'", m_ScriptPath.string().c_str());
+ if (!m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath))
+ {
+ LOGERROR(L"CMapGeneratorWorker::Run: Failed to load RMS '%ls'", m_ScriptPath.string().c_str());
+ return false;
}
// We must destroy the ScriptInterface in the same thread because the JSAPI requires that!
SAFE_DELETE(m_ScriptInterface);
- return ret;
+ return true;
}
int CMapGeneratorWorker::GetProgress()
{
CScopeLock lock(m_WorkerMutex);
return m_Progress;
}
shared_ptr CMapGeneratorWorker::GetResults()
{
CScopeLock lock(m_WorkerMutex);
return m_MapData;
}
bool CMapGeneratorWorker::LoadLibrary(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
return self->LoadScripts(name);
}
void CMapGeneratorWorker::ExportMap(ScriptInterface::CxPrivate* pCxPrivate, CScriptValRooted data)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
// Copy results
CScopeLock lock(self->m_WorkerMutex);
self->m_MapData = self->m_ScriptInterface->WriteStructuredClone(data.get());
self->m_Progress = 0;
}
void CMapGeneratorWorker::SetProgress(ScriptInterface::CxPrivate* pCxPrivate, int progress)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
// Copy data
CScopeLock lock(self->m_WorkerMutex);
self->m_Progress = progress;
}
void CMapGeneratorWorker::MaybeGC(ScriptInterface::CxPrivate* pCxPrivate)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
self->m_ScriptInterface->MaybeGC();
}
std::vector CMapGeneratorWorker::GetCivData(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
VfsPath path(L"civs/");
VfsPaths pathnames;
std::vector data;
// Load all JSON files in civs directory
Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames);
if (ret == INFO::OK)
{
for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
{
// Load JSON file
CVFSFile file;
PSRETURN ret = file.Load(g_VFS, *it);
if (ret != PSRETURN_OK)
{
LOGERROR(L"CMapGeneratorWorker::GetCivData: Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret));
}
else
{
data.push_back(file.DecodeUTF8()); // assume it's UTF-8
}
}
}
else
{
// Some error reading directory
wchar_t error[200];
LOGERROR(L"CMapGeneratorWorker::GetCivData: Error reading directory '%ls': %ls", path.string().c_str(), StatusDescription(ret, error, ARRAY_SIZE(error)));
}
return data;
}
CParamNode CMapGeneratorWorker::GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, std::string templateName)
{
// TODO: Find a way to validate templates outside of the simulation.
// This should be implemented in TemplateLoader though.
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
const CParamNode& templateRoot = self->m_TemplateLoader.GetTemplateFileData(templateName).GetChild("Entity");
if (!templateRoot.IsOk())
LOGERROR(L"Invalid template found for '%hs'", templateName.c_str());
return templateRoot;
}
std::vector CMapGeneratorWorker::FindTemplates(ScriptInterface::CxPrivate* pCxPrivate, std::string path, bool includeSubdirectories)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
return self->m_TemplateLoader.FindTemplates(path, includeSubdirectories, SIMULATION_TEMPLATES);
}
std::vector CMapGeneratorWorker::FindActorTemplates(ScriptInterface::CxPrivate* pCxPrivate, std::string path, bool includeSubdirectories)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
return self->m_TemplateLoader.FindTemplates(path, includeSubdirectories, ACTOR_TEMPLATES);
}
bool CMapGeneratorWorker::LoadScripts(const std::wstring& libraryName)
{
// Ignore libraries that are already loaded
if (m_LoadedLibraries.find(libraryName) != m_LoadedLibraries.end())
{
return true;
}
// Mark this as loaded, to prevent it recursively loading itself
m_LoadedLibraries.insert(libraryName);
VfsPath path = L"maps/random/" + libraryName + L"/";
VfsPaths pathnames;
// Load all scripts in mapgen directory
Status ret = vfs::GetPathnames(g_VFS, path, L"*.js", pathnames);
if (ret == INFO::OK)
{
for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
{
LOGMESSAGE(L"Loading map generator script '%ls'", it->string().c_str());
if (!m_ScriptInterface->LoadGlobalScriptFile(*it))
{
LOGERROR(L"CMapGeneratorWorker::LoadScripts: Failed to load script '%ls'", it->string().c_str());
return false;
}
}
}
else
{
// Some error reading directory
wchar_t error[200];
LOGERROR(L"CMapGeneratorWorker::LoadScripts: Error reading scripts in directory '%ls': %ls", path.string().c_str(), StatusDescription(ret, error, ARRAY_SIZE(error)));
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
CMapGenerator::CMapGenerator() : m_Worker(new CMapGeneratorWorker())
{
}
CMapGenerator::~CMapGenerator()
{
delete m_Worker;
}
void CMapGenerator::GenerateMap(const VfsPath& scriptFile, const std::string& settings)
{
m_Worker->Initialize(scriptFile, settings);
}
int CMapGenerator::GetProgress()
{
return m_Worker->GetProgress();
}
shared_ptr CMapGenerator::GetResults()
{
return m_Worker->GetResults();
}
Index: ps/trunk/source/graphics/MapReader.cpp
===================================================================
--- ps/trunk/source/graphics/MapReader.cpp (revision 15567)
+++ ps/trunk/source/graphics/MapReader.cpp (revision 15568)
@@ -1,1589 +1,1606 @@
/* Copyright (C) 2013 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 "MapReader.h"
#include "graphics/Camera.h"
#include "graphics/CinemaTrack.h"
#include "graphics/Entity.h"
#include "graphics/GameView.h"
#include "graphics/MapGenerator.h"
#include "graphics/Patch.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "lib/timer.h"
#include "lib/external_libraries/libsdl.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/Loader.h"
#include "ps/LoaderThunks.h"
#include "ps/World.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/PostprocManager.h"
#include "renderer/SkyManager.h"
#include "renderer/WaterManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpObstruction.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpVisual.h"
#include "simulation2/components/ICmpWaterManager.h"
#include
CMapReader::CMapReader()
: xml_reader(0), m_PatchesPerSide(0), m_MapGen(0)
{
cur_terrain_tex = 0; // important - resets generator state
// Maps that don't override the default probably want the old lighting model
//m_LightEnv.SetLightingModel("old");
//pPostproc->SetPostEffect(L"default");
}
// LoadMap: try to load the map from given file; reinitialise the scene to new data if successful
void CMapReader::LoadMap(const VfsPath& pathname, const CScriptValRooted& settings, CTerrain *pTerrain_,
WaterManager* pWaterMan_, SkyManager* pSkyMan_,
CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_,
CSimulation2 *pSimulation2_, const CSimContext* pSimContext_, int playerID_, bool skipEntities)
{
// latch parameters (held until DelayedLoadFinished)
pTerrain = pTerrain_;
pLightEnv = pLightEnv_;
pGameView = pGameView_;
pWaterMan = pWaterMan_;
pSkyMan = pSkyMan_;
pCinema = pCinema_;
pTrigMan = pTrigMan_;
pPostproc = pPostproc_;
pSimulation2 = pSimulation2_;
pSimContext = pSimContext_;
m_PlayerID = playerID_;
m_SkipEntities = skipEntities;
m_StartingCameraTarget = INVALID_ENTITY;
m_ScriptSettings = settings;
filename_xml = pathname.ChangeExtension(L".xml");
// In some cases (particularly tests) we don't want to bother storing a large
// mostly-empty .pmp file, so we let the XML file specify basic terrain instead.
// If there's an .xml file and no .pmp, then we're probably in this XML-only mode
only_xml = false;
if (!VfsFileExists(pathname) && VfsFileExists(filename_xml))
{
only_xml = true;
}
file_format_version = CMapIO::FILE_VERSION; // default if there's no .pmp
if (!only_xml)
{
// [25ms]
unpacker.Read(pathname, "PSMP");
file_format_version = unpacker.GetVersion();
}
// check oldest supported version
if (file_format_version < FILE_READ_VERSION)
throw PSERROR_File_InvalidVersion();
// delete all existing entities
if (pSimulation2)
pSimulation2->ResetState();
// reset post effects
if (pPostproc)
pPostproc->SetPostEffect(L"default");
// load map or script settings script
if (settings.undefined())
RegMemFun(this, &CMapReader::LoadScriptSettings, L"CMapReader::LoadScriptSettings", 50);
else
RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50);
// load player settings script (must be done before reading map)
RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50);
// unpack the data
if (!only_xml)
RegMemFun(this, &CMapReader::UnpackMap, L"CMapReader::UnpackMap", 1200);
// read the corresponding XML file
RegMemFun(this, &CMapReader::ReadXML, L"CMapReader::ReadXML", 50);
// apply terrain data to the world
RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5);
// read entities
RegMemFun(this, &CMapReader::ReadXMLEntities, L"CMapReader::ReadXMLEntities", 5800);
// apply misc data to the world
RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5);
// load map settings script (must be done after reading map)
RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5);
RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5);
}
// LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful
void CMapReader::LoadRandomMap(const CStrW& scriptFile, const CScriptValRooted& settings, CTerrain *pTerrain_,
WaterManager* pWaterMan_, SkyManager* pSkyMan_,
CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_,
CSimulation2 *pSimulation2_, int playerID_)
{
// latch parameters (held until DelayedLoadFinished)
m_ScriptFile = scriptFile;
m_ScriptSettings = settings;
pTerrain = pTerrain_;
pLightEnv = pLightEnv_;
pGameView = pGameView_;
pWaterMan = pWaterMan_;
pSkyMan = pSkyMan_;
pCinema = pCinema_;
pTrigMan = pTrigMan_;
pPostproc = pPostproc_;
pSimulation2 = pSimulation2_;
pSimContext = pSimulation2 ? &pSimulation2->GetSimContext() : NULL;
m_PlayerID = playerID_;
m_SkipEntities = false;
m_StartingCameraTarget = INVALID_ENTITY;
// delete all existing entities
if (pSimulation2)
pSimulation2->ResetState();
only_xml = false;
// copy random map settings (before entity creation)
RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50);
// load player settings script (must be done before reading map)
RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50);
// load map generator with random map script
RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 5000);
// parse RMS results into terrain structure
RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500);
// parse RMS results into environment settings
RegMemFun(this, &CMapReader::ParseEnvironment, L"CMapReader::ParseEnvironment", 5);
// parse RMS results into camera settings
RegMemFun(this, &CMapReader::ParseCamera, L"CMapReader::ParseCamera", 5);
// apply terrain data to the world
RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5);
// parse RMS results into entities
RegMemFun(this, &CMapReader::ParseEntities, L"CMapReader::ParseEntities", 1000);
// apply misc data to the world
RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5);
// load map settings script (must be done after reading map)
RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5);
RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5);
}
// UnpackMap: unpack the given data from the raw data stream into local variables
int CMapReader::UnpackMap()
{
// now unpack everything into local data
int ret = UnpackTerrain();
if (ret != 0) // failed or timed out
{
return ret;
}
return 0;
}
// UnpackTerrain: unpack the terrain from the end of the input data stream
// - data: map size, heightmap, list of textures used by map, texture tile assignments
int CMapReader::UnpackTerrain()
{
// yield after this time is reached. balances increased progress bar
// smoothness vs. slowing down loading.
const double end_time = timer_Time() + 200e-3;
// first call to generator (this is skipped after first call,
// i.e. when the loop below was interrupted)
if (cur_terrain_tex == 0)
{
m_PatchesPerSide = (ssize_t)unpacker.UnpackSize();
// unpack heightmap [600us]
size_t verticesPerSide = m_PatchesPerSide*PATCH_SIZE+1;
m_Heightmap.resize(SQR(verticesPerSide));
unpacker.UnpackRaw(&m_Heightmap[0], SQR(verticesPerSide)*sizeof(u16));
// unpack # textures
num_terrain_tex = unpacker.UnpackSize();
m_TerrainTextures.reserve(num_terrain_tex);
}
// unpack texture names; find handle for each texture.
// interruptible.
while (cur_terrain_tex < num_terrain_tex)
{
CStr texturename;
unpacker.UnpackString(texturename);
ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texturename);
m_TerrainTextures.push_back(texentry);
cur_terrain_tex++;
LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex);
}
// unpack tile data [3ms]
ssize_t tilesPerSide = m_PatchesPerSide*PATCH_SIZE;
m_Tiles.resize(size_t(SQR(tilesPerSide)));
unpacker.UnpackRaw(&m_Tiles[0], sizeof(STileDesc)*m_Tiles.size());
// reset generator state.
cur_terrain_tex = 0;
return 0;
}
int CMapReader::ApplyTerrainData()
{
if (m_PatchesPerSide == 0)
{
// we'll probably crash when trying to use this map later
throw PSERROR_Game_World_MapLoadFailed("Error loading map: no terrain data.\nCheck application log for details.");
}
if (!only_xml)
{
// initialise the terrain
pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]);
// setup the textures on the minipatches
STileDesc* tileptr = &m_Tiles[0];
for (ssize_t j=0; jGetPatch(i,j)->m_MiniPatches[m][k]; // can't fail
mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index];
mp.Priority = tileptr->m_Priority;
tileptr++;
}
}
}
}
}
CmpPtr cmpTerrain(*pSimContext, SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->ReloadTerrain();
return 0;
}
// ApplyData: take all the input data, and rebuild the scene from it
int CMapReader::ApplyData()
{
// copy over the lighting parameters
if (pLightEnv)
*pLightEnv = m_LightEnv;
CmpPtr cmpPlayerManager(*pSimContext, SYSTEM_ENTITY);
if (pGameView && cmpPlayerManager)
{
// Default to global camera (with constraints)
pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus());
// TODO: Starting rotation?
CmpPtr cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID));
if (cmpPlayer && cmpPlayer->HasStartingCamera())
{
// Use player starting camera
CFixedVector3D pos = cmpPlayer->GetStartingCameraPos();
pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()));
}
else if (m_StartingCameraTarget != INVALID_ENTITY)
{
// Point camera at entity
CmpPtr cmpPosition(*pSimContext, m_StartingCameraTarget);
if (cmpPosition)
{
CFixedVector3D pos = cmpPosition->GetPosition();
pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()));
}
}
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname)
{
VfsPath filename_xml = pathname.ChangeExtension(L".xml");
CXeromyces xmb_file;
if (xmb_file.Load(g_VFS, filename_xml) != PSRETURN_OK)
return PSRETURN_File_ReadFailed;
// Define all the relevant elements used in the XML file
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(scenario);
EL(scriptsettings);
#undef AT
#undef EL
XMBElement root = xmb_file.GetRoot();
ENSURE(root.GetNodeName() == el_scenario);
XERO_ITER_EL(root, child)
{
int child_name = child.GetNodeName();
if (child_name == el_scriptsettings)
{
m_ScriptSettings = child.GetText();
}
}
return PSRETURN_OK;
}
CScriptValRooted CMapSummaryReader::GetMapSettings(ScriptInterface& scriptInterface)
{
- CScriptValRooted data;
- scriptInterface.Eval("({})", data);
+ JSContext* cx = scriptInterface.GetContext();
+ JSAutoRequest rq(cx);
+
+ JS::RootedValue data(cx);
+ scriptInterface.Eval("({})", &data);
if (!m_ScriptSettings.empty())
- scriptInterface.SetProperty(data.get(), "settings", scriptInterface.ParseJSON(m_ScriptSettings), false);
- return data;
+ scriptInterface.SetProperty(data, "settings", scriptInterface.ParseJSON(m_ScriptSettings), false);
+ return CScriptValRooted(cx, data);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Holds various state data while reading maps, so that loading can be
// interrupted (e.g. to update the progress display) then later resumed.
class CXMLReader
{
NONCOPYABLE(CXMLReader);
public:
CXMLReader(const VfsPath& xml_filename, CMapReader& mapReader)
: m_MapReader(mapReader)
{
Init(xml_filename);
}
CStr ReadScriptSettings();
// read everything except for entities
void ReadXML();
// return semantics: see Loader.cpp!LoadFunc.
int ProgressiveReadEntities();
private:
CXeromyces xmb_file;
CMapReader& m_MapReader;
int el_entity;
int el_tracks;
int el_template, el_player;
int el_position, el_orientation, el_obstruction;
int el_actor;
int at_x, at_y, at_z;
int at_group, at_group2;
int at_id;
int at_angle;
int at_uid;
int at_seed;
XMBElementList nodes; // children of root
// loop counters
int node_idx;
int entity_idx;
// # entities+nonentities processed and total (for progress calc)
int completed_jobs, total_jobs;
// maximum used entity ID, so we can safely allocate new ones
entity_id_t max_uid;
void Init(const VfsPath& xml_filename);
void ReadTerrain(XMBElement parent);
void ReadEnvironment(XMBElement parent);
void ReadCamera(XMBElement parent);
void ReadCinema(XMBElement parent);
void ReadTriggers(XMBElement parent);
int ReadEntities(XMBElement parent, double end_time);
};
void CXMLReader::Init(const VfsPath& xml_filename)
{
// must only assign once, so do it here
node_idx = entity_idx = 0;
if (xmb_file.Load(g_VFS, xml_filename) != PSRETURN_OK)
throw PSERROR_File_ReadFailed();
// define the elements and attributes that are frequently used in the XML file,
// so we don't need to do lots of string construction and comparison when
// reading the data.
// (Needs to be synchronised with the list in CXMLReader - ugh)
#define EL(x) el_##x = xmb_file.GetElementID(#x)
#define AT(x) at_##x = xmb_file.GetAttributeID(#x)
EL(entity);
EL(tracks);
EL(template);
EL(player);
EL(position);
EL(orientation);
EL(obstruction);
EL(actor);
AT(x); AT(y); AT(z);
AT(group); AT(group2);
AT(angle);
AT(uid);
AT(seed);
#undef AT
#undef EL
XMBElement root = xmb_file.GetRoot();
ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario");
nodes = root.GetChildNodes();
// find out total number of entities+nonentities
// (used when calculating progress)
completed_jobs = 0;
total_jobs = 0;
for (int i = 0; i < nodes.Count; i++)
total_jobs += nodes.Item(i).GetChildNodes().Count;
// Find the maximum entity ID, so we can safely allocate new IDs without conflicts
max_uid = SYSTEM_ENTITY;
XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities"));
XERO_ITER_EL(ents, ent)
{
CStr uid = ent.GetAttributes().GetNamedItem(at_uid);
max_uid = std::max(max_uid, (entity_id_t)uid.ToUInt());
}
}
CStr CXMLReader::ReadScriptSettings()
{
XMBElement root = xmb_file.GetRoot();
ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario");
nodes = root.GetChildNodes();
XMBElement settings = nodes.GetFirstNamedItem(xmb_file.GetElementID("ScriptSettings"));
return settings.GetText();
}
void CXMLReader::ReadTerrain(XMBElement parent)
{
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
AT(patches);
AT(texture);
AT(priority);
AT(height);
#undef AT
ssize_t patches = 9;
CStr texture = "grass1_spring";
int priority = 0;
u16 height = 16384;
XERO_ITER_ATTR(parent, attr)
{
if (attr.Name == at_patches)
patches = attr.Value.ToInt();
else if (attr.Name == at_texture)
texture = attr.Value;
else if (attr.Name == at_priority)
priority = attr.Value.ToInt();
else if (attr.Name == at_height)
height = (u16)attr.Value.ToInt();
}
m_MapReader.m_PatchesPerSide = patches;
// Load the texture
ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texture);
m_MapReader.pTerrain->Initialize(patches, NULL);
// Fill the heightmap
u16* heightmap = m_MapReader.pTerrain->GetHeightMap();
ssize_t verticesPerSide = m_MapReader.pTerrain->GetVerticesPerSide();
for (ssize_t i = 0; i < SQR(verticesPerSide); ++i)
heightmap[i] = height;
// Fill the texture map
for (ssize_t pz = 0; pz < patches; ++pz)
{
for (ssize_t px = 0; px < patches; ++px)
{
CPatch* patch = m_MapReader.pTerrain->GetPatch(px, pz); // can't fail
for (ssize_t z = 0; z < PATCH_SIZE; ++z)
{
for (ssize_t x = 0; x < PATCH_SIZE; ++x)
{
patch->m_MiniPatches[z][x].Tex = texentry;
patch->m_MiniPatches[z][x].Priority = priority;
}
}
}
}
}
void CXMLReader::ReadEnvironment(XMBElement parent)
{
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(lightingmodel);
EL(posteffect);
EL(skyset);
EL(suncolour);
EL(sunelevation);
EL(sunrotation);
EL(terrainambientcolour);
EL(unitsambientcolour);
EL(water);
EL(waterbody);
EL(type);
EL(colour);
EL(tint);
EL(height);
EL(shininess); // for compatibility
EL(waviness);
EL(murkiness);
EL(windangle);
EL(reflectiontint); // for compatibility
EL(reflectiontintstrength); // for compatibility
EL(fog);
EL(fogcolour);
EL(fogfactor);
EL(fogthickness);
EL(postproc);
EL(brightness);
EL(contrast);
EL(saturation);
EL(bloom);
AT(r); AT(g); AT(b);
#undef AT
#undef EL
XERO_ITER_EL(parent, element)
{
int element_name = element.GetNodeName();
XMBAttributeList attrs = element.GetAttributes();
if (element_name == el_lightingmodel)
{
// NOP - obsolete.
}
else if (element_name == el_skyset)
{
if (m_MapReader.pSkyMan)
m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8());
}
else if (element_name == el_suncolour)
{
m_MapReader.m_LightEnv.m_SunColor = RGBColor(
attrs.GetNamedItem(at_r).ToFloat(),
attrs.GetNamedItem(at_g).ToFloat(),
attrs.GetNamedItem(at_b).ToFloat());
}
else if (element_name == el_sunelevation)
{
m_MapReader.m_LightEnv.m_Elevation = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_sunrotation)
{
m_MapReader.m_LightEnv.m_Rotation = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_terrainambientcolour)
{
m_MapReader.m_LightEnv.m_TerrainAmbientColor = RGBColor(
attrs.GetNamedItem(at_r).ToFloat(),
attrs.GetNamedItem(at_g).ToFloat(),
attrs.GetNamedItem(at_b).ToFloat());
}
else if (element_name == el_unitsambientcolour)
{
m_MapReader.m_LightEnv.m_UnitsAmbientColor = RGBColor(
attrs.GetNamedItem(at_r).ToFloat(),
attrs.GetNamedItem(at_g).ToFloat(),
attrs.GetNamedItem(at_b).ToFloat());
}
else if (element_name == el_fog)
{
XERO_ITER_EL(element, fog)
{
int element_name = fog.GetNodeName();
if (element_name == el_fogcolour)
{
XMBAttributeList attrs = fog.GetAttributes();
m_MapReader.m_LightEnv.m_FogColor = RGBColor(
attrs.GetNamedItem(at_r).ToFloat(),
attrs.GetNamedItem(at_g).ToFloat(),
attrs.GetNamedItem(at_b).ToFloat());
}
else if (element_name == el_fogfactor)
{
m_MapReader.m_LightEnv.m_FogFactor = fog.GetText().ToFloat();
}
else if (element_name == el_fogthickness)
{
m_MapReader.m_LightEnv.m_FogMax = fog.GetText().ToFloat();
}
}
}
else if (element_name == el_postproc)
{
XERO_ITER_EL(element, postproc)
{
int element_name = postproc.GetNodeName();
if (element_name == el_brightness)
{
m_MapReader.m_LightEnv.m_Brightness = postproc.GetText().ToFloat();
}
else if (element_name == el_contrast)
{
m_MapReader.m_LightEnv.m_Contrast = postproc.GetText().ToFloat();
}
else if (element_name == el_saturation)
{
m_MapReader.m_LightEnv.m_Saturation = postproc.GetText().ToFloat();
}
else if (element_name == el_bloom)
{
m_MapReader.m_LightEnv.m_Bloom = postproc.GetText().ToFloat();
}
else if (element_name == el_posteffect)
{
if (m_MapReader.pPostproc)
m_MapReader.pPostproc->SetPostEffect(postproc.GetText().FromUTF8());
}
}
}
else if (element_name == el_water)
{
XERO_ITER_EL(element, waterbody)
{
ENSURE(waterbody.GetNodeName() == el_waterbody);
XERO_ITER_EL(waterbody, waterelement)
{
int element_name = waterelement.GetNodeName();
if (element_name == el_height)
{
CmpPtr cmpWaterManager(*m_MapReader.pSimContext, SYSTEM_ENTITY);
ENSURE(cmpWaterManager);
cmpWaterManager->SetWaterLevel(entity_pos_t::FromString(waterelement.GetText()));
continue;
}
// The rest are purely graphical effects, and should be ignored if
// graphics are disabled
if (!m_MapReader.pWaterMan)
continue;
if (element_name == el_type)
{
if (waterelement.GetText() == "default")
m_MapReader.pWaterMan->m_WaterType = L"ocean";
else
m_MapReader.pWaterMan->m_WaterType = waterelement.GetText().FromUTF8();
}
else if (element_name == el_shininess || element_name == el_reflectiontint || element_name == el_reflectiontintstrength)
{
// deprecated.
}
#define READ_COLOUR(el, out) \
else if (element_name == el) \
{ \
XMBAttributeList attrs = waterelement.GetAttributes(); \
out = CColor( \
attrs.GetNamedItem(at_r).ToFloat(), \
attrs.GetNamedItem(at_g).ToFloat(), \
attrs.GetNamedItem(at_b).ToFloat(), \
1.f); \
}
#define READ_FLOAT(el, out) \
else if (element_name == el) \
{ \
out = waterelement.GetText().ToFloat(); \
} \
READ_COLOUR(el_colour, m_MapReader.pWaterMan->m_WaterColor)
READ_COLOUR(el_tint, m_MapReader.pWaterMan->m_WaterTint)
READ_FLOAT(el_waviness, m_MapReader.pWaterMan->m_Waviness)
READ_FLOAT(el_murkiness, m_MapReader.pWaterMan->m_Murkiness)
READ_FLOAT(el_windangle, m_MapReader.pWaterMan->m_WindAngle)
#undef READ_FLOAT
#undef READ_COLOUR
else
debug_warn(L"Invalid map XML data");
}
}
}
else
debug_warn(L"Invalid map XML data");
}
m_MapReader.m_LightEnv.CalculateSunDirection();
}
void CXMLReader::ReadCamera(XMBElement parent)
{
// defaults if we don't find player starting camera
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(declination);
EL(rotation);
EL(position);
AT(angle);
AT(x); AT(y); AT(z);
#undef AT
#undef EL
float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f);
CVector3D translation = CVector3D(100, 150, -100);
XERO_ITER_EL(parent, element)
{
int element_name = element.GetNodeName();
XMBAttributeList attrs = element.GetAttributes();
if (element_name == el_declination)
{
declination = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_rotation)
{
rotation = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_position)
{
translation = CVector3D(
attrs.GetNamedItem(at_x).ToFloat(),
attrs.GetNamedItem(at_y).ToFloat(),
attrs.GetNamedItem(at_z).ToFloat());
}
else
debug_warn(L"Invalid map XML data");
}
if (m_MapReader.pGameView)
{
m_MapReader.pGameView->GetCamera()->m_Orientation.SetXRotation(declination);
m_MapReader.pGameView->GetCamera()->m_Orientation.RotateY(rotation);
m_MapReader.pGameView->GetCamera()->m_Orientation.Translate(translation);
m_MapReader.pGameView->GetCamera()->UpdateFrustum();
}
}
void CXMLReader::ReadCinema(XMBElement parent)
{
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(path);
EL(rotation);
EL(distortion);
EL(node);
EL(position);
EL(time);
AT(name);
AT(timescale);
AT(mode);
AT(style);
AT(growth);
AT(switch);
AT(x);
AT(y);
AT(z);
#undef EL
#undef AT
std::map pathList;
XERO_ITER_EL(parent, element)
{
int elementName = element.GetNodeName();
if ( elementName == el_path )
{
XMBAttributeList attrs = element.GetAttributes();
CStrW name(attrs.GetNamedItem(at_name).FromUTF8());
float timescale = attrs.GetNamedItem(at_timescale).ToFloat();
CCinemaData pathData;
pathData.m_Timescale = timescale;
TNSpline spline, backwardSpline;
XERO_ITER_EL(element, pathChild)
{
elementName = pathChild.GetNodeName();
attrs = pathChild.GetAttributes();
//Load distortion attributes
if ( elementName == el_distortion )
{
pathData.m_Mode = attrs.GetNamedItem(at_mode).ToInt();
pathData.m_Style = attrs.GetNamedItem(at_style).ToInt();
pathData.m_Growth = attrs.GetNamedItem(at_growth).ToInt();
pathData.m_Switch = attrs.GetNamedItem(at_switch).ToInt();
}
//Load node data used for spline
else if ( elementName == el_node )
{
SplineData data;
XERO_ITER_EL(pathChild, nodeChild)
{
elementName = nodeChild.GetNodeName();
attrs = nodeChild.GetAttributes();
//Fix?: assumes that time is last element
if ( elementName == el_position )
{
data.Position.X = attrs.GetNamedItem(at_x).ToFloat();
data.Position.Y = attrs.GetNamedItem(at_y).ToFloat();
data.Position.Z = attrs.GetNamedItem(at_z).ToFloat();
continue;
}
else if ( elementName == el_rotation )
{
data.Rotation.X = attrs.GetNamedItem(at_x).ToFloat();
data.Rotation.Y = attrs.GetNamedItem(at_y).ToFloat();
data.Rotation.Z = attrs.GetNamedItem(at_z).ToFloat();
continue;
}
else if ( elementName == el_time )
data.Distance = nodeChild.GetText().ToFloat();
else
debug_warn(L"Invalid cinematic element for node child");
backwardSpline.AddNode(data.Position, data.Rotation, data.Distance);
}
}
else
debug_warn(L"Invalid cinematic element for path child");
}
//Construct cinema path with data gathered
CCinemaPath temp(pathData, backwardSpline);
const std::vector& nodes = temp.GetAllNodes();
if ( nodes.empty() )
{
debug_warn(L"Failure loading cinematics");
return;
}
for ( std::vector::const_reverse_iterator it = nodes.rbegin();
it != nodes.rend(); ++it )
{
spline.AddNode(it->Position, it->Rotation, it->Distance);
}
CCinemaPath path(pathData, spline);
pathList[name] = path;
}
else
ENSURE("Invalid cinema child");
}
if (m_MapReader.pCinema)
m_MapReader.pCinema->SetAllPaths(pathList);
}
void CXMLReader::ReadTriggers(XMBElement UNUSED(parent))
{
}
int CXMLReader::ReadEntities(XMBElement parent, double end_time)
{
XMBElementList entities = parent.GetChildNodes();
ENSURE(m_MapReader.pSimulation2);
CSimulation2& sim = *m_MapReader.pSimulation2;
CmpPtr cmpPlayerManager(sim, SYSTEM_ENTITY);
while (entity_idx < entities.Count)
{
// all new state at this scope and below doesn't need to be
// wrapped, since we only yield after a complete iteration.
XMBElement entity = entities.Item(entity_idx++);
ENSURE(entity.GetNodeName() == el_entity);
XMBAttributeList attrs = entity.GetAttributes();
CStr uid = attrs.GetNamedItem(at_uid);
ENSURE(!uid.empty());
int EntityUid = uid.ToInt();
CStrW TemplateName;
int PlayerID = 0;
CFixedVector3D Position;
CFixedVector3D Orientation;
long Seed = -1;
// Obstruction control groups.
entity_id_t ControlGroup = INVALID_ENTITY;
entity_id_t ControlGroup2 = INVALID_ENTITY;
XERO_ITER_EL(entity, setting)
{
int element_name = setting.GetNodeName();
//
if (element_name == el_template)
{
TemplateName = setting.GetText().FromUTF8();
}
//
else if (element_name == el_player)
{
PlayerID = setting.GetText().ToInt();
}
//
else if (element_name == el_position)
{
XMBAttributeList attrs = setting.GetAttributes();
Position = CFixedVector3D(
fixed::FromString(attrs.GetNamedItem(at_x)),
fixed::FromString(attrs.GetNamedItem(at_y)),
fixed::FromString(attrs.GetNamedItem(at_z)));
}
//
else if (element_name == el_orientation)
{
XMBAttributeList attrs = setting.GetAttributes();
Orientation = CFixedVector3D(
fixed::FromString(attrs.GetNamedItem(at_x)),
fixed::FromString(attrs.GetNamedItem(at_y)),
fixed::FromString(attrs.GetNamedItem(at_z)));
// TODO: what happens if some attributes are missing?
}
//
else if (element_name == el_obstruction)
{
XMBAttributeList attrs = setting.GetAttributes();
ControlGroup = attrs.GetNamedItem(at_group).ToInt();
ControlGroup2 = attrs.GetNamedItem(at_group2).ToInt();
}
//
else if (element_name == el_actor)
{
XMBAttributeList attrs = setting.GetAttributes();
CStr seedStr = attrs.GetNamedItem(at_seed);
if (!seedStr.empty())
{
Seed = seedStr.ToLong();
ENSURE(Seed >= 0);
}
}
else
debug_warn(L"Invalid map XML data");
}
entity_id_t ent = sim.AddEntity(TemplateName, EntityUid);
entity_id_t player = cmpPlayerManager->GetPlayerByID(PlayerID);
if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
{ // Don't add entities with invalid player IDs
LOGERROR(L"Failed to load entity template '%ls'", TemplateName.c_str());
}
else
{
CmpPtr cmpPosition(sim, ent);
if (cmpPosition)
{
cmpPosition->JumpTo(Position.X, Position.Z);
cmpPosition->SetYRotation(Orientation.Y);
// TODO: other parts of the position
}
CmpPtr cmpOwnership(sim, ent);
if (cmpOwnership)
cmpOwnership->SetOwner(PlayerID);
CmpPtr cmpObstruction(sim, ent);
if (cmpObstruction)
{
if (ControlGroup != INVALID_ENTITY)
cmpObstruction->SetControlGroup(ControlGroup);
if (ControlGroup2 != INVALID_ENTITY)
cmpObstruction->SetControlGroup2(ControlGroup2);
cmpObstruction->ResolveFoundationCollisions();
}
CmpPtr cmpVisual(sim, ent);
if (cmpVisual)
{
if (Seed != -1)
cmpVisual->SetActorSeed((u32)Seed);
// TODO: variation/selection strings
}
if (PlayerID == m_MapReader.m_PlayerID && (boost::algorithm::ends_with(TemplateName, L"civil_centre") || m_MapReader.m_StartingCameraTarget == INVALID_ENTITY))
{
// Focus on civil centre or first entity owned by player
m_MapReader.m_StartingCameraTarget = ent;
}
}
completed_jobs++;
LDR_CHECK_TIMEOUT(completed_jobs, total_jobs);
}
return 0;
}
void CXMLReader::ReadXML()
{
for (int i = 0; i < nodes.Count; ++i)
{
XMBElement node = nodes.Item(i);
CStr name = xmb_file.GetElementString(node.GetNodeName());
if (name == "Terrain")
{
ReadTerrain(node);
}
else if (name == "Environment")
{
ReadEnvironment(node);
}
else if (name == "Camera")
{
ReadCamera(node);
}
else if (name == "ScriptSettings")
{
// Already loaded - this is to prevent an assertion
}
else if (name == "Entities")
{
// Handled by ProgressiveReadEntities instead
}
else if (name == "Paths")
{
ReadCinema(node);
}
else if (name == "Triggers")
{
ReadTriggers(node);
}
else if (name == "Script")
{
if (m_MapReader.pSimulation2)
m_MapReader.pSimulation2->SetStartupScript(node.GetText());
}
else
{
debug_printf(L"Invalid XML element in map file: %hs\n", name.c_str());
debug_warn(L"Invalid map XML data");
}
}
}
int CXMLReader::ProgressiveReadEntities()
{
// yield after this time is reached. balances increased progress bar
// smoothness vs. slowing down loading.
const double end_time = timer_Time() + 200e-3;
int ret;
while (node_idx < nodes.Count)
{
XMBElement node = nodes.Item(node_idx);
CStr name = xmb_file.GetElementString(node.GetNodeName());
if (name == "Entities")
{
if (!m_MapReader.m_SkipEntities)
{
ret = ReadEntities(node, end_time);
if (ret != 0) // error or timed out
return ret;
}
}
node_idx++;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// load script settings from map
int CMapReader::LoadScriptSettings()
{
if (!xml_reader)
xml_reader = new CXMLReader(filename_xml, *this);
// parse the script settings
if (pSimulation2)
pSimulation2->SetMapSettings(xml_reader->ReadScriptSettings());
return 0;
}
// load player settings script
int CMapReader::LoadPlayerSettings()
{
if (pSimulation2)
pSimulation2->LoadPlayerSettings(true);
return 0;
}
// load map settings script
int CMapReader::LoadMapSettings()
{
if (pSimulation2)
pSimulation2->LoadMapSettings();
return 0;
}
int CMapReader::ReadXML()
{
if (!xml_reader)
xml_reader = new CXMLReader(filename_xml, *this);
xml_reader->ReadXML();
return 0;
}
// progressive
int CMapReader::ReadXMLEntities()
{
if (!xml_reader)
xml_reader = new CXMLReader(filename_xml, *this);
int ret = xml_reader->ProgressiveReadEntities();
// finished or failed
if (ret <= 0)
{
SAFE_DELETE(xml_reader);
}
return ret;
}
int CMapReader::DelayLoadFinished()
{
// we were dynamically allocated by CWorld::Initialize
delete this;
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int CMapReader::LoadRMSettings()
{
// copy random map settings over to sim
ENSURE(pSimulation2);
pSimulation2->SetMapSettings(m_ScriptSettings);
return 0;
}
int CMapReader::GenerateMap()
{
if (!m_MapGen)
{
// Initialize map generator
m_MapGen = new CMapGenerator();
VfsPath scriptPath;
if (m_ScriptFile.length())
scriptPath = L"maps/random/"+m_ScriptFile;
// Stringify settings to pass across threads
std::string scriptSettings = pSimulation2->GetScriptInterface().StringifyJSON(m_ScriptSettings.get());
// Try to generate map
m_MapGen->GenerateMap(scriptPath, scriptSettings);
}
// Check status
int progress = m_MapGen->GetProgress();
if (progress < 0)
{
// RMS failed - return to main menu
throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
}
else if (progress == 0)
{
// Finished, get results as StructuredClone object, which must be read to obtain the JS val
shared_ptr results = m_MapGen->GetResults();
// Parse data into simulation context
CScriptValRooted data(pSimulation2->GetScriptInterface().GetContext(), pSimulation2->GetScriptInterface().ReadStructuredClone(results));
if (data.undefined())
{
// RMS failed - return to main menu
throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
}
else
{
m_MapData = data;
}
}
else
{
// Still working
// Sleep for a while, slowing down the rendering thread
// to allow more CPU for the map generator thread
SDL_Delay(100);
}
// return progress
return progress;
};
int CMapReader::ParseTerrain()
{
TIMER(L"ParseTerrain");
-
+ JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
+ JSAutoRequest rq(cx);
+
// parse terrain from map data
// an error here should stop the loading process
#define GET_TERRAIN_PROPERTY(val, prop, out)\
if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
{ LOGERROR(L"CMapReader::ParseTerrain() failed to get '%hs' property", #prop);\
throw PSERROR_Game_World_MapLoadFailed("Error parsing terrain data.\nCheck application log for details"); }
+ JS::RootedValue tmpMapData(cx, m_MapData.get()); // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
u32 size;
- GET_TERRAIN_PROPERTY(m_MapData.get(), size, size)
+ GET_TERRAIN_PROPERTY(tmpMapData, size, size)
m_PatchesPerSide = size / PATCH_SIZE;
// flat heightmap of u16 data
- GET_TERRAIN_PROPERTY(m_MapData.get(), height, m_Heightmap)
+ GET_TERRAIN_PROPERTY(tmpMapData, height, m_Heightmap)
// load textures
std::vector textureNames;
- GET_TERRAIN_PROPERTY(m_MapData.get(), textureNames, textureNames)
+ GET_TERRAIN_PROPERTY(tmpMapData, textureNames, textureNames)
num_terrain_tex = textureNames.size();
while (cur_terrain_tex < num_terrain_tex)
{
ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(textureNames[cur_terrain_tex]);
m_TerrainTextures.push_back(texentry);
cur_terrain_tex++;
}
// build tile data
m_Tiles.resize(SQR(size));
- CScriptValRooted tileData;
- GET_TERRAIN_PROPERTY(m_MapData.get(), tileData, tileData)
+ JS::RootedValue tileData(cx);
+ GET_TERRAIN_PROPERTY(tmpMapData, tileData, &tileData)
// parse tile data object into flat arrays
std::vector tileIndex;
std::vector tilePriority;
- GET_TERRAIN_PROPERTY(tileData.get(), index, tileIndex);
- GET_TERRAIN_PROPERTY(tileData.get(), priority, tilePriority);
+ GET_TERRAIN_PROPERTY(tileData, index, tileIndex);
+ GET_TERRAIN_PROPERTY(tileData, priority, tilePriority);
ENSURE(SQR(size) == tileIndex.size() && SQR(size) == tilePriority.size());
// reorder by patches and store
for (size_t x = 0; x < size; ++x)
{
size_t patchX = x / PATCH_SIZE;
size_t offX = x % PATCH_SIZE;
for (size_t y = 0; y < size; ++y)
{
size_t patchY = y / PATCH_SIZE;
size_t offY = y % PATCH_SIZE;
STileDesc tile;
tile.m_Tex1Index = tileIndex[y*size + x];
tile.m_Tex2Index = 0xFFFF;
tile.m_Priority = tilePriority[y*size + x];
m_Tiles[(patchY * m_PatchesPerSide + patchX) * SQR(PATCH_SIZE) + (offY * PATCH_SIZE + offX)] = tile;
}
}
// reset generator state
cur_terrain_tex = 0;
#undef GET_TERRAIN_PROPERTY
return 0;
}
int CMapReader::ParseEntities()
{
TIMER(L"ParseEntities");
+ JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
+ JSAutoRequest rq(cx);
+
+ JS::RootedValue tmpMapData(cx, m_MapData.get()); // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
// parse entities from map data
std::vector entities;
- if (!pSimulation2->GetScriptInterface().GetProperty(m_MapData.get(), "entities", entities))
+ if (!pSimulation2->GetScriptInterface().GetProperty(tmpMapData, "entities", entities))
LOGWARNING(L"CMapReader::ParseEntities() failed to get 'entities' property");
CSimulation2& sim = *pSimulation2;
CmpPtr cmpPlayerManager(sim, SYSTEM_ENTITY);
size_t entity_idx = 0;
size_t num_entities = entities.size();
Entity currEnt;
while (entity_idx < num_entities)
{
// Get current entity struct
currEnt = entities[entity_idx];
entity_id_t ent = pSimulation2->AddEntity(currEnt.templateName, currEnt.entityID);
entity_id_t player = cmpPlayerManager->GetPlayerByID(currEnt.playerID);
if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
{ // Don't add entities with invalid player IDs
LOGERROR(L"Failed to load entity template '%ls'", currEnt.templateName.c_str());
}
else
{
CmpPtr cmpPosition(sim, ent);
if (cmpPosition)
{
cmpPosition->JumpTo(currEnt.position.X, currEnt.position.Z);
cmpPosition->SetYRotation(currEnt.rotation.Y);
// TODO: other parts of the position
}
CmpPtr cmpOwnership(sim, ent);
if (cmpOwnership)
cmpOwnership->SetOwner(currEnt.playerID);
// Detect and fix collisions between foundation-blocking entities.
// This presently serves to copy wall tower control groups to wall
// segments, allowing players to expand RMS-generated walls.
CmpPtr cmpObstruction(sim, ent);
if (cmpObstruction)
cmpObstruction->ResolveFoundationCollisions();
if (currEnt.playerID == m_PlayerID && (boost::algorithm::ends_with(currEnt.templateName, L"civil_centre") || m_StartingCameraTarget == INVALID_ENTITY))
{
// Focus on civil centre or first entity owned by player
m_StartingCameraTarget = currEnt.entityID;
}
}
entity_idx++;
}
return 0;
}
int CMapReader::ParseEnvironment()
{
// parse environment settings from map data
+ JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
+ JSAutoRequest rq(cx);
+
+ JS::RootedValue tmpMapData(cx, m_MapData.get()); // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
#define GET_ENVIRONMENT_PROPERTY(val, prop, out)\
if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
LOGWARNING(L"CMapReader::ParseEnvironment() failed to get '%hs' property", #prop);
- CScriptValRooted envObj;
- GET_ENVIRONMENT_PROPERTY(m_MapData.get(), Environment, envObj)
+ JS::RootedValue envObj(cx);
+ GET_ENVIRONMENT_PROPERTY(tmpMapData, Environment, &envObj)
- if (envObj.undefined())
+ if (envObj.isUndefined())
{
LOGWARNING(L"CMapReader::ParseEnvironment(): Environment settings not found");
return 0;
}
//m_LightEnv.SetLightingModel("standard");
if (pPostproc)
pPostproc->SetPostEffect(L"default");
std::wstring skySet;
- GET_ENVIRONMENT_PROPERTY(envObj.get(), SkySet, skySet)
+ GET_ENVIRONMENT_PROPERTY(envObj, SkySet, skySet)
if (pSkyMan)
pSkyMan->SetSkySet(skySet);
CColor sunColor;
- GET_ENVIRONMENT_PROPERTY(envObj.get(), SunColour, sunColor)
+ GET_ENVIRONMENT_PROPERTY(envObj, SunColour, sunColor)
m_LightEnv.m_SunColor = RGBColor(sunColor.r, sunColor.g, sunColor.b);
- GET_ENVIRONMENT_PROPERTY(envObj.get(), SunElevation, m_LightEnv.m_Elevation)
- GET_ENVIRONMENT_PROPERTY(envObj.get(), SunRotation, m_LightEnv.m_Rotation)
+ GET_ENVIRONMENT_PROPERTY(envObj, SunElevation, m_LightEnv.m_Elevation)
+ GET_ENVIRONMENT_PROPERTY(envObj, SunRotation, m_LightEnv.m_Rotation)
CColor terrainAmbientColor;
- GET_ENVIRONMENT_PROPERTY(envObj.get(), TerrainAmbientColour, terrainAmbientColor)
+ GET_ENVIRONMENT_PROPERTY(envObj, TerrainAmbientColour, terrainAmbientColor)
m_LightEnv.m_TerrainAmbientColor = RGBColor(terrainAmbientColor.r, terrainAmbientColor.g, terrainAmbientColor.b);
CColor unitsAmbientColor;
- GET_ENVIRONMENT_PROPERTY(envObj.get(), UnitsAmbientColour, unitsAmbientColor)
+ GET_ENVIRONMENT_PROPERTY(envObj, UnitsAmbientColour, unitsAmbientColor)
m_LightEnv.m_UnitsAmbientColor = RGBColor(unitsAmbientColor.r, unitsAmbientColor.g, unitsAmbientColor.b);
// Water properties
- CScriptValRooted waterObj;
- GET_ENVIRONMENT_PROPERTY(envObj.get(), Water, waterObj)
+ JS::RootedValue waterObj(cx);
+ GET_ENVIRONMENT_PROPERTY(envObj, Water, &waterObj)
- CScriptValRooted waterBodyObj;
- GET_ENVIRONMENT_PROPERTY(waterObj.get(), WaterBody, waterBodyObj)
+ JS::RootedValue waterBodyObj(cx);
+ GET_ENVIRONMENT_PROPERTY(waterObj, WaterBody, &waterBodyObj)
// Water level - necessary
float waterHeight;
- GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Height, waterHeight)
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj, Height, waterHeight)
CmpPtr cmpWaterManager(*pSimulation2, SYSTEM_ENTITY);
ENSURE(cmpWaterManager);
cmpWaterManager->SetWaterLevel(entity_pos_t::FromFloat(waterHeight));
// If we have graphics, get rest of settings
if (pWaterMan)
{
- GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Type, pWaterMan->m_WaterType)
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj, Type, pWaterMan->m_WaterType)
if (pWaterMan->m_WaterType == L"default")
pWaterMan->m_WaterType = L"ocean";
- GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Colour, pWaterMan->m_WaterColor)
- GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Tint, pWaterMan->m_WaterTint)
- GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Waviness, pWaterMan->m_Waviness)
- GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Murkiness, pWaterMan->m_Murkiness)
- GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), WindAngle, pWaterMan->m_WindAngle)
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj, Colour, pWaterMan->m_WaterColor)
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj, Tint, pWaterMan->m_WaterTint)
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj, Waviness, pWaterMan->m_Waviness)
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj, Murkiness, pWaterMan->m_Murkiness)
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj, WindAngle, pWaterMan->m_WindAngle)
}
- CScriptValRooted fogObject;
- GET_ENVIRONMENT_PROPERTY(envObj.get(), Fog, fogObject);
+ JS::RootedValue fogObject(cx);
+ GET_ENVIRONMENT_PROPERTY(envObj, Fog, &fogObject);
- GET_ENVIRONMENT_PROPERTY(fogObject.get(), FogFactor, m_LightEnv.m_FogFactor);
- GET_ENVIRONMENT_PROPERTY(fogObject.get(), FogThickness, m_LightEnv.m_FogMax);
+ GET_ENVIRONMENT_PROPERTY(fogObject, FogFactor, m_LightEnv.m_FogFactor);
+ GET_ENVIRONMENT_PROPERTY(fogObject, FogThickness, m_LightEnv.m_FogMax);
CColor fogColor;
- GET_ENVIRONMENT_PROPERTY(fogObject.get(), FogColor, fogColor);
+ GET_ENVIRONMENT_PROPERTY(fogObject, FogColor, fogColor);
m_LightEnv.m_FogColor = RGBColor(fogColor.r, fogColor.g, fogColor.b);
- CScriptValRooted postprocObject;
- GET_ENVIRONMENT_PROPERTY(envObj.get(), Postproc, postprocObject);
+ JS::RootedValue postprocObject(cx);
+ GET_ENVIRONMENT_PROPERTY(envObj, Postproc, &postprocObject);
std::wstring postProcEffect;
- GET_ENVIRONMENT_PROPERTY(postprocObject.get(), PostprocEffect, postProcEffect);
+ GET_ENVIRONMENT_PROPERTY(postprocObject, PostprocEffect, postProcEffect);
if (pPostproc)
pPostproc->SetPostEffect(postProcEffect);
- GET_ENVIRONMENT_PROPERTY(postprocObject.get(), Brightness, m_LightEnv.m_Brightness);
- GET_ENVIRONMENT_PROPERTY(postprocObject.get(), Contrast, m_LightEnv.m_Contrast);
- GET_ENVIRONMENT_PROPERTY(postprocObject.get(), Saturation, m_LightEnv.m_Saturation);
- GET_ENVIRONMENT_PROPERTY(postprocObject.get(), Bloom, m_LightEnv.m_Bloom);
+ GET_ENVIRONMENT_PROPERTY(postprocObject, Brightness, m_LightEnv.m_Brightness);
+ GET_ENVIRONMENT_PROPERTY(postprocObject, Contrast, m_LightEnv.m_Contrast);
+ GET_ENVIRONMENT_PROPERTY(postprocObject, Saturation, m_LightEnv.m_Saturation);
+ GET_ENVIRONMENT_PROPERTY(postprocObject, Bloom, m_LightEnv.m_Bloom);
m_LightEnv.CalculateSunDirection();
#undef GET_ENVIRONMENT_PROPERTY
return 0;
}
int CMapReader::ParseCamera()
{
+ JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
+ JSAutoRequest rq(cx);
// parse camera settings from map data
// defaults if we don't find player starting camera
float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f);
CVector3D translation = CVector3D(100, 150, -100);
#define GET_CAMERA_PROPERTY(val, prop, out)\
if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
LOGWARNING(L"CMapReader::ParseCamera() failed to get '%hs' property", #prop);
- CScriptValRooted cameraObj;
- GET_CAMERA_PROPERTY(m_MapData.get(), Camera, cameraObj)
+ JS::RootedValue tmpMapData(cx, m_MapData.get()); // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
+ JS::RootedValue cameraObj(cx);
+ GET_CAMERA_PROPERTY(tmpMapData, Camera, &cameraObj)
- if (!cameraObj.undefined())
+ if (!cameraObj.isUndefined())
{ // If camera property exists, read values
CFixedVector3D pos;
- GET_CAMERA_PROPERTY(cameraObj.get(), Position, pos)
+ GET_CAMERA_PROPERTY(cameraObj, Position, pos)
translation = pos;
- GET_CAMERA_PROPERTY(cameraObj.get(), Rotation, rotation)
- GET_CAMERA_PROPERTY(cameraObj.get(), Declination, declination)
+ GET_CAMERA_PROPERTY(cameraObj, Rotation, rotation)
+ GET_CAMERA_PROPERTY(cameraObj, Declination, declination)
}
#undef GET_CAMERA_PROPERTY
if (pGameView)
{
pGameView->GetCamera()->m_Orientation.SetXRotation(declination);
pGameView->GetCamera()->m_Orientation.RotateY(rotation);
pGameView->GetCamera()->m_Orientation.Translate(translation);
pGameView->GetCamera()->UpdateFrustum();
}
return 0;
}
CMapReader::~CMapReader()
{
// Cleaup objects
delete xml_reader;
delete m_MapGen;
}
Index: ps/trunk/source/gui/GUIManager.cpp
===================================================================
--- ps/trunk/source/gui/GUIManager.cpp (revision 15567)
+++ ps/trunk/source/gui/GUIManager.cpp (revision 15568)
@@ -1,378 +1,383 @@
/* Copyright (C) 2014 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 "GUIManager.h"
#include "CGUI.h"
#include "lib/timer.h"
#include "ps/Filesystem.h"
#include "ps/CLogger.h"
#include "ps/Profile.h"
#include "ps/XML/Xeromyces.h"
#include "scriptinterface/ScriptInterface.h"
CGUIManager* g_GUI = NULL;
// General TODOs:
//
// A lot of the CGUI data could (and should) be shared between
// multiple pages, instead of treating them as completely independent, to save
// memory and loading time.
// called from main loop when (input) events are received.
// event is passed to other handlers if false is returned.
// trampoline: we don't want to make the HandleEvent implementation static
InReaction gui_handler(const SDL_Event_* ev)
{
PROFILE("GUI event handler");
return g_GUI->HandleEvent(ev);
}
static Status ReloadChangedFileCB(void* param, const VfsPath& path)
{
return static_cast(param)->ReloadChangedFile(path);
}
CGUIManager::CGUIManager()
{
m_ScriptRuntime = g_ScriptRuntime;
m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIManager", m_ScriptRuntime));
m_ScriptInterface->SetCallbackData(this);
m_ScriptInterface->LoadGlobalScripts();
RegisterFileReloadFunc(ReloadChangedFileCB, this);
}
CGUIManager::~CGUIManager()
{
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
}
bool CGUIManager::HasPages()
{
return !m_PageStack.empty();
}
void CGUIManager::SwitchPage(const CStrW& pageName, ScriptInterface* srcScriptInterface, CScriptVal initData)
{
// The page stack is cleared (including the script context where initData came from),
// therefore we have to clone initData.
shared_ptr initDataClone;
if (initData.get() != JSVAL_VOID)
{
initDataClone = srcScriptInterface->WriteStructuredClone(initData.get());
}
m_PageStack.clear();
PushPage(pageName, initDataClone);
}
void CGUIManager::PushPage(const CStrW& pageName, shared_ptr initData)
{
m_PageStack.push_back(SGUIPage());
m_PageStack.back().name = pageName;
m_PageStack.back().initData = initData;
LoadPage(m_PageStack.back());
}
void CGUIManager::PopPage()
{
if (m_PageStack.size() < 2)
{
debug_warn(L"Tried to pop GUI page when there's < 2 in the stack");
return;
}
m_PageStack.pop_back();
}
void CGUIManager::PopPageCB(shared_ptr args)
{
shared_ptr initDataClone = m_PageStack.back().initData;
PopPage();
shared_ptr scriptInterface = m_PageStack.back().gui->GetScriptInterface();
- CScriptVal initDataVal;
+ JSContext* cx = scriptInterface->GetContext();
+ JS::RootedValue initDataVal(cx);
+
if (initDataClone)
- initDataVal = scriptInterface->ReadStructuredClone(initDataClone);
+ initDataVal.set(scriptInterface->ReadStructuredClone(initDataClone));
else
{
LOGERROR(L"Called PopPageCB when initData (which should contain the callback function name) isn't set!");
return;
}
- if (!scriptInterface->HasProperty(initDataVal.get(), "callback"))
+ if (!scriptInterface->HasProperty(initDataVal, "callback"))
{
LOGERROR(L"Called PopPageCB when the callback function name isn't set!");
return;
}
std::string callback;
- if (!scriptInterface->GetProperty(initDataVal.get(), "callback", callback))
+ if (!scriptInterface->GetProperty(initDataVal, "callback", callback))
{
LOGERROR(L"Failed to get the callback property as a string from initData in PopPageCB!");
return;
}
- if (!scriptInterface->HasProperty(scriptInterface->GetGlobalObject(), callback.c_str()))
+ JS::RootedValue global(cx, scriptInterface->GetGlobalObject());
+ if (!scriptInterface->HasProperty(global, callback.c_str()))
{
LOGERROR(L"The specified callback function %hs does not exist in the page %ls", callback.c_str(), m_PageStack.back().name.c_str());
return;
}
- CScriptVal argVal;
+ JS::RootedValue argVal(cx);
if (args)
- argVal = scriptInterface->ReadStructuredClone(args);
- if (!scriptInterface->CallFunctionVoid(scriptInterface->GetGlobalObject(), callback.c_str(), argVal))
+ argVal.set(scriptInterface->ReadStructuredClone(args));
+ if (!scriptInterface->CallFunctionVoid(global, callback.c_str(), argVal))
{
LOGERROR(L"Failed to call the callback function %hs in the page %ls", callback.c_str(), m_PageStack.back().name.c_str());
return;
}
}
void CGUIManager::DisplayMessageBox(int width, int height, const CStrW& title, const CStrW& message)
{
+ JSContext* cx = m_ScriptInterface->GetContext();
+ JSAutoRequest rq(cx);
// Set up scripted init data for the standard message box window
- CScriptValRooted data;
- m_ScriptInterface->Eval("({})", data);
- m_ScriptInterface->SetProperty(data.get(), "width", width, false);
- m_ScriptInterface->SetProperty(data.get(), "height", height, false);
- m_ScriptInterface->SetProperty(data.get(), "mode", 2, false);
- m_ScriptInterface->SetProperty(data.get(), "title", std::wstring(title), false);
- m_ScriptInterface->SetProperty(data.get(), "message", std::wstring(message), false);
+ JS::RootedValue data(cx);
+ m_ScriptInterface->Eval("({})", &data);
+ m_ScriptInterface->SetProperty(data, "width", width, false);
+ m_ScriptInterface->SetProperty(data, "height", height, false);
+ m_ScriptInterface->SetProperty(data, "mode", 2, false);
+ m_ScriptInterface->SetProperty(data, "title", std::wstring(title), false);
+ m_ScriptInterface->SetProperty(data, "message", std::wstring(message), false);
// Display the message box
- PushPage(L"page_msgbox.xml", m_ScriptInterface->WriteStructuredClone(data.get()));
+ PushPage(L"page_msgbox.xml", m_ScriptInterface->WriteStructuredClone(data));
}
void CGUIManager::LoadPage(SGUIPage& page)
{
// If we're hotloading then try to grab some data from the previous page
shared_ptr hotloadData;
if (page.gui)
{
CScriptVal hotloadDataVal;
shared_ptr scriptInterface = page.gui->GetScriptInterface();
scriptInterface->CallFunction(scriptInterface->GetGlobalObject(), "getHotloadData", hotloadDataVal);
hotloadData = scriptInterface->WriteStructuredClone(hotloadDataVal.get());
}
page.inputs.clear();
page.gui.reset(new CGUI(m_ScriptRuntime));
page.gui->Initialize();
VfsPath path = VfsPath("gui") / page.name;
page.inputs.insert(path);
CXeromyces xero;
if (xero.Load(g_VFS, path) != PSRETURN_OK)
// Fail silently (Xeromyces reported the error)
return;
int elmt_page = xero.GetElementID("page");
int elmt_include = xero.GetElementID("include");
XMBElement root = xero.GetRoot();
if (root.GetNodeName() != elmt_page)
{
LOGERROR(L"GUI page '%ls' must have root element ", page.name.c_str());
return;
}
XERO_ITER_EL(root, node)
{
if (node.GetNodeName() != elmt_include)
{
LOGERROR(L"GUI page '%ls' must only have elements inside ", page.name.c_str());
continue;
}
CStrW name (node.GetText().FromUTF8());
PROFILE2("load gui xml");
PROFILE2_ATTR("name: %ls", name.c_str());
TIMER(name.c_str());
VfsPath path = VfsPath("gui") / name;
page.gui->LoadXmlFile(path, page.inputs);
}
// Remember this GUI page, in case the scripts call FindObjectByName
shared_ptr oldGUI = m_CurrentGUI;
m_CurrentGUI = page.gui;
page.gui->SendEventToAll("load");
shared_ptr scriptInterface = page.gui->GetScriptInterface();
CScriptVal initDataVal;
CScriptVal hotloadDataVal;
if (page.initData)
initDataVal = scriptInterface->ReadStructuredClone(page.initData);
if (hotloadData)
hotloadDataVal = scriptInterface->ReadStructuredClone(hotloadData);
// Call the init() function
if (!scriptInterface->CallFunctionVoid(
scriptInterface->GetGlobalObject(),
"init",
initDataVal,
hotloadDataVal)
)
{
LOGERROR(L"GUI page '%ls': Failed to call init() function", page.name.c_str());
}
m_CurrentGUI = oldGUI;
}
Status CGUIManager::ReloadChangedFile(const VfsPath& path)
{
for (PageStackType::iterator it = m_PageStack.begin(); it != m_PageStack.end(); ++it)
{
if (it->inputs.count(path))
{
LOGMESSAGE(L"GUI file '%ls' changed - reloading page '%ls'", path.string().c_str(), it->name.c_str());
LoadPage(*it);
// TODO: this can crash if LoadPage runs an init script which modifies the page stack and breaks our iterators
}
}
return INFO::OK;
}
CScriptVal CGUIManager::GetSavedGameData(ScriptInterface*& pPageScriptInterface)
{
JSContext* cx = top()->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue data(cx);
if (!top()->GetScriptInterface()->CallFunction(top()->GetGlobalObject(), "getSavedGameData", &data))
LOGERROR(L"Failed to call getSavedGameData() on the current GUI page");
pPageScriptInterface = GetScriptInterface().get();
return CScriptVal(data);
}
std::string CGUIManager::GetSavedGameData()
{
CScriptVal data;
top()->GetScriptInterface()->CallFunction(top()->GetGlobalObject(), "getSavedGameData", data);
return top()->GetScriptInterface()->StringifyJSON(data.get(), false);
}
void CGUIManager::RestoreSavedGameData(std::string jsonData)
{
top()->GetScriptInterface()->CallFunctionVoid(top()->GetGlobalObject(), "restoreSavedGameData",
top()->GetScriptInterface()->ParseJSON(jsonData));
}
InReaction CGUIManager::HandleEvent(const SDL_Event_* ev)
{
// We want scripts to have access to the raw input events, so they can do complex
// processing when necessary (e.g. for unit selection and camera movement).
// Sometimes they'll want to be layered behind the GUI widgets (e.g. to detect mousedowns on the
// visible game area), sometimes they'll want to intercepts events before the GUI (e.g.
// to capture all mouse events until a mouseup after dragging).
// So we call two separate handler functions:
bool handled;
{
PROFILE("handleInputBeforeGui");
if (top()->GetScriptInterface()->CallFunction(top()->GetGlobalObject(), "handleInputBeforeGui", *ev, top()->FindObjectUnderMouse(), handled))
if (handled)
return IN_HANDLED;
}
{
PROFILE("handle event in native GUI");
InReaction r = top()->HandleEvent(ev);
if (r != IN_PASS)
return r;
}
{
PROFILE("handleInputAfterGui");
if (top()->GetScriptInterface()->CallFunction(top()->GetGlobalObject(), "handleInputAfterGui", *ev, handled)) if (handled)
return IN_HANDLED;
}
return IN_PASS;
}
bool CGUIManager::GetPreDefinedColor(const CStr& name, CColor& output)
{
return top()->GetPreDefinedColor(name, output);
}
IGUIObject* CGUIManager::FindObjectByName(const CStr& name) const
{
// This can be called from scripts run by TickObjects,
// and we want to return it the same GUI page as is being ticked
if (m_CurrentGUI)
return m_CurrentGUI->FindObjectByName(name);
else
return top()->FindObjectByName(name);
}
void CGUIManager::SendEventToAll(const CStr& eventName)
{
top()->SendEventToAll(eventName);
}
void CGUIManager::TickObjects()
{
PROFILE3("gui tick");
// Save an immutable copy so iterators aren't invalidated by tick handlers
PageStackType pageStack = m_PageStack;
for (PageStackType::iterator it = pageStack.begin(); it != pageStack.end(); ++it)
{
m_CurrentGUI = it->gui;
it->gui->TickObjects();
}
m_CurrentGUI.reset();
}
void CGUIManager::Draw()
{
PROFILE3_GPU("gui");
for (PageStackType::iterator it = m_PageStack.begin(); it != m_PageStack.end(); ++it)
it->gui->Draw();
}
void CGUIManager::UpdateResolution()
{
for (PageStackType::iterator it = m_PageStack.begin(); it != m_PageStack.end(); ++it)
it->gui->UpdateResolution();
}
// This returns a shared_ptr to make sure the CGUI doesn't get deallocated
// while we're in the middle of calling a function on it (e.g. if a GUI script
// calls SwitchPage)
shared_ptr CGUIManager::top() const
{
ENSURE(m_PageStack.size());
return m_PageStack.back().gui;
}
Index: ps/trunk/source/gui/IGUIObject.cpp
===================================================================
--- ps/trunk/source/gui/IGUIObject.cpp (revision 15567)
+++ ps/trunk/source/gui/IGUIObject.cpp (revision 15568)
@@ -1,561 +1,563 @@
/* Copyright (C) 2012 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 .
*/
/*
IGUIObject
*/
#include "precompiled.h"
#include "GUI.h"
#include "gui/scripting/JSInterface_IGUIObject.h"
#include "gui/scripting/JSInterface_GUITypes.h"
#include "scriptinterface/ScriptInterface.h"
#include "ps/CLogger.h"
extern int g_xres, g_yres;
//-------------------------------------------------------------------
// Implementation Macros
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Constructor / Destructor
//-------------------------------------------------------------------
IGUIObject::IGUIObject() :
m_pGUI(NULL),
m_pParent(NULL),
m_MouseHovering(false)
{
AddSetting(GUIST_bool, "enabled");
AddSetting(GUIST_bool, "hidden");
AddSetting(GUIST_CClientArea, "size");
AddSetting(GUIST_CStr, "style");
AddSetting(GUIST_CStr, "hotkey");
AddSetting(GUIST_float, "z");
AddSetting(GUIST_bool, "absolute");
AddSetting(GUIST_bool, "ghost");
AddSetting(GUIST_float, "aspectratio");
AddSetting(GUIST_CStrW, "tooltip");
AddSetting(GUIST_CStr, "tooltip_style");
// Setup important defaults
GUI::SetSetting(this, "hidden", false);
GUI::SetSetting(this, "ghost", false);
GUI::SetSetting(this, "enabled", true);
GUI::SetSetting(this, "absolute", true);
for (int i=0; i<6; i++)
m_LastClickTime[i]=0;
}
IGUIObject::~IGUIObject()
{
{
std::map::iterator it;
for (it = m_Settings.begin(); it != m_Settings.end(); ++it)
{
switch (it->second.m_Type)
{
// delete() needs to know the type of the variable - never delete a void*
#define TYPE(t) case GUIST_##t: delete (t*)it->second.m_pSetting; break;
#include "GUItypes.h"
#undef TYPE
default:
debug_warn(L"Invalid setting type");
}
}
}
}
//-------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------
void IGUIObject::AddChild(IGUIObject *pChild)
{
//
// ENSURE(pChild);
pChild->SetParent(this);
m_Children.push_back(pChild);
// If this (not the child) object is already attached
// to a CGUI, it pGUI pointer will be non-null.
// This will mean we'll have to check if we're using
// names already used.
if (pChild->GetGUI())
{
try
{
// Atomic function, if it fails it won't
// have changed anything
//UpdateObjects();
pChild->GetGUI()->UpdateObjects();
}
catch (PSERROR_GUI&)
{
// If anything went wrong, reverse what we did and throw
// an exception telling it never added a child
m_Children.erase( m_Children.end()-1 );
throw;
}
}
// else do nothing
}
void IGUIObject::AddToPointersMap(map_pObjects &ObjectMap)
{
// Just don't do anything about the top node
if (m_pParent == NULL)
return;
// Now actually add this one
// notice we won't add it if it's doesn't have any parent
// (i.e. being the base object)
if (m_Name.empty())
{
throw PSERROR_GUI_ObjectNeedsName();
}
if (ObjectMap.count(m_Name) > 0)
{
throw PSERROR_GUI_NameAmbiguity(m_Name.c_str());
}
else
{
ObjectMap[m_Name] = this;
}
}
void IGUIObject::Destroy()
{
// Is there anything besides the children to destroy?
}
// Notice if using this, the naming convention of GUIST_ should be strict.
#define TYPE(type) \
case GUIST_##type: \
m_Settings[Name].m_pSetting = new type(); \
break;
void IGUIObject::AddSetting(const EGUISettingType &Type, const CStr& Name)
{
// Is name already taken?
if (m_Settings.count(Name) >= 1)
return;
// Construct, and set type
m_Settings[Name].m_Type = Type;
switch (Type)
{
// Construct the setting.
#include "GUItypes.h"
default:
debug_warn(L"IGUIObject::AddSetting failed, type not recognized!");
break;
}
}
#undef TYPE
bool IGUIObject::MouseOver()
{
if(!GetGUI())
throw PSERROR_GUI_OperationNeedsGUIObject();
return m_CachedActualSize.PointInside(GetMousePos());
}
bool IGUIObject::MouseOverIcon()
{
return false;
}
CPos IGUIObject::GetMousePos() const
{
return ((GetGUI())?(GetGUI()->m_MousePos):CPos());
}
void IGUIObject::UpdateMouseOver(IGUIObject * const &pMouseOver)
{
// Check if this is the object being hovered.
if (pMouseOver == this)
{
if (!m_MouseHovering)
{
// It wasn't hovering, so that must mean it just entered
SendEvent(GUIM_MOUSE_ENTER, "mouseenter");
}
// Either way, set to true
m_MouseHovering = true;
// call mouse over
SendEvent(GUIM_MOUSE_OVER, "mousemove");
}
else // Some other object (or none) is hovered
{
if (m_MouseHovering)
{
m_MouseHovering = false;
SendEvent(GUIM_MOUSE_LEAVE, "mouseleave");
}
}
}
bool IGUIObject::SettingExists(const CStr& Setting) const
{
// Because GetOffsets will direct dynamically defined
// classes with polymorphism to respective ms_SettingsInfo
// we need to make no further updates on this function
// in derived classes.
//return (GetSettingsInfo().count(Setting) >= 1);
return (m_Settings.count(Setting) >= 1);
}
#define TYPE(type) \
else \
if (set.m_Type == GUIST_##type) \
{ \
type _Value; \
if (!GUI::ParseString(Value, _Value)) \
return PSRETURN_GUI_UnableToParse; \
\
GUI::SetSetting(this, Setting, _Value, SkipMessage); \
}
PSRETURN IGUIObject::SetSetting(const CStr& Setting, const CStrW& Value, const bool& SkipMessage)
{
if (!SettingExists(Setting))
{
return PSRETURN_GUI_InvalidSetting;
}
// Get setting
SGUISetting set = m_Settings[Setting];
if (0);
// else...
#include "GUItypes.h"
else
{
// Why does it always fail?
//return PS_FAIL;
return LogInvalidSettings(Setting);
}
return PSRETURN_OK;
}
#undef TYPE
PSRETURN IGUIObject::GetSettingType(const CStr& Setting, EGUISettingType &Type) const
{
if (!SettingExists(Setting))
{
return LogInvalidSettings(Setting);
}
if (m_Settings.find(Setting) == m_Settings.end())
{
return LogInvalidSettings(Setting);
}
Type = m_Settings.find(Setting)->second.m_Type;
return PSRETURN_OK;
}
void IGUIObject::ChooseMouseOverAndClosest(IGUIObject* &pObject)
{
if (MouseOver())
{
// Check if we've got competition at all
if (pObject == NULL)
{
pObject = this;
return;
}
// Or if it's closer
if (GetBufferedZ() >= pObject->GetBufferedZ())
{
pObject = this;
return;
}
}
}
IGUIObject *IGUIObject::GetParent() const
{
// Important, we're not using GetParent() for these
// checks, that could screw it up
if (m_pParent)
{
if (m_pParent->m_pParent == NULL)
return NULL;
}
return m_pParent;
}
void IGUIObject::UpdateCachedSize()
{
bool absolute;
GUI::GetSetting(this, "absolute", absolute);
float aspectratio = 0.f;
GUI::GetSetting(this, "aspectratio", aspectratio);
CClientArea ca;
GUI::GetSetting(this, "size", ca);
// If absolute="false" and the object has got a parent,
// use its cached size instead of the screen. Notice
// it must have just been cached for it to work.
if (absolute == false && m_pParent && !IsRootObject())
m_CachedActualSize = ca.GetClientArea(m_pParent->m_CachedActualSize);
else
m_CachedActualSize = ca.GetClientArea(CRect(0.f, 0.f, (float)g_xres, (float)g_yres));
// In a few cases, GUI objects have to resize to fill the screen
// but maintain a constant aspect ratio.
// Adjust the size to be the max possible, centered in the original size:
if (aspectratio)
{
if (m_CachedActualSize.GetWidth() > m_CachedActualSize.GetHeight()*aspectratio)
{
float delta = m_CachedActualSize.GetWidth() - m_CachedActualSize.GetHeight()*aspectratio;
m_CachedActualSize.left += delta/2.f;
m_CachedActualSize.right -= delta/2.f;
}
else
{
float delta = m_CachedActualSize.GetHeight() - m_CachedActualSize.GetWidth()/aspectratio;
m_CachedActualSize.bottom -= delta/2.f;
m_CachedActualSize.top += delta/2.f;
}
}
}
void IGUIObject::LoadStyle(CGUI &GUIinstance, const CStr& StyleName)
{
// Fetch style
if (GUIinstance.m_Styles.count(StyleName)==1)
{
LoadStyle(GUIinstance.m_Styles[StyleName]);
}
else
{
debug_warn(L"IGUIObject::LoadStyle failed");
}
}
void IGUIObject::LoadStyle(const SGUIStyle &Style)
{
// Iterate settings, it won't be able to set them all probably, but that doesn't matter
std::map::const_iterator cit;
for (cit = Style.m_SettingsDefaults.begin(); cit != Style.m_SettingsDefaults.end(); ++cit)
{
// Try set setting in object
SetSetting(cit->first, cit->second);
// It doesn't matter if it fail, it's not suppose to be able to set every setting.
// since it's generic.
// The beauty with styles is that it can contain more settings
// than exists for the objects using it. So if the SetSetting
// fails, don't care.
}
}
float IGUIObject::GetBufferedZ() const
{
bool absolute;
GUI::GetSetting(this, "absolute", absolute);
float Z;
GUI::GetSetting(this, "z", Z);
if (absolute)
return Z;
else
{
if (GetParent())
return GetParent()->GetBufferedZ() + Z;
else
{
// In philosophy, a parentless object shouldn't be able to have a relative sizing,
// but we'll accept it so that absolute can be used as default without a complaint.
// Also, you could consider those objects children to the screen resolution.
return Z;
}
}
}
void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI* pGUI)
{
if(!GetGUI())
throw PSERROR_GUI_OperationNeedsGUIObject();
JSContext* cx = pGUI->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
const int paramCount = 1;
const char* paramNames[paramCount] = { "mouse" };
// Location to report errors from
CStr CodeName = GetName()+" "+Action;
// Generate a unique name
static int x=0;
char buf[64];
sprintf_s(buf, ARRAY_SIZE(buf), "__eventhandler%d (%s)", x++, Action.c_str());
JSFunction* func = JS_CompileFunction(cx, JSVAL_TO_OBJECT(pGUI->GetGlobalObject()),
buf, paramCount, paramNames, Code.c_str(), Code.length(), CodeName.c_str(), 0);
if (!func)
return; // JS will report an error message
SetScriptHandler(Action, JS_GetFunctionObject(func));
}
void IGUIObject::SetScriptHandler(const CStr& Action, JSObject* Function)
{
m_ScriptHandlers[Action] = CScriptValRooted(m_pGUI->GetScriptInterface()->GetContext(), JS::ObjectValue(*Function));
}
InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& EventName)
{
PROFILE2_EVENT("gui event");
PROFILE2_ATTR("type: %s", EventName.c_str());
PROFILE2_ATTR("object: %s", m_Name.c_str());
SGUIMessage msg(type);
HandleMessage(msg);
ScriptEvent(EventName);
return (msg.skipped ? IN_PASS : IN_HANDLED);
}
void IGUIObject::ScriptEvent(const CStr& Action)
{
std::map::iterator it = m_ScriptHandlers.find(Action);
if (it == m_ScriptHandlers.end())
return;
JSContext* cx = m_pGUI->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
// Set up the 'mouse' parameter
- CScriptVal mouse;
- m_pGUI->GetScriptInterface()->Eval("({})", mouse);
- m_pGUI->GetScriptInterface()->SetProperty(mouse.get(), "x", m_pGUI->m_MousePos.x, false);
- m_pGUI->GetScriptInterface()->SetProperty(mouse.get(), "y", m_pGUI->m_MousePos.y, false);
- m_pGUI->GetScriptInterface()->SetProperty(mouse.get(), "buttons", m_pGUI->m_MouseButtons, false);
-
- jsval paramData[] = { mouse.get() };
-
- jsval result;
- bool ok = JS_CallFunctionValue(cx, GetJSObject(), (*it).second.get(), ARRAY_SIZE(paramData), paramData, &result);
+ JS::RootedValue mouse(cx);
+ m_pGUI->GetScriptInterface()->Eval("({})", &mouse);
+ m_pGUI->GetScriptInterface()->SetProperty(mouse, "x", m_pGUI->m_MousePos.x, false);
+ m_pGUI->GetScriptInterface()->SetProperty(mouse, "y", m_pGUI->m_MousePos.y, false);
+ m_pGUI->GetScriptInterface()->SetProperty(mouse, "buttons", m_pGUI->m_MouseButtons, false);
+
+ JS::AutoValueVector paramData(cx);
+ paramData.append(mouse);
+ JS::RootedObject obj(cx, GetJSObject());
+ JS::RootedValue handlerVal(cx, (*it).second.get());
+ JS::RootedValue result(cx);
+ bool ok = JS_CallFunctionValue(cx, obj, handlerVal, paramData.length(), paramData.begin(), result.address());
if (!ok)
{
// We have no way to propagate the script exception, so just ignore it
// and hope the caller checks JS_IsExceptionPending
}
}
void IGUIObject::ScriptEvent(const CStr& Action, const CScriptValRooted& Argument)
{
std::map::iterator it = m_ScriptHandlers.find(Action);
if (it == m_ScriptHandlers.end())
return;
JSContext* cx = m_pGUI->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
JSObject* object = GetJSObject();
jsval arg = Argument.get();
jsval result;
bool ok = JS_CallFunctionValue(cx, object, (*it).second.get(), 1, &arg, &result);
if (!ok)
{
JS_ReportError(cx, "Errors executing script action \"%s\"", Action.c_str());
}
}
JSObject* IGUIObject::GetJSObject()
{
JSContext* cx = m_pGUI->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
// Cache the object when somebody first asks for it, because otherwise
// we end up doing far too much object allocation. TODO: Would be nice to
// not have these objects hang around forever using up memory, though.
if (m_JSObject.uninitialised())
{
JS::RootedObject obj(cx, m_pGUI->GetScriptInterface()->CreateCustomObject("GUIObject"));
m_JSObject = CScriptValRooted(cx, JS::ObjectValue(*obj));
JS_SetPrivate(obj, this);
}
return &m_JSObject.get().toObject();
}
CStr IGUIObject::GetPresentableName() const
{
// __internal(), must be at least 13 letters to be able to be
// an internal name
if (m_Name.length() <= 12)
return m_Name;
if (m_Name.substr(0, 10) == "__internal")
return CStr("[unnamed object]");
else
return m_Name;
}
void IGUIObject::SetFocus()
{
GetGUI()->m_FocusedObject = this;
}
bool IGUIObject::IsFocused() const
{
return GetGUI()->m_FocusedObject == this;
}
bool IGUIObject::IsRootObject() const
{
return (GetGUI() != 0 && m_pParent == GetGUI()->m_BaseObject);
}
PSRETURN IGUIObject::LogInvalidSettings(const CStr8 &Setting) const
{
LOGWARNING(L"IGUIObject: setting %hs was not found on an object",
Setting.c_str());
return PSRETURN_GUI_InvalidSetting;
}
Index: ps/trunk/source/gui/MiniMap.cpp
===================================================================
--- ps/trunk/source/gui/MiniMap.cpp (revision 15567)
+++ ps/trunk/source/gui/MiniMap.cpp (revision 15568)
@@ -1,706 +1,709 @@
/* Copyright (C) 2013 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
#include "MiniMap.h"
#include "graphics/GameView.h"
#include "graphics/LOSTexture.h"
#include "graphics/MiniPatch.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerritoryTexture.h"
#include "gui/GUI.h"
#include "gui/GUIManager.h"
#include "lib/ogl.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/bits.h"
#include "lib/timer.h"
#include "ps/ConfigDB.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpMinimap.h"
#include "simulation2/system/ParamNode.h"
bool g_GameRestarted = false;
// Set max drawn entities to UINT16_MAX for now, which is more than enough
// TODO: we should be cleverer about drawing them to reduce clutter
const u16 MAX_ENTITIES_DRAWN = 65535;
static unsigned int ScaleColor(unsigned int color, float x)
{
unsigned int r = unsigned(float(color & 0xff) * x);
unsigned int g = unsigned(float((color>>8) & 0xff) * x);
unsigned int b = unsigned(float((color>>16) & 0xff) * x);
return (0xff000000 | b | g<<8 | r<<16);
}
CMiniMap::CMiniMap() :
m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_TerrainDirty(true), m_MapScale(1.f),
m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW),
m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false)
{
AddSetting(GUIST_CColor, "fov_wedge_color");
AddSetting(GUIST_CStrW, "tooltip");
AddSetting(GUIST_CStr, "tooltip_style");
m_Clicking = false;
m_MouseHovering = false;
// Get the maximum height for unit passage in water.
CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml");
const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses");
if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk())
m_ShallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat();
else
m_ShallowPassageHeight = 0.0f;
m_AttributePos.type = GL_FLOAT;
m_AttributePos.elems = 2;
m_VertexArray.AddAttribute(&m_AttributePos);
m_AttributeColor.type = GL_UNSIGNED_BYTE;
m_AttributeColor.elems = 4;
m_VertexArray.AddAttribute(&m_AttributeColor);
m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_VertexArray.Layout();
m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_IndexArray.Layout();
VertexArrayIterator index = m_IndexArray.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
*index++ = i;
m_IndexArray.Upload();
m_IndexArray.FreeBackingStore();
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; i++)
{
(*attrColor)[0] = 0;
(*attrColor)[1] = 0;
(*attrColor)[2] = 0;
(*attrColor)[3] = 0;
++attrColor;
(*attrPos)[0] = -10000.0f;
(*attrPos)[1] = -10000.0f;
++attrPos;
}
m_VertexArray.Upload();
double blinkDuration = 1.0;
// Tests won't have config initialised
if (CConfigDB::IsInitialised())
{
CFG_GET_VAL("gui.session.minimap.pingduration", Double, m_PingDuration);
CFG_GET_VAL("gui.session.minimap.blinkduration", Double, blinkDuration);
}
m_HalfBlinkDuration = blinkDuration/2;
}
CMiniMap::~CMiniMap()
{
Destroy();
}
void CMiniMap::HandleMessage(SGUIMessage &Message)
{
switch(Message.type)
{
case GUIM_MOUSE_PRESS_LEFT:
{
if (m_MouseHovering)
{
SetCameraPos();
m_Clicking = true;
}
break;
}
case GUIM_MOUSE_RELEASE_LEFT:
{
if(m_MouseHovering && m_Clicking)
SetCameraPos();
m_Clicking = false;
break;
}
case GUIM_MOUSE_DBLCLICK_LEFT:
{
if(m_MouseHovering && m_Clicking)
SetCameraPos();
m_Clicking = false;
break;
}
case GUIM_MOUSE_ENTER:
{
m_MouseHovering = true;
break;
}
case GUIM_MOUSE_LEAVE:
{
m_Clicking = false;
m_MouseHovering = false;
break;
}
case GUIM_MOUSE_RELEASE_RIGHT:
{
CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1);
break;
}
case GUIM_MOUSE_DBLCLICK_RIGHT:
{
CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2);
break;
}
case GUIM_MOUSE_MOTION:
{
if (m_MouseHovering && m_Clicking)
SetCameraPos();
break;
}
case GUIM_MOUSE_WHEEL_DOWN:
case GUIM_MOUSE_WHEEL_UP:
Message.Skip();
break;
default:
break;
} // switch
}
bool CMiniMap::MouseOver()
{
// Get the mouse position.
CPos mousePos = GetMousePos();
// Get the position of the center of the minimap.
CPos minimapCenter = CPos(m_CachedActualSize.left + m_CachedActualSize.GetWidth() / 2.0, m_CachedActualSize.bottom - m_CachedActualSize.GetHeight() / 2.0);
// Take the magnitude of the difference of the mouse position and minimap center.
double distFromCenter = sqrt(pow((mousePos.x - minimapCenter.x), 2) + pow((mousePos.y - minimapCenter.y), 2));
// If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap.
if (distFromCenter < m_CachedActualSize.GetWidth() / 2.0)
return true;
else
return false;
}
void CMiniMap::GetMouseWorldCoordinates(float& x, float& z)
{
// Determine X and Z according to proportion of mouse position and minimap
CPos mousePos = GetMousePos();
float px = (mousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth();
float py = (m_CachedActualSize.bottom - mousePos.y) / m_CachedActualSize.GetHeight();
float angle = GetAngle();
// Scale world coordinates for shrunken square map
x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5);
z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5);
}
void CMiniMap::SetCameraPos()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CVector3D target;
GetMouseWorldCoordinates(target.X, target.Z);
target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
g_Game->GetView()->MoveCameraTarget(target);
}
float CMiniMap::GetAngle()
{
CVector3D cameraIn = m_Camera->m_Orientation.GetIn();
return -atan2(cameraIn.X, cameraIn.Z);
}
void CMiniMap::FireWorldClickEvent(int button, int clicks)
{
+ JSContext* cx = g_GUI->GetActiveGUI()->GetScriptInterface()->GetContext();
+ JSAutoRequest rq(cx);
+
float x, z;
GetMouseWorldCoordinates(x, z);
- CScriptValRooted coords;
- g_GUI->GetActiveGUI()->GetScriptInterface()->Eval("({})", coords);
- g_GUI->GetActiveGUI()->GetScriptInterface()->SetProperty(coords.get(), "x", x, false);
- g_GUI->GetActiveGUI()->GetScriptInterface()->SetProperty(coords.get(), "z", z, false);
- ScriptEvent("worldclick", coords);
+ JS::RootedValue coords(cx);
+ g_GUI->GetActiveGUI()->GetScriptInterface()->Eval("({})", &coords);
+ g_GUI->GetActiveGUI()->GetScriptInterface()->SetProperty(coords, "x", x, false);
+ g_GUI->GetActiveGUI()->GetScriptInterface()->SetProperty(coords, "z", z, false);
+ ScriptEvent("worldclick", CScriptValRooted(cx, coords));
UNUSED2(button);
UNUSED2(clicks);
}
// This sets up and draws the rectangle on the minimap
// which represents the view of the camera in the world.
void CMiniMap::DrawViewRect(CMatrix3D transform)
{
// Compute the camera frustum intersected with a fixed-height plane.
// TODO: Currently we hard-code the height, so this'll be dodgy when maps aren't the
// expected height - how can we make it better without the view rect wobbling in
// size while the player scrolls?
float h = 16384.f * HEIGHT_SCALE;
const float width = m_CachedActualSize.GetWidth();
const float height = m_CachedActualSize.GetHeight();
const float invTileMapSize = 1.0f / float(TERRAIN_TILE_SIZE * m_MapSize);
CVector3D hitPt[4];
hitPt[0] = m_Camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), h);
hitPt[1] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), h);
hitPt[2] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, h);
hitPt[3] = m_Camera->GetWorldCoordinates(0, 0, h);
float ViewRect[4][2];
for (int i = 0; i < 4; i++) {
// convert to minimap space
ViewRect[i][0] = (width * hitPt[i].X * invTileMapSize);
ViewRect[i][1] = (height * hitPt[i].Z * invTileMapSize);
}
float viewVerts[] = {
ViewRect[0][0], -ViewRect[0][1],
ViewRect[1][0], -ViewRect[1][1],
ViewRect[2][0], -ViewRect[2][1],
ViewRect[3][0], -ViewRect[3][1]
};
// Enable Scissoring to restrict the rectangle to only the minimap.
glScissor((int)m_CachedActualSize.left, g_Renderer.GetHeight() - (int)m_CachedActualSize.bottom, (int)width, (int)height);
glEnable(GL_SCISSOR_TEST);
glLineWidth(2.0f);
CShaderDefines lineDefines;
lineDefines.Add(str_MINIMAP_LINE, str_1);
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), lineDefines);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->Uniform(str_transform, transform);
shader->Uniform(str_color, 1.0f, 0.3f, 0.3f, 1.0f);
shader->VertexPointer(2, GL_FLOAT, 0, viewVerts);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_LINE_LOOP, 0, 4);
tech->EndPass();
glLineWidth(1.0f);
glDisable(GL_SCISSOR_TEST);
}
struct MinimapUnitVertex
{
u8 r, g, b, a;
float x, y;
};
// Adds a vertex to the passed VertexArray
static void inline addVertex(const MinimapUnitVertex& v,
VertexArrayIterator& attrColor,
VertexArrayIterator& attrPos)
{
(*attrColor)[0] = v.r;
(*attrColor)[1] = v.g;
(*attrColor)[2] = v.b;
(*attrColor)[3] = v.a;
++attrColor;
(*attrPos)[0] = v.x;
(*attrPos)[1] = v.y;
++attrPos;
}
void CMiniMap::DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z)
{
// Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m)
// Scale square maps to fit in circular minimap area
const float s = sin(angle) * m_MapScale;
const float c = cos(angle) * m_MapScale;
const float m = coordMax / 2.f;
float quadTex[] = {
m*(-c + s + 1.f), m*(-c + -s + 1.f),
m*(c + s + 1.f), m*(-c + s + 1.f),
m*(c + -s + 1.f), m*(c + s + 1.f),
m*(c + -s + 1.f), m*(c + s + 1.f),
m*(-c + -s + 1.f), m*(c + -s + 1.f),
m*(-c + s + 1.f), m*(-c + -s + 1.f)
};
float quadVerts[] = {
x, y, z,
x2, y, z,
x2, y2, z,
x2, y2, z,
x, y2, z,
x, y, z
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
shader->VertexPointer(3, GL_FLOAT, 0, quadVerts);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_TRIANGLES, 0, 6);
}
// TODO: render the minimap in a framebuffer and just draw the frambuffer texture
// most of the time, updating the framebuffer twice a frame.
// Here it updates as ping-pong either texture or vertex array each sec to lower gpu stalling
// (those operations cause a gpu sync, which slows down the way gpu works)
void CMiniMap::Draw()
{
PROFILE3("render minimap");
// The terrain isn't actually initialized until the map is loaded, which
// happens when the game is started, so abort until then.
if (!(GetGUI() && g_Game && g_Game->IsGameStarted()))
return;
CSimulation2* sim = g_Game->GetSimulation2();
CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY);
ENSURE(cmpRangeManager);
// Set our globals in case they hadn't been set before
m_Camera = g_Game->GetView()->GetCamera();
m_Terrain = g_Game->GetWorld()->GetTerrain();
m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left);
m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top);
m_MapSize = m_Terrain->GetVerticesPerSide();
m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize);
m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f);
if (!m_TerrainTexture || g_GameRestarted)
CreateTextures();
// only update 2x / second
// (note: since units only move a few pixels per second on the minimap,
// we can get away with infrequent updates; this is slow)
// TODO: Update all but camera at same speed as simulation
static double last_time;
const double cur_time = timer_Time();
const bool doUpdate = cur_time - last_time > 0.5;
if (doUpdate)
{
last_time = cur_time;
if (m_TerrainDirty)
RebuildTerrainTexture();
}
const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom;
const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top;
const float z = GetBufferedZ();
const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize;
const float angle = GetAngle();
const float unitScale = (cmpRangeManager->GetLosCircular() ? 1.f : m_MapScale/2.f);
// Disable depth updates to prevent apparent z-fighting-related issues
// with some drivers causing units to get drawn behind the texture.
glDepthMask(0);
CShaderProgramPtr shader;
CShaderTechniquePtr tech;
CShaderDefines baseDefines;
baseDefines.Add(str_MINIMAP_BASE, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), baseDefines);
tech->BeginPass();
shader = tech->GetShader();
// Draw the main textured quad
shader->BindTexture(str_baseTex, m_TerrainTexture);
const CMatrix3D baseTransform = GetDefaultGuiMatrix();
CMatrix3D baseTextureTransform;
baseTextureTransform.SetIdentity();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, baseTextureTransform);
DrawTexture(shader, texCoordMax, angle, x, y, x2, y2, z);
// Draw territory boundaries
glEnable(GL_BLEND);
CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture();
shader->BindTexture(str_baseTex, territoryTexture.GetTexture());
const CMatrix3D *territoryTransform = territoryTexture.GetMinimapTextureMatrix();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, *territoryTransform);
DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z);
tech->EndPass();
// Draw the LOS quad in black, using alpha values from the LOS texture
CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture();
CShaderDefines losDefines;
losDefines.Add(str_MINIMAP_LOS, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), losDefines);
tech->BeginPass();
shader = tech->GetShader();
shader->BindTexture(str_baseTex, losTexture.GetTexture());
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
const CMatrix3D *losTransform = losTexture.GetMinimapTextureMatrix();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, *losTransform);
DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z);
tech->EndPass();
glDisable(GL_BLEND);
PROFILE_START("minimap units");
CShaderDefines pointDefines;
pointDefines.Add(str_MINIMAP_POINT, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), pointDefines);
tech->BeginPass();
shader = tech->GetShader();
shader->Uniform(str_transform, baseTransform);
CMatrix3D unitMatrix;
unitMatrix.SetIdentity();
// Center the minimap on the origin of the axis of rotation.
unitMatrix.Translate(-(x2 - x) / 2.f, -(y2 - y) / 2.f, 0.f);
// Rotate the map.
unitMatrix.RotateZ(angle);
// Scale square maps to fit.
unitMatrix.Scale(unitScale, unitScale, 1.f);
// Move the minimap back to it's starting position.
unitMatrix.Translate((x2 - x) / 2.f, (y2 - y) / 2.f, 0.f);
// Move the minimap to it's final location.
unitMatrix.Translate(x, y, z);
// Apply the gui matrix.
unitMatrix *= GetDefaultGuiMatrix();
// Load the transform into the shader.
shader->Uniform(str_transform, unitMatrix);
const float sx = (float)m_Width / ((m_MapSize - 1) * TERRAIN_TILE_SIZE);
const float sy = (float)m_Height / ((m_MapSize - 1) * TERRAIN_TILE_SIZE);
CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap);
if (doUpdate)
{
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
m_EntitiesDrawn = 0;
MinimapUnitVertex v;
std::vector pingingVertices;
pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2);
if (cur_time > m_NextBlinkTime)
{
m_BlinkState = !m_BlinkState;
m_NextBlinkTime = cur_time + m_HalfBlinkDuration;
}
entity_pos_t posX, posZ;
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
ICmpMinimap* cmpMinimap = static_cast(it->second);
if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
{
ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetPlayerID());
if (vis != ICmpRangeManager::VIS_HIDDEN)
{
v.a = 255;
v.x = posX.ToFloat() * sx;
v.y = -posZ.ToFloat() * sy;
// Check minimap pinging to indicate something
if (m_BlinkState && cmpMinimap->CheckPing(cur_time, m_PingDuration))
{
v.r = 255; // ping color is white
v.g = 255;
v.b = 255;
pingingVertices.push_back(v);
}
else
{
addVertex(v, attrColor, attrPos);
++m_EntitiesDrawn;
}
}
}
}
// Add the pinged vertices at the end, so they are drawn on top
for (size_t v = 0; v < pingingVertices.size(); ++v)
{
addVertex(pingingVertices[v], attrColor, attrPos);
++m_EntitiesDrawn;
}
ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN);
m_VertexArray.Upload();
}
if (m_EntitiesDrawn > 0)
{
glPointSize(3.f);
u8* indexBase = m_IndexArray.Bind();
u8* base = m_VertexArray.Bind();
const GLsizei stride = (GLsizei)m_VertexArray.GetStride();
shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset);
shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase);
g_Renderer.GetStats().m_DrawCalls++;
CVertexBuffer::Unbind();
glPointSize(1.0f);
}
tech->EndPass();
DrawViewRect(unitMatrix);
PROFILE_END("minimap units");
// Reset depth mask
glDepthMask(1);
}
void CMiniMap::CreateTextures()
{
Destroy();
// Create terrain texture
glGenTextures(1, &m_TerrainTexture);
g_Renderer.BindTexture(0, m_TerrainTexture);
// Initialise texture with solid black, for the areas we don't
// overwrite with glTexSubImage2D later
u32* texData = new u32[m_TextureSize * m_TextureSize];
for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i)
texData[i] = 0xFF000000;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
delete[] texData;
m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)];
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Rebuild and upload both of them
RebuildTerrainTexture();
}
void CMiniMap::RebuildTerrainTexture()
{
u32 x = 0;
u32 y = 0;
u32 w = m_MapSize - 1;
u32 h = m_MapSize - 1;
float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight;
m_TerrainDirty = false;
for(u32 j = 0; j < h; j++)
{
u32 *dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x;
for(u32 i = 0; i < w; i++)
{
float avgHeight = ( m_Terrain->GetVertexGroundLevel((int)i, (int)j)
+ m_Terrain->GetVertexGroundLevel((int)i+1, (int)j)
+ m_Terrain->GetVertexGroundLevel((int)i, (int)j+1)
+ m_Terrain->GetVertexGroundLevel((int)i+1, (int)j+1)
) / 4.0f;
if (avgHeight < waterHeight && avgHeight > waterHeight - m_ShallowPassageHeight)
{
// shallow water
*dataPtr++ = 0xffc09870;
}
else if (avgHeight < waterHeight)
{
// Set water as constant color for consistency on different maps
*dataPtr++ = 0xffa07850;
}
else
{
int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8;
int val = (hmap / 3) + 170;
u32 color = 0xFFFFFFFF;
CMiniPatch *mp = m_Terrain->GetTile(x + i, y + j);
if(mp)
{
CTerrainTextureEntry *tex = mp->GetTextureEntry();
if(tex)
{
// If the texture can't be loaded yet, set the dirty flags
// so we'll try regenerating the terrain texture again soon
if(!tex->GetTexture()->TryLoad())
m_TerrainDirty = true;
color = tex->GetBaseColor();
}
}
*dataPtr++ = ScaleColor(color, float(val) / 255.0f);
}
}
}
// Upload the texture
g_Renderer.BindTexture(0, m_TerrainTexture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData);
}
void CMiniMap::Destroy()
{
if(m_TerrainTexture)
{
glDeleteTextures(1, &m_TerrainTexture);
m_TerrainTexture = 0;
}
delete[] m_TerrainData;
m_TerrainData = 0;
}
Index: ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp
===================================================================
--- ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 15567)
+++ ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 15568)
@@ -1,701 +1,704 @@
/* Copyright (C) 2013 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 "JSInterface_IGUIObject.h"
#include "JSInterface_GUITypes.h"
#include "gui/IGUIObject.h"
#include "gui/CGUI.h"
#include "gui/IGUIScrollBar.h"
#include "gui/CList.h"
#include "gui/GUIManager.h"
#include "ps/CLogger.h"
#include "scriptinterface/ScriptInterface.h"
JSClass JSI_IGUIObject::JSI_class = {
"GUIObject", JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_DeletePropertyStub,
JSI_IGUIObject::getProperty, JSI_IGUIObject::setProperty,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, NULL,
NULL, NULL, NULL, JSI_IGUIObject::construct
};
JSPropertySpec JSI_IGUIObject::JSI_props[] =
{
{ 0 }
};
JSFunctionSpec JSI_IGUIObject::JSI_methods[] =
{
JS_FS("toString", JSI_IGUIObject::toString, 0, 0),
JS_FS("focus", JSI_IGUIObject::focus, 0, 0),
JS_FS("blur", JSI_IGUIObject::blur, 0, 0),
JS_FS("getComputedSize", JSI_IGUIObject::getComputedSize, 0, 0),
JS_FS_END
};
JSBool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp)
{
JSAutoRequest rq(cx);
ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return JS_FALSE;
JS::RootedValue idval(cx);
if (!JS_IdToValue(cx, id, idval.address()))
return JS_FALSE;
std::string propName;
if (!ScriptInterface::FromJSVal(cx, idval, propName))
return JS_FALSE;
// Skip some things which are known to be functions rather than properties.
// ("constructor" *must* be here, else it'll try to GetSettingType before
// the private IGUIObject* has been set (and thus crash). The others are
// partly for efficiency, and also to allow correct reporting of attempts to
// access nonexistent properties.)
if (propName == "constructor" ||
propName == "prototype" ||
propName == "toString" ||
propName == "toJSON" ||
propName == "focus" ||
propName == "blur" ||
propName == "getComputedSize"
)
return JS_TRUE;
// Use onWhatever to access event handlers
if (propName.substr(0, 2) == "on")
{
CStr eventName (CStr(propName.substr(2)).LowerCase());
std::map::iterator it = e->m_ScriptHandlers.find(eventName);
if (it == e->m_ScriptHandlers.end())
vp.set(JS::NullValue());
else
vp.set((*it).second.get());
return JS_TRUE;
}
// Handle the "parent" property specially
if (propName == "parent")
{
IGUIObject* parent = e->GetParent();
if (parent)
{
// If the object isn't parentless, return a new object
vp.set(JS::ObjectValue(*parent->GetJSObject()));
}
else
{
// Return null if there's no parent
vp.set(JS::NullValue());
}
return JS_TRUE;
}
// Also handle "name" specially
else if (propName == "name")
{
vp.set(JS::StringValue(JS_NewStringCopyZ(cx, e->GetName().c_str())));
return JS_TRUE;
}
// Handle all other properties
else
{
// Retrieve the setting's type (and make sure it actually exists)
EGUISettingType Type;
if (e->GetSettingType(propName, Type) != PSRETURN_OK)
{
JS_ReportError(cx, "Invalid GUIObject property '%s'", propName.c_str());
return JS_FALSE;
}
// (All the cases are in {...} to avoid scoping problems)
switch (Type)
{
case GUIST_bool:
{
bool value;
GUI::GetSetting(e, propName, value);
vp.set(JS::BooleanValue(value));
break;
}
case GUIST_int:
{
int value;
GUI::GetSetting(e, propName, value);
vp.set(JS::Int32Value(value));
break;
}
case GUIST_float:
{
float value;
GUI::GetSetting(e, propName, value);
// Create a garbage-collectable double
vp.set(JS::NumberValue(value));
return !vp.isNull();
}
case GUIST_CColor:
{
CColor colour;
GUI::GetSetting(e, propName, colour);
JS::RootedObject obj(cx, pScriptInterface->CreateCustomObject("GUIColor"));
vp.setObject(*obj);
JS::RootedValue c(cx);
// Attempt to minimise ugliness through macrosity
#define P(x) c = JS::NumberValue(colour.x); \
if (c.isNull()) \
return false; \
JS_SetProperty(cx, obj, #x, c.address())
P(r);
P(g);
P(b);
P(a);
#undef P
break;
}
case GUIST_CClientArea:
{
CClientArea area;
GUI::GetSetting(e, propName, area);
JS::RootedObject obj(cx, pScriptInterface->CreateCustomObject("GUISize"));
vp.setObject(*obj);
try
{
- ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
#define P(x, y, z) pScriptInterface->SetProperty(vp, #z, area.x.y, false, true)
P(pixel, left, left);
P(pixel, top, top);
P(pixel, right, right);
P(pixel, bottom, bottom);
P(percent, left, rleft);
P(percent, top, rtop);
P(percent, right, rright);
P(percent, bottom, rbottom);
#undef P
}
catch (PSERROR_Scripting_ConversionFailed&)
{
debug_warn(L"Error creating size object!");
break;
}
break;
}
case GUIST_CGUIString:
{
CGUIString value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value.GetOriginalString());
break;
}
case GUIST_CStr:
{
CStr value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_CStrW:
{
CStrW value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_CGUISpriteInstance:
{
CGUISpriteInstance *value;
GUI::GetSettingPointer(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value->GetName());
break;
}
case GUIST_EAlign:
{
EAlign value;
GUI::GetSetting(e, propName, value);
CStr word;
switch (value)
{
case EAlign_Left: word = "left"; break;
case EAlign_Right: word = "right"; break;
case EAlign_Center: word = "center"; break;
default: debug_warn(L"Invalid EAlign!"); word = "error"; break;
}
ScriptInterface::ToJSVal(cx, vp, word);
break;
}
case GUIST_EVAlign:
{
EVAlign value;
GUI::GetSetting(e, propName, value);
CStr word;
switch (value)
{
case EVAlign_Top: word = "top"; break;
case EVAlign_Bottom: word = "bottom"; break;
case EVAlign_Center: word = "center"; break;
default: debug_warn(L"Invalid EVAlign!"); word = "error"; break;
}
ScriptInterface::ToJSVal(cx, vp, word);
break;
}
case GUIST_CGUIList:
{
CGUIList value;
GUI::GetSetting(e, propName, value);
JS::RootedObject obj(cx, JS_NewArrayObject(cx, 0, NULL));
vp.setObject(*obj);
for (u32 i = 0; i < value.m_Items.size(); ++i)
{
JS::RootedValue val(cx);
ScriptInterface::ToJSVal(cx, &val, value.m_Items[i].GetOriginalString());
JS_SetElement(cx, obj, i, val.address());
}
break;
}
default:
JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str());
DEBUG_WARN_ERR(ERR::LOGIC);
return JS_FALSE;
}
return JS_TRUE;
}
}
JSBool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JSBool UNUSED(strict), JS::MutableHandleValue vp)
{
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return JS_FALSE;
JSAutoRequest rq(cx);
JS::RootedValue idval(cx);
if (!JS_IdToValue(cx, id, idval.address()))
return JS_FALSE;
std::string propName;
if (!ScriptInterface::FromJSVal(cx, idval, propName))
return JS_FALSE;
if (propName == "name")
{
std::string value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return JS_FALSE;
e->SetName(value);
return JS_TRUE;
}
// Use onWhatever to set event handlers
if (propName.substr(0, 2) == "on")
{
if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(cx, &vp.toObject()))
{
JS_ReportError(cx, "on- event-handlers must be functions");
return JS_FALSE;
}
CStr eventName (CStr(propName.substr(2)).LowerCase());
e->SetScriptHandler(eventName, &vp.toObject());
return JS_TRUE;
}
// Retrieve the setting's type (and make sure it actually exists)
EGUISettingType Type;
if (e->GetSettingType(propName, Type) != PSRETURN_OK)
{
JS_ReportError(cx, "Invalid setting '%s'", propName.c_str());
return JS_TRUE;
}
switch (Type)
{
case GUIST_CStr:
{
std::string value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return JS_FALSE;
GUI::SetSetting(e, propName, value);
break;
}
case GUIST_CStrW:
{
std::wstring value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return false;
GUI::SetSetting(e, propName, value);
break;
}
case GUIST_CGUISpriteInstance:
{
std::string value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return false;
GUI::SetSetting(e, propName, CGUISpriteInstance(value));
break;
}
case GUIST_CGUIString:
{
std::wstring value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return JS_FALSE;
CGUIString str;
str.SetValue(value);
GUI::SetSetting(e, propName, str);
break;
}
case GUIST_EAlign:
{
std::string value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return JS_FALSE;
EAlign a;
if (value == "left") a = EAlign_Left;
else if (value == "right") a = EAlign_Right;
else if (value == "center" || value == "centre") a = EAlign_Center;
else
{
JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')");
return JS_FALSE;
}
GUI::SetSetting(e, propName, a);
break;
}
case GUIST_EVAlign:
{
std::string value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return JS_FALSE;
EVAlign a;
if (value == "top") a = EVAlign_Top;
else if (value == "bottom") a = EVAlign_Bottom;
else if (value == "center" || value == "centre") a = EVAlign_Center;
else
{
JS_ReportError(cx, "Invalid alignment (should be 'top', 'bottom' or 'center')");
return JS_FALSE;
}
GUI::SetSetting(e, propName, a);
break;
}
case GUIST_int:
{
int value;
if (ScriptInterface::FromJSVal(cx, vp, value))
GUI::SetSetting(e, propName, value);
else
{
JS_ReportError(cx, "Cannot convert value to int");
return JS_FALSE;
}
break;
}
case GUIST_float:
{
double value;
if (JS_ValueToNumber(cx, vp, &value) == true)
GUI::SetSetting(e, propName, (float)value);
else
{
JS_ReportError(cx, "Cannot convert value to float");
return JS_FALSE;
}
break;
}
case GUIST_bool:
{
JSBool value;
if (JS_ValueToBoolean(cx, vp, &value))
GUI::SetSetting(e, propName, value);
else
{
JS_ReportError(cx, "Cannot convert value to bool");
return JS_FALSE;
}
break;
}
case GUIST_CClientArea:
{
if (vp.isString())
{
std::wstring value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return JS_FALSE;
if (e->SetSetting(propName, value) != PSRETURN_OK)
{
JS_ReportError(cx, "Invalid value for setting '%s'", propName.c_str());
return JS_FALSE;
}
}
else if (vp.isObject() && JS_InstanceOf(cx, &vp.toObject(), &JSI_GUISize::JSI_class, NULL))
{
CClientArea area;
GUI::GetSetting(e, propName, area);
ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
#define P(x, y, z) pScriptInterface->GetProperty(vp, #z, area.x.y)
P(pixel, left, left);
P(pixel, top, top);
P(pixel, right, right);
P(pixel, bottom, bottom);
P(percent, left, rleft);
P(percent, top, rtop);
P(percent, right, rright);
P(percent, bottom, rbottom);
#undef P
GUI::SetSetting(e, propName, area);
}
else
{
JS_ReportError(cx, "Size only accepts strings or GUISize objects");
return JS_FALSE;
}
break;
}
case GUIST_CColor:
{
if (vp.isString())
{
std::wstring value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return JS_FALSE;
if (e->SetSetting(propName, value) != PSRETURN_OK)
{
JS_ReportError(cx, "Invalid value for setting '%s'", propName.c_str());
return JS_FALSE;
}
}
else if (vp.isObject() && JS_InstanceOf(cx, &vp.toObject(), &JSI_GUIColor::JSI_class, NULL))
{
CColor colour;
JS::RootedObject (cx, &vp.toObject());
JS::RootedValue t(cx);
double s;
#define PROP(x) JS_GetProperty(cx, obj, #x, t.address()); \
s = t.toDouble(); \
colour.x = (float)s
PROP(r); PROP(g); PROP(b); PROP(a);
#undef PROP
GUI::SetSetting(e, propName, colour);
}
else
{
JS_ReportError(cx, "Color only accepts strings or GUIColor objects");
return JS_FALSE;
}
break;
}
case GUIST_CGUIList:
{
u32 length;
if (vp.isObject() && JS_GetArrayLength(cx, &vp.toObject(), &length) == true)
{
CGUIList list;
JS::RootedObject obj(cx, &vp.toObject());
for (u32 i=0; i::SetSetting(e, propName, list);
}
else
{
JS_ReportError(cx, "List only accepts a GUIList object");
return JS_FALSE;
}
break;
}
// TODO Gee: (2004-09-01) EAlign and EVAlign too.
default:
JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str());
break;
}
return !JS_IsExceptionPending(cx);
}
JSBool JSI_IGUIObject::construct(JSContext* cx, uint argc, jsval* vp)
{
JSAutoRequest rq(cx);
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
if (args.length() == 0)
{
JS_ReportError(cx, "GUIObject has no default constructor");
return JS_FALSE;
}
JS::RootedObject obj(cx, pScriptInterface->CreateCustomObject("GUIObject"));
// Store the IGUIObject in the JS object's 'private' area
IGUIObject* guiObject = (IGUIObject*)JSVAL_TO_PRIVATE(args[0]);
JS_SetPrivate(obj, guiObject);
args.rval().setObject(*obj);
return JS_TRUE;
}
void JSI_IGUIObject::init(ScriptInterface& scriptInterface)
{
scriptInterface.DefineCustomObjectType(&JSI_class, construct, 1, JSI_props, JSI_methods, NULL, NULL);
}
JSBool JSI_IGUIObject::toString(JSContext* cx, uint argc, jsval* vp)
{
UNUSED2(argc);
+ JSAutoRequest rq(cx);
JS::CallReceiver rec = JS::CallReceiverFromVp(vp);
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, JS_THIS_OBJECT(cx, vp), &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return JS_FALSE;
char buffer[256];
snprintf(buffer, 256, "[GUIObject: %s]", e->GetName().c_str());
buffer[255] = 0;
rec.rval().setString(JS_NewStringCopyZ(cx, buffer));
return JS_TRUE;
}
JSBool JSI_IGUIObject::focus(JSContext* cx, uint argc, jsval* vp)
{
UNUSED2(argc);
+ JSAutoRequest rq(cx);
JS::CallReceiver rec = JS::CallReceiverFromVp(vp);
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, JS_THIS_OBJECT(cx, vp), &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return JS_FALSE;
e->GetGUI()->SetFocusedObject(e);
rec.rval().setUndefined();
return JS_TRUE;
}
JSBool JSI_IGUIObject::blur(JSContext* cx, uint argc, jsval* vp)
{
UNUSED2(argc);
+ JSAutoRequest rq(cx);
JS::CallReceiver rec = JS::CallReceiverFromVp(vp);
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, JS_THIS_OBJECT(cx, vp), &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return JS_FALSE;
e->GetGUI()->SetFocusedObject(NULL);
rec.rval().setUndefined();
return JS_TRUE;
}
JSBool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, jsval* vp)
{
UNUSED2(argc);
+ JSAutoRequest rq(cx);
JS::CallReceiver rec = JS::CallReceiverFromVp(vp);
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, JS_THIS_OBJECT(cx, vp), &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return JS_FALSE;
e->UpdateCachedSize();
CRect size = e->m_CachedActualSize;
JS::RootedValue objVal(cx, JS::ObjectValue(*JS_NewObject(cx, NULL, NULL, NULL)));
try
{
ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
pScriptInterface->SetProperty(objVal, "left", size.left, false, true);
pScriptInterface->SetProperty(objVal, "right", size.right, false, true);
pScriptInterface->SetProperty(objVal, "top", size.top, false, true);
pScriptInterface->SetProperty(objVal, "bottom", size.bottom, false, true);
}
catch (PSERROR_Scripting_ConversionFailed&)
{
debug_warn(L"Error creating size object!");
return JS_FALSE;
}
rec.rval().set(objVal);
return JS_TRUE;
}
Index: ps/trunk/source/gui/scripting/ScriptFunctions.cpp
===================================================================
--- ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 15567)
+++ ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 15568)
@@ -1,982 +1,983 @@
/* Copyright (C) 2014 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/GameView.h"
#include "graphics/MapReader.h"
#include "gui/GUIManager.h"
#include "gui/GUI.h"
#include "gui/IGUIObject.h"
#include "gui/scripting/JSInterface_GUITypes.h"
#include "graphics/scripting/JSInterface_GameView.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/CLogger.h"
#include "ps/CConsole.h"
#include "ps/Errors.h"
#include "ps/Game.h"
#include "ps/Globals.h" // g_frequencyFilter
#include "ps/GUID.h"
#include "ps/World.h"
#include "ps/Hotkey.h"
#include "ps/Overlay.h"
#include "ps/ProfileViewer.h"
#include "ps/Pyrogenesis.h"
#include "ps/SavedGame.h"
#include "ps/scripting/JSInterface_ConfigDB.h"
#include "ps/scripting/JSInterface_Console.h"
#include "ps/scripting/JSInterface_VFS.h"
#include "ps/UserReport.h"
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/Config.h"
#include "renderer/scripting/JSInterface_Renderer.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpAIManager.h"
#include "simulation2/components/ICmpCommandQueue.h"
#include "simulation2/components/ICmpGuiInterface.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/helpers/Selection.h"
#include "soundmanager/SoundManager.h"
#include "soundmanager/scripting/JSInterface_Sound.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, std::wstring name, CScriptVal initData)
{
g_GUI->PushPage(name, pCxPrivate->pScriptInterface->WriteStructuredClone(initData.get()));
}
void SwitchGuiPage(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name, CScriptVal 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, CScriptVal args)
{
g_GUI->PopPageCB(pCxPrivate->pScriptInterface->WriteStructuredClone(args.get()));
}
CScriptVal GuiInterfaceCall(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name, CScriptVal data)
{
if (!g_Game)
return JSVAL_VOID;
CSimulation2* sim = g_Game->GetSimulation2();
ENSURE(sim);
CmpPtr cmpGuiInterface(*sim, SYSTEM_ENTITY);
if (!cmpGuiInterface)
return JSVAL_VOID;
int player = -1;
if (g_Game)
player = g_Game->GetPlayerID();
CScriptValRooted arg (sim->GetScriptInterface().GetContext(), sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), data.get()));
CScriptVal ret (cmpGuiInterface->ScriptCall(player, name, arg.get()));
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(sim->GetScriptInterface(), ret.get());
}
void PostNetworkCommand(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal cmd)
{
if (!g_Game)
return;
CSimulation2* sim = g_Game->GetSimulation2();
ENSURE(sim);
CmpPtr cmpCommandQueue(*sim, SYSTEM_ENTITY);
if (!cmpCommandQueue)
return;
jsval cmd2 = sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), cmd.get());
cmpCommandQueue->PostNetworkCommand(cmd2);
}
std::vector PickEntitiesAtPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y, int range)
{
return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID(), false, range);
}
std::vector PickFriendlyEntitiesInRect(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 PickFriendlyEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate, int player)
{
return PickFriendlyEntitiesInRect(pCxPrivate, 0, 0, g_xres, g_yres, player);
}
std::vector PickSimilarFriendlyEntities(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), 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), std::wstring name)
{
std::wstring old = g_CursorName;
g_CursorName = name;
return old;
}
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);
}
CScriptValRooted 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, CScriptVal 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();
CScriptValRooted gameAttribs (sim->GetScriptInterface().GetContext(),
sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), attribs.get()));
g_Game->SetPlayerID(playerID);
g_Game->StartGame(gameAttribs, "");
}
-CScriptVal StartSavedGame(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name)
+CScriptVal StartSavedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring name)
{
+ CSimulation2* sim = g_Game->GetSimulation2();
+ JSContext* cx = sim->GetScriptInterface().GetContext();
+ JSAutoRequest rq(cx);
+
ENSURE(!g_NetServer);
ENSURE(!g_NetClient);
ENSURE(!g_Game);
// Load the saved game data from disk
CScriptValRooted metadata;
std::string savedState;
- Status err = SavedGames::Load(name, *(pCxPrivate->pScriptInterface), metadata, savedState);
+ Status err = SavedGames::Load(name, sim->GetScriptInterface(), metadata, savedState);
if (err < 0)
return CScriptVal();
g_Game = new CGame();
+
+ JS::RootedValue gameMetadata(cx, metadata.get());
- // Convert from GUI script context to sim script context
- CSimulation2* sim = g_Game->GetSimulation2();
- CScriptValRooted gameMetadata (sim->GetScriptInterface().GetContext(),
- sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), metadata.get()));
-
- CScriptValRooted gameInitAttributes;
- sim->GetScriptInterface().GetProperty(gameMetadata.get(), "initAttributes", gameInitAttributes);
+ JS::RootedValue gameInitAttributes(cx);
+ sim->GetScriptInterface().GetProperty(gameMetadata, "initAttributes", &gameInitAttributes);
int playerID;
- sim->GetScriptInterface().GetProperty(gameMetadata.get(), "player", playerID);
+ sim->GetScriptInterface().GetProperty(gameMetadata, "player", playerID);
// Start the game
g_Game->SetPlayerID(playerID);
- g_Game->StartGame(gameInitAttributes, savedState);
+ g_Game->StartGame(CScriptValRooted(cx, gameInitAttributes), savedState);
- return metadata.get();
+ return gameMetadata.get();
}
void SaveGame(ScriptInterface::CxPrivate* pCxPrivate, std::wstring filename, std::wstring description, CScriptVal GUIMetadata)
{
shared_ptr GUIMetadataClone = pCxPrivate->pScriptInterface->WriteStructuredClone(GUIMetadata.get());
if (SavedGames::Save(filename, description, *g_Game->GetSimulation2(), GUIMetadataClone, g_Game->GetPlayerID()) < 0)
LOGERROR(L"Failed to save game");
}
void SaveGamePrefix(ScriptInterface::CxPrivate* pCxPrivate, std::wstring prefix, std::wstring description, CScriptVal GUIMetadata)
{
shared_ptr GUIMetadataClone = pCxPrivate->pScriptInterface->WriteStructuredClone(GUIMetadata.get());
if (SavedGames::SavePrefix(prefix, description, *g_Game->GetSimulation2(), GUIMetadataClone, g_Game->GetPlayerID()) < 0)
LOGERROR(L"Failed to save game");
}
void SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal attribs)
{
ENSURE(g_NetServer);
g_NetServer->UpdateGameAttributes(attribs, *(pCxPrivate->pScriptInterface));
}
void StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, 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);
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, std::wstring playerName, std::string serverAddress)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game);
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);
}
CScriptVal PollNetworkClient(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_NetClient)
return CScriptVal();
CScriptValRooted poll = g_NetClient->GuiPoll();
// Convert from net client context to GUI script context
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), poll.get());
}
void AssignNetworkPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int playerID, std::string guid)
{
ENSURE(g_NetServer);
g_NetServer->AssignPlayer(playerID, guid);
}
void SetNetworkPlayerStatus(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), 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), 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);
}
std::vector GetAIs(ScriptInterface::CxPrivate* pCxPrivate)
{
return ICmpAIManager::GetAIs(*(pCxPrivate->pScriptInterface));
}
std::vector GetSavedGames(ScriptInterface::CxPrivate* pCxPrivate)
{
return SavedGames::GetSavedGames(*(pCxPrivate->pScriptInterface));
}
bool DeleteSavedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring name)
{
return SavedGames::DeleteSavedGame(name);
}
void OpenURL(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), 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);
}
CScriptVal LoadMapSettings(ScriptInterface::CxPrivate* pCxPrivate, VfsPath pathname)
{
CMapSummaryReader reader;
if (reader.LoadMap(pathname) != PSRETURN_OK)
return CScriptVal();
return reader.GetMapSettings(*(pCxPrivate->pScriptInterface)).get();
}
CScriptVal GetMapSettings(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_Game)
return CScriptVal();
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(
g_Game->GetSimulation2()->GetScriptInterface(),
g_Game->GetSimulation2()->GetMapSettings().get());
}
/**
* 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), std::string hotkeyName)
{
return HotkeyIsPressed(hotkeyName);
}
void DisplayErrorDialog(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring msg)
{
debug_DisplayError(msg.c_str(), DE_NO_DEBUG_INFO, NULL, NULL, NULL, 0, NULL, NULL);
}
CScriptVal 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), std::string type, int version, 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(L"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(L"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(L"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(L"Terrain mipmap written to '%ls'", realPath.string().c_str());
}
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();
}
// 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;
}
CScriptVal GetGUIObjectByName(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStr name)
{
IGUIObject* guiObj = g_GUI->FindObjectByName(name);
if (guiObj)
return OBJECT_TO_JSVAL(guiObj->GetJSObject());
else
return JSVAL_VOID;
}
// 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 = L10n::Instance().ParseDateTime(__DATE__ " " __TIME__, "MMM d yyyy HH:mm:ss", Locale::getUS());
std::string dateTimeString = L10n::Instance().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), L10n::Instance().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), L10n::Instance().Translate("%s (%ls)").c_str(), dateTimeString.c_str(), svn_revision);
}
}
else if (mode == 0) // Date.
{
UDate dateTime = L10n::Instance().ParseDateTime(__DATE__, "MMM d yyyy", Locale::getUS());
std::string dateTimeString = L10n::Instance().LocalizeDateTime(dateTime, L10n::Date, SimpleDateFormat::MEDIUM);
sprintf_s(buf, ARRAY_SIZE(buf), "%s", dateTimeString.c_str());
}
else if (mode == 1) // Time.
{
UDate dateTime = L10n::Instance().ParseDateTime(__TIME__, "HH:mm:ss", Locale::getUS());
std::string dateTimeString = L10n::Instance().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", L10n::Instance().Translate("custom build").c_str());
}
else
{
sprintf_s(buf, ARRAY_SIZE(buf), "%ls", svn_revision);
}
}
return wstring_from_utf8(buf);
}
//-----------------------------------------------------------------------------
// 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(L"Exceeded the maximum number of timer slots for scripts!");
js_start_times[slot].SetFromTimer();
}
void StopJsTimer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), unsigned int slot)
{
if (slot >= MAX_JS_TIMERS)
LOGERROR(L"Exceeded the maximum number of timer slots for scripts!");
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_Sound::RegisterScriptFunctions(scriptInterface);
JSI_L10n::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, int, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
scriptInterface.RegisterFunction, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
scriptInterface.RegisterFunction, int, &PickFriendlyEntitiesOnScreen>("PickFriendlyEntitiesOnScreen");
scriptInterface.RegisterFunction, std::string, bool, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");
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("PollNetworkClient");
scriptInterface.RegisterFunction("SetNetworkGameAttributes");
scriptInterface.RegisterFunction("AssignNetworkPlayer");
scriptInterface.RegisterFunction("SetNetworkPlayerStatus");
scriptInterface.RegisterFunction("ClearAllPlayerReady");
scriptInterface.RegisterFunction("SendNetworkChat");
scriptInterface.RegisterFunction("SendNetworkReady");
scriptInterface.RegisterFunction, &GetAIs>("GetAIs");
scriptInterface.RegisterFunction("GetEngineInfo");
// Saved games
scriptInterface.RegisterFunction("StartSavedGame");
scriptInterface.RegisterFunction, &GetSavedGames>("GetSavedGames");
scriptInterface.RegisterFunction("DeleteSavedGame");
scriptInterface.RegisterFunction("SaveGame");
scriptInterface.RegisterFunction("SaveGamePrefix");
scriptInterface.RegisterFunction("QuickSave");
scriptInterface.RegisterFunction("QuickLoad");
// Misc functions
scriptInterface.RegisterFunction("SetCursor");
scriptInterface.RegisterFunction("GetPlayerID");
scriptInterface.RegisterFunction("SetPlayerID");
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");
// 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");
// Lobby functions
scriptInterface.RegisterFunction("HasXmppClient");
scriptInterface.RegisterFunction("IsRankedGame");
scriptInterface.RegisterFunction("SetRankedGame");
#if CONFIG2_LOBBY // Allow the lobby to be disabled
scriptInterface.RegisterFunction("StartXmppClient");
scriptInterface.RegisterFunction("StartRegisterXmppClient");
scriptInterface.RegisterFunction("StopXmppClient");
scriptInterface.RegisterFunction("ConnectXmppClient");
scriptInterface.RegisterFunction("DisconnectXmppClient");
scriptInterface.RegisterFunction("SendGetGameList");
scriptInterface.RegisterFunction("SendGetBoardList");
scriptInterface.RegisterFunction("SendGetRatingList");
scriptInterface.RegisterFunction("SendRegisterGame");
scriptInterface.RegisterFunction("SendGameReport");
scriptInterface.RegisterFunction("SendUnregisterGame");
scriptInterface.RegisterFunction("SendChangeStateGame");
scriptInterface.RegisterFunction("GetPlayerList");
scriptInterface.RegisterFunction("GetGameList");
scriptInterface.RegisterFunction("GetBoardList");
scriptInterface.RegisterFunction("LobbyGuiPollMessage");
scriptInterface.RegisterFunction("LobbySendMessage");
scriptInterface.RegisterFunction("LobbySetPlayerPresence");
scriptInterface.RegisterFunction("LobbySetNick");
scriptInterface.RegisterFunction("LobbyGetNick");
scriptInterface.RegisterFunction("LobbyKick");
scriptInterface.RegisterFunction("LobbyBan");
scriptInterface.RegisterFunction("LobbyGetPlayerPresence");
scriptInterface.RegisterFunction("LobbyGetPlayerRole");
scriptInterface.RegisterFunction("EncryptPassword");
scriptInterface.RegisterFunction("LobbyGetRoomSubject");
#endif // CONFIG2_LOBBY
}
Index: ps/trunk/source/lobby/XmppClient.cpp
===================================================================
--- ps/trunk/source/lobby/XmppClient.cpp (revision 15567)
+++ ps/trunk/source/lobby/XmppClient.cpp (revision 15568)
@@ -1,956 +1,968 @@
/* Copyright (C) 2014 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 "XmppClient.h"
#include "StanzaExtensions.h"
#include "lib/utf8.h"
// Debug
#include "ps/CLogger.h"
// Gloox
#include "glooxwrapper/glooxwrapper.h"
// Game - script
#include "scriptinterface/ScriptInterface.h"
// Configuration
#include "ps/ConfigDB.h"
//debug
#if 1
#define DbgXMPP(x)
#else
#define DbgXMPP(x) std::cout << x << std::endl;
static std::string tag_xml(const glooxwrapper::IQ& iq)
{
std::string ret;
glooxwrapper::Tag* tag = iq.tag();
ret = tag->xml().to_string();
glooxwrapper::Tag::free(tag);
return ret;
}
#endif
static std::string tag_name(const glooxwrapper::IQ& iq)
{
std::string ret;
glooxwrapper::Tag* tag = iq.tag();
ret = tag->name().to_string();
glooxwrapper::Tag::free(tag);
return ret;
}
IXmppClient* IXmppClient::create(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize,bool regOpt)
{
return new XmppClient(sUsername, sPassword, sRoom, sNick, historyRequestSize, regOpt);
}
/**
* Construct the XMPP client.
*
* @param sUsername Username to login with of register.
* @param sPassword Password to login with or register.
* @param sRoom MUC room to join.
* @param sNick Nick to join with.
* @param historyRequestSize Number of stanzas of room history to request.
* @param regOpt If we are just registering or not.
*/
XmppClient::XmppClient(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize, bool regOpt)
: m_client(NULL), m_mucRoom(NULL), m_registration(NULL), m_username(sUsername), m_password(sPassword), m_nick(sNick)
{
// Read lobby configuration from default.cfg
std::string sServer;
std::string sXpartamupp;
CFG_GET_VAL("lobby.server", String, sServer);
CFG_GET_VAL("lobby.xpartamupp", String, sXpartamupp);
m_xpartamuppId = sXpartamupp + "@" + sServer + "/CC";
glooxwrapper::JID clientJid(sUsername + "@" + sServer + "/0ad");
glooxwrapper::JID roomJid(sRoom + "@conference." + sServer + "/" + sNick);
// If we are connecting, use the full jid and a password
// If we are registering, only use the server name
if(!regOpt)
m_client = new glooxwrapper::Client(clientJid, sPassword);
else
m_client = new glooxwrapper::Client(sServer);
// Disable TLS as we haven't set a certificate on the server yet
m_client->setTls(gloox::TLSDisabled);
// Disable use of the SASL PLAIN mechanism, to prevent leaking credentials
// if the server doesn't list any supported SASL mechanism or the response
// has been modified to exclude those.
const int mechs = gloox::SaslMechAll ^ gloox::SaslMechPlain;
m_client->setSASLMechanisms(mechs);
m_client->registerConnectionListener( this );
m_client->setPresence(gloox::Presence::Available, -1);
m_client->disco()->setVersion( "Pyrogenesis", "0.0.17" );
m_client->disco()->setIdentity( "client", "bot" );
m_client->setCompression(false);
m_client->registerStanzaExtension( new GameListQuery() );
m_client->registerIqHandler( this, ExtGameListQuery);
m_client->registerStanzaExtension( new BoardListQuery() );
m_client->registerIqHandler( this, ExtBoardListQuery);
m_client->registerMessageHandler( this );
// Uncomment to see the raw stanzas
//m_client->getWrapped()->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, this );
if (!regOpt)
{
// Create a Multi User Chat Room
m_mucRoom = new glooxwrapper::MUCRoom(m_client, roomJid, this, 0);
// Get room history.
m_mucRoom->setRequestHistory(historyRequestSize, gloox::MUCRoom::HistoryMaxStanzas);
}
else
{
// Registration
m_registration = new glooxwrapper::Registration(m_client);
m_registration->registerRegistrationHandler(this);
}
}
/**
* Destroy the xmpp client
*/
XmppClient::~XmppClient()
{
DbgXMPP("XmppClient destroyed");
delete m_registration;
delete m_mucRoom;
// Workaround for memory leak in gloox 1.0/1.0.1
m_client->removePresenceExtension(gloox::ExtCaps);
delete m_client;
for (std::vector::const_iterator it = m_GameList.begin(); it != m_GameList.end(); ++it)
glooxwrapper::Tag::free(*it);
for (std::vector::const_iterator it = m_BoardList.begin(); it != m_BoardList.end(); ++it)
glooxwrapper::Tag::free(*it);
}
/// Network
void XmppClient::connect()
{
m_client->connect(false);
}
void XmppClient::disconnect()
{
m_client->disconnect();
}
void XmppClient::recv()
{
m_client->recv(1);
}
/**
* Log (debug) Handler
*/
void XmppClient::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message)
{
std::cout << "log: level: " << level << ", area: " << area << ", message: " << message << std::endl;
}
/*****************************************************
* Connection handlers *
*****************************************************/
/**
* Handle connection
*/
void XmppClient::onConnect()
{
if (m_mucRoom)
{
CreateSimpleMessage("system", "connected");
m_mucRoom->join();
}
if (m_registration)
m_registration->fetchRegistrationFields();
}
/**
* Handle disconnection
*/
void XmppClient::onDisconnect(gloox::ConnectionError error)
{
// Make sure we properly leave the room so that
// everything works if we decide to come back later
if (m_mucRoom)
m_mucRoom->leave();
// Clear game, board and player lists.
for (std::vector::const_iterator it = m_GameList.begin(); it != m_GameList.end(); ++it)
glooxwrapper::Tag::free(*it);
for (std::vector::const_iterator it = m_BoardList.begin(); it != m_BoardList.end(); ++it)
glooxwrapper::Tag::free(*it);
m_BoardList.clear();
m_GameList.clear();
m_PlayerMap.clear();
if(error == gloox::ConnAuthenticationFailed)
CreateSimpleMessage("system", "authentication failed", "error");
else
CreateSimpleMessage("system", "disconnected");
}
/**
* Handle TLS connection
*/
bool XmppClient::onTLSConnect(const glooxwrapper::CertInfo& info)
{
UNUSED2(info);
DbgXMPP("onTLSConnect");
DbgXMPP(
"status: " << info.status <<
"\nissuer: " << info.issuer <<
"\npeer: " << info.server <<
"\nprotocol: " << info.protocol <<
"\nmac: " << info.mac <<
"\ncipher: " << info.cipher <<
"\ncompression: " << info.compression );
return true;
}
/**
* Handle MUC room errors
*/
void XmppClient::handleMUCError(glooxwrapper::MUCRoom*, gloox::StanzaError err)
{
std::string msg = StanzaErrorToString(err);
CreateSimpleMessage("system", msg, "error");
}
/*****************************************************
* Requests to server *
*****************************************************/
/**
* Request a listing of active games from the server.
*/
void XmppClient::SendIqGetGameList()
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid);
iq.addExtension(new GameListQuery());
DbgXMPP("SendIqGetGameList [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Request the leaderboard data from the server.
*/
void XmppClient::SendIqGetBoardList()
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
BoardListQuery* b = new BoardListQuery();
b->m_Command = "getleaderboard";
glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid);
iq.addExtension(b);
DbgXMPP("SendIqGetBoardList [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Request the rating data from the server.
*/
void XmppClient::SendIqGetRatingList()
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
BoardListQuery* b = new BoardListQuery();
b->m_Command = "getratinglist";
glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid);
iq.addExtension(b);
DbgXMPP("SendIqGetRatingList [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Send game report containing numerous game properties to the server.
*
* @param data A JS array of game statistics
*/
void XmppClient::SendIqGameReport(ScriptInterface& scriptInterface, CScriptVal data)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
JS::RootedValue dataval(scriptInterface.GetContext(), data.get());
// Setup some base stanza attributes
GameReport* game = new GameReport();
glooxwrapper::Tag* report = glooxwrapper::Tag::allocate("game");
// Iterate through all the properties reported and add them to the stanza.
std::vector properties;
scriptInterface.EnumeratePropertyNamesWithPrefix(dataval, "", properties);
for (std::vector::size_type i = 0; i != properties.size(); i++)
{
std::wstring value;
scriptInterface.GetProperty(dataval, properties[i].c_str(), value);
report->addAttribute(properties[i], utf8_from_wstring(value));
}
// Add stanza to IQ
game->m_GameReport.push_back(report);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid);
iq.addExtension(game);
DbgXMPP("SendGameReport [" << tag_xml(iq) << "]");
m_client->send(iq);
};
/**
* Send a request to register a game to the server.
*
* @param data A JS array of game attributes
*/
void XmppClient::SendIqRegisterGame(ScriptInterface& scriptInterface, CScriptVal data)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
JS::RootedValue dataval(scriptInterface.GetContext(), data.get());
// Setup some base stanza attributes
GameListQuery* g = new GameListQuery();
g->m_Command = "register";
glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
// Add a fake ip which will be overwritten by the ip stamp XMPP module on the server.
game->addAttribute("ip", "fake");
// Iterate through all the properties reported and add them to the stanza.
std::vector properties;
scriptInterface.EnumeratePropertyNamesWithPrefix(dataval, "", properties);
for (std::vector::size_type i = 0; i != properties.size(); i++)
{
std::wstring value;
scriptInterface.GetProperty(dataval, properties[i].c_str(), value);
game->addAttribute(properties[i], utf8_from_wstring(value));
}
// Push the stanza onto the IQ
g->m_GameList.push_back(game);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid);
iq.addExtension(g);
DbgXMPP("SendIqRegisterGame [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Send a request to unregister a game to the server.
*/
void XmppClient::SendIqUnregisterGame()
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
GameListQuery* g = new GameListQuery();
g->m_Command = "unregister";
g->m_GameList.push_back(glooxwrapper::Tag::allocate( "game" ));
glooxwrapper::IQ iq( gloox::IQ::Set, xpartamuppJid );
iq.addExtension( g );
DbgXMPP("SendIqUnregisterGame [" << tag_xml(iq) << "]");
m_client->send( iq );
}
/**
* Send a request to change the state of a registered game on the server.
*
* A game can either be in the 'running' or 'waiting' state - the server
* decides which - but we need to update the current players that are
* in-game so the server can make the calculation.
*/
void XmppClient::SendIqChangeStateGame(const std::string& nbp, const std::string& players)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
GameListQuery* g = new GameListQuery();
g->m_Command = "changestate";
glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
game->addAttribute("nbp", nbp);
game->addAttribute("players", players);
g->m_GameList.push_back(game);
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid);
iq.addExtension( g );
DbgXMPP("SendIqChangeStateGame [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/*****************************************************
* Account registration *
*****************************************************/
void XmppClient::handleRegistrationFields(const glooxwrapper::JID&, int fields, glooxwrapper::string)
{
glooxwrapper::RegistrationFields vals;
vals.username = m_username;
vals.password = m_password;
m_registration->createAccount(fields, vals);
}
void XmppClient::handleRegistrationResult(const glooxwrapper::JID&, gloox::RegistrationResult result)
{
if (result == gloox::RegistrationSuccess)
{
CreateSimpleMessage("system", "registered");
}
else
{
std::string msg;
#define CASE(X, Y) case gloox::X: msg = Y; break
switch(result)
{
CASE(RegistrationNotAcceptable, "Registration not acceptable");
CASE(RegistrationConflict, "Registration conflict");
CASE(RegistrationNotAuthorized, "Registration not authorized");
CASE(RegistrationBadRequest, "Registration bad request");
CASE(RegistrationForbidden, "Registration forbidden");
CASE(RegistrationRequired, "Registration required");
CASE(RegistrationUnexpectedRequest, "Registration unexpected request");
CASE(RegistrationNotAllowed, "Registration not allowed");
default: msg = "Registration unknown error";
}
#undef CASE
CreateSimpleMessage("system", msg, "error");
}
disconnect();
}
void XmppClient::handleAlreadyRegistered(const glooxwrapper::JID&)
{
DbgXMPP("the account already exists");
}
void XmppClient::handleDataForm(const glooxwrapper::JID&, const glooxwrapper::DataForm&)
{
DbgXMPP("dataForm received");
}
void XmppClient::handleOOB(const glooxwrapper::JID&, const glooxwrapper::OOB&)
{
DbgXMPP("OOB registration requested");
}
/*****************************************************
* Requests from GUI *
*****************************************************/
/**
* Handle requests from the GUI for the list of players.
*
* @return A JS array containing all known players and their presences
*/
CScriptValRooted XmppClient::GUIGetPlayerList(ScriptInterface& scriptInterface)
{
- CScriptValRooted playerList;
- scriptInterface.Eval("([])", playerList);
+ JSContext* cx = scriptInterface.GetContext();
+ JSAutoRequest rq(cx);
+
+ JS::RootedValue playerList(cx);
+ scriptInterface.Eval("([])", &playerList);
// Convert the internal data structure to a Javascript object.
for (std::map