瀏覽代碼

Merge branch 'master' into 'develop'

Ivan Savenko 1 年之前
父節點
當前提交
2a193effcc

+ 8 - 10
AI/StupidAI/StupidAI.cpp

@@ -16,8 +16,6 @@
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleInfo.h"
 
-static std::shared_ptr<CBattleCallback> cbc;
-
 CStupidAI::CStupidAI()
 	: side(-1)
 	, wasWaitingForRealize(false)
@@ -41,7 +39,7 @@ void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
 {
 	print("init called, saving ptr to IBattleCallback");
 	env = ENV;
-	cbc = cb = CB;
+	cb = CB;
 
 	wasWaitingForRealize = CB->waitTillRealize;
 	wasUnlockingGs = CB->unlockGsWhenWaiting;
@@ -73,11 +71,11 @@ public:
 	std::vector<BattleHex> attackFrom; //for melee fight
 	EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0)
 	{}
-	void calcDmg(const BattleID & battleID, const CStack * ourStack)
+	void calcDmg(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const CStack * ourStack)
 	{
 		// FIXME: provide distance info for Jousting bonus
 		DamageEstimation retal;
-		DamageEstimation dmg = cbc->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
+		DamageEstimation dmg = cb->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
 		adi = static_cast<int>((dmg.damage.min + dmg.damage.max) / 2);
 		adr = static_cast<int>((retal.damage.min + retal.damage.max) / 2);
 	}
@@ -93,14 +91,14 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2)
 	return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr);
 }
 
-static bool willSecondHexBlockMoreEnemyShooters(const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
+static bool willSecondHexBlockMoreEnemyShooters(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
 {
 	int shooters[2] = {0}; //count of shooters on hexes
 
 	for(int i = 0; i < 2; i++)
 	{
 		for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
-			if(const auto * s = cbc->getBattle(battleID)->battleGetUnitByPos(neighbour))
+			if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour))
 				if(s->isShooter())
 					shooters[i]++;
 	}
@@ -172,10 +170,10 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 	}
 
 	for ( auto & enemy : enemiesReachable )
-		enemy.calcDmg(battleID, stack);
+		enemy.calcDmg(cb, battleID, stack);
 
 	for ( auto & enemy : enemiesShootable )
-		enemy.calcDmg(battleID, stack);
+		enemy.calcDmg(cb, battleID, stack);
 
 	if(enemiesShootable.size())
 	{
@@ -186,7 +184,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 	else if(enemiesReachable.size())
 	{
 		const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
-		BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(battleID, a, b);});
+		BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(cb, battleID, a, b);});
 
 		cb->battleMakeUnitAction(battleID, BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), targetHex));
 		return;

+ 26 - 1
ChangeLog.md

@@ -1,4 +1,4 @@
-# 1.4.3 -> 1.5.0
+# 1.4.5 -> 1.5.0
 
 ### General
 * Added Chinese translation to map editor
@@ -10,6 +10,31 @@
 * Added status bar to the backpack window
 * Quick backpack window is now only available when enabled Interface enhancements
 
+# 1.4.4 -> 1.4.5
+
+### Stability
+* Fixed crash on creature spellcasting
+* Fixed crash on unit entering magical obstacles such as quicksands
+* Fixed freeze on map loading on some systems
+* Fixed crash on attempt to start campaign with unsupported map
+* Fixed crash on opening creature information window with invalid SPELL_IMMUNITY bonus
+
+### Random Maps Generator
+* Fixed placement of guards sometimes resulting into open connection into third zone
+* Fixed rare crash on multithreaded access during placement of artifacts or wandering monsters
+
+### Map Editor
+* Fixed inspector using wrong editor for some values
+
+### AI
+* Fixed bug leading to AI not attacking wandering monsters in some cases
+* Fixed crash on using StupidAI for autocombat or for enemy players 
+
+# 1.4.3 -> 1.4.4
+
+### General
+* Fixed crash on generation of random maps
+
 # 1.4.2 -> 1.4.3
 
 ### General

+ 1 - 0
Mods/vcmi/config/vcmi/english.json

@@ -72,6 +72,7 @@
 	"vcmi.lobby.noUnderground" : "no underground",
 	"vcmi.lobby.sortDate" : "Sorts maps by change date",
 
+	"vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s",
 	"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
 	"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
 	"vcmi.server.errors.modsToEnable"    : "{Following mods are required}",

