diff --git a/src/fheroes2/ai/ai_hero_action.cpp b/src/fheroes2/ai/ai_hero_action.cpp index ed538094a80..7da0aaa5d5b 100644 --- a/src/fheroes2/ai/ai_hero_action.cpp +++ b/src/fheroes2/ai/ai_hero_action.cpp @@ -572,10 +572,8 @@ namespace } if ( destroy ) { - setMonsterCountOnTile( tile, 0 ); - - removeObjectSprite( tile ); - tile.setAsEmpty(); + removeMainObjectFromTile( tile ); + resetObjectMetadata( tile ); } } @@ -589,8 +587,8 @@ namespace hero.GetKingdom().AddFundsResource( getFundsFromTile( tile ) ); } - removeObjectSprite( tile ); - resetObjectInfoOnTile( tile ); + removeMainObjectFromTile( tile ); + resetObjectMetadata( tile ); hero.GetPath().Reset(); } @@ -668,8 +666,8 @@ namespace kingdom.AddFundsResource( Funds( Resource::GOLD, gold ) ); } - removeObjectSprite( tile ); - resetObjectInfoOnTile( tile ); + removeMainObjectFromTile( tile ); + resetObjectMetadata( tile ); } void AIToCaptureObject( Heroes & hero, const MP2::MapObjectType objectType, const int32_t dstIndex ) @@ -742,7 +740,7 @@ namespace Maps::Tiles & tile = world.GetTiles( dst_index ); hero.GetKingdom().AddFundsResource( getFundsFromTile( tile ) ); - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); hero.setVisitedForAllies( dst_index ); } @@ -760,7 +758,7 @@ namespace hero.GetKingdom().AddFundsResource( Funds( Resource::GOLD, gold ) ); } - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); } hero.SetVisitedWideTile( dst_index, objectType, Visit::GLOBAL ); @@ -780,7 +778,7 @@ namespace else hero.GetKingdom().AddFundsResource( getFundsFromTile( tile ) ); - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); } hero.SetVisited( dst_index, Visit::GLOBAL ); @@ -793,8 +791,8 @@ namespace Maps::Tiles & tile = world.GetTiles( dst_index ); hero.GetKingdom().AddFundsResource( getFundsFromTile( tile ) ); - removeObjectSprite( tile ); - resetObjectInfoOnTile( tile ); + removeMainObjectFromTile( tile ); + resetObjectMetadata( tile ); } void AIToSign( Heroes & hero, int32_t dst_index ) @@ -1217,7 +1215,7 @@ namespace } if ( complete ) { - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); } else if ( 0 == gold && !hero.isObjectTypeVisited( objectType ) ) { // Modify morale @@ -1257,7 +1255,7 @@ namespace hero.AppendSpellToBook( spell ); } - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); hero.SetVisited( dst_index, Visit::GLOBAL ); } else { @@ -1332,7 +1330,7 @@ namespace AIBattleLose( hero, res, true ); } - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); } } @@ -1400,8 +1398,8 @@ namespace // Remove genie lamp sprite if no genies are available to hire. if ( MP2::OBJ_GENIE_LAMP == objectType && ( availableTroopCount == recruitTroopCount ) ) { - removeObjectSprite( tile ); - tile.setAsEmpty(); + removeMainObjectFromTile( tile ); + resetObjectMetadata( tile ); } } @@ -1501,8 +1499,8 @@ namespace const Kingdom & kingdom = hero.GetKingdom(); if ( kingdom.IsVisitTravelersTent( getColorFromTile( tile ) ) ) { - removeObjectSprite( tile ); - tile.setAsEmpty(); + removeMainObjectFromTile( tile ); + resetObjectMetadata( tile ); } } @@ -1527,8 +1525,8 @@ namespace else hero.PickupArtifact( getArtifactFromTile( tile ) ); - removeObjectSprite( tile ); - resetObjectInfoOnTile( tile ); + removeMainObjectFromTile( tile ); + resetObjectMetadata( tile ); } void AIToArtifact( Heroes & hero, int32_t dst_index ) @@ -1580,8 +1578,8 @@ namespace } if ( result && hero.PickupArtifact( art ) ) { - removeObjectSprite( tile ); - resetObjectInfoOnTile( tile ); + removeMainObjectFromTile( tile ); + resetObjectMetadata( tile ); } } @@ -1660,8 +1658,8 @@ namespace if ( kingdom.GetHeroes().size() < Kingdom::GetMaxHeroes() ) { Maps::Tiles & tile = world.GetTiles( tileIndex ); - removeObjectSprite( tile ); - tile.setAsEmpty(); + removeMainObjectFromTile( tile ); + resetObjectMetadata( tile ); Heroes * prisoner = world.FromJailHeroes( tileIndex ); @@ -2218,8 +2216,7 @@ namespace AI gameArea.runSingleObjectAnimation( std::make_shared( tileSource.GetObjectUID(), boatSource, MP2::OBJ_BOAT ) ); } else { - removeObjectSprite( tileSource ); - tileSource.setAsEmpty(); + removeMainObjectFromTile( tileSource ); } Maps::Tiles & tileDest = world.GetTiles( boatDestinationIndex ); diff --git a/src/fheroes2/gui/interface_gamearea.cpp b/src/fheroes2/gui/interface_gamearea.cpp index dfc328fceda..26a476e940a 100644 --- a/src/fheroes2/gui/interface_gamearea.cpp +++ b/src/fheroes2/gui/interface_gamearea.cpp @@ -1204,7 +1204,6 @@ Interface::ObjectFadingOutInfo::~ObjectFadingOutInfo() Maps::Tiles & tile = world.GetTiles( tileId ); if ( tile.GetObject() == type ) { - removeObjectSprite( tile ); - tile.setAsEmpty(); + removeMainObjectFromTile( tile ); } } diff --git a/src/fheroes2/heroes/heroes_action.cpp b/src/fheroes2/heroes/heroes_action.cpp index fb859e39cc9..111707dcabe 100644 --- a/src/fheroes2/heroes/heroes_action.cpp +++ b/src/fheroes2/heroes/heroes_action.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,7 @@ #include "localevent.h" #include "logging.h" #include "m82.h" +#include "map_object_info.h" #include "maps.h" #include "maps_objects.h" #include "maps_tiles.h" @@ -216,7 +218,34 @@ namespace I.setRedraw( Interface::REDRAW_RADAR ); } - void RecruitMonsterFromTile( Heroes & hero, Maps::Tiles & tile, std::string msg, const Troop & troop, bool remove ) + void runActionObjectFadeOutAnumation( const Maps::Tiles & tile, const MP2::MapObjectType objectType ) + { + uint32_t objectUID = 0; + + if ( Maps::getObjectTypeByIcn( tile.getObjectIcnType(), tile.GetObjectSpriteIndex() ) == objectType ) { + objectUID = tile.GetObjectUID(); + } + else { + // In maps made by the original map editor the action object can be in the bottom layer addons. + for ( auto iter = tile.getBottomLayerAddons().rbegin(); iter != tile.getBottomLayerAddons().rend(); ++iter ) { + if ( Maps::getObjectTypeByIcn( iter->_objectIcnType, iter->_imageIndex ) == objectType ) { + objectUID = iter->_uid; + break; + } + } + } + + assert( objectUID != 0 ); + + Interface::AdventureMap & I = Interface::AdventureMap::Get(); + I.getGameArea().runSingleObjectAnimation( std::make_shared( objectUID, tile.GetIndex(), objectType ) ); + + // Update radar in the place of the removed object. + I.getRadar().SetRenderArea( { Maps::GetPoint( tile.GetIndex() ), { 1, 1 } } ); + I.setRedraw( Interface::REDRAW_RADAR ); + } + + void RecruitMonsterFromTile( Heroes & hero, Maps::Tiles & tile, std::string msg, const Troop & troop, const bool remove ) { if ( !hero.GetArmy().CanJoinTroop( troop ) ) fheroes2::showStandardTextMessage( std::move( msg ), _( "You are unable to recruit at this time, your ranks are full." ), Dialog::OK ); @@ -227,10 +256,9 @@ namespace if ( remove && recruit == troop.GetCount() ) { Game::PlayPickupSound(); - setMonsterCountOnTile( tile, 0 ); + runActionObjectFadeOutAnumation( tile, tile.GetObject() ); - Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( - std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); + resetObjectMetadata( tile ); } else { setMonsterCountOnTile( tile, troop.GetCount() - recruit ); @@ -417,10 +445,11 @@ namespace if ( destroy ) { AudioManager::PlaySound( M82::KILLFADE ); - setMonsterCountOnTile( tile, 0 ); + assert( tile.GetObject() == MP2::OBJ_MONSTER ); + + runActionObjectFadeOutAnumation( tile, MP2::OBJ_MONSTER ); - Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( - std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); + resetObjectMetadata( tile ); } // Clear the hero's attacked monster tile index @@ -699,16 +728,9 @@ namespace Game::PlayPickupSound(); - I.getGameArea().runSingleObjectAnimation( std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); - - resetObjectInfoOnTile( tile ); + runActionObjectFadeOutAnumation( tile, objectType ); - if ( objectType == MP2::OBJ_RESOURCE ) { - // Update the position of picked up resource on radar to remove its mark. - const fheroes2::Point resourcePosition = Maps::GetPoint( dst_index ); - I.getRadar().SetRenderArea( { resourcePosition.x, resourcePosition.y, 1, 1 } ); - I.setRedraw( Interface::REDRAW_RADAR ); - } + resetObjectMetadata( tile ); } void ActionToObjectResource( const Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) @@ -778,7 +800,7 @@ namespace fheroes2::showStandardTextMessage( std::move( caption ), std::move( msg ), Dialog::OK ); } - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); hero.setVisitedForAllies( dst_index ); } @@ -816,7 +838,7 @@ namespace hero.PickupArtifact( art ); } - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); } else { message += '\n'; @@ -869,7 +891,7 @@ namespace hero.GetKingdom().AddFundsResource( funds ); } - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); } else { message += '\n'; @@ -906,10 +928,9 @@ namespace Game::PlayPickupSound(); - Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( - std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); + runActionObjectFadeOutAnumation( tile, objectType ); - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); } void ActionToShrine( Heroes & hero, int32_t dst_index ) @@ -1134,7 +1155,7 @@ namespace fheroes2::showStandardTextMessage( std::move( title ), std::move( msg ), Dialog::OK ); } - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); hero.SetVisited( dst_index, Visit::GLOBAL ); } else { @@ -1359,7 +1380,7 @@ namespace } if ( complete ) { - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); hero.SetVisited( dst_index, Visit::GLOBAL ); } else if ( 0 == gold ) { @@ -1520,10 +1541,9 @@ namespace Game::PlayPickupSound(); - Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( - std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); + runActionObjectFadeOutAnumation( tile, objectType ); - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); } void ActionToArtifact( Heroes & hero, int32_t dst_index ) @@ -1698,17 +1718,11 @@ namespace if ( result && hero.PickupArtifact( art ) ) { Game::PlayPickupSound(); - Interface::AdventureMap & I = Interface::AdventureMap::Get(); - - I.getGameArea().runSingleObjectAnimation( std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); + assert( tile.GetObject() == MP2::OBJ_ARTIFACT ); - resetObjectInfoOnTile( tile ); + runActionObjectFadeOutAnumation( tile, MP2::OBJ_ARTIFACT ); - const fheroes2::Point artifactPosition = Maps::GetPoint( dst_index ); - - // Update the position of picked up artifact on radar to remove its mark. - I.getRadar().SetRenderArea( { artifactPosition.x, artifactPosition.y, 1, 1 } ); - I.setRedraw( Interface::REDRAW_RADAR ); + resetObjectMetadata( tile ); } } @@ -1810,10 +1824,9 @@ namespace Game::PlayPickupSound(); - Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( - std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); + runActionObjectFadeOutAnumation( tile, objectType ); - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); } void ActionToGenieLamp( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) @@ -3072,7 +3085,7 @@ namespace break; } - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); } // Even if the hero has been defeated by a demon (and no longer belongs to any @@ -3240,10 +3253,7 @@ namespace _( "In a dazzling display of daring, you break into the local jail and free the hero imprisoned there, who, in return, pledges loyalty to your cause." ), Dialog::OK ); - Interface::AdventureMap & adventureMapInterface = Interface::AdventureMap::Get(); - - adventureMapInterface.getGameArea().runSingleObjectAnimation( - std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); + runActionObjectFadeOutAnumation( tile, objectType ); // TODO: add hero fading in animation together with jail animation. Heroes * prisoner = world.FromJailHeroes( dst_index ); @@ -3252,7 +3262,7 @@ namespace prisoner->Recruit( hero.GetColor(), Maps::GetPoint( dst_index ) ); // Update the kingdom heroes list including the scrollbar. - adventureMapInterface.GetIconsPanel().ResetIcons( ICON_HEROES ); + Interface::AdventureMap::Get().GetIconsPanel().ResetIcons( ICON_HEROES ); } } else { @@ -3494,8 +3504,7 @@ namespace AudioManager::PlaySound( M82::KILLFADE ); - Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( - std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); + runActionObjectFadeOutAnumation( tile, objectType ); } else { fheroes2::showStandardTextMessage( diff --git a/src/fheroes2/maps/map_object_info.cpp b/src/fheroes2/maps/map_object_info.cpp index 0694d546870..cc61cea2b66 100644 --- a/src/fheroes2/maps/map_object_info.cpp +++ b/src/fheroes2/maps/map_object_info.cpp @@ -3747,7 +3747,7 @@ namespace objects.emplace_back( std::move( object ) ); } - // Boat. + // Boat, direction: right. { Maps::ObjectInfo object{ MP2::OBJ_BOAT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_BOAT32, 18, fheroes2::Point{ 0, 0 }, MP2::OBJ_BOAT, Maps::OBJECT_LAYER ); @@ -4752,6 +4752,68 @@ namespace } } + void populateExtraBoatDirections( std::vector & objects ) + { + // Boat, direction: top. + { + Maps::ObjectInfo object{ MP2::OBJ_BOAT }; + object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_BOAT32, 0, fheroes2::Point{ 0, 0 }, MP2::OBJ_BOAT, Maps::OBJECT_LAYER ); + + objects.emplace_back( std::move( object ) ); + } + + // Boat, direction: top-right. + { + Maps::ObjectInfo object{ MP2::OBJ_BOAT }; + object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_BOAT32, 9, fheroes2::Point{ 0, 0 }, MP2::OBJ_BOAT, Maps::OBJECT_LAYER ); + + objects.emplace_back( std::move( object ) ); + } + + // Boat, direction: bottom-right. + { + Maps::ObjectInfo object{ MP2::OBJ_BOAT }; + object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_BOAT32, 27, fheroes2::Point{ 0, 0 }, MP2::OBJ_BOAT, Maps::OBJECT_LAYER ); + + objects.emplace_back( std::move( object ) ); + } + + // Boat, direction: bottom. + { + Maps::ObjectInfo object{ MP2::OBJ_BOAT }; + object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_BOAT32, 36, fheroes2::Point{ 0, 0 }, MP2::OBJ_BOAT, Maps::OBJECT_LAYER ); + + objects.emplace_back( std::move( object ) ); + } + + // Boat, direction: bottom-left. + // TODO: Redo direction setting not to use pseudo sprite index that leads to an empty image. + { + Maps::ObjectInfo object{ MP2::OBJ_BOAT }; + object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_BOAT32, 27 + 128, fheroes2::Point{ 0, 0 }, MP2::OBJ_BOAT, Maps::OBJECT_LAYER ); + + objects.emplace_back( std::move( object ) ); + } + + // Boat, direction: left. + // TODO: Redo direction setting not to use pseudo sprite index that leads to an empty image. + { + Maps::ObjectInfo object{ MP2::OBJ_BOAT }; + object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_BOAT32, 18 + 128, fheroes2::Point{ 0, 0 }, MP2::OBJ_BOAT, Maps::OBJECT_LAYER ); + + objects.emplace_back( std::move( object ) ); + } + + // Boat, direction: top-left. + // TODO: Redo direction setting not to use pseudo sprite index that leads to an empty image. + { + Maps::ObjectInfo object{ MP2::OBJ_BOAT }; + object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_BOAT32, 9 + 128, fheroes2::Point{ 0, 0 }, MP2::OBJ_BOAT, Maps::OBJECT_LAYER ); + + objects.emplace_back( std::move( object ) ); + } + } + void populateObjectData() { static bool isPopulated = false; @@ -4788,6 +4850,8 @@ namespace populateMonsters( objectData[static_cast( Maps::ObjectGroup::MONSTERS )] ); + populateExtraBoatDirections( objectData[static_cast( Maps::ObjectGroup::MAP_EXTRAS )] ); + #if defined( WITH_DEBUG ) // It is important to check that all data is accurately generated. for ( const auto & objects : objectData ) { diff --git a/src/fheroes2/maps/map_object_info.h b/src/fheroes2/maps/map_object_info.h index c6799701951..9d262ee8a0e 100644 --- a/src/fheroes2/maps/map_object_info.h +++ b/src/fheroes2/maps/map_object_info.h @@ -153,6 +153,9 @@ namespace Maps // Monsters. MONSTERS, + // Extra map objects that are not placed in editor (currently). + MAP_EXTRAS, + // IMPORTANT!!! // Put all new entries just above this entry. GROUP_COUNT diff --git a/src/fheroes2/maps/maps_tiles.cpp b/src/fheroes2/maps/maps_tiles.cpp index fcda947a689..5f1cfc559a8 100644 --- a/src/fheroes2/maps/maps_tiles.cpp +++ b/src/fheroes2/maps/maps_tiles.cpp @@ -41,6 +41,7 @@ #include "heroes.h" #include "icn.h" #include "logging.h" +#include "map_object_info.h" #include "maps.h" #include "maps_tiles_helper.h" // TODO: This file should not be included #include "mp2.h" @@ -546,7 +547,7 @@ void Maps::Tiles::setHero( Heroes * hero ) hero->setObjectTypeUnderHero( MP2::OBJ_NONE ); } else { - setAsEmpty(); + updateObjectType(); } _occupantHeroId = Heroes::UNKNOWN; @@ -1424,16 +1425,48 @@ void Maps::Tiles::fixMP2MapTileObjectType( Tiles & tile ) } } -void Maps::Tiles::Remove( uint32_t uniqID ) +bool Maps::Tiles::removeObjectPartsByUID( const uint32_t objectUID ) { - // TODO: this method must update the type of the main object - _addonBottomLayer.remove_if( [uniqID]( const Maps::TilesAddon & v ) { return v._uid == uniqID; } ); - _addonTopLayer.remove_if( [uniqID]( const Maps::TilesAddon & v ) { return v._uid == uniqID; } ); + bool isObjectPartRemoved = false; + if ( _mainAddon._uid == objectUID ) { + _mainAddon = {}; - if ( _mainAddon._uid == uniqID ) { - resetObjectSprite(); - _mainAddon._uid = 0; + isObjectPartRemoved = true; + } + + size_t addonCountBefore = _addonBottomLayer.size(); + _addonBottomLayer.remove_if( [objectUID]( const Maps::TilesAddon & v ) { return v._uid == objectUID; } ); + if ( addonCountBefore != _addonBottomLayer.size() ) { + isObjectPartRemoved = true; + } + + addonCountBefore = _addonTopLayer.size(); + _addonTopLayer.remove_if( [objectUID]( const Maps::TilesAddon & v ) { return v._uid == objectUID; } ); + if ( addonCountBefore != _addonTopLayer.size() ) { + isObjectPartRemoved = true; + } + + if ( isObjectPartRemoved ) { + // Since an object part was removed we have to update main object type. + updateObjectType(); + + setInitialPassability(); + updatePassability(); + + if ( Heroes::isValidId( _occupantHeroId ) ) { + Heroes * hero = world.GetHeroes( _occupantHeroId ); + if ( hero != nullptr ) { + hero->setObjectTypeUnderHero( _mainObjectType ); + + SetObject( MP2::OBJ_HERO ); + } + } + + // TODO: since we remove an object we need to check whether this tile contains any object with additional metadata. + // If it doesn't contain we need to reset metadata. } + + return isObjectPartRemoved; } void Maps::Tiles::removeObjects( const MP2::ObjectIcnType objectIcnType ) @@ -1523,24 +1556,62 @@ void Maps::Tiles::updateTileObjectIcnIndex( Maps::Tiles & tile, const uint32_t u tile._updateRoadFlag(); } -void Maps::Tiles::updateEmpty() +void Maps::Tiles::updateObjectType() { - if ( _mainObjectType == MP2::OBJ_NONE ) { - setAsEmpty(); + // After removing an object there could be an object part in the main addon. + MP2::MapObjectType objectType = Maps::getObjectTypeByIcn( _mainAddon._objectIcnType, _mainAddon._imageIndex ); + if ( MP2::isOffGameActionObject( objectType ) ) { + // Set object type only when this is an interactive object type to make sure that interaction can be done. + SetObject( objectType ); + return; + } + + // And sometimes even in the bottom layer addons. + // Take a note that we iterate object parts from back to front as the latest object part has higher priority. + if ( objectType == MP2::OBJ_NONE ) { + for ( auto iter = _addonBottomLayer.rbegin(); iter != _addonBottomLayer.rend(); ++iter ) { + const MP2::MapObjectType type = Maps::getObjectTypeByIcn( iter->_objectIcnType, iter->_imageIndex ); + if ( type == MP2::OBJ_NONE ) { + continue; + } + + if ( MP2::isOffGameActionObject( type ) ) { + // Set object type only when this is an interactive object type to make sure that interaction can be done. + SetObject( type ); + return; + } + + if ( objectType == MP2::OBJ_NONE ) { + objectType = type; + } + } + } + + // Or object part can be in the top layer addons. + // Take a note that we iterate object parts from back to front as the latest object part has higher priority. + for ( auto iter = _addonTopLayer.rbegin(); iter != _addonTopLayer.rend(); ++iter ) { + const MP2::MapObjectType type = Maps::getObjectTypeByIcn( iter->_objectIcnType, iter->_imageIndex ); + + if ( type != MP2::OBJ_NONE ) { + SetObject( type ); + return; + } + } + + // Top objects do not have object type while bottom object do. + if ( objectType != MP2::OBJ_NONE ) { + SetObject( objectType ); + return; } -} -void Maps::Tiles::setAsEmpty() -{ // If an object is removed we should validate if this tile a potential candidate to be a coast. // Check if this tile is not water and it has neighbouring water tiles. if ( isWater() ) { - SetObject( MP2::OBJ_NONE ); + assert( objectType == MP2::OBJ_NONE ); + SetObject( objectType ); return; } - bool isCoast = false; - const Indexes tileIndices = Maps::getAroundIndexes( _index, 1 ); for ( const int tileIndex : tileIndices ) { if ( tileIndex < 0 ) { @@ -1549,12 +1620,13 @@ void Maps::Tiles::setAsEmpty() } if ( world.GetTiles( tileIndex ).isWater() ) { - isCoast = true; - break; + SetObject( MP2::OBJ_COAST ); + return; } } - SetObject( isCoast ? MP2::OBJ_COAST : MP2::OBJ_NONE ); + assert( objectType == MP2::OBJ_NONE ); + SetObject( objectType ); } uint32_t Maps::Tiles::getObjectIdByObjectIcnType( const MP2::ObjectIcnType objectIcnType ) const diff --git a/src/fheroes2/maps/maps_tiles.h b/src/fheroes2/maps/maps_tiles.h index fa63263bd4f..12571d1952b 100644 --- a/src/fheroes2/maps/maps_tiles.h +++ b/src/fheroes2/maps/maps_tiles.h @@ -295,7 +295,10 @@ namespace Maps } void AddonsSort(); - void Remove( uint32_t uniqID ); + + // Returns true if any object part was removed. + bool removeObjectPartsByUID( const uint32_t objectUID ); + // Use to remove object by ICN type only from this tile. Should be used only for 1 tile size objects and roads or streams. void removeObjects( const MP2::ObjectIcnType objectIcnType ); @@ -340,11 +343,11 @@ namespace Maps Heroes * getHero() const; void setHero( Heroes * hero ); - // If tile is empty (MP2::OBJ_NONE) then verify whether it is a coast and update the tile if needed. - void updateEmpty(); - - // Set tile to coast MP2::OBJ_COAST) if it's near water or to empty (MP2::OBJ_NONE) - void setAsEmpty(); + // Set tile's object type according to the object's sprite if there is any, otherwise + // it is set to coast (MP2::OBJ_COAST) if it's near water or to empty (MP2::OBJ_NONE). + // This method works perfectly only on Resurrection (.fh2m) maps. + // It might not work properly on the original maps due to small differences in object types. + void updateObjectType(); uint32_t getObjectIdByObjectIcnType( const MP2::ObjectIcnType objectIcnType ) const; diff --git a/src/fheroes2/maps/maps_tiles_helper.cpp b/src/fheroes2/maps/maps_tiles_helper.cpp index 86f402cf785..cab61df0290 100644 --- a/src/fheroes2/maps/maps_tiles_helper.cpp +++ b/src/fheroes2/maps/maps_tiles_helper.cpp @@ -123,7 +123,7 @@ namespace if ( !art.isValid() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Failed to set an artifact over a random artifact on tile " << tile.GetIndex() ) resetObjectMetadata( tile ); - tile.setAsEmpty(); + tile.updateObjectType(); return; } @@ -180,7 +180,7 @@ namespace if ( !mons.isValid() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Failed to set a monster over a random monster on tile " << tile.GetIndex() ) resetObjectMetadata( tile ); - tile.setAsEmpty(); + tile.updateObjectType(); return; } @@ -194,50 +194,6 @@ namespace tile.setObjectSpriteIndex( static_cast( mons.GetID() - 1 ) ); // ICN::MONS32 starts from PEASANT } - void removeJailSprite( Maps::Tiles & tile ) - { - assert( tile.GetObject() == MP2::OBJ_JAIL ); - - // remove left sprite - if ( Maps::isValidDirection( tile.GetIndex(), Direction::LEFT ) ) { - const int32_t left = Maps::GetDirectionIndex( tile.GetIndex(), Direction::LEFT ); - world.GetTiles( left ).Remove( tile.GetObjectUID() ); - - // remove left left sprite - if ( Maps::isValidDirection( left, Direction::LEFT ) ) - world.GetTiles( Maps::GetDirectionIndex( left, Direction::LEFT ) ).Remove( tile.GetObjectUID() ); - } - - // remove top sprite - if ( Maps::isValidDirection( tile.GetIndex(), Direction::TOP ) ) { - const int32_t top = Maps::GetDirectionIndex( tile.GetIndex(), Direction::TOP ); - Maps::Tiles & topTile = world.GetTiles( top ); - topTile.Remove( tile.GetObjectUID() ); - - if ( topTile.GetObject() == MP2::OBJ_JAIL && topTile.GetObjectUID() == 0 ) { - // Since the main object was removed from the tile is it safe to mark it as empty, even if other non-main objects exist. - // This is not ideal but it doesn't break things. - topTile.setAsEmpty(); - topTile.FixObject(); - } - - // remove top left sprite - if ( Maps::isValidDirection( top, Direction::LEFT ) ) { - Maps::Tiles & leftTile = world.GetTiles( Maps::GetDirectionIndex( top, Direction::LEFT ) ); - leftTile.Remove( tile.GetObjectUID() ); - - if ( leftTile.GetObject() == MP2::OBJ_JAIL && leftTile.GetObjectUID() == 0 ) { - // Since the main object was removed from the tile is it safe to mark it as empty, even if other non-main objects exist. - // This is not ideal but it doesn't break things. - leftTile.setAsEmpty(); - leftTile.FixObject(); - } - } - } - - tile.Remove( tile.GetObjectUID() ); - } - // Returns the direction vector bits from 'centerTileIndex' where the ground is 'groundId'. int getGroundDirecton( const int32_t centerTileIndex, const int groundId ) { @@ -1340,6 +1296,44 @@ namespace return true; } + + bool removeObjectFromMapByUID( const int32_t startTileIndex, const uint32_t objectUID ) + { + assert( startTileIndex >= 0 && startTileIndex < world.w() * world.h() ); + + assert( objectUID > 0 ); + + std::vector tiles; + tiles.push_back( startTileIndex ); + + std::set processedTileIndicies; + + for ( size_t currentId = 0; currentId < tiles.size(); ++currentId ) { + if ( processedTileIndicies.count( tiles[currentId] ) == 1 ) { + // This tile is already processed, skip it. + continue; + } + + if ( world.GetTiles( tiles[currentId] ).removeObjectPartsByUID( objectUID ) ) { + // This tile has the object. Get neighboring tiles to see if they have the same. + const Maps::Indexes tileIndices = Maps::getAroundIndexes( tiles[currentId], 1 ); + for ( const int tileIndex : tileIndices ) { + if ( tileIndex < 0 ) { + // Invalid tile index. + continue; + } + + if ( processedTileIndicies.count( tileIndex ) == 0 ) { + tiles.push_back( tileIndex ); + } + } + } + + processedTileIndicies.emplace( tiles[currentId] ); + } + + return !processedTileIndicies.empty(); + } } namespace Maps @@ -1930,16 +1924,6 @@ namespace Maps } } - void resetObjectInfoOnTile( Tiles & tile ) - { - resetObjectMetadata( tile ); - - const MP2::MapObjectType objectType = tile.GetObject( false ); - if ( MP2::isPickupObject( objectType ) ) { - tile.setAsEmpty(); - } - } - uint32_t getMonsterCountFromTile( const Tiles & tile ) { switch ( tile.GetObject( false ) ) { @@ -2984,44 +2968,45 @@ namespace Maps restoreMineObjectType( Direction::TOP_RIGHT ); } - void removeObjectSprite( Tiles & tile ) + void removeMainObjectFromTile( const Tiles & tile ) { - switch ( tile.GetObject() ) { - case MP2::OBJ_MONSTER: - tile.Remove( tile.GetObjectUID() ); - break; - case MP2::OBJ_JAIL: - removeJailSprite( tile ); - tile.resetPassability(); - break; - case MP2::OBJ_ARTIFACT: { - const uint32_t uidArtifact = tile.getObjectIdByObjectIcnType( MP2::OBJ_ICN_TYPE_OBJNARTI ); - tile.Remove( uidArtifact ); + removeObjectFromTileByType( tile, tile.GetObject() ); + } - if ( Maps::isValidDirection( tile.GetIndex(), Direction::LEFT ) ) - world.GetTiles( Maps::GetDirectionIndex( tile.GetIndex(), Direction::LEFT ) ).Remove( uidArtifact ); - break; + bool removeObjectFromTileByType( const Tiles & tile, const MP2::MapObjectType objectType ) + { + assert( objectType != MP2::OBJ_NONE ); + + // Verify that this tile indeed contains an object with given object type. + uint32_t objectUID = 0; + + if ( Maps::getObjectTypeByIcn( tile.getObjectIcnType(), tile.GetObjectSpriteIndex() ) == objectType ) { + objectUID = tile.GetObjectUID(); } - case MP2::OBJ_TREASURE_CHEST: - case MP2::OBJ_RESOURCE: { - const uint32_t uidResource = tile.getObjectIdByObjectIcnType( MP2::OBJ_ICN_TYPE_OBJNRSRC ); - tile.Remove( uidResource ); - if ( Maps::isValidDirection( tile.GetIndex(), Direction::LEFT ) ) - world.GetTiles( Maps::GetDirectionIndex( tile.GetIndex(), Direction::LEFT ) ).Remove( uidResource ); - break; + if ( objectUID == 0 ) { + for ( auto iter = tile.getTopLayerAddons().rbegin(); iter != tile.getTopLayerAddons().rend(); ++iter ) { + if ( Maps::getObjectTypeByIcn( iter->_objectIcnType, iter->_imageIndex ) == objectType ) { + objectUID = iter->_uid; + break; + } + } } - case MP2::OBJ_BARRIER: - tile.resetPassability(); - [[fallthrough]]; - default: - // remove shadow sprite from left cell - if ( Maps::isValidDirection( tile.GetIndex(), Direction::LEFT ) ) - world.GetTiles( Maps::GetDirectionIndex( tile.GetIndex(), Direction::LEFT ) ).Remove( tile.GetObjectUID() ); - tile.Remove( tile.GetObjectUID() ); - break; + if ( objectUID == 0 ) { + for ( auto iter = tile.getBottomLayerAddons().rbegin(); iter != tile.getBottomLayerAddons().rend(); ++iter ) { + if ( Maps::getObjectTypeByIcn( iter->_objectIcnType, iter->_imageIndex ) == objectType ) { + objectUID = iter->_uid; + break; + } + } } + + if ( objectUID == 0 ) { + return false; + } + + return removeObjectFromMapByUID( tile.GetIndex(), objectUID ); } bool isClearGround( const Tiles & tile ) diff --git a/src/fheroes2/maps/maps_tiles_helper.h b/src/fheroes2/maps/maps_tiles_helper.h index 178bbb7ab36..aadfb72247f 100644 --- a/src/fheroes2/maps/maps_tiles_helper.h +++ b/src/fheroes2/maps/maps_tiles_helper.h @@ -129,8 +129,6 @@ namespace Maps void resetObjectMetadata( Tiles & tile ); - void resetObjectInfoOnTile( Tiles & tile ); - uint32_t getMonsterCountFromTile( const Tiles & tile ); void setMonsterCountOnTile( Tiles & tile, uint32_t count ); @@ -154,7 +152,9 @@ namespace Maps // updated separately. void restoreAbandonedMine( Tiles & tile, const int resource ); - void removeObjectSprite( Tiles & tile ); + void removeMainObjectFromTile( const Tiles & tile ); + + bool removeObjectFromTileByType( const Tiles & tile, const MP2::MapObjectType objectType ); bool isClearGround( const Tiles & tile ); diff --git a/src/fheroes2/world/world.cpp b/src/fheroes2/world/world.cpp index 631375cc768..cff505b464b 100644 --- a/src/fheroes2/world/world.cpp +++ b/src/fheroes2/world/world.cpp @@ -1293,7 +1293,11 @@ void World::resetPathfinder() void World::updatePassabilities() { for ( Maps::Tiles & tile : vec_tiles ) { - tile.updateEmpty(); + // If tile is empty then update tile's object type if needed. + if ( tile.isSameMainObject( MP2::OBJ_NONE ) ) { + tile.updateObjectType(); + } + tile.setInitialPassability(); } diff --git a/src/fheroes2/world/world_loadmap.cpp b/src/fheroes2/world/world_loadmap.cpp index 9eef2beec77..e57f9c04765 100644 --- a/src/fheroes2/world/world_loadmap.cpp +++ b/src/fheroes2/world/world_loadmap.cpp @@ -1152,8 +1152,7 @@ bool World::ProcessNewMP2Map( const std::string & filename, const bool checkPoLO ultimateArtifactRadius = static_cast( ultArtTileIter->metadata()[0] ); // Remove the predefined Ultimate Artifact object - ultArtTileIter->Remove( ultArtTileIter->GetObjectUID() ); - ultArtTileIter->setAsEmpty(); + ultArtTileIter->removeObjectPartsByUID( ultArtTileIter->GetObjectUID() ); } setUltimateArtifact( ultimateArtifactTileId, ultimateArtifactRadius ); @@ -1249,7 +1248,7 @@ bool World::updateTileMetadata( Maps::Tiles & tile, const MP2::MapObjectType obj case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_WATER_ALTAR: // We need to clear metadata because it is being stored as a part of an MP2 object. - resetObjectInfoOnTile( tile ); + resetObjectMetadata( tile ); updateObjectInfoTile( tile, true ); break; @@ -1260,7 +1259,7 @@ bool World::updateTileMetadata( Maps::Tiles & tile, const MP2::MapObjectType obj case MP2::OBJ_HERO: { // remove map editor sprite if ( tile.getObjectIcnType() == MP2::OBJ_ICN_TYPE_MINIHERO ) { - tile.Remove( tile.GetObjectUID() ); + tile.removeObjectPartsByUID( tile.GetObjectUID() ); } Heroes * chosenHero = GetHeroes( Maps::GetPoint( tile.GetIndex() ) );