Ver Fonte

Refactoring.
Dividing CBattleCallback into smaller files with classes.

FeniksFire há 8 anos atrás
pai
commit
221386a39d

+ 1 - 1
lib/BattleInfo.h

@@ -11,7 +11,7 @@
 #include "SiegeInfo.h"
 #include "SideInBattle.h"
 #include "HeroBonus.h"
-#include "CBattleCallback.h"
+#include "battle/CBattleInfoCallback.h"
 #include "int3.h"
 
 class CStack;

+ 1 - 1
lib/CGameInfoCallback.h

@@ -1,7 +1,7 @@
 #pragma once
 
 #include "ResourceSet.h" // for Res::ERes
-#include "CBattleCallback.h" //for CCallbackBase
+#include "battle/CPlayerBattleCallback.h"
 
 /*
  * CGameInfoCallback.h, part of VCMI engine

+ 11 - 5
lib/CMakeLists.txt

@@ -78,12 +78,18 @@ set(lib_SRCS
 
 		BattleAction.cpp
 		BattleHex.cpp
-                BattleInfo.cpp
-                SiegeInfo.cpp
-                SideInBattle.cpp
-                CStack.cpp
+		BattleInfo.cpp
+		battle/AccessibilityInfo.cpp
+		battle/BattleAttackInfo.cpp
+		battle/CBattleInfoCallback.cpp
+		battle/CBattleInfoEssentials.cpp
+		battle/CCallbackBase.cpp
+		battle/CPlayerBattleCallback.cpp
+		battle/ReachabilityInfo.cpp
+		SiegeInfo.cpp
+		SideInBattle.cpp
+		CStack.cpp
 		CArtHandler.cpp
-		CBattleCallback.cpp
 		CBonusTypeHandler.cpp
 		CBuildingHandler.cpp
 		CConfigHandler.cpp

+ 53 - 0
lib/battle/AccessibilityInfo.cpp

@@ -0,0 +1,53 @@
+/*
+ * AccessibilityInfo.cpp, 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
+ *
+ */
+#include "StdInc.h"
+#include "AccessibilityInfo.h"
+#include "CStack.h"
+
+bool AccessibilityInfo::accessible(BattleHex tile, const CStack *stack) const
+{
+	return accessible(tile, stack->doubleWide(), stack->attackerOwned);
+}
+
+bool AccessibilityInfo::accessible(BattleHex tile, bool doubleWide, bool attackerOwned) const
+{
+	// All hexes that stack would cover if standing on tile have to be accessible.
+	for(auto hex : CStack::getHexes(tile, doubleWide, attackerOwned))
+	{
+		// If the hex is out of range then the tile isn't accessible
+		if(!hex.isValid())
+			return false;
+		// If we're no defender which step on gate and the hex isn't accessible, then the tile
+		// isn't accessible
+		else if(at(hex) != EAccessibility::ACCESSIBLE &&
+				!(at(hex) == EAccessibility::GATE && !attackerOwned))
+		{
+			return false;
+		}
+	}
+	return true;
+}
+
+bool AccessibilityInfo::occupiable(const CStack *stack, BattleHex tile) const
+{
+	//obviously, we can occupy tile by standing on it
+	if(accessible(tile, stack))
+		return true;
+
+	if(stack->doubleWide())
+	{
+		//Check the tile next to -> if stack stands there, it'll also occupy considered hex
+		const BattleHex anotherTile = tile + (stack->attackerOwned ? BattleHex::RIGHT : BattleHex::LEFT);
+		if(accessible(anotherTile, stack))
+			return true;
+	}
+
+	return false;
+}

+ 37 - 0
lib/battle/AccessibilityInfo.h

@@ -0,0 +1,37 @@
+/*
+ * AccessibilityInfo.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 "BattleHex.h"
+
+class CStack;
+
+//Accessibility is property of hex in battle. It doesn't depend on stack, side's perspective and so on.
+namespace EAccessibility
+{
+enum EAccessibility
+{
+	ACCESSIBLE,
+	ALIVE_STACK,
+	OBSTACLE,
+	DESTRUCTIBLE_WALL,
+	GATE, //sieges -> gate opens only for defender stacks
+	UNAVAILABLE, //indestructible wall parts, special battlefields (like boat-to-boat)
+	SIDE_COLUMN //used for first and last columns of hexes that are unavailable but wat machines can stand there
+};
+}
+
+typedef std::array<EAccessibility::EAccessibility, GameConstants::BFIELD_SIZE> TAccessibilityArray;
+
+struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray
+{
+	bool occupiable(const CStack *stack, BattleHex tile) const;
+	bool accessible(BattleHex tile, const CStack *stack) const; //checks for both tiles if stack is double wide
+	bool accessible(BattleHex tile, bool doubleWide, bool attackerOwned) const; //checks for both tiles if stack is double wide
+};

+ 51 - 0
lib/battle/BattleAttackInfo.cpp

@@ -0,0 +1,51 @@
+/*
+ * BattleAttackInfo.cpp, 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
+ *
+ */
+#include "StdInc.h"
+#include "BattleAttackInfo.h"
+#include "CStack.h"
+
+
+BattleAttackInfo::BattleAttackInfo(const CStack *Attacker, const CStack *Defender, bool Shooting)
+{
+	attacker = Attacker;
+	defender = Defender;
+
+	attackerBonuses = Attacker;
+	defenderBonuses = Defender;
+
+	attackerPosition = Attacker->position;
+	defenderPosition = Defender->position;
+
+	attackerCount = Attacker->count;
+	defenderCount = Defender->count;
+
+	shooting = Shooting;
+	chargedFields = 0;
+
+	luckyHit = false;
+	unluckyHit = false;
+	deathBlow = false;
+	ballistaDoubleDamage = false;
+}
+
+BattleAttackInfo BattleAttackInfo::reverse() const
+{
+	BattleAttackInfo ret = *this;
+	std::swap(ret.attacker, ret.defender);
+	std::swap(ret.attackerBonuses, ret.defenderBonuses);
+	std::swap(ret.attackerPosition, ret.defenderPosition);
+	std::swap(ret.attackerCount, ret.defenderCount);
+
+	ret.shooting = false;
+	ret.chargedFields = 0;
+	ret.luckyHit = ret.ballistaDoubleDamage = ret.deathBlow = false;
+
+	return ret;
+}

+ 33 - 0
lib/battle/BattleAttackInfo.h

@@ -0,0 +1,33 @@
+/*
+ * BattleAttackInfo.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 "BattleHex.h"
+
+class CStack;
+class IBonusBearer;
+
+struct DLL_LINKAGE BattleAttackInfo
+{
+	const IBonusBearer *attackerBonuses, *defenderBonuses;
+	const CStack *attacker, *defender;
+	BattleHex attackerPosition, defenderPosition;
+
+	int attackerCount, defenderCount;
+	bool shooting;
+	int chargedFields;
+
+	bool luckyHit;
+	bool unluckyHit;
+	bool deathBlow;
+	bool ballistaDoubleDamage;
+
+	BattleAttackInfo(const CStack *Attacker, const CStack *Defender, bool Shooting = false);
+	BattleAttackInfo reverse() const;
+};

+ 137 - 630
lib/CBattleCallback.cpp → lib/battle/CBattleInfoCallback.cpp

@@ -1,14 +1,3 @@
-#include "StdInc.h"
-#include "CBattleCallback.h"
-#include "CStack.h"
-#include "BattleInfo.h"
-#include "CGameState.h"
-#include "NetPacks.h"
-#include "spells/CSpellHandler.h"
-#include "VCMI_Lib.h"
-#include "CTownHandler.h"
-#include "mapObjects/CGTownInstance.h"
-
 /*
  * CBattleCallback.cpp, part of VCMI engine
  *
@@ -18,330 +7,94 @@
  * Full text of license available in license.txt file, in main folder
  *
  */
