Browse Source

Work in progress on BattleAI. Related changes:
* battle AIs receive ptr to CBattleCallback (not sure why it was CPlayerBattleCallback, likely mistake)
* reworked some battle callback methods to be more generic and able to handle some hypothetic scenarios
* for testing purposes in duel mode the first AI will be taken fro mconfig and the second will remain stupid ai
* minor changes

Michał W. Urbańczyk 13 years ago
parent
commit
62e63d45b1

+ 370 - 53
AI/BattleAI/BattleAI.cpp

@@ -4,8 +4,59 @@
 #include "../../lib/BattleState.h"
 #include "../../CCallback.h"
 #include "../../lib/CCreatureHandler.h"
+#include "../../lib/CSpellHandler.h"
+#include "../../lib/VCMI_Lib.h"
 
-CPlayerBattleCallback * cbc;
+using boost::optional;
+CBattleCallback * cbc;
+
+//#define LOGL(text) tlog6 << (text) << std::endl
+//#define LOGFL(text, formattingEl) tlog6 << boost::str(boost::format(text) % formattingEl) << std::endl
+#define LOGL(text) print(text)
+#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
+
+class StackWithBonuses : public IBonusBearer
+{
+public:
+	const CStack *stack;
+	mutable std::vector<Bonus> bonusesToAdd;
+
+	virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = NULL, const std::string &cachingStr = "") const OVERRIDE
+	{
+		TBonusListPtr ret = make_shared<BonusList>();
+		const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr);
+		boost::copy(*originalList, std::back_inserter(*ret));
+		BOOST_FOREACH(auto &bonus, bonusesToAdd)
+		{
+			if(selector(&bonus)  &&  (!limit || !limit(&bonus)))
+				ret->push_back(&bonus);
+		}
+
+		//TODO limiters?
+
+		return ret;
+	}
+};
+
+struct Skirmish
+{
+	const CStack *attacker, *defender;
+	int retaliationDamage, dealtDamage;
+
+	Skirmish(const CStack *Attacker, const CStack *Defender)
+		:attacker(Attacker), defender(Defender)
+	{
+		TDmgRange retal, dmg = cbc->battleEstimateDamage(attacker, defender, &retal);
+		dealtDamage = (dmg.first + dmg.second) / 2;
+		retaliationDamage = (retal.first + retal.second) / 2;
+
+		if(attacker->hasBonusOfType(Bonus::ADDITIONAL_ATTACK))
+			dealtDamage *= 2;
+		if(attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || defender->hasBonusOfType(Bonus::NO_RETALIATION))
+			retaliationDamage = 0;
+
+	}
+};
 
 CBattleAI::CBattleAI(void)
 	: side(-1), cb(NULL)
@@ -19,10 +70,13 @@ CBattleAI::~CBattleAI(void)
 	print("destroyed");
 }
 
-void CBattleAI::init( CPlayerBattleCallback * CB )
+void CBattleAI::init( CBattleCallback * CB )
 {
 	print("init called, saving ptr to IBattleCallback");
 	cbc = cb = CB;
+	playerID = CB->getPlayerID();; //TODO should be sth in callback
+	CB->waitTillRealize = true;
+	CB->unlockGsWhenWaiting = false;
 }
 
 void CBattleAI::actionFinished( const BattleAction *action )
@@ -94,74 +148,204 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
 	return shooters[0] < shooters[1];
 }
 
-BattleAction CBattleAI::activeStack( const CStack * stack )
+template <typename Container, typename Pred>
+auto sum(const Container & c, Pred p) -> decltype(p(*boost::begin(c)))
 {
-	//boost::this_thread::sleep(boost::posix_time::seconds(2));
-	print("activeStack called for " + stack->nodeName());
-	auto dists = cb->battleGetDistances(stack);
-	std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
-
-	if(stack->type->idNumber == 145) //catapult
+	double ret = 0;
+	BOOST_FOREACH(const auto &element, c)
 	{
-		BattleAction attack;
-		static const int wallHexes[] = {50, 183, 182, 130, 62, 29, 12, 95};
-		attack.destinationTile = wallHexes[ rand()%ARRAY_COUNT(wallHexes) ];
-		attack.actionType = BattleAction::CATAPULT;
-		attack.additionalInfo = 0;
-		attack.side = side;
-		attack.stackNumber = stack->ID;
-
-		return attack;
+		ret += p(element);
 	}
 
-	BOOST_FOREACH(const CStack *s, cb->battleGetStacks(CBattleCallback::ONLY_ENEMY))
+	return ret;
+}
+
+struct ThreatMap
+{	
+	std::array<std::vector<BattleAttackInfo>, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike
+	
+	const CStack *endangered; 
+	std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage; 
+
+	struct ThreatMap(const CStack *Endangered)
+		: endangered(Endangered)
 	{
-		if(cb->battleCanShoot(stack, s->position))
-		{
-			enemiesShootable.push_back(s);
-		}
-		else
+		sufferedDamage.fill(0);
+
+		BOOST_FOREACH(const CStack *enemy, cbc->battleGetStacks())
 		{
-			std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
+			//Consider only stacks of different owner
+			if(enemy->attackerOwned == endangered->attackerOwned)
+				continue;
+
+			//Look-up which tiles can be melee-attacked
+			std::array<bool, GameConstants::BFIELD_SIZE> meleeAttackable;
+			meleeAttackable.fill(false);
+			auto enemyReachability = cbc->getReachability(enemy);
+			for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
+			{
+				if(enemyReachability.isReachable(i))
+				{
+					meleeAttackable[i] = true;
+					BOOST_FOREACH(auto n, BattleHex(i).neighbouringTiles())
+						meleeAttackable[n] = true;
+				}
+			}
 
-			BOOST_FOREACH(BattleHex hex, avHexes)
+			//Gather possible assaults
+			for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
 			{
-				if(CStack::isMeleeAttackPossible(stack, s, hex))
+				if(cbc->battleCanShoot(enemy, i))
+					threatMap[i].push_back(BattleAttackInfo(enemy, endangered, true));
+				else if(meleeAttackable[i])
 				{
-					std::vector<EnemyInfo>::iterator i = std::find(enemiesReachable.begin(), enemiesReachable.end(), s);
-					if(i == enemiesReachable.end())
-					{
-						enemiesReachable.push_back(s);
-						i = enemiesReachable.begin() + (enemiesReachable.size() - 1);
-					}
-
-					i->attackFrom.push_back(hex);
+					BattleAttackInfo bai(enemy, endangered, false);
+					bai.chargedFields = std::max(BattleHex::getDistance(enemy->position, i) - 1, 0); //TODO check real distance (BFS), not just metric
+					threatMap[i].push_back(BattleAttackInfo(bai));
 				}
 			}
+		}
 
-			if(!vstd::contains(enemiesReachable, s) && s->position.isValid())
-				enemiesUnreachable.push_back(s);
+		for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
+		{
+			sufferedDamage[i] = sum(threatMap[i], [](const BattleAttackInfo &bai) -> int
+			{
+				auto dmg = cbc->calculateDmgRange(bai);
+				return (dmg.first + dmg.second)/2;
+			});
 		}
 	}
+};
 
-	if(enemiesShootable.size())
+struct AttackPossibility
+{
+	const CStack *enemy; //redundant (to attack.defender) but looks nice
+	BattleHex tile; //tile from which we attack
+	BattleAttackInfo attack;
+
+	int damageDealt;
+	int damageReceived; //usually by counter-attack
+	int damageDiff() const
 	{
-		const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable);
-		return BattleAction::makeShotAttack(stack, ei.s);
+		return damageDealt - damageReceived;
 	}
-	else if(enemiesReachable.size())
+
+	int attackValue() const
 	{
-		const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
-		return BattleAction::makeMeleeAttack(stack, ei.s, *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters));
+		//TODO consider tactical advantage
+		return damageDiff();
 	}
