浏览代码

* New files for lib: CBattleCallback.cpp and CBattleCallback.h
* Updated MSVC project files
* Filesystem: My version of .SND archive does not have \0 after WAV extension. Fixed (though hardcoded 3-char extension length).
* New bonus types: BLOCK_MAGIC_ABOVE for blocking casting spells above given level and BLOCK_ALL_MAGIC for blocking all magic.
* Heavy rewrite of battle callbacks. Fixed some minor bugs. Code reusage between lib/client/server (removed proxy calls). Better access control and support for various perspectives.
* Fixed #1031
* Fixed Orb of Inhibition and Recanter's Cloak (they were incorrectly implemented). Fixed #97.
* Fleeing hero won't lose artifacts. Spellbook won't be captured. Fixed #980.
* Fixed crash when attacking stack dies before counterattack (ie. because of Fire Shield)
* Server does some basic checks if action requests during battle are valid
* Minor stuff.

Michał W. Urbańczyk 13 年之前
父节点
当前提交
d390113c23

+ 53 - 20
AI/StupidAI/StupidAI.cpp

@@ -5,7 +5,7 @@
 #include "../../CCallback.h"
 #include "../../lib/CCreatureHandler.h"
 
-CBattleCallback * cbc;
+CPlayerBattleCallback * cbc;
 
 CStupidAI::CStupidAI(void)
 	: side(-1), cb(NULL)
@@ -19,7 +19,7 @@ CStupidAI::~CStupidAI(void)
 	print("destroyed");
 }
 
-void CStupidAI::init( CBattleCallback * CB )
+void CStupidAI::init( CPlayerBattleCallback * CB )
 {
 	print("init called, saving ptr to IBattleCallback");
 	cbc = cb = CB;
@@ -60,7 +60,7 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2)
 	return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr);
 }
 
-int distToNearestNeighbour(BattleHex hex, const std::vector<int> & dists, BattleHex *chosenHex = NULL)
+int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = NULL)
 {
 	int ret = 1000000;
 	BOOST_FOREACH(BattleHex n, hex.neighbouringTiles())
@@ -76,7 +76,7 @@ int distToNearestNeighbour(BattleHex hex, const std::vector<int> & dists, Battle
 	return ret;
 }
 
-bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const std::vector<int> & dists)
+bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists)
 {
 	return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists);
 }
@@ -97,9 +97,9 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
 BattleAction CStupidAI::activeStack( const CStack * stack )
 {
 	//boost::this_thread::sleep(boost::posix_time::seconds(2));
-	print("activeStack called");
+	print("activeStack called for " + stack->nodeName());
 	std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
-	std::vector<int> dists = cb->battleGetDistances(stack);
+	auto dists = cb->battleGetDistances(stack);
 	std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
 
 	if(stack->type->idNumber == 145) //catapult
@@ -246,33 +246,66 @@ void CStupidAI::print(const std::string &text) const
 	tlog6 << "CStupidAI [" << this <<"]: " << text << std::endl;
 }
 
-BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex hex)
+BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex destination)
 {
-	BattleHex realDest = hex;
-	BattleHex predecessors[GameConstants::BFIELD_SIZE];
-	std::vector<int> dists = cb->battleGetDistances(stack, hex);
-	if(distToNearestNeighbour(hex, dists, &realDest) > GameConstants::BFIELD_SIZE)
+	assert(destination.isValid());
+	auto avHexes = cb->battleGetAvailableHexes(stack, false);
+	auto reachability = cb->getReachability(stack);
+
+	if(vstd::contains(avHexes, destination))
+		return BattleAction::makeMove(stack, destination);
+	
+	auto destNeighbours = destination.neighbouringTiles();
+	if(vstd::contains_if(destNeighbours, [&](BattleHex n) { return stack->coversPos(destination); }))
 	{
-		print("goTowards: Cannot reach");
+		tlog3 << "Warning: already standing on neighbouring tile!" << std::endl;
+		//We shouldn't even be here...
 		return BattleAction::makeDefend(stack);
 	}
 
-	dists = cb->battleGetDistances(stack, realDest, predecessors);
-	std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
+	vstd::erase_if(destNeighbours, [&](BattleHex hex){ return !reachability.accessibility.accessible(hex, stack); });
 
-	if(!avHexes.size())
+	if(!avHexes.size() || !destNeighbours.size()) //we are blocked or dest is blocked
 	{
 		print("goTowards: Stack cannot move! That's " + stack->nodeName());
 		return BattleAction::makeDefend(stack);
 	}
 
-	while(1)
+	if(stack->hasBonusOfType(Bonus::FLYING))
+	{
+		// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
+		// We just check all available hexes and pick the one closest to the target.
+		auto distToDestNeighbour = [&](BattleHex hex) -> int
+		{
+			auto nearestNeighbourToHex = vstd::minElementByFun(destNeighbours, [&](BattleHex a)
+			{ 
+				return BattleHex::getDistance(a, hex); 
+			});
+
+			return BattleHex::getDistance(*nearestNeighbourToHex, hex);
+		};
+
+		auto nearestAvailableHex = vstd::minElementByFun(avHexes, distToDestNeighbour);
+		return BattleAction::makeMove(stack, *nearestAvailableHex);
+	}
+	else
 	{
-		assert(realDest.isValid());
-		if(vstd::contains(avHexes, hex))
-			return BattleAction::makeMove(stack, hex);
+		BattleHex bestNeighbor = destination;
+		if(distToNearestNeighbour(destination, reachability.distances, &bestNeighbor) > GameConstants::BFIELD_SIZE)
+		{
+			print("goTowards: Cannot reach");
+			return BattleAction::makeDefend(stack);
+		}
 
-		hex = predecessors[hex];
+		BattleHex currentDest = bestNeighbor;
+		while(1)
+		{
+			assert(currentDest.isValid());
+			if(vstd::contains(avHexes, currentDest))
+				return BattleAction::makeMove(stack, currentDest);
+
+			currentDest = reachability.predecessors[currentDest];
+		}
 	}
 }
 

+ 2 - 2
AI/StupidAI/StupidAI.h

@@ -5,14 +5,14 @@
 class CStupidAI : public CBattleGameInterface
 {
 	int side;
-	CBattleCallback *cb;
+	CPlayerBattleCallback *cb;
 
 	void print(const std::string &text) const;
 public:
 	CStupidAI(void);
 	~CStupidAI(void);
 
-	void init(CBattleCallback * CB) OVERRIDE;
+	void init(CPlayerBattleCallback * CB) OVERRIDE;
 	void actionFinished(const BattleAction *action) OVERRIDE;//occurs AFTER every action taken by any stack or by the hero
 	void actionStarted(const BattleAction *action) OVERRIDE;//occurs BEFORE every action taken by any stack or by the hero
 	BattleAction activeStack(const CStack * stack) OVERRIDE; //called when it's turn of that stack

+ 17 - 13
AI/VCAI/VCAI.cpp

@@ -174,13 +174,6 @@ void removeDuplicates(std::vector<T> &vec)
 	vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
 }
 
-
-template<typename Range, typename Predicate>
-void erase_if(Range &vec, Predicate pred)
-{
-	vec.erase(boost::remove_if(vec, pred),vec.end());
-}
-
 struct AtScopeExit
 {
 	boost::function<void()> foo;
@@ -1711,7 +1704,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 			cb->moveHero(*h, CGHeroInstance::convertPosition(endpos, true));
 			waitTillFree(); //movement may cause battle or blocking dialog
 			boost::this_thread::interruption_point();
-			if(h->tempOwner != playerID) //we lost hero - remove all tasks assigned to him/her
+			if(!h) //we lost hero - remove all tasks assigned to him/her
 			{
 				lostHero(h);
 				//we need to throw, otherwise hero will be assigned to sth again
@@ -1727,7 +1720,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 		performObjectInteraction (visitedObject, h);
 	}
 
-	if(h->tempOwner == playerID) //we could have lost hero after last move
+	if(h) //we could have lost hero after last move
 	{
 		cb->recalculatePaths();
 		if (startHpos == h->visitablePos() && !ret) //we didn't move and didn't reach the target
@@ -3616,7 +3609,7 @@ HeroPtr::HeroPtr(const CGHeroInstance *H)
 	h = H;
 	name = h->name;
 
-	hid = H->subID;
+	hid = H->id;
 //	infosCount[ai->playerID][hid]++;
 }
 
@@ -3641,12 +3634,23 @@ const CGHeroInstance * HeroPtr::get(bool doWeExpectNull /*= false*/) const
 {
 	//TODO? check if these all assertions every time we get info about hero affect efficiency
 	//
-	//behave terribly when attempting unauthorised access to hero that is not ours (or was lost)
+	//behave terribly when attempting unauthorized access to hero that is not ours (or was lost)
 	assert(doWeExpectNull || h);
+
 	if(h)
 	{
-		assert(cb->getObj(h->id));
-		assert(h->tempOwner == ai->playerID);
+		auto obj = cb->getObj(hid);
+		const bool owned = obj && obj->tempOwner == ai->playerID;
+
+		if(doWeExpectNull && !owned)
+		{
+			return nullptr;
+		}
+		else
+		{
+			assert(obj);
+			assert(owned);
+		}
 	}
 
 	return h;

+ 2 - 4
CCallback.h

@@ -78,17 +78,15 @@ public:
 
 struct CPack;
 
-class CBattleCallback : public IBattleCallback, public CBattleInfoCallback
+class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback
 {
-private:
-	CBattleCallback(CGameState *GS, int Player, CClient *C);
-
 protected:
 	int sendRequest(const CPack *request); //returns requestID (that'll be matched to requestID in PackageApplied)
 	CClient *cl;
 	//virtual bool hasAccess(int playerId) const;
 
 public:
+	CBattleCallback(CGameState *GS, int Player, CClient *C);
 	int battleMakeAction(BattleAction* action) OVERRIDE;//for casting spells by hero - DO NOT use it for moving active stack
 	bool battleMakeTacticAction(BattleAction * action) OVERRIDE; // performs tactic phase actions
 	

+ 44 - 14
Global.h

@@ -44,17 +44,10 @@
 #include <queue>
 #include <set>
 #include <sstream>
+#include <string>
+//#include <unordered_map>
 #include <utility>
-#include <numeric>
-
-#include <iostream>
-#include <fstream>
-#include <sstream>
-#include <iomanip>
-
-#include <algorithm>
-#include <memory>
-#include <cstdlib>
+#include <vector>
 
 //The only available version is 3, as of Boost 1.50
 #define BOOST_FILESYSTEM_VERSION 3
@@ -72,8 +65,10 @@
 #include <boost/lexical_cast.hpp>
 #include <boost/logic/tribool.hpp>
 #include <boost/program_options.hpp>
+#include <boost/optional.hpp>
 #include <boost/range/algorithm.hpp>
 #include <boost/thread.hpp>
+#include <boost/unordered_map.hpp>
 #include <boost/unordered_set.hpp>
 #include <boost/unordered_map.hpp>
 #include <boost/variant.hpp>
@@ -172,7 +167,14 @@ namespace vstd
 	template <typename Container, typename Item>
 	bool contains(const Container & c, const Item &i)
 	{
-		return std::find(c.begin(),c.end(),i) != c.end();
+		return std::find(boost::begin(c), boost::end(c),i) != boost::end(c);
+	}
+
+	//returns true if container c contains item i
+	template <typename Container, typename Pred>
+	bool contains_if(const Container & c, Pred p)
+	{
+		return std::find_if(boost::begin(c), boost::end(c), p) != boost::end(c);
 	}
 
 	//returns true if map c contains item i
@@ -201,7 +203,7 @@ namespace vstd
 	int find_pos(const Container & c, const T2 &s)
 	{
 		size_t i=0;
-		for (auto iter = c.begin(); iter != c.end(); iter++, i++)
+		for (auto iter = boost::begin(c); iter != boost::end(c); iter++, i++)
 			if(*iter == s)
 				return i;
 		return -1;
@@ -343,11 +345,39 @@ namespace vstd
 	{
 		assert(r.size());
 		index %= r.size();
-		// auto itr = std::begin(r); //not available in gcc-4.5
-		auto itr = r.begin();
+		auto itr = boost::begin(r);
 		std::advance(itr, index);
 		return *itr;
 	}
+	
+	template<typename Range, typename Predicate>
+	void erase_if(Range &vec, Predicate pred)
+	{
+		vec.erase(boost::remove_if(vec, pred),vec.end());
+	}
+
+	template<typename InputRange, typename OutputIterator, typename Predicate>
+	OutputIterator copy_if(const InputRange &input, OutputIterator result, Predicate pred)
+	{
+		return std::copy_if(boost::const_begin(input), boost::end(input), result, pred);
+	}
+
+	template <typename Container>
+	std::insert_iterator<Container> set_inserter(Container &c)
+	{
+		return std::inserter(c, c.end());
+	}
+
+	//Retuns iterator to the element for which the value of ValueFunction is minimal
+	template<class ForwardRange, class ValueFunction>
+	auto minElementByFun(const ForwardRange& rng, ValueFunction vf) -> decltype(boost::begin(rng))
+	{
+		typedef decltype(*boost::begin(rng)) ElemType;
+		return boost::min_element(rng, [&] (const ElemType &lhs, const ElemType &rhs) -> bool
+		{
+			return vf(lhs) < vf(rhs);
+		});
+	}
 }
 
 using std::shared_ptr;

+ 1 - 1
client/BattleInterface/CBattleAnimations.cpp

@@ -141,7 +141,7 @@ CAttackAnimation::CAttackAnimation(CBattleInterface *_owner, const CStack *attac
 
 	assert(attackingStack && "attackingStack is NULL in CBattleAttack::CBattleAttack !\n");
 	bool isCatapultAttack = attackingStack->hasBonusOfType(Bonus::CATAPULT) 
-							&& owner->curInt->cb->battleGetWallUnderHex(_dest) >= 0;
+							&& owner->curInt->cb->battleHexToWallPart(_dest) >= 0;
 
 	assert(attackedStack || isCatapultAttack);
 	attackingStackPosBeforeReturn = attackingStack->position;

+ 50 - 10
client/BattleInterface/CBattleInterface.cpp

@@ -1252,16 +1252,35 @@ void CBattleInterface::bSpellf()
 
 	CCS->curh->changeGraphic(0,0);
 
-	if ( myTurn && curInt->cb->battleCanCastSpell())
+	if(!myTurn)
+		return;
+
+	auto myHero = currentHero();
+	ESpellCastProblem::ESpellCastProblem spellCastProblem;
+	if (curInt->cb->battleCanCastSpell(&spellCastProblem))
 	{
-		const CGHeroInstance * chi = NULL;
-		if(attackingHeroInstance->tempOwner == curInt->playerID)
-			chi = attackingHeroInstance;
-		else
-			chi = defendingHeroInstance;
-		CSpellWindow * spellWindow = new CSpellWindow(genRect(595, 620, (screen->w - 620)/2, (screen->h - 595)/2), chi, curInt);
+		CSpellWindow * spellWindow = new CSpellWindow(genRect(595, 620, (screen->w - 620)/2, (screen->h - 595)/2), myHero, curInt);
 		GH.pushInt(spellWindow);
 	}
+	else if(spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
+	{
+		//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
+		auto blockingBonus = currentHero()->getBonus(Selector::type(Bonus::BLOCK_ALL_MAGIC));
+		if(!blockingBonus)
+			return;;
+		
+		if(blockingBonus->source == Bonus::ARTIFACT)
+		{
+			const int artID = blockingBonus->sid;
+			//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
+			//TODO check who *really* is source of bonus
+			std::string heroName = myHero->hasArt(artID) ? myHero->name : enemyHero().name;
+
+			//%s wields the %s, an ancient artifact which creates a p dead to all magic.
+			LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683]) 
+										% heroName % CGI->arth->artifacts[artID]->Name()));
+		}
+	}
 }
 
 void CBattleInterface::bWaitf()
@@ -1495,11 +1514,11 @@ bool CBattleInterface::isCatapultAttackable(BattleHex hex) const
 	if(!siegeH  ||  tacticsMode)
 		return false;
 
-	int wallUnder = curInt->cb->battleGetWallUnderHex(hex);
+	int wallUnder = curInt->cb->battleHexToWallPart(hex);
 	if(wallUnder == -1)
 		return false;
 
-	return curInt->cb->battleGetWallState(wallUnder) < 3;
+	return curInt->cb->battleGetWallState(wallUnder) < EWallState::DESTROYED;
 }
 
 const CGHeroInstance * CBattleInterface::getActiveHero()
@@ -1985,7 +2004,9 @@ void CBattleInterface::activateStack()
 	bWait->block(vstd::contains(s->state, EBattleStackState::WAITING)); //block waiting button if stack has been already waiting
 
 	//block cast spell button if hero doesn't have a spellbook
-	bSpell->block(!curInt->cb->battleCanCastSpell());
+	ESpellCastProblem::ESpellCastProblem spellcastingProblem;
+	bool canCastSpells = curInt->cb->battleCanCastSpell(&spellcastingProblem);
+	bSpell->block(!canCastSpells && spellcastingProblem != ESpellCastProblem::MAGIC_IS_BLOCKED); //if magic is blocked, we leave button active, so the message can be displayed (cf bug #97)
 	bSurrender->block((curInt == attackerInt ? defendingHeroInstance : attackingHeroInstance) == NULL);
 	bFlee->block(!curInt->cb->battleCanFlee());
 	bSurrender->block(curInt->cb->battleGetSurrenderCost() < 0);
@@ -3484,6 +3505,25 @@ Point CBattleInterface::whereToBlitObstacleImage(SDL_Surface *image, const CObst
 	return r.topLeft();
 }
 
+const CGHeroInstance * CBattleInterface::currentHero() const
+{
+	if(attackingHeroInstance->tempOwner == curInt->playerID)
+		return attackingHeroInstance;
+	else
+		return defendingHeroInstance;
+}
+
+InfoAboutHero CBattleInterface::enemyHero() const
+{
+	InfoAboutHero ret;
+	if(attackingHeroInstance->tempOwner == curInt->playerID)
+		curInt->cb->getHeroInfo(defendingHeroInstance, ret);
+	else
+		curInt->cb->getHeroInfo(attackingHeroInstance, ret);
+
+	return ret;
+}
+
 std::string CBattleInterface::SiegeHelper::townTypeInfixes[GameConstants::F_NUMBER] = {"CS", "RM", "TW", "IN", "NC", "DN", "ST", "FR", "EL"};
 
 CBattleInterface::SiegeHelper::SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface * _owner)

+ 4 - 0
client/BattleInterface/CBattleInterface.h

@@ -45,6 +45,7 @@ class CCreatureAnimation;
 struct ProjectileInfo;
 class CClickableHex;
 struct BattleHex;
+struct InfoAboutHero;
 
 /// Class which manages the locked hex fields that are blocked e.g. by obstacles
 class CBattleObstacle
@@ -284,6 +285,9 @@ public:
 	BattleHex fromWhichHexAttack(BattleHex myNumber);
 	void obstaclePlaced(const CObstacleInstance & oi);
 
+	const CGHeroInstance * currentHero() const;
+	InfoAboutHero enemyHero() const;
+
 	friend class CPlayerInterface;
 	friend class CAdventureMapButton;
 	friend class CInGameConsole;

+ 1 - 1
client/BattleInterface/CBattleInterfaceClasses.cpp

@@ -616,7 +616,7 @@ void CClickableHex::clickRight(tribool down, bool previousState)
 void CStackQueue::update()
 {
 	stacksSorted.clear();
-	owner->curInt->cb->getStackQueue(stacksSorted, QUEUE_SIZE);
+	owner->curInt->cb->battleGetStackQueue(stacksSorted, QUEUE_SIZE);
 	for (int i = 0; i < QUEUE_SIZE ; i++)
 	{
 		stackBoxes[i]->setStack(stacksSorted[i]);

+ 1 - 1
client/CPlayerInterface.cpp

@@ -892,7 +892,7 @@ void CPlayerInterface::battleAttack(const BattleAttack *ba)
 			if (!i->isSecondary()) //display projectile only for primary target
 			{
 				const CStack * attacked = cb->battleGetStackByID(i->stackAttacked);
-				battleInt->stackAttacking(attacker, cb->battleGetPos(i->stackAttacked), attacked, true);
+				battleInt->stackAttacking(attacker, attacked->position, attacked, true);
 			}
 		}
 	}

+ 18 - 3
client/CSpellWindow.cpp

@@ -654,9 +654,24 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 				break;
 			case ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED:
 				{
-					std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name;
-					text = boost::str(boost::format(text) % caster);
-					owner->myInt->showInfoDialog(text);
+					//Recanter's Cloak or similar effect. Try to retrieve bonus
+					const Bonus *b = owner->myHero->getBonus(Selector::type(Bonus::BLOCK_MAGIC_ABOVE));
+					//TODO what about other values and non-artifact sources?
+					if(b && b->val == 2 && b->source == Bonus::ARTIFACT)
+					{
+						std::string artName = CGI->arth->artifacts[b->sid]->Name();
+						//The %s prevents %s from casting 3rd level or higher spells.
+						owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[536]) 
+							% artName % owner->myHero->name));
+					}
+					else
+					{
+						// General message:
+						// %s recites the incantations but they seem to have no effect.
+						std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name;
+						text = boost::str(boost::format(text) % caster);
+						owner->myInt->showInfoDialog(text);
+					}
 				}
 				break;
 			case ESpellCastProblem::NO_APPROPRIATE_TARGET:

+ 27 - 6
client/Client.cpp

@@ -117,7 +117,7 @@ void CClient::waitForMoveAndSend(int color)
 	try
 	{
 		assert(vstd::contains(battleints, color));
-		BattleAction ba = battleints[color]->activeStack(gs->curB->getStack(gs->curB->activeStack, false));
+		BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false));
 		MakeAction temp_action(ba);
 		sendRequest(&temp_action, color);
 		return;
@@ -381,13 +381,14 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 			battleints[color] = playerint[color];
 
 			playerint[color]->init(cb.get());
-			callbacks[color] = cb;
+			battleCallbacks[color] = callbacks[color] = cb;
 		}
 		else
 		{
-			CBattleCallback * cbc = new CBattleCallback(gs, color, this);
+			auto cbc = make_shared<CBattleCallback>(gs, color, this);
+			battleCallbacks[color] = cbc;
 			battleints[color] = CDynLibHandler::getNewBattleAI("StupidAI");
-			battleints[color]->init(cbc);
+			battleints[color]->init(cbc.get());
 		}
 	}
 
@@ -399,7 +400,9 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 		battleints[254] = playerint[254] = p;
 		privilagedBattleEventReceivers.push_back(p);
 		GH.curInt = p;
-		p->init(new CCallback(gs, -1, this));
+		auto cb = make_shared<CCallback>(gs, -1, this);
+		battleCallbacks[-1] = callbacks[-1] = cb;
+		p->init(cb.get());
 		battleStarted(gs->curB);
 	}
 	else
@@ -554,6 +557,15 @@ void CClient::stopConnection()
 
 void CClient::battleStarted(const BattleInfo * info)
 {
+	BOOST_FOREACH(auto &battleCb, battleCallbacks)
+	{
+		if(vstd::contains(info->sides, battleCb.first)  ||  battleCb.first >= GameConstants::PLAYER_LIMIT)
+			battleCb.second->setBattle(info);
+	}
+// 	BOOST_FOREACH(ui8 side, info->sides)
+// 		if(battleCallbacks.count(side))
+// 			battleCallbacks[side]->setBattle(info);
+
 	CPlayerInterface * att, * def;
 	if(vstd::contains(playerint, info->sides[0]) && playerint[info->sides[0]]->human)
 		att = static_cast<CPlayerInterface*>( playerint[info->sides[0]] );
@@ -586,10 +598,19 @@ void CClient::battleStarted(const BattleInfo * info)
 	}
 }
 
