浏览代码

Merge pull request #3498 from IvanSavenko/simturns_pathfinder

[1.4.3] Fixes for simultaneous turns
Ivan Savenko 1 年之前
父节点
当前提交
b4a1a755a4

+ 1 - 0
AI/VCAI/Pathfinding/AIPathfinderConfig.cpp

@@ -41,6 +41,7 @@ namespace AIPathfinding
 		std::shared_ptr<AINodeStorage> nodeStorage)
 		std::shared_ptr<AINodeStorage> nodeStorage)
 		:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), hero(nodeStorage->getHero())
 		:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), hero(nodeStorage->getHero())
 	{
 	{
+		options.ignoreGuards = false;
 		options.useEmbarkAndDisembark = true;
 		options.useEmbarkAndDisembark = true;
 		options.useTeleportTwoWay = true;
 		options.useTeleportTwoWay = true;
 		options.useTeleportOneWay = true;
 		options.useTeleportOneWay = true;

+ 12 - 0
client/adventureMap/AdventureMapInterface.cpp

@@ -467,6 +467,18 @@ void AdventureMapInterface::hotkeyEndingTurn()
 	LOCPLINT->cb->endTurn();
 	LOCPLINT->cb->endTurn();
 
 
 	mapAudio->onPlayerTurnEnded();
 	mapAudio->onPlayerTurnEnded();
+
+	// Normally, game will receive PlayerStartsTurn call almost instantly with new player ID that will switch UI to waiting mode
+	// However, when simturns are active it is possible for such call not to come because another player is still acting
+	// So find first player other than ours that is acting at the moment and update UI as if he had started turn
+	for (auto player = PlayerColor(0); player < PlayerColor::PLAYER_LIMIT; ++player)
+	{
+		if (player != LOCPLINT->playerID && LOCPLINT->cb->isPlayerMakingTurn(player))
+		{
+			onEnemyTurnStarted(player, LOCPLINT->cb->getStartInfo()->playerInfos.at(player).isControlledByHuman());
+			break;
+		}
+	}
 }
 }
 
 
 const CGObjectInstance* AdventureMapInterface::getActiveObject(const int3 &mapPos)
 const CGObjectInstance* AdventureMapInterface::getActiveObject(const int3 &mapPos)

+ 21 - 13
client/lobby/OptionsTabBase.cpp

@@ -22,6 +22,14 @@
 #include "../../lib/MetaString.h"
 #include "../../lib/MetaString.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 
 
+static std::string timeToString(int time)
+{
+	std::stringstream ss;
+	ss << time / 1000 / 60 << ":" << std::setw(2) << std::setfill('0') << time / 1000 % 60;
+	return ss.str();
+};
+
+
 std::vector<TurnTimerInfo> OptionsTabBase::getTimerPresets() const
 std::vector<TurnTimerInfo> OptionsTabBase::getTimerPresets() const
 {
 {
 	std::vector<TurnTimerInfo> result;
 	std::vector<TurnTimerInfo> result;
@@ -141,43 +149,51 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
 		else if(l.empty())
 		else if(l.empty())
 			return sec;
 			return sec;
 
 
-		return std::stoi(l) * 60 + std::stoi(r);
+		return std::min(24*60, std::stoi(l)) * 60 + std::stoi(r);
 	};
 	};
 
 
-	addCallback("parseAndSetTimer_base", [parseTimerString](const std::string & str){
+	addCallback("parseAndSetTimer_base", [this, parseTimerString](const std::string & str){
 		int time = parseTimerString(str) * 1000;
 		int time = parseTimerString(str) * 1000;
 		if(time >= 0)
 		if(time >= 0)
 		{
 		{
 			TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
 			TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
 			tinfo.baseTimer = time;
 			tinfo.baseTimer = time;
 			CSH->setTurnTimerInfo(tinfo);
 			CSH->setTurnTimerInfo(tinfo);
+			if(auto ww = widget<CTextInput>("chessFieldBase"))
+				ww->setText(timeToString(time), false);
 		}
 		}
 	});
 	});
-	addCallback("parseAndSetTimer_turn", [parseTimerString](const std::string & str){
+	addCallback("parseAndSetTimer_turn", [this, parseTimerString](const std::string & str){
 		int time = parseTimerString(str) * 1000;
 		int time = parseTimerString(str) * 1000;
 		if(time >= 0)
 		if(time >= 0)
 		{
 		{
 			TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
 			TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
 			tinfo.turnTimer = time;
 			tinfo.turnTimer = time;
 			CSH->setTurnTimerInfo(tinfo);
 			CSH->setTurnTimerInfo(tinfo);
+			if(auto ww = widget<CTextInput>("chessFieldTurn"))
+				ww->setText(timeToString(time), false);
 		}
 		}
 	});
 	});
