1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

Merge pull request #3498 from IvanSavenko/simturns_pathfinder

[1.4.3] Fixes for simultaneous turns
This commit is contained in:
Ivan Savenko 2024-01-15 12:00:54 +02:00 committed by GitHub
commit b4a1a755a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 51 additions and 14 deletions

View File

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

View File

@ -467,6 +467,18 @@ void AdventureMapInterface::hotkeyEndingTurn()
LOCPLINT->cb->endTurn();
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)

View File

@ -22,6 +22,14 @@
#include "../../lib/MetaString.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> result;
@ -141,43 +149,51 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
else if(l.empty())
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;
if(time >= 0)
{
TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
tinfo.baseTimer = time;
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;
if(time >= 0)
{
TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
tinfo.turnTimer = time;
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;
if(time >= 0)
{
TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
tinfo.battleTimer = time;
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;
if(time >= 0)
{
TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
tinfo.unitTimer = time;
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"))
ww->setText(timeToString(turnTimerRemote.baseTimer), false);
if(auto ww = widget<CTextInput>("chessFieldTurn"))

View File

@ -371,6 +371,8 @@
"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
"useBoat" : true,
// if enabled, pathfinder will take use of any bidirectional monoliths

View File

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

View File

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

View File

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

View File

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

View File

@ -271,7 +271,12 @@ PathfinderBlockingRule::BlockingReason MovementAfterDestinationRule::getBlocking
case EPathNodeAction::BATTLE:
/// Movement after BATTLE action only possible from guarded tile to guardian tile
if(destination.guarded)
return BlockingReason::DESTINATION_GUARDED;
{
if (pathfinderHelper->options.ignoreGuards)
return BlockingReason::DESTINATION_GUARDED;
else
return BlockingReason::NONE;
}
break;
}
@ -299,6 +304,7 @@ PathfinderBlockingRule::BlockingReason MovementToDestinationRule::getBlockingRea
if(source.guarded)
{
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
{
return BlockingReason::SOURCE_GUARDED;

View File

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