Browse Source

Merge pull request #359 from vcmi/SpellsRefactoring9

All important issues are fixed and branch is finally ready to merge!
ArseniyShestakov 7 years ago
parent
commit
d64a3899a3
100 changed files with 4182 additions and 3718 deletions
  1. 64 29
      AI/BattleAI/AttackPossibility.cpp
  2. 14 28
      AI/BattleAI/AttackPossibility.h
  3. 5 0
      AI/BattleAI/BattleAI.cbp
  4. 286 144
      AI/BattleAI/BattleAI.cpp
  5. 1 13
      AI/BattleAI/BattleAI.h
  6. 2 0
      AI/BattleAI/CMakeLists.txt
  7. 5 7
      AI/BattleAI/EnemyInfo.cpp
  8. 7 12
      AI/BattleAI/EnemyInfo.h
  9. 21 0
      AI/BattleAI/PossibleSpellcast.cpp
  10. 29 0
      AI/BattleAI/PossibleSpellcast.h
  11. 46 21
      AI/BattleAI/PotentialTargets.cpp
  12. 2 4
      AI/BattleAI/PotentialTargets.h
  13. 368 6
      AI/BattleAI/StackWithBonuses.cpp
  14. 95 5
      AI/BattleAI/StackWithBonuses.h
  15. 14 35
      AI/StupidAI/StupidAI.cpp
  16. 1 5
      AI/StupidAI/StupidAI.h
  17. 2 2
      AI/VCAI/VCAI.cpp
  18. 6 5
      CCallback.cpp
  19. 2 3
      CCallback.h
  20. BIN
      Mods/vcmi/Sprites/vcmi/battleQueue/defendBig.png
  21. BIN
      Mods/vcmi/Sprites/vcmi/battleQueue/defendSmall.png
  22. 8 0
      Mods/vcmi/Sprites/vcmi/battleQueue/statesBig.json
  23. 8 0
      Mods/vcmi/Sprites/vcmi/battleQueue/statesSmall.json
  24. BIN
      Mods/vcmi/Sprites/vcmi/battleQueue/waitBig.png
  25. BIN
      Mods/vcmi/Sprites/vcmi/battleQueue/waitSmall.png
  26. 0 356
      client/CDefHandler.cpp
  27. 0 94
      client/CDefHandler.h
  28. 40 0
      client/CMT.cpp
  29. 0 2
      client/CMakeLists.txt
  30. 111 99
      client/CPlayerInterface.cpp
  31. 3 6
      client/CPlayerInterface.h
  32. 2 1
      client/CPreGame.cpp
  33. 5 3
      client/Client.cpp
  34. 1 1
      client/Client.h
  35. 6 43
      client/NetPacksClient.cpp
  36. 0 2
      client/VCMI_client.cbp
  37. 48 38
      client/battle/CBattleAnimations.cpp
  38. 4 1
      client/battle/CBattleAnimations.h
  39. 239 207
      client/battle/CBattleInterface.cpp
  40. 22 21
      client/battle/CBattleInterface.h
  41. 84 59
      client/battle/CBattleInterfaceClasses.cpp
  42. 11 11
      client/battle/CBattleInterfaceClasses.h
  43. 3 3
      client/windows/CCastleInterface.cpp
  44. 7 7
      client/windows/CCreatureWindow.cpp
  45. 5 0
      client/windows/CHeroWindow.cpp
  46. 3 1
      client/windows/CHeroWindow.h
  47. 37 93
      client/windows/CSpellWindow.cpp
  48. 1 0
      client/windows/CreaturePurchaseCard.cpp
  49. 2 1
      client/windows/QuickRecruitmentWindow.cpp
  50. 6 1
      config/schemas/settings.json
  51. 10 1
      config/schemas/spell.json
  52. 101 62
      config/spells/ability.json
  53. 131 70
      config/spells/offensive.json
  54. 405 28
      config/spells/other.json
  55. 186 95
      config/spells/timed.json
  56. 58 0
      include/vstd/RNG.h
  57. 3 17
      lib/CArtHandler.cpp
  58. 0 6
      lib/CArtHandler.h
  59. 0 14
      lib/CCreatureHandler.cpp
  60. 0 6
      lib/CCreatureHandler.h
  61. 3 3
      lib/CGameInfoCallback.cpp
  62. 5 2
      lib/CGameInfoCallback.h
  63. 7 18
      lib/CGameInterface.cpp
  64. 3 7
      lib/CGameInterface.h
  65. 8 9
      lib/CGameState.cpp
  66. 5 5
      lib/CGameState.h
  67. 49 4
      lib/CMakeLists.txt
  68. 84 41
      lib/CModHandler.cpp
  69. 38 34
      lib/CModHandler.h
  70. 7 2
      lib/CRandomGenerator.cpp
  71. 10 32
      lib/CRandomGenerator.h
  72. 1 5
      lib/CSkillHandler.cpp
  73. 1 1
      lib/CSkillHandler.h
  74. 130 666
      lib/CStack.cpp
  75. 26 212
      lib/CStack.h
  76. 2 4
      lib/CThreadHelper.cpp
  77. 61 18
      lib/GameConstants.cpp
  78. 34 63
      lib/GameConstants.h
  79. 92 45
      lib/HeroBonus.cpp
  80. 22 9
      lib/HeroBonus.h
  81. 2 2
      lib/IBonusTypeHandler.h
  82. 8 8
      lib/IGameEventsReceiver.h
  83. 3 6
      lib/IHandlerBase.h
  84. 1 1
      lib/JsonNode.h
  85. 67 187
      lib/NetPacks.h
  86. 138 16
      lib/NetPacksBase.h
  87. 102 389
      lib/NetPacksLib.cpp
  88. 8 8
      lib/VCMI_Lib.cpp
  89. 4 4
      lib/VCMI_Lib.h
  90. 61 9
      lib/VCMI_lib.cbp
  91. 22 0
      lib/VCMI_lib.vcxproj
  92. 14 12
      lib/battle/AccessibilityInfo.cpp
  93. 5 2
      lib/battle/AccessibilityInfo.h
  94. 103 40
      lib/battle/BattleAction.cpp
  95. 46 21
      lib/battle/BattleAction.h
  96. 10 21
      lib/battle/BattleAttackInfo.cpp
  97. 10 13
      lib/battle/BattleAttackInfo.h
  98. 25 2
      lib/battle/BattleHex.cpp
  99. 12 0
      lib/battle/BattleHex.h
  100. 433 129
      lib/battle/BattleInfo.cpp

+ 64 - 29
AI/BattleAI/AttackPossibility.cpp

@@ -10,53 +10,88 @@
 #include "StdInc.h"
 #include "AttackPossibility.h"
 
-int AttackPossibility::damageDiff() const
+AttackPossibility::AttackPossibility(BattleHex tile_, const BattleAttackInfo & attack_)
+	: tile(tile_),
+	attack(attack_)
 {
-	if (!priorities)
-		priorities = new Priorities();
-	const auto dealtDmgValue = priorities->stackEvaluator(enemy) * damageDealt;
-	const auto receivedDmgValue = priorities->stackEvaluator(attack.attacker) * damageReceived;
-	return dealtDmgValue - receivedDmgValue;
 }
 
-int AttackPossibility::attackValue() const
+
+int64_t AttackPossibility::damageDiff() const
+{
+	//TODO: use target priority from HypotheticBattle
+	const auto dealtDmgValue = damageDealt;
+	const auto receivedDmgValue = damageReceived;
+
+	int64_t diff = 0;
+
+	//friendly fire or not
+	if(attack.attacker->unitSide() == attack.defender->unitSide())
+		diff = -dealtDmgValue - receivedDmgValue;
+	else
+		diff = dealtDmgValue - receivedDmgValue;
+
+	//mind control
+	auto actualSide = getCbc()->playerToSide(getCbc()->battleGetOwner(attack.attacker));
+	if(actualSide && actualSide.get() != attack.attacker->unitSide())
+		diff = -diff;
+	return diff;
+}
+
+int64_t AttackPossibility::attackValue() const
 {
 	return damageDiff() + tacticImpact;
 }
 
-AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex)
+AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex)
 {
-	auto attacker = AttackInfo.attacker;
-	auto enemy = AttackInfo.defender;
+	const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
+	static const auto selectorBlocksRetaliation = Selector::type(Bonus::BLOCKS_RETALIATION);
+
+	const bool counterAttacksBlocked = attackInfo.attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
+
+	AttackPossibility ap(hex, attackInfo);
 
-	const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacks.available());
-	const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION);
-	const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue();
+	ap.attackerState = attackInfo.attacker->acquireState();
 
-	AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0};
+	const int totalAttacks = ap.attackerState->getTotalAttacks(attackInfo.shooting);
 
-	auto curBai = AttackInfo; //we'll modify here the stack counts
-	for(int i  = 0; i < totalAttacks; i++)
+	if(!attackInfo.shooting)
+		ap.attackerState->setPosition(hex);
+
+	auto defenderState = attackInfo.defender->acquireState();
+	ap.affectedUnits.push_back(defenderState);
+
+	for(int i = 0; i < totalAttacks; i++)
 	{
-		std::pair<ui32, ui32> retaliation(0,0);
-		auto attackDmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), curBai, &retaliation);
-		ap.damageDealt = (attackDmg.first + attackDmg.second) / 2;
-		ap.damageReceived = (retaliation.first + retaliation.second) / 2;
+		TDmgRange retaliation(0,0);
+		auto attackDmg = getCbc()->battleEstimateDamage(ap.attack, &retaliation);
+
+		vstd::amin(attackDmg.first, defenderState->getAvailableHealth());
+		vstd::amin(attackDmg.second, defenderState->getAvailableHealth());
+
+		vstd::amin(retaliation.first, ap.attackerState->getAvailableHealth());
+		vstd::amin(retaliation.second, ap.attackerState->getAvailableHealth());
+
+		ap.damageDealt += (attackDmg.first + attackDmg.second) / 2;
 
-		if(remainingCounterAttacks <= i || counterAttacksBlocked)
-			ap.damageReceived = 0;
+		ap.attackerState->afterAttack(attackInfo.shooting, false);
 
-		curBai.attackerHealth = attacker->healthAfterAttacked(ap.damageReceived);
-		curBai.defenderHealth = enemy->healthAfterAttacked(ap.damageDealt);
-		if(curBai.attackerHealth.getCount() <= 0)
+		//FIXME: use ranged retaliation
+		if(!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked)
+		{
+			ap.damageReceived += (retaliation.first + retaliation.second) / 2;
+			defenderState->afterAttack(attackInfo.shooting, true);
+		}
+
+		ap.attackerState->damage(ap.damageReceived);
+		defenderState->damage(ap.damageDealt);
+
+		if(!ap.attackerState->alive() || !defenderState->alive())
 			break;
-		//TODO what about defender? should we break? but in pessimistic scenario defender might be alive
 	}
 
 	//TODO other damage related to attack (eg. fire shield and other abilities)
 
 	return ap;
 }
-
-
-Priorities* AttackPossibility::priorities = nullptr;

+ 14 - 28
AI/BattleAI/AttackPossibility.h

@@ -8,43 +8,29 @@
  *
  */
 #pragma once
-#include "../../lib/CStack.h"
+#include "../../lib/battle/CUnitState.h"
 #include "../../CCallback.h"
 #include "common.h"
-
-
-struct HypotheticChangesToBattleState
-{
-	std::map<const CStack *, const IBonusBearer *> bonusesOfStacks;
-	std::map<const CStack *, int> counterAttacksLeft;
-};
-
-class Priorities
-{
-public:
-	std::vector<double> resourceTypeBaseValues;
-	std::function<double(const CStack *)> stackEvaluator;
-	Priorities()
-	{
-		//        range::copy(VLC->objh->resVals, std::back_inserter(resourceTypeBaseValues));
-		stackEvaluator = [](const CStack*){ return 1.0; };
-	}
-};
+#include "StackWithBonuses.h"
 
 class AttackPossibility
 {
 public:
-	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 tacticImpact;
+	std::shared_ptr<battle::CUnitState> attackerState;
+
+	std::vector<std::shared_ptr<battle::CUnitState>> affectedUnits;
+
+	int64_t damageDealt = 0;
+	int64_t damageReceived = 0; //usually by counter-attack
+	int64_t tacticImpact = 0;
+
+	AttackPossibility(BattleHex tile_, const BattleAttackInfo & attack_);
 
-	int damageDiff() const;
-	int attackValue() const;
+	int64_t damageDiff() const;
+	int64_t attackValue() const;
 
-	static AttackPossibility evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex);
-	static Priorities * priorities;
+	static AttackPossibility evaluate(const BattleAttackInfo & attackInfo, BattleHex hex);
 };

+ 5 - 0
AI/BattleAI/BattleAI.cbp

@@ -58,6 +58,7 @@
 			<Add option="-Wno-sign-compare" />
 			<Add option="-Wno-unused-parameter" />
 			<Add option="-Wno-overloaded-virtual" />
+			<Add option="-DBOOST_THREAD_USE_LIB" />
 			<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
 			<Add option="-D_WIN32_WINNT=0x0501" />
 			<Add option="-D_WIN32" />
@@ -65,6 +66,7 @@
 			<Add directory="../../include" />
 		</Compiler>
 		<Linker>
+			<Add option="-lboost_thread$(#boost.libsuffix)" />
 			<Add option="-lboost_system$(#boost.libsuffix)" />
 			<Add option="-lVCMI_lib" />
 			<Add directory="../.." />
@@ -73,8 +75,11 @@
 		<Unit filename="AttackPossibility.h" />
 		<Unit filename="BattleAI.cpp" />
 		<Unit filename="BattleAI.h" />
+		<Unit filename="CMakeLists.txt" />
 		<Unit filename="EnemyInfo.cpp" />
 		<Unit filename="EnemyInfo.h" />
+		<Unit filename="PossibleSpellcast.cpp" />
+		<Unit filename="PossibleSpellcast.h" />
 		<Unit filename="PotentialTargets.cpp" />
 		<Unit filename="PotentialTargets.h" />
 		<Unit filename="StackWithBonuses.cpp" />

+ 286 - 144
AI/BattleAI/BattleAI.cpp

@@ -9,13 +9,57 @@
  */
 #include "StdInc.h"
 #include "BattleAI.h"
+
+#include <vstd/RNG.h>
+
 #include "StackWithBonuses.h"
 #include "EnemyInfo.h"
+#include "PossibleSpellcast.h"
+#include "../../lib/CStopWatch.h"
+#include "../../lib/CThreadHelper.h"
 #include "../../lib/spells/CSpellHandler.h"
+#include "../../lib/spells/ISpellMechanics.h"
+#include "../../lib/CStack.h"//todo: remove
 
 #define LOGL(text) print(text)
 #define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
 
+class RNGStub : public vstd::RNG
+{
+public:
+	vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override
+	{
+		return [=]()->int64_t
+		{
+			return (lower + upper)/2;
+		};
+	}
+
+	vstd::TRand getDoubleRange(double lower, double upper) override
+	{
+		return [=]()->double
+		{
+			return (lower + upper)/2;
+		};
+	}
+};
+
+enum class SpellTypes
+{
+	ADVENTURE, BATTLE, OTHER
+};
+
+SpellTypes spellType(const CSpell * spell)
+{
+	if(!spell->isCombatSpell() || spell->isCreatureAbility())
+		return SpellTypes::OTHER;
+
+	if(spell->isOffensiveSpell() || spell->hasEffects() || spell->hasBattleEffects())
+		return SpellTypes::BATTLE;
+
+	return SpellTypes::OTHER;
+}
+
 CBattleAI::CBattleAI()
 	: side(-1), wasWaitingForRealize(false), wasUnlockingGs(false)
 {
@@ -70,31 +114,38 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 			//spellcast may finish battle
 			//send special preudo-action
 			BattleAction cancel;
-			cancel.actionType = Battle::CANCEL;
+			cancel.actionType = EActionType::CANCEL;
 			return cancel;
 		}
 
 		if(auto action = considerFleeingOrSurrendering())
 			return *action;
-		PotentialTargets targets(stack);
+		//best action is from effective owner point if view, we are effective owner as we received "activeStack"
+
+		HypotheticBattle hb(getCbc());
+
+		PotentialTargets targets(stack, &hb);
 		if(targets.possibleAttacks.size())
 		{
 			auto hlp = targets.bestAction();
 			if(hlp.attack.shooting)
-				return BattleAction::makeShotAttack(stack, hlp.enemy);
+				return BattleAction::makeShotAttack(stack, hlp.attack.defender);
 			else
-				return BattleAction::makeMeleeAttack(stack, hlp.enemy, hlp.tile);
+				return BattleAction::makeMeleeAttack(stack, hlp.attack.defender->getPosition(), hlp.tile);
 		}
 		else
 		{
 			if(stack->waited())
 			{
 				//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
-				auto dists = getCbc()->battleGetDistances(stack);
-				const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists)));
-				if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
+				auto dists = getCbc()->battleGetDistances(stack, stack->getPosition());
+				if(!targets.unreachableEnemies.empty())
 				{
-					return goTowards(stack, ei.s->position);
+					const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists)));
+					if(distToNearestNeighbour(ei.s->getPosition(), dists) < GameConstants::BFIELD_SIZE)
+					{
+						return goTowards(stack, ei.s->getPosition());
+					}
 				}
 			}
 			else
@@ -116,9 +167,15 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 
 BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
 {
-	assert(destination.isValid());
-	auto avHexes = cb->battleGetAvailableHexes(stack, false);
+	if(!destination.isValid())
+	{
+		logAi->error("CBattleAI::goTowards: invalid destination");
+		return BattleAction::makeDefend(stack);
+	}
+
 	auto reachability = cb->getReachability(stack);
+	auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
+
 	if(vstd::contains(avHexes, destination))
 		return BattleAction::makeMove(stack, destination);
 	auto destNeighbours = destination.neighbouringTiles();
@@ -156,7 +213,12 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
 		BattleHex currentDest = bestNeighbor;
 		while(1)
 		{
-			assert(currentDest.isValid());
+			if(!currentDest.isValid())
+			{
+				logAi->error("CBattleAI::goTowards: internal error");
+				return BattleAction::makeDefend(stack);
+			}
+
 			if(vstd::contains(avHexes, currentDest))
 				return BattleAction::makeMove(stack, currentDest);
 			currentDest = reachability.predecessors[currentDest];
@@ -166,22 +228,7 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
 
 BattleAction CBattleAI::useCatapult(const CStack * stack)
 {
-	throw std::runtime_error("The method or operation is not implemented.");
-}
-
-
-enum SpellTypes
-{
-	OFFENSIVE_SPELL, TIMED_EFFECT, OTHER
-};
-
-SpellTypes spellType(const CSpell *spell)
-{
-	if (spell->isOffensiveSpell())
-		return OFFENSIVE_SPELL;
-	if (spell->hasEffects())
-		return TIMED_EFFECT;
-	return OTHER;
+	throw std::runtime_error("CBattleAI::useCatapult is not implemented.");
 }
 
 void CBattleAI::attemptCastingSpell()
@@ -190,7 +237,7 @@ void CBattleAI::attemptCastingSpell()
 	if(!hero)
 		return;
 
-	if(cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING) != ESpellCastProblem::OK)
+	if(cb->battleCanCastSpell(hero, spells::Mode::HERO) != ESpellCastProblem::OK)
 		return;
 
 	LOGL("Casting spells sounds like fun. Let's see...");
@@ -198,21 +245,28 @@ void CBattleAI::attemptCastingSpell()
 	std::vector<const CSpell*> possibleSpells;
 	vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this, hero] (const CSpell *s) -> bool
 	{
-		return s->canBeCast(getCbc().get(), ECastingMode::HERO_CASTING, hero) == ESpellCastProblem::OK;
+		return s->canBeCast(getCbc().get(), spells::Mode::HERO, hero);
 	});
 	LOGFL("I can cast %d spells.", possibleSpells.size());
 
 	vstd::erase_if(possibleSpells, [](const CSpell *s)
-	{return spellType(s) == OTHER; });
-	LOGFL("I know about workings of %d of them.", possibleSpells.size());
+	{
+		return spellType(s) != SpellTypes::BATTLE;
+	});
+
+	LOGFL("I know how %d of them works.", possibleSpells.size());
 
 	//Get possible spell-target pairs
 	std::vector<PossibleSpellcast> possibleCasts;
 	for(auto spell : possibleSpells)
 	{
-		for(auto hex : getTargetsToConsider(spell, hero))
+		spells::BattleCast temp(getCbc().get(), hero, spells::Mode::HERO, spell);
+
+		for(auto & target : temp.findPotentialTargets())
 		{
-			PossibleSpellcast ps = {spell, hex, 0};
+			PossibleSpellcast ps;
+			ps.dest = target;
+			ps.spell = spell;
 			possibleCasts.push_back(ps);
 		}
 	}
@@ -220,141 +274,229 @@ void CBattleAI::attemptCastingSpell()
 	if(possibleCasts.empty())
 		return;
 
-	std::map<const CStack*, int> valueOfStack;
-	for(auto stack : cb->battleGetStacks())
+	using ValueMap = PossibleSpellcast::ValueMap;
+
+	auto evaluateQueue = [&](ValueMap & values, const std::vector<battle::Units> & queue, HypotheticBattle * state, size_t minTurnSpan, bool * enemyHadTurnOut) -> bool
+	{
+		bool firstRound = true;
+		bool enemyHadTurn = false;
+		size_t ourTurnSpan = 0;
+
+		bool stop = false;
+
+		for(auto & round : queue)
+		{
+			if(!firstRound)
+				state->nextRound(0);//todo: set actual value?
+			for(auto unit : round)
+			{
+				if(!vstd::contains(values, unit->unitId()))
+					values[unit->unitId()] = 0;
+
+				if(!unit->alive())
+					continue;
+
+				if(state->battleGetOwner(unit) != playerID)
+				{
+					enemyHadTurn = true;
+
+					if(!firstRound || state->battleCastSpells(unit->unitSide()) == 0)
+					{
+						//enemy could counter our spell at this point
+						//anyway, we do not know what enemy will do
+						//just stop evaluation
+						stop = true;
+						break;
+					}
+				}
+				else if(!enemyHadTurn)
+				{
+					ourTurnSpan++;
+				}
+
+				state->nextTurn(unit->unitId());
+
+				PotentialTargets pt(unit, state);
+
+				if(!pt.possibleAttacks.empty())
+				{
+					AttackPossibility ap = pt.bestAction();
+
+					auto swb = state->getForUpdate(unit->unitId());
+					*swb = *ap.attackerState;
+
+					if(ap.damageDealt > 0)
+						swb->removeUnitBonus(Bonus::UntilAttack);
+					if(ap.damageReceived > 0)
+						swb->removeUnitBonus(Bonus::UntilBeingAttacked);
+
+					for(auto affected : ap.affectedUnits)
+					{
+						swb = state->getForUpdate(affected->unitId());
+						*swb = *affected;
+
+						if(ap.damageDealt > 0)
+							swb->removeUnitBonus(Bonus::UntilBeingAttacked);
+						if(ap.damageReceived > 0 && ap.attack.defender->unitId() == affected->unitId())
+							swb->removeUnitBonus(Bonus::UntilAttack);
+					}
+				}
+
+				auto bav = pt.bestActionValue();
+
+				//best action is from effective owner`s point if view, we need to convert to our point if view
+				if(state->battleGetOwner(unit) != playerID)
+					bav = -bav;
+				values[unit->unitId()] += bav;
+			}
+
+			firstRound = false;
+
+			if(stop)
+				break;
+		}
+
+		if(enemyHadTurnOut)
+			*enemyHadTurnOut = enemyHadTurn;
+
+		return ourTurnSpan > minTurnSpan;
+	};
+
+	RNGStub rngStub;
+
+	ValueMap valueOfStack;
+	ValueMap healthOfStack;
+
+	TStacks all = cb->battleGetAllStacks(false);
+
+	for(auto unit : all)
 	{
-		PotentialTargets pt(stack);
-		valueOfStack[stack] = pt.bestActionValue();
+		healthOfStack[unit->unitId()] = unit->getAvailableHealth();
+		valueOfStack[unit->unitId()] = 0;
 	}
 
-	auto evaluateSpellcast = [&] (const PossibleSpellcast &ps) -> int
+	auto amount = all.size();
+
+	std::vector<battle::Units> turnOrder;
+
+	cb->battleGetTurnOrder(turnOrder, amount, 2); //no more than 1 turn after current, each unit at least once
+
 	{
-		const int skillLevel = hero->getSpellSchoolLevel(ps.spell);
-		const int spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
-		switch(spellType(ps.spell))
-		{
-		case OFFENSIVE_SPELL:
+		bool enemyHadTurn = false;
+
+		HypotheticBattle state(cb);
+		evaluateQueue(valueOfStack, turnOrder, &state, 0, &enemyHadTurn);
+
+		if(!enemyHadTurn)
 		{
-			int damageDealt = 0, damageReceived = 0;
-			auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
-			if(stacksSuffering.empty())
-				return -1;
-			for(auto stack : stacksSuffering)
+			auto battleIsFinishedOpt = state.battleIsFinished();
+
+			if(battleIsFinishedOpt)
 			{
-				const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower);
-				if(stack->owner == playerID)
-					damageReceived += dmg;
-				else
-					damageDealt += dmg;
+				print("No need to cast a spell. Battle will finish soon.");
+				return;
 			}
-			const int damageDiff = damageDealt - damageReceived * 10;
-			LOGFL("Casting %s on hex %d would deal { %d %d } damage points among %d stacks.",
-				  ps.spell->name % ps.dest % damageDealt % damageReceived % stacksSuffering.size());
-			//TODO tactic effect too
-			return damageDiff;
 		}
-		case TIMED_EFFECT:
+	}
+
+	auto evaluateSpellcast = [&] (PossibleSpellcast * ps)
+	{
+		int64_t totalGain = 0;
+
+		HypotheticBattle state(cb);
+
+		spells::BattleCast cast(&state, hero, spells::Mode::HERO, ps->spell);
+		cast.target = ps->dest;
+		cast.cast(&state, rngStub);
+		ValueMap newHealthOfStack;
+		ValueMap newValueOfStack;
+
+		size_t ourUnits = 0;
+
+		for(auto unit : all)
+		{
+			newHealthOfStack[unit->unitId()] = unit->getAvailableHealth();
+			newValueOfStack[unit->unitId()] = 0;
+
+			if(state.battleGetOwner(unit) == playerID && unit->alive() && unit->willMove())
+				ourUnits++;
+		}
+
+		size_t minTurnSpan = ourUnits/3; //todo: tweak this
+
+		std::vector<battle::Units> newTurnOrder;
+		state.battleGetTurnOrder(newTurnOrder, amount, 2);
+
+		if(evaluateQueue(newValueOfStack, newTurnOrder, &state, minTurnSpan, nullptr))
 		{
-			auto stacksAffected = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
-			if(stacksAffected.empty())
-				return -1;
-			int totalGain = 0;
-			for(const CStack * sta : stacksAffected)
+			for(auto unit : all)
 			{
-				StackWithBonuses swb;
-				swb.stack = sta;
-				//todo: handle effect actualization in HypotheticChangesToBattleState
-				ps.spell->getEffects(swb.bonusesToAdd, skillLevel, false, hero->getEnchantPower(ps.spell));
-				ps.spell->getEffects(swb.bonusesToAdd, skillLevel, true, hero->getEnchantPower(ps.spell));
-				HypotheticChangesToBattleState state;
-				state.bonusesOfStacks[swb.stack] = &swb;
-				PotentialTargets pt(swb.stack, state);
-				auto newValue = pt.bestActionValue();
-				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 % sta->nodeName() % (gain) % (oldValue) % (newValue));
-				totalGain += gain;
+				auto newValue = getValOr(newValueOfStack, unit->unitId(), 0);
+				auto oldValue = getValOr(valueOfStack, unit->unitId(), 0);
+
+				auto healthDiff = newHealthOfStack[unit->unitId()] - healthOfStack[unit->unitId()];
+
+				if(unit->unitOwner() != playerID)
+					healthDiff = -healthDiff;
+
+				auto gain = newValue - oldValue + healthDiff;
+
+				if(gain != 0)
+					totalGain += gain;
 			}
 
-			LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain));
-			return totalGain;
+			ps->value = totalGain;
 		}
-		default:
-			assert(0);
-			return 0;
+		else
+		{
+			ps->value = -1;
 		}
 	};
 
+	std::vector<std::function<void()>> tasks;
+
 	for(PossibleSpellcast & psc : possibleCasts)
-		psc.value = evaluateSpellcast(psc);
-	auto pscValue = [] (const PossibleSpellcast &ps) -> int
+	{
+		tasks.push_back(std::bind(evaluateSpellcast, &psc));
+
+	}
+
+	uint32_t threadCount = boost::thread::hardware_concurrency();
+
+	if(threadCount == 0)
+	{
+		logGlobal->warn("No information of CPU cores available");
+		threadCount = 1;
+	}
+
+	CStopWatch timer;
+
+	CThreadHelper threadHelper(&tasks, threadCount);
+	threadHelper.run();
+
+	LOGFL("Evaluation took %d ms", timer.getDiff());
+
+	auto pscValue = [] (const PossibleSpellcast &ps) -> int64_t
 	{
 		return ps.value;
 	};
 	auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
-	LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name);
-	BattleAction spellcast;
-	spellcast.actionType = Battle::HERO_SPELL;
-	spellcast.additionalInfo = castToPerform.spell->id;
-	spellcast.destinationTile = castToPerform.dest;
-	spellcast.side = side;
-	spellcast.stackNumber = (!side) ? -1 : -2;
-	cb->battleMakeAction(&spellcast);
-}
 
-std::vector<BattleHex> CBattleAI::getTargetsToConsider(const CSpell * spell, const ISpellCaster * caster) const
-{
-	const CSpell::TargetInfo targetInfo(spell, caster->getSpellSchoolLevel(spell));
-	std::vector<BattleHex> ret;
-	if(targetInfo.massive || targetInfo.type == CSpell::NO_TARGET)
+	if(castToPerform.value > 0)
 	{
-		ret.push_back(BattleHex());
+		LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->name % castToPerform.value);
+		BattleAction spellcast;
+		spellcast.actionType = EActionType::HERO_SPELL;
+		spellcast.actionSubtype = castToPerform.spell->id;
+		spellcast.setTarget(castToPerform.dest);
+		spellcast.side = side;
+		spellcast.stackNumber = (!side) ? -1 : -2;
+		cb->battleMakeAction(&spellcast);
 	}
 	else
 	{
-		switch(targetInfo.type)
-		{
-		case CSpell::CREATURE:
-		{
-			for(const CStack * stack : getCbc()->battleAliveStacks())
-			{
-				bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
-				bool casterStack = stack->owner == caster->getOwner();
-
-				if(!immune)
-					switch (spell->positiveness)
-					{
-					case CSpell::POSITIVE:
-						if(casterStack || targetInfo.smart)
-							ret.push_back(stack->position);
-						break;
-					case CSpell::NEUTRAL:
-						ret.push_back(stack->position);
-						break;
-					case CSpell::NEGATIVE:
-						if(!casterStack || targetInfo.smart)
-							ret.push_back(stack->position);
-						break;
-					}
-			}
-		}
-			break;
-		case CSpell::LOCATION:
-		{
-			for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
-				if(BattleHex(i).isAvailable())
-					ret.push_back(i);
-		}
-			break;
-
-		default:
-			break;
-		}
+		LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->name % castToPerform.value);
 	}
-	return ret;
 }
 
 int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances &dists, BattleHex *chosenHex)
@@ -374,18 +516,18 @@ int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDi
 
 void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side)
 {
-	print("battleStart called");
+	LOG_TRACE(logAi);
 	side = Side;
 }
 
 bool CBattleAI::isCloser(const EnemyInfo &ei1, const EnemyInfo &ei2, const ReachabilityInfo::TDistances &dists)
 {
-	return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists);
+	return distToNearestNeighbour(ei1.s->getPosition(), dists) < distToNearestNeighbour(ei2.s->getPosition(), dists);
 }
 
 void CBattleAI::print(const std::string &text) const
 {
-	logAi->trace("CBattleAI [%p]: %s", this, text);
+	logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text);
 }
 
 boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()

+ 1 - 13
AI/BattleAI/BattleAI.h

@@ -45,13 +45,6 @@ struct CurrentOffensivePotential
 };
 */ // These lines may be usefull but they are't used in the code.
 
-struct PossibleSpellcast
-{
-	const CSpell *spell;
-	BattleHex dest;
-	si32 value;
-};
-
 class CBattleAI : public CBattleGameInterface
 {
 	int side;
@@ -72,7 +65,6 @@ public:
 
 	boost::optional<BattleAction> considerFleeingOrSurrendering();
 
-	std::vector<BattleHex> getTargetsToConsider(const CSpell *spell, const ISpellCaster * caster) const;
 	static int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr);
 	static bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists);
 
@@ -82,7 +74,7 @@ public:
 	//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
 	//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
-	//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
+	//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog) override; //called when stack receives damage (after battleAttack())
 	//void battleEnd(const BattleResult *br) override;
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
 	//void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
@@ -92,9 +84,5 @@ public:
 	//void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
 	//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
 	//void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
-	//void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp
-	//void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned
-	//void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override; //called when a certain set  of obstacles is removed from batlefield; IDs of them are given
 	//void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
-	//void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield
 };

+ 2 - 0
AI/BattleAI/CMakeLists.txt

@@ -8,6 +8,7 @@ set(battleAI_SRCS
 		common.cpp
 		EnemyInfo.cpp
 		main.cpp
+		PossibleSpellcast.cpp
 		PotentialTargets.cpp
 		StackWithBonuses.cpp
 		ThreatMap.cpp
@@ -21,6 +22,7 @@ set(battleAI_HEADERS
 		common.h
 		EnemyInfo.h
 		PotentialTargets.h
+		PossibleSpellcast.h
 		StackWithBonuses.h
 		ThreatMap.h
 )

+ 5 - 7
AI/BattleAI/EnemyInfo.cpp

@@ -9,13 +9,11 @@
  */
 #include "StdInc.h"
 #include "EnemyInfo.h"
-#include "../../lib/CRandomGenerator.h"
-#include "../../CCallback.h"
-#include "common.h"
 
-void EnemyInfo::calcDmg(const CStack * ourStack)
+#include "../../lib/battle/Unit.h"
+
+bool EnemyInfo::operator==(const EnemyInfo & ei) const
 {
-	TDmgRange retal, dmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal);
-	adi = (dmg.first + dmg.second) / 2;
-	adr = (retal.first + retal.second) / 2;
+	return s->unitId() == ei.s->unitId();
 }
+

+ 7 - 12
AI/BattleAI/EnemyInfo.h

@@ -9,21 +9,16 @@
  */
 #pragma once
 
-#include "../../lib/battle/BattleHex.h"
-
-class CStack;
+namespace battle
+{
+	class Unit;
+}
 
 class EnemyInfo
 {
 public:
-	const CStack * s;
-	int adi, adr;
-	std::vector<BattleHex> attackFrom; //for melee fight
-	EnemyInfo(const CStack * _s) : s(_s)
+	const battle::Unit * s;
+	EnemyInfo(const battle::Unit * _s) : s(_s)
 	{}
-	void calcDmg(const CStack * ourStack);
-	bool operator==(const EnemyInfo& ei) const
-	{
-		return s == ei.s;
-	}
+	bool operator==(const EnemyInfo & ei) const;
 };

+ 21 - 0
AI/BattleAI/PossibleSpellcast.cpp

@@ -0,0 +1,21 @@
+/*
+ * PossibleSpellcast.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+
+#include "PossibleSpellcast.h"
+
+PossibleSpellcast::PossibleSpellcast()
+	: spell(nullptr),
+	dest(),
+	value(0)
+{
+}
+
+PossibleSpellcast::~PossibleSpellcast() = default;

+ 29 - 0
AI/BattleAI/PossibleSpellcast.h

@@ -0,0 +1,29 @@
+/*
+ * PossibleSpellcast.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "../../lib/battle/Destination.h"
+#include "../../lib/spells/Magic.h"
+
+class CSpell;
+
+class PossibleSpellcast
+{
+public:
+	using ValueMap = std::map<uint32_t, int64_t>;
+
+	const CSpell * spell;
+	spells::Target dest;
+	int64_t value;
+
+	PossibleSpellcast();
+	virtual ~PossibleSpellcast();
+};

+ 46 - 21
AI/BattleAI/PotentialTargets.cpp

@@ -9,51 +9,76 @@
  */
 #include "StdInc.h"
 #include "PotentialTargets.h"
+#include "../../lib/CStack.h"//todo: remove
 
-PotentialTargets::PotentialTargets(const CStack * attacker, const HypotheticChangesToBattleState & state)
+PotentialTargets::PotentialTargets(const battle::Unit * attacker, const HypotheticBattle * state)
 {
-	auto dists = getCbc()->battleGetDistances(attacker);
-	auto avHexes = getCbc()->battleGetAvailableHexes(attacker, false);
+	auto attIter = state->stackStates.find(attacker->unitId());
+	const battle::Unit * attackerInfo = (attIter == state->stackStates.end()) ? attacker : attIter->second.get();
 
-	for(const CStack *enemy : getCbc()->battleGetStacks())
+	auto reachability = state->getReachability(attackerInfo);
+	auto avHexes = state->battleGetAvailableHexes(reachability, attackerInfo);
+
+	//FIXME: this should part of battleGetAvailableHexes
+	bool forceTarget = false;
+	const battle::Unit * forcedTarget = nullptr;
+	BattleHex forcedHex;
+
+	if(attackerInfo->hasBonusOfType(Bonus::ATTACKS_NEAREST_CREATURE))
+	{
+		forceTarget = true;
+		auto nearest = state->getNearestStack(attackerInfo);
+
+		if(nearest.first != nullptr)
+		{
+			forcedTarget = nearest.first;
+			forcedHex = nearest.second;
+		}
+	}
+
+	auto aliveUnits = state->battleGetUnitsIf([=](const battle::Unit * unit)
+	{
+		return unit->isValidTarget() && unit->unitId() != attackerInfo->unitId();
+	});
+
+	for(auto defender : aliveUnits)
 	{
-		//Consider only stacks of different owner
-		if(enemy->side == attacker->side)
+		if(!forceTarget && !state->battleMatchOwner(attackerInfo, defender))
 			continue;
 
 		auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
 		{
-			auto bai = BattleAttackInfo(attacker, enemy, shooting);
-			bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker);
-			bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender);
+			auto bai = BattleAttackInfo(attackerInfo, defender, shooting);
 
-			if(hex.isValid())
-			{
-				assert(dists[hex] <= attacker->Speed());
-				bai.chargedFields = dists[hex];
-			}
+			if(hex.isValid() && !shooting)
+				bai.chargedFields = reachability.distances[hex];
 
-			return AttackPossibility::evaluate(bai, state, hex);
+			return AttackPossibility::evaluate(bai, hex);
 		};
 
-		if(getCbc()->battleCanShoot(attacker, enemy->position))
+		if(forceTarget)
+		{
+			if(forcedTarget && defender->unitId() == forcedTarget->unitId())
+				possibleAttacks.push_back(GenerateAttackInfo(false, forcedHex));
+			else
+				unreachableEnemies.push_back(defender);
+		}
+		else if(state->battleCanShoot(attackerInfo, defender->getPosition()))
 		{
 			possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID));
 		}
 		else
 		{
 			for(BattleHex hex : avHexes)
-				if(CStack::isMeleeAttackPossible(attacker, enemy, hex))
+				if(CStack::isMeleeAttackPossible(attackerInfo, defender, hex))
 					possibleAttacks.push_back(GenerateAttackInfo(false, hex));
 
-			if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; }))
-				unreachableEnemies.push_back(enemy);
+			if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility & pa) { return pa.attack.defender->unitId() == defender->unitId(); }))
+				unreachableEnemies.push_back(defender);
 		}
 	}
 }
 
-
-
 int PotentialTargets::bestActionValue() const
 {
 	if(possibleAttacks.empty())

+ 2 - 4
AI/BattleAI/PotentialTargets.h

@@ -14,12 +14,10 @@ class PotentialTargets
 {
 public:
 	std::vector<AttackPossibility> possibleAttacks;
-	std::vector<const CStack *> unreachableEnemies;
-
-	//std::function<AttackPossibility(bool,BattleHex)>  GenerateAttackInfo; //args: shooting, destHex
+	std::vector<const battle::Unit *> unreachableEnemies;
 
 	PotentialTargets(){};
-	PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState());
+	PotentialTargets(const battle::Unit * attacker, const HypotheticBattle * state);
 
 	AttackPossibility bestAction() const;
 	int bestActionValue() const;

+ 368 - 6
AI/BattleAI/StackWithBonuses.cpp

@@ -9,21 +9,383 @@
  */
 #include "StdInc.h"
 #include "StackWithBonuses.h"
+#include "../../lib/NetPacksBase.h"
 #include "../../lib/CStack.h"
 
+void actualizeEffect(TBonusListPtr target, const Bonus & ef)
+{
+	for(auto bonus : *target) //TODO: optimize
+	{
+		if(bonus->source == Bonus::SPELL_EFFECT && bonus->type == ef.type && bonus->subtype == ef.subtype)
+		{
+			bonus->turnsRemain = std::max(bonus->turnsRemain, ef.turnsRemain);
+		}
+	}
+}
 
-const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector &selector, const CSelector &limit,
-							const CBonusSystemNode * root, const std::string & cachingStr) const
+StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack)
+	: battle::CUnitState(),
+	origBearer(Stack),
+	owner(Owner),
+	type(Stack->unitType()),
+	baseAmount(Stack->unitBaseAmount()),
+	id(Stack->unitId()),
+	side(Stack->unitSide()),
+	player(Stack->unitOwner()),
+	slot(Stack->unitSlot())
+{
+	localInit(Owner);
+
+	battle::CUnitState::operator=(*Stack);
+}
+
+StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info)
+	: battle::CUnitState(),
+	origBearer(nullptr),
+	owner(Owner),
+	baseAmount(info.count),
+	id(info.id),
+	side(info.side),
+	slot(SlotID::SUMMONED_SLOT_PLACEHOLDER)
+{
+	type = info.type.toCreature();
+	origBearer = type;
+
+	player = Owner->getSidePlayer(side);
+
+	position = info.position;
+	summoned = info.summoned;
+	localInit(Owner);
+}
+
+StackWithBonuses::~StackWithBonuses() = default;
+
+StackWithBonuses & StackWithBonuses::operator=(const battle::CUnitState & other)
+{
+	battle::CUnitState::operator=(other);
+	return *this;
+}
+
+const CCreature * StackWithBonuses::unitType() const
+{
+	return type;
+}
+
+int32_t StackWithBonuses::unitBaseAmount() const
+{
+	return baseAmount;
+}
+
+uint32_t StackWithBonuses::unitId() const
+{
+	return id;
+}
+
+ui8 StackWithBonuses::unitSide() const
+{
+	return side;
+}
+
+PlayerColor StackWithBonuses::unitOwner() const
+{
+	return player;
+}
+
+SlotID StackWithBonuses::unitSlot() const
+{
+	return slot;
+}
+
+const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
+	const CBonusSystemNode * root, const std::string & cachingStr) const
 {
 	TBonusListPtr ret = std::make_shared<BonusList>();
-	const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr);
-	range::copy(*originalList, std::back_inserter(*ret));
-	for(auto &bonus : bonusesToAdd)
+	const TBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr);
+
+	vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
+	{
+		return !vstd::contains(bonusesToRemove, b);
+	});
+
+
+	for(const Bonus & bonus : bonusesToUpdate)
+	{
+		if(selector(&bonus) && (!limit || !limit(&bonus)))
+		{
+			if(ret->getFirst(Selector::source(Bonus::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
+			{
+				actualizeEffect(ret, bonus);
+			}
+			else
+			{
+				auto b = std::make_shared<Bonus>(bonus);
+				ret->push_back(b);
+			}
+		}
+	}
+
+	for(auto & bonus : bonusesToAdd)
 	{
 		auto b = std::make_shared<Bonus>(bonus);
-		if(selector(b.get())  &&  (!limit || !limit(b.get())))
+		if(selector(b.get()) && (!limit || !limit(b.get())))
 			ret->push_back(b);
 	}
 	//TODO limiters?
 	return ret;
 }
+
+int64_t StackWithBonuses::getTreeVersion() const
+{
+	return owner->getTreeVersion();
+}
+
+void StackWithBonuses::addUnitBonus(const std::vector<Bonus> & bonus)
+{
+	vstd::concatenate(bonusesToAdd, bonus);
+}
+
+void StackWithBonuses::updateUnitBonus(const std::vector<Bonus> & bonus)
+{
+	//TODO: optimize, actualize to last value
+
+	vstd::concatenate(bonusesToUpdate, bonus);
+}
+
+void StackWithBonuses::removeUnitBonus(const std::vector<Bonus> & bonus)
+{
+	for(auto & one : bonus)
+	{
+		CSelector selector([&one](const Bonus * b) -> bool
+		{
+			//compare everything but turnsRemain, limiter and propagator
+			return one.duration == b->duration
+				&& one.type == b->type
+				&& one.subtype == b->subtype
+				&& one.source == b->source
+				&& one.val == b->val
+				&& one.sid == b->sid
+				&& one.valType == b->valType
+				&& one.additionalInfo == b->additionalInfo
+				&& one.effectRange == b->effectRange
+				&& one.description == b->description;
+		});
+
+		removeUnitBonus(selector);
+	}
+}
+
+void StackWithBonuses::removeUnitBonus(const CSelector & selector)
+{
+	TBonusListPtr toRemove = origBearer->getBonuses(selector);
+
+	for(auto b : *toRemove)
+		bonusesToRemove.insert(b);
+
+	vstd::erase_if(bonusesToAdd, [&](const Bonus & b){return selector(&b);});
+	vstd::erase_if(bonusesToUpdate, [&](const Bonus & b){return selector(&b);});
+}
+
+void StackWithBonuses::spendMana(const spells::PacketSender * server, const int spellCost) const
+{
+	//TODO: evaluate cast use
+}
+
+HypotheticBattle::HypotheticBattle(Subject realBattle)
+	: BattleProxy(realBattle),
+	bonusTreeVersion(1)
+{
+	auto activeUnit = realBattle->battleActiveUnit();
+	activeUnitId = activeUnit ? activeUnit->unitId() : -1;
+
+	nextId = 0xF0000000;
+}
+
+bool HypotheticBattle::unitHasAmmoCart(const battle::Unit * unit) const
+{
+	//FIXME: check ammocart alive state here
+	return false;
+}
+
+PlayerColor HypotheticBattle::unitEffectiveOwner(const battle::Unit * unit) const
+{
+	return battleGetOwner(unit);
+}
+
+std::shared_ptr<StackWithBonuses> HypotheticBattle::getForUpdate(uint32_t id)
+{
+	auto iter = stackStates.find(id);
+
+	if(iter == stackStates.end())
+	{
+		const CStack * s = subject->battleGetStackByID(id, false);
+
+		auto ret = std::make_shared<StackWithBonuses>(this, s);
+		stackStates[id] = ret;
+		return ret;
+	}
+	else
+	{
+		return iter->second;
+	}
+}
+
+battle::Units HypotheticBattle::getUnitsIf(battle::UnitFilter predicate) const
+{
+	battle::Units proxyed = BattleProxy::getUnitsIf(predicate);
+
+	battle::Units ret;
+	ret.reserve(proxyed.size());
+
+	for(auto unit : proxyed)
+	{
+		//unit was not changed, trust proxyed data
+		if(stackStates.find(unit->unitId()) == stackStates.end())
+			ret.push_back(unit);
+	}
+
+	for(auto id_unit : stackStates)
+	{
+		if(predicate(id_unit.second.get()))
+			ret.push_back(id_unit.second.get());
+	}
+
+	return ret;
+}
+
+int32_t HypotheticBattle::getActiveStackID() const
+{
+	return activeUnitId;
+}
+
+void HypotheticBattle::nextRound(int32_t roundNr)
+{
+	//TODO:HypotheticBattle::nextRound
+
+	for(auto unit : battleAliveUnits())
+	{
+		auto forUpdate = getForUpdate(unit->unitId());
+		//TODO: update Bonus::NTurns effects
+		forUpdate->afterNewRound();
+	}
+}
+
+void HypotheticBattle::nextTurn(uint32_t unitId)
+{
+	activeUnitId = unitId;
+	auto unit = getForUpdate(unitId);
+
+	unit->removeUnitBonus(Bonus::UntilGetsTurn);
+
+	unit->afterGetsTurn();
+}
+
+void HypotheticBattle::addUnit(uint32_t id, const JsonNode & data)
+{
+	battle::UnitInfo info;
+	info.load(id, data);
+	std::shared_ptr<StackWithBonuses> newUnit = std::make_shared<StackWithBonuses>(this, info);
+	stackStates[newUnit->unitId()] = newUnit;
+}
+
+void HypotheticBattle::moveUnit(uint32_t id, BattleHex destination)
+{
+	std::shared_ptr<StackWithBonuses> changed = getForUpdate(id);
+	changed->position = destination;
+}
+
+void HypotheticBattle::setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta)
+{
+	std::shared_ptr<StackWithBonuses> changed = getForUpdate(id);
+
+	changed->load(data);
+
+	if(healthDelta < 0)
+	{
+		changed->removeUnitBonus(Bonus::UntilBeingAttacked);
+	}
+}
+
+void HypotheticBattle::removeUnit(uint32_t id)
+{
+	std::set<uint32_t> ids;
+	ids.insert(id);
+
+	while(!ids.empty())
+	{
+		auto toRemoveId = *ids.begin();
+
+		auto toRemove = getForUpdate(toRemoveId);
+
+		if(!toRemove->ghost)
+		{
+			toRemove->onRemoved();
+
+			//TODO: emulate detachFromAll() somehow
+
+			//stack may be removed instantly (not being killed first)
+			//handle clone remove also here
+			if(toRemove->cloneID >= 0)
+			{
+				ids.insert(toRemove->cloneID);
+				toRemove->cloneID = -1;
+			}
+
+			//TODO: cleanup remaining clone links if any
+//			for(auto s : stacks)
+//			{
+//				if(s->cloneID == toRemoveId)
+//					s->cloneID = -1;
+//			}
+		}
+
+		ids.erase(toRemoveId);
+	}
+}
+
+void HypotheticBattle::addUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
+{
+	getForUpdate(id)->addUnitBonus(bonus);
+	bonusTreeVersion++;
+}
+
+void HypotheticBattle::updateUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
+{
+	getForUpdate(id)->updateUnitBonus(bonus);
+	bonusTreeVersion++;
+}
+
+void HypotheticBattle::removeUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
+{
+	getForUpdate(id)->removeUnitBonus(bonus);
+	bonusTreeVersion++;
+}
+
+void HypotheticBattle::setWallState(int partOfWall, si8 state)
+{
+	//TODO:HypotheticBattle::setWallState
+}
+
+void HypotheticBattle::addObstacle(const ObstacleChanges & changes)
+{
+	//TODO:HypotheticBattle::addObstacle
+}
+
+void HypotheticBattle::removeObstacle(uint32_t id)
+{
+	//TODO:HypotheticBattle::removeObstacle
+}
+
+uint32_t HypotheticBattle::nextUnitId() const
+{
+	return nextId++;
+}
+
+int64_t HypotheticBattle::getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const
+{
+	return (damage.first + damage.second) / 2;
+}
+
+int64_t HypotheticBattle::getTreeVersion() const
+{
+	return getBattleNode()->getTreeVersion() + bonusTreeVersion;
+}

+ 95 - 5
AI/BattleAI/StackWithBonuses.h

@@ -9,15 +9,105 @@
  */
 #pragma once
 #include "../../lib/HeroBonus.h"
+#include "../../lib/battle/BattleProxy.h"
+#include "../../lib/battle/CUnitState.h"
 
+class HypotheticBattle;
 class CStack;
 
-class StackWithBonuses : public IBonusBearer
+class StackWithBonuses : public battle::CUnitState, public virtual IBonusBearer
 {
 public:
-	const CStack *stack;
-	mutable std::vector<Bonus> bonusesToAdd;
 
-	virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit,
-						  const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
+	std::vector<Bonus> bonusesToAdd;
+	std::vector<Bonus> bonusesToUpdate;
+	std::set<std::shared_ptr<Bonus>> bonusesToRemove;
+
+	StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack);
+
+	StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info);
+
+	virtual ~StackWithBonuses();
+
+	StackWithBonuses & operator= (const battle::CUnitState & other);
+
+	///IUnitInfo
+	const CCreature * unitType() const override;
+
+	int32_t unitBaseAmount() const override;
+
+	uint32_t unitId() const override;
+	ui8 unitSide() const override;
+	PlayerColor unitOwner() const override;
+	SlotID unitSlot() const override;
+
+	///IBonusBearer
+	const TBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
+		const CBonusSystemNode * root = nullptr, const std::string & cachingStr = "") const override;
+
+	int64_t getTreeVersion() const override;
+
+	void addUnitBonus(const std::vector<Bonus> & bonus);
+	void updateUnitBonus(const std::vector<Bonus> & bonus);
+	void removeUnitBonus(const std::vector<Bonus> & bonus);
+
+	void removeUnitBonus(const CSelector & selector);
+
+	void spendMana(const spells::PacketSender * server, const int spellCost) const override;
+
+private:
+	const IBonusBearer * origBearer;
+	const HypotheticBattle * owner;
+
+	const CCreature * type;
+	ui32 baseAmount;
+	uint32_t id;
+	ui8 side;
+	PlayerColor player;
+	SlotID slot;
+};
+
+class HypotheticBattle : public BattleProxy, public battle::IUnitEnvironment
+{
+public:
+	std::map<uint32_t, std::shared_ptr<StackWithBonuses>> stackStates;
+
+	HypotheticBattle(Subject realBattle);
+
+	bool unitHasAmmoCart(const battle::Unit * unit) const override;
+	PlayerColor unitEffectiveOwner(const battle::Unit * unit) const override;
+
+	std::shared_ptr<StackWithBonuses> getForUpdate(uint32_t id);
+
+	int32_t getActiveStackID() const override;
+
+	battle::Units getUnitsIf(battle::UnitFilter predicate) const override;
+
+	void nextRound(int32_t roundNr) override;
+	void nextTurn(uint32_t unitId) override;
+
+	void addUnit(uint32_t id, const JsonNode & data) override;
+	void setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta) override;
+	void moveUnit(uint32_t id, BattleHex destination) override;
+	void removeUnit(uint32_t id) override;
+
+	void addUnitBonus(uint32_t id, const std::vector<Bonus> & bonus) override;
+	void updateUnitBonus(uint32_t id, const std::vector<Bonus> & bonus) override;
+	void removeUnitBonus(uint32_t id, const std::vector<Bonus> & bonus) override;
+
+	void setWallState(int partOfWall, si8 state) override;
+
+	void addObstacle(const ObstacleChanges & changes) override;
+	void removeObstacle(uint32_t id) override;
+
+	uint32_t nextUnitId() const override;
+
+	int64_t getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
+
+	int64_t getTreeVersion() const;
+
+private:
+	int32_t bonusTreeVersion;
+	int32_t activeUnitId;
+	mutable uint32_t nextId;
 };

+ 14 - 35
AI/StupidAI/StupidAI.cpp

@@ -53,7 +53,7 @@ struct EnemyInfo
 	{}
 	void calcDmg(const CStack * ourStack)
 	{
-		TDmgRange retal, dmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal);
+		TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, &retal);
 		adi = (dmg.first + dmg.second) / 2;
 		adr = (retal.first + retal.second) / 2;
 	}
@@ -89,7 +89,7 @@ int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& di
 
 bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists)
 {
-	return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists);
+	return distToNearestNeighbour(ei1.s->getPosition(), dists) < distToNearestNeighbour(ei2.s->getPosition(), dists);
 }
 
 }
@@ -111,17 +111,16 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 {
 	//boost::this_thread::sleep(boost::posix_time::seconds(2));
 	print("activeStack called for " + stack->nodeName());
-	auto dists = cb->battleGetDistances(stack);
+	auto dists = cb->battleGetDistances(stack, stack->getPosition());
 	std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
 
 	if(stack->type->idNumber == CreatureID::CATAPULT)
 	{
 		BattleAction attack;
 		static const std::vector<int> wallHexes = {50, 183, 182, 130, 78, 29, 12, 95};
-
-		attack.destinationTile = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault());
-		attack.actionType = Battle::CATAPULT;
-		attack.additionalInfo = 0;
+		auto seletectedHex = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault());
+		attack.aimToHex(seletectedHex);
+		attack.actionType = EActionType::CATAPULT;
 		attack.side = side;
 		attack.stackNumber = stack->ID;
 
@@ -134,13 +133,13 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 
 	for (const CStack *s : cb->battleGetStacks(CBattleCallback::ONLY_ENEMY))
 	{
-		if(cb->battleCanShoot(stack, s->position))
+		if(cb->battleCanShoot(stack, s->getPosition()))
 		{
 			enemiesShootable.push_back(s);
 		}
 		else
 		{
-			std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
+			std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack);
 
 			for (BattleHex hex : avHexes)
 			{
@@ -157,7 +156,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 				}
 			}
 
-			if(!vstd::contains(enemiesReachable, s) && s->position.isValid())
+			if(!vstd::contains(enemiesReachable, s) && s->getPosition().isValid())
 				enemiesUnreachable.push_back(s);
 		}
 	}
@@ -176,16 +175,16 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 	else if(enemiesReachable.size())
 	{
 		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));
+		return BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters));
 	}
 	else if(enemiesUnreachable.size()) //due to #955 - a buggy battle may occur when there are no enemies
 	{
 		assert(enemiesUnreachable.size());
 		const EnemyInfo &ei= *std::min_element(enemiesUnreachable.begin(), enemiesUnreachable.end(), std::bind(isCloser, _1, _2, std::ref(dists)));
 		assert(ei.s);
-		if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
+		if(distToNearestNeighbour(ei.s->getPosition(), dists) < GameConstants::BFIELD_SIZE)
 		{
-			return goTowards(stack, ei.s->position);
+			return goTowards(stack, ei.s->getPosition());
 		}
 	}
 
@@ -197,7 +196,7 @@ void CStupidAI::battleAttack(const BattleAttack *ba)
 	print("battleAttack called");
 }
 
-void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
+void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog)
 {
 	print("battleStacksAttacked called");
 }
@@ -243,31 +242,11 @@ void CStupidAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2
 	side = Side;
 }
 
-void CStupidAI::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom)
-{
-	print("battleStacksHealedRes called");
-}
-
-void CStupidAI::battleNewStackAppeared(const CStack * stack)
-{
-	print("battleNewStackAppeared called");
-}
-
-void CStupidAI::battleObstaclesRemoved(const std::set<si32> & removedObstacles)
-{
-	print("battleObstaclesRemoved called");
-}
-
 void CStupidAI::battleCatapultAttacked(const CatapultAttack & ca)
 {
 	print("battleCatapultAttacked called");
 }
 
-void CStupidAI::battleStacksRemoved(const BattleStacksRemoved & bsr)
-{
-	print("battleStacksRemoved called");
-}
-
 void CStupidAI::print(const std::string &text) const
 {
 	logAi->trace("CStupidAI  [%p]: %s", this, text);
@@ -276,8 +255,8 @@ void CStupidAI::print(const std::string &text) const
 BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex destination)
 {
 	assert(destination.isValid());
-	auto avHexes = cb->battleGetAvailableHexes(stack, false);
 	auto reachability = cb->getReachability(stack);
+	auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
 
 	if(vstd::contains(avHexes, destination))
 		return BattleAction::makeMove(stack, destination);

+ 1 - 5
AI/StupidAI/StupidAI.h

@@ -27,7 +27,7 @@ public:
 	BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
 
 	void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
-	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
+	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog) override; //called when stack receives damage (after battleAttack())
 	void battleEnd(const BattleResult *br) override;
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
 	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
@@ -37,11 +37,7 @@ public:
 	void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
 	//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
 	void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
-	void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp
-	void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned
-	void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override; //called when a certain set  of obstacles is removed from batlefield; IDs of them are given
 	void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
-	void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield
 
 	BattleAction goTowards(const CStack * stack, BattleHex hex );
 

+ 2 - 2
AI/VCAI/VCAI.cpp

@@ -1359,7 +1359,7 @@ static const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingI
 	BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP};
 static const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
 	BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR};
-static const BuildingID spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
+static const BuildingID _spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
 	BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5};
 static const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
 	BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings
@@ -1416,7 +1416,7 @@ void VCAI::buildStructure(const CGTownInstance * t)
 	}
 
 	//remaining tasks
-	if(tryBuildNextStructure(t, std::vector<BuildingID>(spells, spells + ARRAY_COUNT(spells))))
+	if(tryBuildNextStructure(t, std::vector<BuildingID>(_spells, _spells + ARRAY_COUNT(_spells))))
 		return;
 	if(tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
 		return;

+ 6 - 5
CCallback.cpp

@@ -174,7 +174,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
 
 int CBattleCallback::battleMakeAction(BattleAction* action)
 {
-	assert(action->actionType == Battle::HERO_SPELL);
+	assert(action->actionType == EActionType::HERO_SPELL);
 	MakeCustomAction mca(*action);
 	sendRequest(&mca);
 	return 0;
@@ -279,9 +279,11 @@ void CCallback::buildBoat( const IShipyard *obj )
 	sendRequest(&bb);
 }
 
-CCallback::CCallback( CGameState * GS, boost::optional<PlayerColor> Player, CClient *C )
-	:CBattleCallback(GS, Player, C)
+CCallback::CCallback(CGameState * GS, boost::optional<PlayerColor> Player, CClient * C)
+	: CBattleCallback(Player, C)
 {
+	gs = GS;
+
 	waitTillRealize = false;
 	unlockGsWhenWaiting = false;
 }
@@ -367,9 +369,8 @@ void CCallback::unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver>
 	cl->additionalBattleInts[*player] -= battleEvents;
 }
 
-CBattleCallback::CBattleCallback(CGameState *GS, boost::optional<PlayerColor> Player, CClient *C )
+CBattleCallback::CBattleCallback(boost::optional<PlayerColor> Player, CClient *C )
 {
-	gs = GS;
 	player = Player;
 	cl = C;
 }

+ 2 - 3
CCallback.h

@@ -17,7 +17,7 @@ class CGameState;
 struct CPath;
 class CGObjectInstance;
 class CArmedInstance;
-struct BattleAction;
+class BattleAction;
 class CGTownInstance;
 struct lua_State;
 class CClient;
@@ -85,10 +85,9 @@ class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback
 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, boost::optional<PlayerColor> Player, CClient *C);
+	CBattleCallback(boost::optional<PlayerColor> 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
 

BIN
Mods/vcmi/Sprites/vcmi/battleQueue/defendBig.png


BIN
Mods/vcmi/Sprites/vcmi/battleQueue/defendSmall.png


+ 8 - 0
Mods/vcmi/Sprites/vcmi/battleQueue/statesBig.json

@@ -0,0 +1,8 @@
+{
+	"basepath": "vcmi/battleQueue/",
+	"images" :
+	[
+		{ "frame" : 0, "file" : "defendBig"},
+		{ "frame" : 1, "file" : "waitBig"}
+	]
+}

+ 8 - 0
Mods/vcmi/Sprites/vcmi/battleQueue/statesSmall.json

@@ -0,0 +1,8 @@
+{
+	"basepath": "vcmi/battleQueue/",
+	"images" :
+	[
+		{ "frame" : 0, "file" : "defendSmall"},
+		{ "frame" : 1, "file" : "waitSmall"}
+	]
+}

BIN
Mods/vcmi/Sprites/vcmi/battleQueue/waitBig.png


BIN
Mods/vcmi/Sprites/vcmi/battleQueue/waitSmall.png


+ 0 - 356
client/CDefHandler.cpp

@@ -1,356 +0,0 @@
-/*
- * CDefHandler.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#include "StdInc.h"
-#include "SDL.h"
-#include "CDefHandler.h"
-
-#include "../lib/filesystem/Filesystem.h"
-#include "../lib/VCMI_Lib.h"
-#include "CBitmapHandler.h"
-#include "gui/SDL_Extensions.h"
-
-#ifdef unused
-static long long pow(long long a, int b)
-{
-	if (!b) return 1;
-	long c = a;
-	while (--b)
-		a*=c;
-	return a;
-}
-#endif
-
-CDefHandler::CDefHandler()
-{
-}
-CDefHandler::~CDefHandler()
-{
-	for (auto & elem : ourImages)
-	{
-		if (elem.bitmap)
-		{
-			SDL_FreeSurface(elem.bitmap);
-			elem.bitmap=nullptr;
-		}
-	}
-}
-
-void CDefHandler::openFromMemory(ui8 *table, const std::string & name)
-{
-	SDL_Color palette[256];
-	SDefEntry &de = * reinterpret_cast<SDefEntry *>(table);
-	ui8 *p;
-
-	defName = name;
-	DEFType = read_le_u32(&de.DEFType);
-	width = read_le_u32(&de.width);
-	height = read_le_u32(&de.height);
-	ui32 totalBlocks = read_le_u32(&de.totalBlocks);
-
-	for (ui32 it=0;it<256;it++)
-	{
-		palette[it].r = de.palette[it].R;
-		palette[it].g = de.palette[it].G;
-		palette[it].b = de.palette[it].B;
-		palette[it].a = SDL_ALPHA_OPAQUE;
-	}
-
-	// The SDefEntryBlock starts just after the SDefEntry
-	p = reinterpret_cast<ui8 *>(&de);
-	p += sizeof(de);
-
-	int totalEntries=0;
-	for (ui32 z=0; z<totalBlocks; z++)
-	{
-		SDefEntryBlock &block = * reinterpret_cast<SDefEntryBlock *>(p);
-		ui32 totalInBlock;
-
-		totalInBlock = read_le_u32(&block.totalInBlock);
-
-		for (ui32 j=SEntries.size(); j<totalEntries+totalInBlock; j++)
-			SEntries.push_back(SEntry());
-
-		p = block.data;
-		for (ui32 j=0; j<totalInBlock; j++)
-		{
-			char Buffer[13];
-			memcpy(Buffer, p, 12);
-			Buffer[12] = 0;
-			SEntries[totalEntries+j].name=Buffer;
-			p += 13;
-		}
-		for (ui32 j=0; j<totalInBlock; j++)
-		{
-			SEntries[totalEntries+j].offset = read_le_u32(p);
-			p += 4;
-		}
-		//totalEntries+=totalInBlock;
-		for(ui32 hh=0; hh<totalInBlock; ++hh)
-		{
-			SEntries[totalEntries].group = z;
-			++totalEntries;
-		}
-	}
-
-	for(auto & elem : SEntries)
-	{
-		elem.name = elem.name.substr(0, elem.name.find('.')+4);
-	}
-	//RWEntries = new ui32[height];
-	for(ui32 i=0; i < SEntries.size(); ++i)
-	{
-		Cimage nimg;
-		nimg.bitmap = getSprite(i, table, palette);
-		nimg.imName = SEntries[i].name;
-		nimg.groupNumber = SEntries[i].group;
-		ourImages.push_back(nimg);
-	}
-}
-
-SDL_Surface * CDefHandler::getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const
-{
-	SDL_Surface * ret=nullptr;
-
-	ui32 BaseOffset,
-		SpriteWidth, SpriteHeight, //format of sprite
-		TotalRowLength,			// length of read segment
-		add, FullHeight,FullWidth,
-		RowAdd,
-		defType2;
-	int LeftMargin, RightMargin, TopMargin, BottomMargin;
-
-
-	ui8 SegmentType;
-
-	BaseOffset = SEntries[SIndex].offset;
-	SSpriteDef sd = * reinterpret_cast<const SSpriteDef *>(FDef + BaseOffset);
-
-	defType2 = read_le_u32(&sd.defType2);
-	FullWidth = read_le_u32(&sd.FullWidth);
-	FullHeight = read_le_u32(&sd.FullHeight);
-	SpriteWidth = read_le_u32(&sd.SpriteWidth);
-	SpriteHeight = read_le_u32(&sd.SpriteHeight);
-	LeftMargin = read_le_u32(&sd.LeftMargin);
-	TopMargin = read_le_u32(&sd.TopMargin);
-	RightMargin = FullWidth - SpriteWidth - LeftMargin;
-	BottomMargin = FullHeight - SpriteHeight - TopMargin;
-
-	//if(LeftMargin + RightMargin < 0)
-	//	SpriteWidth += LeftMargin + RightMargin; //ugly construction... TODO: check how to do it nicer
-	if(LeftMargin<0)
-		SpriteWidth+=LeftMargin;
-	if(RightMargin<0)
-		SpriteWidth+=RightMargin;
-
-	// Note: this looks bogus because we allocate only FullWidth, not FullWidth+add
-	add = 4 - FullWidth%4;
-	if (add==4)
-		add=0;
-
-	ret = SDL_CreateRGBSurface(SDL_SWSURFACE, FullWidth, FullHeight, 8, 0, 0, 0, 0);
-
-	if(nullptr == ret)
-	{
-		logGlobal->error("%s: Unable to create surface", __FUNCTION__);
-		logGlobal->error("%dX%d", FullWidth, FullHeight);
-		logGlobal->error(SDL_GetError());
-		throw std::runtime_error("Unable to create surface");
-	}
-
-	BaseOffset += sizeof(SSpriteDef);
-	int BaseOffsetor = BaseOffset;
-
-	SDL_Palette * p = SDL_AllocPalette(256);
-	SDL_SetPaletteColors(p, palette, 0, 256);
-	SDL_SetSurfacePalette(ret, p);
-	SDL_FreePalette(p);
-
-	int ftcp=0;
-
-	// If there's a margin anywhere, just blank out the whole surface.
-	if (TopMargin > 0 || BottomMargin > 0 || LeftMargin > 0 || RightMargin > 0) {
-		memset( reinterpret_cast<char*>(ret->pixels), 0, FullHeight*FullWidth);
-	}
-
-	// Skip top margin
-	if (TopMargin > 0)
-		ftcp += TopMargin*(FullWidth+add);
-
-	switch(defType2)
-	{
-	case 0:
-	{
-		for (ui32 i=0;i<SpriteHeight;i++)
-		{
-			if (LeftMargin>0)
-				ftcp += LeftMargin;
-
-			memcpy(reinterpret_cast<char*>(ret->pixels)+ftcp, &FDef[BaseOffset], SpriteWidth);
-			ftcp += SpriteWidth;
-			BaseOffset += SpriteWidth;
-
-			if (RightMargin>0)
-				ftcp += RightMargin;
-		}
-	}
-	break;
-
-	case 1:
-	{
-		const ui32 * RWEntriesLoc = reinterpret_cast<const ui32 *>(FDef+BaseOffset);
-		BaseOffset += sizeof(int) * SpriteHeight;
-		for (ui32 i=0;i<SpriteHeight;i++)
-		{
-			BaseOffset=BaseOffsetor + read_le_u32(RWEntriesLoc + i);
-			if (LeftMargin>0)
-				ftcp += LeftMargin;
-
-			TotalRowLength=0;
-			do
-			{
-				ui32 SegmentLength;
-
-				SegmentType=FDef[BaseOffset++];
-				SegmentLength=FDef[BaseOffset++] + 1;
-
-				if (SegmentType==0xFF)
-				{
-					memcpy(reinterpret_cast<char*>(ret->pixels)+ftcp, FDef + BaseOffset, SegmentLength);
-					BaseOffset+=SegmentLength;
-				}
-				else
-				{
-					memset(reinterpret_cast<char*>(ret->pixels)+ftcp, SegmentType, SegmentLength);
-				}
-				ftcp += SegmentLength;
-				TotalRowLength += SegmentLength;
-			}while(TotalRowLength<SpriteWidth);
-
-			RowAdd=SpriteWidth-TotalRowLength;
-
-			if (RightMargin>0)
-				ftcp += RightMargin;
-
-			if (add>0)
-				ftcp += add+RowAdd;
-		}
-	}
-	break;
-
-	case 2:
-	{
-		BaseOffset = BaseOffsetor + read_le_u16(FDef + BaseOffsetor);
-
-		for (ui32 i=0;i<SpriteHeight;i++)
-		{
-			//BaseOffset = BaseOffsetor+RWEntries[i];
-			if (LeftMargin>0)
-				ftcp += LeftMargin;
-
-			TotalRowLength=0;
-
-			do
-			{
-				SegmentType=FDef[BaseOffset++];
-				ui8 code = SegmentType / 32;
-				ui8 value = (SegmentType & 31) + 1;
-				if(code==7)
-				{
-					memcpy(reinterpret_cast<char*>(ret->pixels)+ftcp, &FDef[BaseOffset], value);
-					ftcp += value;
-					BaseOffset += value;
-				}
-				else
-				{
-					memset(reinterpret_cast<char*>(ret->pixels)+ftcp, code, value);
-					ftcp += value;
-				}
-				TotalRowLength+=value;
-			} while(TotalRowLength<SpriteWidth);
-
-			if (RightMargin>0)
-				ftcp += RightMargin;
-
-			RowAdd=SpriteWidth-TotalRowLength;
-
-			if (add>0)
-				ftcp += add+RowAdd;
-		}
-	}
-	break;
-
-	case 3:
-	{
-		for (ui32 i=0;i<SpriteHeight;i++)
-		{
-			BaseOffset = BaseOffsetor + read_le_u16(FDef + BaseOffsetor+i*2*(SpriteWidth/32));
-			if (LeftMargin>0)
-				ftcp += LeftMargin;
-
-			TotalRowLength=0;
-
-			do
-			{
-				SegmentType=FDef[BaseOffset++];
-				ui8 code = SegmentType / 32;
-				ui8 value = (SegmentType & 31) + 1;
-
-				int len = std::min<ui32>(value, SpriteWidth - TotalRowLength) - std::max(0, -LeftMargin);
-				vstd::amax(len, 0);
-
-				if(code==7)
-				{
-					memcpy((ui8*)ret->pixels + ftcp, FDef + BaseOffset, len);
-					ftcp += len;
-					BaseOffset += len;
-				}
-				else
-				{
-					memset((ui8*)ret->pixels + ftcp, code, len);
-					ftcp += len;
-				}
-				TotalRowLength+=( LeftMargin>=0 ? value : value+LeftMargin );
-			}while(TotalRowLength<SpriteWidth);
-
-			if (RightMargin>0)
-				ftcp += RightMargin;
-
-			RowAdd=SpriteWidth-TotalRowLength;
-
-			if (add>0)
-				ftcp += add+RowAdd;
-		}
-	}
-	break;
-
-	default:
-		throw std::runtime_error("Unknown sprite format.");
-		break;
-	}
-
-	SDL_Color ttcol = ret->format->palette->colors[0];
-	Uint32 keycol = SDL_MapRGBA(ret->format, ttcol.r, ttcol.b, ttcol.g, ttcol.a);
-	SDL_SetColorKey(ret, SDL_TRUE, keycol);
-
-	return ret;
-}
-
-CDefHandler * CDefHandler::giveDef(const std::string & defName)
-{
-	ResourceID resID(std::string("SPRITES/") + defName, EResType::ANIMATION);
-
-	auto data = CResourceHandler::get()->load(resID)->readAll().first;
-	if(!data)
-		throw std::runtime_error("bad def name!");
-	auto   nh = new CDefHandler();
-	nh->openFromMemory(data.get(), defName);
-	return nh;
-}
-

+ 0 - 94
client/CDefHandler.h

@@ -1,94 +0,0 @@
-/*
- * CDefHandler.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#pragma once
-
-#include "../lib/vcmi_endian.h"
-
-struct SDL_Surface;
-struct SDL_Color;
-
-struct Cimage
-{
-	int groupNumber;
-	std::string imName; //name without extension
-	SDL_Surface * bitmap;
-};
-
-// Def entry in file. Integer fields are all little endian and will
-// need to be converted.
-struct SDefEntryBlock
-{
-	ui32 unknown1;
-	ui32 totalInBlock;
-	ui32 unknown2;
-	ui32 unknown3;
-	ui8 data[0];
-} PACKED_STRUCT;
-
-// Def entry in file. Integer fields are all little endian and will
-// need to be converted.
-struct SDefEntry
-{
-	ui32 DEFType;
-	ui32 width;
-	ui32 height;
-	ui32 totalBlocks;
-
-	struct {
-		ui8 R;
-		ui8 G;
-		ui8 B;
-	} palette[256];
-
-	// SDefEntry is followed by a series of SDefEntryBlock
-	// This is commented out because VC++ doesn't accept C99 syntax.
-	//struct SDefEntryBlock blocks[];
-} PACKED_STRUCT;
-
-// Def entry in file. Integer fields are all little endian and will
-// need to be converted.
-struct SSpriteDef
-{
-	ui32 prSize;
-	ui32 defType2;
-	ui32 FullWidth;
-	ui32 FullHeight;
-	ui32 SpriteWidth;
-	ui32 SpriteHeight;
-	ui32 LeftMargin;
-	ui32 TopMargin;
-} PACKED_STRUCT;
-
-
-class CDefHandler
-{
-private:
-	ui32 DEFType;
-	struct SEntry
-	{
-		std::string name;
-		int offset;
-		int group;
-	} ;
-	std::vector<SEntry> SEntries ;
-
-	void openFromMemory(ui8 * table, const std::string & name);
-	SDL_Surface * getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const;
-public:
-	int width, height; //width and height
-	std::string defName;
-	std::vector<Cimage> ourImages;
-
-	CDefHandler();
-	~CDefHandler();
-
-
-	static CDefHandler * giveDef(const std::string & defName);
-};

+ 40 - 0
client/CMT.cpp

@@ -714,6 +714,46 @@ void processCommand(const std::string &message)
 		std::cout << "\rExtracting done :)\n";
 		std::cout << " Extracted files can be found in " << outPath << " directory\n";
 	}
+	else if(message=="get config")
+	{
+		std::cout << "Command accepted.\t";
+
+		const bfs::path outPath =
+			VCMIDirs::get().userCachePath() / "extracted" / "configuration";
+
+		bfs::create_directories(outPath);
+
+		const std::vector<std::string> contentNames = {"heroClasses", "artifacts", "creatures", "factions", "objects", "heroes", "spells", "skills"};
+
+		for(auto contentName : contentNames)
+		{
+			auto & content = VLC->modh->content[contentName];
+
+			auto contentOutPath = outPath / contentName;
+			bfs::create_directories(contentOutPath);
+
+			for(auto & iter : content.modData)
+			{
+				const JsonNode & modData = iter.second.modData;
+
+				for(auto & nameAndObject : modData.Struct())
+				{
+					const JsonNode & object = nameAndObject.second;
+
+					std::string name = CModHandler::normalizeIdentifier(object.meta, "core", nameAndObject.first);
+
+					boost::algorithm::replace_all(name,":","_");
+
+					const bfs::path filePath = contentOutPath / (name + ".json");
+					bfs::ofstream file(filePath);
+					file << object.toJson();
+				}
+			}
+		}
+
+		std::cout << "\rExtracting done :)\n";
+		std::cout << " Extracted files can be found in " << outPath << " directory\n";
+	}
 	else if(message=="get txt")
 	{
 		std::cout << "Command accepted.\t";

+ 0 - 2
client/CMakeLists.txt

@@ -45,7 +45,6 @@ set(client_SRCS
 
 		CBitmapHandler.cpp
 		CreatureCostBox.cpp
-		CDefHandler.cpp
 		CGameInfo.cpp
 		Client.cpp
 		CMessage.cpp
@@ -103,7 +102,6 @@ set(client_HEADERS
 
 		CBitmapHandler.h
 		CreatureCostBox.h
-		CDefHandler.h
 		CGameInfo.h
 		Client.h
 		CMessage.h

+ 111 - 99
client/CPlayerInterface.cpp

@@ -42,7 +42,8 @@
 #include "../lib/JsonNode.h"
 #include "CMusicHandler.h"
 #include "../lib/CondSh.h"
-#include "../lib/NetPacks.h"
+#include "../lib/NetPacksBase.h"
+#include "../lib/NetPacks.h"//todo: remove
 #include "../lib/mapping/CMap.h"
 #include "../lib/VCMIDirs.h"
 #include "mapHandler.h"
@@ -695,80 +696,91 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
 	BATTLE_EVENT_POSSIBLE_RETURN;
 }
 
-
-void CPlayerInterface::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom)
+void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
-	for (auto & healedStack : healedStacks)
+	for(auto & info : units)
 	{
-		const CStack * healed = cb->battleGetStackByID(healedStack.first);
-		if (battleInt->creAnims[healed->ID]->isDead())
+		switch(info.operation)
 		{
-			//stack has been resurrected
-			battleInt->creAnims[healed->ID]->setType(CCreatureAnim::HOLDING);
-		}
-	}
+		case UnitChanges::EOperation::RESET_STATE:
+			{
+				const battle::Unit * unit = cb->battleGetUnitByID(info.id);
 
-	if(lifeDrain)
-	{
-		const CStack * attacker = cb->battleGetStackByID(healedStacks[0].first, false);
-		const CStack * defender = cb->battleGetStackByID(lifeDrainFrom, false);
+				if(!unit)
+				{
+					logGlobal->error("Invalid unit ID %d", info.id);
+					continue;
+				}
 
-		if(attacker && defender)
-		{
-			battleInt->displayEffect(52, attacker->position); //TODO: transparency
-			CCS->soundh->playSound(soundBase::DRAINLIF);
-
-			MetaString text;
-			attacker->addText(text, MetaString::GENERAL_TXT, 361);
-			attacker->addNameReplacement(text, false);
-			text.addReplacement(healedStacks[0].second);
-			defender->addNameReplacement(text, true);
-			battleInt->console->addText(text.toString());
-		}
-		else
-		{
-			logGlobal->error("Unable to display life drain info");
-		}
-	}
-	if(tentHeal)
-	{
-		const CStack * healer = cb->battleGetStackByID(lifeDrainFrom, false);
-		const CStack * target = cb->battleGetStackByID(healedStacks[0].first, false);
+				auto iter = battleInt->creAnims.find(info.id);
 
-		if(healer && target)
-		{
-			MetaString text;
-			text.addTxt(MetaString::GENERAL_TXT, 414);
-			healer->addNameReplacement(text, false);
-			target->addNameReplacement(text, false);
-			text.addReplacement(healedStacks[0].second);
-			battleInt->console->addText(text.toString());
-		}
-		else
-		{
-			logGlobal->error("Unable to display tent heal info");
+				if(iter == battleInt->creAnims.end())
+				{
+					logGlobal->error("Unit %d have no animation", info.id);
+					continue;
+				}
+
+				CCreatureAnimation * animation = iter->second;
+
+				if(unit->alive() && animation->isDead())
+					animation->setType(CCreatureAnim::HOLDING);
+
+				//TODO: handle more cases
+			}
+			break;
+		case UnitChanges::EOperation::REMOVE:
+			battleInt->stackRemoved(info.id);
+			break;
+		case UnitChanges::EOperation::ADD:
+			{
+				const CStack * unit = cb->battleGetStackByID(info.id);
+				if(!unit)
+				{
+					logGlobal->error("Invalid unit ID %d", info.id);
+					continue;
+				}
+				battleInt->unitAdded(unit);
+			}
+			break;
+		default:
+			logGlobal->error("Unknown unit operation %d", (int)info.operation);
+			break;
 		}
 	}
+
+	battleInt->displayCustomEffects(customEffects);
+	battleInt->displayBattleLog(battleLog);
 }
 
-void CPlayerInterface::battleNewStackAppeared(const CStack * stack)
+void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
-	battleInt->newStack(stack);
-}
+	bool needUpdate = false;
 
-void CPlayerInterface::battleObstaclesRemoved(const std::set<si32> & removedObstacles)
-{
-	EVENT_HANDLER_CALLED_BY_CLIENT;
-	BATTLE_EVENT_POSSIBLE_RETURN;
+	for(auto & change : obstacles)
+	{
+		if(change.operation == BattleChanges::EOperation::ADD)
+		{
+			auto instance = cb->battleGetObstacleByID(change.id);
+			if(instance)
+				battleInt->obstaclePlaced(*instance);
+			else
+				logNetwork->error("Invalid obstacle instance %d", change.id);
+		}
+		else
+		{
+			needUpdate = true;
+		}
+	}
 
-	//update accessible hexes
-	battleInt->redrawBackgroundWithHexes(battleInt->activeStack);
+	if(needUpdate)
+		//update accessible hexes
+		battleInt->redrawBackgroundWithHexes(battleInt->activeStack);
 }
 
 void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca)
@@ -779,17 +791,6 @@ void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca)
 	battleInt->stackIsCatapulting(ca);
 }
 
-void CPlayerInterface::battleStacksRemoved(const BattleStacksRemoved & bsr)
-{
-	EVENT_HANDLER_CALLED_BY_CLIENT;
-	BATTLE_EVENT_POSSIBLE_RETURN;
-
-	for (auto & elem : bsr.stackIDs) //for each removed stack
-	{
-		battleInt->stackRemoved(elem);
-	}
-}
-
 void CPlayerInterface::battleNewRound(int round) //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -864,9 +865,9 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
 	BattleAction ret = *(CBattleInterface::givenCommand.data);
 	vstd::clear_pointer(CBattleInterface::givenCommand.data);
 
-	if (ret.actionType == Battle::CANCEL)
+	if(ret.actionType == EActionType::CANCEL)
 	{
-		if (stackId != ret.stackNumber)
+		if(stackId != ret.stackNumber)
 			logGlobal->error("Not current active stack action canceled");
 		logGlobal->trace("Canceled command for %s", stackName);
 	}
@@ -932,37 +933,36 @@ void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte)
 	RETURN_IF_QUICK_COMBAT;
 	battleInt->battleTriggerEffect(bte);
 }
-void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
+void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
 	std::vector<StackAttackedInfo> arg;
-	for (auto & elem : bsa)
+	for(auto & elem : bsa)
 	{
-		const CStack *defender = cb->battleGetStackByID(elem.stackAttacked, false);
-		const CStack *attacker = cb->battleGetStackByID(elem.attackerID, false);
-		if (elem.isEffect())
+		const CStack * defender = cb->battleGetStackByID(elem.stackAttacked, false);
+		const CStack * attacker = cb->battleGetStackByID(elem.attackerID, false);
+		if(elem.isEffect())
 		{
-			if (defender && !elem.isSecondary())
-				battleInt->displayEffect(elem.effect, defender->position);
+			if(defender && !elem.isSecondary())
+				battleInt->displayEffect(elem.effect, defender->getPosition());
 		}
-		if (elem.isSpell())
+		if(elem.isSpell())
 		{
-			if (defender)
-				battleInt->displaySpellEffect(elem.spellID, defender->position);
+			if(defender)
+				battleInt->displaySpellEffect(elem.spellID, defender->getPosition());
 		}
 		//FIXME: why action is deleted during enchanter cast?
 		bool remoteAttack = false;
 
-		if (LOCPLINT->curAction)
-			remoteAttack |= LOCPLINT->curAction->actionType != Battle::WALK_AND_ATTACK;
+		if(LOCPLINT->curAction)
+			remoteAttack |= LOCPLINT->curAction->actionType != EActionType::WALK_AND_ATTACK;
 
 		StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, remoteAttack, elem.killed(), elem.willRebirth(), elem.cloneKilled()};
 		arg.push_back(to_put);
 	}
-
-	battleInt->stacksAreAttacked(arg);
+	battleInt->stacksAreAttacked(arg, battleLog);
 }
 void CPlayerInterface::battleAttack(const BattleAttack * ba)
 {
@@ -982,13 +982,13 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
 	if(ba->lucky()) //lucky hit
 	{
 		battleInt->console->addText(attacker->formatGeneralMessage(-45));
-		battleInt->displayEffect(18, attacker->position);
+		battleInt->displayEffect(18, attacker->getPosition());
 		CCS->soundh->playSound(soundBase::GOODLUCK);
 	}
 	if(ba->unlucky()) //unlucky hit
 	{
 		battleInt->console->addText(attacker->formatGeneralMessage(-44));
-		battleInt->displayEffect(48, attacker->position);
+		battleInt->displayEffect(48, attacker->getPosition());
 		CCS->soundh->playSound(soundBase::BADLUCK);
 	}
 	if(ba->deathBlow())
@@ -997,12 +997,23 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
 		for(auto & elem : ba->bsa)
 		{
 			const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
-			battleInt->displayEffect(73, attacked->position);
+			battleInt->displayEffect(73, attacked->getPosition());
 		}
 		CCS->soundh->playSound(soundBase::deathBlow);
 	}
+
+	battleInt->displayCustomEffects(ba->customEffects);
+
 	battleInt->waitForAnims();
 
+	auto actionTarget = curAction->getTarget(cb.get());
+
+	if(actionTarget.empty() || (actionTarget.size() < 2 && !ba->shot()))
+	{
+		logNetwork->error("Invalid current action: no destination.");
+		return;
+	}
+
 	if(ba->shot())
 	{
 		for(auto & elem : ba->bsa)
@@ -1010,17 +1021,22 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
 			if(!elem.isSecondary()) //display projectile only for primary target
 			{
 				const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
-				battleInt->stackAttacking(attacker, attacked->position, attacked, true);
+				battleInt->stackAttacking(attacker, attacked->getPosition(), attacked, true);
 			}
 		}
 	}
 	else
 	{
+		auto attackFrom = actionTarget.at(0).hexValue;
+		auto attackTarget = actionTarget.at(1).hexValue;
+
+		//TODO: use information from BattleAttack but not curAction
+
 		int shift = 0;
-		if(ba->counter() && BattleHex::mutualPosition(curAction->destinationTile, attacker->position) < 0)
+		if(ba->counter() && BattleHex::mutualPosition(attackTarget, attacker->getPosition()) < 0)
 		{
-			int distp = BattleHex::getDistance(curAction->destinationTile + 1, attacker->position);
-			int distm = BattleHex::getDistance(curAction->destinationTile - 1, attacker->position);
+			int distp = BattleHex::getDistance(attackTarget + 1, attacker->getPosition());
+			int distm = BattleHex::getDistance(attackTarget - 1, attacker->getPosition());
 
 			if(distp < distm)
 				shift = 1;
@@ -1028,25 +1044,21 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
 				shift = -1;
 		}
 		const CStack * attacked = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked);
-		battleInt->stackAttacking(attacker, ba->counter() ? curAction->destinationTile + shift : curAction->additionalInfo, attacked, false);
+		battleInt->stackAttacking(attacker, ba->counter() ? BattleHex(attackTarget + shift) : attackTarget, attacked, false);
 	}
 
 	//battleInt->waitForAnims(); //FIXME: freeze
 
 	if(ba->spellLike())
 	{
+		//TODO: use information from BattleAttack but not curAction
+
+		auto destination = actionTarget.at(0).hexValue;
 		//display hit animation
 		SpellID spellID = ba->spellID;
-		battleInt->displaySpellHit(spellID, curAction->destinationTile);
+		battleInt->displaySpellHit(spellID, destination);
 	}
 }
-void CPlayerInterface::battleObstaclePlaced(const CObstacleInstance &obstacle)
-{
-	EVENT_HANDLER_CALLED_BY_CLIENT;
-	BATTLE_EVENT_POSSIBLE_RETURN;
-
-	battleInt->obstaclePlaced(obstacle);
-}
 
 void CPlayerInterface::battleGateStateChanged(const EGateState state)
 {

+ 3 - 6
client/CPlayerInterface.h

@@ -212,15 +212,12 @@ public:
 	void battleSpellCast(const BattleSpellCast *sc) override;
 	void battleStacksEffectsSet(const SetStackEffect & sse) override; //called when a specific effect is set to stacks
 	void battleTriggerEffect(const BattleTriggerEffect & bte) override; //various one-shot effect
-	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override;
+	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog) override;
 	void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) override; //called by engine just before battle starts; side=0 - left, side=1 - right
 	void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
-	void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected
-	void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned
-	void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override; //called when a certain set  of obstacles is removed from batlefield; IDs of them are given
+	void battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog) override;
+	void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles) override;
 	void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
-	void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield
-	void battleObstaclePlaced(const CObstacleInstance &obstacle) override;
 	void battleGateStateChanged(const EGateState state) override;
 	void yourTacticPhase(int distance) override;
 

+ 2 - 1
client/CPreGame.cpp

@@ -3455,7 +3455,8 @@ void CBonusSelection::selectMap(int mapNr, bool initialSelect)
 		//get header
 		std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second;
 		auto buffer = reinterpret_cast<const ui8 *>(headerStr.data());
-		ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName);
+		CMapService mapService;
+		ourHeader = mapService.loadMapHeader(buffer, headerStr.size(), scenarioName);
 
 		std::map<ui8, std::string> names;
 		names[1] = settings["general"]["playerName"].String();

+ 5 - 3
client/Client.cpp

@@ -41,6 +41,7 @@
 #include "../lib/VCMI_Lib.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/mapping/CMap.h"
+#include "../lib/mapping/CMapService.h"
 #include "../lib/JsonNode.h"
 #include "mapHandler.h"
 #include "../lib/CConfigHandler.h"
@@ -153,7 +154,7 @@ void CClient::waitForMoveAndSend(PlayerColor color)
 		setThreadName("CClient::waitForMoveAndSend");
 		assert(vstd::contains(battleints, color));
 		BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false));
-		if(ba.actionType != Battle::CANCEL)
+		if(ba.actionType != EActionType::CANCEL)
 		{
 			logNetwork->trace("Send battle action to server: %s", ba.toString());
 			MakeAction temp_action(ba);
@@ -413,10 +414,11 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 	c.disableSmartPointerSerialization();
 
 	// Initialize game state
+	CMapService mapService;
 	gs = new CGameState();
 	logNetwork->info("\tCreating gamestate: %i",tmh.getDiff());
 
-	gs->init(si, settings["general"]["saveRandomMaps"].Bool());
+	gs->init(&mapService, si, settings["general"]["saveRandomMaps"].Bool());
 	logNetwork->info("Initializing GameState (together): %d ms", tmh.getDiff());
 
 	// Now after possible random map gen, we know exact player count.
@@ -951,7 +953,7 @@ void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> ba
 	if(needCallback)
 	{
 		logGlobal->trace("\tInitializing the battle interface for player %s", *color);
-		auto cbc = std::make_shared<CBattleCallback>(gs, color, this);
+		auto cbc = std::make_shared<CBattleCallback>(color, this);
 		battleCallbacks[colorUsed] = cbc;
 		battleInterface->init(cbc);
 	}

+ 1 - 1
client/Client.h

@@ -26,7 +26,7 @@ class CGameState;
 class CGameInterface;
 class CConnection;
 class CCallback;
-struct BattleAction;
+class BattleAction;
 struct SharedMemory;
 class CClient;
 class CScriptingModule;

+ 6 - 43
client/NetPacksClient.cpp

@@ -645,11 +645,6 @@ void BattleTriggerEffect::applyCl(CClient * cl)
 	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleTriggerEffect, *this);
 }
 
-void BattleObstaclePlaced::applyCl(CClient * cl)
-{
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclePlaced, *obstacle);
-}
-
 void BattleUpdateGateState::applyFirstCl(CClient * cl)
 {
 	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleGateStateChanged, state);
@@ -667,30 +662,14 @@ void BattleStackMoved::applyFirstCl(CClient *cl)
 	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStackMoved, movedStack, tilesToMove, distance);
 }
 
-//void BattleStackAttacked::(CClient *cl)
-void BattleStackAttacked::applyFirstCl(CClient *cl)
-{
-	std::vector<BattleStackAttacked> bsa;
-	bsa.push_back(*this);
-
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa);
-}
-
 void BattleAttack::applyFirstCl(CClient *cl)
 {
 	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleAttack, this);
-	for (auto & elem : bsa)
-	{
-		for (int z=0; z<elem.healedStacks.size(); ++z)
-		{
-			elem.healedStacks[z].applyCl(cl);
-		}
-	}
 }
 
 void BattleAttack::applyCl(CClient *cl)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa);
+	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa, battleLog);
 }
 
 void StartAction::applyFirstCl(CClient *cl)
@@ -712,7 +691,7 @@ void SetStackEffect::applyCl(CClient *cl)
 
 void StacksInjured::applyCl(CClient *cl)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks);
+	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks, battleLog);
 }
 
 void BattleResultsApplied::applyCl(CClient *cl)
@@ -722,20 +701,15 @@ void BattleResultsApplied::applyCl(CClient *cl)
 	callInterfaceIfPresent(cl, PlayerColor::SPECTATOR, &IGameEventsReceiver::battleResultsApplied);
 }
 
-void StacksHealedOrResurrected::applyCl(CClient * cl)
+void BattleUnitsChanged::applyCl(CClient * cl)
 {
-	std::vector<std::pair<ui32, ui32> > shiftedHealed;
-	for(auto & elem : healedStacks)
-	{
-		shiftedHealed.push_back(std::make_pair(elem.stackId, (ui32)elem.delta));
-	}
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksHealedRes, shiftedHealed, lifeDrain, tentHealing, drainedFrom);
+	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleUnitsChanged, changedStacks, customEffects, battleLog);
 }
 
-void ObstaclesRemoved::applyCl(CClient *cl)
+void BattleObstaclesChanged::applyCl(CClient *cl)
 {
 	//inform interfaces about removed obstacles
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclesRemoved, obstacles);
+	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclesChanged, changes);
 }
 
 void CatapultAttack::applyCl(CClient *cl)
@@ -744,17 +718,6 @@ void CatapultAttack::applyCl(CClient *cl)
 	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleCatapultAttacked, *this);
 }
 
-void BattleStacksRemoved::applyFirstCl(CClient * cl)
-{
-	//inform interfaces about removed stacks
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksRemoved, *this);
-}
-
-void BattleStackAdded::applyCl(CClient *cl)
-{
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleNewStackAppeared, GS(cl)->curB->stacks.back());
-}
-
 CGameState* CPackForClient::GS(CClient *cl)
 {
 	return cl->gs;

+ 0 - 2
client/VCMI_client.cbp

@@ -104,8 +104,6 @@
 		<Unit filename="../CCallback.h" />
 		<Unit filename="CBitmapHandler.cpp" />
 		<Unit filename="CBitmapHandler.h" />
-		<Unit filename="CDefHandler.cpp" />
-		<Unit filename="CDefHandler.h" />
 		<Unit filename="CGameInfo.cpp" />
 		<Unit filename="CGameInfo.h" />
 		<Unit filename="CMT.cpp" />

+ 48 - 38
client/battle/CBattleAnimations.cpp

@@ -138,7 +138,7 @@ CAttackAnimation::CAttackAnimation(CBattleInterface *_owner, const CStack *attac
 		dest(_dest), attackedStack(defender), attackingStack(attacker)
 {
 	assert(attackingStack && "attackingStack is nullptr in CBattleAttack::CBattleAttack !\n");
-	attackingStackPosBeforeReturn = attackingStack->position;
+	attackingStackPosBeforeReturn = attackingStack->getPosition();
 }
 
 CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner)
@@ -184,9 +184,9 @@ bool CDefenceAnimation::init()
 
 
 	//reverse unit if necessary
-	if (attacker && owner->getCurrentPlayerInterface()->cb->isToReverse(stack->position, attacker->position, owner->creDir[stack->ID], attacker->doubleWide(), owner->creDir[attacker->ID]))
+	if(attacker && owner->getCurrentPlayerInterface()->cb->isToReverse(stack->getPosition(), attacker->getPosition(), owner->creDir[stack->ID], attacker->doubleWide(), owner->creDir[attacker->ID]))
 	{
-		owner->addNewAnim(new CReverseAnimation(owner, stack, stack->position, true));
+		owner->addNewAnim(new CReverseAnimation(owner, stack, stack->getPosition(), true));
 		return false;
 	}
 	//unit reversed
@@ -226,11 +226,10 @@ std::string CDefenceAnimation::getMySound()
 {
 	if(killed)
 		return battle_sound(stack->getCreature(), killed);
-
-	if (vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM))
+	else if(stack->defendingAnim)
 		return battle_sound(stack->getCreature(), defend);
-
-	return battle_sound(stack->getCreature(), wince);
+	else
+		return battle_sound(stack->getCreature(), wince);
 }
 
 CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
@@ -243,10 +242,10 @@ CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
 			return CCreatureAnim::DEATH;
 	}
 
-	if(vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM))
+	if(stack->defendingAnim)
 		return CCreatureAnim::DEFENCE;
-
-	return CCreatureAnim::HITTED;
+	else
+		return CCreatureAnim::HITTED;
 }
 
 void CDefenceAnimation::startAnimation()
@@ -324,7 +323,7 @@ bool CMeleeAttackAnimation::init()
 		return false;
 	}
 
-	bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->position, owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]);
+	bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->getPosition(), owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]);
 
 	if(toReverse)
 	{
@@ -366,7 +365,7 @@ bool CMeleeAttackAnimation::init()
 	int mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, dest);
 	if(mutPos == -1 && attackingStack->doubleWide())
 	{
-		mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->position);
+		mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->getPosition());
 	}
 	if (mutPos == -1 && attackedStack->doubleWide())
 	{
@@ -558,7 +557,7 @@ CMovementAnimation::CMovementAnimation(CBattleInterface *_owner, const CStack *_
     : CBattleStackAnimation(_owner, _stack),
       destTiles(_destTiles),
       curentMoveIndex(0),
-      oldPos(stack->position),
+      oldPos(stack->getPosition()),
       begX(0), begY(0),
       distanceX(0), distanceY(0),
       timeToMove(0.0),
@@ -731,16 +730,18 @@ bool CShootingAnimation::init()
 	}
 
 	//reverse unit if necessary
-	if (attackingStack && attackedStack && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, attackedStack->position, owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID]))
+	if (attackingStack && attackedStack && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID]))
 	{
-		owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true));
+		owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
 		return false;
 	}
 
 	// opponent must face attacker ( = different directions) before he can be attacked
-	if (attackingStack && attackedStack &&
-	    owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
-		return false;
+	//FIXME: this cause freeze
+
+//	if (attackingStack && attackedStack &&
+//	    owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
+//		return false;
 
 	// Create the projectile animation
 
@@ -780,7 +781,7 @@ bool CShootingAnimation::init()
 	int multiplier = spi.reverse ? -1 : 1;
 
 	double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x));
-	if(shooter->position < dest)
+	if(shooter->getPosition() < dest)
 		projectileAngle = -projectileAngle;
 
 	// Calculate projectile start position. Offsets are read out of the CRANIM.TXT.
@@ -920,7 +921,7 @@ CCastAnimation::CCastAnimation(CBattleInterface * owner_, const CStack * attacke
 	: CRangedAttackAnimation(owner_, attacker, dest_, defender)
 {
 	if(!dest_.isValid() && defender)
-		dest = defender->position;
+		dest = defender->getPosition();
 }
 
 bool CCastAnimation::init()
@@ -937,17 +938,17 @@ bool CCastAnimation::init()
 	//reverse unit if necessary
 	if(attackedStack)
 	{
-        if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, attackedStack->position, owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID]))
+        if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID]))
 		{
-			owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true));
+			owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
 			return false;
 		}
 	}
 	else
 	{
-        if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, dest, owner->creDir[attackingStack->ID], false, false))
+        if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), dest, owner->creDir[attackingStack->ID], false, false))
 		{
-			owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true));
+			owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
 			return false;
 		}
 	}
@@ -962,13 +963,13 @@ bool CCastAnimation::init()
 
 	// NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise
 	fromPos = owner->creAnims[attackingStack->ID]->pos.topLeft();
-	//xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner);
+	//xycoord = CClickableHex::getXYUnitAnim(shooter->getPosition(), true, shooter, owner);
 
 	destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner);
 
 
 	double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x));
-	if(attackingStack->position < dest)
+	if(attackingStack->getPosition() < dest)
 		projectileAngle = -projectileAngle;
 
 
@@ -1045,7 +1046,6 @@ void CCastAnimation::endAnim()
 CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
 	: CBattleAnimation(_owner),
 	destTile(BattleHex::INVALID),
-	customAnim(_customAnim),
 	x(_x),
 	y(_y),
 	dx(_dx),
@@ -1053,13 +1053,29 @@ CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _custo
 	Vflip(_Vflip),
 	alignToBottom(_alignToBottom)
 {
-	logAnim->debug("Created effect animation %s", customAnim);
+	logAnim->debug("Created effect animation %s", _customAnim);
+
+	customAnim = std::make_shared<CAnimation>(_customAnim);
 }
 
+CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::shared_ptr<CAnimation> _customAnim, int _x, int _y, int _dx, int _dy)
+	: CBattleAnimation(_owner),
+	destTile(BattleHex::INVALID),
+	customAnim(_customAnim),
+	x(_x),
+	y(_y),
+	dx(_dx),
+	dy(_dy),
+	Vflip(false),
+	alignToBottom(false)
+{
+	logAnim->debug("Created custom effect animation");
+}
+
+
 CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom)
 	: CBattleAnimation(_owner),
 	destTile(_destTile),
-	customAnim(_customAnim),
 	x(-1),
 	y(-1),
 	dx(0),
@@ -1067,24 +1083,18 @@ CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _custo
 	Vflip(_Vflip),
 	alignToBottom(_alignToBottom)
 {
-	logAnim->debug("Created effect animation %s", customAnim);
+	logAnim->debug("Created effect animation %s", _customAnim);
+	customAnim = std::make_shared<CAnimation>(_customAnim);
 }
 
-
 bool CEffectAnimation::init()
 {
 	if(!isEarliest(true))
 		return false;
 
-	if(customAnim.empty())
-	{
-		endAnim();
-		return false;
-	}
-
 	const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1);
 
-	std::shared_ptr<CAnimation> animation = std::make_shared<CAnimation>(customAnim);
+	std::shared_ptr<CAnimation> animation = customAnim;
 
 	animation->preload();
 	if(Vflip)

+ 4 - 1
client/battle/CBattleAnimations.h

@@ -238,7 +238,7 @@ class CEffectAnimation : public CBattleAnimation
 {
 private:
 	BattleHex destTile;
-	std::string	customAnim;
+	std::shared_ptr<CAnimation>	customAnim;
 	int	x, y, dx, dy;
 	bool Vflip;
 	bool alignToBottom;
@@ -248,6 +248,9 @@ public:
 	void endAnim() override;
 
 	CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
+
+	CEffectAnimation(CBattleInterface * _owner, std::shared_ptr<CAnimation> _customAnim, int _x, int _y, int _dx = 0, int _dy = 0);
+
 	CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false);
 	virtual ~CEffectAnimation(){};
 };

File diff suppressed because it is too large
+ 239 - 207
client/battle/CBattleInterface.cpp


+ 22 - 21
client/battle/CBattleInterface.h

@@ -19,7 +19,6 @@
 class CLabel;
 class CCreatureSet;
 class CGHeroInstance;
-class CDefHandler;
 class CStack;
 class CCallback;
 class CButton;
@@ -30,7 +29,7 @@ struct BattleSpellCast;
 struct CObstacleInstance;
 template <typename T> struct CondSh;
 struct SetStackEffect;
-struct BattleAction;
+class BattleAction;
 class CGTownInstance;
 struct CatapultAttack;
 struct CatapultProjectileInfo;
@@ -46,15 +45,16 @@ struct ProjectileInfo;
 class CClickableHex;
 struct BattleHex;
 struct InfoAboutHero;
-struct BattleAction;
 class CBattleGameInterface;
+struct CustomEffectInfo;
 class CAnimation;
+class IImage;
 
 /// Small struct which contains information about the id of the attacked stack, the damage dealt,...
 struct StackAttackedInfo
 {
 	const CStack *defender; //attacked stack
-	int32_t dmg; //damage dealt
+	int64_t dmg; //damage dealt
 	unsigned int amountKilled; //how many creatures in stack has been killed
 	const CStack *attacker; //attacking stack
 	bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack
@@ -132,15 +132,8 @@ private:
 
 	std::map<int, std::shared_ptr<CAnimation>> idToProjectile;
 
-	std::map<int, CDefHandler *> idToObstacle; //obstacles located on the battlefield
-	std::map<int, SDL_Surface *> idToAbsoluteObstacle; //obstacles located on the battlefield
-
-	//TODO these should be loaded only when needed (and freed then) but I believe it's rather work for resource manager,
-	//so I didn't implement that (having ongoing RM development)
-	CDefHandler *landMine;
-	CDefHandler *quicksand;
-	CDefHandler *fireWall;
-	CDefHandler *smallForceField[2], *bigForceField[2]; // [side]
+	std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
+	std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;
 
 	std::map<int, bool> creDir; // <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
 	ui8 animCount;
@@ -185,7 +178,9 @@ private:
 	void printConsoleAttacked(const CStack *defender, int dmg, int killed, const CStack *attacker, bool Multiple);
 
 	std::list<ProjectileInfo> projectiles; //projectiles flying on battlefield
-	void giveCommand(Battle::ActionType action, BattleHex tile, ui32 stackID, si32 additional=-1, si32 selectedStack = -1);
+	void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
+	void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
+
 	bool isTileAttackable(const BattleHex & number) const; //returns true if tile 'number' is neighboring any tile from active stack's range or is one of these tiles
 	bool isCatapultAttackable(BattleHex hex) const; //returns true if given tile can be attacked by catapult
 
@@ -259,12 +254,14 @@ private:
 	BattleObjectsByHex sortObjectsByHex();
 	void updateBattleAnimations();
 
-	SDL_Surface *getObstacleImage(const CObstacleInstance &oi);
-	Point getObstaclePosition(SDL_Surface *image, const CObstacleInstance &obstacle);
+	IImage * getObstacleImage(const CObstacleInstance & oi);
+
+	Point getObstaclePosition(IImage * image, const CObstacleInstance & obstacle);
+
 	void redrawBackgroundWithHexes(const CStack *activeStack);
 	/** End of battle screen blitting methods */
 
-	PossibleActions getCasterAction(const CSpell *spell, const ISpellCaster *caster, ECastingMode::ECastingMode mode) const;
+	PossibleActions getCasterAction(const CSpell *spell, const spells::Caster *caster, spells::Mode mode) const;
 
 	void setHeroAnimation(ui8 side, int phase);
 public:
@@ -329,12 +326,12 @@ public:
 
 	//call-ins
 	void startAction(const BattleAction* action);
-	void newStack(const CStack *stack); //new stack appeared on battlefield
-	void stackRemoved(int stackID); //stack disappeared from batlefiled
+	void unitAdded(const CStack * stack); //new stack appeared on battlefield
+	void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
 	void stackActivated(const CStack *stack); //active stack has been changed
 	void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
 	void waitForAnims();
-	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
+	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos, const std::vector<MetaString> & battleLog); //called when a certain amount of stacks has been attacked
 	void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
 	void newRoundFirst( int round );
 	void newRound(int number); //caled when round is ended; number is the number of round
@@ -345,6 +342,10 @@ public:
 	void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
 	void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
 	void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
+
+	void displayBattleLog(const std::vector<MetaString> & battleLog);
+	void displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects);
+
 	void displayEffect(ui32 effect, BattleHex destTile); //displays custom effect on the battlefield
 
 	void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation
@@ -370,7 +371,7 @@ public:
 
 	void gateStateChanged(const EGateState state);
 
-	void initStackProjectile(const CStack *stack);
+	void initStackProjectile(const CStack * stack);
 
 	const CGHeroInstance *currentHero() const;
 	InfoAboutHero enemyHero() const;

+ 84 - 59
client/battle/CBattleInterfaceClasses.cpp

@@ -203,7 +203,7 @@ void CBattleHero::clickLeft(tribool down, bool previousState)
 	if(!myHero || down || !myOwner->myTurn)
 		return;
 
-	if(myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING) == ESpellCastProblem::OK) //check conditions
+	if(myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
 	{
 		for(int it=0; it<GameConstants::BFIELD_SIZE; ++it) //do nothing when any hex is hovered - hero's animation overlaps battlefield
 		{
@@ -505,9 +505,9 @@ Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBatt
 	assert(cbi);
 
 	Point ret(-500, -500); //returned value
-	if(stack && stack->position < 0) //creatures in turrets
+	if(stack && stack->initialPosition < 0) //creatures in turrets
 	{
-		switch(stack->position)
+		switch(stack->initialPosition)
 		{
 		case -2: //keep
 			ret = cbi->siegeH->town->town->clientInfo.siegePositions[18];
@@ -669,105 +669,130 @@ CHeroInfoWindow::CHeroInfoWindow(const InfoAboutHero &hero, Point *position) : C
 	new CLabel(39, 186, EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, std::to_string(currentSpellPoints) + "/" + std::to_string(maxSpellPoints));
 }
 
-void CStackQueue::update()
-{
-	stacksSorted.clear();
-	owner->getCurrentPlayerInterface()->cb->battleGetStackQueue(stacksSorted, stackBoxes.size());
-	if(stacksSorted.size())
-	{
-		for (int i = 0; i < stackBoxes.size() ; i++)
-		{
-			stackBoxes[i]->setStack(stacksSorted[i]);
-		}
-	}
-	else
-	{
-		//no stacks on battlefield... what to do with queue?
-	}
-}
-
 CStackQueue::CStackQueue(bool Embedded, CBattleInterface * _owner)
-:embedded(Embedded), owner(_owner)
+	: embedded(Embedded),
+	owner(_owner)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	if(embedded)
 	{
-		bg = nullptr;
 		pos.w = QUEUE_SIZE * 37;
 		pos.h = 46;
 		pos.x = screen->w/2 - pos.w/2;
 		pos.y = (screen->h - 600)/2 + 10;
+
+		icons = std::make_shared<CAnimation>("CPRSMALL");
+		stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
 	}
 	else
 	{
-		bg = BitmapHandler::loadBitmap("DIBOXBCK");
 		pos.w = 800;
 		pos.h = 85;
+
+		new CFilledTexture("DIBOXBCK", Rect(0,0, pos.w, pos.h));
+
+		icons = std::make_shared<CAnimation>("TWCRPORT");
+		stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
+		//TODO: where use big icons?
+		//stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESBIG");
 	}
+	stateIcons->preload();
 
 	stackBoxes.resize(QUEUE_SIZE);
 	for (int i = 0; i < stackBoxes.size(); i++)
 	{
-		stackBoxes[i] = new StackBox(embedded);
+		stackBoxes[i] = new StackBox(this);
 		stackBoxes[i]->moveBy(Point(1 + (embedded ? 36 : 80)*i, 0));
 	}
 }
 
 CStackQueue::~CStackQueue()
 {
-	SDL_FreeSurface(bg);
 }
 
-void CStackQueue::showAll(SDL_Surface * to)
+void CStackQueue::update()
 {
-	blitBg(to);
+	std::vector<battle::Units> queueData;
 
-	CIntObject::showAll(to);
-}
+	owner->getCurrentPlayerInterface()->cb->battleGetTurnOrder(queueData, stackBoxes.size(), 0);
 
-void CStackQueue::blitBg( SDL_Surface * to )
-{
-	if(bg)
+	size_t boxIndex = 0;
+
+	for(size_t turn = 0; turn < queueData.size() && boxIndex < stackBoxes.size(); turn++)
 	{
-		SDL_SetClipRect(to, &pos);
-		CSDL_Ext::fillTexture(to, bg);
-		SDL_SetClipRect(to, nullptr);
+		for(size_t unitIndex = 0; unitIndex < queueData[turn].size() && boxIndex < stackBoxes.size(); boxIndex++, unitIndex++)
+			stackBoxes[boxIndex]->setStack(queueData[turn][unitIndex], turn);
 	}
+
+	for(; boxIndex < stackBoxes.size(); boxIndex++)
+		stackBoxes[boxIndex]->setStack(nullptr);
 }
 
-void CStackQueue::StackBox::showAll(SDL_Surface * to)
+CStackQueue::StackBox::StackBox(CStackQueue * owner)
+	: bg(nullptr),
+	icon(nullptr),
+	amount(nullptr),
+	stateIcon(nullptr)
 {
-	assert(stack);
-	bg->colorize(stack->owner);
-	CIntObject::showAll(to);
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	bg = new CPicture(owner->embedded ? "StackQueueSmall" : "StackQueueLarge" );
+
+	pos.w = bg->pos.w;
+	pos.h = bg->pos.h;
 
-	if(small)
-		printAtMiddleLoc(makeNumberShort(stack->getCount()), pos.w/2, pos.h - 7, FONT_SMALL, Colors::WHITE, to);
+	if(owner->embedded)
+	{
+		icon = new CAnimImage(owner->icons, 0, 0, 5, 2);
+		amount = new CLabel(pos.w/2, pos.h - 7, FONT_SMALL, CENTER, Colors::WHITE);
+	}
 	else
-		printAtMiddleLoc(makeNumberShort(stack->getCount()), pos.w/2, pos.h - 8, FONT_MEDIUM, Colors::WHITE, to);
-}
+	{
+		icon = new CAnimImage(owner->icons, 0, 0, 9, 1);
+		amount = new CLabel(pos.w/2, pos.h - 8, FONT_MEDIUM, CENTER, Colors::WHITE);
 
-void CStackQueue::StackBox::setStack( const CStack *stack )
-{
-	this->stack = stack;
-	assert(stack);
-	icon->setFrame(stack->getCreature()->iconIndex);
+		int icon_x = pos.w - 17;
+		int icon_y = pos.h - 18;
+
+		stateIcon = new CAnimImage(owner->stateIcons, 0, 0, icon_x, icon_y);
+		stateIcon->visible = false;
+	}
 }
 
-CStackQueue::StackBox::StackBox(bool small):
-    stack(nullptr),
-    small(small)
+void CStackQueue::StackBox::setStack(const battle::Unit * nStack, size_t turn)
 {
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	bg = new CPicture(small ? "StackQueueSmall" : "StackQueueLarge" );
-
-	if (small)
+	if(nStack)
 	{
-		icon = new CAnimImage("CPRSMALL", 0, 0, 5, 2);
+		bg->colorize(nStack->unitOwner());
+		icon->visible = true;
+		icon->setFrame(nStack->creatureIconIndex());
+		amount->setText(makeNumberShort(nStack->getCount()));
+
+		if(stateIcon)
+		{
+			if(nStack->defended(turn) || (turn > 0 && nStack->defended(turn - 1)))
+			{
+				stateIcon->setFrame(0, 0);
+				stateIcon->visible = true;
+			}
+			else if(nStack->waited(turn))
+			{
+				stateIcon->setFrame(1, 0);
+				stateIcon->visible = true;
+			}
+			else
+			{
+				stateIcon->visible = false;
+			}
+		}
 	}
 	else
-		icon = new CAnimImage("TWCRPORT", 0, 0, 9, 1);
+	{
+		bg->colorize(PlayerColor::NEUTRAL);
+		icon->visible = false;
+		icon->setFrame(0);
+		amount->setText("");
 
-	pos.w = bg->pos.w;
-	pos.h = bg->pos.h;
+		if(stateIcon)
+			stateIcon->visible = false;
+	}
 }

+ 11 - 11
client/battle/CBattleInterfaceClasses.h

@@ -23,6 +23,10 @@ class CToggleGroup;
 class CLabel;
 struct BattleResult;
 class CStack;
+namespace battle
+{
+	class Unit;
+}
 class CAnimImage;
 class CPlayerInterface;
 
@@ -141,27 +145,23 @@ class CStackQueue : public CIntObject
 	public:
 		CPicture * bg;
 		CAnimImage * icon;
-		const CStack *stack;
-		bool small;
+		CLabel * amount;
+		CAnimImage * stateIcon;
 
-		void showAll(SDL_Surface * to) override;
-		void setStack(const CStack *nStack);
-		StackBox(bool small);
+		void setStack(const battle::Unit * nStack, size_t turn = 0);
+		StackBox(CStackQueue * owner);
 	};
 
 public:
 	static const int QUEUE_SIZE = 10;
 	const bool embedded;
-	std::vector<const CStack *> stacksSorted;
 	std::vector<StackBox *> stackBoxes;
-
-	SDL_Surface * bg;
-
 	CBattleInterface * owner;
 
+	std::shared_ptr<CAnimation> icons;
+	std::shared_ptr<CAnimation> stateIcons;
+
 	CStackQueue(bool Embedded, CBattleInterface * _owner);
 	~CStackQueue();
 	void update();
-	void showAll(SDL_Surface *to) override;
-	void blitBg(SDL_Surface * to);
 };

+ 3 - 3
client/windows/CCastleInterface.cpp

@@ -1641,11 +1641,11 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
 		new CLabel(78,  11, FONT_SMALL, CENTER, Colors::WHITE, getMyCreature()->namePl);
 
 		Rect sizes(287, 4, 96, 18);
-		values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[190], CGI->generaltexth->fcommands[0], getMyCreature()->Attack()));
+		values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[190], CGI->generaltexth->fcommands[0], getMyCreature()->getAttack(false)));
 		sizes.y+=20;
-		values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[191], CGI->generaltexth->fcommands[1], getMyCreature()->Defense()));
+		values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[191], CGI->generaltexth->fcommands[1], getMyCreature()->getDefence(false)));
 		sizes.y+=21;
-		values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[199], CGI->generaltexth->fcommands[2], getMyCreature()->getMinDamage(), getMyCreature()->getMaxDamage()));
+		values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[199], CGI->generaltexth->fcommands[2], getMyCreature()->getMinDamage(false), getMyCreature()->getMaxDamage(false)));
 		sizes.y+=20;
 		values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[388], CGI->generaltexth->fcommands[3], getMyCreature()->MaxHealth()));
 		sizes.y+=21;

+ 7 - 7
client/windows/CCreatureWindow.cpp

@@ -219,7 +219,7 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
 
 	int dmgMultiply = 1;
 	if(parent->info->owner && parent->info->stackNode->hasBonusOfType(Bonus::SIEGE_WEAPON))
-		dmgMultiply += parent->info->owner->Attack();
+		dmgMultiply += parent->info->owner->getPrimSkillLevel(PrimarySkill::ATTACK);
 
 	new CPicture("stackWindow/icons", 117, 32);
 
@@ -230,9 +230,9 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
 
 	if(battleStack != nullptr) // in battle
 	{
-		printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), battleStack->Attack());
-		printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), battleStack->Defense());
-		printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, battleStack->getMaxDamage() * dmgMultiply);
+		printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->getAttack(battleStack->isShooter()), battleStack->getAttack(battleStack->isShooter()));
+		printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->getDefence(battleStack->isShooter()), battleStack->getDefence(battleStack->isShooter()));
+		printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage(battleStack->isShooter()) * dmgMultiply, battleStack->getMaxDamage(battleStack->isShooter()) * dmgMultiply);
 		printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->MaxHealth(), battleStack->MaxHealth());
 		printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), battleStack->Speed());
 
@@ -250,9 +250,9 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
 		const bool shooter = parent->info->stackNode->hasBonusOfType(Bonus::SHOOTER) && parent->info->stackNode->valOfBonuses(Bonus::SHOTS);
 		const bool caster  = parent->info->stackNode->valOfBonuses(Bonus::CASTS);
 
-		printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), parent->info->stackNode->Attack());
-		printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), parent->info->stackNode->Defense());
-		printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, parent->info->stackNode->getMaxDamage() * dmgMultiply);
+		printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->getAttack(shooter), parent->info->stackNode->getAttack(shooter));
+		printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->getDefence(shooter), parent->info->stackNode->getDefence(shooter));
+		printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage(shooter) * dmgMultiply, parent->info->stackNode->getMaxDamage(shooter) * dmgMultiply);
 		printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->MaxHealth(), parent->info->stackNode->MaxHealth());
 		printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), parent->info->stackNode->Speed());
 

+ 5 - 0
client/windows/CHeroWindow.cpp

@@ -61,6 +61,11 @@ const TBonusListPtr CHeroWithMaybePickedArtifact::getAllBonuses(const CSelector
 	return out;
 }
 
+int64_t CHeroWithMaybePickedArtifact::getTreeVersion() const
+{
+	return hero->getTreeVersion();  //this assumes that hero and artifact belongs to main bonus tree
+}
+
 CHeroWithMaybePickedArtifact::CHeroWithMaybePickedArtifact(CWindowWithArtifacts *Cww, const CGHeroInstance *Hero)
 	:  hero(Hero), cww(Cww)
 {

+ 3 - 1
client/windows/CHeroWindow.h

@@ -39,7 +39,7 @@ public:
 };
 
 //helper class for calculating values of hero bonuses without bonuses from picked up artifact
-class CHeroWithMaybePickedArtifact : public IBonusBearer
+class CHeroWithMaybePickedArtifact : public virtual IBonusBearer
 {
 public:
 	const CGHeroInstance *hero;
@@ -47,6 +47,8 @@ public:
 
 	CHeroWithMaybePickedArtifact(CWindowWithArtifacts *Cww, const CGHeroInstance *Hero);
 	const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
+
+	int64_t getTreeVersion() const override;
 };
 
 class CHeroWindow: public CWindowObject, public CWindowWithGarrison, public CWindowWithArtifacts

+ 37 - 93
client/windows/CSpellWindow.cpp

@@ -34,14 +34,13 @@
 
 #include "../../CCallback.h"
 
-#include "../../lib/CStack.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
-#include "../../lib/CHeroHandler.h"
 #include "../../lib/spells/CSpellHandler.h"
+#include "../../lib/spells/Problem.h"
 #include "../../lib/GameConstants.h"
-#include "../../lib/CGameState.h"
-#include "../../lib/mapObjects/CGTownInstance.h"
+
+#include "../../lib/mapObjects/CGHeroInstance.h"
 
 CSpellWindow::InteractiveArea::InteractiveArea(const SDL_Rect & myRect, std::function<void()> funcL, int helpTextId, CSpellWindow * _owner)
 {
@@ -125,7 +124,7 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
 
 		++sitesPerOurTab[4];
 
-		spell->forEachSchool([&sitesPerOurTab](const SpellSchoolInfo & school, bool & stop)
+		spell->forEachSchool([&sitesPerOurTab](const spells::SchoolInfo & school, bool & stop)
 		{
 			++sitesPerOurTab[(ui8)school.id];
 		});
@@ -536,101 +535,46 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 			owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana));
 			return;
 		}
+
+		//anything that is not combat spell is adventure spell
+		//this not an error in general to cast even creature ability with hero
+		const bool combatSpell = mySpell->isCombatSpell();
+		if(mySpell->isCombatSpell() != !mySpell->isAdventureSpell())
+		{
+			logGlobal->error("Spell have invalid flags");
+			return;
+		}
+
+		const bool inCombat = owner->myInt->battleInt != nullptr;
+		const bool inCastle = owner->myInt->castleInt != nullptr;
+
 		//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
-		if((mySpell->isCombatSpell() && !owner->myInt->battleInt)
-		   || (mySpell->isAdventureSpell() && (owner->myInt->battleInt || owner->myInt->castleInt)))
+		if((combatSpell ^ inCombat) || inCastle)
 		{
 			std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell->id, 0));
 			owner->myInt->showInfoDialog(mySpell->getLevelInfo(schoolLevel).description, hlp);
-			return;
 		}
-
-		//we will cast a spell
-		if(mySpell->isCombatSpell() && owner->myInt->battleInt) //if battle window is open
+		else if(combatSpell)
 		{
-			ESpellCastProblem::ESpellCastProblem problem = mySpell->canBeCast(owner->myInt->cb.get(), ECastingMode::HERO_CASTING, owner->myHero);
-			switch (problem)
+			spells::detail::ProblemImpl problem;
+			if(mySpell->canBeCast(problem, owner->myInt->cb.get(), spells::Mode::HERO, owner->myHero))
 			{
-			case ESpellCastProblem::OK:
-				{
-					owner->myInt->battleInt->castThisSpell(mySpell->id);
-					owner->fexitb();
-					return;
-				}
-				break;
-			case ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED:
-				{
-					std::string text = CGI->generaltexth->allTexts[538], elemental, caster;
-					const PlayerColor player = owner->myInt->playerID;
-
-					const TStacks stacks = owner->myInt->cb->battleGetStacksIf([player](const CStack * s)
-					{
-						return s->owner == player
-							&& vstd::contains(s->state, EBattleStackState::SUMMONED)
-							&& !s->isClone();
-					});
-					for(const CStack * s : stacks)
-					{
-						elemental = s->getCreature()->namePl;
-					}
-					if (owner->myHero->type->sex)
-					{ //female
-						caster = CGI->generaltexth->allTexts[540];
-					}
-					else
-					{ //male
-						caster = CGI->generaltexth->allTexts[539];
-					}
-					std::string summoner = owner->myHero->name;
-
-					text = boost::str(boost::format(text) % summoner % elemental % caster);
-
-					owner->myInt->showInfoDialog(text);
-				}
-				break;
-			case ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED:
-				{
-					//Recanter's Cloak or similar effect. Try to retrieve bonus
-					const auto 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)
-					{
-						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 if(b && b->source == Bonus::TERRAIN_OVERLAY && b->sid == BFieldType::CURSED_GROUND)
-					{
-						owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[537]);
-					}
-					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:
-				{
-					owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[185]);
-				}
-				break;
-			default:
-				{
-					// General message:
-					std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name;
-					text = boost::str(boost::format(text) % caster);
-					owner->myInt->showInfoDialog(text);
-				}
+				owner->myInt->battleInt->castThisSpell(mySpell->id);
+				owner->fexitb();
+			}
+			else
+			{
+				std::vector<std::string> texts;
+				problem.getAll(texts);
+				if(!texts.empty())
+					owner->myInt->showInfoDialog(texts.front());
+				else
+					owner->myInt->showInfoDialog("Unknown problem with this spell, no more information available.");
 			}
 		}
-		else if(mySpell->isAdventureSpell() && !owner->myInt->battleInt) //adventure spell and not in battle
+		else //adventure spell
 		{
-			const CGHeroInstance *h = owner->myHero;
+			const CGHeroInstance * h = owner->myHero;
 			GH.popInt(owner);
 
 			auto guard = vstd::makeScopeGuard([this]()
@@ -639,9 +583,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 				owner->myInt->spellbookSettings.spellbokLastPageAdvmap = owner->currentPage;
 			});
 
-			if(mySpell->getTargetType() == CSpell::LOCATION)
+			if(mySpell->getTargetType() == spells::AimType::LOCATION)
 				adventureInt->enterCastingMode(mySpell);
-			else if(mySpell->getTargetType() == CSpell::NO_TARGET)
+			else if(mySpell->getTargetType() == spells::AimType::NO_TARGET)
 				owner->myInt->cb->castSpell(h, mySpell->id);
 			else
 				logGlobal->error("Invalid spell target type");
@@ -654,7 +598,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState)
 	if(mySpell && down)
 	{
 		std::string dmgInfo;
-		int causedDmg = owner->myInt->cb->estimateSpellDamage(mySpell, owner->myHero);
+		auto causedDmg = owner->myInt->cb->estimateSpellDamage(mySpell, owner->myHero);
 		if(causedDmg == 0 || mySpell->id == SpellID::TITANS_LIGHTNING_BOLT) //Titan's Lightning Bolt already has damage info included
 			dmgInfo = "";
 		else

+ 1 - 0
client/windows/CreaturePurchaseCard.cpp

@@ -16,6 +16,7 @@
 #include "../CreatureCostBox.h"
 #include "QuickRecruitmentWindow.h"
 #include "../gui/CGuiHandler.h"
+#include "../../lib/CCreatureHandler.h"
 
 void CreaturePurchaseCard::initButtons()
 {

+ 2 - 1
client/windows/QuickRecruitmentWindow.cpp

@@ -15,7 +15,8 @@
 #include "../gui/CGuiHandler.h"
 #include "../../CCallback.h"
 #include "../CreatureCostBox.h"
-#include "../lib/ResourceSet.h"
+#include "../../lib/ResourceSet.h"
+#include "../../lib/CCreatureHandler.h"
 #include "CreaturePurchaseCard.h"
 
 

+ 6 - 1
config/schemas/settings.json

@@ -196,7 +196,7 @@
 			"type" : "object",
 			"additionalProperties" : false,
 			"default": {},
-			"required" : [ "animationSpeed", "mouseShadow", "cellBorders", "stackRange", "showQueue" ],
+			"required" : [ "animationSpeed", "mouseShadow", "cellBorders", "stackRange", "showQueue", "queueSize" ],
 			"properties" : {
 				"animationSpeed" : {
 					"type" : "number",
@@ -217,6 +217,11 @@
 				"showQueue" : {
 					"type" : "boolean",
 					"default" : true
+				},
+				"queueSize" : {
+					"type" : "string",
+					"default" : "auto",
+					"enum" : [ "auto", "small", "big" ]
 				}
 			}
 		},

+ 10 - 1
config/schemas/spell.json

@@ -97,6 +97,12 @@
 						"$ref" : "vcmi:bonus"
 					}
 				},
+				"battleEffects":{
+					"type": "object",
+					"additionalProperties" : {
+						"type": "object"
+					}
+				},
 				"targetModifier":{
 					"type": "object",
 					"additionalProperties": false,
@@ -246,7 +252,10 @@
 				 "$ref" : "#/definitions/flags",
 				 "description": "flags structure of bonus names, presence of all bonuses required to be affected by, can't be negated."
 		},
-
+		"targetCondition":{
+			"type": "object",
+			"additionalProperties" : true
+		},
 		"animation":{"$ref": "#/definitions/animation"},
 
 		"graphics":{

+ 101 - 62
config/spells/ability.json

@@ -16,7 +16,7 @@
 					"notActive" : {
 						"val" : 0,
 						"type" : "NOT_ACTIVE",
-						"subtype" : 62,
+						"subtype" : "spell.stoneGaze",
 						"duration" : [
 							"UNTIL_BEING_ATTACKED",
 							"N_TURNS"
@@ -43,11 +43,13 @@
 				}
 			}
 		},
-		"absoluteImmunity":{
-			"SIEGE_WEAPON": true
-		},
 		"flags" : {
 			"indifferent": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON" : "absolute"
+			}
 		}
 	},
 	"poison" : {
@@ -80,15 +82,15 @@
 				}
 			}
 		},
-		"absoluteImmunity":{
-			"SIEGE_WEAPON": true
-		},
-		"immunity" : {
-			"UNDEAD": true,
-			"NON_LIVING": true
-		},
 		"flags" : {
 			"negative": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.NON_LIVING" : "normal",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "normal"
+			}
 		}
 	},
 	"bind" : {
@@ -149,15 +151,15 @@
 				}
 			}
 		},
-		"absoluteImmunity":{
-			"SIEGE_WEAPON": true
-		},
-		"immunity" : {
-			"UNDEAD": true,
-			"NON_LIVING": true
-		},
 		"flags" : {
 			"negative": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.NON_LIVING" : "normal",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "normal"
+			}
 		}
 	},
 	"paralyze" : {
@@ -178,7 +180,7 @@
 					"notActive" : {
 						"val" : 0,
 						"type" : "NOT_ACTIVE",
-						"subtype" : 74,
+						"subtype" : "spell.paralyze",
 						"duration" : [
 							"UNTIL_BEING_ATTACKED",
 							"N_TURNS"
@@ -195,11 +197,13 @@
 				}
 			}
 		},
-		"absoluteImmunity":{
-			"SIEGE_WEAPON": true
-		},
 		"flags" : {
 			"negative": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON" : "absolute"
+			}
 		}
 	},
 	"age" : {
@@ -226,15 +230,15 @@
 				}
 			}
 		},
-		"absoluteImmunity":{
-			"SIEGE_WEAPON": true
-		},
-		"immunity" : {
-			"UNDEAD": true,
-			"NON_LIVING": true
-		},
 		"flags" : {
 			"negative": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.NON_LIVING" : "normal",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "normal"
+			}
 		}
 	},
 	"deathCloud" : {
@@ -249,18 +253,23 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "0-1"
+				"range" : "0-1",
+				"battleEffects":{
+					"damage":{
+						"type":"core:damage"
+					}
+				}
 			}
 		},
-		"absoluteImmunity":{
-			"SIEGE_WEAPON": true
-		},
-		"immunity" : {
-			"UNDEAD": true,
-			"NON_LIVING": true
-		},
 		"flags" : {
 			"indifferent": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.NON_LIVING" : "normal",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "normal"
+			}
 		}
 	},
 	"thunderbolt" : {
@@ -275,12 +284,16 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "0"
+				"range" : "0",
+				"battleEffects":{
+					"damage":{
+						"type":"core:damage"
+					}
+				}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		}
 	},
@@ -296,6 +309,15 @@
 		},
 		"levels" : {
 			"base":{
+				"battleEffects":{
+					"dispel":{
+						"type":"core:dispel",
+						"ignoreImmunity" : true,
+						"dispelNegative":false,
+						"dispelNeutral":false,
+						"dispelPositive":true
+					}
+				},
 				"range" : "0"
 			}
 		},
@@ -316,41 +338,50 @@
 		},
 		"levels" : {
 			"base":{
+				"battleEffects":{
+					"destruction":{
+						"type":"core:damage",
+						"killByCount": true
+					}
+				},
 				"range" : "0"
 			}
 		},
-		"absoluteImmunity" : {
-			"UNDEAD": true,
-			"SIEGE_WEAPON": true,
-			"NON_LIVING": true
-		},
 		"flags" : {
 			"indifferent": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.NON_LIVING" : "absolute",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "absolute"
+			}
 		}
 	},
 	"acidBreath" : {
 		"index" : 80,
 		"targetType": "NO_TARGET",
 
-		"animation":{
-			//???
-		},
-		"sounds": {
-			"cast": "ACID"
-		},
 		"levels" : {
 			"base":{
-				"range" : "0",
-				"targetModifier":{"smart":true},
-				"cumulativeEffects" : {
-					"primarySkill" : {
-						"val" : -3,
-						"type" : "PRIMARY_SKILL",
-						"subtype" : "primSkill.defence",
-						"duration" : "PERMANENT",
-						"valueType" : "ADDITIVE_VALUE"
+				"battleEffects":{
+					"timed":{
+						"type":"core:timed",
+						"cumulative":true,
+						"ignoreImmunity" : true,
+						"bonus" :{
+							"primarySkill" : {
+								"val" : -3,
+								"type" : "PRIMARY_SKILL",
+								"subtype" : "primSkill.defence",
+								"duration" : "PERMANENT",
+								"valueType" : "ADDITIVE_VALUE"
+							}
+						}
 					}
-				}
+				},
+				"range" : "0",
+				"targetModifier":{"smart":true}
 			}
 		},
 		"flags" : {
@@ -369,6 +400,12 @@
 		},
 		"levels" : {
 			"base":{
+				"battleEffects":{
+					"damage":{
+						"type":"core:damage",
+						"ignoreImmunity" : true
+					}
+				},
 				"range" : "0"
 			}
 		},
@@ -376,8 +413,10 @@
 			"damage": true,
 			"indifferent": true
 		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	}
 }

+ 131 - 70
config/spells/offensive.json

@@ -2,7 +2,7 @@
 	"magicArrow" : {
 		"index" : 15,
 		"targetType": "CREATURE",
-		
+
 		"animation":{
 			"projectile": [
 				{"minimumAngle": 0 ,"defName":"C20SPX4"},
@@ -19,22 +19,26 @@
 		"levels" : {
 			"base":{
 				"range" : "0",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":true}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	},
 	"iceBolt" : {
 		"index" : 16,
 		"targetType": "CREATURE",
-		
+
 		"animation":{
 			"projectile": [
 				{"minimumAngle": 0 ,"defName":"C08SPW4"},
@@ -44,54 +48,62 @@
 				{"minimumAngle": 1.50 ,"defName":"C08SPW0"}
 			],
 			"hit":["C08SPW5"]
-		},		
+		},
 		"sounds": {
 			"cast": "ICERAY"
 		},
 		"levels" : {
 			"base":{
 				"range" : "0",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":true}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	},
 	"lightningBolt" : {
 		"index" : 17,
 		"targetType": "CREATURE",
-		
+
 		"animation":{
 			"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
-		},		
+		},
 		"sounds": {
 			"cast": "LIGHTBLT"
 		},
 		"levels" : {
 			"base":{
 				"range" : "0",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":true}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	},
 	"implosion" : {
 		"index" : 18,
 		"targetType": "CREATURE",
-		
+
 		"animation":{
 			"affect":["C05SPE0"]
 		},
@@ -101,245 +113,294 @@
 		"levels" : {
 			"base":{
 				"range" : "0",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":true}
 			}
 		},
-
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true
-		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal",
+				"bonus.SIEGE_WEAPON" : "absolute"
+			}
 		}
 	},
 	"chainLightning" : {
 		"index" : 19,
 		"targetType": "CREATURE",
-		
+
 		"animation":{
 			"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
-		},		
+		},
 		"sounds": {
 			"cast": "CHAINLTE"
 		},
 		"levels" : {
 			"base":{
 				"range" : "0",
+				"battleEffects" : {
+					"directDamage" : {
+						"type" : "core:damage",
+						"chainFactor" : 0.5,
+						"chainLength" : 4
+					}
+				},
 				"targetModifier":{"smart":true}
+			},
+			"advanced" : {
+				"battleEffects" : {
+					"directDamage" : {
+						"chainLength" : 5
+					}
+				}
+			},
+			"expert" : {
+				"battleEffects" : {
+					"directDamage" : {
+						"chainLength" : 5
+					}
+				}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		}
 	},
 	"frostRing" : {
 		"index" : 20,
 		"targetType": "LOCATION",
-		
+
 		"animation":{
 			"hit":["C07SPW"] //C07SPW0 ???
-		},			
+		},
 		"sounds": {
 			"cast": "FROSTING"
 		},
 		"levels" : {
 			"base":{
 				"range" : "1",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":false}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	},
 	"fireball" : {
 		"index" : 21,
 		"targetType": "LOCATION",
-		
+
 		"animation":{
 			"hit":["C13SPF"] //C13SPF0 ???
-		},		
+		},
 		"sounds": {
 			"cast": "SPONTCOMB"
 		},
 		"levels" : {
 			"base":{
 				"range" : "0,1",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":false}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	},
 	"inferno" : {
 		"index" : 22,
 		"targetType": "LOCATION",
-		
+
 		"animation":{
 			"hit":["C04SPF0"]
-		},		
+		},
 		"sounds": {
 			"cast": "FIREBLST"
 		},
 		"levels" : {
 			"base":{
 				"range" : "0-2",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":false}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	},
 	"meteorShower" : {
 		"index" : 23,
 		"targetType": "LOCATION",
-		
+
 		"animation":{
 			"hit":["C08SPE0"]
-		},		
+		},
 		"sounds": {
 			"cast": "METEOR"
 		},
 		"levels" : {
 			"base":{
 				"range" : "0,1",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":false}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	},
 	"deathRipple" : {
 		"index" : 24,
 		"targetType" : "CREATURE",
-		
+
 		"animation":{
 			"affect":["C04SPE0"]
-		},		
+		},
 		"sounds": {
 			"cast": "DEATHRIP"
 		},
 		"levels" : {
 			"base":{
 				"range" : "X",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":false}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		},
-		"absoluteImmunity":{
-			"SIEGE_WEAPON": true,
-			"UNDEAD": true,
-		},		
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "absolute"
+			}
 		}
 	},
 	"destroyUndead" : {
 		"index" : 25,
 		"targetType" : "CREATURE",
-		
+
 		"animation":{
 			"affect":["C14SPA0"]
-		},		
+		},
 		"sounds": {
 			"cast": "SACBRETH"
 		},
 		"levels" : {
 			"base":{
 				"range" : "X",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":false}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		},
-		"absoluteLimit" : {
-			"UNDEAD": true
-		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"allOf" : {
+				"bonus.UNDEAD" : "absolute"
+			},
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	},
 	"armageddon" : {
 		"index" : 26,
 		"targetType" : "CREATURE",
-		
+
 		"animation":{
 			"hit":["C06SPF0"]
-		},		
+		},
 		"sounds": {
 			"cast": "ARMGEDN"
 		},
 		"levels" : {
 			"base":{
 				"range" : "X",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":false}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true
 		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	},
 	"titanBolt" : {
 		"index" : 57,
 		"targetType" : "CREATURE",
-		
+
 		"animation":{
 			"hit":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
-		},		
+		},
 		"sounds": {
 			"cast": "LIGHTBLT"
 		},
 		"levels" : {
 			"base":{
 				"range" : "0",
+				"battleEffects" : {
+					"directDamage" : {"type":"core:damage"}
+				},
 				"targetModifier":{"smart":true}
 			}
 		},
 		"flags" : {
 			"damage": true,
-			"offensive": true,
 			"negative": true,
 			"special": true
 		}

+ 405 - 28
config/spells/other.json

@@ -8,7 +8,42 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"battleEffects" : {
+					"obstacle" : {
+						"type":"core:obstacle",
+						"hidden" : true,
+						"passable" : true,
+						"trap" : true,
+						"trigger" : false,
+						"patchCount" : 4,
+						"turnsRemaining" : -1,
+						"attacker" :{
+							"animation" : "C17SPE1",
+							"appearAnimation" : "C17SPE0",
+							"offsetY" : -42
+						},
+						"defender" :{
+							"animation" : "C17SPE1",
+							"appearAnimation" : "C17SPE0",
+							"offsetY" : -42
+						}
+					}
+				}
+			},
+			"advanced":{
+				"battleEffects":{
+					"obstacle":{
+						"patchCount" : 6
+					}
+				}
+			},
+			"expert":{
+				"battleEffects":{
+					"obstacle":{
+						"patchCount" : 8
+					}
+				}
 			}
 		},
 		"flags" : {
@@ -24,15 +59,57 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"battleEffects" : {
+					"obstacle" : {
+						"type":"core:obstacle",
+						"hidden" : true,
+						"passable" : true,
+						"trap" : false,
+						"trigger" : true,
+						"removeOnTrigger" : true,
+						"patchCount" : 4,
+						"turnsRemaining" : -1,
+						"attacker" :{
+							"animation" : "C09SPF1",
+							"appearAnimation" : "C09SPF0"
+						},
+						"defender" :{
+							"animation" : "C09SPF1",
+							"appearAnimation" : "C09SPF0"
+						}
+					},
+					"damage":{
+						"type":"core:damage",
+						"optional":false,
+						"indirect":true,
+						"customEffectId" : 82
+					}
+				}
+			},
+			"advanced":{
+				"battleEffects":{
+					"obstacle":{
+						"patchCount" : 6
+					}
+				}
+			},
+			"expert":{
+				"battleEffects":{
+					"obstacle":{
+						"patchCount" : 8
+					}
+				}
 			}
 		},
 		"flags" : {
 			"damage": true,
 			"indifferent": true
 		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	},
 	"forceField" : {
@@ -47,6 +124,61 @@
 				"range" : "0",
 				"targetModifier":{
 					"clearAffected": true
+				},
+				"battleEffects":{
+					"obstacle":{
+						"type":"core:obstacle",
+						"hidden" : false,
+						"passable" : false,
+						"trap" : false,
+						"trigger" : false,
+						"patchCount" : 1,
+						"turnsRemaining" : 2,
+						"attacker" :{
+							"range" : [[""]],
+							"shape" : [[""], ["TR"]],
+							"animation" : "C15SPE1",
+							"appearAnimation" : "C15SPE0"
+						},
+						"defender" :{
+							"range" : [[""]],
+							"shape" : [[""], ["TL"]],
+							"animation" : "C15SPE4",
+							"appearAnimation" : "C15SPE0"
+						}
+					}
+				}
+			},
+			"advanced":{
+				"battleEffects":{
+					"obstacle":{
+						"attacker" :{
+							"shape" : [[""], ["TR"], ["TR", "TL"]],
+							"animation" : "C15SPE10",
+							"appearAnimation" : "C15SPE9"
+						},
+						"defender" :{
+							"shape" : [[""], ["TL"], ["TL", "TR"]],
+							"animation" : "C15SPE7",
+							"appearAnimation" : "C15SPE6"
+						}
+					}
+				}
+			},
+			"expert":{
+				"battleEffects":{
+					"obstacle":{
+						"attacker" :{
+							"shape" : [[""], ["TR"], ["TR", "TL"]],
+							"animation" : "C15SPE10",
+							"appearAnimation" : "C15SPE9"
+						},
+						"defender" :{
+							"shape" : [[""], ["TL"], ["TL", "TR"]],
+							"animation" : "C15SPE7",
+							"appearAnimation" : "C15SPE6"
+						}
+					}
 				}
 			}
 		},
@@ -66,6 +198,58 @@
 				"range" : "0",
 				"targetModifier":{
 					"clearAffected": true
+				},
+				"battleEffects":{
+					"obstacle":{
+						"type":"core:obstacle",
+						"hidden" : false,
+						"passable" : true,
+						"trap" : false,
+						"trigger" : true,
+						"patchCount" : 1,
+						"turnsRemaining" : 2,
+						"attacker" :{
+							"shape" : [[""]],
+							"range" : [[""], ["TR"]],
+							"animation" : "C07SPF61",
+							"appearAnimation" : "C07SPF60"
+						},
+						"defender" :{
+							"shape" : [[""]],
+							"range" : [[""], ["TL"]],
+							"animation" : "C07SPF61",
+							"appearAnimation" : "C07SPF60"
+						}
+					},
+					"damage":{
+						"type":"core:damage",
+						"optional":false,
+						"indirect":true
+					}
+				}
+			},
+			"advanced":{
+				"battleEffects":{
+					"obstacle":{
+						"attacker" :{
+							"range" : [[""], ["TR"], ["TR", "TL"]]
+						},
+						"defender" :{
+							"range" : [[""], ["TL"], ["TL", "TR"]]
+						}
+					}
+				}
+			},
+			"expert":{
+				"battleEffects":{
+					"obstacle":{
+						"attacker" :{
+							"range" : [[""], ["TR"], ["TR", "TL"]]
+						},
+						"defender" :{
+							"range" : [[""], ["TL"], ["TL", "TR"]]
+						}
+					}
 				}
 			}
 		},
@@ -73,8 +257,10 @@
 			"damage": true,
 			"indifferent": true
 		},
-		"immunity" : {
-			"DIRECT_DAMAGE_IMMUNITY": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
+			}
 		}
 	},
 	"earthquake" : {
@@ -87,7 +273,27 @@
 		"levels" : {
 			"base":{
 				"targetModifier":{"smart":true},
+				"battleEffects":{
+					"catapult":{
+						"type":"core:catapult",
+						"targetsToAttack": 2
+					}
+				},
 				"range" : "X"
+			},
+			"advanced":{
+				"battleEffects":{
+					"catapult":{
+						"targetsToAttack": 3
+					}
+				}
+			},
+			"expert":{
+				"battleEffects":{
+					"catapult":{
+						"targetsToAttack": 4
+					}
+				}
 			}
 		},
 		"flags" : {
@@ -107,7 +313,19 @@
 		},
 		"levels" : {
  			"base":{
-				"targetModifier":{"smart":true},
+				"targetModifier":{
+					"smart":true
+				},
+				"battleEffects":{
+					"dispel":{
+						"type":"core:dispel",
+						"optional":false,
+						"ignoreImmunity" : true,
+						"dispelNegative":true,
+						"dispelNeutral":true,
+						"dispelPositive":true
+					}
+				},
  				"range" : "0"
  			},
 			"advanced":{
@@ -115,6 +333,16 @@
 			},
  			"expert":{
 				"targetModifier":{"smart":false},
+				"battleEffects":{
+					"dispel":{
+						"optional":true
+					},
+					"removeObstacle":{
+						"optional":true,
+						"type":"core:removeObstacle",
+						"removeAllSpells" : true
+					}
+				},
  				"range" : "X"
  			}
 		},
@@ -135,6 +363,21 @@
 		"levels" : {
 			"base":{
 				"targetModifier":{"smart":true},
+				"battleEffects":{
+					"heal":{
+						"type":"core:heal",
+						"healLevel":"heal",
+						"healPower":"permanent",
+						"optional":true
+					},
+					"cure":{
+						"type":"core:dispel",
+						"optional":true,
+						"dispelNegative":true,
+						"dispelNeutral":false,
+						"dispelPositive":false
+					}
+				},
 				"range" : "0"
 			},
 			"expert":{
@@ -158,17 +401,49 @@
 		"levels" : {
 			"base":{
 				"range" : "0",
+				"battleEffects":{
+					"heal":{
+						"type":"core:heal",
+						"healLevel":"resurrect",
+						"healPower":"oneBattle",
+						"minFullUnits" : 1
+					},
+					"cure":{
+						"type":"core:dispel",
+						"indirect": true,
+						"optional":true,
+						"dispelNegative":true,
+						"dispelNeutral":false,
+						"dispelPositive":false
+					}
+				},
 				"targetModifier":{"smart":true}
+			},
+			"advanced":{
+				"battleEffects":{
+					"heal":{
+						"healPower":"permanent"
+					}
+				}
+			},
+			"expert":{
+				"battleEffects":{
+					"heal":{
+						"healPower":"permanent"
+					}
+				}
 			}
 		},
 		"flags" : {
 			"rising": true,
 			"positive": true
 		},
-		"absoluteImmunity" : {
-			"UNDEAD": true,
-			"SIEGE_WEAPON": true,
-			"NON_LIVING": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.NON_LIVING" : "absolute",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "absolute"
+			}
 		}
 	},
 	"animateDead" : {
@@ -184,6 +459,14 @@
 		"levels" : {
 			"base":{
 				"range" : "0",
+				"battleEffects":{
+					"heal":{
+						"type":"core:heal",
+						"healLevel":"resurrect",
+						"healPower":"permanent",
+						"minFullUnits" : 1
+					}
+				},
 				"targetModifier":{"smart":true}
 			}
 		},
@@ -191,8 +474,10 @@
 			"rising": true,
 			"positive": true
 		},
-		"absoluteLimit" : {
-			"UNDEAD": true
+		"targetCondition" : {
+			"allOf" : {
+				"bonus.UNDEAD" : "absolute"
+			}
 		}
 	},
 	"sacrifice" : {
@@ -208,6 +493,14 @@
 		"levels" : {
 			"base":{
 				"range" : "0",
+				"battleEffects":{
+					"sacrifice":{
+						"type":"core:sacrifice",
+						"healLevel":"resurrect",
+						"healPower":"permanent",
+						"minFullUnits" : 0
+					}
+				},
 				"targetModifier":{"smart":true}
 			}
 		},
@@ -215,10 +508,12 @@
 			"rising": true,
 			"positive": true
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true,
-			"UNDEAD": true,
-			"NON_LIVING": true
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.NON_LIVING" : "absolute",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "absolute"
+			}
 		}
 	},
 	"teleport" : {
@@ -231,14 +526,21 @@
 		"levels" : {
 			"base":{
 				"range" : "0",
+				"battleEffects":{
+					"teleport":{
+						"type":"core:teleport"
+					}
+				},
 				"targetModifier":{"smart":true}
 			}
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true
-		},
 		"flags" : {
 			"positive": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON" : "absolute"
+			}
 		}
 	},
 	"removeObstacle" : {
@@ -252,7 +554,28 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "0"
+				"range" : "0",
+				"battleEffects": {
+					"removeObstacle" : {
+						"optional":false,
+						"type":"core:removeObstacle",
+						"removeUsual" : true
+					}
+				}
+			},
+			"advanced" : {
+				"battleEffects": {
+					"removeObstacle" : {
+						"removeSpells" : ["fireWall"]
+					}
+				}
+			},
+			"expert" : {
+				"battleEffects": {
+					"removeObstacle" : {
+						"removeAllSpells" : true
+					}
+				}
 			}
 		},
 		"flags" : {
@@ -271,14 +594,36 @@
 		"levels" : {
 			"base":{
 				"range" : "0",
+				"battleEffects":{
+					"clone":{
+						"maxTier":5,
+						"type":"core:clone"
+					}
+				},
 				"targetModifier":{"smart":true}
+			},
+			"advanced":{
+				"battleEffects":{
+					"clone":{
+						"maxTier":6
+					}
+				}
+			},
+			"expert":{
+				"battleEffects":{
+					"clone":{
+						"maxTier":1000
+					}
+				}
 			}
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true
-		},
 		"flags" : {
 			"positive": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON" : "absolute"
+			}
 		}
 	},
 	"fireElemental" : {
@@ -292,7 +637,15 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"battleEffects":{
+					"summon":{
+						"exclusive":true,
+						"id":"fireElemental",
+						"permanent":false,
+						"type":"core:summon"
+					}
+				}
 			}
 		},
 		"flags" : {
@@ -310,7 +663,15 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"battleEffects":{
+					"summon":{
+						"exclusive":true,
+						"id":"earthElemental",
+						"permanent":false,
+						"type":"core:summon"
+					}
+				}
 			}
 		},
 		"flags" : {
@@ -328,7 +689,15 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"battleEffects":{
+					"summon":{
+						"exclusive":true,
+						"id":"waterElemental",
+						"permanent":false,
+						"type":"core:summon"
+					}
+				}
 			}
 		},
 		"flags" : {
@@ -346,7 +715,15 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "X",
+				"battleEffects":{
+					"summon":{
+						"exclusive":true,
+						"id":"airElemental",
+						"permanent":false,
+						"type":"core:summon"
+					}
+				}
 			}
 		},
 		"flags" : {

+ 186 - 95
config/spells/timed.json

@@ -108,11 +108,24 @@
 					"spellDamageReduction" : {
 						"type" : "SPELL_DAMAGE_REDUCTION",
 						"subtype" : 0,
-						"duration" : "N_TURNS"
+						"duration" : "N_TURNS",
+						"val" : 30
+					}
+				}
+			},
+			"advanced" : {
+				"effects" : {
+					"spellDamageReduction" : {
+						"val" : 50
 					}
 				}
 			},
 			"expert":{
+				"effects" : {
+					"spellDamageReduction" : {
+						"val" : 50
+					}
+				},
 				"range" : "X"
 			}
 		},
@@ -138,11 +151,24 @@
 					"spellDamageReduction" : {
 						"type" : "SPELL_DAMAGE_REDUCTION",
 						"subtype" : 1,
-						"duration" : "N_TURNS"
+						"duration" : "N_TURNS",
+						"val" : 30
+					}
+				}
+			},
+			"advanced" : {
+				"effects" : {
+					"spellDamageReduction" : {
+						"val" : 50
 					}
 				}
 			},
 			"expert":{
+				"effects" : {
+					"spellDamageReduction" : {
+						"val" : 50
+					}
+				},
 				"range" : "X"
 			}
 		},
@@ -168,11 +194,24 @@
 					"spellDamageReduction" : {
 						"type" : "SPELL_DAMAGE_REDUCTION",
 						"subtype" : 2,
-						"duration" : "N_TURNS"
+						"duration" : "N_TURNS",
+						"val" : 30
+					}
+				}
+			},
+			"advanced" : {
+				"effects" : {
+					"spellDamageReduction" : {
+						"val" : 50
 					}
 				}
 			},
 			"expert":{
+				"effects" : {
+					"spellDamageReduction" : {
+						"val" : 50
+					}
+				},
 				"range" : "X"
 			}
 		},
@@ -198,11 +237,24 @@
 					"spellDamageReduction" : {
 						"type" : "SPELL_DAMAGE_REDUCTION",
 						"subtype" : 3,
-						"duration" : "N_TURNS"
+						"duration" : "N_TURNS",
+						"val" : 30
+					}
+				}
+			},
+			"advanced" : {
+				"effects" : {
+					"spellDamageReduction" : {
+						"val" : 50
 					}
 				}
 			},
 			"expert":{
+				"effects" : {
+					"spellDamageReduction" : {
+						"val" : 50
+					}
+				},
 				"range" : "X"
 			}
 		},
@@ -224,26 +276,47 @@
 			"base":{
 				"range" : "0",
 				"targetModifier":{"smart":true},
-				"effects" : {
-					"levelSpellImmunity" : {
-						"val" : 3,
-						"type" : "LEVEL_SPELL_IMMUNITY",
-						"valueType" : "INDEPENDENT_MAX",
-						"duration" : "N_TURNS"
+				"battleEffects":{
+					"spellImmunity":{
+						"type":"core:timed",
+						"bonus":{
+							"levelSpellImmunity":{
+								"addInfo" : 1, //absolute
+								"val" : 3,
+								"type" : "LEVEL_SPELL_IMMUNITY",
+								"valueType" : "INDEPENDENT_MAX",
+								"duration" : "N_TURNS"
+							}
+						}
+					},
+					"dispel":{
+						"type":"core:dispel",
+						"optional":true,
+						"dispelNegative":true,
+						"dispelNeutral":true,
+						"dispelPositive":false
 					}
 				}
 			},
 			"advanced":{
-				"effects" : {
-					"levelSpellImmunity" : {
-						"val" : 4
+				"battleEffects":{
+					"spellImmunity":{
+						"bonus" :{
+							"levelSpellImmunity":{
+								"val" : 4
+							}
+						}
 					}
 				}
 			},
 			"expert":{
-				"effects" : {
-					"levelSpellImmunity" : {
-						"val" : 5
+				"battleEffects":{
+					"spellImmunity":{
+						"bonus":{
+							"levelSpellImmunity":{
+								"val" : 5
+							}
+						}
 					}
 				}
 			}
@@ -310,12 +383,14 @@
 		"counters" : {
 			"spell.curse": true
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true,
-			"UNDEAD": true
-		},
 		"flags" : {
 			"positive": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "absolute"
+			}
 		}
 	},
 	"curse" : {
@@ -350,12 +425,14 @@
 		"counters" : {
 			"spell.bless": true
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true,
-			"UNDEAD": true
-		},
 		"flags" : {
 			"negative": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "absolute"
+			}
 		}
 	},
 	"bloodlust" : {
@@ -401,11 +478,13 @@
 		"counters" : {
 			"spell.weakness": true
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true
-		},
 		"flags" : {
 			"positive": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON" : "absolute"
+			}
 		}
 	},
 	"precision" : {
@@ -448,11 +527,13 @@
 				}
 			}
 		},
-		"absoluteLimit" : {
-			"SHOOTER": true
-		},
 		"flags" : {
 			"positive": true
+		},
+		"targetCondition" : {
+			"allOf" : {
+				"bonus.SHOOTER" : "absolute"
+			}
 		}
 	},
 	"weakness" : {
@@ -697,16 +778,16 @@
 		"counters" : {
 			"spell.sorrow":true
 		},
-		"absoluteImmunity":{
-			"SIEGE_WEAPON": true,
-			"UNDEAD": true,
-		},
-		"immunity" : {
-			"MIND_IMMUNITY": true,
-			"NON_LIVING": true
-		},
 		"flags" : {
 			"positive": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.MIND_IMMUNITY" : "normal",
+				"bonus.NON_LIVING" : "normal",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "absolute"
+			}
 		}
 	},
 	"sorrow" : {
@@ -750,16 +831,16 @@
 		"counters" : {
 			"spell.mirth":true
 		},
-		"absoluteImmunity":{
-			"SIEGE_WEAPON": true,
-			"UNDEAD": true,
-		},
-		"immunity" : {
-			"MIND_IMMUNITY": true,
-			"NON_LIVING": true
-		},
 		"flags" : {
 			"negative": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.MIND_IMMUNITY" : "normal",
+				"bonus.NON_LIVING" : "normal",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "absolute"
+			}
 		}
 	},
 	"fortune" : {
@@ -894,11 +975,13 @@
 		"counters" : {
 			"spell.slow": true
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true
-		},
 		"flags" : {
 			"positive": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON" : "absolute"
+			}
 		}
 	},
 	"slow" : {
@@ -941,15 +1024,16 @@
 				}
 			}
 		},
-
 		"counters" : {
 			"spell.haste":true
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true
-		},
 		"flags" : {
 			"negative": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON" : "absolute"
+			}
 		}
 	},
 	"slayer" : {
@@ -1024,8 +1108,7 @@
 					"inFrenzy" : {
 						"type" : "IN_FRENZY",
 						"val" : 100,
-						"duration" : "N_TURNS",
-						"turns" : 1
+						"duration" : "UNTIL_ATTACK"
 					}
 				}
 			},
@@ -1044,11 +1127,13 @@
 				}
 			}
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true
-		},
 		"flags" : {
 			"positive": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON" : "absolute"
+			}
 		}
 	},
 	"counterstrike" : {
@@ -1089,11 +1174,13 @@
 				}
 			}
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true
-		},
 		"flags" : {
 			"positive": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON" : "absolute"
+			}
 		}
 	},
 	"berserk" : {
@@ -1127,16 +1214,16 @@
 		"counters" : {
 			"spell.hypnotize": true
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true
-		},
-		"immunity" : {
-			"MIND_IMMUNITY": true,
-			"UNDEAD": true,
-			"NON_LIVING": true
-		},
 		"flags" : {
 			"negative": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.MIND_IMMUNITY" : "normal",
+				"bonus.NON_LIVING" : "normal",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "normal"
+			}
 		}
 	},
 	"hypnotize" : {
@@ -1192,13 +1279,16 @@
 		"counters" : {
 			"spell.berserk": true
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true
-		},
-		"immunity" : {
-			"MIND_IMMUNITY": true,
-			"UNDEAD": true,
-			"NON_LIVING": true
+		"targetCondition" : {
+			"allOf" : {
+				"healthValueSpecial" : "absolute"
+			},
+			"noneOf" : {
+				"bonus.SIEGE_WEAPON":"absolute",
+				"bonus.MIND_IMMUNITY":"normal",
+				"bonus.UNDEAD":"normal",
+				"bonus.NON_LIVING":"normal"
+			}
 		},
 		"flags" : {
 			"negative": true
@@ -1255,19 +1345,19 @@
 				}
 			}
 		},
-		"absoluteLimit" : {
-			"SHOOTER": true
-		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true
-		},
-		"immunity" : {
-			"MIND_IMMUNITY": true,
-			"UNDEAD": true,
-			"NON_LIVING": true
-		},
 		"flags" : {
 			"negative": true
+		},
+		"targetCondition" : {
+			"allOf" : {
+				"bonus.SHOOTER" : "absolute"
+			},
+			"noneOf" : {
+				"bonus.MIND_IMMUNITY" : "normal",
+				"bonus.NON_LIVING" : "normal",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "normal"
+			}
 		}
 	},
 	"blind" : {
@@ -1316,16 +1406,17 @@
 				}
 			}
 		},
-		"absoluteImmunity" : {
-			"SIEGE_WEAPON": true,
-			"UNDEAD": true
-		},
-		"immunity" : {
-			"MIND_IMMUNITY": true,
-			"NON_LIVING": true
-		},
 		"flags" : {
 			"negative": true
+		},
+		"targetCondition" : {
+			"noneOf" : {
+				"bonus.MIND_IMMUNITY" : "normal",
+				"bonus.NON_LIVING" : "normal",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "absolute"
+			}
 		}
+
 	}
 }

+ 58 - 0
include/vstd/RNG.h

@@ -0,0 +1,58 @@
+/*
+ * RNG.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+namespace vstd
+{
+
+typedef std::function<int64_t()> TRandI64;
+typedef std::function<double()> TRand;
+
+class DLL_LINKAGE RNG
+{
+public:
+
+	virtual ~RNG() = default;
+
+	virtual TRandI64 getInt64Range(int64_t lower, int64_t upper) = 0;
+
+	virtual TRand getDoubleRange(double lower, double upper) = 0;
+};
+
+}
+
+namespace RandomGeneratorUtil
+{
+	template<typename Container>
+	auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
+	{
+		assert(!container.empty());
+		return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)());
+	}
+
+	template<typename Container>
+	auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
+	{
+		assert(!container.empty());
+		return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)());
+	}
+
+	template<typename T>
+	void randomShuffle(std::vector<T> & container, vstd::RNG & rand)
+	{
+		int64_t n = (container.end() - container.begin());
+
+		for(int64_t i = n-1; i>0; --i)
+		{
+			std::swap(container.begin()[i],container.begin()[rand.getInt64Range(0, i)()]);
+		}
+	}
+}

+ 3 - 17
lib/CArtHandler.cpp

@@ -728,20 +728,6 @@ void CArtHandler::afterLoadFinalization()
 	CBonusSystemNode::treeHasChanged();
 }
 
-si32 CArtHandler::decodeArfifact(const std::string& identifier)
-{
-	auto rawId = VLC->modh->identifiers.getIdentifier("core", "artifact", identifier);
-	if(rawId)
-		return rawId.get();
-	else
-		return -1;
-}
-
-std::string CArtHandler::encodeArtifact(const si32 index)
-{
-	return VLC->arth->artifacts[index]->identifier;
-}
-
 CArtifactInstance::CArtifactInstance()
 {
 	init();
@@ -1407,7 +1393,7 @@ void CArtifactSet::serializeJsonHero(JsonSerializeFormat & handler, CMap * map)
 		for(const ArtSlotInfo & info : artifactsInBackpack)
 			backpackTemp.push_back(info.artifact->artType->id);
 	}
-	handler.serializeIdArray(NArtifactPosition::backpack, backpackTemp, &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact);
+	handler.serializeIdArray(NArtifactPosition::backpack, backpackTemp);
 	if(!handler.saving)
 	{
         for(const ArtifactID & artifactID : backpackTemp)
@@ -1441,12 +1427,12 @@ void CArtifactSet::serializeJsonSlot(JsonSerializeFormat & handler, const Artifa
 		if(info != nullptr && !info->locked)
 		{
 			artifactID = info->artifact->artType->id;
-			handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE, &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact);
+			handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE);
 		}
 	}
 	else
 	{
-		handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE, &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact);
+		handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE);
 
 		if(artifactID != ArtifactID::NONE)
 		{

+ 0 - 6
lib/CArtHandler.h

@@ -271,12 +271,6 @@ public:
 
 	std::vector<bool> getDefaultAllowed() const override;
 
-	///json serialization helper
-	static si32 decodeArfifact(const std::string & identifier);
-
-	///json serialization helper
-	static std::string encodeArtifact(const si32 index);
-
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & artifacts;

+ 0 - 14
lib/CCreatureHandler.cpp

@@ -479,20 +479,6 @@ std::vector<bool> CCreatureHandler::getDefaultAllowed() const
 	return ret;
 }
 
-si32 CCreatureHandler::decodeCreature(const std::string& identifier)
-{
-	auto rawId = VLC->modh->identifiers.getIdentifier("core", "creature", identifier);
-	if(rawId)
-		return rawId.get();
-	else
-		return -1;
-}
-
-std::string CCreatureHandler::encodeCreature(const si32 index)
-{
-	return VLC->creh->creatures[index]->identifier;
-}
-
 void CCreatureHandler::loadCrExpBon()
 {
 	if (VLC->modh->modules.STACK_EXP) 	//reading default stack experience bonuses

+ 0 - 6
lib/CCreatureHandler.h

@@ -259,12 +259,6 @@ public:
 
 	std::vector<bool> getDefaultAllowed() const override;
 
-	///json serialization helper
-	static si32 decodeCreature(const std::string & identifier);
-
-	///json serialization helper
-	static std::string encodeCreature(const si32 index);
-
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		//TODO: should be optimized, not all these informations needs to be serialized (same for ccreature)

+ 3 - 3
lib/CGameInfoCallback.cpp

@@ -189,14 +189,14 @@ int CGameInfoCallback::getSpellCost(const CSpell * sp, const CGHeroInstance * ca
 	return caster->getSpellCost(sp);
 }
 
-int CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const
+int64_t CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const
 {
 	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
 
 	ERROR_RET_VAL_IF(hero && !canGetFullInfo(hero), "Cannot get info about caster!", -1);
 
-	if (hero) //we see hero's spellbook
-		return sp->calculateDamage(hero, nullptr, hero->getEffectLevel(sp), hero->getEffectPower(sp));
+	if(hero) //we see hero's spellbook
+		return sp->calculateDamage(hero);
 	else
 		return 0; //mage guild
 }

+ 5 - 2
lib/CGameInfoCallback.h

@@ -9,6 +9,7 @@
  */
 #pragma once
 
+#include "int3.h"
 #include "ResourceSet.h" // for Res::ERes
 #include "battle/CPlayerBattleCallback.h"
 
@@ -28,13 +29,15 @@ class CGTeleport;
 class CMapHeader;
 struct TeamState;
 struct QuestInfo;
-class int3;
 struct ShashInt3;
+class CGameState;
 
 
 class DLL_LINKAGE CGameInfoCallback : public virtual CCallbackBase
 {
 protected:
+	CGameState * gs;
+
 	CGameInfoCallback();
 	CGameInfoCallback(CGameState *GS, boost::optional<PlayerColor> Player);
 	bool hasAccess(boost::optional<PlayerColor> playerId) const;
@@ -72,7 +75,7 @@ public:
 	int getHeroCount(PlayerColor player, bool includeGarrisoned) const;
 	bool getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject = nullptr) const;
 	int getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //when called during battle, takes into account creatures' spell cost reduction
-	int estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg
+	int64_t estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg
 	const CArtifactInstance * getArtInstance(ArtifactInstanceID aid) const;
 	const CGObjectInstance * getObjInstance(ObjectInstanceID oid) const;
 

+ 7 - 18
lib/CGameInterface.cpp

@@ -134,7 +134,7 @@ std::shared_ptr<CScriptingModule> CDynLibHandler::getNewScriptingModule(std::str
 BattleAction CGlobalAI::activeStack(const CStack * stack)
 {
 	BattleAction ba;
-	ba.actionType = Battle::DEFEND;
+	ba.actionType = EActionType::DEFEND;
 	ba.stackNumber = stack->ID;
 	return ba;
 }
@@ -164,9 +164,9 @@ void CAdventureAI::battleStart(const CCreatureSet * army1, const CCreatureSet *
 	battleAI->battleStart(army1, army2, tile, hero1, hero2, side);
 }
 
-void CAdventureAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
+void CAdventureAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog)
 {
-	battleAI->battleStacksAttacked(bsa);
+	battleAI->battleStacksAttacked(bsa, battleLog);
 }
 
 void CAdventureAI::actionStarted(const BattleAction & action)
@@ -189,19 +189,9 @@ void CAdventureAI::battleStacksEffectsSet(const SetStackEffect & sse)
 	battleAI->battleStacksEffectsSet(sse);
 }
 
-void CAdventureAI::battleStacksRemoved(const BattleStacksRemoved & bsr)
+void CAdventureAI::battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles)
 {
-	battleAI->battleStacksRemoved(bsr);
-}
-
-void CAdventureAI::battleObstaclesRemoved(const std::set<si32> & removedObstacles)
-{
-	battleAI->battleObstaclesRemoved(removedObstacles);
-}
-
-void CAdventureAI::battleNewStackAppeared(const CStack * stack)
-{
-	battleAI->battleNewStackAppeared(stack);
+	battleAI->battleObstaclesChanged(obstacles);
 }
 
 void CAdventureAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance)
@@ -225,10 +215,9 @@ void CAdventureAI::battleEnd(const BattleResult * br)
 	battleAI.reset();
 }
 
-void CAdventureAI::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain,
-										 bool tentHeal, si32 lifeDrainFrom)
+void CAdventureAI::battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog)
 {
-	battleAI->battleStacksHealedRes(healedStacks, lifeDrain, tentHeal, lifeDrainFrom);
+	battleAI->battleUnitsChanged(units, customEffects, battleLog);
 }
 
 BattleAction CAdventureAI::activeStack(const CStack * stack)

+ 3 - 7
lib/CGameInterface.h

@@ -43,7 +43,6 @@ struct Bonus;
 struct PackageApplied;
 struct SetObjectProperty;
 struct CatapultAttack;
-struct BattleStacksRemoved;
 struct StackLocation;
 class CStackInstance;
 class CCommanderInstance;
@@ -134,20 +133,17 @@ public:
 	virtual void battleNewRound(int round) override;
 	virtual void battleCatapultAttacked(const CatapultAttack & ca) override;
 	virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override;
-	virtual void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override;
+	virtual void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog) override;
 	virtual void actionStarted(const BattleAction &action) override;
 	virtual void battleNewRoundFirst(int round) override;
 	virtual void actionFinished(const BattleAction &action) override;
 	virtual void battleStacksEffectsSet(const SetStackEffect & sse) override;
-	//virtual void battleTriggerEffect(const BattleTriggerEffect & bte);
-	virtual void battleStacksRemoved(const BattleStacksRemoved & bsr) override;
-	virtual void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override;
-	virtual void battleNewStackAppeared(const CStack * stack) override;
+	virtual void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles) override;
 	virtual void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override;
 	virtual void battleAttack(const BattleAttack *ba) override;
 	virtual void battleSpellCast(const BattleSpellCast *sc) override;
 	virtual void battleEnd(const BattleResult *br) override;
-	virtual void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override;
+	virtual void battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog) override;
 
 	virtual void saveGame(BinarySerializer & h, const int version) override; //saving
 	virtual void loadGame(BinaryDeserializer & h, const int version) override; //loading

+ 8 - 9
lib/CGameState.cpp

@@ -33,7 +33,6 @@
 #include "rmg/CMapGenerator.h"
 #include "CStopWatch.h"
 #include "mapping/CMapEditManager.h"
-#include "mapping/CMapService.h"
 #include "serializer/CTypeList.h"
 #include "serializer/CMemorySerializer.h"
 #include "VCMIDirs.h"
@@ -703,7 +702,7 @@ CGameState::~CGameState()
 		ptr.second.dellNull();
 }
 
-void CGameState::init(StartInfo * si, bool allowSavingRandomMap)
+void CGameState::init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap)
 {
 	logGlobal->info("\tUsing random seed: %d", si->seedToBeUsed);
 	getRandomGenerator().setSeed(si->seedToBeUsed);
@@ -714,10 +713,10 @@ void CGameState::init(StartInfo * si, bool allowSavingRandomMap)
 	switch(scenarioOps->mode)
 	{
 	case StartInfo::NEW_GAME:
-		initNewGame(allowSavingRandomMap);
+		initNewGame(mapService, allowSavingRandomMap);
 		break;
 	case StartInfo::CAMPAIGN:
-		initCampaign();
+		initCampaign(mapService);
 		break;
 	default:
 		logGlobal->error("Wrong mode: %d", static_cast<int>(scenarioOps->mode));
@@ -773,7 +772,7 @@ void CGameState::init(StartInfo * si, bool allowSavingRandomMap)
 	}
 }
 
-void CGameState::initNewGame(bool allowSavingRandomMap)
+void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap)
 {
 	if(scenarioOps->createRandomMap())
 	{
@@ -800,7 +799,7 @@ void CGameState::initNewGame(bool allowSavingRandomMap)
 				const std::string fileName = boost::str(boost::format("%s_%d.vmap") % templateName % seed );
 				const auto fullPath = path / fileName;
 
-				CMapService::saveMap(randomMap, fullPath);
+				mapService->saveMap(randomMap, fullPath);
 
 				logGlobal->info("Random map has been saved to:");
 				logGlobal->info(fullPath.string());
@@ -841,11 +840,11 @@ void CGameState::initNewGame(bool allowSavingRandomMap)
 	{
 		logGlobal->info("Open map file: %s", scenarioOps->mapname);
 		const ResourceID mapURI(scenarioOps->mapname, EResType::MAP);
-		map = CMapService::loadMap(mapURI).release();
+		map = mapService->loadMap(mapURI).release();
 	}
 }
 
-void CGameState::initCampaign()
+void CGameState::initCampaign(const IMapService * mapService)
 {
 	logGlobal->info("Open campaign map file: %d", scenarioOps->campState->currentMap.get());
 	auto campaign = scenarioOps->campState;
@@ -857,7 +856,7 @@ void CGameState::initCampaign()
 
 	std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap];
 	auto buffer = reinterpret_cast<const ui8 *>(mapContent.data());
-	map = CMapService::loadMap(buffer, mapContent.size(), scenarioName).release();
+	map = mapService->loadMap(buffer, mapContent.size(), scenarioName).release();
 }
 
 void CGameState::checkMapChecksum()

+ 5 - 5
lib/CGameState.h

@@ -26,7 +26,6 @@ class CTown;
 class CCallback;
 class IGameCallback;
 class CCreatureSet;
-class CStack;
 class CQuest;
 class CGHeroInstance;
 class CGTownInstance;
@@ -54,6 +53,7 @@ class CQuest;
 class CCampaignScenario;
 struct EventCondition;
 class CScenarioTravel;
+class IMapService;
 
 namespace boost
 {
@@ -127,7 +127,7 @@ struct UpgradeInfo
 	UpgradeInfo(){oldID = CreatureID::NONE;};
 };
 
-struct BattleInfo;
+class BattleInfo;
 
 DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult);
 
@@ -152,7 +152,7 @@ public:
 	CGameState();
 	virtual ~CGameState();
 
-	void init(StartInfo * si, bool allowSavingRandomMap = false);
+	void init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap = false);
 
 	ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized)
 	PlayerColor currentPlayer; //ID of player currently having turn
@@ -245,8 +245,8 @@ private:
 
 	// ----- initialization -----
 
-	void initNewGame(bool allowSavingRandomMap);
-	void initCampaign();
+	void initNewGame(const IMapService * mapService, bool allowSavingRandomMap);
+	void initCampaign(const IMapService * mapService);
 	void checkMapChecksum();
 	void initGrailPosition();
 	void initRandomFactionsForPlayers();

+ 49 - 4
lib/CMakeLists.txt

@@ -10,14 +10,19 @@ set(lib_SRCS
 		battle/BattleAttackInfo.cpp
 		battle/BattleHex.cpp
 		battle/BattleInfo.cpp
+		battle/BattleProxy.cpp
 		battle/CBattleInfoCallback.cpp
 		battle/CBattleInfoEssentials.cpp
 		battle/CCallbackBase.cpp
 		battle/CObstacleInstance.cpp
 		battle/CPlayerBattleCallback.cpp
+		battle/CUnitState.cpp
+		battle/Destination.cpp
+		battle/IBattleState.cpp
 		battle/ReachabilityInfo.cpp
 		battle/SideInBattle.cpp
 		battle/SiegeInfo.cpp
+		battle/Unit.cpp
 
 		filesystem/AdapterLoaders.cpp
 		filesystem/CArchiveLoader.cpp
@@ -93,12 +98,29 @@ set(lib_SRCS
 
 		spells/AdventureSpellMechanics.cpp
 		spells/BattleSpellMechanics.cpp
-		spells/CDefaultSpellMechanics.cpp
-		spells/CreatureSpellMechanics.cpp
 		spells/CSpellHandler.cpp
 		spells/ISpellMechanics.cpp
+		spells/Problem.cpp
+		spells/TargetCondition.cpp
 		spells/ViewSpellInt.cpp
 
+		spells/effects/Catapult.cpp
+		spells/effects/Clone.cpp
+		spells/effects/Damage.cpp
+		spells/effects/Dispel.cpp
+		spells/effects/Effect.cpp
+		spells/effects/Effects.cpp
+		spells/effects/Heal.cpp
+		spells/effects/LocationEffect.cpp
+		spells/effects/Obstacle.cpp
+		spells/effects/Registry.cpp
+		spells/effects/UnitEffect.cpp
+		spells/effects/Summon.cpp
+		spells/effects/Teleport.cpp
+		spells/effects/Timed.cpp
+		spells/effects/RemoveObstacle.cpp
+		spells/effects/Sacrifice.cpp
+
 		CAndroidVMHelper.cpp
 		CArtHandler.cpp
 		CBonusTypeHandler.cpp
@@ -143,14 +165,20 @@ set(lib_HEADERS
 		battle/BattleAttackInfo.h
 		battle/BattleHex.h
 		battle/BattleInfo.h
+		battle/BattleProxy.h
 		battle/CBattleInfoCallback.h
 		battle/CBattleInfoEssentials.h
 		battle/CCallbackBase.h
 		battle/CObstacleInstance.h
 		battle/CPlayerBattleCallback.h
+		battle/CUnitState.h
+		battle/Destination.h
+		battle/IBattleState.h
+		battle/IUnitInfo.h
 		battle/ReachabilityInfo.h
 		battle/SideInBattle.h
 		battle/SiegeInfo.h
+		battle/Unit.h
 
 		filesystem/AdapterLoaders.h
 		filesystem/CArchiveLoader.h
@@ -227,14 +255,31 @@ set(lib_HEADERS
 
 		spells/AdventureSpellMechanics.h
 		spells/BattleSpellMechanics.h
-		spells/CDefaultSpellMechanics.h
-		spells/CreatureSpellMechanics.h
 		spells/CSpellHandler.h
 		spells/ISpellMechanics.h
 		spells/Magic.h
 		spells/SpellMechanics.h
+		spells/Problem.h
+		spells/TargetCondition.h
 		spells/ViewSpellInt.h
 
+		spells/effects/Catapult.h
+		spells/effects/Clone.h
+		spells/effects/Damage.h
+		spells/effects/Dispel.h
+		spells/effects/Effect.h
+		spells/effects/Effects.h
+		spells/effects/EffectsFwd.h
+		spells/effects/Heal.h
+		spells/effects/LocationEffect.h
+		spells/effects/Obstacle.h
+		spells/effects/Registry.h
+		spells/effects/UnitEffect.h
+		spells/effects/Summon.h
+		spells/effects/Teleport.h
+		spells/effects/Timed.h
+		spells/effects/RemoveObstacle.h
+		spells/effects/Sacrifice.h
 		AI_Base.h
 		CAndroidVMHelper.h
 		CArtHandler.h

+ 84 - 41
lib/CModHandler.cpp

@@ -316,7 +316,7 @@ void CIdentifierStorage::finalize()
 	state = FINISHED;
 }
 
-CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objectName):
+ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objectName):
 	handler(handler),
 	objectName(objectName),
 	originalData(handler->loadLegacyData(VLC->modh->settings.data["textData"][objectName].Float()))
@@ -327,7 +327,7 @@ CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler,
 	}
 }
 
-bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, std::vector<std::string> fileList, bool validate)
+bool ContentTypeHandler::preloadModData(std::string modName, std::vector<std::string> fileList, bool validate)
 {
 	bool result;
 	JsonNode data = JsonUtils::assembleFromFiles(fileList, result);
@@ -362,7 +362,7 @@ bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, st
 	return result;
 }
 
-bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool validate)
+bool ContentTypeHandler::loadMod(std::string modName, bool validate)
 {
 	ModInfo & modInfo = modData[modName];
 	bool result = true;
@@ -387,42 +387,47 @@ bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool vali
 			// try to add H3 object data
 			size_t index = data["index"].Float();
 
-			if (originalData.size() > index)
+			if(originalData.size() > index)
 			{
 				logMod->trace("found original data in loadMod(%s) at index %d", name, index);
 				JsonUtils::merge(originalData[index], data);
-				performValidate(originalData[index],name);
-				handler->loadObject(modName, name, originalData[index], index);
+				std::swap(originalData[index], data);
 				originalData[index].clear(); // do not use same data twice (same ID)
 			}
 			else
 			{
-				logMod->debug("no original data in loadMod(%s) at index %d", name, index);
-				performValidate(data, name);
-				handler->loadObject(modName, name, data, index);
+				logMod->warn("no original data in loadMod(%s) at index %d", name, index);
 			}
-			continue;
+			performValidate(data, name);
+			handler->loadObject(modName, name, data, index);
+		}
+		else
+		{
+			// normal new object
+			logMod->trace("no index in loadMod(%s)", name);
+			performValidate(data,name);
+			handler->loadObject(modName, name, data);
 		}
-		// normal new object
-		logMod->trace("no index in loadMod(%s)", name);
-		performValidate(data,name);
-		handler->loadObject(modName, name, data);
 	}
 	return result;
 }
 
 
-void CContentHandler::ContentTypeHandler::loadCustom()
+void ContentTypeHandler::loadCustom()
 {
 	handler->loadCustom();
 }
 
-void CContentHandler::ContentTypeHandler::afterLoadFinalization()
+void ContentTypeHandler::afterLoadFinalization()
 {
 	handler->afterLoadFinalization();
 }
 
 CContentHandler::CContentHandler()
+{
+}
+
+void CContentHandler::init()
 {
  	handlers.insert(std::make_pair("heroClasses", ContentTypeHandler(&VLC->heroh->classes, "heroClass")));
 	handlers.insert(std::make_pair("artifacts", ContentTypeHandler(VLC->arth, "artifact")));
@@ -507,6 +512,11 @@ void CContentHandler::load(CModInfo & mod)
 		logMod->info("\t\t[SKIP] %s", mod.name);
 }
 
+const ContentTypeHandler & CContentHandler::operator[](const std::string & name) const
+{
+	return handlers.at(name);
+}
+
 static JsonNode loadModSettings(std::string path)
 {
 	if (CResourceHandler::get("local")->existsResource(ResourceID(path)))
@@ -810,31 +820,42 @@ std::vector<std::string> CModHandler::getModList(std::string path)
 
 void CModHandler::loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods)
 {
-	for (std::string modName : getModList(path))
-	{
-		boost::to_lower(modName);
-		std::string modFullName = parent.empty() ? modName : parent + '.' + modName;
+	for(std::string modName : getModList(path))
+		loadOneMod(modName, parent, modSettings, enableMods);
+}
 
-		if (CResourceHandler::get("initial")->existsResource(ResourceID(CModInfo::getModFile(modFullName))))
-		{
-			CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName))));
-			if (!parent.empty()) // this is submod, add parent to dependecies
-				mod.dependencies.insert(parent);
+void CModHandler::loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods)
+{
+	boost::to_lower(modName);
+	std::string modFullName = parent.empty() ? modName : parent + '.' + modName;
 
-			allMods[modFullName] = mod;
-			if (mod.enabled && enableMods)
-				activeMods.push_back(modFullName);
+	if(CResourceHandler::get("initial")->existsResource(ResourceID(CModInfo::getModFile(modFullName))))
+	{
+		CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName))));
+		if (!parent.empty()) // this is submod, add parent to dependencies
+			mod.dependencies.insert(parent);
 
-			loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.enabled);
-		}
+		allMods[modFullName] = mod;
+		if (mod.enabled && enableMods)
+			activeMods.push_back(modFullName);
+
+		loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.enabled);
 	}
 }
 
-void CModHandler::loadMods()
+void CModHandler::loadMods(bool onlyEssential)
 {
-	const JsonNode modConfig = loadModSettings("config/modSettings.json");
+	JsonNode modConfig;
 
-	loadMods("", "", modConfig["activeMods"], true);
+	if(onlyEssential)
+	{
+		loadOneMod("vcmi", "", modConfig, true);//only vcmi and submods
+	}
+	else
+	{
+		modConfig = loadModSettings("config/modSettings.json");
+		loadMods("", "", modConfig["activeMods"], true);
+	}
 
 	coreMod = CModInfo("core", modConfig["core"], JsonNode(ResourceID("config/gameConfig.json")));
 	coreMod.name = "Original game files";
@@ -941,9 +962,10 @@ void CModHandler::load()
 {
 	CStopWatch totalTime, timer;
 
-	CContentHandler content;
 	logMod->info("\tInitializing content handler: %d ms", timer.getDiff());
 
+	content.init();
+
 	for(const TModID & modName : activeMods)
 	{
 		logMod->trace("Generating checksum for %s", modName);
@@ -976,7 +998,7 @@ void CModHandler::load()
 	logMod->info("\tAll game content loaded in %d ms", totalTime.getDiff());
 }
 
-void CModHandler::afterLoad()
+void CModHandler::afterLoad(bool onlyEssential)
 {
 	JsonNode modSettings;
 	for (auto & modEntry : allMods)
@@ -987,8 +1009,12 @@ void CModHandler::afterLoad()
 	}
 	modSettings["core"] = coreMod.saveLocalData();
 
-	FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::out | std::ofstream::trunc);
-	file << modSettings.toJson();
+	if(!onlyEssential)
+	{
+		FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::out | std::ofstream::trunc);
+		file << modSettings.toJson();
+	}
+
 }
 
 std::string CModHandler::normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier)
@@ -1026,10 +1052,27 @@ void CModHandler::parseIdentifier(const std::string & fullIdentifier, std::strin
 
 std::string CModHandler::makeFullIdentifier(const std::string & scope, const std::string & type, const std::string & identifier)
 {
-	auto p = splitString(identifier, ':');
+	if(type == "")
+		logGlobal->error("Full identifier (%s %s) requires type name", scope, identifier);
+
+	std::string actualScope = scope;
+	std::string actualName = identifier;
 
-	if(p.first != "")
-		return p.first + ":" + type + "." + p.second;//ignore type if identifier is scoped
+	//ignore scope if identifier is scoped
+	auto scopeAndName = splitString(identifier, ':');
+
+	if(scopeAndName.first != "")
+	{
+		actualScope = scopeAndName.first;
+		actualName = scopeAndName.second;
+	}
+
+	if(actualScope == "")
+	{
+		return actualName == "" ? type : type + "." + actualName;
+	}
 	else
-		return scope == "" ? (identifier == "" ? type : type + "." + identifier) : scope + ":" + type + "." + identifier;
+	{
+		return actualName == "" ? actualScope+ ":" + type : actualScope + ":" + type + "." + actualName;
+	}
 }

+ 38 - 34
lib/CModHandler.h

@@ -108,40 +108,38 @@ public:
 	}
 };
 
-/// class used to load all game data into handlers. Used only during loading
-class CContentHandler
+/// internal type to handle loading of one data type (e.g. artifacts, creatures)
+class DLL_LINKAGE ContentTypeHandler
 {
-	/// internal type to handle loading of one data type (e.g. artifacts, creatures)
-	class ContentTypeHandler
+public:
+	struct ModInfo
 	{
-		struct ModInfo
-		{
-			/// mod data from this mod and for this mod
-			JsonNode modData;
-			/// mod data for this mod from other mods (patches)
-			JsonNode patches;
-		};
-
-		/// handler to which all data will be loaded
-		IHandlerBase * handler;
-
-		std::string objectName;
-
-		/// contains all loaded H3 data
-		std::vector<JsonNode> originalData;
-		std::map<std::string, ModInfo> modData;
-
-	public:
-		ContentTypeHandler(IHandlerBase * handler, std::string objectName);
-
-		/// local version of methods in ContentHandler
-		/// returns true if loading was successful
-		bool preloadModData(std::string modName, std::vector<std::string> fileList, bool validate);
-		bool loadMod(std::string modName, bool validate);
-		void loadCustom();
-		void afterLoadFinalization();
+		/// mod data from this mod and for this mod
+		JsonNode modData;
+		/// mod data for this mod from other mods (patches)
+		JsonNode patches;
 	};
+	/// handler to which all data will be loaded
+	IHandlerBase * handler;
+	std::string objectName;
+
+	/// contains all loaded H3 data
+	std::vector<JsonNode> originalData;
+	std::map<std::string, ModInfo> modData;
 
+	ContentTypeHandler(IHandlerBase * handler, std::string objectName);
+
+	/// local version of methods in ContentHandler
+	/// returns true if loading was successful
+	bool preloadModData(std::string modName, std::vector<std::string> fileList, bool validate);
+	bool loadMod(std::string modName, bool validate);
+	void loadCustom();
+	void afterLoadFinalization();
+};
+
+/// class used to load all game data into handlers. Used only during loading
+class DLL_LINKAGE CContentHandler
+{
 	/// preloads all data from fileList as data from modName.
 	bool preloadModData(std::string modName, JsonNode modConfig, bool validate);
 
@@ -150,9 +148,10 @@ class CContentHandler
 
 	std::map<std::string, ContentTypeHandler> handlers;
 public:
-	/// fully initialize object. Will cause reading of H3 config files
 	CContentHandler();
 
+	void init();
+
 	/// preloads all data from fileList as data from modName.
 	void preloadData(CModInfo & mod);
 
@@ -163,6 +162,8 @@ public:
 
 	/// all data was loaded, time for final validation / integration
 	void afterLoadFinalization();
+
+	const ContentTypeHandler & operator[] (const std::string & name) const;
 };
 
 typedef std::string TModID;
@@ -246,14 +247,17 @@ class DLL_LINKAGE CModHandler
 	std::vector <TModID> resolveDependencies(std::vector<TModID> input) const;
 
 	std::vector<std::string> getModList(std::string path);
-	void loadMods(std::string path, std::string namePrefix, const JsonNode & modSettings, bool enableMods);
+	void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods);
+	void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods);
 public:
 
 	CIdentifierStorage identifiers;
 
+	CContentHandler content; //(!)Do not serialize
+
 	/// receives list of available mods and trying to load mod.json from all of them
 	void initializeConfig();
-	void loadMods();
+	void loadMods(bool onlyEssential = false);
 	void loadModFilesystems();
 
 	CModInfo & getModData(TModID modId);
@@ -264,7 +268,7 @@ public:
 
 	/// load content from all available mods
 	void load();
-	void afterLoad();
+	void afterLoad(bool onlyEssential);
 
 	struct DLL_LINKAGE hardcodedFeatures
 	{

+ 7 - 2
lib/CRandomGenerator.cpp

@@ -32,7 +32,12 @@ void CRandomGenerator::resetSeed()
 
 TRandI CRandomGenerator::getIntRange(int lower, int upper)
 {
-    return std::bind(TIntDist(lower, upper), std::ref(rand));
+	return std::bind(TIntDist(lower, upper), std::ref(rand));
+}
+
+vstd::TRandI64 CRandomGenerator::getInt64Range(int64_t lower, int64_t upper)
+{
+	return std::bind(TInt64Dist(lower, upper), std::ref(rand));
 }
 
 int CRandomGenerator::nextInt(int upper)
@@ -50,7 +55,7 @@ int CRandomGenerator::nextInt()
 	return TIntDist()(rand);
 }
 
-TRand CRandomGenerator::getDoubleRange(double lower, double upper)
+vstd::TRand CRandomGenerator::getDoubleRange(double lower, double upper)
 {
     return std::bind(TRealDist(lower, upper), std::ref(rand));
 }

+ 10 - 32
lib/CRandomGenerator.h

@@ -10,16 +10,18 @@
 
 #pragma once
 
+#include <vstd/RNG.h>
+
 typedef std::mt19937 TGenerator;
 typedef std::uniform_int_distribution<int> TIntDist;
+typedef std::uniform_int_distribution<int64_t> TInt64Dist;
 typedef std::uniform_real_distribution<double> TRealDist;
 typedef std::function<int()> TRandI;
-typedef std::function<double()> TRand;
 
 /// The random generator randomly generates integers and real numbers("doubles") between
 /// a given range. This is a header only class and mainly a wrapper for
 /// convenient usage of the standard random API. An instance of this RNG is not thread safe.
-class DLL_LINKAGE CRandomGenerator : boost::noncopyable
+class DLL_LINKAGE CRandomGenerator : public vstd::RNG, boost::noncopyable
 {
 public:
 	/// Seeds the generator by default with the product of the current time in milliseconds and the
@@ -36,22 +38,24 @@ public:
 	/// e.g.: auto a = gen.getIntRange(0,10); a(); a(); a();
 	/// requires: lower <= upper
 	TRandI getIntRange(int lower, int upper);
-	
+
+	vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override;
+
 	/// Generates an integer between 0 and upper.
 	/// requires: 0 <= upper
 	int nextInt(int upper);
 
 	/// requires: lower <= upper
 	int nextInt(int lower, int upper);
-	
+
 	/// Generates an integer between 0 and the maximum value it can hold.
 	int nextInt();
 
 	/// Generate several double/real numbers within the same range.
 	/// e.g.: auto a = gen.getDoubleRange(4.5,10.2); a(); a(); a();
 	/// requires: lower <= upper
-	TRand getDoubleRange(double lower, double upper);
-	
+	vstd::TRand getDoubleRange(double lower, double upper) override;
+
 	/// Generates a double between 0 and upper.
 	/// requires: 0 <= upper
 	double nextDouble(double upper);
@@ -94,29 +98,3 @@ public:
 	}
 };
 
-namespace RandomGeneratorUtil
-{
-	template<typename Container>
-	auto nextItem(const Container & container, CRandomGenerator & rand) -> decltype(std::begin(container))
-	{
-		assert(!container.empty());
-		return std::next(container.begin(), rand.nextInt(container.size() - 1));
-	}
-
-	template<typename Container>
-	auto nextItem(Container & container, CRandomGenerator & rand) -> decltype(std::begin(container))
-	{
-		assert(!container.empty());
-		return std::next(container.begin(), rand.nextInt(container.size() - 1));
-	}
-
-	template<typename T>
-	void randomShuffle(std::vector<T>& container, CRandomGenerator & rand)
-	{
-		int n = (container.end() - container.begin());
-		for (int i = n-1; i>0; --i)
-		{
-			std::swap (container.begin()[i],container.begin()[rand.nextInt(i)]);
-		}
-	}
-}

+ 1 - 5
lib/CSkillHandler.cpp

@@ -22,10 +22,6 @@
 #include "CModHandler.h"
 #include "StringConstants.h"
 
-#include "CStack.h"
-#include "battle/BattleInfo.h"
-#include "battle/CBattleInfoCallback.h"
-
 ///CSkill
 CSkill::LevelInfo::LevelInfo()
 {
@@ -158,7 +154,7 @@ const std::string & CSkillHandler::skillName(int skill) const
 	return objects[skill]->name;
 }
 
-CSkill * CSkillHandler::loadFromJson(const JsonNode & json, const std::string & identifier)
+CSkill * CSkillHandler::loadFromJson(const JsonNode & json, const std::string & identifier, size_t index)
 {
 	CSkill * skill = nullptr;
 

+ 1 - 1
lib/CSkillHandler.h

@@ -83,5 +83,5 @@ public:
 	}
 
 protected:
-	CSkill * loadFromJson(const JsonNode & json, const std::string & identifier) override;
+	CSkill * loadFromJson(const JsonNode & json, const std::string & identifier, size_t index) override;
 };

+ 130 - 666
lib/CStack.cpp

@@ -9,318 +9,58 @@
  */
 #include "StdInc.h"
 #include "CStack.h"
+
+#include <vstd/RNG.h>
+
 #include "CGeneralTextHandler.h"
 #include "battle/BattleInfo.h"
 #include "spells/CSpellHandler.h"
-#include "CRandomGenerator.h"
 #include "NetPacks.h"
 
 
-///CAmmo
-CAmmo::CAmmo(const CStack * Owner, CSelector totalSelector):
-	CStackResource(Owner), totalProxy(Owner, totalSelector)
-{
-
-}
-
-int32_t CAmmo::available() const
-{
-	return total() - used;
-}
-
-bool CAmmo::canUse(int32_t amount) const
-{
-	return available() - amount >= 0;
-}
-
-void CAmmo::reset()
-{
-	used = 0;
-}
-
-int32_t CAmmo::total() const
-{
-	return totalProxy->totalValue();
-}
-
-void CAmmo::use(int32_t amount)
-{
-	if(available() - amount < 0)
-	{
-		logGlobal->error("Stack ammo overuse");
-		used += available();
-	}
-	else
-		used += amount;
-}
-
-///CShots
-CShots::CShots(const CStack * Owner):
-	CAmmo(Owner, Selector::type(Bonus::SHOTS))
-{
-
-}
-
-void CShots::use(int32_t amount)
-{
-	//don't remove ammo if we control a working ammo cart
-	bool hasAmmoCart = false;
-
-	for(const CStack * st : owner->battle->stacks)
-	{
-		if(owner->battle->battleMatchOwner(st, owner, true) && st->getCreature()->idNumber == CreatureID::AMMO_CART && st->alive())
-		{
-			hasAmmoCart = true;
-			break;
-		}
-	}
-
-	if(!hasAmmoCart)
-		CAmmo::use(amount);
-}
-
-///CCasts
-CCasts::CCasts(const CStack * Owner):
-	CAmmo(Owner, Selector::type(Bonus::CASTS))
-{
-
-}
-
-///CRetaliations
-CRetaliations::CRetaliations(const CStack * Owner):
-	CAmmo(Owner, Selector::type(Bonus::ADDITIONAL_RETALIATION)), totalCache(0)
-{
-
-}
-
-int32_t CRetaliations::total() const
-{
-	//after dispell bonus should remain during current round
-	int32_t val = 1 + totalProxy->totalValue();
-	vstd::amax(totalCache, val);
-	return totalCache;
-}
-
-void CRetaliations::reset()
-{
-	CAmmo::reset();
-	totalCache = 0;
-}
-
-///CHealth
-CHealth::CHealth(const IUnitHealthInfo * Owner):
-	owner(Owner)
-{
-	reset();
-}
-
-CHealth::CHealth(const CHealth & other):
-	owner(other.owner),
-	firstHPleft(other.firstHPleft),
-	fullUnits(other.fullUnits),
-	resurrected(other.resurrected)
-{
-
-}
-
-void CHealth::init()
-{
-	reset();
-	fullUnits = owner->unitBaseAmount() > 1 ? owner->unitBaseAmount() - 1 : 0;
-	firstHPleft = owner->unitBaseAmount() > 0 ? owner->unitMaxHealth() : 0;
-}
-
-void CHealth::addResurrected(int32_t amount)
-{
-	resurrected += amount;
-	vstd::amax(resurrected, 0);
-}
-
-int64_t CHealth::available() const
-{
-	return static_cast<int64_t>(firstHPleft) + owner->unitMaxHealth() * fullUnits;
-}
-
-int64_t CHealth::total() const
-{
-	return static_cast<int64_t>(owner->unitMaxHealth()) * owner->unitBaseAmount();
-}
-
-void CHealth::damage(int32_t & amount)
-{
-	const int32_t oldCount = getCount();
-
-	const bool withKills = amount >= firstHPleft;
-
-	if(withKills)
-	{
-		int64_t totalHealth = available();
-		if(amount > totalHealth)
-			amount = totalHealth;
-		totalHealth -= amount;
-		if(totalHealth <= 0)
-		{
-			fullUnits = 0;
-			firstHPleft = 0;
-		}
-		else
-		{
-			setFromTotal(totalHealth);
-		}
-	}
-	else
-	{
-		firstHPleft -= amount;
-	}
-
-	addResurrected(getCount() - oldCount);
-}
-
-void CHealth::heal(int32_t & amount, EHealLevel level, EHealPower power)
-{
-	const int32_t unitHealth = owner->unitMaxHealth();
-	const int32_t oldCount = getCount();
-
-	int32_t maxHeal = std::numeric_limits<int32_t>::max();
-
-	switch(level)
-	{
-	case EHealLevel::HEAL:
-		maxHeal = std::max(0, unitHealth - firstHPleft);
-		break;
-	case EHealLevel::RESURRECT:
-		maxHeal = total() - available();
-		break;
-	default:
-		assert(level == EHealLevel::OVERHEAL);
-		break;
-	}
-
-	vstd::amax(maxHeal, 0);
-	vstd::abetween(amount, 0, maxHeal);
-
-	if(amount == 0)
-		return;
-
-	int64_t availableHealth = available();
-
-	availableHealth	+= amount;
-	setFromTotal(availableHealth);
-
-	if(power == EHealPower::ONE_BATTLE)
-		addResurrected(getCount() - oldCount);
-	else
-		assert(power == EHealPower::PERMANENT);
-}
-
-void CHealth::setFromTotal(const int64_t totalHealth)
-{
-	const int32_t unitHealth = owner->unitMaxHealth();
-	firstHPleft = totalHealth % unitHealth;
-	fullUnits = totalHealth / unitHealth;
-
-	if(firstHPleft == 0 && fullUnits >= 1)
-	{
-		firstHPleft = unitHealth;
-		fullUnits -= 1;
-	}
-}
-
-void CHealth::reset()
-{
-	fullUnits = 0;
-	firstHPleft = 0;
-	resurrected = 0;
-}
-
-int32_t CHealth::getCount() const
-{
-	return fullUnits + (firstHPleft > 0 ? 1 : 0);
-}
-
-int32_t CHealth::getFirstHPleft() const
-{
-	return firstHPleft;
-}
-
-int32_t CHealth::getResurrected() const
-{
-	return resurrected;
-}
-
-void CHealth::fromInfo(const CHealthInfo & info)
-{
-	firstHPleft = info.firstHPleft;
-	fullUnits = info.fullUnits;
-	resurrected = info.resurrected;
-}
-
-void CHealth::toInfo(CHealthInfo & info) const
-{
-	info.firstHPleft = firstHPleft;
-	info.fullUnits = fullUnits;
-	info.resurrected = resurrected;
-}
-
-void CHealth::takeResurrected()
-{
-	if(resurrected != 0)
-	{
-		int64_t totalHealth = available();
-
-		totalHealth -= resurrected * owner->unitMaxHealth();
-		vstd::amax(totalHealth, 0);
-		setFromTotal(totalHealth);
-		resurrected = 0;
-	}
-}
-
 ///CStack
-CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, SlotID S):
-	base(Base), ID(I), owner(O), slot(S), side(Side),
-	counterAttacks(this), shots(this), casts(this), health(this), cloneID(-1),
-	position()
+CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, SlotID S)
+	: CBonusSystemNode(STACK_BATTLE),
+	CUnitState(),
+	base(Base),
+	ID(I),
+	type(Base->type),
+	baseAmount(base->count),
+	owner(O),
+	slot(S),
+	side(Side),
+	initialPosition()
 {
-	assert(base);
-	type = base->type;
-	baseAmount = base->count;
 	health.init(); //???
-	setNodeType(STACK_BATTLE);
 }
 
-CStack::CStack():
-	counterAttacks(this), shots(this), casts(this), health(this)
+CStack::CStack()
+	: CBonusSystemNode(STACK_BATTLE),
+	CUnitState()
 {
-	init();
-	setNodeType(STACK_BATTLE);
+	base = nullptr;
+	type = nullptr;
+	ID = -1;
+	baseAmount = -1;
+	owner = PlayerColor::NEUTRAL;
+	slot = SlotID(255);
+	side = 1;
+	initialPosition = BattleHex();
 }
 
-CStack::CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S):
-	base(nullptr), ID(I), owner(O), slot(S), side(Side),
-	counterAttacks(this), shots(this), casts(this), health(this), cloneID(-1),
-	position()
+CStack::CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S)
+	: CBonusSystemNode(STACK_BATTLE),
+	CUnitState(),
+	base(nullptr),
+	ID(I),
+	type(stack->type),
+	baseAmount(stack->count),
+	owner(O),
+	slot(S),
+	side(Side),
+	initialPosition()
 {
-	type = stack->type;
-	baseAmount = stack->count;
 	health.init(); //???
-	setNodeType(STACK_BATTLE);
-}
-
-int32_t CStack::getKilled() const
-{
-	int32_t res = baseAmount - health.getCount() + health.getResurrected();
-	vstd::amax(res, 0);
-	return res;
-}
-
-int32_t CStack::getCount() const
-{
-	return health.getCount();
-}
-
-int32_t CStack::getFirstHPleft() const
-{
-	return health.getFirstHPleft();
 }
 
 const CCreature * CStack::getCreature() const
@@ -328,23 +68,9 @@ const CCreature * CStack::getCreature() const
 	return type;
 }
 
-void CStack::init()
-{
-	base = nullptr;
-	type = nullptr;
-	ID = -1;
-	baseAmount = -1;
-	owner = PlayerColor::NEUTRAL;
-	slot = SlotID(255);
-	side = 1;
-	position = BattleHex();
-	cloneID = -1;
-}
-
 void CStack::localInit(BattleInfo * battleInfo)
 {
 	battle = battleInfo;
-	cloneID = -1;
 	assert(type);
 
 	exportBonuses();
@@ -359,183 +85,33 @@ void CStack::localInit(BattleInfo * battleInfo)
 		attachTo(const_cast<CCreature *>(type));
 	}
 
-	shots.reset();
-	counterAttacks.reset();
-	casts.reset();
-	health.init();
+	CUnitState::localInit(this);
+	position = initialPosition;
 }
 
 ui32 CStack::level() const
 {
 	if(base)
-		return base->getLevel(); //creatture or commander
+		return base->getLevel(); //creature or commander
 	else
 		return std::max(1, (int)getCreature()->level); //war machine, clone etc
 }
 
 si32 CStack::magicResistance() const
 {
-	si32 magicResistance;
-	if(base)  //TODO: make war machines receive aura of magic resistance
-	{
-		magicResistance = base->magicResistance();
-		int auraBonus = 0;
-		for(const CStack * stack : base->armyObj->battle-> batteAdjacentCreatures(this))
-		{
-			if(stack->owner == owner)
-			{
-				vstd::amax(auraBonus, stack->valOfBonuses(Bonus::SPELL_RESISTANCE_AURA)); //max value
-			}
-		}
-		magicResistance += auraBonus;
-		vstd::amin(magicResistance, 100);
-	}
-	else
-		magicResistance = type->magicResistance();
-	return magicResistance;
-}
-
-bool CStack::willMove(int turn) const
-{
-	return (turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING))
-		   && !moved(turn)
-		   && canMove(turn);
-}
-
-bool CStack::canMove(int turn) const
-{
-	return alive()
-		   && !hasBonus(Selector::type(Bonus::NOT_ACTIVE).And(Selector::turns(turn))); //eg. Ammo Cart or blinded creature
-}
-
-bool CStack::canCast() const
-{
-	return casts.canUse(1);//do not check specific cast abilities here
-}
-
-bool CStack::isCaster() const
-{
-	return casts.total() > 0;//do not check specific cast abilities here
-}
-
-bool CStack::canShoot() const
-{
-	return shots.canUse(1) && hasBonusOfType(Bonus::SHOOTER);
-}
-
-bool CStack::isShooter() const
-{
-	return shots.total() > 0 && hasBonusOfType(Bonus::SHOOTER);
-}
-
-bool CStack::moved(int turn) const
-{
-	if(!turn)
-		return vstd::contains(state, EBattleStackState::MOVED);
-	else
-		return false;
-}
-
-bool CStack::waited(int turn) const
-{
-	if(!turn)
-		return vstd::contains(state, EBattleStackState::WAITING);
-	else
-		return false;
-}
-
-bool CStack::doubleWide() const
-{
-	return getCreature()->doubleWide;
-}
-
-BattleHex CStack::occupiedHex() const
-{
-	return occupiedHex(position);
-}
-
-BattleHex CStack::occupiedHex(BattleHex assumedPos) const
-{
-	if(doubleWide())
-	{
-		if(side == BattleSide::ATTACKER)
-			return assumedPos - 1;
-		else
-			return assumedPos + 1;
-	}
-	else
-	{
-		return BattleHex::INVALID;
-	}
-}
-
-std::vector<BattleHex> CStack::getHexes() const
-{
-	return getHexes(position);
-}
-
-std::vector<BattleHex> CStack::getHexes(BattleHex assumedPos) const
-{
-	return getHexes(assumedPos, doubleWide(), side);
-}
+	si32 magicResistance = IBonusBearer::magicResistance();
 
-std::vector<BattleHex> CStack::getHexes(BattleHex assumedPos, bool twoHex, ui8 side)
-{
-	std::vector<BattleHex> hexes;
-	hexes.push_back(assumedPos);
+	si32 auraBonus = 0;
 
-	if(twoHex)
+	for(auto one : battle->battleAdjacentUnits(this))
 	{
-		if(side == BattleSide::ATTACKER)
-			hexes.push_back(assumedPos - 1);
-		else
-			hexes.push_back(assumedPos + 1);
+		if(one->unitOwner() == owner)
+			vstd::amax(auraBonus, one->valOfBonuses(Bonus::SPELL_RESISTANCE_AURA)); //max value
 	}
+	magicResistance += auraBonus;
+	vstd::amin(magicResistance, 100);
 
-	return hexes;
-}
-
-bool CStack::coversPos(BattleHex pos) const
-{
-	return vstd::contains(getHexes(), pos);
-}
-
-std::vector<BattleHex> CStack::getSurroundingHexes(BattleHex attackerPos) const
-{
-	BattleHex hex = (attackerPos != BattleHex::INVALID) ? attackerPos : position; //use hypothetical position
-	std::vector<BattleHex> hexes;
-	if(doubleWide())
-	{
-		const int WN = GameConstants::BFIELD_WIDTH;
-		if(side == BattleSide::ATTACKER)
-		{
-			//position is equal to front hex
-			BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 2 : WN + 1), hexes);
-			BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 1 : WN), hexes);
-			BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN : WN - 1), hexes);
-			BattleHex::checkAndPush(hex - 2, hexes);
-			BattleHex::checkAndPush(hex + 1, hexes);
-			BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 2 : WN - 1), hexes);
-			BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 1 : WN), hexes);
-			BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN : WN + 1), hexes);
-		}
-		else
-		{
-			BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 1 : WN), hexes);
-			BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN : WN - 1), hexes);
-			BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN - 1 : WN - 2), hexes);
-			BattleHex::checkAndPush(hex + 2, hexes);
-			BattleHex::checkAndPush(hex - 1, hexes);
-			BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 1 : WN), hexes);
-			BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN : WN + 1), hexes);
-			BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN + 1 : WN + 2), hexes);
-		}
-		return hexes;
-	}
-	else
-	{
-		return hex.neighbouringTiles();
-	}
+	return magicResistance;
 }
 
 BattleHex::EDir CStack::destShiftDir() const
@@ -595,7 +171,8 @@ const CGHeroInstance * CStack::getMyHero() const
 std::string CStack::nodeName() const
 {
 	std::ostringstream oss;
-	oss << "Battle stack [" << ID << "]: " << health.getCount() << " creatures of ";
+	oss << owner.getStr();
+	oss << " battle stack [" << ID << "]: " << getCount() << " of ";
 	if(type)
 		oss << type->namePl;
 	else
@@ -607,156 +184,95 @@ std::string CStack::nodeName() const
 	return oss.str();
 }
 
-CHealth CStack::healthAfterAttacked(int32_t & damage) const
+void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand) const
 {
-	return healthAfterAttacked(damage, health);
+	auto newState = acquireState();
+	prepareAttacked(bsa, rand, newState);
 }
 
-CHealth CStack::healthAfterAttacked(int32_t & damage, const CHealth & customHealth) const
+void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, std::shared_ptr<battle::CUnitState> customState)
 {
-	CHealth res = customHealth;
+	auto initialCount = customState->getCount();
+
+	customState->damage(bsa.damageAmount);
 
-	if(isClone())
+	bsa.killedAmount = initialCount - customState->getCount();
+
+	if(!customState->alive() && customState->isClone())
 	{
-		// block ability should not kill clone (0 damage)
-		if(damage > 0)
-		{
-			damage = 1;//??? what should be actual damage against clone?
-			res.reset();
-		}
+		bsa.flags |= BattleStackAttacked::CLONE_KILLED;
 	}
-	else
+	else if(!customState->alive()) //stack killed
 	{
-		res.damage(damage);
-	}
-
-	return res;
-}
-
-CHealth CStack::healthAfterHealed(int32_t & toHeal, EHealLevel level, EHealPower power) const
-{
-	CHealth res = health;
-
-	if(level == EHealLevel::HEAL && power == EHealPower::ONE_BATTLE)
-		logGlobal->error("Heal for one battle does not make sense", nodeName(), toHeal);
-	else if(isClone())
-		logGlobal->error("Attempt to heal clone: %s for %d HP", nodeName(), toHeal);
-	else
-		res.heal(toHeal, level, power);
+		bsa.flags |= BattleStackAttacked::KILLED;
 
-	return res;
-}
+		auto resurrectValue = customState->valOfBonuses(Bonus::REBIRTH);
 
-void CStack::prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand) const
-{
-	prepareAttacked(bsa, rand, health);
-}
+		if(resurrectValue > 0 && customState->canCast()) //there must be casts left
+		{
+			double resurrectFactor = resurrectValue / 100;
 
-void CStack::prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand, const CHealth & customHealth) const
-{
-	CHealth afterAttack = healthAfterAttacked(bsa.damageAmount, customHealth);
+			auto baseAmount = customState->unitBaseAmount();
 
-	bsa.killedAmount = customHealth.getCount() - afterAttack.getCount();
-	afterAttack.toInfo(bsa.newHealth);
-	bsa.newHealth.stackId = ID;
-	bsa.newHealth.delta = -bsa.damageAmount;
+			double resurrectedRaw = baseAmount * resurrectFactor;
 
-	if(afterAttack.available() <= 0 && isClone())
-	{
-		bsa.flags |= BattleStackAttacked::CLONE_KILLED;
-		return; // no rebirth I believe
-	}
+			int32_t resurrectedCount = static_cast<int32_t>(floor(resurrectedRaw));
 
-	if(afterAttack.available() <= 0) //stack killed
-	{
-		bsa.flags |= BattleStackAttacked::KILLED;
+			int32_t resurrectedAdd = static_cast<int32_t>(baseAmount - (resurrectedCount/resurrectFactor));
 
-		int resurrectFactor = valOfBonuses(Bonus::REBIRTH);
-		if(resurrectFactor > 0 && canCast()) //there must be casts left
-		{
-			int resurrectedStackCount = baseAmount * resurrectFactor / 100;
+			auto rangeGen = rand.getInt64Range(0, 99);
 
-			// last stack has proportional chance to rebirth
-			//FIXME: diff is always 0
-			auto diff = baseAmount * resurrectFactor / 100.0 - resurrectedStackCount;
-			if(diff > rand.nextDouble(0, 0.99))
+			for(int32_t i = 0; i < resurrectedAdd; i++)
 			{
-				resurrectedStackCount += 1;
+				if(resurrectValue > rangeGen())
+					resurrectedCount += 1;
 			}
 
-			if(hasBonusOfType(Bonus::REBIRTH, 1))
+			if(customState->hasBonusOfType(Bonus::REBIRTH, 1))
 			{
 				// resurrect at least one Sacred Phoenix
-				vstd::amax(resurrectedStackCount, 1);
+				vstd::amax(resurrectedCount, 1);
 			}
 
-			if(resurrectedStackCount > 0)
+			if(resurrectedCount > 0)
 			{
+				customState->casts.use();
 				bsa.flags |= BattleStackAttacked::REBIRTH;
-				//TODO: use StackHealedOrResurrected
-				bsa.newHealth.firstHPleft = MaxHealth();
-				bsa.newHealth.fullUnits = resurrectedStackCount - 1;
-				bsa.newHealth.resurrected = 0; //TODO: add one-battle rebirth?
+				int64_t toHeal = customState->MaxHealth() * resurrectedCount;
+				//TODO: add one-battle rebirth?
+				customState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
+				customState->counterAttacks.use(customState->counterAttacks.available());
 			}
 		}
 	}
+
+	customState->save(bsa.newState.data);
+	bsa.newState.healthDelta = -bsa.damageAmount;
+	bsa.newState.id = customState->unitId();
+	bsa.newState.operation = UnitChanges::EOperation::RESET_STATE;
 }
 
-bool CStack::isMeleeAttackPossible(const CStack * attacker, const CStack * defender, BattleHex attackerPos, BattleHex defenderPos)
+bool CStack::isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos)
 {
 	if(!attackerPos.isValid())
-		attackerPos = attacker->position;
+		attackerPos = attacker->getPosition();
 	if(!defenderPos.isValid())
-		defenderPos = defender->position;
+		defenderPos = defender->getPosition();
 
 	return
 		(BattleHex::mutualPosition(attackerPos, defenderPos) >= 0)//front <=> front
 		|| (attacker->doubleWide()//back <=> front
-			&& BattleHex::mutualPosition(attackerPos + (attacker->side == BattleSide::ATTACKER ? -1 : 1), defenderPos) >= 0)
+			&& BattleHex::mutualPosition(attackerPos + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1), defenderPos) >= 0)
 		|| (defender->doubleWide()//front <=> back
-			&& BattleHex::mutualPosition(attackerPos, defenderPos + (defender->side == BattleSide::ATTACKER ? -1 : 1)) >= 0)
+			&& BattleHex::mutualPosition(attackerPos, defenderPos + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1)) >= 0)
 		|| (defender->doubleWide() && attacker->doubleWide()//back <=> back
-			&& BattleHex::mutualPosition(attackerPos + (attacker->side == BattleSide::ATTACKER ? -1 : 1), defenderPos + (defender->side == BattleSide::ATTACKER ? -1 : 1)) >= 0);
-
-}
+			&& BattleHex::mutualPosition(attackerPos + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1), defenderPos + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1)) >= 0);
 
-bool CStack::ableToRetaliate() const
-{
-	return alive()
-		   && (counterAttacks.canUse() || hasBonusOfType(Bonus::UNLIMITED_RETALIATIONS))
-		   && !hasBonusOfType(Bonus::SIEGE_WEAPON)
-		   && !hasBonusOfType(Bonus::HYPNOTIZED)
-		   && !hasBonusOfType(Bonus::NO_RETALIATION);
 }
 
 std::string CStack::getName() const
 {
-	return (health.getCount() == 1) ? type->nameSing : type->namePl; //War machines can't use base
-}
-
-bool CStack::isValidTarget(bool allowDead) const
-{
-	return (alive() || (allowDead && isDead())) && position.isValid() && !isTurret();
-}
-
-bool CStack::isDead() const
-{
-	return !alive() && !isGhost();
-}
-
-bool CStack::isClone() const
-{
-	return vstd::contains(state, EBattleStackState::CLONED);
-}
-
-bool CStack::isGhost() const
-{
-	return vstd::contains(state, EBattleStackState::GHOST);
-}
-
-bool CStack::isTurret() const
-{
-	return type->idNumber == CreatureID::ARROW_TOWERS;
+	return (getCount() == 1) ? type->nameSing : type->namePl; //War machines can't use base
 }
 
 bool CStack::canBeHealed() const
@@ -766,122 +282,70 @@ bool CStack::canBeHealed() const
 		   && !hasBonusOfType(Bonus::SIEGE_WEAPON);
 }
 
-void CStack::makeGhost()
-{
-	state.erase(EBattleStackState::ALIVE);
-	state.insert(EBattleStackState::GHOST_PENDING);
-}
-
-bool CStack::alive() const //determines if stack is alive
-{
-	return vstd::contains(state, EBattleStackState::ALIVE);
-}
-
-ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) const
-{
-	int skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->id));
-	vstd::abetween(skill, 0, 3);
-	return skill;
-}
-
-ui32 CStack::getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const
+const CCreature * CStack::unitType() const
 {
-	//stacks does not have sorcery-like bonuses (yet?)
-	return base;
-}
-
-int CStack::getEffectLevel(const CSpell * spell) const
-{
-	return getSpellSchoolLevel(spell);
-}
-
-int CStack::getEffectPower(const CSpell * spell) const
-{
-	return valOfBonuses(Bonus::CREATURE_SPELL_POWER) * health.getCount() / 100;
-}
-
-int CStack::getEnchantPower(const CSpell * spell) const
-{
-	int res = valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
-	if(res <= 0)
-		res = 3;//default for creatures
-	return res;
+	return type;
 }
 
-int CStack::getEffectValue(const CSpell * spell) const
+int32_t CStack::unitBaseAmount() const
 {
-	return valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spell->id.toEnum()) * health.getCount();
+	return baseAmount;
 }
 
-const PlayerColor CStack::getOwner() const
+bool CStack::unitHasAmmoCart(const battle::Unit * unit) const
 {
-	return battle->battleGetOwner(this);
-}
+	bool hasAmmoCart = false;
 
-void CStack::getCasterName(MetaString & text) const
-{
-	//always plural name in case of spell cast.
-	addNameReplacement(text, true);
+	for(const CStack * st : battle->stacks)
+	{
+		if(battle->battleMatchOwner(st, unit, true) && st->getCreature()->idNumber == CreatureID::AMMO_CART && st->alive())
+		{
+			hasAmmoCart = true;
+			break;
+		}
+	}
+	return hasAmmoCart;
 }
 
-void CStack::getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const
+PlayerColor CStack::unitEffectiveOwner(const battle::Unit * unit) const
 {
-	text.addTxt(MetaString::GENERAL_TXT, 565);//The %s casts %s
-	//todo: use text 566 for single creature
-	getCasterName(text);
-	text.addReplacement(MetaString::SPELL_NAME, spell->id.toEnum());
+	return battle->battleGetOwner(unit);
 }
 
-int32_t CStack::unitMaxHealth() const
+uint32_t CStack::unitId() const
 {
-	return MaxHealth();
+	return ID;
 }
 
-int32_t CStack::unitBaseAmount() const
+ui8 CStack::unitSide() const
 {
-	return baseAmount;
+	return side;
 }
 
-void CStack::addText(MetaString & text, ui8 type, int32_t serial, const boost::logic::tribool & plural) const
+PlayerColor CStack::unitOwner() const
 {
-	if(boost::logic::indeterminate(plural))
-		serial = VLC->generaltexth->pluralText(serial, health.getCount());
-	else if(plural)
-		serial = VLC->generaltexth->pluralText(serial, 2);
-	else
-		serial = VLC->generaltexth->pluralText(serial, 1);
-
-	text.addTxt(type, serial);
+	return owner;
 }
 
-void CStack::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const
+SlotID CStack::unitSlot() const
 {
-	if(boost::logic::indeterminate(plural))
-		text.addCreReplacement(type->idNumber, health.getCount());
-	else if(plural)
-		text.addReplacement(MetaString::CRE_PL_NAMES, type->idNumber.num);
-	else
-		text.addReplacement(MetaString::CRE_SING_NAMES, type->idNumber.num);
+	return slot;
 }
 
-std::string CStack::formatGeneralMessage(const int32_t baseTextId) const
+std::string CStack::getDescription() const
 {
-	const int32_t textId = VLC->generaltexth->pluralText(baseTextId, health.getCount());
-
-	MetaString text;
-	text.addTxt(MetaString::GENERAL_TXT, textId);
-	text.addCreReplacement(type->idNumber, health.getCount());
-
-	return text.toString();
+	return nodeName();
 }
 
-void CStack::setHealth(const CHealthInfo & value)
+void CStack::spendMana(const spells::PacketSender * server, const int spellCost) const
 {
-	health.reset();
-	health.fromInfo(value);
-}
+	if(spellCost != 1)
+		logGlobal->warn("Unexpected spell cost %d for creature", spellCost);
 
-void CStack::setHealth(const CHealth & value)
-{
-	health = value;
+	BattleSetStackProperty ssp;
+	ssp.stackID = unitId();
+	ssp.which = BattleSetStackProperty::CASTS;
+	ssp.val = -spellCost;
+	ssp.absolute = false;
+	server->sendAndApply(&ssp);
 }

+ 26 - 212
lib/CStack.h

@@ -9,179 +9,42 @@
  */
 
 #pragma once
+#include "JsonNode.h"
+#include "HeroBonus.h"
+#include "CCreatureHandler.h" //todo: remove
 #include "battle/BattleHex.h"
-#include "CCreatureHandler.h"
 #include "mapObjects/CGHeroInstance.h" // for commander serialization
 
-struct BattleStackAttacked;
-struct BattleInfo;
-class CStack;
-class CHealthInfo;
-
-template <typename Quantity>
-class DLL_LINKAGE CStackResource
-{
-public:
-	CStackResource(const CStack * Owner):
-		owner(Owner)
-	{
-		reset();
-	}
-
-	virtual void reset()
-	{
-		used = 0;
-	};
-
-protected:
-	const CStack * owner;
-	Quantity used;
-};
-
-class DLL_LINKAGE CAmmo : public CStackResource<int32_t>
-{
-public:
-	CAmmo(const CStack * Owner, CSelector totalSelector);
-
-	int32_t available() const;
-	bool canUse(int32_t amount = 1) const;
-	virtual void reset() override;
-	virtual int32_t total() const;
-	virtual void use(int32_t amount = 1);
-
-	template <typename Handler> void serialize(Handler & h, const int version)
-	{
-		if(!h.saving)
-			reset();
-		h & used;
-	}
-protected:
-	CBonusProxy totalProxy;
-};
-
-class DLL_LINKAGE CShots : public CAmmo
-{
-public:
-	CShots(const CStack * Owner);
-	void use(int32_t amount = 1) override;
-};
-
-class DLL_LINKAGE CCasts : public CAmmo
-{
-public:
-	CCasts(const CStack * Owner);
-};
-
-class DLL_LINKAGE CRetaliations : public CAmmo
-{
-public:
-	CRetaliations(const CStack * Owner);
-	int32_t total() const override;
-	void reset() override;
-private:
-	mutable int32_t totalCache;
-};
-
-class DLL_LINKAGE IUnitHealthInfo
-{
-public:
-	virtual int32_t unitMaxHealth() const = 0;
-	virtual int32_t unitBaseAmount() const = 0;
-};
-
-class DLL_LINKAGE CHealth
-{
-public:
-	CHealth(const IUnitHealthInfo * Owner);
-	CHealth(const CHealth & other);
-
-	void init();
-	void reset();
-
-	void damage(int32_t & amount);
-	void heal(int32_t & amount, EHealLevel level, EHealPower power);
-
-	int32_t getCount() const;
-	int32_t getFirstHPleft() const;
-	int32_t getResurrected() const;
+#include "battle/CUnitState.h"
 
-	int64_t available() const;
-	int64_t total() const;
-
-	void toInfo(CHealthInfo & info) const;
-	void fromInfo(const CHealthInfo & info);
-
-	void takeResurrected();
-
-	template <typename Handler> void serialize(Handler & h, const int version)
-	{
-		if(!h.saving)
-			reset();
-		h & firstHPleft;
-		h & fullUnits;
-		h & resurrected;
-	}
-private:
-	void addResurrected(int32_t amount);
-	void setFromTotal(const int64_t totalHealth);
-	const IUnitHealthInfo * owner;
-
-	int32_t firstHPleft;
-	int32_t fullUnits;
-	int32_t resurrected;
-};
+struct BattleStackAttacked;
+class BattleInfo;
 
-class DLL_LINKAGE CStack : public CBonusSystemNode, public ISpellCaster, public IUnitHealthInfo
+class DLL_LINKAGE CStack : public CBonusSystemNode, public battle::CUnitState, public battle::IUnitEnvironment
 {
 public:
 	const CStackInstance * base; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc)
 
 	ui32 ID; //unique ID of stack
-	ui32 baseAmount;
 	const CCreature * type;
+	ui32 baseAmount;
 
 	PlayerColor owner; //owner - player color (255 for neutrals)
 	SlotID slot;  //slot - position in garrison (may be 255 for neutrals/called creatures)
 	ui8 side;
-	BattleHex position; //position on battlefield; -2 - keep, -3 - lower tower, -4 - upper tower
-
-	CRetaliations counterAttacks;
-	CShots shots;
-	CCasts casts;
-	CHealth health;
-
-	///id of alive clone of this stack clone if any
-	si32 cloneID;
-	std::set<EBattleStackState::EBattleStackState> state;
+	BattleHex initialPosition; //position on battlefield; -2 - keep, -3 - lower tower, -4 - upper tower
 
 	CStack(const CStackInstance * base, PlayerColor O, int I, ui8 Side, SlotID S);
 	CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S = SlotID(255));
 	CStack();
 	~CStack();
 
-	int32_t getKilled() const;
-	int32_t getCount() const;
-	int32_t getFirstHPleft() const;
-	const CCreature * getCreature() const;
+	const CCreature * getCreature() const; //deprecated
 
 	std::string nodeName() const override;
 
-	void init(); //set initial (invalid) values
 	void localInit(BattleInfo * battleInfo);
 	std::string getName() const; //plural or singular
-	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 canCast() const;
-	bool isCaster() const;
-
-	bool canMove(int turn = 0) const; //if stack can move
-
-	bool canShoot() const;
-	bool isShooter() const;
 
 	bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
 
@@ -190,68 +53,32 @@ public:
 	std::vector<si32> activeSpells() const; //returns vector of active spell IDs sorted by time of cast
 	const CGHeroInstance * getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise
 
-	static bool isMeleeAttackPossible(const CStack * attacker, const CStack * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
-
-	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, ui8 side); //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
+	static bool isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
 
 	BattleHex::EDir destShiftDir() const;
 
-	CHealth healthAfterAttacked(int32_t & damage) const;
-	CHealth healthAfterAttacked(int32_t & damage, const CHealth & customHealth) const;
-
-	CHealth healthAfterHealed(int32_t & toHeal, EHealLevel level, EHealPower power) const;
-
-	void prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand) const; //requires bsa.damageAmout filled
-	void prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand, const CHealth & customHealth) const; //requires bsa.damageAmout filled
-
-	///ISpellCaster
-
-	ui8 getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool = nullptr) const override;
-	ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
+	void prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand) const; //requires bsa.damageAmout filled
+	static void prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, std::shared_ptr<battle::CUnitState> customState); //requires bsa.damageAmout filled
 
-	///default spell school level for effect calculation
-	int getEffectLevel(const CSpell * spell) const override;
-
-	///default spell-power for damage/heal calculation
-	int getEffectPower(const CSpell * spell) const override;
-
-	///default spell-power for timed effects duration
-	int getEnchantPower(const CSpell * spell) const override;
-
-	///damage/heal override(ignores spell configuration, effect level and effect power)
-	int getEffectValue(const CSpell * spell) const override;
-
-	const PlayerColor getOwner() const override;
-	void getCasterName(MetaString & text) const override;
-	void getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const override;
-
-	///IUnitHealthInfo
-
-	int32_t unitMaxHealth() const override;
+	const CCreature * unitType() const override;
 	int32_t unitBaseAmount() const override;
 
-	///MetaStrings
+	uint32_t unitId() const override;
+	ui8 unitSide() const override;
+	PlayerColor unitOwner() const override;
+	SlotID unitSlot() const override;
 
-	void addText(MetaString & text, ui8 type, int32_t serial, const boost::logic::tribool & plural = boost::logic::indeterminate) const;
-	void addNameReplacement(MetaString & text, const boost::logic::tribool & plural = boost::logic::indeterminate) const;
-	std::string formatGeneralMessage(const int32_t baseTextId) const;
+	std::string getDescription() const override;
 
-	///Non const API for NetPacks
+	bool unitHasAmmoCart(const battle::Unit * unit) const override;
+	PlayerColor unitEffectiveOwner(const battle::Unit * unit) const override;
 
-	///stack will be ghost in next battle state update
-	void makeGhost();
-	void setHealth(const CHealthInfo & value);
-	void setHealth(const CHealth & value);
+	void spendMana(const spells::PacketSender * server, const int spellCost) const override;
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
+		//this assumes that stack objects is newly created
+		//stackState is not serialized here
 		assert(isIndependentNode());
 		h & static_cast<CBonusSystemNode&>(*this);
 		h & type;
@@ -260,12 +87,7 @@ public:
 		h & owner;
 		h & slot;
 		h & side;
-		h & position;
-		h & state;
-		h & shots;
-		h & casts;
-		h & counterAttacks;
-		h & health;
+		h & initialPosition;
 
 		const CArmedInstance * army = (base ? base->armyObj : nullptr);
 		SlotID extSlot = (base ? base->armyObj->findStack(base) : SlotID());
@@ -279,6 +101,7 @@ public:
 		{
 			h & army;
 			h & extSlot;
+
 			if(extSlot == SlotID::COMMANDER_SLOT_PLACEHOLDER)
 			{
 				auto hero = dynamic_cast<const CGHeroInstance *>(army);
@@ -300,17 +123,8 @@ public:
 				base = &army->getStack(extSlot);
 			}
 		}
-
 	}
-	bool alive() const;
-
-	bool isClone() const;
-	bool isDead() const;
-	bool isGhost() const; //determines if stack was removed
-	bool isValidTarget(bool allowDead = false) const; //non-turret non-ghost stacks (can be attacked or be object of magic effect)
-	bool isTurret() const;
 
-	friend class CShots; //for BattleInfo access
 private:
 	const BattleInfo * battle; //do not serialize
 };

+ 2 - 4
lib/CThreadHelper.cpp

@@ -25,13 +25,11 @@ CThreadHelper::CThreadHelper(std::vector<std::function<void()> > *Tasks, int Thr
 void CThreadHelper::run()
 {
 	boost::thread_group grupa;
-	std::vector<boost::thread *> thr;
 	for(int i=0;i<threads;i++)
-		thr.push_back(grupa.create_thread(std::bind(&CThreadHelper::processTasks,this)));
+		grupa.create_thread(std::bind(&CThreadHelper::processTasks,this));
 	grupa.join_all();
 
-	for(auto thread : thr)
-		delete thread;
+	//thread group deletes threads, do not free manually
 }
 void CThreadHelper::processTasks()
 {

+ 61 - 18
lib/GameConstants.cpp

@@ -24,6 +24,7 @@
 #include "CSkillHandler.h"
 #include "StringConstants.h"
 #include "CGeneralTextHandler.h"
+#include "CModHandler.h"
 
 const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2);
 const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3);
@@ -51,11 +52,39 @@ const CArtifact * ArtifactID::toArtifact() const
 	return VLC->arth->artifacts.at(*this);
 }
 
+si32 ArtifactID::decode(const std::string & identifier)
+{
+	auto rawId = VLC->modh->identifiers.getIdentifier("core", "artifact", identifier);
+	if(rawId)
+		return rawId.get();
+	else
+		return -1;
+}
+
+std::string ArtifactID::encode(const si32 index)
+{
+	return VLC->arth->artifacts.at(index)->identifier;
+}
+
 const CCreature * CreatureID::toCreature() const
 {
 	return VLC->creh->creatures.at(*this);
 }
 
+si32 CreatureID::decode(const std::string & identifier)
+{
+	auto rawId = VLC->modh->identifiers.getIdentifier("core", "creature", identifier);
+	if(rawId)
+		return rawId.get();
+	else
+		return -1;
+}
+
+std::string CreatureID::encode(const si32 index)
+{
+	return VLC->creh->creatures.at(index)->identifier;
+}
+
 const CSpell * SpellID::toSpell() const
 {
 	if(num < 0 || num >= VLC->spellh->objects.size())
@@ -66,6 +95,20 @@ const CSpell * SpellID::toSpell() const
 	return VLC->spellh->objects[*this];
 }
 
+si32 SpellID::decode(const std::string & identifier)
+{
+	auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", identifier);
+	if(rawId)
+		return rawId.get();
+	else
+		return -1;
+}
+
+std::string SpellID::encode(const si32 index)
+{
+	return VLC->spellh->objects.at(index)->identifier;
+}
+
 const CSkill * SecondarySkill::toSkill() const
 {
 	return VLC->skillh->objects.at(*this);
@@ -110,26 +153,26 @@ std::string PlayerColor::getStrCap(bool L10n) const
 	return ret;
 }
 
-std::ostream & operator<<(std::ostream & os, const Battle::ActionType actionType)
+std::ostream & operator<<(std::ostream & os, const EActionType actionType)
 {
-	static const std::map<Battle::ActionType, std::string> actionTypeToString =
+	static const std::map<EActionType, std::string> actionTypeToString =
 	{
-		{Battle::END_TACTIC_PHASE, "End tactic phase"},
-		{Battle::INVALID, "Invalid"},
-		{Battle::NO_ACTION, "No action"},
-		{Battle::HERO_SPELL, "Hero spell"},
-		{Battle::WALK, "Walk"},
-		{Battle::DEFEND, "Defend"},
-		{Battle::RETREAT, "Retreat"},
-		{Battle::SURRENDER, "Surrender"},
-		{Battle::WALK_AND_ATTACK, "Walk and attack"},
-		{Battle::SHOOT, "Shoot"},
-		{Battle::WAIT, "Wait"},
-		{Battle::CATAPULT, "Catapult"},
-		{Battle::MONSTER_SPELL, "Monster spell"},
-		{Battle::BAD_MORALE, "Bad morale"},
-		{Battle::STACK_HEAL, "Stack heal"},
-		{Battle::DAEMON_SUMMONING, "Daemon summoning"}
+		{EActionType::END_TACTIC_PHASE, "End tactic phase"},
+		{EActionType::INVALID, "Invalid"},
+		{EActionType::NO_ACTION, "No action"},
+		{EActionType::HERO_SPELL, "Hero spell"},
+		{EActionType::WALK, "Walk"},
+		{EActionType::DEFEND, "Defend"},
+		{EActionType::RETREAT, "Retreat"},
+		{EActionType::SURRENDER, "Surrender"},
+		{EActionType::WALK_AND_ATTACK, "Walk and attack"},
+		{EActionType::SHOOT, "Shoot"},
+		{EActionType::WAIT, "Wait"},
+		{EActionType::CATAPULT, "Catapult"},
+		{EActionType::MONSTER_SPELL, "Monster spell"},
+		{EActionType::BAD_MORALE, "Bad morale"},
+		{EActionType::STACK_HEAL, "Stack heal"},
+		{EActionType::DAEMON_SUMMONING, "Daemon summoning"}
 	};
 
 	auto it = actionTypeToString.find(actionType);

+ 34 - 63
lib/GameConstants.h

@@ -15,10 +15,6 @@ namespace GameConstants
 {
 	DLL_LINKAGE extern const std::string VCMI_VERSION;
 
-	const int BFIELD_WIDTH = 17;
-	const int BFIELD_HEIGHT = 11;
-	const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT;
-
 	const int PUZZLE_MAP_PIECES = 48;
 
 	const int MAX_HEROES_PER_PLAYER = 8;
@@ -443,27 +439,15 @@ namespace ESpellCastProblem
 {
 	enum ESpellCastProblem
 	{
-		OK, NO_HERO_TO_CAST_SPELL, ALREADY_CASTED_THIS_TURN, NO_SPELLBOOK, ANOTHER_ELEMENTAL_SUMMONED,
+		OK, NO_HERO_TO_CAST_SPELL, CASTS_PER_TURN_LIMIT, NO_SPELLBOOK,
 		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,
+		SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL,
 		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
-		NOT_DECIDED,
 		INVALID
 	};
 }
 
-namespace ECastingMode
-{
-	enum ECastingMode
-	{
-		HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
-		MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING,
-		SPELL_LIKE_ATTACK,
-		PASSIVE_CASTING//f.e. opening battle spells
-	};
-}
-
 namespace EMarketMode
 {
 	enum EMarketMode
@@ -474,26 +458,6 @@ namespace EMarketMode
 	};
 }
 
-namespace EBattleStackState
-{
-	enum EBattleStackState
-	{
-		ALIVE = 180,
-		SUMMONED, CLONED,
-		GHOST, //stack was removed from battlefield
-		HAD_MORALE,
-		WAITING,
-		MOVED,
-		DEFENDING,
-		FEAR,
-		//remember to drain mana only once per turn
-		DRAINED_MANA,
-		//only for defending animation
-		DEFENDING_ANIM,
-		GHOST_PENDING// stack will become GHOST in next battle state update
-	};
-}
-
 namespace ECommander
 {
 	enum SecondarySkills {ATTACK, DEFENSE, HEALTH, DAMAGE, SPEED, SPELL_POWER, CASTS, RESISTANCE};
@@ -518,8 +482,6 @@ namespace EWallState
 		DESTROYED,
 		DAMAGED,
 		INTACT
-
-
 	};
 }
 
@@ -778,30 +740,27 @@ namespace Date
 	};
 }
 
-namespace Battle
+enum class EActionType : int32_t
 {
-	enum ActionType
-	{
-		CANCEL = -3,
-		END_TACTIC_PHASE = -2,
-		INVALID = -1,
-		NO_ACTION = 0,
-		HERO_SPELL,
-		WALK, DEFEND,
-		RETREAT,
-		SURRENDER,
-		WALK_AND_ATTACK,
-		SHOOT,
-		WAIT,
-		CATAPULT,
-		MONSTER_SPELL,
-		BAD_MORALE,
-		STACK_HEAL,
-		DAEMON_SUMMONING
-	};
-}
+	CANCEL = -3,
+	END_TACTIC_PHASE = -2,
+	INVALID = -1,
+	NO_ACTION = 0,
+	HERO_SPELL,
+	WALK, DEFEND,
+	RETREAT,
+	SURRENDER,
+	WALK_AND_ATTACK,
+	SHOOT,
+	WAIT,
+	CATAPULT,
+	MONSTER_SPELL,
+	BAD_MORALE,
+	STACK_HEAL,
+	DAEMON_SUMMONING
+};
 
-std::ostream & operator<<(std::ostream & os, const Battle::ActionType actionType);
+DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EActionType actionType);
 
 class DLL_LINKAGE ETerrainType
 {
@@ -969,6 +928,10 @@ public:
 
 	DLL_LINKAGE const CArtifact * toArtifact() const;
 
+	///json serialization helpers
+	static si32 decode(const std::string & identifier);
+	static std::string encode(const si32 index);
+
 	ID_LIKE_CLASS_COMMON(ArtifactID, EArtifactID)
 
 	EArtifactID num;
@@ -1017,6 +980,10 @@ public:
 	ID_LIKE_CLASS_COMMON(CreatureID, ECreatureID)
 
 	ECreatureID num;
+
+	///json serialization helpers
+	static si32 decode(const std::string & identifier);
+	static std::string encode(const si32 index);
 };
 
 ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID)
@@ -1060,6 +1027,10 @@ public:
 	ID_LIKE_CLASS_COMMON(SpellID, ESpellID)
 
 	ESpellID num;
+
+	///json serialization helpers
+	static si32 decode(const std::string & identifier);
+	static std::string encode(const si32 index);
 };
 
 ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID)
@@ -1108,7 +1079,7 @@ enum class EHealPower : ui8
 // Typedef declarations
 typedef ui8 TFaction;
 typedef si64 TExpType;
-typedef std::pair<ui32, ui32> TDmgRange;
+typedef std::pair<si64, si64> TDmgRange;
 typedef si32 TBonusSubtype;
 typedef si32 TQuantity;
 

+ 92 - 45
lib/HeroBonus.cpp

@@ -81,20 +81,59 @@ const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
 }; //untested
 
 ///CBonusProxy
-CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector):
-	cachedLast(0), target(Target), selector(Selector), data()
+CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector)
+	: cachedLast(0),
+	target(Target),
+	selector(Selector),
+	data()
 {
 
 }
 
+CBonusProxy::CBonusProxy(const CBonusProxy & other)
+	: cachedLast(other.cachedLast),
+	target(other.target),
+	selector(other.selector),
+	data(other.data)
+{
+
+}
+
+CBonusProxy::CBonusProxy(CBonusProxy && other)
+	: cachedLast(0),
+	target(other.target),
+	selector(),
+	data()
+{
+	std::swap(cachedLast, other.cachedLast);
+	std::swap(selector, other.selector);
+	std::swap(data, other.data);
+}
+
+CBonusProxy & CBonusProxy::operator=(const CBonusProxy & other)
+{
+	cachedLast = other.cachedLast;
+	selector = other.selector;
+	data = other.data;
+	return *this;
+}
+
+CBonusProxy & CBonusProxy::operator=(CBonusProxy && other)
+{
+	std::swap(cachedLast, other.cachedLast);
+	std::swap(selector, other.selector);
+	std::swap(data, other.data);
+	return *this;
+}
+
 TBonusListPtr CBonusProxy::get() const
 {
-	if(CBonusSystemNode::treeChanged != cachedLast || !data)
+	if(target->getTreeVersion() != cachedLast || !data)
 	{
 		//TODO: support limiters
-		data = target->getAllBonuses(selector, nullptr);
+		data = target->getAllBonuses(selector, Selector::all);
 		data->eliminateDuplicates();
-		cachedLast = CBonusSystemNode::treeChanged;
+		cachedLast = target->getTreeVersion();
 	}
 	return data;
 }
@@ -104,7 +143,7 @@ const BonusList * CBonusProxy::operator->() const
 	return get().get();
 }
 
-int CBonusSystemNode::treeChanged = 1;
+std::atomic<int32_t> CBonusSystemNode::treeChanged(1);
 const bool CBonusSystemNode::cachingEnabled = true;
 
 BonusList::BonusList(bool BelongsToTree) : belongsToTree(BelongsToTree)
@@ -408,48 +447,44 @@ int IBonusBearer::LuckVal() const
 	return vstd::abetween(ret, -3, +3);
 }
 
-si32 IBonusBearer::Attack() const
+ui32 IBonusBearer::MaxHealth() const
 {
-	si32 ret = valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK);
-
-	if (double frenzyPower = valOfBonuses(Bonus::IN_FRENZY)) //frenzy for attacker
-	{
-		ret += (frenzyPower/100) * (double)Defense(false);
-	}
-	vstd::amax(ret, 0);
-
-	return ret;
+	const std::string cachingStr = "type_STACK_HEALTH";
+	static const auto selector = Selector::type(Bonus::STACK_HEALTH);
+	auto value = valOfBonuses(selector, cachingStr);
+	return std::max(1, value); //never 0
 }
 
-si32 IBonusBearer::Defense(bool withFrenzy) const
+int IBonusBearer::getAttack(bool ranged) const
 {
-	si32 ret = valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE);
+	const std::string cachingStr = "type_PRIMARY_SKILLs_ATTACK";
 
-	if(withFrenzy && hasBonusOfType(Bonus::IN_FRENZY)) //frenzy for defender
-	{
-		return 0;
-	}
-	vstd::amax(ret, 0);
+	static const auto selector = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK);
 
-	return ret;
+	return getBonuses(selector, nullptr, cachingStr)->totalValue();
 }
 
-ui32 IBonusBearer::MaxHealth() const
+int IBonusBearer::getDefence(bool ranged) const
 {
-	return std::max(1, valOfBonuses(Bonus::STACK_HEALTH)); //never 0
+	const std::string cachingStr = "type_PRIMARY_SKILLs_DEFENSE";
+
+	static const auto selector = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE);
+
+	return getBonuses(selector, nullptr, cachingStr)->totalValue();
 }
 
-ui32 IBonusBearer::getMinDamage() const
+int IBonusBearer::getMinDamage(bool ranged) const
 {
-	std::stringstream cachingStr;
-	cachingStr << "type_" << Bonus::CREATURE_DAMAGE << "s_0Otype_" << Bonus::CREATURE_DAMAGE << "s_1";
-	return valOfBonuses(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1)), cachingStr.str());
+	const std::string cachingStr = "type_CREATURE_DAMAGEs_0Otype_CREATURE_DAMAGEs_1";
+	static const auto selector = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1));
+	return valOfBonuses(selector, cachingStr);
 }
-ui32 IBonusBearer::getMaxDamage() const
+
+int IBonusBearer::getMaxDamage(bool ranged) const
 {
-	std::stringstream cachingStr;
-	cachingStr << "type_" << Bonus::CREATURE_DAMAGE << "s_0Otype_" << Bonus::CREATURE_DAMAGE << "s_2";
-	return valOfBonuses(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2)), cachingStr.str());
+	const std::string cachingStr = "type_CREATURE_DAMAGEs_0Otype_CREATURE_DAMAGEs_2";
+	static const auto selector = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2));
+	return valOfBonuses(selector, cachingStr);
 }
 
 si32 IBonusBearer::manaLimit() const
@@ -461,13 +496,7 @@ si32 IBonusBearer::manaLimit() const
 
 int IBonusBearer::getPrimSkillLevel(PrimarySkill::PrimarySkill id) const
 {
-	int ret = 0;
-	if(id == PrimarySkill::ATTACK)
-		ret = Attack();
-	else if(id == PrimarySkill::DEFENSE)
-		ret = Defense();
-	else
-		ret = valOfBonuses(Bonus::PRIMARY_SKILL, id);
+	int ret = valOfBonuses(Bonus::PRIMARY_SKILL, id);
 
 	vstd::amax(ret, id/2); //minimal value is 0 for attack and defense and 1 for spell power and knowledge
 	return ret;
@@ -478,7 +507,7 @@ si32 IBonusBearer::magicResistance() const
 	return valOfBonuses(Bonus::MAGIC_RESISTANCE);
 }
 
-ui32 IBonusBearer::Speed(int turn, bool useBind ) const
+ui32 IBonusBearer::Speed(int turn, bool useBind) const
 {
 	//war machines cannot move
 	if(hasBonus(Selector::type(Bonus::SIEGE_WEAPON).And(Selector::turns(turn))))
@@ -505,8 +534,8 @@ bool IBonusBearer::isLiving() const //TODO: theoreticaly there exists "LIVING" b
 
 const std::shared_ptr<Bonus> IBonusBearer::getBonus(const CSelector &selector) const
 {
-	auto bonuses = getAllBonuses(Selector::all, Selector::all);
-	return bonuses->getFirst(selector);
+	auto bonuses = getAllBonuses(selector, Selector::all);
+	return bonuses->getFirst(Selector::all);
 }
 
 std::shared_ptr<Bonus> CBonusSystemNode::getBonusLocalFirst(const CSelector &selector)
@@ -657,7 +686,19 @@ const TBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelecto
 	return ret;
 }
 
-CBonusSystemNode::CBonusSystemNode() : bonuses(true), exportedBonuses(true), nodeType(UNKNOWN), cachedLast(0)
+CBonusSystemNode::CBonusSystemNode()
+	: bonuses(true),
+	exportedBonuses(true),
+	nodeType(UNKNOWN),
+	cachedLast(0)
+{
+}
+
+CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType)
+	: bonuses(true),
+	exportedBonuses(true),
+	nodeType(NodeType),
+	cachedLast(0)
 {
 }
 
@@ -1048,6 +1089,12 @@ void CBonusSystemNode::treeHasChanged()
 	treeChanged++;
 }
 
+int64_t CBonusSystemNode::getTreeVersion() const
+{
+	int64_t ret = treeChanged;
+	return ret << 32;
+}
+
 int NBonus::valOf(const CBonusSystemNode *obj, Bonus::BonusType type, int subtype)
 {
 	if(obj)

+ 22 - 9
lib/HeroBonus.h

@@ -64,16 +64,21 @@ public:
 	}
 };
 
-class DLL_LINKAGE CBonusProxy : public boost::noncopyable
+class DLL_LINKAGE CBonusProxy
 {
 public:
 	CBonusProxy(const IBonusBearer * Target, CSelector Selector);
+	CBonusProxy(const CBonusProxy & other);
+	CBonusProxy(CBonusProxy && other);
+
+	CBonusProxy & operator=(CBonusProxy && other);
+	CBonusProxy & operator=(const CBonusProxy & other);
 
 	TBonusListPtr get() const;
 
 	const BonusList * operator->() const;
 private:
-	mutable int cachedLast;
+	mutable int64_t cachedLast;
 	const IBonusBearer * target;
 	CSelector selector;
 	mutable TBonusListPtr data;
@@ -607,12 +612,15 @@ public:
 	bool hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const;
 
 	//various hlp functions for non-trivial values
-	ui32 getMinDamage() const; //used for stacks and creatures only
-	ui32 getMaxDamage() const;
+	//used for stacks and creatures only
+
+	virtual int getMinDamage(bool ranged) const;
+	virtual int getMaxDamage(bool ranged) const;
+	virtual int getAttack(bool ranged) const;
+	virtual int getDefence(bool ranged) const;
+
 	int MoraleVal() const; //range [-3, +3]
 	int LuckVal() const; //range [-3, +3]
-	si32 Attack() const; //get attack of stack with all modificators
-	si32 Defense(bool withFrenzy = true) const; //get defense of stack with all modificators
 	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;
@@ -620,9 +628,11 @@ public:
 
 	si32 manaLimit() const; //maximum mana value for this hero (basically 10*knowledge)
 	int getPrimSkillLevel(PrimarySkill::PrimarySkill id) const;
+
+	virtual int64_t getTreeVersion() const = 0;
 };
 
-class DLL_LINKAGE CBonusSystemNode : public IBonusBearer, public boost::noncopyable
+class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, public boost::noncopyable
 {
 public:
 	enum ENodeTypes
@@ -642,8 +652,8 @@ private:
 
 	static const bool cachingEnabled;
 	mutable BonusList cachedBonuses;
-	mutable int cachedLast;
-	static int treeChanged;
+	mutable int64_t cachedLast;
+	static std::atomic<int32_t> treeChanged;
 
 	// Setting a value to cachingStr before getting any bonuses caches the result for later requests.
 	// This string needs to be unique, that's why it has to be setted in the following manner:
@@ -656,6 +666,7 @@ private:
 
 public:
 	explicit CBonusSystemNode();
+	explicit CBonusSystemNode(ENodeTypes NodeType);
 	CBonusSystemNode(CBonusSystemNode && other);
 	virtual ~CBonusSystemNode();
 
@@ -711,6 +722,8 @@ public:
 
 	static void treeHasChanged();
 
+	int64_t getTreeVersion() const override;
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 //		h & bonuses;

+ 2 - 2
lib/IBonusTypeHandler.h

@@ -19,6 +19,6 @@ class IBonusTypeHandler
 public:
 	virtual ~IBonusTypeHandler(){};
 
-	virtual std::string bonusToString(const std::shared_ptr<Bonus>& bonus, const IBonusBearer *bearer, bool description) const = 0;
-	virtual std::string bonusToGraphics(const std::shared_ptr<Bonus>& bonus) const = 0;
+	virtual std::string bonusToString(const std::shared_ptr<Bonus> & bonus, const IBonusBearer * bearer, bool description) const = 0;
+	virtual std::string bonusToGraphics(const std::shared_ptr<Bonus> & bonus) const = 0;
 };

+ 8 - 8
lib/IGameEventsReceiver.h

@@ -32,12 +32,11 @@ struct Bonus;
 class IMarket;
 struct SetObjectProperty;
 struct PackageApplied;
-struct BattleAction;
+class BattleAction;
 struct BattleStackAttacked;
 struct BattleResult;
 struct BattleSpellCast;
 struct CatapultAttack;
-struct BattleStacksRemoved;
 class CStack;
 class CCreatureSet;
 struct BattleAttack;
@@ -47,6 +46,10 @@ class CComponent;
 struct CObstacleInstance;
 struct CPackForServer;
 class EVictoryLossCheckResult;
+struct MetaString;
+struct CustomEffectInfo;
+class ObstacleChanges;
+class UnitChanges;
 
 class DLL_LINKAGE IBattleEventsReceiver
 {
@@ -54,7 +57,7 @@ public:
 	virtual void actionFinished(const BattleAction &action){};//occurs AFTER every action taken by any stack or by the hero
 	virtual void actionStarted(const BattleAction &action){};//occurs BEFORE every action taken by any stack or by the hero
 	virtual void battleAttack(const BattleAttack *ba){}; //called when stack is performing attack
-	virtual void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa){}; //called when stack receives damage (after battleAttack())
+	virtual void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog){}; //called when stack receives damage (after battleAttack())
 	virtual void battleEnd(const BattleResult *br){};
 	virtual void battleNewRoundFirst(int round){}; //called at the beginning of each turn before changes are applied;
 	virtual void battleNewRound(int round){}; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
@@ -64,12 +67,9 @@ public:
 	virtual void battleTriggerEffect(const BattleTriggerEffect & bte){}; //called for various one-shot effects
 	virtual void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) {}; //called just before battle start
 	virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side){}; //called by engine when battle starts; side=0 - left, side=1 - right
-	virtual void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom){}; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp
-	virtual void battleNewStackAppeared(const CStack * stack){}; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned
-	virtual void battleObstaclesRemoved(const std::set<si32> & removedObstacles){}; //called when a certain set  of obstacles is removed from batlefield; IDs of them are given
+	virtual void battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog){};
+	virtual void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles){};
 	virtual void battleCatapultAttacked(const CatapultAttack & ca){}; //called when catapult makes an attack
-	virtual void battleStacksRemoved(const BattleStacksRemoved & bsr){}; //called when certain stack is completely removed from battlefield
-	virtual void battleObstaclePlaced(const CObstacleInstance &obstacle){};
 	virtual void battleGateStateChanged(const EGateState state){};
 };
 

+ 3 - 6
lib/IHandlerBase.h

@@ -11,7 +11,6 @@
 
  #include "../lib/ConstTransitivePtr.h"
  #include "VCMI_Lib.h"
- //#include "CModHandler.h"
 
 class JsonNode;
 
@@ -69,8 +68,7 @@ public:
 	void loadObject(std::string scope, std::string name, const JsonNode & data) override
 	{
 		auto type_name = getTypeName();
-		auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
-		object->id = _ObjectID(objects.size());
+		auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), objects.size());
 
 		objects.push_back(object);
 
@@ -79,8 +77,7 @@ public:
 	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override
 	{
 		auto type_name = getTypeName();
-		auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
-		object->id = _ObjectID(index);
+		auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), index);
 
 		assert(objects[index] == nullptr); // ensure that this id was not loaded before
 		objects[index] = object;
@@ -101,7 +98,7 @@ public:
 		return objects[raw_id];
 	}
 protected:
-	virtual _Object * loadFromJson(const JsonNode & json, const std::string & identifier) = 0;
+	virtual _Object * loadFromJson(const JsonNode & json, const std::string & identifier, size_t index) = 0;
 	virtual const std::string getTypeName() const = 0;
 public: //todo: make private
 	std::vector<ConstTransitivePtr<_Object>> objects;

+ 1 - 1
lib/JsonNode.h

@@ -305,7 +305,7 @@ namespace JsonDetail
 			return node.Bool();
 		}
 	};
-} // namespace JsonDetail
+}
 
 template<typename Type>
 Type JsonNode::convertTo() const

+ 67 - 187
lib/NetPacks.h

@@ -12,7 +12,6 @@
 #include "NetPacksBase.h"
 
 #include "battle/BattleAction.h"
-#include "JsonNode.h"
 #include "mapObjects/CGHeroInstance.h"
 #include "ConstTransitivePtr.h"
 #include "int3.h"
@@ -23,10 +22,6 @@
 
 #include "spells/ViewSpellInt.h"
 
-class CClient;
-class CGameState;
-class CGameHandler;
-class CConnection;
 class CCampaignState;
 class CArtifact;
 class CSelectionScreen;
@@ -37,45 +32,7 @@ struct ArtSlotInfo;
 struct QuestInfo;
 class CMapInfo;
 struct StartInfo;
-
-struct CPackForClient : public CPack
-{
-	CPackForClient(){};
-
-	CGameState* GS(CClient *cl);
-	void applyFirstCl(CClient *cl)//called before applying to gs
-	{}
-	void applyCl(CClient *cl)//called after applying to gs
-	{}
-};
-
-struct CPackForServer : public CPack
-{
-	PlayerColor player;
-	CConnection *c;
-	CGameState* GS(CGameHandler *gh);
-	CPackForServer():
-		player(PlayerColor::NEUTRAL),
-		c(nullptr)
-	{
-	}
-
-	bool applyGh(CGameHandler *gh) //called after applying to gs
-	{
-		logGlobal->error("Should not happen... applying plain CPackForServer");
-		return false;
-	}
-
-protected:
-	void throwNotAllowedAction();
-	void throwOnWrongOwner(CGameHandler * gh, ObjectInstanceID id);
-	void throwOnWrongPlayer(CGameHandler * gh, PlayerColor player);
-	void throwAndCompain(CGameHandler * gh, std::string txt);
-	bool isPlayerOwns(CGameHandler * gh, ObjectInstanceID id);
-
-private:
-	void wrongPlayerMessage(CGameHandler * gh, PlayerColor expectedplayer);
-};
+class IBattleState;
 
 struct Query : public CPackForClient
 {
@@ -1351,7 +1308,7 @@ struct MapObjectSelectDialog : public Query
 	}
 };
 
-struct BattleInfo;
+class BattleInfo;
 struct BattleStart : public CPackForClient
 {
 	BattleStart()
@@ -1440,12 +1397,16 @@ struct BattleStackMoved : public CPackForClient
 {
 	ui32 stack;
 	std::vector<BattleHex> tilesToMove;
-	ui8 distance, teleporting;
+	int distance;
+	bool teleporting;
 	BattleStackMoved()
-		:stack(0), distance(0), teleporting(0)
+		: stack(0),
+		distance(0),
+		teleporting(false)
 	{};
 	void applyFirstCl(CClient *cl);
-	void applyGs(CGameState *gs);
+	DLL_LINKAGE void applyGs(CGameState *gs);
+	DLL_LINKAGE void applyBattle(IBattleState * battleState);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & stack;
@@ -1454,52 +1415,46 @@ struct BattleStackMoved : public CPackForClient
 	}
 };
 
-struct StacksHealedOrResurrected : public CPackForClient
+struct BattleUnitsChanged : public CPackForClient
 {
-	StacksHealedOrResurrected()
-		:lifeDrain(false), tentHealing(false), drainedFrom(0), cure(false)
-	{}
+	BattleUnitsChanged(){}
 
 	DLL_LINKAGE void applyGs(CGameState *gs);
+	DLL_LINKAGE void applyBattle(IBattleState * battleState);
 	void applyCl(CClient *cl);
 
-	std::vector<CHealthInfo> healedStacks;
-	bool lifeDrain; //if true, this heal is an effect of life drain or soul steal
-	bool tentHealing; //if true, than it's healing via First Aid Tent
-	si32 drainedFrom; //if life drain or soul steal - then stack life was drain from, if tentHealing - stack that is a healer
-	bool cure; //archangel cast also remove negative effects
+	std::vector<UnitChanges> changedStacks;
+	std::vector<MetaString> battleLog;
+	std::vector<CustomEffectInfo> customEffects;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler & h, const int version)
 	{
-		h & healedStacks;
-		h & lifeDrain;
-		h & tentHealing;
-		h & drainedFrom;
-		h & cure;
+		h & changedStacks;
+		h & battleLog;
+		h & customEffects;
 	}
 };
 
-struct BattleStackAttacked : public CPackForClient
+struct BattleStackAttacked
 {
 	BattleStackAttacked():
 		stackAttacked(0), attackerID(0),
 		killedAmount(0), damageAmount(0),
-		newHealth(),
+		newState(),
 		flags(0), effect(0), spellID(SpellID::NONE)
 	{};
-	void applyFirstCl(CClient * cl);
-	//void applyCl(CClient *cl);
+
 	DLL_LINKAGE void applyGs(CGameState *gs);
+	DLL_LINKAGE void applyBattle(IBattleState * battleState);
 
 	ui32 stackAttacked, attackerID;
 	ui32 killedAmount;
-	si32 damageAmount;
-	CHealthInfo newHealth;
+	int64_t damageAmount;
+	UnitChanges newState;
 	enum EFlags {KILLED = 1, EFFECT = 2/*deprecated */, SECONDARY = 4, REBIRTH = 8, CLONE_KILLED = 16, SPELL_EFFECT = 32 /*, BONUS_EFFECT = 64 */};
 	ui32 flags; //uses EFlags (above)
 	ui32 effect; //set only if flag EFFECT is set
 	SpellID spellID; //only if flag SPELL_EFFECT is set
-	std::vector<StacksHealedOrResurrected> healedStacks; //used when life drain
 
 	bool killed() const//if target stack was killed
 	{
@@ -1526,20 +1481,15 @@ struct BattleStackAttacked : public CPackForClient
 	{
 		return flags & REBIRTH;
 	}
-	bool lifeDrain() const //if this attack involves life drain effect
-	{
-		return healedStacks.size() > 0;
-	}
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & stackAttacked;
 		h & attackerID;
-		h & newHealth;
+		h & newState;
 		h & flags;
 		h & killedAmount;
 		h & damageAmount;
 		h & effect;
-		h & healedStacks;
 		h & spellID;
 	}
 	bool operator<(const BattleStackAttacked &b) const
@@ -1557,6 +1507,8 @@ struct BattleAttack : public CPackForClient
 	DLL_LINKAGE void applyGs(CGameState *gs);
 	void applyCl(CClient *cl);
 
+	BattleUnitsChanged attackerChanges;
+
 	std::vector<BattleStackAttacked> bsa;
 	ui32 stackAttacking;
 	ui32 flags; //uses Eflags (below)
@@ -1564,6 +1516,9 @@ struct BattleAttack : public CPackForClient
 
 	SpellID spellID; //for SPELL_LIKE
 
+	std::vector<MetaString> battleLog;
+	std::vector<CustomEffectInfo> customEffects;
+
 	bool shot() const//distance attack - decrease number of shots
 	{
 		return flags & SHOT;
@@ -1598,6 +1553,9 @@ struct BattleAttack : public CPackForClient
 		h & stackAttacking;
 		h & flags;
 		h & spellID;
+		h & battleLog;
+		h & customEffects;
+		h & attackerChanges;
 	}
 };
 
@@ -1627,24 +1585,9 @@ struct EndAction : public CPackForClient
 
 struct BattleSpellCast : public CPackForClient
 {
-	///custom effect (resistance, reflection, etc)
-	struct CustomEffect
-	{
-		/// WoG AC format
-		ui32 effect;
-		ui32 stack;
-		template <typename Handler> void serialize(Handler &h, const int version)
-		{
-			h & effect;
-			h & stack;
-		}
-	};
-
 	BattleSpellCast()
 	{
 		side = 0;
-		id = 0;
-		skill = 0;
 		manaGained = 0;
 		casterStack = -1;
 		castByHero = true;
@@ -1655,11 +1598,10 @@ struct BattleSpellCast : public CPackForClient
 
 	bool activeCast;
 	ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender
-	ui32 id; //id of spell
-	ui8 skill; //caster's skill level
+	SpellID spellID; //id of spell
 	ui8 manaGained; //mana channeling ability
 	BattleHex tile; //destination tile (may not be set in some global/mass spells
-	std::vector<CustomEffect> customEffects;
+	std::vector<CustomEffectInfo> customEffects;
 	std::set<ui32> affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure)
 	si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID
 	bool castByHero; //if true - spell has been cast by hero, otherwise by a creature
@@ -1668,8 +1610,7 @@ struct BattleSpellCast : public CPackForClient
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & side;
-		h & id;
-		h & skill;
+		h & spellID;
 		h & manaGained;
 		h & tile;
 		h & customEffects;
@@ -1684,27 +1625,20 @@ struct BattleSpellCast : public CPackForClient
 struct SetStackEffect : public CPackForClient
 {
 	SetStackEffect(){};
-	DLL_LINKAGE void applyGs(CGameState *gs);
-	void applyCl(CClient *cl);
-
-	std::vector<ui32> stacks; //affected stacks (IDs)
-
-	//regular effects
-	std::vector<Bonus> effect; //bonuses to apply
-	std::vector<std::pair<ui32, Bonus> > uniqueBonuses; //bonuses per single stack
+	DLL_LINKAGE void applyGs(CGameState * gs);
+	DLL_LINKAGE void applyBattle(IBattleState * battleState);
+	void applyCl(CClient * cl);
 
-	//cumulative effects
-	std::vector<Bonus> cumulativeEffects; //bonuses to apply
-	std::vector<std::pair<ui32, Bonus> > cumulativeUniqueBonuses; //bonuses per single stack
+	std::vector<std::pair<ui32, std::vector<Bonus>>> toAdd;
+	std::vector<std::pair<ui32, std::vector<Bonus>>> toUpdate;
+	std::vector<std::pair<ui32, std::vector<Bonus>>> toRemove;
 
 	std::vector<MetaString> battleLog;
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler & h, const int version)
 	{
-		h & stacks;
-		h & effect;
-		h & uniqueBonuses;
-		h & cumulativeEffects;
-		h & cumulativeUniqueBonuses;
+		h & toAdd;
+		h & toUpdate;
+		h & toRemove;
 		h & battleLog;
 	}
 };
@@ -1712,13 +1646,18 @@ struct SetStackEffect : public CPackForClient
 struct StacksInjured : public CPackForClient
 {
 	StacksInjured(){}
-	DLL_LINKAGE void applyGs(CGameState *gs);
-	void applyCl(CClient *cl);
+	DLL_LINKAGE void applyGs(CGameState * gs);
+	DLL_LINKAGE void applyBattle(IBattleState * battleState);
+
+	void applyCl(CClient * cl);
 
 	std::vector<BattleStackAttacked> stacks;
-	template <typename Handler> void serialize(Handler &h, const int version)
+	std::vector<MetaString> battleLog;
+
+	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 		h & stacks;
+		h & battleLog;
 	}
 };
 
@@ -1736,18 +1675,19 @@ struct BattleResultsApplied : public CPackForClient
 	}
 };
 
-struct ObstaclesRemoved : public CPackForClient
+struct BattleObstaclesChanged : public CPackForClient
 {
-	ObstaclesRemoved(){}
+	BattleObstaclesChanged(){}
 
-	DLL_LINKAGE void applyGs(CGameState *gs);
-	void applyCl(CClient *cl);
+	DLL_LINKAGE void applyGs(CGameState * gs);
+	DLL_LINKAGE void applyBattle(IBattleState * battleState);
+	void applyCl(CClient * cl);
 
-	std::set<si32> obstacles; //uniqueIDs of removed obstacles
+	std::vector<ObstacleChanges> changes;
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	template <typename Handler> void serialize(Handler & h, const int version)
 	{
-		h & obstacles;
+		h & changes;
 	}
 };
 
@@ -1759,9 +1699,7 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient
 		ui8 attackedPart;
 		ui8 damageDealt;
 
-		DLL_LINKAGE std::string toString() const;
-
-		template <typename Handler> void serialize(Handler &h, const int version)
+		template <typename Handler> void serialize(Handler & h, const int version)
 		{
 			h & destinationTile;
 			h & attackedPart;
@@ -1772,9 +1710,9 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient
 	DLL_LINKAGE CatapultAttack();
 	DLL_LINKAGE ~CatapultAttack();
 
-	DLL_LINKAGE void applyGs(CGameState *gs);
-	void applyCl(CClient *cl);
-	DLL_LINKAGE std::string toString() const override;
+	DLL_LINKAGE void applyGs(CGameState * gs);
+	DLL_LINKAGE void applyBattle(IBattleState * battleState);
+	void applyCl(CClient * cl);
 
 	std::vector< AttackInfo > attackedParts;
 	int attacker; //if -1, then a spell caused this
@@ -1786,49 +1724,6 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient
 	}
 };
 
-struct BattleStacksRemoved : public CPackForClient
-{
-	BattleStacksRemoved(){}
-
-	DLL_LINKAGE void applyGs(CGameState *gs);
-	void applyFirstCl(CClient *cl);//inform client before stack objects are destroyed
-
-	std::set<ui32> stackIDs; //IDs of removed stacks
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & stackIDs;
-	}
-};
-
-struct BattleStackAdded : public CPackForClient
-{
-	BattleStackAdded()
-		: side(0), amount(0), pos(0), summoned(0), newStackID(0)
-	{};
-
-	DLL_LINKAGE void applyGs(CGameState *gs);
-	void applyCl(CClient *cl);
-
-	ui8 side;
-	CreatureID creID;
-	int amount;
-	int pos;
-	int summoned; //if true, remove it afterwards
-
-	///Actual stack ID, set on apply, do not serialize
-	int newStackID;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & side;
-		h & creID;
-		h & amount;
-		h & pos;
-		h & summoned;
-	}
-};
-
 struct BattleSetStackProperty : public CPackForClient
 {
 	BattleSetStackProperty()
@@ -1877,21 +1772,6 @@ struct BattleTriggerEffect : public CPackForClient
 	}
 };
 
-struct BattleObstaclePlaced : public CPackForClient
-{
-	BattleObstaclePlaced(){};
-
-	DLL_LINKAGE void applyGs(CGameState *gs); //effect
-	void applyCl(CClient *cl); //play animations & stuff
-
-	std::shared_ptr<CObstacleInstance> obstacle;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & obstacle;
-	}
-};
-
 struct BattleUpdateGateState : public CPackForClient
 {
 	BattleUpdateGateState():state(EGateState::NONE){};

+ 138 - 16
lib/NetPacksBase.h

@@ -9,7 +9,10 @@
  */
 #pragma once
 
+class CClient;
 class CGameState;
+class CGameHandler;
+class CConnection;
 class CStackBasicDescriptor;
 class CGHeroInstance;
 class CStackInstance;
@@ -17,9 +20,11 @@ class CArmedInstance;
 class CArtifactSet;
 class CBonusSystemNode;
 struct ArtSlotInfo;
+class BattleInfo;
 
 #include "ConstTransitivePtr.h"
 #include "GameConstants.h"
+#include "JsonNode.h"
 
 struct DLL_LINKAGE CPack
 {
@@ -31,11 +36,49 @@ struct DLL_LINKAGE CPack
 		logNetwork->error("CPack serialized... this should not happen!");
 		assert(false && "CPack serialized");
 	}
-	void applyGs(CGameState *gs) { }
-	virtual std::string toString() const { return boost::str(boost::format("{CPack: type '%s'}") % typeid(this).name()); }
+
+	void applyGs(CGameState * gs)
+	{}
 };
 
-std::ostream & operator<<(std::ostream & out, const CPack * pack);
+struct CPackForClient : public CPack
+{
+	CPackForClient(){};
+
+	CGameState* GS(CClient *cl);
+	void applyFirstCl(CClient *cl)//called before applying to gs
+	{}
+	void applyCl(CClient *cl)//called after applying to gs
+	{}
+};
+
+struct CPackForServer : public CPack
+{
+	PlayerColor player;
+	CConnection *c;
+	CGameState* GS(CGameHandler *gh);
+	CPackForServer():
+		player(PlayerColor::NEUTRAL),
+		c(nullptr)
+	{
+	}
+
+	bool applyGh(CGameHandler *gh) //called after applying to gs
+	{
+		logGlobal->error("Should not happen... applying plain CPackForServer");
+		return false;
+	}
+
+protected:
+	void throwNotAllowedAction();
+	void throwOnWrongOwner(CGameHandler * gh, ObjectInstanceID id);
+	void throwOnWrongPlayer(CGameHandler * gh, PlayerColor player);
+	void throwAndCompain(CGameHandler * gh, std::string txt);
+	bool isPlayerOwns(CGameHandler * gh, ObjectInstanceID id);
+
+private:
+	void wrongPlayerMessage(CGameHandler * gh, PlayerColor expectedplayer);
+};
 
 struct DLL_LINKAGE MetaString
 {
@@ -196,25 +239,104 @@ struct ArtifactLocation
 	}
 };
 
-class CHealthInfo
+///custom effect (resistance, reflection, etc)
+struct CustomEffectInfo
+{
+	CustomEffectInfo()
+		:effect(0),
+		sound(0),
+		stack(0)
+	{
+	}
+	/// WoG AC format
+	ui32 effect;
+	ui32 sound;
+	ui32 stack;
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & effect;
+		h & sound;
+		h & stack;
+	}
+};
+
+class BattleChanges
+{
+public:
+	enum class EOperation : si8
+	{
+		ADD,
+		RESET_STATE,
+		UPDATE,
+		REMOVE
+	};
+
+	JsonNode data;
+	EOperation operation;
+
+	BattleChanges()
+		: operation(EOperation::RESET_STATE),
+		data()
+	{
+	}
+
+	BattleChanges(EOperation operation_)
+		: operation(operation_),
+		data()
+	{
+	}
+};
+
+class UnitChanges : public BattleChanges
+{
+public:
+	uint32_t id;
+	int64_t healthDelta;
+
+	UnitChanges()
+		: BattleChanges(EOperation::RESET_STATE),
+		id(0),
+		healthDelta(0)
+	{
+	}
+
+	UnitChanges(uint32_t id_, EOperation operation_)
+		: BattleChanges(operation_),
+		id(id_),
+		healthDelta(0)
+	{
+	}
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & id;
+		h & healthDelta;
+		h & data;
+		h & operation;
+	}
+};
+
+class ObstacleChanges : public BattleChanges
 {
 public:
-	CHealthInfo():
-		stackId(0), delta(0), firstHPleft(0), fullUnits(0), resurrected(0)
+	uint32_t id;
+
+	ObstacleChanges()
+		: BattleChanges(EOperation::RESET_STATE),
+		id(0)
+	{
+	}
+
+	ObstacleChanges(uint32_t id_, EOperation operation_)
+		: BattleChanges(operation_),
+		id(id_)
 	{
 	}
-	uint32_t stackId;
-	int32_t delta;
-	int32_t firstHPleft;
-	int32_t fullUnits;
-	int32_t resurrected;
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
-		h & stackId;
-		h & delta;
-		h & firstHPleft;
-		h & fullUnits;
-		h & resurrected;
+		h & id;
+		h & data;
+		h & operation;
 	}
 };

+ 102 - 389
lib/NetPacksLib.cpp

@@ -32,11 +32,6 @@
 #undef max
 
 
-std::ostream & operator<<(std::ostream & out, const CPack * pack)
-{
-	return out << (pack? pack->toString() : "<nullptr>");
-}
-
 DLL_LINKAGE void SetResources::applyGs(CGameState *gs)
 {
 	assert(player < PlayerColor::PLAYER_LIMIT);
@@ -1232,50 +1227,12 @@ DLL_LINKAGE void BattleStart::applyGs(CGameState *gs)
 
 DLL_LINKAGE void BattleNextRound::applyGs(CGameState *gs)
 {
-	for (int i = 0; i < 2; ++i)
-	{
-		gs->curB->sides[i].castSpellsCount = 0;
-		vstd::amax(--gs->curB->sides[i].enchanterCounter, 0);
-	}
-
-	gs->curB->round = round;
-
-	for(CStack *s : gs->curB->stacks)
-	{
-		s->state -= EBattleStackState::DEFENDING;
-		s->state -= EBattleStackState::WAITING;
-		s->state -= EBattleStackState::MOVED;
-		s->state -= EBattleStackState::HAD_MORALE;
-		s->state -= EBattleStackState::FEAR;
-		s->state -= EBattleStackState::DRAINED_MANA;
-		s->counterAttacks.reset();
-		// new turn effects
-		s->updateBonuses(Bonus::NTurns);
-
-		if(s->alive() && s->isClone())
-		{
-			//cloned stack has special lifetime marker
-			//check it after bonuses updated in battleTurnPassed()
-
-			if(!s->hasBonus(Selector::type(Bonus::NONE).And(Selector::source(Bonus::SPELL_EFFECT, SpellID::CLONE))))
-				s->makeGhost();
-		}
-	}
-
-	for(auto &obst : gs->curB->obstacles)
-		obst->battleTurnPassed();
+	gs->curB->nextRound(round);
 }
 
 DLL_LINKAGE void BattleSetActiveStack::applyGs(CGameState *gs)
 {
-	gs->curB->activeStack = stack;
-	CStack *st = gs->curB->getStack(stack);
-
-	//remove bonuses that last until when stack gets new turn
-	st->popBonuses(Bonus::UntilGetsTurn);
-
-	if(vstd::contains(st->state,EBattleStackState::MOVED)) //if stack is moving second time this turn it must had a high morale bonus
-		st->state.insert(EBattleStackState::HAD_MORALE);
+	gs->curB->nextTurn(stack);
 }
 
 DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
@@ -1286,15 +1243,14 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
 	{
 	case Bonus::HP_REGENERATION:
 	{
-		int32_t toHeal = val;
-		CHealth health = st->healthAfterHealed(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
-		st->setHealth(health);
+		int64_t toHeal = val;
+		st->heal(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
 		break;
 	}
 	case Bonus::MANA_DRAIN:
 	{
 		CGHeroInstance * h = gs->getHero(ObjectInstanceID(additionalInfo));
-		st->state.insert (EBattleStackState::DRAINED_MANA);
+		st->drainedMana = true;
 		h->mana -= val;
 		vstd::amax(h->mana, 0);
 		break;
@@ -1310,18 +1266,13 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
 	case Bonus::ENCHANTER:
 		break;
 	case Bonus::FEAR:
-		st->state.insert(EBattleStackState::FEAR);
+		st->fear = true;
 		break;
 	default:
 		logNetwork->error("Unrecognized trigger effect type %d", effect);
 	}
 }
 
-DLL_LINKAGE void BattleObstaclePlaced::applyGs(CGameState *gs)
-{
-	gs->curB->obstacles.push_back(obstacle);
-}
-
 DLL_LINKAGE void BattleUpdateGateState::applyGs(CGameState *gs)
 {
 	if(gs->curB)
@@ -1330,15 +1281,6 @@ DLL_LINKAGE void BattleUpdateGateState::applyGs(CGameState *gs)
 
 void BattleResult::applyGs(CGameState *gs)
 {
-	for (CStack *s : gs->curB->stacks)
-	{
-		if (s->base && s->base->armyObj && vstd::contains(s->state, EBattleStackState::SUMMONED))
-		{
-			//stack with SUMMONED flag but coming from garrison -> most likely resurrected, needs to be removed
-			assert(&s->base->armyObj->getStack(s->slot) == s->base);
-			const_cast<CArmedInstance*>(s->base->armyObj)->eraseStack(s->slot);
-		}
-	}
 	for (auto & elem : gs->curB->stacks)
 		delete elem;
 
@@ -1373,90 +1315,24 @@ void BattleResult::applyGs(CGameState *gs)
 	gs->curB.dellNull();
 }
 
-void BattleStackMoved::applyGs(CGameState *gs)
+DLL_LINKAGE void BattleStackMoved::applyGs(CGameState *gs)
 {
-	CStack *s = gs->curB->getStack(stack);
-	assert(s);
-	BattleHex dest = tilesToMove.back();
-
-	//if unit ended movement on quicksands that were created by enemy, that quicksand patch becomes visible for owner
-	for(auto &oi : gs->curB->obstacles)
-	{
-		if(oi->obstacleType == CObstacleInstance::QUICKSAND
-		&& vstd::contains(oi->getAffectedTiles(), tilesToMove.back()))
-		{
-			SpellCreatedObstacle *sands = dynamic_cast<SpellCreatedObstacle*>(oi.get());
-			assert(sands);
-			if(sands->casterSide != s->side)
-				sands->visibleForAnotherSide = true;
-		}
-	}
-	s->position = dest;
+	applyBattle(gs->curB);
 }
 
-DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState *gs)
+DLL_LINKAGE void BattleStackMoved::applyBattle(IBattleState * battleState)
 {
-	CStack * at = gs->curB->getStack(stackAttacked);
-	assert(at);
-	at->popBonuses(Bonus::UntilBeingAttacked);
-
-	if(willRebirth())
-		at->health.reset();//kill stack first
-	else
-		at->setHealth(newHealth);
-
-	if(killed())
-	{
-		at->state -= EBattleStackState::ALIVE;
-
-		if(at->cloneID >= 0)
-		{
-			//remove clone as well
-			CStack * clone = gs->curB->getStack(at->cloneID);
-			if(clone)
-				clone->makeGhost();
-
-			at->cloneID = -1;
-		}
-	}
-	//life drain handling
-	for(auto & elem : healedStacks)
-		elem.applyGs(gs);
-
-	if(willRebirth())
-	{
-		//TODO: handle rebirth with StacksHealedOrResurrected
-		at->casts.use();
-		at->state.insert(EBattleStackState::ALIVE);
-		at->setHealth(newHealth);
-
-		//removing all spells effects
-		auto selector = [](const Bonus * b)
-		{
-			//Special case: DISRUPTING_RAY is "immune" to dispell
-			//Other even PERMANENT effects can be removed
-			if(b->source == Bonus::SPELL_EFFECT)
-				return b->sid != SpellID::DISRUPTING_RAY;
-			else
-				return false;
-		};
-		at->popBonuses(selector);
-	}
-	if(cloneKilled())
-	{
-		//"hide" killed creatures instead so we keep info about it
-		at->makeGhost();
+	battleState->moveUnit(stack, tilesToMove.back());
+}
 
-		for(CStack * s : gs->curB->stacks)
-		{
-			if(s->cloneID == at->ID)
-				s->cloneID = -1;
-		}
-	}
+DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState * gs)
+{
+	applyBattle(gs->curB);
+}
 
-	//killed summoned creature should be removed like clone
-	if(killed() && vstd::contains(at->state, EBattleStackState::SUMMONED))
-		at->makeGhost();
+DLL_LINKAGE void BattleStackAttacked::applyBattle(IBattleState * battleState)
+{
+	battleState->setUnitState(newState.id, newState.data, newState.healthDelta);
 }
 
 DLL_LINKAGE void BattleAttack::applyGs(CGameState * gs)
@@ -1464,11 +1340,7 @@ DLL_LINKAGE void BattleAttack::applyGs(CGameState * gs)
 	CStack * attacker = gs->curB->getStack(stackAttacking);
 	assert(attacker);
 
-	if(counter())
-		attacker->counterAttacks.use();
-
-	if(shot())
-		attacker->shots.use();
+	attackerChanges.applyGs(gs);
 
 	for(BattleStackAttacked & stackAttacked : bsa)
 		stackAttacked.applyGs(gs);
@@ -1480,7 +1352,7 @@ DLL_LINKAGE void StartAction::applyGs(CGameState *gs)
 {
 	CStack *st = gs->curB->getStack(ba.stackNumber);
 
-	if(ba.actionType == Battle::END_TACTIC_PHASE)
+	if(ba.actionType == EActionType::END_TACTIC_PHASE)
 	{
 		gs->curB->tacticDistance = 0;
 		return;
@@ -1493,318 +1365,159 @@ DLL_LINKAGE void StartAction::applyGs(CGameState *gs)
 		return;
 	}
 
-	if(ba.actionType != Battle::HERO_SPELL) //don't check for stack if it's custom action by hero
+	if(ba.actionType != EActionType::HERO_SPELL) //don't check for stack if it's custom action by hero
 	{
 		assert(st);
 	}
 	else
 	{
-		gs->curB->sides[ba.side].usedSpellsHistory.push_back(SpellID(ba.additionalInfo).toSpell());
+		gs->curB->sides[ba.side].usedSpellsHistory.push_back(SpellID(ba.actionSubtype).toSpell());
 	}
 
 	switch(ba.actionType)
 	{
-	case Battle::DEFEND:
-		st->state -= EBattleStackState::DEFENDING_ANIM;
-		st->state.insert(EBattleStackState::DEFENDING);
-		st->state.insert(EBattleStackState::DEFENDING_ANIM);
+	case EActionType::DEFEND:
+		st->waiting = false;
+		st->defending = true;
+		st->defendingAnim = true;
+		break;
+	case EActionType::WAIT:
+		st->defendingAnim = false;
+		st->waiting = true;
+		break;
+	case EActionType::HERO_SPELL: //no change in current stack state
 		break;
-	case Battle::WAIT:
-		st->state -= EBattleStackState::DEFENDING_ANIM;
-		st->state.insert(EBattleStackState::WAITING);
-		return;
-	case Battle::HERO_SPELL: //no change in current stack state
-		return;
 	default: //any active stack action - attack, catapult, heal, spell...
-		st->state -= EBattleStackState::DEFENDING_ANIM;
-		st->state.insert(EBattleStackState::MOVED);
+		st->waiting = false;
+		st->defendingAnim = false;
+		st->movedThisRound = true;
 		break;
 	}
-
-	if(st)
-		st->state -= EBattleStackState::WAITING; //if stack was waiting it has made move, so it won't be "waiting" anymore (if the action was WAIT, then we have returned)
 }
 
 DLL_LINKAGE void BattleSpellCast::applyGs(CGameState *gs)
 {
 	assert(gs->curB);
 
-	const CSpell * spell = SpellID(id).toSpell();
-
-	spell->applyBattle(gs->curB, this);
-}
-
-void actualizeEffect(CStack * s, const Bonus & ef)
-{
-	for(auto stackBonus : s->getBonusList()) //TODO: optimize
+	if(castByHero)
 	{
-		if(stackBonus->source == Bonus::SPELL_EFFECT && stackBonus->type == ef.type && stackBonus->subtype == ef.subtype)
+		if(side < 2)
 		{
-			stackBonus->turnsRemain = std::max(stackBonus->turnsRemain, ef.turnsRemain);
+			gs->curB->sides[side].castSpellsCount++;
 		}
 	}
-	CBonusSystemNode::treeHasChanged();
 }
 
-void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)
+DLL_LINKAGE void SetStackEffect::applyGs(CGameState *gs)
 {
-	//actualizing features vector
-
-	for(const Bonus &fromEffect : ef)
-	{
-		actualizeEffect(s, fromEffect);
-	}
+	applyBattle(gs->curB);
 }
 
-DLL_LINKAGE void SetStackEffect::applyGs(CGameState *gs)
+DLL_LINKAGE void SetStackEffect::applyBattle(IBattleState * battleState)
 {
-	if(effect.empty() && cumulativeEffects.empty())
-	{
-		logGlobal->error("Trying to apply SetStackEffect with no effects");
-		return;
-	}
+	for(const auto & stackData : toRemove)
+		battleState->removeUnitBonus(stackData.first, stackData.second);
 
-	si32 spellid = effect.empty() ? cumulativeEffects.begin()->sid : effect.begin()->sid; //effects' source ID
+	for(const auto & stackData : toUpdate)
+		battleState->updateUnitBonus(stackData.first, stackData.second);
 
-	auto processEffect = [spellid, this](CStack * sta, const Bonus & effect, bool cumulative)
-	{
-		if(cumulative || !sta->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellid).And(Selector::typeSubtype(effect.type, effect.subtype))))
-		{
-			//no such effect or cumulative - add new
-			logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), effect.Description());
-			sta->addNewBonus(std::make_shared<Bonus>(effect));
-		}
-		else
-		{
-			logBonus->trace("%s updated bonus: %s", sta->nodeName(), effect.Description());
-			actualizeEffect(sta, effect);
-		}
-	};
-
-	for(ui32 id : stacks)
-	{
-		CStack *s = gs->curB->getStack(id);
-		if(s)
-		{
-			for(const Bonus & fromEffect : effect)
-				processEffect(s, fromEffect, false);
-			for(const Bonus & fromEffect : cumulativeEffects)
-				processEffect(s, fromEffect, true);
-		}
-		else
-			logNetwork->error("Cannot find stack %d", id);
-	}
+	for(const auto & stackData : toAdd)
+		battleState->addUnitBonus(stackData.first, stackData.second);
+}
 
-	for(auto & para : uniqueBonuses)
-	{
-		CStack *s = gs->curB->getStack(para.first);
-		if(s)
-			processEffect(s, para.second, false);
-		else
-			logNetwork->error("Cannot find stack %d", para.first);
-	}
 
-	for(auto & para : cumulativeUniqueBonuses)
-	{
-		CStack *s = gs->curB->getStack(para.first);
-		if(s)
-			processEffect(s, para.second, true);
-		else
-			logNetwork->error("Cannot find stack %d", para.first);
-	}
+DLL_LINKAGE void StacksInjured::applyGs(CGameState *gs)
+{
+	applyBattle(gs->curB);
 }
 
-DLL_LINKAGE void StacksInjured::applyGs(CGameState *gs)
+DLL_LINKAGE void StacksInjured::applyBattle(IBattleState * battleState)
 {
 	for(BattleStackAttacked stackAttacked : stacks)
-		stackAttacked.applyGs(gs);
+		stackAttacked.applyBattle(battleState);
 }
 
-DLL_LINKAGE void StacksHealedOrResurrected::applyGs(CGameState *gs)
+DLL_LINKAGE void BattleUnitsChanged::applyGs(CGameState *gs)
 {
-	for(auto & elem : healedStacks)
-	{
-		CStack * changedStack = gs->curB->getStack(elem.stackId, false);
-		assert(changedStack);
-
-		//checking if we resurrect a stack that is under a living stack
-		auto accessibility = gs->curB->getAccesibility();
-
-		if(!changedStack->alive() && !accessibility.accessible(changedStack->position, changedStack))
-		{
-			logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->position.hex);
-			return; //position is already occupied
-		}
-
-		//applying changes
-		bool resurrected = !changedStack->alive(); //indicates if stack is resurrected or just healed
-		if(resurrected)
-		{
-			if(auto totalHealth = changedStack->health.available())
-				logGlobal->warn("Dead stack %s with positive total HP %d", changedStack->nodeName(), totalHealth);
-
-			changedStack->state.insert(EBattleStackState::ALIVE);
-		}
-
-		changedStack->setHealth(elem);
-
-		if(resurrected)
-		{
-			//removing all spells effects
-			auto selector = [](const Bonus * b)
-			{
-				//Special case: DISRUPTING_RAY is "immune" to dispell
-				//Other even PERMANENT effects can be removed
-				if(b->source == Bonus::SPELL_EFFECT)
-					return b->sid != SpellID::DISRUPTING_RAY;
-				else
-					return false;
-			};
-			changedStack->popBonuses(selector);
-		}
-		else if(cure)
-		{
-			//removing all effects from negative spells
-			auto selector = [](const Bonus * b)
-			{
-				//Special case: DISRUPTING_RAY is "immune" to dispell
-				//Other even PERMANENT effects can be removed
-				if(b->source == Bonus::SPELL_EFFECT)
-				{
-					const CSpell * sourceSpell = SpellID(b->sid).toSpell();
-					if(!sourceSpell)
-						return false;
-					return sourceSpell->id != SpellID::DISRUPTING_RAY && sourceSpell->isNegative();
-				}
-				else
-					return false;
-			};
-			changedStack->popBonuses(selector);
-		}
-	}
+	applyBattle(gs->curB);
 }
 
-DLL_LINKAGE void ObstaclesRemoved::applyGs(CGameState *gs)
+DLL_LINKAGE void BattleUnitsChanged::applyBattle(IBattleState * battleState)
 {
-	if(gs->curB) //if there is a battle
+	for(auto & elem : changedStacks)
 	{
-		for(const si32 rem_obst :obstacles)
+		switch(elem.operation)
 		{
-			for(int i=0; i<gs->curB->obstacles.size(); ++i)
-			{
-				if(gs->curB->obstacles[i]->uniqueID == rem_obst) //remove this obstacle
-				{
-					gs->curB->obstacles.erase(gs->curB->obstacles.begin() + i);
-					break;
-				}
-			}
+		case BattleChanges::EOperation::RESET_STATE:
+			battleState->setUnitState(elem.id, elem.data, elem.healthDelta);
+			break;
+		case BattleChanges::EOperation::REMOVE:
+			battleState->removeUnit(elem.id);
+			break;
+		case BattleChanges::EOperation::ADD:
+			battleState->addUnit(elem.id, elem.data);
+			break;
+		default:
+			logNetwork->error("Unknown unit operation %d", (int)elem.operation);
+			break;
 		}
 	}
 }
 
-
-DLL_LINKAGE CatapultAttack::CatapultAttack()
-{
-	attacker = -1;
-}
-
-DLL_LINKAGE CatapultAttack::~CatapultAttack()
+DLL_LINKAGE void BattleObstaclesChanged::applyGs(CGameState * gs)
 {
+	if(gs->curB)
+		applyBattle(gs->curB);
 }
 
-DLL_LINKAGE void CatapultAttack::applyGs(CGameState *gs)
+DLL_LINKAGE void BattleObstaclesChanged::applyBattle(IBattleState * battleState)
 {
-	if(gs->curB && gs->curB->town && gs->curB->town->fortLevel() != CGTownInstance::NONE) //if there is a battle and it's a siege
+	for(const auto & change : changes)
 	{
-		for(const auto &it :attackedParts)
+		switch(change.operation)
 		{
-			gs->curB->si.wallState[it.attackedPart] =
-			        SiegeInfo::applyDamage(EWallState::EWallState(gs->curB->si.wallState[it.attackedPart]), it.damageDealt);
+		case BattleChanges::EOperation::REMOVE:
+			battleState->removeObstacle(change.id);
+			break;
+		case BattleChanges::EOperation::ADD:
+			battleState->addObstacle(change);
+			break;
+		default:
+			logNetwork->error("Unknown obstacle operation %d", (int)change.operation);
+			break;
 		}
 	}
 }
 
-DLL_LINKAGE std::string CatapultAttack::AttackInfo::toString() const
+DLL_LINKAGE CatapultAttack::CatapultAttack()
 {
-	return boost::str(boost::format("{AttackInfo: destinationTile '%d', attackedPart '%d', damageDealt '%d'}")
-					  % destinationTile % static_cast<int>(attackedPart) % static_cast<int>(damageDealt));
+	attacker = -1;
 }
 
-DLL_LINKAGE std::string CatapultAttack::toString() const
+DLL_LINKAGE CatapultAttack::~CatapultAttack()
 {
-	std::ostringstream out;
-	out << "[";
-	for(auto it = attackedParts.begin(); it != attackedParts.end(); ++it)
-	{
-		out << it->toString();
-		if(std::prev(attackedParts.end()) != it) out << ", ";
-	}
-	out << "]";
-
-	return boost::str(boost::format("{CatapultAttack: attackedParts '%s', attacker '%d'}") % out.str() % attacker);
 }
 
-DLL_LINKAGE void BattleStacksRemoved::applyGs(CGameState *gs)
+DLL_LINKAGE void CatapultAttack::applyGs(CGameState * gs)
 {
-	if(!gs->curB)
-		return;
-
-	while(!stackIDs.empty())
-	{
-		ui32 rem_stack = *stackIDs.begin();
-
-		for(int b=0; b<gs->curB->stacks.size(); ++b) //find it in vector of stacks
-		{
-			if(gs->curB->stacks[b]->ID == rem_stack) //if found
-			{
-				CStack * toRemove = gs->curB->stacks[b];
-
-				toRemove->state.erase(EBattleStackState::ALIVE);
-				toRemove->state.erase(EBattleStackState::GHOST_PENDING);
-				toRemove->state.insert(EBattleStackState::GHOST);
-				toRemove->detachFromAll();//TODO: may be some bonuses should remain
-
-				//stack may be removed instantly (not being killed first)
-				//handle clone remove also here
-				if(toRemove->cloneID >= 0)
-				{
-					stackIDs.insert(toRemove->cloneID);
-					toRemove->cloneID = -1;
-				}
-
-				//cleanup remaining clone links if any
-				for(CStack * s : gs->curB->stacks)
-				{
-					if(s->cloneID == toRemove->ID)
-						s->cloneID = -1;
-				}
-
-				break;
-			}
-		}
-
-		stackIDs.erase(rem_stack);
-	}
+	if(gs->curB)
+		applyBattle(gs->curB);
 }
 
-DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
+DLL_LINKAGE void CatapultAttack::applyBattle(IBattleState * battleState)
 {
-	newStackID = 0;
-	if(!BattleHex(pos).isValid())
-	{
-		logNetwork->warn("No place found for new stack!");
+	auto town = battleState->getDefendedTown();
+	if(!town)
 		return;
-	}
-
-	CStackBasicDescriptor csbd(creID, amount);
-	CStack * addedStack = gs->curB->generateNewStack(csbd, side, SlotID::SUMMONED_SLOT_PLACEHOLDER, pos); //TODO: netpacks?
-	if(summoned)
-		addedStack->state.insert(EBattleStackState::SUMMONED);
 
-	addedStack->localInit(gs->curB.get());
-	gs->curB->stacks.push_back(addedStack);
+	if(town->fortLevel() == CGTownInstance::NONE)
+		return;
 
-	newStackID = addedStack->ID;
+	for(const auto & part : attackedParts)
+	{
+		auto newWallState = SiegeInfo::applyDamage(EWallState::EWallState(battleState->getWallState(part.attackedPart)), part.damageDealt);
+		battleState->setWallState(part.attackedPart, newWallState);
+	}
 }
 
 DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState * gs)
@@ -1837,7 +1550,7 @@ DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState * gs)
 		}
 		case CLONED:
 		{
-			stack->state.insert(EBattleStackState::CLONED);
+			stack->cloned = true;
 			break;
 		}
 		case HAS_CLONE:

+ 8 - 8
lib/VCMI_Lib.cpp

@@ -33,13 +33,13 @@
 
 LibClasses * VLC = nullptr;
 
-DLL_LINKAGE void preinitDLL(CConsoleHandler *Console)
+DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential)
 {
 	console = Console;
 	VLC = new LibClasses();
 	try
 	{
-		VLC->loadFilesystem();
+		VLC->loadFilesystem(onlyEssential);
 	}
 	catch(...)
 	{
@@ -48,9 +48,9 @@ DLL_LINKAGE void preinitDLL(CConsoleHandler *Console)
 	}
 }
 
-DLL_LINKAGE void loadDLLClasses()
+DLL_LINKAGE void loadDLLClasses(bool onlyEssential)
 {
-	VLC->init();
+	VLC->init(onlyEssential);
 }
 
 const IBonusTypeHandler * LibClasses::getBth() const
@@ -58,7 +58,7 @@ const IBonusTypeHandler * LibClasses::getBth() const
 	return bth;
 }
 
-void LibClasses::loadFilesystem()
+void LibClasses::loadFilesystem(bool onlyEssential)
 {
 	CStopWatch totalTime;
 	CStopWatch loadTime;
@@ -72,7 +72,7 @@ void LibClasses::loadFilesystem()
 	modh = new CModHandler();
 	logGlobal->info("\tMod handler: %d ms", loadTime.getDiff());
 
-	modh->loadMods();
+	modh->loadMods(onlyEssential);
 	modh->loadModFilesystems();
 	logGlobal->info("\tMod filesystems: %d ms", loadTime.getDiff());
 
@@ -90,7 +90,7 @@ template <class Handler> void createHandler(Handler *&handler, const std::string
 	logHandlerLoaded(name, timer);
 }
 
-void LibClasses::init()
+void LibClasses::init(bool onlyEssential)
 {
 	CStopWatch pomtime, totalTime;
 
@@ -124,7 +124,7 @@ void LibClasses::init()
 
 	modh->load();
 
-	modh->afterLoad();
+	modh->afterLoad(onlyEssential);
 
 	//FIXME: make sure that everything is ok after game restart
 	//TODO: This should be done every time mod config changes

+ 4 - 4
lib/VCMI_Lib.h

@@ -53,11 +53,11 @@ public:
 
 	LibClasses(); //c-tor, loads .lods and NULLs handlers
 	~LibClasses();
-	void init(); //uses standard config file
+	void init(bool onlyEssential); //uses standard config file
 	void clear(); //deletes all handlers and its data
 
 
-	void loadFilesystem();// basic initialization. should be called before init()
+	void loadFilesystem(bool onlyEssential);// basic initialization. should be called before init()
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -85,6 +85,6 @@ public:
 
 extern DLL_LINKAGE LibClasses * VLC;
 
-DLL_LINKAGE void preinitDLL(CConsoleHandler *Console);
-DLL_LINKAGE void loadDLLClasses();
+DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential = false);
+DLL_LINKAGE void loadDLLClasses(bool onlyEssential = false);
 

+ 61 - 9
lib/VCMI_lib.cbp

@@ -17,8 +17,10 @@
 				<Option run_host_application_in_terminal="1" />
 				<Option createStaticLib="1" />
 				<Compiler>
-					<Add option="-g" />
 					<Add option="-Og" />
+					<Add option="-g" />
+					<Add directory="$(#zlib.include)" />
+					<Add directory="lib/" />
 				</Compiler>
 				<Linker>
 					<Add option="-lws2_32" />
@@ -33,6 +35,8 @@
 					<Add option="-liconv" />
 					<Add option="-ldbghelp" />
 					<Add directory="$(#boost.lib32)" />
+					<Add directory="$(#sdl2.lib)" />
+					<Add directory="$(#zlib.lib)" />
 				</Linker>
 			</Target>
 			<Target title="Release-win32">
@@ -45,6 +49,8 @@
 				<Compiler>
 					<Add option="-fomit-frame-pointer" />
 					<Add option="-O2" />
+					<Add directory="$(#zlib.include)" />
+					<Add directory="lib/" />
 				</Compiler>
 				<Linker>
 					<Add option="-s" />
@@ -59,6 +65,8 @@
 					<Add option="-lboost_date_time$(#boost.libsuffix)" />
 					<Add option="-liconv" />
 					<Add directory="$(#boost.lib32)" />
+					<Add directory="$(#sdl2.lib)" />
+					<Add directory="$(#zlib.lib)" />
 				</Linker>
 			</Target>
 			<Target title="Debug-win64">
@@ -74,6 +82,7 @@
 					<Add option="-Og" />
 					<Add option="-g" />
 					<Add directory="$(#zlib64.include)" />
+					<Add directory="lib/" />
 				</Compiler>
 				<Linker>
 					<Add option="-lws2_32" />
@@ -87,8 +96,8 @@
 					<Add option="-lboost_date_time$(#boost.libsuffix)" />
 					<Add option="-liconv" />
 					<Add option="-ldbghelp" />
-					<Add directory="$(#sdl2.lib64)" />
 					<Add directory="$(#boost.lib64)" />
+					<Add directory="$(#sdl2.lib64)" />
 					<Add directory="$(#zlib64.lib)" />
 				</Linker>
 			</Target>
@@ -113,17 +122,15 @@
 			<Add option="-DVCMI_NO_EXTRA_VERSION" />
 			<Add directory="." />
 			<Add directory="$(#sdl2.include)" />
-			<Add directory="$(#zlib.include)" />
 			<Add directory="../include" />
 		</Compiler>
 		<Linker>
 			<Add directory="../" />
-			<Add directory="$(#sdl2.lib)" />
-			<Add directory="$(#zlib.lib)" />
 		</Linker>
 		<Unit filename="../Global.h" />
 		<Unit filename="../Version.h" />
 		<Unit filename="../include/vstd/CLoggerBase.h" />
+		<Unit filename="../include/vstd/RNG.h" />
 		<Unit filename="AI_Base.h" />
 		<Unit filename="CArtHandler.cpp" />
 		<Unit filename="CArtHandler.h" />
@@ -216,6 +223,8 @@
 		<Unit filename="battle/BattleHex.h" />
 		<Unit filename="battle/BattleInfo.cpp" />
 		<Unit filename="battle/BattleInfo.h" />
+		<Unit filename="battle/BattleProxy.cpp" />
+		<Unit filename="battle/BattleProxy.h" />
 		<Unit filename="battle/CBattleInfoCallback.cpp" />
 		<Unit filename="battle/CBattleInfoCallback.h" />
 		<Unit filename="battle/CBattleInfoEssentials.cpp" />
@@ -226,12 +235,21 @@
 		<Unit filename="battle/CObstacleInstance.h" />
 		<Unit filename="battle/CPlayerBattleCallback.cpp" />
 		<Unit filename="battle/CPlayerBattleCallback.h" />
+		<Unit filename="battle/CUnitState.cpp" />
+		<Unit filename="battle/CUnitState.h" />
+		<Unit filename="battle/Destination.cpp" />
+		<Unit filename="battle/Destination.h" />
+		<Unit filename="battle/IBattleState.cpp" />
+		<Unit filename="battle/IBattleState.h" />
+		<Unit filename="battle/IUnitInfo.h" />
 		<Unit filename="battle/ReachabilityInfo.cpp" />
 		<Unit filename="battle/ReachabilityInfo.h" />
 		<Unit filename="battle/SideInBattle.cpp" />
 		<Unit filename="battle/SideInBattle.h" />
 		<Unit filename="battle/SiegeInfo.cpp" />
 		<Unit filename="battle/SiegeInfo.h" />
+		<Unit filename="battle/Unit.cpp" />
+		<Unit filename="battle/Unit.h" />
 		<Unit filename="filesystem/AdapterLoaders.cpp" />
 		<Unit filename="filesystem/AdapterLoaders.h" />
 		<Unit filename="filesystem/CArchiveLoader.cpp" />
@@ -362,21 +380,55 @@
 		<Unit filename="serializer/JsonSerializeFormat.h" />
 		<Unit filename="serializer/JsonSerializer.cpp" />
 		<Unit filename="serializer/JsonSerializer.h" />
+		<Unit filename="serializer/JsonTreeSerializer.h" />
 		<Unit filename="spells/AdventureSpellMechanics.cpp" />
 		<Unit filename="spells/AdventureSpellMechanics.h" />
 		<Unit filename="spells/BattleSpellMechanics.cpp" />
 		<Unit filename="spells/BattleSpellMechanics.h" />
-		<Unit filename="spells/CDefaultSpellMechanics.cpp" />
-		<Unit filename="spells/CDefaultSpellMechanics.h" />
 		<Unit filename="spells/CSpellHandler.cpp" />
 		<Unit filename="spells/CSpellHandler.h" />
-		<Unit filename="spells/CreatureSpellMechanics.cpp" />
-		<Unit filename="spells/CreatureSpellMechanics.h" />
 		<Unit filename="spells/ISpellMechanics.cpp" />
 		<Unit filename="spells/ISpellMechanics.h" />
 		<Unit filename="spells/Magic.h" />
+		<Unit filename="spells/Problem.cpp" />
+		<Unit filename="spells/Problem.h" />
+		<Unit filename="spells/TargetCondition.cpp" />
+		<Unit filename="spells/TargetCondition.h" />
 		<Unit filename="spells/ViewSpellInt.cpp" />
 		<Unit filename="spells/ViewSpellInt.h" />
+		<Unit filename="spells/effects/Catapult.cpp" />
+		<Unit filename="spells/effects/Catapult.h" />
+		<Unit filename="spells/effects/Clone.cpp" />
+		<Unit filename="spells/effects/Clone.h" />
+		<Unit filename="spells/effects/Damage.cpp" />
+		<Unit filename="spells/effects/Damage.h" />
+		<Unit filename="spells/effects/Dispel.cpp" />
+		<Unit filename="spells/effects/Dispel.h" />
+		<Unit filename="spells/effects/Effect.cpp" />
+		<Unit filename="spells/effects/Effect.h" />
+		<Unit filename="spells/effects/Effects.cpp" />
+		<Unit filename="spells/effects/Effects.h" />
+		<Unit filename="spells/effects/EffectsFwd.h" />
+		<Unit filename="spells/effects/Heal.cpp" />
+		<Unit filename="spells/effects/Heal.h" />
+		<Unit filename="spells/effects/LocationEffect.cpp" />
+		<Unit filename="spells/effects/LocationEffect.h" />
+		<Unit filename="spells/effects/Obstacle.cpp" />
+		<Unit filename="spells/effects/Obstacle.h" />
+		<Unit filename="spells/effects/Registry.cpp" />
+		<Unit filename="spells/effects/Registry.h" />
+		<Unit filename="spells/effects/RemoveObstacle.cpp" />
+		<Unit filename="spells/effects/RemoveObstacle.h" />
+		<Unit filename="spells/effects/Sacrifice.cpp" />
+		<Unit filename="spells/effects/Sacrifice.h" />
+		<Unit filename="spells/effects/Summon.cpp" />
+		<Unit filename="spells/effects/Summon.h" />
+		<Unit filename="spells/effects/Teleport.cpp" />
+		<Unit filename="spells/effects/Teleport.h" />
+		<Unit filename="spells/effects/Timed.cpp" />
+		<Unit filename="spells/effects/Timed.h" />
+		<Unit filename="spells/effects/UnitEffect.cpp" />
+		<Unit filename="spells/effects/UnitEffect.h" />
 		<Unit filename="vcmi_endian.h" />
 		<Extensions>
 			<code_completion />

+ 22 - 0
lib/VCMI_lib.vcxproj

@@ -222,7 +222,18 @@
     <ClCompile Include="spells\BattleSpellMechanics.cpp" />
     <ClCompile Include="spells\CreatureSpellMechanics.cpp" />
     <ClCompile Include="spells\CDefaultSpellMechanics.cpp" />
+    <ClCompile Include="spells\Problem.cpp" />
     <ClCompile Include="spells\ViewSpellInt.cpp" />
+    <ClCompile Include="spells\effects\Effect.cpp" />
+    <ClCompile Include="spells\effects\Effects.cpp" />
+    <ClCompile Include="spells\effects\Clone.cpp" />
+    <ClCompile Include="spells\effects\Damage.cpp" />
+    <ClCompile Include="spells\effects\GlobalEffect.cpp" />
+    <ClCompile Include="spells\effects\LocationEffect.cpp" />
+    <ClCompile Include="spells\effects\Registry.cpp" />
+    <ClCompile Include="spells\effects\StackEffect.cpp" />
+    <ClCompile Include="spells\effects\Summon.cpp" />
+    <ClCompile Include="spells\effects\Timed.cpp" />
     <ClCompile Include="filesystem\AdapterLoaders.cpp" />
     <ClCompile Include="filesystem\CArchiveLoader.cpp" />
     <ClCompile Include="filesystem\CBinaryReader.cpp" />
@@ -429,7 +440,18 @@
     <ClInclude Include="spells\ISpellMechanics.h" />
     <ClInclude Include="spells\Magic.h" />
     <ClInclude Include="spells\SpellMechanics.h" />
+    <ClInclude Include="spells\Problem.h" />
     <ClInclude Include="spells\ViewSpellInt.h" />
+    <ClInclude Include="spells\effects\Effect.h" />
+    <ClInclude Include="spells\effects\Effects.h" />
+    <ClInclude Include="spells\effects\Clone.h" />
+    <ClInclude Include="spells\effects\Damage.h" />
+    <ClInclude Include="spells\effects\GlobalEffect.h" />
+    <ClInclude Include="spells\effects\LocationEffect.h" />
+    <ClInclude Include="spells\effects\Registry.h" />
+    <ClInclude Include="spells\effects\StackEffect.h" />
+    <ClInclude Include="spells\effects\Summon.h" />
+    <ClInclude Include="spells\effects\Timed.h" />
     <ClInclude Include="StartInfo.h" />
     <ClInclude Include="StdInc.h" />
     <ClInclude Include="StringConstants.h" />

+ 14 - 12
lib/battle/AccessibilityInfo.cpp

@@ -9,29 +9,31 @@
  */
 #include "StdInc.h"
 #include "AccessibilityInfo.h"
-#include "../CStack.h"
+#include "Unit.h"
 #include "../GameConstants.h"
 
-bool AccessibilityInfo::accessible(BattleHex tile, const CStack * stack) const
+bool AccessibilityInfo::accessible(BattleHex tile, const battle::Unit * stack) const
 {
-	return accessible(tile, stack->doubleWide(), stack->side);
+	return accessible(tile, stack->doubleWide(), stack->unitSide());
 }
 
 bool AccessibilityInfo::accessible(BattleHex tile, bool doubleWide, ui8 side) const
 {
 	// All hexes that stack would cover if standing on tile have to be accessible.
-	for(auto hex : CStack::getHexes(tile, doubleWide, side))
+	//do not use getHexes for speed reasons
+	if(!tile.isValid())
+		return false;
+	if(at(tile) != EAccessibility::ACCESSIBLE && !(at(tile) == EAccessibility::GATE && side == BattleSide::DEFENDER))
+		return false;
+
+	if(doubleWide)
 	{
-		// If the hex is out of range then the tile isn't accessible
-		if(!hex.isValid())
+		auto otherHex = battle::Unit::occupiedHex(tile, doubleWide, side);
+		if(!otherHex.isValid())
 			return false;
-		// If we're no defender which step on gate and the hex isn't accessible, then the tile
-		// isn't accessible
-		else if(at(hex) != EAccessibility::ACCESSIBLE &&
-				!(at(hex) == EAccessibility::GATE && side == BattleSide::DEFENDER))
-		{
+		if(at(otherHex) != EAccessibility::ACCESSIBLE && !(at(otherHex) == EAccessibility::GATE && side == BattleSide::DEFENDER))
 			return false;
-		}
 	}
+
 	return true;
 }

+ 5 - 2
lib/battle/AccessibilityInfo.h

@@ -11,7 +11,10 @@
 #include "BattleHex.h"
 #include "../GameConstants.h"
 
-class CStack;
+namespace battle
+{
+	class Unit;
+}
 
 //Accessibility is property of hex in battle. It doesn't depend on stack, side's perspective and so on.
 enum class EAccessibility
@@ -30,6 +33,6 @@ typedef std::array<EAccessibility, GameConstants::BFIELD_SIZE> TAccessibilityArr
 
 struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray
 {
-	bool accessible(BattleHex tile, const CStack * stack) const; //checks for both tiles if stack is double wide
+	bool accessible(BattleHex tile, const battle::Unit * stack) const; //checks for both tiles if stack is double wide
 	bool accessible(BattleHex tile, bool doubleWide, ui8 side) const; //checks for both tiles if stack is double wide
 };

+ 103 - 40
lib/battle/BattleAction.cpp

@@ -10,77 +10,77 @@
 
 #include "StdInc.h"
 #include "BattleAction.h"
-#include "../CStack.h"
+#include "Unit.h"
+#include "CBattleInfoCallback.h"
 
-using namespace Battle;
+static const int32_t INVALID_UNIT_ID = -1000;
 
 BattleAction::BattleAction():
 	side(-1),
 	stackNumber(-1),
-	actionType(INVALID),
-	destinationTile(-1),
-	additionalInfo(-1),
-	selectedStack(-1)
+	actionType(EActionType::INVALID),
+	actionSubtype(-1)
 {
 }
 
-BattleAction BattleAction::makeHeal(const CStack * healer, const CStack * healed)
+BattleAction BattleAction::makeHeal(const battle::Unit * healer, const battle::Unit * healed)
 {
 	BattleAction ba;
-	ba.side = healer->side;
-	ba.actionType = STACK_HEAL;
-	ba.stackNumber = healer->ID;
-	ba.destinationTile = healed->position;
+	ba.side = healer->unitSide();
+	ba.actionType = EActionType::STACK_HEAL;
+	ba.stackNumber = healer->unitId();
+	ba.aimToUnit(healed);
 	return ba;
 }
 
-BattleAction BattleAction::makeDefend(const CStack * stack)
+BattleAction BattleAction::makeDefend(const battle::Unit * stack)
 {
 	BattleAction ba;
-	ba.side = stack->side;
-	ba.actionType = DEFEND;
-	ba.stackNumber = stack->ID;
+	ba.side = stack->unitSide();
+	ba.actionType = EActionType::DEFEND;
+	ba.stackNumber = stack->unitId();
 	return ba;
 }
 
-
-BattleAction BattleAction::makeMeleeAttack(const CStack * stack, const CStack * attacked, BattleHex attackFrom)
+BattleAction BattleAction::makeMeleeAttack(const battle::Unit * stack, BattleHex destination, BattleHex attackFrom, bool returnAfterAttack)
 {
 	BattleAction ba;
-	ba.side = stack->side;
-	ba.actionType = WALK_AND_ATTACK;
-	ba.stackNumber = stack->ID;
-	ba.destinationTile = attackFrom;
-	ba.additionalInfo = attacked->position;
+	ba.side = stack->unitSide(); //FIXME: will it fail if stack mind controlled?
+	ba.actionType = EActionType::WALK_AND_ATTACK;
+	ba.stackNumber = stack->unitId();
+	ba.aimToHex(attackFrom);
+	ba.aimToHex(destination);
+	if(returnAfterAttack && stack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE))
+		ba.aimToHex(stack->getPosition());
 	return ba;
-
 }
-BattleAction BattleAction::makeWait(const CStack * stack)
+
+BattleAction BattleAction::makeWait(const battle::Unit * stack)
 {
 	BattleAction ba;
-	ba.side = stack->side;
-	ba.actionType = WAIT;
-	ba.stackNumber = stack->ID;
+	ba.side = stack->unitSide();
+	ba.actionType = EActionType::WAIT;
+	ba.stackNumber = stack->unitId();
 	return ba;
 }
 
-BattleAction BattleAction::makeShotAttack(const CStack * shooter, const CStack * target)
+BattleAction BattleAction::makeShotAttack(const battle::Unit * shooter, const battle::Unit * target)
 {
 	BattleAction ba;
-	ba.side = shooter->side;
-	ba.actionType = SHOOT;
-	ba.stackNumber = shooter->ID;
-	ba.destinationTile = target->position;
+	ba.side = shooter->unitSide();
+	ba.actionType = EActionType::SHOOT;
+	ba.stackNumber = shooter->unitId();
+	ba.aimToUnit(target);
 	return ba;
 }
 
-BattleAction BattleAction::makeMove(const CStack * stack, BattleHex dest)
+BattleAction BattleAction::makeMove(const battle::Unit * stack, BattleHex dest)
 {
 	BattleAction ba;
-	ba.side = stack->side;
-	ba.actionType = WALK;
-	ba.stackNumber = stack->ID;
-	ba.destinationTile = dest;
+	ba.side = stack->unitSide();
+	ba.actionType = EActionType::WALK;
+	ba.stackNumber = stack->unitId();
+	ba.aimToHex(dest);
 	return ba;
 }
 
@@ -88,7 +88,7 @@ BattleAction BattleAction::makeEndOFTacticPhase(ui8 side)
 {
 	BattleAction ba;
 	ba.side = side;
-	ba.actionType = END_TACTIC_PHASE;
+	ba.actionType = EActionType::END_TACTIC_PHASE;
 	return ba;
 }
 
@@ -97,11 +97,74 @@ std::string BattleAction::toString() const
 	std::stringstream actionTypeStream;
 	actionTypeStream << actionType;
 
-	boost::format fmt("{BattleAction: side '%d', stackNumber '%d', actionType '%s', destinationTile '%s', additionalInfo '%d', selectedStack '%d'}");
-	fmt % static_cast<int>(side) % stackNumber % actionTypeStream.str() % destinationTile % additionalInfo % selectedStack;
+	std::stringstream targetStream;
+
+	for(const DestinationInfo & info : target)
+	{
+		if(info.unitValue == INVALID_UNIT_ID)
+		{
+			targetStream << info.hexValue;
+		}
+		else
+		{
+			targetStream << info.unitValue;
+			targetStream << "@";
+			targetStream << info.hexValue;
+		}
+		targetStream << ",";
+	}
+
+	boost::format fmt("{BattleAction: side '%d', stackNumber '%d', actionType '%s', actionSubtype '%d', target {%s}}");
+	fmt % static_cast<int>(side) % stackNumber % actionTypeStream.str() % actionSubtype % targetStream.str();
 	return fmt.str();
 }
 
+void BattleAction::aimToHex(const BattleHex & destination)
+{
+	DestinationInfo info;
+	info.hexValue = destination;
+	info.unitValue = INVALID_UNIT_ID;
+
+	target.push_back(info);
+}
+
+void BattleAction::aimToUnit(const battle::Unit * destination)
+{
+	DestinationInfo info;
+	info.hexValue = destination->getPosition();
+	info.unitValue = destination->unitId();
+
+	target.push_back(info);
+}
+
+battle::Target BattleAction::getTarget(const CBattleInfoCallback * cb) const
+{
+	battle::Target ret;
+
+	for(auto & destination : target)
+	{
+		if(destination.unitValue == INVALID_UNIT_ID)
+			ret.emplace_back(destination.hexValue);
+		else
+			ret.emplace_back(cb->battleGetUnitByID(destination.unitValue));
+	}
+
+	return ret;
+}
+
+void BattleAction::setTarget(const battle::Target & target_)
+{
+    target.clear();
+	for(auto & destination : target_)
+	{
+		if(destination.unitValue == nullptr)
+			aimToHex(destination.hexValue);
+		else
+			aimToUnit(destination.unitValue);
+	}
+}
+
+
 std::ostream & operator<<(std::ostream & os, const BattleAction & ba)
 {
 	os << ba.toString();

+ 46 - 21
lib/battle/BattleAction.h

@@ -8,42 +8,67 @@
  *
  */
 #pragma once
-#include "BattleHex.h"
+#include "Destination.h"
 #include "../GameConstants.h"
 
-class CStack;
+class CBattleInfoCallback;
+
+namespace battle
+{
+	class Unit;
+}
 
 /// A struct which handles battle actions like defending, walking,... - represents a creature stack in a battle
-struct DLL_LINKAGE BattleAction
+class DLL_LINKAGE BattleAction
 {
-	ui8 side; //who made this action: false - left, true - right player
+public:
+	ui8 side; //who made this action
 	ui32 stackNumber; //stack ID, -1 left hero, -2 right hero,
-	Battle::ActionType actionType; //use ActionType enum for values
-	BattleHex destinationTile;
-	si32 additionalInfo; // e.g. spell number if type is 1 || 10; tile to attack if type is 6
-	si32 selectedStack; //spell subject for teleport / sacrifice
+	EActionType actionType; //use ActionType enum for values
+
+	si32 actionSubtype;
+
+	BattleAction();
+
+	static BattleAction makeHeal(const battle::Unit * healer, const battle::Unit * healed);
+	static BattleAction makeDefend(const battle::Unit * stack);
+	static BattleAction makeWait(const battle::Unit * stack);
+	static BattleAction makeMeleeAttack(const battle::Unit * stack, BattleHex destination, BattleHex attackFrom, bool returnAfterAttack = true);
+	static BattleAction makeShotAttack(const battle::Unit * shooter, const battle::Unit * target);
+	static BattleAction makeMove(const battle::Unit * stack, BattleHex dest);
+	static BattleAction makeEndOFTacticPhase(ui8 side);
 
-	template <typename Handler> void serialize(Handler &h, const int version)
+	std::string toString() const;
+
+	void aimToHex(const BattleHex & destination);
+	void aimToUnit(const battle::Unit * destination);
+
+	battle::Target getTarget(const CBattleInfoCallback * cb) const;
+	void setTarget(const battle::Target & target_);
+
+	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 		h & side;
 		h & stackNumber;
 		h & actionType;
-		h & destinationTile;
-		h & additionalInfo;
-		h & selectedStack;
+		h & actionSubtype;
+		h & target;
 	}
+private:
 
-	BattleAction();
+	struct DestinationInfo
+	{
+		int32_t unitValue;
+		BattleHex hexValue;
 
-	static BattleAction makeHeal(const CStack * healer, const CStack * healed);
-	static BattleAction makeDefend(const CStack * stack);
-	static BattleAction makeWait(const CStack * stack);
-	static BattleAction makeMeleeAttack(const CStack * stack, const CStack * attacked, BattleHex attackFrom = BattleHex::INVALID);
-	static BattleAction makeShotAttack(const CStack * shooter, const CStack * target);
-	static BattleAction makeMove(const CStack * stack, BattleHex dest);
-	static BattleAction makeEndOFTacticPhase(ui8 side);
+		template <typename Handler> void serialize(Handler & h, const int version)
+		{
+			h & unitValue;
+			h & hexValue;
+		}
+	};
 
-	std::string toString() const;
+	std::vector<DestinationInfo> target;
 };
 
 DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleAction & ba); //todo: remove

+ 10 - 21
lib/battle/BattleAttackInfo.cpp

@@ -9,40 +9,29 @@
  */
 #include "StdInc.h"
 #include "BattleAttackInfo.h"
+#include "CUnitState.h"
 
-
-BattleAttackInfo::BattleAttackInfo(const CStack * Attacker, const CStack * Defender, bool Shooting):
-	attackerHealth(Attacker->health), defenderHealth(Defender->health)
+BattleAttackInfo::BattleAttackInfo(const battle::Unit * Attacker, const battle::Unit * Defender, bool Shooting)
+	: attacker(Attacker),
+	defender(Defender)
 {
-	attacker = Attacker;
-	defender = Defender;
-
-	attackerBonuses = Attacker;
-	defenderBonuses = Defender;
-
-	attackerPosition = Attacker->position;
-	defenderPosition = Defender->position;
-
 	shooting = Shooting;
 	chargedFields = 0;
-
-	luckyHit = false;
-	unluckyHit = false;
-	deathBlow = false;
-	ballistaDoubleDamage = false;
+	additiveBonus = 0.0;
+	multBonus = 1.0;
 }
 
 BattleAttackInfo BattleAttackInfo::reverse() const
 {
 	BattleAttackInfo ret = *this;
+
 	std::swap(ret.attacker, ret.defender);
-	std::swap(ret.attackerBonuses, ret.defenderBonuses);
-	std::swap(ret.attackerPosition, ret.defenderPosition);
-	std::swap(ret.attackerHealth, ret.defenderHealth);
 
 	ret.shooting = false;
 	ret.chargedFields = 0;
-	ret.luckyHit = ret.ballistaDoubleDamage = ret.deathBlow = false;
+
+	ret.additiveBonus = 0.0;
+	ret.multBonus = 1.0;
 
 	return ret;
 }

+ 10 - 13
lib/battle/BattleAttackInfo.h

@@ -8,27 +8,24 @@
  *
  */
 #pragma once
-#include "BattleHex.h"
-#include "../CStack.h"
 
-class IBonusBearer;
+namespace battle
+{
+	class Unit;
+	class CUnitState;
+}
 
 struct DLL_LINKAGE BattleAttackInfo
 {
-	const IBonusBearer *attackerBonuses, *defenderBonuses;
-	const CStack *attacker, *defender;
-	BattleHex attackerPosition, defenderPosition;
-
-	CHealth attackerHealth, defenderHealth;
+	const battle::Unit * attacker;
+	const battle::Unit * defender;
 
 	bool shooting;
 	int chargedFields;
 
-	bool luckyHit;
-	bool unluckyHit;
-	bool deathBlow;
-	bool ballistaDoubleDamage;
+	double additiveBonus;
+	double multBonus;
 
-	BattleAttackInfo(const CStack * Attacker, const CStack * Defender, bool Shooting = false);
+	BattleAttackInfo(const battle::Unit * Attacker, const battle::Unit * Defender, bool Shooting = false);
 	BattleAttackInfo reverse() const;
 };

+ 25 - 2
lib/battle/BattleHex.cpp

@@ -9,7 +9,6 @@
  */
 #include "StdInc.h"
 #include "BattleHex.h"
-#include "../GameConstants.h"
 
 BattleHex::BattleHex() : hex(INVALID) {}
 
@@ -53,7 +52,11 @@ void BattleHex::setY(si16 y)
 void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid)
 {
 	if(hasToBeValid)
-		assert(x >= 0 && x < GameConstants::BFIELD_WIDTH && y >= 0 && y < GameConstants::BFIELD_HEIGHT);
+	{
+		if(!(x >= 0 && x < GameConstants::BFIELD_WIDTH && y >= 0 && y < GameConstants::BFIELD_HEIGHT))
+			throw std::runtime_error("Valid hex required");
+	}
+
 	hex = x + y * GameConstants::BFIELD_WIDTH;
 }
 
@@ -129,6 +132,7 @@ BattleHex BattleHex::operator+(BattleHex::EDir dir) const
 std::vector<BattleHex> BattleHex::neighbouringTiles() const
 {
 	std::vector<BattleHex> ret;
+	ret.reserve(6);
 	for(EDir dir = EDir(0); dir <= EDir(5); dir = EDir(dir+1))
 		checkAndPush(cloneInDirection(dir, false), ret);
 	return ret;
@@ -201,3 +205,22 @@ std::ostream & operator<<(std::ostream & os, const BattleHex & hex)
 {
 	return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex);
 }
+
+static BattleHex::NeighbouringTilesCache calculateNeighbouringTiles()
+{
+	BattleHex::NeighbouringTilesCache ret;
+	ret.resize(GameConstants::BFIELD_SIZE);
+
+	for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
+	{
+		auto hexes = BattleHex(hex).neighbouringTiles();
+
+		size_t index = 0;
+		for(auto neighbour : hexes)
+			ret[hex].at(index++) = neighbour;
+	}
+
+	return ret;
+}
+
+const BattleHex::NeighbouringTilesCache BattleHex::neighbouringTilesCache = calculateNeighbouringTiles();

+ 12 - 0
lib/battle/BattleHex.h

@@ -20,6 +20,13 @@ namespace BattleSide
 	};
 }
 
+namespace GameConstants
+{
+	const int BFIELD_WIDTH = 17;
+	const int BFIELD_HEIGHT = 11;
+	const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT;
+}
+
 typedef boost::optional<ui8> BattleSideOpt;
 
 // for battle stacks' positions
@@ -67,6 +74,11 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
 	{
 		h & hex;
 	}
+
+    using NeighbouringTiles = std::array<BattleHex, 6>;
+    using NeighbouringTilesCache = std::vector<NeighbouringTiles>;
+
+    static const NeighbouringTilesCache neighbouringTilesCache;
 };
 
 DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex);

+ 433 - 129
lib/battle/BattleInfo.cpp

@@ -16,48 +16,7 @@
 #include "../mapObjects/CGTownInstance.h"
 #include "../CGeneralTextHandler.h"
 
-const CStack * BattleInfo::getNextStack() const
-{
-	std::vector<const CStack *> hlp;
-	battleGetStackQueue(hlp, 1, -1);
-
-	if(hlp.size())
-		return hlp[0];
-	else
-		return nullptr;
-}
-
-int BattleInfo::getAvaliableHex(CreatureID creID, ui8 side, int initialPos) const
-{
-	bool twoHex = VLC->creh->creatures[creID]->isDoubleWide();
-	//bool flying = VLC->creh->creatures[creID]->isFlying();
-
-	int pos;
-	if (initialPos > -1)
-		pos = initialPos;
-	else //summon elementals depending on player side
-	{
- 		if(side == BattleSide::ATTACKER)
-	 		pos = 0; //top left
- 		else
- 			pos = GameConstants::BFIELD_WIDTH - 1; //top right
- 	}
-
-	auto accessibility = getAccesibility();
-
-	std::set<BattleHex> occupyable;
-	for(int i = 0; i < accessibility.size(); i++)
-		if(accessibility.accessible(i, twoHex, side))
-			occupyable.insert(i);
-
-	if (occupyable.empty())
-	{
-		return BattleHex::INVALID; //all tiles are covered
-	}
-
-	return BattleHex::getClosestTile(side, pos, occupyable);
-}
-
+///BattleInfo
 std::pair< std::vector<BattleHex>, int > BattleInfo::getPath(BattleHex start, BattleHex dest, const CStack * stack)
 {
 	auto reachability = getReachability(stack);
@@ -79,31 +38,6 @@ std::pair< std::vector<BattleHex>, int > BattleInfo::getPath(BattleHex start, Ba
 	return std::make_pair(path, reachability.distances[dest]);
 }
 
-ui32 BattleInfo::calculateDmg(const CStack * attacker, const CStack * defender,
-	bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg, CRandomGenerator & rand)
-{
-	BattleAttackInfo bai(attacker, defender, shooting);
-	bai.chargedFields = charge;
-	bai.luckyHit = lucky;
-	bai.unluckyHit = unlucky;
-	bai.deathBlow = deathBlow;
-	bai.ballistaDoubleDamage = ballistaDoubleDmg;
-
-	TDmgRange range = calculateDmgRange(bai);
-
-	if(range.first != range.second)
-	{
-		ui32 sum = 0;
-		ui32 howManyToAv = std::min<ui32>(10, attacker->getCount());
-		for(int g=0; g<howManyToAv; ++g)
-			sum += (ui32)rand.nextInt(range.first, range.second);
-
-		return sum / howManyToAv;
-	}
-	else
-		return range.first;
-}
-
 void BattleInfo::calculateCasualties(std::map<ui32,si32> * casualties) const
 {
 	for(auto & elem : stacks)//setting casualties
@@ -115,26 +49,24 @@ void BattleInfo::calculateCasualties(std::map<ui32,si32> * casualties) const
 	}
 }
 
-CStack * BattleInfo::generateNewStack(const CStackInstance & base, ui8 side, SlotID slot, BattleHex position) const
+CStack * BattleInfo::generateNewStack(uint32_t id, const CStackInstance & base, ui8 side, SlotID slot, BattleHex position)
 {
-	int stackID = getIdForNewStack();
 	PlayerColor owner = sides[side].color;
 	assert((owner >= PlayerColor::PLAYER_LIMIT) ||
 		(base.armyObj && base.armyObj->tempOwner == owner));
 
-	auto ret = new CStack(&base, owner, stackID, side, slot);
-	ret->position = getAvaliableHex(base.getCreatureID(), side, position); //TODO: what if no free tile on battlefield was found?
-	ret->state.insert(EBattleStackState::ALIVE); //alive state indication
+	auto ret = new CStack(&base, owner, id, side, slot);
+	ret->initialPosition = getAvaliableHex(base.getCreatureID(), side, position); //TODO: what if no free tile on battlefield was found?
+	stacks.push_back(ret);
 	return ret;
 }
 
-CStack * BattleInfo::generateNewStack(const CStackBasicDescriptor & base, ui8 side, SlotID slot, BattleHex position) const
+CStack * BattleInfo::generateNewStack(uint32_t id, const CStackBasicDescriptor & base, ui8 side, SlotID slot, BattleHex position)
 {
-	int stackID = getIdForNewStack();
 	PlayerColor owner = sides[side].color;
-	auto ret = new CStack(&base, owner, stackID, side, slot);
-	ret->position = position;
-	ret->state.insert(EBattleStackState::ALIVE); //alive state indication
+	auto ret = new CStack(&base, owner, id, side, slot);
+	ret->initialPosition = position;
+	stacks.push_back(ret);
 	return ret;
 }
 
@@ -436,7 +368,7 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
 				CreatureID cre = warMachineArt->artType->warMachine;
 
 				if(cre != CreatureID::NONE)
-					stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex));
+					curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex);
 			}
 		};
 
@@ -481,8 +413,7 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
 			if(creatureBank && i->second->type->isDoubleWide())
 				pos += side ? BattleHex::LEFT : BattleHex::RIGHT;
 
-			CStack * stack = curB->generateNewStack(*i->second, side, i->first, pos);
-			stacks.push_back(stack);
+			curB->generateNewStack(curB->nextUnitId(), *i->second, side, i->first, pos);
 		}
 	}
 
@@ -491,9 +422,7 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
 	{
 		if (heroes[i] && heroes[i]->commander && heroes[i]->commander->alive)
 		{
-			CStack * stack = curB->generateNewStack (*heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER,
-				creatureBank ? commanderBank[i] : commanderField[i]);
-			stacks.push_back(stack);
+			curB->generateNewStack(curB->nextUnitId(), *heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, creatureBank ? commanderBank[i] : commanderField[i]);
 		}
 
 	}
@@ -501,16 +430,14 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
 	if (curB->town && curB->town->fortLevel() >= CGTownInstance::CITADEL)
 	{
 		// keep tower
-		CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -2);
-		stacks.push_back(stack);
+		curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -2);
 
 		if (curB->town->fortLevel() >= CGTownInstance::CASTLE)
 		{
 			// lower tower + upper tower
-			CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -4);
-			stacks.push_back(stack);
-			stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -3);
-			stacks.push_back(stack);
+			curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -4);
+
+			curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -3);
 		}
 
 		//moat
@@ -523,10 +450,6 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
 
 	std::stable_sort(stacks.begin(),stacks.end(),cmpst);
 
-	//spell level limiting bonus
-	curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::LEVEL_SPELL_IMMUNITY, Bonus::OTHER,
-		0, -1, -1, Bonus::INDEPENDENT_MAX));
-
 	auto neutral = std::make_shared<CreatureAlignmentLimiter>(EAlignment::NEUTRAL);
 	auto good = std::make_shared<CreatureAlignmentLimiter>(EAlignment::GOOD);
 	auto evil = std::make_shared<CreatureAlignmentLimiter>(EAlignment::EVIL);
@@ -660,11 +583,6 @@ const CGHeroInstance * BattleInfo::getHero(PlayerColor player) const
 	return nullptr;
 }
 
-PlayerColor BattleInfo::theOtherPlayer(PlayerColor player) const
-{
-	return sides[!whatSide(player)].color;
-}
-
 ui8 BattleInfo::whatSide(PlayerColor player) const
 {
 	for(int i = 0; i < sides.size(); i++)
@@ -675,29 +593,6 @@ ui8 BattleInfo::whatSide(PlayerColor player) const
 	return -1;
 }
 
-int BattleInfo::getIdForNewStack() const
-{
-	if(stacks.size())
-	{
-		//stacks vector may be sorted not by ID and they may be not contiguous -> find stack with max ID
-		auto highestIDStack = *std::max_element(stacks.begin(), stacks.end(),
-								[](const CStack *a, const CStack *b) { return a->ID < b->ID; });
-
-		return highestIDStack->ID + 1;
-	}
-
-	return 0;
-}
-
-std::shared_ptr<CObstacleInstance> BattleInfo::getObstacleOnTile(BattleHex tile) const
-{
-	for(auto &obs : obstacles)
-		if(vstd::contains(obs->getAffectedTiles(), tile))
-			return obs;
-
-	return std::shared_ptr<CObstacleInstance>();
-}
-
 BattlefieldBI::BattlefieldBI BattleInfo::battlefieldTypeToBI(BFieldType bfieldType)
 {
 	static const std::map<BFieldType, BattlefieldBI::BattlefieldBI> theMap =
@@ -728,7 +623,7 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive)
 }
 
 BattleInfo::BattleInfo()
-	: round(-1), activeStack(-1), selectedStack(-1), town(nullptr), tile(-1,-1,-1),
+	: round(-1), activeStack(-1), town(nullptr), tile(-1,-1,-1),
 	battlefieldType(BFieldType::NONE), terrainType(ETerrainType::WRONG),
 	tacticsSide(0), tacticDistance(0)
 {
@@ -736,6 +631,416 @@ BattleInfo::BattleInfo()
 	setNodeType(BATTLE);
 }
 
+BattleInfo::~BattleInfo() = default;
+
+int32_t BattleInfo::getActiveStackID() const
+{
+	return activeStack;
+}
+
+TStacks BattleInfo::getStacksIf(TStackFilter predicate) const
+{
+	TStacks ret;
+	vstd::copy_if(stacks, std::back_inserter(ret), predicate);
+	return ret;
+}
+
+battle::Units BattleInfo::getUnitsIf(battle::UnitFilter predicate) const
+{
+	battle::Units ret;
+	vstd::copy_if(stacks, std::back_inserter(ret), predicate);
+	return ret;
+}
+
+
+BFieldType BattleInfo::getBattlefieldType() const
+{
+	return battlefieldType;
+}
+
+ETerrainType BattleInfo::getTerrainType() const
+{
+	return terrainType;
+}
+
+IBattleInfo::ObstacleCList BattleInfo::getAllObstacles() const
+{
+	ObstacleCList ret;
+
+	for(auto iter = obstacles.cbegin(); iter != obstacles.cend(); iter++)
+		ret.push_back(*iter);
+
+	return ret;
+}
+
+PlayerColor BattleInfo::getSidePlayer(ui8 side) const
+{
+	return sides.at(side).color;
+}
+
+const CArmedInstance * BattleInfo::getSideArmy(ui8 side) const
+{
+	return sides.at(side).armyObject;
+}
+
+const CGHeroInstance * BattleInfo::getSideHero(ui8 side) const
+{
+	return sides.at(side).hero;
+}
+
+ui8 BattleInfo::getTacticDist() const
+{
+	return tacticDistance;
+}
+
+ui8 BattleInfo::getTacticsSide() const
+{
+	return tacticsSide;
+}
+
+const CGTownInstance * BattleInfo::getDefendedTown() const
+{
+	return town;
+}
+
+si8 BattleInfo::getWallState(int partOfWall) const
+{
+	return si.wallState.at(partOfWall);
+}
+
+EGateState BattleInfo::getGateState() const
+{
+	return si.gateState;
+}
+
+uint32_t BattleInfo::getCastSpells(ui8 side) const
+{
+	return sides.at(side).castSpellsCount;
+}
+
+int32_t BattleInfo::getEnchanterCounter(ui8 side) const
+{
+	return sides.at(side).enchanterCounter;
+}
+
+const IBonusBearer * BattleInfo::asBearer() const
+{
+	return this;
+}
+
+int64_t BattleInfo::getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const
+{
+
+	if(damage.first != damage.second)
+	{
+		int64_t sum = 0;
+
+		auto howManyToAv = std::min<int32_t>(10, attackerCount);
+		auto rangeGen = rng.getInt64Range(damage.first, damage.second);
+
+		for(int32_t g = 0; g < howManyToAv; ++g)
+			sum += rangeGen();
+
+		return sum / howManyToAv;
+	}
+	else
+	{
+		return damage.first;
+	}
+}
+
+void BattleInfo::nextRound(int32_t roundNr)
+{
+	for(int i = 0; i < 2; ++i)
+	{
+		sides.at(i).castSpellsCount = 0;
+		vstd::amax(--sides.at(i).enchanterCounter, 0);
+	}
+	round = roundNr;
+
+	for(CStack * s : stacks)
+	{
+		// new turn effects
+		s->updateBonuses(Bonus::NTurns);
+
+		s->afterNewRound();
+	}
+
+	for(auto & obst : obstacles)
+		obst->battleTurnPassed();
+}
+
+void BattleInfo::nextTurn(uint32_t unitId)
+{
+	activeStack = unitId;
+
+	CStack * st = getStack(activeStack);
+
+	//remove bonuses that last until when stack gets new turn
+	st->popBonuses(Bonus::UntilGetsTurn);
+
+	st->afterGetsTurn();
+}
+
+void BattleInfo::addUnit(uint32_t id, const JsonNode & data)
+{
+	battle::UnitInfo info;
+	info.load(id, data);
+	CStackBasicDescriptor base(info.type, info.count);
+
+	PlayerColor owner = getSidePlayer(info.side);
+
+	auto ret = new CStack(&base, owner, info.id, info.side, SlotID::SUMMONED_SLOT_PLACEHOLDER);
+	ret->initialPosition = info.position;
+	stacks.push_back(ret);
+	ret->summoned = info.summoned;
+	ret->localInit(this);
+}
+
+void BattleInfo::moveUnit(uint32_t id, BattleHex destination)
+{
+	auto sta = getStack(id);
+
+	if(!sta)
+	{
+		logGlobal->error("Cannot find stack %d", id);
+		return;
+	}
+
+	for(auto & oi : obstacles)
+	{
+		if((oi->obstacleType == CObstacleInstance::SPELL_CREATED) && vstd::contains(oi->getAffectedTiles(), destination))
+		{
+			SpellCreatedObstacle * obstacle = dynamic_cast<SpellCreatedObstacle*>(oi.get());
+			assert(obstacle);
+			if(obstacle->casterSide != sta->unitSide() && obstacle->hidden)
+				obstacle->revealed = true;
+		}
+	}
+	sta->position = destination;
+}
+
+void BattleInfo::setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta)
+{
+	CStack * changedStack = getStack(id, false);
+	if(!changedStack)
+		throw std::runtime_error("Invalid unit id in BattleInfo update");
+
+	if(!changedStack->alive() && healthDelta > 0)
+	{
+		//checking if we resurrect a stack that is under a living stack
+		auto accessibility = getAccesibility();
+
+		if(!accessibility.accessible(changedStack->getPosition(), changedStack))
+		{
+			logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->getPosition().hex);
+			return; //position is already occupied
+		}
+	}
+
+	bool killed = (-healthDelta) >= changedStack->getAvailableHealth();//todo: check using alive state once rebirth will be handled separately
+
+	bool resurrected = !changedStack->alive() && healthDelta > 0;
+
+	//applying changes
+	changedStack->load(data);
+
+
+	if(healthDelta < 0)
+	{
+		changedStack->popBonuses(Bonus::UntilBeingAttacked);
+	}
+
+	resurrected = resurrected || (killed && changedStack->alive());
+
+	if(killed)
+	{
+		if(changedStack->cloneID >= 0)
+		{
+			//remove clone as well
+			CStack * clone = getStack(changedStack->cloneID);
+			if(clone)
+				clone->makeGhost();
+
+			changedStack->cloneID = -1;
+		}
+	}
+
+	if(resurrected || killed)
+	{
+		//removing all spells effects
+		auto selector = [](const Bonus * b)
+		{
+			//Special case: DISRUPTING_RAY is absolutely permanent
+			if(b->source == Bonus::SPELL_EFFECT)
+				return b->sid != SpellID::DISRUPTING_RAY;
+			else
+				return false;
+		};
+		changedStack->popBonuses(selector);
+	}
+
+	if(!changedStack->alive() && changedStack->isClone())
+	{
+		for(CStack * s : stacks)
+		{
+			if(s->cloneID == changedStack->unitId())
+				s->cloneID = -1;
+		}
+	}
+}
+
+void BattleInfo::removeUnit(uint32_t id)
+{
+	std::set<uint32_t> ids;
+	ids.insert(id);
+
+	while(!ids.empty())
+	{
+		auto toRemoveId = *ids.begin();
+		auto toRemove = getStack(toRemoveId, false);
+
+		if(!toRemove)
+		{
+			logGlobal->error("Cannot find stack %d", toRemoveId);
+			return;
+		}
+
+		if(!toRemove->ghost)
+		{
+			toRemove->onRemoved();
+			toRemove->detachFromAll();
+
+			//stack may be removed instantly (not being killed first)
+			//handle clone remove also here
+			if(toRemove->cloneID >= 0)
+			{
+				ids.insert(toRemove->cloneID);
+				toRemove->cloneID = -1;
+			}
+
+			//cleanup remaining clone links if any
+			for(auto s : stacks)
+			{
+				if(s->cloneID == toRemoveId)
+					s->cloneID = -1;
+			}
+		}
+
+		ids.erase(toRemoveId);
+	}
+}
+
+void BattleInfo::addUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
+{
+	CStack * sta = getStack(id, false);
+
+	if(!sta)
+	{
+		logGlobal->error("Cannot find stack %d", id);
+		return;
+	}
+
+	for(const Bonus & b : bonus)
+		addOrUpdateUnitBonus(sta, b, true);
+}
+
+void BattleInfo::updateUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
+{
+	CStack * sta = getStack(id, false);
+
+	if(!sta)
+	{
+		logGlobal->error("Cannot find stack %d", id);
+		return;
+	}
+
+	for(const Bonus & b : bonus)
+		addOrUpdateUnitBonus(sta, b, false);
+}
+
+void BattleInfo::removeUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
+{
+	CStack * sta = getStack(id, false);
+
+	if(!sta)
+	{
+		logGlobal->error("Cannot find stack %d", id);
+		return;
+	}
+
+	for(const Bonus & one : bonus)
+	{
+		auto selector = [one](const Bonus * b)
+		{
+			//compare everything but turnsRemain, limiter and propagator
+			return one.duration == b->duration
+			&& one.type == b->type
+			&& one.subtype == b->subtype
+			&& one.source == b->source
+			&& one.val == b->val
+			&& one.sid == b->sid
+			&& one.valType == b->valType
+			&& one.additionalInfo == b->additionalInfo
+			&& one.effectRange == b->effectRange
+			&& one.description == b->description;
+		};
+		sta->popBonuses(selector);
+	}
+}
+
+uint32_t BattleInfo::nextUnitId() const
+{
+	return stacks.size();
+}
+
+void BattleInfo::addOrUpdateUnitBonus(CStack * sta, const Bonus & value, bool forceAdd)
+{
+	if(forceAdd || !sta->hasBonus(Selector::source(Bonus::SPELL_EFFECT, value.sid).And(Selector::typeSubtype(value.type, value.subtype))))
+	{
+		//no such effect or cumulative - add new
+		logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), value.Description());
+		sta->addNewBonus(std::make_shared<Bonus>(value));
+	}
+	else
+	{
+		logBonus->trace("%s updated bonus: %s", sta->nodeName(), value.Description());
+
+		for(auto stackBonus : sta->getExportedBonusList()) //TODO: optimize
+		{
+			if(stackBonus->source == value.source && stackBonus->sid == value.sid && stackBonus->type == value.type && stackBonus->subtype == value.subtype)
+			{
+				stackBonus->turnsRemain = std::max(stackBonus->turnsRemain, value.turnsRemain);
+			}
+		}
+		CBonusSystemNode::treeHasChanged();
+	}
+}
+
+void BattleInfo::setWallState(int partOfWall, si8 state)
+{
+	si.wallState.at(partOfWall) = state;
+}
+
+void BattleInfo::addObstacle(const ObstacleChanges & changes)
+{
+	std::shared_ptr<SpellCreatedObstacle> obstacle = std::make_shared<SpellCreatedObstacle>();
+	obstacle->fromInfo(changes);
+	obstacles.push_back(obstacle);
+}
+
+void BattleInfo::removeObstacle(uint32_t id)
+{
+	for(int i=0; i < obstacles.size(); ++i)
+	{
+		if(obstacles[i]->uniqueID == id) //remove this obstacle
+		{
+			obstacles.erase(obstacles.begin() + i);
+			break;
+		}
+	}
+}
+
 CArmedInstance * BattleInfo::battleGetArmyObject(ui8 side) const
 {
 	return const_cast<CArmedInstance*>(CBattleInfoEssentials::battleGetArmyObject(side));
@@ -746,30 +1051,29 @@ CGHeroInstance * BattleInfo::battleGetFightingHero(ui8 side) const
 	return const_cast<CGHeroInstance*>(CBattleInfoEssentials::battleGetFightingHero(side));
 }
 
-
-bool CMP_stack::operator()(const CStack* a, const CStack* b)
+bool CMP_stack::operator()(const battle::Unit * a, const battle::Unit * b)
 {
 	switch(phase)
 	{
 	case 0: //catapult moves after turrets
-		return a->getCreature()->idNumber > b->getCreature()->idNumber; //catapult is 145 and turrets are 149
+		return a->creatureIndex() > b->creatureIndex(); //catapult is 145 and turrets are 149
 	case 1: //fastest first, upper slot first
 		{
-			int as = a->Speed(turn), bs = b->Speed(turn);
+			int as = a->getInitiative(turn), bs = b->getInitiative(turn);
 			if(as != bs)
 				return as > bs;
 			else
-				return a->slot < b->slot;
+				return a->unitSlot() < b->unitSlot(); //FIXME: what about summoned stacks?
 		}
 	case 2: //fastest last, upper slot first
 		//TODO: should be replaced with order of receiving morale!
 	case 3: //fastest last, upper slot first
 		{
-			int as = a->Speed(turn), bs = b->Speed(turn);
+			int as = a->getInitiative(turn), bs = b->getInitiative(turn);
 			if(as != bs)
 				return as < bs;
 			else
-				return a->slot < b->slot;
+				return a->unitSlot() < b->unitSlot();
 		}
 	default:
 		assert(0);

Some files were not shown because too many files changed in this diff