| 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 |