-	else
+};
+
+struct PotentialTargets
+{
+	std::vector<AttackPossibility> possibleAttacks;
+	std::vector<const CStack *> unreachableEnemies;
+
+	std::function<AttackPossibility(bool,BattleHex)>  GenerateAttackInfo; //args: shooting, destHex
+
+	PotentialTargets(const CStack *attacker, optional<IBonusBearer*> attackerBonuses = boost::none)
+	{
+		auto dists = cbc->battleGetDistances(attacker);
+		std::vector<BattleHex> avHexes = cbc->battleGetAvailableHexes(attacker, false);
+
+		BOOST_FOREACH(const CStack *enemy, cbc->battleGetStacks())
+		{
+			//Consider only stacks of different owner
+			if(enemy->attackerOwned == attacker->attackerOwned)
+				continue;
+			
+			GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
+			{
+				auto bai = BattleAttackInfo(attacker, enemy, shooting);
+				if(attackerBonuses)
+					bai.attackerBonuses = *attackerBonuses;
+
+				AttackPossibility ap = {enemy, hex, bai, 0, 0};
+				if(hex.isValid())
+				{
+					assert(dists[hex] <= attacker->Speed());
+					ap.attack.chargedFields = dists[hex];
+				}
+
+				std::pair<ui32, ui32> retaliation;
+				auto attackDmg = cbc->battleEstimateDamage(ap.attack, &retaliation);
+				ap.damageDealt = (attackDmg.first + attackDmg.second) / 2;
+				ap.damageReceived = (retaliation.first + retaliation.second) / 2;
+				//TODO other damage related to attack (eg. fire shield and other abilities)
+				//TODO limit max damage by total stacks health (dealing 100000 dmg to single Pikineer is not that effective)
+
+				return ap;
+			};
+
+			if(cbc->battleCanShoot(attacker, enemy->position))
+			{
+				possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID));
+			}
+			else
+			{
+				BOOST_FOREACH(BattleHex hex, avHexes)
+					if(CStack::isMeleeAttackPossible(attacker, enemy, hex))
+						possibleAttacks.push_back(GenerateAttackInfo(false, hex));
+
+				if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; }))
+					unreachableEnemies.push_back(enemy);
+			}
+		}
+	}
+
+	AttackPossibility bestAction() const
 	{
-		const EnemyInfo &ei= *std::min_element(enemiesUnreachable.begin(), enemiesUnreachable.end(), boost::bind(isCloser, _1, _2, boost::ref(dists)));
-		if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
+		if(possibleAttacks.empty())
+			throw std::runtime_error("No best action, since we don't have any actions");
+
+		return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.damageDiff(); } );
+	}
+};
+
+BattleAction CBattleAI::activeStack( const CStack * stack )
+{
+	try
+	{
+		print("activeStack called for " + stack->nodeName());
+		if(stack->type->idNumber == 145) //catapult
+			return useCatapult(stack);
+
+		if(cb->battleCanCastSpell())
+			attemptCastingSpell();
+
+		ThreatMap threatsToUs(stack);
+		PotentialTargets targets(stack);
+
+		if(targets.possibleAttacks.size())
 		{
-			return goTowards(stack, ei.s->position);
+			auto hlp = targets.bestAction();
+			if(hlp.attack.shooting)
+				return BattleAction::makeShotAttack(stack, hlp.enemy);
+			else
+				return BattleAction::makeMeleeAttack(stack, hlp.enemy, hlp.tile);
+		}
+		else
+		{
+			if(stack->waited())
+			{
+				auto dists = cbc->battleGetDistances(stack);
+				const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, boost::bind(isCloser, _1, _2, boost::ref(dists)));
+				if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
+				{
+					return goTowards(stack, ei.s->position);
+				}
+			}
+			else
+			{
+				return BattleAction::makeWait(stack);
+			}
 		}
 	}
+	catch(std::exception &e)
+	{
+		tlog1 << "Exception occurred in " << __FUNCTION__ << " " << e.what() << std::endl;
+	}
 
 	return BattleAction::makeDefend(stack);
 }
@@ -181,11 +365,6 @@ void CBattleAI::battleEnd(const BattleResult *br)
 	print("battleEnd called");
 }
 
-// void CStupidAI::battleResultsApplied()
-// {
-// 	print("battleResultsApplied called");
-// }
-
 void CBattleAI::battleNewRoundFirst(int round)
 {
 	print("battleNewRoundFirst called");
@@ -244,7 +423,7 @@ void CBattleAI::battleStacksRemoved(const BattleStacksRemoved & bsr)
 
 void CBattleAI::print(const std::string &text) const
 {
-	tlog6 << "CStupidAI [" << this <<"]: " << text << std::endl;
+	tlog6 << "CBattleAI [" << this <<"]: " << text << std::endl;
 }
 
 BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
@@ -310,3 +489,141 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
 	}
 }
 
+BattleAction CBattleAI::useCatapult(const CStack * stack)
+{
+	throw std::exception("The method or operation is not implemented.");
+}
+
+bool isSupportedSpell(const CSpell *spell)
+{
+	switch(spell->id)
+	{
+		// permanent effects
+	case Spells::SHIELD:
+	case Spells::AIR_SHIELD:
+	case Spells::FIRE_SHIELD:
+	case Spells::PROTECTION_FROM_AIR:
+	case Spells::PROTECTION_FROM_FIRE:
+	case Spells::PROTECTION_FROM_WATER:
+	case Spells::PROTECTION_FROM_EARTH:
+	case Spells::ANTI_MAGIC:
+	case Spells::MAGIC_MIRROR:
+	case Spells::BLESS:
+	case Spells::CURSE:
+	case Spells::BLOODLUST:
+	case Spells::PRECISION:
+	case Spells::WEAKNESS:
+	case Spells::STONE_SKIN:
+	case Spells::DISRUPTING_RAY:
+	case Spells::PRAYER:
+	case Spells::MIRTH:
+	case Spells::SORROW:
+	case Spells::FORTUNE:
+	case Spells::MISFORTUNE:
+	case Spells::HASTE:
+	case Spells::SLOW:
+	case Spells::SLAYER:
+	case Spells::FRENZY:
+	case Spells::COUNTERSTRIKE:
+	case Spells::BERSERK:
+	case Spells::HYPNOTIZE:
+	case Spells::FORGETFULNESS:
+	case Spells::BLIND:
+	case Spells::STONE_GAZE:
+	case Spells::POISON:
+	case Spells::BIND:
+	case Spells::DISEASE:
+	case Spells::PARALYZE:
+	case Spells::AGE:
+	case Spells::ACID_BREATH_DEFENSE:
+		return true;
+
+	default:
+		return false;
+	}
+};
+
+struct PossibleSpellcast
+{
+	const CSpell *spell;
+	BattleHex dest;
+};
+
+void CBattleAI::attemptCastingSpell()
+{
+	LOGL("Casting spells sounds like fun. Let's see...");
+
+	auto known = cb->battleGetFightingHero(side);
+
+	//Get all spells we can cast
+	std::vector<const CSpell*> possibleSpells;
+	vstd::copy_if(VLC->spellh->spells, std::back_inserter(possibleSpells), [this] (const CSpell *s) -> bool
+	{ 
+		auto problem = cbc->battleCanCastThisSpell(s); 
+		return problem == ESpellCastProblem::OK; 
+	});
+	LOGFL("I can cast %d spells.", possibleSpells.size());
+
+	vstd::erase_if(possibleSpells, [](const CSpell *s) 
+	{return !isSupportedSpell(s); });
+	LOGFL("I know about workings of %d of them.", possibleSpells.size());
+
+	//Get possible spell-target pairs
+	std::vector<PossibleSpellcast> possibleCasts;
+	BOOST_FOREACH(auto spell, possibleSpells)
+	{
+		BOOST_FOREACH(auto hex, cbc->battleGetPossibleTargets(playerID, spell))
+		{
+			PossibleSpellcast ps = {spell, hex};
+			possibleCasts.push_back(ps);
+		}
+	}
+	LOGFL("Found %d spell-target combinations.", possibleCasts.size());
+	if(possibleCasts.empty())
+		return;
+
+	std::map<const CStack*, int> valueOfStack;
+	BOOST_FOREACH(auto stack, cb->battleGetStacks())
+	{
+		PotentialTargets pt(stack);
+		valueOfStack[stack] = pt.bestAction().attackValue();
+	}
+
+	auto evaluateSpellcast = [&] (const PossibleSpellcast &ps) -> int
+	{
+		int skillLevel = 0;
+
+		StackWithBonuses swb;
+		swb.stack = cb->battleGetStackByPos(ps.dest);
+		if(!swb.stack)
+			return -1;
+
+		Bonus pseudoBonus;
+		pseudoBonus.sid = ps.spell->id;
+		pseudoBonus.val = skillLevel;
+		pseudoBonus.turnsRemain = 1; //TODO
+		CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
+
+		PotentialTargets pt(swb.stack, &swb);
+		auto newValue = pt.bestAction().attackValue();
+		auto oldValue = valueOfStack[swb.stack];
+		auto gain = newValue - oldValue;
+		if(swb.stack->owner != playerID) //enemy
+			gain = -gain;
+
+		LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)",
+			ps.spell->name % swb.stack->nodeName() % gain % (oldValue) % (newValue));
+
+		return gain;
+	};
+
+	auto castToPerform = *vstd::maxElementByFun(possibleCasts, evaluateSpellcast);
+	BattleAction spellcast;
+	spellcast.actionType = BattleAction::HERO_SPELL;
+	spellcast.additionalInfo = castToPerform.spell->id;
+	spellcast.destinationTile = castToPerform.dest;
+	spellcast.side = side;
+	spellcast.stackNumber = (!side) ? -1 : -2;
+
+	cb->battleMakeAction(&spellcast);
+}