-	addCallback("parseAndSetTimer_battle", [parseTimerString](const std::string & str){
+	addCallback("parseAndSetTimer_battle", [this, parseTimerString](const std::string & str){
 		int time = parseTimerString(str) * 1000;
 		int time = parseTimerString(str) * 1000;
 		if(time >= 0)
 		if(time >= 0)
 		{
 		{
 			TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
 			TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
 			tinfo.battleTimer = time;
 			tinfo.battleTimer = time;
 			CSH->setTurnTimerInfo(tinfo);
 			CSH->setTurnTimerInfo(tinfo);
+			if(auto ww = widget<CTextInput>("chessFieldBattle"))
+				ww->setText(timeToString(time), false);
 		}
 		}
 	});
 	});
-	addCallback("parseAndSetTimer_unit", [parseTimerString](const std::string & str){
+	addCallback("parseAndSetTimer_unit", [this, parseTimerString](const std::string & str){
 		int time = parseTimerString(str) * 1000;
 		int time = parseTimerString(str) * 1000;
 		if(time >= 0)
 		if(time >= 0)
 		{
 		{
 			TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
 			TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
 			tinfo.unitTimer = time;
 			tinfo.unitTimer = time;
 			CSH->setTurnTimerInfo(tinfo);
 			CSH->setTurnTimerInfo(tinfo);
+			if(auto ww = widget<CTextInput>("chessFieldUnit"))
+				ww->setText(timeToString(time), false);
 		}
 		}
 	});
 	});
 
 
@@ -359,14 +375,6 @@ void OptionsTabBase::recreate()
 		}
 		}
 	}
 	}
 
 
-	//chess timer
-	auto timeToString = [](int time) -> std::string
-	{
-		std::stringstream ss;
-		ss << time / 1000 / 60 << ":" << std::setw(2) << std::setfill('0') << time / 1000 % 60;
-		return ss.str();
-	};
-
 	if(auto ww = widget<CTextInput>("chessFieldBase"))
 	if(auto ww = widget<CTextInput>("chessFieldBase"))
 		ww->setText(timeToString(turnTimerRemote.baseTimer), false);
 		ww->setText(timeToString(turnTimerRemote.baseTimer), false);
 	if(auto ww = widget<CTextInput>("chessFieldTurn"))
 	if(auto ww = widget<CTextInput>("chessFieldTurn"))

+ 2 - 0
config/gameConfig.json

@@ -371,6 +371,8 @@
 		
 		
 		"pathfinder" :
 		"pathfinder" :
 		{
 		{
+			// if enabled, pathfinder will build path through locations guarded by wandering monsters
+			"ignoreGuards" : false,
 			// if enabled, pathfinder will take use of any available boats
 			// if enabled, pathfinder will take use of any available boats
 			"useBoat" : true,
 			"useBoat" : true,
 			// if enabled, pathfinder will take use of any bidirectional monoliths 
 			// if enabled, pathfinder will take use of any bidirectional monoliths 

+ 1 - 0
lib/GameSettings.cpp

@@ -95,6 +95,7 @@ void GameSettings::load(const JsonNode & input)
 		{EGameSettings::TEXTS_ROAD,                             "textData",  "road"                       },
 		{EGameSettings::TEXTS_ROAD,                             "textData",  "road"                       },
 		{EGameSettings::TEXTS_SPELL,                            "textData",  "spell"                      },
 		{EGameSettings::TEXTS_SPELL,                            "textData",  "spell"                      },
 		{EGameSettings::TEXTS_TERRAIN,                          "textData",  "terrain"                    },
 		{EGameSettings::TEXTS_TERRAIN,                          "textData",  "terrain"                    },
+		{EGameSettings::PATHFINDER_IGNORE_GUARDS,               "pathfinder", "ignoreGuards"              },
 		{EGameSettings::PATHFINDER_USE_BOAT,                    "pathfinder", "useBoat"                   },
 		{EGameSettings::PATHFINDER_USE_BOAT,                    "pathfinder", "useBoat"                   },
 		{EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY,        "pathfinder", "useMonolithTwoWay"         },
 		{EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY,        "pathfinder", "useMonolithTwoWay"         },
 		{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique"   },
 		{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique"   },

+ 1 - 0
lib/GameSettings.h

@@ -60,6 +60,7 @@ enum class EGameSettings
 	MAP_FORMAT_JSON_VCMI,
 	MAP_FORMAT_JSON_VCMI,
 	MAP_FORMAT_IN_THE_WAKE_OF_GODS,
 	MAP_FORMAT_IN_THE_WAKE_OF_GODS,
 	PATHFINDER_USE_BOAT,
 	PATHFINDER_USE_BOAT,
+	PATHFINDER_IGNORE_GUARDS,
 	PATHFINDER_USE_MONOLITH_TWO_WAY,
 	PATHFINDER_USE_MONOLITH_TWO_WAY,
 	PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE,
 	PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE,
 	PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM,
 	PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM,

+ 1 - 0
lib/pathfinder/PathfinderOptions.cpp

@@ -21,6 +21,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 PathfinderOptions::PathfinderOptions()
 PathfinderOptions::PathfinderOptions()
 	: useFlying(true)
 	: useFlying(true)
 	, useWaterWalking(true)
 	, useWaterWalking(true)
+	, ignoreGuards(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_IGNORE_GUARDS))
 	, useEmbarkAndDisembark(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_BOAT))
 	, useEmbarkAndDisembark(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_BOAT))
 	, useTeleportTwoWay(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY))
 	, useTeleportTwoWay(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY))
 	, useTeleportOneWay(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE))
 	, useTeleportOneWay(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE))

