Browse Source

Merge pull request #4087 from IvanSavenko/stabilization

[1.5.3] Stabilization
Ivan Savenko 1 year ago
parent
commit
381171f897

+ 2 - 2
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -403,7 +403,7 @@ void Nullkiller::makeTurn()
 
 		if(selectedTasks.empty())
 		{
-			return;
+			selectedTasks.push_back(taskptr(Goals::Invalid()));
 		}
 
 		bool hasAnySuccess = false;
@@ -456,7 +456,7 @@ void Nullkiller::makeTurn()
 					scanDepth = ScanDepth::ALL_FULL;
 					useHeroChain = false;
 					hasAnySuccess = true;
-					break;;
+					break;
 				}
 
 				logAi->trace("Goal %s has too low priority. It is not worth doing it.", taskDescription);

+ 10 - 3
AI/Nullkiller/Goals/ExploreNeighbourTile.cpp

@@ -26,9 +26,9 @@ bool ExploreNeighbourTile::operator==(const ExploreNeighbourTile & other) const
 
 void ExploreNeighbourTile::accept(AIGateway * ai)
 {
-	ExplorationHelper h(hero, ai->nullkiller.get());
+	ExplorationHelper h(hero, ai->nullkiller.get(), true);
 
-	for(int i = 0; i < tilesToExplore && hero->movementPointsRemaining() > 0; i++)
+	for(int i = 0; i < tilesToExplore && ai->myCb->getObj(hero->id, false) && hero->movementPointsRemaining() > 0; i++)
 	{
 		int3 pos = hero->visitablePos();
 		float value = 0;
@@ -54,7 +54,14 @@ void ExploreNeighbourTile::accept(AIGateway * ai)
 				}
 			});
 
-		if(!target.valid() || !ai->moveHeroToTile(target, hero))
+		if(!target.valid())
+		{
+			return;
+		}
+
+		auto danger = ai->nullkiller->pathfinder->getStorage()->evaluateDanger(target, hero, true);
+
+		if(danger > 0 || !ai->moveHeroToTile(target, hero))
 		{
 			return;
 		}

+ 6 - 4
AI/Nullkiller/Helpers/ExplorationHelper.cpp

@@ -24,8 +24,8 @@ namespace NKAI
 
 using namespace Goals;
 
-ExplorationHelper::ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai)
-	:ai(ai), cbp(ai->cb.get()), hero(hero)
+ExplorationHelper::ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility)
+	:ai(ai), cbp(ai->cb.get()), hero(hero), useCPathfinderAccessibility(useCPathfinderAccessibility)
 {
 	ts = cbp->getPlayerTeam(ai->playerID);
 	sightRadius = hero->getSightRadius();
@@ -104,7 +104,7 @@ bool ExplorationHelper::scanMap()
 
 	if(!bestGoal->invalid())
 	{
-		return false;
+		return true;
 	}
 
 	allowDeadEndCancellation = false;
@@ -222,7 +222,9 @@ bool ExplorationHelper::hasReachableNeighbor(const int3 & pos) const
 		int3 tile = pos + dir;
 		if(cbp->isInTheMap(tile))
 		{
-			auto isAccessible = ai->pathfinder->isTileAccessible(hero, tile);
+			auto isAccessible = useCPathfinderAccessibility
+				? ai->cb->getPathsInfo(hero)->getPathInfo(tile)->reachable()
+				: ai->pathfinder->isTileAccessible(hero, tile);
 
 			if(isAccessible)
 				return true;

+ 2 - 1
AI/Nullkiller/Helpers/ExplorationHelper.h

@@ -34,9 +34,10 @@ private:
 	const TeamState * ts;
 	int3 ourPos;
 	bool allowDeadEndCancellation;
+	bool useCPathfinderAccessibility;
 
 public:
-	ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai);
+	ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility = false);
 	Goals::TSubgoal makeComposition() const;
 	bool scanSector(int scanRadius);
 	bool scanMap();

+ 1 - 1
android/vcmi-app/build.gradle

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