+ 5 - 2
AI/BattleAI/BattleAI.h

@@ -5,14 +5,14 @@
 class CBattleAI : public CBattleGameInterface
 {
 	int side;
-	CPlayerBattleCallback *cb;
+	CBattleCallback *cb;
 
 	void print(const std::string &text) const;
 public:
 	CBattleAI(void);
 	~CBattleAI(void);
 
-	void init(CPlayerBattleCallback * CB) OVERRIDE;
+	void init(CBattleCallback * 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
@@ -35,5 +35,8 @@ public:
 	void battleStacksRemoved(const BattleStacksRemoved & bsr) OVERRIDE; //called when certain stack is completely removed from battlefield
 
 	BattleAction goTowards(const CStack * stack, BattleHex hex );
+	BattleAction useCatapult(const CStack * stack);
+
+	void attemptCastingSpell();
 };
 

+ 1 - 1
AI/BattleAI/main.cpp

@@ -7,7 +7,7 @@
 #define strcpy_s(a, b, c) strncpy(a, c, b)
 #endif
 
-const char *g_cszAiName = "Stupid AI 0.1";
+const char *g_cszAiName = "Battle AI";
 
 extern "C" DLL_EXPORT int GetGlobalAiVersion()
 {

+ 1 - 1
AI/StupidAI/StupidAI.cpp

@@ -19,7 +19,7 @@ CStupidAI::~CStupidAI(void)
 	print("destroyed");
 }
 
-void CStupidAI::init( CPlayerBattleCallback * CB )
+void CStupidAI::init( CBattleCallback * CB )
 {
 	print("init called, saving ptr to IBattleCallback");
 	cbc = cb = CB;

+ 2 - 2
AI/StupidAI/StupidAI.h

@@ -5,14 +5,14 @@
 class CStupidAI : public CBattleGameInterface
 {
 	int side;
-	CPlayerBattleCallback *cb;
+	CBattleCallback *cb;
 
 	void print(const std::string &text) const;
 public:
 	CStupidAI(void);
 	~CStupidAI(void);
 
-	void init(CPlayerBattleCallback * CB) OVERRIDE;
+	void init(CBattleCallback * 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

+ 12 - 1
Global.h

@@ -383,7 +383,7 @@ namespace vstd
 		return std::inserter(c, c.end());
 	}
 
-	//Retuns iterator to the element for which the value of ValueFunction is minimal
+	//Returns 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))
 	{
@@ -393,6 +393,17 @@ namespace vstd
 			return vf(lhs) < vf(rhs);
 		});
 	}
+		
+	//Returns iterator to the element for which the value of ValueFunction is maximal
+	template<class ForwardRange, class ValueFunction>
+	auto maxElementByFun(const ForwardRange& rng, ValueFunction vf) -> decltype(boost::begin(rng))
+	{
+		typedef decltype(*boost::begin(rng)) ElemType;
+		return boost::max_element(rng, [&] (ElemType lhs, ElemType rhs) -> bool
+		{
+			return vf(lhs) < vf(rhs);
+		});
+	}
 }
 
 using std::shared_ptr;

+ 4 - 4
client/BattleInterface/CBattleInterface.cpp

@@ -1265,7 +1265,7 @@ void CBattleInterface::bSpellf()
 	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));
+		auto blockingBonus = currentHero()->getBonusLocalFirst(Selector::type(Bonus::BLOCK_ALL_MAGIC));
 		if(!blockingBonus)
 			return;;
 		
@@ -2002,7 +2002,7 @@ void CBattleInterface::activateStack()
 
 	queue->update();
 	redrawBackgroundWithHexes(activeStack);
-	bWait->block(vstd::contains(s->state, EBattleStackState::WAITING)); //block waiting button if stack has been already waiting
+	bWait->block(s->waited()); //block waiting button if stack has been already waiting
 
 	//block cast spell button if hero doesn't have a spellbook
 	ESpellCastProblem::ESpellCastProblem spellcastingProblem;
@@ -2014,8 +2014,8 @@ void CBattleInterface::activateStack()
 
 
 	//set casting flag to true if creature can use it to not check it every time
-	const Bonus *spellcaster = s->getBonus(Selector::type(Bonus::SPELLCASTER)),
-		*randomSpellcaster = s->getBonus(Selector::type(Bonus::RANDOM_SPELLCASTER));
+	const Bonus *spellcaster = s->getBonusLocalFirst(Selector::type(Bonus::SPELLCASTER)),
+		*randomSpellcaster = s->getBonusLocalFirst(Selector::type(Bonus::RANDOM_SPELLCASTER));
 	if (s->casts &&  (spellcaster || randomSpellcaster))
 	{
 		stackCanCastSpell = true;

+ 1 - 1
client/CCreatureWindow.cpp

@@ -422,7 +422,7 @@ void CCreatureWindow::init(const CStackInstance *Stack, const CBonusSystemNode *
 			{
 				spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds."
 				boost::replace_first (spellText, "%s", CGI->spellh->spells[effect]->name);
-				int duration = battleStack->getBonus(Selector::source(Bonus::SPELL_EFFECT,effect))->turnsRemain;
+				int duration = battleStack->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT,effect))->turnsRemain;
 				boost::replace_first (spellText, "%d", boost::lexical_cast<std::string>(duration));
 
 				new CAnimImage("SpellInt", effect + 1, 0, 20 + 52 * printed, 184);

+ 1 - 1
client/CSpellWindow.cpp

@@ -655,7 +655,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 			case ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED:
 				{
 					//Recanter's Cloak or similar effect. Try to retrieve bonus
-					const Bonus *b = owner->myHero->getBonus(Selector::type(Bonus::BLOCK_MAGIC_ABOVE));
+					const Bonus *b = owner->myHero->getBonusLocalFirst(Selector::type(Bonus::BLOCK_MAGIC_ABOVE));
 					//TODO what about other values and non-artifact sources?
 					if(b && b->val == 2 && b->source == Bonus::ARTIFACT)
 					{

+ 4 - 1
client/Client.cpp

@@ -387,7 +387,10 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 		{
 			auto cbc = make_shared<CBattleCallback>(gs, color, this);
 			battleCallbacks[color] = cbc;
-			battleints[color] = CDynLibHandler::getNewBattleAI("StupidAI");
+			if(!color)
+				battleints[color] = CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String());
+			else
+				battleints[color] = CDynLibHandler::getNewBattleAI("StupidAI");
 			battleints[color]->init(cbc.get());
 		}
 	}

+ 8 - 24
lib/BattleState.cpp

@@ -1155,19 +1155,6 @@ si32 CStack::magicResistance() const
 	return magicResistance;
 }
 
-const Bonus * CStack::getEffect( ui16 id, int turn /*= 0*/ ) const
-{
-	BOOST_FOREACH(Bonus *it, getBonusList())
-	{
-		if(it->source == Bonus::SPELL_EFFECT && it->sid == id)
-		{
-			if(!turn || it->turnsRemain > turn)
-				return &(*it);
-		}
-	}
-	return NULL;
-}
-
 void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
 {
 	si32 power = VLC->spellh->spells[sse.sid]->powers[sse.val];
@@ -1342,17 +1329,6 @@ void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
 	}
 }
 
-ui8 CStack::howManyEffectsSet(ui16 id) const
-{
-	ui8 ret = 0;
-	BOOST_FOREACH(const Bonus *it, getBonusList())
-		if(it->source == Bonus::SPELL_EFFECT && it->sid == id) //effect found
-		{
-			++ret;
-		}
-		return ret;
-}
-
 bool CStack::willMove(int turn /*= 0*/) const
 {
 	return ( turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING) )
@@ -1374,6 +1350,14 @@ bool CStack::moved( int turn /*= 0*/ ) const
 		return false;
 }
 