+#include "StdInc.h"
+#include "CBattleInfoCallback.h"
+#include "CStack.h"
+#include "BattleInfo.h"
+#include "NetPacks.h"
+#include "spells/CSpellHandler.h"
+#include "mapObjects/CGTownInstance.h"
 
 #define RETURN_IF_NOT_BATTLE(X) if(!duringBattle()) {logGlobal->errorStream() << __FUNCTION__ << " called when no battle!"; return X; }
 
 namespace SiegeStuffThatShouldBeMovedToHandlers //  <=== TODO
 {
-	static void retreiveTurretDamageRange(const CGTownInstance * town, const CStack *turret, double &outMinDmg, double &outMaxDmg)
-	{
-		assert(turret->getCreature()->idNumber == CreatureID::ARROW_TOWERS);
-		assert(town);
-		assert(turret->position >= -4 && turret->position <= -2);
-
-		float multiplier = (turret->position == -2) ? 1 : 0.5;
-
-		int baseMin = 6;
-		int baseMax = 10;
-
-		outMinDmg = multiplier * (baseMin + town->getTownLevel() * 2);
-		outMaxDmg = multiplier * (baseMax + town->getTownLevel() * 3);
-	}
-
-	static BattleHex lineToWallHex(int line) //returns hex with wall in given line (y coordinate)
-	{
-		static const BattleHex lineToHex[] = {12, 29, 45, 62, 78, 95, 112, 130, 147, 165, 182};
-
-		return lineToHex[line];
-	}
-
-	static bool sameSideOfWall(BattleHex pos1, BattleHex pos2)
-	{
-		const int wallInStackLine = lineToWallHex(pos1.getY());
-		const int wallInDestLine = lineToWallHex(pos2.getY());
-
-		const bool stackLeft = pos1 < wallInStackLine;
-		const bool destLeft = pos2 < wallInDestLine;
-
-		return stackLeft == destLeft;
-	}
-
-	// parts of wall
-	static const std::pair<int, EWallPart::EWallPart> wallParts[] =
-	{
-		std::make_pair(50,  EWallPart::KEEP),
-		std::make_pair(183, EWallPart::BOTTOM_TOWER),
-		std::make_pair(182, EWallPart::BOTTOM_WALL),
-		std::make_pair(130, EWallPart::BELOW_GATE),
-		std::make_pair(78,  EWallPart::OVER_GATE),
-		std::make_pair(29,  EWallPart::UPPER_WALL),
-		std::make_pair(12,  EWallPart::UPPER_TOWER),
-		std::make_pair(95,  EWallPart::INDESTRUCTIBLE_PART_OF_GATE),
-		std::make_pair(96,  EWallPart::GATE),
-		std::make_pair(45,  EWallPart::INDESTRUCTIBLE_PART),
-		std::make_pair(62,  EWallPart::INDESTRUCTIBLE_PART),
-		std::make_pair(112, EWallPart::INDESTRUCTIBLE_PART),
-		std::make_pair(147, EWallPart::INDESTRUCTIBLE_PART),
-		std::make_pair(165, EWallPart::INDESTRUCTIBLE_PART)
-	};
-
-	static EWallPart::EWallPart hexToWallPart(BattleHex hex)
-	{
-		for(auto & elem : wallParts)
-		{
-			if(elem.first == hex)
-				return elem.second;
-		}
-
-		return EWallPart::INVALID; //not found!
-	}
-
-	static BattleHex WallPartToHex(EWallPart::EWallPart part)
-	{
-		for(auto & elem : wallParts)
-		{
-			if(elem.second == part)
-				return elem.first;
-		}
-
-		return BattleHex::INVALID; //not found!
-	}
-}
-
-using namespace SiegeStuffThatShouldBeMovedToHandlers;
-
-bool CCallbackBase::duringBattle() const
-{
-	return getBattle() != nullptr;
-}
-
-void CCallbackBase::setBattle(const BattleInfo *B)
-{
-	battle = B;
-}
-
-boost::optional<PlayerColor> CCallbackBase::getPlayerID() const
-{
-	return player;
-}
-
-ETerrainType CBattleInfoEssentials::battleTerrainType() const
-{
-	RETURN_IF_NOT_BATTLE(ETerrainType::WRONG);
-	return getBattle()->terrainType;
-}
-
-BFieldType CBattleInfoEssentials::battleGetBattlefieldType() const
-{
-	RETURN_IF_NOT_BATTLE(BFieldType::NONE);
-	return getBattle()->battlefieldType;
-}
-
-std::vector<std::shared_ptr<const CObstacleInstance> > CBattleInfoEssentials::battleGetAllObstacles(boost::optional<BattlePerspective::BattlePerspective> perspective /*= boost::none*/) const
-{
-	std::vector<std::shared_ptr<const CObstacleInstance> > ret;
-	RETURN_IF_NOT_BATTLE(ret);
-
-	if(!perspective)
-	{
-		//if no particular perspective request, use default one
-		perspective = battleGetMySide();
-	}
-	else
-	{
-		if(!!player && *perspective != battleGetMySide())
-		{
-			logGlobal->errorStream() << "Unauthorized access attempt!";
-			assert(0); //I want to notice if that happens
-			//perspective = battleGetMySide();
-		}
-	}
-
-	for(auto oi : getBattle()->obstacles)
-	{
-		if(getBattle()->battleIsObstacleVisibleForSide(*oi, *perspective))
-			ret.push_back(oi);
-	}
-
-	return ret;
-}
-
-bool CBattleInfoEssentials::battleIsObstacleVisibleForSide(const CObstacleInstance & coi, BattlePerspective::BattlePerspective side) const
-{
-	RETURN_IF_NOT_BATTLE(false);
-	return side == BattlePerspective::ALL_KNOWING || coi.visibleForSide(side, battleHasNativeStack(side));
-}
-
-bool CBattleInfoEssentials::battleHasNativeStack(ui8 side) const
-{
-	RETURN_IF_NOT_BATTLE(false);
-
-	for(const CStack *s : battleGetAllStacks())
-	{
-		if(s->attackerOwned == !side  &&  s->getCreature()->isItNativeTerrain(getBattle()->terrainType))
-			return true;
-	}
-
-	return false;
-}
-
-TStacks CBattleInfoEssentials::battleGetAllStacks(bool includeTurrets /*= false*/) const
+static void retreiveTurretDamageRange(const CGTownInstance * town, const CStack *turret, double &outMinDmg, double &outMaxDmg)
 {
-	return battleGetStacksIf([=](const CStack * s)
-	{
-		return !s->isGhost() && (includeTurrets || !s->isTurret());
-	});
-}
-
-TStacks CBattleInfoEssentials::battleGetStacksIf(TStackFilter predicate) const
-{
-	TStacks ret;
-	RETURN_IF_NOT_BATTLE(ret);
+	assert(turret->getCreature()->idNumber == CreatureID::ARROW_TOWERS);
+	assert(town);
+	assert(turret->position >= -4 && turret->position <= -2);
 
-	vstd::copy_if(getBattle()->stacks, std::back_inserter(ret), predicate);
-
-	return ret;
-}
-
-TStacks CBattleInfoEssentials::battleAliveStacks() const
-{
-	return battleGetStacksIf([](const CStack * s){
-		return s->isValidTarget(false);
-	});
-}
-
-TStacks CBattleInfoEssentials::battleAliveStacks(ui8 side) const
-{
-	return battleGetStacksIf([=](const CStack * s){
-		return s->isValidTarget(false) && s->attackerOwned == !side;
-	});
-}
-
-int CBattleInfoEssentials::battleGetMoatDmg() const
-{
-	RETURN_IF_NOT_BATTLE(0);
-
-	auto town = getBattle()->town;
-	if(!town)
-		return 0;
-
-	return town->town->moatDamage;
-}
-
-const CGTownInstance * CBattleInfoEssentials::battleGetDefendedTown() const
-{
-	RETURN_IF_NOT_BATTLE(nullptr);
-
-
-	if(!getBattle() || getBattle()->town == nullptr)
-		return nullptr;
-
-	return getBattle()->town;
-}
+	float multiplier = (turret->position == -2) ? 1 : 0.5;
 
-BattlePerspective::BattlePerspective CBattleInfoEssentials::battleGetMySide() const
-{
-	RETURN_IF_NOT_BATTLE(BattlePerspective::INVALID);
-	if(!player || player.get().isSpectator())
-		return BattlePerspective::ALL_KNOWING;
-	if(*player == getBattle()->sides[0].color)
-		return BattlePerspective::LEFT_SIDE;
-	if(*player == getBattle()->sides[1].color)
-		return BattlePerspective::RIGHT_SIDE;
-
-	logGlobal->errorStream() << "Cannot find player " << *player << " in battle!";
-	return BattlePerspective::INVALID;
-}
+	int baseMin = 6;
+	int baseMax = 10;
 
-const CStack * CBattleInfoEssentials::battleActiveStack() const
-{
-	RETURN_IF_NOT_BATTLE(nullptr);
-	return battleGetStackByID(getBattle()->activeStack);
+	outMinDmg = multiplier * (baseMin + town->getTownLevel() * 2);
+	outMaxDmg = multiplier * (baseMax + town->getTownLevel() * 3);
 }
 
-const CStack* CBattleInfoEssentials::battleGetStackByID(int ID, bool onlyAlive) const
+static BattleHex lineToWallHex(int line) //returns hex with wall in given line (y coordinate)
 {
-	RETURN_IF_NOT_BATTLE(nullptr);
+	static const BattleHex lineToHex[] = {12, 29, 45, 62, 78, 95, 112, 130, 147, 165, 182};
 
-	auto stacks = battleGetStacksIf([=](const CStack * s)
-	{
-		return s->ID == ID && (!onlyAlive || s->alive());
-	});
-
-	if(stacks.empty())
-		return nullptr;
-	else
-		return stacks[0];
+	return lineToHex[line];
 }
 
-bool CBattleInfoEssentials::battleDoWeKnowAbout(ui8 side) const
+static bool sameSideOfWall(BattleHex pos1, BattleHex pos2)
 {
-	RETURN_IF_NOT_BATTLE(false);
-	auto p = battleGetMySide();
-	return p == BattlePerspective::ALL_KNOWING  ||  p == side;
-}
+	const int wallInStackLine = lineToWallHex(pos1.getY());
+	const int wallInDestLine = lineToWallHex(pos2.getY());
 
-si8 CBattleInfoEssentials::battleTacticDist() const
-{
-	RETURN_IF_NOT_BATTLE(0);
-	return getBattle()->tacticDistance;
-}
+	const bool stackLeft = pos1 < wallInStackLine;
+	const bool destLeft = pos2 < wallInDestLine;
 
-si8 CBattleInfoEssentials::battleGetTacticsSide() const
-{
-	RETURN_IF_NOT_BATTLE(-1);
-	return getBattle()->tacticsSide;
+	return stackLeft == destLeft;
 }
 
-const CGHeroInstance * CBattleInfoEssentials::battleGetFightingHero(ui8 side) const
+// parts of wall
+static const std::pair<int, EWallPart::EWallPart> wallParts[] =
 {
-	RETURN_IF_NOT_BATTLE(nullptr);
-	if(side > 1)
-	{
-		logGlobal->errorStream() << "FIXME: " <<  __FUNCTION__ << " wrong argument!";
-		return nullptr;
-	}
+	std::make_pair(50,  EWallPart::KEEP),
+	std::make_pair(183, EWallPart::BOTTOM_TOWER),
+	std::make_pair(182, EWallPart::BOTTOM_WALL),
+	std::make_pair(130, EWallPart::BELOW_GATE),
+	std::make_pair(78,  EWallPart::OVER_GATE),
+	std::make_pair(29,  EWallPart::UPPER_WALL),
+	std::make_pair(12,  EWallPart::UPPER_TOWER),
+	std::make_pair(95,  EWallPart::INDESTRUCTIBLE_PART_OF_GATE),
+	std::make_pair(96,  EWallPart::GATE),
+	std::make_pair(45,  EWallPart::INDESTRUCTIBLE_PART),
+	std::make_pair(62,  EWallPart::INDESTRUCTIBLE_PART),
+	std::make_pair(112, EWallPart::INDESTRUCTIBLE_PART),
+	std::make_pair(147, EWallPart::INDESTRUCTIBLE_PART),
+	std::make_pair(165, EWallPart::INDESTRUCTIBLE_PART)
+};
 
