2023-07-11 15:16:02 +03: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 23:08:30 +03:00
#include "../lib/gameState/TavernSlot.h"
2023-07-11 15:16:02 +03:00
2023-07-11 18:49:19 +03:00
HeroPoolProcessor :: HeroPoolProcessor ()
: gameHandler ( nullptr )
{
}
2023-07-11 15:16:02 +03:00
2023-07-11 18:49:19 +03:00
HeroPoolProcessor :: HeroPoolProcessor ( CGameHandler * gameHandler )
: gameHandler ( gameHandler )
2023-07-11 15:16:02 +03:00
{
}
2023-07-11 23:37:17 +03: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 23:08:30 +03:00
TavernHeroSlot HeroPoolProcessor :: selectSlotForRole ( const PlayerColor & player , TavernSlotRole roleID )
{
const auto & hpool = gameHandler -> gameState () -> hpool ;
const auto & heroes = hpool -> getHeroesFor ( player );
// 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
auto roleLeft = hpool -> getSlotRole ( HeroTypeID ( heroes [ 0 ] -> subID ));
auto roleRight = hpool -> getSlotRole ( HeroTypeID ( heroes [ 1 ] -> subID ));
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 15:16:02 +03:00
void HeroPoolProcessor :: onHeroSurrendered ( const PlayerColor & color , const CGHeroInstance * hero )
{
SetAvailableHero sah ;
2023-07-11 23:37:17 +03:00
if ( playerEndedTurn ( color ))
sah . roleID = TavernSlotRole :: SURRENDERED_TODAY ;
else
sah . roleID = TavernSlotRole :: SURRENDERED ;
sah . slotID = selectSlotForRole ( color , sah . roleID );
2023-07-11 15:16:02 +03:00
sah . player = color ;
sah . hid = hero -> subID ;
sah . army . clear ();
sah . army . setCreature ( SlotID ( 0 ), hero -> type -> initialArmy . at ( 0 ). creature , 1 );
gameHandler -> sendAndApply ( & sah );
}
void HeroPoolProcessor :: onHeroEscaped ( const PlayerColor & color , const CGHeroInstance * hero )
{
SetAvailableHero sah ;
2023-07-11 23:37:17 +03:00
if ( playerEndedTurn ( color ))
sah . roleID = TavernSlotRole :: RETREATED_TODAY ;
else
sah . roleID = TavernSlotRole :: RETREATED ;
sah . slotID = selectSlotForRole ( color , sah . roleID );
2023-07-11 15:16:02 +03:00
sah . player = color ;
sah . hid = hero -> subID ;
gameHandler -> sendAndApply ( & sah );
}
void HeroPoolProcessor :: clearHeroFromSlot ( const PlayerColor & color , TavernHeroSlot slot )
{
SetAvailableHero sah ;
sah . player = color ;
2023-07-11 23:08:30 +03:00
sah . roleID = TavernSlotRole :: NONE ;
sah . slotID = slot ;
2023-07-11 15:16:02 +03:00
sah . hid = HeroTypeID :: NONE ;
gameHandler -> sendAndApply ( & sah );
}
2023-07-11 17:51:14 +03:00
void HeroPoolProcessor :: selectNewHeroForSlot ( const PlayerColor & color , TavernHeroSlot slot , bool needNativeHero , bool giveArmy )
2023-07-11 15:16:02 +03:00
{
SetAvailableHero sah ;
sah . player = color ;
2023-07-11 23:08:30 +03:00
sah . slotID = slot ;
2023-07-11 15:16:02 +03:00
//first hero - native if possible, second hero -> any other class
2023-07-11 19:23:14 +03:00
CGHeroInstance * h = pickHeroFor ( needNativeHero , color );
2023-07-11 15:16:02 +03:00
if ( h )
{
sah . hid = h -> subID ;
2023-07-11 17:51:14 +03:00
if ( giveArmy )
{
2023-07-11 23:08:30 +03:00
sah . roleID = TavernSlotRole :: FULL_ARMY ;
2023-07-11 19:23:14 +03:00
h -> initArmy ( getRandomGenerator ( color ), & sah . army );
2023-07-11 17:51:14 +03:00
}
else
{
2023-07-11 23:08:30 +03:00
sah . roleID = TavernSlotRole :: SINGLE_UNIT ;
2023-07-11 17:51:14 +03:00
sah . army . clear ();
sah . army . setCreature ( SlotID ( 0 ), h -> type -> initialArmy [ 0 ]. creature , 1 );
}
2023-07-11 15:16:02 +03:00
}
else
{
sah . hid = - 1 ;
}
gameHandler -> sendAndApply ( & sah );
}
void HeroPoolProcessor :: onNewWeek ( const PlayerColor & color )
{
2023-07-11 23:37:17 +03:00
const auto & hpool = gameHandler -> gameState () -> hpool ;
const auto & heroes = hpool -> getHeroesFor ( color );
const auto nativeSlotRole = heroes . size () < 1 ? TavernSlotRole :: NONE : hpool -> getSlotRole ( heroes [ 0 ] -> type -> getId ());
const auto randomSlotRole = heroes . size () < 2 ? TavernSlotRole :: NONE : hpool -> getSlotRole ( heroes [ 1 ] -> type -> getId ());
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 15:16:02 +03:00
}
bool HeroPoolProcessor :: hireHero ( const CGObjectInstance * obj , const HeroTypeID & heroToRecruit , const PlayerColor & player )
{
const PlayerState * playerState = gameHandler -> getPlayerState ( player );
const CGTownInstance * town = gameHandler -> getTown ( obj -> id );
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 17:51:14 +03:00
if ( town ) //tavern in town
2023-07-11 15:16:02 +03:00
{
2023-07-11 17:51:14 +03:00
if ( ! town -> hasBuilt ( BuildingID :: TAVERN ) && gameHandler -> complain ( "No tavern!" ))
2023-07-11 15:16:02 +03:00
return false ;
2023-07-11 17:51:14 +03:00
if ( town -> visitingHero && gameHandler -> complain ( "There is visiting hero - no place!" ))
2023-07-11 15:16:02 +03:00
return false ;
}
2023-07-11 17:51:14 +03:00
if ( obj -> ID == Obj :: TAVERN )
2023-07-11 15:16:02 +03:00
{
2023-07-11 17:51:14 +03:00
if ( gameHandler -> getTile ( obj -> visitablePos ()) -> visitableObjects . back () != obj && gameHandler -> complain ( "Tavern entry must be unoccupied!" ))
2023-07-11 15:16:02 +03:00
return false ;
}
auto recruitableHeroes = gameHandler -> gameState () -> hpool -> getHeroesFor ( player );
2023-07-11 17:51:14 +03:00
const CGHeroInstance * recruitedHero = nullptr ;
2023-07-11 15:16:02 +03:00
for ( const auto & hero : recruitableHeroes )
{
2023-07-11 17:51:14 +03:00
if ( hero -> subID == heroToRecruit )
2023-07-11 15:16:02 +03:00
recruitedHero = hero ;
}
2023-07-11 17:51:14 +03:00
if ( ! recruitedHero )
2023-07-11 15:16:02 +03:00
{
2023-07-11 17:51:14 +03:00
gameHandler -> complain ( "Hero is not available for hiring!" );
2023-07-11 15:16:02 +03:00
return false ;
}
HeroRecruited hr ;
hr . tid = obj -> id ;
hr . hid = recruitedHero -> subID ;
hr . player = player ;
hr . tile = recruitedHero -> convertFromVisitablePos ( obj -> visitablePos ());
2023-07-11 17:51:14 +03:00
if ( gameHandler -> getTile ( hr . tile ) -> isWater ())
2023-07-11 15:16:02 +03:00
{
//Create a new boat for hero
gameHandler -> createObject ( obj -> visitablePos (), Obj :: BOAT , recruitedHero -> getBoatType (). getNum ());
hr . boatId = gameHandler -> getTopObj ( hr . tile ) -> id ;
}
2023-07-11 17:51:14 +03:00
// apply netpack -> this will remove hired hero from tavern slot
2023-07-11 15:16:02 +03:00
gameHandler -> sendAndApply ( & hr );
2023-07-11 17:51:14 +03:00
if ( recruitableHeroes [ 0 ] == recruitedHero )
selectNewHeroForSlot ( player , TavernHeroSlot :: NATIVE , false , false );
2023-07-11 15:16:02 +03:00
else
2023-07-11 17:51:14 +03:00
selectNewHeroForSlot ( player , TavernHeroSlot :: RANDOM , false , false );
2023-07-11 15:16:02 +03: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 17:51:14 +03:00
2023-07-11 20:29:44 +03:00
std :: vector < const CHeroClass *> HeroPoolProcessor :: findAvailableClassesFor ( const PlayerColor & player ) const
2023-07-11 18:49:19 +03:00
{
2023-07-11 20:29:44 +03:00
std :: vector < const CHeroClass *> result ;
2023-07-11 18:49:19 +03:00
const auto & hpool = gameHandler -> gameState () -> hpool ;
FactionID factionID = gameHandler -> getPlayerSettings ( player ) -> castle ;
for ( auto & elem : hpool -> unusedHeroesFromPool ())
{
2023-07-11 20:29:44 +03:00
if ( vstd :: contains ( result , elem . second -> type -> heroClass ))
continue ;
2023-07-11 18:49:19 +03:00
bool heroAvailable = hpool -> isHeroAvailableFor ( elem . first , player );
bool heroClassBanned = elem . second -> type -> heroClass -> selectionProbability [ factionID ] == 0 ;
if ( heroAvailable && ! heroClassBanned )
2023-07-11 20:29:44 +03:00
result . push_back ( elem . second -> type -> heroClass );
2023-07-11 18:49:19 +03:00
}
return result ;
}
2023-07-11 20:29:44 +03:00
std :: vector < CGHeroInstance *> HeroPoolProcessor :: findAvailableHeroesFor ( const PlayerColor & player , const CHeroClass * heroClass ) const
2023-07-11 18:49:19 +03:00
{
2023-07-11 20:29:44 +03:00
std :: vector < CGHeroInstance *> result ;
2023-07-11 18:49:19 +03:00
const auto & hpool = gameHandler -> gameState () -> hpool ;
for ( auto & elem : hpool -> unusedHeroesFromPool ())
{
2023-07-11 20:29:44 +03:00
assert ( ! vstd :: contains ( result , elem . second ));
2023-07-11 18:49:19 +03:00
bool heroAvailable = hpool -> isHeroAvailableFor ( elem . first , player );
bool heroClassMatches = elem . second -> type -> heroClass == heroClass ;
if ( heroAvailable && heroClassMatches )
2023-07-11 20:29:44 +03:00
result . push_back ( elem . second );
2023-07-11 18:49:19 +03:00
}
return result ;
}
2023-07-11 19:23:14 +03:00
const CHeroClass * HeroPoolProcessor :: pickClassFor ( bool isNative , const PlayerColor & player )
2023-07-11 17:51:14 +03:00
{
if ( player >= PlayerColor :: PLAYER_LIMIT )
{
logGlobal -> error ( "Cannot pick hero for player %d. Wrong owner!" , player . getStr ());
return nullptr ;
}
2023-07-11 19:23:14 +03:00
FactionID factionID = gameHandler -> getPlayerSettings ( player ) -> castle ;
2023-07-11 17:51:14 +03:00
const auto & hpool = gameHandler -> gameState () -> hpool ;
2023-07-11 18:49:19 +03:00
const auto & currentTavern = hpool -> getHeroesFor ( player );
2023-07-11 17:51:14 +03:00
2023-07-11 20:29:44 +03:00
std :: vector < const CHeroClass *> potentialClasses = findAvailableClassesFor ( player );
std :: vector < const CHeroClass *> possibleClasses ;
2023-07-11 17:51:14 +03:00
2023-07-11 18:49:19 +03:00
if ( potentialClasses . empty ())
2023-07-11 17:51:14 +03:00
{
logGlobal -> error ( "There are no heroes available for player %s!" , player . getStr ());
return nullptr ;
}
2023-07-11 18:49:19 +03:00
for ( const auto & heroClass : potentialClasses )
2023-07-11 17:51:14 +03:00
{
2023-07-11 18:49:19 +03:00
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 20:29:44 +03:00
possibleClasses . push_back ( heroClass );
2023-07-11 17:51:14 +03:00
}
2023-07-11 18:49:19 +03: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 19:23:14 +03:00
int roll = getRandomGenerator ( player ). nextInt ( totalWeight - 1 );
2023-07-11 20:29:44 +03:00
2023-07-11 18:49:19 +03:00
for ( const auto & heroClass : possibleClasses )
{
roll -= heroClass -> selectionProbability . at ( factionID );
if ( roll < 0 )
return heroClass ;
}
return * possibleClasses . rbegin ();
}
2023-07-11 19:23:14 +03:00
CGHeroInstance * HeroPoolProcessor :: pickHeroFor ( bool isNative , const PlayerColor & player )
2023-07-11 18:49:19 +03:00
{
2023-07-11 19:23:14 +03:00
const CHeroClass * heroClass = pickClassFor ( isNative , player );
2023-07-11 18:49:19 +03:00
if ( ! heroClass )
return nullptr ;
2023-07-11 20:29:44 +03:00
std :: vector < CGHeroInstance *> possibleHeroes = findAvailableHeroesFor ( player , heroClass );
2023-07-11 18:49:19 +03:00
assert ( ! possibleHeroes . empty ());
if ( possibleHeroes . empty ())
return nullptr ;
2023-07-11 19:23:14 +03:00
return * RandomGeneratorUtil :: nextItem ( possibleHeroes , getRandomGenerator ( player ));
}
CRandomGenerator & HeroPoolProcessor :: getRandomGenerator ( const PlayerColor & player )
{
if ( playerSeed . count ( player ) == 0 )
2023-07-11 20:29:44 +03:00
{
int seed = gameHandler -> getRandomGenerator (). nextInt ();
playerSeed . emplace ( player , std :: make_unique < CRandomGenerator > ( seed ));
}
2023-07-11 19:23:14 +03:00
2023-07-11 20:29:44 +03:00
return * playerSeed . at ( player );
2023-07-11 17:51:14 +03:00
}