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

6
}
1337
#endif  // USE_OPENGL