GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/mapreader.cpp Lines: 3 546 0.5 %
Date: 2019-08-19 Branches: 2 860 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-2019  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
1
    std::map<std::string, XmlNodePtr> mKnownLayers;
81
1
    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 Z_OK;
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", std::string()).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
    map->screenResized();
364
365
    const std::string fileName = path.substr(path.rfind(dirSeparator) + 1);
366
    map->setProperty("shortName", fileName);
367
368
#ifdef USE_OPENGL
369
    BLOCK_START("MapReader::readMap load atlas")
370
    if (graphicsManager.getUseAtlases())
371
    {
372
        const MapInfo *const info = MapDB::getMapAtlas(fileName);
373
        if (info != nullptr)
374
        {
375
            map->setAtlas(Loader::getAtlas(
376
                info->atlas,
377
                *info->files));
378
        }
379
        else
380
        {
381
            reportAlways("Missing atlas for map: %s",
382
                fileName.c_str())
383
        }
384
    }
385
    BLOCK_END("MapReader::readMap load atlas")
386
#endif  // USE_OPENGL
387
388
    for_each_xml_child_node(childNode, node)
389
    {
390
        if (xmlNameEqual(childNode, "tileset"))
391
        {
392
            Tileset *const tileset = readTileset(childNode, pathDir, map);
393
            if (tileset != nullptr)
394
                map->addTileset(tileset);
395
        }
396
        else if (xmlNameEqual(childNode, "layer"))
397
        {
398
            std::string name = XML::getProperty(childNode, "name", "");
399
            name = toLower(name);
400
            LayerInfoIterator it = mKnownLayers.find(name);
401
            if (it == mKnownLayers.end())
402
            {
403
                readLayer(childNode, map);
404
            }
405
            else
406
            {
407
                logger->log("load replace layer: " + name);
408
                loadReplaceLayer(it, map);
409
            }
410
        }
411
        else if (xmlNameEqual(childNode, "properties"))
412
        {
413
            readProperties(childNode, map);
414
            map->setVersion(atoi(map->getProperty(
415
                "manaplus version", std::string()).c_str()));
416
        }
417
        else if (xmlNameEqual(childNode, "objectgroup"))
418
        {
419
            // The object group offset is applied to each object individually
420
            const int tileOffsetX = XML::getProperty(childNode, "x", 0);
421
            const int tileOffsetY = XML::getProperty(childNode, "y", 0);
422
            const int offsetX = tileOffsetX * tilew;
423
            const int offsetY = tileOffsetY * tileh;
424
            const bool showParticles =
425
                config.getBoolValue("mapparticleeffects");
426
427
            for_each_xml_child_node(objectNode, childNode)
428
            {
429
                if (xmlNameEqual(objectNode, "object"))
430
                {
431
                    std::string objType = XML::getProperty(
432
                        objectNode, "type", "");
433
434
                    objType = toUpper(objType);
435
436
/*
437
                    if (objType == "NPC" ||
438
                        objType == "SCRIPT")
439
                    {
440
                        logger->log("hidden obj: " + objType);
441
                        // Silently skip server-side objects.
442
                        continue;
443
                    }
444
*/
445
446
                    const std::string objName = XML::getProperty(
447
                        objectNode, "name", "");
448
                    const int objX = XML::getProperty(objectNode, "x", 0);
449
                    const int objY = XML::getProperty(objectNode, "y", 0);
450
                    const int objW = XML::getProperty(objectNode, "width", 0);
451
                    const int objH = XML::getProperty(objectNode, "height", 0);
452
453
                    logger->log("- Loading object name: %s type: %s at %d:%d"
454
                        " (%dx%d)", objName.c_str(), objType.c_str(),
455
                        objX, objY, objW, objH);
456
457
                    if (objType == "PARTICLE_EFFECT")
458
                    {
459
                        if (objName.empty())
460
                        {
461
                            logger->log1("   Warning: No particle file given");
462
                            continue;
463
                        }
464
465
                        if (showParticles)
466
                        {
467
                            map->addParticleEffect(objName,
468
                                objX + offsetX,
469
                                objY + offsetY,
470
                                objW,
471
                                objH);
472
                        }
473
                        else
474
                        {
475
                            logger->log("Ignore particle effect: " + objName);
476
                        }
477
                    }
478
                    else if (objType == "WARP")
479
                    {
480
                        if (showWarps)
481
                        {
482
                            map->addParticleEffect(warpPath,
483
                                objX, objY, objW, objH);
484
                        }
485
                        map->addPortal(objName, MapItemType::PORTAL,
486
                                       objX, objY, objW, objH);
487
                    }
488
                    else if (objType == "SPAWN")
489
                    {
490
                        // TRANSLATORS: spawn name
491
//                      map->addPortal(_("Spawn: ") + objName,
492
//                            MapItemType::PORTAL,
493
//                            objX, objY, objW, objH);
494
                    }
495
                    else if (objType == "MUSIC")
496
                    {
497
                        map->addRange(objName, MapItemType::MUSIC,
498
                            objX, objY, objW, objH);
499
                    }
500
                    else
501
                    {
502
                        logger->log1("   Warning: Unknown object type");
503
                    }
504
                }
505
            }
506
        }
507
    }
