2023-10-19 16:19:09 +02:00
/*
* NetPacksClient.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 "ClientNetPackVisitors.h"
#include "Client.h"
#include "CPlayerInterface.h"
#include "windows/GUIClasses.h"
2024-09-27 23:52:33 +02:00
#include "windows/CCastleInterface.h"
2023-10-19 16:19:09 +02:00
#include "mapView/mapHandler.h"
2025-10-18 21:43:05 +02:00
#include "mainmenu/CMainMenu.h"
2024-04-07 14:19:57 +03:00
#include "adventureMap/AdventureMapInterface.h"
2023-10-19 16:19:09 +02:00
#include "adventureMap/CInGameConsole.h"
#include "battle/BattleInterface.h"
#include "battle/BattleWindow.h"
2025-02-10 21:49:23 +00:00
#include "GameEngine.h"
2025-02-11 15:23:33 +00:00
#include "GameInstance.h"
2023-10-19 16:19:09 +02:00
#include "gui/WindowHandler.h"
#include "widgets/MiscWidgets.h"
#include "CMT.h"
2024-03-18 19:52:53 +02:00
#include "GameChatHandler.h"
2023-10-19 16:19:09 +02:00
#include "CServerHandler.h"
2025-04-07 20:10:14 +02:00
#include "UIHelper.h"
2023-10-19 16:19:09 +02:00
2025-05-11 11:58:09 +03:00
#include "../lib/callback/CCallback.h"
2023-10-19 16:19:09 +02:00
#include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/FileInfo.h"
2024-07-20 12:55:17 +00:00
#include "../lib/texts/CGeneralTextHandler.h"
2025-02-14 16:23:37 +00:00
#include "../lib/GameLibrary.h"
2023-10-19 16:19:09 +02:00
#include "../lib/mapping/CMap.h"
#include "../lib/VCMIDirs.h"
#include "../lib/CSoundBase.h"
#include "../lib/StartInfo.h"
#include "../lib/CConfigHandler.h"
2025-06-17 14:37:58 +03:00
#include "../lib/mapObjects/MiscObjects.h"
2023-10-19 16:19:09 +02:00
#include "../lib/mapObjects/CGMarket.h"
2024-01-09 16:43:36 +02:00
#include "../lib/mapObjects/CGTownInstance.h"
2023-10-19 16:19:09 +02:00
#include "../lib/gameState/CGameState.h"
#include "../lib/CStack.h"
#include "../lib/battle/BattleInfo.h"
#include "../lib/GameConstants.h"
#include "../lib/CPlayerState.h"
// TODO: as Tow suggested these template should all be part of CClient
// This will require rework spectator interface properly though
template < typename T , typename ... Args , typename ... Args2 >
bool callOnlyThatInterface ( CClient & cl , PlayerColor player , void ( T ::* ptr )( Args ...), Args2 && ... args )
{
if ( vstd :: contains ( cl . playerint , player ))
{
(( * cl . playerint [ player ]). * ptr )( std :: forward < Args2 > ( args )...);
return true ;
}
return false ;
}
template < typename T , typename ... Args , typename ... Args2 >
bool callInterfaceIfPresent ( CClient & cl , PlayerColor player , void ( T ::* ptr )( Args ...), Args2 && ... args )
{
bool called = callOnlyThatInterface ( cl , player , ptr , std :: forward < Args2 > ( args )...);
return called ;
}
template < typename T , typename ... Args , typename ... Args2 >
void callOnlyThatBattleInterface ( CClient & cl , PlayerColor player , void ( T ::* ptr )( Args ...), Args2 && ... args )
{
if ( vstd :: contains ( cl . battleints , player ))
(( * cl . battleints [ player ]). * ptr )( std :: forward < Args2 > ( args )...);
if ( cl . additionalBattleInts . count ( player ))
{
for ( auto bInt : cl . additionalBattleInts [ player ])
(( * bInt ). * ptr )( std :: forward < Args2 > ( args )...);
}
}
template < typename T , typename ... Args , typename ... Args2 >
void callBattleInterfaceIfPresent ( CClient & cl , PlayerColor player , void ( T ::* ptr )( Args ...), Args2 && ... args )
{
callOnlyThatInterface ( cl , player , ptr , std :: forward < Args2 > ( args )...);
}
//calls all normal interfaces and privileged ones, playerints may be updated when iterating over it, so we need a copy
template < typename T , typename ... Args , typename ... Args2 >
void callAllInterfaces ( CClient & cl , void ( T ::* ptr )( Args ...), Args2 && ... args )
{
for ( auto pInt : cl . playerint )
(( * pInt . second ). * ptr )( std :: forward < Args2 > ( args )...);
}
//calls all normal interfaces and privileged ones, playerints may be updated when iterating over it, so we need a copy
template < typename T , typename ... Args , typename ... Args2 >
void callBattleInterfaceIfPresentForBothSides ( CClient & cl , const BattleID & battleID , void ( T ::* ptr )( Args ...), Args2 && ... args )
{
2025-04-19 14:14:12 +03:00
assert ( cl . gameState (). getBattle ( battleID ));
2023-10-19 16:19:09 +02:00
2025-04-19 14:14:12 +03:00
if ( ! cl . gameState (). getBattle ( battleID ))
2023-10-19 16:19:09 +02:00
{
logGlobal -> error ( "Attempt to call battle interface without ongoing battle!" );
return ;
}
2025-04-19 14:14:12 +03:00
callOnlyThatBattleInterface ( cl , cl . gameState (). getBattle ( battleID ) -> getSide ( BattleSide :: ATTACKER ). color , ptr , std :: forward < Args2 > ( args )...);
callOnlyThatBattleInterface ( cl , cl . gameState (). getBattle ( battleID ) -> getSide ( BattleSide :: DEFENDER ). color , ptr , std :: forward < Args2 > ( args )...);
2025-02-11 15:23:33 +00:00
if ( settings [ "session" ][ "spectate" ]. Bool () && ! settings [ "session" ][ "spectate-skip-battle" ]. Bool () && GAME -> interface () -> battleInt )
2023-10-19 16:19:09 +02:00
{
callOnlyThatBattleInterface ( cl , PlayerColor :: SPECTATOR , ptr , std :: forward < Args2 > ( args )...);
}
}
2026-05-26 23:44:36 +02:00
static void showEagleEyeLearnedSpellsDialog ( CClient & cl , ObjectInstanceID heroId , const std :: set < SpellID > & spells , PlayerColor player )
{
if ( spells . empty ())
return ;
const auto * hero = cl . gameInfo (). getHero ( heroId );
assert ( hero );
callInterfaceIfPresent ( cl , player , & CGameInterface :: showInfoDialog , EInfoWindowMode :: AUTO , UIHelper :: getEagleEyeInfoWindowText ( * hero , spells ), UIHelper :: getSpellsComponents ( spells ), soundBase :: soundID ( 0 ));
}
2023-10-19 16:19:09 +02:00
void ApplyClientNetPackVisitor :: visitSetResources ( SetResources & pack )
{
2024-06-24 03:23:26 +02:00
//todo: inform on actual resource set transferred
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , pack . player , & IGameEventsReceiver :: receivedResource );
}
2025-06-02 21:21:20 +03:00
void ApplyClientNetPackVisitor :: visitSetHeroExperience ( SetHeroExperience & pack )
{
const CGHeroInstance * h = cl . gameInfo (). getHero ( pack . id );
if ( ! h )
{
logNetwork -> error ( "Cannot find hero with pack.id %d" , pack . id . getNum ());
return ;
}
callInterfaceIfPresent ( cl , h -> tempOwner , & IGameEventsReceiver :: heroExperienceChanged , h , pack . val );
}
void ApplyClientNetPackVisitor :: visitSetPrimarySkill ( SetPrimarySkill & pack )
2023-10-19 16:19:09 +02:00
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * h = cl . gameInfo (). getHero ( pack . id );
2023-10-19 16:19:09 +02:00
if ( ! h )
{
logNetwork -> error ( "Cannot find hero with pack.id %d" , pack . id . getNum ());
return ;
}
callInterfaceIfPresent ( cl , h -> tempOwner , & IGameEventsReceiver :: heroPrimarySkillChanged , h , pack . which , pack . val );
}
void ApplyClientNetPackVisitor :: visitSetSecSkill ( SetSecSkill & pack )
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * h = cl . gameInfo (). getHero ( pack . id );
2023-10-19 16:19:09 +02:00
if ( ! h )
{
logNetwork -> error ( "Cannot find hero with pack.id %d" , pack . id . getNum ());
return ;
}
callInterfaceIfPresent ( cl , h -> tempOwner , & IGameEventsReceiver :: heroSecondarySkillChanged , h , pack . which , pack . val );
}
void ApplyClientNetPackVisitor :: visitHeroVisitCastle ( HeroVisitCastle & pack )
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * h = cl . gameInfo (). getHero ( pack . hid );
2023-10-19 16:19:09 +02:00
if ( pack . start ())
{
callInterfaceIfPresent ( cl , h -> tempOwner , & IGameEventsReceiver :: heroVisitsTown , h , gs . getTown ( pack . tid ));
}
}
void ApplyClientNetPackVisitor :: visitSetMana ( SetMana & pack )
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * h = cl . gameInfo (). getHero ( pack . hid );
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , h -> tempOwner , & IGameEventsReceiver :: heroManaPointsChanged , h );
2023-12-03 18:39:25 +02:00
if ( settings [ "session" ][ "headless" ]. Bool ())
return ;
2025-02-10 21:49:23 +00:00
for ( auto window : ENGINE -> windows (). findWindows < BattleWindow > ())
2023-10-19 16:19:09 +02:00
window -> heroManaPointsChanged ( h );
}
void ApplyClientNetPackVisitor :: visitSetMovePoints ( SetMovePoints & pack )
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * h = cl . gameInfo (). getHero ( pack . hid );
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , h -> tempOwner , & IGameEventsReceiver :: heroMovePointsChanged , h );
}
2024-09-28 15:05:13 +02:00
void ApplyClientNetPackVisitor :: visitSetResearchedSpells ( SetResearchedSpells & pack )
2024-09-27 23:52:33 +02:00
{
2025-02-10 21:49:23 +00:00
for ( const auto & win : ENGINE -> windows (). findWindows < CMageGuildScreen > ())
2024-09-28 16:10:03 +02:00
win -> updateSpells ( pack . tid );
2024-09-27 23:52:33 +02:00
}
2023-10-19 16:19:09 +02:00
void ApplyClientNetPackVisitor :: visitFoWChange ( FoWChange & pack )
{
for ( auto & i : cl . playerint )
{
2025-05-20 23:16:28 +03:00
if ( cl . gameInfo (). getPlayerRelations ( i . first , pack . player ) == PlayerRelations :: SAME_PLAYER && pack . waitForDialogs && GAME -> interface () == i . second . get ())
2023-10-19 16:19:09 +02:00
{
2025-02-11 15:23:33 +00:00
GAME -> interface () -> waitWhileDialog ();
2023-10-19 16:19:09 +02:00
}
2025-05-20 23:16:28 +03:00
if ( cl . gameInfo (). getPlayerRelations ( i . first , pack . player ) != PlayerRelations :: ENEMIES )
2023-10-19 16:19:09 +02:00
{
2023-10-22 18:36:41 +03:00
if ( pack . mode == ETileVisibility :: REVEALED )
2023-10-19 16:19:09 +02:00
i . second -> tileRevealed ( pack . tiles );
else
i . second -> tileHidden ( pack . tiles );
}
}
2025-01-23 14:39:56 +00:00
callAllInterfaces ( cl , & CGameInterface :: invalidatePaths );
2023-10-19 16:19:09 +02:00
}
static void dispatchGarrisonChange ( CClient & cl , ObjectInstanceID army1 , ObjectInstanceID army2 )
{
2025-05-20 23:16:28 +03:00
auto obj1 = cl . gameInfo (). getObj ( army1 );
2023-10-19 16:19:09 +02:00
if ( ! obj1 )
{
logNetwork -> error ( "Cannot find army with pack.id %d" , army1 . getNum ());
return ;
}
callInterfaceIfPresent ( cl , obj1 -> tempOwner , & IGameEventsReceiver :: garrisonsChanged , army1 , army2 );
if ( army2 != ObjectInstanceID () && army2 != army1 )
{
2025-05-20 23:16:28 +03:00
auto obj2 = cl . gameInfo (). getObj ( army2 );
2023-10-19 16:19:09 +02:00
if ( ! obj2 )
{
logNetwork -> error ( "Cannot find army with pack.id %d" , army2 . getNum ());
return ;
}
if ( obj1 -> tempOwner != obj2 -> tempOwner )
callInterfaceIfPresent ( cl , obj2 -> tempOwner , & IGameEventsReceiver :: garrisonsChanged , army1 , army2 );
}
}
void ApplyClientNetPackVisitor :: visitChangeStackCount ( ChangeStackCount & pack )
{
dispatchGarrisonChange ( cl , pack . army , ObjectInstanceID ());
}
void ApplyClientNetPackVisitor :: visitSetStackType ( SetStackType & pack )
{
dispatchGarrisonChange ( cl , pack . army , ObjectInstanceID ());
}
void ApplyClientNetPackVisitor :: visitEraseStack ( EraseStack & pack )
{
dispatchGarrisonChange ( cl , pack . army , ObjectInstanceID ());
}
void ApplyClientNetPackVisitor :: visitSwapStacks ( SwapStacks & pack )
{
dispatchGarrisonChange ( cl , pack . srcArmy , pack . dstArmy );
}
void ApplyClientNetPackVisitor :: visitInsertNewStack ( InsertNewStack & pack )
{
dispatchGarrisonChange ( cl , pack . army , ObjectInstanceID ());
}
void ApplyClientNetPackVisitor :: visitRebalanceStacks ( RebalanceStacks & pack )
{
dispatchGarrisonChange ( cl , pack . srcArmy , pack . dstArmy );
}
void ApplyClientNetPackVisitor :: visitBulkRebalanceStacks ( BulkRebalanceStacks & pack )
{
if ( ! pack . moves . empty ())
{
auto destArmy = pack . moves [ 0 ]. srcArmy == pack . moves [ 0 ]. dstArmy
? ObjectInstanceID ()
: pack . moves [ 0 ]. dstArmy ;
dispatchGarrisonChange ( cl , pack . moves [ 0 ]. srcArmy , destArmy );
}
}
void ApplyClientNetPackVisitor :: visitPutArtifact ( PutArtifact & pack )
{
2025-05-20 23:16:28 +03:00
callInterfaceIfPresent ( cl , cl . gameState (). getOwner ( pack . al . artHolder ), & IGameEventsReceiver :: artifactPut , pack . al );
2023-10-19 16:19:09 +02:00
if ( pack . askAssemble )
2025-05-20 23:16:28 +03:00
callInterfaceIfPresent ( cl , cl . gameState (). getOwner ( pack . al . artHolder ), & IGameEventsReceiver :: askToAssembleArtifact , pack . al );
2023-10-19 16:19:09 +02:00
}
2025-04-29 22:26:11 +03:00
void ApplyClientNetPackVisitor :: visitBulkEraseArtifacts ( BulkEraseArtifacts & pack )
2023-10-19 16:19:09 +02:00
{
2024-08-29 18:47:06 +03:00
for ( const auto & slotErase : pack . posPack )
2025-05-20 23:16:28 +03:00
callInterfaceIfPresent ( cl , cl . gameState (). getOwner ( pack . artHolder ), & IGameEventsReceiver :: artifactRemoved , ArtifactLocation ( pack . artHolder , slotErase ));
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitBulkMoveArtifacts ( BulkMoveArtifacts & pack )
{
2025-05-20 23:16:28 +03:00
const auto dstOwner = cl . gameState (). getOwner ( pack . dstArtHolder );
2025-04-05 22:55:57 +02:00
const auto applyMove = [ this , & pack , dstOwner ]( const std :: vector < MoveArtifactInfo > & artsPack )
2023-10-19 16:19:09 +02:00
{
2024-03-28 16:16:10 +02:00
for ( const auto & slotToMove : artsPack )
2023-10-19 16:19:09 +02:00
{
2024-03-28 16:16:10 +02:00
const auto srcLoc = ArtifactLocation ( pack . srcArtHolder , slotToMove . srcPos );
const auto dstLoc = ArtifactLocation ( pack . dstArtHolder , slotToMove . dstPos );
callInterfaceIfPresent ( cl , pack . interfaceOwner , & IGameEventsReceiver :: artifactMoved , srcLoc , dstLoc );
2024-06-22 19:29:39 +03:00
if ( slotToMove . askAssemble )
2024-03-28 16:16:10 +02:00
callInterfaceIfPresent ( cl , pack . interfaceOwner , & IGameEventsReceiver :: askToAssembleArtifact , dstLoc );
if ( pack . interfaceOwner != dstOwner )
callInterfaceIfPresent ( cl , dstOwner , & IGameEventsReceiver :: artifactMoved , srcLoc , dstLoc );
2023-10-19 16:19:09 +02:00
}
};
2024-06-23 23:48:19 +03:00
size_t possibleAssemblyNumOfArts = 0 ;
const auto calcPossibleAssemblyNumOfArts = [ & possibleAssemblyNumOfArts ]( const auto & slotToMove )
{
if ( slotToMove . askAssemble )
possibleAssemblyNumOfArts ++ ;
};
std :: for_each ( pack . artsPack0 . cbegin (), pack . artsPack0 . cend (), calcPossibleAssemblyNumOfArts );
std :: for_each ( pack . artsPack1 . cbegin (), pack . artsPack1 . cend (), calcPossibleAssemblyNumOfArts );
2023-10-19 16:19:09 +02:00
// Begin a session of bulk movement of arts. It is not necessary but useful for the client optimization.
2024-06-23 23:48:19 +03:00
callInterfaceIfPresent ( cl , pack . interfaceOwner , & IGameEventsReceiver :: bulkArtMovementStart ,
pack . artsPack0 . size () + pack . artsPack1 . size (), possibleAssemblyNumOfArts );
2024-03-28 16:16:10 +02:00
if ( pack . interfaceOwner != dstOwner )
2024-06-23 23:48:19 +03:00
callInterfaceIfPresent ( cl , dstOwner , & IGameEventsReceiver :: bulkArtMovementStart ,
pack . artsPack0 . size () + pack . artsPack1 . size (), possibleAssemblyNumOfArts );
2023-10-19 16:19:09 +02:00
applyMove ( pack . artsPack0 );
2024-06-23 23:48:19 +03:00
if ( ! pack . artsPack1 . empty ())
2023-10-19 16:19:09 +02:00
applyMove ( pack . artsPack1 );
}
void ApplyClientNetPackVisitor :: visitAssembledArtifact ( AssembledArtifact & pack )
{
2025-05-20 23:16:28 +03:00
callInterfaceIfPresent ( cl , cl . gameState (). getOwner ( pack . al . artHolder ), & IGameEventsReceiver :: artifactAssembled , pack . al );
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitDisassembledArtifact ( DisassembledArtifact & pack )
{
2025-05-20 23:16:28 +03:00
callInterfaceIfPresent ( cl , cl . gameState (). getOwner ( pack . al . artHolder ), & IGameEventsReceiver :: artifactDisassembled , pack . al );
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitHeroVisit ( HeroVisit & pack )
{
2025-05-20 23:16:28 +03:00
auto hero = cl . gameInfo (). getHero ( pack . heroId );
auto obj = cl . gameInfo (). getObj ( pack . objId , false );
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , pack . player , & IGameEventsReceiver :: heroVisit , hero , obj , pack . starting );
}
void ApplyClientNetPackVisitor :: visitNewTurn ( NewTurn & pack )
{
2025-01-23 14:39:56 +00:00
callAllInterfaces ( cl , & CGameInterface :: invalidatePaths );
2024-08-26 13:49:50 +00:00
2024-09-04 00:13:00 +02:00
if ( pack . newWeekNotification )
2024-08-26 13:49:50 +00:00
{
const auto & newWeek = * pack . newWeekNotification ;
std :: string str = newWeek . text . toString ();
callAllInterfaces ( cl , & CGameInterface :: showInfoDialog , newWeek . type , str , newWeek . components ,( soundBase :: soundID ) newWeek . soundID );
}
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitGiveBonus ( GiveBonus & pack )
{
2025-01-23 14:39:56 +00:00
callAllInterfaces ( cl , & CGameInterface :: invalidatePaths );
2023-10-19 16:19:09 +02:00
switch ( pack . who )
{
2023-11-06 18:27:16 +02:00
case GiveBonus :: ETarget :: OBJECT :
2023-10-19 16:19:09 +02:00
{
2023-11-06 18:27:16 +02:00
const CGHeroInstance * h = gs . getHero ( pack . id . as < ObjectInstanceID > ());
2024-09-04 00:13:00 +02:00
if ( h )
2023-11-06 18:27:16 +02:00
callInterfaceIfPresent ( cl , h -> tempOwner , & IGameEventsReceiver :: heroBonusChanged , h , pack . bonus , true );
2023-10-19 16:19:09 +02:00
}
break ;
case GiveBonus :: ETarget :: PLAYER :
{
2023-11-06 18:27:16 +02:00
callInterfaceIfPresent ( cl , pack . id . as < PlayerColor > (), & IGameEventsReceiver :: playerBonusChanged , pack . bonus , true );
2023-10-19 16:19:09 +02:00
}
break ;
}
}
void ApplyFirstClientNetPackVisitor :: visitChangeObjPos ( ChangeObjPos & pack )
{
2025-05-20 23:16:28 +03:00
const CGObjectInstance * obj = gs . getObjInstance ( pack . objid );
2025-02-11 15:23:33 +00:00
GAME -> map (). onObjectFadeOut ( obj , pack . initiator );
GAME -> map (). waitForOngoingAnimations ();
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitChangeObjPos ( ChangeObjPos & pack )
{
2025-05-20 23:16:28 +03:00
const CGObjectInstance * obj = gs . getObjInstance ( pack . objid );
2025-02-11 15:23:33 +00:00
GAME -> map (). onObjectFadeIn ( obj , pack . initiator );
GAME -> map (). waitForOngoingAnimations ();
2025-01-23 14:39:56 +00:00
callAllInterfaces ( cl , & CGameInterface :: invalidatePaths );
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitPlayerEndsGame ( PlayerEndsGame & pack )
{
callAllInterfaces ( cl , & IGameEventsReceiver :: gameOver , pack . player , pack . victoryLossCheckResult );
2025-05-20 23:16:28 +03:00
bool localHumanWinsGame = vstd :: contains ( cl . playerint , pack . player ) && cl . gameInfo (). getPlayerState ( pack . player ) -> human && pack . victoryLossCheckResult . victory ();
bool lastHumanEndsGame = GAME -> server (). howManyPlayerInterfaces () == 1 && vstd :: contains ( cl . playerint , pack . player ) && cl . gameInfo (). getPlayerState ( pack . player ) -> human && ! settings [ "session" ][ "spectate" ]. Bool ();
2024-04-07 14:19:57 +03:00
2025-10-25 17:55:25 +02:00
if ( lastHumanEndsGame || localHumanWinsGame || pack . silentEnd )
2024-04-07 14:19:57 +03:00
{
assert ( adventureInt );
if ( adventureInt )
{
2025-02-10 21:49:23 +00:00
ENGINE -> windows (). popWindows ( ENGINE -> windows (). count ());
2024-04-07 14:19:57 +03:00
adventureInt . reset ();
}
2025-10-18 21:43:05 +02:00
if ( ! pack . silentEnd )
GAME -> server (). showHighScoresAndEndGameplay ( pack . player , pack . victoryLossCheckResult . victory (), pack . statistic );
else
{
GAME -> server (). endGameplay ();
GAME -> mainmenu () -> menu -> switchToTab ( "main" );
}
2024-04-07 14:19:57 +03:00
}
2023-10-19 16:19:09 +02:00
// In auto testing pack.mode we always close client if red pack.player won or lose
if ( ! settings [ "session" ][ "testmap" ]. isNull () && pack . player == PlayerColor ( 0 ))
2024-03-28 13:39:15 +02:00
{
logAi -> info ( "Red player %s. Ending game." , pack . victoryLossCheckResult . victory () ? "won" : "lost" );
2025-03-03 21:45:58 +00:00
GAME -> onShutdownRequested ( settings [ "session" ][ "spectate" ]. Bool ()); // if spectator is active ask to close client or not
2024-03-28 13:39:15 +02:00
}
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitRemoveBonus ( RemoveBonus & pack )
{
switch ( pack . who )
{
2023-11-06 18:27:16 +02:00
case GiveBonus :: ETarget :: OBJECT :
2023-10-19 16:19:09 +02:00
{
2023-11-06 18:27:16 +02:00
const CGHeroInstance * h = gs . getHero ( pack . whoID . as < ObjectInstanceID > ());
2024-09-04 00:13:00 +02:00
if ( h )
2023-11-06 18:27:16 +02:00
callInterfaceIfPresent ( cl , h -> tempOwner , & IGameEventsReceiver :: heroBonusChanged , h , pack . bonus , false );
2023-10-19 16:19:09 +02:00
}
break ;
case GiveBonus :: ETarget :: PLAYER :
{
//const PlayerState *p = gs.getPlayerState(pack.id);
2023-11-06 18:27:16 +02:00
callInterfaceIfPresent ( cl , pack . whoID . as < PlayerColor > (), & IGameEventsReceiver :: playerBonusChanged , pack . bonus , false );
2023-10-19 16:19:09 +02:00
}
break ;
}
}
void ApplyFirstClientNetPackVisitor :: visitRemoveObject ( RemoveObject & pack )
{
2025-05-20 23:16:28 +03:00
const CGObjectInstance * o = cl . gameInfo (). getObj ( pack . objectID );
2026-06-17 13:18:09 +03:00
if ( ! o )
return ;
2025-06-17 14:37:58 +03:00
const auto * h = dynamic_cast < const CGHeroInstance *> ( o );
2023-10-19 16:19:09 +02:00
2025-02-11 15:23:33 +00:00
GAME -> map (). onObjectFadeOut ( o , pack . initiator );
2025-06-17 14:37:58 +03:00
if ( h && h -> inBoat ())
2025-09-06 05:25:49 +02:00
{
GAME -> map (). waitForOngoingAnimations ();
2025-06-17 14:37:58 +03:00
GAME -> map (). onObjectFadeOut ( h -> getBoat (), pack . initiator );
2025-09-06 05:25:49 +02:00
}
2023-10-19 16:19:09 +02:00
//notify interfaces about removal
for ( auto i = cl . playerint . begin (); i != cl . playerint . end (); i ++ )
{
//below line contains little cheat for AI so it will be aware of deletion of enemy heroes that moved or got re-covered by FoW
//TODO: loose requirements as next AI related crashes appear, for example another pack.player collects object that got re-covered by FoW, unsure if AI code workarounds this
2026-06-17 13:18:09 +03:00
const auto * playerState = cl . gameInfo (). getPlayerState ( i -> first );
if ( gs . isVisibleFor ( o , i -> first ) || ( playerState && ! playerState -> human && o -> ID == Obj :: HERO && o -> tempOwner != i -> first ))
2025-06-17 14:37:58 +03:00
{
2023-10-19 16:19:09 +02:00
i -> second -> objectRemoved ( o , pack . initiator );
2025-06-17 14:37:58 +03:00
if ( h && h -> inBoat ())
i -> second -> objectRemoved ( h -> getBoat (), pack . initiator );
}
2023-10-19 16:19:09 +02:00
}
2025-02-11 15:23:33 +00:00
GAME -> map (). waitForOngoingAnimations ();
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitRemoveObject ( RemoveObject & pack )
{
2025-01-23 14:39:56 +00:00
callAllInterfaces ( cl , & CGameInterface :: invalidatePaths );
2023-10-19 16:19:09 +02:00
for ( auto i = cl . playerint . begin (); i != cl . playerint . end (); i ++ )
i -> second -> objectRemovedAfter ();
}
void ApplyFirstClientNetPackVisitor :: visitTryMoveHero ( TryMoveHero & pack )
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * h = gs . getHero ( pack . id );
2023-10-19 16:19:09 +02:00
2025-02-11 15:23:33 +00:00
switch ( pack . result )
2023-10-19 16:19:09 +02:00
{
2025-02-11 15:23:33 +00:00
case TryMoveHero :: EMBARK :
GAME -> map (). onBeforeHeroEmbark ( h , pack . start , pack . end );
2025-06-18 12:08:10 +03:00
GAME -> map (). waitForOngoingAnimations (); // required - hero must play fade-out animation on his pre-embark position
2025-02-11 15:23:33 +00:00
break ;
case TryMoveHero :: TELEPORTATION :
GAME -> map (). onBeforeHeroTeleported ( h , pack . start , pack . end );
break ;
case TryMoveHero :: DISEMBARK :
GAME -> map (). onBeforeHeroDisembark ( h , pack . start , pack . end );
break ;
2023-10-19 16:19:09 +02:00
}
}
void ApplyClientNetPackVisitor :: visitTryMoveHero ( TryMoveHero & pack )
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * h = cl . gameInfo (). getHero ( pack . id );
2025-01-23 14:39:56 +00:00
callAllInterfaces ( cl , & CGameInterface :: invalidatePaths );
2023-10-19 16:19:09 +02:00
2025-02-11 15:23:33 +00:00
switch ( pack . result )
2023-10-19 16:19:09 +02:00
{
2025-02-11 15:23:33 +00:00
case TryMoveHero :: SUCCESS :
GAME -> map (). onHeroMoved ( h , pack . start , pack . end );
break ;
case TryMoveHero :: EMBARK :
GAME -> map (). onAfterHeroEmbark ( h , pack . start , pack . end );
break ;
case TryMoveHero :: TELEPORTATION :
GAME -> map (). onAfterHeroTeleported ( h , pack . start , pack . end );
break ;
case TryMoveHero :: DISEMBARK :
GAME -> map (). onAfterHeroDisembark ( h , pack . start , pack . end );
break ;
2023-10-19 16:19:09 +02:00
}
PlayerColor player = h -> tempOwner ;
for ( auto & i : cl . playerint )
2025-05-20 23:16:28 +03:00
if ( cl . gameInfo (). getPlayerRelations ( i . first , player ) != PlayerRelations :: ENEMIES )
2023-10-19 16:19:09 +02:00
i . second -> tileRevealed ( pack . fowRevealed );
for ( auto i = cl . playerint . begin (); i != cl . playerint . end (); i ++ )
{
if ( i -> first != PlayerColor :: SPECTATOR && gs . checkForStandardLoss ( i -> first )) // Do not notify vanquished pack.player's interface
continue ;
2025-05-14 15:50:13 +03:00
if ( gs . isVisibleFor ( h -> convertToVisitablePos ( pack . start ), i -> first )
|| gs . isVisibleFor ( h -> convertToVisitablePos ( pack . end ), i -> first ))
2023-10-19 16:19:09 +02:00
{
// pack.src and pack.dst of enemy hero move may be not visible => 'verbose' should be false
2025-05-20 23:16:28 +03:00
const bool verbose = cl . gameInfo (). getPlayerRelations ( i -> first , player ) != PlayerRelations :: ENEMIES ;
2023-10-19 16:19:09 +02:00
i -> second -> heroMoved ( pack , verbose );
}
}
2025-06-21 15:04:52 +03:00
GAME -> map (). waitForOngoingAnimations ();
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitNewStructures ( NewStructures & pack )
{
2025-05-20 23:16:28 +03:00
const CGTownInstance * town = gs . getTown ( pack . tid );
2023-10-19 16:19:09 +02:00
for ( const auto & id : pack . bid )
{
callInterfaceIfPresent ( cl , town -> getOwner (), & IGameEventsReceiver :: buildChanged , town , id , 1 );
}
// invalidate section of map view with our object and force an update
2025-02-11 15:23:33 +00:00
GAME -> map (). onObjectInstantRemove ( town , town -> getOwner ());
GAME -> map (). onObjectInstantAdd ( town , town -> getOwner ());
2023-10-19 16:19:09 +02:00
}
2025-02-11 15:23:33 +00:00
2023-10-19 16:19:09 +02:00
void ApplyClientNetPackVisitor :: visitRazeStructures ( RazeStructures & pack )
{
2025-05-20 23:16:28 +03:00
const CGTownInstance * town = gs . getTown ( pack . tid );
2023-10-19 16:19:09 +02:00
for ( const auto & id : pack . bid )
{
callInterfaceIfPresent ( cl , town -> getOwner (), & IGameEventsReceiver :: buildChanged , town , id , 2 );
}
// invalidate section of map view with our object and force an update
2025-02-11 15:23:33 +00:00
GAME -> map (). onObjectInstantRemove ( town , town -> getOwner ());
GAME -> map (). onObjectInstantAdd ( town , town -> getOwner ());
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitSetAvailableCreatures ( SetAvailableCreatures & pack )
{
2025-05-20 23:16:28 +03:00
const CGDwelling * dw = static_cast < const CGDwelling *> ( cl . gameInfo (). getObj ( pack . tid ));
2023-10-19 16:19:09 +02:00
PlayerColor p ;
if ( dw -> ID == Obj :: WAR_MACHINE_FACTORY ) //War Machines Factory is not flaggable, it's "owned" by visitor
2025-05-20 23:16:28 +03:00
p = cl . gameInfo (). getObjInstance ( cl . gameInfo (). getTile ( dw -> visitablePos ()) -> visitableObjects . back ()) -> getOwner ();
2023-10-19 16:19:09 +02:00
else
p = dw -> tempOwner ;
callInterfaceIfPresent ( cl , p , & IGameEventsReceiver :: availableCreaturesChanged , dw );
}
2026-05-26 22:04:23 +02:00
void ApplyClientNetPackVisitor :: visitChangeSpells ( ChangeSpells & pack )
{
if ( ! pack . learn || pack . spells . empty ())
return ;
const auto * hero = cl . gameInfo (). getHero ( pack . hid );
if ( ! hero )
return ;
if ( hero -> valOfBonuses ( BonusType :: LEARN_BATTLE_SPELL_CHANCE_PRE_BATTLE ) <= 0 )
return ;
2026-05-26 23:44:36 +02:00
showEagleEyeLearnedSpellsDialog ( cl , pack . hid , pack . spells , hero -> tempOwner );
2026-05-26 22:04:23 +02:00
}
2023-10-19 16:19:09 +02:00
void ApplyClientNetPackVisitor :: visitSetHeroesInTown ( SetHeroesInTown & pack )
{
2025-05-20 23:16:28 +03:00
const CGTownInstance * t = gs . getTown ( pack . tid );
const CGHeroInstance * hGarr = gs . getHero ( pack . garrison );
const CGHeroInstance * hVisit = gs . getHero ( pack . visiting );
2023-10-19 16:19:09 +02:00
//inform all players that see this object
for ( auto i = cl . playerint . cbegin (); i != cl . playerint . cend (); ++ i )
{
if ( ! i -> first . isValidPlayer ())
continue ;
2025-05-14 15:50:13 +03:00
if ( gs . isVisibleFor ( t , i -> first ) ||
( hGarr && gs . isVisibleFor ( hGarr , i -> first )) ||
( hVisit && gs . isVisibleFor ( hVisit , i -> first )))
2023-10-19 16:19:09 +02:00
{
cl . playerint [ i -> first ] -> heroInGarrisonChange ( t );
}
}
}
void ApplyClientNetPackVisitor :: visitHeroRecruited ( HeroRecruited & pack )
{
2025-05-20 23:16:28 +03:00
const auto * h = gs . getMap (). getHero ( pack . hid );
2024-10-05 19:37:52 +00:00
if ( h -> getHeroTypeID () != pack . hid )
2023-10-19 16:19:09 +02:00
{
logNetwork -> error ( "Something wrong with hero recruited!" );
}
2025-03-13 21:49:57 +00:00
if ( callInterfaceIfPresent ( cl , h -> tempOwner , & IGameEventsReceiver :: heroCreated , h ))
2023-10-19 16:19:09 +02:00
{
if ( const CGTownInstance * t = gs . getTown ( pack . tid ))
callInterfaceIfPresent ( cl , h -> getOwner (), & IGameEventsReceiver :: heroInGarrisonChange , t );
}
2025-03-13 21:49:57 +00:00
GAME -> map (). onObjectInstantAdd ( h , h -> getOwner ());
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitGiveHero ( GiveHero & pack )
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * h = gs . getHero ( pack . id );
2025-02-11 15:23:33 +00:00
GAME -> map (). onObjectInstantAdd ( h , h -> getOwner ());
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , h -> tempOwner , & IGameEventsReceiver :: heroCreated , h );
}
void ApplyFirstClientNetPackVisitor :: visitGiveHero ( GiveHero & pack )
{
}
void ApplyClientNetPackVisitor :: visitInfoWindow ( InfoWindow & pack )
{
std :: string str = pack . text . toString ();
if ( ! callInterfaceIfPresent ( cl , pack . player , & CGameInterface :: showInfoDialog , pack . type , str , pack . components ,( soundBase :: soundID ) pack . soundID ))
logNetwork -> warn ( "We received InfoWindow for not our player..." );
}
void ApplyFirstClientNetPackVisitor :: visitSetObjectProperty ( SetObjectProperty & pack )
{
//inform all players that see this object
for ( auto it = cl . playerint . cbegin (); it != cl . playerint . cend (); ++ it )
{
2025-05-14 15:50:13 +03:00
if ( gs . isVisibleFor ( gs . getObjInstance ( pack . id ), it -> first ))
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , it -> first , & IGameEventsReceiver :: beforeObjectPropertyChanged , & pack );
}
// invalidate section of map view with our object and force an update with new flag color
2025-02-11 15:23:33 +00:00
if ( pack . what == ObjProperty :: OWNER )
2023-10-19 16:19:09 +02:00
{
auto object = gs . getObjInstance ( pack . id );
2025-02-11 15:23:33 +00:00
GAME -> map (). onObjectInstantRemove ( object , object -> getOwner ());
2023-10-19 16:19:09 +02:00
}
}
void ApplyClientNetPackVisitor :: visitSetObjectProperty ( SetObjectProperty & pack )
{
//inform all players that see this object
for ( auto it = cl . playerint . cbegin (); it != cl . playerint . cend (); ++ it )
{
2025-05-14 15:50:13 +03:00
if ( gs . isVisibleFor ( gs . getObjInstance ( pack . id ), it -> first ))
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , it -> first , & IGameEventsReceiver :: objectPropertyChanged , & pack );
}
// invalidate section of map view with our object and force an update with new flag color
2025-02-11 15:23:33 +00:00
if ( pack . what == ObjProperty :: OWNER )
2023-10-19 16:19:09 +02:00
{
auto object = gs . getObjInstance ( pack . id );
2025-02-11 15:23:33 +00:00
GAME -> map (). onObjectInstantAdd ( object , object -> getOwner ());
2023-10-19 16:19:09 +02:00
}
}
void ApplyClientNetPackVisitor :: visitHeroLevelUp ( HeroLevelUp & pack )
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * hero = cl . gameInfo (). getHero ( pack . heroId );
2023-10-19 16:19:09 +02:00
assert ( hero );
callOnlyThatInterface ( cl , pack . player , & CGameInterface :: heroGotLevel , hero , pack . primskill , pack . skills , pack . queryID );
}
void ApplyClientNetPackVisitor :: visitCommanderLevelUp ( CommanderLevelUp & pack )
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * hero = cl . gameInfo (). getHero ( pack . heroId );
2023-10-19 16:19:09 +02:00
assert ( hero );
2025-03-09 21:51:33 +00:00
const auto & commander = hero -> getCommander ();
2023-10-19 16:19:09 +02:00
assert ( commander );
2025-03-18 23:30:06 +00:00
assert ( commander -> getArmy ()); //is it possible for Commander to exist beyond armed instance?
2023-10-19 16:19:09 +02:00
callOnlyThatInterface ( cl , pack . player , & CGameInterface :: commanderGotLevel , commander , pack . skills , pack . queryID );
}
void ApplyClientNetPackVisitor :: visitBlockingDialog ( BlockingDialog & pack )
{
std :: string str = pack . text . toString ();
2024-04-17 01:08:27 +02:00
if ( ! callOnlyThatInterface ( cl , pack . player , & CGameInterface :: showBlockingDialog , str , pack . components , pack . queryID , ( soundBase :: soundID ) pack . soundID , pack . selection (), pack . cancel (), pack . safeToAutoaccept ()))
2023-10-19 16:19:09 +02:00
logNetwork -> warn ( "We received YesNoDialog for not our player..." );
}
void ApplyClientNetPackVisitor :: visitGarrisonDialog ( GarrisonDialog & pack )
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * h = cl . gameInfo (). getHero ( pack . hid );
const CArmedInstance * obj = static_cast < const CArmedInstance *> ( cl . gameInfo (). getObj ( pack . objid ));
2023-10-19 16:19:09 +02:00
2026-04-19 11:10:24 +02:00
callOnlyThatInterface ( cl , h -> getOwner (), & CGameInterface :: showGarrisonDialog , obj , h , pack . removableUnits , pack . queryID , pack . customTitle );
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitExchangeDialog ( ExchangeDialog & pack )
{
callInterfaceIfPresent ( cl , pack . player , & IGameEventsReceiver :: heroExchangeStarted , pack . hero1 , pack . hero2 , pack . queryID );
}
void ApplyClientNetPackVisitor :: visitTeleportDialog ( TeleportDialog & pack )
{
2025-05-20 23:16:28 +03:00
const CGHeroInstance * h = cl . gameInfo (). getHero ( pack . hero );
2023-10-19 16:19:09 +02:00
callOnlyThatInterface ( cl , h -> getOwner (), & CGameInterface :: showTeleportDialog , h , pack . channel , pack . exits , pack . impassable , pack . queryID );
}
void ApplyClientNetPackVisitor :: visitMapObjectSelectDialog ( MapObjectSelectDialog & pack )
{
callOnlyThatInterface ( cl , pack . player , & CGameInterface :: showMapObjectSelectDialog , pack . queryID , pack . icon , pack . title , pack . description , pack . objects );
}
void ApplyFirstClientNetPackVisitor :: visitBattleStart ( BattleStart & pack )
{
// Cannot use the usual code because curB is not set yet
2025-03-14 17:07:30 +00:00
callOnlyThatBattleInterface ( cl , pack . info -> getSide ( BattleSide :: ATTACKER ). color , & IBattleEventsReceiver :: battleStartBefore , pack . battleID , pack . info -> getSideArmy ( BattleSide :: ATTACKER ), pack . info -> getSideArmy ( BattleSide :: DEFENDER ),
2025-03-30 18:32:07 +03:00
pack . info -> tile , pack . info -> getSideHero ( BattleSide :: ATTACKER ), pack . info -> getSideHero ( BattleSide :: DEFENDER ));
2025-03-14 17:07:30 +00:00
callOnlyThatBattleInterface ( cl , pack . info -> getSide ( BattleSide :: DEFENDER ). color , & IBattleEventsReceiver :: battleStartBefore , pack . battleID , pack . info -> getSideArmy ( BattleSide :: ATTACKER ), pack . info -> getSideArmy ( BattleSide :: DEFENDER ),
2025-03-30 18:32:07 +03:00
pack . info -> tile , pack . info -> getSideHero ( BattleSide :: ATTACKER ), pack . info -> getSideHero ( BattleSide :: DEFENDER ));
2025-03-14 17:07:30 +00:00
callOnlyThatBattleInterface ( cl , PlayerColor :: SPECTATOR , & IBattleEventsReceiver :: battleStartBefore , pack . battleID , pack . info -> getSideArmy ( BattleSide :: ATTACKER ), pack . info -> getSideArmy ( BattleSide :: DEFENDER ),
2025-03-30 18:32:07 +03:00
pack . info -> tile , pack . info -> getSideHero ( BattleSide :: ATTACKER ), pack . info -> getSideHero ( BattleSide :: DEFENDER ));
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitBattleStart ( BattleStart & pack )
{
2025-04-10 12:27:18 +03:00
cl . battleStarted ( pack . battleID );
2023-10-19 16:19:09 +02:00
}
void ApplyFirstClientNetPackVisitor :: visitBattleNextRound ( BattleNextRound & pack )
{
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleNewRoundFirst , pack . battleID );
}
void ApplyClientNetPackVisitor :: visitBattleNextRound ( BattleNextRound & pack )
{
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleNewRound , pack . battleID );
}
void ApplyClientNetPackVisitor :: visitBattleSetActiveStack ( BattleSetActiveStack & pack )
{
2025-04-28 19:34:36 +03:00
if ( pack . reason == BattleUnitTurnReason :: AUTOMATIC_ACTION )
2023-10-19 16:19:09 +02:00
return ;
const CStack * activated = gs . getBattle ( pack . battleID ) -> battleGetStackByID ( pack . stack );
PlayerColor playerToCall ; //pack.player that will move activated stack
2024-12-25 21:25:06 +00:00
if ( activated -> isHypnotized ())
2023-10-19 16:19:09 +02:00
{
2024-08-11 20:22:35 +00:00
playerToCall = gs . getBattle ( pack . battleID ) -> getSide ( BattleSide :: ATTACKER ). color == activated -> unitOwner ()
? gs . getBattle ( pack . battleID ) -> getSide ( BattleSide :: DEFENDER ). color
: gs . getBattle ( pack . battleID ) -> getSide ( BattleSide :: ATTACKER ). color ;
2023-10-19 16:19:09 +02:00
}
else
{
playerToCall = activated -> unitOwner ();
}
cl . startPlayerBattleAction ( pack . battleID , playerToCall );
}
void ApplyClientNetPackVisitor :: visitBattleLogMessage ( BattleLogMessage & pack )
{
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleLogMessage , pack . battleID , pack . lines );
}
void ApplyClientNetPackVisitor :: visitBattleTriggerEffect ( BattleTriggerEffect & pack )
{
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleTriggerEffect , pack . battleID , pack );
}
void ApplyFirstClientNetPackVisitor :: visitBattleUpdateGateState ( BattleUpdateGateState & pack )
{
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleGateStateChanged , pack . battleID , pack . state );
}
void ApplyFirstClientNetPackVisitor :: visitBattleResult ( BattleResult & pack )
{
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleEnd , pack . battleID , & pack , pack . queryID );
cl . battleFinished ( pack . battleID );
}
void ApplyFirstClientNetPackVisitor :: visitBattleStackMoved ( BattleStackMoved & pack )
{
const CStack * movedStack = gs . getBattle ( pack . battleID ) -> battleGetStackByID ( pack . stack );
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleStackMoved , pack . battleID , movedStack , pack . tilesToMove , pack . distance , pack . teleporting );
}
void ApplyFirstClientNetPackVisitor :: visitBattleAttack ( BattleAttack & pack )
{
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleAttack , pack . battleID , & pack );
2024-06-24 03:23:26 +02:00
// battleStacksAttacked should be executed before BattleAttack.applyGs() to play animation before damaging unit
2023-10-19 16:19:09 +02:00
// so this has to be here instead of ApplyClientNetPackVisitor::visitBattleAttack()
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleStacksAttacked , pack . battleID , pack . bsa , pack . shot ());
}
void ApplyClientNetPackVisitor :: visitBattleAttack ( BattleAttack & pack )
{
}
void ApplyFirstClientNetPackVisitor :: visitStartAction ( StartAction & pack )
{
cl . currentBattleAction = std :: make_unique < BattleAction > ( pack . ba );
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: actionStarted , pack . battleID , pack . ba );
}
void ApplyClientNetPackVisitor :: visitBattleSpellCast ( BattleSpellCast & pack )
{
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleSpellCast , pack . battleID , & pack );
}
void ApplyClientNetPackVisitor :: visitSetStackEffect ( SetStackEffect & pack )
{
//informing about effects
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleStacksEffectsSet , pack . battleID , pack );
}
void ApplyClientNetPackVisitor :: visitStacksInjured ( StacksInjured & pack )
{
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleStacksAttacked , pack . battleID , pack . stacks , false );
}
void ApplyClientNetPackVisitor :: visitBattleResultsApplied ( BattleResultsApplied & pack )
{
2026-05-26 23:44:36 +02:00
showEagleEyeLearnedSpellsDialog ( cl , pack . learnedSpells . hid , pack . learnedSpells . spells , pack . victor );
2025-04-07 20:10:14 +02:00
2025-05-09 22:56:22 +02:00
if ( ! pack . movingArtifacts . empty ())
2025-04-05 22:55:57 +02:00
{
2025-07-31 21:52:48 +03:00
const auto * artSet = cl . gameState (). getArtSet ( ArtifactLocation ( pack . movingArtifacts . front (). dstArtHolder ));
2025-04-15 23:08:30 +02:00
assert ( artSet );
std :: vector < Component > artComponents ;
2025-05-09 22:56:22 +02:00
for ( const auto & artPack : pack . movingArtifacts )
2025-04-15 23:08:30 +02:00
{
auto packComponents = UIHelper :: getArtifactsComponents ( * artSet , artPack . artsPack0 );
artComponents . insert ( artComponents . end (), std :: make_move_iterator ( packComponents . begin ()), std :: make_move_iterator ( packComponents . end ()));
}
2025-04-07 20:10:14 +02:00
callInterfaceIfPresent ( cl , pack . victor , & CGameInterface :: showInfoDialog , EInfoWindowMode :: MODAL , UIHelper :: getArtifactsInfoWindowText (),
artComponents , soundBase :: soundID ( 0 ));
2025-04-15 23:08:30 +02:00
}
2025-04-05 22:55:57 +02:00
2025-05-09 22:56:22 +02:00
for ( auto & artPack : pack . movingArtifacts )
2025-04-05 22:55:57 +02:00
visitBulkMoveArtifacts ( artPack );
2025-04-07 20:10:14 +02:00
if ( pack . raisedStack . getCreature ())
callInterfaceIfPresent ( cl , pack . victor , & CGameInterface :: showInfoDialog , EInfoWindowMode :: AUTO ,
UIHelper :: getNecromancyInfoWindowText ( pack . raisedStack ), std :: vector < Component > { Component ( ComponentType :: CREATURE , pack . raisedStack . getId (),
2025-05-01 13:41:48 +03:00
pack . raisedStack . getCount ())}, UIHelper :: getNecromancyInfoWindowSound ());
2025-04-07 20:10:14 +02:00
2025-04-05 17:45:24 +02:00
callInterfaceIfPresent ( cl , pack . victor , & IGameEventsReceiver :: battleResultsApplied );
callInterfaceIfPresent ( cl , pack . loser , & IGameEventsReceiver :: battleResultsApplied );
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , PlayerColor :: SPECTATOR , & IGameEventsReceiver :: battleResultsApplied );
}
2025-11-14 12:48:10 +01:00
void ApplyClientNetPackVisitor :: visitBattleEnded ( BattleEnded & pack )
{
callInterfaceIfPresent ( cl , pack . victor , & IGameEventsReceiver :: battleEnded );
callInterfaceIfPresent ( cl , pack . loser , & IGameEventsReceiver :: battleEnded );
callInterfaceIfPresent ( cl , PlayerColor :: SPECTATOR , & IGameEventsReceiver :: battleEnded );
}
2023-10-19 16:19:09 +02:00
void ApplyClientNetPackVisitor :: visitBattleUnitsChanged ( BattleUnitsChanged & pack )
{
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleUnitsChanged , pack . battleID , pack . changedStacks );
}
void ApplyClientNetPackVisitor :: visitBattleObstaclesChanged ( BattleObstaclesChanged & pack )
{
//inform interfaces about removed obstacles
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleObstaclesChanged , pack . battleID , pack . changes );
}
void ApplyClientNetPackVisitor :: visitCatapultAttack ( CatapultAttack & pack )
{
//inform interfaces about catapult attack
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: battleCatapultAttacked , pack . battleID , pack );
}
void ApplyClientNetPackVisitor :: visitEndAction ( EndAction & pack )
{
callBattleInterfaceIfPresentForBothSides ( cl , pack . battleID , & IBattleEventsReceiver :: actionFinished , pack . battleID , * cl . currentBattleAction );
cl . currentBattleAction . reset ();
}
void ApplyClientNetPackVisitor :: visitPackageApplied ( PackageApplied & pack )
{
callInterfaceIfPresent ( cl , pack . player , & IGameEventsReceiver :: requestRealized , & pack );
2025-03-02 12:56:01 +00:00
if ( ! cl . waitingRequest . tryRemovingElement ( pack . requestID ))
2023-10-19 16:19:09 +02:00
logNetwork -> warn ( "Surprising server message! PackageApplied for unknown requestID!" );
}
void ApplyClientNetPackVisitor :: visitSystemMessage ( SystemMessage & pack )
{
2024-03-18 19:52:53 +02:00
// usually used to receive error messages from server
2024-05-02 21:03:23 +02:00
logNetwork -> error ( "System message: %s" , pack . text . toString ());
2023-10-19 16:19:09 +02:00
2025-02-11 15:23:33 +00:00
GAME -> server (). getGameChat (). onNewSystemMessageReceived ( pack . text . toString ());
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitPlayerBlocked ( PlayerBlocked & pack )
{
callInterfaceIfPresent ( cl , pack . player , & IGameEventsReceiver :: playerBlocked , pack . reason , pack . startOrEnd == PlayerBlocked :: BLOCKADE_STARTED );
}
void ApplyClientNetPackVisitor :: visitPlayerStartsTurn ( PlayerStartsTurn & pack )
{
logNetwork -> debug ( "Server gives turn to %s" , pack . player . toString ());
callAllInterfaces ( cl , & IGameEventsReceiver :: playerStartsTurn , pack . player );
callOnlyThatInterface ( cl , pack . player , & CGameInterface :: yourTurn , pack . queryID );
}
void ApplyClientNetPackVisitor :: visitPlayerEndsTurn ( PlayerEndsTurn & pack )
{
logNetwork -> debug ( "Server ends turn of %s" , pack . player . toString ());
callAllInterfaces ( cl , & IGameEventsReceiver :: playerEndsTurn , pack . player );
}
void ApplyClientNetPackVisitor :: visitTurnTimeUpdate ( TurnTimeUpdate & pack )
{
2023-11-23 17:59:18 +02:00
logNetwork -> debug ( "Server sets turn timer {turn: %d, base: %d, battle: %d, creature: %d} for %s" , pack . turnTimer . turnTimer , pack . turnTimer . baseTimer , pack . turnTimer . battleTimer , pack . turnTimer . unitTimer , pack . player . toString ());
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitPlayerMessageClient ( PlayerMessageClient & pack )
{
logNetwork -> debug ( "pack.player %s sends a message: %s" , pack . player . toString (), pack . text );
2025-02-11 15:23:33 +00:00
GAME -> server (). getGameChat (). onNewGameMessageReceived ( pack . player , pack . text );
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitAdvmapSpellCast ( AdvmapSpellCast & pack )
{
2025-01-23 14:39:56 +00:00
callAllInterfaces ( cl , & CGameInterface :: invalidatePaths );
2025-05-20 23:16:28 +03:00
auto caster = cl . gameInfo (). getHero ( pack . casterID );
2023-10-19 16:19:09 +02:00
if ( caster )
//consider notifying other interfaces that see hero?
callInterfaceIfPresent ( cl , caster -> getOwner (), & IGameEventsReceiver :: advmapSpellCast , caster , pack . spellID );
else
logNetwork -> error ( "Invalid hero instance" );
}
void ApplyClientNetPackVisitor :: visitShowWorldViewEx ( ShowWorldViewEx & pack )
{
callOnlyThatInterface ( cl , pack . player , & CGameInterface :: showWorldViewEx , pack . objectPositions , pack . showTerrain );
}
void ApplyClientNetPackVisitor :: visitOpenWindow ( OpenWindow & pack )
{
switch ( pack . window )
{
case EOpenWindowMode :: RECRUITMENT_FIRST :
case EOpenWindowMode :: RECRUITMENT_ALL :
{
2025-05-20 23:16:28 +03:00
const CGDwelling * dw = dynamic_cast < const CGDwelling *> ( cl . gameInfo (). getObj ( ObjectInstanceID ( pack . object )));
const CArmedInstance * dst = dynamic_cast < const CArmedInstance *> ( cl . gameInfo (). getObj ( ObjectInstanceID ( pack . visitor )));
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , dst -> tempOwner , & IGameEventsReceiver :: showRecruitmentDialog , dw , dst , pack . window == EOpenWindowMode :: RECRUITMENT_FIRST ? 0 : - 1 , pack . queryID );
}
break ;
case EOpenWindowMode :: SHIPYARD_WINDOW :
{
assert ( pack . queryID == QueryID :: NONE );
2025-05-20 23:16:28 +03:00
const auto * sy = dynamic_cast < const IShipyard *> ( cl . gameInfo (). getObj ( ObjectInstanceID ( pack . object )));
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , sy -> getObject () -> getOwner (), & IGameEventsReceiver :: showShipyardDialog , sy );
}
break ;
case EOpenWindowMode :: THIEVES_GUILD :
{
assert ( pack . queryID == QueryID :: NONE );
//displays Thieves' Guild window (when hero enters Den of Thieves)
2025-05-20 23:16:28 +03:00
const CGObjectInstance * obj = cl . gameInfo (). getObj ( ObjectInstanceID ( pack . object ));
const CGHeroInstance * hero = cl . gameInfo (). getHero ( ObjectInstanceID ( pack . visitor ));
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , hero -> getOwner (), & IGameEventsReceiver :: showThievesGuildWindow , obj );
}
break ;
case EOpenWindowMode :: UNIVERSITY_WINDOW :
{
//displays University window (when hero enters University on adventure map)
2025-05-20 23:16:28 +03:00
const auto * market = cl . gameState (). getMarket ( ObjectInstanceID ( pack . object ));
const CGHeroInstance * hero = cl . gameInfo (). getHero ( ObjectInstanceID ( pack . visitor ));
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , hero -> tempOwner , & IGameEventsReceiver :: showUniversityWindow , market , hero , pack . queryID );
}
break ;
case EOpenWindowMode :: MARKET_WINDOW :
{
//displays Thieves' Guild window (when hero enters Den of Thieves)
2025-05-20 23:16:28 +03:00
const CGObjectInstance * obj = cl . gameInfo (). getObj ( ObjectInstanceID ( pack . object ));
const CGHeroInstance * hero = cl . gameInfo (). getHero ( ObjectInstanceID ( pack . visitor ));
const auto market = cl . gameState (). getMarket ( pack . object );
const auto * tile = cl . gameInfo (). getTile ( obj -> visitablePos ());
const auto * topObject = cl . gameInfo (). getObjInstance ( tile -> visitableObjects . back ());
2025-03-19 14:40:45 +00:00
callInterfaceIfPresent ( cl , topObject -> getOwner (), & IGameEventsReceiver :: showMarketWindow , market , hero , pack . queryID );
2023-10-19 16:19:09 +02:00
}
break ;
case EOpenWindowMode :: HILL_FORT_WINDOW :
{
assert ( pack . queryID == QueryID :: NONE );
//displays Hill fort window
2025-05-20 23:16:28 +03:00
const CGObjectInstance * obj = cl . gameInfo (). getObj ( ObjectInstanceID ( pack . object ));
const CGHeroInstance * hero = cl . gameInfo (). getHero ( ObjectInstanceID ( pack . visitor ));
const auto * tile = cl . gameInfo (). getTile ( obj -> visitablePos ());
const auto * topObject = cl . gameInfo (). getObjInstance ( tile -> visitableObjects . back ());
2025-03-19 14:40:45 +00:00
callInterfaceIfPresent ( cl , topObject -> getOwner (), & IGameEventsReceiver :: showHillFortWindow , obj , hero );
2023-10-19 16:19:09 +02:00
}
break ;
case EOpenWindowMode :: PUZZLE_MAP :
{
assert ( pack . queryID == QueryID :: NONE );
2025-05-20 23:16:28 +03:00
const CGHeroInstance * hero = cl . gameInfo (). getHero ( ObjectInstanceID ( pack . visitor ));
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , hero -> getOwner (), & IGameEventsReceiver :: showPuzzleMap );
}
break ;
case EOpenWindowMode :: TAVERN_WINDOW :
{
2025-05-20 23:16:28 +03:00
const CGObjectInstance * obj1 = cl . gameInfo (). getObj ( ObjectInstanceID ( pack . object ));
const CGHeroInstance * hero = cl . gameInfo (). getHero ( ObjectInstanceID ( pack . visitor ));
2023-10-19 16:19:09 +02:00
callInterfaceIfPresent ( cl , hero -> tempOwner , & IGameEventsReceiver :: showTavernWindow , obj1 , hero , pack . queryID );
}
break ;
}
}
void ApplyClientNetPackVisitor :: visitCenterView ( CenterView & pack )
{
callInterfaceIfPresent ( cl , pack . player , & IGameEventsReceiver :: centerView , pack . pos , pack . focusTime );
}
void ApplyClientNetPackVisitor :: visitNewObject ( NewObject & pack )
{
2025-01-23 14:39:56 +00:00
callAllInterfaces ( cl , & CGameInterface :: invalidatePaths );
2023-10-19 16:19:09 +02:00
2025-03-13 19:42:18 +00:00
const CGObjectInstance * obj = pack . newObject . get ();
2025-02-11 15:23:33 +00:00
GAME -> map (). onObjectFadeIn ( obj , pack . initiator );
2023-10-19 16:19:09 +02:00
for ( auto i = cl . playerint . begin (); i != cl . playerint . end (); i ++ )
{
2025-05-14 15:50:13 +03:00
if ( gs . isVisibleFor ( obj , i -> first ))
2023-10-19 16:19:09 +02:00
i -> second -> newObject ( obj );
}
2023-12-03 18:39:25 +02:00
2025-02-11 15:23:33 +00:00
GAME -> map (). waitForOngoingAnimations ();
2023-10-19 16:19:09 +02:00
}
void ApplyClientNetPackVisitor :: visitSetAvailableArtifacts ( SetAvailableArtifacts & pack )
{
2025-04-19 16:12:07 +03:00
if ( ! pack . id . hasValue ()) //artifact merchants globally
2023-10-19 16:19:09 +02:00
{
callAllInterfaces ( cl , & IGameEventsReceiver :: availableArtifactsChanged , nullptr );
}
else
{
2025-05-20 23:16:28 +03:00
const CGBlackMarket * bm = dynamic_cast < const CGBlackMarket *> ( cl . gameInfo (). getObj ( ObjectInstanceID ( pack . id )));
2023-10-19 16:19:09 +02:00
assert ( bm );
2025-05-20 23:16:28 +03:00
const auto * tile = cl . gameInfo (). getTile ( bm -> visitablePos ());
const auto * topObject = cl . gameInfo (). getObjInstance ( tile -> visitableObjects . back ());
2025-03-19 14:40:45 +00:00
callInterfaceIfPresent ( cl , topObject -> getOwner (), & IGameEventsReceiver :: availableArtifactsChanged , bm );
2023-10-19 16:19:09 +02:00
}
}
void ApplyClientNetPackVisitor :: visitEntitiesChanged ( EntitiesChanged & pack )
{
2025-01-23 14:39:56 +00:00
callAllInterfaces ( cl , & CGameInterface :: invalidatePaths );
2023-10-19 16:19:09 +02:00
}
2025-05-19 00:40:05 +02:00
void ApplyClientNetPackVisitor :: visitPlayerCheated ( PlayerCheated & pack )
{
if ( pack . colorScheme != ColorScheme :: KEEP && vstd :: contains ( cl . playerint , pack . player ))
cl . playerint [ pack . player ] -> setColorScheme ( pack . colorScheme );
}
2025-10-05 03:18:47 +02:00
void ApplyClientNetPackVisitor :: visitChangeTownName ( ChangeTownName & pack )
{
if ( ! adventureInt )
return ;
const CGTownInstance * town = gs . getTown ( pack . tid );
if ( town )
{
adventureInt -> onTownChanged ( town );
ENGINE -> windows (). totalRedraw ();
}
}
2025-11-22 13:14:50 +01:00
void ApplyClientNetPackVisitor :: visitResponseStatistic ( ResponseStatistic & pack )
{
callInterfaceIfPresent ( cl , pack . player , & IGameEventsReceiver :: responseStatistic , pack . statistic );
}