+void CClient::battleFinished()
+{
+	BOOST_FOREACH(ui8 side, gs->curB->sides)
+		if(battleCallbacks.count(side))
+			battleCallbacks[side]->setBattle(nullptr);
+}
+
 void CClient::loadNeutralBattleAI()
 {
 	battleints[255] = CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String());
-	battleints[255]->init(new CBattleCallback(gs, 255, this));
+	auto cbc = make_shared<CBattleCallback>(gs, 255, this);
+	battleCallbacks[255] = cbc;
+	battleints[255]->init(cbc.get());
 }
 
 void CClient::commitPackage( CPackForClient *pack )

+ 2 - 0
client/Client.h

@@ -114,6 +114,7 @@ class CClient : public IGameCallback
 public:
 	CCallback *cb;
 	std::map<ui8,shared_ptr<CCallback> > callbacks; //callbacks given to player interfaces
+	std::map<ui8,shared_ptr<CBattleCallback> > battleCallbacks; //callbacks given to player interfaces
 	std::vector<IGameEventsReceiver*> privilagedGameEventReceivers; //scripting modules, spectator interfaces
 	std::vector<IBattleEventsReceiver*> privilagedBattleEventReceivers; //scripting modules, spectator interfaces
 	std::map<ui8,CGameInterface *> playerint;
@@ -225,4 +226,5 @@ public:
 	//////////////////////////////////////////////////////////////////////////
 
 	template <typename Handler> void serialize(Handler &h, const int version);
+	void battleFinished();
 };

+ 3 - 2
client/NetPacksClient.cpp

@@ -589,7 +589,7 @@ void BattleNextRound::applyCl( CClient *cl )
 
 void BattleSetActiveStack::applyCl( CClient *cl )
 {
-	CStack * activated = GS(cl)->curB->getStack(stack);
+	const CStack * activated = GS(cl)->curB->battleGetStackByID(stack);
 	int playerToCall = -1; //player that will move activated stack
 	if( activated->hasBonusOfType(Bonus::HYPNOTIZED) )
 	{
@@ -616,11 +616,12 @@ void BattleObstaclePlaced::applyCl(CClient * cl)
 void BattleResult::applyFirstCl( CClient *cl )
 {
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleEnd,this);
+	cl->battleFinished();
 }
 
 void BattleStackMoved::applyFirstCl( CClient *cl )
 {
-	const CStack * movedStack = GS(cl)->curB->getStack(stack);
+	const CStack * movedStack = GS(cl)->curB->battleGetStackByID(stack);
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleStackMoved,movedStack,tilesToMove,distance);
 }
 

+ 0 - 4
client/VCMI_client.vcxproj

@@ -248,7 +248,6 @@
     <ClCompile Include="CPlayerInterface.cpp" />
     <ClCompile Include="CPreGame.cpp" />
     <ClCompile Include="CQuestLog.cpp" />
-    <ClCompile Include="CSndHandler.cpp" />
     <ClCompile Include="CSpellWindow.cpp" />
     <ClCompile Include="CVideoHandler.cpp" />
     <ClCompile Include="Graphics.cpp" />
@@ -289,12 +288,10 @@
     <ClInclude Include="CKingdomInterface.h" />
     <ClInclude Include="Client.h" />
     <ClInclude Include="CMessage.h" />
-    <ClInclude Include="CMusicBase.h" />
     <ClInclude Include="CMusicHandler.h" />
     <ClInclude Include="CPlayerInterface.h" />
     <ClInclude Include="CPreGame.h" />
     <ClInclude Include="CQuestLog.h" />
-    <ClInclude Include="CSndHandler.h" />
     <ClInclude Include="CSoundBase.h" />
     <ClInclude Include="CSpellWindow.h" />
     <ClInclude Include="CVideoHandler.h" />
@@ -315,7 +312,6 @@
   </ItemGroup>
   <ItemGroup>
     <None Include="..\ChangeLog" />
-    <None Include="ClassDiagram21.cd" />
     <None Include="vcmi.ico" />
   </ItemGroup>
   <ItemGroup>

+ 2 - 5
client/VCMI_client.vcxproj.filters

@@ -46,28 +46,23 @@
     <ClInclude Include="BattleInterface\CBattleInterfaceClasses.h" />
     <ClInclude Include="BattleInterface\CCreatureAnimation.h" />
     <ClInclude Include="CAdvmapInterface.h" />
-    <ClInclude Include="..\hch\CArtHandler.h" />
     <ClInclude Include="CAnimation.h" />
     <ClInclude Include="CBitmapHandler.h" />
-    <ClInclude Include="..\hch\CBuildingHandler.h" />
     <ClInclude Include="..\CCallback.h" />
     <ClInclude Include="CCastleInterface.h" />
     <ClInclude Include="CConfigHandler.h" />
     <ClInclude Include="CCreatureWindow.h" />
     <ClInclude Include="CDefHandler.h" />
     <ClInclude Include="CGameInfo.h" />
-    <ClInclude Include="..\hch\CHeroHandler.h" />
     <ClInclude Include="CHeroWindow.h" />
     <ClInclude Include="CKingdomInterface.h" />
     <ClInclude Include="Client.h" />
     <ClInclude Include="CMessage.h" />
-    <ClInclude Include="..\hch\CObjectHandler.h" />
     <ClInclude Include="CMusicHandler.h" />
     <ClInclude Include="CPlayerInterface.h" />
     <ClInclude Include="CPreGame.h" />
     <ClInclude Include="CSoundBase.h" />
     <ClInclude Include="CSpellWindow.h" />
-    <ClInclude Include="..\hch\CVideoHandler.h" />
     <ClInclude Include="CVideoHandler.h" />
     <ClInclude Include="FontBase.h" />
     <ClInclude Include="FunctionList.h" />
@@ -86,6 +81,7 @@
     <ClCompile Include="CQuestLog.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClInclude Include="mapHandler.h" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="VCMI_client.rc" />
@@ -94,6 +90,7 @@
     </ClInclude>
   </ItemGroup>
   <ItemGroup>
+    <None Include="..\ChangeLog" />
     <None Include="vcmi.ico" />
   </ItemGroup>
 </Project>

+ 3 - 4
lib/BattleHex.cpp

@@ -84,11 +84,10 @@ char BattleHex::getDistance(BattleHex hex1, BattleHex hex2)
 	return std::max(xDst, yDst) + std::min(xDst, yDst) - (yDst + (yDst + xDst < 2 ? 0 : 1))/2;
 }
 
-void BattleHex::checkAndPush(int tile, std::vector<BattleHex> & ret)
+void BattleHex::checkAndPush(BattleHex tile, std::vector<BattleHex> & ret)
 {
-	if( tile>=0 && tile<GameConstants::BFIELD_SIZE && (tile%GameConstants::BFIELD_WIDTH != (GameConstants::BFIELD_WIDTH - 1)) 
-		&& (tile%GameConstants::BFIELD_WIDTH != 0) )
-		ret.push_back(BattleHex(tile));
+	if(tile.isAvailable())
+		ret.push_back(tile);
 }
 
 bool BattleHex::isAvailable() const

+ 1 - 1
lib/BattleHex.h

@@ -108,7 +108,7 @@ struct DLL_LINKAGE BattleHex
 	{
 		h & hex;
 	}
-	static void checkAndPush(int tile, std::vector<BattleHex> & ret);
+	static void checkAndPush(BattleHex tile, std::vector<BattleHex> & ret);
 
 	bool isAvailable() const; //valid position not in first or last column
 };

文件差异内容过多而无法显示
+ 171 - 751
lib/BattleState.cpp


+ 32 - 55
lib/BattleState.h

@@ -9,6 +9,7 @@
 #include "CObstacleInstance.h"
 #include "ConstTransitivePtr.h"
 #include "GameConstants.h"
+#include "CBattleCallback.h"
 
 /*
  * BattleState.h, part of VCMI engine
@@ -28,10 +29,11 @@ class CStackInstance;
 struct BattleStackAttacked;
 
 
+
 //only for use in BattleInfo
 struct DLL_LINKAGE SiegeInfo
 {
-	ui8 wallState[8]; //[0] - keep, [1] - bottom tower, [2] - bottom wall, [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; 1 - intact, 2 - damaged, 3 - destroyed
+	ui8 wallState[EWallParts::PARTS_COUNT]; 
 	
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
@@ -39,17 +41,9 @@ struct DLL_LINKAGE SiegeInfo
 	}
 };
 
-struct DLL_LINKAGE AttackableTiles
-{
-	std::set<BattleHex> hostileCreaturePositions;
-	std::set<BattleHex> friendlyCreaturePositions; //for Dragon Breath
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & hostileCreaturePositions & friendlyCreaturePositions;
-	}
-};
 
-struct DLL_LINKAGE BattleInfo : public CBonusSystemNode
+
+struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback
 {
 	ui8 sides[2]; //sides[0] - attacker, sides[1] - defender
 	si32 round, activeStack, selectedStack;
@@ -81,82 +75,62 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode
 		h & static_cast<CBonusSystemNode&>(*this);
 	}
 
+	//////////////////////////////////////////////////////////////////////////
+	BattleInfo();
+	~BattleInfo(){};
+
 	//////////////////////////////////////////////////////////////////////////
 	//void getBonuses(BonusList &out, const CSelector &selector, const CBonusSystemNode *root = NULL) const;
 	//////////////////////////////////////////////////////////////////////////
+	CStack * getStackT(BattleHex tileID, bool onlyAlive = true);
+	CStack * getStack(int stackID, bool onlyAlive = true);
 
-	const CStack *getStackIf(boost::function<bool(const CStack*)> pred) const;
 	const CStack * getNextStack() const; //which stack will have turn after current one
-	void getStackQueue(std::vector<const CStack *> &out, int howMany, int turn = 0, int lastMoved = -1) const; //returns stack in order of their movement action
-	CStack * getStack(int stackID, bool onlyAlive = true);
-	const CStack * getStack(int stackID, bool onlyAlive = true) const;
-	CStack * getStackT(BattleHex tileID, bool onlyAlive = true);
-	const CStack * getStackT(BattleHex tileID, bool onlyAlive = true) const;
-	void getAccessibilityMap(bool *accessibility, bool twoHex, bool attackerOwned, bool addOccupiable, std::set<BattleHex> & occupyable, bool flying, const CStack* stackToOmmit = NULL) const; //send pointer to at least 187 allocated bytes
-	static bool isAccessible(BattleHex hex, bool * accessibility, bool twoHex, bool attackerOwned, bool flying, bool lastPos); //helper for makeBFS
+	//void getStackQueue(std::vector<const CStack *> &out, int howMany, int turn = 0, int lastMoved = -1) const; //returns stack in order of their movement action
+
+	//void getAccessibilityMap(bool *accessibility, bool twoHex, bool attackerOwned, bool addOccupiable, std::set<BattleHex> & occupyable, bool flying, const CStack* stackToOmmit = NULL) const; //send pointer to at least 187 allocated bytes
+	//static bool isAccessible(BattleHex hex, bool * accessibility, bool twoHex, bool attackerOwned, bool flying, bool lastPos); //helper for makeBFS
 	BattleHex getClosestTile (bool attackerOwned, int initialPos, std::set<BattleHex> & possibilities) const; //TODO: vector or set? copying one to another is bad
 	int getAvaliableHex(TCreature creID, bool attackerOwned, int initialPos = -1) const; //find place for summon / clone effects
-	void makeBFS(BattleHex start, bool*accessibility, BattleHex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const; //*accessibility must be prepared bool[187] array; last two pointers must point to the at least 187-elements int arrays - there is written result
-	std::pair< std::vector<BattleHex>, int > getPath(BattleHex start, BattleHex dest, bool*accessibility, bool flyingCreature, bool twoHex, bool attackerOwned); //returned value: pair<path, length>; length may be different than number of elements in path since flying vreatures jump between distant hexes
-	std::vector<BattleHex> getAccessibility(const CStack * stack, bool addOccupiable, std::vector<BattleHex> * attackable = NULL, bool forPassingBy = false) const; //returns vector of accessible tiles (taking into account the creature range)
+	//void makeBFS(BattleHex start, bool*accessibility, BattleHex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const; //*accessibility must be prepared bool[187] array; last two pointers must point to the at least 187-elements int arrays - there is written result
+	std::pair< std::vector<BattleHex>, int > getPath(BattleHex start, BattleHex dest, const CStack *stack); //returned value: pair<path, length>; length may be different than number of elements in path since flying vreatures jump between distant hexes
+	//std::vector<BattleHex> getAccessibility(const CStack * stack, bool addOccupiable, std::vector<BattleHex> * attackable = NULL, bool forPassingBy = false) const; //returns vector of accessible tiles (taking into account the creature range)
 
-	bool isObstacleVisibleForSide(const CObstacleInstance &obstacle, ui8 side) const;
+	//bool isObstacleVisibleForSide(const CObstacleInstance &obstacle, ui8 side) const;
 	shared_ptr<CObstacleInstance> getObstacleOnTile(BattleHex tile) const;
-	bool isStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack
 	std::set<BattleHex> getStoppers(bool whichSidePerspective) const;
 
 	ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg); //charge - number of hexes travelled before attack (for champion's jousting)
-	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount, TQuantity defenderCount, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
-	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
 	void calculateCasualties(std::map<ui32,si32> *casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount)
-	std::set<CStack*> getAttackedCreatures(const CSpell * s, int skillLevel, ui8 attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell
-	void getPotentiallyAttackableHexes(AttackableTiles &at, const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos); //hexes around target that could be attacked in melee
-	std::set<CStack*> getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks
-	std::set<BattleHex> getAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks
-	std::set<CStack*> getAdjacentCreatures (const CStack * stack) const;
+	using CBattleInfoCallback::getAttackedCreatures;
+	std::set<const CStack*> getAttackedCreatures(const CSpell * s, int skillLevel, ui8 attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell
+	//void getPotentiallyAttackableHexes(AttackableTiles &at, const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos); //hexes around target that could be attacked in melee
+	//std::set<CStack*> getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks
+	//std::set<BattleHex> getAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks
 	static int calculateSpellDuration(const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower);
 	CStack * generateNewStack(const CStackInstance &base, bool attackerOwned, int slot, BattleHex position) const; //helper for CGameHandler::setupBattle and spells addign new stacks to the battlefield
 	CStack * generateNewStack(const CStackBasicDescriptor &base, bool attackerOwned, int slot, BattleHex position) const; //helper for CGameHandler::setupBattle and spells addign new stacks to the battlefield
 	int getIdForNewStack() const; //suggest a currently unused ID that'd suitable for generating a new stack
-	ui32 getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell
-	int hexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
-	int lineToWallHex(int line) const; //returns hex with wall in given line
-	std::pair<const CStack *, BattleHex> getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const; //if attackerOwned is indetermnate, returened stack is of any owner; hex is the number of hex we should be looking from; returns (nerarest creature, predecessorHex)
+	//std::pair<const CStack *, BattleHex> getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const; //if attackerOwned is indetermnate, returened stack is of any owner; hex is the number of hex we should be looking from; returns (nerarest creature, predecessorHex)
 	ui32 calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const;
 	ui32 calculateSpellDmg(const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const; //calculates damage inflicted by spell
 	ui32 calculateHealedHP(const CGHeroInstance * caster, const CSpell * spell, const CStack * stack, const CStack * sacrificedStack = NULL) const;
 	ui32 calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const; //for Archangel
 	ui32 calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const; //unused
 	bool resurrects(TSpell spellid) const; //TODO: move it to spellHandler?
-	si8 hasDistancePenalty(const CStack * stackID, BattleHex destHex) const; //determines if given stack has distance penalty shooting given pos
-	si8 sameSideOfWall(int pos1, int pos2) const; //determines if given positions are on the same side of wall
-	si8 hasWallPenalty(const CStack * stack, BattleHex destHex) const; //determines if given stack has wall penalty shooting given pos
-	si8 canTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel) const; //determines if given stack can teleport to given place
-	bool battleCanShoot(const CStack * stack, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination
+	
 	const CGHeroInstance * getHero(int player) const; //returns fighting hero that belongs to given player
 
-	ESpellCastProblem::ESpellCastProblem battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(int player, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
-	ESpellCastProblem::ESpellCastProblem battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc.
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(int player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
-	bool battleTestElementalImmunity(const CStack * subject, const CSpell * spell, Bonus::BonusType element, bool damageSpell) const;
-	TSpell getRandomBeneficialSpell(const CStack * subject) const;
-	TSpell getRandomCastedSpell(const CStack * caster) const; //called at the beginning of turn for Faerie Dragon
-
-	std::vector<ui32> calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, const std::set<CStack*> affectedCreatures, int casterSideOwner, ECastingMode::ECastingMode mode, int usedSpellPower, int spellLevel) const;
 
+	std::vector<ui32> calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, const std::set<const CStack*> affectedCreatures, int casterSideOwner, ECastingMode::ECastingMode mode, int usedSpellPower, int spellLevel) const;
 
-	bool battleCanFlee(int player) const; //returns true if player can flee from the battle
 	const CStack * battleGetStack(BattleHex pos, bool onlyAlive); //returns stack at given tile
 	const CGHeroInstance * battleGetOwner(const CStack * stack) const; //returns hero that owns given stack; NULL if none
-	si8 battleMinSpellLevel() const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
 	void localInit();
 
 	void localInitStack(CStack * s);
 	static BattleInfo * setupBattle( int3 tile, int terrain, int battlefieldType, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town );
-	bool isInTacticRange( BattleHex dest ) const;
-	int getSurrenderingCost(int player) const;
-	bool hasNativeStack(ui8 side) const;
+	//bool hasNativeStack(ui8 side) const;
 
 	int theOtherPlayer(int player) const;
 	ui8 whatSide(int player) const;
@@ -168,7 +142,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode
 class DLL_LINKAGE CStack : public CBonusSystemNode, public CStackBasicDescriptor
 { 
 public:
-	const CStackInstance *base;
+	const CStackInstance *base; //garrison slot from which stack originates (NULL for war machines, summoned cres, etc)
 
 	ui32 ID; //unique ID of stack
 	ui32 baseAmount;
@@ -226,7 +200,10 @@ public:
 
 	bool doubleWide() const;
 	BattleHex occupiedHex() const; //returns number of occupied hex (not the position) if stack is double wide; otherwise -1
+	BattleHex occupiedHex(BattleHex assumedPos) const; //returns number of occupied hex (not the position) if stack is double wide and would stand on assumedPos; otherwise -1
 	std::vector<BattleHex> getHexes() const; //up to two occupied hexes, starting from front
+	std::vector<BattleHex> getHexes(BattleHex assumedPos) const; //up to two occupied hexes, starting from front
+	static std::vector<BattleHex> getHexes(BattleHex assumedPos, bool twoHex, bool AttackerOwned); //up to two occupied hexes, starting from front
 	bool coversPos(BattleHex position) const; //checks also if unit is double-wide
 	std::vector<BattleHex> getSurroundingHexes(BattleHex attackerPos = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size
 

+ 66 - 41
lib/CArtHandler.cpp

@@ -526,31 +526,46 @@ void CArtHandler::getAllowedArts(std::vector<ConstTransitivePtr<CArtifact> > &ou
 		out.push_back(art);
 	}
 }
-void CArtHandler::giveArtBonus( int aid, Bonus::BonusType type, int val, int subtype, int valType, ILimiter * limiter, int additionalInfo)
+
+Bonus *createBonus(Bonus::BonusType type, int val, int subtype, int valType, shared_ptr<ILimiter> limiter = nullptr, int additionalInfo = 0)
 {
-	Bonus *added = new Bonus(Bonus::PERMANENT,type,Bonus::ARTIFACT,val,aid,subtype);
+	Bonus *added = new Bonus(Bonus::PERMANENT,type,Bonus::ARTIFACT,val,-1,subtype);
 	added->additionalInfo = additionalInfo;
 	added->valType = valType;
-	added->limiter.reset(limiter);
-	if(type == Bonus::MORALE || type == Bonus::LUCK)
-		added->description = artifacts[aid]->Name()  + (val > 0 ? " +" : " ") + boost::lexical_cast<std::string>(val);
-	else
-		added->description = artifacts[aid]->Name();
-	artifacts[aid]->addNewBonus(added);
+	added->limiter = limiter;
+	return added;
 }
 
-void CArtHandler::giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype, IPropagator* propagator /*= NULL*/, int additionalInfo)
+Bonus *createBonus(Bonus::BonusType type, int val, int subtype, shared_ptr<IPropagator> propagator = nullptr, int additionalInfo = 0)
 {
-	Bonus *added = new Bonus(Bonus::PERMANENT,type,Bonus::ARTIFACT,val,aid,subtype);
+	Bonus *added = new Bonus(Bonus::PERMANENT,type,Bonus::ARTIFACT,val,-1,subtype);
 	added->additionalInfo = additionalInfo;
 	added->valType = Bonus::BASE_NUMBER;
-	added->propagator.reset(propagator);
-	if(type == Bonus::MORALE || type == Bonus::LUCK)
-		added->description = artifacts[aid]->Name()  + (val > 0 ? " +" : " ") + boost::lexical_cast<std::string>(val);
+	added->propagator = propagator;
+	return added;
+}
+
+void CArtHandler::giveArtBonus( int aid, Bonus::BonusType type, int val, int subtype, int valType, shared_ptr<ILimiter> limiter, int additionalInfo)
+{
+	giveArtBonus(aid, createBonus(type, val, subtype, valType, limiter, additionalInfo));
+}
+
+void CArtHandler::giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype, shared_ptr<IPropagator> propagator /*= NULL*/, int additionalInfo)
+{
+	giveArtBonus(aid, createBonus(type, val, subtype, propagator, additionalInfo));
+}
+
+void CArtHandler::giveArtBonus(int aid, Bonus *bonus)
+{
+	bonus->sid = aid;
+	if(bonus->subtype == Bonus::MORALE || bonus->type == Bonus::LUCK)
+		bonus->description = artifacts[aid]->Name()  + (bonus->val > 0 ? " +" : " ") + boost::lexical_cast<std::string>(bonus->val);
 	else
-		added->description = artifacts[aid]->Name();
-	artifacts[aid]->addNewBonus(added);
+		bonus->description = artifacts[aid]->Name();
+
+	artifacts[aid]->addNewBonus(bonus);
 }