+ 5 - 5
client/battle/BattleActionsController.cpp

@@ -750,7 +750,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
 
 			if (!spellcastingModeActive())
 			{
-				if (action.spell().toSpell())
+				if (action.spell().hasValue())
 				{
 					owner.giveCommand(EActionType::MONSTER_SPELL, targetHex, action.spell());
 				}
@@ -887,17 +887,17 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
 	{
 		// faerie dragon can cast only one, randomly selected spell until their next move
 		//TODO: faerie dragon type spell should be selected by server
-		const auto * spellToCast = owner.getBattle()->getRandomCastedSpell(CRandomGenerator::getDefault(), casterStack).toSpell();
+		const auto spellToCast = owner.getBattle()->getRandomCastedSpell(CRandomGenerator::getDefault(), casterStack);
 
-		if (spellToCast)
-			creatureSpells.push_back(spellToCast);
+		if (spellToCast.hasValue())
+			creatureSpells.push_back(spellToCast.toSpell());
 	}
 
 	TConstBonusListPtr bl = casterStack->getBonuses(Selector::type()(BonusType::SPELLCASTER));
 
 	for(const auto & bonus : *bl)
 	{
-		if (bonus->additionalInfo[0] <= 0)
+		if (bonus->additionalInfo[0] <= 0 && bonus->subtype.as<SpellID>().hasValue())
 			creatureSpells.push_back(bonus->subtype.as<SpellID>().toSpell());
 	}
 }

+ 4 - 4
client/battle/BattleInterface.cpp

@@ -352,13 +352,13 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 	CCS->curh->set(Cursor::Combat::BLOCKED);
 
 	const SpellID spellID = sc->spellID;
-	const CSpell * spell = spellID.toSpell();
-	auto targetedTile = sc->tile;
 
-	assert(spell);
-	if(!spell)
+	if(!spellID.hasValue())
 		return;
 
+	const CSpell * spell = spellID.toSpell();
+	auto targetedTile = sc->tile;
+
 	const AudioPath & castSoundPath = spell->getCastSound();
 
 	if (!castSoundPath.empty())

+ 14 - 29
client/eventsSDL/InputSourceText.cpp

@@ -21,10 +21,6 @@
 
 #include <SDL_events.h>
 