-	if(!battleDoWeKnowAbout(side))
-	{
-		logGlobal->errorStream() << "FIXME: " <<  __FUNCTION__ << " access check ";
-		return nullptr;
-	}
-
-	return getBattle()->sides[side].hero;
-}
-
-const CArmedInstance * CBattleInfoEssentials::battleGetArmyObject(ui8 side) const
+static EWallPart::EWallPart hexToWallPart(BattleHex hex)
 {
-	RETURN_IF_NOT_BATTLE(nullptr);
-	if(side > 1)
+	for(auto & elem : wallParts)
 	{
-		logGlobal->errorStream() << "FIXME: " <<  __FUNCTION__ << " wrong argument!";
-		return nullptr;
+		if(elem.first == hex)
+			return elem.second;
 	}
 
-	if(!battleDoWeKnowAbout(side))
-	{
-		logGlobal->errorStream() << "FIXME: " <<  __FUNCTION__ << " access check ";
-		return nullptr;
-	}
-
-	return getBattle()->sides[side].armyObject;
+	return EWallPart::INVALID; //not found!
 }
 
-InfoAboutHero CBattleInfoEssentials::battleGetHeroInfo( ui8 side ) const
+static BattleHex WallPartToHex(EWallPart::EWallPart part)
 {
-	auto hero = getBattle()->sides[side].hero;
-	if(!hero)
+	for(auto & elem : wallParts)
 	{
-		logGlobal->warnStream() << __FUNCTION__ << ": side " << (int)side << " does not have hero!";
-		return InfoAboutHero();
+		if(elem.second == part)
+			return elem.first;
 	}
 
-	InfoAboutHero::EInfoLevel infoLevel = battleDoWeKnowAbout(side) ? InfoAboutHero::EInfoLevel::DETAILED : InfoAboutHero::EInfoLevel::BASIC;
-	return InfoAboutHero(hero, infoLevel);
+	return BattleHex::INVALID; //not found!
 }
-
-int CBattleInfoEssentials::battleCastSpells(ui8 side) const
-{
-	RETURN_IF_NOT_BATTLE(-1);
-	return getBattle()->sides[side].castSpellsCount;
 }
 
-const IBonusBearer * CBattleInfoEssentials::getBattleNode() const
-{
-	return getBattle();
-}
+using namespace SiegeStuffThatShouldBeMovedToHandlers;
 
 ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const
 {
@@ -367,17 +120,17 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(con
 	switch (mode)
 	{
 	case ECastingMode::HERO_CASTING:
-		{
-			if(battleCastSpells(side) > 0)
-				return ESpellCastProblem::ALREADY_CASTED_THIS_TURN;
+	{
+		if(battleCastSpells(side) > 0)
+			return ESpellCastProblem::ALREADY_CASTED_THIS_TURN;
 
-			auto hero = dynamic_cast<const CGHeroInstance *>(caster);
+		auto hero = dynamic_cast<const CGHeroInstance *>(caster);
 
-			if(!hero)
-				return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
-			if(hero->hasBonusOfType(Bonus::BLOCK_ALL_MAGIC))
-				return ESpellCastProblem::MAGIC_IS_BLOCKED;
-		}
+		if(!hero)
+			return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
+		if(hero->hasBonusOfType(Bonus::BLOCK_ALL_MAGIC))
+			return ESpellCastProblem::MAGIC_IS_BLOCKED;
+	}
 		break;
 	default:
 		break;
@@ -386,120 +139,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(con
 	return ESpellCastProblem::OK;
 }
 
-bool CBattleInfoEssentials::battleCanFlee(PlayerColor player) const
-{
-	RETURN_IF_NOT_BATTLE(false);
-	const si8 mySide = playerToSide(player);
-	const CGHeroInstance *myHero = battleGetFightingHero(mySide);
-
-	//current player have no hero
-	if(!myHero)
-		return false;
-
-	//eg. one of heroes is wearing shakles of war
-	if(myHero->hasBonusOfType(Bonus::BATTLE_NO_FLEEING))
-		return false;
-
-	//we are besieged defender
-	if(mySide == BattleSide::DEFENDER  &&  battleGetSiegeLevel())
-	{
-		auto town = battleGetDefendedTown();
-		if(!town->hasBuilt(BuildingID::ESCAPE_TUNNEL, ETownType::STRONGHOLD))
-			return false;
-	}
-
-	return true;
-}
-
-si8 CBattleInfoEssentials::playerToSide(PlayerColor player) const
-{
-	RETURN_IF_NOT_BATTLE(-1);
-	int ret = vstd::find_pos_if(getBattle()->sides, [=](const SideInBattle &side){ return side.color == player; });
-	if(ret < 0)
-		logGlobal->warnStream() << "Cannot find side for player " << player;
-
-	return ret;
-}
-
-bool CBattleInfoEssentials::playerHasAccessToHeroInfo(PlayerColor player, const CGHeroInstance * h) const
-{
-	RETURN_IF_NOT_BATTLE(false);
-	const si8 playerSide = playerToSide(player);
-	if (playerSide >= 0)
-	{
-		if (getBattle()->sides[!playerSide].hero == h)
-			return true;
-	}
-	return false;
-}
-
-ui8 CBattleInfoEssentials::battleGetSiegeLevel() const
-{
-	RETURN_IF_NOT_BATTLE(0);
-	return getBattle()->town ? getBattle()->town->fortLevel() : CGTownInstance::NONE;
-}
-
-bool CBattleInfoEssentials::battleCanSurrender(PlayerColor player) const
-{
-	RETURN_IF_NOT_BATTLE(false);
-	ui8 mySide = playerToSide(player);
-	bool iAmSiegeDefender = ( mySide == BattleSide::DEFENDER  &&  battleGetSiegeLevel() );
-	//conditions like for fleeing (except escape tunnel presence) + enemy must have a hero
-	return battleCanFlee(player) && !iAmSiegeDefender && battleHasHero(!mySide);
-}
-
-bool CBattleInfoEssentials::battleHasHero(ui8 side) const
-{
-	RETURN_IF_NOT_BATTLE(false);
-	assert(side < 2);
-	return getBattle()->sides[side].hero;
-}
-
-si8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const
-{
-	RETURN_IF_NOT_BATTLE(0);
-	if(getBattle()->town == nullptr || getBattle()->town->fortLevel() == CGTownInstance::NONE)
-		return EWallState::NONE;
-
-	assert(partOfWall >= 0 && partOfWall < EWallPart::PARTS_COUNT);
-	return getBattle()->si.wallState[partOfWall];
-}
-
-EGateState CBattleInfoEssentials::battleGetGateState() const
-{
-	RETURN_IF_NOT_BATTLE(EGateState::NONE);
-	if(getBattle()->town == nullptr || getBattle()->town->fortLevel() == CGTownInstance::NONE)
-		return EGateState::NONE;
-
-	return getBattle()->si.gateState;
-}
-
-PlayerColor CBattleInfoEssentials::battleGetOwner(const CStack * stack) const
-{
-	RETURN_IF_NOT_BATTLE(PlayerColor::CANNOT_DETERMINE);
-	if(stack->hasBonusOfType(Bonus::HYPNOTIZED))
-		return getBattle()->theOtherPlayer(stack->owner);
-	else
-		return stack->owner;
-}
-
-const CGHeroInstance * CBattleInfoEssentials::battleGetOwnerHero(const CStack * stack) const
-{
-	RETURN_IF_NOT_BATTLE(nullptr);
-	return getBattle()->sides.at(playerToSide(battleGetOwner(stack))).hero;
-}
-
-bool CBattleInfoEssentials::battleMatchOwner(const CStack * attacker, const CStack * defender, const boost::logic::tribool positivness /* = false*/) const
-{
-	RETURN_IF_NOT_BATTLE(false);
-	if(boost::logic::indeterminate(positivness))
-		return true;
-	else if(defender->owner != battleGetOwner(defender))
-		return true;//mind controlled unit is attackable for both sides
-	else
-		return (battleGetOwner(attacker) == battleGetOwner(defender)) == positivness;
-}
-
 si8 CBattleInfoCallback::battleHasWallPenalty( const CStack * stack, BattleHex destHex ) const
 {
 	return battleHasWallPenalty(stack, stack->position, destHex);
@@ -591,7 +230,7 @@ const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyA
 	RETURN_IF_NOT_BATTLE(nullptr);
 	for(auto s : battleGetAllStacks(true))
 		if(vstd::contains(s->getHexes(), pos) && (!onlyAlive || s->alive()))
-				return s;
+			return s;
 
 	return nullptr;
 }
@@ -605,7 +244,7 @@ void CBattleInfoCallback::battleGetStackQueue(std::vector<const CStack *> &out,
 	{
 		const CStack *ret = nullptr;
 		unsigned i, //fastest stack
-			j=0; //fastest stack of the other side
+				j=0; //fastest stack of the other side
 		for(i = 0; i < st.size(); i++)
 			if(st[i])
 				break;
@@ -683,8 +322,8 @@ void CBattleInfoCallback::battleGetStackQueue(std::vector<const CStack *> &out,
 	for(auto s : battleGetAllStacks(true))
 	{
 		if((turn <= 0 && !s->willMove()) //we are considering current round and stack won't move
-			|| (turn > 0 && !s->canMove(turn)) //stack won't be able to move in later rounds
-			|| (turn <= 0 && s == active && out.size() && s == out.front())) //it's active stack already added at the beginning of queue
+		   || (turn > 0 && !s->canMove(turn)) //stack won't be able to move in later rounds
+		   || (turn <= 0 && s == active && out.size() && s == out.front())) //it's active stack already added at the beginning of queue
 		{
 			continue;
 		}
@@ -811,7 +450,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const CStack
 			// Return true if given hex has at least one available neighbour.
 			// Available hexes are already present in ret vector.
 			auto availableNeighbor = boost::find_if(ret, [=] (BattleHex availableHex)
-				{  return BattleHex::mutualPosition(hex, availableHex) >= 0;  });
+			{  return BattleHex::mutualPosition(hex, availableHex) >= 0;  });
 
 			return availableNeighbor != ret.end();
 		};
@@ -893,11 +532,11 @@ bool CBattleInfoCallback::battleCanShoot(const CStack * stack, BattleHex dest) c
 		return false;
 
 	if(stack->hasBonusOfType(Bonus::SHOOTER)//it's shooter
-		&& battleMatchOwner(stack, dst)
-		&& dst->alive()
-		&& (!battleIsStackBlocked(stack)  ||  stack->hasBonusOfType(Bonus::FREE_SHOOTING))
-		&& stack->shots
-		)
+	   && battleMatchOwner(stack, dst)
+	   && dst->alive()
+	   && (!battleIsStackBlocked(stack)  ||  stack->hasBonusOfType(Bonus::FREE_SHOOTING))
+	   && stack->shots
+	   )
 		return true;
 	return false;
 }
@@ -914,19 +553,19 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 	{
 		auto noLimit = Selector::effectRange(Bonus::NO_LIMIT);
 		auto limitMatches = info.shooting
-				? Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT)
-				: Selector::effectRange(Bonus::ONLY_MELEE_FIGHT);
+							? Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT)
+							: Selector::effectRange(Bonus::ONLY_MELEE_FIGHT);
 
 		//any regular bonuses or just ones for melee/ranged
 		return bearer->getBonuses(selector, noLimit.Or(limitMatches))->totalValue();
 	};
 
 	double additiveBonus = 1.0, multBonus = 1.0,