+bool CStack::waited(int turn /*= 0*/) const
+{
+	if(!turn)
+		return vstd::contains(state, EBattleStackState::WAITING);
+	else
+		return false;
+}
+
 bool CStack::doubleWide() const
 {
 	return getCreature()->doubleWide;

+ 1 - 2
lib/BattleState.h

@@ -167,11 +167,10 @@ public:
 	void init(); //set initial (invalid) values
 	void postInit(); //used to finish initialization when inheriting creature parameters is working
 	std::string getName() const; //plural or singular
-	const Bonus * getEffect(ui16 id, int turn = 0) const; //effect id (SP)
-	ui8 howManyEffectsSet(ui16 id) const; //returns amount of effects with given id set for this stack
 	bool willMove(int turn = 0) const; //if stack has remaining move this turn
 	bool ableToRetaliate() const; //if stack can retaliate after attacked
 	bool moved(int turn = 0) const; //if stack was already moved this turn
+	bool waited(int turn = 0) const;
 	bool canMove(int turn = 0) const; //if stack can move
 	bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
 	ui32 Speed(int turn = 0, bool useBind = false) const; //get speed of creature with all modificators

+ 1 - 1
lib/CArtHandler.cpp

@@ -1214,7 +1214,7 @@ void CArtifactInstance::deserializationFix()
 
 int CArtifactInstance::getGivenSpellID() const
 {
-	const Bonus * b = getBonus(Selector::type(Bonus::SPELL));
+	const Bonus * b = getBonusLocalFirst(Selector::type(Bonus::SPELL));
 	if(!b)
 	{
 		tlog3 << "Warning: " << nodeName() << " doesn't bear any spell!\n";

+ 210 - 77
lib/CBattleCallback.cpp

@@ -10,6 +10,26 @@
 
 namespace SiegeStuffThatShouldBeMovedToHandlers //  <=== TODO
 {
+	static void retreiveTurretDamageRange(const CStack *turret, double &outMinDmg, double &outMaxDmg)
+	{
+		assert(turret->getCreature()->idNumber == 149); //arrow turret
+
+		switch(turret->position)
+		{
+		case -2: //keep
+			outMinDmg = 15;
+			outMaxDmg = 15;
+			break;
+		case -3: case -4: //turrets
+			outMinDmg = 7.5;
+			outMaxDmg = 7.5;
+			break;
+		default:
+			tlog1 << "Unknown turret type!" << std::endl;
+			outMaxDmg = outMinDmg = 1;
+		}
+	}
+
 	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};
@@ -89,6 +109,11 @@ void CCallbackBase::setBattle(const BattleInfo *B)
 	battle = B;
 }
 
+int CCallbackBase::getPlayerID() const
+{
+	return player;
+}
+
 ui8 CBattleInfoEssentials::battleTerrainType() const
 {
 	RETURN_IF_NOT_BATTLE(-1);
@@ -369,21 +394,26 @@ ui8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const
 }
 
 si8 CBattleInfoCallback::battleHasWallPenalty( const CStack * stack, BattleHex destHex ) const
+{
+	return battleHasWallPenalty(stack, stack->position, destHex);
+}
+
+si8 CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer *bonusBearer, BattleHex shooterPosition, BattleHex destHex) const
 {
 	RETURN_IF_NOT_BATTLE(false);
-	if (!battleGetSiegeLevel() || stack->hasBonusOfType(Bonus::NO_WALL_PENALTY))
+	if (!battleGetSiegeLevel() || bonusBearer->hasBonusOfType(Bonus::NO_WALL_PENALTY))
 		return false;
 
-	const int wallInStackLine = lineToWallHex(stack->position.getY());
+	const int wallInStackLine = lineToWallHex(shooterPosition.getY());
 	const int wallInDestLine = lineToWallHex(destHex.getY());
 
-	const bool stackLeft = stack->position < wallInStackLine;
+	const bool stackLeft = shooterPosition < 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
+		int row = (shooterPosition + destHex) / (2 * GameConstants::BFIELD_WIDTH);
+		if (shooterPosition > destHex && ((destHex % GameConstants::BFIELD_WIDTH - shooterPosition % GameConstants::BFIELD_WIDTH) < 2)) //shooting up high
 			row -= 2;
 		const int wallPos = lineToWallHex(row);
 		if (battleHexToWallPart(wallPos) != -1) //wall still exists or is indestructible
@@ -524,7 +554,7 @@ void CBattleInfoCallback::battleGetStackQueue(std::vector<const CStack *> &out,
 	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))
+	if(!turn && active && active->willMove() && !active->waited())
 	{
 		out.push_back(active);
 		if(out.size() == howMany)
@@ -549,7 +579,7 @@ void CBattleInfoCallback::battleGetStackQueue(std::vector<const CStack *> &out,
 		}
 
 		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(turn <= 0 && s->waited()) //consider waiting state only for ongoing round
 		{
 			if(vstd::contains(s->state, EBattleStackState::HAD_MORALE))
 				p = 2;
@@ -736,74 +766,66 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const CStack* attacker, const C
 	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
+TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) const
 {
 	double additiveBonus = 1.0, multBonus = 1.0,
-		minDmg = attacker->getMinDamage() * attackerCount,
-		maxDmg = attacker->getMaxDamage() * attackerCount;
+		minDmg = info.attackerBonuses->getMinDamage() * info.attackerCount,
+		maxDmg = info.attackerBonuses->getMaxDamage() * info.attackerCount;
 
-	if(attacker->getCreature()->idNumber == 149) //arrow turret
+	const CCreature *attackerType = info.attacker->getCreature(), 
+		*defenderType = info.defender->getCreature();
+
+	if(attackerType->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;
-		}
+		SiegeStuffThatShouldBeMovedToHandlers::retreiveTurretDamageRange(info.attacker, minDmg, maxDmg);
 	}
 
-	if(attacker->hasBonusOfType(Bonus::SIEGE_WEAPON) && attacker->getCreature()->idNumber != 149) //any siege weapon, but only ballista can attack (second condition - not arrow turret)
+	if(info.attackerBonuses->hasBonusOfType(Bonus::SIEGE_WEAPON) && attackerType->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
+		auto retreiveHeroPrimSkill = [&](int skill) -> int
 		{
-			const Bonus *b = attacker->getBonus(Selector::sourceTypeSel(Bonus::HERO_BASE_SKILL) &&  Selector::typeSubtype(Bonus::PRIMARY_SKILL, skill));
+			const Bonus *b = info.attackerBonuses->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;
+		minDmg *= retreiveHeroPrimSkill(PrimarySkill::ATTACK) + 1;
+		maxDmg *= retreiveHeroPrimSkill(PrimarySkill::ATTACK) + 1;
 	}
 
 	int attackDefenceDifference = 0;
-	if(attacker->hasBonusOfType(Bonus::GENERAL_ATTACK_REDUCTION))
+	if(info.attackerBonuses->hasBonusOfType(Bonus::GENERAL_ATTACK_REDUCTION))
 	{
-		double multAttackReduction = attacker->valOfBonuses(Bonus::GENERAL_ATTACK_REDUCTION, -1024) / 100.0;
-		attackDefenceDifference = attacker->Attack() * multAttackReduction;
+		double multAttackReduction = info.attackerBonuses->valOfBonuses(Bonus::GENERAL_ATTACK_REDUCTION, -1024) / 100.0;
+		attackDefenceDifference = info.attackerBonuses->Attack() * multAttackReduction;
 	}
 	else
 	{
-		attackDefenceDifference = attacker->Attack();
+		attackDefenceDifference = info.attackerBonuses->Attack();
 	}
 
-	if(attacker->hasBonusOfType(Bonus::ENEMY_DEFENCE_REDUCTION))
+	if(info.attackerBonuses->hasBonusOfType(Bonus::ENEMY_DEFENCE_REDUCTION))
 	{
-		double multDefenceReduction = (100 - attacker->valOfBonuses(Bonus::ENEMY_DEFENCE_REDUCTION, -1024)) / 100.0;
-		attackDefenceDifference -= defender->Defense() * multDefenceReduction;
+		double multDefenceReduction = (100 - info.attackerBonuses->valOfBonuses(Bonus::ENEMY_DEFENCE_REDUCTION, -1024)) / 100.0;
+		attackDefenceDifference -= info.defenderBonuses->Defense() * multDefenceReduction;
 	}
 	else
 	{
-		attackDefenceDifference -= defender->Defense();
+		attackDefenceDifference -= info.defenderBonuses->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();
+	if(info.shooting) //precision handling (etc.)
+		attackDefenceDifference += info.attackerBonuses->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();
+		attackDefenceDifference += info.attackerBonuses->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))->totalValue();
 
 
-	if(attacker->getEffect(55)) //slayer handling
+	if(const Bonus *slayerEffect = info.attackerBonuses->getEffect(Spells::SLAYER)) //slayer handling
 	{
 		std::vector<int> affectedIds;
-		int spLevel = attacker->getEffect(55)->val;
+		int spLevel = slayerEffect->val;
 
 		for(int g = 0; g < VLC->creh->creatures.size(); ++g)
 		{
@@ -821,9 +843,9 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
 
 		for(ui32 g=0; g<affectedIds.size(); ++g)
 		{
-			if(defender->getCreature()->idNumber == affectedIds[g])
+			if(defenderType->idNumber == affectedIds[g])
 			{
-				attackDefenceDifference += VLC->spellh->spells[55]->powers[attacker->getEffect(55)->val];
+				attackDefenceDifference += VLC->spellh->spells[Spells::SLAYER]->powers[spLevel];
 				break;
 			}
 		}
@@ -843,51 +865,51 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
 
 
 	//applying jousting bonus
-	if( attacker->hasBonusOfType(Bonus::JOUSTING) && !defender->hasBonusOfType(Bonus::CHARGE_IMMUNITY) )
-		additiveBonus += charge * 0.05;
+	if( info.attackerBonuses->hasBonusOfType(Bonus::JOUSTING) && !info.defenderBonuses->hasBonusOfType(Bonus::CHARGE_IMMUNITY) )
+		additiveBonus += info.chargedFields * 0.05;
 
 
 	//handling secondary abilities and artifacts giving premies to them
-	if(shooting)
-		additiveBonus += attacker->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARCHERY) / 100.0;
+	if(info.shooting)
+		additiveBonus += info.attackerBonuses->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARCHERY) / 100.0;
 	else
-		additiveBonus += attacker->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::OFFENCE) / 100.0;
+		additiveBonus += info.attackerBonuses->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;
+	if(info.defenderBonuses)
+		multBonus *= (std::max(0, 100 - info.defenderBonuses->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARMORER))) / 100.0;
 
 	//handling hate effect
