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

3
}
1340
#endif  // USE_OPENGL