瀏覽代碼

Merge pull request #5363 from IvanSavenko/crashfixes

[1.6.5] Crashfixes for reported crashes from 1.6.4
Ivan Savenko 8 月之前
父節點
當前提交
29d0078a89

+ 11 - 3
client/CPlayerInterface.cpp

@@ -62,6 +62,7 @@
 #include "windows/CTutorialWindow.h"
 #include "windows/GUIClasses.h"
 #include "windows/InfoWindows.h"
+#include "windows/settings/SettingsMainWindow.h"
 
 #include "../CCallback.h"
 
@@ -187,6 +188,7 @@ void CPlayerInterface::closeAllDialogs()
 	while(true)
 	{
 		auto adventureWindow = GH.windows().topWindow<AdventureMapInterface>();
+		auto settingsWindow = GH.windows().topWindow<SettingsMainWindow>();
 		auto infoWindow = GH.windows().topWindow<CInfoWindow>();
 		auto topWindow = GH.windows().topWindow<WindowBase>();
 
@@ -196,10 +198,16 @@ void CPlayerInterface::closeAllDialogs()
 		if(infoWindow && infoWindow->ID != QueryID::NONE)
 			break;
 
-		if (topWindow == nullptr)
-			throw std::runtime_error("Invalid or non-existing top window! Total windows: " + std::to_string(GH.windows().count()));
+		if (settingsWindow)
+		{
+			settingsWindow->close();
+			continue;
+		}
 
-		topWindow->close();
+		if (topWindow)
+			topWindow->close();
+		else
+			GH.windows().popWindows(1); // does not inherits from WindowBase, e.g. settings dialog
 	}
 }
 

+ 12 - 6
client/battle/BattleFieldController.cpp

@@ -683,18 +683,24 @@ BattleHex::EDir BattleFieldController::selectAttackDirection(const BattleHex & m
 		// |    - -   |   - -    |    - -   |   - o o  |  o o -   |   - -    |    - -   |   o o
 
 		for (size_t i : { 1, 2, 3})
-			attackAvailability[i] = occupiableHexes.contains(neighbours[i]) && occupiableHexes.contains(neighbours[i].cloneInDirection(BattleHex::RIGHT, false));
+		{
+			BattleHex target = neighbours[i].cloneInDirection(BattleHex::RIGHT, false);
+			attackAvailability[i] = neighbours[i].isValid() && occupiableHexes.contains(neighbours[i]) && target.isValid() && occupiableHexes.contains(target);
+		}
 
 		for (size_t i : { 4, 5, 0})
-			attackAvailability[i] = occupiableHexes.contains(neighbours[i]) && occupiableHexes.contains(neighbours[i].cloneInDirection(BattleHex::LEFT, false));
+		{
+			BattleHex target = neighbours[i].cloneInDirection(BattleHex::LEFT, false);
+			attackAvailability[i] = neighbours[i].isValid() && occupiableHexes.contains(neighbours[i]) && target.isValid() && occupiableHexes.contains(target);
+		}
 
-		attackAvailability[6] = occupiableHexes.contains(neighbours[0]) && occupiableHexes.contains(neighbours[1]);
-		attackAvailability[7] = occupiableHexes.contains(neighbours[3]) && occupiableHexes.contains(neighbours[4]);
+		attackAvailability[6] = neighbours[0].isValid() && neighbours[1].isValid() && occupiableHexes.contains(neighbours[0]) && occupiableHexes.contains(neighbours[1]);
+		attackAvailability[7] = neighbours[3].isValid() && neighbours[4].isValid() && occupiableHexes.contains(neighbours[3]) && occupiableHexes.contains(neighbours[4]);
 	}
 	else
 	{
 		for (size_t i = 0; i < 6; ++i)
-			attackAvailability[i] = occupiableHexes.contains(neighbours[i]);
+			attackAvailability[i] = neighbours[i].isValid() && occupiableHexes.contains(neighbours[i]);
 
 		attackAvailability[6] = false;
 		attackAvailability[7] = false;
@@ -739,7 +745,7 @@ BattleHex::EDir BattleFieldController::selectAttackDirection(const BattleHex & m
 
 BattleHex BattleFieldController::fromWhichHexAttack(const BattleHex & attackTarget)
 {
-	BattleHex::EDir direction = selectAttackDirection(getHoveredHex());
+	BattleHex::EDir direction = selectAttackDirection(attackTarget);
 
 	const CStack * attacker = owner.stacksController->getActiveStack();
 

+ 6 - 1
client/battle/BattleInterfaceClasses.cpp

@@ -686,7 +686,12 @@ void StackInfoBasicPanel::initializeData(const CStack * stack)
 		if (hasGraphics)
 		{
 			//FIXME: support permanent duration
-			int duration = stack->getFirstBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect)))->turnsRemain;
+			auto spellBonuses = stack->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect)));
+
+			if (spellBonuses->empty())
+				throw std::runtime_error("Failed to find effects for spell " + effect.toSpell()->getJsonKey());
+
+			int duration = spellBonuses->front()->duration;
 
 			icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));
 			if(settings["general"]["enableUiEnhancements"].Bool())

+ 2 - 0
client/globalLobby/GlobalLobbyLoginWindow.cpp

@@ -116,6 +116,7 @@ void GlobalLobbyLoginWindow::onLogin()
 		onConnectionSuccess();
 
 	buttonClose->block(true);
+	buttonLogin->block(true);
 }
 
 void GlobalLobbyLoginWindow::onConnectionSuccess()