-	additiveBonus += attacker->valOfBonuses(Bonus::HATE, defender->getCreature()->idNumber) / 100.;
+	additiveBonus += info.attackerBonuses->valOfBonuses(Bonus::HATE, defenderType->idNumber) / 100.;
 
 	//luck bonus
-	if (lucky)
+	if (info.luckyHit)
 	{
 		additiveBonus += 1.0;
 	}
 
 	//ballista double dmg
-	if(ballistaDoubleDmg)
+	if(info.ballistaDoubleDamage)
 	{
 		additiveBonus += 1.0;
 	}
 
-	if (deathBlow) //Dread Knight and many WoGified creatures
+	if (info.deathBlow) //Dread Knight and many WoGified creatures
 	{
 		additiveBonus += 1.0;
 	}
 
 	//handling spell effects
-	if(!shooting && defender->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 0)) //eg. shield
+	if(!info.shooting && info.defenderBonuses->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 0)) //eg. shield
 	{
-		multBonus *= defender->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 0) / 100.0;
+		multBonus *= info.defenderBonuses->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 0) / 100.0;
 	}
-	else if(shooting && defender->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 1)) //eg. air shield
+	else if(info.shooting && info.defenderBonuses->hasBonusOfType(Bonus::GENERAL_DAMAGE_REDUCTION, 1)) //eg. air shield
 	{
-		multBonus *= defender->valOfBonuses(Bonus::GENERAL_DAMAGE_REDUCTION, 1) / 100.0;
+		multBonus *= info.defenderBonuses->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);
+	TBonusListPtr curseEffects = info.attackerBonuses->getBonuses(Selector::type(Bonus::ALWAYS_MINIMUM_DAMAGE)); //attacker->getEffect(42);
+	TBonusListPtr blessEffects = info.attackerBonuses->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;
 
@@ -904,12 +926,12 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
 	};
 
 	//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);
+	const bool distPenalty = !info.attackerBonuses->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY) && battleHasDistancePenalty(info.attackerBonuses, info.attackerPosition, info.defenderPosition);
+	const bool obstaclePenalty = battleHasWallPenalty(info.attackerBonuses, info.attackerPosition, info.defenderPosition);
 
-	if (shooting)
+	if (info.shooting)
 	{
-		if (distPenalty || defender->hasBonus(isAdvancedAirShield))
+		if (distPenalty || info.defenderBonuses->hasBonus(isAdvancedAirShield))
 		{
 			multBonus *= 0.5;
 		}
@@ -918,7 +940,7 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
 			multBonus *= 0.5; //cumulative
 		}
 	}
-	if (!shooting && attacker->hasBonusOfType(Bonus::SHOOTER) && !attacker->hasBonusOfType(Bonus::NO_MELEE_PENALTY))
+	if(!info.shooting && info.attackerBonuses->hasBonusOfType(Bonus::SHOOTER) && !info.attackerBonuses->hasBonusOfType(Bonus::NO_MELEE_PENALTY))
 	{
 		multBonus *= 0.5;
 	}
@@ -950,18 +972,38 @@ TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const
 	return returnedVal;
 }
 
+TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const CStack* defender, TQuantity attackerCount,
+	bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg ) const
+{
+	BattleAttackInfo bai(attacker, defender, shooting);
+	bai.attackerCount = attackerCount;
+	bai.chargedFields = charge;
+	bai.luckyHit = lucky;
+	bai.deathBlow = deathBlow;
+	bai.ballistaDoubleDamage = ballistaDoubleDmg;
+	return calculateDmgRange(bai);
+}
+
 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 BattleAttackInfo bai(attacker, defender, shooting);
+	return battleEstimateDamage(bai, retaliationDmg);
+}
+
+std::pair<ui32, ui32> CBattleInfoCallback::battleEstimateDamage(const BattleAttackInfo &bai, std::pair<ui32, ui32> * retaliationDmg /*= NULL*/) const
+{
+	RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
+
+	const bool shooting = battleCanShoot(bai.attacker, bai.defenderPosition); //TODO handle bonus bearer
 	//const ui8 mySide = !attacker->attackerOwned;
 
-	TDmgRange ret = calculateDmgRange(attacker, defender, shooting, 0, false, false, false);
+	TDmgRange ret = calculateDmgRange(bai);
 
 	if(retaliationDmg)
 	{
-		if(shooting)
+		if(bai.shooting)
 		{
 			retaliationDmg->first = retaliationDmg->second = 0;
 		}
@@ -972,7 +1014,11 @@ TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, con
 			{
 				BattleStackAttacked bsa;
 				bsa.damageAmount = ret.*pairElems[i];
-				retaliationDmg->*pairElems[!i] = calculateDmgRange(defender, attacker, bsa.newAmount, false, 0, false, false, false).*pairElems[!i];
+				bai.defender->prepareAttacked(bsa);
+
+				auto retaliationAttack = bai.reverse();
+				retaliationAttack.attackerCount = bsa.newAmount;
+				retaliationDmg->*pairElems[!i] = calculateDmgRange(retaliationAttack).*pairElems[!i];
 			}
 		}
 	}
@@ -980,7 +1026,6 @@ TDmgRange CBattleInfoCallback::battleEstimateDamage(const CStack * attacker, con
 	return ret;
 }
 
-
 shared_ptr<const CObstacleInstance> CBattleInfoCallback::battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking /*= true*/) const
 {
 	RETURN_IF_NOT_BATTLE(shared_ptr<const CObstacleInstance>());
@@ -1329,6 +1374,7 @@ ReachabilityInfo::TDistances CBattleInfoCallback::battleGetDistances(const CStac
 	RETURN_IF_NOT_BATTLE(ret);
 
 	ReachabilityInfo::Parameters params(stack);
+	params.perspective = battleGetMySide();
 	params.startPosition = hex.isValid() ? hex : stack->position;
 	auto reachability = getReachability(params);
 
@@ -1342,21 +1388,27 @@ ReachabilityInfo::TDistances CBattleInfoCallback::battleGetDistances(const CStac
 }
 
 si8 CBattleInfoCallback::battleHasDistancePenalty(const CStack * stack, BattleHex destHex) const
+{
+	return battleHasDistancePenalty(stack, stack->position, destHex);
+}
+
+si8 CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer *bonusBearer, BattleHex shooterPosition, BattleHex destHex) const
 {
 	RETURN_IF_NOT_BATTLE(false);
-	if(stack->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY))
+	if(bonusBearer->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY))
 		return false;
 
-	if(BattleHex::getDistance(stack->position, destHex) <= 10)
+	if(BattleHex::getDistance(shooterPosition, destHex) <= GameConstants::BATTLE_PENALTY_DISTANCE)
 		return false;
 
-	const CStack * dstStack = battleGetStackByPos(destHex, false);
-	if(dstStack)
+	if(const CStack * dstStack = battleGetStackByPos(destHex, false))
 	{
 		//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)
+			if(BattleHex::getDistance(shooterPosition, hex) <= GameConstants::BATTLE_PENALTY_DISTANCE)
 				return false;
+
+		//TODO what about two-hex shooters?
 	}
 
 	return true;
