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

Merge pull request #4022 from vcmi/master

Merge master -> beta
This commit is contained in:
Ivan Savenko 2024-05-20 18:09:51 +03:00 committed by GitHub
commit ffe14fc1fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 238 additions and 140 deletions

View File

@ -21,7 +21,6 @@
#include "../../lib/CTownHandler.h" #include "../../lib/CTownHandler.h"
#include "../../lib/mapObjects/MiscObjects.h" #include "../../lib/mapObjects/MiscObjects.h"
#include "../../lib/spells/CSpellHandler.h" #include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CondSh.h"
#include "Pathfinding/AIPathfinder.h" #include "Pathfinding/AIPathfinder.h"
#include "Engine/Nullkiller.h" #include "Engine/Nullkiller.h"

View File

@ -23,7 +23,6 @@
#include "../../lib/CTownHandler.h" #include "../../lib/CTownHandler.h"
#include "../../lib/mapObjects/MiscObjects.h" #include "../../lib/mapObjects/MiscObjects.h"
#include "../../lib/spells/CSpellHandler.h" #include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CondSh.h"
#include "Pathfinding/AIPathfinder.h" #include "Pathfinding/AIPathfinder.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN

View File

@ -10,7 +10,7 @@ android {
applicationId "is.xyz.vcmi" applicationId "is.xyz.vcmi"
minSdk 19 minSdk 19
targetSdk 33 targetSdk 33
versionCode 1513 versionCode 1515
versionName "1.5.1" versionName "1.5.1"
setProperty("archivesBaseName", "vcmi") setProperty("archivesBaseName", "vcmi")
} }

View File

@ -442,6 +442,8 @@ static void mainLoop()
[[noreturn]] static void quitApplication() [[noreturn]] static void quitApplication()
{ {
CSH->endNetwork();
if(!settings["session"]["headless"].Bool()) if(!settings["session"]["headless"].Bool())
{ {
if(CSH->client) if(CSH->client)
@ -450,6 +452,8 @@ static void mainLoop()
GH.windows().clear(); GH.windows().clear();
} }
vstd::clear_pointer(CSH);
CMM.reset(); CMM.reset();
if(!settings["session"]["headless"].Bool()) if(!settings["session"]["headless"].Bool())
@ -473,7 +477,6 @@ static void mainLoop()
vstd::clear_pointer(graphics); vstd::clear_pointer(graphics);
} }
vstd::clear_pointer(CSH);
vstd::clear_pointer(VLC); vstd::clear_pointer(VLC);
// sometimes leads to a hang. TODO: investigate // sometimes leads to a hang. TODO: investigate

View File

