Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix invalid calculation of "short" object passability #8429

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 17 additions & 56 deletions src/fheroes2/maps/maps_tiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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 ) ) {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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 ) {
ihhub marked this conversation as resolved.
Show resolved Hide resolved
_tilePassabilityDirections &= ~Direction::BOTTOM;
}
else if ( isShortObject( bottomTileObjectType )
else if ( isBottomObjectShort
|| ( !bottomTile.containsAnyObjectIcnType( getValidObjectIcnTypes() )
&& ( isCombinedObject( objectType ) || isCombinedObject( bottomTileObjectType ) ) ) ) {
_tilePassabilityDirections &= ~Direction::BOTTOM;
Expand Down
15 changes: 10 additions & 5 deletions src/fheroes2/maps/maps_tiles.h
Original file line number Diff line number Diff line change
@@ -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 <fheroes2@gmail.com> *
Expand Down Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions src/fheroes2/maps/maps_tiles_helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <deque>
#include <initializer_list>
#include <limits>
#include <list>
Expand Down Expand Up @@ -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() ) {
ihhub marked this conversation as resolved.
Show resolved Hide resolved
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<int32_t> indexToTraverse{ startIndex };
std::set<int32_t> 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 ) {
Expand Down
5 changes: 5 additions & 0 deletions src/fheroes2/maps/maps_tiles_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
Loading