diff --git a/src/fheroes2/dialog/dialog_quickinfo.cpp b/src/fheroes2/dialog/dialog_quickinfo.cpp index 7e97cef7b87..d36d933a700 100644 --- a/src/fheroes2/dialog/dialog_quickinfo.cpp +++ b/src/fheroes2/dialog/dialog_quickinfo.cpp @@ -416,9 +416,9 @@ void Dialog::QuickInfo( const Maps::Tiles & tile ) const Maps::Tiles & right = world.GetTiles( tile.GetIndex() + 1 ); const Maps::Tiles * center = nullptr; - if ( MP2::isGroundObject( left.GetObject( false ) ) ) + if ( MP2::isActionObject( left.GetObject( false ) ) ) center = &left; - else if ( MP2::isGroundObject( right.GetObject( false ) ) ) + else if ( MP2::isActionObject( right.GetObject( false ) ) ) center = &right; if ( center ) { 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/game/game_startgame.cpp b/src/fheroes2/game/game_startgame.cpp index dcde8a08c17..c7bcf5d5349 100644 --- a/src/fheroes2/game/game_startgame.cpp +++ b/src/fheroes2/game/game_startgame.cpp @@ -422,7 +422,7 @@ int Interface::Basic::GetCursorFocusShipmaster( const Heroes & from_hero, const default: if ( water ) { - if ( MP2::isWaterObject( tile.GetObject() ) ) + if ( MP2::isWaterActionObject( tile.GetObject() ) ) return Cursor::DistanceThemes( Cursor::CURSOR_HERO_BOAT_ACTION, from_hero.GetRangeRouteDays( tile.GetIndex() ) ); else if ( tile.isPassable( Direction::CENTER, true, false, from_hero.GetColor() ) ) return Cursor::DistanceThemes( Cursor::CURSOR_HERO_BOAT, from_hero.GetRangeRouteDays( tile.GetIndex() ) ); @@ -506,7 +506,7 @@ int Interface::Basic::GetCursorFocusHeroes( const Heroes & from_hero, const Maps default: if ( from_hero.Modes( Heroes::GUARDIAN ) ) return Cursor::POINTER; - else if ( MP2::isGroundObject( tile.GetObject() ) ) { + else if ( MP2::isActionObject( tile.GetObject() ) ) { bool protection = false; if ( !MP2::isPickupObject( tile.GetObject() ) && !MP2::isAbandonedMine( tile.GetObject() ) ) { protection = ( Maps::TileIsUnderProtection( tile.GetIndex() ) || ( !from_hero.isFriends( tile.QuantityColor() ) && tile.CaptureObjectIsProtection() ) ); diff --git a/src/fheroes2/kingdom/kingdom.cpp b/src/fheroes2/kingdom/kingdom.cpp index 2c00072c6cb..d24fd7a1643 100644 --- a/src/fheroes2/kingdom/kingdom.cpp +++ b/src/fheroes2/kingdom/kingdom.cpp @@ -419,7 +419,7 @@ void Kingdom::SetVisited( s32 index, int object ) bool Kingdom::isValidKingdomObject( const Maps::Tiles & tile, int objectID ) const { - if ( !MP2::isGroundObject( objectID ) && objectID != MP2::OBJ_COAST ) + if ( !MP2::isActionObject( objectID ) && objectID != MP2::OBJ_COAST ) return false; if ( isVisited( tile.GetIndex(), objectID ) ) diff --git a/src/fheroes2/maps/maps_tiles.cpp b/src/fheroes2/maps/maps_tiles.cpp index 4c29c622e36..2fc558e0caa 100644 --- a/src/fheroes2/maps/maps_tiles.cpp +++ b/src/fheroes2/maps/maps_tiles.cpp @@ -162,6 +162,131 @@ 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_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: + case MP2::OBJ_CASTLE: + case MP2::OBJN_CASTLE: + case MP2::OBJ_SHIPWRECK: + case MP2::OBJN_SHIPWRECK: + case MP2::OBJ_OBSERVATIONTOWER: + case MP2::OBJN_OBSERVATIONTOWER: + case MP2::OBJ_TREEHOUSE: + case MP2::OBJN_TREEHOUSE: + case MP2::OBJ_WITCHSHUT: + case MP2::OBJN_WITCHSHUT: + case MP2::OBJ_XANADU: + case MP2::OBJN_XANADU: + return true; + default: + break; + } + + return false; + } + + bool isShortObject( const int objectId ) + { + // Some objects allow middle moves even being attached to the bottom. + // These object actually don't have any sprites on tiles above them within addon 2 level objects. + // TODO: find a better way to do not hardcode values here. + + switch ( objectId ) { + case MP2::OBJ_HALFLINGHOLE: + case MP2::OBJN_HALFLINGHOLE: + case MP2::OBJ_LEANTO: + case MP2::OBJ_WATERLAKE: + case MP2::OBJ_TARPIT: + case MP2::OBJ_MERCENARYCAMP: + case MP2::OBJN_MERCENARYCAMP: + case MP2::OBJ_STANDINGSTONES: + case MP2::OBJ_SHRINE1: + case MP2::OBJ_SHRINE2: + case MP2::OBJ_SHRINE3: + case MP2::OBJ_MAGICGARDEN: + case MP2::OBJ_RUINS: + case MP2::OBJN_RUINS: + case MP2::OBJ_SIGN: + case MP2::OBJ_IDOL: + case MP2::OBJ_STONELITHS: + case MP2::OBJN_STONELITHS: + case MP2::OBJ_WAGON: + case MP2::OBJ_WAGONCAMP: + case MP2::OBJN_WAGONCAMP: + case MP2::OBJ_GOBLINHUT: + case MP2::OBJ_FAERIERING: + case MP2::OBJN_FAERIERING: + case MP2::OBJ_BARRIER: + case MP2::OBJ_MAGICWELL: + return true; + default: + break; + } + + return false; + } } Maps::TilesAddon::TilesAddon() @@ -169,7 +294,6 @@ Maps::TilesAddon::TilesAddon() , level( 0 ) , object( 0 ) , index( 0 ) - , tmp( 0 ) {} Maps::TilesAddon::TilesAddon( int lv, u32 gid, int obj, u32 ii ) @@ -177,7 +301,6 @@ Maps::TilesAddon::TilesAddon( int lv, u32 gid, int obj, u32 ii ) , level( lv ) , object( obj ) , index( ii ) - , tmp( 0 ) {} std::string Maps::TilesAddon::String( int lvl ) const @@ -187,8 +310,7 @@ std::string Maps::TilesAddon::String( int lvl ) const << "uniq : " << uniq << std::endl << "tileset : " << static_cast( object ) << ", (" << ICN::GetString( MP2::GetICNObject( object ) ) << ")" << std::endl << "index : " << static_cast( index ) << std::endl - << "level : " << static_cast( level ) << ", (" << static_cast( level % 4 ) << ")" << std::endl - << "tmp : " << static_cast( tmp ) << std::endl; + << "level : " << static_cast( level ) << ", (" << static_cast( level % 4 ) << ")" << std::endl; return os.str(); } @@ -197,7 +319,6 @@ Maps::TilesAddon::TilesAddon( const Maps::TilesAddon & ta ) , level( ta.level ) , object( ta.object ) , index( ta.index ) - , tmp( ta.tmp ) {} bool Maps::TilesAddon::isICN( int icn ) const @@ -283,149 +404,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 ) ) { @@ -463,11 +441,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; @@ -507,190 +480,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 ) { @@ -847,12 +636,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 ) { @@ -884,6 +667,8 @@ Maps::Tiles::Tiles() void Maps::Tiles::Init( s32 index, const MP2::mp2tile_t & mp2 ) { tilePassable = DIRECTION_ALL; + + _level = mp2.quantity1 & 0x03; quantity1 = mp2.quantity1; quantity2 = mp2.quantity2; quantity3 = 0; @@ -899,7 +684,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 && ( _level >> 1 ) & 1 ) { AddonsPushLevel1( mp2 ); } else { @@ -1055,236 +841,143 @@ const fheroes2::Image & Maps::Tiles::GetTileSurface( void ) const return fheroes2::AGG::GetTIL( TIL::GROUND32, TileSpriteIndex(), TileSpriteShape() ); } -bool isMountsRocs( const Maps::TilesAddon & ta ) +int Maps::Tiles::getOriginalPassability() const { - 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 ); -} + const int objId = GetObject( false ); -// 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 ) -{ - // 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; - - 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::isActionObject( objId ) ) { + return MP2::getActionObjectDirection( objId ); } - return false; -} + if ( ( objectTileset == 0 || objectIndex == 255 ) || ( ( _level >> 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 ); + } + 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 ); } - return false; -} -void Maps::Tiles::UpdatePassable( void ) -{ - tilePassable = DIRECTION_ALL; -#ifdef WITH_DEBUG - impassableTileRule = 0; -#endif + const int objId = GetObject( false ); + const bool isActionObject = MP2::isActionObject( objId ); + if ( !isActionObject && objectTileset > 0 && objectIndex < 255 && ( ( _level >> 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 ) ); - const int obj = GetObject( false ); - const bool emptyobj = MP2::OBJ_ZERO == obj || MP2::OBJ_COAST == obj || MP2::OBJ_EVENT == obj; + // If a bottom tile has the same object ID then this tile is inaccessible. + std::vector tileUIDs; + if ( objectTileset > 0 && objectIndex < 255 && uniq != 0 && ( ( _level >> 1 ) & 1 ) == 0 ) { + tileUIDs.emplace_back( uniq ); + } - if ( MP2::isActionObject( obj, isWater() ) ) { - tilePassable = MP2::GetObjectDirect( obj ); -#ifdef WITH_DEBUG - if ( tilePassable == 0 ) - impassableTileRule = 1; -#endif - return; - } + for ( const TilesAddon & addon : addons_level1 ) { + if ( addon.uniq != 0 && ( ( addon.level >> 1 ) & 1 ) == 0 ) { + tileUIDs.emplace_back( addon.uniq ); + } + } - // 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 ); + for ( const uint32_t objectId : tileUIDs ) { + if ( bottomTile.doesObjectExist( objectId ) ) { + tilePassable = 0; + return; + } + } - // 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 - } + if ( isWater() != bottomTile.isWater() ) { + // If object is bordering water then it must be marked as not passable. + tilePassable = 0; + return; + } - // fix mountain layer - if ( tilePassable && hasRocksOrTrees && ( mounts2 || trees2 ) ) { - tilePassable = 0; -#ifdef WITH_DEBUG - impassableTileRule = 3; -#endif + const bool isBottomTileObject = ( ( bottomTile._level >> 1 ) & 1 ) == 0; + + if ( bottomTile.objectTileset > 0 && bottomTile.objectIndex < 255 && isBottomTileObject ) { + const int bottomTileObjId = bottomTile.GetObject( false ); + const bool isBottomTileActionObject = MP2::isActionObject( bottomTileObjId ); + if ( isBottomTileActionObject ) { + if ( ( MP2::getActionObjectDirection( bottomTileObjId ) & Direction::TOP ) == 0 ) { + if ( isShortObject( bottomTileObjId ) ) { + tilePassable &= ~( Direction::BOTTOM | Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ); + } + else { + tilePassable = 0; + return; + } + } + } + else if ( bottomTile.mp2_object != 0 && bottomTile.mp2_object < 128 && MP2::isActionObject( bottomTile.mp2_object + 128 ) + && isShortObject( bottomTile.mp2_object + 128 ) && ( bottomTile.getOriginalPassability() & Direction::TOP ) == 0 ) { + // TODO: add extra logic to handle Stables. + tilePassable &= ~( Direction::BOTTOM | Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ); + } + else if ( isShortObject( bottomTile.mp2_object ) ) { + tilePassable &= ~( Direction::BOTTOM | Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ); + } + else { + tilePassable = 0; + return; + } + } } - - // town twba - if ( tilePassable && FindAddonICN( ICN::OBJNTWBA, 1 ) && ( mounts2 || trees2 ) ) { + else { tilePassable = 0; -#ifdef WITH_DEBUG - impassableTileRule = 5; -#endif - } - - 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 - } + return; } } - // 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 + // 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 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 + // 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; } } +} - // fix corners - if ( Maps::isValidDirection( _index, Direction::LEFT ) ) { - Tiles & left = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::LEFT ) ); +bool Maps::Tiles::doesObjectExist( const uint32_t uid ) const +{ + if ( uniq == uid && ( ( _level >> 1 ) & 1 ) == 0 ) { + return true; + } - // 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; + for ( const TilesAddon & addon : addons_level1 ) { + if ( addon.uniq == uid && ( ( addon.level >> 1 ) & 1 ) == 0 ) { + return true; } } + + return false; } uint32_t Maps::Tiles::GetRegion() const @@ -1309,10 +1002,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 || ( ( _level >> 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 @@ -1323,44 +1041,34 @@ 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 ); + addons_level1.emplace_front( _level, uniq, objectTileset, objectIndex ); } // Some original maps have issues with identifying tiles as roads. This code fixes it. It's not an ideal solution but works fine in most of cases. @@ -1375,12 +1083,12 @@ 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; objectIndex = highestPriorityAddon.index; - quantity1 = ( quantity1 & ( 0xFF - 0x03 ) ) + ( highestPriorityAddon.level & 0x03 ); + _level = highestPriorityAddon.level & 0x03; addons_level1.pop_back(); } @@ -1700,7 +1408,7 @@ void Maps::Tiles::RedrawTop4Hero( fheroes2::Image & dst, const fheroes2::Rect & if ( ( visibleTileROI & mp ) && !addons_level2.empty() ) { for ( Addons::const_iterator it = addons_level2.begin(); it != addons_level2.end(); ++it ) { - if ( skip_ground && MP2::isGroundObject( ( *it ).object ) ) + if ( skip_ground && MP2::isActionObject( ( *it ).object ) ) continue; const uint8_t object = ( *it ).object; @@ -1719,25 +1427,6 @@ void Maps::Tiles::RedrawTop4Hero( fheroes2::Image & dst, const fheroes2::Rect & } } -Maps::TilesAddon * Maps::Tiles::FindAddonICN( int icn, int level, int index ) -{ - if ( level == 1 || level == -1 ) { - for ( Addons::iterator it = addons_level1.begin(); it != addons_level1.end(); ++it ) { - if ( MP2::GetICNObject( it->object ) == icn && ( index == -1 || index == it->index ) ) { - return &( *it ); - } - } - } - if ( level == 2 || level == -1 ) { - for ( Addons::iterator it = addons_level2.begin(); it != addons_level2.end(); ++it ) { - if ( MP2::GetICNObject( it->object ) == icn && ( index == -1 || index == it->index ) ) { - return &( *it ); - } - } - } - return nullptr; -} - Maps::TilesAddon * Maps::Tiles::FindAddonLevel1( u32 uniq1 ) { Addons::iterator it = std::find_if( addons_level1.begin(), addons_level1.end(), [uniq1]( const TilesAddon & v ) { return v.isUniq( uniq1 ); } ); @@ -1756,13 +1445,14 @@ 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 - << "id : " << uniq << std::endl + os << "----------------:>>>>>>>>" << std::endl + << "Tile index : " << _index << ", " + << "point: (" << GetCenter().x << ", " << GetCenter().y << ")" << std::endl + << "uniq : " << uniq << std::endl << "mp2 object : " << GetObject() << ", (" << MP2::StringObject( GetObject() ) << ")" << std::endl << "tileset : " << static_cast( objectTileset ) << ", (" << ICN::GetString( MP2::GetICNObject( objectTileset ) ) << ")" << std::endl << "object index : " << static_cast( objectIndex ) << ", (animated: " << static_cast( hasSpriteAnimation() ) << ")" << std::endl + << "level : " << static_cast( _level ) << std::endl << "region : " << _region << std::endl << "ground : " << Ground::String( GetGround() ) << ", (isRoad: " << tileIsRoad << ")" << std::endl << "passable : " << ( tilePassable ? Direction::String( tilePassable ) : "false" ); @@ -1793,7 +1483,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: @@ -1804,7 +1493,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; @@ -1843,7 +1531,7 @@ std::string Maps::Tiles::String( void ) const } } - os << "----------------:--------" << std::endl; + os << "----------------:<<<<<<<<" << std::endl; return os.str(); } @@ -2740,12 +2428,16 @@ void Maps::Tiles::setAsEmpty() StreamBase & Maps::operator<<( StreamBase & msg, const TilesAddon & ta ) { - return msg << ta.level << ta.uniq << ta.object << ta.index << ta.tmp; + uint8_t temp = 0; + + return msg << ta.level << ta.uniq << ta.object << ta.index << temp; } StreamBase & Maps::operator>>( StreamBase & msg, TilesAddon & ta ) { - msg >> ta.level >> ta.uniq >> ta.object >> ta.index >> ta.tmp; + uint8_t temp = 0; + + msg >> ta.level >> ta.uniq >> ta.object >> ta.index >> temp; return msg; } diff --git a/src/fheroes2/maps/maps_tiles.h b/src/fheroes2/maps/maps_tiles.h index 1df445f4dfb..1472b61d773 100644 --- a/src/fheroes2/maps/maps_tiles.h +++ b/src/fheroes2/maps/maps_tiles.h @@ -82,30 +82,17 @@ 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; u8 index; - u8 tmp; }; struct Addons : public std::list @@ -169,8 +156,6 @@ namespace Maps bool isShadow( void ) const; bool GoodForUltimateArtifact() const; - TilesAddon * FindAddonICN( int icn1, int level = -1, int index = -1 ); - TilesAddon * FindAddonLevel1( u32 uniq1 ); TilesAddon * FindAddonLevel2( u32 uniq2 ); @@ -190,7 +175,18 @@ 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; + + bool doesObjectExist( const uint32_t uid ) const; // ICN::FLAGS32 version void CaptureFlags32( int obj, int col ); @@ -219,7 +215,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 +288,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 +300,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 ); @@ -351,9 +344,11 @@ namespace Maps bool tileIsRoad = false; - // This field does not persist in savegame + // These fields do not persist in savegame uint32_t _region = 0; + uint8_t _level = 0; + #ifdef WITH_DEBUG uint8_t impassableTileRule = 0; #endif diff --git a/src/fheroes2/maps/maps_tiles_quantity.cpp b/src/fheroes2/maps/maps_tiles_quantity.cpp index 92d2336d2ed..7b3d864c83c 100644 --- a/src/fheroes2/maps/maps_tiles_quantity.cpp +++ b/src/fheroes2/maps/maps_tiles_quantity.cpp @@ -425,6 +425,7 @@ Troop Maps::Tiles::QuantityTroop( void ) const void Maps::Tiles::QuantityReset( void ) { + // TODO: don't modify first 2 bits of quantity1. quantity1 = 0; quantity2 = 0; @@ -451,6 +452,7 @@ void Maps::Tiles::QuantityReset( void ) void Maps::Tiles::QuantityUpdate( bool isFirstLoad ) { + // TODO: don't modify first 2 bits of quantity1. switch ( GetObject( false ) ) { case MP2::OBJ_WITCHSHUT: QuantitySetSkill( Skill::Secondary::RandForWitchsHut() ); diff --git a/src/fheroes2/maps/mp2.cpp b/src/fheroes2/maps/mp2.cpp index eb8cb49d463..9206017c40e 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 ) { @@ -793,15 +795,16 @@ bool MP2::isBattleLife( int obj ) return false; } -bool MP2::isActionObject( int obj, bool water ) +bool MP2::isActionObject( const int obj, const bool locatesOnWater ) { - if ( water ) - return isWaterObject( obj ); + if ( locatesOnWater ) { + return isWaterActionObject( obj ); + } - return isGroundObject( obj ); + return isActionObject( obj ); } -bool MP2::isWaterObject( int obj ) +bool MP2::isWaterActionObject( const int obj ) { switch ( obj ) { case OBJ_WATERCHEST: @@ -814,34 +817,60 @@ bool MP2::isWaterObject( int obj ) case OBJ_FLOTSAM: case OBJ_MAGELLANMAPS: case OBJ_COAST: - case OBJ_MERMAID: case OBJ_SIRENS: case OBJ_BARRIER: - - // hack (bug: #3142729) case OBJ_MONSTER: case OBJ_ARTIFACT: case OBJ_RESOURCE: - return true; case OBJ_CASTLE: case OBJ_BOAT: return false; - default: break; } // price loyalty: editor allow place other objects - return Settings::Get().isPriceOfLoyaltySupported() ? isGroundObject( obj ) : false; + return Settings::Get().isPriceOfLoyaltySupported() ? isActionObject( obj ) : false; } -bool MP2::isGroundObject( int obj ) +bool MP2::isActionObject( const 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: + case OBJ_REEFS: + return false; + default: + break; + } + + return true; } bool MP2::isQuantityObject( int obj ) @@ -1073,28 +1102,29 @@ 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: + 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 +1199,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..53b98f03a72 100644 --- a/src/fheroes2/maps/mp2.h +++ b/src/fheroes2/maps/mp2.h @@ -563,9 +563,15 @@ namespace MP2 const char * StringObject( int object ); bool isHiddenForPuzzle( uint8_t tileset, uint8_t index ); - bool isActionObject( int obj, bool water ); - bool isGroundObject( int obj ); - bool isWaterObject( int obj ); + + // The method check whether the object is an action object depending on its location. For example, castle can't be located on water. + bool isActionObject( const int obj, const bool locatesOnWater ); + + // The method checks if the object is an action independent form its location. + bool isActionObject( const int obj ); + + bool isWaterActionObject( const int obj ); + bool isQuantityObject( int obj ); bool isCaptureObject( int obj ); bool isPickupObject( int obj ); @@ -578,14 +584,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() );