2022-08-09 07:54:32 +02:00
/*
* ObjectManager . 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 "ObjectManager.h"
2023-05-20 10:17:37 +02:00
# include "../CMapGenerator.h"
# include "../TileInfo.h"
# include "../RmgMap.h"
2022-08-09 07:54:32 +02:00
# include "RoadPlacer.h"
# include "RiverPlacer.h"
# include "WaterAdopter.h"
2023-05-19 20:30:15 +02:00
# include "ConnectionsPlacer.h"
# include "TownPlacer.h"
# include "MinePlacer.h"
2023-04-23 10:08:16 +02:00
# include "QuestArtifactPlacer.h"
2023-05-20 10:17:37 +02:00
# include "../../CCreatureHandler.h"
2023-06-02 20:47:37 +02:00
# include "../../mapObjectConstructors/AObjectTypeHandler.h"
# include "../../mapObjectConstructors/CObjectClassesHandler.h"
2023-06-17 14:21:42 +02:00
# include "../../mapObjects/CGCreature.h"
2023-05-20 10:17:37 +02:00
# include "../../mapping/CMap.h"
# include "../../mapping/CMapEditManager.h"
# include "../Functions.h"
# include "../RmgObject.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
void ObjectManager : : process ( )
{
zone . fractalize ( ) ;
2022-08-30 23:03:35 +02:00
createRequiredObjects ( ) ;
2022-08-09 07:54:32 +02:00
}
void ObjectManager : : init ( )
{
DEPENDENCY ( WaterAdopter ) ;
2023-05-19 20:30:15 +02:00
DEPENDENCY_ALL ( ConnectionsPlacer ) ; //Monoliths can be placed by other zone, too
DEPENDENCY ( TownPlacer ) ; //Only secondary towns
DEPENDENCY ( MinePlacer ) ;
2022-08-09 07:54:32 +02:00
POSTFUNCTION ( RoadPlacer ) ;
2022-08-20 12:17:27 +02:00
createDistancesPriorityQueue ( ) ;
}
void ObjectManager : : createDistancesPriorityQueue ( )
{
2023-05-19 20:30:15 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2022-08-20 12:17:27 +02:00
tilesByDistance . clear ( ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & tile : zone . areaPossible ( ) . getTilesVector ( ) )
2022-08-20 12:17:27 +02:00
{
tilesByDistance . push ( std : : make_pair ( tile , map . getNearestObjectDistance ( tile ) ) ) ;
}
2022-08-09 07:54:32 +02:00
}
2023-07-06 22:15:00 +02:00
void ObjectManager : : addRequiredObject ( const RequiredObjectInfo & info )
2022-08-09 07:54:32 +02:00
{
2023-05-19 20:30:15 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2023-07-06 22:15:00 +02:00
requiredObjects . emplace_back ( info ) ;
2022-08-09 07:54:32 +02:00
}
2023-07-06 22:15:00 +02:00
void ObjectManager : : addCloseObject ( const RequiredObjectInfo & info )
2022-08-09 07:54:32 +02:00
{
2023-05-19 20:30:15 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2023-07-06 22:15:00 +02:00
closeObjects . emplace_back ( info ) ;
2022-08-09 07:54:32 +02:00
}
2023-07-06 22:15:00 +02:00
void ObjectManager : : addNearbyObject ( const RequiredObjectInfo & info )
2022-08-09 07:54:32 +02:00
{
2023-05-19 20:30:15 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2023-07-06 22:15:00 +02:00
nearbyObjects . emplace_back ( info ) ;
2022-08-09 07:54:32 +02:00
}
void ObjectManager : : updateDistances ( const rmg : : Object & obj )
{
2023-06-25 18:31:54 +02:00
updateDistances ( [ obj ] ( const int3 & tile ) - > ui32
2022-08-09 07:54:32 +02:00
{
2023-06-25 18:31:54 +02:00
return obj . getArea ( ) . distanceSqr ( tile ) ; //optimization, only relative distance is interesting
} ) ;
2022-08-09 07:54:32 +02:00
}
2023-06-18 19:20:12 +02:00
void ObjectManager : : updateDistances ( const int3 & pos )
2023-06-25 18:31:54 +02:00
{
updateDistances ( [ pos ] ( const int3 & tile ) - > ui32
{
return pos . dist2dSQ ( tile ) ; //optimization, only relative distance is interesting
} ) ;
}
void ObjectManager : : updateDistances ( std : : function < ui32 ( const int3 & tile ) > distanceFunction )
2022-08-09 07:54:32 +02:00
{
2023-05-19 20:30:15 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2022-08-20 12:17:27 +02:00
tilesByDistance . clear ( ) ;
2023-12-18 14:52:03 +02:00
for ( const auto & tile : zone . areaPossible ( ) . getTilesVector ( ) ) //don't need to mark distance for not possible tiles
2022-08-09 07:54:32 +02:00
{
2023-06-25 18:31:54 +02:00
ui32 d = distanceFunction ( tile ) ;
2023-02-11 18:05:02 +02:00
map . setNearestObjectDistance ( tile , std : : min ( static_cast < float > ( d ) , map . getNearestObjectDistance ( tile ) ) ) ;
2022-08-20 12:17:27 +02:00
tilesByDistance . push ( std : : make_pair ( tile , map . getNearestObjectDistance ( tile ) ) ) ;
2022-08-09 07:54:32 +02:00
}
}
const rmg : : Area & ObjectManager : : getVisitableArea ( ) const
{
2023-05-19 20:30:15 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2022-08-09 07:54:32 +02:00
return objectsVisitableArea ;
}
2022-09-04 08:54:06 +02:00
std : : vector < CGObjectInstance * > ObjectManager : : getMines ( ) const
{
std : : vector < CGObjectInstance * > mines ;
2023-02-11 18:05:02 +02:00
2023-05-19 20:30:15 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2023-02-11 18:05:02 +02:00
for ( auto * object : objects )
2022-09-04 08:54:06 +02:00
{
if ( object - > ID = = Obj : : MINE )
{
mines . push_back ( object ) ;
}
}
return mines ;
}
2023-02-11 18:05:02 +02:00
int3 ObjectManager : : findPlaceForObject ( const rmg : : Area & searchArea , rmg : : Object & obj , const std : : function < float ( const int3 ) > & weightFunction , OptimizeType optimizer ) const
2022-08-09 07:54:32 +02:00
{
float bestWeight = 0.f ;
int3 result ( - 1 , - 1 , - 1 ) ;
2023-08-11 07:45:24 +02:00
//Blocked area might not cover object position if it has an offset from (0,0)
auto outsideTheMap = [ this , & obj ] ( ) - > bool
{
for ( const auto & oi : obj . instances ( ) )
{
if ( ! map . isOnMap ( oi - > getPosition ( true ) ) )
{
return true ;
}
}
return false ;
} ;
2022-08-09 07:54:32 +02:00
2022-08-20 12:17:27 +02:00
if ( optimizer & OptimizeType : : DISTANCE )
2022-08-09 07:54:32 +02:00
{
2022-08-20 12:17:27 +02:00
auto open = tilesByDistance ;
while ( ! open . empty ( ) )
2022-08-09 07:54:32 +02:00
{
2022-08-20 12:17:27 +02:00
auto node = open . top ( ) ;
open . pop ( ) ;
int3 tile = node . first ;
if ( ! searchArea . contains ( tile ) )
continue ;
obj . setPosition ( tile ) ;
2023-04-10 19:26:53 +02:00
if ( obj . getVisibleTop ( ) . y < 0 )
continue ;
2022-08-20 12:17:27 +02:00
if ( ! searchArea . contains ( obj . getArea ( ) ) | | ! searchArea . overlap ( obj . getAccessibleArea ( ) ) )
continue ;
2023-08-11 07:45:24 +02:00
if ( outsideTheMap ( ) )
continue ;
2022-08-20 12:17:27 +02:00
float weight = weightFunction ( tile ) ;
if ( weight > bestWeight )
{
bestWeight = weight ;
result = tile ;
if ( ! ( optimizer & OptimizeType : : WEIGHT ) )
break ;
}
2022-08-09 07:54:32 +02:00
}
}
2022-08-20 12:17:27 +02:00
else
{
2023-12-18 14:52:03 +02:00
for ( const auto & tile : searchArea . getTilesVector ( ) )
2022-08-20 12:17:27 +02:00
{
obj . setPosition ( tile ) ;
2023-04-10 19:26:53 +02:00
if ( obj . getVisibleTop ( ) . y < 0 )
continue ;
2023-08-11 07:45:24 +02:00
2022-08-20 12:17:27 +02:00
if ( ! searchArea . contains ( obj . getArea ( ) ) | | ! searchArea . overlap ( obj . getAccessibleArea ( ) ) )
continue ;
2023-08-11 07:45:24 +02:00
if ( outsideTheMap ( ) )
continue ;
2022-08-20 12:17:27 +02:00
float weight = weightFunction ( tile ) ;
if ( weight > bestWeight )
{
bestWeight = weight ;
result = tile ;
if ( ! ( optimizer & OptimizeType : : WEIGHT ) )
break ;
}
}
}
2022-08-09 07:54:32 +02:00
if ( result . valid ( ) )
obj . setPosition ( result ) ;
return result ;
}
2022-08-20 12:17:27 +02:00
int3 ObjectManager : : findPlaceForObject ( const rmg : : Area & searchArea , rmg : : Object & obj , si32 min_dist , OptimizeType optimizer ) const
2022-08-09 07:54:32 +02:00
{
2022-08-20 12:17:27 +02:00
return findPlaceForObject ( searchArea , obj , [ this , min_dist , & obj ] ( const int3 & tile )
2022-08-09 07:54:32 +02:00
{
2023-05-19 20:30:15 +02:00
auto ti = map . getTileInfo ( tile ) ;
2022-08-09 07:54:32 +02:00
float dist = ti . getNearestObjectDistance ( ) ;
if ( dist < min_dist )
return - 1.f ;
2023-02-11 18:05:02 +02:00
for ( const auto & t : obj . getArea ( ) . getTilesVector ( ) )
2022-08-20 12:17:27 +02:00
{
2023-06-08 19:31:38 +02:00
auto localDist = map . getTileInfo ( t ) . getNearestObjectDistance ( ) ;
if ( localDist < min_dist )
{
2022-08-20 12:17:27 +02:00
return - 1.f ;
2023-06-08 19:31:38 +02:00
}
else
{
vstd : : amin ( dist , localDist ) ; //Evaluate object tile which will be closest to another object
}
2022-08-20 12:17:27 +02:00
}
2022-08-09 07:54:32 +02:00
return dist ;
} , optimizer ) ;
}
2022-08-20 12:17:27 +02:00
rmg : : Path ObjectManager : : placeAndConnectObject ( const rmg : : Area & searchArea , rmg : : Object & obj , si32 min_dist , bool isGuarded , bool onlyStraight , OptimizeType optimizer ) const
2022-08-09 07:54:32 +02:00
{
2023-08-02 20:41:29 +02:00
RecursiveLock lock ( externalAccessMutex ) ;
2022-08-20 12:17:27 +02:00
return placeAndConnectObject ( searchArea , obj , [ this , min_dist , & obj ] ( const int3 & tile )
2022-08-09 07:54:32 +02:00
{
2023-12-12 08:40:54 +02:00
float bestDistance = 10e9 ;
2023-02-11 18:05:02 +02:00
for ( const auto & t : obj . getArea ( ) . getTilesVector ( ) )
2022-08-20 12:17:27 +02:00
{
2023-12-12 08:40:54 +02:00
float distance = map . getTileInfo ( t ) . getNearestObjectDistance ( ) ;
if ( distance < min_dist )
2022-08-20 12:17:27 +02:00
return - 1.f ;
2023-12-12 08:40:54 +02:00
else
vstd : : amin ( bestDistance , distance ) ;
2022-08-20 12:17:27 +02:00
}
2023-06-10 14:56:03 +02:00
rmg : : Area perimeter ;
rmg : : Area areaToBlock ;
if ( obj . isGuarded ( ) )
{
auto guardedArea = obj . instances ( ) . back ( ) - > getAccessibleArea ( ) ;
guardedArea . add ( obj . instances ( ) . back ( ) - > getVisitablePosition ( ) ) ;
areaToBlock = obj . getAccessibleArea ( true ) ;
areaToBlock . subtract ( guardedArea ) ;
if ( ! areaToBlock . empty ( ) )
{
perimeter = areaToBlock ;
perimeter . unite ( areaToBlock . getBorderOutside ( ) ) ;
//We could have added border around guard
perimeter . subtract ( guardedArea ) ;
}
}
else
{
perimeter = obj . getArea ( ) ;
perimeter . subtract ( obj . getAccessibleArea ( ) ) ;
if ( ! perimeter . empty ( ) )
{
perimeter . unite ( perimeter . getBorderOutside ( ) ) ;
perimeter . subtract ( obj . getAccessibleArea ( ) ) ;
}
}
//Check if perimeter of the object intersects with more than one blocked areas
auto tiles = perimeter . getTiles ( ) ;
vstd : : erase_if ( tiles , [ this ] ( const int3 & tile ) - > bool
{
//Out-of-map area also is an obstacle
if ( ! map . isOnMap ( tile ) )
return false ;
return ! ( map . isBlocked ( tile ) | | map . isUsed ( tile ) ) ;
} ) ;
if ( ! tiles . empty ( ) )
{
rmg : : Area border ( tiles ) ;
border . subtract ( areaToBlock ) ;
if ( ! border . connected ( ) )
{
//We don't want to connect two blocked areas to create impassable obstacle
return - 1.f ;
}
}
2023-12-12 08:40:54 +02:00
return bestDistance ;
2022-08-09 07:54:32 +02:00
} , isGuarded , onlyStraight , optimizer ) ;
}
2023-02-11 18:05:02 +02:00
rmg : : Path ObjectManager : : placeAndConnectObject ( const rmg : : Area & searchArea , rmg : : Object & obj , const std : : function < float ( const int3 ) > & weightFunction , bool isGuarded , bool onlyStraight , OptimizeType optimizer ) const
2022-08-09 07:54:32 +02:00
{
int3 pos ;
auto possibleArea = searchArea ;
2023-12-13 23:13:42 +02:00
auto cachedArea = zone . areaPossible ( ) + zone . freePaths ( ) ;
2022-08-09 07:54:32 +02:00
while ( true )
{
pos = findPlaceForObject ( possibleArea , obj , weightFunction , optimizer ) ;
if ( ! pos . valid ( ) )
{
return rmg : : Path : : invalid ( ) ;
}
possibleArea . erase ( pos ) ; //do not place again at this point
2023-12-21 18:56:21 +02:00
auto accessibleArea = obj . getAccessibleArea ( isGuarded ) * cachedArea ;
2022-08-09 07:54:32 +02:00
//we should exclude tiles which will be covered
if ( isGuarded )
{
2023-02-11 18:05:02 +02:00
const auto & guardedArea = obj . instances ( ) . back ( ) - > getAccessibleArea ( ) ;
2022-08-09 07:54:32 +02:00
accessibleArea . intersect ( guardedArea ) ;
2022-08-11 13:29:39 +02:00
accessibleArea . add ( obj . instances ( ) . back ( ) - > getPosition ( true ) ) ;
2022-08-09 07:54:32 +02:00
}
2023-06-10 14:56:03 +02:00
2023-12-13 23:13:42 +02:00
rmg : : Area subArea ;
if ( isGuarded )
2022-08-09 07:54:32 +02:00
{
2023-12-13 23:13:42 +02:00
const auto & guardedArea = obj . instances ( ) . back ( ) - > getAccessibleArea ( ) ;
const auto & unguardedArea = obj . getAccessibleArea ( isGuarded ) ;
subArea = cachedArea . getSubarea ( [ guardedArea , unguardedArea , obj ] ( const int3 & t )
2022-08-09 07:54:32 +02:00
{
if ( unguardedArea . contains ( t ) & & ! guardedArea . contains ( t ) )
return false ;
2022-08-11 13:29:39 +02:00
//guard position is always target
if ( obj . instances ( ) . back ( ) - > getPosition ( true ) = = t )
return true ;
2023-12-13 23:13:42 +02:00
return ! obj . getArea ( ) . contains ( t ) ;
} ) ;
}
else
{
subArea = cachedArea . getSubarea ( [ obj ] ( const int3 & t )
{
return ! obj . getArea ( ) . contains ( t ) ;
} ) ;
}
2023-12-15 19:26:59 +02:00
auto path = zone . searchPath ( accessibleArea , onlyStraight , subArea ) ;
2022-08-09 07:54:32 +02:00
if ( path . valid ( ) )
{
return path ;
}
}
}
2023-12-18 12:12:52 +02:00
bool ObjectManager : : createMonoliths ( )
{
// Special case for Junction zone only
logGlobal - > trace ( " Creating Monoliths " ) ;
for ( const auto & objInfo : requiredObjects )
{
if ( objInfo . obj - > ID ! = Obj : : MONOLITH_TWO_WAY )
{
continue ;
}
rmg : : Object rmgObject ( * objInfo . obj ) ;
rmgObject . setTemplate ( zone . getTerrainType ( ) , zone . getRand ( ) ) ;
bool guarded = addGuard ( rmgObject , objInfo . guardStrength , true ) ;
Zone : : Lock lock ( zone . areaMutex ) ;
auto path = placeAndConnectObject ( zone . areaPossible ( ) , rmgObject , 3 , guarded , false , OptimizeType : : DISTANCE ) ;
if ( ! path . valid ( ) )
{
logGlobal - > error ( " Failed to fill zone %d due to lack of space " , zone . getId ( ) ) ;
return false ;
}
zone . connectPath ( path ) ;
placeObject ( rmgObject , guarded , true , objInfo . createRoad ) ;
}
vstd : : erase_if ( requiredObjects , [ ] ( const auto & objInfo )
{
return objInfo . obj - > ID = = Obj : : MONOLITH_TWO_WAY ;
} ) ;
return true ;
}
2022-08-09 07:54:32 +02:00
bool ObjectManager : : createRequiredObjects ( )
{
logGlobal - > trace ( " Creating required objects " ) ;
2023-05-19 20:30:15 +02:00
//RecursiveLock lock(externalAccessMutex); //Why could requiredObjects be modified during the loop?
2023-07-06 22:15:00 +02:00
for ( const auto & objInfo : requiredObjects )
2022-08-09 07:54:32 +02:00
{
2023-07-06 22:15:00 +02:00
rmg : : Object rmgObject ( * objInfo . obj ) ;
2023-09-30 01:19:18 +02:00
rmgObject . setTemplate ( zone . getTerrainType ( ) , zone . getRand ( ) ) ;
2023-07-06 22:15:00 +02:00
bool guarded = addGuard ( rmgObject , objInfo . guardStrength , ( objInfo . obj - > ID = = Obj : : MONOLITH_TWO_WAY ) ) ;
2023-05-19 20:30:15 +02:00
Zone : : Lock lock ( zone . areaMutex ) ;
2022-08-20 12:17:27 +02:00
auto path = placeAndConnectObject ( zone . areaPossible ( ) , rmgObject , 3 , guarded , false , OptimizeType : : DISTANCE ) ;
2022-08-09 07:54:32 +02:00
if ( ! path . valid ( ) )
{
logGlobal - > error ( " Failed to fill zone %d due to lack of space " , zone . getId ( ) ) ;
return false ;
}
zone . connectPath ( path ) ;
2023-07-07 20:17:20 +02:00
placeObject ( rmgObject , guarded , true , objInfo . createRoad ) ;
2022-08-09 07:54:32 +02:00
for ( const auto & nearby : nearbyObjects )
{
2023-07-06 22:15:00 +02:00
if ( nearby . nearbyTarget ! = nearby . obj )
2022-08-09 07:54:32 +02:00
continue ;
2023-07-06 22:15:00 +02:00
rmg : : Object rmgNearObject ( * nearby . obj ) ;
2022-08-09 07:54:32 +02:00
rmg : : Area possibleArea ( rmgObject . instances ( ) . front ( ) - > getBlockedArea ( ) . getBorderOutside ( ) ) ;
possibleArea . intersect ( zone . areaPossible ( ) ) ;
if ( possibleArea . empty ( ) )
{
rmgNearObject . clear ( ) ;
continue ;
}
2023-05-20 11:46:32 +02:00
rmgNearObject . setPosition ( * RandomGeneratorUtil : : nextItem ( possibleArea . getTiles ( ) , zone . getRand ( ) ) ) ;
2023-07-07 20:17:20 +02:00
placeObject ( rmgNearObject , false , false , nearby . createRoad ) ;
2022-08-09 07:54:32 +02:00
}
}
2023-07-06 22:15:00 +02:00
for ( const auto & objInfo : closeObjects )
2022-08-09 07:54:32 +02:00
{
2023-05-19 20:30:15 +02:00
Zone : : Lock lock ( zone . areaMutex ) ;
2022-08-09 07:54:32 +02:00
auto possibleArea = zone . areaPossible ( ) ;
2023-05-19 20:30:15 +02:00
2023-07-06 22:15:00 +02:00
rmg : : Object rmgObject ( * objInfo . obj ) ;
2023-09-30 01:19:18 +02:00
rmgObject . setTemplate ( zone . getTerrainType ( ) , zone . getRand ( ) ) ;
2023-07-06 22:15:00 +02:00
bool guarded = addGuard ( rmgObject , objInfo . guardStrength , ( objInfo . obj - > ID = = Obj : : MONOLITH_TWO_WAY ) ) ;
2022-08-09 07:54:32 +02:00
auto path = placeAndConnectObject ( zone . areaPossible ( ) , rmgObject ,
[ this , & rmgObject ] ( const int3 & tile )
{
float dist = rmgObject . getArea ( ) . distanceSqr ( zone . getPos ( ) ) ;
dist * = ( dist > 12.f * 12.f ) ? 10.f : 1.f ; //tiles closer 12 are preferrable
dist = 1000000.f - dist ; //some big number
return dist + map . getNearestObjectDistance ( tile ) ;
2022-08-20 12:17:27 +02:00
} , guarded , false , OptimizeType : : WEIGHT ) ;
2022-08-09 07:54:32 +02:00
if ( ! path . valid ( ) )
{
logGlobal - > error ( " Failed to fill zone %d due to lack of space " , zone . getId ( ) ) ;
return false ;
}
zone . connectPath ( path ) ;
placeObject ( rmgObject , guarded , true ) ;
2023-08-12 10:53:25 +02:00
}
for ( const auto & nearby : nearbyObjects )
{
auto * targetObject = nearby . nearbyTarget ;
if ( ! targetObject | | ! targetObject - > appearance )
2022-08-09 07:54:32 +02:00
{
2023-08-12 10:53:25 +02:00
continue ;
}
rmg : : Object rmgNearObject ( * nearby . obj ) ;
2023-12-18 14:52:03 +02:00
std : : set < int3 > blockedArea = targetObject - > getBlockedPos ( ) ;
rmg : : Area possibleArea ( rmg : : Area ( rmg : : Tileset ( blockedArea . begin ( ) , blockedArea . end ( ) ) ) . getBorderOutside ( ) ) ;
2023-08-12 10:53:25 +02:00
possibleArea . intersect ( zone . areaPossible ( ) ) ;
if ( possibleArea . empty ( ) )
{
rmgNearObject . clear ( ) ;
continue ;
}
rmgNearObject . setPosition ( * RandomGeneratorUtil : : nextItem ( possibleArea . getTiles ( ) , zone . getRand ( ) ) ) ;
placeObject ( rmgNearObject , false , false ) ;
auto path = zone . searchPath ( rmgNearObject . getVisitablePosition ( ) , false ) ;
if ( path . valid ( ) )
{
zone . connectPath ( path ) ;
}
else
{
for ( auto * instance : rmgNearObject . instances ( ) )
2022-08-09 07:54:32 +02:00
{
2023-08-12 10:53:25 +02:00
logGlobal - > error ( " Failed to connect nearby object %s at %s " ,
instance - > object ( ) . getObjectName ( ) , instance - > getPosition ( true ) . toString ( ) ) ;
mapProxy - > removeObject ( & instance - > object ( ) ) ;
2022-08-09 07:54:32 +02:00
}
}
}
//create object on specific positions
//TODO: implement guards
2023-08-11 07:45:24 +02:00
for ( const auto & objInfo : instantObjects ) //Unused ATM
2022-08-09 07:54:32 +02:00
{
2023-07-06 22:15:00 +02:00
rmg : : Object rmgObject ( * objInfo . obj ) ;
rmgObject . setPosition ( objInfo . pos ) ;
2022-08-09 07:54:32 +02:00
placeObject ( rmgObject , false , false ) ;
}
requiredObjects . clear ( ) ;
closeObjects . clear ( ) ;
nearbyObjects . clear ( ) ;
instantObjects . clear ( ) ;
return true ;
}
2023-07-07 20:17:20 +02:00
void ObjectManager : : placeObject ( rmg : : Object & object , bool guarded , bool updateDistance , bool createRoad /* = false*/ )
2022-08-09 07:54:32 +02:00
{
2023-08-11 18:43:22 +02:00
//object.finalize(map);
2023-05-19 20:30:15 +02:00
2023-08-11 07:45:24 +02:00
if ( object . instances ( ) . size ( ) = = 1 & & object . instances ( ) . front ( ) - > object ( ) . ID = = Obj : : MONSTER )
{
//Fix for HoTA offset - lonely guards
2023-08-11 18:43:22 +02:00
2023-08-11 07:45:24 +02:00
auto monster = object . instances ( ) . front ( ) ;
2023-08-11 18:43:22 +02:00
if ( ! monster - > object ( ) . appearance )
{
//Needed to determine visitable offset
2023-09-30 01:19:18 +02:00
monster - > setAnyTemplate ( zone . getRand ( ) ) ;
2023-08-11 18:43:22 +02:00
}
object . getPosition ( ) ;
2023-08-11 07:45:24 +02:00
auto visitableOffset = monster - > object ( ) . getVisitableOffset ( ) ;
auto fixedPos = monster - > getPosition ( true ) + visitableOffset ;
//Do not place guard outside the map
vstd : : abetween ( fixedPos . x , visitableOffset . x , map . width ( ) - 1 ) ;
vstd : : abetween ( fixedPos . y , visitableOffset . y , map . height ( ) - 1 ) ;
int3 parentOffset = monster - > getPosition ( true ) - monster - > getPosition ( false ) ;
monster - > setPosition ( fixedPos - parentOffset ) ;
}
2023-09-30 01:19:18 +02:00
object . finalize ( map , zone . getRand ( ) ) ;
2023-08-11 07:45:24 +02:00
2023-05-19 20:30:15 +02:00
Zone : : Lock lock ( zone . areaMutex ) ;
2022-08-09 07:54:32 +02:00
zone . areaPossible ( ) . subtract ( object . getArea ( ) ) ;
bool keepVisitable = zone . freePaths ( ) . contains ( object . getVisitablePosition ( ) ) ;
zone . freePaths ( ) . subtract ( object . getArea ( ) ) ; //just to avoid areas overlapping
if ( keepVisitable )
zone . freePaths ( ) . add ( object . getVisitablePosition ( ) ) ;
zone . areaUsed ( ) . unite ( object . getArea ( ) ) ;
zone . areaUsed ( ) . erase ( object . getVisitablePosition ( ) ) ;
2023-08-11 07:45:24 +02:00
if ( guarded ) //We assume the monster won't be guarded
2022-08-09 07:54:32 +02:00
{
auto guardedArea = object . instances ( ) . back ( ) - > getAccessibleArea ( ) ;
guardedArea . add ( object . instances ( ) . back ( ) - > getVisitablePosition ( ) ) ;
auto areaToBlock = object . getAccessibleArea ( true ) ;
areaToBlock . subtract ( guardedArea ) ;
zone . areaPossible ( ) . subtract ( areaToBlock ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & i : areaToBlock . getTilesVector ( ) )
2022-08-09 07:54:32 +02:00
if ( map . isOnMap ( i ) & & map . isPossible ( i ) )
map . setOccupied ( i , ETileType : : BLOCKED ) ;
}
2023-12-12 08:40:54 +02:00
lock . unlock ( ) ;
2022-08-09 07:54:32 +02:00
2023-08-02 20:41:29 +02:00
if ( updateDistance )
{
//Update distances in every adjacent zone in case of wide connection
std : : set < TRmgTemplateZoneId > adjacentZones ;
auto objectArea = object . getArea ( ) ;
objectArea . unite ( objectArea . getBorderOutside ( ) ) ;
for ( auto tile : objectArea . getTilesVector ( ) )
{
if ( map . isOnMap ( tile ) )
{
adjacentZones . insert ( map . getZoneID ( tile ) ) ;
}
}
for ( auto id : adjacentZones )
{
auto manager = map . getZones ( ) . at ( id ) - > getModificator < ObjectManager > ( ) ;
if ( manager )
{
2023-12-12 08:40:54 +02:00
// TODO: Update distances for perimeter of guarded object, not just treasures
2023-08-02 20:41:29 +02:00
manager - > updateDistances ( object ) ;
}
}
}
2022-08-09 07:54:32 +02:00
2023-12-18 14:52:03 +02:00
// TODO: Add multiple tiles in one operation to avoid multiple invalidation
2022-08-09 07:54:32 +02:00
for ( auto * instance : object . instances ( ) )
{
objectsVisitableArea . add ( instance - > getVisitablePosition ( ) ) ;
objects . push_back ( & instance - > object ( ) ) ;
if ( auto * m = zone . getModificator < RoadPlacer > ( ) )
{
2023-12-07 13:57:39 +02:00
if ( instance - > object ( ) . blockVisit & & ! instance - > object ( ) . removable )
{
//Cannot be trespassed (Corpse)
continue ;
}
2023-12-21 14:22:23 +02:00
else if ( instance - > object ( ) . appearance - > isVisitableFromTop ( ) )
{
//Passable objects
m - > areaForRoads ( ) . add ( instance - > getVisitablePosition ( ) ) ;
}
2023-12-21 13:29:45 +02:00
else if ( ! instance - > object ( ) . appearance - > isVisitableFromTop ( ) )
2022-08-09 07:54:32 +02:00
{
2023-12-21 14:22:23 +02:00
// Do not route road behind visitable tile
int3 visitablePos = instance - > getVisitablePosition ( ) ;
auto areaVisitable = rmg : : Area ( { visitablePos } ) ;
auto borderAbove = areaVisitable . getBorderOutside ( ) ;
vstd : : erase_if ( borderAbove , [ & ] ( const int3 & tile )
2023-12-21 13:29:45 +02:00
{
2023-12-21 14:22:23 +02:00
return tile . y > = visitablePos . y | |
( ! instance - > object ( ) . blockingAt ( tile + int3 ( 0 , 1 , 0 ) ) & &
instance - > object ( ) . blockingAt ( tile ) ) ;
} ) ;
m - > areaIsolated ( ) . unite ( borderAbove ) ;
2022-08-09 07:54:32 +02:00
}
}
2023-04-23 10:08:16 +02:00
2023-11-03 19:20:25 +02:00
switch ( instance - > object ( ) . ID . toEnum ( ) )
2023-04-23 10:08:16 +02:00
{
case Obj : : RANDOM_TREASURE_ART :
case Obj : : RANDOM_MINOR_ART : //In OH3 quest artifacts have higher value than normal arts
2023-08-11 07:45:24 +02:00
case Obj : : RANDOM_RESOURCE :
2023-04-23 10:08:16 +02:00
{
if ( auto * qap = zone . getModificator < QuestArtifactPlacer > ( ) )
{
qap - > rememberPotentialArtifactToReplace ( & instance - > object ( ) ) ;
}
break ;
}
default :
break ;
}
2022-08-09 07:54:32 +02:00
}
2023-07-07 20:17:20 +02:00
if ( createRoad )
2022-08-09 07:54:32 +02:00
{
2023-07-07 20:17:20 +02:00
if ( auto * m = zone . getModificator < RoadPlacer > ( ) )
m - > addRoadNode ( object . instances ( ) . front ( ) - > getVisitablePosition ( ) ) ;
}
//TODO: Add road node to these objects:
/*
case Obj : : MONOLITH_ONE_WAY_ENTRANCE :
case Obj : : RANDOM_TOWN :
2022-08-09 07:54:32 +02:00
case Obj : : MONOLITH_ONE_WAY_EXIT :
2023-07-07 20:17:20 +02:00
*/
2023-07-06 22:15:00 +02:00
2023-11-03 19:20:25 +02:00
switch ( object . instances ( ) . front ( ) - > object ( ) . ID . toEnum ( ) )
2023-07-07 20:17:20 +02:00
{
2022-08-09 07:54:32 +02:00
case Obj : : WATER_WHEEL :
2023-07-06 22:15:00 +02:00
if ( auto * m = zone . getModificator < RiverPlacer > ( ) )
2022-08-09 07:54:32 +02:00
m - > addRiverNode ( object . instances ( ) . front ( ) - > getVisitablePosition ( ) ) ;
break ;
2023-07-06 22:15:00 +02:00
2022-08-09 07:54:32 +02:00
default :
break ;
}
}
CGCreature * ObjectManager : : chooseGuard ( si32 strength , bool zoneGuard )
{
//precalculate actual (randomized) monster strength based on this post
//http://forum.vcmi.eu/viewtopic.php?p=12426#12426
2023-05-21 00:13:45 +02:00
if ( ! zoneGuard & & zone . monsterStrength = = EMonsterStrength : : ZONE_NONE )
return nullptr ; //no guards inside this zone except for zone guards
2023-05-03 12:07:00 +02:00
2022-08-09 07:54:32 +02:00
int mapMonsterStrength = map . getMapGenOptions ( ) . getMonsterStrength ( ) ;
2023-05-21 00:13:45 +02:00
int monsterStrength = ( zoneGuard ? 0 : zone . monsterStrength - EMonsterStrength : : ZONE_NORMAL ) + mapMonsterStrength - 1 ; //array index from 0 to 4
2022-08-09 07:54:32 +02:00
static const std : : array < int , 5 > value1 { 2500 , 1500 , 1000 , 500 , 0 } ;
static const std : : array < int , 5 > value2 { 7500 , 7500 , 7500 , 5000 , 5000 } ;
static const std : : array < float , 5 > multiplier1 { 0.5 , 0.75 , 1.0 , 1.5 , 1.5 } ;
static const std : : array < float , 5 > multiplier2 { 0.5 , 0.75 , 1.0 , 1.0 , 1.5 } ;
int strength1 = static_cast < int > ( std : : max ( 0.f , ( strength - value1 . at ( monsterStrength ) ) * multiplier1 . at ( monsterStrength ) ) ) ;
int strength2 = static_cast < int > ( std : : max ( 0.f , ( strength - value2 . at ( monsterStrength ) ) * multiplier2 . at ( monsterStrength ) ) ) ;
strength = strength1 + strength2 ;
if ( strength < generator . getConfig ( ) . minGuardStrength )
return nullptr ; //no guard at all
CreatureID creId = CreatureID : : NONE ;
int amount = 0 ;
std : : vector < CreatureID > possibleCreatures ;
for ( auto cre : VLC - > creh - > objects )
{
if ( cre - > special )
continue ;
2023-04-05 02:26:29 +02:00
if ( ! cre - > getAIValue ( ) ) //bug #2681
2022-08-09 07:54:32 +02:00
continue ;
2023-04-09 17:26:32 +02:00
if ( ! vstd : : contains ( zone . getMonsterTypes ( ) , cre - > getFaction ( ) ) )
2022-08-09 07:54:32 +02:00
continue ;
2023-04-05 02:26:29 +02:00
if ( ( static_cast < si32 > ( cre - > getAIValue ( ) * ( cre - > ammMin + cre - > ammMax ) / 2 ) < strength ) & & ( strength < static_cast < si32 > ( cre - > getAIValue ( ) ) * 100 ) ) //at least one full monster. size between average size of given stack and 100
2022-08-09 07:54:32 +02:00
{
2023-04-05 02:26:29 +02:00
possibleCreatures . push_back ( cre - > getId ( ) ) ;
2022-08-09 07:54:32 +02:00
}
}
2023-02-11 18:05:02 +02:00
if ( ! possibleCreatures . empty ( ) )
2022-08-09 07:54:32 +02:00
{
2023-05-20 11:46:32 +02:00
creId = * RandomGeneratorUtil : : nextItem ( possibleCreatures , zone . getRand ( ) ) ;
2023-11-05 18:58:07 +02:00
amount = strength / creId . toEntity ( VLC ) - > getAIValue ( ) ;
2022-08-09 07:54:32 +02:00
if ( amount > = 4 )
2023-05-20 11:46:32 +02:00
amount = static_cast < int > ( amount * zone . getRand ( ) . nextDouble ( 0.75 , 1.25 ) ) ;
2022-08-09 07:54:32 +02:00
}
else //just pick any available creature
{
2023-11-05 18:58:07 +02:00
creId = CreatureID : : AZURE_DRAGON ; //Azure Dragon
amount = strength / creId . toEntity ( VLC ) - > getAIValue ( ) ;
2022-08-09 07:54:32 +02:00
}
auto guardFactory = VLC - > objtypeh - > getHandlerFor ( Obj : : MONSTER , creId ) ;
2023-02-11 18:05:02 +02:00
auto * guard = dynamic_cast < CGCreature * > ( guardFactory - > create ( ) ) ;
2022-08-09 07:54:32 +02:00
guard - > character = CGCreature : : HOSTILE ;
2023-02-11 18:05:02 +02:00
auto * hlp = new CStackInstance ( creId , amount ) ;
2022-08-09 07:54:32 +02:00
//will be set during initialization
guard - > putStack ( SlotID ( 0 ) , hlp ) ;
return guard ;
}
bool ObjectManager : : addGuard ( rmg : : Object & object , si32 strength , bool zoneGuard )
{
auto * guard = chooseGuard ( strength , zoneGuard ) ;
if ( ! guard )
return false ;
2023-12-06 21:49:28 +02:00
// Prefer non-blocking tiles, if any
2023-12-21 13:29:45 +02:00
auto entrableArea = object . getEntrableArea ( ) ;
if ( entrableArea . empty ( ) )
2023-12-06 21:49:28 +02:00
{
2023-12-21 13:29:45 +02:00
entrableArea . add ( object . getVisitablePosition ( ) ) ;
2023-12-06 21:49:28 +02:00
}
2023-12-21 13:29:45 +02:00
rmg : : Area entrableBorder = entrableArea . getBorderOutside ( ) ;
2022-08-09 07:54:32 +02:00
auto accessibleArea = object . getAccessibleArea ( ) ;
2023-12-21 13:29:45 +02:00
accessibleArea . erase_if ( [ & ] ( const int3 & tile )
{
return ! entrableBorder . contains ( tile ) ;
} ) ;
2022-08-09 07:54:32 +02:00
if ( accessibleArea . empty ( ) )
{
delete guard ;
return false ;
}
auto guardTiles = accessibleArea . getTilesVector ( ) ;
auto guardPos = * std : : min_element ( guardTiles . begin ( ) , guardTiles . end ( ) , [ & object ] ( const int3 & l , const int3 & r )
{
auto p = object . getVisitablePosition ( ) ;
if ( l . y > r . y )
return true ;
if ( l . y = = r . y )
return abs ( l . x - p . x ) < abs ( r . x - p . x ) ;
return false ;
} ) ;
auto & instance = object . addInstance ( * guard ) ;
2023-09-30 01:19:18 +02:00
instance . setAnyTemplate ( zone . getRand ( ) ) ; //terrain is irrelevant for monsters, but monsters need some template now
2023-08-11 07:45:24 +02:00
//Fix HoTA monsters with offset template
auto visitableOffset = instance . object ( ) . getVisitableOffset ( ) ;
auto fixedPos = guardPos - object . getPosition ( ) + visitableOffset ;
instance . setPosition ( fixedPos ) ;
2022-08-09 07:54:32 +02:00
return true ;
}
2022-07-26 15:07:42 +02:00
2023-07-06 22:15:00 +02:00
RequiredObjectInfo : : RequiredObjectInfo ( ) :
obj ( nullptr ) ,
nearbyTarget ( nullptr ) ,
guardStrength ( 0 ) ,
2023-07-07 20:17:20 +02:00
createRoad ( true )
2023-07-06 22:15:00 +02:00
{ }
2023-07-07 20:17:20 +02:00
RequiredObjectInfo : : RequiredObjectInfo ( CGObjectInstance * obj , ui32 guardStrength , bool createRoad , CGObjectInstance * nearbyTarget ) :
2023-07-06 22:15:00 +02:00
obj ( obj ) ,
nearbyTarget ( nearbyTarget ) ,
guardStrength ( guardStrength ) ,
2023-07-07 20:17:20 +02:00
createRoad ( createRoad )
2023-07-06 22:15:00 +02:00
{ }
2023-07-07 21:40:55 +02:00
VCMI_LIB_NAMESPACE_END
2023-07-06 22:15:00 +02:00