@ -365,6 +365,7 @@ set(client_HEADERS
Client.h Client.h
ClientCommandManager.h ClientCommandManager.h
ClientNetPackVisitors.h ClientNetPackVisitors.h
ConditionalWait.h
HeroMovementController.h HeroMovementController.h
GameChatHandler.h GameChatHandler.h
LobbyClientNetPackVisitors.h LobbyClientNetPackVisitors.h

View File

@ -72,7 +72,6 @@
#include "../lib/CStopWatch.h" #include "../lib/CStopWatch.h"
#include "../lib/CThreadHelper.h" #include "../lib/CThreadHelper.h"
#include "../lib/CTownHandler.h" #include "../lib/CTownHandler.h"
#include "../lib/CondSh.h"
#include "../lib/GameConstants.h" #include "../lib/GameConstants.h"
#include "../lib/RoadHandler.h" #include "../lib/RoadHandler.h"
#include "../lib/StartInfo.h" #include "../lib/StartInfo.h"
@ -140,7 +139,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player):
battleInt = nullptr; battleInt = nullptr;
castleInt = nullptr; castleInt = nullptr;
makingTurn = false; makingTurn = false;
showingDialog = new CondSh<bool>(false); showingDialog = new ConditionalWait();
cingconsole = new CInGameConsole(); cingconsole = new CInGameConsole();
firstCall = 1; //if loading will be overwritten in serialize firstCall = 1; //if loading will be overwritten in serialize
autosaveCount = 0; autosaveCount = 0;
@ -1005,7 +1004,7 @@ void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector
if (makingTurn && GH.windows().count() > 0 && LOCPLINT == this) if (makingTurn && GH.windows().count() > 0 && LOCPLINT == this)
{ {
CCS->soundh->playSound(static_cast<soundBase::soundID>(soundID)); CCS->soundh->playSound(static_cast<soundBase::soundID>(soundID));
showingDialog->set(true); showingDialog->setBusy();
movementController->requestMovementAbort(); // interrupt movement to show dialog movementController->requestMovementAbort(); // interrupt movement to show dialog
GH.windows().pushWindow(temp); GH.windows().pushWindow(temp);
} }
@ -1028,7 +1027,7 @@ void CPlayerInterface::showInfoDialogAndWait(std::vector<Component> & components
void CPlayerInterface::showYesNoDialog(const std::string &text, CFunctionList<void()> onYes, CFunctionList<void()> onNo, const std::vector<std::shared_ptr<CComponent>> & components) void CPlayerInterface::showYesNoDialog(const std::string &text, CFunctionList<void()> onYes, CFunctionList<void()> onNo, const std::vector<std::shared_ptr<CComponent>> & components)
{ {
movementController->requestMovementAbort(); movementController->requestMovementAbort();
LOCPLINT->showingDialog->setn(true); LOCPLINT->showingDialog->setBusy();
CInfoWindow::showYesNoDialog(text, components, onYes, onNo, playerID); CInfoWindow::showYesNoDialog(text, components, onYes, onNo, playerID);
} }
@ -1217,7 +1216,7 @@ void CPlayerInterface::loadGame( BinaryDeserializer & h )
void CPlayerInterface::moveHero( const CGHeroInstance *h, const CGPath& path ) void CPlayerInterface::moveHero( const CGHeroInstance *h, const CGPath& path )
{ {
assert(h); assert(h);
assert(!showingDialog->get()); assert(!showingDialog->isBusy());
assert(dialogs.empty()); assert(dialogs.empty());
LOG_TRACE(logGlobal); LOG_TRACE(logGlobal);
@ -1227,7 +1226,7 @@ void CPlayerInterface::moveHero( const CGHeroInstance *h, const CGPath& path )
return; //can't find hero return; //can't find hero
//It shouldn't be possible to move hero with open dialog (or dialog waiting in bg) //It shouldn't be possible to move hero with open dialog (or dialog waiting in bg)
if (showingDialog->get() || !dialogs.empty()) if (showingDialog->isBusy() || !dialogs.empty())
return; return;
if (localState->isHeroSleeping(h)) if (localState->isHeroSleeping(h))
@ -1395,9 +1394,7 @@ void CPlayerInterface::waitWhileDialog()
} }
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
boost::unique_lock<boost::mutex> un(showingDialog->mx); showingDialog->waitWhileBusy();
while(showingDialog->data)
showingDialog->cond.wait(un);
} }
void CPlayerInterface::showShipyardDialog(const IShipyard *obj) void CPlayerInterface::showShipyardDialog(const IShipyard *obj)
@ -1502,9 +1499,9 @@ void CPlayerInterface::update()
return; return;
//if there are any waiting dialogs, show them //if there are any waiting dialogs, show them
if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->get()) if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->isBusy())
{ {
showingDialog->set(true); showingDialog->setBusy();
GH.windows().pushWindow(dialogs.front()); GH.windows().pushWindow(dialogs.front());
dialogs.pop_front(); dialogs.pop_front();
} }
@ -1516,6 +1513,11 @@ void CPlayerInterface::update()
GH.windows().simpleRedraw(); GH.windows().simpleRedraw();
} }
void CPlayerInterface::endNetwork()
{
showingDialog->requestTermination();
}
int CPlayerInterface::getLastIndex( std::string namePrefix) int CPlayerInterface::getLastIndex( std::string namePrefix)
{ {
using namespace boost::filesystem; using namespace boost::filesystem;

View File

@ -25,7 +25,7 @@ struct CGPath;
class CCreatureSet; class CCreatureSet;
class CGObjectInstance; class CGObjectInstance;
struct UpgradeInfo; struct UpgradeInfo;
template <typename T> struct CondSh; class ConditionalWait;
struct CPathsInfo; struct CPathsInfo;
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END
@ -74,7 +74,7 @@ public: // TODO: make private
std::unique_ptr<PlayerLocalState> localState; std::unique_ptr<PlayerLocalState> localState;
//minor interfaces //minor interfaces
CondSh<bool> *showingDialog; //indicates if dialog box is displayed ConditionalWait * showingDialog; //indicates if dialog box is displayed
bool makingTurn; //if player is already making his turn bool makingTurn; //if player is already making his turn
@ -202,6 +202,7 @@ public: // public interface for use by client via LOCPLINT access
void proposeLoadingGame(); void proposeLoadingGame();
void performAutosave(); void performAutosave();
void gamePause(bool pause); void gamePause(bool pause);
void endNetwork();
///returns true if all events are processed internally ///returns true if all events are processed internally
bool capturedAllEvents(); bool capturedAllEvents();

View File

@ -29,6 +29,7 @@
#include "../lib/CConfigHandler.h" #include "../lib/CConfigHandler.h"
#include "../lib/CGeneralTextHandler.h" #include "../lib/CGeneralTextHandler.h"
#include "ConditionalWait.h"
#include "../lib/CThreadHelper.h" #include "../lib/CThreadHelper.h"
#include "../lib/StartInfo.h" #include "../lib/StartInfo.h"
#include "../lib/TurnTimerInfo.h" #include "../lib/TurnTimerInfo.h"
@ -131,6 +132,17 @@ CServerHandler::~CServerHandler()
} }
} }
void CServerHandler::endNetwork()
{
if (client)
client->endNetwork();
networkHandler->stop();
{
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
threadNetwork.join();
}
}
CServerHandler::CServerHandler() CServerHandler::CServerHandler()
: networkHandler(INetworkHandler::createHandler()) : networkHandler(INetworkHandler::createHandler())
, lobbyClient(std::make_unique<GlobalLobbyClient>()) , lobbyClient(std::make_unique<GlobalLobbyClient>())
@ -158,7 +170,14 @@ void CServerHandler::threadRunNetwork()
{ {
logGlobal->info("Starting network thread"); logGlobal->info("Starting network thread");
setThreadName("runNetwork"); setThreadName("runNetwork");
networkHandler->run(); try {
networkHandler->run();
}
catch (const TerminationRequestedException & e)
{
logGlobal->info("Terminating network thread");
return;
}
logGlobal->info("Ending network thread"); logGlobal->info("Ending network thread");
} }

View File

@ -13,7 +13,6 @@
#include "../lib/network/NetworkInterface.h" #include "../lib/network/NetworkInterface.h"
#include "../lib/StartInfo.h" #include "../lib/StartInfo.h"
#include "../lib/CondSh.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -208,6 +207,7 @@ public:
void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr); void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr);
void showHighScoresAndEndGameplay(PlayerColor player, bool victory); void showHighScoresAndEndGameplay(PlayerColor player, bool victory);
void endNetwork();
void endGameplay(); void endGameplay();
void restartGameplay(); void restartGameplay();
void startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs = {}); void startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs = {});

View File