@@ -1503,7 +1555,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
 	{
 		if(spell->getTargetType() == CSpell::CREATURE
 			|| (spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE
-				&& mode == ECastingMode::HERO_CASTING
+				&& mode == ECastingMode::HERO_CASTING //TODO why???
 				&& caster
 				&& caster->getSpellSchoolLevel(spell) < SecSkillLevel::EXPERT))
 		{
@@ -1590,7 +1642,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	switch(spell->getTargetType())
 	{
 	case CSpell::CREATURE:
-	case CSpell::CREATURE_EXPERT_MASSIVE:
+	case CSpell::CREATURE_EXPERT_MASSIVE:  
 		if(mode == ECastingMode::HERO_CASTING)
 		{
 			const CGHeroInstance * caster = battleGetFightingHero(side);
@@ -1641,6 +1693,51 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	return ESpellCastProblem::OK;
 }
 
+std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(int player, const CSpell *spell) const
+{
+	std::vector<BattleHex> ret;
+	RETURN_IF_NOT_BATTLE(ret);
+
+	auto mode = ECastingMode::HERO_CASTING; //TODO get rid of this!
+
+	switch(spell->getTargetType())
+	{
+	case CSpell::CREATURE:
+	case CSpell::CREATURE_EXPERT_MASSIVE:
+		{
+			const CGHeroInstance * caster = battleGetFightingHero(playerToSide(player)); //TODO
+
+			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)
+							ret.push_back(stack->position);
+					break;
+
+				case CSpell::NEUTRAL:
+					if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
+						ret.push_back(stack->position);
+					break;
+
+				case CSpell::NEGATIVE:
+					if(stack->owner != caster->getOwner())
+						if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
+							ret.push_back(stack->position);
+					break;
+				}
+			}
+		}
+		break;
+	default:
+		tlog1 << "FIXME " << __FUNCTION__ << " doesn't work with target type " << spell->getTargetType() << std::endl;
+	}
+
+	return ret;
+}
+
 ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
 {
 	RETURN_IF_NOT_BATTLE(-1);
@@ -1906,6 +2003,7 @@ si8 CBattleInfoCallback::battleMaxSpellLevel() const
 
 	return GameConstants::SPELL_LEVELS;
 }
+
 bool AccessibilityInfo::accessible(BattleHex tile, const CStack *stack) const
 {
 	return accessible(tile, stack->doubleWide(), stack->attackerOwned);
@@ -2011,3 +2109,38 @@ bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProb
 
 	return problem == ESpellCastProblem::OK;
 }
+
+BattleAttackInfo::BattleAttackInfo(const CStack *Attacker, const CStack *Defender, bool Shooting)
+{
+	attacker = Attacker;
+	defender = Defender;
+
+	attackerBonuses = Attacker;
+	defenderBonuses = Defender;
+
+	attackerPosition = Attacker->position;
+	defenderPosition = Defender->position;
+
+	attackerCount = Attacker->count;
+	shooting = Shooting;
+	chargedFields = 0;
+
+	luckyHit = false;
+	deathBlow = false;
+	ballistaDoubleDamage = false;
+}
+
+BattleAttackInfo BattleAttackInfo::reverse() const
+{
+	BattleAttackInfo ret = *this;
+	std::swap(ret.attacker, ret.defender);
+	std::swap(ret.attackerBonuses, ret.defenderBonuses);
+	std::swap(ret.attackerPosition, ret.defenderPosition);
+
+	ret.attackerCount = ret.attacker->count;
+	ret.shooting = false;
+	ret.chargedFields = 0;
+	ret.luckyHit = ret.ballistaDoubleDamage = ret.deathBlow = false;
+
+	return ret;
+}

+ 26 - 1
lib/CBattleCallback.h

@@ -8,6 +8,7 @@ class CStack;
 class CSpell;
 struct BattleInfo;
 struct CObstacleInstance;
+class IBonusBearer;
 
 namespace boost
 {class shared_mutex;}
@@ -47,6 +48,7 @@ protected:
 
 public:
 	boost::shared_mutex &getGsMutex(); //just return a reference to mutex, does not lock nor anything
+	int getPlayerID() const;
 
 	friend class CBattleInfoEssentials;
 };
@@ -178,6 +180,24 @@ public:
 	//ESpellCastProblem::ESpellCastProblem battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const; //Checks if player is able to cast spells (at all) at the moment
 };
 
+struct DLL_LINKAGE BattleAttackInfo
+{
+	const IBonusBearer *attackerBonuses, *defenderBonuses;
+	const CStack *attacker, *defender;
+	BattleHex attackerPosition, defenderPosition;
+
+	int attackerCount;
+	bool shooting;
+	int chargedFields;
+
+	bool luckyHit;
+	bool deathBlow;
+	bool ballistaDoubleDamage;
+
+	BattleAttackInfo(const CStack *Attacker, const CStack *Defender, bool Shooting = false);
+	BattleAttackInfo reverse() const;
+};
+
 class DLL_LINKAGE CBattleInfoCallback : public virtual CBattleInfoEssentials
 {
 public:
@@ -201,14 +221,18 @@ public:
 	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 BattleAttackInfo &info) 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, 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 BattleAttackInfo &bai, 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>
 	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 battleHasDistancePenalty(const IBonusBearer *bonusBearer, BattleHex shooterPosition, BattleHex destHex ) const;
 	si8 battleHasWallPenalty(const CStack * stack, BattleHex destHex) const; //checks if given stack has wall penalty
+	si8 battleHasWallPenalty(const IBonusBearer *bonusBearer, BattleHex shooterPosition, 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 
@@ -218,6 +242,7 @@ public:
 	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
+	std::vector<BattleHex> battleGetPossibleTargets(int player, const CSpell *spell) const;
 
 	si32 battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
 	TSpell getRandomBeneficialSpell(const CStack * subject) const;

+ 1 - 1
lib/CCampaignHandler.cpp

@@ -334,7 +334,7 @@ void CCampaignScenario::prepareCrossoverHeroes( std::vector<CGHeroInstance *> he
 		BOOST_FOREACH(CGHeroInstance * cgh, crossoverHeroes)
 		{
 #define RESET_PRIM_SKILL(NAME, VALNAME) \
-			cgh->getBonus(Selector::type(Bonus::PRIMARY_SKILL) && \
+			cgh->getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL) && \
 				Selector::subtype(PrimarySkill::NAME) && \
 				Selector::sourceType(Bonus::HERO_BASE_SKILL) )->val = cgh->type->heroClass->VALNAME;
 

+ 1 - 1
lib/CCreatureHandler.cpp

@@ -257,7 +257,7 @@ static void RemoveAbility(CCreature *cre, const JsonNode &ability)
 
 	Bonus::BonusType ecf = static_cast<Bonus::BonusType>(typeNo);
 
-	Bonus *b = cre->getBonus(Selector::type(ecf));
+	Bonus *b = cre->getBonusLocalFirst(Selector::type(ecf));
 	cre->removeBonus(b);
 }
 

+ 3 - 3
lib/CGameInterface.h

@@ -16,7 +16,7 @@
 
 using namespace boost::logic;
 class CCallback;
-class CPlayerBattleCallback;
+class CBattleCallback;
 class ICallback;
 class CGlobalAI;
 struct Component;
@@ -61,7 +61,7 @@ public:
 	std::string dllName;
 
 	virtual ~CBattleGameInterface() {};
-	virtual void init(CPlayerBattleCallback * CB){};
+	virtual void init(CBattleCallback * 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;
-	CPlayerBattleCallback *cbc;
+	CBattleCallback *cbc;
 
 	//battle interface
 	virtual BattleAction activeStack(const CStack * stack);

+ 15 - 10
lib/CGameState.cpp

@@ -1547,20 +1547,20 @@ void CGameState::initDuel()
 		{
 			CCreature *c = VLC->creh->creatures[cc.id];
 			if(cc.attack >= 0)
-				c->getBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK))->val = cc.attack;
+				c->getBonusLocalFirst(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK))->val = cc.attack;
 			if(cc.defense >= 0)
-				c->getBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE))->val = cc.defense;
+				c->getBonusLocalFirst(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE))->val = cc.defense;
 			if(cc.speed >= 0)
-				c->getBonus(Selector::type(Bonus::STACKS_SPEED))->val = cc.speed;
+				c->getBonusLocalFirst(Selector::type(Bonus::STACKS_SPEED))->val = cc.speed;
 			if(cc.HP >= 0)
-				c->getBonus(Selector::type(Bonus::STACK_HEALTH))->val = cc.HP;
+				c->getBonusLocalFirst(Selector::type(Bonus::STACK_HEALTH))->val = cc.HP;
 			if(cc.dmg >= 0)
 			{
-				c->getBonus(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1))->val = cc.dmg;
-				c->getBonus(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2))->val = cc.dmg;
+				c->getBonusLocalFirst(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1))->val = cc.dmg;
+				c->getBonusLocalFirst(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2))->val = cc.dmg;
 			}
 			if(cc.shoots >= 0)
-				c->getBonus(Selector::type(Bonus::SHOTS))->val = cc.shoots;
+				c->getBonusLocalFirst(Selector::type(Bonus::SHOTS))->val = cc.shoots;
 		}
 	}
 
