2022-08-09 07:54:32 +02:00
/*
* RmgObject . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "RmgObject.h"
# include "RmgMap.h"
# include "../mapping/CMapEditManager.h"
# include "../mapping/CMap.h"
# include "../VCMI_Lib.h"
2023-06-02 20:47:37 +02:00
# include "../mapObjectConstructors/AObjectTypeHandler.h"
# include "../mapObjectConstructors/CObjectClassesHandler.h"
# include "../mapObjects/ObjectTemplate.h"
2024-01-09 16:43:36 +02:00
# include "../mapObjects/CGObjectInstance.h"
2022-08-09 07:54:32 +02:00
# include "Functions.h"
2023-01-09 01:17:37 +02:00
# include "../TerrainHandler.h"
2022-08-09 07:54:32 +02:00
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_BEGIN
2022-08-09 07:54:32 +02:00
using namespace rmg ;
Object : : Instance : : Instance ( const Object & parent , CGObjectInstance & object ) : dParent ( parent ) , dObject ( object )
{
setPosition ( dPosition ) ;
}
Object : : Instance : : Instance ( const Object & parent , CGObjectInstance & object , const int3 & position ) : Instance ( parent , object )
{
setPosition ( position ) ;
}
const Area & Object : : Instance : : getBlockedArea ( ) const
{
if ( dBlockedAreaCache . empty ( ) )
{
2023-12-18 14:52:03 +02:00
std : : set < int3 > blockedArea = dObject . getBlockedPos ( ) ;
dBlockedAreaCache . assign ( rmg : : Tileset ( blockedArea . begin ( ) , blockedArea . end ( ) ) ) ;
2022-08-09 07:54:32 +02:00
if ( dObject . isVisitable ( ) | | dBlockedAreaCache . empty ( ) )
2023-12-18 18:18:13 +02:00
dBlockedAreaCache . add ( dObject . visitablePos ( ) ) ;
2022-08-09 07:54:32 +02:00
}
return dBlockedAreaCache ;
}
2023-04-10 19:26:53 +02:00
int3 Object : : Instance : : getTopTile ( ) const
{
return object ( ) . getTopVisiblePos ( ) ;
}
2022-08-09 07:54:32 +02:00
int3 Object : : Instance : : getPosition ( bool isAbsolute ) const
{
if ( isAbsolute )
return dPosition + dParent . getPosition ( ) ;
else
return dPosition ;
}
int3 Object : : Instance : : getVisitablePosition ( ) const
{
return dObject . visitablePos ( ) ;
}
const rmg : : Area & Object : : Instance : : getAccessibleArea ( ) const
{
if ( dAccessibleAreaCache . empty ( ) )
{
auto neighbours = rmg : : Area ( { getVisitablePosition ( ) } ) . getBorderOutside ( ) ;
2023-12-21 10:58:39 +02:00
// FIXME: Blocked area of removable object is also accessible area for neighbors
2022-08-09 07:54:32 +02:00
rmg : : Area visitable = rmg : : Area ( neighbours ) - getBlockedArea ( ) ;
2023-12-18 14:52:03 +02:00
// TODO: Add in one operation to avoid multiple invalidation
for ( const auto & from : visitable . getTilesVector ( ) )
2022-08-09 07:54:32 +02:00
{
if ( isVisitableFrom ( from ) )
dAccessibleAreaCache . add ( from ) ;
}
}
return dAccessibleAreaCache ;
}
void Object : : Instance : : setPosition ( const int3 & position )
{
dPosition = position ;
dObject . pos = dPosition + dParent . getPosition ( ) ;
dBlockedAreaCache . clear ( ) ;
dAccessibleAreaCache . clear ( ) ;
2023-12-06 10:49:41 +02:00
dParent . clearCachedArea ( ) ;
2022-08-09 07:54:32 +02:00
}
void Object : : Instance : : setPositionRaw ( const int3 & position )
{
if ( ! dObject . pos . valid ( ) )
{
dObject . pos = dPosition + dParent . getPosition ( ) ;
dBlockedAreaCache . clear ( ) ;
dAccessibleAreaCache . clear ( ) ;
2023-12-06 10:49:41 +02:00
dParent . clearCachedArea ( ) ;
2022-08-09 07:54:32 +02:00
}
auto shift = position + dParent . getPosition ( ) - dObject . pos ;
dAccessibleAreaCache . translate ( shift ) ;
dBlockedAreaCache . translate ( shift ) ;
dPosition = position ;
dObject . pos = dPosition + dParent . getPosition ( ) ;
}
2023-09-30 01:19:18 +02:00
void Object : : Instance : : setAnyTemplate ( CRandomGenerator & rng )
2022-08-09 07:54:32 +02:00
{
2023-10-24 16:11:25 +02:00
auto templates = dObject . getObjectHandler ( ) - > getTemplates ( ) ;
2022-09-29 19:07:56 +02:00
if ( templates . empty ( ) )
2023-10-28 11:27:10 +02:00
throw rmgException ( boost : : str ( boost : : format ( " Did not find any graphics for object (%d,%d) " ) % dObject.ID % dObject.getObjTypeIndex())) ;
2022-09-29 19:07:56 +02:00
2023-09-30 01:19:18 +02:00
dObject . appearance = * RandomGeneratorUtil : : nextItem ( templates , rng ) ;
2022-09-29 19:07:56 +02:00
dAccessibleAreaCache . clear ( ) ;
setPosition ( getPosition ( false ) ) ;
}
2023-09-30 01:19:18 +02:00
void Object : : Instance : : setTemplate ( TerrainId terrain , CRandomGenerator & rng )
2022-09-29 19:07:56 +02:00
{
2023-12-21 10:58:39 +02:00
auto templates = dObject . getObjectHandler ( ) - > getMostSpecificTemplates ( terrain ) ;
2022-09-29 19:07:56 +02:00
if ( templates . empty ( ) )
2022-08-09 07:54:32 +02:00
{
2023-01-01 22:42:28 +02:00
auto terrainName = VLC - > terrainTypeHandler - > getById ( terrain ) - > getNameTranslated ( ) ;
2023-10-28 11:27:10 +02:00
throw rmgException ( boost : : str ( boost : : format ( " Did not find graphics for object (%d,%d) at % s " ) % dObject.ID % dObject.getObjTypeIndex() % terrainName)) ;
2022-08-09 07:54:32 +02:00
}
2023-09-30 01:19:18 +02:00
dObject . appearance = * RandomGeneratorUtil : : nextItem ( templates , rng ) ;
2022-08-09 07:54:32 +02:00
dAccessibleAreaCache . clear ( ) ;
setPosition ( getPosition ( false ) ) ;
}
void Object : : Instance : : clear ( )
{
2024-01-19 17:26:51 +02:00
if ( onCleared )
onCleared ( & dObject ) ;
2022-08-09 07:54:32 +02:00
delete & dObject ;
dBlockedAreaCache . clear ( ) ;
dAccessibleAreaCache . clear ( ) ;
2023-12-06 10:49:41 +02:00
dParent . clearCachedArea ( ) ;
2022-08-09 07:54:32 +02:00
}
bool Object : : Instance : : isVisitableFrom ( const int3 & position ) const
{
auto relPosition = position - getPosition ( true ) ;
2022-09-11 15:12:35 +02:00
return dObject . appearance - > isVisitableFrom ( relPosition . x , relPosition . y ) ;
2022-08-09 07:54:32 +02:00
}
2023-12-06 10:49:41 +02:00
bool Object : : Instance : : isBlockedVisitable ( ) const
{
return dObject . isBlockedVisitable ( ) ;
}
2023-12-06 21:49:28 +02:00
bool Object : : Instance : : isRemovable ( ) const
{
return dObject . isRemovable ( ) ;
}
2022-08-09 07:54:32 +02:00
CGObjectInstance & Object : : Instance : : object ( )
{
return dObject ;
}
const CGObjectInstance & Object : : Instance : : object ( ) const
{
return dObject ;
}
2023-06-08 19:51:21 +02:00
Object : : Object ( CGObjectInstance & object , const int3 & position ) :
guarded ( false )
2022-08-09 07:54:32 +02:00
{
addInstance ( object , position ) ;
}
2023-06-08 19:51:21 +02:00
Object : : Object ( CGObjectInstance & object ) :
guarded ( false )
2022-08-09 07:54:32 +02:00
{
addInstance ( object ) ;
}
2023-06-08 19:51:21 +02:00
Object : : Object ( const Object & object ) :
guarded ( false )
2022-08-09 07:54:32 +02:00
{
2023-02-11 18:05:02 +02:00
for ( const auto & i : object . dInstances )
2022-08-09 07:54:32 +02:00
addInstance ( const_cast < CGObjectInstance & > ( i . object ( ) ) , i . getPosition ( ) ) ;
setPosition ( object . getPosition ( ) ) ;
}
2023-12-13 23:20:23 +02:00
std : : list < Object : : Instance * > & Object : : instances ( )
2022-08-09 07:54:32 +02:00
{
2023-12-13 23:20:23 +02:00
if ( cachedInstanceList . empty ( ) )
{
for ( auto & i : dInstances )
cachedInstanceList . push_back ( & i ) ;
}
return cachedInstanceList ;
2022-08-09 07:54:32 +02:00
}
2023-12-13 23:20:23 +02:00
std : : list < const Object : : Instance * > & Object : : instances ( ) const
2022-08-09 07:54:32 +02:00
{
2023-12-13 23:20:23 +02:00
if ( cachedInstanceConstList . empty ( ) )
{
for ( const auto & i : dInstances )
cachedInstanceConstList . push_back ( & i ) ;
}
return cachedInstanceConstList ;
2022-08-09 07:54:32 +02:00
}
void Object : : addInstance ( Instance & object )
{
//assert(object.dParent == *this);
2023-06-08 19:51:21 +02:00
setGuardedIfMonster ( object ) ;
2022-08-09 07:54:32 +02:00
dInstances . push_back ( object ) ;
2023-12-13 23:20:23 +02:00
cachedInstanceList . push_back ( & object ) ;
cachedInstanceConstList . push_back ( & object ) ;
2023-06-08 19:51:21 +02:00
2023-12-06 10:49:41 +02:00
clearCachedArea ( ) ;
2023-12-13 23:20:23 +02:00
visibleTopOffset . reset ( ) ;
2022-08-09 07:54:32 +02:00
}
Object : : Instance & Object : : addInstance ( CGObjectInstance & object )
{
dInstances . emplace_back ( * this , object ) ;
2023-06-08 19:51:21 +02:00
setGuardedIfMonster ( dInstances . back ( ) ) ;
2023-12-13 23:20:23 +02:00
cachedInstanceList . push_back ( & dInstances . back ( ) ) ;
cachedInstanceConstList . push_back ( & dInstances . back ( ) ) ;
2023-06-08 19:51:21 +02:00
2023-12-06 10:49:41 +02:00
clearCachedArea ( ) ;
2023-12-13 23:20:23 +02:00
visibleTopOffset . reset ( ) ;
2022-08-09 07:54:32 +02:00
return dInstances . back ( ) ;
}
Object : : Instance & Object : : addInstance ( CGObjectInstance & object , const int3 & position )
{
dInstances . emplace_back ( * this , object , position ) ;
2023-06-08 19:51:21 +02:00
setGuardedIfMonster ( dInstances . back ( ) ) ;
2023-12-13 23:20:23 +02:00
cachedInstanceList . push_back ( & dInstances . back ( ) ) ;
cachedInstanceConstList . push_back ( & dInstances . back ( ) ) ;
2023-06-08 19:51:21 +02:00
2023-12-06 10:49:41 +02:00
clearCachedArea ( ) ;
2023-12-13 23:20:23 +02:00
visibleTopOffset . reset ( ) ;
2022-08-09 07:54:32 +02:00
return dInstances . back ( ) ;
}
const int3 & Object : : getPosition ( ) const
{
return dPosition ;
}
int3 Object : : getVisitablePosition ( ) const
{
assert ( ! dInstances . empty ( ) ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & instance : dInstances )
2022-08-09 07:54:32 +02:00
if ( ! getArea ( ) . contains ( instance . getVisitablePosition ( ) ) )
return instance . getVisitablePosition ( ) ;
return dInstances . back ( ) . getVisitablePosition ( ) ; //fallback - return position of last object
}
const rmg : : Area & Object : : getAccessibleArea ( bool exceptLast ) const
{
if ( dInstances . empty ( ) )
return dAccessibleAreaFullCache ;
if ( exceptLast & & ! dAccessibleAreaCache . empty ( ) )
return dAccessibleAreaCache ;
if ( ! exceptLast & & ! dAccessibleAreaFullCache . empty ( ) )
return dAccessibleAreaFullCache ;
2023-12-18 14:52:03 +02:00
2023-12-13 23:20:23 +02:00
// FIXME: This clears tiles for every consecutive object
2022-08-09 07:54:32 +02:00
for ( auto i = dInstances . begin ( ) ; i ! = std : : prev ( dInstances . end ( ) ) ; + + i )
dAccessibleAreaCache . unite ( i - > getAccessibleArea ( ) ) ;
2023-12-18 14:52:03 +02:00
2022-08-09 07:54:32 +02:00
dAccessibleAreaFullCache = dAccessibleAreaCache ;
dAccessibleAreaFullCache . unite ( dInstances . back ( ) . getAccessibleArea ( ) ) ;
dAccessibleAreaCache . subtract ( getArea ( ) ) ;
dAccessibleAreaFullCache . subtract ( getArea ( ) ) ;
2023-12-18 14:52:03 +02:00
2022-08-09 07:54:32 +02:00
if ( exceptLast )
return dAccessibleAreaCache ;
else
return dAccessibleAreaFullCache ;
}
2023-12-06 10:49:41 +02:00
const rmg : : Area & Object : : getBlockVisitableArea ( ) const
{
2023-12-13 23:20:23 +02:00
if ( dBlockVisitableCache . empty ( ) )
2023-12-06 10:49:41 +02:00
{
2023-12-13 23:20:23 +02:00
for ( const auto & i : dInstances )
{
// FIXME: Account for blockvis objects with multiple visitable tiles
if ( i . isBlockedVisitable ( ) )
dBlockVisitableCache . add ( i . getVisitablePosition ( ) ) ;
}
2023-12-06 10:49:41 +02:00
}
return dBlockVisitableCache ;
}
2023-12-06 21:49:28 +02:00
const rmg : : Area & Object : : getRemovableArea ( ) const
{
2023-12-19 10:24:01 +02:00
if ( dRemovableAreaCache . empty ( ) )
2023-12-06 21:49:28 +02:00
{
2023-12-13 23:20:23 +02:00
for ( const auto & i : dInstances )
{
if ( i . isRemovable ( ) )
dRemovableAreaCache . unite ( i . getBlockedArea ( ) ) ;
}
2023-12-06 21:49:28 +02:00
}
return dRemovableAreaCache ;
}
2023-12-21 13:29:45 +02:00
const rmg : : Area & Object : : getVisitableArea ( ) const
{
if ( dVisitableCache . empty ( ) )
{
for ( const auto & i : dInstances )
{
// FIXME: Account for bjects with multiple visitable tiles
dVisitableCache . add ( i . getVisitablePosition ( ) ) ;
}
}
return dVisitableCache ;
}
2023-12-06 21:49:28 +02:00
const rmg : : Area Object : : getEntrableArea ( ) const
{
// Calculate Area that hero can freely pass
// Do not use blockVisitTiles, unless they belong to removable objects (resources etc.)
// area = accessibleArea - (blockVisitableArea - removableArea)
2023-12-21 13:29:45 +02:00
// FIXME: What does it do? AccessibleArea means area AROUND the object
rmg : : Area entrableArea = getVisitableArea ( ) ;
2023-12-06 21:49:28 +02:00
rmg : : Area blockVisitableArea = getBlockVisitableArea ( ) ;
blockVisitableArea . subtract ( getRemovableArea ( ) ) ;
entrableArea . subtract ( blockVisitableArea ) ;
return entrableArea ;
}
2022-08-09 07:54:32 +02:00
void Object : : setPosition ( const int3 & position )
{
2023-12-21 13:29:45 +02:00
auto shift = position - dPosition ;
dAccessibleAreaCache . translate ( shift ) ;
dAccessibleAreaFullCache . translate ( shift ) ;
dBlockVisitableCache . translate ( shift ) ;
dVisitableCache . translate ( shift ) ;
dRemovableAreaCache . translate ( shift ) ;
dFullAreaCache . translate ( shift ) ;
2022-08-09 07:54:32 +02:00
dPosition = position ;
for ( auto & i : dInstances )
i . setPositionRaw ( i . getPosition ( ) ) ;
}
2023-09-30 01:19:18 +02:00
void Object : : setTemplate ( const TerrainId & terrain , CRandomGenerator & rng )
2022-08-09 07:54:32 +02:00
{
for ( auto & i : dInstances )
2023-09-30 01:19:18 +02:00
i . setTemplate ( terrain , rng ) ;
2023-12-13 23:20:23 +02:00
visibleTopOffset . reset ( ) ;
2022-08-09 07:54:32 +02:00
}
const Area & Object : : getArea ( ) const
{
if ( ! dFullAreaCache . empty ( ) | | dInstances . empty ( ) )
return dFullAreaCache ;
for ( const auto & instance : dInstances )
{
dFullAreaCache . unite ( instance . getBlockedArea ( ) ) ;
}
return dFullAreaCache ;
}
2023-04-10 19:26:53 +02:00
const int3 Object : : getVisibleTop ( ) const
{
2023-12-13 23:20:23 +02:00
if ( visibleTopOffset )
{
return dPosition + visibleTopOffset . value ( ) ;
}
else
2023-04-10 19:26:53 +02:00
{
2023-12-13 23:20:23 +02:00
int3 topTile ( - 1 , 10000 , - 1 ) ; //Start at the bottom
for ( const auto & i : dInstances )
2023-04-10 19:26:53 +02:00
{
2023-12-13 23:20:23 +02:00
if ( i . getTopTile ( ) . y < topTile . y )
{
topTile = i . getTopTile ( ) ;
}
2023-04-10 19:26:53 +02:00
}
2023-12-13 23:20:23 +02:00
visibleTopOffset = topTile - dPosition ;
return topTile ;
2023-04-10 19:26:53 +02:00
}
}
2023-06-08 19:51:21 +02:00
bool rmg : : Object : : isGuarded ( ) const
{
return guarded ;
}
void rmg : : Object : : setGuardedIfMonster ( const Instance & object )
{
if ( object . object ( ) . ID = = Obj : : MONSTER )
{
guarded = true ;
}
}
2023-09-30 01:19:18 +02:00
void Object : : Instance : : finalize ( RmgMap & map , CRandomGenerator & rng )
2022-08-09 07:54:32 +02:00
{
if ( ! map . isOnMap ( getPosition ( true ) ) )
2023-08-20 22:45:41 +02:00
throw rmgException ( boost : : str ( boost : : format ( " Position of object %d at %s is outside the map " ) % dObject . id % getPosition ( true ) . toString ( ) ) ) ;
2022-08-09 07:54:32 +02:00
2022-09-29 19:07:56 +02:00
//If no specific template was defined for this object, select any matching
if ( ! dObject . appearance )
{
2023-05-19 20:30:15 +02:00
const auto * terrainType = map . getTile ( getPosition ( true ) ) . terType ;
2023-10-24 16:11:25 +02:00
auto templates = dObject . getObjectHandler ( ) - > getTemplates ( terrainType - > getId ( ) ) ;
2022-09-29 19:07:56 +02:00
if ( templates . empty ( ) )
{
2023-10-28 11:27:10 +02:00
throw rmgException ( boost : : str ( boost : : format ( " Did not find graphics for object (%d,%d) at % s ( terrain % d ) " ) % dObject.ID % dObject.getObjTypeIndex() % getPosition(true).toString() % terrainType)) ;
2022-09-29 19:07:56 +02:00
}
else
{
2023-09-30 01:19:18 +02:00
setTemplate ( terrainType - > getId ( ) , rng ) ;
2022-09-29 19:07:56 +02:00
}
}
2022-08-09 07:54:32 +02:00
if ( dObject . isVisitable ( ) & & ! map . isOnMap ( dObject . visitablePos ( ) ) )
2023-08-20 22:45:41 +02:00
throw rmgException ( boost : : str ( boost : : format ( " Visitable tile %s of object %d at %s is outside the map " ) % dObject . visitablePos ( ) . toString ( ) % dObject . id % dObject . pos . toString ( ) ) ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & tile : dObject . getBlockedPos ( ) )
2022-08-09 07:54:32 +02:00
{
if ( ! map . isOnMap ( tile ) )
2023-08-20 22:45:41 +02:00
throw rmgException ( boost : : str ( boost : : format ( " Tile %s of object %d at %s is outside the map " ) % tile . toString ( ) % dObject . id % dObject . pos . toString ( ) ) ) ;
2022-08-09 07:54:32 +02:00
}
2023-02-11 18:05:02 +02:00
for ( const auto & tile : getBlockedArea ( ) . getTilesVector ( ) )
2022-08-09 07:54:32 +02:00
{
2023-08-19 21:35:44 +02:00
map . setOccupied ( tile , ETileType : : USED ) ;
2022-08-09 07:54:32 +02:00
}
2023-05-19 20:30:15 +02:00
map . getMapProxy ( ) - > insertObject ( & dObject ) ;
2022-08-09 07:54:32 +02:00
}
2023-09-30 01:19:18 +02:00
void Object : : finalize ( RmgMap & map , CRandomGenerator & rng )
2022-08-09 07:54:32 +02:00
{
if ( dInstances . empty ( ) )
throw rmgException ( " Cannot finalize object without instances " ) ;
2023-02-11 18:05:02 +02:00
for ( auto & dInstance : dInstances )
2022-08-09 07:54:32 +02:00
{
2023-09-30 01:19:18 +02:00
dInstance . finalize ( map , rng ) ;
2022-08-09 07:54:32 +02:00
}
}
2023-12-06 10:49:41 +02:00
void Object : : clearCachedArea ( ) const
{
dFullAreaCache . clear ( ) ;
dAccessibleAreaCache . clear ( ) ;
dAccessibleAreaFullCache . clear ( ) ;
dBlockVisitableCache . clear ( ) ;
2023-12-21 13:29:45 +02:00
dVisitableCache . clear ( ) ;
2023-12-06 21:49:28 +02:00
dRemovableAreaCache . clear ( ) ;
2023-12-06 10:49:41 +02:00
}
2022-08-09 07:54:32 +02:00
void Object : : clear ( )
{
for ( auto & instance : dInstances )
instance . clear ( ) ;
dInstances . clear ( ) ;
2023-12-13 23:20:23 +02:00
cachedInstanceList . clear ( ) ;
cachedInstanceConstList . clear ( ) ;
visibleTopOffset . reset ( ) ;
2023-12-06 10:49:41 +02:00
clearCachedArea ( ) ;
2022-08-09 07:54:32 +02:00
}
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_END