@@ -142,4 +143,5 @@ void GlobalLobbyLoginWindow::onConnectionFailed(const std::string & reason)
 
 	labelStatus->setText(formatter.toString());
 	buttonClose->block(false);
+	buttonLogin->block(false);
 }

+ 1 - 0
client/gui/CGuiHandler.cpp

@@ -134,6 +134,7 @@ CGuiHandler::~CGuiHandler()
 	// enforce deletion order on shutdown
 	// all UI elements including adventure map must be destroyed before Gui Handler
 	// proper solution would be removal of adventureInt global
+	windowHandlerInstance->clear();
 	adventureInt.reset();
 }
 

+ 1 - 1
client/gui/CIntObject.cpp

@@ -345,7 +345,7 @@ void WindowBase::close()
 	if(!GH.windows().isTopWindow(this))
 	{
 		auto topWindow = GH.windows().topWindow<IShowActivatable>().get();
-		throw std::runtime_error(std::string("Only top interface can be closed! Top window is ") + typeid(*this).name() + " but attempted to close " + typeid(*topWindow).name());
+		throw std::runtime_error(std::string("Only top interface can be closed! Top window is ") + typeid(*topWindow).name() + " but attempted to close " + typeid(*this).name());
 	}
 	GH.windows().popWindows(1);
 }

+ 3 - 0
client/lobby/OptionsTab.cpp

@@ -68,6 +68,9 @@ void OptionsTab::recreate()
 	entries.clear();
 	humanPlayers = 0;
 
+	for (auto heroOverview : GH.windows().findWindows<CHeroOverview>())
+		heroOverview->close();
+
 	for (auto selectionWindow : GH.windows().findWindows<SelectionWindow>())
 	{
 		selectionWindow->reopen();

+ 6 - 0
client/renderSDL/SDLImageScaler.cpp

@@ -120,6 +120,9 @@ const Rect & SDLImageOptimizer::getResultDimensions() const
 
 void SDLImageScaler::scaleSurface(Point targetDimensions, EScalingAlgorithm algorithm)
 {
+	if (!intermediate)
+		return; // may happen on scaling of empty images
+
 	if(!targetDimensions.x || !targetDimensions.y)
 		throw std::runtime_error("invalid scaling dimensions!");
 
@@ -144,6 +147,9 @@ void SDLImageScaler::scaleSurface(Point targetDimensions, EScalingAlgorithm algo
 
 void SDLImageScaler::scaleSurfaceIntegerFactor(int factor, EScalingAlgorithm algorithm)
 {
+	if (!intermediate)
+		return; // may happen on scaling of empty images
+
 	if(factor == 0)
 		throw std::runtime_error("invalid scaling factor!");
 

+ 5 - 1
client/windows/CCreatureWindow.cpp

@@ -234,7 +234,11 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int
 			spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds."
 			boost::replace_first(spellText, "%s", spell->getNameTranslated());
 			//FIXME: support permanent duration
-			int duration = battleStack->getFirstBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect)))->turnsRemain;
+			auto spellBonuses = battleStack->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect)));
+			if (spellBonuses->empty())
+				throw std::runtime_error("Failed to find effects for spell " + effect.toSpell()->getJsonKey());
+
+			int duration = spellBonuses->front()->duration;
 			boost::replace_first(spellText, "%d", std::to_string(duration));
 
 			spellIcons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));

+ 1 - 1
client/windows/settings/SettingsMainWindow.h

@@ -29,7 +29,6 @@ private:
 	std::shared_ptr<CIntObject> createTab(size_t index);
 	void openTab(size_t index);
 
-	void close(); //TODO: copypaste of WindowBase::close(), consider changing Windowbase to IWindowbase with default close() implementation and changing WindowBase inheritance to CIntObject + IWindowBase
 
 	void loadGameButtonCallback();
 	void saveGameButtonCallback();
@@ -40,6 +39,7 @@ private:
 public:
 	SettingsMainWindow(BattleInterface * parentBattleInterface = nullptr);
 
+	void close(); //TODO: copypaste of WindowBase::close(), consider changing Windowbase to IWindowbase with default close() implementation and changing WindowBase inheritance to CIntObject + IWindowBase
 	void showAll(Canvas & to) override;
 	void onScreenResize() override;
 };

+ 14 - 2
lib/texts/TextOperations.cpp

@@ -161,12 +161,24 @@ uint32_t TextOperations::getUnicodeCodepoint(char data, const std::string & enco
 
 std::string TextOperations::toUnicode(const std::string &text, const std::string &encoding)
 {
-	return boost::locale::conv::to_utf<char>(text, encoding);
+	try {
+		return boost::locale::conv::to_utf<char>(text, encoding);
+	}
+	catch (const boost::locale::conv::conversion_error &)
+	{
+		throw std::runtime_error("Failed to convert text '" + text + "' from encoding " + encoding );
+	}
 }
 
 std::string TextOperations::fromUnicode(const std::string &text, const std::string &encoding)
 {
-	return boost::locale::conv::from_utf<char>(text, encoding);
+	try {
+		return boost::locale::conv::from_utf<char>(text, encoding);
+	}
+	catch (const boost::locale::conv::conversion_error &)
+	{
+		throw std::runtime_error("Failed to convert text '" + text + "' to encoding " + encoding );
+	}
 }
 
 void TextOperations::trimRightUnicode(std::string & text, const size_t amount)