@@ -2757,7 +2757,7 @@ DuelParameters DuelParameters::fromJSON(const std::string &fname)
 {
 	DuelParameters ret;
 
-	const JsonNode duelData(ResourceID(fname, EResType::TEXT));
+	const JsonNode duelData(ResourceID("DATA/" + fname, EResType::TEXT));
 	ret.terType = duelData["terType"].Float();
 	ret.bfieldType = duelData["bfieldType"].Float();
 	BOOST_FOREACH(const JsonNode &n, duelData["sides"].Vector())
@@ -2791,8 +2791,13 @@ DuelParameters DuelParameters::fromJSON(const std::string &fname)
 
 		if(ss.heroId != -1)
 		{
-			BOOST_FOREACH(const JsonNode &spell, n["spells"].Vector())
-				ss.spells.insert(spell.Float());
+			auto spells = n["spells"];
+			if(spells.getType() == JsonNode::DATA_STRING  &&  spells.String() == "all")
+				BOOST_FOREACH(auto spell, VLC->spellh->spells)
+					ss.spells.insert(spell->id);
+			else
+				BOOST_FOREACH(const JsonNode &spell, n["spells"].Vector())
+					ss.spells.insert(spell.Float());
 		}
 	}
 

+ 2 - 2
lib/CObjectHandler.cpp

@@ -1148,7 +1148,7 @@ void CGHeroInstance::updateSkill(int which, int val)
 		bool luck = which == LUCK;
 		Bonus::BonusType type[] = {Bonus::MORALE, Bonus::LUCK};
 
-		Bonus *b = getBonus(Selector::type(type[luck]) && Selector::sourceType(Bonus::SECONDARY_SKILL));
+		Bonus *b = getBonusLocalFirst(Selector::type(type[luck]) && Selector::sourceType(Bonus::SECONDARY_SKILL));
 		if(!b)
 		{
 			b = new Bonus(Bonus::PERMANENT, type[luck], Bonus::SECONDARY_SKILL, +val, which, which, Bonus::BASE_NUMBER);
@@ -1160,7 +1160,7 @@ void CGHeroInstance::updateSkill(int which, int val)
 	else if(which == DIPLOMACY) //surrender discount: 20% per level
 	{
 
-		if(Bonus *b = getBonus(Selector::type(Bonus::SURRENDER_DISCOUNT) && Selector::sourceType(Bonus::SECONDARY_SKILL)))
+		if(Bonus *b = getBonusLocalFirst(Selector::type(Bonus::SURRENDER_DISCOUNT) && Selector::sourceType(Bonus::SECONDARY_SKILL)))
 			b->val = +val;
 		else
 			addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::SURRENDER_DISCOUNT, Bonus::SECONDARY_SKILL, val * 20, which));

+ 2 - 0
lib/GameConstants.h

@@ -80,6 +80,8 @@ namespace GameConstants
 	const int AVAILABLE_HEROES_PER_PLAYER = 2;
 	const int SPELLBOOK_GOLD_COST = 500;
 
+	const int BATTLE_PENALTY_DISTANCE = 10; //if the distance is > than this, then shooting stack has distance penalty
+
 	const ui16 BACKPACK_START = 19;
 	const int ID_CATAPULT = 3, ID_LOCK = 145;
 

+ 49 - 4
lib/HeroBonus.cpp

@@ -476,7 +476,52 @@ const TBonusListPtr IBonusBearer::getSpellBonuses() const
 	return getBonuses(Selector::sourceType(Bonus::SPELL_EFFECT), cachingStr.str());
 }
 
-Bonus * CBonusSystemNode::getBonus(const CSelector &selector)
+const Bonus * IBonusBearer::getEffect(ui16 id, int turn /*= 0*/) const
+{
+	//TODO should check only local bonuses?
+	auto bonuses = getAllBonuses();
+	BOOST_FOREACH(const Bonus *it, *bonuses)
+	{
+		if(it->source == Bonus::SPELL_EFFECT && it->sid == id)
+		{
+			if(!turn || it->turnsRemain > turn)
+				return &(*it);
+		}
+	}
+	return NULL;
+}
+
+ui8 IBonusBearer::howManyEffectsSet(ui16 id) const
+{
+	//TODO should check only local bonuses?
+	ui8 ret = 0;
+
+	auto bonuses = getAllBonuses();
+	BOOST_FOREACH(const Bonus *it, *bonuses)
+	{
+		if(it->source == Bonus::SPELL_EFFECT && it->sid == id) //effect found
+		{
+			++ret;
+		}
+	}
+
+	return ret;
+}
+
+const TBonusListPtr IBonusBearer::getAllBonuses() const
+{
+	auto matchAll= [] (const Bonus *) { return true; };
+	auto matchNone= [] (const Bonus *) { return true; };
+	return getAllBonuses(matchAll, matchNone);
+}
+
+const Bonus * IBonusBearer::getBonus(const CSelector &selector) const
+{
+	auto bonuses = getAllBonuses();
+	return bonuses->getFirst(selector);
+}
+
+Bonus * CBonusSystemNode::getBonusLocalFirst(const CSelector &selector)
 {
 	Bonus *ret = bonuses.getFirst(selector);
 	if(ret)
@@ -484,7 +529,7 @@ Bonus * CBonusSystemNode::getBonus(const CSelector &selector)
 
 	FOREACH_PARENT(pname)
 	{
-		ret = pname->getBonus(selector);
+		ret = pname->getBonusLocalFirst(selector);
 		if (ret)
 			return ret;
 	}
@@ -492,9 +537,9 @@ Bonus * CBonusSystemNode::getBonus(const CSelector &selector)
 	return NULL;
 }
 
-const Bonus * CBonusSystemNode::getBonus( const CSelector &selector ) const
+const Bonus * CBonusSystemNode::getBonusLocalFirst( const CSelector &selector ) const
 {
-	return (const_cast<CBonusSystemNode*>(this))->getBonus(selector);
+	return (const_cast<CBonusSystemNode*>(this))->getBonusLocalFirst(selector);
 }
 
 void CBonusSystemNode::getParents(TCNodes &out) const /*retreives list of parent nodes (nodes to inherit bonuses from) */

+ 14 - 3
lib/HeroBonus.h

@@ -351,10 +351,16 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus);
 class DLL_LINKAGE BonusList
 {
 private:
-	std::vector<Bonus*> bonuses;
+	typedef std::vector<Bonus*> TInternalContainer;
+
+	TInternalContainer bonuses;
 	bool belongsToTree;
 
+
 public:
+	typedef TInternalContainer::const_reference const_reference;
+	typedef TInternalContainer::value_type value_type;
+
 	BonusList(bool BelongsToTree = false);
 	BonusList(const BonusList &bonusList);
 	BonusList& operator=(const BonusList &bonusList);
@@ -508,6 +514,9 @@ public:
 	const TBonusListPtr getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const;
 	const TBonusListPtr getBonuses(const CSelector &selector, const std::string &cachingStr = "") const;
 
+	const TBonusListPtr getAllBonuses() const;
+	const Bonus *getBonus(const CSelector &selector) const; //returns any bonus visible on node that matches (or nullptr if none matches)
+
 	//legacy interface
 	int valOfBonuses(Bonus::BonusType type, const CSelector &selector) const;
 	int valOfBonuses(Bonus::BonusType type, int subtype = -1) const; //subtype -> subtype of bonus, if -1 then anyt;
@@ -526,6 +535,8 @@ public:
 	ui32 MaxHealth() const; //get max HP of stack with all modifiers
 	bool isLiving() const; //non-undead, non-non living or alive
 	virtual si32 magicResistance() const;
+	const Bonus * getEffect(ui16 id, int turn = 0) const; //effect id (SP)
+	ui8 howManyEffectsSet(ui16 id) const; //returns amount of effects with given id set for this stack
 
 	si32 manaLimit() const; //maximum mana value for this hero (basically 10*knowledge)
 	int getPrimSkillLevel(int id) const; //0-attack, 1-defence, 2-spell power, 3-knowledge
@@ -567,7 +578,7 @@ public:
 	TBonusListPtr limitBonuses(const BonusList &allBonuses) const; //same as above, returns out by val for convienence
 	const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = NULL, const std::string &cachingStr = "") const;
 	void getParents(TCNodes &out) const;  //retrieves list of parent nodes (nodes to inherit bonuses from),
-	const Bonus *getBonus(const CSelector &selector) const;
+	const Bonus *getBonusLocalFirst(const CSelector &selector) const;
 
 	//non-const interface
 	void getParents(TNodes &out);  //retrieves list of parent nodes (nodes to inherit bonuses from)
@@ -575,7 +586,7 @@ public:
 	void getRedAncestors(TNodes &out);
 	void getRedChildren(TNodes &out);
 	void getRedDescendants(TNodes &out);
-	Bonus *getBonus(const CSelector &selector);
+	Bonus *getBonusLocalFirst(const CSelector &selector);
 
 	void attachTo(CBonusSystemNode *parent);
 	void detachFrom(CBonusSystemNode *parent);

+ 3 - 3
lib/NetPacksLib.cpp

