1 /* Copyright (C) 2017 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include "MapReader.h"
21 
22 #include "graphics/Camera.h"
23 #include "graphics/CinemaManager.h"
24 #include "graphics/Entity.h"
25 #include "graphics/GameView.h"
26 #include "graphics/MapGenerator.h"
27 #include "graphics/Patch.h"
28 #include "graphics/Terrain.h"
29 #include "graphics/TerrainTextureEntry.h"
30 #include "graphics/TerrainTextureManager.h"
31 #include "lib/timer.h"
32 #include "lib/external_libraries/libsdl.h"
33 #include "maths/MathUtil.h"
34 #include "ps/CLogger.h"
35 #include "ps/Loader.h"
36 #include "ps/LoaderThunks.h"
37 #include "ps/World.h"
38 #include "ps/XML/Xeromyces.h"
39 #include "renderer/PostprocManager.h"
40 #include "renderer/SkyManager.h"
41 #include "renderer/WaterManager.h"
42 #include "simulation2/Simulation2.h"
43 #include "simulation2/components/ICmpCinemaManager.h"
44 #include "simulation2/components/ICmpObstruction.h"
45 #include "simulation2/components/ICmpOwnership.h"
46 #include "simulation2/components/ICmpPlayer.h"
47 #include "simulation2/components/ICmpPlayerManager.h"
48 #include "simulation2/components/ICmpPosition.h"
49 #include "simulation2/components/ICmpTerrain.h"
50 #include "simulation2/components/ICmpVisual.h"
51 #include "simulation2/components/ICmpWaterManager.h"
52 
53 #include <boost/algorithm/string/predicate.hpp>
54 
55 
CMapReader()56 CMapReader::CMapReader()
57 	: xml_reader(0), m_PatchesPerSide(0), m_MapGen(0)
58 {
59 	cur_terrain_tex = 0;	// important - resets generator state
60 }
61 
62 // LoadMap: try to load the map from given file; reinitialise the scene to new data if successful
LoadMap(const VfsPath & pathname,JSRuntime * rt,JS::HandleValue 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)63 void CMapReader::LoadMap(const VfsPath& pathname, JSRuntime* rt,  JS::HandleValue settings, CTerrain *pTerrain_,
64 						 WaterManager* pWaterMan_, SkyManager* pSkyMan_,
65 						 CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_,
66 						 CSimulation2 *pSimulation2_, const CSimContext* pSimContext_, int playerID_, bool skipEntities)
67 {
68 	pTerrain = pTerrain_;
69 	pLightEnv = pLightEnv_;
70 	pGameView = pGameView_;
71 	pWaterMan = pWaterMan_;
72 	pSkyMan = pSkyMan_;
73 	pCinema = pCinema_;
74 	pTrigMan = pTrigMan_;
75 	pPostproc = pPostproc_;
76 	pSimulation2 = pSimulation2_;
77 	pSimContext = pSimContext_;
78 	m_PlayerID = playerID_;
79 	m_SkipEntities = skipEntities;
80 	m_StartingCameraTarget = INVALID_ENTITY;
81 	m_ScriptSettings.init(rt, settings);
82 
83 	filename_xml = pathname.ChangeExtension(L".xml");
84 
85 	// In some cases (particularly tests) we don't want to bother storing a large
86 	// mostly-empty .pmp file, so we let the XML file specify basic terrain instead.
87 	// If there's an .xml file and no .pmp, then we're probably in this XML-only mode
88 	only_xml = false;
89 	if (!VfsFileExists(pathname) && VfsFileExists(filename_xml))
90 	{
91 		only_xml = true;
92 	}
93 
94 	file_format_version = CMapIO::FILE_VERSION; // default if there's no .pmp
95 
96 	if (!only_xml)
97 	{
98 		// [25ms]
99 		unpacker.Read(pathname, "PSMP");
100 		file_format_version = unpacker.GetVersion();
101 	}
102 
103 	// check oldest supported version
104 	if (file_format_version < FILE_READ_VERSION)
105 		throw PSERROR_File_InvalidVersion();
106 
107 	// delete all existing entities
108 	if (pSimulation2)
109 		pSimulation2->ResetState();
110 
111 	// reset post effects
112 	if (pPostproc)
113 		pPostproc->SetPostEffect(L"default");
114 
115 	// load map or script settings script
116 	if (settings.isUndefined())
117 		RegMemFun(this, &CMapReader::LoadScriptSettings, L"CMapReader::LoadScriptSettings", 50);
118 	else
119 		RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50);
120 
121 	// load player settings script (must be done before reading map)
122 	RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50);
123 
124 	// unpack the data
125 	if (!only_xml)
126 		RegMemFun(this, &CMapReader::UnpackMap, L"CMapReader::UnpackMap", 1200);
127 
128 	// read the corresponding XML file
129 	RegMemFun(this, &CMapReader::ReadXML, L"CMapReader::ReadXML", 50);
130 
131 	// apply terrain data to the world
132 	RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5);
133 
134 	// read entities
135 	RegMemFun(this, &CMapReader::ReadXMLEntities, L"CMapReader::ReadXMLEntities", 5800);
136 
137 	// apply misc data to the world
138 	RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5);
139 
140 	// load map settings script (must be done after reading map)
141 	RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5);
142 }
143 
144 // LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful
LoadRandomMap(const CStrW & scriptFile,JSRuntime * rt,JS::HandleValue settings,CTerrain * pTerrain_,WaterManager * pWaterMan_,SkyManager * pSkyMan_,CLightEnv * pLightEnv_,CGameView * pGameView_,CCinemaManager * pCinema_,CTriggerManager * pTrigMan_,CPostprocManager * pPostproc_,CSimulation2 * pSimulation2_,int playerID_)145 void CMapReader::LoadRandomMap(const CStrW& scriptFile, JSRuntime* rt, JS::HandleValue settings, CTerrain *pTerrain_,
146 						 WaterManager* pWaterMan_, SkyManager* pSkyMan_,
147 						 CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_,
148 						 CSimulation2 *pSimulation2_, int playerID_)
149 {
150 	m_ScriptFile = scriptFile;
151 	pSimulation2 = pSimulation2_;
152 	pSimContext = pSimulation2 ? &pSimulation2->GetSimContext() : NULL;
153 	m_ScriptSettings.init(rt, settings);
154 	pTerrain = pTerrain_;
155 	pLightEnv = pLightEnv_;
156 	pGameView = pGameView_;
157 	pWaterMan = pWaterMan_;
158 	pSkyMan = pSkyMan_;
159 	pCinema = pCinema_;
160 	pTrigMan = pTrigMan_;
161 	pPostproc = pPostproc_;
162 	m_PlayerID = playerID_;
163 	m_SkipEntities = false;
164 	m_StartingCameraTarget = INVALID_ENTITY;
165 
166 	// delete all existing entities
167 	if (pSimulation2)
168 		pSimulation2->ResetState();
169 
170 	only_xml = false;
171 
172 	// copy random map settings (before entity creation)
173 	RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50);
174 
175 	// load player settings script (must be done before reading map)
176 	RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50);
177 
178 	// load map generator with random map script
179 	RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 20000);
180 
181 	// parse RMS results into terrain structure
182 	RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500);
183 
184 	// parse RMS results into environment settings
185 	RegMemFun(this, &CMapReader::ParseEnvironment, L"CMapReader::ParseEnvironment", 5);
186 
187 	// parse RMS results into camera settings
188 	RegMemFun(this, &CMapReader::ParseCamera, L"CMapReader::ParseCamera", 5);
189 
190 	// apply terrain data to the world
191 	RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5);
192 
193 	// parse RMS results into entities
194 	RegMemFun(this, &CMapReader::ParseEntities, L"CMapReader::ParseEntities", 1000);
195 
196 	// apply misc data to the world
197 	RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5);
198 
199 	// load map settings script (must be done after reading map)
200 	RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5);
201 }
202 
203 // UnpackMap: unpack the given data from the raw data stream into local variables
UnpackMap()204 int CMapReader::UnpackMap()
205 {
206 	return UnpackTerrain();
207 }
208 
209 // UnpackTerrain: unpack the terrain from the end of the input data stream
210 //		- data: map size, heightmap, list of textures used by map, texture tile assignments
UnpackTerrain()211 int CMapReader::UnpackTerrain()
212 {
213 	// yield after this time is reached. balances increased progress bar
214 	// smoothness vs. slowing down loading.
215 	const double end_time = timer_Time() + 200e-3;
216 
217 	// first call to generator (this is skipped after first call,
218 	// i.e. when the loop below was interrupted)
219 	if (cur_terrain_tex == 0)
220 	{
221 		m_PatchesPerSide = (ssize_t)unpacker.UnpackSize();
222 
223 		// unpack heightmap [600us]
224 		size_t verticesPerSide = m_PatchesPerSide*PATCH_SIZE+1;
225 		m_Heightmap.resize(SQR(verticesPerSide));
226 		unpacker.UnpackRaw(&m_Heightmap[0], SQR(verticesPerSide)*sizeof(u16));
227 
228 		// unpack # textures
229 		num_terrain_tex = unpacker.UnpackSize();
230 		m_TerrainTextures.reserve(num_terrain_tex);
231 	}
232 
233 	// unpack texture names; find handle for each texture.
234 	// interruptible.
235 	while (cur_terrain_tex < num_terrain_tex)
236 	{
237 		CStr texturename;
238 		unpacker.UnpackString(texturename);
239 
240 		ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
241 		CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texturename);
242 		m_TerrainTextures.push_back(texentry);
243 
244 		cur_terrain_tex++;
245 		LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex);
246 	}
247 
248 	// unpack tile data [3ms]
249 	ssize_t tilesPerSide = m_PatchesPerSide*PATCH_SIZE;
250 	m_Tiles.resize(size_t(SQR(tilesPerSide)));
251 	unpacker.UnpackRaw(&m_Tiles[0], sizeof(STileDesc)*m_Tiles.size());
252 
253 	// reset generator state.
254 	cur_terrain_tex = 0;
255 
256 	return 0;
257 }
258 
ApplyTerrainData()259 int CMapReader::ApplyTerrainData()
260 {
261 	if (m_PatchesPerSide == 0)
262 	{
263 		// we'll probably crash when trying to use this map later
264 		throw PSERROR_Game_World_MapLoadFailed("Error loading map: no terrain data.\nCheck application log for details.");
265 	}
266 
267 	if (!only_xml)
268 	{
269 		// initialise the terrain
270 		pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]);
271 
272 		// setup the textures on the minipatches
273 		STileDesc* tileptr = &m_Tiles[0];
274 		for (ssize_t j=0; j<m_PatchesPerSide; j++) {
275 			for (ssize_t i=0; i<m_PatchesPerSide; i++) {
276 				for (ssize_t m=0; m<PATCH_SIZE; m++) {
277 					for (ssize_t k=0; k<PATCH_SIZE; k++) {
278 						CMiniPatch& mp = pTerrain->GetPatch(i,j)->m_MiniPatches[m][k];	// can't fail
279 
280 						mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index];
281 						mp.Priority = tileptr->m_Priority;
282 
283 						tileptr++;
284 					}
285 				}
286 			}
287 		}
288 	}
289 
290 	CmpPtr<ICmpTerrain> cmpTerrain(*pSimContext, SYSTEM_ENTITY);
291 	if (cmpTerrain)
292 		cmpTerrain->ReloadTerrain();
293 
294 	return 0;
295 }
296 
297 // ApplyData: take all the input data, and rebuild the scene from it
ApplyData()298 int CMapReader::ApplyData()
299 {
300 	// copy over the lighting parameters
301 	if (pLightEnv)
302 		*pLightEnv = m_LightEnv;
303 
304 	CmpPtr<ICmpPlayerManager> cmpPlayerManager(*pSimContext, SYSTEM_ENTITY);
305 
306 	if (pGameView && cmpPlayerManager)
307 	{
308 		// Default to global camera (with constraints)
309 		pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus());
310 
311 		// TODO: Starting rotation?
312 		CmpPtr<ICmpPlayer> cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID));
313 		if (cmpPlayer && cmpPlayer->HasStartingCamera())
314 		{
315 			// Use player starting camera
316 			CFixedVector3D pos = cmpPlayer->GetStartingCameraPos();
317 			pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()));
318 		}
319 		else if (m_StartingCameraTarget != INVALID_ENTITY)
320 		{
321 			// Point camera at entity
322 			CmpPtr<ICmpPosition> cmpPosition(*pSimContext, m_StartingCameraTarget);
323 			if (cmpPosition)
324 			{
325 				CFixedVector3D pos = cmpPosition->GetPosition();
326 				pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()));
327 			}
328 		}
329 	}
330 
331 	return 0;
332 }
333 
334 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
335 
336 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
337 
338 
LoadMap(const VfsPath & pathname)339 PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname)
340 {
341 	VfsPath filename_xml = pathname.ChangeExtension(L".xml");
342 
343 	CXeromyces xmb_file;
344 	if (xmb_file.Load(g_VFS, filename_xml, "scenario") != PSRETURN_OK)
345 		return PSRETURN_File_ReadFailed;
346 
347 	// Define all the relevant elements used in the XML file
348 	#define EL(x) int el_##x = xmb_file.GetElementID(#x)
349 	#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
350 	EL(scenario);
351 	EL(scriptsettings);
352 	#undef AT
353 	#undef EL
354 
355 	XMBElement root = xmb_file.GetRoot();
356 	ENSURE(root.GetNodeName() == el_scenario);
357 
358 	XERO_ITER_EL(root, child)
359 	{
360 		int child_name = child.GetNodeName();
361 		if (child_name == el_scriptsettings)
362 		{
363 			m_ScriptSettings = child.GetText();
364 		}
365 	}
366 
367 	return PSRETURN_OK;
368 }
369 
GetMapSettings(const ScriptInterface & scriptInterface,JS::MutableHandleValue ret)370 void CMapSummaryReader::GetMapSettings(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
371 {
372 	JSContext* cx = scriptInterface.GetContext();
373 	JSAutoRequest rq(cx);
374 
375 	scriptInterface.Eval("({})", ret);
376 	if (m_ScriptSettings.empty())
377 		return;
378 
379 	JS::RootedValue scriptSettingsVal(cx);
380 	scriptInterface.ParseJSON(m_ScriptSettings, &scriptSettingsVal);
381 	scriptInterface.SetProperty(ret, "settings", scriptSettingsVal, false);
382 }
383 
384 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
385 
386 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
387 
388 
389 // Holds various state data while reading maps, so that loading can be
390 // interrupted (e.g. to update the progress display) then later resumed.
391 class CXMLReader
392 {
393 	NONCOPYABLE(CXMLReader);
394 public:
CXMLReader(const VfsPath & xml_filename,CMapReader & mapReader)395 	CXMLReader(const VfsPath& xml_filename, CMapReader& mapReader)
396 		: m_MapReader(mapReader), nodes(NULL, 0, NULL)
397 	{
398 		Init(xml_filename);
399 	}
400 
401 	CStr ReadScriptSettings();
402 
403 	// read everything except for entities
404 	void ReadXML();
405 
406 	// return semantics: see Loader.cpp!LoadFunc.
407 	int ProgressiveReadEntities();
408 
409 private:
410 	CXeromyces xmb_file;
411 
412 	CMapReader& m_MapReader;
413 
414 	int el_entity;
415 	int el_tracks;
416 	int el_template, el_player;
417 	int el_position, el_orientation, el_obstruction;
418 	int el_actor;
419 	int at_x, at_y, at_z;
420 	int at_group, at_group2;
421 	int at_angle;
422 	int at_uid;
423 	int at_seed;
424 
425 	XMBElementList nodes; // children of root
426 
427 	// loop counters
428 	size_t node_idx;
429 	size_t entity_idx;
430 
431 	// # entities+nonentities processed and total (for progress calc)
432 	int completed_jobs, total_jobs;
433 
434 	// maximum used entity ID, so we can safely allocate new ones
435 	entity_id_t max_uid;
436 
437 	void Init(const VfsPath& xml_filename);
438 
439 	void ReadTerrain(XMBElement parent);
440 	void ReadEnvironment(XMBElement parent);
441 	void ReadCamera(XMBElement parent);
442 	void ReadPaths(XMBElement parent);
443 	void ReadTriggers(XMBElement parent);
444 	int ReadEntities(XMBElement parent, double end_time);
445 };
446 
447 
Init(const VfsPath & xml_filename)448 void CXMLReader::Init(const VfsPath& xml_filename)
449 {
450 	// must only assign once, so do it here
451 	node_idx = entity_idx = 0;
452 
453 	if (xmb_file.Load(g_VFS, xml_filename, "scenario") != PSRETURN_OK)
454 		throw PSERROR_File_ReadFailed();
455 
456 	// define the elements and attributes that are frequently used in the XML file,
457 	// so we don't need to do lots of string construction and comparison when
458 	// reading the data.
459 	// (Needs to be synchronised with the list in CXMLReader - ugh)
460 #define EL(x) el_##x = xmb_file.GetElementID(#x)
461 #define AT(x) at_##x = xmb_file.GetAttributeID(#x)
462 	EL(entity);
463 	EL(tracks);
464 	EL(template);
465 	EL(player);
466 	EL(position);
467 	EL(orientation);
468 	EL(obstruction);
469 	EL(actor);
470 	AT(x); AT(y); AT(z);
471 	AT(group); AT(group2);
472 	AT(angle);
473 	AT(uid);
474 	AT(seed);
475 #undef AT
476 #undef EL
477 
478 	XMBElement root = xmb_file.GetRoot();
479 	ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario");
480 	nodes = root.GetChildNodes();
481 
482 	// find out total number of entities+nonentities
483 	// (used when calculating progress)
484 	completed_jobs = 0;
485 	total_jobs = 0;
486 	for (XMBElement node : nodes)
487 		total_jobs += node.GetChildNodes().size();
488 
489 	// Find the maximum entity ID, so we can safely allocate new IDs without conflicts
490 
491 	max_uid = SYSTEM_ENTITY;
492 
493 	XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities"));
494 	XERO_ITER_EL(ents, ent)
495 	{
496 		CStr uid = ent.GetAttributes().GetNamedItem(at_uid);
497 		max_uid = std::max(max_uid, (entity_id_t)uid.ToUInt());
498 	}
499 }
500 
501 
ReadScriptSettings()502 CStr CXMLReader::ReadScriptSettings()
503 {
504 	XMBElement root = xmb_file.GetRoot();
505 	ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario");
506 	nodes = root.GetChildNodes();
507 
508 	XMBElement settings = nodes.GetFirstNamedItem(xmb_file.GetElementID("ScriptSettings"));
509 
510 	return settings.GetText();
511 }
512 
513 
ReadTerrain(XMBElement parent)514 void CXMLReader::ReadTerrain(XMBElement parent)
515 {
516 #define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
517 	AT(patches);
518 	AT(texture);
519 	AT(priority);
520 	AT(height);
521 #undef AT
522 
523 	ssize_t patches = 9;
524 	CStr texture = "grass1_spring";
525 	int priority = 0;
526 	u16 height = 16384;
527 
528 	XERO_ITER_ATTR(parent, attr)
529 	{
530 		if (attr.Name == at_patches)
531 			patches = attr.Value.ToInt();
532 		else if (attr.Name == at_texture)
533 			texture = attr.Value;
534 		else if (attr.Name == at_priority)
535 			priority = attr.Value.ToInt();
536 		else if (attr.Name == at_height)
537 			height = (u16)attr.Value.ToInt();
538 	}
539 
540 	m_MapReader.m_PatchesPerSide = patches;
541 
542 	// Load the texture
543 	ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
544 	CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texture);
545 
546 	m_MapReader.pTerrain->Initialize(patches, NULL);
547 
548 	// Fill the heightmap
549 	u16* heightmap = m_MapReader.pTerrain->GetHeightMap();
550 	ssize_t verticesPerSide = m_MapReader.pTerrain->GetVerticesPerSide();
551 	for (ssize_t i = 0; i < SQR(verticesPerSide); ++i)
552 		heightmap[i] = height;
553 
554 	// Fill the texture map
555 	for (ssize_t pz = 0; pz < patches; ++pz)
556 	{
557 		for (ssize_t px = 0; px < patches; ++px)
558 		{
559 			CPatch* patch = m_MapReader.pTerrain->GetPatch(px, pz);	// can't fail
560 
561 			for (ssize_t z = 0; z < PATCH_SIZE; ++z)
562 			{
563 				for (ssize_t x = 0; x < PATCH_SIZE; ++x)
564 				{
565 					patch->m_MiniPatches[z][x].Tex = texentry;
566 					patch->m_MiniPatches[z][x].Priority = priority;
567 				}
568 			}
569 		}
570 	}
571 }
572 
ReadEnvironment(XMBElement parent)573 void CXMLReader::ReadEnvironment(XMBElement parent)
574 {
575 #define EL(x) int el_##x = xmb_file.GetElementID(#x)
576 #define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
577 	EL(posteffect);
578 	EL(skyset);
579 	EL(suncolor);
580 	EL(sunelevation);
581 	EL(sunrotation);
582 	EL(terrainambientcolor);
583 	EL(unitsambientcolor);
584 	EL(water);
585 	EL(waterbody);
586 	EL(type);
587 	EL(color);
588 	EL(tint);
589 	EL(height);
590 	EL(waviness);
591 	EL(murkiness);
592 	EL(windangle);
593 	EL(fog);
594 	EL(fogcolor);
595 	EL(fogfactor);
596 	EL(fogthickness);
597 	EL(postproc);
598 	EL(brightness);
599 	EL(contrast);
600 	EL(saturation);
601 	EL(bloom);
602 	AT(r); AT(g); AT(b);
603 #undef AT
604 #undef EL
605 
606 	XERO_ITER_EL(parent, element)
607 	{
608 		int element_name = element.GetNodeName();
609 
610 		XMBAttributeList attrs = element.GetAttributes();
611 
612 		if (element_name == el_skyset)
613 		{
614 			if (m_MapReader.pSkyMan)
615 				m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8());
616 		}
617 		else if (element_name == el_suncolor)
618 		{
619 			m_MapReader.m_LightEnv.m_SunColor = RGBColor(
620 				attrs.GetNamedItem(at_r).ToFloat(),
621 				attrs.GetNamedItem(at_g).ToFloat(),
622 				attrs.GetNamedItem(at_b).ToFloat());
623 		}
624 		else if (element_name == el_sunelevation)
625 		{
626 			m_MapReader.m_LightEnv.m_Elevation = attrs.GetNamedItem(at_angle).ToFloat();
627 		}
628 		else if (element_name == el_sunrotation)
629 		{
630 			m_MapReader.m_LightEnv.m_Rotation = attrs.GetNamedItem(at_angle).ToFloat();
631 		}
632 		else if (element_name == el_terrainambientcolor)
633 		{
634 			m_MapReader.m_LightEnv.m_TerrainAmbientColor = RGBColor(
635 				attrs.GetNamedItem(at_r).ToFloat(),
636 				attrs.GetNamedItem(at_g).ToFloat(),
637 				attrs.GetNamedItem(at_b).ToFloat());
638 		}
639 		else if (element_name == el_unitsambientcolor)
640 		{
641 			m_MapReader.m_LightEnv.m_UnitsAmbientColor = RGBColor(
642 				attrs.GetNamedItem(at_r).ToFloat(),
643 				attrs.GetNamedItem(at_g).ToFloat(),
644 				attrs.GetNamedItem(at_b).ToFloat());
645 		}
646 		else if (element_name == el_fog)
647 		{
648 			XERO_ITER_EL(element, fog)
649 			{
650 				int element_name = fog.GetNodeName();
651 				if (element_name == el_fogcolor)
652 				{
653 					XMBAttributeList attrs = fog.GetAttributes();
654 					m_MapReader.m_LightEnv.m_FogColor = RGBColor(
655 						attrs.GetNamedItem(at_r).ToFloat(),
656 						attrs.GetNamedItem(at_g).ToFloat(),
657 						attrs.GetNamedItem(at_b).ToFloat());
658 				}
659 				else if (element_name == el_fogfactor)
660 				{
661 					m_MapReader.m_LightEnv.m_FogFactor = fog.GetText().ToFloat();
662 				}
663 				else if (element_name == el_fogthickness)
664 				{
665 					m_MapReader.m_LightEnv.m_FogMax = fog.GetText().ToFloat();
666 				}
667 			}
668 		}
669 		else if (element_name == el_postproc)
670 		{
671 			XERO_ITER_EL(element, postproc)
672 			{
673 				int element_name = postproc.GetNodeName();
674 				if (element_name == el_brightness)
675 				{
676 					m_MapReader.m_LightEnv.m_Brightness = postproc.GetText().ToFloat();
677 				}
678 				else if (element_name == el_contrast)
679 				{
680 					m_MapReader.m_LightEnv.m_Contrast = postproc.GetText().ToFloat();
681 				}
682 				else if (element_name == el_saturation)
683 				{
684 					m_MapReader.m_LightEnv.m_Saturation = postproc.GetText().ToFloat();
685 				}
686 				else if (element_name == el_bloom)
687 				{
688 					m_MapReader.m_LightEnv.m_Bloom = postproc.GetText().ToFloat();
689 				}
690 				else if (element_name == el_posteffect)
691 				{
692 					if (m_MapReader.pPostproc)
693 						m_MapReader.pPostproc->SetPostEffect(postproc.GetText().FromUTF8());
694 				}
695 			}
696 		}
697 		else if (element_name == el_water)
698 		{
699 			XERO_ITER_EL(element, waterbody)
700 			{
701 				ENSURE(waterbody.GetNodeName() == el_waterbody);
702 				XERO_ITER_EL(waterbody, waterelement)
703 				{
704 					int element_name = waterelement.GetNodeName();
705 					if (element_name == el_height)
706 					{
707 						CmpPtr<ICmpWaterManager> cmpWaterManager(*m_MapReader.pSimContext, SYSTEM_ENTITY);
708 						ENSURE(cmpWaterManager);
709 						cmpWaterManager->SetWaterLevel(entity_pos_t::FromString(waterelement.GetText()));
710 						continue;
711 					}
712 
713 					// The rest are purely graphical effects, and should be ignored if
714 					// graphics are disabled
715 					if (!m_MapReader.pWaterMan)
716 						continue;
717 
718 					if (element_name == el_type)
719 					{
720 						if (waterelement.GetText() == "default")
721 							m_MapReader.pWaterMan->m_WaterType = L"ocean";
722 						else
723 							m_MapReader.pWaterMan->m_WaterType =  waterelement.GetText().FromUTF8();
724 					}
725 #define READ_COLOR(el, out) \
726 					else if (element_name == el) \
727 					{ \
728 						XMBAttributeList attrs = waterelement.GetAttributes(); \
729 						out = CColor( \
730 							attrs.GetNamedItem(at_r).ToFloat(), \
731 							attrs.GetNamedItem(at_g).ToFloat(), \
732 							attrs.GetNamedItem(at_b).ToFloat(), \
733 							1.f); \
734 					}
735 
736 #define READ_FLOAT(el, out) \
737 					else if (element_name == el) \
738 					{ \
739 						out = waterelement.GetText().ToFloat(); \
740 					} \
741 
742 					READ_COLOR(el_color, m_MapReader.pWaterMan->m_WaterColor)
743 					READ_COLOR(el_tint, m_MapReader.pWaterMan->m_WaterTint)
744 					READ_FLOAT(el_waviness, m_MapReader.pWaterMan->m_Waviness)
745 					READ_FLOAT(el_murkiness, m_MapReader.pWaterMan->m_Murkiness)
746 					READ_FLOAT(el_windangle, m_MapReader.pWaterMan->m_WindAngle)
747 
748 #undef READ_FLOAT
749 #undef READ_COLOR
750 
751 					else
752 						debug_warn(L"Invalid map XML data");
753 				}
754 
755 			}
756 		}
757 		else
758 			debug_warn(L"Invalid map XML data");
759 	}
760 
761 	m_MapReader.m_LightEnv.CalculateSunDirection();
762 }
763 
ReadCamera(XMBElement parent)764 void CXMLReader::ReadCamera(XMBElement parent)
765 {
766 	// defaults if we don't find player starting camera
767 #define EL(x) int el_##x = xmb_file.GetElementID(#x)
768 #define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
769 	EL(declination);
770 	EL(rotation);
771 	EL(position);
772 	AT(angle);
773 	AT(x); AT(y); AT(z);
774 #undef AT
775 #undef EL
776 
777 	float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f);
778 	CVector3D translation = CVector3D(100, 150, -100);
779 
780 	XERO_ITER_EL(parent, element)
781 	{
782 		int element_name = element.GetNodeName();
783 
784 		XMBAttributeList attrs = element.GetAttributes();
785 		if (element_name == el_declination)
786 		{
787 			declination = attrs.GetNamedItem(at_angle).ToFloat();
788 		}
789 		else if (element_name == el_rotation)
790 		{
791 			rotation = attrs.GetNamedItem(at_angle).ToFloat();
792 		}
793 		else if (element_name == el_position)
794 		{
795 			translation = CVector3D(
796 				attrs.GetNamedItem(at_x).ToFloat(),
797 				attrs.GetNamedItem(at_y).ToFloat(),
798 				attrs.GetNamedItem(at_z).ToFloat());
799 		}
800 		else
801 			debug_warn(L"Invalid map XML data");
802 	}
803 
804 	if (m_MapReader.pGameView)
805 	{
806 		m_MapReader.pGameView->GetCamera()->m_Orientation.SetXRotation(declination);
807 		m_MapReader.pGameView->GetCamera()->m_Orientation.RotateY(rotation);
808 		m_MapReader.pGameView->GetCamera()->m_Orientation.Translate(translation);
809 		m_MapReader.pGameView->GetCamera()->UpdateFrustum();
810 	}
811 }
812 
ReadPaths(XMBElement parent)813 void CXMLReader::ReadPaths(XMBElement parent)
814 {
815 	#define EL(x) int el_##x = xmb_file.GetElementID(#x)
816 	#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
817 
818 	EL(path);
819 	EL(rotation);
820 	EL(node);
821 	EL(position);
822 	EL(target);
823 	AT(name);
824 	AT(timescale);
825 	AT(orientation);
826 	AT(mode);
827 	AT(style);
828 	AT(x);
829 	AT(y);
830 	AT(z);
831 	AT(deltatime);
832 
833 #undef EL
834 #undef AT
835 
836 	CmpPtr<ICmpCinemaManager> cmpCinemaManager(*m_MapReader.pSimContext, SYSTEM_ENTITY);
837 	XERO_ITER_EL(parent, element)
838 	{
839 		int elementName = element.GetNodeName();
840 
841 		if (elementName == el_path)
842 		{
843 			CCinemaData pathData;
844 			XMBAttributeList attrs = element.GetAttributes();
845 			CStrW pathName(attrs.GetNamedItem(at_name).FromUTF8());
846 			pathData.m_Name = pathName;
847 			pathData.m_Timescale = fixed::FromString(attrs.GetNamedItem(at_timescale));
848 			pathData.m_Orientation = attrs.GetNamedItem(at_orientation).FromUTF8();
849 			pathData.m_Mode = attrs.GetNamedItem(at_mode).FromUTF8();
850 			pathData.m_Style = attrs.GetNamedItem(at_style).FromUTF8();
851 
852 			TNSpline positionSpline, targetSpline;
853 			fixed lastPositionTime = fixed::Zero();
854 			fixed lastTargetTime = fixed::Zero();
855 
856 			XERO_ITER_EL(element, pathChild)
857 			{
858 				elementName = pathChild.GetNodeName();
859 				attrs = pathChild.GetAttributes();
860 
861 				// Load node data used for spline
862 				if (elementName == el_node)
863 				{
864 					lastPositionTime += fixed::FromString(attrs.GetNamedItem(at_deltatime));
865 					lastTargetTime += fixed::FromString(attrs.GetNamedItem(at_deltatime));
866 					XERO_ITER_EL(pathChild, nodeChild)
867 					{
868 						elementName = nodeChild.GetNodeName();
869 						attrs = nodeChild.GetAttributes();
870 
871 						if (elementName == el_position)
872 						{
873 							CFixedVector3D position(fixed::FromString(attrs.GetNamedItem(at_x)),
874 								fixed::FromString(attrs.GetNamedItem(at_y)),
875 								fixed::FromString(attrs.GetNamedItem(at_z)));
876 
877 							positionSpline.AddNode(position, CFixedVector3D(), lastPositionTime);
878 							lastPositionTime = fixed::Zero();
879 						}
880 						else if (elementName == el_rotation)
881 						{
882 							// TODO: Implement rotation slerp/spline as another object
883 						}
884 						else if (elementName == el_target)
885 						{
886 							CFixedVector3D targetPosition(fixed::FromString(attrs.GetNamedItem(at_x)),
887 								fixed::FromString(attrs.GetNamedItem(at_y)),
888 								fixed::FromString(attrs.GetNamedItem(at_z)));
889 
890 							targetSpline.AddNode(targetPosition, CFixedVector3D(), lastTargetTime);
891 							lastTargetTime = fixed::Zero();
892 						}
893 						else
894 							LOGWARNING("Invalid cinematic element for node child");
895 					}
896 				}
897 				else
898 					LOGWARNING("Invalid cinematic element for path child");
899 			}
900 
901 			// Construct cinema path with data gathered
902 			CCinemaPath path(pathData, positionSpline, targetSpline);
903 			if (path.Empty())
904 			{
905 				LOGWARNING("Path with name '%s' is empty", pathName.ToUTF8());
906 				return;
907 			}
908 
909 			if (!cmpCinemaManager)
910 				continue;
911 			if (!cmpCinemaManager->HasPath(pathName))
912 				cmpCinemaManager->AddPath(path);
913 			else
914 				LOGWARNING("Path with name '%s' already exists", pathName.ToUTF8());
915 		}
916 		else
917 			LOGWARNING("Invalid path child with name '%s'", element.GetText());
918 	}
919 }
920 
ReadTriggers(XMBElement UNUSED (parent))921 void CXMLReader::ReadTriggers(XMBElement UNUSED(parent))
922 {
923 }
924 
ReadEntities(XMBElement parent,double end_time)925 int CXMLReader::ReadEntities(XMBElement parent, double end_time)
926 {
927 	XMBElementList entities = parent.GetChildNodes();
928 
929 	ENSURE(m_MapReader.pSimulation2);
930 	CSimulation2& sim = *m_MapReader.pSimulation2;
931 	CmpPtr<ICmpPlayerManager> cmpPlayerManager(sim, SYSTEM_ENTITY);
932 
933 	while (entity_idx < entities.size())
934 	{
935 		// all new state at this scope and below doesn't need to be
936 		// wrapped, since we only yield after a complete iteration.
937 
938 		XMBElement entity = entities[entity_idx++];
939 		ENSURE(entity.GetNodeName() == el_entity);
940 
941 		XMBAttributeList attrs = entity.GetAttributes();
942 		CStr uid = attrs.GetNamedItem(at_uid);
943 		ENSURE(!uid.empty());
944 		int EntityUid = uid.ToInt();
945 
946 		CStrW TemplateName;
947 		int PlayerID = 0;
948 		CFixedVector3D Position;
949 		CFixedVector3D Orientation;
950 		long Seed = -1;
951 
952 		// Obstruction control groups.
953 		entity_id_t ControlGroup = INVALID_ENTITY;
954 		entity_id_t ControlGroup2 = INVALID_ENTITY;
955 
956 		XERO_ITER_EL(entity, setting)
957 		{
958 			int element_name = setting.GetNodeName();
959 
960 			// <template>
961 			if (element_name == el_template)
962 			{
963 				TemplateName = setting.GetText().FromUTF8();
964 			}
965 			// <player>
966 			else if (element_name == el_player)
967 			{
968 				PlayerID = setting.GetText().ToInt();
969 			}
970 			// <position>
971 			else if (element_name == el_position)
972 			{
973 				XMBAttributeList attrs = setting.GetAttributes();
974 				Position = CFixedVector3D(
975 					fixed::FromString(attrs.GetNamedItem(at_x)),
976 					fixed::FromString(attrs.GetNamedItem(at_y)),
977 					fixed::FromString(attrs.GetNamedItem(at_z)));
978 			}
979 			// <orientation>
980 			else if (element_name == el_orientation)
981 			{
982 				XMBAttributeList attrs = setting.GetAttributes();
983 				Orientation = CFixedVector3D(
984 					fixed::FromString(attrs.GetNamedItem(at_x)),
985 					fixed::FromString(attrs.GetNamedItem(at_y)),
986 					fixed::FromString(attrs.GetNamedItem(at_z)));
987 				// TODO: what happens if some attributes are missing?
988 			}
989 			// <obstruction>
990 			else if (element_name == el_obstruction)
991 			{
992 				XMBAttributeList attrs = setting.GetAttributes();
993 				ControlGroup = attrs.GetNamedItem(at_group).ToInt();
994 				ControlGroup2 = attrs.GetNamedItem(at_group2).ToInt();
995 			}
996 			// <actor>
997 			else if (element_name == el_actor)
998 			{
999 				XMBAttributeList attrs = setting.GetAttributes();
1000 				CStr seedStr = attrs.GetNamedItem(at_seed);
1001 				if (!seedStr.empty())
1002 				{
1003 					Seed = seedStr.ToLong();
1004 					ENSURE(Seed >= 0);
1005 				}
1006 			}
1007 			else
1008 				debug_warn(L"Invalid map XML data");
1009 		}
1010 
1011 		entity_id_t ent = sim.AddEntity(TemplateName, EntityUid);
1012 		entity_id_t player = cmpPlayerManager->GetPlayerByID(PlayerID);
1013 		if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
1014 		{	// Don't add entities with invalid player IDs
1015 			LOGERROR("Failed to load entity template '%s'", utf8_from_wstring(TemplateName));
1016 		}
1017 		else
1018 		{
1019 			CmpPtr<ICmpPosition> cmpPosition(sim, ent);
1020 			if (cmpPosition)
1021 			{
1022 				cmpPosition->JumpTo(Position.X, Position.Z);
1023 				cmpPosition->SetYRotation(Orientation.Y);
1024 				// TODO: other parts of the position
1025 			}
1026 
1027 			CmpPtr<ICmpOwnership> cmpOwnership(sim, ent);
1028 			if (cmpOwnership)
1029 				cmpOwnership->SetOwner(PlayerID);
1030 
1031 			CmpPtr<ICmpObstruction> cmpObstruction(sim, ent);
1032 			if (cmpObstruction)
1033 			{
1034 				if (ControlGroup != INVALID_ENTITY)
1035 					cmpObstruction->SetControlGroup(ControlGroup);
1036 				if (ControlGroup2 != INVALID_ENTITY)
1037 					cmpObstruction->SetControlGroup2(ControlGroup2);
1038 
1039 				cmpObstruction->ResolveFoundationCollisions();
1040 			}
1041 
1042 			CmpPtr<ICmpVisual> cmpVisual(sim, ent);
1043 			if (cmpVisual)
1044 			{
1045 				if (Seed != -1)
1046 					cmpVisual->SetActorSeed((u32)Seed);
1047 				// TODO: variation/selection strings
1048 			}
1049 
1050 			if (PlayerID == m_MapReader.m_PlayerID && (boost::algorithm::ends_with(TemplateName, L"civil_centre") || m_MapReader.m_StartingCameraTarget == INVALID_ENTITY))
1051 			{
1052 				// Focus on civil centre or first entity owned by player
1053 				m_MapReader.m_StartingCameraTarget = ent;
1054 			}
1055 		}
1056 
1057 		completed_jobs++;
1058 		LDR_CHECK_TIMEOUT(completed_jobs, total_jobs);
1059 	}
1060 
1061 	return 0;
1062 }
1063 
ReadXML()1064 void CXMLReader::ReadXML()
1065 {
1066 	for (XMBElement node : nodes)
1067 	{
1068 		CStr name = xmb_file.GetElementString(node.GetNodeName());
1069 		if (name == "Terrain")
1070 		{
1071 			ReadTerrain(node);
1072 		}
1073 		else if (name == "Environment")
1074 		{
1075 			ReadEnvironment(node);
1076 		}
1077 		else if (name == "Camera")
1078 		{
1079 			ReadCamera(node);
1080 		}
1081 		else if (name == "ScriptSettings")
1082 		{
1083 			// Already loaded - this is to prevent an assertion
1084 		}
1085 		else if (name == "Entities")
1086 		{
1087 			// Handled by ProgressiveReadEntities instead
1088 		}
1089 		else if (name == "Paths")
1090 		{
1091 			ReadPaths(node);
1092 		}
1093 		else if (name == "Triggers")
1094 		{
1095 			ReadTriggers(node);
1096 		}
1097 		else if (name == "Script")
1098 		{
1099 			if (m_MapReader.pSimulation2)
1100 				m_MapReader.pSimulation2->SetStartupScript(node.GetText());
1101 		}
1102 		else
1103 		{
1104 			debug_printf("Invalid XML element in map file: %s\n", name.c_str());
1105 			debug_warn(L"Invalid map XML data");
1106 		}
1107 	}
1108 }
1109 
ProgressiveReadEntities()1110 int CXMLReader::ProgressiveReadEntities()
1111 {
1112 	// yield after this time is reached. balances increased progress bar
1113 	// smoothness vs. slowing down loading.
1114 	const double end_time = timer_Time() + 200e-3;
1115 
1116 	int ret;
1117 
1118 	while (node_idx < nodes.size())
1119 	{
1120 		XMBElement node = nodes[node_idx];
1121 		CStr name = xmb_file.GetElementString(node.GetNodeName());
1122 		if (name == "Entities")
1123 		{
1124 			if (!m_MapReader.m_SkipEntities)
1125 			{
1126 				ret = ReadEntities(node, end_time);
1127 				if (ret != 0)	// error or timed out
1128 					return ret;
1129 			}
1130 		}
1131 
1132 		node_idx++;
1133 	}
1134 
1135 	return 0;
1136 }
1137 
1138 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1139 
1140 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1141 
1142 
1143 // load script settings from map
LoadScriptSettings()1144 int CMapReader::LoadScriptSettings()
1145 {
1146 	if (!xml_reader)
1147 		xml_reader = new CXMLReader(filename_xml, *this);
1148 
1149 	// parse the script settings
1150 	if (pSimulation2)
1151 		pSimulation2->SetMapSettings(xml_reader->ReadScriptSettings());
1152 
1153 	return 0;
1154 }
1155 
1156 // load player settings script
LoadPlayerSettings()1157 int CMapReader::LoadPlayerSettings()
1158 {
1159 	if (pSimulation2)
1160 		pSimulation2->LoadPlayerSettings(true);
1161 	return 0;
1162 }
1163 
1164 // load map settings script
LoadMapSettings()1165 int CMapReader::LoadMapSettings()
1166 {
1167 	if (pSimulation2)
1168 		pSimulation2->LoadMapSettings();
1169 	return 0;
1170 }
1171 
ReadXML()1172 int CMapReader::ReadXML()
1173 {
1174 	if (!xml_reader)
1175 		xml_reader = new CXMLReader(filename_xml, *this);
1176 
1177 	xml_reader->ReadXML();
1178 
1179 	return 0;
1180 }
1181 
1182 // progressive
ReadXMLEntities()1183 int CMapReader::ReadXMLEntities()
1184 {
1185 	if (!xml_reader)
1186 		xml_reader = new CXMLReader(filename_xml, *this);
1187 
1188 	int ret = xml_reader->ProgressiveReadEntities();
1189 	// finished or failed
1190 	if (ret <= 0)
1191 	{
1192 		SAFE_DELETE(xml_reader);
1193 	}
1194 
1195 	return ret;
1196 }
1197 
1198 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1199 
1200 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1201 
1202 
LoadRMSettings()1203 int CMapReader::LoadRMSettings()
1204 {
1205 	// copy random map settings over to sim
1206 	ENSURE(pSimulation2);
1207 	pSimulation2->SetMapSettings(m_ScriptSettings);
1208 
1209 	return 0;
1210 }
1211 
GenerateMap()1212 int CMapReader::GenerateMap()
1213 {
1214 	JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
1215 	JSAutoRequest rq(cx);
1216 
1217 	if (!m_MapGen)
1218 	{
1219 		// Initialize map generator
1220 		m_MapGen = new CMapGenerator();
1221 
1222 		VfsPath scriptPath;
1223 
1224 		if (m_ScriptFile.length())
1225 			scriptPath = L"maps/random/"+m_ScriptFile;
1226 
1227 		// Stringify settings to pass across threads
1228 		std::string scriptSettings = pSimulation2->GetScriptInterface().StringifyJSON(&m_ScriptSettings);
1229 
1230 		// Try to generate map
1231 		m_MapGen->GenerateMap(scriptPath, scriptSettings);
1232 	}
1233 
1234 	// Check status
1235 	int progress = m_MapGen->GetProgress();
1236 	if (progress < 0)
1237 	{
1238 		// RMS failed - return to main menu
1239 		throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
1240 	}
1241 	else if (progress == 0)
1242 	{
1243 		// Finished, get results as StructuredClone object, which must be read to obtain the JS::Value
1244 		shared_ptr<ScriptInterface::StructuredClone> results = m_MapGen->GetResults();
1245 
1246 		// Parse data into simulation context
1247 		JS::RootedValue data(cx);
1248 		pSimulation2->GetScriptInterface().ReadStructuredClone(results, &data);
1249 
1250 		if (data.isUndefined())
1251 		{
1252 			// RMS failed - return to main menu
1253 			throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
1254 		}
1255 		else
1256 		{
1257 			m_MapData.init(cx, data);
1258 		}
1259 	}
1260 	else
1261 	{
1262 		// Still working
1263 
1264 		// Sleep for a while, slowing down the rendering thread
1265 		// to allow more CPU for the map generator thread
1266 		SDL_Delay(100);
1267 	}
1268 
1269 	// return progress
1270 	return progress;
1271 };
1272 
1273 
ParseTerrain()1274 int CMapReader::ParseTerrain()
1275 {
1276 	TIMER(L"ParseTerrain");
1277 	JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
1278 	JSAutoRequest rq(cx);
1279 
1280 	// parse terrain from map data
1281 	//	an error here should stop the loading process
1282 #define GET_TERRAIN_PROPERTY(val, prop, out)\
1283 	if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
1284 		{	LOGERROR("CMapReader::ParseTerrain() failed to get '%s' property", #prop);\
1285 			throw PSERROR_Game_World_MapLoadFailed("Error parsing terrain data.\nCheck application log for details"); }
1286 
1287 	u32 size;
1288 	GET_TERRAIN_PROPERTY(m_MapData, size, size)
1289 
1290 	m_PatchesPerSide = size / PATCH_SIZE;
1291 
1292 	// flat heightmap of u16 data
1293 	GET_TERRAIN_PROPERTY(m_MapData, height, m_Heightmap)
1294 
1295 	// load textures
1296 	std::vector<std::string> textureNames;
1297 	GET_TERRAIN_PROPERTY(m_MapData, textureNames, textureNames)
1298 	num_terrain_tex = textureNames.size();
1299 
1300 	while (cur_terrain_tex < num_terrain_tex)
1301 	{
1302 		ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
1303 		CTerrainTextureEntry* texentry = g_TexMan.FindTexture(textureNames[cur_terrain_tex]);
1304 		m_TerrainTextures.push_back(texentry);
1305 
1306 		cur_terrain_tex++;
1307 	}
1308 
1309 	// build tile data
1310 	m_Tiles.resize(SQR(size));
1311 
1312 	JS::RootedValue tileData(cx);
1313 	GET_TERRAIN_PROPERTY(m_MapData, tileData, &tileData)
1314 
1315 	// parse tile data object into flat arrays
1316 	std::vector<u16> tileIndex;
1317 	std::vector<u16> tilePriority;
1318 	GET_TERRAIN_PROPERTY(tileData, index, tileIndex);
1319 	GET_TERRAIN_PROPERTY(tileData, priority, tilePriority);
1320 
1321 	ENSURE(SQR(size) == tileIndex.size() && SQR(size) == tilePriority.size());
1322 
1323 	// reorder by patches and store
1324 	for (size_t x = 0; x < size; ++x)
1325 	{
1326 		size_t patchX = x / PATCH_SIZE;
1327 		size_t offX = x % PATCH_SIZE;
1328 		for (size_t y = 0; y < size; ++y)
1329 		{
1330 			size_t patchY = y / PATCH_SIZE;
1331 			size_t offY = y % PATCH_SIZE;
1332 
1333 			STileDesc tile;
1334 			tile.m_Tex1Index = tileIndex[y*size + x];
1335 			tile.m_Tex2Index = 0xFFFF;
1336 			tile.m_Priority = tilePriority[y*size + x];
1337 
1338 			m_Tiles[(patchY * m_PatchesPerSide + patchX) * SQR(PATCH_SIZE) + (offY * PATCH_SIZE + offX)] = tile;
1339 		}
1340 	}
1341 
1342 	// reset generator state
1343 	cur_terrain_tex = 0;
1344 
1345 #undef GET_TERRAIN_PROPERTY
1346 
1347 	return 0;
1348 }
1349 
ParseEntities()1350 int CMapReader::ParseEntities()
1351 {
1352 	TIMER(L"ParseEntities");
1353 	JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
1354 	JSAutoRequest rq(cx);
1355 
1356 	// parse entities from map data
1357 	std::vector<Entity> entities;
1358 
1359 	if (!pSimulation2->GetScriptInterface().GetProperty(m_MapData, "entities", entities))
1360 		LOGWARNING("CMapReader::ParseEntities() failed to get 'entities' property");
1361 
1362 	CSimulation2& sim = *pSimulation2;
1363 	CmpPtr<ICmpPlayerManager> cmpPlayerManager(sim, SYSTEM_ENTITY);
1364 
1365 	size_t entity_idx = 0;
1366 	size_t num_entities = entities.size();
1367 
1368 	Entity currEnt;
1369 
1370 	while (entity_idx < num_entities)
1371 	{
1372 		// Get current entity struct
1373 		currEnt = entities[entity_idx];
1374 
1375 		entity_id_t ent = pSimulation2->AddEntity(currEnt.templateName, currEnt.entityID);
1376 		entity_id_t player = cmpPlayerManager->GetPlayerByID(currEnt.playerID);
1377 		if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
1378 		{	// Don't add entities with invalid player IDs
1379 			LOGERROR("Failed to load entity template '%s'", utf8_from_wstring(currEnt.templateName));
1380 		}
1381 		else
1382 		{
1383 			CmpPtr<ICmpPosition> cmpPosition(sim, ent);
1384 			if (cmpPosition)
1385 			{
1386 				cmpPosition->JumpTo(currEnt.position.X * (int)TERRAIN_TILE_SIZE, currEnt.position.Z * (int)TERRAIN_TILE_SIZE);
1387 				cmpPosition->SetYRotation(currEnt.rotation.Y);
1388 				// TODO: other parts of the position
1389 			}
1390 
1391 			CmpPtr<ICmpOwnership> cmpOwnership(sim, ent);
1392 			if (cmpOwnership)
1393 				cmpOwnership->SetOwner(currEnt.playerID);
1394 
1395 			// Detect and fix collisions between foundation-blocking entities.
1396 			// This presently serves to copy wall tower control groups to wall
1397 			// segments, allowing players to expand RMS-generated walls.
1398 			CmpPtr<ICmpObstruction> cmpObstruction(sim, ent);
1399 			if (cmpObstruction)
1400 				cmpObstruction->ResolveFoundationCollisions();
1401 
1402 			if (currEnt.playerID == m_PlayerID && (boost::algorithm::ends_with(currEnt.templateName, L"civil_centre") || m_StartingCameraTarget == INVALID_ENTITY))
1403 			{
1404 				// Focus on civil centre or first entity owned by player
1405 				m_StartingCameraTarget = currEnt.entityID;
1406 			}
1407 		}
1408 
1409 		entity_idx++;
1410 	}
1411 
1412 	return 0;
1413 }
1414 
ParseEnvironment()1415 int CMapReader::ParseEnvironment()
1416 {
1417 	// parse environment settings from map data
1418 	JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
1419 	JSAutoRequest rq(cx);
1420 
1421 #define GET_ENVIRONMENT_PROPERTY(val, prop, out)\
1422 	if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
1423 		LOGWARNING("CMapReader::ParseEnvironment() failed to get '%s' property", #prop);
1424 
1425 	JS::RootedValue envObj(cx);
1426 	GET_ENVIRONMENT_PROPERTY(m_MapData, Environment, &envObj)
1427 
1428 	if (envObj.isUndefined())
1429 	{
1430 		LOGWARNING("CMapReader::ParseEnvironment(): Environment settings not found");
1431 		return 0;
1432 	}
1433 
1434 	if (pPostproc)
1435 		pPostproc->SetPostEffect(L"default");
1436 
1437 	std::wstring skySet;
1438 	GET_ENVIRONMENT_PROPERTY(envObj, SkySet, skySet)
1439 	if (pSkyMan)
1440 		pSkyMan->SetSkySet(skySet);
1441 
1442 	CColor sunColor;
1443 	GET_ENVIRONMENT_PROPERTY(envObj, SunColor, sunColor)
1444 	m_LightEnv.m_SunColor = RGBColor(sunColor.r, sunColor.g, sunColor.b);
1445 
1446 	GET_ENVIRONMENT_PROPERTY(envObj, SunElevation, m_LightEnv.m_Elevation)
1447 	GET_ENVIRONMENT_PROPERTY(envObj, SunRotation, m_LightEnv.m_Rotation)
1448 
1449 	CColor terrainAmbientColor;
1450 	GET_ENVIRONMENT_PROPERTY(envObj, TerrainAmbientColor, terrainAmbientColor)
1451 	m_LightEnv.m_TerrainAmbientColor = RGBColor(terrainAmbientColor.r, terrainAmbientColor.g, terrainAmbientColor.b);
1452 
1453 	CColor unitsAmbientColor;
1454 	GET_ENVIRONMENT_PROPERTY(envObj, UnitsAmbientColor, unitsAmbientColor)
1455 	m_LightEnv.m_UnitsAmbientColor = RGBColor(unitsAmbientColor.r, unitsAmbientColor.g, unitsAmbientColor.b);
1456 
1457 	// Water properties
1458 	JS::RootedValue waterObj(cx);
1459 	GET_ENVIRONMENT_PROPERTY(envObj, Water, &waterObj)
1460 
1461 	JS::RootedValue waterBodyObj(cx);
1462 	GET_ENVIRONMENT_PROPERTY(waterObj, WaterBody, &waterBodyObj)
1463 
1464 	// Water level - necessary
1465 	float waterHeight;
1466 	GET_ENVIRONMENT_PROPERTY(waterBodyObj, Height, waterHeight)
1467 
1468 	CmpPtr<ICmpWaterManager> cmpWaterManager(*pSimulation2, SYSTEM_ENTITY);
1469 	ENSURE(cmpWaterManager);
1470 	cmpWaterManager->SetWaterLevel(entity_pos_t::FromFloat(waterHeight));
1471 
1472 	// If we have graphics, get rest of settings
1473 	if (pWaterMan)
1474 	{
1475 		GET_ENVIRONMENT_PROPERTY(waterBodyObj, Type, pWaterMan->m_WaterType)
1476 		if (pWaterMan->m_WaterType == L"default")
1477 			pWaterMan->m_WaterType = L"ocean";
1478 		GET_ENVIRONMENT_PROPERTY(waterBodyObj, Color, pWaterMan->m_WaterColor)
1479 		GET_ENVIRONMENT_PROPERTY(waterBodyObj, Tint, pWaterMan->m_WaterTint)
1480 		GET_ENVIRONMENT_PROPERTY(waterBodyObj, Waviness, pWaterMan->m_Waviness)
1481 		GET_ENVIRONMENT_PROPERTY(waterBodyObj, Murkiness, pWaterMan->m_Murkiness)
1482 		GET_ENVIRONMENT_PROPERTY(waterBodyObj, WindAngle, pWaterMan->m_WindAngle)
1483 	}
1484 
1485 	JS::RootedValue fogObject(cx);
1486 	GET_ENVIRONMENT_PROPERTY(envObj, Fog, &fogObject);
1487 
1488 	GET_ENVIRONMENT_PROPERTY(fogObject, FogFactor, m_LightEnv.m_FogFactor);
1489 	GET_ENVIRONMENT_PROPERTY(fogObject, FogThickness, m_LightEnv.m_FogMax);
1490 
1491 	CColor fogColor;
1492 	GET_ENVIRONMENT_PROPERTY(fogObject, FogColor, fogColor);
1493 	m_LightEnv.m_FogColor = RGBColor(fogColor.r, fogColor.g, fogColor.b);
1494 
1495 	JS::RootedValue postprocObject(cx);
1496 	GET_ENVIRONMENT_PROPERTY(envObj, Postproc, &postprocObject);
1497 
1498 	std::wstring postProcEffect;
1499 	GET_ENVIRONMENT_PROPERTY(postprocObject, PostprocEffect, postProcEffect);
1500 
1501 	if (pPostproc)
1502 		pPostproc->SetPostEffect(postProcEffect);
1503 
1504 	GET_ENVIRONMENT_PROPERTY(postprocObject, Brightness, m_LightEnv.m_Brightness);
1505 	GET_ENVIRONMENT_PROPERTY(postprocObject, Contrast, m_LightEnv.m_Contrast);
1506 	GET_ENVIRONMENT_PROPERTY(postprocObject, Saturation, m_LightEnv.m_Saturation);
1507 	GET_ENVIRONMENT_PROPERTY(postprocObject, Bloom, m_LightEnv.m_Bloom);
1508 
1509 	m_LightEnv.CalculateSunDirection();
1510 
1511 #undef GET_ENVIRONMENT_PROPERTY
1512 
1513 	return 0;
1514 }
1515 
ParseCamera()1516 int CMapReader::ParseCamera()
1517 {
1518 	JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
1519 	JSAutoRequest rq(cx);
1520 	// parse camera settings from map data
1521 	// defaults if we don't find player starting camera
1522 	float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f);
1523 	CVector3D translation = CVector3D(100, 150, -100);
1524 
1525 #define GET_CAMERA_PROPERTY(val, prop, out)\
1526 	if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
1527 		LOGWARNING("CMapReader::ParseCamera() failed to get '%s' property", #prop);
1528 
1529 	JS::RootedValue cameraObj(cx);
1530 	GET_CAMERA_PROPERTY(m_MapData, Camera, &cameraObj)
1531 
1532 	if (!cameraObj.isUndefined())
1533 	{	// If camera property exists, read values
1534 		CFixedVector3D pos;
1535 		GET_CAMERA_PROPERTY(cameraObj, Position, pos)
1536 		translation = pos;
1537 
1538 		GET_CAMERA_PROPERTY(cameraObj, Rotation, rotation)
1539 		GET_CAMERA_PROPERTY(cameraObj, Declination, declination)
1540 	}
1541 #undef GET_CAMERA_PROPERTY
1542 
1543 	if (pGameView)
1544 	{
1545 		pGameView->GetCamera()->m_Orientation.SetXRotation(declination);
1546 		pGameView->GetCamera()->m_Orientation.RotateY(rotation);
1547 		pGameView->GetCamera()->m_Orientation.Translate(translation);
1548 		pGameView->GetCamera()->UpdateFrustum();
1549 	}
1550 
1551 	return 0;
1552 }
1553 
~CMapReader()1554 CMapReader::~CMapReader()
1555 {
1556 	// Cleaup objects
1557 	delete xml_reader;
1558 	delete m_MapGen;
1559 }
1560