1 | #include <game/editor/editor.h> |
2 | |
3 | #include <engine/client.h> |
4 | #include <engine/console.h> |
5 | #include <engine/graphics.h> |
6 | #include <engine/serverbrowser.h> |
7 | #include <engine/shared/datafile.h> |
8 | #include <engine/sound.h> |
9 | #include <engine/storage.h> |
10 | |
11 | #include <game/gamecore.h> |
12 | #include <game/mapitems_ex.h> |
13 | |
14 | #include "image.h" |
15 | #include "sound.h" |
16 | |
17 | template<typename T> |
18 | static int MakeVersion(int i, const T &v) |
19 | { |
20 | return (i << 16) + sizeof(T); |
21 | } |
22 | |
23 | // compatibility with old sound layers |
24 | struct CSoundSource_DEPRECATED |
25 | { |
26 | CPoint m_Position; |
27 | int m_Loop; |
28 | int m_TimeDelay; // in s |
29 | int m_FalloffDistance; |
30 | int m_PosEnv; |
31 | int m_PosEnvOffset; |
32 | int m_SoundEnv; |
33 | int m_SoundEnvOffset; |
34 | }; |
35 | |
36 | bool CEditorMap::Save(const char *pFileName) |
37 | { |
38 | char aFileNameTmp[IO_MAX_PATH_LENGTH]; |
39 | IStorage::FormatTmpPath(aBuf: aFileNameTmp, BufSize: sizeof(aFileNameTmp), pPath: pFileName); |
40 | |
41 | char aBuf[IO_MAX_PATH_LENGTH + 64]; |
42 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "saving to '%s'..." , aFileNameTmp); |
43 | m_pEditor->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "editor" , pStr: aBuf); |
44 | CDataFileWriter Writer; |
45 | if(!Writer.Open(pStorage: m_pEditor->Storage(), pFilename: aFileNameTmp)) |
46 | { |
47 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "failed to open file '%s'..." , aFileNameTmp); |
48 | m_pEditor->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "editor" , pStr: aBuf); |
49 | return false; |
50 | } |
51 | |
52 | // save version |
53 | { |
54 | CMapItemVersion Item; |
55 | Item.m_Version = CMapItemVersion::CURRENT_VERSION; |
56 | Writer.AddItem(Type: MAPITEMTYPE_VERSION, Id: 0, Size: sizeof(Item), pData: &Item); |
57 | } |
58 | |
59 | // save map info |
60 | { |
61 | CMapItemInfoSettings Item; |
62 | Item.m_Version = 1; |
63 | Item.m_Author = Writer.AddDataString(pStr: m_MapInfo.m_aAuthor); |
64 | Item.m_MapVersion = Writer.AddDataString(pStr: m_MapInfo.m_aVersion); |
65 | Item.m_Credits = Writer.AddDataString(pStr: m_MapInfo.m_aCredits); |
66 | Item.m_License = Writer.AddDataString(pStr: m_MapInfo.m_aLicense); |
67 | |
68 | Item.m_Settings = -1; |
69 | if(!m_vSettings.empty()) |
70 | { |
71 | int Size = 0; |
72 | for(const auto &Setting : m_vSettings) |
73 | { |
74 | Size += str_length(str: Setting.m_aCommand) + 1; |
75 | } |
76 | |
77 | char *pSettings = (char *)malloc(size: maximum(a: Size, b: 1)); |
78 | char *pNext = pSettings; |
79 | for(const auto &Setting : m_vSettings) |
80 | { |
81 | int Length = str_length(str: Setting.m_aCommand) + 1; |
82 | mem_copy(dest: pNext, source: Setting.m_aCommand, size: Length); |
83 | pNext += Length; |
84 | } |
85 | Item.m_Settings = Writer.AddData(Size, pData: pSettings); |
86 | free(ptr: pSettings); |
87 | } |
88 | |
89 | Writer.AddItem(Type: MAPITEMTYPE_INFO, Id: 0, Size: sizeof(Item), pData: &Item); |
90 | } |
91 | |
92 | // save images |
93 | for(size_t i = 0; i < m_vpImages.size(); i++) |
94 | { |
95 | std::shared_ptr<CEditorImage> pImg = m_vpImages[i]; |
96 | |
97 | // analyse the image for when saving (should be done when we load the image) |
98 | // TODO! |
99 | pImg->AnalyseTileFlags(); |
100 | |
101 | CMapItemImage Item; |
102 | Item.m_Version = CMapItemImage::CURRENT_VERSION; |
103 | |
104 | Item.m_Width = pImg->m_Width; |
105 | Item.m_Height = pImg->m_Height; |
106 | Item.m_External = pImg->m_External; |
107 | Item.m_ImageName = Writer.AddDataString(pStr: pImg->m_aName); |
108 | if(pImg->m_External) |
109 | { |
110 | Item.m_ImageData = -1; |
111 | } |
112 | else |
113 | { |
114 | const size_t PixelSize = CImageInfo::PixelSize(Format: CImageInfo::FORMAT_RGBA); |
115 | const size_t DataSize = (size_t)Item.m_Width * Item.m_Height * PixelSize; |
116 | if(pImg->m_Format == CImageInfo::FORMAT_RGB) |
117 | { |
118 | // Convert to RGBA |
119 | unsigned char *pDataRGBA = (unsigned char *)malloc(size: DataSize); |
120 | unsigned char *pDataRGB = (unsigned char *)pImg->m_pData; |
121 | for(int j = 0; j < Item.m_Width * Item.m_Height; j++) |
122 | { |
123 | pDataRGBA[j * PixelSize] = pDataRGB[j * 3]; |
124 | pDataRGBA[j * PixelSize + 1] = pDataRGB[j * 3 + 1]; |
125 | pDataRGBA[j * PixelSize + 2] = pDataRGB[j * 3 + 2]; |
126 | pDataRGBA[j * PixelSize + 3] = 255; |
127 | } |
128 | Item.m_ImageData = Writer.AddData(Size: DataSize, pData: pDataRGBA); |
129 | free(ptr: pDataRGBA); |
130 | } |
131 | else |
132 | { |
133 | Item.m_ImageData = Writer.AddData(Size: DataSize, pData: pImg->m_pData); |
134 | } |
135 | } |
136 | Writer.AddItem(Type: MAPITEMTYPE_IMAGE, Id: i, Size: sizeof(Item), pData: &Item); |
137 | } |
138 | |
139 | // save sounds |
140 | for(size_t i = 0; i < m_vpSounds.size(); i++) |
141 | { |
142 | std::shared_ptr<CEditorSound> pSound = m_vpSounds[i]; |
143 | |
144 | CMapItemSound Item; |
145 | Item.m_Version = 1; |
146 | |
147 | Item.m_External = 0; |
148 | Item.m_SoundName = Writer.AddDataString(pStr: pSound->m_aName); |
149 | Item.m_SoundData = Writer.AddData(Size: pSound->m_DataSize, pData: pSound->m_pData); |
150 | // Value is not read in new versions, but we still need to write it for compatibility with old versions. |
151 | Item.m_SoundDataSize = pSound->m_DataSize; |
152 | |
153 | Writer.AddItem(Type: MAPITEMTYPE_SOUND, Id: i, Size: sizeof(Item), pData: &Item); |
154 | } |
155 | |
156 | // save layers |
157 | int LayerCount = 0, GroupCount = 0; |
158 | int AutomapperCount = 0; |
159 | for(const auto &pGroup : m_vpGroups) |
160 | { |
161 | CMapItemGroup GItem; |
162 | GItem.m_Version = CMapItemGroup::CURRENT_VERSION; |
163 | |
164 | GItem.m_ParallaxX = pGroup->m_ParallaxX; |
165 | GItem.m_ParallaxY = pGroup->m_ParallaxY; |
166 | GItem.m_OffsetX = pGroup->m_OffsetX; |
167 | GItem.m_OffsetY = pGroup->m_OffsetY; |
168 | GItem.m_UseClipping = pGroup->m_UseClipping; |
169 | GItem.m_ClipX = pGroup->m_ClipX; |
170 | GItem.m_ClipY = pGroup->m_ClipY; |
171 | GItem.m_ClipW = pGroup->m_ClipW; |
172 | GItem.m_ClipH = pGroup->m_ClipH; |
173 | GItem.m_StartLayer = LayerCount; |
174 | GItem.m_NumLayers = 0; |
175 | |
176 | // save group name |
177 | StrToInts(pInts: GItem.m_aName, NumInts: std::size(GItem.m_aName), pStr: pGroup->m_aName); |
178 | |
179 | for(const std::shared_ptr<CLayer> &pLayer : pGroup->m_vpLayers) |
180 | { |
181 | if(pLayer->m_Type == LAYERTYPE_TILES) |
182 | { |
183 | m_pEditor->Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "editor" , pStr: "saving tiles layer" ); |
184 | std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: pLayer); |
185 | pLayerTiles->PrepareForSave(); |
186 | |
187 | CMapItemLayerTilemap Item; |
188 | Item.m_Version = CMapItemLayerTilemap::CURRENT_VERSION; |
189 | |
190 | Item.m_Layer.m_Version = 0; // was previously uninitialized, do not rely on it being 0 |
191 | Item.m_Layer.m_Flags = pLayerTiles->m_Flags; |
192 | Item.m_Layer.m_Type = pLayerTiles->m_Type; |
193 | |
194 | Item.m_Color = pLayerTiles->m_Color; |
195 | Item.m_ColorEnv = pLayerTiles->m_ColorEnv; |
196 | Item.m_ColorEnvOffset = pLayerTiles->m_ColorEnvOffset; |
197 | |
198 | Item.m_Width = pLayerTiles->m_Width; |
199 | Item.m_Height = pLayerTiles->m_Height; |
200 | // Item.m_Flags = pLayerTiles->m_Game ? TILESLAYERFLAG_GAME : 0; |
201 | |
202 | if(pLayerTiles->m_Tele) |
203 | Item.m_Flags = TILESLAYERFLAG_TELE; |
204 | else if(pLayerTiles->m_Speedup) |
205 | Item.m_Flags = TILESLAYERFLAG_SPEEDUP; |
206 | else if(pLayerTiles->m_Front) |
207 | Item.m_Flags = TILESLAYERFLAG_FRONT; |
208 | else if(pLayerTiles->m_Switch) |
209 | Item.m_Flags = TILESLAYERFLAG_SWITCH; |
210 | else if(pLayerTiles->m_Tune) |
211 | Item.m_Flags = TILESLAYERFLAG_TUNE; |
212 | else |
213 | Item.m_Flags = pLayerTiles->m_Game ? TILESLAYERFLAG_GAME : 0; |
214 | |
215 | Item.m_Image = pLayerTiles->m_Image; |
216 | |
217 | // the following values were previously uninitialized, do not rely on them being -1 when unused |
218 | Item.m_Tele = -1; |
219 | Item.m_Speedup = -1; |
220 | Item.m_Front = -1; |
221 | Item.m_Switch = -1; |
222 | Item.m_Tune = -1; |
223 | |
224 | if(Item.m_Flags && !(pLayerTiles->m_Game)) |
225 | { |
226 | CTile *pEmptyTiles = (CTile *)calloc(nmemb: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height, size: sizeof(CTile)); |
227 | mem_zero(block: pEmptyTiles, size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTile)); |
228 | Item.m_Data = Writer.AddData(Size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTile), pData: pEmptyTiles); |
229 | free(ptr: pEmptyTiles); |
230 | |
231 | if(pLayerTiles->m_Tele) |
232 | 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); |
233 | else if(pLayerTiles->m_Speedup) |
234 | 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); |
235 | else if(pLayerTiles->m_Front) |
236 | Item.m_Front = Writer.AddData(Size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTile), pData: pLayerTiles->m_pTiles); |
237 | else if(pLayerTiles->m_Switch) |
238 | 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); |
239 | else if(pLayerTiles->m_Tune) |
240 | 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); |
241 | } |
242 | else |
243 | Item.m_Data = Writer.AddData(Size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTile), pData: pLayerTiles->m_pTiles); |
244 | |
245 | // save layer name |
246 | StrToInts(pInts: Item.m_aName, NumInts: std::size(Item.m_aName), pStr: pLayerTiles->m_aName); |
247 | |
248 | // save item |
249 | Writer.AddItem(Type: MAPITEMTYPE_LAYER, Id: LayerCount, Size: sizeof(Item), pData: &Item); |
250 | |
251 | // save auto mapper of each tile layer (not physics layer) |
252 | if(!Item.m_Flags) |
253 | { |
254 | CMapItemAutoMapperConfig ItemAutomapper; |
255 | ItemAutomapper.m_Version = CMapItemAutoMapperConfig::CURRENT_VERSION; |
256 | ItemAutomapper.m_GroupId = GroupCount; |
257 | ItemAutomapper.m_LayerId = GItem.m_NumLayers; |
258 | ItemAutomapper.m_AutomapperConfig = pLayerTiles->m_AutoMapperConfig; |
259 | ItemAutomapper.m_AutomapperSeed = pLayerTiles->m_Seed; |
260 | ItemAutomapper.m_Flags = 0; |
261 | if(pLayerTiles->m_AutoAutoMap) |
262 | ItemAutomapper.m_Flags |= CMapItemAutoMapperConfig::FLAG_AUTOMATIC; |
263 | |
264 | Writer.AddItem(Type: MAPITEMTYPE_AUTOMAPPER_CONFIG, Id: AutomapperCount, Size: sizeof(ItemAutomapper), pData: &ItemAutomapper); |
265 | AutomapperCount++; |
266 | } |
267 | } |
268 | else if(pLayer->m_Type == LAYERTYPE_QUADS) |
269 | { |
270 | m_pEditor->Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "editor" , pStr: "saving quads layer" ); |
271 | std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: pLayer); |
272 | CMapItemLayerQuads Item; |
273 | Item.m_Version = 2; |
274 | Item.m_Layer.m_Version = 0; // was previously uninitialized, do not rely on it being 0 |
275 | Item.m_Layer.m_Flags = pLayerQuads->m_Flags; |
276 | Item.m_Layer.m_Type = pLayerQuads->m_Type; |
277 | Item.m_Image = pLayerQuads->m_Image; |
278 | |
279 | Item.m_NumQuads = 0; |
280 | Item.m_Data = -1; |
281 | if(!pLayerQuads->m_vQuads.empty()) |
282 | { |
283 | // add the data |
284 | Item.m_NumQuads = pLayerQuads->m_vQuads.size(); |
285 | Item.m_Data = Writer.AddDataSwapped(Size: pLayerQuads->m_vQuads.size() * sizeof(CQuad), pData: pLayerQuads->m_vQuads.data()); |
286 | } |
287 | else |
288 | { |
289 | // add dummy data for backwards compatibility |
290 | // this allows the layer to be loaded with an empty array since m_NumQuads is 0 while saving |
291 | CQuad Dummy{}; |
292 | Item.m_Data = Writer.AddDataSwapped(Size: sizeof(CQuad), pData: &Dummy); |
293 | } |
294 | |
295 | // save layer name |
296 | StrToInts(pInts: Item.m_aName, NumInts: std::size(Item.m_aName), pStr: pLayerQuads->m_aName); |
297 | |
298 | // save item |
299 | Writer.AddItem(Type: MAPITEMTYPE_LAYER, Id: LayerCount, Size: sizeof(Item), pData: &Item); |
300 | } |
301 | else if(pLayer->m_Type == LAYERTYPE_SOUNDS) |
302 | { |
303 | m_pEditor->Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "editor" , pStr: "saving sounds layer" ); |
304 | std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: pLayer); |
305 | CMapItemLayerSounds Item; |
306 | Item.m_Version = CMapItemLayerSounds::CURRENT_VERSION; |
307 | Item.m_Layer.m_Version = 0; // was previously uninitialized, do not rely on it being 0 |
308 | Item.m_Layer.m_Flags = pLayerSounds->m_Flags; |
309 | Item.m_Layer.m_Type = pLayerSounds->m_Type; |
310 | Item.m_Sound = pLayerSounds->m_Sound; |
311 | |
312 | Item.m_NumSources = 0; |
313 | if(!pLayerSounds->m_vSources.empty()) |
314 | { |
315 | // add the data |
316 | Item.m_NumSources = pLayerSounds->m_vSources.size(); |
317 | Item.m_Data = Writer.AddDataSwapped(Size: pLayerSounds->m_vSources.size() * sizeof(CSoundSource), pData: pLayerSounds->m_vSources.data()); |
318 | } |
319 | else |
320 | { |
321 | // add dummy data for backwards compatibility |
322 | // this allows the layer to be loaded with an empty array since m_NumSources is 0 while saving |
323 | CSoundSource Dummy{}; |
324 | Item.m_Data = Writer.AddDataSwapped(Size: sizeof(CSoundSource), pData: &Dummy); |
325 | } |
326 | |
327 | // save layer name |
328 | StrToInts(pInts: Item.m_aName, NumInts: std::size(Item.m_aName), pStr: pLayerSounds->m_aName); |
329 | |
330 | // save item |
331 | Writer.AddItem(Type: MAPITEMTYPE_LAYER, Id: LayerCount, Size: sizeof(Item), pData: &Item); |
332 | } |
333 | |
334 | GItem.m_NumLayers++; |
335 | LayerCount++; |
336 | } |
337 | |
338 | Writer.AddItem(Type: MAPITEMTYPE_GROUP, Id: GroupCount, Size: sizeof(GItem), pData: &GItem); |
339 | GroupCount++; |
340 | } |
341 | |
342 | // save envelopes |
343 | m_pEditor->Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "editor" , pStr: "saving envelopes" ); |
344 | int PointCount = 0; |
345 | for(size_t e = 0; e < m_vpEnvelopes.size(); e++) |
346 | { |
347 | CMapItemEnvelope Item; |
348 | Item.m_Version = CMapItemEnvelope::CURRENT_VERSION; |
349 | Item.m_Channels = m_vpEnvelopes[e]->GetChannels(); |
350 | Item.m_StartPoint = PointCount; |
351 | Item.m_NumPoints = m_vpEnvelopes[e]->m_vPoints.size(); |
352 | Item.m_Synchronized = m_vpEnvelopes[e]->m_Synchronized; |
353 | StrToInts(pInts: Item.m_aName, NumInts: std::size(Item.m_aName), pStr: m_vpEnvelopes[e]->m_aName); |
354 | |
355 | Writer.AddItem(Type: MAPITEMTYPE_ENVELOPE, Id: e, Size: sizeof(Item), pData: &Item); |
356 | PointCount += Item.m_NumPoints; |
357 | } |
358 | |
359 | // save points |
360 | m_pEditor->Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "editor" , pStr: "saving envelope points" ); |
361 | bool BezierUsed = false; |
362 | for(const auto &pEnvelope : m_vpEnvelopes) |
363 | { |
364 | for(const auto &Point : pEnvelope->m_vPoints) |
365 | { |
366 | if(Point.m_Curvetype == CURVETYPE_BEZIER) |
367 | { |
368 | BezierUsed = true; |
369 | break; |
370 | } |
371 | } |
372 | if(BezierUsed) |
373 | break; |
374 | } |
375 | |
376 | CEnvPoint *pPoints = (CEnvPoint *)calloc(nmemb: maximum(a: PointCount, b: 1), size: sizeof(CEnvPoint)); |
377 | CEnvPointBezier *pPointsBezier = nullptr; |
378 | if(BezierUsed) |
379 | pPointsBezier = (CEnvPointBezier *)calloc(nmemb: maximum(a: PointCount, b: 1), size: sizeof(CEnvPointBezier)); |
380 | PointCount = 0; |
381 | |
382 | for(const auto &pEnvelope : m_vpEnvelopes) |
383 | { |
384 | const CEnvPoint_runtime *pPrevPoint = nullptr; |
385 | for(const auto &Point : pEnvelope->m_vPoints) |
386 | { |
387 | mem_copy(dest: &pPoints[PointCount], source: &Point, size: sizeof(CEnvPoint)); |
388 | if(pPointsBezier != nullptr) |
389 | { |
390 | if(Point.m_Curvetype == CURVETYPE_BEZIER) |
391 | { |
392 | mem_copy(dest: &pPointsBezier[PointCount].m_aOutTangentDeltaX, source: &Point.m_Bezier.m_aOutTangentDeltaX, size: sizeof(Point.m_Bezier.m_aOutTangentDeltaX)); |
393 | mem_copy(dest: &pPointsBezier[PointCount].m_aOutTangentDeltaY, source: &Point.m_Bezier.m_aOutTangentDeltaY, size: sizeof(Point.m_Bezier.m_aOutTangentDeltaY)); |
394 | } |
395 | if(pPrevPoint != nullptr && pPrevPoint->m_Curvetype == CURVETYPE_BEZIER) |
396 | { |
397 | mem_copy(dest: &pPointsBezier[PointCount].m_aInTangentDeltaX, source: &Point.m_Bezier.m_aInTangentDeltaX, size: sizeof(Point.m_Bezier.m_aInTangentDeltaX)); |
398 | mem_copy(dest: &pPointsBezier[PointCount].m_aInTangentDeltaY, source: &Point.m_Bezier.m_aInTangentDeltaY, size: sizeof(Point.m_Bezier.m_aInTangentDeltaY)); |
399 | } |
400 | } |
401 | PointCount++; |
402 | pPrevPoint = &Point; |
403 | } |
404 | } |
405 | |
406 | Writer.AddItem(Type: MAPITEMTYPE_ENVPOINTS, Id: 0, Size: sizeof(CEnvPoint) * PointCount, pData: pPoints); |
407 | free(ptr: pPoints); |
408 | |
409 | if(pPointsBezier != nullptr) |
410 | { |
411 | Writer.AddItem(Type: MAPITEMTYPE_ENVPOINTS_BEZIER, Id: 0, Size: sizeof(CEnvPointBezier) * PointCount, pData: pPointsBezier); |
412 | free(ptr: pPointsBezier); |
413 | } |
414 | |
415 | // finish the data file |
416 | std::shared_ptr<CDataFileWriterFinishJob> pWriterFinishJob = std::make_shared<CDataFileWriterFinishJob>(args&: pFileName, args&: aFileNameTmp, args: std::move(Writer)); |
417 | m_pEditor->Engine()->AddJob(pJob: pWriterFinishJob); |
418 | m_pEditor->m_WriterFinishJobs.push_back(x: pWriterFinishJob); |
419 | |
420 | return true; |
421 | } |
422 | |
423 | bool CEditorMap::Load(const char *pFileName, int StorageType, const std::function<void(const char *pErrorMessage)> &ErrorHandler) |
424 | { |
425 | CDataFileReader DataFile; |
426 | if(!DataFile.Open(pStorage: m_pEditor->Storage(), pFilename: pFileName, StorageType)) |
427 | return false; |
428 | |
429 | // check version |
430 | const CMapItemVersion *pItemVersion = static_cast<CMapItemVersion *>(DataFile.FindItem(Type: MAPITEMTYPE_VERSION, Id: 0)); |
431 | if(pItemVersion == nullptr || pItemVersion->m_Version != CMapItemVersion::CURRENT_VERSION) |
432 | { |
433 | ErrorHandler("Error: The map has an unsupported version." ); |
434 | return false; |
435 | } |
436 | |
437 | Clean(); |
438 | |
439 | // load map info |
440 | { |
441 | int Start, Num; |
442 | DataFile.GetType(Type: MAPITEMTYPE_INFO, pStart: &Start, pNum: &Num); |
443 | for(int i = Start; i < Start + Num; i++) |
444 | { |
445 | int ItemSize = DataFile.GetItemSize(Index: Start); |
446 | int ItemId; |
447 | CMapItemInfoSettings *pItem = (CMapItemInfoSettings *)DataFile.GetItem(Index: i, pType: nullptr, pId: &ItemId); |
448 | if(!pItem || ItemId != 0) |
449 | continue; |
450 | |
451 | const auto &&ReadStringInfo = [&](int Index, char *pBuffer, size_t BufferSize, const char *pErrorContext) { |
452 | const char *pStr = DataFile.GetDataString(Index); |
453 | if(pStr == nullptr) |
454 | { |
455 | char aBuf[128]; |
456 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read %s from map info." , pErrorContext); |
457 | ErrorHandler(aBuf); |
458 | pBuffer[0] = '\0'; |
459 | } |
460 | else |
461 | { |
462 | str_copy(dst: pBuffer, src: pStr, dst_size: BufferSize); |
463 | } |
464 | }; |
465 | |
466 | ReadStringInfo(pItem->m_Author, m_MapInfo.m_aAuthor, sizeof(m_MapInfo.m_aAuthor), "author" ); |
467 | ReadStringInfo(pItem->m_MapVersion, m_MapInfo.m_aVersion, sizeof(m_MapInfo.m_aVersion), "version" ); |
468 | ReadStringInfo(pItem->m_Credits, m_MapInfo.m_aCredits, sizeof(m_MapInfo.m_aCredits), "credits" ); |
469 | ReadStringInfo(pItem->m_License, m_MapInfo.m_aLicense, sizeof(m_MapInfo.m_aLicense), "license" ); |
470 | |
471 | if(pItem->m_Version != 1 || ItemSize < (int)sizeof(CMapItemInfoSettings)) |
472 | break; |
473 | |
474 | if(!(pItem->m_Settings > -1)) |
475 | break; |
476 | |
477 | const unsigned Size = DataFile.GetDataSize(Index: pItem->m_Settings); |
478 | char *pSettings = (char *)DataFile.GetData(Index: pItem->m_Settings); |
479 | char *pNext = pSettings; |
480 | while(pNext < pSettings + Size) |
481 | { |
482 | int StrSize = str_length(str: pNext) + 1; |
483 | m_vSettings.emplace_back(args&: pNext); |
484 | pNext += StrSize; |
485 | } |
486 | } |
487 | } |
488 | |
489 | // load images |
490 | { |
491 | int Start, Num; |
492 | DataFile.GetType(Type: MAPITEMTYPE_IMAGE, pStart: &Start, pNum: &Num); |
493 | for(int i = 0; i < Num; i++) |
494 | { |
495 | CMapItemImage_v2 *pItem = (CMapItemImage_v2 *)DataFile.GetItem(Index: Start + i); |
496 | |
497 | // copy base info |
498 | std::shared_ptr<CEditorImage> pImg = std::make_shared<CEditorImage>(args&: m_pEditor); |
499 | pImg->m_External = pItem->m_External; |
500 | |
501 | const char *pName = DataFile.GetDataString(Index: pItem->m_ImageName); |
502 | if(pName == nullptr || pName[0] == '\0') |
503 | { |
504 | char aBuf[128]; |
505 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read name of image %d." , i); |
506 | ErrorHandler(aBuf); |
507 | } |
508 | else |
509 | str_copy(dst&: pImg->m_aName, src: pName); |
510 | |
511 | if(pItem->m_Version > 1 && pItem->m_MustBe1 != 1) |
512 | { |
513 | char aBuf[128]; |
514 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Unsupported image type of image %d '%s'." , i, pImg->m_aName); |
515 | ErrorHandler(aBuf); |
516 | } |
517 | |
518 | if(pImg->m_External || (pItem->m_Version > 1 && pItem->m_MustBe1 != 1)) |
519 | { |
520 | char aBuf[IO_MAX_PATH_LENGTH]; |
521 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "mapres/%s.png" , pImg->m_aName); |
522 | |
523 | // load external |
524 | CImageInfo ImgInfo; |
525 | if(m_pEditor->Graphics()->LoadPng(Image&: ImgInfo, pFilename: aBuf, StorageType: IStorage::TYPE_ALL)) |
526 | { |
527 | pImg->m_Width = ImgInfo.m_Width; |
528 | pImg->m_Height = ImgInfo.m_Height; |
529 | pImg->m_Format = ImgInfo.m_Format; |
530 | pImg->m_pData = ImgInfo.m_pData; |
531 | int TextureLoadFlag = m_pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; |
532 | if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0) |
533 | TextureLoadFlag = 0; |
534 | pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(Image: ImgInfo, Flags: TextureLoadFlag, pTexName: aBuf); |
535 | ImgInfo.m_pData = nullptr; |
536 | pImg->m_External = 1; |
537 | } |
538 | } |
539 | else |
540 | { |
541 | pImg->m_Width = pItem->m_Width; |
542 | pImg->m_Height = pItem->m_Height; |
543 | pImg->m_Format = CImageInfo::FORMAT_RGBA; |
544 | |
545 | // copy image data |
546 | void *pData = DataFile.GetData(Index: pItem->m_ImageData); |
547 | const size_t DataSize = pImg->DataSize(); |
548 | pImg->m_pData = static_cast<uint8_t *>(malloc(size: DataSize)); |
549 | mem_copy(dest: pImg->m_pData, source: pData, size: DataSize); |
550 | int TextureLoadFlag = m_pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; |
551 | if(pImg->m_Width % 16 != 0 || pImg->m_Height % 16 != 0) |
552 | TextureLoadFlag = 0; |
553 | pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(Image: *pImg, Flags: TextureLoadFlag, pTexName: pImg->m_aName); |
554 | } |
555 | |
556 | // load auto mapper file |
557 | pImg->m_AutoMapper.Load(pTileName: pImg->m_aName); |
558 | |
559 | m_vpImages.push_back(x: pImg); |
560 | |
561 | // unload image |
562 | DataFile.UnloadData(Index: pItem->m_ImageData); |
563 | DataFile.UnloadData(Index: pItem->m_ImageName); |
564 | } |
565 | } |
566 | |
567 | // load sounds |
568 | { |
569 | int Start, Num; |
570 | DataFile.GetType(Type: MAPITEMTYPE_SOUND, pStart: &Start, pNum: &Num); |
571 | for(int i = 0; i < Num; i++) |
572 | { |
573 | CMapItemSound *pItem = (CMapItemSound *)DataFile.GetItem(Index: Start + i); |
574 | |
575 | // copy base info |
576 | std::shared_ptr<CEditorSound> pSound = std::make_shared<CEditorSound>(args&: m_pEditor); |
577 | |
578 | const char *pName = DataFile.GetDataString(Index: pItem->m_SoundName); |
579 | if(pName == nullptr || pName[0] == '\0') |
580 | { |
581 | char aBuf[128]; |
582 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Failed to read name of sound %d." , i); |
583 | ErrorHandler(aBuf); |
584 | } |
585 | else |
586 | str_copy(dst&: pSound->m_aName, src: pName); |
587 | |
588 | if(pItem->m_External) |
589 | { |
590 | char aBuf[IO_MAX_PATH_LENGTH]; |
591 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "mapres/%s.opus" , pSound->m_aName); |
592 | |
593 | // load external |
594 | if(m_pEditor->Storage()->ReadFile(pFilename: aBuf, Type: IStorage::TYPE_ALL, ppResult: &pSound->m_pData, pResultLen: &pSound->m_DataSize)) |
595 | { |
596 | pSound->m_SoundId = m_pEditor->Sound()->LoadOpusFromMem(pData: pSound->m_pData, DataSize: pSound->m_DataSize, FromEditor: true); |
597 | } |
598 | } |
599 | else |
600 | { |
601 | pSound->m_DataSize = DataFile.GetDataSize(Index: pItem->m_SoundData); |
602 | void *pData = DataFile.GetData(Index: pItem->m_SoundData); |
603 | pSound->m_pData = malloc(size: pSound->m_DataSize); |
604 | mem_copy(dest: pSound->m_pData, source: pData, size: pSound->m_DataSize); |
605 | pSound->m_SoundId = m_pEditor->Sound()->LoadOpusFromMem(pData: pSound->m_pData, DataSize: pSound->m_DataSize, FromEditor: true); |
606 | } |
607 | |
608 | m_vpSounds.push_back(x: pSound); |
609 | |
610 | // unload sound |
611 | DataFile.UnloadData(Index: pItem->m_SoundData); |
612 | DataFile.UnloadData(Index: pItem->m_SoundName); |
613 | } |
614 | } |
615 | |
616 | // load groups |
617 | { |
618 | int , LayersNum; |
619 | DataFile.GetType(Type: MAPITEMTYPE_LAYER, pStart: &LayersStart, pNum: &LayersNum); |
620 | |
621 | int Start, Num; |
622 | DataFile.GetType(Type: MAPITEMTYPE_GROUP, pStart: &Start, pNum: &Num); |
623 | |
624 | for(int g = 0; g < Num; g++) |
625 | { |
626 | CMapItemGroup *pGItem = (CMapItemGroup *)DataFile.GetItem(Index: Start + g); |
627 | |
628 | if(pGItem->m_Version < 1 || pGItem->m_Version > CMapItemGroup::CURRENT_VERSION) |
629 | continue; |
630 | |
631 | std::shared_ptr<CLayerGroup> pGroup = NewGroup(); |
632 | pGroup->m_ParallaxX = pGItem->m_ParallaxX; |
633 | pGroup->m_ParallaxY = pGItem->m_ParallaxY; |
634 | pGroup->m_OffsetX = pGItem->m_OffsetX; |
635 | pGroup->m_OffsetY = pGItem->m_OffsetY; |
636 | |
637 | if(pGItem->m_Version >= 2) |
638 | { |
639 | pGroup->m_UseClipping = pGItem->m_UseClipping; |
640 | pGroup->m_ClipX = pGItem->m_ClipX; |
641 | pGroup->m_ClipY = pGItem->m_ClipY; |
642 | pGroup->m_ClipW = pGItem->m_ClipW; |
643 | pGroup->m_ClipH = pGItem->m_ClipH; |
644 | } |
645 | |
646 | // load group name |
647 | if(pGItem->m_Version >= 3) |
648 | IntsToStr(pInts: pGItem->m_aName, NumInts: std::size(pGItem->m_aName), pStr: pGroup->m_aName, StrSize: std::size(pGroup->m_aName)); |
649 | |
650 | for(int l = 0; l < pGItem->m_NumLayers; l++) |
651 | { |
652 | CMapItemLayer *pLayerItem = (CMapItemLayer *)DataFile.GetItem(Index: LayersStart + pGItem->m_StartLayer + l); |
653 | if(!pLayerItem) |
654 | continue; |
655 | |
656 | if(pLayerItem->m_Type == LAYERTYPE_TILES) |
657 | { |
658 | CMapItemLayerTilemap *pTilemapItem = (CMapItemLayerTilemap *)pLayerItem; |
659 | |
660 | std::shared_ptr<CLayerTiles> pTiles; |
661 | if(pTilemapItem->m_Flags & TILESLAYERFLAG_GAME) |
662 | { |
663 | pTiles = std::make_shared<CLayerGame>(args&: m_pEditor, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height); |
664 | MakeGameLayer(pLayer: pTiles); |
665 | MakeGameGroup(pGroup); |
666 | } |
667 | else if(pTilemapItem->m_Flags & TILESLAYERFLAG_TELE) |
668 | { |
669 | if(pTilemapItem->m_Version <= 2) |
670 | pTilemapItem->m_Tele = *((const int *)(pTilemapItem) + 15); |
671 | |
672 | pTiles = std::make_shared<CLayerTele>(args&: m_pEditor, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height); |
673 | MakeTeleLayer(pLayer: pTiles); |
674 | } |
675 | else if(pTilemapItem->m_Flags & TILESLAYERFLAG_SPEEDUP) |
676 | { |
677 | if(pTilemapItem->m_Version <= 2) |
678 | pTilemapItem->m_Speedup = *((const int *)(pTilemapItem) + 16); |
679 | |
680 | pTiles = std::make_shared<CLayerSpeedup>(args&: m_pEditor, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height); |
681 | MakeSpeedupLayer(pLayer: pTiles); |
682 | } |
683 | else if(pTilemapItem->m_Flags & TILESLAYERFLAG_FRONT) |
684 | { |
685 | if(pTilemapItem->m_Version <= 2) |
686 | pTilemapItem->m_Front = *((const int *)(pTilemapItem) + 17); |
687 | |
688 | pTiles = std::make_shared<CLayerFront>(args&: m_pEditor, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height); |
689 | MakeFrontLayer(pLayer: pTiles); |
690 | } |
691 | else if(pTilemapItem->m_Flags & TILESLAYERFLAG_SWITCH) |
692 | { |
693 | if(pTilemapItem->m_Version <= 2) |
694 | pTilemapItem->m_Switch = *((const int *)(pTilemapItem) + 18); |
695 | |
696 | pTiles = std::make_shared<CLayerSwitch>(args&: m_pEditor, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height); |
697 | MakeSwitchLayer(pLayer: pTiles); |
698 | } |
699 | else if(pTilemapItem->m_Flags & TILESLAYERFLAG_TUNE) |
700 | { |
701 | if(pTilemapItem->m_Version <= 2) |
702 | pTilemapItem->m_Tune = *((const int *)(pTilemapItem) + 19); |
703 | |
704 | pTiles = std::make_shared<CLayerTune>(args&: m_pEditor, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height); |
705 | MakeTuneLayer(pLayer: pTiles); |
706 | } |
707 | else |
708 | { |
709 | pTiles = std::make_shared<CLayerTiles>(args&: m_pEditor, args&: pTilemapItem->m_Width, args&: pTilemapItem->m_Height); |
710 | pTiles->m_pEditor = m_pEditor; |
711 | pTiles->m_Color = pTilemapItem->m_Color; |
712 | pTiles->m_ColorEnv = pTilemapItem->m_ColorEnv; |
713 | pTiles->m_ColorEnvOffset = pTilemapItem->m_ColorEnvOffset; |
714 | } |
715 | |
716 | pTiles->m_Flags = pLayerItem->m_Flags; |
717 | |
718 | pGroup->AddLayer(pLayer: pTiles); |
719 | pTiles->m_Image = pTilemapItem->m_Image; |
720 | pTiles->m_Game = pTilemapItem->m_Flags & TILESLAYERFLAG_GAME; |
721 | |
722 | // load layer name |
723 | if(pTilemapItem->m_Version >= 3) |
724 | IntsToStr(pInts: pTilemapItem->m_aName, NumInts: std::size(pTilemapItem->m_aName), pStr: pTiles->m_aName, StrSize: std::size(pTiles->m_aName)); |
725 | |
726 | if(pTiles->m_Tele) |
727 | { |
728 | void *pTeleData = DataFile.GetData(Index: pTilemapItem->m_Tele); |
729 | unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Tele); |
730 | if(Size >= (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTeleTile)) |
731 | { |
732 | CTeleTile *pLayerTeleTiles = std::static_pointer_cast<CLayerTele>(r: pTiles)->m_pTeleTile; |
733 | mem_copy(dest: pLayerTeleTiles, source: pTeleData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTeleTile)); |
734 | |
735 | for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++) |
736 | { |
737 | if(IsValidTeleTile(Index: pLayerTeleTiles[i].m_Type)) |
738 | pTiles->m_pTiles[i].m_Index = pLayerTeleTiles[i].m_Type; |
739 | else |
740 | pTiles->m_pTiles[i].m_Index = 0; |
741 | } |
742 | } |
743 | DataFile.UnloadData(Index: pTilemapItem->m_Tele); |
744 | } |
745 | else if(pTiles->m_Speedup) |
746 | { |
747 | void *pSpeedupData = DataFile.GetData(Index: pTilemapItem->m_Speedup); |
748 | unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Speedup); |
749 | |
750 | if(Size >= (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CSpeedupTile)) |
751 | { |
752 | CSpeedupTile *pLayerSpeedupTiles = std::static_pointer_cast<CLayerSpeedup>(r: pTiles)->m_pSpeedupTile; |
753 | mem_copy(dest: pLayerSpeedupTiles, source: pSpeedupData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CSpeedupTile)); |
754 | |
755 | for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++) |
756 | { |
757 | if(IsValidSpeedupTile(Index: pLayerSpeedupTiles[i].m_Type) && pLayerSpeedupTiles[i].m_Force > 0) |
758 | pTiles->m_pTiles[i].m_Index = pLayerSpeedupTiles[i].m_Type; |
759 | else |
760 | pTiles->m_pTiles[i].m_Index = 0; |
761 | } |
762 | } |
763 | |
764 | DataFile.UnloadData(Index: pTilemapItem->m_Speedup); |
765 | } |
766 | else if(pTiles->m_Front) |
767 | { |
768 | void *pFrontData = DataFile.GetData(Index: pTilemapItem->m_Front); |
769 | unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Front); |
770 | pTiles->ExtractTiles(TilemapItemVersion: pTilemapItem->m_Version, pSavedTiles: (CTile *)pFrontData, SavedTilesSize: Size); |
771 | DataFile.UnloadData(Index: pTilemapItem->m_Front); |
772 | } |
773 | else if(pTiles->m_Switch) |
774 | { |
775 | void *pSwitchData = DataFile.GetData(Index: pTilemapItem->m_Switch); |
776 | unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Switch); |
777 | if(Size >= (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CSwitchTile)) |
778 | { |
779 | CSwitchTile *pLayerSwitchTiles = std::static_pointer_cast<CLayerSwitch>(r: pTiles)->m_pSwitchTile; |
780 | mem_copy(dest: pLayerSwitchTiles, source: pSwitchData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CSwitchTile)); |
781 | |
782 | for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++) |
783 | { |
784 | 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))) |
785 | continue; |
786 | else if(pLayerSwitchTiles[i].m_Type >= (ENTITY_ARMOR_1 + ENTITY_OFFSET) && pLayerSwitchTiles[i].m_Type <= (ENTITY_DOOR + ENTITY_OFFSET)) |
787 | { |
788 | pTiles->m_pTiles[i].m_Index = pLayerSwitchTiles[i].m_Type; |
789 | pTiles->m_pTiles[i].m_Flags = pLayerSwitchTiles[i].m_Flags; |
790 | continue; |
791 | } |
792 | |
793 | if(IsValidSwitchTile(Index: pLayerSwitchTiles[i].m_Type)) |
794 | { |
795 | pTiles->m_pTiles[i].m_Index = pLayerSwitchTiles[i].m_Type; |
796 | pTiles->m_pTiles[i].m_Flags = pLayerSwitchTiles[i].m_Flags; |
797 | } |
798 | } |
799 | } |
800 | DataFile.UnloadData(Index: pTilemapItem->m_Switch); |
801 | } |
802 | else if(pTiles->m_Tune) |
803 | { |
804 | void *pTuneData = DataFile.GetData(Index: pTilemapItem->m_Tune); |
805 | unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Tune); |
806 | if(Size >= (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTuneTile)) |
807 | { |
808 | CTuneTile *pLayerTuneTiles = std::static_pointer_cast<CLayerTune>(r: pTiles)->m_pTuneTile; |
809 | mem_copy(dest: pLayerTuneTiles, source: pTuneData, size: (size_t)pTiles->m_Width * pTiles->m_Height * sizeof(CTuneTile)); |
810 | |
811 | for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++) |
812 | { |
813 | if(IsValidTuneTile(Index: pLayerTuneTiles[i].m_Type)) |
814 | pTiles->m_pTiles[i].m_Index = pLayerTuneTiles[i].m_Type; |
815 | else |
816 | pTiles->m_pTiles[i].m_Index = 0; |
817 | } |
818 | } |
819 | DataFile.UnloadData(Index: pTilemapItem->m_Tune); |
820 | } |
821 | else // regular tile layer or game layer |
822 | { |
823 | void *pData = DataFile.GetData(Index: pTilemapItem->m_Data); |
824 | unsigned int Size = DataFile.GetDataSize(Index: pTilemapItem->m_Data); |
825 | pTiles->ExtractTiles(TilemapItemVersion: pTilemapItem->m_Version, pSavedTiles: (CTile *)pData, SavedTilesSize: Size); |
826 | |
827 | if(pTiles->m_Game && pTilemapItem->m_Version == MakeVersion(i: 1, v: *pTilemapItem)) |
828 | { |
829 | for(int i = 0; i < pTiles->m_Width * pTiles->m_Height; i++) |
830 | { |
831 | if(pTiles->m_pTiles[i].m_Index) |
832 | pTiles->m_pTiles[i].m_Index += ENTITY_OFFSET; |
833 | } |
834 | } |
835 | DataFile.UnloadData(Index: pTilemapItem->m_Data); |
836 | } |
837 | } |
838 | else if(pLayerItem->m_Type == LAYERTYPE_QUADS) |
839 | { |
840 | const CMapItemLayerQuads *pQuadsItem = (CMapItemLayerQuads *)pLayerItem; |
841 | |
842 | std::shared_ptr<CLayerQuads> pQuads = std::make_shared<CLayerQuads>(args&: m_pEditor); |
843 | pQuads->m_Flags = pLayerItem->m_Flags; |
844 | pQuads->m_Image = pQuadsItem->m_Image; |
845 | if(pQuads->m_Image < -1 || pQuads->m_Image >= (int)m_vpImages.size()) |
846 | pQuads->m_Image = -1; |
847 | |
848 | // load layer name |
849 | if(pQuadsItem->m_Version >= 2) |
850 | IntsToStr(pInts: pQuadsItem->m_aName, NumInts: std::size(pQuadsItem->m_aName), pStr: pQuads->m_aName, StrSize: std::size(pQuads->m_aName)); |
851 | |
852 | if(pQuadsItem->m_NumQuads > 0) |
853 | { |
854 | void *pData = DataFile.GetDataSwapped(Index: pQuadsItem->m_Data); |
855 | pQuads->m_vQuads.resize(new_size: pQuadsItem->m_NumQuads); |
856 | mem_copy(dest: pQuads->m_vQuads.data(), source: pData, size: sizeof(CQuad) * pQuadsItem->m_NumQuads); |
857 | DataFile.UnloadData(Index: pQuadsItem->m_Data); |
858 | } |
859 | |
860 | pGroup->AddLayer(pLayer: pQuads); |
861 | } |
862 | else if(pLayerItem->m_Type == LAYERTYPE_SOUNDS) |
863 | { |
864 | const CMapItemLayerSounds *pSoundsItem = (CMapItemLayerSounds *)pLayerItem; |
865 | if(pSoundsItem->m_Version < 1 || pSoundsItem->m_Version > CMapItemLayerSounds::CURRENT_VERSION) |
866 | continue; |
867 | |
868 | std::shared_ptr<CLayerSounds> pSounds = std::make_shared<CLayerSounds>(args&: m_pEditor); |
869 | pSounds->m_Flags = pLayerItem->m_Flags; |
870 | pSounds->m_Sound = pSoundsItem->m_Sound; |
871 | |
872 | // validate m_Sound |
873 | if(pSounds->m_Sound < -1 || pSounds->m_Sound >= (int)m_vpSounds.size()) |
874 | pSounds->m_Sound = -1; |
875 | |
876 | // load layer name |
877 | IntsToStr(pInts: pSoundsItem->m_aName, NumInts: std::size(pSoundsItem->m_aName), pStr: pSounds->m_aName, StrSize: std::size(pSounds->m_aName)); |
878 | |
879 | // load data |
880 | if(pSoundsItem->m_NumSources > 0) |
881 | { |
882 | void *pData = DataFile.GetDataSwapped(Index: pSoundsItem->m_Data); |
883 | pSounds->m_vSources.resize(new_size: pSoundsItem->m_NumSources); |
884 | mem_copy(dest: pSounds->m_vSources.data(), source: pData, size: sizeof(CSoundSource) * pSoundsItem->m_NumSources); |
885 | DataFile.UnloadData(Index: pSoundsItem->m_Data); |
886 | } |
887 | |
888 | pGroup->AddLayer(pLayer: pSounds); |
889 | } |
890 | else if(pLayerItem->m_Type == LAYERTYPE_SOUNDS_DEPRECATED) |
891 | { |
892 | // compatibility with old sound layers |
893 | const CMapItemLayerSounds *pSoundsItem = (CMapItemLayerSounds *)pLayerItem; |
894 | if(pSoundsItem->m_Version < 1 || pSoundsItem->m_Version > CMapItemLayerSounds::CURRENT_VERSION) |
895 | continue; |
896 | |
897 | std::shared_ptr<CLayerSounds> pSounds = std::make_shared<CLayerSounds>(args&: m_pEditor); |
898 | pSounds->m_Flags = pLayerItem->m_Flags; |
899 | pSounds->m_Sound = pSoundsItem->m_Sound; |
900 | |
901 | // validate m_Sound |
902 | if(pSounds->m_Sound < -1 || pSounds->m_Sound >= (int)m_vpSounds.size()) |
903 | pSounds->m_Sound = -1; |
904 | |
905 | // load layer name |
906 | IntsToStr(pInts: pSoundsItem->m_aName, NumInts: std::size(pSoundsItem->m_aName), pStr: pSounds->m_aName, StrSize: std::size(pSounds->m_aName)); |
907 | |
908 | // load data |
909 | CSoundSource_DEPRECATED *pData = (CSoundSource_DEPRECATED *)DataFile.GetDataSwapped(Index: pSoundsItem->m_Data); |
910 | pGroup->AddLayer(pLayer: pSounds); |
911 | pSounds->m_vSources.resize(new_size: pSoundsItem->m_NumSources); |
912 | |
913 | for(int i = 0; i < pSoundsItem->m_NumSources; i++) |
914 | { |
915 | CSoundSource_DEPRECATED *pOldSource = &pData[i]; |
916 | |
917 | CSoundSource &Source = pSounds->m_vSources[i]; |
918 | Source.m_Position = pOldSource->m_Position; |
919 | Source.m_Loop = pOldSource->m_Loop; |
920 | Source.m_Pan = true; |
921 | Source.m_TimeDelay = pOldSource->m_TimeDelay; |
922 | Source.m_Falloff = 0; |
923 | |
924 | Source.m_PosEnv = pOldSource->m_PosEnv; |
925 | Source.m_PosEnvOffset = pOldSource->m_PosEnvOffset; |
926 | Source.m_SoundEnv = pOldSource->m_SoundEnv; |
927 | Source.m_SoundEnvOffset = pOldSource->m_SoundEnvOffset; |
928 | |
929 | Source.m_Shape.m_Type = CSoundShape::SHAPE_CIRCLE; |
930 | Source.m_Shape.m_Circle.m_Radius = pOldSource->m_FalloffDistance; |
931 | } |
932 | |
933 | DataFile.UnloadData(Index: pSoundsItem->m_Data); |
934 | } |
935 | } |
936 | } |
937 | } |
938 | |
939 | // load envelopes |
940 | { |
941 | const CMapBasedEnvelopePointAccess EnvelopePoints(&DataFile); |
942 | |
943 | int EnvStart, EnvNum; |
944 | DataFile.GetType(Type: MAPITEMTYPE_ENVELOPE, pStart: &EnvStart, pNum: &EnvNum); |
945 | for(int e = 0; e < EnvNum; e++) |
946 | { |
947 | CMapItemEnvelope *pItem = (CMapItemEnvelope *)DataFile.GetItem(Index: EnvStart + e); |
948 | int Channels = pItem->m_Channels; |
949 | if(Channels <= 0 || Channels == 2 || Channels > CEnvPoint::MAX_CHANNELS) |
950 | { |
951 | // Fall back to showing all channels if the number of channels is unsupported |
952 | Channels = CEnvPoint::MAX_CHANNELS; |
953 | } |
954 | if(Channels != pItem->m_Channels) |
955 | { |
956 | char aBuf[128]; |
957 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Error: Envelope %d had an invalid number of channels, %d, which was changed to %d." , e, pItem->m_Channels, Channels); |
958 | ErrorHandler(aBuf); |
959 | } |
960 | |
961 | std::shared_ptr<CEnvelope> pEnv = std::make_shared<CEnvelope>(args&: Channels); |
962 | pEnv->m_vPoints.resize(new_size: pItem->m_NumPoints); |
963 | for(int p = 0; p < pItem->m_NumPoints; p++) |
964 | { |
965 | const CEnvPoint *pPoint = EnvelopePoints.GetPoint(Index: pItem->m_StartPoint + p); |
966 | if(pPoint != nullptr) |
967 | mem_copy(dest: &pEnv->m_vPoints[p], source: pPoint, size: sizeof(CEnvPoint)); |
968 | const CEnvPointBezier *pPointBezier = EnvelopePoints.GetBezier(Index: pItem->m_StartPoint + p); |
969 | if(pPointBezier != nullptr) |
970 | mem_copy(dest: &pEnv->m_vPoints[p].m_Bezier, source: pPointBezier, size: sizeof(CEnvPointBezier)); |
971 | } |
972 | if(pItem->m_aName[0] != -1) // compatibility with old maps |
973 | IntsToStr(pInts: pItem->m_aName, NumInts: std::size(pItem->m_aName), pStr: pEnv->m_aName, StrSize: std::size(pEnv->m_aName)); |
974 | m_vpEnvelopes.push_back(x: pEnv); |
975 | if(pItem->m_Version >= CMapItemEnvelope_v2::CURRENT_VERSION) |
976 | pEnv->m_Synchronized = pItem->m_Synchronized; |
977 | } |
978 | } |
979 | |
980 | // load automapper configurations |
981 | { |
982 | int AutomapperConfigStart, AutomapperConfigNum; |
983 | DataFile.GetType(Type: MAPITEMTYPE_AUTOMAPPER_CONFIG, pStart: &AutomapperConfigStart, pNum: &AutomapperConfigNum); |
984 | for(int i = 0; i < AutomapperConfigNum; i++) |
985 | { |
986 | CMapItemAutoMapperConfig *pItem = (CMapItemAutoMapperConfig *)DataFile.GetItem(Index: AutomapperConfigStart + i); |
987 | if(pItem->m_Version == CMapItemAutoMapperConfig::CURRENT_VERSION) |
988 | { |
989 | if(pItem->m_GroupId >= 0 && (size_t)pItem->m_GroupId < m_vpGroups.size() && |
990 | pItem->m_LayerId >= 0 && (size_t)pItem->m_LayerId < m_vpGroups[pItem->m_GroupId]->m_vpLayers.size()) |
991 | { |
992 | std::shared_ptr<CLayer> pLayer = m_vpGroups[pItem->m_GroupId]->m_vpLayers[pItem->m_LayerId]; |
993 | if(pLayer->m_Type == LAYERTYPE_TILES) |
994 | { |
995 | std::shared_ptr<CLayerTiles> pTiles = std::static_pointer_cast<CLayerTiles>(r: m_vpGroups[pItem->m_GroupId]->m_vpLayers[pItem->m_LayerId]); |
996 | // only load auto mappers for tile layers (not physics layers) |
997 | if(!(pTiles->m_Game || pTiles->m_Tele || pTiles->m_Speedup || |
998 | pTiles->m_Front || pTiles->m_Switch || pTiles->m_Tune)) |
999 | { |
1000 | pTiles->m_AutoMapperConfig = pItem->m_AutomapperConfig; |
1001 | pTiles->m_Seed = pItem->m_AutomapperSeed; |
1002 | pTiles->m_AutoAutoMap = !!(pItem->m_Flags & CMapItemAutoMapperConfig::FLAG_AUTOMATIC); |
1003 | } |
1004 | } |
1005 | } |
1006 | } |
1007 | } |
1008 | } |
1009 | |
1010 | PerformSanityChecks(ErrorHandler); |
1011 | |
1012 | m_Modified = false; |
1013 | m_ModifiedAuto = false; |
1014 | m_LastModifiedTime = -1.0f; |
1015 | m_LastSaveTime = m_pEditor->Client()->GlobalTime(); |
1016 | return true; |
1017 | } |
1018 | |
1019 | void CEditorMap::PerformSanityChecks(const std::function<void(const char *pErrorMessage)> &ErrorHandler) |
1020 | { |
1021 | // Check if there are any images with a width or height that is not divisible by 16 which are |
1022 | // used in tile layers. Reset the image for these layers, to prevent crashes with some drivers. |
1023 | size_t ImageIndex = 0; |
1024 | for(const std::shared_ptr<CEditorImage> &pImage : m_vpImages) |
1025 | { |
1026 | if(pImage->m_Width % 16 != 0 || pImage->m_Height % 16 != 0) |
1027 | { |
1028 | size_t GroupIndex = 0; |
1029 | for(const std::shared_ptr<CLayerGroup> &pGroup : m_vpGroups) |
1030 | { |
1031 | size_t LayerIndex = 0; |
1032 | for(const std::shared_ptr<CLayer> &pLayer : pGroup->m_vpLayers) |
1033 | { |
1034 | if(pLayer->m_Type == LAYERTYPE_TILES) |
1035 | { |
1036 | std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: pLayer); |
1037 | if(pLayerTiles->m_Image >= 0 && (size_t)pLayerTiles->m_Image == ImageIndex) |
1038 | { |
1039 | pLayerTiles->m_Image = -1; |
1040 | char aBuf[IO_MAX_PATH_LENGTH + 128]; |
1041 | 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); |
1042 | ErrorHandler(aBuf); |
1043 | } |
1044 | } |
1045 | ++LayerIndex; |
1046 | } |
1047 | ++GroupIndex; |
1048 | } |
1049 | } |
1050 | ++ImageIndex; |
1051 | } |
1052 | } |
1053 | |