+
 void CArtHandler::makeItCreatureArt (int aid, bool onlyCreature /*=true*/)
 {
 	CArtifact *a = artifacts[aid];
@@ -584,6 +599,13 @@ void CArtHandler::addBonuses()
 	#define ART_ATTACK_AND_DEFENSE(ID, val) ART_PRIM_SKILL(ID,0,val); ART_PRIM_SKILL(ID,1,val)
 	#define ART_POWER_AND_KNOWLEDGE(ID, val) ART_PRIM_SKILL(ID,2,val); ART_PRIM_SKILL(ID,3,val)
 
+	//Propagators/limiters used more than once
+	auto battleWidePropagator = make_shared<CPropagatorNodeType>(CBonusSystemNode::BATTLE);
+	auto visitedTownPropagator = make_shared<CPropagatorNodeType>(CBonusSystemNode::TOWN_AND_VISITOR); 
+
+	auto shooterOnlyLimiter = make_shared<HasAnotherBonusLimiter>(Bonus::SHOOTER);
+	auto dragonNatureLimiter = make_shared<HasAnotherBonusLimiter>(Bonus::DRAGON_NATURE);
+	
 	//Attack bonus artifacts (Weapons)
 	ART_PRIM_SKILL(7,0,+2); //Centaur Axe
 	ART_PRIM_SKILL(8,0,+3); //Blackshard of the Dead Knight
@@ -697,7 +719,7 @@ void CArtHandler::addBonuses()
 	giveArtBonus(81,Bonus::FIRE_SPELL_DMG_PREMY,+50);//Orb of Tempestuous Fire
 	giveArtBonus(82,Bonus::WATER_SPELL_DMG_PREMY,+50);//Orb of Driving Rain
 
-	giveArtBonus(83,Bonus::LEVEL_SPELL_IMMUNITY,3,-1,Bonus::INDEPENDENT_MAX);//Recanter's Cloak
+	giveArtBonus(83,createBonus(Bonus::BLOCK_MAGIC_ABOVE, 2, -1, Bonus::INDEPENDENT_MIN)->addPropagator(battleWidePropagator));//Recanter's Cloak
 	giveArtBonus(84,Bonus::BLOCK_MORALE,0);//Spirit of Oppression
 	giveArtBonus(85,Bonus::BLOCK_LUCK,0);//Hourglass of the Evil Hour
 
@@ -707,8 +729,8 @@ void CArtHandler::addBonuses()
 	giveArtBonus(89,Bonus::EARTH_SPELLS,0);//Tome of Earth Magic
 
 	giveArtBonus(90,Bonus::WATER_WALKING, 0, 1);//Boots of Levitation
-	giveArtBonus(91,Bonus::NO_DISTANCE_PENALTY,0, 0, 0, new HasAnotherBonusLimiter(Bonus::SHOOTER));//Golden Bow
-	giveArtBonus(91,Bonus::NO_WALL_PENALTY, 0, 0, 0, new HasAnotherBonusLimiter(Bonus::SHOOTER));
+	giveArtBonus(91,Bonus::NO_DISTANCE_PENALTY,0, 0, 0, shooterOnlyLimiter);//Golden Bow
+	giveArtBonus(91,Bonus::NO_WALL_PENALTY, 0, 0, 0, shooterOnlyLimiter);
 	giveArtBonus(92,Bonus::SPELL_IMMUNITY,0,35);//Sphere of Permanence
 	giveArtBonus(93,Bonus::NEGATE_ALL_NATURAL_IMMUNITIES,0);//Orb of Vulnerability
 
@@ -720,14 +742,15 @@ void CArtHandler::addBonuses()
 	giveArtBonus(98,Bonus::LAND_MOVEMENT,+600);//Boots of Speed
 	giveArtBonus(99,Bonus::STACKS_SPEED,+2);//Cape of Velocity
 
-	giveArtBonus(100,Bonus::SPELL_IMMUNITY,0,59);//Pendant of Dispassion
-	giveArtBonus(101,Bonus::SPELL_IMMUNITY,0,62);//Pendant of Second Sight
-	giveArtBonus(102,Bonus::SPELL_IMMUNITY,0,42);//Pendant of Holiness
-	giveArtBonus(103,Bonus::SPELL_IMMUNITY,0,24);//Pendant of Life
-	giveArtBonus(104,Bonus::SPELL_IMMUNITY,0,25, 1, new HasAnotherBonusLimiter(Bonus::UNDEAD));//Pendant of Death does not display info for living stacks
-	giveArtBonus(105,Bonus::SPELL_IMMUNITY,0,60);//Pendant of Free Will
-	giveArtBonus(106,Bonus::SPELL_IMMUNITY,0,17);//Pendant of Negativity
-	giveArtBonus(107,Bonus::SPELL_IMMUNITY,0,61);//Pendant of Total Recall
+	giveArtBonus(100,Bonus::SPELL_IMMUNITY,0,Spells::BERSERK);//Pendant of Dispassion
+	giveArtBonus(101,Bonus::SPELL_IMMUNITY,0,Spells::BLIND);//Pendant of Second Sight
+	giveArtBonus(102,Bonus::SPELL_IMMUNITY,0,Spells::CURSE);//Pendant of Holiness
+	giveArtBonus(103,Bonus::SPELL_IMMUNITY,0,Spells::DEATH_RIPPLE);//Pendant of Life
+	giveArtBonus(104,Bonus::SPELL_IMMUNITY,0,Spells::DESTROY_UNDEAD, 1, make_shared<HasAnotherBonusLimiter>(Bonus::UNDEAD));//Pendant of Death does not display info for living stacks
+	giveArtBonus(105,Bonus::SPELL_IMMUNITY,0,Spells::HYPNOTIZE);//Pendant of Free Will
+	giveArtBonus(106,Bonus::SPELL_IMMUNITY,0,Spells::LIGHTNING_BOLT);//Pendant of Negativity
+	giveArtBonus(106,Bonus::SPELL_IMMUNITY,0,Spells::CHAIN_LIGHTNING);//Pendant of Negativity
+	giveArtBonus(107,Bonus::SPELL_IMMUNITY,0,Spells::FORGETFULNESS);//Pendant of Total Recall
 	giveArtBonus(108,Bonus::MORALE,+3);//Pendant of Courage
 	giveArtBonus(108,Bonus::LUCK,+3);//Pendant of Courage
 
@@ -741,11 +764,13 @@ void CArtHandler::addBonuses()
 	giveArtBonus(116,Bonus::GENERATE_RESOURCE,+750, Res::GOLD); //Endless Bag of Gold
 	giveArtBonus(117,Bonus::GENERATE_RESOURCE,+500, Res::GOLD); //Endless Purse of Gold
 
-	giveArtBonus(118,Bonus::CREATURE_GROWTH,+5,1, new CPropagatorNodeType(CBonusSystemNode::TOWN_AND_VISITOR)); //Legs of Legion
-	giveArtBonus(119,Bonus::CREATURE_GROWTH,+4,2, new CPropagatorNodeType(CBonusSystemNode::TOWN_AND_VISITOR)); //Loins of Legion
-	giveArtBonus(120,Bonus::CREATURE_GROWTH,+3,3, new CPropagatorNodeType(CBonusSystemNode::TOWN_AND_VISITOR)); //Torso of Legion
-	giveArtBonus(121,Bonus::CREATURE_GROWTH,+2,4, new CPropagatorNodeType(CBonusSystemNode::TOWN_AND_VISITOR)); //Arms of Legion
-	giveArtBonus(122,Bonus::CREATURE_GROWTH,+1,5, new CPropagatorNodeType(CBonusSystemNode::TOWN_AND_VISITOR)); //Head of Legion
+
+	//Town will receive bonus if hero is visiting town or stays in its garrison.
+	giveArtBonus(118,Bonus::CREATURE_GROWTH,+5,1, visitedTownPropagator); //Legs of Legion
+	giveArtBonus(119,Bonus::CREATURE_GROWTH,+4,2, visitedTownPropagator); //Loins of Legion
+	giveArtBonus(120,Bonus::CREATURE_GROWTH,+3,3, visitedTownPropagator); //Torso of Legion
+	giveArtBonus(121,Bonus::CREATURE_GROWTH,+2,4, visitedTownPropagator); //Arms of Legion
+	giveArtBonus(122,Bonus::CREATURE_GROWTH,+1,5, visitedTownPropagator); //Head of Legion
 
 	//Sea Captain's Hat 
 	giveArtBonus(123,Bonus::WHIRLPOOL_PROTECTION,0); 
@@ -753,13 +778,13 @@ void CArtHandler::addBonuses()
 	giveArtBonus(123,Bonus::SPELL,3,0, Bonus::INDEPENDENT_MAX); 
 	giveArtBonus(123,Bonus::SPELL,3,1, Bonus::INDEPENDENT_MAX); 
 
-	giveArtBonus(124,Bonus::SPELLS_OF_LEVEL,3,1); //Spellbinder's Hat
-	giveArtBonus(125,Bonus::ENEMY_CANT_ESCAPE,0); //Shackles of War
-	giveArtBonus(126,Bonus::LEVEL_SPELL_IMMUNITY,GameConstants::SPELL_LEVELS,-1,Bonus::INDEPENDENT_MAX);//Orb of Inhibition
+	giveArtBonus(124, Bonus::SPELLS_OF_LEVEL,3,1); //Spellbinder's Hat
+	giveArtBonus(125, Bonus::ENEMY_CANT_ESCAPE,0); //Shackles of War
+	giveArtBonus(126, Bonus::BLOCK_ALL_MAGIC, 0, -1, battleWidePropagator);//Orb of Inhibition
 
 	//vial of dragon blood
-	giveArtBonus(127, Bonus::PRIMARY_SKILL, +5, PrimarySkill::ATTACK, Bonus::BASE_NUMBER, new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE));
-	giveArtBonus(127, Bonus::PRIMARY_SKILL, +5, PrimarySkill::DEFENSE, Bonus::BASE_NUMBER, new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE));
+	giveArtBonus(127, Bonus::PRIMARY_SKILL, +5, PrimarySkill::ATTACK, Bonus::BASE_NUMBER, dragonNatureLimiter);
+	giveArtBonus(127, Bonus::PRIMARY_SKILL, +5, PrimarySkill::DEFENSE, Bonus::BASE_NUMBER, dragonNatureLimiter);
 
 	//Armageddon's Blade
 	giveArtBonus(128, Bonus::SPELL, 3, 26, Bonus::INDEPENDENT_MAX);
@@ -786,7 +811,7 @@ void CArtHandler::addBonuses()
 	giveArtBonus(132, Bonus::OPENING_BATTLE_SPELL, 50, 52); // Misfortune
 
 	// Statue of Legion - gives only 50% growth
-	giveArtBonus(133, Bonus::CREATURE_GROWTH_PERCENT, 50, -1, new CPropagatorNodeType(CBonusSystemNode::PLAYER));
+	giveArtBonus(133, Bonus::CREATURE_GROWTH_PERCENT, 50, -1, make_shared<CPropagatorNodeType>(CBonusSystemNode::PLAYER));
 
 	//Power of the Dragon Father
 	giveArtBonus(134, Bonus::LEVEL_SPELL_IMMUNITY, 4, -1, Bonus::INDEPENDENT_MAX);
@@ -798,9 +823,9 @@ void CArtHandler::addBonuses()
 	giveArtBonus(136, Bonus::FREE_SHIP_BOARDING, 0);
 
 	//Bow of the Sharpshooter
-	giveArtBonus(137, Bonus::NO_DISTANCE_PENALTY, 0, 0, 0, new HasAnotherBonusLimiter(Bonus::SHOOTER));
-	giveArtBonus(137, Bonus::NO_WALL_PENALTY, 0, 0, 0, new HasAnotherBonusLimiter(Bonus::SHOOTER));
-	giveArtBonus(137, Bonus::FREE_SHOOTING, 0, 0, 0, new HasAnotherBonusLimiter(Bonus::SHOOTER));
+	giveArtBonus(137, Bonus::NO_DISTANCE_PENALTY, 0, 0, 0, shooterOnlyLimiter);
+	giveArtBonus(137, Bonus::NO_WALL_PENALTY, 0, 0, 0, shooterOnlyLimiter);
+	giveArtBonus(137, Bonus::FREE_SHOOTING, 0, 0, 0, shooterOnlyLimiter);
 
 	//Wizard's Well
 	giveArtBonus(138, Bonus::FULL_MANA_REGENERATION, 0);
@@ -844,7 +869,7 @@ void CArtHandler::addBonuses()
 		giveArtBonus(142, Bonus::SPELL_AFTER_ATTACK, 50, Spells::BERSERK, NULL, 1);
 		giveArtBonus(142, Bonus::SPELL_AFTER_ATTACK, 50, Spells::POISON, NULL, 1);
 		giveArtBonus(142, Bonus::SPELL_AFTER_ATTACK, 50, Spells::DISRUPTING_RAY, NULL, 1);
-		artifacts[142].get()->setDescription ("Tripple shots, tripple attack, casts various spells during attack, attacks have range of Inferno, no distance penalty, catapult");
+		artifacts[142].get()->setDescription ("Triple shots, triple attack, casts various spells during attack, attacks have range of Inferno, no distance penalty, catapult");
 		//Monster's Power
 		giveArtBonus(143, Bonus::STACK_HEALTH, +100, -1, Bonus::PERCENT_TO_BASE);
 		giveArtBonus(143, Bonus::CREATURE_DAMAGE, +100, 2, Bonus::PERCENT_TO_ALL);

+ 11 - 2
lib/CArtHandler.h

@@ -34,6 +34,14 @@ namespace ArtifactPosition
 	};
 }
 
+namespace ArtifactId
+{
+	enum ArtifactId
+	{
+		SPELLBOOK = 17
+	};
+}
+
 namespace ArtBearer
 {
 	enum
@@ -181,8 +189,9 @@ public:
 
 class DLL_LINKAGE CArtHandler //handles artifacts
 {
-	void giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype = -1, int valType = Bonus::BASE_NUMBER, ILimiter * limiter = NULL, int additionalinfo = 0);
-	void giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype, IPropagator* propagator, int additionalinfo = 0);
+	void giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype = -1, int valType = Bonus::BASE_NUMBER, shared_ptr<ILimiter> limiter = NULL, int additionalinfo = 0);
+	void giveArtBonus(int aid, Bonus::BonusType type, int val, int subtype, shared_ptr<IPropagator> propagator, int additionalinfo = 0);
+	void giveArtBonus(int aid, Bonus *bonus);
 public:
 	std::vector<CArtifact*> treasures, minors, majors, relics;
 	std::vector< ConstTransitivePtr<CArtifact> > artifacts;

+ 2021 - 0
lib/CBattleCallback.cpp

