1 /* Copyright (C) 2021 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"
21 #include "CinemaManager.h"
24 #include "MapReader.h"
25 #include "MapWriter.h"
28 #include "TerrainTextureEntry.h"
29 #include "TerrainTextureManager.h"
31 #include "maths/MathUtil.h"
32 #include "maths/NUSpline.h"
33 #include "ps/CLogger.h"
34 #include "ps/Loader.h"
35 #include "ps/Filesystem.h"
36 #include "ps/XML/XMLWriter.h"
37 #include "renderer/PostprocManager.h"
38 #include "renderer/SkyManager.h"
39 #include "renderer/WaterManager.h"
40 #include "simulation2/Simulation2.h"
41 #include "simulation2/components/ICmpCinemaManager.h"
42 #include "simulation2/components/ICmpGarrisonHolder.h"
43 #include "simulation2/components/ICmpObstruction.h"
44 #include "simulation2/components/ICmpOwnership.h"
45 #include "simulation2/components/ICmpPosition.h"
46 #include "simulation2/components/ICmpTemplateManager.h"
47 #include "simulation2/components/ICmpTurretHolder.h"
48 #include "simulation2/components/ICmpVisual.h"
49 #include "simulation2/components/ICmpWaterManager.h"
51 ///////////////////////////////////////////////////////////////////////////////////////////////////
52 // CMapWriter constructor: nothing to do at the minute
53 CMapWriter::CMapWriter()
57 ///////////////////////////////////////////////////////////////////////////////////////////////////
58 // SaveMap: try to save the current map to the given file
59 void CMapWriter::SaveMap(const VfsPath
& pathname
, CTerrain
* pTerrain
,
60 WaterManager
* pWaterMan
, SkyManager
* pSkyMan
,
61 CLightEnv
* pLightEnv
, CCamera
* pCamera
, CCinemaManager
* UNUSED(pCinema
),
62 CPostprocManager
* pPostproc
,
63 CSimulation2
* pSimulation2
)
65 CFilePacker
packer(FILE_VERSION
, "PSMP");
67 // build necessary data
68 PackMap(packer
, pTerrain
);
73 packer
.Write(pathname
);
75 catch (PSERROR_File_WriteFailed
&)
77 LOGERROR("Failed to write map '%s'", pathname
.string8());
81 VfsPath pathnameXML
= pathname
.ChangeExtension(L
".xml");
82 WriteXML(pathnameXML
, pWaterMan
, pSkyMan
, pLightEnv
, pCamera
, pPostproc
, pSimulation2
);
85 ///////////////////////////////////////////////////////////////////////////////////////////////////
86 // GetHandleIndex: return the index of the given handle in the given list; or 0xFFFF if
87 // handle isn't in list
88 static u16
GetEntryIndex(const CTerrainTextureEntry
* entry
, const std::vector
<CTerrainTextureEntry
*>& entries
)
90 const size_t limit
= std::min(entries
.size(), size_t(0xFFFEu
)); // paranoia
91 for (size_t i
=0;i
<limit
;i
++) {
92 if (entries
[i
]==entry
) {
100 ///////////////////////////////////////////////////////////////////////////////////////////////////
101 // EnumTerrainTextures: build lists of textures used by map, and tile descriptions for
102 // each tile on the terrain
103 void CMapWriter::EnumTerrainTextures(CTerrain
*pTerrain
,
104 std::vector
<CStr
>& textures
,
105 std::vector
<STileDesc
>& tiles
)
107 // the list of all handles in use
108 std::vector
<CTerrainTextureEntry
*> entries
;
110 // resize tile array to required size
111 tiles
.resize(SQR(pTerrain
->GetVerticesPerSide()-1));
112 STileDesc
* tileptr
=&tiles
[0];
114 // now iterate through all the tiles
115 const ssize_t patchesPerSide
=pTerrain
->GetPatchesPerSide();
116 for (ssize_t j
=0;j
<patchesPerSide
;j
++) {
117 for (ssize_t i
=0;i
<patchesPerSide
;i
++) {
118 for (ssize_t m
=0;m
<PATCH_SIZE
;m
++) {
119 for (ssize_t k
=0;k
<PATCH_SIZE
;k
++) {
120 CMiniPatch
& mp
=pTerrain
->GetPatch(i
,j
)->m_MiniPatches
[m
][k
]; // can't fail
121 u16 index
=u16(GetEntryIndex(mp
.GetTextureEntry(),entries
));
123 index
=(u16
)entries
.size();
124 entries
.push_back(mp
.GetTextureEntry());
127 tileptr
->m_Tex1Index
=index
;
128 tileptr
->m_Tex2Index
=0xFFFF;
129 tileptr
->m_Priority
=mp
.GetPriority();
136 // now find the texture names for each handle
137 for (size_t i
=0;i
<entries
.size();i
++) {
139 CTerrainTextureEntry
* texentry
=entries
[i
];
141 // uh-oh, this shouldn't happen; set texturename to empty string
144 texturename
=texentry
->GetTag();
146 textures
.push_back(texturename
);
150 ///////////////////////////////////////////////////////////////////////////////////////////////////
151 // PackMap: pack the current world into a raw data stream
152 void CMapWriter::PackMap(CFilePacker
& packer
, CTerrain
* pTerrain
)
154 // now pack everything up
155 PackTerrain(packer
, pTerrain
);
158 ///////////////////////////////////////////////////////////////////////////////////////////////////
159 // PackTerrain: pack the terrain onto the end of the output data stream
160 // - data: map size, heightmap, list of textures used by map, texture tile assignments
161 void CMapWriter::PackTerrain(CFilePacker
& packer
, CTerrain
* pTerrain
)
164 const ssize_t mapsize
= pTerrain
->GetPatchesPerSide();
165 packer
.PackSize(mapsize
);
168 packer
.PackRaw(pTerrain
->GetHeightMap(),sizeof(u16
)*SQR(pTerrain
->GetVerticesPerSide()));
170 // the list of textures used by map
171 std::vector
<CStr
> terrainTextures
;
172 // descriptions of each tile
173 std::vector
<STileDesc
> tiles
;
175 // build lists by scanning through the terrain
176 EnumTerrainTextures(pTerrain
, terrainTextures
, tiles
);
178 // pack texture names
179 const size_t numTextures
= terrainTextures
.size();
180 packer
.PackSize(numTextures
);
181 for (size_t i
=0;i
<numTextures
;i
++)
182 packer
.PackString(terrainTextures
[i
]);
185 packer
.PackRaw(&tiles
[0],sizeof(STileDesc
)*tiles
.size());
188 void CMapWriter::WriteXML(const VfsPath
& filename
,
189 WaterManager
* pWaterMan
, SkyManager
* pSkyMan
,
190 CLightEnv
* pLightEnv
, CCamera
* pCamera
,
191 CPostprocManager
* pPostproc
,
192 CSimulation2
* pSimulation2
)
194 XMLWriter_File xmlMapFile
;
196 XMLWriter_Element
scenarioTag(xmlMapFile
, "Scenario");
197 scenarioTag
.Attribute("version", static_cast<int>(FILE_VERSION
));
199 ENSURE(pSimulation2
);
200 CSimulation2
& sim
= *pSimulation2
;
202 if (!sim
.GetStartupScript().empty())
204 XMLWriter_Element
scriptTag(xmlMapFile
, "Script");
205 scriptTag
.Text(sim
.GetStartupScript().c_str(), true);
209 XMLWriter_Element
environmentTag(xmlMapFile
, "Environment");
210 environmentTag
.Setting("SkySet", pSkyMan
->GetSkySet());
212 XMLWriter_Element
sunColorTag(xmlMapFile
, "SunColor");
213 sunColorTag
.Attribute("r", pLightEnv
->m_SunColor
.X
); // yes, it's X/Y/Z...
214 sunColorTag
.Attribute("g", pLightEnv
->m_SunColor
.Y
);
215 sunColorTag
.Attribute("b", pLightEnv
->m_SunColor
.Z
);
218 XMLWriter_Element
SunElevationTag(xmlMapFile
, "SunElevation");
219 SunElevationTag
.Attribute("angle", pLightEnv
->m_Elevation
);
222 XMLWriter_Element
sunRotationTag(xmlMapFile
, "SunRotation");
223 sunRotationTag
.Attribute("angle", pLightEnv
->m_Rotation
);
226 XMLWriter_Element
ambientColorTag(xmlMapFile
, "AmbientColor");
227 ambientColorTag
.Attribute("r", pLightEnv
->m_AmbientColor
.X
);
228 ambientColorTag
.Attribute("g", pLightEnv
->m_AmbientColor
.Y
);
229 ambientColorTag
.Attribute("b", pLightEnv
->m_AmbientColor
.Z
);
232 XMLWriter_Element
fogTag(xmlMapFile
, "Fog");
233 fogTag
.Setting("FogFactor", pLightEnv
->m_FogFactor
);
234 fogTag
.Setting("FogThickness", pLightEnv
->m_FogMax
);
236 XMLWriter_Element
fogColorTag(xmlMapFile
, "FogColor");
237 fogColorTag
.Attribute("r", pLightEnv
->m_FogColor
.X
);
238 fogColorTag
.Attribute("g", pLightEnv
->m_FogColor
.Y
);
239 fogColorTag
.Attribute("b", pLightEnv
->m_FogColor
.Z
);
244 XMLWriter_Element
waterTag(xmlMapFile
, "Water");
246 XMLWriter_Element
waterBodyTag(xmlMapFile
, "WaterBody");
247 CmpPtr
<ICmpWaterManager
> cmpWaterManager(sim
, SYSTEM_ENTITY
);
248 ENSURE(cmpWaterManager
);
249 waterBodyTag
.Setting("Type", pWaterMan
->m_WaterType
);
251 XMLWriter_Element
colorTag(xmlMapFile
, "Color");
252 colorTag
.Attribute("r", pWaterMan
->m_WaterColor
.r
);
253 colorTag
.Attribute("g", pWaterMan
->m_WaterColor
.g
);
254 colorTag
.Attribute("b", pWaterMan
->m_WaterColor
.b
);
257 XMLWriter_Element
tintTag(xmlMapFile
, "Tint");
258 tintTag
.Attribute("r", pWaterMan
->m_WaterTint
.r
);
259 tintTag
.Attribute("g", pWaterMan
->m_WaterTint
.g
);
260 tintTag
.Attribute("b", pWaterMan
->m_WaterTint
.b
);
262 waterBodyTag
.Setting("Height", cmpWaterManager
->GetExactWaterLevel(0, 0));
263 waterBodyTag
.Setting("Waviness", pWaterMan
->m_Waviness
);
264 waterBodyTag
.Setting("Murkiness", pWaterMan
->m_Murkiness
);
265 waterBodyTag
.Setting("WindAngle", pWaterMan
->m_WindAngle
);
270 XMLWriter_Element
postProcTag(xmlMapFile
, "Postproc");
272 postProcTag
.Setting("Brightness", pLightEnv
->m_Brightness
);
273 postProcTag
.Setting("Contrast", pLightEnv
->m_Contrast
);
274 postProcTag
.Setting("Saturation", pLightEnv
->m_Saturation
);
275 postProcTag
.Setting("Bloom", pLightEnv
->m_Bloom
);
276 postProcTag
.Setting("PostEffect", pPostproc
->GetPostEffect());
282 XMLWriter_Element
cameraTag(xmlMapFile
, "Camera");
284 XMLWriter_Element
positionTag(xmlMapFile
, "Position");
285 CVector3D pos
= pCamera
->GetOrientation().GetTranslation();
286 positionTag
.Attribute("x", pos
.X
);
287 positionTag
.Attribute("y", pos
.Y
);
288 positionTag
.Attribute("z", pos
.Z
);
291 CVector3D in
= pCamera
->GetOrientation().GetIn();
292 // Convert to spherical coordinates
293 float rotation
= atan2(in
.X
, in
.Z
);
294 float declination
= atan2(sqrt(in
.X
*in
.X
+ in
.Z
*in
.Z
), in
.Y
) - static_cast<float>(M_PI
/ 2);
297 XMLWriter_Element
rotationTag(xmlMapFile
, "Rotation");
298 rotationTag
.Attribute("angle", rotation
);
301 XMLWriter_Element
declinationTag(xmlMapFile
, "Declination");
302 declinationTag
.Attribute("angle", declination
);
307 std::string settings
= sim
.GetMapSettingsString();
308 if (!settings
.empty())
310 XMLWriter_Element
scriptSettingsTag(xmlMapFile
, "ScriptSettings");
311 scriptSettingsTag
.Text(("\n" + settings
+ "\n").c_str(), true);
316 XMLWriter_Element
entitiesTag(xmlMapFile
, "Entities");
317 CmpPtr
<ICmpTemplateManager
> cmpTemplateManager(sim
, SYSTEM_ENTITY
);
318 ENSURE(cmpTemplateManager
);
320 // This will probably need to be changed in the future, but for now we'll
321 // just save all entities that have a position
322 CSimulation2::InterfaceList ents
= sim
.GetEntitiesWithInterface(IID_Position
);
323 for (CSimulation2::InterfaceList::const_iterator it
= ents
.begin(); it
!= ents
.end(); ++it
)
325 entity_id_t ent
= it
->first
;
327 // Don't save local entities (placement previews etc)
328 if (ENTITY_IS_LOCAL(ent
))
331 XMLWriter_Element
entityTag(xmlMapFile
, "Entity");
332 entityTag
.Attribute("uid", ent
);
334 entityTag
.Setting("Template", cmpTemplateManager
->GetCurrentTemplateName(ent
));
336 CmpPtr
<ICmpOwnership
> cmpOwnership(sim
, ent
);
338 entityTag
.Setting("Player", static_cast<int>(cmpOwnership
->GetOwner()));
340 CmpPtr
<ICmpGarrisonHolder
> cmpGarrisonHolder(sim
, ent
);
341 if (cmpGarrisonHolder
)
343 std::vector
<entity_id_t
> garrison
= cmpGarrisonHolder
->GetEntities();
344 if (!garrison
.empty())
346 XMLWriter_Element
garrisonTag(xmlMapFile
, "Garrison");
347 for (const entity_id_t garr_ent_id
: garrison
)
349 XMLWriter_Element
garrisonedEntityTag(xmlMapFile
, "GarrisonedEntity");
350 garrisonedEntityTag
.Attribute("uid", static_cast<int>(garr_ent_id
));
355 CmpPtr
<ICmpTurretHolder
> cmpTurretHolder(sim
, ent
);
358 std::vector
<std::pair
<std::string
, entity_id_t
> > turrets
= cmpTurretHolder
->GetTurrets();
359 if (!turrets
.empty())
361 XMLWriter_Element
turretTag(xmlMapFile
, "Turrets");
362 for (const std::pair
<std::string
, entity_id_t
>& turret
: turrets
)
364 XMLWriter_Element
turretedEntityTag(xmlMapFile
, "Turret");
365 turretedEntityTag
.Attribute("turret", turret
.first
);
366 turretedEntityTag
.Attribute("uid", static_cast<int>(turret
.second
));
371 CmpPtr
<ICmpPosition
> cmpPosition(sim
, ent
);
375 if (cmpPosition
->IsInWorld())
376 pos
= cmpPosition
->GetPosition();
378 CFixedVector3D rot
= cmpPosition
->GetRotation();
380 XMLWriter_Element
positionTag(xmlMapFile
, "Position");
381 positionTag
.Attribute("x", pos
.X
);
382 positionTag
.Attribute("z", pos
.Z
);
383 // TODO: height offset etc
386 XMLWriter_Element
orientationTag(xmlMapFile
, "Orientation");
387 orientationTag
.Attribute("y", rot
.Y
);
392 CmpPtr
<ICmpObstruction
> cmpObstruction(sim
, ent
);
395 // TODO: Currently only necessary because Atlas
396 // does not set up control groups for its walls.
397 cmpObstruction
->ResolveFoundationCollisions();
399 entity_id_t group
= cmpObstruction
->GetControlGroup();
400 entity_id_t group2
= cmpObstruction
->GetControlGroup2();
402 // Don't waste space writing the default control groups.
403 if (group
!= ent
|| group2
!= INVALID_ENTITY
)
405 XMLWriter_Element
obstructionTag(xmlMapFile
, "Obstruction");
407 obstructionTag
.Attribute("group", group
);
408 if (group2
!= INVALID_ENTITY
)
409 obstructionTag
.Attribute("group2", group2
);
413 CmpPtr
<ICmpVisual
> cmpVisual(sim
, ent
);
416 entity_id_t seed
= static_cast<entity_id_t
>(cmpVisual
->GetActorSeed());
419 XMLWriter_Element
actorTag(xmlMapFile
, "Actor");
420 actorTag
.Attribute("seed",seed
);
422 // TODO: variation/selection strings
428 CmpPtr
<ICmpCinemaManager
> cmpCinemaManager(sim
, SYSTEM_ENTITY
);
429 if (cmpCinemaManager
)
431 const std::map
<CStrW
, CCinemaPath
>& paths
= cmpCinemaManager
->GetPaths();
432 std::map
<CStrW
, CCinemaPath
>::const_iterator it
= paths
.begin();
433 XMLWriter_Element
pathsTag(xmlMapFile
, "Paths");
435 for ( ; it
!= paths
.end(); ++it
)
437 fixed timescale
= it
->second
.GetTimescale();
438 const std::vector
<SplineData
>& position_nodes
= it
->second
.GetAllNodes();
439 const std::vector
<SplineData
>& target_nodes
= it
->second
.GetTargetSpline().GetAllNodes();
440 const CCinemaData
* data
= it
->second
.GetData();
442 XMLWriter_Element
pathTag(xmlMapFile
, "Path");
443 pathTag
.Attribute("name", data
->m_Name
);
444 pathTag
.Attribute("timescale", timescale
);
445 pathTag
.Attribute("orientation", data
->m_Orientation
);
446 pathTag
.Attribute("mode", data
->m_Mode
);
447 pathTag
.Attribute("style", data
->m_Style
);
453 CFixedVector3D value
;
454 SEvent(fixed time
, const char* type
, CFixedVector3D value
)
455 : time(time
), type(type
), value(value
)
457 bool operator<(const SEvent
& another
) const
459 return time
< another
.time
;
463 // All events of a manipulating of camera (position/rotation/target)
464 std::vector
<SEvent
> events
;
465 events
.reserve(position_nodes
.size() + target_nodes
.size());
467 fixed last_position
= fixed::Zero();
468 for (size_t i
= 0; i
< position_nodes
.size(); ++i
)
470 fixed distance
= i
> 0 ? position_nodes
[i
- 1].Distance
: fixed::Zero();
471 last_position
+= distance
;
472 events
.emplace_back(last_position
, "Position", position_nodes
[i
].Position
);
475 fixed last_target
= fixed::Zero();
476 for (size_t i
= 0; i
< target_nodes
.size(); ++i
)
478 fixed distance
= i
> 0 ? target_nodes
[i
- 1].Distance
: fixed::Zero();
479 last_target
+= distance
;
480 events
.emplace_back(last_target
, "Target", target_nodes
[i
].Position
);
483 std::sort(events
.begin(), events
.end());
484 for (size_t i
= 0; i
< events
.size();)
486 XMLWriter_Element
nodeTag(xmlMapFile
, "Node");
487 fixed deltatime
= i
> 0 ? (events
[i
].time
- events
[i
- 1].time
) : fixed::Zero();
488 nodeTag
.Attribute("deltatime", deltatime
);
490 for (; j
< events
.size() && events
[j
].time
== events
[i
].time
; ++j
)
492 // Types: Position/Rotation/Target
493 XMLWriter_Element
eventTypeTag(xmlMapFile
, events
[j
].type
);
494 eventTypeTag
.Attribute("x", events
[j
].value
.X
);
495 eventTypeTag
.Attribute("y", events
[j
].value
.Y
);
496 eventTypeTag
.Attribute("z", events
[j
].value
.Z
);
503 if (!xmlMapFile
.StoreVFS(g_VFS
, filename
))
504 LOGERROR("Failed to write map '%s'", filename
.string8());