2023-06-23 18:33:47 +02:00
/*
* CGameStateCampaign . 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 "CGameStateCampaign.h"
# include "CGameState.h"
# include "QuestInfo.h"
2023-06-25 21:28:24 +02:00
# include "../campaign/CampaignState.h"
2024-07-21 12:49:40 +02:00
# include "../entities/building/CBuilding.h"
# include "../entities/building/CBuildingHandler.h"
2023-06-23 18:33:47 +02:00
# include "../mapping/CMapEditManager.h"
# include "../mapObjects/CGHeroInstance.h"
2024-01-09 16:43:36 +02:00
# include "../mapObjects/CGTownInstance.h"
2023-11-04 19:25:50 +02:00
# include "../networkPacks/ArtifactLocation.h"
2023-06-23 18:33:47 +02:00
# include "../mapObjectConstructors/AObjectTypeHandler.h"
# include "../mapObjectConstructors/CObjectClassesHandler.h"
# include "../StartInfo.h"
# include "../CHeroHandler.h"
# include "../mapping/CMap.h"
# include "../ArtifactUtils.h"
# include "../CPlayerState.h"
# include "../serializer/CMemorySerializer.h"
2024-06-01 17:28:17 +02:00
# include <vstd/RNG.h>
2023-06-23 18:33:47 +02:00
VCMI_LIB_NAMESPACE_BEGIN
CampaignHeroReplacement : : CampaignHeroReplacement ( CGHeroInstance * hero , const ObjectInstanceID & heroPlaceholderId ) :
hero ( hero ) ,
heroPlaceholderId ( heroPlaceholderId )
{
}
CGameStateCampaign : : CGameStateCampaign ( CGameState * owner ) :
gameState ( owner )
{
2024-01-21 16:48:36 +02:00
assert ( gameState - > scenarioOps - > mode = = EStartMode : : CAMPAIGN ) ;
2023-06-23 18:33:47 +02:00
assert ( gameState - > scenarioOps - > campState ! = nullptr ) ;
}
2023-06-26 00:07:55 +02:00
std : : optional < CampaignBonus > CGameStateCampaign : : currentBonus ( ) const
{
auto campaignState = gameState - > scenarioOps - > campState ;
return campaignState - > getBonus ( * campaignState - > currentScenario ( ) ) ;
}
2023-06-26 15:25:34 +02:00
std : : optional < CampaignScenarioID > CGameStateCampaign : : getHeroesSourceScenario ( ) const
2023-06-23 18:33:47 +02:00
{
auto campaignState = gameState - > scenarioOps - > campState ;
2023-06-26 00:07:55 +02:00
auto bonus = currentBonus ( ) ;
2023-06-23 18:33:47 +02:00
2023-06-26 15:25:34 +02:00
if ( bonus & & bonus - > type = = CampaignBonusType : : HEROES_FROM_PREVIOUS_SCENARIO )
2024-01-16 21:02:39 +02:00
return static_cast < CampaignScenarioID > ( bonus - > info2 ) ;
2023-06-23 18:33:47 +02:00
2023-06-26 15:25:34 +02:00
return campaignState - > lastScenario ( ) ;
2023-06-23 18:33:47 +02:00
}
2024-01-31 19:32:59 +02:00
void CGameStateCampaign : : trimCrossoverHeroesParameters ( const CampaignTravel & travelOptions )
2023-06-23 18:33:47 +02:00
{
// TODO this logic (what should be kept) should be part of CScenarioTravel and be exposed via some clean set of methods
if ( ! travelOptions . whatHeroKeeps . experience )
{
//trimming experience
2024-01-31 19:32:59 +02:00
for ( auto & hero : campaignHeroReplacements )
2023-06-23 18:33:47 +02:00
{
2024-01-31 19:32:59 +02:00
hero . hero - > initExp ( gameState - > getRandomGenerator ( ) ) ;
2023-06-23 18:33:47 +02:00
}
}
if ( ! travelOptions . whatHeroKeeps . primarySkills )
{
//trimming prim skills
2024-01-31 19:32:59 +02:00
for ( auto & hero : campaignHeroReplacements )
2023-06-23 18:33:47 +02:00
{
2023-10-05 15:13:52 +02:00
for ( auto g = PrimarySkill : : BEGIN ; g < PrimarySkill : : END ; + + g )
2023-06-23 18:33:47 +02:00
{
auto sel = Selector : : type ( ) ( BonusType : : PRIMARY_SKILL )
2023-10-21 13:50:42 +02:00
. And ( Selector : : subtype ( ) ( BonusSubtypeID ( g ) ) )
2023-06-23 18:33:47 +02:00
. And ( Selector : : sourceType ( ) ( BonusSource : : HERO_BASE_SKILL ) ) ;
2024-01-31 19:32:59 +02:00
hero . hero - > getLocalBonus ( sel ) - > val = hero . hero - > type - > heroClass - > primarySkillInitial [ g . getNum ( ) ] ;
2023-06-23 18:33:47 +02:00
}
}
}
if ( ! travelOptions . whatHeroKeeps . secondarySkills )
{
//trimming sec skills
2024-01-31 19:32:59 +02:00
for ( auto & hero : campaignHeroReplacements )
2023-06-23 18:33:47 +02:00
{
2024-01-31 19:32:59 +02:00
hero . hero - > secSkills = hero . hero - > type - > secSkillsInit ;
hero . hero - > recreateSecondarySkillsBonuses ( ) ;
2023-06-23 18:33:47 +02:00
}
}
if ( ! travelOptions . whatHeroKeeps . spells )
{
2024-01-31 19:32:59 +02:00
for ( auto & hero : campaignHeroReplacements )
2023-06-23 18:33:47 +02:00
{
2024-01-31 19:32:59 +02:00
hero . hero - > removeSpellbook ( ) ;
2023-06-23 18:33:47 +02:00
}
}
if ( ! travelOptions . whatHeroKeeps . artifacts )
{
//trimming artifacts
2024-01-31 19:32:59 +02:00
for ( auto & hero : campaignHeroReplacements )
2023-06-23 18:33:47 +02:00
{
2024-05-28 16:21:29 +02:00
const auto & checkAndRemoveArtifact = [ & ] ( const ArtifactPosition & artifactPosition ) - > bool
2023-06-23 18:33:47 +02:00
{
2023-06-26 15:25:34 +02:00
if ( artifactPosition = = ArtifactPosition : : SPELLBOOK )
2024-05-28 16:21:29 +02:00
return false ; // do not handle spellbook this way
2023-06-23 18:33:47 +02:00
2024-01-31 19:32:59 +02:00
const ArtSlotInfo * info = hero . hero - > getSlot ( artifactPosition ) ;
2023-06-23 18:33:47 +02:00
if ( ! info )
2024-05-28 16:21:29 +02:00
return false ;
2023-06-23 18:33:47 +02:00
2024-07-16 12:42:25 +02:00
// FIXME: double-check how H3 handles case of transferring components of a combined artifact if entire combined artifact is not transferrable
// For example, what happens if hero has assembled Angelic Alliance, AA is not marked is transferrable, but Sandals can be transferred? Should artifact be disassembled?
if ( info - > locked )
return false ;
2023-06-23 18:33:47 +02:00
// TODO: why would there be nullptr artifacts?
const CArtifactInstance * art = info - > artifact ;
if ( ! art )
2024-05-28 16:21:29 +02:00
return false ;
2023-06-23 18:33:47 +02:00
2024-08-02 17:56:05 +02:00
ArtifactLocation al ( hero . hero - > id , artifactPosition ) ;
2023-06-23 18:33:47 +02:00
bool takeable = travelOptions . artifactsKeptByHero . count ( art - > artType - > getId ( ) ) ;
2024-08-02 17:56:05 +02:00
bool locked = hero . hero - > getSlot ( al . slot ) - > locked ;
2023-06-23 18:33:47 +02:00
2024-08-02 17:56:05 +02:00
if ( ! locked & & takeable )
2024-01-31 19:32:59 +02:00
hero . transferrableArtifacts . push_back ( artifactPosition ) ;
2024-08-02 17:56:05 +02:00
if ( ! locked & & ! takeable )
2024-05-28 16:21:29 +02:00
{
2024-01-31 19:32:59 +02:00
hero . hero - > getArt ( al . slot ) - > removeFrom ( * hero . hero , al . slot ) ;
2024-05-28 16:21:29 +02:00
return true ;
}
return false ;
2023-07-31 21:06:54 +02:00
} ;
// process on copy - removal of artifact will invalidate container
2024-01-31 19:32:59 +02:00
auto artifactsWorn = hero . hero - > artifactsWorn ;
2023-10-05 15:13:52 +02:00
for ( const auto & art : artifactsWorn )
2023-07-31 21:06:54 +02:00
checkAndRemoveArtifact ( art . first ) ;
2024-05-28 16:21:29 +02:00
for ( int slotNumber = 0 ; slotNumber < hero . hero - > artifactsInBackpack . size ( ) ; )
{
if ( checkAndRemoveArtifact ( ArtifactPosition : : BACKPACK_START + slotNumber ) )
continue ; // artifact was removed and backpack slots were shifted -> test this slot again
else
slotNumber + + ; // artifact was kept for transfer -> test next slot
} ;
2023-06-23 18:33:47 +02:00
}
}
//trimming creatures
2024-01-31 19:32:59 +02:00
for ( auto & hero : campaignHeroReplacements )
2023-06-23 18:33:47 +02:00
{
auto shouldSlotBeErased = [ & ] ( const std : : pair < SlotID , CStackInstance * > & j ) - > bool
{
2023-08-19 19:48:28 +02:00
CreatureID crid = j . second - > getCreatureID ( ) ;
2023-06-23 18:33:47 +02:00
return ! travelOptions . monstersKeptByHero . count ( crid ) ;
} ;
2024-01-31 19:32:59 +02:00
auto stacksCopy = hero . hero - > stacks ; //copy of the map, so we can iterate iover it and remove stacks
2023-06-23 18:33:47 +02:00
for ( auto & slotPair : stacksCopy )
if ( shouldSlotBeErased ( slotPair ) )
2024-01-31 19:32:59 +02:00
hero . hero - > eraseStack ( slotPair . first ) ;
2023-06-23 18:33:47 +02:00
}
// Removing short-term bonuses
2024-01-31 19:32:59 +02:00
for ( auto & hero : campaignHeroReplacements )
2023-06-23 18:33:47 +02:00
{
2024-01-31 19:32:59 +02:00
hero . hero - > removeBonusesRecursive ( CSelector ( Bonus : : OneDay )
2023-06-23 18:33:47 +02:00
. Or ( CSelector ( Bonus : : OneWeek ) )
. Or ( CSelector ( Bonus : : NTurns ) )
. Or ( CSelector ( Bonus : : NDays ) )
. Or ( CSelector ( Bonus : : OneBattle ) ) ) ;
}
}
void CGameStateCampaign : : placeCampaignHeroes ( )
{
2023-06-23 20:35:14 +02:00
// place bonus hero
2023-06-26 00:07:55 +02:00
auto campaignState = gameState - > scenarioOps - > campState ;
auto campaignBonus = campaignState - > getBonus ( * campaignState - > currentScenario ( ) ) ;
2023-06-25 20:16:03 +02:00
bool campaignGiveHero = campaignBonus & & campaignBonus - > type = = CampaignBonusType : : HERO ;
2023-06-23 18:33:47 +02:00
2023-06-23 20:35:14 +02:00
if ( campaignGiveHero )
{
auto playerColor = PlayerColor ( campaignBonus - > info1 ) ;
auto it = gameState - > scenarioOps - > playerInfos . find ( playerColor ) ;
if ( it ! = gameState - > scenarioOps - > playerInfos . end ( ) )
2023-06-23 18:33:47 +02:00
{
2023-11-08 16:13:08 +02:00
HeroTypeID heroTypeId = HeroTypeID ( campaignBonus - > info2 ) ;
if ( heroTypeId . getNum ( ) = = 0xffff ) // random bonus hero
2023-06-23 18:33:47 +02:00
{
2023-06-23 20:35:14 +02:00
heroTypeId = gameState - > pickUnusedHeroTypeRandomly ( playerColor ) ;
2023-06-23 18:33:47 +02:00
}
2023-06-23 20:35:14 +02:00
gameState - > placeStartingHero ( playerColor , HeroTypeID ( heroTypeId ) , gameState - > map - > players [ playerColor . getNum ( ) ] . posOfMainTown ) ;
2023-06-23 18:33:47 +02:00
}
2023-06-23 20:35:14 +02:00
}
2023-06-23 18:33:47 +02:00
2023-06-26 15:25:34 +02:00
logGlobal - > debug ( " \t Generate list of hero placeholders " ) ;
2024-01-31 19:32:59 +02:00
generateCampaignHeroesToReplace ( ) ;
2023-06-23 18:33:47 +02:00
2023-06-26 15:25:34 +02:00
logGlobal - > debug ( " \t Prepare crossover heroes " ) ;
2024-01-31 19:32:59 +02:00
trimCrossoverHeroesParameters ( campaignState - > scenario ( * campaignState - > currentScenario ( ) ) . travelOptions ) ;
2023-06-26 15:25:34 +02:00
// remove same heroes on the map which will be added through crossover heroes
// INFO: we will remove heroes because later it may be possible that the API doesn't allow having heroes
// with the same hero type id
std : : vector < CGHeroInstance * > removedHeroes ;
2023-06-23 18:33:47 +02:00
2024-01-16 14:45:38 +02:00
std : : set < HeroTypeID > reservedHeroes = campaignState - > getReservedHeroes ( ) ;
std : : set < HeroTypeID > heroesToRemove ;
for ( auto const & heroID : reservedHeroes )
{
// Do not replace reserved heroes initially, e.g. in 1st campaign scenario in which they appear
if ( ! campaignState - > getHeroByType ( heroID ) . isNull ( ) )
heroesToRemove . insert ( heroID ) ;
}
2023-06-23 18:33:47 +02:00
2024-01-31 19:32:59 +02:00
for ( auto & replacement : campaignHeroReplacements )
if ( replacement . heroPlaceholderId . hasValue ( ) )
heroesToRemove . insert ( replacement . hero - > getHeroType ( ) ) ;
2023-06-23 18:33:47 +02:00
2023-06-26 15:25:34 +02:00
for ( auto & heroID : heroesToRemove )
{
auto * hero = gameState - > getUsedHero ( heroID ) ;
if ( hero )
2023-06-23 20:35:14 +02:00
{
2023-06-26 15:25:34 +02:00
removedHeroes . push_back ( hero ) ;
gameState - > map - > heroesOnMap - = hero ;
gameState - > map - > objects [ hero - > id . getNum ( ) ] = nullptr ;
gameState - > map - > removeBlockVisTiles ( hero , true ) ;
2023-06-23 20:35:14 +02:00
}
2023-06-26 15:25:34 +02:00
}
2023-06-23 18:33:47 +02:00
2023-06-26 15:25:34 +02:00
logGlobal - > debug ( " \t Replace placeholders with heroes " ) ;
2024-01-31 19:32:59 +02:00
replaceHeroesPlaceholders ( ) ;
2023-06-23 18:33:47 +02:00
2023-06-26 15:25:34 +02:00
// now add removed heroes again with unused type ID
for ( auto * hero : removedHeroes )
{
2023-09-28 18:43:04 +02:00
HeroTypeID heroTypeId ;
2023-06-26 15:25:34 +02:00
if ( hero - > ID = = Obj : : HERO )
2023-06-23 20:35:14 +02:00
{
2023-06-26 15:25:34 +02:00
heroTypeId = gameState - > pickUnusedHeroTypeRandomly ( hero - > tempOwner ) ;
}
else if ( hero - > ID = = Obj : : PRISON )
{
auto unusedHeroTypeIds = gameState - > getUnusedAllowedHeroes ( ) ;
if ( ! unusedHeroTypeIds . empty ( ) )
2023-06-23 20:35:14 +02:00
{
2023-09-28 18:43:04 +02:00
heroTypeId = ( * RandomGeneratorUtil : : nextItem ( unusedHeroTypeIds , gameState - > getRandomGenerator ( ) ) ) ;
2023-06-23 18:33:47 +02:00
}
2023-06-23 20:35:14 +02:00
else
{
2023-06-26 15:25:34 +02:00
logGlobal - > error ( " No free hero type ID found to replace prison. " ) ;
assert ( 0 ) ;
2023-06-23 20:35:14 +02:00
}
2023-06-23 18:33:47 +02:00
}
2023-06-26 15:25:34 +02:00
else
{
assert ( 0 ) ; // should not happen
}
2023-10-28 11:27:10 +02:00
hero - > setHeroType ( heroTypeId ) ;
2023-06-26 15:25:34 +02:00
gameState - > map - > getEditManager ( ) - > insertObject ( hero ) ;
2023-06-23 18:33:47 +02:00
}
}
void CGameStateCampaign : : giveCampaignBonusToHero ( CGHeroInstance * hero )
{
2023-06-26 00:07:55 +02:00
auto curBonus = currentBonus ( ) ;
2023-06-23 18:33:47 +02:00
if ( ! curBonus )
return ;
2023-06-23 20:35:14 +02:00
assert ( curBonus - > isBonusForHero ( ) ) ;
//apply bonus
switch ( curBonus - > type )
2023-06-23 18:33:47 +02:00
{
2023-06-25 20:16:03 +02:00
case CampaignBonusType : : SPELL :
2023-06-23 20:35:14 +02:00
{
2023-06-23 18:33:47 +02:00
hero - > addSpellToSpellbook ( SpellID ( curBonus - > info2 ) ) ;
break ;
2023-06-23 20:35:14 +02:00
}
2023-06-25 20:16:03 +02:00
case CampaignBonusType : : MONSTER :
2023-06-23 20:35:14 +02:00
{
for ( int i = 0 ; i < GameConstants : : ARMY_SIZE ; i + + )
2023-06-23 18:33:47 +02:00
{
2023-06-23 20:35:14 +02:00
if ( hero - > slotEmpty ( SlotID ( i ) ) )
2023-06-23 18:33:47 +02:00
{
2023-06-23 20:35:14 +02:00
hero - > addToSlot ( SlotID ( i ) , CreatureID ( curBonus - > info2 ) , curBonus - > info3 ) ;
break ;
2023-06-23 18:33:47 +02:00
}
}
break ;
2023-06-23 20:35:14 +02:00
}
2023-06-25 20:16:03 +02:00
case CampaignBonusType : : ARTIFACT :
2023-06-23 20:35:14 +02:00
{
2023-06-23 18:33:47 +02:00
if ( ! gameState - > giveHeroArtifact ( hero , static_cast < ArtifactID > ( curBonus - > info2 ) ) )
logGlobal - > error ( " Cannot give starting artifact - no free slots! " ) ;
break ;
2023-06-23 20:35:14 +02:00
}
2023-06-25 20:16:03 +02:00
case CampaignBonusType : : SPELL_SCROLL :
2023-06-23 20:35:14 +02:00
{
CArtifactInstance * scroll = ArtifactUtils : : createScroll ( SpellID ( curBonus - > info2 ) ) ;
const auto slot = ArtifactUtils : : getArtAnyPosition ( hero , scroll - > getTypeId ( ) ) ;
if ( ArtifactUtils : : isSlotEquipment ( slot ) | | ArtifactUtils : : isSlotBackpack ( slot ) )
2023-10-14 21:00:39 +02:00
scroll - > putAt ( * hero , slot ) ;
2023-06-23 20:35:14 +02:00
else
logGlobal - > error ( " Cannot give starting scroll - no free slots! " ) ;
2023-06-23 18:33:47 +02:00
break ;
2023-06-23 20:35:14 +02:00
}
2023-06-25 20:16:03 +02:00
case CampaignBonusType : : PRIMARY_SKILL :
2023-06-23 20:35:14 +02:00
{
const ui8 * ptr = reinterpret_cast < const ui8 * > ( & curBonus - > info2 ) ;
2023-10-05 15:13:52 +02:00
for ( auto g = PrimarySkill : : BEGIN ; g < PrimarySkill : : END ; + + g )
2023-06-23 18:33:47 +02:00
{
2023-10-05 15:13:52 +02:00
int val = ptr [ g . getNum ( ) ] ;
2023-06-23 20:35:14 +02:00
if ( val = = 0 )
continue ;
2023-10-05 15:13:52 +02:00
2023-10-10 17:05:18 +02:00
auto currentScenario = * gameState - > scenarioOps - > campState - > currentScenario ( ) ;
2023-10-21 13:50:42 +02:00
auto bb = std : : make_shared < Bonus > ( BonusDuration : : PERMANENT , BonusType : : PRIMARY_SKILL , BonusSource : : CAMPAIGN_BONUS , val , BonusSourceID ( currentScenario ) , BonusSubtypeID ( g ) ) ;
2023-06-23 20:35:14 +02:00
hero - > addNewBonus ( bb ) ;
2023-06-23 18:33:47 +02:00
}
break ;
2023-06-23 20:35:14 +02:00
}
2023-06-25 20:16:03 +02:00
case CampaignBonusType : : SECONDARY_SKILL :
2023-06-23 20:35:14 +02:00
{
2023-06-23 18:33:47 +02:00
hero - > setSecSkillLevel ( SecondarySkill ( curBonus - > info2 ) , curBonus - > info3 , true ) ;
break ;
}
}
}
2024-01-31 19:32:59 +02:00
void CGameStateCampaign : : replaceHeroesPlaceholders ( )
2023-06-23 18:33:47 +02:00
{
for ( const auto & campaignHeroReplacement : campaignHeroReplacements )
{
2024-01-31 19:32:59 +02:00
if ( ! campaignHeroReplacement . heroPlaceholderId . hasValue ( ) )
continue ;
2023-06-23 18:33:47 +02:00
auto * heroPlaceholder = dynamic_cast < CGHeroPlaceholder * > ( gameState - > getObjInstance ( campaignHeroReplacement . heroPlaceholderId ) ) ;
CGHeroInstance * heroToPlace = campaignHeroReplacement . hero ;
heroToPlace - > id = campaignHeroReplacement . heroPlaceholderId ;
2023-10-24 23:58:26 +02:00
if ( heroPlaceholder - > tempOwner . isValidPlayer ( ) )
heroToPlace - > tempOwner = heroPlaceholder - > tempOwner ;
2023-06-23 18:33:47 +02:00
heroToPlace - > pos = heroPlaceholder - > pos ;
2023-12-31 23:43:35 +02:00
heroToPlace - > type = heroToPlace - > getHeroType ( ) . toHeroType ( ) ;
2023-06-26 15:25:34 +02:00
heroToPlace - > appearance = VLC - > objtypeh - > getHandlerFor ( Obj : : HERO , heroToPlace - > type - > heroClass - > getIndex ( ) ) - > getTemplates ( ) . front ( ) ;
2023-06-23 18:33:47 +02:00
gameState - > map - > removeBlockVisTiles ( heroPlaceholder , true ) ;
gameState - > map - > objects [ heroPlaceholder - > id . getNum ( ) ] = nullptr ;
gameState - > map - > instanceNames . erase ( heroPlaceholder - > instanceName ) ;
gameState - > map - > heroesOnMap . emplace_back ( heroToPlace ) ;
gameState - > map - > objects [ heroToPlace - > id . getNum ( ) ] = heroToPlace ;
gameState - > map - > addBlockVisTiles ( heroToPlace ) ;
gameState - > map - > instanceNames [ heroToPlace - > instanceName ] = heroToPlace ;
delete heroPlaceholder ;
}
}
2024-01-31 19:32:59 +02:00
void CGameStateCampaign : : transferMissingArtifacts ( const CampaignTravel & travelOptions )
{
CGHeroInstance * receiver = nullptr ;
for ( auto obj : gameState - > map - > objects )
{
if ( ! obj )
continue ;
if ( obj - > ID ! = Obj : : HERO )
continue ;
auto * hero = dynamic_cast < CGHeroInstance * > ( obj . get ( ) ) ;
if ( gameState - > getPlayerState ( hero - > getOwner ( ) ) - > isHuman ( ) )
{
receiver = hero ;
break ;
}
}
assert ( receiver ) ;
for ( const auto & campaignHeroReplacement : campaignHeroReplacements )
{
if ( campaignHeroReplacement . heroPlaceholderId . hasValue ( ) )
continue ;
auto * donorHero = campaignHeroReplacement . hero ;
2024-05-16 09:55:11 +02:00
if ( ! donorHero )
throw std : : runtime_error ( " Failed to find hero to take artifacts from! Scenario: " + gameState - > map - > name . toString ( ) ) ;
2024-01-31 19:32:59 +02:00
for ( auto const & artLocation : campaignHeroReplacement . transferrableArtifacts )
{
auto * artifact = donorHero - > getArt ( artLocation ) ;
2024-05-16 09:55:11 +02:00
if ( ! donorHero )
throw std : : runtime_error ( " Failed to find artifacts to transfer to travelling hero! Scenario: " + gameState - > map - > name . toString ( ) ) ;
2024-01-31 19:32:59 +02:00
artifact - > removeFrom ( * donorHero , artLocation ) ;
if ( receiver )
{
const auto slot = ArtifactUtils : : getArtAnyPosition ( receiver , artifact - > getTypeId ( ) ) ;
if ( ArtifactUtils : : isSlotEquipment ( slot ) | | ArtifactUtils : : isSlotBackpack ( slot ) )
artifact - > putAt ( * receiver , slot ) ;
else
logGlobal - > error ( " Cannot transfer artifact - no free slots! " ) ;
}
else
logGlobal - > error ( " Cannot transfer artifact - no receiver hero! " ) ;
}
delete donorHero ;
}
}
void CGameStateCampaign : : generateCampaignHeroesToReplace ( )
2023-06-23 18:33:47 +02:00
{
2023-06-26 15:25:34 +02:00
auto campaignState = gameState - > scenarioOps - > campState ;
std : : vector < CGHeroPlaceholder * > placeholdersByPower ;
std : : vector < CGHeroPlaceholder * > placeholdersByType ;
2023-06-23 18:33:47 +02:00
2024-01-31 19:32:59 +02:00
campaignHeroReplacements . clear ( ) ;
2023-06-26 15:25:34 +02:00
// find all placeholders on map
2023-06-23 18:33:47 +02:00
for ( auto obj : gameState - > map - > objects )
{
2023-06-26 15:25:34 +02:00
if ( ! obj )
continue ;
2023-06-23 18:33:47 +02:00
2023-06-26 15:25:34 +02:00
if ( obj - > ID ! = Obj : : HERO_PLACEHOLDER )
continue ;
2023-06-23 18:33:47 +02:00
2023-06-26 15:25:34 +02:00
auto * heroPlaceholder = dynamic_cast < CGHeroPlaceholder * > ( obj . get ( ) ) ;
2023-06-23 18:33:47 +02:00
2023-06-26 15:25:34 +02:00
// only 1 field must be set
2023-11-02 16:56:02 +02:00
assert ( heroPlaceholder - > powerRank . has_value ( ) ! = heroPlaceholder - > heroType . has_value ( ) ) ;
2023-06-26 15:25:34 +02:00
if ( heroPlaceholder - > powerRank )
placeholdersByPower . push_back ( heroPlaceholder ) ;
if ( heroPlaceholder - > heroType )
placeholdersByType . push_back ( heroPlaceholder ) ;
}
//selecting heroes by type
2023-10-05 15:13:52 +02:00
for ( const auto * placeholder : placeholdersByType )
2023-06-23 18:33:47 +02:00
{
2023-10-05 15:13:52 +02:00
const auto & node = campaignState - > getHeroByType ( * placeholder - > heroType ) ;
2023-06-26 15:25:34 +02:00
if ( node . isNull ( ) )
2023-06-23 18:33:47 +02:00
{
2023-06-26 15:25:34 +02:00
logGlobal - > info ( " Hero crossover: Unable to replace placeholder for %d (%s)! " , placeholder - > heroType - > getNum ( ) , VLC - > heroTypes ( ) - > getById ( * placeholder - > heroType ) - > getNameTranslated ( ) ) ;
continue ;
2023-06-23 18:33:47 +02:00
}
2023-06-26 15:25:34 +02:00
2024-01-01 16:37:48 +02:00
CGHeroInstance * hero = campaignState - > crossoverDeserialize ( node , gameState - > map ) ;
2023-06-26 15:25:34 +02:00
2023-10-24 16:11:25 +02:00
logGlobal - > info ( " Hero crossover: Loading placeholder for %d (%s) " , hero - > getHeroType ( ) , hero - > getNameTranslated ( ) ) ;
2023-06-26 15:25:34 +02:00
campaignHeroReplacements . emplace_back ( hero , placeholder - > id ) ;
2023-06-23 18:33:47 +02:00
}
2023-06-26 15:25:34 +02:00
auto lastScenario = getHeroesSourceScenario ( ) ;
2024-01-31 19:32:59 +02:00
if ( lastScenario )
2023-06-23 18:33:47 +02:00
{
2023-06-26 15:25:34 +02:00
// sort hero placeholders descending power
boost : : range : : sort ( placeholdersByPower , [ ] ( const CGHeroPlaceholder * a , const CGHeroPlaceholder * b )
{
return * a - > powerRank > * b - > powerRank ;
} ) ;
2023-10-05 15:13:52 +02:00
const auto & nodeList = campaignState - > getHeroesByPower ( lastScenario . value ( ) ) ;
2023-06-26 15:25:34 +02:00
auto nodeListIter = nodeList . begin ( ) ;
2023-10-05 15:13:52 +02:00
for ( const auto * placeholder : placeholdersByPower )
2023-06-23 18:33:47 +02:00
{
2023-06-26 15:25:34 +02:00
if ( nodeListIter = = nodeList . end ( ) )
break ;
2024-01-01 16:37:48 +02:00
CGHeroInstance * hero = campaignState - > crossoverDeserialize ( * nodeListIter , gameState - > map ) ;
2023-06-26 15:25:34 +02:00
nodeListIter + + ;
2023-10-24 16:11:25 +02:00
logGlobal - > info ( " Hero crossover: Loading placeholder as %d (%s) " , hero - > getHeroType ( ) , hero - > getNameTranslated ( ) ) ;
2023-06-26 15:25:34 +02:00
campaignHeroReplacements . emplace_back ( hero , placeholder - > id ) ;
2023-06-23 18:33:47 +02:00
}
2024-01-31 19:32:59 +02:00
// Add remaining heroes without placeholders - to transfer their artifacts to placed heroes
for ( ; nodeListIter ! = nodeList . end ( ) ; + + nodeListIter )
{
CGHeroInstance * hero = campaignState - > crossoverDeserialize ( * nodeListIter , gameState - > map ) ;
campaignHeroReplacements . emplace_back ( hero , ObjectInstanceID : : NONE ) ;
}
2023-06-23 18:33:47 +02:00
}
}
void CGameStateCampaign : : initHeroes ( )
{
2023-06-26 00:07:55 +02:00
auto chosenBonus = currentBonus ( ) ;
2023-06-23 18:33:47 +02:00
if ( chosenBonus & & chosenBonus - > isBonusForHero ( ) & & chosenBonus - > info1 ! = 0xFFFE ) //exclude generated heroes
{
//find human player
PlayerColor humanPlayer = PlayerColor : : NEUTRAL ;
for ( auto & elem : gameState - > players )
{
if ( elem . second . human )
{
humanPlayer = elem . first ;
break ;
}
}
assert ( humanPlayer ! = PlayerColor : : NEUTRAL ) ;
2024-08-25 00:21:26 +02:00
const auto & heroes = gameState - > players [ humanPlayer ] . getHeroes ( ) ;
2023-06-23 18:33:47 +02:00
if ( chosenBonus - > info1 = = 0xFFFD ) //most powerful
{
int maxB = - 1 ;
for ( int b = 0 ; b < heroes . size ( ) ; + + b )
{
if ( maxB = = - 1 | | heroes [ b ] - > getTotalStrength ( ) > heroes [ maxB ] - > getTotalStrength ( ) )
{
maxB = b ;
}
}
if ( maxB < 0 )
logGlobal - > warn ( " Cannot give bonus to hero cause there are no heroes! " ) ;
else
giveCampaignBonusToHero ( heroes [ maxB ] ) ;
}
else //specific hero
{
2024-06-24 03:23:26 +02:00
for ( auto & hero : heroes )
2023-06-23 18:33:47 +02:00
{
2024-06-24 03:23:26 +02:00
if ( hero - > getHeroType ( ) . getNum ( ) = = chosenBonus - > info1 )
2023-06-23 18:33:47 +02:00
{
2024-06-24 03:23:26 +02:00
giveCampaignBonusToHero ( hero ) ;
2023-06-23 18:33:47 +02:00
break ;
}
}
}
}
2024-01-31 12:42:02 +02:00
auto campaignState = gameState - > scenarioOps - > campState ;
auto * yog = gameState - > getUsedHero ( HeroTypeID : : SOLMYR ) ;
if ( yog & & boost : : starts_with ( campaignState - > getFilename ( ) , " DATA/YOG " ) & & campaignState - > currentScenario ( ) - > getNum ( ) = = 2 )
{
assert ( yog - > isCampaignYog ( ) ) ;
gameState - > giveHeroArtifact ( yog , ArtifactID : : ANGELIC_ALLIANCE ) ;
}
2024-01-31 19:32:59 +02:00
transferMissingArtifacts ( campaignState - > scenario ( * campaignState - > currentScenario ( ) ) . travelOptions ) ;
2023-06-23 18:33:47 +02:00
}
void CGameStateCampaign : : initStartingResources ( )
{
auto getHumanPlayerInfo = [ & ] ( ) - > std : : vector < const PlayerSettings * >
{
std : : vector < const PlayerSettings * > ret ;
for ( const auto & playerInfo : gameState - > scenarioOps - > playerInfos )
{
if ( playerInfo . second . isControlledByHuman ( ) )
ret . push_back ( & playerInfo . second ) ;
}
return ret ;
} ;
2023-06-26 00:07:55 +02:00
auto chosenBonus = currentBonus ( ) ;
2023-06-25 20:16:03 +02:00
if ( chosenBonus & & chosenBonus - > type = = CampaignBonusType : : RESOURCE )
2023-06-23 18:33:47 +02:00
{
std : : vector < const PlayerSettings * > people = getHumanPlayerInfo ( ) ; //players we will give resource bonus
for ( const PlayerSettings * ps : people )
{
2023-11-02 16:56:02 +02:00
std : : vector < GameResID > res ; //resources we will give
2023-06-23 18:33:47 +02:00
switch ( chosenBonus - > info1 )
{
case 0 : case 1 : case 2 : case 3 : case 4 : case 5 : case 6 :
res . push_back ( chosenBonus - > info1 ) ;
break ;
case 0xFD : //wood+ore
res . push_back ( GameResID ( EGameResID : : WOOD ) ) ;
res . push_back ( GameResID ( EGameResID : : ORE ) ) ;
break ;
case 0xFE : //rare
res . push_back ( GameResID ( EGameResID : : MERCURY ) ) ;
res . push_back ( GameResID ( EGameResID : : SULFUR ) ) ;
res . push_back ( GameResID ( EGameResID : : CRYSTAL ) ) ;
res . push_back ( GameResID ( EGameResID : : GEMS ) ) ;
break ;
default :
assert ( 0 ) ;
break ;
}
//increasing resource quantity
for ( auto & re : res )
{
gameState - > players [ ps - > color ] . resources [ re ] + = chosenBonus - > info2 ;
}
}
}
}
void CGameStateCampaign : : initTowns ( )
{
2023-06-26 00:07:55 +02:00
auto chosenBonus = currentBonus ( ) ;
2023-06-23 18:33:47 +02:00
2023-06-26 16:15:47 +02:00
if ( ! chosenBonus )
return ;
if ( chosenBonus - > type ! = CampaignBonusType : : BUILDING )
return ;
for ( int g = 0 ; g < gameState - > map - > towns . size ( ) ; + + g )
2023-06-23 18:33:47 +02:00
{
2023-06-26 16:15:47 +02:00
CGTownInstance * town = gameState - > map - > towns [ g ] ;
PlayerState * owner = gameState - > getPlayerState ( town - > getOwner ( ) ) ;
if ( ! owner )
continue ;
PlayerInfo & pi = gameState - > map - > players [ owner - > color . getNum ( ) ] ;
if ( ! owner - > human )
continue ;
if ( town - > pos ! = pi . posOfMainTown )
continue ;
BuildingID newBuilding ;
if ( gameState - > scenarioOps - > campState - > formatVCMI ( ) )
newBuilding = BuildingID ( chosenBonus - > info1 ) ;
else
2024-08-17 21:06:48 +02:00
newBuilding = CBuildingHandler : : campToERMU ( chosenBonus - > info1 , town - > getFaction ( ) , town - > getBuildings ( ) ) ;
2023-06-26 16:15:47 +02:00
// Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
while ( true )
2023-06-23 18:33:47 +02:00
{
2023-06-26 16:15:47 +02:00
if ( newBuilding = = BuildingID : : NONE )
break ;
2023-06-23 18:33:47 +02:00
2024-08-16 17:00:02 +02:00
if ( town - > hasBuilt ( newBuilding ) )
2023-06-26 16:15:47 +02:00
break ;
2023-06-23 18:33:47 +02:00
2024-08-16 17:00:02 +02:00
town - > addBuilding ( newBuilding ) ;
2023-06-26 16:15:47 +02:00
auto building = town - > town - > buildings . at ( newBuilding ) ;
newBuilding = building - > upgrade ;
2023-06-23 18:33:47 +02:00
}
2023-06-26 16:15:47 +02:00
break ;
2023-06-23 18:33:47 +02:00
}
}
bool CGameStateCampaign : : playerHasStartingHero ( PlayerColor playerColor ) const
{
2023-06-26 00:07:55 +02:00
auto campaignBonus = currentBonus ( ) ;
2023-06-23 18:33:47 +02:00
if ( ! campaignBonus )
return false ;
2023-06-25 20:16:03 +02:00
if ( campaignBonus - > type = = CampaignBonusType : : HERO & & playerColor = = PlayerColor ( campaignBonus - > info1 ) )
2023-06-23 18:33:47 +02:00
return true ;
return false ;
}
2024-01-31 20:01:24 +02:00
std : : unique_ptr < CMap > CGameStateCampaign : : getCurrentMap ( )
2023-06-23 18:33:47 +02:00
{
2024-01-01 16:37:48 +02:00
return gameState - > scenarioOps - > campState - > getMap ( CampaignScenarioID : : NONE , gameState - > callback ) ;
2023-06-23 18:33:47 +02:00
}
VCMI_LIB_NAMESPACE_END