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