2018-01-05 20:21:07 +03:00
/*
* CVCMIServer . 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"
2024-01-12 13:30:53 +02:00
# include "CVCMIServer.h"
2018-01-05 20:21:07 +03:00
# include "CGameHandler.h"
2024-01-12 13:30:53 +02:00
# include "GlobalLobbyProcessor.h"
2024-01-21 16:48:36 +02:00
# include "LobbyNetPackVisitors.h"
2023-07-24 00:20:35 +03:00
# include "processors/PlayerMessageProcessor.h"
2018-01-05 20:21:07 +03:00
2025-03-02 12:22:25 +00:00
# include "../lib/CThreadHelper.h"
2025-07-07 18:16:42 +03:00
# include "../lib/GameLibrary.h"
2024-08-23 20:25:58 +00:00
# include "../lib/campaign/CampaignState.h"
2024-10-11 16:30:16 +00:00
# include "../lib/entities/hero/CHeroHandler.h"
# include "../lib/entities/hero/CHeroClass.h"
2024-08-23 20:25:58 +00:00
# include "../lib/gameState/CGameState.h"
# include "../lib/mapping/CMapInfo.h"
# include "../lib/mapping/CMapHeader.h"
2025-06-24 18:18:44 +03:00
# include "../lib/modding/ModIncompatibility.h"
2024-08-23 20:25:58 +00:00
# include "../lib/rmg/CMapGenOptions.h"
2024-01-21 16:48:36 +02:00
# include "../lib/serializer/CMemorySerializer.h"
2025-06-26 17:15:36 +03:00
# include "../lib/serializer/GameConnection.h"
2024-07-20 12:55:17 +00:00
# include "../lib/texts/CGeneralTextHandler.h"
2018-01-05 20:21:07 +03:00
2023-08-23 20:03:40 +02:00
// UUID generation
# include <boost/uuid/uuid.hpp>
# include <boost/uuid/uuid_io.hpp>
# include <boost/uuid/uuid_generators.hpp>
2024-01-12 13:30:53 +02:00
# include <boost/program_options.hpp>
2023-08-23 20:03:40 +02:00
2023-11-18 16:34:18 +02:00
class CVCMIServerPackVisitor : public VCMI_LIB_WRAP_NAMESPACE ( ICPackVisitor )
{
private :
CVCMIServer & handler ;
std : : shared_ptr < CGameHandler > gh ;
2025-06-26 17:15:36 +03:00
std : : shared_ptr < GameConnection > connection ;
2023-11-18 16:34:18 +02:00
public :
2025-06-26 17:15:36 +03:00
CVCMIServerPackVisitor ( CVCMIServer & handler , const std : : shared_ptr < CGameHandler > & gh , const std : : shared_ptr < GameConnection > & connection )
2025-03-01 17:50:52 +00:00
: handler ( handler )
, gh ( gh )
, connection ( connection )
2023-11-18 16:34:18 +02:00
{
}
2024-02-11 16:13:13 +02:00
bool callTyped ( ) override { return false ; }
2023-11-18 16:34:18 +02:00
2024-02-11 16:13:13 +02:00
void visitForLobby ( CPackForLobby & packForLobby ) override
2023-11-18 16:34:18 +02:00
{
2025-03-01 17:50:52 +00:00
handler . handleReceivedPack ( connection , packForLobby ) ;
2023-11-18 16:34:18 +02:00
}
2024-02-11 16:13:13 +02:00
void visitForServer ( CPackForServer & serverPack ) override
2023-11-18 16:34:18 +02:00
{
if ( gh )
2025-06-29 16:13:56 +03:00
gh - > handleReceivedPack ( connection - > connectionID , serverPack ) ;
2023-11-18 16:34:18 +02:00
else
logNetwork - > error ( " Received pack for game server while in lobby! " ) ;
}
2024-02-11 16:13:13 +02:00
void visitForClient ( CPackForClient & clientPack ) override
2023-11-18 16:34:18 +02:00
{
}
} ;
2024-07-12 00:39:36 +03:00
CVCMIServer : : CVCMIServer ( uint16_t port , bool runByClient )
2025-06-29 16:13:56 +03:00
: currentClientId ( GameConnectionID : : FIRST_CONNECTION )
, currentPlayerId ( PlayerConnectionID : : FIRST_HUMAN )
2024-02-10 23:56:02 +02:00
, port ( port )
, runByClient ( runByClient )
2018-01-05 20:21:07 +03:00
{
uuid = boost : : uuids : : to_string ( boost : : uuids : : random_generator ( ) ( ) ) ;
logNetwork - > trace ( " CVCMIServer created! UUID: %s " , uuid ) ;
2024-01-21 16:48:36 +02:00
networkHandler = INetworkHandler : : createHandler ( ) ;
}
CVCMIServer : : ~ CVCMIServer ( ) = default ;
2025-02-02 19:11:05 +00:00
uint16_t CVCMIServer : : prepare ( bool connectToLobby , bool listenForConnections ) {
2024-07-12 00:39:36 +03:00
if ( connectToLobby ) {
lobbyProcessor = std : : make_unique < GlobalLobbyProcessor > ( * this ) ;
return 0 ;
} else {
2025-02-02 19:11:05 +00:00
return startAcceptingIncomingConnections ( listenForConnections ) ;
2024-07-12 00:39:36 +03:00
}
}
2025-02-02 19:11:05 +00:00
uint16_t CVCMIServer : : startAcceptingIncomingConnections ( bool listenForConnections )
2024-01-21 16:48:36 +02:00
{
2025-02-02 19:11:05 +00:00
networkServer = networkHandler - > createServerTCP ( * this ) ;
2024-07-12 00:39:36 +03:00
port
? logNetwork - > info ( " Port %d will be used " , port )
: logNetwork - > info ( " Randomly assigned port will be used " ) ;
2023-11-18 16:34:18 +02:00
2024-07-12 00:39:36 +03:00
// config port may be 0 => srvport will contain the OS-assigned port value
2025-02-02 19:11:05 +00:00
if ( listenForConnections )
{
auto srvport = networkServer - > start ( port ) ;
logNetwork - > info ( " Listening for connections at port %d " , srvport ) ;
return srvport ;
}
else
return 0 ;
2018-01-05 20:21:07 +03:00
}
2024-01-12 01:10:41 +02:00
void CVCMIServer : : onNewConnection ( const std : : shared_ptr < INetworkConnection > & connection )
2023-11-18 16:34:18 +02:00
{
2024-02-03 19:08:45 +02:00
if ( getState ( ) = = EServerState : : LOBBY )
2023-12-25 21:23:27 +02:00
{
2025-06-26 17:15:36 +03:00
activeConnections . push_back ( std : : make_shared < GameConnection > ( connection ) ) ;
2023-12-25 21:23:27 +02:00
activeConnections . back ( ) - > enterLobbyConnectionMode ( ) ;
}
2023-12-26 16:29:06 +02:00
else
{
2024-02-03 19:08:45 +02:00
// TODO: reconnection support
2024-02-02 15:32:06 +02:00
connection - > close ( ) ;
2023-12-26 16:29:06 +02:00
}
2023-11-18 16:34:18 +02:00
}
2024-02-02 01:27:19 +02:00
void CVCMIServer : : onPacketReceived ( const std : : shared_ptr < INetworkConnection > & connection , const std : : vector < std : : byte > & message )
2023-11-18 16:34:18 +02:00
{
2025-06-26 17:15:36 +03:00
std : : shared_ptr < GameConnection > c = findConnection ( connection ) ;
2024-06-04 14:12:16 +00:00
if ( c = = nullptr )
throw std : : out_of_range ( " Unknown connection received in CVCMIServer::findConnection " ) ;
2023-12-26 16:29:06 +02:00
auto pack = c - > retrievePack ( message ) ;
2025-03-01 17:50:52 +00:00
CVCMIServerPackVisitor visitor ( * this , this - > gh , c ) ;
2023-11-18 16:34:18 +02:00
pack - > visit ( visitor ) ;
}
2023-08-22 18:45:13 +03:00
void CVCMIServer : : setState ( EServerState value )
{
2024-04-07 14:19:57 +03:00
if ( value = = EServerState : : SHUTDOWN & & state = = EServerState : : SHUTDOWN )
logGlobal - > warn ( " Attempt to shutdown already shutdown server! " ) ;
// do not attempt to restart dying server
assert ( state ! = EServerState : : SHUTDOWN | | state = = value ) ;
2023-12-26 18:29:13 +02:00
state = value ;
2024-02-03 19:08:45 +02:00
if ( state = = EServerState : : SHUTDOWN )
networkHandler - > stop ( ) ;
2023-08-22 18:45:13 +03:00
}
EServerState CVCMIServer : : getState ( ) const
{
2023-12-26 18:29:13 +02:00
return state ;
2023-08-22 18:45:13 +03:00
}
2025-06-26 17:15:36 +03:00
std : : shared_ptr < GameConnection > CVCMIServer : : findConnection ( const std : : shared_ptr < INetworkConnection > & netConnection )
2023-11-18 16:34:18 +02:00
{
2024-01-21 16:48:36 +02:00
for ( const auto & gameConnection : activeConnections )
2023-12-25 21:23:27 +02:00
{
if ( gameConnection - > isMyConnection ( netConnection ) )
return gameConnection ;
}
2024-06-04 14:12:16 +00:00
return nullptr ;
2023-11-18 16:34:18 +02:00
}
2024-02-10 23:56:02 +02:00
bool CVCMIServer : : wasStartedByClient ( ) const
{
return runByClient ;
}
2018-01-05 20:21:07 +03:00
void CVCMIServer : : run ( )
{
2024-01-12 01:10:41 +02:00
networkHandler - > run ( ) ;
2023-12-26 20:54:32 +02:00
}
2018-01-05 20:21:07 +03:00
2023-12-26 20:54:32 +02:00
void CVCMIServer : : onTimer ( )
{
2024-02-03 19:08:45 +02:00
// we might receive onTimer call after transitioning from GAMEPLAY to LOBBY state, e.g. on game restart
if ( getState ( ) ! = EServerState : : GAMEPLAY )
2023-12-26 20:54:32 +02:00
return ;
2018-01-05 20:21:07 +03:00
2023-12-26 20:54:32 +02:00
static const auto serverUpdateInterval = std : : chrono : : milliseconds ( 100 ) ;
2023-11-18 16:34:18 +02:00
2023-12-26 20:54:32 +02:00
auto timeNow = std : : chrono : : steady_clock : : now ( ) ;
auto timePassedBefore = lastTimerUpdateTime - gameplayStartTime ;
auto timePassedNow = timeNow - gameplayStartTime ;
2023-11-18 16:34:18 +02:00
2023-12-26 20:54:32 +02:00
lastTimerUpdateTime = timeNow ;
2018-01-05 20:21:07 +03:00
2023-12-26 20:54:32 +02:00
auto msPassedBefore = std : : chrono : : duration_cast < std : : chrono : : milliseconds > ( timePassedBefore ) ;
auto msPassedNow = std : : chrono : : duration_cast < std : : chrono : : milliseconds > ( timePassedNow ) ;
auto msDelta = msPassedNow - msPassedBefore ;
if ( msDelta . count ( ) )
gh - > tick ( msDelta . count ( ) ) ;
2024-01-12 01:10:41 +02:00
networkHandler - > createTimer ( * this , serverUpdateInterval ) ;
2023-12-26 18:29:13 +02:00
}
2022-09-29 21:08:05 +04:00
void CVCMIServer : : prepareToRestart ( )
2018-01-05 20:21:07 +03:00
{
2024-02-03 19:08:45 +02:00
if ( getState ( ) ! = EServerState : : GAMEPLAY )
2018-01-05 20:21:07 +03:00
{
2024-02-03 19:08:45 +02:00
assert ( 0 ) ;
return ;
}
2025-03-03 14:46:15 +00:00
* si = * gh - > gs - > getInitialStartInfo ( ) ;
2024-02-03 19:08:45 +02:00
setState ( EServerState : : LOBBY ) ;
if ( si - > campState )
{
assert ( si - > campState - > currentScenario ( ) . has_value ( ) ) ;
campaignMap = si - > campState - > currentScenario ( ) . value_or ( CampaignScenarioID ( 0 ) ) ;
campaignBonus = si - > campState - > getBonusID ( campaignMap ) . value_or ( - 1 ) ;
2022-09-29 00:35:38 +04:00
}
2025-02-02 19:11:05 +00:00
2024-02-12 18:57:20 +02:00
for ( auto activeConnection : activeConnections )
activeConnection - > enterLobbyConnectionMode ( ) ;
2023-12-25 21:23:27 +02:00
2022-09-29 21:08:05 +04:00
gh = nullptr ;
}
2022-09-23 15:02:19 +04:00
2022-09-29 21:08:05 +04:00
bool CVCMIServer : : prepareToStartGame ( )
{
2023-08-22 20:10:20 +04:00
Load : : ProgressAccumulator progressTracking ;
Load : : Progress current ( 1 ) ;
progressTracking . include ( current ) ;
2024-02-03 22:24:32 +02:00
2024-03-12 20:53:39 +02:00
if ( lobbyProcessor )
lobbyProcessor - > sendGameStarted ( ) ;
2025-03-01 22:34:33 +00:00
auto progressTrackingThread = std : : thread ( [ this , & progressTracking ] ( )
2023-08-22 20:10:20 +04:00
{
2025-03-02 12:22:25 +00:00
setThreadName ( " progressTrackingThread " ) ;
2024-02-03 22:24:32 +02:00
auto currentProgress = std : : numeric_limits < Load : : Type > : : max ( ) ;
2023-08-22 20:10:20 +04:00
while ( ! progressTracking . finished ( ) )
2023-08-21 05:06:58 +04:00
{
2023-08-23 15:45:00 +04:00
if ( progressTracking . get ( ) ! = currentProgress )
{
2023-11-18 16:34:18 +02:00
//FIXME: UNGUARDED MULTITHREADED ACCESS!!!
2023-08-23 15:45:00 +04:00
currentProgress = progressTracking . get ( ) ;
2024-10-04 13:10:34 +00:00
LobbyLoadProgress loadProgress ;
loadProgress . progress = currentProgress ;
announcePack ( loadProgress ) ;
2023-08-23 15:45:00 +04:00
}
2025-03-01 22:34:33 +00:00
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 50 ) ) ;
2023-08-21 05:06:58 +04:00
}
} ) ;
2025-02-02 19:11:05 +00:00
2025-06-24 18:18:44 +03:00
gh = std : : make_shared < CGameHandler > ( * this ) ;
2018-01-05 20:21:07 +03:00
switch ( si - > mode )
{
2024-01-21 16:48:36 +02:00
case EStartMode : : CAMPAIGN :
2018-01-05 20:21:07 +03:00
logNetwork - > info ( " Preparing to start new campaign " ) ;
2024-10-30 23:23:56 +01:00
si - > startTime = std : : time ( nullptr ) ;
2023-09-30 20:03:33 +02:00
si - > fileURI = mi - > fileURI ;
2023-06-26 00:01:25 +03:00
si - > campState - > setCurrentMap ( campaignMap ) ;
si - > campState - > setCurrentMapBonus ( campaignBonus ) ;
2023-08-21 05:06:58 +04:00
gh - > init ( si . get ( ) , progressTracking ) ;
2018-01-05 20:21:07 +03:00
break ;
2024-01-21 16:48:36 +02:00
case EStartMode : : NEW_GAME :
2018-01-05 20:21:07 +03:00
logNetwork - > info ( " Preparing to start new game " ) ;
2024-10-30 23:23:56 +01:00
si - > startTime = std : : time ( nullptr ) ;
2023-09-30 20:03:33 +02:00
si - > fileURI = mi - > fileURI ;
2023-08-21 05:06:58 +04:00
gh - > init ( si . get ( ) , progressTracking ) ;
2018-01-05 20:21:07 +03:00
break ;
2024-01-21 16:48:36 +02:00
case EStartMode : : LOAD_GAME :
2018-01-05 20:21:07 +03:00
logNetwork - > info ( " Preparing to start loaded game " ) ;
2025-06-24 18:18:44 +03:00
if ( ! loadSavedGame ( * si ) )
2023-08-22 20:10:20 +04:00
{
current . finish ( ) ;
progressTrackingThread . join ( ) ;
2022-09-23 15:02:19 +04:00
return false ;
2023-08-22 20:10:20 +04:00
}
2018-01-05 20:21:07 +03:00
break ;
default :
logNetwork - > error ( " Wrong mode in StartInfo! " ) ;
assert ( 0 ) ;
break ;
}
2025-02-02 19:11:05 +00:00
2023-08-22 20:10:20 +04:00
current . finish ( ) ;
progressTrackingThread . join ( ) ;
2025-02-02 19:11:05 +00:00
2022-09-23 15:02:19 +04:00
return true ;
2018-01-05 20:21:07 +03:00
}
2023-11-18 16:34:18 +02:00
void CVCMIServer : : startGameImmediately ( )
2018-01-05 20:21:07 +03:00
{
2024-02-12 18:57:20 +02:00
for ( auto activeConnection : activeConnections )
2025-05-21 17:35:53 +03:00
activeConnection - > setCallback ( gh - > gameInfo ( ) ) ;
2018-01-05 20:21:07 +03:00
2025-06-24 18:18:44 +03:00
for ( auto activeConnection : activeConnections )
{
auto players = getAllClientPlayers ( activeConnection - > connectionID ) ;
std : : stringstream sbuffer ;
2025-06-29 16:13:56 +03:00
sbuffer < < " Connection " < < static_cast < int > ( activeConnection - > connectionID ) < < " will handle " < < players . size ( ) < < " player: " ;
2025-06-24 18:18:44 +03:00
for ( PlayerColor color : players )
sbuffer < < color < < " " ;
logGlobal - > info ( sbuffer . str ( ) ) ;
}
2024-01-21 16:48:36 +02:00
gh - > start ( si - > mode = = EStartMode : : LOAD_GAME ) ;
2024-02-03 19:08:45 +02:00
setState ( EServerState : : GAMEPLAY ) ;
2023-12-26 20:54:32 +02:00
lastTimerUpdateTime = gameplayStartTime = std : : chrono : : steady_clock : : now ( ) ;
onTimer ( ) ;
2024-05-14 22:48:32 +02:00
multiplayerWelcomeMessage ( ) ;
2018-01-05 20:21:07 +03:00
}
2024-02-02 01:27:19 +02:00
void CVCMIServer : : onDisconnected ( const std : : shared_ptr < INetworkConnection > & connection , const std : : string & errorMessage )
2023-02-12 09:23:39 +02:00
{
2023-11-18 16:34:18 +02:00
logNetwork - > error ( " Network error receiving a pack. Connection has been closed " ) ;
2023-02-12 09:23:39 +02:00
2025-06-26 17:15:36 +03:00
std : : shared_ptr < GameConnection > c = findConnection ( connection ) ;
2024-06-04 14:12:16 +00:00
2024-10-04 12:48:50 +00:00
// player may have already disconnected via clientDisconnected call
2024-11-25 17:31:20 +00:00
if ( c )
2024-01-21 16:48:36 +02:00
{
2024-10-10 20:31:11 +00:00
LobbyClientDisconnected lcd ;
lcd . clientId = c - > connectionID ;
2025-03-01 17:50:52 +00:00
handleReceivedPack ( c , lcd ) ;
2023-12-27 14:24:31 +02:00
}
2018-01-05 20:21:07 +03:00
}
2025-06-26 17:15:36 +03:00
void CVCMIServer : : handleReceivedPack ( std : : shared_ptr < GameConnection > connection , CPackForLobby & pack )
2018-01-05 20:21:07 +03:00
{
2025-03-01 17:50:52 +00:00
ClientPermissionsCheckerNetPackVisitor checker ( * this , connection ) ;
2024-10-04 13:10:34 +00:00
pack . visit ( checker ) ;
2024-08-23 20:25:58 +00:00
if ( checker . getResult ( ) )
{
2025-03-01 17:50:52 +00:00
ApplyOnServerNetPackVisitor applier ( * this , connection ) ;
2024-10-04 13:10:34 +00:00
pack . visit ( applier ) ;
2024-08-23 20:25:58 +00:00
if ( applier . getResult ( ) )
2024-10-04 13:10:34 +00:00
announcePack ( pack ) ;
2024-08-23 20:25:58 +00:00
}
2018-01-05 20:21:07 +03:00
}
2024-10-04 13:10:34 +00:00
void CVCMIServer : : announcePack ( CPackForLobby & pack )
2018-01-05 20:21:07 +03:00
{
2024-02-12 18:57:20 +02:00
for ( auto activeConnection : activeConnections )
2018-01-05 20:21:07 +03:00
{
2022-07-28 12:29:39 +03:00
// FIXME: we need to avoid sending something to client that not yet get answer for LobbyClientConnected
2018-01-05 20:21:07 +03:00
// Until UUID set we only pass LobbyClientConnected to this client
2023-12-25 21:23:27 +02:00
//if(c->uuid == uuid && !dynamic_cast<LobbyClientConnected *>(pack.get()))
// continue;
2024-10-04 13:10:34 +00:00
activeConnection - > sendPack ( pack ) ;
2018-01-05 20:21:07 +03:00
}
2024-08-23 20:25:58 +00:00
ApplyOnServerAfterAnnounceNetPackVisitor applier ( * this ) ;
2024-10-04 13:10:34 +00:00
pack . visit ( applier ) ;
2018-01-05 20:21:07 +03:00
}
2024-05-10 13:10:33 +00:00
void CVCMIServer : : announceMessage ( const MetaString & txt )
2022-09-23 15:02:19 +04:00
{
2024-05-02 21:03:23 +02:00
logNetwork - > info ( " Show message: %s " , txt . toString ( ) ) ;
2024-10-04 13:10:34 +00:00
LobbyShowMessage cm ;
cm . message = txt ;
announcePack ( cm ) ;
2022-09-23 15:02:19 +04:00
}
2024-05-02 21:03:23 +02:00
void CVCMIServer : : announceMessage ( const std : : string & txt )
{
MetaString str ;
str . appendRawString ( txt ) ;
announceMessage ( str ) ;
}
2024-05-10 13:10:33 +00:00
void CVCMIServer : : announceTxt ( const MetaString & txt , const std : : string & playerName )
2018-01-05 20:21:07 +03:00
{
2024-05-02 21:03:23 +02:00
logNetwork - > info ( " %s says: %s " , playerName , txt . toString ( ) ) ;
2024-10-04 13:10:34 +00:00
LobbyChatMessage cm ;
cm . playerName = playerName ;
cm . message = txt ;
announcePack ( cm ) ;
2018-01-05 20:21:07 +03:00
}
2024-05-02 21:03:23 +02:00
void CVCMIServer : : announceTxt ( const std : : string & txt , const std : : string & playerName )
{
MetaString str ;
str . appendRawString ( txt ) ;
announceTxt ( str , playerName ) ;
}
2025-06-29 16:13:56 +03:00
bool CVCMIServer : : passHost ( GameConnectionID toConnectionId )
2018-01-05 20:21:07 +03:00
{
2024-02-12 18:57:20 +02:00
for ( auto activeConnection : activeConnections )
2018-01-05 20:21:07 +03:00
{
2024-02-12 18:57:20 +02:00
if ( isClientHost ( activeConnection - > connectionID ) )
2018-01-05 20:21:07 +03:00
continue ;
2024-02-12 18:57:20 +02:00
if ( activeConnection - > connectionID ! = toConnectionId )
2018-01-05 20:21:07 +03:00
continue ;
2024-02-12 18:57:20 +02:00
hostClientId = activeConnection - > connectionID ;
2025-06-29 16:13:56 +03:00
announceTxt ( boost : : str ( boost : : format ( " Pass host to connection %d " ) % static_cast < int > ( toConnectionId ) ) ) ;
2018-01-05 20:21:07 +03:00
return true ;
}
return false ;
}
2025-06-26 17:15:36 +03:00
void CVCMIServer : : clientConnected ( std : : shared_ptr < GameConnection > c , std : : vector < std : : string > & names , const std : : string & uuid , EStartMode mode )
2018-01-05 20:21:07 +03:00
{
2024-02-03 19:08:45 +02:00
assert ( getState ( ) = = EServerState : : LOBBY ) ;
2023-12-26 18:29:13 +02:00
2025-06-29 16:13:56 +03:00
c - > connectionID = vstd : : next ( currentClientId , 1 ) ;
2025-03-01 17:50:52 +00:00
c - > uuid = uuid ;
2018-01-05 20:21:07 +03:00
2025-06-29 16:13:56 +03:00
if ( hostClientId = = GameConnectionID : : INVALID )
2018-01-05 20:21:07 +03:00
{
hostClientId = c - > connectionID ;
si - > mode = mode ;
}
2025-06-29 16:13:56 +03:00
logNetwork - > info ( " Connection with client %d established. UUID: %s " , static_cast < int > ( c - > connectionID ) , c - > uuid ) ;
2025-02-02 19:11:05 +00:00
2023-12-26 18:29:13 +02:00
for ( auto & name : names )
2018-01-05 20:21:07 +03:00
{
2025-06-29 16:13:56 +03:00
logNetwork - > info ( " Client %d player: %s " , static_cast < int > ( c - > connectionID ) , name ) ;
PlayerConnectionID id = vstd : : next ( currentPlayerId , 1 ) ;
2018-01-05 20:21:07 +03:00
2023-12-26 18:29:13 +02:00
ClientPlayer cp ;
cp . connection = c - > connectionID ;
cp . name = name ;
2025-05-24 22:50:03 +03:00
playerNames . try_emplace ( id , cp ) ;
2025-06-29 16:13:56 +03:00
announceTxt ( boost : : str ( boost : : format ( " %s (pid %d cid %d) joins the game " ) % name % static_cast < int > ( id ) % static_cast < int > ( c - > connectionID ) ) ) ;
2018-01-05 20:21:07 +03:00
2023-12-26 18:29:13 +02:00
//put new player in first slot with AI
for ( auto & elem : si - > playerInfos )
{
if ( elem . second . isControlledByAI ( ) & & ! elem . second . compOnly )
2018-01-05 20:21:07 +03:00
{
2023-12-26 18:29:13 +02:00
setPlayerConnectedId ( elem . second , id ) ;
break ;
2018-01-05 20:21:07 +03:00
}
}
}
}
2025-06-26 17:15:36 +03:00
void CVCMIServer : : clientDisconnected ( std : : shared_ptr < GameConnection > connection )
2018-01-05 20:21:07 +03:00
{
2024-10-04 12:48:50 +00:00
assert ( vstd : : contains ( activeConnections , connection ) ) ;
logGlobal - > trace ( " Received disconnection request " ) ;
2024-02-12 18:57:20 +02:00
vstd : : erase ( activeConnections , connection ) ;
2023-11-18 16:34:18 +02:00
2024-10-04 12:48:50 +00:00
if ( activeConnections . empty ( ) | | hostClientId = = connection - > connectionID )
{
setState ( EServerState : : SHUTDOWN ) ;
return ;
}
if ( gh & & getState ( ) = = EServerState : : GAMEPLAY )
{
2025-06-29 16:13:56 +03:00
gh - > handleClientDisconnection ( connection - > connectionID ) ;
2024-10-04 12:48:50 +00:00
}
2018-01-05 20:21:07 +03:00
}
2025-06-29 16:13:56 +03:00
void CVCMIServer : : setPlayerConnectedId ( PlayerSettings & pset , PlayerConnectionID player ) const
2018-01-05 20:21:07 +03:00
{
if ( vstd : : contains ( playerNames , player ) )
pset . name = playerNames . find ( player ) - > second . name ;
else
2025-02-14 16:23:37 +00:00
pset . name = LIBRARY - > generaltexth - > allTexts [ 468 ] ; //Computer
2018-01-05 20:21:07 +03:00
pset . connectedPlayerIDs . clear ( ) ;
2025-06-29 16:13:56 +03:00
if ( player ! = PlayerConnectionID : : PLAYER_AI )
2018-01-05 20:21:07 +03:00
pset . connectedPlayerIDs . insert ( player ) ;
}
void CVCMIServer : : updateStartInfoOnMapChange ( std : : shared_ptr < CMapInfo > mapInfo , std : : shared_ptr < CMapGenOptions > mapGenOpts )
{
mi = mapInfo ;
if ( ! mi )
return ;
auto namesIt = playerNames . cbegin ( ) ;
si - > playerInfos . clear ( ) ;
if ( mi - > scenarioOptionsOfSave )
{
2018-04-30 18:09:48 +03:00
si = CMemorySerializer : : deepCopy ( * mi - > scenarioOptionsOfSave ) ;
2024-01-21 16:48:36 +02:00
si - > mode = EStartMode : : LOAD_GAME ;
2018-01-05 20:21:07 +03:00
if ( si - > campState )
2023-06-26 00:01:25 +03:00
campaignMap = si - > campState - > currentScenario ( ) . value ( ) ;
2018-01-05 20:21:07 +03:00
for ( auto & ps : si - > playerInfos )
{
if ( ! ps . second . compOnly & & ps . second . connectedPlayerIDs . size ( ) & & namesIt ! = playerNames . cend ( ) )
{
setPlayerConnectedId ( ps . second , namesIt + + - > first ) ;
}
else
{
2025-06-29 16:13:56 +03:00
setPlayerConnectedId ( ps . second , PlayerConnectionID : : PLAYER_AI ) ;
2018-01-05 20:21:07 +03:00
}
}
}
2024-01-21 16:48:36 +02:00
else if ( si - > mode = = EStartMode : : NEW_GAME | | si - > mode = = EStartMode : : CAMPAIGN )
2018-01-05 20:21:07 +03:00
{
2023-06-26 01:07:55 +03:00
if ( mi - > campaign )
2018-01-05 20:21:07 +03:00
return ;
for ( int i = 0 ; i < mi - > mapHeader - > players . size ( ) ; i + + )
{
const PlayerInfo & pinfo = mi - > mapHeader - > players [ i ] ;
//neither computer nor human can play - no player
if ( ! ( pinfo . canHumanPlay | | pinfo . canComputerPlay ) )
continue ;
PlayerSettings & pset = si - > playerInfos [ PlayerColor ( i ) ] ;
pset . color = PlayerColor ( i ) ;
if ( pinfo . canHumanPlay & & namesIt ! = playerNames . cend ( ) )
{
setPlayerConnectedId ( pset , namesIt + + - > first ) ;
}
else
{
2025-06-29 16:13:56 +03:00
setPlayerConnectedId ( pset , PlayerConnectionID : : PLAYER_AI ) ;
2018-01-05 20:21:07 +03:00
if ( ! pinfo . canHumanPlay )
{
pset . compOnly = true ;
}
}
pset . castle = pinfo . defaultCastle ( ) ;
pset . hero = pinfo . defaultHero ( ) ;
2023-09-28 19:43:04 +03:00
if ( pset . hero ! = HeroTypeID : : RANDOM & & pinfo . hasCustomMainHero ( ) )
2018-01-05 20:21:07 +03:00
{
pset . hero = pinfo . mainCustomHeroId ;
2023-09-28 00:04:05 +02:00
pset . heroNameTextId = pinfo . mainCustomHeroNameTextId ;
2018-01-05 20:21:07 +03:00
pset . heroPortrait = pinfo . mainCustomHeroPortrait ;
}
}
if ( mi - > isRandomMap & & mapGenOpts )
si - > mapGenOptions = std : : shared_ptr < CMapGenOptions > ( mapGenOpts ) ;
else
si - > mapGenOptions . reset ( ) ;
}
2024-03-11 19:47:35 +02:00
if ( lobbyProcessor )
{
std : : string roomDescription ;
if ( si - > mapGenOptions )
{
if ( si - > mapGenOptions - > getMapTemplate ( ) )
roomDescription = si - > mapGenOptions - > getMapTemplate ( ) - > getName ( ) ;
// else - no template selected.
// TODO: handle this somehow?
}
else
roomDescription = mi - > getNameTranslated ( ) ;
lobbyProcessor - > sendChangeRoomDescription ( roomDescription ) ;
}
2018-01-05 20:21:07 +03:00
si - > mapname = mi - > fileURI ;
}
void CVCMIServer : : updateAndPropagateLobbyState ( )
{
// Update player settings for RMG
// TODO: find appropriate location for this code
2024-01-21 16:48:36 +02:00
if ( si - > mapGenOptions & & si - > mode = = EStartMode : : NEW_GAME )
2018-01-05 20:21:07 +03:00
{
for ( const auto & psetPair : si - > playerInfos )
{
const auto & pset = psetPair . second ;
si - > mapGenOptions - > setStartingTownForPlayer ( pset . color , pset . castle ) ;
2024-01-13 14:08:36 +02:00
si - > mapGenOptions - > setStartingHeroForPlayer ( pset . color , pset . hero ) ;
2018-01-05 20:21:07 +03:00
if ( pset . isControlledByHuman ( ) )
{
si - > mapGenOptions - > setPlayerTypeForStandardPlayer ( pset . color , EPlayerType : : HUMAN ) ;
}
2024-06-17 13:35:58 +00:00
else
{
si - > mapGenOptions - > setPlayerTypeForStandardPlayer ( pset . color , EPlayerType : : AI ) ;
}
2018-01-05 20:21:07 +03:00
}
}
2024-10-04 13:10:34 +00:00
LobbyUpdateState lus ;
2025-05-05 16:05:59 +03:00
lus . state = * static_cast < LobbyState * > ( this ) ;
2024-10-04 13:10:34 +00:00
announcePack ( lus ) ;
2018-01-05 20:21:07 +03:00
}
void CVCMIServer : : setPlayer ( PlayerColor clickedColor )
{
struct PlayerToRestore
{
PlayerColor color ;
2025-06-29 16:13:56 +03:00
PlayerConnectionID id ;
2025-08-31 05:54:03 +02:00
void reset ( ) { id = PlayerConnectionID : : INVALID ; color = PlayerColor : : CANNOT_DETERMINE ; }
2018-01-05 20:21:07 +03:00
PlayerToRestore ( ) { reset ( ) ; }
2025-05-24 22:50:03 +03:00
} ;
2018-01-05 20:21:07 +03:00
2025-05-24 22:50:03 +03:00
PlayerToRestore playerToRestore ;
2018-01-05 20:21:07 +03:00
PlayerSettings & clicked = si - > playerInfos [ clickedColor ] ;
//identify clicked player
2025-06-29 16:13:56 +03:00
PlayerConnectionID clickedNameID = PlayerConnectionID : : PLAYER_AI ;
2018-01-05 20:21:07 +03:00
if ( clicked . isControlledByHuman ( ) )
2024-06-24 03:23:26 +02:00
clickedNameID = * ( clicked . connectedPlayerIDs . begin ( ) ) ; //if not AI - set appropriate ID
2018-01-05 20:21:07 +03:00
2025-06-29 16:13:56 +03:00
if ( clickedNameID > PlayerConnectionID : : PLAYER_AI & & playerToRestore . id = = clickedNameID ) //player to restore is about to being replaced -> put him back to the old place
2018-01-05 20:21:07 +03:00
{
PlayerSettings & restPos = si - > playerInfos [ playerToRestore . color ] ;
setPlayerConnectedId ( restPos , playerToRestore . id ) ;
playerToRestore . reset ( ) ;
}
2025-06-29 16:13:56 +03:00
PlayerConnectionID newPlayer ; //which player will take clicked position
2018-01-05 20:21:07 +03:00
//who will be put here?
2025-06-29 16:13:56 +03:00
if ( clickedNameID = = PlayerConnectionID : : PLAYER_AI ) //AI player clicked -> if possible replace computer with unallocated player
2018-01-05 20:21:07 +03:00
{
newPlayer = getIdOfFirstUnallocatedPlayer ( ) ;
2025-06-29 16:13:56 +03:00
if ( newPlayer = = PlayerConnectionID : : PLAYER_AI ) //no "free" player -> get just first one
2018-01-05 20:21:07 +03:00
newPlayer = playerNames . begin ( ) - > first ;
}
else //human clicked -> take next
{
auto i = playerNames . find ( clickedNameID ) ; //clicked one
i + + ; //player AFTER clicked one
if ( i ! = playerNames . end ( ) )
newPlayer = i - > first ;
else
2025-06-29 16:13:56 +03:00
newPlayer = PlayerConnectionID : : PLAYER_AI ; //AI if we scrolled through all players
2018-01-05 20:21:07 +03:00
}
setPlayerConnectedId ( clicked , newPlayer ) ; //put player
//if that player was somewhere else, we need to replace him with computer
2025-06-29 16:13:56 +03:00
if ( newPlayer ! = PlayerConnectionID : : PLAYER_AI ) //not AI
2018-01-05 20:21:07 +03:00
{
for ( auto i = si - > playerInfos . begin ( ) ; i ! = si - > playerInfos . end ( ) ; i + + )
{
2025-06-29 16:13:56 +03:00
PlayerConnectionID curNameID = * ( i - > second . connectedPlayerIDs . begin ( ) ) ;
2018-01-05 20:21:07 +03:00
if ( i - > first ! = clickedColor & & curNameID = = newPlayer )
{
assert ( i - > second . connectedPlayerIDs . size ( ) ) ;
playerToRestore . color = i - > first ;
playerToRestore . id = newPlayer ;
2025-06-29 16:13:56 +03:00
setPlayerConnectedId ( i - > second , PlayerConnectionID : : PLAYER_AI ) ; //set computer
2018-01-05 20:21:07 +03:00
break ;
}
}
}
}
2025-05-24 22:50:03 +03:00
void CVCMIServer : : setPlayerName ( PlayerColor color , const std : : string & name )
2023-10-13 23:04:35 +02:00
{
2023-10-13 23:50:01 +02:00
if ( color = = PlayerColor : : CANNOT_DETERMINE )
return ;
2023-10-13 23:04:35 +02:00
2023-10-13 23:50:01 +02:00
PlayerSettings & player = si - > playerInfos . at ( color ) ;
2023-10-13 23:04:35 +02:00
2023-10-13 23:50:01 +02:00
if ( ! player . isControlledByHuman ( ) )
return ;
2023-10-16 21:24:59 +02:00
if ( player . connectedPlayerIDs . empty ( ) )
2023-10-13 23:50:01 +02:00
return ;
2025-06-29 16:13:56 +03:00
PlayerConnectionID nameID = * ( player . connectedPlayerIDs . begin ( ) ) ; //if not AI - set appropriate ID
2023-10-13 23:50:01 +02:00
playerNames [ nameID ] . name = name ;
setPlayerConnectedId ( player , nameID ) ;
2023-10-13 23:04:35 +02:00
}
2024-07-29 22:54:42 +02:00
void CVCMIServer : : setPlayerHandicap ( PlayerColor color , Handicap handicap )
2024-07-16 00:40:39 +02:00
{
if ( color = = PlayerColor : : CANNOT_DETERMINE )
return ;
si - > playerInfos [ color ] . handicap = handicap ;
int humanPlayer = 0 ;
for ( const auto & pi : si - > playerInfos )
if ( pi . second . isControlledByHuman ( ) )
humanPlayer + + ;
if ( humanPlayer < 2 ) // Singleplayer
return ;
2024-07-16 00:48:27 +02:00
MetaString str ;
str . appendTextID ( " vcmi.lobby.handicap " ) ;
str . appendRawString ( " " ) ;
str . appendName ( color ) ;
2024-07-16 02:01:55 +02:00
str . appendRawString ( " : " ) ;
2024-07-25 23:04:59 +02:00
if ( handicap . startBonus . empty ( ) & & handicap . percentIncome = = 100 & & handicap . percentGrowth = = 100 )
2024-07-16 02:01:55 +02:00
{
str . appendRawString ( " " ) ;
str . appendTextID ( " core.genrltxt.523 " ) ;
announceTxt ( str ) ;
return ;
}
for ( auto & res : EGameResID : : ALL_RESOURCES ( ) )
2024-07-24 01:34:29 +02:00
if ( handicap . startBonus [ res ] ! = 0 )
2024-07-16 02:01:55 +02:00
{
str . appendRawString ( " " ) ;
str . appendName ( res ) ;
str . appendRawString ( " : " ) ;
2024-07-24 01:34:29 +02:00
str . appendRawString ( std : : to_string ( handicap . startBonus [ res ] ) ) ;
2024-07-16 02:01:55 +02:00
}
2024-07-24 01:34:29 +02:00
if ( handicap . percentIncome ! = 100 )
{
str . appendRawString ( " " ) ;
str . appendTextID ( " core.jktext.32 " ) ;
str . appendRawString ( " : " ) ;
str . appendRawString ( std : : to_string ( handicap . percentIncome ) + " % " ) ;
}
2024-07-25 23:04:59 +02:00
if ( handicap . percentGrowth ! = 100 )
{
str . appendRawString ( " " ) ;
str . appendTextID ( " core.genrltxt.194 " ) ;
str . appendRawString ( " : " ) ;
str . appendRawString ( std : : to_string ( handicap . percentGrowth ) + " % " ) ;
}
2024-07-16 00:48:27 +02:00
announceTxt ( str ) ;
2024-07-16 00:40:39 +02:00
}
2018-01-05 20:21:07 +03:00
void CVCMIServer : : optionNextCastle ( PlayerColor player , int dir )
{
PlayerSettings & s = si - > playerInfos [ player ] ;
2023-07-11 15:16:02 +03:00
FactionID & cur = s . castle ;
2023-09-28 19:43:04 +03:00
auto & allowed = getPlayerInfo ( player ) . allowedFactions ;
const bool allowRandomTown = getPlayerInfo ( player ) . isFactionRandom ;
2018-01-05 20:21:07 +03:00
2023-09-28 19:43:04 +03:00
if ( cur = = FactionID : : NONE ) //no change
2018-01-05 20:21:07 +03:00
return ;
2023-09-28 19:43:04 +03:00
if ( cur = = FactionID : : RANDOM ) //first/last available
2018-01-05 20:21:07 +03:00
{
if ( dir > 0 )
cur = * allowed . begin ( ) ; //id of first town
else
cur = * allowed . rbegin ( ) ; //id of last town
}
else // next/previous available
{
if ( ( cur = = * allowed . begin ( ) & & dir < 0 ) | | ( cur = = * allowed . rbegin ( ) & & dir > 0 ) )
{
if ( allowRandomTown )
{
2023-09-28 19:43:04 +03:00
cur = FactionID : : RANDOM ;
2018-01-05 20:21:07 +03:00
}
else
{
if ( dir > 0 )
cur = * allowed . begin ( ) ;
else
cur = * allowed . rbegin ( ) ;
}
}
else
{
assert ( dir > = - 1 & & dir < = 1 ) ; //othervice std::advance may go out of range
2023-07-11 15:16:02 +03:00
auto iter = allowed . find ( cur ) ;
2018-01-05 20:21:07 +03:00
std : : advance ( iter , dir ) ;
cur = * iter ;
}
}
2023-09-28 19:43:04 +03:00
if ( s . hero . isValid ( ) & & ! getPlayerInfo ( player ) . hasCustomMainHero ( ) ) // remove hero unless it set to fixed one in map editor
2018-01-05 20:21:07 +03:00
{
2023-09-28 19:43:04 +03:00
s . hero = HeroTypeID : : RANDOM ;
2018-01-05 20:21:07 +03:00
}
2023-09-28 19:43:04 +03:00
if ( ! cur . isValid ( ) & & s . bonus = = PlayerStartingBonus : : RESOURCE )
s . bonus = PlayerStartingBonus : : RANDOM ;
2018-01-05 20:21:07 +03:00
}
2023-09-28 19:43:04 +03:00
void CVCMIServer : : optionSetCastle ( PlayerColor player , FactionID id )
2023-08-14 00:08:48 +02:00
{
PlayerSettings & s = si - > playerInfos [ player ] ;
FactionID & cur = s . castle ;
2023-09-28 19:43:04 +03:00
auto & allowed = getPlayerInfo ( player ) . allowedFactions ;
2023-08-14 00:08:48 +02:00
2023-09-28 19:43:04 +03:00
if ( cur = = FactionID : : NONE ) //no change
2023-08-14 00:08:48 +02:00
return ;
2023-09-28 19:43:04 +03:00
if ( allowed . find ( id ) = = allowed . end ( ) & & id ! = FactionID : : RANDOM ) // valid id
2023-08-14 00:08:48 +02:00
return ;
cur = static_cast < FactionID > ( id ) ;
2023-09-28 19:43:04 +03:00
if ( s . hero . isValid ( ) & & ! getPlayerInfo ( player ) . hasCustomMainHero ( ) ) // remove hero unless it set to fixed one in map editor
2023-08-14 00:08:48 +02:00
{
2023-09-28 19:43:04 +03:00
s . hero = HeroTypeID : : RANDOM ;
2023-08-14 00:08:48 +02:00
}
2023-09-28 19:43:04 +03:00
if ( ! cur . isValid ( ) & & s . bonus = = PlayerStartingBonus : : RESOURCE )
s . bonus = PlayerStartingBonus : : RANDOM ;
2023-08-14 00:08:48 +02:00
}
2023-06-25 21:16:03 +03:00
void CVCMIServer : : setCampaignMap ( CampaignScenarioID mapId )
2018-01-05 20:21:07 +03:00
{
campaignMap = mapId ;
2023-06-26 00:01:25 +03:00
si - > difficulty = si - > campState - > scenario ( mapId ) . difficulty ;
2018-01-05 20:21:07 +03:00
campaignBonus = - 1 ;
updateStartInfoOnMapChange ( si - > campState - > getMapInfo ( mapId ) ) ;
}
void CVCMIServer : : setCampaignBonus ( int bonusId )
{
campaignBonus = bonusId ;
2023-06-26 00:01:25 +03:00
const CampaignScenario & scenario = si - > campState - > scenario ( campaignMap ) ;
2025-05-28 18:03:17 +03:00
const CampaignBonus & bonus = scenario . travelOptions . bonusesToChoose . at ( bonusId ) ;
if ( bonus . getType ( ) = = CampaignBonusType : : HERO | | bonus . getType ( ) = = CampaignBonusType : : HEROES_FROM_PREVIOUS_SCENARIO )
2018-01-05 20:21:07 +03:00
{
2025-05-28 18:03:17 +03:00
PlayerColor startingPlayer = bonus . getType ( ) = = CampaignBonusType : : HERO ?
bonus . getValue < CampaignBonusStartingHero > ( ) . startingPlayer :
bonus . getValue < CampaignBonusHeroesFromScenario > ( ) . startingPlayer ;
2018-01-05 20:21:07 +03:00
for ( auto & elem : si - > playerInfos )
{
2025-05-28 18:03:17 +03:00
if ( elem . first = = startingPlayer )
2025-06-29 16:13:56 +03:00
setPlayerConnectedId ( elem . second , PlayerConnectionID : : FIRST_HUMAN ) ;
2018-01-05 20:21:07 +03:00
else
2025-06-29 16:13:56 +03:00
setPlayerConnectedId ( elem . second , PlayerConnectionID : : PLAYER_AI ) ;
2018-01-05 20:21:07 +03:00
}
}
}
void CVCMIServer : : optionNextHero ( PlayerColor player , int dir )
{
PlayerSettings & s = si - > playerInfos [ player ] ;
2023-09-28 19:43:04 +03:00
if ( ! s . castle . isValid ( ) | | s . hero = = HeroTypeID : : NONE )
2018-01-05 20:21:07 +03:00
return ;
2023-09-28 19:43:04 +03:00
if ( s . hero = = HeroTypeID : : RANDOM ) // first/last available
2018-01-05 20:21:07 +03:00
{
2023-09-28 19:43:04 +03:00
if ( dir > 0 )
s . hero = nextAllowedHero ( player , HeroTypeID ( - 1 ) , dir ) ;
else
2025-02-14 16:23:37 +00:00
s . hero = nextAllowedHero ( player , HeroTypeID ( LIBRARY - > heroh - > size ( ) ) , dir ) ;
2018-01-05 20:21:07 +03:00
}
else
{
2023-09-28 19:43:04 +03:00
s . hero = nextAllowedHero ( player , s . hero , dir ) ;
2018-01-05 20:21:07 +03:00
}
}
2023-09-28 19:43:04 +03:00
void CVCMIServer : : optionSetHero ( PlayerColor player , HeroTypeID id )
2023-08-14 00:08:48 +02:00
{
PlayerSettings & s = si - > playerInfos [ player ] ;
2023-09-28 19:43:04 +03:00
if ( ! s . castle . isValid ( ) | | s . hero = = HeroTypeID : : NONE )
2023-08-14 00:08:48 +02:00
return ;
2023-09-28 19:43:04 +03:00
if ( id = = HeroTypeID : : RANDOM )
2023-08-14 00:08:48 +02:00
{
2023-09-28 19:43:04 +03:00
s . hero = HeroTypeID : : RANDOM ;
2023-08-14 00:08:48 +02:00
}
if ( canUseThisHero ( player , id ) )
s . hero = static_cast < HeroTypeID > ( id ) ;
}
2023-09-28 19:43:04 +03:00
HeroTypeID CVCMIServer : : nextAllowedHero ( PlayerColor player , HeroTypeID initial , int direction )
2018-01-05 20:21:07 +03:00
{
2023-09-28 19:43:04 +03:00
HeroTypeID first ( initial . getNum ( ) + direction ) ;
if ( direction > 0 )
2018-01-05 20:21:07 +03:00
{
2025-02-14 16:23:37 +00:00
for ( auto i = first ; i . getNum ( ) < LIBRARY - > heroh - > size ( ) ; + + i )
2018-01-05 20:21:07 +03:00
if ( canUseThisHero ( player , i ) )
2023-09-28 19:43:04 +03:00
return i ;
2018-01-05 20:21:07 +03:00
}
else
{
2023-09-28 19:43:04 +03:00
for ( auto i = first ; i . getNum ( ) > = 0 ; - - i )
2018-01-05 20:21:07 +03:00
if ( canUseThisHero ( player , i ) )
2023-09-28 19:43:04 +03:00
return i ;
2018-01-05 20:21:07 +03:00
}
2023-09-28 19:43:04 +03:00
return HeroTypeID : : RANDOM ;
2018-01-05 20:21:07 +03:00
}
void CVCMIServer : : optionNextBonus ( PlayerColor player , int dir )
{
PlayerSettings & s = si - > playerInfos [ player ] ;
2025-05-24 22:50:03 +03:00
s . bonus = static_cast < PlayerStartingBonus > ( static_cast < int > ( s . bonus ) + dir ) ;
2018-01-05 20:21:07 +03:00
2023-09-28 19:43:04 +03:00
if ( s . hero = = HeroTypeID : : NONE & &
2025-05-24 22:50:03 +03:00
getPlayerInfo ( player ) . heroesNames . empty ( ) & &
s . bonus = = PlayerStartingBonus : : ARTIFACT ) //no hero - can't be artifact
2018-01-05 20:21:07 +03:00
{
if ( dir < 0 )
2025-05-24 22:50:03 +03:00
s . bonus = PlayerStartingBonus : : RANDOM ;
2018-01-05 20:21:07 +03:00
else
2025-05-24 22:50:03 +03:00
s . bonus = PlayerStartingBonus : : GOLD ;
2018-01-05 20:21:07 +03:00
}
2025-05-24 22:50:03 +03:00
if ( s . bonus > PlayerStartingBonus : : RESOURCE )
s . bonus = PlayerStartingBonus : : RANDOM ;
if ( s . bonus < PlayerStartingBonus : : RANDOM )
s . bonus = PlayerStartingBonus : : RESOURCE ;
2018-01-05 20:21:07 +03:00
2025-05-24 22:50:03 +03:00
if ( s . castle = = FactionID : : RANDOM & & s . bonus = = PlayerStartingBonus : : RESOURCE ) //random castle - can't be resource
2018-01-05 20:21:07 +03:00
{
if ( dir < 0 )
2025-05-24 22:50:03 +03:00
s . bonus = PlayerStartingBonus : : GOLD ;
2018-01-05 20:21:07 +03:00
else
2025-05-24 22:50:03 +03:00
s . bonus = PlayerStartingBonus : : RANDOM ;
2018-01-05 20:21:07 +03:00
}
}
2023-09-28 19:43:04 +03:00
void CVCMIServer : : optionSetBonus ( PlayerColor player , PlayerStartingBonus id )
2023-08-14 00:08:48 +02:00
{
PlayerSettings & s = si - > playerInfos [ player ] ;
2023-09-28 19:43:04 +03:00
if ( s . hero = = HeroTypeID : : NONE & &
! getPlayerInfo ( player ) . heroesNames . size ( ) & &
id = = PlayerStartingBonus : : ARTIFACT ) //no hero - can't be artifact
2023-08-14 00:08:48 +02:00
return ;
2023-09-28 19:43:04 +03:00
if ( id > PlayerStartingBonus : : RESOURCE )
2023-08-14 00:08:48 +02:00
return ;
2023-09-28 19:43:04 +03:00
if ( id < PlayerStartingBonus : : RANDOM )
2023-08-14 00:08:48 +02:00
return ;
2023-09-28 19:43:04 +03:00
if ( s . castle = = FactionID : : RANDOM & & id = = PlayerStartingBonus : : RESOURCE ) //random castle - can't be resource
2023-08-14 00:08:48 +02:00
return ;
2024-01-16 19:02:39 +00:00
s . bonus = id ;
2023-08-14 00:08:48 +02:00
}
2023-09-28 19:43:04 +03:00
bool CVCMIServer : : canUseThisHero ( PlayerColor player , HeroTypeID ID )
2018-01-05 20:21:07 +03:00
{
2025-02-14 10:43:55 +00:00
if ( ! ID . hasValue ( ) )
return false ;
2025-04-19 16:12:07 +03:00
if ( ID . getNum ( ) > = LIBRARY - > heroh - > size ( ) )
2025-02-14 10:43:55 +00:00
return false ;
2025-04-19 16:12:07 +03:00
if ( si - > playerInfos [ player ] . castle ! = ID . toHeroType ( ) - > heroClass - > faction )
2025-02-14 10:43:55 +00:00
return false ;
if ( vstd : : contains ( getUsedHeroes ( ) , ID ) )
return false ;
if ( ! mi - > mapHeader - > allowedHeroes . count ( ID ) )
return false ;
for ( const auto & disposedHero : mi - > mapHeader - > disposedHeroes )
if ( disposedHero . heroId = = ID & & ! disposedHero . players . count ( player ) )
return false ;
return true ;
2018-01-05 20:21:07 +03:00
}
2023-09-28 19:43:04 +03:00
std : : vector < HeroTypeID > CVCMIServer : : getUsedHeroes ( )
2018-01-05 20:21:07 +03:00
{
2023-09-28 19:43:04 +03:00
std : : vector < HeroTypeID > heroIds ;
2025-05-24 22:50:03 +03:00
for ( const auto & p : si - > playerInfos )
2018-01-05 20:21:07 +03:00
{
2023-09-28 19:43:04 +03:00
const auto & heroes = getPlayerInfo ( p . first ) . heroesNames ;
2025-05-24 22:50:03 +03:00
for ( const auto & hero : heroes )
2025-04-19 16:12:07 +03:00
if ( hero . heroId . hasValue ( ) )
2018-01-05 20:21:07 +03:00
heroIds . push_back ( hero . heroId ) ;
2023-09-28 19:43:04 +03:00
if ( p . second . hero ! = HeroTypeID : : RANDOM )
2018-01-05 20:21:07 +03:00
heroIds . push_back ( p . second . hero ) ;
}
return heroIds ;
}
2025-06-29 16:13:56 +03:00
PlayerConnectionID CVCMIServer : : getIdOfFirstUnallocatedPlayer ( ) const
2018-01-05 20:21:07 +03:00
{
for ( auto i = playerNames . cbegin ( ) ; i ! = playerNames . cend ( ) ; i + + )
{
if ( ! si - > getPlayersSettings ( i - > first ) )
return i - > first ;
}
2025-06-29 16:13:56 +03:00
return PlayerConnectionID : : PLAYER_AI ;
2018-01-05 20:21:07 +03:00
}
2024-02-03 17:04:14 +02:00
2024-05-14 22:48:32 +02:00
void CVCMIServer : : multiplayerWelcomeMessage ( )
{
int humanPlayer = 0 ;
2024-06-01 11:48:30 +00:00
for ( const auto & pi : si - > playerInfos )
2024-05-17 18:25:09 +00:00
if ( pi . second . isControlledByHuman ( ) )
2024-05-14 22:48:32 +02:00
humanPlayer + + ;
if ( humanPlayer < 2 ) // Singleplayer
return ;
2024-12-01 18:29:07 +01:00
gh - > playerMessages - > broadcastSystemMessage ( MetaString : : createFromTextID ( " vcmi.broadcast.command " ) ) ;
2024-05-19 17:12:29 +00:00
2024-07-16 00:58:59 +02:00
for ( const auto & pi : si - > playerInfos )
2024-07-25 23:04:59 +02:00
if ( ! pi . second . handicap . startBonus . empty ( ) | | pi . second . handicap . percentIncome ! = 100 | | pi . second . handicap . percentGrowth ! = 100 )
2024-07-16 00:58:59 +02:00
{
MetaString str ;
str . appendTextID ( " vcmi.lobby.handicap " ) ;
str . appendRawString ( " " ) ;
str . appendName ( pi . first ) ;
2024-07-16 02:01:55 +02:00
str . appendRawString ( " : " ) ;
for ( auto & res : EGameResID : : ALL_RESOURCES ( ) )
2024-07-24 01:34:29 +02:00
if ( pi . second . handicap . startBonus [ res ] ! = 0 )
2024-07-16 02:01:55 +02:00
{
str . appendRawString ( " " ) ;
str . appendName ( res ) ;
str . appendRawString ( " : " ) ;
2024-07-24 01:34:29 +02:00
str . appendRawString ( std : : to_string ( pi . second . handicap . startBonus [ res ] ) ) ;
2024-07-16 02:01:55 +02:00
}
2024-07-24 01:34:29 +02:00
if ( pi . second . handicap . percentIncome ! = 100 )
{
str . appendRawString ( " " ) ;
str . appendTextID ( " core.jktext.32 " ) ;
str . appendRawString ( " : " ) ;
str . appendRawString ( std : : to_string ( pi . second . handicap . percentIncome ) + " % " ) ;
}
2024-07-25 23:04:59 +02:00
if ( pi . second . handicap . percentGrowth ! = 100 )
{
str . appendRawString ( " " ) ;
str . appendTextID ( " core.genrltxt.194 " ) ;
str . appendRawString ( " : " ) ;
str . appendRawString ( std : : to_string ( pi . second . handicap . percentGrowth ) + " % " ) ;
}
2024-07-16 02:20:49 +02:00
gh - > playerMessages - > broadcastSystemMessage ( str ) ;
2024-07-16 00:58:59 +02:00
}
2024-05-14 22:48:32 +02:00
std : : vector < std : : string > optionIds ;
if ( si - > extraOptionsInfo . cheatsAllowed )
2024-06-01 11:48:30 +00:00
optionIds . emplace_back ( " vcmi.optionsTab.cheatAllowed.hover " ) ;
2024-05-14 22:48:32 +02:00
if ( si - > extraOptionsInfo . unlimitedReplay )
2024-06-01 11:48:30 +00:00
optionIds . emplace_back ( " vcmi.optionsTab.unlimitedReplay.hover " ) ;
2024-05-14 22:48:32 +02:00
if ( ! optionIds . size ( ) ) // No settings to publish
return ;
MetaString str ;
2024-05-15 12:40:01 +02:00
str . appendTextID ( " vcmi.optionsTab.extraOptions.hover " ) ;
2024-05-14 22:48:32 +02:00
str . appendRawString ( " : " ) ;
for ( int i = 0 ; i < optionIds . size ( ) ; i + + )
{
str . appendTextID ( optionIds [ i ] ) ;
if ( i < optionIds . size ( ) - 1 )
str . appendRawString ( " , " ) ;
}
gh - > playerMessages - > broadcastSystemMessage ( str ) ;
}
2024-02-03 17:04:14 +02:00
INetworkHandler & CVCMIServer : : getNetworkHandler ( )
{
return * networkHandler ;
}
2025-02-02 17:47:37 +00:00
INetworkServer & CVCMIServer : : getNetworkServer ( )
{
return * networkServer ;
}
2025-06-24 18:18:44 +03:00
bool CVCMIServer : : loadSavedGame ( const StartInfo & info )
{
try
{
gh - > load ( info ) ;
}
catch ( const ModIncompatibility & e )
{
logGlobal - > error ( " Failed to load game: %s " , e . what ( ) ) ;
MetaString errorMsg ;
if ( ! e . whatMissing ( ) . empty ( ) )
{
errorMsg . appendTextID ( " vcmi.server.errors.modsToEnable " ) ;
errorMsg . appendRawString ( " \n " ) ;
errorMsg . appendRawString ( e . whatMissing ( ) ) ;
}
if ( ! e . whatExcessive ( ) . empty ( ) )
{
errorMsg . appendTextID ( " vcmi.server.errors.modsToDisable " ) ;
errorMsg . appendRawString ( " \n " ) ;
errorMsg . appendRawString ( e . whatExcessive ( ) ) ;
}
announceMessage ( errorMsg ) ;
return false ;
}
catch ( const IdentifierResolutionException & e )
{
logGlobal - > error ( " Failed to load game: %s " , e . what ( ) ) ;
MetaString errorMsg ;
errorMsg . appendTextID ( " vcmi.server.errors.unknownEntity " ) ;
errorMsg . replaceRawString ( e . identifierName ) ;
announceMessage ( errorMsg ) ;
return false ;
}
catch ( const std : : exception & e )
{
logGlobal - > error ( " Failed to load game: %s " , e . what ( ) ) ;
auto str = MetaString : : createFromTextID ( " vcmi.broadcast.failedLoadGame " ) ;
str . appendRawString ( " : " ) ;
str . appendRawString ( e . what ( ) ) ;
announceMessage ( str ) ;
return false ;
}
return true ;
}
bool CVCMIServer : : isPlayerHost ( const PlayerColor & color ) const
{
return LobbyInfo : : isPlayerHost ( color ) ;
}
2025-06-29 16:13:56 +03:00
bool CVCMIServer : : hasPlayerAt ( PlayerColor player , GameConnectionID connectionID ) const
2025-06-24 18:18:44 +03:00
{
2025-06-29 16:13:56 +03:00
return vstd : : contains ( getAllClientPlayers ( connectionID ) , player ) ;
2025-06-24 18:18:44 +03:00
}
bool CVCMIServer : : hasBothPlayersAtSameConnection ( PlayerColor left , PlayerColor right ) const
{
for ( const auto & c : activeConnections )
2025-06-29 16:13:56 +03:00
if ( hasPlayerAt ( left , c - > connectionID ) & & hasPlayerAt ( right , c - > connectionID ) )
2025-06-24 18:18:44 +03:00
return true ;
return false ;
}
2025-06-29 16:13:56 +03:00
void CVCMIServer : : applyPack ( CPackForClient & pack )
2025-06-24 18:18:44 +03:00
{
2025-06-29 16:13:56 +03:00
logNetwork - > trace ( " \t Sending to all clients: %s " , typeid ( pack ) . name ( ) ) ;
2025-06-24 18:18:44 +03:00
for ( const auto & c : activeConnections )
c - > sendPack ( pack ) ;
2025-06-29 16:13:56 +03:00
gh - > gs - > apply ( pack ) ;
logNetwork - > trace ( " \t Applied on gameState(): %s " , typeid ( pack ) . name ( ) ) ;
}
void CVCMIServer : : sendPack ( CPackForClient & pack , GameConnectionID connectionID )
{
for ( const auto & c : activeConnections )
if ( c - > connectionID = = connectionID )
c - > sendPack ( pack ) ;
2025-06-24 18:18:44 +03:00
}