@@ -0,0 +1,2021 @@
+#include "StdInc.h"
+#include "CBattleCallback.h"
+#include "BattleState.h"
+#include "CGameState.h"
+#include "NetPacks.h"
+#include "CSpellHandler.h"
+#include "VCMI_Lib.h"
+
+#define RETURN_IF_NOT_BATTLE(X) if(!duringBattle()) {tlog1 << __FUNCTION__ << " called when no battle!\n"; return X; }
+
+namespace SiegeStuffThatShouldBeMovedToHandlers //  <=== TODO
+{
+	static int lineToWallHex(int line) //returns hex with wall in given line (y coordinate)
+	{
+		static const int 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;
+	}
+
+	static int getMoatDamage(int townType)
+	{
+		//TODO move to config file
+		static const int dmgs[] = {70, 70, -1, 
+			90, 70, 90,
+			70, 90, 70};
+
+		if(townType >= 0 && townType < ARRAY_COUNT(dmgs))
+			return dmgs[townType];
+
+		tlog1 << "No moat info for town " << townType << std::endl;
+		return 0;
+	}
+	static EWallParts::EWallParts hexToWallPart(BattleHex hex) 
+	{
+		//potentially attackable parts of wall
+		// -2 - indestructible walls
+		static const std::pair<int, EWallParts::EWallParts> attackable[] = 
+		{
+			std::make_pair(50,  EWallParts::KEEP), 
+			std::make_pair(183, EWallParts::BOTTOM_TOWER), 
+			std::make_pair(182, EWallParts::BOTTOM_WALL), 
+			std::make_pair(130, EWallParts::BELOW_GATE),
+			std::make_pair(62,  EWallParts::OVER_GATE), 
+			std::make_pair(29,  EWallParts::UPPER_WAL), 
+			std::make_pair(12,  EWallParts::UPPER_TOWER), 
+			std::make_pair(95,  EWallParts::GATE), 
+			std::make_pair(96,  EWallParts::GATE),
+			std::make_pair(45,  EWallParts::INDESTRUCTIBLE_PART), 
+			std::make_pair(78,  EWallParts::INDESTRUCTIBLE_PART), 
+			std::make_pair(112, EWallParts::INDESTRUCTIBLE_PART), 
+			std::make_pair(147, EWallParts::INDESTRUCTIBLE_PART)
+		}; 
+
+		for(int g = 0; g < ARRAY_COUNT(attackable); ++g)
+		{
+			if(attackable[g].first == hex)
+				return attackable[g].second;
+		}
+
+		return EWallParts::INVALID; //not found!
+	}
+}
+
+using namespace SiegeStuffThatShouldBeMovedToHandlers;
+
+boost::shared_mutex& CCallbackBase::getGsMutex()
+{
+	return *gs->mx;
+}
+
+bool CCallbackBase::duringBattle() const
+{
+	return getBattle() != nullptr;
+}
+
+void CCallbackBase::setBattle(const BattleInfo *B)
+{
+	battle = B;
+}
+
+ui8 CBattleInfoEssentials::battleTerrainType() const
+{
+	RETURN_IF_NOT_BATTLE(-1);
+	return getBattle()->terrainType;
+}
+
+int CBattleInfoEssentials::battleGetBattlefieldType() const
+{
+	RETURN_IF_NOT_BATTLE(-1);
+	return getBattle()->battlefieldType;
+}
+
+std::vector<shared_ptr<const CObstacleInstance> > CBattleInfoEssentials::battleGetAllObstacles(boost::optional<BattlePerspective::BattlePerspective> perspective /*= boost::none*/) const
+{
+	std::vector<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 >= 0 && *perspective != battleGetMySide())
+		{
+			tlog1 << "Unauthorized access attempt!\n";
+			assert(0); //I want to notice if that happens
+			//perspective = battleGetMySide();
+		}
+	}
+
+	BOOST_FOREACH(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);
+
+	BOOST_FOREACH(const CStack *s, battleGetAllStacks())
+	{
+		if(s->attackerOwned == !side  &&  s->getCreature()->isItNativeTerrain(getBattle()->terrainType))
+			return true;
+	}
+
+	return false;
+}
+
+TStacks CBattleInfoEssentials::battleGetAllStacks() const /*returns all stacks, alive or dead or undead or mechanical :) */
+{
+	TStacks ret;
+	RETURN_IF_NOT_BATTLE(ret);
+	boost::copy(getBattle()->stacks, std::back_inserter(ret));
+	return ret;
+}
+
+TStacks CBattleInfoEssentials::battleAliveStacks() const
+{
+	TStacks ret;
+	vstd::copy_if(battleGetAllStacks(), std::back_inserter(ret), [](const CStack *s){ return s->alive(); });
+	return ret;
+}
+
+TStacks CBattleInfoEssentials::battleAliveStacks(ui8 side) const
+{
+	TStacks ret;
+	vstd::copy_if(battleGetAllStacks(), std::back_inserter(ret), [=](const CStack *s){ return s->alive()  &&  s->attackerOwned == !side; });
+	return ret;
+}
+
+int CBattleInfoEssentials::battleGetMoatDmg() const
+{
+	RETURN_IF_NOT_BATTLE(0);
+
+	auto town = getBattle()->town;
+	if(!town)
+		return 0;
+
+	return getMoatDamage(town->subID);
+}
+
+const CGTownInstance * CBattleInfoEssentials::battleGetDefendedTown() const
+{
+	RETURN_IF_NOT_BATTLE(nullptr);
+	
+
+	if(!getBattle() || getBattle()->town == NULL)
+		return NULL;
+
+	return getBattle()->town;
+}
+
+BattlePerspective::BattlePerspective CBattleInfoEssentials::battleGetMySide() const
+{
+	RETURN_IF_NOT_BATTLE(BattlePerspective::INVALID);
+	if(player < 0)
+		return BattlePerspective::ALL_KNOWING;
+	if(player == getBattle()->sides[0])
+		return BattlePerspective::LEFT_SIDE;
+	if(player == getBattle()->sides[1])
+		return BattlePerspective::RIGHT_SIDE;
+
+	tlog1 << "Cannot find player " << player << " in battle!\n";
+	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);
+
+	BOOST_FOREACH(auto s, battleGetAllStacks())
+		if(s->ID == ID  &&  (!onlyAlive || s->alive()))
+			return s;
+
+	return nullptr;
+}
+
+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)
+	{
+		tlog1 << "FIXME: " <<  __FUNCTION__ << " wrong argument!" << std::endl;
+		return nullptr;
+	}
+
+	if(!battleDoWeKnowAbout(side))
+	{
+		tlog1 << "FIXME: " <<  __FUNCTION__ << " access check " << std::endl;
+		return nullptr;
+	}
+
+	return getBattle()->heroes[side];
+}
+
+int CBattleInfoEssentials::battleCastSpells(ui8 side) const
+{
+	RETURN_IF_NOT_BATTLE(-1);
+	return getBattle()->castSpells[side];
+}
+
+ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const
+{
+	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
+	const ui8 side = playerToSide(player);
+	if(!battleDoWeKnowAbout(side))
+	{
+		tlog3 << "You can't check if enemy can cast given spell!\n";
+		return ESpellCastProblem::INVALID;
+	}
+
+	switch (mode)
+	{
+	case ECastingMode::HERO_CASTING:
+		{
+			if(battleTacticDist())
+				return ESpellCastProblem::ONGOING_TACTIC_PHASE;
+			if(battleCastSpells(side) > 0)
+				return ESpellCastProblem::ALREADY_CASTED_THIS_TURN;
+
+			auto hero = battleGetFightingHero(side);
+
+			if(!hero)
+				return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
+			if(!hero->getArt(ArtifactPosition::SPELLBOOK))
+				return ESpellCastProblem::NO_SPELLBOOK;
+			if(hero->hasBonusOfType(Bonus::BLOCK_ALL_MAGIC))
+				return ESpellCastProblem::MAGIC_IS_BLOCKED;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return ESpellCastProblem::OK;
+}
+
+bool CBattleInfoEssentials::battleCanFlee(int player) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	ui8 mySide = playerToSide(player);
+	const CGHeroInstance *myHero = battleGetFightingHero(mySide), 
+		*enemyHero = battleGetFightingHero(!mySide);
+
+	//current player have no hero
+	if(!myHero) 
+		return false;
+
+	//TODo use bonus system 
+	//ie. one of heroes is wearing shakles of war
+	if(NBonus::hasOfType(enemyHero, Bonus::ENEMY_CANT_ESCAPE) || NBonus::hasOfType(myHero, Bonus::ENEMY_CANT_ESCAPE))
+		return false;
+
+	//we are besieged defender
+	if(mySide == BattleSide::DEFENDER  &&  battleGetSiegeLevel())
+	{
+		auto town = battleGetDefendedTown();
+		if(!(town->subID == 6  &&  town->hasBuilt(EBuilding::SPECIAL_1))) //not a stronghold with escape tunnel
+			return false;
+	}
+	
+	return true;
+}
+
+ui8 CBattleInfoEssentials::playerToSide(int player) const
+{
+	RETURN_IF_NOT_BATTLE(-1);
+	int ret = vstd::find_pos(getBattle()->sides, player);
+	if(ret < 0)
+		tlog3 << "Cannot find side for player " << player << std::endl;
+
+	return ret;
+}
+
+ui8 CBattleInfoEssentials::battleGetSiegeLevel() const
+{
+	RETURN_IF_NOT_BATTLE(0);
+	return getBattle()->siege;
+}
+
+bool CBattleInfoEssentials::battleCanSurrender(int player) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	//conditions like for fleeing + enemy must have a hero
+	return battleCanFlee(player) && battleGetFightingHero(!playerToSide(player));
+}
+
+bool CBattleInfoEssentials::battleHasHero(ui8 side) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	assert(side >= 0 && side < 2);
+	return getBattle()->heroes[side];
+}
+
+ui8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const
+{
+	RETURN_IF_NOT_BATTLE(0);
+	if(getBattle()->siege == 0)
+		return 0;
+
+	assert(partOfWall >= 0 && partOfWall < EWallParts::PARTS_COUNT);
+	return getBattle()->si.wallState[partOfWall];
+}
+
+si8 CBattleInfoCallback::battleHasWallPenalty( const CStack * stack, BattleHex destHex ) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	if (!battleGetSiegeLevel() || stack->hasBonusOfType(Bonus::NO_WALL_PENALTY))
+		return false;
+
+	const int wallInStackLine = lineToWallHex(stack->position.getY());
+	const int wallInDestLine = lineToWallHex(destHex.getY());
+
+	const bool stackLeft = stack->position < wallInStackLine;
+	const bool destRight = destHex > wallInDestLine;
+
+	if (stackLeft && destRight) //shooting from outside to inside
+	{
+		int row = (stack->position + destHex) / (2 * GameConstants::BFIELD_WIDTH);
+		if (stack->position > destHex && ((destHex % GameConstants::BFIELD_WIDTH - stack->position % GameConstants::BFIELD_WIDTH) < 2)) //shooting up high
+			row -= 2;
+		const int wallPos = lineToWallHex(row);
+		if (battleHexToWallPart(wallPos) != -1) //wall still exists or is indestructible
+			return true;
+	}
+
+	return false;
+}
+
+si8 CBattleInfoCallback::battleCanTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	if(getAccesibility().accessible(destHex, stack))
+		return false;
+
+	if (battleGetSiegeLevel() && telportLevel < 2) //check for wall
+		return sameSideOfWall(stack->position, destHex);
+
+	return true;
+}
+
+// std::vector<int> CBattleInfoCallback::battleGetDistances(const CStack * stack, BattleHex hex /*= BattleHex::INVALID*/, BattleHex * predecessors /*= NULL*/)
+// {
+// 	// FIXME - This method is broken, hex argument is not used. However AI depends on that wrong behaviour.
+// 
+// 	if(!hex.isValid())
+// 		hex = stack->position;
+// 
+// 	std::vector<int> ret(GameConstants::BFIELD_SIZE, -1); //fill initial ret with -1's
+// 
+// 	if(!hex.isValid()) //stack has bad position? probably castle turret, return initial values (they can't move)
+// 		return ret;
+// 
+// 	bool ac[GameConstants::BFIELD_SIZE] = {0};
+// 	std::set<BattleHex> occupyable;
+// 	getBattle()->getAccessibilityMap(ac, stack->doubleWide(), stack->attackerOwned, false, occupyable, stack->hasBonusOfType(Bonus::FLYING), stack);
+// 	BattleHex pr[GameConstants::BFIELD_SIZE];
+// 	int dist[GameConstants::BFIELD_SIZE];
+// 	getBattle()->makeBFS(stack->position, ac, pr, dist, stack->doubleWide(), stack->attackerOwned, stack->hasBonusOfType(Bonus::FLYING), false);
+// 
+// 	for(int i=0; i<GameConstants::BFIELD_SIZE; ++i)
+// 	{
+// 		if(pr[i] != -1)
+// 			ret[i] = dist[i];
+// 	}
+// 
+// 	if(predecessors)
+// 	{
+// 		memcpy(predecessors, pr, GameConstants::BFIELD_SIZE * sizeof(BattleHex));
+// 	}
+// 
+// 	return ret;
+// }
+
+std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos  /*= BattleHex::INVALID*/) const
+{
+	std::set<BattleHex> attackedHexes;
+	RETURN_IF_NOT_BATTLE(attackedHexes);
+
+	AttackableTiles at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos);
+
+	BOOST_FOREACH (BattleHex tile, at.hostileCreaturePositions)
+	{
+		const CStack * st = battleGetStackByPos(tile, true);
+		if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk?
+		{
+			attackedHexes.insert(tile);
+		}
+	}
+	BOOST_FOREACH (BattleHex tile, at.friendlyCreaturePositions)
+	{
+		if(const CStack * st = battleGetStackByPos(tile, true)) //friendly stacks can also be damaged by Dragon Breath
+		{
+			attackedHexes.insert(tile);
+		}
+	}
+	return attackedHexes;
+}
+
+si32 CBattleInfoCallback::battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const
+{
+	switch (mode)
+	{
+	case RANDOM_GENIE:
+		return getRandomBeneficialSpell(stack); //target
+		break;
+	case RANDOM_AIMED:
+		return getRandomCastedSpell(stack); //caster
+		break;
+	default:
+		tlog1 << "Incorrect mode of battleGetRandomSpell (" << mode <<")\n";
+		return -1;
+	}
+}
+
+const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyAlive) const
+{
+	RETURN_IF_NOT_BATTLE(nullptr);
+	BOOST_FOREACH(auto s, battleGetAllStacks())
+		if(vstd::contains(s->getHexes(), pos)  &&  (!onlyAlive || s->alive()))
+			return s;
+
+	return nullptr;
+}
+
+void CBattleInfoCallback::battleGetStackQueue(std::vector<const CStack *> &out, const int howMany, const int turn /*= 0*/, int lastMoved /*= -1*/) const
+{
+	RETURN_IF_NOT_BATTLE();
+
+	//let's define a huge lambda
+	auto takeStack = [&](std::vector<const CStack *> &st) -> const CStack*
+	{
+		const CStack *ret = NULL;
+		unsigned i, //fastest stack
+			j=0; //fastest stack of the other side
+		for(i = 0; i < st.size(); i++)
+			if(st[i])
+				break;
+
+		//no stacks left
+		if(i == st.size())
+			return nullptr;
+
+		const CStack *fastest = st[i], *other = NULL;
+		int bestSpeed = fastest->Speed(turn);
+
+		if(fastest->attackerOwned != lastMoved)
+		{
+			ret = fastest;
+		}
+		else
+		{
+			for(j = i + 1; j < st.size(); j++)
+			{
+				if(!st[j]) continue;
+				if(st[j]->attackerOwned != lastMoved || st[j]->Speed(turn) != bestSpeed)
+					break;
+			}
+
+			if(j >= st.size())
+			{
+				ret = fastest;
+			}
+			else
+			{
+				other = st[j];
+				if(other->Speed(turn) != bestSpeed)
+					ret = fastest;
+				else
+					ret = other;
+			}
+		}
+
+		assert(ret);
+		if(ret == fastest)
+			st[i] = NULL;
+		else
+			st[j] = NULL;
+
+		lastMoved = ret->attackerOwned;
+		return ret;
+	};
+
+	//We'll split creatures with remaining movement to 4 buckets
+	// [0] - turrets/catapult, 
+	// [1] - normal (unmoved) creatures, other war machines, 
+	// [2] - waited cres that had morale, 
+	// [3] - rest of waited cres
+	std::vector<const CStack *> phase[4]; 
+	int toMove = 0; //how many stacks still has move
+	const CStack *active = battleActiveStack();
+
+	//active stack hasn't taken any action yet - must be placed at the beginning of queue, no matter what
+	if(!turn && active && active->willMove() && !vstd::contains(active->state, EBattleStackState::WAITING))
+	{
+		out.push_back(active);
+		if(out.size() == howMany)
+			return;
+	}
+
+	BOOST_FOREACH(auto s, battleGetAllStacks())
+	{
+		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
+		{
+			continue;
+		}
+
+		int p = -1; //in which phase this tack will move?
+		if(turn <= 0 && vstd::contains(s->state, EBattleStackState::WAITING)) //consider waiting state only for ongoing round
+		{
+			if(vstd::contains(s->state, EBattleStackState::HAD_MORALE))
+				p = 2;
+			else
+				p = 3;
+		}
+		else if(s->getCreature()->idNumber == 145  ||  s->getCreature()->idNumber == 149) //catapult and turrets are first
+		{
+			p = 0;
+		}
+		else
+		{
+			p = 1;
+		}
+
+		phase[p].push_back(s);
+		toMove++;
+	}
+
+	for(int i = 0; i < 4; i++)
+		boost::sort(phase[i], CMP_stack(i, turn > 0 ? turn : 0));
+
+	for(size_t i = 0; i < phase[0].size() && i < howMany; i++)
+		out.push_back(phase[0][i]);
+
+	if(out.size() == howMany)
+		return;
+
+	if(lastMoved == -1)
+	{
+		if(active)
+		{
+			if(out.size() && out.front() == active)
+				lastMoved = active->attackerOwned;
+			else
+				lastMoved = active->attackerOwned;
+		}
+		else
+		{
+			lastMoved = 0;
+		}
+	}
+
+	int pi = 1;
+	while(out.size() < howMany)
+	{
+		const CStack *hlp = takeStack(phase[pi]);
+		if(!hlp)
+		{
+			pi++;
+			if(pi > 3)
+			{
+				//if(turn != 2)
+				battleGetStackQueue(out, howMany, turn + 1, lastMoved);
+				return;
+			}
+		}
+		else
+		{
+			out.push_back(hlp);
+		}
+	}
+}
+
+void CBattleInfoCallback::battleGetStackCountOutsideHexes(bool *ac) const
+{
+	RETURN_IF_NOT_BATTLE();
+	auto accessibility = getAccesibility();
+
+	for(int i = 0; i < accessibility.size(); i++)
+		ac[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
+}
+
+std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const CStack * stack, bool addOccupiable, std::vector<BattleHex> * attackable) const
+{
+	std::vector<BattleHex> ret;
+
+	RETURN_IF_NOT_BATTLE(ret);
+	if(!stack->position.isValid()) //turrets
+		return ret;
+
+	auto reachability = getReachability(stack);
+
+	for (int i = 0; i < GameConstants::BFIELD_SIZE; ++i)
+	{
+		// If obstacles or other stacks makes movement impossible, it can't be helped.
+		if(!reachability.isReachable(i))
+			continue;
+
+		if(battleTacticDist() && battleGetTacticsSide() == !stack->attackerOwned)
+		{
+			//Stack has to perform tactic-phase movement -> can enter any reachable tile within given range
+			if(!isInTacticRange(i))
+				continue;
+		}
+		else
+		{
+			//Not tactics phase -> destination must be reachable and within stack range.
+			if(reachability.distances[i] > stack->Speed(0, true))
+				continue;
+		}
+
+		ret.push_back(i);
+
+		if(addOccupiable && stack->doubleWide())
+		{
+			//If two-hex stack can stand on hex i then obviously it can occupy its second hex from that position
+			ret.push_back(stack->occupiedHex(i));
+		}
+	}
+
+
+	if(attackable)
+	{
+		auto meleeAttackable = [&](BattleHex hex) -> bool
+		{
+			// 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 availableNeighbor != ret.end();
+		};
+
+		BOOST_FOREACH(const CStack * otherSt, battleAliveStacks(stack->attackerOwned))
+		{
+			if(!otherSt->isValidTarget(false))
+				continue;
+
+			std::vector<BattleHex> occupied = otherSt->getHexes();
+
+			if(battleCanShoot(stack, otherSt->position))
+			{
+				attackable->insert(attackable->end(), occupied.begin(), occupied.end());
+				continue;
+			}
+
+			BOOST_FOREACH(BattleHex he, occupied)
+			{
+				if(meleeAttackable(he))
+					attackable->push_back(he);
+			}
+		}
+	}
+
+	//adding occupiable likely adds duplicates to ret -> clean it up
+	boost::sort(ret);
+	ret.erase(boost::unique(ret).end(), ret.end());
+	return ret;
+}
+
+bool CBattleInfoCallback::battleCanShoot(const CStack * stack, BattleHex dest) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+
+	if(battleTacticDist()) //no shooting during tactics
+		return false; 
+
+	const CStack *dst = battleGetStackByPos(dest);
+
+	if(!stack || !dst)
+		return false;
+
+	if(stack->hasBonusOfType(Bonus::FORGETFULL)) //forgetfulness
+		return false;
+
+	if(stack->getCreature()->idNumber == 145 && dst) //catapult cannot attack creatures
+		return false;
+
+	//const CGHeroInstance * stackHero = battleGetOwner(stack);
+	if(stack->hasBonusOfType(Bonus::SHOOTER)//it's shooter
+		&& stack->owner != dst->owner
+		&& dst->alive()
+		&& (!battleIsStackBlocked(stack)  ||  stack->hasBonusOfType(Bonus::FREE_SHOOTING))
+		&& stack->shots
+		)
+		return true;
+	return false;
+}
+
+TDmgRange CBattleInfoCallback::calculateDmgRange(const CStack* attacker, const CStack* defender, bool shooting, 
+												 ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const
+{
+	return calculateDmgRange(attacker, defender, attacker->count, shooting, charge, lucky, deathBlow, ballistaDoubleDmg);
+}
+
+TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const CStack* defender, TQuantity attackerCount, 
+	bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg ) const
+{
+	double additiveBonus = 1.0, multBonus = 1.0,
+		minDmg = attacker->getMinDamage() * attackerCount, 
+		maxDmg = attacker->getMaxDamage() * attackerCount;
+
+	if(attacker->getCreature()->idNumber == 149) //arrow turret
+	{
+		switch(attacker->position)
+		{
+		case -2: //keep
+			minDmg = 15;
+			maxDmg = 15;
+			break;
+		case -3: case -4: //turrets
+			minDmg = 7.5;
+			maxDmg = 7.5;
+			break;
+		}
+	}
+
+	if(attacker->hasBonusOfType(Bonus::SIEGE_WEAPON) && attacker->getCreature()->idNumber != 149) //any siege weapon, but only ballista can attack (second condition - not arrow turret)
+	{ //minDmg and maxDmg are multiplied by hero attack + 1
+		auto retreivePrimSkill = [&](int skill) -> int
+		{
+			const Bonus *b = attacker->getBonus(Selector::sourceTypeSel(Bonus::HERO_BASE_SKILL) &&  Selector::typeSubtype(Bonus::PRIMARY_SKILL, skill));
+			return b ? b->val : 0; //if there is no hero or no info on his primary skill, return 0
+		};
+
+
+		minDmg *= retreivePrimSkill(PrimarySkill::ATTACK) + 1; 
+		maxDmg *= retreivePrimSkill(PrimarySkill::ATTACK) + 1; 
+	}
+
+	int attackDefenceDifference = 0;
+	if(attacker->hasBonusOfType(Bonus::GENERAL_ATTACK_REDUCTION))
+	{
+		double multAttackReduction = attacker->valOfBonuses(Bonus::GENERAL_ATTACK_REDUCTION, -1024) / 100.0;
+		attackDefenceDifference = attacker->Attack() * multAttackReduction;
+	}
+	else
+	{
+		attackDefenceDifference = attacker->Attack();
+	}
+
+	if(attacker->hasBonusOfType(Bonus::ENEMY_DEFENCE_REDUCTION))
+	{
+		double multDefenceReduction = (100 - attacker->valOfBonuses(Bonus::ENEMY_DEFENCE_REDUCTION, -1024)) / 100.0;
+		attackDefenceDifference -= defender->Defense() * multDefenceReduction;
+	}
+	else
+	{
+		attackDefenceDifference -= defender->Defense();
+	}
+
+	//calculating total attack/defense skills modifier
+
+	if(shooting) //precision handling (etc.)
+		attackDefenceDifference += attacker->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT))->totalValue();
+	else //bloodlust handling (etc.)
+		attackDefenceDifference += attacker->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))->totalValue();
+
+
+	if(attacker->getEffect(55)) //slayer handling
+	{
+		std::vector<int> affectedIds;
+		int spLevel = attacker->getEffect(55)->val;
+
+		for(int g = 0; g < VLC->creh->creatures.size(); ++g)
+		{
+			BOOST_FOREACH(const 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 +
+				{
+					affectedIds.push_back(g);
+					break;
+				}
+			}
+		}
+
+		for(ui32 g=0; g<affectedIds.size(); ++g)
+		{
+			if(defender->getCreature()->idNumber == affectedIds[g])
+			{
+				attackDefenceDifference += VLC->spellh->spells[55]->powers[attacker->getEffect(55)->val];
+				break;
+			}
+		}
+	}
+
+	//bonus from attack/defense skills
+	if(attackDefenceDifference < 0) //decreasing dmg
+	{
+		const double dec = std::min(0.025 * (-attackDefenceDifference), 0.7);
+		multBonus *= 1.0 - dec;
+	}
+	else //increasing dmg
+	{
+		const double inc = std::min(0.05 * attackDefenceDifference, 4.0);
+		additiveBonus += inc;
+	}
+
+
+	//applying jousting bonus
+	if( attacker->hasBonusOfType(Bonus::JOUSTING) && !defender->hasBonusOfType(Bonus::CHARGE_IMMUNITY) )
+		additiveBonus += charge * 0.05;
+
+
+	//handling secondary abilities and artifacts giving premies to them
+	if(shooting)
+		additiveBonus += attacker->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARCHERY) / 100.0;
+	else
+		additiveBonus += attacker->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::OFFENCE) / 100.0;
+
+	if(defender)
+		multBonus *= (std::max(0, 100 - defender->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARMORER))) / 100.0;
+
+	//handling hate effect
+	additiveBonus += attacker->valOfBonuses(Bonus::HATE, defender->getCreature()->idNumber) / 100.;
+
+	//luck bonus
+	if (lucky)
+	{
+		additiveBonus += 1.0;
+	}
+
+	//ballista double dmg
+	if(ballistaDoubleDmg)
+	{
+		additiveBonus += 1.0;
+	}
+
+	if (deathBlow) //Dread Knight and many WoGified creatures
+	{
+		additiveBonus += 1.0;
+	}
+
+	//handling spell effects
+	if(!shooting && defender->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 0)) //eg. shield
+	{
+		multBonus *= defender->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 0) / 100.0;
+	}
+	else if(shooting && defender->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 1)) //eg. air shield
+	{
+		multBonus *= defender->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 1) / 100.0;
+	}
+
+	TBonusListPtr curseEffects = attacker->getBonuses(Selector::type(Bonus::ALWAYS_MINIMUM_DAMAGE)); //attacker->getEffect(42);
+	TBonusListPtr blessEffects = attacker->getBonuses(Selector::type(Bonus::ALWAYS_MAXIMUM_DAMAGE)); //attacker->getEffect(43);
+	int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue();
+	double curseMultiplicativePenalty = curseEffects->size() ? (*std::max_element(curseEffects->begin(), curseEffects->end(), &Bonus::compareByAdditionalInfo))->additionalInfo : 0;
+
+	if(curseMultiplicativePenalty) //curse handling (partial, the rest is below)
+	{
+		multBonus *= 1.0 - curseMultiplicativePenalty/100;
+	}
+
+	auto isAdvancedAirShield = [](const Bonus *bonus)
+	{
+		return bonus->source == Bonus::SPELL_EFFECT 
+			&& bonus->sid == Spells::AIR_SHIELD 
+			&& bonus->val >= SecSkillLevel::ADVANCED;
+	};
+
+	//wall / distance penalty + advanced air shield
+	const bool distPenalty = !attacker->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY) && battleHasDistancePenalty(attacker, defender->position);
+	const bool obstaclePenalty = battleHasWallPenalty(attacker, defender->position);
+
+	if (shooting)
+	{
+		if (distPenalty || defender->hasBonus(isAdvancedAirShield))
+		{
+			multBonus *= 0.5;
+		}
+		if (obstaclePenalty)
+		{
+			multBonus *= 0.5; //cumulative
+		}
+	}
+	if (!shooting && attacker->hasBonusOfType(Bonus::SHOOTER) && !attacker->hasBonusOfType(Bonus::NO_MELEE_PENALTY))
+	{
+		multBonus *= 0.5;
+	}
+
+	minDmg *= additiveBonus * multBonus;
+	maxDmg *= additiveBonus * multBonus;
+
+	TDmgRange returnedVal;
+
+	if(curseEffects->size()) //curse handling (rest)
+	{
+		minDmg += curseBlessAdditiveModifier;
+		returnedVal = std::make_pair(int(minDmg), int(minDmg));
+	}
+	else if(blessEffects->size()) //bless handling
+	{
+		maxDmg += curseBlessAdditiveModifier;
+		returnedVal =  std::make_pair(int(maxDmg), int(maxDmg));
+	}
+	else
+	{
+		returnedVal =  std::make_pair(int(minDmg), int(maxDmg));
+	}
+
+	//damage cannot be less than 1
+	vstd::amax(returnedVal.first, 1);
+	vstd::amax(returnedVal.second, 1);
+
+	return returnedVal;
+}
+
+TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg) const
+{
+	RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
+
+	const bool shooting = battleCanShoot(attacker, defender->position);
+	const ui8 mySide = !attacker->attackerOwned;
+	
+	TDmgRange ret = calculateDmgRange(attacker, defender, shooting, 0, false, false, false);
+
+	if(retaliationDmg)
+	{
+		if(shooting)
+		{
+			retaliationDmg->first = retaliationDmg->second = 0;
+		}
+		else
+		{
+			ui32 TDmgRange::* pairElems[] = {&TDmgRange::first, &TDmgRange::second};
+			for (int i=0; i<2; ++i)
+			{
+				BattleStackAttacked bsa;
+				bsa.damageAmount = ret.*pairElems[i];
+				retaliationDmg->*pairElems[!i] = calculateDmgRange(defender, attacker, bsa.newAmount, false, 0, false, false, false).*pairElems[!i];
+			}
+		}
+	}
+
+	return ret;
+}
+
+
+shared_ptr<const CObstacleInstance> CBattleInfoCallback::battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking /*= true*/) const
+{
+	RETURN_IF_NOT_BATTLE(shared_ptr<const CObstacleInstance>());
+
+	BOOST_FOREACH(auto &obs, battleGetAllObstacles())
+	{
+		if(vstd::contains(obs->getBlockedTiles(), tile)
+			|| (!onlyBlocking  &&  vstd::contains(obs->getAffectedTiles(), tile)))
+		{
+			return obs;
+		}
+	}
+
+	return shared_ptr<const CObstacleInstance>();
+}
+
+AccessibilityInfo CBattleInfoCallback::getAccesibility() const
+{
+	AccessibilityInfo ret;
+	ret.fill(EAccessibility::ACCESSIBLE);
+
+	//removing accessibility for side columns of hexes
+	for(int y = 0; y < GameConstants::BFIELD_HEIGHT; y++)
+	{
+		ret[BattleHex(GameConstants::BFIELD_WIDTH - 1, y)] = EAccessibility::SIDE_COLUMN;
+		ret[BattleHex(0, y)] = EAccessibility::SIDE_COLUMN;
+	}
+
+	//tiles occupied by standing stacks
+	BOOST_FOREACH(auto stack, battleAliveStacks())
+	{
+		BOOST_FOREACH(auto hex, stack->getHexes())
+			if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns
+				ret[hex] = EAccessibility::ALIVE_STACK;
+	}
+
+	//obstacles
+	BOOST_FOREACH(const auto &obst, battleGetAllObstacles())
+	{
+		BOOST_FOREACH(auto hex, obst->getBlockedTiles())
+			ret[hex] = EAccessibility::OBSTACLE;
+	}
+
+	//walls
+	if(battleGetSiegeLevel() > 0)
+	{
+		static const int permanentlyLocked[] = {12, 45, 78, 112, 147, 165};
+		BOOST_FOREACH(auto hex, permanentlyLocked)
+			ret[hex] = EAccessibility::UNAVAILABLE;
+
+		//TODO likely duplicated logic
+		static const std::pair<int, BattleHex> lockedIfNotDestroyed[] = //(which part of wall, which hex is blocked if this part of wall is not destroyed
+			{std::make_pair(2, BattleHex(182)), std::make_pair(3, BattleHex(130)),
+			std::make_pair(4, BattleHex(62)), std::make_pair(5, BattleHex(29))};
+
+		for(int b=0; b<ARRAY_COUNT(lockedIfNotDestroyed); ++b)
+		{
+			if(battleGetWallState(lockedIfNotDestroyed[b].first) < 3)
+				ret[lockedIfNotDestroyed[b].second] = EAccessibility::DESTRUCTIBLE_WALL;
+		}
+
+		//gate
+		if(battleGetWallState(7) < 3) //if it attacker's unit and gate is not destroyed
+		{
+			ret[95] = ret[96] = EAccessibility::GATE; //block gate's hexes
+		}
+	}
+
+	return ret;
+}
+
+ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibility, const ReachabilityInfo::Parameters params) const
+{
+	ReachabilityInfo ret;
+	ret.accessibility = accessibility;
+	ret.params = params;
+
+	ret.predecessors.fill(BattleHex::INVALID);
+	ret.distances.fill(ReachabilityInfo::INFINITE_DIST);
+
+	const std::set<BattleHex> quicksands = getStoppers(params.perspective);
+	//const bool twoHexCreature = params.doubleWide;
+
+
+	std::queue<BattleHex> hexq; //bfs queue
+
+	//first element
+	hexq.push(params.startPosition);
+	ret.distances[params.startPosition] = 0;
+
+	while(!hexq.empty()) //bfs loop
+	{
+		const BattleHex curHex = hexq.front();
+		hexq.pop();
+
+		//walking stack can't step past the quicksands
+		//TODO what if second hex of two-hex creature enters quicksand
+		if(curHex != params.startPosition && vstd::contains(quicksands, curHex)) 
+			continue;
+
+		const int costToNeighbour = ret.distances[curHex] + 1;
+		BOOST_FOREACH(BattleHex neighbour, curHex.neighbouringTiles())
+		{
+			const bool accessible = accessibility.accessible(neighbour, params.doubleWide, params.attackerOwned);
+			const int costFoundSoFar = ret.distances[neighbour];
+
+			if(accessible  &&  costToNeighbour < costFoundSoFar)
+			{
+				hexq.push(neighbour);
+				ret.distances[neighbour] = costToNeighbour;
+				ret.predecessors[neighbour] = curHex;
+			}
+		}
+	}
+
+	return ret;
+}
+
+ReachabilityInfo CBattleInfoCallback::makeBFS(const CStack *stack) const
+{
+	return makeBFS(getAccesibility(), ReachabilityInfo::Parameters(stack));
+}
+
+std::set<BattleHex> CBattleInfoCallback::getStoppers(BattlePerspective::BattlePerspective whichSidePerspective) const
+{
+	std::set<BattleHex> ret;
+	RETURN_IF_NOT_BATTLE(ret);
+
+	BOOST_FOREACH(auto &oi, battleGetAllObstacles(whichSidePerspective))
+	{
+		if(battleIsObstacleVisibleForSide(*oi, whichSidePerspective))
+		{
+			range::copy(oi->getStoppingTile(), vstd::set_inserter(ret));
+		}
+	}
+
+	return ret;
+}
+
+std::pair<const CStack *, BattleHex> CBattleInfoCallback::getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const
+{
+	auto reachability = getReachability(closest);
+
+	// I hate std::pairs with their undescriptive member names first / second
+	struct DistStack
+	{
+		int distanceToPred;
+		const CStack *stack;
+	};
+
+	std::vector<DistStack> stackPairs; //pairs <<distance, hex>, stack>
+	for(int g=0; g<GameConstants::BFIELD_SIZE; ++g)
+	{
+		const CStack * atG = battleGetStackByPos(g);
+		if(!atG || atG->ID == closest->ID) //if there is not stack or we are the closest one
+			continue;
+
+		if(boost::logic::indeterminate(attackerOwned) || atG->attackerOwned == attackerOwned)
+		{
+			if(reachability.isReachable(g))
+				continue;
+
+			DistStack hlp = {reachability.distances[reachability.predecessors[g]], atG};
+			stackPairs.push_back(hlp);
+		}
+	}
+
+	if(stackPairs.size() > 0)
+	{
+		auto comparator = [](DistStack lhs, DistStack rhs) { return lhs.distanceToPred < rhs.distanceToPred; };
+		auto minimal = boost::min_element(stackPairs, comparator);
+		return std::make_pair(minimal->stack, reachability.predecessors[minimal->stack->position]);
+	}
+
+	return std::make_pair<const CStack * , BattleHex>(NULL, BattleHex::INVALID);
+}
+
+si8 CBattleInfoCallback::battleGetTacticDist() const
+{
+	RETURN_IF_NOT_BATTLE(0);
+
+	//TODO get rid of this method
+	if(battleDoWeKnowAbout(battleGetTacticsSide()))
+		return battleTacticDist();
+
+	return 0;
+}
+
+bool CBattleInfoCallback::isInTacticRange(BattleHex dest) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	auto side = battleGetTacticsSide();
+	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));
+}
+
+ReachabilityInfo CBattleInfoCallback::getReachability(const CStack *stack) const
+{
+	ReachabilityInfo::Parameters params(stack);
+
+	if(!battleDoWeKnowAbout(!stack->attackerOwned))
+	{
+		//Stack is held by enemy, we can't use his perspective to check for reachability.
+		tlog3 << "Falling back to our perspective for reachability lookup for " << stack->nodeName() << std::endl;
+		params.perspective = battleGetMySide();
+	}
+
+	return getReachability(params);
+}
+
+ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Parameters &params) const
+{
+	if(params.flying)
+		return getFlyingReachability(params);
+	else
+		return makeBFS(getAccesibility(), params);
+}
+
+ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters params) const
+{
+	ReachabilityInfo ret;
+	ret.accessibility = getAccesibility();
+
+	for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
+	{
+		if(ret.accessibility.accessible(i, params.doubleWide, params.attackerOwned))
+		{
+			ret.predecessors[i] = params.startPosition;
+			ret.distances[i] = BattleHex::getDistance(params.startPosition, i);
+		}
+	}
+
+	return ret;
+}
+
+AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) const
+{
+	AttackableTiles at;
+	RETURN_IF_NOT_BATTLE(at);
+
+	const int WN = GameConstants::BFIELD_WIDTH;
+	ui16 hex = (attackerPos != BattleHex::INVALID) ? attackerPos.hex : attacker->position.hex; //real or hypothetical (cursor) position
+	if (attacker->hasBonusOfType(Bonus::ATTACKS_ALL_ADJACENT))
+	{
+		boost::copy(attacker->getSurroundingHexes(attackerPos), vstd::set_inserter(at.hostileCreaturePositions));
+// 		BOOST_FOREACH (BattleHex tile, attacker->getSurroundingHexes(attackerPos))
+// 			at.hostileCreaturePositions.insert(tile);
+	}
+	if (attacker->hasBonusOfType(Bonus::THREE_HEADED_ATTACK))
+	{
+		std::vector<BattleHex> hexes = attacker->getSurroundingHexes(attackerPos);
+		BOOST_FOREACH (BattleHex tile, hexes)
+		{
+			if ((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition(tile, hex) > -1) //adjacent both to attacker's head and attacked tile
+				|| tile == destinationTile) //or simply attacked directly
+			{
+				const CStack * st = battleGetStackByPos(tile, true);
+				if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk?
+				{
+					at.hostileCreaturePositions.insert(tile);
+				}
+			}
+		}
+	}
+	if (attacker->hasBonusOfType(Bonus::TWO_HEX_ATTACK_BREATH))
+	{
+		std::vector<BattleHex> hexes; //only one, in fact
+		int pseudoVector = destinationTile.hex - hex;
+		switch (pseudoVector)
+		{
+		case 1:
+		case -1:
+			BattleHex::checkAndPush(destinationTile.hex + pseudoVector, hexes);
+			break;
+		case WN: //17
+		case WN + 1: //18
+		case -WN: //-17
+		case -WN + 1: //-16
+			BattleHex::checkAndPush(destinationTile.hex + pseudoVector + ((hex/WN)%2 ? 1 : -1 ), hexes);
+			break;
+		case WN-1: //16
+		case -WN-1: //-18
+			BattleHex::checkAndPush(destinationTile.hex + pseudoVector + ((hex/WN)%2 ? 1 : 0), hexes);
+			break;
+		}
+		BOOST_FOREACH (BattleHex tile, hexes)
+		{
+			//friendly stacks can also be damaged by Dragon Breath
+			if(const CStack * st = battleGetStackByPos(tile, true)) 
+				at.friendlyCreaturePositions.insert(tile);
+		}
+	}
+
+	return at;
+}
+
+std::set<const CStack*> CBattleInfoCallback::getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos /*= BattleHex::INVALID*/) const
+{
+	std::set<const CStack*> attackedCres;
+	RETURN_IF_NOT_BATTLE(attackedCres);
+
+	AttackableTiles at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos);
+	BOOST_FOREACH (BattleHex tile, at.hostileCreaturePositions) //all around & three-headed attack
+	{
+		const CStack * st = battleGetStackByPos(tile, true);
+		if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk?
+		{
+			attackedCres.insert(st);
+		}
+	}
+	BOOST_FOREACH (BattleHex tile, at.friendlyCreaturePositions)
+	{
+		const CStack * st = battleGetStackByPos(tile, true);
+		if(st) //friendly stacks can also be damaged by Dragon Breath
+		{
+			attackedCres.insert(st);
+		}
+	}
+	return attackedCres;
+}
+
+ReachabilityInfo::TDistances CBattleInfoCallback::battleGetDistances(const CStack * stack, BattleHex hex /*= BattleHex::INVALID*/, BattleHex * predecessors /*= NULL*/) const
+{
+	ReachabilityInfo::TDistances ret;
+	ret.fill(-1);
+	RETURN_IF_NOT_BATTLE(ret);
+
+	ReachabilityInfo::Parameters params(stack);
+	params.startPosition = hex.isValid() ? hex : stack->position;
+	auto reachability = getReachability(params);
+
+	boost::copy(reachability.distances, ret.begin());
+
+	if(predecessors)
+		for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
+			predecessors[i] = reachability.predecessors[i];
+
+	return ret;
+}
+
+si8 CBattleInfoCallback::battleHasDistancePenalty(const CStack * stack, BattleHex destHex) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	if(stack->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY))
+		return false;
+
+	if(BattleHex::getDistance(stack->position, destHex) <= 10)
+		return false;
+
+	const CStack * dstStack = battleGetStackByPos(destHex, false);
+	if(dstStack)
+	{
+		//If on dest hex stands stack that occupies a hex within our distance
+		BOOST_FOREACH(auto hex, dstStack->getHexes())
+			if(BattleHex::getDistance(stack->position, hex) <= 10)
+				return false;
+	}
+
+	return true;
+}
+
+EWallParts::EWallParts CBattleInfoCallback::battleHexToWallPart(BattleHex hex) const
+{
+	RETURN_IF_NOT_BATTLE(EWallParts::INVALID);
+	return hexToWallPart(hex);
+}
+
+ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const
+{
+	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
+
+
+	// Get stack at destination hex -> subject of our spell.
+	const CStack * subject = battleGetStackByPos(dest, !spell->isRisingSpell()); //only alive if not rising spell
+
+	if(subject)
+	{
+		if (spell->isPositive() && subject->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
+			return ESpellCastProblem::OK;
+
+		switch (spell->id) //TODO: more general logic for new spells?
+		{
+		case Spells::DESTROY_UNDEAD:
+			if (!subject->hasBonusOfType(Bonus::UNDEAD))
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+			break;
+		case Spells::DEATH_RIPPLE:
+			if (subject->hasBonusOfType(Bonus::SIEGE_WEAPON))
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //don't break here - undeads and war machines are immune, non-living are not
+		case Spells::BLESS:
+		case Spells::CURSE: //undeads are immune to bless & curse
+			if (subject->hasBonusOfType(Bonus::UNDEAD))
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; 
+			break;
+		case Spells::HASTE:
+		case Spells::SLOW:
+		case Spells::TELEPORT:
+		case Spells::CLONE:
+			if (subject->hasBonusOfType(Bonus::SIEGE_WEAPON))
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //war machines are immune to some spells than involve movement
+			if (spell->id == Spells::CLONE && caster) //TODO: how about stacks casting Clone?
+			{
+				if (vstd::contains(subject->state, EBattleStackState::CLONED))
+					return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; //can't clone already cloned creature
+				int maxLevel = (std::max(caster->getSpellSchoolLevel(spell), (ui8)1) + 4);
+				int creLevel = subject->getCreature()->level;
+				if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, 1-7 for expert
+					return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+			}
+			break;
+		case Spells::FORGETFULNESS:
+			if (!subject->hasBonusOfType(Bonus::SHOOTER))
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+			break;
+		case Spells::DISPEL_HELPFUL_SPELLS:
+			{
+				TBonusListPtr spellBon = subject->getSpellBonuses();
+				bool hasPositiveSpell = false;
+				BOOST_FOREACH(const Bonus * b, *spellBon)
+				{
+					if(VLC->spellh->spells[b->sid]->isPositive())
+					{
+						hasPositiveSpell = true;
+						break;
+					}
+				}
+				if(!hasPositiveSpell)
+				{
+					return ESpellCastProblem::NO_SPELLS_TO_DISPEL;
+				}
+			}
+			break;
+		}
+
+		const bool damageSpell = spell->isDamageSpell();
+		auto battleTestElementalImmunity = [&](Bonus::BonusType element) -> bool //helper for battleisImmune
+		{
+			if (!spell->isPositive()) //negative or indifferent
+			{
+				if ((damageSpell && subject->hasBonusOfType(element, 2)) || subject->hasBonusOfType(element, 1))
+					return true;
+			}
+			else if (spell->isPositive()) //positive
+			{
+				if (subject->hasBonusOfType(element, 0)) //must be immune to all spells
+					return true;
+			}
+			return false;
+		};
+
+
+		if (damageSpell && subject->hasBonusOfType(Bonus::DIRECT_DAMAGE_IMMUNITY))
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+
+		if (spell->fire)
+		{
+			if (battleTestElementalImmunity(Bonus::FIRE_IMMUNITY))
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+		}
+		if (spell->water)
+		{
+			if (battleTestElementalImmunity(Bonus::WATER_IMMUNITY))
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+		}
+		if (spell->earth)
+		{
+			if (battleTestElementalImmunity(Bonus::EARTH_IMMUNITY))
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+		}
+		if (spell->air)
+		{
+			if (battleTestElementalImmunity(Bonus::AIR_IMMUNITY))
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+		}
+		if (vstd::contains(VLC->spellh->mindSpells, spell->id))
+		{
+			if (subject->hasBonusOfType(Bonus::MIND_IMMUNITY))
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+		}
+
+		if (spell->isRisingSpell())
+		{
+			if (subject->count >= subject->baseAmount) //TODO: calculate potential hp raised
+				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+		}
+
+		TBonusListPtr immunities = subject->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY));
+		if(subject->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES))
+		{
+			immunities->remove_if([](const Bonus* b){  return b->source == Bonus::CREATURE_ABILITY;  });
+		}
+
+		if(subject->hasBonusOfType(Bonus::SPELL_IMMUNITY, spell->id) 
+			|| ( immunities->size() > 0  &&  immunities->totalValue() >= spell->level  &&  spell->level))
+		{ 
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+		}
+	}
+	else //no target stack on this tile
+	{
+		if(spell->getTargetType() == CSpell::CREATURE 
+			|| (spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE 
+				&& mode == ECastingMode::HERO_CASTING 
+				&& caster
+				&& caster->getSpellSchoolLevel(spell) < SecSkillLevel::EXPERT))
+		{
+			return ESpellCastProblem::WRONG_SPELL_TARGET;
+		}
+	}
+
+	return ESpellCastProblem::OK;
+}
+
+ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( int player, const CSpell * spell, ECastingMode::ECastingMode mode ) const
+{
+	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
+	const ui8 side = playerToSide(player);
+	if(!battleDoWeKnowAbout(side))
+		ESpellCastProblem::INVALID;
+
+	ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(player, mode);
+	if(genProblem != ESpellCastProblem::OK)
+		return genProblem;
+
+	//Casting hero, set only if he is an actual caster.
+	const CGHeroInstance *castingHero = mode == ECastingMode::HERO_CASTING
+										? battleGetFightingHero(side)
+										: nullptr;
+
+
+	switch(mode)
+	{
+	case ECastingMode::HERO_CASTING:
+		{
+			assert(castingHero);
+			if(!castingHero->canCastThisSpell(spell))
+				return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL;
+			if(castingHero->mana < battleGetSpellCost(spell, castingHero)) //not enough mana
+				return ESpellCastProblem::NOT_ENOUGH_MANA;
+		}
+		break;
+	}
+
+
+	if(spell->id < 10) //it's adventure spell (not combat))
+		return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
+
+	//TODO?
+	//if(NBonus::hasOfType(heroes[1-cside], Bonus::SPELL_IMMUNITY, spell->id)) //non - casting hero provides immunity for this spell 
+	//	return ESpellCastProblem::SECOND_HEROS_SPELL_IMMUNITY;
+	if(spell->isNegative())
+	{
+		bool allEnemiesImmune = true;
+		BOOST_FOREACH(auto enemyStack, battleAliveStacks(!side))
+		{
+			if(!enemyStack->hasBonusOfType(Bonus::SPELL_IMMUNITY, spell->id))
+			{
+				allEnemiesImmune = false;
+				break;
+			}
+		}
+
+		if(allEnemiesImmune)
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+	}
+
+
+	if(battleMaxSpellLevel() < spell->level) //effect like Recanter's Cloak or Orb of Inhibition
+		return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED;
+
+	//IDs of summon elemental spells (fire, earth, water, air)
+	int spellIDs[] = {	Spells::SUMMON_FIRE_ELEMENTAL, Spells::SUMMON_EARTH_ELEMENTAL, 
+						Spells::SUMMON_WATER_ELEMENTAL, Spells::SUMMON_AIR_ELEMENTAL }; 
+	//(fire, earth, water, air) elementals
+	int creIDs[] = {114, 113, 115, 112}; 
+
+	int arpos = vstd::find_pos(spellIDs, spell->id);
+	if(arpos < ARRAY_COUNT(spellIDs))
+	{
+		//check if there are summoned elementals of other type
+		BOOST_FOREACH( const CStack * st, battleAliveStacks())
+			if(vstd::contains(st->state, EBattleStackState::SUMMONED) && st->getCreature()->idNumber == creIDs[arpos])
+				return ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED;
+	}
+
+	//checking if there exists an appropriate target
+	switch(spell->getTargetType())
+	{
+	case CSpell::CREATURE:
+	case CSpell::CREATURE_EXPERT_MASSIVE:
+		if(mode == ECastingMode::HERO_CASTING)
+		{
+			const CGHeroInstance * caster = battleGetFightingHero(player);
+			bool targetExists = false;
+			BOOST_FOREACH(const CStack * stack, battleAliveStacks())
+			{
+				switch (spell->positiveness)
+				{
+				case CSpell::POSITIVE:
+					if(stack->owner == caster->getOwner())
+					{
+						if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
+						{
+							targetExists = true;
+							break;
+						}
+					}
+					break;
+				case CSpell::NEUTRAL:
+					if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
+					{
+						targetExists = true;
+						break;
+					}
+					break;
+				case CSpell::NEGATIVE:
+					if(stack->owner != caster->getOwner())
+					{
+						if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
+						{
+							targetExists = true;
+							break;
+						}
+					}
+					break;
+				}
+			}
+			if(!targetExists)
+			{
+				return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+			}
+		}
+		break;
+	case CSpell::OBSTACLE:
+		break;
+	}
+
+	return ESpellCastProblem::OK;
+}
+
+ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
+{
+	RETURN_IF_NOT_BATTLE(-1);
+	//TODO should be replaced using bonus system facilities (propagation onto battle node)
+
+	ui32 ret = caster->getSpellCost(sp);
+
+	//checking for friendly stacks reducing cost of the spell and
+	//enemy stacks increasing it
+	si32 manaReduction = 0;
+	si32 manaIncrease = 0;
+
+
+	BOOST_FOREACH(auto stack, battleAliveStacks())
+	{
+		if(stack->owner == caster->tempOwner  &&  stack->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ALLY) )
+		{
+			vstd::amax(manaReduction, stack->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ALLY));
+		}
+		if( stack->owner != caster->tempOwner  &&  stack->hasBonusOfType(Bonus::CHANGES_SPELL_COST_FOR_ENEMY) )
+		{
+			vstd::amax(manaIncrease, stack->valOfBonuses(Bonus::CHANGES_SPELL_COST_FOR_ENEMY));
+		}
+	}
+
+	return ret - manaReduction + manaIncrease;
+}
+
+ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpellHere( int player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest ) const
+{
+	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
+	ESpellCastProblem::ESpellCastProblem moreGeneralProblem = battleCanCastThisSpell(player, spell, mode);
+	if(moreGeneralProblem != ESpellCastProblem::OK)
+		return moreGeneralProblem;
+
+	if(spell->getTargetType() == CSpell::OBSTACLE)
+	{
+		//isObstacleOnTile(dest)
+		// 
+		// 
+		//TODO
+		//assert that it's remove obstacle
+		//rules whether we can remove spell-created obstacle
+		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+	}
+
+
+	//get dead stack if we cast resurrection or animate dead
+	const CStack *deadStack = getStackIf([dest](const CStack *s) { return !s->alive() && s->position == dest; });
+	const CStack *aliveStack = getStackIf([dest](const CStack *s) { return s->alive() && s->position == dest;});
+
+
+	if(spell->isRisingSpell())
+	{
+		if(!deadStack && !aliveStack)
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+		if(spell->id == Spells::ANIMATE_DEAD  &&  deadStack  &&  !deadStack->hasBonusOfType(Bonus::UNDEAD)) 
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+		if(deadStack && deadStack->owner != player) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+	}
+	else if(spell->getTargetType() == CSpell::CREATURE  ||  spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE)
+	{
+		if(!aliveStack)
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+		if(spell->isNegative() && aliveStack->owner == player)
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+		if(spell->isPositive() && aliveStack->owner != player)
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+	}
+
+
+	if (mode == ECastingMode::HERO_CASTING)
+		return battleIsImmune(battleGetFightingHero(playerToSide(player)), spell, mode, dest);
+	else
+		return battleIsImmune(NULL, spell, mode, dest);
+}
+
+const CStack * CBattleInfoCallback::getStackIf(boost::function<bool(const CStack*)> pred) const
+{
+	RETURN_IF_NOT_BATTLE(nullptr);
+	auto stacks = battleGetAllStacks();
+	auto stackItr = range::find_if(stacks, pred);
+	return stackItr == stacks.end() 
+		? NULL
+		: *stackItr;
+}
+
+bool CBattleInfoCallback::battleIsStackBlocked(const CStack * stack) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+
+	if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON)) //siege weapons cannot be blocked
+		return false;
+
+	BOOST_FOREACH(const CStack * s,  batteAdjacentCreatures(stack))
+	{
+		if (s->owner != stack->owner) //blocked by enemy stack
+			return true;
+	}
+	return false;
+}
+
+std::set<const CStack*> CBattleInfoCallback:: batteAdjacentCreatures(const CStack * stack) const
+{
+	std::set<const CStack*> stacks;
+	RETURN_IF_NOT_BATTLE(stacks);
+
+	BOOST_FOREACH (BattleHex hex, stack->getSurroundingHexes())
+		if(const CStack *neighbour = battleGetStackByPos(hex, true))
+			stacks.insert(neighbour);
+
+	return stacks;
+}
+
+TSpell CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) const
+{
+	RETURN_IF_NOT_BATTLE(-1);
+	std::vector<TSpell> possibleSpells;
+
+	BOOST_FOREACH(const CSpell *spell, VLC->spellh->spells)
+	{
+		if (spell->isPositive()) //only positive
+		{
+			if (subject->hasBonusFrom(Bonus::SPELL_EFFECT, spell->id) 
+				|| battleCanCastThisSpellHere(subject->owner, spell, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
+				continue;
+
+			switch (spell->id)
+			{
+			case Spells::SHIELD:
+			case Spells::FIRE_SHIELD: // not if all enemy units are shooters
+				{
+					auto walker = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack
+					{
+						return stack->owner != subject->owner && !stack->shots;
+					});
+
+					if (!walker)
+						continue;
+				}
+				break;
+			case Spells::AIR_SHIELD: //only against active shooters
+				{
+
+					auto shooter = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack
+					{
+						return stack->owner != subject->owner && stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots;
+					});
+					if (!shooter)
+						continue;
+				}
+				break;
+			case Spells::ANTI_MAGIC:
+			case Spells::MAGIC_MIRROR:
+				{
+					if (!battleHasHero(subject->attackerOwned)) //only if there is enemy hero
+						continue;
+				}
+				break;
+			case Spells::CURE: //only damaged units - what about affected by curse?
+				{
+					if (subject->firstHPleft >= subject->MaxHealth())
+						continue;
+				}
+				break;
+			case Spells::BLOODLUST:
+				{
+					if (subject->shots) //if can shoot - only if enemy uits are adjacent
+						continue;
+				}
+				break;
+			case Spells::PRECISION:
+				{
+					if (!(subject->hasBonusOfType(Bonus::SHOOTER) && subject->shots))
+						continue;
+				}
+				break;
+			case Spells::SLAYER://only if monsters are present
+				{
+					auto kingMonster = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack
+					{
+						return stack->owner != subject->owner 
+							&& (stack->hasBonus(Selector::type(Bonus::KING1) || Selector::type(Bonus::KING2) || Selector::type(Bonus::KING3)));
+					});
+
+					if (!kingMonster)
+						continue;
+				}
+				break;
+			case Spells::CLONE: //not allowed
+				continue;
+				break;
+			}
+			possibleSpells.push_back(spell->id);
+		}
+	}
+
+	if (possibleSpells.size())
+		return possibleSpells[rand() % possibleSpells.size()];
+	else
+		return -1;
+}
+
+TSpell CBattleInfoCallback::getRandomCastedSpell(const CStack * caster) const
+{
+	RETURN_IF_NOT_BATTLE(-1);
+
+	TBonusListPtr bl = caster->getBonuses(Selector::type(Bonus::SPELLCASTER));
+	if (!bl->size())
+		return -1;
+	int totalWeight = 0;
+	BOOST_FOREACH(Bonus * b, *bl)
+	{
+		totalWeight += std::max(b->additionalInfo, 1); //minimal chance to cast is 1
+	}
+	int randomPos = rand() % totalWeight;
+	BOOST_FOREACH(Bonus * b, *bl)
+	{
+		randomPos -= std::max(b->additionalInfo, 1);
+		if(randomPos < 0)
+		{
+			return b->subtype;
+		}
+	}
+
+	return -1;
+}
+
+int CBattleInfoCallback::battleGetSurrenderCost(int Player) const
+{
+	RETURN_IF_NOT_BATTLE(-3);
+	if(!battleCanSurrender(Player))
+		return -1;
+
+	int ret = 0;
+	double discount = 0;
+	BOOST_FOREACH(const CStack *s, battleAliveStacks(playerToSide(Player)))
+		if(s->base) //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines
+			ret += s->getCreature()->cost[Res::GOLD] * s->count;
+
+	if(const CGHeroInstance *h = battleGetFightingHero(playerToSide(Player)))
+		discount += h->valOfBonuses(Bonus::SURRENDER_DISCOUNT);
+
+	ret *= (100.0 - discount) / 100.0;
+	vstd::amax(ret, 0); //no negative costs for >100% discounts (impossible in original H3 mechanics, but some day...)
+	return ret;
+}
+
+si8 CBattleInfoCallback::battleMaxSpellLevel() const
+{
+	const CBonusSystemNode *node = NULL;
+	if(const CGHeroInstance *h =  battleGetFightingHero(battleGetMySide()))
+		node = h;
+	//TODO else use battle node
+	if(!node)
+		return GameConstants::SPELL_LEVELS; 
+
+	//We can't "just get value" - it'd be 0 if there are bonuses (and all would be blocked)
+	auto b = node->getBonuses(Selector::type(Bonus::BLOCK_MAGIC_ABOVE));
+	if(b->size())
+		return b->totalValue();
+
+	return GameConstants::SPELL_LEVELS;
+}
+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.
+	BOOST_FOREACH(auto hex, CStack::getHexes(tile, doubleWide, attackerOwned))
+		if(!hex.isValid() || at(hex) != EAccessibility::ACCESSIBLE)
+			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);
+}
+
+ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpell(const CSpell * spell) const
+{
+	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
+	return CBattleInfoCallback::battleCanCastThisSpell(player, spell, ECastingMode::HERO_CASTING);
+}
+
+ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpell(const CSpell * spell, BattleHex destination) const
+{
+	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
+	return battleCanCastThisSpellHere(player, spell, ECastingMode::HERO_CASTING, destination);
+}
+
+ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const
+{
+	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
+	return battleCanCastThisSpellHere(player, spell, ECastingMode::CREATURE_ACTIVE_CASTING, destination);
+}
+
+bool CPlayerBattleCallback::battleCanFlee() const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	return CBattleInfoEssentials::battleCanFlee(player);
+}
+
+TStacks CPlayerBattleCallback::battleGetStacks(EStackOwnership whose /*= MINE_AND_ENEMY*/, bool onlyAlive /*= true*/) const
+{
+	TStacks ret;
+	RETURN_IF_NOT_BATTLE(ret);
+	vstd::copy_if(battleGetAllStacks(), std::back_inserter(ret), [=](const CStack *s) -> bool
+	{
+		const bool ownerMatches = (whose == MINE_AND_ENEMY) 
+			|| (whose == ONLY_MINE && s->owner == player) 
+			|| (whose == ONLY_ENEMY && s->owner != player);
+		const bool alivenessMatches = s->alive()  ||  !onlyAlive;
+		return ownerMatches && alivenessMatches;
+	});
+
+	return ret;
+}
+
+int CPlayerBattleCallback::battleGetSurrenderCost() const
+{
+	RETURN_IF_NOT_BATTLE(-3)
+	return CBattleInfoCallback::battleGetSurrenderCost(player);
+}
+
+bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem /*= nullptr*/) const
+{
+	RETURN_IF_NOT_BATTLE(false);
+	auto problem = CBattleInfoCallback::battleCanCastSpell(player, ECastingMode::HERO_CASTING);
+	if(outProblem)
+		*outProblem = problem;
+
+	return problem == ESpellCastProblem::OK;
+}