-		minDmg = info.attackerBonuses->getMinDamage() * info.attackerCount,//TODO: ONLY_MELEE_FIGHT / ONLY_DISTANCE_FIGHT
-		maxDmg = info.attackerBonuses->getMaxDamage() * info.attackerCount;
+			minDmg = info.attackerBonuses->getMinDamage() * info.attackerCount,//TODO: ONLY_MELEE_FIGHT / ONLY_DISTANCE_FIGHT
+			maxDmg = info.attackerBonuses->getMaxDamage() * info.attackerCount;
 
 	const CCreature *attackerType = info.attacker->getCreature(),
-		*defenderType = info.defender->getCreature();
+			*defenderType = info.defender->getCreature();
 
 	if(attackerType->idNumber == CreatureID::ARROW_TOWERS)
 	{
@@ -965,8 +604,8 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 			for(const std::shared_ptr<Bonus> b : VLC->creh->creatures[g]->getBonusList())
 			{
 				if ( (b->type == Bonus::KING3 && spLevel >= 3) || //expert
-					(b->type == Bonus::KING2 && spLevel >= 2) || //adv +
-					(b->type == Bonus::KING1 && spLevel >= 0) ) //none or basic +
+					 (b->type == Bonus::KING2 && spLevel >= 2) || //adv +
+					 (b->type == Bonus::KING1 && spLevel >= 0) ) //none or basic +
 				{
 					affectedIds.push_back(g);
 					break;
@@ -1078,8 +717,8 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 	auto isAdvancedAirShield = [](const Bonus* bonus)
 	{
 		return bonus->source == Bonus::SPELL_EFFECT
-			&& bonus->sid == SpellID::AIR_SHIELD
-			&& bonus->val >= SecSkillLevel::ADVANCED;
+				&& bonus->sid == SpellID::AIR_SHIELD
+				&& bonus->val >= SecSkillLevel::ADVANCED;
 	};
 
 	//wall / distance penalty + advanced air shield
@@ -1140,7 +779,7 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 }
 
 TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const CStack* defender, TQuantity attackerCount,
-	bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg ) const
+												  bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg ) const
 {
 	BattleAttackInfo bai(attacker, defender, shooting);
 	bai.attackerCount = attackerCount;
@@ -1201,7 +840,7 @@ std::shared_ptr<const CObstacleInstance> CBattleInfoCallback::battleGetObstacleO
 	for(auto &obs : battleGetAllObstacles())
 	{
 		if(vstd::contains(obs->getBlockedTiles(), tile)
-			|| (!onlyBlocking  &&  vstd::contains(obs->getAffectedTiles(), tile)))
+		   || (!onlyBlocking  &&  vstd::contains(obs->getAffectedTiles(), tile)))
 		{
 			return obs;
 		}
@@ -1423,7 +1062,7 @@ bool CBattleInfoCallback::isInTacticRange(BattleHex dest) const
 	auto dist = battleGetTacticDist();
 
 	return ((!side && dest.getX() > 0 && dest.getX() <= dist)
-		|| (side && dest.getX() < GameConstants::BFIELD_WIDTH - 1 && dest.getX() >= GameConstants::BFIELD_WIDTH - dist - 1));
+			|| (side && dest.getX() < GameConstants::BFIELD_WIDTH - 1 && dest.getX() >= GameConstants::BFIELD_WIDTH - dist - 1));
 }
 
 ReachabilityInfo CBattleInfoCallback::getReachability(const CStack *stack) const
@@ -1680,7 +1319,7 @@ bool CBattleInfoCallback::isWallPartPotentiallyAttackable(EWallPart::EWallPart w
 {
 	RETURN_IF_NOT_BATTLE(false);
 	return wallPart != EWallPart::INDESTRUCTIBLE_PART && wallPart != EWallPart::INDESTRUCTIBLE_PART_OF_GATE &&
-			wallPart != EWallPart::INVALID;
+																	 wallPart != EWallPart::INVALID;
 }
 
 std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
@@ -1737,8 +1376,13 @@ const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)
 	auto stacks = battleGetAllStacks();
 	auto stackItr = range::find_if(stacks, pred);
 	return stackItr == stacks.end()
-		? nullptr
-		: *stackItr;
+			? nullptr
+			: *stackItr;
+}
+
+si8 CBattleInfoCallback::battleHasShootingPenalty(const CStack * stack, BattleHex destHex)
+{
+	return battleHasDistancePenalty(stack, destHex) || battleHasWallPenalty(stack, destHex);
 }
 
 bool CBattleInfoCallback::battleIsStackBlocked(const CStack * stack) const
@@ -1812,33 +1456,33 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
 		cachingStr << "source_" << Bonus::SPELL_EFFECT << "id_" << spellID.num;
 
 		if(subject->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellID), Selector::all, cachingStr.str())
-			//TODO: this ability has special limitations
-			|| spellID.toSpell()->canBeCastAt(this, subject, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
+		   //TODO: this ability has special limitations
+		   || spellID.toSpell()->canBeCastAt(this, subject, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
 			continue;
 
 		switch (spellID)
 		{
 		case SpellID::SHIELD:
 		case SpellID::FIRE_SHIELD: // not if all enemy units are shooters
+		{
+			auto walker = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
 			{
-				auto walker = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
-				{
-					return !stack->shots;
-				});
+				return !stack->shots;
+			});
 
-				if (!walker)
-					continue;
-			}
+			if (!walker)
+				continue;
+		}
 			break;
 		case SpellID::AIR_SHIELD: //only against active shooters
+		{
+			auto shooter = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
 			{
-				auto shooter = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
-				{
-					return stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots;
-				});
-				if (!shooter)
-					continue;
-			}
+				return stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots;
+			});
+			if (!shooter)
+				continue;
+		}
 			break;
 		case SpellID::ANTI_MAGIC:
 		case SpellID::MAGIC_MIRROR:
@@ -1846,46 +1490,46 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
 		case SpellID::PROTECTION_FROM_EARTH:
 		case SpellID::PROTECTION_FROM_FIRE:
 		case SpellID::PROTECTION_FROM_WATER:
-			{
-				const ui8 enemySide = (ui8)subject->attackerOwned;
-				//todo: only if enemy has spellbook
-				if (!battleHasHero(enemySide)) //only if there is enemy hero
-					continue;
-			}
+		{
+			const ui8 enemySide = (ui8)subject->attackerOwned;
+			//todo: only if enemy has spellbook
+			if (!battleHasHero(enemySide)) //only if there is enemy hero
+				continue;
+		}
 			break;
 		case SpellID::CURE: //only damaged units
-			{
-				//do not cast on affected by debuffs
-				if (subject->firstHPleft >= subject->MaxHealth())
-					continue;
-			}
+		{
+			//do not cast on affected by debuffs
+			if (subject->firstHPleft >= subject->MaxHealth())
+				continue;
+		}
 			break;
 		case SpellID::BLOODLUST:
-			{
-				if (subject->shots) //if can shoot - only if enemy uits are adjacent
-					continue;
-			}
+		{
+			if (subject->shots) //if can shoot - only if enemy uits are adjacent
+				continue;
+		}
 			break;
 		case SpellID::PRECISION:
-			{
-				if (!(subject->hasBonusOfType(Bonus::SHOOTER) && subject->shots))
-					continue;
-			}
+		{
+			if (!(subject->hasBonusOfType(Bonus::SHOOTER) && subject->shots))
+				continue;
+		}
 			break;
 		case SpellID::SLAYER://only if monsters are present
+		{
+			auto kingMonster = getAliveEnemy([&](const CStack *stack) -> bool //look for enemy, non-shooting stack
 			{
-				auto kingMonster = getAliveEnemy([&](const CStack *stack) -> bool //look for enemy, non-shooting stack
-				{
-					const auto isKing = Selector::type(Bonus::KING1)
-						.Or(Selector::type(Bonus::KING2))
-						.Or(Selector::type(Bonus::KING3));
+				const auto isKing = Selector::type(Bonus::KING1)
+									.Or(Selector::type(Bonus::KING2))
+									.Or(Selector::type(Bonus::KING3));
 
-					return stack->hasBonus(isKing);
-				});
+				return stack->hasBonus(isKing);
+			});
 
-				if (!kingMonster)
-					continue;
-			}
+			if (!kingMonster)
+				continue;
+		}
 			break;
 		}
 		beneficialSpells.push_back(spellID);
