2023-08-22 17:45:13 +02:00
/*
* TurnOrderProcessor . 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 "TurnOrderProcessor.h"
2023-12-23 18:11:25 +02:00
# include "PlayerMessageProcessor.h"
2023-08-22 17:45:13 +02:00
# include "../queries/QueriesProcessor.h"
2023-08-28 02:42:05 +02:00
# include "../queries/MapQueries.h"
2023-08-22 17:45:13 +02:00
# include "../CGameHandler.h"
# include "../CVCMIServer.h"
# include "../../lib/CPlayerState.h"
2024-05-19 22:03:24 +02:00
# include "../../lib/mapping/CMap.h"
# include "../../lib/mapObjects/CGObjectInstance.h"
# include "../../lib/gameState/CGameState.h"
2023-09-19 21:26:56 +02:00
# include "../../lib/pathfinder/CPathfinder.h"
# include "../../lib/pathfinder/PathfinderOptions.h"
2023-08-22 17:45:13 +02:00
TurnOrderProcessor : : TurnOrderProcessor ( CGameHandler * owner ) :
gameHandler ( owner )
{
}
2023-09-19 18:46:27 +02:00
int TurnOrderProcessor : : simturnsTurnsMaxLimit ( ) const
{
2024-05-19 19:12:29 +02:00
if ( simturnsMaxDurationDays )
return * simturnsMaxDurationDays ;
2023-09-22 18:57:43 +02:00
return gameHandler - > getStartInfo ( ) - > simturnsInfo . optionalTurns ;
2023-09-19 18:46:27 +02:00
}
int TurnOrderProcessor : : simturnsTurnsMinLimit ( ) const
2023-08-22 17:45:13 +02:00
{
2024-05-19 19:12:29 +02:00
if ( simturnsMinDurationDays )
return * simturnsMinDurationDays ;
2023-09-22 18:57:43 +02:00
return gameHandler - > getStartInfo ( ) - > simturnsInfo . requiredTurns ;
2023-09-18 21:09:16 +02:00
}
2023-12-23 18:11:25 +02:00
std : : vector < TurnOrderProcessor : : PlayerPair > TurnOrderProcessor : : computeContactStatus ( ) const
2023-09-20 12:53:06 +02:00
{
2023-12-23 18:11:25 +02:00
std : : vector < PlayerPair > result ;
2023-09-20 12:53:06 +02:00
assert ( actedPlayers . empty ( ) ) ;
assert ( actingPlayers . empty ( ) ) ;
for ( auto left : awaitingPlayers )
{
for ( auto right : awaitingPlayers )
{
if ( left = = right )
continue ;
if ( computeCanActSimultaneously ( left , right ) )
2023-12-23 18:11:25 +02:00
result . push_back ( { left , right } ) ;
2023-09-20 12:53:06 +02:00
}
}
2023-12-23 18:11:25 +02:00
return result ;
}
void TurnOrderProcessor : : updateAndNotifyContactStatus ( )
{
auto newBlockedContacts = computeContactStatus ( ) ;
if ( newBlockedContacts . empty ( ) )
{
// Simturns between all players have ended - send single global notification
if ( ! blockedContacts . empty ( ) )
gameHandler - > playerMessages - > broadcastSystemMessage ( " Simultaneous turns have ended " ) ;
}
else
{
// Simturns between some players have ended - notify each pair
for ( auto const & contact : blockedContacts )
{
if ( vstd : : contains ( newBlockedContacts , contact ) )
continue ;
MetaString message ;
message . appendRawString ( " Simultaneous turns between players %s and %s have ended " ) ; // FIXME: we should send MetaString itself and localize it on client side
message . replaceName ( contact . a ) ;
message . replaceName ( contact . b ) ;
gameHandler - > playerMessages - > broadcastSystemMessage ( message . toString ( ) ) ;
}
}
blockedContacts = newBlockedContacts ;
2023-09-20 12:53:06 +02:00
}
2023-09-18 21:09:16 +02:00
bool TurnOrderProcessor : : playersInContact ( PlayerColor left , PlayerColor right ) const
{
2023-09-19 21:26:56 +02:00
// TODO: refactor, cleanup and optimize
boost : : multi_array < bool , 3 > leftReachability ;
boost : : multi_array < bool , 3 > rightReachability ;
int3 mapSize = gameHandler - > getMapSize ( ) ;
leftReachability . resize ( boost : : extents [ mapSize . z ] [ mapSize . x ] [ mapSize . y ] ) ;
rightReachability . resize ( boost : : extents [ mapSize . z ] [ mapSize . x ] [ mapSize . y ] ) ;
const auto * leftInfo = gameHandler - > getPlayerState ( left , false ) ;
const auto * rightInfo = gameHandler - > getPlayerState ( right , false ) ;
2024-05-19 22:03:24 +02:00
for ( auto obj : gameHandler - > gameState ( ) - > map - > objects )
{
if ( obj & & obj - > isVisitable ( ) )
{
int3 pos = obj - > visitablePos ( ) ;
if ( obj - > tempOwner = = left )
leftReachability [ pos . z ] [ pos . x ] [ pos . y ] = true ;
if ( obj - > tempOwner = = right )
rightReachability [ pos . z ] [ pos . x ] [ pos . y ] = true ;
}
}
2023-09-19 21:26:56 +02:00
for ( const auto & hero : leftInfo - > heroes )
{
CPathsInfo out ( mapSize , hero ) ;
auto config = std : : make_shared < SingleHeroPathfinderConfig > ( out , gameHandler - > gameState ( ) , hero ) ;
2024-01-13 19:44:37 +02:00
config - > options . ignoreGuards = true ;
2024-01-13 23:23:36 +02:00
config - > options . turnLimit = 1 ;
2023-09-19 21:26:56 +02:00
CPathfinder pathfinder ( gameHandler - > gameState ( ) , config ) ;
pathfinder . calculatePaths ( ) ;
for ( int z = 0 ; z < mapSize . z ; + + z )
for ( int y = 0 ; y < mapSize . y ; + + y )
for ( int x = 0 ; x < mapSize . x ; + + x )
if ( out . getNode ( { x , y , z } ) - > reachable ( ) )
leftReachability [ z ] [ x ] [ y ] = true ;
}
for ( const auto & hero : rightInfo - > heroes )
{
CPathsInfo out ( mapSize , hero ) ;
auto config = std : : make_shared < SingleHeroPathfinderConfig > ( out , gameHandler - > gameState ( ) , hero ) ;
2024-01-13 19:44:37 +02:00
config - > options . ignoreGuards = true ;
2024-01-13 23:23:36 +02:00
config - > options . turnLimit = 1 ;
2023-09-19 21:26:56 +02:00
CPathfinder pathfinder ( gameHandler - > gameState ( ) , config ) ;
pathfinder . calculatePaths ( ) ;
for ( int z = 0 ; z < mapSize . z ; + + z )
for ( int y = 0 ; y < mapSize . y ; + + y )
for ( int x = 0 ; x < mapSize . x ; + + x )
if ( out . getNode ( { x , y , z } ) - > reachable ( ) )
rightReachability [ z ] [ x ] [ y ] = true ;
}
for ( int z = 0 ; z < mapSize . z ; + + z )
for ( int y = 0 ; y < mapSize . y ; + + y )
for ( int x = 0 ; x < mapSize . x ; + + x )
if ( leftReachability [ z ] [ x ] [ y ] & & rightReachability [ z ] [ x ] [ y ] )
return true ;
2023-08-22 17:45:13 +02:00
return false ;
}
2023-09-19 23:05:11 +02:00
bool TurnOrderProcessor : : isContactAllowed ( PlayerColor active , PlayerColor waiting ) const
{
2023-09-20 12:53:06 +02:00
assert ( active ! = waiting ) ;
return ! vstd : : contains ( blockedContacts , PlayerPair { active , waiting } ) ;
2023-09-19 23:05:11 +02:00
}
2023-09-20 12:53:06 +02:00
bool TurnOrderProcessor : : computeCanActSimultaneously ( PlayerColor active , PlayerColor waiting ) const
2023-09-18 21:09:16 +02:00
{
const auto * activeInfo = gameHandler - > getPlayerState ( active , false ) ;
const auto * waitingInfo = gameHandler - > getPlayerState ( waiting , false ) ;
assert ( active ! = waiting ) ;
assert ( activeInfo ) ;
assert ( waitingInfo ) ;
2024-07-08 22:57:47 +02:00
if ( activeInfo - > human ! = waitingInfo - > human )
2023-09-18 21:09:16 +02:00
{
2024-07-11 17:59:55 +02:00
// only one AI and one human can play simultaneously from single connection
2023-09-22 18:57:43 +02:00
if ( ! gameHandler - > getStartInfo ( ) - > simturnsInfo . allowHumanWithAI )
return false ;
2024-07-08 22:57:47 +02:00
}
else
{
// two AI or two humans in hotseat can't play at the same time
if ( gameHandler - > hasBothPlayersAtSameConnection ( active , waiting ) )
2023-09-18 21:09:16 +02:00
return false ;
}
2023-09-19 18:46:27 +02:00
if ( gameHandler - > getDate ( Date : : DAY ) < simturnsTurnsMinLimit ( ) )
return true ;
if ( gameHandler - > getDate ( Date : : DAY ) > simturnsTurnsMaxLimit ( ) )
return false ;
2024-05-19 22:13:42 +02:00
if ( gameHandler - > getStartInfo ( ) - > simturnsInfo . ignoreAlliedContacts & & activeInfo - > team = = waitingInfo - > team )
return true ;
2023-09-18 21:09:16 +02:00
if ( playersInContact ( active , waiting ) )
return false ;
return true ;
}
2023-08-22 17:45:13 +02:00
bool TurnOrderProcessor : : mustActBefore ( PlayerColor left , PlayerColor right ) const
{
const auto * leftInfo = gameHandler - > getPlayerState ( left , false ) ;
const auto * rightInfo = gameHandler - > getPlayerState ( right , false ) ;
assert ( left ! = right ) ;
assert ( leftInfo & & rightInfo ) ;
if ( ! leftInfo )
return false ;
if ( ! rightInfo )
return true ;
if ( leftInfo - > isHuman ( ) & & ! rightInfo - > isHuman ( ) )
return true ;
if ( ! leftInfo - > isHuman ( ) & & rightInfo - > isHuman ( ) )
return false ;
2023-11-09 00:08:13 +02:00
return false ;
2023-08-22 17:45:13 +02:00
}
bool TurnOrderProcessor : : canStartTurn ( PlayerColor which ) const
{
for ( auto player : awaitingPlayers )
{
2023-08-23 17:46:30 +02:00
if ( player ! = which & & mustActBefore ( player , which ) )
2023-08-22 17:45:13 +02:00
return false ;
}
for ( auto player : actingPlayers )
{
2023-09-20 12:53:06 +02:00
if ( player ! = which & & isContactAllowed ( player , which ) )
2023-08-22 17:45:13 +02:00
return false ;
}
return true ;
}
void TurnOrderProcessor : : doStartNewDay ( )
{
assert ( awaitingPlayers . empty ( ) ) ;
assert ( actingPlayers . empty ( ) ) ;
bool activePlayer = false ;
for ( auto player : actedPlayers )
{
if ( gameHandler - > getPlayerState ( player ) - > status = = EPlayerStatus : : INGAME )
activePlayer = true ;
}
if ( ! activePlayer )
2023-12-13 16:53:02 +02:00
{
2024-02-03 19:08:45 +02:00
gameHandler - > gameLobby ( ) - > setState ( EServerState : : SHUTDOWN ) ;
2023-12-13 16:53:02 +02:00
return ;
}
2023-08-22 17:45:13 +02:00
std : : swap ( actedPlayers , awaitingPlayers ) ;
2023-08-23 17:46:30 +02:00
gameHandler - > onNewTurn ( ) ;
2023-12-23 18:11:25 +02:00
updateAndNotifyContactStatus ( ) ;
2023-08-23 17:46:30 +02:00
tryStartTurnsForPlayers ( ) ;
2023-08-22 17:45:13 +02:00
}
void TurnOrderProcessor : : doStartPlayerTurn ( PlayerColor which )
{
assert ( gameHandler - > getPlayerState ( which ) ) ;
assert ( gameHandler - > getPlayerState ( which ) - > status = = EPlayerStatus : : INGAME ) ;
2024-04-26 12:16:02 +02:00
// Only if player is actually starting his turn (and not loading from save)
if ( ! actingPlayers . count ( which ) )
gameHandler - > onPlayerTurnStarted ( which ) ;
2023-08-23 17:46:30 +02:00
actingPlayers . insert ( which ) ;
awaitingPlayers . erase ( which ) ;
2023-08-22 17:45:13 +02:00
2023-10-07 01:44:37 +02:00
auto turnQuery = std : : make_shared < TimerPauseQuery > ( gameHandler , which ) ;
2023-08-28 02:42:05 +02:00
gameHandler - > queries - > addQuery ( turnQuery ) ;
2023-09-17 00:13:58 +02:00
PlayerStartsTurn pst ;
pst . player = which ;
pst . queryID = turnQuery - > queryID ;
gameHandler - > sendAndApply ( & pst ) ;
2023-08-23 17:46:30 +02:00
2023-09-18 21:09:16 +02:00
assert ( ! actingPlayers . empty ( ) ) ;
2023-08-22 17:45:13 +02:00
}
2023-08-24 12:36:35 +02:00
void TurnOrderProcessor : : doEndPlayerTurn ( PlayerColor which )
2023-08-22 17:45:13 +02:00
{
2023-08-24 22:34:28 +02:00
assert ( isPlayerMakingTurn ( which ) ) ;
2023-08-24 20:35:01 +02:00
assert ( gameHandler - > getPlayerStatus ( which ) = = EPlayerStatus : : INGAME ) ;
2023-08-22 17:45:13 +02:00
actingPlayers . erase ( which ) ;
2023-08-24 12:36:35 +02:00
actedPlayers . insert ( which ) ;
2023-08-22 17:45:13 +02:00
2023-09-17 00:13:58 +02:00
PlayerEndsTurn pet ;
pet . player = which ;
gameHandler - > sendAndApply ( & pet ) ;
2023-08-22 17:45:13 +02:00
if ( ! awaitingPlayers . empty ( ) )
tryStartTurnsForPlayers ( ) ;
if ( actingPlayers . empty ( ) )
doStartNewDay ( ) ;
2023-08-23 17:46:30 +02:00
assert ( ! actingPlayers . empty ( ) ) ;
2023-08-22 17:45:13 +02:00
}
void TurnOrderProcessor : : addPlayer ( PlayerColor which )
{
awaitingPlayers . insert ( which ) ;
}
2023-08-24 12:36:35 +02:00
void TurnOrderProcessor : : onPlayerEndsGame ( PlayerColor which )
{
awaitingPlayers . erase ( which ) ;
actingPlayers . erase ( which ) ;
actedPlayers . erase ( which ) ;
if ( ! awaitingPlayers . empty ( ) )
tryStartTurnsForPlayers ( ) ;
if ( actingPlayers . empty ( ) )
doStartNewDay ( ) ;
}
bool TurnOrderProcessor : : onPlayerEndsTurn ( PlayerColor which )
2023-08-22 17:45:13 +02:00
{
2023-08-24 22:34:28 +02:00
if ( ! isPlayerMakingTurn ( which ) )
2023-08-22 17:45:13 +02:00
{
gameHandler - > complain ( " Can not end turn for player that is not acting! " ) ;
return false ;
}
if ( gameHandler - > getPlayerStatus ( which ) ! = EPlayerStatus : : INGAME )
{
gameHandler - > complain ( " Can not end turn for player that is not in game! " ) ;
return false ;
}
if ( gameHandler - > queries - > topQuery ( which ) ! = nullptr )
{
gameHandler - > complain ( " Cannot end turn before resolving queries! " ) ;
return false ;
}
2023-08-24 12:36:35 +02:00
gameHandler - > onPlayerTurnEnded ( which ) ;
2023-08-23 17:46:30 +02:00
2023-08-24 20:35:01 +02:00
// it is possible that player have lost - e.g. spent 7 days without town
// in this case - don't call doEndPlayerTurn - turn transfer was already handled by onPlayerEndsGame
if ( gameHandler - > getPlayerStatus ( which ) = = EPlayerStatus : : INGAME )
doEndPlayerTurn ( which ) ;
2023-08-23 17:46:30 +02:00
2023-08-22 17:45:13 +02:00
return true ;
}
void TurnOrderProcessor : : onGameStarted ( )
{
2023-09-20 12:53:06 +02:00
if ( actingPlayers . empty ( ) )
2023-12-23 18:11:25 +02:00
blockedContacts = computeContactStatus ( ) ;
2023-09-20 12:53:06 +02:00
2023-08-22 17:45:13 +02:00
// this may be game load - send notification to players that they can act
auto actingPlayersCopy = actingPlayers ;
for ( auto player : actingPlayersCopy )
doStartPlayerTurn ( player ) ;
2023-08-23 17:46:30 +02:00
tryStartTurnsForPlayers ( ) ;
2023-08-22 17:45:13 +02:00
}
void TurnOrderProcessor : : tryStartTurnsForPlayers ( )
{
auto awaitingPlayersCopy = awaitingPlayers ;
for ( auto player : awaitingPlayersCopy )
{
if ( canStartTurn ( player ) )
doStartPlayerTurn ( player ) ;
}
}
2023-08-24 22:34:28 +02:00
bool TurnOrderProcessor : : isPlayerAwaitsTurn ( PlayerColor which ) const
2023-08-22 17:45:13 +02:00
{
return vstd : : contains ( awaitingPlayers , which ) ;
}
2023-08-24 22:34:28 +02:00
bool TurnOrderProcessor : : isPlayerMakingTurn ( PlayerColor which ) const
2023-08-22 17:45:13 +02:00
{
return vstd : : contains ( actingPlayers , which ) ;
}
2023-08-24 22:34:28 +02:00
bool TurnOrderProcessor : : isPlayerAwaitsNewDay ( PlayerColor which ) const
2023-08-22 17:45:13 +02:00
{
return vstd : : contains ( actedPlayers , which ) ;
}
2024-05-19 19:12:29 +02:00
void TurnOrderProcessor : : setMinSimturnsDuration ( int days )
{
simturnsMinDurationDays = gameHandler - > getDate ( Date : : DAY ) + days ;
}
void TurnOrderProcessor : : setMaxSimturnsDuration ( int days )
{
simturnsMaxDurationDays = gameHandler - > getDate ( Date : : DAY ) + days ;
}