+ 269 - 0
lib/CBattleCallback.h

@@ -0,0 +1,269 @@
+#pragma once
+#include "BattleHex.h"
+
+class CGameState;
+class CGTownInstance;
+class CGHeroInstance;
+class CStack;
+class CSpell;
+struct BattleInfo;
+struct CObstacleInstance;
+
+namespace boost
+{class shared_mutex;}
+
+namespace BattleSide
+{
+	enum {ATTACKER = 0, DEFENDER = 1};
+}
+
+typedef std::vector<const CStack*> TStacks;
+
+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, NULL if none or not applicable
+
+	const BattleInfo * getBattle() const
+	{
+		return battle;
+	}
+
+protected:
+	CGameState *gs;
+	int player; // -1 gives access to all information, otherwise callback provides only information "visible" for player
+
+	CCallbackBase(CGameState *GS, int Player)
+		: gs(GS), player(Player)
+	{}
+	CCallbackBase()
+		: gs(NULL), player(-1)
+	{}
+	
+	void setBattle(const BattleInfo *B);
+	bool duringBattle() const;
+
+public:
+	boost::shared_mutex &getGsMutex(); //just return a reference to mutex, does not lock nor anything
+
+	friend class CBattleInfoEssentials;
+};
+
+
+struct DLL_LINKAGE AttackableTiles
+{
+	std::set<BattleHex> hostileCreaturePositions;
+	std::set<BattleHex> friendlyCreaturePositions; //for Dragon Breath
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & hostileCreaturePositions & friendlyCreaturePositions;
+	}
+};
+
+//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 dependant 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;
+
+	static const int 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;
+
+		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 predecessors[hex].isValid();
+	}
+};
+
+class DLL_LINKAGE CBattleInfoEssentials : public virtual CCallbackBase
+{
+protected:
+	bool battleDoWeKnowAbout(ui8 side) const;
+public:
+	enum EStackOwnership
+	{
+		ONLY_MINE, ONLY_ENEMY, MINE_AND_ENEMY
+	};
+
+	BattlePerspective::BattlePerspective battleGetMySide() const;
+
+	ui8 battleTerrainType() const;
+	int battleGetBattlefieldType() const; //   1. sand/shore   2. sand/mesas   3. dirt/birches   4. dirt/hills   5. dirt/pines   6. grass/hills   7. grass/pines   8. lava   9. magic plains   10. snow/mountains   11. snow/trees   12. subterranean   13. swamp/trees   14. fiery fields   15. rock lands   16. magic clouds   17. lucid pools   18. holy ground   19. clover field   20. evil fog   21. "favourable winds" text on magic plains background   22. cursed ground   23. rough   24. ship to ship   25. ship
+	std::vector<shared_ptr<const CObstacleInstance> > battleGetAllObstacles(boost::optional<BattlePerspective::BattlePerspective> perspective = boost::none) const; //returns all obstacles on the battlefield
+	TStacks battleGetAllStacks() const; //returns all stacks, alive or dead or undead or mechanical :)
+	bool battleHasNativeStack(ui8 side) const;
+	ui8 battleGetWallState(int partOfWall) 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
+	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, NULL 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(int player) const;
+	bool battleCanSurrender(int player) const;
+	ui8 playerToSide(int player) 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 casted
+	const CGHeroInstance * battleGetFightingHero(ui8 side) const;
+
+	//helpers
+	TStacks battleAliveStacks() const;
+	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;
+	//ESpellCastProblem::ESpellCastProblem battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const; //Checks if player is able to cast spells (at all) at the moment
+};
+
+class DLL_LINKAGE CBattleInfoCallback : public virtual CBattleInfoEssentials
+{
+public:
+	enum ERandomSpell
+	{
+		RANDOM_GENIE, RANDOM_AIMED
+	};
+
+	//battle
+	shared_ptr<const CObstacleInstance> battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking = true) const; //blocking obstacles makes tile inaccessible, others cause special effects (like Land Mines, Moat, Quicksands)
+	const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true) const; //returns stack info by given pos
+	void battleGetStackQueue(std::vector<const CStack *> &out, const int howMany, const int turn = 0, int lastMoved = -1) const;
+	void battleGetStackCountOutsideHexes(bool *ac) const; // returns hexes which when in front of a stack cause us to move the amount box back
+
+	//void getStackQueue( std::vector<const CStack *> &out, int howMany ) const; //returns vector of stack in order of their move sequence
+	std::vector<BattleHex> battleGetAvailableHexes(const CStack * stack, bool addOccupiable, std::vector<BattleHex> * attackable = NULL) const; //returns numbers of hexes reachable by creature with id ID
+
+	int battleGetSurrenderCost(int Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible
+	ReachabilityInfo::TDistances battleGetDistances(const CStack * stack, BattleHex hex = BattleHex::INVALID, BattleHex * predecessors = NULL) const; //returns vector of distances to [dest hex number]
+	std::set<BattleHex> battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const;
+	bool battleCanShoot(const CStack * stack, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination
+	bool battleIsStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack
+	std::set<const CStack*>  batteAdjacentCreatures (const CStack * stack) const;
+
+	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
+	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
+
+	//hextowallpart  //int battleGetWallUnderHex(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
+	std::pair<ui32, ui32> battleEstimateDamage(const CStack * attacker, const CStack * defender, std::pair<ui32, ui32> * retaliationDmg = NULL) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
+	si8 battleHasDistancePenalty( const CStack * stack, BattleHex destHex ) const;
+	si8 battleHasWallPenalty(const CStack * stack, BattleHex destHex) const; //checks if given stack has wall penalty
+	EWallParts::EWallParts battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
+
+	//*** MAGIC 
+	si8 battleMaxSpellLevel() const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
+	ui32 battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell
+	ESpellCastProblem::ESpellCastProblem battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell
+	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(int player, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
+	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(int player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
+	ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here
+
+	si32 battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
+	TSpell getRandomBeneficialSpell(const CStack * subject) const;
+	TSpell getRandomCastedSpell(const CStack * caster) const; //called at the beginning of turn for Faerie Dragon
+
+	//checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc.
+	ESpellCastProblem::ESpellCastProblem battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; 
+
+
+	const CStack * getStackIf(boost::function<bool(const CStack*)> pred) const;
+
+	si8 battleHasShootingPenalty(const CStack * stack, BattleHex destHex)
+	{
+		return battleHasDistancePenalty(stack, destHex) || battleHasWallPenalty(stack, destHex);
+	}
+	si8 battleCanTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel) const; //checks if teleportation of given stack to given position can take place
+
+
+	//convenience methods using the ones above
+	bool isInTacticRange( BattleHex dest ) const;
+	si8 battleGetTacticDist() const; //returns tactic distance for calling player or 0 if this player is not in tactic phase (for ALL_KNOWING actual distance for tactic side)
+
+	AttackableTiles getPotentiallyAttackableHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) const;
+	std::set<const CStack*> getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const; //calculates range of multi-hex attacks
+
+	ReachabilityInfo getReachability(const CStack *stack) const;
+	ReachabilityInfo getReachability(const ReachabilityInfo::Parameters &params) const;
+	AccessibilityInfo getAccesibility() const;
+	std::pair<const CStack *, BattleHex> getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const;
+protected:
+	ReachabilityInfo getFlyingReachability(const ReachabilityInfo::Parameters params) const;
+	ReachabilityInfo makeBFS(const AccessibilityInfo &accessibility, const ReachabilityInfo::Parameters params) const;
+	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
+	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell) const; //determines if given spell can be casted (and returns problem description)
+	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell, BattleHex destination) const; //if hero can cast spell here
+	ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here
+	int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible
+
+	bool battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem = nullptr) const; //returns true, if caller can cast a spell. If not, if pointer is given via arg, the reason will be written.
+};