508
509
    map->initializeAmbientLayers();
510
    map->clearIndexedTilesets();
511
    map->setActorsFix(0,
512
        atoi(map->getProperty("actorsfix", std::string()).c_str()));
513
    map->reduce();
514
    map->setWalkLayer(Loader::getWalkLayer(fileName, map));
515
    unloadTempLayers();
516
    map->updateDrawLayersList();
517
    BLOCK_END("MapReader::readMap xml")
518
    return map;
519
}
520
521
void MapReader::readProperties(XmlNodeConstPtrConst node,
522
                               Properties *const props)
523
{
524
    BLOCK_START("MapReader::readProperties")
525
    if (node == nullptr)
526
    {
527
        BLOCK_END("MapReader::readProperties")
528
        return;
529
    }
530
531
    for_each_xml_child_node(childNode, node)
532
    {
533
        if (!xmlNameEqual(childNode, "property"))
534
            continue;
535
536
        // Example: <property name="name" value="value"/>
537
        const std::string name = XML::getProperty(childNode, "name", "");
538
        const std::string value = XML::getProperty(childNode, "value", "");
539
540
        if (!name.empty() && !value.empty())
541
        {
542
            if (name == "name")
543
                props->setProperty(name, translator->getStr(value));
544
            else
545
                props->setProperty(name, value);
546
        }
547
    }
548
    BLOCK_END("MapReader::readProperties")
549
}
550
551
inline static void setTile(Map *const map,
552
                           MapLayer *const layer,
553
                           const MapLayerTypeT &layerType,
554
                           MapHeights *const heights,
555
                           const int x, const int y,
556
                           const int gid) A_NONNULL(1);
557
558
inline static void setTile(Map *const map,
559
                           MapLayer *const layer,
560
                           const MapLayerTypeT &layerType,
561
                           MapHeights *const heights,
562
                           const int x, const int y,
563
                           const int gid)
