Index: ps/trunk/source/graphics/MapGenerator.cpp
===================================================================
--- ps/trunk/source/graphics/MapGenerator.cpp (revision 21112)
+++ ps/trunk/source/graphics/MapGenerator.cpp (revision 21113)
@@ -1,291 +1,316 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2018 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 "graphics/MapIO.h"
#include "graphics/Terrain.h"
+#include "lib/status.h"
#include "lib/timer.h"
#include "ps/CLogger.h"
#include "ps/Profile.h"
#include "ps/scripting/JSInterface_VFS.h"
+#include "scriptinterface/ScriptConversions.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("Engine", "MapGenerator", ScriptInterface::CreateRuntime(g_ScriptRuntime, 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()
{
// We must destroy the ScriptInterface in the same thread because the JSAPI requires that!
// Also we must not be in a request when calling the ScriptInterface destructor, so the autoFree object
// must be instantiated before the request (destructors are called in reverse order of instantiation)
struct AutoFree {
AutoFree(ScriptInterface* p) : m_p(p) {}
~AutoFree() { SAFE_DELETE(m_p); }
ScriptInterface* m_p;
} autoFree(m_ScriptInterface);
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);
// Functions for RMS
JSI_VFS::RegisterScriptFunctions_Maps(*m_ScriptInterface);
m_ScriptInterface->RegisterFunction("LoadLibrary");
+ m_ScriptInterface->RegisterFunction("LoadHeightmapImage");
m_ScriptInterface->RegisterFunction("ExportMap");
m_ScriptInterface->RegisterFunction("SetProgress");
m_ScriptInterface->RegisterFunction("GetTemplate");
m_ScriptInterface->RegisterFunction("TemplateExists");
m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindTemplates>("FindTemplates");
m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindActorTemplates>("FindActorTemplates");
m_ScriptInterface->RegisterFunction("GetTerrainTileSize");
// Globalscripts may use VFS script functions
m_ScriptInterface->LoadGlobalScripts();
// Parse settings
JS::RootedValue settingsVal(cx);
if (!m_ScriptInterface->ParseJSON(m_Settings, &settingsVal) && settingsVal.isUndefined())
{
LOGERROR("CMapGeneratorWorker::Run: Failed to parse settings");
return false;
}
// Prevent unintentional modifications to the settings object by random map scripts
if (!m_ScriptInterface->FreezeObject(settingsVal, true))
{
LOGERROR("CMapGeneratorWorker::Run: Failed to deepfreeze settings");
return false;
}
// Init RNG seed
u32 seed = 0;
if (!m_ScriptInterface->HasProperty(settingsVal, "Seed") ||
!m_ScriptInterface->GetProperty(settingsVal, "Seed", seed))
LOGWARNING("CMapGeneratorWorker::Run: No seed value specified - using 0");
m_MapGenRNG.seed(seed);
// Copy settings to global variable
JS::RootedValue global(cx, m_ScriptInterface->GetGlobalObject());
if (!m_ScriptInterface->SetProperty(global, "g_MapSettings", settingsVal, true, true))
{
LOGERROR("CMapGeneratorWorker::Run: Failed to define g_MapSettings");
return false;
}
// Load RMS
LOGMESSAGE("Loading RMS '%s'", m_ScriptPath.string8());
if (!m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath))
{
LOGERROR("CMapGeneratorWorker::Run: Failed to load RMS '%s'", m_ScriptPath.string8());
return false;
}
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, const std::wstring& name)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
return self->LoadScripts(name);
}
void CMapGeneratorWorker::ExportMap(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
// Copy results
CScopeLock lock(self->m_WorkerMutex);
self->m_MapData = self->m_ScriptInterface->WriteStructuredClone(data);
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;
}
CParamNode CMapGeneratorWorker::GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
const CParamNode& templateRoot = self->m_TemplateLoader.GetTemplateFileData(templateName).GetChild("Entity");
if (!templateRoot.IsOk())
LOGERROR("Invalid template found for '%s'", templateName.c_str());
return templateRoot;
}
bool CMapGeneratorWorker::TemplateExists(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
return self->m_TemplateLoader.TemplateExists(templateName);
}
std::vector CMapGeneratorWorker::FindTemplates(ScriptInterface::CxPrivate* pCxPrivate, const 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, const std::string& path, bool includeSubdirectories)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
return self->m_TemplateLoader.FindTemplates(path, includeSubdirectories, ACTOR_TEMPLATES);
}
int CMapGeneratorWorker::GetTerrainTileSize(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return TERRAIN_TILE_SIZE;
}
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 (const VfsPath& p : pathnames)
{
LOGMESSAGE("Loading map generator script '%s'", p.string8());
if (!m_ScriptInterface->LoadGlobalScriptFile(p))
{
LOGERROR("CMapGeneratorWorker::LoadScripts: Failed to load script '%s'", p.string8());
return false;
}
}
}
else
{
// Some error reading directory
wchar_t error[200];
LOGERROR("CMapGeneratorWorker::LoadScripts: Error reading scripts in directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error))));
return false;
}
return true;
}
+JS::Value CMapGeneratorWorker::LoadHeightmap(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& vfsPath)
+{
+ OsPath realPath;
+ if (g_VFS->GetRealPath(vfsPath, realPath) != INFO::OK)
+ return JS::UndefinedValue();
+
+ std::vector heightmap;
+ if (LoadHeightmapImage(realPath, heightmap) != INFO::OK)
+ {
+ LOGERROR("Could not load heightmap file '%s'", utf8_from_wstring(vfsPath).c_str());
+ return JS::UndefinedValue();
+ }
+
+ CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
+ JSContext* cx = self->m_ScriptInterface->GetContext();
+ JSAutoRequest rq(cx);
+ JS::RootedValue returnValue(cx);
+ ToJSVal_vector(cx, &returnValue, heightmap);
+ return returnValue;
+}
+
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
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/MapGenerator.h
===================================================================
--- ps/trunk/source/graphics/MapGenerator.h (revision 21112)
+++ ps/trunk/source/graphics/MapGenerator.h (revision 21113)
@@ -1,151 +1,152 @@
/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_MAPGENERATOR
#define INCLUDED_MAPGENERATOR
#include "ps/FileIo.h"
#include "ps/ThreadUtil.h"
#include "ps/TemplateLoader.h"
#include "scriptinterface/ScriptInterface.h"
#include
#include
class CMapGeneratorWorker;
/**
* Random map generator interface. Initialized by CMapReader and then checked
* periodically during loading, until it's finished (progress value is 0).
*
* The actual work is performed by CMapGeneratorWorker in a separate thread.
*/
class CMapGenerator
{
NONCOPYABLE(CMapGenerator);
public:
CMapGenerator();
~CMapGenerator();
/**
* Start the map generator thread
*
* @param scriptFile The VFS path for the script, e.g. "maps/random/latium.js"
* @param settings JSON string containing settings for the map generator
*/
void GenerateMap(const VfsPath& scriptFile, const std::string& settings);
/**
* Get status of the map generator thread
*
* @return Progress percentage 1-100 if active, 0 when finished, or -1 on error
*/
int GetProgress();
/**
* Get random map data, according to this format:
* http://trac.wildfiregames.com/wiki/Random_Map_Generator_Internals#Dataformat
*
* @return StructuredClone containing map data
*/
shared_ptr GetResults();
private:
CMapGeneratorWorker* m_Worker;
};
/**
* Random map generator worker thread.
* (This is run in a thread so that the GUI remains responsive while loading)
*
* Thread-safety:
* - Initialize and constructor/destructor must be called from the main thread.
* - ScriptInterface created and destroyed by thread
* - StructuredClone used to return JS map data - JS:Values can't be used across threads/runtimes.
*/
class CMapGeneratorWorker
{
public:
CMapGeneratorWorker();
~CMapGeneratorWorker();
/**
* Start the map generator thread
*
* @param scriptFile The VFS path for the script, e.g. "maps/random/latium.js"
* @param settings JSON string containing settings for the map generator
*/
void Initialize(const VfsPath& scriptFile, const std::string& settings);
/**
* Get status of the map generator thread
*
* @return Progress percentage 1-100 if active, 0 when finished, or -1 on error
*/
int GetProgress();
/**
* Get random map data, according to this format:
* http://trac.wildfiregames.com/wiki/Random_Map_Generator_Internals#Dataformat
*
* @return StructuredClone containing map data
*/
shared_ptr GetResults();
private:
// Mapgen
/**
* Load all scripts of the given library
*
* @param libraryName String specifying name of the library (subfolder of ../maps/random/)
* @return true if all scripts ran successfully, false if there's an error
*/
bool LoadScripts(const std::wstring& libraryName);
// callbacks for script functions
static bool LoadLibrary(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name);
static void ExportMap(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data);
+ static JS::Value LoadHeightmap(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& src);
static void SetProgress(ScriptInterface::CxPrivate* pCxPrivate, int progress);
static CParamNode GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName);
static bool TemplateExists(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName);
static std::vector FindTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories);
static std::vector FindActorTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories);
static int GetTerrainTileSize(ScriptInterface::CxPrivate* pCxPrivate);
std::set m_LoadedLibraries;
shared_ptr m_MapData;
boost::rand48 m_MapGenRNG;
int m_Progress;
ScriptInterface* m_ScriptInterface;
VfsPath m_ScriptPath;
std::string m_Settings;
CTemplateLoader m_TemplateLoader;
// Thread
static void* RunThread(void* data);
bool Run();
pthread_t m_WorkerThread;
CMutex m_WorkerMutex;
};
#endif //INCLUDED_MAPGENERATOR
Index: ps/trunk/source/graphics/MapIO.cpp
===================================================================
--- ps/trunk/source/graphics/MapIO.cpp (nonexistent)
+++ ps/trunk/source/graphics/MapIO.cpp (revision 21113)
@@ -0,0 +1,74 @@
+/* Copyright (C) 2018 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 "MapIO.h"
+
+#include "graphics/Patch.h"
+#include "lib/file/file.h"
+#include "lib/os_path.h"
+#include "lib/status.h"
+#include "lib/tex/tex.h"
+#include "maths/MathUtil.h"
+
+Status LoadHeightmapImage(const OsPath& filepath, std::vector& heightmap)
+{
+ File file;
+ RETURN_STATUS_IF_ERR(file.Open(OsString(filepath), O_RDONLY));
+
+ size_t fileSize = lseek(file.Descriptor(), 0, SEEK_END);
+ lseek(file.Descriptor(), 0, SEEK_SET);
+
+ shared_ptr fileData = shared_ptr(new u8[fileSize]);
+
+ Status readvalue = read(file.Descriptor(), fileData.get(), fileSize);
+ file.Close();
+ RETURN_STATUS_IF_ERR(readvalue);
+
+ // Decode to a raw pixel format
+ Tex tex;
+ RETURN_STATUS_IF_ERR(tex.decode(fileData, fileSize));
+
+ // Convert to uncompressed BGRA with no mipmaps
+ RETURN_STATUS_IF_ERR(tex.transform_to((tex.m_Flags | TEX_BGR | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)));
+
+ // Pick smallest side of texture; truncate if not divisible by PATCH_SIZE
+ ssize_t tileSize = std::min(tex.m_Width, tex.m_Height);
+ tileSize -= tileSize % PATCH_SIZE;
+
+ u8* mapdata = tex.get_data();
+ ssize_t bytesPP = tex.m_Bpp / 8;
+ ssize_t mapLineSkip = tex.m_Width * bytesPP;
+
+ // Copy image data into the heightmap
+ heightmap.resize(SQR(tileSize + 1));
+ for (ssize_t y = 0; y < tileSize + 1; ++y)
+ for (ssize_t x = 0; x < tileSize + 1; ++x)
+ {
+ // Repeat the last pixel of the image for the last vertex of the heightmap
+ int offset = std::min(y, tileSize - 1) * mapLineSkip + std::min(x, tileSize - 1) * bytesPP;
+
+ // Pick color channel with highest value
+ u16 value = std::max({mapdata[offset], mapdata[offset + bytesPP], mapdata[offset + bytesPP * 2]});
+ value = mapdata[offset];
+
+ heightmap[(tileSize - y) * (tileSize + 1) + x] = clamp(value * 256, 0, 65535);
+ }
+
+ return INFO::OK;
+}
Property changes on: ps/trunk/source/graphics/MapIO.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/source/graphics/MapIO.h
===================================================================
--- ps/trunk/source/graphics/MapIO.h (revision 21112)
+++ ps/trunk/source/graphics/MapIO.h (revision 21113)
@@ -1,42 +1,48 @@
/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_MAPIO
#define INCLUDED_MAPIO
+#include "lib/os_path.h"
+#include "lib/status.h"
+
+// Opens the given texture file and stores it in a one-dimensional u16 vector.
+Status LoadHeightmapImage(const OsPath& filepath, std::vector& heightmap);
+
class CMapIO
{
public:
// current file version given to saved maps
enum { FILE_VERSION = 6 };
// supported file read version - file with version less than this will be reject
enum { FILE_READ_VERSION = 6 };
#pragma pack(push, 1)
// description of a tile for I/O purposes
struct STileDesc {
// index into the texture array of first texture on tile
u16 m_Tex1Index;
// index into the texture array of second texture; (0xFFFF) if none
u16 m_Tex2Index;
// priority
u32 m_Priority;
};
#pragma pack(pop)
};
#endif
Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp
===================================================================
--- ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp (revision 21112)
+++ ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp (revision 21113)
@@ -1,394 +1,344 @@
/* Copyright (C) 2018 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 "MessageHandler.h"
#include "../GameLoop.h"
#include "../CommandProc.h"
#include "graphics/GameView.h"
#include "graphics/LOSTexture.h"
+#include "graphics/MapIO.h"
#include "graphics/MapWriter.h"
#include "graphics/Patch.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "lib/bits.h"
-#include "lib/file/file.h"
-#include "lib/tex/tex.h"
+#include "lib/file/vfs/vfs_path.h"
+#include "lib/status.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/Loader.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTerrain.h"
namespace
{
void InitGame()
{
if (g_Game)
{
delete g_Game;
g_Game = NULL;
}
g_Game = new CGame(false, false);
// Default to player 1 for playtesting
g_Game->SetPlayerID(1);
}
void StartGame(JS::MutableHandleValue attrs)
{
g_Game->StartGame(attrs, "");
// TODO: Non progressive load can fail - need a decent way to handle this
LDR_NonprogressiveLoad();
// Disable fog-of-war - this must be done before starting the game,
// as visual actors cache their visibility state on first render.
CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (cmpRangeManager)
cmpRangeManager->SetLosRevealAll(-1, true);
PSRETURN ret = g_Game->ReallyStartGame();
ENSURE(ret == PSRETURN_OK);
}
}
namespace AtlasMessage {
QUERYHANDLER(GenerateMap)
{
try
{
InitGame();
// Random map
const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
JS::RootedValue settings(cx);
scriptInterface.ParseJSON(*msg->settings, &settings);
scriptInterface.SetProperty(settings, "mapType", std::string("random"));
JS::RootedValue attrs(cx);
scriptInterface.Eval("({})", &attrs);
scriptInterface.SetProperty(attrs, "mapType", std::string("random"));
scriptInterface.SetProperty(attrs, "script", std::wstring(*msg->filename));
scriptInterface.SetProperty(attrs, "settings", settings);
StartGame(&attrs);
msg->status = 0;
}
catch (PSERROR_Game_World_MapLoadFailed&)
{
// Cancel loading
LDR_Cancel();
// Since map generation failed and we don't know why, use the blank map as a fallback
InitGame();
const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
JS::RootedValue settings(cx);
scriptInterface.Eval("({})", &settings);
// Set up 8-element array of empty objects to satisfy init
JS::RootedValue playerData(cx);
scriptInterface.Eval("([])", &playerData);
for (int i = 0; i < 8; ++i)
{
JS::RootedValue player(cx);
scriptInterface.Eval("({})", &player);
scriptInterface.SetPropertyInt(playerData, i, player);
}
scriptInterface.SetProperty(settings, "mapType", std::string("scenario"));
scriptInterface.SetProperty(settings, "PlayerData", playerData);
JS::RootedValue atts(cx);
scriptInterface.Eval("({})", &atts);
scriptInterface.SetProperty(atts, "mapType", std::string("scenario"));
scriptInterface.SetProperty(atts, "map", std::wstring(L"maps/scenarios/_default"));
scriptInterface.SetProperty(atts, "settings", settings);
StartGame(&atts);
msg->status = -1;
}
}
MESSAGEHANDLER(LoadMap)
{
InitGame();
const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
// Scenario
CStrW map = *msg->filename;
CStrW mapBase = map.BeforeLast(L".pmp"); // strip the file extension, if any
JS::RootedValue attrs(cx);
scriptInterface.Eval("({})", &attrs);
scriptInterface.SetProperty(attrs, "mapType", std::string("scenario"));
scriptInterface.SetProperty(attrs, "map", std::wstring(mapBase));
StartGame(&attrs);
}
MESSAGEHANDLER(ImportHeightmap)
{
- CStrW src = *msg->filename;
-
- size_t fileSize;
- shared_ptr fileData;
-
- // read in image file
- File file;
- if (file.Open(src, O_RDONLY) < 0)
- {
- LOGERROR("Failed to load heightmap.");
- return;
- }
-
- fileSize = lseek(file.Descriptor(), 0, SEEK_END);
- lseek(file.Descriptor(), 0, SEEK_SET);
-
- fileData = shared_ptr(new u8[fileSize]);
-
- if (read(file.Descriptor(), fileData.get(), fileSize) < 0)
- {
- LOGERROR("Failed to read heightmap image.");
- file.Close();
- return;
- }
-
- file.Close();
-
- // decode to a raw pixel format
- Tex tex;
- if (tex.decode(fileData, fileSize) < 0)
+ std::vector heightmap_source;
+ if (LoadHeightmapImage(*msg->filename, heightmap_source) != INFO::OK)
{
LOGERROR("Failed to decode heightmap.");
return;
}
- // Convert to uncompressed BGRA with no mipmaps
- if (tex.transform_to((tex.m_Flags | TEX_BGR | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)) < 0)
- {
- LOGERROR("Failed to transform heightmap.");
- return;
- }
-
- // pick smallest side of texture; truncate if not divisible by PATCH_SIZE
- ssize_t terrainSize = std::min(tex.m_Width, tex.m_Height);
- terrainSize -= terrainSize % PATCH_SIZE;
-
// resize terrain to heightmap size
+ // Notice that the number of tiles/pixels per side of the heightmap image is
+ // one less than the number of vertices per side of the heightmap.
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
- terrain->Resize(terrainSize / PATCH_SIZE);
- ENSURE(terrainSize + 1 == g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide());
+ terrain->Resize((sqrt(heightmap_source.size()) - 1) / PATCH_SIZE);
// copy heightmap data into map
u16* heightmap = g_Game->GetWorld()->GetTerrain()->GetHeightMap();
-
- u8* mapdata = tex.get_data();
- ssize_t bytesPP = tex.m_Bpp / 8;
- ssize_t mapLineSkip = tex.m_Width * bytesPP;
-
- for (ssize_t y = 0; y < terrainSize + 1; ++y)
- {
- for (ssize_t x = 0; x < terrainSize + 1; ++x)
- {
- // repeat the last pixel of the image for the last vertex of the heightmap
- int offset = std::min(y, terrainSize - 1) * mapLineSkip + std::min(x, terrainSize - 1) * bytesPP;
-
- // pick color channel with highest value
- u16 value = std::max(mapdata[offset+bytesPP*2], std::max(mapdata[offset], mapdata[offset+bytesPP]));
- heightmap[(terrainSize - y) * (terrainSize + 1) + x] = clamp(value * 256, 0, 65535);
- }
- }
+ ENSURE(heightmap_source.size() == (std::size_t) SQR(g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide()));
+ std::copy(heightmap_source.begin(), heightmap_source.end(), heightmap);
// update simulation
CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
- if (cmpTerrain) cmpTerrain->ReloadTerrain();
+ if (cmpTerrain)
+ cmpTerrain->ReloadTerrain();
+
g_Game->GetView()->GetLOSTexture().MakeDirty();
}
MESSAGEHANDLER(SaveMap)
{
CMapWriter writer;
VfsPath pathname = VfsPath(*msg->filename).ChangeExtension(L".pmp");
writer.SaveMap(pathname,
g_Game->GetWorld()->GetTerrain(),
g_Renderer.GetWaterManager(), g_Renderer.GetSkyManager(),
&g_LightEnv, g_Game->GetView()->GetCamera(), g_Game->GetView()->GetCinema(),
&g_Renderer.GetPostprocManager(),
g_Game->GetSimulation2());
}
QUERYHANDLER(GetMapSettings)
{
msg->settings = g_Game->GetSimulation2()->GetMapSettingsString();
}
BEGIN_COMMAND(SetMapSettings)
{
std::string m_OldSettings, m_NewSettings;
void SetSettings(const std::string& settings)
{
g_Game->GetSimulation2()->SetMapSettings(settings);
}
void Do()
{
m_OldSettings = g_Game->GetSimulation2()->GetMapSettingsString();
m_NewSettings = *msg->settings;
SetSettings(m_NewSettings);
}
// TODO: we need some way to notify the Atlas UI when the settings are changed
// externally, otherwise this will have no visible effect
void Undo()
{
// SetSettings(m_OldSettings);
}
void Redo()
{
// SetSettings(m_NewSettings);
}
void MergeIntoPrevious(cSetMapSettings* prev)
{
prev->m_NewSettings = m_NewSettings;
}
};
END_COMMAND(SetMapSettings)
MESSAGEHANDLER(LoadPlayerSettings)
{
g_Game->GetSimulation2()->LoadPlayerSettings(msg->newplayers);
}
QUERYHANDLER(GetMapSizes)
{
msg->sizes = g_Game->GetSimulation2()->GetMapSizes();
}
QUERYHANDLER(GetRMSData)
{
msg->data = g_Game->GetSimulation2()->GetRMSData();
}
BEGIN_COMMAND(ResizeMap)
{
int m_OldTiles, m_NewTiles;
cResizeMap()
{
}
void MakeDirty()
{
CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->ReloadTerrain();
// The LOS texture won't normally get updated when running Atlas
// (since there's no simulation updates), so explicitly dirty it
g_Game->GetView()->GetLOSTexture().MakeDirty();
}
void ResizeTerrain(int tiles)
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
terrain->Resize(tiles / PATCH_SIZE);
MakeDirty();
}
void Do()
{
CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (!cmpTerrain)
{
m_OldTiles = m_NewTiles = 0;
}
else
{
m_OldTiles = (int)cmpTerrain->GetTilesPerSide();
m_NewTiles = msg->tiles;
}
ResizeTerrain(m_NewTiles);
}
void Undo()
{
ResizeTerrain(m_OldTiles);
}
void Redo()
{
ResizeTerrain(m_NewTiles);
}
};
END_COMMAND(ResizeMap)
QUERYHANDLER(VFSFileExists)
{
msg->exists = VfsFileExists(*msg->path);
}
static Status AddToFilenames(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
std::vector& filenames = *(std::vector*)cbData;
filenames.push_back(pathname.string().c_str());
return INFO::OK;
}
QUERYHANDLER(GetMapList)
{
std::vector scenarioFilenames;
vfs::ForEachFile(g_VFS, L"maps/scenarios/", AddToFilenames, (uintptr_t)&scenarioFilenames, L"*.xml", vfs::DIR_RECURSIVE);
msg->scenarioFilenames = scenarioFilenames;
std::vector skirmishFilenames;
vfs::ForEachFile(g_VFS, L"maps/skirmishes/", AddToFilenames, (uintptr_t)&skirmishFilenames, L"*.xml", vfs::DIR_RECURSIVE);
msg->skirmishFilenames = skirmishFilenames;
}
}