+ 1 - 1
client/CPlayerInterface.cpp

@@ -244,7 +244,7 @@ void CPlayerInterface::performAutosave()
 				int txtlen = TextOperations::getUnicodeCharactersCount(name);
 
 				TextOperations::trimRightUnicode(name, std::max(0, txtlen - 15));
-				std::string forbiddenChars("\\/:?\"<>| ");
+				std::string forbiddenChars("\\/:*?\"<>| ");
 				std::replace_if(name.begin(), name.end(), [&](char c) { return std::string::npos != forbiddenChars.find(c); }, '_' );
 
 				prefix = name + "_" + cb->getStartInfo()->startTimeIso8601 + "/";

+ 4 - 0
client/adventureMap/AdventureMapInterface.cpp

@@ -643,11 +643,15 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
 		objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner);
 		std::string text = LOCPLINT->localState->getCurrentHero() ? objAtTile->getHoverText(LOCPLINT->localState->getCurrentHero()) : objAtTile->getHoverText(LOCPLINT->playerID);
 		boost::replace_all(text,"\n"," ");
+		if (GH.isKeyboardShiftDown())
+			text.append(" (" + std::to_string(targetPosition.x) + ", " + std::to_string(targetPosition.y) + ")");
 		GH.statusbar()->write(text);
 	}
 	else if(isTargetPositionVisible)
 	{
 		std::string tileTooltipText = CGI->mh->getTerrainDescr(targetPosition, false);
+		if (GH.isKeyboardShiftDown())
+			tileTooltipText.append(" (" + std::to_string(targetPosition.x) + ", " + std::to_string(targetPosition.y) + ")");
 		GH.statusbar()->write(tileTooltipText);
 	}
 

+ 2 - 2
client/battle/BattleWindow.cpp

@@ -763,8 +763,8 @@ void BattleWindow::blockUI(bool on)
 	setShortcutBlocked(EShortcut::BATTLE_SELECT_ACTION, on || owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_AUTOCOMBAT, (settings["battle"]["endWithAutocombat"].Bool() && onlyOnePlayerHuman) ? on || owner.tacticsMode || owner.actionsController->spellcastingModeActive() : owner.actionsController->spellcastingModeActive());
 	setShortcutBlocked(EShortcut::BATTLE_END_WITH_AUTOCOMBAT, on || owner.tacticsMode || !onlyOnePlayerHuman || owner.actionsController->spellcastingModeActive());
-	setShortcutBlocked(EShortcut::BATTLE_TACTICS_END, on && owner.tacticsMode);
-	setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on && owner.tacticsMode);
+	setShortcutBlocked(EShortcut::BATTLE_TACTICS_END, on || !owner.tacticsMode);
+	setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on || !owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode);
 }

+ 8 - 16
client/widgets/CGarrisonInt.cpp

@@ -542,42 +542,34 @@ void CGarrisonInt::addSplitBtn(std::shared_ptr<CButton> button)
 
 void CGarrisonInt::createSlots()
 {
+	availableSlots.clear();
+
 	int distance = interx + (smallIcons ? 32 : 58);
 	for(auto i : { EGarrisonType::UPPER, EGarrisonType::LOWER })
 	{
 		Point offset = garOffset * static_cast<int>(i);
-
-		std::vector<std::shared_ptr<CGarrisonSlot>> garrisonSlots;
-		garrisonSlots.resize(7);
-		if(army(i))
-		{
-			for(auto & elem : army(i)->Slots())
-			{
-				garrisonSlots[elem.first.getNum()] = std::make_shared<CGarrisonSlot>(this, offset.x + (elem.first.getNum()*distance), offset.y, elem.first, i, elem.second);
-			}
-		}
 		for(int j = 0; j < 7; j++)
 		{
-			if(!garrisonSlots[j])
-				garrisonSlots[j] = std::make_shared<CGarrisonSlot>(this, offset.x + (j*distance), offset.y, SlotID(j), i, nullptr);
+			Point position(offset.x + (j*distance), offset.y);
 
 			if(layout == ESlotsLayout::TWO_ROWS && j >= 4)
 			{
-				garrisonSlots[j]->moveBy(Point(-126, 37));
+				position += Point(-126, 37);
 			}
 			else if(layout == ESlotsLayout::REVERSED_TWO_ROWS)
 			{
 				if(j >= 3)
 				{
-					garrisonSlots[j]->moveBy(Point(-90, 49));
+					position += Point(-90, 49);
 				}
 				else
 				{
-					garrisonSlots[j]->moveBy(Point(36, 0));
+					position += Point(36, 0);
 				}
 			}
+			SlotID slot(j);
+			availableSlots.push_back(std::make_shared<CGarrisonSlot>(this, position.x, position.y, slot, i, army(i) ? army(i)->getStackPtr(slot) : nullptr));
 		}
-		vstd::concatenate(availableSlots, garrisonSlots);
 	}
 }
 