@ -346,6 +346,22 @@ void CClient::save(const std::string & fname)
sendRequest(&save_game, PlayerColor::NEUTRAL); sendRequest(&save_game, PlayerColor::NEUTRAL);
} }
void CClient::endNetwork()
{
if (CGI->mh)
CGI->mh->endNetwork();
if (CPlayerInterface::battleInt)
CPlayerInterface::battleInt->endNetwork();
for(auto & i : playerint)
{
auto interface = std::dynamic_pointer_cast<CPlayerInterface>(i.second);
if (interface)
interface->endNetwork();
}
}
void CClient::endGame() void CClient::endGame()
{ {
#if SCRIPTING_ENABLED #if SCRIPTING_ENABLED

View File

@ -135,6 +135,7 @@ public:
void serialize(BinaryDeserializer & h); void serialize(BinaryDeserializer & h);
void save(const std::string & fname); void save(const std::string & fname);
void endNetwork();
void endGame(); void endGame();
void initMapHandler(); void initMapHandler();

View File

@ -179,11 +179,6 @@ void ClientCommandManager::handleRedrawCommand()
GH.windows().totalRedraw(); GH.windows().totalRedraw();
} }
void ClientCommandManager::handleNotDialogCommand()
{
LOCPLINT->showingDialog->setn(false);
}
void ClientCommandManager::handleTranslateGameCommand() void ClientCommandManager::handleTranslateGameCommand()
{ {
std::map<std::string, std::map<std::string, std::string>> textsByMod; std::map<std::string, std::map<std::string, std::string>> textsByMod;
@ -584,9 +579,6 @@ void ClientCommandManager::processCommand(const std::string & message, bool call
else if(commandName == "redraw") else if(commandName == "redraw")
handleRedrawCommand(); handleRedrawCommand();
else if(commandName == "not dialog")
handleNotDialogCommand();
else if(message=="translate" || message=="translate game") else if(message=="translate" || message=="translate game")
handleTranslateGameCommand(); handleTranslateGameCommand();

View File

@ -45,9 +45,6 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a
// Redraw the current screen // Redraw the current screen
void handleRedrawCommand(); void handleRedrawCommand();
// Set the state indicating if dialog box is active to "no"
void handleNotDialogCommand();
// Extracts all translateable game texts into Translation directory, separating files on per-mod basis // Extracts all translateable game texts into Translation directory, separating files on per-mod basis
void handleTranslateGameCommand(); void handleTranslateGameCommand();

77
client/ConditionalWait.h Normal file
View File

@ -0,0 +1,77 @@
/*
* ConditionalWait.h, 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
*
*/
#pragma once
#include <condition_variable>
VCMI_LIB_NAMESPACE_BEGIN
class TerminationRequestedException : public std::exception
{
public:
using exception::exception;
const char* what() const noexcept override
{
return "Thread termination requested";
}
};
class ConditionalWait
{
bool isBusyValue = false;
bool isTerminating = false;
std::condition_variable cond;
std::mutex mx;
void set(bool value)
{
boost::unique_lock<std::mutex> lock(mx);
isBusyValue = value;
}
public:
ConditionalWait() = default;
void setBusy()
{
set(true);
}
void setFree()
{
set(false);
cond.notify_all();
}
void requestTermination()
{
isTerminating = true;
setFree();
}
bool isBusy()
{
std::unique_lock<std::mutex> lock(mx);
return isBusyValue;
}
void waitWhileBusy()
{
std::unique_lock<std::mutex> un(mx);
while(isBusyValue)
cond.wait(un);
if (isTerminating)
throw TerminationRequestedException();
}
};
VCMI_LIB_NAMESPACE_END

View File

@ -22,7 +22,7 @@
#include "../CCallback.h" #include "../CCallback.h"
#include "../lib/CondSh.h" #include "ConditionalWait.h"
#include "../lib/pathfinder/CGPathNode.h" #include "../lib/pathfinder/CGPathNode.h"
#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/networkPacks/PacksForClient.h" #include "../lib/networkPacks/PacksForClient.h"
@ -237,7 +237,7 @@ void HeroMovementController::onMoveHeroApplied()
assert(currentlyMovingHero); assert(currentlyMovingHero);
const auto * hero = currentlyMovingHero; const auto * hero = currentlyMovingHero;
bool canMove = LOCPLINT->localState->hasPath(hero) && LOCPLINT->localState->getPath(hero).nextNode().turns == 0 && !LOCPLINT->showingDialog->get(); bool canMove = LOCPLINT->localState->hasPath(hero) && LOCPLINT->localState->getPath(hero).nextNode().turns == 0 && !LOCPLINT->showingDialog->isBusy();
bool wantStop = stoppingMovement; bool wantStop = stoppingMovement;
bool canStop = !canMove || canHeroStopAtNode(LOCPLINT->localState->getPath(hero).currNode()); bool canStop = !canMove || canHeroStopAtNode(LOCPLINT->localState->getPath(hero).currNode());

View File

@ -38,7 +38,6 @@
#include "../../lib/CConfigHandler.h" #include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h" #include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h" #include "../../lib/CHeroHandler.h"
#include "../../lib/CondSh.h"
#include "../../lib/gameState/InfoAboutArmy.h" #include "../../lib/gameState/InfoAboutArmy.h"
#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/networkPacks/PacksForClientBattle.h" #include "../../lib/networkPacks/PacksForClientBattle.h"
@ -95,7 +94,7 @@ BattleInterface::BattleInterface(const BattleID & battleID, const CCreatureSet *
obstacleController.reset(new BattleObstacleController(*this)); obstacleController.reset(new BattleObstacleController(*this));
adventureInt->onAudioPaused(); adventureInt->onAudioPaused();
ongoingAnimationsState.set(true); ongoingAnimationsState.setBusy();
GH.windows().pushWindow(windowObject); GH.windows().pushWindow(windowObject);
windowObject->blockUI(true); windowObject->blockUI(true);
@ -744,6 +743,11 @@ void BattleInterface::castThisSpell(SpellID spellID)
actionsController->castThisSpell(spellID); actionsController->castThisSpell(spellID);
} }
void BattleInterface::endNetwork()
{
ongoingAnimationsState.requestTermination();
}
void BattleInterface::executeStagedAnimations() void BattleInterface::executeStagedAnimations()
{ {
EAnimationEvents earliestStage = EAnimationEvents::COUNT; EAnimationEvents earliestStage = EAnimationEvents::COUNT;
@ -775,19 +779,19 @@ void BattleInterface::executeAnimationStage(EAnimationEvents event)
void BattleInterface::onAnimationsStarted() void BattleInterface::onAnimationsStarted()
{ {
ongoingAnimationsState.setn(true); ongoingAnimationsState.setBusy();
} }
void BattleInterface::onAnimationsFinished() void BattleInterface::onAnimationsFinished()
{ {
ongoingAnimationsState.setn(false); ongoingAnimationsState.setFree();
} }
void BattleInterface::waitForAnimations() void BattleInterface::waitForAnimations()
{ {
{ {
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
ongoingAnimationsState.waitUntil(false); ongoingAnimationsState.waitWhileBusy();
} }
assert(!hasAnimations()); assert(!hasAnimations());
@ -802,7 +806,7 @@ void BattleInterface::waitForAnimations()
bool BattleInterface::hasAnimations() bool BattleInterface::hasAnimations()
{ {
return ongoingAnimationsState.get(); return ongoingAnimationsState.isBusy();
} }
void BattleInterface::checkForAnimations() void BattleInterface::checkForAnimations()

View File

@ -12,7 +12,7 @@
#include "BattleConstants.h" #include "BattleConstants.h"
#include "../gui/CIntObject.h" #include "../gui/CIntObject.h"
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation #include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
#include "../../lib/CondSh.h" #include "../ConditionalWait.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -99,7 +99,7 @@ class BattleInterface
}; };
/// Conditional variables that are set depending on ongoing animations on the battlefield /// Conditional variables that are set depending on ongoing animations on the battlefield
CondSh<bool> ongoingAnimationsState; ConditionalWait ongoingAnimationsState;
/// List of events that are waiting to be triggered /// List of events that are waiting to be triggered
std::vector<AwaitingAnimationEvents> awaitingEvents; std::vector<AwaitingAnimationEvents> awaitingEvents;
@ -186,6 +186,7 @@ public:
void setBattleQueueVisibility(bool visible); void setBattleQueueVisibility(bool visible);
void setStickyHeroWindowsVisibility(bool visible); void setStickyHeroWindowsVisibility(bool visible);
void endNetwork();
void executeStagedAnimations(); void executeStagedAnimations();
void executeAnimationStage( EAnimationEvents event); void executeAnimationStage( EAnimationEvents event);
void onAnimationsStarted(); void onAnimationsStarted();

View File

@ -52,7 +52,6 @@
#include "../../lib/CTownHandler.h" #include "../../lib/CTownHandler.h"
#include "../../lib/CHeroHandler.h" #include "../../lib/CHeroHandler.h"
#include "../../lib/StartInfo.h" #include "../../lib/StartInfo.h"
#include "../../lib/CondSh.h"
#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/networkPacks/PacksForClientBattle.h" #include "../../lib/networkPacks/PacksForClientBattle.h"
#include "../../lib/TextOperations.h" #include "../../lib/TextOperations.h"
@ -778,7 +777,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
void BattleResultWindow::activate() void BattleResultWindow::activate()
{ {
owner.showingDialog->set(true); owner.showingDialog->setBusy();
CIntObject::activate(); CIntObject::activate();
} }
@ -871,7 +870,7 @@ void BattleResultWindow::buttonPressed(int button)
//Result window and battle interface are gone. We requested all dialogs to be closed before opening the battle, //Result window and battle interface are gone. We requested all dialogs to be closed before opening the battle,
//so we can be sure that there is no dialogs left on GUI stack. //so we can be sure that there is no dialogs left on GUI stack.
intTmp.showingDialog->setn(false); intTmp.showingDialog->setFree();
CCS->videoh->close(); CCS->videoh->close();
} }

View File

@ -39,7 +39,6 @@
#include "../../lib/battle/BattleHex.h" #include "../../lib/battle/BattleHex.h"
#include "../../lib/CRandomGenerator.h" #include "../../lib/CRandomGenerator.h"
#include "../../lib/CStack.h" #include "../../lib/CStack.h"
#include "../../lib/CondSh.h"
#include "../../lib/TextOperations.h" #include "../../lib/TextOperations.h"
static void onAnimationFinished(const CStack *stack, std::weak_ptr<CreatureAnimation> anim) static void onAnimationFinished(const CStack *stack, std::weak_ptr<CreatureAnimation> anim)

View File

@ -9,7 +9,6 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "CGuiHandler.h" #include "CGuiHandler.h"
#include "../lib/CondSh.h"
#include "CIntObject.h" #include "CIntObject.h"
#include "CursorHandler.h" #include "CursorHandler.h"

View File

@ -10,7 +10,6 @@
#pragma once #pragma once
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
template <typename T> struct CondSh;
class Point; class Point;
class Rect; class Rect;
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -58,7 +58,6 @@
#include "../../lib/CConfigHandler.h" #include "../../lib/CConfigHandler.h"
#include "../../lib/GameConstants.h" #include "../../lib/GameConstants.h"
#include "../../lib/CRandomGenerator.h" #include "../../lib/CRandomGenerator.h"
#include "../../lib/CondSh.h"
std::shared_ptr<CMainMenu> CMM; std::shared_ptr<CMainMenu> CMM;
ISelectionScreenInfo * SEL; ISelectionScreenInfo * SEL;
@ -275,7 +274,8 @@ CMainMenuConfig::CMainMenuConfig()
: campaignSets(JsonPath::builtin("config/campaignSets.json")) : campaignSets(JsonPath::builtin("config/campaignSets.json"))
, config(JsonPath::builtin("config/mainmenu.json")) , config(JsonPath::builtin("config/mainmenu.json"))
{ {
if (config["game-select"].Vector().empty())
handleFatalError("Main menu config is invalid or corrupted. Please disable any mods or reinstall VCMI", false);
} }
const CMainMenuConfig & CMainMenuConfig::get() const CMainMenuConfig & CMainMenuConfig::get()

View File

@ -25,6 +25,8 @@ public:
virtual ~IMapObjectObserver(); virtual ~IMapObjectObserver();
virtual bool hasOngoingAnimations() = 0; virtual bool hasOngoingAnimations() = 0;
virtual void waitForOngoingAnimations(){};
virtual void endNetwork(){};
/// Plays fade-in animation and adds object to map /// Plays fade-in animation and adds object to map
virtual void onObjectFadeIn(const CGObjectInstance * obj, const PlayerColor & initiator) = 0; virtual void onObjectFadeIn(const CGObjectInstance * obj, const PlayerColor & initiator) = 0;

View File

@ -25,6 +25,7 @@
#include "../../lib/CConfigHandler.h" #include "../../lib/CConfigHandler.h"
#include "../../lib/StartInfo.h" #include "../../lib/StartInfo.h"
#include "../../lib/UnlockGuard.h"
#include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/MiscObjects.h" #include "../../lib/mapObjects/MiscObjects.h"
#include "../../lib/pathfinder/CGPathNode.h" #include "../../lib/pathfinder/CGPathNode.h"
@ -346,6 +347,7 @@ bool MapViewController::isEventVisible(const CGHeroInstance * obj, const int3 &
void MapViewController::fadeOutObject(const CGObjectInstance * obj) void MapViewController::fadeOutObject(const CGObjectInstance * obj)
{ {
animationWait.setBusy();
logGlobal->debug("Starting fade out animation"); logGlobal->debug("Starting fade out animation");
fadingOutContext = std::make_shared<MapRendererAdventureFadingContext>(*state); fadingOutContext = std::make_shared<MapRendererAdventureFadingContext>(*state);
fadingOutContext->animationTime = adventureContext->animationTime; fadingOutContext->animationTime = adventureContext->animationTime;
@ -366,6 +368,7 @@ void MapViewController::fadeOutObject(const CGObjectInstance * obj)
void MapViewController::fadeInObject(const CGObjectInstance * obj) void MapViewController::fadeInObject(const CGObjectInstance * obj)
{ {
animationWait.setBusy();
logGlobal->debug("Starting fade in animation"); logGlobal->debug("Starting fade in animation");
fadingInContext = std::make_shared<MapRendererAdventureFadingContext>(*state); fadingInContext = std::make_shared<MapRendererAdventureFadingContext>(*state);
fadingInContext->animationTime = adventureContext->animationTime; fadingInContext->animationTime = adventureContext->animationTime;
@ -505,6 +508,7 @@ void MapViewController::onAfterHeroTeleported(const CGHeroInstance * obj, const
if(isEventVisible(obj, from, dest)) if(isEventVisible(obj, from, dest))
{ {
animationWait.setBusy();
logGlobal->debug("Starting teleport animation"); logGlobal->debug("Starting teleport animation");
teleportContext = std::make_shared<MapRendererAdventureTransitionContext>(*state); teleportContext = std::make_shared<MapRendererAdventureTransitionContext>(*state);
teleportContext->animationTime = adventureContext->animationTime; teleportContext->animationTime = adventureContext->animationTime;
@ -540,6 +544,7 @@ void MapViewController::onHeroMoved(const CGHeroInstance * obj, const int3 & fro
if(movementTime > 1) if(movementTime > 1)
{ {
animationWait.setBusy();
logGlobal->debug("Starting movement animation"); logGlobal->debug("Starting movement animation");
movementContext = std::make_shared<MapRendererAdventureMovingContext>(*state); movementContext = std::make_shared<MapRendererAdventureMovingContext>(*state);
movementContext->animationTime = adventureContext->animationTime; movementContext->animationTime = adventureContext->animationTime;
@ -577,6 +582,17 @@ bool MapViewController::hasOngoingAnimations()
return false; return false;
} }
void MapViewController::waitForOngoingAnimations()
{
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
animationWait.waitWhileBusy();
}
void MapViewController::endNetwork()
{
animationWait.requestTermination();
}
void MapViewController::activateAdventureContext(uint32_t animationTime) void MapViewController::activateAdventureContext(uint32_t animationTime)
{ {
resetContext(); resetContext();
@ -642,6 +658,7 @@ void MapViewController::resetContext()
worldViewContext.reset(); worldViewContext.reset();
spellViewContext.reset(); spellViewContext.reset();
puzzleMapContext.reset(); puzzleMapContext.reset();
animationWait.setFree();
} }
void MapViewController::setTerrainVisibility(bool showAllTerrain) void MapViewController::setTerrainVisibility(bool showAllTerrain)

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#include "IMapRendererObserver.h" #include "IMapRendererObserver.h"
#include "../ConditionalWait.h"
#include "../../lib/Point.h" #include "../../lib/Point.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -34,6 +35,8 @@ class MapRendererPuzzleMapContext;
/// such as its position and any animations /// such as its position and any animations
class MapViewController : public IMapObjectObserver class MapViewController : public IMapObjectObserver
{ {
ConditionalWait animationWait;
std::shared_ptr<IMapRendererContext> context; std::shared_ptr<IMapRendererContext> context;
std::shared_ptr<MapRendererContextState> state; std::shared_ptr<MapRendererContextState> state;
std::shared_ptr<MapViewModel> model; std::shared_ptr<MapViewModel> model;
@ -68,6 +71,9 @@ private:
// IMapObjectObserver impl // IMapObjectObserver impl
bool hasOngoingAnimations() override; bool hasOngoingAnimations() override;
void waitForOngoingAnimations() override;
void endNetwork() override;
void onObjectFadeIn(const CGObjectInstance * obj, const PlayerColor & initiator) override; void onObjectFadeIn(const CGObjectInstance * obj, const PlayerColor & initiator) override;
void onObjectFadeOut(const CGObjectInstance * obj, const PlayerColor & initiator) override; void onObjectFadeOut(const CGObjectInstance * obj, const PlayerColor & initiator) override;
void onObjectInstantAdd(const CGObjectInstance * obj, const PlayerColor & initiator) override; void onObjectInstantAdd(const CGObjectInstance * obj, const PlayerColor & initiator) override;

View File

@ -19,7 +19,6 @@
#include "../../lib/CGeneralTextHandler.h" #include "../../lib/CGeneralTextHandler.h"
#include "../../lib/TerrainHandler.h" #include "../../lib/TerrainHandler.h"
#include "../../lib/UnlockGuard.h"
#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h" #include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/ObjectTemplate.h" #include "../../lib/mapObjects/ObjectTemplate.h"
@ -36,13 +35,19 @@ bool CMapHandler::hasOngoingAnimations()
void CMapHandler::waitForOngoingAnimations() void CMapHandler::waitForOngoingAnimations()
{ {
while(CGI->mh->hasOngoingAnimations()) for(auto * observer : observers)
{ {
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); if (observer->hasOngoingAnimations())
boost::this_thread::sleep_for(boost::chrono::milliseconds(1)); observer->waitForOngoingAnimations();
} }
} }
void CMapHandler::endNetwork()
{
for(auto * observer : observers)
observer->endNetwork();
}
std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) const std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) const
{ {
const TerrainTile & t = map->getTile(pos); const TerrainTile & t = map->getTile(pos);

View File

@ -71,6 +71,7 @@ public:
/// blocking wait until all ongoing animatins are over /// blocking wait until all ongoing animatins are over
void waitForOngoingAnimations(); void waitForOngoingAnimations();
void endNetwork();
static bool compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b); static bool compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b);
}; };

View File

@ -12,7 +12,7 @@
#include "../eventsSDL/InputHandler.h" #include "../eventsSDL/InputHandler.h"
#include "../../lib/CConfigHandler.h" #include "../../lib/CConfigHandler.h"
#include "../../lib/CondSh.h" #include "../ConditionalWait.h"
#include "../../lib/CGeneralTextHandler.h" #include "../../lib/CGeneralTextHandler.h"
#include "../CPlayerInterface.h" #include "../CPlayerInterface.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
@ -67,7 +67,7 @@ void CTutorialWindow::openWindowFirstTime(const TutorialMode & m)
if(GH.input().hasTouchInputDevice() && !persistentStorage["gui"]["tutorialCompleted" + std::to_string(m)].Bool()) if(GH.input().hasTouchInputDevice() && !persistentStorage["gui"]["tutorialCompleted" + std::to_string(m)].Bool())
{ {
if(LOCPLINT) if(LOCPLINT)
LOCPLINT->showingDialog->set(true); LOCPLINT->showingDialog->setBusy();
GH.windows().pushWindow(std::make_shared<CTutorialWindow>(m)); GH.windows().pushWindow(std::make_shared<CTutorialWindow>(m));
Settings s = persistentStorage.write["gui"]["tutorialCompleted" + std::to_string(m)]; Settings s = persistentStorage.write["gui"]["tutorialCompleted" + std::to_string(m)];
@ -78,7 +78,7 @@ void CTutorialWindow::openWindowFirstTime(const TutorialMode & m)
void CTutorialWindow::exit() void CTutorialWindow::exit()
{ {
if(LOCPLINT) if(LOCPLINT)
LOCPLINT->showingDialog->setn(false); LOCPLINT->showingDialog->setFree();
close(); close();
} }

View File

@ -56,7 +56,7 @@
#include "../lib/CGeneralTextHandler.h" #include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h" #include "../lib/CHeroHandler.h"
#include "../lib/GameSettings.h" #include "../lib/GameSettings.h"
#include "../lib/CondSh.h" #include "ConditionalWait.h"
#include "../lib/CSkillHandler.h" #include "../lib/CSkillHandler.h"
#include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/Filesystem.h"
#include "../lib/TextOperations.h" #include "../lib/TextOperations.h"
@ -402,7 +402,7 @@ CLevelWindow::CLevelWindow(const CGHeroInstance * hero, PrimarySkill pskill, std
{ {
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
LOCPLINT->showingDialog->setn(true); LOCPLINT->showingDialog->setBusy();
if(!skills.empty()) if(!skills.empty())
{ {
@ -445,7 +445,7 @@ CLevelWindow::~CLevelWindow()
if (box && box->selectedIndex() != -1) if (box && box->selectedIndex() != -1)
cb(box->selectedIndex()); cb(box->selectedIndex());
LOCPLINT->showingDialog->setn(false); LOCPLINT->showingDialog->setFree();
} }
CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::function<void()> & onWindowClosed) CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::function<void()> & onWindowClosed)

View File

@ -29,7 +29,7 @@
#include "../../CCallback.h" #include "../../CCallback.h"
#include "../../lib/CConfigHandler.h" #include "../../lib/CConfigHandler.h"
#include "../../lib/CondSh.h" #include "../ConditionalWait.h"
#include "../../lib/gameState/InfoAboutArmy.h" #include "../../lib/gameState/InfoAboutArmy.h"
#include "../../lib/mapObjects/CGCreature.h" #include "../../lib/mapObjects/CGCreature.h"
#include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGHeroInstance.h"
@ -140,7 +140,7 @@ void CInfoWindow::close()
WindowBase::close(); WindowBase::close();
if(LOCPLINT) if(LOCPLINT)
LOCPLINT->showingDialog->setn(false); LOCPLINT->showingDialog->setFree();
} }
void CInfoWindow::showAll(Canvas & to) void CInfoWindow::showAll(Canvas & to)
@ -158,7 +158,7 @@ void CInfoWindow::showInfoDialog(const std::string & text, const TCompsInfo & co
void CInfoWindow::showYesNoDialog(const std::string & text, const TCompsInfo & components, const CFunctionList<void()> & onYes, const CFunctionList<void()> & onNo, PlayerColor player) void CInfoWindow::showYesNoDialog(const std::string & text, const TCompsInfo & components, const CFunctionList<void()> & onYes, const CFunctionList<void()> & onNo, PlayerColor player)
{ {
assert(!LOCPLINT || LOCPLINT->showingDialog->get()); assert(!LOCPLINT || LOCPLINT->showingDialog->isBusy());
std::vector<std::pair<AnimationPath, CFunctionList<void()>>> pom; std::vector<std::pair<AnimationPath, CFunctionList<void()>>> pom;
pom.emplace_back(AnimationPath::builtin("IOKAY.DEF"), nullptr); pom.emplace_back(AnimationPath::builtin("IOKAY.DEF"), nullptr);
pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr); pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr);

View File

@ -142,5 +142,4 @@ Below a list of supported commands, with their arguments wrapped in `<>`
`activate <0/1/2>` - activate game windows (no current use, apparently broken long ago) `activate <0/1/2>` - activate game windows (no current use, apparently broken long ago)
`redraw` - force full graphical redraw `redraw` - force full graphical redraw
`screen` - show value of screenBuf variable, which prints "screen" when adventure map has current focus, "screen2" otherwise, and dumps values of both screen surfaces to .bmp files `screen` - show value of screenBuf variable, which prints "screen" when adventure map has current focus, "screen2" otherwise, and dumps values of both screen surfaces to .bmp files
`not dialog` - set the state indicating if dialog box is active to "no"
`tell hs <hero ID> <artifact slot ID>` - write what artifact is present on artifact slot with specified ID for hero with specified ID. (must be called during gameplay) `tell hs <hero ID> <artifact slot ID>` - write what artifact is present on artifact slot with specified ID for hero with specified ID. (must be called during gameplay)

View File

@ -648,7 +648,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
JsonNode advMapFile = node["graphics"]["map"]; JsonNode advMapFile = node["graphics"]["map"];
JsonNode advMapMask = node["graphics"]["mapMask"]; JsonNode advMapMask = node["graphics"]["mapMask"];
VLC->identifiers()->requestIdentifier(scope, "object", "monster", [=](si32 index) VLC->identifiers()->requestIdentifier(scope, "object", "monster", [cre, scope, advMapFile, advMapMask](si32 index)
{ {
JsonNode conf; JsonNode conf;
conf.setModScope(scope); conf.setModScope(scope);
@ -669,7 +669,12 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
// object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower) // object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower)
if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, cre->getId().num)->getTemplates().empty()) if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, cre->getId().num)->getTemplates().empty())
{
assert(cre->special);
if (!cre->special)
logMod->error("Creature %s does not have valid map object but is not marked as special!", cre->getJsonKey());
VLC->objtypeh->removeSubObject(Obj::MONSTER, cre->getId().num); VLC->objtypeh->removeSubObject(Obj::MONSTER, cre->getId().num);
}
}); });
return cre; return cre;

View File

@ -23,6 +23,8 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
std::recursive_mutex TextLocalizationContainer::globalTextMutex;
/// Detects language and encoding of H3 text files based on matching against pregenerated footprints of H3 file /// Detects language and encoding of H3 text files based on matching against pregenerated footprints of H3 file
void CGeneralTextHandler::detectInstallParameters() void CGeneralTextHandler::detectInstallParameters()
{ {
@ -251,6 +253,8 @@ bool CLegacyConfigParser::endLine()
void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized) void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized)
{ {
std::lock_guard<std::recursive_mutex> globalLock(globalTextMutex);
assert(!modContext.empty()); assert(!modContext.empty());
assert(!language.empty()); assert(!language.empty());
@ -265,12 +269,16 @@ void TextLocalizationContainer::registerStringOverride(const std::string & modCo
void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container) void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container)
{ {
std::lock_guard<std::recursive_mutex> globalLock(globalTextMutex);
assert(!vstd::contains(subContainers, &container)); assert(!vstd::contains(subContainers, &container));
subContainers.push_back(&container); subContainers.push_back(&container);
} }
void TextLocalizationContainer::removeSubContainer(const TextLocalizationContainer & container) void TextLocalizationContainer::removeSubContainer(const TextLocalizationContainer & container)
{ {
std::lock_guard<std::recursive_mutex> globalLock(globalTextMutex);
assert(vstd::contains(subContainers, &container)); assert(vstd::contains(subContainers, &container));
subContainers.erase(std::remove(subContainers.begin(), subContainers.end(), &container), subContainers.end()); subContainers.erase(std::remove(subContainers.begin(), subContainers.end(), &container), subContainers.end());
@ -278,6 +286,8 @@ void TextLocalizationContainer::removeSubContainer(const TextLocalizationContain
const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const
{ {
std::lock_guard<std::recursive_mutex> globalLock(globalTextMutex);
if(stringsLocalizations.count(identifier.get()) == 0) if(stringsLocalizations.count(identifier.get()) == 0)
{ {
for(auto containerIter = subContainers.rbegin(); containerIter != subContainers.rend(); ++containerIter) for(auto containerIter = subContainers.rbegin(); containerIter != subContainers.rend(); ++containerIter)
@ -297,6 +307,8 @@ const std::string & TextLocalizationContainer::deserialize(const TextIdentifier
void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language) void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language)
{ {
std::lock_guard<std::recursive_mutex> globalLock(globalTextMutex);
assert(!modContext.empty()); assert(!modContext.empty());
assert(!Languages::getLanguageOptions(language).identifier.empty()); assert(!Languages::getLanguageOptions(language).identifier.empty());
assert(UID.get().find("..") == std::string::npos); // invalid identifier - there is section that was evaluated to empty string assert(UID.get().find("..") == std::string::npos); // invalid identifier - there is section that was evaluated to empty string
@ -327,6 +339,8 @@ void TextLocalizationContainer::registerString(const std::string & modContext, c
bool TextLocalizationContainer::validateTranslation(const std::string & language, const std::string & modContext, const JsonNode & config) const bool TextLocalizationContainer::validateTranslation(const std::string & language, const std::string & modContext, const JsonNode & config) const
{ {
std::lock_guard<std::recursive_mutex> globalLock(globalTextMutex);
bool allPresent = true; bool allPresent = true;
for(const auto & string : stringsLocalizations) for(const auto & string : stringsLocalizations)
@ -384,11 +398,15 @@ void TextLocalizationContainer::loadTranslationOverrides(const std::string & lan
bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) const bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) const
{ {
std::lock_guard<std::recursive_mutex> globalLock(globalTextMutex);
return stringsLocalizations.count(UID.get()); return stringsLocalizations.count(UID.get());
} }
void TextLocalizationContainer::exportAllTexts(std::map<std::string, std::map<std::string, std::string>> & storage) const void TextLocalizationContainer::exportAllTexts(std::map<std::string, std::map<std::string, std::string>> & storage) const
{ {
std::lock_guard<std::recursive_mutex> globalLock(globalTextMutex);
for (auto const & subContainer : subContainers) for (auto const & subContainer : subContainers)
subContainer->exportAllTexts(storage); subContainer->exportAllTexts(storage);
@ -418,6 +436,8 @@ std::string TextLocalizationContainer::getModLanguage(const std::string & modCon
void TextLocalizationContainer::jsonSerialize(JsonNode & dest) const void TextLocalizationContainer::jsonSerialize(JsonNode & dest) const
{ {
std::lock_guard<std::recursive_mutex> globalLock(globalTextMutex);
for(auto & s : stringsLocalizations) for(auto & s : stringsLocalizations)
{ {
dest.Struct()[s.first].String() = s.second.baseValue; dest.Struct()[s.first].String() = s.second.baseValue;
@ -692,6 +712,7 @@ std::string CGeneralTextHandler::getInstalledEncoding()
std::vector<std::string> CGeneralTextHandler::findStringsWithPrefix(const std::string & prefix) std::vector<std::string> CGeneralTextHandler::findStringsWithPrefix(const std::string & prefix)
{ {
std::lock_guard<std::recursive_mutex> globalLock(globalTextMutex);
std::vector<std::string> result; std::vector<std::string> result;
for(const auto & entry : stringsLocalizations) for(const auto & entry : stringsLocalizations)

View File

@ -117,6 +117,8 @@ public:
class DLL_LINKAGE TextLocalizationContainer class DLL_LINKAGE TextLocalizationContainer
{ {
protected: protected:
static std::recursive_mutex globalTextMutex;
struct StringState struct StringState
{ {
/// Human-readable string that was added on registration /// Human-readable string that was added on registration
@ -153,6 +155,9 @@ protected:
std::string getModLanguage(const std::string & modContext); std::string getModLanguage(const std::string & modContext);
// returns true if identifier with such name was registered, even if not translated to current language
bool identifierExists(const TextIdentifier & UID) const;
public: public:
/// validates translation of specified language for specified mod /// validates translation of specified language for specified mod
/// returns true if localization is valid and complete /// returns true if localization is valid and complete
@ -163,9 +168,6 @@ public:
/// Any entries loaded by this will have priority over texts registered normally /// Any entries loaded by this will have priority over texts registered normally
void loadTranslationOverrides(const std::string & language, const std::string & modContext, JsonNode const & file); void loadTranslationOverrides(const std::string & language, const std::string & modContext, JsonNode const & file);
// returns true if identifier with such name was registered, even if not translated to current language
bool identifierExists(const TextIdentifier & UID) const;
/// add selected string to internal storage /// add selected string to internal storage
void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized); void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized);
void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language); void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language);
@ -196,6 +198,8 @@ public:
template <typename Handler> template <typename Handler>
void serialize(Handler & h) void serialize(Handler & h)
{ {
std::lock_guard<std::recursive_mutex> globalLock(globalTextMutex);
std::string key; std::string key;
auto sz = stringsLocalizations.size(); auto sz = stringsLocalizations.size();
h & sz; h & sz;

View File

@ -649,7 +649,6 @@ set(lib_MAIN_HEADERS
CGameInterface.h CGameInterface.h
CGeneralTextHandler.h CGeneralTextHandler.h
CHeroHandler.h CHeroHandler.h
CondSh.h
ConstTransitivePtr.h ConstTransitivePtr.h
Color.h Color.h
CPlayerState.h CPlayerState.h

View File

@ -1,71 +0,0 @@
/*
* CondSh.h, 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
*
*/
#pragma once
VCMI_LIB_NAMESPACE_BEGIN
/// Used for multithreading, wraps boost functions
template <typename T> struct CondSh
{
T data;
boost::condition_variable cond;
boost::mutex mx;
CondSh() : data(T()) {}
CondSh(T t) : data(t) {}
// set data
void set(T t)
{
boost::unique_lock<boost::mutex> lock(mx);
data = t;
}
// set data and notify
void setn(T t)
{
set(t);
cond.notify_all();
};
// get stored value
T get()
{
boost::unique_lock<boost::mutex> lock(mx);
return data;
}
// waits until data is set to false
void waitWhileTrue()
{
boost::unique_lock<boost::mutex> un(mx);
while(data)
cond.wait(un);
}
// waits while data is set to arg
void waitWhile(const T & t)
{
boost::unique_lock<boost::mutex> un(mx);
while(data == t)
cond.wait(un);
}
// waits until data is set to arg
void waitUntil(const T & t)
{
boost::unique_lock<boost::mutex> un(mx);
while(data != t)
cond.wait(un);
}
};
VCMI_LIB_NAMESPACE_END

View File

@ -117,7 +117,9 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u
if (readBuffer.size() < expectedPacketSize) if (readBuffer.size() < expectedPacketSize)
{ {
throw std::runtime_error("Failed to read packet! " + std::to_string(readBuffer.size()) + " bytes read, but " + std::to_string(expectedPacketSize) + " bytes expected!"); // FIXME: figure out what causes this. This should not be possible without error set
std::string errorMessage = "Failed to read packet! " + std::to_string(readBuffer.size()) + " bytes read, but " + std::to_string(expectedPacketSize) + " bytes expected!";
onError(errorMessage);
} }
std::vector<std::byte> message(expectedPacketSize); std::vector<std::byte> message(expectedPacketSize);

View File

@ -986,7 +986,7 @@ void CVCMIServer::multiplayerWelcomeMessage()
{ {
int humanPlayer = 0; int humanPlayer = 0;
for (auto & pi : si->playerInfos) for (auto & pi : si->playerInfos)
if(gh->getPlayerState(pi.first)->isHuman()) if(pi.second.isControlledByHuman())
humanPlayer++; humanPlayer++;
if(humanPlayer < 2) // Singleplayer if(humanPlayer < 2) // Singleplayer