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