[Gameplay] Reduce loom cost
[0ad.git] / source / graphics / MapReader.cpp
blob8925b517eefa639a6684c9502ac698e735d87ccf
1 /* Copyright (C) 2023 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "MapReader.h"
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 "maths/MathUtil.h"
33 #include "ps/CLogger.h"
34 #include "ps/Loader.h"
35 #include "ps/World.h"
36 #include "ps/XML/Xeromyces.h"
37 #include "renderer/PostprocManager.h"
38 #include "renderer/SkyManager.h"
39 #include "renderer/WaterManager.h"
40 #include "scriptinterface/Object.h"
41 #include "scriptinterface/ScriptContext.h"
42 #include "scriptinterface/ScriptRequest.h"
43 #include "scriptinterface/JSON.h"
44 #include "simulation2/Simulation2.h"
45 #include "simulation2/components/ICmpCinemaManager.h"
46 #include "simulation2/components/ICmpGarrisonHolder.h"
47 #include "simulation2/components/ICmpObstruction.h"
48 #include "simulation2/components/ICmpOwnership.h"
49 #include "simulation2/components/ICmpPlayer.h"
50 #include "simulation2/components/ICmpPlayerManager.h"
51 #include "simulation2/components/ICmpPosition.h"
52 #include "simulation2/components/ICmpTerrain.h"
53 #include "simulation2/components/ICmpTurretHolder.h"
54 #include "simulation2/components/ICmpVisual.h"
55 #include "simulation2/components/ICmpWaterManager.h"
57 #include <boost/algorithm/string/predicate.hpp>
59 #if defined(_MSC_VER) && _MSC_VER > 1900
60 #pragma warning(disable: 4456) // Declaration hides previous local declaration.
61 #pragma warning(disable: 4458) // Declaration hides class member.
62 #endif
64 CMapReader::CMapReader()
65 : xml_reader(0), m_PatchesPerSide(0), m_MapGen(0)
67 cur_terrain_tex = 0; // important - resets generator state
70 // LoadMap: try to load the map from given file; reinitialise the scene to new data if successful
71 void CMapReader::LoadMap(const VfsPath& pathname, const ScriptContext& cx, JS::HandleValue settings, CTerrain *pTerrain_,
72 WaterManager* pWaterMan_, SkyManager* pSkyMan_,
73 CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_,
74 CSimulation2 *pSimulation2_, const CSimContext* pSimContext_, int playerID_, bool skipEntities)
76 pTerrain = pTerrain_;
77 pLightEnv = pLightEnv_;
78 pGameView = pGameView_;
79 pWaterMan = pWaterMan_;
80 pSkyMan = pSkyMan_;
81 pCinema = pCinema_;
82 pTrigMan = pTrigMan_;
83 pPostproc = pPostproc_;
84 pSimulation2 = pSimulation2_;
85 pSimContext = pSimContext_;
86 m_PlayerID = playerID_;
87 m_SkipEntities = skipEntities;
88 m_StartingCameraTarget = INVALID_ENTITY;
89 m_ScriptSettings.init(cx.GetGeneralJSContext(), settings);
91 filename_xml = pathname.ChangeExtension(L".xml");
93 // In some cases (particularly tests) we don't want to bother storing a large
94 // mostly-empty .pmp file, so we let the XML file specify basic terrain instead.
95 // If there's an .xml file and no .pmp, then we're probably in this XML-only mode
96 only_xml = false;
97 if (!VfsFileExists(pathname) && VfsFileExists(filename_xml))
99 only_xml = true;
102 file_format_version = CMapIO::FILE_VERSION; // default if there's no .pmp
104 if (!only_xml)
106 // [25ms]
107 unpacker.Read(pathname, "PSMP");
108 file_format_version = unpacker.GetVersion();
111 // check oldest supported version
112 if (file_format_version < FILE_READ_VERSION)
113 throw PSERROR_Game_World_MapLoadFailed("Could not load terrain file - too old version!");
115 // delete all existing entities
116 if (pSimulation2)
117 pSimulation2->ResetState();
119 // reset post effects
120 if (pPostproc)
121 pPostproc->SetPostEffect(L"default");
123 // load map or script settings script
124 if (settings.isUndefined())
125 LDR_Register([this](const double)
127 return LoadScriptSettings();
128 }, L"CMapReader::LoadScriptSettings", 50);
129 else
130 LDR_Register([this](const double)
132 return LoadRMSettings();
133 }, L"CMapReader::LoadRMSettings", 50);
135 // load player settings script (must be done before reading map)
136 LDR_Register([this](const double)
138 return LoadPlayerSettings();
139 }, L"CMapReader::LoadPlayerSettings", 50);
141 // unpack the data
142 if (!only_xml)
143 LDR_Register([this](const double)
145 return UnpackTerrain();
146 }, L"CMapReader::UnpackMap", 1200);
148 // read the corresponding XML file
149 LDR_Register([this](const double)
151 return ReadXML();
152 }, L"CMapReader::ReadXML", 50);
154 // apply terrain data to the world
155 LDR_Register([this](const double)
157 return ApplyTerrainData();
158 }, L"CMapReader::ApplyTerrainData", 5);
160 // read entities
161 LDR_Register([this](const double)
163 return ReadXMLEntities();
164 }, L"CMapReader::ReadXMLEntities", 5800);
166 // apply misc data to the world
167 LDR_Register([this](const double)
169 return ApplyData();
170 }, L"CMapReader::ApplyData", 5);
172 // load map settings script (must be done after reading map)
173 LDR_Register([this](const double)
175 return LoadMapSettings();
176 }, L"CMapReader::LoadMapSettings", 5);
179 // LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful
180 void CMapReader::LoadRandomMap(const CStrW& scriptFile, const ScriptContext& cx, JS::HandleValue settings, CTerrain *pTerrain_,
181 WaterManager* pWaterMan_, SkyManager* pSkyMan_,
182 CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_,
183 CSimulation2 *pSimulation2_, int playerID_)
185 pSimulation2 = pSimulation2_;
186 pSimContext = pSimulation2 ? &pSimulation2->GetSimContext() : NULL;
187 m_ScriptSettings.init(cx.GetGeneralJSContext(), settings);
188 pTerrain = pTerrain_;
189 pLightEnv = pLightEnv_;
190 pGameView = pGameView_;
191 pWaterMan = pWaterMan_;
192 pSkyMan = pSkyMan_;
193 pCinema = pCinema_;
194 pTrigMan = pTrigMan_;
195 pPostproc = pPostproc_;
196 m_PlayerID = playerID_;
197 m_SkipEntities = false;
198 m_StartingCameraTarget = INVALID_ENTITY;
200 // delete all existing entities
201 if (pSimulation2)
202 pSimulation2->ResetState();
204 only_xml = false;
206 // copy random map settings (before entity creation)
207 LDR_Register([this](const double)
209 return LoadRMSettings();
210 }, L"CMapReader::LoadRMSettings", 50);
212 // load player settings script (must be done before reading map)
213 LDR_Register([this](const double)
215 return LoadPlayerSettings();
216 }, L"CMapReader::LoadPlayerSettings", 50);
218 // load map generator with random map script
219 LDR_Register([this, scriptFile](const double)
221 return GenerateMap(scriptFile);
222 }, L"CMapReader::GenerateMap", 20000);
224 // parse RMS results into terrain structure
225 LDR_Register([this](const double)
227 return ParseTerrain();
228 }, L"CMapReader::ParseTerrain", 500);
230 // parse RMS results into environment settings
231 LDR_Register([this](const double)
233 return ParseEnvironment();
234 }, L"CMapReader::ParseEnvironment", 5);
236 // parse RMS results into camera settings
237 LDR_Register([this](const double)
239 return ParseCamera();
240 }, L"CMapReader::ParseCamera", 5);
242 // apply terrain data to the world
243 LDR_Register([this](const double)
245 return ApplyTerrainData();
246 }, L"CMapReader::ApplyTerrainData", 5);
248 // parse RMS results into entities
249 LDR_Register([this](const double)
251 return ParseEntities();
252 }, L"CMapReader::ParseEntities", 1000);
254 // apply misc data to the world
255 LDR_Register([this](const double)
257 return ApplyData();
258 }, L"CMapReader::ApplyData", 5);
260 // load map settings script (must be done after reading map)
261 LDR_Register([this](const double)
263 return LoadMapSettings();
264 }, L"CMapReader::LoadMapSettings", 5);
267 // UnpackTerrain: unpack the terrain from the end of the input data stream
268 // - data: map size, heightmap, list of textures used by map, texture tile assignments
269 int CMapReader::UnpackTerrain()
271 // yield after this time is reached. balances increased progress bar
272 // smoothness vs. slowing down loading.
273 const double end_time = timer_Time() + 200e-3;
275 // first call to generator (this is skipped after first call,
276 // i.e. when the loop below was interrupted)
277 if (cur_terrain_tex == 0)
279 m_PatchesPerSide = (ssize_t)unpacker.UnpackSize();
281 // unpack heightmap [600us]
282 size_t verticesPerSide = m_PatchesPerSide*PATCH_SIZE+1;
283 m_Heightmap.resize(SQR(verticesPerSide));
284 unpacker.UnpackRaw(&m_Heightmap[0], SQR(verticesPerSide)*sizeof(u16));
286 // unpack # textures
287 num_terrain_tex = unpacker.UnpackSize();
288 m_TerrainTextures.reserve(num_terrain_tex);
291 // unpack texture names; find handle for each texture.
292 // interruptible.
293 while (cur_terrain_tex < num_terrain_tex)
295 CStr texturename;
296 unpacker.UnpackString(texturename);
298 if (CTerrainTextureManager::IsInitialised())
300 CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texturename);
301 m_TerrainTextures.push_back(texentry);
304 cur_terrain_tex++;
305 LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex);
308 // unpack tile data [3ms]
309 ssize_t tilesPerSide = m_PatchesPerSide*PATCH_SIZE;
310 m_Tiles.resize(size_t(SQR(tilesPerSide)));
311 unpacker.UnpackRaw(&m_Tiles[0], sizeof(STileDesc)*m_Tiles.size());
313 // reset generator state.
314 cur_terrain_tex = 0;
316 return 0;
319 int CMapReader::ApplyTerrainData()
321 if (m_PatchesPerSide == 0)
323 // we'll probably crash when trying to use this map later
324 throw PSERROR_Game_World_MapLoadFailed("Error loading map: no terrain data.\nCheck application log for details.");
327 if (!only_xml)
329 // initialise the terrain
330 pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]);
332 if (CTerrainTextureManager::IsInitialised())
334 // setup the textures on the minipatches
335 STileDesc* tileptr = &m_Tiles[0];
336 for (ssize_t j=0; j<m_PatchesPerSide; j++) {
337 for (ssize_t i=0; i<m_PatchesPerSide; i++) {
338 for (ssize_t m=0; m<PATCH_SIZE; m++) {
339 for (ssize_t k=0; k<PATCH_SIZE; k++) {
340 CMiniPatch& mp = pTerrain->GetPatch(i,j)->m_MiniPatches[m][k]; // can't fail
342 mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index];
343 mp.Priority = tileptr->m_Priority;
345 tileptr++;
353 CmpPtr<ICmpTerrain> cmpTerrain(*pSimContext, SYSTEM_ENTITY);
354 if (cmpTerrain)
355 cmpTerrain->ReloadTerrain();
357 return 0;
360 // ApplyData: take all the input data, and rebuild the scene from it
361 int CMapReader::ApplyData()
363 // copy over the lighting parameters
364 if (pLightEnv)
365 *pLightEnv = m_LightEnv;
367 CmpPtr<ICmpPlayerManager> cmpPlayerManager(*pSimContext, SYSTEM_ENTITY);
369 if (pGameView && cmpPlayerManager)
371 // Default to global camera (with constraints)
372 pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus());
374 // TODO: Starting rotation?
375 CmpPtr<ICmpPlayer> cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID));
376 if (cmpPlayer && cmpPlayer->HasStartingCamera())
378 // Use player starting camera
379 CFixedVector3D pos = cmpPlayer->GetStartingCameraPos();
380 pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()));
382 else if (m_StartingCameraTarget != INVALID_ENTITY)
384 // Point camera at entity
385 CmpPtr<ICmpPosition> cmpPosition(*pSimContext, m_StartingCameraTarget);
386 if (cmpPosition)
388 CFixedVector3D pos = cmpPosition->GetPosition();
389 pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()));
394 return 0;
397 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
399 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
402 PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname)
404 VfsPath filename_xml = pathname.ChangeExtension(L".xml");
406 CXeromyces xmb_file;
407 if (xmb_file.Load(g_VFS, filename_xml, "scenario") != PSRETURN_OK)
408 return PSRETURN_File_ReadFailed;
410 // Define all the relevant elements used in the XML file
411 #define EL(x) int el_##x = xmb_file.GetElementID(#x)
412 #define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
413 EL(scenario);
414 EL(scriptsettings);
415 #undef AT
416 #undef EL
418 XMBElement root = xmb_file.GetRoot();
419 ENSURE(root.GetNodeName() == el_scenario);
421 XERO_ITER_EL(root, child)
423 int child_name = child.GetNodeName();
424 if (child_name == el_scriptsettings)
426 m_ScriptSettings = child.GetText();
430 return PSRETURN_OK;
433 void CMapSummaryReader::GetMapSettings(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
435 ScriptRequest rq(scriptInterface);
437 Script::CreateObject(rq, ret);
439 if (m_ScriptSettings.empty())
440 return;
442 JS::RootedValue scriptSettingsVal(rq.cx);
443 Script::ParseJSON(rq, m_ScriptSettings, &scriptSettingsVal);
444 Script::SetProperty(rq, ret, "settings", scriptSettingsVal, false);
447 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
449 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
452 // Holds various state data while reading maps, so that loading can be
453 // interrupted (e.g. to update the progress display) then later resumed.
454 class CXMLReader
456 NONCOPYABLE(CXMLReader);
457 public:
458 CXMLReader(const VfsPath& xml_filename, CMapReader& mapReader)
459 : m_MapReader(mapReader), nodes(NULL, 0, NULL)
461 Init(xml_filename);
464 CStr ReadScriptSettings();
466 // read everything except for entities
467 void ReadXML();
469 // return semantics: see Loader.cpp!LoadFunc.
470 int ProgressiveReadEntities();
472 private:
473 CXeromyces xmb_file;
475 CMapReader& m_MapReader;
477 int el_entity;
478 int el_tracks;
479 int el_template, el_player;
480 int el_position, el_orientation, el_obstruction;
481 int el_garrison;
482 int el_turrets;
483 int el_actor;
484 int at_x;
485 int at_y;
486 int at_z;
487 int at_group, at_group2;
488 int at_angle;
489 int at_uid;
490 int at_seed;
491 int at_turret;
493 XMBElementList nodes; // children of root
495 // loop counters
496 size_t node_idx;
497 size_t entity_idx;
499 // # entities+nonentities processed and total (for progress calc)
500 int completed_jobs, total_jobs;
502 // maximum used entity ID, so we can safely allocate new ones
503 entity_id_t max_uid;
505 void Init(const VfsPath& xml_filename);
507 void ReadTerrain(XMBElement parent);
508 void ReadEnvironment(XMBElement parent);
509 void ReadCamera(XMBElement parent);
510 void ReadPaths(XMBElement parent);
511 void ReadTriggers(XMBElement parent);
512 int ReadEntities(XMBElement parent, double end_time);
516 void CXMLReader::Init(const VfsPath& xml_filename)
518 // must only assign once, so do it here
519 node_idx = entity_idx = 0;
521 if (xmb_file.Load(g_VFS, xml_filename, "scenario") != PSRETURN_OK)
522 throw PSERROR_Game_World_MapLoadFailed("Could not read map XML file!");
524 // define the elements and attributes that are frequently used in the XML file,
525 // so we don't need to do lots of string construction and comparison when
526 // reading the data.
527 // (Needs to be synchronised with the list in CXMLReader - ugh)
528 #define EL(x) el_##x = xmb_file.GetElementID(#x)
529 #define AT(x) at_##x = xmb_file.GetAttributeID(#x)
530 EL(entity);
531 EL(tracks);
532 EL(template);
533 EL(player);
534 EL(position);
535 EL(garrison);
536 EL(turrets);
537 EL(orientation);
538 EL(obstruction);
539 EL(actor);
540 AT(x); AT(y); AT(z);
541 AT(group); AT(group2);
542 AT(angle);
543 AT(uid);
544 AT(seed);
545 AT(turret);
546 #undef AT
547 #undef EL
549 XMBElement root = xmb_file.GetRoot();
550 ENSURE(xmb_file.GetElementStringView(root.GetNodeName()) == "Scenario");
551 nodes = root.GetChildNodes();
553 // find out total number of entities+nonentities
554 // (used when calculating progress)
555 completed_jobs = 0;
556 total_jobs = 0;
557 for (XMBElement node : nodes)
558 total_jobs += node.GetChildNodes().size();
560 // Find the maximum entity ID, so we can safely allocate new IDs without conflicts
562 max_uid = SYSTEM_ENTITY;
564 XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities"));
565 XERO_ITER_EL(ents, ent)
567 CStr uid = ent.GetAttributes().GetNamedItem(at_uid);
568 max_uid = std::max(max_uid, (entity_id_t)uid.ToUInt());
573 CStr CXMLReader::ReadScriptSettings()
575 XMBElement root = xmb_file.GetRoot();
576 ENSURE(xmb_file.GetElementStringView(root.GetNodeName()) == "Scenario");
577 nodes = root.GetChildNodes();
579 XMBElement settings = nodes.GetFirstNamedItem(xmb_file.GetElementID("ScriptSettings"));
581 return settings.GetText();
585 void CXMLReader::ReadTerrain(XMBElement parent)
587 #define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
588 AT(patches);
589 AT(texture);
590 AT(priority);
591 AT(height);
592 #undef AT
594 ssize_t patches = 9;
595 CStr texture = "grass1_spring";
596 int priority = 0;
597 u16 height = 16384;
599 XERO_ITER_ATTR(parent, attr)
601 if (attr.Name == at_patches)
602 patches = attr.Value.ToInt();
603 else if (attr.Name == at_texture)
604 texture = attr.Value;
605 else if (attr.Name == at_priority)
606 priority = attr.Value.ToInt();
607 else if (attr.Name == at_height)
608 height = (u16)attr.Value.ToInt();
611 m_MapReader.m_PatchesPerSide = patches;
613 // Load the texture
614 CTerrainTextureEntry* texentry = nullptr;
615 if (CTerrainTextureManager::IsInitialised())
616 texentry = g_TexMan.FindTexture(texture);
618 m_MapReader.pTerrain->Initialize(patches, NULL);
620 // Fill the heightmap
621 u16* heightmap = m_MapReader.pTerrain->GetHeightMap();
622 ssize_t verticesPerSide = m_MapReader.pTerrain->GetVerticesPerSide();
623 for (ssize_t i = 0; i < SQR(verticesPerSide); ++i)
624 heightmap[i] = height;
626 // Fill the texture map
627 for (ssize_t pz = 0; pz < patches; ++pz)
629 for (ssize_t px = 0; px < patches; ++px)
631 CPatch* patch = m_MapReader.pTerrain->GetPatch(px, pz); // can't fail
633 for (ssize_t z = 0; z < PATCH_SIZE; ++z)
635 for (ssize_t x = 0; x < PATCH_SIZE; ++x)
637 patch->m_MiniPatches[z][x].Tex = texentry;
638 patch->m_MiniPatches[z][x].Priority = priority;
645 void CXMLReader::ReadEnvironment(XMBElement parent)
647 #define EL(x) int el_##x = xmb_file.GetElementID(#x)
648 #define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
649 EL(posteffect);
650 EL(skyset);
651 EL(suncolor);
652 EL(sunelevation);
653 EL(sunrotation);
654 EL(ambientcolor);
655 EL(water);
656 EL(waterbody);
657 EL(type);
658 EL(color);
659 EL(tint);
660 EL(height);
661 EL(waviness);
662 EL(murkiness);
663 EL(windangle);
664 EL(fog);
665 EL(fogcolor);
666 EL(fogfactor);
667 EL(fogthickness);
668 EL(postproc);
669 EL(brightness);
670 EL(contrast);
671 EL(saturation);
672 EL(bloom);
673 AT(r); AT(g); AT(b);
674 #undef AT
675 #undef EL
677 XERO_ITER_EL(parent, element)
679 int element_name = element.GetNodeName();
681 XMBAttributeList attrs = element.GetAttributes();
683 if (element_name == el_skyset)
685 if (m_MapReader.pSkyMan)
686 m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8());
688 else if (element_name == el_suncolor)
690 m_MapReader.m_LightEnv.m_SunColor = RGBColor(
691 attrs.GetNamedItem(at_r).ToFloat(),
692 attrs.GetNamedItem(at_g).ToFloat(),
693 attrs.GetNamedItem(at_b).ToFloat());
695 else if (element_name == el_sunelevation)
697 m_MapReader.m_LightEnv.m_Elevation = attrs.GetNamedItem(at_angle).ToFloat();
699 else if (element_name == el_sunrotation)
701 m_MapReader.m_LightEnv.m_Rotation = attrs.GetNamedItem(at_angle).ToFloat();
703 else if (element_name == el_ambientcolor)
705 m_MapReader.m_LightEnv.m_AmbientColor = RGBColor(
706 attrs.GetNamedItem(at_r).ToFloat(),
707 attrs.GetNamedItem(at_g).ToFloat(),
708 attrs.GetNamedItem(at_b).ToFloat());
710 else if (element_name == el_fog)
712 XERO_ITER_EL(element, fog)
714 int fog_element_name = fog.GetNodeName();
715 if (fog_element_name == el_fogcolor)
717 XMBAttributeList fogAttributes = fog.GetAttributes();
718 m_MapReader.m_LightEnv.m_FogColor = RGBColor(
719 fogAttributes.GetNamedItem(at_r).ToFloat(),
720 fogAttributes.GetNamedItem(at_g).ToFloat(),
721 fogAttributes.GetNamedItem(at_b).ToFloat());
723 else if (fog_element_name == el_fogfactor)
725 m_MapReader.m_LightEnv.m_FogFactor = fog.GetText().ToFloat();
727 else if (fog_element_name == el_fogthickness)
729 m_MapReader.m_LightEnv.m_FogMax = fog.GetText().ToFloat();
733 else if (element_name == el_postproc)
735 XERO_ITER_EL(element, postproc)
737 int post_element_name = postproc.GetNodeName();
738 if (post_element_name == el_brightness)
740 m_MapReader.m_LightEnv.m_Brightness = postproc.GetText().ToFloat();
742 else if (post_element_name == el_contrast)
744 m_MapReader.m_LightEnv.m_Contrast = postproc.GetText().ToFloat();
746 else if (post_element_name == el_saturation)
748 m_MapReader.m_LightEnv.m_Saturation = postproc.GetText().ToFloat();
750 else if (post_element_name == el_bloom)
752 m_MapReader.m_LightEnv.m_Bloom = postproc.GetText().ToFloat();
754 else if (post_element_name == el_posteffect)
756 if (m_MapReader.pPostproc)
757 m_MapReader.pPostproc->SetPostEffect(postproc.GetText().FromUTF8());
761 else if (element_name == el_water)
763 XERO_ITER_EL(element, waterbody)
765 ENSURE(waterbody.GetNodeName() == el_waterbody);
766 XERO_ITER_EL(waterbody, waterelement)
768 int water_element_name = waterelement.GetNodeName();
769 if (water_element_name == el_height)
771 CmpPtr<ICmpWaterManager> cmpWaterManager(*m_MapReader.pSimContext, SYSTEM_ENTITY);
772 ENSURE(cmpWaterManager);
773 cmpWaterManager->SetWaterLevel(entity_pos_t::FromString(waterelement.GetText()));
774 continue;
777 // The rest are purely graphical effects, and should be ignored if
778 // graphics are disabled
779 if (!m_MapReader.pWaterMan)
780 continue;
782 if (water_element_name == el_type)
784 if (waterelement.GetText() == "default")
785 m_MapReader.pWaterMan->m_WaterType = L"ocean";
786 else
787 m_MapReader.pWaterMan->m_WaterType = waterelement.GetText().FromUTF8();
789 #define READ_COLOR(el, out) \
790 else if (water_element_name == el) \
792 XMBAttributeList colorAttrs = waterelement.GetAttributes(); \
793 out = CColor( \
794 colorAttrs.GetNamedItem(at_r).ToFloat(), \
795 colorAttrs.GetNamedItem(at_g).ToFloat(), \
796 colorAttrs.GetNamedItem(at_b).ToFloat(), \
797 1.f); \
800 #define READ_FLOAT(el, out) \
801 else if (water_element_name == el) \
803 out = waterelement.GetText().ToFloat(); \
806 READ_COLOR(el_color, m_MapReader.pWaterMan->m_WaterColor)
807 READ_COLOR(el_tint, m_MapReader.pWaterMan->m_WaterTint)
808 READ_FLOAT(el_waviness, m_MapReader.pWaterMan->m_Waviness)
809 READ_FLOAT(el_murkiness, m_MapReader.pWaterMan->m_Murkiness)
810 READ_FLOAT(el_windangle, m_MapReader.pWaterMan->m_WindAngle)
812 #undef READ_FLOAT
813 #undef READ_COLOR
815 else
816 debug_warn(L"Invalid map XML data");
821 else
822 debug_warn(L"Invalid map XML data");
825 m_MapReader.m_LightEnv.CalculateSunDirection();
828 void CXMLReader::ReadCamera(XMBElement parent)
830 // defaults if we don't find player starting camera
831 #define EL(x) int el_##x = xmb_file.GetElementID(#x)
832 #define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
833 EL(declination);
834 EL(rotation);
835 EL(position);
836 AT(angle);
837 AT(x); AT(y); AT(z);
838 #undef AT
839 #undef EL
841 float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f);
842 CVector3D translation = CVector3D(100, 150, -100);
844 XERO_ITER_EL(parent, element)
846 int element_name = element.GetNodeName();
848 XMBAttributeList attrs = element.GetAttributes();
849 if (element_name == el_declination)
851 declination = attrs.GetNamedItem(at_angle).ToFloat();
853 else if (element_name == el_rotation)
855 rotation = attrs.GetNamedItem(at_angle).ToFloat();
857 else if (element_name == el_position)
859 translation = CVector3D(
860 attrs.GetNamedItem(at_x).ToFloat(),
861 attrs.GetNamedItem(at_y).ToFloat(),
862 attrs.GetNamedItem(at_z).ToFloat());
864 else
865 debug_warn(L"Invalid map XML data");
868 if (m_MapReader.pGameView)
870 m_MapReader.pGameView->GetCamera()->m_Orientation.SetXRotation(declination);
871 m_MapReader.pGameView->GetCamera()->m_Orientation.RotateY(rotation);
872 m_MapReader.pGameView->GetCamera()->m_Orientation.Translate(translation);
873 m_MapReader.pGameView->GetCamera()->UpdateFrustum();
877 void CXMLReader::ReadPaths(XMBElement parent)
879 #define EL(x) int el_##x = xmb_file.GetElementID(#x)
880 #define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
882 EL(path);
883 EL(rotation);
884 EL(node);
885 EL(position);
886 EL(target);
887 AT(name);
888 AT(timescale);
889 AT(orientation);
890 AT(mode);
891 AT(style);
892 AT(x);
893 AT(y);
894 AT(z);
895 AT(deltatime);
897 #undef EL
898 #undef AT
900 CmpPtr<ICmpCinemaManager> cmpCinemaManager(*m_MapReader.pSimContext, SYSTEM_ENTITY);
901 XERO_ITER_EL(parent, element)
903 int elementName = element.GetNodeName();
905 if (elementName == el_path)
907 CCinemaData pathData;
908 XMBAttributeList attrs = element.GetAttributes();
909 CStrW pathName(attrs.GetNamedItem(at_name).FromUTF8());
910 pathData.m_Name = pathName;
911 pathData.m_Timescale = fixed::FromString(attrs.GetNamedItem(at_timescale));
912 pathData.m_Orientation = attrs.GetNamedItem(at_orientation).FromUTF8();
913 pathData.m_Mode = attrs.GetNamedItem(at_mode).FromUTF8();
914 pathData.m_Style = attrs.GetNamedItem(at_style).FromUTF8();
916 TNSpline positionSpline, targetSpline;
917 fixed lastPositionTime = fixed::Zero();
918 fixed lastTargetTime = fixed::Zero();
920 XERO_ITER_EL(element, pathChild)
922 elementName = pathChild.GetNodeName();
923 attrs = pathChild.GetAttributes();
925 // Load node data used for spline
926 if (elementName == el_node)
928 lastPositionTime += fixed::FromString(attrs.GetNamedItem(at_deltatime));
929 lastTargetTime += fixed::FromString(attrs.GetNamedItem(at_deltatime));
930 XERO_ITER_EL(pathChild, nodeChild)
932 elementName = nodeChild.GetNodeName();
933 attrs = nodeChild.GetAttributes();
935 if (elementName == el_position)
937 CFixedVector3D position(fixed::FromString(attrs.GetNamedItem(at_x)),
938 fixed::FromString(attrs.GetNamedItem(at_y)),
939 fixed::FromString(attrs.GetNamedItem(at_z)));
941 positionSpline.AddNode(position, CFixedVector3D(), lastPositionTime);
942 lastPositionTime = fixed::Zero();
944 else if (elementName == el_rotation)
946 // TODO: Implement rotation slerp/spline as another object
948 else if (elementName == el_target)
950 CFixedVector3D targetPosition(fixed::FromString(attrs.GetNamedItem(at_x)),
951 fixed::FromString(attrs.GetNamedItem(at_y)),
952 fixed::FromString(attrs.GetNamedItem(at_z)));
954 targetSpline.AddNode(targetPosition, CFixedVector3D(), lastTargetTime);
955 lastTargetTime = fixed::Zero();
957 else
958 LOGWARNING("Invalid cinematic element for node child");
961 else
962 LOGWARNING("Invalid cinematic element for path child");
965 // Construct cinema path with data gathered
966 CCinemaPath path(pathData, positionSpline, targetSpline);
967 if (path.Empty())
969 LOGWARNING("Path with name '%s' is empty", pathName.ToUTF8());
970 return;
973 if (!cmpCinemaManager)
974 continue;
975 if (!cmpCinemaManager->HasPath(pathName))
976 cmpCinemaManager->AddPath(path);
977 else
978 LOGWARNING("Path with name '%s' already exists", pathName.ToUTF8());
980 else
981 LOGWARNING("Invalid path child with name '%s'", element.GetText());
985 void CXMLReader::ReadTriggers(XMBElement UNUSED(parent))
989 int CXMLReader::ReadEntities(XMBElement parent, double end_time)
991 XMBElementList entities = parent.GetChildNodes();
993 ENSURE(m_MapReader.pSimulation2);
994 CSimulation2& sim = *m_MapReader.pSimulation2;
995 CmpPtr<ICmpPlayerManager> cmpPlayerManager(sim, SYSTEM_ENTITY);
997 while (entity_idx < entities.size())
999 // all new state at this scope and below doesn't need to be
1000 // wrapped, since we only yield after a complete iteration.
1002 XMBElement entity = entities[entity_idx++];
1003 ENSURE(entity.GetNodeName() == el_entity);
1005 XMBAttributeList attrs = entity.GetAttributes();
1006 CStr uid = attrs.GetNamedItem(at_uid);
1007 ENSURE(!uid.empty());
1008 int EntityUid = uid.ToInt();
1010 CStrW TemplateName;
1011 int PlayerID = 0;
1012 std::vector<entity_id_t> Garrison;
1013 std::vector<std::pair<std::string, entity_id_t>> Turrets;
1014 CFixedVector3D Position;
1015 CFixedVector3D Orientation;
1016 long Seed = -1;
1018 // Obstruction control groups.
1019 entity_id_t ControlGroup = INVALID_ENTITY;
1020 entity_id_t ControlGroup2 = INVALID_ENTITY;
1022 XERO_ITER_EL(entity, setting)
1024 int element_name = setting.GetNodeName();
1026 // <template>
1027 if (element_name == el_template)
1029 TemplateName = setting.GetText().FromUTF8();
1031 // <player>
1032 else if (element_name == el_player)
1034 PlayerID = setting.GetText().ToInt();
1036 // <position>
1037 else if (element_name == el_position)
1039 XMBAttributeList positionAttrs = setting.GetAttributes();
1040 Position = CFixedVector3D(
1041 fixed::FromString(positionAttrs.GetNamedItem(at_x)),
1042 fixed::FromString(positionAttrs.GetNamedItem(at_y)),
1043 fixed::FromString(positionAttrs.GetNamedItem(at_z)));
1045 // <orientation>
1046 else if (element_name == el_orientation)
1048 XMBAttributeList orientationAttrs = setting.GetAttributes();
1049 Orientation = CFixedVector3D(
1050 fixed::FromString(orientationAttrs.GetNamedItem(at_x)),
1051 fixed::FromString(orientationAttrs.GetNamedItem(at_y)),
1052 fixed::FromString(orientationAttrs.GetNamedItem(at_z)));
1053 // TODO: what happens if some attributes are missing?
1055 // <obstruction>
1056 else if (element_name == el_obstruction)
1058 XMBAttributeList obstructionAttrs = setting.GetAttributes();
1059 ControlGroup = obstructionAttrs.GetNamedItem(at_group).ToInt();
1060 ControlGroup2 = obstructionAttrs.GetNamedItem(at_group2).ToInt();
1062 // <garrison>
1063 else if (element_name == el_garrison)
1065 XMBElementList garrison = setting.GetChildNodes();
1066 Garrison.reserve(garrison.size());
1067 for (const XMBElement& garr_ent : garrison)
1069 XMBAttributeList garrisonAttrs = garr_ent.GetAttributes();
1070 Garrison.push_back(garrisonAttrs.GetNamedItem(at_uid).ToInt());
1073 // <turrets>
1074 else if (element_name == el_turrets)
1076 XMBElementList turrets = setting.GetChildNodes();
1077 Turrets.reserve(turrets.size());
1078 for (const XMBElement& turretPoint : turrets)
1080 XMBAttributeList turretAttrs = turretPoint.GetAttributes();
1081 Turrets.emplace_back(
1082 turretAttrs.GetNamedItem(at_turret),
1083 turretAttrs.GetNamedItem(at_uid).ToInt()
1087 // <actor>
1088 else if (element_name == el_actor)
1090 XMBAttributeList attrs = setting.GetAttributes();
1091 CStr seedStr = attrs.GetNamedItem(at_seed);
1092 if (!seedStr.empty())
1094 Seed = seedStr.ToLong();
1095 ENSURE(Seed >= 0);
1098 else
1099 debug_warn(L"Invalid map XML data");
1102 entity_id_t ent = sim.AddEntity(TemplateName, EntityUid);
1103 entity_id_t player = cmpPlayerManager->GetPlayerByID(PlayerID);
1104 if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
1105 { // Don't add entities with invalid player IDs
1106 LOGERROR("Failed to load entity template '%s'", utf8_from_wstring(TemplateName));
1108 else
1110 CmpPtr<ICmpPosition> cmpPosition(sim, ent);
1111 if (cmpPosition)
1113 cmpPosition->JumpTo(Position.X, Position.Z);
1114 cmpPosition->SetYRotation(Orientation.Y);
1115 // TODO: other parts of the position
1118 if (!Garrison.empty())
1120 CmpPtr<ICmpGarrisonHolder> cmpGarrisonHolder(sim, ent);
1121 if (cmpGarrisonHolder)
1122 cmpGarrisonHolder->SetInitEntities(std::move(Garrison));
1123 else
1124 LOGERROR("CXMLMapReader::ReadEntities() entity '%d' of player '%d' has no GarrisonHolder component and thus cannot garrison units.", ent, PlayerID);
1127 // Needs to be before ownership changes to prevent initialising
1128 // subunits too soon.
1129 if (!Turrets.empty())
1131 CmpPtr<ICmpTurretHolder> cmpTurretHolder(sim, ent);
1132 if (cmpTurretHolder)
1133 cmpTurretHolder->SetInitEntities(std::move(Turrets));
1134 else
1135 LOGERROR("CXMLMapReader::ReadEntities() entity '%d' of player '%d' has no TurretHolder component and thus cannot use turrets.", ent, PlayerID);
1138 CmpPtr<ICmpOwnership> cmpOwnership(sim, ent);
1139 if (cmpOwnership)
1140 cmpOwnership->SetOwner(PlayerID);
1142 CmpPtr<ICmpObstruction> cmpObstruction(sim, ent);
1143 if (cmpObstruction)
1145 if (ControlGroup != INVALID_ENTITY)
1146 cmpObstruction->SetControlGroup(ControlGroup);
1147 if (ControlGroup2 != INVALID_ENTITY)
1148 cmpObstruction->SetControlGroup2(ControlGroup2);
1150 cmpObstruction->ResolveFoundationCollisions();
1153 CmpPtr<ICmpVisual> cmpVisual(sim, ent);
1154 if (cmpVisual)
1156 if (Seed != -1)
1157 cmpVisual->SetActorSeed((u32)Seed);
1158 // TODO: variation/selection strings
1161 if (PlayerID == m_MapReader.m_PlayerID && (boost::algorithm::ends_with(TemplateName, L"civil_centre") || m_MapReader.m_StartingCameraTarget == INVALID_ENTITY))
1163 // Focus on civil centre or first entity owned by player
1164 m_MapReader.m_StartingCameraTarget = ent;
1168 completed_jobs++;
1169 LDR_CHECK_TIMEOUT(completed_jobs, total_jobs);
1172 return 0;
1175 void CXMLReader::ReadXML()
1177 for (XMBElement node : nodes)
1179 CStr name = xmb_file.GetElementString(node.GetNodeName());
1180 if (name == "Terrain")
1182 ReadTerrain(node);
1184 else if (name == "Environment")
1186 ReadEnvironment(node);
1188 else if (name == "Camera")
1190 ReadCamera(node);
1192 else if (name == "ScriptSettings")
1194 // Already loaded - this is to prevent an assertion
1196 else if (name == "Entities")
1198 // Handled by ProgressiveReadEntities instead
1200 else if (name == "Paths")
1202 ReadPaths(node);
1204 else if (name == "Triggers")
1206 ReadTriggers(node);
1208 else if (name == "Script")
1210 if (m_MapReader.pSimulation2)
1211 m_MapReader.pSimulation2->SetStartupScript(node.GetText());
1213 else
1215 debug_printf("Invalid XML element in map file: %s\n", name.c_str());
1216 debug_warn(L"Invalid map XML data");
1221 int CXMLReader::ProgressiveReadEntities()
1223 // yield after this time is reached. balances increased progress bar
1224 // smoothness vs. slowing down loading.
1225 const double end_time = timer_Time() + 200e-3;
1227 int ret;
1229 while (node_idx < nodes.size())
1231 XMBElement node = nodes[node_idx];
1232 CStr name = xmb_file.GetElementString(node.GetNodeName());
1233 if (name == "Entities")
1235 if (!m_MapReader.m_SkipEntities)
1237 ret = ReadEntities(node, end_time);
1238 if (ret != 0) // error or timed out
1239 return ret;
1243 node_idx++;
1246 return 0;
1249 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1251 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1254 // load script settings from map
1255 int CMapReader::LoadScriptSettings()
1257 if (!xml_reader)
1258 xml_reader = new CXMLReader(filename_xml, *this);
1260 // parse the script settings
1261 if (pSimulation2)
1262 pSimulation2->SetMapSettings(xml_reader->ReadScriptSettings());
1264 return 0;
1267 // load player settings script
1268 int CMapReader::LoadPlayerSettings()
1270 if (pSimulation2)
1271 pSimulation2->LoadPlayerSettings(true);
1272 return 0;
1275 // load map settings script
1276 int CMapReader::LoadMapSettings()
1278 if (pSimulation2)
1279 pSimulation2->LoadMapSettings();
1280 return 0;
1283 int CMapReader::ReadXML()
1285 if (!xml_reader)
1286 xml_reader = new CXMLReader(filename_xml, *this);
1288 xml_reader->ReadXML();
1290 return 0;
1293 // progressive
1294 int CMapReader::ReadXMLEntities()
1296 if (!xml_reader)
1297 xml_reader = new CXMLReader(filename_xml, *this);
1299 int ret = xml_reader->ProgressiveReadEntities();
1300 // finished or failed
1301 if (ret <= 0)
1303 SAFE_DELETE(xml_reader);
1306 return ret;
1309 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1311 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1314 int CMapReader::LoadRMSettings()
1316 // copy random map settings over to sim
1317 ENSURE(pSimulation2);
1318 pSimulation2->SetMapSettings(m_ScriptSettings);
1320 return 0;
1323 int CMapReader::GenerateMap(const CStrW& scriptFile)
1325 ScriptRequest rq(pSimulation2->GetScriptInterface());
1327 if (!m_MapGen)
1329 // Initialize map generator
1330 m_MapGen = new CMapGenerator();
1332 VfsPath scriptPath;
1334 if (scriptFile.length())
1335 scriptPath = L"maps/random/" + scriptFile;
1337 // Stringify settings to pass across threads
1338 std::string scriptSettings = Script::StringifyJSON(rq, &m_ScriptSettings);
1340 // Try to generate map
1341 m_MapGen->GenerateMap(scriptPath, scriptSettings);
1344 // Check status
1345 int progress = m_MapGen->GetProgress();
1346 if (progress < 0)
1348 // RMS failed - return to main menu
1349 throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
1351 else if (progress == 0)
1353 // Finished, get results as StructuredClone object, which must be read to obtain the JS::Value
1354 Script::StructuredClone results = m_MapGen->GetResults();
1356 // Parse data into simulation context
1357 JS::RootedValue data(rq.cx);
1358 Script::ReadStructuredClone(rq, results, &data);
1360 if (data.isUndefined())
1362 // RMS failed - return to main menu
1363 throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
1365 else
1367 m_MapData.init(rq.cx, data);
1371 // return progress
1372 return progress;
1376 int CMapReader::ParseTerrain()
1378 TIMER(L"ParseTerrain");
1379 ScriptRequest rq(pSimulation2->GetScriptInterface());
1381 // parse terrain from map data
1382 // an error here should stop the loading process
1383 #define GET_TERRAIN_PROPERTY(val, prop, out)\
1384 if (!Script::GetProperty(rq, val, #prop, out))\
1385 { LOGERROR("CMapReader::ParseTerrain() failed to get '%s' property", #prop);\
1386 throw PSERROR_Game_World_MapLoadFailed("Error parsing terrain data.\nCheck application log for details"); }
1388 u32 size;
1389 GET_TERRAIN_PROPERTY(m_MapData, size, size)
1391 m_PatchesPerSide = size / PATCH_SIZE;
1393 // flat heightmap of u16 data
1394 GET_TERRAIN_PROPERTY(m_MapData, height, m_Heightmap)
1396 // load textures
1397 std::vector<std::string> textureNames;
1398 GET_TERRAIN_PROPERTY(m_MapData, textureNames, textureNames)
1399 num_terrain_tex = textureNames.size();
1401 while (cur_terrain_tex < num_terrain_tex)
1403 if (CTerrainTextureManager::IsInitialised())
1405 CTerrainTextureEntry* texentry = g_TexMan.FindTexture(textureNames[cur_terrain_tex]);
1406 m_TerrainTextures.push_back(texentry);
1409 cur_terrain_tex++;
1412 // build tile data
1413 m_Tiles.resize(SQR(size));
1415 JS::RootedValue tileData(rq.cx);
1416 GET_TERRAIN_PROPERTY(m_MapData, tileData, &tileData)
1418 // parse tile data object into flat arrays
1419 std::vector<u16> tileIndex;
1420 std::vector<u16> tilePriority;
1421 GET_TERRAIN_PROPERTY(tileData, index, tileIndex);
1422 GET_TERRAIN_PROPERTY(tileData, priority, tilePriority);
1424 ENSURE(SQR(size) == tileIndex.size() && SQR(size) == tilePriority.size());
1426 // reorder by patches and store
1427 for (size_t x = 0; x < size; ++x)
1429 size_t patchX = x / PATCH_SIZE;
1430 size_t offX = x % PATCH_SIZE;
1431 for (size_t y = 0; y < size; ++y)
1433 size_t patchY = y / PATCH_SIZE;
1434 size_t offY = y % PATCH_SIZE;
1436 STileDesc tile;
1437 tile.m_Tex1Index = tileIndex[y*size + x];
1438 tile.m_Tex2Index = 0xFFFF;
1439 tile.m_Priority = tilePriority[y*size + x];
1441 m_Tiles[(patchY * m_PatchesPerSide + patchX) * SQR(PATCH_SIZE) + (offY * PATCH_SIZE + offX)] = tile;
1445 // reset generator state
1446 cur_terrain_tex = 0;
1448 #undef GET_TERRAIN_PROPERTY
1450 return 0;
1453 int CMapReader::ParseEntities()
1455 TIMER(L"ParseEntities");
1456 ScriptRequest rq(pSimulation2->GetScriptInterface());
1458 // parse entities from map data
1459 std::vector<Entity> entities;
1461 if (!Script::GetProperty(rq, m_MapData, "entities", entities))
1462 LOGWARNING("CMapReader::ParseEntities() failed to get 'entities' property");
1464 CSimulation2& sim = *pSimulation2;
1465 CmpPtr<ICmpPlayerManager> cmpPlayerManager(sim, SYSTEM_ENTITY);
1467 size_t entity_idx = 0;
1468 size_t num_entities = entities.size();
1470 Entity currEnt;
1472 while (entity_idx < num_entities)
1474 // Get current entity struct
1475 currEnt = entities[entity_idx];
1477 entity_id_t ent = pSimulation2->AddEntity(currEnt.templateName, currEnt.entityID);
1478 entity_id_t player = cmpPlayerManager->GetPlayerByID(currEnt.playerID);
1479 if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
1480 { // Don't add entities with invalid player IDs
1481 LOGERROR("Failed to load entity template '%s'", utf8_from_wstring(currEnt.templateName));
1483 else
1485 CmpPtr<ICmpPosition> cmpPosition(sim, ent);
1486 if (cmpPosition)
1488 cmpPosition->JumpTo(currEnt.position.X * (int)TERRAIN_TILE_SIZE, currEnt.position.Z * (int)TERRAIN_TILE_SIZE);
1489 cmpPosition->SetYRotation(currEnt.rotation.Y);
1490 // TODO: other parts of the position
1493 CmpPtr<ICmpOwnership> cmpOwnership(sim, ent);
1494 if (cmpOwnership)
1495 cmpOwnership->SetOwner(currEnt.playerID);
1497 // Detect and fix collisions between foundation-blocking entities.
1498 // This presently serves to copy wall tower control groups to wall
1499 // segments, allowing players to expand RMS-generated walls.
1500 CmpPtr<ICmpObstruction> cmpObstruction(sim, ent);
1501 if (cmpObstruction)
1502 cmpObstruction->ResolveFoundationCollisions();
1504 if (currEnt.playerID == m_PlayerID && (boost::algorithm::ends_with(currEnt.templateName, L"civil_centre") || m_StartingCameraTarget == INVALID_ENTITY))
1506 // Focus on civil centre or first entity owned by player
1507 m_StartingCameraTarget = currEnt.entityID;
1511 entity_idx++;
1514 return 0;
1517 int CMapReader::ParseEnvironment()
1519 // parse environment settings from map data
1520 ScriptRequest rq(pSimulation2->GetScriptInterface());
1522 #define GET_ENVIRONMENT_PROPERTY(val, prop, out)\
1523 if (!Script::GetProperty(rq, val, #prop, out))\
1524 LOGWARNING("CMapReader::ParseEnvironment() failed to get '%s' property", #prop);
1526 JS::RootedValue envObj(rq.cx);
1527 GET_ENVIRONMENT_PROPERTY(m_MapData, Environment, &envObj)
1529 if (envObj.isUndefined())
1531 LOGWARNING("CMapReader::ParseEnvironment(): Environment settings not found");
1532 return 0;
1535 if (pPostproc)
1536 pPostproc->SetPostEffect(L"default");
1538 std::wstring skySet;
1539 GET_ENVIRONMENT_PROPERTY(envObj, SkySet, skySet)
1540 if (pSkyMan)
1541 pSkyMan->SetSkySet(skySet);
1543 CColor sunColor;
1544 GET_ENVIRONMENT_PROPERTY(envObj, SunColor, sunColor)
1545 m_LightEnv.m_SunColor = RGBColor(sunColor.r, sunColor.g, sunColor.b);
1547 GET_ENVIRONMENT_PROPERTY(envObj, SunElevation, m_LightEnv.m_Elevation)
1548 GET_ENVIRONMENT_PROPERTY(envObj, SunRotation, m_LightEnv.m_Rotation)
1550 CColor ambientColor;
1551 GET_ENVIRONMENT_PROPERTY(envObj, AmbientColor, ambientColor)
1552 m_LightEnv.m_AmbientColor = RGBColor(ambientColor.r, ambientColor.g, ambientColor.b);
1554 // Water properties
1555 JS::RootedValue waterObj(rq.cx);
1556 GET_ENVIRONMENT_PROPERTY(envObj, Water, &waterObj)
1558 JS::RootedValue waterBodyObj(rq.cx);
1559 GET_ENVIRONMENT_PROPERTY(waterObj, WaterBody, &waterBodyObj)
1561 // Water level - necessary
1562 float waterHeight;
1563 GET_ENVIRONMENT_PROPERTY(waterBodyObj, Height, waterHeight)
1565 CmpPtr<ICmpWaterManager> cmpWaterManager(*pSimulation2, SYSTEM_ENTITY);
1566 ENSURE(cmpWaterManager);
1567 cmpWaterManager->SetWaterLevel(entity_pos_t::FromFloat(waterHeight));
1569 // If we have graphics, get rest of settings
1570 if (pWaterMan)
1572 GET_ENVIRONMENT_PROPERTY(waterBodyObj, Type, pWaterMan->m_WaterType)
1573 if (pWaterMan->m_WaterType == L"default")
1574 pWaterMan->m_WaterType = L"ocean";
1575 GET_ENVIRONMENT_PROPERTY(waterBodyObj, Color, pWaterMan->m_WaterColor)
1576 GET_ENVIRONMENT_PROPERTY(waterBodyObj, Tint, pWaterMan->m_WaterTint)
1577 GET_ENVIRONMENT_PROPERTY(waterBodyObj, Waviness, pWaterMan->m_Waviness)
1578 GET_ENVIRONMENT_PROPERTY(waterBodyObj, Murkiness, pWaterMan->m_Murkiness)
1579 GET_ENVIRONMENT_PROPERTY(waterBodyObj, WindAngle, pWaterMan->m_WindAngle)
1582 JS::RootedValue fogObject(rq.cx);
1583 GET_ENVIRONMENT_PROPERTY(envObj, Fog, &fogObject);
1585 GET_ENVIRONMENT_PROPERTY(fogObject, FogFactor, m_LightEnv.m_FogFactor);
1586 GET_ENVIRONMENT_PROPERTY(fogObject, FogThickness, m_LightEnv.m_FogMax);
1588 CColor fogColor;
1589 GET_ENVIRONMENT_PROPERTY(fogObject, FogColor, fogColor);
1590 m_LightEnv.m_FogColor = RGBColor(fogColor.r, fogColor.g, fogColor.b);
1592 JS::RootedValue postprocObject(rq.cx);
1593 GET_ENVIRONMENT_PROPERTY(envObj, Postproc, &postprocObject);
1595 std::wstring postProcEffect;
1596 GET_ENVIRONMENT_PROPERTY(postprocObject, PostprocEffect, postProcEffect);
1598 if (pPostproc)
1599 pPostproc->SetPostEffect(postProcEffect);
1601 GET_ENVIRONMENT_PROPERTY(postprocObject, Brightness, m_LightEnv.m_Brightness);
1602 GET_ENVIRONMENT_PROPERTY(postprocObject, Contrast, m_LightEnv.m_Contrast);
1603 GET_ENVIRONMENT_PROPERTY(postprocObject, Saturation, m_LightEnv.m_Saturation);
1604 GET_ENVIRONMENT_PROPERTY(postprocObject, Bloom, m_LightEnv.m_Bloom);
1606 m_LightEnv.CalculateSunDirection();
1608 #undef GET_ENVIRONMENT_PROPERTY
1610 return 0;
1613 int CMapReader::ParseCamera()
1615 ScriptRequest rq(pSimulation2->GetScriptInterface());
1617 // parse camera settings from map data
1618 // defaults if we don't find player starting camera
1619 float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f);
1620 CVector3D translation = CVector3D(100, 150, -100);
1622 #define GET_CAMERA_PROPERTY(val, prop, out)\
1623 if (!Script::GetProperty(rq, val, #prop, out))\
1624 LOGWARNING("CMapReader::ParseCamera() failed to get '%s' property", #prop);
1626 JS::RootedValue cameraObj(rq.cx);
1627 GET_CAMERA_PROPERTY(m_MapData, Camera, &cameraObj)
1629 if (!cameraObj.isUndefined())
1630 { // If camera property exists, read values
1631 CFixedVector3D pos;
1632 GET_CAMERA_PROPERTY(cameraObj, Position, pos)
1633 translation = pos;
1635 GET_CAMERA_PROPERTY(cameraObj, Rotation, rotation)
1636 GET_CAMERA_PROPERTY(cameraObj, Declination, declination)
1638 #undef GET_CAMERA_PROPERTY
1640 if (pGameView)
1642 pGameView->GetCamera()->m_Orientation.SetXRotation(declination);
1643 pGameView->GetCamera()->m_Orientation.RotateY(rotation);
1644 pGameView->GetCamera()->m_Orientation.Translate(translation);
1645 pGameView->GetCamera()->UpdateFrustum();
1648 return 0;
1651 CMapReader::~CMapReader()
1653 // Cleaup objects
1654 delete xml_reader;
1655 delete m_MapGen;