+ 1 - 0
lib/pathfinder/PathfinderOptions.h

@@ -25,6 +25,7 @@ struct DLL_LINKAGE PathfinderOptions
 	bool useFlying;
 	bool useFlying;
 	bool useWaterWalking;
 	bool useWaterWalking;
 	bool useEmbarkAndDisembark;
 	bool useEmbarkAndDisembark;
+	bool ignoreGuards;
 	bool useTeleportTwoWay; // Two-way monoliths and Subterranean Gate
 	bool useTeleportTwoWay; // Two-way monoliths and Subterranean Gate
 	bool useTeleportOneWay; // One-way monoliths with one known exit only
 	bool useTeleportOneWay; // One-way monoliths with one known exit only
 	bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit
 	bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit

+ 7 - 1
lib/pathfinder/PathfindingRules.cpp

@@ -271,7 +271,12 @@ PathfinderBlockingRule::BlockingReason MovementAfterDestinationRule::getBlocking
 	case EPathNodeAction::BATTLE:
 	case EPathNodeAction::BATTLE:
 		/// Movement after BATTLE action only possible from guarded tile to guardian tile
 		/// Movement after BATTLE action only possible from guarded tile to guardian tile
 		if(destination.guarded)
 		if(destination.guarded)
-			return BlockingReason::DESTINATION_GUARDED;
+		{
+			if (pathfinderHelper->options.ignoreGuards)
+				return BlockingReason::DESTINATION_GUARDED;
+			else
+				return BlockingReason::NONE;
+		}
 
 
 		break;
 		break;
 	}
 	}
@@ -299,6 +304,7 @@ PathfinderBlockingRule::BlockingReason MovementToDestinationRule::getBlockingRea
 		if(source.guarded)
 		if(source.guarded)
 		{
 		{
 			if(!(pathfinderConfig->options.originalMovementRules && source.node->layer == EPathfindingLayer::AIR) 
 			if(!(pathfinderConfig->options.originalMovementRules && source.node->layer == EPathfindingLayer::AIR) 
+				&& !pathfinderConfig->options.ignoreGuards
 				&&	(!destination.isGuardianTile || pathfinderHelper->getGuardiansCount(source.coord) > 1)) // Can step into tile of guard
 				&&	(!destination.isGuardianTile || pathfinderHelper->getGuardiansCount(source.coord) > 1)) // Can step into tile of guard
 			{
 			{
 				return BlockingReason::SOURCE_GUARDED;
 				return BlockingReason::SOURCE_GUARDED;

+ 4 - 0
server/processors/TurnOrderProcessor.cpp

@@ -106,6 +106,8 @@ bool TurnOrderProcessor::playersInContact(PlayerColor left, PlayerColor right) c
 	{
 	{
 		CPathsInfo out(mapSize, hero);
 		CPathsInfo out(mapSize, hero);
 		auto config = std::make_shared<SingleHeroPathfinderConfig>(out, gameHandler->gameState(), hero);
 		auto config = std::make_shared<SingleHeroPathfinderConfig>(out, gameHandler->gameState(), hero);
+		config->options.ignoreGuards = true;
+		config->options.turnLimit = 1;
 		CPathfinder pathfinder(gameHandler->gameState(), config);
 		CPathfinder pathfinder(gameHandler->gameState(), config);
 		pathfinder.calculatePaths();
 		pathfinder.calculatePaths();
 
 
@@ -120,6 +122,8 @@ bool TurnOrderProcessor::playersInContact(PlayerColor left, PlayerColor right) c
 	{
 	{
 		CPathsInfo out(mapSize, hero);
 		CPathsInfo out(mapSize, hero);
 		auto config = std::make_shared<SingleHeroPathfinderConfig>(out, gameHandler->gameState(), hero);
 		auto config = std::make_shared<SingleHeroPathfinderConfig>(out, gameHandler->gameState(), hero);
+		config->options.ignoreGuards = true;
+		config->options.turnLimit = 1;
 		CPathfinder pathfinder(gameHandler->gameState(), config);
 		CPathfinder pathfinder(gameHandler->gameState(), config);
 		pathfinder.calculatePaths();
 		pathfinder.calculatePaths();