564
{
565
    const Tileset * const set = map->getTilesetWithGid(gid);
566
    switch (layerType)
567
    {
568
        case MapLayerType::TILES:
569
        {
570
            if (layer == nullptr)
571
                break;
572
            if (set != nullptr &&
573
                !set->isEmpty())
574
            {
575
                Image *const img = set->get(gid - set->getFirstGid());
576
                layer->setTile(x, y, img);
577
            }
578
            else
579
            {
580
                layer->setTile(x, y, nullptr);
581
            }
582
            break;
583
        }
584
585
        case MapLayerType::COLLISION:
586
        {
587
            if (set != nullptr)
588
            {
589
                if (map->getVersion() >= 1)
590
                {
591
                    const int collisionId = gid - set->getFirstGid();
592
                    CollisionTypeT type;
593
                    if (collisionId < 0 ||
594
                        collisionId >= CAST_S32(CollisionType::COLLISION_MAX))
595
                    {
596
                        type = CollisionType::COLLISION_EMPTY;
597
                    }
598
                    else
599
                    {
600
                        type = static_cast<CollisionTypeT>(collisionId);
601
                    }
602
                    switch (type)
603
                    {
604
                        case CollisionType::COLLISION_EMPTY:
605
                            map->addBlockMask(x, y, BlockType::GROUND);
606
                            break;
607
                        case CollisionType::COLLISION_WALL:
608
                            map->addBlockMask(x, y, BlockType::WALL);
609
                            break;
610
                        case CollisionType::COLLISION_AIR:
611
                            map->addBlockMask(x, y, BlockType::AIR);
612
                            break;
613
                        case CollisionType::COLLISION_WATER:
614
                            map->addBlockMask(x, y, BlockType::WATER);
615
                            break;
616
                        case CollisionType::COLLISION_GROUNDTOP:
617
                            map->addBlockMask(x, y, BlockType::GROUNDTOP);
618
                            break;
619
                        case CollisionType::COLLISION_PLAYER_WALL:
620
                            map->addBlockMask(x, y, BlockType::PLAYERWALL);
621
                            break;
622
                        case CollisionType::COLLISION_MONSTER_WALL:
623
                            map->addBlockMask(x, y, BlockType::MONSTERWALL);
624
                            break;
625
                        case CollisionType::COLLISION_MAX:
626
                        default:
627
                            break;
628
                    }
629
                }
630
                else
631
                {
632
                    if (gid - set->getFirstGid() != 0)
633
                        map->addBlockMask(x, y, BlockType::WALL);
634
                }
635
            }
636
            break;
637
        }
638
639
        case MapLayerType::HEIGHTS:
640
        {
641
            if (set == nullptr || heights == nullptr)
642
                break;
643
            if (map->getVersion() >= 2)
644
            {
645
                heights->setHeight(x, y, CAST_U8(
646
                    gid - set->getFirstGid() + 1));
647
            }
648
            else
649
            {
650
                Image *const img = set->get(gid - set->getFirstGid());
651
                if (layer != nullptr)
652
                    layer->setTile(x, y, img);
653
            }
654
            break;
655
        }
656
657
        default:
658
        case MapLayerType::ACTIONS:
659
            break;
660
    }
661
}
662
663
bool MapReader::readBase64Layer(XmlNodeConstPtrConst childNode,
664
                                Map *const map,
665
                                MapLayer *const layer,
666
                                const MapLayerTypeT &layerType,
667
                                MapHeights *const heights,
668
                                const std::string &compression,
669
                                int &restrict x, int &restrict y,
670
                                const int w, const int h)
