diff --git a/VisualStudio/fheroes2/sources.props b/VisualStudio/fheroes2/sources.props index 85ef8521bf1..78ffea884ec 100644 --- a/VisualStudio/fheroes2/sources.props +++ b/VisualStudio/fheroes2/sources.props @@ -186,6 +186,7 @@ + @@ -370,6 +371,7 @@ + diff --git a/src/fheroes2/gui/interface_gamearea.cpp b/src/fheroes2/gui/interface_gamearea.cpp index 78474bb02c8..9febc73057e 100644 --- a/src/fheroes2/gui/interface_gamearea.cpp +++ b/src/fheroes2/gui/interface_gamearea.cpp @@ -47,6 +47,7 @@ #include "logging.h" #include "maps.h" #include "maps_tiles.h" +#include "maps_tiles_render.h" #include "pal.h" #include "players.h" #include "route.h" @@ -404,9 +405,9 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle int32_t maxY = tileROI.y + tileROI.height; #ifdef WITH_DEBUG - const bool drawFog = ( ( flag & LEVEL_FOG ) == LEVEL_FOG ) && !IS_DEVEL(); + const bool renderFog = ( ( flag & LEVEL_FOG ) == LEVEL_FOG ) && !IS_DEVEL(); #else - const bool drawFog = ( flag & LEVEL_FOG ) == LEVEL_FOG; + const bool renderFog = ( flag & LEVEL_FOG ) == LEVEL_FOG; #endif // Render terrain. @@ -415,19 +416,19 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle if ( offset.y < 0 || offset.y >= world.h() ) { for ( ; offset.x < maxX; ++offset.x ) { - Maps::Tiles::RedrawEmptyTile( dst, offset, *this ); + Maps::redrawEmptyTile( dst, offset, *this ); } } else { for ( ; offset.x < maxX; ++offset.x ) { if ( offset.x < 0 || offset.x >= world.w() ) { - Maps::Tiles::RedrawEmptyTile( dst, offset, *this ); + Maps::redrawEmptyTile( dst, offset, *this ); } else { const Maps::Tiles & tile = world.GetTiles( offset.x, offset.y ); // Do not render terrain on the tiles fully covered with the fog. - if ( tile.getFogDirection() != DIRECTION_ALL || !drawFog ) { - DrawTile( dst, tile.GetTileSurface(), offset ); + if ( tile.getFogDirection() != DIRECTION_ALL || !renderFog ) { + DrawTile( dst, getTileSurface( tile ), offset ); } } } @@ -487,7 +488,7 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle MP2::MapObjectType objectType = tile.GetObject(); // We will skip objects which are fully under the fog. - const bool isTileUnderFog = ( tile.getFogDirection() == DIRECTION_ALL ) && drawFog; + const bool isTileUnderFog = ( tile.getFogDirection() == DIRECTION_ALL ) && renderFog; switch ( objectType ) { case MP2::OBJ_HEROES: { @@ -528,8 +529,8 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle const uint8_t alphaValue = getObjectAlphaValue( tile.GetIndex(), MP2::OBJ_MONSTER ); - auto spriteInfo = tile.getMonsterSpritesPerTile(); - auto spriteShadowInfo = tile.getMonsterShadowSpritesPerTile(); + auto spriteInfo = getMonsterSpritesPerTile( tile ); + auto spriteShadowInfo = getMonsterShadowSpritesPerTile( tile ); populateStaticTileUnfitObjectInfo( tileUnfit, spriteInfo, spriteShadowInfo, tile.GetCenter(), alphaValue ); @@ -547,8 +548,8 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle const uint8_t alphaValue = getObjectAlphaValue( tile.GetIndex(), MP2::OBJ_BOAT ); - auto spriteInfo = tile.getBoatSpritesPerTile(); - auto spriteShadowInfo = tile.getBoatShadowSpritesPerTile(); + auto spriteInfo = getBoatSpritesPerTile( tile ); + auto spriteShadowInfo = getBoatShadowSpritesPerTile( tile ); populateStaticTileUnfitObjectInfo( tileUnfit, spriteInfo, spriteShadowInfo, tile.GetCenter(), alphaValue ); @@ -561,7 +562,7 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle // These are parts of original action objects which must be rendered under heroes. if ( objectType == MP2::OBJ_MINES ) { - auto spriteInfo = tile.getMineGuardianSpritesPerTile(); + auto spriteInfo = getMineGuardianSpritesPerTile( tile ); if ( !spriteInfo.empty() ) { const uint8_t alphaValue = getObjectAlphaValue( tile.GetObjectUID() ); populateStaticTileUnfitBackgroundObjectInfo( tileUnfit, spriteInfo, tile.GetCenter(), alphaValue ); @@ -575,14 +576,14 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle for ( int32_t x = minX; x < maxX; ++x ) { const Maps::Tiles & tile = world.GetTiles( x, y ); - if ( tile.getFogDirection() == DIRECTION_ALL && drawFog ) { + if ( tile.getFogDirection() == DIRECTION_ALL && renderFog ) { continue; } // Draw roads, rivers and cracks. - tile.redrawBottomLayerObjects( dst, isPuzzleDraw, *this, Maps::TERRAIN_LAYER ); + redrawBottomLayerObjects( tile, dst, isPuzzleDraw, *this, Maps::TERRAIN_LAYER ); - tile.redrawBottomLayerObjects( dst, isPuzzleDraw, *this, Maps::BACKGROUND_LAYER ); + redrawBottomLayerObjects( tile, dst, isPuzzleDraw, *this, Maps::BACKGROUND_LAYER ); } } @@ -593,11 +594,11 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle for ( int32_t x = minX; x < maxX; ++x ) { const Maps::Tiles & tile = world.GetTiles( x, y ); - if ( tile.getFogDirection() == DIRECTION_ALL && drawFog ) { + if ( tile.getFogDirection() == DIRECTION_ALL && renderFog ) { continue; } - tile.redrawBottomLayerObjects( dst, isPuzzleDraw, *this, Maps::SHADOW_LAYER ); + redrawBottomLayerObjects( tile, dst, isPuzzleDraw, *this, Maps::SHADOW_LAYER ); } } @@ -614,12 +615,12 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle for ( int32_t x = minX; x < maxX; ++x ) { const Maps::Tiles & tile = world.GetTiles( x, y ); - if ( tile.getFogDirection() == DIRECTION_ALL && drawFog ) { + if ( tile.getFogDirection() == DIRECTION_ALL && renderFog ) { continue; } // TODO: some action objects have tiles above which are still on bottom layer. These images must be drawn last. - tile.redrawBottomLayerObjects( dst, isPuzzleDraw, *this, Maps::OBJECT_LAYER ); + redrawBottomLayerObjects( tile, dst, isPuzzleDraw, *this, Maps::OBJECT_LAYER ); } } @@ -637,7 +638,7 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle for ( int32_t x = roiToRenderMinX; x < roiExtraObjectsMaxX; ++x ) { const Maps::Tiles & tile = world.GetTiles( x, y ); - const bool isTileUnderFog = ( tile.getFogDirection() == DIRECTION_ALL ) && drawFog; + const bool isTileUnderFog = ( tile.getFogDirection() == DIRECTION_ALL ) && renderFog; if ( isTileUnderFog ) { // To correctly render tall extra objects (ghosts over abandoned mine) it is needed to analyze one tile to the bottom direction under fog. const bool isUpperTileUnderFog = ( y > 0 ) ? ( world.GetTiles( x, y - 1 ).getFogDirection() == DIRECTION_ALL ) : true; @@ -646,7 +647,7 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle continue; } - tile.redrawTopLayerExtraObjects( dst, isPuzzleDraw, *this ); + redrawTopLayerExtraObjects( tile, dst, isPuzzleDraw, *this ); continue; } @@ -660,14 +661,14 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle topLayerTallObjects.emplace_back( &addon ); } else { - tile.redrawTopLayerObject( dst, isPuzzleDraw, *this, addon ); + redrawTopLayerObject( tile, dst, isPuzzleDraw, *this, addon ); } } - tile.redrawTopLayerExtraObjects( dst, isPuzzleDraw, *this ); + redrawTopLayerExtraObjects( tile, dst, isPuzzleDraw, *this ); for ( const Maps::TilesAddon * addon : topLayerTallObjects ) { - tile.redrawTopLayerObject( dst, isPuzzleDraw, *this, *addon ); + redrawTopLayerObject( tile, dst, isPuzzleDraw, *this, *addon ); } } } @@ -732,7 +733,7 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle for ( int32_t y = minY; y < maxY; ++y ) { for ( int32_t x = minX; x < maxX; ++x ) { - world.GetTiles( x, y ).RedrawPassable( dst, friendColors, *this ); + redrawPassable( world.GetTiles( x, y ), dst, friendColors, *this ); } } } @@ -740,20 +741,20 @@ void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzle else #endif // redraw fog - if ( drawFog ) { + if ( renderFog ) { for ( int32_t y = minY; y < maxY; ++y ) { for ( int32_t x = minX; x < maxX; ++x ) { const Maps::Tiles & tile = world.GetTiles( x, y ); if ( tile.getFogDirection() != Direction::UNKNOWN ) { - tile.drawFog( dst, *this ); + drawFog( tile, dst, *this ); if ( drawTowns ) { - tile.drawByObjectIcnType( dst, *this, MP2::OBJ_ICN_TYPE_OBJNTWBA ); + drawByObjectIcnType( tile, dst, *this, MP2::OBJ_ICN_TYPE_OBJNTWBA ); const MP2::MapObjectType objectType = tile.GetObject( false ); if ( objectType == MP2::OBJ_CASTLE || objectType == MP2::OBJ_NON_ACTION_CASTLE ) { - tile.drawByObjectIcnType( dst, *this, MP2::OBJ_ICN_TYPE_OBJNTOWN ); + drawByObjectIcnType( tile, dst, *this, MP2::OBJ_ICN_TYPE_OBJNTOWN ); } } } diff --git a/src/fheroes2/maps/maps_tiles.cpp b/src/fheroes2/maps/maps_tiles.cpp index 6d2ca332566..062cb8297bd 100644 --- a/src/fheroes2/maps/maps_tiles.cpp +++ b/src/fheroes2/maps/maps_tiles.cpp @@ -47,7 +47,6 @@ #include "heroes.h" #include "icn.h" #include "image.h" -#include "interface_gamearea.h" #include "logging.h" #include "maps.h" #include "maps_tiles_helper.h" // TODO: This file should not be included @@ -77,7 +76,6 @@ #include "til.h" #include "tools.h" #include "trees.h" -#include "ui_object_rendering.h" #include "world.h" namespace @@ -266,113 +264,6 @@ namespace } #endif - bool contains( const int base, const int value ) - { - return ( base & value ) == value; - } - -#ifdef WITH_DEBUG - const fheroes2::Image & PassableViewSurface( const int passable ) - { - static std::map imageMap; - - auto iter = imageMap.find( passable ); - if ( iter != imageMap.end() ) { - return iter->second; - } - - const int32_t size = 31; - const uint8_t red = 0xBA; - const uint8_t green = 0x5A; - - fheroes2::Image sf( size, size ); - sf.reset(); - - if ( 0 == passable || Direction::CENTER == passable ) { - fheroes2::DrawBorder( sf, red ); - } - else if ( DIRECTION_ALL == passable ) { - fheroes2::DrawBorder( sf, green ); - } - else { - const uint8_t topLeftColor = ( ( passable & Direction::TOP_LEFT ) != 0 ) ? green : red; - const uint8_t bottomRightColor = ( ( passable & Direction::BOTTOM_RIGHT ) != 0 ) ? green : red; - const uint8_t topRightColor = ( ( passable & Direction::TOP_RIGHT ) != 0 ) ? green : red; - const uint8_t bottomLeftColor = ( ( passable & Direction::BOTTOM_LEFT ) != 0 ) ? green : red; - const uint8_t topColor = ( ( passable & Direction::TOP ) != 0 ) ? green : red; - const uint8_t bottomColor = ( ( passable & Direction::BOTTOM ) != 0 ) ? green : red; - const uint8_t leftColor = ( ( passable & Direction::LEFT ) != 0 ) ? green : red; - const uint8_t rightColor = ( ( passable & Direction::RIGHT ) != 0 ) ? green : red; - - uint8_t * image = sf.image(); - uint8_t * transform = sf.transform(); - - // Horizontal - for ( int32_t i = 0; i < 10; ++i ) { - *( image + i ) = topLeftColor; - *( transform + i ) = 0; - - *( image + i + ( size - 1 ) * size ) = bottomLeftColor; - *( transform + i + ( size - 1 ) * size ) = 0; - } - - for ( int32_t i = 10; i < 21; ++i ) { - *( image + i ) = topColor; - *( transform + i ) = 0; - - *( image + i + ( size - 1 ) * size ) = bottomColor; - *( transform + i + ( size - 1 ) * size ) = 0; - } - - for ( int32_t i = 21; i < size; ++i ) { - *( image + i ) = topRightColor; - *( transform + i ) = 0; - - *( image + i + ( size - 1 ) * size ) = bottomRightColor; - *( transform + i + ( size - 1 ) * size ) = 0; - } - - // Vertical - for ( int32_t i = 0; i < 10; ++i ) { - *( image + i * size ) = topLeftColor; - *( transform + i * size ) = 0; - - *( image + size - 1 + i * size ) = topRightColor; - *( transform + size - 1 + i * size ) = 0; - } - - for ( int32_t i = 10; i < 21; ++i ) { - *( image + i * size ) = leftColor; - *( transform + i * size ) = 0; - - *( image + size - 1 + i * size ) = rightColor; - *( transform + size - 1 + i * size ) = 0; - } - - for ( int32_t i = 21; i < size; ++i ) { - *( image + i * size ) = bottomLeftColor; - *( transform + i * size ) = 0; - - *( image + size - 1 + i * size ) = bottomRightColor; - *( transform + size - 1 + i * size ) = 0; - } - } - - return imageMap.try_emplace( passable, std::move( sf ) ).first->second; - } - - const fheroes2::Image & getDebugFogImage() - { - static const fheroes2::Image fog = []() { - fheroes2::Image temp( 32, 32 ); - fheroes2::FillTransform( temp, 0, 0, temp.width(), temp.height(), 2 ); - return temp; - }(); - - return fog; - } -#endif - bool isShortObject( const MP2::MapObjectType objectType ) { // Some objects allow middle moves even being attached to the bottom. @@ -450,22 +341,6 @@ namespace return false; } - bool isDirectRenderingRestricted( const int icnId ) - { - switch ( icnId ) { - case ICN::UNKNOWN: - case ICN::MONS32: - case ICN::BOAT32: - case ICN::MINIHERO: - // Either it is an invalid sprite or a sprite which needs to be divided into tiles in order to properly render it. - return true; - default: - break; - } - - return false; - } - const char * getObjectLayerName( const uint8_t level ) { switch ( level ) { @@ -857,11 +732,6 @@ int Maps::Tiles::getBoatDirection() const return Direction::UNKNOWN; } -const fheroes2::Image & Maps::Tiles::GetTileSurface() const -{ - return fheroes2::AGG::GetTIL( TIL::GROUND32, _terrainImageIndex, ( _terrainFlags & 0x3 ) ); -} - int Maps::Tiles::getOriginalPassability() const { const MP2::MapObjectType objectType = GetObject( false ); @@ -1160,414 +1030,6 @@ int Maps::Tiles::GetGround() const return Maps::Ground::BEACH; } -void Maps::Tiles::RedrawEmptyTile( fheroes2::Image & dst, const fheroes2::Point & mp, const Interface::GameArea & area ) -{ - if ( mp.y == -1 && mp.x >= 0 && mp.x < world.w() ) { // top first row - area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 20 + ( mp.x % 4 ), 0 ), mp ); - } - else if ( mp.x == world.w() && mp.y >= 0 && mp.y < world.h() ) { // right first row - area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 24 + ( mp.y % 4 ), 0 ), mp ); - } - else if ( mp.y == world.h() && mp.x >= 0 && mp.x < world.w() ) { // bottom first row - area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 28 + ( mp.x % 4 ), 0 ), mp ); - } - else if ( mp.x == -1 && mp.y >= 0 && mp.y < world.h() ) { // left first row - area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 32 + ( mp.y % 4 ), 0 ), mp ); - } - else { - area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, ( std::abs( mp.y ) % 4 ) * 4 + std::abs( mp.x ) % 4, 0 ), mp ); - } -} - -void Maps::Tiles::RedrawPassable( fheroes2::Image & dst, const int friendColors, const Interface::GameArea & area ) const -{ -#ifdef WITH_DEBUG - if ( isFog( friendColors ) ) { - area.BlitOnTile( dst, getDebugFogImage(), 0, 0, Maps::GetPoint( _index ), false, 255 ); - } - if ( 0 == tilePassable || DIRECTION_ALL != tilePassable ) { - area.BlitOnTile( dst, PassableViewSurface( tilePassable ), 0, 0, Maps::GetPoint( _index ), false, 255 ); - } -#else - (void)dst; - (void)area; - (void)friendColors; -#endif -} - -void Maps::Tiles::redrawBottomLayerObjects( fheroes2::Image & dst, bool isPuzzleDraw, const Interface::GameArea & area, const uint8_t level ) const -{ - assert( level <= 0x03 ); - - const fheroes2::Point & mp = Maps::GetPoint( _index ); - - // Since the original game stores information about objects in a very weird way and this is how it is implemented for us we need to do the following procedure: - // - run through all bottom objects first which are stored in the addon stack - // - check the main object which is on the tile - - // Some addons must be rendered after the main object on the tile. This applies for flags. - // Since this method is called intensively during rendering we have to avoid memory allocation on heap. - const size_t maxPostRenderAddons = 16; - std::array postRenderingAddon{}; - size_t postRenderAddonCount = 0; - - for ( const TilesAddon & addon : addons_level1 ) { - if ( ( addon._layerType & 0x03 ) != level ) { - continue; - } - - if ( isPuzzleDraw && MP2::isHiddenForPuzzle( GetGround(), addon._objectIcnType, addon._imageIndex ) ) { - continue; - } - - if ( addon._objectIcnType == MP2::OBJ_ICN_TYPE_FLAG32 ) { - // Based on logically thinking it is impossible to have more than 16 flags on a single tile. - assert( postRenderAddonCount < maxPostRenderAddons ); - - postRenderingAddon[postRenderAddonCount] = &addon; - ++postRenderAddonCount; - continue; - } - - renderAddonObject( dst, area, mp, addon ); - } - - if ( _objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN && ( _layerType & 0x03 ) == level - && ( !isPuzzleDraw || !MP2::isHiddenForPuzzle( GetGround(), _objectIcnType, _imageIndex ) ) ) { - renderMainObject( dst, area, mp ); - } - - for ( size_t i = 0; i < postRenderAddonCount; ++i ) { - assert( postRenderingAddon[i] != nullptr ); - - renderAddonObject( dst, area, mp, *postRenderingAddon[i] ); - } -} - -void Maps::Tiles::renderAddonObject( fheroes2::Image & output, const Interface::GameArea & area, const fheroes2::Point & offset, const TilesAddon & addon ) -{ - assert( addon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN && addon._imageIndex != 255 ); - - const int icn = MP2::getIcnIdFromObjectIcnType( addon._objectIcnType ); - if ( isDirectRenderingRestricted( icn ) ) { - return; - } - - const uint8_t alphaValue = area.getObjectAlphaValue( addon._uid ); - - const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icn, addon._imageIndex ); - - // Ideally we need to check that the image is within a tile area. However, flags are among those for which this rule doesn't apply. - if ( icn == ICN::FLAG32 ) { - assert( sprite.width() <= TILEWIDTH && sprite.height() <= TILEWIDTH ); - } - else { - assert( sprite.x() >= 0 && sprite.width() + sprite.x() <= TILEWIDTH && sprite.y() >= 0 && sprite.height() + sprite.y() <= TILEWIDTH ); - } - - area.BlitOnTile( output, sprite, sprite.x(), sprite.y(), offset, false, alphaValue ); - - const uint32_t animationIndex = ICN::AnimationFrame( icn, addon._imageIndex, Game::getAdventureMapAnimationIndex() ); - if ( animationIndex > 0 ) { - const fheroes2::Sprite & animationSprite = fheroes2::AGG::GetICN( icn, animationIndex ); - - // If this assertion blows up we are trying to render an image bigger than a tile. Render this object properly as heroes or monsters! - assert( animationSprite.x() >= 0 && animationSprite.width() + animationSprite.x() <= TILEWIDTH && animationSprite.y() >= 0 - && animationSprite.height() + animationSprite.y() <= TILEWIDTH ); - - area.BlitOnTile( output, animationSprite, animationSprite.x(), animationSprite.y(), offset, false, alphaValue ); - } -} - -void Maps::Tiles::renderMainObject( fheroes2::Image & output, const Interface::GameArea & area, const fheroes2::Point & offset ) const -{ - assert( _objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN && _imageIndex != 255 ); - - const int mainObjectIcn = MP2::getIcnIdFromObjectIcnType( _objectIcnType ); - if ( isDirectRenderingRestricted( mainObjectIcn ) ) { - return; - } - - const uint8_t mainObjectAlphaValue = area.getObjectAlphaValue( _uid ); - - const fheroes2::Sprite & mainObjectSprite = fheroes2::AGG::GetICN( mainObjectIcn, _imageIndex ); - - // If this assertion blows up we are trying to render an image bigger than a tile. Render this object properly as heroes or monsters! - assert( mainObjectSprite.x() >= 0 && mainObjectSprite.width() + mainObjectSprite.x() <= TILEWIDTH && mainObjectSprite.y() >= 0 - && mainObjectSprite.height() + mainObjectSprite.y() <= TILEWIDTH ); - - area.BlitOnTile( output, mainObjectSprite, mainObjectSprite.x(), mainObjectSprite.y(), offset, false, mainObjectAlphaValue ); - - // Render possible animation image. - // TODO: quantity2 is used in absolutely incorrect way! Fix all the logic for it. As of now (quantity2 != 0) expression is used only for Magic Garden. - const uint32_t mainObjectAnimationIndex = ICN::AnimationFrame( mainObjectIcn, _imageIndex, Game::getAdventureMapAnimationIndex(), metadata()[1] != 0 ); - if ( mainObjectAnimationIndex > 0 ) { - const fheroes2::Sprite & animationSprite = fheroes2::AGG::GetICN( mainObjectIcn, mainObjectAnimationIndex ); - - // If this assertion blows up we are trying to render an image bigger than a tile. Render this object properly as heroes or monsters! - assert( animationSprite.x() >= 0 && animationSprite.width() + animationSprite.x() <= TILEWIDTH && animationSprite.y() >= 0 - && animationSprite.height() + animationSprite.y() <= TILEWIDTH ); - - area.BlitOnTile( output, animationSprite, animationSprite.x(), animationSprite.y(), offset, false, mainObjectAlphaValue ); - } -} - -void Maps::Tiles::drawByObjectIcnType( fheroes2::Image & output, const Interface::GameArea & area, const MP2::ObjectIcnType objectIcnType ) const -{ - const fheroes2::Point & tileOffset = Maps::GetPoint( _index ); - - for ( const TilesAddon & addon : addons_level1 ) { - if ( addon._objectIcnType == objectIcnType ) { - renderAddonObject( output, area, tileOffset, addon ); - } - } - - if ( _objectIcnType == objectIcnType ) { - renderMainObject( output, area, tileOffset ); - } - - for ( const TilesAddon & addon : addons_level2 ) { - if ( addon._objectIcnType == objectIcnType ) { - renderAddonObject( output, area, tileOffset, addon ); - } - } -} - -std::vector Maps::Tiles::getMonsterSpritesPerTile() const -{ - assert( GetObject() == MP2::OBJ_MONSTER ); - - const Monster & monster = getMonsterFromTile( *this ); - const std::pair spriteIndices = GetMonsterSpriteIndices( *this, monster.GetSpriteIndex() ); - - const int icnId{ ICN::MINI_MONSTER_IMAGE }; - const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.first ); - const fheroes2::Point monsterSpriteOffset( monsterSprite.x() + 16, monsterSprite.y() + 30 ); - - std::vector outputSquareInfo; - std::vector> outputImageInfo; - fheroes2::DivideImageBySquares( monsterSpriteOffset, monsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); - - assert( outputSquareInfo.size() == outputImageInfo.size() ); - - std::vector objectInfo; - for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { - objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.first, false, - static_cast( 255 ) ); - } - - outputSquareInfo.clear(); - outputImageInfo.clear(); - - if ( spriteIndices.second > 0 ) { - const fheroes2::Sprite & secondaryMonsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.second ); - const fheroes2::Point secondaryMonsterSpriteOffset( secondaryMonsterSprite.x() + 16, secondaryMonsterSprite.y() + 30 ); - - fheroes2::DivideImageBySquares( secondaryMonsterSpriteOffset, secondaryMonsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); - - assert( outputSquareInfo.size() == outputImageInfo.size() ); - - for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { - objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.second, false, - static_cast( 255 ) ); - } - } - - return objectInfo; -} - -std::vector Maps::Tiles::getMonsterShadowSpritesPerTile() const -{ - assert( GetObject() == MP2::OBJ_MONSTER ); - - const Monster & monster = getMonsterFromTile( *this ); - const std::pair spriteIndices = GetMonsterSpriteIndices( *this, monster.GetSpriteIndex() ); - - const int icnId{ ICN::MINI_MONSTER_SHADOW }; - const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.first ); - const fheroes2::Point monsterSpriteOffset( monsterSprite.x() + 16, monsterSprite.y() + 30 ); - - std::vector outputSquareInfo; - std::vector> outputImageInfo; - fheroes2::DivideImageBySquares( monsterSpriteOffset, monsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); - - assert( outputSquareInfo.size() == outputImageInfo.size() ); - - std::vector objectInfo; - for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { - objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.first, false, - static_cast( 255 ) ); - } - - outputSquareInfo.clear(); - outputImageInfo.clear(); - - if ( spriteIndices.second > 0 ) { - const fheroes2::Sprite & secondaryMonsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.second ); - const fheroes2::Point secondaryMonsterSpriteOffset( secondaryMonsterSprite.x() + 16, secondaryMonsterSprite.y() + 30 ); - - fheroes2::DivideImageBySquares( secondaryMonsterSpriteOffset, secondaryMonsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); - - assert( outputSquareInfo.size() == outputImageInfo.size() ); - - for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { - objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.second, false, - static_cast( 255 ) ); - } - } - - return objectInfo; -} - -std::vector Maps::Tiles::getBoatSpritesPerTile() const -{ - // TODO: combine both boat image generation for heroes and empty boats. - assert( GetObject() == MP2::OBJ_BOAT ); - - const uint32_t spriteIndex = ( _imageIndex == 255 ) ? 18 : _imageIndex; - - const bool isReflected = ( spriteIndex > 128 ); - - const int icnId{ ICN::BOAT32 }; - const uint32_t icnIndex = spriteIndex % 128; - const fheroes2::Sprite & boatSprite = fheroes2::AGG::GetICN( icnId, icnIndex ); - - const fheroes2::Point boatSpriteOffset( ( isReflected ? ( TILEWIDTH + 1 - boatSprite.x() - boatSprite.width() ) : boatSprite.x() ), boatSprite.y() + TILEWIDTH - 11 ); - - std::vector outputSquareInfo; - std::vector> outputImageInfo; - fheroes2::DivideImageBySquares( boatSpriteOffset, boatSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); - - assert( outputSquareInfo.size() == outputImageInfo.size() ); - - std::vector objectInfo; - for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { - objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, isReflected, static_cast( 255 ) ); - } - - return objectInfo; -} - -std::vector Maps::Tiles::getBoatShadowSpritesPerTile() const -{ - assert( GetObject() == MP2::OBJ_BOAT ); - - // TODO: boat shadow logic is more complex than this and it is not directly depend on spriteIndex. Find the proper logic and fix it! - const uint32_t spriteIndex = ( _imageIndex == 255 ) ? 18 : _imageIndex; - - const int icnId{ ICN::BOATSHAD }; - const uint32_t icnIndex = spriteIndex % 128; - const fheroes2::Sprite & boatShadowSprite = fheroes2::AGG::GetICN( icnId, icnIndex ); - const fheroes2::Point boatShadowSpriteOffset( boatShadowSprite.x(), TILEWIDTH + boatShadowSprite.y() - 11 ); - - // Shadows cannot be flipped so flip flag is always false. - std::vector outputSquareInfo; - std::vector> outputImageInfo; - fheroes2::DivideImageBySquares( boatShadowSpriteOffset, boatShadowSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); - - assert( outputSquareInfo.size() == outputImageInfo.size() ); - - std::vector objectInfo; - for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { - objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, false, static_cast( 255 ) ); - } - - return objectInfo; -} - -std::vector Maps::Tiles::getMineGuardianSpritesPerTile() const -{ - assert( GetObject( false ) == MP2::OBJ_MINES ); - - std::vector objectInfo; - - const int32_t spellID = Maps::getMineSpellIdFromTile( *this ); - switch ( spellID ) { - case Spell::SETEGUARDIAN: - case Spell::SETAGUARDIAN: - case Spell::SETFGUARDIAN: - case Spell::SETWGUARDIAN: { - static_assert( Spell::SETAGUARDIAN - Spell::SETEGUARDIAN == 1 && Spell::SETFGUARDIAN - Spell::SETEGUARDIAN == 2 && Spell::SETWGUARDIAN - Spell::SETEGUARDIAN == 3, - "Why are you changing the order of spells?! Be extremely careful of what you are doing" ); - - const int icnId{ ICN::OBJNXTRA }; - const uint32_t icnIndex = spellID - Spell::SETEGUARDIAN; - const fheroes2::Sprite & image = fheroes2::AGG::GetICN( icnId, icnIndex ); - - std::vector outputSquareInfo; - std::vector> outputImageInfo; - fheroes2::DivideImageBySquares( { image.x(), image.y() }, image, TILEWIDTH, outputSquareInfo, outputImageInfo ); - - assert( outputSquareInfo.size() == outputImageInfo.size() ); - - for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { - objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, false, static_cast( 255 ) ); - } - break; - } - default: - break; - } - - return objectInfo; -} - -void Maps::Tiles::redrawTopLayerExtraObjects( fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area ) const -{ - if ( isPuzzleDraw ) { - // Extra objects should not be shown on Puzzle Map as they are temporary objects appearing under specific conditions like flags. - return; - } - - // Ghost animation is unique and can be rendered in multiple cases. - bool renderFlyingGhosts = false; - - const MP2::MapObjectType objectType = GetObject( false ); - if ( objectType == MP2::OBJ_ABANDONED_MINE ) { - renderFlyingGhosts = true; - } - else if ( objectType == MP2::OBJ_MINES ) { - const int32_t spellID = Maps::getMineSpellIdFromTile( *this ); - - switch ( spellID ) { - case Spell::NONE: - // No spell exists. Nothing we need to render. - case Spell::SETEGUARDIAN: - case Spell::SETAGUARDIAN: - case Spell::SETFGUARDIAN: - case Spell::SETWGUARDIAN: - // The logic for these spells is done while rendering the bottom layer. Nothing should be done here. - break; - case Spell::HAUNT: - renderFlyingGhosts = true; - break; - default: - // Did you add a new spell for mines? Add the rendering for it above! - assert( 0 ); - break; - } - } - - if ( renderFlyingGhosts ) { - // This sprite is bigger than TILEWIDTH but rendering is correct for heroes and boats. - // TODO: consider adding this sprite as a part of an addon. - const fheroes2::Sprite & image = fheroes2::AGG::GetICN( ICN::OBJNHAUN, Game::getAdventureMapAnimationIndex() % 15 ); - - const uint8_t alphaValue = area.getObjectAlphaValue( _uid ); - - area.BlitOnTile( dst, image, image.x(), image.y(), Maps::GetPoint( _index ), false, alphaValue ); - } -} - -void Maps::Tiles::redrawTopLayerObject( fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area, const TilesAddon & addon ) const -{ - if ( isPuzzleDraw && MP2::isHiddenForPuzzle( GetGround(), addon._objectIcnType, addon._imageIndex ) ) { - return; - } - - renderAddonObject( dst, area, Maps::GetPoint( _index ), addon ); -} - Maps::TilesAddon * Maps::Tiles::FindAddonLevel1( uint32_t uniq1 ) { Addons::iterator it = std::find_if( addons_level1.begin(), addons_level1.end(), [uniq1]( const TilesAddon & v ) { return v.isUniq( uniq1 ); } ); @@ -2351,47 +1813,6 @@ void Maps::Tiles::RestoreAbandonedMine( Tiles & tile, const int resource ) } } -std::pair Maps::Tiles::GetMonsterSpriteIndices( const Tiles & tile, uint32_t monsterIndex ) -{ - const int tileIndex = tile._index; - int attackerIndex = -1; - - // scan for a hero around - for ( const int32_t idx : ScanAroundObject( tileIndex, MP2::OBJ_HEROES, false ) ) { - const Heroes * hero = world.GetTiles( idx ).GetHeroes(); - assert( hero != nullptr ); - - // hero is going to attack monsters on this tile - if ( hero->GetAttackedMonsterTileIndex() == tileIndex ) { - attackerIndex = idx; - break; - } - } - - std::pair spriteIndices( monsterIndex * 9, 0 ); - - // draw an attacking sprite if there is an attacking hero nearby - if ( attackerIndex != -1 ) { - spriteIndices.first += 7; - - switch ( Maps::GetDirection( tileIndex, attackerIndex ) ) { - case Direction::TOP_LEFT: - case Direction::LEFT: - case Direction::BOTTOM_LEFT: - spriteIndices.first += 1; - break; - default: - break; - } - } - else { - const fheroes2::Point & mp = Maps::GetPoint( tileIndex ); - const std::array & monsterAnimationSequence = fheroes2::getMonsterAnimationSequence(); - spriteIndices.second = monsterIndex * 9 + 1 + monsterAnimationSequence[( Game::getAdventureMapAnimationIndex() + mp.x * mp.y ) % monsterAnimationSequence.size()]; - } - return spriteIndices; -} - void Maps::Tiles::ClearFog( const int colors ) { _fogColors &= ~colors; @@ -2530,229 +1951,6 @@ void Maps::Tiles::updateFogDirectionsInArea( const fheroes2::Point & minPos, con } } -void Maps::Tiles::drawFog( fheroes2::Image & dst, const Interface::GameArea & area ) const -{ - // This method should not be called for a tile without fog. - assert( _fogDirection & Direction::CENTER ); - - const fheroes2::Point & mp = Maps::GetPoint( _index ); - - // TODO: Cache all directions into a map: have an array which represents all conditions within this method. The index can be '_fogDirection', the value is index. - // And another array to store revert flag. - - if ( DIRECTION_ALL == _fogDirection ) { - const fheroes2::Image & sf = fheroes2::AGG::GetTIL( TIL::CLOF32, ( mp.x + mp.y ) % 4, 0 ); - area.DrawTile( dst, sf, mp ); - } - else { - uint32_t index = 0; - bool revert = false; - - if ( !( _fogDirection & ( Direction::TOP | Direction::BOTTOM | Direction::LEFT | Direction::RIGHT ) ) ) { - index = 10; - } - else if ( ( contains( _fogDirection, Direction::TOP ) ) && !( _fogDirection & ( Direction::BOTTOM | Direction::LEFT | Direction::RIGHT ) ) ) { - index = 6; - } - else if ( ( contains( _fogDirection, Direction::RIGHT ) ) && !( _fogDirection & ( Direction::TOP | Direction::BOTTOM | Direction::LEFT ) ) ) { - index = 7; - } - else if ( ( contains( _fogDirection, Direction::LEFT ) ) && !( _fogDirection & ( Direction::TOP | Direction::BOTTOM | Direction::RIGHT ) ) ) { - index = 7; - revert = true; - } - else if ( ( contains( _fogDirection, Direction::BOTTOM ) ) && !( _fogDirection & ( Direction::TOP | Direction::LEFT | Direction::RIGHT ) ) ) { - index = 8; - } - else if ( ( contains( _fogDirection, DIRECTION_CENTER_COL ) ) && !( _fogDirection & ( Direction::LEFT | Direction::RIGHT ) ) ) { - index = 9; - } - else if ( ( contains( _fogDirection, DIRECTION_CENTER_ROW ) ) && !( _fogDirection & ( Direction::TOP | Direction::BOTTOM ) ) ) { - index = 29; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~Direction::TOP_RIGHT ) ) ) { - index = 15; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~Direction::TOP_LEFT ) ) ) { - index = 15; - revert = true; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~Direction::BOTTOM_RIGHT ) ) ) { - index = 22; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~Direction::BOTTOM_LEFT ) ) ) { - index = 22; - revert = true; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT ) ) ) ) { - index = 16; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::BOTTOM_LEFT ) ) ) ) { - index = 16; - revert = true; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_RIGHT | Direction::BOTTOM_LEFT ) ) ) ) { - index = 17; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::BOTTOM_RIGHT ) ) ) ) { - index = 17; - revert = true; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::TOP_RIGHT ) ) ) ) { - index = 18; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ) ) ) ) { - index = 23; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_TOP_RIGHT_CORNER ) ) ) { - index = 13; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_TOP_LEFT_CORNER ) ) ) { - index = 13; - revert = true; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_BOTTOM_RIGHT_CORNER ) ) ) { - index = 14; - } - else if ( _fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_BOTTOM_LEFT_CORNER ) ) ) { - index = 14; - revert = true; - } - else if ( contains( _fogDirection, Direction::LEFT | Direction::BOTTOM_LEFT | Direction::BOTTOM ) - && !( _fogDirection & ( Direction::TOP | Direction::RIGHT ) ) ) { - index = 11; - } - else if ( contains( _fogDirection, Direction::RIGHT | Direction::BOTTOM_RIGHT | Direction::BOTTOM ) - && !( _fogDirection & ( Direction::TOP | Direction::LEFT ) ) ) { - index = 11; - revert = true; - } - else if ( contains( _fogDirection, Direction::LEFT | Direction::TOP_LEFT | Direction::TOP ) && !( _fogDirection & ( Direction::BOTTOM | Direction::RIGHT ) ) ) { - index = 12; - } - else if ( contains( _fogDirection, Direction::RIGHT | Direction::TOP_RIGHT | Direction::TOP ) && !( _fogDirection & ( Direction::BOTTOM | Direction::LEFT ) ) ) { - index = 12; - revert = true; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::TOP_LEFT ) - && !( _fogDirection & ( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT | Direction::TOP_RIGHT ) ) ) { - index = 19; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::TOP_RIGHT ) - && !( _fogDirection & ( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT | Direction::TOP_LEFT ) ) ) { - index = 19; - revert = true; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::BOTTOM_LEFT ) - && !( _fogDirection & ( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT | Direction::TOP_LEFT ) ) ) { - index = 20; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::BOTTOM_RIGHT ) - && !( _fogDirection & ( Direction::TOP_RIGHT | Direction::BOTTOM_LEFT | Direction::TOP_LEFT ) ) ) { - index = 20; - revert = true; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP ) - && !( _fogDirection & ( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT | Direction::BOTTOM_LEFT | Direction::TOP_LEFT ) ) ) { - index = 22; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::BOTTOM_LEFT ) - && !( _fogDirection & ( Direction::TOP | Direction::BOTTOM_RIGHT ) ) ) { - index = 24; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::BOTTOM_RIGHT ) - && !( _fogDirection & ( Direction::TOP | Direction::BOTTOM_LEFT ) ) ) { - index = 24; - revert = true; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_COL | Direction::LEFT | Direction::TOP_LEFT ) - && !( _fogDirection & ( Direction::RIGHT | Direction::BOTTOM_LEFT ) ) ) { - index = 25; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_COL | Direction::RIGHT | Direction::TOP_RIGHT ) - && !( _fogDirection & ( Direction::LEFT | Direction::BOTTOM_RIGHT ) ) ) { - index = 25; - revert = true; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_COL | Direction::BOTTOM_LEFT | Direction::LEFT ) - && !( _fogDirection & ( Direction::RIGHT | Direction::TOP_LEFT ) ) ) { - index = 26; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_COL | Direction::BOTTOM_RIGHT | Direction::RIGHT ) - && !( _fogDirection & ( Direction::LEFT | Direction::TOP_RIGHT ) ) ) { - index = 26; - revert = true; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | Direction::TOP_LEFT | Direction::TOP ) - && !( _fogDirection & ( Direction::BOTTOM | Direction::TOP_RIGHT ) ) ) { - index = 30; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | Direction::TOP_RIGHT | Direction::TOP ) - && !( _fogDirection & ( Direction::BOTTOM | Direction::TOP_LEFT ) ) ) { - index = 30; - revert = true; - } - else if ( contains( _fogDirection, Direction::BOTTOM | Direction::LEFT ) - && !( _fogDirection & ( Direction::TOP | Direction::RIGHT | Direction::BOTTOM_LEFT ) ) ) { - index = 27; - } - else if ( contains( _fogDirection, Direction::BOTTOM | Direction::RIGHT ) - && !( _fogDirection & ( Direction::TOP | Direction::TOP_LEFT | Direction::LEFT | Direction::BOTTOM_RIGHT ) ) ) { - index = 27; - revert = true; - } - else if ( contains( _fogDirection, Direction::LEFT | Direction::TOP ) - && !( _fogDirection & ( Direction::TOP_LEFT | Direction::RIGHT | Direction::BOTTOM | Direction::BOTTOM_RIGHT ) ) ) { - index = 28; - } - else if ( contains( _fogDirection, Direction::RIGHT | Direction::TOP ) - && !( _fogDirection & ( Direction::TOP_RIGHT | Direction::LEFT | Direction::BOTTOM | Direction::BOTTOM_LEFT ) ) ) { - index = 28; - revert = true; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | Direction::TOP ) - && !( _fogDirection & ( Direction::BOTTOM | Direction::TOP_LEFT | Direction::TOP_RIGHT ) ) ) { - index = 31; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_COL | Direction::RIGHT ) - && !( _fogDirection & ( Direction::LEFT | Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT ) ) ) { - index = 32; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_COL | Direction::LEFT ) - && !( _fogDirection & ( Direction::RIGHT | Direction::TOP_LEFT | Direction::BOTTOM_LEFT ) ) ) { - index = 32; - revert = true; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM ) - && !( _fogDirection & ( Direction::TOP | Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ) ) ) { - index = 33; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | DIRECTION_BOTTOM_ROW ) && !( _fogDirection & Direction::TOP ) ) { - index = ( _index % 2 ) ? 0 : 1; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_ROW | DIRECTION_TOP_ROW ) && !( _fogDirection & Direction::BOTTOM ) ) { - index = ( _index % 2 ) ? 4 : 5; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_COL | DIRECTION_LEFT_COL ) && !( _fogDirection & Direction::RIGHT ) ) { - index = ( _index % 2 ) ? 2 : 3; - } - else if ( contains( _fogDirection, DIRECTION_CENTER_COL | DIRECTION_RIGHT_COL ) && !( _fogDirection & Direction::LEFT ) ) { - index = ( _index % 2 ) ? 2 : 3; - revert = true; - } - else { - // unknown - DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid direction for fog: " << _fogDirection << ". Tile index: " << _index ) - const fheroes2::Image & sf = fheroes2::AGG::GetTIL( TIL::CLOF32, ( mp.x + mp.y ) % 4, 0 ); - area.DrawTile( dst, sf, mp ); - return; - } - - const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::CLOP32, index ); - area.BlitOnTile( dst, sprite, ( revert ? TILEWIDTH - sprite.x() - sprite.width() : sprite.x() ), sprite.y(), mp, revert, 255 ); - } -} - void Maps::Tiles::updateTileById( Maps::Tiles & tile, const uint32_t uid, const uint8_t newIndex ) { Maps::TilesAddon * addon = tile.FindAddonLevel1( uid ); diff --git a/src/fheroes2/maps/maps_tiles.h b/src/fheroes2/maps/maps_tiles.h index 590d2352b7e..20f54688ab2 100644 --- a/src/fheroes2/maps/maps_tiles.h +++ b/src/fheroes2/maps/maps_tiles.h @@ -39,17 +39,6 @@ class Heroes; class StreamBase; -namespace fheroes2 -{ - class Image; - struct ObjectRenderingInfo; -} - -namespace Interface -{ - class GameArea; -} - namespace Maps { enum ObjectLayerType : uint8_t @@ -178,8 +167,6 @@ namespace Maps return 30 > _terrainImageIndex; } - const fheroes2::Image & GetTileSurface() const; - bool isSameMainObject( const MP2::MapObjectType objectType ) const { return objectType == _mainObjectType; @@ -262,10 +249,6 @@ namespace Maps void removeOwnershipFlag( const MP2::MapObjectType objectType ); - static void RedrawEmptyTile( fheroes2::Image & dst, const fheroes2::Point & mp, const Interface::GameArea & area ); - void redrawTopLayerExtraObjects( fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area ) const; - void redrawTopLayerObject( fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area, const TilesAddon & addon ) const; - // Determine the fog direction in the area between min and max positions for given player(s) color code and store it in corresponding tile data. static void updateFogDirectionsInArea( const fheroes2::Point & minPos, const fheroes2::Point & maxPos, const int32_t color ); // Return fog direction of tile. A tile without fog returns "Direction::UNKNOWN". @@ -274,18 +257,6 @@ namespace Maps return _fogDirection; } - void drawFog( fheroes2::Image & dst, const Interface::GameArea & area ) const; - void RedrawPassable( fheroes2::Image & dst, const int friendColors, const Interface::GameArea & area ) const; - void redrawBottomLayerObjects( fheroes2::Image & dst, bool isPuzzleDraw, const Interface::GameArea & area, const uint8_t level ) const; - - void drawByObjectIcnType( fheroes2::Image & output, const Interface::GameArea & area, const MP2::ObjectIcnType objectIcnType ) const; - - std::vector getMonsterSpritesPerTile() const; - std::vector getMonsterShadowSpritesPerTile() const; - std::vector getBoatSpritesPerTile() const; - std::vector getBoatShadowSpritesPerTile() const; - std::vector getMineGuardianSpritesPerTile() const; - void AddonsPushLevel1( const MP2::mp2tile_t & mt ); void AddonsPushLevel1( const MP2::mp2addon_t & ma ); @@ -345,6 +316,16 @@ namespace Maps return _metadata; } + uint8_t getTerrainFlags() const + { + return _terrainFlags; + } + + uint16_t getTerrainImageIndex() const + { + return _terrainImageIndex; + } + Heroes * GetHeroes() const; void SetHeroes( Heroes * ); @@ -363,7 +344,6 @@ namespace Maps bool containsSprite( const MP2::ObjectIcnType objectIcnType, const uint32_t imageIdx ) const; static std::pair ColorRaceFromHeroSprite( const uint32_t heroSpriteIndex ); - static std::pair GetMonsterSpriteIndices( const Tiles & tile, const uint32_t monsterIndex ); // Restores an abandoned mine whose main tile is 'tile', turning it into an ordinary mine that brings // resources of type 'resource'. This method updates all sprites and sets object types for non-action @@ -399,9 +379,6 @@ namespace Maps friend StreamBase & operator<<( StreamBase &, const Tiles & ); friend StreamBase & operator>>( StreamBase &, Tiles & ); - static void renderAddonObject( fheroes2::Image & output, const Interface::GameArea & area, const fheroes2::Point & offset, const TilesAddon & addon ); - void renderMainObject( fheroes2::Image & output, const Interface::GameArea & area, const fheroes2::Point & offset ) const; - static uint8_t convertOldMainObjectType( const uint8_t mainObjectType ); // The old code was using weird quantity based values which were very hard to understand. diff --git a/src/fheroes2/maps/maps_tiles_render.cpp b/src/fheroes2/maps/maps_tiles_render.cpp new file mode 100644 index 00000000000..2d7bc2c058c --- /dev/null +++ b/src/fheroes2/maps/maps_tiles_render.cpp @@ -0,0 +1,843 @@ +/*************************************************************************** + * fheroes2: https://github.com/ihhub/fheroes2 * + * Copyright (C) 2023 * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "maps_tiles_render.h" + +#include "agg_image.h" +#include "game.h" +#include "icn.h" +#include "interface_gamearea.h" +#include "logging.h" +#include "maps.h" +#include "maps_tiles.h" +#include "maps_tiles_helper.h" +#include "monster_anim.h" +#include "til.h" +#include "ui_object_rendering.h" +#include "world.h" + +namespace +{ + bool contains( const int base, const int value ) + { + return ( base & value ) == value; + } + + bool isDirectRenderingRestricted( const int icnId ) + { + switch ( icnId ) { + case ICN::UNKNOWN: + case ICN::MONS32: + case ICN::BOAT32: + case ICN::MINIHERO: + // Either it is an invalid sprite or a sprite which needs to be divided into tiles in order to properly render it. + return true; + default: + break; + } + + return false; + } + + void renderAddonObject( fheroes2::Image & output, const Interface::GameArea & area, const fheroes2::Point & offset, const Maps::TilesAddon & addon ) + { + assert( addon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN && addon._imageIndex != 255 ); + + const int icn = MP2::getIcnIdFromObjectIcnType( addon._objectIcnType ); + if ( isDirectRenderingRestricted( icn ) ) { + return; + } + + const uint8_t alphaValue = area.getObjectAlphaValue( addon._uid ); + + const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icn, addon._imageIndex ); + + // Ideally we need to check that the image is within a tile area. However, flags are among those for which this rule doesn't apply. + if ( icn == ICN::FLAG32 ) { + assert( sprite.width() <= TILEWIDTH && sprite.height() <= TILEWIDTH ); + } + else { + assert( sprite.x() >= 0 && sprite.width() + sprite.x() <= TILEWIDTH && sprite.y() >= 0 && sprite.height() + sprite.y() <= TILEWIDTH ); + } + + area.BlitOnTile( output, sprite, sprite.x(), sprite.y(), offset, false, alphaValue ); + + const uint32_t animationIndex = ICN::AnimationFrame( icn, addon._imageIndex, Game::getAdventureMapAnimationIndex() ); + if ( animationIndex > 0 ) { + const fheroes2::Sprite & animationSprite = fheroes2::AGG::GetICN( icn, animationIndex ); + + // If this assertion blows up we are trying to render an image bigger than a tile. Render this object properly as heroes or monsters! + assert( animationSprite.x() >= 0 && animationSprite.width() + animationSprite.x() <= TILEWIDTH && animationSprite.y() >= 0 + && animationSprite.height() + animationSprite.y() <= TILEWIDTH ); + + area.BlitOnTile( output, animationSprite, animationSprite.x(), animationSprite.y(), offset, false, alphaValue ); + } + } + + void renderMainObject( fheroes2::Image & output, const Interface::GameArea & area, const fheroes2::Point & offset, const Maps::Tiles & tile ) + { + assert( tile.getObjectIcnType() != MP2::OBJ_ICN_TYPE_UNKNOWN && tile.GetObjectSpriteIndex() != 255 ); + + const int mainObjectIcn = MP2::getIcnIdFromObjectIcnType( tile.getObjectIcnType() ); + if ( isDirectRenderingRestricted( mainObjectIcn ) ) { + return; + } + + const uint8_t mainObjectAlphaValue = area.getObjectAlphaValue( tile.GetObjectUID() ); + + const fheroes2::Sprite & mainObjectSprite = fheroes2::AGG::GetICN( mainObjectIcn, tile.GetObjectSpriteIndex() ); + + // If this assertion blows up we are trying to render an image bigger than a tile. Render this object properly as heroes or monsters! + assert( mainObjectSprite.x() >= 0 && mainObjectSprite.width() + mainObjectSprite.x() <= TILEWIDTH && mainObjectSprite.y() >= 0 + && mainObjectSprite.height() + mainObjectSprite.y() <= TILEWIDTH ); + + area.BlitOnTile( output, mainObjectSprite, mainObjectSprite.x(), mainObjectSprite.y(), offset, false, mainObjectAlphaValue ); + + // Render possible animation image. + // TODO: quantity2 is used in absolutely incorrect way! Fix all the logic for it. As of now (quantity2 != 0) expression is used only for Magic Garden. + const uint32_t mainObjectAnimationIndex = ICN::AnimationFrame( mainObjectIcn, tile.GetObjectSpriteIndex(), Game::getAdventureMapAnimationIndex(), + tile.metadata()[1] != 0 ); + if ( mainObjectAnimationIndex > 0 ) { + const fheroes2::Sprite & animationSprite = fheroes2::AGG::GetICN( mainObjectIcn, mainObjectAnimationIndex ); + + // If this assertion blows up we are trying to render an image bigger than a tile. Render this object properly as heroes or monsters! + assert( animationSprite.x() >= 0 && animationSprite.width() + animationSprite.x() <= TILEWIDTH && animationSprite.y() >= 0 + && animationSprite.height() + animationSprite.y() <= TILEWIDTH ); + + area.BlitOnTile( output, animationSprite, animationSprite.x(), animationSprite.y(), offset, false, mainObjectAlphaValue ); + } + } + +#ifdef WITH_DEBUG + const fheroes2::Image & PassableViewSurface( const int passable ) + { + static std::map imageMap; + + auto iter = imageMap.find( passable ); + if ( iter != imageMap.end() ) { + return iter->second; + } + + const int32_t size = 31; + const uint8_t red = 0xBA; + const uint8_t green = 0x5A; + + fheroes2::Image sf( size, size ); + sf.reset(); + + if ( 0 == passable || Direction::CENTER == passable ) { + fheroes2::DrawBorder( sf, red ); + } + else if ( DIRECTION_ALL == passable ) { + fheroes2::DrawBorder( sf, green ); + } + else { + const uint8_t topLeftColor = ( ( passable & Direction::TOP_LEFT ) != 0 ) ? green : red; + const uint8_t bottomRightColor = ( ( passable & Direction::BOTTOM_RIGHT ) != 0 ) ? green : red; + const uint8_t topRightColor = ( ( passable & Direction::TOP_RIGHT ) != 0 ) ? green : red; + const uint8_t bottomLeftColor = ( ( passable & Direction::BOTTOM_LEFT ) != 0 ) ? green : red; + const uint8_t topColor = ( ( passable & Direction::TOP ) != 0 ) ? green : red; + const uint8_t bottomColor = ( ( passable & Direction::BOTTOM ) != 0 ) ? green : red; + const uint8_t leftColor = ( ( passable & Direction::LEFT ) != 0 ) ? green : red; + const uint8_t rightColor = ( ( passable & Direction::RIGHT ) != 0 ) ? green : red; + + uint8_t * image = sf.image(); + uint8_t * transform = sf.transform(); + + // Horizontal + for ( int32_t i = 0; i < 10; ++i ) { + *( image + i ) = topLeftColor; + *( transform + i ) = 0; + + *( image + i + ( size - 1 ) * size ) = bottomLeftColor; + *( transform + i + ( size - 1 ) * size ) = 0; + } + + for ( int32_t i = 10; i < 21; ++i ) { + *( image + i ) = topColor; + *( transform + i ) = 0; + + *( image + i + ( size - 1 ) * size ) = bottomColor; + *( transform + i + ( size - 1 ) * size ) = 0; + } + + for ( int32_t i = 21; i < size; ++i ) { + *( image + i ) = topRightColor; + *( transform + i ) = 0; + + *( image + i + ( size - 1 ) * size ) = bottomRightColor; + *( transform + i + ( size - 1 ) * size ) = 0; + } + + // Vertical + for ( int32_t i = 0; i < 10; ++i ) { + *( image + i * size ) = topLeftColor; + *( transform + i * size ) = 0; + + *( image + size - 1 + i * size ) = topRightColor; + *( transform + size - 1 + i * size ) = 0; + } + + for ( int32_t i = 10; i < 21; ++i ) { + *( image + i * size ) = leftColor; + *( transform + i * size ) = 0; + + *( image + size - 1 + i * size ) = rightColor; + *( transform + size - 1 + i * size ) = 0; + } + + for ( int32_t i = 21; i < size; ++i ) { + *( image + i * size ) = bottomLeftColor; + *( transform + i * size ) = 0; + + *( image + size - 1 + i * size ) = bottomRightColor; + *( transform + size - 1 + i * size ) = 0; + } + } + + return imageMap.try_emplace( passable, std::move( sf ) ).first->second; + } + + const fheroes2::Image & getDebugFogImage() + { + static const fheroes2::Image fog = []() { + fheroes2::Image temp( 32, 32 ); + fheroes2::FillTransform( temp, 0, 0, temp.width(), temp.height(), 2 ); + return temp; + }(); + + return fog; + } +#endif + + std::pair GetMonsterSpriteIndices( const Maps::Tiles & tile, uint32_t monsterIndex ) + { + const int tileIndex = tile.GetIndex(); + int attackerIndex = -1; + + // scan for a hero around + for ( const int32_t idx : Maps::ScanAroundObject( tileIndex, MP2::OBJ_HEROES, false ) ) { + const Heroes * hero = world.GetTiles( idx ).GetHeroes(); + assert( hero != nullptr ); + + // hero is going to attack monsters on this tile + if ( hero->GetAttackedMonsterTileIndex() == tileIndex ) { + attackerIndex = idx; + break; + } + } + + std::pair spriteIndices( monsterIndex * 9, 0 ); + + // draw an attacking sprite if there is an attacking hero nearby + if ( attackerIndex != -1 ) { + spriteIndices.first += 7; + + switch ( Maps::GetDirection( tileIndex, attackerIndex ) ) { + case Direction::TOP_LEFT: + case Direction::LEFT: + case Direction::BOTTOM_LEFT: + spriteIndices.first += 1; + break; + default: + break; + } + } + else { + const fheroes2::Point & mp = Maps::GetPoint( tileIndex ); + const std::array & monsterAnimationSequence = fheroes2::getMonsterAnimationSequence(); + spriteIndices.second = monsterIndex * 9 + 1 + monsterAnimationSequence[( Game::getAdventureMapAnimationIndex() + mp.x * mp.y ) % monsterAnimationSequence.size()]; + } + return spriteIndices; + } +} + +namespace Maps +{ + void redrawEmptyTile( fheroes2::Image & dst, const fheroes2::Point & mp, const Interface::GameArea & area ) + { + if ( mp.y == -1 && mp.x >= 0 && mp.x < world.w() ) { // top first row + area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 20 + ( mp.x % 4 ), 0 ), mp ); + } + else if ( mp.x == world.w() && mp.y >= 0 && mp.y < world.h() ) { // right first row + area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 24 + ( mp.y % 4 ), 0 ), mp ); + } + else if ( mp.y == world.h() && mp.x >= 0 && mp.x < world.w() ) { // bottom first row + area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 28 + ( mp.x % 4 ), 0 ), mp ); + } + else if ( mp.x == -1 && mp.y >= 0 && mp.y < world.h() ) { // left first row + area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 32 + ( mp.y % 4 ), 0 ), mp ); + } + else { + area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, ( std::abs( mp.y ) % 4 ) * 4 + std::abs( mp.x ) % 4, 0 ), mp ); + } + } + + void redrawTopLayerExtraObjects( const Tiles & tile, fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area ) + { + if ( isPuzzleDraw ) { + // Extra objects should not be shown on Puzzle Map as they are temporary objects appearing under specific conditions like flags. + return; + } + + // Ghost animation is unique and can be rendered in multiple cases. + bool renderFlyingGhosts = false; + + const MP2::MapObjectType objectType = tile.GetObject( false ); + if ( objectType == MP2::OBJ_ABANDONED_MINE ) { + renderFlyingGhosts = true; + } + else if ( objectType == MP2::OBJ_MINES ) { + const int32_t spellID = Maps::getMineSpellIdFromTile( tile ); + + switch ( spellID ) { + case Spell::NONE: + // No spell exists. Nothing we need to render. + case Spell::SETEGUARDIAN: + case Spell::SETAGUARDIAN: + case Spell::SETFGUARDIAN: + case Spell::SETWGUARDIAN: + // The logic for these spells is done while rendering the bottom layer. Nothing should be done here. + break; + case Spell::HAUNT: + renderFlyingGhosts = true; + break; + default: + // Did you add a new spell for mines? Add the rendering for it above! + assert( 0 ); + break; + } + } + + if ( renderFlyingGhosts ) { + // This sprite is bigger than TILEWIDTH but rendering is correct for heroes and boats. + // TODO: consider adding this sprite as a part of an addon. + const fheroes2::Sprite & image = fheroes2::AGG::GetICN( ICN::OBJNHAUN, Game::getAdventureMapAnimationIndex() % 15 ); + + const uint8_t alphaValue = area.getObjectAlphaValue( tile.GetObjectUID() ); + + area.BlitOnTile( dst, image, image.x(), image.y(), Maps::GetPoint( tile.GetIndex() ), false, alphaValue ); + } + } + + void redrawTopLayerObject( const Tiles & tile, fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area, const TilesAddon & addon ) + { + if ( isPuzzleDraw && MP2::isHiddenForPuzzle( tile.GetGround(), addon._objectIcnType, addon._imageIndex ) ) { + return; + } + + renderAddonObject( dst, area, Maps::GetPoint( tile.GetIndex() ), addon ); + } + + void drawFog( const Tiles & tile, fheroes2::Image & dst, const Interface::GameArea & area ) + { + const uint16_t fogDirection = tile.getFogDirection(); + // This method should not be called for a tile without fog. + assert( fogDirection & Direction::CENTER ); + + const fheroes2::Point & mp = Maps::GetPoint( tile.GetIndex() ); + + // TODO: Cache all directions into a map: have an array which represents all conditions within this method. The index can be 'fogDirection', the value is index. + // And another array to store revert flag. + + + if ( DIRECTION_ALL == fogDirection ) { + const fheroes2::Image & sf = fheroes2::AGG::GetTIL( TIL::CLOF32, ( mp.x + mp.y ) % 4, 0 ); + area.DrawTile( dst, sf, mp ); + } + else { + uint32_t index = 0; + bool revert = false; + + if ( !( fogDirection & ( Direction::TOP | Direction::BOTTOM | Direction::LEFT | Direction::RIGHT ) ) ) { + index = 10; + } + else if ( ( contains( fogDirection, Direction::TOP ) ) && !( fogDirection & ( Direction::BOTTOM | Direction::LEFT | Direction::RIGHT ) ) ) { + index = 6; + } + else if ( ( contains( fogDirection, Direction::RIGHT ) ) && !( fogDirection & ( Direction::TOP | Direction::BOTTOM | Direction::LEFT ) ) ) { + index = 7; + } + else if ( ( contains( fogDirection, Direction::LEFT ) ) && !( fogDirection & ( Direction::TOP | Direction::BOTTOM | Direction::RIGHT ) ) ) { + index = 7; + revert = true; + } + else if ( ( contains( fogDirection, Direction::BOTTOM ) ) && !( fogDirection & ( Direction::TOP | Direction::LEFT | Direction::RIGHT ) ) ) { + index = 8; + } + else if ( ( contains( fogDirection, DIRECTION_CENTER_COL ) ) && !( fogDirection & ( Direction::LEFT | Direction::RIGHT ) ) ) { + index = 9; + } + else if ( ( contains( fogDirection, DIRECTION_CENTER_ROW ) ) && !( fogDirection & ( Direction::TOP | Direction::BOTTOM ) ) ) { + index = 29; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~Direction::TOP_RIGHT ) ) ) { + index = 15; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~Direction::TOP_LEFT ) ) ) { + index = 15; + revert = true; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~Direction::BOTTOM_RIGHT ) ) ) { + index = 22; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~Direction::BOTTOM_LEFT ) ) ) { + index = 22; + revert = true; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT ) ) ) ) { + index = 16; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::BOTTOM_LEFT ) ) ) ) { + index = 16; + revert = true; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_RIGHT | Direction::BOTTOM_LEFT ) ) ) ) { + index = 17; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::BOTTOM_RIGHT ) ) ) ) { + index = 17; + revert = true; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::TOP_RIGHT ) ) ) ) { + index = 18; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ) ) ) ) { + index = 23; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_TOP_RIGHT_CORNER ) ) ) { + index = 13; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_TOP_LEFT_CORNER ) ) ) { + index = 13; + revert = true; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_BOTTOM_RIGHT_CORNER ) ) ) { + index = 14; + } + else if ( fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_BOTTOM_LEFT_CORNER ) ) ) { + index = 14; + revert = true; + } + else if ( contains( fogDirection, Direction::LEFT | Direction::BOTTOM_LEFT | Direction::BOTTOM ) + && !( fogDirection & ( Direction::TOP | Direction::RIGHT ) ) ) { + index = 11; + } + else if ( contains( fogDirection, Direction::RIGHT | Direction::BOTTOM_RIGHT | Direction::BOTTOM ) + && !( fogDirection & ( Direction::TOP | Direction::LEFT ) ) ) { + index = 11; + revert = true; + } + else if ( contains( fogDirection, Direction::LEFT | Direction::TOP_LEFT | Direction::TOP ) && !( fogDirection & ( Direction::BOTTOM | Direction::RIGHT ) ) ) { + index = 12; + } + else if ( contains( fogDirection, Direction::RIGHT | Direction::TOP_RIGHT | Direction::TOP ) && !( fogDirection & ( Direction::BOTTOM | Direction::LEFT ) ) ) { + index = 12; + revert = true; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::TOP_LEFT ) + && !( fogDirection & ( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT | Direction::TOP_RIGHT ) ) ) { + index = 19; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::TOP_RIGHT ) + && !( fogDirection & ( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT | Direction::TOP_LEFT ) ) ) { + index = 19; + revert = true; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::BOTTOM_LEFT ) + && !( fogDirection & ( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT | Direction::TOP_LEFT ) ) ) { + index = 20; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::BOTTOM_RIGHT ) + && !( fogDirection & ( Direction::TOP_RIGHT | Direction::BOTTOM_LEFT | Direction::TOP_LEFT ) ) ) { + index = 20; + revert = true; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP ) + && !( fogDirection & ( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT | Direction::BOTTOM_LEFT | Direction::TOP_LEFT ) ) ) { + index = 22; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::BOTTOM_LEFT ) + && !( fogDirection & ( Direction::TOP | Direction::BOTTOM_RIGHT ) ) ) { + index = 24; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::BOTTOM_RIGHT ) + && !( fogDirection & ( Direction::TOP | Direction::BOTTOM_LEFT ) ) ) { + index = 24; + revert = true; + } + else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::LEFT | Direction::TOP_LEFT ) + && !( fogDirection & ( Direction::RIGHT | Direction::BOTTOM_LEFT ) ) ) { + index = 25; + } + else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::RIGHT | Direction::TOP_RIGHT ) + && !( fogDirection & ( Direction::LEFT | Direction::BOTTOM_RIGHT ) ) ) { + index = 25; + revert = true; + } + else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::BOTTOM_LEFT | Direction::LEFT ) + && !( fogDirection & ( Direction::RIGHT | Direction::TOP_LEFT ) ) ) { + index = 26; + } + else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::BOTTOM_RIGHT | Direction::RIGHT ) + && !( fogDirection & ( Direction::LEFT | Direction::TOP_RIGHT ) ) ) { + index = 26; + revert = true; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::TOP_LEFT | Direction::TOP ) + && !( fogDirection & ( Direction::BOTTOM | Direction::TOP_RIGHT ) ) ) { + index = 30; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::TOP_RIGHT | Direction::TOP ) + && !( fogDirection & ( Direction::BOTTOM | Direction::TOP_LEFT ) ) ) { + index = 30; + revert = true; + } + else if ( contains( fogDirection, Direction::BOTTOM | Direction::LEFT ) + && !( fogDirection & ( Direction::TOP | Direction::RIGHT | Direction::BOTTOM_LEFT ) ) ) { + index = 27; + } + else if ( contains( fogDirection, Direction::BOTTOM | Direction::RIGHT ) + && !( fogDirection & ( Direction::TOP | Direction::TOP_LEFT | Direction::LEFT | Direction::BOTTOM_RIGHT ) ) ) { + index = 27; + revert = true; + } + else if ( contains( fogDirection, Direction::LEFT | Direction::TOP ) + && !( fogDirection & ( Direction::TOP_LEFT | Direction::RIGHT | Direction::BOTTOM | Direction::BOTTOM_RIGHT ) ) ) { + index = 28; + } + else if ( contains( fogDirection, Direction::RIGHT | Direction::TOP ) + && !( fogDirection & ( Direction::TOP_RIGHT | Direction::LEFT | Direction::BOTTOM | Direction::BOTTOM_LEFT ) ) ) { + index = 28; + revert = true; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::TOP ) + && !( fogDirection & ( Direction::BOTTOM | Direction::TOP_LEFT | Direction::TOP_RIGHT ) ) ) { + index = 31; + } + else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::RIGHT ) + && !( fogDirection & ( Direction::LEFT | Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT ) ) ) { + index = 32; + } + else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::LEFT ) + && !( fogDirection & ( Direction::RIGHT | Direction::TOP_LEFT | Direction::BOTTOM_LEFT ) ) ) { + index = 32; + revert = true; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM ) + && !( fogDirection & ( Direction::TOP | Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ) ) ) { + index = 33; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | DIRECTION_BOTTOM_ROW ) && !( fogDirection & Direction::TOP ) ) { + index = ( tile.GetIndex() % 2 ) ? 0 : 1; + } + else if ( contains( fogDirection, DIRECTION_CENTER_ROW | DIRECTION_TOP_ROW ) && !( fogDirection & Direction::BOTTOM ) ) { + index = ( tile.GetIndex() % 2 ) ? 4 : 5; + } + else if ( contains( fogDirection, DIRECTION_CENTER_COL | DIRECTION_LEFT_COL ) && !( fogDirection & Direction::RIGHT ) ) { + index = ( tile.GetIndex() % 2 ) ? 2 : 3; + } + else if ( contains( fogDirection, DIRECTION_CENTER_COL | DIRECTION_RIGHT_COL ) && !( fogDirection & Direction::LEFT ) ) { + index = ( tile.GetIndex() % 2 ) ? 2 : 3; + revert = true; + } + else { + // unknown + DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid direction for fog: " << fogDirection << ". Tile index: " << tile.GetIndex() ) + const fheroes2::Image & sf = fheroes2::AGG::GetTIL( TIL::CLOF32, ( mp.x + mp.y ) % 4, 0 ); + area.DrawTile( dst, sf, mp ); + return; + } + + const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::CLOP32, index ); + area.BlitOnTile( dst, sprite, ( revert ? TILEWIDTH - sprite.x() - sprite.width() : sprite.x() ), sprite.y(), mp, revert, 255 ); + } + } + + void redrawPassable( const Tiles & tile, fheroes2::Image & dst, const int friendColors, const Interface::GameArea & area ) + { + #ifdef WITH_DEBUG + if ( tile.isFog( friendColors ) ) { + area.BlitOnTile( dst, getDebugFogImage(), 0, 0, Maps::GetPoint( tile.GetIndex() ), false, 255 ); + } + if ( 0 == tile.GetPassable() || DIRECTION_ALL != tile.GetPassable() ) { + area.BlitOnTile( dst, PassableViewSurface( tile.GetPassable() ), 0, 0, Maps::GetPoint( tile.GetIndex() ), false, 255 ); + } + #else + (void)dst; + (void)area; + (void)friendColors; + #endif + } + + void redrawBottomLayerObjects( const Tiles & tile, fheroes2::Image & dst, bool isPuzzleDraw, const Interface::GameArea & area, const uint8_t level ) + { + assert( level <= 0x03 ); + + const fheroes2::Point & mp = Maps::GetPoint( tile.GetIndex() ); + + // Since the original game stores information about objects in a very weird way and this is how it is implemented for us we need to do the following procedure: + // - run through all bottom objects first which are stored in the addon stack + // - check the main object which is on the tile + + // Some addons must be rendered after the main object on the tile. This applies for flags. + // Since this method is called intensively during rendering we have to avoid memory allocation on heap. + const size_t maxPostRenderAddons = 16; + std::array postRenderingAddon{}; + size_t postRenderAddonCount = 0; + + for ( const TilesAddon & addon : tile.getLevel1Addons() ) { + if ( ( addon._layerType & 0x03 ) != level ) { + continue; + } + + if ( isPuzzleDraw && MP2::isHiddenForPuzzle( tile.GetGround(), addon._objectIcnType, addon._imageIndex ) ) { + continue; + } + + if ( addon._objectIcnType == MP2::OBJ_ICN_TYPE_FLAG32 ) { + // Based on logically thinking it is impossible to have more than 16 flags on a single tile. + assert( postRenderAddonCount < maxPostRenderAddons ); + + postRenderingAddon[postRenderAddonCount] = &addon; + ++postRenderAddonCount; + continue; + } + + renderAddonObject( dst, area, mp, addon ); + } + + if ( tile.getObjectIcnType() != MP2::OBJ_ICN_TYPE_UNKNOWN && ( tile.getLayerType() & 0x03 ) == level + && ( !isPuzzleDraw || !MP2::isHiddenForPuzzle( tile.GetGround(), tile.getObjectIcnType(), tile.GetObjectSpriteIndex() ) ) ) { + renderMainObject( dst, area, mp, tile ); + } + + for ( size_t i = 0; i < postRenderAddonCount; ++i ) { + assert( postRenderingAddon[i] != nullptr ); + + renderAddonObject( dst, area, mp, *postRenderingAddon[i] ); + } + } + + void drawByObjectIcnType( const Tiles & tile, fheroes2::Image & output, const Interface::GameArea & area, const MP2::ObjectIcnType objectIcnType ) + { + const fheroes2::Point & tileOffset = Maps::GetPoint( tile.GetIndex() ); + + for ( const TilesAddon & addon : tile.getLevel1Addons() ) { + if ( addon._objectIcnType == objectIcnType ) { + renderAddonObject( output, area, tileOffset, addon ); + } + } + + if ( tile.getObjectIcnType() == objectIcnType ) { + renderMainObject( output, area, tileOffset, tile ); + } + + for ( const TilesAddon & addon : tile.getLevel2Addons() ) { + if ( addon._objectIcnType == objectIcnType ) { + renderAddonObject( output, area, tileOffset, addon ); + } + } + } + + std::vector getMonsterSpritesPerTile( const Tiles & tile ) + { + assert( tile.GetObject() == MP2::OBJ_MONSTER ); + + const Monster & monster = getMonsterFromTile( tile ); + const std::pair spriteIndices = GetMonsterSpriteIndices( tile, monster.GetSpriteIndex() ); + + const int icnId{ ICN::MINI_MONSTER_IMAGE }; + const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.first ); + const fheroes2::Point monsterSpriteOffset( monsterSprite.x() + 16, monsterSprite.y() + 30 ); + + std::vector outputSquareInfo; + std::vector> outputImageInfo; + fheroes2::DivideImageBySquares( monsterSpriteOffset, monsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); + + assert( outputSquareInfo.size() == outputImageInfo.size() ); + + std::vector objectInfo; + for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { + objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.first, false, + static_cast( 255 ) ); + } + + outputSquareInfo.clear(); + outputImageInfo.clear(); + + if ( spriteIndices.second > 0 ) { + const fheroes2::Sprite & secondaryMonsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.second ); + const fheroes2::Point secondaryMonsterSpriteOffset( secondaryMonsterSprite.x() + 16, secondaryMonsterSprite.y() + 30 ); + + fheroes2::DivideImageBySquares( secondaryMonsterSpriteOffset, secondaryMonsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); + + assert( outputSquareInfo.size() == outputImageInfo.size() ); + + for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { + objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.second, false, + static_cast( 255 ) ); + } + } + + return objectInfo; + } + + std::vector getMonsterShadowSpritesPerTile( const Tiles & tile ) + { + assert( tile.GetObject() == MP2::OBJ_MONSTER ); + + const Monster & monster = getMonsterFromTile( tile ); + const std::pair spriteIndices = GetMonsterSpriteIndices( tile, monster.GetSpriteIndex() ); + + const int icnId{ ICN::MINI_MONSTER_SHADOW }; + const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.first ); + const fheroes2::Point monsterSpriteOffset( monsterSprite.x() + 16, monsterSprite.y() + 30 ); + + std::vector outputSquareInfo; + std::vector> outputImageInfo; + fheroes2::DivideImageBySquares( monsterSpriteOffset, monsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); + + assert( outputSquareInfo.size() == outputImageInfo.size() ); + + std::vector objectInfo; + for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { + objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.first, false, + static_cast( 255 ) ); + } + + outputSquareInfo.clear(); + outputImageInfo.clear(); + + if ( spriteIndices.second > 0 ) { + const fheroes2::Sprite & secondaryMonsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.second ); + const fheroes2::Point secondaryMonsterSpriteOffset( secondaryMonsterSprite.x() + 16, secondaryMonsterSprite.y() + 30 ); + + fheroes2::DivideImageBySquares( secondaryMonsterSpriteOffset, secondaryMonsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); + + assert( outputSquareInfo.size() == outputImageInfo.size() ); + + for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { + objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.second, false, + static_cast( 255 ) ); + } + } + + return objectInfo; + } + + std::vector getBoatSpritesPerTile( const Tiles & tile ) + { + // TODO: combine both boat image generation for heroes and empty boats. + assert( tile.GetObject() == MP2::OBJ_BOAT ); + + const uint32_t spriteIndex = ( tile.GetObjectSpriteIndex() == 255 ) ? 18 : tile.GetObjectSpriteIndex(); + + const bool isReflected = ( spriteIndex > 128 ); + + const int icnId{ ICN::BOAT32 }; + const uint32_t icnIndex = spriteIndex % 128; + const fheroes2::Sprite & boatSprite = fheroes2::AGG::GetICN( icnId, icnIndex ); + + const fheroes2::Point boatSpriteOffset( ( isReflected ? ( TILEWIDTH + 1 - boatSprite.x() - boatSprite.width() ) : boatSprite.x() ), boatSprite.y() + TILEWIDTH - 11 ); + + std::vector outputSquareInfo; + std::vector> outputImageInfo; + fheroes2::DivideImageBySquares( boatSpriteOffset, boatSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); + + assert( outputSquareInfo.size() == outputImageInfo.size() ); + + std::vector objectInfo; + for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { + objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, isReflected, static_cast( 255 ) ); + } + + return objectInfo; + } + + std::vector getBoatShadowSpritesPerTile( const Tiles & tile ) + { + assert( tile.GetObject() == MP2::OBJ_BOAT ); + + // TODO: boat shadow logic is more complex than this and it is not directly depend on spriteIndex. Find the proper logic and fix it! + const uint32_t spriteIndex = ( tile.GetObjectSpriteIndex() == 255 ) ? 18 : tile.GetObjectSpriteIndex(); + + const int icnId{ ICN::BOATSHAD }; + const uint32_t icnIndex = spriteIndex % 128; + const fheroes2::Sprite & boatShadowSprite = fheroes2::AGG::GetICN( icnId, icnIndex ); + const fheroes2::Point boatShadowSpriteOffset( boatShadowSprite.x(), TILEWIDTH + boatShadowSprite.y() - 11 ); + + // Shadows cannot be flipped so flip flag is always false. + std::vector outputSquareInfo; + std::vector> outputImageInfo; + fheroes2::DivideImageBySquares( boatShadowSpriteOffset, boatShadowSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); + + assert( outputSquareInfo.size() == outputImageInfo.size() ); + + std::vector objectInfo; + for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { + objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, false, static_cast( 255 ) ); + } + + return objectInfo; + } + + std::vector getMineGuardianSpritesPerTile( const Tiles & tile ) + { + assert( tile.GetObject( false ) == MP2::OBJ_MINES ); + + std::vector objectInfo; + + const int32_t spellID = Maps::getMineSpellIdFromTile( tile ); + switch ( spellID ) { + case Spell::SETEGUARDIAN: + case Spell::SETAGUARDIAN: + case Spell::SETFGUARDIAN: + case Spell::SETWGUARDIAN: { + static_assert( Spell::SETAGUARDIAN - Spell::SETEGUARDIAN == 1 && Spell::SETFGUARDIAN - Spell::SETEGUARDIAN == 2 && Spell::SETWGUARDIAN - Spell::SETEGUARDIAN == 3, + "Why are you changing the order of spells?! Be extremely careful of what you are doing" ); + + const int icnId{ ICN::OBJNXTRA }; + const uint32_t icnIndex = spellID - Spell::SETEGUARDIAN; + const fheroes2::Sprite & image = fheroes2::AGG::GetICN( icnId, icnIndex ); + + std::vector outputSquareInfo; + std::vector> outputImageInfo; + fheroes2::DivideImageBySquares( { image.x(), image.y() }, image, TILEWIDTH, outputSquareInfo, outputImageInfo ); + + assert( outputSquareInfo.size() == outputImageInfo.size() ); + + for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { + objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, false, static_cast( 255 ) ); + } + break; + } + default: + break; + } + + return objectInfo; + } + + const fheroes2::Image & getTileSurface( const Tiles & tile ) + { + return fheroes2::AGG::GetTIL( TIL::GROUND32, tile.getTerrainImageIndex(), ( tile.getTerrainFlags() & 0x3 ) ); + } +} diff --git a/src/fheroes2/maps/maps_tiles_render.h b/src/fheroes2/maps/maps_tiles_render.h new file mode 100644 index 00000000000..d50fa9e84df --- /dev/null +++ b/src/fheroes2/maps/maps_tiles_render.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * fheroes2: https://github.com/ihhub/fheroes2 * + * Copyright (C) 2023 * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#pragma once + +#include + +#include "math_base.h" +#include "mp2.h" + +namespace fheroes2 +{ + class Image; + struct ObjectRenderingInfo; +} + +namespace Interface +{ + class GameArea; +} + +namespace Maps +{ + class Tiles; + struct TilesAddon; + + void redrawEmptyTile( fheroes2::Image & dst, const fheroes2::Point & mp, const Interface::GameArea & area ); + + void redrawTopLayerExtraObjects( const Tiles & tile, fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area ); + void redrawTopLayerObject( const Tiles & tile, fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area, const TilesAddon & addon ); + + void drawFog( const Tiles & tile, fheroes2::Image & dst, const Interface::GameArea & area ); + + void redrawPassable( const Tiles & tile, fheroes2::Image & dst, const int friendColors, const Interface::GameArea & area ); + + void redrawBottomLayerObjects( const Tiles & tile, fheroes2::Image & dst, bool isPuzzleDraw, const Interface::GameArea & area, const uint8_t level ); + + void drawByObjectIcnType( const Tiles & tile, fheroes2::Image & output, const Interface::GameArea & area, const MP2::ObjectIcnType objectIcnType ); + + std::vector getMonsterSpritesPerTile( const Tiles & tile ); + std::vector getMonsterShadowSpritesPerTile( const Tiles & tile ); + std::vector getBoatSpritesPerTile( const Tiles & tile ); + std::vector getBoatShadowSpritesPerTile( const Tiles & tile ); + std::vector getMineGuardianSpritesPerTile( const Tiles & tile ); + + const fheroes2::Image & getTileSurface( const Tiles & tile ); +}