@@ -54,7 +54,7 @@ DLL_LINKAGE void SetPrimSkill::applyGs( CGameState *gs )
 
 	if(which <4)
 	{
-		Bonus *skill = hero->getBonus(Selector::type(Bonus::PRIMARY_SKILL) && Selector::subtype(which) && Selector::sourceType(Bonus::HERO_BASE_SKILL));
+		Bonus *skill = hero->getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL) && Selector::subtype(which) && Selector::sourceType(Bonus::HERO_BASE_SKILL));
 		assert(skill);
 		
 		if(abs)
@@ -1025,7 +1025,7 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs( CGameState *gs )
 		}
 		case Bonus::POISON:
 		{
-			Bonus * b = st->getBonus(Selector::source(Bonus::SPELL_EFFECT, 71) && Selector::type(Bonus::STACK_HEALTH));
+			Bonus * b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, 71) && Selector::type(Bonus::STACK_HEALTH));
 			if (b)
 				b->val = val;
 			break;
@@ -1299,7 +1299,7 @@ DLL_LINKAGE void SetStackEffect::applyGs( CGameState *gs )
 		CStack *s = gs->curB->getStack(id);
 		if(s)
 		{
-			if(spellid == 47 || spellid == 80 || !s->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellid)))//disrupting ray or acid breath or not on the list - just add	
+			if(spellid == Spells::DISRUPTING_RAY || spellid == Spells::ACID_BREATH_DEFENSE || !s->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellid)))//disrupting ray or acid breath or not on the list - just add	
 			{
 				BOOST_FOREACH(Bonus &fromEffect, effect)
 				{

+ 53 - 55
server/CGameHandler.cpp

@@ -787,7 +787,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 		}
 	}
 
-	const Bonus * bonus = att->getBonus(Selector::type(Bonus::SPELL_LIKE_ATTACK));
+	const Bonus * bonus = att->getBonusLocalFirst(Selector::type(Bonus::SPELL_LIKE_ATTACK));
 	if (bonus && (bat.shot())) //TODO: make it work in meele?
 	{
 		bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
@@ -1342,7 +1342,7 @@ void CGameHandler::newTurn()
 		}
 		if (t->hasBonusOfType (Bonus::DARKNESS))
 		{
-			t->hideTiles(t->getOwner(), t->getBonus(Selector::type(Bonus::DARKNESS))->val);
+			t->hideTiles(t->getOwner(), t->getBonusLocalFirst(Selector::type(Bonus::DARKNESS))->val);
 		}
 		//unhiding what shouldn't be hidden? //that's handled in netpacks client
 	}
@@ -3642,7 +3642,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			BattleStackAdded bsa;
 			bsa.attacker = summoner->attackerOwned;
 
-			bsa.creID = summoner->getBonus(Selector::type(Bonus::DAEMON_SUMMONING))->subtype; //in case summoner can summon more than one type of monsters... scream!
+			bsa.creID = summoner->getBonusLocalFirst(Selector::type(Bonus::DAEMON_SUMMONING))->subtype; //in case summoner can summon more than one type of monsters... scream!
 			ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID);
 			bsa.amount = std::min ((ui32)(risedHp / VLC->creh->creatures[bsa.creID]->MaxHealth()), destStack->baseAmount);
 
@@ -3676,8 +3676,8 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			int spellID = ba.additionalInfo;
 			BattleHex destination(ba.destinationTile);
 
-			const Bonus *randSpellcaster = stack->getBonus(Selector::type(Bonus::RANDOM_SPELLCASTER));
-			const Bonus * spellcaster = stack->getBonus(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
+			const Bonus *randSpellcaster = stack->getBonusLocalFirst(Selector::type(Bonus::RANDOM_SPELLCASTER));
+			const Bonus * spellcaster = stack->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
 
 			//TODO special bonus for genies ability
 			if(randSpellcaster && battleGetRandomStackSpell(stack, CBattleInfoCallback::RANDOM_AIMED) < 0)
@@ -4126,67 +4126,65 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 			pseudoBonus.val = spellLvl;
 			pseudoBonus.turnsRemain = gs->curB->calculateSpellDuration(spell, caster, stackSpellPower ? stackSpellPower : usedSpellPower);
 			CStack::stackEffectToFeature(sse.effect, pseudoBonus);
-			if (spellID == 72 && stack)//bind
+			if (spellID == Spells::BIND && stack)//bind
 			{
 				sse.effect.back().additionalInfo = stack->ID; //we need to know who casted Bind
 			}
 			const Bonus * bonus = NULL;
 			if (caster)
-				bonus = caster->getBonus(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID));
+				bonus = caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID));
+			//TODO does hero speciality should affects his stack casting spells?
 
 			si32 power = 0;
-			for(auto it = attackedCres.begin(); it != attackedCres.end(); ++it)
+			BOOST_FOREACH(const CStack *affected, attackedCres)
 			{
-				if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
+				if(vstd::contains(sc.resisted, affected->ID)) //this creature resisted the spell
 					continue;
-				sse.stacks.push_back((*it)->ID);
+				sse.stacks.push_back(affected->ID);
 
 				//Apply hero specials - peculiar enchants
-				if ((*it)->base) // no war machines - TODO: make it work
-				{
-					ui8 tier = (*it)->base->type->level;
-					if (bonus)
- 					{
- 	 					switch(bonus->additionalInfo)
- 	 					{
- 	 						case 0: //normal
-							{
- 	 							switch(tier)
- 	 							{
- 	 								case 1: case 2:
- 	 									power = 3;
- 	 								break;
- 	 								case 3: case 4:
- 	 									power = 2;
- 	 								break;
- 	 								case 5: case 6:
- 	 									power = 1;
- 	 								break;
- 	 							}
-								Bonus specialBonus(sse.effect.back());
-								specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
-								sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> ((*it)->ID, specialBonus)); //additional premy to given effect
-							}
- 	 						break;
- 	 						case 1: //only Coronius as yet
-							{
- 	 							power = std::max(5 - tier, 0);
-								Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
-								specialBonus.sid = spellID;
-				 	 			sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> ((*it)->ID, specialBonus)); //additional attack to Slayer effect
-							}
- 	 						break;
- 	 					}
- 						}
-					if (caster && caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, spellID)) //TODO: better handling of bonus percentages
+				const ui8 tier = affected->getCreature()->level;
+				if (bonus)
+ 				{
+ 	 				switch(bonus->additionalInfo)
  	 				{
- 	 					int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID) / tier;
-						Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
-						specialBonus.valType = Bonus::PERCENT_TO_ALL;
-						specialBonus.sid = spellID;
- 	 					sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> ((*it)->ID, specialBonus));
+ 	 					case 0: //normal
+						{
+ 	 						switch(tier)
+ 	 						{
+ 	 							case 1: case 2:
+ 	 								power = 3;
+ 	 							break;
+ 	 							case 3: case 4:
+ 	 								power = 2;
+ 	 							break;
+ 	 							case 5: case 6:
+ 	 								power = 1;
+ 	 							break;
+ 	 						}
+							Bonus specialBonus(sse.effect.back());
+							specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
+							sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
+						}
+ 	 					break;
+ 	 					case 1: //only Coronius as yet
+						{
+ 	 						power = std::max(5 - tier, 0);
+							Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
+							specialBonus.sid = spellID;
+				 	 		sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
+						}
+ 	 					break;
  	 				}
-				}
+ 				}
+				if (caster && caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, spellID)) //TODO: better handling of bonus percentages
+ 	 			{
+ 	 				int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID) / tier;
+					Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
+					specialBonus.valType = Bonus::PERCENT_TO_ALL;
+					specialBonus.sid = spellID;
+ 	 				sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
+ 	 			}
 			}
 
 			if(!sse.stacks.empty())
@@ -4531,7 +4529,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 
 		if(st->hasBonusOfType(Bonus::POISON))
 		{
-			const Bonus * b = st->getBonus(Selector::source(Bonus::SPELL_EFFECT, 71) && Selector::type(Bonus::STACK_HEALTH));
+			const Bonus * b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, 71) && Selector::type(Bonus::STACK_HEALTH));
 			if (b) //TODO: what if not?...
 			{
 				bte.val = std::max (b->val - 10, -(st->valOfBonuses(Bonus::POISON)));
@@ -5984,7 +5982,7 @@ void CGameHandler::runBattle()
 				nextStackMorale = next->MoraleVal();
 				if(!vstd::contains(next->state,EBattleStackState::HAD_MORALE)  //only one extra move per turn possible
 					&& !vstd::contains(next->state,EBattleStackState::DEFENDING)
-					&& !vstd::contains(next->state,EBattleStackState::WAITING)
+					&& !next->waited()
 					&& !vstd::contains(next->state, EBattleStackState::FEAR)
 					&&  next->alive()
 					&&  nextStackMorale > 0