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: std::max(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_AutoAutomapper)
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: std::max(a: PointCount, b: 1), size: sizeof(CEnvPoint));
393 CEnvPointBezier *pPointsBezier = nullptr;
394 if(BezierUsed)
395 pPointsBezier = (CEnvPointBezier *)calloc(nmemb: std::max(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 // load map info
477 {
478 int Start, Num;
479 pMap->GetType(Type: MAPITEMTYPE_INFO, pStart: &Start, pNum: &Num);
480 for(int i = Start; i < Start + Num; i++)
481 {
482 int ItemSize = pMap->GetItemSize(Index: i);
483 int ItemId;
484 CMapItemInfoSettings *pItem = (CMapItemInfoSettings *)pMap->GetItem(Index: i, pType: nullptr, pId: &ItemId);
485 if(!pItem || ItemId != 0)
486 continue;
487
488 const auto &&ReadStringInfo = [&](int Index, char *pBuffer, size_t BufferSize, const char *pErrorContext) {
489 const char *pStr = pMap->GetDataString(Index);
490 if(pStr == nullptr)
491 {
492 char aBuf[128];
493 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read %s from map info.", pErrorContext);
494 ErrorHandler(aBuf);
495 pBuffer[0] = '\0';
496 }
497 else
498 {
499 str_copy(dst: pBuffer, src: pStr, dst_size: BufferSize);
500 }
501 };
502
503 ReadStringInfo(pItem->m_Author, m_MapInfo.m_aAuthor, sizeof(m_MapInfo.m_aAuthor), "author");
504 ReadStringInfo(pItem->m_MapVersion, m_MapInfo.m_aVersion, sizeof(m_MapInfo.m_aVersion), "version");
505 ReadStringInfo(pItem->m_Credits, m_MapInfo.m_aCredits, sizeof(m_MapInfo.m_aCredits), "credits");
506 ReadStringInfo(pItem->m_License, m_MapInfo.m_aLicense, sizeof(m_MapInfo.m_aLicense), "license");
507
508 if(pItem->m_Version != 1 || ItemSize < (int)sizeof(CMapItemInfoSettings))
509 break;
510
511 if(!(pItem->m_Settings > -1))
512 break;
513
514 const unsigned Size = pMap->GetDataSize(Index: pItem->m_Settings);
515 char *pSettings = (char *)pMap->GetData(Index: pItem->m_Settings);
516 char *pNext = pSettings;
517 while(pNext < pSettings + Size)
518 {
519 int StrSize = str_length(str: pNext) + 1;
520 m_vSettings.emplace_back(args&: pNext);
521 pNext += StrSize;
522 }
523 }
524 }
525
526 // load images
527 {
528 int Start, Num;
529 pMap->GetType(Type: MAPITEMTYPE_IMAGE, pStart: &Start, pNum: &Num);
530 for(int i = 0; i < Num; i++)
531 {
532 CMapItemImage_v2 *pItem = (CMapItemImage_v2 *)pMap->GetItem(Index: Start + i);
533
534 // copy base info
535 std::shared_ptr<CEditorImage> pImg = std::make_shared<CEditorImage>(args: this);
536 pImg->m_External = pItem->m_External;
537
538 const char *pName = pMap->GetDataString(Index: pItem->m_ImageName);
539 if(pName == nullptr || pName[0] == '\0')
540 {
541 char aBuf[128];
542 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read name of image %d.", i);
543 ErrorHandler(aBuf);
544 }
545 else
546 str_copy(dst&: pImg->m_aName, src: pName);
547
548 if(pItem->m_Version > 1 && pItem->m_MustBe1 != 1)
549 {
550 char aBuf[128];
551 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Unsupported image type of image %d '%s'.", i, pImg->m_aName);
552 ErrorHandler(aBuf);
553 }
554
555 if(pImg->m_External || (pItem->m_Version > 1 && pItem->m_MustBe1 != 1))
556 {
557 char aBuf[IO_MAX_PATH_LENGTH];
558 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "mapres/%s.png", pImg->m_aName);
559
560 // load external
561 if(m_pEditor->Graphics()->LoadPng(Image&: *pImg, pFilename: aBuf, StorageType: IStorage::TYPE_ALL))
562 {
563 ConvertToRgba(Image&: *pImg);
564
565 int TextureLoadFlag = m_pEditor->Graphics()->TextureLoadFlags();
566 if(pImg->m_Width % 16 != 0 || pImg->m_Height % 16 != 0)
567 TextureLoadFlag = 0;
568 pImg->m_External = 1;
569 pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(Image: *pImg, Flags: TextureLoadFlag, pTexName: aBuf);
570 }
571 else
572 {
573 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to load external image '%s'.", pImg->m_aName);
574 ErrorHandler(aBuf);
575 }
576 }
577 else
578 {
579 pImg->m_Width = pItem->m_Width;
580 pImg->m_Height = pItem->m_Height;
581 pImg->m_Format = CImageInfo::FORMAT_RGBA;
582
583 const void *pData = pMap->GetData(Index: pItem->m_ImageData);
584 if(pItem->m_Width <= 0 || pItem->m_Height <= 0 || pData == nullptr || (size_t)pMap->GetDataSize(Index: pItem->m_ImageData) < pImg->DataSize())
585 {
586 pImg->m_Width = 0;
587 pImg->m_Height = 0;
588 char aBuf[128];
589 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to load data of image %d '%s'.", i, pImg->m_aName);
590 ErrorHandler(aBuf);
591 }
592 else
593 {
594 pImg->Allocate();
595
596 // copy image data
597 mem_copy(dest: pImg->m_pData, source: pData, size: pImg->DataSize());
598 int TextureLoadFlag = m_pEditor->Graphics()->TextureLoadFlags();
599 if(pImg->m_Width % 16 != 0 || pImg->m_Height % 16 != 0)
600 TextureLoadFlag = 0;
601 pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(Image: *pImg, Flags: TextureLoadFlag, pTexName: pImg->m_aName);
602 }
603 }
604
605 // load auto mapper file
606 pImg->m_Automapper.Load(pTileName: pImg->m_aName);
607
608 m_vpImages.push_back(x: pImg);
609
610 // unload image
611 pMap->UnloadData(Index: pItem->m_ImageData);
612 pMap->UnloadData(Index: pItem->m_ImageName);
613 }
614 }
615
616 // load sounds
617 {
618 int Start, Num;
619 pMap->GetType(Type: MAPITEMTYPE_SOUND, pStart: &Start, pNum: &Num);
620 for(int i = 0; i < Num; i++)
621 {
622 CMapItemSound *pItem = (CMapItemSound *)pMap->GetItem(Index: Start + i);
623
624 // copy base info
625 std::shared_ptr<CEditorSound> pSound = std::make_shared<CEditorSound>(args: this);
626
627 const char *pName = pMap->GetDataString(Index: pItem->m_SoundName);
628 if(pName == nullptr || pName[0] == '\0')
629 {
630 char aBuf[128];
631 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read name of sound %d.", i);
632 ErrorHandler(aBuf);
633 }
634 else
635 str_copy(dst&: pSound->m_aName, src: pName);
636
637 if(pItem->m_External)
638 {
639 char aBuf[IO_MAX_PATH_LENGTH];
640 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "mapres/%s.opus", pSound->m_aName);
641
642 // load external
643 if(m_pEditor->Storage()->ReadFile(pFilename: aBuf, Type: IStorage::TYPE_ALL, ppResult: &pSound->m_pData, pResultLen: &pSound->m_DataSize))
644 {
645 pSound->m_SoundId = m_pEditor->Sound()->LoadOpusFromMem(pData: pSound->m_pData, DataSize: pSound->m_DataSize, ForceLoad: true, pContextName: pSound->m_aName);
646 }
647 else
648 {
649 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to load external sound '%s'.", pSound->m_aName);
650 ErrorHandler(aBuf);
651 }
652 }
653 else
654 {
655 pSound->m_DataSize = pMap->GetDataSize(Index: pItem->m_SoundData);
656 void *pData = pMap->GetData(Index: pItem->m_SoundData);
657 pSound->m_pData = malloc(size: pSound->m_DataSize);
658 mem_copy(dest: pSound->m_pData, source: pData, size: pSound->m_DataSize);
659 pSound->m_SoundId = m_pEditor->Sound()->LoadOpusFromMem(pData: pSound->m_pData, DataSize: pSound->m_DataSize, ForceLoad: true, pContextName: pSound->m_aName);
660 }
661
662 m_vpSounds.push_back(x: pSound);
663
664 // unload sound
665 pMap->UnloadData(Index: pItem->m_SoundData);
666 pMap->UnloadData(Index: pItem->m_SoundName);
667 }
668 }
669
670 // load groups
671 {
672 int LayersStart, LayersNum;
673 pMap->GetType(Type: MAPITEMTYPE_LAYER, pStart: &LayersStart, pNum: &LayersNum);
674
675 int Start, Num;
676 pMap->GetType(Type: MAPITEMTYPE_GROUP, pStart: &Start, pNum: &Num);
677
678 for(int g = 0; g < Num; g++)
679 {
680 CMapItemGroup *pGItem = (CMapItemGroup *)pMap->GetItem(Index: Start + g);
681
682 if(pGItem->m_Version < 1 || pGItem->m_Version > 3)
683 continue;
684
685 std::shared_ptr<CLayerGroup> pGroup = NewGroup();
686 pGroup->m_ParallaxX = pGItem->m_ParallaxX;
687 pGroup->m_ParallaxY = pGItem->m_ParallaxY;
688 pGroup->m_OffsetX = pGItem->m_OffsetX;
689 pGroup->m_OffsetY = pGItem->m_OffsetY;
690
691 if(pGItem->m_Version >= 2)
692 {
693 pGroup->m_UseClipping = pGItem->m_UseClipping;
694 pGroup->m_ClipX = pGItem->m_ClipX;
695 pGroup->m_ClipY = pGItem->m_ClipY;
696 pGroup->m_ClipW = pGItem->m_ClipW;
697 pGroup->m_ClipH = pGItem->m_ClipH;
698 }
699
700 // load group name
701 if(pGItem->m_Version >= 3)
702 IntsToStr(pInts: pGItem->m_aName, NumInts: std::size(pGItem->m_aName), pStr: pGroup->m_aName, StrSize: std::size(pGroup->m_aName));
703
704 for(int l = 0; l < pGItem->m_NumLayers; l++)
705 {
706 CMapItemLayer *pLayerItem = (CMapItemLayer *)pMap->GetItem(Index: LayersStart + pGItem->m_StartLayer + l);
707 if(!pLayerItem)
708 continue;
709
710 if(pLayerItem->m_Type == LAYERTYPE_TILES)
711 {
712 CMapItemLayerTilemap *pTilemapItem = (CMapItemLayerTilemap *)pLayerItem;
713
714 std::shared_ptr<CLayerTiles> pTiles;
715 if(pTilemapItem->m_Flags & TILESLAYERFLAG_GAME)
716 {
717 pTiles = std::make_shared<CLayerGame>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
718 MakeGameLayer(pLayer: pTiles);
719 MakeGameGroup(pGroup);
720 }
721 else if(pTilemapItem->m_Flags & TILESLAYERFLAG_TELE)
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 pTiles = std::make_shared<CLayerSpeedup>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
729 MakeSpeedupLayer(pLayer: pTiles);
730 }
731 else if(pTilemapItem->m_Flags & TILESLAYERFLAG_FRONT)
732 {
733 pTiles = std::make_shared<CLayerFront>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
734 MakeFrontLayer(pLayer: pTiles);
735 }
736 else if(pTilemapItem->m_Flags & TILESLAYERFLAG_SWITCH)
737 {
738 pTiles = std::make_shared<CLayerSwitch>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
739 MakeSwitchLayer(pLayer: pTiles);
740 }
741 else if(pTilemapItem->m_Flags & TILESLAYERFLAG_TUNE)
742 {
743 pTiles = std::make_shared<CLayerTune>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
744 MakeTuneLayer(pLayer: pTiles);
745 }
746 else
747 {
748 pTiles = std::make_shared<CLayerTiles>(args: this, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height);
749 pTiles->m_Color = pTilemapItem->m_Color;
750 pTiles->m_ColorEnv = pTilemapItem->m_ColorEnv;
751 pTiles->m_ColorEnvOffset = pTilemapItem->m_ColorEnvOffset;
752 }
753
754 pTiles->m_Flags = pLayerItem->m_Flags;
755
756 pGroup->AddLayer(pLayer: pTiles);
757 pTiles->m_Image = pTilemapItem->m_Image;
758
759 // validate image index
760 if(pTiles->m_Image < -1 || pTiles->m_Image >= (int)m_vpImages.size())
761 {
762 pTiles->m_Image = -1;
763 }
764
765 // load layer name
766 IntsToStr(pInts: pTilemapItem->m_aName, NumInts: std::size(pTilemapItem->m_aName), pStr: pTiles->m_aName, StrSize: std::size(pTiles->m_aName));
767
768 if(pTiles->m_HasTele)
769 {
770 const void *pData = pMap->GetData(Index: pTilemapItem->m_Tele);
771 if(pData != nullptr)
772 {
773 CTeleTile *pLayerTeleTiles = std::static_pointer_cast<CLayerTele>(r: pTiles)->m_pTeleTile;
774 mem_copy(dest: pLayerTeleTiles, source: pData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTeleTile));
775 for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++)
776 {
777 if(IsValidTeleTile(Index: pLayerTeleTiles[i].m_Type))
778 pTiles->m_pTiles[i].m_Index = pLayerTeleTiles[i].m_Type;
779 else
780 pTiles->m_pTiles[i].m_Index = 0;
781 }
782 }
783 pMap->UnloadData(Index: pTilemapItem->m_Tele);
784 }
785 else if(pTiles->m_HasSpeedup)
786 {
787 const void *pData = pMap->GetData(Index: pTilemapItem->m_Speedup);
788 if(pData != nullptr)
789 {
790 CSpeedupTile *pLayerSpeedupTiles = std::static_pointer_cast<CLayerSpeedup>(r: pTiles)->m_pSpeedupTile;
791 mem_copy(dest: pLayerSpeedupTiles, source: pData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CSpeedupTile));
792 for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++)
793 {
794 if(IsValidSpeedupTile(Index: pLayerSpeedupTiles[i].m_Type) && pLayerSpeedupTiles[i].m_Force > 0)
795 pTiles->m_pTiles[i].m_Index = pLayerSpeedupTiles[i].m_Type;
796 else
797 pTiles->m_pTiles[i].m_Index = 0;
798 }
799 }
800 pMap->UnloadData(Index: pTilemapItem->m_Speedup);
801 }
802 else if(pTiles->m_HasFront)
803 {
804 const void *pData = pMap->GetData(Index: pTilemapItem->m_Front);
805 if(pData != nullptr)
806 {
807 mem_copy(dest: pTiles->m_pTiles, source: pData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTile));
808 }
809 pMap->UnloadData(Index: pTilemapItem->m_Front);
810 }
811 else if(pTiles->m_HasSwitch)
812 {
813 const void *pData = pMap->GetData(Index: pTilemapItem->m_Switch);
814 if(pData != nullptr)
815 {
816 CSwitchTile *pLayerSwitchTiles = std::static_pointer_cast<CLayerSwitch>(r: pTiles)->m_pSwitchTile;
817 mem_copy(dest: pLayerSwitchTiles, source: pData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CSwitchTile));
818 for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++)
819 {
820 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)))
821 continue;
822 else if(pLayerSwitchTiles[i].m_Type >= (ENTITY_ARMOR_1 + ENTITY_OFFSET) && pLayerSwitchTiles[i].m_Type <= (ENTITY_DOOR + ENTITY_OFFSET))
823 {
824 pTiles->m_pTiles[i].m_Index = pLayerSwitchTiles[i].m_Type;
825 pTiles->m_pTiles[i].m_Flags = pLayerSwitchTiles[i].m_Flags;
826 continue;
827 }
828
829 if(IsValidSwitchTile(Index: pLayerSwitchTiles[i].m_Type))
830 {
831 pTiles->m_pTiles[i].m_Index = pLayerSwitchTiles[i].m_Type;
832 pTiles->m_pTiles[i].m_Flags = pLayerSwitchTiles[i].m_Flags;
833 }
834 }
835 pMap->UnloadData(Index: pTilemapItem->m_Switch);
836 }
837 }
838 else if(pTiles->m_HasTune)
839 {
840 const void *pData = pMap->GetData(Index: pTilemapItem->m_Tune);
841 if(pData != nullptr)
842 {
843 CTuneTile *pLayerTuneTiles = std::static_pointer_cast<CLayerTune>(r: pTiles)->m_pTuneTile;
844 mem_copy(dest: pLayerTuneTiles, source: pData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTuneTile));
845 for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++)
846 {
847 if(IsValidTuneTile(Index: pLayerTuneTiles[i].m_Type))
848 pTiles->m_pTiles[i].m_Index = pLayerTuneTiles[i].m_Type;
849 else
850 pTiles->m_pTiles[i].m_Index = 0;
851 }
852 pMap->UnloadData(Index: pTilemapItem->m_Tune);
853 }
854 }
855 else // regular tile layer or game layer
856 {
857 const void *pData = pMap->GetData(Index: pTilemapItem->m_Data);
858 if(pData != nullptr)
859 {
860 mem_copy(dest: pTiles->m_pTiles, source: pData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTile));
861 }
862 pMap->UnloadData(Index: pTilemapItem->m_Data);
863 }
864 }
865 else if(pLayerItem->m_Type == LAYERTYPE_QUADS)
866 {
867 const CMapItemLayerQuads *pQuadsItem = (CMapItemLayerQuads *)pLayerItem;
868
869 std::shared_ptr<CLayerQuads> pQuads = std::make_shared<CLayerQuads>(args: this);
870 pQuads->m_Flags = pLayerItem->m_Flags;
871 pQuads->m_Image = pQuadsItem->m_Image;
872
873 // validate image index
874 if(pQuads->m_Image < -1 || pQuads->m_Image >= (int)m_vpImages.size())
875 {
876 pQuads->m_Image = -1;
877 }
878
879 // load layer name
880 if(pQuadsItem->m_Version >= 2)
881 IntsToStr(pInts: pQuadsItem->m_aName, NumInts: std::size(pQuadsItem->m_aName), pStr: pQuads->m_aName, StrSize: std::size(pQuads->m_aName));
882
883 if(pQuadsItem->m_NumQuads > 0)
884 {
885 const void *pData = pMap->GetDataSwapped(Index: pQuadsItem->m_Data);
886 if(pData != nullptr && (size_t)pMap->GetDataSize(Index: pQuadsItem->m_Data) >= sizeof(CQuad) * (size_t)pQuadsItem->m_NumQuads)
887 {
888 pQuads->m_vQuads.resize(sz: pQuadsItem->m_NumQuads);
889 mem_copy(dest: pQuads->m_vQuads.data(), source: pData, size: sizeof(CQuad) * pQuadsItem->m_NumQuads);
890 }
891 else
892 {
893 char aBuf[128];
894 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read quads of layer %d.", l);
895 ErrorHandler(aBuf);
896 }
897 pMap->UnloadData(Index: pQuadsItem->m_Data);
898 }
899
900 pGroup->AddLayer(pLayer: pQuads);
901 }
902 else if(pLayerItem->m_Type == LAYERTYPE_SOUNDS)
903 {
904 const CMapItemLayerSounds *pSoundsItem = (CMapItemLayerSounds *)pLayerItem;
905 if(pSoundsItem->m_Version < 1 || pSoundsItem->m_Version > 2)
906 continue;
907
908 std::shared_ptr<CLayerSounds> pSounds = std::make_shared<CLayerSounds>(args: this);
909 pSounds->m_Flags = pLayerItem->m_Flags;
910 pSounds->m_Sound = pSoundsItem->m_Sound;
911
912 // validate sound index
913 if(pSounds->m_Sound < -1 || pSounds->m_Sound >= (int)m_vpSounds.size())
914 {
915 pSounds->m_Sound = -1;
916 }
917
918 // load layer name
919 IntsToStr(pInts: pSoundsItem->m_aName, NumInts: std::size(pSoundsItem->m_aName), pStr: pSounds->m_aName, StrSize: std::size(pSounds->m_aName));
920
921 // load data
922 if(pSoundsItem->m_NumSources > 0)
923 {
924 const void *pData = pMap->GetDataSwapped(Index: pSoundsItem->m_Data);
925 if(pData != nullptr && (size_t)pMap->GetDataSize(Index: pSoundsItem->m_Data) >= sizeof(CSoundSource) * (size_t)pSoundsItem->m_NumSources)
926 {
927 pSounds->m_vSources.resize(sz: pSoundsItem->m_NumSources);
928 mem_copy(dest: pSounds->m_vSources.data(), source: pData, size: sizeof(CSoundSource) * pSoundsItem->m_NumSources);
929 }
930 else
931 {
932 char aBuf[128];
933 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read sound sources of layer %d.", l);
934 ErrorHandler(aBuf);
935 }
936 pMap->UnloadData(Index: pSoundsItem->m_Data);
937 }
938
939 pGroup->AddLayer(pLayer: pSounds);
940 }
941 else if(pLayerItem->m_Type == LAYERTYPE_SOUNDS_DEPRECATED)
942 {
943 // compatibility with old sound layers
944 const CMapItemLayerSounds *pSoundsItem = (CMapItemLayerSounds *)pLayerItem;
945 if(pSoundsItem->m_Version < 1 || pSoundsItem->m_Version > 2)
946 continue;
947
948 std::shared_ptr<CLayerSounds> pSounds = std::make_shared<CLayerSounds>(args: this);
949 pSounds->m_Flags = pLayerItem->m_Flags;
950 pSounds->m_Sound = pSoundsItem->m_Sound;
951
952 // validate sound index
953 if(pSounds->m_Sound < -1 || pSounds->m_Sound >= (int)m_vpSounds.size())
954 {
955 pSounds->m_Sound = -1;
956 }
957
958 // load layer name
959 IntsToStr(pInts: pSoundsItem->m_aName, NumInts: std::size(pSoundsItem->m_aName), pStr: pSounds->m_aName, StrSize: std::size(pSounds->m_aName));
960
961 pGroup->AddLayer(pLayer: pSounds);
962
963 // load data
964 if(pSoundsItem->m_NumSources > 0)
965 {
966 const CSoundSourceDeprecated *pData = (const CSoundSourceDeprecated *)pMap->GetDataSwapped(Index: pSoundsItem->m_Data);
967 if(pData == nullptr || (size_t)pMap->GetDataSize(Index: pSoundsItem->m_Data) < sizeof(CSoundSourceDeprecated) * (size_t)pSoundsItem->m_NumSources)
968 {
969 char aBuf[128];
970 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read sound sources of layer %d.", l);
971 ErrorHandler(aBuf);
972 }
973 else
974 {
975 pSounds->m_vSources.resize(sz: pSoundsItem->m_NumSources);
976
977 for(int i = 0; i < pSoundsItem->m_NumSources; i++)
978 {
979 const CSoundSourceDeprecated *pOldSource = &pData[i];
980
981 CSoundSource &Source = pSounds->m_vSources[i];
982 Source.m_Position = pOldSource->m_Position;
983 Source.m_Loop = pOldSource->m_Loop;
984 Source.m_Pan = true;
985 Source.m_TimeDelay = pOldSource->m_TimeDelay;
986 Source.m_Falloff = 0;
987
988 Source.m_PosEnv = pOldSource->m_PosEnv;
989 Source.m_PosEnvOffset = pOldSource->m_PosEnvOffset;
990 Source.m_SoundEnv = pOldSource->m_SoundEnv;
991 Source.m_SoundEnvOffset = pOldSource->m_SoundEnvOffset;
992
993 Source.m_Shape.m_Type = CSoundShape::SHAPE_CIRCLE;
994 Source.m_Shape.m_Circle.m_Radius = pOldSource->m_FalloffDistance;
995 }
996 }
997
998 pMap->UnloadData(Index: pSoundsItem->m_Data);
999 }
1000 }
1001 }
1002 }
1003 }
1004
1005 // load envelopes
1006 {
1007 const CMapBasedEnvelopePointAccess EnvelopePoints(pMap.get());
1008
1009 int EnvelopeStart, EnvelopeNum;
1010 pMap->GetType(Type: MAPITEMTYPE_ENVELOPE, pStart: &EnvelopeStart, pNum: &EnvelopeNum);
1011 for(int EnvelopeIndex = 0; EnvelopeIndex < EnvelopeNum; EnvelopeIndex++)
1012 {
1013 CMapItemEnvelope *pItem = (CMapItemEnvelope *)pMap->GetItem(Index: EnvelopeStart + EnvelopeIndex);
1014 int Channels = pItem->m_Channels;
1015 if(Channels <= 0 || Channels == 2 || Channels > CEnvPoint::MAX_CHANNELS)
1016 {
1017 // Fall back to showing all channels if the number of channels is unsupported
1018 Channels = CEnvPoint::MAX_CHANNELS;
1019 }
1020 if(Channels != pItem->m_Channels)
1021 {
1022 char aBuf[128];
1023 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);
1024 ErrorHandler(aBuf);
1025 }
1026
1027 std::shared_ptr<CEnvelope> pEnvelope = std::make_shared<CEnvelope>(args&: Channels);
1028 pEnvelope->m_vPoints.resize(sz: pItem->m_NumPoints);
1029 for(int PointIndex = 0; PointIndex < pItem->m_NumPoints; PointIndex++)
1030 {
1031 const CEnvPoint *pPoint = EnvelopePoints.GetPoint(Index: pItem->m_StartPoint + PointIndex);
1032 if(pPoint != nullptr)
1033 mem_copy(dest: &pEnvelope->m_vPoints[PointIndex], source: pPoint, size: sizeof(CEnvPoint));
1034 const CEnvPointBezier *pPointBezier = EnvelopePoints.GetBezier(Index: pItem->m_StartPoint + PointIndex);
1035 if(pPointBezier != nullptr)
1036 mem_copy(dest: &pEnvelope->m_vPoints[PointIndex].m_Bezier, source: pPointBezier, size: sizeof(CEnvPointBezier));
1037 }
1038 if(pItem->m_aName[0] != -1) // compatibility with old maps
1039 IntsToStr(pInts: pItem->m_aName, NumInts: std::size(pItem->m_aName), pStr: pEnvelope->m_aName, StrSize: std::size(pEnvelope->m_aName));
1040 m_vpEnvelopes.push_back(x: pEnvelope);
1041 if(pItem->m_Version >= 2)
1042 pEnvelope->m_Synchronized = pItem->m_Synchronized;
1043 }
1044 }
1045
1046 // load automapper configurations
1047 {
1048 int AutomapperConfigStart, AutomapperConfigNum;
1049 pMap->GetType(Type: MAPITEMTYPE_AUTOMAPPER_CONFIG, pStart: &AutomapperConfigStart, pNum: &AutomapperConfigNum);
1050 for(int i = 0; i < AutomapperConfigNum; i++)
1051 {
1052 CMapItemAutomapperConfig *pItem = (CMapItemAutomapperConfig *)pMap->GetItem(Index: AutomapperConfigStart + i);
1053 if(pItem->m_Version == 1)
1054 {
1055 if(pItem->m_GroupId >= 0 && (size_t)pItem->m_GroupId < m_vpGroups.size() &&
1056 pItem->m_LayerId >= 0 && (size_t)pItem->m_LayerId < m_vpGroups[pItem->m_GroupId]->m_vpLayers.size())
1057 {
1058 std::shared_ptr<CLayer> pLayer = m_vpGroups[pItem->m_GroupId]->m_vpLayers[pItem->m_LayerId];
1059 if(pLayer->m_Type == LAYERTYPE_TILES)
1060 {
1061 std::shared_ptr<CLayerTiles> pTiles = std::static_pointer_cast<CLayerTiles>(r: m_vpGroups[pItem->m_GroupId]->m_vpLayers[pItem->m_LayerId]);
1062 // only load auto mappers for tile layers (not physics layers)
1063 if(!(pTiles->m_HasGame || pTiles->m_HasTele || pTiles->m_HasSpeedup ||
1064 pTiles->m_HasFront || pTiles->m_HasSwitch || pTiles->m_HasTune))
1065 {
1066 pTiles->m_AutomapperConfig = pItem->m_AutomapperConfig;
1067 pTiles->m_Seed = pItem->m_AutomapperSeed;
1068 pTiles->m_AutoAutomapper = !!(pItem->m_Flags & CMapItemAutomapperConfig::FLAG_AUTOMATIC);
1069 }
1070 }
1071 }
1072 }
1073 }
1074 }
1075
1076 str_copy(dst&: m_aFilename, src: pFilename);
1077
1078 CheckIntegrity();
1079 PerformSanityChecks(ErrorHandler);
1080
1081 SortImages();
1082 SelectGameLayer();
1083
1084 ResetModifiedState();
1085 return true;
1086}
1087
1088bool CEditorMap::Append(const char *pFilename, int StorageType, bool IgnoreHistory, const FErrorHandler &ErrorHandler)
1089{
1090 CEditorMap NewMap(Editor());
1091 if(!NewMap.Load(pFilename, StorageType, ErrorHandler))
1092 return false;
1093
1094 CEditorActionAppendMap::SPrevInfo Info{
1095 .m_Groups: (int)m_vpGroups.size(),
1096 .m_Images: (int)m_vpImages.size(),
1097 .m_Sounds: (int)m_vpSounds.size(),
1098 .m_Envelopes: (int)m_vpEnvelopes.size()};
1099
1100 // Keep a map to check if specific indices have already been replaced to prevent
1101 // replacing those indices again when transferring images
1102 std::map<int *, bool> ReplacedIndicesMap;
1103 const auto &&ReplaceIndex = [&ReplacedIndicesMap](int ToReplace, int ReplaceWith) {
1104 return [&ReplacedIndicesMap, ToReplace, ReplaceWith](int *pIndex) {
1105 if(*pIndex == ToReplace && !ReplacedIndicesMap[pIndex])
1106 {
1107 *pIndex = ReplaceWith;
1108 ReplacedIndicesMap[pIndex] = true;
1109 }
1110 };
1111 };
1112
1113 const auto &&Rename = [&](const std::shared_ptr<CEditorImage> &pImage) {
1114 char aRenamed[IO_MAX_PATH_LENGTH];
1115 int DuplicateCount = 1;
1116 str_copy(dst&: aRenamed, src: pImage->m_aName);
1117 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())
1118 str_format(buffer: aRenamed, buffer_size: sizeof(aRenamed), format: "%s (%d)", pImage->m_aName, DuplicateCount++); // Rename to "image_name (%d)"
1119 str_copy(dst&: pImage->m_aName, src: aRenamed);
1120 };
1121
1122 // Transfer non-duplicate images
1123 for(auto NewMapIt = NewMap.m_vpImages.begin(); NewMapIt != NewMap.m_vpImages.end(); ++NewMapIt)
1124 {
1125 const auto &pNewImage = *NewMapIt;
1126 auto NameIsTaken = [pNewImage](const std::shared_ptr<CEditorImage> &OtherImage) { return str_comp(a: pNewImage->m_aName, b: OtherImage->m_aName) == 0; };
1127 auto MatchInCurrentMap = std::find_if(first: m_vpImages.begin(), last: m_vpImages.end(), pred: NameIsTaken);
1128
1129 const bool IsDuplicate = MatchInCurrentMap != m_vpImages.end();
1130 const int IndexToReplace = NewMapIt - NewMap.m_vpImages.begin();
1131
1132 if(IsDuplicate)
1133 {
1134 // Check for image data
1135 const bool ImageDataEquals = (*MatchInCurrentMap)->DataEquals(Other: *pNewImage);
1136
1137 if(ImageDataEquals)
1138 {
1139 const int IndexToReplaceWith = MatchInCurrentMap - m_vpImages.begin();
1140
1141 dbg_msg(sys: "editor", fmt: "map already contains image %s with the same data, removing duplicate", pNewImage->m_aName);
1142
1143 // In the new map, replace the index of the duplicate image to the index of the same in the current map.
1144 NewMap.ModifyImageIndex(IndexModifyFunction: ReplaceIndex(IndexToReplace, IndexToReplaceWith));
1145 }
1146 else
1147 {
1148 // Rename image and add it
1149 Rename(pNewImage);
1150
1151 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);
1152
1153 NewMap.ModifyImageIndex(IndexModifyFunction: ReplaceIndex(IndexToReplace, m_vpImages.size()));
1154 pNewImage->OnAttach(pMap: this);
1155 m_vpImages.push_back(x: pNewImage);
1156 }
1157 }
1158 else
1159 {
1160 NewMap.ModifyImageIndex(IndexModifyFunction: ReplaceIndex(IndexToReplace, m_vpImages.size()));
1161 pNewImage->OnAttach(pMap: this);
1162 m_vpImages.push_back(x: pNewImage);
1163 }
1164 }
1165 NewMap.m_vpImages.clear();
1166
1167 // modify indices
1168 const auto &&ModifyAddIndex = [](int AddAmount) {
1169 return [AddAmount](int *pIndex) {
1170 if(*pIndex >= 0)
1171 *pIndex += AddAmount;
1172 };
1173 };
1174 NewMap.ModifySoundIndex(IndexModifyFunction: ModifyAddIndex(m_vpSounds.size()));
1175 NewMap.ModifyEnvelopeIndex(IndexModifyFunction: ModifyAddIndex(m_vpEnvelopes.size()));
1176
1177 // transfer sounds
1178 for(const auto &pSound : NewMap.m_vpSounds)
1179 {
1180 pSound->OnAttach(pMap: this);
1181 m_vpSounds.push_back(x: pSound);
1182 }
1183 NewMap.m_vpSounds.clear();
1184
1185 // transfer envelopes
1186 for(const auto &pEnvelope : NewMap.m_vpEnvelopes)
1187 m_vpEnvelopes.push_back(x: pEnvelope);
1188 NewMap.m_vpEnvelopes.clear();
1189
1190 // transfer groups
1191 for(const auto &pGroup : NewMap.m_vpGroups)
1192 {
1193 if(pGroup != NewMap.m_pGameGroup)
1194 {
1195 pGroup->OnAttach(pMap: this);
1196 m_vpGroups.push_back(x: pGroup);
1197 }
1198 }
1199 NewMap.m_vpGroups.clear();
1200
1201 // transfer server settings
1202 for(const auto &pSetting : NewMap.m_vSettings)
1203 {
1204 // Check if setting already exists
1205 bool AlreadyExists = false;
1206 for(const auto &pExistingSetting : m_vSettings)
1207 {
1208 if(!str_comp(a: pExistingSetting.m_aCommand, b: pSetting.m_aCommand))
1209 AlreadyExists = true;
1210 }
1211 if(!AlreadyExists)
1212 m_vSettings.push_back(x: pSetting);
1213 }
1214 NewMap.m_vSettings.clear();
1215
1216 auto IndexMap = SortImages();
1217
1218 if(!IgnoreHistory)
1219 m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionAppendMap>(args: this, args&: pFilename, args&: Info, args&: IndexMap));
1220
1221 CheckIntegrity();
1222 OnModify();
1223
1224 // all done \o/
1225 return true;
1226}
1227
1228void CEditorMap::PerformSanityChecks(const FErrorHandler &ErrorHandler)
1229{
1230 // Check if there are any images with a width or height that is not divisible by 16 which are
1231 // used in tile layers. Reset the image for these layers, to prevent crashes with some drivers.
1232 size_t ImageIndex = 0;
1233 for(const std::shared_ptr<CEditorImage> &pImage : m_vpImages)
1234 {
1235 if(pImage->m_Width % 16 != 0 || pImage->m_Height % 16 != 0)
1236 {
1237 size_t GroupIndex = 0;
1238 for(const std::shared_ptr<CLayerGroup> &pGroup : m_vpGroups)
1239 {
1240 size_t LayerIndex = 0;
1241 for(const std::shared_ptr<CLayer> &pLayer : pGroup->m_vpLayers)
1242 {
1243 if(pLayer->m_Type == LAYERTYPE_TILES)
1244 {
1245 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: pLayer);
1246 if(pLayerTiles->m_Image >= 0 && (size_t)pLayerTiles->m_Image == ImageIndex)
1247 {
1248 pLayerTiles->m_Image = -1;
1249 char aBuf[IO_MAX_PATH_LENGTH + 128];
1250 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);
1251 ErrorHandler(aBuf);
1252 }
1253 }
1254 ++LayerIndex;
1255 }
1256 ++GroupIndex;
1257 }
1258 }
1259 ++ImageIndex;
1260 }
1261}
1262
1263bool CEditorMap::PerformAutosave(const std::function<void(const char *pErrorMessage)> &ErrorHandler)
1264{
1265 char aDate[20];
1266 char aAutosavePath[IO_MAX_PATH_LENGTH];
1267 str_timestamp(buffer: aDate, buffer_size: sizeof(aDate));
1268 str_format(buffer: aAutosavePath, buffer_size: sizeof(aAutosavePath), format: "maps/auto/%s_%s.map", m_aAutosaveName, aDate);
1269
1270 m_LastSaveTime = Editor()->Client()->GlobalTime();
1271 if(Save(pFilename: aAutosavePath, ErrorHandler))
1272 {
1273 m_ModifiedAuto = false;
1274 // Clean up autosaves
1275 if(g_Config.m_EdAutosaveMax)
1276 {
1277 CFileCollection AutosavedMaps;
1278 AutosavedMaps.Init(pStorage: Editor()->Storage(), pPath: "maps/auto", pFileDesc: m_aAutosaveName, pFileExt: ".map", MaxEntries: g_Config.m_EdAutosaveMax);
1279 }
1280 return true;
1281 }
1282 else
1283 {
1284 char aErrorMessage[IO_MAX_PATH_LENGTH + 128];
1285 str_format(buffer: aErrorMessage, buffer_size: sizeof(aErrorMessage), format: "Failed to automatically save map to file '%s'.", aAutosavePath);
1286 ErrorHandler(aErrorMessage);
1287 return false;
1288 }
1289}
1290