-#ifdef VCMI_APPLE
-#	include <dispatch/dispatch.h>
-#endif
-
 void InputSourceText::handleEventTextInput(const SDL_TextInputEvent & text)
 {
 	GH.events().dispatchTextInput(text.text);
@@ -37,38 +33,27 @@ void InputSourceText::handleEventTextEditing(const SDL_TextEditingEvent & text)
 
 void InputSourceText::startTextInput(const Rect & whereInput)
 {
-
-#ifdef VCMI_APPLE
-	dispatch_async(dispatch_get_main_queue(), ^{
-#endif
-
-	Rect rectInScreenCoordinates = GH.screenHandler().convertLogicalPointsToWindow(whereInput);
-	SDL_Rect textInputRect = CSDL_Ext::toSDL(rectInScreenCoordinates);
-
-	SDL_SetTextInputRect(&textInputRect);
-
-	if (SDL_IsTextInputActive() == SDL_FALSE)
+	GH.dispatchMainThread([whereInput]()
 	{
-		SDL_StartTextInput();
-	}
+		Rect rectInScreenCoordinates = GH.screenHandler().convertLogicalPointsToWindow(whereInput);
+		SDL_Rect textInputRect = CSDL_Ext::toSDL(rectInScreenCoordinates);
+
+		SDL_SetTextInputRect(&textInputRect);
 
-#ifdef VCMI_APPLE
+		if (SDL_IsTextInputActive() == SDL_FALSE)
+		{
+			SDL_StartTextInput();
+		}
 	});
-#endif
 }
 
 void InputSourceText::stopTextInput()
 {
-#ifdef VCMI_APPLE
-	dispatch_async(dispatch_get_main_queue(), ^{
-#endif
-
-	if (SDL_IsTextInputActive() == SDL_TRUE)
+	GH.dispatchMainThread([]()
 	{
-		SDL_StopTextInput();
-	}
-
-#ifdef VCMI_APPLE
+		if (SDL_IsTextInputActive() == SDL_TRUE)
+		{
+			SDL_StopTextInput();
+		}
 	});
-#endif
 }

+ 14 - 2
client/lobby/CLobbyScreen.cpp

@@ -135,11 +135,23 @@ void CLobbyScreen::toggleTab(std::shared_ptr<CIntObject> tab)
 
 void CLobbyScreen::startCampaign()
 {
-	if(CSH->mi)
-	{
+	if(!CSH->mi)
+		return;
+
+	try {
 		auto ourCampaign = CampaignHandler::getCampaign(CSH->mi->fileURI);
 		CSH->setCampaignState(ourCampaign);
 	}
+	catch (const std::runtime_error &e)
+	{
+		// handle possible exception on map loading. For example campaign that contains map in unsupported format
+		// for example, wog campaigns or hota campaigns without hota map support mod
+		MetaString message;
+		message.appendTextID("vcmi.client.errors.invalidMap");
+		message.replaceRawString(e.what());
+
+		CInfoWindow::showInfoDialog(message.toString(), {});
+	}
 }
 
 void CLobbyScreen::startScenario(bool allowOnlyAI)

+ 2 - 0
client/renderSDL/SDLImage.cpp

@@ -205,6 +205,8 @@ void SDLImage::exportBitmap(const boost::filesystem::path& path) const
 
 void SDLImage::playerColored(PlayerColor player)
 {
+	if (!surf)
+		return;
 	graphics->blueToPlayersAdv(surf, player);
 }
 

+ 10 - 23
client/widgets/TextControls.cpp

@@ -553,8 +553,7 @@ Point CGStatusBar::getBorderSize()
 
 CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList<void(const std::string &)> & CB, bool giveFocusToInput)
 	: CLabel(Pos.x, Pos.y, font, ETextAlignment::CENTER),
-	cb(CB),
-	CFocusable(std::make_shared<CKeyboardFocusListener>(this))
+	cb(CB)
 {
 	setRedrawParent(true);
 	pos.h = Pos.h;
@@ -570,7 +569,7 @@ CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList<void(c
 }
 
 CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath & bgName, const CFunctionList<void(const std::string &)> & CB)
-	:cb(CB), 	CFocusable(std::make_shared<CKeyboardFocusListener>(this))
+	:cb(CB)
 {
 	pos += Pos.topLeft();
 	pos.h = Pos.h;
@@ -587,7 +586,6 @@ CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath
 }
 
 CTextInput::CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf)
-	:CFocusable(std::make_shared<CKeyboardFocusListener>(this))
 {
 	pos += Pos.topLeft();
 	OBJ_CONSTRUCTION;
@@ -603,20 +601,15 @@ CTextInput::CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf)
 #endif
 }
 
-std::atomic<int> CKeyboardFocusListener::usageIndex(0);
+std::atomic<int> CFocusable::usageIndex(0);
 
-CKeyboardFocusListener::CKeyboardFocusListener(CTextInput * textInput)
-	:textInput(textInput)
+void CFocusable::focusGot()
 {
-}
-
-void CKeyboardFocusListener::focusGot()
-{
-	GH.startTextInput(textInput->pos);
+	GH.startTextInput(pos);
 	usageIndex++;
 }
 
-void CKeyboardFocusListener::focusLost()
+void CFocusable::focusLost()
 {
 	if(0 == --usageIndex)
 	{
@@ -769,12 +762,6 @@ void CTextInput::numberFilter(std::string & text, const std::string & oldText, i
 }
 
 CFocusable::CFocusable()
-	:CFocusable(std::make_shared<IFocusListener>())
-{
-}
-
-CFocusable::CFocusable(std::shared_ptr<IFocusListener> focusListener)
-	: focusListener(focusListener)
 {
 	focus = false;
 	focusables.push_back(this);
@@ -785,7 +772,7 @@ CFocusable::~CFocusable()
 	if(hasFocus())
 	{
 		inputWithFocus = nullptr;
-		focusListener->focusLost();
+		focusLost();
 	}
 
 	focusables -= this;
@@ -799,13 +786,13 @@ bool CFocusable::hasFocus() const
 void CFocusable::giveFocus()
 {
 	focus = true;
-	focusListener->focusGot();
+	focusGot();
 	redraw();
 
 	if(inputWithFocus)
 	{
 		inputWithFocus->focus = false;
-		inputWithFocus->focusListener->focusLost();
+		inputWithFocus->focusLost();
 		inputWithFocus->redraw();
 	}
 
@@ -837,7 +824,7 @@ void CFocusable::removeFocus()
 	if(this == inputWithFocus)
 	{
 		focus = false;
-		focusListener->focusLost();
+		focusLost();
 		redraw();
 
 		inputWithFocus = nullptr;

+ 6 - 30
client/widgets/TextControls.h

@@ -163,25 +163,12 @@ public:
 	void clear() override;
 	void setEnteringMode(bool on) override;
 	void setEnteredText(const std::string & text) override;
-
-};
-
-class CFocusable;
-
-class IFocusListener
-{
-public:
-	virtual void focusGot() {};
-	virtual void focusLost() {};
-	virtual ~IFocusListener() = default;
 };
 
 /// UIElement which can get input focus
 class CFocusable : public virtual CIntObject
 {
-private:
-	std::shared_ptr<IFocusListener> focusListener;
-
+	static std::atomic<int> usageIndex;
 public:
 	bool focus; //only one focusable control can have focus at one moment
 
@@ -190,38 +177,27 @@ public:
 	void removeFocus(); //remove focus
 	bool hasFocus() const;
 
+	void focusGot();
+	void focusLost();
+
 	static std::list<CFocusable *> focusables; //all existing objs
 	static CFocusable * inputWithFocus; //who has focus now
 
 	CFocusable();
-	CFocusable(std::shared_ptr<IFocusListener> focusListener);
 	~CFocusable();
 };
 
-class CTextInput;
-class CKeyboardFocusListener : public IFocusListener
-{
-private:
-	static std::atomic<int> usageIndex;
-	CTextInput * textInput;
-
-public:
-	CKeyboardFocusListener(CTextInput * textInput);
-	void focusGot() override;
-	void focusLost() override;
-};
-
 /// Text input box where players can enter text
 class CTextInput : public CLabel, public CFocusable
 {
 	std::string newText;
 	std::string helpBox; //for right-click help
-	
+
 protected:
 	std::string visibleText() override;
 
 public:
-	
+
 	CFunctionList<void(const std::string &)> cb;
 	CFunctionList<void(std::string &, const std::string &)> filters;
 	void setText(const std::string & nText) override;

+ 6 - 0
debian/changelog

@@ -4,6 +4,12 @@ vcmi (1.5.0) jammy; urgency=medium
 
  -- Ivan Savenko <[email protected]>  Fri, 1 Mar 2024 12:00:00 +0200
 
+vcmi (1.4.5) jammy; urgency=medium
+
+  * New upstream release
+
+  -- Ivan Savenko <[email protected]>  Tue, 23 Jan 2024 12:00:00 +0200
+
 vcmi (1.4.4) jammy; urgency=medium
 
   * New upstream release

+ 1 - 0
launcher/eu.vcmi.VCMI.metainfo.xml

@@ -77,6 +77,7 @@
 	</screenshots>
 	<releases>
 		<release version="1.5.0" date="2024-03-01" type="development"/>
+		<release version="1.4.5" date="2024-01-23" type="stable"/>
 		<release version="1.4.4" date="2024-01-20" type="stable"/>
 		<release version="1.4.3" date="2024-01-19" type="stable"/>
 		<release version="1.4.2" date="2023-12-25" type="stable"/>

+ 3 - 0
launcher/modManager/cmodlist.cpp

@@ -38,6 +38,9 @@ bool CModEntry::isEnabled() const
 	if(!isInstalled())
 		return false;
 
+	if (!isVisible())
+		return false;
+
 	return modSettings["active"].toBool();
 }
 

+ 1 - 1
launcher/modManager/cmodlistview_moc.cpp

@@ -521,7 +521,7 @@ QStringList CModListView::findDependentMods(QString mod, bool excludeDisabled)
 	{
 		auto current = modModel->getMod(modName);
 
-		if(!current.isInstalled())
+		if(!current.isInstalled() || !current.isVisible())
 			continue;
 
 		if(current.getDependencies().contains(mod, Qt::CaseInsensitive))

+ 7 - 4
lib/CBonusTypeHandler.cpp

@@ -76,10 +76,10 @@ std::string CBonusTypeHandler::bonusToString(const std::shared_ptr<Bonus> & bonu
 	if (text.find("${val}") != std::string::npos)
 		boost::algorithm::replace_all(text, "${val}", std::to_string(bearer->valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype))));
 
-	if (text.find("${subtype.creature}") != std::string::npos && bonus->subtype.as<CreatureID>() != CreatureID::NONE)
+	if (text.find("${subtype.creature}") != std::string::npos && bonus->subtype.as<CreatureID>().hasValue())
 		boost::algorithm::replace_all(text, "${subtype.creature}", bonus->subtype.as<CreatureID>().toCreature()->getNamePluralTranslated());
 
-	if (text.find("${subtype.spell}") != std::string::npos && bonus->subtype.as<SpellID>() != SpellID::NONE)
+	if (text.find("${subtype.spell}") != std::string::npos && bonus->subtype.as<SpellID>().hasValue())
 		boost::algorithm::replace_all(text, "${subtype.spell}", bonus->subtype.as<SpellID>().toSpell()->getNameTranslated());
 
 	return text;
@@ -95,8 +95,11 @@ ImagePath CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bonu
 	case BonusType::SPELL_IMMUNITY:
 	{
 		fullPath = true;
-		const CSpell * sp = bonus->subtype.as<SpellID>().toSpell();
-		fileName = sp->getIconImmune();
+		if (bonus->subtype.as<SpellID>().hasValue())
+		{
+			const CSpell * sp = bonus->subtype.as<SpellID>().toSpell();
+			fileName = sp->getIconImmune();
+		}
 		break;
 	}
 	case BonusType::SPELL_DAMAGE_REDUCTION: //Spell damage reduction for all schools

+ 2 - 2
lib/JsonRandom.cpp

@@ -80,7 +80,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 	IdentifierType JsonRandom::decodeKey(const std::string & modScope, const std::string & value, const Variables & variables)
 	{
 		if (value.empty() || value[0] != '@')
-			return IdentifierType(*VLC->identifiers()->getIdentifier(modScope, IdentifierType::entityType(), value));
+			return IdentifierType(VLC->identifiers()->getIdentifier(modScope, IdentifierType::entityType(), value).value_or(-1));
 		else
 			return loadVariable(IdentifierType::entityType(), value, variables, IdentifierType::NONE);
 	}
@@ -89,7 +89,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 	IdentifierType JsonRandom::decodeKey(const JsonNode & value, const Variables & variables)
 	{
 		if (value.String().empty() || value.String()[0] != '@')
-			return IdentifierType(*VLC->identifiers()->getIdentifier(IdentifierType::entityType(), value));
+			return IdentifierType(VLC->identifiers()->getIdentifier(IdentifierType::entityType(), value).value_or(-1));
 		else
 			return loadVariable(IdentifierType::entityType(), value.String(), variables, IdentifierType::NONE);
 	}

+ 3 - 2
lib/battle/CBattleInfoCallback.cpp

@@ -867,9 +867,10 @@ bool CBattleInfoCallback::handleObstacleTriggersForUnit(SpellCastEnvironment & s
 			auto shouldReveal = !spellObstacle->hidden || !battleIsObstacleVisibleForSide(*obstacle, (BattlePerspective::BattlePerspective)side);
 			const auto * hero = battleGetFightingHero(spellObstacle->casterSide);
 			auto caster = spells::ObstacleCasterProxy(getBattle()->getSidePlayer(spellObstacle->casterSide), hero, *spellObstacle);
-			const auto * sp = obstacle->getTrigger().toSpell();
-			if(obstacle->triggersEffects() && sp)
+
+			if(obstacle->triggersEffects() && obstacle->getTrigger().hasValue())
 			{
+				const auto * sp = obstacle->getTrigger().toSpell();
 				auto cast = spells::BattleCast(this, &caster, spells::Mode::PASSIVE, sp);
 				spells::detail::ProblemImpl ignored;
 				auto target = spells::Target(1, spells::Destination(&unit));

+ 5 - 0
lib/constants/IdentifierBase.h

@@ -52,6 +52,11 @@ public:
 		num = value;
 	}
 
+	constexpr bool hasValue() const
+	{
+		return num >= 0;
+	}
+
 	struct hash
 	{
 		size_t operator()(const IdentifierBase & id) const

+ 6 - 15
lib/mapObjectConstructors/AObjectTypeHandler.cpp

@@ -21,17 +21,7 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 AObjectTypeHandler::AObjectTypeHandler() = default;
-
-AObjectTypeHandler::~AObjectTypeHandler()
-{
-	// FIXME: currently on Android there is a weird crash in destructor of 'base' member
-	// this code attempts to localize and fix this crash
-	if (base)
-	{
-		base->clear();
-		base.reset();
-	}
-}
+AObjectTypeHandler::~AObjectTypeHandler() = default;
 
 std::string AObjectTypeHandler::getJsonKey() const
 {
@@ -89,12 +79,12 @@ void AObjectTypeHandler::init(const JsonNode & input)
 		if (base)
 			JsonUtils::inherit(entry.second, *base);
 
-		auto * tmpl = new ObjectTemplate;
+		auto tmpl = std::make_shared<ObjectTemplate>();
 		tmpl->id = Obj(type);
 		tmpl->subid = subtype;
 		tmpl->stringID = entry.first; // FIXME: create "fullID" - type.object.template?
 		tmpl->readJson(entry.second);
-		templates.push_back(std::shared_ptr<const ObjectTemplate>(tmpl));
+		templates.push_back(tmpl);
 	}
 
 	for(const JsonNode & node : input["sounds"]["ambient"].Vector())
@@ -188,12 +178,13 @@ void AObjectTypeHandler::addTemplate(JsonNode config)
 	config.setType(JsonNode::JsonType::DATA_STRUCT); // ensure that input is not null
 	if (base)
 		JsonUtils::inherit(config, *base);
-	auto * tmpl = new ObjectTemplate;
+
+	auto tmpl = std::make_shared<ObjectTemplate>();
 	tmpl->id = Obj(type);
 	tmpl->subid = subtype;
 	tmpl->stringID.clear(); // TODO?
 	tmpl->readJson(config);
-	templates.emplace_back(tmpl);
+	templates.push_back(tmpl);
 }
 
 std::vector<std::shared_ptr<const ObjectTemplate>> AObjectTypeHandler::getTemplates() const

+ 2 - 2
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -109,13 +109,13 @@ std::vector<JsonNode> CObjectClassesHandler::loadLegacyData()
 
 	for (size_t i = 0; i < totalNumber; i++)
 	{
-		auto * tmpl = new ObjectTemplate;
+		auto tmpl = std::make_shared<ObjectTemplate>();
 
 		tmpl->readTxt(parser);
 		parser.endLine();
 
 		std::pair key(tmpl->id, tmpl->subid);
-		legacyTemplates.insert(std::make_pair(key, std::shared_ptr<const ObjectTemplate>(tmpl)));
+		legacyTemplates.insert(std::make_pair(key, tmpl));
 	}
 
 	objects.resize(256);

+ 4 - 4
lib/mapObjectConstructors/DwellingInstanceConstructor.cpp

@@ -35,17 +35,17 @@ void DwellingInstanceConstructor::initTypeData(const JsonNode & input)
 	const auto totalLevels = levels.size();
 
 	availableCreatures.resize(totalLevels);
-	for(auto currentLevel = 0; currentLevel < totalLevels; currentLevel++)
+	for(int currentLevel = 0; currentLevel < totalLevels; currentLevel++)
 	{
 		const JsonVector & creaturesOnLevel = levels[currentLevel].Vector();
 		const auto creaturesNumber = creaturesOnLevel.size();
 		availableCreatures[currentLevel].resize(creaturesNumber);
 
-		for(auto currentCreature = 0; currentCreature < creaturesNumber; currentCreature++)
+		for(int currentCreature = 0; currentCreature < creaturesNumber; currentCreature++)
 		{
-			VLC->identifiers()->requestIdentifier("creature", creaturesOnLevel[currentCreature], [=] (si32 index)
+			VLC->identifiers()->requestIdentifier("creature", creaturesOnLevel[currentCreature], [this, currentLevel, currentCreature] (si32 index)
 			{
-				availableCreatures[currentLevel][currentCreature] = CreatureID(index).toCreature();
+				availableCreatures.at(currentLevel).at(currentCreature) = CreatureID(index).toCreature();
 			});
 		}
 		assert(!availableCreatures[currentLevel].empty());

+ 2 - 2
lib/mapping/MapFormatJson.cpp

@@ -1075,14 +1075,14 @@ void CMapLoaderJson::MapObjectLoader::construct()
 
 	auto handler = VLC->objtypeh->getHandlerFor( ModScope::scopeMap(), typeName, subtypeName);
 
-	auto * appearance = new ObjectTemplate;
+	auto appearance = std::make_shared<ObjectTemplate>();
 
 	appearance->id = Obj(handler->getIndex());
 	appearance->subid = handler->getSubIndex();
 	appearance->readJson(configuration["template"], false);
 
 	// Will be destroyed soon and replaced with shared template
-	instance = handler->create(owner->map->cb, std::shared_ptr<const ObjectTemplate>(appearance));
+	instance = handler->create(owner->map->cb, appearance);
 
 	instance->id = ObjectInstanceID(static_cast<si32>(owner->map->objects.size()));
 	instance->instanceName = jsonKey;

+ 2 - 2
server/CGameHandler.cpp

@@ -3966,14 +3966,14 @@ bool CGameHandler::moveStack(const StackLocation &src, const StackLocation &dst,
 
 void CGameHandler::castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos)
 {
-	const CSpell * s = spellID.toSpell();
-	if(!s)
+	if (!spellID.hasValue())
 		return;
 
 	AdventureSpellCastParameters p;
 	p.caster = caster;
 	p.pos = pos;
 
+	const CSpell * s = spellID.toSpell();
 	s->adventureCast(spellEnv, p);
 }
 

+ 3 - 2
server/NetPacksServer.cpp

@@ -327,9 +327,9 @@ void ApplyGhNetPackVisitor::visitCastAdvSpell(CastAdvSpell & pack)
 {
 	gh.throwIfWrongOwner(&pack, pack.hid);
 
-	const CSpell * s = pack.sid.toSpell();
-	if(!s)
+	if (!pack.sid.hasValue())
 		gh.throwNotAllowedAction(&pack);
+
 	const CGHeroInstance * h = gh.getHero(pack.hid);
 	if(!h)
 		gh.throwNotAllowedAction(&pack);
@@ -338,6 +338,7 @@ void ApplyGhNetPackVisitor::visitCastAdvSpell(CastAdvSpell & pack)
 	p.caster = h;
 	p.pos = pack.pos;
 
+	const CSpell * s = pack.sid.toSpell();
 	result = s->adventureCast(gh.spellEnv, p);
 }
 

+ 13 - 13
server/TurnTimerHandler.cpp

@@ -88,21 +88,21 @@ void TurnTimerHandler::onPlayerGetTurn(PlayerColor player)
 void TurnTimerHandler::update(int waitTime)
 {
 	std::lock_guard<std::recursive_mutex> guard(mx);
-	if(const auto * gs = gameHandler.gameState())
-	{
-		for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player)
-			if(gs->isPlayerMakingTurn(player))
-				onPlayerMakingTurn(player, waitTime);
-		
-		// create copy for iterations - battle might end during onBattleLoop call
-		std::vector<BattleID> ongoingBattles;
+	if(!gameHandler.getStartInfo()->turnTimerInfo.isEnabled())
+		return;
 
-		for (auto & battle : gs->currentBattles)
-			ongoingBattles.push_back(battle->battleID);
+	for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player)
+		if(gameHandler.gameState()->isPlayerMakingTurn(player))
+			onPlayerMakingTurn(player, waitTime);
 
-		for (auto & battleID : ongoingBattles)
-			onBattleLoop(battleID, waitTime);
-	}
+	// create copy for iterations - battle might end during onBattleLoop call
+	std::vector<BattleID> ongoingBattles;
+
+	for (auto & battle : gameHandler.gameState()->currentBattles)
+		ongoingBattles.push_back(battle->battleID);
+
+	for (auto & battleID : ongoingBattles)
+		onBattleLoop(battleID, waitTime);
 }
 
 bool TurnTimerHandler::timerCountDown(int & timer, int initialTimer, PlayerColor player, int waitTime)

+ 2 - 2
server/battles/BattleActionProcessor.cpp

@@ -102,13 +102,13 @@ bool BattleActionProcessor::doHeroSpellAction(const CBattleInfoCallback & battle
 		return false;
 	}
 
-	const CSpell * s = ba.spell.toSpell();
-	if (!s)
+	if (!ba.spell.hasValue())
 	{
 		logGlobal->error("Wrong spell id (%d)!", ba.spell.getNum());
 		return false;
 	}
 
+	const CSpell * s = ba.spell.toSpell();
 	spells::BattleCast parameters(&battle, h, spells::Mode::HERO, s);
 
 	spells::detail::ProblemImpl problem;