2022-08-09 09:54:32 +04: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"
#include "CMapGenerator.h"
#include "TileInfo.h"
#include "RmgMap.h"
#include "RoadPlacer.h"
#include "RiverPlacer.h"
#include "WaterAdopter.h"
#include "../CCreatureHandler.h"
#include "../mapObjects/CommonConstructors.h"
#include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
#include "../mapping/CMap.h"
#include "../mapping/CMapEditManager.h"
#include "Functions.h"
#include "RmgObject.h"
void ObjectManager :: process ()
{
zone . fractalize ();
2022-08-31 01:03:35 +04:00
createRequiredObjects ();
2022-08-09 09:54:32 +04:00
}
void ObjectManager :: init ()
{
DEPENDENCY ( WaterAdopter );
POSTFUNCTION ( RoadPlacer );
2022-08-20 14:17:27 +04:00
createDistancesPriorityQueue ();
}
void ObjectManager :: createDistancesPriorityQueue ()
{
tilesByDistance . clear ();
for ( auto & tile : zone . areaPossible (). getTilesVector ())
{
tilesByDistance . push ( std :: make_pair ( tile , map . getNearestObjectDistance ( tile )));
}
2022-08-09 09:54:32 +04:00
}
void ObjectManager :: addRequiredObject ( CGObjectInstance * obj , si32 strength )
{
requiredObjects . push_back ( std :: make_pair ( obj , strength ));
}
void ObjectManager :: addCloseObject ( CGObjectInstance * obj , si32 strength )
{
closeObjects . push_back ( std :: make_pair ( obj , strength ));
}
void ObjectManager :: addNearbyObject ( CGObjectInstance * obj , CGObjectInstance * nearbyTarget )
{
nearbyObjects . push_back ( std :: make_pair ( obj , nearbyTarget ));
}
void ObjectManager :: updateDistances ( const rmg :: Object & obj )
{
2022-08-20 14:17:27 +04:00
tilesByDistance . clear ();
2022-08-09 09:54:32 +04:00
for ( auto tile : zone . areaPossible (). getTiles ()) //don't need to mark distance for not possible tiles
{
ui32 d = obj . getArea (). distanceSqr ( tile ); //optimization, only relative distance is interesting
map . setNearestObjectDistance ( tile , std :: min (( float ) d , map . getNearestObjectDistance ( tile )));
2022-08-20 14:17:27 +04:00
tilesByDistance . push ( std :: make_pair ( tile , map . getNearestObjectDistance ( tile )));
2022-08-09 09:54:32 +04:00
}
}
const rmg :: Area & ObjectManager :: getVisitableArea () const
{
return objectsVisitableArea ;
}
2022-08-20 14:17:27 +04:00
int3 ObjectManager :: findPlaceForObject ( const rmg :: Area & searchArea , rmg :: Object & obj , std :: function < float ( const int3 ) > weightFunction , OptimizeType optimizer ) const
2022-08-09 09:54:32 +04:00
{
float bestWeight = 0.f ;
int3 result ( - 1 , - 1 , - 1 );
2022-08-20 14:17:27 +04:00
if ( optimizer & OptimizeType :: DISTANCE )
2022-08-09 09:54:32 +04:00
{
2022-08-20 14:17:27 +04:00
auto open = tilesByDistance ;
while ( ! open . empty ())
2022-08-09 09:54:32 +04:00
{
2022-08-20 14:17:27 +04:00
auto node = open . top ();
open . pop ();
int3 tile = node . first ;
if ( ! searchArea . contains ( tile ))
continue ;
obj . setPosition ( tile );
if ( ! searchArea . contains ( obj . getArea ()) || ! searchArea . overlap ( obj . getAccessibleArea ()))
continue ;
float weight = weightFunction ( tile );
if ( weight > bestWeight )
{
bestWeight = weight ;
result = tile ;
if ( ! ( optimizer & OptimizeType :: WEIGHT ))
break ;
}
2022-08-09 09:54:32 +04:00
}
}
2022-08-20 14:17:27 +04:00
else
{
for ( const auto & tile : searchArea . getTiles ())
{
obj . setPosition ( tile );
if ( ! searchArea . contains ( obj . getArea ()) || ! searchArea . overlap ( obj . getAccessibleArea ()))
continue ;
float weight = weightFunction ( tile );
if ( weight > bestWeight )
{
bestWeight = weight ;
result = tile ;
if ( ! ( optimizer & OptimizeType :: WEIGHT ))
break ;
}
}
}
2022-08-09 09:54:32 +04:00
if ( result . valid ())
obj . setPosition ( result );
return result ;
}
2022-08-20 14:17:27 +04:00
int3 ObjectManager :: findPlaceForObject ( const rmg :: Area & searchArea , rmg :: Object & obj , si32 min_dist , OptimizeType optimizer ) const
2022-08-09 09:54:32 +04:00
{
2022-08-20 14:17:27 +04:00
return findPlaceForObject ( searchArea , obj , [ this , min_dist , & obj ]( const int3 & tile )
2022-08-09 09:54:32 +04:00
{
auto ti = map . getTile ( tile );
float dist = ti . getNearestObjectDistance ();
if ( dist < min_dist )
return - 1.f ;
2022-08-20 14:17:27 +04:00
for ( auto & t : obj . getArea (). getTilesVector ())
{
if ( map . getTile ( t ). getNearestObjectDistance () < min_dist )
return - 1.f ;
}
2022-08-09 09:54:32 +04:00
return dist ;
}, optimizer );
}
2022-08-20 14:17:27 +04: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 09:54:32 +04:00
{
2022-08-20 14:17:27 +04:00
return placeAndConnectObject ( searchArea , obj , [ this , min_dist , & obj ]( const int3 & tile )
2022-08-09 09:54:32 +04:00
{
auto ti = map . getTile ( tile );
float dist = ti . getNearestObjectDistance ();
if ( dist < min_dist )
return - 1.f ;
2022-08-20 14:17:27 +04:00
for ( auto & t : obj . getArea (). getTilesVector ())
{
if ( map . getTile ( t ). getNearestObjectDistance () < min_dist )
return - 1.f ;
}
2022-08-09 09:54:32 +04:00
return dist ;
}, isGuarded , onlyStraight , optimizer );
}
2022-08-20 14:17:27 +04:00
rmg :: Path ObjectManager :: placeAndConnectObject ( const rmg :: Area & searchArea , rmg :: Object & obj , std :: function < float ( const int3 ) > weightFunction , bool isGuarded , bool onlyStraight , OptimizeType optimizer ) const
2022-08-09 09:54:32 +04:00
{
int3 pos ;
auto possibleArea = searchArea ;
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
auto accessibleArea = obj . getAccessibleArea ( isGuarded ) * ( zone . areaPossible () + zone . freePaths ());
//we should exclude tiles which will be covered
if ( isGuarded )
{
auto & guardedArea = obj . instances (). back () -> getAccessibleArea ();
accessibleArea . intersect ( guardedArea );
2022-08-11 15:29:39 +04:00
accessibleArea . add ( obj . instances (). back () -> getPosition ( true ));
2022-08-09 09:54:32 +04:00
}
auto path = zone . searchPath ( accessibleArea , onlyStraight , [ & obj , isGuarded ]( const int3 & t )
{
if ( isGuarded )
{
auto & guardedArea = obj . instances (). back () -> getAccessibleArea ();
auto & unguardedArea = obj . getAccessibleArea ( isGuarded );
if ( unguardedArea . contains ( t ) && ! guardedArea . contains ( t ))
return false ;
2022-08-11 15:29:39 +04:00
//guard position is always target
if ( obj . instances (). back () -> getPosition ( true ) == t )
return true ;
2022-08-09 09:54:32 +04:00
}
return ! obj . getArea (). contains ( t );
});
if ( path . valid ())
{
return path ;
}
}
}
bool ObjectManager :: createRequiredObjects ()
{
logGlobal -> trace ( "Creating required objects" );
2022-08-31 01:03:35 +04:00
RandomGeneratorUtil :: randomShuffle ( requiredObjects , generator . rand );
2022-08-09 09:54:32 +04:00
for ( const auto & object : requiredObjects )
{
auto * obj = object . first ;
int3 pos ;
rmg :: Object rmgObject ( * obj );
rmgObject . setTemplate ( zone . getTerrainType ());
bool guarded = addGuard ( rmgObject , object . second , ( obj -> ID == Obj :: MONOLITH_TWO_WAY ));
2022-08-20 14:17:27 +04:00
auto path = placeAndConnectObject ( zone . areaPossible (), rmgObject , 3 , guarded , false , OptimizeType :: DISTANCE );
2022-08-09 09:54:32 +04: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 );
for ( const auto & nearby : nearbyObjects )
{
if ( nearby . second != obj )
continue ;
rmg :: Object rmgNearObject ( * nearby . first );
rmg :: Area possibleArea ( rmgObject . instances (). front () -> getBlockedArea (). getBorderOutside ());
possibleArea . intersect ( zone . areaPossible ());
if ( possibleArea . empty ())
{
rmgNearObject . clear ();
continue ;
}
rmgNearObject . setPosition ( * RandomGeneratorUtil :: nextItem ( possibleArea . getTiles (), generator . rand ));
placeObject ( rmgNearObject , false , false );
}
}
for ( const auto & object : closeObjects )
{
auto * obj = object . first ;
int3 pos ;
auto possibleArea = zone . areaPossible ();
rmg :: Object rmgObject ( * obj );
rmgObject . setTemplate ( zone . getTerrainType ());
bool guarded = addGuard ( rmgObject , object . second , ( obj -> ID == Obj :: MONOLITH_TWO_WAY ));
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 14:17:27 +04:00
}, guarded , false , OptimizeType :: WEIGHT );
2022-08-09 09:54:32 +04: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 );
for ( const auto & nearby : nearbyObjects )
{
if ( nearby . second != obj )
continue ;
rmg :: Object rmgNearObject ( * nearby . first );
rmg :: Area possibleArea ( rmgObject . instances (). front () -> getBlockedArea (). getBorderOutside ());
possibleArea . intersect ( zone . areaPossible ());
if ( possibleArea . empty ())
{
rmgNearObject . clear ();
continue ;
}
rmgNearObject . setPosition ( * RandomGeneratorUtil :: nextItem ( possibleArea . getTiles (), generator . rand ));
placeObject ( rmgNearObject , false , false );
}
}
//create object on specific positions
//TODO: implement guards
for ( const auto & obj : instantObjects )
{
rmg :: Object rmgObject ( * obj . first );
rmgObject . setPosition ( obj . second );
placeObject ( rmgObject , false , false );
}
requiredObjects . clear ();
closeObjects . clear ();
nearbyObjects . clear ();
instantObjects . clear ();
return true ;
}
void ObjectManager :: placeObject ( rmg :: Object & object , bool guarded , bool updateDistance )
{
object . finalize ( map );
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 ());
if ( guarded )
{
auto guardedArea = object . instances (). back () -> getAccessibleArea ();
guardedArea . add ( object . instances (). back () -> getVisitablePosition ());
auto areaToBlock = object . getAccessibleArea ( true );
areaToBlock . subtract ( guardedArea );
zone . areaPossible (). subtract ( areaToBlock );
for ( auto & i : areaToBlock . getTilesVector ())
if ( map . isOnMap ( i ) && map . isPossible ( i ))
map . setOccupied ( i , ETileType :: BLOCKED );
}
if ( updateDistance )
updateDistances ( object );
for ( auto * instance : object . instances ())
{
objectsVisitableArea . add ( instance -> getVisitablePosition ());
objects . push_back ( & instance -> object ());
if ( auto * m = zone . getModificator < RoadPlacer > ())
{
if ( instance -> object (). appearance . isVisitableFromTop ())
m -> areaForRoads (). add ( instance -> getVisitablePosition ());
else
{
m -> areaIsolated (). add ( instance -> getVisitablePosition () + int3 ( 0 , - 1 , 0 ));
}
}
}
switch ( object . instances (). front () -> object (). ID )
{
case Obj :: TOWN :
case Obj :: RANDOM_TOWN :
case Obj :: MONOLITH_TWO_WAY :
case Obj :: MONOLITH_ONE_WAY_ENTRANCE :
case Obj :: MONOLITH_ONE_WAY_EXIT :
case Obj :: SUBTERRANEAN_GATE :
case Obj :: SHIPYARD :
if ( auto * m = zone . getModificator < RoadPlacer > ())
m -> addRoadNode ( object . instances (). front () -> getVisitablePosition ());
break ;
case Obj :: WATER_WHEEL :
if ( auto * m = zone . getModificator < RiverPlacer > ())
m -> addRiverNode ( object . instances (). front () -> getVisitablePosition ());
break ;
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
int mapMonsterStrength = map . getMapGenOptions (). getMonsterStrength ();
int monsterStrength = ( zoneGuard ? 0 : zone . zoneMonsterStrength ) + mapMonsterStrength - 1 ; //array index from 0 to 4
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 ;
if ( ! cre -> AIValue ) //bug #2681
continue ;
if ( ! vstd :: contains ( zone . getMonsterTypes (), cre -> faction ))
continue ;
if ((( si32 )( cre -> AIValue * ( cre -> ammMin + cre -> ammMax ) / 2 ) < strength ) && ( strength < ( si32 ) cre -> AIValue * 100 )) //at least one full monster. size between average size of given stack and 100
{
possibleCreatures . push_back ( cre -> idNumber );
}
}
if ( possibleCreatures . size ())
{
creId = * RandomGeneratorUtil :: nextItem ( possibleCreatures , generator . rand );
amount = strength / VLC -> creh -> objects [ creId ] -> AIValue ;
if ( amount >= 4 )
amount = static_cast < int > ( amount * generator . rand . nextDouble ( 0.75 , 1.25 ));
}
else //just pick any available creature
{
creId = CreatureID ( 132 ); //Azure Dragon
amount = strength / VLC -> creh -> objects [ creId ] -> AIValue ;
}
auto guardFactory = VLC -> objtypeh -> getHandlerFor ( Obj :: MONSTER , creId );
auto guard = ( CGCreature * ) guardFactory -> create ( ObjectTemplate ());
guard -> character = CGCreature :: HOSTILE ;
auto hlp = new CStackInstance ( creId , amount );
//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 ;
rmg :: Area visitablePos ({ object . getVisitablePosition ()});
visitablePos . unite ( visitablePos . getBorderOutside ());
auto accessibleArea = object . getAccessibleArea ();
accessibleArea . intersect ( visitablePos );
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 );
instance . setPosition ( guardPos - object . getPosition ());
return true ;
}