@@ -1993,140 +1637,3 @@ boost::optional<int> CBattleInfoCallback::battleIsFinished() const
 		return 1;
 	return boost::none;
 }
-
-bool AccessibilityInfo::accessible(BattleHex tile, const CStack *stack) const
-{
-	return accessible(tile, stack->doubleWide(), stack->attackerOwned);
-}
-
-bool AccessibilityInfo::accessible(BattleHex tile, bool doubleWide, bool attackerOwned) const
-{
-	// All hexes that stack would cover if standing on tile have to be accessible.
-	for(auto hex : CStack::getHexes(tile, doubleWide, attackerOwned))
-	{
-		// If the hex is out of range then the tile isn't accessible
-		if(!hex.isValid())
-			return false;
-		// If we're no defender which step on gate and the hex isn't accessible, then the tile
-		// isn't accessible
-		else if(at(hex) != EAccessibility::ACCESSIBLE &&
-			!(at(hex) == EAccessibility::GATE && !attackerOwned))
-		{
-			return false;
-		}
-	}
-	return true;
-}
-
-bool AccessibilityInfo::occupiable(const CStack *stack, BattleHex tile) const
-{
-	//obviously, we can occupy tile by standing on it
-	if(accessible(tile, stack))
-		return true;
-
-	if(stack->doubleWide())
-	{
-		//Check the tile next to -> if stack stands there, it'll also occupy considered hex
-		const BattleHex anotherTile = tile + (stack->attackerOwned ? BattleHex::RIGHT : BattleHex::LEFT);
-		if(accessible(anotherTile, stack))
-			return true;
-	}
-
-	return false;
-}
-
-ReachabilityInfo::Parameters::Parameters()
-{
-	stack = nullptr;
-	perspective = BattlePerspective::ALL_KNOWING;
-	attackerOwned = doubleWide = flying = false;
-}
-
-ReachabilityInfo::Parameters::Parameters(const CStack *Stack)
-{
-	stack = Stack;
-	perspective = (BattlePerspective::BattlePerspective)(!Stack->attackerOwned);
-	startPosition = Stack->position;
-	doubleWide = stack->doubleWide();
-	attackerOwned = stack->attackerOwned;
-	flying = stack->hasBonusOfType(Bonus::FLYING);
-	knownAccessible = stack->getHexes();
-}
-
-bool CPlayerBattleCallback::battleCanFlee() const
-{
-	RETURN_IF_NOT_BATTLE(false);
-	ASSERT_IF_CALLED_WITH_PLAYER
-	return CBattleInfoEssentials::battleCanFlee(*player);
-}
-
-TStacks CPlayerBattleCallback::battleGetStacks(EStackOwnership whose /*= MINE_AND_ENEMY*/, bool onlyAlive /*= true*/) const
-{
-	if(whose != MINE_AND_ENEMY)
-	{
-		ASSERT_IF_CALLED_WITH_PLAYER
-	}
-
-	return battleGetStacksIf([=](const CStack * s){
-		const bool ownerMatches = (whose == MINE_AND_ENEMY)
-			|| (whose == ONLY_MINE && s->owner == player)
-			|| (whose == ONLY_ENEMY && s->owner != player);
-
-		return ownerMatches && s->isValidTarget(!onlyAlive);
-	});
-}
-
-int CPlayerBattleCallback::battleGetSurrenderCost() const
-{
-	RETURN_IF_NOT_BATTLE(-3)
-	ASSERT_IF_CALLED_WITH_PLAYER
-	return CBattleInfoCallback::battleGetSurrenderCost(*player);
-}
-
-const CGHeroInstance * CPlayerBattleCallback::battleGetMyHero() const
-{
-	return CBattleInfoEssentials::battleGetFightingHero(battleGetMySide());
-}
-
-InfoAboutHero CPlayerBattleCallback::battleGetEnemyHero() const
-{
-	return battleGetHeroInfo(!battleGetMySide());
-}
-
-BattleAttackInfo::BattleAttackInfo(const CStack *Attacker, const CStack *Defender, bool Shooting)
-{
-	attacker = Attacker;
-	defender = Defender;
-
-	attackerBonuses = Attacker;
-	defenderBonuses = Defender;
-
-	attackerPosition = Attacker->position;
-	defenderPosition = Defender->position;
-
-	attackerCount = Attacker->count;
-	defenderCount = Defender->count;
-
-	shooting = Shooting;
-	chargedFields = 0;
-
-	luckyHit = false;
-	unluckyHit = false;
-	deathBlow = false;
-	ballistaDoubleDamage = false;
-}
-
-BattleAttackInfo BattleAttackInfo::reverse() const
-{
-	BattleAttackInfo ret = *this;
-	std::swap(ret.attacker, ret.defender);
-	std::swap(ret.attackerBonuses, ret.defenderBonuses);
-	std::swap(ret.attackerPosition, ret.defenderPosition);
-	std::swap(ret.attackerCount, ret.defenderCount);
-
-	ret.shooting = false;
-	ret.chargedFields = 0;
-	ret.luckyHit = ret.ballistaDoubleDamage = ret.deathBlow = false;
-
-	return ret;
-}

+ 5 - 232
lib/CBattleCallback.h → lib/battle/CBattleInfoCallback.h

@@ -1,6 +1,3 @@
-#pragma once
-#include "BattleHex.h"
-
 /*
  * CBattleCallback.h, part of VCMI engine
  *
@@ -10,64 +7,19 @@
  * Full text of license available in license.txt file, in main folder
  *
  */
+#pragma once
+#include "CCallbackBase.h"
+#include "ReachabilityInfo.h"
+#include "BattleAttackInfo.h"
 
-class CGameState;
-class CGTownInstance;
 class CGHeroInstance;
 class CStack;
 class ISpellCaster;
 class CSpell;
-struct BattleInfo;
 struct CObstacleInstance;
 class IBonusBearer;
-struct InfoAboutHero;
-class CArmedInstance;
 class CRandomGenerator;
 
-namespace boost
-{class shared_mutex;}
-
-namespace BattleSide
-{
-	enum {ATTACKER = 0, DEFENDER = 1};
-}
-
-typedef std::vector<const CStack*> TStacks;
-typedef std::function<bool(const CStack *)> TStackFilter;
-
-class CBattleInfoEssentials;
-
-//Basic class for various callbacks (interfaces called by players to get info about game and so forth)
-class DLL_LINKAGE CCallbackBase
-{
-	const BattleInfo *battle; //battle to which the player is engaged, nullptr if none or not applicable
-
-	const BattleInfo * getBattle() const
-	{
-		return battle;
-	}
-
-protected:
-	CGameState *gs;
-	boost::optional<PlayerColor> player; // not set gives access to all information, otherwise callback provides only information "visible" for player
-
-	CCallbackBase(CGameState *GS, boost::optional<PlayerColor> Player)
-		: battle(nullptr), gs(GS), player(Player)
-	{}
-	CCallbackBase()
-		: battle(nullptr), gs(nullptr)
-	{}
-
-	void setBattle(const BattleInfo *B);
-	bool duringBattle() const;
-
-public:
-	boost::optional<PlayerColor> getPlayerID() const;
-
-	friend class CBattleInfoEssentials;
-};
-
-
 struct DLL_LINKAGE AttackableTiles
 {
 	std::set<BattleHex> hostileCreaturePositions;
@@ -78,170 +30,6 @@ struct DLL_LINKAGE AttackableTiles
 	}
 };
 
