2023-10-19 16:19:09 +02:00
/*
* CPlayerInterface . 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 "CPlayerInterface.h"
# include <vcmi/Artifact.h>
# include "CGameInfo.h"
# include "CServerHandler.h"
# include "HeroMovementController.h"
# include "PlayerLocalState.h"
# include "adventureMap/AdventureMapInterface.h"
# include "adventureMap/CInGameConsole.h"
# include "adventureMap/CList.h"
# include "battle/BattleEffectsController.h"
# include "battle/BattleFieldController.h"
# include "battle/BattleInterface.h"
# include "battle/BattleInterfaceClasses.h"
# include "battle/BattleWindow.h"
# include "eventsSDL/InputHandler.h"
# include "eventsSDL/NotificationHandler.h"
# include "gui/CGuiHandler.h"
# include "gui/CursorHandler.h"
# include "gui/WindowHandler.h"
# include "mainmenu/CMainMenu.h"
# include "mainmenu/CHighScoreScreen.h"
# include "mapView/mapHandler.h"
2024-05-02 14:55:20 +02:00
# include "media/IMusicPlayer.h"
# include "media/ISoundPlayer.h"
2023-10-19 16:19:09 +02:00
# include "render/CAnimation.h"
# include "render/IImage.h"
2024-01-31 03:30:10 +02:00
# include "render/IRenderHandler.h"
2023-10-19 16:19:09 +02:00
# include "widgets/Buttons.h"
# include "widgets/CComponent.h"
# include "widgets/CGarrisonInt.h"
# include "windows/CCastleInterface.h"
# include "windows/CCreatureWindow.h"
2024-05-19 12:53:56 +02:00
# include "windows/CExchangeWindow.h"
2023-10-19 16:19:09 +02:00
# include "windows/CHeroWindow.h"
# include "windows/CKingdomInterface.h"
2024-02-03 13:26:24 +02:00
# include "windows/CMarketWindow.h"
2023-10-19 16:19:09 +02:00
# include "windows/CPuzzleWindow.h"
# include "windows/CQuestLog.h"
# include "windows/CSpellWindow.h"
2023-11-14 22:52:30 +02:00
# include "windows/CTutorialWindow.h"
2023-10-19 16:19:09 +02:00
# include "windows/GUIClasses.h"
# include "windows/InfoWindows.h"
# include "../CCallback.h"
# include "../lib/CConfigHandler.h"
2024-07-20 14:55:17 +02:00
# include "../lib/texts/CGeneralTextHandler.h"
2023-10-19 16:19:09 +02:00
# include "../lib/CPlayerState.h"
2024-06-01 17:28:17 +02:00
# include "../lib/CRandomGenerator.h"
2023-10-19 16:19:09 +02:00
# include "../lib/CStack.h"
# include "../lib/CStopWatch.h"
# include "../lib/CThreadHelper.h"
# include "../lib/GameConstants.h"
# include "../lib/RoadHandler.h"
# include "../lib/StartInfo.h"
# include "../lib/TerrainHandler.h"
# include "../lib/UnlockGuard.h"
# include "../lib/VCMIDirs.h"
# include "../lib/bonuses/Limiters.h"
# include "../lib/bonuses/Propagators.h"
# include "../lib/bonuses/Updaters.h"
# include "../lib/gameState/CGameState.h"
2024-05-11 15:09:12 +02:00
# include "../lib/mapObjects/CGMarket.h"
2023-10-19 16:19:09 +02:00
# include "../lib/mapObjects/CGTownInstance.h"
# include "../lib/mapObjects/MiscObjects.h"
# include "../lib/mapObjects/ObjectTemplate.h"
# include "../lib/mapping/CMapHeader.h"
2023-10-23 12:59:15 +02:00
# include "../lib/networkPacks/PacksForClient.h"
# include "../lib/networkPacks/PacksForClientBattle.h"
# include "../lib/networkPacks/PacksForServer.h"
2023-10-19 16:19:09 +02:00
# include "../lib/pathfinder/CGPathNode.h"
2024-08-24 00:32:02 +02:00
# include "../lib/serializer/CTypeList.h"
# include "../lib/serializer/ESerializationVersion.h"
2023-10-19 16:19:09 +02:00
# include "../lib/spells/CSpellHandler.h"
2024-07-20 14:55:17 +02:00
# include "../lib/texts/TextOperations.h"
2023-10-19 16:19:09 +02:00
// The macro below is used to mark functions that are called by client when game state changes.
// They all assume that interface mutex is locked.
# define EVENT_HANDLER_CALLED_BY_CLIENT
2024-01-16 21:25:21 +02:00
# define BATTLE_EVENT_POSSIBLE_RETURN if (LOCPLINT != this) return; if (isAutoFightOn && !battleInt) return
2023-10-19 16:19:09 +02:00
CPlayerInterface * LOCPLINT ;
std : : shared_ptr < BattleInterface > CPlayerInterface : : battleInt ;
struct HeroObjectRetriever
{
const CGHeroInstance * operator ( ) ( const ConstTransitivePtr < CGHeroInstance > & h ) const
{
return h ;
}
const CGHeroInstance * operator ( ) ( const ConstTransitivePtr < CStackInstance > & s ) const
{
return nullptr ;
}
} ;
CPlayerInterface : : CPlayerInterface ( PlayerColor Player ) :
localState ( std : : make_unique < PlayerLocalState > ( * this ) ) ,
2024-07-15 23:03:06 +02:00
movementController ( std : : make_unique < HeroMovementController > ( ) ) ,
artifactController ( std : : make_unique < ArtifactsUIController > ( ) )
2023-10-19 16:19:09 +02:00
{
logGlobal - > trace ( " \t Human player interface for player %s being constructed " , Player . toString ( ) ) ;
LOCPLINT = this ;
playerID = Player ;
human = true ;
2024-05-23 13:36:18 +02:00
battleInt . reset ( ) ;
2023-10-19 16:19:09 +02:00
castleInt = nullptr ;
makingTurn = false ;
2024-05-18 13:04:10 +02:00
showingDialog = new ConditionalWait ( ) ;
2023-10-19 16:19:09 +02:00
cingconsole = new CInGameConsole ( ) ;
autosaveCount = 0 ;
isAutoFightOn = false ;
2024-01-27 16:02:03 +02:00
isAutoFightEndBattle = false ;
2023-10-19 16:19:09 +02:00
ignoreEvents = false ;
}
CPlayerInterface : : ~ CPlayerInterface ( )
{
logGlobal - > trace ( " \t Human player interface for player %s being destructed " , playerID . toString ( ) ) ;
delete showingDialog ;
delete cingconsole ;
if ( LOCPLINT = = this )
LOCPLINT = nullptr ;
}
void CPlayerInterface : : initGameInterface ( std : : shared_ptr < Environment > ENV , std : : shared_ptr < CCallback > CB )
{
cb = CB ;
env = ENV ;
CCS - > musich - > loadTerrainMusicThemes ( ) ;
initializeHeroTownList ( ) ;
adventureInt . reset ( new AdventureMapInterface ( ) ) ;
}
2024-07-29 17:57:49 +02:00
void CPlayerInterface : : closeAllDialogs ( )
2023-10-19 16:19:09 +02:00
{
2024-07-29 17:57:49 +02:00
// remove all active dialogs that do not expect query answer
2024-09-11 23:00:06 +02:00
while ( true )
2023-10-19 16:19:09 +02:00
{
2024-07-29 17:57:49 +02:00
auto adventureWindow = GH . windows ( ) . topWindow < AdventureMapInterface > ( ) ;
auto infoWindow = GH . windows ( ) . topWindow < CInfoWindow > ( ) ;
2024-09-11 23:00:06 +02:00
auto topWindow = GH . windows ( ) . topWindow < WindowBase > ( ) ;
2023-10-19 16:19:09 +02:00
2024-07-29 17:57:49 +02:00
if ( adventureWindow ! = nullptr )
break ;
2023-10-19 16:19:09 +02:00
2024-07-29 17:57:49 +02:00
if ( infoWindow & & infoWindow - > ID ! = QueryID : : NONE )
break ;
2023-10-19 16:19:09 +02:00
2024-09-11 23:00:06 +02:00
topWindow - > close ( ) ;
2024-07-29 17:57:49 +02:00
}
}
2024-04-06 10:42:39 +02:00
2024-07-29 17:57:49 +02:00
void CPlayerInterface : : playerEndsTurn ( PlayerColor player )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( player = = playerID )
{
makingTurn = false ;
closeAllDialogs ( ) ;
2024-08-22 18:39:30 +02:00
// remove all pending dialogs that do not expect query answer
vstd : : erase_if ( dialogs , [ ] ( const std : : shared_ptr < CInfoWindow > & window ) {
return window - > ID = = QueryID : : NONE ;
} ) ;
2023-10-19 16:19:09 +02:00
}
}
void CPlayerInterface : : playerStartsTurn ( PlayerColor player )
{
if ( GH . windows ( ) . findWindows < AdventureMapInterface > ( ) . empty ( ) )
{
// after map load - remove all active windows and replace them with adventure map
GH . windows ( ) . clear ( ) ;
GH . windows ( ) . pushWindow ( adventureInt ) ;
}
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( player ! = playerID & & LOCPLINT = = this )
{
waitWhileDialog ( ) ;
bool isHuman = cb - > getStartInfo ( ) - > playerInfos . count ( player ) & & cb - > getStartInfo ( ) - > playerInfos . at ( player ) . isControlledByHuman ( ) ;
if ( makingTurn = = false )
adventureInt - > onEnemyTurnStarted ( player , isHuman ) ;
}
}
void CPlayerInterface : : performAutosave ( )
{
int frequency = static_cast < int > ( settings [ " general " ] [ " saveFrequency " ] . Integer ( ) ) ;
if ( frequency > 0 & & cb - > getDate ( ) % frequency = = 0 )
{
bool usePrefix = settings [ " general " ] [ " useSavePrefix " ] . Bool ( ) ;
std : : string prefix = std : : string ( ) ;
if ( usePrefix )
{
prefix = settings [ " general " ] [ " savePrefix " ] . String ( ) ;
if ( prefix . empty ( ) )
{
std : : string name = cb - > getMapHeader ( ) - > name . toString ( ) ;
int txtlen = TextOperations : : getUnicodeCharactersCount ( name ) ;
2024-10-31 00:23:56 +02:00
TextOperations : : trimRightUnicode ( name , std : : max ( 0 , txtlen - 14 ) ) ;
2024-06-11 16:30:13 +02:00
auto const & isSymbolIllegal = [ & ] ( char c ) {
static const std : : string forbiddenChars ( " \\ /:*? \" <>| " ) ;
bool charForbidden = forbiddenChars . find ( c ) ! = std : : string : : npos ;
bool charNonprintable = static_cast < unsigned char > ( c ) < static_cast < unsigned char > ( ' ' ) ;
return charForbidden | | charNonprintable ;
} ;
std : : replace_if ( name . begin ( ) , name . end ( ) , isSymbolIllegal , ' _ ' ) ;
2023-10-19 16:19:09 +02:00
2024-10-31 00:23:56 +02:00
prefix = vstd : : getFormattedDateTime ( cb - > getStartInfo ( ) - > startTime , " %Y-%m-%d_%H-%M " ) + " _ " + name + " / " ;
2023-10-19 16:19:09 +02:00
}
}
autosaveCount + + ;
int autosaveCountLimit = settings [ " general " ] [ " autosaveCountLimit " ] . Integer ( ) ;
if ( autosaveCountLimit > 0 )
{
cb - > save ( " Saves/Autosave/ " + prefix + std : : to_string ( autosaveCount ) ) ;
autosaveCount % = autosaveCountLimit ;
}
else
{
std : : string stringifiedDate = std : : to_string ( cb - > getDate ( Date : : MONTH ) )
+ std : : to_string ( cb - > getDate ( Date : : WEEK ) )
+ std : : to_string ( cb - > getDate ( Date : : DAY_OF_WEEK ) ) ;
cb - > save ( " Saves/Autosave/ " + prefix + stringifiedDate ) ;
}
}
}
void CPlayerInterface : : gamePause ( bool pause )
{
cb - > gamePause ( pause ) ;
}
void CPlayerInterface : : yourTurn ( QueryID queryID )
{
2024-07-29 17:57:49 +02:00
closeAllDialogs ( ) ;
2023-11-14 22:52:30 +02:00
CTutorialWindow : : openWindowFirstTime ( TutorialMode : : TOUCH_ADVENTUREMAP ) ;
2023-10-19 16:19:09 +02:00
EVENT_HANDLER_CALLED_BY_CLIENT ;
2024-04-29 16:02:27 +02:00
int humanPlayersCount = 0 ;
for ( const auto & info : cb - > getStartInfo ( ) - > playerInfos )
if ( info . second . isControlledByHuman ( ) )
humanPlayersCount + + ;
bool hotseatWait = humanPlayersCount > 1 ;
2023-10-19 16:19:09 +02:00
LOCPLINT = this ;
GH . curInt = this ;
NotificationHandler : : notify ( " Your turn " ) ;
if ( settings [ " general " ] [ " startTurnAutosave " ] . Bool ( ) )
{
performAutosave ( ) ;
}
2024-04-29 16:02:27 +02:00
if ( hotseatWait ) //hot seat or MP message
2023-10-19 16:19:09 +02:00
{
adventureInt - > onHotseatWaitStarted ( playerID ) ;
makingTurn = true ;
std : : string msg = CGI - > generaltexth - > allTexts [ 13 ] ;
boost : : replace_first ( msg , " %s " , cb - > getStartInfo ( ) - > playerInfos . find ( playerID ) - > second . name ) ;
std : : vector < std : : shared_ptr < CComponent > > cmp ;
2023-10-31 11:09:56 +02:00
cmp . push_back ( std : : make_shared < CComponent > ( ComponentType : : FLAG , playerID ) ) ;
2023-10-19 16:19:09 +02:00
showInfoDialog ( msg , cmp ) ;
}
else
{
makingTurn = true ;
adventureInt - > onPlayerTurnStarted ( playerID ) ;
}
2024-04-29 16:02:27 +02:00
acceptTurn ( queryID , hotseatWait ) ;
2023-10-19 16:19:09 +02:00
}
2024-04-29 16:02:27 +02:00
void CPlayerInterface : : acceptTurn ( QueryID queryID , bool hotseatWait )
2023-10-19 16:19:09 +02:00
{
if ( settings [ " session " ] [ " autoSkip " ] . Bool ( ) )
{
while ( auto iw = GH . windows ( ) . topWindow < CInfoWindow > ( ) )
iw - > close ( ) ;
}
2024-04-29 16:02:27 +02:00
if ( hotseatWait )
2023-10-19 16:19:09 +02:00
{
waitWhileDialog ( ) ; // wait for player to accept turn in hot-seat mode
adventureInt - > onPlayerTurnStarted ( playerID ) ;
}
// warn player if he has no town
if ( cb - > howManyTowns ( ) = = 0 )
{
auto playerColor = * cb - > getPlayerID ( ) ;
std : : vector < Component > components ;
2023-10-31 11:09:56 +02:00
components . emplace_back ( ComponentType : : FLAG , playerColor ) ;
2023-10-19 16:19:09 +02:00
MetaString text ;
const auto & optDaysWithoutCastle = cb - > getPlayerState ( playerColor ) - > daysWithoutCastle ;
if ( optDaysWithoutCastle )
{
auto daysWithoutCastle = optDaysWithoutCastle . value ( ) ;
if ( daysWithoutCastle < 6 )
{
text . appendLocalString ( EMetaText : : ARRAY_TXT , 128 ) ; //%s, you only have %d days left to capture a town or you will be banished from this land.
2023-11-02 22:01:49 +02:00
text . replaceName ( playerColor ) ;
2023-10-19 16:19:09 +02:00
text . replaceNumber ( 7 - daysWithoutCastle ) ;
}
else if ( daysWithoutCastle = = 6 )
{
text . appendLocalString ( EMetaText : : ARRAY_TXT , 129 ) ; //%s, this is your last day to capture a town or you will be banished from this land.
2023-11-02 22:01:49 +02:00
text . replaceName ( playerColor ) ;
2023-10-19 16:19:09 +02:00
}
showInfoDialogAndWait ( components , text ) ;
}
else
logGlobal - > warn ( " Player has no towns, but daysWithoutCastle is not set " ) ;
}
cb - > selectionMade ( 0 , queryID ) ;
movementController - > onPlayerTurnStarted ( ) ;
}
void CPlayerInterface : : heroMoved ( const TryMoveHero & details , bool verbose )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
waitWhileDialog ( ) ;
if ( LOCPLINT ! = this )
return ;
//FIXME: read once and store
if ( settings [ " session " ] [ " spectate " ] . Bool ( ) & & settings [ " session " ] [ " spectate-ignore-hero " ] . Bool ( ) )
return ;
const CGHeroInstance * hero = cb - > getHero ( details . id ) ; //object representing this hero
if ( ! hero )
return ;
movementController - > onTryMoveHero ( hero , details ) ;
}
void CPlayerInterface : : heroKilled ( const CGHeroInstance * hero )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
LOG_TRACE_PARAMS ( logGlobal , " Hero %s killed handler for player %s " , hero - > getNameTranslated ( ) % playerID ) ;
// if hero is not in town garrison
if ( vstd : : contains ( localState - > getWanderingHeroes ( ) , hero ) )
localState - > removeWanderingHero ( hero ) ;
adventureInt - > onHeroChanged ( hero ) ;
localState - > erasePath ( hero ) ;
}
2024-09-28 22:58:05 +02:00
void CPlayerInterface : : townRemoved ( const CGTownInstance * town )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( town - > tempOwner = = playerID )
{
localState - > removeOwnedTown ( town ) ;
adventureInt - > onTownChanged ( town ) ;
}
}
2023-10-19 16:19:09 +02:00
void CPlayerInterface : : heroVisit ( const CGHeroInstance * visitor , const CGObjectInstance * visitedObj , bool start )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( start & & visitedObj )
{
2024-06-01 17:28:17 +02:00
auto visitSound = visitedObj - > getVisitSound ( CRandomGenerator : : getDefault ( ) ) ;
if ( visitSound )
CCS - > soundh - > playSound ( visitSound . value ( ) ) ;
2023-10-19 16:19:09 +02:00
}
}
void CPlayerInterface : : heroCreated ( const CGHeroInstance * hero )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
localState - > addWanderingHero ( hero ) ;
adventureInt - > onHeroChanged ( hero ) ;
2024-09-28 23:54:51 +02:00
if ( castleInt )
CCS - > soundh - > playSound ( soundBase : : newBuilding ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : openTownWindow ( const CGTownInstance * town )
{
if ( castleInt )
castleInt - > close ( ) ;
castleInt = nullptr ;
auto newCastleInt = std : : make_shared < CCastleInterface > ( town ) ;
GH . windows ( ) . pushWindow ( newCastleInt ) ;
}
void CPlayerInterface : : heroPrimarySkillChanged ( const CGHeroInstance * hero , PrimarySkill which , si64 val )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( which = = PrimarySkill : : EXPERIENCE )
{
2024-06-05 15:24:36 +02:00
for ( auto ctw : GH . windows ( ) . findWindows < IMarketHolder > ( ) )
ctw - > updateExperience ( ) ;
2023-10-19 16:19:09 +02:00
}
else
2024-06-05 15:24:36 +02:00
{
2023-10-19 16:19:09 +02:00
adventureInt - > onHeroChanged ( hero ) ;
2024-06-05 15:24:36 +02:00
}
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : heroSecondarySkillChanged ( const CGHeroInstance * hero , int which , int val )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
2024-06-05 15:24:36 +02:00
for ( auto cuw : GH . windows ( ) . findWindows < IMarketHolder > ( ) )
cuw - > updateSecondarySkills ( ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : heroManaPointsChanged ( const CGHeroInstance * hero )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
adventureInt - > onHeroChanged ( hero ) ;
if ( makingTurn & & hero - > tempOwner = = playerID )
adventureInt - > onHeroChanged ( hero ) ;
}
void CPlayerInterface : : heroMovePointsChanged ( const CGHeroInstance * hero )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( makingTurn & & hero - > tempOwner = = playerID )
adventureInt - > onHeroChanged ( hero ) ;
}
void CPlayerInterface : : receivedResource ( )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
2024-06-05 15:24:36 +02:00
for ( auto mw : GH . windows ( ) . findWindows < IMarketHolder > ( ) )
mw - > updateResources ( ) ;
2023-10-19 16:19:09 +02:00
GH . windows ( ) . totalRedraw ( ) ;
}
void CPlayerInterface : : heroGotLevel ( const CGHeroInstance * hero , PrimarySkill pskill , std : : vector < SecondarySkill > & skills , QueryID queryID )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
waitWhileDialog ( ) ;
CCS - > soundh - > playSound ( soundBase : : heroNewLevel ) ;
GH . windows ( ) . createAndPushWindow < CLevelWindow > ( hero , pskill , skills , [ = ] ( ui32 selection )
{
cb - > selectionMade ( selection , queryID ) ;
} ) ;
}
void CPlayerInterface : : commanderGotLevel ( const CCommanderInstance * commander , std : : vector < ui32 > skills , QueryID queryID )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
waitWhileDialog ( ) ;
CCS - > soundh - > playSound ( soundBase : : heroNewLevel ) ;
GH . windows ( ) . createAndPushWindow < CStackWindow > ( commander , skills , [ = ] ( ui32 selection )
{
cb - > selectionMade ( selection , queryID ) ;
} ) ;
}
void CPlayerInterface : : heroInGarrisonChange ( const CGTownInstance * town )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( town - > garrisonHero ) //wandering hero moved to the garrison
{
// This method also gets called on hero recruitment -> garrisoned hero is already in garrison
if ( town - > garrisonHero - > tempOwner = = playerID & & vstd : : contains ( localState - > getWanderingHeroes ( ) , town - > garrisonHero ) )
localState - > removeWanderingHero ( town - > garrisonHero ) ;
}
if ( town - > visitingHero ) //hero leaves garrison
{
// This method also gets called on hero recruitment -> wandering heroes already contains new hero
if ( town - > visitingHero - > tempOwner = = playerID & & ! vstd : : contains ( localState - > getWanderingHeroes ( ) , town - > visitingHero ) )
localState - > addWanderingHero ( town - > visitingHero ) ;
}
adventureInt - > onHeroChanged ( nullptr ) ;
adventureInt - > onTownChanged ( town ) ;
2023-11-09 00:01:48 +02:00
for ( auto cgh : GH . windows ( ) . findWindows < IGarrisonHolder > ( ) )
if ( cgh - > holdsGarrison ( town ) )
cgh - > updateGarrisons ( ) ;
2023-10-19 16:19:09 +02:00
for ( auto ki : GH . windows ( ) . findWindows < CKingdomInterface > ( ) )
ki - > townChanged ( town ) ;
// Perform totalRedraw to update hero list on adventure map, if any dialogs are open
GH . windows ( ) . totalRedraw ( ) ;
}
void CPlayerInterface : : heroVisitsTown ( const CGHeroInstance * hero , const CGTownInstance * town )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( hero - > tempOwner ! = playerID )
return ;
waitWhileDialog ( ) ;
openTownWindow ( town ) ;
}
void CPlayerInterface : : garrisonsChanged ( ObjectInstanceID id1 , ObjectInstanceID id2 )
{
2023-11-09 00:01:48 +02:00
std : : vector < const CArmedInstance * > instances ;
2023-10-19 16:19:09 +02:00
2023-11-09 00:01:48 +02:00
if ( auto obj = dynamic_cast < const CArmedInstance * > ( cb - > getObjInstance ( id1 ) ) )
2023-10-19 16:19:09 +02:00
instances . push_back ( obj ) ;
if ( id2 ! = ObjectInstanceID ( ) & & id2 ! = id1 )
{
2023-11-09 00:01:48 +02:00
if ( auto obj = dynamic_cast < const CArmedInstance * > ( cb - > getObjInstance ( id2 ) ) )
2023-10-19 16:19:09 +02:00
instances . push_back ( obj ) ;
}
garrisonsChanged ( instances ) ;
}
2023-11-09 00:01:48 +02:00
void CPlayerInterface : : garrisonsChanged ( std : : vector < const CArmedInstance * > objs )
2023-10-19 16:19:09 +02:00
{
for ( auto object : objs )
{
auto * hero = dynamic_cast < const CGHeroInstance * > ( object ) ;
auto * town = dynamic_cast < const CGTownInstance * > ( object ) ;
2023-11-09 00:01:48 +02:00
if ( town )
adventureInt - > onTownChanged ( town ) ;
2023-10-19 16:19:09 +02:00
if ( hero )
{
adventureInt - > onHeroChanged ( hero ) ;
2023-11-09 00:01:48 +02:00
if ( hero - > inTownGarrison & & hero - > visitedTown ! = town )
2023-10-19 16:19:09 +02:00
adventureInt - > onTownChanged ( hero - > visitedTown ) ;
}
}
for ( auto cgh : GH . windows ( ) . findWindows < IGarrisonHolder > ( ) )
2023-11-09 00:01:48 +02:00
if ( cgh - > holdsGarrisons ( objs ) )
cgh - > updateGarrisons ( ) ;
2023-10-19 16:19:09 +02:00
GH . windows ( ) . totalRedraw ( ) ;
}
void CPlayerInterface : : buildChanged ( const CGTownInstance * town , BuildingID buildingID , int what ) //what: 1 - built, 2 - demolished
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
adventureInt - > onTownChanged ( town ) ;
if ( castleInt )
{
castleInt - > townlist - > updateElement ( town ) ;
if ( castleInt - > town = = town )
{
switch ( what )
{
case 1 :
CCS - > soundh - > playSound ( soundBase : : newBuilding ) ;
castleInt - > addBuilding ( buildingID ) ;
break ;
case 2 :
castleInt - > removeBuilding ( buildingID ) ;
break ;
}
}
// Perform totalRedraw in order to force redraw of updated town list icon from adventure map
GH . windows ( ) . totalRedraw ( ) ;
}
for ( auto cgh : GH . windows ( ) . findWindows < ITownHolder > ( ) )
cgh - > buildChanged ( ) ;
}
void CPlayerInterface : : battleStartBefore ( const BattleID & battleID , const CCreatureSet * army1 , const CCreatureSet * army2 , int3 tile , const CGHeroInstance * hero1 , const CGHeroInstance * hero2 )
{
movementController - > onBattleStarted ( ) ;
2024-08-19 20:20:11 +02:00
waitForAllDialogs ( ) ;
2023-10-19 16:19:09 +02:00
}
2024-08-11 22:22:35 +02:00
void CPlayerInterface : : battleStart ( const BattleID & battleID , const CCreatureSet * army1 , const CCreatureSet * army2 , int3 tile , const CGHeroInstance * hero1 , const CGHeroInstance * hero2 , BattleSide side , bool replayAllowed )
2023-10-19 16:19:09 +02:00
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
bool useQuickCombat = settings [ " adventure " ] [ " quickCombat " ] . Bool ( ) ;
bool forceQuickCombat = settings [ " adventure " ] [ " forceQuickCombat " ] . Bool ( ) ;
if ( ( replayAllowed & & useQuickCombat ) | | forceQuickCombat )
{
autofightingAI = CDynLibHandler : : getNewBattleAI ( settings [ " server " ] [ " friendlyAI " ] . String ( ) ) ;
AutocombatPreferences autocombatPreferences = AutocombatPreferences ( ) ;
autocombatPreferences . enableSpellsUsage = settings [ " battle " ] [ " enableAutocombatSpells " ] . Bool ( ) ;
autofightingAI - > initBattleInterface ( env , cb , autocombatPreferences ) ;
autofightingAI - > battleStart ( battleID , army1 , army2 , tile , hero1 , hero2 , side , false ) ;
isAutoFightOn = true ;
cb - > registerBattleInterface ( autofightingAI ) ;
}
2024-08-19 20:20:11 +02:00
waitForAllDialogs ( ) ;
2023-10-19 16:19:09 +02:00
BATTLE_EVENT_POSSIBLE_RETURN ;
}
void CPlayerInterface : : battleUnitsChanged ( const BattleID & battleID , const std : : vector < UnitChanges > & units )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
for ( auto & info : units )
{
switch ( info . operation )
{
case UnitChanges : : EOperation : : RESET_STATE :
{
const CStack * stack = cb - > getBattle ( battleID ) - > battleGetStackByID ( info . id ) ;
if ( ! stack )
{
logGlobal - > error ( " Invalid unit ID %d " , info . id ) ;
continue ;
}
battleInt - > stackReset ( stack ) ;
}
break ;
case UnitChanges : : EOperation : : REMOVE :
battleInt - > stackRemoved ( info . id ) ;
break ;
case UnitChanges : : EOperation : : ADD :
{
const CStack * unit = cb - > getBattle ( battleID ) - > battleGetStackByID ( info . id ) ;
if ( ! unit )
{
logGlobal - > error ( " Invalid unit ID %d " , info . id ) ;
continue ;
}
battleInt - > stackAdded ( unit ) ;
}
break ;
default :
logGlobal - > error ( " Unknown unit operation %d " , ( int ) info . operation ) ;
break ;
}
}
}
void CPlayerInterface : : battleObstaclesChanged ( const BattleID & battleID , const std : : vector < ObstacleChanges > & obstacles )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
std : : vector < std : : shared_ptr < const CObstacleInstance > > newObstacles ;
std : : vector < ObstacleChanges > removedObstacles ;
for ( auto & change : obstacles )
{
if ( change . operation = = BattleChanges : : EOperation : : ADD )
{
auto instance = cb - > getBattle ( battleID ) - > battleGetObstacleByID ( change . id ) ;
if ( instance )
newObstacles . push_back ( instance ) ;
else
logNetwork - > error ( " Invalid obstacle instance %d " , change . id ) ;
}
if ( change . operation = = BattleChanges : : EOperation : : REMOVE )
removedObstacles . push_back ( change ) ; //Obstacles are already removed, so, show animation based on json struct
}
if ( ! newObstacles . empty ( ) )
battleInt - > obstaclePlaced ( newObstacles ) ;
if ( ! removedObstacles . empty ( ) )
battleInt - > obstacleRemoved ( removedObstacles ) ;
battleInt - > fieldController - > redrawBackgroundWithHexes ( ) ;
}
void CPlayerInterface : : battleCatapultAttacked ( const BattleID & battleID , const CatapultAttack & ca )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > stackIsCatapulting ( ca ) ;
}
void CPlayerInterface : : battleNewRound ( const BattleID & battleID ) //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > newRound ( ) ;
}
void CPlayerInterface : : actionStarted ( const BattleID & battleID , const BattleAction & action )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > startAction ( action ) ;
}
void CPlayerInterface : : actionFinished ( const BattleID & battleID , const BattleAction & action )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > endAction ( action ) ;
}
void CPlayerInterface : : activeStack ( const BattleID & battleID , const CStack * stack ) //called when it's turn of that stack
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
logGlobal - > trace ( " Awaiting command for %s " , stack - > nodeName ( ) ) ;
assert ( ! cb - > getBattle ( battleID ) - > battleIsFinished ( ) ) ;
if ( cb - > getBattle ( battleID ) - > battleIsFinished ( ) )
{
logGlobal - > error ( " Received CPlayerInterface::activeStack after battle is finished! " ) ;
cb - > battleMakeUnitAction ( battleID , BattleAction : : makeDefend ( stack ) ) ;
return ;
}
if ( autofightingAI )
{
if ( isAutoFightOn )
{
//FIXME: we want client rendering to proceed while AI is making actions
// so unlock mutex while AI is busy since this might take quite a while, especially if hero has many spells
auto unlockInterface = vstd : : makeUnlockGuard ( GH . interfaceMutex ) ;
autofightingAI - > activeStack ( battleID , stack ) ;
return ;
}
cb - > unregisterBattleInterface ( autofightingAI ) ;
autofightingAI . reset ( ) ;
}
assert ( battleInt ) ;
if ( ! battleInt )
{
// probably battle is finished already
cb - > battleMakeUnitAction ( battleID , BattleAction : : makeDefend ( stack ) ) ;
}
battleInt - > stackActivated ( stack ) ;
}
void CPlayerInterface : : battleEnd ( const BattleID & battleID , const BattleResult * br , QueryID queryID )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( isAutoFightOn | | autofightingAI )
{
isAutoFightOn = false ;
cb - > unregisterBattleInterface ( autofightingAI ) ;
autofightingAI . reset ( ) ;
if ( ! battleInt )
{
2024-01-27 16:02:03 +02:00
bool allowManualReplay = queryID ! = QueryID : : NONE & & ! isAutoFightEndBattle ;
2023-10-19 16:19:09 +02:00
auto wnd = std : : make_shared < BattleResultWindow > ( * br , * this , allowManualReplay ) ;
2024-01-27 16:02:03 +02:00
if ( allowManualReplay | | isAutoFightEndBattle )
2023-10-19 16:19:09 +02:00
{
wnd - > resultCallback = [ = ] ( ui32 selection )
{
cb - > selectionMade ( selection , queryID ) ;
} ;
}
2024-01-27 16:02:03 +02:00
isAutoFightEndBattle = false ;
2023-10-19 16:19:09 +02:00
GH . windows ( ) . pushWindow ( wnd ) ;
// #1490 - during AI turn when quick combat is on, we need to display the message and wait for user to close it.
// Otherwise NewTurn causes freeze.
waitWhileDialog ( ) ;
return ;
}
}
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > battleFinished ( * br , queryID ) ;
}
void CPlayerInterface : : battleLogMessage ( const BattleID & battleID , const std : : vector < MetaString > & lines )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > displayBattleLog ( lines ) ;
}
void CPlayerInterface : : battleStackMoved ( const BattleID & battleID , const CStack * stack , std : : vector < BattleHex > dest , int distance , bool teleport )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > stackMoved ( stack , dest , distance , teleport ) ;
}
void CPlayerInterface : : battleSpellCast ( const BattleID & battleID , const BattleSpellCast * sc )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > spellCast ( sc ) ;
}
void CPlayerInterface : : battleStacksEffectsSet ( const BattleID & battleID , const SetStackEffect & sse )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > battleStacksEffectsSet ( sse ) ;
}
void CPlayerInterface : : battleTriggerEffect ( const BattleID & battleID , const BattleTriggerEffect & bte )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > effectsController - > battleTriggerEffect ( bte ) ;
if ( bte . effect = = vstd : : to_underlying ( BonusType : : MANA_DRAIN ) )
{
const CGHeroInstance * manaDrainedHero = LOCPLINT - > cb - > getHero ( ObjectInstanceID ( bte . additionalInfo ) ) ;
battleInt - > windowObject - > heroManaPointsChanged ( manaDrainedHero ) ;
}
}
void CPlayerInterface : : battleStacksAttacked ( const BattleID & battleID , const std : : vector < BattleStackAttacked > & bsa , bool ranged )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
std : : vector < StackAttackedInfo > arg ;
for ( auto & elem : bsa )
{
const CStack * defender = cb - > getBattle ( battleID ) - > battleGetStackByID ( elem . stackAttacked , false ) ;
const CStack * attacker = cb - > getBattle ( battleID ) - > battleGetStackByID ( elem . attackerID , false ) ;
assert ( defender ) ;
StackAttackedInfo info ;
info . defender = defender ;
info . attacker = attacker ;
info . damageDealt = elem . damageAmount ;
info . amountKilled = elem . killedAmount ;
info . spellEffect = SpellID : : NONE ;
info . indirectAttack = ranged ;
info . killed = elem . killed ( ) ;
info . rebirth = elem . willRebirth ( ) ;
info . cloneKilled = elem . cloneKilled ( ) ;
info . fireShield = elem . fireShield ( ) ;
if ( elem . isSpell ( ) )
info . spellEffect = elem . spellID ;
arg . push_back ( info ) ;
}
battleInt - > stacksAreAttacked ( arg ) ;
}
void CPlayerInterface : : battleAttack ( const BattleID & battleID , const BattleAttack * ba )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
StackAttackInfo info ;
info . attacker = cb - > getBattle ( battleID ) - > battleGetStackByID ( ba - > stackAttacking ) ;
info . defender = nullptr ;
info . indirectAttack = ba - > shot ( ) ;
info . lucky = ba - > lucky ( ) ;
info . unlucky = ba - > unlucky ( ) ;
info . deathBlow = ba - > deathBlow ( ) ;
info . lifeDrain = ba - > lifeDrain ( ) ;
info . tile = ba - > tile ;
info . spellEffect = SpellID : : NONE ;
if ( ba - > spellLike ( ) )
info . spellEffect = ba - > spellID ;
for ( auto & elem : ba - > bsa )
{
if ( ! elem . isSecondary ( ) )
{
assert ( info . defender = = nullptr ) ;
info . defender = cb - > getBattle ( battleID ) - > battleGetStackByID ( elem . stackAttacked ) ;
}
else
{
info . secondaryDefender . push_back ( cb - > getBattle ( battleID ) - > battleGetStackByID ( elem . stackAttacked ) ) ;
}
}
2024-09-22 18:25:18 +02:00
assert ( info . defender ! = nullptr | | ( info . spellEffect ! = SpellID : : NONE & & info . indirectAttack ) ) ;
2023-10-19 16:19:09 +02:00
assert ( info . attacker ! = nullptr ) ;
battleInt - > stackAttacking ( info ) ;
}
void CPlayerInterface : : battleGateStateChanged ( const BattleID & battleID , const EGateState state )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > gateStateChanged ( state ) ;
}
void CPlayerInterface : : yourTacticPhase ( const BattleID & battleID , int distance )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
}
void CPlayerInterface : : showInfoDialog ( EInfoWindowMode type , const std : : string & text , const std : : vector < Component > & components , int soundID )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
bool autoTryHover = settings [ " gameTweaks " ] [ " infoBarPick " ] . Bool ( ) & & type = = EInfoWindowMode : : AUTO ;
auto timer = type = = EInfoWindowMode : : INFO ? 3000 : 4500 ; //Implement long info windows like in HD mod
if ( autoTryHover | | type = = EInfoWindowMode : : INFO )
{
waitWhileDialog ( ) ; //Fix for mantis #98
adventureInt - > showInfoBoxMessage ( components , text , timer ) ;
// abort movement, if any. Strictly speaking unnecessary, but prevents some edge cases, like movement sound on visiting Magi Hut with "show messages in status window" on
movementController - > requestMovementAbort ( ) ;
if ( makingTurn & & GH . windows ( ) . count ( ) > 0 & & LOCPLINT = = this )
CCS - > soundh - > playSound ( static_cast < soundBase : : soundID > ( soundID ) ) ;
return ;
}
if ( settings [ " session " ] [ " autoSkip " ] . Bool ( ) & & ! GH . isKeyboardShiftDown ( ) )
{
return ;
}
std : : vector < Component > vect = components ; //I do not know currently how to avoid copy here
do
{
std : : vector < Component > sender = { vect . begin ( ) , vect . begin ( ) + std : : min ( vect . size ( ) , static_cast < size_t > ( 8 ) ) } ;
std : : vector < std : : shared_ptr < CComponent > > intComps ;
for ( auto & component : sender )
intComps . push_back ( std : : make_shared < CComponent > ( component ) ) ;
showInfoDialog ( text , intComps , soundID ) ;
vect . erase ( vect . begin ( ) , vect . begin ( ) + std : : min ( vect . size ( ) , static_cast < size_t > ( 8 ) ) ) ;
}
while ( ! vect . empty ( ) ) ;
}
void CPlayerInterface : : showInfoDialog ( const std : : string & text , std : : shared_ptr < CComponent > component )
{
std : : vector < std : : shared_ptr < CComponent > > intComps ;
intComps . push_back ( component ) ;
showInfoDialog ( text , intComps , soundBase : : sound_todo ) ;
}
void CPlayerInterface : : showInfoDialog ( const std : : string & text , const std : : vector < std : : shared_ptr < CComponent > > & components , int soundID )
{
LOG_TRACE_PARAMS ( logGlobal , " player=%s, text=%s, is LOCPLINT=%d " , playerID % text % ( this = = LOCPLINT ) ) ;
waitWhileDialog ( ) ;
if ( settings [ " session " ] [ " autoSkip " ] . Bool ( ) & & ! GH . isKeyboardShiftDown ( ) )
{
return ;
}
std : : shared_ptr < CInfoWindow > temp = CInfoWindow : : create ( text , playerID , components ) ;
2024-08-23 23:30:50 +02:00
if ( ( makingTurn | | ( battleInt & & battleInt - > curInt & & battleInt - > curInt . get ( ) = = this ) ) & & GH . windows ( ) . count ( ) > 0 & & LOCPLINT = = this )
2023-10-19 16:19:09 +02:00
{
CCS - > soundh - > playSound ( static_cast < soundBase : : soundID > ( soundID ) ) ;
2024-05-18 13:04:10 +02:00
showingDialog - > setBusy ( ) ;
2023-10-19 16:19:09 +02:00
movementController - > requestMovementAbort ( ) ; // interrupt movement to show dialog
GH . windows ( ) . pushWindow ( temp ) ;
}
else
{
dialogs . push_back ( temp ) ;
}
}
void CPlayerInterface : : showInfoDialogAndWait ( std : : vector < Component > & components , const MetaString & text )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
std : : string str = text . toString ( ) ;
showInfoDialog ( EInfoWindowMode : : MODAL , str , components , 0 ) ;
waitWhileDialog ( ) ;
}
void CPlayerInterface : : showYesNoDialog ( const std : : string & text , CFunctionList < void ( ) > onYes , CFunctionList < void ( ) > onNo , const std : : vector < std : : shared_ptr < CComponent > > & components )
{
movementController - > requestMovementAbort ( ) ;
2024-05-18 13:04:10 +02:00
LOCPLINT - > showingDialog - > setBusy ( ) ;
2023-10-19 16:19:09 +02:00
CInfoWindow : : showYesNoDialog ( text , components , onYes , onNo , playerID ) ;
}
2024-04-17 01:08:27 +02:00
void CPlayerInterface : : showBlockingDialog ( const std : : string & text , const std : : vector < Component > & components , QueryID askID , const int soundID , bool selection , bool cancel , bool safeToAutoaccept )
2023-10-19 16:19:09 +02:00
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
waitWhileDialog ( ) ;
movementController - > requestMovementAbort ( ) ;
CCS - > soundh - > playSound ( static_cast < soundBase : : soundID > ( soundID ) ) ;
if ( ! selection & & cancel ) //simple yes/no dialog
{
2024-04-17 01:08:27 +02:00
if ( settings [ " general " ] [ " enableUiEnhancements " ] . Bool ( ) & & safeToAutoaccept )
{
cb - > selectionMade ( 1 , askID ) ; //as in HD mod, we try to skip dialogs that server considers visual fluff which does not affect gamestate
return ;
}
2023-10-19 16:19:09 +02:00
std : : vector < std : : shared_ptr < CComponent > > intComps ;
for ( auto & component : components )
intComps . push_back ( std : : make_shared < CComponent > ( component ) ) ; //will be deleted by close in window
showYesNoDialog ( text , [ = ] ( ) { cb - > selectionMade ( 1 , askID ) ; } , [ = ] ( ) { cb - > selectionMade ( 0 , askID ) ; } , intComps ) ;
}
else if ( selection )
{
std : : vector < std : : shared_ptr < CSelectableComponent > > intComps ;
for ( auto & component : components )
intComps . push_back ( std : : make_shared < CSelectableComponent > ( component ) ) ; //will be deleted by CSelWindow::close
std : : vector < std : : pair < AnimationPath , CFunctionList < void ( ) > > > pom ;
pom . push_back ( { AnimationPath : : builtin ( " IOKAY.DEF " ) , 0 } ) ;
if ( cancel )
{
pom . push_back ( { AnimationPath : : builtin ( " ICANCEL.DEF " ) , 0 } ) ;
}
int charperline = 35 ;
if ( pom . size ( ) > 1 )
charperline = 50 ;
GH . windows ( ) . createAndPushWindow < CSelWindow > ( text , playerID , charperline , intComps , pom , askID ) ;
intComps [ 0 ] - > clickPressed ( GH . getCursorPosition ( ) ) ;
intComps [ 0 ] - > clickReleased ( GH . getCursorPosition ( ) ) ;
}
}
void CPlayerInterface : : showTeleportDialog ( const CGHeroInstance * hero , TeleportChannelID channel , TTeleportExitsList exits , bool impassable , QueryID askID )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
movementController - > showTeleportDialog ( hero , channel , exits , impassable , askID ) ;
}
void CPlayerInterface : : showMapObjectSelectDialog ( QueryID askID , const Component & icon , const MetaString & title , const MetaString & description , const std : : vector < ObjectInstanceID > & objects )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
2024-06-17 22:46:47 +02:00
std : : vector < ObjectInstanceID > objectGuiOrdered = objects ;
std : : map < ObjectInstanceID , int > townOrder ;
auto ownedTowns = localState - > getOwnedTowns ( ) ;
for ( int i = 0 ; i < ownedTowns . size ( ) ; + + i )
townOrder [ ownedTowns [ i ] - > id ] = i ;
auto townComparator = [ & townOrder ] ( const ObjectInstanceID & left , const ObjectInstanceID & right ) {
uint32_t leftIndex = townOrder . count ( left ) ? townOrder . at ( left ) : std : : numeric_limits < uint32_t > : : max ( ) ;
uint32_t rightIndex = townOrder . count ( right ) ? townOrder . at ( right ) : std : : numeric_limits < uint32_t > : : max ( ) ;
return leftIndex < rightIndex ;
} ;
std : : stable_sort ( objectGuiOrdered . begin ( ) , objectGuiOrdered . end ( ) , townComparator ) ;
2024-01-31 22:29:04 +02:00
2023-10-19 16:19:09 +02:00
auto selectCallback = [ = ] ( int selection )
{
cb - > sendQueryReply ( selection , askID ) ;
} ;
auto cancelCallback = [ = ] ( )
{
cb - > sendQueryReply ( std : : nullopt , askID ) ;
} ;
const std : : string localTitle = title . toString ( ) ;
const std : : string localDescription = description . toString ( ) ;
std : : vector < int > tempList ;
2024-06-17 22:46:47 +02:00
tempList . reserve ( objectGuiOrdered . size ( ) ) ;
2023-10-19 16:19:09 +02:00
2024-07-15 09:46:40 +02:00
for ( const auto & item : objectGuiOrdered )
2023-10-19 16:19:09 +02:00
tempList . push_back ( item . getNum ( ) ) ;
CComponent localIconC ( icon ) ;
std : : shared_ptr < CIntObject > localIcon = localIconC . image ;
localIconC . removeChild ( localIcon . get ( ) , false ) ;
2024-01-31 03:30:10 +02:00
std : : vector < std : : shared_ptr < IImage > > images ;
2024-07-15 09:46:40 +02:00
for ( const auto & obj : objectGuiOrdered )
2024-01-31 03:30:10 +02:00
{
if ( ! settings [ " general " ] [ " enableUiEnhancements " ] . Bool ( ) )
break ;
const CGTownInstance * t = dynamic_cast < const CGTownInstance * > ( cb - > getObj ( obj ) ) ;
if ( t )
{
2024-10-05 21:37:52 +02:00
auto image = GH . renderHandler ( ) . loadImage ( AnimationPath : : builtin ( " ITPA " ) , t - > getTown ( ) - > clientInfo . icons [ t - > hasFort ( ) ] [ false ] + 2 , 0 , EImageBlitMode : : OPAQUE ) ;
2024-07-25 20:22:27 +02:00
image - > scaleTo ( Point ( 35 , 23 ) ) ;
2024-06-04 13:46:45 +02:00
images . push_back ( image ) ;
2024-01-31 03:30:10 +02:00
}
}
auto wnd = std : : make_shared < CObjectListWindow > ( tempList , localIcon , localTitle , localDescription , selectCallback , 0 , images ) ;
2023-10-19 16:19:09 +02:00
wnd - > onExit = cancelCallback ;
2024-06-17 22:46:47 +02:00
wnd - > onPopup = [ this , objectGuiOrdered ] ( int index ) { CRClickPopup : : createAndPush ( cb - > getObj ( objectGuiOrdered [ index ] ) , GH . getCursorPosition ( ) ) ; } ;
wnd - > onClicked = [ this , objectGuiOrdered ] ( int index ) { adventureInt - > centerOnObject ( cb - > getObj ( objectGuiOrdered [ index ] ) ) ; GH . windows ( ) . totalRedraw ( ) ; } ;
2023-10-19 16:19:09 +02:00
GH . windows ( ) . pushWindow ( wnd ) ;
}
void CPlayerInterface : : tileRevealed ( const std : : unordered_set < int3 > & pos )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
//FIXME: wait for dialog? Magi hut/eye would benefit from this but may break other areas
adventureInt - > onMapTilesChanged ( pos ) ;
}
void CPlayerInterface : : tileHidden ( const std : : unordered_set < int3 > & pos )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
adventureInt - > onMapTilesChanged ( pos ) ;
}
void CPlayerInterface : : openHeroWindow ( const CGHeroInstance * hero )
{
GH . windows ( ) . createAndPushWindow < CHeroWindow > ( hero ) ;
}
void CPlayerInterface : : availableCreaturesChanged ( const CGDwelling * town )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( const CGTownInstance * townObj = dynamic_cast < const CGTownInstance * > ( town ) )
{
for ( auto fortScreen : GH . windows ( ) . findWindows < CFortScreen > ( ) )
fortScreen - > creaturesChangedEventHandler ( ) ;
for ( auto castleInterface : GH . windows ( ) . findWindows < CCastleInterface > ( ) )
if ( castleInterface - > town = = town )
castleInterface - > creaturesChangedEventHandler ( ) ;
if ( townObj )
for ( auto ki : GH . windows ( ) . findWindows < CKingdomInterface > ( ) )
ki - > townChanged ( townObj ) ;
}
else if ( town & & GH . windows ( ) . count ( ) > 0 & & ( town - > ID = = Obj : : CREATURE_GENERATOR1
| | town - > ID = = Obj : : CREATURE_GENERATOR4 | | town - > ID = = Obj : : WAR_MACHINE_FACTORY ) )
{
for ( auto crw : GH . windows ( ) . findWindows < CRecruitmentWindow > ( ) )
if ( crw - > dwelling = = town )
crw - > availableCreaturesChanged ( ) ;
}
}
void CPlayerInterface : : heroBonusChanged ( const CGHeroInstance * hero , const Bonus & bonus , bool gain )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( bonus . type = = BonusType : : NONE )
return ;
adventureInt - > onHeroChanged ( hero ) ;
if ( ( bonus . type = = BonusType : : FLYING_MOVEMENT | | bonus . type = = BonusType : : WATER_WALKING ) & & ! gain )
{
//recalculate paths because hero has lost bonus influencing pathfinding
localState - > erasePath ( hero ) ;
}
}
void CPlayerInterface : : moveHero ( const CGHeroInstance * h , const CGPath & path )
{
2024-07-03 17:46:05 +02:00
LOG_TRACE ( logGlobal ) ;
if ( ! LOCPLINT - > makingTurn )
return ;
2023-10-19 16:19:09 +02:00
assert ( h ) ;
2024-05-18 13:04:10 +02:00
assert ( ! showingDialog - > isBusy ( ) ) ;
2023-10-19 16:19:09 +02:00
assert ( dialogs . empty ( ) ) ;
if ( ! h )
return ; //can't find hero
//It shouldn't be possible to move hero with open dialog (or dialog waiting in bg)
2024-05-18 13:04:10 +02:00
if ( showingDialog - > isBusy ( ) | | ! dialogs . empty ( ) )
2023-10-19 16:19:09 +02:00
return ;
if ( localState - > isHeroSleeping ( h ) )
localState - > setHeroAwaken ( h ) ;
movementController - > requestMovementStart ( h , path ) ;
}
void CPlayerInterface : : showGarrisonDialog ( const CArmedInstance * up , const CGHeroInstance * down , bool removableUnits , QueryID queryID )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
auto onEnd = [ = ] ( ) { cb - > selectionMade ( 0 , queryID ) ; } ;
if ( movementController - > isHeroMovingThroughGarrison ( down , up ) )
{
onEnd ( ) ;
return ;
}
waitForAllDialogs ( ) ;
auto cgw = std : : make_shared < CGarrisonWindow > ( up , down , removableUnits ) ;
cgw - > quit - > addCallback ( onEnd ) ;
GH . windows ( ) . pushWindow ( cgw ) ;
}
void CPlayerInterface : : requestRealized ( PackageApplied * pa )
{
2023-11-08 22:05:36 +02:00
if ( pa - > packType = = CTypeList : : getInstance ( ) . getTypeID < MoveHero > ( nullptr ) )
2023-10-19 16:19:09 +02:00
movementController - > onMoveHeroApplied ( ) ;
2023-11-08 22:05:36 +02:00
if ( pa - > packType = = CTypeList : : getInstance ( ) . getTypeID < QueryReply > ( nullptr ) )
2023-10-19 16:19:09 +02:00
movementController - > onQueryReplyApplied ( ) ;
}
void CPlayerInterface : : showHeroExchange ( ObjectInstanceID hero1 , ObjectInstanceID hero2 )
{
heroExchangeStarted ( hero1 , hero2 , QueryID ( - 1 ) ) ;
}
void CPlayerInterface : : heroExchangeStarted ( ObjectInstanceID hero1 , ObjectInstanceID hero2 , QueryID query )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
GH . windows ( ) . createAndPushWindow < CExchangeWindow > ( hero1 , hero2 , query ) ;
}
void CPlayerInterface : : beforeObjectPropertyChanged ( const SetObjectProperty * sop )
{
if ( sop - > what = = ObjProperty : : OWNER )
{
const CGObjectInstance * obj = cb - > getObj ( sop - > id ) ;
if ( obj - > ID = = Obj : : TOWN )
{
auto town = static_cast < const CGTownInstance * > ( obj ) ;
if ( obj - > tempOwner = = playerID )
{
localState - > removeOwnedTown ( town ) ;
adventureInt - > onTownChanged ( town ) ;
}
}
}
}
void CPlayerInterface : : objectPropertyChanged ( const SetObjectProperty * sop )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( sop - > what = = ObjProperty : : OWNER )
{
const CGObjectInstance * obj = cb - > getObj ( sop - > id ) ;
if ( obj - > ID = = Obj : : TOWN )
{
auto town = static_cast < const CGTownInstance * > ( obj ) ;
if ( obj - > tempOwner = = playerID )
{
localState - > addOwnedTown ( town ) ;
adventureInt - > onTownChanged ( town ) ;
}
}
//redraw minimap if owner changed
std : : set < int3 > pos = obj - > getBlockedPos ( ) ;
std : : unordered_set < int3 > upos ( pos . begin ( ) , pos . end ( ) ) ;
adventureInt - > onMapTilesChanged ( upos ) ;
assert ( cb - > getTownsInfo ( ) . size ( ) = = localState - > getOwnedTowns ( ) . size ( ) ) ;
}
}
void CPlayerInterface : : initializeHeroTownList ( )
{
if ( localState - > getWanderingHeroes ( ) . empty ( ) )
{
for ( auto & hero : cb - > getHeroesInfo ( ) )
{
if ( ! hero - > inTownGarrison )
localState - > addWanderingHero ( hero ) ;
}
}
if ( localState - > getOwnedTowns ( ) . empty ( ) )
{
for ( auto & town : cb - > getTownsInfo ( ) )
localState - > addOwnedTown ( town ) ;
}
2024-10-08 21:55:51 +02:00
localState - > deserialize ( * cb - > getPlayerState ( playerID ) - > playerLocalSettings ) ;
2023-10-19 16:19:09 +02:00
if ( adventureInt )
adventureInt - > onHeroChanged ( nullptr ) ;
}
void CPlayerInterface : : showRecruitmentDialog ( const CGDwelling * dwelling , const CArmedInstance * dst , int level , QueryID queryID )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
waitWhileDialog ( ) ;
auto recruitCb = [ = ] ( CreatureID id , int count )
{
cb - > recruitCreatures ( dwelling , dst , id , count , - 1 ) ;
} ;
auto closeCb = [ = ] ( )
{
cb - > selectionMade ( 0 , queryID ) ;
} ;
GH . windows ( ) . createAndPushWindow < CRecruitmentWindow > ( dwelling , level , dst , recruitCb , closeCb ) ;
}
void CPlayerInterface : : waitWhileDialog ( )
{
if ( GH . amIGuiThread ( ) )
{
logGlobal - > warn ( " Cannot wait for dialogs in gui thread (deadlock risk)! " ) ;
return ;
}
auto unlockInterface = vstd : : makeUnlockGuard ( GH . interfaceMutex ) ;
2024-05-18 13:04:10 +02:00
showingDialog - > waitWhileBusy ( ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : showShipyardDialog ( const IShipyard * obj )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
auto state = obj - > shipyardStatus ( ) ;
TResources cost ;
obj - > getBoatCost ( cost ) ;
GH . windows ( ) . createAndPushWindow < CShipyardWindow > ( cost , state , obj - > getBoatType ( ) , [ = ] ( ) { cb - > buildBoat ( obj ) ; } ) ;
}
void CPlayerInterface : : newObject ( const CGObjectInstance * obj )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
//we might have built a boat in shipyard in opened town screen
if ( obj - > ID = = Obj : : BOAT
& & LOCPLINT - > castleInt
& & obj - > visitablePos ( ) = = LOCPLINT - > castleInt - > town - > bestLocation ( ) )
{
CCS - > soundh - > playSound ( soundBase : : newBuilding ) ;
LOCPLINT - > castleInt - > addBuilding ( BuildingID : : SHIP ) ;
}
}
void CPlayerInterface : : centerView ( int3 pos , int focusTime )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
waitWhileDialog ( ) ;
CCS - > curh - > hide ( ) ;
adventureInt - > centerOnTile ( pos ) ;
if ( focusTime )
{
GH . windows ( ) . totalRedraw ( ) ;
{
IgnoreEvents ignore ( * this ) ;
auto unlockInterface = vstd : : makeUnlockGuard ( GH . interfaceMutex ) ;
boost : : this_thread : : sleep_for ( boost : : chrono : : milliseconds ( focusTime ) ) ;
}
}
CCS - > curh - > show ( ) ;
}
void CPlayerInterface : : objectRemoved ( const CGObjectInstance * obj , const PlayerColor & initiator )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
2024-06-01 17:28:17 +02:00
if ( playerID = = initiator )
2023-10-19 16:19:09 +02:00
{
2024-06-01 17:28:17 +02:00
auto removalSound = obj - > getRemovalSound ( CRandomGenerator : : getDefault ( ) ) ;
if ( removalSound )
{
waitWhileDialog ( ) ;
CCS - > soundh - > playSound ( removalSound . value ( ) ) ;
}
2023-10-19 16:19:09 +02:00
}
CGI - > mh - > waitForOngoingAnimations ( ) ;
if ( obj - > ID = = Obj : : HERO & & obj - > tempOwner = = playerID )
{
const CGHeroInstance * h = static_cast < const CGHeroInstance * > ( obj ) ;
heroKilled ( h ) ;
}
2024-09-28 22:58:05 +02:00
if ( obj - > ID = = Obj : : TOWN & & obj - > tempOwner = = playerID )
{
const CGTownInstance * t = static_cast < const CGTownInstance * > ( obj ) ;
townRemoved ( t ) ;
}
2023-10-19 16:19:09 +02:00
GH . fakeMouseMove ( ) ;
}
void CPlayerInterface : : objectRemovedAfter ( )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
adventureInt - > onMapTilesChanged ( boost : : none ) ;
// visiting or garrisoned hero removed - update window
if ( castleInt )
castleInt - > updateGarrisons ( ) ;
for ( auto ki : GH . windows ( ) . findWindows < CKingdomInterface > ( ) )
ki - > heroRemoved ( ) ;
}
void CPlayerInterface : : playerBlocked ( int reason , bool start )
{
if ( reason = = PlayerBlocked : : EReason : : UPCOMING_BATTLE )
{
if ( CSH - > howManyPlayerInterfaces ( ) > 1 & & LOCPLINT ! = this & & LOCPLINT - > makingTurn = = false )
{
//one of our players who isn't last in order got attacked not by our another player (happens for example in hotseat mode)
LOCPLINT = this ;
GH . curInt = this ;
adventureInt - > onCurrentPlayerChanged ( playerID ) ;
std : : string msg = CGI - > generaltexth - > translate ( " vcmi.adventureMap.playerAttacked " ) ;
boost : : replace_first ( msg , " %s " , cb - > getStartInfo ( ) - > playerInfos . find ( playerID ) - > second . name ) ;
std : : vector < std : : shared_ptr < CComponent > > cmp ;
2023-10-31 11:09:56 +02:00
cmp . push_back ( std : : make_shared < CComponent > ( ComponentType : : FLAG , playerID ) ) ;
2023-10-19 16:19:09 +02:00
makingTurn = true ; //workaround for stiff showInfoDialog implementation
showInfoDialog ( msg , cmp ) ;
2024-09-19 17:30:51 +02:00
waitWhileDialog ( ) ;
2023-10-19 16:19:09 +02:00
makingTurn = false ;
}
}
}
void CPlayerInterface : : update ( )
{
// Make sure that gamestate won't change when GUI objects may obtain its parts on event processing or drawing request
2024-06-01 13:48:30 +02:00
boost : : shared_lock gsLock ( CGameState : : mutex ) ;
2023-10-19 16:19:09 +02:00
// While mutexes were locked away we may be have stopped being the active interface
if ( LOCPLINT ! = this )
return ;
//if there are any waiting dialogs, show them
2024-08-04 15:14:28 +02:00
if ( makingTurn & & ! dialogs . empty ( ) & & ! showingDialog - > isBusy ( ) )
2023-10-19 16:19:09 +02:00
{
2024-05-18 13:04:10 +02:00
showingDialog - > setBusy ( ) ;
2023-10-19 16:19:09 +02:00
GH . windows ( ) . pushWindow ( dialogs . front ( ) ) ;
dialogs . pop_front ( ) ;
}
assert ( adventureInt ) ;
// Handles mouse and key input
GH . handleEvents ( ) ;
GH . windows ( ) . simpleRedraw ( ) ;
}
2024-05-18 13:04:10 +02:00
void CPlayerInterface : : endNetwork ( )
{
showingDialog - > requestTermination ( ) ;
}
2023-10-19 16:19:09 +02:00
int CPlayerInterface : : getLastIndex ( std : : string namePrefix )
{
using namespace boost : : filesystem ;
using namespace boost : : algorithm ;
path gamesDir = VCMIDirs : : get ( ) . userSavePath ( ) ;
std : : map < std : : time_t , int > dates ; //save number => datestamp
const directory_iterator enddir ;
if ( ! exists ( gamesDir ) )
create_directory ( gamesDir ) ;
else
for ( directory_iterator dir ( gamesDir ) ; dir ! = enddir ; + + dir )
{
if ( is_regular_file ( dir - > status ( ) ) )
{
std : : string name = dir - > path ( ) . filename ( ) . string ( ) ;
if ( starts_with ( name , namePrefix ) & & ends_with ( name , " .vcgm1 " ) )
{
char nr = name [ namePrefix . size ( ) ] ;
if ( std : : isdigit ( nr ) )
dates [ last_write_time ( dir - > path ( ) ) ] = boost : : lexical_cast < int > ( nr ) ;
}
}
}
if ( ! dates . empty ( ) )
return ( - - dates . end ( ) ) - > second ; //return latest file number
return 0 ;
}
void CPlayerInterface : : gameOver ( PlayerColor player , const EVictoryLossCheckResult & victoryLossCheckResult )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( player = = playerID )
{
if ( victoryLossCheckResult . loss ( ) )
showInfoDialog ( CGI - > generaltexth - > allTexts [ 95 ] ) ;
assert ( GH . curInt = = LOCPLINT ) ;
auto previousInterface = LOCPLINT ; //without multiple player interfaces some of lines below are useless, but for hotseat we wanna swap player interface temporarily
LOCPLINT = this ; //this is needed for dialog to show and avoid freeze, dialog showing logic should be reworked someday
GH . curInt = this ; //waiting for dialogs requires this to get events
if ( ! makingTurn )
{
makingTurn = true ; //also needed for dialog to show with current implementation
waitForAllDialogs ( ) ;
makingTurn = false ;
}
else
waitForAllDialogs ( ) ;
GH . curInt = previousInterface ;
LOCPLINT = previousInterface ;
}
}
void CPlayerInterface : : playerBonusChanged ( const Bonus & bonus , bool gain )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
}
void CPlayerInterface : : showPuzzleMap ( )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
waitWhileDialog ( ) ;
//TODO: interface should not know the real position of Grail...
double ratio = 0 ;
int3 grailPos = cb - > getGrailPos ( & ratio ) ;
GH . windows ( ) . createAndPushWindow < CPuzzleWindow > ( grailPos , ratio ) ;
}
void CPlayerInterface : : viewWorldMap ( )
{
adventureInt - > openWorldView ( ) ;
}
void CPlayerInterface : : advmapSpellCast ( const CGHeroInstance * caster , SpellID spellID )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
if ( GH . windows ( ) . topWindow < CSpellWindow > ( ) )
GH . windows ( ) . popWindows ( 1 ) ;
if ( spellID = = SpellID : : FLY | | spellID = = SpellID : : WATER_WALK )
localState - > erasePath ( caster ) ;
auto castSoundPath = spellID . toSpell ( ) - > getCastSound ( ) ;
if ( ! castSoundPath . empty ( ) )
CCS - > soundh - > playSound ( castSoundPath ) ;
}
void CPlayerInterface : : tryDigging ( const CGHeroInstance * h )
{
int msgToShow = - 1 ;
const auto diggingStatus = h - > diggingStatus ( ) ;
switch ( diggingStatus )
{
case EDiggingStatus : : CAN_DIG :
break ;
case EDiggingStatus : : LACK_OF_MOVEMENT :
msgToShow = 56 ; //"Digging for artifacts requires a whole day, try again tomorrow."
break ;
case EDiggingStatus : : TILE_OCCUPIED :
msgToShow = 97 ; //Try searching on clear ground.
break ;
case EDiggingStatus : : WRONG_TERRAIN :
msgToShow = 60 ; ////Try looking on land!
break ;
case EDiggingStatus : : BACKPACK_IS_FULL :
msgToShow = 247 ; //Searching for the Grail is fruitless...
break ;
default :
assert ( 0 ) ;
}
if ( msgToShow < 0 )
cb - > dig ( h ) ;
else
showInfoDialog ( CGI - > generaltexth - > allTexts [ msgToShow ] ) ;
}
void CPlayerInterface : : battleNewRoundFirst ( const BattleID & battleID )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
BATTLE_EVENT_POSSIBLE_RETURN ;
battleInt - > newRoundFirst ( ) ;
}
2024-08-17 21:06:48 +02:00
void CPlayerInterface : : showMarketWindow ( const IMarket * market , const CGHeroInstance * visitor , QueryID queryID )
2023-10-19 16:19:09 +02:00
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
auto onWindowClosed = [ this , queryID ] ( ) {
cb - > selectionMade ( 0 , queryID ) ;
} ;
if ( market - > allowsTrade ( EMarketMode : : ARTIFACT_EXP ) & & visitor - > getAlignment ( ) ! = EAlignment : : EVIL )
2024-02-03 13:26:24 +02:00
GH . windows ( ) . createAndPushWindow < CMarketWindow > ( market , visitor , onWindowClosed , EMarketMode : : ARTIFACT_EXP ) ;
2023-10-19 16:19:09 +02:00
else if ( market - > allowsTrade ( EMarketMode : : CREATURE_EXP ) & & visitor - > getAlignment ( ) ! = EAlignment : : GOOD )
2024-02-03 13:26:24 +02:00
GH . windows ( ) . createAndPushWindow < CMarketWindow > ( market , visitor , onWindowClosed , EMarketMode : : CREATURE_EXP ) ;
2023-10-19 16:19:09 +02:00
else if ( market - > allowsTrade ( EMarketMode : : CREATURE_UNDEAD ) )
GH . windows ( ) . createAndPushWindow < CTransformerWindow > ( market , visitor , onWindowClosed ) ;
2024-08-20 16:15:50 +02:00
else if ( ! market - > availableModes ( ) . empty ( ) )
for ( auto mode = EMarketMode : : RESOURCE_RESOURCE ; mode ! = EMarketMode : : MARKET_AFTER_LAST_PLACEHOLDER ; mode = vstd : : next ( mode , 1 ) )
{
if ( vstd : : contains ( market - > availableModes ( ) , mode ) )
{
GH . windows ( ) . createAndPushWindow < CMarketWindow > ( market , visitor , onWindowClosed , mode ) ;
break ;
}
}
else
onWindowClosed ( ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : showUniversityWindow ( const IMarket * market , const CGHeroInstance * visitor , QueryID queryID )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
auto onWindowClosed = [ this , queryID ] ( ) {
cb - > selectionMade ( 0 , queryID ) ;
} ;
2024-08-26 23:25:39 +02:00
GH . windows ( ) . createAndPushWindow < CUniversityWindow > ( visitor , BuildingID : : NONE , market , onWindowClosed ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : showHillFortWindow ( const CGObjectInstance * object , const CGHeroInstance * visitor )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
GH . windows ( ) . createAndPushWindow < CHillFortWindow > ( visitor , object ) ;
}
void CPlayerInterface : : availableArtifactsChanged ( const CGBlackMarket * bm )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
2024-06-05 15:24:36 +02:00
for ( auto cmw : GH . windows ( ) . findWindows < IMarketHolder > ( ) )
2024-02-29 22:33:12 +02:00
cmw - > updateArtifacts ( ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : showTavernWindow ( const CGObjectInstance * object , const CGHeroInstance * visitor , QueryID queryID )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
auto onWindowClosed = [ this , queryID ] ( ) {
2024-01-18 22:21:07 +02:00
if ( queryID ! = QueryID : : NONE )
cb - > selectionMade ( 0 , queryID ) ;
2023-10-19 16:19:09 +02:00
} ;
GH . windows ( ) . createAndPushWindow < CTavernWindow > ( object , onWindowClosed ) ;
}
void CPlayerInterface : : showThievesGuildWindow ( const CGObjectInstance * obj )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
GH . windows ( ) . createAndPushWindow < CThievesGuildWindow > ( obj ) ;
}
void CPlayerInterface : : showQuestLog ( )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
GH . windows ( ) . createAndPushWindow < CQuestLog > ( LOCPLINT - > cb - > getMyQuests ( ) ) ;
}
void CPlayerInterface : : showShipyardDialogOrProblemPopup ( const IShipyard * obj )
{
if ( obj - > shipyardStatus ( ) ! = IBoatGenerator : : GOOD )
{
MetaString txt ;
obj - > getProblemText ( txt ) ;
showInfoDialog ( txt . toString ( ) ) ;
}
else
showShipyardDialog ( obj ) ;
}
void CPlayerInterface : : askToAssembleArtifact ( const ArtifactLocation & al )
{
2024-07-15 23:03:06 +02:00
artifactController - > askToAssemble ( al , true , true ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : artifactPut ( const ArtifactLocation & al )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
2023-10-14 21:00:39 +02:00
adventureInt - > onHeroChanged ( cb - > getHero ( al . artHolder ) ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : artifactRemoved ( const ArtifactLocation & al )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
2023-10-14 21:00:39 +02:00
adventureInt - > onHeroChanged ( cb - > getHero ( al . artHolder ) ) ;
2024-07-15 23:03:06 +02:00
artifactController - > artifactRemoved ( ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : artifactMoved ( const ArtifactLocation & src , const ArtifactLocation & dst )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
2023-10-14 21:00:39 +02:00
adventureInt - > onHeroChanged ( cb - > getHero ( dst . artHolder ) ) ;
2024-07-15 23:03:06 +02:00
artifactController - > artifactMoved ( ) ;
2023-10-19 16:19:09 +02:00
}
2024-06-23 22:48:19 +02:00
void CPlayerInterface : : bulkArtMovementStart ( size_t totalNumOfArts , size_t possibleAssemblyNumOfArts )
2023-10-19 16:19:09 +02:00
{
2024-07-15 23:03:06 +02:00
artifactController - > bulkArtMovementStart ( totalNumOfArts , possibleAssemblyNumOfArts ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : artifactAssembled ( const ArtifactLocation & al )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
2023-10-14 21:00:39 +02:00
adventureInt - > onHeroChanged ( cb - > getHero ( al . artHolder ) ) ;
2024-07-15 23:03:06 +02:00
artifactController - > artifactAssembled ( ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : artifactDisassembled ( const ArtifactLocation & al )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
2023-10-14 21:00:39 +02:00
adventureInt - > onHeroChanged ( cb - > getHero ( al . artHolder ) ) ;
2024-07-15 23:03:06 +02:00
artifactController - > artifactDisassembled ( ) ;
2023-10-19 16:19:09 +02:00
}
void CPlayerInterface : : waitForAllDialogs ( )
{
2024-08-19 20:20:11 +02:00
if ( ! makingTurn )
return ;
2023-10-19 16:19:09 +02:00
while ( ! dialogs . empty ( ) )
{
auto unlockInterface = vstd : : makeUnlockGuard ( GH . interfaceMutex ) ;
boost : : this_thread : : sleep_for ( boost : : chrono : : milliseconds ( 5 ) ) ;
}
waitWhileDialog ( ) ;
}
void CPlayerInterface : : proposeLoadingGame ( )
{
showYesNoDialog (
CGI - > generaltexth - > allTexts [ 68 ] ,
[ ] ( )
{
2024-02-04 19:56:04 +02:00
CSH - > endGameplay ( ) ;
CMM - > menu - > switchToTab ( " load " ) ;
2023-10-19 16:19:09 +02:00
} ,
nullptr
) ;
}
bool CPlayerInterface : : capturedAllEvents ( )
{
if ( movementController - > isHeroMoving ( ) )
{
//just inform that we are capturing events. they will be processed by heroMoved() in client thread.
return true ;
}
bool needToLockAdventureMap = adventureInt & & adventureInt - > isActive ( ) & & CGI - > mh - > hasOngoingAnimations ( ) ;
bool quickCombatOngoing = isAutoFightOn & & ! battleInt ;
if ( ignoreEvents | | needToLockAdventureMap | | quickCombatOngoing )
{
GH . input ( ) . ignoreEventsUntilInput ( ) ;
return true ;
}
return false ;
}
void CPlayerInterface : : showWorldViewEx ( const std : : vector < ObjectPosInfo > & objectPositions , bool showTerrain )
{
EVENT_HANDLER_CALLED_BY_CLIENT ;
adventureInt - > openWorldView ( objectPositions , showTerrain ) ;
}
std : : optional < BattleAction > CPlayerInterface : : makeSurrenderRetreatDecision ( const BattleID & battleID , const BattleStateInfoForRetreat & battleState )
{
return std : : nullopt ;
}