+ 23 - 27
client/windows/CExchangeWindow.cpp

@@ -46,9 +46,7 @@ static bool isQuickExchangeLayoutAvailable()
 
 CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID)
 	: CWindowObject(PLAYER_COLORED | BORDERED, ImagePath::builtin(isQuickExchangeLayoutAvailable() ? QUICK_EXCHANGE_BG : "TRADE2")),
-	controller(hero1, hero2),
-	moveStackLeftButtons(),
-	moveStackRightButtons()
+	controller(hero1, hero2)
 {
 	const bool qeLayout = isQuickExchangeLayoutAvailable();
 
@@ -193,54 +191,52 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 
 	if(qeLayout)
 	{
-		moveAllGarrButtonLeft    = std::make_shared<CButton>(Point(325, 118), AnimationPath::builtin("quick-exchange/armRight.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[1]),
-			[this](){ this->moveUnitsShortcut(false); });
-		exchangeGarrButton       = std::make_shared<CButton>(Point(377, 118), AnimationPath::builtin("quick-exchange/swapAll.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[2]),
-			[this](){ controller.swapArmy(); });
-		moveAllGarrButtonRight   = std::make_shared<CButton>(Point(425, 118), AnimationPath::builtin("quick-exchange/armLeft.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[1]),
-			[this](){ this->moveUnitsShortcut(true); });
-		moveArtifactsButtonLeft  = std::make_shared<CButton>(Point(325, 154), AnimationPath::builtin("quick-exchange/artRight.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[3]),
-			[this](){ this->moveArtifactsCallback(false);});
-		exchangeArtifactsButton  = std::make_shared<CButton>(Point(377, 154), AnimationPath::builtin("quick-exchange/swapAll.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[4]),
-			[this](){ this->swapArtifactsCallback(); });
-		moveArtifactsButtonRight = std::make_shared<CButton>(Point(425, 154), AnimationPath::builtin("quick-exchange/artLeft.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[3]),
-			[this](){ this->moveArtifactsCallback(true);});
-
-		backpackButtonLeft       = std::make_shared<CButton>(Point(325, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
+		buttonMoveUnitsFromLeftToRight = std::make_shared<CButton>(Point(325, 118), AnimationPath::builtin("quick-exchange/armRight.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[1]), [this](){ this->moveUnitsShortcut(true); });
+		buttonMoveUnitsFromRightToLeft = std::make_shared<CButton>(Point(425, 118), AnimationPath::builtin("quick-exchange/armLeft.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[1]), [this](){ this->moveUnitsShortcut(false); });
+		buttonMoveArtifactsFromLeftToRight = std::make_shared<CButton>(Point(325, 154), AnimationPath::builtin("quick-exchange/artRight.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[3]), [this](){ this->moveArtifactsCallback(true);});
+		buttonMoveArtifactsFromRightToLeft = std::make_shared<CButton>(Point(425, 154), AnimationPath::builtin("quick-exchange/artLeft.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[3]), [this](){ this->moveArtifactsCallback(false);});
+
+		exchangeUnitsButton = std::make_shared<CButton>(Point(377, 118), AnimationPath::builtin("quick-exchange/swapAll.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[2]), [this](){ controller.swapArmy(); });
+		exchangeArtifactsButton  = std::make_shared<CButton>(Point(377, 154), AnimationPath::builtin("quick-exchange/swapAll.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[4]), [this](){ this->swapArtifactsCallback(); });
+
+		backpackButtonLeft = std::make_shared<CButton>(Point(325, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
 			[this](){ this->backpackShortcut(true); });
-		backpackButtonRight      = std::make_shared<CButton>(Point(419, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
+		backpackButtonRight = std::make_shared<CButton>(Point(419, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
 			[this](){ this->backpackShortcut(false); });
 		backpackButtonLeft->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/backpackButtonIcon")));
 		backpackButtonRight->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/backpackButtonIcon")));
 
 		auto leftHeroBlock = heroInst[0]->tempOwner != LOCPLINT->cb->getPlayerID();
 		auto rightHeroBlock = heroInst[1]->tempOwner != LOCPLINT->cb->getPlayerID();
-		moveAllGarrButtonLeft->block(leftHeroBlock);
-		exchangeGarrButton->block(leftHeroBlock || rightHeroBlock);
-		moveAllGarrButtonRight->block(rightHeroBlock);
-		moveArtifactsButtonLeft->block(leftHeroBlock);
+
+		buttonMoveUnitsFromLeftToRight->block(leftHeroBlock);
+		buttonMoveUnitsFromRightToLeft->block(rightHeroBlock);
+		buttonMoveArtifactsFromLeftToRight->block(leftHeroBlock);
+		buttonMoveArtifactsFromRightToLeft->block(rightHeroBlock);
+
+		exchangeUnitsButton->block(leftHeroBlock || rightHeroBlock);
 		exchangeArtifactsButton->block(leftHeroBlock || rightHeroBlock);
-		moveArtifactsButtonRight->block(rightHeroBlock);
+
 		backpackButtonLeft->block(leftHeroBlock);
 		backpackButtonRight->block(rightHeroBlock);
 
 		for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
 		{
-			moveStackLeftButtons.push_back(
+			moveUnitFromRightToLeftButtons.push_back(
 				std::make_shared<CButton>(
 					Point(484 + 35 * i, 154),
 					AnimationPath::builtin("quick-exchange/unitLeft.DEF"),
 					CButton::tooltip(CGI->generaltexth->qeModCommands[1]),
 					std::bind(&CExchangeController::moveStack, &controller, false, SlotID(i))));
-			moveStackLeftButtons.back()->block(leftHeroBlock);
+			moveUnitFromRightToLeftButtons.back()->block(leftHeroBlock);
 
-			moveStackRightButtons.push_back(
+			moveUnitFromLeftToRightButtons.push_back(
 				std::make_shared<CButton>(
 					Point(66 + 35 * i, 154),
 					AnimationPath::builtin("quick-exchange/unitRight.DEF"),
 					CButton::tooltip(CGI->generaltexth->qeModCommands[1]),
 					std::bind(&CExchangeController::moveStack, &controller, true, SlotID(i))));
-			moveStackLeftButtons.back()->block(rightHeroBlock);
+			moveUnitFromLeftToRightButtons.back()->block(rightHeroBlock);
 		}
 	}
 

+ 9 - 7
client/windows/CExchangeWindow.h

@@ -41,14 +41,16 @@ class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public
 	std::array<std::shared_ptr<CButton>, 2> questlogButton;
 
 	std::shared_ptr<CGarrisonInt> garr;
-	std::shared_ptr<CButton> moveAllGarrButtonLeft;
-	std::shared_ptr<CButton> exchangeGarrButton;
-	std::shared_ptr<CButton> moveAllGarrButtonRight;
-	std::shared_ptr<CButton> moveArtifactsButtonLeft;
+	std::shared_ptr<CButton> buttonMoveUnitsFromLeftToRight;
+	std::shared_ptr<CButton> buttonMoveUnitsFromRightToLeft;
+	std::shared_ptr<CButton> buttonMoveArtifactsFromLeftToRight;
+	std::shared_ptr<CButton> buttonMoveArtifactsFromRightToLeft;
+
+	std::shared_ptr<CButton> exchangeUnitsButton;
 	std::shared_ptr<CButton> exchangeArtifactsButton;
-	std::shared_ptr<CButton> moveArtifactsButtonRight;
-	std::vector<std::shared_ptr<CButton>> moveStackLeftButtons;
-	std::vector<std::shared_ptr<CButton>> moveStackRightButtons;
+
+	std::vector<std::shared_ptr<CButton>> moveUnitFromLeftToRightButtons;
+	std::vector<std::shared_ptr<CButton>> moveUnitFromRightToLeftButtons;
 	std::shared_ptr<CButton> backpackButtonLeft;
 	std::shared_ptr<CButton> backpackButtonRight;
 	CExchangeController controller;

+ 3 - 2
client/windows/GUIClasses.cpp

@@ -434,14 +434,15 @@ CLevelWindow::CLevelWindow(const CGHeroInstance * hero, PrimarySkill pskill, std
 	skillValue = std::make_shared<CLabel>(192, 253, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->primarySkillNames[static_cast<int>(pskill)] + " +1");
 }
 
-
-CLevelWindow::~CLevelWindow()
+void CLevelWindow::close()
 {
 	//FIXME: call callback if there was nothing to select?
 	if (box && box->selectedIndex() != -1)
 		cb(box->selectedIndex());
 
 	LOCPLINT->showingDialog->setFree();
+
+	CWindowObject::close();
 }
 
 CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::function<void()> & onWindowClosed)

+ 2 - 1
client/windows/GUIClasses.h

@@ -150,7 +150,8 @@ class CLevelWindow : public CWindowObject
 
 public:
 	CLevelWindow(const CGHeroInstance *hero, PrimarySkill pskill, std::vector<SecondarySkill> &skills, std::function<void(ui32)> callback);
-	~CLevelWindow();
+
+	void close() override;
 };
 
 /// Town portal, castle gate window

+ 7 - 3
lib/CCreatureHandler.cpp

@@ -646,10 +646,15 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
 			registerObject(scope, type_name, extraName.String(), cre->getIndex());
 	}
 
+	if (!cre->special &&
+		!CResourceHandler::get()->existsResource(cre->animDefName) &&
+		!CResourceHandler::get()->existsResource(cre->animDefName.addPrefix("SPRITES/")))
+		throw ModLoadingException(scope, "creature " + cre->getJsonKey() + " has no combat animation but is not marked as special!" );
+
 	JsonNode advMapFile = node["graphics"]["map"];
 	JsonNode advMapMask = node["graphics"]["mapMask"];
 
-	VLC->identifiers()->requestIdentifier(scope, "object", "monster", [cre, scope, advMapFile, advMapMask](si32 index)
+	VLC->identifiers()->requestIdentifier(scope, "object", "monster", [cre, scope, advMapFile, advMapMask](si32 monsterIndex)
 	{
 		JsonNode conf;
 		conf.setModScope(scope);
@@ -672,7 +677,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
 		if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, cre->getId().num)->getTemplates().empty())
 		{
 			if (!cre->special)
-				throw DataLoadingException("Mod " + scope + " is corrupted! Please disable or reinstall this mod. Reason: creature " + cre->getJsonKey() + " has no adventure map animation but is not marked as special!" );
+				throw ModLoadingException(scope, "creature " + cre->getJsonKey() + " has no adventure map animation but is not marked as special!" );
 
 			VLC->objtypeh->removeSubObject(Obj::MONSTER, cre->getId().num);
 		}
@@ -737,7 +742,6 @@ void CCreatureHandler::loadCrExpMod()
 	}
 }
 
-
 void CCreatureHandler::loadCrExpBon(CBonusSystemNode & globalEffects)
 {
 	if (VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) 	//reading default stack experience bonuses

+ 2 - 3
lib/CHeroHandler.cpp

@@ -218,8 +218,7 @@ void CHeroClass::serializeJson(JsonSerializeFormat & handler)
 CHeroClass::CHeroClass():
 	faction(0),
 	affinity(0),
-	defaultTavernChance(0),
-	commander(nullptr) 
+	defaultTavernChance(0)
 {
 }
 
@@ -302,7 +301,7 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js
 	VLC->identifiers()->requestIdentifier ("creature", node["commander"],
 	[=](si32 commanderID)
 	{
-		heroClass->commander = CreatureID(commanderID).toCreature();
+		heroClass->commander = CreatureID(commanderID);
 	});
 
 	heroClass->defaultTavernChance = static_cast<ui32>(node["defaultTavern"].Float());

+ 1 - 1
lib/CHeroHandler.h

@@ -121,7 +121,7 @@ public:
 	// resulting chance = sqrt(town.chance * heroClass.chance)
 	ui32 defaultTavernChance;
 
-	const CCreature * commander;
+	CreatureID commander;
 
 	std::vector<int> primarySkillInitial;  // initial primary skills
 	std::vector<int> primarySkillLowLevel; // probability (%) of getting point of primary skill when getting level

+ 8 - 0
lib/ExceptionsCommon.h

@@ -14,3 +14,11 @@ class DLL_LINKAGE DataLoadingException: public std::runtime_error
 public:
     using std::runtime_error::runtime_error;
 };
+
+class DLL_LINKAGE ModLoadingException: public DataLoadingException
+{
+public:
+	ModLoadingException(const std::string & modName, const std::string & reason)
+		: DataLoadingException("Mod " + modName + " is corrupted! Please disable or reinstall this mod. Reason: " + reason)
+	{}
+};

+ 2 - 2
lib/gameState/CGameState.cpp

@@ -1499,8 +1499,8 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio
 		case EventCondition::TRANSPORT:
 		{
 			const auto * t = getTown(condition.objectID);
-			return (t->visitingHero && t->visitingHero->hasArt(condition.objectType.as<ArtifactID>())) ||
-				   (t->garrisonHero && t->garrisonHero->hasArt(condition.objectType.as<ArtifactID>()));
+			return (t->visitingHero && t->visitingHero->getOwner() == player && t->visitingHero->hasArt(condition.objectType.as<ArtifactID>())) ||
+				   (t->garrisonHero && t->garrisonHero->getOwner() == player && t->garrisonHero->hasArt(condition.objectType.as<ArtifactID>()));
 		}
 		case EventCondition::DAYS_PASSED:
 		{

+ 2 - 2
lib/mapObjects/CGHeroInstance.cpp

@@ -402,9 +402,9 @@ void CGHeroInstance::initHero(CRandomGenerator & rand)
 		addNewBonus(bonus);
 	}
 
-	if (VLC->settings()->getBoolean(EGameSettings::MODULE_COMMANDERS) && !commander)
+	if (VLC->settings()->getBoolean(EGameSettings::MODULE_COMMANDERS) && !commander && type->heroClass->commander.hasValue())
 	{
-		commander = new CCommanderInstance(type->heroClass->commander->getId());
+		commander = new CCommanderInstance(type->heroClass->commander);
 		commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders
 		commander->giveStackExp (exp); //after our exp is set
 	}

+ 5 - 1
server/CGameHandler.cpp

@@ -1104,7 +1104,11 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
 		objectToVisit = t.visitableObjects.back();
 
 	if (isInTheMap(guardPos))
-		guardian = getTile(guardPos)->visitableObjects.back();
+	{
+		for (auto const & object : getTile(guardPos)->visitableObjects)
+			if (object->ID == MapObjectID::MONSTER) // exclude other objects, such as hero flying above monster
+				guardian = object;
+	}
 
 	const bool embarking = !h->boat && objectToVisit && objectToVisit->ID == Obj::BOAT;
 	const bool disembarking = h->boat

+ 7 - 1
server/CVCMIServer.cpp

@@ -165,6 +165,9 @@ void CVCMIServer::onNewConnection(const std::shared_ptr<INetworkConnection> & co
 void CVCMIServer::onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message)
 {
 	std::shared_ptr<CConnection> c = findConnection(connection);
+	if (c == nullptr)
+		throw std::out_of_range("Unknown connection received in CVCMIServer::findConnection");
+
 	auto pack = c->retrievePack(message);
 	pack->c = c;
 	CVCMIServerPackVisitor visitor(*this, this->gh);
@@ -197,7 +200,7 @@ std::shared_ptr<CConnection> CVCMIServer::findConnection(const std::shared_ptr<I
 			return gameConnection;
 	}
 
-	throw std::runtime_error("Unknown connection received in CVCMIServer::findConnection");
+	return nullptr;
 }
 
 bool CVCMIServer::wasStartedByClient() const
@@ -342,6 +345,9 @@ void CVCMIServer::onDisconnected(const std::shared_ptr<INetworkConnection> & con
 	logNetwork->error("Network error receiving a pack. Connection has been closed");
 
 	std::shared_ptr<CConnection> c = findConnection(connection);
+	if (!c)
+		return; // player have already disconnected via clientDisconnected call
+
 	vstd::erase(activeConnections, c);
 
 	if(activeConnections.empty() || hostClientId == c->connectionID)

+ 5 - 11
server/queries/BattleQueries.cpp

@@ -27,7 +27,7 @@ void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisi
 {
 	assert(result);
 
-	if(result && !isAiVsHuman)
+	if(result)
 		objectVisit.visitedObject->battleFinished(objectVisit.visitingHero, *result);
 }
 
@@ -38,8 +38,6 @@ CBattleQuery::CBattleQuery(CGameHandler * owner, const IBattleInfo * bi):
 	belligerents[0] = bi->getSideArmy(0);
 	belligerents[1] = bi->getSideArmy(1);
 
-	isAiVsHuman = bi->getSidePlayer(1).isValidPlayer() && gh->getPlayerState(bi->getSidePlayer(0))->isHuman() != gh->getPlayerState(bi->getSidePlayer(1))->isHuman();
-
 	addPlayer(bi->getSidePlayer(0));
 	addPlayer(bi->getSidePlayer(1));
 }
@@ -89,7 +87,9 @@ CBattleDialogQuery::CBattleDialogQuery(CGameHandler * owner, const IBattleInfo *
 
 void CBattleDialogQuery::onRemoval(PlayerColor color)
 {
-	if (!gh->getPlayerState(color)->isHuman())
+	// answer to this query was already processed when handling 1st player
+	// this removal call for 2nd player which can be safely ignored
+	if (resultProcessed)
 		return;
 
 	assert(answer);
@@ -108,13 +108,7 @@ void CBattleDialogQuery::onRemoval(PlayerColor color)
 	}
 	else
 	{
-		auto hero = bi->getSideHero(BattleSide::ATTACKER);
-		auto visitingObj = bi->getDefendedTown() ? bi->getDefendedTown() : gh->getVisitingObject(hero);
-		bool isAiVsHuman = bi->getSidePlayer(1).isValidPlayer() && gh->getPlayerState(bi->getSidePlayer(0))->isHuman() != gh->getPlayerState(bi->getSidePlayer(1))->isHuman();
-		
 		gh->battles->endBattleConfirm(bi->getBattleID());
-
-		if(visitingObj && result && isAiVsHuman)
-			visitingObj->battleFinished(hero, *result);
 	}
+	resultProcessed = true;
 }

+ 1 - 1
server/queries/BattleQueries.h

@@ -23,7 +23,6 @@ public:
 	std::array<const CArmedInstance *,2> belligerents;
 	std::array<int, 2> initialHeroMana;
 
-	bool isAiVsHuman;
 	BattleID battleID;
 	std::optional<BattleResult> result;
 
@@ -37,6 +36,7 @@ public:
 
 class CBattleDialogQuery : public CDialogQuery
 {
+	bool resultProcessed = false;
 public:
 	CBattleDialogQuery(CGameHandler * owner, const IBattleInfo * Bi, std::optional<BattleResult> Br);