-//Accessibility is property of hex in battle. It doesn't depend on stack, side's perspective and so on.
-namespace EAccessibility
-{
-	enum EAccessibility
-	{
-		ACCESSIBLE,
-		ALIVE_STACK,
-		OBSTACLE,
-		DESTRUCTIBLE_WALL,
-		GATE, //sieges -> gate opens only for defender stacks
-		UNAVAILABLE, //indestructible wall parts, special battlefields (like boat-to-boat)
-		SIDE_COLUMN //used for first and last columns of hexes that are unavailable but wat machines can stand there
-	};
-}
-
-typedef std::array<EAccessibility::EAccessibility, GameConstants::BFIELD_SIZE> TAccessibilityArray;
-
-struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray
-{
-	bool occupiable(const CStack *stack, BattleHex tile) const;
-	bool accessible(BattleHex tile, const CStack *stack) const; //checks for both tiles if stack is double wide
-	bool accessible(BattleHex tile, bool doubleWide, bool attackerOwned) const; //checks for both tiles if stack is double wide
-};
-
-namespace BattlePerspective
-{
-	enum BattlePerspective
-	{
-		INVALID = -2,
-		ALL_KNOWING = -1,
-		LEFT_SIDE,
-		RIGHT_SIDE
-	};
-}
-
-// Reachability info is result of BFS calculation. It's dependent on stack (it's owner, whether it's flying),
-// startPosition and perpective.
-struct DLL_LINKAGE ReachabilityInfo
-{
-	typedef std::array<int, GameConstants::BFIELD_SIZE> TDistances;
-	typedef std::array<BattleHex, GameConstants::BFIELD_SIZE> TPredecessors;
-
-	enum { 	INFINITE_DIST = 1000000 };
-
-	struct DLL_LINKAGE Parameters
-	{
-		const CStack *stack; //stack for which calculation is mage => not required (kept for debugging mostly), following variables are enough
-
-		bool attackerOwned;
-		bool doubleWide;
-		bool flying;
-		std::vector<BattleHex> knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself)
-
-		BattleHex startPosition; //assumed position of stack
-		BattlePerspective::BattlePerspective perspective; //some obstacles (eg. quicksands) may be invisible for some side
-
-		Parameters();
-		Parameters(const CStack *Stack);
-	};
-
-	Parameters params;
-	AccessibilityInfo accessibility;
-	TDistances distances;
-	TPredecessors predecessors;
-
-	ReachabilityInfo()
-	{
-		distances.fill(INFINITE_DIST);
-		predecessors.fill(BattleHex::INVALID);
-	}
-
-	bool isReachable(BattleHex hex) const
-	{
-		return distances[hex] < INFINITE_DIST;
-	}
-};
-
-class DLL_LINKAGE CBattleInfoEssentials : public virtual CCallbackBase
-{
-protected:
-	bool battleDoWeKnowAbout(ui8 side) const;
-	const IBonusBearer * getBattleNode() const;
-public:
-	enum EStackOwnership
-	{
-		ONLY_MINE, ONLY_ENEMY, MINE_AND_ENEMY
-	};
-
-	BattlePerspective::BattlePerspective battleGetMySide() const;
-
-	ETerrainType battleTerrainType() const;
-	BFieldType battleGetBattlefieldType() const;
-	std::vector<std::shared_ptr<const CObstacleInstance> > battleGetAllObstacles(boost::optional<BattlePerspective::BattlePerspective> perspective = boost::none) const; //returns all obstacles on the battlefield
-
-    /** @brief Main method for getting battle stacks
-     *
-     * @param predicate Functor that shall return true for desired stack
-     * @return filtered stacks
-     *
-     */
-	TStacks battleGetStacksIf(TStackFilter predicate) const;
-
-	bool battleHasNativeStack(ui8 side) const;
-	int battleGetMoatDmg() const; //what dmg unit will suffer if ending turn in the moat
-	const CGTownInstance * battleGetDefendedTown() const; //returns defended town if current battle is a siege, nullptr instead
-	const CStack *battleActiveStack() const;
-	si8 battleTacticDist() const; //returns tactic distance in current tactics phase; 0 if not in tactics phase
-	si8 battleGetTacticsSide() const; //returns which side is in tactics phase, undefined if none (?)
-	bool battleCanFlee(PlayerColor player) const;
-	bool battleCanSurrender(PlayerColor player) const;
-	si8 playerToSide(PlayerColor player) const;
-	bool playerHasAccessToHeroInfo(PlayerColor player, const CGHeroInstance * h) const;
-	ui8 battleGetSiegeLevel() const; //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle
-	bool battleHasHero(ui8 side) const;
-	int battleCastSpells(ui8 side) const; //how many spells has given side cast
-	const CGHeroInstance * battleGetFightingHero(ui8 side) const; //depracated for players callback, easy to get wrong
-	const CArmedInstance * battleGetArmyObject(ui8 side) const;
-	InfoAboutHero battleGetHeroInfo(ui8 side) const;
-
-	// for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall,
-	// [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle
-	si8 battleGetWallState(int partOfWall) const;
-	EGateState battleGetGateState() const;
-
-	//helpers
-	///returns all stacks, alive or dead or undead or mechanical :)
-	TStacks battleGetAllStacks(bool includeTurrets = false) const;
-
-	///returns all alive stacks excluding turrets
-	TStacks battleAliveStacks() const;
-	///returns all alive stacks from particular side excluding turrets
-	TStacks battleAliveStacks(ui8 side) const;
-	const CStack * battleGetStackByID(int ID, bool onlyAlive = true) const; //returns stack info by given ID
-	bool battleIsObstacleVisibleForSide(const CObstacleInstance & coi, BattlePerspective::BattlePerspective side) const;
-
-	///returns player that controls given stack; mind control included
-	PlayerColor battleGetOwner(const CStack * stack) const;
-
-	///returns hero that controls given stack; nullptr if none; mind control included
-	const CGHeroInstance * battleGetOwnerHero(const CStack * stack) const;
-
-	///check that stacks are controlled by same|other player(s) depending on positiveness
-	///mind control included
-	bool battleMatchOwner(const CStack * attacker, const CStack * defender, const boost::logic::tribool positivness = false) const;
-};
-
-struct DLL_LINKAGE BattleAttackInfo
-{
-	const IBonusBearer *attackerBonuses, *defenderBonuses;
-	const CStack *attacker, *defender;
-	BattleHex attackerPosition, defenderPosition;
-
-	int attackerCount, defenderCount;
-	bool shooting;
-	int chargedFields;
-
-	bool luckyHit;
-	bool unluckyHit;
-	bool deathBlow;
-	bool ballistaDoubleDamage;
-
-	BattleAttackInfo(const CStack *Attacker, const CStack *Defender, bool Shooting = false);
-	BattleAttackInfo reverse() const;
-};
 
 class DLL_LINKAGE CBattleInfoCallback : public virtual CBattleInfoEssentials
 {
@@ -299,10 +87,7 @@ public:
 
 	const CStack * getStackIf(std::function<bool(const CStack*)> pred) const;
 
-	si8 battleHasShootingPenalty(const CStack * stack, BattleHex destHex)
-	{
-		return battleHasDistancePenalty(stack, destHex) || battleHasWallPenalty(stack, destHex);
-	}
+	si8 battleHasShootingPenalty(const CStack * stack, BattleHex destHex);
 	si8 battleCanTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel) const; //checks if teleportation of given stack to given position can take place
 
 
@@ -327,15 +112,3 @@ protected:
 	ReachabilityInfo makeBFS(const CStack *stack) const; //uses default parameters -> stack position and owner's perspective
 	std::set<BattleHex> getStoppers(BattlePerspective::BattlePerspective whichSidePerspective) const; //get hexes with stopping obstacles (quicksands)
 };
-
-class DLL_LINKAGE CPlayerBattleCallback : public CBattleInfoCallback
-{
-public:
-	bool battleCanFlee() const; //returns true if caller can flee from the battle
-	TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true) const; //returns stacks on battlefield
-
-	int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible
-
-	const CGHeroInstance * battleGetMyHero() const;
-	InfoAboutHero battleGetEnemyHero() const;
-};

+ 359 - 0
lib/battle/CBattleInfoEssentials.cpp

