diff --git a/src/fheroes2/maps/maps_tiles.cpp b/src/fheroes2/maps/maps_tiles.cpp index abfdb9206e8..90d96fc6b5f 100644 --- a/src/fheroes2/maps/maps_tiles.cpp +++ b/src/fheroes2/maps/maps_tiles.cpp @@ -248,48 +248,6 @@ namespace } #endif - bool isShortObject( const MP2::MapObjectType objectType ) - { - // 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 ( objectType ) { - case MP2::OBJ_HALFLING_HOLE: - case MP2::OBJ_NON_ACTION_HALFLING_HOLE: - case MP2::OBJ_LEAN_TO: - case MP2::OBJ_WATER_LAKE: - case MP2::OBJ_TAR_PIT: - case MP2::OBJ_MERCENARY_CAMP: - case MP2::OBJ_NON_ACTION_MERCENARY_CAMP: - case MP2::OBJ_STANDING_STONES: - case MP2::OBJ_SHRINE_FIRST_CIRCLE: - case MP2::OBJ_SHRINE_SECOND_CIRCLE: - case MP2::OBJ_SHRINE_THIRD_CIRCLE: - case MP2::OBJ_MAGIC_GARDEN: - case MP2::OBJ_RUINS: - case MP2::OBJ_NON_ACTION_RUINS: - case MP2::OBJ_SIGN: - case MP2::OBJ_IDOL: - case MP2::OBJ_STONE_LITHS: - case MP2::OBJ_NON_ACTION_STONE_LITHS: - case MP2::OBJ_WAGON: - case MP2::OBJ_WAGON_CAMP: - case MP2::OBJ_NON_ACTION_WAGON_CAMP: - case MP2::OBJ_GOBLIN_HUT: - case MP2::OBJ_FAERIE_RING: - case MP2::OBJ_NON_ACTION_FAERIE_RING: - case MP2::OBJ_BARRIER: - case MP2::OBJ_MAGIC_WELL: - case MP2::OBJ_NOTHING_SPECIAL: - return true; - default: - break; - } - - return false; - } - bool isDetachedObjectType( const MP2::MapObjectType objectType ) { // Some objects do not take into account other objects below them. @@ -507,7 +465,7 @@ void Maps::Tiles::Init( int32_t index, const MP2::mp2tile_t & mp2 ) _isTileMarkedAsRoad = true; } - if ( mp2.mapObjectType == MP2::OBJ_NONE && ( layerType == Maps::ObjectLayerType::SHADOW_LAYER || layerType == Maps::ObjectLayerType::TERRAIN_LAYER ) ) { + if ( _mainObjectType == MP2::OBJ_NONE && ( layerType == Maps::ObjectLayerType::SHADOW_LAYER || layerType == Maps::ObjectLayerType::TERRAIN_LAYER ) ) { // If an object sits on shadow or terrain layer then we should put it as a bottom layer add-on. if ( bottomObjectIcnType != MP2::ObjectIcnType::OBJ_ICN_TYPE_UNKNOWN ) { _addonBottomLayer.emplace_back( layerType, mp2.level1ObjectUID, bottomObjectIcnType, mp2.bottomIcnImageIndex ); @@ -693,6 +651,17 @@ int Maps::Tiles::getBoatDirection() const int Maps::Tiles::getOriginalPassability() const { + // Run through all objects in this tile and calculate the passability based on all of them. + if ( isValidReefsSprite( _mainAddon._objectIcnType, _mainAddon._imageIndex ) ) { + return 0; + } + + for ( const TilesAddon & addon : _addonBottomLayer ) { + if ( isValidReefsSprite( addon._objectIcnType, addon._imageIndex ) ) { + return 0; + } + } + const MP2::MapObjectType objectType = GetObject( false ); if ( MP2::isActionObject( objectType ) ) { @@ -704,16 +673,6 @@ int Maps::Tiles::getOriginalPassability() const return DIRECTION_ALL; } - if ( isValidReefsSprite( _mainAddon._objectIcnType, _mainAddon._imageIndex ) ) { - return 0; - } - - for ( const TilesAddon & addon : _addonBottomLayer ) { - if ( isValidReefsSprite( addon._objectIcnType, addon._imageIndex ) ) { - return 0; - } - } - // Objects have fixed passability. return DIRECTION_CENTER_ROW | DIRECTION_BOTTOM_ROW; } @@ -790,9 +749,11 @@ void Maps::Tiles::updatePassability() const MP2::MapObjectType bottomTileObjectType = bottomTile.GetObject( false ); const MP2::MapObjectType correctedObjectType = MP2::getBaseActionObjectType( bottomTileObjectType ); + const bool isBottomObjectShort = isMainObjectShort( bottomTile ); + if ( MP2::isActionObject( bottomTileObjectType ) ) { if ( ( MP2::getActionObjectDirection( bottomTileObjectType ) & Direction::TOP ) == 0 ) { - if ( isShortObject( bottomTileObjectType ) ) { + if ( isBottomObjectShort ) { _tilePassabilityDirections &= ~Direction::BOTTOM; } else { @@ -802,10 +763,10 @@ void Maps::Tiles::updatePassability() } } else if ( bottomTile._mainObjectType != MP2::OBJ_NONE && correctedObjectType != bottomTileObjectType && MP2::isActionObject( correctedObjectType ) - && isShortObject( correctedObjectType ) && ( bottomTile.getOriginalPassability() & Direction::TOP ) == 0 ) { + && isBottomObjectShort && ( bottomTile.getOriginalPassability() & Direction::TOP ) == 0 ) { _tilePassabilityDirections &= ~Direction::BOTTOM; } - else if ( isShortObject( bottomTileObjectType ) + else if ( isBottomObjectShort || ( !bottomTile.containsAnyObjectIcnType( getValidObjectIcnTypes() ) && ( isCombinedObject( objectType ) || isCombinedObject( bottomTileObjectType ) ) ) ) { _tilePassabilityDirections &= ~Direction::BOTTOM; diff --git a/src/fheroes2/maps/maps_tiles.h b/src/fheroes2/maps/maps_tiles.h index dd8478a37aa..da1b0002bdf 100644 --- a/src/fheroes2/maps/maps_tiles.h +++ b/src/fheroes2/maps/maps_tiles.h @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2019 - 2023 * + * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * @@ -42,12 +42,17 @@ class StreamBase; namespace Maps { + // Layer types. They affect passability and also rendering. Rendering must be in the following order: + // - terrain objects (they have no shadows) + // - shadows + // - background objects + // - objects enum ObjectLayerType : uint8_t { - OBJECT_LAYER = 0, // main and action objects like mines, forest, mountains, castles and etc. - BACKGROUND_LAYER = 1, // background objects like lakes or bushes. - SHADOW_LAYER = 2, // shadows and some special objects like castle's entrance road. - TERRAIN_LAYER = 3 // roads, water flaws and cracks. Essentially everything what is a part of terrain. + OBJECT_LAYER = 0, // Common objects like mines, forest, mountains, castles and etc. They affect passability. + BACKGROUND_LAYER = 1, // Objects that still affect passability but they must be rendered as background. Such objects are lakes, bushes and etc. + SHADOW_LAYER = 2, // Shadows and some special objects like castle's entrance road. No passability changes. + TERRAIN_LAYER = 3 // Roads, water flaws and cracks. Essentially everything what is a part of terrain. No passability changes. }; struct TilesAddon diff --git a/src/fheroes2/maps/maps_tiles_helper.cpp b/src/fheroes2/maps/maps_tiles_helper.cpp index e0126a652d0..c20185e9dd7 100644 --- a/src/fheroes2/maps/maps_tiles_helper.cpp +++ b/src/fheroes2/maps/maps_tiles_helper.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -3120,6 +3121,65 @@ namespace Maps } } + bool doesTileHaveObjectUID( const Tiles & tile, const uint32_t uid ) + { + if ( tile.GetObjectUID() == uid ) { + return true; + } + + for ( const TilesAddon & addon : tile.getBottomLayerAddons() ) { + if ( addon._uid == uid ) { + return true; + } + } + + for ( const TilesAddon & addon : tile.getTopLayerAddons() ) { + if ( addon._uid == uid ) { + return true; + } + } + + return false; + } + + bool isMainObjectShort( const Tiles & tile ) + { + const uint32_t uid = tile.GetObjectUID(); + const int32_t startIndex = tile.GetIndex(); + + const int32_t startY = startIndex / world.w(); + + std::deque indexToTraverse{ startIndex }; + std::set traversedIndex; + + const Directions & directions = Direction::All(); + + while ( !indexToTraverse.empty() ) { + traversedIndex.emplace( indexToTraverse.front() ); + + for ( const int direction : directions ) { + if ( !isValidDirection( indexToTraverse.front(), direction ) ) { + continue; + } + + const int32_t newIndex = Maps::GetDirectionIndex( indexToTraverse.front(), direction ); + const Tiles & newTile = world.GetTiles( newIndex ); + + if ( doesTileHaveObjectUID( newTile, uid ) && traversedIndex.count( newIndex ) == 0 ) { + if ( ( newIndex / world.w() ) != startY ) { + return false; + } + + indexToTraverse.emplace_back( newIndex ); + } + } + + indexToTraverse.pop_front(); + } + + return true; + } + bool removeObjectTypeFromTile( Tiles & tile, const MP2::ObjectIcnType objectIcnType ) { if ( tile.getObjectIdByObjectIcnType( objectIcnType ) == 0 ) { diff --git a/src/fheroes2/maps/maps_tiles_helper.h b/src/fheroes2/maps/maps_tiles_helper.h index 02920426dc1..9ac5b27f51c 100644 --- a/src/fheroes2/maps/maps_tiles_helper.h +++ b/src/fheroes2/maps/maps_tiles_helper.h @@ -176,6 +176,11 @@ namespace Maps // Determine the fog direction in the area between min and max positions for given player(s) color code and store it in corresponding tile data. void updateFogDirectionsInArea( const fheroes2::Point & minPos, const fheroes2::Point & maxPos, const int32_t color ); + bool doesTileHaveObjectUID( const Tiles & tile, const uint32_t uid ); + + // An object is considered as short if its height is no more than 1 tile. + bool isMainObjectShort( const Tiles & tile ); + // The functions below are used only in the map Editor. void setTerrainOnTiles( const int32_t startTileId, const int32_t endTileId, const int groundId );