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