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/map/map.h"
25 
26 #include "configuration.h"
27 #include "render/graphics.h"
28 #include "notifymanager.h"
29 #include "settings.h"
30 
31 #include "being/localplayer.h"
32 
33 #include "enums/resources/notifytypes.h"
34 
35 #include "enums/resources/map/blockmask.h"
36 #include "enums/resources/map/mapitemtype.h"
37 
38 #include "fs/mkdir.h"
39 
40 #include "gui/userpalette.h"
41 
42 #include "particle/particle.h"
43 
44 #include "resources/ambientlayer.h"
45 
46 #ifdef USE_OPENGL
47 #include "resources/atlas/atlasresource.h"
48 #endif  // USE_OPENGL
49 
50 #include "resources/image/subimage.h"
51 
52 #include "resources/loaders/imageloader.h"
53 
54 #include "resources/map/location.h"
55 #include "resources/map/mapheights.h"
56 #include "resources/map/mapobjectlist.h"
57 #include "resources/map/maplayer.h"
58 #include "resources/map/mapitem.h"
59 #include "resources/map/objectslayer.h"
60 #include "resources/map/speciallayer.h"
61 #include "resources/map/tileanimation.h"
62 #include "resources/map/tileset.h"
63 #include "resources/map/walklayer.h"
64 
65 #ifdef USE_OPENGL
66 #include "render/renderers.h"
67 #endif  // USE_OPENGL
68 
69 #include "utils/checkutils.h"
70 #include "utils/delete2.h"
71 #include "utils/dtor.h"
72 #include "utils/foreach.h"
73 #include "utils/timer.h"
74 
75 #include <sys/stat.h>
76 
77 #include <climits>
78 #include <fstream>
79 #include <queue>
80 
81 #include "debug.h"
82 
83 class ActorFunctuator final
84 {
85     public:
A_DEFAULT_COPY(ActorFunctuator)86         A_DEFAULT_COPY(ActorFunctuator)
87 
88         bool operator()(const Actor *const a,
89                         const Actor *const b) const
90         {
91             if ((a == nullptr) || (b == nullptr))
92                 return false;
93             return a->getSortPixelY() < b->getSortPixelY();
94         }
95 } actorCompare;
96 
Map(const std::string & name,const int width,const int height,const int tileWidth,const int tileHeight)97 Map::Map(const std::string &name,
98          const int width,
99          const int height,
100          const int tileWidth,
101          const int tileHeight) :
102     Properties(),
103     mWidth(width), mHeight(height),
104     mTileWidth(tileWidth), mTileHeight(tileHeight),
105     mMaxTileHeight(height),
106     mMetaTiles(new MetaTile[mWidth * mHeight]),
107     mWalkLayer(nullptr),
108     mLayers(),
109     mDrawUnderLayers(),
110     mDrawOverLayers(),
111     mTilesets(),
112     mActors(),
113     mHasWarps(false),
114     mDrawLayersFlags(MapType::NORMAL),
115     mOnClosedList(1),
116     mOnOpenList(2),
117     mBackgrounds(),
118     mForegrounds(),
119     mLastAScrollX(0.0F),
120     mLastAScrollY(0.0F),
121     mParticleEffects(),
122     mMapPortals(),
123     mTileAnimations(),
124     mName(name),
125     mOverlayDetail(config.getIntValue("OverlayDetail")),
126     mOpacity(config.getFloatValue("guialpha")),
127 #ifdef USE_OPENGL
128     mOpenGL(intToRenderType(config.getIntValue("opengl"))),
129 #else  // USE_OPENGL
130     mOpenGL(RENDER_SOFTWARE),
131 #endif  // USE_OPENGL
132     mPvp(0),
133     mTilesetsIndexed(false),
134     mIndexedTilesets(nullptr),
135     mIndexedTilesetsSize(0),
136     mActorFixX(0),
137     mActorFixY(0),
138     mVersion(0),
139     mSpecialLayer(new SpecialLayer("special layer", width, height)),
140     mTempLayer(new SpecialLayer("temp layer", width, height)),
141     mObjects(new ObjectsLayer(width, height)),
142     mFringeLayer(nullptr),
143     mLastX(-1),
144     mLastY(-1),
145     mLastScrollX(-1),
146     mLastScrollY(-1),
147     mDrawX(-1),
148     mDrawY(-1),
149     mDrawScrollX(-1),
150     mDrawScrollY(-1),
151     mMask(1),
152 #ifdef USE_OPENGL
153     mAtlas(nullptr),
154 #endif  // USE_OPENGL
155     mHeights(nullptr),
156     mRedrawMap(true),
157     mBeingOpacity(false),
158 #ifdef USE_OPENGL
159     mCachedDraw(mOpenGL == RENDER_NORMAL_OPENGL ||
160         mOpenGL == RENDER_GLES_OPENGL ||
161         mOpenGL == RENDER_GLES2_OPENGL ||
162         mOpenGL == RENDER_MODERN_OPENGL),
163 #else  // USE_OPENGL
164     mCachedDraw(false),
165 #endif  // USE_OPENGL
166     mCustom(false),
167     mDrawOnlyFringe(false),
168     mClear(false)
169 {
170     config.addListener("OverlayDetail", this);
171     config.addListener("guialpha", this);
172     config.addListener("beingopacity", this);
173 
174     if (mOpacity != 1.0F)
175         mBeingOpacity = config.getBoolValue("beingopacity");
176     else
177         mBeingOpacity = false;
178 }
179 
~Map()180 Map::~Map()
181 {
182     config.removeListeners(this);
183     CHECKLISTENERS
184 
185     if (mWalkLayer != nullptr)
186     {
187         mWalkLayer->decRef();
188         mWalkLayer = nullptr;
189     }
190     mFringeLayer = nullptr;
191     delete_all(mLayers);
192     delete_all(mTilesets);
193     delete_all(mForegrounds);
194     delete_all(mBackgrounds);
195     delete_all(mTileAnimations);
196     delete2(mSpecialLayer)
197     delete2(mTempLayer)
198     delete2(mObjects)
199     delete_all(mMapPortals);
200 #ifdef USE_OPENGL
201     if (mAtlas != nullptr)
202     {
203         mAtlas->decRef();
204         mAtlas = nullptr;
205     }
206 #endif  // USE_OPENGL
207     delete2(mHeights)
208     delete [] mMetaTiles;
209 }
210 
optionChanged(const std::string & restrict value)211 void Map::optionChanged(const std::string &restrict value) restrict2
212 {
213     if (value == "OverlayDetail")
214     {
215         mOverlayDetail = config.getIntValue("OverlayDetail");
216     }
217     else if (value == "guialpha")
218     {
219         mOpacity = config.getFloatValue("guialpha");
220         if (mOpacity != 1.0F)
221             mBeingOpacity = config.getBoolValue("beingopacity");
222         else
223             mBeingOpacity = false;
224     }
225     else if (value == "beingopacity")
226     {
227         if (mOpacity != 1.0F)
228             mBeingOpacity = config.getBoolValue("beingopacity");
229         else
230             mBeingOpacity = false;
231     }
232 }
233 
initializeAmbientLayers()234 void Map::initializeAmbientLayers() restrict2
235 {
236     // search for "foreground*" or "overlay*" (old term) in map properties
237     for (int i = 0; /* terminated by a break */; i++)
238     {
239         std::string name;
240         if (hasProperty(std::string("foreground").append(
241             toString(i)).append("image")))
242         {
243             name = "foreground" + toString(i);
244         }
245         else if (hasProperty(std::string("overlay").append(
246                  toString(i)).append("image")))
247         {
248             name = "overlay" + toString(i);
249         }
250         else
251         {
252             break;  // the FOR loop
253         }
254 
255         Image *restrict const img = Loader::getImage(
256             getProperty(name + "image", std::string()));
257         if (img != nullptr)
258         {
259             int mask = atoi(getProperty(name + "mask", std::string()).c_str());
260             if (mask == 0)
261                 mask = 1;
262             const float parallax = getFloatProperty(name + "parallax", 0.0F);
263             mForegrounds.push_back(new AmbientLayer(
264                 name,
265                 img,
266                 getFloatProperty(name + "parallaxX", parallax),
267                 getFloatProperty(name + "parallaxY", parallax),
268                 getFloatProperty(name + "posX", 0.0F),
269                 getFloatProperty(name + "posY", 0.0F),
270                 getFloatProperty(name + "scrollX", 0.0F),
271                 getFloatProperty(name + "scrollY", 0.0F),
272                 getBoolProperty(name + "keepratio", false),
273                 mask));
274 
275             // The AmbientLayer takes control over the image.
276             img->decRef();
277         }
278     }
279 
280     // search for "background*" in map properties
281     for (int i = 0; hasProperty(std::string("background").append(
282          toString(i)).append("image")); i ++)
283     {
284         const std::string name("background" + toString(i));
285         Image *restrict const img = Loader::getImage(
286             getProperty(name + "image", std::string()));
287 
288         if (img != nullptr)
289         {
290             int mask = atoi(getProperty(name + "mask", std::string()).c_str());
291             if (mask == 0)
292                 mask = 1;
293 
294             const float parallax = getFloatProperty(name + "parallax", 0.0F);
295             mBackgrounds.push_back(new AmbientLayer(
296                 name,
297                 img,
298                 getFloatProperty(name + "parallaxX", parallax),
299                 getFloatProperty(name + "parallaxY", parallax),
300                 getFloatProperty(name + "posX", 0.0F),
301                 getFloatProperty(name + "posY", 0.0F),
302                 getFloatProperty(name + "scrollX", 0.0F),
303                 getFloatProperty(name + "scrollY", 0.0F),
304                 getBoolProperty(name + "keepratio", false),
305                 mask));
306 
307             // The AmbientLayer takes control over the image.
308             img->decRef();
309         }
310     }
311 }
312 
addLayer(MapLayer * const layer)313 void Map::addLayer(MapLayer *const layer) restrict2
314 {
315     mLayers.push_back(layer);
316     if (layer->isFringeLayer() && (mFringeLayer == nullptr))
317         mFringeLayer = layer;
318 }
319 
addTileset(Tileset * const tileset)320 void Map::addTileset(Tileset *const tileset) restrict2
321 {
322     mTilesets.push_back(tileset);
323     const int height = tileset->getHeight();
324     if (height > mMaxTileHeight)
325         mMaxTileHeight = height;
326 }
327 
update(const int ticks)328 void Map::update(const int ticks) restrict2
329 {
330     // Update animated tiles
331     FOR_EACH (TileAnimationMapCIter, iAni, mTileAnimations)
332     {
333         TileAnimation *restrict const tileAni = iAni->second;
334         if ((tileAni != nullptr) && tileAni->update(ticks))
335             mRedrawMap = true;
336     }
337 }
338 
draw(Graphics * restrict const graphics,int scrollX,int scrollY)339 void Map::draw(Graphics *restrict const graphics,
340                int scrollX, int scrollY) restrict2
341 {
342     if (localPlayer == nullptr)
343         return;
344 
345     if (mClear)
346         mainGraphics->clearScreen();
347 
348     BLOCK_START("Map::draw")
349     // Calculate range of tiles which are on-screen
350     const int endPixelY = graphics->mHeight + scrollY + mTileHeight - 1
351         + mMaxTileHeight - mTileHeight;
352     const int startX = scrollX / mTileWidth - 2;
353     const int startY = scrollY / mTileHeight;
354     const int endX = (graphics->mWidth + scrollX + mTileWidth - 1)
355         / mTileWidth + 1;
356     const int endY = endPixelY / mTileHeight + 1;
357 
358     // Make sure actors are sorted ascending by Y-coordinate
359     // so that they overlap correctly
360     BLOCK_START("Map::draw sort")
361     mActors.sort(actorCompare);
362     BLOCK_END("Map::draw sort")
363 
364     // update scrolling of all ambient layers
365     updateAmbientLayers(static_cast<float>(scrollX),
366                         static_cast<float>(scrollY));
367 
368     // Draw backgrounds
369     drawAmbientLayers(graphics,
370         MapLayerPosition::BACKGROUND_LAYERS,
371         mOverlayDetail);
372 
373     if (mDrawLayersFlags == MapType::BLACKWHITE && (userPalette != nullptr))
374     {
375         graphics->setColor(userPalette->getColorWithAlpha(
376             UserColorId::WALKABLE_HIGHLIGHT));
377 
378         graphics->fillRectangle(Rect(0, 0,
379             graphics->mWidth, graphics->mHeight));
380     }
381 
382 #ifdef USE_OPENGL
383     int updateFlag = 0;
384 
385     if (mCachedDraw)
386     {
387         if (mLastX != startX || mLastY != startY || mLastScrollX != scrollX
388             || mLastScrollY != scrollY)
389         {   // player moving
390             mLastX = startX;
391             mLastY = startY;
392             mLastScrollX = scrollX;
393             mLastScrollY = scrollY;
394             updateFlag = 2;
395         }
396         else if (mRedrawMap || startX != mDrawX || startY != mDrawY ||
397                  scrollX != mDrawScrollX || scrollY != mDrawScrollY)
398         {   // player mode to new position
399             mRedrawMap = false;
400             mDrawX = startX;
401             mDrawY = startY;
402             mDrawScrollX = scrollX;
403             mDrawScrollY = scrollY;
404             updateFlag = 1;
405         }
406     }
407 #endif  // USE_OPENGL
408 
409     if (mDrawOnlyFringe)
410     {
411         if (mFringeLayer != nullptr)
412         {
413             mFringeLayer->setSpecialLayer(mSpecialLayer);
414             mFringeLayer->setTempLayer(mTempLayer);
415             mFringeLayer->drawFringe(graphics,
416                 startX, startY,
417                 endX, endY,
418                 scrollX, scrollY,
419                 mActors);
420         }
421     }
422     else
423     {
424 #ifdef USE_OPENGL
425         if (mCachedDraw)
426         {
427             if (updateFlag != 0)
428             {
429                 FOR_EACH (Layers::iterator, it, mDrawUnderLayers)
430                 {
431                     (*it)->updateOGL(graphics,
432                         startX, startY,
433                         endX, endY,
434                         scrollX, scrollY);
435                 }
436                 FOR_EACH (Layers::iterator, it, mDrawOverLayers)
437                 {
438                     (*it)->updateOGL(graphics,
439                         startX, startY,
440                         endX, endY,
441                         scrollX, scrollY);
442                 }
443             }
444 
445             FOR_EACH (Layers::iterator, it, mDrawUnderLayers)
446                 (*it)->drawOGL(graphics);
447 
448             if (mFringeLayer != nullptr)
449             {
450                 mFringeLayer->setSpecialLayer(mSpecialLayer);
451                 mFringeLayer->setTempLayer(mTempLayer);
452                 mFringeLayer->drawFringe(graphics,
453                     startX, startY,
454                     endX, endY,
455                     scrollX, scrollY,
456                     mActors);
457             }
458 
459             FOR_EACH (Layers::iterator, it, mDrawOverLayers)
460                 (*it)->drawOGL(graphics);
461         }
462         else
463 #endif  // USE_OPENGL
464         {
465             FOR_EACH (Layers::iterator, it, mDrawUnderLayers)
466             {
467                 (*it)->draw(graphics,
468                     startX, startY,
469                     endX, endY,
470                     scrollX, scrollY);
471             }
472 
473             if (mFringeLayer != nullptr)
474             {
475                 mFringeLayer->setSpecialLayer(mSpecialLayer);
476                 mFringeLayer->setTempLayer(mTempLayer);
477                 mFringeLayer->drawFringe(graphics,
478                     startX, startY,
479                     endX, endY,
480                     scrollX, scrollY,
481                     mActors);
482             }
483 
484             FOR_EACH (Layers::iterator, it, mDrawOverLayers)
485             {
486                 (*it)->draw(graphics, startX, startY,
487                     endX, endY,
488                     scrollX, scrollY);
489             }
490         }
491     }
492 
493     // Don't draw if gui opacity == 1
494     if (mBeingOpacity)
495     {
496         // Draws beings with a lower opacity to make them visible
497         // even when covered by a wall or some other elements...
498         ActorsCIter ai = mActors.begin();
499         const ActorsCIter ai_end = mActors.end();
500 
501         if (mOpenGL == RENDER_SOFTWARE)
502         {
503             while (ai != ai_end)
504             {
505                 if (Actor *restrict const actor = *ai)
506                 {
507                     const int x = actor->getTileX();
508                     const int y = actor->getTileY();
509                     if (x < startX || x > endX || y < startY || y > endY)
510                     {
511                         ++ai;
512                         continue;
513                     }
514                     // For now, just draw actors with only one layer.
515                     if (actor->getNumberOfLayers() == 1)
516                     {
517                         actor->setAlpha(0.3F);
518                         actor->draw(graphics, -scrollX, -scrollY);
519                         actor->setAlpha(1.0F);
520                     }
521                 }
522                 ++ai;
523             }
524         }
525         else
526         {
527             while (ai != ai_end)
528             {
529                 if (Actor *const actor = *ai)
530                 {
531                     actor->setAlpha(0.3F);
532                     actor->draw(graphics, -scrollX, -scrollY);
533                     actor->setAlpha(1.0F);
534                 }
535                 ++ai;
536             }
537         }
538     }
539 
540     drawAmbientLayers(graphics,
541         MapLayerPosition::FOREGROUND_LAYERS,
542         mOverlayDetail);
543     BLOCK_END("Map::draw")
544 }
545 
546 #define fillCollision(collision, color) \
547     if (x < endX && mMetaTiles[tilePtr].blockmask & (collision))\
548     {\
549         width = mapTileSize;\
550         for (int x2 = tilePtr + 1; x < endX; x2 ++)\
551         {\
552             if (!(mMetaTiles[x2].blockmask & (collision)))\
553                 break;\
554             width += mapTileSize;\
555             x ++;\
556             tilePtr ++;\
557         }\
558         if (width && userPalette)\
559         {\
560             graphics->setColor(userPalette->getColorWithAlpha(\
561                 UserColorId::color));\
562             graphics->fillRectangle(Rect(\
563                 x0 * mTileWidth - scrollX, \
564                 y * mTileHeight - scrollY, \
565                 width, mapTileSize));\
566         }\
567     }\
568 
drawCollision(Graphics * restrict const graphics,const int scrollX,const int scrollY,const MapTypeT drawFlags) const569 void Map::drawCollision(Graphics *restrict const graphics,
570                         const int scrollX,
571                         const int scrollY,
572                         const MapTypeT drawFlags) const restrict2
573 {
574     const int endPixelY = graphics->mHeight + scrollY + mTileHeight - 1;
575     int startX = scrollX / mTileWidth;
576     int startY = scrollY / mTileHeight;
577     int endX = (graphics->mWidth + scrollX + mTileWidth - 1) / mTileWidth;
578     int endY = endPixelY / mTileHeight;
579 
580     if (startX < 0)
581         startX = 0;
582     if (startY < 0)
583         startY = 0;
584     if (endX > mWidth)
585         endX = mWidth;
586     if (endY > mHeight)
587         endY = mHeight;
588 
589     if (drawFlags < MapType::SPECIAL)
590     {
591         graphics->setColor(userPalette->getColorWithAlpha(UserColorId::NET));
592         graphics->drawNet(
593             startX * mTileWidth - scrollX,
594             startY * mTileHeight - scrollY,
595             endX * mTileWidth - scrollX,
596             endY * mTileHeight - scrollY,
597             mapTileSize, mapTileSize);
598     }
599 
600     for (int y = startY; y < endY; y++)
601     {
602         const int yWidth = y * mWidth;
603         int tilePtr = startX + yWidth;
604         for (int x = startX; x < endX; x++, tilePtr++)
605         {
606             int width = 0;
607             const int x0 = x;
608 
609             fillCollision(BlockMask::WALL, COLLISION_HIGHLIGHT)
610             fillCollision(BlockMask::AIR, AIR_COLLISION_HIGHLIGHT)
611             fillCollision(BlockMask::WATER, WATER_COLLISION_HIGHLIGHT)
612             fillCollision(BlockMask::GROUNDTOP, GROUNDTOP_COLLISION_HIGHLIGHT)
613             fillCollision(BlockMask::PLAYERWALL, COLLISION_HIGHLIGHT)
614             fillCollision(BlockMask::MONSTERWALL, MONSTER_COLLISION_HIGHLIGHT)
615         }
616     }
617 }
618 
updateAmbientLayers(const float scrollX,const float scrollY)619 void Map::updateAmbientLayers(const float scrollX,
620                               const float scrollY) restrict2
621 {
622     BLOCK_START("Map::updateAmbientLayers")
623     static int lastTick = tick_time;
624 
625     if (mLastAScrollX == 0.0F && mLastAScrollY == 0.0F)
626     {
627         // First call - initialisation
628         mLastAScrollX = scrollX;
629         mLastAScrollY = scrollY;
630     }
631 
632     // Update Overlays
633     const float dx = scrollX - mLastAScrollX;
634     const float dy = scrollY - mLastAScrollY;
635     const int timePassed = get_elapsed_time(lastTick);
636 
637     // need check mask to update or not to update
638 
639     FOR_EACH (AmbientLayerVectorIter, i, mBackgrounds)
640     {
641         AmbientLayer *const layer = *i;
642         if ((layer != nullptr) && ((layer->mMask & mMask) != 0))
643             layer->update(timePassed, dx, dy);
644     }
645 
646     FOR_EACH (AmbientLayerVectorIter, i, mForegrounds)
647     {
648         AmbientLayer *const layer = *i;
649         if ((layer != nullptr) && ((layer->mMask & mMask) != 0))
650             layer->update(timePassed, dx, dy);
651     }
652 
653     mLastAScrollX = scrollX;
654     mLastAScrollY = scrollY;
655     lastTick = tick_time;
656     BLOCK_END("Map::updateAmbientLayers")
657 }
658 
drawAmbientLayers(Graphics * restrict const graphics,const MapLayerPositionT type,const int detail) const659 void Map::drawAmbientLayers(Graphics *restrict const graphics,
660                             const MapLayerPositionT type,
661                             const int detail) const restrict2
662 {
663     BLOCK_START("Map::drawAmbientLayers")
664     // Detail 0 = no ambient effects except background image
665     if (detail <= 0 && type != MapLayerPosition::BACKGROUND_LAYERS)
666     {
667         BLOCK_END("Map::drawAmbientLayers")
668         return;
669     }
670 
671     // find out which layer list to draw
672     const AmbientLayerVector *restrict layers = nullptr;
673     switch (type)
674     {
675         case MapLayerPosition::FOREGROUND_LAYERS:
676             layers = &mForegrounds;
677             break;
678         case MapLayerPosition::BACKGROUND_LAYERS:
679             layers = &mBackgrounds;
680             break;
681         default:
682             return;
683     }
684 
685     // Draw overlays
686     FOR_EACHP (AmbientLayerVectorCIter, i, layers)
687     {
688         const AmbientLayer *restrict const layer = *i;
689         // need check mask to draw or not to draw
690         if ((layer != nullptr) && ((layer->mMask & mMask) != 0))
691             (layer)->draw(graphics, graphics->mWidth, graphics->mHeight);
692 
693         // Detail 1: only one overlay, higher: all overlays
694         if (detail == 1)
695             break;
696     }
697     BLOCK_END("Map::drawAmbientLayers")
698 }
699 
getTilesetWithGid(const int gid) const700 const Tileset *Map::getTilesetWithGid(const int gid) const restrict2
701 {
702     if (gid >= 0 && gid < mIndexedTilesetsSize)
703         return mIndexedTilesets[gid];
704     return nullptr;
705 }
706 
addBlockMask(const int x,const int y,const BlockTypeT type)707 void Map::addBlockMask(const int x, const int y,
708                        const BlockTypeT type) restrict2
709 {
710     if (type == BlockType::NONE || !contains(x, y))
711         return;
712 
713     const int tileNum = x + y * mWidth;
714 
715     switch (type)
716     {
717         case BlockType::WALL:
718             mMetaTiles[tileNum].blockmask |= BlockMask::WALL;
719             break;
720         case BlockType::AIR:
721             mMetaTiles[tileNum].blockmask |= BlockMask::AIR;
722             break;
723         case BlockType::WATER:
724             mMetaTiles[tileNum].blockmask |= BlockMask::WATER;
725             break;
726         case BlockType::GROUND:
727             mMetaTiles[tileNum].blockmask |= BlockMask::GROUND;
728             break;
729         case BlockType::GROUNDTOP:
730             mMetaTiles[tileNum].blockmask |= BlockMask::GROUNDTOP;
731             break;
732         case BlockType::PLAYERWALL:
733             mMetaTiles[tileNum].blockmask |= BlockMask::PLAYERWALL;
734             break;
735         case BlockType::MONSTERWALL:
736             mMetaTiles[tileNum].blockmask |= BlockMask::MONSTERWALL;
737             break;
738         default:
739         case BlockType::NONE:
740         case BlockType::NB_BLOCKTYPES:
741             // Do nothing.
742             break;
743     }
744 }
745 
setBlockMask(const int x,const int y,const BlockTypeT type)746 void Map::setBlockMask(const int x, const int y,
747                        const BlockTypeT type) restrict2
748 {
749     if (type == BlockType::NONE || !contains(x, y))
750         return;
751 
752     const int tileNum = x + y * mWidth;
753 
754     switch (type)
755     {
756         case BlockType::WALL:
757             mMetaTiles[tileNum].blockmask = BlockMask::WALL;
758             break;
759         case BlockType::AIR:
760             mMetaTiles[tileNum].blockmask = BlockMask::AIR;
761             break;
762         case BlockType::WATER:
763             mMetaTiles[tileNum].blockmask = BlockMask::WATER;
764             break;
765         case BlockType::GROUND:
766             mMetaTiles[tileNum].blockmask = BlockMask::GROUND;
767             break;
768         case BlockType::GROUNDTOP:
769             mMetaTiles[tileNum].blockmask = BlockMask::GROUNDTOP;
770             break;
771         case BlockType::PLAYERWALL:
772             mMetaTiles[tileNum].blockmask = BlockMask::PLAYERWALL;
773             break;
774         case BlockType::MONSTERWALL:
775             mMetaTiles[tileNum].blockmask = BlockMask::MONSTERWALL;
776             break;
777         default:
778         case BlockType::NONE:
779         case BlockType::NB_BLOCKTYPES:
780             // Do nothing.
781             break;
782     }
783 }
784 
getWalk(const int x,const int y,const unsigned char blockWalkMask) const785 bool Map::getWalk(const int x, const int y,
786                   const unsigned char blockWalkMask) const restrict2
787 {
788     // You can't walk outside of the map
789     if (x < 0 || y < 0 || x >= mWidth || y >= mHeight)
790         return false;
791 
792     // Check if the tile is walkable
793     return (mMetaTiles[x + y * mWidth].blockmask & blockWalkMask) == 0;
794 }
795 
getBlockMask(const int x,const int y) const796 unsigned char Map::getBlockMask(const int x,
797                                 const int y) const restrict2
798 {
799     // You can't walk outside of the map
800     if (x < 0 || y < 0 || x >= mWidth || y >= mHeight)
801         return 0;
802 
803     // Check if the tile is walkable
804     return mMetaTiles[x + y * mWidth].blockmask;
805 }
806 
setWalk(const int x,const int y)807 void Map::setWalk(const int x, const int y) restrict2
808 {
809     addBlockMask(x, y, BlockType::GROUNDTOP);
810 }
811 
contains(const int x,const int y) const812 bool Map::contains(const int x, const int y) const restrict2
813 {
814     return x >= 0 && y >= 0 && x < mWidth && y < mHeight;
815 }
816 
getMetaTile(const int x,const int y) const817 const MetaTile *Map::getMetaTile(const int x, const int y) const restrict2
818 {
819     return &mMetaTiles[x + y * mWidth];
820 }
821 
addActor(Actor * const actor)822 Actors::iterator Map::addActor(Actor *const actor) restrict2
823 {
824     mActors.push_front(actor);
825 //    mSpritesUpdated = true;
826     return mActors.begin();
827 }
828 
removeActor(const Actors::iterator & restrict iterator)829 void Map::removeActor(const Actors::iterator &restrict iterator) restrict2
830 {
831     mActors.erase(iterator);
832 //    mSpritesUpdated = true;
833 }
834 
getMusicFile() const835 const std::string Map::getMusicFile() const restrict2
836 {
837     return getProperty("music", std::string());
838 }
839 
getName() const840 const std::string Map::getName() const restrict2
841 {
842     if (hasProperty("name"))
843         return getProperty("name", std::string());
844 
845     return getProperty("mapname", std::string());
846 }
847 
getFilename() const848 const std::string Map::getFilename() const restrict2
849 {
850     const std::string fileName = getProperty("_filename", std::string());
851     const size_t lastSlash = fileName.rfind('/') + 1;
852     return fileName.substr(lastSlash, fileName.rfind('.') - lastSlash);
853 }
854 
getGatName() const855 const std::string Map::getGatName() const restrict2
856 {
857     const std::string fileName = getProperty("_filename", std::string());
858     const size_t lastSlash = rfindSepatator(fileName) + 1;
859     return fileName.substr(lastSlash,
860         fileName.rfind('.') - lastSlash).append(".gat");
861 }
862 
findPath(const int startX,const int startY,const int destX,const int destY,const unsigned char blockWalkMask,const int maxCost)863 Path Map::findPath(const int startX, const int startY,
864                    const int destX, const int destY,
865                    const unsigned char blockWalkMask,
866                    const int maxCost) restrict2
867 {
868     BLOCK_START("Map::findPath")
869     // The basic walking cost of a tile.
870     static const int basicCost = 100;
871     const int basicCost2 = 100 * 362 / 256;
872     const float basicCostF = 100.0 * 362 / 256;
873 
874     // Path to be built up (empty by default)
875     Path path;
876 
877     if (startX >= mWidth || startY >= mHeight || startX < 0 || startY < 0)
878     {
879         BLOCK_END("Map::findPath")
880         return path;
881     }
882 
883     // Return when destination not walkable
884     if (!getWalk(destX, destY, blockWalkMask))
885     {
886         BLOCK_END("Map::findPath")
887         return path;
888     }
889 
890     // Reset starting tile's G cost to 0
891     MetaTile *const startTile = &mMetaTiles[startX + startY * mWidth];
892     if (startTile == nullptr)
893     {
894         BLOCK_END("Map::findPath")
895         return path;
896     }
897 
898     startTile->Gcost = 0;
899 
900     // Declare open list, a list with open tiles sorted on F cost
901     std::priority_queue<Location> openList;
902 
903     // Add the start point to the open list
904     openList.push(Location(startX, startY, startTile));
905 
906     bool foundPath = false;
907 
908     // Keep trying new open tiles until no more tiles to try or target found
909     while (!openList.empty() && !foundPath)
910     {
911         // Take the location with the lowest F cost from the open list.
912         const Location curr = openList.top();
913         openList.pop();
914 
915         const MetaTile *const tile = curr.tile;
916 
917         // If the tile is already on the closed list, this means it has already
918         // been processed with a shorter path to the start point (lower G cost)
919         if (tile->whichList == mOnClosedList)
920             continue;
921 
922         // Put the current tile on the closed list
923         curr.tile->whichList = mOnClosedList;
924 
925         const int curWidth = curr.y * mWidth;
926         const int tileGcost = tile->Gcost;
927 
928         // Check the adjacent tiles
929         for (int dy = -1; dy <= 1; dy++)
930         {
931             const int y = curr.y + dy;
932             if (y < 0 || y >= mHeight)
933                 continue;
934 
935             const int yWidth = y * mWidth;
936             const int dy1 = std::abs(y - destY);
937 
938             for (int dx = -1; dx <= 1; dx++)
939             {
940                 // Calculate location of tile to check
941                 const int x = curr.x + dx;
942 
943                 // Skip if if we're checking the same tile we're leaving from,
944                 // or if the new location falls outside of the map boundaries
945                 if ((dx == 0 && dy == 0) || x < 0 || x >= mWidth)
946                     continue;
947 
948                 MetaTile *const newTile = &mMetaTiles[x + yWidth];
949 
950                 // Skip if the tile is on the closed list or is not walkable
951                 // unless its the destination tile
952                 // +++ probably here "newTile->blockmask & BlockMask::WALL"
953                 // can be removed. It left here only for protect from
954                 // walk on wall in any case
955                 if (newTile->whichList == mOnClosedList ||
956                     (((newTile->blockmask & blockWalkMask) != 0)
957                     && !(x == destX && y == destY))
958                     || ((newTile->blockmask & BlockMask::WALL) != 0))
959                 {
960                     continue;
961                 }
962 
963                 // When taking a diagonal step, verify that we can skip the
964                 // corner.
965                 if (dx != 0 && dy != 0)
966                 {
967                     const MetaTile *const t1 = &mMetaTiles[curr.x +
968                         (curr.y + dy) * mWidth];
969                     const MetaTile *const t2 = &mMetaTiles[curr.x +
970                         dx + curWidth];
971 
972                     // on player abilities.
973                     if (((t1->blockmask | t2->blockmask) & blockWalkMask) != 0)
974                         continue;
975                 }
976 
977                 // Calculate G cost for this route, ~sqrt(2) for moving diagonal
978                 int Gcost = tileGcost + (dx == 0 || dy == 0
979                     ? basicCost : basicCost2);
980 
981                 /* Demote an arbitrary direction to speed pathfinding by
982                    adding a defect
983                    Important: as long as the total defect along any path is
984                    less than the basicCost, the pathfinder will still find one
985                    of the shortest paths! */
986                 if (dx == 0 || dy == 0)
987                 {
988                     // Demote horizontal and vertical directions, so that two
989                     // consecutive directions cannot have the same Fcost.
990                     ++Gcost;
991                 }
992 
993 /*
994                 // It costs extra to walk through a being (needs to be enough
995                 // to make it more attractive to walk around).
996                 if (occupied(x, y))
997                 {
998                     Gcost += 3 * basicCost;
999                 }
1000 */
1001 
1002                 // Skip if Gcost becomes too much
1003                 // Warning: probably not entirely accurate
1004                 if (maxCost > 0 && Gcost > maxCost * basicCost)
1005                     continue;
1006 
1007                 if (newTile->whichList != mOnOpenList)
1008                 {
1009                     // Found a new tile (not on open nor on closed list)
1010 
1011                     /* Update Hcost of the new tile. The pathfinder does not
1012                        work reliably if the heuristic cost is higher than the
1013                        real cost. In particular, using Manhattan distance is
1014                        forbidden here. */
1015                     const int dx1 = std::abs(x - destX);
1016                     newTile->Hcost = std::abs(dx1 - dy1) * basicCost +
1017                         CAST_S32(static_cast<float>(std::min(dx1, dy1)) *
1018                         (basicCostF));
1019 
1020                     // Set the current tile as the parent of the new tile
1021                     newTile->parentX = curr.x;
1022                     newTile->parentY = curr.y;
1023 
1024                     // Update Gcost and Fcost of new tile
1025                     newTile->Gcost = Gcost;
1026                     newTile->Fcost = Gcost + newTile->Hcost;
1027 
1028                     if (x != destX || y != destY)
1029                     {
1030                         // Add this tile to the open list
1031                         newTile->whichList = mOnOpenList;
1032                         openList.push(Location(x, y, newTile));
1033                     }
1034                     else
1035                     {
1036                         // Target location was found
1037                         foundPath = true;
1038                     }
1039                 }
1040                 else if (Gcost < newTile->Gcost)
1041                 {
1042                     // Found a shorter route.
1043                     // Update Gcost and Fcost of the new tile
1044                     newTile->Gcost = Gcost;
1045                     newTile->Fcost = Gcost + newTile->Hcost;
1046 
1047                     // Set the current tile as the parent of the new tile
1048                     newTile->parentX = curr.x;
1049                     newTile->parentY = curr.y;
1050 
1051                     // Add this tile to the open list (it's already
1052                     // there, but this instance has a lower F score)
1053                     openList.push(Location(x, y, newTile));
1054                 }
1055             }
1056         }
1057     }
1058 
1059     // Two new values to indicate whether a tile is on the open or closed list,
1060     // this way we don't have to clear all the values between each pathfinding.
1061     if (mOnOpenList > UINT_MAX - 2)
1062     {
1063         // We reset the list memebers value.
1064         mOnClosedList = 1;
1065         mOnOpenList = 2;
1066 
1067         // Clean up the metaTiles
1068         const int size = mWidth * mHeight;
1069         for (int i = 0; i < size; ++i)
1070             mMetaTiles[i].whichList = 0;
1071     }
1072     else
1073     {
1074         mOnClosedList += 2;
1075         mOnOpenList += 2;
1076     }
1077 
1078     // If a path has been found, iterate backwards using the parent locations
1079     // to extract it.
1080     if (foundPath)
1081     {
1082         int pathX = destX;
1083         int pathY = destY;
1084 
1085         while (pathX != startX || pathY != startY)
1086         {
1087             // Add the new path node to the start of the path list
1088             path.push_front(Position(pathX, pathY));
1089 
1090             // Find out the next parent
1091             const MetaTile *const tile = &mMetaTiles[pathX + pathY * mWidth];
1092             pathX = tile->parentX;
1093             pathY = tile->parentY;
1094         }
1095     }
1096 
1097     BLOCK_END("Map::findPath")
1098     return path;
1099 }
1100 
addParticleEffect(const std::string & effectFile,const int x,const int y,const int w,const int h)1101 void Map::addParticleEffect(const std::string &effectFile,
1102                             const int x, const int y,
1103                             const int w, const int h) restrict2
1104 {
1105     ParticleEffectData newEffect(effectFile, x, y, w, h);
1106     mParticleEffects.push_back(newEffect);
1107 }
1108 
initializeParticleEffects() const1109 void Map::initializeParticleEffects() const restrict2
1110 {
1111     BLOCK_START("Map::initializeParticleEffects")
1112     if (particleEngine == nullptr)
1113     {
1114         BLOCK_END("Map::initializeParticleEffects")
1115         return;
1116     }
1117 
1118     if (config.getBoolValue("particleeffects"))
1119     {
1120         for (STD_VECTOR<ParticleEffectData>::const_iterator
1121              i = mParticleEffects.begin();
1122              i != mParticleEffects.end();
1123              ++i)
1124         {
1125             // +++ add z for map particle effects?
1126             Particle *const p = particleEngine->addEffect(
1127                 i->file,
1128                 i->x,
1129                 i->y,
1130                 0);
1131             if ((p != nullptr) &&
1132                 i->w > 0 &&
1133                 i->h > 0)
1134             {
1135                 p->adjustEmitterSize(i->w, i->h);
1136             }
1137         }
1138     }
1139      BLOCK_END("Map::initializeParticleEffects")
1140 }
1141 
addExtraLayer()1142 void Map::addExtraLayer() restrict2
1143 {
1144     BLOCK_START("Map::addExtraLayer")
1145     if (mSpecialLayer == nullptr)
1146     {
1147         logger->log1("No special layer");
1148         BLOCK_END("Map::addExtraLayer")
1149         return;
1150     }
1151     const std::string mapFileName = pathJoin(getUserMapDirectory(),
1152         "extralayer.txt");
1153     logger->log("loading extra layer: " + mapFileName);
1154     struct stat statbuf;
1155     if (stat(mapFileName.c_str(), &statbuf) == 0 &&
1156         S_ISREG(statbuf.st_mode))
1157     {
1158         std::ifstream mapFile;
1159         mapFile.open(mapFileName.c_str(), std::ios::in);
1160         if (!mapFile.is_open())
1161         {
1162             mapFile.close();
1163             BLOCK_END("Map::addExtraLayer")
1164             return;
1165         }
1166         char line[201];
1167 
1168         while (mapFile.getline(line, 200))
1169         {
1170             std::string buf;
1171             std::string str = line;
1172             if (!str.empty())
1173             {
1174                 std::string x;
1175                 std::string y;
1176                 std::string type1;
1177                 std::string comment;
1178                 std::stringstream ss(str);
1179                 ss >> x;
1180                 ss >> y;
1181                 ss >> type1;
1182                 ss >> comment;
1183                 while (ss >> buf)
1184                     comment.append(" ").append(buf);
1185 
1186                 const int type = atoi(type1.c_str());
1187 
1188                 if (comment.empty())
1189                 {
1190                     if (type < MapItemType::ARROW_UP
1191                         || type > MapItemType::ARROW_RIGHT)
1192                     {
1193                         comment = "unknown";
1194                     }
1195                 }
1196                 if (type == MapItemType::PORTAL)
1197                 {
1198                     updatePortalTile(comment,
1199                         type,
1200                         atoi(x.c_str()),
1201                         atoi(y.c_str()),
1202                         false);
1203                 }
1204                 else if (type == MapItemType::HOME)
1205                 {
1206                     updatePortalTile(comment,
1207                         type,
1208                         atoi(x.c_str()),
1209                         atoi(y.c_str()),
1210                         true);
1211                 }
1212                 else
1213                 {
1214                     addPortalTile(comment,
1215                         type,
1216                         atoi(x.c_str()),
1217                         atoi(y.c_str()));
1218                 }
1219             }
1220         }
1221         mapFile.close();
1222     }
1223     BLOCK_END("Map::addExtraLayer")
1224 }
1225 
saveExtraLayer() const1226 void Map::saveExtraLayer() const restrict2
1227 {
1228     if (mSpecialLayer == nullptr)
1229     {
1230         logger->log1("No special layer");
1231         return;
1232     }
1233     const std::string mapFileName = pathJoin(getUserMapDirectory(),
1234         "extralayer.txt");
1235     logger->log("saving extra layer: " + mapFileName);
1236 
1237     if (mkdir_r(getUserMapDirectory().c_str()) != 0)
1238     {
1239         logger->log(strprintf("%s doesn't exist and can't be created! "
1240                               "Exiting.", getUserMapDirectory().c_str()));
1241         return;
1242     }
1243 
1244     std::ofstream mapFile;
1245     mapFile.open(mapFileName.c_str(), std::ios::binary);
1246     if (!mapFile.is_open())
1247     {
1248         reportAlways("Error opening file for writing: %s",
1249             mapFileName.c_str())
1250         return;
1251     }
1252 
1253     const int width = mSpecialLayer->mWidth;
1254     const int height = mSpecialLayer->mHeight;
1255 
1256     for (int x = 0; x < width; x ++)
1257     {
1258         for (int y = 0; y < height; y ++)
1259         {
1260             const MapItem *restrict const item = mSpecialLayer->getTile(x, y);
1261             if ((item != nullptr) && item->mType != MapItemType::EMPTY
1262                 && item->mType != MapItemType::HOME)
1263             {
1264                 mapFile << x << " " << y << " "
1265                     << CAST_S32(item->mType) << " "
1266                     << item->mComment << std::endl;
1267             }
1268         }
1269     }
1270     mapFile.close();
1271 }
1272 
getUserMapDirectory() const1273 std::string Map::getUserMapDirectory() const restrict2
1274 {
1275     return pathJoin(settings.serverConfigDir,
1276         getProperty("_realfilename", std::string()));
1277 }
1278 
addRange(const std::string & restrict name,const int type,const int x,const int y,const int dx,const int dy)1279 void Map::addRange(const std::string &restrict name,
1280                    const int type,
1281                    const int x, const int y,
1282                    const int dx, const int dy) restrict2
1283 {
1284     if (mObjects == nullptr)
1285         return;
1286 
1287     mObjects->addObject(name, type, x / mapTileSize, y / mapTileSize,
1288         dx / mapTileSize, dy / mapTileSize);
1289 }
1290 
addPortal(const std::string & restrict name,const int type,const int x,const int y,const int dx,const int dy)1291 void Map::addPortal(const std::string &restrict name,
1292                     const int type,
1293                     const int x, const int y,
1294                     const int dx, const int dy) restrict2
1295 {
1296     addPortalTile(name, type, (x / mapTileSize) + (dx / mapTileSize / 2),
1297         (y / mapTileSize) + (dy / mapTileSize / 2));
1298 }
1299 
addPortalTile(const std::string & restrict name,const int type,const int x,const int y)1300 void Map::addPortalTile(const std::string &restrict name,
1301                         const int type,
1302                         const int x, const int y) restrict2
1303 {
1304     if (mSpecialLayer != nullptr)
1305     {
1306         mSpecialLayer->setTile(x, y, new MapItem(type, name, x, y));
1307         mSpecialLayer->updateCache();
1308     }
1309 
1310     mMapPortals.push_back(new MapItem(type, name, x, y));
1311 }
1312 
updatePortalTile(const std::string & restrict name,const int type,const int x,const int y,const bool addNew)1313 void Map::updatePortalTile(const std::string &restrict name,
1314                            const int type,
1315                            const int x, const int y,
1316                            const bool addNew) restrict2
1317 {
1318     MapItem *restrict item = findPortalXY(x, y);
1319     if (item != nullptr)
1320     {
1321         item->mComment = name;
1322         item->setType(type);
1323         item->mX = x;
1324         item->mY = y;
1325         if (mSpecialLayer != nullptr)
1326         {
1327             item = new MapItem(type, name, x, y);
1328             mSpecialLayer->setTile(x, y, item);
1329             mSpecialLayer->updateCache();
1330         }
1331     }
1332     else if (addNew)
1333     {
1334         addPortalTile(name, type, x, y);
1335     }
1336 }
1337 
findPortalXY(const int x,const int y) const1338 MapItem *Map::findPortalXY(const int x, const int y) const restrict2
1339 {
1340     FOR_EACH (STD_VECTOR<MapItem*>::const_iterator, it, mMapPortals)
1341     {
1342         if (*it == nullptr)
1343             continue;
1344 
1345         MapItem *restrict const item = *it;
1346         if (item->mX == x && item->mY == y)
1347             return item;
1348     }
1349     return nullptr;
1350 }
1351 
getAnimationForGid(const int gid) const1352 const TileAnimation *Map::getAnimationForGid(const int gid) const restrict2
1353 {
1354     if (mTileAnimations.empty())
1355         return nullptr;
1356 
1357     const TileAnimationMapCIter i = mTileAnimations.find(gid);
1358     return (i == mTileAnimations.end()) ? nullptr : i->second;
1359 }
1360 
setPvpMode(const int mode)1361 void Map::setPvpMode(const int mode) restrict2
1362 {
1363     const int oldMode = mPvp;
1364 
1365     if (mode == 0)
1366         mPvp = 0;
1367     else
1368         mPvp |= mode;
1369 
1370     if (mPvp != oldMode && (localPlayer != nullptr))
1371     {
1372         switch (mPvp)
1373         {
1374             case 0:
1375                 NotifyManager::notify(NotifyTypes::PVP_OFF_GVG_OFF);
1376                 break;
1377             case 1:
1378                 NotifyManager::notify(NotifyTypes::PVP_ON);
1379                 break;
1380             case 2:
1381                 NotifyManager::notify(NotifyTypes::GVG_ON);
1382                 break;
1383             case 3:
1384                 NotifyManager::notify(NotifyTypes::PVP_ON_GVG_ON);
1385                 break;
1386             default:
1387                 NotifyManager::notify(NotifyTypes::PVP_UNKNOWN);
1388                 break;
1389         }
1390     }
1391 }
1392 
getObjectData(const unsigned x,const unsigned y,const int type) const1393 std::string Map::getObjectData(const unsigned x, const unsigned y,
1394                                const int type) const restrict2
1395 {
1396     if (mObjects == nullptr)
1397         return "";
1398 
1399     MapObjectList *restrict const list = mObjects->getAt(x, y);
1400     if (list == nullptr)
1401         return "";
1402 
1403     STD_VECTOR<MapObject>::const_iterator it = list->objects.begin();
1404     const STD_VECTOR<MapObject>::const_iterator it_end = list->objects.end();
1405     while (it != it_end)
1406     {
1407         if ((*it).type == type)
1408             return (*it).data;
1409         ++ it;
1410     }
1411 
1412     return "";
1413 }
1414 
indexTilesets()1415 void Map::indexTilesets() restrict2
1416 {
1417     if (mTilesetsIndexed)
1418         return;
1419 
1420     mTilesetsIndexed = true;
1421 
1422     const Tileset *restrict s = nullptr;
1423     size_t sSz = 0;
1424     FOR_EACH (Tilesets::const_iterator, it, mTilesets)
1425     {
1426         const size_t sz = (*it)->size();
1427         if ((s == nullptr) || CAST_SIZE(s->getFirstGid()) + sSz
1428             < CAST_SIZE((*it)->getFirstGid()) + sz)
1429         {
1430             s = *it;
1431             sSz = sz;
1432         }
1433     }
1434     if (s == nullptr)
1435     {
1436         mIndexedTilesetsSize = 0;
1437         mIndexedTilesets = nullptr;
1438         return;
1439     }
1440 
1441     const int size = CAST_S32(s->getFirstGid())
1442         + CAST_S32(s->size());
1443     mIndexedTilesetsSize = size;
1444     mIndexedTilesets = new Tileset*[CAST_SIZE(size)];
1445     std::fill_n(mIndexedTilesets, size, static_cast<Tileset*>(nullptr));
1446 
1447     FOR_EACH (Tilesets::const_iterator, it, mTilesets)
1448     {
1449         Tileset *restrict const s2 = *it;
1450         if (s2 != nullptr)
1451         {
1452             const int start = s2->getFirstGid();
1453             const int end = start + CAST_S32(s2->size());
1454             for (int f = start; f < end; f ++)
1455             {
1456                 if (f < size)
1457                     mIndexedTilesets[f] = s2;
1458             }
1459         }
1460     }
1461 }
1462 
clearIndexedTilesets()1463 void Map::clearIndexedTilesets() restrict2
1464 {
1465     if (!mTilesetsIndexed)
1466         return;
1467 
1468     mTilesetsIndexed = false;
1469     delete [] mIndexedTilesets;
1470     mIndexedTilesetsSize = 0;
1471 }
1472 
reduce()1473 void Map::reduce() restrict2
1474 {
1475 #ifndef USE_SDL2
1476     if ((mFringeLayer == nullptr) ||
1477         mOpenGL != RENDER_SOFTWARE ||
1478         !config.getBoolValue("enableMapReduce"))
1479     {
1480         return;
1481     }
1482 
1483     int cnt = 0;
1484     for (int x = 0; x < mWidth; x ++)
1485     {
1486         for (int y = 0; y < mHeight; y ++)
1487         {
1488             bool correct(true);
1489             bool dontHaveAlpha(false);
1490 
1491             FOR_EACH (LayersCIter, layeri, mLayers)
1492             {
1493                 const MapLayer *restrict const layer = *layeri;
1494                 if (x >= layer->mWidth || y >= layer->mHeight)
1495                     continue;
1496 
1497                 // skip layers with flags
1498                 if (layer->mTileCondition != -1 || layer->mMask != 1)
1499                     continue;
1500 
1501                 Image *restrict const img =
1502                     layer->mTiles[x + y * layer->mWidth].image;
1503                 if (img != nullptr)
1504                 {
1505                     if (img->hasAlphaChannel() && img->isAlphaCalculated())
1506                     {
1507                         if (!img->isAlphaVisible())
1508                         {
1509                             dontHaveAlpha = true;
1510                             img->setAlphaVisible(false);
1511                         }
1512                     }
1513                     else if (img->mBounds.w > mapTileSize
1514                              || img->mBounds.h > mapTileSize)
1515                     {
1516                         correct = false;
1517                         img->setAlphaVisible(true);
1518                         break;
1519                     }
1520                     else if (!img->isHasAlphaChannel())
1521                     {
1522                         dontHaveAlpha = true;
1523                         img->setAlphaVisible(false);
1524                     }
1525                     else if (img->hasAlphaChannel())
1526                     {
1527                         const uint8_t *restrict const arr =
1528                             img->SDLgetAlphaChannel();
1529                         if (arr == nullptr)
1530                             continue;
1531 
1532                         bool bad(false);
1533                         bool stop(false);
1534                         int width;
1535                         const SubImage *restrict const subImg
1536                             = dynamic_cast<SubImage*>(img);
1537                         if (subImg != nullptr)
1538                             width = subImg->mInternalBounds.w;
1539                         else
1540                             width = img->mBounds.w;
1541 
1542                         for (int f = img->mBounds.x;
1543                              f < img->mBounds.x + img->mBounds.w; f ++)
1544                         {
1545                             for (int d = img->mBounds.y;
1546                                  d < img->mBounds.y + img->mBounds.h; d ++)
1547                             {
1548                                 const uint8_t chan = arr[f + d * width];
1549                                 if (chan != 255)
1550                                 {
1551                                     bad = true;
1552                                     stop = true;
1553                                     break;
1554                                 }
1555                             }
1556                             if (stop)
1557                                 break;
1558                         }
1559                         if (!bad)
1560                         {
1561                             dontHaveAlpha = true;
1562                             img->setAlphaVisible(false);
1563                         }
1564                         else
1565                         {
1566                             img->setAlphaVisible(true);
1567                         }
1568                     }
1569                     img->setAlphaCalculated(true);
1570                 }
1571             }
1572             if (!correct || !dontHaveAlpha)
1573                 continue;
1574 
1575             Layers::reverse_iterator ri = mLayers.rbegin();
1576             while (ri != mLayers.rend())
1577             {
1578                 const MapLayer *restrict const layer = *ri;
1579                 if (x >= layer->mWidth || y >= layer->mHeight)
1580                 {
1581                     ++ ri;
1582                     continue;
1583                 }
1584 
1585                 // skip layers with flags
1586                 if (layer->mTileCondition != -1 || layer->mMask != 1)
1587                 {
1588                     ++ ri;
1589                     continue;
1590                 }
1591 
1592                 const Image *restrict img =
1593                     layer->mTiles[x + y * layer->mWidth].image;
1594                 if ((img != nullptr) && !img->isAlphaVisible())
1595                 {   // removing all down tiles
1596                     ++ ri;
1597                     while (ri != mLayers.rend())
1598                     {
1599                         MapLayer *restrict const layer2 = *ri;
1600                         // skip layers with flags
1601                         if (layer2->mTileCondition != -1 || layer2->mMask != 1)
1602                         {
1603                             ++ ri;
1604                             continue;
1605                         }
1606                         const size_t pos = x + y * CAST_SIZE(layer2->mWidth);
1607                         img = layer2->mTiles[pos].image;
1608                         if (img != nullptr)
1609                         {
1610                             layer2->mTiles[pos].image = nullptr;
1611                             cnt ++;
1612                         }
1613                         ++ ri;
1614                     }
1615                     break;
1616                 }
1617                 ++ ri;
1618             }
1619         }
1620     }
1621     logger->log("tiles reduced: %d", cnt);
1622 #endif  // USE_SDL2
1623 }
1624 
addHeights(const MapHeights * restrict const heights)1625 void Map::addHeights(const MapHeights *restrict const heights) restrict2
1626 {
1627     delete mHeights;
1628     mHeights = heights;
1629 }
1630 
getHeightOffset(const int x,const int y) const1631 uint8_t Map::getHeightOffset(const int x, const int y) const restrict2
1632 {
1633     if (mHeights == nullptr)
1634         return 0;
1635     return mHeights->getHeight(x, y);
1636 }
1637 
updateDrawLayersList()1638 void Map::updateDrawLayersList() restrict2
1639 {
1640     mDrawUnderLayers.clear();
1641     mDrawOverLayers.clear();
1642 
1643     if (mDrawOnlyFringe)
1644         return;
1645 
1646     LayersCIter layers = mLayers.begin();
1647     const LayersCIter layers_end = mLayers.end();
1648     for (; layers != layers_end; ++ layers)
1649     {
1650         MapLayer *const layer = *layers;
1651         if ((layer->mMask & mMask) == 0)
1652             continue;
1653 
1654         if (layer->isFringeLayer())
1655         {
1656             ++ layers;
1657             break;
1658         }
1659 
1660         mDrawUnderLayers.push_back(layer);
1661     }
1662 
1663     if (mDrawLayersFlags == MapType::SPECIAL2)
1664         return;
1665 
1666     for (; layers != layers_end; ++ layers)
1667     {
1668         MapLayer *const layer = *layers;
1669         if ((layer->mMask & mMask) == 0)
1670             continue;
1671 
1672         mDrawOverLayers.push_back(layer);
1673     }
1674 }
1675 
setMask(const int mask)1676 void Map::setMask(const int mask) restrict2
1677 {
1678     if (mask != mMask)
1679     {
1680         mRedrawMap = true;
1681         mMask = mask;
1682         updateDrawLayersList();
1683     }
1684 }
1685 
setMusicFile(const std::string & restrict file)1686 void Map::setMusicFile(const std::string &restrict file) restrict2
1687 {
1688     setProperty("music", file);
1689 }
1690 
addAnimation(const int gid,TileAnimation * restrict const animation)1691 void Map::addAnimation(const int gid,
1692                        TileAnimation *restrict const animation) restrict2
1693 {
1694     std::map<int, TileAnimation*>::iterator it = mTileAnimations.find(gid);
1695     if (it != mTileAnimations.end())
1696     {
1697         logger->log("duplicate map animation with gid = %d", gid);
1698         delete (*it).second;
1699     }
1700     mTileAnimations[gid] = animation;
1701 }
1702 
setDrawLayersFlags(const MapTypeT & restrict n)1703 void Map::setDrawLayersFlags(const MapTypeT &restrict n) restrict2
1704 {
1705     mDrawLayersFlags = n;
1706     if (mDrawLayersFlags == MapType::SPECIAL3 ||
1707         mDrawLayersFlags == MapType::SPECIAL4 ||
1708         mDrawLayersFlags == MapType::BLACKWHITE)
1709     {
1710         mDrawOnlyFringe = true;
1711     }
1712     else
1713     {
1714         mDrawOnlyFringe = false;
1715     }
1716     updateDrawLayersList();
1717     FOR_EACH (Layers::iterator, it, mLayers)
1718     {
1719         MapLayer *restrict const layer = *it;
1720         layer->setDrawLayerFlags(mDrawLayersFlags);
1721     }
1722 }
1723 
setActorsFix(const int x,const int y)1724 void Map::setActorsFix(const int x, const int y) restrict2
1725 {
1726     mActorFixX = x;
1727     mActorFixY = y;
1728     if (mFringeLayer != nullptr)
1729         mFringeLayer->setActorsFix(y);
1730 }
1731 
updateConditionLayers()1732 void Map::updateConditionLayers() restrict2
1733 {
1734     mRedrawMap = true;
1735 
1736     FOR_EACH (LayersCIter, it, mLayers)
1737     {
1738         MapLayer *restrict const layer = *it;
1739         if ((layer == nullptr) || layer->mTileCondition == -1)
1740             continue;
1741         layer->updateConditionTiles(mMetaTiles,
1742             mWidth, mHeight);
1743     }
1744 }
1745 
preCacheLayers()1746 void Map::preCacheLayers() restrict2
1747 {
1748     FOR_EACH (LayersCIter, it, mLayers)
1749     {
1750         MapLayer *restrict const layer = *it;
1751         if (layer != nullptr)
1752             layer->updateCache(mWidth, mHeight);
1753     }
1754 }
1755 
calcMemoryLocal() const1756 int Map::calcMemoryLocal() const
1757 {
1758     return static_cast<int>(sizeof(Map) +
1759         mName.capacity() +
1760         sizeof(MetaTile) * mWidth * mHeight +
1761         sizeof(MapLayer*) * (mLayers.capacity() +
1762         mDrawUnderLayers.capacity() +
1763         mDrawOverLayers.capacity()) +
1764         sizeof(Tileset*) * mTilesets.capacity() +
1765         sizeof(Actor*) * mActors.size() +
1766         sizeof(AmbientLayer*) * (mBackgrounds.capacity()
1767         + mForegrounds.capacity()) +
1768         sizeof(ParticleEffectData) * mParticleEffects.capacity() +
1769         sizeof(MapItem) * mMapPortals.capacity() +
1770         (sizeof(TileAnimation) + sizeof(int)) * mTileAnimations.size() +
1771         sizeof(Tileset*) * mIndexedTilesetsSize);
1772 }
1773 
calcMemoryChilds(const int level) const1774 int Map::calcMemoryChilds(const int level) const
1775 {
1776     int sz = 0;
1777 
1778     if (mWalkLayer != nullptr)
1779         sz += mWalkLayer->calcMemory(level + 1);
1780     FOR_EACH (LayersCIter, it, mLayers)
1781     {
1782         sz += (*it)->calcMemory(level + 1);
1783     }
1784     FOR_EACH (Tilesets::const_iterator, it, mTilesets)
1785     {
1786         sz += (*it)->calcMemory(level + 1);
1787     }
1788     FOR_EACH (AmbientLayerVectorCIter, it, mBackgrounds)
1789     {
1790         sz += (*it)->calcMemory(level + 1);
1791     }
1792     FOR_EACH (AmbientLayerVectorCIter, it, mForegrounds)
1793     {
1794         sz += (*it)->calcMemory(level + 1);
1795     }
1796     if (mSpecialLayer != nullptr)
1797         mSpecialLayer->calcMemory(level + 1);
1798     if (mTempLayer != nullptr)
1799         mTempLayer->calcMemory(level + 1);
1800     if (mObjects != nullptr)
1801         mObjects->calcMemory(level + 1);
1802     if (mHeights != nullptr)
1803         mHeights->calcMemory(level + 1);
1804     return sz;
1805 }
1806 
screenResized()1807 void Map::screenResized()
1808 {
1809     if (mWidth * mapTileSize < mainGraphics->mWidth ||
1810         mHeight * mapTileSize < mainGraphics->mHeight)
1811     {
1812         mClear = true;
1813     }
1814     else
1815     {
1816         mClear = false;
1817     }
1818 }
1819 
1820 #ifdef USE_OPENGL
getAtlasCount() const1821 int Map::getAtlasCount() const restrict2
1822 {
1823     if (mAtlas == nullptr)
1824         return 0;
1825     return CAST_S32(mAtlas->atlases.size());
1826 }
1827 #endif  // USE_OPENGL
1828