From 1ecc79b384653eb3b998596ac20637babd47367f Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Fri, 25 Jun 2021 22:02:58 +0800 Subject: [PATCH] Fix passabilities of objects on map close #1951 close #3750 close #3745 close #1521 close #496 --- src/fheroes2/game/game_interface.cpp | 2 +- src/fheroes2/maps/maps_tiles.cpp | 746 +++++++-------------------- src/fheroes2/maps/maps_tiles.h | 26 +- src/fheroes2/maps/mp2.cpp | 76 ++- src/fheroes2/maps/mp2.h | 4 +- src/fheroes2/world/world.cpp | 7 +- src/fheroes2/world/world_loadmap.cpp | 54 +- 7 files changed, 277 insertions(+), 638 deletions(-) diff --git a/src/fheroes2/game/game_interface.cpp b/src/fheroes2/game/game_interface.cpp index c44dac2f6fa..952e709e348 100644 --- a/src/fheroes2/game/game_interface.cpp +++ b/src/fheroes2/game/game_interface.cpp @@ -261,7 +261,7 @@ int32_t Interface::Basic::GetDimensionDoorDestination( const int32_t from, const if ( valid ) { const Maps::Tiles & tile = world.GetTiles( dst ); - valid = ( ( spellROI & mp ) && MP2::isClearGroundObject( tile.GetObject() ) && water == world.GetTiles( dst ).isWater() ); + valid = ( ( spellROI & mp ) && tile.isClearGround() && water == tile.isWater() ); } cursor.SetThemes( valid ? ( water ? static_cast( Cursor::CURSOR_HERO_BOAT ) : static_cast( Cursor::CURSOR_HERO_MOVE ) ) diff --git a/src/fheroes2/maps/maps_tiles.cpp b/src/fheroes2/maps/maps_tiles.cpp index 9c30b869e3f..7267a4ac6b7 100644 --- a/src/fheroes2/maps/maps_tiles.cpp +++ b/src/fheroes2/maps/maps_tiles.cpp @@ -161,6 +161,77 @@ namespace return imageMap.emplace( passable, std::move( sf ) ).first->second; } #endif + + bool isTallObject( const int objectId ) + { + // Some objects don't allow diagonal moves from top to bottom as they're considered as very tall. + switch ( objectId ) { + case MP2::OBJ_WATERWHEEL: + case MP2::OBJN_WATERWHEEL: + case MP2::OBJ_WINDMILL: + case MP2::OBJN_WINDMILL: + case MP2::OBJ_STONES: + case MP2::OBJ_DRAGONCITY: + case MP2::OBJN_DRAGONCITY: + case MP2::OBJ_FORT: + case MP2::OBJN_FORT: + case MP2::OBJ_ALCHEMYLAB: + case MP2::OBJN_ALCHEMYLAB: + case MP2::OBJ_MINES: + case MP2::OBJN_MINES: + case MP2::OBJ_ABANDONEDMINE: + case MP2::OBJN_ABANDONEDMINE: + case MP2::OBJ_TRAVELLERTENT: + case MP2::OBJN_TRAVELLERTENT: + case MP2::OBJ_FREEMANFOUNDRY: + case MP2::OBJN_FREEMANFOUNDRY: + case MP2::OBJ_WAGONCAMP: + case MP2::OBJN_WAGONCAMP: + case MP2::OBJ_TREECITY: + case MP2::OBJN_TREECITY: + case MP2::OBJ_SAWMILL: + case MP2::OBJN_SAWMILL: + case MP2::OBJ_GRAVEYARD: + case MP2::OBJN_GRAVEYARD: + case MP2::OBJ_ARENA: + case MP2::OBJN_ARENA: + case MP2::OBJ_AIRALTAR: + case MP2::OBJN_AIRALTAR: + case MP2::OBJ_FIREALTAR: + case MP2::OBJN_FIREALTAR: + case MP2::OBJ_EARTHALTAR: + case MP2::OBJN_EARTHALTAR: + case MP2::OBJ_WATERALTAR: + case MP2::OBJN_WATERALTAR: + case MP2::OBJ_CITYDEAD: + case MP2::OBJN_CITYDEAD: + case MP2::OBJ_STABLES: + case MP2::OBJN_STABLES: + case MP2::OBJ_BARROWMOUNDS: + case MP2::OBJN_BARROWMOUNDS: + case MP2::OBJ_ORACLE: + case MP2::OBJN_ORACLE: + case MP2::OBJ_TEMPLE: + case MP2::OBJN_TEMPLE: + case MP2::OBJ_MERMAID: + case MP2::OBJN_MERMAID: + case MP2::OBJ_STANDINGSTONES: + case MP2::OBJ_PYRAMID: + case MP2::OBJN_PYRAMID: + case MP2::OBJ_TROLLBRIDGE: + case MP2::OBJN_TROLLBRIDGE: + case MP2::OBJ_HILLFORT: + case MP2::OBJ_ALCHEMYTOWER: + case MP2::OBJN_ALCHEMYTOWER: + case MP2::OBJ_HUTMAGI: + case MP2::OBJN_HUTMAGI: + case MP2::OBJ_DERELICTSHIP: + case MP2::OBJN_DERELICTSHIP: + return true; + } + + return false; + } } Maps::TilesAddon::TilesAddon() @@ -282,149 +353,6 @@ int Maps::Tiles::GetLoyaltyObject( const uint8_t tileset, const uint8_t icnIndex return MP2::OBJ_ZERO; } -int Maps::Tiles::GetPassable( const uint32_t tileset, const uint32_t index ) -{ - const int icn = MP2::GetICNObject( tileset ); - - switch ( icn ) { - case ICN::MTNSNOW: - case ICN::MTNSWMP: - case ICN::MTNLAVA: - case ICN::MTNDSRT: - case ICN::MTNMULT: - case ICN::MTNGRAS: - return ObjMnts1::GetPassable( icn, index ); - - case ICN::MTNCRCK: - case ICN::MTNDIRT: - return ObjMnts2::GetPassable( icn, index ); - - case ICN::TREJNGL: - case ICN::TREEVIL: - case ICN::TRESNOW: - case ICN::TREFIR: - case ICN::TREFALL: - case ICN::TREDECI: - return ObjTree::GetPassable( index ); - case ICN::OBJNSNOW: - return ObjSnow::GetPassable( index ); - case ICN::OBJNSWMP: - return ObjSwmp::GetPassable( index ); - case ICN::OBJNGRAS: - return ObjGras::GetPassable( index ); - case ICN::OBJNGRA2: - return ObjGra2::GetPassable( index ); - case ICN::OBJNCRCK: - return ObjCrck::GetPassable( index ); - case ICN::OBJNDIRT: - return ObjDirt::GetPassable( index ); - case ICN::OBJNDSRT: - return ObjDsrt::GetPassable( index ); - case ICN::OBJNMUL2: - return ObjMul2::GetPassable( index ); - case ICN::OBJNMULT: - return ObjMult::GetPassable( index ); - case ICN::OBJNLAVA: - return ObjLava::GetPassable( index ); - case ICN::OBJNLAV3: - return ObjLav3::GetPassable( index ); - case ICN::OBJNLAV2: - return ObjLav2::GetPassable( index ); - case ICN::OBJNWAT2: - return ObjWat2::GetPassable( index ); - case ICN::OBJNWATR: - return ObjWatr::GetPassable( index ); - - case ICN::OBJNTWBA: - return ObjTwba::GetPassable( index ); - case ICN::OBJNTOWN: - return ObjTown::GetPassable( index ); - - case ICN::X_LOC1: - return ObjXlc1::GetPassable( index ); - case ICN::X_LOC2: - return ObjXlc2::GetPassable( index ); - case ICN::X_LOC3: - return ObjXlc3::GetPassable( index ); - - default: - break; - } - - return DIRECTION_ALL; -} - -int Maps::TilesAddon::GetActionObject( const Maps::TilesAddon & ta ) -{ - switch ( MP2::GetICNObject( ta.object ) ) { - case ICN::MTNSNOW: - case ICN::MTNSWMP: - case ICN::MTNLAVA: - case ICN::MTNDSRT: - case ICN::MTNMULT: - case ICN::MTNGRAS: - return ObjMnts1::GetActionObject( ta.index ); - - case ICN::MTNCRCK: - case ICN::MTNDIRT: - return ObjMnts2::GetActionObject( ta.index ); - - case ICN::TREJNGL: - case ICN::TREEVIL: - case ICN::TRESNOW: - case ICN::TREFIR: - case ICN::TREFALL: - case ICN::TREDECI: - return ObjTree::GetActionObject( ta.index ); - - case ICN::OBJNSNOW: - return ObjSnow::GetActionObject( ta.index ); - case ICN::OBJNSWMP: - return ObjSwmp::GetActionObject( ta.index ); - case ICN::OBJNGRAS: - return ObjGras::GetActionObject( ta.index ); - case ICN::OBJNGRA2: - return ObjGra2::GetActionObject( ta.index ); - case ICN::OBJNCRCK: - return ObjCrck::GetActionObject( ta.index ); - case ICN::OBJNDIRT: - return ObjDirt::GetActionObject( ta.index ); - case ICN::OBJNDSRT: - return ObjDsrt::GetActionObject( ta.index ); - case ICN::OBJNMUL2: - return ObjMul2::GetActionObject( ta.index ); - case ICN::OBJNMULT: - return ObjMult::GetActionObject( ta.index ); - case ICN::OBJNLAVA: - return ObjLava::GetActionObject( ta.index ); - case ICN::OBJNLAV3: - return ObjLav3::GetActionObject( ta.index ); - case ICN::OBJNLAV2: - return ObjLav2::GetActionObject( ta.index ); - case ICN::OBJNWAT2: - return ObjWat2::GetActionObject( ta.index ); - case ICN::OBJNWATR: - return ObjWatr::GetActionObject( ta.index ); - - case ICN::OBJNTWBA: - return ObjTwba::GetActionObject( ta.index ); - case ICN::OBJNTOWN: - return ObjTown::GetActionObject( ta.index ); - - case ICN::X_LOC1: - return ObjXlc1::GetActionObject( ta.index ); - case ICN::X_LOC2: - return ObjXlc2::GetActionObject( ta.index ); - case ICN::X_LOC3: - return ObjXlc3::GetActionObject( ta.index ); - - default: - break; - } - - return MP2::OBJ_ZERO; -} - bool Maps::TilesAddon::isRoad() const { switch ( MP2::GetICNObject( object ) ) { @@ -462,11 +390,6 @@ bool Maps::TilesAddon::hasRoadFlag() const return ( object >> 1 ) & 1; } -bool Maps::TilesAddon::isRoadObject( const TilesAddon & ta ) -{ - return ICN::ROAD == MP2::GetICNObject( ta.object ); -} - bool Maps::TilesAddon::hasSpriteAnimation() const { return object & 1; @@ -506,190 +429,6 @@ bool Maps::TilesAddon::isShadow( const TilesAddon & ta ) return Tiles::isShadowSprite( ta.object, ta.index ); } -bool Maps::TilesAddon::isMounts( const TilesAddon & ta ) -{ - switch ( MP2::GetICNObject( ta.object ) ) { - case ICN::MTNSNOW: - case ICN::MTNSWMP: - case ICN::MTNLAVA: - case ICN::MTNDSRT: - case ICN::MTNMULT: - case ICN::MTNGRAS: - return !ObjMnts1::isShadow( ta.index ); - - case ICN::MTNCRCK: - case ICN::MTNDIRT: - return !ObjMnts2::isShadow( ta.index ); - - default: - break; - } - - return false; -} - -bool Maps::TilesAddon::isRocs( const TilesAddon & ta ) -{ - switch ( MP2::GetICNObject( ta.object ) ) { - // roc objects - case ICN::OBJNSNOW: - if ( ( ta.index > 21 && ta.index < 25 ) || ( ta.index > 25 && ta.index < 29 ) || ta.index == 30 || ta.index == 32 || ta.index == 34 || ta.index == 35 - || ( ta.index > 36 && ta.index < 40 ) ) - return true; - break; - - case ICN::OBJNSWMP: - if ( ta.index == 201 || ta.index == 205 || ( ta.index > 207 && ta.index < 211 ) ) - return true; - break; - - case ICN::OBJNGRAS: - if ( ( ta.index > 32 && ta.index < 36 ) || ta.index == 37 || ta.index == 38 || ta.index == 40 || ta.index == 41 || ta.index == 43 || ta.index == 45 ) - return true; - break; - - case ICN::OBJNDIRT: - if ( ta.index == 92 || ta.index == 93 || ta.index == 95 || ta.index == 98 || ta.index == 99 || ta.index == 101 || ta.index == 102 || ta.index == 104 - || ta.index == 105 ) - return true; - break; - - case ICN::OBJNCRCK: - if ( ta.index == 10 || ta.index == 11 || ta.index == 18 || ta.index == 19 || ta.index == 21 || ta.index == 22 || ( ta.index > 23 && ta.index < 28 ) - || ( ta.index > 28 && ta.index < 33 ) || ta.index == 34 || ta.index == 35 || ta.index == 37 || ta.index == 38 || ( ta.index > 39 && ta.index < 45 ) - || ta.index == 46 || ta.index == 47 || ta.index == 49 || ta.index == 50 || ta.index == 52 || ta.index == 53 || ta.index == 55 ) - return true; - break; - - case ICN::OBJNWAT2: - if ( ta.index == 0 || ta.index == 2 ) - return true; - break; - - case ICN::OBJNWATR: - if ( ta.index == 182 || ta.index == 183 || ( ta.index > 184 && ta.index < 188 ) ) - return true; - break; - - default: - break; - } - - return false; -} - -bool Maps::TilesAddon::isForests( const TilesAddon & ta ) -{ - switch ( MP2::GetICNObject( ta.object ) ) { - case ICN::TREJNGL: - case ICN::TREEVIL: - case ICN::TRESNOW: - case ICN::TREFIR: - case ICN::TREFALL: - case ICN::TREDECI: - return !ObjTree::isShadow( ta.index ); - - default: - break; - } - - return false; -} - -bool Maps::TilesAddon::isStump( const TilesAddon & ta ) -{ - switch ( MP2::GetICNObject( ta.object ) ) { - case ICN::OBJNSNOW: - if ( ta.index == 41 || ta.index == 42 ) - return true; - break; - - default: - break; - } - - return false; -} - -bool Maps::TilesAddon::isDeadTrees( const TilesAddon & ta ) -{ - switch ( MP2::GetICNObject( ta.object ) ) { - case ICN::OBJNMUL2: - if ( ta.index == 16 || ta.index == 18 || ta.index == 19 ) - return true; - break; - - case ICN::OBJNMULT: - if ( ta.index == 0 || ta.index == 2 || ta.index == 4 ) - return true; - break; - - case ICN::OBJNSNOW: - if ( ( ta.index > 50 && ta.index < 53 ) || ( ta.index > 54 && ta.index < 59 ) || ( ta.index > 59 && ta.index < 63 ) || ( ta.index > 63 && ta.index < 67 ) - || ta.index == 68 || ta.index == 69 || ta.index == 71 || ta.index == 72 || ta.index == 74 || ta.index == 75 || ta.index == 77 ) - return true; - break; - - case ICN::OBJNSWMP: - if ( ta.index == 161 || ta.index == 162 || ( ta.index > 163 && ta.index < 170 ) || ( ta.index > 170 && ta.index < 175 ) || ta.index == 176 || ta.index == 177 ) - return true; - break; - - default: - break; - } - - return false; -} - -bool Maps::TilesAddon::isCactus( const TilesAddon & ta ) -{ - switch ( MP2::GetICNObject( ta.object ) ) { - case ICN::OBJNDSRT: - if ( ta.index == 24 || ta.index == 26 || ta.index == 28 || ( ta.index > 29 && ta.index < 33 ) || ta.index == 34 || ta.index == 36 || ta.index == 37 - || ta.index == 39 || ta.index == 40 || ta.index == 42 || ta.index == 43 || ta.index == 45 || ta.index == 48 || ta.index == 49 || ta.index == 51 - || ta.index == 53 ) - return true; - break; - - case ICN::OBJNCRCK: - if ( ta.index == 14 || ta.index == 16 ) - return true; - break; - - default: - break; - } - - return false; -} - -bool Maps::TilesAddon::isTrees( const TilesAddon & ta ) -{ - switch ( MP2::GetICNObject( ta.object ) ) { - // tree objects - case ICN::OBJNDSRT: - if ( ta.index == 3 || ta.index == 4 || ta.index == 6 || ta.index == 7 || ta.index == 9 || ta.index == 10 || ta.index == 12 || ta.index == 74 || ta.index == 76 ) - return true; - break; - - case ICN::OBJNDIRT: - if ( ta.index == 115 || ta.index == 118 || ta.index == 120 || ta.index == 123 || ta.index == 125 || ta.index == 127 ) - return true; - break; - - case ICN::OBJNGRAS: - if ( ta.index == 80 || ( ta.index > 82 && ta.index < 86 ) || ta.index == 87 || ( ta.index > 88 && ta.index < 92 ) ) - return true; - break; - - default: - break; - } - - return false; -} - bool Maps::Tiles::isShadowSprite( const int icn, const uint8_t icnIndex ) { switch ( icn ) { @@ -846,12 +585,6 @@ std::pair Maps::Tiles::ColorRaceFromHeroSprite( const uint32_t heroSpr return res; } -bool Maps::TilesAddon::ForceLevel1( const TilesAddon & ta ) -{ - // broken ship: left roc - return ICN::OBJNWAT2 == MP2::GetICNObject( ta.object ) && ta.index == 11; -} - /* Maps::Addons */ void Maps::Addons::Remove( u32 uniq ) { @@ -898,7 +631,8 @@ void Maps::Tiles::Init( s32 index, const MP2::mp2tile_t & mp2 ) // those bitfields are set by map editor regardless if map object is there tileIsRoad = ( mp2.objectName1 >> 1 ) & 1; - if ( mp2.mapObject == MP2::OBJ_ZERO || ( quantity1 >> 1 ) & 1 ) { + // If an object has priority 2 (shadow) or 3 (ground) then we put it as an addon. + if ( mp2.mapObject == MP2::OBJ_ZERO && ( quantity1 >> 1 ) & 1 ) { AddonsPushLevel1( mp2 ); } else { @@ -1054,234 +788,99 @@ const fheroes2::Image & Maps::Tiles::GetTileSurface( void ) const return fheroes2::AGG::GetTIL( TIL::GROUND32, TileSpriteIndex(), TileSpriteShape() ); } -bool isMountsRocs( const Maps::TilesAddon & ta ) -{ - return Maps::TilesAddon::isMounts( ta ) || Maps::TilesAddon::isRocs( ta ); -} - -bool isForestsTrees( const Maps::TilesAddon & ta ) -{ - return Maps::TilesAddon::isForests( ta ) || Maps::TilesAddon::isTrees( ta ) || Maps::TilesAddon::isCactus( ta ); -} - -// Return true if tile will be impassable if another object overlays it. Edge case when dealing with mountain and forest tiles. -bool isImpassableIfOverlayed( uint8_t objectTileset, uint8_t icnIndex ) +int Maps::Tiles::getOriginalPassability() const { - // Unfortunately can't be determined by the object ID, have to check the tiles - switch ( MP2::GetICNObject( objectTileset ) ) { - case ICN::MTNSNOW: - case ICN::MTNSWMP: - case ICN::MTNLAVA: - case ICN::MTNDSRT: - case ICN::MTNMULT: - case ICN::MTNGRAS: - return !ObjMnts1::isShadow( icnIndex ); - - case ICN::MTNCRCK: - case ICN::MTNDIRT: - return !ObjMnts2::isShadow( icnIndex ); - - case ICN::TREJNGL: - case ICN::TREEVIL: - case ICN::TRESNOW: - case ICN::TREFIR: - case ICN::TREFALL: - case ICN::TREDECI: - return !ObjTree::isShadow( icnIndex ); - - case ICN::OBJNSNOW: - if ( ( icnIndex > 21 && icnIndex < 25 ) || ( icnIndex > 25 && icnIndex < 29 ) || icnIndex == 30 || icnIndex == 32 || icnIndex == 34 || icnIndex == 35 - || ( icnIndex > 36 && icnIndex < 40 ) ) - return true; - break; - - case ICN::OBJNSWMP: - if ( icnIndex == 201 || icnIndex == 205 || ( icnIndex > 207 && icnIndex < 211 ) ) - return true; - break; - - case ICN::OBJNGRAS: - if ( ( icnIndex > 32 && icnIndex < 36 ) || icnIndex == 37 || icnIndex == 38 || icnIndex == 40 || icnIndex == 41 || icnIndex == 43 || icnIndex == 45 - || icnIndex == 80 || ( icnIndex > 82 && icnIndex < 86 ) || icnIndex == 87 || ( icnIndex > 88 && icnIndex < 92 ) ) - return true; - break; - - case ICN::OBJNDIRT: - if ( icnIndex == 92 || icnIndex == 93 || icnIndex == 95 || icnIndex == 98 || icnIndex == 99 || icnIndex == 101 || icnIndex == 102 || icnIndex == 104 - || icnIndex == 105 || icnIndex == 115 || icnIndex == 118 || icnIndex == 120 || icnIndex == 123 || icnIndex == 125 || icnIndex == 127 ) - return true; - break; - - case ICN::OBJNCRCK: - if ( icnIndex == 10 || icnIndex == 11 || icnIndex == 14 || icnIndex == 16 || icnIndex == 18 || icnIndex == 19 || icnIndex == 21 || icnIndex == 22 - || ( icnIndex > 23 && icnIndex < 28 ) || ( icnIndex > 28 && icnIndex < 33 ) || icnIndex == 34 || icnIndex == 35 || icnIndex == 37 || icnIndex == 38 - || ( icnIndex > 39 && icnIndex < 45 ) || icnIndex == 46 || icnIndex == 47 || icnIndex == 49 || icnIndex == 50 || icnIndex == 52 || icnIndex == 53 - || icnIndex == 55 ) - return true; - break; - - case ICN::OBJNWAT2: - if ( icnIndex == 0 || icnIndex == 2 ) - return true; - break; + const int objId = GetObject( false ); - case ICN::OBJNWATR: - if ( icnIndex == 182 || icnIndex == 183 || ( icnIndex > 184 && icnIndex < 188 ) ) - return true; - break; - - case ICN::OBJNDSRT: - if ( icnIndex == 3 || icnIndex == 4 || icnIndex == 6 || icnIndex == 7 || icnIndex == 9 || icnIndex == 10 || icnIndex == 12 || icnIndex == 24 || icnIndex == 26 - || icnIndex == 28 || ( icnIndex > 29 && icnIndex < 33 ) || icnIndex == 34 || icnIndex == 36 || icnIndex == 37 || icnIndex == 39 || icnIndex == 40 - || icnIndex == 42 || icnIndex == 43 || icnIndex == 45 || icnIndex == 48 || icnIndex == 49 || icnIndex == 51 || icnIndex == 53 || icnIndex == 74 - || icnIndex == 76 ) - return true; - break; - - default: - break; + if ( MP2::isGroundObject( objId ) ) { + return MP2::getActionObjectDirection( objId ); } - return false; -} + if ( ( objectTileset == 0 || objectIndex == 255 ) || ( ( quantity1 >> 1 ) & 1 ) ) { + // No object exists. Make it fully passable. + return DIRECTION_ALL; + } -bool Exclude4LongObject( const Maps::TilesAddon & ta ) -{ - const int icn = MP2::GetICNObject( ta.object ); - return Maps::Tiles::isShadowSprite( icn, ta.index ) || icn == ICN::ROAD || icn == ICN::STREAM || ( icn == ICN::OBJNMUL2 && ta.index < 14 ); + // Objects have fixed passability. + return DIRECTION_CENTER_ROW | DIRECTION_BOTTOM_ROW; } -bool HaveLongObjectUniq( const Maps::Addons & level, u32 uid ) +void Maps::Tiles::setInitialPassability() { - for ( Maps::Addons::const_iterator it = level.begin(); it != level.end(); ++it ) - if ( !Exclude4LongObject( *it ) && ( *it ).isUniq( uid ) ) - return true; - return false; + tilePassable = getOriginalPassability(); } -bool Maps::Tiles::isLongObject( int direction ) +void Maps::Tiles::updatePassability() { - if ( Maps::isValidDirection( _index, direction ) ) { - const Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, direction ) ); - - for ( Addons::const_iterator it = addons_level1.begin(); it != addons_level1.end(); ++it ) - if ( !Exclude4LongObject( *it ) - && ( HaveLongObjectUniq( tile.addons_level1, ( *it ).uniq ) - || ( !Maps::TilesAddon::isTrees( *it ) && HaveLongObjectUniq( tile.addons_level2, ( *it ).uniq ) ) ) ) - return true; + if ( !Maps::isValidDirection( _index, Direction::LEFT ) ) { + tilePassable &= ~( Direction::LEFT | Direction::TOP_LEFT | Direction::BOTTOM_LEFT ); } - return false; -} - -void Maps::Tiles::UpdatePassable( void ) -{ - tilePassable = DIRECTION_ALL; -#ifdef WITH_DEBUG - impassableTileRule = 0; -#endif - - const int obj = GetObject( false ); - const bool emptyobj = MP2::OBJ_ZERO == obj || MP2::OBJ_COAST == obj || MP2::OBJ_EVENT == obj; - - if ( MP2::isActionObject( obj, isWater() ) ) { - tilePassable = MP2::GetObjectDirect( obj ); -#ifdef WITH_DEBUG - if ( tilePassable == 0 ) - impassableTileRule = 1; -#endif - return; + if ( !Maps::isValidDirection( _index, Direction::RIGHT ) ) { + tilePassable &= ~( Direction::RIGHT | Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT ); + } + if ( !Maps::isValidDirection( _index, Direction::TOP ) ) { + tilePassable &= ~( Direction::TOP | Direction::TOP_LEFT | Direction::TOP_RIGHT ); + } + if ( !Maps::isValidDirection( _index, Direction::BOTTOM ) ) { + tilePassable &= ~( Direction::BOTTOM | Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ); } - // on ground - if ( MP2::OBJ_HEROES != mp2_object && !isWater() ) { - bool hasRocksOrTrees = isImpassableIfOverlayed( objectTileset, objectIndex ); - bool mounts2 = std::any_of( addons_level2.begin(), addons_level2.end(), isMountsRocs ); - bool trees2 = std::any_of( addons_level2.begin(), addons_level2.end(), isForestsTrees ); - - // fix coast passable - if ( tilePassable && !emptyobj && Maps::TileIsCoast( _index, Direction::TOP | Direction::BOTTOM | Direction::LEFT | Direction::RIGHT ) && !isShadow() ) { - tilePassable = 0; -#ifdef WITH_DEBUG - impassableTileRule = 2; -#endif - } - - // fix mountain layer - if ( tilePassable && hasRocksOrTrees && ( mounts2 || trees2 ) ) { - tilePassable = 0; -#ifdef WITH_DEBUG - impassableTileRule = 3; -#endif - } + const int objId = GetObject( false ); + const bool isActionObject = MP2::isGroundObject( objId ); + if ( !isActionObject && objectTileset > 0 && objectIndex < 255 && ( ( quantity1 >> 1 ) & 1 ) == 0 ) { + // This is a non-action object. + if ( Maps::isValidDirection( _index, Direction::BOTTOM ) ) { + const Tiles & bottomTile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::BOTTOM ) ); - // town twba - if ( tilePassable && FindAddonICN( ICN::OBJNTWBA, 1 ) && ( mounts2 || trees2 ) ) { - tilePassable = 0; -#ifdef WITH_DEBUG - impassableTileRule = 5; -#endif - } + if ( isWater() != bottomTile.isWater() ) { + // If object is bordering water it must be marked as not passable. + tilePassable = 0; + return; + } - if ( Maps::isValidDirection( _index, Direction::TOP ) ) { - Tiles & top = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::TOP ) ); - // fix: rocs on water - if ( top.isWater() && top.tilePassable && !( Direction::TOP & top.tilePassable ) ) { - top.tilePassable = 0; -#ifdef WITH_DEBUG - top.impassableTileRule = 6; -#endif + if ( bottomTile.objectTileset > 0 && bottomTile.objectIndex < 255 && ( ( bottomTile.quantity1 >> 1 ) & 1 ) == 0 ) { + if ( bottomTile.uniq == uniq || isWater() != bottomTile.isWater() ) { + // It's the same object. + tilePassable = 0; + } + else { + const int bottomTileObjId = bottomTile.GetObject( false ); + const bool isBottomTileActionObject = MP2::isGroundObject( bottomTileObjId ); + if ( isBottomTileActionObject ) { + if ( ( MP2::getActionObjectDirection( bottomTileObjId ) & Direction::TOP ) == 0 ) { + tilePassable &= ~( Direction::BOTTOM | Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ); + } + } + else if ( bottomTile.mp2_object != 0 && bottomTile.mp2_object < 128 && MP2::isGroundObject( bottomTile.mp2_object + 128 ) + && ( bottomTile.getOriginalPassability() & Direction::TOP ) == 0 ) { + tilePassable &= ~( Direction::BOTTOM | Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ); + } + else { + tilePassable = 0; + } + } } } - } - - // fix bottom border: disable passable for all no action objects - if ( tilePassable && !Maps::isValidDirection( _index, Direction::BOTTOM ) && !emptyobj && !MP2::isActionObject( obj, isWater() ) ) { - tilePassable = 0; -#ifdef WITH_DEBUG - impassableTileRule = 7; -#endif - } - - // check all sprite (level 1) - tilePassable &= Tiles::GetPassable( objectTileset, objectIndex ); - for ( Addons::const_iterator it = addons_level1.begin(); it != addons_level1.end(); ++it ) { - if ( tilePassable ) { - tilePassable &= Tiles::GetPassable( it->object, it->index ); - -#ifdef WITH_DEBUG - if ( 0 == tilePassable ) - impassableTileRule = 8; -#endif + else { + tilePassable = 0; } } - // fix top passable - if ( Maps::isValidDirection( _index, Direction::TOP ) ) { - Tiles & top = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::TOP ) ); - - if ( isWater() == top.isWater() && isImpassableIfOverlayed( top.objectTileset, top.objectIndex ) && !MP2::isActionObject( top.GetObject( false ), isWater() ) - && ( tilePassable && !( tilePassable & DIRECTION_TOP_ROW ) ) && !( top.tilePassable & DIRECTION_TOP_ROW ) ) { - top.tilePassable = 0; -#ifdef WITH_DEBUG - top.impassableTileRule = 9; -#endif + // Left side. + if ( ( tilePassable & Direction::TOP_LEFT ) && Maps::isValidDirection( _index, Direction::LEFT ) ) { + const Tiles & leftTile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::LEFT ) ); + const bool leftTileTallObject = isTallObject( leftTile.GetObject( false ) ); + if ( leftTileTallObject && ( leftTile.getOriginalPassability() & Direction::TOP ) == 0 ) { + tilePassable &= ~Direction::TOP_LEFT; } } - - // fix corners - if ( Maps::isValidDirection( _index, Direction::LEFT ) ) { - Tiles & left = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::LEFT ) ); - - // left corner - if ( left.tilePassable && isLongObject( Direction::TOP ) && !( ( Direction::TOP | Direction::TOP_LEFT ) & tilePassable ) - && ( Direction::TOP_RIGHT & left.tilePassable ) ) { - left.tilePassable &= ~Direction::TOP_RIGHT; - } - else - // right corner - if ( tilePassable && left.isLongObject( Direction::TOP ) && !( ( Direction::TOP | Direction::TOP_RIGHT ) & left.tilePassable ) - && ( Direction::TOP_LEFT & tilePassable ) ) { - tilePassable &= ~Direction::TOP_LEFT; + + // Right side. + if ( ( tilePassable & Direction::TOP_RIGHT ) && Maps::isValidDirection( _index, Direction::RIGHT ) ) { + const Tiles & rightTile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::RIGHT ) ); + const bool rightTileTallObject = isTallObject( rightTile.GetObject( false ) ); + if ( rightTileTallObject && ( rightTile.getOriginalPassability() & Direction::TOP ) == 0 ) { + tilePassable &= ~Direction::TOP_RIGHT; } } } @@ -1308,10 +907,35 @@ int Maps::Tiles::GetPassable( void ) const return tilePassable; } +bool Maps::Tiles::isClearGround() const +{ + const int objId = GetObject( true ); + + switch ( objId ) { + case MP2::OBJ_ZERO: + case MP2::OBJ_COAST: + return true; + + default: + break; + } + + if ( objectTileset == 0 || objectIndex == 255 || ( ( quantity1 >> 1 ) & 1 ) == 1 ) { + if ( MP2::isActionObject( objId, isWater() ) ) { + return false; + } + // No objects are here. + return true; + } + + return false; +} + void Maps::Tiles::AddonsPushLevel1( const MP2::mp2tile_t & mt ) { - if ( mt.objectName1 && mt.indexName1 < 0xFF ) - AddonsPushLevel1( TilesAddon( mt.quantity1, mt.level1ObjectUID, mt.objectName1, mt.indexName1 ) ); + if ( mt.objectName1 && mt.indexName1 < 0xFF ) { + addons_level1.emplace_back( mt.quantity1, mt.level1ObjectUID, mt.objectName1, mt.indexName1 ); + } // MP2 "objectName" is a bitfield // 6 bits is ICN tileset id, 1 bit isRoad flag, 1 bit hasAnimation flag @@ -1322,43 +946,33 @@ void Maps::Tiles::AddonsPushLevel1( const MP2::mp2tile_t & mt ) void Maps::Tiles::AddonsPushLevel1( const MP2::mp2addon_t & ma ) { if ( ma.objectNameN1 && ma.indexNameN1 < 0xFF ) { - AddonsPushLevel1( TilesAddon( ma.quantityN, ma.level1ObjectUID, ma.objectNameN1, ma.indexNameN1 ) ); + addons_level1.emplace_back( ma.quantityN, ma.level1ObjectUID, ma.objectNameN1, ma.indexNameN1 ); } } void Maps::Tiles::AddonsPushLevel1( const TilesAddon & ta ) { - addons_level1.push_back( ta ); + addons_level1.emplace_back( ta ); } void Maps::Tiles::AddonsPushLevel2( const MP2::mp2tile_t & mt ) { if ( mt.objectName2 && mt.indexName2 < 0xFF ) { - AddonsPushLevel2( TilesAddon( mt.quantity1, mt.level2ObjectUID, mt.objectName2, mt.indexName2 ) ); + addons_level2.emplace_back( mt.quantity1, mt.level2ObjectUID, mt.objectName2, mt.indexName2 ); } } void Maps::Tiles::AddonsPushLevel2( const MP2::mp2addon_t & ma ) { if ( ma.objectNameN2 && ma.indexNameN2 < 0xFF ) { - AddonsPushLevel2( TilesAddon( ma.quantityN, ma.level2ObjectUID, ma.objectNameN2, ma.indexNameN2 ) ); + addons_level2.emplace_back( ma.quantityN, ma.level2ObjectUID, ma.objectNameN2, ma.indexNameN2 ); } } -void Maps::Tiles::AddonsPushLevel2( const TilesAddon & ta ) -{ - if ( TilesAddon::ForceLevel1( ta ) ) - addons_level1.push_back( ta ); - else - addons_level2.push_back( ta ); -} - void Maps::Tiles::AddonsSort() { // Push everything to the container and sort it by level. - bool topObjectExists = false; if ( objectTileset != 0 && objectIndex < 255 ) { - topObjectExists = true; addons_level1.emplace_front( quantity1 & 3, uniq, objectTileset, objectIndex ); } @@ -1374,7 +988,7 @@ void Maps::Tiles::AddonsSort() addons_level1.sort( TilesAddon::PredicateSortRules1 ); - if ( topObjectExists ) { + if ( !addons_level1.empty() ) { const TilesAddon & highestPriorityAddon = addons_level1.back(); uniq = highestPriorityAddon.uniq; objectTileset = highestPriorityAddon.object; @@ -1755,9 +1369,9 @@ std::string Maps::Tiles::String( void ) const { std::ostringstream os; - os << "----------------:--------" << std::endl - << "maps index : " << _index << ", " - << "point: x(" << GetCenter().x << "), y(" << GetCenter().y << ")" << std::endl + os << "----------------:>>>>>>>>" << std::endl + << "Tile index : " << _index << ", " + << "point: (" << GetCenter().x << ", " << GetCenter().y << ")" << std::endl << "id : " << uniq << std::endl << "mp2 object : " << GetObject() << ", (" << MP2::StringObject( GetObject() ) << ")" << std::endl << "tileset : " << static_cast( objectTileset ) << ", (" << ICN::GetString( MP2::GetICNObject( objectTileset ) ) << ")" << std::endl @@ -1792,7 +1406,6 @@ std::string Maps::Tiles::String( void ) const case MP2::OBJ_TROLLBRIDGE: case MP2::OBJ_DRAGONCITY: case MP2::OBJ_CITYDEAD: - // case MP2::OBJ_WATCHTOWER: case MP2::OBJ_EXCAVATION: case MP2::OBJ_CAVE: @@ -1803,7 +1416,6 @@ std::string Maps::Tiles::String( void ) const case MP2::OBJ_HALFLINGHOLE: case MP2::OBJ_PEASANTHUT: case MP2::OBJ_THATCHEDHUT: - // case MP2::OBJ_MONSTER: os << "count : " << MonsterCount() << std::endl; break; @@ -1842,7 +1454,7 @@ std::string Maps::Tiles::String( void ) const } } - os << "----------------:--------" << std::endl; + os << "----------------:<<<<<<<<" << std::endl; return os.str(); } diff --git a/src/fheroes2/maps/maps_tiles.h b/src/fheroes2/maps/maps_tiles.h index 1df445f4dfb..e94510759fc 100644 --- a/src/fheroes2/maps/maps_tiles.h +++ b/src/fheroes2/maps/maps_tiles.h @@ -82,25 +82,13 @@ namespace Maps std::string String( int level ) const; static bool isShadow( const TilesAddon & ); - static bool isRoadObject( const TilesAddon & ); static bool isResource( const TilesAddon & ); static bool isArtifact( const TilesAddon & ); static bool isFlag32( const TilesAddon & ); - static bool isMounts( const TilesAddon & ); - static bool isRocs( const TilesAddon & ); - static bool isForests( const TilesAddon & ); - static bool isTrees( const TilesAddon & ); - static bool isDeadTrees( const TilesAddon & ); - static bool isCactus( const TilesAddon & ); - static bool isStump( const TilesAddon & ); - static int GetActionObject( const TilesAddon & ); - static bool PredicateSortRules1( const TilesAddon &, const TilesAddon & ); - static bool ForceLevel1( const TilesAddon & ); - u32 uniq; u8 level; u8 object; @@ -190,7 +178,16 @@ namespace Maps uint32_t GetRegion() const; void UpdateRegion( uint32_t newRegionID ); - void UpdatePassable( void ); + + // Set initial passability based on information read from mp2 and addon structures. + void setInitialPassability(); + + // Update passability based on neigbhours around. + void updatePassability(); + + int getOriginalPassability() const; + + bool isClearGround() const; // ICN::FLAGS32 version void CaptureFlags32( int obj, int col ); @@ -219,7 +216,6 @@ namespace Maps void AddonsPushLevel1( const TilesAddon & ); void AddonsPushLevel2( const MP2::mp2tile_t & ); void AddonsPushLevel2( const MP2::mp2addon_t & ); - void AddonsPushLevel2( const TilesAddon & ); void AddonsSort( void ); void Remove( u32 uniqID ); @@ -293,7 +289,6 @@ namespace Maps static bool isShadowSprite( const int tileset, const uint8_t icnIndex ); static void UpdateAbandoneMineLeftSprite( uint8_t & tileset, uint8_t & index, const int resource ); static void UpdateAbandoneMineRightSprite( uint8_t & tileset, uint8_t & index ); - static int GetPassable( const uint32_t tileset, const uint32_t index ); static std::pair ColorRaceFromHeroSprite( const uint32_t heroSpriteIndex ); static std::pair GetMonsterSpriteIndices( const Tiles & tile, const uint32_t monsterIndex ); static void PlaceMonsterOnTile( Tiles & tile, const Monster & mons, const uint32_t count ); @@ -306,7 +301,6 @@ namespace Maps // correct flags, ICN::FLAGS32 vesion void CorrectFlags32( u32 index, bool ); void RemoveJailSprite( void ); - bool isLongObject( int direction ); void QuantitySetVariant( int ); void QuantitySetExt( int ); diff --git a/src/fheroes2/maps/mp2.cpp b/src/fheroes2/maps/mp2.cpp index eb8cb49d463..9e67ec07187 100644 --- a/src/fheroes2/maps/mp2.cpp +++ b/src/fheroes2/maps/mp2.cpp @@ -27,6 +27,8 @@ #include "settings.h" #include "translations.h" +#include + /* return name icn object */ int MP2::GetICNObject( int tileset ) { @@ -841,7 +843,35 @@ bool MP2::isWaterObject( int obj ) bool MP2::isGroundObject( int obj ) { // check if first bit is set - return obj > 127 && obj != OBJ_EVENT && obj != OBJN_STABLES && obj != OBJN_ALCHEMYTOWER; + if ( obj < 128 ) { + return false; + } + switch ( obj ) { + case OBJ_EVENT: + case OBJN_STABLES: + case OBJN_ALCHEMYTOWER: + case OBJ_UNKNW_E2: + case OBJ_UNKNW_E3: + case OBJ_UNKNW_E4: + case OBJ_UNKNW_E5: + case OBJ_UNKNW_E6: + case OBJ_UNKNW_E7: + case OBJ_UNKNW_E8: + case OBJ_UNKNW_F9: + case OBJ_UNKNW_FA: + case OBJ_UNKNW_91: + case OBJ_UNKNW_92: + case OBJ_UNKNW_9C: + case OBJ_UNKNW_A1: + case OBJ_UNKNW_AA: + case OBJ_UNKNW_B2: + case OBJ_UNKNW_B8: + case OBJ_UNKNW_B9: + case OBJ_UNKNW_D1: + return false; + } + + return true; } bool MP2::isQuantityObject( int obj ) @@ -1073,28 +1103,30 @@ bool MP2::isNeedStayFront( int obj ) return isPickupObject( obj ); } -bool MP2::isClearGroundObject( int obj ) +int MP2::getActionObjectDirection( const int objId ) { - switch ( obj ) { - case OBJ_ZERO: - case OBJ_COAST: - return true; - - default: - break; - } - - return false; -} - -int MP2::GetObjectDirect( int obj ) -{ - switch ( obj ) { + switch ( objId ) { case OBJ_JAIL: case OBJ_BARRIER: + case OBJ_ARTIFACT: + case OBJ_RESOURCE: + case OBJ_TREASURECHEST: + case OBJ_MONSTER: + case OBJ_ANCIENTLAMP: + case OBJ_CAMPFIRE: + case OBJ_SHIPWRECKSURVIROR: + case OBJ_FLOTSAM: + case OBJ_WATERCHEST: + case OBJ_BUOY: + case OBJ_WHIRLPOOL: + case OBJ_BOTTLE: + case OBJ_COAST: + return DIRECTION_ALL; + case OBJ_BOAT: return DIRECTION_ALL; case OBJ_SHIPWRECK: + // Logically right tile from Shipwreck is ocean so it could be safe to allow it. return Direction::CENTER | Direction::LEFT | DIRECTION_BOTTOM_ROW; case OBJ_DERELICTSHIP: @@ -1169,20 +1201,18 @@ int MP2::GetObjectDirect( int obj ) case OBJ_ARENA: case OBJ_SIRENS: case OBJ_MERMAID: - return DIRECTION_CENTER_ROW | DIRECTION_BOTTOM_ROW; - case OBJ_WATERWHEEL: - return Direction::CENTER | Direction::LEFT | Direction::RIGHT | DIRECTION_BOTTOM_ROW; - case OBJ_MAGELLANMAPS: - return Direction::CENTER | Direction::LEFT | DIRECTION_BOTTOM_ROW; + return DIRECTION_CENTER_ROW | DIRECTION_BOTTOM_ROW; case OBJ_CASTLE: return Direction::CENTER | Direction::BOTTOM; default: + // Did you add a new action object? Please add its passability! + assert( 0 ); break; } - return DIRECTION_ALL; + return Direction::UNKNOWN; } diff --git a/src/fheroes2/maps/mp2.h b/src/fheroes2/maps/mp2.h index 21290d67920..b9c69a2103c 100644 --- a/src/fheroes2/maps/mp2.h +++ b/src/fheroes2/maps/mp2.h @@ -578,14 +578,14 @@ namespace MP2 bool isProtectedObject( int obj ); bool isNeedStayFront( int obj ); - bool isClearGroundObject( int obj ); bool isDayLife( int obj ); bool isWeekLife( int obj ); bool isMonthLife( int obj ); bool isBattleLife( int obj ); - int GetObjectDirect( int obj ); + // Make sure that you pass a valid action object. + int getActionObjectDirection( const int objId ); } #endif diff --git a/src/fheroes2/world/world.cpp b/src/fheroes2/world/world.cpp index 531bc705a13..16984d4f4ed 100644 --- a/src/fheroes2/world/world.cpp +++ b/src/fheroes2/world/world.cpp @@ -1142,7 +1142,12 @@ void World::PostLoad() // update tile passable for ( Maps::Tiles & tile : vec_tiles ) { tile.updateEmpty(); - tile.UpdatePassable(); + tile.setInitialPassability(); + } + + // Once the original passabilities are set we know all neighbours. Now we have to update passabilities based on neighbours. + for ( Maps::Tiles & tile : vec_tiles ) { + tile.updatePassability(); } // cache data that's accessed often diff --git a/src/fheroes2/world/world_loadmap.cpp b/src/fheroes2/world/world_loadmap.cpp index cb47a33855f..b52bf082a30 100644 --- a/src/fheroes2/world/world_loadmap.cpp +++ b/src/fheroes2/world/world_loadmap.cpp @@ -661,36 +661,10 @@ void World::ProcessNewMap() } } - PostLoad(); - - // play with hero - vec_kingdoms.ApplyPlayWithStartingHero(); - - if ( Settings::Get().ExtWorldStartHeroLossCond4Humans() ) - vec_kingdoms.AddCondLossHeroes( vec_heroes ); - - // play with debug hero - if ( IS_DEVEL() ) { - // get first castle position - Kingdom & kingdom = GetKingdom( Color::GetFirst( Players::HumanColors() ) ); - - if ( !kingdom.GetCastles().empty() ) { - const Castle * castle = kingdom.GetCastles().front(); - const fheroes2::Point & cp = castle->GetCenter(); - Heroes * hero = vec_heroes.Get( Heroes::DEBUG_HERO ); - - if ( hero && !world.GetTiles( cp.x, cp.y + 1 ).GetHeroes() ) { - hero->Recruit( castle->GetColor(), fheroes2::Point( cp.x, cp.y + 1 ) ); - } - } - } - - // set ultimate + // Set Ultimate Artifact. + fheroes2::Point ultimate_pos; MapsTiles::iterator it = std::find_if( vec_tiles.begin(), vec_tiles.end(), []( const Maps::Tiles & tile ) { return tile.isObject( static_cast( MP2::OBJ_RNDULTIMATEARTIFACT ) ); } ); - fheroes2::Point ultimate_pos; - - // not found if ( vec_tiles.end() == it ) { // generate position for ultimate MapsIndexes pools; @@ -741,6 +715,30 @@ void World::ProcessNewMap() ultimate_pos = ( *it ).GetCenter(); } + PostLoad(); + + // play with hero + vec_kingdoms.ApplyPlayWithStartingHero(); + + if ( Settings::Get().ExtWorldStartHeroLossCond4Humans() ) + vec_kingdoms.AddCondLossHeroes( vec_heroes ); + + // play with debug hero + if ( IS_DEVEL() ) { + // get first castle position + Kingdom & kingdom = GetKingdom( Color::GetFirst( Players::HumanColors() ) ); + + if ( !kingdom.GetCastles().empty() ) { + const Castle * castle = kingdom.GetCastles().front(); + const fheroes2::Point & cp = castle->GetCenter(); + Heroes * hero = vec_heroes.Get( Heroes::DEBUG_HERO ); + + if ( hero && !world.GetTiles( cp.x, cp.y + 1 ).GetHeroes() ) { + hero->Recruit( castle->GetColor(), fheroes2::Point( cp.x, cp.y + 1 ) ); + } + } + } + vec_rumors.emplace_back( _( "The ultimate artifact is really the %{name}." ) ); StringReplace( vec_rumors.back(), "%{name}", ultimate_artifact.GetName() );