GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/mapreader.cpp Lines: 3 553 0.5 %
Date: 2017-11-29 Branches: 2 850 0.2 %

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

6
}
1324
#endif  // USE_OPENGL