671
{
672
    if (childNode == nullptr)
673
        return false;
674
675
    if (!compression.empty() && compression != "gzip"
676
        && compression != "zlib")
677
    {
678
        reportAlways("Warning: only gzip and zlib layer"
679
            " compression supported!")
680
        return false;
681
    }
682
683
    // Read base64 encoded map file
684
    if (!XmlHaveChildContent(childNode))
685
        return true;
686
687
    const size_t len = strlen(XmlChildContent(childNode)) + 1;
688
    unsigned char *charData = new unsigned char[len + 1];
689
    const char *const xmlChars = XmlChildContent(childNode);
690
    const char *charStart = reinterpret_cast<const char*>(xmlChars);
691
    if (charStart == nullptr)
692
    {
693
        delete [] charData;
694
        return false;
695
    }
696
697
    unsigned char *charIndex = charData;
698
699
    while (*charStart != 0)
700
    {
701
        if (*charStart != ' ' &&
702
            *charStart != '\t' &&
703
            *charStart != '\n')
704
        {
705
            *charIndex = *charStart;
706
            charIndex++;
707
        }
708
        charStart++;
709
    }
710
    *charIndex = '\0';
711
712
    int binLen;
713
    unsigned char *binData = php3_base64_decode(charData,
714
        CAST_S32(strlen(reinterpret_cast<char*>(
715
        charData))), &binLen);
716
717
    delete [] charData;
718
//    XmlFree(const_cast<char*>(xmlChars));
719
720
    if (binData != nullptr)
721
    {
722
        if (compression == "gzip" || compression == "zlib")
723
        {
724
            // Inflate the gzipped layer data
725
            unsigned char *inflated = nullptr;
726
            const unsigned int inflatedSize =
727
                inflateMemory(binData, binLen, inflated);
728
729
            free(binData);
730
            binData = inflated;
731
            binLen = inflatedSize;
732
733
            if (inflated == nullptr)
734
            {
735
                reportAlways("Error: Could not decompress layer!")
736
                return false;
737
            }
738
        }
739
740
        const std::map<int, TileAnimation*> &tileAnimations
741
            = map->getTileAnimations();
742
743
        const bool hasAnimations = !tileAnimations.empty();
744
        if (hasAnimations)
745
        {
746
            for (int i = 0; i < binLen - 3; i += 4)
747
            {
748
                const int gid = binData[i] |
749
                    binData[i + 1] << 8 |
750
                    binData[i + 2] << 16 |
751
                    binData[i + 3] << 24;
752
753
                setTile(map, layer, layerType, heights, x, y, gid);
754
                TileAnimationMapCIter it = tileAnimations.find(gid);
755
                if (it != tileAnimations.end())
756
                {
757
                    TileAnimation *const ani = it->second;
758
                    if (ani != nullptr)
759
                        ani->addAffectedTile(layer, x + y * w);
760
                }
761
762
                x++;
763
                if (x == w)
764
                {
765
                    x = 0; y++;
766
767
                    // When we're done, don't crash on too much data
768
                    if (y == h)
769
                        break;
770
                }
771
            }
772
        }
773
        else
774
        {
775
            for (int i = 0; i < binLen - 3; i += 4)
776
            {
777
                const int gid = binData[i] |
778
                    binData[i + 1] << 8 |
779
                    binData[i + 2] << 16 |
780
                    binData[i + 3] << 24;
781
782
                setTile(map, layer, layerType, heights, x, y, gid);
783
784
                x++;
785
                if (x == w)
786
                {
787
                    x = 0; y++;
788
789
                    // When we're done, don't crash on too much data
790
                    if (y == h)
791
                        break;
792
                }
793
            }
794
        }
795
        free(binData);
796
    }
797
    return true;
798
}
799
800
bool MapReader::readCsvLayer(XmlNodeConstPtrConst childNode,
801
                             Map *const map,
802
                             MapLayer *const layer,
803
                             const MapLayerTypeT &layerType,
804
                             MapHeights *const heights,
805
                             int &restrict x, int &restrict y,
806
                             const int w, const int h)