+ 3 - 3
lib/CGameInterface.h

@@ -16,7 +16,7 @@
 
 using namespace boost::logic;
 class CCallback;
-class CBattleCallback;
+class CPlayerBattleCallback;
 class ICallback;
 class CGlobalAI;
 struct Component;
@@ -61,7 +61,7 @@ public:
 	std::string dllName;
 
 	virtual ~CBattleGameInterface() {};
-	virtual void init(CBattleCallback * CB){};
+	virtual void init(CPlayerBattleCallback * CB){};
 
 	//battle call-ins
 	virtual BattleAction activeStack(const CStack * stack)=0; //called when it's turn of that stack
@@ -115,7 +115,7 @@ public:
 
 	std::string battleAIName;
 	CBattleGameInterface *battleAI;
-	CBattleCallback *cbc;
+	CPlayerBattleCallback *cbc;
 
 	//battle interface
 	virtual BattleAction activeStack(const CStack * stack);

+ 4 - 0
lib/CSpellHandler.cpp

@@ -256,6 +256,10 @@ bool CSpell::isRisingSpell() const
 	return vstd::contains(VLC->spellh->risingSpells, id);
 }
 
+bool CSpell::isDamageSpell() const
+{
+	return vstd::contains(VLC->spellh->damageSpells, id);
+}
 bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos)
 {
 	int3 diff = pos - center;

+ 2 - 1
lib/CSpellHandler.h

@@ -22,7 +22,7 @@ class DLL_LINKAGE CSpell
 public:
 	enum ETargetType {NO_TARGET, CREATURE, CREATURE_EXPERT_MASSIVE, OBSTACLE};
 	enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1};
-	ui32 id;
+	TSpell id;
 	std::string name;
 	std::string abbName; //abbreviated name
 	std::vector<std::string> descriptions; //descriptions of spell for skill levels: 0 - none, 1 - basic, etc
@@ -48,6 +48,7 @@ public:
 	bool isPositive() const;
 	bool isNegative() const;
 	bool isRisingSpell() const;
+	bool isDamageSpell() const;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 3 - 3
lib/Filesystem/CLodArchiveLoader.cpp

@@ -161,11 +161,11 @@ void CLodArchiveLoader::initSNDArchive(CFileInputStream & fileStream)
 		SoundEntryBlock sndEntry = sndEntries[i];
 		ArchiveEntry entry;
 
-		//for some reason entries in snd have format NAME\0WAV\0\0\0....
-		//we need to replace first \0 with dot and trim line
+		//for some reason entries in snd have format NAME\0WAVRUBBISH....
+		//we need to replace first \0 with dot and take the 3 chars with extension (and drop the rest)
 		entry.name = std::string(sndEntry.filename, 40);
+		entry.name.resize(entry.name.find_first_of('\0') + 4); //+4 because we take dot and 3-char extension
 		entry.name[entry.name.find_first_of('\0')] = '.';
-		entry.name.resize(entry.name.find_first_of('\0'));
 
 		entry.offset = SDL_SwapLE32(sndEntry.offset);
 		entry.realSize = SDL_SwapLE32(sndEntry.size);

+ 36 - 1
lib/GameConstants.h

@@ -140,7 +140,9 @@ namespace ESpellCastProblem
 		OK, NO_HERO_TO_CAST_SPELL, ALREADY_CASTED_THIS_TURN, NO_SPELLBOOK, ANOTHER_ELEMENTAL_SUMMONED,
 		HERO_DOESNT_KNOW_SPELL, NOT_ENOUGH_MANA, ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL,
 		SECOND_HEROS_SPELL_IMMUNITY, SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL,
-		NO_APPROPRIATE_TARGET, STACK_IMMUNE_TO_SPELL, WRONG_SPELL_TARGET, ONGOING_TACTIC_PHASE
+		NO_APPROPRIATE_TARGET, STACK_IMMUNE_TO_SPELL, WRONG_SPELL_TARGET, ONGOING_TACTIC_PHASE,
+		MAGIC_IS_BLOCKED, //For Orb of Inhibition and similar - no casting at all
+		INVALID
 	};
 }
 
@@ -172,6 +174,28 @@ namespace ECommander
 	const int MAX_SKILL_LEVEL = 5;
 }
 
+namespace EWallParts
+{
+	enum EWallParts
+	{
+		INDESTRUCTIBLE_PART = -2, INVALID = -1,
+		KEEP = 0, BOTTOM_TOWER, BOTTOM_WALL, BELOW_GATE, OVER_GATE, UPPER_WAL, UPPER_TOWER, GATE,
+
+		PARTS_COUNT
+	};
+}
+
+namespace EWallState
+{
+	enum
+	{
+		NONE, //no wall
+		INTACT,
+		DAMAGED,
+		DESTROYED
+	};
+}
+
 namespace Obj
 {
 	enum
@@ -224,6 +248,17 @@ namespace Obj
 	};
 }
 
+namespace SecSkillLevel
+{
+	enum SecSkillLevel
+	{
+		NONE,
+		BASIC,
+		ADVANCED,
+		EXPERT
+	};
+}
+
 //follows ERM BI (battle image) format
 namespace BattlefieldBI
 {

+ 18 - 2
lib/HeroBonus.cpp

@@ -134,10 +134,26 @@ int BonusList::totalValue() const
 
 	if(hasIndepMin && hasIndepMax)
 		assert(indepMin < indepMax);
+
+	const int notIndepBonuses = boost::count_if(bonuses, [](const Bonus *b) 
+	{ 
+		return b->valType != Bonus::INDEPENDENT_MAX && b->valType != Bonus::INDEPENDENT_MIN; 
+	});
+
 	if (hasIndepMax)
-		vstd::amax(valFirst, indepMax);
+	{
+		if(notIndepBonuses)
+			vstd::amax(valFirst, indepMax);
+		else
+			valFirst = indepMax;
+	}
 	if (hasIndepMin)
-		vstd::amin(valFirst, indepMin);
+	{
+		if(notIndepBonuses)
+			vstd::amin(valFirst, indepMin);
+		else
+			valFirst = indepMin;
+	}
 
 	return valFirst;
 }

+ 4 - 1
lib/HeroBonus.h

@@ -95,6 +95,8 @@ typedef boost::function<bool(const Bonus*)> CSelector;
 	BONUS_NAME(SPELL_BEFORE_ATTACK) /* subtype - spell id, value - chance %, additional info % 1000 - level, (additional info)/1000 -> [0 - all attacks, 1 - shot only, 2 - melee only*/ \
 	BONUS_NAME(SPELL_RESISTANCE_AURA) /*eg. unicorns, value - resistance bonus in % for adjacent creatures*/ \
 	BONUS_NAME(LEVEL_SPELL_IMMUNITY) /*creature is immune to all spell with level below or equal to value of this bonus*/ \
+	BONUS_NAME(BLOCK_MAGIC_ABOVE) /*blocks casting spells of the level > value */ \
+	BONUS_NAME(BLOCK_ALL_MAGIC) /*blocks casting spells of the level > value */ \
 	BONUS_NAME(TWO_HEX_ATTACK_BREATH) /*eg. dragons*/	\
 	BONUS_NAME(SPELL_DAMAGE_REDUCTION) /*eg. golems; value - reduction in %, subtype - spell school; -1 - all, 0 - air, 1 - fire, 2 - water, 3 - earth*/ \
 	BONUS_NAME(NO_WALL_PENALTY)							\
@@ -626,7 +628,8 @@ public:
 	}
 	enum ENodeTypes
 	{
-		UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALITY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM, TOWN_AND_VISITOR
+		UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALITY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM, 
+		TOWN_AND_VISITOR, BATTLE
 	};
 };
 

+ 2 - 413
lib/IGameCallback.cpp

@@ -34,417 +34,6 @@
 
 extern boost::rand48 ran;
 