@@ -0,0 +1,359 @@
+/*
+ * CBattleInfoEssentials.cpp, 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
+ *
+ */
+#include "StdInc.h"
+#include "CBattleInfoEssentials.h"
+#include "CStack.h"
+#include "BattleInfo.h"
+#include "NetPacks.h"
+#include "mapObjects/CGTownInstance.h"
+
+#define RETURN_IF_NOT_BATTLE(X) if(!duringBattle()) {logGlobal->errorStream() << __FUNCTION__ << " called when no battle!"; return X; }
+
+ETerrainType CBattleInfoEssentials::battleTerrainType() const
+{
+	RETURN_IF_NOT_BATTLE(ETerrainType::WRONG);
+	return getBattle()->terrainType;
+}
+
+BFieldType CBattleInfoEssentials::battleGetBattlefieldType() const
+{
+	RETURN_IF_NOT_BATTLE(BFieldType::NONE);
+	return getBattle()->battlefieldType;
+}
+
+std::vector<std::shared_ptr<const CObstacleInstance> > CBattleInfoEssentials::battleGetAllObstacles(boost::optional<BattlePerspective::BattlePerspective> perspective /*= boost::none*/) const
+{
+	std::vector<std::shared_ptr<const CObstacleInstance> > ret;
+	RETURN_IF_NOT_BATTLE(ret);
+
+	if(!perspective)
+	{
+		//if no particular perspective request, use default one
+		perspective = battleGetMySide();
+	}
+	else
+	{
+		if(!!player && *perspective != battleGetMySide())
+		{
+			logGlobal->errorStream() << "Unauthorized access attempt!";
+			assert(0); //I want to notice if that happens
+			//perspective = battleGetMySide();
+		}
+	}
+
+	for(auto oi : getBattle()->obstacles)
+	{
+		if(getBattle()->battleIsObstacleVisibleForSide(*oi, *perspective))
+			ret.push_back(oi);
+	}
+
+	return ret;
+}
+
+bool CBattleInfoEssentials::battleIsObstacleVisibleForSide(const CObstacleInstance & coi, BattlePerspective::BattlePerspective side) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	return side == BattlePerspective::ALL_KNOWING || coi.visibleForSide(side, battleHasNativeStack(side));
+}
+
+bool CBattleInfoEssentials::battleHasNativeStack(ui8 side) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+
+	for(const CStack *s : battleGetAllStacks())
+	{
+		if(s->attackerOwned == !side  &&  s->getCreature()->isItNativeTerrain(getBattle()->terrainType))
+			return true;
+	}
+
+	return false;
+}
+
+TStacks CBattleInfoEssentials::battleGetAllStacks(bool includeTurrets /*= false*/) const
+{
+	return battleGetStacksIf([=](const CStack * s)
+	{
+		return !s->isGhost() && (includeTurrets || !s->isTurret());
+	});
+}
+
+TStacks CBattleInfoEssentials::battleGetStacksIf(TStackFilter predicate) const
+{
+	TStacks ret;
+	RETURN_IF_NOT_BATTLE(ret);
+
+	vstd::copy_if(getBattle()->stacks, std::back_inserter(ret), predicate);
+
+	return ret;
+}
+
+TStacks CBattleInfoEssentials::battleAliveStacks() const
+{
+	return battleGetStacksIf([](const CStack * s){
+		return s->isValidTarget(false);
+	});
+}
+
+TStacks CBattleInfoEssentials::battleAliveStacks(ui8 side) const
+{
+	return battleGetStacksIf([=](const CStack * s){
+		return s->isValidTarget(false) && s->attackerOwned == !side;
+	});
+}
+
+int CBattleInfoEssentials::battleGetMoatDmg() const
+{
+	RETURN_IF_NOT_BATTLE(0);
+
+	auto town = getBattle()->town;
+	if(!town)
+		return 0;
+
+	return town->town->moatDamage;
+}
+
+const CGTownInstance * CBattleInfoEssentials::battleGetDefendedTown() const
+{
+	RETURN_IF_NOT_BATTLE(nullptr);
+
+
+	if(!getBattle() || getBattle()->town == nullptr)
+		return nullptr;
+
+	return getBattle()->town;
+}
+
+BattlePerspective::BattlePerspective CBattleInfoEssentials::battleGetMySide() const
+{
+	RETURN_IF_NOT_BATTLE(BattlePerspective::INVALID);
+	if(!player || player.get().isSpectator())
+		return BattlePerspective::ALL_KNOWING;
+	if(*player == getBattle()->sides[0].color)
+		return BattlePerspective::LEFT_SIDE;
+	if(*player == getBattle()->sides[1].color)
+		return BattlePerspective::RIGHT_SIDE;
+
+	logGlobal->errorStream() << "Cannot find player " << *player << " in battle!";
+	return BattlePerspective::INVALID;
+}
+
+const CStack * CBattleInfoEssentials::battleActiveStack() const
+{
+	RETURN_IF_NOT_BATTLE(nullptr);
+	return battleGetStackByID(getBattle()->activeStack);
+}
+
+const CStack* CBattleInfoEssentials::battleGetStackByID(int ID, bool onlyAlive) const
+{
+	RETURN_IF_NOT_BATTLE(nullptr);
+
+	auto stacks = battleGetStacksIf([=](const CStack * s)
+	{
+		return s->ID == ID && (!onlyAlive || s->alive());
+	});
+
+	if(stacks.empty())
+		return nullptr;
+	else
+		return stacks[0];
+}
+
+bool CBattleInfoEssentials::battleDoWeKnowAbout(ui8 side) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	auto p = battleGetMySide();
+	return p == BattlePerspective::ALL_KNOWING  ||  p == side;
+}
+
+si8 CBattleInfoEssentials::battleTacticDist() const
+{
+	RETURN_IF_NOT_BATTLE(0);
+	return getBattle()->tacticDistance;
+}
+
+si8 CBattleInfoEssentials::battleGetTacticsSide() const
+{
+	RETURN_IF_NOT_BATTLE(-1);
+	return getBattle()->tacticsSide;
+}
+
+const CGHeroInstance * CBattleInfoEssentials::battleGetFightingHero(ui8 side) const
+{
+	RETURN_IF_NOT_BATTLE(nullptr);
+	if(side > 1)
+	{
+		logGlobal->errorStream() << "FIXME: " <<  __FUNCTION__ << " wrong argument!";
+		return nullptr;
+	}
+
+	if(!battleDoWeKnowAbout(side))
+	{
+		logGlobal->errorStream() << "FIXME: " <<  __FUNCTION__ << " access check ";
+		return nullptr;
+	}
+
+	return getBattle()->sides[side].hero;
+}
+
+const CArmedInstance * CBattleInfoEssentials::battleGetArmyObject(ui8 side) const
+{
+	RETURN_IF_NOT_BATTLE(nullptr);
+	if(side > 1)
+	{
+		logGlobal->errorStream() << "FIXME: " <<  __FUNCTION__ << " wrong argument!";
+		return nullptr;
+	}
+
+	if(!battleDoWeKnowAbout(side))
+	{
+		logGlobal->errorStream() << "FIXME: " <<  __FUNCTION__ << " access check ";
+		return nullptr;
+	}
+
+	return getBattle()->sides[side].armyObject;
+}
+
+InfoAboutHero CBattleInfoEssentials::battleGetHeroInfo( ui8 side ) const
+{
+	auto hero = getBattle()->sides[side].hero;
+	if(!hero)
+	{
+		logGlobal->warnStream() << __FUNCTION__ << ": side " << (int)side << " does not have hero!";
+		return InfoAboutHero();
+	}
+
+	InfoAboutHero::EInfoLevel infoLevel = battleDoWeKnowAbout(side) ? InfoAboutHero::EInfoLevel::DETAILED : InfoAboutHero::EInfoLevel::BASIC;
+	return InfoAboutHero(hero, infoLevel);
+}
+
+int CBattleInfoEssentials::battleCastSpells(ui8 side) const
+{
+	RETURN_IF_NOT_BATTLE(-1);
+	return getBattle()->sides[side].castSpellsCount;
+}
+
+const IBonusBearer * CBattleInfoEssentials::getBattleNode() const
+{
+	return getBattle();
+}
+
+bool CBattleInfoEssentials::battleCanFlee(PlayerColor player) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	const si8 mySide = playerToSide(player);
+	const CGHeroInstance *myHero = battleGetFightingHero(mySide);
+
+	//current player have no hero
+	if(!myHero)
+		return false;
+
+	//eg. one of heroes is wearing shakles of war
+	if(myHero->hasBonusOfType(Bonus::BATTLE_NO_FLEEING))
+		return false;
+
+	//we are besieged defender
+	if(mySide == BattleSide::DEFENDER  &&  battleGetSiegeLevel())
+	{
+		auto town = battleGetDefendedTown();
+		if(!town->hasBuilt(BuildingID::ESCAPE_TUNNEL, ETownType::STRONGHOLD))
+			return false;
+	}
+
+	return true;
+}
+
+si8 CBattleInfoEssentials::playerToSide(PlayerColor player) const
+{
+	RETURN_IF_NOT_BATTLE(-1);
+	int ret = vstd::find_pos_if(getBattle()->sides, [=](const SideInBattle &side){ return side.color == player; });
+	if(ret < 0)
+		logGlobal->warnStream() << "Cannot find side for player " << player;
+
+	return ret;
+}
+
+bool CBattleInfoEssentials::playerHasAccessToHeroInfo(PlayerColor player, const CGHeroInstance * h) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	const si8 playerSide = playerToSide(player);
+	if (playerSide >= 0)
+	{
+		if (getBattle()->sides[!playerSide].hero == h)
+			return true;
+	}
+	return false;
+}
+
+ui8 CBattleInfoEssentials::battleGetSiegeLevel() const
+{
+	RETURN_IF_NOT_BATTLE(0);
+	return getBattle()->town ? getBattle()->town->fortLevel() : CGTownInstance::NONE;
+}
+
+bool CBattleInfoEssentials::battleCanSurrender(PlayerColor player) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	ui8 mySide = playerToSide(player);
+	bool iAmSiegeDefender = ( mySide == BattleSide::DEFENDER  &&  battleGetSiegeLevel() );
+	//conditions like for fleeing (except escape tunnel presence) + enemy must have a hero
+	return battleCanFlee(player) && !iAmSiegeDefender && battleHasHero(!mySide);
+}
+
+bool CBattleInfoEssentials::battleHasHero(ui8 side) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	assert(side < 2);
+	return getBattle()->sides[side].hero;
+}
+
+si8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const
+{
+	RETURN_IF_NOT_BATTLE(0);
+	if(getBattle()->town == nullptr || getBattle()->town->fortLevel() == CGTownInstance::NONE)
+		return EWallState::NONE;
+
+	assert(partOfWall >= 0 && partOfWall < EWallPart::PARTS_COUNT);
+	return getBattle()->si.wallState[partOfWall];
+}
+
+EGateState CBattleInfoEssentials::battleGetGateState() const
+{
+	RETURN_IF_NOT_BATTLE(EGateState::NONE);
+	if(getBattle()->town == nullptr || getBattle()->town->fortLevel() == CGTownInstance::NONE)
+		return EGateState::NONE;
+
+	return getBattle()->si.gateState;
+}
+
+PlayerColor CBattleInfoEssentials::battleGetOwner(const CStack * stack) const
+{
+	RETURN_IF_NOT_BATTLE(PlayerColor::CANNOT_DETERMINE);
+	if(stack->hasBonusOfType(Bonus::HYPNOTIZED))
+		return getBattle()->theOtherPlayer(stack->owner);
+	else
+		return stack->owner;
+}
+
+const CGHeroInstance * CBattleInfoEssentials::battleGetOwnerHero(const CStack * stack) const
+{
+	RETURN_IF_NOT_BATTLE(nullptr);
+	return getBattle()->sides.at(playerToSide(battleGetOwner(stack))).hero;
+}
+
+bool CBattleInfoEssentials::battleMatchOwner(const CStack * attacker, const CStack * defender, const boost::logic::tribool positivness /* = false*/) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	if(boost::logic::indeterminate(positivness))
+		return true;
+	else if(defender->owner != battleGetOwner(defender))
+		return true;//mind controlled unit is attackable for both sides
+	else
+		return (battleGetOwner(attacker) == battleGetOwner(defender)) == positivness;
+}

+ 108 - 0
lib/battle/CBattleInfoEssentials.h

