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