-boost::shared_mutex& CCallbackBase::getGsMutex()
-{
-	return *gs->mx;
-}
-
-si8 CBattleInfoCallback::battleHasDistancePenalty( const CStack * stack, BattleHex destHex )
-{
-	return gs->curB->hasDistancePenalty(stack, destHex);
-}
-
-si8 CBattleInfoCallback::battleHasWallPenalty( const CStack * stack, BattleHex destHex )
-{
-	return gs->curB->hasWallPenalty(stack, destHex);
-}
-
-si8 CBattleInfoCallback::battleCanTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel)
-{
-	return gs->curB->canTeleportTo(stack, destHex, telportLevel);
-}
-
-std::vector<int> CBattleInfoCallback::battleGetDistances(const CStack * stack, BattleHex hex /*= BattleHex::INVALID*/, BattleHex * predecessors /*= NULL*/)
-{
-	// FIXME - This method is broken, hex argument is not used. However AI depends on that wrong behaviour.
-
-	if(!hex.isValid())
-		hex = stack->position;
-
-	std::vector<int> ret(GameConstants::BFIELD_SIZE, -1); //fill initial ret with -1's
-
-	if(!hex.isValid()) //stack has bad position? probably castle turret, return initial values (they can't move)
-		return ret;
-
-	bool ac[GameConstants::BFIELD_SIZE] = {0};
-	std::set<BattleHex> occupyable;
-	gs->curB->getAccessibilityMap(ac, stack->doubleWide(), stack->attackerOwned, false, occupyable, stack->hasBonusOfType(Bonus::FLYING), stack);
-	BattleHex pr[GameConstants::BFIELD_SIZE];
-	int dist[GameConstants::BFIELD_SIZE];
-	gs->curB->makeBFS(stack->position, ac, pr, dist, stack->doubleWide(), stack->attackerOwned, stack->hasBonusOfType(Bonus::FLYING), false);
-
-	for(int i=0; i<GameConstants::BFIELD_SIZE; ++i)
-	{
-		if(pr[i] != -1)
-			ret[i] = dist[i];
-	}
-
-	if(predecessors)
-	{
-		memcpy(predecessors, pr, GameConstants::BFIELD_SIZE * sizeof(BattleHex));
-	}
-
-	return ret;
-}
-std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos  /*= BattleHex::INVALID*/)
-{
-	if(!gs->curB)
-	{
-
-		tlog1 << "battleGetAttackedHexes called when there is no battle!\n";
-		std::set<BattleHex> set;
-		return set;
-	}
-
-	return gs->curB->getAttackedHexes(attacker, destinationTile, attackerPos);
-}
-
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( const CSpell * spell )
-{
-	if(!gs->curB)
-	{
-
-		tlog1 << "battleCanCastThisSpell called when there is no battle!\n";
-		return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
-	}
-
-	return gs->curB->battleCanCastThisSpell(player, spell, ECastingMode::HERO_CASTING);
-}
-
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell(const CSpell * spell, BattleHex destination)
-{
-	if(!gs->curB)
-	{
-
-		tlog1 << "battleCanCastThisSpell called when there is no battle!\n";
-		return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
-	}
-
-	return gs->curB->battleCanCastThisSpellHere(player, spell, ECastingMode::HERO_CASTING, destination);
-}
-
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination)
-{
-	if(!gs->curB)
-	{
-
-		tlog1 << "battleCanCastThisSpell called when there is no battle!\n";
-		return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
-	}
-
-	return gs->curB->battleCanCastThisSpellHere(player, spell, ECastingMode::CREATURE_ACTIVE_CASTING, destination);
-}
-
-si32 CBattleInfoCallback::battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode)
-{
-	switch (mode)
-	{
-		case RANDOM_GENIE:
-			return gs->curB->getRandomBeneficialSpell(stack); //target
-			break;
-		case RANDOM_AIMED:
-			return gs->curB->getRandomCastedSpell(stack); //caster
-			break;
-		default:
-			tlog1 << "Incorrect mode of battleGetRandomSpell (" << mode <<")\n";
-			return -1;
-	}
-}
-
-si8 CBattleInfoCallback::battleGetTacticDist()
-{
-	if (!gs->curB)
-	{
-		tlog1 << "battleGetTacticDist called when no battle!\n";
-		return 0;
-	}
-
-	if (gs->curB->sides[gs->curB->tacticsSide] == player)
-	{
-		return gs->curB->tacticDistance;
-	}
-	return 0;
-}
-
-ui8 CBattleInfoCallback::battleGetMySide()
-{
-	if (!gs->curB)
-	{
-		tlog1 << "battleGetMySide called when no battle!\n";
-		return 0;
-	}
-
-	return gs->curB->sides[1] == player;
-}
-
-int CBattleInfoCallback::battleGetSurrenderCost()
-{
-	if (!gs->curB)
-	{
-		tlog1 << "battleGetSurrenderCost called when no battle!\n";
-		return -1;
-	}
-
-	return gs->curB->getSurrenderingCost(player);
-}
-
-int CBattleInfoCallback::battleGetBattlefieldType()
-{
-	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
-	//return gs->battleGetBattlefieldType();
-
-	if(!gs->curB)
-	{
-		tlog2<<"battleGetBattlefieldType called when there is no battle!"<<std::endl;
-		return -1;
-	}
-	return gs->curB->battlefieldType;
-}
-
-// int CBattleInfoCallback::battleGetObstaclesAtTile(BattleHex tile) //returns bitfield 
-// {
-// 	//TODO - write
-// 	return -1;
-// }
-
-std::vector<shared_ptr<const CObstacleInstance> > CBattleInfoCallback::battleGetAllObstacles()
-{
-	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
-	std::vector<shared_ptr<const CObstacleInstance> > ret;
-	if(gs->curB)
-	{
-		BOOST_FOREACH(auto oi, gs->curB->obstacles)
-		{
-			if(player < 0 || gs->curB->isObstacleVisibleForSide(*oi, battleGetMySide()))
-				ret.push_back(oi);
-		}
-	}
-
-	return ret;
-}
-
-const CStack* CBattleInfoCallback::battleGetStackByID(int ID, bool onlyAlive)
-{
-	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
-	if(!gs->curB) return NULL;
-	return gs->curB->getStack(ID, onlyAlive);
-}
-
-const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyAlive)
-{
-	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
-	return gs->curB->battleGetStack(pos, onlyAlive);
-}
-
-BattleHex CBattleInfoCallback::battleGetPos(int stack)
-{
-	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
-	if(!gs->curB)
-	{
-		tlog2<<"battleGetPos called when there is no battle!"<<std::endl;
-		return BattleHex::INVALID;
-	}
-	for(size_t g=0; g<gs->curB->stacks.size(); ++g)
-	{
-		if(gs->curB->stacks[g]->ID == stack)
-			return gs->curB->stacks[g]->position;
-	}
-	return BattleHex::INVALID;
-}
-
-TStacks CBattleInfoCallback::battleGetStacks(EStackOwnership whose /*= MINE_AND_ENEMY*/, bool onlyAlive /*= true*/)
-{
-	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
-	TStacks ret;
-	if(!gs->curB) //there is no battle
-	{
-		tlog2<<"battleGetStacks called when there is no battle!"<<std::endl;
-		return ret;
-	}
-
-	BOOST_FOREACH(const CStack *s, gs->curB->stacks)
-	{
-		bool ownerMatches = (whose == MINE_AND_ENEMY) || (whose == ONLY_MINE && s->owner == player) || (whose == ONLY_ENEMY && s->owner != player);
-		bool alivenessMatches = s->alive()  ||  !onlyAlive;
-		if(ownerMatches && alivenessMatches)
-			ret.push_back(s);
-	}
-
-	return ret;
-}
-
-void CBattleInfoCallback::getStackQueue( std::vector<const CStack *> &out, int howMany )
-{
-	if(!gs->curB)
-	{
-		tlog2 << "battleGetStackQueue called when there is not battle!" << std::endl;
-		return;
-	}
-	gs->curB->getStackQueue(out, howMany);
-}
-
-void CBattleInfoCallback::battleGetStackCountOutsideHexes(bool *ac)
-{
-	if(!gs->curB)
-	{
-		tlog2<<"battleGetAvailableHexes called when there is no battle!"<<std::endl;
-        for (int i = 0; i < GameConstants::BFIELD_SIZE; ++i) ac[i] = false;
-	}
-    else {
-        std::set<BattleHex> ignored;
-        gs->curB->getAccessibilityMap(ac, false /*ignored*/, false, false, ignored, false /*ignored*/, NULL);
-    }
-}
-
-std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const CStack * stack, bool addOccupiable, std::vector<BattleHex> * attackable)
-{
-	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
-	if(!gs->curB)
-	{
-		tlog2<<"battleGetAvailableHexes called when there is no battle!"<<std::endl;
-		return std::vector<BattleHex>();
-	}
-	return gs->curB->getAccessibility(stack, addOccupiable, attackable);
-	//return gs->battleGetRange(ID);
-}
-
-bool CBattleInfoCallback::battleCanShoot(const CStack * stack, BattleHex dest)
-{
-	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
-
-	if(!gs->curB) return false;
-
-	return gs->curB->battleCanShoot(stack, dest);
-}
-
-bool CBattleInfoCallback::battleCanCastSpell()
-{
-	if(!gs->curB) //there is no battle
-		return false;
-
-	return gs->curB->battleCanCastSpell(player, ECastingMode::HERO_CASTING) == ESpellCastProblem::OK;
-}
-
-bool CBattleInfoCallback::battleCanFlee()
-{
-	return gs->curB->battleCanFlee(player);
-}
-
-const CGTownInstance *CBattleInfoCallback::battleGetDefendedTown()
-{
-	if(!gs->curB || gs->curB->town == NULL)
-		return NULL;
-
-	return gs->curB->town;
-}
-
-ui8 CBattleInfoCallback::battleGetWallState(int partOfWall)
-{
-	if(!gs->curB || gs->curB->siege == 0)
-	{
-		return 0;
-	}
-	return gs->curB->si.wallState[partOfWall];
-}
-
-int CBattleInfoCallback::battleGetWallUnderHex(BattleHex hex)
-{
-	if(!gs->curB || gs->curB->siege == 0)
-	{
-		return -1;
-	}
-	return gs->curB->hexToWallPart(hex);
-}
-
-TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg)
-{
-	if(!gs->curB)
-		return std::make_pair(0, 0);
-
-	const CGHeroInstance * attackerHero, * defenderHero;
-	bool shooting = battleCanShoot(attacker, defender->position);
-
-	if(gs->curB->sides[0] == attacker->owner)
-	{
-		attackerHero = gs->curB->heroes[0];
-		defenderHero = gs->curB->heroes[1];
-	}
-	else
-	{
-		attackerHero = gs->curB->heroes[1];
-		defenderHero = gs->curB->heroes[0];
-	}
-
-	TDmgRange ret = gs->curB->calculateDmgRange(attacker, defender, attackerHero, defenderHero, shooting, 0, false, false, false);
-
-	if(retaliationDmg)
-	{
-		if(shooting)
-		{
-			retaliationDmg->first = retaliationDmg->second = 0;
-		}
-		else
-		{
-			ui32 TDmgRange::* pairElems[] = {&TDmgRange::first, &TDmgRange::second};
-			for (int i=0; i<2; ++i)
-			{
-				BattleStackAttacked bsa;
-				bsa.damageAmount = ret.*pairElems[i];
-				retaliationDmg->*pairElems[!i] = gs->curB->calculateDmgRange(defender, attacker, bsa.newAmount, attacker->count, attackerHero, defenderHero, false, 0, false, false, false).*pairElems[!i];
-			}
-		}
-	}
-
-	return ret;
-}
-
-ui8 CBattleInfoCallback::battleGetSiegeLevel()
-{
-	if(!gs->curB)
-		return 0;
-
-	return gs->curB->siege;
-}
-
-const CGHeroInstance * CBattleInfoCallback::battleGetFightingHero(ui8 side) const
-{
-	if(!gs->curB)
-		return 0;
-
-	//TODO: this method should not exist... you shouldn't be able to get info about enemy hero
-	return gs->curB->heroes[side];
-}
-
-shared_ptr<const CObstacleInstance> CBattleInfoCallback::battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking /*= true*/)
-{
-	if(!gs->curB)
-		return shared_ptr<const CObstacleInstance>();
-
-	BOOST_FOREACH(auto &obs, battleGetAllObstacles())
-	{
-		if(vstd::contains(obs->getBlockedTiles(), tile)
-			|| (!onlyBlocking  &&  vstd::contains(obs->getAffectedTiles(), tile)))
-		{
-			return obs;
-		}
-	}
-	return shared_ptr<const CObstacleInstance>();
-}
-
-int CBattleInfoCallback::battleGetMoatDmg()
-{
-	if(!gs->curB  ||  !gs->curB->town)
-		return 0;
-
-	//TODO move to config file
-	static const int dmgs[] = {70, 70, -1, 
-								90, 70, 90,
-								70, 90, 70};
-	if(gs->curB->town->subID < ARRAY_COUNT(dmgs))
-		return dmgs[gs->curB->town->subID];
-	return 0;
-}
-
 CGameState * CPrivilagedInfoCallback::gameState ()
 { 
 	return gs;
@@ -729,7 +318,7 @@ int CGameInfoCallback::getSpellCost(const CSpell * sp, const CGHeroInstance * ca
 	ERROR_RET_VAL_IF(!canGetFullInfo(caster), "Cannot get info about caster!", -1);
 	//if there is a battle
 	if(gs->curB)
-		return gs->curB->getSpellCost(sp, caster);
+		return gs->curB->battleGetSpellCost(sp, caster);
 
 	//if there is no battle
 	return caster->getSpellCost(sp);
@@ -784,7 +373,7 @@ bool CGameInfoCallback::getTownInfo( const CGObjectInstance *town, InfoAboutTown
 	//TODO vision support
 	if(town->ID == GameConstants::TOWNI_TYPE)
 		dest.initFromTown(static_cast<const CGTownInstance *>(town), detailed);
-	else if(town->ID == 33 || town->ID == 219)
+	else if(town->ID == Obj::GARRISON || town->ID == Obj::GARRISON2)
 		dest.initFromArmy(static_cast<const CArmedInstance *>(town), detailed);
 	else
 		return false;

+ 1 - 80
lib/IGameCallback.h

@@ -3,10 +3,10 @@
 
 #include "BattleHex.h"
 #include "../client/FunctionList.h"
-#include "CObstacleInstance.h"
 #include "ResourceSet.h"
 #include "int3.h"
 #include "GameConstants.h"
+#include "CBattleCallback.h"
 
 /*
  * IGameCallback.h, part of VCMI engine
@@ -59,85 +59,6 @@ struct TeamState;
 struct QuestInfo;
 class CGCreature;
 
-typedef std::vector<const CStack*> TStacks;
-
-namespace boost
-{class shared_mutex;}
-
-class DLL_LINKAGE CCallbackBase
-{
-protected:
-	CGameState *gs;
-	int player; // -1 gives access to all information, otherwise limited to knowledge of given player
-
-	CCallbackBase(CGameState *GS, int Player)
-		: gs(GS), player(Player)
-	{}
-	CCallbackBase()
-	: gs(NULL), player(-1)
-	{}
-
-public:
-	boost::shared_mutex &getGsMutex(); //just return a reference to mutex, does not lock nor anything
-};
-
-class DLL_LINKAGE CBattleInfoCallback : public virtual CCallbackBase
-{
-public:
-	enum EStackOwnership
-	{
-		ONLY_MINE, ONLY_ENEMY, MINE_AND_ENEMY
-	};
-	enum ERandomSpell
-	{
-		RANDOM_GENIE, RANDOM_AIMED
-	};
-
-	//battle
-	int battleGetBattlefieldType(); //   1. sand/shore   2. sand/mesas   3. dirt/birches   4. dirt/hills   5. dirt/pines   6. grass/hills   7. grass/pines   8. lava   9. magic plains   10. snow/mountains   11. snow/trees   12. subterranean   13. swamp/trees   14. fiery fields   15. rock lands   16. magic clouds   17. lucid pools   18. holy ground   19. clover field   20. evil fog   21. "favourable winds" text on magic plains background   22. cursed ground   23. rough   24. ship to ship   25. ship
-	//int battleGetObstaclesAtTile(BattleHex tile); //returns bitfield
-	shared_ptr<const CObstacleInstance> battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking = true);
-	std::vector<shared_ptr<const CObstacleInstance> > battleGetAllObstacles(); //returns all obstacles on the battlefield
-	const CStack * battleGetStackByID(int ID, bool onlyAlive = true); //returns stack info by given ID
-	const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true); //returns stack info by given pos
-	BattleHex battleGetPos(int stack); //returns position (tile ID) of stack
-	TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true); //returns stacks on battlefield
-	void getStackQueue( std::vector<const CStack *> &out, int howMany ); //returns vector of stack in order of their move sequence
-    void battleGetStackCountOutsideHexes(bool *ac); // returns hexes which when in front of a stack cause us to move the amount box back
-	std::vector<BattleHex> battleGetAvailableHexes(const CStack * stack, bool addOccupiable, std::vector<BattleHex> * attackable = NULL); //returns numbers of hexes reachable by creature with id ID
-	std::vector<int> battleGetDistances(const CStack * stack, BattleHex hex = BattleHex::INVALID, BattleHex * predecessors = NULL); //returns vector of distances to [dest hex number]
-	std::set<BattleHex> battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID);
-	bool battleCanShoot(const CStack * stack, BattleHex dest); //returns true if unit with id ID can shoot to dest
-	bool battleCanCastSpell(); //returns true, if caller can cast a spell
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell); //determines if given spell can be casted (and returns problem description)
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell, BattleHex destination); //if hero can cast spell here
-	ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination); //determines if creature can cast a spell here
-	si32 battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode);
-	bool battleCanFlee(); //returns true if caller can flee from the battle
-	int battleGetSurrenderCost(); //returns cost of surrendering battle, -1 if surrendering is not possible
-	const CGTownInstance * battleGetDefendedTown(); //returns defended town if current battle is a siege, NULL instead
-	ui8 battleGetWallState(int partOfWall); //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
-	int battleGetWallUnderHex(BattleHex hex); //returns part of destructible wall / gate / keep under given hex or -1 if not found
-	std::pair<ui32, ui32> battleEstimateDamage(const CStack * attacker, const CStack * defender, std::pair<ui32, ui32> * retaliationDmg = NULL); //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
-	ui8 battleGetSiegeLevel(); //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle
-	const CGHeroInstance * battleGetFightingHero(ui8 side) const; //returns hero corresponding to given side (0 - attacker, 1 - defender)
-	si8 battleHasDistancePenalty(const CStack * stack, BattleHex destHex); //checks if given stack has distance penalty
-	si8 battleHasWallPenalty(const CStack * stack, BattleHex destHex); //checks if given stack has wall penalty
-	si8 battleHasShootingPenalty(const CStack * stack, BattleHex destHex)
-	{
-		return battleHasDistancePenalty(stack, destHex) || battleHasWallPenalty(stack, destHex);
-	}
-	si8 battleCanTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel); //checks if teleportation of given stack to given position can take place
-	si8 battleGetTacticDist(); //returns tactic distance for calling player or 0 if player is not in tactic phase
-	ui8 battleGetMySide(); //return side of player in battle (attacker/defender)
-	int battleGetMoatDmg(); //what dmg unit will suffer if ending turn in the moat
-
-	//convenience methods using the ones above
-	TStacks battleGetAllStacks() //returns all stacks, alive or dead or undead or mechanical :)
-	{
-		return battleGetStacks(MINE_AND_ENEMY, false);
-	}
-};
 
 class DLL_LINKAGE CGameInfoCallback : public virtual CCallbackBase
 {

+ 7 - 9
lib/NetPacksLib.cpp

@@ -1113,6 +1113,7 @@ void BattleStackMoved::applyGs( CGameState *gs )
 DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs )
 {
 	CStack * at = gs->curB->getStack(stackAttacked);
+	assert(at);
 	at->count = newAmount;
 	at->firstHPleft = newHP;
 
@@ -1334,16 +1335,13 @@ DLL_LINKAGE void StacksHealedOrResurrected::applyGs( CGameState *gs )
 		CStack * changedStack = gs->curB->getStack(healedStacks[g].stackID, false);
 
 		//checking if we resurrect a stack that is under a living stack
-		std::vector<BattleHex> access = gs->curB->getAccessibility(changedStack, true);
-		bool acc[GameConstants::BFIELD_SIZE];
-		for(int h=0; h<GameConstants::BFIELD_SIZE; ++h)
-			acc[h] = false;
-		for(int h=0; h<access.size(); ++h)
-			acc[access[h]] = true;
-		if(!changedStack->alive() && !gs->curB->isAccessible(changedStack->position, acc,
-			changedStack->doubleWide(), changedStack->attackerOwned,
-			changedStack->hasBonusOfType(Bonus::FLYING), true))
+		auto accessibility = gs->curB->getAccesibility();
+		
+		if(!changedStack->alive() && !accessibility.accessible(changedStack->position, changedStack))
+		{
+			tlog1 << "Cannot resurrect " << changedStack->nodeName() << " because hex " << changedStack->position << " is occupied!\n";
 			return; //position is already occupied
+		}
 
 		//applying changes
 		bool resurrected = !changedStack->alive(); //indicates if stack is resurrected or just healed

+ 0 - 3
lib/ResourceSet.h

@@ -1,8 +1,5 @@
 #pragma once
 
-
-#include <climits>
-
 typedef si32 TResource;
 typedef si64 TResourceCap; //to avoid overflow when adding integers. Signed values are easier to control.
 

+ 6 - 7
lib/VCMI_lib.vcxproj

@@ -230,14 +230,13 @@
     <ClCompile Include="CCreatureHandler.cpp" />
     <ClCompile Include="CCreatureSet.cpp" />
     <ClCompile Include="CDefObjInfoHandler.cpp" />
-    <ClCompile Include="CFileUtility.cpp" />
     <ClCompile Include="CGameInterface.cpp" />
     <ClCompile Include="CGameState.cpp" />
     <ClCompile Include="CGeneralTextHandler.cpp" />
     <ClCompile Include="CHeroHandler.cpp" />
-    <ClCompile Include="CLodHandler.cpp" />
     <ClCompile Include="CLogger.cpp" />
     <ClCompile Include="CMapInfo.cpp" />
+    <ClCompile Include="CModHandler.cpp" />
     <ClCompile Include="CObjectHandler.cpp" />
     <ClCompile Include="CObstacleInstance.cpp" />
     <ClCompile Include="Connection.cpp" />
@@ -245,15 +244,15 @@
     <ClCompile Include="CThreadHelper.cpp" />
     <ClCompile Include="CTownHandler.cpp" />
     <ClCompile Include="Filesystem\CBinaryReader.cpp" />
+    <ClCompile Include="Filesystem\CCompressedStream.cpp" />
     <ClCompile Include="Filesystem\CFileInfo.cpp" />
     <ClCompile Include="Filesystem\CFileInputStream.cpp" />
     <ClCompile Include="Filesystem\CFilesystemLoader.cpp" />
     <ClCompile Include="Filesystem\CLodArchiveLoader.cpp" />
-    <ClCompile Include="Filesystem\CLodStream.cpp" />
     <ClCompile Include="Filesystem\CMemoryStream.cpp" />
     <ClCompile Include="Filesystem\CResourceLoader.cpp" />
-    <ClCompile Include="Filesystem\ISimpleResourceLoader.cpp" />
     <ClCompile Include="HeroBonus.cpp" />
+    <ClCompile Include="CBattleCallback.cpp" />
     <ClCompile Include="IGameCallback.cpp" />
     <ClCompile Include="JsonNode.cpp" />
     <ClCompile Include="map.cpp" />
@@ -283,14 +282,13 @@
     <ClInclude Include="CCreatureHandler.h" />
     <ClInclude Include="CCreatureSet.h" />
     <ClInclude Include="CDefObjInfoHandler.h" />
-    <ClInclude Include="CFileUtility.h" />
     <ClInclude Include="CGameInterface.h" />
     <ClInclude Include="CGameState.h" />
     <ClInclude Include="CGeneralTextHandler.h" />
     <ClInclude Include="CHeroHandler.h" />
-    <ClInclude Include="CLodHandler.h" />
     <ClInclude Include="CLogger.h" />
     <ClInclude Include="CMapInfo.h" />
+    <ClInclude Include="CModHandler.h" />
     <ClInclude Include="CObjectHandler.h" />
     <ClInclude Include="CObstacleInstance.h" />
     <ClInclude Include="CondSh.h" />
@@ -302,17 +300,18 @@
     <ClInclude Include="CThreadHelper.h" />
     <ClInclude Include="CTownHandler.h" />
     <ClInclude Include="Filesystem\CBinaryReader.h" />
+    <ClInclude Include="Filesystem\CCompressedStream.h" />
     <ClInclude Include="Filesystem\CFileInfo.h" />
     <ClInclude Include="Filesystem\CFileInputStream.h" />
     <ClInclude Include="Filesystem\CFilesystemLoader.h" />
     <ClInclude Include="Filesystem\CInputStream.h" />
     <ClInclude Include="Filesystem\CLodArchiveLoader.h" />
-    <ClInclude Include="Filesystem\CLodStream.h" />
     <ClInclude Include="Filesystem\CMemoryStream.h" />
     <ClInclude Include="Filesystem\CResourceLoader.h" />
     <ClInclude Include="Filesystem\ISimpleResourceLoader.h" />
     <ClInclude Include="GameConstants.h" />
     <ClInclude Include="HeroBonus.h" />
+    <ClInclude Include="CBattleCallback.h" />
     <ClInclude Include="IGameCallback.h" />
     <ClInclude Include="IGameEventsReceiver.h" />
     <ClInclude Include="int3.h" />

+ 92 - 74
server/CGameHandler.cpp

@@ -531,8 +531,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 	ConstTransitivePtr <CGHeroInstance> loserHero = battleResult.data->winner != 0 ? hero1 : hero2;
 	std::vector<ui32> arts; //display them in window
 
-	//TODO: display loot in window
-	if (result < BattleResult::SURRENDER && winnerHero)
+	if (result == BattleResult::NORMAL && winnerHero)
 	{
 		if (loserHero)
 		{
@@ -542,7 +541,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 				MoveArtifact ma;
 				ma.src = ArtifactLocation (loserHero, artSlot.first);
 				const CArtifactInstance * art =  ma.src.getArt();
-				if (art && !art->artType->isBig()) // don't move war machines or locked arts (spellbook)
+				if (art && !art->artType->isBig() && art->id != ArtifactId::SPELLBOOK) // don't move war machines or locked arts (spellbook)
 				{
 					arts.push_back (art->artType->id);
 					ma.dst = ArtifactLocation (winnerHero, art->firstAvailableSlot(winnerHero));
@@ -692,6 +691,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 		addToSlot(StackLocation(winnerHero, necroSlot), raisedStack.type, raisedStack.count);
 	}
 	sendAndApply(&resultsApplied);
+	setBattle(nullptr);
 
 	if(duel)
 	{
@@ -775,10 +775,10 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 
 	if (!bat.shot()) //multiple-hex attack - only in meele
 	{
-		std::set<CStack*> attackedCreatures = gs->curB->getAttackedCreatures(att, targetHex);
+		std::set<const CStack*> attackedCreatures = gs->curB->getAttackedCreatures(att, targetHex);
 		//TODO: get exact attacked hex for defender
 
-		BOOST_FOREACH(CStack * stack, attackedCreatures)
+		BOOST_FOREACH(const CStack * stack, attackedCreatures)
 		{
 			if (stack != def) //do not hit same stack twice
 			{
@@ -793,10 +793,10 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 		bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
 		bat.bsa.front().effect = VLC->spellh->spells[bonus->subtype]->mainEffectAnim; //hopefully it does not interfere with any other effect?
 
-		std::set<CStack*> attackedCreatures = gs->curB->getAttackedCreatures(VLC->spellh->spells[bonus->subtype], bonus->val, att->owner, targetHex);
+		std::set<const CStack*> attackedCreatures = gs->curB->getAttackedCreatures(VLC->spellh->spells[bonus->subtype], bonus->val, att->owner, targetHex);
 		//TODO: get exact attacked hex for defender
 
-		BOOST_FOREACH(CStack * stack, attackedCreatures)
+		BOOST_FOREACH(const CStack * stack, attackedCreatures)
 		{
 			if (stack != def) //do not hit same stack twice
 			{
@@ -924,8 +924,8 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 {
 	int ret = 0;
 
-	CStack *curStack = gs->curB->getStack(stack),
-		*stackAtEnd = gs->curB->getStackT(dest);
+	const CStack *curStack = gs->curB->battleGetStackByID(stack),
+		*stackAtEnd = gs->curB->battleGetStackByPos(dest);
 
 	assert(curStack);
 	assert(dest < GameConstants::BFIELD_SIZE);
@@ -936,50 +936,30 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 	}
 
 	//initing necessary tables
-	bool accessibility[GameConstants::BFIELD_SIZE];
-	std::vector<BattleHex> accessible = gs->curB->getAccessibility(curStack, false, NULL, true);
-	for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
-	{
-		accessibility[b] = false;
-	}
-	for(int g=0; g<accessible.size(); ++g)
-	{
-		accessibility[accessible[g]] = true;
-	}
+	auto accessibility = getAccesibility();
 
 	//shifting destination (if we have double wide stack and we can occupy dest but not be exactly there)
-	if(!stackAtEnd && curStack->doubleWide() && !accessibility[dest])
+	if(!stackAtEnd && curStack->doubleWide() && !accessibility.accessible(dest, curStack))
 	{
 		if(curStack->attackerOwned)
 		{
-			if(accessibility[dest+1])
+			if(accessibility.accessible(dest+1, curStack))
 				dest += BattleHex::RIGHT;
 		}
 		else
 		{
-			if(accessibility[dest-1])
+			if(accessibility.accessible(dest-1, curStack))
 				dest += BattleHex::LEFT;
 		}
 	}
 
-	if((stackAtEnd && stackAtEnd!=curStack && stackAtEnd->alive()) || !accessibility[dest])
-		return 0;
-
-	bool accessibilityWithOccupyable[GameConstants::BFIELD_SIZE];
-	std::vector<BattleHex> accOc = gs->curB->getAccessibility(curStack, true, NULL, true);
-	for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
-	{
-		accessibilityWithOccupyable[b] = false;
-	}
-	for(int g=0; g<accOc.size(); ++g)
+	if((stackAtEnd && stackAtEnd!=curStack && stackAtEnd->alive()) || !accessibility.accessible(dest, curStack))
 	{
-		accessibilityWithOccupyable[accOc[g]] = true;
+		complain("Given destination is not accessible!");
+		return 0;
 	}
-
-	//if(dists[dest] > curStack->creature->speed && !(stackAtEnd && dists[dest] == curStack->creature->speed+1)) //we can attack a stack if we can go to adjacent hex
-	//	return false;
-
-	std::pair< std::vector<BattleHex>, int > path = gs->curB->getPath(curStack->position, dest, accessibilityWithOccupyable, curStack->hasBonusOfType(Bonus::FLYING), curStack->doubleWide(), curStack->attackerOwned);
+	
+	std::pair< std::vector<BattleHex>, int > path = gs->curB->getPath(curStack->position, dest, curStack);
 
 	ret = path.second;
 
@@ -3220,6 +3200,40 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 	tlog1 << "\tMaking action of type " << ba.actionType << std::endl;
 	bool ok = true;
 
+
+	const CStack *stack = battleGetStackByID(ba.stackNumber); //may be nullptr if action is not about stack
+	const bool isAboutActiveStack = stack && (stack == battleActiveStack()); 
+	
+	switch(ba.actionType)
+	{
+	case BattleAction::WALK: //walk
+	case BattleAction::DEFEND: //defend
+	case BattleAction::WAIT: //wait
+	case BattleAction::WALK_AND_ATTACK: //walk or attack
+	case BattleAction::SHOOT: //shoot
+	case BattleAction::CATAPULT: //catapult
+	case BattleAction::STACK_HEAL: //healing with First Aid Tent
+	case BattleAction::DAEMON_SUMMONING:
+	case BattleAction::MONSTER_SPELL:
+
+		if(!stack)
+		{
+			complain("No such stack!");
+			return false;
+		}
+		if(!stack->alive())
+		{
+			complain("This stack is dead: " + stack->nodeName());
+			return false;
+		}
+		if(!isAboutActiveStack)
+		{
+			complain("Action has to be about active stack!");
+			return false;
+		}
+	}
+
+
 	switch(ba.actionType)
 	{
 	case BattleAction::END_TACTIC_PHASE: //wait
@@ -3267,7 +3281,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 	case BattleAction::SURRENDER:
 		{
 			int player = gs->curB->sides[ba.side];
-			int cost = gs->curB->getSurrenderingCost(player);
+			int cost = gs->curB->battleGetSurrenderCost(player);
 			if(cost < 0)
 				complain("Cannot surrender!");
 			else if(getResource(player, Res::GOLD) < cost)
@@ -3284,10 +3298,10 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 		{
 			StartAction start_action(ba);
 			sendAndApply(&start_action); //start movement and attack
-			int startingPos = gs->curB->getStack(ba.stackNumber)->position;
+			int startingPos = gs->curB->battleGetStackByID(ba.stackNumber)->position;
 			int distance = moveStack(ba.stackNumber, ba.destinationTile);
-			CStack *curStack = gs->curB->getStack(ba.stackNumber),
-				*stackAtEnd = gs->curB->getStackT(ba.additionalInfo);
+			const CStack *curStack = gs->curB->battleGetStackByID(ba.stackNumber),
+				*stackAtEnd = gs->curB->battleGetStackByPos(ba.additionalInfo);
 
 			if(!curStack || !stackAtEnd)
 			{
@@ -3295,6 +3309,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 				break;
 			}
 
+			tlog5 << curStack->nodeName() << " will attack " << stackAtEnd->nodeName() << std::endl;
+
 			if(curStack->position != ba.destinationTile //we wasn't able to reach destination tile
 				&& !(curStack->doubleWide()
 					&&  ( curStack->position == ba.destinationTile + (curStack->attackerOwned ?  +1 : -1 ) )
@@ -3341,7 +3357,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 
 			//counterattack
 			if(!curStack->hasBonusOfType(Bonus::BLOCKS_RETALIATION)
-				&& stackAtEnd->ableToRetaliate())
+				&& stackAtEnd->ableToRetaliate()
+				&& curStack->alive()) //attacker may have died (fire shield)
 			{
 				BattleAttack bat;
 				prepareAttack(bat, stackAtEnd, curStack, 0, curStack->position);
@@ -3374,8 +3391,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 		}
 	case BattleAction::SHOOT: //shoot
 		{
-			CStack *curStack = gs->curB->getStack(ba.stackNumber),
-				*destStack= gs->curB->getStackT(ba.destinationTile);
+			const CStack *curStack = gs->curB->battleGetStackByID(ba.stackNumber),
+				*destStack= gs->curB->battleGetStackByPos(ba.destinationTile);
 			if( !gs->curB->battleCanShoot(curStack, ba.destinationTile) )
 				break;
 
@@ -3423,7 +3440,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side];
 			CHeroHandler::SBallisticsLevelInfo sbi = VLC->heroh->ballistics[attackingHero->getSecSkillLevel(CGHeroInstance::BALLISTICS)];
 
-			int attackedPart = gs->curB->hexToWallPart(ba.destinationTile);
+			int attackedPart = gs->curB->battleHexToWallPart(ba.destinationTile);
 			if(attackedPart < 0)
 			{
 				complain("catapult tried to attack non-catapultable hex!");
@@ -3524,8 +3541,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			StartAction start_action(ba);
 			sendAndApply(&start_action);
 			const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side];
-			CStack *healer = gs->curB->getStack(ba.stackNumber),
-				*destStack = gs->curB->getStackT(ba.destinationTile);
+			const CStack *healer = gs->curB->battleGetStackByID(ba.stackNumber),
+				*destStack = gs->curB->battleGetStackByPos(ba.destinationTile);
 
 			if(healer == NULL || destStack == NULL || !healer->hasBonusOfType(Bonus::HEALER))
 			{
@@ -3567,8 +3584,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			StartAction start_action(ba);
 			sendAndApply(&start_action);
 
-			CStack *summoner = gs->curB->getStack(ba.stackNumber),
-				*destStack = gs->curB->getStackT(ba.destinationTile, false);
+			const CStack *summoner = gs->curB->battleGetStackByID(ba.stackNumber),
+				*destStack = gs->curB->battleGetStackByPos(ba.destinationTile, false);
 
 			BattleStackAdded bsa;
 			bsa.attacker = summoner->attackerOwned;
@@ -3603,7 +3620,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			StartAction start_action(ba);
 			sendAndApply(&start_action);
 
-			CStack * stack = gs->curB->getStack(ba.stackNumber);
+			const CStack * stack = gs->curB->battleGetStackByID(ba.stackNumber);
 			int spellID = ba.additionalInfo;
 			BattleHex destination(ba.destinationTile);
 
@@ -3852,7 +3869,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 
 	if (caster) //calculate spell cost
 	{
-		sc.spellCost = gs->curB->getSpellCost(VLC->spellh->spells[spellID], caster);
+		sc.spellCost = gs->curB->battleGetSpellCost(VLC->spellh->spells[spellID], caster);
 
 		if (secHero && mode == ECastingMode::HERO_CASTING) //handle mana channel
 		{
@@ -3869,18 +3886,18 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 	}
 
 	//calculating affected creatures for all spells
-	std::set<CStack*> attackedCres;
+	std::set<const CStack*> attackedCres;
 	if (mode != ECastingMode::ENCHANTER_CASTING)
 	{
 		attackedCres = gs->curB->getAttackedCreatures(spell, spellLvl, casterColor, destination);
-		for(std::set<CStack*>::const_iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+		for(std::set<const CStack*>::const_iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
 		{
 			sc.affectedCres.insert((*it)->ID);
 		}
 	}
 	else //enchanter - hit all possible stacks
 	{
-		BOOST_FOREACH (CStack * stack, gs->curB->stacks)
+		BOOST_FOREACH (const CStack * stack, gs->curB->stacks)
 		{
 			/*if it's non negative spell and our unit or non positive spell and hostile unit */
 			if((!spell->isNegative() && stack->owner == casterColor)
@@ -3898,7 +3915,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 	sc.resisted = gs->curB->calculateResistedStacks(spell, caster, secHero, attackedCres, casterColor, mode, usedSpellPower, spellLvl);
 
 	//calculating dmg to display
-	for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+	for(std::set<const CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
 	{
 		if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
 			continue;
@@ -3976,7 +3993,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 				}
 			}
 			int chainLightningModifier = 0;
-			for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+			for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it)
 			{
 				if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
 					continue;
@@ -4067,7 +4084,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 				bonus = caster->getBonus(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID));
 
 			si32 power = 0;
-			for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+			for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it)
 			{
 				if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
 					continue;
@@ -4154,7 +4171,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 			StacksHealedOrResurrected shr;
 			shr.lifeDrain = (ui8)false;
 			shr.tentHealing = (ui8)false;
-			for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+			for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it)
 			{
 				if(vstd::contains(sc.resisted, (*it)->ID) //this creature resisted the spell
 					|| (spellID == Spells::ANIMATE_DEAD && !(*it)->hasBonusOfType(Bonus::UNDEAD)) //we try to cast animate dead on living stack
@@ -4172,7 +4189,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 						hi.healedHP = gs->curB->calculateHealedHP(spell, usedSpellPower, spellLvl, *it);
 				}
 				else
-					hi.healedHP = gs->curB->calculateHealedHP(caster, spell, *it, gs->curB->getStack(selectedStack));
+					hi.healedHP = gs->curB->calculateHealedHP(caster, spell, *it, gs->curB->battleGetStackByID(selectedStack));
 				hi.lowLevelResurrection = spellLvl <= 1;
 				shr.healedStacks.push_back(hi);
 			}
@@ -4227,7 +4244,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 		break;
 	case Spells::CLONE:
 		{
-			CStack * clonedStack = NULL;
+			const CStack * clonedStack = NULL;
 			if (attackedCres.size())
 				clonedStack = *attackedCres.begin();
 			if (!clonedStack)
@@ -4270,7 +4287,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 		break;
 	case Spells::DEATH_STARE: //handled in a bit different way
 		{
-			for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+			for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it)
 			{
 				if((*it)->hasBonusOfType(Bonus::UNDEAD) || (*it)->hasBonusOfType(Bonus::NON_LIVING)) //this creature is immune
 				{
@@ -4291,7 +4308,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 		break;
 	case Spells::ACID_BREATH_DAMAGE: //new effect, separate from acid breath defense reduction
 		{
-			for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) //no immunities
+			for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it) //no immunities
 			{
 				BattleStackAttacked bsa;
 				bsa.flags |= BattleStackAttacked::EFFECT;
@@ -4323,7 +4340,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 	//Magic Mirror effect
 	if (spell->isNegative() && mode != ECastingMode::MAGIC_MIRROR && spell->level && spell->range[0] == "0") //it is actual spell and can be reflected to single target, no recurrence
 	{
-		for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
+		for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it)
 		{
 			int mirrorChance = (*it)->valOfBonuses(Bonus::MAGIC_MIRROR);
 			if(mirrorChance > rand()%100)
@@ -4388,7 +4405,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 									ECastingMode::HERO_CASTING, NULL, ba.selectedStack);
 
 				sendAndApply(&end_action);
-				if( !gs->curB->getStack(gs->curB->activeStack, false)->alive() )
+				if( !gs->curB->battleGetStackByID(gs->curB->activeStack, false)->alive() )
 				{
 					battleMadeAction.setn(true);
 				}
@@ -4426,11 +4443,11 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 		{
 			bool unbind = true;
 			BonusList bl = *(st->getBonuses(Selector::type(Bonus::BIND_EFFECT)));
-			std::set<CStack*> stacks = gs->curB->getAdjacentCreatures(st);
+			std::set<const CStack*> stacks = gs->curB-> batteAdjacentCreatures(st);
 
 			BOOST_FOREACH(Bonus * b, bl)
 			{
-				const CStack * stack = gs->curB->getStack(b->additionalInfo); //binding stack must be alive and adjacent
+				const CStack * stack = gs->curB->battleGetStackByID(b->additionalInfo); //binding stack must be alive and adjacent
 				if (stack)
 				{
 					if (vstd::contains(stacks, stack)) //binding stack is still present
@@ -4533,7 +4550,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 	}
 }
 
-void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, CStack * curStack)
+void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, const CStack * curStack)
 {
 	//we want to determine following vars depending on obstacle type
 	int damage = -1;
@@ -4552,7 +4569,7 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, C
 	else if(obstacle.obstacleType == CObstacleInstance::LAND_MINE)
 	{
 		//You don't get hit by a Mine you can see.
-		if(gs->curB->isObstacleVisibleForSide(obstacle, side))
+		if(gs->curB->battleIsObstacleVisibleForSide(obstacle, (BattlePerspective::BattlePerspective)side))
 			return;
 
 		oneTimeObstacle = true;
@@ -5168,7 +5185,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
 			{
 				if (bat.bsa[g].newAmount > 0 && !bat.bsa[g].isSecondary()) //apply effects only to first target stack if it's alive
 				{
-					oneOfAttacked = gs->curB->getStack(bat.bsa[g].stackAttacked);
+					oneOfAttacked = gs->curB->battleGetStackByID(bat.bsa[g].stackAttacked);
 					break;
 				}
 			}
@@ -5206,13 +5223,13 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
 
 void CGameHandler::handleAttackBeforeCasting (const BattleAttack & bat)
 {
-	const CStack * attacker = gs->curB->getStack(bat.stackAttacking);
+	const CStack * attacker = gs->curB->battleGetStackByID(bat.stackAttacking);
 	attackCasting(bat, Bonus::SPELL_BEFORE_ATTACK, attacker); //no detah stare / acid bretah needed?
 }
 
 void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 {
-	const CStack * attacker = gs->curB->getStack(bat.stackAttacking);
+	const CStack * attacker = gs->curB->battleGetStackByID(bat.stackAttacking);
 	if (!attacker) //could be already dead
 		return;
 	attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker);
@@ -5241,7 +5258,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 		if (staredCreatures)
 		{
 			if (bat.bsa.size() && bat.bsa[0].newAmount > 0) //TODO: death stare was not originally available for multiple-hex attacks, but...
-			handleSpellCasting(79, 0, gs->curB->getStack(bat.bsa[0].stackAttacked)->position,
+			handleSpellCasting(79, 0, gs->curB->battleGetStackByID(bat.bsa[0].stackAttacked)->position,
 				!attacker->attackerOwned, attacker->owner, NULL, NULL, staredCreatures, ECastingMode::AFTER_ATTACK_CASTING, attacker);
 		}
 	}
@@ -5255,7 +5272,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 	}
 	if (acidDamage)
 	{
-		handleSpellCasting(81, 0, gs->curB->getStack(bat.bsa[0].stackAttacked)->position,
+		handleSpellCasting(81, 0, gs->curB->battleGetStackByID(bat.bsa[0].stackAttacked)->position,
 				!attacker->attackerOwned, attacker->owner, NULL, NULL,
 				acidDamage * attacker->count, ECastingMode::AFTER_ATTACK_CASTING, attacker);
 	}
@@ -5716,6 +5733,7 @@ bool CGameHandler::swapStacks(const StackLocation &sl1, const StackLocation &sl2
 
 void CGameHandler::runBattle()
 {
+	setBattle(gs->curB);
 	assert(gs->curB);
 	//TODO: pre-tactic stuff, call scripts etc.
 

+ 1 - 1
server/CGameHandler.h

@@ -201,7 +201,7 @@ public:
 		int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack, si32 selectedStack = -1);
 	bool makeCustomAction(BattleAction &ba);
 	void stackTurnTrigger(const CStack * stack);
-	void handleDamageFromObstacle(const CObstacleInstance &obstacle, CStack * curStack); //checks if obstacle is land mine and handles possible consequences
+	void handleDamageFromObstacle(const CObstacleInstance &obstacle, const CStack * curStack); //checks if obstacle is land mine and handles possible consequences
 	void removeObstacle(const CObstacleInstance &obstacle);
 	bool queryReply( ui32 qid, ui32 answer, ui8 player );
 	bool hireHero( const CGObjectInstance *obj, ui8 hid, ui8 player );

+ 3 - 2
server/NetPacksServer.cpp

@@ -251,7 +251,7 @@ bool MakeAction::applyGh( CGameHandler *gh )
 		if(gh->connections[b->sides[b->tacticsSide]] != c) 
 			ERROR_AND_RETURN;
 	}
-	else if(gh->connections[b->getStack(b->activeStack)->owner] != c) 
+	else if(gh->connections[b->battleGetStackByID(b->activeStack)->owner] != c) 
 		ERROR_AND_RETURN;
 
 	return gh->makeBattleAction(ba);
@@ -262,9 +262,10 @@ bool MakeCustomAction::applyGh( CGameHandler *gh )
 	const BattleInfo *b = GS(gh)->curB;
 	if(!b) ERROR_AND_RETURN;
 	if(b->tacticDistance) ERROR_AND_RETURN;
-	const CStack *active = GS(gh)->curB->getStack(GS(gh)->curB->activeStack);
+	const CStack *active = GS(gh)->curB->battleGetStackByID(GS(gh)->curB->activeStack);
 	if(!active) ERROR_AND_RETURN;
 	if(gh->connections[active->owner] != c) ERROR_AND_RETURN;
+	if(ba.actionType != BattleAction::HERO_SPELL) ERROR_AND_RETURN;
 	return gh->makeCustomAction(ba);
 }
 

部分文件因为文件数量过多而无法显示