1 |
|
|
/* |
2 |
|
|
* The ManaPlus Client |
3 |
|
|
* Copyright (C) 2004-2009 The Mana World Development Team |
4 |
|
|
* Copyright (C) 2009-2010 The Mana Developers |
5 |
|
|
* Copyright (C) 2011-2019 The ManaPlus Developers |
6 |
|
|
* Copyright (C) 2019-2021 Andrei Karas |
7 |
|
|
* |
8 |
|
|
* This file is part of The ManaPlus Client. |
9 |
|
|
* |
10 |
|
|
* This program is free software; you can redistribute it and/or modify |
11 |
|
|
* it under the terms of the GNU General Public License as published by |
12 |
|
|
* the Free Software Foundation; either version 2 of the License, or |
13 |
|
|
* any later version. |
14 |
|
|
* |
15 |
|
|
* This program is distributed in the hope that it will be useful, |
16 |
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 |
|
|
* GNU General Public License for more details. |
19 |
|
|
* |
20 |
|
|
* You should have received a copy of the GNU General Public License |
21 |
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
22 |
|
|
*/ |
23 |
|
|
|
24 |
|
|
#include "resources/mapreader.h" |
25 |
|
|
|
26 |
|
|
#include "configuration.h" |
27 |
|
|
#ifdef USE_OPENGL |
28 |
|
|
#include "graphicsmanager.h" |
29 |
|
|
#endif // USE_OPENGL |
30 |
|
|
#include "main.h" |
31 |
|
|
|
32 |
|
|
#include "const/resources/map/map.h" |
33 |
|
|
|
34 |
|
|
#include "enums/resources/map/collisiontype.h" |
35 |
|
|
#include "enums/resources/map/mapitemtype.h" |
36 |
|
|
|
37 |
|
|
#include "fs/virtfs/fs.h" |
38 |
|
|
|
39 |
|
|
#include "resources/map/map.h" |
40 |
|
|
#include "resources/map/mapheights.h" |
41 |
|
|
#include "resources/map/maplayer.h" |
42 |
|
|
#include "resources/map/tileset.h" |
43 |
|
|
|
44 |
|
|
#include "resources/beingcommon.h" |
45 |
|
|
#include "resources/animation/animation.h" |
46 |
|
|
|
47 |
|
|
#include "resources/image/image.h" |
48 |
|
|
|
49 |
|
|
#ifdef USE_OPENGL |
50 |
|
|
#include "resources/db/mapdb.h" |
51 |
|
|
#include "resources/loaders/atlasloader.h" |
52 |
|
|
#include "resources/loaders/emptyatlasloader.h" |
53 |
|
|
#endif // USE_OPENGL |
54 |
|
|
|
55 |
|
|
#include "resources/map/tileanimation.h" |
56 |
|
|
|
57 |
|
|
#include "resources/loaders/imageloader.h" |
58 |
|
|
|
59 |
|
|
#include "resources/loaders/walklayerloader.h" |
60 |
|
|
|
61 |
|
|
#include "utils/base64.h" |
62 |
|
|
#include "utils/checkutils.h" |
63 |
|
|
#include "utils/delete2.h" |
64 |
|
|
#include "utils/stringmap.h" |
65 |
|
|
|
66 |
|
|
#include "utils/translation/podict.h" |
67 |
|
|
|
68 |
|
|
#include <zlib.h> |
69 |
|
|
|
70 |
|
|
#include "debug.h" |
71 |
|
|
|
72 |
|
|
typedef std::map<std::string, XmlNodePtr>::iterator LayerInfoIterator; |
73 |
|
|
typedef std::set<XML::Document*>::iterator DocIterator; |
74 |
|
|
|
75 |
|
|
#ifdef USE_OPENGL |
76 |
|
|
Resource *MapReader::mEmptyAtlas = nullptr; |
77 |
|
|
#endif // USE_OPENGL |
78 |
|
|
|
79 |
|
|
namespace |
80 |
|
|
{ |
81 |
|
1 |
std::map<std::string, XmlNodePtr> mKnownLayers; |
82 |
|
1 |
std::set<XML::Document*> mKnownDocs; |
83 |
|
|
} // namespace |
84 |
|
|
|
85 |
|
|
static int inflateMemory(unsigned char *restrict const in, |
86 |
|
|
const unsigned int inLength, |
87 |
|
|
unsigned char *&restrict out, |
88 |
|
|
unsigned int &restrict outLength); |
89 |
|
|
|
90 |
|
|
static int inflateMemory(unsigned char *restrict const in, |
91 |
|
|
const unsigned int inLength, |
92 |
|
|
unsigned char *&restrict out); |
93 |
|
|
|
94 |
|
|
static std::string resolveRelativePath(std::string base, std::string relative) |
95 |
|
|
{ |
96 |
|
|
// Remove trailing "/", if present |
97 |
|
|
size_t i = base.length(); |
98 |
|
|
if (base.at(i - 1) == '/') |
99 |
|
|
base.erase(i - 1, i); |
100 |
|
|
|
101 |
|
|
while (relative.substr(0, 3) == "../") |
102 |
|
|
{ |
103 |
|
|
relative.erase(0, 3); // Remove "../" |
104 |
|
|
if (!base.empty()) // If base is already empty, we can't trim anymore |
105 |
|
|
{ |
106 |
|
|
i = base.find_last_of('/'); |
107 |
|
|
if (i == std::string::npos) |
108 |
|
|
i = 0; |
109 |
|
|
base.erase(i, base.length()); // Remove deepest folder in base |
110 |
|
|
} |
111 |
|
|
} |
112 |
|
|
|
113 |
|
|
// Re-add trailing slash, if needed |
114 |
|
|
if (!base.empty() && base[base.length() - 1] != '/') |
115 |
|
|
base.append("/"); |
116 |
|
|
|
117 |
|
|
return base + relative; |
118 |
|
|
} |
119 |
|
|
|
120 |
|
|
/** |
121 |
|
|
* Inflates either zlib or gzip deflated memory. The inflated memory is |
122 |
|
|
* expected to be freed by the caller. |
123 |
|
|
*/ |
124 |
|
|
int inflateMemory(unsigned char *restrict const in, |
125 |
|
|
const unsigned int inLength, |
126 |
|
|
unsigned char *&restrict out, |
127 |
|
|
unsigned int &restrict outLength) |
128 |
|
|
{ |
129 |
|
|
int bufferSize = 256 * 1024; |
130 |
|
|
out = static_cast<unsigned char*>(calloc(bufferSize, 1)); |
131 |
|
|
|
132 |
|
|
z_stream strm; |
133 |
|
|
strm.zalloc = nullptr; |
134 |
|
|
strm.zfree = nullptr; |
135 |
|
|
strm.opaque = nullptr; |
136 |
|
|
strm.next_in = in; |
137 |
|
|
strm.avail_in = inLength; |
138 |
|
|
strm.next_out = out; |
139 |
|
|
strm.avail_out = bufferSize; |
140 |
|
|
|
141 |
|
|
PRAGMACLANG6GCC(GCC diagnostic push) |
142 |
|
|
PRAGMACLANG6GCC(GCC diagnostic ignored "-Wold-style-cast") |
143 |
|
|
int ret = inflateInit2(&strm, 15 + 32); |
144 |
|
|
PRAGMACLANG6GCC(GCC diagnostic pop) |
145 |
|
|
|
146 |
|
|
if (ret != Z_OK) |
147 |
|
|
return ret; |
148 |
|
|
|
149 |
|
|
do |
150 |
|
|
{ |
151 |
|
|
if (strm.next_out == nullptr) |
152 |
|
|
{ |
153 |
|
|
inflateEnd(&strm); |
154 |
|
|
return Z_MEM_ERROR; |
155 |
|
|
} |
156 |
|
|
|
157 |
|
|
ret = inflate(&strm, Z_NO_FLUSH); |
158 |
|
|
if (ret == Z_STREAM_ERROR) |
159 |
|
|
return ret; |
160 |
|
|
|
161 |
|
|
switch (ret) |
162 |
|
|
{ |
163 |
|
|
case Z_NEED_DICT: |
164 |
|
|
ret = Z_DATA_ERROR; |
165 |
|
|
A_FALLTHROUGH |
166 |
|
|
case Z_DATA_ERROR: |
167 |
|
|
case Z_MEM_ERROR: |
168 |
|
|
(void) inflateEnd(&strm); |
169 |
|
|
return ret; |
170 |
|
|
default: |
171 |
|
|
break; |
172 |
|
|
} |
173 |
|
|
|
174 |
|
|
if (ret != Z_STREAM_END) |
175 |
|
|
{ |
176 |
|
|
out = static_cast<unsigned char*>(realloc(out, bufferSize * 2)); |
177 |
|
|
|
178 |
|
|
if (out == nullptr) |
179 |
|
|
{ |
180 |
|
|
inflateEnd(&strm); |
181 |
|
|
return Z_MEM_ERROR; |
182 |
|
|
} |
183 |
|
|
|
184 |
|
|
strm.next_out = out + CAST_SIZE(bufferSize); |
185 |
|
|
strm.avail_out = bufferSize; |
186 |
|
|
bufferSize *= 2; |
187 |
|
|
} |
188 |
|
|
} |
189 |
|
|
while (ret != Z_STREAM_END); |
190 |
|
|
|
191 |
|
|
outLength = bufferSize - strm.avail_out; |
192 |
|
|
(void) inflateEnd(&strm); |
193 |
|
|
return Z_OK; |
194 |
|
|
} |
195 |
|
|
|
196 |
|
|
int inflateMemory(unsigned char *restrict const in, |
197 |
|
|
const unsigned int inLength, |
198 |
|
|
unsigned char *&restrict out) |
199 |
|
|
{ |
200 |
|
|
unsigned int outLength = 0; |
201 |
|
|
const int ret = inflateMemory(in, inLength, out, outLength); |
202 |
|
|
|
203 |
|
|
if (ret != Z_OK || (out == nullptr)) |
204 |
|
|
{ |
205 |
|
|
if (ret == Z_MEM_ERROR) |
206 |
|
|
{ |
207 |
|
|
reportAlways("Error: Out of memory while decompressing map data!") |
208 |
|
|
} |
209 |
|
|
else if (ret == Z_VERSION_ERROR) |
210 |
|
|
{ |
211 |
|
|
reportAlways("Error: Incompatible zlib version!") |
212 |
|
|
} |
213 |
|
|
else if (ret == Z_DATA_ERROR) |
214 |
|
|
{ |
215 |
|
|
reportAlways("Error: Incorrect zlib compressed data!") |
216 |
|
|
} |
217 |
|
|
else |
218 |
|
|
{ |
219 |
|
|
reportAlways("Error: Unknown error while decompressing map data!") |
220 |
|
|
} |
221 |
|
|
|
222 |
|
|
free(out); |
223 |
|
|
out = nullptr; |
224 |
|
|
outLength = 0; |
225 |
|
|
} |
226 |
|
|
|
227 |
|
|
return outLength; |
228 |
|
|
} |
229 |
|
|
|
230 |
|
|
void MapReader::addLayerToList(const std::string &fileName, |
231 |
|
|
const SkipError skipError) |
232 |
|
|
{ |
233 |
|
|
XML::Document *doc = new XML::Document(fileName, |
234 |
|
|
UseVirtFs_true, |
235 |
|
|
skipError); |
236 |
|
|
XmlNodePtrConst node = doc->rootNode(); |
237 |
|
|
if (node == nullptr) |
238 |
|
|
{ |
239 |
|
|
delete doc; |
240 |
|
|
return; |
241 |
|
|
} |
242 |
|
|
|
243 |
|
|
int cnt = 0; |
244 |
|
|
for_each_xml_child_node(childNode, node) |
245 |
|
|
{ |
246 |
|
|
if (!xmlNameEqual(childNode, "layer")) |
247 |
|
|
continue; |
248 |
|
|
std::string name = XML::getProperty(childNode, "name", ""); |
249 |
|
|
if (name.empty()) |
250 |
|
|
continue; |
251 |
|
|
name = toLower(name); |
252 |
|
|
logger->log("found patch layer: " + name); |
253 |
|
|
mKnownLayers[name] = childNode; |
254 |
|
|
mKnownDocs.insert(doc); |
255 |
|
|
cnt ++; |
256 |
|
|
} |
257 |
|
|
if (cnt == 0) |
258 |
|
|
delete doc; |
259 |
|
|
} |
260 |
|
|
|
261 |
|
|
Map *MapReader::readMap(const std::string &restrict filename, |
262 |
|
|
const std::string &restrict realFilename) |
263 |
|
|
{ |
264 |
|
|
BLOCK_START("MapReader::readMap str") |
265 |
|
|
logger->log("Attempting to read map %s", realFilename.c_str()); |
266 |
|
|
|
267 |
|
|
XML::Document doc(realFilename, UseVirtFs_true, SkipError_false); |
268 |
|
|
if (!doc.isLoaded()) |
269 |
|
|
{ |
270 |
|
|
BLOCK_END("MapReader::readMap str") |
271 |
|
|
return createEmptyMap(filename, realFilename); |
272 |
|
|
} |
273 |
|
|
|
274 |
|
|
XmlNodePtrConst node = doc.rootNode(); |
275 |
|
|
|
276 |
|
|
Map *map = nullptr; |
277 |
|
|
// Parse the inflated map data |
278 |
|
|
if (node != nullptr) |
279 |
|
|
{ |
280 |
|
|
if (!xmlNameEqual(node, "map")) |
281 |
|
|
logger->log("Error: Not a map file (%s)!", realFilename.c_str()); |
282 |
|
|
else |
283 |
|
|
map = readMap(node, realFilename); |
284 |
|
|
} |
285 |
|
|
else |
286 |
|
|
{ |
287 |
|
|
reportAlways("Error while parsing map file (%s)!", |
288 |
|
|
realFilename.c_str()) |
289 |
|
|
} |
290 |
|
|
|
291 |
|
|
if (map != nullptr) |
292 |
|
|
{ |
293 |
|
|
map->setProperty("_filename", realFilename); |
294 |
|
|
map->setProperty("_realfilename", filename); |
295 |
|
|
|
296 |
|
|
if (map->getProperty("music", std::string()).empty()) |
297 |
|
|
updateMusic(map); |
298 |
|
|
|
299 |
|
|
map->updateConditionLayers(); |
300 |
|
|
map->preCacheLayers(); |
301 |
|
|
} |
302 |
|
|
|
303 |
|
|
BLOCK_END("MapReader::readMap str") |
304 |
|
|
return map; |
305 |
|
|
} |
306 |
|
|
|
307 |
|
|
void MapReader::loadLayers(const std::string &path) |
308 |
|
|
{ |
309 |
|
|
BLOCK_START("MapReader::loadLayers") |
310 |
|
|
loadXmlDir2(path, addLayerToList, ".tmx", SkipError_false) |
311 |
|
|
BLOCK_END("MapReader::loadLayers") |
312 |
|
|
} |
313 |
|
|
|
314 |
|
|
void MapReader::unloadTempLayers() |
315 |
|
|
{ |
316 |
|
|
FOR_EACH (DocIterator, it, mKnownDocs) |
317 |
|
|
delete (*it); |
318 |
|
|
mKnownLayers.clear(); |
319 |
|
|
mKnownDocs.clear(); |
320 |
|
|
} |
321 |
|
|
|
322 |
|
|
static void loadReplaceLayer(const LayerInfoIterator &it, |
323 |
|
|
Map *const map) A_NONNULL(2); |
324 |
|
|
static void loadReplaceLayer(const LayerInfoIterator &it, |
325 |
|
|
Map *const map) |
326 |
|
|
{ |
327 |
|
|
MapReader::readLayer((*it).second, map); |
328 |
|
|
} |
329 |
|
|
|
330 |
|
|
Map *MapReader::readMap(XmlNodePtrConst node, const std::string &path) |
331 |
|
|
{ |
332 |
|
|
if (node == nullptr) |
333 |
|
|
return nullptr; |
334 |
|
|
|
335 |
|
|
BLOCK_START("MapReader::readMap xml") |
336 |
|
|
// Take the filename off the path |
337 |
|
|
const std::string pathDir = path.substr(0, path.rfind('/') + 1); |
338 |
|
|
|
339 |
|
|
const int w = XML::getProperty(node, "width", 0); |
340 |
|
|
const int h = XML::getProperty(node, "height", 0); |
341 |
|
|
const int tilew = XML::getProperty(node, "tilewidth", -1); |
342 |
|
|
const int tileh = XML::getProperty(node, "tileheight", -1); |
343 |
|
|
|
344 |
|
|
const bool showWarps = config.getBoolValue("warpParticle"); |
345 |
|
|
const std::string warpPath = pathJoin(paths.getStringValue("particles"), |
346 |
|
|
paths.getStringValue("portalEffectFile")); |
347 |
|
|
|
348 |
|
|
if (tilew < 0 || tileh < 0) |
349 |
|
|
{ |
350 |
|
|
reportAlways("MapReader: Warning: " |
351 |
|
|
"Uninitialized tile width or height value for map: %s", |
352 |
|
|
path.c_str()) |
353 |
|
|
BLOCK_END("MapReader::readMap xml") |
354 |
|
|
return nullptr; |
355 |
|
|
} |
356 |
|
|
|
357 |
|
|
logger->log("loading replace layer list"); |
358 |
|
|
loadLayers(path + "_replace.d"); |
359 |
|
|
|
360 |
|
|
Map *const map = new Map(path, |
361 |
|
|
w, h, |
362 |
|
|
tilew, tileh); |
363 |
|
|
|
364 |
|
|
map->screenResized(); |
365 |
|
|
|
366 |
|
|
const std::string fileName = path.substr(path.rfind(dirSeparator) + 1); |
367 |
|
|
map->setProperty("shortName", fileName); |
368 |
|
|
|
369 |
|
|
#ifdef USE_OPENGL |
370 |
|
|
BLOCK_START("MapReader::readMap load atlas") |
371 |
|
|
if (graphicsManager.getUseAtlases()) |
372 |
|
|
{ |
373 |
|
|
const MapInfo *const info = MapDB::getMapAtlas(fileName); |
374 |
|
|
if (info != nullptr) |
375 |
|
|
{ |
376 |
|
|
map->setAtlas(Loader::getAtlas( |
377 |
|
|
info->atlas, |
378 |
|
|
*info->files)); |
379 |
|
|
} |
380 |
|
|
else |
381 |
|
|
{ |
382 |
|
|
reportAlways("Missing atlas for map: %s", |
383 |
|
|
fileName.c_str()) |
384 |
|
|
} |
385 |
|
|
} |
386 |
|
|
BLOCK_END("MapReader::readMap load atlas") |
387 |
|
|
#endif // USE_OPENGL |
388 |
|
|
|
389 |
|
|
for_each_xml_child_node(childNode, node) |
390 |
|
|
{ |
391 |
|
|
if (xmlNameEqual(childNode, "tileset")) |
392 |
|
|
{ |
393 |
|
|
Tileset *const tileset = readTileset(childNode, pathDir, map); |
394 |
|
|
if (tileset != nullptr) |
395 |
|
|
map->addTileset(tileset); |
396 |
|
|
} |
397 |
|
|
else if (xmlNameEqual(childNode, "layer")) |
398 |
|
|
{ |
399 |
|
|
std::string name = XML::getProperty(childNode, "name", ""); |
400 |
|
|
name = toLower(name); |
401 |
|
|
LayerInfoIterator it = mKnownLayers.find(name); |
402 |
|
|
if (it == mKnownLayers.end()) |
403 |
|
|
{ |
404 |
|
|
readLayer(childNode, map); |
405 |
|
|
} |
406 |
|
|
else |
407 |
|
|
{ |
408 |
|
|
logger->log("load replace layer: " + name); |
409 |
|
|
loadReplaceLayer(it, map); |
410 |
|
|
} |
411 |
|
|
} |
412 |
|
|
else if (xmlNameEqual(childNode, "properties")) |
413 |
|
|
{ |
414 |
|
|
readProperties(childNode, map); |
415 |
|
|
map->setVersion(atoi(map->getProperty( |
416 |
|
|
"manaplus version", std::string()).c_str())); |
417 |
|
|
} |
418 |
|
|
else if (xmlNameEqual(childNode, "objectgroup")) |
419 |
|
|
{ |
420 |
|
|
// The object group offset is applied to each object individually |
421 |
|
|
const int tileOffsetX = XML::getProperty(childNode, "x", 0); |
422 |
|
|
const int tileOffsetY = XML::getProperty(childNode, "y", 0); |
423 |
|
|
const int offsetX = tileOffsetX * tilew; |
424 |
|
|
const int offsetY = tileOffsetY * tileh; |
425 |
|
|
const bool showParticles = |
426 |
|
|
config.getBoolValue("mapparticleeffects"); |
427 |
|
|
|
428 |
|
|
for_each_xml_child_node(objectNode, childNode) |
429 |
|
|
{ |
430 |
|
|
if (xmlNameEqual(objectNode, "object")) |
431 |
|
|
{ |
432 |
|
|
std::string objType = XML::getProperty( |
433 |
|
|
objectNode, "type", ""); |
434 |
|
|
|
435 |
|
|
objType = toUpper(objType); |
436 |
|
|
|
437 |
|
|
/* |
438 |
|
|
if (objType == "NPC" || |
439 |
|
|
objType == "SCRIPT") |
440 |
|
|
{ |
441 |
|
|
logger->log("hidden obj: " + objType); |
442 |
|
|
// Silently skip server-side objects. |
443 |
|
|
continue; |
444 |
|
|
} |
445 |
|
|
*/ |
446 |
|
|
|
447 |
|
|
const std::string objName = XML::getProperty( |
448 |
|
|
objectNode, "name", ""); |
449 |
|
|
const int objX = XML::getProperty(objectNode, "x", 0); |
450 |
|
|
const int objY = XML::getProperty(objectNode, "y", 0); |
451 |
|
|
const int objW = XML::getProperty(objectNode, "width", 0); |
452 |
|
|
const int objH = XML::getProperty(objectNode, "height", 0); |
453 |
|
|
|
454 |
|
|
logger->log("- Loading object name: %s type: %s at %d:%d" |
455 |
|
|
" (%dx%d)", objName.c_str(), objType.c_str(), |
456 |
|
|
objX, objY, objW, objH); |
457 |
|
|
|
458 |
|
|
if (objType == "PARTICLE_EFFECT") |
459 |
|
|
{ |
460 |
|
|
if (objName.empty()) |
461 |
|
|
{ |
462 |
|
|
logger->log1(" Warning: No particle file given"); |
463 |
|
|
continue; |
464 |
|
|
} |
465 |
|
|
|
466 |
|
|
if (showParticles) |
467 |
|
|
{ |
468 |
|
|
map->addParticleEffect(objName, |
469 |
|
|
objX + offsetX, |
470 |
|
|
objY + offsetY, |
471 |
|
|
objW, |
472 |
|
|
objH); |
473 |
|
|
} |
474 |
|
|
else |
475 |
|
|
{ |
476 |
|
|
logger->log("Ignore particle effect: " + objName); |
477 |
|
|
} |
478 |
|
|
} |
479 |
|
|
else if (objType == "WARP") |
480 |
|
|
{ |
481 |
|
|
if (showWarps) |
482 |
|
|
{ |
483 |
|
|
map->addParticleEffect(warpPath, |
484 |
|
|
objX, objY, objW, objH); |
485 |
|
|
} |
486 |
|
|
map->addPortal(objName, MapItemType::PORTAL, |
487 |
|
|
objX, objY, objW, objH); |
488 |
|
|
} |
489 |
|
|
else if (objType == "SPAWN") |
490 |
|
|
{ |
491 |
|
|
// TRANSLATORS: spawn name |
492 |
|
|
// map->addPortal(_("Spawn: ") + objName, |
493 |
|
|
// MapItemType::PORTAL, |
494 |
|
|
// objX, objY, objW, objH); |
495 |
|
|
} |
496 |
|
|
else if (objType == "MUSIC") |
497 |
|
|
{ |
498 |
|
|
map->addRange(objName, MapItemType::MUSIC, |
499 |
|
|
objX, objY, objW, objH); |
500 |
|
|
} |
501 |
|
|
else |
502 |
|
|
{ |
503 |
|
|
logger->log1(" Warning: Unknown object type"); |
504 |
|
|
} |
505 |
|
|
} |
506 |
|
|
} |
507 |
|
|
} |
508 |
|
|
} |
509 |
|
|
|
510 |
|
|
map->initializeAmbientLayers(); |
511 |
|
|
map->clearIndexedTilesets(); |
512 |
|
|
map->setActorsFix(0, |
513 |
|
|
atoi(map->getProperty("actorsfix", std::string()).c_str())); |
514 |
|
|
map->reduce(); |
515 |
|
|
map->setWalkLayer(Loader::getWalkLayer(fileName, map)); |
516 |
|
|
unloadTempLayers(); |
517 |
|
|
map->updateDrawLayersList(); |
518 |
|
|
BLOCK_END("MapReader::readMap xml") |
519 |
|
|
return map; |
520 |
|
|
} |
521 |
|
|
|
522 |
|
|
void MapReader::readProperties(XmlNodeConstPtrConst node, |
523 |
|
|
Properties *const props) |
524 |
|
|
{ |
525 |
|
|
BLOCK_START("MapReader::readProperties") |
526 |
|
|
if (node == nullptr) |
527 |
|
|
{ |
528 |
|
|
BLOCK_END("MapReader::readProperties") |
529 |
|
|
return; |
530 |
|
|
} |
531 |
|
|
|
532 |
|
|
for_each_xml_child_node(childNode, node) |
533 |
|
|
{ |
534 |
|
|
if (!xmlNameEqual(childNode, "property")) |
535 |
|
|
continue; |
536 |
|
|
|
537 |
|
|
// Example: <property name="name" value="value"/> |
538 |
|
|
const std::string name = XML::getProperty(childNode, "name", ""); |
539 |
|
|
const std::string value = XML::getProperty(childNode, "value", ""); |
540 |
|
|
|
541 |
|
|
if (!name.empty() && !value.empty()) |
542 |
|
|
{ |
543 |
|
|
if (name == "name") |
544 |
|
|
props->setProperty(name, translator->getStr(value)); |
545 |
|
|
else |
546 |
|
|
props->setProperty(name, value); |
547 |
|
|
} |
548 |
|
|
} |
549 |
|
|
BLOCK_END("MapReader::readProperties") |
550 |
|
|
} |
551 |
|
|
|
552 |
|
|
inline static void setTile(Map *const map, |
553 |
|
|
MapLayer *const layer, |
554 |
|
|
const MapLayerTypeT &layerType, |
555 |
|
|
MapHeights *const heights, |
556 |
|
|
const int x, const int y, |
557 |
|
|
const int gid) A_NONNULL(1); |
558 |
|
|
|
559 |
|
|
inline static void setTile(Map *const map, |
560 |
|
|
MapLayer *const layer, |
561 |
|
|
const MapLayerTypeT &layerType, |
562 |
|
|
MapHeights *const heights, |
563 |
|
|
const int x, const int y, |
564 |
|
|
const int gid) |
565 |
|
|
{ |
566 |
|
|
const Tileset * const set = map->getTilesetWithGid(gid); |
567 |
|
|
switch (layerType) |
568 |
|
|
{ |
569 |
|
|
case MapLayerType::TILES: |
570 |
|
|
{ |
571 |
|
|
if (layer == nullptr) |
572 |
|
|
break; |
573 |
|
|
if (set != nullptr && |
574 |
|
|
!set->isEmpty()) |
575 |
|
|
{ |
576 |
|
|
Image *const img = set->get(gid - set->getFirstGid()); |
577 |
|
|
layer->setTile(x, y, img); |
578 |
|
|
} |
579 |
|
|
else |
580 |
|
|
{ |
581 |
|
|
layer->setTile(x, y, nullptr); |
582 |
|
|
} |
583 |
|
|
break; |
584 |
|
|
} |
585 |
|
|
|
586 |
|
|
case MapLayerType::COLLISION: |
587 |
|
|
{ |
588 |
|
|
if (set != nullptr) |
589 |
|
|
{ |
590 |
|
|
if (map->getVersion() >= 1) |
591 |
|
|
{ |
592 |
|
|
const int collisionId = gid - set->getFirstGid(); |
593 |
|
|
CollisionTypeT type; |
594 |
|
|
if (collisionId < 0 || |
595 |
|
|
collisionId >= CAST_S32(CollisionType::COLLISION_MAX)) |
596 |
|
|
{ |
597 |
|
|
type = CollisionType::COLLISION_EMPTY; |
598 |
|
|
} |
599 |
|
|
else |
600 |
|
|
{ |
601 |
|
|
type = static_cast<CollisionTypeT>(collisionId); |
602 |
|
|
} |
603 |
|
|
switch (type) |
604 |
|
|
{ |
605 |
|
|
case CollisionType::COLLISION_EMPTY: |
606 |
|
|
map->addBlockMask(x, y, BlockType::GROUND); |
607 |
|
|
break; |
608 |
|
|
case CollisionType::COLLISION_WALL: |
609 |
|
|
map->addBlockMask(x, y, BlockType::WALL); |
610 |
|
|
break; |
611 |
|
|
case CollisionType::COLLISION_AIR: |
612 |
|
|
map->addBlockMask(x, y, BlockType::AIR); |
613 |
|
|
break; |
614 |
|
|
case CollisionType::COLLISION_WATER: |
615 |
|
|
map->addBlockMask(x, y, BlockType::WATER); |
616 |
|
|
break; |
617 |
|
|
case CollisionType::COLLISION_GROUNDTOP: |
618 |
|
|
map->addBlockMask(x, y, BlockType::GROUNDTOP); |
619 |
|
|
break; |
620 |
|
|
case CollisionType::COLLISION_PLAYER_WALL: |
621 |
|
|
map->addBlockMask(x, y, BlockType::PLAYERWALL); |
622 |
|
|
break; |
623 |
|
|
case CollisionType::COLLISION_MONSTER_WALL: |
624 |
|
|
map->addBlockMask(x, y, BlockType::MONSTERWALL); |
625 |
|
|
break; |
626 |
|
|
case CollisionType::COLLISION_MAX: |
627 |
|
|
default: |
628 |
|
|
break; |
629 |
|
|
} |
630 |
|
|
} |
631 |
|
|
else |
632 |
|
|
{ |
633 |
|
|
if (gid - set->getFirstGid() != 0) |
634 |
|
|
map->addBlockMask(x, y, BlockType::WALL); |
635 |
|
|
} |
636 |
|
|
} |
637 |
|
|
break; |
638 |
|
|
} |
639 |
|
|
|
640 |
|
|
case MapLayerType::HEIGHTS: |
641 |
|
|
{ |
642 |
|
|
if (set == nullptr || heights == nullptr) |
643 |
|
|
break; |
644 |
|
|
if (map->getVersion() >= 2) |
645 |
|
|
{ |
646 |
|
|
heights->setHeight(x, y, CAST_U8( |
647 |
|
|
gid - set->getFirstGid() + 1)); |
648 |
|
|
} |
649 |
|
|
else |
650 |
|
|
{ |
651 |
|
|
Image *const img = set->get(gid - set->getFirstGid()); |
652 |
|
|
if (layer != nullptr) |
653 |
|
|
layer->setTile(x, y, img); |
654 |
|
|
} |
655 |
|
|
break; |
656 |
|
|
} |
657 |
|
|
|
658 |
|
|
default: |
659 |
|
|
case MapLayerType::ACTIONS: |
660 |
|
|
break; |
661 |
|
|
} |
662 |
|
|
} |
663 |
|
|
|
664 |
|
|
bool MapReader::readBase64Layer(XmlNodeConstPtrConst childNode, |
665 |
|
|
Map *const map, |
666 |
|
|
MapLayer *const layer, |
667 |
|
|
const MapLayerTypeT &layerType, |
668 |
|
|
MapHeights *const heights, |
669 |
|
|
const std::string &compression, |
670 |
|
|
int &restrict x, int &restrict y, |
671 |
|
|
const int w, const int h) |
672 |
|
|
{ |
673 |
|
|
if (childNode == nullptr) |
674 |
|
|
return false; |
675 |
|
|
|
676 |
|
|
if (!compression.empty() && compression != "gzip" |
677 |
|
|
&& compression != "zlib") |
678 |
|
|
{ |
679 |
|
|
reportAlways("Warning: only gzip and zlib layer" |
680 |
|
|
" compression supported!") |
681 |
|
|
return false; |
682 |
|
|
} |
683 |
|
|
|
684 |
|
|
// Read base64 encoded map file |
685 |
|
|
if (!XmlHaveChildContent(childNode)) |
686 |
|
|
return true; |
687 |
|
|
|
688 |
|
|
const size_t len = strlen(XmlChildContent(childNode)) + 1; |
689 |
|
|
unsigned char *charData = new unsigned char[len + 1]; |
690 |
|
|
const char *const xmlChars = XmlChildContent(childNode); |
691 |
|
|
const char *charStart = reinterpret_cast<const char*>(xmlChars); |
692 |
|
|
if (charStart == nullptr) |
693 |
|
|
{ |
694 |
|
|
delete [] charData; |
695 |
|
|
return false; |
696 |
|
|
} |
697 |
|
|
|
698 |
|
|
unsigned char *charIndex = charData; |
699 |
|
|
|
700 |
|
|
while (*charStart != 0) |
701 |
|
|
{ |
702 |
|
|
if (*charStart != ' ' && |
703 |
|
|
*charStart != '\t' && |
704 |
|
|
*charStart != '\n') |
705 |
|
|
{ |
706 |
|
|
*charIndex = *charStart; |
707 |
|
|
charIndex++; |
708 |
|
|
} |
709 |
|
|
charStart++; |
710 |
|
|
} |
711 |
|
|
*charIndex = '\0'; |
712 |
|
|
|
713 |
|
|
int binLen; |
714 |
|
|
unsigned char *binData = php3_base64_decode(charData, |
715 |
|
|
CAST_S32(strlen(reinterpret_cast<char*>( |
716 |
|
|
charData))), &binLen); |
717 |
|
|
|
718 |
|
|
delete [] charData; |
719 |
|
|
// XmlFree(const_cast<char*>(xmlChars)); |
720 |
|
|
|
721 |
|
|
if (binData != nullptr) |
722 |
|
|
{ |
723 |
|
|
if (compression == "gzip" || compression == "zlib") |
724 |
|
|
{ |
725 |
|
|
// Inflate the gzipped layer data |
726 |
|
|
unsigned char *inflated = nullptr; |
727 |
|
|
const unsigned int inflatedSize = |
728 |
|
|
inflateMemory(binData, binLen, inflated); |
729 |
|
|
|
730 |
|
|
free(binData); |
731 |
|
|
binData = inflated; |
732 |
|
|
binLen = inflatedSize; |
733 |
|
|
|
734 |
|
|
if (inflated == nullptr) |
735 |
|
|
{ |
736 |
|
|
reportAlways("Error: Could not decompress layer!") |
737 |
|
|
return false; |
738 |
|
|
} |
739 |
|
|
} |
740 |
|
|
|
741 |
|
|
const std::map<int, TileAnimation*> &tileAnimations |
742 |
|
|
= map->getTileAnimations(); |
743 |
|
|
|
744 |
|
|
const bool hasAnimations = !tileAnimations.empty(); |
745 |
|
|
if (hasAnimations) |
746 |
|
|
{ |
747 |
|
|
for (int i = 0; i < binLen - 3; i += 4) |
748 |
|
|
{ |
749 |
|
|
const int gid = binData[i] | |
750 |
|
|
binData[i + 1] << 8 | |
751 |
|
|
binData[i + 2] << 16 | |
752 |
|
|
binData[i + 3] << 24; |
753 |
|
|
|
754 |
|
|
setTile(map, layer, layerType, heights, x, y, gid); |
755 |
|
|
TileAnimationMapCIter it = tileAnimations.find(gid); |
756 |
|
|
if (it != tileAnimations.end()) |
757 |
|
|
{ |
758 |
|
|
TileAnimation *const ani = it->second; |
759 |
|
|
if (ani != nullptr) |
760 |
|
|
ani->addAffectedTile(layer, x + y * w); |
761 |
|
|
} |
762 |
|
|
|
763 |
|
|
x++; |
764 |
|
|
if (x == w) |
765 |
|
|
{ |
766 |
|
|
x = 0; y++; |
767 |
|
|
|
768 |
|
|
// When we're done, don't crash on too much data |
769 |
|
|
if (y == h) |
770 |
|
|
break; |
771 |
|
|
} |
772 |
|
|
} |
773 |
|
|
} |
774 |
|
|
else |
775 |
|
|
{ |
776 |
|
|
for (int i = 0; i < binLen - 3; i += 4) |
777 |
|
|
{ |
778 |
|
|
const int gid = binData[i] | |
779 |
|
|
binData[i + 1] << 8 | |
780 |
|
|
binData[i + 2] << 16 | |
781 |
|
|
binData[i + 3] << 24; |
782 |
|
|
|
783 |
|
|
setTile(map, layer, layerType, heights, x, y, gid); |
784 |
|
|
|
785 |
|
|
x++; |
786 |
|
|
if (x == w) |
787 |
|
|
{ |
788 |
|
|
x = 0; y++; |
789 |
|
|
|
790 |
|
|
// When we're done, don't crash on too much data |
791 |
|
|
if (y == h) |
792 |
|
|
break; |
793 |
|
|
} |
794 |
|
|
} |
795 |
|
|
} |
796 |
|
|
free(binData); |
797 |
|
|
} |
798 |
|
|
return true; |
799 |
|
|
} |
800 |
|
|
|
801 |
|
|
bool MapReader::readCsvLayer(XmlNodeConstPtrConst childNode, |
802 |
|
|
Map *const map, |
803 |
|
|
MapLayer *const layer, |
804 |
|
|
const MapLayerTypeT &layerType, |
805 |
|
|
MapHeights *const heights, |
806 |
|
|
int &restrict x, int &restrict y, |
807 |
|
|
const int w, const int h) |
808 |
|
|
{ |
809 |
|
|
if (childNode == nullptr) |
810 |
|
|
return false; |
811 |
|
|
|
812 |
|
|
if (!XmlHaveChildContent(childNode)) |
813 |
|
|
return true; |
814 |
|
|
|
815 |
|
|
const char *const xmlChars = XmlChildContent(childNode); |
816 |
|
|
const char *const data = reinterpret_cast<const char*>(xmlChars); |
817 |
|
|
if (data == nullptr) |
818 |
|
|
return false; |
819 |
|
|
|
820 |
|
|
std::string csv(data); |
821 |
|
|
size_t oldPos = 0; |
822 |
|
|
|
823 |
|
|
const std::map<int, TileAnimation*> &tileAnimations |
824 |
|
|
= map->getTileAnimations(); |
825 |
|
|
const bool hasAnimations = !tileAnimations.empty(); |
826 |
|
|
|
827 |
|
|
if (hasAnimations) |
828 |
|
|
{ |
829 |
|
|
while (oldPos != std::string::npos) |
830 |
|
|
{ |
831 |
|
|
const size_t pos = csv.find_first_of(',', oldPos); |
832 |
|
|
if (pos == std::string::npos) |
833 |
|
|
return false; |
834 |
|
|
|
835 |
|
|
const int gid = atoi(csv.substr(oldPos, pos - oldPos).c_str()); |
836 |
|
|
setTile(map, layer, layerType, heights, x, y, gid); |
837 |
|
|
TileAnimationMapCIter it = tileAnimations.find(gid); |
838 |
|
|
if (it != tileAnimations.end()) |
839 |
|
|
{ |
840 |
|
|
TileAnimation *const ani = it->second; |
841 |
|
|
if (ani != nullptr) |
842 |
|
|
ani->addAffectedTile(layer, x + y * w); |
843 |
|
|
} |
844 |
|
|
|
845 |
|
|
x++; |
846 |
|
|
if (x == w) |
847 |
|
|
{ |
848 |
|
|
x = 0; y++; |
849 |
|
|
|
850 |
|
|
// When we're done, don't crash on too much data |
851 |
|
|
if (y == h) |
852 |
|
|
return false; |
853 |
|
|
} |
854 |
|
|
oldPos = pos + 1; |
855 |
|
|
} |
856 |
|
|
} |
857 |
|
|
else |
858 |
|
|
{ |
859 |
|
|
while (oldPos != std::string::npos) |
860 |
|
|
{ |
861 |
|
|
const size_t pos = csv.find_first_of(',', oldPos); |
862 |
|
|
if (pos == std::string::npos) |
863 |
|
|
return false; |
864 |
|
|
|
865 |
|
|
const int gid = atoi(csv.substr(oldPos, pos - oldPos).c_str()); |
866 |
|
|
setTile(map, layer, layerType, heights, x, y, gid); |
867 |
|
|
|
868 |
|
|
x++; |
869 |
|
|
if (x == w) |
870 |
|
|
{ |
871 |
|
|
x = 0; y++; |
872 |
|
|
|
873 |
|
|
// When we're done, don't crash on too much data |
874 |
|
|
if (y == h) |
875 |
|
|
return false; |
876 |
|
|
} |
877 |
|
|
oldPos = pos + 1; |
878 |
|
|
} |
879 |
|
|
} |
880 |
|
|
return true; |
881 |
|
|
} |
882 |
|
|
|
883 |
|
|
void MapReader::readLayer(XmlNodeConstPtr node, Map *const map) |
884 |
|
|
{ |
885 |
|
|
if (node == nullptr) |
886 |
|
|
return; |
887 |
|
|
|
888 |
|
|
// Layers are not necessarily the same size as the map |
889 |
|
|
const int w = XML::getProperty(node, "width", map->getWidth()); |
890 |
|
|
const int h = XML::getProperty(node, "height", map->getHeight()); |
891 |
|
|
const int offsetX = XML::getProperty(node, "x", 0); |
892 |
|
|
const int offsetY = XML::getProperty(node, "y", 0); |
893 |
|
|
std::string name = XML::getProperty(node, "name", ""); |
894 |
|
|
name = toLower(name); |
895 |
|
|
|
896 |
|
|
const bool isFringeLayer = (name.substr(0, 6) == "fringe"); |
897 |
|
|
const bool isCollisionLayer = (name.substr(0, 9) == "collision"); |
898 |
|
|
const bool isHeightLayer = (name.substr(0, 7) == "heights"); |
899 |
|
|
const bool isActionsLayer = (name.substr(0, 7) == "actions"); |
900 |
|
|
int mask = 1; |
901 |
|
|
int tileCondition = -1; |
902 |
|
|
int conditionLayer = 0; |
903 |
|
|
|
904 |
|
|
MapLayerTypeT layerType = MapLayerType::TILES; |
905 |
|
|
if (isCollisionLayer) |
906 |
|
|
layerType = MapLayerType::COLLISION; |
907 |
|
|
else if (isHeightLayer) |
908 |
|
|
layerType = MapLayerType::HEIGHTS; |
909 |
|
|
else if (isActionsLayer) |
910 |
|
|
layerType = MapLayerType::ACTIONS; |
911 |
|
|
|
912 |
|
|
map->indexTilesets(); |
913 |
|
|
|
914 |
|
|
MapLayer *layer = nullptr; |
915 |
|
|
MapHeights *heights = nullptr; |
916 |
|
|
|
917 |
|
|
logger->log("- Loading layer \"%s\"", name.c_str()); |
918 |
|
|
int x = 0; |
919 |
|
|
int y = 0; |
920 |
|
|
|
921 |
|
|
// Load the tile data |
922 |
|
|
for_each_xml_child_node(childNode, node) |
923 |
|
|
{ |
924 |
|
|
if (xmlNameEqual(childNode, "properties")) |
925 |
|
|
{ |
926 |
|
|
for_each_xml_child_node(prop, childNode) |
927 |
|
|
{ |
928 |
|
|
if (!xmlNameEqual(prop, "property")) |
929 |
|
|
continue; |
930 |
|
|
const std::string pname = XML::getProperty(prop, "name", ""); |
931 |
|
|
const std::string value = XML::getProperty(prop, "value", ""); |
932 |
|
|
// ignoring any layer if property Hidden is 1 |
933 |
|
|
if (pname == "Hidden") |
934 |
|
|
{ |
935 |
|
|
if (value == "1") |
936 |
|
|
return; |
937 |
|
|
} |
938 |
|
|
else if (pname == "Version") |
939 |
|
|
{ |
940 |
|
|
if (value > CHECK_VERSION) |
941 |
|
|
return; |
942 |
|
|
} |
943 |
|
|
else if (pname == "NotVersion") |
944 |
|
|
{ |
945 |
|
|
if (value <= CHECK_VERSION) |
946 |
|
|
return; |
947 |
|
|
} |
948 |
|
|
else if (pname == "Mask") |
949 |
|
|
{ |
950 |
|
|
mask = atoi(value.c_str()); |
951 |
|
|
} |
952 |
|
|
else if (pname == "TileCondition") |
953 |
|
|
{ |
954 |
|
|
tileCondition = atoi(value.c_str()); |
955 |
|
|
} |
956 |
|
|
else if (pname == "ConditionLayer") |
957 |
|
|
{ |
958 |
|
|
conditionLayer = atoi(value.c_str()); |
959 |
|
|
} |
960 |
|
|
else if (pname == "SideView") |
961 |
|
|
{ |
962 |
|
|
if (value != "down") |
963 |
|
|
return; |
964 |
|
|
} |
965 |
|
|
} |
966 |
|
|
} |
967 |
|
|
|
968 |
|
|
if (!xmlNameEqual(childNode, "data")) |
969 |
|
|
continue; |
970 |
|
|
|
971 |
|
|
// Disable for future usage "TileCondition" attribute |
972 |
|
|
// if already set ConditionLayer to non zero |
973 |
|
|
if (conditionLayer != 0) |
974 |
|
|
tileCondition = -1; |
975 |
|
|
|
976 |
|
|
switch (layerType) |
977 |
|
|
{ |
978 |
|
|
case MapLayerType::TILES: |
979 |
|
|
{ |
980 |
|
|
layer = new MapLayer(name, |
981 |
|
|
offsetX, offsetY, |
982 |
|
|
w, h, |
983 |
|
|
isFringeLayer, |
984 |
|
|
mask, |
985 |
|
|
tileCondition); |
986 |
|
|
map->addLayer(layer); |
987 |
|
|
break; |
988 |
|
|
} |
989 |
|
|
case MapLayerType::HEIGHTS: |
990 |
|
|
{ |
991 |
|
|
heights = new MapHeights(w, h); |
992 |
|
|
map->addHeights(heights); |
993 |
|
|
break; |
994 |
|
|
} |
995 |
|
|
default: |
996 |
|
|
case MapLayerType::ACTIONS: |
997 |
|
|
case MapLayerType::COLLISION: |
998 |
|
|
break; |
999 |
|
|
} |
1000 |
|
|
|
1001 |
|
|
const std::string encoding = |
1002 |
|
|
XML::getProperty(childNode, "encoding", ""); |
1003 |
|
|
const std::string compression = |
1004 |
|
|
XML::getProperty(childNode, "compression", ""); |
1005 |
|
|
|
1006 |
|
|
if (encoding == "base64") |
1007 |
|
|
{ |
1008 |
|
|
if (readBase64Layer(childNode, map, layer, layerType, |
1009 |
|
|
heights, compression, x, y, w, h)) |
1010 |
|
|
{ |
1011 |
|
|
continue; |
1012 |
|
|
} |
1013 |
|
|
else |
1014 |
|
|
{ |
1015 |
|
|
return; |
1016 |
|
|
} |
1017 |
|
|
} |
1018 |
|
|
else if (encoding == "csv") |
1019 |
|
|
{ |
1020 |
|
|
if (readCsvLayer(childNode, map, layer, layerType, |
1021 |
|
|
heights, x, y, w, h)) |
1022 |
|
|
{ |
1023 |
|
|
continue; |
1024 |
|
|
} |
1025 |
|
|
else |
1026 |
|
|
{ |
1027 |
|
|
return; |
1028 |
|
|
} |
1029 |
|
|
} |
1030 |
|
|
else |
1031 |
|
|
{ |
1032 |
|
|
const std::map<int, TileAnimation*> &tileAnimations |
1033 |
|
|
= map->getTileAnimations(); |
1034 |
|
|
const bool hasAnimations = !tileAnimations.empty(); |
1035 |
|
|
|
1036 |
|
|
// Read plain XML map file |
1037 |
|
|
for_each_xml_child_node(childNode2, childNode) |
1038 |
|
|
{ |
1039 |
|
|
if (!xmlNameEqual(childNode2, "tile")) |
1040 |
|
|
continue; |
1041 |
|
|
|
1042 |
|
|
const int gid = XML::getProperty(childNode2, "gid", -1); |
1043 |
|
|
setTile(map, layer, layerType, heights, x, y, gid); |
1044 |
|
|
if (hasAnimations) |
1045 |
|
|
{ |
1046 |
|
|
TileAnimationMapCIter it = tileAnimations.find(gid); |
1047 |
|
|
if (it != tileAnimations.end()) |
1048 |
|
|
{ |
1049 |
|
|
TileAnimation *const ani = it->second; |
1050 |
|
|
if (ani != nullptr) |
1051 |
|
|
ani->addAffectedTile(layer, x + y * w); |
1052 |
|
|
} |
1053 |
|
|
} |
1054 |
|
|
|
1055 |
|
|
x++; |
1056 |
|
|
if (x == w) |
1057 |
|
|
{ |
1058 |
|
|
x = 0; y++; |
1059 |
|
|
if (y >= h) |
1060 |
|
|
break; |
1061 |
|
|
} |
1062 |
|
|
} |
1063 |
|
|
} |
1064 |
|
|
|
1065 |
|
|
if (y < h) |
1066 |
|
|
std::cerr << "TOO SMALL!\n"; |
1067 |
|
|
if (x != 0) |
1068 |
|
|
std::cerr << "TOO SMALL!\n"; |
1069 |
|
|
|
1070 |
|
|
// There can be only one data element |
1071 |
|
|
break; |
1072 |
|
|
} |
1073 |
|
|
} |
1074 |
|
|
|
1075 |
|
|
Tileset *MapReader::readTileset(XmlNodePtr node, |
1076 |
|
|
const std::string &path, |
1077 |
|
|
Map *const map) |
1078 |
|
|
{ |
1079 |
|
|
BLOCK_START("MapReader::readTileset") |
1080 |
|
|
if (node == nullptr) |
1081 |
|
|
{ |
1082 |
|
|
BLOCK_END("MapReader::readTileset") |
1083 |
|
|
return nullptr; |
1084 |
|
|
} |
1085 |
|
|
|
1086 |
|
|
const int firstGid = XML::getProperty(node, "firstgid", 0); |
1087 |
|
|
const int margin = XML::getProperty(node, "margin", 0); |
1088 |
|
|
const int spacing = XML::getProperty(node, "spacing", 0); |
1089 |
|
|
XML::Document* doc = nullptr; |
1090 |
|
|
Tileset *set = nullptr; |
1091 |
|
|
std::string pathDir(path); |
1092 |
|
|
std::map<std::string, std::string> props; |
1093 |
|
|
|
1094 |
|
|
if (XmlHasProp(node, "source")) |
1095 |
|
|
{ |
1096 |
|
|
std::string filename = XML::getProperty(node, "source", ""); |
1097 |
|
|
filename = resolveRelativePath(path, filename); |
1098 |
|
|
|
1099 |
|
|
doc = new XML::Document(filename, UseVirtFs_true, SkipError_false); |
1100 |
|
|
node = doc->rootNode(); |
1101 |
|
|
if (node == nullptr) |
1102 |
|
|
{ |
1103 |
|
|
delete doc; |
1104 |
|
|
BLOCK_END("MapReader::readTileset") |
1105 |
|
|
return nullptr; |
1106 |
|
|
} |
1107 |
|
|
|
1108 |
|
|
// Reset path to be realtive to the tsx file |
1109 |
|
|
pathDir = filename.substr(0, filename.rfind('/') + 1); |
1110 |
|
|
} |
1111 |
|
|
|
1112 |
|
|
const int tw = XML::getProperty(node, "tilewidth", map->getTileWidth()); |
1113 |
|
|
const int th = XML::getProperty(node, "tileheight", map->getTileHeight()); |
1114 |
|
|
|
1115 |
|
|
for_each_xml_child_node(childNode, node) |
1116 |
|
|
{ |
1117 |
|
|
if (xmlNameEqual(childNode, "image")) |
1118 |
|
|
{ |
1119 |
|
|
// ignore second other <image> tags in tileset |
1120 |
|
|
if (set != nullptr) |
1121 |
|
|
continue; |
1122 |
|
|
|
1123 |
|
|
const std::string source = XML::getProperty( |
1124 |
|
|
childNode, "source", ""); |
1125 |
|
|
|
1126 |
|
|
if (!source.empty()) |
1127 |
|
|
{ |
1128 |
|
|
const std::string sourceResolved = resolveRelativePath(pathDir, |
1129 |
|
|
source); |
1130 |
|
|
|
1131 |
|
|
Image *const tilebmp = Loader::getImage(sourceResolved); |
1132 |
|
|
|
1133 |
|
|
if (tilebmp != nullptr) |
1134 |
|
|
{ |
1135 |
|
|
set = new Tileset(tilebmp, |
1136 |
|
|
tw, th, |
1137 |
|
|
firstGid, |
1138 |
|
|
margin, |
1139 |
|
|
spacing); |
1140 |
|
|
tilebmp->decRef(); |
1141 |
|
|
#ifdef USE_OPENGL |
1142 |
|
|
if (MapDB::isEmptyTileset(sourceResolved)) |
1143 |
|
|
set->setEmpty(true); |
1144 |
|
|
if (tilebmp->getType() == ImageType::Image && |
1145 |
|
|
map->haveAtlas() == true && |
1146 |
|
|
graphicsManager.getUseAtlases()) |
1147 |
|
|
{ |
1148 |
|
|
reportAlways("Error: image '%s' not present in atlas", |
1149 |
|
|
source.c_str()) |
1150 |
|
|
} |
1151 |
|
|
#endif // USE_OPENGL |
1152 |
|
|
} |
1153 |
|
|
else |
1154 |
|
|
{ |
1155 |
|
|
reportAlways("Error: Failed to load tileset (%s)", |
1156 |
|
|
source.c_str()) |
1157 |
|
|
} |
1158 |
|
|
} |
1159 |
|
|
} |
1160 |
|
|
else if (xmlNameEqual(childNode, "properties")) |
1161 |
|
|
{ |
1162 |
|
|
for_each_xml_child_node(propertyNode, childNode) |
1163 |
|
|
{ |
1164 |
|
|
if (!xmlNameEqual(propertyNode, "property")) |
1165 |
|
|
continue; |
1166 |
|
|
const std::string name = XML::getProperty( |
1167 |
|
|
propertyNode, "name", ""); |
1168 |
|
|
if (!name.empty()) |
1169 |
|
|
props[name] = XML::getProperty(propertyNode, "value", ""); |
1170 |
|
|
} |
1171 |
|
|
} |
1172 |
|
|
else if (xmlNameEqual(childNode, "tile")) |
1173 |
|
|
{ |
1174 |
|
|
bool haveAnimation(false); |
1175 |
|
|
|
1176 |
|
|
for_each_xml_child_node(tileNode, childNode) |
1177 |
|
|
{ |
1178 |
|
|
const bool isProps = xmlNameEqual(tileNode, "properties"); |
1179 |
|
|
const bool isAnim = xmlNameEqual(tileNode, "animation"); |
1180 |
|
|
if (!isProps && !isAnim) |
1181 |
|
|
continue; |
1182 |
|
|
|
1183 |
|
|
const int tileGID = firstGid + XML::getProperty( |
1184 |
|
|
childNode, "id", 0); |
1185 |
|
|
|
1186 |
|
|
Animation *ani = new Animation("from map"); |
1187 |
|
|
|
1188 |
|
|
if (isProps) |
1189 |
|
|
{ |
1190 |
|
|
// read tile properties to a map for simpler handling |
1191 |
|
|
StringIntMap tileProperties; |
1192 |
|
|
for_each_xml_child_node(propertyNode, tileNode) |
1193 |
|
|
{ |
1194 |
|
|
if (!xmlNameEqual(propertyNode, "property")) |
1195 |
|
|
continue; |
1196 |
|
|
|
1197 |
|
|
haveAnimation = true; |
1198 |
|
|
const std::string name = XML::getProperty( |
1199 |
|
|
propertyNode, "name", ""); |
1200 |
|
|
if (!name.empty()) |
1201 |
|
|
{ |
1202 |
|
|
const int value = XML::getProperty( |
1203 |
|
|
propertyNode, "value", 0); |
1204 |
|
|
tileProperties[name] = value; |
1205 |
|
|
logger->log("Tile Prop of %d \"%s\" = \"%d\"", |
1206 |
|
|
tileGID, name.c_str(), value); |
1207 |
|
|
} |
1208 |
|
|
} |
1209 |
|
|
|
1210 |
|
|
// create animation |
1211 |
|
|
if (set == nullptr || |
1212 |
|
|
!config.getBoolValue("playMapAnimations")) |
1213 |
|
|
{ |
1214 |
|
|
delete ani; |
1215 |
|
|
continue; |
1216 |
|
|
} |
1217 |
|
|
|
1218 |
|
|
for (int i = 0; ; i++) |
1219 |
|
|
{ |
1220 |
|
|
const std::string iStr(toString(i)); |
1221 |
|
|
StringIntMapCIter iFrame |
1222 |
|
|
= tileProperties.find("animation-frame" + iStr); |
1223 |
|
|
StringIntMapCIter iDelay |
1224 |
|
|
= tileProperties.find("animation-delay" + iStr); |
1225 |
|
|
// possible need add random attribute? |
1226 |
|
|
if (iFrame != tileProperties.end() |
1227 |
|
|
&& iDelay != tileProperties.end()) |
1228 |
|
|
{ |
1229 |
|
|
ani->addFrame(set->get(iFrame->second), |
1230 |
|
|
iDelay->second, 0, 0, 100); |
1231 |
|
|
} |
1232 |
|
|
else |
1233 |
|
|
{ |
1234 |
|
|
break; |
1235 |
|
|
} |
1236 |
|
|
} |
1237 |
|
|
} |
1238 |
|
|
else if (isAnim && !haveAnimation) |
1239 |
|
|
{ |
1240 |
|
|
for_each_xml_child_node(frameNode, tileNode) |
1241 |
|
|
{ |
1242 |
|
|
if (!xmlNameEqual(frameNode, "frame")) |
1243 |
|
|
continue; |
1244 |
|
|
|
1245 |
|
|
if (set != nullptr) |
1246 |
|
|
{ |
1247 |
|
|
const int tileId = XML::getProperty( |
1248 |
|
|
frameNode, "tileid", 0); |
1249 |
|
|
const int duration = XML::getProperty( |
1250 |
|
|
frameNode, "duration", 0) / 10; |
1251 |
|
|
|
1252 |
|
|
ani->addFrame(set->get(tileId), |
1253 |
|
|
duration, |
1254 |
|
|
0, 0, 100); |
1255 |
|
|
} |
1256 |
|
|
} |
1257 |
|
|
} |
1258 |
|
|
|
1259 |
|
|
if (ani->getLength() > 0) |
1260 |
|
|
map->addAnimation(tileGID, new TileAnimation(ani)); |
1261 |
|
|
else |
1262 |
|
|
delete2(ani) |
1263 |
|
|
} |
1264 |
|
|
} |
1265 |
|
|
} |
1266 |
|
|
|
1267 |
|
|
delete doc; |
1268 |
|
|
|
1269 |
|
|
if (set != nullptr) |
1270 |
|
|
set->setProperties(props); |
1271 |
|
|
BLOCK_END("MapReader::readTileset") |
1272 |
|
|
return set; |
1273 |
|
|
} |
1274 |
|
|
|
1275 |
|
|
Map *MapReader::createEmptyMap(const std::string &restrict filename, |
1276 |
|
|
const std::string &restrict realFilename) |
1277 |
|
|
{ |
1278 |
|
|
logger->log1("Creating empty map"); |
1279 |
|
|
Map *const map = new Map("empty map", |
1280 |
|
|
300, 300, |
1281 |
|
|
mapTileSize, mapTileSize); |
1282 |
|
|
map->setProperty("_filename", realFilename); |
1283 |
|
|
map->setProperty("_realfilename", filename); |
1284 |
|
|
updateMusic(map); |
1285 |
|
|
map->setCustom(true); |
1286 |
|
|
MapLayer *layer = new MapLayer("nolayer", |
1287 |
|
|
0, 0, |
1288 |
|
|
300, 300, |
1289 |
|
|
false, |
1290 |
|
|
1, |
1291 |
|
|
-1); |
1292 |
|
|
map->addLayer(layer); |
1293 |
|
|
layer = new MapLayer("nolayer", |
1294 |
|
|
0, 0, |
1295 |
|
|
300, 300, |
1296 |
|
|
true, |
1297 |
|
|
1, |
1298 |
|
|
-1); |
1299 |
|
|
map->addLayer(layer); |
1300 |
|
|
map->updateDrawLayersList(); |
1301 |
|
|
map->updateConditionLayers(); |
1302 |
|
|
map->preCacheLayers(); |
1303 |
|
|
|
1304 |
|
|
return map; |
1305 |
|
|
} |
1306 |
|
|
|
1307 |
|
|
void MapReader::updateMusic(Map *const map) |
1308 |
|
|
{ |
1309 |
|
|
std::string name = map->getProperty("shortName", std::string()); |
1310 |
|
|
const size_t p = name.rfind('.'); |
1311 |
|
|
if (p != std::string::npos) |
1312 |
|
|
name = name.substr(0, p); |
1313 |
|
|
name.append(".ogg"); |
1314 |
|
|
if (VirtFs::exists(pathJoin(paths.getStringValue("music"), name))) |
1315 |
|
|
map->setProperty("music", name); |
1316 |
|
|
} |
1317 |
|
|
|
1318 |
|
|
#ifdef USE_OPENGL |
1319 |
|
|
void MapReader::loadEmptyAtlas() |
1320 |
|
|
{ |
1321 |
|
|
if (!graphicsManager.getUseAtlases()) |
1322 |
|
|
return; |
1323 |
|
|
|
1324 |
|
|
const MapInfo *const info = MapDB::getAtlas( |
1325 |
|
|
paths.getStringValue("emptyAtlasName")); |
1326 |
|
|
if (info != nullptr) |
1327 |
|
|
{ |
1328 |
|
|
mEmptyAtlas = Loader::getEmptyAtlas( |
1329 |
|
|
info->atlas, |
1330 |
|
|
*info->files); |
1331 |
|
|
delete info; |
1332 |
|
|
} |
1333 |
|
|
} |
1334 |
|
|
|
1335 |
|
|
void MapReader::unloadEmptyAtlas() |
1336 |
|
|
{ |
1337 |
|
|
if (mEmptyAtlas != nullptr) |
1338 |
|
|
mEmptyAtlas->decRef(); |
1339 |
✓✗✓✗
|
3 |
} |
1340 |
|
|
#endif // USE_OPENGL |