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