2023-07-11 14:16:02 +02:00
/*
* HeroPoolProcessor . 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 "HeroPoolProcessor.h"
# include "CGameHandler.h"
# include "../lib/CHeroHandler.h"
# include "../lib/CPlayerState.h"
# include "../lib/GameSettings.h"
# include "../lib/NetPacks.h"
# include "../lib/StartInfo.h"
# include "../lib/mapObjects/CGTownInstance.h"
# include "../lib/gameState/CGameState.h"
# include "../lib/gameState/TavernHeroesPool.h"
2023-07-11 22:08:30 +02:00
# include "../lib/gameState/TavernSlot.h"
2023-07-11 14:16:02 +02:00
2023-07-11 17:49:19 +02:00
HeroPoolProcessor : : HeroPoolProcessor ( )
: gameHandler ( nullptr )
{
}
2023-07-11 14:16:02 +02:00
2023-07-11 17:49:19 +02:00
HeroPoolProcessor : : HeroPoolProcessor ( CGameHandler * gameHandler )
: gameHandler ( gameHandler )
2023-07-11 14:16:02 +02:00
{
}
2023-07-11 22:37:17 +02:00
bool HeroPoolProcessor : : playerEndedTurn ( const PlayerColor & player )
{
// our player is acting right now and have not ended turn
if ( player = = gameHandler - > gameState ( ) - > currentPlayer )
return false ;
auto turnOrder = gameHandler - > generatePlayerTurnOrder ( ) ;
for ( auto const & entry : turnOrder )
{
// our player is yet to start turn
if ( entry = = gameHandler - > gameState ( ) - > currentPlayer )
return false ;
// our player have finished turn
if ( entry = = player )
return true ;
}
assert ( false ) ;
return false ;
}
2023-07-11 22:08:30 +02:00
TavernHeroSlot HeroPoolProcessor : : selectSlotForRole ( const PlayerColor & player , TavernSlotRole roleID )
{
2023-07-12 11:29:05 +02:00
const auto & heroesPool = gameHandler - > gameState ( ) - > heroesPool ;
2023-07-11 22:08:30 +02:00
2023-07-12 11:29:05 +02:00
const auto & heroes = heroesPool - > getHeroesFor ( player ) ;
2023-07-11 22:08:30 +02:00
// if tavern has empty slot - use it
if ( heroes . size ( ) = = 0 )
return TavernHeroSlot : : NATIVE ;
if ( heroes . size ( ) = = 1 )
return TavernHeroSlot : : RANDOM ;
// try to find "better" slot to overwrite
// we want to avoid overwriting retreated heroes when tavern still has slot with random hero
// as well as avoid overwriting surrendered heroes if we can overwrite retreated hero
2023-07-12 11:29:05 +02:00
auto roleLeft = heroesPool - > getSlotRole ( HeroTypeID ( heroes [ 0 ] - > subID ) ) ;
auto roleRight = heroesPool - > getSlotRole ( HeroTypeID ( heroes [ 1 ] - > subID ) ) ;
2023-07-11 22:08:30 +02:00
if ( roleLeft > roleRight )
return TavernHeroSlot : : RANDOM ;
if ( roleLeft < roleRight )
return TavernHeroSlot : : NATIVE ;
// both slots are equal in "value", so select randomly
if ( getRandomGenerator ( player ) . nextInt ( 100 ) > 50 )
return TavernHeroSlot : : RANDOM ;
else
return TavernHeroSlot : : NATIVE ;
}
2023-07-11 14:16:02 +02:00
void HeroPoolProcessor : : onHeroSurrendered ( const PlayerColor & color , const CGHeroInstance * hero )
{
SetAvailableHero sah ;
2023-07-11 22:37:17 +02:00
if ( playerEndedTurn ( color ) )
sah . roleID = TavernSlotRole : : SURRENDERED_TODAY ;
else
sah . roleID = TavernSlotRole : : SURRENDERED ;
sah . slotID = selectSlotForRole ( color , sah . roleID ) ;
2023-07-11 14:16:02 +02:00
sah . player = color ;
sah . hid = hero - > subID ;
gameHandler - > sendAndApply ( & sah ) ;
}
void HeroPoolProcessor : : onHeroEscaped ( const PlayerColor & color , const CGHeroInstance * hero )
{
SetAvailableHero sah ;
2023-07-11 22:37:17 +02:00
if ( playerEndedTurn ( color ) )
sah . roleID = TavernSlotRole : : RETREATED_TODAY ;
else
sah . roleID = TavernSlotRole : : RETREATED ;
sah . slotID = selectSlotForRole ( color , sah . roleID ) ;
2023-07-11 14:16:02 +02:00
sah . player = color ;
sah . hid = hero - > subID ;
2023-08-07 18:13:02 +02:00
sah . army . clearSlots ( ) ;
2023-07-12 11:29:05 +02:00
sah . army . setCreature ( SlotID ( 0 ) , hero - > type - > initialArmy . at ( 0 ) . creature , 1 ) ;
2023-07-11 14:16:02 +02:00
gameHandler - > sendAndApply ( & sah ) ;
}
void HeroPoolProcessor : : clearHeroFromSlot ( const PlayerColor & color , TavernHeroSlot slot )
{
SetAvailableHero sah ;
sah . player = color ;
2023-07-11 22:08:30 +02:00
sah . roleID = TavernSlotRole : : NONE ;
sah . slotID = slot ;
2023-07-11 14:16:02 +02:00
sah . hid = HeroTypeID : : NONE ;
gameHandler - > sendAndApply ( & sah ) ;
}
2023-07-11 16:51:14 +02:00
void HeroPoolProcessor : : selectNewHeroForSlot ( const PlayerColor & color , TavernHeroSlot slot , bool needNativeHero , bool giveArmy )
2023-07-11 14:16:02 +02:00
{
SetAvailableHero sah ;
sah . player = color ;
2023-07-11 22:08:30 +02:00
sah . slotID = slot ;
2023-07-11 14:16:02 +02:00
2023-07-12 11:29:05 +02:00
CGHeroInstance * newHero = pickHeroFor ( needNativeHero , color ) ;
2023-07-11 14:16:02 +02:00
2023-07-12 11:29:05 +02:00
if ( newHero )
2023-07-11 14:16:02 +02:00
{
2023-07-12 11:29:05 +02:00
sah . hid = newHero - > subID ;
2023-07-11 16:51:14 +02:00
if ( giveArmy )
{
2023-07-11 22:08:30 +02:00
sah . roleID = TavernSlotRole : : FULL_ARMY ;
2023-07-12 11:29:05 +02:00
newHero - > initArmy ( getRandomGenerator ( color ) , & sah . army ) ;
2023-07-11 16:51:14 +02:00
}
else
{
2023-07-11 22:08:30 +02:00
sah . roleID = TavernSlotRole : : SINGLE_UNIT ;
2023-08-07 18:13:02 +02:00
sah . army . clearSlots ( ) ;
2023-07-12 11:29:05 +02:00
sah . army . setCreature ( SlotID ( 0 ) , newHero - > type - > initialArmy [ 0 ] . creature , 1 ) ;
2023-07-11 16:51:14 +02:00
}
2023-07-11 14:16:02 +02:00
}
else
{
sah . hid = - 1 ;
}
gameHandler - > sendAndApply ( & sah ) ;
}
void HeroPoolProcessor : : onNewWeek ( const PlayerColor & color )
{
2023-07-12 11:29:05 +02:00
const auto & heroesPool = gameHandler - > gameState ( ) - > heroesPool ;
const auto & heroes = heroesPool - > getHeroesFor ( color ) ;
2023-07-11 22:37:17 +02:00
2023-07-12 11:29:05 +02:00
const auto nativeSlotRole = heroes . size ( ) < 1 ? TavernSlotRole : : NONE : heroesPool - > getSlotRole ( heroes [ 0 ] - > type - > getId ( ) ) ;
const auto randomSlotRole = heroes . size ( ) < 2 ? TavernSlotRole : : NONE : heroesPool - > getSlotRole ( heroes [ 1 ] - > type - > getId ( ) ) ;
2023-07-11 22:37:17 +02:00
bool resetNativeSlot = nativeSlotRole ! = TavernSlotRole : : RETREATED_TODAY & & nativeSlotRole ! = TavernSlotRole : : SURRENDERED_TODAY ;
bool resetRandomSlot = randomSlotRole ! = TavernSlotRole : : RETREATED_TODAY & & randomSlotRole ! = TavernSlotRole : : SURRENDERED_TODAY ;
if ( resetNativeSlot )
clearHeroFromSlot ( color , TavernHeroSlot : : NATIVE ) ;
if ( resetRandomSlot )
clearHeroFromSlot ( color , TavernHeroSlot : : RANDOM ) ;
if ( resetNativeSlot )
selectNewHeroForSlot ( color , TavernHeroSlot : : NATIVE , true , true ) ;
if ( resetRandomSlot )
selectNewHeroForSlot ( color , TavernHeroSlot : : RANDOM , false , true ) ;
2023-07-11 14:16:02 +02:00
}
2023-07-12 11:29:05 +02:00
bool HeroPoolProcessor : : hireHero ( const ObjectInstanceID & objectID , const HeroTypeID & heroToRecruit , const PlayerColor & player )
2023-07-11 14:16:02 +02:00
{
const PlayerState * playerState = gameHandler - > getPlayerState ( player ) ;
2023-07-12 11:29:05 +02:00
const CGObjectInstance * mapObject = gameHandler - > getObj ( objectID ) ;
const CGTownInstance * town = gameHandler - > getTown ( objectID ) ;
if ( ! mapObject & & gameHandler - > complain ( " Invalid map object! " ) )
return false ;
if ( ! playerState & & gameHandler - > complain ( " Invalid player! " ) )
return false ;
2023-07-11 14:16:02 +02:00
if ( playerState - > resources [ EGameResID : : GOLD ] < GameConstants : : HERO_GOLD_COST & & gameHandler - > complain ( " Not enough gold for buying hero! " ) )
return false ;
if ( gameHandler - > getHeroCount ( player , false ) > = VLC - > settings ( ) - > getInteger ( EGameSettings : : HEROES_PER_PLAYER_ON_MAP_CAP ) & & gameHandler - > complain ( " Cannot hire hero, too many wandering heroes already! " ) )
return false ;
if ( gameHandler - > getHeroCount ( player , true ) > = VLC - > settings ( ) - > getInteger ( EGameSettings : : HEROES_PER_PLAYER_TOTAL_CAP ) & & gameHandler - > complain ( " Cannot hire hero, too many heroes garrizoned and wandering already! " ) )
return false ;
2023-07-11 16:51:14 +02:00
if ( town ) //tavern in town
2023-07-11 14:16:02 +02:00
{
2023-07-12 11:29:05 +02:00
if ( gameHandler - > getPlayerRelations ( mapObject - > tempOwner , player ) = = PlayerRelations : : ENEMIES & & gameHandler - > complain ( " Can't buy hero in enemy town! " ) )
return false ;
2023-07-11 16:51:14 +02:00
if ( ! town - > hasBuilt ( BuildingID : : TAVERN ) & & gameHandler - > complain ( " No tavern! " ) )
2023-07-11 14:16:02 +02:00
return false ;
2023-07-11 16:51:14 +02:00
if ( town - > visitingHero & & gameHandler - > complain ( " There is visiting hero - no place! " ) )
2023-07-11 14:16:02 +02:00
return false ;
}
2023-07-12 11:29:05 +02:00
if ( mapObject - > ID = = Obj : : TAVERN )
2023-07-11 14:16:02 +02:00
{
2023-07-12 11:29:05 +02:00
if ( gameHandler - > getTile ( mapObject - > visitablePos ( ) ) - > visitableObjects . back ( ) ! = mapObject & & gameHandler - > complain ( " Tavern entry must be unoccupied! " ) )
2023-07-11 14:16:02 +02:00
return false ;
}
2023-07-12 11:29:05 +02:00
auto recruitableHeroes = gameHandler - > gameState ( ) - > heroesPool - > getHeroesFor ( player ) ;
2023-07-11 14:16:02 +02:00
2023-07-11 16:51:14 +02:00
const CGHeroInstance * recruitedHero = nullptr ;
2023-07-11 14:16:02 +02:00
for ( const auto & hero : recruitableHeroes )
{
2023-07-11 16:51:14 +02:00
if ( hero - > subID = = heroToRecruit )
2023-07-11 14:16:02 +02:00
recruitedHero = hero ;
}
2023-07-11 16:51:14 +02:00
if ( ! recruitedHero )
2023-07-11 14:16:02 +02:00
{
2023-07-11 16:51:14 +02:00
gameHandler - > complain ( " Hero is not available for hiring! " ) ;
2023-07-11 14:16:02 +02:00
return false ;
}
2023-08-01 18:51:33 +02:00
const auto targetPos = mapObject - > visitablePos ( ) ;
2023-07-11 14:16:02 +02:00
HeroRecruited hr ;
2023-07-12 11:29:05 +02:00
hr . tid = mapObject - > id ;
2023-07-11 14:16:02 +02:00
hr . hid = recruitedHero - > subID ;
hr . player = player ;
2023-08-01 18:51:33 +02:00
hr . tile = recruitedHero - > convertFromVisitablePos ( targetPos ) ;
if ( gameHandler - > getTile ( hr . tile ) - > isWater ( ) & & ! recruitedHero - > boat )
2023-07-11 14:16:02 +02:00
{
//Create a new boat for hero
2023-08-01 18:51:33 +02:00
gameHandler - > createObject ( targetPos , Obj : : BOAT , recruitedHero - > getBoatType ( ) . getNum ( ) ) ;
2023-07-11 14:16:02 +02:00
2023-08-01 18:51:33 +02:00
hr . boatId = gameHandler - > getTopObj ( targetPos ) - > id ;
2023-07-11 14:16:02 +02:00
}
2023-07-11 16:51:14 +02:00
2023-07-12 11:29:05 +02:00
// apply netpack -> this will remove hired hero from pool
2023-07-11 14:16:02 +02:00
gameHandler - > sendAndApply ( & hr ) ;
2023-07-11 16:51:14 +02:00
if ( recruitableHeroes [ 0 ] = = recruitedHero )
selectNewHeroForSlot ( player , TavernHeroSlot : : NATIVE , false , false ) ;
2023-07-11 14:16:02 +02:00
else
2023-07-11 16:51:14 +02:00
selectNewHeroForSlot ( player , TavernHeroSlot : : RANDOM , false , false ) ;
2023-07-11 14:16:02 +02:00
gameHandler - > giveResource ( player , EGameResID : : GOLD , - GameConstants : : HERO_GOLD_COST ) ;
if ( town )
{
gameHandler - > visitCastleObjects ( town , recruitedHero ) ;
gameHandler - > giveSpells ( town , recruitedHero ) ;
}
return true ;
}
2023-07-11 16:51:14 +02:00
2023-07-11 19:29:44 +02:00
std : : vector < const CHeroClass * > HeroPoolProcessor : : findAvailableClassesFor ( const PlayerColor & player ) const
2023-07-11 16:51:14 +02:00
{
2023-07-11 19:29:44 +02:00
std : : vector < const CHeroClass * > result ;
2023-07-11 16:51:14 +02:00
2023-07-12 11:29:05 +02:00
const auto & heroesPool = gameHandler - > gameState ( ) - > heroesPool ;
2023-07-11 17:49:19 +02:00
FactionID factionID = gameHandler - > getPlayerSettings ( player ) - > castle ;
2023-07-11 16:51:14 +02:00
2023-07-12 11:29:05 +02:00
for ( auto & elem : heroesPool - > unusedHeroesFromPool ( ) )
2023-07-11 16:51:14 +02:00
{
2023-07-11 19:29:44 +02:00
if ( vstd : : contains ( result , elem . second - > type - > heroClass ) )
continue ;
2023-07-12 11:29:05 +02:00
bool heroAvailable = heroesPool - > isHeroAvailableFor ( elem . first , player ) ;
2023-07-11 17:49:19 +02:00
bool heroClassBanned = elem . second - > type - > heroClass - > selectionProbability [ factionID ] = = 0 ;
2023-07-11 16:51:14 +02:00
2023-07-11 17:49:19 +02:00
if ( heroAvailable & & ! heroClassBanned )
2023-07-11 19:29:44 +02:00
result . push_back ( elem . second - > type - > heroClass ) ;
2023-07-11 17:49:19 +02:00
}
2023-07-11 16:51:14 +02:00
2023-07-11 17:49:19 +02:00
return result ;
}
2023-07-11 16:51:14 +02:00
2023-07-11 19:29:44 +02:00
std : : vector < CGHeroInstance * > HeroPoolProcessor : : findAvailableHeroesFor ( const PlayerColor & player , const CHeroClass * heroClass ) const
2023-07-11 17:49:19 +02:00
{
2023-07-11 19:29:44 +02:00
std : : vector < CGHeroInstance * > result ;
2023-07-11 16:51:14 +02:00
2023-07-12 11:29:05 +02:00
const auto & heroesPool = gameHandler - > gameState ( ) - > heroesPool ;
2023-07-11 16:51:14 +02:00
2023-07-12 11:29:05 +02:00
for ( auto & elem : heroesPool - > unusedHeroesFromPool ( ) )
2023-07-11 16:51:14 +02:00
{
2023-07-11 19:29:44 +02:00
assert ( ! vstd : : contains ( result , elem . second ) ) ;
2023-07-12 11:29:05 +02:00
bool heroAvailable = heroesPool - > isHeroAvailableFor ( elem . first , player ) ;
2023-07-11 17:49:19 +02:00
bool heroClassMatches = elem . second - > type - > heroClass = = heroClass ;
2023-07-11 16:51:14 +02:00
2023-07-11 17:49:19 +02:00
if ( heroAvailable & & heroClassMatches )
2023-07-11 19:29:44 +02:00
result . push_back ( elem . second ) ;
2023-07-11 16:51:14 +02:00
}
2023-07-11 17:49:19 +02:00
return result ;
}
2023-07-11 18:23:14 +02:00
const CHeroClass * HeroPoolProcessor : : pickClassFor ( bool isNative , const PlayerColor & player )
2023-07-11 17:49:19 +02:00
{
if ( player > = PlayerColor : : PLAYER_LIMIT )
{
logGlobal - > error ( " Cannot pick hero for player %d. Wrong owner! " , player . getStr ( ) ) ;
return nullptr ;
}
2023-07-11 18:23:14 +02:00
FactionID factionID = gameHandler - > getPlayerSettings ( player ) - > castle ;
2023-07-12 11:29:05 +02:00
const auto & heroesPool = gameHandler - > gameState ( ) - > heroesPool ;
const auto & currentTavern = heroesPool - > getHeroesFor ( player ) ;
2023-07-11 17:49:19 +02:00
2023-07-11 19:29:44 +02:00
std : : vector < const CHeroClass * > potentialClasses = findAvailableClassesFor ( player ) ;
std : : vector < const CHeroClass * > possibleClasses ;
2023-07-11 17:49:19 +02:00
if ( potentialClasses . empty ( ) )
2023-07-11 16:51:14 +02:00
{
logGlobal - > error ( " There are no heroes available for player %s! " , player . getStr ( ) ) ;
return nullptr ;
}
2023-07-11 17:49:19 +02:00
for ( const auto & heroClass : potentialClasses )
{
if ( isNative & & heroClass - > faction ! = factionID )
continue ;
bool hasSameClass = vstd : : contains_if ( currentTavern , [ & ] ( const CGHeroInstance * hero ) {
return hero - > type - > heroClass = = heroClass ;
} ) ;
if ( hasSameClass )
continue ;
2023-07-11 19:29:44 +02:00
possibleClasses . push_back ( heroClass ) ;
2023-07-11 17:49:19 +02:00
}
if ( possibleClasses . empty ( ) )
{
logGlobal - > error ( " Cannot pick native hero for %s. Picking any... " , player . getStr ( ) ) ;
possibleClasses = potentialClasses ;
}
int totalWeight = 0 ;
for ( const auto & heroClass : possibleClasses )
totalWeight + = heroClass - > selectionProbability . at ( factionID ) ;
2023-07-11 18:23:14 +02:00
int roll = getRandomGenerator ( player ) . nextInt ( totalWeight - 1 ) ;
2023-07-11 19:29:44 +02:00
2023-07-11 17:49:19 +02:00
for ( const auto & heroClass : possibleClasses )
2023-07-11 16:51:14 +02:00
{
2023-07-11 17:49:19 +02:00
roll - = heroClass - > selectionProbability . at ( factionID ) ;
2023-07-11 16:51:14 +02:00
if ( roll < 0 )
2023-07-11 17:49:19 +02:00
return heroClass ;
2023-07-11 16:51:14 +02:00
}
2023-07-11 17:49:19 +02:00
return * possibleClasses . rbegin ( ) ;
}
2023-07-11 18:23:14 +02:00
CGHeroInstance * HeroPoolProcessor : : pickHeroFor ( bool isNative , const PlayerColor & player )
2023-07-11 17:49:19 +02:00
{
2023-07-11 18:23:14 +02:00
const CHeroClass * heroClass = pickClassFor ( isNative , player ) ;
2023-07-11 17:49:19 +02:00
if ( ! heroClass )
return nullptr ;
2023-07-11 19:29:44 +02:00
std : : vector < CGHeroInstance * > possibleHeroes = findAvailableHeroesFor ( player , heroClass ) ;
2023-07-11 17:49:19 +02:00
assert ( ! possibleHeroes . empty ( ) ) ;
if ( possibleHeroes . empty ( ) )
return nullptr ;
2023-07-11 18:23:14 +02:00
return * RandomGeneratorUtil : : nextItem ( possibleHeroes , getRandomGenerator ( player ) ) ;
}
CRandomGenerator & HeroPoolProcessor : : getRandomGenerator ( const PlayerColor & player )
{
if ( playerSeed . count ( player ) = = 0 )
2023-07-11 19:29:44 +02:00
{
int seed = gameHandler - > getRandomGenerator ( ) . nextInt ( ) ;
playerSeed . emplace ( player , std : : make_unique < CRandomGenerator > ( seed ) ) ;
}
2023-07-11 18:23:14 +02:00
2023-07-11 19:29:44 +02:00
return * playerSeed . at ( player ) ;
2023-07-11 16:51:14 +02:00
}