807
{
808
    if (childNode == nullptr)
809
        return false;
810
811
    if (!XmlHaveChildContent(childNode))
812
        return true;
813
814
    const char *const xmlChars = XmlChildContent(childNode);
815
    const char *const data = reinterpret_cast<const char*>(xmlChars);
816
    if (data == nullptr)
817
        return false;
818
819
    std::string csv(data);
820
    size_t oldPos = 0;
821
822
    const std::map<int, TileAnimation*> &tileAnimations
823
        = map->getTileAnimations();
824
    const bool hasAnimations = !tileAnimations.empty();
825
826
    if (hasAnimations)
827
    {
828
        while (oldPos != std::string::npos)
829
        {
830
            const size_t pos = csv.find_first_of(',', oldPos);
831
            if (pos == std::string::npos)
832
                return false;
833
834
            const int gid = atoi(csv.substr(oldPos, pos - oldPos).c_str());
835
            setTile(map, layer, layerType, heights, x, y, gid);
836
            TileAnimationMapCIter it = tileAnimations.find(gid);
837
            if (it != tileAnimations.end())
838
            {
839
                TileAnimation *const ani = it->second;
840
                if (ani != nullptr)
841
                    ani->addAffectedTile(layer, x + y * w);
842
            }
843
844
            x++;
845
            if (x == w)
846
            {
847
                x = 0; y++;
848
849
                // When we're done, don't crash on too much data
850
                if (y == h)
851
                    return false;
852
            }
853
            oldPos = pos + 1;
854
        }
855
    }
856
    else
857
    {
858
        while (oldPos != std::string::npos)
859
        {
860
            const size_t pos = csv.find_first_of(',', oldPos);
861
            if (pos == std::string::npos)
862
                return false;
863
864
            const int gid = atoi(csv.substr(oldPos, pos - oldPos).c_str());
865
            setTile(map, layer, layerType, heights, x, y, gid);
866
867
            x++;
868
            if (x == w)
869
            {
870
                x = 0; y++;
871
872
                // When we're done, don't crash on too much data
873
                if (y == h)
874
                    return false;
875
            }
876
            oldPos = pos + 1;
877
        }
878
    }
879
    return true;
880
}
881
882
void MapReader::readLayer(XmlNodeConstPtr node, Map *const map)
883
{
884
    if (node == nullptr)
885
        return;
886
887
    // Layers are not necessarily the same size as the map
888
    const int w = XML::getProperty(node, "width", map->getWidth());
889
    const int h = XML::getProperty(node, "height", map->getHeight());
890
    const int offsetX = XML::getProperty(node, "x", 0);
891
    const int offsetY = XML::getProperty(node, "y", 0);
892
    std::string name = XML::getProperty(node, "name", "");
893
    name = toLower(name);
894
895
    const bool isFringeLayer = (name.substr(0, 6) == "fringe");
896
    const bool isCollisionLayer = (name.substr(0, 9) == "collision");
897
    const bool isHeightLayer = (name.substr(0, 7) == "heights");
898
    const bool isActionsLayer = (name.substr(0, 7) == "actions");
899
    int mask = 1;
900
    int tileCondition = -1;
901
    int conditionLayer = 0;
902
903
    MapLayerTypeT layerType = MapLayerType::TILES;
904
    if (isCollisionLayer)
905
        layerType = MapLayerType::COLLISION;
906
    else if (isHeightLayer)
907
        layerType = MapLayerType::HEIGHTS;
908
    else if (isActionsLayer)
909
        layerType = MapLayerType::ACTIONS;
910
911
    map->indexTilesets();
912
913
    MapLayer *layer = nullptr;
914
    MapHeights *heights = nullptr;
915
916
    logger->log("- Loading layer \"%s\"", name.c_str());
917
    int x = 0;
918
    int y = 0;
919
920
    // Load the tile data
921
    for_each_xml_child_node(childNode, node)
922
    {
923
        if (xmlNameEqual(childNode, "properties"))
924
        {
925
            for_each_xml_child_node(prop, childNode)
926
            {
927
                if (!xmlNameEqual(prop, "property"))
928
                    continue;
929
                const std::string pname = XML::getProperty(prop, "name", "");
930
                const std::string value = XML::getProperty(prop, "value", "");
931
                // ignoring any layer if property Hidden is 1
932
                if (pname == "Hidden")
933
                {
934
                    if (value == "1")
935
                        return;
936
                }
937
                else if (pname == "Version")
938
                {
939
                    if (value > CHECK_VERSION)
940
                        return;
941
                }
942
                else if (pname == "NotVersion")
943
                {
944
                    if (value <= CHECK_VERSION)
945
                        return;
946
                }
947
                else if (pname == "Mask")
948
                {
949
                    mask = atoi(value.c_str());
950
                }
951
                else if (pname == "TileCondition")
952
                {
953
                    tileCondition = atoi(value.c_str());
954
                }
955
                else if (pname == "ConditionLayer")
956
                {
957
                    conditionLayer = atoi(value.c_str());
958
                }
959
                else if (pname == "SideView")
960
                {
961
                    if (value != "down")
962
                        return;
963
                }
964
            }
965
        }
966
967
        if (!xmlNameEqual(childNode, "data"))
968
            continue;
969
970
        // Disable for future usage "TileCondition" attribute
971
        // if already set ConditionLayer to non zero
972
        if (conditionLayer != 0)
973
            tileCondition = -1;
974
975
        switch (layerType)
976
        {
977
            case MapLayerType::TILES:
978
            {
979
                layer = new MapLayer(name,
980
                    offsetX, offsetY,
981
                    w, h,
982
                    isFringeLayer,
983
                    mask,
984
                    tileCondition);
985
                map->addLayer(layer);
986
                break;
987
            }
988
            case MapLayerType::HEIGHTS:
989
            {
990
                heights = new MapHeights(w, h);
991
                map->addHeights(heights);
992
                break;
993
            }
994
            default:
995
            case MapLayerType::ACTIONS:
996
            case MapLayerType::COLLISION:
997
                break;
998
        }
999
1000
        const std::string encoding =
1001
            XML::getProperty(childNode, "encoding", "");
1002
        const std::string compression =
1003
            XML::getProperty(childNode, "compression", "");
1004
1005
        if (encoding == "base64")
1006
        {
1007
            if (readBase64Layer(childNode, map, layer, layerType,
1008
                heights, compression, x, y, w, h))
1009
            {
1010
                continue;
1011
            }
1012
            else
1013
            {
1014
                return;
1015
            }
1016
        }
1017
        else if (encoding == "csv")
1018
        {
1019
            if (readCsvLayer(childNode, map, layer, layerType,
1020
                heights, x, y, w, h))
1021
            {
1022
                continue;
1023
            }
1024
            else
1025
            {
1026
                return;
1027
            }
1028
        }
1029
        else
1030
        {
1031
            const std::map<int, TileAnimation*> &tileAnimations
1032
                = map->getTileAnimations();
1033
            const bool hasAnimations = !tileAnimations.empty();
1034
1035
            // Read plain XML map file
1036
            for_each_xml_child_node(childNode2, childNode)
1037
            {
1038
                if (!xmlNameEqual(childNode2, "tile"))
1039
                    continue;
1040
1041
                const int gid = XML::getProperty(childNode2, "gid", -1);
1042
                setTile(map, layer, layerType, heights, x, y, gid);
1043
                if (hasAnimations)
1044
                {
1045
                    TileAnimationMapCIter it = tileAnimations.find(gid);
1046
                    if (it != tileAnimations.end())
1047
                    {
1048
                        TileAnimation *const ani = it->second;
1049
                        if (ani != nullptr)
1050
                            ani->addAffectedTile(layer, x + y * w);
1051
                    }
1052
                }
1053
1054
                x++;
1055
                if (x == w)
1056
                {
1057
                    x = 0; y++;
1058
                    if (y >= h)
1059
                        break;
1060
                }
1061
            }
1062
        }
1063
1064
        if (y < h)
1065
            std::cerr << "TOO SMALL!\n";
1066
        if (x != 0)
1067
            std::cerr << "TOO SMALL!\n";
1068
1069
        // There can be only one data element
1070
        break;
1071
    }
1072
}
1073
1074
Tileset *MapReader::readTileset(XmlNodePtr node,
1075
                                const std::string &path,
1076
                                Map *const map)
