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