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