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