1077
{
1078
    BLOCK_START("MapReader::readTileset")
1079
    if (node == nullptr)
1080
    {
1081
        BLOCK_END("MapReader::readTileset")
1082
        return nullptr;
1083
    }
1084
1085
    const int firstGid = XML::getProperty(node, "firstgid", 0);
1086
    const int margin = XML::getProperty(node, "margin", 0);
1087
    const int spacing = XML::getProperty(node, "spacing", 0);
1088
    XML::Document* doc = nullptr;
1089
    Tileset *set = nullptr;
1090
    std::string pathDir(path);
1091
    std::map<std::string, std::string> props;
1092
1093
    if (XmlHasProp(node, "source"))
1094
    {
1095
        std::string filename = XML::getProperty(node, "source", "");
1096
        filename = resolveRelativePath(path, filename);
1097
1098
        doc = new XML::Document(filename, UseVirtFs_true, SkipError_false);
1099
        node = doc->rootNode();
1100
        if (node == nullptr)
1101
        {
1102
            delete doc;
1103
            BLOCK_END("MapReader::readTileset")
1104
            return nullptr;
1105
        }
1106
1107
        // Reset path to be realtive to the tsx file
1108
        pathDir = filename.substr(0, filename.rfind('/') + 1);
1109
    }
1110
1111
    const int tw = XML::getProperty(node, "tilewidth", map->getTileWidth());
1112
    const int th = XML::getProperty(node, "tileheight", map->getTileHeight());
1113
1114
    for_each_xml_child_node(childNode, node)
1115
    {
1116
        if (xmlNameEqual(childNode, "image"))
1117
        {
1118
            // ignore second other <image> tags in tileset
1119
            if (set != nullptr)
1120
                continue;
1121
1122
            const std::string source = XML::getProperty(
1123
                childNode, "source", "");
1124
1125
            if (!source.empty())
1126
            {
1127
                const std::string sourceResolved = resolveRelativePath(pathDir,
1128
                    source);
1129
1130
                Image *const tilebmp = Loader::getImage(sourceResolved);
1131
1132
                if (tilebmp != nullptr)
1133
                {
1134
                    set = new Tileset(tilebmp,
1135
                        tw, th,
1136
                        firstGid,
1137
                        margin,
1138
                        spacing);
1139
                    tilebmp->decRef();
1140
#ifdef USE_OPENGL
1141
                    if (MapDB::isEmptyTileset(sourceResolved))
1142
                        set->setEmpty(true);
1143
                    if (tilebmp->getType() == ImageType::Image &&
1144
                        map->haveAtlas() == true &&
1145
                        graphicsManager.getUseAtlases())
1146
                    {
1147
                        reportAlways("Error: image '%s' not present in atlas",
1148
                            source.c_str())
1149
                    }
1150
#endif  // USE_OPENGL
1151
                }
1152
                else
1153
                {
1154
                    reportAlways("Error: Failed to load tileset (%s)",
1155
                        source.c_str())
1156
                }
1157
            }
1158
        }
1159
        else if (xmlNameEqual(childNode, "properties"))
1160
        {
1161
            for_each_xml_child_node(propertyNode, childNode)
1162
            {
1163
                if (!xmlNameEqual(propertyNode, "property"))
1164
                    continue;
1165
                const std::string name = XML::getProperty(
1166
                    propertyNode, "name", "");
1167
                if (!name.empty())
1168
                    props[name] = XML::getProperty(propertyNode, "value", "");
1169
            }
1170
        }
1171
        else if (xmlNameEqual(childNode, "tile"))
1172
        {
1173
            bool haveAnimation(false);
1174
1175
            for_each_xml_child_node(tileNode, childNode)
1176
            {
1177
                const bool isProps = xmlNameEqual(tileNode, "properties");
1178
                const bool isAnim = xmlNameEqual(tileNode, "animation");
1179
                if (!isProps && !isAnim)
1180
                    continue;
1181
1182
                const int tileGID = firstGid + XML::getProperty(
1183
                    childNode, "id", 0);
1184
1185
                Animation *ani = new Animation("from map");
1186
1187
                if (isProps)
1188
                {
1189
                    // read tile properties to a map for simpler handling
1190
                    StringIntMap tileProperties;
1191
                    for_each_xml_child_node(propertyNode, tileNode)
1192
                    {
1193
                        if (!xmlNameEqual(propertyNode, "property"))
1194
                            continue;
1195
1196
                        haveAnimation = true;
1197
                        const std::string name = XML::getProperty(
1198
                            propertyNode, "name", "");
1199
                        const int value = XML::getProperty(
1200
                            propertyNode, "value", 0);
1201
                        if (!name.empty())
1202
                        {
1203
                            tileProperties[name] = value;
1204
                            logger->log("Tile Prop of %d \"%s\" = \"%d\"",
1205
                                tileGID, name.c_str(), value);
1206
                        }
1207
                    }
1208
1209
                    // create animation
1210
                    if (set == nullptr ||
1211
                        !config.getBoolValue("playMapAnimations"))
1212
                    {
1213
                        delete ani;
1214
                        continue;
1215
                    }
1216
1217
                    for (int i = 0; ; i++)
1218
                    {
1219
                        const std::string iStr(toString(i));
1220
                        StringIntMapCIter iFrame
1221
                            = tileProperties.find("animation-frame" + iStr);
1222
                        StringIntMapCIter iDelay
1223
                            = tileProperties.find("animation-delay" + iStr);
1224
                        // possible need add random attribute?
1225
                        if (iFrame != tileProperties.end()
1226
                            && iDelay != tileProperties.end())
1227
                        {
1228
                            ani->addFrame(set->get(iFrame->second),
1229
                                iDelay->second, 0, 0, 100);
1230
                        }
1231
                        else
1232
                        {
1233
                            break;
1234
                        }
1235
                    }
1236
                }
1237
                else if (isAnim && !haveAnimation)
1238
                {
1239
                    for_each_xml_child_node(frameNode, tileNode)
1240
                    {
1241
                        if (!xmlNameEqual(frameNode, "frame"))
1242
                            continue;
1243
1244
                        const int tileId = XML::getProperty(
1245
                            frameNode, "tileid", 0);
1246
                        const int duration = XML::getProperty(
1247
                            frameNode, "duration", 0) /  10;
1248
1249
                        if (set != nullptr)
1250
                        {
1251
                            ani->addFrame(set->get(tileId),
1252
                                duration,
1253
                                0, 0, 100);
1254
                        }
1255
                    }
1256
                }
1257
1258
                if (ani->getLength() > 0)
1259
                    map->addAnimation(tileGID, new TileAnimation(ani));
1260
                else
1261
                    delete2(ani)
1262
            }
1263
        }
1264
    }
1265
1266
    delete doc;
1267
1268
    if (set != nullptr)
1269
        set->setProperties(props);
1270
    BLOCK_END("MapReader::readTileset")
1271
    return set;
1272
}
1273
1274
Map *MapReader::createEmptyMap(const std::string &restrict filename,
1275
                               const std::string &restrict realFilename)
