2022-08-09 07:54:32 +02:00
/*
* TownPlacer . 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 "TownPlacer.h"
2023-05-20 10:17:37 +02:00
# include "../CMapGenerator.h"
# include "../RmgMap.h"
2023-06-02 20:47:37 +02:00
# include "../../mapObjectConstructors/AObjectTypeHandler.h"
# include "../../mapObjectConstructors/CObjectClassesHandler.h"
2024-01-09 16:43:36 +02:00
# include "../../mapObjects/CGTownInstance.h"
2023-05-20 10:17:37 +02:00
# include "../../mapping/CMap.h"
# include "../../mapping/CMapEditManager.h"
# include "../../spells/CSpellHandler.h" //for choosing random spells
# include "../RmgPath.h"
# include "../RmgObject.h"
2022-08-09 07:54:32 +02:00
# include "ObjectManager.h"
2023-05-20 10:17:37 +02:00
# include "../Functions.h"
2022-08-09 07:54:32 +02:00
# include "RoadPlacer.h"
2023-03-29 16:54:22 +02:00
# include "MinePlacer.h"
2022-08-09 07:54:32 +02:00
# include "WaterAdopter.h"
2023-05-20 10:17:37 +02:00
# include "../TileInfo.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 TownPlacer : : process ( )
{
auto * manager = zone . getModificator < ObjectManager > ( ) ;
if ( ! manager )
{
logGlobal - > error ( " ObjectManager doesn't exist for zone %d, skip modificator %s " , zone . getId ( ) , getName ( ) ) ;
return ;
}
placeTowns ( * manager ) ;
}
void TownPlacer : : init ( )
{
2023-03-29 16:54:22 +02:00
POSTFUNCTION ( MinePlacer ) ;
2022-08-09 07:54:32 +02:00
POSTFUNCTION ( RoadPlacer ) ;
}
void TownPlacer : : placeTowns ( ObjectManager & manager )
{
2022-09-05 18:54:24 +02:00
if ( zone . getOwner ( ) & & ( ( zone . getType ( ) = = ETemplateZoneType : : CPU_START ) | | ( zone . getType ( ) = = ETemplateZoneType : : PLAYER_START ) ) )
2022-08-09 07:54:32 +02:00
{
//set zone types to player faction, generate main town
logGlobal - > info ( " Preparing playing zone " ) ;
int player_id = * zone . getOwner ( ) - 1 ;
2023-05-19 20:30:15 +02:00
auto & playerInfo = map . getPlayer ( player_id ) ;
2022-08-09 07:54:32 +02:00
PlayerColor player ( player_id ) ;
if ( playerInfo . canAnyonePlay ( ) )
{
player = PlayerColor ( player_id ) ;
zone . setTownType ( map . getMapGenOptions ( ) . getPlayersSettings ( ) . find ( player ) - > second . getStartingTown ( ) ) ;
2023-08-18 12:38:19 +02:00
if ( zone . getTownType ( ) = = FactionID : : RANDOM )
2022-08-09 07:54:32 +02:00
zone . setTownType ( getRandomTownType ( true ) ) ;
}
else //no player - randomize town
{
player = PlayerColor : : NEUTRAL ;
zone . setTownType ( getRandomTownType ( ) ) ;
}
auto townFactory = VLC - > objtypeh - > getHandlerFor ( Obj : : TOWN , zone . getTownType ( ) ) ;
2023-02-11 18:05:02 +02:00
2024-01-01 16:37:48 +02:00
CGTownInstance * town = dynamic_cast < CGTownInstance * > ( townFactory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2022-08-09 07:54:32 +02:00
town - > tempOwner = player ;
town - > builtBuildings . insert ( BuildingID : : FORT ) ;
town - > builtBuildings . insert ( BuildingID : : DEFAULT ) ;
for ( auto spell : VLC - > spellh - > objects ) //add all regular spells to town
{
if ( ! spell - > isSpecial ( ) & & ! spell - > isCreatureAbility ( ) )
town - > possibleSpells . push_back ( spell - > id ) ;
}
auto position = placeMainTown ( manager , * town ) ;
totalTowns + + ;
//register MAIN town of zone only
2023-04-09 17:26:32 +02:00
map . registerZone ( town - > getFaction ( ) ) ;
2022-08-09 07:54:32 +02:00
if ( playerInfo . canAnyonePlay ( ) ) //configure info for owning player
{
logGlobal - > trace ( " Fill player info %d " , player_id ) ;
// Update player info
playerInfo . allowedFactions . clear ( ) ;
playerInfo . allowedFactions . insert ( zone . getTownType ( ) ) ;
playerInfo . hasMainTown = true ;
playerInfo . posOfMainTown = position ;
playerInfo . generateHeroAtMainTown = true ;
//now create actual towns
addNewTowns ( zone . getPlayerTowns ( ) . getCastleCount ( ) - 1 , true , player , manager ) ;
addNewTowns ( zone . getPlayerTowns ( ) . getTownCount ( ) , false , player , manager ) ;
}
else
{
addNewTowns ( zone . getPlayerTowns ( ) . getCastleCount ( ) - 1 , true , PlayerColor : : NEUTRAL , manager ) ;
addNewTowns ( zone . getPlayerTowns ( ) . getTownCount ( ) , false , PlayerColor : : NEUTRAL , manager ) ;
}
}
else //randomize town types for any other zones as well
{
zone . setTownType ( getRandomTownType ( ) ) ;
}
addNewTowns ( zone . getNeutralTowns ( ) . getCastleCount ( ) , true , PlayerColor : : NEUTRAL , manager ) ;
addNewTowns ( zone . getNeutralTowns ( ) . getTownCount ( ) , false , PlayerColor : : NEUTRAL , manager ) ;
if ( ! totalTowns ) //if there's no town present, get random faction for dwellings and pandoras
{
//25% chance for neutral
2023-05-20 11:46:32 +02:00
if ( zone . getRand ( ) . nextInt ( 1 , 100 ) < = 25 )
2022-08-09 07:54:32 +02:00
{
zone . setTownType ( ETownType : : NEUTRAL ) ;
}
else
{
2023-02-11 18:05:02 +02:00
if ( ! zone . getTownTypes ( ) . empty ( ) )
2023-05-20 11:46:32 +02:00
zone . setTownType ( * RandomGeneratorUtil : : nextItem ( zone . getTownTypes ( ) , zone . getRand ( ) ) ) ;
2023-02-11 18:05:02 +02:00
else if ( ! zone . getMonsterTypes ( ) . empty ( ) )
2023-05-20 11:46:32 +02:00
zone . setTownType ( * RandomGeneratorUtil : : nextItem ( zone . getMonsterTypes ( ) , zone . getRand ( ) ) ) ; //this happens in Clash of Dragons in treasure zones, where all towns are banned
2022-08-09 07:54:32 +02:00
else //just in any case
zone . setTownType ( getRandomTownType ( ) ) ;
}
}
}
int3 TownPlacer : : placeMainTown ( ObjectManager & manager , CGTownInstance & town )
{
//towns are big objects and should be centered around visitable position
rmg : : Object rmgObject ( town ) ;
2023-09-30 01:19:18 +02:00
rmgObject . setTemplate ( zone . getTerrainType ( ) , zone . getRand ( ) ) ;
2023-05-19 20:30:15 +02:00
int3 position ( - 1 , - 1 , - 1 ) ;
2022-08-09 07:54:32 +02:00
{
2023-05-19 20:30:15 +02:00
Zone : : Lock lock ( zone . areaMutex ) ;
2024-03-27 07:16:48 +02:00
position = manager . findPlaceForObject ( zone . areaPossible ( ) . get ( ) , rmgObject , [ this ] ( const int3 & t )
2023-05-19 20:30:15 +02:00
{
float distance = zone . getPos ( ) . dist2dSQ ( t ) ;
return 100000.f - distance ; //some big number
} , ObjectManager : : OptimizeType : : WEIGHT ) ;
}
2023-04-01 17:07:43 +02:00
rmgObject . setPosition ( position + int3 ( 2 , 2 , 0 ) ) ; //place visitable tile in the exact center of a zone
2023-07-07 20:17:20 +02:00
manager . placeObject ( rmgObject , false , true , true ) ;
2022-08-09 07:54:32 +02:00
cleanupBoundaries ( rmgObject ) ;
zone . setPos ( rmgObject . getVisitablePosition ( ) ) ; //roads lead to main town
return position ;
}
void TownPlacer : : cleanupBoundaries ( const rmg : : Object & rmgObject )
{
2023-05-19 20:30:15 +02:00
Zone : : Lock lock ( zone . areaMutex ) ;
2023-02-11 18:05:02 +02:00
for ( const auto & t : rmgObject . getArea ( ) . getBorderOutside ( ) )
2022-08-09 07:54:32 +02:00
{
2023-06-09 21:22:44 +02:00
if ( t . y > rmgObject . getVisitablePosition ( ) . y ) //Line below the town
2022-08-09 07:54:32 +02:00
{
2023-06-09 21:22:44 +02:00
if ( map . isOnMap ( t ) )
{
map . setOccupied ( t , ETileType : : FREE ) ;
2024-03-27 07:16:48 +02:00
zone . areaPossible ( ) - > erase ( t ) ;
zone . freePaths ( ) - > add ( t ) ;
2023-06-09 21:22:44 +02:00
}
2022-08-09 07:54:32 +02:00
}
}
}
2023-02-11 18:05:02 +02:00
void TownPlacer : : addNewTowns ( int count , bool hasFort , const PlayerColor & player , ObjectManager & manager )
2022-08-09 07:54:32 +02:00
{
for ( int i = 0 ; i < count ; i + + )
{
2023-11-05 19:13:18 +02:00
FactionID subType = zone . getTownType ( ) ;
2022-08-09 07:54:32 +02:00
if ( totalTowns > 0 )
{
if ( ! zone . areTownsSameType ( ) )
{
2023-02-11 18:05:02 +02:00
if ( ! zone . getTownTypes ( ) . empty ( ) )
2023-05-20 11:46:32 +02:00
subType = * RandomGeneratorUtil : : nextItem ( zone . getTownTypes ( ) , zone . getRand ( ) ) ;
2022-08-09 07:54:32 +02:00
else
2023-05-20 11:46:32 +02:00
subType = * RandomGeneratorUtil : : nextItem ( zone . getDefaultTownTypes ( ) , zone . getRand ( ) ) ; //it is possible to have zone with no towns allowed
2022-08-09 07:54:32 +02:00
}
}
auto townFactory = VLC - > objtypeh - > getHandlerFor ( Obj : : TOWN , subType ) ;
2024-01-01 16:37:48 +02:00
auto * town = dynamic_cast < CGTownInstance * > ( townFactory - > create ( map . mapInstance - > cb , nullptr ) ) ;
2022-08-09 07:54:32 +02:00
town - > ID = Obj : : TOWN ;
town - > tempOwner = player ;
if ( hasFort )
town - > builtBuildings . insert ( BuildingID : : FORT ) ;
town - > builtBuildings . insert ( BuildingID : : DEFAULT ) ;
for ( auto spell : VLC - > spellh - > objects ) //add all regular spells to town
{
if ( ! spell - > isSpecial ( ) & & ! spell - > isCreatureAbility ( ) )
town - > possibleSpells . push_back ( spell - > id ) ;
}
if ( totalTowns < = 0 )
{
//FIXME: discovered bug with small zones - getPos is close to map boarder and we have outOfMap exception
//register MAIN town of zone
2023-04-09 17:26:32 +02:00
map . registerZone ( town - > getFaction ( ) ) ;
2022-08-09 07:54:32 +02:00
//first town in zone goes in the middle
placeMainTown ( manager , * town ) ;
}
else
2023-07-07 20:17:20 +02:00
{
manager . addRequiredObject ( RequiredObjectInfo ( town , 0 , true ) ) ;
}
2022-08-09 07:54:32 +02:00
totalTowns + + ;
}
}
2023-11-08 16:13:08 +02:00
FactionID TownPlacer : : getRandomTownType ( bool matchUndergroundType )
2022-08-09 07:54:32 +02:00
{
2023-02-11 18:05:02 +02:00
auto townTypesAllowed = ( ! zone . getTownTypes ( ) . empty ( ) ? zone . getTownTypes ( ) : zone . getDefaultTownTypes ( ) ) ;
2022-08-09 07:54:32 +02:00
if ( matchUndergroundType )
{
2023-04-09 17:26:32 +02:00
std : : set < FactionID > townTypesVerify ;
for ( auto factionIdx : townTypesAllowed )
2022-08-09 07:54:32 +02:00
{
bool preferUnderground = ( * VLC - > townh ) [ factionIdx ] - > preferUndergroundPlacement ;
if ( zone . isUnderground ( ) ? preferUnderground : ! preferUnderground )
{
townTypesVerify . insert ( factionIdx ) ;
}
}
if ( ! townTypesVerify . empty ( ) )
townTypesAllowed = townTypesVerify ;
}
2023-05-20 11:46:32 +02:00
return * RandomGeneratorUtil : : nextItem ( townTypesAllowed , zone . getRand ( ) ) ;
2022-08-09 07:54:32 +02:00
}
int TownPlacer : : getTotalTowns ( ) const
{
return totalTowns ;
}
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_END