@@ -0,0 +1,108 @@
+/*
+ * CBattleInfoEssentials.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 "CCallbackBase.h"
+
+class CGTownInstance;
+class CGHeroInstance;
+class CStack;
+struct CObstacleInstance;
+class IBonusBearer;
+struct InfoAboutHero;
+class CArmedInstance;
+
+typedef std::vector<const CStack*> TStacks;
+typedef std::function<bool(const CStack *)> TStackFilter;
+
+namespace BattlePerspective
+{
+enum BattlePerspective
+{
+	INVALID = -2,
+	ALL_KNOWING = -1,
+	LEFT_SIDE,
+	RIGHT_SIDE
+};
+}
+
+namespace BattleSide
+{
+enum {ATTACKER = 0, DEFENDER = 1};
+}
+
+
+class DLL_LINKAGE CBattleInfoEssentials : public virtual CCallbackBase
+{
+protected:
+	bool battleDoWeKnowAbout(ui8 side) const;
+	const IBonusBearer * getBattleNode() const;
+public:
+	enum EStackOwnership
+	{
+		ONLY_MINE, ONLY_ENEMY, MINE_AND_ENEMY
+	};
+
+	BattlePerspective::BattlePerspective battleGetMySide() const;
+
+	ETerrainType battleTerrainType() const;
+	BFieldType battleGetBattlefieldType() const;
+	std::vector<std::shared_ptr<const CObstacleInstance> > battleGetAllObstacles(boost::optional<BattlePerspective::BattlePerspective> perspective = boost::none) const; //returns all obstacles on the battlefield
+
+	/** @brief Main method for getting battle stacks
+	 *
+	 * @param predicate Functor that shall return true for desired stack
+	 * @return filtered stacks
+	 *
+	 */
+	TStacks battleGetStacksIf(TStackFilter predicate) const;
+
+	bool battleHasNativeStack(ui8 side) const;
+	int battleGetMoatDmg() const; //what dmg unit will suffer if ending turn in the moat
+	const CGTownInstance * battleGetDefendedTown() const; //returns defended town if current battle is a siege, nullptr instead
+	const CStack *battleActiveStack() const;
+	si8 battleTacticDist() const; //returns tactic distance in current tactics phase; 0 if not in tactics phase
+	si8 battleGetTacticsSide() const; //returns which side is in tactics phase, undefined if none (?)
+	bool battleCanFlee(PlayerColor player) const;
+	bool battleCanSurrender(PlayerColor player) const;
+	si8 playerToSide(PlayerColor player) const;
+	bool playerHasAccessToHeroInfo(PlayerColor player, const CGHeroInstance * h) const;
+	ui8 battleGetSiegeLevel() const; //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle
+	bool battleHasHero(ui8 side) const;
+	int battleCastSpells(ui8 side) const; //how many spells has given side cast
+	const CGHeroInstance * battleGetFightingHero(ui8 side) const; //depracated for players callback, easy to get wrong
+	const CArmedInstance * battleGetArmyObject(ui8 side) const;
+	InfoAboutHero battleGetHeroInfo(ui8 side) const;
+
+	// for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall,
+	// [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle
+	si8 battleGetWallState(int partOfWall) const;
+	EGateState battleGetGateState() const;
+
+	//helpers
+	///returns all stacks, alive or dead or undead or mechanical :)
+	TStacks battleGetAllStacks(bool includeTurrets = false) const;
+
+	///returns all alive stacks excluding turrets
+	TStacks battleAliveStacks() const;
+	///returns all alive stacks from particular side excluding turrets
+	TStacks battleAliveStacks(ui8 side) const;
+	const CStack * battleGetStackByID(int ID, bool onlyAlive = true) const; //returns stack info by given ID
+	bool battleIsObstacleVisibleForSide(const CObstacleInstance & coi, BattlePerspective::BattlePerspective side) const;
+
+	///returns player that controls given stack; mind control included
+	PlayerColor battleGetOwner(const CStack * stack) const;
+
+	///returns hero that controls given stack; nullptr if none; mind control included
+	const CGHeroInstance * battleGetOwnerHero(const CStack * stack) const;
+
+	///check that stacks are controlled by same|other player(s) depending on positiveness
+	///mind control included
+	bool battleMatchOwner(const CStack * attacker, const CStack * defender, const boost::logic::tribool positivness = false) const;
+};

+ 42 - 0
lib/battle/CCallbackBase.cpp

@@ -0,0 +1,42 @@
+/*
+ * CCallbackBase.cpp, 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
+ *
+ */
+#include "StdInc.h"
+#include "CCallbackBase.h"
+#include "BattleInfo.h"
+#include "CGameState.h"
+
+bool CCallbackBase::duringBattle() const
+{
+	return getBattle() != nullptr;
+}
+
+const BattleInfo *CCallbackBase::getBattle() const
+{
+	return battle;
+}
+
+CCallbackBase::CCallbackBase(CGameState * GS, boost::optional<PlayerColor> Player)
+	: battle(nullptr), gs(GS), player(Player)
+{}
+
+CCallbackBase::CCallbackBase()
+	: battle(nullptr), gs(nullptr)
+{}
+
+void CCallbackBase::setBattle(const BattleInfo *B)
+{
+	battle = B;
+}
+
+boost::optional<PlayerColor> CCallbackBase::getPlayerID() const
+{
+	return player;
+}
+

+ 40 - 0
lib/battle/CCallbackBase.h

@@ -0,0 +1,40 @@
+/*
+ * CCallbackBase.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 "GameConstants.h"
+
+class CGameState;
+struct BattleInfo;
+
+class CBattleInfoEssentials;
+
+//Basic class for various callbacks (interfaces called by players to get info about game and so forth)
+class DLL_LINKAGE CCallbackBase
+{
+	const BattleInfo *battle; //battle to which the player is engaged, nullptr if none or not applicable
+
+	const BattleInfo * getBattle() const;
+
+protected:
+	CGameState *gs;
+	boost::optional<PlayerColor> player; // not set gives access to all information, otherwise callback provides only information "visible" for player
+
+	CCallbackBase(CGameState *GS, boost::optional<PlayerColor> Player);
+	CCallbackBase();
+
+	void setBattle(const BattleInfo *B);
+	bool duringBattle() const;
+
+public:
+	boost::optional<PlayerColor> getPlayerID() const;
+
+	friend class CBattleInfoEssentials;
+};
+

+ 55 - 0
lib/battle/CPlayerBattleCallback.cpp

@@ -0,0 +1,55 @@
+/*
+ * CPlayerBattleCallback.cpp, 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
+ *
+ */
+#include "StdInc.h"
+#include "CPlayerBattleCallback.h"
+#include "CStack.h"
+#include "CGameState.h"
+#define RETURN_IF_NOT_BATTLE(X) if(!duringBattle()) {logGlobal->errorStream() << __FUNCTION__ << " called when no battle!"; return X; }
+
+bool CPlayerBattleCallback::battleCanFlee() const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	ASSERT_IF_CALLED_WITH_PLAYER
+			return CBattleInfoEssentials::battleCanFlee(*player);
+}
+
+TStacks CPlayerBattleCallback::battleGetStacks(EStackOwnership whose /*= MINE_AND_ENEMY*/, bool onlyAlive /*= true*/) const
+{
+	if(whose != MINE_AND_ENEMY)
+	{
+		ASSERT_IF_CALLED_WITH_PLAYER
+	}
+
+	return battleGetStacksIf([=](const CStack * s){
+		const bool ownerMatches = (whose == MINE_AND_ENEMY)
+								  || (whose == ONLY_MINE && s->owner == player)
+								  || (whose == ONLY_ENEMY && s->owner != player);
+
+		return ownerMatches && s->isValidTarget(!onlyAlive);
+	});
+}
+
+int CPlayerBattleCallback::battleGetSurrenderCost() const
+{
+	RETURN_IF_NOT_BATTLE(-3)
+			ASSERT_IF_CALLED_WITH_PLAYER
+			return CBattleInfoCallback::battleGetSurrenderCost(*player);
+}
+
+const CGHeroInstance * CPlayerBattleCallback::battleGetMyHero() const
+{
+	return CBattleInfoEssentials::battleGetFightingHero(battleGetMySide());
+}
+
+InfoAboutHero CPlayerBattleCallback::battleGetEnemyHero() const
+{
+	return battleGetHeroInfo(!battleGetMySide());
+}
+

+ 26 - 0
lib/battle/CPlayerBattleCallback.h

@@ -0,0 +1,26 @@
+/*
+ * CPlayerBattleCallback.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 "CBattleInfoCallback.h"
+
+class CGHeroInstance;
+
+class DLL_LINKAGE CPlayerBattleCallback : public CBattleInfoCallback
+{
+public:
+	bool battleCanFlee() const; //returns true if caller can flee from the battle
+	TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true) const; //returns stacks on battlefield
+
+	int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible
+
+	const CGHeroInstance * battleGetMyHero() const;
+	InfoAboutHero battleGetEnemyHero() const;
+};
+

+ 43 - 0
lib/battle/ReachabilityInfo.cpp

@@ -0,0 +1,43 @@
+/*
+ * ReachabilityInfo.cpp, 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
+ *
+ */
+
+#include "StdInc.h"
+#include "ReachabilityInfo.h"
+#include "CStack.h"
+
+
+ReachabilityInfo::Parameters::Parameters()
+{
+	stack = nullptr;
+	perspective = BattlePerspective::ALL_KNOWING;
+	attackerOwned = doubleWide = flying = false;
+}
+
+ReachabilityInfo::Parameters::Parameters(const CStack *Stack)
+{
+	stack = Stack;
+	perspective = (BattlePerspective::BattlePerspective)(!Stack->attackerOwned);
+	startPosition = Stack->position;
+	doubleWide = stack->doubleWide();
+	attackerOwned = stack->attackerOwned;
+	flying = stack->hasBonusOfType(Bonus::FLYING);
+	knownAccessible = stack->getHexes();
+}
+
+ReachabilityInfo::ReachabilityInfo()
+{
+	distances.fill(INFINITE_DIST);
+	predecessors.fill(BattleHex::INVALID);
+}
+
+bool ReachabilityInfo::isReachable(BattleHex hex) const
+{
+	return distances[hex] < INFINITE_DIST;
+}

+ 52 - 0
lib/battle/ReachabilityInfo.h

@@ -0,0 +1,52 @@
+/*
+ * ReachabilityInfo.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 "BattleHex.h"
+#include "CBattleInfoEssentials.h"
+#include "AccessibilityInfo.h"
+
+class CStack;
+
+// Reachability info is result of BFS calculation. It's dependent on stack (it's owner, whether it's flying),
+// startPosition and perpective.
+struct DLL_LINKAGE ReachabilityInfo
+{
+	typedef std::array<int, GameConstants::BFIELD_SIZE> TDistances;
+	typedef std::array<BattleHex, GameConstants::BFIELD_SIZE> TPredecessors;
+
+	enum { 	INFINITE_DIST = 1000000 };
+
+	struct DLL_LINKAGE Parameters
+	{
+		const CStack *stack; //stack for which calculation is mage => not required (kept for debugging mostly), following variables are enough
+
+		bool attackerOwned;
+		bool doubleWide;
+		bool flying;
+		std::vector<BattleHex> knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself)
+
+		BattleHex startPosition; //assumed position of stack
+		BattlePerspective::BattlePerspective perspective; //some obstacles (eg. quicksands) may be invisible for some side
+
+		Parameters();
+		Parameters(const CStack *Stack);
+	};
+
+	Parameters params;
+	AccessibilityInfo accessibility;
+	TDistances distances;
+	TPredecessors predecessors;
+
+	ReachabilityInfo();
+
+	bool isReachable(BattleHex hex) const;
+};
+
+

+ 1 - 1
lib/spells/CSpellHandler.cpp

@@ -24,7 +24,7 @@
 
 #include "../CStack.h"
 #include "../BattleInfo.h"
-#include "../CBattleCallback.h"
+#include "../battle/CBattleInfoCallback.h"
 #include "../CGameState.h" //todo: remove
 
 #include "../NetPacks.h" //todo: remove