1276
{
1277
    logger->log1("Creating empty map");
1278
    Map *const map = new Map("empty map",
1279
        300, 300,
1280
        mapTileSize, mapTileSize);
1281
    map->setProperty("_filename", realFilename);
1282
    map->setProperty("_realfilename", filename);
1283
    updateMusic(map);
1284
    map->setCustom(true);
1285
    MapLayer *layer = new MapLayer("nolayer",
1286
        0, 0,
1287
        300, 300,
1288
        false,
1289
        1,
1290
        -1);
1291
    map->addLayer(layer);
1292
    layer = new MapLayer("nolayer",
1293
        0, 0,
1294
        300, 300,
1295
        true,
1296
        1,
1297
        -1);
1298
    map->addLayer(layer);
1299
    map->updateDrawLayersList();
1300
    map->updateConditionLayers();
1301
    map->preCacheLayers();
1302
1303
    return map;
1304
}
1305
1306
void MapReader::updateMusic(Map *const map)
1307
{
1308
    std::string name = map->getProperty("shortName", std::string());
1309
    const size_t p = name.rfind('.');
1310
    if (p != std::string::npos)
1311
        name = name.substr(0, p);
1312
    name.append(".ogg");
1313
    if (VirtFs::exists(pathJoin(paths.getStringValue("music"), name)))
1314
        map->setProperty("music", name);
1315
}
1316
1317
#ifdef USE_OPENGL
1318
void MapReader::loadEmptyAtlas()
1319
{
1320
    if (!graphicsManager.getUseAtlases())
1321
        return;
1322
1323
    const MapInfo *const info = MapDB::getAtlas(
1324
        paths.getStringValue("emptyAtlasName"));
1325
    if (info != nullptr)
1326
    {
1327
        mEmptyAtlas = Loader::getEmptyAtlas(
1328
            info->atlas,
1329
            *info->files);
1330
        delete info;
1331
    }
1332
}
1333
1334
void MapReader::unloadEmptyAtlas()
1335
{
1336
    if (mEmptyAtlas != nullptr)
1337
        mEmptyAtlas->decRef();
1338

3
}
1339
#endif  // USE_OPENGL