1#include "image.h"
2#include "sound.h"
3
4#include <base/log.h>
5
6#include <engine/client.h>
7#include <engine/engine.h>
8#include <engine/gfx/image_manipulation.h>
9#include <engine/graphics.h>
10#include <engine/shared/config.h>
11#include <engine/shared/datafile.h>
12#include <engine/shared/filecollection.h>
13#include <engine/sound.h>
14#include <engine/storage.h>
15
16#include <game/editor/editor.h>
17#include <game/gamecore.h>
18#include <game/mapitems_ex.h>
19
20// compatibility with old sound layers
21class CSoundSourceDeprecated
22{
23public:
24 CPoint m_Position;
25 int m_Loop;
26 int m_TimeDelay; // in s
27 int m_FalloffDistance;
28 int m_PosEnv;
29 int m_PosEnvOffset;
30 int m_SoundEnv;
31 int m_SoundEnvOffset;
32};
33
34void CDataFileWriterFinishJob::Run()
35{
36 m_Writer.Finish();
37}
38
39CDataFileWriterFinishJob::CDataFileWriterFinishJob(const char *pRealFilename, const char *pTempFilename, CDataFileWriter &&Writer) :
40 m_Writer(std::move(Writer))
41{
42 str_copy(dst&: m_aRealFilename, src: pRealFilename);
43 str_copy(dst&: m_aTempFilename, src: pTempFilename);
44}
45
46bool CEditorMap::Save(const char *pFilename, const FErrorHandler &ErrorHandler)
47{
48 char aFilenameTmp[IO_MAX_PATH_LENGTH];
49 IStorage::FormatTmpPath(aBuf: aFilenameTmp, BufSize: sizeof(aFilenameTmp), pPath: pFilename);
50
51 log_info("editor/save", "Saving map to '%s'...", aFilenameTmp);
52
53 if(!PerformPreSaveSanityChecks(ErrorHandler))
54 {
55 return false;
56 }
57
58 CDataFileWriter Writer;
59 if(!Writer.Open(pStorage: m_pEditor->Storage(), pFilename: aFilenameTmp))
60 {
61 char aBuf[IO_MAX_PATH_LENGTH + 64];
62 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to open file '%s' for writing.", aFilenameTmp);
63 ErrorHandler(aBuf);
64 return false;
65 }
66
67 // save version
68 {
69 CMapItemVersion Item;
70 Item.m_Version = 1;
71 Writer.AddItem(Type: MAPITEMTYPE_VERSION, Id: 0, Size: sizeof(Item), pData: &Item);
72 }
73
74 // save map info
75 {
76 CMapItemInfoSettings Item;
77 Item.m_Version = 1;
78 Item.m_Author = Writer.AddDataString(pStr: m_MapInfo.m_aAuthor);
79 Item.m_MapVersion = Writer.AddDataString(pStr: m_MapInfo.m_aVersion);
80 Item.m_Credits = Writer.AddDataString(pStr: m_MapInfo.m_aCredits);
81 Item.m_License = Writer.AddDataString(pStr: m_MapInfo.m_aLicense);
82
83 Item.m_Settings = -1;
84 if(!m_vSettings.empty())
85 {
86 int Size = 0;
87 for(const auto &Setting : m_vSettings)
88 {
89 Size += str_length(str: Setting.m_aCommand) + 1;
90 }
91
92 char *pSettings = (char *)malloc(size: maximum(a: Size, b: 1));
93 char *pNext = pSettings;
94 for(const auto &Setting : m_vSettings)
95 {
96 int Length = str_length(str: Setting.m_aCommand) + 1;
97 mem_copy(dest: pNext, source: Setting.m_aCommand, size: Length);
98 pNext += Length;
99 }
100 Item.m_Settings = Writer.AddData(Size, pData: pSettings);
101 free(ptr: pSettings);
102 }
103
104 Writer.AddItem(Type: MAPITEMTYPE_INFO, Id: 0, Size: sizeof(Item), pData: &Item);
105 }
106
107 // save images
108 for(size_t i = 0; i < m_vpImages.size(); i++)
109 {
110 std::shared_ptr<CEditorImage> pImg = m_vpImages[i];
111
112 // analyse the image for when saving (should be done when we load the image)
113 // TODO!
114 pImg->AnalyseTileFlags();
115
116 CMapItemImage Item;
117 Item.m_Version = 1;
118
119 Item.m_Width = pImg->m_Width;
120 Item.m_Height = pImg->m_Height;
121 Item.m_External = pImg->m_External;
122 Item.m_ImageName = Writer.AddDataString(pStr: pImg->m_aName);
123 if(pImg->m_External)
124 {
125 Item.m_ImageData = -1;
126 }
127 else
128 {
129 dbg_assert(pImg->m_Format == CImageInfo::FORMAT_RGBA, "Embedded images must be in RGBA format");
130 Item.m_ImageData = Writer.AddData(Size: pImg->DataSize(), pData: pImg->m_pData);
131 }
132 Writer.AddItem(Type: MAPITEMTYPE_IMAGE, Id: i, Size: sizeof(Item), pData: &Item);
133 }
134
135 // save sounds
136 for(size_t i = 0; i < m_vpSounds.size(); i++)
137 {
138 std::shared_ptr<CEditorSound> pSound = m_vpSounds[i];
139
140 CMapItemSound Item;
141 Item.m_Version = 1;
142
143 Item.m_External = 0;
144 Item.m_SoundName = Writer.AddDataString(pStr: pSound->m_aName);
145 Item.m_SoundData = Writer.AddData(Size: pSound->m_DataSize, pData: pSound->m_pData);
146 // Value is not read in new versions, but we still need to write it for compatibility with old versions.
147 Item.m_SoundDataSize = pSound->m_DataSize;
148
149 Writer.AddItem(Type: MAPITEMTYPE_SOUND, Id: i, Size: sizeof(Item), pData: &Item);
150 }
151
152 // save layers
153 int LayerCount = 0, GroupCount = 0;
154 int AutomapperCount = 0;
155 for(const auto &pGroup : m_vpGroups)
156 {
157 log_trace("editor/save", "Saving group");
158
159 CMapItemGroup GItem;
160 GItem.m_Version = 3;
161
162 GItem.m_ParallaxX = pGroup->m_ParallaxX;
163 GItem.m_ParallaxY = pGroup->m_ParallaxY;
164 GItem.m_OffsetX = pGroup->m_OffsetX;
165 GItem.m_OffsetY = pGroup->m_OffsetY;
166 GItem.m_UseClipping = pGroup->m_UseClipping;
167 GItem.m_ClipX = pGroup->m_ClipX;
168 GItem.m_ClipY = pGroup->m_ClipY;
169 GItem.m_ClipW = pGroup->m_ClipW;
170 GItem.m_ClipH = pGroup->m_ClipH;
171 GItem.m_StartLayer = LayerCount;
172 GItem.m_NumLayers = 0;
173
174 // save group name
175 StrToInts(pInts: GItem.m_aName, NumInts: std::size(GItem.m_aName), pStr: pGroup->m_aName);
176
177 for(const std::shared_ptr<CLayer> &pLayer : pGroup->m_vpLayers)
178 {
179 if(pLayer->m_Type == LAYERTYPE_TILES)
180 {
181 log_trace("editor/save", "Saving tiles layer");
182 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: pLayer);
183 pLayerTiles->PrepareForSave();
184
185 CMapItemLayerTilemap Item;
186 Item.m_Version = 3;
187
188 Item.m_Layer.m_Version = 0; // was previously uninitialized, do not rely on it being 0
189 Item.m_Layer.m_Flags = pLayerTiles->m_Flags;
190 Item.m_Layer.m_Type = pLayerTiles->m_Type;
191
192 Item.m_Color = pLayerTiles->m_Color;
193 Item.m_ColorEnv = pLayerTiles->m_ColorEnv;
194 Item.m_ColorEnvOffset = pLayerTiles->m_ColorEnvOffset;
195
196 Item.m_Width = pLayerTiles->m_Width;
197 Item.m_Height = pLayerTiles->m_Height;
198 // Item.m_Flags = pLayerTiles->m_Game ? TILESLAYERFLAG_GAME : 0;
199
200 if(pLayerTiles->m_HasTele)
201 Item.m_Flags = TILESLAYERFLAG_TELE;
202 else if(pLayerTiles->m_HasSpeedup)
203 Item.m_Flags = TILESLAYERFLAG_SPEEDUP;
204 else if(pLayerTiles->m_HasFront)
205 Item.m_Flags = TILESLAYERFLAG_FRONT;
206 else if(pLayerTiles->m_HasSwitch)
207 Item.m_Flags = TILESLAYERFLAG_SWITCH;
208 else if(pLayerTiles->m_HasTune)
209 Item.m_Flags = TILESLAYERFLAG_TUNE;
210 else
211 Item.m_Flags = pLayerTiles->m_HasGame ? TILESLAYERFLAG_GAME : 0;
212
213 Item.m_Image = pLayerTiles->m_Image;
214
215 // the following values were previously uninitialized, do not rely on them being -1 when unused
216 Item.m_Tele = -1;
217 Item.m_Speedup = -1;
218 Item.m_Front = -1;
219 Item.m_Switch = -1;
220 Item.m_Tune = -1;
221
222 if(Item.m_Flags && !(pLayerTiles->m_HasGame))
223 {
224 CTile *pEmptyTiles = (CTile *)calloc(nmemb: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height, size: sizeof(CTile));
225 mem_zero(block: pEmptyTiles, size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTile));
226 Item.m_Data = Writer.AddData(Size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTile), pData: pEmptyTiles);
227 free(ptr: pEmptyTiles);
228
229 if(pLayerTiles->m_HasTele)
230 Item.m_Tele = Writer.AddData(Size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTeleTile), pData: std::static_pointer_cast<CLayerTele>(r: pLayerTiles)->m_pTeleTile);
231 else if(pLayerTiles->m_HasSpeedup)
232 Item.m_Speedup = Writer.AddData(Size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CSpeedupTile), pData: std::static_pointer_cast<CLayerSpeedup>(r: pLayerTiles)->m_pSpeedupTile);
233 else if(pLayerTiles->m_HasFront)
234 Item.m_Front = Writer.AddData(Size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTile), pData: pLayerTiles->m_pTiles);
235 else if(pLayerTiles->m_HasSwitch)
236 Item.m_Switch = Writer.AddData(Size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CSwitchTile), pData: std::static_pointer_cast<CLayerSwitch>(r: pLayerTiles)->m_pSwitchTile);
237 else if(pLayerTiles->m_HasTune)
238 Item.m_Tune = Writer.AddData(Size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTuneTile), pData: std::static_pointer_cast<CLayerTune>(r: pLayerTiles)->m_pTuneTile);
239 }
240 else
241 Item.m_Data = Writer.AddData(Size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTile), pData: pLayerTiles->m_pTiles);
242
243 // save layer name
244 StrToInts(pInts: Item.m_aName, NumInts: std::size(Item.m_aName), pStr: pLayerTiles->m_aName);
245
246 // save item
247 Writer.AddItem(Type: MAPITEMTYPE_LAYER, Id: LayerCount, Size: sizeof(Item), pData: &Item);
248
249 // save auto mapper of each tile layer (not physics layer)
250 if(!Item.m_Flags)
251 {
252 CMapItemAutoMapperConfig ItemAutomapper;
253 ItemAutomapper.m_Version = 1;
254 ItemAutomapper.m_GroupId = GroupCount;
255 ItemAutomapper.m_LayerId = GItem.m_NumLayers;
256 ItemAutomapper.m_AutomapperConfig = pLayerTiles->m_AutoMapperConfig;
257 ItemAutomapper.m_AutomapperSeed = pLayerTiles->m_Seed;
258 ItemAutomapper.m_Flags = 0;
259 if(pLayerTiles->m_AutoAutoMap)
260 ItemAutomapper.m_Flags |= CMapItemAutoMapperConfig::FLAG_AUTOMATIC;
261
262 Writer.AddItem(Type: MAPITEMTYPE_AUTOMAPPER_CONFIG, Id: AutomapperCount, Size: sizeof(ItemAutomapper), pData: &ItemAutomapper);
263 AutomapperCount++;
264 }
265 }
266 else if(pLayer->m_Type == LAYERTYPE_QUADS)
267 {
268 log_trace("editor/save", "Saving quads layer");
269 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: pLayer);
270 CMapItemLayerQuads Item;
271 Item.m_Version = 2;
272 Item.m_Layer.m_Version = 0; // was previously uninitialized, do not rely on it being 0
273 Item.m_Layer.m_Flags = pLayerQuads->m_Flags;
274 Item.m_Layer.m_Type = pLayerQuads->m_Type;
275 Item.m_Image = pLayerQuads->m_Image;
276
277 Item.m_NumQuads = 0;
278 Item.m_Data = -1;
279 if(!pLayerQuads->m_vQuads.empty())
280 {
281 // add the data
282 Item.m_NumQuads = pLayerQuads->m_vQuads.size();
283 Item.m_Data = Writer.AddDataSwapped(Size: pLayerQuads->m_vQuads.size() * sizeof(CQuad), pData: pLayerQuads->m_vQuads.data());
284 }
285 else
286 {
287 // add dummy data for backwards compatibility
288 // this allows the layer to be loaded with an empty array since m_NumQuads is 0 while saving
289 CQuad Dummy{};
290 Item.m_Data = Writer.AddDataSwapped(Size: sizeof(CQuad), pData: &Dummy);
291 }
292
293 // save layer name
294 StrToInts(pInts: Item.m_aName, NumInts: std::size(Item.m_aName), pStr: pLayerQuads->m_aName);
295
296 // save item
297 Writer.AddItem(Type: MAPITEMTYPE_LAYER, Id: LayerCount, Size: sizeof(Item), pData: &Item);
298 }
299 else if(pLayer->m_Type == LAYERTYPE_SOUNDS)
300 {
301 log_trace("editor/save", "Saving sounds layer");
302 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: pLayer);
303 CMapItemLayerSounds Item;
304 Item.m_Version = 2;
305 Item.m_Layer.m_Version = 0; // was previously uninitialized, do not rely on it being 0
306 Item.m_Layer.m_Flags = pLayerSounds->m_Flags;
307 Item.m_Layer.m_Type = pLayerSounds->m_Type;
308 Item.m_Sound = pLayerSounds->m_Sound;
309
310 Item.m_NumSources = 0;
311 if(!pLayerSounds->m_vSources.empty())
312 {
313 // add the data
314 Item.m_NumSources = pLayerSounds->m_vSources.size();
315 Item.m_Data = Writer.AddDataSwapped(Size: pLayerSounds->m_vSources.size() * sizeof(CSoundSource), pData: pLayerSounds->m_vSources.data());
316 }
317 else
318 {
319 // add dummy data for backwards compatibility
320 // this allows the layer to be loaded with an empty array since m_NumSources is 0 while saving
321 CSoundSource Dummy{};
322 Item.m_Data = Writer.AddDataSwapped(Size: sizeof(CSoundSource), pData: &Dummy);
323 }
324
325 // save layer name
326 StrToInts(pInts: Item.m_aName, NumInts: std::size(Item.m_aName), pStr: pLayerSounds->m_aName);
327
328 // save item
329 Writer.AddItem(Type: MAPITEMTYPE_LAYER, Id: LayerCount, Size: sizeof(Item), pData: &Item);
330 }
331
332 GItem.m_NumLayers++;
333 LayerCount++;
334 }
335
336 Writer.AddItem(Type: MAPITEMTYPE_GROUP, Id: GroupCount, Size: sizeof(GItem), pData: &GItem);
337 GroupCount++;
338 }
339
340 // save envelopes
341 log_trace("editor/save", "Saving envelopes");
342 int PointCount = 0;
343 for(size_t e = 0; e < m_vpEnvelopes.size(); e++)
344 {
345 CMapItemEnvelope Item;
346 Item.m_Version = 2;
347 Item.m_Channels = m_vpEnvelopes[e]->GetChannels();
348 Item.m_StartPoint = PointCount;
349 Item.m_NumPoints = m_vpEnvelopes[e]->m_vPoints.size();
350 Item.m_Synchronized = m_vpEnvelopes[e]->m_Synchronized;
351 StrToInts(pInts: Item.m_aName, NumInts: std::size(Item.m_aName), pStr: m_vpEnvelopes[e]->m_aName);
352
353 Writer.AddItem(Type: MAPITEMTYPE_ENVELOPE, Id: e, Size: sizeof(Item), pData: &Item);
354 PointCount += Item.m_NumPoints;
355 }
356
357 // save points
358 log_trace("editor/save", "Saving envelope points");
359 bool BezierUsed = false;
360 for(const auto &pEnvelope : m_vpEnvelopes)
361 {
362 for(const auto &Point : pEnvelope->m_vPoints)
363 {
364 if(Point.m_Curvetype == CURVETYPE_BEZIER)
365 {
366 BezierUsed = true;
367 break;
368 }
369 }
370 if(BezierUsed)
371 break;
372 }
373
374 CEnvPoint *pPoints = (CEnvPoint *)calloc(nmemb: maximum(a: PointCount, b: 1), size: sizeof(CEnvPoint));
375 CEnvPointBezier *pPointsBezier = nullptr;
376 if(BezierUsed)
377 pPointsBezier = (CEnvPointBezier *)calloc(nmemb: maximum(a: PointCount, b: 1), size: sizeof(CEnvPointBezier));
378 PointCount = 0;
379
380 for(const auto &pEnvelope : m_vpEnvelopes)
381 {
382 const CEnvPoint_runtime *pPrevPoint = nullptr;
383 for(const auto &Point : pEnvelope->m_vPoints)
384 {
385 mem_copy(dest: &pPoints[PointCount], source: &Point, size: sizeof(CEnvPoint));
386 if(pPointsBezier != nullptr)
387 {
388 if(Point.m_Curvetype == CURVETYPE_BEZIER)
389 {
390 mem_copy(dest: &pPointsBezier[PointCount].m_aOutTangentDeltaX, source: &Point.m_Bezier.m_aOutTangentDeltaX, size: sizeof(Point.m_Bezier.m_aOutTangentDeltaX));
391 mem_copy(dest: &pPointsBezier[PointCount].m_aOutTangentDeltaY, source: &Point.m_Bezier.m_aOutTangentDeltaY, size: sizeof(Point.m_Bezier.m_aOutTangentDeltaY));
392 }
393 if(pPrevPoint != nullptr && pPrevPoint->m_Curvetype == CURVETYPE_BEZIER)
394 {
395 mem_copy(dest: &pPointsBezier[PointCount].m_aInTangentDeltaX, source: &Point.m_Bezier.m_aInTangentDeltaX, size: sizeof(Point.m_Bezier.m_aInTangentDeltaX));
396 mem_copy(dest: &pPointsBezier[PointCount].m_aInTangentDeltaY, source: &Point.m_Bezier.m_aInTangentDeltaY, size: sizeof(Point.m_Bezier.m_aInTangentDeltaY));
397 }
398 }
399 PointCount++;
400 pPrevPoint = &Point;
401 }
402 }
403
404 Writer.AddItem(Type: MAPITEMTYPE_ENVPOINTS, Id: 0, Size: sizeof(CEnvPoint) * PointCount, pData: pPoints);
405 free(ptr: pPoints);
406
407 if(pPointsBezier != nullptr)
408 {
409 Writer.AddItem(Type: MAPITEMTYPE_ENVPOINTS_BEZIER, Id: 0, Size: sizeof(CEnvPointBezier) * PointCount, pData: pPointsBezier);
410 free(ptr: pPointsBezier);
411 }
412
413 // finish the data file
414 std::shared_ptr<CDataFileWriterFinishJob> pWriterFinishJob = std::make_shared<CDataFileWriterFinishJob>(args&: pFilename, args&: aFilenameTmp, args: std::move(Writer));
415 m_pEditor->Engine()->AddJob(pJob: pWriterFinishJob);
416 m_pEditor->m_WriterFinishJobs.push_back(x: pWriterFinishJob);
417
418 return true;
419}
420
421bool CEditorMap::PerformPreSaveSanityChecks(const FErrorHandler &ErrorHandler)
422{
423 bool Success = true;
424 char aErrorMessage[256];
425
426 for(const std::shared_ptr<CEditorImage> &pImage : m_vpImages)
427 {
428 if(!pImage->m_External && pImage->m_pData == nullptr)
429 {
430 str_format(buffer: aErrorMessage, buffer_size: sizeof(aErrorMessage), format: "Error: Saving is not possible because the image '%s' could not be loaded. Remove or replace this image.", pImage->m_aName);
431 ErrorHandler(aErrorMessage);
432 Success = false;
433 }
434 }
435
436 for(const std::shared_ptr<CEditorSound> &pSound : m_vpSounds)
437 {
438 if(pSound->m_pData == nullptr)
439 {
440 str_format(buffer: aErrorMessage, buffer_size: sizeof(aErrorMessage), format: "Error: Saving is not possible because the sound '%s' could not be loaded. Remove or replace this sound.", pSound->m_aName);
441 ErrorHandler(aErrorMessage);
442 Success = false;
443 }
444 }
445
446 return Success;
447}
448
449bool CEditorMap::Load(const char *pFilename, int StorageType, const FErrorHandler &ErrorHandler)
450{
451 CDataFileReader DataFile;
452 if(!DataFile.Open(pStorage: m_pEditor->Storage(), pFilename, StorageType))
453 {
454 ErrorHandler("Error: Failed to open map file. See local console for details.");
455 return false;
456 }
457
458 // check version
459 const CMapItemVersion *pItemVersion = static_cast<CMapItemVersion *>(DataFile.FindItem(Type: MAPITEMTYPE_VERSION, Id: 0));
460 if(pItemVersion == nullptr || pItemVersion->m_Version != 1)
461 {
462 ErrorHandler("Error: The map has an unsupported version.");
463 return false;
464 }
465
466 Clean();
467
468 // load map info
469 {
470 int Start, Num;
471 DataFile.GetType(Type: MAPITEMTYPE_INFO, pStart: &Start, pNum: &Num);
472 for(int i = Start; i < Start + Num; i++)
473 {
474 int ItemSize = DataFile.GetItemSize(Index: Start);
475 int ItemId;
476 CMapItemInfoSettings *pItem = (CMapItemInfoSettings *)DataFile.GetItem(Index: i, pType: nullptr, pId: &ItemId);
477 if(!pItem || ItemId != 0)
478 continue;
479
480 const auto &&ReadStringInfo = [&](int Index, char *pBuffer, size_t BufferSize, const char *pErrorContext) {
481 const char *pStr = DataFile.GetDataString(Index);
482 if(pStr == nullptr)
483 {
484 char aBuf[128];
485 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read %s from map info.", pErrorContext);
486 ErrorHandler(aBuf);
487 pBuffer[0] = '\0';
488 }
489 else
490 {
491 str_copy(dst: pBuffer, src: pStr, dst_size: BufferSize);
492 }
493 };
494
495 ReadStringInfo(pItem->m_Author, m_MapInfo.m_aAuthor, sizeof(m_MapInfo.m_aAuthor), "author");
496 ReadStringInfo(pItem->m_MapVersion, m_MapInfo.m_aVersion, sizeof(m_MapInfo.m_aVersion), "version");
497 ReadStringInfo(pItem->m_Credits, m_MapInfo.m_aCredits, sizeof(m_MapInfo.m_aCredits), "credits");
498 ReadStringInfo(pItem->m_License, m_MapInfo.m_aLicense, sizeof(m_MapInfo.m_aLicense), "license");
499
500 if(pItem->m_Version != 1 || ItemSize < (int)sizeof(CMapItemInfoSettings))
501 break;
502
503 if(!(pItem->m_Settings > -1))
504 break;
505
506 const unsigned Size = DataFile.GetDataSize(Index: pItem->m_Settings);
507 char *pSettings = (char *)DataFile.GetData(Index: pItem->m_Settings);
508 char *pNext = pSettings;
509 while(pNext < pSettings + Size)
510 {
511 int StrSize = str_length(str: pNext) + 1;
512 m_vSettings.emplace_back(args&: pNext);
513 pNext += StrSize;
514 }
515 }
516 }
517
518 // load images
519 {
520 int Start, Num;
521 DataFile.GetType(Type: MAPITEMTYPE_IMAGE, pStart: &Start, pNum: &Num);
522 for(int i = 0; i < Num; i++)
523 {
524 CMapItemImage_v2 *pItem = (CMapItemImage_v2 *)DataFile.GetItem(Index: Start + i);
525
526 // copy base info
527 std::shared_ptr<CEditorImage> pImg = std::make_shared<CEditorImage>(args: this);
528 pImg->m_External = pItem->m_External;
529
530 const char *pName = DataFile.GetDataString(Index: pItem->m_ImageName);
531 if(pName == nullptr || pName[0] == '\0')
532 {
533 char aBuf[128];
534 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read name of image %d.", i);
535 ErrorHandler(aBuf);
536 }
537 else
538 str_copy(dst&: pImg->m_aName, src: pName);
539
540 if(pItem->m_Version > 1 && pItem->m_MustBe1 != 1)
541 {
542 char aBuf[128];
543 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Unsupported image type of image %d '%s'.", i, pImg->m_aName);
544 ErrorHandler(aBuf);
545 }
546
547 if(pImg->m_External || (pItem->m_Version > 1 && pItem->m_MustBe1 != 1))
548 {
549 char aBuf[IO_MAX_PATH_LENGTH];
550 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "mapres/%s.png", pImg->m_aName);
551
552 // load external
553 CImageInfo ImgInfo;
554 if(m_pEditor->Graphics()->LoadPng(Image&: ImgInfo, pFilename: aBuf, StorageType: IStorage::TYPE_ALL))
555 {
556 pImg->m_Width = ImgInfo.m_Width;
557 pImg->m_Height = ImgInfo.m_Height;
558 pImg->m_Format = ImgInfo.m_Format;
559 pImg->m_pData = ImgInfo.m_pData;
560 ConvertToRgba(Image&: *pImg);
561
562 int TextureLoadFlag = m_pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
563 if(pImg->m_Width % 16 != 0 || pImg->m_Height % 16 != 0)
564 TextureLoadFlag = 0;
565 pImg->m_External = 1;
566 pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(Image: *pImg, Flags: TextureLoadFlag, pTexName: aBuf);
567 }
568 else
569 {
570 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to load external image '%s'.", pImg->m_aName);
571 ErrorHandler(aBuf);
572 }
573 }
574 else
575 {
576 pImg->m_Width = pItem->m_Width;
577 pImg->m_Height = pItem->m_Height;
578 pImg->m_Format = CImageInfo::FORMAT_RGBA;
579
580 // copy image data
581 void *pData = DataFile.GetData(Index: pItem->m_ImageData);
582 const size_t DataSize = pImg->DataSize();
583 pImg->m_pData = static_cast<uint8_t *>(malloc(size: DataSize));
584 mem_copy(dest: pImg->m_pData, source: pData, size: DataSize);
585 int TextureLoadFlag = m_pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
586 if(pImg->m_Width % 16 != 0 || pImg->m_Height % 16 != 0)
587 TextureLoadFlag = 0;
588 pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(Image: *pImg, Flags: TextureLoadFlag, pTexName: pImg->m_aName);
589 }
590
591 // load auto mapper file
592 pImg->m_AutoMapper.Load(pTileName: pImg->m_aName);
593
594 m_vpImages.push_back(x: pImg);
595
596 // unload image
597 DataFile.UnloadData(Index: pItem->m_ImageData);
598 DataFile.UnloadData(Index: pItem->m_ImageName);
599 }
600 }
601
602 // load sounds
603 {
604 int Start, Num;
605 DataFile.GetType(Type: MAPITEMTYPE_SOUND, pStart: &Start, pNum: &Num);
606 for(int i = 0; i < Num; i++)
607 {
608 CMapItemSound *pItem = (CMapItemSound *)DataFile.GetItem(Index: Start + i);
609
610 // copy base info
611 std::shared_ptr<CEditorSound> pSound = std::make_shared<CEditorSound>(args: this);
612
613 const char *pName = DataFile.GetDataString(Index: pItem->m_SoundName);
614 if(pName == nullptr || pName[0] == '\0')
615 {
616 char aBuf[128];
617 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read name of sound %d.", i);
618 ErrorHandler(aBuf);
619 }
620 else
621 str_copy(dst&: pSound->m_aName, src: pName);
622
623 if(pItem->m_External)
624 {
625 char aBuf[IO_MAX_PATH_LENGTH];
626 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "mapres/%s.opus", pSound->m_aName);
627
628 // load external
629 if(m_pEditor->Storage()->ReadFile(pFilename: aBuf, Type: IStorage::TYPE_ALL, ppResult: &pSound->m_pData, pResultLen: &pSound->m_DataSize))
630 {
631 pSound->m_SoundId = m_pEditor->Sound()->LoadOpusFromMem(pData: pSound->m_pData, DataSize: pSound->m_DataSize, ForceLoad: true, pContextName: pSound->m_aName);
632 }
633 else
634 {
635 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to load external sound '%s'.", pSound->m_aName);
636 ErrorHandler(aBuf);
637 }
638 }
639 else
640 {
641 pSound->m_DataSize = DataFile.GetDataSize(Index: pItem->m_SoundData);
642 void *pData = DataFile.GetData(Index: pItem->m_SoundData);
643 pSound->m_pData = malloc(size: pSound->m_DataSize);
644 mem_copy(dest: pSound->m_pData, source: pData, size: pSound->m_DataSize);
645 pSound->m_SoundId = m_pEditor->Sound()->LoadOpusFromMem(pData: pSound->m_pData, DataSize: pSound->m_DataSize, ForceLoad: true, pContextName: pSound->m_aName);
646 }
647
648 m_vpSounds.push_back(x: pSound);
649
650 // unload sound
651 DataFile.UnloadData(Index: pItem->m_SoundData);
652 DataFile.UnloadData(Index: pItem->m_SoundName);
653 }
654 }
655
656 // load groups
657 {
658 int LayersStart, LayersNum;
659 DataFile.GetType(Type: MAPITEMTYPE_LAYER, pStart: &LayersStart, pNum: &LayersNum);
660
661 int Start, Num;
662 DataFile.GetType(Type: MAPITEMTYPE_GROUP, pStart: &Start, pNum: &Num);
663
664 for(int g = 0; g < Num; g++)
665 {
666 CMapItemGroup *pGItem = (CMapItemGroup *)DataFile.GetItem(Index: Start + g);
667
668 if(pGItem->m_Version < 1 || pGItem->m_Version > 3)
669 continue;
670
671 std::shared_ptr<CLayerGroup> pGroup = NewGroup();
672 pGroup->m_ParallaxX = pGItem->m_ParallaxX;
673 pGroup->m_ParallaxY = pGItem->m_ParallaxY;
674 pGroup->m_OffsetX = pGItem->m_OffsetX;
675 pGroup->m_OffsetY = pGItem->m_OffsetY;
676
677 if(pGItem->m_Version >= 2)
678 {
679 pGroup->m_UseClipping = pGItem->m_UseClipping;
680 pGroup->m_ClipX = pGItem->m_ClipX;
681 pGroup->m_ClipY = pGItem->m_ClipY;
682 pGroup->m_ClipW = pGItem->m_ClipW;
683 pGroup->m_ClipH = pGItem->m_ClipH;
684 }
685
686 // load group name
687 if(pGItem->m_Version >= 3)
688 IntsToStr(pInts: pGItem->m_aName, NumInts: std::size(pGItem->m_aName), pStr: pGroup->m_aName, StrSize: std::size(pGroup->m_aName));
689
690 for(int l = 0; l < pGItem->m_NumLayers; l++)
691 {
692 CMapItemLayer *pLayerItem = (CMapItemLayer *)DataFile.GetItem(Index: LayersStart + pGItem->m_StartLayer + l);
693 if(!pLayerItem)
694 continue;
695
696 if(pLayerItem->m_Type == LAYERTYPE_TILES)
697 {
698 CMapItemLayerTilemap *pTilemapItem = (CMapItemLayerTilemap *)pLayerItem;
699
700 std::shared_ptr<CLayerTiles> pTiles;
701 if(pTilemapItem->m_Flags & TILESLAYERFLAG_GAME)
702 {
703 pTiles = std::make_shared<CLayerGame>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
704 MakeGameLayer(pLayer: pTiles);
705 MakeGameGroup(pGroup);
706 }
707 else if(pTilemapItem->m_Flags & TILESLAYERFLAG_TELE)
708 {
709 if(pTilemapItem->m_Version <= 2)
710 pTilemapItem->m_Tele = *((const int *)(pTilemapItem) + 15);
711
712 pTiles = std::make_shared<CLayerTele>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
713 MakeTeleLayer(pLayer: pTiles);
714 }
715 else if(pTilemapItem->m_Flags & TILESLAYERFLAG_SPEEDUP)
716 {
717 if(pTilemapItem->m_Version <= 2)
718 pTilemapItem->m_Speedup = *((const int *)(pTilemapItem) + 16);
719
720 pTiles = std::make_shared<CLayerSpeedup>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
721 MakeSpeedupLayer(pLayer: pTiles);
722 }
723 else if(pTilemapItem->m_Flags & TILESLAYERFLAG_FRONT)
724 {
725 if(pTilemapItem->m_Version <= 2)
726 pTilemapItem->m_Front = *((const int *)(pTilemapItem) + 17);
727
728 pTiles = std::make_shared<CLayerFront>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
729 MakeFrontLayer(pLayer: pTiles);
730 }
731 else if(pTilemapItem->m_Flags & TILESLAYERFLAG_SWITCH)
732 {
733 if(pTilemapItem->m_Version <= 2)
734 pTilemapItem->m_Switch = *((const int *)(pTilemapItem) + 18);
735
736 pTiles = std::make_shared<CLayerSwitch>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
737 MakeSwitchLayer(pLayer: pTiles);
738 }
739 else if(pTilemapItem->m_Flags & TILESLAYERFLAG_TUNE)
740 {
741 if(pTilemapItem->m_Version <= 2)
742 pTilemapItem->m_Tune = *((const int *)(pTilemapItem) + 19);
743
744 pTiles = std::make_shared<CLayerTune>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
745 MakeTuneLayer(pLayer: pTiles);
746 }
747 else
748 {
749 pTiles = std::make_shared<CLayerTiles>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
750 pTiles->m_Color = pTilemapItem->m_Color;
751 pTiles->m_ColorEnv = pTilemapItem->m_ColorEnv;
752 pTiles->m_ColorEnvOffset = pTilemapItem->m_ColorEnvOffset;
753 }
754
755 pTiles->m_Flags = pLayerItem->m_Flags;
756
757 pGroup->AddLayer(pLayer: pTiles);
758 pTiles->m_Image = pTilemapItem->m_Image;
759 pTiles->m_HasGame = pTilemapItem->m_Flags & TILESLAYERFLAG_GAME;
760
761 // validate image index
762 if(pTiles->m_Image < -1 || pTiles->m_Image >= (int)m_vpImages.size())
763 {
764 pTiles->m_Image = -1;
765 }
766
767 // load layer name
768 if(pTilemapItem->m_Version >= 3)
769 IntsToStr(pInts: pTilemapItem->m_aName, NumInts: std::size(pTilemapItem->m_aName), pStr: pTiles->m_aName, StrSize: std::size(pTiles->m_aName));
770
771 if(pTiles->m_HasTele)
772 {
773 void *pTeleData = DataFile.GetData(Index: pTilemapItem->m_Tele);
774 unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Tele);
775 if(Size >= (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTeleTile))
776 {
777 CTeleTile *pLayerTeleTiles = std::static_pointer_cast<CLayerTele>(r: pTiles)->m_pTeleTile;
778 mem_copy(dest: pLayerTeleTiles, source: pTeleData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTeleTile));
779
780 for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++)
781 {
782 if(IsValidTeleTile(Index: pLayerTeleTiles[i].m_Type))
783 pTiles->m_pTiles[i].m_Index = pLayerTeleTiles[i].m_Type;
784 else
785 pTiles->m_pTiles[i].m_Index = 0;
786 }
787 }
788 DataFile.UnloadData(Index: pTilemapItem->m_Tele);
789 }
790 else if(pTiles->m_HasSpeedup)
791 {
792 void *pSpeedupData = DataFile.GetData(Index: pTilemapItem->m_Speedup);
793 unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Speedup);
794
795 if(Size >= (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CSpeedupTile))
796 {
797 CSpeedupTile *pLayerSpeedupTiles = std::static_pointer_cast<CLayerSpeedup>(r: pTiles)->m_pSpeedupTile;
798 mem_copy(dest: pLayerSpeedupTiles, source: pSpeedupData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CSpeedupTile));
799
800 for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++)
801 {
802 if(IsValidSpeedupTile(Index: pLayerSpeedupTiles[i].m_Type) && pLayerSpeedupTiles[i].m_Force > 0)
803 pTiles->m_pTiles[i].m_Index = pLayerSpeedupTiles[i].m_Type;
804 else
805 pTiles->m_pTiles[i].m_Index = 0;
806 }
807 }
808
809 DataFile.UnloadData(Index: pTilemapItem->m_Speedup);
810 }
811 else if(pTiles->m_HasFront)
812 {
813 void *pFrontData = DataFile.GetData(Index: pTilemapItem->m_Front);
814 unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Front);
815 pTiles->ExtractTiles(TilemapItemVersion: pTilemapItem->m_Version, pSavedTiles: (CTile *)pFrontData, SavedTilesSize: Size);
816 DataFile.UnloadData(Index: pTilemapItem->m_Front);
817 }
818 else if(pTiles->m_HasSwitch)
819 {
820 void *pSwitchData = DataFile.GetData(Index: pTilemapItem->m_Switch);
821 unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Switch);
822 if(Size >= (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CSwitchTile))
823 {
824 CSwitchTile *pLayerSwitchTiles = std::static_pointer_cast<CLayerSwitch>(r: pTiles)->m_pSwitchTile;
825 mem_copy(dest: pLayerSwitchTiles, source: pSwitchData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CSwitchTile));
826
827 for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++)
828 {
829 if(((pLayerSwitchTiles[i].m_Type > (ENTITY_CRAZY_SHOTGUN + ENTITY_OFFSET) && pLayerSwitchTiles[i].m_Type < (ENTITY_DRAGGER_WEAK + ENTITY_OFFSET)) || pLayerSwitchTiles[i].m_Type == (ENTITY_LASER_O_FAST + 1 + ENTITY_OFFSET)))
830 continue;
831 else if(pLayerSwitchTiles[i].m_Type >= (ENTITY_ARMOR_1 + ENTITY_OFFSET) && pLayerSwitchTiles[i].m_Type <= (ENTITY_DOOR + ENTITY_OFFSET))
832 {
833 pTiles->m_pTiles[i].m_Index = pLayerSwitchTiles[i].m_Type;
834 pTiles->m_pTiles[i].m_Flags = pLayerSwitchTiles[i].m_Flags;
835 continue;
836 }
837
838 if(IsValidSwitchTile(Index: pLayerSwitchTiles[i].m_Type))
839 {
840 pTiles->m_pTiles[i].m_Index = pLayerSwitchTiles[i].m_Type;
841 pTiles->m_pTiles[i].m_Flags = pLayerSwitchTiles[i].m_Flags;
842 }
843 }
844 }
845 DataFile.UnloadData(Index: pTilemapItem->m_Switch);
846 }
847 else if(pTiles->m_HasTune)
848 {
849 void *pTuneData = DataFile.GetData(Index: pTilemapItem->m_Tune);
850 unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Tune);
851 if(Size >= (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTuneTile))
852 {
853 CTuneTile *pLayerTuneTiles = std::static_pointer_cast<CLayerTune>(r: pTiles)->m_pTuneTile;
854 mem_copy(dest: pLayerTuneTiles, source: pTuneData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTuneTile));
855
856 for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++)
857 {
858 if(IsValidTuneTile(Index: pLayerTuneTiles[i].m_Type))
859 pTiles->m_pTiles[i].m_Index = pLayerTuneTiles[i].m_Type;
860 else
861 pTiles->m_pTiles[i].m_Index = 0;
862 }
863 }
864 DataFile.UnloadData(Index: pTilemapItem->m_Tune);
865 }
866 else // regular tile layer or game layer
867 {
868 void *pData = DataFile.GetData(Index: pTilemapItem->m_Data);
869 unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Data);
870 pTiles->ExtractTiles(TilemapItemVersion: pTilemapItem->m_Version, pSavedTiles: (CTile *)pData, SavedTilesSize: Size);
871 DataFile.UnloadData(Index: pTilemapItem->m_Data);
872 }
873 }
874 else if(pLayerItem->m_Type == LAYERTYPE_QUADS)
875 {
876 const CMapItemLayerQuads *pQuadsItem = (CMapItemLayerQuads *)pLayerItem;
877
878 std::shared_ptr<CLayerQuads> pQuads = std::make_shared<CLayerQuads>(args: this);
879 pQuads->m_Flags = pLayerItem->m_Flags;
880 pQuads->m_Image = pQuadsItem->m_Image;
881
882 // validate image index
883 if(pQuads->m_Image < -1 || pQuads->m_Image >= (int)m_vpImages.size())
884 {
885 pQuads->m_Image = -1;
886 }
887
888 // load layer name
889 if(pQuadsItem->m_Version >= 2)
890 IntsToStr(pInts: pQuadsItem->m_aName, NumInts: std::size(pQuadsItem->m_aName), pStr: pQuads->m_aName, StrSize: std::size(pQuads->m_aName));
891
892 if(pQuadsItem->m_NumQuads > 0)
893 {
894 void *pData = DataFile.GetDataSwapped(Index: pQuadsItem->m_Data);
895 pQuads->m_vQuads.resize(new_size: pQuadsItem->m_NumQuads);
896 mem_copy(dest: pQuads->m_vQuads.data(), source: pData, size: sizeof(CQuad) * pQuadsItem->m_NumQuads);
897 DataFile.UnloadData(Index: pQuadsItem->m_Data);
898 }
899
900 pGroup->AddLayer(pLayer: pQuads);
901 }
902 else if(pLayerItem->m_Type == LAYERTYPE_SOUNDS)
903 {
904 const CMapItemLayerSounds *pSoundsItem = (CMapItemLayerSounds *)pLayerItem;
905 if(pSoundsItem->m_Version < 1 || pSoundsItem->m_Version > 2)
906 continue;
907
908 std::shared_ptr<CLayerSounds> pSounds = std::make_shared<CLayerSounds>(args: this);
909 pSounds->m_Flags = pLayerItem->m_Flags;
910 pSounds->m_Sound = pSoundsItem->m_Sound;
911
912 // validate sound index
913 if(pSounds->m_Sound < -1 || pSounds->m_Sound >= (int)m_vpSounds.size())
914 {
915 pSounds->m_Sound = -1;
916 }
917
918 // load layer name
919 IntsToStr(pInts: pSoundsItem->m_aName, NumInts: std::size(pSoundsItem->m_aName), pStr: pSounds->m_aName, StrSize: std::size(pSounds->m_aName));
920
921 // load data
922 if(pSoundsItem->m_NumSources > 0)
923 {
924 void *pData = DataFile.GetDataSwapped(Index: pSoundsItem->m_Data);
925 pSounds->m_vSources.resize(new_size: pSoundsItem->m_NumSources);
926 mem_copy(dest: pSounds->m_vSources.data(), source: pData, size: sizeof(CSoundSource) * pSoundsItem->m_NumSources);
927 DataFile.UnloadData(Index: pSoundsItem->m_Data);
928 }
929
930 pGroup->AddLayer(pLayer: pSounds);
931 }
932 else if(pLayerItem->m_Type == LAYERTYPE_SOUNDS_DEPRECATED)
933 {
934 // compatibility with old sound layers
935 const CMapItemLayerSounds *pSoundsItem = (CMapItemLayerSounds *)pLayerItem;
936 if(pSoundsItem->m_Version < 1 || pSoundsItem->m_Version > 2)
937 continue;
938
939 std::shared_ptr<CLayerSounds> pSounds = std::make_shared<CLayerSounds>(args: this);
940 pSounds->m_Flags = pLayerItem->m_Flags;
941 pSounds->m_Sound = pSoundsItem->m_Sound;
942
943 // validate sound index
944 if(pSounds->m_Sound < -1 || pSounds->m_Sound >= (int)m_vpSounds.size())
945 {
946 pSounds->m_Sound = -1;
947 }
948
949 // load layer name
950 IntsToStr(pInts: pSoundsItem->m_aName, NumInts: std::size(pSoundsItem->m_aName), pStr: pSounds->m_aName, StrSize: std::size(pSounds->m_aName));
951
952 // load data
953 CSoundSourceDeprecated *pData = (CSoundSourceDeprecated *)DataFile.GetDataSwapped(Index: pSoundsItem->m_Data);
954 pGroup->AddLayer(pLayer: pSounds);
955 pSounds->m_vSources.resize(new_size: pSoundsItem->m_NumSources);
956
957 for(int i = 0; i < pSoundsItem->m_NumSources; i++)
958 {
959 CSoundSourceDeprecated *pOldSource = &pData[i];
960
961 CSoundSource &Source = pSounds->m_vSources[i];
962 Source.m_Position = pOldSource->m_Position;
963 Source.m_Loop = pOldSource->m_Loop;
964 Source.m_Pan = true;
965 Source.m_TimeDelay = pOldSource->m_TimeDelay;
966 Source.m_Falloff = 0;
967
968 Source.m_PosEnv = pOldSource->m_PosEnv;
969 Source.m_PosEnvOffset = pOldSource->m_PosEnvOffset;
970 Source.m_SoundEnv = pOldSource->m_SoundEnv;
971 Source.m_SoundEnvOffset = pOldSource->m_SoundEnvOffset;
972
973 Source.m_Shape.m_Type = CSoundShape::SHAPE_CIRCLE;
974 Source.m_Shape.m_Circle.m_Radius = pOldSource->m_FalloffDistance;
975 }
976
977 DataFile.UnloadData(Index: pSoundsItem->m_Data);
978 }
979 }
980 }
981 }
982
983 // load envelopes
984 {
985 const CMapBasedEnvelopePointAccess EnvelopePoints(&DataFile);
986
987 int EnvelopeStart, EnvelopeNum;
988 DataFile.GetType(Type: MAPITEMTYPE_ENVELOPE, pStart: &EnvelopeStart, pNum: &EnvelopeNum);
989 for(int EnvelopeIndex = 0; EnvelopeIndex < EnvelopeNum; EnvelopeIndex++)
990 {
991 CMapItemEnvelope *pItem = (CMapItemEnvelope *)DataFile.GetItem(Index: EnvelopeStart + EnvelopeIndex);
992 int Channels = pItem->m_Channels;
993 if(Channels <= 0 || Channels == 2 || Channels > CEnvPoint::MAX_CHANNELS)
994 {
995 // Fall back to showing all channels if the number of channels is unsupported
996 Channels = CEnvPoint::MAX_CHANNELS;
997 }
998 if(Channels != pItem->m_Channels)
999 {
1000 char aBuf[128];
1001 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Envelope %d had an invalid number of channels, %d, which was changed to %d.", EnvelopeIndex, pItem->m_Channels, Channels);
1002 ErrorHandler(aBuf);
1003 }
1004
1005 std::shared_ptr<CEnvelope> pEnvelope = std::make_shared<CEnvelope>(args&: Channels);
1006 pEnvelope->m_vPoints.resize(new_size: pItem->m_NumPoints);
1007 for(int PointIndex = 0; PointIndex < pItem->m_NumPoints; PointIndex++)
1008 {
1009 const CEnvPoint *pPoint = EnvelopePoints.GetPoint(Index: pItem->m_StartPoint + PointIndex);
1010 if(pPoint != nullptr)
1011 mem_copy(dest: &pEnvelope->m_vPoints[PointIndex], source: pPoint, size: sizeof(CEnvPoint));
1012 const CEnvPointBezier *pPointBezier = EnvelopePoints.GetBezier(Index: pItem->m_StartPoint + PointIndex);
1013 if(pPointBezier != nullptr)
1014 mem_copy(dest: &pEnvelope->m_vPoints[PointIndex].m_Bezier, source: pPointBezier, size: sizeof(CEnvPointBezier));
1015 }
1016 if(pItem->m_aName[0] != -1) // compatibility with old maps
1017 IntsToStr(pInts: pItem->m_aName, NumInts: std::size(pItem->m_aName), pStr: pEnvelope->m_aName, StrSize: std::size(pEnvelope->m_aName));
1018 m_vpEnvelopes.push_back(x: pEnvelope);
1019 if(pItem->m_Version >= 2)
1020 pEnvelope->m_Synchronized = pItem->m_Synchronized;
1021 }
1022 }
1023
1024 // load automapper configurations
1025 {
1026 int AutomapperConfigStart, AutomapperConfigNum;
1027 DataFile.GetType(Type: MAPITEMTYPE_AUTOMAPPER_CONFIG, pStart: &AutomapperConfigStart, pNum: &AutomapperConfigNum);
1028 for(int i = 0; i < AutomapperConfigNum; i++)
1029 {
1030 CMapItemAutoMapperConfig *pItem = (CMapItemAutoMapperConfig *)DataFile.GetItem(Index: AutomapperConfigStart + i);
1031 if(pItem->m_Version == 1)
1032 {
1033 if(pItem->m_GroupId >= 0 && (size_t)pItem->m_GroupId < m_vpGroups.size() &&
1034 pItem->m_LayerId >= 0 && (size_t)pItem->m_LayerId < m_vpGroups[pItem->m_GroupId]->m_vpLayers.size())
1035 {
1036 std::shared_ptr<CLayer> pLayer = m_vpGroups[pItem->m_GroupId]->m_vpLayers[pItem->m_LayerId];
1037 if(pLayer->m_Type == LAYERTYPE_TILES)
1038 {
1039 std::shared_ptr<CLayerTiles> pTiles = std::static_pointer_cast<CLayerTiles>(r: m_vpGroups[pItem->m_GroupId]->m_vpLayers[pItem->m_LayerId]);
1040 // only load auto mappers for tile layers (not physics layers)
1041 if(!(pTiles->m_HasGame || pTiles->m_HasTele || pTiles->m_HasSpeedup ||
1042 pTiles->m_HasFront || pTiles->m_HasSwitch || pTiles->m_HasTune))
1043 {
1044 pTiles->m_AutoMapperConfig = pItem->m_AutomapperConfig;
1045 pTiles->m_Seed = pItem->m_AutomapperSeed;
1046 pTiles->m_AutoAutoMap = !!(pItem->m_Flags & CMapItemAutoMapperConfig::FLAG_AUTOMATIC);
1047 }
1048 }
1049 }
1050 }
1051 }
1052 }
1053
1054 str_copy(dst&: m_aFilename, src: pFilename);
1055
1056 CheckIntegrity();
1057 PerformSanityChecks(ErrorHandler);
1058
1059 SortImages();
1060 SelectGameLayer();
1061
1062 ResetModifiedState();
1063 return true;
1064}
1065
1066void CEditorMap::PerformSanityChecks(const FErrorHandler &ErrorHandler)
1067{
1068 // Check if there are any images with a width or height that is not divisible by 16 which are
1069 // used in tile layers. Reset the image for these layers, to prevent crashes with some drivers.
1070 size_t ImageIndex = 0;
1071 for(const std::shared_ptr<CEditorImage> &pImage : m_vpImages)
1072 {
1073 if(pImage->m_Width % 16 != 0 || pImage->m_Height % 16 != 0)
1074 {
1075 size_t GroupIndex = 0;
1076 for(const std::shared_ptr<CLayerGroup> &pGroup : m_vpGroups)
1077 {
1078 size_t LayerIndex = 0;
1079 for(const std::shared_ptr<CLayer> &pLayer : pGroup->m_vpLayers)
1080 {
1081 if(pLayer->m_Type == LAYERTYPE_TILES)
1082 {
1083 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: pLayer);
1084 if(pLayerTiles->m_Image >= 0 && (size_t)pLayerTiles->m_Image == ImageIndex)
1085 {
1086 pLayerTiles->m_Image = -1;
1087 char aBuf[IO_MAX_PATH_LENGTH + 128];
1088 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: The image '%s' (size %" PRIzu "x%" PRIzu ") has a width or height that is not divisible by 16 and therefore cannot be used for tile layers. The image of layer #%" PRIzu " '%s' in group #%" PRIzu " '%s' has been unset.", pImage->m_aName, pImage->m_Width, pImage->m_Height, LayerIndex, pLayer->m_aName, GroupIndex, pGroup->m_aName);
1089 ErrorHandler(aBuf);
1090 }
1091 }
1092 ++LayerIndex;
1093 }
1094 ++GroupIndex;
1095 }
1096 }
1097 ++ImageIndex;
1098 }
1099}
1100
1101bool CEditorMap::PerformAutosave(const std::function<void(const char *pErrorMessage)> &ErrorHandler)
1102{
1103 char aDate[20];
1104 char aAutosavePath[IO_MAX_PATH_LENGTH];
1105 str_timestamp(buffer: aDate, buffer_size: sizeof(aDate));
1106 char aFilenameNoExt[IO_MAX_PATH_LENGTH];
1107 if(m_aFilename[0] == '\0')
1108 {
1109 str_copy(dst&: aFilenameNoExt, src: "unnamed");
1110 }
1111 else
1112 {
1113 const char *pFilename = fs_filename(path: m_aFilename);
1114 str_truncate(dst: aFilenameNoExt, dst_size: sizeof(aFilenameNoExt), src: pFilename, truncation_len: str_length(str: pFilename) - str_length(str: ".map"));
1115 }
1116 str_format(buffer: aAutosavePath, buffer_size: sizeof(aAutosavePath), format: "maps/auto/%s_%s.map", aFilenameNoExt, aDate);
1117
1118 m_LastSaveTime = Editor()->Client()->GlobalTime();
1119 if(Save(pFilename: aAutosavePath, ErrorHandler))
1120 {
1121 m_ModifiedAuto = false;
1122 // Clean up autosaves
1123 if(g_Config.m_EdAutosaveMax)
1124 {
1125 CFileCollection AutosavedMaps;
1126 AutosavedMaps.Init(pStorage: Editor()->Storage(), pPath: "maps/auto", pFileDesc: aFilenameNoExt, pFileExt: ".map", MaxEntries: g_Config.m_EdAutosaveMax);
1127 }
1128 return true;
1129 }
1130 else
1131 {
1132 char aErrorMessage[IO_MAX_PATH_LENGTH + 128];
1133 str_format(buffer: aErrorMessage, buffer_size: sizeof(aErrorMessage), format: "Failed to automatically save map to file '%s'.", aAutosavePath);
1134 ErrorHandler(aErrorMessage);
1135 return false;
1136 }
1137}
1138